]> git.ipfire.org Git - thirdparty/mkosi.git/commitdiff
Add optional file ID for qemu drives
authorDaan De Meyer <daan.j.demeyer@gmail.com>
Sun, 12 May 2024 14:01:06 +0000 (16:01 +0200)
committerDaan De Meyer <daan.j.demeyer@gmail.com>
Sun, 12 May 2024 18:28:51 +0000 (20:28 +0200)
For testing multipath in systemd's integration tests, we need multiple
qemu drives backed by the same file. Let's allow specifying an additional
file ID to make this possible with QemuDrive=.

mkosi/config.py
mkosi/qemu.py
mkosi/resources/mkosi.md
mkosi/util.py
tests/test_json.py

index 759a54dc4c1b7133ed5dcd0aa5807964038086a5..100d006ca8c516504c0eade1156cc9714f2e4a26 100644 (file)
@@ -138,6 +138,7 @@ class QemuDrive:
     size: int
     directory: Optional[Path]
     options: Optional[str]
+    file_id: str
 
 
 # We use negative numbers for specifying special constants
@@ -1051,7 +1052,7 @@ def parse_drive(value: str) -> QemuDrive:
     if len(parts) < 2:
         die(f"Missing size in drive '{value}")
 
-    if len(parts) > 4:
+    if len(parts) > 5:
         die(f"Too many components in drive '{value}")
 
     id = parts[0]
@@ -1062,8 +1063,9 @@ def parse_drive(value: str) -> QemuDrive:
 
     directory = parse_path(parts[2]) if len(parts) > 2 and parts[2] else None
     options = parts[3] if len(parts) > 3 and parts[3] else None
+    file_id = parts[4] if len(parts) > 4 and parts[4] else id
 
-    return QemuDrive(id=id, size=size, directory=directory, options=options)
+    return QemuDrive(id=id, size=size, directory=directory, options=options, file_id=file_id)
 
 
 def config_parse_sector_size(value: Optional[str], old: Optional[int]) -> Optional[int]:
