Statistics
| Branch: | Tag: | Revision:

root / test / py / ganeti.objects_unittest.py @ 5b798711

History | View | Annotate | Download (30 kB)

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

    
4
# Copyright (C) 2006, 2007, 2008, 2010, 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 objects module"""
23

    
24

    
25
import copy
26
import pprint
27
import unittest
28

    
29
from ganeti import constants
30
from ganeti import objects
31
from ganeti import errors
32
from ganeti import serializer
33

    
34
import testutils
35

    
36

    
37
class SimpleObject(objects.ConfigObject):
38
  __slots__ = ["a", "b"]
39

    
40

    
41
class TestDictState(unittest.TestCase):
42
  """Simple dict tansformation tests"""
43

    
44
  def testSimpleObjectToDict(self):
45
    o1 = SimpleObject(a="1")
46
    self.assertEquals(o1.ToDict(), {"a": "1"})
47
    self.assertEquals(o1.__getstate__(), {"a": "1"})
48
    self.assertEquals(o1.__getstate__(), o1.ToDict())
49
    o1.a = 2
50
    o1.b = 5
51
    self.assertEquals(o1.ToDict(), {"a": 2, "b": 5})
52
    o2 = SimpleObject.FromDict(o1.ToDict())
53
    self.assertEquals(o1.ToDict(), {"a": 2, "b": 5})
54

    
55

    
56
class TestClusterObject(unittest.TestCase):
57
  """Tests done on a L{objects.Cluster}"""
58

    
59
  def setUp(self):
60
    hvparams = {
61
      constants.HT_FAKE: {
62
        "foo": "bar",
63
        "bar": "foo",
64
        "foobar": "barfoo",
65
        },
66
      }
67
    os_hvp = {
68
      "lenny-image": {
69
        constants.HT_FAKE: {
70
          "foo": "baz",
71
          "foobar": "foobar",
72
          "blah": "blibb",
73
          "blubb": "blah",
74
          },
75
        constants.HT_XEN_PVM: {
76
          "root_path": "/dev/sda5",
77
          "foo": "foobar",
78
          },
79
        },
80
      "ubuntu-hardy": {
81
        },
82
      }
83
    ndparams = {
84
        constants.ND_OOB_PROGRAM: "/bin/cluster-oob",
85
        constants.ND_SPINDLE_COUNT: 1,
86
        constants.ND_EXCLUSIVE_STORAGE: False,
87
        }
88

    
89
    self.fake_cl = objects.Cluster(hvparams=hvparams, os_hvp=os_hvp,
90
                                   ndparams=ndparams)
91
    self.fake_cl.UpgradeConfig()
92

    
93
  def testGetHVDefaults(self):
94
    cl = self.fake_cl
95
    self.failUnlessEqual(cl.GetHVDefaults(constants.HT_FAKE),
96
                         cl.hvparams[constants.HT_FAKE])
97
    self.failUnlessEqual(cl.GetHVDefaults(None), {})
98
    defaults = cl.GetHVDefaults(constants.HT_XEN_PVM,
99
                                          os_name="lenny-image")
100
    for param, value in cl.os_hvp["lenny-image"][constants.HT_XEN_PVM].items():
101
      self.assertEqual(value, defaults[param])
102

    
103
  def testFillHvFullMerge(self):
104
    inst_hvparams = {
105
      "blah": "blubb",
106
      }
107

    
108
    fake_dict = constants.HVC_DEFAULTS[constants.HT_FAKE].copy()
109
    fake_dict.update({
110
      "foo": "baz",
111
      "bar": "foo",
112
      "foobar": "foobar",
113
      "blah": "blubb",
114
      "blubb": "blah",
115
      })
116
    fake_inst = objects.Instance(name="foobar",
117
                                 os="lenny-image",
118
                                 hypervisor=constants.HT_FAKE,
119
                                 hvparams=inst_hvparams)
120
    self.assertEqual(fake_dict, self.fake_cl.FillHV(fake_inst))
121

    
122
  def testFillHvGlobalParams(self):
123
    fake_inst = objects.Instance(name="foobar",
124
                                 os="ubuntu-hardy",
125
                                 hypervisor=constants.HT_FAKE,
126
                                 hvparams={})
127
    self.assertEqual(self.fake_cl.hvparams[constants.HT_FAKE],
128
                     self.fake_cl.FillHV(fake_inst))
129

    
130
  def testFillHvInstParams(self):
131
    inst_hvparams = {
132
      "blah": "blubb",
133
      }
134
    fake_inst = objects.Instance(name="foobar",
135
                                 os="ubuntu-hardy",
136
                                 hypervisor=constants.HT_XEN_PVM,
137
                                 hvparams=inst_hvparams)
138
    filled_conf = self.fake_cl.FillHV(fake_inst)
