Statistics
| Branch: | Tag: | Revision:

root / test / ganeti.cli_unittest.py @ 956631b6

History | View | Annotate | Download (31.5 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
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()