Files
napalm-vmware/napalm_vmware/parse/vm_config.py
Christian Manivong c8e472b4c6 feat: NAPALM drivers for VMware ESXi and vCenter
Two drivers from one package, both in the hypervisor role:

- vmware_esxi talks to one host directly: facts, vmnics and vmkernel
  NICs, CDP/LLDP neighbours, sensors, VMs, datastores and port groups.
- vmware_vcenter talks to a vCenter: every VM of every host it manages,
  with the host as the VM's node, plus distributed port groups. It
  reports no interfaces of its own; the hosts' NICs belong to the hosts.

Both implement the HypervisorDriver VM contract: get_vms, get_vm_config,
start/stop/reboot/suspend_vm and the four snapshot methods, and emit raw
device warnings (maintenance mode, disconnected host, config issues,
host managed by a vCenter, free license making the API read-only).

Every read is a PropertyCollector query for the explicit paths in
paths.py, converted by to_plain() into dicts and lists; the parsers
only ever see that. tools/harvest.py dumps exactly those paths to JSON
and tools/sanitize.py scrubs the dump, so a real host can become a test
fixture without code changes. A VM's vmid is its instance UUID, which
survives vMotion and re-registration; a MoRef does not.

Tested against govmomi's vcsim in ESXi and vCenter mode, including real
power and snapshot tasks. Not yet tested against real hardware.
2026-09-24 09:07:30 +02:00

97 lines
3.4 KiB
Python

"""``get_vm_config``: a VM's virtual hardware as a VMConfigDict."""
from __future__ import annotations
from typing import Any
from napalm_device_types.models import VMConfigDict, VMDiskDict, VMNICDict
from napalm_vmware.parse import vm_devices as dev
from napalm_vmware.parse.networks import NetworkIndex, nic_network
_GB = 1024**3
_BOOT_KIND = {
"VirtualMachineBootOptionsBootableCdromDevice": "cdrom",
"VirtualMachineBootOptionsBootableFloppyDevice": "floppy",
}
def _boot_order(vm: dict[str, Any], labels: dict[int, str]) -> list[str]:
order: list[str] = []
for entry in vm.get("config.bootOptions.bootOrder") or []:
kind = entry.get("_type", "")
if kind in _BOOT_KIND:
order.append(_BOOT_KIND[kind])
elif "deviceKey" in entry:
order.append(labels.get(entry["deviceKey"], str(entry["deviceKey"])))
return order
def _disks(vm: dict[str, Any], boot_order: list[str]) -> list[VMDiskDict]:
disks = [d for d in dev.devices(vm) if dev.is_disk(d)]
result: list[VMDiskDict] = []
for position, disk in enumerate(disks):
name = dev.label(disk)
# Without an explicit boot order the firmware boots the first disk.
bootable = name in boot_order if boot_order else position == 0
result.append(
{
"device": name,
"storage": dev.disk_datastore(disk),
"size": dev.disk_bytes(disk) // _GB,
"format": "vmdk",
"bootable": bootable,
}
)
return result
def _nics(vm: dict[str, Any], index: NetworkIndex) -> list[VMNICDict]:
result: list[VMNICDict] = []
for nic in filter(dev.is_nic, dev.devices(vm)):
network, vlan = nic_network(nic, index)
result.append(
{
"device": dev.label(nic),
"mac": nic.get("macAddress", "").upper(),
"model": dev.nic_model(nic),
"bridge": network,
"vlan_id": vlan,
}
)
return result
def vm_config(vm: dict[str, Any], index: NetworkIndex) -> VMConfigDict:
"""The VM's hardware. Raises ``ValueError`` for a VM without a config."""
if not vm.get("config.instanceUuid"):
raise ValueError(f"VM {vm.get('name', vm.get('_moref'))!r} has no readable configuration")
labels = {d["key"]: dev.label(d) for d in dev.devices(vm) if "key" in d}
boot_order = _boot_order(vm, labels)
vcpus = int(vm.get("config.hardware.numCPU") or 0)
cores = int(vm.get("config.hardware.numCoresPerSocket") or 1)
result: VMConfigDict = {
"name": vm.get("name", ""),
"vmid": vm["config.instanceUuid"],
"vcpus": vcpus,
"memory": int(vm.get("config.hardware.memoryMB") or 0),
"os_type": vm.get("config.guestId", ""),
"boot_order": boot_order,
"disks": _disks(vm, boot_order),
"nics": _nics(vm, index),
"description": vm.get("config.annotation", ""),
"tags": [],
"os_name": vm.get("config.guestFullName", ""),
"sockets": max(vcpus // cores, 1),
"cores_per_socket": cores,
"firmware": vm.get("config.firmware", "bios"),
"passthrough": [
{"slot": dev.label(d), "kind": kind, "config": dev.summary(d)}
for d in dev.devices(vm)
if (kind := dev.passthrough_kind(d))
],
}
if vm.get("config.version"):
result["machine"] = vm["config.version"]
return result