Add the class LXCVersion and the function _GetLXCVersion which can be
used to obtain version information from specified command.
Change _VerifyLXCCommands to use this function instead of calling
commands directory, and adjust unit tests for changes made.

Signed-off-by: Yuto KAWAMURA(kawamuray) <[email protected]>
---
 lib/hypervisor/hv_lxc.py                     | 88 ++++++++++++++++++++--------
 test/py/ganeti.hypervisor.hv_lxc_unittest.py | 18 +++---
 2 files changed, 73 insertions(+), 33 deletions(-)

diff --git a/lib/hypervisor/hv_lxc.py b/lib/hypervisor/hv_lxc.py
index 61a198e..3f11a3d 100644
--- a/lib/hypervisor/hv_lxc.py
+++ b/lib/hypervisor/hv_lxc.py
@@ -49,6 +49,39 @@ from ganeti.hypervisor import hv_base
 from ganeti.errors import HypervisorError
 
 
+class LXCVersion(tuple): # pylint: disable=R0924
+  """LXC version class.
+
+  """
+  # Let beta version following micro version, but don't care about it
+  _VERSION_RE = re.compile(r"^(\d+)\.(\d+)\.(\d+)")
+
+  @classmethod
+  def _Parse(cls, version_string):
+    """Parse a passed string as a LXC version string.
+
+    @param version_string: a valid LXC version string
+    @type version_string: string
+    @raise ValueError: if version_string is a invalid LXC version string
+    @rtype tuple(int, int, int)
+    @return (major_num, minor_num, micro_num)
+
+    """
+    match = cls._VERSION_RE.match(version_string)
+    if match:
+      return tuple(map(int, match.groups()))
+    else:
+      raise ValueError("'%s' is not valid LXC version string" % version_string)
+
+  def __new__(cls, version_string):
+    version = super(LXCVersion, cls).__new__(cls, cls._Parse(version_string))
+    version.original_string = version_string
+    return version
+
+  def __str__(self):
+    return self.original_string
+
+
 class LXCHypervisor(hv_base.BaseHypervisor):
   """LXC-based virtualization.
 
@@ -60,7 +93,7 @@ class LXCHypervisor(hv_base.BaseHypervisor):
   _PROC_CGROUPS_FILE = "/proc/cgroups"
   _PROC_SELF_CGROUP_FILE = "/proc/self/cgroup"
 
-  _LXC_MIN_VERSION_REQUIRED = "1.0.0"
+  _LXC_MIN_VERSION_REQUIRED = LXCVersion("1.0.0")
   _LXC_COMMANDS_REQUIRED = [
     "lxc-console",
     "lxc-ls",
@@ -82,8 +115,6 @@ class LXCHypervisor(hv_base.BaseHypervisor):
     constants.HV_LXC_STARTUP_WAIT: hv_base.OPT_NONNEGATIVE_INT_CHECK,
     }
 
-  # Let beta version following micro version, but don't care about it
-  _LXC_VERSION_RE = re.compile(r"^(\d+)\.(\d+)\.(\d+)")
   _REBOOT_TIMEOUT = 120 # secs
   _REQUIRED_CGROUP_SUBSYSTEMS = [
     "cpuset",
@@ -814,15 +845,28 @@ class LXCHypervisor(hv_base.BaseHypervisor):
                                    command=["lxc-console", "-n", 
instance.name])
 
   @classmethod
-  def _ParseLXCVersion(cls, version_string):
-    """Return a parsed result of lxc version string.
+  def _GetLXCVersion(cls, from_cmd="lxc-start"):
+    """Return the LXC version currently used in system.
+
+    Version information will retrieved by command specified by from_cmd.
 
-    @return: tuple of major, minor and micro version number
-    @rtype: tuple(int, int, int)
+    @param from_cmd: the lxc command used to retrieve a version information
+    @type from_cmd: string
+    @rtype L{LXCVersion}
+    @return a version object which represents the version retrieved from the
+      command
 
     """
-    match = cls._LXC_VERSION_RE.match(version_string)
-    return tuple(map(int, match.groups())) if match else None
+    result = utils.RunCmd([from_cmd, "--version"])
+    if result.failed:
+      raise HypervisorError("Failed to get version info from command %s: %s" %
+                            (from_cmd, result.output))
+
+    try:
+      return LXCVersion(result.stdout.strip())
+    except ValueError, err:
+      raise HypervisorError("Can't parse LXC version of %s: %s" %
+                            (from_cmd, err))
 
   @classmethod
   def _VerifyLXCCommands(cls):
@@ -833,7 +877,6 @@ class LXCHypervisor(hv_base.BaseHypervisor):
              there is no problem.
 
     """
-    version_required = cls._ParseLXCVersion(cls._LXC_MIN_VERSION_REQUIRED)
     msgs = []
     for cmd in cls._LXC_COMMANDS_REQUIRED:
       try:
@@ -848,21 +891,16 @@ class LXCHypervisor(hv_base.BaseHypervisor):
             msgs.append("The python version of 'lxc-ls' is required."
                         " Maybe lxc was installed without --enable-python")
         else:
-          result = utils.RunCmd([cmd, "--version"])
-          if result.failed:
-            msgs.append("Can't get version info from %s: %s" %
-                        (cmd, result.output))
-          else:
-            version_str = result.stdout.strip()
-            version = cls._ParseLXCVersion(version_str)
-            if version:
-              if version < version_required:
-                msgs.append("LXC version >= %s is required but command %s has"
-                            " version %s" %
-                            (cls._LXC_MIN_VERSION_REQUIRED, cmd, version_str))
-            else:
-              msgs.append("Can't parse version info from %s output: %s" %
-                          (cmd, version_str))
+          try:
+            version = cls._GetLXCVersion(from_cmd=cmd)
+          except HypervisorError, err:
+            msgs.append(str(err))
+            continue
+
+          if version < cls._LXC_MIN_VERSION_REQUIRED:
+            msgs.append("LXC version >= %s is required but command %s has"
+                        " version %s" %
+                        (cls._LXC_MIN_VERSION_REQUIRED, cmd, version))
       except errors.OpExecError:
         msgs.append("Required command %s not found" % cmd)
 
diff --git a/test/py/ganeti.hypervisor.hv_lxc_unittest.py 
b/test/py/ganeti.hypervisor.hv_lxc_unittest.py
index 8cd08c7..208ddcf 100755
--- a/test/py/ganeti.hypervisor.hv_lxc_unittest.py
+++ b/test/py/ganeti.hypervisor.hv_lxc_unittest.py
@@ -40,7 +40,7 @@ from ganeti import utils
 
 from ganeti.hypervisor import hv_base
 from ganeti.hypervisor import hv_lxc
-from ganeti.hypervisor.hv_lxc import LXCHypervisor
+from ganeti.hypervisor.hv_lxc import LXCHypervisor, LXCVersion
 
 import mock
 import os
@@ -67,6 +67,14 @@ def RunResultOk(stdout):
   return utils.RunResult(0, None, stdout, "", [], None, None)
 
 
+class TestLXCVersion(unittest.TestCase):
+  def testParseLXCVersion(self):
+    self.assertEqual(LXCVersion("1.0.0"), (1, 0, 0))
+    self.assertEqual(LXCVersion("1.0.0.alpha1"), (1, 0, 0))
+    self.assertRaises(ValueError, LXCVersion, "1.0")
+    self.assertRaises(ValueError, LXCVersion, "1.2a.0")
+
+
 class LXCHypervisorTestCase(unittest.TestCase):
   """Used to test classes instantiating the LXC hypervisor class.
 
@@ -208,7 +216,7 @@ class TestVerifyLXCCommands(unittest.TestCase):
     self.RunCmdPatch = patch_object(utils, "RunCmd", runcmd_mock)
     self.RunCmdPatch.start()
     version_patch = patch_object(LXCHypervisor, "_LXC_MIN_VERSION_REQUIRED",
-                                 "1.2.3")
+                                 LXCVersion("1.2.3"))
     self._LXC_MIN_VERSION_REQUIRED_Patch = version_patch
     self._LXC_MIN_VERSION_REQUIRED_Patch.start()
     self.hvc = LXCHypervisor
@@ -217,12 +225,6 @@ class TestVerifyLXCCommands(unittest.TestCase):
     self.RunCmdPatch.stop()
     self._LXC_MIN_VERSION_REQUIRED_Patch.stop()
 
-  def testParseLXCVersion(self):
-    self.assertEqual(self.hvc._ParseLXCVersion("1.0.0"), (1, 0, 0))
-    self.assertEqual(self.hvc._ParseLXCVersion("1.0.0.alpha1"), (1, 0, 0))
-    self.assertEqual(self.hvc._ParseLXCVersion("1.0"), None)
-    self.assertEqual(self.hvc._ParseLXCVersion("1.2a.0"), None)
-
   @patch_object(LXCHypervisor, "_LXC_COMMANDS_REQUIRED", ["lxc-stop"])
   def testCommandVersion(self):
     utils.RunCmd.return_value = RunResultOk("1.2.3\n")
-- 
1.8.5.5

Reply via email to