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.
91 lines
3.5 KiB
Python
91 lines
3.5 KiB
Python
"""Bulk reads through the vSphere PropertyCollector.
|
|
|
|
Every read in this package asks for an explicit list of property paths
|
|
(``napalm_vmware.paths``) instead of touching attributes on managed objects.
|
|
One round trip per object type rather than one per attribute, and it keeps
|
|
working where lazy attribute access does not: pyVmomi fails to deserialize
|
|
whole ``summary`` objects from some servers while the individual paths are
|
|
fine.
|
|
|
|
This class is the one seam the tests replace. Its output is already plain
|
|
(see :mod:`napalm_vmware._plain`), so a fake only has to return dicts.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from collections.abc import Sequence
|
|
from typing import Any
|
|
|
|
from pyVmomi import vim, vmodl
|
|
|
|
from napalm_vmware._plain import to_plain
|
|
|
|
_PC = vmodl.query.PropertyCollector
|
|
_PAGE_SIZE = 500
|
|
|
|
|
|
def _vim_type(type_name: str) -> Any:
|
|
vim_type = getattr(vim, type_name, None) or getattr(vim.dvs, type_name, None)
|
|
if vim_type is None:
|
|
raise ValueError(f"Unknown vSphere managed object type {type_name!r}")
|
|
return vim_type
|
|
|
|
|
|
class Inventory:
|
|
"""Read-only access to a connected ServiceInstance's content."""
|
|
|
|
def __init__(self, content: Any) -> None:
|
|
self._content = content
|
|
|
|
def about(self) -> dict[str, Any]:
|
|
"""``ServiceContent.about``: product, version and API type of the endpoint."""
|
|
return to_plain(self._content.about)
|
|
|
|
def collect(self, type_name: str, paths: Sequence[str]) -> list[dict[str, Any]]:
|
|
"""Every object of ``type_name`` in the inventory, with the given properties."""
|
|
vim_type = _vim_type(type_name)
|
|
view = self._content.viewManager.CreateContainerView(
|
|
self._content.rootFolder, [vim_type], True
|
|
)
|
|
try:
|
|
traversal = _PC.TraversalSpec(
|
|
name="view", path="view", skip=False, type=vim.view.ContainerView
|
|
)
|
|
obj_spec = _PC.ObjectSpec(obj=view, skip=True, selectSet=[traversal])
|
|
return self._retrieve(obj_spec, vim_type, paths)
|
|
finally:
|
|
view.Destroy()
|
|
|
|
def properties(self, obj: Any, paths: Sequence[str]) -> dict[str, Any]:
|
|
"""The given properties of one managed object; ``{}`` if it is gone."""
|
|
rows = self._retrieve(_PC.ObjectSpec(obj=obj), type(obj), paths)
|
|
return rows[0] if rows else {}
|
|
|
|
def licenses(self) -> list[dict[str, Any]]:
|
|
"""Licenses assigned on this endpoint (edition key, name, used/total)."""
|
|
manager = getattr(self._content, "licenseManager", None)
|
|
if manager is None:
|
|
return []
|
|
return self.properties(manager, ["licenses"]).get("licenses", [])
|
|
|
|
def _retrieve(self, obj_spec: Any, vim_type: Any, paths: Sequence[str]) -> list[dict[str, Any]]:
|
|
spec = _PC.FilterSpec(
|
|
objectSet=[obj_spec], propSet=[_PC.PropertySpec(type=vim_type, pathSet=list(paths))]
|
|
)
|
|
pc = self._content.propertyCollector
|
|
result = pc.RetrievePropertiesEx([spec], _PC.RetrieveOptions(maxObjects=_PAGE_SIZE))
|
|
rows: list[dict[str, Any]] = []
|
|
while result is not None:
|
|
rows.extend(_row(obj) for obj in result.objects)
|
|
if not result.token:
|
|
break
|
|
result = pc.ContinueRetrievePropertiesEx(token=result.token)
|
|
return rows
|
|
|
|
|
|
def _row(obj_content: Any) -> dict[str, Any]:
|
|
row: dict[str, Any] = {"_moref": obj_content.obj._moId}
|
|
for prop in obj_content.propSet or []:
|
|
row[prop.name] = to_plain(prop.val)
|
|
return row
|