From 2c30e9d709c444390223f96d37304bdbedc080c2 Mon Sep 17 00:00:00 2001
From: Alexander Schreiber <als@google.com>
Date: Mon, 17 Sep 2007 11:18:49 +0000
Subject: [PATCH] Added TcpPing to do ping-alike via TCP connect(2) with
 defined source address. To be used to replace the currently fping(8) based
 reachability test.

Reviewed-by: imsnah
---
 lib/utils.py                  | 35 ++++++++++++++++++++-
 test/ganeti.utils_unittest.py | 57 ++++++++++++++++++++++++++++++++++-
 2 files changed, 90 insertions(+), 2 deletions(-)

diff --git a/lib/utils.py b/lib/utils.py
index b43570f6b..3f2864658 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 285c7fff5..388cb44bd 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()
-- 
GitLab