Statistics
| Branch: | Tag: | Revision:

root / test / py / qa.qa_config_unittest.py @ 76fda900

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

    
146
class TestQaConfigLoad(unittest.TestCase):
147
  def setUp(self):
148
    self.tmpdir = tempfile.mkdtemp()
149

    
150
  def tearDown(self):
151
    shutil.rmtree(self.tmpdir)
152

    
153
  def testLoadNonExistent(self):
154
    filename = utils.PathJoin(self.tmpdir, "does.not.exist")
155
    self.assertRaises(EnvironmentError, qa_config._QaConfig.Load, filename)
156

    
157
  @staticmethod
158
  def _WriteConfig(filename, data):
159
    utils.WriteFile(filename, data=serializer.DumpJson(data))
160

    
161
  def _CheckLoadError(self, filename, data, expected):
162
    self._WriteConfig(filename, data)
163

    
164
    try:
165
      qa_config._QaConfig.Load(filename)
166
    except qa_error.Error, err:
167
      self.assertTrue(str(err).startswith(expected))
168
    else:
169
      self.fail("Exception was not raised")
170

    
171
  def testFailsValidation(self):
172
    filename = utils.PathJoin(self.tmpdir, "qa.json")
173
    testconfig = {}
174

    
175
    check_fn = compat.partial(self._CheckLoadError, filename, testconfig)
176

    
177
    # No cluster name
178
    check_fn("Cluster name is required")
179

    
180
    testconfig["name"] = "cluster.example.com"
181

    
182
    # No nodes
183
    check_fn("Need at least one node")
184

    
185
    testconfig["nodes"] = [
186
      {
187
        "primary": "xen-test-0",
188
        "secondary": "192.0.2.1",
189
        },
190
      ]
191

    
192
    # No instances
193
    check_fn("Need at least one instance")
194

    
195
    testconfig["instances"] = [
196
      {
197
        "name": "xen-test-inst1",
198
        },
199
      ]
200

    
201
    # Missing "disk" and "disk-growth"
202
    check_fn("Config options 'disk' and 'disk-growth' ")
203

    
204
    testconfig["disk"] = []
205
    testconfig["disk-growth"] = testconfig["disk"]
206

    
207
    # Minimal accepted configuration
208
    self._WriteConfig(filename, testconfig)
209
    result = qa_config._QaConfig.Load(filename)
210
    self.assertTrue(result.get("nodes"))
211

    
212
    # Non-existent instance check script
213
    testconfig[qa_config._INSTANCE_CHECK_KEY] = \
214
      utils.PathJoin(self.tmpdir, "instcheck")
215
    check_fn("Can't find instance check script")
216
    del testconfig[qa_config._INSTANCE_CHECK_KEY]
217

    
218
    # No enabled hypervisor
219
    testconfig[qa_config._ENABLED_HV_KEY] = None
220
    check_fn("No hypervisor is enabled")
221

    
222
    # Unknown hypervisor
223
    testconfig[qa_config._ENABLED_HV_KEY] = ["#unknownhv#"]
224
    check_fn("Unknown hypervisor(s) enabled:")
225
    del testconfig[qa_config._ENABLED_HV_KEY]
226

    
227
    # Invalid path for virtual cluster base directory
228
    testconfig[qa_config._VCLUSTER_MASTER_KEY] = "value"
229
    testconfig[qa_config._VCLUSTER_BASEDIR_KEY] = "./not//normalized/"
230
    check_fn("Path given in option 'vcluster-basedir' must be")
231

    
232
    # Inconsistent virtual cluster settings
233
    testconfig.pop(qa_config._VCLUSTER_MASTER_KEY)
234
    testconfig[qa_config._VCLUSTER_BASEDIR_KEY] = "/tmp"
235
    check_fn("All or none of the")
236

    
237
    testconfig[qa_config._VCLUSTER_MASTER_KEY] = "master.example.com"
238
    testconfig.pop(qa_config._VCLUSTER_BASEDIR_KEY)
239
    check_fn("All or none of the")
240

    
241
    # Accepted virtual cluster settings
242
    testconfig[qa_config._VCLUSTER_MASTER_KEY] = "master.example.com"
243
    testconfig[qa_config._VCLUSTER_BASEDIR_KEY] = "/tmp"
244

    
245
    self._WriteConfig(filename, testconfig)
246
    result = qa_config._QaConfig.Load(filename)
