Statistics
| Branch: | Tag: | Revision:

root / test / ganeti.utils_unittest.py @ 17b97ab3

History | View | Annotate | Download (30.7 kB)

1
#!/usr/bin/python
2
#
3

    
4
# Copyright (C) 2006, 2007, 2010, 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 unittesting the utils module"""
23

    
24
import errno
25
import fcntl
26
import glob
27
import os
28
import os.path
29
import re
30
import shutil
31
import signal
32
import socket
33
import stat
34
import tempfile
35
import time
36
import unittest
37
import warnings
38
import random
39
import operator
40

    
41
import testutils
42
from ganeti import constants
43
from ganeti import compat
44
from ganeti import utils
45
from ganeti import errors
46
from ganeti.utils import RunCmd, \
47
     FirstFree, \
48
     RunParts
49

    
50

    
51
class TestIsProcessAlive(unittest.TestCase):
52
  """Testing case for IsProcessAlive"""
53

    
54
  def testExists(self):
55
    mypid = os.getpid()
56
    self.assert_(utils.IsProcessAlive(mypid), "can't find myself running")
57

    
58
  def testNotExisting(self):
59
    pid_non_existing = os.fork()
60
    if pid_non_existing == 0:
61
      os._exit(0)
62
    elif pid_non_existing < 0:
63
      raise SystemError("can't fork")
64
    os.waitpid(pid_non_existing, 0)
65
    self.assertFalse(utils.IsProcessAlive(pid_non_existing),
66
                     "nonexisting process detected")
67

    
68

    
69
class TestGetProcStatusPath(unittest.TestCase):
70
  def test(self):
71
    self.assert_("/1234/" in utils._GetProcStatusPath(1234))
72
    self.assertNotEqual(utils._GetProcStatusPath(1),
73
                        utils._GetProcStatusPath(2))
74

    
75

    
76
class TestIsProcessHandlingSignal(unittest.TestCase):
77
  def setUp(self):
78
    self.tmpdir = tempfile.mkdtemp()
79

    
80
  def tearDown(self):
81
    shutil.rmtree(self.tmpdir)
82

    
83
  def testParseSigsetT(self):
84
    self.assertEqual(len(utils._ParseSigsetT("0")), 0)
85
    self.assertEqual(utils._ParseSigsetT("1"), set([1]))
86
    self.assertEqual(utils._ParseSigsetT("1000a"), set([2, 4, 17]))
87
    self.assertEqual(utils._ParseSigsetT("810002"), set([2, 17, 24, ]))
88
    self.assertEqual(utils._ParseSigsetT("0000000180000202"),
89
                     set([2, 10, 32, 33]))
90
    self.assertEqual(utils._ParseSigsetT("0000000180000002"),
91
                     set([2, 32, 33]))
92
    self.assertEqual(utils._ParseSigsetT("0000000188000002"),
93
                     set([2, 28, 32, 33]))
94
    self.assertEqual(utils._ParseSigsetT("000000004b813efb"),
95
                     set([1, 2, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 17,
96
                          24, 25, 26, 28, 31]))
97
    self.assertEqual(utils._ParseSigsetT("ffffff"), set(range(1, 25)))
98

    
99
  def testGetProcStatusField(self):
100
    for field in ["SigCgt", "Name", "FDSize"]:
101
      for value in ["", "0", "cat", "  1234 KB"]:
102
        pstatus = "\n".join([
103
          "VmPeak: 999 kB",
104
          "%s: %s" % (field, value),
105
          "TracerPid: 0",
106
          ])
107
        result = utils._GetProcStatusField(pstatus, field)
108
        self.assertEqual(result, value.strip())
109

    
110
  def test(self):
111
    sp = utils.PathJoin(self.tmpdir, "status")
112

    
113
    utils.WriteFile(sp, data="\n".join([
114
      "Name:   bash",
115
      "State:  S (sleeping)",
116
      "SleepAVG:       98%",
117
      "Pid:    22250",
118
      "PPid:   10858",
119
      "TracerPid:      0",
120
      "SigBlk: 0000000000010000",
121
      "SigIgn: 0000000000384004",
122
      "SigCgt: 000000004b813efb",
123
      "CapEff: 0000000000000000",
124
      ]))
125

    
126
    self.assert_(utils.IsProcessHandlingSignal(1234, 10, status_path=sp))
127

    
128
  def testNoSigCgt(self):
129
    sp = utils.PathJoin(self.tmpdir, "status")
130

    
131
    utils.WriteFile(sp, data="\n".join([
132
      "Name:   bash",
133
      ]))
134

    
135
    self.assertRaises(RuntimeError, utils.IsProcessHandlingSignal,
136
                      1234, 10, status_path=sp)
137

    
138
  def testNoSuchFile(self):
139
    sp = utils.PathJoin(self.tmpdir, "notexist")
140

    
141
    self.assertFalse(utils.IsProcessHandlingSignal(1234, 10, status_path=sp))
142

    
143
  @staticmethod
144
  def _TestRealProcess():
145
    signal.signal(signal.SIGUSR1, signal.SIG_DFL)
146
    if utils.IsProcessHandlingSignal(os.getpid(), signal.SIGUSR1):
147
      raise Exception("SIGUSR1 is handled when it should not be")
