Merge "standardize ssh auth"
[yardstick.git] / yardstick / ssh.py
index 927ca94..cf9adf0 100644 (file)
@@ -25,7 +25,7 @@ Execute command and get output:
     status, stdout, stderr = ssh.execute("ps ax")
     if status:
         raise Exception("Command failed with non-zero status.")
-    print stdout.splitlines()
+    print(stdout.splitlines())
 
 Execute command with huge output:
 
@@ -62,6 +62,7 @@ Eventlet:
     sshclient = eventlet.import_patched("yardstick.ssh")
 
 """
+from __future__ import absolute_import
 import os
 import select
 import socket
@@ -69,12 +70,15 @@ import time
 import re
 
 import logging
+
 import paramiko
+from chainmap import ChainMap
+from oslo_utils import encodeutils
 from scp import SCPClient
 import six
 
 
-DEFAULT_PORT = 22
+SSH_PORT = paramiko.config.SSH_PORT
 
 
 class SSHError(Exception):
@@ -88,7 +92,7 @@ class SSHTimeout(SSHError):
 class SSH(object):
     """Represent ssh connection."""
 
-    def __init__(self, user, host, port=DEFAULT_PORT, pkey=None,
+    def __init__(self, user, host, port=SSH_PORT, pkey=None,
                  key_filename=None, password=None, name=None):
         """Initialize SSH client.
 
@@ -107,6 +111,9 @@ class SSH(object):
 
         self.user = user
         self.host = host
+        # everybody wants to debug this in the caller, do it here instead
+        self.log.debug("user:%s host:%s", user, host)
+
         # we may get text port from YAML, convert to int
         self.port = int(port)
         self.pkey = self._get_pkey(pkey) if pkey else None
@@ -121,6 +128,23 @@ class SSH(object):
         else:
             logging.getLogger("paramiko").setLevel(logging.WARN)
 
+    @classmethod
+    def from_node(cls, node, overrides=None, defaults=None):
+        if overrides is None:
+            overrides = {}
+        if defaults is None:
+            defaults = {}
+        params = ChainMap(overrides, node, defaults)
+        return cls(
+            user=params['user'],
+            host=params['ip'],
+            # paramiko doesn't like None default, requires SSH_PORT default
+            port=params.get('ssh_port', SSH_PORT),
+            pkey=params.get('pkey'),
+            key_filename=params.get('key_filename'),
+            password=params.get('password'),
+            name=params.get('name'))
+
     def _get_pkey(self, key):
         if isinstance(key, six.string_types):
             key = six.moves.StringIO(key)
@@ -199,7 +223,8 @@ class SSH(object):
         session.exec_command(cmd)
         start_time = time.time()
 
-        data_to_send = ""
+        # encode on transmit, decode on receive
+        data_to_send = encodeutils.safe_encode("", incoming='utf-8')
         stderr_data = None
 
         # If we have data to be sent to stdin then `select' should also
@@ -214,14 +239,15 @@ class SSH(object):
             r, w, e = select.select([session], writes, [session], 1)
 
             if session.recv_ready():
-                data = session.recv(4096)
+                data = encodeutils.safe_decode(session.recv(4096), 'utf-8')
                 self.log.debug("stdout: %r", data)
                 if stdout is not None:
                     stdout.write(data)
                 continue
 
             if session.recv_stderr_ready():
-                stderr_data = session.recv_stderr(4096)
+                stderr_data = encodeutils.safe_decode(
+                    session.recv_stderr(4096), 'utf-8')
                 self.log.debug("stderr: %r", stderr_data)
                 if stderr is not None:
                     stderr.write(stderr_data)
@@ -230,7 +256,11 @@ class SSH(object):
             if session.send_ready():
                 if stdin is not None and not stdin.closed:
                     if not data_to_send:
-                        data_to_send = stdin.read(4096)
+                        stdin_txt = stdin.read(4096)
+                        if stdin_txt is None:
+                            stdin_txt = ''
+                        data_to_send = encodeutils.safe_encode(
+                            stdin_txt, incoming='utf-8')
                         if not data_to_send:
                             # we may need to keep stdin open
                             if not keep_stdin_open: