Skip to content

Commit d232463

Browse files
dylandreimerinkkkourt
authored andcommitted
runner: Added VM runner
This commit add the `lvh run` command, which is a generalized version of the Tetragon runner. It is essentially a small wrapper around qemu to aid in setting the correct attributes to make it run. It modifies the base image in its current iteration to add test scripts and systemd services. HOWTO run it: mkdir images docker run -v $(pwd)/images:/mnt/images \ quay.io/lvh-images/root-images:latest \ cp /data/images/kind.qcow2.zst /mnt/images cd images zstd -d kind.qcow2.zst && cd .. go run ./cmd/lvh run --image images/cilium.qcow2 \ --host-mount ~/sandbox/gopath/src/github.com/cilium/ \ --daemonize -p 2222:22 ssh -p 2222 -o "StrictHostKeyChecking=no" root@localhost # hack hack Signed-off-by: Dylan Reimerink <[email protected]> Signed-off-by: Mark Pashmfouroush <[email protected]> Signed-off-by: Martynas Pumputis <[email protected]>
1 parent 101c3e3 commit d232463

File tree

5 files changed

+262
-1
lines changed

5 files changed

+262
-1
lines changed

cmd/lvh/main.go

+2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package main
33
import (
44
"github.com/cilium/little-vm-helper/cmd/lvh/images"
55
"github.com/cilium/little-vm-helper/cmd/lvh/kernels"
6+
"github.com/cilium/little-vm-helper/cmd/lvh/runner"
67

78
"github.com/spf13/cobra"
89
)
@@ -19,6 +20,7 @@ func init() {
1920
rootCmd.AddCommand(
2021
images.ImagesCommand(),
2122
kernels.KernelsCommand(),
23+
runner.RunCommand(),
2224
)
2325
}
2426

cmd/lvh/runner/conf.go

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package runner
2+
3+
import (
4+
"github.com/sirupsen/logrus"
5+
)
6+
7+
type RunConf struct {
8+
// Image filename
9+
Image string
10+
// kernel filename to boot with. (if empty no -kernel option will be passed to qemu)
11+
KernelFname string
12+
// Do not run the qemu command, just print it
13+
QemuPrint bool
14+
// Do not use KVM acceleration, even if /dev/kvm exists
15+
DisableKVM bool
16+
// Daemonize QEMU after initializing
17+
Daemonize bool
18+
19+
// Disable the network connection to the VM
20+
DisableNetwork bool
21+
ForwardedPorts []PortForward
22+
23+
Logger *logrus.Logger
24+
25+
HostMount string
26+
}
27+
28+
func (rc *RunConf) testImageFname() string {
29+
return rc.Image
30+
}
31+
32+
type PortForward struct {
33+
HostPort int
34+
VMPort int
35+
Protocol string
36+
}

cmd/lvh/runner/qemu.go

+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package runner
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"strings"
7+
8+
"github.com/sirupsen/logrus"
9+
)
10+
11+
func BuildQemuArgs(log *logrus.Logger, rcnf *RunConf) ([]string, error) {
12+
qemuArgs := []string{
13+
// no need for all the default devices
14+
"-nodefaults",
15+
// no need display (-nographics seems a bit slower)
16+
"-display", "none",
17+
// don't reboot, just exit
18+
"-no-reboot",
19+
// cpus, memory
20+
"-smp", "2", "-m", "4G",
21+
}
22+
23+
// quick-and-dirty kvm detection
24+
if !rcnf.DisableKVM {
25+
if f, err := os.OpenFile("/dev/kvm", os.O_RDWR, 0755); err == nil {
26+
qemuArgs = append(qemuArgs, "-enable-kvm", "-cpu", "kvm64")
27+
f.Close()
28+
} else {
29+
log.Info("KVM disabled")
30+
}
31+
}
32+
33+
qemuArgs = append(qemuArgs,
34+
"-hda", rcnf.testImageFname(),
35+
)
36+
37+
if rcnf.KernelFname != "" {
38+
appendArgs := []string{
39+
"root=/dev/sda",
40+
"console=ttyS0",
41+
"earlyprintk=ttyS0",
42+
"panic=-1",
43+
}
44+
qemuArgs = append(qemuArgs,
45+
"-kernel", rcnf.KernelFname,
46+
"-append", fmt.Sprintf("%s", strings.Join(appendArgs, " ")),
47+
)
48+
}
49+
50+
if !rcnf.DisableNetwork {
51+
netdev := "user,id=user.0"
52+
for _, fwd := range rcnf.ForwardedPorts {
53+
netdev = fmt.Sprintf("%s,hostfwd=%s::%d-:%d", netdev, fwd.Protocol, fwd.HostPort, fwd.VMPort)
54+
}
55+
56+
qemuArgs = append(qemuArgs,
57+
"-netdev", netdev,
58+
"-device", "virtio-net-pci,netdev=user.0",
59+
)
60+
}
61+
62+
if !rcnf.Daemonize {
63+
qemuArgs = append(qemuArgs,
64+
"-serial", "mon:stdio",
65+
"-device", "virtio-serial-pci",
66+
)
67+
} else {
68+
qemuArgs = append(qemuArgs, "-daemonize")
69+
}
70+
71+
qemuArgs = append(qemuArgs,
72+
"-fsdev", fmt.Sprintf("local,id=host_id,path=%s,security_model=none", rcnf.HostMount),
73+
"-device", "virtio-9p-pci,fsdev=host_id,mount_tag=host_mount",
74+
)
75+
76+
return qemuArgs, nil
77+
}

cmd/lvh/runner/runner.go

