Skip to content

Commit a926047

Browse files
committed
feat: replace the vm.sh script with a Go CLI
Signed-off-by: Alexandre Sollier <[email protected]>
1 parent a0ad335 commit a926047

File tree

853 files changed

+507536
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

853 files changed

+507536
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package main
2+
3+
import "kubevirt.io/kubevirtci/cluster-provision/centos9/vmcli/cmd"
4+
5+
func main() {
6+
cmd.Execute()
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package qemu
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"os"
7+
"os/exec"
8+
"strconv"
9+
)
10+
11+
// Wrapper around the output of the "qemu-img info" command
12+
type qemuImgInfo struct {
13+
// The virtual size of the disk image
14+
VirtualSize uint64 `json:"virtual-size"`
15+
}
16+
17+
// Creates a virtual disk image
18+
func CreateDisk(path string, format string, size uint64) error {
19+
cmd := exec.Command("qemu-img", "create", "-f", format, path, strconv.FormatUint(size, 10))
20+
cmd.Stdout = os.Stdout
21+
cmd.Stderr = os.Stderr
22+
23+
return cmd.Run()
24+
}
25+
26+
// Creates a virtual disk image that is backed by another disk image
27+
func CreateDiskWithBackingFile(path string, format string, size uint64, backingPath string, backingFormat string) error {
28+
cmd := exec.Command("qemu-img", "create", "-f", format, "-o", fmt.Sprintf("backing_file=%s", backingPath), "-F", backingFormat, path, strconv.FormatUint(size, 10))
29+
cmd.Stdout = os.Stdout
30+
cmd.Stderr = os.Stderr
31+
32+
return cmd.Run()
33+
}
34+
35+
// Parse a JSON qemu-img info object
36+
func ParseDiskInfo(b []byte) (qemuImgInfo, error) {
37+
var info qemuImgInfo
38+
err := json.Unmarshal(b, &info)
39+
40+
return info, err
41+
}
42+
43+
// Get information about the specified disk image on the file system
44+
func GetDiskInfo(diskPath string) (qemuImgInfo, error) {
45+
cmd := exec.Command("qemu-img", "info", "--output", "json", diskPath)
46+
cmd.Stderr = os.Stderr
47+
48+
out, err := cmd.Output()
49+
if err != nil {
50+
return qemuImgInfo{}, err
51+
}
52+
53+
return ParseDiskInfo(out)
54+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package qemu_test
2+
3+
import (
4+
. "github.com/onsi/ginkgo/v2"
5+
. "github.com/onsi/gomega"
6+
7+
"kubevirt.io/kubevirtci/cluster-provision/centos9/vmcli/cmd/qemu"
8+
)
9+
10+
var _ = Describe("Qemu qemu-img wrapper", func() {
11+
Describe("Parsing the disk information", func() {
12+
When("the disk information is valid", func() {
13+
const sampleQemuImgInfoOutput = `
14+
{
15+
"children": [
16+
{
17+
"name": "file",
18+
"info": {
19+
"children": [
20+
],
21+
"virtual-size": 197120,
22+
"filename": "test",
23+
"format": "file",
24+
"actual-size": 200704,
25+
"format-specific": {
26+
"type": "file",
27+
"data": {
28+
}
29+
},
30+
"dirty-flag": false
31+
}
32+
}
33+
],
34+
"virtual-size": 1048576,
35+
"filename": "test",
36+
"cluster-size": 65536,
37+
"format": "qcow2",
38+
"actual-size": 200704,
39+
"format-specific": {
40+
"type": "qcow2",
41+
"data": {
42+
"compat": "1.1",
43+
"compression-type": "zlib",
44+
"lazy-refcounts": false,
45+
"refcount-bits": 16,
46+
"corrupt": false,
47+
"extended-l2": false
48+
}
49+
},
50+
"dirty-flag": false
51+
}
52+
`
53+
It("returns the correct disk virtual size", func() {
54+
diskInfo, err := qemu.ParseDiskInfo([]byte(sampleQemuImgInfoOutput))
55+
Expect(err).NotTo(HaveOccurred())
56+
Expect(diskInfo.VirtualSize).To(BeNumerically("==", 1048576))
57+
})
58+
})
59+
60+
When("the disk information is invalid", func() {
61+
It("fails", func() {
62+
_, err := qemu.ParseDiskInfo([]byte("{"))
63+
Expect(err).To(HaveOccurred())
64+
})
65+
})
66+
})
67+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package qemu_test
2+
3+
import (
4+
"testing"
5+
6+
. "github.com/onsi/ginkgo/v2"
7+
. "github.com/onsi/gomega"
8+
)
9+
10+
func TestQemu(t *testing.T) {
11+
RegisterFailHandler(Fail)
12+
RunSpecs(t, "Qemu Suite")
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
package qemu
2+
3+
import (
4+
"fmt"
5+
"regexp"
6+
"strconv"
7+
"strings"
8+
9+
"github.com/google/uuid"
10+
)
11+
12+
// The name of the QEMU main executable
13+
// TODO: On RHEL and similar, the executable is "qemu-kvm". We'll need to check the distribution and use the correct executable.
14+
const qemuExec = "qemu-system-%s"
15+
16+
// Thin wrapper around the "qemu-system-x" command
17+
type QemuSystem struct {
18+
// The architecture of the "qemu-system" command to start
19+
Arch string
20+
// The memory to allocate to the guest, wraps the "-m" argument
21+
Memory string
22+
// The number of the CPUs in the SMP system, wraps the "-smp" argument
23+
CpuCount uint64
24+
// The number of NUMA nodes in the system, wraps "-object memory-backend-ram" and "-numa node" arguments
25+
Numa uint64
26+
// Whether to use KVM, wraps the "-enable-kvm" argument
27+
KvmEnabled bool
28+
// Select the CPU model, wraps the "-cpu" argument
29+
CpuModel string
30+
// Select the emulated machine, wraps the "-machine" argument
31+
Machine string
32+
// Set the system UUID, wraps the "-uuid" argument
33+
SystemUuid uuid.UUID
34+
// Start a VNC server on the specified display, wraps the "-vnc" argument
35+
VncServer string
36+
// Redirect the virtual serial port to the specified character device, wraps the "-serial" argument
37+
SerialHostDev string
38+
// Path to the initramfs file, wraps the "-initrd" argument
39+
InitrdPath string
40+
// Path to the kernel to boot, wraps the "-kernel" argument
41+
KernelPath string
42+
// The cmdlines to pass to the booted kernel, wraps the "-append" argument
43+
KernelArgs []string
44+
// Drives to attach to the guest, wraps the "-drive" arguments
45+
Drives []string
46+
// Devices to attach to the guest, wraps the "-device" arguments
47+
Devices []string
48+
// The network backend to configure, wraps the "-netdev" argument
49+
Netdev string
50+
}
51+
52+
// Parse the memory argument into a value and possibly a unit
53+
func (q QemuSystem) ParseMemory() (uint64, string, error) {
54+
regex, err := regexp.Compile(`(\d+)(\w*)`)
55+
if err != nil {
56+
return 0, "", err
57+
}
58+
59+
submatch := regex.FindStringSubmatch(q.Memory)
60+
if len(submatch) < 2 {
61+
return 0, "", fmt.Errorf("Unable to parse the QEMU memory argument %q", q.Memory)
62+
}
63+
64+
val, err := strconv.ParseUint(submatch[1], 10, 64)
65+
if err != nil {
66+
return 0, "", err
67+
}
68+
69+
unit := ""
70+
if len(submatch) >= 3 {
71+
unit = submatch[2]
72+
}
73+
74+
return val, unit, nil
75+
}
76+
77+
// Generate the QEMU arguments for creating the NUMA topology
78+
func (q QemuSystem) GenerateNumaArguments() ([]string, error) {
79+
if q.Numa < 2 {
80+
return []string{}, nil
81+
}
82+
83+
result := []string{}
84+
85+
memoryValue, memoryUnit, err := q.ParseMemory()
86+
if err != nil {
87+
return []string{}, err
88+
}
89+
90+
if q.CpuCount%q.Numa > 0 || memoryValue%q.Numa > 0 {
91+
return []string{}, fmt.Errorf("unable to calculate symmetric NUMA topology with vCPUs:%v Memory:%v NUMA:%v", q.CpuCount, q.Memory, q.Numa)
92+
}
93+
94+
memoryPerNodeValue := memoryValue / q.Numa
95+
memoryPerNode := fmt.Sprintf("%v%v", memoryPerNodeValue, memoryUnit)
96+
cpuPerNode := q.CpuCount / q.Numa
97+
98+
for nodeId := uint64(0); nodeId < q.Numa; nodeId++ {
99+
nodeFirstCpu := nodeId * cpuPerNode
100+
nodeLastCpu := nodeFirstCpu + cpuPerNode - 1
101+
102+
memoryBackendRamArg := fmt.Sprintf("-object memory-backend-ram,size=%v,id=m%v", memoryPerNode, nodeId)
103+
numaNodeArg := fmt.Sprintf("-numa node,nodeid=%v,memdev=m%v,cpus=%v-%v", nodeId, nodeId, nodeFirstCpu, nodeLastCpu)
104+
105+
result = append(result, memoryBackendRamArg, numaNodeArg)
106+
}
107+
108+
return result, nil
109+
}
110+
111+
// Generate the command line to use to start QEMU
112+
func (q QemuSystem) GenerateCmdline() (string, error) {
113+
qemuArgs := []string{
114+
fmt.Sprintf(qemuExec, q.Arch),
115+
"-m", q.Memory,
116+
"-smp", strconv.FormatUint(q.CpuCount, 10),
117+
"-cpu", q.CpuModel,
118+
"-M", q.Machine,
119+
"-uuid", q.SystemUuid.String(),
120+
"-vnc", q.VncServer,
121+
"-serial", q.SerialHostDev,
122+
"-initrd", q.InitrdPath,
123+
"-kernel", q.KernelPath,
124+
"-append", fmt.Sprintf("\"%s\"", strings.TrimSpace(strings.Join(q.KernelArgs, " "))),
125+
"-netdev", q.Netdev,
126+
}
127+
128+
if q.KvmEnabled {
129+
qemuArgs = append(qemuArgs, "-enable-kvm")
130+
}
131+
132+
for _, drive := range q.Drives {
133+
qemuArgs = append(qemuArgs, "-drive", drive)
134+
}
135+
136+
for _, device := range q.Devices {
137+
qemuArgs = append(qemuArgs, "-device", device)
138+
}
139+
140+
numaArgs, err := q.GenerateNumaArguments()
141+
if err != nil {
142+
return "", err
143+
}
144+
145+
if len(numaArgs) > 0 {
146+
qemuArgs = append(qemuArgs, numaArgs...)
147+
}
148+
149+
return strings.Join(qemuArgs, " "), nil
150+
}

0 commit comments

Comments
 (0)