blob: 20527055d9c938194c0928a26cf330368df1b9a7 [file] [log] [blame]
Jay Pipes051075a2012-04-28 17:39:37 -04001# vim: tabstop=4 shiftwidth=4 softtabstop=4
2
3# Copyright 2012 OpenStack, LLC
4# All Rights Reserved.
5#
6# Licensed under the Apache License, Version 2.0 (the "License"); you may
7# not use this file except in compliance with the License. You may obtain
8# a copy of the License at
9#
10# http://www.apache.org/licenses/LICENSE-2.0
11#
12# Unless required by applicable law or agreed to in writing, software
13# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15# License for the specific language governing permissions and limitations
16# under the License.
17
Jay Pipes051075a2012-04-28 17:39:37 -040018
Monty Taylorb2ca5ca2013-04-28 18:00:21 -070019import cStringIO
Matthew Treinisha83a16e2012-12-07 13:44:02 -050020import select
21import socket
22import time
23import warnings
24
Daryl Walleck6b9b2882012-04-08 21:43:39 -050025from tempest import exceptions
Daryl Walleck1465d612011-11-02 02:22:15 -050026
Jay Pipes051075a2012-04-28 17:39:37 -040027
Daryl Walleck1465d612011-11-02 02:22:15 -050028with warnings.catch_warnings():
29 warnings.simplefilter("ignore")
30 import paramiko
31
32
33class Client(object):
34
Attila Fazekasa23f5002012-10-23 19:32:45 +020035 def __init__(self, host, username, password=None, timeout=300, pkey=None,
Jay Pipes051075a2012-04-28 17:39:37 -040036 channel_timeout=10, look_for_keys=False, key_filename=None):
Daryl Walleck1465d612011-11-02 02:22:15 -050037 self.host = host
38 self.username = username
39 self.password = password
Attila Fazekasa23f5002012-10-23 19:32:45 +020040 if isinstance(pkey, basestring):
Monty Taylorb2ca5ca2013-04-28 18:00:21 -070041 pkey = paramiko.RSAKey.from_private_key(
42 cStringIO.StringIO(str(pkey)))
Attila Fazekasa23f5002012-10-23 19:32:45 +020043 self.pkey = pkey
Jay Pipes051075a2012-04-28 17:39:37 -040044 self.look_for_keys = look_for_keys
45 self.key_filename = key_filename
Daryl Walleck1465d612011-11-02 02:22:15 -050046 self.timeout = int(timeout)
Jaroslav Hennerab327842012-09-11 15:44:29 +020047 self.channel_timeout = float(channel_timeout)
48 self.buf_size = 1024
Daryl Walleck1465d612011-11-02 02:22:15 -050049
Andrea Frittoli334f1fd2013-05-15 06:57:43 +010050 def _get_ssh_connection(self, sleep=1.5, backoff=1.01):
Sean Daguef237ccb2013-01-04 15:19:14 -050051 """Returns an ssh connection to the specified host."""
Daryl Walleck1465d612011-11-02 02:22:15 -050052 _timeout = True
Andrea Frittoli334f1fd2013-05-15 06:57:43 +010053 bsleep = sleep
Daryl Walleck1465d612011-11-02 02:22:15 -050054 ssh = paramiko.SSHClient()
55 ssh.set_missing_host_key_policy(
56 paramiko.AutoAddPolicy())
57 _start_time = time.time()
58
Mate Lakatc3f8cd62013-08-23 12:00:42 +010059 while not self._is_timed_out(_start_time):
Daryl Walleck1465d612011-11-02 02:22:15 -050060 try:
61 ssh.connect(self.host, username=self.username,
Jay Pipes051075a2012-04-28 17:39:37 -040062 password=self.password,
63 look_for_keys=self.look_for_keys,
64 key_filename=self.key_filename,
Attila Fazekasa23f5002012-10-23 19:32:45 +020065 timeout=self.timeout, pkey=self.pkey)
Daryl Walleck1465d612011-11-02 02:22:15 -050066 _timeout = False
67 break
Andrea Frittoli334f1fd2013-05-15 06:57:43 +010068 except (socket.error,
69 paramiko.AuthenticationException):
70 time.sleep(bsleep)
71 bsleep *= backoff
Daryl Walleck1465d612011-11-02 02:22:15 -050072 continue
73 if _timeout:
Daryl Walleck6b9b2882012-04-08 21:43:39 -050074 raise exceptions.SSHTimeout(host=self.host,
75 user=self.username,
76 password=self.password)
Daryl Walleck1465d612011-11-02 02:22:15 -050077 return ssh
78
Mate Lakatc3f8cd62013-08-23 12:00:42 +010079 def _is_timed_out(self, start_time):
80 return (time.time() - self.timeout) > start_time
Daryl Walleck1465d612011-11-02 02:22:15 -050081
82 def connect_until_closed(self):
Sean Daguef237ccb2013-01-04 15:19:14 -050083 """Connect to the server and wait until connection is lost."""
Daryl Walleck1465d612011-11-02 02:22:15 -050084 try:
85 ssh = self._get_ssh_connection()
86 _transport = ssh.get_transport()
87 _start_time = time.time()
Mate Lakatc3f8cd62013-08-23 12:00:42 +010088 _timed_out = self._is_timed_out(_start_time)
Daryl Walleck1465d612011-11-02 02:22:15 -050089 while _transport.is_active() and not _timed_out:
90 time.sleep(5)
Mate Lakatc3f8cd62013-08-23 12:00:42 +010091 _timed_out = self._is_timed_out(_start_time)
Daryl Walleck1465d612011-11-02 02:22:15 -050092 ssh.close()
93 except (EOFError, paramiko.AuthenticationException, socket.error):
94 return
95
96 def exec_command(self, cmd):
Jaroslav Hennerab327842012-09-11 15:44:29 +020097 """
98 Execute the specified command on the server.
Daryl Walleck1465d612011-11-02 02:22:15 -050099
Jaroslav Hennerab327842012-09-11 15:44:29 +0200100 Note that this method is reading whole command outputs to memory, thus
101 shouldn't be used for large outputs.
Daryl Walleck1465d612011-11-02 02:22:15 -0500102
Jaroslav Hennerab327842012-09-11 15:44:29 +0200103 :returns: data read from standard output of the command.
104 :raises: SSHExecCommandFailed if command returns nonzero
105 status. The exception contains command status stderr content.
Daryl Walleck1465d612011-11-02 02:22:15 -0500106 """
107 ssh = self._get_ssh_connection()
Jaroslav Hennerab327842012-09-11 15:44:29 +0200108 transport = ssh.get_transport()
109 channel = transport.open_session()
Attila Fazekase14e5a42013-03-06 07:52:51 +0100110 channel.fileno() # Register event pipe
Jaroslav Hennerab327842012-09-11 15:44:29 +0200111 channel.exec_command(cmd)
112 channel.shutdown_write()
113 out_data = []
114 err_data = []
Matthew Treinishdcaa2b42013-08-12 19:16:16 +0000115 poll = select.poll()
116 poll.register(channel, select.POLLIN)
Mate Lakat99f16632013-08-23 08:50:32 +0100117 start_time = time.time()
118
Jaroslav Hennerab327842012-09-11 15:44:29 +0200119 while True:
Matthew Treinishdcaa2b42013-08-12 19:16:16 +0000120 ready = poll.poll(self.channel_timeout)
Jaroslav Hennerab327842012-09-11 15:44:29 +0200121 if not any(ready):
Mate Lakatc3f8cd62013-08-23 12:00:42 +0100122 if not self._is_timed_out(start_time):
Mate Lakat99f16632013-08-23 08:50:32 +0100123 continue
Jaroslav Hennerab327842012-09-11 15:44:29 +0200124 raise exceptions.TimeoutException(
Sean Dague14c68182013-04-14 15:34:30 -0400125 "Command: '{0}' executed on host '{1}'.".format(
126 cmd, self.host))
Jaroslav Hennerab327842012-09-11 15:44:29 +0200127 if not ready[0]: # If there is nothing to read.
128 continue
129 out_chunk = err_chunk = None
130 if channel.recv_ready():
131 out_chunk = channel.recv(self.buf_size)
132 out_data += out_chunk,
133 if channel.recv_stderr_ready():
134 err_chunk = channel.recv_stderr(self.buf_size)
135 err_data += err_chunk,
136 if channel.closed and not err_chunk and not out_chunk:
137 break
138 exit_status = channel.recv_exit_status()
139 if 0 != exit_status:
140 raise exceptions.SSHExecCommandFailed(
Sean Dague14c68182013-04-14 15:34:30 -0400141 command=cmd, exit_status=exit_status,
142 strerror=''.join(err_data))
Jaroslav Hennerab327842012-09-11 15:44:29 +0200143 return ''.join(out_data)
Daryl Walleck1465d612011-11-02 02:22:15 -0500144
145 def test_connection_auth(self):
Sean Daguef237ccb2013-01-04 15:19:14 -0500146 """Returns true if ssh can connect to server."""
Daryl Walleck1465d612011-11-02 02:22:15 -0500147 try:
148 connection = self._get_ssh_connection()
149 connection.close()
150 except paramiko.AuthenticationException:
151 return False
152
153 return True