Statistics
| Branch: | Tag: | Revision:

root / test / ganeti.utils_unittest.py @ 09352fa4

History | View | Annotate | Download (26.4 kB)

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

    
4
# Copyright (C) 2006, 2007 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 unittest
25
import os
26
import time
27
import tempfile
28
import os.path
29
import os
30
import md5
31
import signal
32
import socket
33
import shutil
34
import re
35
import select
36

    
37
import ganeti
38
import testutils
39
from ganeti import constants
40
from ganeti import utils
41
from ganeti.utils import IsProcessAlive, RunCmd, \
42
     RemoveFile, CheckDict, MatchNameComponent, FormatUnit, \
43
     ParseUnit, AddAuthorizedKey, RemoveAuthorizedKey, \
44
     ShellQuote, ShellQuoteArgs, TcpPing, ListVisibleFiles, \
45
     SetEtcHostsEntry, RemoveEtcHostsEntry, FirstFree, OwnIpAddress
46
from ganeti.errors import LockError, UnitParseError, GenericError, \
47
     ProgrammerError
48

    
49

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

    
53
  def _CreateZombie(self):
54
    # create a zombie
55
    r_fd, w_fd = os.pipe()
56
    pid_zombie = os.fork()
57
    if pid_zombie == 0:
58
      # explicit close of read, write end will be closed only due to exit
59
      os.close(r_fd)
60
      os._exit(0)
61
    elif pid_zombie < 0:
62
      raise SystemError("can't fork")
63
    # parent: we close our end of the w_fd, so reads will error as
64
    # soon as the OS cleans the child's filedescriptors on its exit
65
    os.close(w_fd)
66
    # wait for 60 seconds at max for the exit (pathological case, just
67
    # so that the test doesn't hang indefinitely)
68
    r_set, w_set, e_set = select.select([r_fd], [], [], 60)
69
    if not r_set and not e_set:
70
      self.fail("Timeout exceeded in zombie creation")
71
    return pid_zombie
72

    
73
  def testExists(self):
74
    mypid = os.getpid()
75
    self.assert_(IsProcessAlive(mypid),
76
                 "can't find myself running")
77

    
78
  def testZombie(self):
79
    pid_zombie = self._CreateZombie()
80
    is_zombie = not IsProcessAlive(pid_zombie)
81
    self.assert_(is_zombie, "zombie not detected as zombie")
82
    os.waitpid(pid_zombie, os.WNOHANG)
83

    
84
  def testNotExisting(self):
85
    pid_non_existing = os.fork()
86
    if pid_non_existing == 0:
87
      os._exit(0)
88
    elif pid_non_existing < 0:
89
      raise SystemError("can't fork")
90
    os.waitpid(pid_non_existing, 0)
91
    self.assert_(not IsProcessAlive(pid_non_existing),
92
                 "nonexisting process detected")
93

    
94

    
95
class TestPidFileFunctions(unittest.TestCase):
96
  """Tests for WritePidFile, RemovePidFile and ReadPidFile"""
97

    
98
  def setUp(self):
99
    self.dir = tempfile.mkdtemp()
100
    self.f_dpn = lambda name: os.path.join(self.dir, "%s.pid" % name)
101
    utils.DaemonPidFileName = self.f_dpn
102

    
103
  def testPidFileFunctions(self):
104
    pid_file = self.f_dpn('test')
105
    utils.WritePidFile('test')
106
    self.failUnless(os.path.exists(pid_file),
107
                    "PID file should have been created")
108
    read_pid = utils.ReadPidFile(pid_file)
109
    self.failUnlessEqual(read_pid, os.getpid())
110
    self.failUnless(utils.IsProcessAlive(read_pid))
111
    self.failUnlessRaises(GenericError, utils.WritePidFile, 'test')
112
    utils.RemovePidFile('test')
113
    self.failIf(os.path.exists(pid_file),
114
                "PID file should not exist anymore")
115
    self.failUnlessEqual(utils.ReadPidFile(pid_file), 0,
116
                         "ReadPidFile should return 0 for missing pid file")
117
    fh = open(pid_file, "w")
118
    fh.write("blah\n")
119
    fh.close()
120
    self.failUnlessEqual(utils.ReadPidFile(pid_file), 0,
121
                         "ReadPidFile should return 0 for invalid pid file")
122
    utils.RemovePidFile('test')
123
    self.failIf(os.path.exists(pid_file),
124
                "PID file should not exist anymore")
125

    
126
  def testKill(self):
127
    pid_file = self.f_dpn('child')