148

    
149
    signal.signal(signal.SIGUSR1, lambda signum, frame: None)
150
    if not utils.IsProcessHandlingSignal(os.getpid(), signal.SIGUSR1):
151
      raise Exception("SIGUSR1 is not handled when it should be")
152

    
153
    signal.signal(signal.SIGUSR1, signal.SIG_IGN)
154
    if utils.IsProcessHandlingSignal(os.getpid(), signal.SIGUSR1):
155
      raise Exception("SIGUSR1 is not handled when it should be")
156

    
157
    signal.signal(signal.SIGUSR1, signal.SIG_DFL)
158
    if utils.IsProcessHandlingSignal(os.getpid(), signal.SIGUSR1):
159
      raise Exception("SIGUSR1 is handled when it should not be")
160

    
161
    return True
162

    
163
  def testRealProcess(self):
164
    self.assert_(utils.RunInSeparateProcess(self._TestRealProcess))
165

    
166

    
167
class TestRunCmd(testutils.GanetiTestCase):
168
  """Testing case for the RunCmd function"""
169

    
170
  def setUp(self):
171
    testutils.GanetiTestCase.setUp(self)
172
    self.magic = time.ctime() + " ganeti test"
173
    self.fname = self._CreateTempFile()
174
    self.fifo_tmpdir = tempfile.mkdtemp()
175
    self.fifo_file = os.path.join(self.fifo_tmpdir, "ganeti_test_fifo")
176
    os.mkfifo(self.fifo_file)
177

    
178
  def tearDown(self):
179
    shutil.rmtree(self.fifo_tmpdir)
180
    testutils.GanetiTestCase.tearDown(self)
181

    
182
  def testOk(self):
183
    """Test successful exit code"""
184
    result = RunCmd("/bin/sh -c 'exit 0'")
185
    self.assertEqual(result.exit_code, 0)
186
    self.assertEqual(result.output, "")
187

    
188
  def testFail(self):
189
    """Test fail exit code"""
190
    result = RunCmd("/bin/sh -c 'exit 1'")
191
    self.assertEqual(result.exit_code, 1)
192
    self.assertEqual(result.output, "")
193

    
194
  def testStdout(self):
195
    """Test standard output"""
196
    cmd = 'echo -n "%s"' % self.magic
197
    result = RunCmd("/bin/sh -c '%s'" % cmd)
198
    self.assertEqual(result.stdout, self.magic)
199
    result = RunCmd("/bin/sh -c '%s'" % cmd, output=self.fname)
200
    self.assertEqual(result.output, "")
201
    self.assertFileContent(self.fname, self.magic)
202

    
203
  def testStderr(self):
204
    """Test standard error"""
205
    cmd = 'echo -n "%s"' % self.magic
206
    result = RunCmd("/bin/sh -c '%s' 1>&2" % cmd)
207
    self.assertEqual(result.stderr, self.magic)
208
    result = RunCmd("/bin/sh -c '%s' 1>&2" % cmd, output=self.fname)
209
    self.assertEqual(result.output, "")
210
    self.assertFileContent(self.fname, self.magic)
211

    
212
  def testCombined(self):
213
    """Test combined output"""
214
    cmd = 'echo -n "A%s"; echo -n "B%s" 1>&2' % (self.magic, self.magic)
215
    expected = "A" + self.magic + "B" + self.magic
216
    result = RunCmd("/bin/sh -c '%s'" % cmd)
217
    self.assertEqual(result.output, expected)
218
    result = RunCmd("/bin/sh -c '%s'" % cmd, output=self.fname)
219
    self.assertEqual(result.output, "")
220
    self.assertFileContent(self.fname, expected)
221

    
222
  def testSignal(self):
223
    """Test signal"""
224
    result = RunCmd(["python", "-c", "import os; os.kill(os.getpid(), 15)"])
225
    self.assertEqual(result.signal, 15)
226
    self.assertEqual(result.output, "")
227

    
228
  def testTimeoutClean(self):
229
    cmd = "trap 'exit 0' TERM; read < %s" % self.fifo_file
230
    result = RunCmd(["/bin/sh", "-c", cmd], timeout=0.2)
231
    self.assertEqual(result.exit_code, 0)
232

    
233
  def testTimeoutKill(self):
234
    cmd = ["/bin/sh", "-c", "trap '' TERM; read < %s" % self.fifo_file]
235
    timeout = 0.2
236
    out, err, status, ta = utils._RunCmdPipe(cmd, {}, False, "/", False,
237
                                             timeout, _linger_timeout=0.2)
238
    self.assert_(status < 0)
239
    self.assertEqual(-status, signal.SIGKILL)
240

    
241
  def testTimeoutOutputAfterTerm(self):
242
    cmd = "trap 'echo sigtermed; exit 1' TERM; read < %s" % self.fifo_file
243
    result = RunCmd(["/bin/sh", "-c", cmd], timeout=0.2)
244
    self.assert_(result.failed)
245
    self.assertEqual(result.stdout, "sigtermed\n")
246

    
247
  def testListRun(self):
248
    """Test list runs"""
249
    result = RunCmd(["true"])
250
    self.assertEqual(result.signal, None)
251
    self.assertEqual(result.exit_code, 0)
