-
Notifications
You must be signed in to change notification settings - Fork 9
Description
Summary
Three modules send fields on PATCH that HyperCore's v2 API (/rest/v2) rejects with HTTP 400. The v1 API silently accepted these fields; v2 uses additionalProperties: false on all PATCH schemas, making them hard rejections. All
three fields are parent-reference fields set at creation time that cannot be changed via PATCH — they are being sent unnecessarily because the collection reuses the same serialization method for both POST (create) and PATCH
(update).
This was discovered during a systematic compatibility audit of the collection against HyperCore v2/9.7.1 using authenticated OpenAPI spec comparison and Ansible collection source analysis.
Affected modules and fields
┌────────────────┬────────────────────────────────────┬─────────────────────┬─────────────────────────┬────────────────────────────┐
│ Module │ Endpoint │ Field sent on PATCH │ Should be sent on POST? │ Can be sent on PATCH (v2)? │
├────────────────┼────────────────────────────────────┼─────────────────────┼─────────────────────────┼────────────────────────────┤
│ vm_disk │ PATCH /VirDomainBlockDevice/{uuid} │ virDomainUUID │ ✅ yes │ ❌ 400 │
├────────────────┼────────────────────────────────────┼─────────────────────┼─────────────────────────┼────────────────────────────┤
│ vm_disk │ PATCH /VirDomainBlockDevice/{uuid} │ slot │ ✅ yes │ ❌ 400 │
├────────────────┼────────────────────────────────────┼─────────────────────┼─────────────────────────┼────────────────────────────┤
│ vm_nic │ PATCH /VirDomainNetDevice/{uuid} │ virDomainUUID │ ✅ yes │ ❌ 400 │
├────────────────┼────────────────────────────────────┼─────────────────────┼─────────────────────────┼────────────────────────────┤
│ vm_replication │ PATCH /VirDomainReplication/{uuid} │ sourceDomainUUID │ ✅ yes │ ❌ 400 │
└────────────────┴────────────────────────────────────┴─────────────────────┴─────────────────────────┴────────────────────────────┘
virDomainUUID (on both disk and NIC) and sourceDomainUUID (on replication) are the UUID of the parent VM — a relationship established at creation that cannot be reassigned via PATCH. slot specifies disk position at creation.
Root cause — exact locations
- plugins/module_utils/disk.py — post_and_patch_payload()
to_hypercore() unconditionally returns virDomainUUID and slot. post_and_patch_payload() has partial slot-stripping logic for nvram/vtpm types but retains both fields for all standard disk types (virtio_disk, ide_disk, scsi_disk,
ide_cdrom, ide_floppy). This payload is used directly as the PATCH body.
- plugins/module_utils/nic.py — to_hypercore()
def to_hypercore(self):
return {
"virDomainUUID": self.vm_uuid, # ← sent on every PATCH
"vlan": self.vlan,
...
}
Used for both POST (create NIC) and PATCH (update NIC) without filtering.
- plugins/module_utils/replication.py — to_hypercore()
def to_hypercore(self):
return {
"sourceDomainUUID": self.vm_uuid, # ← sent on every PATCH
"connectionUUID": ...,
"enable": ...,
}
Used directly as the PATCH body.
Integration test targets that fail against v2/9.7.1
Of 69 integration test targets analysed, 14 fail due to these three issues:
┌──────────────────────┬───────────────────────────────────────────────────────────────────────┐
│ Target │ Failing operation │
├──────────────────────┼───────────────────────────────────────────────────────────────────────┤
│ vm_disk │ Disk update PATCH → virDomainUUID + slot │
├──────────────────────┼───────────────────────────────────────────────────────────────────────┤
│ vm_disk__iso_eject │ ISO detach via iso_image_management() → disk PATCH with virDomainUUID │
├──────────────────────┼───────────────────────────────────────────────────────────────────────┤
│ vm_disk__nvram_vtpm │ slot is stripped for these types but virDomainUUID remains │
├──────────────────────┼───────────────────────────────────────────────────────────────────────┤
│ vm_nic │ NIC update PATCH → virDomainUUID │
├──────────────────────┼───────────────────────────────────────────────────────────────────────┤
│ vm_replication │ Replication update PATCH → sourceDomainUUID │
├──────────────────────┼───────────────────────────────────────────────────────────────────────┤
│ vm_clone__replicated │ Post-clone replication setup → sourceDomainUUID PATCH │
├──────────────────────┼───────────────────────────────────────────────────────────────────────┤
│ vm │ Exercises both disk and NIC update paths │
├──────────────────────┼───────────────────────────────────────────────────────────────────────┤
│ vm__iso_eject │ ISO management hits disk PATCH with virDomainUUID │
├──────────────────────┼───────────────────────────────────────────────────────────────────────┤
│ virtual_disk_attach │ Attach template body includes virDomainUUID + slot │
├──────────────────────┼───────────────────────────────────────────────────────────────────────┤
│ git_issue_3 │ Disk enlarge → disk PATCH with banned fields │
├──────────────────────┼───────────────────────────────────────────────────────────────────────┤
│ git_issue_15 │ Tiering priority update → disk PATCH with banned fields │
├──────────────────────┼───────────────────────────────────────────────────────────────────────┤
│ role_template2vm │ Role calls vm_disk resize + vm_nic set — both broken │
└──────────────────────┴───────────────────────────────────────────────────────────────────────┘
55 of 69 targets are unaffected. Notable safe targets: vm__remove_disk (DELETE only), vm_clone (POST only), vm__rename/vm__machine_type/vm_boot_devices/vm_node_affinity (PATCH /VirDomain with clean fields).
Suggested fix
Since v1 silently ignores extra fields, stripping these from PATCH is safe for both v1 and v2 — no version detection needed.
The pattern for each fix is to make the parent-reference fields conditional on whether the call is a create (POST) or update (PATCH). Example for nic.py:
def to_hypercore(self, for_update=False):
payload = {
"vlan": self.vlan,
"connected": self.connected,
"type": self.type.value,
"macAddress": self.mac,
}
if not for_update:
payload["virDomainUUID"] = self.vm_uuid
return payload
Same pattern for replication.py (sourceDomainUUID) and disk.py (virDomainUUID, slot). Update call sites that perform PATCH to pass for_update=True.
Context
- HyperCore /rest/v1 is planned to be renamed/replaced by /rest/v2 as the public API
- v2 uses additionalProperties: false on all PATCH schemas — any field not explicitly in the schema causes a hard 400 rejection (not a silent no-op as in v1)
- The v2 API team is also aware of these missing fields and is planning to restore them in v2 before public release; this collection fix provides defence-in-depth so the collection works correctly regardless of API version
- These three fields are the only collection → v2 incompatibilities found; all other collection operations (55/69 integration test targets) work correctly against v2/9.7.1