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() |