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.
This commit is contained in:
Christian Manivong
2026-09-24 10:00:33 +02:00
parent c8e472b4c6
commit 2b84ceae3a
25 changed files with 1851 additions and 14 deletions
+36 -2
View File
@@ -30,7 +30,10 @@ hardware**: see [Harvesting fixtures](#harvesting-fixtures).
| `get_vm_storage_pools` | ✅ | ✅ | datastores |
| `get_virtual_networks` | ✅ | ✅ (+ dvPortgroups) | port groups |
| `get_device_warnings` | ✅ | ✅ | raw `{code, meta}` |
| VM provisioning, VIBs, updates, host reboot | — | — | out of scope for v1 |
| `reboot_host` | ✅ (maintenance mode only) | — | `RebootHost_Task` |
| `create_vm_from_cloud_init`, `destroy_vm`, `get_vm_status` | ✅ | ✅ | see [Provisioning](#provisioning) |
| `get_network_targets`, `get_image_storages` | ✅ | ✅ | port groups, datastores |
| VIBs, updates | — | — | not yet |
Unverified assumptions, to be checked against real hardware:
@@ -44,6 +47,9 @@ Unverified assumptions, to be checked against real hardware:
- An account that may read the inventory. For power and snapshot actions it
also needs *Virtual machine → Interaction → Power on/off/Reset/Suspend* and
*Virtual machine → Snapshot management*.
- For provisioning: `qemu-img` (package `qemu-utils`) where the driver runs,
and HTTPS from there to every ESXi host that may receive a VM -- through a
vCenter the disk upload goes straight to the host, not via the vCenter.
- **A paid license for any write.** On the free vSphere Hypervisor license
the API is read-only; the driver reports `vmware_api_read_only` and turns
the refusal into a readable error.
@@ -69,7 +75,9 @@ driver.close()
```
`optional_args`: `port` (default 443), `verify_ssl` / `ssl_verify` (default
`True`; ESXi ships a self-signed certificate). Other keys are ignored.
`True`; ESXi ships a self-signed certificate), `image_cache_dir` (where
converted cloud images are kept; default a directory under the system temp
dir). Other keys are ignored.
VMs are addressed by name, by `vmid`, or by MoRef (`vm-42`). A name shared
by two VMs is refused rather than guessed.
@@ -94,6 +102,32 @@ tools/sanitize.py tools/harvest-out/esxi8-dell.json > tests/fixtures/esxi8-dell.
the ones the drivers read. `tools/harvest-out/` is gitignored. Read the
sanitised file before committing it.
## Provisioning
`create_vm_from_cloud_init` takes the same cloud images netOrk's catalog
offers for Proxmox (qcow2/raw):
1. The image is downloaded where the driver runs, its checksum verified (one
retry), and converted with `qemu-img` to a streamOptimized VMDK. The VMDK
is cached by URL in `image_cache_dir`; the download is not kept.
2. A host is chosen that is connected, not in maintenance mode, and sees the
datastore and every requested port group (the named datastore, or the one
with the most free space).
3. The VM is created from a minimal OVF descriptor (PVSCSI disk, VMXNET3
NICs) and the disk streamed in over NFC.
4. Requested MACs are pinned, the disk grown to `disk_resize_gb`, and a
NoCloud seed ISO (user-data, meta-data, network-config) uploaded next to
the VM's files and attached as a CD-ROM on a new SATA controller.
5. The VM is powered on. Any failure after step 3 removes the VM again.
A port group fixes its VLAN, so `get_network_targets` reports each one with
`kind="portgroup"`, `vlan_aware=False` and its `fixed_vlan_tag`; a NIC asking
for a different `vlan_tag` is refused. The guest agent netOrk installs is
`open-vm-tools` (`GUEST_AGENT_PACKAGES`), which reports the IP address back.
Unverified until #305: that each distribution's cloud kernel carries the
PVSCSI and VMXNET3 drivers.
## Design notes
**One seam.** Every read goes through `Inventory.collect(type, paths)`, a