Statistics
| Branch: | Tag: | Revision:

root / test / py / ganeti.cli_unittest.py @ 9db0b351

History | View | Annotate | Download (58.3 kB)

1
#!/usr/bin/python
2
#
3

    
4
# Copyright (C) 2008, 2011, 2012, 2013 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 copy
25
import testutils
26
import time
27
import unittest
28
import yaml
29
from cStringIO import StringIO
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
  RESULT_NOPREFIX = {"a": "b", "c": {}, "no_d": {}, "-e": {}}
75

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

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

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

    
91

    
92
class TestIdentKeyVal(unittest.TestCase):
93
  """Testing case for cli.check_ident_key_val"""
94

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

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

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

    
121
  @staticmethod
122
  def _csikv(value):
123
    return cli._SplitIdentKeyVal("opt", value, False)
124

    
125
  def testIdentKeyValNoPrefix(self):
126
    """Test identkeyval without prefixes"""
127
    test_cases = [
128
      ("foo:bar", None),
129
      ("foo:no_bar", None),
130
      ("foo:bar=baz,bar=baz", None),
131
      ("foo",
132
       ("foo", {})),
133
      ("foo:bar=baz",
134
       ("foo", {"bar": "baz"})),
135
      ("no_foo:-1=baz,no_op=3",
136
       ("no_foo", {"-1": "baz", "no_op": "3"})),
137
      ]
138
    for (arg, res) in test_cases:
139
      if res is None:
140
        self.assertRaises(ParameterError, self._csikv, arg)
141
      else:
142
        self.assertEqual(self._csikv(arg), res)
143

    
144

    
145
class TestMultilistIdentKeyVal(unittest.TestCase):
146
  """Test for cli.check_multilist_ident_key_val()"""
147

    
148
  @staticmethod
149
  def _cmikv(value):
150
    return cli.check_multilist_ident_key_val("option", "opt", value)
151

    
152
  def testListIdentKeyVal(self):
153
    test_cases = [
154
      ("",
155
       None),
156
      ("foo", [
157
        {"foo": {}}
158
        ]),
159
      ("foo:bar=baz", [
160
        {"foo": {"bar": "baz"}}
161
        ]),
162
      ("foo:bar=baz/foo:bat=bad",
163
       None),
164
      ("foo:abc=42/bar:def=11", [
165
        {"foo": {"abc": "42"},
166
         "bar": {"def": "11"}}
167
        ]),
168
      ("foo:abc=42/bar:def=11,ghi=07", [
169
        {"foo": {"abc": "42"},
170
         "bar": {"def": "11", "ghi": "07"}}
171
        ]),
172
      ("foo:abc=42/bar:def=11//",
173
       None),
174
      ("foo:abc=42/bar:def=11,ghi=07//foobar", [
175
        {"foo": {"abc": "42"},
176
         "bar": {"def": "11", "ghi": "07"}},
177
        {"foobar": {}}
178
        ]),
179
      ("foo:abc=42/bar:def=11,ghi=07//foobar:xyz=88", [
180
        {"foo": {"abc": "42"},
181
         "bar": {"def": "11", "ghi": "07"}},
182
        {"foobar": {"xyz": "88"}}
183
        ]),
184
      ("foo:abc=42/bar:def=11,ghi=07//foobar:xyz=88/foo:uvw=314", [
185
        {"foo": {"abc": "42"},
186
         "bar": {"def": "11", "ghi": "07"}},
187
        {"foobar": {"xyz": "88"},
188
         "foo": {"uvw": "314"}}
189
        ]),
190
      ]
191
    for (arg, res) in test_cases:
192
      if res is None:
193
        self.assertRaises(ParameterError, self._cmikv, arg)
194
      else:
195
        self.assertEqual(res, self._cmikv(arg))
196

    
197

    
198
class TestToStream(unittest.TestCase):
199
  """Test the ToStream functions"""
200

    
201
  def testBasic(self):
202
    for data in ["foo",
203
                 "foo %s",
204
                 "foo %(test)s",
205
                 "foo %s %s",
206
                 "",
207
                 ]:
208
      buf = StringIO()
209
      cli._ToStream(buf, data)
210
      self.failUnlessEqual(buf.getvalue(), data + "\n")
211

    
212
  def testParams(self):
213
      buf = StringIO()
214
      cli._ToStream(buf, "foo %s", 1)
215
      self.failUnlessEqual(buf.getvalue(), "foo 1\n")
216
      buf = StringIO()
217
      cli._ToStream(buf, "foo %s", (15,16))
218
      self.failUnlessEqual(buf.getvalue(), "foo (15, 16)\n")
219
      buf = StringIO()
220
      cli._ToStream(buf, "foo %s %s", "a", "b")
221
      self.failUnlessEqual(buf.getvalue(), "foo a b\n")
222

    
223

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

    
227
  FIELDS1 = ["f1", "f2"]
228
  DATA1 = [
229
    ["abc", 1234],
230
    ["foobar", 56],
231
    ["b", -14],
232
    ]
233

    
234
  def _test(self, headers, fields, separator, data,
235
            numfields, unitfields, units, expected):
236
    table = cli.GenerateTable(headers, fields, separator, data,
237
                              numfields=numfields, unitfields=unitfields,
238
                              units=units)
239
    self.assertEqual(table, expected)
240

    
241
  def testPlain(self):
242
    exp = [
243
      "Field1 Field2",
244
      "abc    1234",
245
      "foobar 56",
246
      "b      -14",
247
      ]
248
    self._test(self.HEADERS, self.FIELDS1, None, self.DATA1,
249
               None, None, "m", exp)
250

    
251
  def testNoFields(self):
252
    self._test(self.HEADERS, [], None, [[], []],
253
               None, None, "m", ["", "", ""])
254
    self._test(None, [], None, [[], []],
255
               None, None, "m", ["", ""])
256

    
257
  def testSeparator(self):
258
    for sep in ["#", ":", ",", "^", "!", "%", "|", "###", "%%", "!!!", "||"]:
259
      exp = [
260
        "Field1%sField2" % sep,
261
        "abc%s1234" % sep,
262
        "foobar%s56" % sep,
263
        "b%s-14" % sep,
264
        ]
265
      self._test(self.HEADERS, self.FIELDS1, sep, self.DATA1,
266
                 None, None, "m", exp)
267

    
268
  def testNoHeader(self):
269
    exp = [
270
      "abc    1234",
271
      "foobar 56",
272
      "b      -14",
273
      ]
274
    self._test(None, self.FIELDS1, None, self.DATA1,
275
               None, None, "m", exp)
276

    
277
  def testUnknownField(self):
278
    headers = {
279
      "f1": "Field1",
280
      }
281
    exp = [
282
      "Field1 UNKNOWN",
283
      "abc    1234",
284
      "foobar 56",
285
      "b      -14",
286
      ]
287
    self._test(headers, ["f1", "UNKNOWN"], None, self.DATA1,
288
               None, None, "m", exp)
289

    
290
  def testNumfields(self):
291
    fields = ["f1", "f2", "f3"]
292
    data = [
293
      ["abc", 1234, 0],
294
      ["foobar", 56, 3],
295
      ["b", -14, "-"],
296
      ]
297
    exp = [
298
      "Field1 Field2 Field3",
299
      "abc      1234      0",
300
      "foobar     56      3",
301
      "b         -14      -",
302
      ]
303
    self._test(self.HEADERS, fields, None, data,
304
               ["f2", "f3"], None, "m", exp)
305

    
306
  def testUnitfields(self):
307
    expnosep = [
308
      "Field1 Field2 Field3",
309
      "abc      1234     0M",
310
      "foobar     56     3M",
311
      "b         -14      -",
312
      ]
313

    
314
    expsep = [
315
      "Field1:Field2:Field3",
316
      "abc:1234:0M",
317
      "foobar:56:3M",
318
      "b:-14:-",
319
      ]
320

    
321
    for sep, expected in [(None, expnosep), (":", expsep)]:
322
      fields = ["f1", "f2", "f3"]
323
      data = [
324
        ["abc", 1234, 0],
325
        ["foobar", 56, 3],
326
        ["b", -14, "-"],
327
        ]
328
      self._test(self.HEADERS, fields, sep, data,
329
                 ["f2", "f3"], ["f3"], "h", expected)
330

    
331
  def testUnusual(self):
332
    data = [
333
      ["%", "xyz"],
334
      ["%%", "abc"],
335
      ]
336
    exp = [
337
      "Field1 Field2",
338
      "%      xyz",
339
      "%%     abc",
340
      ]
341
    self._test(self.HEADERS, ["f1", "f2"], None, data,
342
               None, None, "m", exp)
343

    
344

    
345
class TestFormatQueryResult(unittest.TestCase):
346
  def test(self):
347
    fields = [
348
      objects.QueryFieldDefinition(name="name", title="Name",
349
                                   kind=constants.QFT_TEXT),
350
      objects.QueryFieldDefinition(name="size", title="Size",
351
                                   kind=constants.QFT_NUMBER),
352
      objects.QueryFieldDefinition(name="act", title="Active",
353
                                   kind=constants.QFT_BOOL),
354
      objects.QueryFieldDefinition(name="mem", title="Memory",
355
                                   kind=constants.QFT_UNIT),
356
      objects.QueryFieldDefinition(name="other", title="SomeList",
357
                                   kind=constants.QFT_OTHER),
358
      ]
