Skip to content

Commit 686c619

Browse files
committed
Fixed pylint issues and missing imports
1 parent 9ce08fe commit 686c619

1 file changed

Lines changed: 93 additions & 84 deletions

File tree

test_tooling/qemu/qemu.py

Lines changed: 93 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -3,53 +3,107 @@
33
import pathlib
44
import re
55
import getpass
6-
import os
76
import grp
87
import shlex
9-
import stat
108
import sys
119
import time
1210
import shutil
1311
import hashlib
14-
from collections.abc import Iterator
1512
from select import EPOLLHUP, EPOLLIN, epoll
1613
from shutil import which
1714
from types import TracebackType
18-
from typing import Any, Self, override
1915
from argparse import ArgumentParser
20-
from typing import Callable, Optional, Dict, Any, List, Union, Iterator, TYPE_CHECKING
21-
16+
from typing import Self, Any, override, Iterator
2217

23-
username = getpass.getuser()
24-
groupname = grp.getgrgid(os.getgid()).gr_name
2518

2619
class RequirementError(Exception):
2720
pass
2821

22+
2923
class ArgumentError(Exception):
3024
pass
3125

32-
class SysCallError(Exception):
33-
def __init__(self, message: str, exit_code: int | None = None, worker_log: bytes = b'') -> None:
34-
super().__init__(message)
35-
self.message = message
36-
self.exit_code = exit_code
37-
self.worker_log = worker_log
26+
27+
def get_master(interface):
28+
master_path = pathlib.Path(f"/sys/class/net/{interface}/master")
29+
return master_path.readlink().name if master_path.exists() else None
3830

3931
def gray(text):
4032
return f"\033[38;5;246m{text}\033[0m"
33+
4134
def orange(text):
4235
return f"\033[38;5;208m{text}\033[0m"
36+
4337
def red(text):
4438
return f"\033[31m{text}\033[0m"
4539

46-
def get_master(interface):
47-
master_path = pathlib.Path(f"/sys/class/net/{interface}/master")
48-
return master_path.readlink().name if master_path.exists() else None
40+
sudo_password = None # Gets populated later
41+
harddrives = {}
42+
username = getpass.getuser()
43+
groupname = grp.getgrgid(os.getgid()).gr_name
4944