252
    result = RunCmd(["/bin/sh", "-c", "exit 1"])
253
    self.assertEqual(result.signal, None)
254
    self.assertEqual(result.exit_code, 1)
255
    result = RunCmd(["echo", "-n", self.magic])
256
    self.assertEqual(result.signal, None)
257
    self.assertEqual(result.exit_code, 0)
258
    self.assertEqual(result.stdout, self.magic)
259

    
260
  def testFileEmptyOutput(self):
261
    """Test file output"""
262
    result = RunCmd(["true"], output=self.fname)
263
    self.assertEqual(result.signal, None)
264
    self.assertEqual(result.exit_code, 0)
265
    self.assertFileContent(self.fname, "")
266

    
267
  def testLang(self):
268
    """Test locale environment"""
269
    old_env = os.environ.copy()
270
    try:
271
      os.environ["LANG"] = "en_US.UTF-8"
272
      os.environ["LC_ALL"] = "en_US.UTF-8"
273
      result = RunCmd(["locale"])
274
      for line in result.output.splitlines():
275
        key, value = line.split("=", 1)
276
        # Ignore these variables, they're overridden by LC_ALL
277
        if key == "LANG" or key == "LANGUAGE":
278
          continue
279
        self.failIf(value and value != "C" and value != '"C"',
280
            "Variable %s is set to the invalid value '%s'" % (key, value))
281
    finally:
282
      os.environ = old_env
283

    
284
  def testDefaultCwd(self):
285
    """Test default working directory"""
286
    self.failUnlessEqual(RunCmd(["pwd"]).stdout.strip(), "/")
287

    
288
  def testCwd(self):
289
    """Test default working directory"""
290
    self.failUnlessEqual(RunCmd(["pwd"], cwd="/").stdout.strip(), "/")
291
    self.failUnlessEqual(RunCmd(["pwd"], cwd="/tmp").stdout.strip(), "/tmp")
292
    cwd = os.getcwd()
293
    self.failUnlessEqual(RunCmd(["pwd"], cwd=cwd).stdout.strip(), cwd)
294

    
295
  def testResetEnv(self):
296
    """Test environment reset functionality"""
297
    self.failUnlessEqual(RunCmd(["env"], reset_env=True).stdout.strip(), "")
298
    self.failUnlessEqual(RunCmd(["env"], reset_env=True,
299
                                env={"FOO": "bar",}).stdout.strip(), "FOO=bar")
300

    
301
  def testNoFork(self):
302
    """Test that nofork raise an error"""
303
    self.assertFalse(utils._no_fork)
304
    utils.DisableFork()
305
    try:
306
      self.assertTrue(utils._no_fork)
307
      self.assertRaises(errors.ProgrammerError, RunCmd, ["true"])
308
    finally:
309
      utils._no_fork = False
310

    
311
  def testWrongParams(self):
312
    """Test wrong parameters"""
313
    self.assertRaises(errors.ProgrammerError, RunCmd, ["true"],
314
                      output="/dev/null", interactive=True)
315

    
316

    
317
class TestRunParts(testutils.GanetiTestCase):
318
  """Testing case for the RunParts function"""
319

    
320
  def setUp(self):
321
    self.rundir = tempfile.mkdtemp(prefix="ganeti-test", suffix=".tmp")
322

    
323
  def tearDown(self):
324
    shutil.rmtree(self.rundir)
325

    
326
  def testEmpty(self):
327
    """Test on an empty dir"""
328
    self.failUnlessEqual(RunParts(self.rundir, reset_env=True), [])
329

    
330
  def testSkipWrongName(self):
331
    """Test that wrong files are skipped"""
332
    fname = os.path.join(self.rundir, "00test.dot")
333
    utils.WriteFile(fname, data="")
334
    os.chmod(fname, stat.S_IREAD | stat.S_IEXEC)
335
    relname = os.path.basename(fname)
336
    self.failUnlessEqual(RunParts(self.rundir, reset_env=True),
337
                         [(relname, constants.RUNPARTS_SKIP, None)])
338

    
339
  def testSkipNonExec(self):
340
    """Test that non executable files are skipped"""
341
    fname = os.path.join(self.rundir, "00test")
342
    utils.WriteFile(fname, data="")
343
    relname = os.path.basename(fname)
344
    self.failUnlessEqual(RunParts(self.rundir, reset_env=True),
345
                         [(relname, constants.RUNPARTS_SKIP, None)])
346

    
347
  def testError(self):
348
    """Test error on a broken executable"""
349
    fname = os.path.join(self.rundir, "00test")
350
    utils.WriteFile(fname, data="")
351
    os.chmod(fname, stat.S_IREAD | stat.S_IEXEC)
352
    (relname, status, error) = RunParts(self.rundir, reset_env=True)[0]
353
    self.failUnlessEqual(relname, os.path.basename(fname))
354
    self.failUnlessEqual(status, constants.RUNPARTS_ERR)
355
    self.failUnless(error)
356

    
357
  def testSorted(self):
358
    """Test executions are sorted"""
359
    files = []
360
    files.append(os.path.join(self.rundir, "64test"))
361
    files.append(os.path.join(self.rundir, "00test"))
362
    files.append(os.path.join(self.rundir, "42test"))
363

    
364
    for fname in files:
365
      utils.WriteFile(fname, data="")
366

    
367
    results = RunParts(self.rundir, reset_env=True)
368

    
369
    for fname in sorted(files):
370
      self.failUnlessEqual(os.path.basename(fname), results.pop(0)[0])
371

    
372
  def testOk(self):
373
    """Test correct execution"""
374
    fname = os.path.join(self.rundir, "00test")
375
    utils.WriteFile(fname, data="#!/bin/sh\n\necho -n ciao")
376
    os.chmod(fname, stat.S_IREAD | stat.S_IEXEC)
377
    (relname, status, runresult) = RunParts(self.rundir, reset_env=True)[0]
378
    self.failUnlessEqual(relname, os.path.basename(fname))
379
    self.failUnlessEqual(status, constants.RUNPARTS_RUN)
380
    self.failUnlessEqual(runresult.stdout, "ciao")
381

    
382
  def testRunFail(self):
383
    """Test correct execution, with run failure"""
384
    fname = os.path.join(self.rundir, "00test")
385
    utils.WriteFile(fname, data="#!/bin/sh\n\nexit 1")
386
    os.chmod(fname, stat.S_IREAD | stat.S_IEXEC)
387
    (relname, status, runresult) = RunParts(self.rundir, reset_env=True)[0]
388
    self.failUnlessEqual(relname, os.path.basename(fname))
389
    self.failUnlessEqual(status, constants.RUNPARTS_RUN)
390
    self.failUnlessEqual(runresult.exit_code, 1)
391
    self.failUnless(runresult.failed)
392

    
393
  def testRunMix(self):
394
    files = []
395
    files.append(os.path.join(self.rundir, "00test"))
396
    files.append(os.path.join(self.rundir, "42test"))
397
    files.append(os.path.join(self.rundir, "64test"))
398
    files.append(os.path.join(self.rundir, "99test"))
399

    
400
    files.sort()
401

    
402
    # 1st has errors in execution
403
    utils.WriteFile(files[0], data="#!/bin/sh\n\nexit 1")
404
    os.chmod(files[0], stat.S_IREAD | stat.S_IEXEC)
405

    
406
    # 2nd is skipped
407
    utils.WriteFile(files[1], data="")
408

    
409
    # 3rd cannot execute properly
410
    utils.WriteFile(files[2], data="")
411
    os.chmod(files[2], stat.S_IREAD | stat.S_IEXEC)
412

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

    
417
    results = RunParts(self.rundir, reset_env=True)
418

    
419
    (relname, status, runresult) = results[0]
420
    self.failUnlessEqual(relname, os.path.basename(files[0]))
421
    self.failUnlessEqual(status, constants.RUNPARTS_RUN)
422
    self.failUnlessEqual(runresult.exit_code, 1)
423
    self.failUnless(runresult.failed)
424

    
425
    (relname, status, runresult) = results[1]
426
    self.failUnlessEqual(relname, os.path.basename(files[1]))
427
    self.failUnlessEqual(status, constants.RUNPARTS_SKIP)
428
    self.failUnlessEqual(runresult, None)
429

    
430
    (relname, status, runresult) = results[2]
431
    self.failUnlessEqual(relname, os.path.basename(files[2]))
432
    self.failUnlessEqual(status, constants.RUNPARTS_ERR)
433
    self.failUnless(runresult)
434

    
435
    (relname, status, runresult) = results[3]
436
    self.failUnlessEqual(relname, os.path.basename(files[3]))
437
    self.failUnlessEqual(status, constants.RUNPARTS_RUN)
438
    self.failUnlessEqual(runresult.output, "ciao")
439
    self.failUnlessEqual(runresult.exit_code, 0)
440
    self.failUnless(not runresult.failed)
441

    
442
  def testMissingDirectory(self):
443
    nosuchdir = utils.PathJoin(self.rundir, "no/such/directory")
444
    self.assertEqual(RunParts(nosuchdir), [])
445

    
446

    
447
class TestStartDaemon(testutils.GanetiTestCase):
448
  def setUp(self):
449
    self.tmpdir = tempfile.mkdtemp(prefix="ganeti-test")
450
    self.tmpfile = os.path.join(self.tmpdir, "test")
451

    
452
  def tearDown(self):
453
    shutil.rmtree(self.tmpdir)
454

    
455
  def testShell(self):
456
    utils.StartDaemon("echo Hello World > %s" % self.tmpfile)
457
    self._wait(self.tmpfile, 60.0, "Hello World")
458

    
459
  def testShellOutput(self):
460
    utils.StartDaemon("echo Hello World", output=self.tmpfile)
461
    self._wait(self.tmpfile, 60.0, "Hello World")
462

    
463
  def testNoShellNoOutput(self):
464
    utils.StartDaemon(["pwd"])
465

    
466
  def testNoShellNoOutputTouch(self):
467
    testfile = os.path.join(self.tmpdir, "check")
468
    self.failIf(os.path.exists(testfile))
469
    utils.StartDaemon(["touch", testfile])
470
    self._wait(testfile, 60.0, "")
471

    
472
  def testNoShellOutput(self):
473
    utils.StartDaemon(["pwd"], output=self.tmpfile)
474
    self._wait(self.tmpfile, 60.0, "/")
