import os.path
import os
import md5
+import signal
import socket
import shutil
import re
+import select
import ganeti
import testutils
from ganeti import constants
from ganeti import utils
-from ganeti.utils import IsProcessAlive, Lock, Unlock, RunCmd, \
+from ganeti.utils import IsProcessAlive, RunCmd, \
RemoveFile, CheckDict, MatchNameComponent, FormatUnit, \
ParseUnit, AddAuthorizedKey, RemoveAuthorizedKey, \
ShellQuote, ShellQuoteArgs, TcpPing, ListVisibleFiles, \
- SetEtcHostsEntry, RemoveEtcHostsEntry
-from ganeti.errors import LockError, UnitParseError
+ SetEtcHostsEntry, RemoveEtcHostsEntry, FirstFree, OwnIpAddress
+from ganeti.errors import LockError, UnitParseError, GenericError, \
+ ProgrammerError
class TestIsProcessAlive(unittest.TestCase):
"""Testing case for IsProcessAlive"""
- def setUp(self):
- # create a zombie and a (hopefully) non-existing process id
- self.pid_zombie = os.fork()
- if self.pid_zombie == 0:
- os._exit(0)
- elif self.pid_zombie < 0:
- raise SystemError("can't fork")
- self.pid_non_existing = os.fork()
- if self.pid_non_existing == 0:
+
+ def _CreateZombie(self):
+ # create a zombie
+ r_fd, w_fd = os.pipe()
+ pid_zombie = os.fork()
+ if pid_zombie == 0:
+ # explicit close of read, write end will be closed only due to exit
+ os.close(r_fd)
os._exit(0)
- elif self.pid_non_existing > 0:
- os.waitpid(self.pid_non_existing, 0)
- else:
+ elif pid_zombie < 0:
raise SystemError("can't fork")
-
+ # parent: we close our end of the w_fd, so reads will error as
+ # soon as the OS cleans the child's filedescriptors on its exit
+ os.close(w_fd)
+ # wait for 60 seconds at max for the exit (pathological case, just
+ # so that the test doesn't hang indefinitely)
+ r_set, w_set, e_set = select.select([r_fd], [], [], 60)
+ if not r_set and not e_set:
+ self.fail("Timeout exceeded in zombie creation")
+ return pid_zombie
def testExists(self):
mypid = os.getpid()
"can't find myself running")
def testZombie(self):
- self.assert_(not IsProcessAlive(self.pid_zombie),
- "zombie not detected as zombie")
-
+ pid_zombie = self._CreateZombie()
+ is_zombie = not IsProcessAlive(pid_zombie)
+ self.assert_(is_zombie, "zombie not detected as zombie")
+ os.waitpid(pid_zombie, os.WNOHANG)
def testNotExisting(self):
- self.assert_(not IsProcessAlive(self.pid_non_existing),
- "noexisting process detected")
+ pid_non_existing = os.fork()
+ if pid_non_existing == 0:
+ os._exit(0)
+ elif pid_non_existing < 0:
+ raise SystemError("can't fork")
+ os.waitpid(pid_non_existing, 0)
+ self.assert_(not IsProcessAlive(pid_non_existing),
+ "nonexisting process detected")
-class TestLocking(unittest.TestCase):
- """Testing case for the Lock/Unlock functions"""
+class TestPidFileFunctions(unittest.TestCase):
+ """Tests for WritePidFile, RemovePidFile and ReadPidFile"""
def setUp(self):
- lock_dir = tempfile.mkdtemp(prefix="ganeti.unittest.",
- suffix=".locking")
- self.old_lock_dir = constants.LOCK_DIR
- constants.LOCK_DIR = lock_dir
+ self.dir = tempfile.mkdtemp()
+ self.f_dpn = lambda name: os.path.join(self.dir, "%s.pid" % name)
+ utils.DaemonPidFileName = self.f_dpn
+
+ def testPidFileFunctions(self):
+ pid_file = self.f_dpn('test')
+ utils.WritePidFile('test')
+ self.failUnless(os.path.exists(pid_file),
+ "PID file should have been created")
+ read_pid = utils.ReadPidFile(pid_file)
+ self.failUnlessEqual(read_pid, os.getpid())
+ self.failUnless(utils.IsProcessAlive(read_pid))
+ self.failUnlessRaises(GenericError, utils.WritePidFile, 'test')
+ utils.RemovePidFile('test')
+ self.failIf(os.path.exists(pid_file),
+ "PID file should not exist anymore")
+ self.failUnlessEqual(utils.ReadPidFile(pid_file), 0,
+ "ReadPidFile should return 0 for missing pid file")
+ fh = open(pid_file, "w")
+ fh.write("blah\n")
+ fh.close()
+ self.failUnlessEqual(utils.ReadPidFile(pid_file), 0,
+ "ReadPidFile should return 0 for invalid pid file")
+ utils.RemovePidFile('test')
+ self.failIf(os.path.exists(pid_file),
+ "PID file should not exist anymore")
+
+ def testKill(self):
+ pid_file = self.f_dpn('child')
+ r_fd, w_fd = os.pipe()
+ new_pid = os.fork()
+ if new_pid == 0: #child
+ utils.WritePidFile('child')
+ os.write(w_fd, 'a')
+ signal.pause()
+ os._exit(0)
+ return
+ # else we are in the parent
+ # wait until the child has written the pid file
+ os.read(r_fd, 1)
+ read_pid = utils.ReadPidFile(pid_file)
+ self.failUnlessEqual(read_pid, new_pid)
+ self.failUnless(utils.IsProcessAlive(new_pid))
+ utils.KillProcess(new_pid)
+ self.failIf(utils.IsProcessAlive(new_pid))
+ utils.RemovePidFile('child')
+ self.failUnlessRaises(ProgrammerError, utils.KillProcess, 0)
def tearDown(self):
- try:
- ganeti.utils.Unlock("unittest")
- except LockError:
- pass
- shutil.rmtree(constants.LOCK_DIR, ignore_errors=True)
- constants.LOCK_DIR = self.old_lock_dir
-
- def clean_lock(self, name):
- try:
- ganeti.utils.Unlock("unittest")
- except LockError:
- pass
-
-
- def testLock(self):
- self.clean_lock("unittest")
- self.assertEqual(None, Lock("unittest"))
-
-
- def testUnlock(self):
- self.clean_lock("unittest")
- ganeti.utils.Lock("unittest")
- self.assertEqual(None, Unlock("unittest"))
-
- def testDoubleLock(self):
- self.clean_lock("unittest")
- ganeti.utils.Lock("unittest")
- self.assertRaises(LockError, Lock, "unittest")
+ for name in os.listdir(self.dir):
+ os.unlink(os.path.join(self.dir, name))
+ os.rmdir(self.dir)
class TestRunCmd(unittest.TestCase):
"failed to ping alive host on deaf port (no source addr)")
+class TestOwnIpAddress(unittest.TestCase):
+ """Testcase for OwnIpAddress"""
+
+ def testOwnLoopback(self):
+ """check having the loopback ip"""
+ self.failUnless(OwnIpAddress(constants.LOCALHOST_IP_ADDRESS),
+ "Should own the loopback address")
+
+ def testNowOwnAddress(self):
+ """check that I don't own an address"""
+
+ # network 192.0.2.0/24 is reserved for test/documentation as per
+ # rfc 3330, so we *should* not have an address of this range... if
+ # this fails, we should extend the test to multiple addresses
+ DST_IP = "192.0.2.1"
+ self.failIf(OwnIpAddress(DST_IP), "Should not own IP address %s" % DST_IP)
+
+
class TestListVisibleFiles(unittest.TestCase):
"""Test case for ListVisibleFiles"""
self._test(["a", "b", "a"], ["a", "b"])
+class TestFirstFree(unittest.TestCase):
+ """Test case for the FirstFree function"""
+
+ def test(self):
+ """Test FirstFree"""
+ self.failUnlessEqual(FirstFree([0, 1, 3]), 2)
+ self.failUnlessEqual(FirstFree([]), None)
+ self.failUnlessEqual(FirstFree([3, 4, 6]), 0)
+ self.failUnlessEqual(FirstFree([3, 4, 6], base=3), 5)
+ self.failUnlessRaises(AssertionError, FirstFree, [0, 3, 4, 6], base=3)
+
+
+class TestFileLock(unittest.TestCase):
+ """Test case for the FileLock class"""
+
+ def setUp(self):
+ self.tmpfile = tempfile.NamedTemporaryFile()
+ self.lock = utils.FileLock(self.tmpfile.name)
+
+ def testSharedNonblocking(self):
+ self.lock.Shared(blocking=False)
+ self.lock.Close()
+
+ def testExclusiveNonblocking(self):
+ self.lock.Exclusive(blocking=False)
+ self.lock.Close()
+
+ def testUnlockNonblocking(self):
+ self.lock.Unlock(blocking=False)
+ self.lock.Close()
+
+ def testSharedBlocking(self):
+ self.lock.Shared(blocking=True)
+ self.lock.Close()
+
+ def testExclusiveBlocking(self):
+ self.lock.Exclusive(blocking=True)
+ self.lock.Close()
+
+ def testUnlockBlocking(self):
+ self.lock.Unlock(blocking=True)
+ self.lock.Close()
+
+ def testSharedExclusiveUnlock(self):
+ self.lock.Shared(blocking=False)
+ self.lock.Exclusive(blocking=False)
+ self.lock.Unlock(blocking=False)
+ self.lock.Close()
+
+ def testExclusiveSharedUnlock(self):
+ self.lock.Exclusive(blocking=False)
+ self.lock.Shared(blocking=False)
+ self.lock.Unlock(blocking=False)
+ self.lock.Close()
+
+ def testCloseShared(self):
+ self.lock.Close()
+ self.assertRaises(AssertionError, self.lock.Shared, blocking=False)
+
+ def testCloseExclusive(self):
+ self.lock.Close()
+ self.assertRaises(AssertionError, self.lock.Exclusive, blocking=False)
+
+ def testCloseUnlock(self):
+ self.lock.Close()
+ self.assertRaises(AssertionError, self.lock.Unlock, blocking=False)
+
+
+class TestTimeFunctions(unittest.TestCase):
+ """Test case for time functions"""
+
+ def runTest(self):
+ self.assertEqual(utils.SplitTime(1), (1, 0))
+ self.assertEqual(utils.SplitTime(1.5), (1, 500000))
+ self.assertEqual(utils.SplitTime(1218448917.4809151), (1218448917, 480915))
+ self.assertEqual(utils.SplitTime(123.48012), (123, 480120))
+ self.assertEqual(utils.SplitTime(123.9996), (123, 999600))
+ self.assertEqual(utils.SplitTime(123.9995), (123, 999500))
+ self.assertEqual(utils.SplitTime(123.9994), (123, 999400))
+ self.assertEqual(utils.SplitTime(123.999999999), (123, 999999))
+
+ self.assertRaises(AssertionError, utils.SplitTime, -1)
+
+ self.assertEqual(utils.MergeTime((1, 0)), 1.0)
+ self.assertEqual(utils.MergeTime((1, 500000)), 1.5)
+ self.assertEqual(utils.MergeTime((1218448917, 500000)), 1218448917.5)
+
+ self.assertEqual(round(utils.MergeTime((1218448917, 481000)), 3), 1218448917.481)
+ self.assertEqual(round(utils.MergeTime((1, 801000)), 3), 1.801)
+
+ self.assertRaises(AssertionError, utils.MergeTime, (0, -1))
+ self.assertRaises(AssertionError, utils.MergeTime, (0, 1000000))
+ self.assertRaises(AssertionError, utils.MergeTime, (0, 9999999))
+ self.assertRaises(AssertionError, utils.MergeTime, (-1, 0))
+ self.assertRaises(AssertionError, utils.MergeTime, (-9999, 0))
+
+
if __name__ == '__main__':
unittest.main()