Skip to content

Commit 5cf1cfd

Browse files
committed
qemu: Add support for full emulation
With this, one can e.g.: `cosa run --qemu-image fedora-coreos-36*s390x.qcow2 --arch s390x` This was surprisingly easy. It's tempting to do *some* full emulation testing for s390x and ppc64le on x86_64/aarch64. My immediate motivation however is setting up an environment to debug coreos/rpm-ostree#4146
1 parent d0a0874 commit 5cf1cfd

File tree

6 files changed

+74
-32
lines changed

6 files changed

+74
-32
lines changed

mantle/cmd/kola/options.go

+3
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,9 @@ func syncOptionsImpl(useCosa bool) error {
298298
return err
299299
}
300300
}
301+
// Currently the `--arch` option is defined in terms of coreos-assembler, but
302+
// we also unconditionally use it for qemu if present.
303+
kola.QEMUOptions.Arch = kola.Options.CosaBuildArch
301304

302305
units, _ := root.PersistentFlags().GetStringSlice("debug-systemd-units")
303306
for _, unit := range units {

mantle/cmd/kola/qemuexec.go

+9
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ var (
4747
usernet bool
4848
cpuCountHost bool
4949

50+
architecture string
51+
5052
hostname string
5153
ignition string
5254
butane string
@@ -81,6 +83,7 @@ func init() {
8183
cmdQemuExec.Flags().StringSliceVar(&ignitionFragments, "add-ignition", nil, "Append well-known Ignition fragment: [\"autologin\", \"autoresize\"]")
8284
cmdQemuExec.Flags().StringVarP(&hostname, "hostname", "", "", "Set hostname via DHCP")
8385
cmdQemuExec.Flags().IntVarP(&memory, "memory", "m", 0, "Memory in MB")
86+
cmdQemuExec.Flags().StringVar(&architecture, "arch", "", "Use full emulation for target architecture (e.g. aarch64, x86_64, s390x, ppc64le)")
8487
cmdQemuExec.Flags().StringArrayVarP(&addDisks, "add-disk", "D", []string{}, "Additional disk, human readable size (repeatable)")
8588
cmdQemuExec.Flags().BoolVar(&cpuCountHost, "auto-cpus", false, "Automatically set number of cpus to host count")
8689
cmdQemuExec.Flags().BoolVar(&directIgnition, "ignition-direct", false, "Do not parse Ignition, pass directly to instance")
@@ -205,6 +208,12 @@ func runQemuExec(cmd *cobra.Command, args []string) error {
205208
builder := platform.NewQemuBuilder()
206209
defer builder.Close()
207210

211+
if architecture != "" {
212+
if err := builder.SetArchitecture(architecture); err != nil {
213+
return err
214+
}
215+
}
216+
208217
var config *conf.Conf
209218
if butane != "" {
210219
buf, err := ioutil.ReadFile(butane)

mantle/platform/machine/unprivqemu/cluster.go

+5
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,11 @@ func (qc *Cluster) NewMachineWithQemuOptions(userdata *conf.UserData, options pl
101101
builder.ConfigFile = confPath
102102
defer builder.Close()
103103
builder.UUID = qm.id
104+
if qc.flight.opts.Arch != "" {
105+
if err := builder.SetArchitecture(qc.flight.opts.Arch); err != nil {
106+
return nil, err
107+
}
108+
}
104109
builder.Firmware = qc.flight.opts.Firmware
105110
builder.Swtpm = qc.flight.opts.Swtpm
106111
builder.Hostname = fmt.Sprintf("qemu%d", qc.BaseCluster.AllocateMachineSerial())

mantle/platform/machine/unprivqemu/flight.go

+1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ type Options struct {
3434
Board string
3535
Firmware string
3636
Memory string
37+
Arch string
3738

3839
NbdDisk bool
3940
MultiPathDisk bool

mantle/platform/qemu.go

+54-32
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ type bootIso struct {
136136
// QemuInstance holds an instantiated VM through its lifecycle.
137137
type QemuInstance struct {
138138
qemu exec.Cmd
139+
architecture string
139140
tempdir string
140141
swtpm exec.Cmd
141142
nbdServers []exec.Cmd
@@ -295,7 +296,10 @@ func (inst *QemuInstance) Destroy() {
295296
// is used to boot from the network device (boot once is not supported). For s390x, the boot ordering was not a problem as it
296297
// would always read from disk first. For aarch64, the bootindex needs to be switched to boot from disk before a reboot
297298
func (inst *QemuInstance) SwitchBootOrder() (err2 error) {
298-
if coreosarch.CurrentRpmArch() != "s390x" && coreosarch.CurrentRpmArch() != "aarch64" {
299+
switch inst.architecture {
300+
case "s390x", "aarch64":
301+
break
302+
default:
299303
//Not applicable for other arches
300304
return nil
301305
}
@@ -401,6 +405,8 @@ type QemuBuilder struct {
401405
// File to which to redirect the serial console
402406
ConsoleFile string
403407

408+
// If set, use QEMU full emulation for the target architecture
409+
architecture string
404410
// Memory defaults to 1024 on most architectures, others it may be 2048
405411
Memory int
406412
// Processors < 0 means to use host count, unset means 1, values > 1 are directly used
@@ -454,10 +460,11 @@ type QemuBuilder struct {
454460
// NewQemuBuilder creates a new build for QEMU with default settings.
455461
func NewQemuBuilder() *QemuBuilder {
456462
ret := QemuBuilder{
457-
Firmware: "bios",
458-
Swtpm: true,
459-
Pdeathsig: true,
460-
Argv: []string{},
463+
Firmware: "bios",
464+
Swtpm: true,
465+
Pdeathsig: true,
466+
Argv: []string{},
467+
architecture: coreosarch.CurrentRpmArch(),
461468
}
462469
return &ret
463470
}
@@ -526,15 +533,15 @@ func (builder *QemuBuilder) AddFd(fd *os.File) string {
526533
}
527534

528535
// virtio returns a virtio device argument for qemu, which is architecture dependent
529-
func virtio(device, args string) string {
536+
func virtio(arch, device, args string) string {
530537
var suffix string
531-
switch coreosarch.CurrentRpmArch() {
538+
switch arch {
532539
case "x86_64", "ppc64le", "aarch64":
533540
suffix = "pci"
534541
case "s390x":
535542
suffix = "ccw"
536543
default:
537-
panic(fmt.Sprintf("RpmArch %s unhandled in virtio()", coreosarch.CurrentRpmArch()))
544+
panic(fmt.Sprintf("RpmArch %s unhandled in virtio()", arch))
538545
}
539546
return fmt.Sprintf("virtio-%s-%s,%s", device, suffix, args)
540547
}
@@ -574,7 +581,7 @@ func (builder *QemuBuilder) setupNetworking() error {
574581
netdev += ",restrict=on"
575582
}
576583

577-
builder.Append("-netdev", netdev, "-device", virtio("net", "netdev=eth0"))
584+
builder.Append("-netdev", netdev, "-device", virtio(builder.architecture, "net", "netdev=eth0"))
578585
return nil
579586
}
580587

@@ -587,14 +594,24 @@ func (builder *QemuBuilder) setupAdditionalNetworking() error {
587594
macSuffix := fmt.Sprintf("%02x", macCounter)
588595

589596
netdev := fmt.Sprintf("user,id=eth%s,dhcpstart=10.0.2.%s", idSuffix, netSuffix)
590-
device := virtio("net", fmt.Sprintf("netdev=eth%s,mac=52:55:00:d1:56:%s", idSuffix, macSuffix))
597+
device := virtio(builder.architecture, "net", fmt.Sprintf("netdev=eth%s,mac=52:55:00:d1:56:%s", idSuffix, macSuffix))
591598
builder.Append("-netdev", netdev, "-device", device)
592599
macCounter++
593600
}
594601

595602
return nil
596603
}
597604

605+
// SetArchitecture enables qemu full emulation for the target architecture.
606+
func (builder *QemuBuilder) SetArchitecture(arch string) error {
607+
switch arch {
608+
case "x86_64", "aarch64", "s390x", "ppc64le":
609+
builder.architecture = arch
610+
return nil
611+
}
612+
return fmt.Errorf("architecture %s not supported by coreos-assembler qemu", arch)
613+
}
614+
598615
// Mount9p sets up a mount point from the host to guest. To be replaced
599616
// with https://virtio-fs.gitlab.io/ once it lands everywhere.
600617
func (builder *QemuBuilder) Mount9p(source, destHint string, readonly bool) {
@@ -604,13 +621,13 @@ func (builder *QemuBuilder) Mount9p(source, destHint string, readonly bool) {
604621
readonlyStr = ",readonly=on"
605622
}
606623
builder.Append("--fsdev", fmt.Sprintf("local,id=fs%d,path=%s,security_model=mapped%s", builder.fs9pID, source, readonlyStr))
607-
builder.Append("-device", virtio("9p", fmt.Sprintf("fsdev=fs%d,mount_tag=%s", builder.fs9pID, destHint)))
624+
builder.Append("-device", virtio(builder.architecture, "9p", fmt.Sprintf("fsdev=fs%d,mount_tag=%s", builder.fs9pID, destHint)))
608625
}
609626

610627
// supportsFwCfg if the target system supports injecting
611628
// Ignition via the qemu -fw_cfg option.
612629
func (builder *QemuBuilder) supportsFwCfg() bool {
613-
switch coreosarch.CurrentRpmArch() {
630+
switch builder.architecture {
614631
case "s390x", "ppc64le":
615632
return false
616633
}
@@ -619,7 +636,7 @@ func (builder *QemuBuilder) supportsFwCfg() bool {
619636

620637
// supportsSwtpm if the target system supports a virtual TPM device
621638
func (builder *QemuBuilder) supportsSwtpm() bool {
622-
switch coreosarch.CurrentRpmArch() {
639+
switch builder.architecture {
623640
case "s390x":
624641
// s390x does not support a backend for TPM
625642
return false
@@ -655,7 +672,7 @@ type coreosGuestfish struct {
655672
remote string
656673
}
657674

658-
func newGuestfish(diskImagePath string, diskSectorSize int) (*coreosGuestfish, error) {
675+
func newGuestfish(arch, diskImagePath string, diskSectorSize int) (*coreosGuestfish, error) {
659676
// Set guestfish backend to direct in order to avoid libvirt as backend.
660677
// Using libvirt can lead to permission denied issues if it does not have access
661678
// rights to the qcow image
@@ -668,7 +685,7 @@ func newGuestfish(diskImagePath string, diskSectorSize int) (*coreosGuestfish, e
668685
cmd.Env = append(os.Environ(), "LIBGUESTFS_BACKEND=direct")
669686

670687
// Hack to run with a wrapper on older P8 hardware running RHEL7
671-
switch coreosarch.CurrentRpmArch() {
688+
switch arch {
672689
case "ppc64le":
673690
u := unix.Utsname{}
674691
if err := unix.Uname(&u); err != nil {
@@ -736,8 +753,8 @@ func (gf *coreosGuestfish) destroy() {
736753
}
737754

738755
// setupPreboot performs changes necessary before the disk is booted
739-
func setupPreboot(confPath, firstbootkargs, kargs string, diskImagePath string, diskSectorSize int) error {
740-
gf, err := newGuestfish(diskImagePath, diskSectorSize)
756+
func setupPreboot(arch, confPath, firstbootkargs, kargs string, diskImagePath string, diskSectorSize int) error {
757+
gf, err := newGuestfish(arch, diskImagePath, diskSectorSize)
741758
if err != nil {
742759
return err
743760
}
@@ -898,7 +915,7 @@ func (builder *QemuBuilder) addDiskImpl(disk *Disk, primary bool) error {
898915
}
899916
requiresInjection := builder.ConfigFile != "" && builder.ForceConfigInjection
900917
if requiresInjection || builder.AppendFirstbootKernelArgs != "" || builder.AppendKernelArgs != "" {
901-
if err := setupPreboot(builder.ConfigFile, builder.AppendFirstbootKernelArgs, builder.AppendKernelArgs,
918+
if err := setupPreboot(builder.architecture, builder.ConfigFile, builder.AppendFirstbootKernelArgs, builder.AppendKernelArgs,
902919
disk.dstFileName, disk.SectorSize); err != nil {
903920
return errors.Wrapf(err, "ignition injection with guestfs failed")
904921
}
@@ -952,13 +969,13 @@ func (builder *QemuBuilder) addDiskImpl(disk *Disk, primary bool) error {
952969
wwn := rand.Uint64()
953970

954971
var bus string
955-
switch coreosarch.CurrentRpmArch() {
972+
switch builder.architecture {
956973
case "x86_64", "ppc64le", "aarch64":
957974
bus = "pci"
958975
case "s390x":
959976
bus = "ccw"
960977
default:
961-
panic(fmt.Sprintf("Mantle doesn't know which bus type to use on %s", coreosarch.CurrentRpmArch()))
978+
panic(fmt.Sprintf("Mantle doesn't know which bus type to use on %s", builder.architecture))
962979
}
963980

964981
for i := 0; i < 2; i++ {
@@ -984,7 +1001,7 @@ func (builder *QemuBuilder) addDiskImpl(disk *Disk, primary bool) error {
9841001
disk.dstFileName = ""
9851002
switch channel {
9861003
case "virtio":
987-
builder.Append("-device", virtio("blk", fmt.Sprintf("drive=%s%s", id, opts)))
1004+
builder.Append("-device", virtio(builder.architecture, "blk", fmt.Sprintf("drive=%s%s", id, opts)))
9881005
case "nvme":
9891006
builder.Append("-device", fmt.Sprintf("nvme,drive=%s%s", id, opts))
9901007
default:
@@ -1067,7 +1084,7 @@ func (builder *QemuBuilder) finalize() {
10671084

10681085
// Then later, other non-x86_64 seemed to just copy that.
10691086
memory := 1024
1070-
switch coreosarch.CurrentRpmArch() {
1087+
switch builder.architecture {
10711088
case "aarch64", "s390x", "ppc64le":
10721089
memory = 2048
10731090
}
@@ -1083,15 +1100,16 @@ func (builder *QemuBuilder) Append(args ...string) {
10831100

10841101
// baseQemuArgs takes a board and returns the basic qemu
10851102
// arguments needed for the current architecture.
1086-
func baseQemuArgs() []string {
1103+
func baseQemuArgs(arch string) ([]string, error) {
10871104
accel := "accel=kvm"
10881105
kvm := true
1089-
if _, ok := os.LookupEnv("COSA_NO_KVM"); ok {
1106+
hostArch := coreosarch.CurrentRpmArch()
1107+
if _, ok := os.LookupEnv("COSA_NO_KVM"); ok || hostArch != arch {
10901108
accel = "accel=tcg"
10911109
kvm = false
10921110
}
10931111
var ret []string
1094-
switch coreosarch.CurrentRpmArch() {
1112+
switch arch {
10951113
case "x86_64":
10961114
ret = []string{
10971115
"qemu-system-x86_64",
@@ -1113,19 +1131,19 @@ func baseQemuArgs() []string {
11131131
"-machine", "pseries,kvm-type=HV,vsmt=8,cap-fwnmi=off," + accel,
11141132
}
11151133
default:
1116-
panic(fmt.Sprintf("RpmArch %s combo not supported for qemu ", coreosarch.CurrentRpmArch()))
1134+
return nil, fmt.Errorf("architecture %s not supported for qemu", arch)
11171135
}
11181136
if kvm {
11191137
ret = append(ret, "-cpu", "host")
11201138
} else {
1121-
if coreosarch.CurrentRpmArch() == "x86_64" {
1139+
if arch == "x86_64" {
11221140
// the default qemu64 CPU model does not support x86_64_v2
11231141
// causing crashes on EL9+ kernels
11241142
// see https://bugzilla.redhat.com/show_bug.cgi?id=2060839
11251143
ret = append(ret, "-cpu", "Nehalem")
11261144
}
11271145
}
1128-
return ret
1146+
return ret, nil
11291147
}
11301148

11311149
func (builder *QemuBuilder) setupUefi(secureBoot bool) error {
@@ -1289,7 +1307,7 @@ func (builder *QemuBuilder) setupIso() error {
12891307
}
12901308
builder.Append("-drive", "file="+builder.iso.path+",format=raw,if=none,readonly=on,id=installiso")
12911309
if builder.isoAsDisk {
1292-
builder.Append("-device", virtio("blk", "drive=installiso"+bootindexStr))
1310+
builder.Append("-device", virtio(builder.architecture, "blk", "drive=installiso"+bootindexStr))
12931311
} else {
12941312
builder.Append("-device", "ide-cd,drive=installiso"+bootindexStr)
12951313
}
@@ -1378,7 +1396,10 @@ func (builder *QemuBuilder) Exec() (*QemuInstance, error) {
13781396
}
13791397
}()
13801398

1381-
argv := baseQemuArgs()
1399+
argv, err := baseQemuArgs(builder.architecture)
1400+
if err != nil {
1401+
return nil, err
1402+
}
13821403
argv = append(argv, "-m", fmt.Sprintf("%d", builder.Memory))
13831404

13841405
if builder.Processors < 0 {
@@ -1416,7 +1437,7 @@ func (builder *QemuBuilder) Exec() (*QemuInstance, error) {
14161437

14171438
// We always provide a random source
14181439
argv = append(argv, "-object", "rng-random,filename=/dev/urandom,id=rng0",
1419-
"-device", virtio("rng", "rng=rng0"))
1440+
"-device", virtio(builder.architecture, "rng", "rng=rng0"))
14201441
if builder.UUID != "" {
14211442
argv = append(argv, "-uuid", builder.UUID)
14221443
}
@@ -1518,7 +1539,7 @@ func (builder *QemuBuilder) Exec() (*QemuInstance, error) {
15181539
}
15191540
argv = append(argv, "-chardev", fmt.Sprintf("socket,id=chrtpm,path=%s", swtpmSock), "-tpmdev", "emulator,id=tpm0,chardev=chrtpm")
15201541
// There are different device backends on each architecture
1521-
switch coreosarch.CurrentRpmArch() {
1542+
switch builder.architecture {
15221543
case "x86_64":
15231544
argv = append(argv, "-device", "tpm-tis,tpmdev=tpm0")
15241545
case "aarch64":
@@ -1560,6 +1581,7 @@ func (builder *QemuBuilder) Exec() (*QemuInstance, error) {
15601581
argv = append(argv, builder.Argv...)
15611582

15621583
inst.qemu = exec.Command(argv[0], argv[1:]...)
1584+
inst.architecture = builder.architecture
15631585

15641586
cmd := inst.qemu.(*exec.ExecCmd)
15651587
cmd.Stderr = os.Stderr

src/deps.txt

+2
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ make git rpm-build
3030

3131
# virt dependencies
3232
libguestfs-tools libguestfs-tools-c /usr/bin/qemu-img qemu-kvm swtpm
33+
# And the main arch emulators for cross-arch testing
34+
qemu-system-aarch64-core qemu-system-ppc-core qemu-system-s390x-core qemu-system-x86-core
3335

3436
# Useful for moving files around
3537
rsync

0 commit comments

Comments
 (0)