diff --git a/internal/tools/uvmboot/lcow.go b/internal/tools/uvmboot/lcow.go index 2cc93c0c17..b666d869b6 100644 --- a/internal/tools/uvmboot/lcow.go +++ b/internal/tools/uvmboot/lcow.go @@ -11,15 +11,19 @@ import ( "github.com/containerd/console" "github.com/opencontainers/runtime-spec/specs-go" + "github.com/sirupsen/logrus" "github.com/urfave/cli" "github.com/Microsoft/hcsshim/internal/cmd" "github.com/Microsoft/hcsshim/internal/log" + "github.com/Microsoft/hcsshim/internal/logfields" "github.com/Microsoft/hcsshim/internal/memory" + "github.com/Microsoft/hcsshim/internal/oci" "github.com/Microsoft/hcsshim/internal/uvm" ) const ( + annotationsArgName = "annotation" bootFilesPathArgName = "boot-files-path" consolePipeArgName = "console-pipe" kernelDirectArgName = "kernel-direct" @@ -48,6 +52,11 @@ var lcowCommand = cli.Command{ Name: "lcow", Usage: "Boot an LCOW UVM", Flags: []cli.Flag{ + cli.StringSliceFlag{ + Name: annotationsArgName, + Usage: "Annotations in the form of `key=value` to apply to the uVM. Use repeat instances to add multiple. " + + "Annotations will be applied to uVM settings BEFORE all other settings.", + }, cli.StringFlag{ Name: kernelArgsArgName, Value: "", @@ -113,7 +122,7 @@ var lcowCommand = cli.Command{ }, cli.StringFlag{ Name: consolePipeArgName, - Usage: "Named pipe for serial console output (which will be enabled)", + Usage: "Named `pipe` for serial console output (which will be enabled)", }, cli.BoolFlag{ Name: "tty,t", @@ -159,7 +168,21 @@ func init() { } func createLCOWOptions(ctx context.Context, c *cli.Context, id string) (*uvm.OptionsLCOW, error) { - options := uvm.NewDefaultOptionsLCOW(id, "") + opt, err := oci.SpecToUVMCreateOpts(ctx, + &specs.Spec{ + Linux: &specs.Linux{}, + Annotations: parseAnnotations(ctx, c, annotationsArgName), + }, + id, "", + ) + if err != nil { + return nil, err + } + options, ok := opt.(*uvm.OptionsLCOW) + if !ok { + return nil, fmt.Errorf("unexpect uVM create options type: %T", opt) + } + setGlobalOptions(c, options.Options) // boot @@ -259,6 +282,37 @@ func createLCOWOptions(ctx context.Context, c *cli.Context, id string) (*uvm.Opt return options, nil } +// parseAnnotations parses the annotations from the [cli.StringSliceFlag] specified by `name`. +func parseAnnotations(ctx context.Context, c *cli.Context, name string) map[string]string { + ss := c.StringSlice(name) + annots := map[string]string{} + + for _, s := range ss { + entry := log.G(ctx).WithField("flag-value", s) + k, v, found := strings.Cut(s, "=") + + if !found { + entry.WithField(logrus.ErrorKey, "missing `=` in annotation").Warnf("invald %s flag value", name) + } else if k == "" || v == "" { + entry.WithField(logrus.ErrorKey, "empty annotation key or value").Warnf("invald %s flag value", name) + } else { + entry = entry.WithFields(logrus.Fields{ + logfields.Key: k, + logfields.Value: v, + }) + entry.Debugf("parsed %s flag", name) + + if vv, ok := annots[k]; ok { + entry.WithField(logfields.Value+"-existing", vv).Warn("overriding existing annotation") + } + + annots[k] = v + } + } + + return annots +} + func runLCOW(ctx context.Context, options *uvm.OptionsLCOW, c *cli.Context) error { vm, err := uvm.CreateLCOW(ctx, options) if err != nil { diff --git a/internal/tools/uvmboot/main.go b/internal/tools/uvmboot/main.go index 0a631f7bc8..de2c1f7dbe 100644 --- a/internal/tools/uvmboot/main.go +++ b/internal/tools/uvmboot/main.go @@ -4,7 +4,6 @@ package main import ( "fmt" - "log" "os" "sync" "time" @@ -76,7 +75,7 @@ func main() { }, cli.BoolFlag{ Name: "debug", - Usage: "Enable debug information", + Usage: "Increase logging verbosity", Destination: &debug, }, cli.BoolFlag{ @@ -98,14 +97,15 @@ func main() { app.Before = func(c *cli.Context) error { if !winapi.IsElevated() { - log.Fatal(c.App.Name + " must be run in an elevated context") + return fmt.Errorf(c.App.Name + " must be run in an elevated context") } + lvl := logrus.WarnLevel if debug { - logrus.SetLevel(logrus.DebugLevel) - } else { - logrus.SetLevel(logrus.WarnLevel) + // as a debugging tool, opt for more logs over less + lvl = logrus.TraceLevel } + logrus.SetLevel(lvl) return nil } @@ -116,6 +116,7 @@ func main() { } func setGlobalOptions(c *cli.Context, options *uvm.Options) { + // TODO: create appropriate spec for (conf) WCOW and handle annotations here if c.GlobalIsSet(cpusArgName) { options.ProcessorCount = int32(c.GlobalUint64(cpusArgName)) } @@ -138,6 +139,7 @@ func setGlobalOptions(c *cli.Context, options *uvm.Options) { } options.ResourcePartitionID = &rpID } + // TODO: create common arg for console pipe and set `uvmConsolePipe` as the default value // Always set the console pipe in uvmboot, it helps with testing/debugging options.ConsolePipe = uvmConsolePipe } diff --git a/internal/tools/uvmboot/mounts.go b/internal/tools/uvmboot/mounts.go index 0f546b9f86..ea65f1173c 100644 --- a/internal/tools/uvmboot/mounts.go +++ b/internal/tools/uvmboot/mounts.go @@ -87,23 +87,26 @@ type mount struct { writable bool } -// parseMounts parses the mounts stored under the cli StringSlice argument, `n`. -func parseMounts(ctx context.Context, c *cli.Context, n string) []mount { - if c.IsSet(n) { - ss := c.StringSlice(n) - ms := make([]mount, 0, len(ss)) - for _, s := range ss { - log.G(ctx).Debugf("parsing %q", s) - - if m, err := mountFromString(s); err == nil { - ms = append(ms, m) - } - } +// parseMounts parses the mounts from the [cli.StringSliceFlag] specified by `name`. +func parseMounts(ctx context.Context, c *cli.Context, name string) []mount { + if !c.IsSet(name) { + return nil + } - return ms + ss := c.StringSlice(name) + ms := make([]mount, 0, len(ss)) + for _, s := range ss { + entry := log.G(ctx).WithField("flag-value", s) + + if m, err := mountFromString(s); err != nil { + entry.WithError(err).Warnf("invald %s flag value", name) + } else { + entry.Debugf("parsed %s flag", name) + ms = append(ms, m) + } } - return nil + return ms } func mountFromString(s string) (m mount, _ error) {