Add unit tests for LUGroupSetParams
[ganeti-local] / test / py / ganeti.utils.process_unittest.py
1 #!/usr/bin/python
2 #
3
4 # Copyright (C) 2011 Google Inc.
5 #
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.
10 #
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.
15 #
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
19 # 02110-1301, USA.
20
21
22 """Script for testing ganeti.utils.process"""
23
24 import unittest
25 import tempfile
26 import shutil
27 import os
28 import stat
29 import time
30 import select
31 import signal
32
33 from ganeti import constants
34 from ganeti import utils
35 from ganeti import errors
36
37 import testutils
38
39
40 class TestIsProcessAlive(unittest.TestCase):
41   """Testing case for IsProcessAlive"""
42
43   def testExists(self):
44     mypid = os.getpid()
45     self.assert_(utils.IsProcessAlive(mypid), "can't find myself running")
46
47   def testNotExisting(self):
48     pid_non_existing = os.fork()
49     if pid_non_existing == 0:
50       os._exit(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")
56
57
58 class TestGetProcStatusPath(unittest.TestCase):
59   def test(self):
60     self.assert_("/1234/" in utils.process._GetProcStatusPath(1234))
61     self.assertNotEqual(utils.process._GetProcStatusPath(1),
62                         utils.process._GetProcStatusPath(2))
63
64
65 class TestIsProcessHandlingSignal(unittest.TestCase):
66   def setUp(self):
67     self.tmpdir = tempfile.mkdtemp()
68
69   def tearDown(self):
70     shutil.rmtree(self.tmpdir)
71
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"),
79                      set([2, 10, 32, 33]))
80     self.assertEqual(parse_sigset_t_fn("0000000180000002"),
81                      set([2, 32, 33]))
82     self.assertEqual(parse_sigset_t_fn("0000000188000002"),
83                      set([2, 28, 32, 33]))
84     self.assertEqual(parse_sigset_t_fn("000000004b813efb"),
85                      set([1, 2, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 17,
86                           24, 25, 26, 28, 31]))
87     self.assertEqual(parse_sigset_t_fn("ffffff"), set(range(1, 25)))
88
89   def testGetProcStatusField(self):
90     for field in ["SigCgt", "Name", "FDSize"]:
91       for value in ["", "0", "cat", "  1234 KB"]:
92         pstatus = "\n".join([
93           "VmPeak: 999 kB",
94           "%s: %s" % (field, value),
95           "TracerPid: 0",
96           ])
97         result = utils.process._GetProcStatusField(pstatus, field)
98         self.assertEqual(result, value.strip())
99
100   def test(self):
101     sp = utils.PathJoin(self.tmpdir, "status")
102
103     utils.WriteFile(sp, data="\n".join([
104       "Name:   bash",
105       "State:  S (sleeping)",
106       "SleepAVG:       98%",
107       "Pid:    22250",
108       "PPid:   10858",
109       "TracerPid:      0",
110       "SigBlk: 0000000000010000",
111       "SigIgn: 0000000000384004",
112       "SigCgt: 000000004b813efb",
113       "CapEff: 0000000000000000",
114       ]))
115
116     self.assert_(utils.IsProcessHandlingSignal(1234, 10, status_path=sp))
117
118   def testNoSigCgt(self):
119     sp = utils.PathJoin(self.tmpdir, "status")
120
121     utils.WriteFile(sp, data="\n".join([
122       "Name:   bash",
123       ]))
124
125     self.assertRaises(RuntimeError, utils.IsProcessHandlingSignal,
126                       1234, 10, status_path=sp)
127
128   def testNoSuchFile(self):
129     sp = utils.PathJoin(self.tmpdir, "notexist")
130
131     self.assertFalse(utils.IsProcessHandlingSignal(1234, 10, status_path=sp))
132
133   @staticmethod
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")
138
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")
142
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")
146
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")
150
151     return True
152
153   def testRealProcess(self):
154     self.assert_(utils.RunInSeparateProcess(self._TestRealProcess))
155
156
157 class _PostforkProcessReadyHelper:
158   """A helper to use with C{postfork_fn} in RunCmd.
159
160   It makes sure a process has reached a certain state by reading from a fifo.
161
162   @ivar write_fd: The fd number to write to
163
164   """
165   def __init__(self, timeout):
166     """Initialize the helper.
167
168     @param fifo_dir: The dir where we can create the fifo
169     @param timeout: The time in seconds to wait before giving up
170
171     """
172     self.timeout = timeout
173     (self.read_fd, self.write_fd) = os.pipe()
174
175   def Ready(self, pid):
176     """Waits until the process is ready.
177
178     @param pid: The pid of the process
179
180     """
181     (read_ready, _, _) = select.select([self.read_fd], [], [], self.timeout)
182
183     if not read_ready:
184       # We hit the timeout
185       raise AssertionError("Timeout %d reached while waiting for process %d"
186                            " to become ready" % (self.timeout, pid))
187
188   def Cleanup(self):
189     """Cleans up the helper.
190
191     """
192     os.close(self.read_fd)
193     os.close(self.write_fd)
194
195
196 class TestRunCmd(testutils.GanetiTestCase):
197   """Testing case for the RunCmd function"""
198
199   def setUp(self):
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)
206
207     # If the process is not ready after 20 seconds we have bigger issues
208     self.proc_ready_helper = _PostforkProcessReadyHelper(20)
209
210   def tearDown(self):
211     self.proc_ready_helper.Cleanup()
212     shutil.rmtree(self.fifo_tmpdir)
213     testutils.GanetiTestCase.tearDown(self)
214
215   def testOk(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, "")
220
221   def testFail(self):
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, "")
226
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)
235
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)
244
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)
254
255   def testSignal(self):
256     """Test signal"""
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, "")
261
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)
269
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)]
273     timeout = 0.2
274     (out, err, status, ta) = \
275       utils.process._RunCmdPipe(cmd, {}, False, "/", False,
276                                 timeout, [self.proc_ready_helper.write_fd],
277                                 None,
278                                 _linger_timeout=0.2,
279                                 postfork_fn=self.proc_ready_helper.Ready)
280     self.assert_(status < 0)
281     self.assertEqual(-status, signal.SIGKILL)
282
283   def testTimeoutOutputAfterTerm(self):
284     cmd = ("trap 'echo sigtermed; exit 1' TERM; echo >&%d; read < %s" %
285            (self.proc_ready_helper.write_fd, self.fifo_file))
286     result = utils.RunCmd(["/bin/sh", "-c", cmd], timeout=0.2,
287                           noclose_fds=[self.proc_ready_helper.write_fd],
288                           postfork_fn=self.proc_ready_helper.Ready)
289     self.assert_(result.failed)
290     self.assertEqual(result.stdout, "sigtermed\n")
291
292   def testListRun(self):
293     """Test list runs"""
294     result = utils.RunCmd(["true"])
295     self.assertEqual(result.signal, None)
296     self.assertEqual(result.exit_code, 0)
297     result = utils.RunCmd(["/bin/sh", "-c", "exit 1"])
298     self.assertEqual(result.signal, None)
299     self.assertEqual(result.exit_code, 1)
300     result = utils.RunCmd(["echo", "-n", self.magic])
301     self.assertEqual(result.signal, None)
302     self.assertEqual(result.exit_code, 0)
303     self.assertEqual(result.stdout, self.magic)
304
305   def testFileEmptyOutput(self):
306     """Test file output"""
307     result = utils.RunCmd(["true"], output=self.fname)
308     self.assertEqual(result.signal, None)
309     self.assertEqual(result.exit_code, 0)
310     self.assertFileContent(self.fname, "")
311
312   def testLang(self):
313     """Test locale environment"""
314     old_env = os.environ.copy()
315     try:
316       os.environ["LANG"] = "en_US.UTF-8"
317       os.environ["LC_ALL"] = "en_US.UTF-8"
318       result = utils.RunCmd(["locale"])
319       for line in result.output.splitlines():
320         key, value = line.split("=", 1)
321         # Ignore these variables, they're overridden by LC_ALL
322         if key == "LANG" or key == "LANGUAGE":
323           continue
324         self.failIf(value and value != "C" and value != '"C"',
325             "Variable %s is set to the invalid value '%s'" % (key, value))
326     finally:
327       os.environ = old_env
328
329   def testDefaultCwd(self):
330     """Test default working directory"""
331     self.failUnlessEqual(utils.RunCmd(["pwd"]).stdout.strip(), "/")
332
333   def testCwd(self):
334     """Test default working directory"""
335     self.failUnlessEqual(utils.RunCmd(["pwd"], cwd="/").stdout.strip(), "/")
336     self.failUnlessEqual(utils.RunCmd(["pwd"], cwd="/tmp").stdout.strip(),
337                          "/tmp")
338     cwd = os.getcwd()
339     self.failUnlessEqual(utils.RunCmd(["pwd"], cwd=cwd).stdout.strip(), cwd)
340
341   def testResetEnv(self):
342     """Test environment reset functionality"""
343     self.failUnlessEqual(utils.RunCmd(["env"], reset_env=True).stdout.strip(),
344                          "")
345     self.failUnlessEqual(utils.RunCmd(["env"], reset_env=True,
346                                       env={"FOO": "bar",}).stdout.strip(),
347                          "FOO=bar")
348
349   def testNoFork(self):
350     """Test that nofork raise an error"""
351     self.assertFalse(utils.process._no_fork)
352     utils.DisableFork()
353     try:
354       self.assertTrue(utils.process._no_fork)
355       self.assertRaises(errors.ProgrammerError, utils.RunCmd, ["true"])
356     finally:
357       utils.process._no_fork = False
358     self.assertFalse(utils.process._no_fork)
359
360   def testWrongParams(self):
361     """Test wrong parameters"""
362     self.assertRaises(errors.ProgrammerError, utils.RunCmd, ["true"],
363                       output="/dev/null", interactive=True)
364
365   def testNocloseFds(self):
366     """Test selective fd retention (noclose_fds)"""
367     temp = open(self.fname, "r+")
368     try:
369       temp.write("test")
370       temp.seek(0)
371       cmd = "read -u %d; echo $REPLY" % temp.fileno()
372       result = utils.RunCmd(["/bin/bash", "-c", cmd])
373       self.assertEqual(result.stdout.strip(), "")
374       temp.seek(0)
375       result = utils.RunCmd(["/bin/bash", "-c", cmd],
376                             noclose_fds=[temp.fileno()])
377       self.assertEqual(result.stdout.strip(), "test")
378     finally:
379       temp.close()
380
381   def testNoInputRead(self):
382     testfile = testutils.TestDataFilename("cert1.pem")
383
384     result = utils.RunCmd(["cat"], timeout=10.0)
385     self.assertFalse(result.failed)
386     self.assertEqual(result.stderr, "")
387     self.assertEqual(result.stdout, "")
388
389   def testInputFileHandle(self):
390     testfile = testutils.TestDataFilename("cert1.pem")
391
392     result = utils.RunCmd(["cat"], input_fd=open(testfile, "r"))
393     self.assertFalse(result.failed)
394     self.assertEqual(result.stdout, utils.ReadFile(testfile))
395     self.assertEqual(result.stderr, "")
396
397   def testInputNumericFileDescriptor(self):
398     testfile = testutils.TestDataFilename("cert2.pem")
399
400     fh = open(testfile, "r")
401     try:
402       result = utils.RunCmd(["cat"], input_fd=fh.fileno())
403     finally:
404       fh.close()
405
406     self.assertFalse(result.failed)
407     self.assertEqual(result.stdout, utils.ReadFile(testfile))
408     self.assertEqual(result.stderr, "")
409
410   def testInputWithCloseFds(self):
411     testfile = testutils.TestDataFilename("cert1.pem")
412
413     temp = open(self.fname, "r+")
414     try:
415       temp.write("test283523367")
416       temp.seek(0)
417
418       result = utils.RunCmd(["/bin/bash", "-c",
419                              ("cat && read -u %s; echo $REPLY" %
420                               temp.fileno())],
421                             input_fd=open(testfile, "r"),
422                             noclose_fds=[temp.fileno()])
423       self.assertFalse(result.failed)
424       self.assertEqual(result.stdout.strip(),
425                        utils.ReadFile(testfile) + "test283523367")
426       self.assertEqual(result.stderr, "")
427     finally:
428       temp.close()
429
430   def testOutputAndInteractive(self):
431     self.assertRaises(errors.ProgrammerError, utils.RunCmd,
432                       [], output=self.fname, interactive=True)
433
434   def testOutputAndInput(self):
435     self.assertRaises(errors.ProgrammerError, utils.RunCmd,
436                       [], output=self.fname, input_fd=open(self.fname))
437
438
439 class TestRunParts(testutils.GanetiTestCase):
440   """Testing case for the RunParts function"""
441
442   def setUp(self):
443     self.rundir = tempfile.mkdtemp(prefix="ganeti-test", suffix=".tmp")
444
445   def tearDown(self):
446     shutil.rmtree(self.rundir)
447
448   def testEmpty(self):
449     """Test on an empty dir"""
450     self.failUnlessEqual(utils.RunParts(self.rundir, reset_env=True), [])
451
452   def testSkipWrongName(self):
453     """Test that wrong files are skipped"""
454     fname = os.path.join(self.rundir, "00test.dot")
455     utils.WriteFile(fname, data="")
456     os.chmod(fname, stat.S_IREAD | stat.S_IEXEC)
457     relname = os.path.basename(fname)
458     self.failUnlessEqual(utils.RunParts(self.rundir, reset_env=True),
459                          [(relname, constants.RUNPARTS_SKIP, None)])
460
461   def testSkipNonExec(self):
462     """Test that non executable files are skipped"""
463     fname = os.path.join(self.rundir, "00test")
464     utils.WriteFile(fname, data="")
465     relname = os.path.basename(fname)
466     self.failUnlessEqual(utils.RunParts(self.rundir, reset_env=True),
467                          [(relname, constants.RUNPARTS_SKIP, None)])
468
469   def testError(self):
470     """Test error on a broken executable"""
471     fname = os.path.join(self.rundir, "00test")
472     utils.WriteFile(fname, data="")
473     os.chmod(fname, stat.S_IREAD | stat.S_IEXEC)
474     (relname, status, error) = utils.RunParts(self.rundir, reset_env=True)[0]
475     self.failUnlessEqual(relname, os.path.basename(fname))
476     self.failUnlessEqual(status, constants.RUNPARTS_ERR)
477     self.failUnless(error)
478
479   def testSorted(self):
480     """Test executions are sorted"""
481     files = []
482     files.append(os.path.join(self.rundir, "64test"))
483     files.append(os.path.join(self.rundir, "00test"))
484     files.append(os.path.join(self.rundir, "42test"))
485
486     for fname in files:
487       utils.WriteFile(fname, data="")
488
489     results = utils.RunParts(self.rundir, reset_env=True)
490
491     for fname in sorted(files):
492       self.failUnlessEqual(os.path.basename(fname), results.pop(0)[0])
493
494   def testOk(self):
495     """Test correct execution"""
496     fname = os.path.join(self.rundir, "00test")
497     utils.WriteFile(fname, data="#!/bin/sh\n\necho -n ciao")
498     os.chmod(fname, stat.S_IREAD | stat.S_IEXEC)
499     (relname, status, runresult) = \
500       utils.RunParts(self.rundir, reset_env=True)[0]
501     self.failUnlessEqual(relname, os.path.basename(fname))
502     self.failUnlessEqual(status, constants.RUNPARTS_RUN)
503     self.failUnlessEqual(runresult.stdout, "ciao")
504
505   def testRunFail(self):
506     """Test correct execution, with run failure"""
507     fname = os.path.join(self.rundir, "00test")
508     utils.WriteFile(fname, data="#!/bin/sh\n\nexit 1")
509     os.chmod(fname, stat.S_IREAD | stat.S_IEXEC)
510     (relname, status, runresult) = \
511       utils.RunParts(self.rundir, reset_env=True)[0]
512     self.failUnlessEqual(relname, os.path.basename(fname))
513     self.failUnlessEqual(status, constants.RUNPARTS_RUN)
514     self.failUnlessEqual(runresult.exit_code, 1)
515     self.failUnless(runresult.failed)
516
517   def testRunMix(self):
518     files = []
519     files.append(os.path.join(self.rundir, "00test"))
520     files.append(os.path.join(self.rundir, "42test"))
521     files.append(os.path.join(self.rundir, "64test"))
522     files.append(os.path.join(self.rundir, "99test"))
523
524     files.sort()
525
526     # 1st has errors in execution
527     utils.WriteFile(files[0], data="#!/bin/sh\n\nexit 1")
528     os.chmod(files[0], stat.S_IREAD | stat.S_IEXEC)
529
530     # 2nd is skipped
531     utils.WriteFile(files[1], data="")
532
533     # 3rd cannot execute properly
534     utils.WriteFile(files[2], data="")
535     os.chmod(files[2], stat.S_IREAD | stat.S_IEXEC)
536
537     # 4th execs
538     utils.WriteFile(files[3], data="#!/bin/sh\n\necho -n ciao")
539     os.chmod(files[3], stat.S_IREAD | stat.S_IEXEC)
540
541     results = utils.RunParts(self.rundir, reset_env=True)
542
543     (relname, status, runresult) = results[0]
544     self.failUnlessEqual(relname, os.path.basename(files[0]))
545     self.failUnlessEqual(status, constants.RUNPARTS_RUN)
546     self.failUnlessEqual(runresult.exit_code, 1)
547     self.failUnless(runresult.failed)
548
549     (relname, status, runresult) = results[1]
550     self.failUnlessEqual(relname, os.path.basename(files[1]))
551     self.failUnlessEqual(status, constants.RUNPARTS_SKIP)
552     self.failUnlessEqual(runresult, None)
553
554     (relname, status, runresult) = results[2]
555     self.failUnlessEqual(relname, os.path.basename(files[2]))
556     self.failUnlessEqual(status, constants.RUNPARTS_ERR)
557     self.failUnless(runresult)
558
559     (relname, status, runresult) = results[3]
560     self.failUnlessEqual(relname, os.path.basename(files[3]))
561     self.failUnlessEqual(status, constants.RUNPARTS_RUN)
562     self.failUnlessEqual(runresult.output, "ciao")
563     self.failUnlessEqual(runresult.exit_code, 0)
564     self.failUnless(not runresult.failed)
565
566   def testMissingDirectory(self):
567     nosuchdir = utils.PathJoin(self.rundir, "no/such/directory")
568     self.assertEqual(utils.RunParts(nosuchdir), [])
569
570
571 class TestStartDaemon(testutils.GanetiTestCase):
572   def setUp(self):
573     self.tmpdir = tempfile.mkdtemp(prefix="ganeti-test")
574     self.tmpfile = os.path.join(self.tmpdir, "test")
575
576   def tearDown(self):
577     shutil.rmtree(self.tmpdir)
578
579   def testShell(self):
580     utils.StartDaemon("echo Hello World > %s" % self.tmpfile)
581     self._wait(self.tmpfile, 60.0, "Hello World")
582
583   def testShellOutput(self):
584     utils.StartDaemon("echo Hello World", output=self.tmpfile)
585     self._wait(self.tmpfile, 60.0, "Hello World")
586
587   def testNoShellNoOutput(self):
588     utils.StartDaemon(["pwd"])
589
590   def testNoShellNoOutputTouch(self):
591     testfile = os.path.join(self.tmpdir, "check")
592     self.failIf(os.path.exists(testfile))
593     utils.StartDaemon(["touch", testfile])
594     self._wait(testfile, 60.0, "")
595
596   def testNoShellOutput(self):
597     utils.StartDaemon(["pwd"], output=self.tmpfile)
598     self._wait(self.tmpfile, 60.0, "/")
599
600   def testNoShellOutputCwd(self):
601     utils.StartDaemon(["pwd"], output=self.tmpfile, cwd=os.getcwd())
602     self._wait(self.tmpfile, 60.0, os.getcwd())
603
604   def testShellEnv(self):
605     utils.StartDaemon("echo \"$GNT_TEST_VAR\"", output=self.tmpfile,
606                       env={ "GNT_TEST_VAR": "Hello World", })
607     self._wait(self.tmpfile, 60.0, "Hello World")
608
609   def testNoShellEnv(self):
610     utils.StartDaemon(["printenv", "GNT_TEST_VAR"], output=self.tmpfile,
611                       env={ "GNT_TEST_VAR": "Hello World", })
612     self._wait(self.tmpfile, 60.0, "Hello World")
613
614   def testOutputFd(self):
615     fd = os.open(self.tmpfile, os.O_WRONLY | os.O_CREAT)
616     try:
617       utils.StartDaemon(["pwd"], output_fd=fd, cwd=os.getcwd())
618     finally:
619       os.close(fd)
620     self._wait(self.tmpfile, 60.0, os.getcwd())
621
622   def testPid(self):
623     pid = utils.StartDaemon("echo $$ > %s" % self.tmpfile)
624     self._wait(self.tmpfile, 60.0, str(pid))
625
626   def testPidFile(self):
627     pidfile = os.path.join(self.tmpdir, "pid")
628     checkfile = os.path.join(self.tmpdir, "abort")
629
630     pid = utils.StartDaemon("while sleep 5; do :; done", pidfile=pidfile,
631                             output=self.tmpfile)
632     try:
633       fd = os.open(pidfile, os.O_RDONLY)
634       try:
635         # Check file is locked
636         self.assertRaises(errors.LockError, utils.LockFile, fd)
637
638         pidtext = os.read(fd, 100)
639       finally:
640         os.close(fd)
641
642       self.assertEqual(int(pidtext.strip()), pid)
643
644       self.assert_(utils.IsProcessAlive(pid))
645     finally:
646       # No matter what happens, kill daemon
647       utils.KillProcess(pid, timeout=5.0, waitpid=False)
648       self.failIf(utils.IsProcessAlive(pid))
649
650     self.assertEqual(utils.ReadFile(self.tmpfile), "")
651
652   def _wait(self, path, timeout, expected):
653     # Due to the asynchronous nature of daemon processes, polling is necessary.
654     # A timeout makes sure the test doesn't hang forever.
655     def _CheckFile():
656       if not (os.path.isfile(path) and
657               utils.ReadFile(path).strip() == expected):
658         raise utils.RetryAgain()
659
660     try:
661       utils.Retry(_CheckFile, (0.01, 1.5, 1.0), timeout)
662     except utils.RetryTimeout:
663       self.fail("Apparently the daemon didn't run in %s seconds and/or"
664                 " didn't write the correct output" % timeout)
665
666   def testError(self):
667     self.assertRaises(errors.OpExecError, utils.StartDaemon,
668                       ["./does-NOT-EXIST/here/0123456789"])
669     self.assertRaises(errors.OpExecError, utils.StartDaemon,
670                       ["./does-NOT-EXIST/here/0123456789"],
671                       output=os.path.join(self.tmpdir, "DIR/NOT/EXIST"))
672     self.assertRaises(errors.OpExecError, utils.StartDaemon,
673                       ["./does-NOT-EXIST/here/0123456789"],
674                       cwd=os.path.join(self.tmpdir, "DIR/NOT/EXIST"))
675     self.assertRaises(errors.OpExecError, utils.StartDaemon,
676                       ["./does-NOT-EXIST/here/0123456789"],
677                       output=os.path.join(self.tmpdir, "DIR/NOT/EXIST"))
678
679     fd = os.open(self.tmpfile, os.O_WRONLY | os.O_CREAT)
680     try:
681       self.assertRaises(errors.ProgrammerError, utils.StartDaemon,
682                         ["./does-NOT-EXIST/here/0123456789"],
683                         output=self.tmpfile, output_fd=fd)
684     finally:
685       os.close(fd)
686
687
688 class RunInSeparateProcess(unittest.TestCase):
689   def test(self):
690     for exp in [True, False]:
691       def _child():
692         return exp
693
694       self.assertEqual(exp, utils.RunInSeparateProcess(_child))
695
696   def testArgs(self):
697     for arg in [0, 1, 999, "Hello World", (1, 2, 3)]:
698       def _child(carg1, carg2):
699         return carg1 == "Foo" and carg2 == arg
700
701       self.assert_(utils.RunInSeparateProcess(_child, "Foo", arg))
702
703   def testPid(self):
704     parent_pid = os.getpid()
705
706     def _check():
707       return os.getpid() == parent_pid
708
709     self.failIf(utils.RunInSeparateProcess(_check))
710
711   def testSignal(self):
712     def _kill():
713       os.kill(os.getpid(), signal.SIGTERM)
714
715     self.assertRaises(errors.GenericError,
716                       utils.RunInSeparateProcess, _kill)
717
718   def testException(self):
719     def _exc():
720       raise errors.GenericError("This is a test")
721
722     self.assertRaises(errors.GenericError,
723                       utils.RunInSeparateProcess, _exc)
724
725
726 if __name__ == "__main__":
727   testutils.GanetiTestProgram()