128
    r_fd, w_fd = os.pipe()
129
    new_pid = os.fork()
130
    if new_pid == 0: #child
131
      utils.WritePidFile('child')
132
      os.write(w_fd, 'a')
133
      signal.pause()
134
      os._exit(0)
135
      return
136
    # else we are in the parent
137
    # wait until the child has written the pid file
138
    os.read(r_fd, 1)
139
    read_pid = utils.ReadPidFile(pid_file)
140
    self.failUnlessEqual(read_pid, new_pid)
141
    self.failUnless(utils.IsProcessAlive(new_pid))
142
    utils.KillProcess(new_pid)
143
    self.failIf(utils.IsProcessAlive(new_pid))
144
    utils.RemovePidFile('child')
145
    self.failUnlessRaises(ProgrammerError, utils.KillProcess, 0)
146

    
147
  def tearDown(self):
148
    for name in os.listdir(self.dir):
149
      os.unlink(os.path.join(self.dir, name))
150
    os.rmdir(self.dir)
151

    
152

    
153
class TestRunCmd(unittest.TestCase):
154
  """Testing case for the RunCmd function"""
155

    
156
  def setUp(self):
157
    self.magic = time.ctime() + " ganeti test"
158

    
159
  def testOk(self):
160
    """Test successful exit code"""
161
    result = RunCmd("/bin/sh -c 'exit 0'")
162
    self.assertEqual(result.exit_code, 0)
163

    
164
  def testFail(self):
165
    """Test fail exit code"""
166
    result = RunCmd("/bin/sh -c 'exit 1'")
167
    self.assertEqual(result.exit_code, 1)
168

    
169

    
170
  def testStdout(self):
171
    """Test standard output"""
172
    cmd = 'echo -n "%s"' % self.magic
173
    result = RunCmd("/bin/sh -c '%s'" % cmd)
174
    self.assertEqual(result.stdout, self.magic)
175

    
176

    
177
  def testStderr(self):
178
    """Test standard error"""
179
    cmd = 'echo -n "%s"' % self.magic
180
    result = RunCmd("/bin/sh -c '%s' 1>&2" % cmd)
181
    self.assertEqual(result.stderr, self.magic)
182

    
183

    
184
  def testCombined(self):
185
    """Test combined output"""
186
    cmd = 'echo -n "A%s"; echo -n "B%s" 1>&2' % (self.magic, self.magic)
187
    result = RunCmd("/bin/sh -c '%s'" % cmd)
188
    self.assertEqual(result.output, "A" + self.magic + "B" + self.magic)
189

    
190
  def testSignal(self):
191
    """Test signal"""
192
    result = RunCmd(["python", "-c", "import os; os.kill(os.getpid(), 15)"])
193
    self.assertEqual(result.signal, 15)
194

    
195
  def testListRun(self):
196
    """Test list runs"""
197
    result = RunCmd(["true"])
198
    self.assertEqual(result.signal, None)
199
    self.assertEqual(result.exit_code, 0)
200
    result = RunCmd(["/bin/sh", "-c", "exit 1"])
201
    self.assertEqual(result.signal, None)
202
    self.assertEqual(result.exit_code, 1)
203
    result = RunCmd(["echo", "-n", self.magic])
204
    self.assertEqual(result.signal, None)
205
    self.assertEqual(result.exit_code, 0)
206
    self.assertEqual(result.stdout, self.magic)
207

    
208
  def testLang(self):
209
    """Test locale environment"""
210
    old_env = os.environ.copy()
211
    try:
212
      os.environ["LANG"] = "en_US.UTF-8"
213
      os.environ["LC_ALL"] = "en_US.UTF-8"
214
      result = RunCmd(["locale"])
215
      for line in result.output.splitlines():
216
        key, value = line.split("=", 1)
217
        # Ignore these variables, they're overridden by LC_ALL
218
        if key == "LANG" or key == "LANGUAGE":
219
          continue
220
        self.failIf(value and value != "C" and value != '"C"',
221
            "Variable %s is set to the invalid value '%s'" % (key, value))
222
    finally:
223
      os.environ = old_env
224

    
225

    
226
class TestRemoveFile(unittest.TestCase):
227
  """Test case for the RemoveFile function"""
228

    
229
  def setUp(self):
230
    """Create a temp dir and file for each case"""
231
    self.tmpdir = tempfile.mkdtemp('', 'ganeti-unittest-')
232
    fd, self.tmpfile = tempfile.mkstemp('', '', self.tmpdir)
233
    os.close(fd)