359

    
360
    response = objects.QueryResponse(fields=fields, data=[
361
      [(constants.RS_NORMAL, "nodeA"), (constants.RS_NORMAL, 128),
362
       (constants.RS_NORMAL, False), (constants.RS_NORMAL, 1468006),
363
       (constants.RS_NORMAL, [])],
364
      [(constants.RS_NORMAL, "other"), (constants.RS_NORMAL, 512),
365
       (constants.RS_NORMAL, True), (constants.RS_NORMAL, 16),
366
       (constants.RS_NORMAL, [1, 2, 3])],
367
      [(constants.RS_NORMAL, "xyz"), (constants.RS_NORMAL, 1024),
368
       (constants.RS_NORMAL, True), (constants.RS_NORMAL, 4096),
369
       (constants.RS_NORMAL, [{}, {}])],
370
      ])
371

    
372
    self.assertEqual(cli.FormatQueryResult(response, unit="h", header=True),
373
      (cli.QR_NORMAL, [
374
      "Name  Size Active Memory SomeList",
375
      "nodeA  128 N        1.4T []",
376
      "other  512 Y         16M [1, 2, 3]",
377
      "xyz   1024 Y        4.0G [{}, {}]",
378
      ]))
379

    
380
  def testTimestampAndUnit(self):
381
    fields = [
382
      objects.QueryFieldDefinition(name="name", title="Name",
383
                                   kind=constants.QFT_TEXT),
384
      objects.QueryFieldDefinition(name="size", title="Size",
385
                                   kind=constants.QFT_UNIT),
386
      objects.QueryFieldDefinition(name="mtime", title="ModTime",
387
                                   kind=constants.QFT_TIMESTAMP),
388
      ]
389

    
390
    response = objects.QueryResponse(fields=fields, data=[
391
      [(constants.RS_NORMAL, "a"), (constants.RS_NORMAL, 1024),
392
       (constants.RS_NORMAL, 0)],
393
      [(constants.RS_NORMAL, "b"), (constants.RS_NORMAL, 144996),
394
       (constants.RS_NORMAL, 1291746295)],
395
      ])
396

    
397
    self.assertEqual(cli.FormatQueryResult(response, unit="m", header=True),
398
      (cli.QR_NORMAL, [
399
      "Name   Size ModTime",
400
      "a      1024 %s" % utils.FormatTime(0),
401
      "b    144996 %s" % utils.FormatTime(1291746295),
402
      ]))
403

    
404
  def testOverride(self):
405
    fields = [
406
      objects.QueryFieldDefinition(name="name", title="Name",
407
                                   kind=constants.QFT_TEXT),
408
      objects.QueryFieldDefinition(name="cust", title="Custom",
409
                                   kind=constants.QFT_OTHER),
410
      objects.QueryFieldDefinition(name="xt", title="XTime",
411
                                   kind=constants.QFT_TIMESTAMP),
412
      ]
413

    
414
    response = objects.QueryResponse(fields=fields, data=[
415
      [(constants.RS_NORMAL, "x"), (constants.RS_NORMAL, ["a", "b", "c"]),
416
       (constants.RS_NORMAL, 1234)],
417
      [(constants.RS_NORMAL, "y"), (constants.RS_NORMAL, range(10)),
418
       (constants.RS_NORMAL, 1291746295)],
419
      ])
420

    
421
    override = {
422
      "cust": (utils.CommaJoin, False),
423
      "xt": (hex, True),
424
      }
425

    
426
    self.assertEqual(cli.FormatQueryResult(response, unit="h", header=True,
427
                                           format_override=override),
428
      (cli.QR_NORMAL, [
429
      "Name Custom                            XTime",
430
      "x    a, b, c                           0x4d2",
431
      "y    0, 1, 2, 3, 4, 5, 6, 7, 8, 9 0x4cfe7bf7",
432
      ]))
433

    
434
  def testSeparator(self):
435
    fields = [
436
      objects.QueryFieldDefinition(name="name", title="Name",
437
                                   kind=constants.QFT_TEXT),
438
      objects.QueryFieldDefinition(name="count", title="Count",
439
                                   kind=constants.QFT_NUMBER),
440
      objects.QueryFieldDefinition(name="desc", title="Description",
441
                                   kind=constants.QFT_TEXT),
442
      ]
443

    
444
    response = objects.QueryResponse(fields=fields, data=[
445
      [(constants.RS_NORMAL, "instance1.example.com"),
446
       (constants.RS_NORMAL, 21125), (constants.RS_NORMAL, "Hello World!")],
447
      [(constants.RS_NORMAL, "mail.other.net"),
448
       (constants.RS_NORMAL, -9000), (constants.RS_NORMAL, "a,b,c")],
449
      ])
450

    
451
    for sep in [":", "|", "#", "|||", "###", "@@@", "@#@"]:
452
      for header in [None, "Name%sCount%sDescription" % (sep, sep)]:
453
        exp = []
454
        if header:
455
          exp.append(header)
456
        exp.extend([
457
          "instance1.example.com%s21125%sHello World!" % (sep, sep),
458
          "mail.other.net%s-9000%sa,b,c" % (sep, sep),
459
          ])
460

    
461
        self.assertEqual(cli.FormatQueryResult(response, separator=sep,
462
                                               header=bool(header)),
463
                         (cli.QR_NORMAL, exp))
464

    
465
  def testStatusWithUnknown(self):
466
    fields = [
467
      objects.QueryFieldDefinition(name="id", title="ID",
468
                                   kind=constants.QFT_NUMBER),
469
      objects.QueryFieldDefinition(name="unk", title="unk",
470
                                   kind=constants.QFT_UNKNOWN),
471
      objects.QueryFieldDefinition(name="unavail", title="Unavail",
472
                                   kind=constants.QFT_BOOL),
473
      objects.QueryFieldDefinition(name="nodata", title="NoData",
474
                                   kind=constants.QFT_TEXT),
475
      objects.QueryFieldDefinition(name="offline", title="OffLine",
476
                                   kind=constants.QFT_TEXT),
477
      ]
478

    
479
    response = objects.QueryResponse(fields=fields, data=[
480
      [(constants.RS_NORMAL, 1), (constants.RS_UNKNOWN, None),
481
       (constants.RS_NORMAL, False), (constants.RS_NORMAL, ""),
482
       (constants.RS_OFFLINE, None)],
483
      [(constants.RS_NORMAL, 2), (constants.RS_UNKNOWN, None),
484
       (constants.RS_NODATA, None), (constants.RS_NORMAL, "x"),
485
       (constants.RS_OFFLINE, None)],
486
      [(constants.RS_NORMAL, 3), (constants.RS_UNKNOWN, None),
487
       (constants.RS_NORMAL, False), (constants.RS_UNAVAIL, None),
488
       (constants.RS_OFFLINE, None)],
489
      ])
490

    
491
    self.assertEqual(cli.FormatQueryResult(response, header=True,
492
                                           separator="|", verbose=True),
493
      (cli.QR_UNKNOWN, [
494
      "ID|unk|Unavail|NoData|OffLine",
495
      "1|(unknown)|N||(offline)",
496
      "2|(unknown)|(nodata)|x|(offline)",
497
      "3|(unknown)|N|(unavail)|(offline)",
498
      ]))
499
    self.assertEqual(cli.FormatQueryResult(response, header=True,
500
                                           separator="|", verbose=False),
501
      (cli.QR_UNKNOWN, [
502
      "ID|unk|Unavail|NoData|OffLine",
503
      "1|??|N||*",
504
      "2|??|?|x|*",
505
      "3|??|N|-|*",
506
      ]))
507

    
508
  def testNoData(self):
509
    fields = [
510
      objects.QueryFieldDefinition(name="id", title="ID",
511
                                   kind=constants.QFT_NUMBER),
512
      objects.QueryFieldDefinition(name="name", title="Name",
513
                                   kind=constants.QFT_TEXT),
514
      ]
515

    
516
    response = objects.QueryResponse(fields=fields, data=[])
517

    
518
    self.assertEqual(cli.FormatQueryResult(response, header=True),
519
                     (cli.QR_NORMAL, ["ID Name"]))
520

    
521
  def testNoDataWithUnknown(self):
522
    fields = [
523
      objects.QueryFieldDefinition(name="id", title="ID",
524
                                   kind=constants.QFT_NUMBER),
525
      objects.QueryFieldDefinition(name="unk", title="unk",
526
                                   kind=constants.QFT_UNKNOWN),
527
      ]
528

    
529
    response = objects.QueryResponse(fields=fields, data=[])
530

    
531
    self.assertEqual(cli.FormatQueryResult(response, header=False),
532
                     (cli.QR_UNKNOWN, []))
533

    
534
  def testStatus(self):
535
    fields = [
536
      objects.QueryFieldDefinition(name="id", title="ID",
537
                                   kind=constants.QFT_NUMBER),
538
      objects.QueryFieldDefinition(name="unavail", title="Unavail",
539
                                   kind=constants.QFT_BOOL),
540
      objects.QueryFieldDefinition(name="nodata", title="NoData",
541
                                   kind=constants.QFT_TEXT),
542
      objects.QueryFieldDefinition(name="offline", title="OffLine",
543
                                   kind=constants.QFT_TEXT),
544
      ]
