Statistics
| Branch: | Tag: | Revision:

root / test / ganeti.utils.process_unittest.py @ d6491981

History | View | Annotate | Download (22.8 kB)

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 _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
                                _linger_timeout=0.2,
278
                                _postfork_fn=self.proc_ready_helper.Ready)
279
    self.assert_(status < 0)
280
    self.assertEqual(-status, signal.SIGKILL)
281

    
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")
290

    
291
  def testListRun(self):
292
    """Test list runs"""
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)
303

    
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, "")
310

    
311
  def testLang(self):
312
    """Test locale environment"""
313
    old_env = os.environ.copy()
314
    try:
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":
322
          continue
323
        self.failIf(value and value != "C" and value != '"C"',
324
            "Variable %s is set to the invalid value '%s'" % (key, value))
325
    finally:
326
      os.environ = old_env
327

    
328
  def testDefaultCwd(self):
329
    """Test default working directory"""
330
    self.failUnlessEqual(utils.RunCmd(["pwd"]).stdout.strip(), "/")
331

    
332
  def testCwd(self):
333
    """Test default working directory"""
334
    self.failUnlessEqual(utils.RunCmd(["pwd"], cwd="/").stdout.strip(), "/")
335
    self.failUnlessEqual(utils.RunCmd(["pwd"], cwd="/tmp").stdout.strip(),
336
                         "/tmp")
337
    cwd = os.getcwd()
338
    self.failUnlessEqual(utils.RunCmd(["pwd"], cwd=cwd).stdout.strip(), cwd)
339

    
340
  def testResetEnv(self):
341
    """Test environment reset functionality"""
342
    self.failUnlessEqual(utils.RunCmd(["env"], reset_env=True).stdout.strip(),
343
                         "")
344
    self.failUnlessEqual(utils.RunCmd(["env"], reset_env=True,
345
                                      env={"FOO": "bar",}).stdout.strip(),
346
                         "FOO=bar")
347

    
348
  def testNoFork(self):
349
    """Test that nofork raise an error"""
350
    self.assertFalse(utils.process._no_fork)
351
    utils.DisableFork()
352
    try:
353
      self.assertTrue(utils.process._no_fork)
354
      self.assertRaises(errors.ProgrammerError, utils.RunCmd, ["true"])
355
    finally:
356
      utils.process._no_fork = False
357
    self.assertFalse(utils.process._no_fork)
358

    
359
  def testWrongParams(self):
360
    """Test wrong parameters"""
361
    self.assertRaises(errors.ProgrammerError, utils.RunCmd, ["true"],
362
                      output="/dev/null", interactive=True)
363

    
364
  def testNocloseFds(self):
365
    """Test selective fd retention (noclose_fds)"""
366
    temp = open(self.fname, "r+")
367
    try:
368
      temp.write("test")
369
      temp.seek(0)
370
      cmd = "read -u %d; echo $REPLY" % temp.fileno()
371
      result = utils.RunCmd(["/bin/bash", "-c", cmd])
372
      self.assertEqual(result.stdout.strip(), "")
373
      temp.seek(0)
374
      result = utils.RunCmd(["/bin/bash", "-c", cmd],
375
                            noclose_fds=[temp.fileno()])
376
      self.assertEqual(result.stdout.strip(), "test")
377
    finally:
378
      temp.close()
379

    
380

    
381
class TestRunParts(testutils.GanetiTestCase):
382
  """Testing case for the RunParts function"""
383

    
384
  def setUp(self):
385
    self.rundir = tempfile.mkdtemp(prefix="ganeti-test", suffix=".tmp")
386

    
387
  def tearDown(self):
388
    shutil.rmtree(self.rundir)
389

    
390
  def testEmpty(self):
391
    """Test on an empty dir"""
392
    self.failUnlessEqual(utils.RunParts(self.rundir, reset_env=True), [])
393

    
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)])
402

    
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)])
410

    
411
  def testError(self):
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)
420

    
421
  def testSorted(self):
422
    """Test executions are sorted"""
423
    files = []
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"))
427

    
428
    for fname in files:
429
      utils.WriteFile(fname, data="")
430

    
431
    results = utils.RunParts(self.rundir, reset_env=True)
432

    
433
    for fname in sorted(files):
434
      self.failUnlessEqual(os.path.basename(fname), results.pop(0)[0])
435

    
436
  def testOk(self):
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")
446

    
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)
458

    
459
  def testRunMix(self):
460
    files = []
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"))
465

    
466
    files.sort()
467

    
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)
471

    
472
    # 2nd is skipped
473
    utils.WriteFile(files[1], data="")
474

    
475
    # 3rd cannot execute properly
476
    utils.WriteFile(files[2], data="")
477
    os.chmod(files[2], stat.S_IREAD | stat.S_IEXEC)
478

    
479
    # 4th execs
480
    utils.WriteFile(files[3], data="#!/bin/sh\n\necho -n ciao")
481
    os.chmod(files[3], stat.S_IREAD | stat.S_IEXEC)
482

    
483
    results = utils.RunParts(self.rundir, reset_env=True)
484

    
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)
490

    
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)
495

    
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)
500

    
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)
507

    
508
  def testMissingDirectory(self):