475

    
476
  def testNoShellOutputCwd(self):
477
    utils.StartDaemon(["pwd"], output=self.tmpfile, cwd=os.getcwd())
478
    self._wait(self.tmpfile, 60.0, os.getcwd())
479

    
480
  def testShellEnv(self):
481
    utils.StartDaemon("echo \"$GNT_TEST_VAR\"", output=self.tmpfile,
482
                      env={ "GNT_TEST_VAR": "Hello World", })
483
    self._wait(self.tmpfile, 60.0, "Hello World")
484

    
485
  def testNoShellEnv(self):
486
    utils.StartDaemon(["printenv", "GNT_TEST_VAR"], output=self.tmpfile,
487
                      env={ "GNT_TEST_VAR": "Hello World", })
488
    self._wait(self.tmpfile, 60.0, "Hello World")
489

    
490
  def testOutputFd(self):
491
    fd = os.open(self.tmpfile, os.O_WRONLY | os.O_CREAT)
492
    try:
493
      utils.StartDaemon(["pwd"], output_fd=fd, cwd=os.getcwd())
494
    finally:
495
      os.close(fd)
496
    self._wait(self.tmpfile, 60.0, os.getcwd())
497

    
498
  def testPid(self):
499
    pid = utils.StartDaemon("echo $$ > %s" % self.tmpfile)
500
    self._wait(self.tmpfile, 60.0, str(pid))
501

    
502
  def testPidFile(self):
503
    pidfile = os.path.join(self.tmpdir, "pid")
504
    checkfile = os.path.join(self.tmpdir, "abort")
505

    
506
    pid = utils.StartDaemon("while sleep 5; do :; done", pidfile=pidfile,
507
                            output=self.tmpfile)
508
    try:
509
      fd = os.open(pidfile, os.O_RDONLY)
510
      try:
511
        # Check file is locked
512
        self.assertRaises(errors.LockError, utils.LockFile, fd)
513

    
514
        pidtext = os.read(fd, 100)
515
      finally:
516
        os.close(fd)
517

    
518
      self.assertEqual(int(pidtext.strip()), pid)
519

    
520
      self.assert_(utils.IsProcessAlive(pid))
521
    finally:
522
      # No matter what happens, kill daemon
523
      utils.KillProcess(pid, timeout=5.0, waitpid=False)
524
      self.failIf(utils.IsProcessAlive(pid))
525

    
526
    self.assertEqual(utils.ReadFile(self.tmpfile), "")
527

    
528
  def _wait(self, path, timeout, expected):
529
    # Due to the asynchronous nature of daemon processes, polling is necessary.
530
    # A timeout makes sure the test doesn't hang forever.
531
    def _CheckFile():
532
      if not (os.path.isfile(path) and
533
              utils.ReadFile(path).strip() == expected):
534
        raise utils.RetryAgain()
535

    
536
    try:
537
      utils.Retry(_CheckFile, (0.01, 1.5, 1.0), timeout)
538
    except utils.RetryTimeout:
539
      self.fail("Apparently the daemon didn't run in %s seconds and/or"
540
                " didn't write the correct output" % timeout)
541

    
542
  def testError(self):
543
    self.assertRaises(errors.OpExecError, utils.StartDaemon,
544
                      ["./does-NOT-EXIST/here/0123456789"])
545
    self.assertRaises(errors.OpExecError, utils.StartDaemon,
546
                      ["./does-NOT-EXIST/here/0123456789"],
547
                      output=os.path.join(self.tmpdir, "DIR/NOT/EXIST"))
548
    self.assertRaises(errors.OpExecError, utils.StartDaemon,
549
                      ["./does-NOT-EXIST/here/0123456789"],
550
                      cwd=os.path.join(self.tmpdir, "DIR/NOT/EXIST"))
551
    self.assertRaises(errors.OpExecError, utils.StartDaemon,
552
                      ["./does-NOT-EXIST/here/0123456789"],
553
                      output=os.path.join(self.tmpdir, "DIR/NOT/EXIST"))
554

    
555
    fd = os.open(self.tmpfile, os.O_WRONLY | os.O_CREAT)
556
    try:
557
      self.assertRaises(errors.ProgrammerError, utils.StartDaemon,
558
                        ["./does-NOT-EXIST/here/0123456789"],
559
                        output=self.tmpfile, output_fd=fd)
560
    finally:
561
      os.close(fd)
562

    
563

    
564
class TestParseCpuMask(unittest.TestCase):
565
  """Test case for the ParseCpuMask function."""
566

    
567
  def testWellFormed(self):
568
    self.assertEqual(utils.ParseCpuMask(""), [])
569
    self.assertEqual(utils.ParseCpuMask("1"), [1])
570
    self.assertEqual(utils.ParseCpuMask("0-2,4,5-5"), [0,1,2,4,5])
571

    
572
  def testInvalidInput(self):
573
    for data in ["garbage", "0,", "0-1-2", "2-1", "1-a"]:
574
      self.assertRaises(errors.ParseError, utils.ParseCpuMask, data)
575

    
576

    
577
class TestGetMounts(unittest.TestCase):
578
  """Test case for GetMounts()."""