139
    for param, value in constants.HVC_DEFAULTS[constants.HT_XEN_PVM].items():
140
      if param == "blah":
141
        value = "blubb"
142
      self.assertEqual(value, filled_conf[param])
143

    
144
  def testFillHvDefaultParams(self):
145
    fake_inst = objects.Instance(name="foobar",
146
                                 os="ubuntu-hardy",
147
                                 hypervisor=constants.HT_XEN_PVM,
148
                                 hvparams={})
149
    self.assertEqual(constants.HVC_DEFAULTS[constants.HT_XEN_PVM],
150
                     self.fake_cl.FillHV(fake_inst))
151

    
152
  def testFillHvPartialParams(self):
153
    os = "lenny-image"
154
    fake_inst = objects.Instance(name="foobar",
155
                                 os=os,
156
                                 hypervisor=constants.HT_XEN_PVM,
157
                                 hvparams={})
158
    filled_conf = self.fake_cl.FillHV(fake_inst)
159
    for param, value in self.fake_cl.os_hvp[os][constants.HT_XEN_PVM].items():
160
      self.assertEqual(value, filled_conf[param])
161

    
162
  def testFillNdParamsCluster(self):
163
    fake_node = objects.Node(name="test",
164
                             ndparams={},
165
                             group="testgroup")
166
    fake_group = objects.NodeGroup(name="testgroup",
167
                                   ndparams={})
168
    self.assertEqual(self.fake_cl.ndparams,
169
                     self.fake_cl.FillND(fake_node, fake_group))
170

    
171
  def testFillNdParamsNodeGroup(self):
172
    fake_node = objects.Node(name="test",
173
                             ndparams={},
174
                             group="testgroup")
175
    group_ndparams = {
176
        constants.ND_OOB_PROGRAM: "/bin/group-oob",
177
        constants.ND_SPINDLE_COUNT: 10,
178
        constants.ND_EXCLUSIVE_STORAGE: True,
179
        constants.ND_OVS: True,
180
        constants.ND_OVS_LINK: "eth2",
181
        constants.ND_OVS_NAME: "openvswitch",
182
        constants.ND_SSH_PORT: 122,
183
        }
184
    fake_group = objects.NodeGroup(name="testgroup",
185
                                   ndparams=group_ndparams)
186
    self.assertEqual(group_ndparams,
187
                     self.fake_cl.FillND(fake_node, fake_group))
188

    
189
  def testFillNdParamsNode(self):
190
    node_ndparams = {
191
        constants.ND_OOB_PROGRAM: "/bin/node-oob",
192
        constants.ND_SPINDLE_COUNT: 2,
193
        constants.ND_EXCLUSIVE_STORAGE: True,
194
        constants.ND_OVS: True,
195
        constants.ND_OVS_LINK: "eth2",
196
        constants.ND_OVS_NAME: "openvswitch",
197
        constants.ND_SSH_PORT: 222,
198
        }
199
    fake_node = objects.Node(name="test",
200
                             ndparams=node_ndparams,
201
                             group="testgroup")
202
    fake_group = objects.NodeGroup(name="testgroup",
203
                                   ndparams={})
204
    self.assertEqual(node_ndparams,
205
                     self.fake_cl.FillND(fake_node, fake_group))
206

    
207
  def testFillNdParamsAll(self):
208
    node_ndparams = {
209
        constants.ND_OOB_PROGRAM: "/bin/node-oob",
210
        constants.ND_SPINDLE_COUNT: 5,
211
        constants.ND_EXCLUSIVE_STORAGE: True,
212
        constants.ND_OVS: True,
213
        constants.ND_OVS_LINK: "eth2",
214
        constants.ND_OVS_NAME: "openvswitch",
215
        constants.ND_SSH_PORT: 322,
216
        }
217
    fake_node = objects.Node(name="test",
218
                             ndparams=node_ndparams,
219
                             group="testgroup")
220
    group_ndparams = {
221
        constants.ND_OOB_PROGRAM: "/bin/group-oob",
222
        constants.ND_SPINDLE_COUNT: 4,
223
        constants.ND_SSH_PORT: 422,
224
        }
225
    fake_group = objects.NodeGroup(name="testgroup",
226
                                   ndparams=group_ndparams)
227
    self.assertEqual(node_ndparams,
228
                     self.fake_cl.FillND(fake_node, fake_group))
229

    
230
  def testPrimaryHypervisor(self):
231
    assert self.fake_cl.enabled_hypervisors is None
232
    self.fake_cl.enabled_hypervisors = [constants.HT_XEN_HVM]
233
    self.assertEqual(self.fake_cl.primary_hypervisor, constants.HT_XEN_HVM)
234

    
235
    self.fake_cl.enabled_hypervisors = [constants.HT_XEN_PVM, constants.HT_KVM]
