Statistics
| Branch: | Tag: | Revision:

root / test / py / ganeti.cli_unittest.py @ 90066780

History | View | Annotate | Download (35.9 kB)

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
import time
26
from cStringIO import StringIO
27

    
28
import ganeti
29
import testutils
30

    
31
from ganeti import constants
32
from ganeti import cli
33
from ganeti import errors
34
from ganeti import utils
35
from ganeti import objects
36
from ganeti import qlang
37
from ganeti.errors import OpPrereqError, ParameterError
38

    
39

    
40
class TestParseTimespec(unittest.TestCase):
41
  """Testing case for ParseTimespec"""
42

    
43
  def testValidTimes(self):
44
    """Test valid timespecs"""
45
    test_data = [
46
      ("1s", 1),
47
      ("1", 1),
48
      ("1m", 60),
49
      ("1h", 60 * 60),
50
      ("1d", 60 * 60 * 24),
51
      ("1w", 60 * 60 * 24 * 7),
52
      ("4h", 4 * 60 * 60),
53
      ("61m", 61 * 60),
54
      ]
55
    for value, expected_result in test_data:
56
      self.failUnlessEqual(cli.ParseTimespec(value), expected_result)
57

    
58
  def testInvalidTime(self):
59
    """Test invalid timespecs"""
60
    test_data = [
61
      "1y",
62
      "",
63
      "aaa",
64
      "s",
65
      ]
66
    for value in test_data:
67
      self.failUnlessRaises(OpPrereqError, cli.ParseTimespec, value)
68

    
69

    
70
class TestSplitKeyVal(unittest.TestCase):
71
  """Testing case for cli._SplitKeyVal"""
72
  DATA = "a=b,c,no_d,-e"
73
  RESULT = {"a": "b", "c": True, "d": False, "e": None}
74

    
75
  def testSplitKeyVal(self):
76
    """Test splitting"""
77
    self.failUnlessEqual(cli._SplitKeyVal("option", self.DATA), self.RESULT)
78

    
79
  def testDuplicateParam(self):
80
    """Test duplicate parameters"""
81
    for data in ("a=1,a=2", "a,no_a"):
82
      self.failUnlessRaises(ParameterError, cli._SplitKeyVal,
83
                            "option", data)
84

    
85
  def testEmptyData(self):
86
    """Test how we handle splitting an empty string"""
87
    self.failUnlessEqual(cli._SplitKeyVal("option", ""), {})
88

    
89

    
90
class TestIdentKeyVal(unittest.TestCase):
91
  """Testing case for cli.check_ident_key_val"""
92

    
93
  def testIdentKeyVal(self):
94
    """Test identkeyval"""
95
    def cikv(value):
96
      return cli.check_ident_key_val("option", "opt", value)
97

    
98
    self.assertEqual(cikv("foo:bar"), ("foo", {"bar": True}))
99
    self.assertEqual(cikv("foo:bar=baz"), ("foo", {"bar": "baz"}))
100
    self.assertEqual(cikv("bar:b=c,c=a"), ("bar", {"b": "c", "c": "a"}))
101
    self.assertEqual(cikv("no_bar"), ("bar", False))
102
    self.assertRaises(ParameterError, cikv, "no_bar:foo")
103
    self.assertRaises(ParameterError, cikv, "no_bar:foo=baz")
104
    self.assertEqual(cikv("-foo"), ("foo", None))
105
    self.assertRaises(ParameterError, cikv, "-foo:a=c")
106

    
107
    # Check negative numbers
108
    self.assertEqual(cikv("-1:remove"), ("-1", {
109
      "remove": True,
110
      }))
111
    self.assertEqual(cikv("-29447:add,size=4G"), ("-29447", {
112
      "add": True,
113
      "size": "4G",
114
      }))
115
    for i in ["-:", "-"]:
116
      self.assertEqual(cikv(i), ("", None))
117

    
118

    
119
class TestToStream(unittest.TestCase):
120
  """Test the ToStream functions"""
121

    
122
  def testBasic(self):
123
    for data in ["foo",
124
                 "foo %s",
125
                 "foo %(test)s",
126
                 "foo %s %s",
127
                 "",
128
                 ]:
129
      buf = StringIO()
130
      cli._ToStream(buf, data)
131
      self.failUnlessEqual(buf.getvalue(), data + "\n")
132

    
133
  def testParams(self):
134
      buf = StringIO()
135
      cli._ToStream(buf, "foo %s", 1)
136
      self.failUnlessEqual(buf.getvalue(), "foo 1\n")
137
      buf = StringIO()
138
      cli._ToStream(buf, "foo %s", (15,16))
139
      self.failUnlessEqual(buf.getvalue(), "foo (15, 16)\n")
140
      buf = StringIO()
141
      cli._ToStream(buf, "foo %s %s", "a", "b")
142
      self.failUnlessEqual(buf.getvalue(), "foo a b\n")
143

    
144

    
145
class TestGenerateTable(unittest.TestCase):
146
  HEADERS = dict([("f%s" % i, "Field%s" % i) for i in range(5)])
147

    
148
  FIELDS1 = ["f1", "f2"]
149
  DATA1 = [
150
    ["abc", 1234],
151
    ["foobar", 56],
152
    ["b", -14],
153
    ]
154

    
155
  def _test(self, headers, fields, separator, data,
156
            numfields, unitfields, units, expected):
157
    table = cli.GenerateTable(headers, fields, separator, data,
158
                              numfields=numfields, unitfields=unitfields,
159
                              units=units)
160
    self.assertEqual(table, expected)
161

    
162
  def testPlain(self):
163
    exp = [
164
      "Field1 Field2",
165
      "abc    1234",
166
      "foobar 56",
167
      "b      -14",
168
      ]
