From bd383a9f35feef8215238db8919a647eca88f70d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= Date: Fri, 20 Sep 2024 13:30:39 +0200 Subject: [PATCH 1/8] add nixos-facter to flash installer --- nixosModules/installer/default.nix | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nixosModules/installer/default.nix b/nixosModules/installer/default.nix index e80484d36..56086efb5 100644 --- a/nixosModules/installer/default.nix +++ b/nixosModules/installer/default.nix @@ -49,6 +49,8 @@ in (modulesPath + "/profiles/base.nix") ]; + environment.systemPackages = [ pkgs.nixos-facter ]; + ######################################################################################################## # # # Copied from: # From 5b878ac8aca62298fda06fe1df092acd8e0bda0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= Date: Fri, 20 Sep 2024 13:34:15 +0200 Subject: [PATCH 2/8] use packaged nixos-facter in generate_hardware_info --- pkgs/clan-cli/clan_cli/machines/hardware.py | 23 ++++++++++----------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/pkgs/clan-cli/clan_cli/machines/hardware.py b/pkgs/clan-cli/clan_cli/machines/hardware.py index 5e59b63de..62728f0d5 100644 --- a/pkgs/clan-cli/clan_cli/machines/hardware.py +++ b/pkgs/clan-cli/clan_cli/machines/hardware.py @@ -117,13 +117,16 @@ def generate_machine_hardware_info( if hostname is not None: machine.target_host_address = hostname - nixos_generate_cmd = [ - "nixos-generate-config", # Filesystems are managed by disko - "--no-filesystems", - "--show-hardware-config", - ] - - nixos_facter_cmd = ["nix", "run", "--refresh", "github:numtide/nixos-facter"] + config_command = ( + ["nixos-facter"] + if report_type == "nixos-facter" + else [ + "nixos-generate-config", + # Filesystems are managed by disko + "--no-filesystems", + "--show-hardware-config", + ] + ) host = machine.target_host target_host = f"{host.user or 'root'}@{host.host}" @@ -148,11 +151,7 @@ def generate_machine_hardware_info( else [] ), target_host, - *( - nixos_generate_cmd - if report_type == "nixos-generate-config" - else nixos_facter_cmd - ), + *config_command, ], ) out = run(cmd) From 0698ecbf29708c308d63bbfca24f23993cb31933 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= Date: Fri, 20 Sep 2024 18:07:49 +0200 Subject: [PATCH 3/8] docs: renaming "Include Machines" to "Adding Machines" --- docs/mkdocs.yml | 2 +- docs/site/manual/{include-machines.md => adding-machines.md} | 2 +- docs/site/manual/index.md | 2 +- docs/site/manual/inventory.md | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) rename docs/site/manual/{include-machines.md => adding-machines.md} (98%) diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 0466878a4..f706fe312 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -52,7 +52,7 @@ nav: - Disk Encryption: getting-started/disk-encryption.md - Mesh VPN: getting-started/mesh-vpn.md - Backup & Restore: getting-started/backups.md - - Include Machines: manual/include-machines.md + - Adding Machines: manual/adding-machines.md - Inventory: manual/inventory.md - Secrets: manual/secrets.md - Secure Boot: manual/secure-boot.md diff --git a/docs/site/manual/include-machines.md b/docs/site/manual/adding-machines.md similarity index 98% rename from docs/site/manual/include-machines.md rename to docs/site/manual/adding-machines.md index 79761c9bb..a1ecf2748 100644 --- a/docs/site/manual/include-machines.md +++ b/docs/site/manual/adding-machines.md @@ -1,4 +1,4 @@ -# Include Machines +# Adding Machines Clan has two general methods of adding machines diff --git a/docs/site/manual/index.md b/docs/site/manual/index.md index 2acc826b8..a73594132 100644 --- a/docs/site/manual/index.md +++ b/docs/site/manual/index.md @@ -23,7 +23,7 @@ Instructions and explanations for practical Implementations ordered by Topics. **How-to Guides for achieving a certain goal or solving a specific issue.** -- [Include Machines](./include-machines.md): Learn how Clan automatically includes machines and Nix files. +- [Adding Machines](./adding-machines.md): Learn how Clan automatically includes machines and Nix files. - [Secrets](./secrets.md): Learn how to manage secrets. diff --git a/docs/site/manual/inventory.md b/docs/site/manual/inventory.md index 83e6f510f..d31d3cee5 100644 --- a/docs/site/manual/inventory.md +++ b/docs/site/manual/inventory.md @@ -14,7 +14,7 @@ This guide will walk you through setting up a backup service, where the inventor ## Prerequisites -- [x] [Add machines](./include-machines.md) to your clan. +- [x] [Add machines](./adding-machines.md) to your clan. ## Services From 4936db0187053c84d31cce81d7abaf7d6b56b2f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= Date: Fri, 20 Sep 2024 18:26:41 +0200 Subject: [PATCH 4/8] fix container test after systemd update --- checks/lib/container-driver/test_driver/__init__.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/checks/lib/container-driver/test_driver/__init__.py b/checks/lib/container-driver/test_driver/__init__.py index d2385c03c..e45cf3db2 100644 --- a/checks/lib/container-driver/test_driver/__init__.py +++ b/checks/lib/container-driver/test_driver/__init__.py @@ -78,7 +78,10 @@ class Machine: assert self.process.stdout is not None, "Machine has no stdout" for line in self.process.stdout: print(line, end="") - if line.startswith("systemd[1]: Startup finished in"): + if ( + line.startswith("systemd[1]: Startup finished in") + or "Welcome to NixOS" in line + ): break else: msg = f"Failed to start container {self.name}" From afbac7f08ce0272f951659905e7eba58391bf737 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= Date: Fri, 20 Sep 2024 18:26:45 +0200 Subject: [PATCH 5/8] bump flakes --- flake.lock | 26 +++++++++++++------------- flake.nix | 2 +- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/flake.lock b/flake.lock index 0b0b5192a..e516e2502 100644 --- a/flake.lock +++ b/flake.lock @@ -7,11 +7,11 @@ ] }, "locked": { - "lastModified": 1726842196, - "narHash": "sha256-u9h03JQUuQJ607xmti9F9Eh6E96kKUAGP+aXWgwm70o=", + "lastModified": 1727156717, + "narHash": "sha256-Ef7UgoTdOB4PGQKSkHGu6SOxnTiArPHGcRf8qGFC39o=", "owner": "nix-community", "repo": "disko", - "rev": "51994df8ba24d5db5459ccf17b6494643301ad28", + "rev": "c61e50b63ad50dda5797b1593ad7771be496efbb", "type": "github" }, "original": { @@ -63,11 +63,11 @@ ] }, "locked": { - "lastModified": 1727020761, - "narHash": "sha256-hDH9XlbsNAoTmdMn//s0OOIyHOjF0RIAFLaiy9nWq9I=", + "lastModified": 1727055034, + "narHash": "sha256-nRy1zsY8HPIGgyfQFsopkC9kmpxC9Dl/+POs7RYMug0=", "owner": "nix-community", "repo": "nixos-images", - "rev": "776ee2484dcf6c8a667b1b918981493ee976dba9", + "rev": "1420644027326490d330828b941a8e612b9cc130", "type": "github" }, "original": { @@ -78,16 +78,16 @@ }, "nixpkgs": { "locked": { - "lastModified": 1725814378, - "narHash": "sha256-cwnCIninNWySL3ruFH5iVFnx/Fr0xL44NOLzvf1s2tc=", + "lastModified": 1727089097, + "narHash": "sha256-ZMHMThPsthhUREwDebXw7GX45bJnBCVbfnH1g5iuSPc=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "61ddb09cfaa7424d7fc8e3040ccd5c8c6f875b15", + "rev": "568bfef547c14ca438c56a0bece08b8bb2b71a9c", "type": "github" }, "original": { "owner": "NixOS", - "ref": "nixos-unstable-small", + "ref": "nixpkgs-unstable", "repo": "nixpkgs", "type": "github" } @@ -147,11 +147,11 @@ ] }, "locked": { - "lastModified": 1726734507, - "narHash": "sha256-VUH5O5AcOSxb0uL/m34dDkxFKP6WLQ6y4I1B4+N3L2w=", + "lastModified": 1727098951, + "narHash": "sha256-gplorAc0ISAUPemUNOnRUs7jr3WiLiHZb3DJh++IkZs=", "owner": "numtide", "repo": "treefmt-nix", - "rev": "ee41a466c2255a3abe6bc50fc6be927cdee57a9f", + "rev": "35dfece10c642eb52928a48bee7ac06a59f93e9a", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 458ebe95c..859789fa6 100644 --- a/flake.nix +++ b/flake.nix @@ -2,7 +2,7 @@ description = "clan.lol base operating system"; inputs = { - nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable-small"; + nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; disko.url = "github:nix-community/disko"; disko.inputs.nixpkgs.follows = "nixpkgs"; sops-nix.url = "github:Mic92/sops-nix"; From 44e17e9ee6de8b5e583dd5417e2061a34d78c261 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= Date: Tue, 24 Sep 2024 11:37:48 +0200 Subject: [PATCH 6/8] fix zfs latest again --- nixosModules/bcachefs.nix | 4 ---- nixosModules/installer/default.nix | 1 + nixosModules/installer/zfs-latest.nix | 28 +++++++++++++++++++++++++++ 3 files changed, 29 insertions(+), 4 deletions(-) create mode 100644 nixosModules/installer/zfs-latest.nix diff --git a/nixosModules/bcachefs.nix b/nixosModules/bcachefs.nix index 43dc7b43c..c17cd9306 100644 --- a/nixosModules/bcachefs.nix +++ b/nixosModules/bcachefs.nix @@ -1,15 +1,11 @@ { lib, pkgs, - config, ... }: { # If we also need zfs, we can use the unstable version as we otherwise don't have a new enough kernel version boot.zfs.package = pkgs.zfsUnstable; - boot.kernelPackages = lib.mkIf config.boot.zfs.enabled ( - lib.mkForce config.boot.zfs.package.latestCompatibleLinuxPackages - ); # Enable bcachefs support boot.supportedFilesystems.bcachefs = lib.mkDefault true; diff --git a/nixosModules/installer/default.nix b/nixosModules/installer/default.nix index 56086efb5..49620e460 100644 --- a/nixosModules/installer/default.nix +++ b/nixosModules/installer/default.nix @@ -47,6 +47,7 @@ in (modulesPath + "/profiles/installation-device.nix") (modulesPath + "/profiles/all-hardware.nix") (modulesPath + "/profiles/base.nix") + ./zfs-latest.nix ]; environment.systemPackages = [ pkgs.nixos-facter ]; diff --git a/nixosModules/installer/zfs-latest.nix b/nixosModules/installer/zfs-latest.nix new file mode 100644 index 000000000..d33bd9d4c --- /dev/null +++ b/nixosModules/installer/zfs-latest.nix @@ -0,0 +1,28 @@ +{ + lib, + pkgs, + config, + ... +}: + +let + isUnstable = config.boot.zfs.package == pkgs.zfsUnstable; + zfsCompatibleKernelPackages = lib.filterAttrs ( + name: kernelPackages: + (builtins.match "linux_[0-9]+_[0-9]+" name) != null + && (builtins.tryEval kernelPackages).success + && ( + (!isUnstable && !kernelPackages.zfs.meta.broken) + || (isUnstable && !kernelPackages.zfs_unstable.meta.broken) + ) + ) pkgs.linuxKernel.packages; + latestKernelPackage = lib.last ( + lib.sort (a: b: (lib.versionOlder a.kernel.version b.kernel.version)) ( + builtins.attrValues zfsCompatibleKernelPackages + ) + ); +in +{ + # Note this might jump back and worth as kernel get added or removed. + boot.kernelPackages = latestKernelPackage; +} From 419da827aed639e284624cdbcc3c2368619bffa1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= Date: Tue, 24 Sep 2024 12:24:27 +0200 Subject: [PATCH 7/8] container driver: add wait_until_succeeds --- checks/lib/container-driver/package.nix | 4 + .../container-driver/test_driver/__init__.py | 61 +++- .../container-driver/test_driver/logger.py | 335 ++++++++++++++++++ 3 files changed, 395 insertions(+), 5 deletions(-) create mode 100644 checks/lib/container-driver/test_driver/logger.py diff --git a/checks/lib/container-driver/package.nix b/checks/lib/container-driver/package.nix index cbb47e2ee..d4dad4aa9 100644 --- a/checks/lib/container-driver/package.nix +++ b/checks/lib/container-driver/package.nix @@ -5,6 +5,8 @@ setuptools, util-linux, systemd, + colorama, + junit-xml, }: buildPythonApplication { pname = "test-driver"; @@ -12,6 +14,8 @@ buildPythonApplication { propagatedBuildInputs = [ util-linux systemd + colorama + junit-xml ] ++ extraPythonPackages python3Packages; nativeBuildInputs = [ setuptools ]; format = "pyproject"; diff --git a/checks/lib/container-driver/test_driver/__init__.py b/checks/lib/container-driver/test_driver/__init__.py index e45cf3db2..a74fab7b4 100644 --- a/checks/lib/container-driver/test_driver/__init__.py +++ b/checks/lib/container-driver/test_driver/__init__.py @@ -5,10 +5,13 @@ import subprocess import time import types from collections.abc import Callable +from contextlib import _GeneratorContextManager from pathlib import Path from tempfile import TemporaryDirectory from typing import Any +from .logger import AbstractLogger, CompositeLogger, TerminalLogger + class Error(Exception): pass @@ -42,12 +45,20 @@ def retry(fn: Callable, timeout: int = 900) -> None: class Machine: - def __init__(self, name: str, toplevel: Path, rootdir: Path, out_dir: str) -> None: + def __init__( + self, + name: str, + toplevel: Path, + logger: AbstractLogger, + rootdir: Path, + out_dir: str, + ) -> None: self.name = name self.toplevel = toplevel self.out_dir = out_dir self.process: subprocess.Popen | None = None self.rootdir: Path = rootdir + self.logger = logger def start(self) -> None: prepare_machine_root(self.name, self.rootdir) @@ -187,6 +198,15 @@ class Machine: ) return proc + def nested( + self, msg: str, attrs: dict[str, str] | None = None + ) -> _GeneratorContextManager: + if attrs is None: + attrs = {} + my_attrs = {"machine": self.name} + my_attrs.update(attrs) + return self.logger.nested(msg, my_attrs) + def systemctl(self, q: str) -> subprocess.CompletedProcess: """ Runs `systemctl` commands with optional support for @@ -203,6 +223,25 @@ class Machine: """ return self.execute(f"systemctl {q}") + def wait_until_succeeds(self, command: str, timeout: int = 900) -> str: + """ + Repeat a shell command with 1-second intervals until it succeeds. + Has a default timeout of 900 seconds which can be modified, e.g. + `wait_until_succeeds(cmd, timeout=10)`. See `execute` for details on + command execution. + Throws an exception on timeout. + """ + output = "" + + def check_success(_: Any) -> bool: + nonlocal output + result = self.execute(command, timeout=timeout) + return result.returncode == 0 + + with self.nested(f"waiting for success: {command}"): + retry(check_success, timeout) + return output + def wait_for_unit(self, unit: str, timeout: int = 900) -> None: """ Wait for a systemd unit to get into "active" state. @@ -260,10 +299,19 @@ def setup_filesystems() -> None: class Driver: - def __init__(self, containers: list[Path], testscript: str, out_dir: str) -> None: + logger: AbstractLogger + + def __init__( + self, + containers: list[Path], + logger: AbstractLogger, + testscript: str, + out_dir: str, + ) -> None: self.containers = containers self.testscript = testscript self.out_dir = out_dir + self.logger = logger setup_filesystems() self.tempdir = TemporaryDirectory() @@ -282,6 +330,7 @@ class Driver: toplevel=container, rootdir=tempdir_path / name, out_dir=self.out_dir, + logger=self.logger, ) ) @@ -367,9 +416,11 @@ def main() -> None: type=writeable_dir, ) args = arg_parser.parse_args() + logger = CompositeLogger([TerminalLogger()]) with Driver( - args.containers, - args.test_script.read_text(), - args.output_directory.resolve(), + containers=args.containers, + testscript=args.test_script.read_text(), + out_dir=args.output_directory.resolve(), + logger=logger, ) as driver: driver.run_tests() diff --git a/checks/lib/container-driver/test_driver/logger.py b/checks/lib/container-driver/test_driver/logger.py new file mode 100644 index 000000000..309801b84 --- /dev/null +++ b/checks/lib/container-driver/test_driver/logger.py @@ -0,0 +1,335 @@ +import atexit +import codecs +import os +import sys +import time +import unicodedata +from abc import ABC, abstractmethod +from collections.abc import Iterator +from contextlib import ExitStack, contextmanager +from pathlib import Path +from queue import Empty, Queue +from typing import Any +from xml.sax.saxutils import XMLGenerator +from xml.sax.xmlreader import AttributesImpl + +from colorama import Fore, Style +from junit_xml import TestCase, TestSuite + + +class AbstractLogger(ABC): + @abstractmethod + def log(self, message: str, attributes: dict[str, str] | None = None) -> None: + pass + + @abstractmethod + @contextmanager + def subtest( + self, name: str, attributes: dict[str, str] | None = None + ) -> Iterator[None]: + pass + + @abstractmethod + @contextmanager + def nested( + self, message: str, attributes: dict[str, str] | None = None + ) -> Iterator[None]: + pass + + @abstractmethod + def info(self, *args: Any, **kwargs: Any) -> None: # type: ignore + pass + + @abstractmethod + def warning(self, *args: Any, **kwargs: Any) -> None: # type: ignore + pass + + @abstractmethod + def error(self, *args: Any, **kwargs: Any) -> None: # type: ignore + pass + + @abstractmethod + def log_serial(self, message: str, machine: str) -> None: + pass + + @abstractmethod + def print_serial_logs(self, enable: bool) -> None: + pass + + +class JunitXMLLogger(AbstractLogger): + class TestCaseState: + def __init__(self) -> None: + self.stdout = "" + self.stderr = "" + self.failure = False + + def __init__(self, outfile: Path) -> None: + self.tests: dict[str, JunitXMLLogger.TestCaseState] = { + "main": self.TestCaseState() + } + self.currentSubtest = "main" + self.outfile: Path = outfile + self._print_serial_logs = True + atexit.register(self.close) + + def log(self, message: str, attributes: dict[str, str] | None = None) -> None: + self.tests[self.currentSubtest].stdout += message + os.linesep + + @contextmanager + def subtest( + self, name: str, attributes: dict[str, str] | None = None + ) -> Iterator[None]: + old_test = self.currentSubtest + self.tests.setdefault(name, self.TestCaseState()) + self.currentSubtest = name + + yield + + self.currentSubtest = old_test + + @contextmanager + def nested( + self, message: str, attributes: dict[str, str] | None = None + ) -> Iterator[None]: + self.log(message) + yield + + def info(self, *args: Any, **kwargs: Any) -> None: + self.tests[self.currentSubtest].stdout += args[0] + os.linesep + + def warning(self, *args: Any, **kwargs: Any) -> None: + self.tests[self.currentSubtest].stdout += args[0] + os.linesep + + def error(self, *args: Any, **kwargs: Any) -> None: + self.tests[self.currentSubtest].stderr += args[0] + os.linesep + self.tests[self.currentSubtest].failure = True + + def log_serial(self, message: str, machine: str) -> None: + if not self._print_serial_logs: + return + + self.log(f"{machine} # {message}") + + def print_serial_logs(self, enable: bool) -> None: + self._print_serial_logs = enable + + def close(self) -> None: + with Path.open(self.outfile, "w") as f: + test_cases = [] + for name, test_case_state in self.tests.items(): + tc = TestCase( + name, + stdout=test_case_state.stdout, + stderr=test_case_state.stderr, + ) + if test_case_state.failure: + tc.add_failure_info("test case failed") + + test_cases.append(tc) + ts = TestSuite("NixOS integration test", test_cases) + f.write(TestSuite.to_xml_string([ts])) + + +class CompositeLogger(AbstractLogger): + def __init__(self, logger_list: list[AbstractLogger]) -> None: + self.logger_list = logger_list + + def add_logger(self, logger: AbstractLogger) -> None: + self.logger_list.append(logger) + + def log(self, message: str, attributes: dict[str, str] | None = None) -> None: + for logger in self.logger_list: + logger.log(message, attributes) + + @contextmanager + def subtest( + self, name: str, attributes: dict[str, str] | None = None + ) -> Iterator[None]: + with ExitStack() as stack: + for logger in self.logger_list: + stack.enter_context(logger.subtest(name, attributes)) + yield + + @contextmanager + def nested( + self, message: str, attributes: dict[str, str] | None = None + ) -> Iterator[None]: + with ExitStack() as stack: + for logger in self.logger_list: + stack.enter_context(logger.nested(message, attributes)) + yield + + def info(self, *args: Any, **kwargs: Any) -> None: # type: ignore + for logger in self.logger_list: + logger.info(*args, **kwargs) + + def warning(self, *args: Any, **kwargs: Any) -> None: # type: ignore + for logger in self.logger_list: + logger.warning(*args, **kwargs) + + def error(self, *args: Any, **kwargs: Any) -> None: # type: ignore + for logger in self.logger_list: + logger.error(*args, **kwargs) + sys.exit(1) + + def print_serial_logs(self, enable: bool) -> None: + for logger in self.logger_list: + logger.print_serial_logs(enable) + + def log_serial(self, message: str, machine: str) -> None: + for logger in self.logger_list: + logger.log_serial(message, machine) + + +class TerminalLogger(AbstractLogger): + def __init__(self) -> None: + self._print_serial_logs = True + + def maybe_prefix(self, message: str, attributes: dict[str, str] | None) -> str: + if attributes and "machine" in attributes: + return f"{attributes['machine']}: {message}" + return message + + @staticmethod + def _eprint(*args: object, **kwargs: Any) -> None: + print(*args, file=sys.stderr, **kwargs) + + def log(self, message: str, attributes: dict[str, str] | None = None) -> None: + self._eprint(self.maybe_prefix(message, attributes)) + + @contextmanager + def subtest( + self, name: str, attributes: dict[str, str] | None = None + ) -> Iterator[None]: + with self.nested("subtest: " + name, attributes): + yield + + @contextmanager + def nested( + self, message: str, attributes: dict[str, str] | None = None + ) -> Iterator[None]: + self._eprint( + self.maybe_prefix( + Style.BRIGHT + Fore.GREEN + message + Style.RESET_ALL, attributes + ) + ) + + tic = time.time() + yield + toc = time.time() + self.log(f"(finished: {message}, in {toc - tic:.2f} seconds)") + + def info(self, *args: Any, **kwargs: Any) -> None: # type: ignore + self.log(*args, **kwargs) + + def warning(self, *args: Any, **kwargs: Any) -> None: # type: ignore + self.log(*args, **kwargs) + + def error(self, *args: Any, **kwargs: Any) -> None: # type: ignore + self.log(*args, **kwargs) + + def print_serial_logs(self, enable: bool) -> None: + self._print_serial_logs = enable + + def log_serial(self, message: str, machine: str) -> None: + if not self._print_serial_logs: + return + + self._eprint(Style.DIM + f"{machine} # {message}" + Style.RESET_ALL) + + +class XMLLogger(AbstractLogger): + def __init__(self, outfile: str) -> None: + self.logfile_handle = codecs.open(outfile, "wb") + self.xml = XMLGenerator(self.logfile_handle, encoding="utf-8") + self.queue: Queue[dict[str, str]] = Queue() + + self._print_serial_logs = True + + self.xml.startDocument() + self.xml.startElement("logfile", attrs=AttributesImpl({})) + + def close(self) -> None: + self.xml.endElement("logfile") + self.xml.endDocument() + self.logfile_handle.close() + + def sanitise(self, message: str) -> str: + return "".join(ch for ch in message if unicodedata.category(ch)[0] != "C") + + def maybe_prefix( + self, message: str, attributes: dict[str, str] | None = None + ) -> str: + if attributes and "machine" in attributes: + return f"{attributes['machine']}: {message}" + return message + + def log_line(self, message: str, attributes: dict[str, str]) -> None: + self.xml.startElement("line", attrs=AttributesImpl(attributes)) + self.xml.characters(message) + self.xml.endElement("line") + + def info(self, *args: Any, **kwargs: Any) -> None: # type: ignore + self.log(*args, **kwargs) + + def warning(self, *args: Any, **kwargs: Any) -> None: # type: ignore + self.log(*args, **kwargs) + + def error(self, *args: Any, **kwargs: Any) -> None: # type: ignore + self.log(*args, **kwargs) + + def log(self, message: str, attributes: dict[str, str] | None = None) -> None: + if attributes is None: + attributes = {} + self.drain_log_queue() + self.log_line(message, attributes) + + def print_serial_logs(self, enable: bool) -> None: + self._print_serial_logs = enable + + def log_serial(self, message: str, machine: str) -> None: + if not self._print_serial_logs: + return + + self.enqueue({"msg": message, "machine": machine, "type": "serial"}) + + def enqueue(self, item: dict[str, str]) -> None: + self.queue.put(item) + + def drain_log_queue(self) -> None: + try: + while True: + item = self.queue.get_nowait() + msg = self.sanitise(item["msg"]) + del item["msg"] + self.log_line(msg, item) + except Empty: + pass + + @contextmanager + def subtest( + self, name: str, attributes: dict[str, str] | None = None + ) -> Iterator[None]: + with self.nested("subtest: " + name, attributes): + yield + + @contextmanager + def nested( + self, message: str, attributes: dict[str, str] | None = None + ) -> Iterator[None]: + if attributes is None: + attributes = {} + self.xml.startElement("nest", attrs=AttributesImpl({})) + self.xml.startElement("head", attrs=AttributesImpl(attributes)) + self.xml.characters(message) + self.xml.endElement("head") + + tic = time.time() + self.drain_log_queue() + yield + self.drain_log_queue() + toc = time.time() + self.log(f"(finished: {message}, in {toc - tic:.2f} seconds)") + + self.xml.endElement("nest") From d1952cbed7c7783c7207c00df51a7f82d89bd685 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= Date: Tue, 24 Sep 2024 12:27:26 +0200 Subject: [PATCH 8/8] matrix-synapse: fix race condition --- checks/matrix-synapse/default.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/checks/matrix-synapse/default.nix b/checks/matrix-synapse/default.nix index 5e7e17780..21bfcfe8b 100644 --- a/checks/matrix-synapse/default.nix +++ b/checks/matrix-synapse/default.nix @@ -70,7 +70,7 @@ start_all() machine.wait_for_unit("matrix-synapse") machine.succeed("${pkgs.netcat}/bin/nc -z -v ::1 8008") - machine.succeed("${pkgs.curl}/bin/curl -Ssf -L http://localhost/_matrix/static/ -H 'Host: matrix.clan.test'") + machine.wait_until_succeeds("${pkgs.curl}/bin/curl -Ssf -L http://localhost/_matrix/static/ -H 'Host: matrix.clan.test'") machine.systemctl("restart matrix-synapse >&2") # check if user creation is idempotent machine.execute("journalctl -u matrix-synapse --no-pager >&2")