Skip to content

Commit 9b7e96d

Browse files
committed
qemu: Add a configuration object
The number of parameters with and without default values to qemu_main() and qemu_command() has gotten out of hand. Add a QemuConfig object, which allows us to build up the configuration in steps, and then run it.
1 parent 3afbbd9 commit 9b7e96d

3 files changed

Lines changed: 227 additions & 95 deletions

File tree

lib/qemu.py

Lines changed: 213 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,190 @@
77
from pexpect_utils import PexpectHelper, standard_boot, ping_test, wget_test
88

99

10+
class QemuConfig:
11+
def __init__(self, machine):
12+
self.machine = machine
13+
self.machine_caps = []
14+
self.cpu = None
15+
self.mem = None
16+
self.accel = 'tcg'
17+
self.smp = None
18+
self.cloud_image = None
19+
self.host_mount = None
20+
self.cmdline = 'noreboot '
21+
self.pexpect_timeout = 60
22+
self.logpath = 'console.log'
23+
self.net = None
24+
self.net_tests = True
25+
self.host_command = 'run'
26+
self.gdb = None
27+
self.interactive = False
28+
self.drive = None
29+
self.initrd = None
30+
self.shutdown = None
31+
self.extra_args = []
32+
self.qemu_path = None
33+
self.login = False
34+
self.prompt = None
35+
self.user = 'root'
36+
self.password = None
37+
38+
def machine_is(self, needle):
39+
return self.machine.startswith(needle)
40+
41+
def configure_from_env(self):
42+
self.accel = get_env_var('ACCEL', self.accel)
43+
self.cpu = get_env_var('CPU', self.cpu)
44+
self.smp = get_env_var('SMP', self.smp)
45+
self.mem = get_env_var('QEMU_MEM_SIZE', self.mem)
46+
self.cloud_image = get_env_var('CLOUD_IMAGE', self.cloud_image)
47+
self.host_mount = get_env_var('QEMU_HOST_MOUNT', self.host_mount)
48+
self.cmdline += get_env_var('LINUX_CMDLINE', '') + ' '
49+
self.pexpect_timeout = int(get_env_var('QEMU_PEXPECT_TIMEOUT', self.pexpect_timeout))
50+
self.logpath = get_env_var('QEMU_CONSOLE_LOG', self.logpath)
51+
self.net_tests = get_env_var('QEMU_NET_TESTS', self.net_tests) != '0'
52+
self.host_command = get_env_var('QEMU_HOST_COMMAND', self.host_command)
53+
self.expected_release = get_expected_release()
54+
self.vmlinux = get_vmlinux()
55+
self.cpuinfo = None
56+
57+
def configure_from_args(self, args):
58+
if '--gdb' in args:
59+
self.extra_args += ['-S', '-s']
60+
self.pexpect_timeout = 0
61+
62+
if '--interactive' in args:
63+
self.interactive = True
64+
65+
def apply_defaults(self):
66+
if self.machine_is('pseries') and self.accel == 'tcg':
67+
self.machine_caps += ['cap-htm=off']
68+
69+
if self.cpuinfo is None:
70+
if self.machine_is('pseries'):
71+
self.cpuinfo = 'IBM pSeries \(emulated by qemu\)'
72+
elif self.machine_is('powernv'):
73+
self.cpuinfo = 'IBM PowerNV \(emulated by qemu\)'
74+
75+
if self.qemu_path is None:
76+
if self.machine_is('pseries') or self.machine_is('powernv'):
77+
self.qemu_path = 'qemu-system-ppc64'
78+
else:
79+
self.qemu_path = 'qemu-system-ppc'
80+
81+
self.qemu_path = get_qemu(self.qemu_path)
82+
83+
if self.mem is None:
84+
if self.machine_is('pseries') or self.machine_is('powernv'):
85+
self.mem = '4G'
86+
else:
87+
self.mem = '1G'
88+
89+
if self.smp is None:
90+
if self.accel == 'tcg':
91+
self.smp = 2
92+
else:
93+
self.smp = 8
94+
95+
if self.net is None:
96+
if self.machine_is('pseries'):
97+
self.net = '-nic user,model=virtio-net-pci'
98+
elif self.machine_is('powernv'):
99+
self.net = '-netdev user,id=net0 -device e1000e,netdev=net0'
100+
else:
101+
self.net = '-nic user'
102+
103+
if self.machine == 'powernv':
104+
if self.cpu and self.cpu.upper() == 'POWER8':
105+
self.machine = 'powernv8'
106+
elif self.cpu and self.cpu.upper() == 'POWER10':
107+
self.machine = 'powernv10'
108+
else:
109+
self.machine = 'powernv9'
110+
111+
if self.cloud_image:
112+
self.login = True
113+
self.password = 'linuxppc'
114+
self.user = 'root'
115+
116+
if 'ubuntu' in self.cloud_image:
117+
self.cmdline += 'root=/dev/vda1 '
118+
self.prompt = 'root@ubuntu:~#'
119+
else:
120+
self.cmdline += 'root=/dev/vda2 '
121+
self.prompt = '\[root@fedora ~\]#'
122+
123+
if self.initrd is None and self.drive is None and self.cloud_image is None:
124+
if self.qemu_path.endswith('qemu-system-ppc'):
125+
subarch = 'ppc'
126+
elif get_endian(self.vmlinux) == 'little':
127+
subarch = 'ppc64le'
128+
elif self.machine_is('powernv') or self.machine_is('pseries'):
129+
subarch = 'ppc64'
130+
else:
131+
subarch = 'ppc64-novsx'
132+
133+
self.initrd = f'{subarch}-rootfs.cpio.gz'
134+
135+
if self.host_mount:
136+
bus = ''
137+
if self.machine_is('powernv'):
138+
bus = ',bus=pcie.0'
139+
140+
self.extra_args.append(f'-fsdev local,id=fsdev0,path={self.host_mount},security_model=none')
141+
self.extra_args.append(f'-device virtio-9p-pci,fsdev=fsdev0,mount_tag=host{bus}')
142+
143+
if self.machine_is('pseries'):
144+
rng = '-object rng-random,filename=/dev/urandom,id=rng0 -device spapr-rng,rng=rng0'
145+
if self.accel == 'kvm':
146+
rng += ',use-kvm=true'
147+
148+
self.extra_args += [rng]
149+
150+
151+
def cmd(self):
152+
logging.info('Using qemu version %s.%s' % get_qemu_version(self.qemu_path))
153+
154+
machine = self.machine
155+
if len(self.machine_caps):
156+
machine = ','.join([machine] + self.machine_caps)
157+
158+
l = [
159+
self.qemu_path,
160+
'-nographic',
161+
'-vga', 'none',
162+
'-M', machine,
163+
'-smp', str(self.smp),
164+
'-m', self.mem,
165+
'-accel', self.accel,
166+
'-kernel', self.vmlinux,
167+
]
168+
169+
if self.net:
170+
l.append(self.net)
171+
172+
if self.initrd:
173+
l.append('-initrd')
174+
l.append(get_root_disk(self.initrd))
175+
176+
if self.drive:
177+
l.append(self.drive)
178+
179+
if self.cpu is not None:
180+
l.append('-cpu')
181+
l.append(self.cpu)
182+
183+
if len(self.cmdline):
184+
l.append('-append')
185+
l.append(f'"{self.cmdline}"')
186+
187+
l.extend(self.extra_args)
188+
189+
logging.debug(l)
190+
191+
return ' '.join(l)
192+
193+
10194
def get_qemu(name='qemu-system-ppc64'):
11195
qemu = get_env_var(name.upper().replace('-', '_'))
12196
if qemu is None:
@@ -121,132 +305,83 @@ def qemu_net_setup(p, iface='eth0'):
121305
p.cmd('route -n')
122306

