root / test / py / ganeti.utils.process_unittest.py @ 0b6c56bb
History | View | Annotate | Download (24.7 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 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() |