mirror of
https://github.com/moby/moby.git
synced 2026-07-14 11:31:53 +00:00
This patch updates all dependencies to match what is used in moby/moby. Making the dependencies match what is used in that repository makes sure we test with the same version as libnetwork is later built with in moby. This also gets rid of some temporary forks that were needed during the migration of Sirupsen/logrus to sirupsen/logrus. Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
801 lines
25 KiB
Go
801 lines
25 KiB
Go
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
|
|
}
|