summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authortreyad <treyad@viosoft.com>2018-11-15 08:31:32 -0800
committertreyad <treyad@viosoft.com>2019-01-14 21:01:36 -0800
commitefd44dbadef7c92253545e9ce2e8034fef259b95 (patch)
treecdaf8997e0181db35f6367f3932c089cca397738
parent07c506d3fff732019651d89de14bf42fa8d9a365 (diff)
Improve SSH to Open/Close interactive terminal
Support Open/Close interactive terminal on a SSH channel. Support Execute command on interactive terminal. JIRA: YARDSTICK-1482 Change-Id: I0d1588707c3fb3e5e65fb72115f27e713d4b4828 Signed-off-by: treyad <treyad@viosoft.com>
-rw-r--r--yardstick/ssh.py80
-rw-r--r--yardstick/tests/unit/test_ssh.py42
2 files changed, 122 insertions, 0 deletions
diff --git a/yardstick/ssh.py b/yardstick/ssh.py
index 8bdc32c7c..c603a81c6 100644
--- a/yardstick/ssh.py
+++ b/yardstick/ssh.py
@@ -456,6 +456,86 @@ class SSH(object):
with client.open_sftp() as sftp:
sftp.getfo(remotepath, file_obj)
+ def interactive_terminal_open(self, time_out=45):
+ """Open interactive terminal on a SSH channel.
+
+ :param time_out: Timeout in seconds.
+ :returns: SSH channel with opened terminal.
+
+ .. warning:: Interruptingcow is used here, and it uses
+ signal(SIGALRM) to let the operating system interrupt program
+ execution. This has the following limitations: Python signal
+ handlers only apply to the main thread, so you cannot use this
+ from other threads. You must not use this in a program that
+ uses SIGALRM itself (this includes certain profilers)
+ """
+ chan = self._get_client().get_transport().open_session()
+ chan.get_pty()
+ chan.invoke_shell()
+ chan.settimeout(int(time_out))
+ chan.set_combine_stderr(True)
+
+ buf = ''
+ while not buf.endswith((":~# ", ":~$ ", "~]$ ", "~]# ")):
+ try:
+ chunk = chan.recv(10 * 1024 * 1024)
+ if not chunk:
+ break
+ buf += chunk
+ if chan.exit_status_ready():
+ self.log.error('Channel exit status ready')
+ break
+ except socket.timeout:
+ raise exceptions.SSHTimeout(error_msg='Socket timeout: %s' % buf)
+ return chan
+
+ def interactive_terminal_exec_command(self, chan, cmd, prompt):
+ """Execute command on interactive terminal.
+
+ interactive_terminal_open() method has to be called first!
+
+ :param chan: SSH channel with opened terminal.
+ :param cmd: Command to be executed.
+ :param prompt: Command prompt, sequence of characters used to
+ indicate readiness to accept commands.
+ :returns: Command output.
+
+ .. warning:: Interruptingcow is used here, and it uses
+ signal(SIGALRM) to let the operating system interrupt program
+ execution. This has the following limitations: Python signal
+ handlers only apply to the main thread, so you cannot use this
+ from other threads. You must not use this in a program that
+ uses SIGALRM itself (this includes certain profilers)
+ """
+ chan.sendall('{c}\n'.format(c=cmd))
+ buf = ''
+ while not buf.endswith(prompt):
+ try:
+ chunk = chan.recv(10 * 1024 * 1024)
+ if not chunk:
+ break
+ buf += chunk
+ if chan.exit_status_ready():
+ self.log.error('Channel exit status ready')
+ break
+ except socket.timeout:
+ message = ("Socket timeout during execution of command: "
+ "%(cmd)s\nBuffer content:\n%(buf)s" % {"cmd": cmd,
+ "buf": buf})
+ raise exceptions.SSHTimeout(error_msg=message)
+ tmp = buf.replace(cmd.replace('\n', ''), '')
+ for item in prompt:
+ tmp.replace(item, '')
+ return tmp
+
+ @staticmethod
+ def interactive_terminal_close(chan):
+ """Close interactive terminal SSH channel.
+
+ :param: chan: SSH channel to be closed.
+ """
+ chan.close()
+
class AutoConnectSSH(SSH):
diff --git a/yardstick/tests/unit/test_ssh.py b/yardstick/tests/unit/test_ssh.py
index 71929f1a2..374fb6644 100644
--- a/yardstick/tests/unit/test_ssh.py
+++ b/yardstick/tests/unit/test_ssh.py
@@ -286,6 +286,48 @@ class SSHTestCase(unittest.TestCase):
mock_paramiko_exec_command.assert_called_once_with('cmd',
get_pty=True)
+ @mock.patch("yardstick.ssh.paramiko")
+ def test_interactive_terminal_open(self, mock_paramiko):
+ fake_client = mock.Mock()
+ fake_session = mock.Mock()
+ fake_session.recv.return_value = ":~# "
+ fake_transport = mock.Mock()
+ fake_transport.open_session.return_value = fake_session
+ fake_client.get_transport.return_value = fake_transport
+ mock_paramiko.SSHClient.return_value = fake_client
+
+ test_ssh = ssh.SSH("admin", "example.net", pkey="key")
+ result = test_ssh.interactive_terminal_open()
+ self.assertEqual(fake_session, result)
+
+ @mock.patch("yardstick.ssh.paramiko")
+ def test_interactive_terminal_exec_command(self, mock_paramiko):
+ fake_client = mock.Mock()
+ fake_session = mock.Mock()
+ fake_session.recv.return_value = "stdout fake data"
+ fake_transport = mock.Mock()
+ fake_transport.open_session.return_value = fake_session
+ fake_client.get_transport.return_value = fake_transport
+ mock_paramiko.SSHClient.return_value = fake_client
+
+ test_ssh = ssh.SSH("admin", "example.net", pkey="key")
+ with mock.patch.object(fake_session, "sendall") \
+ as mock_paramiko_send_command:
+ result = test_ssh.interactive_terminal_exec_command(fake_session,
+ 'cmd', "vat# ")
+ self.assertEqual("stdout fake data", result)
+ mock_paramiko_send_command.assert_called_once_with('cmd\n')
+
+ @mock.patch("yardstick.ssh.paramiko")
+ def test_interactive_terminal_close(self, _):
+ fake_session = mock.Mock()
+ paramiko_sshclient = self.test_client._get_client()
+ paramiko_sshclient.get_transport.open_session.return_value = fake_session
+ with mock.patch.object(fake_session, "close") \
+ as mock_paramiko_terminal_close:
+ self.test_client.interactive_terminal_close(fake_session)
+ mock_paramiko_terminal_close.assert_called_once_with()
+
class SSHRunTestCase(unittest.TestCase):
"""Test SSH.run method in different aspects.