169
    self._test(self.HEADERS, self.FIELDS1, None, self.DATA1,
170
               None, None, "m", exp)
171

    
172
  def testNoFields(self):
173
    self._test(self.HEADERS, [], None, [[], []],
174
               None, None, "m", ["", "", ""])
175
    self._test(None, [], None, [[], []],
176
               None, None, "m", ["", ""])
177

    
178
  def testSeparator(self):
179
    for sep in ["#", ":", ",", "^", "!", "%", "|", "###", "%%", "!!!", "||"]:
180
      exp = [
181
        "Field1%sField2" % sep,
182
        "abc%s1234" % sep,
183
        "foobar%s56" % sep,
184
        "b%s-14" % sep,
185
        ]
186
      self._test(self.HEADERS, self.FIELDS1, sep, self.DATA1,
187
                 None, None, "m", exp)
188

    
189
  def testNoHeader(self):
190
    exp = [
191
      "abc    1234",
192
      "foobar 56",
193
      "b      -14",
194
      ]
195
    self._test(None, self.FIELDS1, None, self.DATA1,
196
               None, None, "m", exp)
197

    
198
  def testUnknownField(self):
199
    headers = {
200
      "f1": "Field1",
201
      }
202
    exp = [
203
      "Field1 UNKNOWN",
204
      "abc    1234",
205
      "foobar 56",
206
      "b      -14",
207
      ]
208
    self._test(headers, ["f1", "UNKNOWN"], None, self.DATA1,
209
               None, None, "m", exp)
210

    
211
  def testNumfields(self):
212
    fields = ["f1", "f2", "f3"]
213
    data = [
214
      ["abc", 1234, 0],
215
      ["foobar", 56, 3],
216
      ["b", -14, "-"],
217
      ]
218
    exp = [
219
      "Field1 Field2 Field3",
220
      "abc      1234      0",
221
      "foobar     56      3",
222
      "b         -14      -",
223
      ]
224
    self._test(self.HEADERS, fields, None, data,
225
               ["f2", "f3"], None, "m", exp)
226

    
227
  def testUnitfields(self):
228
    expnosep = [
229
      "Field1 Field2 Field3",
230
      "abc      1234     0M",
231
      "foobar     56     3M",
232
      "b         -14      -",
233
      ]
234

    
235
    expsep = [
236
      "Field1:Field2:Field3",
237
      "abc:1234:0M",
238
      "foobar:56:3M",
239
      "b:-14:-",
240
      ]
241

    
242
    for sep, expected in [(None, expnosep), (":", expsep)]:
243
      fields = ["f1", "f2", "f3"]
244
      data = [
245
        ["abc", 1234, 0],
246
        ["foobar", 56, 3],
247
        ["b", -14, "-"],
248
        ]
249
      self._test(self.HEADERS, fields, sep, data,
250
                 ["f2", "f3"], ["f3"], "h", expected)
251

    
252
  def testUnusual(self):
253
    data = [
254
      ["%", "xyz"],
255
      ["%%", "abc"],
256
      ]
257
    exp = [
258
      "Field1 Field2",
259
      "%      xyz",
260
      "%%     abc",
261
      ]
262
    self._test(self.HEADERS, ["f1", "f2"], None, data,
263
               None, None, "m", exp)
264

    
265

    
266
class TestFormatQueryResult(unittest.TestCase):
267
  def test(self):
268
    fields = [
269
      objects.QueryFieldDefinition(name="name", title="Name",
270
                                   kind=constants.QFT_TEXT),
271
      objects.QueryFieldDefinition(name="size", title="Size",
272
                                   kind=constants.QFT_NUMBER),
273
      objects.QueryFieldDefinition(name="act", title="Active",
274
                                   kind=constants.QFT_BOOL),
275
      objects.QueryFieldDefinition(name="mem", title="Memory",
276
                                   kind=constants.QFT_UNIT),
277
      objects.QueryFieldDefinition(name="other", title="SomeList",
278
                                   kind=constants.QFT_OTHER),
279
      ]
280

    
281
    response = objects.QueryResponse(fields=fields, data=[
282
      [(constants.RS_NORMAL, "nodeA"), (constants.RS_NORMAL, 128),
283
       (constants.RS_NORMAL, False), (constants.RS_NORMAL, 1468006),
284
       (constants.RS_NORMAL, [])],
285
      [(constants.RS_NORMAL, "other"), (constants.RS_NORMAL, 512),
286
       (constants.RS_NORMAL, True), (constants.RS_NORMAL, 16),
287
       (constants.RS_NORMAL, [1, 2, 3])],
288
      [(constants.RS_NORMAL, "xyz"), (constants.RS_NORMAL, 1024),
289
       (constants.RS_NORMAL, True), (constants.RS_NORMAL, 4096),
290
       (constants.RS_NORMAL, [{}, {}])],
291
      ])
292

    
293
    self.assertEqual(cli.FormatQueryResult(response, unit="h", header=True),
294
      (cli.QR_NORMAL, [
295
      "Name  Size Active Memory SomeList",
296
      "nodeA  128 N        1.4T []",
297
      "other  512 Y         16M [1, 2, 3]",
298
      "xyz   1024 Y        4.0G [{}, {}]",
299
      ]))
300

    
301
  def testTimestampAndUnit(self):
302
    fields = [
303
      objects.QueryFieldDefinition(name="name", title="Name",
304
                                   kind=constants.QFT_TEXT),
305
      objects.QueryFieldDefinition(name="size", title="Size",
306
                                   kind=constants.QFT_UNIT),
307
      objects.QueryFieldDefinition(name="mtime", title="ModTime",
308
                                   kind=constants.QFT_TIMESTAMP),
309
      ]