5045
# https://stackoverflow.com/a/43627833/929999
5146
_VT100_ESCAPE_REGEX = r'\x1B\[[?0-9;]*[a-zA-Z]'
5247
_VT100_ESCAPE_REGEX_BYTES = _VT100_ESCAPE_REGEX.encode()
48+
49+
parser = ArgumentParser(description="A set of common parameters for the tooling", add_help=True)
50+
51+
# Defaults to the order of which the harddrives are defined.
52+
boot_option = parser.add_mutually_exclusive_group()
53+
boot_option.add_argument('--uki', help='Boot a UKI (EFI) image')
54+
boot_option.add_argument('--kernel', help='Boot a Linux kernel')
55+
boot_option.add_argument('--iso', help='Boot a ISO 9660')
56+
57+
networking = parser.add_argument_group("Networking", "Disables the default '-net nic -net user' network behavior of Qemu.")
58+
networking.add_argument("--tap", nargs="?", help="Configures a TAP interface and passes it in as a virtio-net-pci.", default=None, type=str)
59+
networking.add_argument("--tap-mac", nargs="?", help="MAC for the --tap interface", default='52:54:00:00:00:02')
60+
networking.add_argument("--bridge", nargs="?", help="Configures a bridge, to which the --tap is added.", default=None, type=str)
61+
networking.add_argument("--bridge-mac", nargs="?", help="MAC for the interface", default=None)
62+
networking.add_argument("--bridge-master", nargs="?", help="Which interface to set as 'master' on the bridge.", default=None, type=str)
63+
64+
hardware = parser.add_argument_group("Hardware", "General hardware specs for the virtual machine")
65+
# To override the use of EFI boot (will not work with --uki for obvious reasons)
66+
hardware.add_argument("--bios", action="store_true", help="Disables EFI (edk2/ovmf) and uses BIOS support instead", default=False)
67+
hardware.add_argument("--memory", nargs="?", help="Ammount of memory to supply the machine", default=8192)
68+
hardware.add_argument("--harddrive", action='append', help="Sets up one or more virtio-scsi-pci, size is defined by --harddrive test.qcow2:15G", type=str)
69+
hardware.add_argument("--cpu", help="Sets the number of cores to allocate (default nproc -1)", type=str, default=os.cpu_count() - 1 if os.cpu_count() else 1)
70+
hardware.add_argument("--resolution", help="Sets Qemu's VGA resolution", type=str, default="1920x1107")
71+
72+
kernel = parser.add_argument_group("Kernel", "--kernel specific arguments")
73+
kernel.add_argument("--initrd", nargs="?", help="Defines which ISO to run (skips build all together)", default=None, type=pathlib.Path)
74+
75+
args, unknowns = parser.parse_known_args() # pylint: disable=redefined-outer-name
76+
77+
if args.bios and args.uki:
78+
raise ArgumentError("Cannot boot a --uki image with --bios mode (at least not that I know of).")
79+
80+
if args.uki is None and args.kernel is None and args.iso is None and args.harddrive is None:
81+
raise ArgumentError("Cannot boot this machine, define at least one of: --uki, --kernel, --iso, --harddrive")
82+
83+
if args.bridge is None and args.bridge_master:
84+
raise ArgumentError("Cannot use --bridge-master without defining --bridge")
85+
86+
if args.bridge is None and args.bridge_mac:
87+
raise ArgumentError("Cannot use --bridge-mac without defining --bridge")
88+
elif args.bridge and args.bridge_mac is None:
89+
args.bridge_mac = '52:54:00:00:00:1'
90+
91+
if args.tap and not args.bridge and get_master(args.tap) is None:
92+
# We'll allow it, because maybe we're tesing what happens without networking, but the NIC exists. Or the user has some creative iptables/nftables forwarding.
93+
print(orange("--tap does not have a master, consider adding --bridge or manual set a master using ip-link(8)."))
94+
95+
if args.tap is None and args.bridge:
96+
print(orange("--bridge* arguments will be ignored since there's no --tap defined"))
97+
elif args.tap and args.tap_mac is None:
98+
args.tap_mac = '52:54:00:00:00:2'
99+
100+
class SysCallError(Exception):
101+
def __init__(self, message: str, exit_code: int | None = None, worker_log: bytes = b'') -> None:
102+
super().__init__(message)
103+
self.message = message
104+
self.exit_code = exit_code
105+
self.worker_log = worker_log
106+
53107
def clear_vt100_escape_codes(data: bytes) -> bytes:
54108
return re.sub(_VT100_ESCAPE_REGEX_BYTES, b'', data)
55109

@@ -58,6 +112,12 @@ def locate_binary(name: str) -> str:
58112
return path
59113
raise RequirementError(f'Binary {name} does not exist.')
60114

