Browse code

Adding servicing update to postRunProcessing for Windows containers.

This change enables the workflow of finishing installing Windows OS updates in the container after it has completed running, via a special servicing container.

Signed-off-by: Stefan J. Wernli <swernli@microsoft.com>

Stefan J. Wernli authored on 2016/04/14 05:34:07
Showing 9 changed files
... ...
@@ -16,9 +16,21 @@ func platformConstructExitStatus(e libcontainerd.StateInfo) *container.ExitStatu
16 16
 
17 17
 // postRunProcessing perfoms any processing needed on the container after it has stopped.
18 18
 func (daemon *Daemon) postRunProcessing(container *container.Container, e libcontainerd.StateInfo) error {
19
-	//TODO Windows - handle update processing here...
20 19
 	if e.UpdatePending {
21
-		return fmt.Errorf("Windows: Update handling not implemented.")
20
+		spec, err := daemon.createSpec(container)
21
+		if err != nil {
22
+			return err
23
+		}
24
+
25
+		servicingOption := &libcontainerd.ServicingOption{
26
+			IsServicing: true,
27
+		}
28
+
29
+		// Create a new servicing container, which will start, complete the update, and merge back the
30
+		// results if it succeeded, all as part of the below function call.
31
+		if err := daemon.containerd.Create((container.ID + "_servicing"), *spec, servicingOption); err != nil {
32
+			return fmt.Errorf("Post-run update servicing failed: %s", err)
33
+		}
22 34
 	}
23 35
 	return nil
24 36
 }
... ...
@@ -7,7 +7,7 @@ source 'hack/.vendor-helpers.sh'
7 7
 
8 8
 # the following lines are in sorted order, FYI
9 9
 clone git github.com/Azure/go-ansiterm 70b2c90b260171e829f1ebd7c17f600c11858dbe
10
-clone git github.com/Microsoft/hcsshim v0.2.1
10
+clone git github.com/Microsoft/hcsshim v0.2.2
11 11
 clone git github.com/Microsoft/go-winio v0.3.0
12 12
 clone git github.com/Sirupsen/logrus v0.9.0 # logrus is a common dependency among multiple deps
13 13
 clone git github.com/docker/libtrust 9cbd2a1374f46905c68a4eb3694a130610adc62a
... ...
@@ -96,6 +96,7 @@ type containerInit struct {
96 96
 	HvPartition             bool        // True if it a Hyper-V Container
97 97
 	EndpointList            []string    // List of networking endpoints to be attached to container
98 98
 	HvRuntime               *hvRuntime  // Hyper-V container settings
99
+	Servicing               bool        // True if this container is for servicing
99 100
 }
100 101
 
101 102
 // 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
157 157
 		}
158 158
 	}
159 159
 