234

    
235
  def tearDown(self):
236
    if os.path.exists(self.tmpfile):
237
      os.unlink(self.tmpfile)
238
    os.rmdir(self.tmpdir)
239

    
240

    
241
  def testIgnoreDirs(self):
242
    """Test that RemoveFile() ignores directories"""
243
    self.assertEqual(None, RemoveFile(self.tmpdir))
244

    
245

    
246
  def testIgnoreNotExisting(self):
247
    """Test that RemoveFile() ignores non-existing files"""
248
    RemoveFile(self.tmpfile)
249
    RemoveFile(self.tmpfile)
250

    
251

    
252
  def testRemoveFile(self):
253
    """Test that RemoveFile does remove a file"""
254
    RemoveFile(self.tmpfile)
255
    if os.path.exists(self.tmpfile):
256
      self.fail("File '%s' not removed" % self.tmpfile)
257

    
258

    
259
  def testRemoveSymlink(self):
260
    """Test that RemoveFile does remove symlinks"""
261
    symlink = self.tmpdir + "/symlink"
262
    os.symlink("no-such-file", symlink)
263
    RemoveFile(symlink)
264
    if os.path.exists(symlink):
265
      self.fail("File '%s' not removed" % symlink)
266
    os.symlink(self.tmpfile, symlink)
267
    RemoveFile(symlink)
268
    if os.path.exists(symlink):
269
      self.fail("File '%s' not removed" % symlink)
270

    
271

    
272
class TestCheckdict(unittest.TestCase):
273
  """Test case for the CheckDict function"""
274

    
275
  def testAdd(self):
276
    """Test that CheckDict adds a missing key with the correct value"""
277

    
278
    tgt = {'a':1}
279
    tmpl = {'b': 2}
280
    CheckDict(tgt, tmpl)
281
    if 'b' not in tgt or tgt['b'] != 2:
282
      self.fail("Failed to update dict")
283

    
284

    
285
  def testNoUpdate(self):
286
    """Test that CheckDict does not overwrite an existing key"""
287
    tgt = {'a':1, 'b': 3}
288
    tmpl = {'b': 2}
289
    CheckDict(tgt, tmpl)
290
    self.failUnlessEqual(tgt['b'], 3)
291

    
292

    
293
class TestMatchNameComponent(unittest.TestCase):
294
  """Test case for the MatchNameComponent function"""
295

    
296
  def testEmptyList(self):
297
    """Test that there is no match against an empty list"""
298

    
299
    self.failUnlessEqual(MatchNameComponent("", []), None)
300
    self.failUnlessEqual(MatchNameComponent("test", []), None)
301

    
302
  def testSingleMatch(self):
303
    """Test that a single match is performed correctly"""
304
    mlist = ["test1.example.com", "test2.example.com", "test3.example.com"]
305
    for key in "test2", "test2.example", "test2.example.com":
306
      self.failUnlessEqual(MatchNameComponent(key, mlist), mlist[1])
307

    
308
  def testMultipleMatches(self):
309
    """Test that a multiple match is returned as None"""
310
    mlist = ["test1.example.com", "test1.example.org", "test1.example.net"]
311
    for key in "test1", "test1.example":
312
      self.failUnlessEqual(MatchNameComponent(key, mlist), None)
313

    
314

    
315
class TestFormatUnit(unittest.TestCase):
316
  """Test case for the FormatUnit function"""
317

    
318
  def testMiB(self):
319
    self.assertEqual(FormatUnit(1), '1M')
320
    self.assertEqual(FormatUnit(100), '100M')
321
    self.assertEqual(FormatUnit(1023), '1023M')
322

    
323
  def testGiB(self):
324
    self.assertEqual(FormatUnit(1024), '1.0G')
325
    self.assertEqual(FormatUnit(1536), '1.5G')
326
    self.assertEqual(FormatUnit(17133), '16.7G')
327
    self.assertEqual(FormatUnit(1024 * 1024 - 1), '1024.0G')
328

    
329
  def testTiB(self):
330
    self.assertEqual(FormatUnit(1024 * 1024), '1.0T')
331
    self.assertEqual(FormatUnit(5120 * 1024), '5.0T')
332
    self.assertEqual(FormatUnit(29829 * 1024), '29.1T')
333

    
334

    
335
class TestParseUnit(unittest.TestCase):
336
  """Test case for the ParseUnit function"""
