Files
napalm-vmware/tests/test_provision_ovf.py
Christian Manivong 2b84ceae3a feat: VM provisioning, and reboot_host on ESXi
create_vm_from_cloud_init takes the same qcow2/raw cloud images netOrk
offers for Proxmox. ESXi can neither boot nor download them, so the
driver does both: download with checksum check and one retry, convert
with qemu-img to a streamOptimized VMDK (cached by URL), import through
a minimal OVF descriptor over NFC, pin requested MACs, grow the disk,
and attach a NoCloud seed ISO placed next to the VM's files. NoCloud
rather than guestinfo because it needs nothing in the guest; the
user-data installs open-vm-tools, which the driver declares as its
guest agent. A failure after the import removes the VM again.

Placement is a pure decision over inventory rows: a connected host
outside maintenance mode that sees the datastore and every port group,
with the resource pool and VM folder found by walking up to the
datacenter -- one path for a standalone host and for a vCenter.

Two faults vcsim surfaced and the tests now pin: a chunked upload body
next to a Content-Length is refused with 500, so the disk goes up as a
sized file object that also reports lease progress; and a device edit
replaces the device as sent, so disk and NIC edits start from the live
objects, backing included (vcsim panicked on a disk without one).

destroy_vm, get_vm_status, get_network_targets (port groups with their
fixed VLAN) and get_image_storages complete the contract.

reboot_host on ESXi uses RebootHost_Task and refuses outside
maintenance mode: force=True would cut power to running VMs.

Tested against vcsim in ESXi and vCenter mode, end to end.
2026-09-24 10:00:33 +02:00

80 lines
2.6 KiB
Python

"""The OVF descriptor an import is built from."""
from __future__ import annotations
import xml.etree.ElementTree as ET
import pytest
from napalm_vmware.provision.ovf import ovf_descriptor
NS = {
"ovf": "http://schemas.dmtf.org/ovf/envelope/1",
"rasd": "http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_ResourceAllocationSettingData",
"vmw": "http://www.vmware.com/schema/ovf",
}
OVF = "{http://schemas.dmtf.org/ovf/envelope/1}"
RASD = "{" + NS["rasd"] + "}"
@pytest.fixture
def root():
return ET.fromstring(
ovf_descriptor("web01", cpu=2, memory_mb=4096, capacity_bytes=10 * 1024**3, nic_count=2)
)
def _items(root):
return root.findall(".//ovf:VirtualHardwareSection/ovf:Item", NS)
def _by_type(root, resource_type):
return [i for i in _items(root) if i.find("rasd:ResourceType", NS).text == str(resource_type)]
def test_cpu_and_memory(root):
(cpu,) = _by_type(root, 3)
(mem,) = _by_type(root, 4)
assert cpu.find("rasd:VirtualQuantity", NS).text == "2"
assert mem.find("rasd:VirtualQuantity", NS).text == "4096"
def test_disk_references_the_stream_optimized_file(root):
disk = root.find(".//ovf:DiskSection/ovf:Disk", NS)
assert disk.get(f"{OVF}capacity") == str(10 * 1024**3)
assert disk.get(f"{OVF}format").endswith("#streamOptimized")
(file_ref,) = root.findall(".//ovf:References/ovf:File", NS)
assert disk.get(f"{OVF}fileRef") == file_ref.get(f"{OVF}id")
(item,) = _by_type(root, 17)
assert item.find("rasd:HostResource", NS).text == f"ovf:/disk/{disk.get(f'{OVF}diskId')}"
def test_one_vmxnet3_nic_per_network(root):
nics = _by_type(root, 10)
assert [n.find("rasd:Connection", NS).text for n in nics] == ["net0", "net1"]
assert {n.find("rasd:ResourceSubType", NS).text for n in nics} == {"VmxNet3"}
networks = root.findall(".//ovf:NetworkSection/ovf:Network", NS)
assert [n.get(f"{OVF}name") for n in networks] == ["net0", "net1"]
def test_rasd_elements_are_in_schema_order(root):
"""vSphere rejects an Item whose CIM elements are out of order."""
for item in _items(root):
names = [child.tag.removeprefix(RASD) for child in item]
assert names == sorted(names), names
def test_name_is_escaped():
ET.fromstring(ovf_descriptor("a<b&c", cpu=1, memory_mb=512, capacity_bytes=1, nic_count=1))
def test_efi_firmware():
root = ET.fromstring(
ovf_descriptor("x", cpu=1, memory_mb=512, capacity_bytes=1, nic_count=1, firmware="efi")
)
config = root.find(".//vmw:Config", NS)
assert (config.get("{" + NS["vmw"] + "}key"), config.get("{" + NS["vmw"] + "}value")) == (
"firmware",
"efi",
)