mirror of
https://github.com/retspen/webvirtcloud
synced 2026-11-08 11:16:47 +00:00
wvmInstance kept the guest agent's and the ARP table's addresses from
the first NIC it was asked about, and never read them again on that
object; it carried a lease lookup that was never filled.
The addresses are now one {MAC: (IPv4, IPv6)} read, kept like the
domain XML only inside a cached_reads block: the VM page, which reads
its NICs twice, asks the agent once (it may take its whole timeout),
and a read outside a page's block is a new one. The agent's addresses
still come before the ARP table's.
101 lines
4.7 KiB
Python
101 lines
4.7 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, and the VM page with it."""
|
|
|
|
import json
|
|
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_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_interface_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_interface_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_addresses_are_read_once_in_a_page_and_again_after_it(self):
|
|
inst = vm(agent=True)
|
|
with inst.cached_reads():
|
|
inst.get_interface_addresses(MAC)
|
|
inst.get_interface_addresses("52:54:00:00:00:02")
|
|
self.assertEqual(self.agent.call_count, 1)
|
|
# outside a page's block, a new read: a NIC's address can change
|
|
inst.get_interface_addresses(MAC)
|
|
self.assertEqual(self.agent.call_count, 2)
|
|
|
|
def test_a_mac_without_addresses_has_none(self):
|
|
self.assertEqual(vm(agent=False).get_interface_addresses("52:54:00:00:00:02"), (None, None))
|
|
|
|
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_interface_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()
|