579

    
580
  TESTDATA = (
581
    "rootfs /     rootfs rw 0 0\n"
582
    "none   /sys  sysfs  rw,nosuid,nodev,noexec,relatime 0 0\n"
583
    "none   /proc proc   rw,nosuid,nodev,noexec,relatime 0 0\n")
584

    
585
  def setUp(self):
586
    self.tmpfile = tempfile.NamedTemporaryFile()
587
    utils.WriteFile(self.tmpfile.name, data=self.TESTDATA)
588

    
589
  def testGetMounts(self):
590
    self.assertEqual(utils.GetMounts(filename=self.tmpfile.name),
591
      [
592
        ("rootfs", "/", "rootfs", "rw"),
593
        ("none", "/sys", "sysfs", "rw,nosuid,nodev,noexec,relatime"),
594
        ("none", "/proc", "proc", "rw,nosuid,nodev,noexec,relatime"),
595
      ])
596

    
597
class TestNewUUID(unittest.TestCase):
598
  """Test case for NewUUID"""
599

    
600
  def runTest(self):
601
    self.failUnless(utils.UUID_RE.match(utils.NewUUID()))
602

    
603

    
604
class TestFirstFree(unittest.TestCase):
605
  """Test case for the FirstFree function"""
606

    
607
  def test(self):
608
    """Test FirstFree"""
609
    self.failUnlessEqual(FirstFree([0, 1, 3]), 2)
610
    self.failUnlessEqual(FirstFree([]), None)
611
    self.failUnlessEqual(FirstFree([3, 4, 6]), 0)
612
    self.failUnlessEqual(FirstFree([3, 4, 6], base=3), 5)
613
    self.failUnlessRaises(AssertionError, FirstFree, [0, 3, 4, 6], base=3)
614

    
615

    
616
class TestTimeFunctions(unittest.TestCase):
617
  """Test case for time functions"""
618

    
619
  def runTest(self):
620
    self.assertEqual(utils.SplitTime(1), (1, 0))
621
    self.assertEqual(utils.SplitTime(1.5), (1, 500000))
622
    self.assertEqual(utils.SplitTime(1218448917.4809151), (1218448917, 480915))
623
    self.assertEqual(utils.SplitTime(123.48012), (123, 480120))
624
    self.assertEqual(utils.SplitTime(123.9996), (123, 999600))
625
    self.assertEqual(utils.SplitTime(123.9995), (123, 999500))
626
    self.assertEqual(utils.SplitTime(123.9994), (123, 999400))
627
    self.assertEqual(utils.SplitTime(123.999999999), (123, 999999))
628

    
629
    self.assertRaises(AssertionError, utils.SplitTime, -1)
630

    
631
    self.assertEqual(utils.MergeTime((1, 0)), 1.0)
632
    self.assertEqual(utils.MergeTime((1, 500000)), 1.5)
633
    self.assertEqual(utils.MergeTime((1218448917, 500000)), 1218448917.5)
634

    
635
    self.assertEqual(round(utils.MergeTime((1218448917, 481000)), 3),
636
                     1218448917.481)
637
    self.assertEqual(round(utils.MergeTime((1, 801000)), 3), 1.801)
638

    
639
    self.assertRaises(AssertionError, utils.MergeTime, (0, -1))
640
    self.assertRaises(AssertionError, utils.MergeTime, (0, 1000000))
641
    self.assertRaises(AssertionError, utils.MergeTime, (0, 9999999))
642
    self.assertRaises(AssertionError, utils.MergeTime, (-1, 0))
643
    self.assertRaises(AssertionError, utils.MergeTime, (-9999, 0))
644

    
645

    
646
class FieldSetTestCase(unittest.TestCase):
647
  """Test case for FieldSets"""
648

    
649
  def testSimpleMatch(self):
650
    f = utils.FieldSet("a", "b", "c", "def")
651
    self.failUnless(f.Matches("a"))
652
    self.failIf(f.Matches("d"), "Substring matched")
653
    self.failIf(f.Matches("defghi"), "Prefix string matched")
654
    self.failIf(f.NonMatching(["b", "c"]))
655
    self.failIf(f.NonMatching(["a", "b", "c", "def"]))
656
    self.failUnless(f.NonMatching(["a", "d"]))
657

    
658
  def testRegexMatch(self):
659
    f = utils.FieldSet("a", "b([0-9]+)", "c")
660
    self.failUnless(f.Matches("b1"))
661
    self.failUnless(f.Matches("b99"))
662
    self.failIf(f.Matches("b/1"))
663
    self.failIf(f.NonMatching(["b12", "c"]))
664
    self.failUnless(f.NonMatching(["a", "1"]))
665

    
666
class TestForceDictType(unittest.TestCase):
667
  """Test case for ForceDictType"""
668
  KEY_TYPES = {
669
    "a": constants.VTYPE_INT,
670
    "b": constants.VTYPE_BOOL,
671
    "c": constants.VTYPE_STRING,
672
    "d": constants.VTYPE_SIZE,
673
    "e": constants.VTYPE_MAYBE_STRING,
674
    }
675

    
676
  def _fdt(self, dict, allowed_values=None):
677
    if allowed_values is None:
678
      utils.ForceDictType(dict, self.KEY_TYPES)
679
    else:
680
      utils.ForceDictType(dict, self.KEY_TYPES, allowed_values=allowed_values)
