4 # Copyright (C) 2008 Google Inc.
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.
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.
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
22 """Script for unittesting the cli module"""
25 from cStringIO import StringIO
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.errors import OpPrereqError, ParameterError
38 class TestParseTimespec(unittest.TestCase):
39 """Testing case for ParseTimespec"""
41 def testValidTimes(self):
42 """Test valid timespecs"""
49 ('1w', 60 * 60 * 24 * 7),
53 for value, expected_result in test_data:
54 self.failUnlessEqual(cli.ParseTimespec(value), expected_result)
56 def testInvalidTime(self):
57 """Test invalid timespecs"""
64 for value in test_data:
65 self.failUnlessRaises(OpPrereqError, cli.ParseTimespec, value)
68 class TestSplitKeyVal(unittest.TestCase):
69 """Testing case for cli._SplitKeyVal"""
70 DATA = "a=b,c,no_d,-e"
71 RESULT = {"a": "b", "c": True, "d": False, "e": None}
73 def testSplitKeyVal(self):
75 self.failUnlessEqual(cli._SplitKeyVal("option", self.DATA), self.RESULT)
77 def testDuplicateParam(self):
78 """Test duplicate parameters"""
79 for data in ("a=1,a=2", "a,no_a"):
80 self.failUnlessRaises(ParameterError, cli._SplitKeyVal,
83 def testEmptyData(self):
84 """Test how we handle splitting an empty string"""
85 self.failUnlessEqual(cli._SplitKeyVal("option", ""), {})
87 class TestIdentKeyVal(unittest.TestCase):
88 """Testing case for cli.check_ident_key_val"""
90 def testIdentKeyVal(self):
91 """Test identkeyval"""
93 return cli.check_ident_key_val("option", "opt", value)
95 self.assertEqual(cikv("foo:bar"), ("foo", {"bar": True}))
96 self.assertEqual(cikv("foo:bar=baz"), ("foo", {"bar": "baz"}))
97 self.assertEqual(cikv("bar:b=c,c=a"), ("bar", {"b": "c", "c": "a"}))
98 self.assertEqual(cikv("no_bar"), ("bar", False))
99 self.assertRaises(ParameterError, cikv, "no_bar:foo")
100 self.assertRaises(ParameterError, cikv, "no_bar:foo=baz")
101 self.assertEqual(cikv("-foo"), ("foo", None))
102 self.assertRaises(ParameterError, cikv, "-foo:a=c")
105 class TestToStream(unittest.TestCase):
106 """Test the ToStream functions"""
116 cli._ToStream(buf, data)
117 self.failUnlessEqual(buf.getvalue(), data+'\n')
119 def testParams(self):
121 cli._ToStream(buf, "foo %s", 1)
122 self.failUnlessEqual(buf.getvalue(), "foo 1\n")
124 cli._ToStream(buf, "foo %s", (15,16))
125 self.failUnlessEqual(buf.getvalue(), "foo (15, 16)\n")
127 cli._ToStream(buf, "foo %s %s", "a", "b")
128 self.failUnlessEqual(buf.getvalue(), "foo a b\n")
131 class TestGenerateTable(unittest.TestCase):
132 HEADERS = dict([("f%s" % i, "Field%s" % i) for i in range(5)])
134 FIELDS1 = ["f1", "f2"]
141 def _test(self, headers, fields, separator, data,
142 numfields, unitfields, units, expected):
143 table = cli.GenerateTable(headers, fields, separator, data,
144 numfields=numfields, unitfields=unitfields,
146 self.assertEqual(table, expected)
155 self._test(self.HEADERS, self.FIELDS1, None, self.DATA1,
156 None, None, "m", exp)
158 def testNoFields(self):
159 self._test(self.HEADERS, [], None, [[], []],
160 None, None, "m", ["", "", ""])
161 self._test(None, [], None, [[], []],
162 None, None, "m", ["", ""])
164 def testSeparator(self):
165 for sep in ["#", ":", ",", "^", "!", "%", "|", "###", "%%", "!!!", "||"]:
167 "Field1%sField2" % sep,
172 self._test(self.HEADERS, self.FIELDS1, sep, self.DATA1,
173 None, None, "m", exp)
175 def testNoHeader(self):
181 self._test(None, self.FIELDS1, None, self.DATA1,
182 None, None, "m", exp)
184 def testUnknownField(self):
194 self._test(headers, ["f1", "UNKNOWN"], None, self.DATA1,
195 None, None, "m", exp)
197 def testNumfields(self):
198 fields = ["f1", "f2", "f3"]
205 "Field1 Field2 Field3",
210 self._test(self.HEADERS, fields, None, data,
211 ["f2", "f3"], None, "m", exp)
213 def testUnitfields(self):
215 "Field1 Field2 Field3",
222 "Field1:Field2:Field3",
228 for sep, expected in [(None, expnosep), (":", expsep)]:
229 fields = ["f1", "f2", "f3"]
235 self._test(self.HEADERS, fields, sep, data,
236 ["f2", "f3"], ["f3"], "h", expected)
238 def testUnusual(self):
248 self._test(self.HEADERS, ["f1", "f2"], None, data,
249 None, None, "m", exp)
252 class TestFormatQueryResult(unittest.TestCase):
255 objects.QueryFieldDefinition(name="name", title="Name",
256 kind=constants.QFT_TEXT),
257 objects.QueryFieldDefinition(name="size", title="Size",
258 kind=constants.QFT_NUMBER),
259 objects.QueryFieldDefinition(name="act", title="Active",
260 kind=constants.QFT_BOOL),
261 objects.QueryFieldDefinition(name="mem", title="Memory",
262 kind=constants.QFT_UNIT),
263 objects.QueryFieldDefinition(name="other", title="SomeList",
264 kind=constants.QFT_OTHER),
267 response = objects.QueryResponse(fields=fields, data=[
268 [(constants.QRFS_NORMAL, "nodeA"), (constants.QRFS_NORMAL, 128),
269 (constants.QRFS_NORMAL, False), (constants.QRFS_NORMAL, 1468006),
270 (constants.QRFS_NORMAL, [])],
271 [(constants.QRFS_NORMAL, "other"), (constants.QRFS_NORMAL, 512),
272 (constants.QRFS_NORMAL, True), (constants.QRFS_NORMAL, 16),
273 (constants.QRFS_NORMAL, [1, 2, 3])],
274 [(constants.QRFS_NORMAL, "xyz"), (constants.QRFS_NORMAL, 1024),
275 (constants.QRFS_NORMAL, True), (constants.QRFS_NORMAL, 4096),
276 (constants.QRFS_NORMAL, [{}, {}])],
279 self.assertEqual(cli.FormatQueryResult(response, unit="h", header=True),
281 "Name Size Active Memory SomeList",
282 "nodeA 128 N 1.4T []",
283 "other 512 Y 16M [1, 2, 3]",
284 "xyz 1024 Y 4.0G [{}, {}]",
287 def testTimestampAndUnit(self):
289 objects.QueryFieldDefinition(name="name", title="Name",
290 kind=constants.QFT_TEXT),
291 objects.QueryFieldDefinition(name="size", title="Size",
292 kind=constants.QFT_UNIT),
293 objects.QueryFieldDefinition(name="mtime", title="ModTime",
294 kind=constants.QFT_TIMESTAMP),
297 response = objects.QueryResponse(fields=fields, data=[
298 [(constants.QRFS_NORMAL, "a"), (constants.QRFS_NORMAL, 1024),
299 (constants.QRFS_NORMAL, 0)],
300 [(constants.QRFS_NORMAL, "b"), (constants.QRFS_NORMAL, 144996),
301 (constants.QRFS_NORMAL, 1291746295)],
304 self.assertEqual(cli.FormatQueryResult(response, unit="m", header=True),
307 "a 1024 %s" % utils.FormatTime(0),
308 "b 144996 %s" % utils.FormatTime(1291746295),
311 def testOverride(self):
313 objects.QueryFieldDefinition(name="name", title="Name",
314 kind=constants.QFT_TEXT),
315 objects.QueryFieldDefinition(name="cust", title="Custom",
316 kind=constants.QFT_OTHER),
317 objects.QueryFieldDefinition(name="xt", title="XTime",
318 kind=constants.QFT_TIMESTAMP),
321 response = objects.QueryResponse(fields=fields, data=[
322 [(constants.QRFS_NORMAL, "x"), (constants.QRFS_NORMAL, ["a", "b", "c"]),
323 (constants.QRFS_NORMAL, 1234)],
324 [(constants.QRFS_NORMAL, "y"), (constants.QRFS_NORMAL, range(10)),
325 (constants.QRFS_NORMAL, 1291746295)],
329 "cust": (utils.CommaJoin, False),
333 self.assertEqual(cli.FormatQueryResult(response, unit="h", header=True,
334 format_override=override),
338 "y 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 0x4cfe7bf7",
341 def testSeparator(self):
343 objects.QueryFieldDefinition(name="name", title="Name",
344 kind=constants.QFT_TEXT),
345 objects.QueryFieldDefinition(name="count", title="Count",
346 kind=constants.QFT_NUMBER),
347 objects.QueryFieldDefinition(name="desc", title="Description",
348 kind=constants.QFT_TEXT),
351 response = objects.QueryResponse(fields=fields, data=[
352 [(constants.QRFS_NORMAL, "instance1.example.com"),
353 (constants.QRFS_NORMAL, 21125), (constants.QRFS_NORMAL, "Hello World!")],
354 [(constants.QRFS_NORMAL, "mail.other.net"),
355 (constants.QRFS_NORMAL, -9000), (constants.QRFS_NORMAL, "a,b,c")],
358 for sep in [":", "|", "#", "|||", "###", "@@@", "@#@"]:
359 for header in [None, "Name%sCount%sDescription" % (sep, sep)]:
364 "instance1.example.com%s21125%sHello World!" % (sep, sep),
365 "mail.other.net%s-9000%sa,b,c" % (sep, sep),
368 self.assertEqual(cli.FormatQueryResult(response, separator=sep,
369 header=bool(header)),
370 (cli.QR_NORMAL, exp))
372 def testStatusWithUnknown(self):
374 objects.QueryFieldDefinition(name="id", title="ID",
375 kind=constants.QFT_NUMBER),
376 objects.QueryFieldDefinition(name="unk", title="unk",
377 kind=constants.QFT_UNKNOWN),
378 objects.QueryFieldDefinition(name="unavail", title="Unavail",
379 kind=constants.QFT_BOOL),
380 objects.QueryFieldDefinition(name="nodata", title="NoData",
381 kind=constants.QFT_TEXT),
384 response = objects.QueryResponse(fields=fields, data=[
385 [(constants.QRFS_NORMAL, 1), (constants.QRFS_UNKNOWN, None),
386 (constants.QRFS_NORMAL, False), (constants.QRFS_NORMAL, "")],
387 [(constants.QRFS_NORMAL, 2), (constants.QRFS_UNKNOWN, None),
388 (constants.QRFS_NODATA, None), (constants.QRFS_NORMAL, "x")],
389 [(constants.QRFS_NORMAL, 3), (constants.QRFS_UNKNOWN, None),
390 (constants.QRFS_NORMAL, False), (constants.QRFS_UNAVAIL, None)],
393 self.assertEqual(cli.FormatQueryResult(response, header=True,
396 "ID|unk|Unavail|NoData",
398 "2|(unknown)|(nodata)|x",
399 "3|(unknown)|N|(unavail)",
402 def testNoData(self):
404 objects.QueryFieldDefinition(name="id", title="ID",
405 kind=constants.QFT_NUMBER),
406 objects.QueryFieldDefinition(name="name", title="Name",
407 kind=constants.QFT_TEXT),
410 response = objects.QueryResponse(fields=fields, data=[])
412 self.assertEqual(cli.FormatQueryResult(response, header=True),
413 (cli.QR_NORMAL, ["ID Name"]))
415 def testNoDataWithUnknown(self):
417 objects.QueryFieldDefinition(name="id", title="ID",
418 kind=constants.QFT_NUMBER),
419 objects.QueryFieldDefinition(name="unk", title="unk",
420 kind=constants.QFT_UNKNOWN),
423 response = objects.QueryResponse(fields=fields, data=[])
425 self.assertEqual(cli.FormatQueryResult(response, header=False),
426 (cli.QR_UNKNOWN, []))
428 def testStatus(self):
430 objects.QueryFieldDefinition(name="id", title="ID",
431 kind=constants.QFT_NUMBER),
432 objects.QueryFieldDefinition(name="unavail", title="Unavail",
433 kind=constants.QFT_BOOL),
434 objects.QueryFieldDefinition(name="nodata", title="NoData",
435 kind=constants.QFT_TEXT),
438 response = objects.QueryResponse(fields=fields, data=[
439 [(constants.QRFS_NORMAL, 1), (constants.QRFS_NORMAL, False),
440 (constants.QRFS_NORMAL, "")],
441 [(constants.QRFS_NORMAL, 2), (constants.QRFS_NODATA, None),
442 (constants.QRFS_NORMAL, "x")],
443 [(constants.QRFS_NORMAL, 3), (constants.QRFS_NORMAL, False),
444 (constants.QRFS_UNAVAIL, None)],
447 self.assertEqual(cli.FormatQueryResult(response, header=False,
449 (cli.QR_INCOMPLETE, [
455 def testInvalidFieldType(self):
457 objects.QueryFieldDefinition(name="x", title="x",
458 kind="#some#other#type"),
461 response = objects.QueryResponse(fields=fields, data=[])
463 self.assertRaises(NotImplementedError, cli.FormatQueryResult, response)
465 def testInvalidFieldStatus(self):
467 objects.QueryFieldDefinition(name="x", title="x",
468 kind=constants.QFT_TEXT),
471 response = objects.QueryResponse(fields=fields, data=[[(-1, None)]])
472 self.assertRaises(NotImplementedError, cli.FormatQueryResult, response)
474 response = objects.QueryResponse(fields=fields, data=[[(-1, "x")]])
475 self.assertRaises(AssertionError, cli.FormatQueryResult, response)
477 def testEmptyFieldTitle(self):
479 objects.QueryFieldDefinition(name="x", title="",
480 kind=constants.QFT_TEXT),
483 response = objects.QueryResponse(fields=fields, data=[])
484 self.assertRaises(AssertionError, cli.FormatQueryResult, response)
487 class _MockJobPollCb(cli.JobPollCbBase, cli.JobPollReportCbBase):
488 def __init__(self, tc, job_id):
493 self._expect_notchanged = False
494 self._expect_log = []
496 def CheckEmpty(self):
497 self.tc.assertFalse(self._wfjcr)
498 self.tc.assertFalse(self._jobstatus)
499 self.tc.assertFalse(self._expect_notchanged)
500 self.tc.assertFalse(self._expect_log)
502 def AddWfjcResult(self, *args):
503 self._wfjcr.append(args)
505 def AddQueryJobsResult(self, *args):
506 self._jobstatus.append(args)
508 def WaitForJobChangeOnce(self, job_id, fields,
509 prev_job_info, prev_log_serial):
510 self.tc.assertEqual(job_id, self.job_id)
511 self.tc.assertEqualValues(fields, ["status"])
512 self.tc.assertFalse(self._expect_notchanged)
513 self.tc.assertFalse(self._expect_log)
515 (exp_prev_job_info, exp_prev_log_serial, result) = self._wfjcr.pop(0)
516 self.tc.assertEqualValues(prev_job_info, exp_prev_job_info)
517 self.tc.assertEqual(prev_log_serial, exp_prev_log_serial)
519 if result == constants.JOB_NOTCHANGED:
520 self._expect_notchanged = True
522 (_, logmsgs) = result
524 self._expect_log.extend(logmsgs)
528 def QueryJobs(self, job_ids, fields):
529 self.tc.assertEqual(job_ids, [self.job_id])
530 self.tc.assertEqualValues(fields, ["status", "opstatus", "opresult"])
531 self.tc.assertFalse(self._expect_notchanged)
532 self.tc.assertFalse(self._expect_log)
534 result = self._jobstatus.pop(0)
535 self.tc.assertEqual(len(fields), len(result))
538 def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
539 self.tc.assertEqual(job_id, self.job_id)
540 self.tc.assertEqualValues((serial, timestamp, log_type, log_msg),
541 self._expect_log.pop(0))
543 def ReportNotChanged(self, job_id, status):
544 self.tc.assertEqual(job_id, self.job_id)
545 self.tc.assert_(self._expect_notchanged)
546 self._expect_notchanged = False
549 class TestGenericPollJob(testutils.GanetiTestCase):
550 def testSuccessWithLog(self):
552 cbs = _MockJobPollCb(self, job_id)
554 cbs.AddWfjcResult(None, None, constants.JOB_NOTCHANGED)
556 cbs.AddWfjcResult(None, None,
557 ((constants.JOB_STATUS_QUEUED, ), None))
559 cbs.AddWfjcResult((constants.JOB_STATUS_QUEUED, ), None,
560 constants.JOB_NOTCHANGED)
562 cbs.AddWfjcResult((constants.JOB_STATUS_QUEUED, ), None,
563 ((constants.JOB_STATUS_RUNNING, ),
564 [(1, utils.SplitTime(1273491611.0),
565 constants.ELOG_MESSAGE, "Step 1"),
566 (2, utils.SplitTime(1273491615.9),
567 constants.ELOG_MESSAGE, "Step 2"),
568 (3, utils.SplitTime(1273491625.02),
569 constants.ELOG_MESSAGE, "Step 3"),
570 (4, utils.SplitTime(1273491635.05),
571 constants.ELOG_MESSAGE, "Step 4"),
572 (37, utils.SplitTime(1273491645.0),
573 constants.ELOG_MESSAGE, "Step 5"),
574 (203, utils.SplitTime(127349155.0),
575 constants.ELOG_MESSAGE, "Step 6")]))
577 cbs.AddWfjcResult((constants.JOB_STATUS_RUNNING, ), 203,
578 ((constants.JOB_STATUS_RUNNING, ),
579 [(300, utils.SplitTime(1273491711.01),
580 constants.ELOG_MESSAGE, "Step X"),
581 (302, utils.SplitTime(1273491815.8),
582 constants.ELOG_MESSAGE, "Step Y"),
583 (303, utils.SplitTime(1273491925.32),
584 constants.ELOG_MESSAGE, "Step Z")]))
586 cbs.AddWfjcResult((constants.JOB_STATUS_RUNNING, ), 303,
587 ((constants.JOB_STATUS_SUCCESS, ), None))
589 cbs.AddQueryJobsResult(constants.JOB_STATUS_SUCCESS,
590 [constants.OP_STATUS_SUCCESS,
591 constants.OP_STATUS_SUCCESS],
592 ["Hello World", "Foo man bar"])
594 self.assertEqual(["Hello World", "Foo man bar"],
595 cli.GenericPollJob(job_id, cbs, cbs))
598 def testJobLost(self):
601 cbs = _MockJobPollCb(self, job_id)
602 cbs.AddWfjcResult(None, None, constants.JOB_NOTCHANGED)
603 cbs.AddWfjcResult(None, None, None)
604 self.assertRaises(errors.JobLost, cli.GenericPollJob, job_id, cbs, cbs)
610 cbs = _MockJobPollCb(self, job_id)
611 cbs.AddWfjcResult(None, None, constants.JOB_NOTCHANGED)
612 cbs.AddWfjcResult(None, None, ((constants.JOB_STATUS_ERROR, ), None))
613 cbs.AddQueryJobsResult(constants.JOB_STATUS_ERROR,
614 [constants.OP_STATUS_SUCCESS,
615 constants.OP_STATUS_ERROR],
616 ["Hello World", "Error code 123"])
617 self.assertRaises(errors.OpExecError, cli.GenericPollJob, job_id, cbs, cbs)
620 def testError2(self):
623 cbs = _MockJobPollCb(self, job_id)
624 cbs.AddWfjcResult(None, None, ((constants.JOB_STATUS_ERROR, ), None))
625 encexc = errors.EncodeException(errors.LockError("problem"))
626 cbs.AddQueryJobsResult(constants.JOB_STATUS_ERROR,
627 [constants.OP_STATUS_ERROR], [encexc])
628 self.assertRaises(errors.LockError, cli.GenericPollJob, job_id, cbs, cbs)
631 def testWeirdError(self):
634 cbs = _MockJobPollCb(self, job_id)
635 cbs.AddWfjcResult(None, None, ((constants.JOB_STATUS_ERROR, ), None))
636 cbs.AddQueryJobsResult(constants.JOB_STATUS_ERROR,
637 [constants.OP_STATUS_RUNNING,
638 constants.OP_STATUS_RUNNING],
640 self.assertRaises(errors.OpExecError, cli.GenericPollJob, job_id, cbs, cbs)
643 def testCancel(self):
646 cbs = _MockJobPollCb(self, job_id)
647 cbs.AddWfjcResult(None, None, constants.JOB_NOTCHANGED)
648 cbs.AddWfjcResult(None, None, ((constants.JOB_STATUS_CANCELING, ), None))
649 cbs.AddQueryJobsResult(constants.JOB_STATUS_CANCELING,
650 [constants.OP_STATUS_CANCELING,
651 constants.OP_STATUS_CANCELING],
653 self.assertRaises(errors.OpExecError, cli.GenericPollJob, job_id, cbs, cbs)
657 class TestFormatLogMessage(unittest.TestCase):
659 self.assertEqual(cli.FormatLogMessage(constants.ELOG_MESSAGE,
662 self.assertRaises(TypeError, cli.FormatLogMessage,
663 constants.ELOG_MESSAGE, [1, 2, 3])
665 self.assert_(cli.FormatLogMessage("some other type", (1, 2, 3)))
668 class TestParseFields(unittest.TestCase):
670 self.assertEqual(cli.ParseFields(None, []), [])
671 self.assertEqual(cli.ParseFields("name,foo,hello", []),
672 ["name", "foo", "hello"])
673 self.assertEqual(cli.ParseFields(None, ["def", "ault", "fields", "here"]),
674 ["def", "ault", "fields", "here"])
675 self.assertEqual(cli.ParseFields("name,foo", ["def", "ault"]),
677 self.assertEqual(cli.ParseFields("+name,foo", ["def", "ault"]),
678 ["def", "ault", "name", "foo"])
681 class TestConstants(unittest.TestCase):
682 def testPriority(self):
683 self.assertEqual(set(cli._PRIONAME_TO_VALUE.values()),
684 set(constants.OP_PRIO_SUBMIT_VALID))
685 self.assertEqual(list(value for _, value in cli._PRIORITY_NAMES),
686 sorted(constants.OP_PRIO_SUBMIT_VALID, reverse=True))
689 class TestParseNicOption(unittest.TestCase):
691 self.assertEqual(cli.ParseNicOption([("0", { "link": "eth0", })]),
692 [{ "link": "eth0", }])
693 self.assertEqual(cli.ParseNicOption([("5", { "ip": "192.0.2.7", })]),
694 [{}, {}, {}, {}, {}, { "ip": "192.0.2.7", }])
696 def testErrors(self):
697 for i in [None, "", "abc", "zero", "Hello World", "\0", []]:
698 self.assertRaises(errors.OpPrereqError, cli.ParseNicOption,
699 [(i, { "link": "eth0", })])
700 self.assertRaises(errors.OpPrereqError, cli.ParseNicOption,
703 self.assertRaises(errors.TypeEnforcementError, cli.ParseNicOption,
704 [(0, { True: False, })])
706 self.assertRaises(errors.TypeEnforcementError, cli.ParseNicOption,
707 [(3, { "mode": [], })])
710 if __name__ == '__main__':
711 testutils.GanetiTestProgram()