This patch adds the class LXCVersion and the function _GetLXCVersion which can be used to obtain version information from an LXC command. _VerifyLXCCommands has been changed to use this function instead of calling commands directly, and adjust unit tests for changes made.
Signed-off-by: Yuto KAWAMURA(kawamuray) <[email protected]> --- lib/hypervisor/hv_lxc.py | 89 ++++++++++++++++++++-------- test/py/ganeti.hypervisor.hv_lxc_unittest.py | 18 +++--- 2 files changed, 74 insertions(+), 33 deletions(-) diff --git a/lib/hypervisor/hv_lxc.py b/lib/hypervisor/hv_lxc.py index 4981c9e..0c9bc6d 100644 --- a/lib/hypervisor/hv_lxc.py +++ b/lib/hypervisor/hv_lxc.py @@ -62,6 +62,40 @@ def _CreateBlankFile(path, mode): raise HypervisorError("Failed to create file %s: %s" % (path, err)) +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 an LXC version string. + + @param version_string: a valid LXC version string + @type version_string: string + @raise ValueError: if version_string is an 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 a 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. @@ -93,7 +127,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", @@ -115,8 +149,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", @@ -834,15 +866,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 _GetLXCVersionFromCmd(cls, from_cmd): + """Return the LXC version currently used in the system. + + Version information will be 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 from %s: %s" % + (from_cmd, err)) @classmethod def _VerifyLXCCommands(cls): @@ -853,7 +898,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: @@ -868,21 +912,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._GetLXCVersionFromCmd(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") -- 2.0.4
