diff --git a/README.md b/README.md index fd5f21f8..b96521aa 100644 --- a/README.md +++ b/README.md @@ -852,6 +852,30 @@ adt-buildvm-ubuntu-cloud ``` When done move the downloaded image into the location described above. +The QEMU backend will normally create the VM with the default device +backends. The backends for the network and drive devices can be overriden +using the `device-backends` map in the systems configuration, and can be +set to any string which will then be passed to QEMU. Currently, only +setting the backend for the network and drive interfaces are supported. + +For example, to set the drive backend to `virtio` and the network backend +to `virtio-net-pci`, the following system configuration could be used: + +_$PROJECT/spread.yaml_ +``` +backends: + qemu: + systems: + - ubuntu-16.04: + username: ubuntu + password: ubuntu + device-backends: + drive: virtio + network: virtio-net-pci +``` + +Care must be taken when setting these values as they are passed verbatim +to the QEMU instance, without checking if the backend is supported by QEMU. diff --git a/spread/project.go b/spread/project.go index e70470f0..47542bf7 100644 --- a/spread/project.go +++ b/spread/project.go @@ -139,6 +139,13 @@ type System struct { Priority OptionalInt Manual bool + + // Specify the backends to use for devices in the system under test. + // The specific effect of this will depend on the backend used for + // this system. + // Currently, only the qemu backend supports this, and only for the + // drive and network device drivers. + DeviceBackends DeviceBackendsMap `yaml:"device-backends"` } func (system *System) String() string { return system.Backend + ":" + system.Name } @@ -1363,3 +1370,5 @@ func sortedKeys(m map[string]bool) []string { sort.Strings(keys) return keys } + +type DeviceBackendsMap map[string]string diff --git a/spread/project_test.go b/spread/project_test.go index b51ff447..4414429e 100644 --- a/spread/project_test.go +++ b/spread/project_test.go @@ -74,6 +74,9 @@ backends: - system-2: plan: plan-for-2 - system-3: + - system-4: + device-backends: + test-device: test-backend suites: tests/: summary: mock tests @@ -91,6 +94,8 @@ suites: c.Check(backend.Systems["system-1"].Plan, Equals, "global-plan") c.Check(backend.Systems["system-2"].Plan, Equals, "plan-for-2") c.Check(backend.Systems["system-3"].Plan, Equals, "global-plan") + c.Check(len(backend.Systems["system-4"].DeviceBackends), Equals, 1) + c.Check(backend.Systems["system-4"].DeviceBackends["test-device"], Equals, "test-backend") } func (s *projectSuite) TestOptionalInt(c *C) { diff --git a/spread/qemu.go b/spread/qemu.go index 20f0bdb0..a51b06ad 100644 --- a/spread/qemu.go +++ b/spread/qemu.go @@ -6,6 +6,7 @@ import ( "os" "os/exec" "path/filepath" + "regexp" "strconv" "golang.org/x/net/context" @@ -121,19 +122,58 @@ func biosPath(biosName string) (string, error) { return "", fmt.Errorf("cannot find bios path for %q", biosName) } +func setDefaultDeviceBackends(system *System) error { + if system.DeviceBackends == nil { + system.DeviceBackends = map[string]string{} + } + + defaults := map[string]string{ + "drive": "none", + "network": "e1000", + } + + // Set the default device backends for the devices that were not set in + // the system YAML + for device, defaultBackend := range defaults { + if _, ok := system.DeviceBackends[device]; !ok { + system.DeviceBackends[device] = defaultBackend + } + } + + // Make sure that the values set in the configuration are sane and don't + // allow the user to pass arbitrary arguments to QEMU by ensuring that + // the value is a single word + r := regexp.MustCompile(`^\S+$`) + + for device, backend := range system.DeviceBackends { + if !r.Match([]byte(backend)) { + return fmt.Errorf(`invalid backend for device %s: "%s"`, device, backend) + } + } + + return nil +} + func qemuCmd(system *System, path string, mem, port int) (*exec.Cmd, error) { + err := setDefaultDeviceBackends(system) + if err != nil { + return nil, err + } + serial := fmt.Sprintf("telnet:127.0.0.1:%d,server,nowait", port+100) monitor := fmt.Sprintf("telnet:127.0.0.1:%d,server,nowait", port+200) - fwd := fmt.Sprintf("user,hostfwd=tcp:127.0.0.1:%d-:22", port) + fwd := fmt.Sprintf("user,id=user0,hostfwd=tcp:127.0.0.1:%d-:22", port) + netdev := fmt.Sprintf("netdev=user0,driver=%s", system.DeviceBackends["network"]) + drivedev := fmt.Sprintf("file=%s,format=raw,if=%s", path, system.DeviceBackends["drive"]) cmd := exec.Command("qemu-system-x86_64", "-enable-kvm", "-snapshot", "-m", strconv.Itoa(mem), - "-net", "nic", - "-net", fwd, + "-netdev", fwd, + "-device", netdev, "-serial", serial, "-monitor", monitor, - path) + "-drive", drivedev) if os.Getenv("SPREAD_QEMU_GUI") != "1" { cmd.Args = append([]string{cmd.Args[0], "-nographic"}, cmd.Args[1:]...) } diff --git a/spread/qemu_test.go b/spread/qemu_test.go index 619448ec..5d1149f8 100644 --- a/spread/qemu_test.go +++ b/spread/qemu_test.go @@ -1,6 +1,7 @@ package spread_test import ( + "fmt" "io/ioutil" "os" "path/filepath" @@ -91,3 +92,87 @@ func (s *qemuSuite) TestQemuCmdWithEfi(c *C) { c.Check(strings.Contains(s, ":-bios:/usr/share/OVMF/OVMF_CODE.fd:"), Equals, tc.UseBiosQemuOption) } } + +func (s *qemuSuite) TestQemuDeviceBackends(c *C) { + imageName := "ubuntu-20.06-64" + + restore := makeMockQemuImg(c, imageName) + defer restore() + + path := "/path/to/image" + + tests := []struct { + deviceBackends map[string]string + driveDevString string + netDevString string + expectedErr string + }{ + { + map[string]string{}, + fmt.Sprintf("file=%s,format=raw,if=none", path), + "netdev=user0,driver=e1000", + "", + }, + { + map[string]string{ + "drive": "virtio", + }, + fmt.Sprintf("file=%s,format=raw,if=virtio", path), + "netdev=user0,driver=e1000", + "", + }, + { + map[string]string{ + "network": "virtio-net-pci", + }, + fmt.Sprintf("file=%s,format=raw,if=none", path), + "netdev=user0,driver=virtio-net-pci", + "", + }, + { + map[string]string{ + "drive": "virtio", + "network": "virtio-net-pci", + }, + fmt.Sprintf("file=%s,format=raw,if=virtio", path), + "netdev=user0,driver=virtio-net-pci", + "", + }, + { + map[string]string{ + "drive": "invalid drive backend", + }, + "", + "", + `invalid backend for device drive: "invalid drive backend"`, + }, + { + map[string]string{ + "drive": "", + }, + "", + "", + `invalid backend for device drive: ""`, + }, + } + + for _, tc := range tests { + ms := &spread.System{ + Name: "some-name", + Image: imageName, + Backend: "qemu", + DeviceBackends: tc.deviceBackends, + } + cmd, err := spread.QemuCmd(ms, path, 512, 9999) + + if tc.expectedErr != "" { + c.Check(err, ErrorMatches, tc.expectedErr) + continue + } + + c.Assert(err, IsNil) + s := strings.Join(cmd.Args, " ") + c.Assert(s, Matches, fmt.Sprintf("^.*-drive %s.*$", tc.driveDevString)) + c.Assert(s, Matches, fmt.Sprintf("^.*-device %s.*$", tc.netDevString)) + } +}