545

    
546
    response = objects.QueryResponse(fields=fields, data=[
547
      [(constants.RS_NORMAL, 1), (constants.RS_NORMAL, False),
548
       (constants.RS_NORMAL, ""), (constants.RS_OFFLINE, None)],
549
      [(constants.RS_NORMAL, 2), (constants.RS_NODATA, None),
550
       (constants.RS_NORMAL, "x"), (constants.RS_NORMAL, "abc")],
551
      [(constants.RS_NORMAL, 3), (constants.RS_NORMAL, False),
552
       (constants.RS_UNAVAIL, None), (constants.RS_OFFLINE, None)],
553
      ])
554

    
555
    self.assertEqual(cli.FormatQueryResult(response, header=False,
556
                                           separator="|", verbose=True),
557
      (cli.QR_INCOMPLETE, [
558
      "1|N||(offline)",
559
      "2|(nodata)|x|abc",
560
      "3|N|(unavail)|(offline)",
561
      ]))
562
    self.assertEqual(cli.FormatQueryResult(response, header=False,
563
                                           separator="|", verbose=False),
564
      (cli.QR_INCOMPLETE, [
565
      "1|N||*",
566
      "2|?|x|abc",
567
      "3|N|-|*",
568
      ]))
569

    
570
  def testInvalidFieldType(self):
571
    fields = [
572
      objects.QueryFieldDefinition(name="x", title="x",
573
                                   kind="#some#other#type"),
574
      ]
575

    
576
    response = objects.QueryResponse(fields=fields, data=[])
577

    
578
    self.assertRaises(NotImplementedError, cli.FormatQueryResult, response)
579

    
580
  def testInvalidFieldStatus(self):
581
    fields = [
582
      objects.QueryFieldDefinition(name="x", title="x",
583
                                   kind=constants.QFT_TEXT),
584
      ]
585

    
586
    response = objects.QueryResponse(fields=fields, data=[[(-1, None)]])
587
    self.assertRaises(NotImplementedError, cli.FormatQueryResult, response)
588

    
589
    response = objects.QueryResponse(fields=fields, data=[[(-1, "x")]])
590
    self.assertRaises(AssertionError, cli.FormatQueryResult, response)
591

    
592
  def testEmptyFieldTitle(self):
593
    fields = [
594
      objects.QueryFieldDefinition(name="x", title="",
595
                                   kind=constants.QFT_TEXT),
596
      ]
597

    
598
    response = objects.QueryResponse(fields=fields, data=[])
599
    self.assertRaises(AssertionError, cli.FormatQueryResult, response)
600

    
601

    
602
class _MockJobPollCb(cli.JobPollCbBase, cli.JobPollReportCbBase):
603
  def __init__(self, tc, job_id):
604
    self.tc = tc
605
    self.job_id = job_id
606
    self._wfjcr = []
607
    self._jobstatus = []
608
    self._expect_notchanged = False
609
    self._expect_log = []
610

    
611
  def CheckEmpty(self):
612
    self.tc.assertFalse(self._wfjcr)
613
    self.tc.assertFalse(self._jobstatus)
614
    self.tc.assertFalse(self._expect_notchanged)
615
    self.tc.assertFalse(self._expect_log)
616

    
617
  def AddWfjcResult(self, *args):
618
    self._wfjcr.append(args)
619

    
620
  def AddQueryJobsResult(self, *args):
621
    self._jobstatus.append(args)
622

    
623
  def WaitForJobChangeOnce(self, job_id, fields,
624
                           prev_job_info, prev_log_serial):
625
    self.tc.assertEqual(job_id, self.job_id)
626
    self.tc.assertEqualValues(fields, ["status"])
627
    self.tc.assertFalse(self._expect_notchanged)
628
    self.tc.assertFalse(self._expect_log)
629

    
630
    (exp_prev_job_info, exp_prev_log_serial, result) = self._wfjcr.pop(0)
631
    self.tc.assertEqualValues(prev_job_info, exp_prev_job_info)
632
    self.tc.assertEqual(prev_log_serial, exp_prev_log_serial)
633

    
634
    if result == constants.JOB_NOTCHANGED:
635
      self._expect_notchanged = True
636
    elif result:
637
      (_, logmsgs) = result
638
      if logmsgs:
639
        self._expect_log.extend(logmsgs)
640

    
641
    return result
642

    
643
  def QueryJobs(self, job_ids, fields):
644
    self.tc.assertEqual(job_ids, [self.job_id])
645
    self.tc.assertEqualValues(fields, ["status", "opstatus", "opresult"])
646
    self.tc.assertFalse(self._expect_notchanged)
647
    self.tc.assertFalse(self._expect_log)
648

    
649
    result = self._jobstatus.pop(0)
650
    self.tc.assertEqual(len(fields), len(result))
651
    return [result]
652

    
653
  def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
654
    self.tc.assertEqual(job_id, self.job_id)
655
    self.tc.assertEqualValues((serial, timestamp, log_type, log_msg),
656
                              self._expect_log.pop(0))
657

    
658
  def ReportNotChanged(self, job_id, status):
659
    self.tc.assertEqual(job_id, self.job_id)
660
    self.tc.assert_(self._expect_notchanged)
661
    self._expect_notchanged = False
662

    
663

    
664
class TestGenericPollJob(testutils.GanetiTestCase):
665
  def testSuccessWithLog(self):
666
    job_id = 29609
667
    cbs = _MockJobPollCb(self, job_id)
668

    
669
    cbs.AddWfjcResult(None, None, constants.JOB_NOTCHANGED)
670

    
671
    cbs.AddWfjcResult(None, None,
672
                      ((constants.JOB_STATUS_QUEUED, ), None))
673

    
674
    cbs.AddWfjcResult((constants.JOB_STATUS_QUEUED, ), None,
675
                      constants.JOB_NOTCHANGED)
676

    
677
    cbs.AddWfjcResult((constants.JOB_STATUS_QUEUED, ), None,
678
                      ((constants.JOB_STATUS_RUNNING, ),
679
                       [(1, utils.SplitTime(1273491611.0),
680
                         constants.ELOG_MESSAGE, "Step 1"),
681
                        (2, utils.SplitTime(1273491615.9),
682
                         constants.ELOG_MESSAGE, "Step 2"),
683
                        (3, utils.SplitTime(1273491625.02),
684
                         constants.ELOG_MESSAGE, "Step 3"),
685
                        (4, utils.SplitTime(1273491635.05),
686
                         constants.ELOG_MESSAGE, "Step 4"),
687
                        (37, utils.SplitTime(1273491645.0),
688
                         constants.ELOG_MESSAGE, "Step 5"),
689
                        (203, utils.SplitTime(127349155.0),
690
                         constants.ELOG_MESSAGE, "Step 6")]))
691

    
692
    cbs.AddWfjcResult((constants.JOB_STATUS_RUNNING, ), 203,
693
                      ((constants.JOB_STATUS_RUNNING, ),
694
                       [(300, utils.SplitTime(1273491711.01),
695
                         constants.ELOG_MESSAGE, "Step X"),
696
                        (302, utils.SplitTime(1273491815.8),
697
                         constants.ELOG_MESSAGE, "Step Y"),
698
                        (303, utils.SplitTime(1273491925.32),
699
                         constants.ELOG_MESSAGE, "Step Z")]))
700

    
701
    cbs.AddWfjcResult((constants.JOB_STATUS_RUNNING, ), 303,
702
                      ((constants.JOB_STATUS_SUCCESS, ), None))
703

    
704
    cbs.AddQueryJobsResult(constants.JOB_STATUS_SUCCESS,
705
                           [constants.OP_STATUS_SUCCESS,
706
                            constants.OP_STATUS_SUCCESS],
707
                           ["Hello World", "Foo man bar"])
708

    
709
    self.assertEqual(["Hello World", "Foo man bar"],
710
                     cli.GenericPollJob(job_id, cbs, cbs))
711
    cbs.CheckEmpty()
712

    
713
  def testJobLost(self):
714
    job_id = 13746
715

    
716
    cbs = _MockJobPollCb(self, job_id)
717
    cbs.AddWfjcResult(None, None, constants.JOB_NOTCHANGED)
718
    cbs.AddWfjcResult(None, None, None)
719
    self.assertRaises(errors.JobLost, cli.GenericPollJob, job_id, cbs, cbs)
720
    cbs.CheckEmpty()
721

    
722
  def testError(self):
723
    job_id = 31088
724

    
725
    cbs = _MockJobPollCb(self, job_id)
726
    cbs.AddWfjcResult(None, None, constants.JOB_NOTCHANGED)
727
    cbs.AddWfjcResult(None, None, ((constants.JOB_STATUS_ERROR, ), None))
728
    cbs.AddQueryJobsResult(constants.JOB_STATUS_ERROR,
729
                           [constants.OP_STATUS_SUCCESS,
730
                            constants.OP_STATUS_ERROR],
731
                           ["Hello World", "Error code 123"])
732
    self.assertRaises(errors.OpExecError, cli.GenericPollJob, job_id, cbs, cbs)
733
    cbs.CheckEmpty()
734

    
735
  def testError2(self):
736
    job_id = 22235
737

    
738
    cbs = _MockJobPollCb(self, job_id)
739
    cbs.AddWfjcResult(None, None, ((constants.JOB_STATUS_ERROR, ), None))
740
    encexc = errors.EncodeException(errors.LockError("problem"))
