- zip() in provisioning is strict (B905): the OVF asks for one adapter per requested NIC, so the two lists match. A VM that came back with fewer adapters used to get a seed that left a NIC unconfigured; it now fails, and create_vm_from_cloud_init discards it as on any other error after the import. A test covers it. - A failed abort of the import lease is logged at debug level instead of passed over (B110); the original error is still the one raised. - nosec for false positives, each with its reason: the 0.0.0.0 entries in the parsers are addresses vSphere reports, not binds (B104); qemu-img runs with a fixed argv and no shell (B404, B603); xml.sax.saxutils only escapes the OVF descriptor the driver writes, nothing is parsed (B406), so defusedxml would not apply.
89 lines
3.2 KiB
Python
89 lines
3.2 KiB
Python
"""NAPALM interface getters for an ESXi host.
|
|
|
|
A host has two kinds of interface worth reporting: physical uplinks
|
|
(``vmnicN``) and VMkernel adapters (``vmkN``), which carry the host's own IP
|
|
addresses. Port groups are not interfaces; they are reported by
|
|
``get_virtual_networks``.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import ipaddress
|
|
from typing import Any
|
|
|
|
_DEFAULT_MTU = 1500
|
|
# Addresses vSphere reports for an interface without one; data, not a bind.
|
|
_UNASSIGNED = {"", "0.0.0.0"} # nosec B104
|
|
|
|
|
|
def _uplink_mtus(host: dict[str, Any]) -> dict[str, int]:
|
|
"""pnic key -> MTU of the standard vSwitch that uses it as an uplink."""
|
|
mtus: dict[str, int] = {}
|
|
for vswitch in host.get("config.network.vswitch") or []:
|
|
for pnic_key in vswitch.get("pnic", []):
|
|
mtus[pnic_key] = int(vswitch.get("mtu") or _DEFAULT_MTU)
|
|
return mtus
|
|
|
|
|
|
def _iface(up: bool, description: str, speed: float, mtu: int, mac: str) -> dict[str, Any]:
|
|
return {
|
|
"is_up": up,
|
|
"is_enabled": True,
|
|
"description": description,
|
|
"last_flapped": -1.0,
|
|
"speed": speed,
|
|
"mtu": mtu,
|
|
"mac_address": mac.upper(),
|
|
}
|
|
|
|
|
|
def host_interfaces(host: dict[str, Any]) -> dict[str, dict[str, Any]]:
|
|
"""vmnics and vmks in the shape of NAPALM's ``get_interfaces``."""
|
|
mtus = _uplink_mtus(host)
|
|
result: dict[str, dict[str, Any]] = {}
|
|
for pnic in host.get("config.network.pnic") or []:
|
|
link = pnic.get("linkSpeed") or {}
|
|
description = " ".join(p for p in (pnic.get("driver"), pnic.get("pci")) if p)
|
|
result[pnic["device"]] = _iface(
|
|
up=bool(link),
|
|
description=description,
|
|
speed=float(link.get("speedMb", 0)),
|
|
mtu=mtus.get(pnic.get("key", ""), _DEFAULT_MTU),
|
|
mac=pnic.get("mac", ""),
|
|
)
|
|
for vnic in host.get("config.network.vnic") or []:
|
|
spec = vnic.get("spec") or {}
|
|
result[vnic["device"]] = _iface(
|
|
up=True,
|
|
description=vnic.get("portgroup") or spec.get("portgroup", ""),
|
|
speed=0.0,
|
|
mtu=int(spec.get("mtu") or _DEFAULT_MTU),
|
|
mac=spec.get("mac", ""),
|
|
)
|
|
return result
|
|
|
|
|
|
def _ipv4(ip: dict[str, Any]) -> dict[str, dict[str, int]]:
|
|
address = ip.get("ipAddress", "")
|
|
if address in _UNASSIGNED:
|
|
return {}
|
|
network = ipaddress.IPv4Network(f"0.0.0.0/{ip.get('subnetMask') or '32'}")
|
|
return {address: {"prefix_length": network.prefixlen}}
|
|
|
|
|
|
def _ipv6(ip: dict[str, Any]) -> dict[str, dict[str, int]]:
|
|
entries = (ip.get("ipV6Config") or {}).get("ipV6Address", [])
|
|
return {e["ipAddress"]: {"prefix_length": int(e.get("prefixLength", 128))} for e in entries}
|
|
|
|
|
|
def host_interfaces_ip(host: dict[str, Any]) -> dict[str, dict[str, Any]]:
|
|
"""Addresses of the VMkernel adapters, as NAPALM's ``get_interfaces_ip``."""
|
|
result: dict[str, dict[str, Any]] = {}
|
|
for vnic in host.get("config.network.vnic") or []:
|
|
ip = (vnic.get("spec") or {}).get("ip") or {}
|
|
families = {"ipv4": _ipv4(ip), "ipv6": _ipv6(ip)}
|
|
families = {k: v for k, v in families.items() if v}
|
|
if families:
|
|
result[vnic["device"]] = families
|
|
return result
|