123307

124-
def qemu_main(qemu_machine, cpuinfo_platform, cpu, net, args):
125-
expected_release = get_expected_release()
126-
if expected_release is None:
308+
def qemu_main(qconf):
309+
if qconf.expected_release is None or qconf.vmlinux is None:
127310
return False
128311

129-
vmlinux = get_vmlinux()
130-
if vmlinux is None:
312+
if qconf.host_mount and not os.path.isdir(qconf.host_mount):
313+
logging.error('QEMU_HOST_MOUNT must point to a directory')
131314
return False
132315

133-
accel = get_env_var('ACCEL', 'tcg')
134-
135-
smp = get_env_var('SMP', None)
136-
if smp is None:
137-
if accel == 'tcg':
138-
smp = 2
139-
else:
140-
smp = 8
141-
142-
cmdline = 'noreboot '
143-
144-
cloud_image = get_env_var('CLOUD_IMAGE')
145-
if cloud_image:
316+
if qconf.cloud_image:
146317
# Create snapshot image
147318
rdpath = get_root_disk_path()
148-
src = f'{rdpath}/{cloud_image}'
319+
src = f'{rdpath}/{qconf.cloud_image}'
149320
pid = os.getpid()
150321
dst = f'{rdpath}/qemu-temp-{pid}.img'
151322
cmd = f'qemu-img create -f qcow2 -F qcow2 -b {src} {dst}'.split()
152323
subprocess.run(cmd, check=True)
153-
154324
atexit.register(lambda: os.unlink(dst))
155325

