Skip to content

Commit 6efa5fd

Browse files
authored
Add annotation flag to uvmboot (#2521)
Allow specifying annotations for uVMs (currently LCOW only) to enable certain behavior instead of needing new flags for uVM creation options. Update `parseMounts` to log (instead of ignore) errors from parsing `mount` flag values. Add `trace` flag to enable trace (instead of just debug) level logs. Signed-off-by: Hamza El-Saawy <hamzaelsaawy@microsoft.com>
1 parent 81ce6c2 commit 6efa5fd

File tree

3 files changed

+81
-22
lines changed

3 files changed

+81
-22
lines changed

internal/tools/uvmboot/lcow.go

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,19 @@ import (
1111

1212
"github.com/containerd/console"
1313
"github.com/opencontainers/runtime-spec/specs-go"
14+
"github.com/sirupsen/logrus"
1415
"github.com/urfave/cli"
1516

1617
"github.com/Microsoft/hcsshim/internal/cmd"
1718
"github.com/Microsoft/hcsshim/internal/log"
19+
"github.com/Microsoft/hcsshim/internal/logfields"
1820
"github.com/Microsoft/hcsshim/internal/memory"
21+
"github.com/Microsoft/hcsshim/internal/oci"
1922
"github.com/Microsoft/hcsshim/internal/uvm"
2023
)
2124

2225
const (
26+
annotationsArgName = "annotation"
2327
bootFilesPathArgName = "boot-files-path"
2428
consolePipeArgName = "console-pipe"
2529
kernelDirectArgName = "kernel-direct"
@@ -48,6 +52,11 @@ var lcowCommand = cli.Command{
4852
Name: "lcow",
4953
Usage: "Boot an LCOW UVM",
5054
Flags: []cli.Flag{
55+
cli.StringSliceFlag{
56+
Name: annotationsArgName,
57+
Usage: "Annotations in the form of `key=value` to apply to the uVM. Use repeat instances to add multiple. " +
58+
"Annotations will be applied to uVM settings BEFORE all other settings.",
59+
},
5160
cli.StringFlag{
5261
Name: kernelArgsArgName,
5362
Value: "",
@@ -113,7 +122,7 @@ var lcowCommand = cli.Command{
113122
},
114123
cli.StringFlag{
115124
Name: consolePipeArgName,
116-
Usage: "Named pipe for serial console output (which will be enabled)",
125+
Usage: "Named `pipe` for serial console output (which will be enabled)",
117126
},
118127
cli.BoolFlag{
119128
Name: "tty,t",
@@ -159,7 +168,21 @@ func init() {
159168
}
160169

161170
func createLCOWOptions(ctx context.Context, c *cli.Context, id string) (*uvm.OptionsLCOW, error) {
162-
options := uvm.NewDefaultOptionsLCOW(id, "")
171+
opt, err := oci.SpecToUVMCreateOpts(ctx,
172+
&specs.Spec{
173+
Linux: &specs.Linux{},
174+
Annotations: parseAnnotations(ctx, c, annotationsArgName),
175+
},
176+
id, "",
177+
)
178+
if err != nil {
179+
return nil, err
180+
}
181+
options, ok := opt.(*uvm.OptionsLCOW)
182+
if !ok {
183+
return nil, fmt.Errorf("unexpect uVM create options type: %T", opt)
184+
}
185+
163186
setGlobalOptions(c, options.Options)
164187

165188
// boot
@@ -259,6 +282,37 @@ func createLCOWOptions(ctx context.Context, c *cli.Context, id string) (*uvm.Opt
259282
return options, nil
260283
}
261284

285+
// parseAnnotations parses the annotations from the [cli.StringSliceFlag] specified by `name`.
286+
func parseAnnotations(ctx context.Context, c *cli.Context, name string) map[string]string {
287+
ss := c.StringSlice(name)
288+
annots := map[string]string{}
289+
290+
for _, s := range ss {
291+
entry := log.G(ctx).WithField("flag-value", s)
292+
k, v, found := strings.Cut(s, "=")
293+
294+
if !found {
295+
entry.WithField(logrus.ErrorKey, "missing `=` in annotation").Warnf("invald %s flag value", name)
296+
} else if k == "" || v == "" {
297+
entry.WithField(logrus.ErrorKey, "empty annotation key or value").Warnf("invald %s flag value", name)
298+
} else {
299+
entry = entry.WithFields(logrus.Fields{
300+
logfields.Key: k,
301+
logfields.Value: v,
302+
})
303+
entry.Debugf("parsed %s flag", name)
304+
305+
if vv, ok := annots[k]; ok {
306+
entry.WithField(logfields.Value+"-existing", vv).Warn("overriding existing annotation")
307+
}
308+
309+
annots[k] = v
310+
}
311+
}
312+
313+
return annots
314+
}
315+
262316
func runLCOW(ctx context.Context, options *uvm.OptionsLCOW, c *cli.Context) error {
263317
vm, err := uvm.CreateLCOW(ctx, options)
264318
if err != nil {

internal/tools/uvmboot/main.go

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ package main
44

55
import (
66
"fmt"
7-
"log"
87
"os"
98
"sync"
109
"time"
@@ -76,7 +75,7 @@ func main() {
7675
},
7776
cli.BoolFlag{
7877
Name: "debug",
79-
Usage: "Enable debug information",
78+
Usage: "Increase logging verbosity",
8079
Destination: &debug,
8180
},
8281
cli.BoolFlag{
@@ -98,14 +97,15 @@ func main() {
9897

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

103+
lvl := logrus.WarnLevel
104104
if debug {
105-
logrus.SetLevel(logrus.DebugLevel)
106-
} else {
107-
logrus.SetLevel(logrus.WarnLevel)
105+
// as a debugging tool, opt for more logs over less
106+
lvl = logrus.TraceLevel
108107
}
108+
logrus.SetLevel(lvl)
109109

110110
return nil
111111
}
@@ -116,6 +116,7 @@ func main() {
116116
}
117117

118118
func setGlobalOptions(c *cli.Context, options *uvm.Options) {
119+
// TODO: create appropriate spec for (conf) WCOW and handle annotations here
119120
if c.GlobalIsSet(cpusArgName) {
120121
options.ProcessorCount = int32(c.GlobalUint64(cpusArgName))
121122
}
@@ -138,6 +139,7 @@ func setGlobalOptions(c *cli.Context, options *uvm.Options) {
138139
}
139140
options.ResourcePartitionID = &rpID
140141
}
142+
// TODO: create common arg for console pipe and set `uvmConsolePipe` as the default value
141143
// Always set the console pipe in uvmboot, it helps with testing/debugging
142144
options.ConsolePipe = uvmConsolePipe
143145
}

internal/tools/uvmboot/mounts.go

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -87,23 +87,26 @@ type mount struct {
8787
writable bool
8888
}
8989

90-
// parseMounts parses the mounts stored under the cli StringSlice argument, `n`.
91-
func parseMounts(ctx context.Context, c *cli.Context, n string) []mount {
92-
if c.IsSet(n) {
93-
ss := c.StringSlice(n)
94-
ms := make([]mount, 0, len(ss))
95-
for _, s := range ss {
96-
log.G(ctx).Debugf("parsing %q", s)
97-
98-
if m, err := mountFromString(s); err == nil {
99-
ms = append(ms, m)
100-
}
101-
}
90+
// parseMounts parses the mounts from the [cli.StringSliceFlag] specified by `name`.
91+
func parseMounts(ctx context.Context, c *cli.Context, name string) []mount {
92+
if !c.IsSet(name) {
93+
return nil
94+
}
10295

103-
return ms
96+
ss := c.StringSlice(name)
97+
ms := make([]mount, 0, len(ss))
98+
for _, s := range ss {
99+
entry := log.G(ctx).WithField("flag-value", s)
100+
101+
if m, err := mountFromString(s); err != nil {
102+
entry.WithError(err).Warnf("invald %s flag value", name)
103+
} else {
104+
entry.Debugf("parsed %s flag", name)
105+
ms = append(ms, m)
106+
}
104107
}
105108

106-
return nil
109+
return ms
107110
}
108111

109112
func mountFromString(s string) (m mount, _ error) {

0 commit comments

Comments
 (0)