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.
122 lines
4.6 KiB
Python
122 lines
4.6 KiB
Python
"""``get_vms``: one VMDict per virtual machine, plus the keys netOrk reads.
|
|
|
|
Beyond the ``VMDict`` contract, netOrk's VM sync expects ``type``,
|
|
``interfaces`` (NAPALM shape plus ``ipv4``/``bridge``/``tag``), ``ipv4``,
|
|
``agent_enabled``/``agent_running`` (here: VMware Tools), ``disks`` and
|
|
``onboot``. ``moref`` is added so an operator can find the VM in vSphere.
|
|
|
|
``vmid`` is the VM's *instance* UUID: stable across vMotion and
|
|
re-registration, unique within a vCenter, and the key ``SearchIndex.FindByUuid``
|
|
looks VMs up by. The MoRef is none of those things.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import ipaddress
|
|
from typing import Any
|
|
|
|
from napalm_vmware.parse import vm_devices as dev
|
|
from napalm_vmware.parse.networks import NetworkIndex, nic_network
|
|
|
|
_STATUS = {"poweredOn": "running", "poweredOff": "stopped", "suspended": "suspended"}
|
|
|
|
|
|
def _is_ipv4(address: str) -> bool:
|
|
try:
|
|
return isinstance(ipaddress.ip_address(address), ipaddress.IPv4Address)
|
|
except ValueError:
|
|
return False
|
|
|
|
|
|
def _cpu_usage(vm: dict[str, Any], host_mhz: int) -> float:
|
|
used = (vm.get("summary.quickStats") or {}).get("overallCpuUsage") or 0
|
|
capacity = int(vm.get("config.hardware.numCPU") or 0) * host_mhz
|
|
if not capacity:
|
|
return 0.0
|
|
return round(min(used / capacity, 1.0), 4)
|
|
|
|
|
|
def _interfaces(vm: dict[str, Any], index: NetworkIndex) -> dict[str, dict[str, Any]]:
|
|
guest_nics = {n.get("deviceConfigId"): n for n in vm.get("guest.net") or []}
|
|
result: dict[str, dict[str, Any]] = {}
|
|
for nic in filter(dev.is_nic, dev.devices(vm)):
|
|
network, vlan = nic_network(nic, index)
|
|
guest = guest_nics.get(nic.get("key"), {})
|
|
connected = guest.get("connected", (nic.get("connectable") or {}).get("connected", False))
|
|
ipv4 = next((a for a in guest.get("ipAddress", []) if _is_ipv4(a)), "")
|
|
result[dev.label(nic)] = {
|
|
"is_up": bool(connected),
|
|
"is_enabled": True,
|
|
"description": network,
|
|
"mac_address": nic.get("macAddress", "").upper(),
|
|
"speed": -1.0,
|
|
"mtu": 1500,
|
|
"last_flapped": -1.0,
|
|
"ipv4": ipv4,
|
|
"bridge": network,
|
|
"tag": vlan or None,
|
|
}
|
|
return result
|
|
|
|
|
|
def _autostart_vms(hosts: list[dict[str, Any]]) -> set[str]:
|
|
"""MoRefs of VMs the host's autostart manager will power on at boot."""
|
|
morefs: set[str] = set()
|
|
for host in hosts:
|
|
config = host.get("config.autoStart") or {}
|
|
if not (config.get("defaults") or {}).get("enabled"):
|
|
continue
|
|
for entry in config.get("powerInfo", []):
|
|
if str(entry.get("startAction", "")).lower() == "poweron":
|
|
morefs.add(entry.get("key", ""))
|
|
return morefs
|
|
|
|
|
|
def _vm(
|
|
vm: dict[str, Any], hosts: dict[str, dict[str, Any]], index: NetworkIndex, onboot: bool
|
|
) -> dict[str, Any]:
|
|
host = hosts.get(vm.get("runtime.host", ""), {})
|
|
stats = vm.get("summary.quickStats") or {}
|
|
interfaces = _interfaces(vm, index)
|
|
primary = vm.get("guest.ipAddress") or ""
|
|
ipv4 = (
|
|
primary
|
|
if _is_ipv4(primary)
|
|
else next((i["ipv4"] for i in interfaces.values() if i["ipv4"]), "")
|
|
)
|
|
return {
|
|
"name": vm.get("name", ""),
|
|
"vmid": vm["config.instanceUuid"],
|
|
"moref": vm["_moref"],
|
|
"type": "vm",
|
|
"status": _STATUS.get(vm.get("runtime.powerState", ""), "unknown"),
|
|
"vcpus": int(vm.get("config.hardware.numCPU") or 0),
|
|
"memory": int(vm.get("config.hardware.memoryMB") or 0),
|
|
"cpu_usage": _cpu_usage(vm, int((host.get("summary.hardware") or {}).get("cpuMhz") or 0)),
|
|
"memory_usage": int(stats.get("guestMemoryUsage") or 0),
|
|
"uptime": int(stats.get("uptimeSeconds") or 0),
|
|
"node": host.get("name", ""),
|
|
"interfaces": interfaces,
|
|
"ipv4": ipv4,
|
|
"agent_enabled": vm.get("guest.toolsStatus", "toolsNotInstalled") != "toolsNotInstalled",
|
|
"agent_running": vm.get("guest.toolsRunningStatus") == "guestToolsRunning",
|
|
"disks": [
|
|
{"name": dev.label(d), "size_mb": dev.disk_bytes(d) // (1024 * 1024)}
|
|
for d in filter(dev.is_disk, dev.devices(vm))
|
|
],
|
|
"onboot": onboot,
|
|
}
|
|
|
|
|
|
def vm_list(
|
|
vms: list[dict[str, Any]], hosts: list[dict[str, Any]], index: NetworkIndex
|
|
) -> list[dict[str, Any]]:
|
|
"""All VMs, templates and unidentifiable (config-less) VMs excluded."""
|
|
by_moref = {h["_moref"]: h for h in hosts}
|
|
autostart = _autostart_vms(hosts)
|
|
return [
|
|
_vm(vm, by_moref, index, vm["_moref"] in autostart)
|
|
for vm in vms
|
|
if vm.get("config.instanceUuid") and not vm.get("config.template")
|
|
]
|