Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

spread: add device backend configuration support to QEMU #168

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

<a name="google"/>

Expand Down
9 changes: 9 additions & 0 deletions spread/project.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down Expand Up @@ -1363,3 +1370,5 @@ func sortedKeys(m map[string]bool) []string {
sort.Strings(keys)
return keys
}

type DeviceBackendsMap map[string]string
5 changes: 5 additions & 0 deletions spread/project_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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) {
Expand Down
48 changes: 44 additions & 4 deletions spread/qemu.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"os"
"os/exec"
"path/filepath"
"regexp"
"strconv"

"golang.org/x/net/context"
Expand Down Expand Up @@ -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:]...)
}
Expand Down
85 changes: 85 additions & 0 deletions spread/qemu_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package spread_test

import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
Expand Down Expand Up @@ -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))
}
}