4 # Copyright (C) 2010 Google Inc.
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 # General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
22 """Script for testing ganeti.backend"""
30 from ganeti import utils
31 from ganeti import constants
32 from ganeti import backend
33 from ganeti import netutils
34 from ganeti import errors
40 class TestX509Certificates(unittest.TestCase):
42 self.tmpdir = tempfile.mkdtemp()
45 shutil.rmtree(self.tmpdir)
48 (name, cert_pem) = backend.CreateX509Certificate(300, cryptodir=self.tmpdir)
50 self.assertEqual(utils.ReadFile(os.path.join(self.tmpdir, name,
51 backend._X509_CERT_FILE)),
53 self.assert_(0 < os.path.getsize(os.path.join(self.tmpdir, name,
54 backend._X509_KEY_FILE)))
56 (name2, cert_pem2) = \
57 backend.CreateX509Certificate(300, cryptodir=self.tmpdir)
59 backend.RemoveX509Certificate(name, cryptodir=self.tmpdir)
60 backend.RemoveX509Certificate(name2, cryptodir=self.tmpdir)
62 self.assertEqual(utils.ListVisibleFiles(self.tmpdir), [])
64 def testNonEmpty(self):
65 (name, _) = backend.CreateX509Certificate(300, cryptodir=self.tmpdir)
67 utils.WriteFile(utils.PathJoin(self.tmpdir, name, "hello-world"),
70 self.assertRaises(backend.RPCFail, backend.RemoveX509Certificate,
71 name, cryptodir=self.tmpdir)
73 self.assertEqual(utils.ListVisibleFiles(self.tmpdir), [name])
76 class TestNodeVerify(testutils.GanetiTestCase):
77 def testMasterIPLocalhost(self):
78 # this a real functional test, but requires localhost to be reachable
79 local_data = (netutils.Hostname.GetSysName(),
80 constants.IP4_ADDRESS_LOCALHOST)
81 result = backend.VerifyNode({constants.NV_MASTERIP: local_data}, None)
82 self.failUnless(constants.NV_MASTERIP in result,
83 "Master IP data not returned")
84 self.failUnless(result[constants.NV_MASTERIP], "Cannot reach localhost")
86 def testMasterIPUnreachable(self):
87 # Network 192.0.2.0/24 is reserved for test/documentation as per
89 bad_data = ("master.example.com", "192.0.2.1")
90 # we just test that whatever TcpPing returns, VerifyNode returns too
91 netutils.TcpPing = lambda a, b, source=None: False
92 result = backend.VerifyNode({constants.NV_MASTERIP: bad_data}, None)
93 self.failUnless(constants.NV_MASTERIP in result,
94 "Master IP data not returned")
95 self.failIf(result[constants.NV_MASTERIP],
96 "Result from netutils.TcpPing corrupted")
99 def _DefRestrictedCmdOwner():
100 return (os.getuid(), os.getgid())
103 class TestVerifyRestrictedCmdName(unittest.TestCase):
104 def testAcceptableName(self):
105 for i in ["foo", "bar", "z1", "000first", "hello-world"]:
106 for fn in [lambda s: s, lambda s: s.upper(), lambda s: s.title()]:
107 (status, msg) = backend._VerifyRestrictedCmdName(fn(i))
108 self.assertTrue(status)
109 self.assertTrue(msg is None)
111 def testEmptyAndSpace(self):
112 for i in ["", " ", "\t", "\n"]:
113 (status, msg) = backend._VerifyRestrictedCmdName(i)
114 self.assertFalse(status)
115 self.assertEqual(msg, "Missing command name")
117 def testNameWithSlashes(self):
118 for i in ["/", "./foo", "../moo", "some/name"]:
119 (status, msg) = backend._VerifyRestrictedCmdName(i)
120 self.assertFalse(status)
121 self.assertEqual(msg, "Invalid command name")
123 def testForbiddenCharacters(self):
124 for i in ["#", ".", "..", "bash -c ls", "'"]:
125 (status, msg) = backend._VerifyRestrictedCmdName(i)
126 self.assertFalse(status)
127 self.assertEqual(msg, "Command name contains forbidden characters")
130 class TestVerifyRestrictedCmdDirectory(unittest.TestCase):
132 self.tmpdir = tempfile.mkdtemp()
135 shutil.rmtree(self.tmpdir)
137 def testCanNotStat(self):
138 tmpname = utils.PathJoin(self.tmpdir, "foobar")
139 self.assertFalse(os.path.exists(tmpname))
141 backend._VerifyRestrictedCmdDirectory(tmpname, _owner=NotImplemented)
142 self.assertFalse(status)
143 self.assertTrue(msg.startswith("Can't stat(2) '"))
145 def testTooPermissive(self):
146 tmpname = utils.PathJoin(self.tmpdir, "foobar")
149 for mode in [0777, 0706, 0760, 0722]:
150 os.chmod(tmpname, mode)
151 self.assertTrue(os.path.isdir(tmpname))
153 backend._VerifyRestrictedCmdDirectory(tmpname, _owner=NotImplemented)
154 self.assertFalse(status)
155 self.assertTrue(msg.startswith("Permissions on '"))
157 def testNoDirectory(self):
158 tmpname = utils.PathJoin(self.tmpdir, "foobar")
159 utils.WriteFile(tmpname, data="empty\n")
160 self.assertTrue(os.path.isfile(tmpname))
162 backend._VerifyRestrictedCmdDirectory(tmpname,
163 _owner=_DefRestrictedCmdOwner())
164 self.assertFalse(status)
165 self.assertTrue(msg.endswith("is not a directory"))
167 def testNormal(self):
168 tmpname = utils.PathJoin(self.tmpdir, "foobar")
170 self.assertTrue(os.path.isdir(tmpname))
172 backend._VerifyRestrictedCmdDirectory(tmpname,
173 _owner=_DefRestrictedCmdOwner())
174 self.assertTrue(status)
175 self.assertTrue(msg is None)
178 class TestVerifyRestrictedCmd(unittest.TestCase):
180 self.tmpdir = tempfile.mkdtemp()
183 shutil.rmtree(self.tmpdir)
185 def testCanNotStat(self):
186 tmpname = utils.PathJoin(self.tmpdir, "helloworld")
187 self.assertFalse(os.path.exists(tmpname))
189 backend._VerifyRestrictedCmd(self.tmpdir, "helloworld",
190 _owner=NotImplemented)
191 self.assertFalse(status)
192 self.assertTrue(msg.startswith("Can't stat(2) '"))
194 def testNotExecutable(self):
195 tmpname = utils.PathJoin(self.tmpdir, "cmdname")
196 utils.WriteFile(tmpname, data="empty\n")
198 backend._VerifyRestrictedCmd(self.tmpdir, "cmdname",
199 _owner=_DefRestrictedCmdOwner())
200 self.assertFalse(status)
201 self.assertTrue(msg.startswith("access(2) thinks '"))
203 def testExecutable(self):
204 tmpname = utils.PathJoin(self.tmpdir, "cmdname")
205 utils.WriteFile(tmpname, data="empty\n", mode=0700)
206 (status, executable) = \
207 backend._VerifyRestrictedCmd(self.tmpdir, "cmdname",
208 _owner=_DefRestrictedCmdOwner())
209 self.assertTrue(status)
210 self.assertEqual(executable, tmpname)
213 class TestPrepareRestrictedCmd(unittest.TestCase):
214 _TEST_PATH = "/tmp/some/test/path"
216 def testDirFails(self):
218 self.assertEqual(path, self._TEST_PATH)
219 return (False, "test error 31420")
222 backend._PrepareRestrictedCmd(self._TEST_PATH, "cmd21152",
224 _verify_name=NotImplemented,
225 _verify_cmd=NotImplemented)
226 self.assertFalse(status)
227 self.assertEqual(msg, "test error 31420")
229 def testNameFails(self):
231 self.assertEqual(cmd, "cmd4617")
232 return (False, "test error 591")
235 backend._PrepareRestrictedCmd(self._TEST_PATH, "cmd4617",
236 _verify_dir=lambda _: (True, None),
238 _verify_cmd=NotImplemented)
239 self.assertFalse(status)
240 self.assertEqual(msg, "test error 591")
242 def testCommandFails(self):
244 self.assertEqual(path, self._TEST_PATH)
245 self.assertEqual(cmd, "cmd17577")
246 return (False, "test error 25524")
249 backend._PrepareRestrictedCmd(self._TEST_PATH, "cmd17577",
250 _verify_dir=lambda _: (True, None),
251 _verify_name=lambda _: (True, None),
253 self.assertFalse(status)
254 self.assertEqual(msg, "test error 25524")
256 def testSuccess(self):
258 return (True, utils.PathJoin(path, cmd))
260 (status, executable) = \
261 backend._PrepareRestrictedCmd(self._TEST_PATH, "cmd22633",
262 _verify_dir=lambda _: (True, None),
263 _verify_name=lambda _: (True, None),
265 self.assertTrue(status)
266 self.assertEqual(executable, utils.PathJoin(self._TEST_PATH, "cmd22633"))
269 def _SleepForRestrictedCmd(duration):
273 def _GenericRestrictedCmdError(cmd):
274 return "Executing command '%s' failed" % cmd
277 class TestRunRestrictedCmd(unittest.TestCase):
279 self.tmpdir = tempfile.mkdtemp()
282 shutil.rmtree(self.tmpdir)
284 def testNonExistantLockDirectory(self):
285 lockfile = utils.PathJoin(self.tmpdir, "does", "not", "exist")
286 sleep_fn = testutils.CallCounter(_SleepForRestrictedCmd)
287 self.assertFalse(os.path.exists(lockfile))
288 self.assertRaises(backend.RPCFail,
289 backend.RunRestrictedCmd, "test",
290 _lock_timeout=NotImplemented,
292 _path=NotImplemented,
294 _prepare_fn=NotImplemented,
295 _runcmd_fn=NotImplemented,
297 self.assertEqual(sleep_fn.Count(), 1)
300 def _TryLock(lockfile):
301 sleep_fn = testutils.CallCounter(_SleepForRestrictedCmd)
305 backend.RunRestrictedCmd("test22717",
308 _path=NotImplemented,
310 _prepare_fn=NotImplemented,
311 _runcmd_fn=NotImplemented,
313 except backend.RPCFail, err:
314 assert str(err) == _GenericRestrictedCmdError("test22717"), \
315 "Did not fail with generic error message"
318 assert sleep_fn.Count() == 1
322 def testLockHeldByOtherProcess(self):
323 lockfile = utils.PathJoin(self.tmpdir, "lock")
325 lock = utils.FileLock.Open(lockfile)
326 lock.Exclusive(blocking=True, timeout=1.0)
328 self.assertTrue(utils.RunInSeparateProcess(self._TryLock, lockfile))
333 def _PrepareRaisingException(path, cmd):
334 assert cmd == "test23122"
335 raise Exception("test")
337 def testPrepareRaisesException(self):
338 lockfile = utils.PathJoin(self.tmpdir, "lock")
340 sleep_fn = testutils.CallCounter(_SleepForRestrictedCmd)
341 prepare_fn = testutils.CallCounter(self._PrepareRaisingException)
344 backend.RunRestrictedCmd("test23122",
345 _lock_timeout=1.0, _lock_file=lockfile,
346 _path=NotImplemented, _runcmd_fn=NotImplemented,
347 _sleep_fn=sleep_fn, _prepare_fn=prepare_fn,
349 except backend.RPCFail, err:
350 self.assertEqual(str(err), _GenericRestrictedCmdError("test23122"))
352 self.fail("Didn't fail")
354 self.assertEqual(sleep_fn.Count(), 1)
355 self.assertEqual(prepare_fn.Count(), 1)
358 def _PrepareFails(path, cmd):
359 assert cmd == "test29327"
360 return ("some error message", None)
362 def testPrepareFails(self):
363 lockfile = utils.PathJoin(self.tmpdir, "lock")
365 sleep_fn = testutils.CallCounter(_SleepForRestrictedCmd)
366 prepare_fn = testutils.CallCounter(self._PrepareFails)
369 backend.RunRestrictedCmd("test29327",
370 _lock_timeout=1.0, _lock_file=lockfile,
371 _path=NotImplemented, _runcmd_fn=NotImplemented,
372 _sleep_fn=sleep_fn, _prepare_fn=prepare_fn,
374 except backend.RPCFail, err:
375 self.assertEqual(str(err), _GenericRestrictedCmdError("test29327"))
377 self.fail("Didn't fail")
379 self.assertEqual(sleep_fn.Count(), 1)
380 self.assertEqual(prepare_fn.Count(), 1)
383 def _SuccessfulPrepare(path, cmd):
384 return (True, utils.PathJoin(path, cmd))
386 def testRunCmdFails(self):
387 lockfile = utils.PathJoin(self.tmpdir, "lock")
389 def fn(args, env=NotImplemented, reset_env=NotImplemented,
390 postfork_fn=NotImplemented):
391 self.assertEqual(args, [utils.PathJoin(self.tmpdir, "test3079")])
392 self.assertEqual(env, {})
393 self.assertTrue(reset_env)
394 self.assertTrue(callable(postfork_fn))
396 trylock = utils.FileLock.Open(lockfile)
398 # See if lockfile is still held
399 self.assertRaises(EnvironmentError, trylock.Exclusive, blocking=False)
401 # Call back to release lock
402 postfork_fn(NotImplemented)
404 # See if lockfile can be acquired
405 trylock.Exclusive(blocking=False)
409 # Simulate a failed command
410 return utils.RunResult(constants.EXIT_FAILURE, None,
411 "stdout", "stderr406328567",
412 utils.ShellQuoteArgs(args),
413 NotImplemented, NotImplemented)
415 sleep_fn = testutils.CallCounter(_SleepForRestrictedCmd)
416 prepare_fn = testutils.CallCounter(self._SuccessfulPrepare)
417 runcmd_fn = testutils.CallCounter(fn)
420 backend.RunRestrictedCmd("test3079",
421 _lock_timeout=1.0, _lock_file=lockfile,
422 _path=self.tmpdir, _runcmd_fn=runcmd_fn,
423 _sleep_fn=sleep_fn, _prepare_fn=prepare_fn,
425 except backend.RPCFail, err:
426 self.assertTrue(str(err).startswith("Remote command 'test3079' failed:"))
427 self.assertTrue("stderr406328567" in str(err),
428 msg="Error did not include output")
430 self.fail("Didn't fail")
432 self.assertEqual(sleep_fn.Count(), 0)
433 self.assertEqual(prepare_fn.Count(), 1)
434 self.assertEqual(runcmd_fn.Count(), 1)
436 def testRunCmdSucceeds(self):
437 lockfile = utils.PathJoin(self.tmpdir, "lock")
439 def fn(args, env=NotImplemented, reset_env=NotImplemented,
440 postfork_fn=NotImplemented):
441 self.assertEqual(args, [utils.PathJoin(self.tmpdir, "test5667")])
442 self.assertEqual(env, {})
443 self.assertTrue(reset_env)
445 # Call back to release lock
446 postfork_fn(NotImplemented)
448 # Simulate a successful command
449 return utils.RunResult(constants.EXIT_SUCCESS, None, "stdout14463", "",
450 utils.ShellQuoteArgs(args),
451 NotImplemented, NotImplemented)
453 sleep_fn = testutils.CallCounter(_SleepForRestrictedCmd)
454 prepare_fn = testutils.CallCounter(self._SuccessfulPrepare)
455 runcmd_fn = testutils.CallCounter(fn)
457 result = backend.RunRestrictedCmd("test5667",
458 _lock_timeout=1.0, _lock_file=lockfile,
459 _path=self.tmpdir, _runcmd_fn=runcmd_fn,
461 _prepare_fn=prepare_fn,
463 self.assertEqual(result, "stdout14463")
465 self.assertEqual(sleep_fn.Count(), 0)
466 self.assertEqual(prepare_fn.Count(), 1)
467 self.assertEqual(runcmd_fn.Count(), 1)
469 def testCommandsDisabled(self):
471 backend.RunRestrictedCmd("test",
472 _lock_timeout=NotImplemented,
473 _lock_file=NotImplemented,
474 _path=NotImplemented,
475 _sleep_fn=NotImplemented,
476 _prepare_fn=NotImplemented,
477 _runcmd_fn=NotImplemented,
479 except backend.RPCFail, err:
480 self.assertEqual(str(err), "Remote commands disabled at configure time")
482 self.fail("Did not raise exception")
485 if __name__ == "__main__":
486 testutils.GanetiTestProgram()