741
    cbs.AddQueryJobsResult(constants.JOB_STATUS_ERROR,
742
                           [constants.OP_STATUS_ERROR], [encexc])
743
    self.assertRaises(errors.LockError, cli.GenericPollJob, job_id, cbs, cbs)
744
    cbs.CheckEmpty()
745

    
746
  def testWeirdError(self):
747
    job_id = 28847
748

    
749
    cbs = _MockJobPollCb(self, job_id)
750
    cbs.AddWfjcResult(None, None, ((constants.JOB_STATUS_ERROR, ), None))
751
    cbs.AddQueryJobsResult(constants.JOB_STATUS_ERROR,
752
                           [constants.OP_STATUS_RUNNING,
753
                            constants.OP_STATUS_RUNNING],
754
                           [None, None])
755
    self.assertRaises(errors.OpExecError, cli.GenericPollJob, job_id, cbs, cbs)
756
    cbs.CheckEmpty()
757

    
758
  def testCancel(self):
759
    job_id = 4275
760

    
761
    cbs = _MockJobPollCb(self, job_id)
762
    cbs.AddWfjcResult(None, None, constants.JOB_NOTCHANGED)
763
    cbs.AddWfjcResult(None, None, ((constants.JOB_STATUS_CANCELING, ), None))
764
    cbs.AddQueryJobsResult(constants.JOB_STATUS_CANCELING,
765
                           [constants.OP_STATUS_CANCELING,
766
                            constants.OP_STATUS_CANCELING],
767
                           [None, None])
768
    self.assertRaises(errors.OpExecError, cli.GenericPollJob, job_id, cbs, cbs)
769
    cbs.CheckEmpty()
770

    
771

    
772
class TestFormatLogMessage(unittest.TestCase):
773
  def test(self):
774
    self.assertEqual(cli.FormatLogMessage(constants.ELOG_MESSAGE,
775
                                          "Hello World"),
776
                     "Hello World")
777
    self.assertRaises(TypeError, cli.FormatLogMessage,
778
                      constants.ELOG_MESSAGE, [1, 2, 3])
779

    
780
    self.assert_(cli.FormatLogMessage("some other type", (1, 2, 3)))
781

    
782

    
783
class TestParseFields(unittest.TestCase):
784
  def test(self):
785
    self.assertEqual(cli.ParseFields(None, []), [])
786
    self.assertEqual(cli.ParseFields("name,foo,hello", []),
787
                     ["name", "foo", "hello"])
788
    self.assertEqual(cli.ParseFields(None, ["def", "ault", "fields", "here"]),
789
                     ["def", "ault", "fields", "here"])
790
    self.assertEqual(cli.ParseFields("name,foo", ["def", "ault"]),
791
                     ["name", "foo"])
792
    self.assertEqual(cli.ParseFields("+name,foo", ["def", "ault"]),
793
                     ["def", "ault", "name", "foo"])
794

    
795

    
796
class TestConstants(unittest.TestCase):
797
  def testPriority(self):
798
    self.assertEqual(set(cli._PRIONAME_TO_VALUE.values()),
799
                     set(constants.OP_PRIO_SUBMIT_VALID))
800
    self.assertEqual(list(value for _, value in cli._PRIORITY_NAMES),
801
                     sorted(constants.OP_PRIO_SUBMIT_VALID, reverse=True))
802

    
803

    
804
class TestParseNicOption(unittest.TestCase):
805
  def test(self):
806
    self.assertEqual(cli.ParseNicOption([("0", { "link": "eth0", })]),
807
                     [{ "link": "eth0", }])
808
    self.assertEqual(cli.ParseNicOption([("5", { "ip": "192.0.2.7", })]),
809
                     [{}, {}, {}, {}, {}, { "ip": "192.0.2.7", }])
810

    
811
  def testErrors(self):
812
    for i in [None, "", "abc", "zero", "Hello World", "\0", []]:
813
      self.assertRaises(errors.OpPrereqError, cli.ParseNicOption,
814
                        [(i, { "link": "eth0", })])
815
      self.assertRaises(errors.OpPrereqError, cli.ParseNicOption,
816
                        [("0", i)])
817

    
818
    self.assertRaises(errors.TypeEnforcementError, cli.ParseNicOption,
819
                      [(0, { True: False, })])
820

    
821
    self.assertRaises(errors.TypeEnforcementError, cli.ParseNicOption,
822
                      [(3, { "mode": [], })])
823

    
824

    
825
class TestFormatResultError(unittest.TestCase):
826
  def testNormal(self):
827
    for verbose in [False, True]:
828
      self.assertRaises(AssertionError, cli.FormatResultError,
829
                        constants.RS_NORMAL, verbose)
830

    
831
  def testUnknown(self):
832
    for verbose in [False, True]:
833
      self.assertRaises(NotImplementedError, cli.FormatResultError,
834
                        "#some!other!status#", verbose)
835

    
836
  def test(self):
837
    for status in constants.RS_ALL:
838
      if status == constants.RS_NORMAL:
839
        continue
840

    
841
      self.assertNotEqual(cli.FormatResultError(status, False),
842
                          cli.FormatResultError(status, True))
843

    
844
      result = cli.FormatResultError(status, True)
845
      self.assertTrue(result.startswith("("))
846
      self.assertTrue(result.endswith(")"))
847

    
848

    
849
class TestGetOnlineNodes(unittest.TestCase):
850
  class _FakeClient:
851
    def __init__(self):
852
      self._query = []
853

    
854
    def AddQueryResult(self, *args):
855
      self._query.append(args)
856

    
857
    def CountPending(self):
858
      return len(self._query)
859

    
860
    def Query(self, res, fields, qfilter):
861
      if res != constants.QR_NODE:
862
        raise Exception("Querying wrong resource")
863

    
864
      (exp_fields, check_filter, result) = self._query.pop(0)
865

    
866
      if exp_fields != fields:
867
        raise Exception("Expected fields %s, got %s" % (exp_fields, fields))
868

    
869
      if not (qfilter is None or check_filter(qfilter)):
870
        raise Exception("Filter doesn't match expectations")
871

    
872
      return objects.QueryResponse(fields=None, data=result)
873

    
874
  def testEmpty(self):
875
    cl = self._FakeClient()
876

    
877
    cl.AddQueryResult(["name", "offline", "sip"], None, [])
878
    self.assertEqual(cli.GetOnlineNodes(None, cl=cl), [])
879
    self.assertEqual(cl.CountPending(), 0)
880

    
881
  def testNoSpecialFilter(self):
882
    cl = self._FakeClient()
883

    
884
    cl.AddQueryResult(["name", "offline", "sip"], None, [
885
      [(constants.RS_NORMAL, "master.example.com"),
886
       (constants.RS_NORMAL, False),
887
       (constants.RS_NORMAL, "192.0.2.1")],
888
      [(constants.RS_NORMAL, "node2.example.com"),
889
       (constants.RS_NORMAL, False),
890
       (constants.RS_NORMAL, "192.0.2.2")],
891
      ])
892
    self.assertEqual(cli.GetOnlineNodes(None, cl=cl),
893
                     ["master.example.com", "node2.example.com"])
894
    self.assertEqual(cl.CountPending(), 0)
895

    
896
  def testNoMaster(self):
897
    cl = self._FakeClient()
898

    
899
    def _CheckFilter(qfilter):
900
      self.assertEqual(qfilter, [qlang.OP_NOT, [qlang.OP_TRUE, "master"]])
901
      return True
902

    
903
    cl.AddQueryResult(["name", "offline", "sip"], _CheckFilter, [
904
      [(constants.RS_NORMAL, "node2.example.com"),
905
       (constants.RS_NORMAL, False),
906
       (constants.RS_NORMAL, "192.0.2.2")],
907
      ])
908
    self.assertEqual(cli.GetOnlineNodes(None, cl=cl, filter_master=True),
909
                     ["node2.example.com"])
910
    self.assertEqual(cl.CountPending(), 0)
911

    
912
  def testSecondaryIpAddress(self):
913
    cl = self._FakeClient()
914

    
915
    cl.AddQueryResult(["name", "offline", "sip"], None, [
916
      [(constants.RS_NORMAL, "master.example.com"),
917
       (constants.RS_NORMAL, False),
918
       (constants.RS_NORMAL, "192.0.2.1")],
919
      [(constants.RS_NORMAL, "node2.example.com"),
920
       (constants.RS_NORMAL, False),
921
       (constants.RS_NORMAL, "192.0.2.2")],
922
      ])
923
    self.assertEqual(cli.GetOnlineNodes(None, cl=cl, secondary_ips=True),
924
                     ["192.0.2.1", "192.0.2.2"])
925
    self.assertEqual(cl.CountPending(), 0)
926

    
927
  def testNoMasterFilterNodeName(self):
928
    cl = self._FakeClient()
929

    
930
    def _CheckFilter(qfilter):
931
      self.assertEqual(qfilter,
932
        [qlang.OP_AND,
933
         [qlang.OP_OR] + [[qlang.OP_EQUAL, "name", name]
934
                          for name in ["node2", "node3"]],
935
         [qlang.OP_NOT, [qlang.OP_TRUE, "master"]]])
936
      return True
937

    
938
    cl.AddQueryResult(["name", "offline", "sip"], _CheckFilter, [
939
      [(constants.RS_NORMAL, "node2.example.com"),
940
       (constants.RS_NORMAL, False),
941
       (constants.RS_NORMAL, "192.0.2.12")],
942
      [(constants.RS_NORMAL, "node3.example.com"),
943
       (constants.RS_NORMAL, False),
944
       (constants.RS_NORMAL, "192.0.2.13")],
945
      ])