236
    self.assertEqual(self.fake_cl.primary_hypervisor, constants.HT_XEN_PVM)
237

    
238
    self.fake_cl.enabled_hypervisors = sorted(constants.HYPER_TYPES)
239
    self.assertEqual(self.fake_cl.primary_hypervisor, constants.HT_CHROOT)
240

    
241
  def testUpgradeConfig(self):
242
    # FIXME: This test is incomplete
243
    cluster = objects.Cluster()
244
    cluster.UpgradeConfig()
245
    cluster = objects.Cluster(ipolicy={"unknown_key": None})
246
    self.assertRaises(errors.ConfigurationError, cluster.UpgradeConfig)
247

    
248
  def testUpgradeEnabledDiskTemplates(self):
249
    cfg = objects.ConfigData()
250
    cfg.cluster = objects.Cluster()
251
    cfg.cluster.volume_group_name = "myvg"
252
    instance1 = objects.Instance()
253
    instance1.disk_template = constants.DT_DISKLESS
254
    instance2 = objects.Instance()
255
    instance2.disk_template = constants.DT_RBD
256
    cfg.instances = { "myinstance1": instance1, "myinstance2": instance2 }
257
    nodegroup = objects.NodeGroup()
258
    nodegroup.ipolicy = {}
259
    nodegroup.ipolicy[constants.IPOLICY_DTS] = [instance1.disk_template, \
260
      constants.DT_BLOCK]
261
    cfg.cluster.ipolicy = {}
262
    cfg.cluster.ipolicy[constants.IPOLICY_DTS] = \
263
      [constants.DT_EXT, constants.DT_DISKLESS]
264
    cfg.nodegroups = { "mynodegroup": nodegroup }
265
    cfg._UpgradeEnabledDiskTemplates()
266
    expected_disk_templates = [constants.DT_DRBD8,
267
                               constants.DT_PLAIN,
268
                               instance1.disk_template,
269
                               instance2.disk_template]
270
    self.assertEqual(set(expected_disk_templates),
271
                     set(cfg.cluster.enabled_disk_templates))
272
    self.assertEqual(set([instance1.disk_template]),
273
                     set(cfg.cluster.ipolicy[constants.IPOLICY_DTS]))
274

    
275

    
276
class TestClusterObjectTcpUdpPortPool(unittest.TestCase):
277
  def testNewCluster(self):
278
    self.assertTrue(objects.Cluster().tcpudp_port_pool is None)
279

    
280
  def testSerializingEmpty(self):
281
    self.assertEqual(objects.Cluster().ToDict(), {
282
      "tcpudp_port_pool": [],
283
      })
284

    
285
  def testSerializing(self):
286
    cluster = objects.Cluster.FromDict({})
287
    self.assertEqual(cluster.tcpudp_port_pool, set())
288

    
289
    cluster.tcpudp_port_pool.add(3546)
290
    cluster.tcpudp_port_pool.add(62511)
291

    
292
    data = cluster.ToDict()
293
    self.assertEqual(data.keys(), ["tcpudp_port_pool"])
294
    self.assertEqual(sorted(data["tcpudp_port_pool"]), sorted([3546, 62511]))
295

    
296
  def testDeserializingEmpty(self):
297
    cluster = objects.Cluster.FromDict({})
298
    self.assertEqual(cluster.tcpudp_port_pool, set())
299

    
300
  def testDeserialize(self):
301
    cluster = objects.Cluster.FromDict({
302
      "tcpudp_port_pool": [26214, 10039, 267],
303
      })
304
    self.assertEqual(cluster.tcpudp_port_pool, set([26214, 10039, 267]))
305

    
306

    
307
class TestOS(unittest.TestCase):
308
  ALL_DATA = [
309
    "debootstrap",
310
    "debootstrap+default",
311
    "debootstrap++default",
312
    ]
313

    
314
  def testSplitNameVariant(self):
315
    for name in self.ALL_DATA:
316
      self.assertEqual(len(objects.OS.SplitNameVariant(name)), 2)
317

    
318
  def testVariant(self):
319
    self.assertEqual(objects.OS.GetVariant("debootstrap"), "")
320
    self.assertEqual(objects.OS.GetVariant("debootstrap+default"), "default")
321

    
322

    
323
class TestInstance(unittest.TestCase):
324
  def testFindDisk(self):
325
    inst = objects.Instance(name="fakeinstdrbd.example.com",
326
      primary_node="node10.example.com",
327
      disks=[
328
        objects.Disk(dev_type=constants.DT_DRBD8, size=786432,
329
          logical_id=("node10.example.com", "node15.example.com",
330
                      12300, 0, 0, "secret"),
331
          children=[
332
            objects.Disk(dev_type=constants.DT_PLAIN, size=786432,
333
                         logical_id=("myxenvg", "disk0")),
334
            objects.Disk(dev_type=constants.DT_PLAIN, size=128,
335
                         logical_id=("myxenvg", "meta0"))
336
          ],
337
          iv_name="disk/0")
338
        ])