@@ -4154,19 +4156,20 @@ def json_type_transformer(refcls: Union[type[Args], type[Config]]) -> Callable[[
     def config_drive_transformer(drives: list[dict[str, Any]], fieldtype: type[QemuDrive]) -> list[QemuDrive]:
         # TODO: exchange for TypeGuard and list comprehension once on 3.10
         ret = []
+
         for d in drives:
             assert "Id" in d
             assert "Size" in d
-            assert "Directory" in d
-            assert "Options" in d
             ret.append(
                 QemuDrive(
                     id=d["Id"],
-                    size=int(d["Size"]),
-                    directory=Path(d["Directory"]) if d["Directory"] else None,
-                    options=d["Options"],
+                    size=d["Size"] if isinstance(d["Size"], int) else parse_bytes(d["Size"]),
+                    directory=Path(d["Directory"]) if d.get("Directory") else None,
+                    options=d.get("Options"),
+                    file_id=d.get("FileId", d["Id"]),
                 )
             )
+
         return ret
 
     def generic_version_transformer(
index 13b725d4b682d568b2d96460fd15256e964ae220..85c4294853db2edca9c5389b638aca3daf563bcc 100644 (file)
@@ -46,7 +46,7 @@ from mkosi.sandbox import Mount
 from mkosi.tree import copy_tree, rmtree
 from mkosi.types import PathString
 from mkosi.user import INVOKING_USER, become_root, become_root_cmd
-from mkosi.util import StrEnum, flock, flock_or_die, try_or
+from mkosi.util import StrEnum, flock, flock_or_die, groupby, try_or
 from mkosi.versioncomp import GenericVersion
 
 QEMU_KVM_DEVICE_VERSION = GenericVersion("9.0")
@@ -1123,14 +1123,15 @@ def run_qemu(args: Args, config: Config) -> None:
                 f"type=11,value=io.systemd.boot.kernel-cmdline-extra={' '.join(kcl).replace(',', ',,')}",
             ]
 
-        for drive in config.qemu_drives:
-            file = stack.enter_context(finalize_drive(drive))
+        for _, drives in groupby(config.qemu_drives, key=lambda d: d.file_id):
+            file = stack.enter_context(finalize_drive(drives[0]))
 
-            arg = f"if=none,id={drive.id},file={file},format=raw"
-            if drive.options:
-                arg += f",{drive.options}"
+            for drive in drives:
+                arg = f"if=none,id={drive.id},file={file},format=raw,file.locking=off"
+                if drive.options:
+                    arg += f",{drive.options}"
 
-            cmdline += ["-drive", arg]
+                cmdline += ["-drive", arg]
 
         cmdline += config.qemu_args
         cmdline += args.cmdline
index 965bd6044257584cdbf3b7f1eee0c8ef20a4156b..aa56c198ec68a2f12cf1f19d0fe1ce938b509510 100644 (file)
@@ -1477,15 +1477,18 @@ boolean argument: either `1`, `yes`, or `true` to enable, or `0`, `no`,
 `QemuDrives=`, `--qemu-drive=`
 
 : Add a qemu drive. Takes a colon-delimited string of format
-  `<id>:<size>[:<directory>[:<options>]]`. `id` specifies the qemu id we
-  assign to the drive. This can be used as the `drive=` property in
-  various qemu devices. `size` specifies the size of the drive. This
-  takes a size in bytes. Additionally, the suffixes `K`, `M` and `G` can
-  be used to specify a size in kilobytes, megabytes and gigabytes
-  respectively. `directory` optionally specifies the directory in which
-  to create the file backing the drive. `options` optionally specifies
-  extra comma-delimited properties which are passed verbatim to qemu's
-  `-drive` option.
+  `<id>:<size>[:<directory>[:<options>[:<file-id>]]]`. `id` specifies
+  the qemu ID assigned to the drive. This can be used as the `drive=`
+  property in various qemu devices. `size` specifies the size of the
+  drive. This takes a size in bytes. Additionally, the suffixes `K`, `M`
+  and `G` can be used to specify a size in kilobytes, megabytes and
+  gigabytes respectively. `directory` optionally specifies the directory
+  in which to create the file backing the drive. `options` optionally
+  specifies extra comma-delimited properties which are passed verbatim
+  to qemu's `-drive` option. `file-id` specifies the ID of the file
+  backing the drive. Drives with the same file ID will share the
+  backing file. The directory and size of the file will be determined
+  from the first drive with a given file ID.
 
 : Example usage:
 
index 6bd3ac3efaae117dd81e3dca63f968a2bba9f1c6..34c2c434bb305d2aae1b15bc555f0492e546110f 100644 (file)
@@ -17,7 +17,7 @@ import re
 import resource
 import stat
 import tempfile
-from collections.abc import Iterable, Iterator, Mapping, Sequence
+from collections.abc import Hashable, Iterable, Iterator, Mapping, Sequence
 from pathlib import Path
 from types import ModuleType
 from typing import Any, Callable, Optional, TypeVar, no_type_check
@@ -27,6 +27,7 @@ from mkosi.types import PathString
 
 T = TypeVar("T")
 V = TypeVar("V")
+S = TypeVar("S", bound=Hashable)
 
 
 def dictify(f: Callable[..., Iterator[tuple[T, V]]]) -> Callable[..., dict[T, V]]:
@@ -319,3 +320,17 @@ def try_or(fn: Callable[..., T], exception: type[Exception], default: T) -> T:
         return fn()
     except exception:
         return default
+
+
+def groupby(seq: Sequence[T], key: Callable[[T], S]) -> list[tuple[S, list[T]]]:
+    grouped: dict[S, list[T]] = {}
+
+    for i in seq:
+        k = key(i)
+
+        if k not in grouped:
+            grouped[k] = []
+
+        grouped[k].append(i)
+
+    return [(key, group) for key, group in grouped.items()]
index 3008bc7715919f6575ff14c5e74dffa17a1bde96..88ce42b332b75c4166bbe06c80f3c1e842fb1fdc 100644 (file)
@@ -232,12 +232,14 @@ def test_config() -> None:
             "QemuDrives": [
                 {
                     "Directory": "/foo/bar",
+                    "FileId": "red",
                     "Id": "abc",
                     "Options": "abc,qed",
                     "Size": 200
                 },
                 {
                     "Directory": null,
+                    "FileId": "wcd",
                     "Id": "abc",
                     "Options": "",
                     "Size": 200
@@ -437,7 +439,10 @@ def test_config() -> None:
         proxy_url="https://my/proxy",
         qemu_args=[],
         qemu_cdrom=False,
-        qemu_drives=[QemuDrive("abc", 200, Path("/foo/bar"), "abc,qed"), QemuDrive("abc", 200, None, "")],
+        qemu_drives=[
+            QemuDrive("abc", 200, Path("/foo/bar"), "abc,qed", "red"),
+            QemuDrive("abc", 200, None, "", "wcd"),
+        ],
         qemu_firmware=QemuFirmware.linux,
         qemu_firmware_variables=Path("/foo/bar"),
         qemu_gui=True,