946
    self.assertEqual(cli.GetOnlineNodes(["node2", "node3"], cl=cl,
947
                                        secondary_ips=True, filter_master=True),
948
                     ["192.0.2.12", "192.0.2.13"])
949
    self.assertEqual(cl.CountPending(), 0)
950

    
951
  def testOfflineNodes(self):
952
    cl = self._FakeClient()
953

    
954
    cl.AddQueryResult(["name", "offline", "sip"], None, [
955
      [(constants.RS_NORMAL, "master.example.com"),
956
       (constants.RS_NORMAL, False),
957
       (constants.RS_NORMAL, "192.0.2.1")],
958
      [(constants.RS_NORMAL, "node2.example.com"),
959
       (constants.RS_NORMAL, True),
960
       (constants.RS_NORMAL, "192.0.2.2")],
961
      [(constants.RS_NORMAL, "node3.example.com"),
962
       (constants.RS_NORMAL, True),
963
       (constants.RS_NORMAL, "192.0.2.3")],
964
      ])
965
    self.assertEqual(cli.GetOnlineNodes(None, cl=cl, nowarn=True),
966
                     ["master.example.com"])
967
    self.assertEqual(cl.CountPending(), 0)
968

    
969
  def testNodeGroup(self):
970
    cl = self._FakeClient()
971

    
972
    def _CheckFilter(qfilter):
973
      self.assertEqual(qfilter,
974
        [qlang.OP_OR, [qlang.OP_EQUAL, "group", "foobar"],
975
                      [qlang.OP_EQUAL, "group.uuid", "foobar"]])
976
      return True
977

    
978
    cl.AddQueryResult(["name", "offline", "sip"], _CheckFilter, [
979
      [(constants.RS_NORMAL, "master.example.com"),
980
       (constants.RS_NORMAL, False),
981
       (constants.RS_NORMAL, "192.0.2.1")],
982
      [(constants.RS_NORMAL, "node3.example.com"),
983
       (constants.RS_NORMAL, False),
984
       (constants.RS_NORMAL, "192.0.2.3")],
985
      ])
986
    self.assertEqual(cli.GetOnlineNodes(None, cl=cl, nodegroup="foobar"),
987
                     ["master.example.com", "node3.example.com"])
988
    self.assertEqual(cl.CountPending(), 0)
989

    
990

    
991
class TestFormatTimestamp(unittest.TestCase):
992
  def testGood(self):
993
    self.assertEqual(cli.FormatTimestamp((0, 1)),
994
                     time.strftime("%F %T", time.localtime(0)) + ".000001")
995
    self.assertEqual(cli.FormatTimestamp((1332944009, 17376)),
996
                     (time.strftime("%F %T", time.localtime(1332944009)) +
997
                      ".017376"))
998

    
999
  def testWrong(self):
1000
    for i in [0, [], {}, "", [1]]:
1001
      self.assertEqual(cli.FormatTimestamp(i), "?")
1002

    
1003

    
1004
class TestFormatUsage(unittest.TestCase):
1005
  def test(self):
1006
    binary = "gnt-unittest"
1007
    commands = {
1008
      "cmdA":
1009
        (NotImplemented, NotImplemented, NotImplemented, NotImplemented,
1010
         "description of A"),
1011
      "bbb":
1012
        (NotImplemented, NotImplemented, NotImplemented, NotImplemented,
1013
         "Hello World," * 10),
1014
      "longname":
1015
        (NotImplemented, NotImplemented, NotImplemented, NotImplemented,
1016
         "Another description"),
1017
      }
1018

    
1019
    self.assertEqual(list(cli._FormatUsage(binary, commands)), [
1020
      "Usage: gnt-unittest {command} [options...] [argument...]",
1021
      "gnt-unittest <command> --help to see details, or man gnt-unittest",
1022
      "",
1023
      "Commands:",
1024
      (" bbb      - Hello World,Hello World,Hello World,Hello World,Hello"
1025
       " World,Hello"),
1026
      "            World,Hello World,Hello World,Hello World,Hello World,",
1027
      " cmdA     - description of A",
1028
      " longname - Another description",
1029
      "",
1030
      ])
1031

    
1032

    
1033
class TestParseArgs(unittest.TestCase):
1034
  def testNoArguments(self):
1035
    for argv in [[], ["gnt-unittest"]]:
1036
      try:
1037
        cli._ParseArgs("gnt-unittest", argv, {}, {}, set())
1038
      except cli._ShowUsage, err:
1039
        self.assertTrue(err.exit_error)
1040
      else:
1041
        self.fail("Did not raise exception")
1042

    
1043
  def testVersion(self):
1044
    for argv in [["test", "--version"], ["test", "--version", "somethingelse"]]:
1045
      try:
1046
        cli._ParseArgs("test", argv, {}, {}, set())
1047
      except cli._ShowVersion:
1048
        pass
1049
      else:
1050
        self.fail("Did not raise exception")
1051

    
1052
  def testHelp(self):
1053
    for argv in [["test", "--help"], ["test", "--help", "somethingelse"]]:
1054
      try:
1055
        cli._ParseArgs("test", argv, {}, {}, set())
1056
      except cli._ShowUsage, err:
1057
        self.assertFalse(err.exit_error)
1058
      else:
1059
        self.fail("Did not raise exception")
1060

    
1061
  def testUnknownCommandOrAlias(self):
1062
    for argv in [["test", "list"], ["test", "somethingelse", "--help"]]:
1063
      try:
1064
        cli._ParseArgs("test", argv, {}, {}, set())
1065
      except cli._ShowUsage, err:
1066
        self.assertTrue(err.exit_error)
1067
      else:
1068
        self.fail("Did not raise exception")
1069

    
1070
  def testInvalidAliasList(self):
1071
    cmd = {
1072
      "list": NotImplemented,
1073
      "foo": NotImplemented,
1074
      }
1075
    aliases = {
1076
      "list": NotImplemented,
1077
      "foo": NotImplemented,
1078
      }
1079
    assert sorted(cmd.keys()) == sorted(aliases.keys())
1080
    self.assertRaises(AssertionError, cli._ParseArgs, "test",
1081
                      ["test", "list"], cmd, aliases, set())
1082

    
1083
  def testAliasForNonExistantCommand(self):
1084
    cmd = {}
1085
    aliases = {
1086
      "list": NotImplemented,
1087
      }
1088
    self.assertRaises(errors.ProgrammerError, cli._ParseArgs, "test",
1089
                      ["test", "list"], cmd, aliases, set())
1090

    
1091

    
1092
class TestQftNames(unittest.TestCase):
1093
  def testComplete(self):
1094
    self.assertEqual(frozenset(cli._QFT_NAMES), constants.QFT_ALL)
1095

    
1096
  def testUnique(self):
1097
    lcnames = map(lambda s: s.lower(), cli._QFT_NAMES.values())
1098
    self.assertFalse(utils.FindDuplicates(lcnames))
1099

    
1100
  def testUppercase(self):
1101
    for name in cli._QFT_NAMES.values():
1102
      self.assertEqual(name[0], name[0].upper())
1103

    
1104

    
1105
class TestFieldDescValues(unittest.TestCase):
1106
  def testKnownKind(self):
1107
    fdef = objects.QueryFieldDefinition(name="aname",
1108
                                        title="Atitle",
1109
                                        kind=constants.QFT_TEXT,
1110
                                        doc="aaa doc aaa")
1111
    self.assertEqual(cli._FieldDescValues(fdef),
1112
                     ["aname", "Text", "Atitle", "aaa doc aaa"])
1113

    
1114
  def testUnknownKind(self):
1115
    kind = "#foo#"
1116

    
1117
    self.assertFalse(kind in constants.QFT_ALL)
1118
    self.assertFalse(kind in cli._QFT_NAMES)
1119

    
1120
    fdef = objects.QueryFieldDefinition(name="zname", title="Ztitle",
1121
                                        kind=kind, doc="zzz doc zzz")
1122
    self.assertEqual(cli._FieldDescValues(fdef),
1123
                     ["zname", kind, "Ztitle", "zzz doc zzz"])
1124

    
1125

    
1126
class TestSerializeGenericInfo(unittest.TestCase):
1127
  """Test case for cli._SerializeGenericInfo"""
1128
  def _RunTest(self, data, expected):
1129
    buf = StringIO()
1130
    cli._SerializeGenericInfo(buf, data, 0)
1131
    self.assertEqual(buf.getvalue(), expected)
1132

    
1133
  def testSimple(self):
1134
    test_samples = [
1135
      ("abc", "abc\n"),
1136
      ([], "\n"),
1137
      ({}, "\n"),
1138
      (["1", "2", "3"], "- 1\n- 2\n- 3\n"),
1139
      ([("z", "26")], "z: 26\n"),
1140
      ({"z": "26"}, "z: 26\n"),
1141
      ([("z", "26"), ("a", "1")], "z: 26\na: 1\n"),
1142
      ({"z": "26", "a": "1"}, "a: 1\nz: 26\n"),
1143
      ]
1144
    for (data, expected) in test_samples:
1145
      self._RunTest(data, expected)
1146

    
1147
  def testLists(self):
