Use bulk-adding of keys in renew-crypto
[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
1035 def _TearDownTestData(self):
1036 os.remove(self._pub_key_file)
1037
1038 def _GetCallsPerNode(self):
1039 calls_per_node = {}
1040 for (pos, keyword) in self._run_cmd_mock.call_args_list:
1041 (cluster_name, node, _, _, data) = pos
1042 if not node in calls_per_node:
1043 calls_per_node[node] = []
1044 calls_per_node[node].append(data)
1045 return calls_per_node
1046
1047 def testGenerateKey(self):
1048 test_node_name = "node_name_7"
1049 test_node_uuid = "node_uuid_7"
1050
1051 self._SetupTestData()
1052 ssh.AddPublicKey(test_node_uuid, "some_old_key",
1053 key_file=self._pub_key_file)
1054
1055 backend._GenerateNodeSshKey(
1056 test_node_uuid, test_node_name,
1057 self._ssh_file_manager.GetSshPortMap(self._SSH_PORT),
1058 pub_key_file=self._pub_key_file,
1059 ssconf_store=self._ssconf_mock,
1060 noded_cert_file=self.noded_cert_file,
1061 run_cmd_fn=self._run_cmd_mock)
1062
1063 calls_per_node = self._GetCallsPerNode()
1064 for node, calls in calls_per_node.items():
1065 self.assertEquals(node, test_node_name)
1066 for call in calls:
1067 self.assertTrue(constants.SSHS_GENERATE in call)
1068
1069 def _AddNewNodeToTestData(self, name, uuid, key, pot_mc, mc, master):
1070 self._ssh_file_manager.SetOrAddNode(name, uuid, key, pot_mc, mc, master)
1071
1072 if pot_mc:
1073 ssh.AddPublicKey(name, key, key_file=self._pub_key_file)
1074 self._potential_master_candidates.append(name)
1075
1076 self._ssconf_mock.GetSshPortMap.return_value = \
1077 self._ssh_file_manager.GetSshPortMap(self._SSH_PORT)
1078
1079 def _GetNewMasterCandidate(self):
1080 """Returns the properties of a new master candidate node."""
1081 return ("new_node_name", "new_node_uuid", "new_node_key",
1082 True, True, False)
1083
1084 def _GetNewNumberedMasterCandidate(self, num):
1085 """Returns the properties of a new master candidate node."""
1086 return ("new_node_name_%s" % num,
1087 "new_node_uuid_%s" % num,
1088 "new_node_key_%s" % num,
1089 True, True, False)
1090
1091 def _GetNewNumberedPotentialMasterCandidate(self, num):
1092 """Returns the properties of a new potential master candidate node."""
1093 return ("new_node_name_%s" % num,
1094 "new_node_uuid_%s" % num,
1095 "new_node_key_%s" % num,
1096 False, True, False)
1097
1098 def _GetNewNumberedNormalNode(self, num):
1099 """Returns the properties of a new normal node."""
1100 return ("new_node_name_%s" % num,
1101 "new_node_uuid_%s" % num,
1102 "new_node_key_%s" % num,
1103 False, False, False)
1104
1105 def testAddMasterCandidate(self):
1106 (new_node_name, new_node_uuid, new_node_key, is_master_candidate,
1107 is_potential_master_candidate, is_master) = self._GetNewMasterCandidate()
1108
1109 self._AddNewNodeToTestData(
1110 new_node_name, new_node_uuid, new_node_key,
1111 is_potential_master_candidate, is_master_candidate,
1112 is_master)
1113
1114 backend.AddNodeSshKey(new_node_uuid, new_node_name,
1115 self._potential_master_candidates,
1116 to_authorized_keys=is_master_candidate,
1117 to_public_keys=is_potential_master_candidate,
1118 get_public_keys=is_potential_master_candidate,
1119 pub_key_file=self._pub_key_file,
1120 ssconf_store=self._ssconf_mock,
1121 noded_cert_file=self.noded_cert_file,
1122 run_cmd_fn=self._run_cmd_mock)
1123
1124 self._ssh_file_manager.AssertPotentialMasterCandidatesOnlyHavePublicKey(
1125 new_node_name)
1126 self._ssh_file_manager.AssertAllNodesHaveAuthorizedKey(new_node_key)
1127
1128 def _SetupNodeBulk(self, num_nodes, node_fn):
1129 """Sets up the test data for a bulk of nodes.
1130
1131 @param num_nodes: number of nodes
1132 @type num_nodes: integer
1133 @param node_fn: function
1134 @param node_fn: function to generate data of one node, taking an
1135 integer as only argument
1136
1137 """
1138 node_list = []
1139 key_map = {}
1140
1141 for i in range(num_nodes):
1142 (new_node_name, new_node_uuid, new_node_key, is_master_candidate,
1143 is_potential_master_candidate, is_master) = \
1144 node_fn(i)
1145
1146 self._AddNewNodeToTestData(
1147 new_node_name, new_node_uuid, new_node_key,
1148 is_potential_master_candidate, is_master_candidate,
1149 is_master)
1150
1151 node_list.append(
1152 backend.SshAddNodeInfo(
1153 uuid=new_node_uuid,
1154 name=new_node_name,
1155 to_authorized_keys=is_master_candidate,
1156 to_public_keys=is_potential_master_candidate,
1157 get_public_keys=is_potential_master_candidate))
1158
1159 key_map[new_node_name] = new_node_key
1160
1161 return (node_list, key_map)
1162
1163 def testAddMasterCandidateBulk(self):
1164 num_nodes = 3
1165 (node_list, key_map) = self._SetupNodeBulk(
1166 num_nodes, self._GetNewNumberedMasterCandidate)
1167
1168 backend.AddNodeSshKeyBulk(node_list,
1169 self._potential_master_candidates,
1170 pub_key_file=self._pub_key_file,
1171 ssconf_store=self._ssconf_mock,
1172 noded_cert_file=self.noded_cert_file,
1173 run_cmd_fn=self._run_cmd_mock)
1174
1175 for node_info in node_list:
1176 self._ssh_file_manager.AssertPotentialMasterCandidatesOnlyHavePublicKey(
1177 node_info.name)
1178 self._ssh_file_manager.AssertAllNodesHaveAuthorizedKey(
1179 key_map[node_info.name])
1180
1181 def testAddPotentialMasterCandidateBulk(self):
1182 num_nodes = 3
1183 (node_list, key_map) = self._SetupNodeBulk(
1184 num_nodes, self._GetNewNumberedPotentialMasterCandidate)
1185
1186 backend.AddNodeSshKeyBulk(node_list,
1187 self._potential_master_candidates,
1188 pub_key_file=self._pub_key_file,
1189 ssconf_store=self._ssconf_mock,
1190 noded_cert_file=self.noded_cert_file,
1191 run_cmd_fn=self._run_cmd_mock)
1192
1193 for node_info in node_list:
1194 self._ssh_file_manager.AssertPotentialMasterCandidatesOnlyHavePublicKey(
1195 node_info.name)
1196 self._ssh_file_manager.AssertNoNodeHasAuthorizedKey(
1197 key_map[node_info.name])
1198
1199 def testAddPotentialMasterCandidate(self):
1200 new_node_name = "new_node_name"
1201 new_node_uuid = "new_node_uuid"
1202 new_node_key = "new_node_key"
1203 is_master_candidate = False
1204 is_potential_master_candidate = True
1205 is_master = False
1206
1207 self._AddNewNodeToTestData(
1208 new_node_name, new_node_uuid, new_node_key,
1209 is_potential_master_candidate, is_master_candidate,
1210 is_master)
1211
1212 backend.AddNodeSshKey(new_node_uuid, new_node_name,
1213 self._potential_master_candidates,
1214 to_authorized_keys=is_master_candidate,
1215 to_public_keys=is_potential_master_candidate,
1216 get_public_keys=is_potential_master_candidate,
1217 pub_key_file=self._pub_key_file,
1218 ssconf_store=self._ssconf_mock,
1219 noded_cert_file=self.noded_cert_file,
1220 run_cmd_fn=self._run_cmd_mock)
1221
1222 self._ssh_file_manager.AssertPotentialMasterCandidatesOnlyHavePublicKey(
1223 new_node_name)
1224 self._ssh_file_manager.AssertNoNodeHasAuthorizedKey(new_node_key)
1225
1226 def testAddNormalNode(self):
1227 new_node_name = "new_node_name"
1228 new_node_uuid = "new_node_uuid"
1229 new_node_key = "new_node_key"
1230 is_master_candidate = False
1231 is_potential_master_candidate = False
1232 is_master = False
1233
1234 self._AddNewNodeToTestData(
1235 new_node_name, new_node_uuid, new_node_key,
1236 is_potential_master_candidate, is_master_candidate,
1237 is_master)
1238
1239 self.assertRaises(
1240 AssertionError, backend.AddNodeSshKey, new_node_uuid, new_node_name,
1241 self._potential_master_candidates,
1242 to_authorized_keys=is_master_candidate,
1243 to_public_keys=is_potential_master_candidate,
1244 get_public_keys=is_potential_master_candidate,
1245 pub_key_file=self._pub_key_file,
1246 ssconf_store=self._ssconf_mock,
1247 noded_cert_file=self.noded_cert_file,
1248 run_cmd_fn=self._run_cmd_mock)
1249
1250 self._ssh_file_manager.AssertNoNodeHasPublicKey(new_node_uuid, new_node_key)
1251 self._ssh_file_manager.AssertNoNodeHasAuthorizedKey(new_node_key)
1252
1253 def testAddNormalBulk(self):
1254 num_nodes = 3
1255 (node_list, key_map) = self._SetupNodeBulk(
1256 num_nodes, self._GetNewNumberedNormalNode)
1257
1258 self.assertRaises(
1259 AssertionError, backend.AddNodeSshKeyBulk, node_list,
1260 self._potential_master_candidates,
1261 pub_key_file=self._pub_key_file,
1262 ssconf_store=self._ssconf_mock,
1263 noded_cert_file=self.noded_cert_file,
1264 run_cmd_fn=self._run_cmd_mock)
1265
1266 for node_info in node_list:
1267 self._ssh_file_manager.AssertNoNodeHasPublicKey(
1268 node_info.uuid, key_map[node_info.name])
1269 self._ssh_file_manager.AssertNoNodeHasAuthorizedKey(
1270 key_map[node_info.name])
1271
1272 def _GetNewNumberedNode(self, num):
1273 """Returns the properties of a node.
1274
1275 This will in round-robin style return a master candidate, a
1276 potential master candiate and a normal node.
1277
1278 """
1279 is_master_candidate = num % 3 == 0
1280 is_potential_master_candidate = num % 3 == 0 or num % 3 == 1
1281 is_master = False
1282 return ("new_node_name_%s" % num,
1283 "new_node_uuid_%s" % num,
1284 "new_node_key_%s" % num,
1285 is_master_candidate, is_potential_master_candidate, is_master)
1286
1287 def testAddDiverseNodeBulk(self):
1288 """Tests adding keys of several nodes with several qualities.
1289
1290 This tests subsumes previous tests. However, we leave the previous
1291 tests here, because debugging problems with this all-embracing test
1292 is much more tedious than having one of the one-purpose tests fail.
1293
1294 """
1295 num_nodes = 9
1296 (node_list, key_map) = self._SetupNodeBulk(
1297 num_nodes, self._GetNewNumberedNode)
1298
1299 backend.AddNodeSshKeyBulk(node_list,
1300 self._potential_master_candidates,
1301 pub_key_file=self._pub_key_file,
1302 ssconf_store=self._ssconf_mock,
1303 noded_cert_file=self.noded_cert_file,
1304 run_cmd_fn=self._run_cmd_mock)
1305
1306 for node_info in node_list:
1307 if node_info.to_authorized_keys:
1308 self._ssh_file_manager.AssertAllNodesHaveAuthorizedKey(
1309 key_map[node_info.name])
1310 else:
1311 self._ssh_file_manager.AssertNoNodeHasAuthorizedKey(
1312 key_map[node_info.name])
1313 if node_info.to_public_keys:
1314 self._ssh_file_manager.AssertPotentialMasterCandidatesOnlyHavePublicKey(
1315 node_info.name)
1316 else:
1317 self._ssh_file_manager.AssertNoNodeHasPublicKey(
1318 node_info.uuid, key_map[node_info.name])
1319
1320 def testPromoteToMasterCandidate(self):
1321 # Get one of the potential master candidates
1322 node_name, node_info = \
1323 self._ssh_file_manager.GetAllPurePotentialMasterCandidates()[0]
1324 # Update it's role to master candidate in the test data
1325 self._ssh_file_manager.SetOrAddNode(
1326 node_name, node_info.uuid, node_info.key,
1327 node_info.is_potential_master_candidate, True, node_info.is_master)
1328
1329 backend.AddNodeSshKey(node_info.uuid, node_name,
1330 self._potential_master_candidates,
1331 to_authorized_keys=True,
1332 to_public_keys=False,
1333 get_public_keys=False,
1334 pub_key_file=self._pub_key_file,
1335 ssconf_store=self._ssconf_mock,
1336 noded_cert_file=self.noded_cert_file,
1337 run_cmd_fn=self._run_cmd_mock)
1338
1339 self._ssh_file_manager.AssertPotentialMasterCandidatesOnlyHavePublicKey(
1340 node_name)
1341 self._ssh_file_manager.AssertAllNodesHaveAuthorizedKey(node_info.key)
1342
1343 def testRemoveMasterCandidate(self):
1344 node_name, (node_uuid, node_key, is_potential_master_candidate,
1345 is_master_candidate, is_master) = \
1346 self._ssh_file_manager.GetAllMasterCandidates()[0]
1347
1348 backend.RemoveNodeSshKey(node_uuid, node_name,
1349 self._master_candidate_uuids,
1350 self._potential_master_candidates,
1351 from_authorized_keys=True,
1352 from_public_keys=True,
1353 clear_authorized_keys=True,
1354 clear_public_keys=True,
1355 pub_key_file=self._pub_key_file,
1356 ssconf_store=self._ssconf_mock,
1357 noded_cert_file=self.noded_cert_file,
1358 run_cmd_fn=self._run_cmd_mock)
1359
1360 self._ssh_file_manager.AssertNoNodeHasPublicKey(node_uuid, node_key)
1361 self._ssh_file_manager.AssertNodeSetOnlyHasAuthorizedKey(
1362 [node_name], node_key)
1363 self.assertEqual(0,
1364 len(self._ssh_file_manager.GetPublicKeysOfNode(node_name)))
1365 self.assertEqual(1,
1366 len(self._ssh_file_manager.GetAuthorizedKeysOfNode(node_name)))
1367
1368 def testRemovePotentialMasterCandidate(self):
1369 (node_name, node_info) = \
1370 self._ssh_file_manager.GetAllPurePotentialMasterCandidates()[0]
1371
1372 backend.RemoveNodeSshKey(node_info.uuid, node_name,
1373 self._master_candidate_uuids,
1374 self._potential_master_candidates,
1375 from_authorized_keys=False,
1376 from_public_keys=True,
1377 clear_authorized_keys=True,
1378 clear_public_keys=True,
1379 pub_key_file=self._pub_key_file,
1380 ssconf_store=self._ssconf_mock,
1381 noded_cert_file=self.noded_cert_file,
1382 run_cmd_fn=self._run_cmd_mock)
1383
1384 self._ssh_file_manager.AssertNoNodeHasPublicKey(
1385 node_info.uuid, node_info.key)
1386 self._ssh_file_manager.AssertNoNodeHasAuthorizedKey(node_info.key)
1387 self.assertEqual(0,
1388 len(self._ssh_file_manager.GetPublicKeysOfNode(node_name)))
1389 self.assertEqual(0,
1390 len(self._ssh_file_manager.GetAuthorizedKeysOfNode(node_name)))
1391
1392 def testRemoveNormalNode(self):
1393 node_name, node_info = self._ssh_file_manager.GetAllNormalNodes()[0]
1394
1395 backend.RemoveNodeSshKey(node_info.uuid, node_name,
1396 self._master_candidate_uuids,
1397 self._potential_master_candidates,
1398 from_authorized_keys=False,
1399 from_public_keys=False,
1400 clear_authorized_keys=True,
1401 clear_public_keys=True,
1402 pub_key_file=self._pub_key_file,
1403 ssconf_store=self._ssconf_mock,
1404 noded_cert_file=self.noded_cert_file,
1405 run_cmd_fn=self._run_cmd_mock)
1406
1407 self._ssh_file_manager.AssertNoNodeHasPublicKey(
1408 node_info.uuid, node_info.key)
1409 self._ssh_file_manager.AssertNoNodeHasAuthorizedKey(node_info.key)
1410 self.assertEqual(0,
1411 len(self._ssh_file_manager.GetPublicKeysOfNode(node_name)))
1412 self.assertEqual(0,
1413 len(self._ssh_file_manager.GetAuthorizedKeysOfNode(node_name)))
1414
1415 def testDemoteMasterCandidateToPotentialMasterCandidate(self):
1416 node_name, node_info = self._ssh_file_manager.GetAllMasterCandidates()[0]
1417 self._ssh_file_manager.SetOrAddNode(
1418 node_name, node_info.uuid, node_info.key,
1419 node_info.is_potential_master_candidate, False, node_info.is_master)
1420
1421 backend.RemoveNodeSshKey(node_info.uuid, node_name,
1422 self._master_candidate_uuids,
1423 self._potential_master_candidates,
1424 from_authorized_keys=True,
1425 from_public_keys=False,
1426 clear_authorized_keys=False,
1427 clear_public_keys=False,
1428 pub_key_file=self._pub_key_file,
1429 ssconf_store=self._ssconf_mock,
1430 noded_cert_file=self.noded_cert_file,
1431 run_cmd_fn=self._run_cmd_mock)
1432
1433 self._ssh_file_manager.AssertPotentialMasterCandidatesOnlyHavePublicKey(
1434 node_name)
1435 self._ssh_file_manager.AssertNodeSetOnlyHasAuthorizedKey(
1436 [node_name], node_info.key)
1437
1438 def testDemotePotentialMasterCandidateToNormalNode(self):
1439 (node_name, node_info) = \
1440 self._ssh_file_manager.GetAllPurePotentialMasterCandidates()[0]
1441 self._ssh_file_manager.SetOrAddNode(
1442 node_name, node_info.uuid, node_info.key, False,
1443 node_info.is_master_candidate, node_info.is_master)
1444
1445 backend.RemoveNodeSshKey(node_info.uuid, node_name,
1446 self._master_candidate_uuids,
1447 self._potential_master_candidates,
1448 from_authorized_keys=False,
1449 from_public_keys=True,
1450 clear_authorized_keys=False,
1451 clear_public_keys=False,
1452 pub_key_file=self._pub_key_file,
1453 ssconf_store=self._ssconf_mock,
1454 noded_cert_file=self.noded_cert_file,
1455 run_cmd_fn=self._run_cmd_mock)
1456
1457 self._ssh_file_manager.AssertNoNodeHasPublicKey(
1458 node_info.uuid, node_info.key)
1459 self._ssh_file_manager.AssertNoNodeHasAuthorizedKey(node_info.key)
1460
1461 def _GetReducedOnlineNodeList(self):
1462 """'Randomly' mark some nodes as offline."""
1463 return [name for name in self._all_nodes
1464 if '3' not in name and '5' not in name]
1465
1466 def testAddKeyWithOfflineNodes(self):
1467 (new_node_name, new_node_uuid, new_node_key, is_master_candidate,
1468 is_potential_master_candidate, is_master) = self._GetNewMasterCandidate()
1469
1470 self._AddNewNodeToTestData(
1471 new_node_name, new_node_uuid, new_node_key,
1472 is_potential_master_candidate, is_master_candidate,
1473 is_master)
1474 self._online_nodes = self._GetReducedOnlineNodeList()
1475 self._ssconf_mock.GetOnlineNodeList.side_effect = \
1476 lambda : self._online_nodes
1477
1478 backend.AddNodeSshKey(new_node_uuid, new_node_name,
1479 self._potential_master_candidates,
1480 to_authorized_keys=is_master_candidate,
1481 to_public_keys=is_potential_master_candidate,
1482 get_public_keys=is_potential_master_candidate,
1483 pub_key_file=self._pub_key_file,
1484 ssconf_store=self._ssconf_mock,
1485 noded_cert_file=self.noded_cert_file,
1486 run_cmd_fn=self._run_cmd_mock)
1487
1488 for node in self._all_nodes:
1489 if node in self._online_nodes:
1490 self.assertTrue(self._ssh_file_manager.NodeHasAuthorizedKey(
1491 node, new_node_key))
1492 else:
1493 self.assertFalse(self._ssh_file_manager.NodeHasAuthorizedKey(
1494 node, new_node_key))
1495
1496 def testRemoveKeyWithOfflineNodes(self):
1497 (node_name, node_info) = \
1498 self._ssh_file_manager.GetAllMasterCandidates()[0]
1499 self._online_nodes = self._GetReducedOnlineNodeList()
1500 self._ssconf_mock.GetOnlineNodeList.side_effect = \
1501 lambda : self._online_nodes
1502
1503 backend.RemoveNodeSshKey(node_info.uuid, node_name,
1504 self._master_candidate_uuids,
1505 self._potential_master_candidates,
1506 from_authorized_keys=True,
1507 from_public_keys=True,
1508 clear_authorized_keys=True,
1509 clear_public_keys=True,
1510 pub_key_file=self._pub_key_file,
1511 ssconf_store=self._ssconf_mock,
1512 noded_cert_file=self.noded_cert_file,
1513 run_cmd_fn=self._run_cmd_mock)
1514
1515 offline_nodes = [node for node in self._all_nodes
1516 if node not in self._online_nodes]
1517 self._ssh_file_manager.AssertNodeSetOnlyHasAuthorizedKey(
1518 offline_nodes + [node_name], node_info.key)
1519
1520 def testAddKeySuccessfullyOnNewNodeWithRetries(self):
1521 """Tests adding a new node's key when updating that node takes retries.
1522
1523 This test checks whether adding a new node's key successfully updates
1524 the SSH key files of all nodes, even if updating the new node's key files
1525 itself takes a couple of retries to succeed.
1526
1527 """
1528 (new_node_name, new_node_uuid, new_node_key, is_master_candidate,
1529 is_potential_master_candidate, is_master) = self._GetNewMasterCandidate()
1530
1531 self._AddNewNodeToTestData(
1532 new_node_name, new_node_uuid, new_node_key,
1533 is_potential_master_candidate, is_master_candidate,
1534 is_master)
1535 self._ssh_file_manager.SetMaxRetries(
1536 new_node_name, constants.SSHS_MAX_RETRIES)
1537
1538 backend.AddNodeSshKey(new_node_uuid, new_node_name,
1539 self._potential_master_candidates,
1540 to_authorized_keys=is_master_candidate,
1541 to_public_keys=is_potential_master_candidate,
1542 get_public_keys=is_potential_master_candidate,
1543 pub_key_file=self._pub_key_file,
1544 ssconf_store=self._ssconf_mock,
1545 noded_cert_file=self.noded_cert_file,
1546 run_cmd_fn=self._run_cmd_mock)
1547
1548 self._ssh_file_manager.AssertPotentialMasterCandidatesOnlyHavePublicKey(
1549 new_node_name)
1550 self._ssh_file_manager.AssertAllNodesHaveAuthorizedKey(
1551 new_node_key)
1552
1553 def testAddKeyFailedOnNewNodeWithRetries(self):
1554 """Tests clean up if updating a new node's SSH setup fails.
1555
1556 If adding the keys of a new node fails, because updating the SSH key files
1557 of that new node fails, check whether already carried out operations are
1558 successfully rolled back and thus the state of the cluster is cleaned up.
1559
1560 """
1561 (new_node_name, new_node_uuid, new_node_key, is_master_candidate,
1562 is_potential_master_candidate, is_master) = self._GetNewMasterCandidate()
1563
1564 self._AddNewNodeToTestData(
1565 new_node_name, new_node_uuid, new_node_key,
1566 is_potential_master_candidate, is_master_candidate,
1567 is_master)
1568 self._ssh_file_manager.SetMaxRetries(
1569 new_node_name, constants.SSHS_MAX_RETRIES + 1)
1570
1571 self.assertRaises(
1572 errors.SshUpdateError, backend.AddNodeSshKey, new_node_uuid,
1573 new_node_name, self._potential_master_candidates,
1574 to_authorized_keys=is_master_candidate,
1575 to_public_keys=is_potential_master_candidate,
1576 get_public_keys=is_potential_master_candidate,
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 for node in self._all_nodes:
1583 if node == new_node_name:
1584 self.assertTrue(self._ssh_file_manager.NodeHasAuthorizedKey(
1585 node, new_node_key))
1586 else:
1587 self.assertFalse(self._ssh_file_manager.NodeHasAuthorizedKey(
1588 node, new_node_key))
1589
1590 self._ssh_file_manager.AssertNoNodeHasPublicKey(new_node_uuid, new_node_key)
1591
1592 def testAddKeySuccessfullyOnOldNodeWithRetries(self):
1593 """Tests adding a new key even if updating nodes takes retries.
1594
1595 This tests whether adding a new node's key successfully finishes,
1596 even if one of the other cluster nodes takes a couple of retries
1597 to succeed.
1598
1599 """
1600 (new_node_name, new_node_uuid, new_node_key, is_master_candidate,
1601 is_potential_master_candidate, is_master) = self._GetNewMasterCandidate()
1602
1603 other_node_name, _ = self._ssh_file_manager.GetAllMasterCandidates()[0]
1604 self._ssh_file_manager.SetMaxRetries(
1605 other_node_name, constants.SSHS_MAX_RETRIES)
1606 assert other_node_name != new_node_name
1607 self._AddNewNodeToTestData(
1608 new_node_name, new_node_uuid, new_node_key,
1609 is_potential_master_candidate, is_master_candidate,
1610 is_master)
1611
1612 backend.AddNodeSshKey(new_node_uuid, new_node_name,
1613 self._potential_master_candidates,
1614 to_authorized_keys=is_master_candidate,
1615 to_public_keys=is_potential_master_candidate,
1616 get_public_keys=is_potential_master_candidate,
1617 pub_key_file=self._pub_key_file,
1618 ssconf_store=self._ssconf_mock,
1619 noded_cert_file=self.noded_cert_file,
1620 run_cmd_fn=self._run_cmd_mock)
1621
1622 self._ssh_file_manager.AssertAllNodesHaveAuthorizedKey(new_node_key)
1623
1624 def testAddKeyFailedOnOldNodeWithRetries(self):
1625 """Tests adding keys when updating one node's SSH setup fails.
1626
1627 This tests whether when adding a new node's key and one node is
1628 unreachable (but not marked as offline) the operation still finishes
1629 properly and only that unreachable node's SSH key setup did not get
1630 updated.
1631
1632 """
1633 (new_node_name, new_node_uuid, new_node_key, is_master_candidate,
1634 is_potential_master_candidate, is_master) = self._GetNewMasterCandidate()
1635
1636 other_node_name, _ = self._ssh_file_manager.GetAllMasterCandidates()[0]
1637 self._ssh_file_manager.SetMaxRetries(
1638 other_node_name, constants.SSHS_MAX_RETRIES + 1)
1639 assert other_node_name != new_node_name
1640 self._AddNewNodeToTestData(
1641 new_node_name, new_node_uuid, new_node_key,
1642 is_potential_master_candidate, is_master_candidate,
1643 is_master)
1644
1645 node_errors = backend.AddNodeSshKey(
1646 new_node_uuid, new_node_name, self._potential_master_candidates,
1647 to_authorized_keys=is_master_candidate,
1648 to_public_keys=is_potential_master_candidate,
1649 get_public_keys=is_potential_master_candidate,
1650 pub_key_file=self._pub_key_file,
1651 ssconf_store=self._ssconf_mock,
1652 noded_cert_file=self.noded_cert_file,
1653 run_cmd_fn=self._run_cmd_mock)
1654
1655 rest_nodes = [node for node in self._all_nodes
1656 if node != other_node_name]
1657 rest_nodes.append(new_node_name)
1658 self._ssh_file_manager.AssertNodeSetOnlyHasAuthorizedKey(
1659 rest_nodes, new_node_key)
1660 self.assertTrue([error_msg for (node, error_msg) in node_errors
1661 if node == other_node_name])
1662
1663 def testRemoveKeySuccessfullyWithRetriesOnOtherNode(self):
1664 """Test removing keys even if one of the old nodes needs retries.
1665
1666 This tests checks whether a key can be removed successfully even
1667 when one of the other nodes needs to be contacted with several
1668 retries.
1669
1670 """
1671 all_master_candidates = self._ssh_file_manager.GetAllMasterCandidates()
1672 node_name, node_info = all_master_candidates[0]
1673 other_node_name, _ = all_master_candidates[1]
1674 assert node_name != self._master_node
1675 assert other_node_name != self._master_node
1676 assert node_name != other_node_name
1677 self._ssh_file_manager.SetMaxRetries(
1678 other_node_name, constants.SSHS_MAX_RETRIES)
1679
1680 backend.RemoveNodeSshKey(node_info.uuid, node_name,
1681 self._master_candidate_uuids,
1682 self._potential_master_candidates,
1683 from_authorized_keys=True,
1684 from_public_keys=True,
1685 clear_authorized_keys=True,
1686 clear_public_keys=True,
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 self._ssh_file_manager.AssertNoNodeHasPublicKey(
1693 node_info.uuid, node_info.key)
1694 self._ssh_file_manager.AssertNodeSetOnlyHasAuthorizedKey(
1695 [node_name], node_info.key)
1696
1697 def testRemoveKeyFailedWithRetriesOnOtherNode(self):
1698 """Test removing keys even if one of the old nodes fails even with retries.
1699
1700 This tests checks whether the removal of a key finishes properly, even if
1701 the update of the key files on one of the other nodes fails despite several
1702 retries.
1703
1704 """
1705 all_master_candidates = self._ssh_file_manager.GetAllMasterCandidates()
1706 node_name, node_info = all_master_candidates[0]
1707 other_node_name, _ = all_master_candidates[1]
1708 assert node_name != self._master_node
1709 assert other_node_name != self._master_node
1710 assert node_name != other_node_name
1711 self._ssh_file_manager.SetMaxRetries(
1712 other_node_name, constants.SSHS_MAX_RETRIES + 1)
1713
1714 error_msgs = backend.RemoveNodeSshKey(
1715 node_info.uuid, node_name, self._master_candidate_uuids,
1716 self._potential_master_candidates,
1717 from_authorized_keys=True, from_public_keys=True,
1718 clear_authorized_keys=True, clear_public_keys=True,
1719 pub_key_file=self._pub_key_file, ssconf_store=self._ssconf_mock,
1720 noded_cert_file=self.noded_cert_file, run_cmd_fn=self._run_cmd_mock)
1721
1722 self._ssh_file_manager.AssertNodeSetOnlyHasAuthorizedKey(
1723 [other_node_name, node_name], node_info.key)
1724 self.assertTrue([error_msg for (node, error_msg) in error_msgs
1725 if node == other_node_name])
1726
1727 def testRemoveKeySuccessfullyWithRetriesOnTargetNode(self):
1728 """Test removing keys even if the target nodes needs retries.
1729
1730 This tests checks whether a key can be removed successfully even
1731 when removing the key on the node itself needs retries.
1732
1733 """
1734 all_master_candidates = self._ssh_file_manager.GetAllMasterCandidates()
1735 node_name, node_info = all_master_candidates[0]
1736 assert node_name != self._master_node
1737 self._ssh_file_manager.SetMaxRetries(
1738 node_name, constants.SSHS_MAX_RETRIES)
1739
1740 backend.RemoveNodeSshKey(node_info.uuid, node_name,
1741 self._master_candidate_uuids,
1742 self._potential_master_candidates,
1743 from_authorized_keys=True,
1744 from_public_keys=True,
1745 clear_authorized_keys=True,
1746 clear_public_keys=True,
1747 pub_key_file=self._pub_key_file,
1748 ssconf_store=self._ssconf_mock,
1749 noded_cert_file=self.noded_cert_file,
1750 run_cmd_fn=self._run_cmd_mock)
1751
1752 self._ssh_file_manager.AssertNoNodeHasPublicKey(
1753 node_info.uuid, node_info.key)
1754 self._ssh_file_manager.AssertNodeSetOnlyHasAuthorizedKey(
1755 [node_name], node_info.key)
1756
1757 def testRemoveKeyFailedWithRetriesOnTargetNode(self):
1758 """Test removing keys even if contacting the node fails with retries.
1759
1760 This tests checks whether the removal of a key finishes properly, even if
1761 the update of the key files on the node itself fails despite several
1762 retries.
1763
1764 """
1765 all_master_candidates = self._ssh_file_manager.GetAllMasterCandidates()
1766 node_name, node_info = all_master_candidates[0]
1767 assert node_name != self._master_node
1768 self._ssh_file_manager.SetMaxRetries(
1769 node_name, constants.SSHS_MAX_RETRIES + 1)
1770
1771 error_msgs = backend.RemoveNodeSshKey(
1772 node_info.uuid, node_name, self._master_candidate_uuids,
1773 self._potential_master_candidates,
1774 from_authorized_keys=True, from_public_keys=True,
1775 clear_authorized_keys=True, clear_public_keys=True,
1776 pub_key_file=self._pub_key_file, ssconf_store=self._ssconf_mock,
1777 noded_cert_file=self.noded_cert_file, run_cmd_fn=self._run_cmd_mock)
1778
1779 self._ssh_file_manager.AssertNodeSetOnlyHasAuthorizedKey(
1780 [node_name], node_info.key)
1781 self.assertTrue([error_msg for (node, error_msg) in error_msgs
1782 if node == node_name])
1783
1784
1785 class TestVerifySshSetup(testutils.GanetiTestCase):
1786
1787 _NODE1_UUID = "uuid1"
1788 _NODE2_UUID = "uuid2"
1789 _NODE3_UUID = "uuid3"
1790 _NODE1_NAME = "name1"
1791 _NODE2_NAME = "name2"
1792 _NODE3_NAME = "name3"
1793 _NODE1_KEYS = ["key11"]
1794 _NODE2_KEYS = ["key21"]
1795 _NODE3_KEYS = ["key31"]
1796
1797 _NODE_STATUS_LIST = [
1798 (_NODE1_UUID, _NODE1_NAME, True, True, True),
1799 (_NODE2_UUID, _NODE2_NAME, False, True, True),
1800 (_NODE3_UUID, _NODE3_NAME, False, False, True),
1801 ]
1802
1803 _PUB_KEY_RESULT = {
1804 _NODE1_UUID: _NODE1_KEYS,
1805 _NODE2_UUID: _NODE2_KEYS,
1806 _NODE3_UUID: _NODE3_KEYS,
1807 }
1808
1809 _AUTH_RESULT = {
1810 _NODE1_KEYS[0]: True,
1811 _NODE2_KEYS[0]: False,
1812 _NODE3_KEYS[0]: False,
1813 }
1814
1815 def setUp(self):
1816 testutils.GanetiTestCase.setUp(self)
1817 self._has_authorized_patcher = testutils \
1818 .patch_object(ssh, "HasAuthorizedKey")
1819 self._has_authorized_mock = self._has_authorized_patcher.start()
1820 self._query_patcher = testutils \
1821 .patch_object(ssh, "QueryPubKeyFile")
1822 self._query_mock = self._query_patcher.start()
1823 self._read_file_patcher = testutils \
1824 .patch_object(utils, "ReadFile")
1825 self._read_file_mock = self._read_file_patcher.start()
1826 self._read_file_mock.return_value = self._NODE1_KEYS[0]
1827 self.tmpdir = tempfile.mkdtemp()
1828 self.pub_key_file = os.path.join(self.tmpdir, "pub_key_file")
1829 open(self.pub_key_file, "w").close()
1830
1831 def tearDown(self):
1832 super(testutils.GanetiTestCase, self).tearDown()
1833 self._has_authorized_patcher.stop()
1834 self._query_patcher.stop()
1835 self._read_file_patcher.stop()
1836 shutil.rmtree(self.tmpdir)
1837
1838 def testValidData(self):
1839 self._has_authorized_mock.side_effect = \
1840 lambda _, key : self._AUTH_RESULT[key]
1841 self._query_mock.return_value = self._PUB_KEY_RESULT
1842 result = backend._VerifySshSetup(self._NODE_STATUS_LIST,
1843 self._NODE1_NAME,
1844 pub_key_file=self.pub_key_file)
1845 self.assertEqual(result, [])
1846
1847 def testMissingKey(self):
1848 self._has_authorized_mock.side_effect = \
1849 lambda _, key : self._AUTH_RESULT[key]
1850 pub_key_missing = copy.deepcopy(self._PUB_KEY_RESULT)
1851 del pub_key_missing[self._NODE2_UUID]
1852 self._query_mock.return_value = pub_key_missing
1853 result = backend._VerifySshSetup(self._NODE_STATUS_LIST,
1854 self._NODE1_NAME,
1855 pub_key_file=self.pub_key_file)
1856 self.assertTrue(self._NODE2_UUID in result[0])
1857
1858 def testUnknownKey(self):
1859 self._has_authorized_mock.side_effect = \
1860 lambda _, key : self._AUTH_RESULT[key]
1861 pub_key_missing = copy.deepcopy(self._PUB_KEY_RESULT)
1862 pub_key_missing["unkownnodeuuid"] = "pinkbunny"
1863 self._query_mock.return_value = pub_key_missing
1864 result = backend._VerifySshSetup(self._NODE_STATUS_LIST,
1865 self._NODE1_NAME,
1866 pub_key_file=self.pub_key_file)
1867 self.assertTrue("unkownnodeuuid" in result[0])
1868
1869 def testMissingMasterCandidate(self):
1870 auth_result = copy.deepcopy(self._AUTH_RESULT)
1871 auth_result["key11"] = False
1872 self._has_authorized_mock.side_effect = \
1873 lambda _, key : auth_result[key]
1874 self._query_mock.return_value = self._PUB_KEY_RESULT
1875 result = backend._VerifySshSetup(self._NODE_STATUS_LIST,
1876 self._NODE1_NAME,
1877 pub_key_file=self.pub_key_file)
1878 self.assertTrue(self._NODE1_UUID in result[0])
1879
1880 def testSuperfluousNormalNode(self):
1881 auth_result = copy.deepcopy(self._AUTH_RESULT)
1882 auth_result["key31"] = True
1883 self._has_authorized_mock.side_effect = \
1884 lambda _, key : auth_result[key]
1885 self._query_mock.return_value = self._PUB_KEY_RESULT
1886 result = backend._VerifySshSetup(self._NODE_STATUS_LIST,
1887 self._NODE1_NAME,
1888 pub_key_file=self.pub_key_file)
1889 self.assertTrue(self._NODE3_UUID in result[0])
1890
1891
1892 class TestOSEnvironment(unittest.TestCase):
1893 """Ensure the presence of public and private parameters.
1894
1895 They have to be present inside os environment variables.
1896
1897 """
1898
1899 def _CreateEnv(self):
1900 """Create and return an environment."""
1901 config_mock = ConfigMock()
1902 inst = config_mock.AddNewInstance(
1903 osparams={"public_param": "public_info"},
1904 osparams_private=serializer.PrivateDict({"private_param":
1905 "private_info",
1906 "another_private_param":
1907 "more_privacy"}),
1908 nics = [])
1909 inst.disks_info = ""
1910 inst.secondary_nodes = []
1911
1912 return backend.OSEnvironment(inst, config_mock.CreateOs())
1913
1914 def testParamPresence(self):
1915 env = self._CreateEnv()
1916 env_keys = env.keys()
1917 self.assertTrue("OSP_PUBLIC_PARAM" in env)
1918 self.assertTrue("OSP_PRIVATE_PARAM" in env)
1919 self.assertTrue("OSP_ANOTHER_PRIVATE_PARAM" in env)
1920 self.assertEqual("public_info", env["OSP_PUBLIC_PARAM"])
1921 self.assertEqual("private_info", env["OSP_PRIVATE_PARAM"])
1922 self.assertEqual("more_privacy", env["OSP_ANOTHER_PRIVATE_PARAM"])
1923
1924
1925 if __name__ == "__main__":
1926 testutils.GanetiTestProgram()