1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2026-11-08 11:16:47 +00:00
webvirtcloud/vrtManager/tests/test_interface_addresses.py
catborise aab8dbf1df perf(instances): a VM page does not wait for the guest agent's addresses
A running VM's page read its addresses while it rendered: the guest agent,
when its channel is connected but it does not answer, holds the page (and
a gunicorn worker) for its whole 10 s timeout, on every load. The page now
shows them once it has loaded: vm_addresses.js asks the new
instances/<pk>/addresses/ for each NIC's addresses and fills the header's
IPv4 and each network card's IPv4 and IPv6.
2026-11-07 15:42:49 +03:00

95 lines
4.6 KiB
Python

"""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"<domain><devices><interface type='network'><mac address='{MAC}'/></interface></devices></domain>"
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()