1148
    adict = {
1149
      "aa": "11",
1150
      "bb": "22",
1151
      "cc": "33",
1152
      }
1153
    adict_exp = ("- aa: 11\n"
1154
                 "  bb: 22\n"
1155
                 "  cc: 33\n")
1156
    anobj = [
1157
      ("zz", "11"),
1158
      ("ww", "33"),
1159
      ("xx", "22"),
1160
      ]
1161
    anobj_exp = ("- zz: 11\n"
1162
                 "  ww: 33\n"
1163
                 "  xx: 22\n")
1164
    alist = ["aa", "cc", "bb"]
1165
    alist_exp = ("- - aa\n"
1166
                 "  - cc\n"
1167
                 "  - bb\n")
1168
    test_samples = [
1169
      (adict, adict_exp),
1170
      (anobj, anobj_exp),
1171
      (alist, alist_exp),
1172
      ]
1173
    for (base_data, base_expected) in test_samples:
1174
      for k in range(1, 4):
1175
        data = k * [base_data]
1176
        expected = k * base_expected
1177
        self._RunTest(data, expected)
1178

    
1179
  def testDictionaries(self):
1180
    data = [
1181
      ("aaa", ["x", "y"]),
1182
      ("bbb", {
1183
          "w": "1",
1184
          "z": "2",
1185
          }),
1186
      ("ccc", [
1187
          ("xyz", "123"),
1188
          ("efg", "456"),
1189
          ]),
1190
      ]
1191
    expected = ("aaa: \n"
1192
                "  - x\n"
1193
                "  - y\n"
1194
                "bbb: \n"
1195
                "  w: 1\n"
1196
                "  z: 2\n"
1197
                "ccc: \n"
1198
                "  xyz: 123\n"
1199
                "  efg: 456\n")
1200
    self._RunTest(data, expected)
1201
    self._RunTest(dict(data), expected)
1202

    
1203

    
1204
class TestFormatPolicyInfo(unittest.TestCase):
1205
  """Test case for cli.FormatPolicyInfo.
1206

1207
  These tests rely on cli._SerializeGenericInfo (tested elsewhere).
1208

1209
  """
1210
  def setUp(self):
1211
    # Policies are big, and we want to see the difference in case of an error
1212
    self.maxDiff = None
1213

    
1214
  def _RenameDictItem(self, parsed, old, new):
1215
    self.assertTrue(old in parsed)
1216
    self.assertTrue(new not in parsed)
1217
    parsed[new] = parsed[old]
1218
    del parsed[old]
1219

    
1220
  def _TranslateParsedNames(self, parsed):
1221
    for (pretty, raw) in [
1222
      ("bounds specs", constants.ISPECS_MINMAX),
1223
      ("allowed disk templates", constants.IPOLICY_DTS)
1224
      ]:
1225
      self._RenameDictItem(parsed, pretty, raw)
1226
    for minmax in parsed[constants.ISPECS_MINMAX]:
1227
      for key in minmax:
1228
        keyparts = key.split("/", 1)
1229
        if len(keyparts) > 1:
1230
          self._RenameDictItem(minmax, key, keyparts[0])
1231
    self.assertTrue(constants.IPOLICY_DTS in parsed)
1232
    parsed[constants.IPOLICY_DTS] = yaml.load("[%s]" %
1233
                                              parsed[constants.IPOLICY_DTS])
1234

    
1235
  @staticmethod
1236
  def _PrintAndParsePolicy(custom, effective, iscluster):
1237
    formatted = cli.FormatPolicyInfo(custom, effective, iscluster)
1238
    buf = StringIO()
1239
    cli._SerializeGenericInfo(buf, formatted, 0)
1240
    return yaml.load(buf.getvalue())
1241

    
1242
  def _PrintAndCheckParsed(self, policy):
1243
    parsed = self._PrintAndParsePolicy(policy, NotImplemented, True)
1244
    self._TranslateParsedNames(parsed)
1245
    self.assertEqual(parsed, policy)
1246

    
1247
  def _CompareClusterGroupItems(self, cluster, group, skip=None):
1248
    if isinstance(group, dict):
1249
      self.assertTrue(isinstance(cluster, dict))
1250
      if skip is None:
1251
        skip = frozenset()
1252
      self.assertEqual(frozenset(cluster.keys()).difference(skip),
1253
                       frozenset(group.keys()))
1254
      for key in group:
1255
        self._CompareClusterGroupItems(cluster[key], group[key])
1256
    elif isinstance(group, list):
1257
      self.assertTrue(isinstance(cluster, list))
1258
      self.assertEqual(len(cluster), len(group))
1259
      for (cval, gval) in zip(cluster, group):
1260
        self._CompareClusterGroupItems(cval, gval)
1261
    else:
1262
      self.assertTrue(isinstance(group, basestring))
1263
      self.assertEqual("default (%s)" % cluster, group)
1264

    
1265
  def _TestClusterVsGroup(self, policy):
1266
    cluster = self._PrintAndParsePolicy(policy, NotImplemented, True)
1267
    group = self._PrintAndParsePolicy({}, policy, False)
1268
    self._CompareClusterGroupItems(cluster, group, ["std"])
1269

    
1270
  def testWithDefaults(self):
1271
    self._PrintAndCheckParsed(constants.IPOLICY_DEFAULTS)
1272
    self._TestClusterVsGroup(constants.IPOLICY_DEFAULTS)
1273

    
1274

    
1275
class TestCreateIPolicyFromOpts(unittest.TestCase):
1276
  """Test case for cli.CreateIPolicyFromOpts."""
1277
  def setUp(self):
1278
    # Policies are big, and we want to see the difference in case of an error
1279
    self.maxDiff = None
1280

    
1281
  def _RecursiveCheckMergedDicts(self, default_pol, diff_pol, merged_pol,
1282
                                 merge_minmax=False):
1283
    self.assertTrue(type(default_pol) is dict)
1284
    self.assertTrue(type(diff_pol) is dict)
1285
    self.assertTrue(type(merged_pol) is dict)
1286
    self.assertEqual(frozenset(default_pol.keys()),
1287
                     frozenset(merged_pol.keys()))
1288
    for (key, val) in merged_pol.items():
1289
      if key in diff_pol:
1290
        if type(val) is dict:
1291
          self._RecursiveCheckMergedDicts(default_pol[key], diff_pol[key], val)
1292
        elif (merge_minmax and key == "minmax" and type(val) is list and
1293
              len(val) == 1):
1294
          self.assertEqual(len(default_pol[key]), 1)
1295
          self.assertEqual(len(diff_pol[key]), 1)
1296
          self._RecursiveCheckMergedDicts(default_pol[key][0],
1297
                                          diff_pol[key][0], val[0])
1298
        else:
1299
          self.assertEqual(val, diff_pol[key])
1300
      else:
1301
        self.assertEqual(val, default_pol[key])
1302

    
1303
  def testClusterPolicy(self):
1304
    pol0 = cli.CreateIPolicyFromOpts(
1305
      ispecs_mem_size={},
1306
      ispecs_cpu_count={},
1307
      ispecs_disk_count={},
1308
      ispecs_disk_size={},
1309
      ispecs_nic_count={},
1310
      ipolicy_disk_templates=None,
1311
      ipolicy_vcpu_ratio=None,
1312
      ipolicy_spindle_ratio=None,
1313
      fill_all=True
1314
      )
1315
    self.assertEqual(pol0, constants.IPOLICY_DEFAULTS)
1316

    
1317
    exp_pol1 = {
1318
      constants.ISPECS_MINMAX: [
1319
        {
1320
          constants.ISPECS_MIN: {
1321
            constants.ISPEC_CPU_COUNT: 2,
1322
            constants.ISPEC_DISK_COUNT: 1,
1323
            },
1324
          constants.ISPECS_MAX: {
1325
            constants.ISPEC_MEM_SIZE: 12*1024,
1326
            constants.ISPEC_DISK_COUNT: 2,
1327
            },
1328
          },
1329
        ],
1330
      constants.ISPECS_STD: {
1331
        constants.ISPEC_CPU_COUNT: 2,
1332
        constants.ISPEC_DISK_COUNT: 2,
1333
        },
1334
      constants.IPOLICY_VCPU_RATIO: 3.1,
1335
      }
1336
    pol1 = cli.CreateIPolicyFromOpts(
1337
      ispecs_mem_size={"max": "12g"},
1338
      ispecs_cpu_count={"min": 2, "std": 2},
1339
      ispecs_disk_count={"min": 1, "max": 2, "std": 2},
1340
      ispecs_disk_size={},
1341
      ispecs_nic_count={},
1342
      ipolicy_disk_templates=None,
1343
      ipolicy_vcpu_ratio=3.1,
1344
      ipolicy_spindle_ratio=None,
1345
      fill_all=True
1346
      )
1347
    self._RecursiveCheckMergedDicts(constants.IPOLICY_DEFAULTS,
1348
                                    exp_pol1, pol1, merge_minmax=True)