339

    
340
    self.assertEqual(inst.FindDisk(0), inst.disks[0])
341
    self.assertRaises(errors.OpPrereqError, inst.FindDisk, "hello")
342
    self.assertRaises(errors.OpPrereqError, inst.FindDisk, 100)
343
    self.assertRaises(errors.OpPrereqError, inst.FindDisk, 1)
344

    
345

    
346
class TestNode(unittest.TestCase):
347
  def testEmpty(self):
348
    self.assertEqual(objects.Node().ToDict(), {})
349
    self.assertTrue(isinstance(objects.Node.FromDict({}), objects.Node))
350

    
351
  def testHvState(self):
352
    node = objects.Node(name="node18157.example.com", hv_state={
353
      constants.HT_XEN_HVM: objects.NodeHvState(cpu_total=64),
354
      constants.HT_KVM: objects.NodeHvState(cpu_node=1),
355
      })
356

    
357
    node2 = objects.Node.FromDict(node.ToDict())
358

    
359
    # Make sure nothing can reference it anymore
360
    del node
361

    
362
    self.assertEqual(node2.name, "node18157.example.com")
363
    self.assertEqual(frozenset(node2.hv_state), frozenset([
364
      constants.HT_XEN_HVM,
365
      constants.HT_KVM,
366
      ]))
367
    self.assertEqual(node2.hv_state[constants.HT_KVM].cpu_node, 1)
368
    self.assertEqual(node2.hv_state[constants.HT_XEN_HVM].cpu_total, 64)
369

    
370
  def testDiskState(self):
371
    node = objects.Node(name="node32087.example.com", disk_state={
372
      constants.DT_PLAIN: {
373
        "lv32352": objects.NodeDiskState(total=128),
374
        "lv2082": objects.NodeDiskState(total=512),
375
        },
376
      })
377

    
378
    node2 = objects.Node.FromDict(node.ToDict())
379

    
380
    # Make sure nothing can reference it anymore
381
    del node
382

    
383
    self.assertEqual(node2.name, "node32087.example.com")
384
    self.assertEqual(frozenset(node2.disk_state), frozenset([
385
      constants.DT_PLAIN,
386
      ]))
387
    self.assertEqual(frozenset(node2.disk_state[constants.DT_PLAIN]),
388
                     frozenset(["lv32352", "lv2082"]))
389
    self.assertEqual(node2.disk_state[constants.DT_PLAIN]["lv2082"].total, 512)
390
    self.assertEqual(node2.disk_state[constants.DT_PLAIN]["lv32352"].total, 128)
391

    
392
  def testFilterEsNdp(self):
393
    node1 = objects.Node(name="node11673.example.com", ndparams={
394
      constants.ND_EXCLUSIVE_STORAGE: True,
395
      })
396
    node2 = objects.Node(name="node11674.example.com", ndparams={
397
      constants.ND_SPINDLE_COUNT: 3,
398
      constants.ND_EXCLUSIVE_STORAGE: False,
399
      })
400
    self.assertTrue(constants.ND_EXCLUSIVE_STORAGE in node1.ndparams)
401
    node1.UpgradeConfig()
402
    self.assertFalse(constants.ND_EXCLUSIVE_STORAGE in node1.ndparams)
403
    self.assertTrue(constants.ND_EXCLUSIVE_STORAGE in node2.ndparams)
404
    self.assertTrue(constants.ND_SPINDLE_COUNT in node2.ndparams)
405
    node2.UpgradeConfig()
406
    self.assertFalse(constants.ND_EXCLUSIVE_STORAGE in node2.ndparams)
407
    self.assertTrue(constants.ND_SPINDLE_COUNT in node2.ndparams)
408

    
409

    
410
class TestInstancePolicy(unittest.TestCase):
411
  def setUp(self):
412
    # Policies are big, and we want to see the difference in case of an error
413
    self.maxDiff = None
414

    
415
  def _AssertIPolicyIsFull(self, policy):
416
    self.assertEqual(frozenset(policy.keys()), constants.IPOLICY_ALL_KEYS)
417
    self.assertTrue(len(policy[constants.ISPECS_MINMAX]) > 0)
418
    for minmax in policy[constants.ISPECS_MINMAX]:
419
      self.assertEqual(frozenset(minmax.keys()), constants.ISPECS_MINMAX_KEYS)
420
      for key in constants.ISPECS_MINMAX_KEYS:
