diff --git a/daemon/monitor_windows.go b/daemon/monitor_windows.go index 4a6339467b..6f9744c685 100644 --- a/daemon/monitor_windows.go +++ b/daemon/monitor_windows.go @@ -16,9 +16,21 @@ func platformConstructExitStatus(e libcontainerd.StateInfo) *container.ExitStatu // postRunProcessing perfoms any processing needed on the container after it has stopped. func (daemon *Daemon) postRunProcessing(container *container.Container, e libcontainerd.StateInfo) error { - //TODO Windows - handle update processing here... if e.UpdatePending { - return fmt.Errorf("Windows: Update handling not implemented.") + spec, err := daemon.createSpec(container) + if err != nil { + return err + } + + servicingOption := &libcontainerd.ServicingOption{ + IsServicing: true, + } + + // Create a new servicing container, which will start, complete the update, and merge back the + // results if it succeeded, all as part of the below function call. + if err := daemon.containerd.Create((container.ID + "_servicing"), *spec, servicingOption); err != nil { + return fmt.Errorf("Post-run update servicing failed: %s", err) + } } return nil } diff --git a/hack/vendor.sh b/hack/vendor.sh index cdaf3941e4..d90393bf63 100755 --- a/hack/vendor.sh +++ b/hack/vendor.sh @@ -7,7 +7,7 @@ source 'hack/.vendor-helpers.sh' # the following lines are in sorted order, FYI clone git github.com/Azure/go-ansiterm 70b2c90b260171e829f1ebd7c17f600c11858dbe -clone git github.com/Microsoft/hcsshim v0.2.1 +clone git github.com/Microsoft/hcsshim v0.2.2 clone git github.com/Microsoft/go-winio v0.3.0 clone git github.com/Sirupsen/logrus v0.9.0 # logrus is a common dependency among multiple deps clone git github.com/docker/libtrust 9cbd2a1374f46905c68a4eb3694a130610adc62a diff --git a/libcontainerd/client_windows.go b/libcontainerd/client_windows.go index 3cacec6106..22aa07ead8 100644 --- a/libcontainerd/client_windows.go +++ b/libcontainerd/client_windows.go @@ -96,6 +96,7 @@ type containerInit struct { HvPartition bool // True if it a Hyper-V Container EndpointList []string // List of networking endpoints to be attached to container HvRuntime *hvRuntime // Hyper-V container settings + Servicing bool // True if this container is for servicing } // defaultOwner is a tag passed to HCS to allow it to differentiate between @@ -157,6 +158,13 @@ func (clnt *client) Create(containerID string, spec Spec, options ...CreateOptio } } + for _, option := range options { + if s, ok := option.(*ServicingOption); ok { + cu.Servicing = s.IsServicing + break + } + } + if cu.HvPartition { cu.SandboxPath = filepath.Dir(spec.Windows.LayerFolder) } else { diff --git a/libcontainerd/container_windows.go b/libcontainerd/container_windows.go index 362673f45d..6b074f17d0 100644 --- a/libcontainerd/container_windows.go +++ b/libcontainerd/container_windows.go @@ -35,13 +35,32 @@ func (ctr *container) newProcess(friendlyName string) *process { func (ctr *container) start() error { var err error - // Start the container + // Start the container. If this is a servicing container, this call will block + // until the container is done with the servicing execution. logrus.Debugln("Starting container ", ctr.containerID) if err = hcsshim.StartComputeSystem(ctr.containerID); err != nil { logrus.Errorf("Failed to start compute system: %s", err) return err } + for _, option := range ctr.options { + if s, ok := option.(*ServicingOption); ok && s.IsServicing { + // Since the servicing operation is complete when StartCommputeSystem returns without error, + // we can shutdown (which triggers merge) and exit early. + const shutdownTimeout = 5 * 60 * 1000 // 4 minutes + const terminateTimeout = 1 * 60 * 1000 // 1 minute + if err := hcsshim.ShutdownComputeSystem(ctr.containerID, shutdownTimeout, ""); err != nil { + logrus.Errorf("Failed during cleanup of servicing container: %s", err) + // Terminate the container, ignoring errors. + if err2 := hcsshim.TerminateComputeSystem(ctr.containerID, terminateTimeout, ""); err2 != nil { + logrus.Errorf("Failed to terminate container %s after shutdown failure: %q", ctr.containerID, err2) + } + return err + } + return nil + } + } + createProcessParms := hcsshim.CreateProcessParams{ EmulateConsole: ctr.ociSpec.Process.Terminal, WorkingDirectory: ctr.ociSpec.Process.Cwd, @@ -149,10 +168,13 @@ func (ctr *container) waitExit(pid uint32, processFriendlyName string, isFirstPr // If this is the init process, always call into vmcompute.dll to // shutdown the container after we have completed. if isFirstProcessToStart { - - // TODO Windows - add call into hcsshim to check if an update - // is pending once that is available. - //si.UpdatePending = CHECK IF UPDATE NEEDED + propertyCheckFlag := 1 // Include update pending check. + csProperties, err := hcsshim.GetComputeSystemProperties(ctr.containerID, uint32(propertyCheckFlag)) + if err != nil { + logrus.Warnf("GetComputeSystemProperties failed (container may have been killed): %s", err) + } else { + si.UpdatePending = csProperties.AreUpdatesPending + } logrus.Debugf("Shutting down container %s", ctr.containerID) // Explicit timeout here rather than hcsshim.TimeoutInfinte to avoid a diff --git a/libcontainerd/types_windows.go b/libcontainerd/types_windows.go index 0f8bdd678c..1e7014c110 100644 --- a/libcontainerd/types_windows.go +++ b/libcontainerd/types_windows.go @@ -22,7 +22,8 @@ type StateInfo struct { CommonStateInfo // Platform specific StateInfo - UpdatePending bool + + UpdatePending bool // Indicates that there are some update operations pending that should be completed by a servicing container. } // Stats contains a stats properties from containerd. @@ -30,3 +31,9 @@ type Stats struct{} // Resources defines updatable container resource values. type Resources struct{} + +// ServicingOption is an empty CreateOption with a no-op application that siginifies +// the container needs to be use for a Windows servicing operation. +type ServicingOption struct { + IsServicing bool +} diff --git a/libcontainerd/utils_windows.go b/libcontainerd/utils_windows.go index a9d95d635f..76ad61819d 100644 --- a/libcontainerd/utils_windows.go +++ b/libcontainerd/utils_windows.go @@ -14,3 +14,8 @@ func setupEnvironmentVariables(a []string) map[string]string { } return r } + +// Apply for a servicing option is a no-op. +func (s *ServicingOption) Apply(interface{}) error { + return nil +} diff --git a/vendor/src/github.com/Microsoft/hcsshim/getcomputesystemproperties.go b/vendor/src/github.com/Microsoft/hcsshim/getcomputesystemproperties.go new file mode 100644 index 0000000000..3c544492da --- /dev/null +++ b/vendor/src/github.com/Microsoft/hcsshim/getcomputesystemproperties.go @@ -0,0 +1,43 @@ +package hcsshim + +import ( + "encoding/json" + + "github.com/Sirupsen/logrus" +) + +// ComputeSystemProperties is a struct describing the returned properties. +type ComputeSystemProperties struct { + ID string + Name string + Stopped bool + AreUpdatesPending bool +} + +// GetComputeSystemProperties gets the properties for the compute system with the given ID. +func GetComputeSystemProperties(id string, flags uint32) (ComputeSystemProperties, error) { + title := "hcsshim::GetComputeSystemProperties " + + csProps := ComputeSystemProperties{ + Stopped: false, + AreUpdatesPending: false, + } + + logrus.Debugf("Calling proc") + var buffer *uint16 + err := getComputeSystemProperties(id, flags, &buffer) + if err != nil { + err = makeError(err, title, "") + logrus.Error(err) + return csProps, err + } + propData := convertAndFreeCoTaskMemString(buffer) + logrus.Debugf(title+" - succeeded output=%s", propData) + + if err = json.Unmarshal([]byte(propData), &csProps); err != nil { + logrus.Error(err) + return csProps, err + } + + return csProps, nil +} diff --git a/vendor/src/github.com/Microsoft/hcsshim/hcsshim.go b/vendor/src/github.com/Microsoft/hcsshim/hcsshim.go index 88650b8ac9..1ea04e06bd 100644 --- a/vendor/src/github.com/Microsoft/hcsshim/hcsshim.go +++ b/vendor/src/github.com/Microsoft/hcsshim/hcsshim.go @@ -48,6 +48,7 @@ import ( //sys terminateComputeSystem(id string) (hr error) = vmcompute.TerminateComputeSystem? //sys terminateProcessInComputeSystem(id string, pid uint32) (hr error) = vmcompute.TerminateProcessInComputeSystem? //sys waitForProcessInComputeSystem(id string, pid uint32, timeout uint32, exitCode *uint32) (hr error) = vmcompute.WaitForProcessInComputeSystem? +//sys getComputeSystemProperties(id string, flags uint32, properties **uint16) (hr error) = vmcompute.GetComputeSystemProperties? //sys _hnsCall(method string, path string, object string, response **uint16) (hr error) = vmcompute.HNSCall? diff --git a/vendor/src/github.com/Microsoft/hcsshim/zhcsshim.go b/vendor/src/github.com/Microsoft/hcsshim/zhcsshim.go index 03924b6fcc..f5eb2d68b4 100644 --- a/vendor/src/github.com/Microsoft/hcsshim/zhcsshim.go +++ b/vendor/src/github.com/Microsoft/hcsshim/zhcsshim.go @@ -48,6 +48,7 @@ var ( procTerminateComputeSystem = modvmcompute.NewProc("TerminateComputeSystem") procTerminateProcessInComputeSystem = modvmcompute.NewProc("TerminateProcessInComputeSystem") procWaitForProcessInComputeSystem = modvmcompute.NewProc("WaitForProcessInComputeSystem") + procGetComputeSystemProperties = modvmcompute.NewProc("GetComputeSystemProperties") procHNSCall = modvmcompute.NewProc("HNSCall") ) @@ -713,6 +714,26 @@ func _waitForProcessInComputeSystem(id *uint16, pid uint32, timeout uint32, exit return } +func getComputeSystemProperties(id string, flags uint32, properties **uint16) (hr error) { + var _p0 *uint16 + _p0, hr = syscall.UTF16PtrFromString(id) + if hr != nil { + return + } + return _getComputeSystemProperties(_p0, flags, properties) +} + +func _getComputeSystemProperties(id *uint16, flags uint32, properties **uint16) (hr error) { + if hr = procGetComputeSystemProperties.Find(); hr != nil { + return + } + r0, _, _ := syscall.Syscall(procGetComputeSystemProperties.Addr(), 3, uintptr(unsafe.Pointer(id)), uintptr(flags), uintptr(unsafe.Pointer(properties))) + if int32(r0) < 0 { + hr = syscall.Errno(win32FromHresult(r0)) + } + return +} + func _hnsCall(method string, path string, object string, response **uint16) (hr error) { var _p0 *uint16 _p0, hr = syscall.UTF16PtrFromString(method)