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