blob: a1a054a32c53509b8a3460be70952507798ae42d [file] [log] [blame]
Itzik Browne67ebb52016-05-15 05:34:41 +00001# Copyright 2016 Red Hat, Inc.
2# All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain
6# a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
Slawek Kaplonskiaf83e832020-02-05 12:11:54 +010015import distutils
16import re
Chandan Kumarc125fd12017-11-15 19:41:01 +053017import subprocess
Itzik Browne67ebb52016-05-15 05:34:41 +000018
Federico Ressibf877c82018-08-22 08:36:37 +020019from debtcollector import removals
YAMAMOTO Takashi25935722017-01-23 15:34:11 +090020import netaddr
Assaf Muller92fdc782018-05-31 10:32:47 -040021from neutron_lib.api import validators
22from neutron_lib import constants as neutron_lib_constants
Alex Stafeyevc4d9c352016-12-12 04:13:33 -050023from oslo_log import log
Slawek Kaplonski8cccfe02020-09-29 22:34:09 +020024from paramiko import ssh_exception as ssh_exc
Genadi Chereshnya6d10c6e2017-07-05 11:34:20 +030025from tempest.common.utils import net_utils
Itzik Browne67ebb52016-05-15 05:34:41 +000026from tempest.common import waiters
Itzik Browne67ebb52016-05-15 05:34:41 +000027from tempest.lib.common.utils import data_utils
YAMAMOTO Takashi25935722017-01-23 15:34:11 +090028from tempest.lib.common.utils import test_utils
Jakub Libosvarc0c2f1d2017-01-31 12:12:21 -050029from tempest.lib import exceptions as lib_exc
Itzik Browne67ebb52016-05-15 05:34:41 +000030
Chandan Kumar667d3d32017-09-22 12:24:06 +053031from neutron_tempest_plugin.api import base as base_api
Rodolfo Alonso Hernandez4849f002020-01-16 16:01:10 +000032from neutron_tempest_plugin.common import ip as ip_utils
Slawek Kaplonskic4e963e2019-09-11 22:55:34 +020033from neutron_tempest_plugin.common import shell
Chandan Kumar667d3d32017-09-22 12:24:06 +053034from neutron_tempest_plugin.common import ssh
Slawek Kaplonskifd4141f2020-03-14 14:34:00 +010035from neutron_tempest_plugin.common import utils
Chandan Kumar667d3d32017-09-22 12:24:06 +053036from neutron_tempest_plugin import config
Slawek Kaplonskiaf83e832020-02-05 12:11:54 +010037from neutron_tempest_plugin import exceptions
Chandan Kumar667d3d32017-09-22 12:24:06 +053038from neutron_tempest_plugin.scenario import constants
Itzik Browne67ebb52016-05-15 05:34:41 +000039
40CONF = config.CONF
Itzik Browne67ebb52016-05-15 05:34:41 +000041
Alex Stafeyevc4d9c352016-12-12 04:13:33 -050042LOG = log.getLogger(__name__)
43
Itzik Browne67ebb52016-05-15 05:34:41 +000044
Slawek Kaplonskiaf83e832020-02-05 12:11:54 +010045def get_ncat_version(ssh_client=None):
46 cmd = "ncat --version 2>&1"
47 try:
48 version_result = shell.execute(cmd, ssh_client=ssh_client).stdout
49 except exceptions.ShellCommandFailed:
50 m = None
51 else:
52 m = re.match(r"Ncat: Version ([\d.]+) *.", version_result)
53 # NOTE(slaweq): by default lets assume we have ncat 7.60 which is in Ubuntu
54 # 18.04 which is used on u/s gates
55 return distutils.version.StrictVersion(m.group(1) if m else '7.60')
56
57
Slawek Kaplonskifd4141f2020-03-14 14:34:00 +010058def get_ncat_server_cmd(port, protocol, msg=None):
Slawek Kaplonskiaf83e832020-02-05 12:11:54 +010059 udp = ''
60 if protocol.lower() == neutron_lib_constants.PROTO_NAME_UDP:
61 udp = '-u'
62 cmd = "nc %(udp)s -p %(port)s -lk " % {
63 'udp': udp, 'port': port}
Slawek Kaplonskifd4141f2020-03-14 14:34:00 +010064 if msg:
65 if CONF.neutron_plugin_options.default_image_is_advanced:
Flavio Fernandesb056ac22020-07-01 14:57:13 -040066 cmd += "-c 'echo %s' " % msg
Slawek Kaplonskifd4141f2020-03-14 14:34:00 +010067 else:
Flavio Fernandesb056ac22020-07-01 14:57:13 -040068 cmd += "-e echo %s " % msg
69 cmd += "< /dev/zero &{0}sleep 0.1{0}".format('\n')
Slawek Kaplonskiaf83e832020-02-05 12:11:54 +010070 return cmd
71
72
73def get_ncat_client_cmd(ip_address, port, protocol):
74 udp = ''
75 if protocol.lower() == neutron_lib_constants.PROTO_NAME_UDP:
76 udp = '-u'
77 cmd = 'echo "knock knock" | nc '
78 ncat_version = get_ncat_version()
79 if ncat_version > distutils.version.StrictVersion('7.60'):
80 cmd += '-z '
81 cmd += '-w 1 %(udp)s %(host)s %(port)s' % {
82 'udp': udp, 'host': ip_address, 'port': port}
83 return cmd
84
85
Itzik Browne67ebb52016-05-15 05:34:41 +000086class BaseTempestTestCase(base_api.BaseNetworkTest):
Itzik Browne67ebb52016-05-15 05:34:41 +000087
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +000088 def create_server(self, flavor_ref, image_ref, key_name, networks,
Roee Agiman6a0a18a2017-11-16 11:51:56 +020089 **kwargs):
Itzik Brownbac51dc2016-10-31 12:25:04 +000090 """Create a server using tempest lib
Brian Haleyaee61ac2018-10-09 20:00:27 -040091
Itzik Brownbac51dc2016-10-31 12:25:04 +000092 All the parameters are the ones used in Compute API
Roee Agiman6a0a18a2017-11-16 11:51:56 +020093 * - Kwargs that require admin privileges
Itzik Brownbac51dc2016-10-31 12:25:04 +000094
95 Args:
96 flavor_ref(str): The flavor of the server to be provisioned.
97 image_ref(str): The image of the server to be provisioned.
98 key_name(str): SSH key to to be used to connect to the
99 provisioned server.
100 networks(list): List of dictionaries where each represent
101 an interface to be attached to the server. For network
102 it should be {'uuid': network_uuid} and for port it should
103 be {'port': port_uuid}
Roee Agiman6a0a18a2017-11-16 11:51:56 +0200104 kwargs:
Itzik Brownbac51dc2016-10-31 12:25:04 +0000105 name(str): Name of the server to be provisioned.
106 security_groups(list): List of dictionaries where
107 the keys is 'name' and the value is the name of
108 the security group. If it's not passed the default
109 security group will be used.
Roee Agiman6a0a18a2017-11-16 11:51:56 +0200110 availability_zone(str)*: The availability zone that
111 the instance will be in.
112 You can request a specific az without actually creating one,
113 Just pass 'X:Y' where X is the default availability
114 zone, and Y is the compute host name.
Itzik Brownbac51dc2016-10-31 12:25:04 +0000115 """
116
Jakub Libosvarffd9b912017-11-16 09:54:14 +0000117 kwargs.setdefault('name', data_utils.rand_name('server-test'))
Itzik Brownbac51dc2016-10-31 12:25:04 +0000118
Jakub Libosvarffd9b912017-11-16 09:54:14 +0000119 # We cannot use setdefault() here because caller could have passed
120 # security_groups=None and we don't want to pass None to
121 # client.create_server()
122 if not kwargs.get('security_groups'):
123 kwargs['security_groups'] = [{'name': 'default'}]
Roee Agiman6a0a18a2017-11-16 11:51:56 +0200124
Slawek Kaplonski2211eab2020-10-20 16:43:53 +0200125 client = kwargs.pop('client', None)
126 if client is None:
127 client = self.os_primary.servers_client
128 if kwargs.get('availability_zone'):
129 client = self.os_admin.servers_client
Roee Agiman6a0a18a2017-11-16 11:51:56 +0200130
Jakub Libosvarffd9b912017-11-16 09:54:14 +0000131 server = client.create_server(
132 flavorRef=flavor_ref,
133 imageRef=image_ref,
134 key_name=key_name,
135 networks=networks,
136 **kwargs)
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +0000137
138 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
Roee Agiman6a0a18a2017-11-16 11:51:56 +0200139 waiters.wait_for_server_termination,
140 client,
141 server['server']['id'])
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +0000142 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
Roee Agiman6a0a18a2017-11-16 11:51:56 +0200143 client.delete_server,
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +0000144 server['server']['id'])
Slawek Kaplonski2211eab2020-10-20 16:43:53 +0200145
146 self.wait_for_server_active(server['server'], client=client)
147 self.wait_for_guest_os_ready(server['server'], client=client)
148
Itzik Browne67ebb52016-05-15 05:34:41 +0000149 return server
150
151 @classmethod
Jens Harbott54357632017-11-21 11:47:06 +0000152 def create_secgroup_rules(cls, rule_list, secgroup_id=None,
153 client=None):
Roee Agiman6a0a18a2017-11-16 11:51:56 +0200154 client = client or cls.os_primary.network_client
Itzik Browne67ebb52016-05-15 05:34:41 +0000155 if not secgroup_id:
156 sgs = client.list_security_groups()['security_groups']
157 for sg in sgs:
158 if sg['name'] == constants.DEFAULT_SECURITY_GROUP:
159 secgroup_id = sg['id']
160 break
Hang Yange6e0ccf2021-02-26 15:07:05 -0600161 resp = []
Itzik Brown1ef813a2016-06-06 12:56:21 +0000162 for rule in rule_list:
163 direction = rule.pop('direction')
Hang Yange6e0ccf2021-02-26 15:07:05 -0600164 resp.append(client.create_security_group_rule(
165 direction=direction,
166 security_group_id=secgroup_id,
167 **rule)['security_group_rule'])
168 return resp
Itzik Brown1ef813a2016-06-06 12:56:21 +0000169
170 @classmethod
Roee Agiman6a0a18a2017-11-16 11:51:56 +0200171 def create_loginable_secgroup_rule(cls, secgroup_id=None,
172 client=None):
Itzik Brown1ef813a2016-06-06 12:56:21 +0000173 """This rule is intended to permit inbound ssh
174
175 Allowing ssh traffic traffic from all sources, so no group_id is
176 provided.
177 Setting a group_id would only permit traffic from ports
178 belonging to the same security group.
179 """
Federico Ressi4c590d72018-10-10 14:01:08 +0200180 return cls.create_security_group_rule(
181 security_group_id=secgroup_id,
182 client=client,
183 protocol=neutron_lib_constants.PROTO_NAME_TCP,
184 direction=neutron_lib_constants.INGRESS_DIRECTION,
185 port_range_min=22,
186 port_range_max=22)
Itzik Browne67ebb52016-05-15 05:34:41 +0000187
188 @classmethod
Roee Agiman6a0a18a2017-11-16 11:51:56 +0200189 def create_pingable_secgroup_rule(cls, secgroup_id=None,
190 client=None):
Federico Ressi4c590d72018-10-10 14:01:08 +0200191 """This rule is intended to permit inbound ping
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900192
Federico Ressi4c590d72018-10-10 14:01:08 +0200193 """
194 return cls.create_security_group_rule(
195 security_group_id=secgroup_id, client=client,
196 protocol=neutron_lib_constants.PROTO_NAME_ICMP,
197 direction=neutron_lib_constants.INGRESS_DIRECTION)
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900198
199 @classmethod
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300200 def create_router_by_client(cls, is_admin=False, **kwargs):
201 kwargs.update({'router_name': data_utils.rand_name('router'),
202 'admin_state_up': True,
203 'external_network_id': CONF.network.public_network_id})
204 if not is_admin:
205 router = cls.create_router(**kwargs)
206 else:
207 router = cls.create_admin_router(**kwargs)
Alex Stafeyevc4d9c352016-12-12 04:13:33 -0500208 LOG.debug("Created router %s", router['name'])
Slawek Kaplonskiedf3cba2021-04-21 10:34:02 +0200209 cls._wait_for_router_ha_active(router['id'])
Itzik Browne67ebb52016-05-15 05:34:41 +0000210 return router
211
ccamposre6b10042021-02-12 12:26:08 +0100212 @classmethod
Slawek Kaplonskiedf3cba2021-04-21 10:34:02 +0200213 def _wait_for_router_ha_active(cls, router_id):
214 router = cls.os_admin.network_client.show_router(router_id)['router']
215 if not router.get('ha'):
216 return
217
218 def _router_active_on_l3_agent():
219 agents = cls.os_admin.network_client.list_l3_agents_hosting_router(
220 router_id)['agents']
221 return "active" in [agent['ha_state'] for agent in agents]
222
223 error_msg = (
224 "Router %s is not active on any of the L3 agents" % router_id)
Slawek Kaplonskibb1532a2021-06-17 13:45:56 +0000225 # NOTE(slaweq): timeout here should be lower for sure, but due to
226 # the bug https://launchpad.net/bugs/1923633 let's wait even 10
227 # minutes until router will be active on some of the L3 agents
228 utils.wait_until_true(_router_active_on_l3_agent,
229 timeout=600, sleep=5,
230 exception=lib_exc.TimeoutException(error_msg))
Slawek Kaplonskiedf3cba2021-04-21 10:34:02 +0200231
232 @classmethod
ccamposre6b10042021-02-12 12:26:08 +0100233 def skip_if_no_extension_enabled_in_l3_agents(cls, extension):
234 l3_agents = cls.os_admin.network_client.list_agents(
235 binary='neutron-l3-agent')['agents']
236 if not l3_agents:
237 # the tests should not be skipped when neutron-l3-agent does not
238 # exist (this validation doesn't apply to the setups like
239 # e.g. ML2/OVN)
240 return
241 for agent in l3_agents:
242 if extension in agent['configurations'].get('extensions', []):
243 return
244 raise cls.skipTest("No L3 agent with '%s' extension enabled found." %
245 extension)
246
Federico Ressibf877c82018-08-22 08:36:37 +0200247 @removals.remove(version='Stein',
248 message="Please use create_floatingip method instead of "
249 "create_and_associate_floatingip.")
Roee Agiman6a0a18a2017-11-16 11:51:56 +0200250 def create_and_associate_floatingip(self, port_id, client=None):
251 client = client or self.os_primary.network_client
Federico Ressibf877c82018-08-22 08:36:37 +0200252 return self.create_floatingip(port_id=port_id, client=client)
Itzik Browne67ebb52016-05-15 05:34:41 +0000253
Hongbin Lu965b03d2018-04-25 22:32:30 +0000254 def create_interface(cls, server_id, port_id, client=None):
255 client = client or cls.os_primary.interfaces_client
256 body = client.create_interface(server_id, port_id=port_id)
257 return body['interfaceAttachment']
258
259 def delete_interface(cls, server_id, port_id, client=None):
260 client = client or cls.os_primary.interfaces_client
261 client.delete_interface(server_id, port_id=port_id)
262
Brian Haleyd11f4ec2019-08-13 12:09:57 -0400263 def setup_network_and_server(self, router=None, server_name=None,
264 network=None, **kwargs):
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300265 """Create network resources and a server.
Itzik Brownbac51dc2016-10-31 12:25:04 +0000266
267 Creating a network, subnet, router, keypair, security group
268 and a server.
269 """
Assaf Mullerd54ae6c2018-05-31 11:38:00 -0400270 self.network = network or self.create_network()
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +0000271 LOG.debug("Created network %s", self.network['name'])
272 self.subnet = self.create_subnet(self.network)
273 LOG.debug("Created subnet %s", self.subnet['id'])
Itzik Brown1ef813a2016-06-06 12:56:21 +0000274
Brian Haleyf86ac2e2017-06-21 10:43:50 -0400275 secgroup = self.os_primary.network_client.create_security_group(
Chandan Kumarc125fd12017-11-15 19:41:01 +0530276 name=data_utils.rand_name('secgroup'))
Alex Stafeyevc4d9c352016-12-12 04:13:33 -0500277 LOG.debug("Created security group %s",
278 secgroup['security_group']['name'])
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +0000279 self.security_groups.append(secgroup['security_group'])
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300280 if not router:
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +0000281 router = self.create_router_by_client(**kwargs)
282 self.create_router_interface(router['id'], self.subnet['id'])
283 self.keypair = self.create_keypair()
284 self.create_loginable_secgroup_rule(
Itzik Brownbac51dc2016-10-31 12:25:04 +0000285 secgroup_id=secgroup['security_group']['id'])
Assaf Muller92fdc782018-05-31 10:32:47 -0400286
287 server_kwargs = {
288 'flavor_ref': CONF.compute.flavor_ref,
289 'image_ref': CONF.compute.image_ref,
290 'key_name': self.keypair['name'],
291 'networks': [{'uuid': self.network['id']}],
292 'security_groups': [{'name': secgroup['security_group']['name']}],
293 }
294 if server_name is not None:
295 server_kwargs['name'] = server_name
296
297 self.server = self.create_server(**server_kwargs)
Jakub Libosvar1345d9d2017-06-09 13:59:05 +0000298 self.port = self.client.list_ports(network_id=self.network['id'],
299 device_id=self.server[
300 'server']['id'])['ports'][0]
Federico Ressibf877c82018-08-22 08:36:37 +0200301 self.fip = self.create_floatingip(port=self.port)
Jakub Libosvarc0c2f1d2017-01-31 12:12:21 -0500302
Bence Romsics2abbc922020-09-30 16:10:07 +0200303 def check_connectivity(self, host, ssh_user=None, ssh_key=None,
304 servers=None, ssh_timeout=None, ssh_client=None):
305 # Either ssh_client or ssh_user+ssh_key is mandatory.
306 if ssh_client is None:
307 ssh_client = ssh.Client(host, ssh_user,
308 pkey=ssh_key, timeout=ssh_timeout)
Jakub Libosvarc0c2f1d2017-01-31 12:12:21 -0500309 try:
310 ssh_client.test_connection_auth()
Slawek Kaplonski8cccfe02020-09-29 22:34:09 +0200311 except (lib_exc.SSHTimeout, ssh_exc.AuthenticationException) as ssh_e:
Jakub Libosvarc0c2f1d2017-01-31 12:12:21 -0500312 LOG.debug(ssh_e)
313 self._log_console_output(servers)
Rodolfo Alonso Hernandez4849f002020-01-16 16:01:10 +0000314 self._log_local_network_status()
Jakub Libosvarc0c2f1d2017-01-31 12:12:21 -0500315 raise
316
317 def _log_console_output(self, servers=None):
318 if not CONF.compute_feature_enabled.console_output:
319 LOG.debug('Console output not supported, cannot log')
320 return
321 if not servers:
Brian Haleyf86ac2e2017-06-21 10:43:50 -0400322 servers = self.os_primary.servers_client.list_servers()
Jakub Libosvarc0c2f1d2017-01-31 12:12:21 -0500323 servers = servers['servers']
324 for server in servers:
Slawek Kaplonskicff79232020-03-03 14:12:18 +0100325 # NOTE(slaweq): sometimes servers are passed in dictionary with
326 # "server" key as first level key and in other cases it may be that
327 # it is just the "inner" dict without "server" key. Lets try to
328 # handle both cases
329 server = server.get("server") or server
Jakub Libosvarc0c2f1d2017-01-31 12:12:21 -0500330 try:
331 console_output = (
Brian Haleyf86ac2e2017-06-21 10:43:50 -0400332 self.os_primary.servers_client.get_console_output(
Jakub Libosvarc0c2f1d2017-01-31 12:12:21 -0500333 server['id'])['output'])
334 LOG.debug('Console output for %s\nbody=\n%s',
335 server['id'], console_output)
336 except lib_exc.NotFound:
337 LOG.debug("Server %s disappeared(deleted) while looking "
338 "for the console log", server['id'])
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900339
Rodolfo Alonso Hernandez4849f002020-01-16 16:01:10 +0000340 def _log_local_network_status(self):
Slawek Kaplonski8033af72020-05-05 12:01:37 +0200341 self._log_ns_network_status()
342 for ns_name in ip_utils.IPCommand().list_namespaces():
343 self._log_ns_network_status(ns_name=ns_name)
344
345 def _log_ns_network_status(self, ns_name=None):
Rodolfo Alonso Hernandez9817d4f2020-11-17 08:50:50 +0000346 try:
347 local_ips = ip_utils.IPCommand(namespace=ns_name).list_addresses()
348 local_routes = ip_utils.IPCommand(namespace=ns_name).list_routes()
Rodolfo Alonso Hernandezc134ea92021-04-14 15:15:01 +0000349 arp_table = ip_utils.arp_table(namespace=ns_name)
350 iptables = ip_utils.list_iptables(namespace=ns_name)
351 lsockets = ip_utils.list_listening_sockets(namespace=ns_name)
Rodolfo Alonso Hernandez9817d4f2020-11-17 08:50:50 +0000352 except exceptions.ShellCommandFailed:
353 LOG.debug('Namespace %s has been deleted synchronously during the '
354 'host network collection process', ns_name)
355 return
356
Slawek Kaplonski8033af72020-05-05 12:01:37 +0200357 LOG.debug('Namespace %s; IP Addresses:\n%s',
358 ns_name, '\n'.join(str(r) for r in local_ips))
Slawek Kaplonski8033af72020-05-05 12:01:37 +0200359 LOG.debug('Namespace %s; Local routes:\n%s',
360 ns_name, '\n'.join(str(r) for r in local_routes))
Slawek Kaplonski8033af72020-05-05 12:01:37 +0200361 LOG.debug('Namespace %s; Local ARP table:\n%s',
362 ns_name, '\n'.join(str(r) for r in arp_table))
Rodolfo Alonso Hernandezc134ea92021-04-14 15:15:01 +0000363 LOG.debug('Namespace %s; Local iptables:\n%s', ns_name, iptables)
364 LOG.debug('Namespace %s; Listening sockets:\n%s', ns_name, lsockets)
Rodolfo Alonso Hernandez4849f002020-01-16 16:01:10 +0000365
LIU Yulong68ab2452019-05-18 10:19:49 +0800366 def _check_remote_connectivity(self, source, dest, count,
367 should_succeed=True,
Assaf Muller92fdc782018-05-31 10:32:47 -0400368 nic=None, mtu=None, fragmentation=True,
Roman Safronov12663cf2020-07-27 13:11:07 +0300369 timeout=None, pattern=None,
Nurmatov Mamatisa1b1c9d32021-12-27 15:37:03 +0300370 forbid_packet_loss=False,
371 check_response_ip=True):
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900372 """check ping server via source ssh connection
373
374 :param source: RemoteClient: an ssh connection from which to ping
375 :param dest: and IP to ping against
LIU Yulong68ab2452019-05-18 10:19:49 +0800376 :param count: Number of ping packet(s) to send
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900377 :param should_succeed: boolean should ping succeed or not
378 :param nic: specific network interface to ping from
Genadi Chereshnya6d10c6e2017-07-05 11:34:20 +0300379 :param mtu: mtu size for the packet to be sent
380 :param fragmentation: Flag for packet fragmentation
LIU Yulong68ab2452019-05-18 10:19:49 +0800381 :param timeout: Timeout for all ping packet(s) to succeed
Eduardo Olivaresf2b60542020-01-30 09:37:22 +0100382 :param pattern: hex digits included in ICMP messages
Roman Safronov12663cf2020-07-27 13:11:07 +0300383 :param forbid_packet_loss: forbid or allow some lost packets
Nurmatov Mamatisa1b1c9d32021-12-27 15:37:03 +0300384 :param check_response_ip: check response ip
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900385 :returns: boolean -- should_succeed == ping
386 :returns: ping is false if ping failed
387 """
LIU Yulong68ab2452019-05-18 10:19:49 +0800388 def ping_host(source, host, count,
Genadi Chereshnya6d10c6e2017-07-05 11:34:20 +0300389 size=CONF.validation.ping_size, nic=None, mtu=None,
Eduardo Olivaresf2b60542020-01-30 09:37:22 +0100390 fragmentation=True, pattern=None):
Assaf Muller92fdc782018-05-31 10:32:47 -0400391 IP_VERSION_4 = neutron_lib_constants.IP_VERSION_4
392 IP_VERSION_6 = neutron_lib_constants.IP_VERSION_6
393
394 # Use 'ping6' for IPv6 addresses, 'ping' for IPv4 and hostnames
395 ip_version = (
396 IP_VERSION_6 if netaddr.valid_ipv6(host) else IP_VERSION_4)
397 cmd = (
398 'ping6' if ip_version == IP_VERSION_6 else 'ping')
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900399 if nic:
400 cmd = 'sudo {cmd} -I {nic}'.format(cmd=cmd, nic=nic)
Genadi Chereshnya6d10c6e2017-07-05 11:34:20 +0300401 if mtu:
402 if not fragmentation:
403 cmd += ' -M do'
404 size = str(net_utils.get_ping_payload_size(
Assaf Muller92fdc782018-05-31 10:32:47 -0400405 mtu=mtu, ip_version=ip_version))
Eduardo Olivaresf2b60542020-01-30 09:37:22 +0100406 if pattern:
407 cmd += ' -p {pattern}'.format(pattern=pattern)
Maciej Józefczyk3c324e02020-03-16 10:52:08 +0000408 cmd += ' -c{0} -W{0} -s{1} {2}'.format(count, size, host)
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900409 return source.exec_command(cmd)
410
411 def ping_remote():
412 try:
LIU Yulong68ab2452019-05-18 10:19:49 +0800413 result = ping_host(source, dest, count, nic=nic, mtu=mtu,
Eduardo Olivaresf2b60542020-01-30 09:37:22 +0100414 fragmentation=fragmentation,
415 pattern=pattern)
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900416
417 except lib_exc.SSHExecCommandFailed:
418 LOG.warning('Failed to ping IP: %s via a ssh connection '
419 'from: %s.', dest, source.host)
420 return not should_succeed
421 LOG.debug('ping result: %s', result)
Assaf Muller92fdc782018-05-31 10:32:47 -0400422
Roman Safronov12663cf2020-07-27 13:11:07 +0300423 if forbid_packet_loss and ' 0% packet loss' not in result:
424 LOG.debug('Packet loss detected')
425 return not should_succeed
426
Nurmatov Mamatisa1b1c9d32021-12-27 15:37:03 +0300427 if (check_response_ip and
428 validators.validate_ip_address(dest) is None):
Assaf Muller92fdc782018-05-31 10:32:47 -0400429 # Assert that the return traffic was from the correct
430 # source address.
431 from_source = 'from %s' % dest
432 self.assertIn(from_source, result)
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900433 return should_succeed
434
Assaf Muller92fdc782018-05-31 10:32:47 -0400435 return test_utils.call_until_true(
436 ping_remote, timeout or CONF.validation.ping_timeout, 1)
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900437
438 def check_remote_connectivity(self, source, dest, should_succeed=True,
Slawek Kaplonskib07251f2018-05-16 12:21:50 +0200439 nic=None, mtu=None, fragmentation=True,
LIU Yulong68ab2452019-05-18 10:19:49 +0800440 servers=None, timeout=None,
Eduardo Olivaresf2b60542020-01-30 09:37:22 +0100441 ping_count=CONF.validation.ping_count,
Nurmatov Mamatisa1b1c9d32021-12-27 15:37:03 +0300442 pattern=None, forbid_packet_loss=False,
443 check_response_ip=True):
Slawek Kaplonskib07251f2018-05-16 12:21:50 +0200444 try:
445 self.assertTrue(self._check_remote_connectivity(
LIU Yulong68ab2452019-05-18 10:19:49 +0800446 source, dest, ping_count, should_succeed, nic, mtu,
447 fragmentation,
Roman Safronov12663cf2020-07-27 13:11:07 +0300448 timeout=timeout, pattern=pattern,
Nurmatov Mamatisa1b1c9d32021-12-27 15:37:03 +0300449 forbid_packet_loss=forbid_packet_loss,
450 check_response_ip=check_response_ip))
Slawek Kaplonski8cccfe02020-09-29 22:34:09 +0200451 except (lib_exc.SSHTimeout, ssh_exc.AuthenticationException) as ssh_e:
Slawek Kaplonskib07251f2018-05-16 12:21:50 +0200452 LOG.debug(ssh_e)
453 self._log_console_output(servers)
Slawek Kaplonski8cccfe02020-09-29 22:34:09 +0200454 self._log_local_network_status()
Slawek Kaplonskib07251f2018-05-16 12:21:50 +0200455 raise
456 except AssertionError:
457 self._log_console_output(servers)
Slawek Kaplonski8cccfe02020-09-29 22:34:09 +0200458 self._log_local_network_status()
Slawek Kaplonskib07251f2018-05-16 12:21:50 +0200459 raise
Chandan Kumarc125fd12017-11-15 19:41:01 +0530460
461 def ping_ip_address(self, ip_address, should_succeed=True,
462 ping_timeout=None, mtu=None):
463 # the code is taken from tempest/scenario/manager.py in tempest git
464 timeout = ping_timeout or CONF.validation.ping_timeout
465 cmd = ['ping', '-c1', '-w1']
466
467 if mtu:
468 cmd += [
469 # don't fragment
470 '-M', 'do',
471 # ping receives just the size of ICMP payload
472 '-s', str(net_utils.get_ping_payload_size(mtu, 4))
473 ]
474 cmd.append(ip_address)
475
476 def ping():
477 proc = subprocess.Popen(cmd,
478 stdout=subprocess.PIPE,
479 stderr=subprocess.PIPE)
480 proc.communicate()
481
482 return (proc.returncode == 0) == should_succeed
483
484 caller = test_utils.find_test_caller()
485 LOG.debug('%(caller)s begins to ping %(ip)s in %(timeout)s sec and the'
486 ' expected result is %(should_succeed)s', {
487 'caller': caller, 'ip': ip_address, 'timeout': timeout,
488 'should_succeed':
489 'reachable' if should_succeed else 'unreachable'
490 })
491 result = test_utils.call_until_true(ping, timeout, 1)
Manjeet Singh Bhatia8bbf8992019-03-04 11:59:57 -0800492
493 # To make sure ping_ip_address called by test works
494 # as expected.
495 self.assertTrue(result)
496
Chandan Kumarc125fd12017-11-15 19:41:01 +0530497 LOG.debug('%(caller)s finishes ping %(ip)s in %(timeout)s sec and the '
498 'ping result is %(result)s', {
499 'caller': caller, 'ip': ip_address, 'timeout': timeout,
500 'result': 'expected' if result else 'unexpected'
501 })
502 return result
Federico Ressie7417b72018-05-30 05:50:58 +0200503
504 def wait_for_server_status(self, server, status, client=None, **kwargs):
505 """Waits for a server to reach a given status.
506
507 :param server: mapping having schema {'id': <server_id>}
508 :param status: string status to wait for (es: 'ACTIVE')
509 :param clien: servers client (self.os_primary.servers_client as
510 default value)
511 """
512
513 client = client or self.os_primary.servers_client
514 waiters.wait_for_server_status(client, server['id'], status, **kwargs)
515
516 def wait_for_server_active(self, server, client=None):
517 """Waits for a server to reach active status.
518
519 :param server: mapping having schema {'id': <server_id>}
520 :param clien: servers client (self.os_primary.servers_client as
521 default value)
522 """
523 self.wait_for_server_status(
524 server, constants.SERVER_STATUS_ACTIVE, client)
Jakub Libosvar031fd5a2019-07-15 16:10:07 +0000525
Slawek Kaplonski2211eab2020-10-20 16:43:53 +0200526 def wait_for_guest_os_ready(self, server, client=None):
527 if not CONF.compute_feature_enabled.console_output:
528 LOG.debug('Console output not supported, cannot check if server '
Rodolfo Alonso Hernandezdff870b2020-11-06 08:41:44 +0000529 '%s is ready.', server['id'])
Slawek Kaplonski2211eab2020-10-20 16:43:53 +0200530 return
531
532 client = client or self.os_primary.servers_client
533
534 def system_booted():
535 console_output = client.get_console_output(server['id'])['output']
536 for line in console_output.split('\n'):
537 if 'login:' in line.lower():
538 return True
539 return False
540
541 try:
542 utils.wait_until_true(system_booted, sleep=5)
543 except utils.WaitTimeout:
544 LOG.debug("No correct output in console of server %s found. "
545 "Guest operating system status can't be checked.",
546 server['id'])
547
Flavio Fernandesa1952c62020-10-02 06:39:08 -0400548 def check_servers_hostnames(self, servers, timeout=None, log_errors=True,
549 external_port=None):
Jakub Libosvar031fd5a2019-07-15 16:10:07 +0000550 """Compare hostnames of given servers with their names."""
551 try:
552 for server in servers:
553 kwargs = {}
nfridmand8969542020-06-02 14:59:09 +0300554 if timeout:
555 kwargs['timeout'] = timeout
Jakub Libosvar031fd5a2019-07-15 16:10:07 +0000556 try:
Flavio Fernandesa1952c62020-10-02 06:39:08 -0400557 kwargs['port'] = external_port or (
Slawek Kaplonskic4e963e2019-09-11 22:55:34 +0200558 server['port_forwarding_tcp']['external_port'])
Jakub Libosvar031fd5a2019-07-15 16:10:07 +0000559 except KeyError:
560 pass
561 ssh_client = ssh.Client(
562 self.fip['floating_ip_address'],
563 CONF.validation.image_ssh_user,
564 pkey=self.keypair['private_key'],
565 **kwargs)
566 self.assertIn(server['name'],
Rodolfo Alonso Hernandezaf394dd2020-11-12 14:26:13 +0000567 ssh_client.get_hostname())
Slawek Kaplonski8cccfe02020-09-29 22:34:09 +0200568 except (lib_exc.SSHTimeout, ssh_exc.AuthenticationException) as ssh_e:
Jakub Libosvar031fd5a2019-07-15 16:10:07 +0000569 LOG.debug(ssh_e)
570 if log_errors:
571 self._log_console_output(servers)
Slawek Kaplonski8cccfe02020-09-29 22:34:09 +0200572 self._log_local_network_status()
Jakub Libosvar031fd5a2019-07-15 16:10:07 +0000573 raise
574 except AssertionError as assert_e:
575 LOG.debug(assert_e)
576 if log_errors:
577 self._log_console_output(servers)
Slawek Kaplonski8cccfe02020-09-29 22:34:09 +0200578 self._log_local_network_status()
Jakub Libosvar031fd5a2019-07-15 16:10:07 +0000579 raise
Slawek Kaplonskic4e963e2019-09-11 22:55:34 +0200580
Slawek Kaplonskifd4141f2020-03-14 14:34:00 +0100581 def ensure_nc_listen(self, ssh_client, port, protocol, echo_msg=None,
582 servers=None):
583 """Ensure that nc server listening on the given TCP/UDP port is up.
584
585 Listener is created always on remote host.
586 """
587 def spawn_and_check_process():
588 self.nc_listen(ssh_client, port, protocol, echo_msg, servers)
589 return utils.process_is_running(ssh_client, "nc")
590
591 utils.wait_until_true(spawn_and_check_process)
592
593 def nc_listen(self, ssh_client, port, protocol, echo_msg=None,
594 servers=None):
Slawek Kaplonskic4e963e2019-09-11 22:55:34 +0200595 """Create nc server listening on the given TCP/UDP port.
596
597 Listener is created always on remote host.
598 """
Slawek Kaplonskic4e963e2019-09-11 22:55:34 +0200599 try:
Slawek Kaplonskiaf83e832020-02-05 12:11:54 +0100600 return ssh_client.execute_script(
601 get_ncat_server_cmd(port, protocol, echo_msg),
Flavio Fernandesb056ac22020-07-01 14:57:13 -0400602 become_root=True, combine_stderr=True)
Slawek Kaplonski8cccfe02020-09-29 22:34:09 +0200603 except (lib_exc.SSHTimeout, ssh_exc.AuthenticationException) as ssh_e:
Slawek Kaplonskic4e963e2019-09-11 22:55:34 +0200604 LOG.debug(ssh_e)
Slawek Kaplonskifd4141f2020-03-14 14:34:00 +0100605 self._log_console_output(servers)
Slawek Kaplonski8cccfe02020-09-29 22:34:09 +0200606 self._log_local_network_status()
Slawek Kaplonskic4e963e2019-09-11 22:55:34 +0200607 raise
608
609 def nc_client(self, ip_address, port, protocol):
610 """Check connectivity to TCP/UDP port at host via nc.
611
612 Client is always executed locally on host where tests are executed.
613 """
Slawek Kaplonskiaf83e832020-02-05 12:11:54 +0100614 cmd = get_ncat_client_cmd(ip_address, port, protocol)
Slawek Kaplonskic4e963e2019-09-11 22:55:34 +0200615 result = shell.execute_local_command(cmd)
616 self.assertEqual(0, result.exit_status)
617 return result.stdout