mirror of
https://github.com/retspen/webvirtcloud
synced 2026-11-08 19:26:48 +00:00
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.
95 lines
4.6 KiB
Python
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()
|