337

    
338
  SCALES = (('', 1),
339
            ('M', 1), ('G', 1024), ('T', 1024 * 1024),
340
            ('MB', 1), ('GB', 1024), ('TB', 1024 * 1024),
341
            ('MiB', 1), ('GiB', 1024), ('TiB', 1024 * 1024))
342

    
343
  def testRounding(self):
344
    self.assertEqual(ParseUnit('0'), 0)
345
    self.assertEqual(ParseUnit('1'), 4)
346
    self.assertEqual(ParseUnit('2'), 4)
347
    self.assertEqual(ParseUnit('3'), 4)
348

    
349
    self.assertEqual(ParseUnit('124'), 124)
350
    self.assertEqual(ParseUnit('125'), 128)
351
    self.assertEqual(ParseUnit('126'), 128)
352
    self.assertEqual(ParseUnit('127'), 128)
353
    self.assertEqual(ParseUnit('128'), 128)
354
    self.assertEqual(ParseUnit('129'), 132)
355
    self.assertEqual(ParseUnit('130'), 132)
356

    
357
  def testFloating(self):
358
    self.assertEqual(ParseUnit('0'), 0)
359
    self.assertEqual(ParseUnit('0.5'), 4)
360
    self.assertEqual(ParseUnit('1.75'), 4)
361
    self.assertEqual(ParseUnit('1.99'), 4)
362
    self.assertEqual(ParseUnit('2.00'), 4)
363
    self.assertEqual(ParseUnit('2.01'), 4)
364
    self.assertEqual(ParseUnit('3.99'), 4)
365
    self.assertEqual(ParseUnit('4.00'), 4)
366
    self.assertEqual(ParseUnit('4.01'), 8)
367
    self.assertEqual(ParseUnit('1.5G'), 1536)
368
    self.assertEqual(ParseUnit('1.8G'), 1844)
369
    self.assertEqual(ParseUnit('8.28T'), 8682212)
370

    
371
  def testSuffixes(self):
372
    for sep in ('', ' ', '   ', "\t", "\t "):
373
      for suffix, scale in TestParseUnit.SCALES:
374
        for func in (lambda x: x, str.lower, str.upper):
375
          self.assertEqual(ParseUnit('1024' + sep + func(suffix)),
376
                           1024 * scale)
377

    
378
  def testInvalidInput(self):
379
    for sep in ('-', '_', ',', 'a'):
380
      for suffix, _ in TestParseUnit.SCALES:
381
        self.assertRaises(UnitParseError, ParseUnit, '1' + sep + suffix)
382

    
383
    for suffix, _ in TestParseUnit.SCALES:
384
      self.assertRaises(UnitParseError, ParseUnit, '1,3' + suffix)
385

    
386

    
387
class TestSshKeys(testutils.GanetiTestCase):
388
  """Test case for the AddAuthorizedKey function"""
389

    
390
  KEY_A = 'ssh-dss AAAAB3NzaC1w5256closdj32mZaQU root@key-a'
391
  KEY_B = ('command="/usr/bin/fooserver -t --verbose",from="1.2.3.4" '
392
           'ssh-dss AAAAB3NzaC1w520smc01ms0jfJs22 root@key-b')
393

    
394
  def setUp(self):
395
    (fd, self.tmpname) = tempfile.mkstemp(prefix='ganeti-test')
396
    try:
397
      handle = os.fdopen(fd, 'w')
398
      try:
399
        handle.write("%s\n" % TestSshKeys.KEY_A)
400
        handle.write("%s\n" % TestSshKeys.KEY_B)
401
      finally:
402
        handle.close()
403
    except:
404
      utils.RemoveFile(self.tmpname)
405
      raise
406

    
407
  def tearDown(self):
408
    utils.RemoveFile(self.tmpname)
409
    del self.tmpname
410

    
411
  def testAddingNewKey(self):
412
    AddAuthorizedKey(self.tmpname, 'ssh-dss AAAAB3NzaC1kc3MAAACB root@test')
413

    
414
    self.assertFileContent(self.tmpname,
415
      "ssh-dss AAAAB3NzaC1w5256closdj32mZaQU root@key-a\n"
416
      'command="/usr/bin/fooserver -t --verbose",from="1.2.3.4"'
417
      " ssh-dss AAAAB3NzaC1w520smc01ms0jfJs22 root@key-b\n"
418
      "ssh-dss AAAAB3NzaC1kc3MAAACB root@test\n")
419

    
420
  def testAddingAlmostButNotCompletelyTheSameKey(self):
421
    AddAuthorizedKey(self.tmpname,
422
        'ssh-dss AAAAB3NzaC1w5256closdj32mZaQU root@test')
