Commit 06c9a520 authored by Michael Hanselmann's avatar Michael Hanselmann

hv_xen: Refactor getting node information, add tests

Refactor and add tests for getting node (Domain-0) information.
Signed-off-by: default avatarMichael Hanselmann <hansmi@google.com>
Reviewed-by: default avatarBernardo Dal Seno <bdalseno@google.com>
parent b255379d
......@@ -1038,6 +1038,7 @@ TEST_FILES = \
test/data/vgreduce-removemissing-2.02.66-ok.txt \
test/data/vgs-missing-pvs-2.02.02.txt \
test/data/vgs-missing-pvs-2.02.66.txt \
test/data/xen-xm-info-4.0.1.txt \
test/data/xen-xm-list-4.0.1-dom0-only.txt \
test/data/xen-xm-list-4.0.1-four-instances.txt \
test/py/ganeti-cli.test \
......
......@@ -158,6 +158,102 @@ def _GetXmList(fn, include_node, _timeout=5):
return _ParseXmList(lines, include_node)
def _ParseNodeInfo(info):
"""Return information about the node.
@return: a dict with the following keys (memory values in MiB):
- memory_total: the total memory size on the node
- memory_free: the available memory on the node for instances
- nr_cpus: total number of CPUs
- nr_nodes: in a NUMA system, the number of domains
- nr_sockets: the number of physical CPU sockets in the node
- hv_version: the hypervisor version in the form (major, minor)
"""
result = {}
cores_per_socket = threads_per_core = nr_cpus = None
xen_major, xen_minor = None, None
memory_total = None
memory_free = None
for line in info.splitlines():
fields = line.split(":", 1)
if len(fields) < 2:
continue
(key, val) = map(lambda s: s.strip(), fields)
# Note: in Xen 3, memory has changed to total_memory
if key in ("memory", "total_memory"):
memory_total = int(val)
elif key == "free_memory":
memory_free = int(val)
elif key == "nr_cpus":
nr_cpus = result["cpu_total"] = int(val)
elif key == "nr_nodes":
result["cpu_nodes"] = int(val)
elif key == "cores_per_socket":
cores_per_socket = int(val)
elif key == "threads_per_core":
threads_per_core = int(val)
elif key == "xen_major":
xen_major = int(val)
elif key == "xen_minor":
xen_minor = int(val)
if None not in [cores_per_socket, threads_per_core, nr_cpus]:
result["cpu_sockets"] = nr_cpus / (cores_per_socket * threads_per_core)
if memory_free is not None:
result["memory_free"] = memory_free
if memory_total is not None:
result["memory_total"] = memory_total
if not (xen_major is None or xen_minor is None):
result[constants.HV_NODEINFO_KEY_VERSION] = (xen_major, xen_minor)
return result
def _MergeInstanceInfo(info, fn):
"""Updates node information from L{_ParseNodeInfo} with instance info.
@type info: dict
@param info: Result from L{_ParseNodeInfo}
@type fn: callable
@param fn: Function returning result of running C{xm list}
@rtype: dict
"""
total_instmem = 0
for (name, _, mem, vcpus, _, _) in fn(True):
if name == _DOM0_NAME:
info["memory_dom0"] = mem
info["dom0_cpus"] = vcpus
# Include Dom0 in total memory usage
total_instmem += mem
memory_free = info.get("memory_free")
memory_total = info.get("memory_total")
# Calculate memory used by hypervisor
if None not in [memory_total, memory_free, total_instmem]:
info["memory_hv"] = memory_total - memory_free - total_instmem
return info
def _GetNodeInfo(info, fn):
"""Combines L{_MergeInstanceInfo} and L{_ParseNodeInfo}.
"""
return _MergeInstanceInfo(_ParseNodeInfo(info), fn)
class XenHypervisor(hv_base.BaseHypervisor):
"""Xen generic hypervisor interface
......@@ -372,80 +468,17 @@ class XenHypervisor(hv_base.BaseHypervisor):
def GetNodeInfo(self):
"""Return information about the node.
@return: a dict with the following keys (memory values in MiB):
- memory_total: the total memory size on the node
- memory_free: the available memory on the node for instances
- memory_dom0: the memory used by the node itself, if available
- nr_cpus: total number of CPUs
- nr_nodes: in a NUMA system, the number of domains
- nr_sockets: the number of physical CPU sockets in the node
- hv_version: the hypervisor version in the form (major, minor)
@see: L{_GetNodeInfo} and L{_ParseNodeInfo}
"""
# TODO: Abstract running Xen command for testing
result = utils.RunCmd([constants.XEN_CMD, "info"])
if result.failed:
logging.error("Can't run 'xm info' (%s): %s", result.fail_reason,
result.output)
return None
xmoutput = result.stdout.splitlines()
result = {}
cores_per_socket = threads_per_core = nr_cpus = None
xen_major, xen_minor = None, None
memory_total = None
memory_free = None
for line in xmoutput:
splitfields = line.split(":", 1)
if len(splitfields) > 1:
key = splitfields[0].strip()
val = splitfields[1].strip()
# note: in xen 3, memory has changed to total_memory
if key == "memory" or key == "total_memory":
memory_total = int(val)
elif key == "free_memory":
memory_free = int(val)
elif key == "nr_cpus":
nr_cpus = result["cpu_total"] = int(val)
elif key == "nr_nodes":
result["cpu_nodes"] = int(val)
elif key == "cores_per_socket":
cores_per_socket = int(val)
elif key == "threads_per_core":
threads_per_core = int(val)
elif key == "xen_major":
xen_major = int(val)
elif key == "xen_minor":
xen_minor = int(val)
if None not in [cores_per_socket, threads_per_core, nr_cpus]:
result["cpu_sockets"] = nr_cpus / (cores_per_socket * threads_per_core)
total_instmem = 0
for (name, _, mem, vcpus, _, _) in self._GetXmList(True):
if name == _DOM0_NAME:
result["memory_dom0"] = mem
result["dom0_cpus"] = vcpus
# Include Dom0 in total memory usage
total_instmem += mem
if memory_free is not None:
result["memory_free"] = memory_free
if memory_total is not None:
result["memory_total"] = memory_total
# Calculate memory used by hypervisor
if None not in [memory_total, memory_free, total_instmem]:
result["memory_hv"] = memory_total - memory_free - total_instmem
if not (xen_major is None or xen_minor is None):
result[constants.HV_NODEINFO_KEY_VERSION] = (xen_major, xen_minor)
return result
return _GetNodeInfo(result.stdout, self._GetXmList)
@classmethod
def GetInstanceConsole(cls, instance, hvparams, beparams):
......
host : host.example.com
release : 3.2.0
version : #1 SMP Tue Jan 1 00:00:00 UTC 2013
machine : x86_64
nr_cpus : 4
nr_nodes : 1
cores_per_socket : 2
threads_per_core : 1
cpu_mhz : 2800
hw_caps : bfebfbff:20100800:00000000:00000940:0004e3bd:00000000:00000001:00000000
virt_caps :
total_memory : 16378
free_memory : 8004
node_to_cpu : node0:0-3
node_to_memory : node0:8004
node_to_dma32_mem : node0:2985
max_node_id : 0
xen_major : 4
xen_minor : 0
xen_extra : .1
xen_caps : xen-3.0-x86_64 xen-3.0-x86_32p
xen_scheduler : credit
xen_pagesize : 4096
platform_params : virt_start=0xffff800000000000
xen_changeset : unavailable
xen_commandline : placeholder dom0_mem=1024M com1=115200,8n1 console=com1
cc_compiler : gcc version 4.4.5 (Debian 4.4.5-8)
cc_compile_by : user
cc_compile_domain : example.com
cc_compile_date : Tue Jan 1 00:00:00 UTC 2013
xend_config_format : 4
......@@ -152,5 +152,66 @@ class TestGetXmList(testutils.GanetiTestCase):
self.assertEqual(fn.Count(), 1)
class TestParseNodeInfo(testutils.GanetiTestCase):
def testEmpty(self):
self.assertEqual(hv_xen._ParseNodeInfo(""), {})
def testUnknownInput(self):
data = "\n".join([
"foo bar",
"something else goes",
"here",
])
self.assertEqual(hv_xen._ParseNodeInfo(data), {})
def testBasicInfo(self):
data = testutils.ReadTestData("xen-xm-info-4.0.1.txt")
result = hv_xen._ParseNodeInfo(data)
self.assertEqual(result, {
"cpu_nodes": 1,
"cpu_sockets": 2,
"cpu_total": 4,
"hv_version": (4, 0),
"memory_free": 8004,
"memory_total": 16378,
})
class TestMergeInstanceInfo(testutils.GanetiTestCase):
def testEmpty(self):
self.assertEqual(hv_xen._MergeInstanceInfo({}, lambda _: []), {})
def _FakeXmList(self, include_node):
self.assertTrue(include_node)
return [
(hv_xen._DOM0_NAME, NotImplemented, 4096, 7, NotImplemented,
NotImplemented),
("inst1.example.com", NotImplemented, 2048, 4, NotImplemented,
NotImplemented),
]
def testMissingNodeInfo(self):
result = hv_xen._MergeInstanceInfo({}, self._FakeXmList)
self.assertEqual(result, {
"memory_dom0": 4096,
"dom0_cpus": 7,
})
def testWithNodeInfo(self):
info = testutils.ReadTestData("xen-xm-info-4.0.1.txt")
result = hv_xen._GetNodeInfo(info, self._FakeXmList)
self.assertEqual(result, {
"cpu_nodes": 1,
"cpu_sockets": 2,
"cpu_total": 4,
"dom0_cpus": 7,
"hv_version": (4, 0),
"memory_dom0": 4096,
"memory_free": 8004,
"memory_hv": 2230,
"memory_total": 16378,
})
if __name__ == "__main__":
testutils.GanetiTestProgram()
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment