The LXC hypervisor now requires LXC >= 1.0.0 for all commands used by
it.
This patch adds the _VerifyLXCCommands() which checks for the existence
of each command and satisfactory version.

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

diff --git a/lib/hypervisor/hv_lxc.py b/lib/hypervisor/hv_lxc.py
index 8051bfc..9a15bfc 100644
--- a/lib/hypervisor/hv_lxc.py
+++ b/lib/hypervisor/hv_lxc.py
@@ -27,6 +27,7 @@ import os
 import os.path
 import logging
 import sys
+import re
 
 from ganeti import constants
 from ganeti import errors # pylint: disable=W0611
@@ -59,6 +60,15 @@ 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_COMMANDS_REQUIRED = [
+    "lxc-console",
+    "lxc-ls",
+    "lxc-start",
+    "lxc-stop",
+    "lxc-wait",
+    ]
+
   _DEVS = [
     "c 1:3",   # /dev/null
     "c 1:5",   # /dev/zero
@@ -89,6 +99,9 @@ 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+)")
+
   def __init__(self):
     hv_base.BaseHypervisor.__init__(self)
     utils.EnsureDirs([
@@ -702,6 +715,61 @@ class LXCHypervisor(hv_base.BaseHypervisor):
                                    user=constants.SSH_CONSOLE_USER,
                                    command=["lxc-console", "-n", 
instance.name])
 
+  @classmethod
+  def _ParseLXCVersion(cls, version_string):
+    """Return a parsed result of lxc version string.
+
+    @return tuple of major, minor and micro version number
+    @rtype tuple(int, int, int)
+
+    """
+    match = cls._LXC_VERSION_RE.match(version_string)
+    return tuple(map(int, match.groups())) if match else None
+
+  @classmethod
+  def _VerifyLXCCommands(cls):
+    """Verify the validity of lxc command line tools.
+
+    @rtype list(str)
+    @return: list of problem descriptions. the blank list will be returned if
+             there is no problem.
+
+    """
+    version_required = cls._ParseLXCVersion(cls._LXC_MIN_VERSION_REQUIRED)
+    msgs = []
+    for cmd in cls._LXC_COMMANDS_REQUIRED:
+      try:
+        # lxc-ls needs special checking procedure.
+        # there are two different version of lxc-ls, one is written in python
+        # and the other is written in shell script.
+        # we have to ensure the python version of lxc-ls is installed.
+        if cmd == "lxc-ls":
+          help_string = utils.RunCmd(["lxc-ls", "--help"]).output
+          if "--running" not in help_string:
+            # shell script version has no --running switch
+            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))
+      except errors.OpExecError:
+        msgs.append("Required command %s not found" % cmd)
+
+    return msgs
+
   def Verify(self, hvparams=None):
     """Verify the hypervisor.
 
@@ -724,6 +792,8 @@ class LXCHypervisor(hv_base.BaseHypervisor):
     except errors.HypervisorError, err:
       msgs.append(str(err))
 
+    msgs.extend(self._VerifyLXCCommands())
+
     return self._FormatVerifyResults(msgs)
 
   @classmethod
diff --git a/test/py/ganeti.hypervisor.hv_lxc_unittest.py 
b/test/py/ganeti.hypervisor.hv_lxc_unittest.py
index cc4bb38..59567a3 100755
--- a/test/py/ganeti.hypervisor.hv_lxc_unittest.py
+++ b/test/py/ganeti.hypervisor.hv_lxc_unittest.py
@@ -24,6 +24,7 @@
 import unittest
 
 from ganeti import constants
+from ganeti import errors
 from ganeti import objects
 from ganeti import hypervisor
 from ganeti import utils
@@ -181,5 +182,55 @@ class TestCgroupReadData(unittest.TestCase):
     self.assertEqual(self.hv._GetCgroupMemoryLimit("instance1"), 128)
 
 
+class TestVerifyLXCCommands(unittest.TestCase):
+  def setUp(self):
+    runcmd_mock = mock.Mock(return_value="")
+    self.RunCmdPatch = patch_object(utils, "RunCmd", runcmd_mock)
+    self.RunCmdPatch.start()
+    version_patch = patch_object(LXCHypervisor, "_LXC_MIN_VERSION_REQUIRED",
+                                 "1.2.3")
+    self._LXC_MIN_VERSION_REQUIRED_Patch = version_patch
+    self._LXC_MIN_VERSION_REQUIRED_Patch.start()
+    self.hvc = LXCHypervisor
+
+  def tearDown(self):
+    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")
+    self.assertFalse(self.hvc._VerifyLXCCommands())
+    utils.RunCmd.return_value = RunResultOk("1.10.0\n")
+    self.assertFalse(self.hvc._VerifyLXCCommands())
+    utils.RunCmd.return_value = RunResultOk("1.2.2\n")
+    self.assertTrue(self.hvc._VerifyLXCCommands())
+
+  @patch_object(LXCHypervisor, "_LXC_COMMANDS_REQUIRED", ["lxc-stop"])
+  def testCommandVersionInvalid(self):
+    utils.RunCmd.return_value = utils.RunResult(1, None, "", "", [], None, 
None)
+    self.assertTrue(self.hvc._VerifyLXCCommands())
+    utils.RunCmd.return_value = RunResultOk("1.2a.0\n")
+    self.assertTrue(self.hvc._VerifyLXCCommands())
+
+  @patch_object(LXCHypervisor, "_LXC_COMMANDS_REQUIRED", ["lxc-stop"])
+  def testCommandNotExists(self):
+    utils.RunCmd.side_effect = errors.OpExecError
+    self.assertTrue(self.hvc._VerifyLXCCommands())
+
+  @patch_object(LXCHypervisor, "_LXC_COMMANDS_REQUIRED", ["lxc-ls"])
+  def testVerifyLXCLs(self):
+    utils.RunCmd.return_value = RunResultOk("garbage\n--running\ngarbage")
+    self.assertFalse(self.hvc._VerifyLXCCommands())
+    utils.RunCmd.return_value = RunResultOk("foo")
+    self.assertTrue(self.hvc._VerifyLXCCommands())
+
+
 if __name__ == "__main__":
   testutils.GanetiTestProgram()
-- 
1.8.5.5

Reply via email to