681

    
682
    return dict
683

    
684
  def testSimpleDict(self):
685
    self.assertEqual(self._fdt({}), {})
686
    self.assertEqual(self._fdt({'a': 1}), {'a': 1})
687
    self.assertEqual(self._fdt({'a': '1'}), {'a': 1})
688
    self.assertEqual(self._fdt({'a': 1, 'b': 1}), {'a':1, 'b': True})
689
    self.assertEqual(self._fdt({'b': 1, 'c': 'foo'}), {'b': True, 'c': 'foo'})
690
    self.assertEqual(self._fdt({'b': 1, 'c': False}), {'b': True, 'c': ''})
691
    self.assertEqual(self._fdt({'b': 'false'}), {'b': False})
692
    self.assertEqual(self._fdt({'b': 'False'}), {'b': False})
693
    self.assertEqual(self._fdt({'b': False}), {'b': False})
694
    self.assertEqual(self._fdt({'b': 'true'}), {'b': True})
695
    self.assertEqual(self._fdt({'b': 'True'}), {'b': True})
696
    self.assertEqual(self._fdt({'d': '4'}), {'d': 4})
697
    self.assertEqual(self._fdt({'d': '4M'}), {'d': 4})
698
    self.assertEqual(self._fdt({"e": None, }), {"e": None, })
699
    self.assertEqual(self._fdt({"e": "Hello World", }), {"e": "Hello World", })
700
    self.assertEqual(self._fdt({"e": False, }), {"e": '', })
701
    self.assertEqual(self._fdt({"b": "hello", }, ["hello"]), {"b": "hello"})
702

    
703
  def testErrors(self):
704
    self.assertRaises(errors.TypeEnforcementError, self._fdt, {'a': 'astring'})
705
    self.assertRaises(errors.TypeEnforcementError, self._fdt, {"b": "hello"})
706
    self.assertRaises(errors.TypeEnforcementError, self._fdt, {'c': True})
707
    self.assertRaises(errors.TypeEnforcementError, self._fdt, {'d': 'astring'})
708
    self.assertRaises(errors.TypeEnforcementError, self._fdt, {'d': '4 L'})
709
    self.assertRaises(errors.TypeEnforcementError, self._fdt, {"e": object(), })
710
    self.assertRaises(errors.TypeEnforcementError, self._fdt, {"e": [], })
711
    self.assertRaises(errors.TypeEnforcementError, self._fdt, {"x": None, })
712
    self.assertRaises(errors.TypeEnforcementError, self._fdt, [])
713
    self.assertRaises(errors.ProgrammerError, utils.ForceDictType,
714
                      {"b": "hello"}, {"b": "no-such-type"})
715

    
716

    
717
class RunInSeparateProcess(unittest.TestCase):
718
  def test(self):
719
    for exp in [True, False]:
720
      def _child():
721
        return exp
722

    
723
      self.assertEqual(exp, utils.RunInSeparateProcess(_child))
724

    
725
  def testArgs(self):
726
    for arg in [0, 1, 999, "Hello World", (1, 2, 3)]:
727
      def _child(carg1, carg2):
728
        return carg1 == "Foo" and carg2 == arg
729

    
730
      self.assert_(utils.RunInSeparateProcess(_child, "Foo", arg))
731

    
732
  def testPid(self):
733
    parent_pid = os.getpid()
734

    
735
    def _check():
736
      return os.getpid() == parent_pid
737

    
738
    self.failIf(utils.RunInSeparateProcess(_check))
739

    
740
  def testSignal(self):
741
    def _kill():
742
      os.kill(os.getpid(), signal.SIGTERM)
743

    
744
    self.assertRaises(errors.GenericError,
745
                      utils.RunInSeparateProcess, _kill)
746

    
747
  def testException(self):
748
    def _exc():
749
      raise errors.GenericError("This is a test")
750

    
751
    self.assertRaises(errors.GenericError,
752
                      utils.RunInSeparateProcess, _exc)
753

    
754

    
755
class TestValidateServiceName(unittest.TestCase):
756
  def testValid(self):
757
    testnames = [
758
      0, 1, 2, 3, 1024, 65000, 65534, 65535,
759
      "ganeti",
760
      "gnt-masterd",
761
      "HELLO_WORLD_SVC",
762
      "hello.world.1",
763
      "0", "80", "1111", "65535",
764
      ]
765

    
766
    for name in testnames:
767
      self.assertEqual(utils.ValidateServiceName(name), name)
768

    
769
  def testInvalid(self):
770
    testnames = [
771
      -15756, -1, 65536, 133428083,
772
      "", "Hello World!", "!", "'", "\"", "\t", "\n", "`",
773
      "-8546", "-1", "65536",
774
      (129 * "A"),
775
      ]
776

    
777
    for name in testnames:
778
      self.assertRaises(errors.OpPrereqError, utils.ValidateServiceName, name)
779

    
780

    
781
class TestReadLockedPidFile(unittest.TestCase):
782
  def setUp(self):
783
    self.tmpdir = tempfile.mkdtemp()
784

    
785
  def tearDown(self):
786
    shutil.rmtree(self.tmpdir)
787

    
788
  def testNonExistent(self):
789
    path = utils.PathJoin(self.tmpdir, "nonexist")
