Vendor opengcs and hcsshim

Signed-off-by: Darren Stahl <darst@microsoft.com>
This commit is contained in:
Darren Stahl
2017-08-28 15:07:51 -07:00
parent cf09e32618
commit 4a782f7b54
19 changed files with 1243 additions and 32 deletions

274
vendor/github.com/Microsoft/opengcs/client/config.go generated vendored Normal file
View File

@@ -0,0 +1,274 @@
// +build windows
package client
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"strconv"
"strings"
"github.com/Microsoft/hcsshim"
"github.com/sirupsen/logrus"
)
// Mode is the operational mode, both requested, and actual after verification
type Mode uint
const (
// Constants for the actual mode after validation
// ModeActualError means an error has occurred during validation
ModeActualError = iota
// ModeActualVhdx means that we are going to use VHDX boot after validation
ModeActualVhdx
// ModeActualKernelInitrd means that we are going to use kernel+initrd for boot after validation
ModeActualKernelInitrd
// Constants for the requested mode
// ModeRequestAuto means auto-select the boot mode for a utility VM
ModeRequestAuto = iota // VHDX will be priority over kernel+initrd
// ModeRequestVhdx means request VHDX boot if possible
ModeRequestVhdx
// ModeRequestKernelInitrd means request Kernel+initrd boot if possible
ModeRequestKernelInitrd
// defaultUvmTimeoutSeconds is the default time to wait for utility VM operations
defaultUvmTimeoutSeconds = 5 * 60
// 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. 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 {
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
UvmTimeoutSeconds int // How long to wait for the utility VM to respond in seconds
Uvm hcsshim.Container // The actual container
MappedVirtualDisks []hcsshim.MappedVirtualDisk // Data-disks to be attached
}
// 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 "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 rOpts.TimeoutSeconds, err = strconv.Atoi(opt[1]); err != nil {
return rOpts, fmt.Errorf("opengcstimeoutsecs option could not be interpreted as an integer")
}
if rOpts.TimeoutSeconds < 0 {
return rOpts, fmt.Errorf("opengcstimeoutsecs option cannot be negative")
}
}
}
}
// Set default values if not supplied
if rOpts.KirdPath == "" {
rOpts.KirdPath = filepath.Join(os.Getenv("ProgramFiles"), "Linux Containers")
}
if rOpts.Vhdx == "" {
rOpts.Vhdx = filepath.Join(rOpts.KirdPath, `uvm.vhdx`)
}
if rOpts.KernelFile == "" {
rOpts.KernelFile = `bootx64.efi`
}
if rOpts.InitrdFile == "" {
rOpts.InitrdFile = `initrd.img`
}
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
}
// Next priority, the environment
if envTimeoutSeconds != 0 {
config.UvmTimeoutSeconds = envTimeoutSeconds
return nil
}
// Last priority is the default timeout
config.UvmTimeoutSeconds = defaultUvmTimeoutSeconds
// Set the default requested mode
config.RequestedMode = ModeRequestAuto
return nil
}
// 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("VHDx mode must supply a VHDx")
}
if config.RequestedMode == ModeRequestKernelInitrd && (config.KernelFile == "" || config.InitrdFile == "") {
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("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
}
}
// 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("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("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("initrd '%s' not found", filepath.Join(config.KirdPath, config.InitrdFile))
}
config.ActualMode = ModeActualKernelInitrd
// Ensure all the MappedVirtualDisks exist on the host
for _, mvd := range config.MappedVirtualDisks {
if _, err := os.Stat(mvd.HostPath); err != nil {
return fmt.Errorf("mapped virtual disk '%s' not found", mvd.HostPath)
}
if mvd.ContainerPath == "" {
return fmt.Errorf("mapped virtual disk '%s' requested without a container path", mvd.HostPath)
}
}
return nil
}
// 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 {
return err
}
configuration := &hcsshim.ContainerConfig{
HvPartition: true,
Name: config.Name,
SystemType: "container",
ContainerType: "linux",
TerminateOnLastHandleClosed: true,
MappedVirtualDisks: config.MappedVirtualDisks,
}
if config.ActualMode == ModeActualVhdx {
configuration.HvRuntime = &hcsshim.HvRuntime{
ImagePath: config.Vhdx,
BootSource: "Vhd",
WritableBootSource: true,
}
} else {
configuration.HvRuntime = &hcsshim.HvRuntime{
ImagePath: config.KirdPath,
LinuxInitrdFile: config.InitrdFile,
LinuxKernelFile: config.KernelFile,
LinuxBootParameters: config.BootParameters,
}
}
configurationS, _ := json.Marshal(configuration)
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: StartUtilityVM: uvm created, starting...")
err = uvm.Start()
if err != nil {
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 StartUtilityVM: uvm %s is running", config.Name)
return nil
}

