61855924b04a7c4ba7961f2cea4ad5d4b4ddeb2c
[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 testPromoteToMasterCandidate(self):
1273 # Get one of the potential master candidates
1274 node_name, node_info = \
1275 self._ssh_file_manager.GetAllPurePotentialMasterCandidates()[0]
1276 # Update it's role to master candidate in the test data
1277 self._ssh_file_manager.SetOrAddNode(
1278 node_name, node_info.uuid, node_info.key,
1279 node_info.is_potential_master_candidate, True, node_info.is_master)
1280
1281 backend.AddNodeSshKey(node_info.uuid, node_name,
1282 self._potential_master_candidates,
1283 to_authorized_keys=True,
1284 to_public_keys=False,
1285 get_public_keys=False,
1286 pub_key_file=self._pub_key_file,
1287 ssconf_store=self._ssconf_mock,
1288 noded_cert_file=self.noded_cert_file,
1289 run_cmd_fn=self._run_cmd_mock)
1290
1291 self._ssh_file_manager.AssertPotentialMasterCandidatesOnlyHavePublicKey(
1292 node_name)
1293 self._ssh_file_manager.AssertAllNodesHaveAuthorizedKey(node_info.key)
1294
1295 def testRemoveMasterCandidate(self):
1296 node_name, (node_uuid, node_key, is_potential_master_candidate,
1297 is_master_candidate, is_master) = \
1298 self._ssh_file_manager.GetAllMasterCandidates()[0]
1299
1300 backend.RemoveNodeSshKey(node_uuid, node_name,
1301 self._master_candidate_uuids,
1302 self._potential_master_candidates,
1303 from_authorized_keys=True,
1304 from_public_keys=True,
1305 clear_authorized_keys=True,
1306 clear_public_keys=True,
1307 pub_key_file=self._pub_key_file,
1308 ssconf_store=self._ssconf_mock,
1309 noded_cert_file=self.noded_cert_file,
1310 run_cmd_fn=self._run_cmd_mock)
1311
1312 self._ssh_file_manager.AssertNoNodeHasPublicKey(node_uuid, node_key)
1313 self._ssh_file_manager.AssertNodeSetOnlyHasAuthorizedKey(
1314 [node_name], node_key)
1315 self.assertEqual(0,
1316 len(self._ssh_file_manager.GetPublicKeysOfNode(node_name)))
1317 self.assertEqual(1,
1318 len(self._ssh_file_manager.GetAuthorizedKeysOfNode(node_name)))
1319
1320 def testRemovePotentialMasterCandidate(self):
1321 (node_name, node_info) = \
1322 self._ssh_file_manager.GetAllPurePotentialMasterCandidates()[0]
1323
1324 backend.RemoveNodeSshKey(node_info.uuid, node_name,
1325 self._master_candidate_uuids,
1326 self._potential_master_candidates,
1327 from_authorized_keys=False,
1328 from_public_keys=True,
1329 clear_authorized_keys=True,
1330 clear_public_keys=True,
1331 pub_key_file=self._pub_key_file,
1332 ssconf_store=self._ssconf_mock,
1333 noded_cert_file=self.noded_cert_file,
1334 run_cmd_fn=self._run_cmd_mock)
1335
1336 self._ssh_file_manager.AssertNoNodeHasPublicKey(
1337 node_info.uuid, node_info.key)
1338 self._ssh_file_manager.AssertNoNodeHasAuthorizedKey(node_info.key)
1339 self.assertEqual(0,
1340 len(self._ssh_file_manager.GetPublicKeysOfNode(node_name)))
1341 self.assertEqual(0,
1342 len(self._ssh_file_manager.GetAuthorizedKeysOfNode(node_name)))
1343
1344 def testRemoveNormalNode(self):
1345 node_name, node_info = self._ssh_file_manager.GetAllNormalNodes()[0]
1346
1347 backend.RemoveNodeSshKey(node_info.uuid, node_name,
1348 self._master_candidate_uuids,
1349 self._potential_master_candidates,
1350 from_authorized_keys=False,
1351 from_public_keys=False,
1352 clear_authorized_keys=True,
1353 clear_public_keys=True,
1354 pub_key_file=self._pub_key_file,
1355 ssconf_store=self._ssconf_mock,
1356 noded_cert_file=self.noded_cert_file,
1357 run_cmd_fn=self._run_cmd_mock)
1358
1359 self._ssh_file_manager.AssertNoNodeHasPublicKey(
1360 node_info.uuid, node_info.key)
1361 self._ssh_file_manager.AssertNoNodeHasAuthorizedKey(node_info.key)
1362 self.assertEqual(0,
1363 len(self._ssh_file_manager.GetPublicKeysOfNode(node_name)))
1364 self.assertEqual(0,
1365 len(self._ssh_file_manager.GetAuthorizedKeysOfNode(node_name)))
1366
1367 def testDemoteMasterCandidateToPotentialMasterCandidate(self):
1368 node_name, node_info = self._ssh_file_manager.GetAllMasterCandidates()[0]
1369 self._ssh_file_manager.SetOrAddNode(
1370 node_name, node_info.uuid, node_info.key,
1371 node_info.is_potential_master_candidate, False, node_info.is_master)
1372
1373 backend.RemoveNodeSshKey(node_info.uuid, node_name,
1374 self._master_candidate_uuids,
1375 self._potential_master_candidates,
1376 from_authorized_keys=True,
1377 from_public_keys=False,
1378 clear_authorized_keys=False,
1379 clear_public_keys=False,
1380 pub_key_file=self._pub_key_file,
1381 ssconf_store=self._ssconf_mock,
1382 noded_cert_file=self.noded_cert_file,
1383 run_cmd_fn=self._run_cmd_mock)
1384
1385 self._ssh_file_manager.AssertPotentialMasterCandidatesOnlyHavePublicKey(
1386 node_name)
1387 self._ssh_file_manager.AssertNodeSetOnlyHasAuthorizedKey(
1388 [node_name], node_info.key)
1389
1390 def testDemotePotentialMasterCandidateToNormalNode(self):
1391 (node_name, node_info) = \
1392 self._ssh_file_manager.GetAllPurePotentialMasterCandidates()[0]
1393 self._ssh_file_manager.SetOrAddNode(
1394 node_name, node_info.uuid, node_info.key, False,
1395 node_info.is_master_candidate, node_info.is_master)
1396
1397 backend.RemoveNodeSshKey(node_info.uuid, node_name,
1398 self._master_candidate_uuids,
1399 self._potential_master_candidates,
1400 from_authorized_keys=False,
1401 from_public_keys=True,
1402 clear_authorized_keys=False,
1403 clear_public_keys=False,
1404 pub_key_file=self._pub_key_file,
1405 ssconf_store=self._ssconf_mock,
1406 noded_cert_file=self.noded_cert_file,
1407 run_cmd_fn=self._run_cmd_mock)
1408
1409 self._ssh_file_manager.AssertNoNodeHasPublicKey(
1410 node_info.uuid, node_info.key)
1411 self._ssh_file_manager.AssertNoNodeHasAuthorizedKey(node_info.key)
1412
1413 def _GetReducedOnlineNodeList(self):
1414 """'Randomly' mark some nodes as offline."""
1415 return [name for name in self._all_nodes
1416 if '3' not in name and '5' not in name]
1417
1418 def testAddKeyWithOfflineNodes(self):
1419 (new_node_name, new_node_uuid, new_node_key, is_master_candidate,
1420 is_potential_master_candidate, is_master) = self._GetNewMasterCandidate()
1421
1422 self._AddNewNodeToTestData(
1423 new_node_name, new_node_uuid, new_node_key,
1424 is_potential_master_candidate, is_master_candidate,
1425 is_master)
1426 self._online_nodes = self._GetReducedOnlineNodeList()
1427 self._ssconf_mock.GetOnlineNodeList.side_effect = \
1428 lambda : self._online_nodes
1429
1430 backend.AddNodeSshKey(new_node_uuid, new_node_name,
1431 self._potential_master_candidates,
1432 to_authorized_keys=is_master_candidate,
1433 to_public_keys=is_potential_master_candidate,
1434 get_public_keys=is_potential_master_candidate,
1435 pub_key_file=self._pub_key_file,
1436 ssconf_store=self._ssconf_mock,
1437 noded_cert_file=self.noded_cert_file,
1438 run_cmd_fn=self._run_cmd_mock)
1439
1440 for node in self._all_nodes:
1441 if node in self._online_nodes:
1442 self.assertTrue(self._ssh_file_manager.NodeHasAuthorizedKey(
1443 node, new_node_key))
1444 else:
1445 self.assertFalse(self._ssh_file_manager.NodeHasAuthorizedKey(
1446 node, new_node_key))
1447
1448 def testRemoveKeyWithOfflineNodes(self):
1449 (node_name, node_info) = \
1450 self._ssh_file_manager.GetAllMasterCandidates()[0]
1451 self._online_nodes = self._GetReducedOnlineNodeList()
1452 self._ssconf_mock.GetOnlineNodeList.side_effect = \
1453 lambda : self._online_nodes
1454
1455 backend.RemoveNodeSshKey(node_info.uuid, node_name,
1456 self._master_candidate_uuids,
1457 self._potential_master_candidates,
1458 from_authorized_keys=True,
1459 from_public_keys=True,
1460 clear_authorized_keys=True,
1461 clear_public_keys=True,
1462 pub_key_file=self._pub_key_file,
1463 ssconf_store=self._ssconf_mock,
1464 noded_cert_file=self.noded_cert_file,
1465 run_cmd_fn=self._run_cmd_mock)
1466
1467 offline_nodes = [node for node in self._all_nodes
1468 if node not in self._online_nodes]
1469 self._ssh_file_manager.AssertNodeSetOnlyHasAuthorizedKey(
1470 offline_nodes + [node_name], node_info.key)
1471
1472 def testAddKeySuccessfullyOnNewNodeWithRetries(self):
1473 """Tests adding a new node's key when updating that node takes retries.
1474
1475 This test checks whether adding a new node's key successfully updates
1476 the SSH key files of all nodes, even if updating the new node's key files
1477 itself takes a couple of retries to succeed.
1478
1479 """
1480 (new_node_name, new_node_uuid, new_node_key, is_master_candidate,
1481 is_potential_master_candidate, is_master) = self._GetNewMasterCandidate()
1482
1483 self._AddNewNodeToTestData(
1484 new_node_name, new_node_uuid, new_node_key,
1485 is_potential_master_candidate, is_master_candidate,
1486 is_master)
1487 self._ssh_file_manager.SetMaxRetries(
1488 new_node_name, constants.SSHS_MAX_RETRIES)
1489
1490 backend.AddNodeSshKey(new_node_uuid, new_node_name,
1491 self._potential_master_candidates,
1492 to_authorized_keys=is_master_candidate,
1493 to_public_keys=is_potential_master_candidate,
1494 get_public_keys=is_potential_master_candidate,
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 self._ssh_file_manager.AssertPotentialMasterCandidatesOnlyHavePublicKey(
1501 new_node_name)
1502 self._ssh_file_manager.AssertAllNodesHaveAuthorizedKey(
1503 new_node_key)
1504
1505 def testAddKeyFailedOnNewNodeWithRetries(self):
1506 """Tests clean up if updating a new node's SSH setup fails.
1507
1508 If adding the keys of a new node fails, because updating the SSH key files
1509 of that new node fails, check whether already carried out operations are
1510 successfully rolled back and thus the state of the cluster is cleaned up.
1511
1512 """
1513 (new_node_name, new_node_uuid, new_node_key, is_master_candidate,
1514 is_potential_master_candidate, is_master) = self._GetNewMasterCandidate()
1515
1516 self._AddNewNodeToTestData(
1517 new_node_name, new_node_uuid, new_node_key,
1518 is_potential_master_candidate, is_master_candidate,
1519 is_master)
1520 self._ssh_file_manager.SetMaxRetries(
1521 new_node_name, constants.SSHS_MAX_RETRIES + 1)
1522
1523 self.assertRaises(
1524 errors.SshUpdateError, backend.AddNodeSshKey, new_node_uuid,
1525 new_node_name, self._potential_master_candidates,
1526 to_authorized_keys=is_master_candidate,
1527 to_public_keys=is_potential_master_candidate,
1528 get_public_keys=is_potential_master_candidate,
1529 pub_key_file=self._pub_key_file,
1530 ssconf_store=self._ssconf_mock,
1531 noded_cert_file=self.noded_cert_file,
1532 run_cmd_fn=self._run_cmd_mock)
1533
1534 for node in self._all_nodes:
1535 if node == new_node_name:
1536 self.assertTrue(self._ssh_file_manager.NodeHasAuthorizedKey(
1537 node, new_node_key))
1538 else:
1539 self.assertFalse(self._ssh_file_manager.NodeHasAuthorizedKey(
1540 node, new_node_key))
1541
1542 self._ssh_file_manager.AssertNoNodeHasPublicKey(new_node_uuid, new_node_key)
1543
1544 def testAddKeySuccessfullyOnOldNodeWithRetries(self):
1545 """Tests adding a new key even if updating nodes takes retries.
1546
1547 This tests whether adding a new node's key successfully finishes,
1548 even if one of the other cluster nodes takes a couple of retries
1549 to succeed.
1550
1551 """
1552 (new_node_name, new_node_uuid, new_node_key, is_master_candidate,
1553 is_potential_master_candidate, is_master) = self._GetNewMasterCandidate()
1554
1555 other_node_name, _ = self._ssh_file_manager.GetAllMasterCandidates()[0]
1556 self._ssh_file_manager.SetMaxRetries(
1557 other_node_name, constants.SSHS_MAX_RETRIES)
1558 assert other_node_name != new_node_name
1559 self._AddNewNodeToTestData(
1560 new_node_name, new_node_uuid, new_node_key,
1561 is_potential_master_candidate, is_master_candidate,
1562 is_master)
1563
1564 backend.AddNodeSshKey(new_node_uuid, new_node_name,
1565 self._potential_master_candidates,
1566 to_authorized_keys=is_master_candidate,
1567 to_public_keys=is_potential_master_candidate,
1568 get_public_keys=is_potential_master_candidate,
1569 pub_key_file=self._pub_key_file,
1570 ssconf_store=self._ssconf_mock,
1571 noded_cert_file=self.noded_cert_file,
1572 run_cmd_fn=self._run_cmd_mock)
1573
1574 self._ssh_file_manager.AssertAllNodesHaveAuthorizedKey(new_node_key)
1575
1576 def testAddKeyFailedOnOldNodeWithRetries(self):
1577 """Tests adding keys when updating one node's SSH setup fails.
1578
1579 This tests whether when adding a new node's key and one node is
1580 unreachable (but not marked as offline) the operation still finishes
1581 properly and only that unreachable node's SSH key setup did not get
1582 updated.
1583
1584 """
1585 (new_node_name, new_node_uuid, new_node_key, is_master_candidate,
1586 is_potential_master_candidate, is_master) = self._GetNewMasterCandidate()
1587
1588 other_node_name, _ = self._ssh_file_manager.GetAllMasterCandidates()[0]
1589 self._ssh_file_manager.SetMaxRetries(
1590 other_node_name, constants.SSHS_MAX_RETRIES + 1)
1591 assert other_node_name != new_node_name
1592 self._AddNewNodeToTestData(
1593 new_node_name, new_node_uuid, new_node_key,
1594 is_potential_master_candidate, is_master_candidate,
1595 is_master)
1596
1597 node_errors = backend.AddNodeSshKey(
1598 new_node_uuid, new_node_name, self._potential_master_candidates,
1599 to_authorized_keys=is_master_candidate,
1600 to_public_keys=is_potential_master_candidate,
1601 get_public_keys=is_potential_master_candidate,
1602 pub_key_file=self._pub_key_file,
1603 ssconf_store=self._ssconf_mock,
1604 noded_cert_file=self.noded_cert_file,
1605 run_cmd_fn=self._run_cmd_mock)
1606
1607 rest_nodes = [node for node in self._all_nodes
1608 if node != other_node_name]
1609 rest_nodes.append(new_node_name)
1610 self._ssh_file_manager.AssertNodeSetOnlyHasAuthorizedKey(
1611 rest_nodes, new_node_key)
1612 self.assertTrue([error_msg for (node, error_msg) in node_errors
1613 if node == other_node_name])
1614
1615 def testRemoveKeySuccessfullyWithRetriesOnOtherNode(self):
1616 """Test removing keys even if one of the old nodes needs retries.
1617
1618 This tests checks whether a key can be removed successfully even
1619 when one of the other nodes needs to be contacted with several
1620 retries.
1621
1622 """
1623 all_master_candidates = self._ssh_file_manager.GetAllMasterCandidates()
1624 node_name, node_info = all_master_candidates[0]
1625 other_node_name, _ = all_master_candidates[1]
1626 assert node_name != self._master_node
1627 assert other_node_name != self._master_node
1628 assert node_name != other_node_name
1629 self._ssh_file_manager.SetMaxRetries(
1630 other_node_name, constants.SSHS_MAX_RETRIES)
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 self._ssh_file_manager.AssertNoNodeHasPublicKey(
1645 node_info.uuid, node_info.key)
1646 self._ssh_file_manager.AssertNodeSetOnlyHasAuthorizedKey(
1647 [node_name], node_info.key)
1648
1649 def testRemoveKeyFailedWithRetriesOnOtherNode(self):
1650 """Test removing keys even if one of the old nodes fails even with retries.
1651
1652 This tests checks whether the removal of a key finishes properly, even if
1653 the update of the key files on one of the other nodes fails despite several
1654 retries.
1655
1656 """
1657 all_master_candidates = self._ssh_file_manager.GetAllMasterCandidates()
1658 node_name, node_info = all_master_candidates[0]
1659 other_node_name, _ = all_master_candidates[1]
1660 assert node_name != self._master_node
1661 assert other_node_name != self._master_node
1662 assert node_name != other_node_name
1663 self._ssh_file_manager.SetMaxRetries(
1664 other_node_name, constants.SSHS_MAX_RETRIES + 1)
1665
1666 error_msgs = backend.RemoveNodeSshKey(
1667 node_info.uuid, node_name, self._master_candidate_uuids,
1668 self._potential_master_candidates,
1669 from_authorized_keys=True, from_public_keys=True,
1670 clear_authorized_keys=True, clear_public_keys=True,
1671 pub_key_file=self._pub_key_file, ssconf_store=self._ssconf_mock,
1672 noded_cert_file=self.noded_cert_file, run_cmd_fn=self._run_cmd_mock)
1673
1674 self._ssh_file_manager.AssertNodeSetOnlyHasAuthorizedKey(
1675 [other_node_name, node_name], node_info.key)
1676 self.assertTrue([error_msg for (node, error_msg) in error_msgs
1677 if node == other_node_name])
1678
1679 def testRemoveKeySuccessfullyWithRetriesOnTargetNode(self):
1680 """Test removing keys even if the target nodes needs retries.
1681
1682 This tests checks whether a key can be removed successfully even
1683 when removing the key on the node itself needs retries.
1684
1685 """
1686 all_master_candidates = self._ssh_file_manager.GetAllMasterCandidates()
1687 node_name, node_info = all_master_candidates[0]
1688 assert node_name != self._master_node
1689 self._ssh_file_manager.SetMaxRetries(
1690 node_name, constants.SSHS_MAX_RETRIES)
1691
1692 backend.RemoveNodeSshKey(node_info.uuid, node_name,
1693 self._master_candidate_uuids,
1694 self._potential_master_candidates,
1695 from_authorized_keys=True,
1696 from_public_keys=True,
1697 clear_authorized_keys=True,
1698 clear_public_keys=True,
1699 pub_key_file=self._pub_key_file,
1700 ssconf_store=self._ssconf_mock,
1701 noded_cert_file=self.noded_cert_file,
1702 run_cmd_fn=self._run_cmd_mock)
1703
1704 self._ssh_file_manager.AssertNoNodeHasPublicKey(
1705 node_info.uuid, node_info.key)
1706 self._ssh_file_manager.AssertNodeSetOnlyHasAuthorizedKey(
1707 [node_name], node_info.key)
1708
1709 def testRemoveKeyFailedWithRetriesOnTargetNode(self):
1710 """Test removing keys even if contacting the node fails with retries.
1711
1712 This tests checks whether the removal of a key finishes properly, even if
1713 the update of the key files on the node itself fails despite several
1714 retries.
1715
1716 """
1717 all_master_candidates = self._ssh_file_manager.GetAllMasterCandidates()
1718 node_name, node_info = all_master_candidates[0]
1719 assert node_name != self._master_node
1720 self._ssh_file_manager.SetMaxRetries(
1721 node_name, constants.SSHS_MAX_RETRIES + 1)
1722
1723 error_msgs = backend.RemoveNodeSshKey(
1724 node_info.uuid, node_name, self._master_candidate_uuids,
1725 self._potential_master_candidates,
1726 from_authorized_keys=True, from_public_keys=True,
1727 clear_authorized_keys=True, clear_public_keys=True,
1728 pub_key_file=self._pub_key_file, ssconf_store=self._ssconf_mock,
1729 noded_cert_file=self.noded_cert_file, run_cmd_fn=self._run_cmd_mock)
1730
1731 self._ssh_file_manager.AssertNodeSetOnlyHasAuthorizedKey(
1732 [node_name], node_info.key)
1733 self.assertTrue([error_msg for (node, error_msg) in error_msgs
1734 if node == node_name])
1735
1736
1737 class TestVerifySshSetup(testutils.GanetiTestCase):
1738
1739 _NODE1_UUID = "uuid1"
1740 _NODE2_UUID = "uuid2"
1741 _NODE3_UUID = "uuid3"
1742 _NODE1_NAME = "name1"
1743 _NODE2_NAME = "name2"
1744 _NODE3_NAME = "name3"
1745 _NODE1_KEYS = ["key11"]
1746 _NODE2_KEYS = ["key21"]
1747 _NODE3_KEYS = ["key31"]
1748
1749 _NODE_STATUS_LIST = [
1750 (_NODE1_UUID, _NODE1_NAME, True, True, True),
1751 (_NODE2_UUID, _NODE2_NAME, False, True, True),
1752 (_NODE3_UUID, _NODE3_NAME, False, False, True),
1753 ]
1754
1755 _PUB_KEY_RESULT = {
1756 _NODE1_UUID: _NODE1_KEYS,
1757 _NODE2_UUID: _NODE2_KEYS,
1758 _NODE3_UUID: _NODE3_KEYS,
1759 }
1760
1761 _AUTH_RESULT = {
1762 _NODE1_KEYS[0]: True,
1763 _NODE2_KEYS[0]: False,
1764 _NODE3_KEYS[0]: False,
1765 }
1766
1767 def setUp(self):
1768 testutils.GanetiTestCase.setUp(self)
1769 self._has_authorized_patcher = testutils \
1770 .patch_object(ssh, "HasAuthorizedKey")
1771 self._has_authorized_mock = self._has_authorized_patcher.start()
1772 self._query_patcher = testutils \
1773 .patch_object(ssh, "QueryPubKeyFile")
1774 self._query_mock = self._query_patcher.start()
1775 self._read_file_patcher = testutils \
1776 .patch_object(utils, "ReadFile")
1777 self._read_file_mock = self._read_file_patcher.start()
1778 self._read_file_mock.return_value = self._NODE1_KEYS[0]
1779 self.tmpdir = tempfile.mkdtemp()
1780 self.pub_key_file = os.path.join(self.tmpdir, "pub_key_file")
1781 open(self.pub_key_file, "w").close()
1782
1783 def tearDown(self):
1784 super(testutils.GanetiTestCase, self).tearDown()
1785 self._has_authorized_patcher.stop()
1786 self._query_patcher.stop()
1787 self._read_file_patcher.stop()
1788 shutil.rmtree(self.tmpdir)
1789
1790 def testValidData(self):
1791 self._has_authorized_mock.side_effect = \
1792 lambda _, key : self._AUTH_RESULT[key]
1793 self._query_mock.return_value = self._PUB_KEY_RESULT
1794 result = backend._VerifySshSetup(self._NODE_STATUS_LIST,
1795 self._NODE1_NAME,
1796 pub_key_file=self.pub_key_file)
1797 self.assertEqual(result, [])
1798
1799 def testMissingKey(self):
1800 self._has_authorized_mock.side_effect = \
1801 lambda _, key : self._AUTH_RESULT[key]
1802 pub_key_missing = copy.deepcopy(self._PUB_KEY_RESULT)
1803 del pub_key_missing[self._NODE2_UUID]
1804 self._query_mock.return_value = pub_key_missing
1805 result = backend._VerifySshSetup(self._NODE_STATUS_LIST,
1806 self._NODE1_NAME,
1807 pub_key_file=self.pub_key_file)
1808 self.assertTrue(self._NODE2_UUID in result[0])
1809
1810 def testUnknownKey(self):
1811 self._has_authorized_mock.side_effect = \
1812 lambda _, key : self._AUTH_RESULT[key]
1813 pub_key_missing = copy.deepcopy(self._PUB_KEY_RESULT)
1814 pub_key_missing["unkownnodeuuid"] = "pinkbunny"
1815 self._query_mock.return_value = pub_key_missing
1816 result = backend._VerifySshSetup(self._NODE_STATUS_LIST,
1817 self._NODE1_NAME,
1818 pub_key_file=self.pub_key_file)
1819 self.assertTrue("unkownnodeuuid" in result[0])
1820
1821 def testMissingMasterCandidate(self):
1822 auth_result = copy.deepcopy(self._AUTH_RESULT)
1823 auth_result["key11"] = False
1824 self._has_authorized_mock.side_effect = \
1825 lambda _, key : auth_result[key]
1826 self._query_mock.return_value = self._PUB_KEY_RESULT
1827 result = backend._VerifySshSetup(self._NODE_STATUS_LIST,
1828 self._NODE1_NAME,
1829 pub_key_file=self.pub_key_file)
1830 self.assertTrue(self._NODE1_UUID in result[0])
1831
1832 def testSuperfluousNormalNode(self):
1833 auth_result = copy.deepcopy(self._AUTH_RESULT)
1834 auth_result["key31"] = True
1835 self._has_authorized_mock.side_effect = \
1836 lambda _, key : auth_result[key]
1837 self._query_mock.return_value = self._PUB_KEY_RESULT
1838 result = backend._VerifySshSetup(self._NODE_STATUS_LIST,
1839 self._NODE1_NAME,
1840 pub_key_file=self.pub_key_file)
1841 self.assertTrue(self._NODE3_UUID in result[0])
1842
1843
1844 class TestOSEnvironment(unittest.TestCase):
1845 """Ensure the presence of public and private parameters.
1846
1847 They have to be present inside os environment variables.
1848
1849 """
1850
1851 def _CreateEnv(self):
1852 """Create and return an environment."""
1853 config_mock = ConfigMock()
1854 inst = config_mock.AddNewInstance(
1855 osparams={"public_param": "public_info"},
1856 osparams_private=serializer.PrivateDict({"private_param":
1857 "private_info",
1858 "another_private_param":
1859 "more_privacy"}),
1860 nics = [])
1861 inst.disks_info = ""
1862 inst.secondary_nodes = []
1863
1864 return backend.OSEnvironment(inst, config_mock.CreateOs())
1865
1866 def testParamPresence(self):
1867 env = self._CreateEnv()
1868 env_keys = env.keys()
1869 self.assertTrue("OSP_PUBLIC_PARAM" in env)
1870 self.assertTrue("OSP_PRIVATE_PARAM" in env)
1871 self.assertTrue("OSP_ANOTHER_PRIVATE_PARAM" in env)
1872 self.assertEqual("public_info", env["OSP_PUBLIC_PARAM"])
1873 self.assertEqual("private_info", env["OSP_PRIVATE_PARAM"])
1874 self.assertEqual("more_privacy", env["OSP_ANOTHER_PRIVATE_PARAM"])
1875
1876
1877 if __name__ == "__main__":
1878 testutils.GanetiTestProgram()