1349

    
1350
    exp_pol2 = {
1351
      constants.ISPECS_MINMAX: [
1352
        {
1353
          constants.ISPECS_MIN: {
1354
            constants.ISPEC_DISK_SIZE: 512,
1355
            constants.ISPEC_NIC_COUNT: 2,
1356
            },
1357
          constants.ISPECS_MAX: {
1358
            constants.ISPEC_NIC_COUNT: 3,
1359
            },
1360
          },
1361
        ],
1362
      constants.ISPECS_STD: {
1363
        constants.ISPEC_CPU_COUNT: 2,
1364
        constants.ISPEC_NIC_COUNT: 3,
1365
        },
1366
      constants.IPOLICY_SPINDLE_RATIO: 1.3,
1367
      constants.IPOLICY_DTS: ["templates"],
1368
      }
1369
    pol2 = cli.CreateIPolicyFromOpts(
1370
      ispecs_mem_size={},
1371
      ispecs_cpu_count={"std": 2},
1372
      ispecs_disk_count={},
1373
      ispecs_disk_size={"min": "0.5g"},
1374
      ispecs_nic_count={"min": 2, "max": 3, "std": 3},
1375
      ipolicy_disk_templates=["templates"],
1376
      ipolicy_vcpu_ratio=None,
1377
      ipolicy_spindle_ratio=1.3,
1378
      fill_all=True
1379
      )
1380
    self._RecursiveCheckMergedDicts(constants.IPOLICY_DEFAULTS,
1381
                                      exp_pol2, pol2, merge_minmax=True)
1382

    
1383
    for fill_all in [False, True]:
1384
      exp_pol3 = {
1385
        constants.ISPECS_STD: {
1386
          constants.ISPEC_CPU_COUNT: 2,
1387
          constants.ISPEC_NIC_COUNT: 3,
1388
          },
1389
        }
1390
      pol3 = cli.CreateIPolicyFromOpts(
1391
        std_ispecs={
1392
          constants.ISPEC_CPU_COUNT: "2",
1393
          constants.ISPEC_NIC_COUNT: "3",
1394
          },
1395
        ipolicy_disk_templates=None,
1396
        ipolicy_vcpu_ratio=None,
1397
        ipolicy_spindle_ratio=None,
1398
        fill_all=fill_all
1399
        )
1400
      if fill_all:
1401
        self._RecursiveCheckMergedDicts(constants.IPOLICY_DEFAULTS,
1402
                                        exp_pol3, pol3, merge_minmax=True)
1403
      else:
1404
        self.assertEqual(pol3, exp_pol3)
1405

    
1406
  def testPartialPolicy(self):
1407
    exp_pol0 = objects.MakeEmptyIPolicy()
1408
    pol0 = cli.CreateIPolicyFromOpts(
1409
      minmax_ispecs=None,
1410
      std_ispecs=None,
1411
      ipolicy_disk_templates=None,
1412
      ipolicy_vcpu_ratio=None,
1413
      ipolicy_spindle_ratio=None,
1414
      fill_all=False
1415
      )
1416
    self.assertEqual(pol0, exp_pol0)
1417

    
1418
    exp_pol1 = {
1419
      constants.IPOLICY_VCPU_RATIO: 3.1,
1420
      }
1421
    pol1 = cli.CreateIPolicyFromOpts(
1422
      minmax_ispecs=None,
1423
      std_ispecs=None,
1424
      ipolicy_disk_templates=None,
1425
      ipolicy_vcpu_ratio=3.1,
1426
      ipolicy_spindle_ratio=None,
1427
      fill_all=False
1428
      )
1429
    self.assertEqual(pol1, exp_pol1)
1430

    
1431
    exp_pol2 = {
1432
      constants.IPOLICY_SPINDLE_RATIO: 1.3,
1433
      constants.IPOLICY_DTS: ["templates"],
1434
      }
1435
    pol2 = cli.CreateIPolicyFromOpts(
1436
      minmax_ispecs=None,
1437
      std_ispecs=None,
1438
      ipolicy_disk_templates=["templates"],
1439
      ipolicy_vcpu_ratio=None,
1440
      ipolicy_spindle_ratio=1.3,
1441
      fill_all=False
1442
      )
1443
    self.assertEqual(pol2, exp_pol2)
1444

    
1445
  def _TestInvalidISpecs(self, minmax_ispecs, std_ispecs, fail=True):
1446
    for fill_all in [False, True]:
1447
      if fail:
1448
        self.assertRaises((errors.OpPrereqError,
1449
                           errors.UnitParseError,
1450
                           errors.TypeEnforcementError),
1451
                          cli.CreateIPolicyFromOpts,
1452
                          minmax_ispecs=minmax_ispecs,
1453
                          std_ispecs=std_ispecs,
1454
                          fill_all=fill_all)
1455
      else:
1456
        cli.CreateIPolicyFromOpts(minmax_ispecs=minmax_ispecs,
1457
                                  std_ispecs=std_ispecs,
1458
                                  fill_all=fill_all)
1459

    
1460
  def testInvalidPolicies(self):
1461
    self.assertRaises(AssertionError, cli.CreateIPolicyFromOpts,
1462
                      std_ispecs={constants.ISPEC_MEM_SIZE: 1024},
1463
                      ipolicy_disk_templates=None, ipolicy_vcpu_ratio=None,
1464
                      ipolicy_spindle_ratio=None, group_ipolicy=True)
1465
    self.assertRaises(errors.OpPrereqError, cli.CreateIPolicyFromOpts,
1466
                      ispecs_mem_size={"wrong": "x"}, ispecs_cpu_count={},
1467
                      ispecs_disk_count={}, ispecs_disk_size={},
1468
                      ispecs_nic_count={}, ipolicy_disk_templates=None,
1469
                      ipolicy_vcpu_ratio=None, ipolicy_spindle_ratio=None,
1470
                      fill_all=True)
1471
    self.assertRaises(errors.TypeEnforcementError, cli.CreateIPolicyFromOpts,
1472
                      ispecs_mem_size={}, ispecs_cpu_count={"min": "default"},
1473
                      ispecs_disk_count={}, ispecs_disk_size={},
1474
                      ispecs_nic_count={}, ipolicy_disk_templates=None,
1475
                      ipolicy_vcpu_ratio=None, ipolicy_spindle_ratio=None,
1476
                      fill_all=True)
1477

    
1478
    good_mmspecs = [
1479
      constants.ISPECS_MINMAX_DEFAULTS,
1480
      constants.ISPECS_MINMAX_DEFAULTS,
1481
      ]
1482
    self._TestInvalidISpecs(good_mmspecs, None, fail=False)
1483
    broken_mmspecs = copy.deepcopy(good_mmspecs)
1484
    for minmaxpair in broken_mmspecs:
1485
      for key in constants.ISPECS_MINMAX_KEYS:
1486
        for par in constants.ISPECS_PARAMETERS:
1487
          old = minmaxpair[key][par]
1488
          del minmaxpair[key][par]
1489
          self._TestInvalidISpecs(broken_mmspecs, None)
1490
          minmaxpair[key][par] = "invalid"
1491
          self._TestInvalidISpecs(broken_mmspecs, None)
1492
          minmaxpair[key][par] = old
1493
        minmaxpair[key]["invalid_key"] = None
1494
        self._TestInvalidISpecs(broken_mmspecs, None)
1495
        del minmaxpair[key]["invalid_key"]
1496
      minmaxpair["invalid_key"] = None
1497
      self._TestInvalidISpecs(broken_mmspecs, None)
1498
      del minmaxpair["invalid_key"]
1499
      assert broken_mmspecs == good_mmspecs
1500

    
1501
    good_stdspecs = constants.IPOLICY_DEFAULTS[constants.ISPECS_STD]
1502
    self._TestInvalidISpecs(None, good_stdspecs, fail=False)
1503
    broken_stdspecs = copy.deepcopy(good_stdspecs)
1504
    for par in constants.ISPECS_PARAMETERS:
1505
      old = broken_stdspecs[par]
1506
      broken_stdspecs[par] = "invalid"
1507
      self._TestInvalidISpecs(None, broken_stdspecs)
1508
      broken_stdspecs[par] = old
1509
    broken_stdspecs["invalid_key"] = None
1510
    self._TestInvalidISpecs(None, broken_stdspecs)
1511
    del broken_stdspecs["invalid_key"]
1512
    assert broken_stdspecs == good_stdspecs
1513

    
1514
  def testAllowedValues(self):
1515
    allowedv = "blah"
1516
    exp_pol1 = {
1517
      constants.ISPECS_MINMAX: allowedv,
1518
      constants.IPOLICY_DTS: allowedv,
1519
      constants.IPOLICY_VCPU_RATIO: allowedv,
1520
      constants.IPOLICY_SPINDLE_RATIO: allowedv,
1521
      }
1522
    pol1 = cli.CreateIPolicyFromOpts(minmax_ispecs=[{allowedv: {}}],
1523
                                     std_ispecs=None,
1524
                                     ipolicy_disk_templates=allowedv,
1525
                                     ipolicy_vcpu_ratio=allowedv,
1526
                                     ipolicy_spindle_ratio=allowedv,
1527
                                     allowed_values=[allowedv])
1528
    self.assertEqual(pol1, exp_pol1)
1529

    
1530
  @staticmethod
1531
  def _ConvertSpecToStrings(spec):
1532
    ret = {}
1533
    for (par, val) in spec.items():
1534
      ret[par] = str(val)
1535
    return ret
1536

    
1537
  def _CheckNewStyleSpecsCall(self, exp_ipolicy, minmax_ispecs, std_ispecs,
1538
                              group_ipolicy, fill_all):
