Skip to content

Commit

Permalink
Update uvmboot WCOW, remove globals
Browse files Browse the repository at this point in the history
Update WCOW uvmboot command to work, and make functionality more inline
with LCOW's. Specifically, add the `fwd-std*` and `output-handling` flags,
and share them, along with the `exec` flag definition, between the two.

Add a `no-cmd` flag to skip prepending `cmd /c` to the exec command and
allow passing in a command line exactly.

Fix naming conflicts, where `c` was either a `*cli.Context` or `*cmd.Cmd`,
the former is now consistently `cCtx` (which matches usage elsewhere).
Rename `cmd` variables to prevent overshadowing module import.

Replace global variables with calls to `cCtx.*` flag-access functions to
avoid global (mutatable) state.

Signed-off-by: Hamza El-Saawy <hamzaelsaawy@microsoft.com>
  • Loading branch information
helsaawy committed Sep 12, 2024
1 parent 3156992 commit 494a6c5
Show file tree
Hide file tree
Showing 3 changed files with 261 additions and 188 deletions.
142 changes: 55 additions & 87 deletions internal/tools/uvmboot/lcow.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,9 @@ const (
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"
disableTimeSyncArgName = "disable-time-sync"
vpMemMaxCountArgName = "vpmem-max-count"
vpMemMaxSizeArgName = "vpmem-max-size"
scsiMountsArgName = "mount-scsi"
Expand All @@ -37,15 +35,12 @@ const (
securityPolicyEnforcerArgName = "security-policy-enforcer"
)

var (
lcowUseTerminal bool
lcowDisableTimeSync bool
)

var lcowCommand = cli.Command{
Name: "lcow",
Usage: "Boot an LCOW UVM",
Flags: []cli.Flag{
CustomHelpTemplate: cli.CommandHelpTemplate + "EXAMPLES:\n" +
` .\uvmboot.exe -gcs lcow -boot-files-path "C:\ContainerPlat\LinuxBootFiles" -root-fs-type vhd -t -exec "/bin/bash"`,
Flags: append(commonUVMFlags,
cli.StringFlag{
Name: kernelArgsArgName,
Value: "",
Expand Down Expand Up @@ -76,9 +71,8 @@ var lcowCommand = cli.Command{
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,
Name: disableTimeSyncArgName,
Usage: "Disable the time synchronization service",
},
cli.StringFlag{
Name: securityPolicyArgName,
Expand All @@ -93,31 +87,10 @@ var lcowCommand = cli.Command{
Name: securityHardwareFlag,
Usage: "Use VMGS file to run on secure hardware. ('root-fs-type' must be set to 'none')",
},
cli.StringFlag{
Name: execCommandLineArgName,
Usage: "Command to execute in the UVM.",
},
cli.BoolFlag{
Name: forwardStdoutArgName,
Usage: "Whether stdout from the process in the UVM should be forwarded",
},
cli.BoolFlag{
Name: forwardStderrArgName,
Usage: "Whether stderr from the process in the UVM should be forwarded",
},
cli.StringFlag{
Name: outputHandlingArgName,
Usage: "Controls how output from UVM is handled. Use 'stdout' to print all output to stdout",
},
cli.StringFlag{
Name: consolePipeArgName,
Usage: "Named pipe for serial console output (which will be enabled)",
},
cli.BoolFlag{
Name: "tty,t",
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. " +
Expand All @@ -134,58 +107,53 @@ var lcowCommand = cli.Command{
Name: vpmemMountsArgName,
Usage: "List of VHDs to VPMem mount into the UVM. Use repeat instances to add multiple. ",
},
},
Action: func(c *cli.Context) error {
runMany(c, func(id string) error {
),
Action: func(cCtx *cli.Context) error {
runMany(cCtx, func(id string) error {
ctx := context.Background()

options, err := createLCOWOptions(ctx, c, id)
options, err := createLCOWOptions(ctx, cCtx, id)
if err != nil {
return err
}

return runLCOW(ctx, options, c)
return runLCOW(ctx, cCtx, options)
})

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(ctx context.Context, c *cli.Context, id string) (*uvm.OptionsLCOW, error) {
func createLCOWOptions(ctx context.Context, cCtx *cli.Context, id string) (*uvm.OptionsLCOW, error) {
options := uvm.NewDefaultOptionsLCOW(id, "")
setGlobalOptions(c, options.Options)
setGlobalOptions(cCtx, options.Options)

// boot
if c.IsSet(bootFilesPathArgName) {
options.UpdateBootFilesPath(ctx, c.String(bootFilesPathArgName))
if cCtx.IsSet(bootFilesPathArgName) {
options.UpdateBootFilesPath(ctx, cCtx.String(bootFilesPathArgName))
}

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

// rootfs
if c.IsSet(rootFSTypeArgName) {
switch strings.ToLower(c.String(rootFSTypeArgName)) {
if cCtx.IsSet(rootFSTypeArgName) {
switch strings.ToLower(cCtx.String(rootFSTypeArgName)) {
case "initrd":
options.RootFSFile = uvm.InitrdFile
options.PreferredRootFSType = uvm.PreferredRootFSTypeInitRd
Expand All @@ -196,59 +164,59 @@ func createLCOWOptions(ctx context.Context, c *cli.Context, id string) (*uvm.Opt
options.RootFSFile = ""
options.PreferredRootFSType = uvm.PreferredRootFSTypeNA
default:
return nil, unrecognizedError(c.String(rootFSTypeArgName), rootFSTypeArgName)
return nil, unrecognizedError(cCtx.String(rootFSTypeArgName), rootFSTypeArgName)
}
}

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

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

// general settings
if lcowDisableTimeSync {
options.DisableTimeSyncService = true
if cCtx.IsSet(disableTimeSyncArgName) {
options.DisableTimeSyncService = cCtx.Bool(disableTimeSyncArgName)
}

// empty policy string defaults to open door
if c.IsSet(securityPolicyArgName) {
options.SecurityPolicy = c.String(securityPolicyArgName)
if cCtx.IsSet(securityPolicyArgName) {
options.SecurityPolicy = cCtx.String(securityPolicyArgName)
}
if c.IsSet(securityPolicyEnforcerArgName) {
options.SecurityPolicyEnforcer = c.String(securityPolicyEnforcerArgName)
if cCtx.IsSet(securityPolicyEnforcerArgName) {
options.SecurityPolicyEnforcer = cCtx.String(securityPolicyEnforcerArgName)
}
if c.IsSet(securityHardwareFlag) {
if cCtx.IsSet(securityHardwareFlag) {
options.GuestStateFile = uvm.GuestStateFile
options.SecurityPolicyEnabled = true
options.AllowOvercommit = false
Expand All @@ -257,7 +225,7 @@ func createLCOWOptions(ctx context.Context, c *cli.Context, id string) (*uvm.Opt
return options, nil
}

func runLCOW(ctx context.Context, options *uvm.OptionsLCOW, c *cli.Context) error {
func runLCOW(ctx context.Context, cCtx *cli.Context, options *uvm.OptionsLCOW) error {
vm, err := uvm.CreateLCOW(ctx, options)
if err != nil {
return err
Expand All @@ -270,20 +238,20 @@ func runLCOW(ctx context.Context, options *uvm.OptionsLCOW, c *cli.Context) erro
return err
}

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

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

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

if options.UseGuestConnection {
if err := execViaGCS(ctx, vm, c); err != nil {
if err := execViaGCS(ctx, vm, cCtx); err != nil {
return err
}
_ = vm.Terminate(ctx)
Expand All @@ -298,7 +266,7 @@ func runLCOW(ctx context.Context, options *uvm.OptionsLCOW, c *cli.Context) erro
func execViaGCS(ctx context.Context, vm *uvm.UtilityVM, cCtx *cli.Context) error {
c := cmd.CommandContext(ctx, vm, "sh", "-c", cCtx.String(execCommandLineArgName))
c.Log = log.L.Dup()
if lcowUseTerminal {
if cCtx.Bool(useTerminalArgName) {
c.Spec.Terminal = true
c.Stdin = os.Stdin
c.Stdout = os.Stdout
Expand Down
60 changes: 44 additions & 16 deletions internal/tools/uvmboot/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ package main

import (
"fmt"
"log"
"os"
"sync"
"time"
Expand All @@ -16,22 +15,51 @@ import (
"github.com/Microsoft/hcsshim/internal/winapi"
)

// Global flag names.
const (
debugArgName = "debug"
cpusArgName = "cpus"
memoryArgName = "memory"
allowOvercommitArgName = "allow-overcommit"
enableDeferredCommitArgName = "enable-deferred-commit"
measureArgName = "measure"
parallelArgName = "parallel"
countArgName = "count"
useGCSArgName = "gcs"
)

// Shared command flag names.
const (
execCommandLineArgName = "exec"
forwardStdoutArgName = "fwd-stdout"
forwardStderrArgName = "fwd-stderr"
outputHandlingArgName = "output-handling"
useTerminalArgName = "tty"
)

var (
debug bool
useGCS bool
)
// Shared command flags.
var commonUVMFlags = []cli.Flag{
cli.StringFlag{
Name: execCommandLineArgName,
Usage: "Command to execute in the UVM.",
},
cli.BoolFlag{
Name: forwardStdoutArgName,
Usage: "Whether stdout from the process in the UVM should be forwarded",
},
cli.BoolFlag{
Name: forwardStderrArgName,
Usage: "Whether stderr from the process in the UVM should be forwarded",
},
cli.StringFlag{
Name: outputHandlingArgName,
Usage: "Controls how output from UVM is handled. Use 'stdout' to print all output to stdout",
},
cli.BoolFlag{
Name: useTerminalArgName + ",t",
Usage: "create the process in the UVM with a TTY enabled",
},
}

type uvmRunFunc func(string) error

Expand Down Expand Up @@ -72,14 +100,12 @@ func main() {
Usage: "Enable deferred commit on the UVM",
},
cli.BoolFlag{
Name: "debug",
Usage: "Enable debug information",
Destination: &debug,
Name: debugArgName,
Usage: "Enable debug logs",
},
cli.BoolFlag{
Name: "gcs",
Usage: "Launch the GCS and perform requested operations via its RPC interface",
Destination: &useGCS,
Name: useGCSArgName,
Usage: "Launch the GCS and perform requested operations via its RPC interface. Currently LCOW only",
},
}

Expand All @@ -88,12 +114,12 @@ func main() {
wcowCommand,
}

app.Before = func(c *cli.Context) error {
app.Before = func(cCtx *cli.Context) error {
if !winapi.IsElevated() {
log.Fatal(c.App.Name + " must be run in an elevated context")
return fmt.Errorf(cCtx.App.Name + " must be run in an elevated context")
}

if debug {
if cCtx.Bool(debugArgName) {
logrus.SetLevel(logrus.DebugLevel)
} else {
logrus.SetLevel(logrus.WarnLevel)
Expand All @@ -103,7 +129,8 @@ func main() {
}

if err := app.Run(os.Args); err != nil {
logrus.Fatalf("%v\n", err)
fmt.Fprintln(app.ErrWriter, err)
os.Exit(1)
}
}

Expand All @@ -122,7 +149,8 @@ func setGlobalOptions(c *cli.Context, options *uvm.Options) {
}
}

// todo: add a context here to propagate cancel/timeouts to runFunc uvm
// TODO: add a context here to propagate cancel/timeouts to runFunc uvm
// TODO: [runMany] can theoretically call runFunc multiple times on the same goroutine and starve others, fix that

func runMany(c *cli.Context, runFunc uvmRunFunc) {
parallelCount := c.GlobalInt(parallelArgName)
Expand Down
Loading

0 comments on commit 494a6c5

Please sign in to comment.