commit 06c9a52082019477c4f24229a1817486fb347b61
Author: Michael Hanselmann <hansmi@google.com>
Date:   Mon Jan 21 18:17:42 2013 +0100

    hv_xen: Refactor getting node information, add tests
    
    Refactor and add tests for getting node (Domain-0) information.
    
    Signed-off-by: Michael Hanselmann <hansmi@google.com>
    Reviewed-by: Bernardo Dal Seno <bdalseno@google.com>

diff --git a/Makefile.am b/Makefile.am
index aee5563..3a168e8 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -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 \
diff --git a/lib/hypervisor/hv_xen.py b/lib/hypervisor/hv_xen.py
index 26a3a16..001a205 100644
--- a/lib/hypervisor/hv_xen.py
+++ b/lib/hypervisor/hv_xen.py
@@ -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):
diff --git a/test/data/xen-xm-info-4.0.1.txt b/test/data/xen-xm-info-4.0.1.txt
new file mode 100644
index 0000000..5a678b4
--- /dev/null
+++ b/test/data/xen-xm-info-4.0.1.txt
@@ -0,0 +1,31 @@
+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
diff --git a/test/py/ganeti.hypervisor.hv_xen_unittest.py b/test/py/ganeti.hypervisor.hv_xen_unittest.py
index 55ab5c6..e057187 100755
--- a/test/py/ganeti.hypervisor.hv_xen_unittest.py
+++ b/test/py/ganeti.hypervisor.hv_xen_unittest.py
@@ -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()
