unit tests: Add tests for file mode handling in utils.WriteFile
[ganeti-local] / test / ganeti.cmdlib_unittest.py
1 #!/usr/bin/python
2 #
3
4 # Copyright (C) 2008, 2011 Google Inc.
5 #
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 # General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 # 02110-1301, USA.
20
21
22 """Script for unittesting the cmdlib module"""
23
24
25 import os
26 import unittest
27 import time
28 import tempfile
29 import shutil
30 import operator
31 import itertools
32
33 from ganeti import constants
34 from ganeti import mcpu
35 from ganeti import cmdlib
36 from ganeti import opcodes
37 from ganeti import errors
38 from ganeti import utils
39 from ganeti import luxi
40 from ganeti import ht
41 from ganeti import objects
42 from ganeti import compat
43 from ganeti import rpc
44 from ganeti.hypervisor import hv_xen
45
46 import testutils
47 import mocks
48
49
50 class TestCertVerification(testutils.GanetiTestCase):
51   def setUp(self):
52     testutils.GanetiTestCase.setUp(self)
53
54     self.tmpdir = tempfile.mkdtemp()
55
56   def tearDown(self):
57     shutil.rmtree(self.tmpdir)
58
59   def testVerifyCertificate(self):
60     cmdlib._VerifyCertificate(self._TestDataFilename("cert1.pem"))
61
62     nonexist_filename = os.path.join(self.tmpdir, "does-not-exist")
63
64     (errcode, msg) = cmdlib._VerifyCertificate(nonexist_filename)
65     self.assertEqual(errcode, cmdlib.LUClusterVerifyConfig.ETYPE_ERROR)
66
67     # Try to load non-certificate file
68     invalid_cert = self._TestDataFilename("bdev-net.txt")
69     (errcode, msg) = cmdlib._VerifyCertificate(invalid_cert)
70     self.assertEqual(errcode, cmdlib.LUClusterVerifyConfig.ETYPE_ERROR)
71
72
73 class TestOpcodeParams(testutils.GanetiTestCase):
74   def testParamsStructures(self):
75     for op in sorted(mcpu.Processor.DISPATCH_TABLE):
76       lu = mcpu.Processor.DISPATCH_TABLE[op]
77       lu_name = lu.__name__
78       self.failIf(hasattr(lu, "_OP_REQP"),
79                   msg=("LU '%s' has old-style _OP_REQP" % lu_name))
80       self.failIf(hasattr(lu, "_OP_DEFS"),
81                   msg=("LU '%s' has old-style _OP_DEFS" % lu_name))
82       self.failIf(hasattr(lu, "_OP_PARAMS"),
83                   msg=("LU '%s' has old-style _OP_PARAMS" % lu_name))
84
85
86 class TestIAllocatorChecks(testutils.GanetiTestCase):
87   def testFunction(self):
88     class TestLU(object):
89       def __init__(self, opcode):
90         self.cfg = mocks.FakeConfig()
91         self.op = opcode
92
93     class OpTest(opcodes.OpCode):
94        OP_PARAMS = [
95         ("iallocator", None, ht.NoType, None),
96         ("node", None, ht.NoType, None),
97         ]
98
99     default_iallocator = mocks.FakeConfig().GetDefaultIAllocator()
100     other_iallocator = default_iallocator + "_not"
101
102     op = OpTest()
103     lu = TestLU(op)
104
105     c_i = lambda: cmdlib._CheckIAllocatorOrNode(lu, "iallocator", "node")
106
107     # Neither node nor iallocator given
108     op.iallocator = None
109     op.node = None
110     c_i()
111     self.assertEqual(lu.op.iallocator, default_iallocator)
112     self.assertEqual(lu.op.node, None)
113
114     # Both, iallocator and node given
115     op.iallocator = "test"
116     op.node = "test"
117     self.assertRaises(errors.OpPrereqError, c_i)
118
119     # Only iallocator given
120     op.iallocator = other_iallocator
121     op.node = None
122     c_i()
123     self.assertEqual(lu.op.iallocator, other_iallocator)
124     self.assertEqual(lu.op.node, None)
125
126     # Only node given
127     op.iallocator = None
128     op.node = "node"
129     c_i()
130     self.assertEqual(lu.op.iallocator, None)
131     self.assertEqual(lu.op.node, "node")
132
133     # No node, iallocator or default iallocator
134     op.iallocator = None
135     op.node = None
136     lu.cfg.GetDefaultIAllocator = lambda: None
137     self.assertRaises(errors.OpPrereqError, c_i)
138
139
140 class TestLUTestJqueue(unittest.TestCase):
141   def test(self):
142     self.assert_(cmdlib.LUTestJqueue._CLIENT_CONNECT_TIMEOUT <
143                  (luxi.WFJC_TIMEOUT * 0.75),
144                  msg=("Client timeout too high, might not notice bugs"
145                       " in WaitForJobChange"))
146
147
148 class TestLUQuery(unittest.TestCase):
149   def test(self):
150     self.assertEqual(sorted(cmdlib._QUERY_IMPL.keys()),
151                      sorted(constants.QR_VIA_OP))
152
153     assert constants.QR_NODE in constants.QR_VIA_OP
154     assert constants.QR_INSTANCE in constants.QR_VIA_OP
155
156     for i in constants.QR_VIA_OP:
157       self.assert_(cmdlib._GetQueryImplementation(i))
158
159     self.assertRaises(errors.OpPrereqError, cmdlib._GetQueryImplementation, "")
160     self.assertRaises(errors.OpPrereqError, cmdlib._GetQueryImplementation,
161                       "xyz")
162
163
164 class TestLUGroupAssignNodes(unittest.TestCase):
165
166   def testCheckAssignmentForSplitInstances(self):
167     node_data = dict((name, objects.Node(name=name, group=group))
168                      for (name, group) in [("n1a", "g1"), ("n1b", "g1"),
169                                            ("n2a", "g2"), ("n2b", "g2"),
170                                            ("n3a", "g3"), ("n3b", "g3"),
171                                            ("n3c", "g3"),
172                                            ])
173
174     def Instance(name, pnode, snode):
175       if snode is None:
176         disks = []
177         disk_template = constants.DT_DISKLESS
178       else:
179         disks = [objects.Disk(dev_type=constants.LD_DRBD8,
180                               logical_id=[pnode, snode, 1, 17, 17])]
181         disk_template = constants.DT_DRBD8
182
183       return objects.Instance(name=name, primary_node=pnode, disks=disks,
184                               disk_template=disk_template)
185
186     instance_data = dict((name, Instance(name, pnode, snode))
187                          for name, pnode, snode in [("inst1a", "n1a", "n1b"),
188                                                     ("inst1b", "n1b", "n1a"),
189                                                     ("inst2a", "n2a", "n2b"),
190                                                     ("inst3a", "n3a", None),
191                                                     ("inst3b", "n3b", "n1b"),
192                                                     ("inst3c", "n3b", "n2b"),
193                                                     ])
194
195     # Test first with the existing state.
196     (new, prev) = \
197       cmdlib.LUGroupAssignNodes.CheckAssignmentForSplitInstances([],
198                                                                  node_data,
199                                                                  instance_data)
200
201     self.assertEqual([], new)
202     self.assertEqual(set(["inst3b", "inst3c"]), set(prev))
203
204     # And now some changes.
205     (new, prev) = \
206       cmdlib.LUGroupAssignNodes.CheckAssignmentForSplitInstances([("n1b",
207                                                                    "g3")],
208                                                                  node_data,
209                                                                  instance_data)
210
211     self.assertEqual(set(["inst1a", "inst1b"]), set(new))
212     self.assertEqual(set(["inst3c"]), set(prev))
213
214
215 class TestClusterVerifySsh(unittest.TestCase):
216   def testMultipleGroups(self):
217     fn = cmdlib.LUClusterVerifyGroup._SelectSshCheckNodes
218     mygroupnodes = [
219       objects.Node(name="node20", group="my", offline=False),
220       objects.Node(name="node21", group="my", offline=False),
221       objects.Node(name="node22", group="my", offline=False),
222       objects.Node(name="node23", group="my", offline=False),
223       objects.Node(name="node24", group="my", offline=False),
224       objects.Node(name="node25", group="my", offline=False),
225       objects.Node(name="node26", group="my", offline=True),
226       ]
227     nodes = [
228       objects.Node(name="node1", group="g1", offline=True),
229       objects.Node(name="node2", group="g1", offline=False),
230       objects.Node(name="node3", group="g1", offline=False),
231       objects.Node(name="node4", group="g1", offline=True),
232       objects.Node(name="node5", group="g1", offline=False),
233       objects.Node(name="node10", group="xyz", offline=False),
234       objects.Node(name="node11", group="xyz", offline=False),
235       objects.Node(name="node40", group="alloff", offline=True),
236       objects.Node(name="node41", group="alloff", offline=True),
237       objects.Node(name="node50", group="aaa", offline=False),
238       ] + mygroupnodes
239     assert not utils.FindDuplicates(map(operator.attrgetter("name"), nodes))
240
241     (online, perhost) = fn(mygroupnodes, "my", nodes)
242     self.assertEqual(online, ["node%s" % i for i in range(20, 26)])
243     self.assertEqual(set(perhost.keys()), set(online))
244
245     self.assertEqual(perhost, {
246       "node20": ["node10", "node2", "node50"],
247       "node21": ["node11", "node3", "node50"],
248       "node22": ["node10", "node5", "node50"],
249       "node23": ["node11", "node2", "node50"],
250       "node24": ["node10", "node3", "node50"],
251       "node25": ["node11", "node5", "node50"],
252       })
253
254   def testSingleGroup(self):
255     fn = cmdlib.LUClusterVerifyGroup._SelectSshCheckNodes
256     nodes = [
257       objects.Node(name="node1", group="default", offline=True),
258       objects.Node(name="node2", group="default", offline=False),
259       objects.Node(name="node3", group="default", offline=False),
260       objects.Node(name="node4", group="default", offline=True),
261       ]
262     assert not utils.FindDuplicates(map(operator.attrgetter("name"), nodes))
263
264     (online, perhost) = fn(nodes, "default", nodes)
265     self.assertEqual(online, ["node2", "node3"])
266     self.assertEqual(set(perhost.keys()), set(online))
267
268     self.assertEqual(perhost, {
269       "node2": [],
270       "node3": [],
271       })
272
273
274 class TestClusterVerifyFiles(unittest.TestCase):
275   @staticmethod
276   def _FakeErrorIf(errors, cond, ecode, item, msg, *args, **kwargs):
277     assert ((ecode == constants.CV_ENODEFILECHECK and
278              ht.TNonEmptyString(item)) or
279             (ecode == constants.CV_ECLUSTERFILECHECK and
280              item is None))
281
282     if args:
283       msg = msg % args
284
285     if cond:
286       errors.append((item, msg))
287
288   _VerifyFiles = cmdlib.LUClusterVerifyGroup._VerifyFiles
289
290   def test(self):
291     errors = []
292     master_name = "master.example.com"
293     nodeinfo = [
294       objects.Node(name=master_name, offline=False, vm_capable=True),
295       objects.Node(name="node2.example.com", offline=False, vm_capable=True),
296       objects.Node(name="node3.example.com", master_candidate=True,
297                    vm_capable=False),
298       objects.Node(name="node4.example.com", offline=False, vm_capable=True),
299       objects.Node(name="nodata.example.com", offline=False, vm_capable=True),
300       objects.Node(name="offline.example.com", offline=True),
301       ]
302     cluster = objects.Cluster(modify_etc_hosts=True,
303                               enabled_hypervisors=[constants.HT_XEN_HVM])
304     files_all = set([
305       constants.CLUSTER_DOMAIN_SECRET_FILE,
306       constants.RAPI_CERT_FILE,
307       constants.RAPI_USERS_FILE,
308       ])
309     files_opt = set([
310       constants.RAPI_USERS_FILE,
311       hv_xen.XL_CONFIG_FILE,
312       constants.VNC_PASSWORD_FILE,
313       ])
314     files_mc = set([
315       constants.CLUSTER_CONF_FILE,
316       ])
317     files_vm = set([
318       hv_xen.XEND_CONFIG_FILE,
319       hv_xen.XL_CONFIG_FILE,
320       constants.VNC_PASSWORD_FILE,
321       ])
322     nvinfo = {
323       master_name: rpc.RpcResult(data=(True, {
324         constants.NV_FILELIST: {
325           constants.CLUSTER_CONF_FILE: "82314f897f38b35f9dab2f7c6b1593e0",
326           constants.RAPI_CERT_FILE: "babbce8f387bc082228e544a2146fee4",
327           constants.CLUSTER_DOMAIN_SECRET_FILE: "cds-47b5b3f19202936bb4",
328           hv_xen.XEND_CONFIG_FILE: "b4a8a824ab3cac3d88839a9adeadf310",
329           hv_xen.XL_CONFIG_FILE: "77935cee92afd26d162f9e525e3d49b9"
330         }})),
331       "node2.example.com": rpc.RpcResult(data=(True, {
332         constants.NV_FILELIST: {
333           constants.RAPI_CERT_FILE: "97f0356500e866387f4b84233848cc4a",
334           hv_xen.XEND_CONFIG_FILE: "b4a8a824ab3cac3d88839a9adeadf310",
335           }
336         })),
337       "node3.example.com": rpc.RpcResult(data=(True, {
338         constants.NV_FILELIST: {
339           constants.RAPI_CERT_FILE: "97f0356500e866387f4b84233848cc4a",
340           constants.CLUSTER_DOMAIN_SECRET_FILE: "cds-47b5b3f19202936bb4",
341           }
342         })),
343       "node4.example.com": rpc.RpcResult(data=(True, {
344         constants.NV_FILELIST: {
345           constants.RAPI_CERT_FILE: "97f0356500e866387f4b84233848cc4a",
346           constants.CLUSTER_CONF_FILE: "conf-a6d4b13e407867f7a7b4f0f232a8f527",
347           constants.CLUSTER_DOMAIN_SECRET_FILE: "cds-47b5b3f19202936bb4",
348           constants.RAPI_USERS_FILE: "rapiusers-ea3271e8d810ef3",
349           hv_xen.XL_CONFIG_FILE: "77935cee92afd26d162f9e525e3d49b9"
350           }
351         })),
352       "nodata.example.com": rpc.RpcResult(data=(True, {})),
353       "offline.example.com": rpc.RpcResult(offline=True),
354       }
355     assert set(nvinfo.keys()) == set(map(operator.attrgetter("name"), nodeinfo))
356
357     self._VerifyFiles(compat.partial(self._FakeErrorIf, errors), nodeinfo,
358                       master_name, nvinfo,
359                       (files_all, files_opt, files_mc, files_vm))
360     self.assertEqual(sorted(errors), sorted([
361       (None, ("File %s found with 2 different checksums (variant 1 on"
362               " node2.example.com, node3.example.com, node4.example.com;"
363               " variant 2 on master.example.com)" % constants.RAPI_CERT_FILE)),
364       (None, ("File %s is missing from node(s) node2.example.com" %
365               constants.CLUSTER_DOMAIN_SECRET_FILE)),
366       (None, ("File %s should not exist on node(s) node4.example.com" %
367               constants.CLUSTER_CONF_FILE)),
368       (None, ("File %s is missing from node(s) node4.example.com" %
369               hv_xen.XEND_CONFIG_FILE)),
370       (None, ("File %s is missing from node(s) node3.example.com" %
371               constants.CLUSTER_CONF_FILE)),
372       (None, ("File %s found with 2 different checksums (variant 1 on"
373               " master.example.com; variant 2 on node4.example.com)" %
374               constants.CLUSTER_CONF_FILE)),
375       (None, ("File %s is optional, but it must exist on all or no nodes (not"
376               " found on master.example.com, node2.example.com,"
377               " node3.example.com)" % constants.RAPI_USERS_FILE)),
378       (None, ("File %s is optional, but it must exist on all or no nodes (not"
379               " found on node2.example.com)" % hv_xen.XL_CONFIG_FILE)),
380       ("nodata.example.com", "Node did not return file checksum data"),
381       ]))
382
383
384 class _FakeLU:
385   def __init__(self):
386     self.warning_log = []
387     self.info_log = []
388
389   def LogWarning(self, text, *args):
390     self.warning_log.append((text, args))
391
392   def LogInfo(self, text, *args):
393     self.info_log.append((text, args))
394
395
396 class TestLoadNodeEvacResult(unittest.TestCase):
397   def testSuccess(self):
398     for moved in [[], [
399       ("inst20153.example.com", "grp2", ["nodeA4509", "nodeB2912"]),
400       ]]:
401       for early_release in [False, True]:
402         for use_nodes in [False, True]:
403           jobs = [
404             [opcodes.OpInstanceReplaceDisks().__getstate__()],
405             [opcodes.OpInstanceMigrate().__getstate__()],
406             ]
407
408           alloc_result = (moved, [], jobs)
409           assert cmdlib.IAllocator._NEVAC_RESULT(alloc_result)
410
411           lu = _FakeLU()
412           result = cmdlib._LoadNodeEvacResult(lu, alloc_result,
413                                               early_release, use_nodes)
414
415           if moved:
416             (_, (info_args, )) = lu.info_log.pop(0)
417             for (instname, instgroup, instnodes) in moved:
418               self.assertTrue(instname in info_args)
419               if use_nodes:
420                 for i in instnodes:
421                   self.assertTrue(i in info_args)
422               else:
423                 self.assertTrue(instgroup in info_args)
424
425           self.assertFalse(lu.info_log)
426           self.assertFalse(lu.warning_log)
427
428           for op in itertools.chain(*result):
429             if hasattr(op.__class__, "early_release"):
430               self.assertEqual(op.early_release, early_release)
431             else:
432               self.assertFalse(hasattr(op, "early_release"))
433
434   def testFailed(self):
435     alloc_result = ([], [
436       ("inst5191.example.com", "errormsg21178"),
437       ], [])
438     assert cmdlib.IAllocator._NEVAC_RESULT(alloc_result)
439
440     lu = _FakeLU()
441     self.assertRaises(errors.OpExecError, cmdlib._LoadNodeEvacResult,
442                       lu, alloc_result, False, False)
443     self.assertFalse(lu.info_log)
444     (_, (args, )) = lu.warning_log.pop(0)
445     self.assertTrue("inst5191.example.com" in args)
446     self.assertTrue("errormsg21178" in args)
447     self.assertFalse(lu.warning_log)
448
449
450 class TestUpdateAndVerifySubDict(unittest.TestCase):
451   def setUp(self):
452     self.type_check = {
453         "a": constants.VTYPE_INT,
454         "b": constants.VTYPE_STRING,
455         "c": constants.VTYPE_BOOL,
456         "d": constants.VTYPE_STRING,
457         }
458
459   def test(self):
460     old_test = {
461       "foo": {
462         "d": "blubb",
463         "a": 321,
464         },
465       "baz": {
466         "a": 678,
467         "b": "678",
468         "c": True,
469         },
470       }
471     test = {
472       "foo": {
473         "a": 123,
474         "b": "123",
475         "c": True,
476         },
477       "bar": {
478         "a": 321,
479         "b": "321",
480         "c": False,
481         },
482       }
483
484     mv = {
485       "foo": {
486         "a": 123,
487         "b": "123",
488         "c": True,
489         "d": "blubb"
490         },
491       "bar": {
492         "a": 321,
493         "b": "321",
494         "c": False,
495         },
496       "baz": {
497         "a": 678,
498         "b": "678",
499         "c": True,
500         },
501       }
502
503     verified = cmdlib._UpdateAndVerifySubDict(old_test, test, self.type_check)
504     self.assertEqual(verified, mv)
505
506   def testWrong(self):
507     test = {
508       "foo": {
509         "a": "blubb",
510         "b": "123",
511         "c": True,
512         },
513       "bar": {
514         "a": 321,
515         "b": "321",
516         "c": False,
517         },
518       }
519
520     self.assertRaises(errors.TypeEnforcementError,
521                       cmdlib._UpdateAndVerifySubDict, {}, test, self.type_check)
522
523
524 class TestHvStateHelper(unittest.TestCase):
525   def testWithoutOpData(self):
526     self.assertEqual(cmdlib._MergeAndVerifyHvState(None, NotImplemented), None)
527
528   def testWithoutOldData(self):
529     new = {
530       constants.HT_XEN_PVM: {
531         constants.HVST_MEMORY_TOTAL: 4096,
532         },
533       }
534     self.assertEqual(cmdlib._MergeAndVerifyHvState(new, None), new)
535
536   def testWithWrongHv(self):
537     new = {
538       "i-dont-exist": {
539         constants.HVST_MEMORY_TOTAL: 4096,
540         },
541       }
542     self.assertRaises(errors.OpPrereqError, cmdlib._MergeAndVerifyHvState, new,
543                       None)
544
545 class TestDiskStateHelper(unittest.TestCase):
546   def testWithoutOpData(self):
547     self.assertEqual(cmdlib._MergeAndVerifyDiskState(None, NotImplemented),
548                      None)
549
550   def testWithoutOldData(self):
551     new = {
552       constants.LD_LV: {
553         "xenvg": {
554           constants.DS_DISK_RESERVED: 1024,
555           },
556         },
557       }
558     self.assertEqual(cmdlib._MergeAndVerifyDiskState(new, None), new)
559
560   def testWithWrongStorageType(self):
561     new = {
562       "i-dont-exist": {
563         "xenvg": {
564           constants.DS_DISK_RESERVED: 1024,
565           },
566         },
567       }
568     self.assertRaises(errors.OpPrereqError, cmdlib._MergeAndVerifyDiskState,
569                       new, None)
570
571
572 if __name__ == "__main__":
573   testutils.GanetiTestProgram()