310

    
311
    response = objects.QueryResponse(fields=fields, data=[
312
      [(constants.RS_NORMAL, "a"), (constants.RS_NORMAL, 1024),
313
       (constants.RS_NORMAL, 0)],
314
      [(constants.RS_NORMAL, "b"), (constants.RS_NORMAL, 144996),
315
       (constants.RS_NORMAL, 1291746295)],
316
      ])
317

    
318
    self.assertEqual(cli.FormatQueryResult(response, unit="m", header=True),
319
      (cli.QR_NORMAL, [
320
      "Name   Size ModTime",
321
      "a      1024 %s" % utils.FormatTime(0),
322
      "b    144996 %s" % utils.FormatTime(1291746295),
323
      ]))
324

    
325
  def testOverride(self):
326
    fields = [
327
      objects.QueryFieldDefinition(name="name", title="Name",
328
                                   kind=constants.QFT_TEXT),
329
      objects.QueryFieldDefinition(name="cust", title="Custom",
330
                                   kind=constants.QFT_OTHER),
331
      objects.QueryFieldDefinition(name="xt", title="XTime",
332
                                   kind=constants.QFT_TIMESTAMP),
333
      ]
334

    
335
    response = objects.QueryResponse(fields=fields, data=[
336
      [(constants.RS_NORMAL, "x"), (constants.RS_NORMAL, ["a", "b", "c"]),
337
       (constants.RS_NORMAL, 1234)],
338
      [(constants.RS_NORMAL, "y"), (constants.RS_NORMAL, range(10)),
339
       (constants.RS_NORMAL, 1291746295)],
340
      ])
341

    
342
    override = {
343
      "cust": (utils.CommaJoin, False),
344
      "xt": (hex, True),
345
      }
346

    
347
    self.assertEqual(cli.FormatQueryResult(response, unit="h", header=True,
348
                                           format_override=override),
349
      (cli.QR_NORMAL, [
350
      "Name Custom                            XTime",
351
      "x    a, b, c                           0x4d2",
352
      "y    0, 1, 2, 3, 4, 5, 6, 7, 8, 9 0x4cfe7bf7",
353
      ]))
354

    
355
  def testSeparator(self):
356
    fields = [
357
      objects.QueryFieldDefinition(name="name", title="Name",
358
                                   kind=constants.QFT_TEXT),
359
      objects.QueryFieldDefinition(name="count", title="Count",
360
                                   kind=constants.QFT_NUMBER),
361
      objects.QueryFieldDefinition(name="desc", title="Description",
362
                                   kind=constants.QFT_TEXT),
363
      ]
364

    
365
    response = objects.QueryResponse(fields=fields, data=[
366
      [(constants.RS_NORMAL, "instance1.example.com"),
367
       (constants.RS_NORMAL, 21125), (constants.RS_NORMAL, "Hello World!")],
368
      [(constants.RS_NORMAL, "mail.other.net"),
369
       (constants.RS_NORMAL, -9000), (constants.RS_NORMAL, "a,b,c")],
370
      ])
371

    
372
    for sep in [":", "|", "#", "|||", "###", "@@@", "@#@"]:
373
      for header in [None, "Name%sCount%sDescription" % (sep, sep)]:
374
        exp = []
375
        if header:
376
          exp.append(header)
377
        exp.extend([
378
          "instance1.example.com%s21125%sHello World!" % (sep, sep),
379
          "mail.other.net%s-9000%sa,b,c" % (sep, sep),
380
          ])
381

    
382
        self.assertEqual(cli.FormatQueryResult(response, separator=sep,
383
                                               header=bool(header)),
384
                         (cli.QR_NORMAL, exp))
385

    
386
  def testStatusWithUnknown(self):
387
    fields = [
388
      objects.QueryFieldDefinition(name="id", title="ID",
389
                                   kind=constants.QFT_NUMBER),
390
      objects.QueryFieldDefinition(name="unk", title="unk",
391
                                   kind=constants.QFT_UNKNOWN),
392
      objects.QueryFieldDefinition(name="unavail", title="Unavail",
393
                                   kind=constants.QFT_BOOL),
394
      objects.QueryFieldDefinition(name="nodata", title="NoData",
395
                                   kind=constants.QFT_TEXT),
396
      objects.QueryFieldDefinition(name="offline", title="OffLine",
397
                                   kind=constants.QFT_TEXT),
398
      ]
399

    
400
    response = objects.QueryResponse(fields=fields, data=[
401
      [(constants.RS_NORMAL, 1), (constants.RS_UNKNOWN, None),
402
       (constants.RS_NORMAL, False), (constants.RS_NORMAL, ""),
403
       (constants.RS_OFFLINE, None)],
404
      [(constants.RS_NORMAL, 2), (constants.RS_UNKNOWN, None),
405
       (constants.RS_NODATA, None), (constants.RS_NORMAL, "x"),
406
       (constants.RS_OFFLINE, None)],
407
      [(constants.RS_NORMAL, 3), (constants.RS_UNKNOWN, None),
408
       (constants.RS_NORMAL, False), (constants.RS_UNAVAIL, None),
409
       (constants.RS_OFFLINE, None)],
410
      ])
411

    
412
    self.assertEqual(cli.FormatQueryResult(response, header=True,
413
                                           separator="|", verbose=True),
414
      (cli.QR_UNKNOWN, [
415
      "ID|unk|Unavail|NoData|OffLine",
416
      "1|(unknown)|N||(offline)",
417
      "2|(unknown)|(nodata)|x|(offline)",
418
      "3|(unknown)|N|(unavail)|(offline)",
419
      ]))
420
    self.assertEqual(cli.FormatQueryResult(response, header=True,
421
                                           separator="|", verbose=False),
422
      (cli.QR_UNKNOWN, [
423
      "ID|unk|Unavail|NoData|OffLine",
424
      "1|??|N||*",
425
      "2|??|?|x|*",
426
      "3|??|N|-|*",
427
      ]))
