blob: b68a879a01a6df8947bae6e22531c699b4f60e86 [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,
51 ping_size=CONF.validation.ping_size)
Daryl Walleck6b9b2882012-04-08 21:43:39 -050052
Emily Hugenbruch877811f2017-03-24 14:32:03 -040053 # Note that this method will not work on SLES11 guests, as they do
54 # not support the TYPE column on lsblk
Evgeny Antyshev4894a912016-11-21 12:17:18 +000055 def get_disks(self):
56 # Select root disk devices as shown by lsblk
57 command = 'lsblk -lb --nodeps'
Elena Ezhova91db24e2014-02-28 20:47:10 +040058 output = self.exec_command(command)
Evgeny Antyshev4894a912016-11-21 12:17:18 +000059 selected = []
60 pos = None
61 for l in output.splitlines():
62 if pos is None and l.find("TYPE") > 0:
63 pos = l.find("TYPE")
64 # Show header line too
65 selected.append(l)
66 # lsblk lists disk type in a column right-aligned with TYPE
Masayuki Igawa0a5d6062017-03-27 12:22:33 +090067 elif pos is not None and pos > 0 and l[pos:pos + 4] == "disk":
Evgeny Antyshev4894a912016-11-21 12:17:18 +000068 selected.append(l)
69
Masayuki Igawa0a5d6062017-03-27 12:22:33 +090070 if selected:
71 return "\n".join(selected)
72 else:
shangxiaobj284d3112017-08-13 23:37:34 -070073 msg = "'TYPE' column is required but the output doesn't have it: "
Masayuki Igawa0a5d6062017-03-27 12:22:33 +090074 raise tempest.lib.exceptions.TempestException(msg + output)
Daryl Walleck98e66dd2012-06-21 04:58:39 -050075
Paras Babbar4b45f9e2019-12-11 16:51:57 -050076 def list_disks(self):
Paras Babbarf1185652019-11-15 16:55:45 -050077 disks_list = self.get_disks()
78 disks_list = [line[0] for line in
79 [device_name.split()
80 for device_name in disks_list.splitlines()][1:]]
Paras Babbar4b45f9e2019-12-11 16:51:57 -050081 return disks_list
Paras Babbarf1185652019-11-15 16:55:45 -050082
Daryl Walleck98e66dd2012-06-21 04:58:39 -050083 def get_boot_time(self):
Vincent Untz3c0b5b92014-01-18 10:56:00 +010084 cmd = 'cut -f1 -d. /proc/uptime'
Elena Ezhova91db24e2014-02-28 20:47:10 +040085 boot_secs = self.exec_command(cmd)
Vincent Untz3c0b5b92014-01-18 10:56:00 +010086 boot_time = time.time() - int(boot_secs)
87 return time.localtime(boot_time)
Attila Fazekasa23f5002012-10-23 19:32:45 +020088
89 def write_to_console(self, message):
90 message = re.sub("([$\\`])", "\\\\\\\\\\1", message)
91 # usually to /dev/ttyS0
92 cmd = 'sudo sh -c "echo \\"%s\\" >/dev/console"' % message
Elena Ezhova91db24e2014-02-28 20:47:10 +040093 return self.exec_command(cmd)
Yair Fried5f670ab2013-12-09 09:26:51 +020094
Yair Friedbc46f592015-11-18 16:29:34 +020095 def get_mac_address(self, nic=""):
96 show_nic = "show {nic} ".format(nic=nic) if nic else ""
97 cmd = "ip addr %s| awk '/ether/ {print $2}'" % show_nic
98 return self.exec_command(cmd).strip().lower()
Yair Fried3097dc12014-01-26 08:46:43 +020099
Evgeny Antyshev9b77ae52016-02-16 09:48:57 +0000100 def get_nic_name_by_mac(self, address):
101 cmd = "ip -o link | awk '/%s/ {print $2}'" % address
102 nic = self.exec_command(cmd)
James Pagea9366272017-06-09 12:05:09 +0100103 return nic.strip().strip(":").split('@')[0].lower()
Evgeny Antyshev9b77ae52016-02-16 09:48:57 +0000104
105 def get_nic_name_by_ip(self, address):
Evgeny Antyshevf58ab6d2015-04-15 08:23:05 +0000106 cmd = "ip -o addr | awk '/%s/ {print $2}'" % address
Daniel Mellado9e3e1062015-08-06 18:07:05 +0200107 nic = self.exec_command(cmd)
Matt Riedemannacd6ecd2019-01-25 10:52:08 -0500108 LOG.debug('(get_nic_name_by_ip) Command result: %s', nic)
James Pagea9366272017-06-09 12:05:09 +0100109 return nic.strip().strip(":").split('@')[0].lower()
Yair Fried413bf2d2014-11-19 17:07:11 +0200110
Yair Fried413bf2d2014-11-19 17:07:11 +0200111 def get_dns_servers(self):
112 cmd = 'cat /etc/resolv.conf'
113 resolve_file = self.exec_command(cmd).strip().split('\n')
114 entries = (l.split() for l in resolve_file)
115 dns_servers = [l[1] for l in entries
116 if len(l) and l[0] == 'nameserver']
117 return dns_servers
118
Yair Fried413bf2d2014-11-19 17:07:11 +0200119 def _renew_lease_udhcpc(self, fixed_ip=None):
120 """Renews DHCP lease via udhcpc client. """
121 file_path = '/var/run/udhcpc.'
Evgeny Antyshev9b77ae52016-02-16 09:48:57 +0000122 nic_name = self.get_nic_name_by_ip(fixed_ip)
Yair Fried413bf2d2014-11-19 17:07:11 +0200123 pid = self.exec_command('cat {path}{nic}.pid'.
124 format(path=file_path, nic=nic_name))
125 pid = pid.strip()
Ken'ichi Ohmichi4e5a69e2017-03-01 18:15:29 -0800126 cmd = 'sudo /bin/kill -{sig} {pid}'.format(pid=pid, sig='USR1')
127 self.exec_command(cmd)
Yair Fried413bf2d2014-11-19 17:07:11 +0200128
129 def _renew_lease_dhclient(self, fixed_ip=None):
130 """Renews DHCP lease via dhclient client. """
Itzik Brownffb14022015-03-23 17:03:55 +0200131 cmd = "sudo /sbin/dhclient -r && sudo /sbin/dhclient"
Yair Fried413bf2d2014-11-19 17:07:11 +0200132 self.exec_command(cmd)
133
Ken'ichi Ohmichi4e337852017-03-01 12:04:23 -0800134 def renew_lease(self, fixed_ip=None, dhcp_client='udhcpc'):
Yair Fried413bf2d2014-11-19 17:07:11 +0200135 """Wrapper method for renewing DHCP lease via given client
136
137 Supporting:
138 * udhcpc
139 * dhclient
140 """
141 # TODO(yfried): add support for dhcpcd
Takashi NATSUME6d5a2b42015-09-08 11:27:49 +0900142 supported_clients = ['udhcpc', 'dhclient']
Takashi NATSUME6d5a2b42015-09-08 11:27:49 +0900143 if dhcp_client not in supported_clients:
Matthew Treinish4217a702016-10-07 17:27:11 -0400144 raise tempest.lib.exceptions.InvalidConfiguration(
145 '%s DHCP client unsupported' % dhcp_client)
Yair Fried413bf2d2014-11-19 17:07:11 +0200146 if dhcp_client == 'udhcpc' and not fixed_ip:
147 raise ValueError("need to set 'fixed_ip' for udhcpc client")
Joe Gordon28788b42015-02-25 12:42:37 -0800148 return getattr(self, '_renew_lease_' + dhcp_client)(fixed_ip=fixed_ip)
Alexander Gubanovabd154c2015-09-23 23:24:06 +0300149
150 def mount(self, dev_name, mount_path='/mnt'):
151 cmd_mount = 'sudo mount /dev/%s %s' % (dev_name, mount_path)
152 self.exec_command(cmd_mount)
153
154 def umount(self, mount_path='/mnt'):
155 self.exec_command('sudo umount %s' % mount_path)
156
157 def make_fs(self, dev_name, fs='ext4'):
Lucian Petrut6a7472a2019-08-07 12:43:08 +0300158 cmd_mkfs = 'sudo mke2fs -t %s /dev/%s' % (fs, dev_name)
Sean Dague57c66552016-02-08 08:51:13 -0500159 try:
160 self.exec_command(cmd_mkfs)
Andrea Frittoli (andreaf)db9672e2016-02-23 14:07:24 -0500161 except tempest.lib.exceptions.SSHExecCommandFailed:
Sean Dague57c66552016-02-08 08:51:13 -0500162 LOG.error("Couldn't mke2fs")
163 cmd_why = 'sudo ls -lR /dev'
Jordan Pittier525ec712016-12-07 17:51:26 +0100164 LOG.info("Contents of /dev: %s", self.exec_command(cmd_why))
Sean Dague57c66552016-02-08 08:51:13 -0500165 raise
Claudiu Belu33c3e602014-08-28 16:38:01 +0300166
167 def nc_listen_host(self, port=80, protocol='tcp'):
168 """Creates persistent nc server listening on the given TCP / UDP port
169
170 :port: the port to start listening on.
171 :protocol: the protocol used by the server. TCP by default.
172 """
173 udp = '-u' if protocol.lower() == 'udp' else ''
174 cmd = "sudo nc %(udp)s -p %(port)s -lk -e echo foolish &" % {
175 'udp': udp, 'port': port}
176 return self.exec_command(cmd)
177
178 def nc_host(self, host, port=80, protocol='tcp', expected_response=None):
179 """Check connectivity to TCP / UDP port at host via nc
180
181 :host: an IP against which the connectivity will be tested.
182 :port: the port to check connectivity against.
183 :protocol: the protocol used by nc to send packets. TCP by default.
184 :expected_response: string representing the expected response
185 from server.
186 :raises SSHExecCommandFailed: if an expected response is given and it
187 does not match the actual server response.
188 """
189 udp = '-u' if protocol.lower() == 'udp' else ''
190 cmd = 'echo "bar" | nc -w 1 %(udp)s %(host)s %(port)s' % {
191 'udp': udp, 'host': host, 'port': port}
192 response = self.exec_command(cmd)
193
194 # sending an UDP packet will always succeed. we need to check
195 # the response.
196 if (expected_response is not None and
197 expected_response != response.strip()):
198 raise tempest.lib.exceptions.SSHExecCommandFailed(
199 command=cmd, exit_status=0, stdout=response, stderr='')
200 return response
201
202 def icmp_check(self, host, nic=None):
203 """Wrapper for icmp connectivity checks"""
204 return self.ping_host(host, nic=nic)
205
206 def udp_check(self, host, **kwargs):
207 """Wrapper for udp connectivity checks."""
208 kwargs.pop('nic', None)
209 return self.nc_host(host, protocol='udp', expected_response='foolish',
210 **kwargs)
211
212 def tcp_check(self, host, **kwargs):
213 """Wrapper for tcp connectivity checks."""
214 kwargs.pop('nic', None)
215 return self.nc_host(host, **kwargs)