diff --git a/src/cijoe/cli/cli.py b/src/cijoe/cli/cli.py
index 0b9782d4..1ec32e8d 100644
--- a/src/cijoe/cli/cli.py
+++ b/src/cijoe/cli/cli.py
@@ -74,9 +74,28 @@ def search_for_file(path: Path) -> Optional[Path]:
def create_combined_toml(configs: list[Path], path: Path):
+ def merge_dicts(a: dict, b: dict) -> dict:
+ """
+ Recursively merge two dictionaries.
+ Values in `b` overwrite values in `a` unless both values are dicts.
+ """
+ result = dict(a)
+
+ for key, value in b.items():
+ if (
+ key in result
+ and isinstance(result[key], dict)
+ and isinstance(value, dict)
+ ):
+ result[key] = merge_dicts(result[key], value)
+ else:
+ result[key] = value
+
+ return result
+
combined_config: dict[str, Any] = {}
for config in configs:
- combined_config |= dict_from_tomlfile(config)
+ combined_config = merge_dicts(combined_config, dict_from_tomlfile(config))
dict_to_tomlfile(combined_config, path)
diff --git a/src/cijoe/core/templates/report-workflow.html.jinja2 b/src/cijoe/core/templates/report-workflow.html.jinja2
index d14ff16c..37e07ddd 100644
--- a/src/cijoe/core/templates/report-workflow.html.jinja2
+++ b/src/cijoe/core/templates/report-workflow.html.jinja2
@@ -349,6 +349,8 @@ function selectFilter(event) {
{% endif %}
.output
+
diff --git a/src/cijoe/qemu/scripts/guest_wait_for_ssh.py b/src/cijoe/qemu/scripts/guest_wait_for_ssh.py
new file mode 100644
index 00000000..4cbc4fea
--- /dev/null
+++ b/src/cijoe/qemu/scripts/guest_wait_for_ssh.py
@@ -0,0 +1,59 @@
+#!/usr/bin/env python3
+"""
+Wait for qemu guest SSH to be ready
+===================================
+
+Note: The script will not fail if the guest does not exist.
+Note: This script does not itself start the qemu guest.
+
+Retargetable: False
+-------------------
+"""
+import logging as log
+import time
+from argparse import ArgumentParser
+
+
+def add_args(parser: ArgumentParser):
+ parser.add_argument(
+ "--transport_name",
+ type=str,
+ default=None,
+ help="Name of the transport to be used. If none given, it uses the first defined transport in the config.",
+ )
+ parser.add_argument(
+ "--timeout",
+ type=int,
+ default=60,
+ help="Amount of seconds to wait for the qemu guest to terminate.",
+ )
+
+
+def main(args, cijoe):
+ """Wait for qemu guest to start"""
+
+ began = time.time()
+ while True:
+ enter = time.time()
+ try:
+ err, state = cijoe.run(
+ "echo 'It is alive!'",
+ transport_name=args.transport_name,
+ )
+ if not err and "It is alive!" in state.output():
+ break
+ except Exception:
+ # do nothing
+ ...
+
+ now = time.time()
+ elapsed_iter = now - enter
+ elapsed_total = now - began
+
+ if elapsed_iter < 5.0:
+ time.sleep(5.0 - elapsed_iter)
+ if elapsed_total > args.timeout:
+ log.error(f"System did not come up within timeout({args.timeout}) seconds")
+ return False
+
+ return 0
diff --git a/src/cijoe/qemu/workflows/example_workflow_guest_aarch64.yaml b/src/cijoe/qemu/workflows/example_workflow_guest_aarch64.yaml
index a9c6de0a..051024fc 100644
--- a/src/cijoe/qemu/workflows/example_workflow_guest_aarch64.yaml
+++ b/src/cijoe/qemu/workflows/example_workflow_guest_aarch64.yaml
@@ -35,6 +35,12 @@ steps:
with:
guest_name: generic-uefi-tcg-aarch64
+- name: guest_wait_start
+ uses: qemu.guest_wait_for_ssh
+ with:
+ transport_name: qemu_guest
+ timeout: 120
+
- name: guest_check
run: |
hostname
diff --git a/src/cijoe/qemu/workflows/example_workflow_guest_x86_64.yaml b/src/cijoe/qemu/workflows/example_workflow_guest_x86_64.yaml
index c3a4dffd..ba790b0a 100644
--- a/src/cijoe/qemu/workflows/example_workflow_guest_x86_64.yaml
+++ b/src/cijoe/qemu/workflows/example_workflow_guest_x86_64.yaml
@@ -35,6 +35,12 @@ steps:
with:
guest_name: generic-bios-kvm-x86_64
+- name: guest_wait_start
+ uses: qemu.guest_wait_for_ssh
+ with:
+ transport_name: qemu_guest
+ timeout: 120
+
- name: guest_check
run: |
hostname
diff --git a/src/cijoe/qemu/wrapper.py b/src/cijoe/qemu/wrapper.py
index 6c0bb4fe..87b9a36b 100644
--- a/src/cijoe/qemu/wrapper.py
+++ b/src/cijoe/qemu/wrapper.py
@@ -218,7 +218,8 @@ def start(self, daemonize=True, extra_args=[]):
"-blockdev",
f"qcow2,node-name=boot,file.driver=file,file.filename={self.boot_img}",
]
- args += ["-device", "virtio-blk-pci,drive=boot"]
+ boot_driver = self.guest_config.get("boot_driver", "virtio-blk-pci")
+ args += ["-device", f"{boot_driver},drive=boot"]
# Process Management stuff
args += ["-pidfile", str(self.pid)]
diff --git a/src/cijoe/system_imaging/auxiliary/cloudinit-linux-common-userdata.user b/src/cijoe/system_imaging/auxiliary/cloudinit-linux-common-userdata.user
index 8daac5b8..278e2bd7 100644
--- a/src/cijoe/system_imaging/auxiliary/cloudinit-linux-common-userdata.user
+++ b/src/cijoe/system_imaging/auxiliary/cloudinit-linux-common-userdata.user
@@ -25,7 +25,8 @@ write_files:
runcmd:
- systemctl restart ssh
-
+- touch /etc/cloud/cloud-init.disabled
+
final_message: "The system is up, after $UPTIME seconds"
power_state:
mode: poweroff