428

    
429
  def testNoData(self):
430
    fields = [
431
      objects.QueryFieldDefinition(name="id", title="ID",
432
                                   kind=constants.QFT_NUMBER),
433
      objects.QueryFieldDefinition(name="name", title="Name",
434
                                   kind=constants.QFT_TEXT),
435
      ]
436

    
437
    response = objects.QueryResponse(fields=fields, data=[])
438

    
439
    self.assertEqual(cli.FormatQueryResult(response, header=True),
440
                     (cli.QR_NORMAL, ["ID Name"]))
441

    
442
  def testNoDataWithUnknown(self):
443
    fields = [
444
      objects.QueryFieldDefinition(name="id", title="ID",
445
                                   kind=constants.QFT_NUMBER),
446
      objects.QueryFieldDefinition(name="unk", title="unk",
447
                                   kind=constants.QFT_UNKNOWN),
448
      ]
449

    
450
    response = objects.QueryResponse(fields=fields, data=[])
451

    
452
    self.assertEqual(cli.FormatQueryResult(response, header=False),
453
                     (cli.QR_UNKNOWN, []))
454

    
455
  def testStatus(self):
456
    fields = [
457
      objects.QueryFieldDefinition(name="id", title="ID",
458
                                   kind=constants.QFT_NUMBER),
459
      objects.QueryFieldDefinition(name="unavail", title="Unavail",
460
                                   kind=constants.QFT_BOOL),
461
      objects.QueryFieldDefinition(name="nodata", title="NoData",
462
                                   kind=constants.QFT_TEXT),
463
      objects.QueryFieldDefinition(name="offline", title="OffLine",
464
                                   kind=constants.QFT_TEXT),
465
      ]
466

    
467
    response = objects.QueryResponse(fields=fields, data=[
468
      [(constants.RS_NORMAL, 1), (constants.RS_NORMAL, False),
469
       (constants.RS_NORMAL, ""), (constants.RS_OFFLINE, None)],
470
      [(constants.RS_NORMAL, 2), (constants.RS_NODATA, None),
471
       (constants.RS_NORMAL, "x"), (constants.RS_NORMAL, "abc")],
472
      [(constants.RS_NORMAL, 3), (constants.RS_NORMAL, False),
473
       (constants.RS_UNAVAIL, None), (constants.RS_OFFLINE, None)],
474
      ])
475

    
476
    self.assertEqual(cli.FormatQueryResult(response, header=False,
477
                                           separator="|", verbose=True),
478
      (cli.QR_INCOMPLETE, [
479
      "1|N||(offline)",
480
      "2|(nodata)|x|abc",
481
      "3|N|(unavail)|(offline)",
482
      ]))
483
    self.assertEqual(cli.FormatQueryResult(response, header=False,
484
                                           separator="|", verbose=False),
485
      (cli.QR_INCOMPLETE, [
486
      "1|N||*",
487
      "2|?|x|abc",
488
      "3|N|-|*",
489
      ]))
490

    
491
  def testInvalidFieldType(self):
492
    fields = [
493
      objects.QueryFieldDefinition(name="x", title="x",
494
                                   kind="#some#other#type"),
495
      ]
496

    
497
    response = objects.QueryResponse(fields=fields, data=[])
498

    
499
    self.assertRaises(NotImplementedError, cli.FormatQueryResult, response)
500

    
501
  def testInvalidFieldStatus(self):
502
    fields = [
503
      objects.QueryFieldDefinition(name="x", title="x",
504
                                   kind=constants.QFT_TEXT),
505
      ]
506

    
507
    response = objects.QueryResponse(fields=fields, data=[[(-1, None)]])
508
    self.assertRaises(NotImplementedError, cli.FormatQueryResult, response)
509

    
510
    response = objects.QueryResponse(fields=fields, data=[[(-1, "x")]])
511
    self.assertRaises(AssertionError, cli.FormatQueryResult, response)
512

    
513
  def testEmptyFieldTitle(self):
514
    fields = [
515
      objects.QueryFieldDefinition(name="x", title="",
516
                                   kind=constants.QFT_TEXT),
517
      ]
518

    
519
    response = objects.QueryResponse(fields=fields, data=[])
520
    self.assertRaises(AssertionError, cli.FormatQueryResult, response)
521

    
522

    
523
class _MockJobPollCb(cli.JobPollCbBase, cli.JobPollReportCbBase):
524
  def __init__(self, tc, job_id):
525
    self.tc = tc
526
    self.job_id = job_id
527
    self._wfjcr = []
528
    self._jobstatus = []
529
    self._expect_notchanged = False
530
    self._expect_log = []
531

    
532
  def CheckEmpty(self):
533
    self.tc.assertFalse(self._wfjcr)
534
    self.tc.assertFalse(self._jobstatus)
535
    self.tc.assertFalse(self._expect_notchanged)
536
    self.tc.assertFalse(self._expect_log)
537

    
538
  def AddWfjcResult(self, *args):
539
    self._wfjcr.append(args)
540

    
541
  def AddQueryJobsResult(self, *args):
542
    self._jobstatus.append(args)
543

    
544
  def WaitForJobChangeOnce(self, job_id, fields,
545
                           prev_job_info, prev_log_serial):
546
    self.tc.assertEqual(job_id, self.job_id)
547
    self.tc.assertEqualValues(fields, ["status"])
548
    self.tc.assertFalse(self._expect_notchanged)
549
    self.tc.assertFalse(self._expect_log)
550

    
551
    (exp_prev_job_info, exp_prev_log_serial, result) = self._wfjcr.pop(0)
552
    self.tc.assertEqualValues(prev_job_info, exp_prev_job_info)
553
    self.tc.assertEqual(prev_log_serial, exp_prev_log_serial)
554

    
555
    if result == constants.JOB_NOTCHANGED:
556
      self._expect_notchanged = True
557
    elif result:
558
      (_, logmsgs) = result
559
      if logmsgs:
560
        self._expect_log.extend(logmsgs)
561

    
562
    return result
563

    
564
  def QueryJobs(self, job_ids, fields):
565
    self.tc.assertEqual(job_ids, [self.job_id])
566
    self.tc.assertEqualValues(fields, ["status", "opstatus", "opresult"])
567
    self.tc.assertFalse(self._expect_notchanged)
568
    self.tc.assertFalse(self._expect_log)
569

    
570
    result = self._jobstatus.pop(0)
571
    self.tc.assertEqual(len(fields), len(result))
572
    return [result]
573

    
574
  def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
575
    self.tc.assertEqual(job_id, self.job_id)
576
    self.tc.assertEqualValues((serial, timestamp, log_type, log_msg),
577
                              self._expect_log.pop(0))
578

    
579
  def ReportNotChanged(self, job_id, status):
580
    self.tc.assertEqual(job_id, self.job_id)
581
    self.tc.assert_(self._expect_notchanged)
582
    self._expect_notchanged = False
583

    
584

    
585
class TestGenericPollJob(testutils.GanetiTestCase):
586
  def testSuccessWithLog(self):
587
    job_id = 29609
588
    cbs = _MockJobPollCb(self, job_id)
589

    
590
    cbs.AddWfjcResult(None, None, constants.JOB_NOTCHANGED)
591

    
592
    cbs.AddWfjcResult(None, None,
593
                      ((constants.JOB_STATUS_QUEUED, ), None))
594

    
595
    cbs.AddWfjcResult((constants.JOB_STATUS_QUEUED, ), None,
596
                      constants.JOB_NOTCHANGED)
597

    
598
    cbs.AddWfjcResult((constants.JOB_STATUS_QUEUED, ), None,
599
                      ((constants.JOB_STATUS_RUNNING, ),
600
                       [(1, utils.SplitTime(1273491611.0),
601
                         constants.ELOG_MESSAGE, "Step 1"),
602
                        (2, utils.SplitTime(1273491615.9),
603
                         constants.ELOG_MESSAGE, "Step 2"),
604
                        (3, utils.SplitTime(1273491625.02),
605
                         constants.ELOG_MESSAGE, "Step 3"),
606
                        (4, utils.SplitTime(1273491635.05),
607
                         constants.ELOG_MESSAGE, "Step 4"),
608
                        (37, utils.SplitTime(1273491645.0),
609
                         constants.ELOG_MESSAGE, "Step 5"),
610
                        (203, utils.SplitTime(127349155.0),
611
                         constants.ELOG_MESSAGE, "Step 6")]))
612

    
613
    cbs.AddWfjcResult((constants.JOB_STATUS_RUNNING, ), 203,
614
                      ((constants.JOB_STATUS_RUNNING, ),
615
                       [(300, utils.SplitTime(1273491711.01),
616
                         constants.ELOG_MESSAGE, "Step X"),
617
                        (302, utils.SplitTime(1273491815.8),
618
                         constants.ELOG_MESSAGE, "Step Y"),
619
                        (303, utils.SplitTime(1273491925.32),
620
                         constants.ELOG_MESSAGE, "Step Z")]))
621

    
622
    cbs.AddWfjcResult((constants.JOB_STATUS_RUNNING, ), 303,
623
                      ((constants.JOB_STATUS_SUCCESS, ), None))
624

    
625
    cbs.AddQueryJobsResult(constants.JOB_STATUS_SUCCESS,
626
                           [constants.OP_STATUS_SUCCESS,
627
                            constants.OP_STATUS_SUCCESS],
628
                           ["Hello World", "Foo man bar"])
629

    
630
    self.assertEqual(["Hello World", "Foo man bar"],
631
                     cli.GenericPollJob(job_id, cbs, cbs))
632
    cbs.CheckEmpty()
633

    
634
  def testJobLost(self):
635
    job_id = 13746
636

    
637
    cbs = _MockJobPollCb(self, job_id)
638
    cbs.AddWfjcResult(None, None, constants.JOB_NOTCHANGED)
639
    cbs.AddWfjcResult(None, None, None)
640
    self.assertRaises(errors.JobLost, cli.GenericPollJob, job_id, cbs, cbs)
641
    cbs.CheckEmpty()
642

    
643
  def testError(self):
644
    job_id = 31088
645

    
646
    cbs = _MockJobPollCb(self, job_id)
647
    cbs.AddWfjcResult(None, None, constants.JOB_NOTCHANGED)
648
    cbs.AddWfjcResult(None, None, ((constants.JOB_STATUS_ERROR, ), None))
649
    cbs.AddQueryJobsResult(constants.JOB_STATUS_ERROR,
650
                           [constants.OP_STATUS_SUCCESS,
651
                            constants.OP_STATUS_ERROR],
652
                           ["Hello World", "Error code 123"])
653
    self.assertRaises(errors.OpExecError, cli.GenericPollJob, job_id, cbs, cbs)
654
    cbs.CheckEmpty()
655

    
656
  def testError2(self):
657
    job_id = 22235
658

    
659
    cbs = _MockJobPollCb(self, job_id)
660
    cbs.AddWfjcResult(None, None, ((constants.JOB_STATUS_ERROR, ), None))
661
    encexc = errors.EncodeException(errors.LockError("problem"))
662
    cbs.AddQueryJobsResult(constants.JOB_STATUS_ERROR,
663
                           [constants.OP_STATUS_ERROR], [encexc])
664
    self.assertRaises(errors.LockError, cli.GenericPollJob, job_id, cbs, cbs)
665
    cbs.CheckEmpty()
666

    
667
  def testWeirdError(self):
668
    job_id = 28847
669

    
670
    cbs = _MockJobPollCb(self, job_id)
