Add function to format all job log messages
[ganeti-local] / test / ganeti.cli_unittest.py
1 #!/usr/bin/python
2 #
3
4 # Copyright (C) 2008 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.errors import OpPrereqError, ParameterError
35
36
37 class TestParseTimespec(unittest.TestCase):
38   """Testing case for ParseTimespec"""
39
40   def testValidTimes(self):
41     """Test valid timespecs"""
42     test_data = [
43       ('1s', 1),
44       ('1', 1),
45       ('1m', 60),
46       ('1h', 60 * 60),
47       ('1d', 60 * 60 * 24),
48       ('1w', 60 * 60 * 24 * 7),
49       ('4h', 4 * 60 * 60),
50       ('61m', 61 * 60),
51       ]
52     for value, expected_result in test_data:
53       self.failUnlessEqual(cli.ParseTimespec(value), expected_result)
54
55   def testInvalidTime(self):
56     """Test invalid timespecs"""
57     test_data = [
58       '1y',
59       '',
60       'aaa',
61       's',
62       ]
63     for value in test_data:
64       self.failUnlessRaises(OpPrereqError, cli.ParseTimespec, value)
65
66
67 class TestSplitKeyVal(unittest.TestCase):
68   """Testing case for cli._SplitKeyVal"""
69   DATA = "a=b,c,no_d,-e"
70   RESULT = {"a": "b", "c": True, "d": False, "e": None}
71
72   def testSplitKeyVal(self):
73     """Test splitting"""
74     self.failUnlessEqual(cli._SplitKeyVal("option", self.DATA), self.RESULT)
75
76   def testDuplicateParam(self):
77     """Test duplicate parameters"""
78     for data in ("a=1,a=2", "a,no_a"):
79       self.failUnlessRaises(ParameterError, cli._SplitKeyVal,
80                             "option", data)
81
82   def testEmptyData(self):
83     """Test how we handle splitting an empty string"""
84     self.failUnlessEqual(cli._SplitKeyVal("option", ""), {})
85
86 class TestIdentKeyVal(unittest.TestCase):
87   """Testing case for cli.check_ident_key_val"""
88
89   def testIdentKeyVal(self):
90     """Test identkeyval"""
91     def cikv(value):
92       return cli.check_ident_key_val("option", "opt", value)
93
94     self.assertEqual(cikv("foo:bar"), ("foo", {"bar": True}))
95     self.assertEqual(cikv("foo:bar=baz"), ("foo", {"bar": "baz"}))
96     self.assertEqual(cikv("bar:b=c,c=a"), ("bar", {"b": "c", "c": "a"}))
97     self.assertEqual(cikv("no_bar"), ("bar", False))
98     self.assertRaises(ParameterError, cikv, "no_bar:foo")
99     self.assertRaises(ParameterError, cikv, "no_bar:foo=baz")
100     self.assertEqual(cikv("-foo"), ("foo", None))
101     self.assertRaises(ParameterError, cikv, "-foo:a=c")
102
103
104 class TestToStream(unittest.TestCase):
105   """Test the ToStream functions"""
106
107   def testBasic(self):
108     for data in ["foo",
109                  "foo %s",
110                  "foo %(test)s",
111                  "foo %s %s",
112                  "",
113                  ]:
114       buf = StringIO()
115       cli._ToStream(buf, data)
116       self.failUnlessEqual(buf.getvalue(), data+'\n')
117
118   def testParams(self):
119       buf = StringIO()
120       cli._ToStream(buf, "foo %s", 1)
121       self.failUnlessEqual(buf.getvalue(), "foo 1\n")
122       buf = StringIO()
123       cli._ToStream(buf, "foo %s", (15,16))
124       self.failUnlessEqual(buf.getvalue(), "foo (15, 16)\n")
125       buf = StringIO()
126       cli._ToStream(buf, "foo %s %s", "a", "b")
127       self.failUnlessEqual(buf.getvalue(), "foo a b\n")
128
129
130 class TestGenerateTable(unittest.TestCase):
131   HEADERS = dict([("f%s" % i, "Field%s" % i) for i in range(5)])
132
133   FIELDS1 = ["f1", "f2"]
134   DATA1 = [
135     ["abc", 1234],
136     ["foobar", 56],
137     ["b", -14],
138     ]
139
140   def _test(self, headers, fields, separator, data,
141             numfields, unitfields, units, expected):
142     table = cli.GenerateTable(headers, fields, separator, data,
143                               numfields=numfields, unitfields=unitfields,
144                               units=units)
145     self.assertEqual(table, expected)
146
147   def testPlain(self):
148     exp = [
149       "Field1 Field2",
150       "abc    1234",
151       "foobar 56",
152       "b      -14",
153       ]
154     self._test(self.HEADERS, self.FIELDS1, None, self.DATA1,
155                None, None, "m", exp)
156
157   def testNoFields(self):
158     self._test(self.HEADERS, [], None, [[], []],
159                None, None, "m", ["", "", ""])
160     self._test(None, [], None, [[], []],
161                None, None, "m", ["", ""])
162
163   def testSeparator(self):
164     for sep in ["#", ":", ",", "^", "!", "%", "|", "###", "%%", "!!!", "||"]:
165       exp = [
166         "Field1%sField2" % sep,
167         "abc%s1234" % sep,
168         "foobar%s56" % sep,
169         "b%s-14" % sep,
170         ]
171       self._test(self.HEADERS, self.FIELDS1, sep, self.DATA1,
172                  None, None, "m", exp)
173
174   def testNoHeader(self):
175     exp = [
176       "abc    1234",
177       "foobar 56",
178       "b      -14",
179       ]
180     self._test(None, self.FIELDS1, None, self.DATA1,
181                None, None, "m", exp)
182
183   def testUnknownField(self):
184     headers = {
185       "f1": "Field1",
186       }
187     exp = [
188       "Field1 UNKNOWN",
189       "abc    1234",
190       "foobar 56",
191       "b      -14",
192       ]
193     self._test(headers, ["f1", "UNKNOWN"], None, self.DATA1,
194                None, None, "m", exp)
195
196   def testNumfields(self):
197     fields = ["f1", "f2", "f3"]
198     data = [
199       ["abc", 1234, 0],
200       ["foobar", 56, 3],
201       ["b", -14, "-"],
202       ]
203     exp = [
204       "Field1 Field2 Field3",
205       "abc      1234      0",
206       "foobar     56      3",
207       "b         -14      -",
208       ]
209     self._test(self.HEADERS, fields, None, data,
210                ["f2", "f3"], None, "m", exp)
211
212   def testUnitfields(self):
213     expnosep = [
214       "Field1 Field2 Field3",
215       "abc      1234     0M",
216       "foobar     56     3M",
217       "b         -14      -",
218       ]
219
220     expsep = [
221       "Field1:Field2:Field3",
222       "abc:1234:0M",
223       "foobar:56:3M",
224       "b:-14:-",
225       ]
226
227     for sep, expected in [(None, expnosep), (":", expsep)]:
228       fields = ["f1", "f2", "f3"]
229       data = [
230         ["abc", 1234, 0],
231         ["foobar", 56, 3],
232         ["b", -14, "-"],
233         ]
234       self._test(self.HEADERS, fields, sep, data,
235                  ["f2", "f3"], ["f3"], "h", expected)
236
237   def testUnusual(self):
238     data = [
239       ["%", "xyz"],
240       ["%%", "abc"],
241       ]
242     exp = [
243       "Field1 Field2",
244       "%      xyz",
245       "%%     abc",
246       ]
247     self._test(self.HEADERS, ["f1", "f2"], None, data,
248                None, None, "m", exp)
249
250
251 class _MockJobPollCb(cli.JobPollCbBase, cli.JobPollReportCbBase):
252   def __init__(self, tc, job_id):
253     self.tc = tc
254     self.job_id = job_id
255     self._wfjcr = []
256     self._jobstatus = []
257     self._expect_notchanged = False
258     self._expect_log = []
259
260   def CheckEmpty(self):
261     self.tc.assertFalse(self._wfjcr)
262     self.tc.assertFalse(self._jobstatus)
263     self.tc.assertFalse(self._expect_notchanged)
264     self.tc.assertFalse(self._expect_log)
265
266   def AddWfjcResult(self, *args):
267     self._wfjcr.append(args)
268
269   def AddQueryJobsResult(self, *args):
270     self._jobstatus.append(args)
271
272   def WaitForJobChangeOnce(self, job_id, fields,
273                            prev_job_info, prev_log_serial):
274     self.tc.assertEqual(job_id, self.job_id)
275     self.tc.assertEqualValues(fields, ["status"])
276     self.tc.assertFalse(self._expect_notchanged)
277     self.tc.assertFalse(self._expect_log)
278
279     (exp_prev_job_info, exp_prev_log_serial, result) = self._wfjcr.pop(0)
280     self.tc.assertEqualValues(prev_job_info, exp_prev_job_info)
281     self.tc.assertEqual(prev_log_serial, exp_prev_log_serial)
282
283     if result == constants.JOB_NOTCHANGED:
284       self._expect_notchanged = True
285     elif result:
286       (_, logmsgs) = result
287       if logmsgs:
288         self._expect_log.extend(logmsgs)
289
290     return result
291
292   def QueryJobs(self, job_ids, fields):
293     self.tc.assertEqual(job_ids, [self.job_id])
294     self.tc.assertEqualValues(fields, ["status", "opstatus", "opresult"])
295     self.tc.assertFalse(self._expect_notchanged)
296     self.tc.assertFalse(self._expect_log)
297
298     result = self._jobstatus.pop(0)
299     self.tc.assertEqual(len(fields), len(result))
300     return [result]
301
302   def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
303     self.tc.assertEqual(job_id, self.job_id)
304     self.tc.assertEqualValues((serial, timestamp, log_type, log_msg),
305                               self._expect_log.pop(0))
306
307   def ReportNotChanged(self, job_id, status):
308     self.tc.assertEqual(job_id, self.job_id)
309     self.tc.assert_(self._expect_notchanged)
310     self._expect_notchanged = False
311
312
313 class TestGenericPollJob(testutils.GanetiTestCase):
314   def testSuccessWithLog(self):
315     job_id = 29609
316     cbs = _MockJobPollCb(self, job_id)
317
318     cbs.AddWfjcResult(None, None, constants.JOB_NOTCHANGED)
319
320     cbs.AddWfjcResult(None, None,
321                       ((constants.JOB_STATUS_QUEUED, ), None))
322
323     cbs.AddWfjcResult((constants.JOB_STATUS_QUEUED, ), None,
324                       constants.JOB_NOTCHANGED)
325
326     cbs.AddWfjcResult((constants.JOB_STATUS_QUEUED, ), None,
327                       ((constants.JOB_STATUS_RUNNING, ),
328                        [(1, utils.SplitTime(1273491611.0),
329                          constants.ELOG_MESSAGE, "Step 1"),
330                         (2, utils.SplitTime(1273491615.9),
331                          constants.ELOG_MESSAGE, "Step 2"),
332                         (3, utils.SplitTime(1273491625.02),
333                          constants.ELOG_MESSAGE, "Step 3"),
334                         (4, utils.SplitTime(1273491635.05),
335                          constants.ELOG_MESSAGE, "Step 4"),
336                         (37, utils.SplitTime(1273491645.0),
337                          constants.ELOG_MESSAGE, "Step 5"),
338                         (203, utils.SplitTime(127349155.0),
339                          constants.ELOG_MESSAGE, "Step 6")]))
340
341     cbs.AddWfjcResult((constants.JOB_STATUS_RUNNING, ), 203,
342                       ((constants.JOB_STATUS_RUNNING, ),
343                        [(300, utils.SplitTime(1273491711.01),
344                          constants.ELOG_MESSAGE, "Step X"),
345                         (302, utils.SplitTime(1273491815.8),
346                          constants.ELOG_MESSAGE, "Step Y"),
347                         (303, utils.SplitTime(1273491925.32),
348                          constants.ELOG_MESSAGE, "Step Z")]))
349
350     cbs.AddWfjcResult((constants.JOB_STATUS_RUNNING, ), 303,
351                       ((constants.JOB_STATUS_SUCCESS, ), None))
352
353     cbs.AddQueryJobsResult(constants.JOB_STATUS_SUCCESS,
354                            [constants.OP_STATUS_SUCCESS,
355                             constants.OP_STATUS_SUCCESS],
356                            ["Hello World", "Foo man bar"])
357
358     self.assertEqual(["Hello World", "Foo man bar"],
359                      cli.GenericPollJob(job_id, cbs, cbs))
360     cbs.CheckEmpty()
361
362   def testJobLost(self):
363     job_id = 13746
364
365     cbs = _MockJobPollCb(self, job_id)
366     cbs.AddWfjcResult(None, None, constants.JOB_NOTCHANGED)
367     cbs.AddWfjcResult(None, None, None)
368     self.assertRaises(errors.JobLost, cli.GenericPollJob, job_id, cbs, cbs)
369     cbs.CheckEmpty()
370
371   def testError(self):
372     job_id = 31088
373
374     cbs = _MockJobPollCb(self, job_id)
375     cbs.AddWfjcResult(None, None, constants.JOB_NOTCHANGED)
376     cbs.AddWfjcResult(None, None, ((constants.JOB_STATUS_ERROR, ), None))
377     cbs.AddQueryJobsResult(constants.JOB_STATUS_ERROR,
378                            [constants.OP_STATUS_SUCCESS,
379                             constants.OP_STATUS_ERROR],
380                            ["Hello World", "Error code 123"])
381     self.assertRaises(errors.OpExecError, cli.GenericPollJob, job_id, cbs, cbs)
382     cbs.CheckEmpty()
383
384   def testError2(self):
385     job_id = 22235
386
387     cbs = _MockJobPollCb(self, job_id)
388     cbs.AddWfjcResult(None, None, ((constants.JOB_STATUS_ERROR, ), None))
389     encexc = errors.EncodeException(errors.LockError("problem"))
390     cbs.AddQueryJobsResult(constants.JOB_STATUS_ERROR,
391                            [constants.OP_STATUS_ERROR], [encexc])
392     self.assertRaises(errors.LockError, cli.GenericPollJob, job_id, cbs, cbs)
393     cbs.CheckEmpty()
394
395   def testWeirdError(self):
396     job_id = 28847
397
398     cbs = _MockJobPollCb(self, job_id)
399     cbs.AddWfjcResult(None, None, ((constants.JOB_STATUS_ERROR, ), None))
400     cbs.AddQueryJobsResult(constants.JOB_STATUS_ERROR,
401                            [constants.OP_STATUS_RUNNING,
402                             constants.OP_STATUS_RUNNING],
403                            [None, None])
404     self.assertRaises(errors.OpExecError, cli.GenericPollJob, job_id, cbs, cbs)
405     cbs.CheckEmpty()
406
407   def testCancel(self):
408     job_id = 4275
409
410     cbs = _MockJobPollCb(self, job_id)
411     cbs.AddWfjcResult(None, None, constants.JOB_NOTCHANGED)
412     cbs.AddWfjcResult(None, None, ((constants.JOB_STATUS_CANCELING, ), None))
413     cbs.AddQueryJobsResult(constants.JOB_STATUS_CANCELING,
414                            [constants.OP_STATUS_CANCELING,
415                             constants.OP_STATUS_CANCELING],
416                            [None, None])
417     self.assertRaises(errors.OpExecError, cli.GenericPollJob, job_id, cbs, cbs)
418     cbs.CheckEmpty()
419
420
421 class TestFormatLogMessage(unittest.TestCase):
422   def test(self):
423     self.assertEqual(cli.FormatLogMessage(constants.ELOG_MESSAGE,
424                                           "Hello World"),
425                      "Hello World")
426     self.assertRaises(TypeError, cli.FormatLogMessage,
427                       constants.ELOG_MESSAGE, [1, 2, 3])
428
429     self.assert_(cli.FormatLogMessage("some other type", (1, 2, 3)))
430
431
432 if __name__ == '__main__':
433   testutils.GanetiTestProgram()