diff --git a/src/minimega/container.go b/src/minimega/container.go index 7d28a30f2..9e834cd7e 100644 --- a/src/minimega/container.go +++ b/src/minimega/container.go @@ -259,6 +259,22 @@ type ContainerConfig struct { // // Note: this configuration only applies to containers. Fifos uint64 + + // Attach one or more volumes to a container. These directories will be + // mounted inside the container at the specified location. + // + // For example, to mount /scratch/data to /data inside the container: + // + // vm config volume /data /scratch/data + // + // Commands with the same will overwrite previous volumes: + // + // vm config volume /data /scratch/data2 + // vm config volume /data + // /scratch/data2 + // + // Note: this configuration only applies to containers. + VolumePaths map[string]string } type ContainerVM struct { @@ -372,8 +388,8 @@ func containerTeardown() { // 7 : uuid // 8 : number of fifos // 9 : preinit program -// 10 : init program (relative to filesystem path) -// 11: init args +// 10+: source:target volumes, `--` signifies end +// 11+ : init program and args (relative to filesystem path) func containerShim() { args := flag.Args() if flag.NArg() < 11 { // 11 because init args can be nil @@ -411,7 +427,19 @@ func containerShim() { log.Fatalln(err) } vmPreinit := args[9] - vmInit := args[10:] + + // find `--` separator between vmVolumes and vmInit + var vmVolumes, vmInit []string + for i, v := range args[10:] { + if v == "--" { + vmInit = args[10+i+1:] + break + } + vmVolumes = append(vmVolumes, v) + } + + log.Debug("vmVolumes: %v", vmVolumes) + log.Debug("vmInit: %v", vmInit) // set hostname log.Debug("vm %v hostname", vmID) @@ -435,6 +463,13 @@ func containerShim() { log.Fatal("containerMountDefaults: %v", err) } + // mount volumes + log.Debug("vm %v containerMountVolumes", vmID) + err = containerMountVolumes(vmFSPath, vmVolumes) + if err != nil { + log.Fatal("containerMountVolumes: %v", err) + } + // mknod log.Debug("vm %v containerMknodDevices", vmID) err = containerMknodDevices(vmFSPath) @@ -590,8 +625,11 @@ func (old ContainerConfig) Copy() ContainerConfig { // Copy all fields res := old - // Make deep copy of slices - // none yet - placeholder + // Make deep copy of volumes + res.VolumePaths = map[string]string{} + for k, v := range old.VolumePaths { + res.VolumePaths[k] = v + } return res } @@ -717,6 +755,8 @@ func (vm *ContainerVM) Info(field string) (string, error) { switch field { case "console_port": return strconv.Itoa(vm.ConsolePort), nil + case "volume": + return marshal(vm.VolumePaths), nil } return vm.ContainerConfig.Info(field) @@ -762,6 +802,10 @@ func (vm *ContainerConfig) String() string { fmt.Fprintf(w, "Init:\t%v\n", vm.Init) fmt.Fprintf(w, "Pre-init:\t%v\n", vm.Preinit) fmt.Fprintf(w, "FIFOs:\t%v\n", vm.Fifos) + fmt.Fprintf(w, "Volumes:\t\n") + for k, v := range vm.VolumePaths { + fmt.Fprintf(w, "\t%v -> %v\n", k, v) + } w.Flush() fmt.Fprintln(&o) return o.String() @@ -881,6 +925,13 @@ func (vm *ContainerVM) launch() error { fmt.Sprintf("%v", vm.Fifos), preinit, } + for k, v := range vm.VolumePaths { + // Create source:target pairs + // TODO: should probably handle spaces + args = append(args, fmt.Sprintf("%v:%v", v, k)) + } + // denotes end of volumes + args = append(args, "--") args = append(args, vm.Init...) // launch the container @@ -1549,6 +1600,28 @@ func containerMountDefaults(fsPath string) error { return nil } +func containerMountVolumes(fsPath string, volumes []string) error { + for _, v := range volumes { + f := strings.Split(v, ":") + if len(f) != 2 { + return fmt.Errorf("invalid volume, expected `source:target`: %v", v) + } + + source := f[0] + target := filepath.Join(fsPath, f[1]) + + if err := os.MkdirAll(target, 0755); err != nil { + return err + } + + if err := syscall.Mount(source, target, "", syscall.MS_BIND, ""); err != nil { + return err + } + } + + return nil +} + // aggressively cleanup container cruff, called by the nuke api func containerNuke() { // walk minimega cgroups for tasks, killing each one @@ -1653,6 +1726,7 @@ func containerCleanCgroupDirs() { filepath.Join(*f_cgroup, "freezer", "minimega"), filepath.Join(*f_cgroup, "memory", "minimega"), filepath.Join(*f_cgroup, "devices", "minimega"), + filepath.Join(*f_cgroup, "cpu", "minimega"), } for _, d := range paths { _, err := os.Stat(d) diff --git a/src/minimega/misc.go b/src/minimega/misc.go index 334aef40c..b2db7bf54 100644 --- a/src/minimega/misc.go +++ b/src/minimega/misc.go @@ -6,6 +6,7 @@ package main import ( "bytes" + "encoding/json" "errors" "fmt" "image" @@ -294,6 +295,18 @@ func mustWrite(fpath, data string) { } } +// marshal returns the JSON-marshaled version of `v`. If we are unable to +// marshal it for whatever reason, we log an error and return an empty string. +func marshal(v interface{}) string { + b, err := json.Marshal(v) + if err != nil { + log.Error("unable to marshal %v: %v", v, err) + return "" + } + + return string(b) +} + // PermStrings creates a random permutation of the source slice using the // "inside-out" version of the Fisher-Yates algorithm. func PermStrings(source []string) []string { diff --git a/src/minimega/vm.go b/src/minimega/vm.go index ac4a1c599..079a48e0c 100644 --- a/src/minimega/vm.go +++ b/src/minimega/vm.go @@ -134,7 +134,8 @@ var vmInfo = []string{ "vcpus", "disk", "snapshot", "initrd", "kernel", "cdrom", "migrate", "append", "serial-ports", "virtio-ports", "vnc_port", // container fields - "filesystem", "hostname", "init", "preinit", "fifo", "console_port", + "filesystem", "hostname", "init", "preinit", "fifo", "volume", + "console_port", // more generic fields (tags can be huge so throw it at the end) "tags", } @@ -630,7 +631,7 @@ func (vm *BaseVM) Info(field string) (string, error) { } } case "tags": - return vm.Tags.String(), nil + return marshal(vm.Tags), nil case "cc_active": return strconv.FormatBool(vm.ActiveCC), nil default: diff --git a/src/minimega/vmconfig.go b/src/minimega/vmconfig.go index 0a6cd6f16..79627c3f1 100644 --- a/src/minimega/vmconfig.go +++ b/src/minimega/vmconfig.go @@ -9,15 +9,11 @@ package main import ( "bridge" "bytes" - "encoding/json" "fmt" - log "minilog" "strings" "text/tabwriter" ) -type Tags map[string]string - // VMConfig contains all the configs possible for a VM. When a VM of a // particular kind is launched, only the pertinent configuration is copied so // fields from other configs will have the zero value for the field type. @@ -66,8 +62,9 @@ type BaseConfig struct { // Networks for the VM, handler is not generated by vmconfiger. Networks []NetConfig - // Tags for the VM, handler is not generated by vmconfiger. - Tags Tags + // Set tags in the same manner as "vm tag". These tags will apply to all + // newly launched VMs. + Tags map[string]string } // NetConfig contains all the network-related config for an interface. The IP @@ -141,7 +138,7 @@ func (vm *BaseConfig) String() string { fmt.Fprintf(w, "Schedule host:\t%v\n", vm.Schedule) fmt.Fprintf(w, "Coschedule limit:\t%v\n", vm.Coschedule) if vm.Tags != nil { - fmt.Fprintf(w, "Tags:\t%v\n", vm.Tags) + fmt.Fprintf(w, "Tags:\t%v\n", marshal(vm.Tags)) } else { fmt.Fprint(w, "Tags:\t{}\n") } @@ -186,16 +183,6 @@ func (vm *BaseConfig) QosString(b, t, i string) string { return strings.Trim(val, " ") } -func (t Tags) String() string { - res, err := json.Marshal(t) - if err != nil { - log.Error("unable to marshal Tags: %v", err) - return "" - } - - return string(res) -} - // TODO: Handle if there are spaces or commas in the tap/bridge names func (net NetConfig) String() (s string) { parts := []string{} diff --git a/src/minimega/vmconfig_cli.go b/src/minimega/vmconfig_cli.go index ea76bb0f4..c9b6d1263 100644 --- a/src/minimega/vmconfig_cli.go +++ b/src/minimega/vmconfig_cli.go @@ -108,17 +108,6 @@ Calling vm config net with no arguments prints the current configuration.`, }, Call: wrapSimpleCLI(cliVMConfigNet), }, - { // vm config tag - HelpShort: "set tags for newly launched VMs", - HelpLong: ` -Set tags in the same manner as "vm tag". These tags will apply to all newly -launched VMs.`, - Patterns: []string{ - "vm config tag [key]", - "vm config tag ", - }, - Call: wrapSimpleCLI(cliVMConfigTag), - }, { // vm config qemu-override HelpShort: "override parts of the QEMU launch string", HelpLong: ` @@ -216,28 +205,6 @@ func cliVMConfigNet(c *minicli.Command, resp *minicli.Response) error { return nil } -func cliVMConfigTag(c *minicli.Command, resp *minicli.Response) error { - k := c.StringArgs["key"] - - // if Tags were cleared, reinitialize them - if vmConfig.Tags == nil { - vmConfig.Tags = map[string]string{} - } - - if v, ok := c.StringArgs["value"]; ok { - // Setting a new value - vmConfig.Tags[k] = v - } else if k != "" { - // Printing a single tag - resp.Response = vmConfig.Tags[k] - } else { - // Printing all configured tags - resp.Response = vmConfig.Tags.String() - } - - return nil -} - func cliVMConfigQemuOverride(c *minicli.Command, resp *minicli.Response) error { if len(c.StringArgs) == 0 { resp.Response = vmConfig.qemuOverrideString() diff --git a/src/minimega/vmconfiger_cli.go b/src/minimega/vmconfiger_cli.go index c9d5e29fd..5a399675b 100644 --- a/src/minimega/vmconfiger_cli.go +++ b/src/minimega/vmconfiger_cli.go @@ -3,6 +3,7 @@ package main import ( + "bytes" "fmt" "minicli" log "minilog" @@ -11,6 +12,19 @@ import ( "strconv" ) +func checkPath(v string) string { + // Ensure that relative paths are always relative to /files/ + if !filepath.IsAbs(v) { + v = filepath.Join(*f_iomBase, v) + } + + if _, err := os.Stat(v); os.IsNotExist(err) { + log.Warn("file does not exist: %v", v) + } + + return v +} + var vmconfigerCLIHandlers = []minicli.Handler{ { HelpShort: "configures filesystem", @@ -29,16 +43,7 @@ Note: this configuration only applies to containers and must be specified. return nil } - v := c.StringArgs["value"] - - // Ensure that relative paths are always relative to /files/ - if !filepath.IsAbs(v) { - v = filepath.Join(*f_iomBase, v) - } - - if _, err := os.Stat(v); os.IsNotExist(err) { - log.Warn("file does not exist: %v", v) - } + v := checkPath(c.StringArgs["value"]) vmConfig.FilesystemPath = v @@ -160,6 +165,57 @@ Note: this configuration only applies to containers. return nil }), }, + { + HelpShort: "configures volume", + HelpLong: `Attach one or more volumes to a container. These directories will be +mounted inside the container at the specified location. + +For example, to mount /scratch/data to /data inside the container: + + vm config volume /data /scratch/data + +Commands with the same will overwrite previous volumes: + + vm config volume /data /scratch/data2 + vm config volume /data + /scratch/data2 + +Note: this configuration only applies to containers. +`, + Patterns: []string{ + "vm config volume", + "vm config volume [value]", + }, + Call: wrapSimpleCLI(func(c *minicli.Command, r *minicli.Response) error { + if c.StringArgs["key"] == "" { + var b bytes.Buffer + + for k, v := range vmConfig.VolumePaths { + fmt.Fprintf(&b, "%v -> %v\n", k, v) + } + + r.Response = b.String() + return nil + } + + if c.StringArgs["value"] == "" { + if vmConfig.VolumePaths != nil { + r.Response = vmConfig.VolumePaths[c.StringArgs["value"]] + } + return nil + } + + if vmConfig.VolumePaths == nil { + vmConfig.VolumePaths = make(map[string]string) + } + + v := checkPath(c.StringArgs["value"]) + + vmConfig.VolumePaths[c.StringArgs["key"]] = v + + return nil + }), + }, { HelpShort: "configures qemu", HelpLong: `Set the QEMU process to invoke. Relative paths are ok. When unspecified, @@ -176,16 +232,7 @@ Note: this configuration only applies to KVM-based VMs. return nil } - v := c.StringArgs["value"] - - // Ensure that relative paths are always relative to /files/ - if !filepath.IsAbs(v) { - v = filepath.Join(*f_iomBase, v) - } - - if _, err := os.Stat(v); os.IsNotExist(err) { - log.Warn("file does not exist: %v", v) - } + v := checkPath(c.StringArgs["value"]) vmConfig.QemuPath = v @@ -208,16 +255,7 @@ Note: this configuration only applies to KVM-based VMs. return nil } - v := c.StringArgs["value"] - - // Ensure that relative paths are always relative to /files/ - if !filepath.IsAbs(v) { - v = filepath.Join(*f_iomBase, v) - } - - if _, err := os.Stat(v); os.IsNotExist(err) { - log.Warn("file does not exist: %v", v) - } + v := checkPath(c.StringArgs["value"]) vmConfig.KernelPath = v @@ -240,16 +278,7 @@ Note: this configuration only applies to KVM-based VMs. return nil } - v := c.StringArgs["value"] - - // Ensure that relative paths are always relative to /files/ - if !filepath.IsAbs(v) { - v = filepath.Join(*f_iomBase, v) - } - - if _, err := os.Stat(v); os.IsNotExist(err) { - log.Warn("file does not exist: %v", v) - } + v := checkPath(c.StringArgs["value"]) vmConfig.InitrdPath = v @@ -272,16 +301,7 @@ Note: this configuration only applies to KVM-based VMs. return nil } - v := c.StringArgs["value"] - - // Ensure that relative paths are always relative to /files/ - if !filepath.IsAbs(v) { - v = filepath.Join(*f_iomBase, v) - } - - if _, err := os.Stat(v); os.IsNotExist(err) { - log.Warn("file does not exist: %v", v) - } + v := checkPath(c.StringArgs["value"]) vmConfig.CdromPath = v @@ -307,16 +327,7 @@ Note: this configuration only applies to KVM-based VMs. return nil } - v := c.StringArgs["value"] - - // Ensure that relative paths are always relative to /files/ - if !filepath.IsAbs(v) { - v = filepath.Join(*f_iomBase, v) - } - - if _, err := os.Stat(v); os.IsNotExist(err) { - log.Warn("file does not exist: %v", v) - } + v := checkPath(c.StringArgs["value"]) vmConfig.MigratePath = v @@ -475,16 +486,8 @@ Note: this configuration only applies to KVM-based VMs. vals := c.ListArgs["value"] - for i, v := range vals { - // Ensure that relative paths are always relative to /files/ - if !filepath.IsAbs(v) { - v = filepath.Join(*f_iomBase, v) - vals[i] = v - } - - if _, err := os.Stat(v); os.IsNotExist(err) { - log.Warn("file does not exist: %v", v) - } + for i := range vals { + vals[i] = checkPath(vals[i]) } vmConfig.DiskPaths = vals @@ -657,6 +660,43 @@ Default: -1 return nil }), }, + { + HelpShort: "configures tags", + HelpLong: `Set tags in the same manner as "vm tag". These tags will apply to all +newly launched VMs. +`, + Patterns: []string{ + "vm config tags", + "vm config tags [value]", + }, + Call: wrapSimpleCLI(func(c *minicli.Command, r *minicli.Response) error { + if c.StringArgs["key"] == "" { + var b bytes.Buffer + + for k, v := range vmConfig.Tags { + fmt.Fprintf(&b, "%v -> %v\n", k, v) + } + + r.Response = b.String() + return nil + } + + if c.StringArgs["value"] == "" { + if vmConfig.Tags != nil { + r.Response = vmConfig.Tags[c.StringArgs["value"]] + } + return nil + } + + if vmConfig.Tags == nil { + vmConfig.Tags = make(map[string]string) + } + + vmConfig.Tags[c.StringArgs["key"]] = c.StringArgs["value"] + + return nil + }), + }, { HelpShort: "reset one or more configurations to default value", Patterns: []string{ @@ -686,6 +726,7 @@ Default: -1 "clear vm config ", "clear vm config ", "clear vm config ", + "clear vm config ", }, Call: wrapSimpleCLI(func(c *minicli.Command, r *minicli.Response) error { // at most one key will be set in BoolArgs but we don't know what it @@ -775,6 +816,9 @@ func (v *ContainerConfig) Info(field string) (string, error) { if field == "fifos" { return strconv.FormatUint(v.Fifos, 10), nil } + if field == "volume" { + return fmt.Sprintf("%v", v.VolumePaths), nil + } return "", fmt.Errorf("invalid info field: %v", field) } @@ -795,6 +839,9 @@ func (v *ContainerConfig) Clear(mask string) { if mask == Wildcard || mask == "fifos" { v.Fifos = 0 } + if mask == Wildcard || mask == "volume" { + v.VolumePaths = nil + } } func (v *KVMConfig) Info(field string) (string, error) { diff --git a/src/vmconfiger/generator.go b/src/vmconfiger/generator.go index fb226587c..01b47a9db 100644 --- a/src/vmconfiger/generator.go +++ b/src/vmconfiger/generator.go @@ -76,7 +76,8 @@ func (g *Generator) Format() []byte { func (g *Generator) Run() error { fs := token.NewFileSet() - t := template.Must(template.New("funcs").Parse(funcsTemplate)) + t := template.Must(template.New("header").Parse(headerTemplate)) + template.Must(t.New("funcs").Parse(funcsTemplate)) template.Must(t.New("clear").Parse(clearTemplate)) // template name must match the type it intends to generate template.Must(t.New("int64").Parse(numTemplate)) @@ -84,6 +85,7 @@ func (g *Generator) Run() error { template.Must(t.New("bool").Parse(boolTemplate)) template.Must(t.New("string").Parse(stringTemplate)) template.Must(t.New("slice").Parse(sliceTemplate)) + template.Must(t.New("map").Parse(mapTemplate)) g.template = t @@ -106,18 +108,12 @@ func (g *Generator) Run() error { } // Print the header and package clause. - g.Printf("// Code generated by \"vmconfiger %s\"; DO NOT EDIT\n\n", strings.Join(os.Args[1:], " ")) - g.Printf("package %s\n", g.pkg.Name) - g.Printf(`import ( - "fmt" - "minicli" - log "minilog" - "os" - "path/filepath" - "strconv" -)`) - g.Printf("\n") - g.Printf("var vmconfigerCLIHandlers = []minicli.Handler{\n") + g.Execute("header", struct { + Args, Package string + }{ + Args: strings.Join(os.Args[1:], " "), + Package: g.pkg.Name, + }) g.fields = map[string][]Field{} @@ -265,6 +261,41 @@ func (g *Generator) handleNode(node ast.Node) bool { g.fields[strctName] = append(g.fields[strctName], f) g.Execute("slice", f) + case *ast.MapType: + v, ok := typ.Key.(*ast.Ident) + v2, ok2 := typ.Value.(*ast.Ident) + if !ok || v.Name != "string" || !ok2 || v2.Name != "string" { + log.Error("unhandled type: %v", typ) + // always add field, even if we don't generate the handler + g.fields[strctName] = append(g.fields[strctName], Field{ + Field: name, + ConfigName: configName, + Default: "nil", + Doc: doc, + }) + + continue + } + + zero := "nil" + if f := strings.Fields(getDefault(doc, "")); len(f) > 0 { + for i := range f { + f[i] = strings.TrimSuffix(f[i], ",") + } + zero = fmt.Sprintf("map[string]string{%v}", strings.Join(f, ",")) + } + + f := Field{ + Field: name, + ConfigName: configName, + Default: zero, + Doc: doc, + Path: strings.Contains(name, "Path"), + } + + g.fields[strctName] = append(g.fields[strctName], f) + + g.Execute("map", f) default: log.Error("unhandled type for %v: %v", name, typ) // always add field, even if we don't generate the handler diff --git a/src/vmconfiger/templates.go b/src/vmconfiger/templates.go index efcd62280..129f0d2fe 100644 --- a/src/vmconfiger/templates.go +++ b/src/vmconfiger/templates.go @@ -4,6 +4,37 @@ package main +const headerTemplate = ` +// Code generated by "vmconfiger {{ .Args }}"; DO NOT EDIT + +package {{ .Package }} + +import ( + "fmt" + "minicli" + log "minilog" + "bytes" + "os" + "path/filepath" + "strconv" +) + +func checkPath(v string) string { + // Ensure that relative paths are always relative to /files/ + if !filepath.IsAbs(v) { + v = filepath.Join(*f_iomBase, v) + } + + if _, err := os.Stat(v); os.IsNotExist(err) { + log.Warn("file does not exist: %v", v) + } + + return v +} + +var vmconfigerCLIHandlers = []minicli.Handler{ +` + const stringTemplate = `{ HelpShort: "configures {{ .ConfigName }}", HelpLong: ` + "`{{ .Doc }}`," + ` @@ -17,16 +48,7 @@ const stringTemplate = `{ } {{ if .Path }} - v := c.StringArgs["value"] - - // Ensure that relative paths are always relative to /files/ - if !filepath.IsAbs(v) { - v = filepath.Join(*f_iomBase, v) - } - - if _, err := os.Stat(v); os.IsNotExist(err) { - log.Warn("file does not exist: %v", v) - } + v := checkPath(c.StringArgs["value"]) vmConfig.{{ .Field }} = v {{ else }} @@ -57,21 +79,56 @@ const sliceTemplate = `{ {{ if .Path }} vals := c.ListArgs["value"] - for i, v := range vals { - // Ensure that relative paths are always relative to /files/ - if !filepath.IsAbs(v) { - v = filepath.Join(*f_iomBase, v) - vals[i] = v + for i := range vals { + vals[i] = checkPath(vals[i]) + } + + vmConfig.{{ .Field }} = vals + {{ else }} + vmConfig.{{ .Field }} = c.ListArgs["value"] + {{ end }} + + return nil + }), +}, +` + +const mapTemplate = `{ + HelpShort: "configures {{ .ConfigName }}", + HelpLong: ` + "`{{ .Doc }}`," + ` + Patterns: []string{ + "vm config {{ .ConfigName }}", + "vm config {{ .ConfigName }} [value]", + }, + Call: wrapSimpleCLI(func (c *minicli.Command, r *minicli.Response) error { + if c.StringArgs["key"] == "" { + var b bytes.Buffer + + for k, v := range vmConfig.{{ .Field }} { + fmt.Fprintf(&b, "%v -> %v\n", k, v) } - if _, err := os.Stat(v); os.IsNotExist(err) { - log.Warn("file does not exist: %v", v) + r.Response = b.String() + return nil + } + + if c.StringArgs["value"] == "" { + if vmConfig.{{ .Field }} != nil { + r.Response = vmConfig.{{ .Field }}[c.StringArgs["value"]] } + return nil } - vmConfig.{{ .Field }} = vals + if vmConfig.{{ .Field }} == nil { + vmConfig.{{ .Field }} = make(map[string]string) + } + + {{ if .Path }} + v := checkPath(c.StringArgs["value"]) + + vmConfig.{{ .Field }}[c.StringArgs["key"]] = v {{ else }} - vmConfig.{{ .Field }} = c.ListArgs["value"] + vmConfig.{{ .Field }}[c.StringArgs["key"]] = c.StringArgs["value"] {{ end }} return nil diff --git a/tests/container_volumes b/tests/container_volumes new file mode 100644 index 000000000..0ff89725e --- /dev/null +++ b/tests/container_volumes @@ -0,0 +1,20 @@ +vm config filesystem $containerfs +shell mkdir /tmp/minivolume +shell touch /tmp/minivolume/tumbleweed + +vm config volume /scratch /tmp/minivolume +vm config volume +vm config uuid 67fd0bd5-d419-4ec2-94a7-077f52450e7a +vm launch container foo + +vm start all + +cc exec ls /scratch + +# wait for command to check in +shell sleep 10 + +cc response all + +# cleanup +shell rm -r /tmp/minivolume diff --git a/tests/container_volumes.want b/tests/container_volumes.want new file mode 100644 index 000000000..ef705ca06 --- /dev/null +++ b/tests/container_volumes.want @@ -0,0 +1,23 @@ +## vm config filesystem $containerfs +## shell mkdir /tmp/minivolume +## shell touch /tmp/minivolume/tumbleweed + +## vm config volume /scratch /tmp/minivolume +## vm config volume +/scratch -> /tmp/minivolume +## vm config uuid 67fd0bd5-d419-4ec2-94a7-077f52450e7a +## vm launch container foo + +## vm start all + +## cc exec ls /scratch + +## # wait for command to check in +## shell sleep 10 + +## cc response all +1/67fd0bd5-d419-4ec2-94a7-077f52450e7a/stdout: +tumbleweed + +## # cleanup +## shell rm -r /tmp/minivolume diff --git a/tests/vm_config_save.want b/tests/vm_config_save.want index 13d6bcc1d..9e0e16a7f 100644 --- a/tests/vm_config_save.want +++ b/tests/vm_config_save.want @@ -27,6 +27,7 @@ Hostname: Init: [/init] Pre-init: FIFOs: 0 +Volumes: ## # Set a bunch of things ## vm config uuid 9131e2a3-ad6b-4f38-b5ba-5ff2b19e32dc @@ -63,6 +64,7 @@ Hostname: Init: [/init] Pre-init: FIFOs: 0 +Volumes: ## # Test save/clear/restore ## vm config save test @@ -96,6 +98,7 @@ Hostname: Init: [/init] Pre-init: FIFOs: 0 +Volumes: ## vm config restore test ## vm config Current VM configuration: @@ -126,3 +129,4 @@ Hostname: Init: [/init] Pre-init: FIFOs: 0 +Volumes: diff --git a/tests/vm_tags.want b/tests/vm_tags.want index ab434a355..39ff5bb38 100644 --- a/tests/vm_tags.want +++ b/tests/vm_tags.want @@ -1,7 +1,7 @@ ## # Set some tags and launch VMs ## vm config tag color red ## vm config tag -{"color":"red"} +color -> red ## vm launch kvm vm0 ## vm config tag shape square ## vm launch kvm vm[1,2] @@ -33,7 +33,6 @@ tags ## # Make sure that clear only affects the configured tags ## clear vm config tag ## vm config tag -{} ## .column tags vm info tags {"color":"blue","shape":"square"}