Allow per-hypervisor optional files
[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 # 0.0510-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
32 from ganeti import constants
33 from ganeti import mcpu
34 from ganeti import cmdlib
35 from ganeti import opcodes
36 from ganeti import errors
37 from ganeti import utils
38 from ganeti import luxi
39 from ganeti import ht
40 from ganeti import objects
41 from ganeti import compat
42 from ganeti import rpc
43 from ganeti.hypervisor import hv_xen
44
45 import testutils
46 import mocks
47
48
49 class TestCertVerification(testutils.GanetiTestCase):
50   def setUp(self):
51     testutils.GanetiTestCase.setUp(self)
52
53     self.tmpdir = tempfile.mkdtemp()
54
55   def tearDown(self):
56     shutil.rmtree(self.tmpdir)
57
58   def testVerifyCertificate(self):
59     cmdlib._VerifyCertificate(self._TestDataFilename("cert1.pem"))
60
61     nonexist_filename = os.path.join(self.tmpdir, "does-not-exist")
62
63     (errcode, msg) = cmdlib._VerifyCertificate(nonexist_filename)
64     self.assertEqual(errcode, cmdlib.LUClusterVerifyConfig.ETYPE_ERROR)
65
66     # Try to load non-certificate file
67     invalid_cert = self._TestDataFilename("bdev-net.txt")
68     (errcode, msg) = cmdlib._VerifyCertificate(invalid_cert)
69     self.assertEqual(errcode, cmdlib.LUClusterVerifyConfig.ETYPE_ERROR)
70
71
72 class TestOpcodeParams(testutils.GanetiTestCase):
73   def testParamsStructures(self):
74     for op in sorted(mcpu.Processor.DISPATCH_TABLE):
75       lu = mcpu.Processor.DISPATCH_TABLE[op]
76       lu_name = lu.__name__
77       self.failIf(hasattr(lu, "_OP_REQP"),
78                   msg=("LU '%s' has old-style _OP_REQP" % lu_name))
79       self.failIf(hasattr(lu, "_OP_DEFS"),
80                   msg=("LU '%s' has old-style _OP_DEFS" % lu_name))
81       self.failIf(hasattr(lu, "_OP_PARAMS"),
82                   msg=("LU '%s' has old-style _OP_PARAMS" % lu_name))
83
84
85 class TestIAllocatorChecks(testutils.GanetiTestCase):
86   def testFunction(self):
87     class TestLU(object):
88       def __init__(self, opcode):
89         self.cfg = mocks.FakeConfig()
90         self.op = opcode
91
92     class OpTest(opcodes.OpCode):
93        OP_PARAMS = [
94         ("iallocator", None, ht.NoType, None),
95         ("node", None, ht.NoType, None),
96         ]
97
98     default_iallocator = mocks.FakeConfig().GetDefaultIAllocator()
99     other_iallocator = default_iallocator + "_not"
100
101     op = OpTest()
102     lu = TestLU(op)
103
104     c_i = lambda: cmdlib._CheckIAllocatorOrNode(lu, "iallocator", "node")
105
106     # Neither node nor iallocator given
107     op.iallocator = None
108     op.node = None
109     c_i()
110     self.assertEqual(lu.op.iallocator, default_iallocator)
111     self.assertEqual(lu.op.node, None)
112
113     # Both, iallocator and node given
114     op.iallocator = "test"
115     op.node = "test"
116     self.assertRaises(errors.OpPrereqError, c_i)
117
118     # Only iallocator given
119     op.iallocator = other_iallocator
120     op.node = None
121     c_i()
122     self.assertEqual(lu.op.iallocator, other_iallocator)
123     self.assertEqual(lu.op.node, None)
124
125     # Only node given
126     op.iallocator = None
127     op.node = "node"
128     c_i()
129     self.assertEqual(lu.op.iallocator, None)
130     self.assertEqual(lu.op.node, "node")
131
132     # No node, iallocator or default iallocator
133     op.iallocator = None
134     op.node = None
135     lu.cfg.GetDefaultIAllocator = lambda: None
136     self.assertRaises(errors.OpPrereqError, c_i)
137
138
139 class TestLUTestJqueue(unittest.TestCase):
140   def test(self):
141     self.assert_(cmdlib.LUTestJqueue._CLIENT_CONNECT_TIMEOUT <
142                  (luxi.WFJC_TIMEOUT * 0.75),
143                  msg=("Client timeout too high, might not notice bugs"
144                       " in WaitForJobChange"))
145
146
147 class TestLUQuery(unittest.TestCase):
148   def test(self):
149     self.assertEqual(sorted(cmdlib._QUERY_IMPL.keys()),
150                      sorted(constants.QR_VIA_OP))
151
152     assert constants.QR_NODE in constants.QR_VIA_OP
153     assert constants.QR_INSTANCE in constants.QR_VIA_OP
154
155     for i in constants.QR_VIA_OP:
156       self.assert_(cmdlib._GetQueryImplementation(i))
157
158     self.assertRaises(errors.OpPrereqError, cmdlib._GetQueryImplementation, "")
159     self.assertRaises(errors.OpPrereqError, cmdlib._GetQueryImplementation,
160                       "xyz")
161
162
163 class TestLUGroupAssignNodes(unittest.TestCase):
164
165   def testCheckAssignmentForSplitInstances(self):
166     node_data = dict((name, objects.Node(name=name, group=group))
167                      for (name, group) in [("n1a", "g1"), ("n1b", "g1"),
168                                            ("n2a", "g2"), ("n2b", "g2"),
169                                            ("n3a", "g3"), ("n3b", "g3"),
170                                            ("n3c", "g3"),
171                                            ])
172
173     def Instance(name, pnode, snode):
174       if snode is None:
175         disks = []
176         disk_template = constants.DT_DISKLESS
177       else:
178         disks = [objects.Disk(dev_type=constants.LD_DRBD8,
179                               logical_id=[pnode, snode, 1, 17, 17])]
180         disk_template = constants.DT_DRBD8
181
182       return objects.Instance(name=name, primary_node=pnode, disks=disks,
183                               disk_template=disk_template)
184
185     instance_data = dict((name, Instance(name, pnode, snode))
186                          for name, pnode, snode in [("inst1a", "n1a", "n1b"),
187                                                     ("inst1b", "n1b", "n1a"),
188                                                     ("inst2a", "n2a", "n2b"),
189                                                     ("inst3a", "n3a", None),
190                                                     ("inst3b", "n3b", "n1b"),
191                                                     ("inst3c", "n3b", "n2b"),
192                                                     ])
193
194     # Test first with the existing state.
195     (new, prev) = \
196       cmdlib.LUGroupAssignNodes.CheckAssignmentForSplitInstances([],
197                                                                  node_data,
198                                                                  instance_data)
199
200     self.assertEqual([], new)
201     self.assertEqual(set(["inst3b", "inst3c"]), set(prev))
202
203     # And now some changes.
204     (new, prev) = \
205       cmdlib.LUGroupAssignNodes.CheckAssignmentForSplitInstances([("n1b",
206                                                                    "g3")],
207                                                                  node_data,
208                                                                  instance_data)
209
210     self.assertEqual(set(["inst1a", "inst1b"]), set(new))
211     self.assertEqual(set(["inst3c"]), set(prev))
212
213
214 class TestClusterVerifySsh(unittest.TestCase):
215   def testMultipleGroups(self):
216     fn = cmdlib.LUClusterVerifyGroup._SelectSshCheckNodes
217     mygroupnodes = [
218       objects.Node(name="node20", group="my", offline=False),
219       objects.Node(name="node21", group="my", offline=False),
220       objects.Node(name="node22", group="my", offline=False),
221       objects.Node(name="node23", group="my", offline=False),
222       objects.Node(name="node24", group="my", offline=False),
223       objects.Node(name="node25", group="my", offline=False),
224       objects.Node(name="node26", group="my", offline=True),
225       ]
226     nodes = [
227       objects.Node(name="node1", group="g1", offline=True),
228       objects.Node(name="node2", group="g1", offline=False),
229       objects.Node(name="node3", group="g1", offline=False),
230       objects.Node(name="node4", group="g1", offline=True),
231       objects.Node(name="node5", group="g1", offline=False),
232       objects.Node(name="node10", group="xyz", offline=False),
233       objects.Node(name="node11", group="xyz", offline=False),
234       objects.Node(name="node40", group="alloff", offline=True),
235       objects.Node(name="node41", group="alloff", offline=True),
236       objects.Node(name="node50", group="aaa", offline=False),
237       ] + mygroupnodes
238     assert not utils.FindDuplicates(map(operator.attrgetter("name"), nodes))
239
240     (online, perhost) = fn(mygroupnodes, "my", nodes)
241     self.assertEqual(online, ["node%s" % i for i in range(20, 26)])
242     self.assertEqual(set(perhost.keys()), set(online))
243
244     self.assertEqual(perhost, {
245       "node20": ["node10", "node2", "node50"],
246       "node21": ["node11", "node3", "node50"],
247       "node22": ["node10", "node5", "node50"],
248       "node23": ["node11", "node2", "node50"],
249       "node24": ["node10", "node3", "node50"],
250       "node25": ["node11", "node5", "node50"],
251       })
252
253   def testSingleGroup(self):
254     fn = cmdlib.LUClusterVerifyGroup._SelectSshCheckNodes
255     nodes = [
256       objects.Node(name="node1", group="default", offline=True),
257       objects.Node(name="node2", group="default", offline=False),
258       objects.Node(name="node3", group="default", offline=False),
259       objects.Node(name="node4", group="default", offline=True),
260       ]
261     assert not utils.FindDuplicates(map(operator.attrgetter("name"), nodes))
262
263     (online, perhost) = fn(nodes, "default", nodes)
264     self.assertEqual(online, ["node2", "node3"])
265     self.assertEqual(set(perhost.keys()), set(online))
266
267     self.assertEqual(perhost, {
268       "node2": [],
269       "node3": [],
270       })
271
272
273 class TestClusterVerifyFiles(unittest.TestCase):
274   @staticmethod
275   def _FakeErrorIf(errors, cond, ecode, item, msg, *args, **kwargs):
276     assert ((ecode == cmdlib.LUClusterVerifyGroup.ENODEFILECHECK and
277              ht.TNonEmptyString(item)) or
278             (ecode == cmdlib.LUClusterVerifyGroup.ECLUSTERFILECHECK and
279              item is None))
280
281     if args:
282       msg = msg % args
283
284     if cond:
285       errors.append((item, msg))
286
287   _VerifyFiles = cmdlib.LUClusterVerifyGroup._VerifyFiles
288
289   def test(self):
290     errors = []
291     master_name = "master.example.com"
292     nodeinfo = [
293       objects.Node(name=master_name, offline=False, vm_capable=True),
294       objects.Node(name="node2.example.com", offline=False, vm_capable=True),
295       objects.Node(name="node3.example.com", master_candidate=True,
296                    vm_capable=False),
297       objects.Node(name="node4.example.com", offline=False, vm_capable=True),
298       objects.Node(name="nodata.example.com", offline=False, vm_capable=True),
299       objects.Node(name="offline.example.com", offline=True),
300       ]
301     cluster = objects.Cluster(modify_etc_hosts=True,
302                               enabled_hypervisors=[constants.HT_XEN_HVM])
303     files_all = set([
304       constants.CLUSTER_DOMAIN_SECRET_FILE,
305       constants.RAPI_CERT_FILE,
306       constants.RAPI_USERS_FILE,
307       ])
308     files_opt = set([
309       constants.RAPI_USERS_FILE,
310       hv_xen.XL_CONFIG_FILE,
311       constants.VNC_PASSWORD_FILE,
312       ])
313     files_mc = set([
314       constants.CLUSTER_CONF_FILE,
315       ])
316     files_vm = set([
317       hv_xen.XEND_CONFIG_FILE,
318       hv_xen.XL_CONFIG_FILE,
319       constants.VNC_PASSWORD_FILE,
320       ])
321     nvinfo = {
322       master_name: rpc.RpcResult(data=(True, {
323         constants.NV_FILELIST: {
324           constants.CLUSTER_CONF_FILE: "82314f897f38b35f9dab2f7c6b1593e0",
325           constants.RAPI_CERT_FILE: "babbce8f387bc082228e544a2146fee4",
326           constants.CLUSTER_DOMAIN_SECRET_FILE: "cds-47b5b3f19202936bb4",
327           hv_xen.XEND_CONFIG_FILE: "b4a8a824ab3cac3d88839a9adeadf310",
328           hv_xen.XL_CONFIG_FILE: "77935cee92afd26d162f9e525e3d49b9"
329         }})),
330       "node2.example.com": rpc.RpcResult(data=(True, {
331         constants.NV_FILELIST: {
332           constants.RAPI_CERT_FILE: "97f0356500e866387f4b84233848cc4a",
333           hv_xen.XEND_CONFIG_FILE: "b4a8a824ab3cac3d88839a9adeadf310",
334           }
335         })),
336       "node3.example.com": rpc.RpcResult(data=(True, {
337         constants.NV_FILELIST: {
338           constants.RAPI_CERT_FILE: "97f0356500e866387f4b84233848cc4a",
339           constants.CLUSTER_DOMAIN_SECRET_FILE: "cds-47b5b3f19202936bb4",
340           }
341         })),
342       "node4.example.com": rpc.RpcResult(data=(True, {
343         constants.NV_FILELIST: {
344           constants.RAPI_CERT_FILE: "97f0356500e866387f4b84233848cc4a",
345           constants.CLUSTER_CONF_FILE: "conf-a6d4b13e407867f7a7b4f0f232a8f527",
346           constants.CLUSTER_DOMAIN_SECRET_FILE: "cds-47b5b3f19202936bb4",
347           constants.RAPI_USERS_FILE: "rapiusers-ea3271e8d810ef3",
348           hv_xen.XL_CONFIG_FILE: "77935cee92afd26d162f9e525e3d49b9"
349           }
350         })),
351       "nodata.example.com": rpc.RpcResult(data=(True, {})),
352       "offline.example.com": rpc.RpcResult(offline=True),
353       }
354     assert set(nvinfo.keys()) == set(map(operator.attrgetter("name"), nodeinfo))
355
356     self._VerifyFiles(compat.partial(self._FakeErrorIf, errors), nodeinfo,
357                       master_name, nvinfo,
358                       (files_all, files_opt, files_mc, files_vm))
359     self.assertEqual(sorted(errors), sorted([
360       (None, ("File %s found with 2 different checksums (variant 1 on"
361               " node2.example.com, node3.example.com, node4.example.com;"
362               " variant 2 on master.example.com)" % constants.RAPI_CERT_FILE)),
363       (None, ("File %s is missing from node(s) node2.example.com" %
364               constants.CLUSTER_DOMAIN_SECRET_FILE)),
365       (None, ("File %s should not exist on node(s) node4.example.com" %
366               constants.CLUSTER_CONF_FILE)),
367       (None, ("File %s is missing from node(s) node4.example.com" %
368               hv_xen.XEND_CONFIG_FILE)),
369       (None, ("File %s is missing from node(s) node3.example.com" %
370               constants.CLUSTER_CONF_FILE)),
371       (None, ("File %s found with 2 different checksums (variant 1 on"
372               " master.example.com; variant 2 on node4.example.com)" %
373               constants.CLUSTER_CONF_FILE)),
374       (None, ("File %s is optional, but it must exist on all or no nodes (not"
375               " found on master.example.com, node2.example.com,"
376               " node3.example.com)" % constants.RAPI_USERS_FILE)),
377       (None, ("File %s is optional, but it must exist on all or no nodes (not"
378               " found on node2.example.com)" % hv_xen.XL_CONFIG_FILE)),
379       ("nodata.example.com", "Node did not return file checksum data"),
380       ]))
381
382
383 if __name__ == "__main__":
384   testutils.GanetiTestProgram()