blob: faf182a8a3b89f4c87b57c6f1f91d03c097563bb [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
Daryl Walleck1465d612011-11-02 02:22:15 -050018import time
19import socket
20import warnings
Jaroslav Hennerab327842012-09-11 15:44:29 +020021import select
Jay Pipes051075a2012-04-28 17:39:37 -040022
Daryl Walleck6b9b2882012-04-08 21:43:39 -050023from tempest import exceptions
Daryl Walleck1465d612011-11-02 02:22:15 -050024
Jay Pipes051075a2012-04-28 17:39:37 -040025
Daryl Walleck1465d612011-11-02 02:22:15 -050026with warnings.catch_warnings():
27 warnings.simplefilter("ignore")
28 import paramiko
29
30
31class Client(object):
32
Jay Pipes051075a2012-04-28 17:39:37 -040033 def __init__(self, host, username, password=None, timeout=300,
34 channel_timeout=10, look_for_keys=False, key_filename=None):
Daryl Walleck1465d612011-11-02 02:22:15 -050035 self.host = host
36 self.username = username
37 self.password = password
Jay Pipes051075a2012-04-28 17:39:37 -040038 self.look_for_keys = look_for_keys
39 self.key_filename = key_filename
Daryl Walleck1465d612011-11-02 02:22:15 -050040 self.timeout = int(timeout)
Jaroslav Hennerab327842012-09-11 15:44:29 +020041 self.channel_timeout = float(channel_timeout)
42 self.buf_size = 1024
Daryl Walleck1465d612011-11-02 02:22:15 -050043
44 def _get_ssh_connection(self):
45 """Returns an ssh connection to the specified host"""
46 _timeout = True
47 ssh = paramiko.SSHClient()
48 ssh.set_missing_host_key_policy(
49 paramiko.AutoAddPolicy())
50 _start_time = time.time()
51
52 while not self._is_timed_out(self.timeout, _start_time):
53 try:
54 ssh.connect(self.host, username=self.username,
Jay Pipes051075a2012-04-28 17:39:37 -040055 password=self.password,
56 look_for_keys=self.look_for_keys,
57 key_filename=self.key_filename,
58 timeout=self.timeout)
Daryl Walleck1465d612011-11-02 02:22:15 -050059 _timeout = False
60 break
61 except socket.error:
62 continue
63 except paramiko.AuthenticationException:
Jay Pipes051075a2012-04-28 17:39:37 -040064 time.sleep(5)
Daryl Walleck1465d612011-11-02 02:22:15 -050065 continue
66 if _timeout:
Daryl Walleck6b9b2882012-04-08 21:43:39 -050067 raise exceptions.SSHTimeout(host=self.host,
68 user=self.username,
69 password=self.password)
Daryl Walleck1465d612011-11-02 02:22:15 -050070 return ssh
71
72 def _is_timed_out(self, timeout, start_time):
73 return (time.time() - timeout) > start_time
74
75 def connect_until_closed(self):
76 """Connect to the server and wait until connection is lost"""
77 try:
78 ssh = self._get_ssh_connection()
79 _transport = ssh.get_transport()
80 _start_time = time.time()
81 _timed_out = self._is_timed_out(self.timeout, _start_time)
82 while _transport.is_active() and not _timed_out:
83 time.sleep(5)
84 _timed_out = self._is_timed_out(self.timeout, _start_time)
85 ssh.close()
86 except (EOFError, paramiko.AuthenticationException, socket.error):
87 return
88
89 def exec_command(self, cmd):
Jaroslav Hennerab327842012-09-11 15:44:29 +020090 """
91 Execute the specified command on the server.
Daryl Walleck1465d612011-11-02 02:22:15 -050092
Jaroslav Hennerab327842012-09-11 15:44:29 +020093 Note that this method is reading whole command outputs to memory, thus
94 shouldn't be used for large outputs.
Daryl Walleck1465d612011-11-02 02:22:15 -050095
Jaroslav Hennerab327842012-09-11 15:44:29 +020096 :returns: data read from standard output of the command.
97 :raises: SSHExecCommandFailed if command returns nonzero
98 status. The exception contains command status stderr content.
Daryl Walleck1465d612011-11-02 02:22:15 -050099 """
100 ssh = self._get_ssh_connection()
Jaroslav Hennerab327842012-09-11 15:44:29 +0200101 transport = ssh.get_transport()
102 channel = transport.open_session()
103 channel.exec_command(cmd)
104 channel.shutdown_write()
105 out_data = []
106 err_data = []
107
108 select_params = [channel], [], [], self.channel_timeout
109 while True:
110 ready = select.select(*select_params)
111 if not any(ready):
112 raise exceptions.TimeoutException(
113 "Command: '{0}' executed on host '{1}'.".format(
114 cmd, self.host))
115 if not ready[0]: # If there is nothing to read.
116 continue
117 out_chunk = err_chunk = None
118 if channel.recv_ready():
119 out_chunk = channel.recv(self.buf_size)
120 out_data += out_chunk,
121 if channel.recv_stderr_ready():
122 err_chunk = channel.recv_stderr(self.buf_size)
123 err_data += err_chunk,
124 if channel.closed and not err_chunk and not out_chunk:
125 break
126 exit_status = channel.recv_exit_status()
127 if 0 != exit_status:
128 raise exceptions.SSHExecCommandFailed(
129 command=cmd, exit_status=exit_status,
130 strerror=''.join(err_data))
131 return ''.join(out_data)
Daryl Walleck1465d612011-11-02 02:22:15 -0500132
133 def test_connection_auth(self):
134 """ Returns true if ssh can connect to server"""
135 try:
136 connection = self._get_ssh_connection()
137 connection.close()
138 except paramiko.AuthenticationException:
139 return False
140
141 return True