671
    cbs.AddWfjcResult(None, None, ((constants.JOB_STATUS_ERROR, ), None))
672
    cbs.AddQueryJobsResult(constants.JOB_STATUS_ERROR,
673
                           [constants.OP_STATUS_RUNNING,
674
                            constants.OP_STATUS_RUNNING],
675
                           [None, None])
676
    self.assertRaises(errors.OpExecError, cli.GenericPollJob, job_id, cbs, cbs)
677
    cbs.CheckEmpty()
678

    
679
  def testCancel(self):
680
    job_id = 4275
681

    
682
    cbs = _MockJobPollCb(self, job_id)
683
    cbs.AddWfjcResult(None, None, constants.JOB_NOTCHANGED)
684
    cbs.AddWfjcResult(None, None, ((constants.JOB_STATUS_CANCELING, ), None))
685
    cbs.AddQueryJobsResult(constants.JOB_STATUS_CANCELING,
686
                           [constants.OP_STATUS_CANCELING,
687
                            constants.OP_STATUS_CANCELING],
688
                           [None, None])
689
    self.assertRaises(errors.OpExecError, cli.GenericPollJob, job_id, cbs, cbs)
690
    cbs.CheckEmpty()
691

    
692

    
693
class TestFormatLogMessage(unittest.TestCase):
694
  def test(self):
695
    self.assertEqual(cli.FormatLogMessage(constants.ELOG_MESSAGE,
696
                                          "Hello World"),
697
                     "Hello World")
698
    self.assertRaises(TypeError, cli.FormatLogMessage,
699
                      constants.ELOG_MESSAGE, [1, 2, 3])
700

    
701
    self.assert_(cli.FormatLogMessage("some other type", (1, 2, 3)))
702

    
703

    
704
class TestParseFields(unittest.TestCase):
705
  def test(self):
706
    self.assertEqual(cli.ParseFields(None, []), [])
707
    self.assertEqual(cli.ParseFields("name,foo,hello", []),
708
                     ["name", "foo", "hello"])
709
    self.assertEqual(cli.ParseFields(None, ["def", "ault", "fields", "here"]),
710
                     ["def", "ault", "fields", "here"])
711
    self.assertEqual(cli.ParseFields("name,foo", ["def", "ault"]),
712
                     ["name", "foo"])
713
    self.assertEqual(cli.ParseFields("+name,foo", ["def", "ault"]),
714
                     ["def", "ault", "name", "foo"])
715

    
716

    
717
class TestConstants(unittest.TestCase):
718
  def testPriority(self):
719
    self.assertEqual(set(cli._PRIONAME_TO_VALUE.values()),
720
                     set(constants.OP_PRIO_SUBMIT_VALID))
721
    self.assertEqual(list(value for _, value in cli._PRIORITY_NAMES),
722
                     sorted(constants.OP_PRIO_SUBMIT_VALID, reverse=True))
723

    
724

    
725
class TestParseNicOption(unittest.TestCase):
726
  def test(self):
727
    self.assertEqual(cli.ParseNicOption([("0", { "link": "eth0", })]),
728
                     [{ "link": "eth0", }])
729
    self.assertEqual(cli.ParseNicOption([("5", { "ip": "192.0.2.7", })]),
730
                     [{}, {}, {}, {}, {}, { "ip": "192.0.2.7", }])
731

    
732
  def testErrors(self):
733
    for i in [None, "", "abc", "zero", "Hello World", "\0", []]:
734
      self.assertRaises(errors.OpPrereqError, cli.ParseNicOption,
735
                        [(i, { "link": "eth0", })])
736
      self.assertRaises(errors.OpPrereqError, cli.ParseNicOption,
737
                        [("0", i)])
738

    
739
    self.assertRaises(errors.TypeEnforcementError, cli.ParseNicOption,
740
                      [(0, { True: False, })])
741

    
742
    self.assertRaises(errors.TypeEnforcementError, cli.ParseNicOption,
743
                      [(3, { "mode": [], })])
744

    
745

    
746
class TestFormatResultError(unittest.TestCase):
747
  def testNormal(self):
748
    for verbose in [False, True]:
749
      self.assertRaises(AssertionError, cli.FormatResultError,
750
                        constants.RS_NORMAL, verbose)
751

    
752
  def testUnknown(self):
753
    for verbose in [False, True]:
754
      self.assertRaises(NotImplementedError, cli.FormatResultError,
755
                        "#some!other!status#", verbose)
756

    
757
  def test(self):
758
    for status in constants.RS_ALL:
759
      if status == constants.RS_NORMAL:
760
        continue
761

    
762
      self.assertNotEqual(cli.FormatResultError(status, False),
763
                          cli.FormatResultError(status, True))
764

    
765
      result = cli.FormatResultError(status, True)
766
      self.assertTrue(result.startswith("("))
767
      self.assertTrue(result.endswith(")"))
768

    
769

    
770
class TestGetOnlineNodes(unittest.TestCase):
771
  class _FakeClient:
772
    def __init__(self):
773
      self._query = []
774

    
775
    def AddQueryResult(self, *args):
776
      self._query.append(args)
777

    
778
    def CountPending(self):
779
      return len(self._query)
780

    
781
    def Query(self, res, fields, qfilter):
782
      if res != constants.QR_NODE:
783
        raise Exception("Querying wrong resource")
784

    
785
      (exp_fields, check_filter, result) = self._query.pop(0)
786

    
787
      if exp_fields != fields:
788
        raise Exception("Expected fields %s, got %s" % (exp_fields, fields))
789

    
790
      if not (qfilter is None or check_filter(qfilter)):
791
        raise Exception("Filter doesn't match expectations")
792

    
793
      return objects.QueryResponse(fields=None, data=result)
794

    
795
  def testEmpty(self):