423

    
424
    self.assertFileContent(self.tmpname,
425
      "ssh-dss AAAAB3NzaC1w5256closdj32mZaQU root@key-a\n"
426
      'command="/usr/bin/fooserver -t --verbose",from="1.2.3.4"'
427
      " ssh-dss AAAAB3NzaC1w520smc01ms0jfJs22 root@key-b\n"
428
      "ssh-dss AAAAB3NzaC1w5256closdj32mZaQU root@test\n")
429

    
430
  def testAddingExistingKeyWithSomeMoreSpaces(self):
431
    AddAuthorizedKey(self.tmpname,
432
        'ssh-dss  AAAAB3NzaC1w5256closdj32mZaQU   root@key-a')
433

    
434
    self.assertFileContent(self.tmpname,
435
      "ssh-dss AAAAB3NzaC1w5256closdj32mZaQU root@key-a\n"
436
      'command="/usr/bin/fooserver -t --verbose",from="1.2.3.4"'
437
      " ssh-dss AAAAB3NzaC1w520smc01ms0jfJs22 root@key-b\n")
438

    
439
  def testRemovingExistingKeyWithSomeMoreSpaces(self):
440
    RemoveAuthorizedKey(self.tmpname,
441
        'ssh-dss  AAAAB3NzaC1w5256closdj32mZaQU   root@key-a')
442

    
443
    self.assertFileContent(self.tmpname,
444
      'command="/usr/bin/fooserver -t --verbose",from="1.2.3.4"'
445
      " ssh-dss AAAAB3NzaC1w520smc01ms0jfJs22 root@key-b\n")
446

    
447
  def testRemovingNonExistingKey(self):
448
    RemoveAuthorizedKey(self.tmpname,
449
        'ssh-dss  AAAAB3Nsdfj230xxjxJjsjwjsjdjU   root@test')
450

    
451
    self.assertFileContent(self.tmpname,
452
      "ssh-dss AAAAB3NzaC1w5256closdj32mZaQU root@key-a\n"
453
      'command="/usr/bin/fooserver -t --verbose",from="1.2.3.4"'
454
      " ssh-dss AAAAB3NzaC1w520smc01ms0jfJs22 root@key-b\n")
455

    
456

    
457
class TestEtcHosts(testutils.GanetiTestCase):
458
  """Test functions modifying /etc/hosts"""
459

    
460
  def setUp(self):
461
    (fd, self.tmpname) = tempfile.mkstemp(prefix='ganeti-test')
462
    try:
463
      handle = os.fdopen(fd, 'w')
464
      try:
465
        handle.write('# This is a test file for /etc/hosts\n')
466
        handle.write('127.0.0.1\tlocalhost\n')
467
        handle.write('192.168.1.1 router gw\n')
468
      finally:
469
        handle.close()
470
    except:
471
      utils.RemoveFile(self.tmpname)
472
      raise
473

    
474
  def tearDown(self):
475
    utils.RemoveFile(self.tmpname)
476
    del self.tmpname
477

    
478
  def testSettingNewIp(self):
479
    SetEtcHostsEntry(self.tmpname, '1.2.3.4', 'myhost.domain.tld', ['myhost'])
480

    
481
    self.assertFileContent(self.tmpname,
482
      "# This is a test file for /etc/hosts\n"
483
      "127.0.0.1\tlocalhost\n"
484
      "192.168.1.1 router gw\n"
485
      "1.2.3.4\tmyhost.domain.tld myhost\n")
486

    
487
  def testSettingExistingIp(self):
488
    SetEtcHostsEntry(self.tmpname, '192.168.1.1', 'myhost.domain.tld',
489
                     ['myhost'])
490

    
491
    self.assertFileContent(self.tmpname,
492
      "# This is a test file for /etc/hosts\n"
493
      "127.0.0.1\tlocalhost\n"
494
      "192.168.1.1\tmyhost.domain.tld myhost\n")
495

    
496
  def testSettingDuplicateName(self):
497
    SetEtcHostsEntry(self.tmpname, '1.2.3.4', 'myhost', ['myhost'])
498

    
499
    self.assertFileContent(self.tmpname,
500
      "# This is a test file for /etc/hosts\n"
501
      "127.0.0.1\tlocalhost\n"
502
      "192.168.1.1 router gw\n"
503
      "1.2.3.4\tmyhost\n")
504

    
505
  def testRemovingExistingHost(self):
506
    RemoveEtcHostsEntry(self.tmpname, 'router')
