Author SHA1 Message Date
christianmanivong 5059df6b25 feat(hypervisor): create_vm_from_cloud_init downloads a cloud image directly
Replaces template-clone semantics (template: str, existing Proxmox template
VMID) with image_url: str — the driver now downloads the cloud image itself
and imports it as the VM's root disk, rather than requiring an admin to have
pre-built a template. Adds image_checksum for optional verification and a
separate download_timeout since image downloads can take much longer than
the rest of provisioning.
2026-07-07 10:25:45 +02:00
christianmanivong a37dc8d632 Merge feature/network-target-vlan-tag: expose fixed VLAN tag for SDN vnets 2026-07-07 10:20:42 +02:00
christianmanivong cb274156a4 feat(models): add fixed_vlan_tag to NetworkTargetDict for SDN vnets 2026-07-07 10:20:36 +02:00
christianmanivong a16ca77156 Merge feature/network-targets: add get_network_targets() interface 2026-07-07 09:09:57 +02:00
christianmanivong fcf72b6dad feat(hypervisor): add get_network_targets() interface for VM NIC provisioning
Returns selectable bridge/vnet targets for a new VM's NIC, distinguishing
real bridges (Linux, OVS) from SDN vnets, and exposing whether a NIC on that
target may additionally carry a vlan_tag (Linux bridge vlan_aware flag, OVS
always, SDN vnet never — the VLAN is already fixed by the vnet's zone/tag).
2026-07-07 09:03:44 +02:00
christianmanivong 2cc93885b4 Reapply "Merge feature/generic-vm-provisioning: generalize create_vm_from_cloud_init interface"
This reverts commit 97cab9754b.
2026-07-07 08:20:55 +02:00
christianmanivong 97cab9754b Revert "Merge feature/generic-vm-provisioning: generalize create_vm_from_cloud_init interface"
This reverts commit 7d18c12579, reversing
changes made to 7f0dd789b0.
2026-07-07 00:53:06 +02:00
christianmanivong 7d18c12579 Merge feature/generic-vm-provisioning: generalize create_vm_from_cloud_init interface 2026-07-07 00:46:30 +02:00
christianmanivong d1f40c5337 feat(hypervisor): generalize create_vm_from_cloud_init interface to support arbitrary NIC configs
- Replace fixed mgmt/capture dual-NIC parameters with generic NICConfigDict list
- Add optional disk_resize_gb parameter for post-clone disk expansion
- Update docstrings to reflect generic NIC approach (primary NIC concept)
- Add NICConfigDict TypedDict supporting access VLAN, trunk VLAN, and DHCP flags
2026-07-06 23:27:40 +02:00
christianmanivongandClaude Haiku 4.5 7f0dd789b0 feat(hypervisor): add VM provisioning interface stubs
Add three new methods to HypervisorDriver:
- create_vm_from_cloud_init(): provision VM from template with dual-NIC config
- destroy_vm(): stop and remove VM with optional disk cleanup
- get_vm_status(): poll runtime status, optionally wait for IP via guest-agent

New TypedDicts VMProvisionResultDict and VMStatusDict in models.py
document the provisioning API contract.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-07-06 21:45:52 +02:00
2 changed files with 198 additions and 0 deletions
+153
View File
@@ -15,11 +15,15 @@ from napalm_device_types.base import DeviceTypeDriver
from napalm_device_types._ucd_metrics import IF_SKIP_DEFAULT, collect_ucd_metrics
from napalm_device_types.models import (
HealthMetricsDict,
NICConfigDict,
NetworkTargetDict,
PackageDict,
SnapshotDict,
StorageVolumeDict,
VMConfigDict,
VMDict,
VMProvisionResultDict,
VMStatusDict,
VirtualNetworkDict,
)
@@ -535,3 +539,152 @@ class HypervisorDriver(DeviceTypeDriver):
)
"""
raise NotImplementedError
# ------------------------------------------------------------------
# Virtual machine provisioning
# ------------------------------------------------------------------
def create_vm_from_cloud_init(
self,
name: str,
*,
image_url: str,
cpu: int,
memory: int,
nics: List[NICConfigDict],
cloud_init_config: Dict[str, Any],
image_checksum: str | None = None,
ssh_public_keys: List[str] | None = None,
disk_resize_gb: int | None = None,
download_timeout: int = 300,
timeout: int = 180,
) -> VMProvisionResultDict:
"""
Create a new virtual machine from a cloud image via Cloud-Init.
Downloads the cloud image (qcow2/raw) directly on the hypervisor if not
already cached there, creates a new VM shell, imports the image as its
root disk, configures virtual network interfaces, and injects Cloud-Init
configuration via a storage snippet or similar mechanism. The resulting
VM is left in a running state.
Implementations should cache downloaded images by URL/filename on the
hypervisor so repeated provisioning from the same image does not
re-download it every time.
Args:
name (string) - new VM display name
image_url (string) - URL of the cloud image to download and use as
the VM's root disk (e.g. an official Debian/Ubuntu cloud image).
cpu (int) - number of virtual CPUs to assign
memory (int) - RAM to assign in megabytes
nics (list[NICConfigDict]) - list of network interface configurations.
First NIC is primary (DHCP by default); subsequent NICs are optional.
Each entry specifies bridge, optional vlan_tag (access) or trunk_vlan_tags,
and dhcp flag.
cloud_init_config (dict) - user-data dict (will be rendered to YAML).
Should include hostname, bootstrap_token, runcmd, and any custom config.
image_checksum (string | None) - expected checksum of the downloaded
image (e.g. "sha256:<hex>"). If given, verified after download;
mismatch raises RuntimeError. If None, no verification is performed.
ssh_public_keys (list[str] | None) - SSH public keys to inject into guest.
If None or empty, no SSH key injection is performed.
disk_resize_gb (int | None) - resize root disk to this size in GB.
If None, disk remains the downloaded image's native size. Default None.
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
steps (VM creation, disk import, config, start). Default 180.
Returns:
VMProvisionResultDict - ``{"vmid": str, "name": str, "node": str}``
vmid is the hypervisor-internal VM ID as a string (numeric for Proxmox).
Raises:
RuntimeError - if provisioning fails (download failure, checksum
mismatch, storage unavailable, invalid config, timeout, etc.)
"""
raise NotImplementedError
def destroy_vm(
self,
vmid: str,
*,
remove_disk: bool = True,
timeout: int = 60,
) -> None:
"""
Destroy a virtual machine and optionally remove its storage.
Stops the VM (if running) and purges it from the hypervisor. Optionally
also removes associated disks and ephemeral storage (e.g. Cloud-Init snippets).
Args:
vmid (string) - hypervisor-internal VM ID (as returned from ``get_vms()``
or ``create_vm_from_cloud_init()``).
remove_disk (bool) - if True (default), delete all disks and snapshot
data associated with the VM. If False, only the VM configuration
is removed; disks are left behind (rare use case).
timeout (int) - maximum seconds to wait for the destroy operation
(stop + delete). Default 60.
Raises:
RuntimeError - if the VM does not exist or destroy fails.
"""
raise NotImplementedError
def get_vm_status(
self,
vmid: str,
*,
wait_for_ip: bool = False,
timeout: int = 300,
poll_interval: int = 5,
) -> VMStatusDict:
"""
Get the current runtime status of a virtual machine.
Optionally waits for the VM to acquire an IP address on its management
NIC (net0), useful when provisioning a VM and waiting for it to boot.
Args:
vmid (string) - hypervisor-internal VM ID.
wait_for_ip (bool) - if True, poll until the VM's guest-agent reports
an IP address on the first NIC (net0, the management NIC). Useful
for ``create_vm_from_cloud_init()`` follow-up. If False, return
immediate status without waiting (may have no IP).
timeout (int) - maximum seconds to wait for IP acquisition
(if wait_for_ip=True). Raises RuntimeError if timeout is exceeded.
Default 300.
poll_interval (int) - seconds between status polls (if wait_for_ip=True).
Default 5.
Returns:
VMStatusDict - ``{"status": str, "ip_address": str, "hostname": str,
"mac_address": str}``. ip_address, hostname, and mac_address refer to
the primary NIC (first interface) and are only present if the VM is
running and has network info available.
Raises:
RuntimeError - if the VM does not exist or if wait_for_ip=True and
timeout is exceeded.
"""
raise NotImplementedError
def get_network_targets(self) -> List[NetworkTargetDict]:
"""
List the network targets a new VM's NIC may attach to.
Returns only targets that are actually valid ``NICConfigDict.bridge``
values — real bridges (Linux or OVS) and SDN network segments (vnets).
Physical NICs, bonds, and other non-bridge interface types are excluded,
since VMs cannot attach directly to them on any hypervisor this interface
supports.
Returns:
List[NetworkTargetDict] - each entry's ``vlan_aware`` flag tells the
caller whether a ``NICConfigDict.vlan_tag`` may additionally be set
for a NIC using that target (see ``NetworkTargetDict`` for the
per-kind rules).
"""
raise NotImplementedError
+45
View File
@@ -704,3 +704,48 @@ class HealthMetricsDict(TypedDict):
load_5: NotRequired[float]
load_15: NotRequired[float]
interfaces: NotRequired[Dict[str, HealthMetricsIfaceDict]]
class NICConfigDict(TypedDict):
"""Network interface configuration for VM provisioning."""
bridge: str # Bridge or network name
vlan_tag: NotRequired[int | None] # Access VLAN (None = untagged)
trunk_vlan_tags: NotRequired[list[int]] # Trunk VLAN list (alternative to vlan_tag)
dhcp: NotRequired[bool] # Enable DHCP (default True for first NIC, False for others)
class VMProvisionResultDict(TypedDict):
"""Return value of ``create_vm_from_cloud_init()`` — provisioned VM identifier."""
vmid: str # Hypervisor-internal VM ID (string for portability across hypervisors)
name: str # VM display name
node: str # Cluster node name (empty string for standalone hypervisors)
class VMStatusDict(TypedDict):
"""Return value of ``get_vm_status()`` — runtime information of a VM."""
status: str # "running", "stopped", "paused", "suspended", etc.
ip_address: NotRequired[str] # Management NIC IP (absent if VM has no IP or is stopped)
hostname: NotRequired[str] # Hostname resolved from IP (if available)
mac_address: NotRequired[str] # MAC of management NIC
class NetworkTargetDict(TypedDict):
"""A selectable network target for a new VM's NIC (``NICConfigDict.bridge``).
Distinguishes real bridges (Linux or OVS) from SDN network segments (vnets),
and tells the caller whether a separate ``vlan_tag`` may be applied on top:
- Linux bridge: vlan_aware reflects the bridge's own ``bridge_vlan_aware`` flag.
- OVS bridge: always vlan_aware (OVS bridges tag per-port regardless of a
dedicated "VLAN aware" setting).
- SDN vnet: never vlan_aware — the VLAN is already fixed by the vnet's zone/tag,
so a NIC attached to it must not also carry a ``vlan_tag``. That fixed VLAN
is surfaced via ``fixed_vlan_tag`` instead, for display purposes.
"""
name: str # Bridge or vnet name, usable directly as NICConfigDict.bridge
kind: str # "bridge" or "vnet"
vlan_aware: bool # True if a NICConfigDict.vlan_tag may be set on top of this target
fixed_vlan_tag: NotRequired[int | None] # vnet only: the VLAN ID already baked into it