root / test / ganeti.cli_unittest.py @ 05484a24
History | View | Annotate | Download (31.2 kB)
1 |
#!/usr/bin/python
|
---|---|
2 |
#
|
3 |
|
4 |
# Copyright (C) 2008, 2011 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 unittesting the cli module"""
|
23 |
|
24 |
import unittest |
25 |
from cStringIO import StringIO |
26 |
|
27 |
import ganeti |
28 |
import testutils |
29 |
|
30 |
from ganeti import constants |
31 |
from ganeti import cli |
32 |
from ganeti import errors |
33 |
from ganeti import utils |
34 |
from ganeti import objects |
35 |
from ganeti import qlang |
36 |
from ganeti.errors import OpPrereqError, ParameterError |
37 |
|
38 |
|
39 |
class TestParseTimespec(unittest.TestCase): |
40 |
"""Testing case for ParseTimespec"""
|
41 |
|
42 |
def testValidTimes(self): |
43 |
"""Test valid timespecs"""
|
44 |
test_data = [ |
45 |
('1s', 1), |
46 |
('1', 1), |
47 |
('1m', 60), |
48 |
('1h', 60 * 60), |
49 |
('1d', 60 * 60 * 24), |
50 |
('1w', 60 * 60 * 24 * 7), |
51 |
('4h', 4 * 60 * 60), |
52 |
('61m', 61 * 60), |
53 |
] |
54 |
for value, expected_result in test_data: |
55 |
self.failUnlessEqual(cli.ParseTimespec(value), expected_result)
|
56 |
|
57 |
def testInvalidTime(self): |
58 |
"""Test invalid timespecs"""
|
59 |
test_data = [ |
60 |
'1y',
|
61 |
'',
|
62 |
'aaa',
|
63 |
's',
|
64 |
] |
65 |
for value in test_data: |
66 |
self.failUnlessRaises(OpPrereqError, cli.ParseTimespec, value)
|
67 |
|
68 |
|
69 |
class TestSplitKeyVal(unittest.TestCase): |
70 |
"""Testing case for cli._SplitKeyVal"""
|
71 |
DATA = "a=b,c,no_d,-e"
|
72 |
RESULT = {"a": "b", "c": True, "d": False, "e": None} |
73 |
|
74 |
def testSplitKeyVal(self): |
75 |
"""Test splitting"""
|
76 |
self.failUnlessEqual(cli._SplitKeyVal("option", self.DATA), self.RESULT) |
77 |
|
78 |
def testDuplicateParam(self): |
79 |
"""Test duplicate parameters"""
|
80 |
for data in ("a=1,a=2", "a,no_a"): |
81 |
self.failUnlessRaises(ParameterError, cli._SplitKeyVal,
|
82 |
"option", data)
|
83 |
|
84 |
def testEmptyData(self): |
85 |
"""Test how we handle splitting an empty string"""
|
86 |
self.failUnlessEqual(cli._SplitKeyVal("option", ""), {}) |
87 |
|
88 |
class TestIdentKeyVal(unittest.TestCase): |
89 |
"""Testing case for cli.check_ident_key_val"""
|
90 |
|
91 |
def testIdentKeyVal(self): |
92 |
"""Test identkeyval"""
|
93 |
def cikv(value): |
94 |
return cli.check_ident_key_val("option", "opt", value) |
95 |
|
96 |
self.assertEqual(cikv("foo:bar"), ("foo", {"bar": True})) |
97 |
self.assertEqual(cikv("foo:bar=baz"), ("foo", {"bar": "baz"})) |
98 |
self.assertEqual(cikv("bar:b=c,c=a"), ("bar", {"b": "c", "c": "a"})) |
99 |
self.assertEqual(cikv("no_bar"), ("bar", False)) |
100 |
self.assertRaises(ParameterError, cikv, "no_bar:foo") |
101 |
self.assertRaises(ParameterError, cikv, "no_bar:foo=baz") |
102 |
self.assertEqual(cikv("-foo"), ("foo", None)) |
103 |
self.assertRaises(ParameterError, cikv, "-foo:a=c") |
104 |
|
105 |
|
106 |
class TestToStream(unittest.TestCase): |
107 |
"""Test the ToStream functions"""
|
108 |
|
109 |
def testBasic(self): |
110 |
for data in ["foo", |
111 |
"foo %s",
|
112 |
"foo %(test)s",
|
113 |
"foo %s %s",
|
114 |
"",
|
115 |
]: |
116 |
buf = StringIO() |
117 |
cli._ToStream(buf, data) |
118 |
self.failUnlessEqual(buf.getvalue(), data+'\n') |
119 |
|
120 |
def testParams(self): |
121 |
buf = StringIO() |
122 |
cli._ToStream(buf, "foo %s", 1) |
123 |
self.failUnlessEqual(buf.getvalue(), "foo 1\n") |
124 |
buf = StringIO() |
125 |
cli._ToStream(buf, "foo %s", (15,16)) |
126 |
self.failUnlessEqual(buf.getvalue(), "foo (15, 16)\n") |
127 |
buf = StringIO() |
128 |
cli._ToStream(buf, "foo %s %s", "a", "b") |
129 |
self.failUnlessEqual(buf.getvalue(), "foo a b\n") |
130 |
|
131 |
|
132 |
class TestGenerateTable(unittest.TestCase): |
133 |
HEADERS = dict([("f%s" % i, "Field%s" % i) for i in range(5)]) |
134 |
|
135 |
FIELDS1 = ["f1", "f2"] |
136 |
DATA1 = [ |
137 |
["abc", 1234], |
138 |
["foobar", 56], |
139 |
["b", -14], |
140 |
] |
141 |
|
142 |
def _test(self, headers, fields, separator, data, |
143 |
numfields, unitfields, units, expected): |
144 |
table = cli.GenerateTable(headers, fields, separator, data, |
145 |
numfields=numfields, unitfields=unitfields, |
146 |
units=units) |
147 |
self.assertEqual(table, expected)
|
148 |
|
149 |
def testPlain(self): |
150 |
exp = [ |
151 |
"Field1 Field2",
|
152 |
"abc 1234",
|
153 |
"foobar 56",
|
154 |
"b -14",
|
155 |
] |
156 |
self._test(self.HEADERS, self.FIELDS1, None, self.DATA1, |
157 |
None, None, "m", exp) |
158 |
|
159 |
def testNoFields(self): |
160 |
self._test(self.HEADERS, [], None, [[], []], |
161 |
None, None, "m", ["", "", ""]) |
162 |
self._test(None, [], None, [[], []], |
163 |
None, None, "m", ["", ""]) |
164 |
|
165 |
def testSeparator(self): |
166 |
for sep in ["#", ":", ",", "^", "!", "%", "|", "###", "%%", "!!!", "||"]: |
167 |
exp = [ |
168 |
"Field1%sField2" % sep,
|
169 |
"abc%s1234" % sep,
|
170 |
"foobar%s56" % sep,
|
171 |
"b%s-14" % sep,
|
172 |
] |
173 |
self._test(self.HEADERS, self.FIELDS1, sep, self.DATA1, |
174 |
None, None, "m", exp) |
175 |
|
176 |
def testNoHeader(self): |
177 |
exp = [ |
178 |
"abc 1234",
|
179 |
"foobar 56",
|
180 |
"b -14",
|
181 |
] |
182 |
self._test(None, self.FIELDS1, None, self.DATA1, |
183 |
None, None, "m", exp) |
184 |
|
185 |
def testUnknownField(self): |
186 |
headers = { |
187 |
"f1": "Field1", |
188 |
} |
189 |
exp = [ |
190 |
"Field1 UNKNOWN",
|
191 |
"abc 1234",
|
192 |
"foobar 56",
|
193 |
"b -14",
|
194 |
] |
195 |
self._test(headers, ["f1", "UNKNOWN"], None, self.DATA1, |
196 |
None, None, "m", exp) |
197 |
|
198 |
def testNumfields(self): |
199 |
fields = ["f1", "f2", "f3"] |
200 |
data = [ |
201 |
["abc", 1234, 0], |
202 |
["foobar", 56, 3], |
203 |
["b", -14, "-"], |
204 |
] |
205 |
exp = [ |
206 |
"Field1 Field2 Field3",
|
207 |
"abc 1234 0",
|
208 |
"foobar 56 3",
|
209 |
"b -14 -",
|
210 |
] |
211 |
self._test(self.HEADERS, fields, None, data, |
212 |
["f2", "f3"], None, "m", exp) |
213 |
|
214 |
def testUnitfields(self): |
215 |
expnosep = [ |
216 |
"Field1 Field2 Field3",
|
217 |
"abc 1234 0M",
|
218 |
"foobar 56 3M",
|
219 |
"b -14 -",
|
220 |
] |
221 |
|
222 |
expsep = [ |
223 |
"Field1:Field2:Field3",
|
224 |
"abc:1234:0M",
|
225 |
"foobar:56:3M",
|
226 |
"b:-14:-",
|
227 |
] |
228 |
|
229 |
for sep, expected in [(None, expnosep), (":", expsep)]: |
230 |
fields = ["f1", "f2", "f3"] |
231 |
data = [ |
232 |
["abc", 1234, 0], |
233 |
["foobar", 56, 3], |
234 |
["b", -14, "-"], |
235 |
] |
236 |
self._test(self.HEADERS, fields, sep, data, |
237 |
["f2", "f3"], ["f3"], "h", expected) |
238 |
|
239 |
def testUnusual(self): |
240 |
data = [ |
241 |
["%", "xyz"], |
242 |
["%%", "abc"], |
243 |
] |
244 |
exp = [ |
245 |
"Field1 Field2",
|
246 |
"% xyz",
|
247 |
"%% abc",
|
248 |
] |
249 |
self._test(self.HEADERS, ["f1", "f2"], None, data, |
250 |
None, None, "m", exp) |
251 |
|
252 |
|
253 |
class TestFormatQueryResult(unittest.TestCase): |
254 |
def test(self): |
255 |
fields = [ |
256 |
objects.QueryFieldDefinition(name="name", title="Name", |
257 |
kind=constants.QFT_TEXT), |
258 |
objects.QueryFieldDefinition(name="size", title="Size", |
259 |
kind=constants.QFT_NUMBER), |
260 |
objects.QueryFieldDefinition(name="act", title="Active", |
261 |
kind=constants.QFT_BOOL), |
262 |
objects.QueryFieldDefinition(name="mem", title="Memory", |
263 |
kind=constants.QFT_UNIT), |
264 |
objects.QueryFieldDefinition(name="other", title="SomeList", |
265 |
kind=constants.QFT_OTHER), |
266 |
] |
267 |
|
268 |
response = objects.QueryResponse(fields=fields, data=[ |
269 |
[(constants.RS_NORMAL, "nodeA"), (constants.RS_NORMAL, 128), |
270 |
(constants.RS_NORMAL, False), (constants.RS_NORMAL, 1468006), |
271 |
(constants.RS_NORMAL, [])], |
272 |
[(constants.RS_NORMAL, "other"), (constants.RS_NORMAL, 512), |
273 |
(constants.RS_NORMAL, True), (constants.RS_NORMAL, 16), |
274 |
(constants.RS_NORMAL, [1, 2, 3])], |
275 |
[(constants.RS_NORMAL, "xyz"), (constants.RS_NORMAL, 1024), |
276 |
(constants.RS_NORMAL, True), (constants.RS_NORMAL, 4096), |
277 |
(constants.RS_NORMAL, [{}, {}])], |
278 |
]) |
279 |
|
280 |
self.assertEqual(cli.FormatQueryResult(response, unit="h", header=True), |
281 |
(cli.QR_NORMAL, [ |
282 |
"Name Size Active Memory SomeList",
|
283 |
"nodeA 128 N 1.4T []",
|
284 |
"other 512 Y 16M [1, 2, 3]",
|
285 |
"xyz 1024 Y 4.0G [{}, {}]",
|
286 |
])) |
287 |
|
288 |
def testTimestampAndUnit(self): |
289 |
fields = [ |
290 |
objects.QueryFieldDefinition(name="name", title="Name", |
291 |
kind=constants.QFT_TEXT), |
292 |
objects.QueryFieldDefinition(name="size", title="Size", |
293 |
kind=constants.QFT_UNIT), |
294 |
objects.QueryFieldDefinition(name="mtime", title="ModTime", |
295 |
kind=constants.QFT_TIMESTAMP), |
296 |
] |
297 |
|
298 |
response = objects.QueryResponse(fields=fields, data=[ |
299 |
[(constants.RS_NORMAL, "a"), (constants.RS_NORMAL, 1024), |
300 |
(constants.RS_NORMAL, 0)],
|
301 |
[(constants.RS_NORMAL, "b"), (constants.RS_NORMAL, 144996), |
302 |
(constants.RS_NORMAL, 1291746295)],
|
303 |
]) |
304 |
|
305 |
self.assertEqual(cli.FormatQueryResult(response, unit="m", header=True), |
306 |
(cli.QR_NORMAL, [ |
307 |
"Name Size ModTime",
|
308 |
"a 1024 %s" % utils.FormatTime(0), |
309 |
"b 144996 %s" % utils.FormatTime(1291746295), |
310 |
])) |
311 |
|
312 |
def testOverride(self): |
313 |
fields = [ |
314 |
objects.QueryFieldDefinition(name="name", title="Name", |
315 |
kind=constants.QFT_TEXT), |
316 |
objects.QueryFieldDefinition(name="cust", title="Custom", |
317 |
kind=constants.QFT_OTHER), |
318 |
objects.QueryFieldDefinition(name="xt", title="XTime", |
319 |
kind=constants.QFT_TIMESTAMP), |
320 |
] |
321 |
|
322 |
response = objects.QueryResponse(fields=fields, data=[ |
323 |
[(constants.RS_NORMAL, "x"), (constants.RS_NORMAL, ["a", "b", "c"]), |
324 |
(constants.RS_NORMAL, 1234)],
|
325 |
[(constants.RS_NORMAL, "y"), (constants.RS_NORMAL, range(10)), |
326 |
(constants.RS_NORMAL, 1291746295)],
|
327 |
]) |
328 |
|
329 |
override = { |
330 |
"cust": (utils.CommaJoin, False), |
331 |
"xt": (hex, True), |
332 |
} |
333 |
|
334 |
self.assertEqual(cli.FormatQueryResult(response, unit="h", header=True, |
335 |
format_override=override), |
336 |
(cli.QR_NORMAL, [ |
337 |
"Name Custom XTime",
|
338 |
"x a, b, c 0x4d2",
|
339 |
"y 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 0x4cfe7bf7",
|
340 |
])) |
341 |
|
342 |
def testSeparator(self): |
343 |
fields = [ |
344 |
objects.QueryFieldDefinition(name="name", title="Name", |
345 |
kind=constants.QFT_TEXT), |
346 |
objects.QueryFieldDefinition(name="count", title="Count", |
347 |
kind=constants.QFT_NUMBER), |
348 |
objects.QueryFieldDefinition(name="desc", title="Description", |
349 |
kind=constants.QFT_TEXT), |
350 |
] |
351 |
|
352 |
response = objects.QueryResponse(fields=fields, data=[ |
353 |
[(constants.RS_NORMAL, "instance1.example.com"),
|
354 |
(constants.RS_NORMAL, 21125), (constants.RS_NORMAL, "Hello World!")], |
355 |
[(constants.RS_NORMAL, "mail.other.net"),
|
356 |
(constants.RS_NORMAL, -9000), (constants.RS_NORMAL, "a,b,c")], |
357 |
]) |
358 |
|
359 |
for sep in [":", "|", "#", "|||", "###", "@@@", "@#@"]: |
360 |
for header in [None, "Name%sCount%sDescription" % (sep, sep)]: |
361 |
exp = [] |
362 |
if header:
|
363 |
exp.append(header) |
364 |
exp.extend([ |
365 |
"instance1.example.com%s21125%sHello World!" % (sep, sep),
|
366 |
"mail.other.net%s-9000%sa,b,c" % (sep, sep),
|
367 |
]) |
368 |
|
369 |
self.assertEqual(cli.FormatQueryResult(response, separator=sep,
|
370 |
header=bool(header)),
|
371 |
(cli.QR_NORMAL, exp)) |
372 |
|
373 |
def testStatusWithUnknown(self): |
374 |
fields = [ |
375 |
objects.QueryFieldDefinition(name="id", title="ID", |
376 |
kind=constants.QFT_NUMBER), |
377 |
objects.QueryFieldDefinition(name="unk", title="unk", |
378 |
kind=constants.QFT_UNKNOWN), |
379 |
objects.QueryFieldDefinition(name="unavail", title="Unavail", |
380 |
kind=constants.QFT_BOOL), |
381 |
objects.QueryFieldDefinition(name="nodata", title="NoData", |
382 |
kind=constants.QFT_TEXT), |
383 |
objects.QueryFieldDefinition(name="offline", title="OffLine", |
384 |
kind=constants.QFT_TEXT), |
385 |
] |
386 |
|
387 |
response = objects.QueryResponse(fields=fields, data=[ |
388 |
[(constants.RS_NORMAL, 1), (constants.RS_UNKNOWN, None), |
389 |
(constants.RS_NORMAL, False), (constants.RS_NORMAL, ""), |
390 |
(constants.RS_OFFLINE, None)],
|
391 |
[(constants.RS_NORMAL, 2), (constants.RS_UNKNOWN, None), |
392 |
(constants.RS_NODATA, None), (constants.RS_NORMAL, "x"), |
393 |
(constants.RS_OFFLINE, None)],
|
394 |
[(constants.RS_NORMAL, 3), (constants.RS_UNKNOWN, None), |
395 |
(constants.RS_NORMAL, False), (constants.RS_UNAVAIL, None), |
396 |
(constants.RS_OFFLINE, None)],
|
397 |
]) |
398 |
|
399 |
self.assertEqual(cli.FormatQueryResult(response, header=True, |
400 |
separator="|", verbose=True), |
401 |
(cli.QR_UNKNOWN, [ |
402 |
"ID|unk|Unavail|NoData|OffLine",
|
403 |
"1|(unknown)|N||(offline)",
|
404 |
"2|(unknown)|(nodata)|x|(offline)",
|
405 |
"3|(unknown)|N|(unavail)|(offline)",
|
406 |
])) |
407 |
self.assertEqual(cli.FormatQueryResult(response, header=True, |
408 |
separator="|", verbose=False), |
409 |
(cli.QR_UNKNOWN, [ |
410 |
"ID|unk|Unavail|NoData|OffLine",
|
411 |
"1|??|N||*",
|
412 |
"2|??|?|x|*",
|
413 |
"3|??|N|-|*",
|
414 |
])) |
415 |
|
416 |
def testNoData(self): |
417 |
fields = [ |
418 |
objects.QueryFieldDefinition(name="id", title="ID", |
419 |
kind=constants.QFT_NUMBER), |
420 |
objects.QueryFieldDefinition(name="name", title="Name", |
421 |
kind=constants.QFT_TEXT), |
422 |
] |
423 |
|
424 |
response = objects.QueryResponse(fields=fields, data=[]) |
425 |
|
426 |
self.assertEqual(cli.FormatQueryResult(response, header=True), |
427 |
(cli.QR_NORMAL, ["ID Name"]))
|
428 |
|
429 |
def testNoDataWithUnknown(self): |
430 |
fields = [ |
431 |
objects.QueryFieldDefinition(name="id", title="ID", |
432 |
kind=constants.QFT_NUMBER), |
433 |
objects.QueryFieldDefinition(name="unk", title="unk", |
434 |
kind=constants.QFT_UNKNOWN), |
435 |
] |
436 |
|
437 |
response = objects.QueryResponse(fields=fields, data=[]) |
438 |
|
439 |
self.assertEqual(cli.FormatQueryResult(response, header=False), |
440 |
(cli.QR_UNKNOWN, [])) |
441 |
|
442 |
def testStatus(self): |
443 |
fields = [ |
444 |
objects.QueryFieldDefinition(name="id", title="ID", |
445 |
kind=constants.QFT_NUMBER), |
446 |
objects.QueryFieldDefinition(name="unavail", title="Unavail", |
447 |
kind=constants.QFT_BOOL), |
448 |
objects.QueryFieldDefinition(name="nodata", title="NoData", |
449 |
kind=constants.QFT_TEXT), |
450 |
objects.QueryFieldDefinition(name="offline", title="OffLine", |
451 |
kind=constants.QFT_TEXT), |
452 |
] |
453 |
|
454 |
response = objects.QueryResponse(fields=fields, data=[ |
455 |
[(constants.RS_NORMAL, 1), (constants.RS_NORMAL, False), |
456 |
(constants.RS_NORMAL, ""), (constants.RS_OFFLINE, None)], |
457 |
[(constants.RS_NORMAL, 2), (constants.RS_NODATA, None), |
458 |
(constants.RS_NORMAL, "x"), (constants.RS_NORMAL, "abc")], |
459 |
[(constants.RS_NORMAL, 3), (constants.RS_NORMAL, False), |
460 |
(constants.RS_UNAVAIL, None), (constants.RS_OFFLINE, None)], |
461 |
]) |
462 |
|
463 |
self.assertEqual(cli.FormatQueryResult(response, header=False, |
464 |
separator="|", verbose=True), |
465 |
(cli.QR_INCOMPLETE, [ |
466 |
"1|N||(offline)",
|
467 |
"2|(nodata)|x|abc",
|
468 |
"3|N|(unavail)|(offline)",
|
469 |
])) |
470 |
self.assertEqual(cli.FormatQueryResult(response, header=False, |
471 |
separator="|", verbose=False), |
472 |
(cli.QR_INCOMPLETE, [ |
473 |
"1|N||*",
|
474 |
"2|?|x|abc",
|
475 |
"3|N|-|*",
|
476 |
])) |
477 |
|
478 |
def testInvalidFieldType(self): |
479 |
fields = [ |
480 |
objects.QueryFieldDefinition(name="x", title="x", |
481 |
kind="#some#other#type"),
|
482 |
] |
483 |
|
484 |
response = objects.QueryResponse(fields=fields, data=[]) |
485 |
|
486 |
self.assertRaises(NotImplementedError, cli.FormatQueryResult, response) |
487 |
|
488 |
def testInvalidFieldStatus(self): |
489 |
fields = [ |
490 |
objects.QueryFieldDefinition(name="x", title="x", |
491 |
kind=constants.QFT_TEXT), |
492 |
] |
493 |
|
494 |
response = objects.QueryResponse(fields=fields, data=[[(-1, None)]]) |
495 |
self.assertRaises(NotImplementedError, cli.FormatQueryResult, response) |
496 |
|
497 |
response = objects.QueryResponse(fields=fields, data=[[(-1, "x")]]) |
498 |
self.assertRaises(AssertionError, cli.FormatQueryResult, response) |
499 |
|
500 |
def testEmptyFieldTitle(self): |
501 |
fields = [ |
502 |
objects.QueryFieldDefinition(name="x", title="", |
503 |
kind=constants.QFT_TEXT), |
504 |
] |
505 |
|
506 |
response = objects.QueryResponse(fields=fields, data=[]) |
507 |
self.assertRaises(AssertionError, cli.FormatQueryResult, response) |
508 |
|
509 |
|
510 |
class _MockJobPollCb(cli.JobPollCbBase, cli.JobPollReportCbBase): |
511 |
def __init__(self, tc, job_id): |
512 |
self.tc = tc
|
513 |
self.job_id = job_id
|
514 |
self._wfjcr = []
|
515 |
self._jobstatus = []
|
516 |
self._expect_notchanged = False |
517 |
self._expect_log = []
|
518 |
|
519 |
def CheckEmpty(self): |
520 |
self.tc.assertFalse(self._wfjcr) |
521 |
self.tc.assertFalse(self._jobstatus) |
522 |
self.tc.assertFalse(self._expect_notchanged) |
523 |
self.tc.assertFalse(self._expect_log) |
524 |
|
525 |
def AddWfjcResult(self, *args): |
526 |
self._wfjcr.append(args)
|
527 |
|
528 |
def AddQueryJobsResult(self, *args): |
529 |
self._jobstatus.append(args)
|
530 |
|
531 |
def WaitForJobChangeOnce(self, job_id, fields, |
532 |
prev_job_info, prev_log_serial): |
533 |
self.tc.assertEqual(job_id, self.job_id) |
534 |
self.tc.assertEqualValues(fields, ["status"]) |
535 |
self.tc.assertFalse(self._expect_notchanged) |
536 |
self.tc.assertFalse(self._expect_log) |
537 |
|
538 |
(exp_prev_job_info, exp_prev_log_serial, result) = self._wfjcr.pop(0) |
539 |
self.tc.assertEqualValues(prev_job_info, exp_prev_job_info)
|
540 |
self.tc.assertEqual(prev_log_serial, exp_prev_log_serial)
|
541 |
|
542 |
if result == constants.JOB_NOTCHANGED:
|
543 |
self._expect_notchanged = True |
544 |
elif result:
|
545 |
(_, logmsgs) = result |
546 |
if logmsgs:
|
547 |
self._expect_log.extend(logmsgs)
|
548 |
|
549 |
return result
|
550 |
|
551 |
def QueryJobs(self, job_ids, fields): |
552 |
self.tc.assertEqual(job_ids, [self.job_id]) |
553 |
self.tc.assertEqualValues(fields, ["status", "opstatus", "opresult"]) |
554 |
self.tc.assertFalse(self._expect_notchanged) |
555 |
self.tc.assertFalse(self._expect_log) |
556 |
|
557 |
result = self._jobstatus.pop(0) |
558 |
self.tc.assertEqual(len(fields), len(result)) |
559 |
return [result]
|
560 |
|
561 |
def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg): |
562 |
self.tc.assertEqual(job_id, self.job_id) |
563 |
self.tc.assertEqualValues((serial, timestamp, log_type, log_msg),
|
564 |
self._expect_log.pop(0)) |
565 |
|
566 |
def ReportNotChanged(self, job_id, status): |
567 |
self.tc.assertEqual(job_id, self.job_id) |
568 |
self.tc.assert_(self._expect_notchanged) |
569 |
self._expect_notchanged = False |
570 |
|
571 |
|
572 |
class TestGenericPollJob(testutils.GanetiTestCase): |
573 |
def testSuccessWithLog(self): |
574 |
job_id = 29609
|
575 |
cbs = _MockJobPollCb(self, job_id)
|
576 |
|
577 |
cbs.AddWfjcResult(None, None, constants.JOB_NOTCHANGED) |
578 |
|
579 |
cbs.AddWfjcResult(None, None, |
580 |
((constants.JOB_STATUS_QUEUED, ), None))
|
581 |
|
582 |
cbs.AddWfjcResult((constants.JOB_STATUS_QUEUED, ), None,
|
583 |
constants.JOB_NOTCHANGED) |
584 |
|
585 |
cbs.AddWfjcResult((constants.JOB_STATUS_QUEUED, ), None,
|
586 |
((constants.JOB_STATUS_RUNNING, ), |
587 |
[(1, utils.SplitTime(1273491611.0), |
588 |
constants.ELOG_MESSAGE, "Step 1"),
|
589 |
(2, utils.SplitTime(1273491615.9), |
590 |
constants.ELOG_MESSAGE, "Step 2"),
|
591 |
(3, utils.SplitTime(1273491625.02), |
592 |
constants.ELOG_MESSAGE, "Step 3"),
|
593 |
(4, utils.SplitTime(1273491635.05), |
594 |
constants.ELOG_MESSAGE, "Step 4"),
|
595 |
(37, utils.SplitTime(1273491645.0), |
596 |
constants.ELOG_MESSAGE, "Step 5"),
|
597 |
(203, utils.SplitTime(127349155.0), |
598 |
constants.ELOG_MESSAGE, "Step 6")]))
|
599 |
|
600 |
cbs.AddWfjcResult((constants.JOB_STATUS_RUNNING, ), 203,
|
601 |
((constants.JOB_STATUS_RUNNING, ), |
602 |
[(300, utils.SplitTime(1273491711.01), |
603 |
constants.ELOG_MESSAGE, "Step X"),
|
604 |
(302, utils.SplitTime(1273491815.8), |
605 |
constants.ELOG_MESSAGE, "Step Y"),
|
606 |
(303, utils.SplitTime(1273491925.32), |
607 |
constants.ELOG_MESSAGE, "Step Z")]))
|
608 |
|
609 |
cbs.AddWfjcResult((constants.JOB_STATUS_RUNNING, ), 303,
|
610 |
((constants.JOB_STATUS_SUCCESS, ), None))
|
611 |
|
612 |
cbs.AddQueryJobsResult(constants.JOB_STATUS_SUCCESS, |
613 |
[constants.OP_STATUS_SUCCESS, |
614 |
constants.OP_STATUS_SUCCESS], |
615 |
["Hello World", "Foo man bar"]) |
616 |
|
617 |
self.assertEqual(["Hello World", "Foo man bar"], |
618 |
cli.GenericPollJob(job_id, cbs, cbs)) |
619 |
cbs.CheckEmpty() |
620 |
|
621 |
def testJobLost(self): |
622 |
job_id = 13746
|
623 |
|
624 |
cbs = _MockJobPollCb(self, job_id)
|
625 |
cbs.AddWfjcResult(None, None, constants.JOB_NOTCHANGED) |
626 |
cbs.AddWfjcResult(None, None, None) |
627 |
self.assertRaises(errors.JobLost, cli.GenericPollJob, job_id, cbs, cbs)
|
628 |
cbs.CheckEmpty() |
629 |
|
630 |
def testError(self): |
631 |
job_id = 31088
|
632 |
|
633 |
cbs = _MockJobPollCb(self, job_id)
|
634 |
cbs.AddWfjcResult(None, None, constants.JOB_NOTCHANGED) |
635 |
cbs.AddWfjcResult(None, None, ((constants.JOB_STATUS_ERROR, ), None)) |
636 |
cbs.AddQueryJobsResult(constants.JOB_STATUS_ERROR, |
637 |
[constants.OP_STATUS_SUCCESS, |
638 |
constants.OP_STATUS_ERROR], |
639 |
["Hello World", "Error code 123"]) |
640 |
self.assertRaises(errors.OpExecError, cli.GenericPollJob, job_id, cbs, cbs)
|
641 |
cbs.CheckEmpty() |
642 |
|
643 |
def testError2(self): |
644 |
job_id = 22235
|
645 |
|
646 |
cbs = _MockJobPollCb(self, job_id)
|
647 |
cbs.AddWfjcResult(None, None, ((constants.JOB_STATUS_ERROR, ), None)) |
648 |
encexc = errors.EncodeException(errors.LockError("problem"))
|
649 |
cbs.AddQueryJobsResult(constants.JOB_STATUS_ERROR, |
650 |
[constants.OP_STATUS_ERROR], [encexc]) |
651 |
self.assertRaises(errors.LockError, cli.GenericPollJob, job_id, cbs, cbs)
|
652 |
cbs.CheckEmpty() |
653 |
|
654 |
def testWeirdError(self): |
655 |
job_id = 28847
|
656 |
|
657 |
cbs = _MockJobPollCb(self, job_id)
|
658 |
cbs.AddWfjcResult(None, None, ((constants.JOB_STATUS_ERROR, ), None)) |
659 |
cbs.AddQueryJobsResult(constants.JOB_STATUS_ERROR, |
660 |
[constants.OP_STATUS_RUNNING, |
661 |
constants.OP_STATUS_RUNNING], |
662 |
[None, None]) |
663 |
self.assertRaises(errors.OpExecError, cli.GenericPollJob, job_id, cbs, cbs)
|
664 |
cbs.CheckEmpty() |
665 |
|
666 |
def testCancel(self): |
667 |
job_id = 4275
|
668 |
|
669 |
cbs = _MockJobPollCb(self, job_id)
|
670 |
cbs.AddWfjcResult(None, None, constants.JOB_NOTCHANGED) |
671 |
cbs.AddWfjcResult(None, None, ((constants.JOB_STATUS_CANCELING, ), None)) |
672 |
cbs.AddQueryJobsResult(constants.JOB_STATUS_CANCELING, |
673 |
[constants.OP_STATUS_CANCELING, |
674 |
constants.OP_STATUS_CANCELING], |
675 |
[None, None]) |
676 |
self.assertRaises(errors.OpExecError, cli.GenericPollJob, job_id, cbs, cbs)
|
677 |
cbs.CheckEmpty() |
678 |
|
679 |
|
680 |
class TestFormatLogMessage(unittest.TestCase): |
681 |
def test(self): |
682 |
self.assertEqual(cli.FormatLogMessage(constants.ELOG_MESSAGE,
|
683 |
"Hello World"),
|
684 |
"Hello World")
|
685 |
self.assertRaises(TypeError, cli.FormatLogMessage, |
686 |
constants.ELOG_MESSAGE, [1, 2, 3]) |
687 |
|
688 |
self.assert_(cli.FormatLogMessage("some other type", (1, 2, 3))) |
689 |
|
690 |
|
691 |
class TestParseFields(unittest.TestCase): |
692 |
def test(self): |
693 |
self.assertEqual(cli.ParseFields(None, []), []) |
694 |
self.assertEqual(cli.ParseFields("name,foo,hello", []), |
695 |
["name", "foo", "hello"]) |
696 |
self.assertEqual(cli.ParseFields(None, ["def", "ault", "fields", "here"]), |
697 |
["def", "ault", "fields", "here"]) |
698 |
self.assertEqual(cli.ParseFields("name,foo", ["def", "ault"]), |
699 |
["name", "foo"]) |
700 |
self.assertEqual(cli.ParseFields("+name,foo", ["def", "ault"]), |
701 |
["def", "ault", "name", "foo"]) |
702 |
|
703 |
|
704 |
class TestConstants(unittest.TestCase): |
705 |
def testPriority(self): |
706 |
self.assertEqual(set(cli._PRIONAME_TO_VALUE.values()), |
707 |
set(constants.OP_PRIO_SUBMIT_VALID))
|
708 |
self.assertEqual(list(value for _, value in cli._PRIORITY_NAMES), |
709 |
sorted(constants.OP_PRIO_SUBMIT_VALID, reverse=True)) |
710 |
|
711 |
|
712 |
class TestParseNicOption(unittest.TestCase): |
713 |
def test(self): |
714 |
self.assertEqual(cli.ParseNicOption([("0", { "link": "eth0", })]), |
715 |
[{ "link": "eth0", }]) |
716 |
self.assertEqual(cli.ParseNicOption([("5", { "ip": "192.0.2.7", })]), |
717 |
[{}, {}, {}, {}, {}, { "ip": "192.0.2.7", }]) |
718 |
|
719 |
def testErrors(self): |
720 |
for i in [None, "", "abc", "zero", "Hello World", "\0", []]: |
721 |
self.assertRaises(errors.OpPrereqError, cli.ParseNicOption,
|
722 |
[(i, { "link": "eth0", })]) |
723 |
self.assertRaises(errors.OpPrereqError, cli.ParseNicOption,
|
724 |
[("0", i)])
|
725 |
|
726 |
self.assertRaises(errors.TypeEnforcementError, cli.ParseNicOption,
|
727 |
[(0, { True: False, })]) |
728 |
|
729 |
self.assertRaises(errors.TypeEnforcementError, cli.ParseNicOption,
|
730 |
[(3, { "mode": [], })]) |
731 |
|
732 |
|
733 |
class TestFormatResultError(unittest.TestCase): |
734 |
def testNormal(self): |
735 |
for verbose in [False, True]: |
736 |
self.assertRaises(AssertionError, cli.FormatResultError, |
737 |
constants.RS_NORMAL, verbose) |
738 |
|
739 |
def testUnknown(self): |
740 |
for verbose in [False, True]: |
741 |
self.assertRaises(NotImplementedError, cli.FormatResultError, |
742 |
"#some!other!status#", verbose)
|
743 |
|
744 |
def test(self): |
745 |
for status in constants.RS_ALL: |
746 |
if status == constants.RS_NORMAL:
|
747 |
continue
|
748 |
|
749 |
self.assertNotEqual(cli.FormatResultError(status, False), |
750 |
cli.FormatResultError(status, True))
|
751 |
|
752 |
result = cli.FormatResultError(status, True)
|
753 |
self.assertTrue(result.startswith("(")) |
754 |
self.assertTrue(result.endswith(")")) |
755 |
|
756 |
|
757 |
class TestGetOnlineNodes(unittest.TestCase): |
758 |
class _FakeClient: |
759 |
def __init__(self): |
760 |
self._query = []
|
761 |
|
762 |
def AddQueryResult(self, *args): |
763 |
self._query.append(args)
|
764 |
|
765 |
def CountPending(self): |
766 |
return len(self._query) |
767 |
|
768 |
def Query(self, res, fields, filter_): |
769 |
if res != constants.QR_NODE:
|
770 |
raise Exception("Querying wrong resource") |
771 |
|
772 |
(exp_fields, check_filter, result) = self._query.pop(0) |
773 |
|
774 |
if exp_fields != fields:
|
775 |
raise Exception("Expected fields %s, got %s" % (exp_fields, fields)) |
776 |
|
777 |
if not (filter_ is None or check_filter(filter_)): |
778 |
raise Exception("Filter doesn't match expectations") |
779 |
|
780 |
return objects.QueryResponse(fields=None, data=result) |
781 |
|
782 |
def testEmpty(self): |
783 |
cl = self._FakeClient()
|
784 |
|
785 |
cl.AddQueryResult(["name", "offline", "sip"], None, []) |
786 |
self.assertEqual(cli.GetOnlineNodes(None, cl=cl), []) |
787 |
self.assertEqual(cl.CountPending(), 0) |
788 |
|
789 |
def testNoSpecialFilter(self): |
790 |
cl = self._FakeClient()
|
791 |
|
792 |
cl.AddQueryResult(["name", "offline", "sip"], None, [ |
793 |
[(constants.RS_NORMAL, "master.example.com"),
|
794 |
(constants.RS_NORMAL, False),
|
795 |
(constants.RS_NORMAL, "192.0.2.1")],
|
796 |
[(constants.RS_NORMAL, "node2.example.com"),
|
797 |
(constants.RS_NORMAL, False),
|
798 |
(constants.RS_NORMAL, "192.0.2.2")],
|
799 |
]) |
800 |
self.assertEqual(cli.GetOnlineNodes(None, cl=cl), |
801 |
["master.example.com", "node2.example.com"]) |
802 |
self.assertEqual(cl.CountPending(), 0) |
803 |
|
804 |
def testNoMaster(self): |
805 |
cl = self._FakeClient()
|
806 |
|
807 |
def _CheckFilter(filter_): |
808 |
self.assertEqual(filter_, [qlang.OP_NOT, [qlang.OP_TRUE, "master"]]) |
809 |
return True |
810 |
|
811 |
cl.AddQueryResult(["name", "offline", "sip"], _CheckFilter, [ |
812 |
[(constants.RS_NORMAL, "node2.example.com"),
|
813 |
(constants.RS_NORMAL, False),
|
814 |
(constants.RS_NORMAL, "192.0.2.2")],
|
815 |
]) |
816 |
self.assertEqual(cli.GetOnlineNodes(None, cl=cl, filter_master=True), |
817 |
["node2.example.com"])
|
818 |
self.assertEqual(cl.CountPending(), 0) |
819 |
|
820 |
def testSecondaryIpAddress(self): |
821 |
cl = self._FakeClient()
|
822 |
|
823 |
cl.AddQueryResult(["name", "offline", "sip"], None, [ |
824 |
[(constants.RS_NORMAL, "master.example.com"),
|
825 |
(constants.RS_NORMAL, False),
|
826 |
(constants.RS_NORMAL, "192.0.2.1")],
|
827 |
[(constants.RS_NORMAL, "node2.example.com"),
|
828 |
(constants.RS_NORMAL, False),
|
829 |
(constants.RS_NORMAL, "192.0.2.2")],
|
830 |
]) |
831 |
self.assertEqual(cli.GetOnlineNodes(None, cl=cl, secondary_ips=True), |
832 |
["192.0.2.1", "192.0.2.2"]) |
833 |
self.assertEqual(cl.CountPending(), 0) |
834 |
|
835 |
def testNoMasterFilterNodeName(self): |
836 |
cl = self._FakeClient()
|
837 |
|
838 |
def _CheckFilter(filter_): |
839 |
self.assertEqual(filter_,
|
840 |
[qlang.OP_AND, |
841 |
[qlang.OP_OR] + [[qlang.OP_EQUAL, "name", name]
|
842 |
for name in ["node2", "node3"]], |
843 |
[qlang.OP_NOT, [qlang.OP_TRUE, "master"]]])
|
844 |
return True |
845 |
|
846 |
cl.AddQueryResult(["name", "offline", "sip"], _CheckFilter, [ |
847 |
[(constants.RS_NORMAL, "node2.example.com"),
|
848 |
(constants.RS_NORMAL, False),
|
849 |
(constants.RS_NORMAL, "192.0.2.12")],
|
850 |
[(constants.RS_NORMAL, "node3.example.com"),
|
851 |
(constants.RS_NORMAL, False),
|
852 |
(constants.RS_NORMAL, "192.0.2.13")],
|
853 |
]) |
854 |
self.assertEqual(cli.GetOnlineNodes(["node2", "node3"], cl=cl, |
855 |
secondary_ips=True, filter_master=True), |
856 |
["192.0.2.12", "192.0.2.13"]) |
857 |
self.assertEqual(cl.CountPending(), 0) |
858 |
|
859 |
def testOfflineNodes(self): |
860 |
cl = self._FakeClient()
|
861 |
|
862 |
cl.AddQueryResult(["name", "offline", "sip"], None, [ |
863 |
[(constants.RS_NORMAL, "master.example.com"),
|
864 |
(constants.RS_NORMAL, False),
|
865 |
(constants.RS_NORMAL, "192.0.2.1")],
|
866 |
[(constants.RS_NORMAL, "node2.example.com"),
|
867 |
(constants.RS_NORMAL, True),
|
868 |
(constants.RS_NORMAL, "192.0.2.2")],
|
869 |
[(constants.RS_NORMAL, "node3.example.com"),
|
870 |
(constants.RS_NORMAL, True),
|
871 |
(constants.RS_NORMAL, "192.0.2.3")],
|
872 |
]) |
873 |
self.assertEqual(cli.GetOnlineNodes(None, cl=cl, nowarn=True), |
874 |
["master.example.com"])
|
875 |
self.assertEqual(cl.CountPending(), 0) |
876 |
|
877 |
def testNodeGroup(self): |
878 |
cl = self._FakeClient()
|
879 |
|
880 |
def _CheckFilter(filter_): |
881 |
self.assertEqual(filter_,
|
882 |
[qlang.OP_OR, [qlang.OP_EQUAL, "group", "foobar"], |
883 |
[qlang.OP_EQUAL, "group.uuid", "foobar"]]) |
884 |
return True |
885 |
|
886 |
cl.AddQueryResult(["name", "offline", "sip"], _CheckFilter, [ |
887 |
[(constants.RS_NORMAL, "master.example.com"),
|
888 |
(constants.RS_NORMAL, False),
|
889 |
(constants.RS_NORMAL, "192.0.2.1")],
|
890 |
[(constants.RS_NORMAL, "node3.example.com"),
|
891 |
(constants.RS_NORMAL, False),
|
892 |
(constants.RS_NORMAL, "192.0.2.3")],
|
893 |
]) |
894 |
self.assertEqual(cli.GetOnlineNodes(None, cl=cl, nodegroup="foobar"), |
895 |
["master.example.com", "node3.example.com"]) |
896 |
self.assertEqual(cl.CountPending(), 0) |
897 |
|
898 |
|
899 |
if __name__ == '__main__': |
900 |
testutils.GanetiTestProgram() |