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