package hcsshim import ( "encoding/json" "runtime" "sync" "syscall" "time" "github.com/Sirupsen/logrus" ) var ( defaultTimeout = time.Minute * 4 ) const ( pendingUpdatesQuery = `{ "PropertyTypes" : ["PendingUpdates"]}` statisticsQuery = `{ "PropertyTypes" : ["Statistics"]}` processListQuery = `{ "PropertyTypes" : ["ProcessList"]}` ) 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"` IsDummy bool `json:",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"` } // 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"` } // CreateContainer creates a new container with the given configuration but does not start it. func CreateContainer(id string, c *ContainerConfig) (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) 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 { return nil, makeContainerError(container, operation, "", err) } } err = processAsyncHcsResult(createError, resultp, container.callbackNumber, hcsNotificationSystemCreateCompleted, &defaultTimeout) if err != nil { return nil, makeContainerError(container, operation, configuration, err) } logrus.Debugf(title+" succeeded id=%s handle=%d", id, container.handle) runtime.SetFinalizer(container, closeContainer) return container, nil } // 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) runtime.SetFinalizer(container, closeContainer) 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 } // 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=%s", container.id, process.processID) runtime.SetFinalizer(process, closeProcess) 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) runtime.SetFinalizer(process, closeProcess) 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 runtime.SetFinalizer(container, nil) logrus.Debugf(title+" succeeded id=%s", container.id) return nil } // closeContainer wraps container.Close for use by a finalizer func closeContainer(container *container) { container.Close() } 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 }