Bulk-removal of SSH keys
[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 testRemoveNormalNode(self):
1427 node_name, node_info = self._ssh_file_manager.GetAllNormalNodes()[0]
1428
1429 backend.RemoveNodeSshKey(node_info.uuid, node_name,
1430 self._master_candidate_uuids,
1431 self._potential_master_candidates,
1432 from_authorized_keys=False,
1433 from_public_keys=False,
1434 clear_authorized_keys=True,
1435 clear_public_keys=True,
1436 pub_key_file=self._pub_key_file,
1437 ssconf_store=self._ssconf_mock,
1438 noded_cert_file=self.noded_cert_file,
1439 run_cmd_fn=self._run_cmd_mock)
1440
1441 self._ssh_file_manager.AssertNoNodeHasPublicKey(
1442 node_info.uuid, node_info.key)
1443 self._ssh_file_manager.AssertNodeSetOnlyHasAuthorizedKey(
1444 [node_name], node_info.key)
1445 self.assertEqual(0,
1446 len(self._ssh_file_manager.GetPublicKeysOfNode(node_name)))
1447 self.assertEqual(1,
1448 len(self._ssh_file_manager.GetAuthorizedKeysOfNode(node_name)))
1449
1450 def testDemoteMasterCandidateToPotentialMasterCandidate(self):
1451 node_name, node_info = self._ssh_file_manager.GetAllMasterCandidates()[0]
1452 self._ssh_file_manager.SetOrAddNode(
1453 node_name, node_info.uuid, node_info.key,
1454 node_info.is_potential_master_candidate, False, node_info.is_master)
1455
1456 backend.RemoveNodeSshKey(node_info.uuid, node_name,
1457 self._master_candidate_uuids,
1458 self._potential_master_candidates,
1459 from_authorized_keys=True,
1460 from_public_keys=False,
1461 clear_authorized_keys=False,
1462 clear_public_keys=False,
1463 pub_key_file=self._pub_key_file,
1464 ssconf_store=self._ssconf_mock,
1465 noded_cert_file=self.noded_cert_file,
1466 run_cmd_fn=self._run_cmd_mock)
1467
1468 self._ssh_file_manager.AssertPotentialMasterCandidatesOnlyHavePublicKey(
1469 node_name)
1470 self._ssh_file_manager.AssertNodeSetOnlyHasAuthorizedKey(
1471 [node_name], node_info.key)
1472
1473 def testDemotePotentialMasterCandidateToNormalNode(self):
1474 (node_name, node_info) = \
1475 self._ssh_file_manager.GetAllPurePotentialMasterCandidates()[0]
1476 self._ssh_file_manager.SetOrAddNode(
1477 node_name, node_info.uuid, node_info.key, False,
1478 node_info.is_master_candidate, node_info.is_master)
1479
1480 backend.RemoveNodeSshKey(node_info.uuid, node_name,
1481 self._master_candidate_uuids,
1482 self._potential_master_candidates,
1483 from_authorized_keys=False,
1484 from_public_keys=True,
1485 clear_authorized_keys=False,
1486 clear_public_keys=False,
1487 pub_key_file=self._pub_key_file,
1488 ssconf_store=self._ssconf_mock,
1489 noded_cert_file=self.noded_cert_file,
1490 run_cmd_fn=self._run_cmd_mock)
1491
1492 self._ssh_file_manager.AssertNoNodeHasPublicKey(
1493 node_info.uuid, node_info.key)
1494 self._ssh_file_manager.AssertNodeSetOnlyHasAuthorizedKey(
1495 [node_name], node_info.key)
1496
1497 def _GetReducedOnlineNodeList(self):
1498 """'Randomly' mark some nodes as offline."""
1499 return [name for name in self._all_nodes
1500 if '3' not in name and '5' not in name]
1501
1502 def testAddKeyWithOfflineNodes(self):
1503 (new_node_name, new_node_uuid, new_node_key, is_master_candidate,
1504 is_potential_master_candidate, is_master) = self._GetNewMasterCandidate()
1505
1506 self._AddNewNodeToTestData(
1507 new_node_name, new_node_uuid, new_node_key,
1508 is_potential_master_candidate, is_master_candidate,
1509 is_master)
1510 self._online_nodes = self._GetReducedOnlineNodeList()
1511 self._ssconf_mock.GetOnlineNodeList.side_effect = \
1512 lambda : self._online_nodes
1513
1514 backend.AddNodeSshKey(new_node_uuid, new_node_name,
1515 self._potential_master_candidates,
1516 to_authorized_keys=is_master_candidate,
1517 to_public_keys=is_potential_master_candidate,
1518 get_public_keys=is_potential_master_candidate,
1519 pub_key_file=self._pub_key_file,
1520 ssconf_store=self._ssconf_mock,
1521 noded_cert_file=self.noded_cert_file,
1522 run_cmd_fn=self._run_cmd_mock)
1523
1524 for node in self._all_nodes:
1525 if node in self._online_nodes:
1526 self.assertTrue(self._ssh_file_manager.NodeHasAuthorizedKey(
1527 node, new_node_key))
1528 else:
1529 self.assertFalse(self._ssh_file_manager.NodeHasAuthorizedKey(
1530 node, new_node_key))
1531
1532 def testRemoveKeyWithOfflineNodes(self):
1533 (node_name, node_info) = \
1534 self._ssh_file_manager.GetAllMasterCandidates()[0]
1535 self._online_nodes = self._GetReducedOnlineNodeList()
1536 self._ssconf_mock.GetOnlineNodeList.side_effect = \
1537 lambda : self._online_nodes
1538
1539 backend.RemoveNodeSshKey(node_info.uuid, node_name,
1540 self._master_candidate_uuids,
1541 self._potential_master_candidates,
1542 from_authorized_keys=True,
1543 from_public_keys=True,
1544 clear_authorized_keys=True,
1545 clear_public_keys=True,
1546 pub_key_file=self._pub_key_file,
1547 ssconf_store=self._ssconf_mock,
1548 noded_cert_file=self.noded_cert_file,
1549 run_cmd_fn=self._run_cmd_mock)
1550
1551 offline_nodes = [node for node in self._all_nodes
1552 if node not in self._online_nodes]
1553 self._ssh_file_manager.AssertNodeSetOnlyHasAuthorizedKey(
1554 offline_nodes + [node_name], node_info.key)
1555
1556 def testAddKeySuccessfullyOnNewNodeWithRetries(self):
1557 """Tests adding a new node's key when updating that node takes retries.
1558
1559 This test checks whether adding a new node's key successfully updates
1560 the SSH key files of all nodes, even if updating the new node's key files
1561 itself takes a couple of retries to succeed.
1562
1563 """
1564 (new_node_name, new_node_uuid, new_node_key, is_master_candidate,
1565 is_potential_master_candidate, is_master) = self._GetNewMasterCandidate()
1566
1567 self._AddNewNodeToTestData(
1568 new_node_name, new_node_uuid, new_node_key,
1569 is_potential_master_candidate, is_master_candidate,
1570 is_master)
1571 self._ssh_file_manager.SetMaxRetries(
1572 new_node_name, constants.SSHS_MAX_RETRIES)
1573
1574 backend.AddNodeSshKey(new_node_uuid, new_node_name,
1575 self._potential_master_candidates,
1576 to_authorized_keys=is_master_candidate,
1577 to_public_keys=is_potential_master_candidate,
1578 get_public_keys=is_potential_master_candidate,
1579 pub_key_file=self._pub_key_file,
1580 ssconf_store=self._ssconf_mock,
1581 noded_cert_file=self.noded_cert_file,
1582 run_cmd_fn=self._run_cmd_mock)
1583
1584 self._ssh_file_manager.AssertPotentialMasterCandidatesOnlyHavePublicKey(
1585 new_node_name)
1586 self._ssh_file_manager.AssertAllNodesHaveAuthorizedKey(
1587 new_node_key)
1588
1589 def testAddKeyFailedOnNewNodeWithRetries(self):
1590 """Tests clean up if updating a new node's SSH setup fails.
1591
1592 If adding the keys of a new node fails, because updating the SSH key files
1593 of that new node fails, check whether already carried out operations are
1594 successfully rolled back and thus the state of the cluster is cleaned up.
1595
1596 """
1597 (new_node_name, new_node_uuid, new_node_key, is_master_candidate,
1598 is_potential_master_candidate, is_master) = self._GetNewMasterCandidate()
1599
1600 self._AddNewNodeToTestData(
1601 new_node_name, new_node_uuid, new_node_key,
1602 is_potential_master_candidate, is_master_candidate,
1603 is_master)
1604 self._ssh_file_manager.SetMaxRetries(
1605 new_node_name, constants.SSHS_MAX_RETRIES + 1)
1606
1607 self.assertRaises(
1608 errors.SshUpdateError, backend.AddNodeSshKey, new_node_uuid,
1609 new_node_name, self._potential_master_candidates,
1610 to_authorized_keys=is_master_candidate,
1611 to_public_keys=is_potential_master_candidate,
1612 get_public_keys=is_potential_master_candidate,
1613 pub_key_file=self._pub_key_file,
1614 ssconf_store=self._ssconf_mock,
1615 noded_cert_file=self.noded_cert_file,
1616 run_cmd_fn=self._run_cmd_mock)
1617
1618 master_node = self._ssh_file_manager.GetMasterNodeName()
1619 for node in self._all_nodes:
1620 if node in [new_node_name, master_node]:
1621 self.assertTrue(self._ssh_file_manager.NodeHasAuthorizedKey(
1622 node, new_node_key))
1623 else:
1624 self.assertFalse(self._ssh_file_manager.NodeHasAuthorizedKey(
1625 node, new_node_key))
1626
1627 self._ssh_file_manager.AssertNoNodeHasPublicKey(new_node_uuid, new_node_key)
1628
1629 def testAddKeySuccessfullyOnOldNodeWithRetries(self):
1630 """Tests adding a new key even if updating nodes takes retries.
1631
1632 This tests whether adding a new node's key successfully finishes,
1633 even if one of the other cluster nodes takes a couple of retries
1634 to succeed.
1635
1636 """
1637 (new_node_name, new_node_uuid, new_node_key, is_master_candidate,
1638 is_potential_master_candidate, is_master) = self._GetNewMasterCandidate()
1639
1640 other_node_name, _ = self._ssh_file_manager.GetAllMasterCandidates()[0]
1641 self._ssh_file_manager.SetMaxRetries(
1642 other_node_name, constants.SSHS_MAX_RETRIES)
1643 assert other_node_name != new_node_name
1644 self._AddNewNodeToTestData(
1645 new_node_name, new_node_uuid, new_node_key,
1646 is_potential_master_candidate, is_master_candidate,
1647 is_master)
1648
1649 backend.AddNodeSshKey(new_node_uuid, new_node_name,
1650 self._potential_master_candidates,
1651 to_authorized_keys=is_master_candidate,
1652 to_public_keys=is_potential_master_candidate,
1653 get_public_keys=is_potential_master_candidate,
1654 pub_key_file=self._pub_key_file,
1655 ssconf_store=self._ssconf_mock,
1656 noded_cert_file=self.noded_cert_file,
1657 run_cmd_fn=self._run_cmd_mock)
1658
1659 self._ssh_file_manager.AssertAllNodesHaveAuthorizedKey(new_node_key)
1660
1661 def testAddKeyFailedOnOldNodeWithRetries(self):
1662 """Tests adding keys when updating one node's SSH setup fails.
1663
1664 This tests whether when adding a new node's key and one node is
1665 unreachable (but not marked as offline) the operation still finishes
1666 properly and only that unreachable node's SSH key setup did not get
1667 updated.
1668
1669 """
1670 (new_node_name, new_node_uuid, new_node_key, is_master_candidate,
1671 is_potential_master_candidate, is_master) = self._GetNewMasterCandidate()
1672
1673 other_node_name, _ = self._ssh_file_manager.GetAllMasterCandidates()[0]
1674 self._ssh_file_manager.SetMaxRetries(
1675 other_node_name, constants.SSHS_MAX_RETRIES + 1)
1676 assert other_node_name != new_node_name
1677 self._AddNewNodeToTestData(
1678 new_node_name, new_node_uuid, new_node_key,
1679 is_potential_master_candidate, is_master_candidate,
1680 is_master)
1681
1682 node_errors = backend.AddNodeSshKey(
1683 new_node_uuid, new_node_name, self._potential_master_candidates,
1684 to_authorized_keys=is_master_candidate,
1685 to_public_keys=is_potential_master_candidate,
1686 get_public_keys=is_potential_master_candidate,
1687 pub_key_file=self._pub_key_file,
1688 ssconf_store=self._ssconf_mock,
1689 noded_cert_file=self.noded_cert_file,
1690 run_cmd_fn=self._run_cmd_mock)
1691
1692 rest_nodes = [node for node in self._all_nodes
1693 if node != other_node_name]
1694 rest_nodes.append(new_node_name)
1695 self._ssh_file_manager.AssertNodeSetOnlyHasAuthorizedKey(
1696 rest_nodes, new_node_key)
1697 self.assertTrue([error_msg for (node, error_msg) in node_errors
1698 if node == other_node_name])
1699
1700 def testRemoveKeySuccessfullyWithRetriesOnOtherNode(self):
1701 """Test removing keys even if one of the old nodes needs retries.
1702
1703 This tests checks whether a key can be removed successfully even
1704 when one of the other nodes needs to be contacted with several
1705 retries.
1706
1707 """
1708 all_master_candidates = self._ssh_file_manager.GetAllMasterCandidates()
1709 node_name, node_info = all_master_candidates[0]
1710 other_node_name, _ = all_master_candidates[1]
1711 assert node_name != self._master_node
1712 assert other_node_name != self._master_node
1713 assert node_name != other_node_name
1714 self._ssh_file_manager.SetMaxRetries(
1715 other_node_name, constants.SSHS_MAX_RETRIES)
1716
1717 backend.RemoveNodeSshKey(node_info.uuid, node_name,
1718 self._master_candidate_uuids,
1719 self._potential_master_candidates,
1720 from_authorized_keys=True,
1721 from_public_keys=True,
1722 clear_authorized_keys=True,
1723 clear_public_keys=True,
1724 pub_key_file=self._pub_key_file,
1725 ssconf_store=self._ssconf_mock,
1726 noded_cert_file=self.noded_cert_file,
1727 run_cmd_fn=self._run_cmd_mock)
1728
1729 self._ssh_file_manager.AssertNoNodeHasPublicKey(
1730 node_info.uuid, node_info.key)
1731 self._ssh_file_manager.AssertNodeSetOnlyHasAuthorizedKey(
1732 [node_name], node_info.key)
1733
1734 def testRemoveKeyFailedWithRetriesOnOtherNode(self):
1735 """Test removing keys even if one of the old nodes fails even with retries.
1736
1737 This tests checks whether the removal of a key finishes properly, even if
1738 the update of the key files on one of the other nodes fails despite several
1739 retries.
1740
1741 """
1742 all_master_candidates = self._ssh_file_manager.GetAllMasterCandidates()
1743 node_name, node_info = all_master_candidates[0]
1744 other_node_name, _ = all_master_candidates[1]
1745 assert node_name != self._master_node
1746 assert other_node_name != self._master_node
1747 assert node_name != other_node_name
1748 self._ssh_file_manager.SetMaxRetries(
1749 other_node_name, constants.SSHS_MAX_RETRIES + 1)
1750
1751 error_msgs = backend.RemoveNodeSshKey(
1752 node_info.uuid, node_name, self._master_candidate_uuids,
1753 self._potential_master_candidates,
1754 from_authorized_keys=True, from_public_keys=True,
1755 clear_authorized_keys=True, clear_public_keys=True,
1756 pub_key_file=self._pub_key_file, ssconf_store=self._ssconf_mock,
1757 noded_cert_file=self.noded_cert_file, run_cmd_fn=self._run_cmd_mock)
1758
1759 self._ssh_file_manager.AssertNodeSetOnlyHasAuthorizedKey(
1760 [other_node_name, node_name], node_info.key)
1761 self.assertTrue([error_msg for (node, error_msg) in error_msgs
1762 if node == other_node_name])
1763
1764 def testRemoveKeySuccessfullyWithRetriesOnTargetNode(self):
1765 """Test removing keys even if the target nodes needs retries.
1766
1767 This tests checks whether a key can be removed successfully even
1768 when removing the key on the node itself needs retries.
1769
1770 """
1771 all_master_candidates = self._ssh_file_manager.GetAllMasterCandidates()
1772 node_name, node_info = all_master_candidates[0]
1773 assert node_name != self._master_node
1774 self._ssh_file_manager.SetMaxRetries(
1775 node_name, constants.SSHS_MAX_RETRIES)
1776
1777 backend.RemoveNodeSshKey(node_info.uuid, node_name,
1778 self._master_candidate_uuids,
1779 self._potential_master_candidates,
1780 from_authorized_keys=True,
1781 from_public_keys=True,
1782 clear_authorized_keys=True,
1783 clear_public_keys=True,
1784 pub_key_file=self._pub_key_file,
1785 ssconf_store=self._ssconf_mock,
1786 noded_cert_file=self.noded_cert_file,
1787 run_cmd_fn=self._run_cmd_mock)
1788
1789 self._ssh_file_manager.AssertNoNodeHasPublicKey(
1790 node_info.uuid, node_info.key)
1791 self._ssh_file_manager.AssertNodeSetOnlyHasAuthorizedKey(
1792 [node_name], node_info.key)
1793
1794 def testRemoveKeyFailedWithRetriesOnTargetNode(self):
1795 """Test removing keys even if contacting the node fails with retries.
1796
1797 This tests checks whether the removal of a key finishes properly, even if
1798 the update of the key files on the node itself fails despite several
1799 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 + 1)
1807
1808 error_msgs = backend.RemoveNodeSshKey(
1809 node_info.uuid, node_name, self._master_candidate_uuids,
1810 self._potential_master_candidates,
1811 from_authorized_keys=True, from_public_keys=True,
1812 clear_authorized_keys=True, clear_public_keys=True,
1813 pub_key_file=self._pub_key_file, ssconf_store=self._ssconf_mock,
1814 noded_cert_file=self.noded_cert_file, run_cmd_fn=self._run_cmd_mock)
1815
1816 self._ssh_file_manager.AssertNodeSetOnlyHasAuthorizedKey(
1817 [node_name], node_info.key)
1818 self.assertTrue([error_msg for (node, error_msg) in error_msgs
1819 if node == node_name])
1820
1821
1822 class TestVerifySshSetup(testutils.GanetiTestCase):
1823
1824 _NODE1_UUID = "uuid1"
1825 _NODE2_UUID = "uuid2"
1826 _NODE3_UUID = "uuid3"
1827 _NODE1_NAME = "name1"
1828 _NODE2_NAME = "name2"
1829 _NODE3_NAME = "name3"
1830 _NODE1_KEYS = ["key11"]
1831 _NODE2_KEYS = ["key21"]
1832 _NODE3_KEYS = ["key31"]
1833
1834 _NODE_STATUS_LIST = [
1835 (_NODE1_UUID, _NODE1_NAME, True, True, True),
1836 (_NODE2_UUID, _NODE2_NAME, False, True, True),
1837 (_NODE3_UUID, _NODE3_NAME, False, False, True),
1838 ]
1839
1840 _PUB_KEY_RESULT = {
1841 _NODE1_UUID: _NODE1_KEYS,
1842 _NODE2_UUID: _NODE2_KEYS,
1843 _NODE3_UUID: _NODE3_KEYS,
1844 }
1845
1846 _AUTH_RESULT = {
1847 _NODE1_KEYS[0]: True,
1848 _NODE2_KEYS[0]: False,
1849 _NODE3_KEYS[0]: False,
1850 }
1851
1852 def setUp(self):
1853 testutils.GanetiTestCase.setUp(self)
1854 self._has_authorized_patcher = testutils \
1855 .patch_object(ssh, "HasAuthorizedKey")
1856 self._has_authorized_mock = self._has_authorized_patcher.start()
1857 self._query_patcher = testutils \
1858 .patch_object(ssh, "QueryPubKeyFile")
1859 self._query_mock = self._query_patcher.start()
1860 self._read_file_patcher = testutils \
1861 .patch_object(utils, "ReadFile")
1862 self._read_file_mock = self._read_file_patcher.start()
1863 self._read_file_mock.return_value = self._NODE1_KEYS[0]
1864 self.tmpdir = tempfile.mkdtemp()
1865 self.pub_key_file = os.path.join(self.tmpdir, "pub_key_file")
1866 open(self.pub_key_file, "w").close()
1867
1868 def tearDown(self):
1869 super(testutils.GanetiTestCase, self).tearDown()
1870 self._has_authorized_patcher.stop()
1871 self._query_patcher.stop()
1872 self._read_file_patcher.stop()
1873 shutil.rmtree(self.tmpdir)
1874
1875 def testValidData(self):
1876 self._has_authorized_mock.side_effect = \
1877 lambda _, key : self._AUTH_RESULT[key]
1878 self._query_mock.return_value = self._PUB_KEY_RESULT
1879 result = backend._VerifySshSetup(self._NODE_STATUS_LIST,
1880 self._NODE1_NAME,
1881 pub_key_file=self.pub_key_file)
1882 self.assertEqual(result, [])
1883
1884 def testMissingKey(self):
1885 self._has_authorized_mock.side_effect = \
1886 lambda _, key : self._AUTH_RESULT[key]
1887 pub_key_missing = copy.deepcopy(self._PUB_KEY_RESULT)
1888 del pub_key_missing[self._NODE2_UUID]
1889 self._query_mock.return_value = pub_key_missing
1890 result = backend._VerifySshSetup(self._NODE_STATUS_LIST,
1891 self._NODE1_NAME,
1892 pub_key_file=self.pub_key_file)
1893 self.assertTrue(self._NODE2_UUID in result[0])
1894
1895 def testUnknownKey(self):
1896 self._has_authorized_mock.side_effect = \
1897 lambda _, key : self._AUTH_RESULT[key]
1898 pub_key_missing = copy.deepcopy(self._PUB_KEY_RESULT)
1899 pub_key_missing["unkownnodeuuid"] = "pinkbunny"
1900 self._query_mock.return_value = pub_key_missing
1901 result = backend._VerifySshSetup(self._NODE_STATUS_LIST,
1902 self._NODE1_NAME,
1903 pub_key_file=self.pub_key_file)
1904 self.assertTrue("unkownnodeuuid" in result[0])
1905
1906 def testMissingMasterCandidate(self):
1907 auth_result = copy.deepcopy(self._AUTH_RESULT)
1908 auth_result["key11"] = False
1909 self._has_authorized_mock.side_effect = \
1910 lambda _, key : auth_result[key]
1911 self._query_mock.return_value = self._PUB_KEY_RESULT
1912 result = backend._VerifySshSetup(self._NODE_STATUS_LIST,
1913 self._NODE1_NAME,
1914 pub_key_file=self.pub_key_file)
1915 self.assertTrue(self._NODE1_UUID in result[0])
1916
1917 def testSuperfluousNormalNode(self):
1918 auth_result = copy.deepcopy(self._AUTH_RESULT)
1919 auth_result["key31"] = True
1920 self._has_authorized_mock.side_effect = \
1921 lambda _, key : auth_result[key]
1922 self._query_mock.return_value = self._PUB_KEY_RESULT
1923 result = backend._VerifySshSetup(self._NODE_STATUS_LIST,
1924 self._NODE1_NAME,
1925 pub_key_file=self.pub_key_file)
1926 self.assertTrue(self._NODE3_UUID in result[0])
1927
1928
1929 class TestOSEnvironment(unittest.TestCase):
1930 """Ensure the presence of public and private parameters.
1931
1932 They have to be present inside os environment variables.
1933
1934 """
1935
1936 def _CreateEnv(self):
1937 """Create and return an environment."""
1938 config_mock = ConfigMock()
1939 inst = config_mock.AddNewInstance(
1940 osparams={"public_param": "public_info"},
1941 osparams_private=serializer.PrivateDict({"private_param":
1942 "private_info",
1943 "another_private_param":
1944 "more_privacy"}),
1945 nics = [])
1946 inst.disks_info = ""
1947 inst.secondary_nodes = []
1948
1949 return backend.OSEnvironment(inst, config_mock.CreateOs())
1950
1951 def testParamPresence(self):
1952 env = self._CreateEnv()
1953 env_keys = env.keys()
1954 self.assertTrue("OSP_PUBLIC_PARAM" in env)
1955 self.assertTrue("OSP_PRIVATE_PARAM" in env)
1956 self.assertTrue("OSP_ANOTHER_PRIVATE_PARAM" in env)
1957 self.assertEqual("public_info", env["OSP_PUBLIC_PARAM"])
1958 self.assertEqual("private_info", env["OSP_PRIVATE_PARAM"])
1959 self.assertEqual("more_privacy", env["OSP_ANOTHER_PRIVATE_PARAM"])
1960
1961
1962 if __name__ == "__main__":
1963 testutils.GanetiTestProgram()