blob: 7c551155e90f8b2ffd100a7cd5a99c8d85800595 [file] [log] [blame]
Sean Dague556add52013-07-19 14:28:44 -04001# 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
Attila Fazekasa23f5002012-10-23 19:32:45 +020013import re
Matthew Treinisha83a16e2012-12-07 13:44:02 -050014import time
15
David Kranz968f1b32015-06-18 16:58:18 -040016from oslo_log import log as logging
Matthew Treinish96e9e882014-06-09 18:37:19 -040017
Sean Dague86bd8422013-12-20 09:56:44 -050018from tempest import config
Ken'ichi Ohmichid25a1a32017-03-01 13:40:35 -080019from tempest.lib.common.utils.linux import remote_client
Andrea Frittoli (andreaf)db9672e2016-02-23 14:07:24 -050020import tempest.lib.exceptions
Daryl Walleck98e66dd2012-06-21 04:58:39 -050021
Sean Dague86bd8422013-12-20 09:56:44 -050022CONF = config.CONF
23
David Kranz968f1b32015-06-18 16:58:18 -040024LOG = logging.getLogger(__name__)
25
Daryl Walleck6b9b2882012-04-08 21:43:39 -050026
Ken'ichi Ohmichid25a1a32017-03-01 13:40:35 -080027class RemoteClient(remote_client.RemoteClient):
Andrea Frittoli (andreaf)5f5e4fc2016-04-29 16:00:17 -050028
Ken'ichi Ohmichid25a1a32017-03-01 13:40:35 -080029 # TODO(oomichi): Make this class deprecated after migrating
30 # necessary methods to tempest.lib and cleaning
31 # unnecessary methods up from this class.
Andrea Frittoli (andreaf)5f5e4fc2016-04-29 16:00:17 -050032 def __init__(self, ip_address, username, password=None, pkey=None,
33 server=None, servers_client=None):
34 """Executes commands in a VM over ssh
35
36 :param ip_address: IP address to ssh to
37 :param username: ssh username
38 :param password: ssh password (optional)
39 :param pkey: ssh public key (optional)
40 :param server: server dict, used for debugging purposes
41 :param servers_client: servers client, used for debugging purposes
42 """
Ken'ichi Ohmichid25a1a32017-03-01 13:40:35 -080043 super(RemoteClient, self).__init__(
44 ip_address, username, password=password, pkey=pkey,
45 server=server, servers_client=servers_client,
46 ssh_timeout=CONF.validation.ssh_timeout,
47 connect_timeout=CONF.validation.connect_timeout,
48 console_output_enabled=CONF.compute_feature_enabled.console_output,
49 ssh_shell_prologue=CONF.validation.ssh_shell_prologue,
50 ping_count=CONF.validation.ping_count,
Ade Lee6ded0702021-09-04 15:56:34 -040051 ping_size=CONF.validation.ping_size,
52 ssh_key_type=CONF.validation.ssh_key_type)
Daryl Walleck6b9b2882012-04-08 21:43:39 -050053
Emily Hugenbruch877811f2017-03-24 14:32:03 -040054 # Note that this method will not work on SLES11 guests, as they do
55 # not support the TYPE column on lsblk
Evgeny Antyshev4894a912016-11-21 12:17:18 +000056 def get_disks(self):
57 # Select root disk devices as shown by lsblk
58 command = 'lsblk -lb --nodeps'
Elena Ezhova91db24e2014-02-28 20:47:10 +040059 output = self.exec_command(command)
Evgeny Antyshev4894a912016-11-21 12:17:18 +000060 selected = []
61 pos = None
62 for l in output.splitlines():
63 if pos is None and l.find("TYPE") > 0:
64 pos = l.find("TYPE")
65 # Show header line too
66 selected.append(l)
67 # lsblk lists disk type in a column right-aligned with TYPE
Masayuki Igawa0a5d6062017-03-27 12:22:33 +090068 elif pos is not None and pos > 0 and l[pos:pos + 4] == "disk":
Evgeny Antyshev4894a912016-11-21 12:17:18 +000069 selected.append(l)
70
Masayuki Igawa0a5d6062017-03-27 12:22:33 +090071 if selected:
72 return "\n".join(selected)
73 else:
shangxiaobj284d3112017-08-13 23:37:34 -070074 msg = "'TYPE' column is required but the output doesn't have it: "
Masayuki Igawa0a5d6062017-03-27 12:22:33 +090075 raise tempest.lib.exceptions.TempestException(msg + output)
Daryl Walleck98e66dd2012-06-21 04:58:39 -050076
Paras Babbar4b45f9e2019-12-11 16:51:57 -050077 def list_disks(self):
Paras Babbarf1185652019-11-15 16:55:45 -050078 disks_list = self.get_disks()
79 disks_list = [line[0] for line in
80 [device_name.split()
81 for device_name in disks_list.splitlines()][1:]]
Paras Babbar4b45f9e2019-12-11 16:51:57 -050082 return disks_list
Paras Babbarf1185652019-11-15 16:55:45 -050083
Daryl Walleck98e66dd2012-06-21 04:58:39 -050084 def get_boot_time(self):
Vincent Untz3c0b5b92014-01-18 10:56:00 +010085 cmd = 'cut -f1 -d. /proc/uptime'
Elena Ezhova91db24e2014-02-28 20:47:10 +040086 boot_secs = self.exec_command(cmd)
Vincent Untz3c0b5b92014-01-18 10:56:00 +010087 boot_time = time.time() - int(boot_secs)
88 return time.localtime(boot_time)
Attila Fazekasa23f5002012-10-23 19:32:45 +020089
90 def write_to_console(self, message):
91 message = re.sub("([$\\`])", "\\\\\\\\\\1", message)
92 # usually to /dev/ttyS0
93 cmd = 'sudo sh -c "echo \\"%s\\" >/dev/console"' % message
Elena Ezhova91db24e2014-02-28 20:47:10 +040094 return self.exec_command(cmd)
Yair Fried5f670ab2013-12-09 09:26:51 +020095
Yair Friedbc46f592015-11-18 16:29:34 +020096 def get_mac_address(self, nic=""):
97 show_nic = "show {nic} ".format(nic=nic) if nic else ""
98 cmd = "ip addr %s| awk '/ether/ {print $2}'" % show_nic
99 return self.exec_command(cmd).strip().lower()
Yair Fried3097dc12014-01-26 08:46:43 +0200100
Evgeny Antyshev9b77ae52016-02-16 09:48:57 +0000101 def get_nic_name_by_mac(self, address):
102 cmd = "ip -o link | awk '/%s/ {print $2}'" % address
103 nic = self.exec_command(cmd)
James Pagea9366272017-06-09 12:05:09 +0100104 return nic.strip().strip(":").split('@')[0].lower()
Evgeny Antyshev9b77ae52016-02-16 09:48:57 +0000105
106 def get_nic_name_by_ip(self, address):
Evgeny Antyshevf58ab6d2015-04-15 08:23:05 +0000107 cmd = "ip -o addr | awk '/%s/ {print $2}'" % address
Daniel Mellado9e3e1062015-08-06 18:07:05 +0200108 nic = self.exec_command(cmd)
Matt Riedemannacd6ecd2019-01-25 10:52:08 -0500109 LOG.debug('(get_nic_name_by_ip) Command result: %s', nic)
James Pagea9366272017-06-09 12:05:09 +0100110 return nic.strip().strip(":").split('@')[0].lower()
Yair Fried413bf2d2014-11-19 17:07:11 +0200111
Slawek Kaplonski126fe652021-07-23 13:18:05 +0200112 def _get_dns_servers(self):
Yair Fried413bf2d2014-11-19 17:07:11 +0200113 cmd = 'cat /etc/resolv.conf'
114 resolve_file = self.exec_command(cmd).strip().split('\n')
115 entries = (l.split() for l in resolve_file)
116 dns_servers = [l[1] for l in entries
117 if len(l) and l[0] == 'nameserver']
118 return dns_servers
119
Slawek Kaplonski126fe652021-07-23 13:18:05 +0200120 def get_dns_servers(self, timeout=5):
121 start_time = int(time.time())
122 dns_servers = []
123 while True:
124 dns_servers = self._get_dns_servers()
125 if dns_servers:
126 break
127 LOG.debug("DNS Servers list empty.")
128 if int(time.time()) - start_time >= timeout:
129 LOG.debug("DNS Servers list empty after %s.", timeout)
130 break
131 return dns_servers
132
Yair Fried413bf2d2014-11-19 17:07:11 +0200133 def _renew_lease_udhcpc(self, fixed_ip=None):
134 """Renews DHCP lease via udhcpc client. """
135 file_path = '/var/run/udhcpc.'
Evgeny Antyshev9b77ae52016-02-16 09:48:57 +0000136 nic_name = self.get_nic_name_by_ip(fixed_ip)
Yair Fried413bf2d2014-11-19 17:07:11 +0200137 pid = self.exec_command('cat {path}{nic}.pid'.
138 format(path=file_path, nic=nic_name))
139 pid = pid.strip()
Ken'ichi Ohmichi4e5a69e2017-03-01 18:15:29 -0800140 cmd = 'sudo /bin/kill -{sig} {pid}'.format(pid=pid, sig='USR1')
141 self.exec_command(cmd)
Yair Fried413bf2d2014-11-19 17:07:11 +0200142
143 def _renew_lease_dhclient(self, fixed_ip=None):
144 """Renews DHCP lease via dhclient client. """
Itzik Brownffb14022015-03-23 17:03:55 +0200145 cmd = "sudo /sbin/dhclient -r && sudo /sbin/dhclient"
Yair Fried413bf2d2014-11-19 17:07:11 +0200146 self.exec_command(cmd)
147
Slawek Kaplonski49163f92023-01-20 12:20:35 +0100148 def _renew_lease_dhcpcd(self, fixed_ip=None):
149 """Renews DHCP lease via dhcpcd client. """
150 cmd = "sudo /sbin/dhcpcd --rebind"
151 self.exec_command(cmd)
152
Ken'ichi Ohmichi4e337852017-03-01 12:04:23 -0800153 def renew_lease(self, fixed_ip=None, dhcp_client='udhcpc'):
Yair Fried413bf2d2014-11-19 17:07:11 +0200154 """Wrapper method for renewing DHCP lease via given client
155
156 Supporting:
157 * udhcpc
158 * dhclient
Slawek Kaplonski49163f92023-01-20 12:20:35 +0100159 * dhcpcd
Yair Fried413bf2d2014-11-19 17:07:11 +0200160 """
Slawek Kaplonski49163f92023-01-20 12:20:35 +0100161 supported_clients = ['udhcpc', 'dhclient', 'dhcpcd']
Takashi NATSUME6d5a2b42015-09-08 11:27:49 +0900162 if dhcp_client not in supported_clients:
Matthew Treinish4217a702016-10-07 17:27:11 -0400163 raise tempest.lib.exceptions.InvalidConfiguration(
164 '%s DHCP client unsupported' % dhcp_client)
Yair Fried413bf2d2014-11-19 17:07:11 +0200165 if dhcp_client == 'udhcpc' and not fixed_ip:
166 raise ValueError("need to set 'fixed_ip' for udhcpc client")
Joe Gordon28788b42015-02-25 12:42:37 -0800167 return getattr(self, '_renew_lease_' + dhcp_client)(fixed_ip=fixed_ip)
Alexander Gubanovabd154c2015-09-23 23:24:06 +0300168
169 def mount(self, dev_name, mount_path='/mnt'):
170 cmd_mount = 'sudo mount /dev/%s %s' % (dev_name, mount_path)
171 self.exec_command(cmd_mount)
172
173 def umount(self, mount_path='/mnt'):
174 self.exec_command('sudo umount %s' % mount_path)
175
176 def make_fs(self, dev_name, fs='ext4'):
Lucian Petrut6a7472a2019-08-07 12:43:08 +0300177 cmd_mkfs = 'sudo mke2fs -t %s /dev/%s' % (fs, dev_name)
Sean Dague57c66552016-02-08 08:51:13 -0500178 try:
179 self.exec_command(cmd_mkfs)
Andrea Frittoli (andreaf)db9672e2016-02-23 14:07:24 -0500180 except tempest.lib.exceptions.SSHExecCommandFailed:
Sean Dague57c66552016-02-08 08:51:13 -0500181 LOG.error("Couldn't mke2fs")
182 cmd_why = 'sudo ls -lR /dev'
Jordan Pittier525ec712016-12-07 17:51:26 +0100183 LOG.info("Contents of /dev: %s", self.exec_command(cmd_why))
Sean Dague57c66552016-02-08 08:51:13 -0500184 raise
Claudiu Belu33c3e602014-08-28 16:38:01 +0300185
186 def nc_listen_host(self, port=80, protocol='tcp'):
187 """Creates persistent nc server listening on the given TCP / UDP port
188
189 :port: the port to start listening on.
190 :protocol: the protocol used by the server. TCP by default.
191 """
192 udp = '-u' if protocol.lower() == 'udp' else ''
193 cmd = "sudo nc %(udp)s -p %(port)s -lk -e echo foolish &" % {
194 'udp': udp, 'port': port}
195 return self.exec_command(cmd)
196
197 def nc_host(self, host, port=80, protocol='tcp', expected_response=None):
198 """Check connectivity to TCP / UDP port at host via nc
199
200 :host: an IP against which the connectivity will be tested.
201 :port: the port to check connectivity against.
202 :protocol: the protocol used by nc to send packets. TCP by default.
203 :expected_response: string representing the expected response
204 from server.
205 :raises SSHExecCommandFailed: if an expected response is given and it
206 does not match the actual server response.
207 """
208 udp = '-u' if protocol.lower() == 'udp' else ''
209 cmd = 'echo "bar" | nc -w 1 %(udp)s %(host)s %(port)s' % {
210 'udp': udp, 'host': host, 'port': port}
211 response = self.exec_command(cmd)
212
213 # sending an UDP packet will always succeed. we need to check
214 # the response.
215 if (expected_response is not None and
216 expected_response != response.strip()):
217 raise tempest.lib.exceptions.SSHExecCommandFailed(
218 command=cmd, exit_status=0, stdout=response, stderr='')
219 return response
220
221 def icmp_check(self, host, nic=None):
222 """Wrapper for icmp connectivity checks"""
223 return self.ping_host(host, nic=nic)
224
225 def udp_check(self, host, **kwargs):
226 """Wrapper for udp connectivity checks."""
227 kwargs.pop('nic', None)
228 return self.nc_host(host, protocol='udp', expected_response='foolish',
229 **kwargs)
230
231 def tcp_check(self, host, **kwargs):
232 """Wrapper for tcp connectivity checks."""
233 kwargs.pop('nic', None)
234 return self.nc_host(host, **kwargs)