root / test / ganeti.cli_unittest.py @ 4e338533
History | View | Annotate | Download (13.2 kB)
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 |
if __name__ == '__main__': |
422 |
testutils.GanetiTestProgram() |