diff --git a/cmd/runhcs/create-scratch.go b/cmd/runhcs/create-scratch.go new file mode 100644 index 0000000000..55d26b5b07 --- /dev/null +++ b/cmd/runhcs/create-scratch.go @@ -0,0 +1,74 @@ +package main + +import ( + "os" + "path/filepath" + + "github.com/Microsoft/hcsshim/internal/appargs" + "github.com/Microsoft/hcsshim/internal/lcow" + "github.com/Microsoft/hcsshim/internal/osversion" + "github.com/Microsoft/hcsshim/internal/uvm" + gcsclient "github.com/Microsoft/opengcs/client" + "github.com/pkg/errors" + "github.com/urfave/cli" +) + +var createScratchCommand = cli.Command{ + Name: "create-scratch", + Usage: "creates a scratch vhdx at 'destpath' that is ext4 formatted", + Description: "Creates a scratch vhdx at 'destpath' that is ext4 formatted", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "destpath", + Usage: "Required: describes the destination vhd path", + }, + }, + Before: appargs.Validate(), + Action: func(context *cli.Context) error { + dest := context.String("destpath") + if dest == "" { + return errors.New("'destpath' is required") + } + + // If we only have v1 lcow support do it the old way. + if osversion.Get().Build < osversion.RS5 { + cfg := gcsclient.Config{ + Options: gcsclient.Options{ + KirdPath: filepath.Join(os.Getenv("ProgramFiles"), "Linux Containers"), + KernelFile: "kernel", + InitrdFile: "initrd.img", + }, + Name: "createscratch-uvm", + UvmTimeoutSeconds: 5 * 60, // 5 Min + } + + if err := cfg.StartUtilityVM(); err != nil { + return errors.Wrapf(err, "failed to start '%s'", cfg.Name) + } + defer cfg.Uvm.Terminate() + + if err := cfg.CreateExt4Vhdx(dest, lcow.DefaultScratchSizeGB, ""); err != nil { + return errors.Wrapf(err, "failed to create ext4vhdx for '%s'", cfg.Name) + } + } else { + opts := uvm.UVMOptions{ + ID: "createscratch-uvm", + OperatingSystem: "linux", + } + convertUVM, err := uvm.Create(&opts) + if err != nil { + return errors.Wrapf(err, "failed to create '%s'", opts.ID) + } + if err := convertUVM.Start(); err != nil { + return errors.Wrapf(err, "failed to start '%s'", opts.ID) + } + defer convertUVM.Terminate() + + if err := lcow.CreateScratch(convertUVM, dest, lcow.DefaultScratchSizeGB, "", ""); err != nil { + return errors.Wrapf(err, "failed to create ext4vhdx for '%s'", opts.ID) + } + } + + return nil + }, +} diff --git a/cmd/runhcs/main.go b/cmd/runhcs/main.go index f7e7699b28..eb395f354e 100644 --- a/cmd/runhcs/main.go +++ b/cmd/runhcs/main.go @@ -91,6 +91,7 @@ func main() { } app.Commands = []cli.Command{ createCommand, + createScratchCommand, deleteCommand, // eventsCommand, execCommand, @@ -104,6 +105,7 @@ func main() { shimCommand, startCommand, stateCommand, + tarToVhdCommand, // updateCommand, vmshimCommand, } diff --git a/cmd/runhcs/start.go b/cmd/runhcs/start.go index 788cbfc5f3..b510c88363 100644 --- a/cmd/runhcs/start.go +++ b/cmd/runhcs/start.go @@ -36,7 +36,7 @@ your host.`, case containerRunning: return errors.New("cannot start an already running container") default: - return fmt.Errorf("cannot start a container in the %s state\n", status) + return fmt.Errorf("cannot start a container in the '%s' state", status) } }, } diff --git a/cmd/runhcs/state.go b/cmd/runhcs/state.go index 816230bd30..1d8a8ba08f 100644 --- a/cmd/runhcs/state.go +++ b/cmd/runhcs/state.go @@ -3,6 +3,7 @@ package main import ( "encoding/json" "os" + "strings" "github.com/Microsoft/hcsshim/internal/appargs" "github.com/urfave/cli" @@ -25,7 +26,10 @@ instance of a container.`, } status, err := c.Status() if err != nil { - return err + if !strings.Contains(err.Error(), "operation is not valid in the current state") { + return err + } + status = containerUnknown } cs := containerState{ Version: c.Spec.Version, diff --git a/cmd/runhcs/tar2vhd.go b/cmd/runhcs/tar2vhd.go new file mode 100644 index 0000000000..2a7ed67716 --- /dev/null +++ b/cmd/runhcs/tar2vhd.go @@ -0,0 +1,116 @@ +package main + +import ( + "io" + "os" + "path/filepath" + + "github.com/Microsoft/hcsshim/internal/appargs" + "github.com/Microsoft/hcsshim/internal/lcow" + "github.com/Microsoft/hcsshim/internal/osversion" + "github.com/Microsoft/hcsshim/internal/uvm" + gcsclient "github.com/Microsoft/opengcs/client" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "github.com/urfave/cli" +) + +var tarToVhdCommand = cli.Command{ + Name: "tar2vhd", + Usage: "converts a tar over stdin to a vhd at 'destpath'", + Description: "The tar2vhd command converts the tar at ('sourcepath'|stdin) to a vhd at 'destpath'", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "sourcepath", + Usage: "Optional: describes the path to the tar on disk", + }, + cli.StringFlag{ + Name: "scratchpath", + Usage: "Required: describes the path to the scratch.vhdx file to use for the transformation", + }, + cli.StringFlag{ + Name: "destpath", + Usage: "Required: describes the destination vhd path to write the contents of the tar to on disk", + }, + }, + Before: appargs.Validate(), + Action: func(context *cli.Context) error { + var rdr io.Reader + if src := context.String("sourcepath"); src != "" { + // Source is via file path not stdin + f, err := os.OpenFile(src, os.O_RDONLY, 0) + if err != nil { + return errors.Wrapf(err, "failed to open 'sourcepath': '%s'", src) + } + defer f.Close() + rdr = f + } else { + rdr = os.Stdin + } + + scratch := context.String("scratchpath") + if scratch == "" { + return errors.New("'scratchpath' is required") + } + + dest := context.String("destpath") + if dest == "" { + return errors.New("'destpath' is required") + } + + // If we only have v1 lcow support do it the old way. + if osversion.Get().Build < osversion.RS5 { + cfg := gcsclient.Config{ + Options: gcsclient.Options{ + KirdPath: filepath.Join(os.Getenv("ProgramFiles"), "Linux Containers"), + KernelFile: "kernel", + InitrdFile: "initrd.img", + }, + Name: "tar2vhd-uvm", + UvmTimeoutSeconds: 5 * 60, // 5 Min + } + + if err := cfg.StartUtilityVM(); err != nil { + return errors.Wrapf(err, "failed to start '%s'", cfg.Name) + } + defer cfg.Uvm.Terminate() + + if err := cfg.HotAddVhd(scratch, "/tmp/scratch", false, true); err != nil { + return errors.Wrapf(err, "failed to mount scratch path: '%s' to '%s'", scratch, cfg.Name) + } + + n, err := cfg.TarToVhd(dest, rdr) + if err != nil { + return errors.Wrapf(err, "failed to convert tar2vhd for '%s'", cfg.Name) + } + + logrus.Debugf("wrote %v bytes to %s", n, dest) + } else { + opts := uvm.UVMOptions{ + ID: "tar2vhd-uvm", + OperatingSystem: "linux", + } + convertUVM, err := uvm.Create(&opts) + if err != nil { + return errors.Wrapf(err, "failed to create '%s'", opts.ID) + } + if err := convertUVM.Start(); err != nil { + return errors.Wrapf(err, "failed to start '%s'", opts.ID) + } + defer convertUVM.Terminate() + + if _, _, err := convertUVM.AddSCSI(scratch, "/tmp/scratch"); err != nil { + return errors.Wrapf(err, "failed to mount scratch path: '%s' to '%s'", scratch, opts.ID) + } + + n, err := lcow.TarToVhd(convertUVM, dest, rdr) + if err != nil { + return errors.Wrapf(err, "failed to convert tar2vhd for '%s'", opts.ID) + } + + logrus.Debugf("wrote %v bytes to %s", n, dest) + } + + return nil + }, +} diff --git a/internal/lcow/tar2vhd.go b/internal/lcow/tar2vhd.go index 0c4bdc631d..08d90a736b 100644 --- a/internal/lcow/tar2vhd.go +++ b/internal/lcow/tar2vhd.go @@ -4,6 +4,7 @@ import ( "fmt" "io" "os" + "time" "github.com/Microsoft/hcsshim/internal/uvm" specs "github.com/opencontainers/runtime-spec/specs-go" @@ -28,10 +29,12 @@ func TarToVhd(lcowUVM *uvm.UtilityVM, targetVHDFile string, reader io.Reader) (i // BUGBUG Delete the file on failure tar2vhd, byteCounts, err := CreateProcess(&ProcessOptions{ + HCSSystem: lcowUVM.ComputeSystem(), Process: &specs.Process{Args: []string{"tar2vhd"}}, CreateInUtilityVm: true, Stdin: reader, Stdout: outFile, + CopyTimeout: 2 * time.Minute, }) if err != nil { return 0, fmt.Errorf("failed to start tar2vhd for %s: %s", targetVHDFile, err)