From 95b4418c96ccbbdc27db5b20995321038b4addf2 Mon Sep 17 00:00:00 2001 From: catborise Date: Fri, 9 Oct 2026 08:11:48 +0300 Subject: [PATCH] fix(instances): a VM without a guest agent shows its ARP address The ARP lookup passed 3 as the address source; libvirt's VIR_DOMAIN_INTERFACE_ADDRESSES_SRC_ARP is 2 and libvirt refused 3 as an unknown source. The error was suppressed, so a VM without a guest agent never showed an address. --- README.md | 1 + vrtManager/instance.py | 4 +- vrtManager/test_interface_addresses.py | 54 ++++++++++++++++++++++++++ 3 files changed, 57 insertions(+), 2 deletions(-) create mode 100644 vrtManager/test_interface_addresses.py diff --git a/README.md b/README.md index e64be2f5..e95a4656 100644 --- a/README.md +++ b/README.md @@ -534,6 +534,7 @@ A rollback leaves the clone on a detached HEAD at the restored commit; the updat - A VM that vanishes from its compute keeps its owners for `INSTANCE_OWNERSHIP_RETENTION_DAYS` (30) in case the same UUID comes back; superusers list and remove these records on the "Removed VMs" admin page. - The log rotates (10 MB × 5) in `data/webvirtcloud.log`. - A disk can be grown while its VM runs or is paused, not only when it is shut off. +- A running VM without a guest agent shows the IPv4 address the host learned by ARP (the lookup always failed before). - After a live migration the VM's NVRAM file is deleted on the source, when it is in libvirt's `/var/lib/libvirt/qemu/nvram` and the destination does not share that directory (a VM created there later with the same name would start with those UEFI variables). - gstfsd finds the guest's operating system the way libguestfs inspects it, so a root on LVM works, and it always answers (an error when it finds no or several operating systems). Update `/usr/local/bin/gstfsd` on every compute (see [Adding a Compute Node](#adding-a-compute-node), step 3). - Docker no longer publishes port 6080; the console goes through nginx on the panel's port (`WS_PUBLIC_PORT`, default 80). diff --git a/vrtManager/instance.py b/vrtManager/instance.py index 356820f4..d45e07b6 100644 --- a/vrtManager/instance.py +++ b/vrtManager/instance.py @@ -14,6 +14,7 @@ try: VIR_DOMAIN_AFFECT_CONFIG, VIR_DOMAIN_AFFECT_LIVE, VIR_DOMAIN_INTERFACE_ADDRESSES_SRC_AGENT, + VIR_DOMAIN_INTERFACE_ADDRESSES_SRC_ARP, VIR_DOMAIN_RUNNING, VIR_DOMAIN_XML_SECURE, VIR_DOMAIN_XML_INACTIVE, @@ -671,8 +672,7 @@ class wvmInstance(wvmConnect): VIR_DOMAIN_INTERFACE_ADDRESSES_SRC_AGENT ) - arp_flag = 3 # libvirt."VIR_DOMAIN_INTERFACE_ADDRESSES_SRC_ARP" - self._ip_cache["arp"] = self._get_interface_addresses(arp_flag) + self._ip_cache["arp"] = self._get_interface_addresses(VIR_DOMAIN_INTERFACE_ADDRESSES_SRC_ARP) def get_net_devices(self, config=False): def networks(ctx): diff --git a/vrtManager/test_interface_addresses.py b/vrtManager/test_interface_addresses.py new file mode 100644 index 00000000..29ebc573 --- /dev/null +++ b/vrtManager/test_interface_addresses.py @@ -0,0 +1,54 @@ +"""A running VM's addresses come from the guest agent when it answers and +from the host's ARP table, so a VM without an agent shows its IPv4 address.""" + +import unittest +from unittest.mock import MagicMock, patch + +from django.conf import settings + +if not settings.configured: + settings.configure(MAC_OUI="52:54:10") + +from libvirt import VIR_DOMAIN_INTERFACE_ADDRESSES_SRC_AGENT, VIR_DOMAIN_INTERFACE_ADDRESSES_SRC_ARP +from vrtManager.instance import wvmInstance + +MAC = "52:54:00:00:00:01" +ARP = {"vnet0": {"hwaddr": MAC, "addrs": [{"type": 0, "addr": "192.0.2.10", "prefix": 0}]}} +AGENT = {"eth0": {"hwaddr": MAC, "addrs": [{"type": 0, "addr": "192.0.2.20", "prefix": 24}]}} + + +def vm(agent): + inst = wvmInstance.__new__(wvmInstance) + inst._ip_cache = None + inst.instance = MagicMock() + inst.instance.interfaceAddresses.side_effect = lambda source: { + VIR_DOMAIN_INTERFACE_ADDRESSES_SRC_AGENT: AGENT, + VIR_DOMAIN_INTERFACE_ADDRESSES_SRC_ARP: ARP, + }[source] + patch.object(inst, "get_status", return_value=1).start() + patch.object(inst, "is_agent_ready", return_value=agent).start() + return inst + + +class InterfaceAddressesTestCase(unittest.TestCase): + def tearDown(self): + patch.stopall() + + def test_a_vm_without_an_agent_shows_its_arp_address(self): + inst = vm(agent=False) + self.assertEqual(inst.get_interface_addresses(MAC), (["192.0.2.10"], [])) + self.assertEqual( + [c.args for c in inst.instance.interfaceAddresses.call_args_list], [(VIR_DOMAIN_INTERFACE_ADDRESSES_SRC_ARP,)] + ) + + def test_the_agents_address_comes_first(self): + inst = vm(agent=True) + self.assertEqual(inst.get_interface_addresses(MAC), (["192.0.2.20"], [])) + self.assertEqual( + [c.args for c in inst.instance.interfaceAddresses.call_args_list], + [(VIR_DOMAIN_INTERFACE_ADDRESSES_SRC_AGENT,), (VIR_DOMAIN_INTERFACE_ADDRESSES_SRC_ARP,)], + ) + + +if __name__ == "__main__": + unittest.main()