247
    self.assertEqual(result.GetVclusterSettings(),
248
                     ("master.example.com", "/tmp"))
249

    
250

    
251
class TestQaConfigWithSampleConfig(unittest.TestCase):
252
  """Tests using C{qa-sample.json}.
253

254
  This test case serves two purposes:
255

256
    - Ensure shipped C{qa-sample.json} file is considered a valid QA
257
      configuration
258
    - Test some functions of L{qa_config._QaConfig} without having to
259
      mock a whole configuration file
260

261
  """
262
  def setUp(self):
263
    filename = "%s/qa/qa-sample.json" % testutils.GetSourceDir()
264

    
265
    self.config = qa_config._QaConfig.Load(filename)
266

    
267
  def testGetEnabledHypervisors(self):
268
    self.assertEqual(self.config.GetEnabledHypervisors(),
269
                     [constants.DEFAULT_ENABLED_HYPERVISOR])
270

    
271
  def testGetDefaultHypervisor(self):
272
    self.assertEqual(self.config.GetDefaultHypervisor(),
273
                     constants.DEFAULT_ENABLED_HYPERVISOR)
274

    
275
  def testGetInstanceCheckScript(self):
276
    self.assertTrue(self.config.GetInstanceCheckScript() is None)
277

    
278
  def testGetAndGetItem(self):
279
    self.assertEqual(self.config["nodes"], self.config.get("nodes"))
280

    
281
  def testGetMasterNode(self):
282
    self.assertEqual(self.config.GetMasterNode(), self.config["nodes"][0])
283

    
284
  def testGetVclusterSettings(self):
285
    # Shipped default settings should be to not use a virtual cluster
286
    self.assertEqual(self.config.GetVclusterSettings(), (None, None))
287

    
288
    self.assertFalse(qa_config.UseVirtualCluster(_cfg=self.config))
289

    
290

    
291
class TestQaConfig(unittest.TestCase):
292
  def setUp(self):
293
    filename = \
294
      testutils.TestDataFilename("qa-minimal-nodes-instances-only.json")
295

    
296
    self.config = qa_config._QaConfig.Load(filename)
297

    
298
  def testExclusiveStorage(self):
299
    self.assertRaises(AssertionError, self.config.GetExclusiveStorage)
300

    
301
    for value in [False, True, 0, 1, 30804, ""]:
302
      self.config.SetExclusiveStorage(value)
303
      self.assertEqual(self.config.GetExclusiveStorage(), bool(value))
304

    
305
      for template in constants.DISK_TEMPLATES:
306
        if value and template not in constants.DTS_EXCL_STORAGE:
307
          self.assertFalse(self.config.IsTemplateSupported(template))
308
        else:
309
          self.assertTrue(self.config.IsTemplateSupported(template))
310

    
311
  def testInstanceConversion(self):
312
    self.assertTrue(isinstance(self.config["instances"][0],
313
                               qa_config._QaInstance))
314

    
315
  def testNodeConversion(self):
316
    self.assertTrue(isinstance(self.config["nodes"][0],
317
                               qa_config._QaNode))
318

    
319
  def testAcquireAndReleaseInstance(self):
320
    self.assertFalse(compat.any(map(operator.attrgetter("used"),
321
                                    self.config["instances"])))
322

    
323
    inst = qa_config.AcquireInstance(_cfg=self.config)
324
    self.assertTrue(inst.used)
325
    self.assertTrue(inst.disk_template is None)
326

    
327
    inst.Release()
328

    
329
    self.assertFalse(inst.used)
330
    self.assertTrue(inst.disk_template is None)
331

    
332
    self.assertFalse(compat.any(map(operator.attrgetter("used"),
333
                                    self.config["instances"])))
334

    
335
  def testAcquireInstanceTooMany(self):
336
    # Acquire all instances
337
    for _ in range(len(self.config["instances"])):
338
      inst = qa_config.AcquireInstance(_cfg=self.config)
339
      self.assertTrue(inst.used)
340
      self.assertTrue(inst.disk_template is None)
341

    
342
    # The next acquisition must fail
343
    self.assertRaises(qa_error.OutOfInstancesError,
344
                      qa_config.AcquireInstance, _cfg=self.config)
345

    
346
  def testAcquireNodeNoneAdded(self):
347
    self.assertFalse(compat.any(map(operator.attrgetter("added"),
348
                                    self.config["nodes"])))
