be28eb27d13a7c23f40fcdbc3ac68ce944d5669d
[ganeti-github.git] / test / py / ganeti.client.gnt_cluster_unittest.py
1 #!/usr/bin/python
2 #
3
4 # Copyright (C) 2011 Google Inc.
5 # All rights reserved.
6 #
7 # Redistribution and use in source and binary forms, with or without
8 # modification, are permitted provided that the following conditions are
9 # met:
10 #
11 # 1. Redistributions of source code must retain the above copyright notice,
12 # this list of conditions and the following disclaimer.
13 #
14 # 2. Redistributions in binary form must reproduce the above copyright
15 # notice, this list of conditions and the following disclaimer in the
16 # documentation and/or other materials provided with the distribution.
17 #
18 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
19 # IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
20 # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
21 # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
22 # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
23 # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
24 # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
25 # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
26 # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
27 # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28 # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
30
31 """Script for testing ganeti.client.gnt_cluster"""
32
33 import unittest
34 import optparse
35 import os
36 import shutil
37 import tempfile
38
39 from ganeti import errors
40 from ganeti.client import gnt_cluster
41 from ganeti import utils
42 from ganeti import compat
43 from ganeti import constants
44 from ganeti import ssh
45 from ganeti import cli
46
47 import mock
48 import testutils
49
50
51 class TestEpoUtilities(unittest.TestCase):
52 def setUp(self):
53 self.nodes2ip = dict(("node%s" % i, "192.0.2.%s" % i) for i in range(1, 10))
54 self.nodes = set(self.nodes2ip.keys())
55 self.ips2node = dict((v, k) for (k, v) in self.nodes2ip.items())
56
57 def _FakeAction(*args):
58 return True
59
60 def _FakePing(ip, port, live_port_needed=False):
61 self.assert_(live_port_needed)
62 self.assertEqual(port, 0)
63 return True
64
65 def _FakeSleep(secs):
66 self.assert_(secs >= 0 and secs <= 5)
67 return
68
69 def _NoopFeedback(self, text):
70 return
71
72 def testPingFnRemoveHostsUp(self):
73 seen = set()
74 def _FakeSeenPing(ip, *args, **kwargs):
75 node = self.ips2node[ip]
76 self.assertFalse(node in seen)
77 seen.add(node)
78 return True
79
80 helper = gnt_cluster._RunWhenNodesReachableHelper(self.nodes,
81 self._FakeAction,
82 self.nodes2ip, 0,
83 self._NoopFeedback,
84 _ping_fn=_FakeSeenPing,
85 _sleep_fn=self._FakeSleep)
86
87 nodes_len = len(self.nodes)
88 for (num, _) in enumerate(self.nodes):
89 helper.Wait(5)
90 if num < nodes_len - 1:
91 self.assertRaises(utils.RetryAgain, helper)
92 else:
93 helper()
94
95 self.assertEqual(seen, self.nodes)
96 self.assertFalse(helper.down)
97 self.assertEqual(helper.up, self.nodes)
98
99 def testActionReturnFalseSetsHelperFalse(self):
100 called = False
101 def _FalseAction(*args):
102 return called
103
104 helper = gnt_cluster._RunWhenNodesReachableHelper(self.nodes, _FalseAction,
105 self.nodes2ip, 0,
106 self._NoopFeedback,
107 _ping_fn=self._FakePing,
108 _sleep_fn=self._FakeSleep)
109 for _ in self.nodes:
110 try:
111 helper()
112 except utils.RetryAgain:
113 called = True
114
115 self.assertFalse(helper.success)
116
117 def testMaybeInstanceStartup(self):
118 instances_arg = []
119 def _FakeInstanceStart(opts, instances, start):
120 instances_arg.append(set(instances))
121 return None
122
123 inst_map = {
124 "inst1": set(["node1", "node2"]),
125 "inst2": set(["node1", "node3"]),
126 "inst3": set(["node2", "node1"]),
127 "inst4": set(["node2", "node1", "node3"]),
128 "inst5": set(["node4"]),
129 }
130
131 fn = _FakeInstanceStart
132 self.assert_(gnt_cluster._MaybeInstanceStartup(None, inst_map, set(),
133 _instance_start_fn=fn))
134 self.assertFalse(instances_arg)
135 result = gnt_cluster._MaybeInstanceStartup(None, inst_map, set(["node1"]),
136 _instance_start_fn=fn)
137 self.assert_(result)
138 self.assertFalse(instances_arg)
139 result = gnt_cluster._MaybeInstanceStartup(None, inst_map,
140 set(["node1", "node3"]),
141 _instance_start_fn=fn)
142 self.assert_(result is None)
143 self.assertEqual(instances_arg.pop(0), set(["inst2"]))
144 self.assertFalse("inst2" in inst_map)
145 result = gnt_cluster._MaybeInstanceStartup(None, inst_map,
146 set(["node1", "node3"]),
147 _instance_start_fn=fn)
148 self.assert_(result)
149 self.assertFalse(instances_arg)
150 result = gnt_cluster._MaybeInstanceStartup(None, inst_map,
151 set(["node1", "node3", "node2"]),
152 _instance_start_fn=fn)
153 self.assertEqual(instances_arg.pop(0), set(["inst1", "inst3", "inst4"]))
154 self.assert_(result is None)
155 result = gnt_cluster._MaybeInstanceStartup(None, inst_map,
156 set(["node1", "node3", "node2",
157 "node4"]),
158 _instance_start_fn=fn)
159 self.assert_(result is None)
160 self.assertEqual(instances_arg.pop(0), set(["inst5"]))
161 self.assertFalse(inst_map)
162
163
164 class _ClientForEpo:
165 def __init__(self, groups, nodes):
166 self._groups = groups
167 self._nodes = nodes
168
169 def QueryGroups(self, names, fields, use_locking):
170 assert not use_locking
171 assert fields == ["node_list"]
172 return self._groups
173
174 def QueryNodes(self, names, fields, use_locking):
175 assert not use_locking
176 assert fields == ["name", "master", "pinst_list", "sinst_list", "powered",
177 "offline"]
178 return self._nodes
179
180
181 class TestEpo(unittest.TestCase):
182 _ON_EXITCODE = 253
183 _OFF_EXITCODE = 254
184
185 def _ConfirmForce(self, *args):
186 self.fail("Shouldn't need confirmation")
187
188 def _Confirm(self, exp_names, result, names, ltype, text):
189 self.assertEqual(names, exp_names)
190 self.assertFalse(result is NotImplemented)
191 return result
192
193 def _Off(self, exp_node_list, opts, node_list, inst_map):
194 self.assertEqual(node_list, exp_node_list)
195 self.assertFalse(inst_map)
196 return self._OFF_EXITCODE
197
198 def _Test(self, *args, **kwargs):
199 defaults = dict(qcl=NotImplemented, _on_fn=NotImplemented,
200 _off_fn=NotImplemented,
201 _stdout_fn=lambda *args: None,
202 _stderr_fn=lambda *args: None)
203 defaults.update(kwargs)
204 return gnt_cluster.Epo(*args, **defaults)
205
206 def testShowAllWithGroups(self):
207 opts = optparse.Values(dict(groups=True, show_all=True))
208 result = self._Test(opts, NotImplemented)
209 self.assertEqual(result, constants.EXIT_FAILURE)
210
211 def testShowAllWithArgs(self):
212 opts = optparse.Values(dict(groups=False, show_all=True))
213 result = self._Test(opts, ["a", "b", "c"])
214 self.assertEqual(result, constants.EXIT_FAILURE)
215
216 def testNoArgumentsNoParameters(self):
217 for (force, confirm_result) in [(True, NotImplemented), (False, False),
218 (False, True)]:
219 opts = optparse.Values(dict(groups=False, show_all=False, force=force,
220 on=False))
221 client = _ClientForEpo(NotImplemented, [
222 ("node1.example.com", False, [], [], True, False),
223 ])
224
225 if force:
226 confirm_fn = self._ConfirmForce
227 else:
228 confirm_fn = compat.partial(self._Confirm, ["node1.example.com"],
229 confirm_result)
230
231 off_fn = compat.partial(self._Off, ["node1.example.com"])
232
233 result = self._Test(opts, [], qcl=client, _off_fn=off_fn,
234 _confirm_fn=confirm_fn)
235 if force or confirm_result:
236 self.assertEqual(result, self._OFF_EXITCODE)
237 else:
238 self.assertEqual(result, constants.EXIT_FAILURE)
239
240 def testPowerOn(self):
241 for master in [False, True]:
242 opts = optparse.Values(dict(groups=False, show_all=True,
243 force=True, on=True))
244 client = _ClientForEpo(NotImplemented, [
245 ("node1.example.com", False, [], [], True, False),
246 ("node2.example.com", False, [], [], False, False),
247 ("node3.example.com", False, [], [], True, True),
248 ("node4.example.com", False, [], [], None, True),
249 ("node5.example.com", master, [], [], False, False),
250 ])
251
252 def _On(_, all_nodes, node_list, inst_map):
253 self.assertEqual(all_nodes,
254 ["node%s.example.com" % i for i in range(1, 6)])
255 if master:
256 self.assertEqual(node_list, ["node2.example.com"])
257 else:
258 self.assertEqual(node_list, ["node2.example.com",
259 "node5.example.com"])
260 self.assertFalse(inst_map)
261 return self._ON_EXITCODE
262
263 result = self._Test(opts, [], qcl=client, _on_fn=_On,
264 _confirm_fn=self._ConfirmForce)
265 self.assertEqual(result, self._ON_EXITCODE)
266
267 def testMasterWithoutShowAll(self):
268 opts = optparse.Values(dict(groups=False, show_all=False,
269 force=True, on=False))
270 client = _ClientForEpo(NotImplemented, [
271 ("node1.example.com", True, [], [], True, False),
272 ])
273 result = self._Test(opts, [], qcl=client, _confirm_fn=self._ConfirmForce)
274 self.assertEqual(result, constants.EXIT_FAILURE)
275
276
277 class DrbdHelperTestCase(unittest.TestCase):
278
279 def setUp(self):
280 unittest.TestCase.setUp(self)
281 self.enabled_disk_templates = []
282
283 def enableDrbd(self):
284 self.enabled_disk_templates = [constants.DT_DRBD8]
285
286 def disableDrbd(self):
287 self.enabled_disk_templates = [constants.DT_DISKLESS]
288
289
290 class InitDrbdHelper(DrbdHelperTestCase):
291
292 def testNoDrbdNoHelper(self):
293 opts = mock.Mock()
294 opts.drbd_helper = None
295 self.disableDrbd()
296 helper = gnt_cluster._InitDrbdHelper(opts, self.enabled_disk_templates)
297 self.assertEquals(None, helper)
298
299 def testNoDrbdHelper(self):
300 opts = mock.Mock()
301 self.disableDrbd()
302 opts.drbd_helper = "/bin/true"
303 helper = gnt_cluster._InitDrbdHelper(opts, self.enabled_disk_templates)
304 self.assertEquals(opts.drbd_helper, helper)
305
306 def testDrbdHelperNone(self):
307 opts = mock.Mock()
308 self.enableDrbd()
309 opts.drbd_helper = None
310 helper = gnt_cluster._InitDrbdHelper(opts, self.enabled_disk_templates)
311 self.assertEquals(constants.DEFAULT_DRBD_HELPER, helper)
312
313 def testDrbdHelperEmpty(self):
314 opts = mock.Mock()
315 self.enableDrbd()
316 opts.drbd_helper = ''
317 self.assertRaises(errors.OpPrereqError, gnt_cluster._InitDrbdHelper, opts,
318 self.enabled_disk_templates)
319
320 def testDrbdHelper(self):
321 opts = mock.Mock()
322 self.enableDrbd()
323 opts.drbd_helper = "/bin/true"
324 helper = gnt_cluster._InitDrbdHelper(opts, self.enabled_disk_templates)
325 self.assertEquals(opts.drbd_helper, helper)
326
327
328 class GetDrbdHelper(DrbdHelperTestCase):
329
330 def testNoDrbdNoHelper(self):
331 opts = mock.Mock()
332 self.disableDrbd()
333 opts.drbd_helper = None
334 helper = gnt_cluster._GetDrbdHelper(opts, self.enabled_disk_templates)
335 self.assertEquals(None, helper)
336
337 def testNoTemplateInfoNoHelper(self):
338 opts = mock.Mock()
339 opts.drbd_helper = None
340 helper = gnt_cluster._GetDrbdHelper(opts, None)
341 self.assertEquals(None, helper)
342
343 def testNoTemplateInfoHelper(self):
344 opts = mock.Mock()
345 opts.drbd_helper = "/bin/true"
346 helper = gnt_cluster._GetDrbdHelper(opts, None)
347 self.assertEquals(opts.drbd_helper, helper)
348
349 def testNoDrbdHelper(self):
350 opts = mock.Mock()
351 self.disableDrbd()
352 opts.drbd_helper = "/bin/true"
353 helper = gnt_cluster._GetDrbdHelper(opts, None)
354 self.assertEquals(opts.drbd_helper, helper)
355
356 def testDrbdNoHelper(self):
357 opts = mock.Mock()
358 self.enableDrbd()
359 opts.drbd_helper = None
360 helper = gnt_cluster._GetDrbdHelper(opts, self.enabled_disk_templates)
361 self.assertEquals(None, helper)
362
363 def testDrbdHelper(self):
364 opts = mock.Mock()
365 self.enableDrbd()
366 opts.drbd_helper = "/bin/true"
367 helper = gnt_cluster._GetDrbdHelper(opts, self.enabled_disk_templates)
368 self.assertEquals(opts.drbd_helper, helper)
369
370
371 class TestBuildGanetiPubKeys(testutils.GanetiTestCase):
372
373 _SOME_KEY_DICT = {"rsa": "key_rsa",
374 "dsa": "key_dsa"}
375 _MASTER_NODE_NAME = "master_node"
376 _MASTER_NODE_UUID = "master_uuid"
377 _NUM_NODES = 2 # excluding master node
378 _ONLINE_NODE_NAMES = ["node%s_name" % i for i in range(_NUM_NODES)]
379 _ONLINE_NODE_UUIDS = ["node%s_uuid" % i for i in range(_NUM_NODES)]
380 _CLUSTER_NAME = "cluster_name"
381 _PRIV_KEY = "master_private_key"
382 _PUB_KEY = "master_public_key"
383 _AUTH_KEYS = "a\nb\nc"
384
385 def _setUpFakeKeys(self):
386 os.makedirs(os.path.join(self.tmpdir, ".ssh"))
387
388 for key_type in ["rsa", "dsa"]:
389 self.priv_filename = os.path.join(self.tmpdir, ".ssh", "id_%s" % key_type)
390 utils.WriteFile(self.priv_filename, data=self._PRIV_KEY)
391
392 self.pub_filename = os.path.join(
393 self.tmpdir, ".ssh", "id_%s.pub" % key_type)
394 utils.WriteFile(self.pub_filename, data=self._PUB_KEY)
395
396 self.auth_filename = os.path.join(self.tmpdir, ".ssh", "authorized_keys")
397 utils.WriteFile(self.auth_filename, data=self._AUTH_KEYS)
398
399 def setUp(self):
400 testutils.GanetiTestCase.setUp(self)
401 self.tmpdir = tempfile.mkdtemp()
402 self.pub_key_filename = os.path.join(self.tmpdir, "ganeti_test_pub_keys")
403 self._setUpFakeKeys()
404
405 self._ssh_read_remote_ssh_pub_keys_patcher = testutils \
406 .patch_object(ssh, "ReadRemoteSshPubKeys")
407 self._ssh_read_remote_ssh_pub_keys_mock = \
408 self._ssh_read_remote_ssh_pub_keys_patcher.start()
409 self._ssh_read_remote_ssh_pub_keys_mock.return_value = self._SOME_KEY_DICT
410
411 self.mock_cl = mock.Mock()
412 self.mock_cl.QueryConfigValues = mock.Mock()
413 self.mock_cl.QueryConfigValues.return_value = \
414 (self._CLUSTER_NAME, self._MASTER_NODE_NAME)
415
416 self._get_online_nodes_mock = mock.Mock()
417 self._get_online_nodes_mock.return_value = \
418 self._ONLINE_NODE_NAMES
419
420 self._get_nodes_ssh_ports_mock = mock.Mock()
421 self._get_nodes_ssh_ports_mock.return_value = \
422 [22 for i in range(self._NUM_NODES + 1)]
423
424 self._get_node_uuids_mock = mock.Mock()
425 self._get_node_uuids_mock.return_value = \
426 self._ONLINE_NODE_UUIDS + [self._MASTER_NODE_UUID]
427
428 self._options = mock.Mock()
429 self._options.ssh_key_check = False
430
431 def _GetTempHomedir(self, _):
432 return self.tmpdir
433
434 def tearDown(self):
435 super(testutils.GanetiTestCase, self).tearDown()
436 shutil.rmtree(self.tmpdir)
437 self._ssh_read_remote_ssh_pub_keys_patcher.stop()
438
439 def testNewPubKeyFile(self):
440 gnt_cluster._BuildGanetiPubKeys(
441 self._options,
442 pub_key_file=self.pub_key_filename,
443 cl=self.mock_cl,
444 get_online_nodes_fn=self._get_online_nodes_mock,
445 get_nodes_ssh_ports_fn=self._get_nodes_ssh_ports_mock,
446 get_node_uuids_fn=self._get_node_uuids_mock,
447 homedir_fn=self._GetTempHomedir)
448 key_file_result = utils.ReadFile(self.pub_key_filename)
449 for node_uuid in self._ONLINE_NODE_UUIDS + [self._MASTER_NODE_UUID]:
450 self.assertTrue(node_uuid in key_file_result)
451 self.assertTrue(self._PUB_KEY in key_file_result)
452
453 def testOverridePubKeyFile(self):
454 fd = open(self.pub_key_filename, "w")
455 fd.write("Pink Bunny")
456 fd.close()
457 gnt_cluster._BuildGanetiPubKeys(
458 self._options,
459 pub_key_file=self.pub_key_filename,
460 cl=self.mock_cl,
461 get_online_nodes_fn=self._get_online_nodes_mock,
462 get_nodes_ssh_ports_fn=self._get_nodes_ssh_ports_mock,
463 get_node_uuids_fn=self._get_node_uuids_mock,
464 homedir_fn=self._GetTempHomedir)
465 self.assertFalse("Pink Bunny" in self.pub_key_filename)
466
467
468 if __name__ == "__main__":
469 testutils.GanetiTestProgram()