1539
    ipolicy = cli.CreateIPolicyFromOpts(minmax_ispecs=minmax_ispecs,
1540
                                        std_ispecs=std_ispecs,
1541
                                        group_ipolicy=group_ipolicy,
1542
                                        fill_all=fill_all)
1543
    self.assertEqual(ipolicy, exp_ipolicy)
1544

    
1545
  def _TestFullISpecsInner(self, skel_exp_ipol, exp_minmax, exp_std,
1546
                           group_ipolicy, fill_all):
1547
    exp_ipol = skel_exp_ipol.copy()
1548
    if exp_minmax is not None:
1549
      minmax_ispecs = []
1550
      for exp_mm_pair in exp_minmax:
1551
        mmpair = {}
1552
        for (key, spec) in exp_mm_pair.items():
1553
          mmpair[key] = self._ConvertSpecToStrings(spec)
1554
        minmax_ispecs.append(mmpair)
1555
      exp_ipol[constants.ISPECS_MINMAX] = exp_minmax
1556
    else:
1557
      minmax_ispecs = None
1558
    if exp_std is not None:
1559
      std_ispecs = self._ConvertSpecToStrings(exp_std)
1560
      exp_ipol[constants.ISPECS_STD] = exp_std
1561
    else:
1562
      std_ispecs = None
1563

    
1564
    self._CheckNewStyleSpecsCall(exp_ipol, minmax_ispecs, std_ispecs,
1565
                                 group_ipolicy, fill_all)
1566
    if minmax_ispecs:
1567
      for mmpair in minmax_ispecs:
1568
        for (key, spec) in mmpair.items():
1569
          for par in [constants.ISPEC_MEM_SIZE, constants.ISPEC_DISK_SIZE]:
1570
            if par in spec:
1571
              spec[par] += "m"
1572
              self._CheckNewStyleSpecsCall(exp_ipol, minmax_ispecs, std_ispecs,
1573
                                           group_ipolicy, fill_all)
1574
    if std_ispecs:
1575
      for par in [constants.ISPEC_MEM_SIZE, constants.ISPEC_DISK_SIZE]:
1576
        if par in std_ispecs:
1577
          std_ispecs[par] += "m"
1578
          self._CheckNewStyleSpecsCall(exp_ipol, minmax_ispecs, std_ispecs,
1579
                                       group_ipolicy, fill_all)
1580

    
1581
  def testFullISpecs(self):
1582
    exp_minmax1 = [
1583
      {
1584
        constants.ISPECS_MIN: {
1585
          constants.ISPEC_MEM_SIZE: 512,
1586
          constants.ISPEC_CPU_COUNT: 2,
1587
          constants.ISPEC_DISK_COUNT: 2,
1588
          constants.ISPEC_DISK_SIZE: 512,
1589
          constants.ISPEC_NIC_COUNT: 2,
1590
          constants.ISPEC_SPINDLE_USE: 2,
1591
          },
1592
        constants.ISPECS_MAX: {
1593
          constants.ISPEC_MEM_SIZE: 768*1024,
1594
          constants.ISPEC_CPU_COUNT: 7,
1595
          constants.ISPEC_DISK_COUNT: 6,
1596
          constants.ISPEC_DISK_SIZE: 2048*1024,
1597
          constants.ISPEC_NIC_COUNT: 3,
1598
          constants.ISPEC_SPINDLE_USE: 3,
1599
          },
1600
        },
1601
      ]
1602
    exp_minmax2 = [
1603
      {
1604
        constants.ISPECS_MIN: {
1605
          constants.ISPEC_MEM_SIZE: 512,
1606
          constants.ISPEC_CPU_COUNT: 2,
1607
          constants.ISPEC_DISK_COUNT: 2,
1608
          constants.ISPEC_DISK_SIZE: 512,
1609
          constants.ISPEC_NIC_COUNT: 2,
1610
          constants.ISPEC_SPINDLE_USE: 2,
1611
          },
1612
        constants.ISPECS_MAX: {
1613
          constants.ISPEC_MEM_SIZE: 768*1024,
1614
          constants.ISPEC_CPU_COUNT: 7,
1615
          constants.ISPEC_DISK_COUNT: 6,
1616
          constants.ISPEC_DISK_SIZE: 2048*1024,
1617
          constants.ISPEC_NIC_COUNT: 3,
1618
          constants.ISPEC_SPINDLE_USE: 3,
1619
          },
1620
        },
1621
      {
1622
        constants.ISPECS_MIN: {
1623
          constants.ISPEC_MEM_SIZE: 1024*1024,
1624
          constants.ISPEC_CPU_COUNT: 3,
1625
          constants.ISPEC_DISK_COUNT: 3,
1626
          constants.ISPEC_DISK_SIZE: 256,
1627
          constants.ISPEC_NIC_COUNT: 4,
1628
          constants.ISPEC_SPINDLE_USE: 5,
1629
          },
1630
        constants.ISPECS_MAX: {
1631
          constants.ISPEC_MEM_SIZE: 2048*1024,
1632
          constants.ISPEC_CPU_COUNT: 5,
1633
          constants.ISPEC_DISK_COUNT: 5,
1634
          constants.ISPEC_DISK_SIZE: 1024*1024,
1635
          constants.ISPEC_NIC_COUNT: 5,
1636
          constants.ISPEC_SPINDLE_USE: 7,
1637
          },
1638
        },
1639
      ]
1640
    exp_std1 = {
1641
      constants.ISPEC_MEM_SIZE: 768*1024,
1642
      constants.ISPEC_CPU_COUNT: 7,
1643
      constants.ISPEC_DISK_COUNT: 6,
1644
      constants.ISPEC_DISK_SIZE: 2048*1024,
1645
      constants.ISPEC_NIC_COUNT: 3,
1646
      constants.ISPEC_SPINDLE_USE: 1,
1647
      }
1648
    for fill_all in [False, True]:
1649
      if fill_all:
1650
        skel_ipolicy = constants.IPOLICY_DEFAULTS
1651
      else:
1652
        skel_ipolicy = {}
1653
      self._TestFullISpecsInner(skel_ipolicy, None, exp_std1,
1654
                                False, fill_all)
1655
      for exp_minmax in [exp_minmax1, exp_minmax2]:
1656
        self._TestFullISpecsInner(skel_ipolicy, exp_minmax, exp_std1,
1657
                                  False, fill_all)
1658
        self._TestFullISpecsInner(skel_ipolicy, exp_minmax, None,
1659
                                  False, fill_all)
1660

    
1661

    
1662
class TestPrintIPolicyCommand(unittest.TestCase):
1663
  """Test case for cli.PrintIPolicyCommand"""
1664
  _SPECS1 = {
1665
    "par1": 42,
1666
    "par2": "xyz",
1667
    }
1668
  _SPECS1_STR = "par1=42,par2=xyz"
1669
  _SPECS2 = {
1670
    "param": 10,
1671
    "another_param": 101,
1672
    }
1673
  _SPECS2_STR = "another_param=101,param=10"
1674
  _SPECS3 = {
1675
    "par1": 1024,
1676
    "param": "abc",
1677
    }
1678
  _SPECS3_STR = "par1=1024,param=abc"
1679

    
1680
  def _CheckPrintIPolicyCommand(self, ipolicy, isgroup, expected):
1681
    buf = StringIO()
1682
    cli.PrintIPolicyCommand(buf, ipolicy, isgroup)
1683
    self.assertEqual(buf.getvalue(), expected)
1684

    
1685
  def testIgnoreStdForGroup(self):
1686
    self._CheckPrintIPolicyCommand({"std": self._SPECS1}, True, "")
1687

    
1688
  def testIgnoreEmpty(self):
1689
    policies = [
1690
      {},
1691
      {"std": {}},
1692
      {"minmax": []},
1693
      {"minmax": [{}]},
1694
      {"minmax": [{
1695
        "min": {},
1696
        "max": {},
1697
        }]},
1698
      {"minmax": [{
1699
        "min": self._SPECS1,
1700
        "max": {},
1701
        }]},
1702
      ]
1703
    for pol in policies:
1704
      self._CheckPrintIPolicyCommand(pol, False, "")
1705

    
1706
  def testFullPolicies(self):
1707
    cases = [
1708
      ({"std": self._SPECS1},
1709
       " %s %s" % (cli.IPOLICY_STD_SPECS_STR, self._SPECS1_STR)),
1710
      ({"minmax": [{
1711
        "min": self._SPECS1,
1712
        "max": self._SPECS2,
1713
        }]},
1714
       " %s min:%s/max:%s" % (cli.IPOLICY_BOUNDS_SPECS_STR,
1715
                              self._SPECS1_STR, self._SPECS2_STR)),
1716
      ({"minmax": [
1717
        {
1718
          "min": self._SPECS1,
1719
          "max": self._SPECS2,
1720
          },
1721
        {
1722
          "min": self._SPECS2,
1723
          "max": self._SPECS3,
1724
          },
1725
        ]},
1726
       " %s min:%s/max:%s//min:%s/max:%s" %
1727
       (cli.IPOLICY_BOUNDS_SPECS_STR, self._SPECS1_STR, self._SPECS2_STR,
1728
        self._SPECS2_STR, self._SPECS3_STR)),
1729
      ]
1730
    for (pol, exp) in cases:
1731
      self._CheckPrintIPolicyCommand(pol, False, exp)
1732

    
1733

    
1734
if __name__ == "__main__":
1735
  testutils.GanetiTestProgram()