mirror of
https://github.com/mulgadc/spinifex.git
synced 2026-11-02 17:23:32 +00:00
feat(vm): render virtio-serial ports backed by listening unix sockets (#1443)
This commit is contained in:
@@ -36,6 +36,7 @@ type MicroVMSpec struct {
|
||||
FwCfg []FwCfgEntry
|
||||
NetDevs []NetDev
|
||||
Devices []Device
|
||||
SerialPorts []SerialPort
|
||||
}
|
||||
|
||||
// NewMicroVMConfig builds the Config for a direct-boot microvm launch: qboot,
|
||||
@@ -58,6 +59,7 @@ func NewMicroVMConfig(s MicroVMSpec) Config {
|
||||
FwCfg: s.FwCfg,
|
||||
NetDevs: s.NetDevs,
|
||||
Devices: s.Devices,
|
||||
SerialPorts: s.SerialPorts,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -468,6 +468,17 @@ type Config struct {
|
||||
Initrd string `json:"initrd,omitempty"` // path to initramfs; emits -initrd when set
|
||||
KernelCmdline string `json:"kernel_cmdline,omitempty"` // emits -append when set
|
||||
FwCfg []FwCfgEntry `json:"fw_cfg,omitempty"` // each emits -fw_cfg name=<name>,file=<path>
|
||||
|
||||
// SerialPorts are named virtio-serial ports, each backed by a host unix
|
||||
// socket that QEMU listens on. They carry no network traffic.
|
||||
SerialPorts []SerialPort `json:"serial_ports,omitempty"`
|
||||
}
|
||||
|
||||
// SerialPort is one virtserialport named Name in the guest, whose host end is
|
||||
// a unix socket at Socket that QEMU creates and listens on.
|
||||
type SerialPort struct {
|
||||
Name string `json:"name"`
|
||||
Socket string `json:"socket"`
|
||||
}
|
||||
|
||||
// FwCfgEntry describes a single QEMU firmware configuration file entry.
|
||||
@@ -672,6 +683,12 @@ func (cfg *Config) Execute() (*exec.Cmd, error) {
|
||||
args = append(args, "-fw_cfg", fmt.Sprintf("name=%s,file=%s", fw.Name, fw.File))
|
||||
}
|
||||
|
||||
portArgs, err := serialPortArgs(cfg.SerialPorts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
args = append(args, portArgs...)
|
||||
|
||||
if cfg.Architecture == "" {
|
||||
return nil, fmt.Errorf("architecture missing")
|
||||
}
|
||||
@@ -700,6 +717,29 @@ func (cfg *Config) Execute() (*exec.Cmd, error) {
|
||||
return cmd, nil
|
||||
}
|
||||
|
||||
// serialPortArgs renders one virtio-serial controller and, per port, a
|
||||
// listening socket chardev and its virtserialport. Commas are refused because
|
||||
// QEMU would read them as further options.
|
||||
func serialPortArgs(ports []SerialPort) ([]string, error) {
|
||||
if len(ports) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
args := []string{"-device", "virtio-serial-pci,id=vserial0"}
|
||||
for i, p := range ports {
|
||||
if p.Name == "" || p.Socket == "" {
|
||||
return nil, fmt.Errorf("serial port %d: name and socket are required", i)
|
||||
}
|
||||
if strings.ContainsRune(p.Name, ',') || strings.ContainsRune(p.Socket, ',') {
|
||||
return nil, fmt.Errorf("serial port %q: name and socket must not contain a comma", p.Name)
|
||||
}
|
||||
id := fmt.Sprintf("vport%d", i)
|
||||
args = append(args,
|
||||
"-chardev", fmt.Sprintf("socket,id=%s,path=%s,server=on,wait=off", id, p.Socket),
|
||||
"-device", fmt.Sprintf("virtserialport,bus=vserial0.0,chardev=%s,name=%s", id, p.Name))
|
||||
}
|
||||
return args, nil
|
||||
}
|
||||
|
||||
// appendOpt renders a chardev append option, or nothing when off, so a
|
||||
// fresh launch keeps the command line it always had.
|
||||
func appendOpt(key string, on bool) string {
|
||||
|
||||
@@ -694,3 +694,55 @@ func TestExecute_MicrovmWithPCIEFileChardev(t *testing.T) {
|
||||
assert.Equal(t, "file,id=console0,path=/var/log/console.log", argValue(args, "-chardev"))
|
||||
assert.Equal(t, "chardev:console0", argValue(args, "-serial"))
|
||||
}
|
||||
|
||||
func TestExecute_SerialPortsRenderListeningSocketsAndPorts(t *testing.T) {
|
||||
cfg := NewMicroVMConfig(MicroVMSpec{
|
||||
Name: "lambda", Architecture: "x86_64", VCPUs: 1, MemoryMiB: 256,
|
||||
ImageDir: "/img",
|
||||
SerialPorts: []SerialPort{
|
||||
{Name: "lambda.ctl", Socket: "/run/env/ctl.sock"},
|
||||
{Name: "second", Socket: "/run/env/second.sock"},
|
||||
},
|
||||
})
|
||||
cmd, err := cfg.Execute()
|
||||
require.NoError(t, err)
|
||||
args := cmd.Args[1:]
|
||||
|
||||
assert.Equal(t, []string{
|
||||
"socket,id=vport0,path=/run/env/ctl.sock,server=on,wait=off",
|
||||
"socket,id=vport1,path=/run/env/second.sock,server=on,wait=off",
|
||||
}, allArgValues(args, "-chardev"))
|
||||
devs := allArgValues(args, "-device")
|
||||
assert.Equal(t, 1, strings.Count(strings.Join(devs, " "), "virtio-serial-pci"))
|
||||
assert.Contains(t, devs, "virtio-serial-pci,id=vserial0")
|
||||
assert.Contains(t, devs, "virtserialport,bus=vserial0.0,chardev=vport0,name=lambda.ctl")
|
||||
assert.Contains(t, devs, "virtserialport,bus=vserial0.0,chardev=vport1,name=second")
|
||||
}
|
||||
|
||||
func TestExecute_NoSerialPortsAddsNoVirtioSerial(t *testing.T) {
|
||||
cfg := NewMicroVMConfig(MicroVMSpec{Name: "lb", Architecture: "x86_64", VCPUs: 1, MemoryMiB: 256, ImageDir: "/img"})
|
||||
cmd, err := cfg.Execute()
|
||||
require.NoError(t, err)
|
||||
assert.NotContains(t, strings.Join(cmd.Args, " "), "virtio-serial")
|
||||
assert.NotContains(t, strings.Join(cmd.Args, " "), "virtserialport")
|
||||
}
|
||||
|
||||
func TestExecute_SerialPortRefusesOptionInjection(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
port SerialPort
|
||||
want string
|
||||
}{
|
||||
{SerialPort{Name: "ctl,id=x", Socket: "/run/ctl.sock"}, "must not contain a comma"},
|
||||
{SerialPort{Name: "ctl", Socket: "/run/ctl.sock,server=off"}, "must not contain a comma"},
|
||||
{SerialPort{Name: "", Socket: "/run/ctl.sock"}, "name and socket are required"},
|
||||
{SerialPort{Name: "ctl", Socket: ""}, "name and socket are required"},
|
||||
} {
|
||||
cfg := NewMicroVMConfig(MicroVMSpec{
|
||||
Name: "lambda", Architecture: "x86_64", VCPUs: 1, MemoryMiB: 256,
|
||||
ImageDir: "/img", SerialPorts: []SerialPort{tc.port},
|
||||
})
|
||||
_, err := cfg.Execute()
|
||||
require.Error(t, err, "port %+v", tc.port)
|
||||
assert.Contains(t, err.Error(), tc.want, "port %+v", tc.port)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user