160
+	for _, option := range options {
161
+		if s, ok := option.(*ServicingOption); ok {
162
+			cu.Servicing = s.IsServicing
163
+			break
164
+		}
165
+	}
166
+
160 167
 	if cu.HvPartition {
161 168
 		cu.SandboxPath = filepath.Dir(spec.Windows.LayerFolder)
162 169
 	} else {
... ...
@@ -35,13 +35,32 @@ func (ctr *container) newProcess(friendlyName string) *process {
35 35
 func (ctr *container) start() error {
36 36
 	var err error
37 37
 
38
-	// Start the container
38
+	// Start the container.  If this is a servicing container, this call will block
39
+	// until the container is done with the servicing execution.
39 40
 	logrus.Debugln("Starting container ", ctr.containerID)
40 41
 	if err = hcsshim.StartComputeSystem(ctr.containerID); err != nil {
41 42
 		logrus.Errorf("Failed to start compute system: %s", err)
42 43
 		return err
43 44
 	}
44 45
 
46
+	for _, option := range ctr.options {
47
+		if s, ok := option.(*ServicingOption); ok && s.IsServicing {
48
+			// Since the servicing operation is complete when StartCommputeSystem returns without error,
49
+			// we can shutdown (which triggers merge) and exit early.
50
+			const shutdownTimeout = 5 * 60 * 1000  // 4 minutes
51
+			const terminateTimeout = 1 * 60 * 1000 // 1 minute
52
+			if err := hcsshim.ShutdownComputeSystem(ctr.containerID, shutdownTimeout, ""); err != nil {
53
+				logrus.Errorf("Failed during cleanup of servicing container: %s", err)
54
+				// Terminate the container, ignoring errors.
55
+				if err2 := hcsshim.TerminateComputeSystem(ctr.containerID, terminateTimeout, ""); err2 != nil {
56
+					logrus.Errorf("Failed to terminate container %s after shutdown failure: %q", ctr.containerID, err2)
57
+				}
58
+				return err
59
+			}
60
+			return nil
61
+		}
62
+	}
63
+
45 64
 	createProcessParms := hcsshim.CreateProcessParams{
46 65
 		EmulateConsole:   ctr.ociSpec.Process.Terminal,
47 66
 		WorkingDirectory: ctr.ociSpec.Process.Cwd,
... ...
@@ -149,10 +168,13 @@ func (ctr *container) waitExit(pid uint32, processFriendlyName string, isFirstPr
149 149
 	// If this is the init process, always call into vmcompute.dll to
150 150
 	// shutdown the container after we have completed.
151 151
 	if isFirstProcessToStart {
152
-
153
-		// TODO Windows - add call into hcsshim to check if an update
154
-		// is pending once that is available.
155
-		//si.UpdatePending = CHECK IF UPDATE NEEDED
152
+		propertyCheckFlag := 1 // Include update pending check.
153
+		csProperties, err := hcsshim.GetComputeSystemProperties(ctr.containerID, uint32(propertyCheckFlag))
154
+		if err != nil {
155
+			logrus.Warnf("GetComputeSystemProperties failed (container may have been killed): %s", err)
156
+		} else {
157
+			si.UpdatePending = csProperties.AreUpdatesPending
158
+		}
156 159
 
157 160
 		logrus.Debugf("Shutting down container %s", ctr.containerID)
158 161
 		// Explicit timeout here rather than hcsshim.TimeoutInfinte to avoid a
... ...
@@ -22,7 +22,8 @@ type StateInfo struct {
22 22
 	CommonStateInfo
23 23
 
24 24
 	// Platform specific StateInfo
25
-	UpdatePending bool
25
+
26
+	UpdatePending bool // Indicates that there are some update operations pending that should be completed by a servicing container.
26 27
 }
27 28
 
28 29
 // Stats contains a stats properties from containerd.
... ...
@@ -30,3 +31,9 @@ type Stats struct{}
30 30
 
31 31
 // Resources defines updatable container resource values.
32 32
 type Resources struct{}
33
+
34
+// ServicingOption is an empty CreateOption with a no-op application that siginifies
35
+// the container needs to be use for a Windows servicing operation.
36
+type ServicingOption struct {
37
+	IsServicing bool
38
+}
... ...
@@ -14,3 +14,8 @@ func setupEnvironmentVariables(a []string) map[string]string {
14 14
 	}
15 15
 	return r
16 16
 }
17
+
18
+// Apply for a servicing option is a no-op.
19
+func (s *ServicingOption) Apply(interface{}) error {
20
+	return nil
21
+}
17 22
new file mode 100644
... ...
@@ -0,0 +1,43 @@
0
+package hcsshim
1
+
2
+import (
3
+	"encoding/json"
4
+
5
+	"github.com/Sirupsen/logrus"
6
+)
7
+
8
+// ComputeSystemProperties is a struct describing the returned properties.
9
+type ComputeSystemProperties struct {
10
+	ID                string
11
+	Name              string
12
+	Stopped           bool
13
+	AreUpdatesPending bool
14
+}
15
+
16
+// GetComputeSystemProperties gets the properties for the compute system with the given ID.
17
+func GetComputeSystemProperties(id string, flags uint32) (ComputeSystemProperties, error) {
18
+	title := "hcsshim::GetComputeSystemProperties "
19
+
20
+	csProps := ComputeSystemProperties{
21
+		Stopped:           false,
22
+		AreUpdatesPending: false,
23
+	}
24
+
25
+	logrus.Debugf("Calling proc")
26
+	var buffer *uint16
27
+	err := getComputeSystemProperties(id, flags, &buffer)
28
+	if err != nil {
29
+		err = makeError(err, title, "")
30
+		logrus.Error(err)
31
+		return csProps, err
32
+	}
33
+	propData := convertAndFreeCoTaskMemString(buffer)
34
+	logrus.Debugf(title+" - succeeded output=%s", propData)
35
+
36
+	if err = json.Unmarshal([]byte(propData), &csProps); err != nil {
37
+		logrus.Error(err)
38
+		return csProps, err
39
+	}
40
+
41
+	return csProps, nil
42
+}
... ...
@@ -48,6 +48,7 @@ import (
48 48
 //sys terminateComputeSystem(id string) (hr error) = vmcompute.TerminateComputeSystem?
49 49
 //sys terminateProcessInComputeSystem(id string, pid uint32) (hr error) = vmcompute.TerminateProcessInComputeSystem?
50 50
 //sys waitForProcessInComputeSystem(id string, pid uint32, timeout uint32, exitCode *uint32) (hr error) = vmcompute.WaitForProcessInComputeSystem?
51
+//sys getComputeSystemProperties(id string, flags uint32, properties **uint16) (hr error) = vmcompute.GetComputeSystemProperties?
51 52
 
52 53
 //sys _hnsCall(method string, path string, object string, response **uint16) (hr error) = vmcompute.HNSCall?
53 54
 
... ...
@@ -48,6 +48,7 @@ var (
48 48
 	procTerminateComputeSystem                     = modvmcompute.NewProc("TerminateComputeSystem")
49 49
 	procTerminateProcessInComputeSystem            = modvmcompute.NewProc("TerminateProcessInComputeSystem")
50 50
 	procWaitForProcessInComputeSystem              = modvmcompute.NewProc("WaitForProcessInComputeSystem")
51
+	procGetComputeSystemProperties                 = modvmcompute.NewProc("GetComputeSystemProperties")
51 52
 	procHNSCall                                    = modvmcompute.NewProc("HNSCall")
52 53
 )
53 54
 
... ...
@@ -713,6 +714,26 @@ func _waitForProcessInComputeSystem(id *uint16, pid uint32, timeout uint32, exit
713 713
 	return
714 714
 }
715 715
 
716
+func getComputeSystemProperties(id string, flags uint32, properties **uint16) (hr error) {
717
+	var _p0 *uint16
718
+	_p0, hr = syscall.UTF16PtrFromString(id)
719
+	if hr != nil {
720
+		return
721
+	}
722
+	return _getComputeSystemProperties(_p0, flags, properties)
723
+}
724
+
725
+func _getComputeSystemProperties(id *uint16, flags uint32, properties **uint16) (hr error) {
726
+	if hr = procGetComputeSystemProperties.Find(); hr != nil {
727
+		return
728
+	}
729
+	r0, _, _ := syscall.Syscall(procGetComputeSystemProperties.Addr(), 3, uintptr(unsafe.Pointer(id)), uintptr(flags), uintptr(unsafe.Pointer(properties)))
730
+	if int32(r0) < 0 {
731
+		hr = syscall.Errno(win32FromHresult(r0))
732
+	}
733
+	return
734
+}
735
+
716 736
 func _hnsCall(method string, path string, object string, response **uint16) (hr error) {
717 737
 	var _p0 *uint16
718 738
 	_p0, hr = syscall.UTF16PtrFromString(method)