421
        self.assertEqual(frozenset(minmax[key].keys()),
422
                         constants.ISPECS_PARAMETERS)
423
    self.assertEqual(frozenset(policy[constants.ISPECS_STD].keys()),
424
                     constants.ISPECS_PARAMETERS)
425

    
426
  def testDefaultIPolicy(self):
427
    objects.InstancePolicy.CheckParameterSyntax(constants.IPOLICY_DEFAULTS,
428
                                                True)
429
    self._AssertIPolicyIsFull(constants.IPOLICY_DEFAULTS)
430

    
431
  def _AssertPolicyIsBad(self, ipolicy, do_check_std=None):
432
    if do_check_std is None:
433
      check_std_vals = [False, True]
434
    else:
435
      check_std_vals = [do_check_std]
436
    for check_std in check_std_vals:
437
      self.assertRaises(errors.ConfigurationError,
438
                        objects.InstancePolicy.CheckISpecSyntax,
439
                        ipolicy, check_std)
440

    
441
  def testCheckISpecSyntax(self):
442
    default_stdspec = constants.IPOLICY_DEFAULTS[constants.ISPECS_STD]
443
    incomplete_ipolicies = [
444
      {
445
         constants.ISPECS_MINMAX: [],
446
         constants.ISPECS_STD: default_stdspec,
447
         },
448
      {
449
         constants.ISPECS_MINMAX: [{}],
450
         constants.ISPECS_STD: default_stdspec,
451
         },
452
      {
453
        constants.ISPECS_MINMAX: [{
454
          constants.ISPECS_MIN: NotImplemented,
455
          }],
456
        constants.ISPECS_STD: default_stdspec,
457
        },
458
      {
459
        constants.ISPECS_MINMAX: [{
460
          constants.ISPECS_MAX: NotImplemented,
461
          }],
462
        constants.ISPECS_STD: default_stdspec,
463
        },
464
      {
465
        constants.ISPECS_MINMAX: [{
466
          constants.ISPECS_MIN: NotImplemented,
467
          constants.ISPECS_MAX: NotImplemented,
468
          }],
469
        },
470
      ]
471
    for ipol in incomplete_ipolicies:
472
      self.assertRaises(errors.ConfigurationError,
473
                        objects.InstancePolicy.CheckISpecSyntax,
474
                        ipol, True)
475
      oldminmax = ipol[constants.ISPECS_MINMAX]
476
      if oldminmax:
477
        # Prepending valid specs shouldn't change the error
478
        ipol[constants.ISPECS_MINMAX] = ([constants.ISPECS_MINMAX_DEFAULTS] +
479
                                         oldminmax)
480
        self.assertRaises(errors.ConfigurationError,
481
                          objects.InstancePolicy.CheckISpecSyntax,
482
                          ipol, True)
483

    
484
    good_ipolicy = {
485
      constants.ISPECS_MINMAX: [
486
        {
487
          constants.ISPECS_MIN: {
488
            constants.ISPEC_MEM_SIZE: 64,
489
            constants.ISPEC_CPU_COUNT: 1,
490
            constants.ISPEC_DISK_COUNT: 2,
491
            constants.ISPEC_DISK_SIZE: 64,
492
            constants.ISPEC_NIC_COUNT: 1,
493
            constants.ISPEC_SPINDLE_USE: 1,
494
            },
495
          constants.ISPECS_MAX: {
496
            constants.ISPEC_MEM_SIZE: 16384,
497
            constants.ISPEC_CPU_COUNT: 5,
498
            constants.ISPEC_DISK_COUNT: 12,
499
            constants.ISPEC_DISK_SIZE: 1024,
500
            constants.ISPEC_NIC_COUNT: 9,
501
            constants.ISPEC_SPINDLE_USE: 18,
502
            },
503
          },
504
        {
505
          constants.ISPECS_MIN: {
506
            constants.ISPEC_MEM_SIZE: 32768,
507
            constants.ISPEC_CPU_COUNT: 8,
508
            constants.ISPEC_DISK_COUNT: 1,
509
            constants.ISPEC_DISK_SIZE: 1024,
510
            constants.ISPEC_NIC_COUNT: 1,
511
            constants.ISPEC_SPINDLE_USE: 1,
512
            },
513
          constants.ISPECS_MAX: {
514
            constants.ISPEC_MEM_SIZE: 65536,
515
            constants.ISPEC_CPU_COUNT: 10,
516
            constants.ISPEC_DISK_COUNT: 5,
517
            constants.ISPEC_DISK_SIZE: 1024 * 1024,
518
            constants.ISPEC_NIC_COUNT: 3,
519
            constants.ISPEC_SPINDLE_USE: 12,
520
            },
521
          },
522
        ],
523
      }
