Statistics
| Branch: | Tag: | Revision:

root / test / py / qa.qa_config_unittest.py @ c072e788

History | View | Annotate | Download (13.6 kB)

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 options 'disk' and 'disk-growth' ")
209

    
210
    testconfig["disk"] = []
211
    testconfig["disk-growth"] = testconfig["disk"]
212

    
213
    # Minimal accepted configuration
214
    self._WriteConfig(filename, testconfig)
215
    result = qa_config._QaConfig.Load(filename)
216
    self.assertTrue(result.get("nodes"))
217

    
218
    # Non-existent instance check script
219
    testconfig[qa_config._INSTANCE_CHECK_KEY] = \
220
      utils.PathJoin(self.tmpdir, "instcheck")
221
    check_fn("Can't find instance check script")
222
    del testconfig[qa_config._INSTANCE_CHECK_KEY]
223

    
224
    # No enabled hypervisor
225
    testconfig[qa_config._ENABLED_HV_KEY] = None
226
    check_fn("No hypervisor is enabled")
227

    
228
    # Unknown hypervisor
229
    testconfig[qa_config._ENABLED_HV_KEY] = ["#unknownhv#"]
230
    check_fn("Unknown hypervisor(s) enabled:")
231
    del testconfig[qa_config._ENABLED_HV_KEY]
232

    
233
    # Invalid path for virtual cluster base directory
234
    testconfig[qa_config._VCLUSTER_MASTER_KEY] = "value"
235
    testconfig[qa_config._VCLUSTER_BASEDIR_KEY] = "./not//normalized/"
236
    check_fn("Path given in option 'vcluster-basedir' must be")
237

    
238
    # Inconsistent virtual cluster settings
239
    testconfig.pop(qa_config._VCLUSTER_MASTER_KEY)
240
    testconfig[qa_config._VCLUSTER_BASEDIR_KEY] = "/tmp"
241
    check_fn("All or none of the")
242

    
243
    testconfig[qa_config._VCLUSTER_MASTER_KEY] = "master.example.com"
244
    testconfig.pop(qa_config._VCLUSTER_BASEDIR_KEY)
245
    check_fn("All or none of the")
246

    
247
    # Accepted virtual cluster settings
248
    testconfig[qa_config._VCLUSTER_MASTER_KEY] = "master.example.com"
249
    testconfig[qa_config._VCLUSTER_BASEDIR_KEY] = "/tmp"
250

    
251
    self._WriteConfig(filename, testconfig)
252
    result = qa_config._QaConfig.Load(filename)
253
    self.assertEqual(result.GetVclusterSettings(),
254
                     ("master.example.com", "/tmp"))
255

    
256

    
257
class TestQaConfigWithSampleConfig(unittest.TestCase):
258
  """Tests using C{qa-sample.json}.
259

260
  This test case serves two purposes:
261

262
    - Ensure shipped C{qa-sample.json} file is considered a valid QA
263
      configuration
264
    - Test some functions of L{qa_config._QaConfig} without having to
265
      mock a whole configuration file
266

267
  """
268
  def setUp(self):
269
    filename = "%s/qa/qa-sample.json" % testutils.GetSourceDir()
270

    
271
    self.config = qa_config._QaConfig.Load(filename)
272

    
273
  def testGetEnabledHypervisors(self):
274
    self.assertEqual(self.config.GetEnabledHypervisors(),
275
                     [constants.DEFAULT_ENABLED_HYPERVISOR])
276

    
277
  def testGetDefaultHypervisor(self):
278
    self.assertEqual(self.config.GetDefaultHypervisor(),
279
                     constants.DEFAULT_ENABLED_HYPERVISOR)
280

    
281
  def testGetInstanceCheckScript(self):
282
    self.assertTrue(self.config.GetInstanceCheckScript() is None)
283

    
284
  def testGetAndGetItem(self):
285
    self.assertEqual(self.config["nodes"], self.config.get("nodes"))
286

    
287
  def testGetMasterNode(self):
288
    self.assertEqual(self.config.GetMasterNode(), self.config["nodes"][0])
289

    
290
  def testGetVclusterSettings(self):
291
    # Shipped default settings should be to not use a virtual cluster
292
    self.assertEqual(self.config.GetVclusterSettings(), (None, None))
293

    
294
    self.assertFalse(qa_config.UseVirtualCluster(_cfg=self.config))
295

    
296

    
297
class TestQaConfig(unittest.TestCase):
298
  def setUp(self):
299
    filename = \
300
      testutils.TestDataFilename("qa-minimal-nodes-instances-only.json")
301

    
302
    self.config = qa_config._QaConfig.Load(filename)
303

    
304
  def testExclusiveStorage(self):
305
    self.assertRaises(AssertionError, self.config.GetExclusiveStorage)
306

    
307
    for value in [False, True, 0, 1, 30804, ""]:
308
      self.config.SetExclusiveStorage(value)
309
      self.assertEqual(self.config.GetExclusiveStorage(), bool(value))
310

    
311
      for template in constants.DISK_TEMPLATES:
312
        if value and template not in constants.DTS_EXCL_STORAGE:
313
          self.assertFalse(self.config.IsTemplateSupported(template))
314
        else:
315
          self.assertTrue(self.config.IsTemplateSupported(template))
316

    
317
  def testInstanceConversion(self):
318
    self.assertTrue(isinstance(self.config["instances"][0],
319
                               qa_config._QaInstance))
320

    
321
  def testNodeConversion(self):
322
    self.assertTrue(isinstance(self.config["nodes"][0],
323
                               qa_config._QaNode))
324

    
325
  def testAcquireAndReleaseInstance(self):
