Document DRBD dynamic resync params in man pages
[ganeti-local] / test / ganeti.cli_unittest.py
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, qfilter):
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 (qfilter is None or check_filter(qfilter)):
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(qfilter):
808       self.assertEqual(qfilter, [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(qfilter):
839       self.assertEqual(qfilter,
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(qfilter):
881       self.assertEqual(qfilter,
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()