aboutsummaryrefslogtreecommitdiffstats
path: root/resources/libraries/python/ssh.py
diff options
context:
space:
mode:
authorStefan Kobza <skobza@cisco.com>2016-01-11 18:03:25 +0100
committerStefan Kobza <skobza@cisco.com>2016-02-08 22:38:32 +0100
commit33499c81c94c2d3baef9d3e9f061cd76ef86fa74 (patch)
tree2d000ba17b821339a05e0c039f71e48e09553de9 /resources/libraries/python/ssh.py
parent5cbeca02602061d32212e14f289d65cf648920e4 (diff)
New version of RF tests.
Change-Id: I241a2b7a7706e65f71cfd4a62e2a40f053fc5d07 Signed-off-by: Stefan Kobza <skobza@cisco.com>
Diffstat (limited to 'resources/libraries/python/ssh.py')
-rw-r--r--resources/libraries/python/ssh.py235
1 files changed, 235 insertions, 0 deletions
diff --git a/resources/libraries/python/ssh.py b/resources/libraries/python/ssh.py
new file mode 100644
index 0000000000..72e41c76a6
--- /dev/null
+++ b/resources/libraries/python/ssh.py
@@ -0,0 +1,235 @@
+# Copyright (c) 2016 Cisco and/or its affiliates.
+# 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 paramiko
+from scp import SCPClient
+from time import time
+from robot.api import logger
+from interruptingcow import timeout
+from robot.utils.asserts import assert_equal, assert_not_equal
+
+__all__ = ["exec_cmd", "exec_cmd_no_error"]
+
+# TODO: load priv key
+
+
+class SSH(object):
+
+ __MAX_RECV_BUF = 10*1024*1024
+ __existing_connections = {}
+
+ def __init__(self):
+ self._ssh = paramiko.SSHClient()
+ self._ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+ self._hostname = None
+
+ def _node_hash(self, node):
+ return hash(frozenset([node['host'], node['port']]))
+
+ def connect(self, node):
+ """Connect to node prior to running exec_command or scp.
+
+ If there already is a connection to the node, this method reuses it.
+ """
+ self._hostname = node['host']
+ node_hash = self._node_hash(node)
+ if node_hash in self.__existing_connections:
+ self._ssh = self.__existing_connections[node_hash]
+ else:
+ start = time()
+ self._ssh.connect(node['host'], username=node['username'],
+ password=node['password'])
+ self.__existing_connections[node_hash] = self._ssh
+ logger.trace('connect took {} seconds'.format(time() - start))
+
+ def exec_command(self, cmd, timeout=10):
+ """Execute SSH command on a new channel on the connected Node.
+
+ Returns (return_code, stdout, stderr).
+ """
+ logger.trace('exec_command on {0}: {1}'.format(self._hostname, cmd))
+ start = time()
+ chan = self._ssh.get_transport().open_session()
+ if timeout is not None:
+ chan.settimeout(int(timeout))
+ chan.exec_command(cmd)
+ end = time()
+ logger.trace('exec_command "{0}" on {1} took {2} seconds'.format(
+ cmd, self._hostname, end-start))
+
+ stdout = ""
+ while True:
+ buf = chan.recv(self.__MAX_RECV_BUF)
+ stdout += buf
+ if not buf:
+ break
+
+ stderr = ""
+ while True:
+ buf = chan.recv_stderr(self.__MAX_RECV_BUF)
+ stderr += buf
+ if not buf:
+ break
+
+ return_code = chan.recv_exit_status()
+ logger.trace('chan_recv/_stderr took {} seconds'.format(time()-end))
+
+ return (return_code, stdout, stderr)
+
+ def exec_command_sudo(self, cmd, cmd_input=None, timeout=10):
+ """Execute SSH command with sudo on a new channel on the connected Node.
+
+ :param cmd: Command to be executed.
+ :param cmd_input: Input redirected to the command.
+ :param timeout: Timeout.
+ :return: return_code, stdout, stderr
+
+ :Example:
+
+ >>> from ssh import SSH
+ >>> ssh = SSH()
+ >>> ssh.connect(node)
+ >>> #Execute command without input (sudo -S cmd)
+ >>> ssh.exex_command_sudo("ifconfig eth0 down")
+ >>> #Execute command with input (sudo -S cmd <<< "input")
+ >>> ssh.exex_command_sudo("vpe_api_test", "dump_interface_table")
+ """
+ if cmd_input is None:
+ command = 'sudo -S {c}'.format(c=cmd)
+ else:
+ command = 'sudo -S {c} <<< "{i}"'.format(c=cmd, i=cmd_input)
+ return self.exec_command(command, timeout)
+
+ def interactive_terminal_open(self, time_out=10):
+ """Open interactive terminal on a new channel on the connected Node.
+
+ :param time_out: Timeout in seconds.
+ :return: 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._ssh.get_transport().open_session()
+ chan.get_pty()
+ chan.invoke_shell()
+ chan.settimeout(int(time_out))
+
+ buf = ''
+ try:
+ with timeout(time_out, exception=RuntimeError):
+ while not buf.endswith(':~$ '):
+ if chan.recv_ready():
+ buf = chan.recv(4096)
+ except RuntimeError:
+ raise Exception('Open interactive terminal timeout.')
+ return chan
+
+ def interactive_terminal_exec_command(self, chan, cmd, prompt,
+ time_out=10):
+ """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.
+ :param time_out: Timeout in seconds.
+ :return: 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 = ''
+ try:
+ with timeout(time_out, exception=RuntimeError):
+ while not buf.endswith(prompt):
+ if chan.recv_ready():
+ buf += chan.recv(4096)
+ except RuntimeError:
+ raise Exception("Exec '{c}' timeout.".format(c=cmd))
+ tmp = buf.replace(cmd.replace('\n', ''), '')
+ return tmp.replace(prompt, '')
+
+ def interactive_terminal_close(self, chan):
+ """Close interactive terminal SSH channel.
+
+ :param: chan: SSH channel to be closed.
+ """
+ chan.close()
+
+ def scp(self, local_path, remote_path):
+ """Copy files from local_path to remote_path.
+
+ connect() method has to be called first!
+ """
+ logger.trace('SCP {0} to {1}:{2}'.format(
+ local_path, self._hostname, remote_path))
+ # SCPCLient takes a paramiko transport as its only argument
+ scp = SCPClient(self._ssh.get_transport())
+ start = time()
+ scp.put(local_path, remote_path)
+ scp.close()
+ end = time()
+ logger.trace('SCP took {0} seconds'.format(end-start))
+
+
+def exec_cmd(node, cmd, timeout=None, sudo=False):
+ """Convenience function to ssh/exec/return rc, out & err.
+
+ Returns (rc, stdout, stderr).
+ """
+ if node is None:
+ raise TypeError('Node parameter is None')
+ if cmd is None:
+ raise TypeError('Command parameter is None')
+ if len(cmd) == 0:
+ raise ValueError('Empty command parameter')
+
+ ssh = SSH()
+ try:
+ ssh.connect(node)
+ except Exception, e:
+ logger.error("Failed to connect to node" + e)
+ return None
+
+ try:
+ if not sudo:
+ (ret_code, stdout, stderr) = ssh.exec_command(cmd, timeout=timeout)
+ else:
+ (ret_code, stdout, stderr) = ssh.exec_command_sudo(cmd,
+ timeout=timeout)
+ except Exception, e:
+ logger.error(e)
+ return None
+
+ return (ret_code, stdout, stderr)
+
+def exec_cmd_no_error(node, cmd, timeout=None, sudo=False):
+ """Convenience function to ssh/exec/return out & err.
+ Verifies that return code is zero.
+
+ Returns (stdout, stderr).
+ """
+ (rc, stdout, stderr) = exec_cmd(node,cmd, timeout=timeout, sudo=sudo)
+ assert_equal(rc, 0, 'Command execution failed: "{}"\n{}'.
+ format(cmd, stderr))
+ return (stdout, stderr)