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),
382 objects.QueryFieldDefinition(name="offline", title="OffLine",
383 kind=constants.QFT_TEXT),
386 response = objects.QueryResponse(fields=fields, data=[
387 [(constants.QRFS_NORMAL, 1), (constants.QRFS_UNKNOWN, None),
388 (constants.QRFS_NORMAL, False), (constants.QRFS_NORMAL, ""),
389 (constants.QRFS_OFFLINE, None)],
390 [(constants.QRFS_NORMAL, 2), (constants.QRFS_UNKNOWN, None),
391 (constants.QRFS_NODATA, None), (constants.QRFS_NORMAL, "x"),
392 (constants.QRFS_OFFLINE, None)],
393 [(constants.QRFS_NORMAL, 3), (constants.QRFS_UNKNOWN, None),
394 (constants.QRFS_NORMAL, False), (constants.QRFS_UNAVAIL, None),
395 (constants.QRFS_OFFLINE, None)],
398 self.assertEqual(cli.FormatQueryResult(response, header=True,
401 "ID|unk|Unavail|NoData|OffLine",
402 "1|(unknown)|N||(offline)",
403 "2|(unknown)|(nodata)|x|(offline)",
404 "3|(unknown)|N|(unavail)|(offline)",
407 def testNoData(self):
409 objects.QueryFieldDefinition(name="id", title="ID",
410 kind=constants.QFT_NUMBER),
411 objects.QueryFieldDefinition(name="name", title="Name",
412 kind=constants.QFT_TEXT),
415 response = objects.QueryResponse(fields=fields, data=[])
417 self.assertEqual(cli.FormatQueryResult(response, header=True),
418 (cli.QR_NORMAL, ["ID Name"]))
420 def testNoDataWithUnknown(self):
422 objects.QueryFieldDefinition(name="id", title="ID",
423 kind=constants.QFT_NUMBER),
424 objects.QueryFieldDefinition(name="unk", title="unk",
425 kind=constants.QFT_UNKNOWN),
428 response = objects.QueryResponse(fields=fields, data=[])
430 self.assertEqual(cli.FormatQueryResult(response, header=False),
431 (cli.QR_UNKNOWN, []))
433 def testStatus(self):
435 objects.QueryFieldDefinition(name="id", title="ID",
436 kind=constants.QFT_NUMBER),
437 objects.QueryFieldDefinition(name="unavail", title="Unavail",
438 kind=constants.QFT_BOOL),
439 objects.QueryFieldDefinition(name="nodata", title="NoData",
440 kind=constants.QFT_TEXT),
441 objects.QueryFieldDefinition(name="offline", title="OffLine",
442 kind=constants.QFT_TEXT),
445 response = objects.QueryResponse(fields=fields, data=[
446 [(constants.QRFS_NORMAL, 1), (constants.QRFS_NORMAL, False),
447 (constants.QRFS_NORMAL, ""), (constants.QRFS_OFFLINE, None)],
448 [(constants.QRFS_NORMAL, 2), (constants.QRFS_NODATA, None),
449 (constants.QRFS_NORMAL, "x"), (constants.QRFS_NORMAL, "abc")],
450 [(constants.QRFS_NORMAL, 3), (constants.QRFS_NORMAL, False),
451 (constants.QRFS_UNAVAIL, None), (constants.QRFS_OFFLINE, None)],
454 self.assertEqual(cli.FormatQueryResult(response, header=False,
456 (cli.QR_INCOMPLETE, [
459 "3|N|(unavail)|(offline)",
462 def testInvalidFieldType(self):
464 objects.QueryFieldDefinition(name="x", title="x",
465 kind="#some#other#type"),
468 response = objects.QueryResponse(fields=fields, data=[])
470 self.assertRaises(NotImplementedError, cli.FormatQueryResult, response)
472 def testInvalidFieldStatus(self):
474 objects.QueryFieldDefinition(name="x", title="x",
475 kind=constants.QFT_TEXT),
478 response = objects.QueryResponse(fields=fields, data=[[(-1, None)]])
479 self.assertRaises(NotImplementedError, cli.FormatQueryResult, response)
481 response = objects.QueryResponse(fields=fields, data=[[(-1, "x")]])
482 self.assertRaises(AssertionError, cli.FormatQueryResult, response)
484 def testEmptyFieldTitle(self):
486 objects.QueryFieldDefinition(name="x", title="",
487 kind=constants.QFT_TEXT),
490 response = objects.QueryResponse(fields=fields, data=[])
491 self.assertRaises(AssertionError, cli.FormatQueryResult, response)
494 class _MockJobPollCb(cli.JobPollCbBase, cli.JobPollReportCbBase):
495 def __init__(self, tc, job_id):
500 self._expect_notchanged = False
501 self._expect_log = []
503 def CheckEmpty(self):
504 self.tc.assertFalse(self._wfjcr)
505 self.tc.assertFalse(self._jobstatus)
506 self.tc.assertFalse(self._expect_notchanged)
507 self.tc.assertFalse(self._expect_log)
509 def AddWfjcResult(self, *args):
510 self._wfjcr.append(args)
512 def AddQueryJobsResult(self, *args):
513 self._jobstatus.append(args)
515 def WaitForJobChangeOnce(self, job_id, fields,
516 prev_job_info, prev_log_serial):
517 self.tc.assertEqual(job_id, self.job_id)
518 self.tc.assertEqualValues(fields, ["status"])
519 self.tc.assertFalse(self._expect_notchanged)
520 self.tc.assertFalse(self._expect_log)
522 (exp_prev_job_info, exp_prev_log_serial, result) = self._wfjcr.pop(0)
523 self.tc.assertEqualValues(prev_job_info, exp_prev_job_info)
524 self.tc.assertEqual(prev_log_serial, exp_prev_log_serial)
526 if result == constants.JOB_NOTCHANGED:
527 self._expect_notchanged = True
529 (_, logmsgs) = result
531 self._expect_log.extend(logmsgs)
535 def QueryJobs(self, job_ids, fields):
536 self.tc.assertEqual(job_ids, [self.job_id])
537 self.tc.assertEqualValues(fields, ["status", "opstatus", "opresult"])
538 self.tc.assertFalse(self._expect_notchanged)
539 self.tc.assertFalse(self._expect_log)
541 result = self._jobstatus.pop(0)
542 self.tc.assertEqual(len(fields), len(result))
545 def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
546 self.tc.assertEqual(job_id, self.job_id)
547 self.tc.assertEqualValues((serial, timestamp, log_type, log_msg),
548 self._expect_log.pop(0))
550 def ReportNotChanged(self, job_id, status):
551 self.tc.assertEqual(job_id, self.job_id)
552 self.tc.assert_(self._expect_notchanged)
553 self._expect_notchanged = False
556 class TestGenericPollJob(testutils.GanetiTestCase):
557 def testSuccessWithLog(self):
559 cbs = _MockJobPollCb(self, job_id)
561 cbs.AddWfjcResult(None, None, constants.JOB_NOTCHANGED)
563 cbs.AddWfjcResult(None, None,
564 ((constants.JOB_STATUS_QUEUED, ), None))
566 cbs.AddWfjcResult((constants.JOB_STATUS_QUEUED, ), None,
567 constants.JOB_NOTCHANGED)
569 cbs.AddWfjcResult((constants.JOB_STATUS_QUEUED, ), None,
570 ((constants.JOB_STATUS_RUNNING, ),
571 [(1, utils.SplitTime(1273491611.0),
572 constants.ELOG_MESSAGE, "Step 1"),
573 (2, utils.SplitTime(1273491615.9),
574 constants.ELOG_MESSAGE, "Step 2"),
575 (3, utils.SplitTime(1273491625.02),
576 constants.ELOG_MESSAGE, "Step 3"),
577 (4, utils.SplitTime(1273491635.05),
578 constants.ELOG_MESSAGE, "Step 4"),
579 (37, utils.SplitTime(1273491645.0),
580 constants.ELOG_MESSAGE, "Step 5"),
581 (203, utils.SplitTime(127349155.0),
582 constants.ELOG_MESSAGE, "Step 6")]))
584 cbs.AddWfjcResult((constants.JOB_STATUS_RUNNING, ), 203,
585 ((constants.JOB_STATUS_RUNNING, ),
586 [(300, utils.SplitTime(1273491711.01),
587 constants.ELOG_MESSAGE, "Step X"),
588 (302, utils.SplitTime(1273491815.8),
589 constants.ELOG_MESSAGE, "Step Y"),
590 (303, utils.SplitTime(1273491925.32),
591 constants.ELOG_MESSAGE, "Step Z")]))
593 cbs.AddWfjcResult((constants.JOB_STATUS_RUNNING, ), 303,
594 ((constants.JOB_STATUS_SUCCESS, ), None))
596 cbs.AddQueryJobsResult(constants.JOB_STATUS_SUCCESS,
597 [constants.OP_STATUS_SUCCESS,
598 constants.OP_STATUS_SUCCESS],
599 ["Hello World", "Foo man bar"])
601 self.assertEqual(["Hello World", "Foo man bar"],
602 cli.GenericPollJob(job_id, cbs, cbs))
605 def testJobLost(self):
608 cbs = _MockJobPollCb(self, job_id)
609 cbs.AddWfjcResult(None, None, constants.JOB_NOTCHANGED)
610 cbs.AddWfjcResult(None, None, None)
611 self.assertRaises(errors.JobLost, cli.GenericPollJob, job_id, cbs, cbs)
617 cbs = _MockJobPollCb(self, job_id)
618 cbs.AddWfjcResult(None, None, constants.JOB_NOTCHANGED)
619 cbs.AddWfjcResult(None, None, ((constants.JOB_STATUS_ERROR, ), None))
620 cbs.AddQueryJobsResult(constants.JOB_STATUS_ERROR,
621 [constants.OP_STATUS_SUCCESS,
622 constants.OP_STATUS_ERROR],
623 ["Hello World", "Error code 123"])
624 self.assertRaises(errors.OpExecError, cli.GenericPollJob, job_id, cbs, cbs)
627 def testError2(self):
630 cbs = _MockJobPollCb(self, job_id)
631 cbs.AddWfjcResult(None, None, ((constants.JOB_STATUS_ERROR, ), None))
632 encexc = errors.EncodeException(errors.LockError("problem"))
633 cbs.AddQueryJobsResult(constants.JOB_STATUS_ERROR,
634 [constants.OP_STATUS_ERROR], [encexc])
635 self.assertRaises(errors.LockError, cli.GenericPollJob, job_id, cbs, cbs)
638 def testWeirdError(self):
641 cbs = _MockJobPollCb(self, job_id)
642 cbs.AddWfjcResult(None, None, ((constants.JOB_STATUS_ERROR, ), None))
643 cbs.AddQueryJobsResult(constants.JOB_STATUS_ERROR,
644 [constants.OP_STATUS_RUNNING,
645 constants.OP_STATUS_RUNNING],
647 self.assertRaises(errors.OpExecError, cli.GenericPollJob, job_id, cbs, cbs)
650 def testCancel(self):
653 cbs = _MockJobPollCb(self, job_id)
654 cbs.AddWfjcResult(None, None, constants.JOB_NOTCHANGED)
655 cbs.AddWfjcResult(None, None, ((constants.JOB_STATUS_CANCELING, ), None))
656 cbs.AddQueryJobsResult(constants.JOB_STATUS_CANCELING,
657 [constants.OP_STATUS_CANCELING,
658 constants.OP_STATUS_CANCELING],
660 self.assertRaises(errors.OpExecError, cli.GenericPollJob, job_id, cbs, cbs)
664 class TestFormatLogMessage(unittest.TestCase):
666 self.assertEqual(cli.FormatLogMessage(constants.ELOG_MESSAGE,
669 self.assertRaises(TypeError, cli.FormatLogMessage,
670 constants.ELOG_MESSAGE, [1, 2, 3])
672 self.assert_(cli.FormatLogMessage("some other type", (1, 2, 3)))
675 class TestParseFields(unittest.TestCase):
677 self.assertEqual(cli.ParseFields(None, []), [])
678 self.assertEqual(cli.ParseFields("name,foo,hello", []),
679 ["name", "foo", "hello"])
680 self.assertEqual(cli.ParseFields(None, ["def", "ault", "fields", "here"]),
681 ["def", "ault", "fields", "here"])
682 self.assertEqual(cli.ParseFields("name,foo", ["def", "ault"]),
684 self.assertEqual(cli.ParseFields("+name,foo", ["def", "ault"]),
685 ["def", "ault", "name", "foo"])
688 class TestConstants(unittest.TestCase):
689 def testPriority(self):
690 self.assertEqual(set(cli._PRIONAME_TO_VALUE.values()),
691 set(constants.OP_PRIO_SUBMIT_VALID))
692 self.assertEqual(list(value for _, value in cli._PRIORITY_NAMES),
693 sorted(constants.OP_PRIO_SUBMIT_VALID, reverse=True))
696 class TestParseNicOption(unittest.TestCase):
698 self.assertEqual(cli.ParseNicOption([("0", { "link": "eth0", })]),
699 [{ "link": "eth0", }])
700 self.assertEqual(cli.ParseNicOption([("5", { "ip": "192.0.2.7", })]),
701 [{}, {}, {}, {}, {}, { "ip": "192.0.2.7", }])
703 def testErrors(self):
704 for i in [None, "", "abc", "zero", "Hello World", "\0", []]:
705 self.assertRaises(errors.OpPrereqError, cli.ParseNicOption,
706 [(i, { "link": "eth0", })])
707 self.assertRaises(errors.OpPrereqError, cli.ParseNicOption,
710 self.assertRaises(errors.TypeEnforcementError, cli.ParseNicOption,
711 [(0, { True: False, })])
713 self.assertRaises(errors.TypeEnforcementError, cli.ParseNicOption,
714 [(3, { "mode": [], })])
717 if __name__ == '__main__':
718 testutils.GanetiTestProgram()