Merge "Remove common/ssh.py due to migration to tempest_lib"
diff --git a/tempest/common/ssh.py b/tempest/common/ssh.py
deleted file mode 100644
index d0e484c..0000000
--- a/tempest/common/ssh.py
+++ /dev/null
@@ -1,151 +0,0 @@
-# Copyright 2012 OpenStack Foundation
-# All Rights Reserved.
-#
-# 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 select
-import socket
-import time
-import warnings
-
-from oslo_log import log as logging
-import six
-from six import moves
-
-from tempest import exceptions
-
-
-with warnings.catch_warnings():
- warnings.simplefilter("ignore")
- import paramiko
-
-
-LOG = logging.getLogger(__name__)
-
-
-class Client(object):
-
- def __init__(self, host, username, password=None, timeout=300, pkey=None,
- channel_timeout=10, look_for_keys=False, key_filename=None):
- self.host = host
- self.username = username
- self.password = password
- if isinstance(pkey, six.string_types):
- pkey = paramiko.RSAKey.from_private_key(
- moves.cStringIO(str(pkey)))
- self.pkey = pkey
- self.look_for_keys = look_for_keys
- self.key_filename = key_filename
- self.timeout = int(timeout)
- self.channel_timeout = float(channel_timeout)
- self.buf_size = 1024
-
- def _get_ssh_connection(self, sleep=1.5, backoff=1):
- """Returns an ssh connection to the specified host."""
- bsleep = sleep
- ssh = paramiko.SSHClient()
- ssh.set_missing_host_key_policy(
- paramiko.AutoAddPolicy())
- _start_time = time.time()
- if self.pkey is not None:
- LOG.info("Creating ssh connection to '%s' as '%s'"
- " with public key authentication",
- self.host, self.username)
- else:
- LOG.info("Creating ssh connection to '%s' as '%s'"
- " with password %s",
- self.host, self.username, str(self.password))
- attempts = 0
- while True:
- try:
- ssh.connect(self.host, username=self.username,
- password=self.password,
- look_for_keys=self.look_for_keys,
- key_filename=self.key_filename,
- timeout=self.channel_timeout, pkey=self.pkey)
- LOG.info("ssh connection to %s@%s successfuly created",
- self.username, self.host)
- return ssh
- except (socket.error,
- paramiko.SSHException) as e:
- if self._is_timed_out(_start_time):
- LOG.exception("Failed to establish authenticated ssh"
- " connection to %s@%s after %d attempts",
- self.username, self.host, attempts)
- raise exceptions.SSHTimeout(host=self.host,
- user=self.username,
- password=self.password)
- bsleep += backoff
- attempts += 1
- LOG.warning("Failed to establish authenticated ssh"
- " connection to %s@%s (%s). Number attempts: %s."
- " Retry after %d seconds.",
- self.username, self.host, e, attempts, bsleep)
- time.sleep(bsleep)
-
- def _is_timed_out(self, start_time):
- return (time.time() - self.timeout) > start_time
-
- def exec_command(self, cmd):
- """
- Execute the specified command on the server.
-
- Note that this method is reading whole command outputs to memory, thus
- shouldn't be used for large outputs.
-
- :returns: data read from standard output of the command.
- :raises: SSHExecCommandFailed if command returns nonzero
- status. The exception contains command status stderr content.
- """
- ssh = self._get_ssh_connection()
- transport = ssh.get_transport()
- channel = transport.open_session()
- channel.fileno() # Register event pipe
- channel.exec_command(cmd)
- channel.shutdown_write()
- out_data = []
- err_data = []
- poll = select.poll()
- poll.register(channel, select.POLLIN)
- start_time = time.time()
-
- while True:
- ready = poll.poll(self.channel_timeout)
- if not any(ready):
- if not self._is_timed_out(start_time):
- continue
- raise exceptions.TimeoutException(
- "Command: '{0}' executed on host '{1}'.".format(
- cmd, self.host))
- if not ready[0]: # If there is nothing to read.
- continue
- out_chunk = err_chunk = None
- if channel.recv_ready():
- out_chunk = channel.recv(self.buf_size)
- out_data += out_chunk,
- if channel.recv_stderr_ready():
- err_chunk = channel.recv_stderr(self.buf_size)
- err_data += err_chunk,
- if channel.closed and not err_chunk and not out_chunk:
- break
- exit_status = channel.recv_exit_status()
- if 0 != exit_status:
- raise exceptions.SSHExecCommandFailed(
- command=cmd, exit_status=exit_status,
- strerror=''.join(err_data))
- return ''.join(out_data)
-
- def test_connection_auth(self):
- """Raises an exception when we can not connect to server via ssh."""
- connection = self._get_ssh_connection()
- connection.close()
diff --git a/tempest/common/utils/linux/remote_client.py b/tempest/common/utils/linux/remote_client.py
index 29fb493..bedff1f 100644
--- a/tempest/common/utils/linux/remote_client.py
+++ b/tempest/common/utils/linux/remote_client.py
@@ -15,8 +15,8 @@
import time
import six
+from tempest_lib.common import ssh
-from tempest.common import ssh
from tempest import config
from tempest import exceptions
diff --git a/tempest/exceptions.py b/tempest/exceptions.py
index 09f7016..15617c6 100644
--- a/tempest/exceptions.py
+++ b/tempest/exceptions.py
@@ -128,17 +128,6 @@
message = "Got identity error"
-class SSHTimeout(TempestException):
- message = ("Connection to the %(host)s via SSH timed out.\n"
- "User: %(user)s, Password: %(password)s")
-
-
-class SSHExecCommandFailed(TempestException):
- """Raised when remotely executed command returns nonzero status."""
- message = ("Command '%(command)s', exit status: %(exit_status)d, "
- "Error:\n%(strerror)s")
-
-
class ServerUnreachable(TempestException):
message = "The server is not reachable via the configured network"
diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py
index 98bacf0..e6ab163 100644
--- a/tempest/scenario/manager.py
+++ b/tempest/scenario/manager.py
@@ -381,7 +381,7 @@
def _log_net_info(self, exc):
# network debug is called as part of ssh init
- if not isinstance(exc, exceptions.SSHTimeout):
+ if not isinstance(exc, lib_exc.SSHTimeout):
LOG.debug('Network information on a devstack host')
def create_server_snapshot(self, server, name=None):
@@ -766,7 +766,7 @@
def ping_remote():
try:
source.ping_host(dest)
- except exceptions.SSHExecCommandFailed:
+ except lib_exc.SSHExecCommandFailed:
LOG.warn('Failed to ping IP: %s via a ssh connection from: %s.'
% (dest, source.ssh_client.host))
return not should_succeed
diff --git a/tempest/stress/driver.py b/tempest/stress/driver.py
index 62e60d6..72a2796 100644
--- a/tempest/stress/driver.py
+++ b/tempest/stress/driver.py
@@ -20,11 +20,11 @@
from oslo_log import log as logging
from oslo_utils import importutils
from six import moves
+from tempest_lib.common import ssh
from tempest_lib.common.utils import data_utils
from tempest import clients
from tempest.common import isolated_creds
-from tempest.common import ssh
from tempest import config
from tempest import exceptions
from tempest.stress import cleanup
diff --git a/tempest/tests/test_ssh.py b/tempest/tests/test_ssh.py
deleted file mode 100644
index aaacaab..0000000
--- a/tempest/tests/test_ssh.py
+++ /dev/null
@@ -1,189 +0,0 @@
-# Copyright 2014 OpenStack Foundation
-#
-# 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 contextlib
-import socket
-import time
-
-import mock
-import testtools
-
-from tempest.common import ssh
-from tempest import exceptions
-from tempest.tests import base
-
-
-class TestSshClient(base.TestCase):
-
- def test_pkey_calls_paramiko_RSAKey(self):
- with contextlib.nested(
- mock.patch('paramiko.RSAKey.from_private_key'),
- mock.patch('six.moves.cStringIO')) as (rsa_mock, cs_mock):
- cs_mock.return_value = mock.sentinel.csio
- pkey = 'mykey'
- ssh.Client('localhost', 'root', pkey=pkey)
- rsa_mock.assert_called_once_with(mock.sentinel.csio)
- cs_mock.assert_called_once_with('mykey')
- rsa_mock.reset_mock()
- cs_mock.reset_mock()
- pkey = mock.sentinel.pkey
- # Shouldn't call out to load a file from RSAKey, since
- # a sentinel isn't a basestring...
- ssh.Client('localhost', 'root', pkey=pkey)
- self.assertEqual(0, rsa_mock.call_count)
- self.assertEqual(0, cs_mock.call_count)
-
- def _set_ssh_connection_mocks(self):
- client_mock = mock.MagicMock()
- client_mock.connect.return_value = True
- return (self.patch('paramiko.SSHClient'),
- self.patch('paramiko.AutoAddPolicy'),
- client_mock)
-
- def test_get_ssh_connection(self):
- c_mock, aa_mock, client_mock = self._set_ssh_connection_mocks()
- s_mock = self.patch('time.sleep')
-
- c_mock.return_value = client_mock
- aa_mock.return_value = mock.sentinel.aa
-
- # Test normal case for successful connection on first try
- client = ssh.Client('localhost', 'root', timeout=2)
- client._get_ssh_connection(sleep=1)
-
- aa_mock.assert_called_once_with()
- client_mock.set_missing_host_key_policy.assert_called_once_with(
- mock.sentinel.aa)
- expected_connect = [mock.call(
- 'localhost',
- username='root',
- pkey=None,
- key_filename=None,
- look_for_keys=False,
- timeout=10.0,
- password=None
- )]
- self.assertEqual(expected_connect, client_mock.connect.mock_calls)
- self.assertEqual(0, s_mock.call_count)
-
- def test_get_ssh_connection_two_attemps(self):
- c_mock, aa_mock, client_mock = self._set_ssh_connection_mocks()
-
- c_mock.return_value = client_mock
- client_mock.connect.side_effect = [
- socket.error,
- mock.MagicMock()
- ]
-
- client = ssh.Client('localhost', 'root', timeout=1)
- start_time = int(time.time())
- client._get_ssh_connection(sleep=1)
- end_time = int(time.time())
- self.assertLess((end_time - start_time), 4)
- self.assertGreater((end_time - start_time), 1)
-
- def test_get_ssh_connection_timeout(self):
- c_mock, aa_mock, client_mock = self._set_ssh_connection_mocks()
-
- c_mock.return_value = client_mock
- client_mock.connect.side_effect = [
- socket.error,
- socket.error,
- socket.error,
- ]
-
- client = ssh.Client('localhost', 'root', timeout=2)
- start_time = int(time.time())
- with testtools.ExpectedException(exceptions.SSHTimeout):
- client._get_ssh_connection()
- end_time = int(time.time())
- self.assertLess((end_time - start_time), 5)
- self.assertGreaterEqual((end_time - start_time), 2)
-
- def test_exec_command(self):
- gsc_mock = self.patch('tempest.common.ssh.Client._get_ssh_connection')
- ito_mock = self.patch('tempest.common.ssh.Client._is_timed_out')
- select_mock = self.patch('select.poll')
-
- client_mock = mock.MagicMock()
- tran_mock = mock.MagicMock()
- chan_mock = mock.MagicMock()
- poll_mock = mock.MagicMock()
-
- def reset_mocks():
- gsc_mock.reset_mock()
- ito_mock.reset_mock()
- select_mock.reset_mock()
- poll_mock.reset_mock()
- client_mock.reset_mock()
- tran_mock.reset_mock()
- chan_mock.reset_mock()
-
- select_mock.return_value = poll_mock
- gsc_mock.return_value = client_mock
- ito_mock.return_value = True
- client_mock.get_transport.return_value = tran_mock
- tran_mock.open_session.return_value = chan_mock
- poll_mock.poll.side_effect = [
- [0, 0, 0]
- ]
-
- # Test for a timeout condition immediately raised
- client = ssh.Client('localhost', 'root', timeout=2)
- with testtools.ExpectedException(exceptions.TimeoutException):
- client.exec_command("test")
-
- chan_mock.fileno.assert_called_once_with()
- chan_mock.exec_command.assert_called_once_with("test")
- chan_mock.shutdown_write.assert_called_once_with()
-
- SELECT_POLLIN = 1
- poll_mock.register.assert_called_once_with(chan_mock, SELECT_POLLIN)
- poll_mock.poll.assert_called_once_with(10)
-
- # Test for proper reading of STDOUT and STDERROR and closing
- # of all file descriptors.
-
- reset_mocks()
-
- select_mock.return_value = poll_mock
- gsc_mock.return_value = client_mock
- ito_mock.return_value = False
- client_mock.get_transport.return_value = tran_mock
- tran_mock.open_session.return_value = chan_mock
- poll_mock.poll.side_effect = [
- [1, 0, 0]
- ]
- closed_prop = mock.PropertyMock(return_value=True)
- type(chan_mock).closed = closed_prop
- chan_mock.recv_exit_status.return_value = 0
- chan_mock.recv.return_value = ''
- chan_mock.recv_stderr.return_value = ''
-
- client = ssh.Client('localhost', 'root', timeout=2)
- client.exec_command("test")
-
- chan_mock.fileno.assert_called_once_with()
- chan_mock.exec_command.assert_called_once_with("test")
- chan_mock.shutdown_write.assert_called_once_with()
-
- SELECT_POLLIN = 1
- poll_mock.register.assert_called_once_with(chan_mock, SELECT_POLLIN)
- poll_mock.poll.assert_called_once_with(10)
- chan_mock.recv_ready.assert_called_once_with()
- chan_mock.recv.assert_called_once_with(1024)
- chan_mock.recv_stderr_ready.assert_called_once_with()
- chan_mock.recv_stderr.assert_called_once_with(1024)
- chan_mock.recv_exit_status.assert_called_once_with()
- closed_prop.assert_called_once_with()