Skip to content

Commit b993c55

Browse files
committed
mantle/kola: Add COSA_VIRTIOFS=1 and dual 9p/virtiofs support
In coreos#3428 I tried a wholesale switch to virtiofs. There are some kernel/KVM crashes we haven't yet debugged. It wasn't hard to factor things out so that it becomes a dynamic choice, keeping the previous 9p support. This way for e.g. local development one can now `env COSA_VIRTIOFS=1 cosa run --qemu-image rhcos.qcow2 --bind-ro ...`
1 parent 4395ad8 commit b993c55

File tree

3 files changed

+135
-36
lines changed

3 files changed

+135
-36
lines changed

mantle/cmd/kola/qemuexec.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,8 @@ func init() {
9191
cmdQemuExec.Flags().BoolVarP(&devshellConsole, "devshell-console", "c", false, "Connect directly to serial console in devshell mode")
9292
cmdQemuExec.Flags().StringVarP(&ignition, "ignition", "i", "", "Path to Ignition config")
9393
cmdQemuExec.Flags().StringVarP(&butane, "butane", "B", "", "Path to Butane config")
94-
cmdQemuExec.Flags().StringArrayVar(&bindro, "bind-ro", nil, "Mount readonly via 9pfs a host directory (use --bind-ro=/path/to/host,/var/mnt/guest")
95-
cmdQemuExec.Flags().StringArrayVar(&bindrw, "bind-rw", nil, "Same as above, but writable")
94+
cmdQemuExec.Flags().StringArrayVar(&bindro, "bind-ro", nil, "Mount $hostpath,$guestpath readonly; for example --bind-ro=/path/on/host,/var/mnt/guest)")
95+
cmdQemuExec.Flags().StringArrayVar(&bindrw, "bind-rw", nil, "Mount $hostpath,$guestpath writable; for example --bind-rw=/path/on/host,/var/mnt/guest)")
9696
cmdQemuExec.Flags().BoolVarP(&forceConfigInjection, "inject-ignition", "", false, "Force injecting Ignition config using guestfs")
9797
cmdQemuExec.Flags().BoolVar(&propagateInitramfsFailure, "propagate-initramfs-failure", false, "Error out if the system fails in the initramfs")
9898
cmdQemuExec.Flags().StringVarP(&consoleFile, "console-to-file", "", "", "Filepath in which to save serial console logs")
@@ -296,18 +296,18 @@ func runQemuExec(cmd *cobra.Command, args []string) error {
296296
if err != nil {
297297
return err
298298
}
299-
builder.Mount9p(src, dest, true)
299+
builder.MountHost(src, dest, true)
300300
ensureConfig()
301-
config.Mount9p(dest, true)
301+
config.MountHost(dest, builder.UseVirtiofs, true)
302302
}
303303
for _, b := range bindrw {
304304
src, dest, err := parseBindOpt(b)
305305
if err != nil {
306306
return err
307307
}
308-
builder.Mount9p(src, dest, false)
308+
builder.MountHost(src, dest, false)
309309
ensureConfig()
310-
config.Mount9p(dest, false)
310+
config.MountHost(dest, builder.UseVirtiofs, false)
311311
}
312312
builder.ForceConfigInjection = forceConfigInjection
313313
if len(firstbootkargs) > 0 {

mantle/platform/conf/conf.go

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1362,11 +1362,22 @@ resize_terminal() {
13621362
PROMPT_COMMAND+=(resize_terminal)`, 0644)
13631363
}
13641364

1365-
// Mount9p adds an Ignition config to mount an folder with 9p
1366-
func (c *Conf) Mount9p(dest string, readonly bool) {
1367-
readonlyStr := ""
1368-
if readonly {
1369-
readonlyStr = ",ro"
1365+
// MountHost adds an Ignition config to mount an folder
1366+
func (c *Conf) MountHost(dest string, virtiofs, readonly bool) {
1367+
mountType := "virtiofs"
1368+
if !virtiofs {
1369+
mountType = "9p"
1370+
}
1371+
options := ""
1372+
if virtiofs {
1373+
if readonly {
1374+
options = "ro"
1375+
}
1376+
} else {
1377+
options = "trans=virtio,version=9p2000.L,msize=10485760"
1378+
if readonly {
1379+
options += ",ro"
1380+
}
13701381
}
13711382
content := fmt.Sprintf(`[Unit]
13721383
DefaultDependencies=no
@@ -1375,11 +1386,11 @@ Before=basic.target
13751386
[Mount]
13761387
What=%s
13771388
Where=%s
1378-
Type=9p
1379-
Options=trans=virtio,version=9p2000.L%s,msize=10485760
1389+
Type=%s
1390+
Options=%s
13801391
[Install]
13811392
WantedBy=multi-user.target
1382-
`, dest, dest, readonlyStr)
1393+
`, dest, dest, mountType, options)
13831394
c.AddSystemdUnit(fmt.Sprintf("%s.mount", systemdunit.UnitNameEscape(dest[1:])), content, Enable)
13841395
}
13851396

mantle/platform/qemu.go

Lines changed: 110 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
// Why not libvirt?
1818
// Two main reasons. First, we really do want to use qemu, and not
1919
// something else. We rely on qemu features/APIs and there's a general
20-
// assumption that the qemu process is local (e.g. we expose 9p filesystem
20+
// assumption that the qemu process is local (e.g. we expose 9p/virtiofs filesystem
2121
// sharing). Second, libvirt runs as a daemon, but we want the
2222
// VMs "lifecycle bound" to their creating process (e.g. kola),
2323
// so that e.g. Ctrl-C (SIGINT) kills both reliably.
@@ -145,11 +145,12 @@ type bootIso struct {
145145

146146
// QemuInstance holds an instantiated VM through its lifecycle.
147147
type QemuInstance struct {
148-
qemu exec.Cmd
149-
architecture string
150-
tempdir string
151-
swtpm exec.Cmd
152-
nbdServers []exec.Cmd
148+
qemu exec.Cmd
149+
architecture string
150+
tempdir string
151+
swtpm exec.Cmd
152+
// Helpers are child processes such as nbd or virtiofsd that should be lifecycle bound to qemu
153+
helpers []exec.Cmd
153154
hostForwardedPorts []HostForwardPort
154155

155156
journalPipe *os.File
@@ -287,12 +288,12 @@ func (inst *QemuInstance) Destroy() {
287288
inst.swtpm.Kill() //nolint // Ignore errors
288289
inst.swtpm = nil
289290
}
290-
for _, nbdServ := range inst.nbdServers {
291-
if nbdServ != nil {
292-
nbdServ.Kill() //nolint // Ignore errors
291+
for _, p := range inst.helpers {
292+
if p != nil {
293+
p.Kill() //nolint // Ignore errors
293294
}
294295
}
295-
inst.nbdServers = nil
296+
inst.helpers = nil
296297

297298
if inst.tempdir != "" {
298299
if err := os.RemoveAll(inst.tempdir); err != nil {
@@ -421,6 +422,13 @@ func (inst *QemuInstance) RemovePrimaryBlockDevice() (err2 error) {
421422
return nil
422423
}
423424

425+
// A directory mounted from the host into the guest, via 9p or virtiofs
426+
type HostMount struct {
427+
src string
428+
dest string
429+
readonly bool
430+
}
431+
424432
// QemuBuilder is a configurator that can then create a qemu instance
425433
type QemuBuilder struct {
426434
// ConfigFile is a path to Ignition configuration
@@ -469,6 +477,8 @@ type QemuBuilder struct {
469477
ignitionSet bool
470478
ignitionRendered bool
471479

480+
// UseVirtiofs should be true if mounts use virtiofs (default: 9p)
481+
UseVirtiofs bool
472482
UsermodeNetworking bool
473483
RestrictNetworking bool
474484
requestedHostForwardPorts []HostForwardPort
@@ -477,9 +487,12 @@ type QemuBuilder struct {
477487
finalized bool
478488
diskID uint
479489
disks []*Disk
480-
fs9pID uint
481490
// virtioSerialID is incremented for each device
482491
virtioSerialID uint
492+
// hostMounts is an array of directories mounted (via 9p or virtiofs) from the host
493+
hostMounts []HostMount
494+
// hostMountSerialID is incremented for each device
495+
hostMountSerialID uint
483496
// fds is file descriptors we own to pass to qemu
484497
fds []*os.File
485498

@@ -499,12 +512,14 @@ func NewQemuBuilder() *QemuBuilder {
499512
default:
500513
defaultFirmware = ""
501514
}
515+
_, useVirtiofs := os.LookupEnv("COSA_VIRTIOFS")
502516
ret := QemuBuilder{
503517
Firmware: defaultFirmware,
504518
Swtpm: true,
505519
Pdeathsig: true,
506520
Argv: []string{},
507521
architecture: coreosarch.CurrentRpmArch(),
522+
UseVirtiofs: useVirtiofs,
508523
}
509524
return &ret
510525
}
@@ -702,16 +717,10 @@ func (builder *QemuBuilder) encryptIgnitionConfig() error {
702717
return nil
703718
}
704719

705-
// Mount9p sets up a mount point from the host to guest. To be replaced
706-
// with https://virtio-fs.gitlab.io/ once it lands everywhere.
707-
func (builder *QemuBuilder) Mount9p(source, destHint string, readonly bool) {
708-
builder.fs9pID++
709-
readonlyStr := ""
710-
if readonly {
711-
readonlyStr = ",readonly=on"
712-
}
713-
builder.Append("--fsdev", fmt.Sprintf("local,id=fs%d,path=%s,security_model=mapped%s", builder.fs9pID, source, readonlyStr))
714-
builder.Append("-device", virtio(builder.architecture, "9p", fmt.Sprintf("fsdev=fs%d,mount_tag=%s", builder.fs9pID, destHint)))
720+
// MountVirtiofs sets up a mount point from the host to guest.
721+
// Note that virtiofs does not currently support read-only mounts (which is really surprising!)
722+
func (builder *QemuBuilder) MountHost(source, dest string, readonly bool) {
723+
builder.hostMounts = append(builder.hostMounts, HostMount{src: source, dest: dest, readonly: readonly})
715724
}
716725

717726
// supportsFwCfg if the target system supports injecting
@@ -1691,7 +1700,7 @@ func (builder *QemuBuilder) Exec() (*QemuInstance, error) {
16911700
if err := cmd.Start(); err != nil {
16921701
return nil, errors.Wrapf(err, "spawing nbd server")
16931702
}
1694-
inst.nbdServers = append(inst.nbdServers, cmd)
1703+
inst.helpers = append(inst.helpers, cmd)
16951704
}
16961705
}
16971706

@@ -1771,6 +1780,85 @@ func (builder *QemuBuilder) Exec() (*QemuInstance, error) {
17711780
return nil, err
17721781
}
17731782

1783+
// Process 9p/virtiofs mounts
1784+
if len(builder.hostMounts) > 0 && builder.UseVirtiofs {
1785+
if err := builder.ensureTempdir(); err != nil {
1786+
return nil, err
1787+
}
1788+
1789+
plog.Debug("creating viritofs helpers")
1790+
1791+
// Allocate a shared memory object and set up NUMA, which is needed for virtiofs
1792+
builder.Append("-object", fmt.Sprintf("memory-backend-file,id=mem,size=%dM,mem-path=/dev/shm,share=on", builder.Memory), "-numa", "node,memdev=mem")
1793+
1794+
// We assume we're already running in a container
1795+
isolation := "none"
1796+
1797+
// Spawn off a virtiofsd helper per mounted path
1798+
virtiofsHelpers := make(map[string]exec.Cmd)
1799+
for i, virtiofs := range builder.hostMounts {
1800+
// By far the most common failure to spawn virtiofsd will be a typo'd source directory,
1801+
// so let's synchronously check that ourselves here.
1802+
if _, err := os.Stat(virtiofs.src); err != nil {
1803+
return nil, fmt.Errorf("Failed to access virtiofs source directory %s", virtiofs.src)
1804+
}
1805+
virtiofsChar := fmt.Sprintf("virtiofschar%d", i)
1806+
virtiofsdSocket := filepath.Join(builder.tempdir, fmt.Sprintf("virtiofsd-%d.sock", i))
1807+
builder.Append("-chardev", fmt.Sprintf("socket,id=%s,path=%s", virtiofsChar, virtiofsdSocket))
1808+
builder.Append("-device", fmt.Sprintf("vhost-user-fs-pci,queue-size=1024,chardev=%s,tag=%s", virtiofsChar, virtiofs.dest))
1809+
plog.Debugf("creating viritofs helper for %s", virtiofs.src)
1810+
// TODO: Honor virtiofs.readonly somehow here
1811+
p := exec.Command("/usr/libexec/virtiofsd", "--sandbox", isolation, "--socket-path", virtiofsdSocket, "--shared-dir", virtiofs.src)
1812+
// Quiet the daemon by default
1813+
p.Env = append(p.Env, "RUST_LOG=ERROR")
1814+
p.Stderr = os.Stderr
1815+
p.SysProcAttr = &syscall.SysProcAttr{
1816+
Pdeathsig: syscall.SIGTERM,
1817+
}
1818+
if err := p.Start(); err != nil {
1819+
return nil, fmt.Errorf("Failed to start virtiofsd")
1820+
}
1821+
virtiofsHelpers[virtiofsdSocket] = p
1822+
}
1823+
// Loop waiting for the sockets to appear
1824+
delay := time.Millisecond * 250
1825+
repetitions := time.Minute / delay
1826+
for i := 0; i < int(repetitions) && len(virtiofsHelpers) > 0; i++ {
1827+
// Delay between subsequent loops
1828+
if i > 0 {
1829+
time.Sleep(delay)
1830+
}
1831+
found := []string{}
1832+
for sockpath := range virtiofsHelpers {
1833+
if _, err := os.Stat(sockpath); err == nil {
1834+
found = append(found, sockpath)
1835+
}
1836+
}
1837+
for _, sockpath := range found {
1838+
delete(virtiofsHelpers, sockpath)
1839+
}
1840+
}
1841+
if len(virtiofsHelpers) > 0 {
1842+
sockets := ""
1843+
for sockpath := range virtiofsHelpers {
1844+
if len(sockets) > 0 {
1845+
sockets += " "
1846+
}
1847+
sockets += sockpath
1848+
}
1849+
return nil, fmt.Errorf("Failed to wait for virtiofsd sockets: %s", sockets)
1850+
}
1851+
} else if len(builder.hostMounts) > 0 {
1852+
for i, hostmnt := range builder.hostMounts {
1853+
readonlyStr := ""
1854+
if hostmnt.readonly {
1855+
readonlyStr = ",readonly=on"
1856+
}
1857+
builder.Append("--fsdev", fmt.Sprintf("local,id=fs%d,path=%s,security_model=mapped%s", i, hostmnt.src, readonlyStr))
1858+
builder.Append("-device", virtio(builder.architecture, "9p", fmt.Sprintf("fsdev=fs%d,mount_tag=%s", i, hostmnt.dest)))
1859+
}
1860+
}
1861+
17741862
fdnum := 3 // first additional file starts at position 3
17751863
for i := range builder.fds {
17761864
fdset := i + 1 // Start at 1

0 commit comments

Comments
 (0)