Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Schutzfile
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
"minimal-installer": "rhos-01/fedora-42",
"network-installer": "rhos-01/fedora-42",
"everything-network-installer": "rhos-01/fedora-42",
"server-network-installer": "rhos-01/fedora-42"
"server-network-installer": "rhos-01/fedora-42",
"pxe-tar-xz": "rhos-01/fedora-42"
}
}
},
Expand Down
2 changes: 1 addition & 1 deletion schutzbot/terraform
Original file line number Diff line number Diff line change
@@ -1 +1 @@
c5ff0cf74a790e472f3f885ac640c82f0ec39a5e
3209ede9b21f1df5629afa8b087ae23a70f652a6
25 changes: 20 additions & 5 deletions test/scripts/boot-image
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,12 @@ class CannotRunQemuTest(Exception):
self.skip_reason = skip_reason


class MissingBootImplementation(Exception):
def __init__(self, skip_reason):
super().__init__(skip_reason)
self.skip_reason = skip_reason


def ensure_can_run_qemu_test(image_path, config_file):
"""
Check if the given image_path, config_file is capable of running a
Expand Down Expand Up @@ -327,15 +333,18 @@ def boot_qemu_pxe(arch, pxe_tar_path):
subprocess.check_call(
"cat initrd.img rootfs.cpio > combined.img", shell=True, cwd=tmpdir)

# start an HTTP server to serve the rootfs.img
# Start an HTTP server to serve the rootfs.img and terminate it after the test.
# Explicitly terminate the HTTP server to avoid blocking on wait(), this cannot
# be done with a context manager for subprocesses.
http_port = get_free_port()
with subprocess.Popen(
http_server = subprocess.Popen( # pylint: disable=consider-using-with
["python3", "-m", "http.server", f"{http_port}"],
cwd=tmpdir,
# prevent blocking output
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL
):
)
try:
# test disk is unused for live OS
test_disk_path = pathlib.Path(tmpdir) / "disk.img"
with open(test_disk_path, "w", encoding="utf-8") as fp:
Expand Down Expand Up @@ -372,6 +381,9 @@ def boot_qemu_pxe(arch, pxe_tar_path):
# 2. modify vm.py to be able to talk directly to the serial console
# and then run commands directly via that
vm.force_stop()
finally:
http_server.terminate()
http_server.wait()


def cmd_boot_aws(arch, image_name, privkey, pubkey, image_path, script_cmd):
Expand Down Expand Up @@ -529,6 +541,10 @@ def main():
arch = build_info["arch"]
image_type = build_info["image-type"]

if not testlib.can_boot_test(image_type, arch):
print(f"{image_type} boot tests are not supported yet")
return

print(f"Testing image at {image_path}")
bib_image_id = ""
# Keep test/scripts/imagetestlib.py:CAN_BOOT_TEST in sync as it
Expand Down Expand Up @@ -574,8 +590,7 @@ def main():
boot_wsl(distro, arch, image_path, build_config_path)
case _:
# skip
print(f"{image_type} boot tests are not supported yet")
return
raise MissingBootImplementation(f"{arch} {image_type} is missing a boot implementation.")

print("✅ Marking boot successful")
# amend build info with boot success
Expand Down
11 changes: 10 additions & 1 deletion test/scripts/imgtestlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,16 @@ def read_manifests(path):
return manifests


def can_boot_test(image_type, arch):
return image_type in CAN_BOOT_TEST.get("*", []) + CAN_BOOT_TEST.get(arch, [])


def check_for_build(manifest_fname, build_info_dir, errors):
"""
Checks if a manifest was built (and optionally booted) successfully.

