|
7 | 7 | from pexpect_utils import PexpectHelper, standard_boot, ping_test, wget_test |
8 | 8 |
|
9 | 9 |
|
| 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 | + |
10 | 194 | def get_qemu(name='qemu-system-ppc64'): |
11 | 195 | qemu = get_env_var(name.upper().replace('-', '_')) |
12 | 196 | if qemu is None: |
@@ -121,132 +305,83 @@ def qemu_net_setup(p, iface='eth0'): |
121 | 305 | p.cmd('route -n') |
122 | 306 |
|
123 | 307 |
|
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: |
127 | 310 | return False |
128 | 311 |
|
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') |
131 | 314 | return False |
132 | 315 |
|
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: |
146 | 317 | # Create snapshot image |
147 | 318 | rdpath = get_root_disk_path() |
148 | | - src = f'{rdpath}/{cloud_image}' |
| 319 | + src = f'{rdpath}/{qconf.cloud_image}' |
149 | 320 | pid = os.getpid() |
150 | 321 | dst = f'{rdpath}/qemu-temp-{pid}.img' |
151 | 322 | cmd = f'qemu-img create -f qcow2 -F qcow2 -b {src} {dst}'.split() |
152 | 323 | subprocess.run(cmd, check=True) |
153 | | - |
154 | 324 | atexit.register(lambda: os.unlink(dst)) |
155 | 325 |
|
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'): |
164 | 327 | 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') |
167 | 330 | else: |
168 | 331 | 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 |
175 | 332 |
|
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' |
181 | 335 |
|
182 | | - extra_args += [rng] |
| 336 | + cmd = qconf.cmd() |
183 | 337 |
|
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: |
204 | 339 | logging.info("Running interactively ...") |
205 | 340 | rc = subprocess.run(cmd, shell=True).returncode |
206 | 341 | return rc == 0 |
207 | 342 |
|
208 | | - setup_timeout(10 * pexpect_timeout) |
| 343 | + setup_timeout(10 * qconf.pexpect_timeout) |
| 344 | + pexpect_timeout = qconf.pexpect_timeout |
209 | 345 | if pexpect_timeout: |
210 | 346 | boot_timeout = pexpect_timeout * 5 |
211 | 347 | else: |
212 | 348 | boot_timeout = pexpect_timeout = None |
213 | 349 |
|
214 | | - logpath = get_env_var('QEMU_CONSOLE_LOG', 'console.log') |
215 | | - |
216 | 350 | 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) |
218 | 352 |
|
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) |
223 | 354 |
|
224 | 355 | 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}') |
226 | 357 | p.expect_prompt() |
227 | 358 |
|
228 | 359 | p.send('cat /proc/cpuinfo') |
229 | | - p.expect(cpuinfo_platform) |
| 360 | + if qconf.cpuinfo: |
| 361 | + p.expect(qconf.cpuinfo) |
230 | 362 | p.expect_prompt() |
231 | 363 |
|
232 | | - if get_env_var('QEMU_NET_TESTS', True) != '0': |
| 364 | + if qconf.net_tests: |
233 | 365 | qemu_net_setup(p) |
234 | 366 | ping_test(p) |
235 | 367 | wget_test(p) |
236 | 368 |
|
237 | | - if host_mount: |
| 369 | + if qconf.host_mount: |
238 | 370 | # Clear timeout, we don't know how long it will take |
239 | 371 | setup_timeout(0) |
240 | 372 | p.cmd('mkdir -p /mnt') |
241 | 373 | 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})') |
244 | 375 | p.expect_prompt(timeout=None) # no timeout |
245 | 376 |
|
246 | | - p.send('poweroff') |
| 377 | + if qconf.shutdown: |
| 378 | + qconf.shutdown(p) |
| 379 | + else: |
| 380 | + p.send('poweroff') |
| 381 | + |
247 | 382 | p.wait_for_exit(timeout=boot_timeout) |
248 | 383 |
|
249 | | - if filter_log_warnings(open(logpath), open('warnings.txt', 'w')): |
| 384 | + if filter_log_warnings(open(qconf.logpath), open('warnings.txt', 'w')): |
250 | 385 | logging.error('Errors/warnings seen in console log') |
251 | 386 | return False |
252 | 387 |
|
|
0 commit comments