Procházet zdrojové kódy

Revendor jhowardmsft/opengcs @ v0.0.12

Signed-off-by: John Howard <jhoward@microsoft.com>
John Howard před 8 roky
rodič
revize
0bd1cf2517

+ 1 - 1
vendor.conf

@@ -8,7 +8,7 @@ github.com/docker/libtrust 9cbd2a1374f46905c68a4eb3694a130610adc62a
 github.com/go-check/check 4ed411733c5785b40214c70bce814c3a3a689609 https://github.com/cpuguy83/check.git
 github.com/gorilla/context v1.1
 github.com/gorilla/mux v1.1
-github.com/jhowardmsft/opengcs b9d0120d36f26e981a50bf18bac1bb3f0c2b8fef https://github.com/dmcgowan/opengcs.git
+github.com/jhowardmsft/opengcs v0.0.12
 github.com/kr/pty 5cf931ef8f
 github.com/mattn/go-shellwords v1.0.3
 github.com/sirupsen/logrus v1.0.1

+ 111 - 87
vendor/github.com/jhowardmsft/opengcs/gogcs/client/config.go

@@ -39,21 +39,20 @@ const (
 	// defaultUvmTimeoutSeconds is the default time to wait for utility VM operations
 	defaultUvmTimeoutSeconds = 5 * 60
 
-	// DefaultSandboxSizeMB is the size of the default sandbox size in MB
-	DefaultSandboxSizeMB = 20 * 1024 * 1024
+	// DefaultVhdxSizeGB is the size of the default sandbox & scratch in GB
+	DefaultVhdxSizeGB = 20
+
+	// defaultVhdxBlockSizeMB is the block-size for the sandbox/scratch VHDx's this package can create.
+	defaultVhdxBlockSizeMB = 1
 )
 
