11 "github.com/Sirupsen/logrus"
15 defaultTimeout = time.Minute * 4
19 pendingUpdatesQuery = `{ "PropertyTypes" : ["PendingUpdates"]}`
20 statisticsQuery = `{ "PropertyTypes" : ["Statistics"]}`
21 processListQuery = `{ "PropertyTypes" : ["ProcessList"]}`
24 type container struct {
25 handleLock sync.RWMutex
28 callbackNumber uintptr
31 // ContainerProperties holds the properties for a container and the processes running in that container
32 type ContainerProperties struct {
37 SiloGUID string `json:"SiloGuid,omitempty"`
38 RuntimeID string `json:"RuntimeId,omitempty"`
39 IsRuntimeTemplate bool `json:",omitempty"`
40 RuntimeImagePath string `json:",omitempty"`
41 Stopped bool `json:",omitempty"`
42 ExitType string `json:",omitempty"`
43 AreUpdatesPending bool `json:",omitempty"`
44 ObRoot string `json:",omitempty"`
45 Statistics Statistics `json:",omitempty"`
46 ProcessList []ProcessListItem `json:",omitempty"`
49 // MemoryStats holds the memory statistics for a container
50 type MemoryStats struct {
51 UsageCommitBytes uint64 `json:"MemoryUsageCommitBytes,omitempty"`
52 UsageCommitPeakBytes uint64 `json:"MemoryUsageCommitPeakBytes,omitempty"`
53 UsagePrivateWorkingSetBytes uint64 `json:"MemoryUsagePrivateWorkingSetBytes,omitempty"`
56 // ProcessorStats holds the processor statistics for a container
57 type ProcessorStats struct {
58 TotalRuntime100ns uint64 `json:",omitempty"`
59 RuntimeUser100ns uint64 `json:",omitempty"`
60 RuntimeKernel100ns uint64 `json:",omitempty"`
63 // StorageStats holds the storage statistics for a container
64 type StorageStats struct {
65 ReadCountNormalized uint64 `json:",omitempty"`
66 ReadSizeBytes uint64 `json:",omitempty"`
67 WriteCountNormalized uint64 `json:",omitempty"`
68 WriteSizeBytes uint64 `json:",omitempty"`
71 // NetworkStats holds the network statistics for a container
72 type NetworkStats struct {
73 BytesReceived uint64 `json:",omitempty"`
74 BytesSent uint64 `json:",omitempty"`
75 PacketsReceived uint64 `json:",omitempty"`
76 PacketsSent uint64 `json:",omitempty"`
77 DroppedPacketsIncoming uint64 `json:",omitempty"`
78 DroppedPacketsOutgoing uint64 `json:",omitempty"`
79 EndpointId string `json:",omitempty"`
80 InstanceId string `json:",omitempty"`
83 // Statistics is the structure returned by a statistics call on a container
84 type Statistics struct {
85 Timestamp time.Time `json:",omitempty"`
86 ContainerStartTime time.Time `json:",omitempty"`
87 Uptime100ns uint64 `json:",omitempty"`
88 Memory MemoryStats `json:",omitempty"`
89 Processor ProcessorStats `json:",omitempty"`
90 Storage StorageStats `json:",omitempty"`
91 Network []NetworkStats `json:",omitempty"`
94 // ProcessList is the structure of an item returned by a ProcessList call on a container
95 type ProcessListItem struct {
96 CreateTimestamp time.Time `json:",omitempty"`
97 ImageName string `json:",omitempty"`
98 KernelTime100ns uint64 `json:",omitempty"`
99 MemoryCommitBytes uint64 `json:",omitempty"`
100 MemoryWorkingSetPrivateBytes uint64 `json:",omitempty"`
101 MemoryWorkingSetSharedBytes uint64 `json:",omitempty"`
102 ProcessId uint32 `json:",omitempty"`
103 UserTime100ns uint64 `json:",omitempty"`
106 // Type of Request Support in ModifySystem
107 type RequestType string
109 // Type of Resource Support in ModifySystem
110 type ResourceType string
114 Add RequestType = "Add"
115 Remove RequestType = "Remove"
116 Network ResourceType = "Network"
119 // ResourceModificationRequestResponse is the structure used to send request to the container to modify the system
120 // Supported resource types are Network and Request Types are Add/Remove
121 type ResourceModificationRequestResponse struct {
122 Resource ResourceType `json:"ResourceType"`
123 Data interface{} `json:"Settings"`
124 Request RequestType `json:"RequestType,omitempty"`
127 // createContainerAdditionalJSON is read from the environment at initialisation
128 // time. It allows an environment variable to define additional JSON which
129 // is merged in the CreateContainer call to HCS.
130 var createContainerAdditionalJSON string
133 createContainerAdditionalJSON = os.Getenv("HCSSHIM_CREATECONTAINER_ADDITIONALJSON")
136 // CreateContainer creates a new container with the given configuration but does not start it.
137 func CreateContainer(id string, c *ContainerConfig) (Container, error) {
138 return createContainerWithJSON(id, c, "")
141 // CreateContainerWithJSON creates a new container with the given configuration but does not start it.
142 // It is identical to CreateContainer except that optional additional JSON can be merged before passing to HCS.
143 func CreateContainerWithJSON(id string, c *ContainerConfig, additionalJSON string) (Container, error) {
144 return createContainerWithJSON(id, c, additionalJSON)
147 func createContainerWithJSON(id string, c *ContainerConfig, additionalJSON string) (Container, error) {
148 operation := "CreateContainer"
149 title := "HCSShim::" + operation
151 container := &container{
155 configurationb, err := json.Marshal(c)
160 configuration := string(configurationb)
161 logrus.Debugf(title+" id=%s config=%s", id, configuration)
163 // Merge any additional JSON. Priority is given to what is passed in explicitly,
164 // falling back to what's set in the environment.
165 if additionalJSON == "" && createContainerAdditionalJSON != "" {
166 additionalJSON = createContainerAdditionalJSON
168 if additionalJSON != "" {
169 configurationMap := map[string]interface{}{}
170 if err := json.Unmarshal([]byte(configuration), &configurationMap); err != nil {
171 return nil, fmt.Errorf("failed to unmarshal %s: %s", configuration, err)
174 additionalMap := map[string]interface{}{}
175 if err := json.Unmarshal([]byte(additionalJSON), &additionalMap); err != nil {
176 return nil, fmt.Errorf("failed to unmarshal %s: %s", additionalJSON, err)
179 mergedMap := mergeMaps(additionalMap, configurationMap)
180 mergedJSON, err := json.Marshal(mergedMap)
182 return nil, fmt.Errorf("failed to marshal merged configuration map %+v: %s", mergedMap, err)
185 configuration = string(mergedJSON)
186 logrus.Debugf(title+" id=%s merged config=%s", id, configuration)
191 identity syscall.Handle
193 createError := hcsCreateComputeSystem(id, configuration, identity, &container.handle, &resultp)
195 if createError == nil || IsPending(createError) {
196 if err := container.registerCallback(); err != nil {
197 return nil, makeContainerError(container, operation, "", err)
201 err = processAsyncHcsResult(createError, resultp, container.callbackNumber, hcsNotificationSystemCreateCompleted, &defaultTimeout)
203 return nil, makeContainerError(container, operation, configuration, err)
206 logrus.Debugf(title+" succeeded id=%s handle=%d", id, container.handle)
207 return container, nil
210 // mergeMaps recursively merges map `fromMap` into map `ToMap`. Any pre-existing values
211 // in ToMap are overwritten. Values in fromMap are added to ToMap.
212 // From http://stackoverflow.com/questions/40491438/merging-two-json-strings-in-golang
213 func mergeMaps(fromMap, ToMap interface{}) interface{} {
214 switch fromMap := fromMap.(type) {
215 case map[string]interface{}:
216 ToMap, ok := ToMap.(map[string]interface{})
220 for keyToMap, valueToMap := range ToMap {
221 if valueFromMap, ok := fromMap[keyToMap]; ok {
222 fromMap[keyToMap] = mergeMaps(valueFromMap, valueToMap)
224 fromMap[keyToMap] = valueToMap
228 // merge(nil, map[string]interface{...}) -> map[string]interface{...}
229 ToMap, ok := ToMap.(map[string]interface{})
237 // OpenContainer opens an existing container by ID.
238 func OpenContainer(id string) (Container, error) {
239 operation := "OpenContainer"
240 title := "HCSShim::" + operation
241 logrus.Debugf(title+" id=%s", id)
243 container := &container{
251 err := hcsOpenComputeSystem(id, &handle, &resultp)
252 err = processHcsResult(err, resultp)
254 return nil, makeContainerError(container, operation, "", err)
257 container.handle = handle
259 if err := container.registerCallback(); err != nil {
260 return nil, makeContainerError(container, operation, "", err)
263 logrus.Debugf(title+" succeeded id=%s handle=%d", id, handle)
264 return container, nil
267 // GetContainers gets a list of the containers on the system that match the query
268 func GetContainers(q ComputeSystemQuery) ([]ContainerProperties, error) {
269 operation := "GetContainers"
270 title := "HCSShim::" + operation
272 queryb, err := json.Marshal(q)
277 query := string(queryb)
278 logrus.Debugf(title+" query=%s", query)
282 computeSystemsp *uint16
284 err = hcsEnumerateComputeSystems(query, &computeSystemsp, &resultp)
285 err = processHcsResult(err, resultp)
290 if computeSystemsp == nil {
291 return nil, ErrUnexpectedValue
293 computeSystemsRaw := convertAndFreeCoTaskMemBytes(computeSystemsp)
294 computeSystems := []ContainerProperties{}
295 if err := json.Unmarshal(computeSystemsRaw, &computeSystems); err != nil {
299 logrus.Debugf(title + " succeeded")
300 return computeSystems, nil
303 // Start synchronously starts the container.
304 func (container *container) Start() error {
305 container.handleLock.RLock()
306 defer container.handleLock.RUnlock()
308 title := "HCSShim::Container::" + operation
309 logrus.Debugf(title+" id=%s", container.id)
311 if container.handle == 0 {
312 return makeContainerError(container, operation, "", ErrAlreadyClosed)
316 err := hcsStartComputeSystem(container.handle, "", &resultp)
317 err = processAsyncHcsResult(err, resultp, container.callbackNumber, hcsNotificationSystemStartCompleted, &defaultTimeout)
319 return makeContainerError(container, operation, "", err)
322 logrus.Debugf(title+" succeeded id=%s", container.id)
326 // Shutdown requests a container shutdown, if IsPending() on the error returned is true,
327 // it may not actually be shut down until Wait() succeeds.
328 func (container *container) Shutdown() error {
329 container.handleLock.RLock()
330 defer container.handleLock.RUnlock()
331 operation := "Shutdown"
332 title := "HCSShim::Container::" + operation
333 logrus.Debugf(title+" id=%s", container.id)
335 if container.handle == 0 {
336 return makeContainerError(container, operation, "", ErrAlreadyClosed)
340 err := hcsShutdownComputeSystem(container.handle, "", &resultp)
341 err = processHcsResult(err, resultp)
343 return makeContainerError(container, operation, "", err)
346 logrus.Debugf(title+" succeeded id=%s", container.id)
350 // Terminate requests a container terminate, if IsPending() on the error returned is true,
351 // it may not actually be shut down until Wait() succeeds.
352 func (container *container) Terminate() error {
353 container.handleLock.RLock()
354 defer container.handleLock.RUnlock()
355 operation := "Terminate"
356 title := "HCSShim::Container::" + operation
357 logrus.Debugf(title+" id=%s", container.id)
359 if container.handle == 0 {
360 return makeContainerError(container, operation, "", ErrAlreadyClosed)
364 err := hcsTerminateComputeSystem(container.handle, "", &resultp)
365 err = processHcsResult(err, resultp)
367 return makeContainerError(container, operation, "", err)
370 logrus.Debugf(title+" succeeded id=%s", container.id)
374 // Wait synchronously waits for the container to shutdown or terminate.
375 func (container *container) Wait() error {
377 title := "HCSShim::Container::" + operation
378 logrus.Debugf(title+" id=%s", container.id)
380 err := waitForNotification(container.callbackNumber, hcsNotificationSystemExited, nil)
382 return makeContainerError(container, operation, "", err)
385 logrus.Debugf(title+" succeeded id=%s", container.id)
389 // WaitTimeout synchronously waits for the container to terminate or the duration to elapse.
390 // If the timeout expires, IsTimeout(err) == true
391 func (container *container) WaitTimeout(timeout time.Duration) error {
392 operation := "WaitTimeout"
393 title := "HCSShim::Container::" + operation
394 logrus.Debugf(title+" id=%s", container.id)
396 err := waitForNotification(container.callbackNumber, hcsNotificationSystemExited, &timeout)
398 return makeContainerError(container, operation, "", err)
401 logrus.Debugf(title+" succeeded id=%s", container.id)
405 func (container *container) properties(query string) (*ContainerProperties, error) {
410 err := hcsGetComputeSystemProperties(container.handle, query, &propertiesp, &resultp)
411 err = processHcsResult(err, resultp)
416 if propertiesp == nil {
417 return nil, ErrUnexpectedValue
419 propertiesRaw := convertAndFreeCoTaskMemBytes(propertiesp)
420 properties := &ContainerProperties{}
421 if err := json.Unmarshal(propertiesRaw, properties); err != nil {
424 return properties, nil
427 // HasPendingUpdates returns true if the container has updates pending to install
428 func (container *container) HasPendingUpdates() (bool, error) {
429 container.handleLock.RLock()
430 defer container.handleLock.RUnlock()
431 operation := "HasPendingUpdates"
432 title := "HCSShim::Container::" + operation
433 logrus.Debugf(title+" id=%s", container.id)
435 if container.handle == 0 {
436 return false, makeContainerError(container, operation, "", ErrAlreadyClosed)
439 properties, err := container.properties(pendingUpdatesQuery)
441 return false, makeContainerError(container, operation, "", err)
444 logrus.Debugf(title+" succeeded id=%s", container.id)
445 return properties.AreUpdatesPending, nil
448 // Statistics returns statistics for the container
449 func (container *container) Statistics() (Statistics, error) {
450 container.handleLock.RLock()
451 defer container.handleLock.RUnlock()
452 operation := "Statistics"
453 title := "HCSShim::Container::" + operation
454 logrus.Debugf(title+" id=%s", container.id)
456 if container.handle == 0 {
457 return Statistics{}, makeContainerError(container, operation, "", ErrAlreadyClosed)
460 properties, err := container.properties(statisticsQuery)
462 return Statistics{}, makeContainerError(container, operation, "", err)
465 logrus.Debugf(title+" succeeded id=%s", container.id)
466 return properties.Statistics, nil
469 // ProcessList returns an array of ProcessListItems for the container
470 func (container *container) ProcessList() ([]ProcessListItem, error) {
471 container.handleLock.RLock()
472 defer container.handleLock.RUnlock()
473 operation := "ProcessList"
474 title := "HCSShim::Container::" + operation
475 logrus.Debugf(title+" id=%s", container.id)
477 if container.handle == 0 {
478 return nil, makeContainerError(container, operation, "", ErrAlreadyClosed)
481 properties, err := container.properties(processListQuery)
483 return nil, makeContainerError(container, operation, "", err)
486 logrus.Debugf(title+" succeeded id=%s", container.id)
487 return properties.ProcessList, nil
490 // Pause pauses the execution of the container. This feature is not enabled in TP5.
491 func (container *container) Pause() error {
492 container.handleLock.RLock()
493 defer container.handleLock.RUnlock()
495 title := "HCSShim::Container::" + operation
496 logrus.Debugf(title+" id=%s", container.id)
498 if container.handle == 0 {
499 return makeContainerError(container, operation, "", ErrAlreadyClosed)
503 err := hcsPauseComputeSystem(container.handle, "", &resultp)
504 err = processAsyncHcsResult(err, resultp, container.callbackNumber, hcsNotificationSystemPauseCompleted, &defaultTimeout)
506 return makeContainerError(container, operation, "", err)
509 logrus.Debugf(title+" succeeded id=%s", container.id)
513 // Resume resumes the execution of the container. This feature is not enabled in TP5.
514 func (container *container) Resume() error {
515 container.handleLock.RLock()
516 defer container.handleLock.RUnlock()
517 operation := "Resume"
518 title := "HCSShim::Container::" + operation
519 logrus.Debugf(title+" id=%s", container.id)
521 if container.handle == 0 {
522 return makeContainerError(container, operation, "", ErrAlreadyClosed)
526 err := hcsResumeComputeSystem(container.handle, "", &resultp)
527 err = processAsyncHcsResult(err, resultp, container.callbackNumber, hcsNotificationSystemResumeCompleted, &defaultTimeout)
529 return makeContainerError(container, operation, "", err)
532 logrus.Debugf(title+" succeeded id=%s", container.id)
536 // CreateProcess launches a new process within the container.
537 func (container *container) CreateProcess(c *ProcessConfig) (Process, error) {
538 container.handleLock.RLock()
539 defer container.handleLock.RUnlock()
540 operation := "CreateProcess"
541 title := "HCSShim::Container::" + operation
543 processInfo hcsProcessInformation
544 processHandle hcsProcess
548 if container.handle == 0 {
549 return nil, makeContainerError(container, operation, "", ErrAlreadyClosed)
552 // If we are not emulating a console, ignore any console size passed to us
553 if !c.EmulateConsole {
558 configurationb, err := json.Marshal(c)
560 return nil, makeContainerError(container, operation, "", err)
563 configuration := string(configurationb)
564 logrus.Debugf(title+" id=%s config=%s", container.id, configuration)
566 err = hcsCreateProcess(container.handle, configuration, &processInfo, &processHandle, &resultp)
567 err = processHcsResult(err, resultp)
569 return nil, makeContainerError(container, operation, configuration, err)
573 handle: processHandle,
574 processID: int(processInfo.ProcessId),
575 container: container,
576 cachedPipes: &cachedPipes{
577 stdIn: processInfo.StdInput,
578 stdOut: processInfo.StdOutput,
579 stdErr: processInfo.StdError,
583 if err := process.registerCallback(); err != nil {
584 return nil, makeContainerError(container, operation, "", err)
587 logrus.Debugf(title+" succeeded id=%s processid=%d", container.id, process.processID)
591 // OpenProcess gets an interface to an existing process within the container.
592 func (container *container) OpenProcess(pid int) (Process, error) {
593 container.handleLock.RLock()
594 defer container.handleLock.RUnlock()
595 operation := "OpenProcess"
596 title := "HCSShim::Container::" + operation
597 logrus.Debugf(title+" id=%s, processid=%d", container.id, pid)
599 processHandle hcsProcess
603 if container.handle == 0 {
604 return nil, makeContainerError(container, operation, "", ErrAlreadyClosed)
607 err := hcsOpenProcess(container.handle, uint32(pid), &processHandle, &resultp)
608 err = processHcsResult(err, resultp)
610 return nil, makeContainerError(container, operation, "", err)
614 handle: processHandle,
616 container: container,
619 if err := process.registerCallback(); err != nil {
620 return nil, makeContainerError(container, operation, "", err)
623 logrus.Debugf(title+" succeeded id=%s processid=%s", container.id, process.processID)
627 // Close cleans up any state associated with the container but does not terminate or wait for it.
628 func (container *container) Close() error {
629 container.handleLock.Lock()
630 defer container.handleLock.Unlock()
632 title := "HCSShim::Container::" + operation
633 logrus.Debugf(title+" id=%s", container.id)
635 // Don't double free this
636 if container.handle == 0 {
640 if err := container.unregisterCallback(); err != nil {
641 return makeContainerError(container, operation, "", err)
644 if err := hcsCloseComputeSystem(container.handle); err != nil {
645 return makeContainerError(container, operation, "", err)
650 logrus.Debugf(title+" succeeded id=%s", container.id)
654 func (container *container) registerCallback() error {
655 context := ¬ifcationWatcherContext{
656 channels: newChannels(),
659 callbackMapLock.Lock()
660 callbackNumber := nextCallback
662 callbackMap[callbackNumber] = context
663 callbackMapLock.Unlock()
665 var callbackHandle hcsCallback
666 err := hcsRegisterComputeSystemCallback(container.handle, notificationWatcherCallback, callbackNumber, &callbackHandle)
670 context.handle = callbackHandle
671 container.callbackNumber = callbackNumber
676 func (container *container) unregisterCallback() error {
677 callbackNumber := container.callbackNumber
679 callbackMapLock.RLock()
680 context := callbackMap[callbackNumber]
681 callbackMapLock.RUnlock()
687 handle := context.handle
693 // hcsUnregisterComputeSystemCallback has its own syncronization
694 // to wait for all callbacks to complete. We must NOT hold the callbackMapLock.
695 err := hcsUnregisterComputeSystemCallback(handle)
700 closeChannels(context.channels)
702 callbackMapLock.Lock()
703 callbackMap[callbackNumber] = nil
704 callbackMapLock.Unlock()
711 // Modifies the System by sending a request to HCS
712 func (container *container) Modify(config *ResourceModificationRequestResponse) error {
713 container.handleLock.RLock()
714 defer container.handleLock.RUnlock()
715 operation := "Modify"
716 title := "HCSShim::Container::" + operation
718 if container.handle == 0 {
719 return makeContainerError(container, operation, "", ErrAlreadyClosed)
722 requestJSON, err := json.Marshal(config)
727 requestString := string(requestJSON)
728 logrus.Debugf(title+" id=%s request=%s", container.id, requestString)
731 err = hcsModifyComputeSystem(container.handle, requestString, &resultp)
732 err = processHcsResult(err, resultp)
734 return makeContainerError(container, operation, "", err)
736 logrus.Debugf(title+" succeeded id=%s", container.id)