Unverified Commit 7e2d8935 authored by github-actions[bot]'s avatar github-actions[bot] Committed by GitHub
Browse files

Merge master into staging-next

parents f55aa936 13f32500
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -4520,6 +4520,12 @@
    githubId = 1708810;
    name = "Daniel Vianna";
  };
  dmytrokyrychuk = {
    email = "dmytro@kyrych.uk";
    github = "dmytrokyrychuk";
    githubId = 699961;
    name = "Dmytro Kyrychuk";
  };
  dnr = {
    email = "dnr@dnr.im";
    github = "dnr";
+2 −0
Original line number Diff line number Diff line
@@ -80,6 +80,8 @@

- [Jool](https://nicmx.github.io/Jool/en/index.html), a kernelspace NAT64 and SIIT implementation, providing translation between IPv4 and IPv6. Available as [networking.jool.enable](#opt-networking.jool.enable).

- [Home Assistant Satellite], a streaming audio satellite for Home Assistant voice pipelines, where you can reuse existing mic/speaker hardware. Available as [services.homeassistant-satellite](#opt-services.homeassistant-satellite.enable).

- [Apache Guacamole](https://guacamole.apache.org/), a cross-platform, clientless remote desktop gateway. Available as [services.guacamole-server](#opt-services.guacamole-server.enable) and [services.guacamole-client](#opt-services.guacamole-client.enable) services.

- [pgBouncer](https://www.pgbouncer.org), a PostgreSQL connection pooler. Available as [services.pgbouncer](#opt-services.pgbouncer.enable).
+14 −1
Original line number Diff line number Diff line
@@ -19,6 +19,8 @@ from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple

from test_driver.logger import rootlog

from .qmp import QMPSession

CHAR_TO_KEY = {
    "A": "shift-a",
    "N": "shift-n",
@@ -144,6 +146,7 @@ class StartCommand:
    def cmd(
        self,
        monitor_socket_path: Path,
        qmp_socket_path: Path,
        shell_socket_path: Path,
        allow_reboot: bool = False,
    ) -> str:
@@ -167,6 +170,7 @@ class StartCommand:

        return (
            f"{self._cmd}"
            f" -qmp unix:{qmp_socket_path},server=on,wait=off"
            f" -monitor unix:{monitor_socket_path}"
            f" -chardev socket,id=shell,path={shell_socket_path}"
            f"{qemu_opts}"
@@ -194,11 +198,14 @@ class StartCommand:
        state_dir: Path,
        shared_dir: Path,
        monitor_socket_path: Path,
        qmp_socket_path: Path,
        shell_socket_path: Path,
        allow_reboot: bool,
    ) -> subprocess.Popen:
        return subprocess.Popen(
            self.cmd(monitor_socket_path, shell_socket_path, allow_reboot),
            self.cmd(
                monitor_socket_path, qmp_socket_path, shell_socket_path, allow_reboot
            ),
            stdin=subprocess.PIPE,
            stdout=subprocess.PIPE,
            stderr=subprocess.STDOUT,
@@ -309,6 +316,7 @@ class Machine:
    shared_dir: Path
    state_dir: Path
    monitor_path: Path
    qmp_path: Path
    shell_path: Path

    start_command: StartCommand
@@ -317,6 +325,7 @@ class Machine:
    process: Optional[subprocess.Popen]
    pid: Optional[int]
    monitor: Optional[socket.socket]
    qmp_client: Optional[QMPSession]
    shell: Optional[socket.socket]
    serial_thread: Optional[threading.Thread]

@@ -352,6 +361,7 @@ class Machine:

        self.state_dir = self.tmp_dir / f"vm-state-{self.name}"
        self.monitor_path = self.state_dir / "monitor"
        self.qmp_path = self.state_dir / "qmp"
        self.shell_path = self.state_dir / "shell"
        if (not self.keep_vm_state) and self.state_dir.exists():
            self.cleanup_statedir()
@@ -360,6 +370,7 @@ class Machine:
        self.process = None
        self.pid = None
        self.monitor = None
        self.qmp_client = None
        self.shell = None
        self.serial_thread = None

@@ -1112,11 +1123,13 @@ class Machine:
            self.state_dir,
            self.shared_dir,
            self.monitor_path,
            self.qmp_path,
            self.shell_path,
            allow_reboot,
        )
        self.monitor, _ = monitor_socket.accept()
        self.shell, _ = shell_socket.accept()
        self.qmp_client = QMPSession.from_path(self.qmp_path)

        # Store last serial console lines for use
        # of wait_for_console_text
+98 −0
Original line number Diff line number Diff line
import json
import logging
import os
import socket
from collections.abc import Iterator
from pathlib import Path
from queue import Queue
from typing import Any

logger = logging.getLogger(__name__)


class QMPAPIError(RuntimeError):
    def __init__(self, message: dict[str, Any]):
        assert "error" in message, "Not an error message!"
        try:
            self.class_name = message["class"]
            self.description = message["desc"]
            # NOTE: Some errors can occur before the Server is able to read the
            # id member; in these cases the id member will not be part of the
            # error response, even if provided by the client.
            self.transaction_id = message.get("id")
        except KeyError:
            raise RuntimeError("Malformed QMP API error response")

    def __str__(self) -> str:
        return f"<QMP API error related to transaction {self.transaction_id} [{self.class_name}]: {self.description}>"


class QMPSession:
    def __init__(self, sock: socket.socket) -> None:
        self.sock = sock
        self.results: Queue[dict[str, str]] = Queue()
        self.pending_events: Queue[dict[str, Any]] = Queue()
        self.reader = sock.makefile("r")
        self.writer = sock.makefile("w")
        # Make the reader non-blocking so we can kind of select on it.
        os.set_blocking(self.reader.fileno(), False)
        hello = self._wait_for_new_result()
        logger.debug(f"Got greeting from QMP API: {hello}")
        # The greeting message format is:
        # { "QMP": { "version": json-object, "capabilities": json-array } }
        assert "QMP" in hello, f"Unexpected result: {hello}"
        self.send("qmp_capabilities")

    @classmethod
    def from_path(cls, path: Path) -> "QMPSession":
        sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
        sock.connect(str(path))
        return cls(sock)

    def __del__(self) -> None:
        self.sock.close()

    def _wait_for_new_result(self) -> dict[str, str]:
        assert self.results.empty(), "Results set is not empty, missed results!"
        while self.results.empty():
            self.read_pending_messages()
        return self.results.get()

    def read_pending_messages(self) -> None:
        line = self.reader.readline()
        if not line:
            return
        evt_or_result = json.loads(line)
        logger.debug(f"Received a message: {evt_or_result}")

        # It's a result
        if "return" in evt_or_result or "QMP" in evt_or_result:
            self.results.put(evt_or_result)
        # It's an event
        elif "event" in evt_or_result:
            self.pending_events.put(evt_or_result)
        else:
            raise QMPAPIError(evt_or_result)

    def wait_for_event(self, timeout: int = 10) -> dict[str, Any]:
        while self.pending_events.empty():
            self.read_pending_messages()

        return self.pending_events.get(timeout=timeout)

    def events(self, timeout: int = 10) -> Iterator[dict[str, Any]]:
        while not self.pending_events.empty():
            yield self.pending_events.get(timeout=timeout)

    def send(self, cmd: str, args: dict[str, str] = {}) -> dict[str, str]:
        self.read_pending_messages()
        assert self.results.empty(), "Results set is not empty, missed results!"
        data: dict[str, Any] = dict(execute=cmd)
        if args != {}:
            data["arguments"] = args

        logger.debug(f"Sending {data} to QMP...")
        json.dump(data, self.writer)
        self.writer.write("\n")
        self.writer.flush()
        return self._wait_for_new_result()
+3 −0
Original line number Diff line number Diff line
@@ -520,6 +520,7 @@
  ./services/hardware/hddfancontrol.nix
  ./services/hardware/illum.nix
  ./services/hardware/interception-tools.nix
  ./services/hardware/iptsd.nix
  ./services/hardware/irqbalance.nix
  ./services/hardware/joycond.nix
  ./services/hardware/kanata.nix
@@ -558,6 +559,7 @@
  ./services/home-automation/esphome.nix
  ./services/home-automation/evcc.nix
  ./services/home-automation/home-assistant.nix
  ./services/home-automation/homeassistant-satellite.nix
  ./services/home-automation/zigbee2mqtt.nix
  ./services/logging/SystemdJournal2Gelf.nix
  ./services/logging/awstats.nix
@@ -736,6 +738,7 @@
  ./services/misc/soft-serve.nix
  ./services/misc/sonarr.nix
  ./services/misc/sourcehut
  ./services/misc/spice-autorandr.nix
  ./services/misc/spice-vdagentd.nix
  ./services/misc/spice-webdavd.nix
  ./services/misc/ssm-agent.nix
Loading