Bulk-remove SSH keys of potential master candidates
[ganeti-github.git] / test / py / ganeti.backend_unittest.py
1 #!/usr/bin/python
2 #
3
4 # Copyright (C) 2010, 2013 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.backend"""
32
33 import collections
34 import copy
35 import mock
36 import os
37 import shutil
38 import tempfile
39 import testutils
40 import testutils_ssh
41 import unittest
42
43 from ganeti import backend
44 from ganeti import constants
45 from ganeti import errors
46 from ganeti import hypervisor
47 from ganeti import netutils
48 from ganeti import objects
49 from ganeti import serializer
50 from ganeti import ssh
51 from ganeti import utils
52 from testutils.config_mock import ConfigMock
53
54
55 class TestX509Certificates(unittest.TestCase):
56 def setUp(self):
57 self.tmpdir = tempfile.mkdtemp()
58
59 def tearDown(self):
60 shutil.rmtree(self.tmpdir)
61
62 def test(self):
63 (name, cert_pem) = backend.CreateX509Certificate(300, cryptodir=self.tmpdir)
64
65 self.assertEqual(utils.ReadFile(os.path.join(self.tmpdir, name,
66 backend._X509_CERT_FILE)),
67 cert_pem)
68 self.assert_(0 < os.path.getsize(os.path.join(self.tmpdir, name,
69 backend._X509_KEY_FILE)))
70
71 (name2, cert_pem2) = \
72 backend.CreateX509Certificate(300, cryptodir=self.tmpdir)
73
74 backend.RemoveX509Certificate(name, cryptodir=self.tmpdir)
75 backend.RemoveX509Certificate(name2, cryptodir=self.tmpdir)
76
77 self.assertEqual(utils.ListVisibleFiles(self.tmpdir), [])
78
79 def testNonEmpty(self):
80 (name, _) = backend.CreateX509Certificate(300, cryptodir=self.tmpdir)
81
82 utils.WriteFile(utils.PathJoin(self.tmpdir, name, "hello-world"),
83 data="Hello World")
84
85 self.assertRaises(backend.RPCFail, backend.RemoveX509Certificate,
86 name, cryptodir=self.tmpdir)
87
88 self.assertEqual(utils.ListVisibleFiles(self.tmpdir), [name])
89
90
91 class TestGetCryptoTokens(testutils.GanetiTestCase):
92
93 def setUp(self):
94 self._get_digest_fn_orig = utils.GetCertificateDigest
95 self._create_digest_fn_orig = utils.GenerateNewSslCert
96 self._ssl_digest = "12345"
97 utils.GetCertificateDigest = mock.Mock(
98 return_value=self._ssl_digest)
99 utils.GenerateNewSslCert = mock.Mock()
100
101 def tearDown(self):
102 utils.GetCertificateDigest = self._get_digest_fn_orig
103 utils.GenerateNewSslCert = self._create_digest_fn_orig
104
105 def testGetSslToken(self):
106 result = backend.GetCryptoTokens(
107 [(constants.CRYPTO_TYPE_SSL_DIGEST, constants.CRYPTO_ACTION_GET, None)])
108 self.assertTrue((constants.CRYPTO_TYPE_SSL_DIGEST, self._ssl_digest)
109 in result)
110
111 def testUnknownTokenType(self):
112 self.assertRaises(errors.ProgrammerError,
113 backend.GetCryptoTokens,
114 [("pink_bunny", constants.CRYPTO_ACTION_GET, None)])
115
116 def testUnknownAction(self):
117 self.assertRaises(errors.ProgrammerError,
118 backend.GetCryptoTokens,
119 [(constants.CRYPTO_TYPE_SSL_DIGEST, "illuminate", None)])
120
121
122 class TestNodeVerify(testutils.GanetiTestCase):
123
124 def setUp(self):
125 testutils.GanetiTestCase.setUp(self)
126 self._mock_hv = None
127
128 def _GetHypervisor(self, hv_name):
129 self._mock_hv = hypervisor.GetHypervisor(hv_name)
130 self._mock_hv.ValidateParameters = mock.Mock()
131 self._mock_hv.Verify = mock.Mock()
132 return self._mock_hv
133
134 def testMasterIPLocalhost(self):
135 # this a real functional test, but requires localhost to be reachable
136 local_data = (netutils.Hostname.GetSysName(),
137 constants.IP4_ADDRESS_LOCALHOST)
138 result = backend.VerifyNode({constants.NV_MASTERIP: local_data},
139 None, {}, {}, {})
140 self.failUnless(constants.NV_MASTERIP in result,
141 "Master IP data not returned")
142 self.failUnless(result[constants.NV_MASTERIP], "Cannot reach localhost")
143
144 def testMasterIPUnreachable(self):
145 # Network 192.0.2.0/24 is reserved for test/documentation as per
146 # RFC 5737
147 bad_data = ("master.example.com", "192.0.2.1")
148 # we just test that whatever TcpPing returns, VerifyNode returns too
149 netutils.TcpPing = lambda a, b, source=None: False
150 result = backend.VerifyNode({constants.NV_MASTERIP: bad_data},
151 None, {}, {}, {})
152 self.failUnless(constants.NV_MASTERIP in result,
153 "Master IP data not returned")
154 self.failIf(result[constants.NV_MASTERIP],
155 "Result from netutils.TcpPing corrupted")
156
157 def testVerifyHvparams(self):
158 test_hvparams = {constants.HV_XEN_CMD: constants.XEN_CMD_XL}
159 test_what = {constants.NV_HVPARAMS: \
160 [("mynode", constants.HT_XEN_PVM, test_hvparams)]}
161 result = {}
162 backend._VerifyHvparams(test_what, True, result,
163 get_hv_fn=self._GetHypervisor)
164 self._mock_hv.ValidateParameters.assert_called_with(test_hvparams)
165
166 def testVerifyHypervisors(self):
167 hvname = constants.HT_XEN_PVM
168 hvparams = {constants.HV_XEN_CMD: constants.XEN_CMD_XL}
169 all_hvparams = {hvname: hvparams}
170 test_what = {constants.NV_HYPERVISOR: [hvname]}
171 result = {}
172 backend._VerifyHypervisors(
173 test_what, True, result, all_hvparams=all_hvparams,
174 get_hv_fn=self._GetHypervisor)
175 self._mock_hv.Verify.assert_called_with(hvparams=hvparams)
176
177 @testutils.patch_object(utils, "VerifyCertificate")
178 def testVerifyClientCertificateSuccess(self, verif_cert):
179 # mock the underlying x509 verification because the test cert is expired
180 verif_cert.return_value = (None, None)
181 cert_file = testutils.TestDataFilename("cert2.pem")
182 (errcode, digest) = backend._VerifyClientCertificate(cert_file=cert_file)
183 self.assertEqual(constants.CV_WARNING, errcode)
184 self.assertTrue(isinstance(digest, str))
185
186 @testutils.patch_object(utils, "VerifyCertificate")
187 def testVerifyClientCertificateFailed(self, verif_cert):
188 expected_errcode = 666
189 verif_cert.return_value = (expected_errcode,
190 "The devil created this certificate.")
191 cert_file = testutils.TestDataFilename("cert2.pem")
192 (errcode, digest) = backend._VerifyClientCertificate(cert_file=cert_file)
193 self.assertEqual(expected_errcode, errcode)
194
195 def testVerifyClientCertificateNoCert(self):
196 cert_file = testutils.TestDataFilename("cert-that-does-not-exist.pem")
197 (errcode, digest) = backend._VerifyClientCertificate(cert_file=cert_file)
198 self.assertEqual(constants.CV_ERROR, errcode)
199
200
201 def _DefRestrictedCmdOwner():
202 return (os.getuid(), os.getgid())
203
204
205 class TestVerifyRestrictedCmdName(unittest.TestCase):
206 def testAcceptableName(self):
207 for i in ["foo", "bar", "z1", "000first", "hello-world"]:
208 for fn in [lambda s: s, lambda s: s.upper(), lambda s: s.title()]:
209 (status, msg) = backend._VerifyRestrictedCmdName(fn(i))
210 self.assertTrue(status)
211 self.assertTrue(msg is None)
212
213 def testEmptyAndSpace(self):
214 for i in ["", " ", "\t", "\n"]:
215 (status, msg) = backend._VerifyRestrictedCmdName(i)
216 self.assertFalse(status)
217 self.assertEqual(msg, "Missing command name")
218
219 def testNameWithSlashes(self):
220 for i in ["/", "./foo", "../moo", "some/name"]:
221 (status, msg) = backend._VerifyRestrictedCmdName(i)
222 self.assertFalse(status)
223 self.assertEqual(msg, "Invalid command name")
224
225 def testForbiddenCharacters(self):
226 for i in ["#", ".", "..", "bash -c ls", "'"]:
227 (status, msg) = backend._VerifyRestrictedCmdName(i)
228 self.assertFalse(status)
229 self.assertEqual(msg, "Command name contains forbidden characters")
230
231
232 class TestVerifyRestrictedCmdDirectory(unittest.TestCase):
233 def setUp(self):
234 self.tmpdir = tempfile.mkdtemp()
235
236 def tearDown(self):
237 shutil.rmtree(self.tmpdir)
238
239 def testCanNotStat(self):
240 tmpname = utils.PathJoin(self.tmpdir, "foobar")
241 self.assertFalse(os.path.exists(tmpname))
242 (status, msg) = \
243 backend._VerifyRestrictedCmdDirectory(tmpname, _owner=NotImplemented)
244 self.assertFalse(status)
245 self.assertTrue(msg.startswith("Can't stat(2) '"))
246
247 def testTooPermissive(self):
248 tmpname = utils.PathJoin(self.tmpdir, "foobar")
249 os.mkdir(tmpname)
250
251 for mode in [0777, 0706, 0760, 0722]:
252 os.chmod(tmpname, mode)
253 self.assertTrue(os.path.isdir(tmpname))
254 (status, msg) = \
255 backend._VerifyRestrictedCmdDirectory(tmpname, _owner=NotImplemented)
256 self.assertFalse(status)
257 self.assertTrue(msg.startswith("Permissions on '"))
258
259 def testNoDirectory(self):
260 tmpname = utils.PathJoin(self.tmpdir, "foobar")
261 utils.WriteFile(tmpname, data="empty\n")
262 self.assertTrue(os.path.isfile(tmpname))
263 (status, msg) = \
264 backend._VerifyRestrictedCmdDirectory(tmpname,
265 _owner=_DefRestrictedCmdOwner())
266 self.assertFalse(status)
267 self.assertTrue(msg.endswith("is not a directory"))
268
269 def testNormal(self):
270 tmpname = utils.PathJoin(self.tmpdir, "foobar")
271 os.mkdir(tmpname)
272 os.chmod(tmpname, 0755)
273 self.assertTrue(os.path.isdir(tmpname))
274 (status, msg) = \
275 backend._VerifyRestrictedCmdDirectory(tmpname,
276 _owner=_DefRestrictedCmdOwner())
277 self.assertTrue(status)
278 self.assertTrue(msg is None)
279
280
281 class TestVerifyRestrictedCmd(unittest.TestCase):
282 def setUp(self):
283 self.tmpdir = tempfile.mkdtemp()
284
285 def tearDown(self):
286 shutil.rmtree(self.tmpdir)
287
288 def testCanNotStat(self):
289 tmpname = utils.PathJoin(self.tmpdir, "helloworld")
290 self.assertFalse(os.path.exists(tmpname))
291 (status, msg) = \
292 backend._VerifyRestrictedCmd(self.tmpdir, "helloworld",
293 _owner=NotImplemented)
294 self.assertFalse(status)
295 self.assertTrue(msg.startswith("Can't stat(2) '"))
296
297 def testNotExecutable(self):
298 tmpname = utils.PathJoin(self.tmpdir, "cmdname")
299 utils.WriteFile(tmpname, data="empty\n")
300 (status, msg) = \
301 backend._VerifyRestrictedCmd(self.tmpdir, "cmdname",
302 _owner=_DefRestrictedCmdOwner())
303 self.assertFalse(status)
304 self.assertTrue(msg.startswith("access(2) thinks '"))
305
306 def testExecutable(self):
307 tmpname = utils.PathJoin(self.tmpdir, "cmdname")
308 utils.WriteFile(tmpname, data="empty\n", mode=0700)
309 (status, executable) = \
310 backend._VerifyRestrictedCmd(self.tmpdir, "cmdname",
311 _owner=_DefRestrictedCmdOwner())
312 self.assertTrue(status)
313 self.assertEqual(executable, tmpname)
314
315
316 class TestPrepareRestrictedCmd(unittest.TestCase):
317 _TEST_PATH = "/tmp/some/test/path"
318
319 def testDirFails(self):
320 def fn(path):
321 self.assertEqual(path, self._TEST_PATH)
322 return (False, "test error 31420")
323
324 (status, msg) = \
325 backend._PrepareRestrictedCmd(self._TEST_PATH, "cmd21152",
326 _verify_dir=fn,
327 _verify_name=NotImplemented,
328 _verify_cmd=NotImplemented)
329 self.assertFalse(status)
330 self.assertEqual(msg, "test error 31420")
331
332 def testNameFails(self):
333 def fn(cmd):
334 self.assertEqual(cmd, "cmd4617")
335 return (False, "test error 591")
336
337 (status, msg) = \
338 backend._PrepareRestrictedCmd(self._TEST_PATH, "cmd4617",
339 _verify_dir=lambda _: (True, None),
340 _verify_name=fn,
341 _verify_cmd=NotImplemented)
342 self.assertFalse(status)
343 self.assertEqual(msg, "test error 591")
344
345 def testCommandFails(self):
346 def fn(path, cmd):
347 self.assertEqual(path, self._TEST_PATH)
348 self.assertEqual(cmd, "cmd17577")
349 return (False, "test error 25524")
350
351 (status, msg) = \
352 backend._PrepareRestrictedCmd(self._TEST_PATH, "cmd17577",
353 _verify_dir=lambda _: (True, None),
354 _verify_name=lambda _: (True, None),
355 _verify_cmd=fn)
356 self.assertFalse(status)
357 self.assertEqual(msg, "test error 25524")
358
359 def testSuccess(self):
360 def fn(path, cmd):
361 return (True, utils.PathJoin(path, cmd))
362
363 (status, executable) = \
364 backend._PrepareRestrictedCmd(self._TEST_PATH, "cmd22633",
365 _verify_dir=lambda _: (True, None),
366 _verify_name=lambda _: (True, None),
367 _verify_cmd=fn)
368 self.assertTrue(status)
369 self.assertEqual(executable, utils.PathJoin(self._TEST_PATH, "cmd22633"))
370
371
372 def _SleepForRestrictedCmd(duration):
373 assert duration > 5
374
375
376 def _GenericRestrictedCmdError(cmd):
377 return "Executing command '%s' failed" % cmd
378
379
380 class TestRunRestrictedCmd(unittest.TestCase):
381 def setUp(self):
382 self.tmpdir = tempfile.mkdtemp()
383
384 def tearDown(self):
385 shutil.rmtree(self.tmpdir)
386
387 def testNonExistantLockDirectory(self):
388 lockfile = utils.PathJoin(self.tmpdir, "does", "not", "exist")
389 sleep_fn = testutils.CallCounter(_SleepForRestrictedCmd)
390 self.assertFalse(os.path.exists(lockfile))
391 self.assertRaises(backend.RPCFail,
392 backend.RunRestrictedCmd, "test",
393 _lock_timeout=NotImplemented,
394 _lock_file=lockfile,
395 _path=NotImplemented,
396 _sleep_fn=sleep_fn,
397 _prepare_fn=NotImplemented,
398 _runcmd_fn=NotImplemented,
399 _enabled=True)
400 self.assertEqual(sleep_fn.Count(), 1)
401
402 @staticmethod
403 def _TryLock(lockfile):
404 sleep_fn = testutils.CallCounter(_SleepForRestrictedCmd)
405
406 result = False
407 try:
408 backend.RunRestrictedCmd("test22717",
409 _lock_timeout=0.1,
410 _lock_file=lockfile,
411 _path=NotImplemented,
412 _sleep_fn=sleep_fn,
413 _prepare_fn=NotImplemented,
414 _runcmd_fn=NotImplemented,
415 _enabled=True)
416 except backend.RPCFail, err:
417 assert str(err) == _GenericRestrictedCmdError("test22717"), \
418 "Did not fail with generic error message"
419 result = True
420
421 assert sleep_fn.Count() == 1
422
423 return result
424
425 def testLockHeldByOtherProcess(self):
426 lockfile = utils.PathJoin(self.tmpdir, "lock")
427
428 lock = utils.FileLock.Open(lockfile)
429 lock.Exclusive(blocking=True, timeout=1.0)
430 try:
431 self.assertTrue(utils.RunInSeparateProcess(self._TryLock, lockfile))
432 finally:
433 lock.Close()
434
435 @staticmethod
436 def _PrepareRaisingException(path, cmd):
437 assert cmd == "test23122"
438 raise Exception("test")
439
440 def testPrepareRaisesException(self):
441 lockfile = utils.PathJoin(self.tmpdir, "lock")
442
443 sleep_fn = testutils.CallCounter(_SleepForRestrictedCmd)
444 prepare_fn = testutils.CallCounter(self._PrepareRaisingException)
445
446 try:
447 backend.RunRestrictedCmd("test23122",
448 _lock_timeout=1.0, _lock_file=lockfile,
449 _path=NotImplemented, _runcmd_fn=NotImplemented,
450 _sleep_fn=sleep_fn, _prepare_fn=prepare_fn,
451 _enabled=True)
452 except backend.RPCFail, err:
453 self.assertEqual(str(err), _GenericRestrictedCmdError("test23122"))
454 else:
455 self.fail("Didn't fail")
456
457 self.assertEqual(sleep_fn.Count(), 1)
458 self.assertEqual(prepare_fn.Count(), 1)
459
460 @staticmethod
461 def _PrepareFails(path, cmd):
462 assert cmd == "test29327"
463 return ("some error message", None)
464
465 def testPrepareFails(self):
466 lockfile = utils.PathJoin(self.tmpdir, "lock")
467
468 sleep_fn = testutils.CallCounter(_SleepForRestrictedCmd)
469 prepare_fn = testutils.CallCounter(self._PrepareFails)
470
471 try:
472 backend.RunRestrictedCmd("test29327",
473 _lock_timeout=1.0, _lock_file=lockfile,
474 _path=NotImplemented, _runcmd_fn=NotImplemented,
475 _sleep_fn=sleep_fn, _prepare_fn=prepare_fn,
476 _enabled=True)
477 except backend.RPCFail, err:
478 self.assertEqual(str(err), _GenericRestrictedCmdError("test29327"))
479 else:
480 self.fail("Didn't fail")
481
482 self.assertEqual(sleep_fn.Count(), 1)
483 self.assertEqual(prepare_fn.Count(), 1)
484
485 @staticmethod
486 def _SuccessfulPrepare(path, cmd):
487 return (True, utils.PathJoin(path, cmd))
488
489 def testRunCmdFails(self):
490 lockfile = utils.PathJoin(self.tmpdir, "lock")
491
492 def fn(args, env=NotImplemented, reset_env=NotImplemented,
493 postfork_fn=NotImplemented):
494 self.assertEqual(args, [utils.PathJoin(self.tmpdir, "test3079")])
495 self.assertEqual(env, {})
496 self.assertTrue(reset_env)
497 self.assertTrue(callable(postfork_fn))
498
499 trylock = utils.FileLock.Open(lockfile)
500 try:
501 # See if lockfile is still held
502 self.assertRaises(EnvironmentError, trylock.Exclusive, blocking=False)
503
504 # Call back to release lock
505 postfork_fn(NotImplemented)
506
507 # See if lockfile can be acquired
508 trylock.Exclusive(blocking=False)
509 finally:
510 trylock.Close()
511
512 # Simulate a failed command
513 return utils.RunResult(constants.EXIT_FAILURE, None,
514 "stdout", "stderr406328567",
515 utils.ShellQuoteArgs(args),
516 NotImplemented, NotImplemented)
517
518 sleep_fn = testutils.CallCounter(_SleepForRestrictedCmd)
519 prepare_fn = testutils.CallCounter(self._SuccessfulPrepare)
520 runcmd_fn = testutils.CallCounter(fn)
521
522 try:
523 backend.RunRestrictedCmd("test3079",
524 _lock_timeout=1.0, _lock_file=lockfile,
525 _path=self.tmpdir, _runcmd_fn=runcmd_fn,
526 _sleep_fn=sleep_fn, _prepare_fn=prepare_fn,
527 _enabled=True)
528 except backend.RPCFail, err:
529 self.assertTrue(str(err).startswith("Restricted command 'test3079'"
530 " failed:"))
531 self.assertTrue("stderr406328567" in str(err),
532 msg="Error did not include output")
533 else:
534 self.fail("Didn't fail")
535
536 self.assertEqual(sleep_fn.Count(), 0)
537 self.assertEqual(prepare_fn.Count(), 1)
538 self.assertEqual(runcmd_fn.Count(), 1)
539
540 def testRunCmdSucceeds(self):
541 lockfile = utils.PathJoin(self.tmpdir, "lock")
542
543 def fn(args, env=NotImplemented, reset_env=NotImplemented,
544 postfork_fn=NotImplemented):
545 self.assertEqual(args, [utils.PathJoin(self.tmpdir, "test5667")])
546 self.assertEqual(env, {})
547 self.assertTrue(reset_env)
548
549 # Call back to release lock
550 postfork_fn(NotImplemented)
551
552 # Simulate a successful command
553 return utils.RunResult(constants.EXIT_SUCCESS, None, "stdout14463", "",
554 utils.ShellQuoteArgs(args),
555 NotImplemented, NotImplemented)
556
557 sleep_fn = testutils.CallCounter(_SleepForRestrictedCmd)
558 prepare_fn = testutils.CallCounter(self._SuccessfulPrepare)
559 runcmd_fn = testutils.CallCounter(fn)
560
561 result = backend.RunRestrictedCmd("test5667",
562 _lock_timeout=1.0, _lock_file=lockfile,
563 _path=self.tmpdir, _runcmd_fn=runcmd_fn,
564 _sleep_fn=sleep_fn,
565 _prepare_fn=prepare_fn,
566 _enabled=True)
567 self.assertEqual(result, "stdout14463")
568
569 self.assertEqual(sleep_fn.Count(), 0)
570 self.assertEqual(prepare_fn.Count(), 1)
571 self.assertEqual(runcmd_fn.Count(), 1)
572
573 def testCommandsDisabled(self):
574 try:
575 backend.RunRestrictedCmd("test",
576 _lock_timeout=NotImplemented,
577 _lock_file=NotImplemented,
578 _path=NotImplemented,
579 _sleep_fn=NotImplemented,
580 _prepare_fn=NotImplemented,
581 _runcmd_fn=NotImplemented,
582 _enabled=False)
583 except backend.RPCFail, err:
584 self.assertEqual(str(err),
585 "Restricted commands disabled at configure time")
586 else:
587 self.fail("Did not raise exception")
588
589
590 class TestSetWatcherPause(unittest.TestCase):
591 def setUp(self):
592 self.tmpdir = tempfile.mkdtemp()
593 self.filename = utils.PathJoin(self.tmpdir, "pause")
594
595 def tearDown(self):
596 shutil.rmtree(self.tmpdir)
597
598 def testUnsetNonExisting(self):
599 self.assertFalse(os.path.exists(self.filename))
600 backend.SetWatcherPause(None, _filename=self.filename)
601 self.assertFalse(os.path.exists(self.filename))
602
603 def testSetNonNumeric(self):
604 for i in ["", [], {}, "Hello World", "0", "1.0"]:
605 self.assertFalse(os.path.exists(self.filename))
606
607 try:
608 backend.SetWatcherPause(i, _filename=self.filename)
609 except backend.RPCFail, err:
610 self.assertEqual(str(err), "Duration must be numeric")
611 else:
612 self.fail("Did not raise exception")
613
614 self.assertFalse(os.path.exists(self.filename))
615
616 def testSet(self):
617 self.assertFalse(os.path.exists(self.filename))
618
619 for i in range(10):
620 backend.SetWatcherPause(i, _filename=self.filename)
621 self.assertEqual(utils.ReadFile(self.filename), "%s\n" % i)
622 self.assertEqual(os.stat(self.filename).st_mode & 0777, 0644)
623
624
625 class TestGetBlockDevSymlinkPath(unittest.TestCase):
626 def setUp(self):
627 self.tmpdir = tempfile.mkdtemp()
628
629 def tearDown(self):
630 shutil.rmtree(self.tmpdir)
631
632 def _Test(self, name, idx):
633 self.assertEqual(backend._GetBlockDevSymlinkPath(name, idx,
634 _dir=self.tmpdir),
635 ("%s/%s%s%s" % (self.tmpdir, name,
636 constants.DISK_SEPARATOR, idx)))
637
638 def test(self):
639 for idx in range(100):
640 self._Test("inst1.example.com", idx)
641
642
643 class TestGetInstanceList(unittest.TestCase):
644
645 def setUp(self):
646 self._test_hv = self._TestHypervisor()
647 self._test_hv.ListInstances = mock.Mock(
648 return_value=["instance1", "instance2", "instance3"] )
649
650 class _TestHypervisor(hypervisor.hv_base.BaseHypervisor):
651 def __init__(self):
652 hypervisor.hv_base.BaseHypervisor.__init__(self)
653
654 def _GetHypervisor(self, name):
655 return self._test_hv
656
657 def testHvparams(self):
658 fake_hvparams = {constants.HV_XEN_CMD: constants.XEN_CMD_XL}
659 hvparams = {constants.HT_FAKE: fake_hvparams}
660 backend.GetInstanceList([constants.HT_FAKE], all_hvparams=hvparams,
661 get_hv_fn=self._GetHypervisor)
662 self._test_hv.ListInstances.assert_called_with(hvparams=fake_hvparams)
663
664
665 class TestInstanceConsoleInfo(unittest.TestCase):
666
667 def setUp(self):
668 self._test_hv_a = self._TestHypervisor()
669 self._test_hv_a.GetInstanceConsole = mock.Mock(
670 return_value = objects.InstanceConsole(instance="inst", kind="aHy")
671 )
672 self._test_hv_b = self._TestHypervisor()
673 self._test_hv_b.GetInstanceConsole = mock.Mock(
674 return_value = objects.InstanceConsole(instance="inst", kind="bHy")
675 )
676
677 class _TestHypervisor(hypervisor.hv_base.BaseHypervisor):
678 def __init__(self):
679 hypervisor.hv_base.BaseHypervisor.__init__(self)
680
681 def _GetHypervisor(self, name):
682 if name == "a":
683 return self._test_hv_a
684 else:
685 return self._test_hv_b
686
687 def testRightHypervisor(self):
688 dictMaker = lambda hyName: {
689 "instance":{"hypervisor":hyName},
690 "node":{},
691 "group":{},
692 "hvParams":{},
693 "beParams":{},
694 }
695
696 call = {
697 'i1':dictMaker("a"),
698 'i2':dictMaker("b"),
699 }
700
701 res = backend.GetInstanceConsoleInfo(call, get_hv_fn=self._GetHypervisor)
702
703 self.assertTrue(res["i1"]["kind"] == "aHy")
704 self.assertTrue(res["i2"]["kind"] == "bHy")
705
706
707 class TestGetHvInfo(unittest.TestCase):
708
709 def setUp(self):
710 self._test_hv = self._TestHypervisor()
711 self._test_hv.GetNodeInfo = mock.Mock()
712
713 class _TestHypervisor(hypervisor.hv_base.BaseHypervisor):
714 def __init__(self):
715 hypervisor.hv_base.BaseHypervisor.__init__(self)
716
717 def _GetHypervisor(self, name):
718 return self._test_hv
719
720 def testGetHvInfoAllNone(self):
721 result = backend._GetHvInfoAll(None)
722 self.assertTrue(result is None)
723
724 def testGetHvInfoAll(self):
725 hvname = constants.HT_XEN_PVM
726 hvparams = {constants.HV_XEN_CMD: constants.XEN_CMD_XL}
727 hv_specs = [(hvname, hvparams)]
728
729 backend._GetHvInfoAll(hv_specs, self._GetHypervisor)
730 self._test_hv.GetNodeInfo.assert_called_with(hvparams=hvparams)
731
732
733 class TestApplyStorageInfoFunction(unittest.TestCase):
734
735 _STORAGE_KEY = "some_key"
736 _SOME_ARGS = ["some_args"]
737
738 def setUp(self):
739 self.mock_storage_fn = mock.Mock()
740
741 def testApplyValidStorageType(self):
742 storage_type = constants.ST_LVM_VG
743 info_fn_orig = backend._STORAGE_TYPE_INFO_FN
744 backend._STORAGE_TYPE_INFO_FN = {
745 storage_type: self.mock_storage_fn
746 }
747
748 backend._ApplyStorageInfoFunction(
749 storage_type, self._STORAGE_KEY, self._SOME_ARGS)
750
751 self.mock_storage_fn.assert_called_with(self._STORAGE_KEY, self._SOME_ARGS)
752 backend._STORAGE_TYPE_INFO_FN = info_fn_orig
753
754 def testApplyInValidStorageType(self):
755 storage_type = "invalid_storage_type"
756 info_fn_orig = backend._STORAGE_TYPE_INFO_FN
757 backend._STORAGE_TYPE_INFO_FN = {}
758
759 self.assertRaises(KeyError, backend._ApplyStorageInfoFunction,
760 storage_type, self._STORAGE_KEY, self._SOME_ARGS)
761 backend._STORAGE_TYPE_INFO_FN = info_fn_orig
762
763 def testApplyNotImplementedStorageType(self):
764 storage_type = "not_implemented_storage_type"
765 info_fn_orig = backend._STORAGE_TYPE_INFO_FN
766 backend._STORAGE_TYPE_INFO_FN = {storage_type: None}
767
768 self.assertRaises(NotImplementedError,
769 backend._ApplyStorageInfoFunction,
770 storage_type, self._STORAGE_KEY, self._SOME_ARGS)
771 backend._STORAGE_TYPE_INFO_FN = info_fn_orig
772
773
774 class TestGetLvmVgSpaceInfo(unittest.TestCase):
775
776 def testValid(self):
777 path = "somepath"
778 excl_stor = True
779 orig_fn = backend._GetVgInfo
780 backend._GetVgInfo = mock.Mock()
781 backend._GetLvmVgSpaceInfo(path, [excl_stor])
782 backend._GetVgInfo.assert_called_with(path, excl_stor)
783 backend._GetVgInfo = orig_fn
784
785 def testNoExclStorageNotBool(self):
786 path = "somepath"
787 excl_stor = "123"
788 self.assertRaises(errors.ProgrammerError, backend._GetLvmVgSpaceInfo,
789 path, [excl_stor])
790
791 def testNoExclStorageNotInList(self):
792 path = "somepath"
793 excl_stor = "123"
794 self.assertRaises(errors.ProgrammerError, backend._GetLvmVgSpaceInfo,
795 path, excl_stor)
796
797 class TestGetLvmPvSpaceInfo(unittest.TestCase):
798
799 def testValid(self):
800 path = "somepath"
801 excl_stor = True
802 orig_fn = backend._GetVgSpindlesInfo
803 backend._GetVgSpindlesInfo = mock.Mock()
804 backend._GetLvmPvSpaceInfo(path, [excl_stor])
805 backend._GetVgSpindlesInfo.assert_called_with(path, excl_stor)
806 backend._GetVgSpindlesInfo = orig_fn
807
808
809 class TestCheckStorageParams(unittest.TestCase):
810
811 def testParamsNone(self):
812 self.assertRaises(errors.ProgrammerError, backend._CheckStorageParams,
813 None, NotImplemented)
814
815 def testParamsWrongType(self):
816 self.assertRaises(errors.ProgrammerError, backend._CheckStorageParams,
817 "string", NotImplemented)
818
819 def testParamsEmpty(self):
820 backend._CheckStorageParams([], 0)
821
822 def testParamsValidNumber(self):
823 backend._CheckStorageParams(["a", True], 2)
824
825 def testParamsInvalidNumber(self):
826 self.assertRaises(errors.ProgrammerError, backend._CheckStorageParams,
827 ["b", False], 3)
828
829
830 class TestGetVgSpindlesInfo(unittest.TestCase):
831
832 def setUp(self):
833 self.vg_free = 13
834 self.vg_size = 31
835 self.mock_fn = mock.Mock(return_value=(self.vg_free, self.vg_size))
836
837 def testValidInput(self):
838 name = "myvg"
839 excl_stor = True
840 result = backend._GetVgSpindlesInfo(name, excl_stor, info_fn=self.mock_fn)
841 self.mock_fn.assert_called_with(name)
842 self.assertEqual(name, result["name"])
843 self.assertEqual(constants.ST_LVM_PV, result["type"])
844 self.assertEqual(self.vg_free, result["storage_free"])
845 self.assertEqual(self.vg_size, result["storage_size"])
846
847 def testNoExclStor(self):
848 name = "myvg"
849 excl_stor = False
850 result = backend._GetVgSpindlesInfo(name, excl_stor, info_fn=self.mock_fn)
851 self.mock_fn.assert_not_called()
852 self.assertEqual(name, result["name"])
853 self.assertEqual(constants.ST_LVM_PV, result["type"])
854 self.assertEqual(0, result["storage_free"])
855 self.assertEqual(0, result["storage_size"])
856
857
858 class TestGetVgSpindlesInfo(unittest.TestCase):
859
860 def testValidInput(self):
861 self.vg_free = 13
862 self.vg_size = 31
863 self.mock_fn = mock.Mock(return_value=[(self.vg_free, self.vg_size)])
864 name = "myvg"
865 excl_stor = True
866 result = backend._GetVgInfo(name, excl_stor, info_fn=self.mock_fn)
867 self.mock_fn.assert_called_with([name], excl_stor)
868 self.assertEqual(name, result["name"])
869 self.assertEqual(constants.ST_LVM_VG, result["type"])
870 self.assertEqual(self.vg_free, result["storage_free"])
871 self.assertEqual(self.vg_size, result["storage_size"])
872
873 def testNoExclStor(self):
874 name = "myvg"
875 excl_stor = True
876 self.mock_fn = mock.Mock(return_value=None)
877 result = backend._GetVgInfo(name, excl_stor, info_fn=self.mock_fn)
878 self.mock_fn.assert_called_with([name], excl_stor)
879 self.assertEqual(name, result["name"])
880 self.assertEqual(constants.ST_LVM_VG, result["type"])
881 self.assertEqual(None, result["storage_free"])
882 self.assertEqual(None, result["storage_size"])
883
884
885 class TestGetNodeInfo(unittest.TestCase):
886
887 _SOME_RESULT = None
888
889 def testApplyStorageInfoFunction(self):
890 orig_fn = backend._ApplyStorageInfoFunction
891 backend._ApplyStorageInfoFunction = mock.Mock(
892 return_value=self._SOME_RESULT)
893 storage_units = [(st, st + "_key", [st + "_params"]) for st in
894 constants.STORAGE_TYPES]
895
896 backend.GetNodeInfo(storage_units, None)
897
898 call_args_list = backend._ApplyStorageInfoFunction.call_args_list
899 self.assertEqual(len(constants.STORAGE_TYPES), len(call_args_list))
900 for call in call_args_list:
901 storage_type, storage_key, storage_params = call[0]
902 self.assertEqual(storage_type + "_key", storage_key)
903 self.assertEqual([storage_type + "_params"], storage_params)
904 self.assertTrue(storage_type in constants.STORAGE_TYPES)
905 backend._ApplyStorageInfoFunction = orig_fn
906
907
908 class TestSpaceReportingConstants(unittest.TestCase):
909 """Ensures consistency between STS_REPORT and backend.
910
911 These tests ensure, that the constant 'STS_REPORT' is consistent
912 with the implementation of invoking space reporting functions
913 in backend.py. Once space reporting is available for all types,
914 the constant can be removed and these tests as well.
915
916 """
917
918 REPORTING = set(constants.STS_REPORT)
919 NOT_REPORTING = set(constants.STORAGE_TYPES) - REPORTING
920
921 def testAllReportingTypesHaveAReportingFunction(self):
922 for storage_type in TestSpaceReportingConstants.REPORTING:
923 self.assertTrue(backend._STORAGE_TYPE_INFO_FN[storage_type] is not None)
924
925 def testAllNotReportingTypesDontHaveFunction(self):
926 for storage_type in TestSpaceReportingConstants.NOT_REPORTING:
927 self.assertEqual(None, backend._STORAGE_TYPE_INFO_FN[storage_type])
928
929
930 class TestAddRemoveGenerateNodeSshKey(testutils.GanetiTestCase):
931
932 _CLUSTER_NAME = "mycluster"
933 _SSH_PORT = 22
934
935 def setUp(self):
936 self._ssh_file_manager = testutils_ssh.FakeSshFileManager()
937 testutils.GanetiTestCase.setUp(self)
938 self._ssh_add_authorized_patcher = testutils \
939 .patch_object(ssh, "AddAuthorizedKeys")
940 self._ssh_remove_authorized_patcher = testutils \
941 .patch_object(ssh, "RemoveAuthorizedKeys")
942 self._ssh_add_authorized_mock = self._ssh_add_authorized_patcher.start()
943 self._ssh_add_authorized_mock.side_effect = \
944 self._ssh_file_manager.AddAuthorizedKeys
945
946 self._ssconf_mock = mock.Mock()
947 self._ssconf_mock.GetNodeList = mock.Mock()
948 self._ssconf_mock.GetMasterNode = mock.Mock()
949 self._ssconf_mock.GetClusterName = mock.Mock()
950 self._ssconf_mock.GetOnlineNodeList = mock.Mock()
951 self._ssconf_mock.GetSshPortMap = mock.Mock()
952
953 self._run_cmd_mock = mock.Mock()
954 self._run_cmd_mock.side_effect = self._ssh_file_manager.RunCommand
955
956 self._ssh_remove_authorized_mock = \
957 self._ssh_remove_authorized_patcher.start()
958 self._ssh_remove_authorized_mock.side_effect = \
959 self._ssh_file_manager.RemoveAuthorizedKeys
960
961 self._ssh_add_public_key_patcher = testutils \
962 .patch_object(ssh, "AddPublicKey")
963 self._ssh_add_public_key_mock = \
964 self._ssh_add_public_key_patcher.start()
965 self._ssh_add_public_key_mock.side_effect = \
966 self._ssh_file_manager.AddPublicKey
967
968 self._ssh_remove_public_key_patcher = testutils \
969 .patch_object(ssh, "RemovePublicKey")
970 self._ssh_remove_public_key_mock = \
971 self._ssh_remove_public_key_patcher.start()
972 self._ssh_remove_public_key_mock.side_effect = \
973 self._ssh_file_manager.RemovePublicKey
974
975 self._ssh_query_pub_key_file_patcher = testutils \
976 .patch_object(ssh, "QueryPubKeyFile")
977 self._ssh_query_pub_key_file_mock = \
978 self._ssh_query_pub_key_file_patcher.start()
979 self._ssh_query_pub_key_file_mock.side_effect = \
980 self._ssh_file_manager.QueryPubKeyFile
981
982 self._ssh_replace_name_by_uuid_patcher = testutils \
983 .patch_object(ssh, "ReplaceNameByUuid")
984 self._ssh_replace_name_by_uuid_mock = \
985 self._ssh_replace_name_by_uuid_patcher.start()
986 self._ssh_replace_name_by_uuid_mock.side_effect = \
987 self._ssh_file_manager.ReplaceNameByUuid
988
989 self.noded_cert_file = testutils.TestDataFilename("cert1.pem")
990
991 self._SetupTestData()
992
993 def tearDown(self):
994 super(testutils.GanetiTestCase, self).tearDown()
995 self._ssh_add_authorized_patcher.stop()
996 self._ssh_remove_authorized_patcher.stop()
997 self._ssh_add_public_key_patcher.stop()
998 self._ssh_remove_public_key_patcher.stop()
999 self._ssh_query_pub_key_file_patcher.stop()
1000 self._ssh_replace_name_by_uuid_patcher.stop()
1001 self._TearDownTestData()
1002
1003 def _SetupTestData(self, number_of_nodes=15, number_of_pot_mcs=5,
1004 number_of_mcs=5):
1005 """Sets up consistent test data for a cluster with a couple of nodes.
1006
1007 """
1008 self._pub_key_file = self._CreateTempFile()
1009 self._all_nodes = []
1010 self._potential_master_candidates = []
1011 self._master_candidate_uuids = []
1012
1013 self._ssconf_mock.reset_mock()
1014 self._ssconf_mock.GetNodeList.reset_mock()
1015 self._ssconf_mock.GetMasterNode.reset_mock()
1016 self._ssconf_mock.GetClusterName.reset_mock()
1017 self._ssconf_mock.GetOnlineNodeList.reset_mock()
1018 self._run_cmd_mock.reset_mock()
1019
1020 self._ssh_file_manager.InitAllNodes(15, 10, 5)
1021 self._master_node = self._ssh_file_manager.GetMasterNodeName()
1022 self._ssconf_mock.GetSshPortMap.return_value = \
1023 self._ssh_file_manager.GetSshPortMap(self._SSH_PORT)
1024 self._potential_master_candidates = \
1025 self._ssh_file_manager.GetAllPotentialMasterCandidateNodeNames()
1026 self._master_candidate_uuids = \
1027 self._ssh_file_manager.GetAllMasterCandidateUuids()
1028 self._all_nodes = self._ssh_file_manager.GetAllNodeNames()
1029
1030 self._ssconf_mock.GetNodeList.side_effect = \
1031 self._ssh_file_manager.GetAllNodeNames
1032 self._ssconf_mock.GetOnlineNodeList.side_effect = \
1033 self._ssh_file_manager.GetAllNodeNames
1034 self._ssconf_mock.GetMasterNode.side_effect = \
1035 self._ssh_file_manager.GetMasterNodeName
1036
1037 def _TearDownTestData(self):
1038 os.remove(self._pub_key_file)
1039
1040 def _GetCallsPerNode(self):
1041 calls_per_node = {}
1042 for (pos, keyword) in self._run_cmd_mock.call_args_list:
1043 (cluster_name, node, _, _, data) = pos
1044 if not node in calls_per_node:
1045 calls_per_node[node] = []
1046 calls_per_node[node].append(data)
1047 return calls_per_node
1048
1049 def testGenerateKey(self):
1050 test_node_name = "node_name_7"
1051 test_node_uuid = "node_uuid_7"
1052
1053 self._SetupTestData()
1054 ssh.AddPublicKey(test_node_uuid, "some_old_key",
1055 key_file=self._pub_key_file)
1056
1057 backend._GenerateNodeSshKey(
1058 test_node_uuid, test_node_name,
1059 self._ssh_file_manager.GetSshPortMap(self._SSH_PORT),
1060 pub_key_file=self._pub_key_file,
1061 ssconf_store=self._ssconf_mock,
1062 noded_cert_file=self.noded_cert_file,
1063 run_cmd_fn=self._run_cmd_mock)
1064
1065 calls_per_node = self._GetCallsPerNode()
1066 for node, calls in calls_per_node.items():
1067 self.assertEquals(node, test_node_name)
1068 for call in calls:
1069 self.assertTrue(constants.SSHS_GENERATE in call)
1070
1071 def _AddNewNodeToTestData(self, name, uuid, key, pot_mc, mc, master):
1072 self._ssh_file_manager.SetOrAddNode(name, uuid, key, pot_mc, mc, master)
1073
1074 if pot_mc:
1075 ssh.AddPublicKey(name, key, key_file=self._pub_key_file)
1076 self._potential_master_candidates.append(name)
1077
1078 self._ssconf_mock.GetSshPortMap.return_value = \
1079 self._ssh_file_manager.GetSshPortMap(self._SSH_PORT)
1080
1081 def _GetNewMasterCandidate(self):
1082 """Returns the properties of a new master candidate node."""
1083 return ("new_node_name", "new_node_uuid", "new_node_key",
1084 True, True, False)
1085
1086 def _GetNewNumberedMasterCandidate(self, num):
1087 """Returns the properties of a new master candidate node."""
1088 return ("new_node_name_%s" % num,
1089 "new_node_uuid_%s" % num,
1090 "new_node_key_%s" % num,
1091 True, True, False)
1092
1093 def _GetNewNumberedPotentialMasterCandidate(self, num):
1094 """Returns the properties of a new potential master candidate node."""
1095 return ("new_node_name_%s" % num,
1096 "new_node_uuid_%s" % num,
1097 "new_node_key_%s" % num,
1098 False, True, False)
1099
1100 def _GetNewNumberedNormalNode(self, num):
1101 """Returns the properties of a new normal node."""
1102 return ("new_node_name_%s" % num,
1103 "new_node_uuid_%s" % num,
1104 "new_node_key_%s" % num,
1105 False, False, False)
1106
1107 def testAddMasterCandidate(self):
1108 (new_node_name, new_node_uuid, new_node_key, is_master_candidate,
1109 is_potential_master_candidate, is_master) = self._GetNewMasterCandidate()
1110
1111 self._AddNewNodeToTestData(
1112 new_node_name, new_node_uuid, new_node_key,
1113 is_potential_master_candidate, is_master_candidate,
1114 is_master)
1115
1116 backend.AddNodeSshKey(new_node_uuid, new_node_name,
1117 self._potential_master_candidates,
1118 to_authorized_keys=is_master_candidate,
1119 to_public_keys=is_potential_master_candidate,
1120 get_public_keys=is_potential_master_candidate,
1121 pub_key_file=self._pub_key_file,
1122 ssconf_store=self._ssconf_mock,
1123 noded_cert_file=self.noded_cert_file,
1124 run_cmd_fn=self._run_cmd_mock)
1125
1126 self._ssh_file_manager.AssertPotentialMasterCandidatesOnlyHavePublicKey(
1127 new_node_name)
1128 self._ssh_file_manager.AssertAllNodesHaveAuthorizedKey(new_node_key)
1129
1130 def _SetupNodeBulk(self, num_nodes, node_fn):
1131 """Sets up the test data for a bulk of nodes.
1132
1133 @param num_nodes: number of nodes
1134 @type num_nodes: integer
1135 @param node_fn: function
1136 @param node_fn: function to generate data of one node, taking an
1137 integer as only argument
1138
1139 """
1140 node_list = []
1141 key_map = {}
1142
1143 for i in range(num_nodes):
1144 (new_node_name, new_node_uuid, new_node_key, is_master_candidate,
1145 is_potential_master_candidate, is_master) = \
1146 node_fn(i)
1147
1148 self._AddNewNodeToTestData(
1149 new_node_name, new_node_uuid, new_node_key,
1150 is_potential_master_candidate, is_master_candidate,
1151 is_master)
1152
1153 node_list.append(
1154 backend.SshAddNodeInfo(
1155 uuid=new_node_uuid,
1156 name=new_node_name,
1157 to_authorized_keys=is_master_candidate,
1158 to_public_keys=is_potential_master_candidate,
1159 get_public_keys=is_potential_master_candidate))
1160
1161 key_map[new_node_name] = new_node_key
1162
1163 return (node_list, key_map)
1164
1165 def testAddMasterCandidateBulk(self):
1166 num_nodes = 3
1167 (node_list, key_map) = self._SetupNodeBulk(
1168 num_nodes, self._GetNewNumberedMasterCandidate)
1169
1170 backend.AddNodeSshKeyBulk(node_list,
1171 self._potential_master_candidates,
1172 pub_key_file=self._pub_key_file,
1173 ssconf_store=self._ssconf_mock,
1174 noded_cert_file=self.noded_cert_file,
1175 run_cmd_fn=self._run_cmd_mock)
1176
1177 for node_info in node_list:
1178 self._ssh_file_manager.AssertPotentialMasterCandidatesOnlyHavePublicKey(
1179 node_info.name)
1180 self._ssh_file_manager.AssertAllNodesHaveAuthorizedKey(
1181 key_map[node_info.name])
1182
1183 def testAddPotentialMasterCandidateBulk(self):
1184 num_nodes = 3
1185 (node_list, key_map) = self._SetupNodeBulk(
1186 num_nodes, self._GetNewNumberedPotentialMasterCandidate)
1187
1188 backend.AddNodeSshKeyBulk(node_list,
1189 self._potential_master_candidates,
1190 pub_key_file=self._pub_key_file,
1191 ssconf_store=self._ssconf_mock,
1192 noded_cert_file=self.noded_cert_file,
1193 run_cmd_fn=self._run_cmd_mock)
1194
1195 for node_info in node_list:
1196 self._ssh_file_manager.AssertPotentialMasterCandidatesOnlyHavePublicKey(
1197 node_info.name)
1198 self._ssh_file_manager.AssertNoNodeHasAuthorizedKey(
1199 key_map[node_info.name])
1200
1201 def testAddPotentialMasterCandidate(self):
1202 new_node_name = "new_node_name"
1203 new_node_uuid = "new_node_uuid"
1204 new_node_key = "new_node_key"
1205 is_master_candidate = False
1206 is_potential_master_candidate = True
1207 is_master = False
1208
1209 self._AddNewNodeToTestData(
1210 new_node_name, new_node_uuid, new_node_key,
1211 is_potential_master_candidate, is_master_candidate,
1212 is_master)
1213
1214 backend.AddNodeSshKey(new_node_uuid, new_node_name,
1215 self._potential_master_candidates,
1216 to_authorized_keys=is_master_candidate,
1217 to_public_keys=is_potential_master_candidate,
1218 get_public_keys=is_potential_master_candidate,
1219 pub_key_file=self._pub_key_file,
1220 ssconf_store=self._ssconf_mock,
1221 noded_cert_file=self.noded_cert_file,
1222 run_cmd_fn=self._run_cmd_mock)
1223
1224 self._ssh_file_manager.AssertPotentialMasterCandidatesOnlyHavePublicKey(
1225 new_node_name)
1226 self._ssh_file_manager.AssertNoNodeHasAuthorizedKey(new_node_key)
1227
1228 def testAddNormalNode(self):
1229 new_node_name = "new_node_name"
1230 new_node_uuid = "new_node_uuid"
1231 new_node_key = "new_node_key"
1232 is_master_candidate = False
1233 is_potential_master_candidate = False
1234 is_master = False
1235
1236 self._AddNewNodeToTestData(
1237 new_node_name, new_node_uuid, new_node_key,
1238 is_potential_master_candidate, is_master_candidate,
1239 is_master)
1240
1241 self.assertRaises(
1242 AssertionError, backend.AddNodeSshKey, new_node_uuid, new_node_name,
1243 self._potential_master_candidates,
1244 to_authorized_keys=is_master_candidate,
1245 to_public_keys=is_potential_master_candidate,
1246 get_public_keys=is_potential_master_candidate,
1247 pub_key_file=self._pub_key_file,
1248 ssconf_store=self._ssconf_mock,
1249 noded_cert_file=self.noded_cert_file,
1250 run_cmd_fn=self._run_cmd_mock)
1251
1252 self._ssh_file_manager.AssertNoNodeHasPublicKey(new_node_uuid, new_node_key)
1253 self._ssh_file_manager.AssertNoNodeHasAuthorizedKey(new_node_key)
1254
1255 def testAddNormalBulk(self):
1256 num_nodes = 3
1257 (node_list, key_map) = self._SetupNodeBulk(
1258 num_nodes, self._GetNewNumberedNormalNode)
1259
1260 self.assertRaises(
1261 AssertionError, backend.AddNodeSshKeyBulk, node_list,
1262 self._potential_master_candidates,
1263 pub_key_file=self._pub_key_file,
1264 ssconf_store=self._ssconf_mock,
1265 noded_cert_file=self.noded_cert_file,
1266 run_cmd_fn=self._run_cmd_mock)
1267
1268 for node_info in node_list:
1269 self._ssh_file_manager.AssertNoNodeHasPublicKey(
1270 node_info.uuid, key_map[node_info.name])
1271 self._ssh_file_manager.AssertNoNodeHasAuthorizedKey(
1272 key_map[node_info.name])
1273
1274 def _GetNewNumberedNode(self, num):
1275 """Returns the properties of a node.
1276
1277 This will in round-robin style return a master candidate, a
1278 potential master candiate and a normal node.
1279
1280 """
1281 is_master_candidate = num % 3 == 0
1282 is_potential_master_candidate = num % 3 == 0 or num % 3 == 1
1283 is_master = False
1284 return ("new_node_name_%s" % num,
1285 "new_node_uuid_%s" % num,
1286 "new_node_key_%s" % num,
1287 is_master_candidate, is_potential_master_candidate, is_master)
1288
1289 def testAddDiverseNodeBulk(self):
1290 """Tests adding keys of several nodes with several qualities.
1291
1292 This tests subsumes previous tests. However, we leave the previous
1293 tests here, because debugging problems with this all-embracing test
1294 is much more tedious than having one of the one-purpose tests fail.
1295
1296 """
1297 num_nodes = 9
1298 (node_list, key_map) = self._SetupNodeBulk(
1299 num_nodes, self._GetNewNumberedNode)
1300
1301 backend.AddNodeSshKeyBulk(node_list,
1302 self._potential_master_candidates,
1303 pub_key_file=self._pub_key_file,
1304 ssconf_store=self._ssconf_mock,
1305 noded_cert_file=self.noded_cert_file,
1306 run_cmd_fn=self._run_cmd_mock)
1307
1308 for node_info in node_list:
1309 if node_info.to_authorized_keys:
1310 self._ssh_file_manager.AssertAllNodesHaveAuthorizedKey(
1311 key_map[node_info.name])
1312 else:
1313 self._ssh_file_manager.AssertNoNodeHasAuthorizedKey(
1314 key_map[node_info.name])
1315 if node_info.to_public_keys:
1316 self._ssh_file_manager.AssertPotentialMasterCandidatesOnlyHavePublicKey(
1317 node_info.name)
1318 else:
1319 self._ssh_file_manager.AssertNoNodeHasPublicKey(
1320 node_info.uuid, key_map[node_info.name])
1321
1322 def testPromoteToMasterCandidate(self):
1323 # Get one of the potential master candidates
1324 node_name, node_info = \
1325 self._ssh_file_manager.GetAllPurePotentialMasterCandidates()[0]
1326 # Update it's role to master candidate in the test data
1327 self._ssh_file_manager.SetOrAddNode(
1328 node_name, node_info.uuid, node_info.key,
1329 node_info.is_potential_master_candidate, True, node_info.is_master)
1330
1331 backend.AddNodeSshKey(node_info.uuid, node_name,
1332 self._potential_master_candidates,
1333 to_authorized_keys=True,
1334 to_public_keys=False,
1335 get_public_keys=False,
1336 pub_key_file=self._pub_key_file,
1337 ssconf_store=self._ssconf_mock,
1338 noded_cert_file=self.noded_cert_file,
1339 run_cmd_fn=self._run_cmd_mock)
1340
1341 self._ssh_file_manager.AssertPotentialMasterCandidatesOnlyHavePublicKey(
1342 node_name)
1343 self._ssh_file_manager.AssertAllNodesHaveAuthorizedKey(node_info.key)
1344
1345 def testRemoveMasterCandidate(self):
1346 node_name, (node_uuid, node_key, is_potential_master_candidate,
1347 is_master_candidate, is_master) = \
1348 self._ssh_file_manager.GetAllMasterCandidates()[0]
1349
1350 backend.RemoveNodeSshKey(node_uuid, node_name,
1351 self._master_candidate_uuids,
1352 self._potential_master_candidates,
1353 from_authorized_keys=True,
1354 from_public_keys=True,
1355 clear_authorized_keys=True,
1356 clear_public_keys=True,
1357 pub_key_file=self._pub_key_file,
1358 ssconf_store=self._ssconf_mock,
1359 noded_cert_file=self.noded_cert_file,
1360 run_cmd_fn=self._run_cmd_mock)
1361
1362 self._ssh_file_manager.AssertNoNodeHasPublicKey(node_uuid, node_key)
1363 self._ssh_file_manager.AssertNodeSetOnlyHasAuthorizedKey(
1364 [node_name], node_key)
1365 self.assertEqual(0,
1366 len(self._ssh_file_manager.GetPublicKeysOfNode(node_name)))
1367 self.assertEqual(1,
1368 len(self._ssh_file_manager.GetAuthorizedKeysOfNode(node_name)))
1369
1370 def testRemoveMasterCandidateBulk(self):
1371 node_list = []
1372 key_map = {}
1373 for node_name, (node_uuid, node_key, _, _, _) in \
1374 self._ssh_file_manager.GetAllMasterCandidates()[:3]:
1375 node_list.append(backend.SshRemoveNodeInfo(uuid=node_uuid,
1376 name=node_name,
1377 from_authorized_keys=True,
1378 from_public_keys=True,
1379 clear_authorized_keys=True,
1380 clear_public_keys=True))
1381 key_map[node_name] = node_key
1382
1383 backend.RemoveNodeSshKeyBulk(node_list,
1384 self._master_candidate_uuids,
1385 self._potential_master_candidates,
1386 pub_key_file=self._pub_key_file,
1387 ssconf_store=self._ssconf_mock,
1388 noded_cert_file=self.noded_cert_file,
1389 run_cmd_fn=self._run_cmd_mock)
1390
1391 for node_info in node_list:
1392 self._ssh_file_manager.AssertNoNodeHasPublicKey(
1393 node_info.uuid, key_map[node_info.name])
1394 self._ssh_file_manager.AssertNodeSetOnlyHasAuthorizedKey(
1395 [node_info.name], key_map[node_info.name])
1396 self.assertEqual(0,
1397 len(self._ssh_file_manager.GetPublicKeysOfNode(node_info.name)))
1398 self.assertEqual(1,
1399 len(self._ssh_file_manager.GetAuthorizedKeysOfNode(node_info.name)))
1400
1401 def testRemovePotentialMasterCandidate(self):
1402 (node_name, node_info) = \
1403 self._ssh_file_manager.GetAllPurePotentialMasterCandidates()[0]
1404
1405 backend.RemoveNodeSshKey(node_info.uuid, node_name,
1406 self._master_candidate_uuids,
1407 self._potential_master_candidates,
1408 from_authorized_keys=False,
1409 from_public_keys=True,
1410 clear_authorized_keys=True,
1411 clear_public_keys=True,
1412 pub_key_file=self._pub_key_file,
1413 ssconf_store=self._ssconf_mock,
1414 noded_cert_file=self.noded_cert_file,
1415 run_cmd_fn=self._run_cmd_mock)
1416
1417 self._ssh_file_manager.AssertNoNodeHasPublicKey(
1418 node_info.uuid, node_info.key)
1419 self._ssh_file_manager.AssertNodeSetOnlyHasAuthorizedKey(
1420 [node_name], node_info.key)
1421 self.assertEqual(0,
1422 len(self._ssh_file_manager.GetPublicKeysOfNode(node_name)))
1423 self.assertEqual(1,
1424 len(self._ssh_file_manager.GetAuthorizedKeysOfNode(node_name)))
1425
1426 def testRemovePotentialMasterCandidateBulk(self):
1427 node_list = []
1428 key_map = {}
1429 for node_name, (node_uuid, node_key, _, _, _) in \
1430 self._ssh_file_manager.GetAllPurePotentialMasterCandidates()[:3]:
1431 node_list.append(backend.SshRemoveNodeInfo(uuid=node_uuid,
1432 name=node_name,
1433 from_authorized_keys=False,
1434 from_public_keys=True,
1435 clear_authorized_keys=True,
1436 clear_public_keys=True))
1437 key_map[node_name] = node_key
1438
1439 backend.RemoveNodeSshKeyBulk(node_list,
1440 self._master_candidate_uuids,
1441 self._potential_master_candidates,
1442 pub_key_file=self._pub_key_file,
1443 ssconf_store=self._ssconf_mock,
1444 noded_cert_file=self.noded_cert_file,
1445 run_cmd_fn=self._run_cmd_mock)
1446
1447 for node_info in node_list:
1448 self._ssh_file_manager.AssertNoNodeHasPublicKey(
1449 node_info.uuid, key_map[node_info.name])
1450 self._ssh_file_manager.AssertNodeSetOnlyHasAuthorizedKey(
1451 [node_info.name], key_map[node_info.name])
1452 self.assertEqual(0,
1453 len(self._ssh_file_manager.GetPublicKeysOfNode(node_info.name)))
1454 self.assertEqual(1,
1455 len(self._ssh_file_manager.GetAuthorizedKeysOfNode(node_info.name)))
1456
1457 def testRemoveNormalNode(self):
1458 node_name, node_info = self._ssh_file_manager.GetAllNormalNodes()[0]
1459
1460 backend.RemoveNodeSshKey(node_info.uuid, node_name,
1461 self._master_candidate_uuids,
1462 self._potential_master_candidates,
1463 from_authorized_keys=False,
1464 from_public_keys=False,
1465 clear_authorized_keys=True,
1466 clear_public_keys=True,
1467 pub_key_file=self._pub_key_file,
1468 ssconf_store=self._ssconf_mock,
1469 noded_cert_file=self.noded_cert_file,
1470 run_cmd_fn=self._run_cmd_mock)
1471
1472 self._ssh_file_manager.AssertNoNodeHasPublicKey(
1473 node_info.uuid, node_info.key)
1474 self._ssh_file_manager.AssertNodeSetOnlyHasAuthorizedKey(
1475 [node_name], node_info.key)
1476 self.assertEqual(0,
1477 len(self._ssh_file_manager.GetPublicKeysOfNode(node_name)))
1478 self.assertEqual(1,
1479 len(self._ssh_file_manager.GetAuthorizedKeysOfNode(node_name)))
1480
1481 def testDemoteMasterCandidateToPotentialMasterCandidate(self):
1482 node_name, node_info = self._ssh_file_manager.GetAllMasterCandidates()[0]
1483 self._ssh_file_manager.SetOrAddNode(
1484 node_name, node_info.uuid, node_info.key,
1485 node_info.is_potential_master_candidate, False, node_info.is_master)
1486
1487 backend.RemoveNodeSshKey(node_info.uuid, node_name,
1488 self._master_candidate_uuids,
1489 self._potential_master_candidates,
1490 from_authorized_keys=True,
1491 from_public_keys=False,
1492 clear_authorized_keys=False,
1493 clear_public_keys=False,
1494 pub_key_file=self._pub_key_file,
1495 ssconf_store=self._ssconf_mock,
1496 noded_cert_file=self.noded_cert_file,
1497 run_cmd_fn=self._run_cmd_mock)
1498
1499 self._ssh_file_manager.AssertPotentialMasterCandidatesOnlyHavePublicKey(
1500 node_name)
1501 self._ssh_file_manager.AssertNodeSetOnlyHasAuthorizedKey(
1502 [node_name], node_info.key)
1503
1504 def testDemotePotentialMasterCandidateToNormalNode(self):
1505 (node_name, node_info) = \
1506 self._ssh_file_manager.GetAllPurePotentialMasterCandidates()[0]
1507 self._ssh_file_manager.SetOrAddNode(
1508 node_name, node_info.uuid, node_info.key, False,
1509 node_info.is_master_candidate, node_info.is_master)
1510
1511 backend.RemoveNodeSshKey(node_info.uuid, node_name,
1512 self._master_candidate_uuids,
1513 self._potential_master_candidates,
1514 from_authorized_keys=False,
1515 from_public_keys=True,
1516 clear_authorized_keys=False,
1517 clear_public_keys=False,
1518 pub_key_file=self._pub_key_file,
1519 ssconf_store=self._ssconf_mock,
1520 noded_cert_file=self.noded_cert_file,
1521 run_cmd_fn=self._run_cmd_mock)
1522
1523 self._ssh_file_manager.AssertNoNodeHasPublicKey(
1524 node_info.uuid, node_info.key)
1525 self._ssh_file_manager.AssertNodeSetOnlyHasAuthorizedKey(
1526 [node_name], node_info.key)
1527
1528 def _GetReducedOnlineNodeList(self):
1529 """'Randomly' mark some nodes as offline."""
1530 return [name for name in self._all_nodes
1531 if '3' not in name and '5' not in name]
1532
1533 def testAddKeyWithOfflineNodes(self):
1534 (new_node_name, new_node_uuid, new_node_key, is_master_candidate,
1535 is_potential_master_candidate, is_master) = self._GetNewMasterCandidate()
1536
1537 self._AddNewNodeToTestData(
1538 new_node_name, new_node_uuid, new_node_key,
1539 is_potential_master_candidate, is_master_candidate,
1540 is_master)
1541 self._online_nodes = self._GetReducedOnlineNodeList()
1542 self._ssconf_mock.GetOnlineNodeList.side_effect = \
1543 lambda : self._online_nodes
1544
1545 backend.AddNodeSshKey(new_node_uuid, new_node_name,
1546 self._potential_master_candidates,
1547 to_authorized_keys=is_master_candidate,
1548 to_public_keys=is_potential_master_candidate,
1549 get_public_keys=is_potential_master_candidate,
1550 pub_key_file=self._pub_key_file,
1551 ssconf_store=self._ssconf_mock,
1552 noded_cert_file=self.noded_cert_file,
1553 run_cmd_fn=self._run_cmd_mock)
1554
1555 for node in self._all_nodes:
1556 if node in self._online_nodes:
1557 self.assertTrue(self._ssh_file_manager.NodeHasAuthorizedKey(
1558 node, new_node_key))
1559 else:
1560 self.assertFalse(self._ssh_file_manager.NodeHasAuthorizedKey(
1561 node, new_node_key))
1562
1563 def testRemoveKeyWithOfflineNodes(self):
1564 (node_name, node_info) = \
1565 self._ssh_file_manager.GetAllMasterCandidates()[0]
1566 self._online_nodes = self._GetReducedOnlineNodeList()
1567 self._ssconf_mock.GetOnlineNodeList.side_effect = \
1568 lambda : self._online_nodes
1569
1570 backend.RemoveNodeSshKey(node_info.uuid, node_name,
1571 self._master_candidate_uuids,
1572 self._potential_master_candidates,
1573 from_authorized_keys=True,
1574 from_public_keys=True,
1575 clear_authorized_keys=True,
1576 clear_public_keys=True,
1577 pub_key_file=self._pub_key_file,
1578 ssconf_store=self._ssconf_mock,
1579 noded_cert_file=self.noded_cert_file,
1580 run_cmd_fn=self._run_cmd_mock)
1581
1582 offline_nodes = [node for node in self._all_nodes
1583 if node not in self._online_nodes]
1584 self._ssh_file_manager.AssertNodeSetOnlyHasAuthorizedKey(
1585 offline_nodes + [node_name], node_info.key)
1586
1587 def testAddKeySuccessfullyOnNewNodeWithRetries(self):
1588 """Tests adding a new node's key when updating that node takes retries.
1589
1590 This test checks whether adding a new node's key successfully updates
1591 the SSH key files of all nodes, even if updating the new node's key files
1592 itself takes a couple of retries to succeed.
1593
1594 """
1595 (new_node_name, new_node_uuid, new_node_key, is_master_candidate,
1596 is_potential_master_candidate, is_master) = self._GetNewMasterCandidate()
1597
1598 self._AddNewNodeToTestData(
1599 new_node_name, new_node_uuid, new_node_key,
1600 is_potential_master_candidate, is_master_candidate,
1601 is_master)
1602 self._ssh_file_manager.SetMaxRetries(
1603 new_node_name, constants.SSHS_MAX_RETRIES)
1604
1605 backend.AddNodeSshKey(new_node_uuid, new_node_name,
1606 self._potential_master_candidates,
1607 to_authorized_keys=is_master_candidate,
1608 to_public_keys=is_potential_master_candidate,
1609 get_public_keys=is_potential_master_candidate,
1610 pub_key_file=self._pub_key_file,
1611 ssconf_store=self._ssconf_mock,
1612 noded_cert_file=self.noded_cert_file,
1613 run_cmd_fn=self._run_cmd_mock)
1614
1615 self._ssh_file_manager.AssertPotentialMasterCandidatesOnlyHavePublicKey(
1616 new_node_name)
1617 self._ssh_file_manager.AssertAllNodesHaveAuthorizedKey(
1618 new_node_key)
1619
1620 def testAddKeyFailedOnNewNodeWithRetries(self):
1621 """Tests clean up if updating a new node's SSH setup fails.
1622
1623 If adding the keys of a new node fails, because updating the SSH key files
1624 of that new node fails, check whether already carried out operations are
1625 successfully rolled back and thus the state of the cluster is cleaned up.
1626
1627 """
1628 (new_node_name, new_node_uuid, new_node_key, is_master_candidate,
1629 is_potential_master_candidate, is_master) = self._GetNewMasterCandidate()
1630
1631 self._AddNewNodeToTestData(
1632 new_node_name, new_node_uuid, new_node_key,
1633 is_potential_master_candidate, is_master_candidate,
1634 is_master)
1635 self._ssh_file_manager.SetMaxRetries(
1636 new_node_name, constants.SSHS_MAX_RETRIES + 1)
1637
1638 self.assertRaises(
1639 errors.SshUpdateError, backend.AddNodeSshKey, new_node_uuid,
1640 new_node_name, self._potential_master_candidates,
1641 to_authorized_keys=is_master_candidate,
1642 to_public_keys=is_potential_master_candidate,
1643 get_public_keys=is_potential_master_candidate,
1644 pub_key_file=self._pub_key_file,
1645 ssconf_store=self._ssconf_mock,
1646 noded_cert_file=self.noded_cert_file,
1647 run_cmd_fn=self._run_cmd_mock)
1648
1649 master_node = self._ssh_file_manager.GetMasterNodeName()
1650 for node in self._all_nodes:
1651 if node in [new_node_name, master_node]:
1652 self.assertTrue(self._ssh_file_manager.NodeHasAuthorizedKey(
1653 node, new_node_key))
1654 else:
1655 self.assertFalse(self._ssh_file_manager.NodeHasAuthorizedKey(
1656 node, new_node_key))
1657
1658 self._ssh_file_manager.AssertNoNodeHasPublicKey(new_node_uuid, new_node_key)
1659
1660 def testAddKeySuccessfullyOnOldNodeWithRetries(self):
1661 """Tests adding a new key even if updating nodes takes retries.
1662
1663 This tests whether adding a new node's key successfully finishes,
1664 even if one of the other cluster nodes takes a couple of retries
1665 to succeed.
1666
1667 """
1668 (new_node_name, new_node_uuid, new_node_key, is_master_candidate,
1669 is_potential_master_candidate, is_master) = self._GetNewMasterCandidate()
1670
1671 other_node_name, _ = self._ssh_file_manager.GetAllMasterCandidates()[0]
1672 self._ssh_file_manager.SetMaxRetries(
1673 other_node_name, constants.SSHS_MAX_RETRIES)
1674 assert other_node_name != new_node_name
1675 self._AddNewNodeToTestData(
1676 new_node_name, new_node_uuid, new_node_key,
1677 is_potential_master_candidate, is_master_candidate,
1678 is_master)
1679
1680 backend.AddNodeSshKey(new_node_uuid, new_node_name,
1681 self._potential_master_candidates,
1682 to_authorized_keys=is_master_candidate,
1683 to_public_keys=is_potential_master_candidate,
1684 get_public_keys=is_potential_master_candidate,
1685 pub_key_file=self._pub_key_file,
1686 ssconf_store=self._ssconf_mock,
1687 noded_cert_file=self.noded_cert_file,
1688 run_cmd_fn=self._run_cmd_mock)
1689
1690 self._ssh_file_manager.AssertAllNodesHaveAuthorizedKey(new_node_key)
1691
1692 def testAddKeyFailedOnOldNodeWithRetries(self):
1693 """Tests adding keys when updating one node's SSH setup fails.
1694
1695 This tests whether when adding a new node's key and one node is
1696 unreachable (but not marked as offline) the operation still finishes
1697 properly and only that unreachable node's SSH key setup did not get
1698 updated.
1699
1700 """
1701 (new_node_name, new_node_uuid, new_node_key, is_master_candidate,
1702 is_potential_master_candidate, is_master) = self._GetNewMasterCandidate()
1703
1704 other_node_name, _ = self._ssh_file_manager.GetAllMasterCandidates()[0]
1705 self._ssh_file_manager.SetMaxRetries(
1706 other_node_name, constants.SSHS_MAX_RETRIES + 1)
1707 assert other_node_name != new_node_name
1708 self._AddNewNodeToTestData(
1709 new_node_name, new_node_uuid, new_node_key,
1710 is_potential_master_candidate, is_master_candidate,
1711 is_master)
1712
1713 node_errors = backend.AddNodeSshKey(
1714 new_node_uuid, new_node_name, self._potential_master_candidates,
1715 to_authorized_keys=is_master_candidate,
1716 to_public_keys=is_potential_master_candidate,
1717 get_public_keys=is_potential_master_candidate,
1718 pub_key_file=self._pub_key_file,
1719 ssconf_store=self._ssconf_mock,
1720 noded_cert_file=self.noded_cert_file,
1721 run_cmd_fn=self._run_cmd_mock)
1722
1723 rest_nodes = [node for node in self._all_nodes
1724 if node != other_node_name]
1725 rest_nodes.append(new_node_name)
1726 self._ssh_file_manager.AssertNodeSetOnlyHasAuthorizedKey(
1727 rest_nodes, new_node_key)
1728 self.assertTrue([error_msg for (node, error_msg) in node_errors
1729 if node == other_node_name])
1730
1731 def testRemoveKeySuccessfullyWithRetriesOnOtherNode(self):
1732 """Test removing keys even if one of the old nodes needs retries.
1733
1734 This tests checks whether a key can be removed successfully even
1735 when one of the other nodes needs to be contacted with several
1736 retries.
1737
1738 """
1739 all_master_candidates = self._ssh_file_manager.GetAllMasterCandidates()
1740 node_name, node_info = all_master_candidates[0]
1741 other_node_name, _ = all_master_candidates[1]
1742 assert node_name != self._master_node
1743 assert other_node_name != self._master_node
1744 assert node_name != other_node_name
1745 self._ssh_file_manager.SetMaxRetries(
1746 other_node_name, constants.SSHS_MAX_RETRIES)
1747
1748 backend.RemoveNodeSshKey(node_info.uuid, node_name,
1749 self._master_candidate_uuids,
1750 self._potential_master_candidates,
1751 from_authorized_keys=True,
1752 from_public_keys=True,
1753 clear_authorized_keys=True,
1754 clear_public_keys=True,
1755 pub_key_file=self._pub_key_file,
1756 ssconf_store=self._ssconf_mock,
1757 noded_cert_file=self.noded_cert_file,
1758 run_cmd_fn=self._run_cmd_mock)
1759
1760 self._ssh_file_manager.AssertNoNodeHasPublicKey(
1761 node_info.uuid, node_info.key)
1762 self._ssh_file_manager.AssertNodeSetOnlyHasAuthorizedKey(
1763 [node_name], node_info.key)
1764
1765 def testRemoveKeyFailedWithRetriesOnOtherNode(self):
1766 """Test removing keys even if one of the old nodes fails even with retries.
1767
1768 This tests checks whether the removal of a key finishes properly, even if
1769 the update of the key files on one of the other nodes fails despite several
1770 retries.
1771
1772 """
1773 all_master_candidates = self._ssh_file_manager.GetAllMasterCandidates()
1774 node_name, node_info = all_master_candidates[0]
1775 other_node_name, _ = all_master_candidates[1]
1776 assert node_name != self._master_node
1777 assert other_node_name != self._master_node
1778 assert node_name != other_node_name
1779 self._ssh_file_manager.SetMaxRetries(
1780 other_node_name, constants.SSHS_MAX_RETRIES + 1)
1781
1782 error_msgs = backend.RemoveNodeSshKey(
1783 node_info.uuid, node_name, self._master_candidate_uuids,
1784 self._potential_master_candidates,
1785 from_authorized_keys=True, from_public_keys=True,
1786 clear_authorized_keys=True, clear_public_keys=True,
1787 pub_key_file=self._pub_key_file, ssconf_store=self._ssconf_mock,
1788 noded_cert_file=self.noded_cert_file, run_cmd_fn=self._run_cmd_mock)
1789
1790 self._ssh_file_manager.AssertNodeSetOnlyHasAuthorizedKey(
1791 [other_node_name, node_name], node_info.key)
1792 self.assertTrue([error_msg for (node, error_msg) in error_msgs
1793 if node == other_node_name])
1794
1795 def testRemoveKeySuccessfullyWithRetriesOnTargetNode(self):
1796 """Test removing keys even if the target nodes needs retries.
1797
1798 This tests checks whether a key can be removed successfully even
1799 when removing the key on the node itself needs retries.
1800
1801 """
1802 all_master_candidates = self._ssh_file_manager.GetAllMasterCandidates()
1803 node_name, node_info = all_master_candidates[0]
1804 assert node_name != self._master_node
1805 self._ssh_file_manager.SetMaxRetries(
1806 node_name, constants.SSHS_MAX_RETRIES)
1807
1808 backend.RemoveNodeSshKey(node_info.uuid, node_name,
1809 self._master_candidate_uuids,
1810 self._potential_master_candidates,
1811 from_authorized_keys=True,
1812 from_public_keys=True,
1813 clear_authorized_keys=True,
1814 clear_public_keys=True,
1815 pub_key_file=self._pub_key_file,
1816 ssconf_store=self._ssconf_mock,
1817 noded_cert_file=self.noded_cert_file,
1818 run_cmd_fn=self._run_cmd_mock)
1819
1820 self._ssh_file_manager.AssertNoNodeHasPublicKey(
1821 node_info.uuid, node_info.key)
1822 self._ssh_file_manager.AssertNodeSetOnlyHasAuthorizedKey(
1823 [node_name], node_info.key)
1824
1825 def testRemoveKeyFailedWithRetriesOnTargetNode(self):
1826 """Test removing keys even if contacting the node fails with retries.
1827
1828 This tests checks whether the removal of a key finishes properly, even if
1829 the update of the key files on the node itself fails despite several
1830 retries.
1831
1832 """
1833 all_master_candidates = self._ssh_file_manager.GetAllMasterCandidates()
1834 node_name, node_info = all_master_candidates[0]
1835 assert node_name != self._master_node
1836 self._ssh_file_manager.SetMaxRetries(
1837 node_name, constants.SSHS_MAX_RETRIES + 1)
1838
1839 error_msgs = backend.RemoveNodeSshKey(
1840 node_info.uuid, node_name, self._master_candidate_uuids,
1841 self._potential_master_candidates,
1842 from_authorized_keys=True, from_public_keys=True,
1843 clear_authorized_keys=True, clear_public_keys=True,
1844 pub_key_file=self._pub_key_file, ssconf_store=self._ssconf_mock,
1845 noded_cert_file=self.noded_cert_file, run_cmd_fn=self._run_cmd_mock)
1846
1847 self._ssh_file_manager.AssertNodeSetOnlyHasAuthorizedKey(
1848 [node_name], node_info.key)
1849 self.assertTrue([error_msg for (node, error_msg) in error_msgs
1850 if node == node_name])
1851
1852
1853 class TestVerifySshSetup(testutils.GanetiTestCase):
1854
1855 _NODE1_UUID = "uuid1"
1856 _NODE2_UUID = "uuid2"
1857 _NODE3_UUID = "uuid3"
1858 _NODE1_NAME = "name1"
1859 _NODE2_NAME = "name2"
1860 _NODE3_NAME = "name3"
1861 _NODE1_KEYS = ["key11"]
1862 _NODE2_KEYS = ["key21"]
1863 _NODE3_KEYS = ["key31"]
1864
1865 _NODE_STATUS_LIST = [
1866 (_NODE1_UUID, _NODE1_NAME, True, True, True),
1867 (_NODE2_UUID, _NODE2_NAME, False, True, True),
1868 (_NODE3_UUID, _NODE3_NAME, False, False, True),
1869 ]
1870
1871 _PUB_KEY_RESULT = {
1872 _NODE1_UUID: _NODE1_KEYS,
1873 _NODE2_UUID: _NODE2_KEYS,
1874 _NODE3_UUID: _NODE3_KEYS,
1875 }
1876
1877 _AUTH_RESULT = {
1878 _NODE1_KEYS[0]: True,
1879 _NODE2_KEYS[0]: False,
1880 _NODE3_KEYS[0]: False,
1881 }
1882
1883 def setUp(self):
1884 testutils.GanetiTestCase.setUp(self)
1885 self._has_authorized_patcher = testutils \
1886 .patch_object(ssh, "HasAuthorizedKey")
1887 self._has_authorized_mock = self._has_authorized_patcher.start()
1888 self._query_patcher = testutils \
1889 .patch_object(ssh, "QueryPubKeyFile")
1890 self._query_mock = self._query_patcher.start()
1891 self._read_file_patcher = testutils \
1892 .patch_object(utils, "ReadFile")
1893 self._read_file_mock = self._read_file_patcher.start()
1894 self._read_file_mock.return_value = self._NODE1_KEYS[0]
1895 self.tmpdir = tempfile.mkdtemp()
1896 self.pub_key_file = os.path.join(self.tmpdir, "pub_key_file")
1897 open(self.pub_key_file, "w").close()
1898
1899 def tearDown(self):
1900 super(testutils.GanetiTestCase, self).tearDown()
1901 self._has_authorized_patcher.stop()
1902 self._query_patcher.stop()
1903 self._read_file_patcher.stop()
1904 shutil.rmtree(self.tmpdir)
1905
1906 def testValidData(self):
1907 self._has_authorized_mock.side_effect = \
1908 lambda _, key : self._AUTH_RESULT[key]
1909 self._query_mock.return_value = self._PUB_KEY_RESULT
1910 result = backend._VerifySshSetup(self._NODE_STATUS_LIST,
1911 self._NODE1_NAME,
1912 pub_key_file=self.pub_key_file)
1913 self.assertEqual(result, [])
1914
1915 def testMissingKey(self):
1916 self._has_authorized_mock.side_effect = \
1917 lambda _, key : self._AUTH_RESULT[key]
1918 pub_key_missing = copy.deepcopy(self._PUB_KEY_RESULT)
1919 del pub_key_missing[self._NODE2_UUID]
1920 self._query_mock.return_value = pub_key_missing
1921 result = backend._VerifySshSetup(self._NODE_STATUS_LIST,
1922 self._NODE1_NAME,
1923 pub_key_file=self.pub_key_file)
1924 self.assertTrue(self._NODE2_UUID in result[0])
1925
1926 def testUnknownKey(self):
1927 self._has_authorized_mock.side_effect = \
1928 lambda _, key : self._AUTH_RESULT[key]
1929 pub_key_missing = copy.deepcopy(self._PUB_KEY_RESULT)
1930 pub_key_missing["unkownnodeuuid"] = "pinkbunny"
1931 self._query_mock.return_value = pub_key_missing
1932 result = backend._VerifySshSetup(self._NODE_STATUS_LIST,
1933 self._NODE1_NAME,
1934 pub_key_file=self.pub_key_file)
1935 self.assertTrue("unkownnodeuuid" in result[0])
1936
1937 def testMissingMasterCandidate(self):
1938 auth_result = copy.deepcopy(self._AUTH_RESULT)
1939 auth_result["key11"] = False
1940 self._has_authorized_mock.side_effect = \
1941 lambda _, key : auth_result[key]
1942 self._query_mock.return_value = self._PUB_KEY_RESULT
1943 result = backend._VerifySshSetup(self._NODE_STATUS_LIST,
1944 self._NODE1_NAME,
1945 pub_key_file=self.pub_key_file)
1946 self.assertTrue(self._NODE1_UUID in result[0])
1947
1948 def testSuperfluousNormalNode(self):
1949 auth_result = copy.deepcopy(self._AUTH_RESULT)
1950 auth_result["key31"] = True
1951 self._has_authorized_mock.side_effect = \
1952 lambda _, key : auth_result[key]
1953 self._query_mock.return_value = self._PUB_KEY_RESULT
1954 result = backend._VerifySshSetup(self._NODE_STATUS_LIST,
1955 self._NODE1_NAME,
1956 pub_key_file=self.pub_key_file)
1957 self.assertTrue(self._NODE3_UUID in result[0])
1958
1959
1960 class TestOSEnvironment(unittest.TestCase):
1961 """Ensure the presence of public and private parameters.
1962
1963 They have to be present inside os environment variables.
1964
1965 """
1966
1967 def _CreateEnv(self):
1968 """Create and return an environment."""
1969 config_mock = ConfigMock()
1970 inst = config_mock.AddNewInstance(
1971 osparams={"public_param": "public_info"},
1972 osparams_private=serializer.PrivateDict({"private_param":
1973 "private_info",
1974 "another_private_param":
1975 "more_privacy"}),
1976 nics = [])
1977 inst.disks_info = ""
1978 inst.secondary_nodes = []
1979
1980 return backend.OSEnvironment(inst, config_mock.CreateOs())
1981
1982 def testParamPresence(self):
1983 env = self._CreateEnv()
1984 env_keys = env.keys()
1985 self.assertTrue("OSP_PUBLIC_PARAM" in env)
1986 self.assertTrue("OSP_PRIVATE_PARAM" in env)
1987 self.assertTrue("OSP_ANOTHER_PRIVATE_PARAM" in env)
1988 self.assertEqual("public_info", env["OSP_PUBLIC_PARAM"])
1989 self.assertEqual("private_info", env["OSP_PRIVATE_PARAM"])
1990 self.assertEqual("more_privacy", env["OSP_ANOTHER_PRIVATE_PARAM"])
1991
1992
1993 if __name__ == "__main__":
1994 testutils.GanetiTestProgram()