feat!: declare the guest agent per guest OS, and share what provisioning needs
CI / test (3.10) (push) Successful in 39s
CI / test (3.11) (push) Successful in 36s
CI / test (3.12) (push) Successful in 39s
CI / test (3.10) (pull_request) Successful in 32s
CI / test (3.11) (pull_request) Successful in 23s
CI / test (3.12) (pull_request) Successful in 24s
CI / test (3.10) (push) Successful in 39s
CI / test (3.11) (push) Successful in 36s
CI / test (3.12) (push) Successful in 39s
CI / test (3.10) (pull_request) Successful in 32s
CI / test (3.11) (pull_request) Successful in 23s
CI / test (3.12) (pull_request) Successful in 24s
GUEST_AGENT_PACKAGES / GUEST_AGENT_RUNCMD described one agent per hypervisor. A hypervisor that provisions FreeBSD and OpenBSD guests needs a different agent per guest, and netOrk has to know which guests a driver can provision at all (NetOrk/netork#794). Both now come from one class-level mapping netOrk reads off the driver class: GUEST_AGENTS = {guest_os: (packages, runcmd)} Its keys are the guests the driver provisions, and create_vm_from_cloud_init(guest_os=...) refuses any other. The base keeps Linux with qemu-guest-agent. New module `provisioning`, generic for every hypervisor: - QEMU_GUEST_AGENTS: the QEMU agent for Linux, FreeBSD and OpenBSD, with package names and service commands verified on FreeBSD 15.1 and OpenBSD 7.9 cloud images (NetOrk/netork#793). - network_config(): cloud-init's network-config v2 with MAC matching, moved here from napalm-vmware. FreeBSD's nuageinit reads no other version. - split_compression(): how a packed image (.xz, .gz, .bz2, .zst) is unpacked. BREAKING CHANGE: GUEST_AGENT_PACKAGES and GUEST_AGENT_RUNCMD are gone; drivers declare GUEST_AGENTS instead.
This commit is contained in:
@@ -14,6 +14,7 @@ from typing import Any, Dict, List, TYPE_CHECKING
|
||||
from napalm_device_types.base import DeviceTypeDriver
|
||||
from napalm_device_types.packages import PackageManagementMixin
|
||||
from napalm_device_types.health_metrics import HealthMetricsMixin
|
||||
from napalm_device_types.provisioning import QEMU_GUEST_AGENTS, GuestAgents
|
||||
from napalm_device_types.models import (
|
||||
NICConfigDict,
|
||||
NetworkTargetDict,
|
||||
@@ -44,11 +45,11 @@ class HypervisorDriver(PackageManagementMixin, HealthMetricsMixin, DeviceTypeDri
|
||||
ROLE: str = "hypervisor"
|
||||
TYPE_LABEL: str = "Hypervisor"
|
||||
|
||||
#: What cloud-init installs and starts on a VM provisioned through this
|
||||
#: driver, so the hypervisor can read the guest's IP address back.
|
||||
GUEST_AGENT_PACKAGES: tuple[str, ...] = ("qemu-guest-agent",)
|
||||
GUEST_AGENT_RUNCMD: tuple[str, ...] = ("systemctl enable --now qemu-guest-agent",)
|
||||
|
||||
#: What cloud-init installs and runs on a VM provisioned through this
|
||||
#: driver, per guest OS, so the hypervisor can read the guest's IP
|
||||
#: address back. The guest operating systems listed here are the ones
|
||||
#: the driver can provision; netOrk reads this off the class.
|
||||
GUEST_AGENTS: GuestAgents = {"linux": QEMU_GUEST_AGENTS["linux"]}
|
||||
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
@@ -464,6 +465,7 @@ class HypervisorDriver(PackageManagementMixin, HealthMetricsMixin, DeviceTypeDri
|
||||
disk_resize_gb: int | None = None,
|
||||
storage: str | None = None,
|
||||
cpu_type: str | None = None,
|
||||
guest_os: str = "linux",
|
||||
download_timeout: int = 300,
|
||||
timeout: int = 180,
|
||||
) -> VMProvisionResultDict:
|
||||
@@ -512,6 +514,13 @@ class HypervisorDriver(PackageManagementMixin, HealthMetricsMixin, DeviceTypeDri
|
||||
value other than None with ValueError; one that has it raises
|
||||
ValueError for a name it does not list or that is not
|
||||
``available`` on this node, before creating anything.
|
||||
guest_os (string) - the operating system in the image, a key of
|
||||
``GUEST_AGENTS`` (``"linux"``, ``"freebsd"``, ``"openbsd"``). The
|
||||
driver sets up the VM's hardware for it (OS type, how the guest
|
||||
agent is attached) and, where the guest needs it, writes the
|
||||
network-config itself (``provisioning.network_config``). A
|
||||
guest_os the driver does not list raises ValueError before
|
||||
anything is created. Default ``"linux"``.
|
||||
download_timeout (int) - maximum seconds to wait for the image download
|
||||
(skipped entirely if already cached on the hypervisor). Default 300.
|
||||
timeout (int) - maximum seconds to wait for the remaining provisioning
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
"""
|
||||
What provisioning a VM from a cloud image needs to know about the guest.
|
||||
|
||||
The same for every hypervisor: which agent a guest runs so a QEMU-based
|
||||
hypervisor can read its IP, the network-config cloud-init and FreeBSD's
|
||||
nuageinit both read, and how a packed cloud image is unpacked. How a
|
||||
hypervisor attaches, boots and talks to the VM stays in its driver.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Mapping
|
||||
from typing import Any
|
||||
|
||||
#: ``{guest_os: (packages, runcmd)}``: what cloud-init installs and runs so
|
||||
#: the hypervisor can read the guest's IP address back.
|
||||
GuestAgents = Mapping[str, tuple[tuple[str, ...], tuple[str, ...]]]
|
||||
|
||||
#: The QEMU guest agent per guest OS, for hypervisors built on QEMU. Package
|
||||
#: names and service commands were verified on FreeBSD 15.1 and OpenBSD 7.9
|
||||
#: cloud images (NetOrk/netork#793).
|
||||
QEMU_GUEST_AGENTS: GuestAgents = {
|
||||
"linux": (("qemu-guest-agent",), ("systemctl enable --now qemu-guest-agent",)),
|
||||
"freebsd": (
|
||||
("qemu-guest-agent",),
|
||||
("sysrc qemu_guest_agent_enable=YES", "service qemu-guest-agent start"),
|
||||
),
|
||||
"openbsd": (("qemu-ga",), ("rcctl enable qemu_ga", "rcctl start qemu_ga")),
|
||||
}
|
||||
|
||||
#: Suffix of a packed image -> the command that writes it unpacked to stdout.
|
||||
_DECOMPRESSORS = {
|
||||
"xz": "xz -dc",
|
||||
"gz": "gzip -dc",
|
||||
"bz2": "bzip2 -dc",
|
||||
"zst": "zstd -dc",
|
||||
}
|
||||
|
||||
|
||||
def network_config(nics: list[tuple[str, bool]]) -> dict[str, Any] | None:
|
||||
"""Network-config v2: DHCP on every ``(mac, dhcp)`` NIC that asks for it.
|
||||
|
||||
Version 2 because FreeBSD's nuageinit reads no other: given the v1 that
|
||||
Proxmox generates, it fails and skips the rest of its first stage,
|
||||
runcmd included. ``None`` when no NIC wants DHCP, so the guest is left
|
||||
alone rather than told to configure nothing.
|
||||
"""
|
||||
ethernets = {
|
||||
f"nic{i}": {"match": {"macaddress": mac.lower()}, "dhcp4": True}
|
||||
for i, (mac, dhcp) in enumerate(nics)
|
||||
if dhcp
|
||||
}
|
||||
return {"version": 2, "ethernets": ethernets} if ethernets else None
|
||||
|
||||
|
||||
def split_compression(filename: str) -> tuple[str, str | None]:
|
||||
"""``(unpacked filename, command that unpacks to stdout)`` for *filename*.
|
||||
|
||||
The command is None for an image that is not packed, which is then
|
||||
imported as it is.
|
||||
"""
|
||||
stem, dot, suffix = filename.rpartition(".")
|
||||
command = _DECOMPRESSORS.get(suffix.lower()) if dot and stem else None
|
||||
return (stem, command) if command else (filename, None)
|
||||
Reference in New Issue
Block a user