507

    
508
    self.assertFileContent(self.tmpname,
509
      "# This is a test file for /etc/hosts\n"
510
      "127.0.0.1\tlocalhost\n"
511
      "192.168.1.1 gw\n")
512

    
513
  def testRemovingSingleExistingHost(self):
514
    RemoveEtcHostsEntry(self.tmpname, 'localhost')
515

    
516
    self.assertFileContent(self.tmpname,
517
      "# This is a test file for /etc/hosts\n"
518
      "192.168.1.1 router gw\n")
519

    
520
  def testRemovingNonExistingHost(self):
521
    RemoveEtcHostsEntry(self.tmpname, 'myhost')
522

    
523
    self.assertFileContent(self.tmpname,
524
      "# This is a test file for /etc/hosts\n"
525
      "127.0.0.1\tlocalhost\n"
526
      "192.168.1.1 router gw\n")
527

    
528
  def testRemovingAlias(self):
529
    RemoveEtcHostsEntry(self.tmpname, 'gw')
530

    
531
    self.assertFileContent(self.tmpname,
532
      "# This is a test file for /etc/hosts\n"
533
      "127.0.0.1\tlocalhost\n"
534
      "192.168.1.1 router\n")
535

    
536

    
537
class TestShellQuoting(unittest.TestCase):
538
  """Test case for shell quoting functions"""
539

    
540
  def testShellQuote(self):
541
    self.assertEqual(ShellQuote('abc'), "abc")
542
    self.assertEqual(ShellQuote('ab"c'), "'ab\"c'")
543
    self.assertEqual(ShellQuote("a'bc"), "'a'\\''bc'")
544
    self.assertEqual(ShellQuote("a b c"), "'a b c'")
545
    self.assertEqual(ShellQuote("a b\\ c"), "'a b\\ c'")
546

    
547
  def testShellQuoteArgs(self):
548
    self.assertEqual(ShellQuoteArgs(['a', 'b', 'c']), "a b c")
549
    self.assertEqual(ShellQuoteArgs(['a', 'b"', 'c']), "a 'b\"' c")
550
    self.assertEqual(ShellQuoteArgs(['a', 'b\'', 'c']), "a 'b'\\\''' c")
551

    
552

    
553
class TestTcpPing(unittest.TestCase):
554
  """Testcase for TCP version of ping - against listen(2)ing port"""
555

    
556
  def setUp(self):
557
    self.listener = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
558
    self.listener.bind((constants.LOCALHOST_IP_ADDRESS, 0))
559
    self.listenerport = self.listener.getsockname()[1]
560
    self.listener.listen(1)
561

    
562
  def tearDown(self):
563
    self.listener.shutdown(socket.SHUT_RDWR)
564
    del self.listener
565
    del self.listenerport
566

    
567
  def testTcpPingToLocalHostAccept(self):
568
    self.assert_(TcpPing(constants.LOCALHOST_IP_ADDRESS,
569
                         self.listenerport,
570
                         timeout=10,
571
                         live_port_needed=True,
572
                         source=constants.LOCALHOST_IP_ADDRESS,
573
                         ),
574
                 "failed to connect to test listener")
575

    
576
    self.assert_(TcpPing(constants.LOCALHOST_IP_ADDRESS,
577
                         self.listenerport,
578
                         timeout=10,
579
                         live_port_needed=True,
580
                         ),
581
                 "failed to connect to test listener (no source)")
582

    
583

    
584
class TestTcpPingDeaf(unittest.TestCase):
585
  """Testcase for TCP version of ping - against non listen(2)ing port"""
586

    
587
  def setUp(self):
588
    self.deaflistener = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
589
    self.deaflistener.bind((constants.LOCALHOST_IP_ADDRESS, 0))
590
    self.deaflistenerport = self.deaflistener.getsockname()[1]
591

    
592
  def tearDown(self):
593
    del self.deaflistener
594
    del self.deaflistenerport
595

    
596
  def testTcpPingToLocalHostAcceptDeaf(self):
597
    self.failIf(TcpPing(constants.LOCALHOST_IP_ADDRESS,
598
                        self.deaflistenerport,
599
                        timeout=constants.TCP_PING_TIMEOUT,
600
                        live_port_needed=True,
601
                        source=constants.LOCALHOST_IP_ADDRESS,
602
                        ), # need successful connect(2)
603
                "successfully connected to deaf listener")