509
    nosuchdir = utils.PathJoin(self.rundir, "no/such/directory")
510
    self.assertEqual(utils.RunParts(nosuchdir), [])
511

    
512

    
513
class TestStartDaemon(testutils.GanetiTestCase):
514
  def setUp(self):
515
    self.tmpdir = tempfile.mkdtemp(prefix="ganeti-test")
516
    self.tmpfile = os.path.join(self.tmpdir, "test")
517

    
518
  def tearDown(self):
519
    shutil.rmtree(self.tmpdir)
520

    
521
  def testShell(self):
522
    utils.StartDaemon("echo Hello World > %s" % self.tmpfile)
523
    self._wait(self.tmpfile, 60.0, "Hello World")
524

    
525
  def testShellOutput(self):
526
    utils.StartDaemon("echo Hello World", output=self.tmpfile)
527
    self._wait(self.tmpfile, 60.0, "Hello World")
528

    
529
  def testNoShellNoOutput(self):
530
    utils.StartDaemon(["pwd"])
531

    
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, "")
537

    
538
  def testNoShellOutput(self):
539
    utils.StartDaemon(["pwd"], output=self.tmpfile)
540
    self._wait(self.tmpfile, 60.0, "/")
541

    
542
  def testNoShellOutputCwd(self):
543
    utils.StartDaemon(["pwd"], output=self.tmpfile, cwd=os.getcwd())
544
    self._wait(self.tmpfile, 60.0, os.getcwd())
545

    
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")
550

    
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")
555

    
556
  def testOutputFd(self):
557
    fd = os.open(self.tmpfile, os.O_WRONLY | os.O_CREAT)
558
    try:
559
      utils.StartDaemon(["pwd"], output_fd=fd, cwd=os.getcwd())
560
    finally:
561
      os.close(fd)
562
    self._wait(self.tmpfile, 60.0, os.getcwd())
563

    
564
  def testPid(self):
565
    pid = utils.StartDaemon("echo $$ > %s" % self.tmpfile)
566
    self._wait(self.tmpfile, 60.0, str(pid))
567

    
568
  def testPidFile(self):
569
    pidfile = os.path.join(self.tmpdir, "pid")
570
    checkfile = os.path.join(self.tmpdir, "abort")
571

    
572
    pid = utils.StartDaemon("while sleep 5; do :; done", pidfile=pidfile,
573
                            output=self.tmpfile)
574
    try:
575
      fd = os.open(pidfile, os.O_RDONLY)
576
      try:
577
        # Check file is locked
578
        self.assertRaises(errors.LockError, utils.LockFile, fd)
579

    
580
        pidtext = os.read(fd, 100)
581
      finally:
582
        os.close(fd)
583

    
584
      self.assertEqual(int(pidtext.strip()), pid)
585

    
586
      self.assert_(utils.IsProcessAlive(pid))
587
    finally:
588
      # No matter what happens, kill daemon
589
      utils.KillProcess(pid, timeout=5.0, waitpid=False)
590
      self.failIf(utils.IsProcessAlive(pid))
591

    
592
    self.assertEqual(utils.ReadFile(self.tmpfile), "")
593

    
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.
597
    def _CheckFile():
598
      if not (os.path.isfile(path) and
599
              utils.ReadFile(path).strip() == expected):
600
        raise utils.RetryAgain()
601

    
602
    try:
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)
607

    
608
  def testError(self):
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"))
620

    
621
    fd = os.open(self.tmpfile, os.O_WRONLY | os.O_CREAT)
622
    try:
623
      self.assertRaises(errors.ProgrammerError, utils.StartDaemon,
624
                        ["./does-NOT-EXIST/here/0123456789"],
625
                        output=self.tmpfile, output_fd=fd)
626
    finally:
627
      os.close(fd)
628

    
629

    
630
class RunInSeparateProcess(unittest.TestCase):
631
  def test(self):
632
    for exp in [True, False]:
633
      def _child():
634
        return exp
635

    
636
      self.assertEqual(exp, utils.RunInSeparateProcess(_child))
637

    
638
  def testArgs(self):
639
    for arg in [0, 1, 999, "Hello World", (1, 2, 3)]:
640
      def _child(carg1, carg2):
641
        return carg1 == "Foo" and carg2 == arg
642

    
643
      self.assert_(utils.RunInSeparateProcess(_child, "Foo", arg))
644

    
645
  def testPid(self):
646
    parent_pid = os.getpid()
647

    
648
    def _check():
649
      return os.getpid() == parent_pid
650

    
651
    self.failIf(utils.RunInSeparateProcess(_check))
652

    
653
  def testSignal(self):
654
    def _kill():
655
      os.kill(os.getpid(), signal.SIGTERM)
656

    
657
    self.assertRaises(errors.GenericError,
658
                      utils.RunInSeparateProcess, _kill)
659

    
660
  def testException(self):
661
    def _exc():
662
      raise errors.GenericError("This is a test")
663

    
664
    self.assertRaises(errors.GenericError,
665
                      utils.RunInSeparateProcess, _exc)
666

    
667

    
668
if __name__ == "__main__":
669
  testutils.GanetiTestProgram()