Unit tests for objects.InstancePolicy + a fix
[ganeti-local] / test / py / ganeti.objects_unittest.py
1 #!/usr/bin/python
2 #
3
4 # Copyright (C) 2006, 2007, 2008, 2010, 2012 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 unittest
26
27 from ganeti import constants
28 from ganeti import objects
29 from ganeti import errors
30
31 import testutils
32
33
34 class SimpleObject(objects.ConfigObject):
35   __slots__ = ["a", "b"]
36
37
38 class TestDictState(unittest.TestCase):
39   """Simple dict tansformation tests"""
40
41   def testSimpleObjectToDict(self):
42     o1 = SimpleObject(a="1")
43     self.assertEquals(o1.ToDict(), {"a": "1"})
44     self.assertEquals(o1.__getstate__(), {"a": "1"})
45     self.assertEquals(o1.__getstate__(), o1.ToDict())
46     o1.a = 2
47     o1.b = 5
48     self.assertEquals(o1.ToDict(), {"a": 2, "b": 5})
49     o2 = SimpleObject.FromDict(o1.ToDict())
50     self.assertEquals(o1.ToDict(), {"a": 2, "b": 5})
51
52
53 class TestClusterObject(unittest.TestCase):
54   """Tests done on a L{objects.Cluster}"""
55
56   def setUp(self):
57     hvparams = {
58       constants.HT_FAKE: {
59         "foo": "bar",
60         "bar": "foo",
61         "foobar": "barfoo",
62         },
63       }
64     os_hvp = {
65       "lenny-image": {
66         constants.HT_FAKE: {
67           "foo": "baz",
68           "foobar": "foobar",
69           "blah": "blibb",
70           "blubb": "blah",
71           },
72         constants.HT_XEN_PVM: {
73           "root_path": "/dev/sda5",
74           "foo": "foobar",
75           },
76         },
77       "ubuntu-hardy": {
78         },
79       }
80     ndparams = {
81         constants.ND_OOB_PROGRAM: "/bin/cluster-oob",
82         constants.ND_SPINDLE_COUNT: 1,
83         constants.ND_EXCLUSIVE_STORAGE: False,
84         }
85
86     self.fake_cl = objects.Cluster(hvparams=hvparams, os_hvp=os_hvp,
87                                    ndparams=ndparams)
88     self.fake_cl.UpgradeConfig()
89
90   def testGetHVDefaults(self):
91     cl = self.fake_cl
92     self.failUnlessEqual(cl.GetHVDefaults(constants.HT_FAKE),
93                          cl.hvparams[constants.HT_FAKE])
94     self.failUnlessEqual(cl.GetHVDefaults(None), {})
95     self.failUnlessEqual(cl.GetHVDefaults(constants.HT_XEN_PVM,
96                                           os_name="lenny-image"),
97                          cl.os_hvp["lenny-image"][constants.HT_XEN_PVM])
98
99
100   def testFillHvFullMerge(self):
101     inst_hvparams = {
102       "blah": "blubb",
103       }
104
105     fake_dict = {
106       "foo": "baz",
107       "bar": "foo",
108       "foobar": "foobar",
109       "blah": "blubb",
110       "blubb": "blah",
111       }
112     fake_inst = objects.Instance(name="foobar",
113                                  os="lenny-image",
114                                  hypervisor=constants.HT_FAKE,
115                                  hvparams=inst_hvparams)
116     self.assertEqual(fake_dict, self.fake_cl.FillHV(fake_inst))
117
118   def testFillHvGlobalParams(self):
119     fake_inst = objects.Instance(name="foobar",
120                                  os="ubuntu-hardy",
121                                  hypervisor=constants.HT_FAKE,
122                                  hvparams={})
123     self.assertEqual(self.fake_cl.hvparams[constants.HT_FAKE],
124                      self.fake_cl.FillHV(fake_inst))
125
126   def testFillHvInstParams(self):
127     inst_hvparams = {
128       "blah": "blubb",
129       }
130     fake_inst = objects.Instance(name="foobar",
131                                  os="ubuntu-hardy",
132                                  hypervisor=constants.HT_XEN_PVM,
133                                  hvparams=inst_hvparams)
134     self.assertEqual(inst_hvparams, self.fake_cl.FillHV(fake_inst))
135
136   def testFillHvEmptyParams(self):
137     fake_inst = objects.Instance(name="foobar",
138                                  os="ubuntu-hardy",
139                                  hypervisor=constants.HT_XEN_PVM,
140                                  hvparams={})
141     self.assertEqual({}, self.fake_cl.FillHV(fake_inst))
142
143   def testFillHvPartialParams(self):
144     os = "lenny-image"
145     fake_inst = objects.Instance(name="foobar",
146                                  os=os,
147                                  hypervisor=constants.HT_XEN_PVM,
148                                  hvparams={})
149     self.assertEqual(self.fake_cl.os_hvp[os][constants.HT_XEN_PVM],
150                      self.fake_cl.FillHV(fake_inst))
151
152   def testFillNdParamsCluster(self):
153     fake_node = objects.Node(name="test",
154                              ndparams={},
155                              group="testgroup")
156     fake_group = objects.NodeGroup(name="testgroup",
157                                    ndparams={})
158     self.assertEqual(self.fake_cl.ndparams,
159                      self.fake_cl.FillND(fake_node, fake_group))
160
161   def testFillNdParamsNodeGroup(self):
162     fake_node = objects.Node(name="test",
163                              ndparams={},
164                              group="testgroup")
165     group_ndparams = {
166         constants.ND_OOB_PROGRAM: "/bin/group-oob",
167         constants.ND_SPINDLE_COUNT: 10,
168         constants.ND_EXCLUSIVE_STORAGE: True,
169         }
170     fake_group = objects.NodeGroup(name="testgroup",
171                                    ndparams=group_ndparams)
172     self.assertEqual(group_ndparams,
173                      self.fake_cl.FillND(fake_node, fake_group))
174
175   def testFillNdParamsNode(self):
176     node_ndparams = {
177         constants.ND_OOB_PROGRAM: "/bin/node-oob",
178         constants.ND_SPINDLE_COUNT: 2,
179         constants.ND_EXCLUSIVE_STORAGE: True,
180         }
181     fake_node = objects.Node(name="test",
182                              ndparams=node_ndparams,
183                              group="testgroup")
184     fake_group = objects.NodeGroup(name="testgroup",
185                                    ndparams={})
186     self.assertEqual(node_ndparams,
187                      self.fake_cl.FillND(fake_node, fake_group))
188
189   def testFillNdParamsAll(self):
190     node_ndparams = {
191         constants.ND_OOB_PROGRAM: "/bin/node-oob",
192         constants.ND_SPINDLE_COUNT: 5,
193         constants.ND_EXCLUSIVE_STORAGE: True,
194         }
195     fake_node = objects.Node(name="test",
196                              ndparams=node_ndparams,
197                              group="testgroup")
198     group_ndparams = {
199         constants.ND_OOB_PROGRAM: "/bin/group-oob",
200         constants.ND_SPINDLE_COUNT: 4,
201         }
202     fake_group = objects.NodeGroup(name="testgroup",
203                                    ndparams=group_ndparams)
204     self.assertEqual(node_ndparams,
205                      self.fake_cl.FillND(fake_node, fake_group))
206
207   def testPrimaryHypervisor(self):
208     assert self.fake_cl.enabled_hypervisors is None
209     self.fake_cl.enabled_hypervisors = [constants.HT_XEN_HVM]
210     self.assertEqual(self.fake_cl.primary_hypervisor, constants.HT_XEN_HVM)
211
212     self.fake_cl.enabled_hypervisors = [constants.HT_XEN_PVM, constants.HT_KVM]
213     self.assertEqual(self.fake_cl.primary_hypervisor, constants.HT_XEN_PVM)
214
215     self.fake_cl.enabled_hypervisors = sorted(constants.HYPER_TYPES)
216     self.assertEqual(self.fake_cl.primary_hypervisor, constants.HT_CHROOT)
217
218   def testUpgradeConfig(self):
219     # FIXME: This test is incomplete
220     cluster = objects.Cluster()
221     cluster.UpgradeConfig()
222     cluster = objects.Cluster(ipolicy={"unknown_key": None})
223     self.assertRaises(errors.ConfigurationError, cluster.UpgradeConfig)
224
225
226 class TestOS(unittest.TestCase):
227   ALL_DATA = [
228     "debootstrap",
229     "debootstrap+default",
230     "debootstrap++default",
231     ]
232
233   def testSplitNameVariant(self):
234     for name in self.ALL_DATA:
235       self.assertEqual(len(objects.OS.SplitNameVariant(name)), 2)
236
237   def testVariant(self):
238     self.assertEqual(objects.OS.GetVariant("debootstrap"), "")
239     self.assertEqual(objects.OS.GetVariant("debootstrap+default"), "default")
240
241
242 class TestInstance(unittest.TestCase):
243   def _GenericCheck(self, inst):
244     for i in [inst.all_nodes, inst.secondary_nodes]:
245       self.assertTrue(isinstance(inst.all_nodes, (list, tuple)),
246                       msg="Data type doesn't guarantee order")
247
248     self.assertTrue(inst.primary_node not in inst.secondary_nodes)
249     self.assertEqual(inst.all_nodes[0], inst.primary_node,
250                      msg="Primary node not first node in list")
251
252   def testNodesNoDisks(self):
253     inst = objects.Instance(name="fakeinst.example.com",
254       primary_node="pnode.example.com",
255       disks=[
256         ])
257
258     self._GenericCheck(inst)
259     self.assertEqual(len(inst.secondary_nodes), 0)
260     self.assertEqual(set(inst.all_nodes), set([inst.primary_node]))
261     self.assertEqual(inst.MapLVsByNode(), {
262       inst.primary_node: [],
263       })
264
265   def testNodesPlainDisks(self):
266     inst = objects.Instance(name="fakeinstplain.example.com",
267       primary_node="node3.example.com",
268       disks=[
269         objects.Disk(dev_type=constants.LD_LV, size=128,
270                      logical_id=("myxenvg", "disk25494")),
271         objects.Disk(dev_type=constants.LD_LV, size=512,
272                      logical_id=("myxenvg", "disk29071")),
273         ])
274
275     self._GenericCheck(inst)
276     self.assertEqual(len(inst.secondary_nodes), 0)
277     self.assertEqual(set(inst.all_nodes), set([inst.primary_node]))
278     self.assertEqual(inst.MapLVsByNode(), {
279       inst.primary_node: ["myxenvg/disk25494", "myxenvg/disk29071"],
280       })
281
282   def testNodesDrbdDisks(self):
283     inst = objects.Instance(name="fakeinstdrbd.example.com",
284       primary_node="node10.example.com",
285       disks=[
286         objects.Disk(dev_type=constants.LD_DRBD8, size=786432,
287           logical_id=("node10.example.com", "node15.example.com",
288                       12300, 0, 0, "secret"),
289           children=[
290             objects.Disk(dev_type=constants.LD_LV, size=786432,
291                          logical_id=("myxenvg", "disk0")),
292             objects.Disk(dev_type=constants.LD_LV, size=128,
293                          logical_id=("myxenvg", "meta0"))
294           ],
295           iv_name="disk/0")
296         ])
297
298     self._GenericCheck(inst)
299     self.assertEqual(set(inst.secondary_nodes), set(["node15.example.com"]))
300     self.assertEqual(set(inst.all_nodes),
301                      set([inst.primary_node, "node15.example.com"]))
302     self.assertEqual(inst.MapLVsByNode(), {
303       inst.primary_node: ["myxenvg/disk0", "myxenvg/meta0"],
304       "node15.example.com": ["myxenvg/disk0", "myxenvg/meta0"],
305       })
306
307     self.assertEqual(inst.FindDisk(0), inst.disks[0])
308     self.assertRaises(errors.OpPrereqError, inst.FindDisk, "hello")
309     self.assertRaises(errors.OpPrereqError, inst.FindDisk, 100)
310     self.assertRaises(errors.OpPrereqError, inst.FindDisk, 1)
311
312
313 class TestNode(unittest.TestCase):
314   def testEmpty(self):
315     self.assertEqual(objects.Node().ToDict(), {})
316     self.assertTrue(isinstance(objects.Node.FromDict({}), objects.Node))
317
318   def testHvState(self):
319     node = objects.Node(name="node18157.example.com", hv_state={
320       constants.HT_XEN_HVM: objects.NodeHvState(cpu_total=64),
321       constants.HT_KVM: objects.NodeHvState(cpu_node=1),
322       })
323
324     node2 = objects.Node.FromDict(node.ToDict())
325
326     # Make sure nothing can reference it anymore
327     del node
328
329     self.assertEqual(node2.name, "node18157.example.com")
330     self.assertEqual(frozenset(node2.hv_state), frozenset([
331       constants.HT_XEN_HVM,
332       constants.HT_KVM,
333       ]))
334     self.assertEqual(node2.hv_state[constants.HT_KVM].cpu_node, 1)
335     self.assertEqual(node2.hv_state[constants.HT_XEN_HVM].cpu_total, 64)
336
337   def testDiskState(self):
338     node = objects.Node(name="node32087.example.com", disk_state={
339       constants.LD_LV: {
340         "lv32352": objects.NodeDiskState(total=128),
341         "lv2082": objects.NodeDiskState(total=512),
342         },
343       })
344
345     node2 = objects.Node.FromDict(node.ToDict())
346
347     # Make sure nothing can reference it anymore
348     del node
349
350     self.assertEqual(node2.name, "node32087.example.com")
351     self.assertEqual(frozenset(node2.disk_state), frozenset([
352       constants.LD_LV,
353       ]))
354     self.assertEqual(frozenset(node2.disk_state[constants.LD_LV]), frozenset([
355       "lv32352",
356       "lv2082",
357       ]))
358     self.assertEqual(node2.disk_state[constants.LD_LV]["lv2082"].total, 512)
359     self.assertEqual(node2.disk_state[constants.LD_LV]["lv32352"].total, 128)
360
361   def testFilterEsNdp(self):
362     node1 = objects.Node(name="node11673.example.com", ndparams={
363       constants.ND_EXCLUSIVE_STORAGE: True,
364       })
365     node2 = objects.Node(name="node11674.example.com", ndparams={
366       constants.ND_SPINDLE_COUNT: 3,
367       constants.ND_EXCLUSIVE_STORAGE: False,
368       })
369     self.assertTrue(constants.ND_EXCLUSIVE_STORAGE in node1.ndparams)
370     node1.UpgradeConfig()
371     self.assertFalse(constants.ND_EXCLUSIVE_STORAGE in node1.ndparams)
372     self.assertTrue(constants.ND_EXCLUSIVE_STORAGE in node2.ndparams)
373     self.assertTrue(constants.ND_SPINDLE_COUNT in node2.ndparams)
374     node2.UpgradeConfig()
375     self.assertFalse(constants.ND_EXCLUSIVE_STORAGE in node2.ndparams)
376     self.assertTrue(constants.ND_SPINDLE_COUNT in node2.ndparams)
377
378
379 class TestInstancePolicy(unittest.TestCase):
380   def setUp(self):
381     # Policies are big, and we want to see the difference in case of an error
382     self.maxDiff = None
383
384   def _AssertIPolicyIsFull(self, policy):
385     self.assertEqual(frozenset(policy.keys()), constants.IPOLICY_ALL_KEYS)
386     for key in constants.IPOLICY_ISPECS:
387       spec = policy[key]
388       self.assertEqual(frozenset(spec.keys()), constants.ISPECS_PARAMETERS)
389
390   def testDefaultIPolicy(self):
391     objects.InstancePolicy.CheckParameterSyntax(constants.IPOLICY_DEFAULTS,
392                                                 True)
393     self._AssertIPolicyIsFull(constants.IPOLICY_DEFAULTS)
394
395   def testCheckISpecSyntax(self):
396     par = "my_parameter"
397     for check_std in [True, False]:
398       if check_std:
399         allkeys = constants.IPOLICY_ISPECS
400       else:
401         allkeys = constants.IPOLICY_ISPECS - frozenset([constants.ISPECS_STD])
402       # Only one policy limit
403       for key in allkeys:
404         policy = dict((k, {}) for k in allkeys)
405         policy[key][par] = 11
406         objects.InstancePolicy.CheckISpecSyntax(policy, par, check_std)
407       # Min and max only
408       good_values = [(11, 11), (11, 40), (0, 0)]
409       for (mn, mx) in good_values:
410         policy = dict((k, {}) for k in allkeys)
411         policy[constants.ISPECS_MIN][par] = mn
412         policy[constants.ISPECS_MAX][par] = mx
413         objects.InstancePolicy.CheckISpecSyntax(policy, par, check_std)
414       policy = dict((k, {}) for k in allkeys)
415       policy[constants.ISPECS_MIN][par] = 11
416       policy[constants.ISPECS_MAX][par] = 5
417       self.assertRaises(errors.ConfigurationError,
418                         objects.InstancePolicy.CheckISpecSyntax,
419                         policy, par, check_std)
420     # Min, std, max
421     good_values = [
422       (11, 11, 11),
423       (11, 11, 40),
424       (11, 40, 40),
425       ]
426     for (mn, st, mx) in good_values:
427       policy = {
428         constants.ISPECS_MIN: {par: mn},
429         constants.ISPECS_STD: {par: st},
430         constants.ISPECS_MAX: {par: mx},
431         }
432       objects.InstancePolicy.CheckISpecSyntax(policy, par, True)
433     bad_values = [
434       (11, 11,  5),
435       (40, 11, 11),
436       (11, 80, 40),
437       (11,  5, 40),
438       (11,  5,  5),
439       (40, 40, 11),
440       ]
441     for (mn, st, mx) in bad_values:
442       policy = {
443         constants.ISPECS_MIN: {par: mn},
444         constants.ISPECS_STD: {par: st},
445         constants.ISPECS_MAX: {par: mx},
446         }
447       self.assertRaises(errors.ConfigurationError,
448                         objects.InstancePolicy.CheckISpecSyntax,
449                         policy, par, True)
450
451   def testCheckDiskTemplates(self):
452     invalid = "this_is_not_a_good_template"
453     for dt in constants.DISK_TEMPLATES:
454       objects.InstancePolicy.CheckDiskTemplates([dt])
455     objects.InstancePolicy.CheckDiskTemplates(list(constants.DISK_TEMPLATES))
456     bad_examples = [
457       [invalid],
458       [constants.DT_DRBD8, invalid],
459       list(constants.DISK_TEMPLATES) + [invalid],
460       [],
461       None,
462       ]
463     for dtl in bad_examples:
464       self.assertRaises(errors.ConfigurationError,
465                         objects.InstancePolicy.CheckDiskTemplates,
466                         dtl)
467
468   def testCheckParameterSyntax(self):
469     invalid = "this_key_shouldnt_be_here"
470     for check_std in [True, False]:
471       self.assertRaises(KeyError,
472                         objects.InstancePolicy.CheckParameterSyntax,
473                         {}, check_std)
474       policy = objects.MakeEmptyIPolicy()
475       policy[invalid] = None
476       self.assertRaises(errors.ConfigurationError,
477                         objects.InstancePolicy.CheckParameterSyntax,
478                         policy, check_std)
479       for par in constants.IPOLICY_PARAMETERS:
480         policy = objects.MakeEmptyIPolicy()
481         for val in ("blah", None, {}, [42]):
482           policy[par] = val
483           self.assertRaises(errors.ConfigurationError,
484                             objects.InstancePolicy.CheckParameterSyntax,
485                             policy, check_std)
486
487   def testFillIPolicyEmpty(self):
488     policy = objects.FillIPolicy(constants.IPOLICY_DEFAULTS, {})
489     objects.InstancePolicy.CheckParameterSyntax(policy, True)
490     self.assertEqual(policy, constants.IPOLICY_DEFAULTS)
491
492   def _AssertISpecsMerged(self, default_spec, diff_spec, merged_spec):
493     for (param, value) in merged_spec.items():
494       if param in diff_spec:
495         self.assertEqual(value, diff_spec[param])
496       else:
497         self.assertEqual(value, default_spec[param])
498
499   def _AssertIPolicyMerged(self, default_pol, diff_pol, merged_pol):
500     for (key, value) in merged_pol.items():
501       if key in diff_pol:
502         if key in constants.IPOLICY_ISPECS:
503           self._AssertISpecsMerged(default_pol[key], diff_pol[key], value)
504         else:
505           self.assertEqual(value, diff_pol[key])
506       else:
507         self.assertEqual(value, default_pol[key])
508
509   def testFillIPolicy(self):
510     partial_policies = [
511       {constants.IPOLICY_VCPU_RATIO: 3.14},
512       {constants.IPOLICY_SPINDLE_RATIO: 2.72},
513       {constants.IPOLICY_DTS: [constants.DT_FILE]},
514       ]
515     for diff_pol in partial_policies:
516       policy = objects.FillIPolicy(constants.IPOLICY_DEFAULTS, diff_pol)
517       objects.InstancePolicy.CheckParameterSyntax(policy, True)
518       self._AssertIPolicyIsFull(policy)
519       self._AssertIPolicyMerged(constants.IPOLICY_DEFAULTS, diff_pol, policy)
520
521   def testFillIPolicySpecs(self):
522     partial_policies = [
523       {constants.ISPECS_MIN: {constants.ISPEC_MEM_SIZE: 32},
524        constants.ISPECS_MAX: {constants.ISPEC_CPU_COUNT: 1024}},
525       {constants.ISPECS_STD: {constants.ISPEC_DISK_SIZE: 2048},
526        constants.ISPECS_MAX: {
527           constants.ISPEC_DISK_COUNT: constants.MAX_DISKS - 1,
528           constants.ISPEC_NIC_COUNT: constants.MAX_NICS - 1,
529           }},
530       {constants.ISPECS_STD: {constants.ISPEC_SPINDLE_USE: 3}},
531       ]
532     for diff_pol in partial_policies:
533       policy = objects.FillIPolicy(constants.IPOLICY_DEFAULTS, diff_pol)
534       objects.InstancePolicy.CheckParameterSyntax(policy, True)
535       self._AssertIPolicyIsFull(policy)
536       self._AssertIPolicyMerged(constants.IPOLICY_DEFAULTS, diff_pol, policy)
537
538
539 if __name__ == "__main__":
540   testutils.GanetiTestProgram()