blob: f23622ba5bbb2b645504a10e2f8991fe8356e080 [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
rabid2916d02017-09-22 18:19:24 +053022from heat_tempest_plugin.common import exceptions
Steve Baker450aa7f2014-08-25 10:37:27 +120023
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):
Peter Razumovskyf0ac9582015-09-24 16:49:03 +030091 """Execute the specified command on the server.
Steve Baker450aa7f2014-08-25 10:37:27 +120092
93 Note that this method is reading whole command outputs to memory, thus
94 shouldn't be used for large outputs.
95
96 :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.
99 """
100 ssh = self._get_ssh_connection()
101 transport = ssh.get_transport()
102 channel = transport.open_session()
103 channel.fileno() # Register event pipe
104 channel.exec_command(cmd)
105 channel.shutdown_write()
106 out_data = []
107 err_data = []
108 poll = select.poll()
109 poll.register(channel, select.POLLIN)
110 start_time = time.time()
111
112 while True:
113 ready = poll.poll(self.channel_timeout)
114 if not any(ready):
115 if not self._is_timed_out(start_time):
116 continue
117 raise exceptions.TimeoutException(
118 "Command: '{0}' executed on host '{1}'.".format(
119 cmd, self.host))
120 if not ready[0]: # If there is nothing to read.
121 continue
122 out_chunk = err_chunk = None
123 if channel.recv_ready():
124 out_chunk = channel.recv(self.buf_size)
125 out_data += out_chunk,
126 if channel.recv_stderr_ready():
127 err_chunk = channel.recv_stderr(self.buf_size)
128 err_data += err_chunk,
129 if channel.closed and not err_chunk and not out_chunk:
130 break
131 exit_status = channel.recv_exit_status()
132 if 0 != exit_status:
133 raise exceptions.SSHExecCommandFailed(
134 command=cmd, exit_status=exit_status,
135 strerror=''.join(err_data))
136 return ''.join(out_data)
137
138 def test_connection_auth(self):
139 """Raises an exception when we can not connect to server via ssh."""
140 connection = self._get_ssh_connection()
141 connection.close()
142
143
tengqm499a9d72015-03-24 11:12:19 +0800144class RemoteClient(object):
Steve Baker450aa7f2014-08-25 10:37:27 +1200145
146 # NOTE(afazekas): It should always get an address instead of server
147 def __init__(self, server, username, password=None, pkey=None,
148 conf=None):
149 self.conf = conf
150 ssh_timeout = self.conf.ssh_timeout
151 network = self.conf.network_for_ssh
152 ip_version = self.conf.ip_version_for_ssh
153 ssh_channel_timeout = self.conf.ssh_channel_timeout
154 if isinstance(server, six.string_types):
155 ip_address = server
156 else:
157 addresses = server['addresses'][network]
158 for address in addresses:
159 if address['version'] == ip_version:
160 ip_address = address['addr']
161 break
162 else:
163 raise exceptions.ServerUnreachable()
164 self.ssh_client = Client(ip_address, username, password,
165 ssh_timeout, pkey=pkey,
166 channel_timeout=ssh_channel_timeout)
167
168 def exec_command(self, cmd):
169 return self.ssh_client.exec_command(cmd)
170
171 def validate_authentication(self):
Peter Razumovskyf0ac9582015-09-24 16:49:03 +0300172 """Validate ssh connection and authentication.
173
174 This method raises an Exception when the validation fails.
Steve Baker450aa7f2014-08-25 10:37:27 +1200175 """
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)