"""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], *, raw: bool = False) -> dict[str, Any]: """The given properties of one managed object; ``{}`` if it is gone. ``raw=True`` keeps the pyVmomi objects -- for a caller that has to hand a device back to vSphere whole, backing and all, in a reconfigure. """ rows = self._retrieve(_PC.ObjectSpec(obj=obj), type(obj), paths, raw=raw) 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], *, raw: bool = False ) -> 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, raw) for obj in result.objects) if not result.token: break result = pc.ContinueRetrievePropertiesEx(token=result.token) return rows def _row(obj_content: Any, raw: bool = False) -> dict[str, Any]: row: dict[str, Any] = {"_moref": obj_content.obj._moId} for prop in obj_content.propSet or []: row[prop.name] = prop.val if raw else to_plain(prop.val) return row