+146
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
package runner
2+
3+
import (
4+
"fmt"
5+
"os/exec"
6+
"strconv"
7+
"strings"
8+
"time"
9+
10+
"github.com/sirupsen/logrus"
11+
"github.com/spf13/cobra"
12+
"golang.org/x/sys/unix"
13+
)
14+
15+
var (
16+
rcnf RunConf
17+
18+
ports []string
19+
)
20+
21+
func RunCommand() *cobra.Command {
22+
cmd := &cobra.Command{
23+
Use: "run",
24+
Short: "run/start VMs based on generated base images and kernels",
25+
SilenceUsage: true,
26+
RunE: func(cmd *cobra.Command, args []string) error {
27+
var err error
28+
29+
rcnf.Logger = logrus.New()
30+
31+
rcnf.ForwardedPorts, err = parsePorts(ports)
32+
if err != nil {
33+
return fmt.Errorf("Port flags: %w", err)
34+
}
35+
36+
t0 := time.Now()
37+
38+
err = StartQemu(rcnf)
39+
dur := time.Since(t0).Round(time.Millisecond)
40+
fmt.Printf("Execution took %v\n", dur)
41+
if err != nil {
42+
return fmt.Errorf("Qemu exited with an error: %w", err)
43+
}
44+
45+
return nil
46+
},
47+
}
48+
49+
cmd.Flags().StringVar(&rcnf.Image, "image", "", "VM image file path")
50+
cmd.MarkFlagRequired("image")
51+
cmd.Flags().StringVar(&rcnf.KernelFname, "kernel", "", "kernel filename to boot with. (if empty no -kernel option will be passed to qemu)")
52+
cmd.Flags().BoolVar(&rcnf.QemuPrint, "qemu-cmd-print", false, "Do not run the qemu command, just print it")
53+
cmd.Flags().BoolVar(&rcnf.DisableKVM, "qemu-disable-kvm", false, "Do not use KVM acceleration, even if /dev/kvm exists")
54+
cmd.Flags().BoolVar(&rcnf.Daemonize, "daemonize", false, "daemonize QEMU after initializing")
55+
cmd.Flags().StringVar(&rcnf.HostMount, "host-mount", "", "Mount the specified host directory in the VM using a 'host_mount' tag")
56+
cmd.Flags().StringArrayVarP(&ports, "port", "p", nil, "Forward a port (hostport[:vmport[:tcp|udp]])")
57+
58+
return cmd
59+
}
60+
61+
func parsePorts(flags []string) ([]PortForward, error) {
62+
var forwards []PortForward
63+
for _, flag := range flags {
64+
hostPortStr, vmPortAndProto, found := strings.Cut(flag, ":")
65+
if !found {
66+
hostPort, err := strconv.Atoi(flag)
67+
if err != nil {
68+
return nil, fmt.Errorf("'%s' is not a valid port number", flag)
69+
}
70+
forwards = append(forwards, PortForward{
71+
HostPort: hostPort,
72+
VMPort: hostPort,
73+
Protocol: "tcp",
74+
})
75+
continue
76+
}
77+
78+
hostPort, err := strconv.Atoi(hostPortStr)
79+
if err != nil {
80+
return nil, fmt.Errorf("'%s' is not a valid port number", hostPortStr)
81+
}
82+
83+
vmPortStr, proto, found := strings.Cut(vmPortAndProto, ":")
84+
if !found {
85+
vmPort, err := strconv.Atoi(vmPortAndProto)
86+
if err != nil {
87+
return nil, fmt.Errorf("'%s' is not a valid port number", vmPortAndProto)
88+
}
89+
forwards = append(forwards, PortForward{
90+
HostPort: hostPort,
91+
VMPort: vmPort,
92+
Protocol: "tcp",
93+
})
94+
continue
95+
}
96+
97+
vmPort, err := strconv.Atoi(vmPortStr)
98+
if err != nil {
99+
return nil, fmt.Errorf("'%s' is not a valid port number", vmPortStr)
100+
}
101+
102+
proto = strings.ToLower(proto)
103+
if proto != "tcp" && proto != "udp" {
104+
return nil, fmt.Errorf("port forward protocol must be tcp or udp")
105+
}
106+
107+
forwards = append(forwards, PortForward{
108+
HostPort: hostPort,
109+
VMPort: vmPort,
110+
Protocol: proto,
111+
})
112+
}
113+
114+
return forwards, nil
115+
}
116+
117+
const qemuBin = "qemu-system-x86_64"
118+
119+
func StartQemu(rcnf RunConf) error {
120+
qemuArgs, err := BuildQemuArgs(rcnf.Logger, &rcnf)
121+
if err != nil {
122+
return err
123+
}
124+
125+
if rcnf.QemuPrint {
126+
var sb strings.Builder
127+
sb.WriteString(qemuBin)
128+
for _, arg := range qemuArgs {
129+
sb.WriteString(" ")
130+
if len(arg) > 0 && arg[0] == '-' {
131+
sb.WriteString("\\\n\t")
132+
}
133+
sb.WriteString(arg)
134+
}
135+
136+
fmt.Printf("%s\n", sb.String())
137+
return nil
138+
}
139+
140+
qemuPath, err := exec.LookPath(qemuBin)
141+
if err != nil {
142+
return err
143+
}
144+
145+
return unix.Exec(qemuPath, append([]string{qemuBin}, qemuArgs...), nil)
146+
}

scripts/example.sh

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#!/bin/bash
1+
#!/usr/bin/env bash
22

33
set -e
44
set -o pipefail

0 commit comments

Comments
 (0)