| package hcsshim |
| |
| import ( |
| "encoding/json" |
| "fmt" |
| "os" |
| "sync" |
| "syscall" |
| "time" |
| |
| "github.com/sirupsen/logrus" |
| ) |
| |
| var ( |
| defaultTimeout = time.Minute * 4 |
| ) |
| |
| const ( |
| pendingUpdatesQuery = `{ "PropertyTypes" : ["PendingUpdates"]}` |
| statisticsQuery = `{ "PropertyTypes" : ["Statistics"]}` |
| processListQuery = `{ "PropertyTypes" : ["ProcessList"]}` |
| mappedVirtualDiskQuery = `{ "PropertyTypes" : ["MappedVirtualDisk"]}` |
| ) |
| |
| type container struct { |
| handleLock sync.RWMutex |
| handle hcsSystem |
| id string |
| callbackNumber uintptr |
| } |
| |
| // ContainerProperties holds the properties for a container and the processes running in that container |
| type ContainerProperties struct { |
| ID string `json:"Id"` |
| Name string |
| SystemType string |
| Owner string |
| SiloGUID string `json:"SiloGuid,omitempty"` |
| RuntimeID string `json:"RuntimeId,omitempty"` |
| IsRuntimeTemplate bool `json:",omitempty"` |
| RuntimeImagePath string `json:",omitempty"` |
| Stopped bool `json:",omitempty"` |
| ExitType string `json:",omitempty"` |
| AreUpdatesPending bool `json:",omitempty"` |
| ObRoot string `json:",omitempty"` |
| Statistics Statistics `json:",omitempty"` |
| ProcessList []ProcessListItem `json:",omitempty"` |
| MappedVirtualDiskControllers map[int]MappedVirtualDiskController `json:",omitempty"` |
| } |
| |
| // MemoryStats holds the memory statistics for a container |
| type MemoryStats struct { |
| UsageCommitBytes uint64 `json:"MemoryUsageCommitBytes,omitempty"` |
| UsageCommitPeakBytes uint64 `json:"MemoryUsageCommitPeakBytes,omitempty"` |
| UsagePrivateWorkingSetBytes uint64 `json:"MemoryUsagePrivateWorkingSetBytes,omitempty"` |
| } |
| |
| // ProcessorStats holds the processor statistics for a container |
| type ProcessorStats struct { |
| TotalRuntime100ns uint64 `json:",omitempty"` |
| RuntimeUser100ns uint64 `json:",omitempty"` |
| RuntimeKernel100ns uint64 `json:",omitempty"` |
| } |
| |
| // StorageStats holds the storage statistics for a container |
| type StorageStats struct { |
| ReadCountNormalized uint64 `json:",omitempty"` |
| ReadSizeBytes uint64 `json:",omitempty"` |
| WriteCountNormalized uint64 `json:",omitempty"` |
| WriteSizeBytes uint64 `json:",omitempty"` |
| } |
| |
| // NetworkStats holds the network statistics for a container |
| type NetworkStats struct { |
| BytesReceived uint64 `json:",omitempty"` |
| BytesSent uint64 `json:",omitempty"` |
| PacketsReceived uint64 `json:",omitempty"` |
| PacketsSent uint64 `json:",omitempty"` |
| DroppedPacketsIncoming uint64 `json:",omitempty"` |
| DroppedPacketsOutgoing uint64 `json:",omitempty"` |
| EndpointId string `json:",omitempty"` |
| InstanceId string `json:",omitempty"` |
| } |
| |
| // Statistics is the structure returned by a statistics call on a container |
| type Statistics struct { |
| Timestamp time.Time `json:",omitempty"` |
| ContainerStartTime time.Time `json:",omitempty"` |
| Uptime100ns uint64 `json:",omitempty"` |
| Memory MemoryStats `json:",omitempty"` |
| Processor ProcessorStats `json:",omitempty"` |
| Storage StorageStats `json:",omitempty"` |
| Network []NetworkStats `json:",omitempty"` |
| } |
| |
| // ProcessList is the structure of an item returned by a ProcessList call on a container |
| type ProcessListItem struct { |
| CreateTimestamp time.Time `json:",omitempty"` |
| ImageName string `json:",omitempty"` |
| KernelTime100ns uint64 `json:",omitempty"` |
| MemoryCommitBytes uint64 `json:",omitempty"` |
| MemoryWorkingSetPrivateBytes uint64 `json:",omitempty"` |
| MemoryWorkingSetSharedBytes uint64 `json:",omitempty"` |
| ProcessId uint32 `json:",omitempty"` |
| UserTime100ns uint64 `json:",omitempty"` |
| } |
| |
| // MappedVirtualDiskController is the structure of an item returned by a MappedVirtualDiskList call on a container |
| type MappedVirtualDiskController struct { |
| MappedVirtualDisks map[int]MappedVirtualDisk `json:",omitempty"` |
| } |
| |
| // Type of Request Support in ModifySystem |
| type RequestType string |
| |
| // Type of Resource Support in ModifySystem |
| type ResourceType string |
| |
| // RequestType const |
| const ( |
| Add RequestType = "Add" |
| Remove RequestType = "Remove" |
| Network ResourceType = "Network" |
| ) |
| |
| // ResourceModificationRequestResponse is the structure used to send request to the container to modify the system |
| // Supported resource types are Network and Request Types are Add/Remove |
| type ResourceModificationRequestResponse struct { |
| Resource ResourceType `json:"ResourceType"` |
| Data interface{} `json:"Settings"` |
| Request RequestType `json:"RequestType,omitempty"` |
| } |
| |
| // createContainerAdditionalJSON is read from the environment at initialisation |
| // time. It allows an environment variable to define additional JSON which |
| // is merged in the CreateContainer call to HCS. |
| var createContainerAdditionalJSON string |
| |
| func init() { |
| createContainerAdditionalJSON = os.Getenv("HCSSHIM_CREATECONTAINER_ADDITIONALJSON") |
| } |
| |
| // CreateContainer creates a new container with the given configuration but does not start it. |
| func CreateContainer(id string, c *ContainerConfig) (Container, error) { |
| return createContainerWithJSON(id, c, "") |
| } |
| |
| // CreateContainerWithJSON creates a new container with the given configuration but does not start it. |
| // It is identical to CreateContainer except that optional additional JSON can be merged before passing to HCS. |
| func CreateContainerWithJSON(id string, c *ContainerConfig, additionalJSON string) (Container, error) { |
| return createContainerWithJSON(id, c, additionalJSON) |
| } |
| |
| func createContainerWithJSON(id string, c *ContainerConfig, additionalJSON string) (Container, error) { |
| operation := "CreateContainer" |
| title := "HCSShim::" + operation |
| |
| container := &container{ |
| id: id, |
| } |
| |
| configurationb, err := json.Marshal(c) |
| if err != nil { |
| return nil, err |
| } |
| |
| configuration := string(configurationb) |
| logrus.Debugf(title+" id=%s config=%s", id, configuration) |
| |
| // Merge any additional JSON. Priority is given to what is passed in explicitly, |
| // falling back to what's set in the environment. |
| if additionalJSON == "" && createContainerAdditionalJSON != "" { |
| additionalJSON = createContainerAdditionalJSON |
| } |
| if additionalJSON != "" { |
| configurationMap := map[string]interface{}{} |
| if err := json.Unmarshal([]byte(configuration), &configurationMap); err != nil { |
| return nil, fmt.Errorf("failed to unmarshal %s: %s", configuration, err) |
| } |
| |
| additionalMap := map[string]interface{}{} |
| if err := json.Unmarshal([]byte(additionalJSON), &additionalMap); err != nil { |
| return nil, fmt.Errorf("failed to unmarshal %s: %s", additionalJSON, err) |
| } |
| |
| mergedMap := mergeMaps(additionalMap, configurationMap) |
| mergedJSON, err := json.Marshal(mergedMap) |
| if err != nil { |
| return nil, fmt.Errorf("failed to marshal merged configuration map %+v: %s", mergedMap, err) |
| } |
| |
| configuration = string(mergedJSON) |
| logrus.Debugf(title+" id=%s merged config=%s", id, configuration) |
| } |
| |
| var ( |
| resultp *uint16 |
| identity syscall.Handle |
| ) |
| createError := hcsCreateComputeSystem(id, configuration, identity, &container.handle, &resultp) |
| |
| if createError == nil || IsPending(createError) { |
| if err := container.registerCallback(); err != nil { |
| // Terminate the container if it still exists. We're okay to ignore a failure here. |
| container.Terminate() |
| return nil, makeContainerError(container, operation, "", err) |
| } |
| } |
| |
| err = processAsyncHcsResult(createError, resultp, container.callbackNumber, hcsNotificationSystemCreateCompleted, &defaultTimeout) |
| if err != nil { |
| if err == ErrTimeout { |
| // Terminate the container if it still exists. We're okay to ignore a failure here. |
| container.Terminate() |
| } |
| return nil, makeContainerError(container, operation, configuration, err) |
| } |
| |
| logrus.Debugf(title+" succeeded id=%s handle=%d", id, container.handle) |
| return container, nil |
| } |
| |
| // mergeMaps recursively merges map `fromMap` into map `ToMap`. Any pre-existing values |
| // in ToMap are overwritten. Values in fromMap are added to ToMap. |
| // From http://stackoverflow.com/questions/40491438/merging-two-json-strings-in-golang |
| func mergeMaps(fromMap, ToMap interface{}) interface{} { |
| switch fromMap := fromMap.(type) { |
| case map[string]interface{}: |
| ToMap, ok := ToMap.(map[string]interface{}) |
| if !ok { |
| return fromMap |
| } |
| for keyToMap, valueToMap := range ToMap { |
| if valueFromMap, ok := fromMap[keyToMap]; ok { |
| fromMap[keyToMap] = mergeMaps(valueFromMap, valueToMap) |
| } else { |
| fromMap[keyToMap] = valueToMap |
| } |
| } |
| case nil: |
| // merge(nil, map[string]interface{...}) -> map[string]interface{...} |
| ToMap, ok := ToMap.(map[string]interface{}) |
| if ok { |
| return ToMap |
| } |
| } |
| return fromMap |
| } |
| |
| // OpenContainer opens an existing container by ID. |
| func OpenContainer(id string) (Container, error) { |
| operation := "OpenContainer" |
| title := "HCSShim::" + operation |
| logrus.Debugf(title+" id=%s", id) |
| |
| container := &container{ |
| id: id, |
| } |
| |
| var ( |
| handle hcsSystem |
| resultp *uint16 |
| ) |
| err := hcsOpenComputeSystem(id, &handle, &resultp) |
| err = processHcsResult(err, resultp) |
| if err != nil { |
| return nil, makeContainerError(container, operation, "", err) |
| } |
| |
| container.handle = handle |
| |
| if err := container.registerCallback(); err != nil { |
| return nil, makeContainerError(container, operation, "", err) |
| } |
| |
| logrus.Debugf(title+" succeeded id=%s handle=%d", id, handle) |
| return container, nil |
| } |
| |
| // GetContainers gets a list of the containers on the system that match the query |
| func GetContainers(q ComputeSystemQuery) ([]ContainerProperties, error) { |
| operation := "GetContainers" |
| title := "HCSShim::" + operation |
| |
| queryb, err := json.Marshal(q) |
| if err != nil { |
| return nil, err |
| } |
| |
| query := string(queryb) |
| logrus.Debugf(title+" query=%s", query) |
| |
| var ( |
| resultp *uint16 |
| computeSystemsp *uint16 |
| ) |
| err = hcsEnumerateComputeSystems(query, &computeSystemsp, &resultp) |
| err = processHcsResult(err, resultp) |
| if err != nil { |
| return nil, err |
| } |
| |
| if computeSystemsp == nil { |
| return nil, ErrUnexpectedValue |
| } |
| computeSystemsRaw := convertAndFreeCoTaskMemBytes(computeSystemsp) |
| computeSystems := []ContainerProperties{} |
| if err := json.Unmarshal(computeSystemsRaw, &computeSystems); err != nil { |
| return nil, err |
| } |
| |
| logrus.Debugf(title + " succeeded") |
| return computeSystems, nil |
| } |
| |
| // Start synchronously starts the container. |
| func (container *container) Start() error { |
| container.handleLock.RLock() |
| defer container.handleLock.RUnlock() |
| operation := "Start" |
| title := "HCSShim::Container::" + operation |
| logrus.Debugf(title+" id=%s", container.id) |
| |
| if container.handle == 0 { |
| return makeContainerError(container, operation, "", ErrAlreadyClosed) |
| } |
| |
| var resultp *uint16 |
| err := hcsStartComputeSystem(container.handle, "", &resultp) |
| err = processAsyncHcsResult(err, resultp, container.callbackNumber, hcsNotificationSystemStartCompleted, &defaultTimeout) |
| if err != nil { |
| return makeContainerError(container, operation, "", err) |
| } |
| |
| logrus.Debugf(title+" succeeded id=%s", container.id) |
| return nil |
| } |
| |
| // Shutdown requests a container shutdown, if IsPending() on the error returned is true, |
| // it may not actually be shut down until Wait() succeeds. |
| func (container *container) Shutdown() error { |
| container.handleLock.RLock() |
| defer container.handleLock.RUnlock() |
| operation := "Shutdown" |
| title := "HCSShim::Container::" + operation |
| logrus.Debugf(title+" id=%s", container.id) |
| |
| if container.handle == 0 { |
| return makeContainerError(container, operation, "", ErrAlreadyClosed) |
| } |
| |
| var resultp *uint16 |
| err := hcsShutdownComputeSystem(container.handle, "", &resultp) |
| err = processHcsResult(err, resultp) |
| if err != nil { |
| return makeContainerError(container, operation, "", err) |
| } |
| |
| logrus.Debugf(title+" succeeded id=%s", container.id) |
| return nil |
| } |
| |
| // Terminate requests a container terminate, if IsPending() on the error returned is true, |
| // it may not actually be shut down until Wait() succeeds. |
| func (container *container) Terminate() error { |
| container.handleLock.RLock() |
| defer container.handleLock.RUnlock() |
| operation := "Terminate" |
| title := "HCSShim::Container::" + operation |
| logrus.Debugf(title+" id=%s", container.id) |
| |
| if container.handle == 0 { |
| return makeContainerError(container, operation, "", ErrAlreadyClosed) |
| } |
| |
| var resultp *uint16 |
| err := hcsTerminateComputeSystem(container.handle, "", &resultp) |
| err = processHcsResult(err, resultp) |
| if err != nil { |
| return makeContainerError(container, operation, "", err) |
| } |
| |
| logrus.Debugf(title+" succeeded id=%s", container.id) |
| return nil |
| } |
| |
| // Wait synchronously waits for the container to shutdown or terminate. |
| func (container *container) Wait() error { |
| operation := "Wait" |
| title := "HCSShim::Container::" + operation |
| logrus.Debugf(title+" id=%s", container.id) |
| |
| err := waitForNotification(container.callbackNumber, hcsNotificationSystemExited, nil) |
| if err != nil { |
| return makeContainerError(container, operation, "", err) |
| } |
| |
| logrus.Debugf(title+" succeeded id=%s", container.id) |
| return nil |
| } |
| |
| // WaitTimeout synchronously waits for the container to terminate or the duration to elapse. |
| // If the timeout expires, IsTimeout(err) == true |
| func (container *container) WaitTimeout(timeout time.Duration) error { |
| operation := "WaitTimeout" |
| title := "HCSShim::Container::" + operation |
| logrus.Debugf(title+" id=%s", container.id) |
| |
| err := waitForNotification(container.callbackNumber, hcsNotificationSystemExited, &timeout) |
| if err != nil { |
| return makeContainerError(container, operation, "", err) |
| } |
| |
| logrus.Debugf(title+" succeeded id=%s", container.id) |
| return nil |
| } |
| |
| func (container *container) properties(query string) (*ContainerProperties, error) { |
| var ( |
| resultp *uint16 |
| propertiesp *uint16 |
| ) |
| err := hcsGetComputeSystemProperties(container.handle, query, &propertiesp, &resultp) |
| err = processHcsResult(err, resultp) |
| if err != nil { |
| return nil, err |
| } |
| |
| if propertiesp == nil { |
| return nil, ErrUnexpectedValue |
| } |
| propertiesRaw := convertAndFreeCoTaskMemBytes(propertiesp) |
| properties := &ContainerProperties{} |
| if err := json.Unmarshal(propertiesRaw, properties); err != nil { |
| return nil, err |
| } |
| return properties, nil |
| } |
| |
| // HasPendingUpdates returns true if the container has updates pending to install |
| func (container *container) HasPendingUpdates() (bool, error) { |
| container.handleLock.RLock() |
| defer container.handleLock.RUnlock() |
| operation := "HasPendingUpdates" |
| title := "HCSShim::Container::" + operation |
| logrus.Debugf(title+" id=%s", container.id) |
| |
| if container.handle == 0 { |
| return false, makeContainerError(container, operation, "", ErrAlreadyClosed) |
| } |
| |
| properties, err := container.properties(pendingUpdatesQuery) |
| if err != nil { |
| return false, makeContainerError(container, operation, "", err) |
| } |
| |
| logrus.Debugf(title+" succeeded id=%s", container.id) |
| return properties.AreUpdatesPending, nil |
| } |
| |
| // Statistics returns statistics for the container |
| func (container *container) Statistics() (Statistics, error) { |
| container.handleLock.RLock() |
| defer container.handleLock.RUnlock() |
| operation := "Statistics" |
| title := "HCSShim::Container::" + operation |
| logrus.Debugf(title+" id=%s", container.id) |
| |
| if container.handle == 0 { |
| return Statistics{}, makeContainerError(container, operation, "", ErrAlreadyClosed) |
| } |
| |
| properties, err := container.properties(statisticsQuery) |
| if err != nil { |
| return Statistics{}, makeContainerError(container, operation, "", err) |
| } |
| |
| logrus.Debugf(title+" succeeded id=%s", container.id) |
| return properties.Statistics, nil |
| } |
| |
| // ProcessList returns an array of ProcessListItems for the container |
| func (container *container) ProcessList() ([]ProcessListItem, error) { |
| container.handleLock.RLock() |
| defer container.handleLock.RUnlock() |
| operation := "ProcessList" |
| title := "HCSShim::Container::" + operation |
| logrus.Debugf(title+" id=%s", container.id) |
| |
| if container.handle == 0 { |
| return nil, makeContainerError(container, operation, "", ErrAlreadyClosed) |
| } |
| |
| properties, err := container.properties(processListQuery) |
| if err != nil { |
| return nil, makeContainerError(container, operation, "", err) |
| } |
| |
| logrus.Debugf(title+" succeeded id=%s", container.id) |
| return properties.ProcessList, nil |
| } |
| |
| // MappedVirtualDisks returns a map of the controllers and the disks mapped |
| // to a container. |
| // |
| // Example of JSON returned by the query. |
| //{ |
| // "Id":"1126e8d7d279c707a666972a15976371d365eaf622c02cea2c442b84f6f550a3_svm", |
| // "SystemType":"Container", |
| // "RuntimeOsType":"Linux", |
| // "RuntimeId":"00000000-0000-0000-0000-000000000000", |
| // "State":"Running", |
| // "MappedVirtualDiskControllers":{ |
| // "0":{ |
| // "MappedVirtualDisks":{ |
| // "2":{ |
| // "HostPath":"C:\\lcow\\lcow\\scratch\\1126e8d7d279c707a666972a15976371d365eaf622c02cea2c442b84f6f550a3.vhdx", |
| // "ContainerPath":"/mnt/gcs/LinuxServiceVM/scratch", |
| // "Lun":2, |
| // "CreateInUtilityVM":true |
| // }, |
| // "3":{ |
| // "HostPath":"C:\\lcow\\lcow\\1126e8d7d279c707a666972a15976371d365eaf622c02cea2c442b84f6f550a3\\sandbox.vhdx", |
| // "Lun":3, |
| // "CreateInUtilityVM":true, |
| // "AttachOnly":true |
| // } |
| // } |
| // } |
| // } |
| //} |
| func (container *container) MappedVirtualDisks() (map[int]MappedVirtualDiskController, error) { |
| container.handleLock.RLock() |
| defer container.handleLock.RUnlock() |
| operation := "MappedVirtualDiskList" |
| title := "HCSShim::Container::" + operation |
| logrus.Debugf(title+" id=%s", container.id) |
| |
| if container.handle == 0 { |
| return nil, makeContainerError(container, operation, "", ErrAlreadyClosed) |
| } |
| |
| properties, err := container.properties(mappedVirtualDiskQuery) |
| if err != nil { |
| return nil, makeContainerError(container, operation, "", err) |
| } |
| |
| logrus.Debugf(title+" succeeded id=%s", container.id) |
| return properties.MappedVirtualDiskControllers, nil |
| } |
| |
| // Pause pauses the execution of the container. This feature is not enabled in TP5. |
| func (container *container) Pause() error { |
| container.handleLock.RLock() |
| defer container.handleLock.RUnlock() |
| operation := "Pause" |
| title := "HCSShim::Container::" + operation |
| logrus.Debugf(title+" id=%s", container.id) |
| |
| if container.handle == 0 { |
| return makeContainerError(container, operation, "", ErrAlreadyClosed) |
| } |
| |
| var resultp *uint16 |
| err := hcsPauseComputeSystem(container.handle, "", &resultp) |
| err = processAsyncHcsResult(err, resultp, container.callbackNumber, hcsNotificationSystemPauseCompleted, &defaultTimeout) |
| if err != nil { |
| return makeContainerError(container, operation, "", err) |
| } |
| |
| logrus.Debugf(title+" succeeded id=%s", container.id) |
| return nil |
| } |
| |
| // Resume resumes the execution of the container. This feature is not enabled in TP5. |
| func (container *container) Resume() error { |
| container.handleLock.RLock() |
| defer container.handleLock.RUnlock() |
| operation := "Resume" |
| title := "HCSShim::Container::" + operation |
| logrus.Debugf(title+" id=%s", container.id) |
| |
| if container.handle == 0 { |
| return makeContainerError(container, operation, "", ErrAlreadyClosed) |
| } |
| |
| var resultp *uint16 |
| err := hcsResumeComputeSystem(container.handle, "", &resultp) |
| err = processAsyncHcsResult(err, resultp, container.callbackNumber, hcsNotificationSystemResumeCompleted, &defaultTimeout) |
| if err != nil { |
| return makeContainerError(container, operation, "", err) |
| } |
| |
| logrus.Debugf(title+" succeeded id=%s", container.id) |
| return nil |
| } |
| |
| // CreateProcess launches a new process within the container. |
| func (container *container) CreateProcess(c *ProcessConfig) (Process, error) { |
| container.handleLock.RLock() |
| defer container.handleLock.RUnlock() |
| operation := "CreateProcess" |
| title := "HCSShim::Container::" + operation |
| var ( |
| processInfo hcsProcessInformation |
| processHandle hcsProcess |
| resultp *uint16 |
| ) |
| |
| if container.handle == 0 { |
| return nil, makeContainerError(container, operation, "", ErrAlreadyClosed) |
| } |
| |
| // If we are not emulating a console, ignore any console size passed to us |
| if !c.EmulateConsole { |
| c.ConsoleSize[0] = 0 |
| c.ConsoleSize[1] = 0 |
| } |
| |
| configurationb, err := json.Marshal(c) |
| if err != nil { |
| return nil, makeContainerError(container, operation, "", err) |
| } |
| |
| configuration := string(configurationb) |
| logrus.Debugf(title+" id=%s config=%s", container.id, configuration) |
| |
| err = hcsCreateProcess(container.handle, configuration, &processInfo, &processHandle, &resultp) |
| err = processHcsResult(err, resultp) |
| if err != nil { |
| return nil, makeContainerError(container, operation, configuration, err) |
| } |
| |
| process := &process{ |
| handle: processHandle, |
| processID: int(processInfo.ProcessId), |
| container: container, |
| cachedPipes: &cachedPipes{ |
| stdIn: processInfo.StdInput, |
| stdOut: processInfo.StdOutput, |
| stdErr: processInfo.StdError, |
| }, |
| } |
| |
| if err := process.registerCallback(); err != nil { |
| return nil, makeContainerError(container, operation, "", err) |
| } |
| |
| logrus.Debugf(title+" succeeded id=%s processid=%d", container.id, process.processID) |
| return process, nil |
| } |
| |
| // OpenProcess gets an interface to an existing process within the container. |
| func (container *container) OpenProcess(pid int) (Process, error) { |
| container.handleLock.RLock() |
| defer container.handleLock.RUnlock() |
| operation := "OpenProcess" |
| title := "HCSShim::Container::" + operation |
| logrus.Debugf(title+" id=%s, processid=%d", container.id, pid) |
| var ( |
| processHandle hcsProcess |
| resultp *uint16 |
| ) |
| |
| if container.handle == 0 { |
| return nil, makeContainerError(container, operation, "", ErrAlreadyClosed) |
| } |
| |
| err := hcsOpenProcess(container.handle, uint32(pid), &processHandle, &resultp) |
| err = processHcsResult(err, resultp) |
| if err != nil { |
| return nil, makeContainerError(container, operation, "", err) |
| } |
| |
| process := &process{ |
| handle: processHandle, |
| processID: pid, |
| container: container, |
| } |
| |
| if err := process.registerCallback(); err != nil { |
| return nil, makeContainerError(container, operation, "", err) |
| } |
| |
| logrus.Debugf(title+" succeeded id=%s processid=%s", container.id, process.processID) |
| return process, nil |
| } |
| |
| // Close cleans up any state associated with the container but does not terminate or wait for it. |
| func (container *container) Close() error { |
| container.handleLock.Lock() |
| defer container.handleLock.Unlock() |
| operation := "Close" |
| title := "HCSShim::Container::" + operation |
| logrus.Debugf(title+" id=%s", container.id) |
| |
| // Don't double free this |
| if container.handle == 0 { |
| return nil |
| } |
| |
| if err := container.unregisterCallback(); err != nil { |
| return makeContainerError(container, operation, "", err) |
| } |
| |
| if err := hcsCloseComputeSystem(container.handle); err != nil { |
| return makeContainerError(container, operation, "", err) |
| } |
| |
| container.handle = 0 |
| |
| logrus.Debugf(title+" succeeded id=%s", container.id) |
| return nil |
| } |
| |
| func (container *container) registerCallback() error { |
| context := ¬ifcationWatcherContext{ |
| channels: newChannels(), |
| } |
| |
| callbackMapLock.Lock() |
| callbackNumber := nextCallback |
| nextCallback++ |
| callbackMap[callbackNumber] = context |
| callbackMapLock.Unlock() |
| |
| var callbackHandle hcsCallback |
| err := hcsRegisterComputeSystemCallback(container.handle, notificationWatcherCallback, callbackNumber, &callbackHandle) |
| if err != nil { |
| return err |
| } |
| context.handle = callbackHandle |
| container.callbackNumber = callbackNumber |
| |
| return nil |
| } |
| |
| func (container *container) unregisterCallback() error { |
| callbackNumber := container.callbackNumber |
| |
| callbackMapLock.RLock() |
| context := callbackMap[callbackNumber] |
| callbackMapLock.RUnlock() |
| |
| if context == nil { |
| return nil |
| } |
| |
| handle := context.handle |
| |
| if handle == 0 { |
| return nil |
| } |
| |
| // hcsUnregisterComputeSystemCallback has its own syncronization |
| // to wait for all callbacks to complete. We must NOT hold the callbackMapLock. |
| err := hcsUnregisterComputeSystemCallback(handle) |
| if err != nil { |
| return err |
| } |
| |
| closeChannels(context.channels) |
| |
| callbackMapLock.Lock() |
| callbackMap[callbackNumber] = nil |
| callbackMapLock.Unlock() |
| |
| handle = 0 |
| |
| return nil |
| } |
| |
| // Modifies the System by sending a request to HCS |
| func (container *container) Modify(config *ResourceModificationRequestResponse) error { |
| container.handleLock.RLock() |
| defer container.handleLock.RUnlock() |
| operation := "Modify" |
| title := "HCSShim::Container::" + operation |
| |
| if container.handle == 0 { |
| return makeContainerError(container, operation, "", ErrAlreadyClosed) |
| } |
| |
| requestJSON, err := json.Marshal(config) |
| if err != nil { |
| return err |
| } |
| |
| requestString := string(requestJSON) |
| logrus.Debugf(title+" id=%s request=%s", container.id, requestString) |
| |
| var resultp *uint16 |
| err = hcsModifyComputeSystem(container.handle, requestString, &resultp) |
| err = processHcsResult(err, resultp) |
| if err != nil { |
| return makeContainerError(container, operation, "", err) |
| } |
| logrus.Debugf(title+" succeeded id=%s", container.id) |
| return nil |
| } |