import time
import tempfile
import os.path
+import os
import md5
import socket
-
+import shutil
+import re
import ganeti
+from ganeti import constants
+from ganeti import utils
from ganeti.utils import IsProcessAlive, Lock, Unlock, RunCmd, \
RemoveFile, CheckDict, MatchNameComponent, FormatUnit, \
ParseUnit, AddAuthorizedKey, RemoveAuthorizedKey, \
- ShellQuote, ShellQuoteArgs, _ParseIpOutput, TcpPing
+ ShellQuote, ShellQuoteArgs, TcpPing, ListVisibleFiles, \
+ SetEtcHostsEntry, RemoveEtcHostsEntry
from ganeti.errors import LockError, UnitParseError
+class GanetiTestCase(unittest.TestCase):
+ def assertFileContent(self, file_name, content):
+ """Checks the content of a file.
+
+ """
+ handle = open(file_name, 'r')
+ try:
+ self.assertEqual(handle.read(), content)
+ finally:
+ handle.close()
+
+
class TestIsProcessAlive(unittest.TestCase):
"""Testing case for IsProcessAlive"""
def setUp(self):
class TestLocking(unittest.TestCase):
"""Testing case for the Lock/Unlock functions"""
+
+ def setUp(self):
+ lock_dir = tempfile.mkdtemp(prefix="ganeti.unittest.",
+ suffix=".locking")
+ self.old_lock_dir = constants.LOCK_DIR
+ constants.LOCK_DIR = lock_dir
+
+ 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")
ganeti.utils.Lock("unittest")
self.assertEqual(None, Unlock("unittest"))
-
def testDoubleLock(self):
self.clean_lock("unittest")
ganeti.utils.Lock("unittest")
for sep in ('', ' ', ' ', "\t", "\t "):
for suffix, scale in TestParseUnit.SCALES:
for func in (lambda x: x, str.lower, str.upper):
- self.assertEqual(ParseUnit('1024' + sep + func(suffix)), 1024 * scale)
+ self.assertEqual(ParseUnit('1024' + sep + func(suffix)),
+ 1024 * scale)
def testInvalidInput(self):
for sep in ('-', '_', ',', 'a'):
self.assertRaises(UnitParseError, ParseUnit, '1,3' + suffix)
-class TestSshKeys(unittest.TestCase):
+class TestSshKeys(GanetiTestCase):
"""Test case for the AddAuthorizedKey function"""
KEY_A = 'ssh-dss AAAAB3NzaC1w5256closdj32mZaQU root@key-a'
KEY_B = ('command="/usr/bin/fooserver -t --verbose",from="1.2.3.4" '
'ssh-dss AAAAB3NzaC1w520smc01ms0jfJs22 root@key-b')
- # NOTE: The MD5 sums below were calculated after manually
- # checking the output files.
-
- def writeTestFile(self):
- (fd, tmpname) = tempfile.mkstemp(prefix = 'ganeti-test')
- f = os.fdopen(fd, 'w')
+ def setUp(self):
+ (fd, self.tmpname) = tempfile.mkstemp(prefix='ganeti-test')
try:
- f.write(TestSshKeys.KEY_A)
- f.write("\n")
- f.write(TestSshKeys.KEY_B)
- f.write("\n")
- finally:
- f.close()
+ handle = os.fdopen(fd, 'w')
+ try:
+ handle.write("%s\n" % TestSshKeys.KEY_A)
+ handle.write("%s\n" % TestSshKeys.KEY_B)
+ finally:
+ handle.close()
+ except:
+ utils.RemoveFile(self.tmpname)
+ raise
- return tmpname
+ def tearDown(self):
+ utils.RemoveFile(self.tmpname)
+ del self.tmpname
def testAddingNewKey(self):
- tmpname = self.writeTestFile()
- try:
- AddAuthorizedKey(tmpname, 'ssh-dss AAAAB3NzaC1kc3MAAACB root@test')
+ AddAuthorizedKey(self.tmpname, 'ssh-dss AAAAB3NzaC1kc3MAAACB root@test')
- f = open(tmpname, 'r')
- try:
- self.assertEqual(md5.new(f.read(8192)).hexdigest(),
- 'ccc71523108ca6e9d0343797dc3e9f16')
- finally:
- f.close()
- finally:
- os.unlink(tmpname)
+ self.assertFileContent(self.tmpname,
+ "ssh-dss AAAAB3NzaC1w5256closdj32mZaQU root@key-a\n"
+ 'command="/usr/bin/fooserver -t --verbose",from="1.2.3.4"'
+ " ssh-dss AAAAB3NzaC1w520smc01ms0jfJs22 root@key-b\n"
+ "ssh-dss AAAAB3NzaC1kc3MAAACB root@test\n")
- def testAddingAlmostButNotCompletlyTheSameKey(self):
- tmpname = self.writeTestFile()
- try:
- AddAuthorizedKey(tmpname,
- 'ssh-dss AAAAB3NzaC1w5256closdj32mZaQU root@test')
+ def testAddingAlmostButNotCompletelyTheSameKey(self):
+ AddAuthorizedKey(self.tmpname,
+ 'ssh-dss AAAAB3NzaC1w5256closdj32mZaQU root@test')
- f = open(tmpname, 'r')
- try:
- self.assertEqual(md5.new(f.read(8192)).hexdigest(),
- 'f2c939d57addb5b3a6846884be896b46')
- finally:
- f.close()
- finally:
- os.unlink(tmpname)
+ self.assertFileContent(self.tmpname,
+ "ssh-dss AAAAB3NzaC1w5256closdj32mZaQU root@key-a\n"
+ 'command="/usr/bin/fooserver -t --verbose",from="1.2.3.4"'
+ " ssh-dss AAAAB3NzaC1w520smc01ms0jfJs22 root@key-b\n"
+ "ssh-dss AAAAB3NzaC1w5256closdj32mZaQU root@test\n")
def testAddingExistingKeyWithSomeMoreSpaces(self):
- tmpname = self.writeTestFile()
- try:
- AddAuthorizedKey(tmpname,
- 'ssh-dss AAAAB3NzaC1w5256closdj32mZaQU root@key-a')
+ AddAuthorizedKey(self.tmpname,
+ 'ssh-dss AAAAB3NzaC1w5256closdj32mZaQU root@key-a')
- f = open(tmpname, 'r')
- try:
- self.assertEqual(md5.new(f.read(8192)).hexdigest(),
- '4e612764808bd46337eb0f575415fc30')
- finally:
- f.close()
- finally:
- os.unlink(tmpname)
+ self.assertFileContent(self.tmpname,
+ "ssh-dss AAAAB3NzaC1w5256closdj32mZaQU root@key-a\n"
+ 'command="/usr/bin/fooserver -t --verbose",from="1.2.3.4"'
+ " ssh-dss AAAAB3NzaC1w520smc01ms0jfJs22 root@key-b\n")
def testRemovingExistingKeyWithSomeMoreSpaces(self):
- tmpname = self.writeTestFile()
- try:
- RemoveAuthorizedKey(tmpname,
- 'ssh-dss AAAAB3NzaC1w5256closdj32mZaQU root@key-a')
+ RemoveAuthorizedKey(self.tmpname,
+ 'ssh-dss AAAAB3NzaC1w5256closdj32mZaQU root@key-a')
- f = open(tmpname, 'r')
- try:
- self.assertEqual(md5.new(f.read(8192)).hexdigest(),
- '77516d987fca07f70e30b830b3e4f2ed')
- finally:
- f.close()
- finally:
- os.unlink(tmpname)
+ self.assertFileContent(self.tmpname,
+ 'command="/usr/bin/fooserver -t --verbose",from="1.2.3.4"'
+ " ssh-dss AAAAB3NzaC1w520smc01ms0jfJs22 root@key-b\n")
def testRemovingNonExistingKey(self):
- tmpname = self.writeTestFile()
- try:
- RemoveAuthorizedKey(tmpname,
- 'ssh-dss AAAAB3Nsdfj230xxjxJjsjwjsjdjU root@test')
+ RemoveAuthorizedKey(self.tmpname,
+ 'ssh-dss AAAAB3Nsdfj230xxjxJjsjwjsjdjU root@test')
+
+ self.assertFileContent(self.tmpname,
+ "ssh-dss AAAAB3NzaC1w5256closdj32mZaQU root@key-a\n"
+ 'command="/usr/bin/fooserver -t --verbose",from="1.2.3.4"'
+ " ssh-dss AAAAB3NzaC1w520smc01ms0jfJs22 root@key-b\n")
- f = open(tmpname, 'r')
+
+class TestEtcHosts(GanetiTestCase):
+ """Test functions modifying /etc/hosts"""
+
+ def setUp(self):
+ (fd, self.tmpname) = tempfile.mkstemp(prefix='ganeti-test')
+ try:
+ handle = os.fdopen(fd, 'w')
try:
- self.assertEqual(md5.new(f.read(8192)).hexdigest(),
- '4e612764808bd46337eb0f575415fc30')
+ handle.write('# This is a test file for /etc/hosts\n')
+ handle.write('127.0.0.1\tlocalhost\n')
+ handle.write('192.168.1.1 router gw\n')
finally:
- f.close()
- finally:
- os.unlink(tmpname)
+ handle.close()
+ except:
+ utils.RemoveFile(self.tmpname)
+ raise
+
+ def tearDown(self):
+ utils.RemoveFile(self.tmpname)
+ del self.tmpname
+
+ def testSettingNewIp(self):
+ SetEtcHostsEntry(self.tmpname, '1.2.3.4', 'myhost.domain.tld', ['myhost'])
+
+ self.assertFileContent(self.tmpname,
+ "# This is a test file for /etc/hosts\n"
+ "127.0.0.1\tlocalhost\n"
+ "192.168.1.1 router gw\n"
+ "1.2.3.4\tmyhost.domain.tld myhost\n")
+
+ def testSettingExistingIp(self):
+ SetEtcHostsEntry(self.tmpname, '192.168.1.1', 'myhost.domain.tld',
+ ['myhost'])
+
+ self.assertFileContent(self.tmpname,
+ "# This is a test file for /etc/hosts\n"
+ "127.0.0.1\tlocalhost\n"
+ "192.168.1.1\tmyhost.domain.tld myhost\n")
+
+ def testSettingDuplicateName(self):
+ SetEtcHostsEntry(self.tmpname, '1.2.3.4', 'myhost', ['myhost'])
+
+ self.assertFileContent(self.tmpname,
+ "# This is a test file for /etc/hosts\n"
+ "127.0.0.1\tlocalhost\n"
+ "192.168.1.1 router gw\n"
+ "1.2.3.4\tmyhost\n")
+
+ def testRemovingExistingHost(self):
+ RemoveEtcHostsEntry(self.tmpname, 'router')
+
+ self.assertFileContent(self.tmpname,
+ "# This is a test file for /etc/hosts\n"
+ "127.0.0.1\tlocalhost\n"
+ "192.168.1.1 gw\n")
+
+ def testRemovingSingleExistingHost(self):
+ RemoveEtcHostsEntry(self.tmpname, 'localhost')
+
+ self.assertFileContent(self.tmpname,
+ "# This is a test file for /etc/hosts\n"
+ "192.168.1.1 router gw\n")
+
+ def testRemovingNonExistingHost(self):
+ RemoveEtcHostsEntry(self.tmpname, 'myhost')
+
+ self.assertFileContent(self.tmpname,
+ "# This is a test file for /etc/hosts\n"
+ "127.0.0.1\tlocalhost\n"
+ "192.168.1.1 router gw\n")
+
+ def testRemovingAlias(self):
+ RemoveEtcHostsEntry(self.tmpname, 'gw')
+
+ self.assertFileContent(self.tmpname,
+ "# This is a test file for /etc/hosts\n"
+ "127.0.0.1\tlocalhost\n"
+ "192.168.1.1 router\n")
class TestShellQuoting(unittest.TestCase):
self.assertEqual(ShellQuoteArgs(['a', 'b\'', 'c']), "a 'b'\\\''' c")
-class TestIpAdressList(unittest.TestCase):
- """Test case for local IP addresses"""
-
- def _test(self, output, required):
- ips = _ParseIpOutput(output)
-
- # Sort the output, so our check below works in all cases
- ips.sort()
- required.sort()
-
- self.assertEqual(required, ips)
-
- def testSingleIpAddress(self):
- output = \
- ("3: lo inet 127.0.0.1/8 brd 127.255.255.255 scope host lo\n"
- "5: eth0 inet 10.0.0.1/24 brd 172.30.15.127 scope global eth0\n")
- self._test(output, ['127.0.0.1', '10.0.0.1'])
-
- def testMultipleIpAddresses(self):
- output = \
- ("3: lo inet 127.0.0.1/8 brd 127.255.255.255 scope host lo\n"
- "5: eth0 inet 10.0.0.1/24 brd 172.30.15.127 scope global eth0\n"
- "5: eth0 inet 1.2.3.4/8 brd 1.255.255.255 scope global eth0:test\n")
- self._test(output, ['127.0.0.1', '10.0.0.1', '1.2.3.4'])
-
-
class TestTcpPing(unittest.TestCase):
"""Testcase for TCP version of ping - against listen(2)ing port"""
def setUp(self):
self.listener = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- self.listener.bind(("127.0.0.1", 0))
+ self.listener.bind((constants.LOCALHOST_IP_ADDRESS, 0))
self.listenerport = self.listener.getsockname()[1]
self.listener.listen(1)
del self.listenerport
def testTcpPingToLocalHostAccept(self):
- self.assert_(TcpPing("127.0.0.1",
- "127.0.0.1",
+ self.assert_(TcpPing(constants.LOCALHOST_IP_ADDRESS,
self.listenerport,
timeout=10,
- live_port_needed=True),
+ live_port_needed=True,
+ source=constants.LOCALHOST_IP_ADDRESS,
+ ),
"failed to connect to test listener")
+ self.assert_(TcpPing(constants.LOCALHOST_IP_ADDRESS,
+ self.listenerport,
+ timeout=10,
+ live_port_needed=True,
+ ),
+ "failed to connect to test listener (no source)")
+
class TestTcpPingDeaf(unittest.TestCase):
"""Testcase for TCP version of ping - against non listen(2)ing port"""
def setUp(self):
self.deaflistener = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- self.deaflistener.bind(("127.0.0.1", 0))
+ self.deaflistener.bind((constants.LOCALHOST_IP_ADDRESS, 0))
self.deaflistenerport = self.deaflistener.getsockname()[1]
def tearDown(self):
del self.deaflistenerport
def testTcpPingToLocalHostAcceptDeaf(self):
- self.failIf(TcpPing("127.0.0.1",
- "127.0.0.1",
+ self.failIf(TcpPing(constants.LOCALHOST_IP_ADDRESS,
self.deaflistenerport,
- timeout=10, # timeout for blocking operations
- live_port_needed=True), # need successful connect(2)
+ timeout=constants.TCP_PING_TIMEOUT,
+ live_port_needed=True,
+ source=constants.LOCALHOST_IP_ADDRESS,
+ ), # need successful connect(2)
"successfully connected to deaf listener")
+ self.failIf(TcpPing(constants.LOCALHOST_IP_ADDRESS,
+ self.deaflistenerport,
+ timeout=constants.TCP_PING_TIMEOUT,
+ live_port_needed=True,
+ ), # need successful connect(2)
+ "successfully connected to deaf listener (no source addr)")
+
def testTcpPingToLocalHostNoAccept(self):
- self.assert_(TcpPing("127.0.0.1",
- "127.0.0.1",
+ self.assert_(TcpPing(constants.LOCALHOST_IP_ADDRESS,
self.deaflistenerport,
- timeout=10, # timeout for blocking operations
- live_port_needed=False), # ECONNREFUSED is OK
+ timeout=constants.TCP_PING_TIMEOUT,
+ live_port_needed=False,
+ source=constants.LOCALHOST_IP_ADDRESS,
+ ), # ECONNREFUSED is OK
"failed to ping alive host on deaf port")
+ self.assert_(TcpPing(constants.LOCALHOST_IP_ADDRESS,
+ self.deaflistenerport,
+ timeout=constants.TCP_PING_TIMEOUT,
+ live_port_needed=False,
+ ), # ECONNREFUSED is OK
+ "failed to ping alive host on deaf port (no source addr)")
+
+
+class TestListVisibleFiles(unittest.TestCase):
+ """Test case for ListVisibleFiles"""
+
+ def setUp(self):
+ self.path = tempfile.mkdtemp()
+
+ def tearDown(self):
+ shutil.rmtree(self.path)
+
+ def _test(self, files, expected):
+ # Sort a copy
+ expected = expected[:]
+ expected.sort()
+
+ for name in files:
+ f = open(os.path.join(self.path, name), 'w')
+ try:
+ f.write("Test\n")
+ finally:
+ f.close()
+
+ found = ListVisibleFiles(self.path)
+ found.sort()
+
+ self.assertEqual(found, expected)
+
+ def testAllVisible(self):
+ files = ["a", "b", "c"]
+ expected = files
+ self._test(files, expected)
+
+ def testNoneVisible(self):
+ files = [".a", ".b", ".c"]
+ expected = []
+ self._test(files, expected)
+
+ def testSomeVisible(self):
+ files = ["a", "b", ".c"]
+ expected = ["a", "b"]
+ self._test(files, expected)
+
+
+class TestNewUUID(unittest.TestCase):
+ """Test case for NewUUID"""
+
+ _re_uuid = re.compile('^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-'
+ '[a-f0-9]{4}-[a-f0-9]{12}$')
+
+ def runTest(self):
+ self.failUnless(self._re_uuid.match(utils.NewUUID()))
+
+
+class TestUniqueSequence(unittest.TestCase):
+ """Test case for UniqueSequence"""
+
+ def _test(self, input, expected):
+ self.assertEqual(utils.UniqueSequence(input), expected)
+
+ def runTest(self):
+ # Ordered input
+ self._test([1, 2, 3], [1, 2, 3])
+ self._test([1, 1, 2, 2, 3, 3], [1, 2, 3])
+ self._test([1, 2, 2, 3], [1, 2, 3])
+ self._test([1, 2, 3, 3], [1, 2, 3])
+
+ # Unordered input
+ self._test([1, 2, 3, 1, 2, 3], [1, 2, 3])
+ self._test([1, 1, 2, 3, 3, 1, 2], [1, 2, 3])
+
+ # Strings
+ self._test(["a", "a"], ["a"])
+ self._test(["a", "b"], ["a", "b"])
+ self._test(["a", "b", "a"], ["a", "b"])
+
if __name__ == '__main__':
unittest.main()