156-
if 'ubuntu' in cloud_image:
157-
cmdline += 'root=/dev/vda1 '
158-
prompt = 'root@ubuntu:~#'
159-
else:
160-
cmdline += 'root=/dev/vda2 '
161-
prompt = '\[root@fedora ~\]#'
162-
163-
if 'powernv' in qemu_machine:
326+
if qconf.machine_is('powernv'):
164327
interface = 'none'
165-
drive = '-device virtio-blk-pci,drive=drive0,id=blk0,bus=pcie.0 ' \
166-
'-device virtio-blk-pci,drive=drive1,id=blk1,bus=pcie.1 '
328+
qconf.extra_args.append('-device virtio-blk-pci,drive=drive0,id=blk0,bus=pcie.0')
329+
qconf.extra_args.append('-device virtio-blk-pci,drive=drive1,id=blk1,bus=pcie.1')
167330
else:
168331
interface = 'virtio'
169-
drive = ''
170-
171-
drive += f'-drive file={dst},format=qcow2,if={interface},id=drive0 ' \
172-
f'-drive file={rdpath}/cloud-init-user-data.img,format=raw,if={interface},readonly=on,id=drive1'
173-
else:
174-
drive = None
175332

176-
extra_args = []
177-
if 'pseries' in qemu_machine:
178-
rng = '-object rng-random,filename=/dev/urandom,id=rng0 -device spapr-rng,rng=rng0'
179-
if accel == 'kvm':
180-
rng += ',use-kvm=true'
333+
qconf.drive = f'-drive file={dst},format=qcow2,if={interface},id=drive0 '
334+
qconf.drive += f'-drive file={rdpath}/cloud-init-user-data.img,format=raw,if={interface},readonly=on,id=drive1'
181335

182-
extra_args += [rng]
336+
cmd = qconf.cmd()
183337

184-
host_mount = get_env_var('QEMU_HOST_MOUNT')
185-
if host_mount and not os.path.isdir(host_mount):
186-
logging.error('QEMU_HOST_MOUNT must point to a directory')
187-
return False
188-
189-
cmdline += get_env_var('LINUX_CMDLINE', '')
190-
191-
# Default timeout for a single pexpect call
192-
pexpect_timeout = int(get_env_var('QEMU_PEXPECT_TIMEOUT', 60))
193-
194-
gdb = None
195-
if '--gdb' in args:
196-
gdb = '-s -S'
197-
pexpect_timeout = 0
198-
199-
cmd = qemu_command(machine=qemu_machine, cpu=cpu, mem='4G', smp=smp, vmlinux=vmlinux,
200-
drive=drive, host_mount=host_mount, cmdline=cmdline, accel=accel,
201-
net=net, gdb=gdb, extra_args=extra_args)
202-
203-
if '--interactive' in args:
338+
if qconf.interactive:
204339
logging.info("Running interactively ...")
205340
rc = subprocess.run(cmd, shell=True).returncode
206341
return rc == 0
207342

