blind review on list-operations added by 2to3:
[nepi.git] / src / nepi / util / sshfuncs.py
index 83e2d7e..f33b9c6 100644 (file)
@@ -38,13 +38,11 @@ _re_inet = re.compile("\d+:\s+(?P<name>[a-z0-9_-]+)\s+inet6?\s+(?P<inet>[a-f0-9.
 
 logger = logging.getLogger("sshfuncs")
 
-def log(msg, level, out = None, err = None):
+def log(msg, level = logging.DEBUG, out = None, err = None):
     if out:
         msg += " - OUT: %s " % out
-
     if err:
         msg += " - ERROR: %s " % err
-
     logger.log(level, msg)
 
 if hasattr(os, "devnull"):
@@ -59,6 +57,7 @@ class STDOUT:
     Special value that when given to rspawn in stderr causes stderr to 
     redirect to whatever stdout was redirected to.
     """
+    pass
 
 class ProcStatus:
     """
@@ -80,8 +79,13 @@ def resolve_hostname(host):
     ip = None
 
     if host in ["localhost", "127.0.0.1", "::1"]:
-        p = subprocess.Popen("ip -o addr list", shell=True,
-                stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+        p = subprocess.Popen(
+            "ip -o addr list",
+            shell=True,
+            stdout=subprocess.PIPE,
+            stderr=subprocess.PIPE,
+            universal_newlines = True,
+        )
         stdout, stderr = p.communicate()
         m = _re_inet.findall(stdout)
         ip = m[0][1].split("/")[0]
@@ -117,10 +121,13 @@ def openssh_has_persist():
     """
     global OPENSSH_HAS_PERSIST
     if OPENSSH_HAS_PERSIST is None:
-        proc = subprocess.Popen(["ssh","-v"],
+        proc = subprocess.Popen(
+            ["ssh", "-v"],
             stdout = subprocess.PIPE,
             stderr = subprocess.STDOUT,
-            stdin = open("/dev/null","r") )
+            stdin = subprocess.DEVNULL,
+            universal_newlines = True,
+        )
         out,err = proc.communicate()
         proc.wait()
         
@@ -189,12 +196,12 @@ def shell_escape(s):
         return s
     else:
         # unsafe string - escape
-        def escp(c):
+        def escape(c):
             if (32 <= ord(c) < 127 or c in ('\r','\n','\t')) and c not in ("'",'"'):
                 return c
             else:
                 return "'$'\\x%02x''" % (ord(c),)
-        s = ''.join(map(escp,s))
+        s = ''.join(map(escape, s))
         return "'%s'" % (s,)
 
 def eintr_retry(func):
@@ -203,15 +210,15 @@ def eintr_retry(func):
     @functools.wraps(func)
     def rv(*p, **kw):
         retry = kw.pop("_retry", False)
-        for i in xrange(0 if retry else 4):
+        for i in range(0 if retry else 4):
             try:
                 return func(*p, **kw)
-            except (select.error, socket.error), args:
+            except (select.error, socket.error) as args:
                 if args[0] == errno.EINTR:
                     continue
                 else:
                     raise 
-            except OSError, e:
+            except OSError as e:
                 if e.errno == errno.EINTR:
                     continue
                 else:
@@ -303,13 +310,13 @@ def rexec(command, host, user,
         stdout = stderr = stdin = None
 
     return _retry_rexec(args, log_msg, 
-            stderr = stderr,
-            stdin = stdin,
-            stdout = stdout,
-            env = env, 
-            retry = retry, 
-            tmp_known_hosts = tmp_known_hosts,
-            blocking = blocking)
+                        stderr = stderr,
+                        stdin = stdin,
+                        stdout = stdout,
+                        env = env, 
+                        retry = retry, 
+                        tmp_known_hosts = tmp_known_hosts,
+                        blocking = blocking)
 
 def rcopy(source, dest,
         port = None,
@@ -337,16 +344,20 @@ def rcopy(source, dest,
     elif isinstance(source, str) and ':' in source:
         remspec, path = source.split(':',1)
     else:
-        raise ValueError, "Both endpoints cannot be local"
+        raise ValueError("Both endpoints cannot be local")
     user,host = remspec.rsplit('@',1)
     
     # plain scp
     tmp_known_hosts = None
 
     args = ['scp', '-q', '-p', '-C',
+            # 2015-06-01 Thierry: I am commenting off blowfish
+            # as this is not available on a plain ubuntu 15.04 install
+            # this IMHO is too fragile, shoud be something the user
+            # decides explicitly (so he is at least aware of that dependency)
             # Speed up transfer using blowfish cypher specification which is 
             # faster than the default one (3des)
-            '-c', 'blowfish',
+            '-c', 'blowfish',
             # Don't bother with localhost. Makes test easier
             '-o', 'NoHostAuthenticationForLocalhost=yes',
             '-o', 'ConnectTimeout=60',
@@ -399,22 +410,22 @@ def rcopy(source, dest,
             blocking = True)
 
 def rspawn(command, pidfile, 
-        stdout = '/dev/null', 
-        stderr = STDOUT, 
-        stdin = '/dev/null',
-        home = None, 
-        create_home = False, 
-        sudo = False,
-        host = None, 
-        port = None, 
-        user = None, 
-        gwuser = None,
-        gw = None,
-        agent = None, 
-        identity = None, 
-        server_key = None,
-        tty = False,
-        strict_host_checking = True):
+           stdout = '/dev/null', 
+           stderr = STDOUT, 
+           stdin = '/dev/null',
+           home = None, 
+           create_home = False, 
+           sudo = False,
+           host = None, 
+           port = None, 
+           user = None, 
+           gwuser = None,
+           gw = None,
+           agent = None, 
+           identity = None, 
+           server_key = None,
+           tty = False,
+           strict_host_checking = True):
     """
     Spawn a remote command such that it will continue working asynchronously in 
     background. 
@@ -494,21 +505,21 @@ def rspawn(command, pidfile,
         )
     
     if proc.wait():
-        raise RuntimeError, "Failed to set up application on host %s: %s %s" % (host, out,err,)
+        raise RuntimeError("Failed to set up application on host %s: %s %s" % (host, out,err,))
 
     return ((out, err), proc)
 
 @eintr_retry
 def rgetpid(pidfile,
-        host = None, 
-        port = None, 
-        user = None, 
-        gwuser = None,
-        gw = None,
-        agent = None, 
-        identity = None,
-        server_key = None,
-        strict_host_checking = True):
+            host = None, 
+            port = None, 
+            user = None, 
+            gwuser = None,
+            gw = None,
+            agent = None, 
+            identity = None,
+            server_key = None,
+            strict_host_checking = True):
     """
     Returns the pid and ppid of a process from a remote file where the 
     information was stored.
@@ -545,7 +556,7 @@ def rgetpid(pidfile,
     
     if out:
         try:
-            return map(int,out.strip().split(' ',1))
+            return [ int(x) for x in out.strip().split(' ',1)) ]
         except:
             # Ignore, many ways to fail that don't matter that much
             return None
@@ -679,23 +690,28 @@ fi
     return (out, err), proc
 
 def _retry_rexec(args,
-        log_msg,
-        stdout = subprocess.PIPE,
-        stdin = subprocess.PIPE, 
-        stderr = subprocess.PIPE,
-        env = None,
-        retry = 3,
-        tmp_known_hosts = None,
-        blocking = True):
-
-    for x in xrange(retry):
+                 log_msg,
+                 stdout = subprocess.PIPE,
+                 stdin = subprocess.PIPE, 
+                 stderr = subprocess.PIPE,
+                 env = None,
+                 retry = 3,
+                 tmp_known_hosts = None,
+                 blocking = True):
+
+    for x in range(retry):
+        # display command actually invoked when debug is turned on
+        message = " ".join( [ "'{}'".format(arg) for arg in args ] )
+        log("sshfuncs: invoking {}".format(message), logging.DEBUG)
         # connects to the remote host and starts a remote connection
-        proc = subprocess.Popen(args,
-                env = env,
-                stdout = stdout,
-                stdin = stdin, 
-                stderr = stderr)
-        
+        proc = subprocess.Popen(
+            args,
+            env = env,
+            stdout = stdout,
+            stdin = stdin, 
+            stderr = stderr,
+            universal_newlines = True,
+        )        
         # attach tempfile object to the process, to make sure the file stays
         # alive until the process is finished with it
         proc._known_hosts = tmp_known_hosts
@@ -709,7 +725,9 @@ def _retry_rexec(args,
                 # The method communicate was re implemented for performance issues
                 # when using python subprocess communicate method the ssh commands 
                 # last one minute each
+                #log("BEFORE communicate", level=logging.INFO); import time; beg=time.time()
                 out, err = _communicate(proc, input=None)
+                #log("AFTER communicate - {}s".format(time.time()-beg), level=logging.INFO)
 
             elif stdout:
                 out = proc.stdout.read()
@@ -737,7 +755,7 @@ def _retry_rexec(args,
                     time.sleep(t)
                     continue
             break
-        except RuntimeError, e:
+        except RuntimeError as e:
             msg = " rexec EXCEPTION - TIMEOUT -> %s \n %s" % ( e.args, log_msg )
             log(msg, logging.DEBUG, out, err)
 
@@ -803,7 +821,7 @@ def _communicate(proc, input, timeout=None, err_on_timeout=True):
 
         try:
             rlist, wlist, xlist = select.select(read_set, write_set, [], select_timeout)
-        except select.error,e:
+        except select.error as e:
             if e[0] != 4:
                 raise
             else:
@@ -826,15 +844,25 @@ def _communicate(proc, input, timeout=None, err_on_timeout=True):
                 write_set.remove(proc.stdin)
 
         if proc.stdout in rlist:
-            data = os.read(proc.stdout.fileno(), 1024)
-            if data == "":
+            # python2 version used to do this
+            # data = os.read(proc.stdout.fileno(), 1024)
+            # however this always returned bytes...
+            data = proc.stdout.read()
+            log('we have read {}'.format(data))
+            # data should be str and not bytes because we use
+            # universal_lines = True, but to be clean
+            # instead of saying data != "" 
+            if not data:
+                log('closing stdout')
                 proc.stdout.close()
                 read_set.remove(proc.stdout)
             stdout.append(data)
 
         if proc.stderr in rlist:
-            data = os.read(proc.stderr.fileno(), 1024)
-            if data == "":
+            # likewise (see above)
+            # data = os.read(proc.stderr.fileno(), 1024)
+            data = proc.stderr.read()
+            if not data:
                 proc.stderr.close()
                 read_set.remove(proc.stderr)
             stderr.append(data)
@@ -845,19 +873,19 @@ def _communicate(proc, input, timeout=None, err_on_timeout=True):
     if stderr is not None:
         stderr = ''.join(stderr)
 
-    # Translate newlines, if requested.  We cannot let the file
-    # object do the translation: It is based on stdio, which is
-    # impossible to combine with select (unless forcing no
-    # buffering).
-    if proc.universal_newlines and hasattr(file, 'newlines'):
-        if stdout:
-            stdout = proc._translate_newlines(stdout)
-        if stderr:
-            stderr = proc._translate_newlines(stderr)
+#    # Translate newlines, if requested.  We cannot let the file
+#    # object do the translation: It is based on stdio, which is
+#    # impossible to combine with select (unless forcing no
+#    # buffering).
+#    if proc.universal_newlines and hasattr(file, 'newlines'):
+#        if stdout:
+#            stdout = proc._translate_newlines(stdout)
+#        if stderr:
+#            stderr = proc._translate_newlines(stderr)
 
     if killed and err_on_timeout:
         errcode = proc.poll()
-        raise RuntimeError("Operation timed out", errcode, stdout, stderr)
+        raise RuntimeError("Operation timed out", errcode, stdout, stderr)
     else:
         if killed:
             proc.poll()