| #!/usr/bin/python |
| # |
| |
| # Copyright (C) 2012, 2013 Google Inc. |
| # |
| # This program is free software; you can redistribute it and/or modify |
| # it under the terms of the GNU General Public License as published by |
| # the Free Software Foundation; either version 2 of the License, or |
| # (at your option) any later version. |
| # |
| # This program is distributed in the hope that it will be useful, but |
| # WITHOUT ANY WARRANTY; without even the implied warranty of |
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| # General Public License for more details. |
| # |
| # You should have received a copy of the GNU General Public License |
| # along with this program; if not, write to the Free Software |
| # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
| # 02110-1301, USA. |
| |
| |
| """Script for testing qa.qa_config""" |
| |
| import unittest |
| import tempfile |
| import shutil |
| import os |
| import operator |
| |
| from ganeti import utils |
| from ganeti import serializer |
| from ganeti import constants |
| from ganeti import compat |
| |
| from qa import qa_config |
| from qa import qa_error |
| |
| import testutils |
| |
| |
| class TestTestEnabled(unittest.TestCase): |
| def testSimple(self): |
| for name in ["test", ["foobar"], ["a", "b"]]: |
| self.assertTrue(qa_config.TestEnabled(name, _cfg={})) |
| |
| for default in [False, True]: |
| self.assertFalse(qa_config.TestEnabled("foo", _cfg={ |
| "tests": { |
| "default": default, |
| "foo": False, |
| }, |
| })) |
| |
| self.assertTrue(qa_config.TestEnabled("bar", _cfg={ |
| "tests": { |
| "default": default, |
| "bar": True, |
| }, |
| })) |
| |
| def testEitherWithDefault(self): |
| names = qa_config.Either("one") |
| |
| self.assertTrue(qa_config.TestEnabled(names, _cfg={ |
| "tests": { |
| "default": True, |
| }, |
| })) |
| |
| self.assertFalse(qa_config.TestEnabled(names, _cfg={ |
| "tests": { |
| "default": False, |
| }, |
| })) |
| |
| def testEither(self): |
| names = [qa_config.Either(["one", "two"]), |
| qa_config.Either("foo"), |
| "hello", |
| ["bar", "baz"]] |
| |
| self.assertTrue(qa_config.TestEnabled(names, _cfg={ |
| "tests": { |
| "default": True, |
| }, |
| })) |
| |
| self.assertFalse(qa_config.TestEnabled(names, _cfg={ |
| "tests": { |
| "default": False, |
| }, |
| })) |
| |
| for name in ["foo", "bar", "baz", "hello"]: |
| self.assertFalse(qa_config.TestEnabled(names, _cfg={ |
| "tests": { |
| "default": True, |
| name: False, |
| }, |
| })) |
| |
| self.assertFalse(qa_config.TestEnabled(names, _cfg={ |
| "tests": { |
| "default": True, |
| "one": False, |
| "two": False, |
| }, |
| })) |
| |
| self.assertTrue(qa_config.TestEnabled(names, _cfg={ |
| "tests": { |
| "default": True, |
| "one": False, |
| "two": True, |
| }, |
| })) |
| |
| self.assertFalse(qa_config.TestEnabled(names, _cfg={ |
| "tests": { |
| "default": True, |
| "one": True, |
| "two": True, |
| "foo": False, |
| }, |
| })) |
| |
| def testEitherNestedWithAnd(self): |
| names = qa_config.Either([["one", "two"], "foo"]) |
| |
| self.assertTrue(qa_config.TestEnabled(names, _cfg={ |
| "tests": { |
| "default": True, |
| }, |
| })) |
| |
| for name in ["one", "two"]: |
| self.assertFalse(qa_config.TestEnabled(names, _cfg={ |
| "tests": { |
| "default": True, |
| "foo": False, |
| name: False, |
| }, |
| })) |
| |
| def testCallable(self): |
| self.assertTrue(qa_config.TestEnabled([lambda: True], _cfg={})) |
| |
| for value in [None, False, "", 0]: |
| self.assertFalse(qa_config.TestEnabled(lambda: value, _cfg={})) |
| |
| |
| class TestQaConfigLoad(unittest.TestCase): |
| def setUp(self): |
| self.tmpdir = tempfile.mkdtemp() |
| |
| def tearDown(self): |
| shutil.rmtree(self.tmpdir) |
| |
| def testLoadNonExistent(self): |
| filename = utils.PathJoin(self.tmpdir, "does.not.exist") |
| self.assertRaises(EnvironmentError, qa_config._QaConfig.Load, filename) |
| |
| @staticmethod |
| def _WriteConfig(filename, data): |
| utils.WriteFile(filename, data=serializer.DumpJson(data)) |
| |
| def _CheckLoadError(self, filename, data, expected): |
| self._WriteConfig(filename, data) |
| |
| try: |
| qa_config._QaConfig.Load(filename) |
| except qa_error.Error, err: |
| self.assertTrue(str(err).startswith(expected)) |
| else: |
| self.fail("Exception was not raised") |
| |
| def testFailsValidation(self): |
| filename = utils.PathJoin(self.tmpdir, "qa.json") |
| testconfig = {} |
| |
| check_fn = compat.partial(self._CheckLoadError, filename, testconfig) |
| |
| # No cluster name |
| check_fn("Cluster name is required") |
| |
| testconfig["name"] = "cluster.example.com" |
| |
| # No nodes |
| check_fn("Need at least one node") |
| |
| testconfig["nodes"] = [ |
| { |
| "primary": "xen-test-0", |
| "secondary": "192.0.2.1", |
| }, |
| ] |
| |
| # No instances |
| check_fn("Need at least one instance") |
| |
| testconfig["instances"] = [ |
| { |
| "name": "xen-test-inst1", |
| }, |
| ] |
| |
| # Missing "disk" and "disk-growth" |
| check_fn("Config option 'disks'") |
| |
| testconfig["disks"] = [] |
| |
| # Minimal accepted configuration |
| self._WriteConfig(filename, testconfig) |
| result = qa_config._QaConfig.Load(filename) |
| self.assertTrue(result.get("nodes")) |
| |
| # Non-existent instance check script |
| testconfig[qa_config._INSTANCE_CHECK_KEY] = \ |
| utils.PathJoin(self.tmpdir, "instcheck") |
| check_fn("Can't find instance check script") |
| del testconfig[qa_config._INSTANCE_CHECK_KEY] |
| |
| # No enabled hypervisor |
| testconfig[qa_config._ENABLED_HV_KEY] = None |
| check_fn("No hypervisor is enabled") |
| |
| # Unknown hypervisor |
| testconfig[qa_config._ENABLED_HV_KEY] = ["#unknownhv#"] |
| check_fn("Unknown hypervisor(s) enabled:") |
| del testconfig[qa_config._ENABLED_HV_KEY] |
| |
| # Invalid path for virtual cluster base directory |
| testconfig[qa_config._VCLUSTER_MASTER_KEY] = "value" |
| testconfig[qa_config._VCLUSTER_BASEDIR_KEY] = "./not//normalized/" |
| check_fn("Path given in option 'vcluster-basedir' must be") |
| |
| # Inconsistent virtual cluster settings |
| testconfig.pop(qa_config._VCLUSTER_MASTER_KEY) |
| testconfig[qa_config._VCLUSTER_BASEDIR_KEY] = "/tmp" |
| check_fn("All or none of the") |
| |
| testconfig[qa_config._VCLUSTER_MASTER_KEY] = "master.example.com" |
| testconfig.pop(qa_config._VCLUSTER_BASEDIR_KEY) |
| check_fn("All or none of the") |
| |
| # Accepted virtual cluster settings |
| testconfig[qa_config._VCLUSTER_MASTER_KEY] = "master.example.com" |
| testconfig[qa_config._VCLUSTER_BASEDIR_KEY] = "/tmp" |
| |
| self._WriteConfig(filename, testconfig) |
| result = qa_config._QaConfig.Load(filename) |
| self.assertEqual(result.GetVclusterSettings(), |
| ("master.example.com", "/tmp")) |
| |
| |
| class TestQaConfigWithSampleConfig(unittest.TestCase): |
| """Tests using C{qa-sample.json}. |
| |
| This test case serves two purposes: |
| |
| - Ensure shipped C{qa-sample.json} file is considered a valid QA |
| configuration |
| - Test some functions of L{qa_config._QaConfig} without having to |
| mock a whole configuration file |
| |
| """ |
| def setUp(self): |
| filename = "%s/qa/qa-sample.json" % testutils.GetSourceDir() |
| |
| self.config = qa_config._QaConfig.Load(filename) |
| |
| def testGetEnabledHypervisors(self): |
| self.assertEqual(self.config.GetEnabledHypervisors(), |
| [constants.DEFAULT_ENABLED_HYPERVISOR]) |
| |
| def testGetDefaultHypervisor(self): |
| self.assertEqual(self.config.GetDefaultHypervisor(), |
| constants.DEFAULT_ENABLED_HYPERVISOR) |
| |
| def testGetInstanceCheckScript(self): |
| self.assertTrue(self.config.GetInstanceCheckScript() is None) |
| |
| def testGetAndGetItem(self): |
| self.assertEqual(self.config["nodes"], self.config.get("nodes")) |
| |
| def testGetMasterNode(self): |
| self.assertEqual(self.config.GetMasterNode(), self.config["nodes"][0]) |
| |
| def testGetVclusterSettings(self): |
| # Shipped default settings should be to not use a virtual cluster |
| self.assertEqual(self.config.GetVclusterSettings(), (None, None)) |
| |
| self.assertFalse(qa_config.UseVirtualCluster(_cfg=self.config)) |
| |
| |
| class TestQaConfig(unittest.TestCase): |
| def setUp(self): |
| filename = \ |
| testutils.TestDataFilename("qa-minimal-nodes-instances-only.json") |
| |
| self.config = qa_config._QaConfig.Load(filename) |
| |
| def testExclusiveStorage(self): |
| self.assertRaises(AssertionError, self.config.GetExclusiveStorage) |
| |
| for value in [False, True, 0, 1, 30804, ""]: |
| self.config.SetExclusiveStorage(value) |
| self.assertEqual(self.config.GetExclusiveStorage(), bool(value)) |
| |
| def testIsTemplateSupported(self): |
| enabled_dts = self.config.GetEnabledDiskTemplates() |
| for e_s in [False, True]: |
| self.config.SetExclusiveStorage(e_s) |
| for template in constants.DISK_TEMPLATES: |
| if (template not in enabled_dts or |
| e_s and template not in constants.DTS_EXCL_STORAGE): |
| self.assertFalse(self.config.IsTemplateSupported(template)) |
| else: |
| self.assertTrue(self.config.IsTemplateSupported(template)) |
| |
| def testInstanceConversion(self): |
| self.assertTrue(isinstance(self.config["instances"][0], |
| qa_config._QaInstance)) |
| |
| def testNodeConversion(self): |
| self.assertTrue(isinstance(self.config["nodes"][0], |
| qa_config._QaNode)) |
| |
| 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) |
| |
| inst.Release() |
| |
| 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) |
| |
| def testAcquireNodeNoneAdded(self): |
| self.assertFalse(compat.any(map(operator.attrgetter("added"), |
| self.config["nodes"]))) |
| |
| # First call must return master node |
| node = qa_config.AcquireNode(_cfg=self.config) |
| self.assertEqual(node, self.config.GetMasterNode()) |
| |
| # Next call with exclusion list fails |
| self.assertRaises(qa_error.OutOfNodesError, qa_config.AcquireNode, |
| exclude=[node], _cfg=self.config) |
| |
| def testAcquireNodeTooMany(self): |
| # Mark all nodes as marked (master excluded) |
| for node in self.config["nodes"]: |
| if node != self.config.GetMasterNode(): |
| node.MarkAdded() |
| |
| nodecount = len(self.config["nodes"]) |
| |
| self.assertTrue(nodecount > 1) |
| |
| acquired = [] |
| |
| for _ in range(nodecount): |
| node = qa_config.AcquireNode(exclude=acquired, _cfg=self.config) |
| if node == self.config.GetMasterNode(): |
| self.assertFalse(node.added) |
| else: |
| self.assertTrue(node.added) |
| self.assertEqual(node.use_count, 1) |
| acquired.append(node) |
| |
| self.assertRaises(qa_error.OutOfNodesError, qa_config.AcquireNode, |
| exclude=acquired, _cfg=self.config) |
| |
| def testAcquireNodeOrder(self): |
| # Mark all nodes as marked (master excluded) |
| for node in self.config["nodes"]: |
| if node != self.config.GetMasterNode(): |
| node.MarkAdded() |
| |
| nodecount = len(self.config["nodes"]) |
| |
| for iterations in [0, 1, 3, 100, 127, 7964]: |
| acquired = [] |
| |
| for i in range(iterations): |
| node = qa_config.AcquireNode(_cfg=self.config) |
| self.assertTrue(node.use_count > 0) |
| self.assertEqual(node.use_count, (i / nodecount + 1)) |
| acquired.append((node.use_count, node.primary, node)) |
| |
| # Check if returned nodes were in correct order |
| key_fn = lambda (a, b, c): (a, utils.NiceSortKey(b), c) |
| self.assertEqual(acquired, sorted(acquired, key=key_fn)) |
| |
| # Release previously acquired nodes |
| qa_config.ReleaseManyNodes(map(operator.itemgetter(2), acquired)) |
| |
| # Check if nodes were actually released |
| for node in self.config["nodes"]: |
| self.assertEqual(node.use_count, 0) |
| self.assertTrue(node.added or node == self.config.GetMasterNode()) |
| |
| |
| class TestRepresentation(unittest.TestCase): |
| def _Check(self, target, part): |
| self.assertTrue(part in repr(target).split()) |
| |
| def testQaInstance(self): |
| inst = qa_config._QaInstance("inst1.example.com", []) |
| self._Check(inst, "name=inst1.example.com") |
| self._Check(inst, "nicmac=[]") |
| |
| # Default values |
| self._Check(inst, "disk_template=None") |
| self._Check(inst, "used=None") |
| |
| # Use instance |
| inst.Use() |
| self._Check(inst, "used=True") |
| |
| # Disk template |
| inst.SetDiskTemplate(constants.DT_DRBD8) |
| self._Check(inst, "disk_template=%s" % constants.DT_DRBD8) |
| |
| # Release instance |
| inst.Release() |
| self._Check(inst, "used=False") |
| self._Check(inst, "disk_template=None") |
| |
| def testQaNode(self): |
| node = qa_config._QaNode("primary.example.com", "192.0.2.1") |
| self._Check(node, "primary=primary.example.com") |
| self._Check(node, "secondary=192.0.2.1") |
| self._Check(node, "added=False") |
| self._Check(node, "use_count=0") |
| |
| # Mark as added |
| node.MarkAdded() |
| self._Check(node, "added=True") |
| |
| # Use node |
| for i in range(1, 5): |
| node.Use() |
| self._Check(node, "use_count=%s" % i) |
| |
| # Release node |
| for i in reversed(range(1, 5)): |
| node.Release() |
| self._Check(node, "use_count=%s" % (i - 1)) |
| |
| self._Check(node, "use_count=0") |
| |
| # Mark as added |
| node.MarkRemoved() |
| self._Check(node, "added=False") |
| |
| |
| if __name__ == "__main__": |
| testutils.GanetiTestProgram() |