fix: roll back a node update the compute refused

A compute 409 (for instance "must be stopped to change the console type
to vnc") left every value that had already been applied locally on the
controller node, because the update loop sets them before the PUT.
Nothing was dumped at that point, but project.dump() serializes the
whole topology: the next successful change anywhere in the project wrote
the refused values to disk, and the next project open recreated the node
with them — this time accepted by a stopped node. A refused change
silently landed later.

Restore the applied properties, the node properties, custom adapters,
missing images and the derived ports before re-raising, the way the
degraded-image create() path already cleans up after its own failure.
This commit is contained in:
YueGuobin committed 2026-10-16 16:49:06 +08:00
1 parent b79dcaa49d
commit 290c542afe
2 files changed
+44

No files matched your search

+14
View File
@@ -711,6 +711,9 @@ class Node:
old_properties = copy.deepcopy(self._properties)
old_custom_adapters = copy.deepcopy(self._custom_adapters)
old_missing_images = copy.deepcopy(self._missing_images)
# Values applied locally below, kept to roll the node back if the
# compute refuses the update (see the ComputeConflictError handler).
old_prop_values = {prop: getattr(self, prop) for prop in kwargs if prop != "properties"}
compute_properties = None
# Update node properties with additional elements
@@ -766,9 +769,20 @@ class Node:
try:
response = await self.put(None, data=data)
except ComputeConflictError:
# The compute refused the update: roll back everything applied
# locally. Keeping the requested values would leave the
# controller describing a node the compute never accepted, and
# the next project dump persists them — a refused change would
# then silently land the next time the node is created.
for prop, old_value in old_prop_values.items():
setattr(self, prop, old_value)
if old_name != self.name:
# special case when the new name is already updated on controller but refused by the compute
self.name = old_name
self._properties = old_properties
self._custom_adapters = old_custom_adapters
self._missing_images = old_missing_images
self._list_ports()
raise
await self.parse_node_response(response.json)
elif old_json != self.asdict():