"""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. The agent is asked with a timeout: interfaceAddresses() would wait for a hung agent indefinitely; the VM page asks for them after it loads.""" import json import unittest from unittest.mock import MagicMock, patch from libvirt import VIR_DOMAIN_INTERFACE_ADDRESSES_SRC_ARP, libvirtError from vrtManager import instance as module 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}]}} # guest-network-get-interfaces: the loopback has no hardware address AGENT = [{"name": "lo", "ip-addresses": [{"ip-address-type": "ipv4", "ip-address": "127.0.0.1", "prefix": 8}]}, {"name": "eth0", "hardware-address": MAC, "ip-addresses": [ {"ip-address-type": "ipv4", "ip-address": "192.0.2.20", "prefix": 24}, {"ip-address-type": "ipv6", "ip-address": "2001:db8::20", "prefix": 64}]}] def vm(agent): inst = wvmInstance.__new__(wvmInstance) inst.instance = MagicMock() inst.instance.interfaceAddresses.side_effect = lambda source: {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 setUp(self): self.agent = patch.object(module, "qemuAgentCommand", return_value=json.dumps({"return": AGENT})).start() 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_addresses(), {MAC: (["192.0.2.10"], [])}) self.agent.assert_not_called() def test_the_agents_address_comes_first_and_is_asked_with_a_timeout(self): inst = vm(agent=True) self.assertEqual(inst.get_addresses(), {MAC: (["192.0.2.20"], ["2001:db8::20/64"])}) self.agent.assert_called_once_with(inst.instance, '{"execute": "guest-network-get-interfaces"}', 10, 0) self.assertEqual([c.args for c in inst.instance.interfaceAddresses.call_args_list], [(VIR_DOMAIN_INTERFACE_ADDRESSES_SRC_ARP,)]) def test_the_vm_s_definition_does_not_read_them(self): inst = vm(agent=True) inst._XMLDesc = lambda flags: f"" self.assertEqual([nic["mac"] for nic in inst.get_net_devices()], [MAC]) self.agent.assert_not_called() inst.instance.interfaceAddresses.assert_not_called() def test_a_vm_that_does_not_run_has_none(self): inst = vm(agent=True) inst.get_status.return_value = 5 self.assertEqual(inst.get_addresses(), {}) def test_an_agent_that_does_not_answer_leaves_the_arp_address(self): self.agent.side_effect = libvirtError("guest agent didn't respond to command within '10' seconds") self.assertEqual(vm(agent=True).get_addresses(), {MAC: (["192.0.2.10"], [])}) class OsInfoTestCase(unittest.TestCase): def test_the_os_details_are_asked_with_a_timeout(self): answers = {"guest-get-osinfo": {"kernel-release": "6.12"}, "guest-get-timezone": {"zone": "UTC"}, "guest-get-host-name": {"host-name": "web"}} inst = wvmInstance.__new__(wvmInstance) inst.instance = MagicMock() with patch.object(module, "qemuAgentCommand", side_effect=lambda dom, cmd, timeout, flags: json.dumps( {"return": answers[json.loads(cmd)["execute"]]})) as agent: self.assertEqual(inst.osinfo(), {"kernel-release": "6.12", "zone": "UTC", "host-name": "web"}) self.assertEqual({c.args[2] for c in agent.call_args_list}, {10}) def test_a_command_the_agent_lacks_leaves_out_only_its_details(self): inst = wvmInstance.__new__(wvmInstance) inst.instance = MagicMock() def agent(dom, cmd, timeout, flags): if json.loads(cmd)["execute"] == "guest-get-timezone": raise libvirtError("The command guest-get-timezone has not been found") return json.dumps({"return": {"guest-get-osinfo": {"id": "alma"}, "guest-get-host-name": {"host-name": "web"}}[ json.loads(cmd)["execute"]]}) with patch.object(module, "qemuAgentCommand", side_effect=agent): self.assertEqual(inst.osinfo(), {"id": "alma", "host-name": "web"}) if __name__ == "__main__": unittest.main()