796
    cl = self._FakeClient()
797

    
798
    cl.AddQueryResult(["name", "offline", "sip"], None, [])
799
    self.assertEqual(cli.GetOnlineNodes(None, cl=cl), [])
800
    self.assertEqual(cl.CountPending(), 0)
801

    
802
  def testNoSpecialFilter(self):
803
    cl = self._FakeClient()
804

    
805
    cl.AddQueryResult(["name", "offline", "sip"], None, [
806
      [(constants.RS_NORMAL, "master.example.com"),
807
       (constants.RS_NORMAL, False),
808
       (constants.RS_NORMAL, "192.0.2.1")],
809
      [(constants.RS_NORMAL, "node2.example.com"),
810
       (constants.RS_NORMAL, False),
811
       (constants.RS_NORMAL, "192.0.2.2")],
812
      ])
813
    self.assertEqual(cli.GetOnlineNodes(None, cl=cl),
814
                     ["master.example.com", "node2.example.com"])
815
    self.assertEqual(cl.CountPending(), 0)
816

    
817
  def testNoMaster(self):
818
    cl = self._FakeClient()
819

    
820
    def _CheckFilter(qfilter):
821
      self.assertEqual(qfilter, [qlang.OP_NOT, [qlang.OP_TRUE, "master"]])
822
      return True
823

    
824
    cl.AddQueryResult(["name", "offline", "sip"], _CheckFilter, [
825
      [(constants.RS_NORMAL, "node2.example.com"),
826
       (constants.RS_NORMAL, False),
827
       (constants.RS_NORMAL, "192.0.2.2")],
828
      ])
829
    self.assertEqual(cli.GetOnlineNodes(None, cl=cl, filter_master=True),
830
                     ["node2.example.com"])
831
    self.assertEqual(cl.CountPending(), 0)
832

    
833
  def testSecondaryIpAddress(self):
834
    cl = self._FakeClient()
835

    
836
    cl.AddQueryResult(["name", "offline", "sip"], None, [
837
      [(constants.RS_NORMAL, "master.example.com"),
838
       (constants.RS_NORMAL, False),
839
       (constants.RS_NORMAL, "192.0.2.1")],
840
      [(constants.RS_NORMAL, "node2.example.com"),
841
       (constants.RS_NORMAL, False),
842
       (constants.RS_NORMAL, "192.0.2.2")],
843
      ])
844
    self.assertEqual(cli.GetOnlineNodes(None, cl=cl, secondary_ips=True),
845
                     ["192.0.2.1", "192.0.2.2"])
846
    self.assertEqual(cl.CountPending(), 0)
847

    
848
  def testNoMasterFilterNodeName(self):
849
    cl = self._FakeClient()
850

    
851
    def _CheckFilter(qfilter):
852
      self.assertEqual(qfilter,
853
        [qlang.OP_AND,
854
         [qlang.OP_OR] + [[qlang.OP_EQUAL, "name", name]
855
                          for name in ["node2", "node3"]],
856
         [qlang.OP_NOT, [qlang.OP_TRUE, "master"]]])
857
      return True
858

    
859
    cl.AddQueryResult(["name", "offline", "sip"], _CheckFilter, [
860
      [(constants.RS_NORMAL, "node2.example.com"),
861
       (constants.RS_NORMAL, False),
862
       (constants.RS_NORMAL, "192.0.2.12")],
863
      [(constants.RS_NORMAL, "node3.example.com"),
864
       (constants.RS_NORMAL, False),
865
       (constants.RS_NORMAL, "192.0.2.13")],
866
      ])
867
    self.assertEqual(cli.GetOnlineNodes(["node2", "node3"], cl=cl,
868
                                        secondary_ips=True, filter_master=True),
869
                     ["192.0.2.12", "192.0.2.13"])
870
    self.assertEqual(cl.CountPending(), 0)
871

    
872
  def testOfflineNodes(self):
873
    cl = self._FakeClient()
874

    
875
    cl.AddQueryResult(["name", "offline", "sip"], None, [
876
      [(constants.RS_NORMAL, "master.example.com"),
877
       (constants.RS_NORMAL, False),
878
       (constants.RS_NORMAL, "192.0.2.1")],
879
      [(constants.RS_NORMAL, "node2.example.com"),
880
       (constants.RS_NORMAL, True),
881
       (constants.RS_NORMAL, "192.0.2.2")],
882
      [(constants.RS_NORMAL, "node3.example.com"),
883
       (constants.RS_NORMAL, True),
884
       (constants.RS_NORMAL, "192.0.2.3")],
885
      ])
886
    self.assertEqual(cli.GetOnlineNodes(None, cl=cl, nowarn=True),
887
                     ["master.example.com"])
888
    self.assertEqual(cl.CountPending(), 0)
889

    
890
  def testNodeGroup(self):
891
    cl = self._FakeClient()
892

    
893
    def _CheckFilter(qfilter):
894
      self.assertEqual(qfilter,
895
        [qlang.OP_OR, [qlang.OP_EQUAL, "group", "foobar"],
896
                      [qlang.OP_EQUAL, "group.uuid", "foobar"]])
897
      return True
898

    
899
    cl.AddQueryResult(["name", "offline", "sip"], _CheckFilter, [
900
      [(constants.RS_NORMAL, "master.example.com"),
901
       (constants.RS_NORMAL, False),
902
       (constants.RS_NORMAL, "192.0.2.1")],
903
      [(constants.RS_NORMAL, "node3.example.com"),
904
       (constants.RS_NORMAL, False),
905
       (constants.RS_NORMAL, "192.0.2.3")],
906
      ])
907
    self.assertEqual(cli.GetOnlineNodes(None, cl=cl, nodegroup="foobar"),
908
                     ["master.example.com", "node3.example.com"])
909
    self.assertEqual(cl.CountPending(), 0)
