blob: cd4092bf8af19bf02a99daaebc052639db6c137c [file] [log] [blame]
Ken'ichi Ohmichid25a1a32017-03-01 13:40:35 -08001# 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
13import sys
14
15import netaddr
16from oslo_log import log as logging
17import six
18
19from tempest.lib.common import ssh
20from tempest.lib.common.utils import test_utils
21import tempest.lib.exceptions
22
23LOG = logging.getLogger(__name__)
24
25
26def debug_ssh(function):
27 """Decorator to generate extra debug info in case off SSH failure"""
28 def wrapper(self, *args, **kwargs):
29 try:
30 return function(self, *args, **kwargs)
zhuflf52c7592017-05-25 13:55:24 +080031 except Exception as e:
32 caller = test_utils.find_test_caller() or "not found"
33 if not isinstance(e, tempest.lib.exceptions.SSHTimeout):
34 message = ('Initializing SSH connection to %(ip)s failed. '
35 'Error: %(error)s' % {'ip': self.ip_address,
36 'error': e})
37 message = '(%s) %s' % (caller, message)
38 LOG.error(message)
39 raise
40 else:
41 try:
42 original_exception = sys.exc_info()
43 if self.server:
44 msg = 'Caller: %s. Timeout trying to ssh to server %s'
45 LOG.debug(msg, caller, self.server)
46 if self.console_output_enabled and self.servers_client:
47 try:
48 msg = 'Console log for server %s: %s'
49 console_log = (
50 self.servers_client.get_console_output(
51 self.server['id'])['output'])
52 LOG.debug(msg, self.server['id'], console_log)
53 except Exception:
54 msg = 'Could not get console_log for server %s'
55 LOG.debug(msg, self.server['id'])
56 # re-raise the original ssh timeout exception
57 six.reraise(*original_exception)
58 finally:
59 # Delete the traceback to avoid circular references
60 _, _, trace = original_exception
61 del trace
Ken'ichi Ohmichid25a1a32017-03-01 13:40:35 -080062 return wrapper
63
64
65class RemoteClient(object):
66
67 def __init__(self, ip_address, username, password=None, pkey=None,
68 server=None, servers_client=None, ssh_timeout=300,
69 connect_timeout=60, console_output_enabled=True,
Kris Stercxkd8152de2017-07-30 09:44:12 +020070 ssh_shell_prologue="set -eu -o pipefail; PATH=$PATH:/sbin;",
Ken'ichi Ohmichid25a1a32017-03-01 13:40:35 -080071 ping_count=1, ping_size=56):
72 """Executes commands in a VM over ssh
73
74 :param ip_address: IP address to ssh to
75 :param username: Ssh username
76 :param password: Ssh password
77 :param pkey: Ssh public key
78 :param server: Server dict, used for debugging purposes
79 :param servers_client: Servers client, used for debugging purposes
80 :param ssh_timeout: Timeout in seconds to wait for the ssh banner
81 :param connect_timeout: Timeout in seconds to wait for TCP connection
82 :param console_output_enabled: Support serial console output?
83 :param ssh_shell_prologue: Shell fragments to use before command
84 :param ping_count: Number of ping packets
85 :param ping_size: Packet size for ping packets
86 """
87 self.server = server
88 self.servers_client = servers_client
zhuflf52c7592017-05-25 13:55:24 +080089 self.ip_address = ip_address
Ken'ichi Ohmichid25a1a32017-03-01 13:40:35 -080090 self.console_output_enabled = console_output_enabled
91 self.ssh_shell_prologue = ssh_shell_prologue
92 self.ping_count = ping_count
93 self.ping_size = ping_size
94
95 self.ssh_client = ssh.Client(ip_address, username, password,
96 ssh_timeout, pkey=pkey,
97 channel_timeout=connect_timeout)
98
99 @debug_ssh
100 def exec_command(self, cmd):
101 # Shell options below add more clearness on failures,
102 # path is extended for some non-cirros guest oses (centos7)
103 cmd = self.ssh_shell_prologue + " " + cmd
104 LOG.debug("Remote command: %s", cmd)
105 return self.ssh_client.exec_command(cmd)
106
107 @debug_ssh
108 def validate_authentication(self):
109 """Validate ssh connection and authentication
110
111 This method raises an Exception when the validation fails.
112 """
113 self.ssh_client.test_connection_auth()
114
115 def ping_host(self, host, count=None, size=None, nic=None):
116 if count is None:
117 count = self.ping_count
118 if size is None:
119 size = self.ping_size
120
121 addr = netaddr.IPAddress(host)
122 cmd = 'ping6' if addr.version == 6 else 'ping'
123 if nic:
124 cmd = 'sudo {cmd} -I {nic}'.format(cmd=cmd, nic=nic)
125 cmd += ' -c{0} -w{0} -s{1} {2}'.format(count, size, host)
126 return self.exec_command(cmd)