feat!: a VM's vmid is a string, and its config can describe its hardware

VMDict.vmid and VMConfigDict.vmid were int. Proxmox numbers its guests,
but VMware identifies a VM by UUID, which an int cannot hold. The
provisioning dicts already carried vmid as a string; the read side now
matches. Proxmox reports "100".

VMConfigDict gains optional hardware details -- os_name, cpu_type,
sockets, cores_per_socket, firmware, machine and passthrough (PCI/USB,
as VMPassthroughDict) -- so netOrk's VM hardware view can be filled by
any hypervisor instead of reading Proxmox's raw config through the
driver's private API.

Also fixes the README's hypervisor example, which still named the
pre-contract snapshot_create.

BREAKING CHANGE: VMDict.vmid and VMConfigDict.vmid are str.
This commit is contained in:
2026-09-24 09:06:36 +02:00
parent 70841feaa1
commit 1ce0a0b6f2
5 changed files with 99 additions and 18 deletions
+1 -1
View File
@@ -238,7 +238,7 @@ class ProxmoxDriver(HypervisorDriver):
# return List[VMDict]
...
def snapshot_create(self, name, snapshot, description="", include_memory=False):
def create_vm_snapshot(self, name, snapshot, description="", include_memory=False):
...
```
+26 -14
View File
@@ -59,7 +59,8 @@ class HypervisorDriver(PackageManagementMixin, HealthMetricsMixin, DeviceTypeDri
Each entry contains:
* name (string) - VM display name
* vmid (int) - hypervisor-internal numeric ID
* vmid (string) - hypervisor-internal ID (Proxmox: ``"100"``,
VMware: the instance UUID)
* status (string) - ``"running"``, ``"stopped"``, ``"paused"``, ``"suspended"``
* vcpus (int) - number of virtual CPUs assigned
* memory (int) - configured RAM in megabytes
@@ -73,7 +74,7 @@ class HypervisorDriver(PackageManagementMixin, HealthMetricsMixin, DeviceTypeDri
[
{
"name": "web01",
"vmid": 100,
"vmid": "100",
"status": "running",
"vcpus": 4,
"memory": 8192,
@@ -84,7 +85,7 @@ class HypervisorDriver(PackageManagementMixin, HealthMetricsMixin, DeviceTypeDri
},
{
"name": "db-backup",
"vmid": 101,
"vmid": "101",
"status": "stopped",
"vcpus": 2,
"memory": 4096,
@@ -101,13 +102,14 @@ class HypervisorDriver(PackageManagementMixin, HealthMetricsMixin, DeviceTypeDri
"""
Returns the full hardware configuration of a virtual machine.
:param name: VM name or numeric VMID as a string.
:param name: VM name or its ``vmid``.
:raises ValueError: If no VM with the given name/ID exists.
The returned dictionary contains:
* name (string) - VM display name
* vmid (int) - hypervisor-internal numeric ID
* vmid (string) - hypervisor-internal ID (Proxmox: ``"100"``,
VMware: the instance UUID)
* vcpus (int) - number of virtual CPUs
* memory (int) - RAM in megabytes
* os_type (string) - guest OS type hint (e.g. ``"l26"``, ``"win11"``, ``"other"``)
@@ -131,11 +133,21 @@ class HypervisorDriver(PackageManagementMixin, HealthMetricsMixin, DeviceTypeDri
* description (string) - free-text notes / description
* tags (list of strings) - organisational tags
Optional, present only where the hypervisor exposes them:
* os_name (string) - human-readable guest OS
* cpu_type (string) - emulated CPU model
* sockets (int), cores_per_socket (int) - vCPU topology
* firmware (string) - ``"bios"`` or ``"efi"``
* machine (string) - machine type / virtual hardware version
* passthrough (list) - host devices handed to the VM, each with
``slot``, ``kind`` (``"pci"``/``"usb"``) and ``config``
Example::
{
"name": "web01",
"vmid": 100,
"vmid": "100",
"vcpus": 4,
"memory": 8192,
"os_type": "l26",
@@ -174,7 +186,7 @@ class HypervisorDriver(PackageManagementMixin, HealthMetricsMixin, DeviceTypeDri
The method blocks until the hypervisor reports the VM as running.
:param name: VM name or numeric VMID as a string.
:param name: VM name or its ``vmid``.
:raises ValueError: If no VM with the given name/ID exists.
:raises RuntimeError: If the VM cannot be started (e.g. resource limit).
@@ -192,7 +204,7 @@ class HypervisorDriver(PackageManagementMixin, HealthMetricsMixin, DeviceTypeDri
and the method blocks until the VM is stopped. With ``force=True``
the VM is immediately powered off (equivalent to pulling the plug).
:param name: VM name or numeric VMID as a string.
:param name: VM name or its ``vmid``.
:param force: ``True`` for immediate power-off, ``False`` for graceful shutdown.
:raises ValueError: If no VM with the given name/ID exists.
:raises RuntimeError: If the VM is already stopped.
@@ -211,7 +223,7 @@ class HypervisorDriver(PackageManagementMixin, HealthMetricsMixin, DeviceTypeDri
With ``force=False`` (default) a graceful ACPI reboot is requested.
With ``force=True`` the VM is reset immediately without OS shutdown.
:param name: VM name or numeric VMID as a string.
:param name: VM name or its ``vmid``.
:param force: ``True`` for an immediate reset, ``False`` for graceful reboot.
:raises ValueError: If no VM with the given name/ID exists.
:raises RuntimeError: If the VM is not currently running.
@@ -228,7 +240,7 @@ class HypervisorDriver(PackageManagementMixin, HealthMetricsMixin, DeviceTypeDri
Suspends (pauses) a running virtual machine, preserving its in-memory
state. The VM can be resumed with :meth:`start_vm`.
:param name: VM name or numeric VMID as a string.
:param name: VM name or its ``vmid``.
:raises ValueError: If no VM with the given name/ID exists.
:raises RuntimeError: If the VM is not currently running.
@@ -246,7 +258,7 @@ class HypervisorDriver(PackageManagementMixin, HealthMetricsMixin, DeviceTypeDri
"""
Returns all snapshots of a virtual machine.
:param name: VM name or numeric VMID as a string.
:param name: VM name or its ``vmid``.
:raises ValueError: If no VM with the given name/ID exists.
Each entry contains:
@@ -286,7 +298,7 @@ class HypervisorDriver(PackageManagementMixin, HealthMetricsMixin, DeviceTypeDri
"""
Creates a snapshot of a virtual machine.
:param name: VM name or numeric VMID as a string.
:param name: VM name or its ``vmid``.
:param snapshot: Name for the new snapshot.
:param description: Optional human-readable description.
:param include_memory: Whether to include the current RAM state
@@ -306,7 +318,7 @@ class HypervisorDriver(PackageManagementMixin, HealthMetricsMixin, DeviceTypeDri
"""
Deletes a snapshot of a virtual machine.
:param name: VM name or numeric VMID as a string.
:param name: VM name or its ``vmid``.
:param snapshot: Name of the snapshot to delete.
:raises ValueError: If the VM or snapshot does not exist.
:raises RuntimeError: If other snapshots depend on this one (must delete children first).
@@ -324,7 +336,7 @@ class HypervisorDriver(PackageManagementMixin, HealthMetricsMixin, DeviceTypeDri
The VM is stopped (if running), reverted, and then left in the state
the snapshot recorded (running or stopped depending on ``has_memory``).
:param name: VM name or numeric VMID as a string.
:param name: VM name or its ``vmid``.
:param snapshot: Name of the snapshot to roll back to.
:raises ValueError: If the VM or snapshot does not exist.
:raises RuntimeError: If the rollback fails.
+18 -2
View File
@@ -512,7 +512,7 @@ class VMNICDict(TypedDict):
class VMDict(TypedDict):
name: str
vmid: int
vmid: str
status: str
vcpus: int
memory: int
@@ -522,9 +522,17 @@ class VMDict(TypedDict):
node: str
class VMPassthroughDict(TypedDict):
"""A host device handed through to a VM (PCI, USB)."""
slot: str # hypervisor's device key, e.g. "hostpci0"
kind: str # "pci" or "usb"
config: str # hypervisor's own description of the device
class VMConfigDict(TypedDict):
name: str
vmid: int
vmid: str
vcpus: int
memory: int
os_type: str
@@ -533,6 +541,14 @@ class VMConfigDict(TypedDict):
nics: List[VMNICDict]
description: str
tags: List[str]
# Hardware details not every hypervisor exposes; absent when unknown.
os_name: NotRequired[str] # human-readable guest OS, e.g. "Ubuntu Linux (64-bit)"
cpu_type: NotRequired[str] # e.g. "host", "kvm64"
sockets: NotRequired[int]
cores_per_socket: NotRequired[int]
firmware: NotRequired[str] # "bios" or "efi"
machine: NotRequired[str] # machine type / virtual hardware version
passthrough: NotRequired[List[VMPassthroughDict]]
class StorageVolumeDict(TypedDict):
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "napalm-device-types"
version = "1.0.0"
version = "2.0.0"
description = "Abstract device-type base classes for NAPALM drivers"
readme = "README.md"
requires-python = ">=3.10"
+53
View File
@@ -0,0 +1,53 @@
"""A VM's ``vmid`` is a string on every hypervisor.
Proxmox numbers its guests, but VMware identifies them by UUID or MoRef
(``"vm-42"``). An ``int`` in the contract forced netOrk to call ``int()`` on
whatever came back, which cannot represent the second kind at all. The
provisioning dicts already carried ``vmid`` as a string; these pin the
read-side dicts to the same type.
"""
from __future__ import annotations
from typing import get_type_hints
import pytest
from napalm_device_types.models import (
VMConfigDict,
VMDict,
VMProvisionResultDict,
)
@pytest.mark.parametrize("model", [VMDict, VMConfigDict, VMProvisionResultDict])
def test_vmid_is_a_string(model):
assert get_type_hints(model)["vmid"] is str
class TestVMConfigCarriesWhatAHardwareViewShows:
"""netOrk's VM hardware view used to read Proxmox's raw config through the
driver's private API. The contract has to carry those details so a second
hypervisor can fill the same view -- optionally, since not every platform
has every one of them."""
OPTIONAL = {
"os_name",
"cpu_type",
"sockets",
"cores_per_socket",
"firmware",
"machine",
"passthrough",
}
def test_hardware_details_are_optional_fields(self):
assert self.OPTIONAL <= VMConfigDict.__optional_keys__
def test_contract_core_stays_required(self):
assert "vmid" in VMConfigDict.__required_keys__
assert "disks" in VMConfigDict.__required_keys__
def test_passthrough_entry_shape(self):
from napalm_device_types.models import VMPassthroughDict
assert get_type_hints(VMPassthroughDict) == {"slot": str, "kind": str, "config": str}