[Tempest] Port remote_client into Manila

Manila tempest tests make use of remote_client [1], which won't be making it
to the tempest stable interfaces, as it imports tempest code which would
result in a circular dependency.

This commit ports a reduced version of remote_client into manila code in
order to have manila's tempest plugin to drop the dependency on it.

[1] https://github.com/openstack/tempest/blob/master/tempest/common/utils/linux/remote_client.py

Partially-Implements: bp/tempest-no-deps

Change-Id: I97a8c57adce9cd541766cc1a2f21ca9ceb92efe9
diff --git a/manila_tempest_tests/common/remote_client.py b/manila_tempest_tests/common/remote_client.py
new file mode 100644
index 0000000..29f1b49
--- /dev/null
+++ b/manila_tempest_tests/common/remote_client.py
@@ -0,0 +1,92 @@
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+import six
+import sys
+
+from oslo_log import log
+
+from tempest import config
+from tempest.lib.common import ssh
+from tempest.lib.common.utils import test_utils
+import tempest.lib.exceptions
+
+CONF = config.CONF
+
+LOG = log.getLogger(__name__)
+
+
+def debug_ssh(function):
+    """Decorator to generate extra debug info in case of ssh failure"""
+    def wrapper(self, *args, **kwargs):
+        try:
+            return function(self, *args, **kwargs)
+        except tempest.lib.exceptions.SSHTimeout:
+            try:
+                original_exception = sys.exc_info()
+                caller = test_utils.find_test_caller() or "not found"
+                if self.server:
+                    msg = 'Caller: %s. Timeout trying to ssh to server %s'
+                    LOG.debug(msg, caller, self.server)
+                    if self.log_console and self.servers_client:
+                        try:
+                            msg = 'Console log for server %s: %s'
+                            console_log = (
+                                self.servers_client.get_console_output(
+                                    self.server['id'])['output'])
+                            LOG.debug(msg, self.server['id'], console_log)
+                        except Exception:
+                            msg = 'Could not get console_log for server %s'
+                            LOG.debug(msg, self.server['id'])
+                # re-raise the original ssh timeout exception
+                six.reraise(*original_exception)
+            finally:
+                # Delete the traceback to avoid circular references
+                _, _, trace = original_exception
+                del trace
+    return wrapper
+
+
+class RemoteClient(object):
+
+    def __init__(self, ip_address, username, password=None, pkey=None,
+                 server=None, servers_client=None):
+        """Executes commands in a VM over ssh
+
+        :param ip_address: IP address to ssh to
+        :param username: ssh username
+        :param password: ssh password (optional)
+        :param pkey: ssh public key (optional)
+        :param server: server dict, used for debugging purposes
+        :param servers_client: servers client, used for debugging purposes
+        """
+        self.server = server
+        self.servers_client = servers_client
+        self.log_console = CONF.compute_feature_enabled.console_output
+
+        self.ssh_client = ssh.Client(ip_address, username, password, pkey=pkey)
+
+    @debug_ssh
+    def exec_command(self, cmd):
+        # Shell options below add more clearness on failures,
+        # path is extended for some non-cirros guest oses (centos7)
+        cmd = CONF.validation.ssh_shell_prologue + " " + cmd
+        LOG.debug("Remote command: %s" % cmd)
+        return self.ssh_client.exec_command(cmd)
+
+    @debug_ssh
+    def validate_authentication(self):
+        """Validate ssh connection and authentication
+
+           This method raises an Exception when the validation fails.
+        """
+        self.ssh_client.test_connection_auth()
diff --git a/manila_tempest_tests/tests/scenario/manager_share.py b/manila_tempest_tests/tests/scenario/manager_share.py
index 14fb34d..d3a51bc 100644
--- a/manila_tempest_tests/tests/scenario/manager_share.py
+++ b/manila_tempest_tests/tests/scenario/manager_share.py
@@ -16,12 +16,12 @@
 from oslo_log import log
 import six
 
-from tempest.common.utils.linux import remote_client
 from tempest import config
 from tempest.lib.common.utils import data_utils
 from tempest.scenario import manager
 
 from manila_tempest_tests.common import constants
+from manila_tempest_tests.common import remote_client
 from manila_tempest_tests.services.share.json import shares_client
 from manila_tempest_tests.services.share.v2.json import (
     shares_client as shares_v2_client)