Merge branch 'stable-2.7' into stable-2.8
[ganeti-local] / test / py / qa.qa_config_unittest.py
1 #!/usr/bin/python
2 #
3
4 # Copyright (C) 2012, 2013 Google Inc.
5 #
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 # General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 # 02110-1301, USA.
20
21
22 """Script for testing qa.qa_config"""
23
24 import unittest
25 import tempfile
26 import shutil
27 import os
28 import operator
29
30 from ganeti import utils
31 from ganeti import serializer
32 from ganeti import constants
33 from ganeti import compat
34
35 from qa import qa_config
36 from qa import qa_error
37
38 import testutils
39
40
41 class TestTestEnabled(unittest.TestCase):
42   def testSimple(self):
43     for name in ["test", ["foobar"], ["a", "b"]]:
44       self.assertTrue(qa_config.TestEnabled(name, _cfg={}))
45
46     for default in [False, True]:
47       self.assertFalse(qa_config.TestEnabled("foo", _cfg={
48         "tests": {
49           "default": default,
50           "foo": False,
51           },
52         }))
53
54       self.assertTrue(qa_config.TestEnabled("bar", _cfg={
55         "tests": {
56           "default": default,
57           "bar": True,
58           },
59         }))
60
61   def testEitherWithDefault(self):
62     names = qa_config.Either("one")
63
64     self.assertTrue(qa_config.TestEnabled(names, _cfg={
65       "tests": {
66         "default": True,
67         },
68       }))
69
70     self.assertFalse(qa_config.TestEnabled(names, _cfg={
71       "tests": {
72         "default": False,
73         },
74       }))
75
76   def testEither(self):
77     names = [qa_config.Either(["one", "two"]),
78              qa_config.Either("foo"),
79              "hello",
80              ["bar", "baz"]]
81
82     self.assertTrue(qa_config.TestEnabled(names, _cfg={
83       "tests": {
84         "default": True,
85         },
86       }))
87
88     self.assertFalse(qa_config.TestEnabled(names, _cfg={
89       "tests": {
90         "default": False,
91         },
92       }))
93
94     for name in ["foo", "bar", "baz", "hello"]:
95       self.assertFalse(qa_config.TestEnabled(names, _cfg={
96         "tests": {
97           "default": True,
98           name: False,
99           },
100         }))
101
102     self.assertFalse(qa_config.TestEnabled(names, _cfg={
103       "tests": {
104         "default": True,
105         "one": False,
106         "two": False,
107         },
108       }))
109
110     self.assertTrue(qa_config.TestEnabled(names, _cfg={
111       "tests": {
112         "default": True,
113         "one": False,
114         "two": True,
115         },
116       }))
117
118     self.assertFalse(qa_config.TestEnabled(names, _cfg={
119       "tests": {
120         "default": True,
121         "one": True,
122         "two": True,
123         "foo": False,
124         },
125       }))
126
127   def testEitherNestedWithAnd(self):
128     names = qa_config.Either([["one", "two"], "foo"])
129
130     self.assertTrue(qa_config.TestEnabled(names, _cfg={
131       "tests": {
132         "default": True,
133         },
134       }))
135
136     for name in ["one", "two"]:
137       self.assertFalse(qa_config.TestEnabled(names, _cfg={
138         "tests": {
139           "default": True,
140           "foo": False,
141           name: False,
142           },
143         }))
144
145   def testCallable(self):
146     self.assertTrue(qa_config.TestEnabled([lambda: True], _cfg={}))
147
148     for value in [None, False, "", 0]:
149       self.assertFalse(qa_config.TestEnabled(lambda: value, _cfg={}))
150
151
152 class TestQaConfigLoad(unittest.TestCase):
153   def setUp(self):
154     self.tmpdir = tempfile.mkdtemp()
155
156   def tearDown(self):
157     shutil.rmtree(self.tmpdir)
158
159   def testLoadNonExistent(self):
160     filename = utils.PathJoin(self.tmpdir, "does.not.exist")
161     self.assertRaises(EnvironmentError, qa_config._QaConfig.Load, filename)
162
163   @staticmethod
164   def _WriteConfig(filename, data):
165     utils.WriteFile(filename, data=serializer.DumpJson(data))
166
167   def _CheckLoadError(self, filename, data, expected):
168     self._WriteConfig(filename, data)
169
170     try:
171       qa_config._QaConfig.Load(filename)
172     except qa_error.Error, err:
173       self.assertTrue(str(err).startswith(expected))
174     else:
175       self.fail("Exception was not raised")
176
177   def testFailsValidation(self):
178     filename = utils.PathJoin(self.tmpdir, "qa.json")
179     testconfig = {}
180
181     check_fn = compat.partial(self._CheckLoadError, filename, testconfig)
182
183     # No cluster name
184     check_fn("Cluster name is required")
185
186     testconfig["name"] = "cluster.example.com"
187
188     # No nodes
189     check_fn("Need at least one node")
190
191     testconfig["nodes"] = [
192       {
193         "primary": "xen-test-0",
194         "secondary": "192.0.2.1",
195         },
196       ]
197
198     # No instances
199     check_fn("Need at least one instance")
200
201     testconfig["instances"] = [
202       {
203         "name": "xen-test-inst1",
204         },
205       ]
206
207     # Missing "disk" and "disk-growth"
208     check_fn("Config option 'disks'")
209
210     testconfig["disks"] = []
211
212     # Minimal accepted configuration
213     self._WriteConfig(filename, testconfig)
214     result = qa_config._QaConfig.Load(filename)
215     self.assertTrue(result.get("nodes"))
216
217     # Non-existent instance check script
218     testconfig[qa_config._INSTANCE_CHECK_KEY] = \
219       utils.PathJoin(self.tmpdir, "instcheck")
220     check_fn("Can't find instance check script")
221     del testconfig[qa_config._INSTANCE_CHECK_KEY]
222
223     # No enabled hypervisor
224     testconfig[qa_config._ENABLED_HV_KEY] = None
225     check_fn("No hypervisor is enabled")
226
227     # Unknown hypervisor
228     testconfig[qa_config._ENABLED_HV_KEY] = ["#unknownhv#"]
229     check_fn("Unknown hypervisor(s) enabled:")
230     del testconfig[qa_config._ENABLED_HV_KEY]
231
232     # Invalid path for virtual cluster base directory
233     testconfig[qa_config._VCLUSTER_MASTER_KEY] = "value"
234     testconfig[qa_config._VCLUSTER_BASEDIR_KEY] = "./not//normalized/"
235     check_fn("Path given in option 'vcluster-basedir' must be")
236
237     # Inconsistent virtual cluster settings
238     testconfig.pop(qa_config._VCLUSTER_MASTER_KEY)
239     testconfig[qa_config._VCLUSTER_BASEDIR_KEY] = "/tmp"
240     check_fn("All or none of the")
241
242     testconfig[qa_config._VCLUSTER_MASTER_KEY] = "master.example.com"
243     testconfig.pop(qa_config._VCLUSTER_BASEDIR_KEY)
244     check_fn("All or none of the")
245
246     # Accepted virtual cluster settings
247     testconfig[qa_config._VCLUSTER_MASTER_KEY] = "master.example.com"
248     testconfig[qa_config._VCLUSTER_BASEDIR_KEY] = "/tmp"
249
250     self._WriteConfig(filename, testconfig)
251     result = qa_config._QaConfig.Load(filename)
252     self.assertEqual(result.GetVclusterSettings(),
253                      ("master.example.com", "/tmp"))
254
255
256 class TestQaConfigWithSampleConfig(unittest.TestCase):
257   """Tests using C{qa-sample.json}.
258
259   This test case serves two purposes:
260
261     - Ensure shipped C{qa-sample.json} file is considered a valid QA
262       configuration
263     - Test some functions of L{qa_config._QaConfig} without having to
264       mock a whole configuration file
265
266   """
267   def setUp(self):
268     filename = "%s/qa/qa-sample.json" % testutils.GetSourceDir()
269
270     self.config = qa_config._QaConfig.Load(filename)
271
272   def testGetEnabledHypervisors(self):
273     self.assertEqual(self.config.GetEnabledHypervisors(),
274                      [constants.DEFAULT_ENABLED_HYPERVISOR])
275
276   def testGetDefaultHypervisor(self):
277     self.assertEqual(self.config.GetDefaultHypervisor(),
278                      constants.DEFAULT_ENABLED_HYPERVISOR)
279
280   def testGetInstanceCheckScript(self):
281     self.assertTrue(self.config.GetInstanceCheckScript() is None)
282
283   def testGetAndGetItem(self):
284     self.assertEqual(self.config["nodes"], self.config.get("nodes"))
285
286   def testGetMasterNode(self):
287     self.assertEqual(self.config.GetMasterNode(), self.config["nodes"][0])
288
289   def testGetVclusterSettings(self):
290     # Shipped default settings should be to not use a virtual cluster
291     self.assertEqual(self.config.GetVclusterSettings(), (None, None))
292
293     self.assertFalse(qa_config.UseVirtualCluster(_cfg=self.config))
294
295
296 class TestQaConfig(unittest.TestCase):
297   def setUp(self):
298     filename = \
299       testutils.TestDataFilename("qa-minimal-nodes-instances-only.json")
300
301     self.config = qa_config._QaConfig.Load(filename)
302
303   def testExclusiveStorage(self):
304     self.assertRaises(AssertionError, self.config.GetExclusiveStorage)
305
306     for value in [False, True, 0, 1, 30804, ""]:
307       self.config.SetExclusiveStorage(value)
308       self.assertEqual(self.config.GetExclusiveStorage(), bool(value))
309
310   def testIsTemplateSupported(self):
311     enabled_dts = self.config.GetEnabledDiskTemplates()
312     for e_s in [False, True]:
313       self.config.SetExclusiveStorage(e_s)
314       for template in constants.DISK_TEMPLATES:
315         if (template not in enabled_dts or
316             e_s and template not in constants.DTS_EXCL_STORAGE):
317           self.assertFalse(self.config.IsTemplateSupported(template))
318         else:
319           self.assertTrue(self.config.IsTemplateSupported(template))
320
321   def testInstanceConversion(self):
322     self.assertTrue(isinstance(self.config["instances"][0],
323                                qa_config._QaInstance))
324
325   def testNodeConversion(self):
326     self.assertTrue(isinstance(self.config["nodes"][0],
327                                qa_config._QaNode))
328
329   def testAcquireAndReleaseInstance(self):
330     self.assertFalse(compat.any(map(operator.attrgetter("used"),
331                                     self.config["instances"])))
332
333     inst = qa_config.AcquireInstance(_cfg=self.config)
334     self.assertTrue(inst.used)
335     self.assertTrue(inst.disk_template is None)
336
337     inst.Release()
338
339     self.assertFalse(inst.used)
340     self.assertTrue(inst.disk_template is None)
341
342     self.assertFalse(compat.any(map(operator.attrgetter("used"),
343                                     self.config["instances"])))
344
345   def testAcquireInstanceTooMany(self):
346     # Acquire all instances
347     for _ in range(len(self.config["instances"])):
348       inst = qa_config.AcquireInstance(_cfg=self.config)
349       self.assertTrue(inst.used)
350       self.assertTrue(inst.disk_template is None)
351
352     # The next acquisition must fail
353     self.assertRaises(qa_error.OutOfInstancesError,
354                       qa_config.AcquireInstance, _cfg=self.config)
355
356   def testAcquireNodeNoneAdded(self):
357     self.assertFalse(compat.any(map(operator.attrgetter("added"),
358                                     self.config["nodes"])))
359
360     # First call must return master node
361     node = qa_config.AcquireNode(_cfg=self.config)
362     self.assertEqual(node, self.config.GetMasterNode())
363
364     # Next call with exclusion list fails
365     self.assertRaises(qa_error.OutOfNodesError, qa_config.AcquireNode,
366                       exclude=[node], _cfg=self.config)
367
368   def testAcquireNodeTooMany(self):
369     # Mark all nodes as marked (master excluded)
370     for node in self.config["nodes"]:
371       if node != self.config.GetMasterNode():
372         node.MarkAdded()
373
374     nodecount = len(self.config["nodes"])
375
376     self.assertTrue(nodecount > 1)
377
378     acquired = []
379
380     for _ in range(nodecount):
381       node = qa_config.AcquireNode(exclude=acquired, _cfg=self.config)
382       if node == self.config.GetMasterNode():
383         self.assertFalse(node.added)
384       else:
385         self.assertTrue(node.added)
386       self.assertEqual(node.use_count, 1)
387       acquired.append(node)
388
389     self.assertRaises(qa_error.OutOfNodesError, qa_config.AcquireNode,
390                       exclude=acquired, _cfg=self.config)
391
392   def testAcquireNodeOrder(self):
393     # Mark all nodes as marked (master excluded)
394     for node in self.config["nodes"]:
395       if node != self.config.GetMasterNode():
396         node.MarkAdded()
397
398     nodecount = len(self.config["nodes"])
399
400     for iterations in [0, 1, 3, 100, 127, 7964]:
401       acquired = []
402
403       for i in range(iterations):
404         node = qa_config.AcquireNode(_cfg=self.config)
405         self.assertTrue(node.use_count > 0)
406         self.assertEqual(node.use_count, (i / nodecount + 1))
407         acquired.append((node.use_count, node.primary, node))
408
409       # Check if returned nodes were in correct order
410       key_fn = lambda (a, b, c): (a, utils.NiceSortKey(b), c)
411       self.assertEqual(acquired, sorted(acquired, key=key_fn))
412
413       # Release previously acquired nodes
414       qa_config.ReleaseManyNodes(map(operator.itemgetter(2), acquired))
415
416       # Check if nodes were actually released
417       for node in self.config["nodes"]:
418         self.assertEqual(node.use_count, 0)
419         self.assertTrue(node.added or node == self.config.GetMasterNode())
420
421
422 class TestRepresentation(unittest.TestCase):
423   def _Check(self, target, part):
424     self.assertTrue(part in repr(target).split())
425
426   def testQaInstance(self):
427     inst = qa_config._QaInstance("inst1.example.com", [])
428     self._Check(inst, "name=inst1.example.com")
429     self._Check(inst, "nicmac=[]")
430
431     # Default values
432     self._Check(inst, "disk_template=None")
433     self._Check(inst, "used=None")
434
435     # Use instance
436     inst.Use()
437     self._Check(inst, "used=True")
438
439     # Disk template
440     inst.SetDiskTemplate(constants.DT_DRBD8)
441     self._Check(inst, "disk_template=%s" % constants.DT_DRBD8)
442
443     # Release instance
444     inst.Release()
445     self._Check(inst, "used=False")
446     self._Check(inst, "disk_template=None")
447
448   def testQaNode(self):
449     node = qa_config._QaNode("primary.example.com", "192.0.2.1")
450     self._Check(node, "primary=primary.example.com")
451     self._Check(node, "secondary=192.0.2.1")
452     self._Check(node, "added=False")
453     self._Check(node, "use_count=0")
454
455     # Mark as added
456     node.MarkAdded()
457     self._Check(node, "added=True")
458
459     # Use node
460     for i in range(1, 5):
461       node.Use()
462       self._Check(node, "use_count=%s" % i)
463
464     # Release node
465     for i in reversed(range(1, 5)):
466       node.Release()
467       self._Check(node, "use_count=%s" % (i - 1))
468
469     self._Check(node, "use_count=0")
470
471     # Mark as added
472     node.MarkRemoved()
473     self._Check(node, "added=False")
474
475
476 if __name__ == "__main__":
477   testutils.GanetiTestProgram()