This function returns True if the image needs to be built.
"""
build_info_path = os.path.join(build_info_dir, "info.json")
# rebuild if matching build info is not found
if not os.path.exists(build_info_path):
Expand Down Expand Up @@ -323,7 +332,7 @@ def check_for_build(manifest_fname, build_info_dir, errors):
print(" No PR/branch info available")

image_type = dl_config["image-type"]
if image_type not in CAN_BOOT_TEST.get("*", []) + CAN_BOOT_TEST.get(dl_config["arch"], []):
if not can_boot_test(image_type, dl_config["arch"]):
print(f" Boot testing for {image_type} is not yet supported")
return False

Expand Down
45 changes: 33 additions & 12 deletions vmtest/vm.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ def _log(self, msg):
sys.stdout.write(msg.rstrip("\n") + "\n")

def wait_ssh_ready(self):
wait_ssh_ready(self._address, self._ssh_port, sleep=1, max_wait_sec=600)
wait_ssh_ready(self._address, self._ssh_port, sleep=1, max_wait_sec=1800)

@abc.abstractmethod
def force_stop(self):
Expand Down Expand Up @@ -170,6 +170,12 @@ def __del__(self):
self.force_stop()
shutil.rmtree(self._tmpdir)

def _num_cores(self):
"""
Return the number of CPU cores available on the system.
"""
return os.cpu_count() or 1

def _gen_qemu_cmdline(self, snapshot, use_ovmf):
virtio_scsi_hd = [
"-device", "virtio-scsi-pci,id=scsi",
Expand All @@ -181,56 +187,71 @@ def _gen_qemu_cmdline(self, snapshot, use_ovmf):
"qemu-system-aarch64",
"-machine", "virt",
"-cpu", "cortex-a57",
"-smp", "2",
"-accel", "tcg,thread=multi",
"-smp", str(self._num_cores()),
"-bios", "/usr/share/AAVMF/AAVMF_CODE.fd",
] + virtio_scsi_hd
elif self._arch in ("amd64", "x86_64"):
qemu_cmdline = [
"qemu-system-x86_64",
"-M", "accel=kvm",
# get "illegal instruction" inside the VM otherwise
"-cpu", "host",
"-M", "q35,accel=kvm",
# RHEL 10 requires x86_64-v3 pass it to avoid "illegal instruction", but
# do not use "host" because some modern features are not supported by some
# distributions when running locally on very recent laptops.
"-cpu", "Haswell-v4",
] + virtio_scsi_hd
if use_ovmf:
qemu_cmdline.extend(["-bios", find_ovmf()])
elif self._arch in ("ppc64le", "ppc64"):
qemu_cmdline = [
"qemu-system-ppc64",
"-machine", "pseries",
"-smp", "2",
"-smp", str(self._num_cores()),
] + virtio_scsi_hd
elif self._arch == "s390x":
qemu_cmdline = [
"qemu-system-s390x",
"-machine", "s390-ccw-virtio",
"-smp", "2",
"-smp", str(self._num_cores()),
# sepcial disk setup
"-device", "virtio-blk,drive=disk0,bootindex=1",
]
virtio_net_device = "virtio-net-ccw"
else:
raise ValueError(f"unsupported architecture {self._arch}")

if self._img.suffix == ".qcow2":
img_format = "qcow2"
elif self._img.suffix == ".img":
img_format = "raw"
else:
raise ValueError(f"Unsupported image extension: {self._img}. Must be .qcow2 or .img")

# common part
qemu_cmdline += [
"-m", self._memory,
"-m", str(self._memory),
"-serial", "stdio",
"-monitor", "none",
# make sure SSH key generation during boot does not block due to lack of entropy
"-object", "rng-random,filename=/dev/urandom,id=rng0",
"-device", "virtio-rng-pci,rng=rng0",
# network
"-device", f"{virtio_net_device},netdev=net.0,id=net.0",
"-netdev", f"user,id=net.0,hostfwd=tcp::{self._ssh_port}-:22",
"-qmp", f"unix:{self._qmp_socket},server,nowait",
# boot
"-drive", f"file={self._img},if=none,id=disk0,format=qcow2",
"-drive", f"file={self._img},if=none,id=disk0,cache=unsafe,format={img_format}",
]
if not os.environ.get("OSBUILD_TEST_QEMU_GUI"):
qemu_cmdline.append("-nographic")
if self._cdrom:
qemu_cmdline.extend(["-cdrom", self._cdrom])
qemu_cmdline.extend(["-cdrom", str(self._cdrom)])
if snapshot:
qemu_cmdline.append("-snapshot")
if self._extra_args:
qemu_cmdline.extend(self._extra_args)
qemu_cmdline.append(self._img)
qemu_cmdline.extend(str(arg) for arg in self._extra_args)

print("QEMU: " + " ".join(qemu_cmdline))
return qemu_cmdline

# XXX: move args to init() so that __enter__ can use them?
Expand Down
Loading