524
    good_ipolicy[constants.ISPECS_STD] = copy.deepcopy(
525
      good_ipolicy[constants.ISPECS_MINMAX][0][constants.ISPECS_MAX])
526
    # Check that it's really good before making it bad
527
    objects.InstancePolicy.CheckISpecSyntax(good_ipolicy, True)
528

    
529
    bad_ipolicy = copy.deepcopy(good_ipolicy)
530
    for minmax in bad_ipolicy[constants.ISPECS_MINMAX]:
531
      for (key, spec) in minmax.items():
532
        for param in spec:
533
          oldv = spec[param]
534
          del spec[param]
535
          self._AssertPolicyIsBad(bad_ipolicy)
536
          if key == constants.ISPECS_MIN:
537
            spec[param] = minmax[constants.ISPECS_MAX][param] + 1
538
          self._AssertPolicyIsBad(bad_ipolicy)
539
          spec[param] = oldv
540
    assert bad_ipolicy == good_ipolicy
541

    
542
    stdspec = bad_ipolicy[constants.ISPECS_STD]
543
    for param in stdspec:
544
      oldv = stdspec[param]
545
      del stdspec[param]
546
      self._AssertPolicyIsBad(bad_ipolicy, True)
547
      # Note that std spec is the same as a max spec
548
      stdspec[param] = oldv + 1
549
      self._AssertPolicyIsBad(bad_ipolicy, True)
550
      stdspec[param] = oldv
551
    assert bad_ipolicy == good_ipolicy
552

    
553
    for minmax in good_ipolicy[constants.ISPECS_MINMAX]:
554
      for spec in minmax.values():
555
        good_ipolicy[constants.ISPECS_STD] = spec
556
        objects.InstancePolicy.CheckISpecSyntax(good_ipolicy, True)
557

    
558
  def testCheckISpecParamSyntax(self):
559
    par = "my_parameter"
560
    for check_std in [True, False]:
561
      # Min and max only
562
      good_values = [(11, 11), (11, 40), (0, 0)]
563
      for (mn, mx) in good_values:
564
        minmax = dict((k, {}) for k in constants.ISPECS_MINMAX_KEYS)
565
        minmax[constants.ISPECS_MIN][par] = mn
566
        minmax[constants.ISPECS_MAX][par] = mx
567
        objects.InstancePolicy._CheckISpecParamSyntax(minmax, {}, par,
568
                                                     check_std)
569
      minmax = dict((k, {}) for k in constants.ISPECS_MINMAX_KEYS)
570
      minmax[constants.ISPECS_MIN][par] = 11
571
      minmax[constants.ISPECS_MAX][par] = 5
572
      self.assertRaises(errors.ConfigurationError,
573
                        objects.InstancePolicy._CheckISpecParamSyntax,
574
                        minmax, {}, par, check_std)
575
    # Min, std, max
576
    good_values = [
577
      (11, 11, 11),
578
      (11, 11, 40),
579
      (11, 40, 40),
580
      ]
581
    for (mn, st, mx) in good_values:
582
      minmax = {
583
        constants.ISPECS_MIN: {par: mn},
584
        constants.ISPECS_MAX: {par: mx},
585
        }
586
      stdspec = {par: st}
587
      objects.InstancePolicy._CheckISpecParamSyntax(minmax, stdspec, par, True)
588
    bad_values = [
589
      (11, 11,  5, True),
590
      (40, 11, 11, True),
591
      (11, 80, 40, False),
592
      (11,  5, 40, False,),
593
      (11,  5,  5, True),
594
      (40, 40, 11, True),
595
      ]
596
    for (mn, st, mx, excp) in bad_values:
597
      minmax = {
598
        constants.ISPECS_MIN: {par: mn},
599
        constants.ISPECS_MAX: {par: mx},
600
        }
601
      stdspec = {par: st}
602
      if excp:
603
        self.assertRaises(errors.ConfigurationError,
604
                          objects.InstancePolicy._CheckISpecParamSyntax,
605
                          minmax, stdspec, par, True)
606
      else:
607
        ret = objects.InstancePolicy._CheckISpecParamSyntax(minmax, stdspec,
608
                                                            par, True)
609
        self.assertFalse(ret)
610

    
611
  def testCheckDiskTemplates(self):
612
    invalid = "this_is_not_a_good_template"
613
    for dt in constants.DISK_TEMPLATES:
614
      objects.InstancePolicy.CheckDiskTemplates([dt])
615
    objects.InstancePolicy.CheckDiskTemplates(list(constants.DISK_TEMPLATES))
616
    bad_examples = [
617
      [invalid],
618
      [constants.DT_DRBD8, invalid],
619
      list(constants.DISK_TEMPLATES) + [invalid],
620
      [],
621
      None,
622
      ]
623
    for dtl in bad_examples:
