Merge feature/nic-mac-address: pin explicit NIC MAC when given
This commit is contained in:
@@ -313,8 +313,9 @@ class ProxmoxVMProvisionMixin:
|
||||
if not bridge:
|
||||
raise ValueError(f"NIC {i}: bridge is required")
|
||||
|
||||
# Build base config: model + bridge
|
||||
net_config = f"virtio,bridge={bridge}"
|
||||
# Build base config: model[=mac] + bridge
|
||||
mac = nic.get("mac")
|
||||
net_config = f"virtio={mac},bridge={bridge}" if mac else f"virtio,bridge={bridge}"
|
||||
|
||||
# Add VLAN configuration (access vs trunk)
|
||||
if "trunk_vlan_tags" in nic and nic["trunk_vlan_tags"]:
|
||||
|
||||
@@ -987,3 +987,105 @@ def test_create_vm_writes_snippet_via_ssh_with_correct_content():
|
||||
content = base64.b64decode(encoded).decode("utf-8")
|
||||
assert content.startswith("#cloud-config\n")
|
||||
assert "hostname: write-shape-vm" in content
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Explicit NIC MAC address
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_create_vm_from_cloud_init_with_explicit_mac():
|
||||
"""When nics[i]['mac'] is set, it's pinned via virtio=<mac>,bridge=...
|
||||
(needed so a caller can create a matching DHCP reservation before the
|
||||
VM even boots)."""
|
||||
mixin = ProxmoxVMProvisionMixin()
|
||||
mixin._node_name = "pve1"
|
||||
|
||||
mock_api = MagicMock()
|
||||
mock_api.cluster.nextid.get.return_value = 107
|
||||
mock_api.storage.return_value.get.return_value = {"path": "/var/lib/vz"}
|
||||
|
||||
mock_node = MagicMock()
|
||||
mock_node.storage.get.return_value = [
|
||||
{"storage": "local-lvm", "type": "lvmthin", "content": "images,rootdir", "enabled": 1},
|
||||
{"storage": "snippets", "type": "dir", "content": "snippets", "enabled": 1},
|
||||
]
|
||||
mixin._api = mock_api
|
||||
mixin._node_api = MagicMock(return_value=mock_node)
|
||||
mixin._download_cloud_image = MagicMock(return_value="/var/lib/vz/template/netork-images/debian-12.qcow2")
|
||||
mixin._run_node_command = MagicMock(return_value="")
|
||||
|
||||
mock_vm = MagicMock()
|
||||
mock_node.qemu.return_value = mock_vm
|
||||
mock_node.qemu.post.return_value = None
|
||||
mock_vm.config.post.return_value = None
|
||||
mock_vm.config.get.return_value = {
|
||||
"unused0": "local-lvm:vm-107-disk-0",
|
||||
"scsi0": "local-lvm:vm-107-disk-0",
|
||||
}
|
||||
mock_vm.status.start.post.return_value = "UPID:pve1:133:start"
|
||||
|
||||
mock_task = MagicMock()
|
||||
mock_task.status.get.return_value = {"status": "stopped", "exitstatus": "OK"}
|
||||
mock_node.tasks.return_value = mock_task
|
||||
|
||||
with patch("time.sleep"):
|
||||
mixin.create_vm_from_cloud_init(
|
||||
name="pinned-mac-vm",
|
||||
image_url="https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-genericcloud-amd64.qcow2",
|
||||
cpu=2,
|
||||
memory=2048,
|
||||
nics=[{"bridge": "vmbr0", "vlan_tag": 10, "mac": "02:aa:bb:cc:dd:ee"}],
|
||||
cloud_init_config={"hostname": "pinned-mac-vm"},
|
||||
)
|
||||
|
||||
net_call_args = next(c for c in mock_vm.config.post.call_args_list if "net0" in c[1])
|
||||
assert net_call_args[1]["net0"] == "virtio=02:aa:bb:cc:dd:ee,bridge=vmbr0,tag=10"
|
||||
|
||||
|
||||
def test_create_vm_from_cloud_init_without_mac_uses_bare_virtio():
|
||||
"""Regression: omitting nics[i]['mac'] must still produce the original
|
||||
bare 'virtio,bridge=...' string (auto-generated MAC), not 'virtio=None,...'."""
|
||||
mixin = ProxmoxVMProvisionMixin()
|
||||
mixin._node_name = "pve1"
|
||||
|
||||
mock_api = MagicMock()
|
||||
mock_api.cluster.nextid.get.return_value = 108
|
||||
mock_api.storage.return_value.get.return_value = {"path": "/var/lib/vz"}
|
||||
|
||||
mock_node = MagicMock()
|
||||
mock_node.storage.get.return_value = [
|
||||
{"storage": "local-lvm", "type": "lvmthin", "content": "images,rootdir", "enabled": 1},
|
||||
{"storage": "snippets", "type": "dir", "content": "snippets", "enabled": 1},
|
||||
]
|
||||
mixin._api = mock_api
|
||||
mixin._node_api = MagicMock(return_value=mock_node)
|
||||
mixin._download_cloud_image = MagicMock(return_value="/var/lib/vz/template/netork-images/debian-12.qcow2")
|
||||
mixin._run_node_command = MagicMock(return_value="")
|
||||
|
||||
mock_vm = MagicMock()
|
||||
mock_node.qemu.return_value = mock_vm
|
||||
mock_node.qemu.post.return_value = None
|
||||
mock_vm.config.post.return_value = None
|
||||
mock_vm.config.get.return_value = {
|
||||
"unused0": "local-lvm:vm-108-disk-0",
|
||||
"scsi0": "local-lvm:vm-108-disk-0",
|
||||
}
|
||||
mock_vm.status.start.post.return_value = "UPID:pve1:134:start"
|
||||
|
||||
mock_task = MagicMock()
|
||||
mock_task.status.get.return_value = {"status": "stopped", "exitstatus": "OK"}
|
||||
mock_node.tasks.return_value = mock_task
|
||||
|
||||
with patch("time.sleep"):
|
||||
mixin.create_vm_from_cloud_init(
|
||||
name="auto-mac-vm",
|
||||
image_url="https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-genericcloud-amd64.qcow2",
|
||||
cpu=2,
|
||||
memory=2048,
|
||||
nics=[{"bridge": "vmbr0"}],
|
||||
cloud_init_config={"hostname": "auto-mac-vm"},
|
||||
)
|
||||
|
||||
net_call_args = next(c for c in mock_vm.config.post.call_args_list if "net0" in c[1])
|
||||
assert net_call_args[1]["net0"] == "virtio,bridge=vmbr0"
|
||||
|
||||
Reference in New Issue
Block a user