349

    
350
    # First call must return master node
351
    node = qa_config.AcquireNode(_cfg=self.config)
352
    self.assertEqual(node, self.config.GetMasterNode())
353

    
354
    # Next call with exclusion list fails
355
    self.assertRaises(qa_error.OutOfNodesError, qa_config.AcquireNode,
356
                      exclude=[node], _cfg=self.config)
357

    
358
  def testAcquireNodeTooMany(self):
359
    # Mark all nodes as marked (master excluded)
360
    for node in self.config["nodes"]:
361
      if node != self.config.GetMasterNode():
362
        node.MarkAdded()
363

    
364
    nodecount = len(self.config["nodes"])
365

    
366
    self.assertTrue(nodecount > 1)
367

    
368
    acquired = []
369

    
370
    for _ in range(nodecount):
371
      node = qa_config.AcquireNode(exclude=acquired, _cfg=self.config)
372
      if node == self.config.GetMasterNode():
373
        self.assertFalse(node.added)
374
      else:
375
        self.assertTrue(node.added)
376
      self.assertEqual(node.use_count, 1)
377
      acquired.append(node)
378

    
379
    self.assertRaises(qa_error.OutOfNodesError, qa_config.AcquireNode,
380
                      exclude=acquired, _cfg=self.config)
381

    
382
  def testAcquireNodeOrder(self):
383
    # Mark all nodes as marked (master excluded)
384
    for node in self.config["nodes"]:
385
      if node != self.config.GetMasterNode():
386
        node.MarkAdded()
387

    
388
    nodecount = len(self.config["nodes"])
389

    
390
    for iterations in [0, 1, 3, 100, 127, 7964]:
391
      acquired = []
392

    
393
      for i in range(iterations):
394
        node = qa_config.AcquireNode(_cfg=self.config)
395
        self.assertTrue(node.use_count > 0)
396
        self.assertEqual(node.use_count, (i / nodecount + 1))
397
        acquired.append((node.use_count, node.primary, node))
398

    
399
      # Check if returned nodes were in correct order
400
      key_fn = lambda (a, b, c): (a, utils.NiceSortKey(b), c)
401
      self.assertEqual(acquired, sorted(acquired, key=key_fn))
402

    
403
      # Release previously acquired nodes
404
      qa_config.ReleaseManyNodes(map(operator.itemgetter(2), acquired))
405

    
406
      # Check if nodes were actually released
407
      for node in self.config["nodes"]:
408
        self.assertEqual(node.use_count, 0)
409
        self.assertTrue(node.added or node == self.config.GetMasterNode())
410

    
411

    
412
class TestRepresentation(unittest.TestCase):
413
  def _Check(self, target, part):
414
    self.assertTrue(part in repr(target).split())
415

    
416
  def testQaInstance(self):
417
    inst = qa_config._QaInstance("inst1.example.com", [])
418
    self._Check(inst, "name=inst1.example.com")
419
    self._Check(inst, "nicmac=[]")
420

    
421
    # Default values
422
    self._Check(inst, "disk_template=None")
423
    self._Check(inst, "used=None")
424

    
425
    # Use instance
426
    inst.Use()
427
    self._Check(inst, "used=True")
428

    
429
    # Disk template
430
    inst.SetDiskTemplate(constants.DT_DRBD8)
431
    self._Check(inst, "disk_template=%s" % constants.DT_DRBD8)
432

    
433
    # Release instance
434
    inst.Release()
435
    self._Check(inst, "used=False")
436
    self._Check(inst, "disk_template=None")
437

    
438
  def testQaNode(self):
439
    node = qa_config._QaNode("primary.example.com", "192.0.2.1")
440
    self._Check(node, "primary=primary.example.com")
441
    self._Check(node, "secondary=192.0.2.1")
442
    self._Check(node, "added=False")
443
    self._Check(node, "use_count=0")
444

    
445
    # Mark as added
446
    node.MarkAdded()
447
    self._Check(node, "added=True")
448

    
449
    # Use node
450
    for i in range(1, 5):
451
      node.Use()
452
      self._Check(node, "use_count=%s" % i)
453

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

    
459
    self._Check(node, "use_count=0")
460

    
461
    # Mark as added
462
    node.MarkRemoved()
463
    self._Check(node, "added=False")
464

    
465

    
466
if __name__ == "__main__":
467
  testutils.GanetiTestProgram()