910

    
911

    
912
class TestFormatTimestamp(unittest.TestCase):
913
  def testGood(self):
914
    self.assertEqual(cli.FormatTimestamp((0, 1)),
915
                     time.strftime("%F %T", time.localtime(0)) + ".000001")
916
    self.assertEqual(cli.FormatTimestamp((1332944009, 17376)),
917
                     (time.strftime("%F %T", time.localtime(1332944009)) +
918
                      ".017376"))
919

    
920
  def testWrong(self):
921
    for i in [0, [], {}, "", [1]]:
922
      self.assertEqual(cli.FormatTimestamp(i), "?")
923

    
924

    
925
class TestFormatUsage(unittest.TestCase):
926
  def test(self):
927
    binary = "gnt-unittest"
928
    commands = {
929
      "cmdA":
930
        (NotImplemented, NotImplemented, NotImplemented, NotImplemented,
931
         "description of A"),
932
      "bbb":
933
        (NotImplemented, NotImplemented, NotImplemented, NotImplemented,
934
         "Hello World," * 10),
935
      "longname":
936
        (NotImplemented, NotImplemented, NotImplemented, NotImplemented,
937
         "Another description"),
938
      }
939

    
940
    self.assertEqual(list(cli._FormatUsage(binary, commands)), [
941
      "Usage: gnt-unittest {command} [options...] [argument...]",
942
      "gnt-unittest <command> --help to see details, or man gnt-unittest",
943
      "",
944
      "Commands:",
945
      (" bbb      - Hello World,Hello World,Hello World,Hello World,Hello"
946
       " World,Hello"),
947
      "            World,Hello World,Hello World,Hello World,Hello World,",
948
      " cmdA     - description of A",
949
      " longname - Another description",
950
      "",
951
      ])
952

    
953

    
954
class TestParseArgs(unittest.TestCase):
955
  def testNoArguments(self):
956
    for argv in [[], ["gnt-unittest"]]:
957
      try:
958
        cli._ParseArgs("gnt-unittest", argv, {}, {}, set())
959
      except cli._ShowUsage, err:
960
        self.assertTrue(err.exit_error)
961
      else:
962
        self.fail("Did not raise exception")
963

    
964
  def testVersion(self):
965
    for argv in [["test", "--version"], ["test", "--version", "somethingelse"]]:
966
      try:
967
        cli._ParseArgs("test", argv, {}, {}, set())
968
      except cli._ShowVersion:
969
        pass
970
      else:
971
        self.fail("Did not raise exception")
972

    
973
  def testHelp(self):
974
    for argv in [["test", "--help"], ["test", "--help", "somethingelse"]]:
975
      try:
976
        cli._ParseArgs("test", argv, {}, {}, set())
977
      except cli._ShowUsage, err:
978
        self.assertFalse(err.exit_error)
979
      else:
980
        self.fail("Did not raise exception")
981

    
982
  def testUnknownCommandOrAlias(self):
983
    for argv in [["test", "list"], ["test", "somethingelse", "--help"]]:
984
      try:
985
        cli._ParseArgs("test", argv, {}, {}, set())
986
      except cli._ShowUsage, err:
987
        self.assertTrue(err.exit_error)
988
      else:
989
        self.fail("Did not raise exception")
990

    
991
  def testInvalidAliasList(self):
992
    cmd = {
993
      "list": NotImplemented,
994
      "foo": NotImplemented,
995
      }
996
    aliases = {
997
      "list": NotImplemented,
998
      "foo": NotImplemented,
999
      }
1000
    assert sorted(cmd.keys()) == sorted(aliases.keys())
1001
    self.assertRaises(AssertionError, cli._ParseArgs, "test",
1002
                      ["test", "list"], cmd, aliases, set())
1003

    
1004
  def testAliasForNonExistantCommand(self):
1005
    cmd = {}
1006
    aliases = {
1007
      "list": NotImplemented,
1008
      }
1009
    self.assertRaises(errors.ProgrammerError, cli._ParseArgs, "test",
1010
                      ["test", "list"], cmd, aliases, set())
1011

    
1012

    
1013
class TestQftNames(unittest.TestCase):
1014
  def testComplete(self):
1015
    self.assertEqual(frozenset(cli._QFT_NAMES), constants.QFT_ALL)
1016

    
1017
  def testUnique(self):
1018
    lcnames = map(lambda s: s.lower(), cli._QFT_NAMES.values())
1019
    self.assertFalse(utils.FindDuplicates(lcnames))
1020

    
1021
  def testUppercase(self):
1022
    for name in cli._QFT_NAMES.values():
1023
      self.assertEqual(name[0], name[0].upper())
1024

    
1025

    
1026
class TestFieldDescValues(unittest.TestCase):
1027
  def testKnownKind(self):
1028
    fdef = objects.QueryFieldDefinition(name="aname",
1029
                                        title="Atitle",
1030
                                        kind=constants.QFT_TEXT,
1031
                                        doc="aaa doc aaa")
1032
    self.assertEqual(cli._FieldDescValues(fdef),
1033
                     ["aname", "Text", "Atitle", "aaa doc aaa"])
1034

    
1035
  def testUnknownKind(self):
1036
    kind = "#foo#"
1037

    
1038
    self.assertFalse(kind in constants.QFT_ALL)
1039
    self.assertFalse(kind in cli._QFT_NAMES)
1040

    
1041
    fdef = objects.QueryFieldDefinition(name="zname", title="Ztitle",
1042
                                        kind=kind, doc="zzz doc zzz")
1043
    self.assertEqual(cli._FieldDescValues(fdef),
1044
                     ["zname", kind, "Ztitle", "zzz doc zzz"])
1045

    
1046

    
1047
if __name__ == "__main__":
1048
  testutils.GanetiTestProgram()