624
      self.assertRaises(errors.ConfigurationError,
625
                        objects.InstancePolicy.CheckDiskTemplates,
626
                        dtl)
627

    
628
  def testCheckParameterSyntax(self):
629
    invalid = "this_key_shouldnt_be_here"
630
    for check_std in [True, False]:
631
      objects.InstancePolicy.CheckParameterSyntax({}, check_std)
632
      policy = {invalid: None}
633
      self.assertRaises(errors.ConfigurationError,
634
                        objects.InstancePolicy.CheckParameterSyntax,
635
                        policy, check_std)
636
      for par in constants.IPOLICY_PARAMETERS:
637
        for val in ("blah", None, {}, [42]):
638
          policy = {par: val}
639
          self.assertRaises(errors.ConfigurationError,
640
                            objects.InstancePolicy.CheckParameterSyntax,
641
                            policy, check_std)
642

    
643
  def testFillIPolicyEmpty(self):
644
    policy = objects.FillIPolicy(constants.IPOLICY_DEFAULTS, {})
645
    objects.InstancePolicy.CheckParameterSyntax(policy, True)
646
    self.assertEqual(policy, constants.IPOLICY_DEFAULTS)
647

    
648
  def _AssertISpecsMerged(self, default_spec, diff_spec, merged_spec):
649
    for (param, value) in merged_spec.items():
650
      if param in diff_spec:
651
        self.assertEqual(value, diff_spec[param])
652
      else:
653
        self.assertEqual(value, default_spec[param])
654

    
655
  def _AssertIPolicyMerged(self, default_pol, diff_pol, merged_pol):
656
    for (key, value) in merged_pol.items():
657
      if key in diff_pol:
658
        if key == constants.ISPECS_STD:
659
          self._AssertISpecsMerged(default_pol[key], diff_pol[key], value)
660
        else:
661
          self.assertEqual(value, diff_pol[key])
662
      else:
663
        self.assertEqual(value, default_pol[key])
664

    
665
  def testFillIPolicy(self):
666
    partial_policies = [
667
      {constants.IPOLICY_VCPU_RATIO: 3.14},
668
      {constants.IPOLICY_SPINDLE_RATIO: 2.72},
669
      {constants.IPOLICY_DTS: [constants.DT_FILE]},
670
      {constants.ISPECS_STD: {constants.ISPEC_DISK_COUNT: 3}},
671
      {constants.ISPECS_MINMAX: [constants.ISPECS_MINMAX_DEFAULTS,
672
                                 constants.ISPECS_MINMAX_DEFAULTS]}
673
      ]
674
    for diff_pol in partial_policies:
675
      policy = objects.FillIPolicy(constants.IPOLICY_DEFAULTS, diff_pol)
676
      objects.InstancePolicy.CheckParameterSyntax(policy, True)
677
      self._AssertIPolicyIsFull(policy)
678
      self._AssertIPolicyMerged(constants.IPOLICY_DEFAULTS, diff_pol, policy)
679

    
680
  def testFillIPolicyKeepsUnknown(self):
681
    INVALID_KEY = "invalid_ipolicy_key"
682
    diff_pol = {
683
      INVALID_KEY: None,
684
      }
685
    policy = objects.FillIPolicy(constants.IPOLICY_DEFAULTS, diff_pol)
686
    self.assertTrue(INVALID_KEY in policy)
687

    
688

    
689
class TestDisk(unittest.TestCase):
690
  def addChild(self, disk):
691
    """Adds a child of the same device type as the parent."""
692
    disk.children = []
693
    child = objects.Disk()
694
    child.dev_type = disk.dev_type
695
    disk.children.append(child)
696

    
697
  def testUpgradeConfigDevTypeLegacy(self):
698
    for old, new in [("drbd8", constants.DT_DRBD8),
699
                     ("lvm", constants.DT_PLAIN)]:
700
      disk = objects.Disk()
701
      disk.dev_type = old
702
      self.addChild(disk)
703
      disk.UpgradeConfig()
704
      self.assertEqual(new, disk.dev_type)
705
      self.assertEqual(new, disk.children[0].dev_type)
706

    
707
  def testUpgradeConfigDevTypeLegacyUnchanged(self):
708
    dev_types = [constants.DT_FILE, constants.DT_SHARED_FILE,
709
                 constants.DT_BLOCK, constants.DT_EXT,
710
                 constants.DT_RBD, constants.DT_GLUSTER]
711
    for dev_type in dev_types:
712
      disk = objects.Disk()
713
      disk.dev_type = dev_type
714
      self.addChild(disk)
715
      disk.UpgradeConfig()
716
      self.assertEqual(dev_type, disk.dev_type)
717
      self.assertEqual(dev_type, disk.children[0].dev_type)