View File

@@ -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
}

View File

@@ -0,0 +1,40 @@
// +build windows
package client
import (
"fmt"
"github.com/Microsoft/hcsshim"
"github.com/sirupsen/logrus"
)
// HotAddVhd hot-adds a VHD to a utility VM. This is used in the global one-utility-VM-
// 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, readOnly bool, mount bool) error {
logrus.Debugf("opengcs: HotAddVhd: %s: %s", hostPath, containerPath)
if config.Uvm == nil {
return fmt.Errorf("cannot hot-add VHD as no utility VM is in configuration")
}
modification := &hcsshim.ResourceModificationRequestResponse{
Resource: "MappedVirtualDisk",
Data: hcsshim.MappedVirtualDisk{
HostPath: hostPath,
ContainerPath: containerPath,
CreateInUtilityVM: true,
ReadOnly: readOnly,
AttachOnly: !mount,
},
Request: "Add",
}
if err := config.Uvm.Modify(modification); err != nil {
return fmt.Errorf("failed to modify utility VM configuration for hot-add: %s", err)
}
logrus.Debugf("opengcs: HotAddVhd: %s added successfully", hostPath)
return nil
}

View File

@@ -0,0 +1,34 @@
// +build windows
package client
import (
"fmt"
"github.com/Microsoft/hcsshim"
"github.com/sirupsen/logrus"
)
// HotRemoveVhd hot-removes a VHD from a utility VM. This is used in the global one-utility-VM-
// service-VM per host scenario.
func (config *Config) HotRemoveVhd(hostPath string) error {
logrus.Debugf("opengcs: HotRemoveVhd: %s", hostPath)
if config.Uvm == nil {
return fmt.Errorf("cannot hot-add VHD as no utility VM is in configuration")
}
modification := &hcsshim.ResourceModificationRequestResponse{
Resource: "MappedVirtualDisk",
Data: hcsshim.MappedVirtualDisk{
HostPath: hostPath,
CreateInUtilityVM: true,
},
Request: "Remove",
}
if err := config.Uvm.Modify(modification); err != nil {
return fmt.Errorf("failed modifying utility VM for hot-remove %s: %s", hostPath, err)
}
logrus.Debugf("opengcs: HotRemoveVhd: %s removed successfully", hostPath)
return nil
}

View File

@@ -0,0 +1,31 @@
// +build windows
package client
import (
"fmt"
"os"
"path/filepath"
)
// LayerVhdDetails is a utility for getting a file name, size and indication of
// sandbox for a VHD(x) in a folder. A read-only layer will be layer.vhd. A
// read-write layer will be sandbox.vhdx.
func LayerVhdDetails(folder string) (string, int64, bool, error) {
var fileInfo os.FileInfo
isSandbox := false
filename := filepath.Join(folder, "layer.vhd")
var err error
if fileInfo, err = os.Stat(filename); err != nil {
filename = filepath.Join(folder, "sandbox.vhdx")
if fileInfo, err = os.Stat(filename); err != nil {
if os.IsNotExist(err) {
return "", 0, isSandbox, fmt.Errorf("could not find layer or sandbox in %s", folder)
}
return "", 0, isSandbox, fmt.Errorf("error locating layer or sandbox in %s: %s", folder, err)
}
isSandbox = true
}
return filename, fileInfo.Size(), isSandbox, nil
}

112
vendor/github.com/Microsoft/opengcs/client/process.go generated vendored Normal file
View File