604

    
605
    self.failIf(TcpPing(constants.LOCALHOST_IP_ADDRESS,
606
                        self.deaflistenerport,
607
                        timeout=constants.TCP_PING_TIMEOUT,
608
                        live_port_needed=True,
609
                        ), # need successful connect(2)
610
                "successfully connected to deaf listener (no source addr)")
611

    
612
  def testTcpPingToLocalHostNoAccept(self):
613
    self.assert_(TcpPing(constants.LOCALHOST_IP_ADDRESS,
614
                         self.deaflistenerport,
615
                         timeout=constants.TCP_PING_TIMEOUT,
616
                         live_port_needed=False,
617
                         source=constants.LOCALHOST_IP_ADDRESS,
618
                         ), # ECONNREFUSED is OK
619
                 "failed to ping alive host on deaf port")
620

    
621
    self.assert_(TcpPing(constants.LOCALHOST_IP_ADDRESS,
622
                         self.deaflistenerport,
623
                         timeout=constants.TCP_PING_TIMEOUT,
624
                         live_port_needed=False,
625
                         ), # ECONNREFUSED is OK
626
                 "failed to ping alive host on deaf port (no source addr)")
627

    
628

    
629
class TestOwnIpAddress(unittest.TestCase):
630
  """Testcase for OwnIpAddress"""
631

    
632
  def testOwnLoopback(self):
633
    """check having the loopback ip"""
634
    self.failUnless(OwnIpAddress(constants.LOCALHOST_IP_ADDRESS),
635
                    "Should own the loopback address")
636

    
637
  def testNowOwnAddress(self):
638
    """check that I don't own an address"""
639

    
640
    # network 192.0.2.0/24 is reserved for test/documentation as per
641
    # rfc 3330, so we *should* not have an address of this range... if
642
    # this fails, we should extend the test to multiple addresses
643
    DST_IP = "192.0.2.1"
644
    self.failIf(OwnIpAddress(DST_IP), "Should not own IP address %s" % DST_IP)
645

    
646

    
647
class TestListVisibleFiles(unittest.TestCase):
648
  """Test case for ListVisibleFiles"""
649

    
650
  def setUp(self):
651
    self.path = tempfile.mkdtemp()
652

    
653
  def tearDown(self):
654
    shutil.rmtree(self.path)
655

    
656
  def _test(self, files, expected):
657
    # Sort a copy
658
    expected = expected[:]
659
    expected.sort()
660

    
661
    for name in files:
662
      f = open(os.path.join(self.path, name), 'w')
663
      try:
664
        f.write("Test\n")
665
      finally:
666
        f.close()
667

    
668
    found = ListVisibleFiles(self.path)
669
    found.sort()
670

    
671
    self.assertEqual(found, expected)
672

    
673
  def testAllVisible(self):
674
    files = ["a", "b", "c"]
675
    expected = files
676
    self._test(files, expected)
677

    
678
  def testNoneVisible(self):
679
    files = [".a", ".b", ".c"]
680
    expected = []
681
    self._test(files, expected)
682

    
683
  def testSomeVisible(self):
684
    files = ["a", "b", ".c"]
685
    expected = ["a", "b"]
686
    self._test(files, expected)
687

    
688

    
689
class TestNewUUID(unittest.TestCase):
690
  """Test case for NewUUID"""
691

    
692
  _re_uuid = re.compile('^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-'
693
                        '[a-f0-9]{4}-[a-f0-9]{12}$')
694

    
695
  def runTest(self):
696
    self.failUnless(self._re_uuid.match(utils.NewUUID()))
697

    
698

    
699
class TestUniqueSequence(unittest.TestCase):
700
  """Test case for UniqueSequence"""
701

    
702
  def _test(self, input, expected):
703
    self.assertEqual(utils.UniqueSequence(input), expected)
704

    
705
  def runTest(self):
706
    # Ordered input
707
    self._test([1, 2, 3], [1, 2, 3])
708
    self._test([1, 1, 2, 2, 3, 3], [1, 2, 3])
709
    self._test([1, 2, 2, 3], [1, 2, 3])
710
    self._test([1, 2, 3, 3], [1, 2, 3])
711

    
712
    # Unordered input
713
    self._test([1, 2, 3, 1, 2, 3], [1, 2, 3])
714
    self._test([1, 1, 2, 3, 3, 1, 2], [1, 2, 3])
715

    
716
    # Strings
717
    self._test(["a", "a"], ["a"])
718
    self._test(["a", "b"], ["a", "b"])
719
    self._test(["a", "b", "a"], ["a", "b"])
720

    
721

    
722
class TestFirstFree(unittest.TestCase):
723
  """Test case for the FirstFree function"""