208-
setup_timeout(10 * pexpect_timeout)
343+
setup_timeout(10 * qconf.pexpect_timeout)
344+
pexpect_timeout = qconf.pexpect_timeout
209345
if pexpect_timeout:
210346
boot_timeout = pexpect_timeout * 5
211347
else:
212348
boot_timeout = pexpect_timeout = None
213349

214-
logpath = get_env_var('QEMU_CONSOLE_LOG', 'console.log')
215-
216350
p = PexpectHelper()
217-
p.spawn(cmd, logfile=open(logpath, 'w'), timeout=pexpect_timeout)
351+
p.spawn(cmd, logfile=open(qconf.logpath, 'w'), timeout=pexpect_timeout)
218352

219-
if cloud_image:
220-
standard_boot(p, prompt=prompt, login=True, password='linuxppc', timeout=boot_timeout)
221-
else:
222-
standard_boot(p, timeout=boot_timeout)
353+
standard_boot(p, qconf.login, qconf.user, qconf.password, boot_timeout, qconf.prompt)
223354

224355
p.send("echo -n 'booted-revision: '; uname -r")
225-
p.expect(f'booted-revision: {expected_release}')
356+
p.expect(f'booted-revision: {qconf.expected_release}')
226357
p.expect_prompt()
227358

228359
p.send('cat /proc/cpuinfo')
229-
p.expect(cpuinfo_platform)
360+
if qconf.cpuinfo:
361+
p.expect(qconf.cpuinfo)
230362
p.expect_prompt()
231363

232-
if get_env_var('QEMU_NET_TESTS', True) != '0':
364+
if qconf.net_tests:
233365
qemu_net_setup(p)
234366
ping_test(p)
235367
wget_test(p)
236368

237-
if host_mount:
369+
if qconf.host_mount:
238370
# Clear timeout, we don't know how long it will take
239371
setup_timeout(0)
240372
p.cmd('mkdir -p /mnt')
241373
p.cmd('mount -t 9p -o version=9p2000.L,trans=virtio host /mnt')
242-
host_command = get_env_var('QEMU_HOST_COMMAND', 'run')
243-
p.send(f'[ -x /mnt/{host_command} ] && (cd /mnt && ./{host_command})')
374+
p.send(f'[ -x /mnt/{qconf.host_command} ] && (cd /mnt && ./{qconf.host_command})')
244375
p.expect_prompt(timeout=None) # no timeout
245376

246-
p.send('poweroff')
377+
if qconf.shutdown:
378+
qconf.shutdown(p)
379+
else:
380+
p.send('poweroff')
381+
247382
p.wait_for_exit(timeout=boot_timeout)
248383

249-
if filter_log_warnings(open(logpath), open('warnings.txt', 'w')):
384+
if filter_log_warnings(open(qconf.logpath), open('warnings.txt', 'w')):
250385
logging.error('Errors/warnings seen in console log')
251386
return False
252387

scripts/boot/qemu-powernv

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,23 +22,18 @@
2222
import os, sys
2323
sys.path.append(f'{os.path.dirname(sys.argv[0])}/../../lib')
2424

25-
from qemu import qemu_main
26-
from utils import setup_logging, get_env_var
25+
from qemu import QemuConfig, qemu_main
26+
from utils import setup_logging
2727

2828

2929
def main():
3030
setup_logging()
3131

32-
cpu = get_env_var('CPU', 'POWER8')
33-
if cpu == 'POWER8':
34-
machine = 'powernv8'
35-
elif cpu == 'POWER9':
36-
machine = 'powernv9'
37-
else:
38-
machine = 'powernv10'
32+
qconf = QemuConfig('powernv')
33+
qconf.configure_from_env()
34+
qconf.configure_from_args(sys.argv[1:])
35+
qconf.apply_defaults()
3936

40-
return qemu_main(machine, 'IBM PowerNV \(emulated by qemu\)', cpu,
41-
net='-netdev user,id=net0 -device e1000e,netdev=net0',
42-
args=sys.argv[1:])
37+
return qemu_main(qconf)
4338

4439
sys.exit(0 if main() else 1)

0 commit comments

Comments
 (0)