-// Config is the structure used to configuring a utility VM to be used
-// as a service VM. There are two ways of starting. Either supply a VHD,
-// or a Kernel+Initrd. For the latter, both must be supplied, and both
-// must be in the same directory.
+// Config is the structure used to configuring a utility VM. There are two ways
+// of starting. Either supply a VHD, or a Kernel+Initrd. For the latter, both
+// must be supplied, and both must be in the same directory.
 //
 // VHD is the priority.
 type Config struct {
-	KirdPath           string                      // Path to where kernel/initrd are found (defaults to c:\program files\Linux Containers)
-	KernelFile         string                      // Kernel for Utility VM (embedded in a UEFI bootloader) - does NOT include full path, just filename
-	InitrdFile         string                      // Initrd image for Utility VM - does NOT include full path, just filename
-	Vhdx               string                      // VHD for booting the utility VM - is a full path
+	Options                                        // Configuration options
 	Name               string                      // Name of the utility VM
 	RequestedMode      Mode                        // What mode is preferred when validating
 	ActualMode         Mode                        // What mode was obtained during validation
@@ -62,105 +61,129 @@ type Config struct {
 	MappedVirtualDisks []hcsshim.MappedVirtualDisk // Data-disks to be attached
 }
 
-// GenerateDefault generates a default config from a set of options
-// If baseDir is not supplied, defaults to $env:ProgramFiles\Linux Containers
-func (config *Config) GenerateDefault(options []string) error {
-	if config.UvmTimeoutSeconds < 0 {
-		return fmt.Errorf("opengcs: cannot generate a config when supplied a negative utility VM timeout")
-	}
-
-	envTimeoutSeconds := 0
-	optTimeoutSeconds := 0
-
-	if config.UvmTimeoutSeconds != 0 {
-		envTimeout := os.Getenv("OPENGCS_UVM_TIMEOUT_SECONDS")
-		if len(envTimeout) > 0 {
-			var err error
-			if envTimeoutSeconds, err = strconv.Atoi(envTimeout); err != nil {
-				return fmt.Errorf("opengcs: OPENGCS_UVM_TIMEOUT_SECONDS could not be interpreted as an integer")
-			}
-			if envTimeoutSeconds < 0 {
-				return fmt.Errorf("opengcs: OPENGCS_UVM_TIMEOUT_SECONDS cannot be negative")
-			}
-		}
-	}
+// Options is the structure used by a client to define configurable options for a utility VM.
+type Options struct {
+	KirdPath       string // Path to where kernel/initrd are found (defaults to %PROGRAMFILES%\Linux Containers)
+	KernelFile     string // Kernel for Utility VM (embedded in a UEFI bootloader) - does NOT include full path, just filename
+	InitrdFile     string // Initrd image for Utility VM - does NOT include full path, just filename
+	Vhdx           string // VHD for booting the utility VM - is a full path
+	TimeoutSeconds int    // Requested time for the utility VM to respond in seconds (may be over-ridden by environment)
+	BootParameters string // Additional boot parameters for initrd booting (not VHDx)
+}
 
+// ParseOptions parses a set of K-V pairs into options used by opengcs. Note
+// for consistency with the LCOW graphdriver in docker, we keep the same
+// convention of an `lcow.` prefix.
+func ParseOptions(options []string) (Options, error) {
+	rOpts := Options{TimeoutSeconds: 0}
 	for _, v := range options {
 		opt := strings.SplitN(v, "=", 2)
 		if len(opt) == 2 {
 			switch strings.ToLower(opt[0]) {
-			case "opengcskirdpath":
-				config.KirdPath = opt[1]
-			case "opengcskernel":
-				config.KernelFile = opt[1]
-			case "opengcsinitrd":
-				config.InitrdFile = opt[1]
-			case "opengcsvhdx":
-				config.Vhdx = opt[1]
-			case "opengcstimeoutsecs":
+			case "lcow.kirdpath":
+				rOpts.KirdPath = opt[1]
+			case "lcow.kernel":
+				rOpts.KernelFile = opt[1]
+			case "lcow.initrd":
+				rOpts.InitrdFile = opt[1]
+			case "lcow.vhdx":
+				rOpts.Vhdx = opt[1]
+			case "lcow.bootparameters":
+				rOpts.BootParameters = opt[1]
+			case "lcow.timeout":
 				var err error
-				if optTimeoutSeconds, err = strconv.Atoi(opt[1]); err != nil {
-					return fmt.Errorf("opengcs: opengcstimeoutsecs option could not be interpreted as an integer")
+				if rOpts.TimeoutSeconds, err = strconv.Atoi(opt[1]); err != nil {
+					return rOpts, fmt.Errorf("opengcstimeoutsecs option could not be interpreted as an integer")
 				}
-				if optTimeoutSeconds < 0 {
-					return fmt.Errorf("opengcs: opengcstimeoutsecs option cannot be negative")
+				if rOpts.TimeoutSeconds < 0 {
+					return rOpts, fmt.Errorf("opengcstimeoutsecs option cannot be negative")
 				}
 			}
 		}
 	}
 
-	if config.KirdPath == "" {
-		config.KirdPath = filepath.Join(os.Getenv("ProgramFiles"), "Linux Containers")
+	// Set default values if not supplied
+	if rOpts.KirdPath == "" {
+		rOpts.KirdPath = filepath.Join(os.Getenv("ProgramFiles"), "Linux Containers")
 	}
-
-	if config.Vhdx == "" {
-		config.Vhdx = filepath.Join(config.KirdPath, `uvm.vhdx`)
+	if rOpts.Vhdx == "" {
+		rOpts.Vhdx = filepath.Join(rOpts.KirdPath, `uvm.vhdx`)
 	}
-	if config.KernelFile == "" {
-		config.KernelFile = `bootx64.efi`
+	if rOpts.KernelFile == "" {
+		rOpts.KernelFile = `bootx64.efi`
 	}
-	if config.InitrdFile == "" {
-		config.InitrdFile = `initrd.img`
+	if rOpts.InitrdFile == "" {
+		rOpts.InitrdFile = `initrd.img`
 	}
 
-	// Which timeout are we going to take? If not through option or environment,
-	// then use the default constant, otherwise the maximum of the option or
-	// environment supplied setting. A requested on in the config supplied
-	// overrides all of this.
-	if config.UvmTimeoutSeconds == 0 {
-		config.UvmTimeoutSeconds = defaultUvmTimeoutSeconds
-		if optTimeoutSeconds != 0 || envTimeoutSeconds != 0 {
-			config.UvmTimeoutSeconds = optTimeoutSeconds
-			if envTimeoutSeconds > optTimeoutSeconds {
-				config.UvmTimeoutSeconds = envTimeoutSeconds
-			}
+	return rOpts, nil
+}
+
+// GenerateDefault generates a default config from a set of options
+// If baseDir is not supplied, defaults to $env:ProgramFiles\Linux Containers
+func (config *Config) GenerateDefault(options []string) error {
+	// Parse the options that the user supplied.
+	var err error
+	config.Options, err = ParseOptions(options)
+	if err != nil {
+		return err
+	}
+
+	// Get the timeout from the environment
+	envTimeoutSeconds := 0
+	envTimeout := os.Getenv("OPENGCS_UVM_TIMEOUT_SECONDS")
+	if len(envTimeout) > 0 {
+		var err error
+		if envTimeoutSeconds, err = strconv.Atoi(envTimeout); err != nil {
+			return fmt.Errorf("OPENGCS_UVM_TIMEOUT_SECONDS could not be interpreted as an integer")
 		}
+		if envTimeoutSeconds < 0 {
+			return fmt.Errorf("OPENGCS_UVM_TIMEOUT_SECONDS cannot be negative")
+		}
+	}
+
+	// Priority to the requested timeout from the options.
+	if config.TimeoutSeconds != 0 {
+		config.UvmTimeoutSeconds = config.TimeoutSeconds
+		return nil
 	}
 
-	config.MappedVirtualDisks = nil
+	// Next priority, the environment
+	if envTimeoutSeconds != 0 {
+		config.UvmTimeoutSeconds = envTimeoutSeconds
+		return nil
+	}
+
+	// Last priority is the default timeout
+	config.UvmTimeoutSeconds = defaultUvmTimeoutSeconds
 
 	return nil
 }
 
-// validate validates a Config structure for starting a utility VM.
-func (config *Config) validate() error {
+// Validate validates a Config structure for starting a utility VM.
+func (config *Config) Validate() error {
 	config.ActualMode = ModeActualError
 
 	if config.RequestedMode == ModeRequestVhdx && config.Vhdx == "" {
-		return fmt.Errorf("opengcs: config is invalid - request for VHDX mode did not supply a VHDX")
+		return fmt.Errorf("VHDx mode must supply a VHDx")
 	}
 	if config.RequestedMode == ModeRequestKernelInitrd && (config.KernelFile == "" || config.InitrdFile == "") {
-		return fmt.Errorf("opengcs: config is invalid - request for Kernel+Initrd mode must supply both kernel and initrd")
+		return fmt.Errorf("kernel+initrd mode must supply both kernel and initrd")
 	}
 
 	// Validate that if VHDX requested or auto, it exists.
 	if config.RequestedMode == ModeRequestAuto || config.RequestedMode == ModeRequestVhdx {
 		if _, err := os.Stat(config.Vhdx); os.IsNotExist(err) {
 			if config.RequestedMode == ModeRequestVhdx {
-				return fmt.Errorf("opengcs: mode requested was VHDX but '%s' could not be found", config.Vhdx)
+				return fmt.Errorf("VHDx '%s' not found", config.Vhdx)
 			}
 		} else {
 			config.ActualMode = ModeActualVhdx
+
+			// Can't specify boot parameters with VHDx
+			if config.BootParameters != "" {
+				return fmt.Errorf("Boot parameters cannot be specified in VHDx mode")
+			}
 			return nil
 		}
 	}
@@ -168,16 +191,16 @@ func (config *Config) validate() error {
 	// So must be kernel+initrd, or auto where we fallback as the VHDX doesn't exist
 	if config.InitrdFile == "" || config.KernelFile == "" {
 		if config.RequestedMode == ModeRequestKernelInitrd {
-			return fmt.Errorf("opengcs: both initrd and kernel options for utility VM boot must be supplied")
+			return fmt.Errorf("initrd and kernel options must be supplied")
 		}
 		return fmt.Errorf("opengcs: configuration is invalid")
 	}
 
 	if _, err := os.Stat(filepath.Join(config.KirdPath, config.KernelFile)); os.IsNotExist(err) {
-		return fmt.Errorf("opengcs: kernel '%s' was not found", filepath.Join(config.KirdPath, config.KernelFile))
+		return fmt.Errorf("kernel '%s' not found", filepath.Join(config.KirdPath, config.KernelFile))
 	}
 	if _, err := os.Stat(filepath.Join(config.KirdPath, config.InitrdFile)); os.IsNotExist(err) {
-		return fmt.Errorf("opengcs: initrd '%s' was not found", filepath.Join(config.KirdPath, config.InitrdFile))
+		return fmt.Errorf("initrd '%s' not found", filepath.Join(config.KirdPath, config.InitrdFile))
 	}
 
 	config.ActualMode = ModeActualKernelInitrd
@@ -185,21 +208,21 @@ func (config *Config) validate() error {
 	// Ensure all the MappedVirtualDisks exist on the host
 	for _, mvd := range config.MappedVirtualDisks {
 		if _, err := os.Stat(mvd.HostPath); err != nil {
-			return fmt.Errorf("opengcs: MappedVirtualDisk '%s' was not found", mvd.HostPath)
+			return fmt.Errorf("mapped virtual disk '%s' not found", mvd.HostPath)
 		}
 		if mvd.ContainerPath == "" {
-			return fmt.Errorf("opengcs: MappedVirtualDisk '%s' has no container path", mvd.HostPath)
+			return fmt.Errorf("mapped virtual disk '%s' requested without a container path", mvd.HostPath)
 		}
 	}
 
 	return nil
 }
 
-// Create creates a utility VM from a configuration.
-func (config *Config) Create() error {
-	logrus.Debugf("opengcs Create: %+v", config)
+// StartUtilityVM creates and starts a utility VM from a configuration.
+func (config *Config) StartUtilityVM() error {
+	logrus.Debugf("opengcs: StartUtilityVM: %+v", config)
 
-	if err := config.validate(); err != nil {
+	if err := config.Validate(); err != nil {
 		return err
 	}
 
@@ -218,28 +241,29 @@ func (config *Config) Create() error {
 		}
 	} else {
 		configuration.HvRuntime = &hcsshim.HvRuntime{
-			ImagePath:       config.KirdPath,
-			LinuxInitrdFile: config.InitrdFile,
-			LinuxKernelFile: config.KernelFile,
+			ImagePath:           config.KirdPath,
+			LinuxInitrdFile:     config.InitrdFile,
+			LinuxKernelFile:     config.KernelFile,
+			LinuxBootParameters: config.BootParameters,
 		}
 	}
 
 	configurationS, _ := json.Marshal(configuration)
-	logrus.Debugf("opengcs Create: calling HCS with '%s'", string(configurationS))
+	logrus.Debugf("opengcs: StartUtilityVM: calling HCS with '%s'", string(configurationS))
 	uvm, err := hcsshim.CreateContainer(config.Name, configuration)
 	if err != nil {
 		return err
 	}
-	logrus.Debugf("opengcs Create: uvm created, starting...")
+	logrus.Debugf("opengcs: StartUtilityVM: uvm created, starting...")
 	err = uvm.Start()
 	if err != nil {
-		logrus.Debugf("opengcs Create: uvm failed to start: %s", err)
+		logrus.Debugf("opengcs: StartUtilityVM: uvm failed to start: %s", err)
 		// Make sure we don't leave it laying around as it's been created in HCS
 		uvm.Terminate()
 		return err
 	}
 
 	config.Uvm = uvm
-	logrus.Debugf("opengcs Create: uvm %s is running", config.Name)
+	logrus.Debugf("opengcs StartUtilityVM: uvm %s is running", config.Name)
 	return nil
 }

+ 165 - 0
vendor/github.com/jhowardmsft/opengcs/gogcs/client/createext4vhdx.go

@@ -0,0 +1,165 @@
+// +build windows
+
+package client
+
+import (
+	"bytes"
+	"fmt"
+	"os"
+	"strings"
+	"time"
+
+	winio "github.com/Microsoft/go-winio/vhd"
+	//	"github.com/Microsoft/hcsshim"
+	"github.com/sirupsen/logrus"
+)
+
+// dismount is a simple utility function wrapping a conditional HotRemove. It would
+// have been easier if you could cancel a deferred function, but this works just
+// as well.
+func (config *Config) dismount(file string) error {
+	logrus.Debugf("opengcs: CreateExt4Vhdx: hot-remove of %s", file)
+	err := config.HotRemoveVhd(file)
+	if err != nil {
+		logrus.Warnf("failed to hot-remove: %s", err)
+	}
+	return err
+}
+
+// CreateExt4Vhdx does what it says on the tin. It is the responsibility of the caller to synchronise
+// simultaneous attempts to create the cache file.
+func (config *Config) CreateExt4Vhdx(destFile string, sizeGB uint32, cacheFile string) error {
+	// Smallest we can accept is the default sandbox size as we can't size down, only expand.
+	if sizeGB < DefaultVhdxSizeGB {
+		sizeGB = DefaultVhdxSizeGB
+	}
+
+	logrus.Debugf("opengcs: CreateExt4Vhdx: %s size:%dGB cache:%s", destFile, sizeGB, cacheFile)
+
+	// Retrieve from cache if the default size and already on disk
+	if cacheFile != "" && sizeGB == DefaultVhdxSizeGB {
+		if _, err := os.Stat(cacheFile); err == nil {
+			if err := CopyFile(cacheFile, destFile, false); err != nil {
+				return fmt.Errorf("failed to copy cached file '%s' to '%s': %s", cacheFile, destFile, err)
+			}
+			logrus.Debugf("opengcs: CreateExt4Vhdx: %s fulfilled from cache", destFile)
+			return nil
+		}
+	}
+
+	// Must have a utility VM to operate on
+	if config.Uvm == nil {
+		return fmt.Errorf("no utility VM")
+	}
+
+	// Create the VHDX
+	if err := winio.CreateVhdx(destFile, sizeGB, defaultVhdxBlockSizeMB); err != nil {
+		return fmt.Errorf("failed to create VHDx %s: %s", destFile, err)
+	}
+
+	// Attach it to the utility VM, but don't mount it (as there's no filesystem on it)
+	if err := config.HotAddVhd(destFile, "", false, false); err != nil {
+		return fmt.Errorf("opengcs: CreateExt4Vhdx: failed to hot-add %s to utility VM: %s", cacheFile, err)
+	}
+
+	// Get the list of mapped virtual disks to find the controller and LUN IDs
+	logrus.Debugf("opengcs: CreateExt4Vhdx: %s querying mapped virtual disks", destFile)
+	mvdControllers, err := config.Uvm.MappedVirtualDisks()
+	if err != nil {
+		return fmt.Errorf("failed to get mapped virtual disks: %s", err)
+	}
+
+	// Find our mapped disk from the list of all currently added.
+	controller := -1
+	lun := -1
+	for controllerNumber, controllerElement := range mvdControllers {
+		for diskNumber, diskElement := range controllerElement.MappedVirtualDisks {
+			if diskElement.HostPath == destFile {
+				controller = controllerNumber
+				lun = diskNumber
+				break
+			}
+		}
+	}
+	if controller == -1 || lun == -1 {
+		config.dismount(destFile)
+		return fmt.Errorf("failed to find %s in mapped virtual disks after hot-adding", destFile)
+	}
+	logrus.Debugf("opengcs: CreateExt4Vhdx: %s at C=%d L=%d", destFile, controller, lun)
+
+	// Validate /sys/bus/scsi/devices/C:0:0:L exists as a directory
+	testdCommand := fmt.Sprintf(`test -d /sys/bus/scsi/devices/%d:0:0:%d`, controller, lun)
+	testdProc, err := config.RunProcess(testdCommand, nil, nil, nil)
+	if err != nil {
+		config.dismount(destFile)
+		return fmt.Errorf("failed to `%s` following hot-add %s to utility VM: %s", testdCommand, destFile, err)
+	}
+	defer testdProc.Close()
+	testdProc.WaitTimeout(time.Duration(int(time.Second) * config.UvmTimeoutSeconds))
+	testdExitCode, err := testdProc.ExitCode()
+	if err != nil {
+		config.dismount(destFile)
+		return fmt.Errorf("failed to get exit code from `%s` following hot-add %s to utility VM: %s", testdCommand, destFile, err)
+	}
+	if testdExitCode != 0 {
+		config.dismount(destFile)
+		return fmt.Errorf("`%s` return non-zero exit code (%d) following hot-add %s to utility VM", testdCommand, testdExitCode, destFile)
+	}
+
+	// Get the device from under the block subdirectory by doing a simple ls. This will come back as (eg) `sda`
+	lsCommand := fmt.Sprintf(`ls /sys/bus/scsi/devices/%d:0:0:%d/block`, controller, lun)
+	var lsOutput bytes.Buffer
+	lsProc, err := config.RunProcess(lsCommand, nil, &lsOutput, nil)
+	if err != nil {
+		config.dismount(destFile)
+		return fmt.Errorf("failed to `%s` following hot-add %s to utility VM: %s", lsCommand, destFile, err)
+	}
+	defer lsProc.Close()
+	lsProc.WaitTimeout(time.Duration(int(time.Second) * config.UvmTimeoutSeconds))
+	lsExitCode, err := lsProc.ExitCode()
+	if err != nil {
+		config.dismount(destFile)
+		return fmt.Errorf("failed to get exit code from `%s` following hot-add %s to utility VM: %s", lsCommand, destFile, err)
+	}
+	if lsExitCode != 0 {
+		config.dismount(destFile)
+		return fmt.Errorf("`%s` return non-zero exit code (%d) following hot-add %s to utility VM", lsCommand, lsExitCode, destFile)
+	}
+	device := fmt.Sprintf(`/dev/%s`, strings.TrimSpace(lsOutput.String()))
+	logrus.Debugf("opengcs: CreateExt4Vhdx: %s: device at %s", destFile, device)
+
+	// Format it ext4
+	mkfsCommand := fmt.Sprintf(`mkfs.ext4 -q -E lazy_itable_init=1 -O ^has_journal,sparse_super2,uninit_bg,^resize_inode %s`, device)
+	var mkfsStderr bytes.Buffer
+	mkfsProc, err := config.RunProcess(mkfsCommand, nil, nil, &mkfsStderr)
+	if err != nil {
+		config.dismount(destFile)
+		return fmt.Errorf("failed to RunProcess %q following hot-add %s to utility VM: %s", destFile, mkfsCommand, err)
+	}
+	defer mkfsProc.Close()
+	mkfsProc.WaitTimeout(time.Duration(int(time.Second) * config.UvmTimeoutSeconds))
+	mkfsExitCode, err := mkfsProc.ExitCode()
+	if err != nil {
+		config.dismount(destFile)
+		return fmt.Errorf("failed to get exit code from `%s` following hot-add %s to utility VM: %s", mkfsCommand, destFile, err)
+	}
+	if mkfsExitCode != 0 {
+		config.dismount(destFile)
+		return fmt.Errorf("`%s` return non-zero exit code (%d) following hot-add %s to utility VM: %s", mkfsCommand, mkfsExitCode, destFile, strings.TrimSpace(mkfsStderr.String()))
+	}
+
+	// Dismount before we copy it
+	if err := config.dismount(destFile); err != nil {
+		return fmt.Errorf("failed to hot-remove: %s", err)
+	}
+
+	// Populate the cache.
+	if cacheFile != "" && (sizeGB == DefaultVhdxSizeGB) {
+		if err := CopyFile(destFile, cacheFile, true); err != nil {
+			return fmt.Errorf("failed to seed cache '%s' from '%s': %s", destFile, cacheFile, err)
+		}
+	}
+
+	logrus.Debugf("opengcs: CreateExt4Vhdx: %s created (non-cache)", destFile)
+	return nil
+}

+ 0 - 67
vendor/github.com/jhowardmsft/opengcs/gogcs/client/createsandbox.go

@@ -1,67 +0,0 @@
-// +build windows
-
-package client
-
-import (
-	"fmt"
-	"os"
-
-	"github.com/sirupsen/logrus"
-)
-
-// CreateSandbox does what it says on the tin. This is done by copying a prebuilt-sandbox from the ServiceVM.
-// It is the responsibility of the caller to synchronise simultaneous attempts to create the cache file.
-// TODO: @jhowardmsft maxSizeInMB isn't hooked up in GCS. Needs a platform change which is in flight.
-func (config *Config) CreateSandbox(destFile string, maxSizeInMB uint32, cacheFile string) error {
-	// Smallest we can accept is the default sandbox size as we can't size down, only expand.
-	if maxSizeInMB < DefaultSandboxSizeMB {
-		maxSizeInMB = DefaultSandboxSizeMB
-	}
-
-	logrus.Debugf("opengcs: CreateSandbox: %s size:%dMB cache:%s", destFile, maxSizeInMB, cacheFile)
-
-	// Retrieve from cache if the default size and already on disk
-	if cacheFile != "" && maxSizeInMB == DefaultSandboxSizeMB {
-		if _, err := os.Stat(cacheFile); err == nil {
-			if err := CopyFile(cacheFile, destFile, false); err != nil {
-				return fmt.Errorf("opengcs: CreateSandbox: Failed to copy cached sandbox '%s' to '%s': %s", cacheFile, destFile, err)
-			}
-			logrus.Debugf("opengcs: CreateSandbox: %s fulfilled from cache", destFile)
-			return nil
-		}
-	}
-
-	if config.Uvm == nil {
-		return fmt.Errorf("opengcs: CreateSandbox: No utility VM has been created")
-	}
-
-	// TODO @jhowardmsft - needs a platform change so that can specify size. eg fmt.Sprintf("createSandbox -size %d", maxSizeInMB))
-	process, err := config.createUtilsProcess("createSandbox")
-	if err != nil {
-		return fmt.Errorf("opengcs: CreateSandbox: %s: failed to create utils process: %s", destFile, err)
-	}
-
-	defer func() {
-		process.Process.Close()
-	}()
-
-	logrus.Debugf("opengcs: CreateSandbox: %s: writing from stdout", destFile)
-	// Get back the sandbox VHDx stream from the service VM and write it to file
-	resultSize, err := writeFileFromReader(destFile, process.Stdout, config.UvmTimeoutSeconds, fmt.Sprintf("createSandbox %s", destFile))
-	if err != nil {
-		return fmt.Errorf("opengcs: CreateSandbox: %s: failed writing %d bytes to target file: %s", destFile, resultSize, err)
-	}
-
-	// Populate the cache
-	if cacheFile != "" && maxSizeInMB == DefaultSandboxSizeMB {
-		// It may already exist due to being created on another thread, in which case no copy back needed.
-		if _, err := os.Stat(cacheFile); os.IsNotExist(err) {
-			if err := CopyFile(destFile, cacheFile, false); err != nil {
-				return fmt.Errorf("opengcs: CreateSandbox: Failed to seed sandbox cache '%s' from '%s': %s", destFile, cacheFile, err)
-			}
-		}
-	}
-
-	logrus.Debugf("opengcs: CreateSandbox: %s created (non-cache)", destFile)
-	return nil
-}

+ 5 - 4
vendor/github.com/jhowardmsft/opengcs/gogcs/client/hotaddvhd.go

@@ -13,7 +13,7 @@ import (
 // service-VM per host scenario. In order to do a graphdriver `Diff`, we hot-add the
 // sandbox to /mnt/<id> so that we can run `exportSandbox` inside the utility VM to
 // get a tar-stream of the sandboxes contents back to the daemon.
-func (config *Config) HotAddVhd(hostPath string, containerPath string) error {
+func (config *Config) HotAddVhd(hostPath string, containerPath string, readOnly bool, mount bool) error {
 	logrus.Debugf("opengcs: HotAddVhd: %s: %s", hostPath, containerPath)
 
 	if config.Uvm == nil {
@@ -26,13 +26,14 @@ func (config *Config) HotAddVhd(hostPath string, containerPath string) error {
 			HostPath:          hostPath,
 			ContainerPath:     containerPath,
 			CreateInUtilityVM: true,
-			//ReadOnly:          true,
+			ReadOnly:          readOnly,
+			AttachOnly:        !mount,
 		},
 		Request: "Add",
 	}
-	logrus.Debugf("opengcs: HotAddVhd: %s to %s", hostPath, containerPath)
+
 	if err := config.Uvm.Modify(modification); err != nil {
-		return fmt.Errorf("opengcs: HotAddVhd: failed: %s", err)
+		return fmt.Errorf("failed to modify utility VM configuration for hot-add: %s", err)
 	}
 	logrus.Debugf("opengcs: HotAddVhd: %s added successfully", hostPath)
 	return nil

+ 1 - 1
vendor/github.com/jhowardmsft/opengcs/gogcs/client/hotremovevhd.go

@@ -27,7 +27,7 @@ func (config *Config) HotRemoveVhd(hostPath string) error {
 		Request: "Remove",
 	}
 	if err := config.Uvm.Modify(modification); err != nil {
-		return fmt.Errorf("opengcs: HotRemoveVhd: %s failed: %s", hostPath, err)
+		return fmt.Errorf("failed modifying utility VM for hot-remove %s: %s", hostPath, err)
 	}
 	logrus.Debugf("opengcs: HotRemoveVhd: %s removed successfully", hostPath)
 	return nil

+ 26 - 14
vendor/github.com/jhowardmsft/opengcs/gogcs/client/process.go

@@ -15,6 +15,7 @@ type process struct {
 	Process hcsshim.Process
 	Stdin   io.WriteCloser
 	Stdout  io.ReadCloser
+	Stderr  io.ReadCloser
 }
 
 // createUtilsProcess is a convenient wrapper for hcsshim.createUtilsProcess to use when
@@ -45,13 +46,13 @@ func (config *Config) createUtilsProcess(commandLine string) (process, error) {
 	}
 	proc.Process, err = config.Uvm.CreateProcess(processConfig)
 	if err != nil {
-		return process{}, fmt.Errorf("opengcs: createUtilsProcess: CreateProcess %+v failed %s", config, err)
+		return process{}, fmt.Errorf("failed to create process (%+v) in utility VM: %s", config, err)
 	}
 
-	if proc.Stdin, proc.Stdout, _, err = proc.Process.Stdio(); err != nil {
+	if proc.Stdin, proc.Stdout, proc.Stderr, err = proc.Process.Stdio(); err != nil {
 		proc.Process.Kill() // Should this have a timeout?
 		proc.Process.Close()
-		return process{}, fmt.Errorf("opengcs: createUtilsProcess: failed to get Stdio pipes %s", err)
+		return process{}, fmt.Errorf("failed to get stdio pipes for process %+v: %s", config, err)
 	}
 
 	logrus.Debugf("opengcs: createUtilsProcess success: pid %d", proc.Process.Pid())
@@ -60,41 +61,52 @@ func (config *Config) createUtilsProcess(commandLine string) (process, error) {
 
 // RunProcess runs the given command line program in the utilityVM. It takes in
 // an input to the reader to feed into stdin and returns stdout to output.
-func (config *Config) RunProcess(commandLine string, input io.Reader, output io.Writer) error {
+// IMPORTANT: It is the responsibility of the caller to call Close() on the returned process.
+func (config *Config) RunProcess(commandLine string, stdin io.Reader, stdout io.Writer, stderr io.Writer) (hcsshim.Process, error) {
 	logrus.Debugf("opengcs: RunProcess: %s", commandLine)
 	process, err := config.createUtilsProcess(commandLine)
 	if err != nil {
-		return err
+		return nil, err
 	}
-	defer process.Process.Close()
 
 	// Send the data into the process's stdin
-	if input != nil {
+	if stdin != nil {
 		if _, err = copyWithTimeout(process.Stdin,
-			input,
+			stdin,
 			0,
 			config.UvmTimeoutSeconds,
 			fmt.Sprintf("send to stdin of %s", commandLine)); err != nil {
-			return err
+			return nil, err
 		}
 
 		// Don't need stdin now we've sent everything. This signals GCS that we are finished sending data.
 		if err := process.Process.CloseStdin(); err != nil {
-			return err
+			return nil, err
 		}
 	}
 
-	if output != nil {
+	if stdout != nil {
 		// Copy the data over to the writer.
-		if _, err := copyWithTimeout(output,
+		if _, err := copyWithTimeout(stdout,
 			process.Stdout,
 			0,
 			config.UvmTimeoutSeconds,
 			fmt.Sprintf("RunProcess: copy back from %s", commandLine)); err != nil {
-			return err
+			return nil, err
+		}
+	}
+
+	if stderr != nil {
+		// Copy the data over to the writer.
+		if _, err := copyWithTimeout(stderr,
+			process.Stderr,
+			0,
+			config.UvmTimeoutSeconds,
+			fmt.Sprintf("RunProcess: copy back from %s", commandLine)); err != nil {
+			return nil, err
 		}
 	}
 
 	logrus.Debugf("opengcs: runProcess success: %s", commandLine)
-	return nil
+	return process.Process, nil
 }

+ 4 - 4
vendor/github.com/jhowardmsft/opengcs/gogcs/client/tartovhd.go

@@ -19,24 +19,24 @@ func (config *Config) TarToVhd(targetVHDFile string, reader io.Reader) (int64, e
 
 	process, err := config.createUtilsProcess("tar2vhd")
 	if err != nil {
-		return 0, fmt.Errorf("opengcs: TarToVhd: %s: failed to create utils process tar2vhd: %s", targetVHDFile, err)
+		return 0, fmt.Errorf("failed to start tar2vhd for %s: %s", targetVHDFile, err)
 	}
 	defer process.Process.Close()
 
 	// Send the tarstream into the `tar2vhd`s stdin
 	if _, err = copyWithTimeout(process.Stdin, reader, 0, config.UvmTimeoutSeconds, fmt.Sprintf("stdin of tar2vhd for generating %s", targetVHDFile)); err != nil {
-		return 0, fmt.Errorf("opengcs: TarToVhd: %s: failed to send to tar2vhd in uvm: %s", targetVHDFile, err)
+		return 0, fmt.Errorf("failed sending to tar2vhd for %s: %s", targetVHDFile, err)
 	}
 
 	// Don't need stdin now we've sent everything. This signals GCS that we are finished sending data.
 	if err := process.Process.CloseStdin(); err != nil {
-		return 0, fmt.Errorf("opengcs: TarToVhd: %s: failed closing stdin handle: %s", targetVHDFile, err)
+		return 0, fmt.Errorf("failed closing stdin handle for %s: %s", targetVHDFile, err)
 	}
 
 	// Write stdout contents of `tar2vhd` to the VHD file
 	payloadSize, err := writeFileFromReader(targetVHDFile, process.Stdout, config.UvmTimeoutSeconds, fmt.Sprintf("stdout of tar2vhd to %s", targetVHDFile))
 	if err != nil {
-		return 0, fmt.Errorf("opengcs: TarToVhd: %s: failed writing VHD file: %s", targetVHDFile, err)
+		return 0, fmt.Errorf("failed to write %s during tar2vhd: %s", targetVHDFile, err)
 	}
 
 	logrus.Debugf("opengcs: TarToVhd: %s created, %d bytes", targetVHDFile, payloadSize)