diff --git a/lib/utils.py b/lib/utils.py index b43570f6b14b9061c6a0e1d5c6c176d74afadbf0..3f28646587c4709eb2397010af084296096efeb6 100644 --- a/lib/utils.py +++ b/lib/utils.py @@ -32,7 +32,7 @@ import re import socket import tempfile import shutil -from errno import ENOENT, ENOTDIR, EISDIR, EEXIST +from errno import ENOENT, ENOTDIR, EISDIR, EEXIST, EADDRNOTAVAIL, ECONNREFUSED from ganeti import logger from ganeti import errors @@ -786,3 +786,36 @@ def GetLocalIPAddresses(): " output: %s" % (result.cmd, result.fail_reason, result.output)) return _ParseIpOutput(result.output) + + +def TcpPing(source, target, port, timeout=10, live_port_needed=True): + """Simple ping implementation using TCP connect(2). + + Try to do a TCP connect(2) from the specified source IP to the specified + target IP and the specified target port. If live_port_needed is set to true, + requires the remote end to accept the connection. The timeout is specified + in seconds and defaults to 10 seconds + + """ + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + + sucess = False + + try: + sock.bind((source, 0)) + except socket.error, (errcode, errstring): + if errcode == EADDRNOTAVAIL: + success = False + + sock.settimeout(timeout) + + try: + sock.connect((target, port)) + sock.close() + success = True + except socket.timeout: + success = False + except socket.error, (errcode, errstring): + success = (not live_port_needed) and (errcode == ECONNREFUSED) + + return success diff --git a/test/ganeti.utils_unittest.py b/test/ganeti.utils_unittest.py index 285c7fff5fd403ded86c1984b0eb0aaaf81f4e58..388cb44bd804a0816f903cd0e9ab1f3e9d35e729 100755 --- a/test/ganeti.utils_unittest.py +++ b/test/ganeti.utils_unittest.py @@ -27,14 +27,17 @@ import time import tempfile import os.path import md5 +import socket +from errno import EADDRNOTAVAIL import ganeti from ganeti.utils import IsProcessAlive, Lock, Unlock, RunCmd, \ RemoveFile, CheckDict, MatchNameComponent, FormatUnit, \ ParseUnit, AddAuthorizedKey, RemoveAuthorizedKey, \ - ShellQuote, ShellQuoteArgs, _ParseIpOutput + ShellQuote, ShellQuoteArgs, _ParseIpOutput, TcpPing from ganeti.errors import LockError, UnitParseError + class TestIsProcessAlive(unittest.TestCase): """Testing case for IsProcessAlive""" def setUp(self): @@ -467,5 +470,57 @@ class TestIpAdressList(unittest.TestCase): 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.listenerport = self.listener.getsockname()[1] + self.listener.listen(1) + + def tearDown(self): + self.listener.shutdown(socket.SHUT_RDWR) + del self.listener + del self.listenerport + + def testTcpPingToLocalHostAccept(self): + self.assert_(TcpPing("127.0.0.1", + "127.0.0.1", + self.listenerport, + timeout=10, + live_port_needed=True), + "failed to connect to test listener") + + +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.deaflistenerport = self.deaflistener.getsockname()[1] + + def tearDown(self): + del self.deaflistener + del self.deaflistenerport + + def testTcpPingToLocalHostAcceptDeaf(self): + self.failIf(TcpPing("127.0.0.1", + "127.0.0.1", + self.deaflistenerport, + timeout=10, # timeout for blocking operations + live_port_needed=True), # need successful connect(2) + "successfully connected to deaf listener") + + def testTcpPingToLocalHostNoAccept(self): + self.assert_(TcpPing("127.0.0.1", + "127.0.0.1", + self.deaflistenerport, + timeout=10, # timeout for blocking operations + live_port_needed=False), # ECONNREFUSED is OK + "failed to ping alive host on deaf port") + + if __name__ == '__main__': unittest.main()