Files
napalm-vmware/napalm_vmware/base.py
T
Christian Manivong 990dfe2f3c
CI / test (3.10) (push) Successful in 27s
CI / test (3.11) (push) Successful in 25s
CI / test (3.12) (push) Successful in 27s
CI / test (3.10) (pull_request) Successful in 27s
CI / test (3.11) (pull_request) Successful in 24s
CI / test (3.12) (pull_request) Successful in 26s
feat!: report hosts and licenses as facts, not warnings (0.2.0)
get_device_warnings is gone; netOrk names the warnings from facts
(netOrk #1111). get_hypervisor_hosts() reports each host's connection
state, maintenance mode, configuration issues and managing vCenter;
get_licenses() the assigned licenses with the edition key as edition,
never the key itself.

The reach stays: the ESXi driver reports connected as None (a
disconnected host could not have answered), and managed_by only for a
host it reached directly. parse/warnings.py is replaced by
parse/hosts.py and parse/licenses.py.
2026-10-17 09:41:47 +02:00

165 lines
6.3 KiB
Python

"""What the ESXi and vCenter drivers share: session, VM lookup, common getters."""
from __future__ import annotations
from pathlib import Path
from typing import Any, ClassVar
from napalm.base.exceptions import ConnectionException
from napalm_device_types import HypervisorDriver, LicenseMixin
from napalm_device_types.models import (
HypervisorHostDict,
LicenseDict,
SnapshotDict,
StorageVolumeDict,
VirtualNetworkDict,
VMConfigDict,
)
from napalm_vmware import _session, paths
from napalm_vmware._inventory import Inventory
from napalm_vmware.parse.environment import environment
from napalm_vmware.parse.hosts import hypervisor_hosts
from napalm_vmware.parse.licenses import license_list
from napalm_vmware.parse.networks import NetworkIndex, network_index, virtual_networks
from napalm_vmware.parse.snapshots import snapshot_list
from napalm_vmware.parse.storage import storage_pools
from napalm_vmware.parse.vm_config import vm_config
from napalm_vmware.parse.vms import vm_list
from napalm_vmware.provision.image import DEFAULT_CACHE_DIR
_DEFAULT_PORT = 443
#: ``about.apiType`` -> the driver that handles it, for a helpful refusal.
_DRIVER_FOR_API = {"HostAgent": "vmware_esxi", "VirtualCenter": "vmware_vcenter"}
class VmwareBaseDriver(LicenseMixin, HypervisorDriver):
"""Shared implementation. Concrete drivers set ``API_TYPE`` and ``STANDALONE``."""
VENDOR = "VMware"
USES_SSH = False
platform = "vmware"
#: ``about.apiType`` this driver accepts.
API_TYPE: ClassVar[str] = ""
#: True when the driver talks to one host directly (ESXi), False for vCenter.
STANDALONE: ClassVar[bool] = True
def __init__(
self,
hostname: str,
username: str,
password: str,
timeout: int = 60,
optional_args: dict[str, Any] | None = None,
) -> None:
self.hostname = hostname
self.username = username
self.password = password
self.timeout = timeout
args = optional_args or {}
self._port = int(args.get("port") or _DEFAULT_PORT)
self._verify_ssl = bool(args.get("verify_ssl", args.get("ssl_verify", True)))
# Where converted cloud images are kept between provisioning jobs.
self._image_cache_dir = Path(args.get("image_cache_dir") or DEFAULT_CACHE_DIR)
self._si: Any = None
self._inventory: Any = None
# -- session -------------------------------------------------------------
def open(self) -> None:
try:
si = _session.connect(
self.hostname,
self._port,
self.username,
self.password,
verify_ssl=self._verify_ssl,
timeout=self.timeout,
)
except Exception as exc:
raise ConnectionException(f"Cannot connect to {self.hostname}: {exc}") from exc
inventory = Inventory(si.RetrieveContent())
about = inventory.about()
if about.get("apiType") != self.API_TYPE:
_session.disconnect(si)
other = _DRIVER_FOR_API.get(about.get("apiType", ""), "another driver")
raise ConnectionException(
f"{self.hostname} is {about.get('fullName', 'not a supported VMware endpoint')}"
f"; use the {other} driver for it"
)
self._si, self._inventory = si, inventory
def close(self) -> None:
if self._si is not None:
_session.disconnect(self._si)
self._si = self._inventory = None
def is_alive(self) -> dict[str, bool]:
return {"is_alive": self._si is not None and _session.alive(self._si)}
def _mo(self, vim_type: Any, moref: str) -> Any:
"""A live managed-object reference for a MoRef id from a plain row."""
return vim_type(moref, self._si._stub)
# -- inventory reads -------------------------------------------------------
def _hosts(self) -> list[dict[str, Any]]:
return self._inventory.collect("HostSystem", paths.HOST)
def _vm_rows(self) -> list[dict[str, Any]]:
return self._inventory.collect("VirtualMachine", paths.VM)
def _index(self, hosts: list[dict[str, Any]]) -> NetworkIndex:
dv_portgroups = self._inventory.collect("DistributedVirtualPortgroup", paths.DV_PORTGROUP)
return network_index(hosts, dv_portgroups)
def _find_vm(self, name: str) -> dict[str, Any]:
"""The VM whose instance UUID, MoRef or name is ``name``."""
vms = [
vm
for vm in self._vm_rows()
if vm.get("config.instanceUuid") and not vm.get("config.template")
]
for key in ("config.instanceUuid", "_moref"):
match = [vm for vm in vms if vm.get(key) == name]
if match:
return match[0]
match = [vm for vm in vms if vm.get("name") == name]
if len(match) > 1:
raise ValueError(f"{len(match)} VMs are named {name!r}; address one by its vmid")
if not match:
raise ValueError(f"There is no VM named or identified by {name!r}")
return match[0]
# -- HypervisorDriver ------------------------------------------------------
def get_vms(self) -> list[dict[str, Any]]:
hosts = self._hosts()
return vm_list(self._vm_rows(), hosts, self._index(hosts))
def get_vm_config(self, name: str) -> VMConfigDict:
return vm_config(self._find_vm(name), self._index(self._hosts()))
def get_vm_snapshots(self, name: str) -> list[SnapshotDict]:
return snapshot_list(self._find_vm(name))
def get_vm_storage_pools(self) -> dict[str, StorageVolumeDict]:
return storage_pools(self._inventory.collect("Datastore", paths.DATASTORE))
def get_virtual_networks(self) -> dict[str, VirtualNetworkDict]:
return virtual_networks( # type: ignore[return-value]
self._hosts(),
self._inventory.collect("DistributedVirtualPortgroup", paths.DV_PORTGROUP),
self._inventory.collect("DistributedVirtualSwitch", paths.DV_SWITCH),
)
def get_environment(self) -> dict[str, Any]:
return environment(self._hosts())
def get_hypervisor_hosts(self) -> list[HypervisorHostDict]:
return hypervisor_hosts(self._hosts(), standalone=self.STANDALONE)
def get_licenses(self) -> list[LicenseDict]:
return license_list(self._inventory.licenses())