@@ -0,0 +1,112 @@
// +build windows
package client
import (
"fmt"
"io"
"github.com/Microsoft/hcsshim"
"github.com/sirupsen/logrus"
)
// Process is the structure pertaining to a process running in a utility VM.
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
// communicating with a utility VM.
func (config *Config) createUtilsProcess(commandLine string) (process, error) {
logrus.Debugf("opengcs: createUtilsProcess")
if config.Uvm == nil {
return process{}, fmt.Errorf("cannot create utils process as no utility VM is in configuration")
}
var (
err error
proc process
)
env := make(map[string]string)
env["PATH"] = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:"
processConfig := &hcsshim.ProcessConfig{
EmulateConsole: false,
CreateStdInPipe: true,
CreateStdOutPipe: true,
CreateStdErrPipe: true,
CreateInUtilityVm: true,
WorkingDirectory: "/bin",
Environment: env,
CommandLine: commandLine,
}
proc.Process, err = config.Uvm.CreateProcess(processConfig)
if err != nil {
return process{}, fmt.Errorf("failed to create process (%+v) in utility VM: %s", config, err)
}
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("failed to get stdio pipes for process %+v: %s", config, err)
}
logrus.Debugf("opengcs: createUtilsProcess success: pid %d", proc.Process.Pid())
return proc, nil
}
// 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.
// 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 nil, err
}
// Send the data into the process's stdin
if stdin != nil {
if _, err = copyWithTimeout(process.Stdin,
stdin,
0,
config.UvmTimeoutSeconds,
fmt.Sprintf("send to stdin of %s", commandLine)); err != nil {
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 nil, err
}
}
if stdout != nil {
// Copy the data over to the writer.
if _, err := copyWithTimeout(stdout,
process.Stdout,
0,
config.UvmTimeoutSeconds,
fmt.Sprintf("RunProcess: copy back from %s", commandLine)); err != nil {
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 process.Process, nil
}

44
vendor/github.com/Microsoft/opengcs/client/tartovhd.go generated vendored Normal file
View File

@@ -0,0 +1,44 @@
// +build windows
package client
import (
"fmt"
"io"
"github.com/sirupsen/logrus"
)
// TarToVhd streams a tarstream contained in an io.Reader to a fixed vhd file
func (config *Config) TarToVhd(targetVHDFile string, reader io.Reader) (int64, error) {
logrus.Debugf("opengcs: TarToVhd: %s", targetVHDFile)
if config.Uvm == nil {
return 0, fmt.Errorf("cannot Tar2Vhd as no utility VM is in configuration")
}
process, err := config.createUtilsProcess("tar2vhd")
if err != nil {
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("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("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("failed to write %s during tar2vhd: %s", targetVHDFile, err)
}
logrus.Debugf("opengcs: TarToVhd: %s created, %d bytes", targetVHDFile, payloadSize)
return payloadSize, err
}

View File

@@ -0,0 +1,3 @@
// +build !windows
package client

View File

@@ -0,0 +1,99 @@
// +build windows
package client
import (
"fmt"
"io"
"os"
"syscall"
"time"
"unsafe"
"github.com/sirupsen/logrus"
)
var (
modkernel32 = syscall.NewLazyDLL("kernel32.dll")
procCopyFileW = modkernel32.NewProc("CopyFileW")
)
// writeFileFromReader writes an output file from an io.Reader
func writeFileFromReader(path string, reader io.Reader, timeoutSeconds int, context string) (int64, error) {
outFile, err := os.Create(path)
if err != nil {
return 0, fmt.Errorf("opengcs: writeFileFromReader: failed to create %s: %s", path, err)
}
defer outFile.Close()
return copyWithTimeout(outFile, reader, 0, timeoutSeconds, context)
}
// copyWithTimeout is a wrapper for io.Copy using a timeout duration
func copyWithTimeout(dst io.Writer, src io.Reader, size int64, timeoutSeconds int, context string) (int64, error) {
logrus.Debugf("opengcs: copywithtimeout: size %d: timeout %d: (%s)", size, timeoutSeconds, context)
type resultType struct {
err error
bytes int64
}
done := make(chan resultType, 1)
go func() {
result := resultType{}
result.bytes, result.err = io.Copy(dst, src)
done <- result
}()
var result resultType
timedout := time.After(time.Duration(timeoutSeconds) * time.Second)
select {
case <-timedout:
return 0, fmt.Errorf("opengcs: copyWithTimeout: timed out (%s)", context)
case result = <-done:
if result.err != nil && result.err != io.EOF {
// See https://github.com/golang/go/blob/f3f29d1dea525f48995c1693c609f5e67c046893/src/os/exec/exec_windows.go for a clue as to why we are doing this :)
if se, ok := result.err.(syscall.Errno); ok {
const (
errNoData = syscall.Errno(232)
errBrokenPipe = syscall.Errno(109)
)
if se == errNoData || se == errBrokenPipe {
logrus.Debugf("opengcs: copyWithTimeout: hit NoData or BrokenPipe: %d: %s", se, context)
return result.bytes, nil
}
}
return 0, fmt.Errorf("opengcs: copyWithTimeout: error reading: '%s' after %d bytes (%s)", result.err, result.bytes, context)
}
}
logrus.Debugf("opengcs: copyWithTimeout: success - copied %d bytes (%s)", result.bytes, context)
return result.bytes, nil
}
// CopyFile is a utility for copying a file - used for the sandbox cache.
// Uses CopyFileW win32 API for performance
func CopyFile(srcFile, destFile string, overwrite bool) error {
var bFailIfExists uint32 = 1
if overwrite {
bFailIfExists = 0
}
lpExistingFileName, err := syscall.UTF16PtrFromString(srcFile)
if err != nil {
return err
}
lpNewFileName, err := syscall.UTF16PtrFromString(destFile)
if err != nil {
return err
}
r1, _, err := syscall.Syscall(
procCopyFileW.Addr(),
3,
uintptr(unsafe.Pointer(lpExistingFileName)),
uintptr(unsafe.Pointer(lpNewFileName)),
uintptr(bFailIfExists))
if r1 == 0 {
return fmt.Errorf("failed CopyFileW Win32 call from '%s' to '%s': %s", srcFile, destFile, err)
}
return nil
}

67
vendor/github.com/Microsoft/opengcs/client/vhdtotar.go generated vendored Normal file
View File

@@ -0,0 +1,67 @@
// +build windows
package client
import (
"fmt"
"io"
"os"
"github.com/sirupsen/logrus"
)
// VhdToTar does what is says - it exports a VHD in a specified
// folder (either a read-only layer.vhd, or a read-write sandbox.vhd) to a
// ReadCloser containing a tar-stream of the layers contents.
func (config *Config) VhdToTar(vhdFile string, uvmMountPath string, isSandbox bool, vhdSize int64) (io.ReadCloser, error) {
logrus.Debugf("opengcs: VhdToTar: %s isSandbox: %t", vhdFile, isSandbox)
if config.Uvm == nil {
return nil, fmt.Errorf("cannot VhdToTar as no utility VM is in configuration")
}
vhdHandle, err := os.Open(vhdFile)
if err != nil {
return nil, fmt.Errorf("opengcs: VhdToTar: failed to open %s: %s", vhdFile, err)
}
defer vhdHandle.Close()
logrus.Debugf("opengcs: VhdToTar: exporting %s, size %d, isSandbox %t", vhdHandle.Name(), vhdSize, isSandbox)
// Different binary depending on whether a RO layer or a RW sandbox
command := "vhd2tar"
if isSandbox {
command = fmt.Sprintf("exportSandbox -path %s", uvmMountPath)
}
// Start the binary in the utility VM
process, err := config.createUtilsProcess(command)
if err != nil {
return nil, fmt.Errorf("opengcs: VhdToTar: %s: failed to create utils process %s: %s", vhdHandle.Name(), command, err)
}
if !isSandbox {
// Send the VHD contents to the utility VM processes stdin handle if not a sandbox
logrus.Debugf("opengcs: VhdToTar: copying the layer VHD into the utility VM")
if _, err = copyWithTimeout(process.Stdin, vhdHandle, vhdSize, config.UvmTimeoutSeconds, fmt.Sprintf("vhdtotarstream: sending %s to %s", vhdHandle.Name(), command)); err != nil {
process.Process.Close()
return nil, fmt.Errorf("opengcs: VhdToTar: %s: failed to copyWithTimeout on the stdin pipe (to utility VM): %s", vhdHandle.Name(), err)
}
}
// Start a goroutine which copies the stdout (ie the tar stream)
reader, writer := io.Pipe()
go func() {
defer writer.Close()
defer process.Process.Close()
logrus.Debugf("opengcs: VhdToTar: copying tar stream back from the utility VM")
bytes, err := copyWithTimeout(writer, process.Stdout, vhdSize, config.UvmTimeoutSeconds, fmt.Sprintf("vhdtotarstream: copy tarstream from %s", command))
if err != nil {
logrus.Errorf("opengcs: VhdToTar: %s: copyWithTimeout on the stdout pipe (from utility VM) failed: %s", vhdHandle.Name(), err)
}
logrus.Debugf("opengcs: VhdToTar: copied %d bytes of the tarstream of %s from the utility VM", bytes, vhdHandle.Name())
}()
// Return the read-side of the pipe connected to the goroutine which is reading from the stdout of the process in the utility VM
return reader, nil
}