Skip to content

Commit

Permalink
uvmboot functionality (#1359)
Browse files Browse the repository at this point in the history
* uvmboot functionality

Added functionality to internal\tools\uvmboot for LCOW:
* specifying boot file path;
* picking kernel or vmlinux file;
* mounting SCSI VHDS and sharing files into the uVM;
* disabling the time sync;
* setting the uVM security policy.

Added `IsElevated() bool` function to `internal/winapi` to quite early
if the command is not run with admin privileges rather than returning a
cryptic error.

This is to support testing and benchmarking the Linux GCS.

Signed-off-by: Hamza El-Saawy <hamzaelsaawy@microsoft.com>

* PR: math

Signed-off-by: Hamza El-Saawy <hamzaelsaawy@microsoft.com>

* PR: spelling

Signed-off-by: Hamza El-Saawy <hamzaelsaawy@microsoft.com>
  • Loading branch information
helsaawy authored Apr 26, 2022
1 parent f6694ad commit 12a54a3
Show file tree
Hide file tree
Showing 5 changed files with 330 additions and 74 deletions.
222 changes: 161 additions & 61 deletions internal/tools/uvmboot/lcow.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package main

import (
"context"
"fmt"
"io"
"os"
"strings"
Expand All @@ -17,19 +18,25 @@ import (
)

const (
bootFilesPathArgName = "boot-files-path"
consolePipeArgName = "console-pipe"
kernelDirectArgName = "kernel-direct"
kernelFileArgName = "kernel-file"
forwardStdoutArgName = "fwd-stdout"
forwardStderrArgName = "fwd-stderr"
outputHandlingArgName = "output-handling"
kernelArgsArgName = "kernel-args"
rootFSTypeArgName = "root-fs-type"
vpMemMaxCountArgName = "vpmem-max-count"
vpMemMaxSizeArgName = "vpmem-max-size"
scsiMountsArgName = "mount"
shareFilesArgName = "share"
securityPolicyArgName = "security-policy"
)

var (
lcowUseTerminal bool
lcowUseTerminal bool
lcowDisableTimeSync bool
)

var lcowCommand = cli.Command{
Expand All @@ -45,6 +52,10 @@ var lcowCommand = cli.Command{
Name: rootFSTypeArgName,
Usage: "Either 'initrd' or 'vhd'. (default: 'vhd' if rootfs.vhd exists)",
},
cli.StringFlag{
Name: bootFilesPathArgName,
Usage: "The `path` to the boot files directory",
},
cli.UintFlag{
Name: vpMemMaxCountArgName,
Usage: "Number of VPMem devices on the UVM. Uses hcsshim default if not specified",
Expand All @@ -57,6 +68,19 @@ var lcowCommand = cli.Command{
Name: kernelDirectArgName,
Usage: "Use kernel direct booting for UVM (default: true on builds >= 18286)",
},
cli.StringFlag{
Name: kernelFileArgName,
Usage: "The kernel `file` to use; either 'kernel' or 'vmlinux'. (default: 'kernel')",
},
cli.BoolFlag{
Name: "disable-time-sync",
Usage: "Disable the time synchronization service",
Destination: &lcowDisableTimeSync,
},
cli.StringFlag{
Name: securityPolicyArgName,
Usage: "Security policy to set on the UVM. Leave empty to use an open door policy",
},
cli.StringFlag{
Name: execCommandLineArgName,
Usage: "Command to execute in the UVM.",
Expand All @@ -82,94 +106,169 @@ var lcowCommand = cli.Command{
Usage: "create the process in the UVM with a TTY enabled",
Destination: &lcowUseTerminal,
},
cli.StringSliceFlag{
Name: scsiMountsArgName,
Usage: "List of VHDs to SCSI mount into the UVM. Use repeat instances to add multiple. " +
"Value is of the form `'host[,guest[,w]]'`, where 'host' is path to the VHD, " +
`'guest' is an optional mount path inside the UVM, and 'w' mounts the VHD as writeable`,
},
cli.StringSliceFlag{
Name: shareFilesArgName,
Usage: "List of paths or files to plan9 share into the UVM. Use repeat instances to add multiple. " +
"Value is of the form `'host,guest[,w]' where 'host' is path to the VHD, " +
`'guest' is the mount path inside the UVM, and 'w' sets the shared files to writeable`,
},
},
Action: func(c *cli.Context) error {
runMany(c, func(id string) error {
options := uvm.NewDefaultOptionsLCOW(id, "")
setGlobalOptions(c, options.Options)
useGcs := c.GlobalBool(gcsArgName)
options.UseGuestConnection = useGcs
ctx := context.Background()

if c.IsSet(kernelDirectArgName) {
options.KernelDirect = c.Bool(kernelDirectArgName)
}
if c.IsSet(rootFSTypeArgName) {
switch strings.ToLower(c.String(rootFSTypeArgName)) {
case "initrd":
options.RootFSFile = uvm.InitrdFile
options.PreferredRootFSType = uvm.PreferredRootFSTypeInitRd
case "vhd":
options.RootFSFile = uvm.VhdFile
options.PreferredRootFSType = uvm.PreferredRootFSTypeVHD
default:
logrus.Fatalf("Unrecognized value '%s' for option %s", c.String(rootFSTypeArgName), rootFSTypeArgName)
}
}
if c.IsSet(kernelArgsArgName) {
options.KernelBootOptions = c.String(kernelArgsArgName)
}
if c.IsSet(vpMemMaxCountArgName) {
options.VPMemDeviceCount = uint32(c.Uint(vpMemMaxCountArgName))
}
if c.IsSet(vpMemMaxSizeArgName) {
options.VPMemSizeBytes = c.Uint64(vpMemMaxSizeArgName) * memory.MiB // convert from MB to bytes
}
if !useGcs {
if c.IsSet(execCommandLineArgName) {
options.ExecCommandLine = c.String(execCommandLineArgName)
}
if c.IsSet(forwardStdoutArgName) {
options.ForwardStdout = c.Bool(forwardStdoutArgName)
}
if c.IsSet(forwardStderrArgName) {
options.ForwardStderr = c.Bool(forwardStderrArgName)
}
if c.IsSet(outputHandlingArgName) {
switch strings.ToLower(c.String(outputHandlingArgName)) {
case "stdout":
options.OutputHandler = uvm.OutputHandler(func(r io.Reader) {
_, _ = io.Copy(os.Stdout, r)
})
default:
logrus.Fatalf("Unrecognized value '%s' for option %s", c.String(outputHandlingArgName), outputHandlingArgName)
}
}
}
if c.IsSet(consolePipeArgName) {
options.ConsolePipe = c.String(consolePipeArgName)
options, err := createLCOWOptions(ctx, c, id)
if err != nil {
return err
}

if err := runLCOW(context.TODO(), options, c); err != nil {
if err := runLCOW(ctx, options, c); err != nil {
return err
}

return nil
})

return nil
},
}

func init() {
lcowCommand.CustomHelpTemplate = cli.CommandHelpTemplate + "EXAMPLES:\n" +
`.\uvmboot.exe -gcs lcow -boot-files-path "C:\ContainerPlat\LinuxBootFiles" -root-fs-type vhd -t -exec "/bin/bash"`
}

func createLCOWOptions(_ context.Context, c *cli.Context, id string) (*uvm.OptionsLCOW, error) {
options := uvm.NewDefaultOptionsLCOW(id, "")
setGlobalOptions(c, options.Options)

// boot
if c.IsSet(bootFilesPathArgName) {
options.BootFilesPath = c.String(bootFilesPathArgName)
}

// kernel
if c.IsSet(kernelDirectArgName) {
options.KernelDirect = c.Bool(kernelDirectArgName)
}
if c.IsSet(kernelFileArgName) {
switch strings.ToLower(c.String(kernelFileArgName)) {
case uvm.KernelFile:
options.KernelFile = uvm.KernelFile
case uvm.UncompressedKernelFile:
options.KernelFile = uvm.UncompressedKernelFile
default:
return nil, unrecognizedError(c.String(kernelFileArgName), kernelFileArgName)
}
}
if c.IsSet(kernelArgsArgName) {
options.KernelBootOptions = c.String(kernelArgsArgName)
}

// rootfs
if c.IsSet(rootFSTypeArgName) {
switch strings.ToLower(c.String(rootFSTypeArgName)) {
case "initrd":
options.RootFSFile = uvm.InitrdFile
options.PreferredRootFSType = uvm.PreferredRootFSTypeInitRd
case "vhd":
options.RootFSFile = uvm.VhdFile
options.PreferredRootFSType = uvm.PreferredRootFSTypeVHD
default:
return nil, unrecognizedError(c.String(rootFSTypeArgName), rootFSTypeArgName)
}
}

if c.IsSet(vpMemMaxCountArgName) {
options.VPMemDeviceCount = uint32(c.Uint(vpMemMaxCountArgName))
}
if c.IsSet(vpMemMaxSizeArgName) {
options.VPMemSizeBytes = c.Uint64(vpMemMaxSizeArgName) * memory.MiB // convert from MB to bytes
}

// GCS
options.UseGuestConnection = useGCS
if !useGCS {
if c.IsSet(execCommandLineArgName) {
options.ExecCommandLine = c.String(execCommandLineArgName)
}
if c.IsSet(forwardStdoutArgName) {
options.ForwardStdout = c.Bool(forwardStdoutArgName)
}
if c.IsSet(forwardStderrArgName) {
options.ForwardStderr = c.Bool(forwardStderrArgName)
}
if c.IsSet(outputHandlingArgName) {
switch strings.ToLower(c.String(outputHandlingArgName)) {
case "stdout":
options.OutputHandler = uvm.OutputHandler(func(r io.Reader) {
_, _ = io.Copy(os.Stdout, r)
})
default:
return nil, unrecognizedError(c.String(outputHandlingArgName), outputHandlingArgName)
}
}
}
if c.IsSet(consolePipeArgName) {
options.ConsolePipe = c.String(consolePipeArgName)
}

// general settings
if lcowDisableTimeSync {
options.DisableTimeSyncService = true
}

if c.IsSet(securityPolicyArgName) {
options.SecurityPolicy = c.String(options.SecurityPolicy)
options.SecurityPolicyEnabled = true
}

return options, nil
}

func runLCOW(ctx context.Context, options *uvm.OptionsLCOW, c *cli.Context) error {
uvm, err := uvm.CreateLCOW(ctx, options)
vm, err := uvm.CreateLCOW(ctx, options)
if err != nil {
return err
}
defer uvm.Close()
defer vm.Close()

if err := uvm.Start(ctx); err != nil {
if err := vm.Start(ctx); err != nil {
return err
}

if c.IsSet(securityPolicyArgName) {
if err := vm.SetSecurityPolicy(ctx, options.SecurityPolicy); err != nil {
return fmt.Errorf("could not set UVM security policy: %w", err)
}
logrus.WithField("policy", options.SecurityPolicy).Debug("Set UVM security policy")
}

if err := mountSCSI(ctx, c, vm); err != nil {
return err
}

if err := shareFiles(ctx, c, vm); err != nil {
return err
}

if options.UseGuestConnection {
if err := execViaGcs(uvm, c); err != nil {
if err := execViaGcs(vm, c); err != nil {
return err
}
_ = uvm.Terminate(ctx)
_ = uvm.Wait()
return uvm.ExitError()
_ = vm.Terminate(ctx)
_ = vm.Wait()

return vm.ExitError()
}

return uvm.Wait()
return vm.Wait()
}

func execViaGcs(vm *uvm.UtilityVM, c *cli.Context) error {
Expand Down Expand Up @@ -197,5 +296,6 @@ func execViaGcs(vm *uvm.UtilityVM, c *cli.Context) error {
cmd.Stderr = os.Stdout // match non-GCS behavior and forward to stdout
}
}

return cmd.Run()
}
Loading

0 comments on commit 12a54a3

Please sign in to comment.