724

    
725
  def test(self):
726
    """Test FirstFree"""
727
    self.failUnlessEqual(FirstFree([0, 1, 3]), 2)
728
    self.failUnlessEqual(FirstFree([]), None)
729
    self.failUnlessEqual(FirstFree([3, 4, 6]), 0)
730
    self.failUnlessEqual(FirstFree([3, 4, 6], base=3), 5)
731
    self.failUnlessRaises(AssertionError, FirstFree, [0, 3, 4, 6], base=3)
732

    
733

    
734
class TestFileLock(unittest.TestCase):
735
  """Test case for the FileLock class"""
736

    
737
  def setUp(self):
738
    self.tmpfile = tempfile.NamedTemporaryFile()
739
    self.lock = utils.FileLock(self.tmpfile.name)
740

    
741
  def testSharedNonblocking(self):
742
    self.lock.Shared(blocking=False)
743
    self.lock.Close()
744

    
745
  def testExclusiveNonblocking(self):
746
    self.lock.Exclusive(blocking=False)
747
    self.lock.Close()
748

    
749
  def testUnlockNonblocking(self):
750
    self.lock.Unlock(blocking=False)
751
    self.lock.Close()
752

    
753
  def testSharedBlocking(self):
754
    self.lock.Shared(blocking=True)
755
    self.lock.Close()
756

    
757
  def testExclusiveBlocking(self):
758
    self.lock.Exclusive(blocking=True)
759
    self.lock.Close()
760

    
761
  def testUnlockBlocking(self):
762
    self.lock.Unlock(blocking=True)
763
    self.lock.Close()
764

    
765
  def testSharedExclusiveUnlock(self):
766
    self.lock.Shared(blocking=False)
767
    self.lock.Exclusive(blocking=False)
768
    self.lock.Unlock(blocking=False)
769
    self.lock.Close()
770

    
771
  def testExclusiveSharedUnlock(self):
772
    self.lock.Exclusive(blocking=False)
773
    self.lock.Shared(blocking=False)
774
    self.lock.Unlock(blocking=False)
775
    self.lock.Close()
776

    
777
  def testCloseShared(self):
778
    self.lock.Close()
779
    self.assertRaises(AssertionError, self.lock.Shared, blocking=False)
780

    
781
  def testCloseExclusive(self):
782
    self.lock.Close()
783
    self.assertRaises(AssertionError, self.lock.Exclusive, blocking=False)
784

    
785
  def testCloseUnlock(self):
786
    self.lock.Close()
787
    self.assertRaises(AssertionError, self.lock.Unlock, blocking=False)
788

    
789

    
790
class TestTimeFunctions(unittest.TestCase):
791
  """Test case for time functions"""
792

    
793
  def runTest(self):
794
    self.assertEqual(utils.SplitTime(1), (1, 0))
795
    self.assertEqual(utils.SplitTime(1.5), (1, 500000))
796
    self.assertEqual(utils.SplitTime(1218448917.4809151), (1218448917, 480915))
797
    self.assertEqual(utils.SplitTime(123.48012), (123, 480120))
798
    self.assertEqual(utils.SplitTime(123.9996), (123, 999600))
799
    self.assertEqual(utils.SplitTime(123.9995), (123, 999500))
800
    self.assertEqual(utils.SplitTime(123.9994), (123, 999400))
801
    self.assertEqual(utils.SplitTime(123.999999999), (123, 999999))
802

    
803
    self.assertRaises(AssertionError, utils.SplitTime, -1)
804

    
805
    self.assertEqual(utils.MergeTime((1, 0)), 1.0)
806
    self.assertEqual(utils.MergeTime((1, 500000)), 1.5)
807
    self.assertEqual(utils.MergeTime((1218448917, 500000)), 1218448917.5)
808

    
809
    self.assertEqual(round(utils.MergeTime((1218448917, 481000)), 3), 1218448917.481)
810
    self.assertEqual(round(utils.MergeTime((1, 801000)), 3), 1.801)
811

    
812
    self.assertRaises(AssertionError, utils.MergeTime, (0, -1))
813
    self.assertRaises(AssertionError, utils.MergeTime, (0, 1000000))
814
    self.assertRaises(AssertionError, utils.MergeTime, (0, 9999999))
815
    self.assertRaises(AssertionError, utils.MergeTime, (-1, 0))
816
    self.assertRaises(AssertionError, utils.MergeTime, (-9999, 0))
817

    
818

    
819
if __name__ == '__main__':
820
  unittest.main()