718

    
719

    
720
class TestSimpleFillOS(unittest.TestCase):
721
    # We have to make sure that:
722
    #  * From within the configuration, variants override defaults
723
    #  * Temporary values override configuration
724
    #  * No overlap between public, private and secret dicts is allowed
725
    #
726
    # As a result, here are the actors in this test:
727
    #
728
    # A:  temporary                 public
729
    # B:  temporary                        private
730
    # C:  temporary                                secret
731
    # X:  temporary                 public private secret
732
    # D:            configuration   public                       variant
733
    # E:            configuration   public                  base
734
    # F:            configuration          private               variant
735
    # G:            configuration          private          base
736
    #
737
    # Every time a param is assigned "ERROR", we expect FillOSParams to override
738
    # it. If it doesn't, it's an error.
739
    #
740
    # Every time a param is assigned itself as a value, it's the value we expect
741
    # FillOSParams to give us back.
742

    
743
    def setUp(self):
744
      self.fake_cl = objects.Cluster()
745
      self.fake_cl.UpgradeConfig()
746
      self.fake_cl.osparams = {"os": {"A": "ERROR",
747
                                      "D": "ERROR",
748
                                      "E": "E"},
749
                               "os+a": {"D": "D"}}
750
      self.fake_cl.osparams_private_cluster = {"os": {"B": "ERROR",
751
                                                      "F": "ERROR",
752
                                                      "G": "G"},
753
                                               "os+a": {"F": "F"}}
754

    
755
    def testConflictPublicPrivate(self):
756
      "Make sure we disallow attempts to override params based on visibility."
757
      public_dict = {"A": "A", "X": "X"}
758
      private_dict = {"B": "B", "X": "X"}
759
      secret_dict = {"C": "C"}
760
      dicts_pp = (public_dict, private_dict)
761
      dicts_pps = (public_dict, private_dict, secret_dict)
762

    
763
      # Without secret parameters
764
      self.assertRaises(errors.OpPrereqError,
765
                        lambda: self.fake_cl.SimpleFillOS("os+a", *dicts_pp))
766

    
767
      # but also with those.
768
      self.assertRaises(errors.OpPrereqError,
769
                        lambda: self.fake_cl.SimpleFillOS("os+a", *dicts_pps))
770

    
771
    def testConflictPublicSecret(self):
772
      "Make sure we disallow attempts to override params based on visibility."
773
      public_dict = {"A": "A", "X": "X"}
774
      private_dict = {"B": "B"}
775
      secret_dict = {"C": "C", "X": "X"}
776
      dicts_pps = (public_dict, private_dict, secret_dict)
777

    
778
      self.assertRaises(errors.OpPrereqError,
779
                        lambda: self.fake_cl.SimpleFillOS("os+a", *dicts_pps))
780

    
781
    def testConflictPrivateSecret(self):
782
      "Make sure we disallow attempts to override params based on visibility."
783
      public_dict = {"A": "A"}
784
      private_dict = {"B": "B", "X": "X"}
785
      secret_dict = {"C": "C", "X": "X"}
786
      dicts_pps = (public_dict, private_dict, secret_dict)
787

    
788
      self.assertRaises(errors.OpPrereqError,
789
                        lambda: self.fake_cl.SimpleFillOS("os+a", *dicts_pps))
790

    
791
    def testValidValues(self):
792
      "Make sure we handle all overriding we do allow correctly."
793
      public_dict = {"A": "A"}
794
      private_dict = {"B": "B"}
795
      secret_dict = {"C": "C"}
796
      dicts_p = (public_dict,)
797
      dicts_pp = (public_dict, private_dict)
798
      dicts_pps = (public_dict, private_dict, secret_dict)
799
      expected_keys_p = ("A", "D", "E") # nothing private, secret
800
      expected_keys_pp = ("A", "B", "D", "E", "F", "G") # nothing secret
801
      expected_keys_pps = ("A", "B", "C", "D", "E", "F", "G") # all of them
802

    
803
      for (dicts, expected_keys) in [(dicts_p, expected_keys_p),
804
                                     (dicts_pp, expected_keys_pp),
805
                                     (dicts_pps, expected_keys_pps)]:
806
        result = self.fake_cl.SimpleFillOS("os+a", *dicts)
807
        # Values
808
        for key in result:
809
          if not result[key] == key:
810
            self.fail("Invalid public-private fill with input:\n%s\n%s"
811
                      % (pprint.pformat(dicts), result))
812
        # Completeness
813
        if set(result) != set(expected_keys):
814
          self.fail("Problem with key %s from merge result of:\n%s\n%s"
815
                    % (set(expected_keys) ^ set(result), # symmetric difference
816
                       pprint.pformat(dicts),
817
                       result))
818

    
819
if __name__ == "__main__":
820
  testutils.GanetiTestProgram()