diff --git a/changelog/62558.fixed b/changelog/62558.fixed new file mode 100644 index 00000000000..cdc2a8489db --- /dev/null +++ b/changelog/62558.fixed @@ -0,0 +1 @@ +Fixed salt-cloud cloning a proxmox VM with a specified new vmid. diff --git a/salt/cloud/clouds/proxmox.py b/salt/cloud/clouds/proxmox.py index af025a789fb..7469aedf863 100644 --- a/salt/cloud/clouds/proxmox.py +++ b/salt/cloud/clouds/proxmox.py @@ -676,10 +676,7 @@ def create(vm_): ret["creation_data"] = data name = vm_["name"] # hostname which we know - if "clone" in vm_ and vm_["clone"] is True: - vmid = newid - else: - vmid = data["vmid"] # vmid which we have received + vmid = data["vmid"] # vmid which we have received host = data["node"] # host which we have received nodeType = data["technology"] # VM tech (Qemu / OpenVZ) @@ -1015,6 +1012,7 @@ def create_node(vm_, newid): ) for prop in _get_properties("/nodes/{node}/qemu", "POST", static_props): if prop in vm_: # if the property is set, use it for the VM request + # If specified, vmid will override newid. newnode[prop] = vm_[prop] # The node is ready. Lets request it to be added @@ -1057,7 +1055,12 @@ def create_node(vm_, newid): ) else: node = query("post", "nodes/{}/{}".format(vmhost, vm_["technology"]), newnode) - return _parse_proxmox_upid(node, vm_) + result = _parse_proxmox_upid(node, vm_) + + # When cloning, the upid contains the clone_from vmid instead of the new vmid + result["vmid"] = newnode["vmid"] + + return result def show_instance(name, call=None): diff --git a/tests/unit/cloud/clouds/test_proxmox.py b/tests/unit/cloud/clouds/test_proxmox.py index 7944bfeefe6..cd6a83f412b 100644 --- a/tests/unit/cloud/clouds/test_proxmox.py +++ b/tests/unit/cloud/clouds/test_proxmox.py @@ -150,7 +150,7 @@ class ProxmoxTest(TestCase, LoaderModuleMockMixin): "nodes/myhost/qemu/123/clone", {"newid": ANY}, ) - assert result == {} + assert result == {"vmid": ANY} # CASE 2: host:ID notation mock_query.reset_mock() @@ -161,7 +161,7 @@ class ProxmoxTest(TestCase, LoaderModuleMockMixin): "nodes/otherhost/qemu/123/clone", {"newid": ANY}, ) - assert result == {} + assert result == {"vmid": ANY} def test_clone_pool(self): """ @@ -186,7 +186,62 @@ class ProxmoxTest(TestCase, LoaderModuleMockMixin): "nodes/myhost/qemu/123/clone", {"newid": ANY, "pool": "mypool"}, ) - assert result == {} + assert result == {"vmid": ANY} + + def test_clone_id(self): + """ + Test cloning a VM with a specified vmid. + """ + next_vmid = 101 + explicit_vmid = 201 + upid = "UPID:myhost:00123456:12345678:9ABCDEF0:qmclone:123:root@pam:" + + def mock_query_response(conn_type, option, post_data=None): + if conn_type == "get" and option == "cluster/tasks": + return [{"upid": upid, "status": "OK"}] + if conn_type == "post" and option.endswith("/clone"): + return upid + return None + + mock_wait_for_state = MagicMock(return_value=True) + with patch( + "salt.cloud.clouds.proxmox._get_properties", + MagicMock(return_value=["vmid"]), + ), patch( + "salt.cloud.clouds.proxmox._get_next_vmid", + MagicMock(return_value=next_vmid), + ), patch( + "salt.cloud.clouds.proxmox.start", MagicMock(return_value=True) + ), patch( + "salt.cloud.clouds.proxmox.wait_for_state", mock_wait_for_state + ), patch( + "salt.cloud.clouds.proxmox.query", side_effect=mock_query_response + ): + vm_ = { + "profile": "my_proxmox", + "driver": "proxmox", + "technology": "qemu", + "name": "new2", + "host": "myhost", + "clone": True, + "clone_from": 123, + "ip_address": "10.10.10.10", + } + + # CASE 1: No vmid specified in profile (previous behavior) + proxmox.create(vm_) + mock_wait_for_state.assert_called_with( + next_vmid, + "running", + ) + + # CASE 2: vmid specified in profile + vm_["vmid"] = explicit_vmid + proxmox.create(vm_) + mock_wait_for_state.assert_called_with( + explicit_vmid, + "running", + ) def test_find_agent_ips(self): """