326
    self.assertFalse(compat.any(map(operator.attrgetter("used"),
327
                                    self.config["instances"])))
328

    
329
    inst = qa_config.AcquireInstance(_cfg=self.config)
330
    self.assertTrue(inst.used)
331
    self.assertTrue(inst.disk_template is None)
332

    
333
    inst.Release()
334

    
335
    self.assertFalse(inst.used)
336
    self.assertTrue(inst.disk_template is None)
337

    
338
    self.assertFalse(compat.any(map(operator.attrgetter("used"),
339
                                    self.config["instances"])))
340

    
341
  def testAcquireInstanceTooMany(self):
342
    # Acquire all instances
343
    for _ in range(len(self.config["instances"])):
344
      inst = qa_config.AcquireInstance(_cfg=self.config)
345
      self.assertTrue(inst.used)
346
      self.assertTrue(inst.disk_template is None)
347

    
348
    # The next acquisition must fail
349
    self.assertRaises(qa_error.OutOfInstancesError,
350
                      qa_config.AcquireInstance, _cfg=self.config)
351

    
352
  def testAcquireNodeNoneAdded(self):
353
    self.assertFalse(compat.any(map(operator.attrgetter("added"),
354
                                    self.config["nodes"])))
355

    
356
    # First call must return master node
357
    node = qa_config.AcquireNode(_cfg=self.config)
358
    self.assertEqual(node, self.config.GetMasterNode())
359

    
360
    # Next call with exclusion list fails
361
    self.assertRaises(qa_error.OutOfNodesError, qa_config.AcquireNode,
362
                      exclude=[node], _cfg=self.config)
363

    
364
  def testAcquireNodeTooMany(self):
365
    # Mark all nodes as marked (master excluded)
366
    for node in self.config["nodes"]:
367
      if node != self.config.GetMasterNode():
368
        node.MarkAdded()
369

    
370
    nodecount = len(self.config["nodes"])
371

    
372
    self.assertTrue(nodecount > 1)
373

    
374
    acquired = []
375

    
376
    for _ in range(nodecount):
377
      node = qa_config.AcquireNode(exclude=acquired, _cfg=self.config)
378
      if node == self.config.GetMasterNode():
379
        self.assertFalse(node.added)
380
      else:
381
        self.assertTrue(node.added)
382
      self.assertEqual(node.use_count, 1)
383
      acquired.append(node)
384

    
385
    self.assertRaises(qa_error.OutOfNodesError, qa_config.AcquireNode,
386
                      exclude=acquired, _cfg=self.config)
387

    
388
  def testAcquireNodeOrder(self):
389
    # Mark all nodes as marked (master excluded)
390
    for node in self.config["nodes"]:
391
      if node != self.config.GetMasterNode():
392
        node.MarkAdded()
393

    
394
    nodecount = len(self.config["nodes"])
395

    
396
    for iterations in [0, 1, 3, 100, 127, 7964]:
397
      acquired = []
398

    
399
      for i in range(iterations):
400
        node = qa_config.AcquireNode(_cfg=self.config)
401
        self.assertTrue(node.use_count > 0)
402
        self.assertEqual(node.use_count, (i / nodecount + 1))
403
        acquired.append((node.use_count, node.primary, node))
404

    
405
      # Check if returned nodes were in correct order
406
      key_fn = lambda (a, b, c): (a, utils.NiceSortKey(b), c)
407
      self.assertEqual(acquired, sorted(acquired, key=key_fn))
408

    
409
      # Release previously acquired nodes
410
      qa_config.ReleaseManyNodes(map(operator.itemgetter(2), acquired))
411

    
412
      # Check if nodes were actually released
413
      for node in self.config["nodes"]:
414
        self.assertEqual(node.use_count, 0)
415
        self.assertTrue(node.added or node == self.config.GetMasterNode())
416

    
417

    
418
class TestRepresentation(unittest.TestCase):
419
  def _Check(self, target, part):
420
    self.assertTrue(part in repr(target).split())
421

    
422
  def testQaInstance(self):
423
    inst = qa_config._QaInstance("inst1.example.com", [])
424
    self._Check(inst, "name=inst1.example.com")
425
    self._Check(inst, "nicmac=[]")
426

    
427
    # Default values
428
    self._Check(inst, "disk_template=None")
429
    self._Check(inst, "used=None")
430

    
431
    # Use instance
432
    inst.Use()
433
    self._Check(inst, "used=True")
434

    
435
    # Disk template
436
    inst.SetDiskTemplate(constants.DT_DRBD8)
437
    self._Check(inst, "disk_template=%s" % constants.DT_DRBD8)
438

    
439
    # Release instance
440
    inst.Release()
441
    self._Check(inst, "used=False")
442
    self._Check(inst, "disk_template=None")
443

    
444
  def testQaNode(self):
445
    node = qa_config._QaNode("primary.example.com", "192.0.2.1")
446
    self._Check(node, "primary=primary.example.com")
447
    self._Check(node, "secondary=192.0.2.1")
448
    self._Check(node, "added=False")
449
    self._Check(node, "use_count=0")
450

    
451
    # Mark as added
452
    node.MarkAdded()
453
    self._Check(node, "added=True")
454

    
455
    # Use node
456
    for i in range(1, 5):
457
      node.Use()
458
      self._Check(node, "use_count=%s" % i)
459

    
460
    # Release node
461
    for i in reversed(range(1, 5)):
462
      node.Release()
463
      self._Check(node, "use_count=%s" % (i - 1))
464

    
465
    self._Check(node, "use_count=0")
466

    
467
    # Mark as added
468
    node.MarkRemoved()
469
    self._Check(node, "added=False")
470

    
471

    
472
if __name__ == "__main__":
473
  testutils.GanetiTestProgram()