diff --git a/vrtManager/connection.py b/vrtManager/connection.py index 1c98c3dd..6abe7efd 100644 --- a/vrtManager/connection.py +++ b/vrtManager/connection.py @@ -403,6 +403,11 @@ class wvmConnect(object): """The host's capabilities, read once per object (a request): most getters below parse them""" return self.wvm.getCapabilities() + @cached_property + def host_info(self): + """The host's info (architecture, memory in MiB, CPUs, ...), read once per object""" + return self.wvm.getInfo() + @cached_property def dom_cap_xmls(self): """The domain capabilities read so far, by emulator, arch, machine and domain type""" diff --git a/vrtManager/hostdetails.py b/vrtManager/hostdetails.py index cc155364..0f626a4d 100644 --- a/vrtManager/hostdetails.py +++ b/vrtManager/hostdetails.py @@ -34,7 +34,7 @@ class wvmHostDetails(wvmConnect): """ Function return memory usage on node. """ - all_mem = self.wvm.getInfo()[1] * 1048576 + all_mem = self.host_info[1] * 1048576 freemem = self.wvm.getMemoryStats(-1, 0) if isinstance(freemem, dict): free = (freemem["buffers"] + freemem["free"] + freemem["cached"]) * 1024 @@ -78,9 +78,9 @@ class wvmHostDetails(wvmConnect): Function return host server information: hostname, cpu, memory, ... """ info = [self.wvm.getHostname()] # hostname - info.append(self.wvm.getInfo()[0]) # architecture - info.append(self.wvm.getInfo()[1] * 1048576) # memory - info.append(self.wvm.getInfo()[2]) # cpu core count + info.append(self.host_info[0]) # architecture + info.append(self.host_info[1] * 1048576) # memory + info.append(self.host_info[2]) # cpu core count info.append( get_xml_path(self.wvm.getSysinfo(0), func=cpu_version) ) # cpu version diff --git a/vrtManager/instance.py b/vrtManager/instance.py index 0f4e44a7..6b35aeeb 100644 --- a/vrtManager/instance.py +++ b/vrtManager/instance.py @@ -490,13 +490,13 @@ class wvmInstance(wvmConnect): @contextlib.contextmanager def cached_reads(self): """ - Within the block the domain XML (per flags value), the VM's state, - the host's info and each disk's volume are read from libvirt once - (the host's capabilities are read once per object anyway), for pages - that only read: a VM page reads the XML in some 30 getters and each - disk in two lists. Code that changes the VM must not run inside: most - changes (devices, QoS, memory) do not go through _defineXML, the only - call that drops the cache. + Within the block the domain XML (per flags value), the VM's state and + each disk's volume are read from libvirt once (the host's info and + capabilities are read once per object anyway), for pages that only + read: a VM page reads the XML in some 30 getters and each disk in two + lists. Code that changes the VM must not run inside: most changes + (devices, QoS, memory) do not go through _defineXML, the only call + that drops the cache. """ if self._read_cache is not None: raise RuntimeError("cached_reads blocks do not nest") @@ -522,9 +522,6 @@ class wvmInstance(wvmConnect): self._read_cache.clear() return self.wvm.defineXML(xml) - def _host_info(self): - return self._cached("host info", self.wvm.getInfo) - def get_status(self): """ VIR_DOMAIN_NOSTATE = 0 @@ -617,11 +614,11 @@ class wvmInstance(wvmConnect): return description or "" def get_max_memory(self): - return self._host_info()[1] * 1048576 + return self.host_info[1] * 1048576 def get_max_cpus(self): """Get number of physical CPUs.""" - hostinfo = self._host_info() + hostinfo = self.host_info pcpus = hostinfo[4] * hostinfo[5] * hostinfo[6] * hostinfo[7] return range(1, int(pcpus + 1)) @@ -1308,7 +1305,7 @@ class wvmInstance(wvmConnect): "nics": [{"dev": i, "rx": 0, "tx": 0} for i in range(len(nics))], } - key = [self.instance.ID(), self._host_info()[2], disks, nics] + key = [self.instance.ID(), self.host_info[2], disks, nics] unread = (-1,) * 8 def read(): diff --git a/vrtManager/test_capabilities_once.py b/vrtManager/test_host_reads.py similarity index 69% rename from vrtManager/test_capabilities_once.py rename to vrtManager/test_host_reads.py index 97b9fa99..54e6ab93 100644 --- a/vrtManager/test_capabilities_once.py +++ b/vrtManager/test_host_reads.py @@ -1,6 +1,7 @@ -"""A wvmConnect reads the host's capabilities once, and its domain +"""A wvmConnect reads the host's info and capabilities once, and its domain capabilities once per emulator, arch, machine and domain type: the create -page's getters parse them some 60 times. An object lives for one request.""" +page's getters parse the capabilities some 60 times, the overview reads the +info four times. An object lives for one request.""" import unittest @@ -10,6 +11,7 @@ if not settings.configured: settings.configure(MAC_OUI="52:54:10") from vrtManager.connection import wvmConnect +from vrtManager.hostdetails import wvmHostDetails CAPABILITIES = """x86_64 hvm/usr/bin/qemu-kvm @@ -25,13 +27,30 @@ class FakeConn: self.reads.append("capabilities") return CAPABILITIES + def getInfo(self): + self.reads.append("info") + return ["x86_64", 2048, 4, 0, 1, 2, 2, 1] + + def getMemoryStats(self, cell, flags): + self.reads.append("memory") + return {"buffers": 0, "free": 1048576, "cached": 0} + + def getHostname(self): + return "host" + + def getSysinfo(self, flags): + return "" + + def getURI(self): + return "qemu:///system" + def getDomainCapabilities(self, emulator, arch, machine, virttype): self.reads.append((emulator, arch, machine, virttype)) return "" -def connection(): - conn = wvmConnect.__new__(wvmConnect) # no libvirt connection: only these calls +def connection(cls=wvmConnect): + conn = cls.__new__(cls) # no libvirt connection: only these calls conn.wvm = FakeConn() return conn @@ -57,6 +76,13 @@ class CapabilitiesOnceTestCase(unittest.TestCase): ["capabilities", ("/usr/bin/qemu-kvm", "x86_64", "q35", "kvm"), ("/usr/bin/qemu-kvm", "x86_64", "pc", "kvm")], ) + def test_the_overview_reads_the_info_once_and_the_free_memory_each_time(self): + conn = connection(wvmHostDetails) + conn.get_node_info() + conn.get_memory_usage() + self.assertEqual(conn.get_memory_usage()["total"], 2048 * 1048576) + self.assertEqual(conn.wvm.reads, ["info", "memory", "memory"]) + def test_two_objects_do_not_share_their_reads(self): a, b = connection(), connection() a.get_cap_xml() diff --git a/vrtManager/test_read_cache.py b/vrtManager/test_read_cache.py index b6b96760..2d24d16e 100644 --- a/vrtManager/test_read_cache.py +++ b/vrtManager/test_read_cache.py @@ -1,7 +1,7 @@ """wvmInstance.cached_reads(): inside the block the domain XML (per flags), -the VM's state, the host info and each disk's volume are read from libvirt -once; outside it every read goes to libvirt, as code that changes the VM -needs. The host capabilities are read once per object.""" +the VM's state and each disk's volume are read from libvirt once; outside it +every read goes to libvirt, as code that changes the VM needs. The host's +info and capabilities are read once per object.""" import unittest from unittest.mock import MagicMock @@ -60,20 +60,17 @@ class CachedReadsTestCase(unittest.TestCase): p = proxy() p._XMLDesc(0) p._XMLDesc(0) - p._host_info() - p._host_info() - self.assertEqual((p.instance.reads, p.wvm.caps), ([0, 0], 2)) + p.get_status() + p.get_status() + self.assertEqual(p.instance.reads, [0, 0, "info", "info"]) def test_inside_the_block_each_flags_value_is_read_once(self): p = proxy() with p.cached_reads(): first = [p._XMLDesc(0), p._XMLDesc(1), p._XMLDesc(3)] again = [p._XMLDesc(0), p._XMLDesc(1), p._XMLDesc(3)] - p._host_info() - p._host_info() self.assertEqual(first, again) self.assertEqual(p.instance.reads, [0, 1, 3]) - self.assertEqual(p.wvm.caps, 1) def test_the_block_ends_with_its_cache(self): p = proxy() diff --git a/vrtManager/test_usage.py b/vrtManager/test_usage.py index ca25b62a..7077d03c 100644 --- a/vrtManager/test_usage.py +++ b/vrtManager/test_usage.py @@ -55,7 +55,7 @@ def instance(state, clock): proxy._read_cache = None proxy.instance = FakeDomain(state, clock) proxy._XMLDesc = lambda flags: XML - proxy._host_info = lambda: ["x86_64", 0, 4] # 4 host CPUs + proxy.host_info = ["x86_64", 0, 4] # 4 host CPUs proxy.get_status = lambda: state return proxy @@ -106,7 +106,7 @@ class UsageTestCase(unittest.TestCase): proxy.instance.domain_id = 8 def other_host_cpus(proxy): - proxy._host_info = lambda: ["x86_64", 0, 8] + proxy.host_info = ["x86_64", 0, 8] def nic_renamed(proxy): proxy._XMLDesc = lambda flags: XML.replace("vnet0", "vnet1")