4 # Copyright (C) 2011 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.utils.process"""
33 from ganeti import constants
34 from ganeti import utils
35 from ganeti import errors
40 class TestIsProcessAlive(unittest.TestCase):
41 """Testing case for IsProcessAlive"""
45 self.assert_(utils.IsProcessAlive(mypid), "can't find myself running")
47 def testNotExisting(self):
48 pid_non_existing = os.fork()
49 if pid_non_existing == 0:
51 elif pid_non_existing < 0:
52 raise SystemError("can't fork")
53 os.waitpid(pid_non_existing, 0)
54 self.assertFalse(utils.IsProcessAlive(pid_non_existing),
55 "nonexisting process detected")
58 class TestGetProcStatusPath(unittest.TestCase):
60 self.assert_("/1234/" in utils.process._GetProcStatusPath(1234))
61 self.assertNotEqual(utils.process._GetProcStatusPath(1),
62 utils.process._GetProcStatusPath(2))
65 class TestIsProcessHandlingSignal(unittest.TestCase):
67 self.tmpdir = tempfile.mkdtemp()
70 shutil.rmtree(self.tmpdir)
72 def testParseSigsetT(self):
73 parse_sigset_t_fn = utils.process._ParseSigsetT
74 self.assertEqual(len(parse_sigset_t_fn("0")), 0)
75 self.assertEqual(parse_sigset_t_fn("1"), set([1]))
76 self.assertEqual(parse_sigset_t_fn("1000a"), set([2, 4, 17]))
77 self.assertEqual(parse_sigset_t_fn("810002"), set([2, 17, 24, ]))
78 self.assertEqual(parse_sigset_t_fn("0000000180000202"),
80 self.assertEqual(parse_sigset_t_fn("0000000180000002"),
82 self.assertEqual(parse_sigset_t_fn("0000000188000002"),
84 self.assertEqual(parse_sigset_t_fn("000000004b813efb"),
85 set([1, 2, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 17,
87 self.assertEqual(parse_sigset_t_fn("ffffff"), set(range(1, 25)))
89 def testGetProcStatusField(self):
90 for field in ["SigCgt", "Name", "FDSize"]:
91 for value in ["", "0", "cat", " 1234 KB"]:
94 "%s: %s" % (field, value),
97 result = utils.process._GetProcStatusField(pstatus, field)
98 self.assertEqual(result, value.strip())
101 sp = utils.PathJoin(self.tmpdir, "status")
103 utils.WriteFile(sp, data="\n".join([
105 "State: S (sleeping)",
110 "SigBlk: 0000000000010000",
111 "SigIgn: 0000000000384004",
112 "SigCgt: 000000004b813efb",
113 "CapEff: 0000000000000000",
116 self.assert_(utils.IsProcessHandlingSignal(1234, 10, status_path=sp))
118 def testNoSigCgt(self):
119 sp = utils.PathJoin(self.tmpdir, "status")
121 utils.WriteFile(sp, data="\n".join([
125 self.assertRaises(RuntimeError, utils.IsProcessHandlingSignal,
126 1234, 10, status_path=sp)
128 def testNoSuchFile(self):
129 sp = utils.PathJoin(self.tmpdir, "notexist")
131 self.assertFalse(utils.IsProcessHandlingSignal(1234, 10, status_path=sp))
134 def _TestRealProcess():
135 signal.signal(signal.SIGUSR1, signal.SIG_DFL)
136 if utils.IsProcessHandlingSignal(os.getpid(), signal.SIGUSR1):
137 raise Exception("SIGUSR1 is handled when it should not be")
139 signal.signal(signal.SIGUSR1, lambda signum, frame: None)
140 if not utils.IsProcessHandlingSignal(os.getpid(), signal.SIGUSR1):
141 raise Exception("SIGUSR1 is not handled when it should be")
143 signal.signal(signal.SIGUSR1, signal.SIG_IGN)
144 if utils.IsProcessHandlingSignal(os.getpid(), signal.SIGUSR1):
145 raise Exception("SIGUSR1 is not handled when it should be")
147 signal.signal(signal.SIGUSR1, signal.SIG_DFL)
148 if utils.IsProcessHandlingSignal(os.getpid(), signal.SIGUSR1):
149 raise Exception("SIGUSR1 is handled when it should not be")
153 def testRealProcess(self):
154 self.assert_(utils.RunInSeparateProcess(self._TestRealProcess))
157 class _PostforkProcessReadyHelper:
158 """A helper to use with _postfork_fn in RunCmd.
160 It makes sure a process has reached a certain state by reading from a fifo.
162 @ivar write_fd: The fd number to write to
165 def __init__(self, timeout):
166 """Initialize the helper.
168 @param fifo_dir: The dir where we can create the fifo
169 @param timeout: The time in seconds to wait before giving up
172 self.timeout = timeout
173 (self.read_fd, self.write_fd) = os.pipe()
175 def Ready(self, pid):
176 """Waits until the process is ready.
178 @param pid: The pid of the process
181 (read_ready, _, _) = select.select([self.read_fd], [], [], self.timeout)
185 raise AssertionError("Timeout %d reached while waiting for process %d"
186 " to become ready" % (self.timeout, pid))
189 """Cleans up the helper.
192 os.close(self.read_fd)
193 os.close(self.write_fd)
196 class TestRunCmd(testutils.GanetiTestCase):
197 """Testing case for the RunCmd function"""
200 testutils.GanetiTestCase.setUp(self)
201 self.magic = time.ctime() + " ganeti test"
202 self.fname = self._CreateTempFile()
203 self.fifo_tmpdir = tempfile.mkdtemp()
204 self.fifo_file = os.path.join(self.fifo_tmpdir, "ganeti_test_fifo")
205 os.mkfifo(self.fifo_file)
207 # If the process is not ready after 20 seconds we have bigger issues
208 self.proc_ready_helper = _PostforkProcessReadyHelper(20)
211 self.proc_ready_helper.Cleanup()
212 shutil.rmtree(self.fifo_tmpdir)
213 testutils.GanetiTestCase.tearDown(self)
216 """Test successful exit code"""
217 result = utils.RunCmd("/bin/sh -c 'exit 0'")
218 self.assertEqual(result.exit_code, 0)
219 self.assertEqual(result.output, "")
222 """Test fail exit code"""
223 result = utils.RunCmd("/bin/sh -c 'exit 1'")
224 self.assertEqual(result.exit_code, 1)
225 self.assertEqual(result.output, "")
227 def testStdout(self):
228 """Test standard output"""
229 cmd = 'echo -n "%s"' % self.magic
230 result = utils.RunCmd("/bin/sh -c '%s'" % cmd)
231 self.assertEqual(result.stdout, self.magic)
232 result = utils.RunCmd("/bin/sh -c '%s'" % cmd, output=self.fname)
233 self.assertEqual(result.output, "")
234 self.assertFileContent(self.fname, self.magic)
236 def testStderr(self):
237 """Test standard error"""
238 cmd = 'echo -n "%s"' % self.magic
239 result = utils.RunCmd("/bin/sh -c '%s' 1>&2" % cmd)
240 self.assertEqual(result.stderr, self.magic)
241 result = utils.RunCmd("/bin/sh -c '%s' 1>&2" % cmd, output=self.fname)
242 self.assertEqual(result.output, "")
243 self.assertFileContent(self.fname, self.magic)
245 def testCombined(self):
246 """Test combined output"""
247 cmd = 'echo -n "A%s"; echo -n "B%s" 1>&2' % (self.magic, self.magic)
248 expected = "A" + self.magic + "B" + self.magic
249 result = utils.RunCmd("/bin/sh -c '%s'" % cmd)
250 self.assertEqual(result.output, expected)
251 result = utils.RunCmd("/bin/sh -c '%s'" % cmd, output=self.fname)
252 self.assertEqual(result.output, "")
253 self.assertFileContent(self.fname, expected)
255 def testSignal(self):
257 result = utils.RunCmd(["python", "-c",
258 "import os; os.kill(os.getpid(), 15)"])
259 self.assertEqual(result.signal, 15)
260 self.assertEqual(result.output, "")
262 def testTimeoutClean(self):
263 cmd = ("trap 'exit 0' TERM; echo >&%d; read < %s" %
264 (self.proc_ready_helper.write_fd, self.fifo_file))
265 result = utils.RunCmd(["/bin/sh", "-c", cmd], timeout=0.2,
266 noclose_fds=[self.proc_ready_helper.write_fd],
267 _postfork_fn=self.proc_ready_helper.Ready)
268 self.assertEqual(result.exit_code, 0)
270 def testTimeoutKill(self):
271 cmd = ["/bin/sh", "-c", "trap '' TERM; echo >&%d; read < %s" %
272 (self.proc_ready_helper.write_fd, self.fifo_file)]
274 (out, err, status, ta) = \
275 utils.process._RunCmdPipe(cmd, {}, False, "/", False,
276 timeout, [self.proc_ready_helper.write_fd],
278 _postfork_fn=self.proc_ready_helper.Ready)
279 self.assert_(status < 0)
280 self.assertEqual(-status, signal.SIGKILL)
282 def testTimeoutOutputAfterTerm(self):
283 cmd = ("trap 'echo sigtermed; exit 1' TERM; echo >&%d; read < %s" %
284 (self.proc_ready_helper.write_fd, self.fifo_file))
285 result = utils.RunCmd(["/bin/sh", "-c", cmd], timeout=0.2,
286 noclose_fds=[self.proc_ready_helper.write_fd],
287 _postfork_fn=self.proc_ready_helper.Ready)
288 self.assert_(result.failed)
289 self.assertEqual(result.stdout, "sigtermed\n")
291 def testListRun(self):
293 result = utils.RunCmd(["true"])
294 self.assertEqual(result.signal, None)
295 self.assertEqual(result.exit_code, 0)
296 result = utils.RunCmd(["/bin/sh", "-c", "exit 1"])
297 self.assertEqual(result.signal, None)
298 self.assertEqual(result.exit_code, 1)
299 result = utils.RunCmd(["echo", "-n", self.magic])
300 self.assertEqual(result.signal, None)
301 self.assertEqual(result.exit_code, 0)
302 self.assertEqual(result.stdout, self.magic)
304 def testFileEmptyOutput(self):
305 """Test file output"""
306 result = utils.RunCmd(["true"], output=self.fname)
307 self.assertEqual(result.signal, None)
308 self.assertEqual(result.exit_code, 0)
309 self.assertFileContent(self.fname, "")
312 """Test locale environment"""
313 old_env = os.environ.copy()
315 os.environ["LANG"] = "en_US.UTF-8"
316 os.environ["LC_ALL"] = "en_US.UTF-8"
317 result = utils.RunCmd(["locale"])
318 for line in result.output.splitlines():
319 key, value = line.split("=", 1)
320 # Ignore these variables, they're overridden by LC_ALL
321 if key == "LANG" or key == "LANGUAGE":
323 self.failIf(value and value != "C" and value != '"C"',
324 "Variable %s is set to the invalid value '%s'" % (key, value))
328 def testDefaultCwd(self):
329 """Test default working directory"""
330 self.failUnlessEqual(utils.RunCmd(["pwd"]).stdout.strip(), "/")
333 """Test default working directory"""
334 self.failUnlessEqual(utils.RunCmd(["pwd"], cwd="/").stdout.strip(), "/")
335 self.failUnlessEqual(utils.RunCmd(["pwd"], cwd="/tmp").stdout.strip(),
338 self.failUnlessEqual(utils.RunCmd(["pwd"], cwd=cwd).stdout.strip(), cwd)
340 def testResetEnv(self):
341 """Test environment reset functionality"""
342 self.failUnlessEqual(utils.RunCmd(["env"], reset_env=True).stdout.strip(),
344 self.failUnlessEqual(utils.RunCmd(["env"], reset_env=True,
345 env={"FOO": "bar",}).stdout.strip(),
348 def testNoFork(self):
349 """Test that nofork raise an error"""
350 self.assertFalse(utils.process._no_fork)
353 self.assertTrue(utils.process._no_fork)
354 self.assertRaises(errors.ProgrammerError, utils.RunCmd, ["true"])
356 utils.process._no_fork = False
357 self.assertFalse(utils.process._no_fork)
359 def testWrongParams(self):
360 """Test wrong parameters"""
361 self.assertRaises(errors.ProgrammerError, utils.RunCmd, ["true"],
362 output="/dev/null", interactive=True)
364 def testNocloseFds(self):
365 """Test selective fd retention (noclose_fds)"""
366 temp = open(self.fname, "r+")
370 cmd = "read -u %d; echo $REPLY" % temp.fileno()
371 result = utils.RunCmd(["/bin/bash", "-c", cmd])
372 self.assertEqual(result.stdout.strip(), "")
374 result = utils.RunCmd(["/bin/bash", "-c", cmd],
375 noclose_fds=[temp.fileno()])
376 self.assertEqual(result.stdout.strip(), "test")
381 class TestRunParts(testutils.GanetiTestCase):
382 """Testing case for the RunParts function"""
385 self.rundir = tempfile.mkdtemp(prefix="ganeti-test", suffix=".tmp")
388 shutil.rmtree(self.rundir)
391 """Test on an empty dir"""
392 self.failUnlessEqual(utils.RunParts(self.rundir, reset_env=True), [])
394 def testSkipWrongName(self):
395 """Test that wrong files are skipped"""
396 fname = os.path.join(self.rundir, "00test.dot")
397 utils.WriteFile(fname, data="")
398 os.chmod(fname, stat.S_IREAD | stat.S_IEXEC)
399 relname = os.path.basename(fname)
400 self.failUnlessEqual(utils.RunParts(self.rundir, reset_env=True),
401 [(relname, constants.RUNPARTS_SKIP, None)])
403 def testSkipNonExec(self):
404 """Test that non executable files are skipped"""
405 fname = os.path.join(self.rundir, "00test")
406 utils.WriteFile(fname, data="")
407 relname = os.path.basename(fname)
408 self.failUnlessEqual(utils.RunParts(self.rundir, reset_env=True),
409 [(relname, constants.RUNPARTS_SKIP, None)])
412 """Test error on a broken executable"""
413 fname = os.path.join(self.rundir, "00test")
414 utils.WriteFile(fname, data="")
415 os.chmod(fname, stat.S_IREAD | stat.S_IEXEC)
416 (relname, status, error) = utils.RunParts(self.rundir, reset_env=True)[0]
417 self.failUnlessEqual(relname, os.path.basename(fname))
418 self.failUnlessEqual(status, constants.RUNPARTS_ERR)
419 self.failUnless(error)
421 def testSorted(self):
422 """Test executions are sorted"""
424 files.append(os.path.join(self.rundir, "64test"))
425 files.append(os.path.join(self.rundir, "00test"))
426 files.append(os.path.join(self.rundir, "42test"))
429 utils.WriteFile(fname, data="")
431 results = utils.RunParts(self.rundir, reset_env=True)
433 for fname in sorted(files):
434 self.failUnlessEqual(os.path.basename(fname), results.pop(0)[0])
437 """Test correct execution"""
438 fname = os.path.join(self.rundir, "00test")
439 utils.WriteFile(fname, data="#!/bin/sh\n\necho -n ciao")
440 os.chmod(fname, stat.S_IREAD | stat.S_IEXEC)
441 (relname, status, runresult) = \
442 utils.RunParts(self.rundir, reset_env=True)[0]
443 self.failUnlessEqual(relname, os.path.basename(fname))
444 self.failUnlessEqual(status, constants.RUNPARTS_RUN)
445 self.failUnlessEqual(runresult.stdout, "ciao")
447 def testRunFail(self):
448 """Test correct execution, with run failure"""
449 fname = os.path.join(self.rundir, "00test")
450 utils.WriteFile(fname, data="#!/bin/sh\n\nexit 1")
451 os.chmod(fname, stat.S_IREAD | stat.S_IEXEC)
452 (relname, status, runresult) = \
453 utils.RunParts(self.rundir, reset_env=True)[0]
454 self.failUnlessEqual(relname, os.path.basename(fname))
455 self.failUnlessEqual(status, constants.RUNPARTS_RUN)
456 self.failUnlessEqual(runresult.exit_code, 1)
457 self.failUnless(runresult.failed)
459 def testRunMix(self):
461 files.append(os.path.join(self.rundir, "00test"))
462 files.append(os.path.join(self.rundir, "42test"))
463 files.append(os.path.join(self.rundir, "64test"))
464 files.append(os.path.join(self.rundir, "99test"))
468 # 1st has errors in execution
469 utils.WriteFile(files[0], data="#!/bin/sh\n\nexit 1")
470 os.chmod(files[0], stat.S_IREAD | stat.S_IEXEC)
473 utils.WriteFile(files[1], data="")
475 # 3rd cannot execute properly
476 utils.WriteFile(files[2], data="")
477 os.chmod(files[2], stat.S_IREAD | stat.S_IEXEC)
480 utils.WriteFile(files[3], data="#!/bin/sh\n\necho -n ciao")
481 os.chmod(files[3], stat.S_IREAD | stat.S_IEXEC)
483 results = utils.RunParts(self.rundir, reset_env=True)
485 (relname, status, runresult) = results[0]
486 self.failUnlessEqual(relname, os.path.basename(files[0]))
487 self.failUnlessEqual(status, constants.RUNPARTS_RUN)
488 self.failUnlessEqual(runresult.exit_code, 1)
489 self.failUnless(runresult.failed)
491 (relname, status, runresult) = results[1]
492 self.failUnlessEqual(relname, os.path.basename(files[1]))
493 self.failUnlessEqual(status, constants.RUNPARTS_SKIP)
494 self.failUnlessEqual(runresult, None)
496 (relname, status, runresult) = results[2]
497 self.failUnlessEqual(relname, os.path.basename(files[2]))
498 self.failUnlessEqual(status, constants.RUNPARTS_ERR)
499 self.failUnless(runresult)
501 (relname, status, runresult) = results[3]
502 self.failUnlessEqual(relname, os.path.basename(files[3]))
503 self.failUnlessEqual(status, constants.RUNPARTS_RUN)
504 self.failUnlessEqual(runresult.output, "ciao")
505 self.failUnlessEqual(runresult.exit_code, 0)
506 self.failUnless(not runresult.failed)
508 def testMissingDirectory(self):
509 nosuchdir = utils.PathJoin(self.rundir, "no/such/directory")
510 self.assertEqual(utils.RunParts(nosuchdir), [])
513 class TestStartDaemon(testutils.GanetiTestCase):
515 self.tmpdir = tempfile.mkdtemp(prefix="ganeti-test")
516 self.tmpfile = os.path.join(self.tmpdir, "test")
519 shutil.rmtree(self.tmpdir)
522 utils.StartDaemon("echo Hello World > %s" % self.tmpfile)
523 self._wait(self.tmpfile, 60.0, "Hello World")
525 def testShellOutput(self):
526 utils.StartDaemon("echo Hello World", output=self.tmpfile)
527 self._wait(self.tmpfile, 60.0, "Hello World")
529 def testNoShellNoOutput(self):
530 utils.StartDaemon(["pwd"])
532 def testNoShellNoOutputTouch(self):
533 testfile = os.path.join(self.tmpdir, "check")
534 self.failIf(os.path.exists(testfile))
535 utils.StartDaemon(["touch", testfile])
536 self._wait(testfile, 60.0, "")
538 def testNoShellOutput(self):
539 utils.StartDaemon(["pwd"], output=self.tmpfile)
540 self._wait(self.tmpfile, 60.0, "/")
542 def testNoShellOutputCwd(self):
543 utils.StartDaemon(["pwd"], output=self.tmpfile, cwd=os.getcwd())
544 self._wait(self.tmpfile, 60.0, os.getcwd())
546 def testShellEnv(self):
547 utils.StartDaemon("echo \"$GNT_TEST_VAR\"", output=self.tmpfile,
548 env={ "GNT_TEST_VAR": "Hello World", })
549 self._wait(self.tmpfile, 60.0, "Hello World")
551 def testNoShellEnv(self):
552 utils.StartDaemon(["printenv", "GNT_TEST_VAR"], output=self.tmpfile,
553 env={ "GNT_TEST_VAR": "Hello World", })
554 self._wait(self.tmpfile, 60.0, "Hello World")
556 def testOutputFd(self):
557 fd = os.open(self.tmpfile, os.O_WRONLY | os.O_CREAT)
559 utils.StartDaemon(["pwd"], output_fd=fd, cwd=os.getcwd())
562 self._wait(self.tmpfile, 60.0, os.getcwd())
565 pid = utils.StartDaemon("echo $$ > %s" % self.tmpfile)
566 self._wait(self.tmpfile, 60.0, str(pid))
568 def testPidFile(self):
569 pidfile = os.path.join(self.tmpdir, "pid")
570 checkfile = os.path.join(self.tmpdir, "abort")
572 pid = utils.StartDaemon("while sleep 5; do :; done", pidfile=pidfile,
575 fd = os.open(pidfile, os.O_RDONLY)
577 # Check file is locked
578 self.assertRaises(errors.LockError, utils.LockFile, fd)
580 pidtext = os.read(fd, 100)
584 self.assertEqual(int(pidtext.strip()), pid)
586 self.assert_(utils.IsProcessAlive(pid))
588 # No matter what happens, kill daemon
589 utils.KillProcess(pid, timeout=5.0, waitpid=False)
590 self.failIf(utils.IsProcessAlive(pid))
592 self.assertEqual(utils.ReadFile(self.tmpfile), "")
594 def _wait(self, path, timeout, expected):
595 # Due to the asynchronous nature of daemon processes, polling is necessary.
596 # A timeout makes sure the test doesn't hang forever.
598 if not (os.path.isfile(path) and
599 utils.ReadFile(path).strip() == expected):
600 raise utils.RetryAgain()
603 utils.Retry(_CheckFile, (0.01, 1.5, 1.0), timeout)
604 except utils.RetryTimeout:
605 self.fail("Apparently the daemon didn't run in %s seconds and/or"
606 " didn't write the correct output" % timeout)
609 self.assertRaises(errors.OpExecError, utils.StartDaemon,
610 ["./does-NOT-EXIST/here/0123456789"])
611 self.assertRaises(errors.OpExecError, utils.StartDaemon,
612 ["./does-NOT-EXIST/here/0123456789"],
613 output=os.path.join(self.tmpdir, "DIR/NOT/EXIST"))
614 self.assertRaises(errors.OpExecError, utils.StartDaemon,
615 ["./does-NOT-EXIST/here/0123456789"],
616 cwd=os.path.join(self.tmpdir, "DIR/NOT/EXIST"))
617 self.assertRaises(errors.OpExecError, utils.StartDaemon,
618 ["./does-NOT-EXIST/here/0123456789"],
619 output=os.path.join(self.tmpdir, "DIR/NOT/EXIST"))
621 fd = os.open(self.tmpfile, os.O_WRONLY | os.O_CREAT)
623 self.assertRaises(errors.ProgrammerError, utils.StartDaemon,
624 ["./does-NOT-EXIST/here/0123456789"],
625 output=self.tmpfile, output_fd=fd)
630 class RunInSeparateProcess(unittest.TestCase):
632 for exp in [True, False]:
636 self.assertEqual(exp, utils.RunInSeparateProcess(_child))
639 for arg in [0, 1, 999, "Hello World", (1, 2, 3)]:
640 def _child(carg1, carg2):
641 return carg1 == "Foo" and carg2 == arg
643 self.assert_(utils.RunInSeparateProcess(_child, "Foo", arg))
646 parent_pid = os.getpid()
649 return os.getpid() == parent_pid
651 self.failIf(utils.RunInSeparateProcess(_check))
653 def testSignal(self):
655 os.kill(os.getpid(), signal.SIGTERM)
657 self.assertRaises(errors.GenericError,
658 utils.RunInSeparateProcess, _kill)
660 def testException(self):
662 raise errors.GenericError("This is a test")
664 self.assertRaises(errors.GenericError,
665 utils.RunInSeparateProcess, _exc)
668 if __name__ == "__main__":
669 testutils.GanetiTestProgram()