790
    self.assert_(utils.ReadLockedPidFile(path) is None)
791

    
792
  def testUnlocked(self):
793
    path = utils.PathJoin(self.tmpdir, "pid")
794
    utils.WriteFile(path, data="123")
795
    self.assert_(utils.ReadLockedPidFile(path) is None)
796

    
797
  def testLocked(self):
798
    path = utils.PathJoin(self.tmpdir, "pid")
799
    utils.WriteFile(path, data="123")
800

    
801
    fl = utils.FileLock.Open(path)
802
    try:
803
      fl.Exclusive(blocking=True)
804

    
805
      self.assertEqual(utils.ReadLockedPidFile(path), 123)
806
    finally:
807
      fl.Close()
808

    
809
    self.assert_(utils.ReadLockedPidFile(path) is None)
810

    
811
  def testError(self):
812
    path = utils.PathJoin(self.tmpdir, "foobar", "pid")
813
    utils.WriteFile(utils.PathJoin(self.tmpdir, "foobar"), data="")
814
    # open(2) should return ENOTDIR
815
    self.assertRaises(EnvironmentError, utils.ReadLockedPidFile, path)
816

    
817

    
818
class TestFindMatch(unittest.TestCase):
819
  def test(self):
820
    data = {
821
      "aaaa": "Four A",
822
      "bb": {"Two B": True},
823
      re.compile(r"^x(foo|bar|bazX)([0-9]+)$"): (1, 2, 3),
824
      }
825

    
826
    self.assertEqual(utils.FindMatch(data, "aaaa"), ("Four A", []))
827
    self.assertEqual(utils.FindMatch(data, "bb"), ({"Two B": True}, []))
828

    
829
    for i in ["foo", "bar", "bazX"]:
830
      for j in range(1, 100, 7):
831
        self.assertEqual(utils.FindMatch(data, "x%s%s" % (i, j)),
832
                         ((1, 2, 3), [i, str(j)]))
833

    
834
  def testNoMatch(self):
835
    self.assert_(utils.FindMatch({}, "") is None)
836
    self.assert_(utils.FindMatch({}, "foo") is None)
837
    self.assert_(utils.FindMatch({}, 1234) is None)
838

    
839
    data = {
840
      "X": "Hello World",
841
      re.compile("^(something)$"): "Hello World",
842
      }
843

    
844
    self.assert_(utils.FindMatch(data, "") is None)
845
    self.assert_(utils.FindMatch(data, "Hello World") is None)
846

    
847

    
848
class TimeMock:
849
  def __init__(self, values):
850
    self.values = values
851

    
852
  def __call__(self):
853
    return self.values.pop(0)
854

    
855

    
856
class TestRunningTimeout(unittest.TestCase):
857
  def setUp(self):
858
    self.time_fn = TimeMock([0.0, 0.3, 4.6, 6.5])
859

    
860
  def testRemainingFloat(self):
861
    timeout = utils.RunningTimeout(5.0, True, _time_fn=self.time_fn)
862
    self.assertAlmostEqual(timeout.Remaining(), 4.7)
863
    self.assertAlmostEqual(timeout.Remaining(), 0.4)
864
    self.assertAlmostEqual(timeout.Remaining(), -1.5)
865

    
866
  def testRemaining(self):
867
    self.time_fn = TimeMock([0, 2, 4, 5, 6])
868
    timeout = utils.RunningTimeout(5, True, _time_fn=self.time_fn)
869
    self.assertEqual(timeout.Remaining(), 3)
870
    self.assertEqual(timeout.Remaining(), 1)
871
    self.assertEqual(timeout.Remaining(), 0)
872
    self.assertEqual(timeout.Remaining(), -1)
873

    
874
  def testRemainingNonNegative(self):
875
    timeout = utils.RunningTimeout(5.0, False, _time_fn=self.time_fn)
876
    self.assertAlmostEqual(timeout.Remaining(), 4.7)
877
    self.assertAlmostEqual(timeout.Remaining(), 0.4)
878
    self.assertEqual(timeout.Remaining(), 0.0)
879

    
880
  def testNegativeTimeout(self):
881
    self.assertRaises(ValueError, utils.RunningTimeout, -1.0, True)
882

    
883

    
884
class TestTryConvert(unittest.TestCase):
885
  def test(self):
886
    for src, fn, result in [
887
      ("1", int, 1),
888
      ("a", int, "a"),
889
      ("", bool, False),
890
      ("a", bool, True),
891
      ]:
892
      self.assertEqual(utils.TryConvert(fn, src), result)
893

    
894

    
895
class TestIsValidShellParam(unittest.TestCase):
896
  def test(self):
897
    for val, result in [
898
      ("abc", True),
899
      ("ab;cd", False),
900
      ]:
901
      self.assertEqual(utils.IsValidShellParam(val), result)
902

    
903

    
904
class TestBuildShellCmd(unittest.TestCase):
905
  def test(self):
906
    self.assertRaises(errors.ProgrammerError, utils.BuildShellCmd,
907
                      "ls %s", "ab;cd")
908
    self.assertEqual(utils.BuildShellCmd("ls %s", "ab"), "ls ab")
909

    
910

    
911
if __name__ == '__main__':
912
  testutils.GanetiTestProgram()