Statistics
| Branch: | Tag: | Revision:

root / test / py / qa.qa_config_unittest.py @ 560ef132

History | View | Annotate | Download (13.7 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 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()