diff --git a/qa/qa_config.py b/qa/qa_config.py index b73ee638f0cc77d712b13942478a224b8bae48e5..879cff988a74f7ffcfb25c36951b2ab805a1d1e9 100644 --- a/qa/qa_config.py +++ b/qa/qa_config.py @@ -40,6 +40,84 @@ _ENABLED_HV_KEY = "enabled-hypervisors" _config = None +class _QaInstance(object): + __slots__ = [ + "name", + "nicmac", + "used", + "disk_template", + ] + + def __init__(self, name, nicmac): + """Initializes instances of this class. + + """ + self.name = name + self.nicmac = nicmac + self.used = None + self.disk_template = None + + @classmethod + def FromDict(cls, data): + """Creates instance object from JSON dictionary. + + """ + nicmac = [] + + macaddr = data.get("nic.mac/0") + if macaddr: + nicmac.append(macaddr) + + return cls(name=data["name"], nicmac=nicmac) + + def __getitem__(self, key): + """Legacy dict-like interface. + + """ + if key == "name": + return self.name + else: + raise KeyError(key) + + def get(self, key, default): + """Legacy dict-like interface. + + """ + try: + return self[key] + except KeyError: + return default + + def GetNicMacAddr(self, idx, default): + """Returns MAC address for NIC. + + @type idx: int + @param idx: NIC index + @param default: Default value + + """ + if len(self.nicmac) > idx: + return self.nicmac[idx] + else: + return default + + +_RESOURCE_CONVERTER = { + "instances": _QaInstance.FromDict, + } + + +def _ConvertResources((key, value)): + """Converts cluster resources in configuration to Python objects. + + """ + fn = _RESOURCE_CONVERTER.get(key, None) + if fn: + return (key, map(fn, value)) + else: + return (key, value) + + class _QaConfig(object): def __init__(self, data): """Initializes instances of this class. @@ -61,7 +139,8 @@ class _QaConfig(object): """ data = serializer.LoadJson(utils.ReadFile(filename)) - result = cls(data) + result = cls(dict(map(_ConvertResources, + data.items()))) # pylint: disable=E1103 result.Validate() return result @@ -308,7 +387,7 @@ def GetInstanceNicMac(inst, default=None): """Returns MAC address for instance's network interface. """ - return inst.get("nic.mac/0", default) + return inst.GetNicMacAddr(0, default) def GetMasterNode(): @@ -318,33 +397,41 @@ def GetMasterNode(): return GetConfig().GetMasterNode() -def AcquireInstance(): +def AcquireInstance(_cfg=None): """Returns an instance which isn't in use. """ + if _cfg is None: + cfg = GetConfig() + else: + cfg = _cfg + # Filter out unwanted instances - tmp_flt = lambda inst: not inst.get("_used", False) - instances = filter(tmp_flt, GetConfig()["instances"]) - del tmp_flt + instances = filter(lambda inst: not inst.used, cfg["instances"]) - if len(instances) == 0: + if not instances: raise qa_error.OutOfInstancesError("No instances left") inst = instances[0] - inst["_used"] = True - inst["_template"] = None + + assert not inst.used + assert inst.disk_template is None + + inst.used = True + return inst def ReleaseInstance(inst): - inst["_used"] = False + inst.used = False + inst.disk_template = None def GetInstanceTemplate(inst): """Return the disk template of an instance. """ - templ = inst["_template"] + templ = inst.disk_template assert templ is not None return templ @@ -353,7 +440,7 @@ def SetInstanceTemplate(inst, template): """Set the disk template for an instance. """ - inst["_template"] = template + inst.disk_template = template def SetExclusiveStorage(value): diff --git a/test/py/qa.qa_config_unittest.py b/test/py/qa.qa_config_unittest.py index 710b7065167fdc91d149aafba4550fdb2676dc52..0dcfc267fe5afaf2896e06d058fd08ec8296c4fa 100755 --- a/test/py/qa.qa_config_unittest.py +++ b/test/py/qa.qa_config_unittest.py @@ -25,6 +25,7 @@ import unittest import tempfile import shutil import os +import operator from ganeti import utils from ganeti import serializer @@ -272,6 +273,37 @@ class TestQaConfig(unittest.TestCase): else: self.assertTrue(self.config.IsTemplateSupported(template)) + def testInstanceConversion(self): + self.assertTrue(isinstance(self.config["instances"][0], + qa_config._QaInstance)) + + def testAcquireAndReleaseInstance(self): + self.assertFalse(compat.any(map(operator.attrgetter("used"), + self.config["instances"]))) + + inst = qa_config.AcquireInstance(_cfg=self.config) + self.assertTrue(inst.used) + self.assertTrue(inst.disk_template is None) + + qa_config.ReleaseInstance(inst) + + self.assertFalse(inst.used) + self.assertTrue(inst.disk_template is None) + + self.assertFalse(compat.any(map(operator.attrgetter("used"), + self.config["instances"]))) + + def testAcquireInstanceTooMany(self): + # Acquire all instances + for _ in range(len(self.config["instances"])): + inst = qa_config.AcquireInstance(_cfg=self.config) + self.assertTrue(inst.used) + self.assertTrue(inst.disk_template is None) + + # The next acquisition must fail + self.assertRaises(qa_error.OutOfInstancesError, + qa_config.AcquireInstance, _cfg=self.config) + if __name__ == "__main__": testutils.GanetiTestProgram()