115+
def _pid_exists(pid: int) -> bool:
116+
try:
117+
return any(subprocess.check_output(['ps', '--no-headers', '-o', 'pid', '-p', str(pid)]).strip())
118+
except subprocess.CalledProcessError:
119+
return False
120+
61121
class SysCommandWorker:
62122
def __init__(
63123
self,
@@ -105,7 +165,7 @@ def __contains__(self, key: bytes) -> bool:
105165

106166
return False
107167

108-
def __iter__(self, *args: str, **kwargs: dict[str, Any]) -> Iterator[bytes]:
168+
def __iter__(self, *args: str, **kwargs: dict[str, Any]) -> Iterator[bytes]: # pylint: disable=redefined-outer-name
109169
last_line = self._trace_log.rfind(b'\n')
110170
lines = filter(None, self._trace_log[self._trace_log_pos : last_line].splitlines())
111171
for line in lines:
@@ -197,8 +257,6 @@ def peak(self, output: str | bytes) -> bool:
197257
except UnicodeDecodeError:
198258
return False
199259

200-
_cmd_output(output)
201-
202260
sys.stdout.write(output)
203261
sys.stdout.flush()
204262

@@ -264,70 +322,18 @@ def execute(self) -> bool:
264322
def decode(self, encoding: str = 'UTF-8') -> str:
265323
return self._trace_log.decode(encoding)
266324

267-
parser = ArgumentParser(description="A set of common parameters for the tooling", add_help=True)
268-
269-
# Defaults to the order of which the harddrives are defined.
270-
boot_option = parser.add_mutually_exclusive_group()
271-
boot_option.add_argument('--uki', help='Boot a UKI (EFI) image')
272-
boot_option.add_argument('--kernel', help='Boot a Linux kernel')
273-
boot_option.add_argument('--iso', help='Boot a ISO 9660')
274-
275-
networking = parser.add_argument_group("Networking", "Disables the default '-net nic -net user' network behavior of Qemu.")
276-
networking.add_argument("--tap", nargs="?", help="Configures a TAP interface and passes it in as a virtio-net-pci.", default=None, type=str)
277-
networking.add_argument("--tap-mac", nargs="?", help="MAC for the --tap interface", default='52:54:00:00:00:02')
278-
networking.add_argument("--bridge", nargs="?", help="Configures a bridge, to which the --tap is added.", default=None, type=str)
279-
networking.add_argument("--bridge-mac", nargs="?", help="MAC for the interface", default=None)
280-
networking.add_argument("--bridge-master", nargs="?", help="Which interface to set as 'master' on the bridge.", default=None, type=str)
281-
282-
hardware = parser.add_argument_group("Hardware", "General hardware specs for the virtual machine")
283-
# To override the use of EFI boot (will not work with --uki for obvious reasons)
284-
hardware.add_argument("--bios", action="store_true", help="Disables EFI (edk2/ovmf) and uses BIOS support instead", default=False)
285-
hardware.add_argument("--memory", nargs="?", help="Ammount of memory to supply the machine", default=8192)
286-
hardware.add_argument("--harddrive", action='append', help="Sets up one or more virtio-scsi-pci, size is defined by --harddrive test.qcow2:15G", type=str)
287-
hardware.add_argument("--cpu", help="Sets the number of cores to allocate (default nproc -1)", type=str, default=os.cpu_count() - 1 if os.cpu_count() else 1)
288-
hardware.add_argument("--resolution", help="Sets Qemu's VGA resolution", type=str, default="1920x1107")
289-
290-
kernel = parser.add_argument_group("Kernel", "--kernel specific arguments")
291-
kernel.add_argument("--initrd", nargs="?", help="Defines which ISO to run (skips build all together)", default=None, type=pathlib.Path)
292-
293-
args, unknowns = parser.parse_known_args()
294-
295-
if args.bios and args.uki:
296-
raise ArgumentError(f"Cannot boot a --uki image with --bios mode (at least not that I know of).")
297-
298-
if args.uki is None and args.kernel is None and args.iso is None and args.harddrive is None:
299-
raise ArgumentError(f"Cannot boot this machine, define at least one of: --uki, --kernel, --iso, --harddrive")
300-
301-
if args.bridge is None and args.bridge_master:
302-
raise ArgumentError(f"Cannot use --bridge-master without defining --bridge")
303-
304-
if args.bridge is None and args.bridge_mac:
305-
raise ArgumentError(f"Cannot use --bridge-mac without defining --bridge")
306-
elif args.bridge and args.bridge_mac is None:
307-
args.bridge_mac = '52:54:00:00:00:1'
308-
309-
if args.tap and not args.bridge and get_master(args.tap) is None:
310-
# We'll allow it, because maybe we're tesing what happens without networking, but the NIC exists. Or the user has some creative iptables/nftables forwarding.
311-
print(orange(f"--tap does not have a master, consider adding --bridge or manual set a master using ip-link(8)."))
312-
313-
if args.tap is None and args.bridge:
314-
print(orange(f"--bridge* arguments will be ignored since there's no --tap defined"))
315-
elif args.tap and args.tap_mac is None:
316-
args.tap_mac = '52:54:00:00:00:2'
317-
318-
sudo_password = None
319325
def ensure_sudo():
320-
global sudo_password
326+
global sudo_password # pylint: disable=global-statement
321327

322328
if sudo_password is None:
323329
if (sudo_password := getpass.getpass(f"[sudo] password for {username}: ")) == "":
324-
raise ValueError(f"Certain commands need sudo to work and no sudo password was given.")
330+
raise ValueError("Certain commands need sudo to work and no sudo password was given.")
325331

326332
def setup_networking():
327333
if args.tap:
328334
if pathlib.Path(f"/sys/class/net/{args.tap}").exists() is False:
329335
print(gray(f"Creating {args.tap} for user {username} and group {groupname}"))
330-
handle, pw_prompted = archinstall.SysCommandWorker(f"sudo ip tuntap add dev {args.tap} mode tap user {username} group {groupname}"), False
336+
handle, pw_prompted = SysCommandWorker(f"sudo ip tuntap add dev {args.tap} mode tap user {username} group {groupname}"), False
331337
while handle.is_alive():
332338
if b'password for' in handle and pw_prompted is False:
333339
ensure_sudo()
@@ -386,7 +392,6 @@ def setup_networking():
386392
handle.write(bytes(sudo_password, 'UTF-8'))
387393
pw_prompted = True
388394

389-
harddrives={}
390395
def setup_disks():
391396
if args.harddrive:
392397
for harddrive_arg in args.harddrive:
@@ -395,7 +400,11 @@ def setup_disks():
395400
harddrives[path] = size.strip()
396401

397402
if path.exists() is False:
398-
if (handle := SysCommand(f"qemu-img create -f qcow2 {hdd} {size}")).exit_code != 0:
403+
handle = SysCommandWorker(f"qemu-img create -f qcow2 {hdd} {size}")
404+
while handle.is_alive():
405+
time.sleep(0.01)
406+
407+
if handle.exit_code != 0:
399408
raise ValueError(f"Could not create harddrive {hdd}: {handle}")
400409

401410
setup_networking()
@@ -409,16 +418,16 @@ def setup_disks():
409418

410419
boot_index = 0
411420
qemu = 'qemu-system-x86_64'
412-
qemu += f' -cpu host'
413-
qemu += f' -enable-kvm'
414-
qemu += f' -machine q35,accel=kvm'
415-
qemu += f' -object rng-random,filename=/dev/urandom,id=rng0'
416-
qemu += f' -device virtio-rng-pci,rng=rng0'
417-
qemu += f' -global driver=cfi.pflash01,property=secure,value=on'
421+
qemu += ' -cpu host'
422+
qemu += ' -enable-kvm'
423+
qemu += ' -machine q35,accel=kvm'
424+
qemu += ' -object rng-random,filename=/dev/urandom,id=rng0'
425+
qemu += ' -device virtio-rng-pci,rng=rng0'
426+
qemu += ' -global driver=cfi.pflash01,property=secure,value=on'
418427
qemu += f' -smp {args.cpu},sockets=1,dies=1,cores={args.cpu},threads=1'
419428
# qemu += f' -vga vga'
420429
qemu += f' -device VGA,edid=on,xres={args.resolution.split('x')[0]},yres={args.resolution.split('x')[1]}'
421-
qemu += f' -device intel-iommu,device-iotlb=on,caching-mode=on'
430+
qemu += ' -device intel-iommu,device-iotlb=on,caching-mode=on'
422431
qemu += f' -m {args.memory}'
423432
if args.bios is False:
424433
qemu += f' -drive if=pflash,format=raw,readonly=on,file=./OVMF_CODE.secboot.4m.fd.{disk_paths_hash}'

0 commit comments

Comments
 (0)