blob: 224f3bf2d953aee66a059e7462efc7e6c7867dc0 [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
lkuchland13acd12017-12-27 15:24:47 +020013import functools
Ken'ichi Ohmichid25a1a32017-03-01 13:40:35 -080014import sys
15
16import netaddr
17from oslo_log import log as logging
Ken'ichi Ohmichid25a1a32017-03-01 13:40:35 -080018
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"""
lkuchland13acd12017-12-27 15:24:47 +020028 @functools.wraps(function)
Ken'ichi Ohmichid25a1a32017-03-01 13:40:35 -080029 def wrapper(self, *args, **kwargs):
30 try:
31 return function(self, *args, **kwargs)
zhuflf52c7592017-05-25 13:55:24 +080032 except Exception as e:
33 caller = test_utils.find_test_caller() or "not found"
34 if not isinstance(e, tempest.lib.exceptions.SSHTimeout):
zhufl35422ac2017-11-13 16:54:55 +080035 message = ('Executing command on %(ip)s failed. '
zhuflf52c7592017-05-25 13:55:24 +080036 'Error: %(error)s' % {'ip': self.ip_address,
37 'error': e})
38 message = '(%s) %s' % (caller, message)
39 LOG.error(message)
40 raise
41 else:
42 try:
43 original_exception = sys.exc_info()
44 if self.server:
45 msg = 'Caller: %s. Timeout trying to ssh to server %s'
46 LOG.debug(msg, caller, self.server)
47 if self.console_output_enabled and self.servers_client:
48 try:
49 msg = 'Console log for server %s: %s'
50 console_log = (
51 self.servers_client.get_console_output(
52 self.server['id'])['output'])
53 LOG.debug(msg, self.server['id'], console_log)
54 except Exception:
55 msg = 'Could not get console_log for server %s'
56 LOG.debug(msg, self.server['id'])
songwenpinge6623072021-02-22 14:47:34 +080057 # raise the original ssh timeout exception
58 raise
zhuflf52c7592017-05-25 13:55:24 +080059 finally:
60 # Delete the traceback to avoid circular references
61 _, _, trace = original_exception
62 del trace
Ken'ichi Ohmichid25a1a32017-03-01 13:40:35 -080063 return wrapper
64
65
66class RemoteClient(object):
67
68 def __init__(self, ip_address, username, password=None, pkey=None,
69 server=None, servers_client=None, ssh_timeout=300,
70 connect_timeout=60, console_output_enabled=True,
Kris Stercxkd8152de2017-07-30 09:44:12 +020071 ssh_shell_prologue="set -eu -o pipefail; PATH=$PATH:/sbin;",
Ade Lee6ded0702021-09-04 15:56:34 -040072 ping_count=1, ping_size=56, ssh_key_type='rsa'):
Ken'ichi Ohmichid25a1a32017-03-01 13:40:35 -080073 """Executes commands in a VM over ssh
74
75 :param ip_address: IP address to ssh to
76 :param username: Ssh username
77 :param password: Ssh password
78 :param pkey: Ssh public key
79 :param server: Server dict, used for debugging purposes
80 :param servers_client: Servers client, used for debugging purposes
81 :param ssh_timeout: Timeout in seconds to wait for the ssh banner
82 :param connect_timeout: Timeout in seconds to wait for TCP connection
83 :param console_output_enabled: Support serial console output?
84 :param ssh_shell_prologue: Shell fragments to use before command
85 :param ping_count: Number of ping packets
86 :param ping_size: Packet size for ping packets
Ade Lee6ded0702021-09-04 15:56:34 -040087 :param ssh_key_type: ssh key type (rsa, ecdsa)
Ken'ichi Ohmichid25a1a32017-03-01 13:40:35 -080088 """
89 self.server = server
90 self.servers_client = servers_client
zhuflf52c7592017-05-25 13:55:24 +080091 self.ip_address = ip_address
Ken'ichi Ohmichid25a1a32017-03-01 13:40:35 -080092 self.console_output_enabled = console_output_enabled
93 self.ssh_shell_prologue = ssh_shell_prologue
94 self.ping_count = ping_count
95 self.ping_size = ping_size
Ade Lee6ded0702021-09-04 15:56:34 -040096 self.ssh_key_type = ssh_key_type
Ken'ichi Ohmichid25a1a32017-03-01 13:40:35 -080097
98 self.ssh_client = ssh.Client(ip_address, username, password,
99 ssh_timeout, pkey=pkey,
Ade Lee6ded0702021-09-04 15:56:34 -0400100 channel_timeout=connect_timeout,
101 ssh_key_type=ssh_key_type)
Ken'ichi Ohmichid25a1a32017-03-01 13:40:35 -0800102
103 @debug_ssh
104 def exec_command(self, cmd):
105 # Shell options below add more clearness on failures,
106 # path is extended for some non-cirros guest oses (centos7)
107 cmd = self.ssh_shell_prologue + " " + cmd
108 LOG.debug("Remote command: %s", cmd)
109 return self.ssh_client.exec_command(cmd)
110
111 @debug_ssh
112 def validate_authentication(self):
113 """Validate ssh connection and authentication
114
115 This method raises an Exception when the validation fails.
116 """
117 self.ssh_client.test_connection_auth()
118
119 def ping_host(self, host, count=None, size=None, nic=None):
120 if count is None:
121 count = self.ping_count
122 if size is None:
123 size = self.ping_size
124
125 addr = netaddr.IPAddress(host)
126 cmd = 'ping6' if addr.version == 6 else 'ping'
127 if nic:
128 cmd = 'sudo {cmd} -I {nic}'.format(cmd=cmd, nic=nic)
129 cmd += ' -c{0} -w{0} -s{1} {2}'.format(count, size, host)
130 return self.exec_command(cmd)
Mohammed Naserb6c6d2a2017-12-29 18:13:29 -0500131
132 def mount_config_drive(self):
133 """Mount the config drive inside a virtual machine
134
135 This method will not unmount the config drive, so unmount_config_drive
136 must be used for cleanup.
137 """
Lee Yarwood99b7c112020-03-02 20:04:49 +0000138 cmd_blkid = 'blkid -L config-2 -o device'
139 dev_name = self.exec_command(cmd_blkid).strip()
Mohammed Naserb6c6d2a2017-12-29 18:13:29 -0500140
141 try:
142 self.exec_command('sudo mount %s /mnt' % dev_name)
143 except tempest.lib.exceptions.SSHExecCommandFailed:
144 # So the command failed, let's try to know why and print some
145 # useful information.
146 lsblk = self.exec_command('sudo lsblk --fs --ascii')
147 LOG.error("Mounting %s on /mnt failed. Right after the "
148 "failure 'lsblk' in the guest reported:\n%s",
149 dev_name, lsblk)
150 raise
151
152 def unmount_config_drive(self):
153 self.exec_command('sudo umount /mnt')