blob: c8c4f95230b78a07eda8bbd4339319807bb6eb82 [file] [log] [blame]
Steve Baker450aa7f2014-08-25 10:37:27 +12001# Licensed under the Apache License, Version 2.0 (the "License"); you may
2# not use this file except in compliance with the License. You may obtain
3# a copy of the License at
4#
5# http://www.apache.org/licenses/LICENSE-2.0
6#
7# Unless required by applicable law or agreed to in writing, software
8# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10# License for the specific language governing permissions and limitations
11# under the License.
12
Steve Baker450aa7f2014-08-25 10:37:27 +120013import re
14import select
Steve Baker450aa7f2014-08-25 10:37:27 +120015import socket
16import time
17
Steve Baker24641292015-03-13 10:47:50 +130018from oslo_log import log as logging
Pavlo Shchelokovskyy60e0ecd2014-12-14 22:17:21 +020019import paramiko
20import six
21
Steve Baker450aa7f2014-08-25 10:37:27 +120022from heat_integrationtests.common import exceptions
23
24LOG = logging.getLogger(__name__)
25
26
27class Client(object):
28
29 def __init__(self, host, username, password=None, timeout=300, pkey=None,
30 channel_timeout=10, look_for_keys=False, key_filename=None):
31 self.host = host
32 self.username = username
33 self.password = password
34 if isinstance(pkey, six.string_types):
35 pkey = paramiko.RSAKey.from_private_key(
Sirushti Murugesan4920fda2015-04-22 00:35:26 +053036 six.moves.cStringIO(str(pkey)))
Steve Baker450aa7f2014-08-25 10:37:27 +120037 self.pkey = pkey
38 self.look_for_keys = look_for_keys
39 self.key_filename = key_filename
40 self.timeout = int(timeout)
41 self.channel_timeout = float(channel_timeout)
42 self.buf_size = 1024
43
44 def _get_ssh_connection(self, sleep=1.5, backoff=1):
45 """Returns an ssh connection to the specified host."""
46 bsleep = sleep
47 ssh = paramiko.SSHClient()
48 ssh.set_missing_host_key_policy(
49 paramiko.AutoAddPolicy())
50 _start_time = time.time()
51 if self.pkey is not None:
52 LOG.info("Creating ssh connection to '%s' as '%s'"
53 " with public key authentication",
54 self.host, self.username)
55 else:
56 LOG.info("Creating ssh connection to '%s' as '%s'"
57 " with password %s",
58 self.host, self.username, str(self.password))
59 attempts = 0
60 while True:
61 try:
62 ssh.connect(self.host, username=self.username,
63 password=self.password,
64 look_for_keys=self.look_for_keys,
65 key_filename=self.key_filename,
66 timeout=self.channel_timeout, pkey=self.pkey)
67 LOG.info("ssh connection to %s@%s successfuly created",
68 self.username, self.host)
69 return ssh
70 except (socket.error,
71 paramiko.SSHException) as e:
72 if self._is_timed_out(_start_time):
73 LOG.exception("Failed to establish authenticated ssh"
74 " connection to %s@%s after %d attempts",
75 self.username, self.host, attempts)
76 raise exceptions.SSHTimeout(host=self.host,
77 user=self.username,
78 password=self.password)
79 bsleep += backoff
80 attempts += 1
81 LOG.warning("Failed to establish authenticated ssh"
82 " connection to %s@%s (%s). Number attempts: %s."
83 " Retry after %d seconds.",
84 self.username, self.host, e, attempts, bsleep)
85 time.sleep(bsleep)
86
87 def _is_timed_out(self, start_time):
88 return (time.time() - self.timeout) > start_time
89
90 def exec_command(self, cmd):
91 """
92 Execute the specified command on the server.
93
94 Note that this method is reading whole command outputs to memory, thus
95 shouldn't be used for large outputs.
96
97 :returns: data read from standard output of the command.
98 :raises: SSHExecCommandFailed if command returns nonzero
99 status. The exception contains command status stderr content.
100 """
101 ssh = self._get_ssh_connection()
102 transport = ssh.get_transport()
103 channel = transport.open_session()
104 channel.fileno() # Register event pipe
105 channel.exec_command(cmd)
106 channel.shutdown_write()
107 out_data = []
108 err_data = []
109 poll = select.poll()
110 poll.register(channel, select.POLLIN)
111 start_time = time.time()
112
113 while True:
114 ready = poll.poll(self.channel_timeout)
115 if not any(ready):
116 if not self._is_timed_out(start_time):
117 continue
118 raise exceptions.TimeoutException(
119 "Command: '{0}' executed on host '{1}'.".format(
120 cmd, self.host))
121 if not ready[0]: # If there is nothing to read.
122 continue
123 out_chunk = err_chunk = None
124 if channel.recv_ready():
125 out_chunk = channel.recv(self.buf_size)
126 out_data += out_chunk,
127 if channel.recv_stderr_ready():
128 err_chunk = channel.recv_stderr(self.buf_size)
129 err_data += err_chunk,
130 if channel.closed and not err_chunk and not out_chunk:
131 break
132 exit_status = channel.recv_exit_status()
133 if 0 != exit_status:
134 raise exceptions.SSHExecCommandFailed(
135 command=cmd, exit_status=exit_status,
136 strerror=''.join(err_data))
137 return ''.join(out_data)
138
139 def test_connection_auth(self):
140 """Raises an exception when we can not connect to server via ssh."""
141 connection = self._get_ssh_connection()
142 connection.close()
143
144
tengqm499a9d72015-03-24 11:12:19 +0800145class RemoteClient(object):
Steve Baker450aa7f2014-08-25 10:37:27 +1200146
147 # NOTE(afazekas): It should always get an address instead of server
148 def __init__(self, server, username, password=None, pkey=None,
149 conf=None):
150 self.conf = conf
151 ssh_timeout = self.conf.ssh_timeout
152 network = self.conf.network_for_ssh
153 ip_version = self.conf.ip_version_for_ssh
154 ssh_channel_timeout = self.conf.ssh_channel_timeout
155 if isinstance(server, six.string_types):
156 ip_address = server
157 else:
158 addresses = server['addresses'][network]
159 for address in addresses:
160 if address['version'] == ip_version:
161 ip_address = address['addr']
162 break
163 else:
164 raise exceptions.ServerUnreachable()
165 self.ssh_client = Client(ip_address, username, password,
166 ssh_timeout, pkey=pkey,
167 channel_timeout=ssh_channel_timeout)
168
169 def exec_command(self, cmd):
170 return self.ssh_client.exec_command(cmd)
171
172 def validate_authentication(self):
173 """Validate ssh connection and authentication
174 This method raises an Exception when the validation fails.
175 """
176 self.ssh_client.test_connection_auth()
177
178 def get_partitions(self):
179 # Return the contents of /proc/partitions
180 command = 'cat /proc/partitions'
181 output = self.exec_command(command)
182 return output
183
184 def get_boot_time(self):
185 cmd = 'cut -f1 -d. /proc/uptime'
186 boot_secs = self.exec_command(cmd)
187 boot_time = time.time() - int(boot_secs)
188 return time.localtime(boot_time)
189
190 def write_to_console(self, message):
191 message = re.sub("([$\\`])", "\\\\\\\\\\1", message)
192 # usually to /dev/ttyS0
193 cmd = 'sudo sh -c "echo \\"%s\\" >/dev/console"' % message
194 return self.exec_command(cmd)
195
196 def ping_host(self, host):
197 cmd = 'ping -c1 -w1 %s' % host
198 return self.exec_command(cmd)
199
200 def get_ip_list(self):
201 cmd = "/bin/ip address"
202 return self.exec_command(cmd)