Files
napalm-vmware/napalm_vmware/parse/environment.py
T
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

66 lines
2.4 KiB
Python

"""NAPALM ``get_environment`` from host quick stats and hardware sensors."""
from __future__ import annotations
from typing import Any
_SENSORS = "runtime.healthSystemRuntime.systemHealthInfo.numericSensorInfo"
_MB = 1024 * 1024
def _reading(sensor: dict[str, Any]) -> float:
return float(sensor.get("currentReading", 0)) * 10 ** int(sensor.get("unitModifier", 0))
def _health(sensor: dict[str, Any]) -> str:
return (sensor.get("healthState") or {}).get("key", "unknown")
def _cpu_percent(host: dict[str, Any]) -> float:
hardware = host.get("summary.hardware") or {}
capacity = int(hardware.get("cpuMhz") or 0) * int(hardware.get("numCpuCores") or 0)
used = (host.get("summary.quickStats") or {}).get("overallCpuUsage") or 0
return round(used / capacity * 100, 2) if capacity else 0.0
def _add_sensors(env: dict[str, Any], host: dict[str, Any], prefix: str) -> None:
for sensor in host.get(_SENSORS) or []:
name = f"{prefix}{sensor.get('name', '')}"
kind = sensor.get("sensorType")
health = _health(sensor)
if kind == "temperature":
env["temperature"][name] = {
"temperature": _reading(sensor),
"is_alert": health in ("yellow", "red"),
"is_critical": health == "red",
}
elif kind == "fan":
env["fans"][name] = {"status": health == "green"}
elif kind == "power":
env["power"][name] = {
"status": health == "green",
"capacity": -1.0,
"output": _reading(sensor),
}
def environment(hosts: list[dict[str, Any]]) -> dict[str, Any]:
"""One host: keyed like a single device. Several (vCenter): keyed by host."""
env: dict[str, Any] = {
"fans": {},
"temperature": {},
"power": {},
"cpu": {},
"memory": {"available_ram": 0, "used_ram": 0},
}
single = len(hosts) == 1
for host in hosts:
key = "0" if single else host.get("name", host["_moref"])
env["cpu"][key] = {"%usage": _cpu_percent(host)}
hardware = host.get("summary.hardware") or {}
stats = host.get("summary.quickStats") or {}
env["memory"]["available_ram"] += int(hardware.get("memorySize") or 0)
env["memory"]["used_ram"] += int(stats.get("overallMemoryUsage") or 0) * _MB
_add_sensors(env, host, "" if single else f"{key}/")
return env