blob: cf68224006c4bbe51381678159e0781c0641fbd6 [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
Alex Stafeyevc4d9c352016-12-12 04:13:33 -050041LOG = log.getLogger(__name__)
Eduardo Olivares46fa4242022-04-18 12:47:43 +020042SSH_EXC_TUPLE = (lib_exc.SSHTimeout,
43 ssh_exc.AuthenticationException,
44 ssh_exc.NoValidConnectionsError,
45 ConnectionResetError)
Alex Stafeyevc4d9c352016-12-12 04:13:33 -050046
Itzik Browne67ebb52016-05-15 05:34:41 +000047
Slawek Kaplonskiaf83e832020-02-05 12:11:54 +010048def get_ncat_version(ssh_client=None):
49 cmd = "ncat --version 2>&1"
50 try:
51 version_result = shell.execute(cmd, ssh_client=ssh_client).stdout
52 except exceptions.ShellCommandFailed:
53 m = None
54 else:
55 m = re.match(r"Ncat: Version ([\d.]+) *.", version_result)
56 # NOTE(slaweq): by default lets assume we have ncat 7.60 which is in Ubuntu
57 # 18.04 which is used on u/s gates
58 return distutils.version.StrictVersion(m.group(1) if m else '7.60')
59
60
Slawek Kaplonskifd4141f2020-03-14 14:34:00 +010061def get_ncat_server_cmd(port, protocol, msg=None):
Slawek Kaplonskiaf83e832020-02-05 12:11:54 +010062 udp = ''
63 if protocol.lower() == neutron_lib_constants.PROTO_NAME_UDP:
64 udp = '-u'
65 cmd = "nc %(udp)s -p %(port)s -lk " % {
66 'udp': udp, 'port': port}
Slawek Kaplonskifd4141f2020-03-14 14:34:00 +010067 if msg:
68 if CONF.neutron_plugin_options.default_image_is_advanced:
Flavio Fernandesb056ac22020-07-01 14:57:13 -040069 cmd += "-c 'echo %s' " % msg
Slawek Kaplonskifd4141f2020-03-14 14:34:00 +010070 else:
Flavio Fernandesb056ac22020-07-01 14:57:13 -040071 cmd += "-e echo %s " % msg
72 cmd += "< /dev/zero &{0}sleep 0.1{0}".format('\n')
Slawek Kaplonskiaf83e832020-02-05 12:11:54 +010073 return cmd
74
75
76def get_ncat_client_cmd(ip_address, port, protocol):
77 udp = ''
78 if protocol.lower() == neutron_lib_constants.PROTO_NAME_UDP:
79 udp = '-u'
80 cmd = 'echo "knock knock" | nc '
81 ncat_version = get_ncat_version()
82 if ncat_version > distutils.version.StrictVersion('7.60'):
83 cmd += '-z '
84 cmd += '-w 1 %(udp)s %(host)s %(port)s' % {
85 'udp': udp, 'host': ip_address, 'port': port}
86 return cmd
87
88
Itzik Browne67ebb52016-05-15 05:34:41 +000089class BaseTempestTestCase(base_api.BaseNetworkTest):
Itzik Browne67ebb52016-05-15 05:34:41 +000090
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +000091 def create_server(self, flavor_ref, image_ref, key_name, networks,
Roee Agiman6a0a18a2017-11-16 11:51:56 +020092 **kwargs):
Itzik Brownbac51dc2016-10-31 12:25:04 +000093 """Create a server using tempest lib
Brian Haleyaee61ac2018-10-09 20:00:27 -040094
Itzik Brownbac51dc2016-10-31 12:25:04 +000095 All the parameters are the ones used in Compute API
Roee Agiman6a0a18a2017-11-16 11:51:56 +020096 * - Kwargs that require admin privileges
Itzik Brownbac51dc2016-10-31 12:25:04 +000097
98 Args:
99 flavor_ref(str): The flavor of the server to be provisioned.
100 image_ref(str): The image of the server to be provisioned.
101 key_name(str): SSH key to to be used to connect to the
102 provisioned server.
103 networks(list): List of dictionaries where each represent
104 an interface to be attached to the server. For network
105 it should be {'uuid': network_uuid} and for port it should
106 be {'port': port_uuid}
Roee Agiman6a0a18a2017-11-16 11:51:56 +0200107 kwargs:
Itzik Brownbac51dc2016-10-31 12:25:04 +0000108 name(str): Name of the server to be provisioned.
109 security_groups(list): List of dictionaries where
110 the keys is 'name' and the value is the name of
111 the security group. If it's not passed the default
112 security group will be used.
Roee Agiman6a0a18a2017-11-16 11:51:56 +0200113 availability_zone(str)*: The availability zone that
114 the instance will be in.
115 You can request a specific az without actually creating one,
116 Just pass 'X:Y' where X is the default availability
117 zone, and Y is the compute host name.
Itzik Brownbac51dc2016-10-31 12:25:04 +0000118 """
119
Jakub Libosvarffd9b912017-11-16 09:54:14 +0000120 kwargs.setdefault('name', data_utils.rand_name('server-test'))
Itzik Brownbac51dc2016-10-31 12:25:04 +0000121
Jakub Libosvarffd9b912017-11-16 09:54:14 +0000122 # We cannot use setdefault() here because caller could have passed
123 # security_groups=None and we don't want to pass None to
124 # client.create_server()
125 if not kwargs.get('security_groups'):
126 kwargs['security_groups'] = [{'name': 'default'}]
Roee Agiman6a0a18a2017-11-16 11:51:56 +0200127
Slawek Kaplonski2211eab2020-10-20 16:43:53 +0200128 client = kwargs.pop('client', None)
129 if client is None:
130 client = self.os_primary.servers_client
131 if kwargs.get('availability_zone'):
132 client = self.os_admin.servers_client
Roee Agiman6a0a18a2017-11-16 11:51:56 +0200133
Jakub Libosvarffd9b912017-11-16 09:54:14 +0000134 server = client.create_server(
135 flavorRef=flavor_ref,
136 imageRef=image_ref,
137 key_name=key_name,
138 networks=networks,
139 **kwargs)
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +0000140
141 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
Roee Agiman6a0a18a2017-11-16 11:51:56 +0200142 waiters.wait_for_server_termination,
143 client,
144 server['server']['id'])
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +0000145 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
Roee Agiman6a0a18a2017-11-16 11:51:56 +0200146 client.delete_server,
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +0000147 server['server']['id'])
Slawek Kaplonski2211eab2020-10-20 16:43:53 +0200148
149 self.wait_for_server_active(server['server'], client=client)
150 self.wait_for_guest_os_ready(server['server'], client=client)
151
Itzik Browne67ebb52016-05-15 05:34:41 +0000152 return server
153
154 @classmethod
Jens Harbott54357632017-11-21 11:47:06 +0000155 def create_secgroup_rules(cls, rule_list, secgroup_id=None,
156 client=None):
Roee Agiman6a0a18a2017-11-16 11:51:56 +0200157 client = client or cls.os_primary.network_client
Itzik Browne67ebb52016-05-15 05:34:41 +0000158 if not secgroup_id:
159 sgs = client.list_security_groups()['security_groups']
160 for sg in sgs:
161 if sg['name'] == constants.DEFAULT_SECURITY_GROUP:
162 secgroup_id = sg['id']
163 break
Hang Yange6e0ccf2021-02-26 15:07:05 -0600164 resp = []
Itzik Brown1ef813a2016-06-06 12:56:21 +0000165 for rule in rule_list:
166 direction = rule.pop('direction')
Hang Yange6e0ccf2021-02-26 15:07:05 -0600167 resp.append(client.create_security_group_rule(
168 direction=direction,
169 security_group_id=secgroup_id,
170 **rule)['security_group_rule'])
171 return resp
Itzik Brown1ef813a2016-06-06 12:56:21 +0000172
173 @classmethod
Roee Agiman6a0a18a2017-11-16 11:51:56 +0200174 def create_loginable_secgroup_rule(cls, secgroup_id=None,
175 client=None):
Itzik Brown1ef813a2016-06-06 12:56:21 +0000176 """This rule is intended to permit inbound ssh
177
178 Allowing ssh traffic traffic from all sources, so no group_id is
179 provided.
180 Setting a group_id would only permit traffic from ports
181 belonging to the same security group.
182 """
Federico Ressi4c590d72018-10-10 14:01:08 +0200183 return cls.create_security_group_rule(
184 security_group_id=secgroup_id,
185 client=client,
186 protocol=neutron_lib_constants.PROTO_NAME_TCP,
187 direction=neutron_lib_constants.INGRESS_DIRECTION,
188 port_range_min=22,
189 port_range_max=22)
Itzik Browne67ebb52016-05-15 05:34:41 +0000190
191 @classmethod
Roee Agiman6a0a18a2017-11-16 11:51:56 +0200192 def create_pingable_secgroup_rule(cls, secgroup_id=None,
193 client=None):
Federico Ressi4c590d72018-10-10 14:01:08 +0200194 """This rule is intended to permit inbound ping
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900195
Federico Ressi4c590d72018-10-10 14:01:08 +0200196 """
197 return cls.create_security_group_rule(
198 security_group_id=secgroup_id, client=client,
199 protocol=neutron_lib_constants.PROTO_NAME_ICMP,
200 direction=neutron_lib_constants.INGRESS_DIRECTION)
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900201
202 @classmethod
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300203 def create_router_by_client(cls, is_admin=False, **kwargs):
204 kwargs.update({'router_name': data_utils.rand_name('router'),
205 'admin_state_up': True,
206 'external_network_id': CONF.network.public_network_id})
207 if not is_admin:
208 router = cls.create_router(**kwargs)
209 else:
210 router = cls.create_admin_router(**kwargs)
Alex Stafeyevc4d9c352016-12-12 04:13:33 -0500211 LOG.debug("Created router %s", router['name'])
Slawek Kaplonskiedf3cba2021-04-21 10:34:02 +0200212 cls._wait_for_router_ha_active(router['id'])
Itzik Browne67ebb52016-05-15 05:34:41 +0000213 return router
214
ccamposre6b10042021-02-12 12:26:08 +0100215 @classmethod
Slawek Kaplonskiedf3cba2021-04-21 10:34:02 +0200216 def _wait_for_router_ha_active(cls, router_id):
217 router = cls.os_admin.network_client.show_router(router_id)['router']
218 if not router.get('ha'):
219 return
220
221 def _router_active_on_l3_agent():
222 agents = cls.os_admin.network_client.list_l3_agents_hosting_router(
223 router_id)['agents']
224 return "active" in [agent['ha_state'] for agent in agents]
225
226 error_msg = (
227 "Router %s is not active on any of the L3 agents" % router_id)
Slawek Kaplonskibb1532a2021-06-17 13:45:56 +0000228 # NOTE(slaweq): timeout here should be lower for sure, but due to
229 # the bug https://launchpad.net/bugs/1923633 let's wait even 10
230 # minutes until router will be active on some of the L3 agents
231 utils.wait_until_true(_router_active_on_l3_agent,
232 timeout=600, sleep=5,
233 exception=lib_exc.TimeoutException(error_msg))
Slawek Kaplonskiedf3cba2021-04-21 10:34:02 +0200234
235 @classmethod
ccamposre6b10042021-02-12 12:26:08 +0100236 def skip_if_no_extension_enabled_in_l3_agents(cls, extension):
237 l3_agents = cls.os_admin.network_client.list_agents(
238 binary='neutron-l3-agent')['agents']
239 if not l3_agents:
240 # the tests should not be skipped when neutron-l3-agent does not
241 # exist (this validation doesn't apply to the setups like
242 # e.g. ML2/OVN)
243 return
244 for agent in l3_agents:
245 if extension in agent['configurations'].get('extensions', []):
246 return
247 raise cls.skipTest("No L3 agent with '%s' extension enabled found." %
248 extension)
249
Federico Ressibf877c82018-08-22 08:36:37 +0200250 @removals.remove(version='Stein',
251 message="Please use create_floatingip method instead of "
252 "create_and_associate_floatingip.")
Roee Agiman6a0a18a2017-11-16 11:51:56 +0200253 def create_and_associate_floatingip(self, port_id, client=None):
254 client = client or self.os_primary.network_client
Federico Ressibf877c82018-08-22 08:36:37 +0200255 return self.create_floatingip(port_id=port_id, client=client)
Itzik Browne67ebb52016-05-15 05:34:41 +0000256
Hongbin Lu965b03d2018-04-25 22:32:30 +0000257 def create_interface(cls, server_id, port_id, client=None):
258 client = client or cls.os_primary.interfaces_client
259 body = client.create_interface(server_id, port_id=port_id)
260 return body['interfaceAttachment']
261
262 def delete_interface(cls, server_id, port_id, client=None):
263 client = client or cls.os_primary.interfaces_client
264 client.delete_interface(server_id, port_id=port_id)
265
Brian Haleyd11f4ec2019-08-13 12:09:57 -0400266 def setup_network_and_server(self, router=None, server_name=None,
267 network=None, **kwargs):
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300268 """Create network resources and a server.
Itzik Brownbac51dc2016-10-31 12:25:04 +0000269
270 Creating a network, subnet, router, keypair, security group
271 and a server.
272 """
Assaf Mullerd54ae6c2018-05-31 11:38:00 -0400273 self.network = network or self.create_network()
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +0000274 LOG.debug("Created network %s", self.network['name'])
275 self.subnet = self.create_subnet(self.network)
276 LOG.debug("Created subnet %s", self.subnet['id'])
Itzik Brown1ef813a2016-06-06 12:56:21 +0000277
Brian Haleyf86ac2e2017-06-21 10:43:50 -0400278 secgroup = self.os_primary.network_client.create_security_group(
Chandan Kumarc125fd12017-11-15 19:41:01 +0530279 name=data_utils.rand_name('secgroup'))
Alex Stafeyevc4d9c352016-12-12 04:13:33 -0500280 LOG.debug("Created security group %s",
281 secgroup['security_group']['name'])
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +0000282 self.security_groups.append(secgroup['security_group'])
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300283 if not router:
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +0000284 router = self.create_router_by_client(**kwargs)
285 self.create_router_interface(router['id'], self.subnet['id'])
286 self.keypair = self.create_keypair()
287 self.create_loginable_secgroup_rule(
Itzik Brownbac51dc2016-10-31 12:25:04 +0000288 secgroup_id=secgroup['security_group']['id'])
Assaf Muller92fdc782018-05-31 10:32:47 -0400289
290 server_kwargs = {
291 'flavor_ref': CONF.compute.flavor_ref,
292 'image_ref': CONF.compute.image_ref,
293 'key_name': self.keypair['name'],
294 'networks': [{'uuid': self.network['id']}],
295 'security_groups': [{'name': secgroup['security_group']['name']}],
296 }
297 if server_name is not None:
298 server_kwargs['name'] = server_name
299
300 self.server = self.create_server(**server_kwargs)
Jakub Libosvar1345d9d2017-06-09 13:59:05 +0000301 self.port = self.client.list_ports(network_id=self.network['id'],
302 device_id=self.server[
303 'server']['id'])['ports'][0]
Federico Ressibf877c82018-08-22 08:36:37 +0200304 self.fip = self.create_floatingip(port=self.port)
Jakub Libosvarc0c2f1d2017-01-31 12:12:21 -0500305
Bence Romsics2abbc922020-09-30 16:10:07 +0200306 def check_connectivity(self, host, ssh_user=None, ssh_key=None,
307 servers=None, ssh_timeout=None, ssh_client=None):
308 # Either ssh_client or ssh_user+ssh_key is mandatory.
309 if ssh_client is None:
310 ssh_client = ssh.Client(host, ssh_user,
311 pkey=ssh_key, timeout=ssh_timeout)
Jakub Libosvarc0c2f1d2017-01-31 12:12:21 -0500312 try:
313 ssh_client.test_connection_auth()
Eduardo Olivares46fa4242022-04-18 12:47:43 +0200314 except SSH_EXC_TUPLE as ssh_e:
Jakub Libosvarc0c2f1d2017-01-31 12:12:21 -0500315 LOG.debug(ssh_e)
316 self._log_console_output(servers)
Rodolfo Alonso Hernandez4849f002020-01-16 16:01:10 +0000317 self._log_local_network_status()
Jakub Libosvarc0c2f1d2017-01-31 12:12:21 -0500318 raise
319
320 def _log_console_output(self, servers=None):
321 if not CONF.compute_feature_enabled.console_output:
322 LOG.debug('Console output not supported, cannot log')
323 return
324 if not servers:
Brian Haleyf86ac2e2017-06-21 10:43:50 -0400325 servers = self.os_primary.servers_client.list_servers()
Jakub Libosvarc0c2f1d2017-01-31 12:12:21 -0500326 servers = servers['servers']
327 for server in servers:
Slawek Kaplonskicff79232020-03-03 14:12:18 +0100328 # NOTE(slaweq): sometimes servers are passed in dictionary with
329 # "server" key as first level key and in other cases it may be that
330 # it is just the "inner" dict without "server" key. Lets try to
331 # handle both cases
332 server = server.get("server") or server
Jakub Libosvarc0c2f1d2017-01-31 12:12:21 -0500333 try:
334 console_output = (
Brian Haleyf86ac2e2017-06-21 10:43:50 -0400335 self.os_primary.servers_client.get_console_output(
Jakub Libosvarc0c2f1d2017-01-31 12:12:21 -0500336 server['id'])['output'])
337 LOG.debug('Console output for %s\nbody=\n%s',
338 server['id'], console_output)
339 except lib_exc.NotFound:
340 LOG.debug("Server %s disappeared(deleted) while looking "
341 "for the console log", server['id'])
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900342
Rodolfo Alonso Hernandez4849f002020-01-16 16:01:10 +0000343 def _log_local_network_status(self):
Slawek Kaplonski8033af72020-05-05 12:01:37 +0200344 self._log_ns_network_status()
345 for ns_name in ip_utils.IPCommand().list_namespaces():
346 self._log_ns_network_status(ns_name=ns_name)
347
348 def _log_ns_network_status(self, ns_name=None):
Rodolfo Alonso Hernandez9817d4f2020-11-17 08:50:50 +0000349 try:
350 local_ips = ip_utils.IPCommand(namespace=ns_name).list_addresses()
351 local_routes = ip_utils.IPCommand(namespace=ns_name).list_routes()
Rodolfo Alonso Hernandezc134ea92021-04-14 15:15:01 +0000352 arp_table = ip_utils.arp_table(namespace=ns_name)
353 iptables = ip_utils.list_iptables(namespace=ns_name)
354 lsockets = ip_utils.list_listening_sockets(namespace=ns_name)
Rodolfo Alonso Hernandez9817d4f2020-11-17 08:50:50 +0000355 except exceptions.ShellCommandFailed:
356 LOG.debug('Namespace %s has been deleted synchronously during the '
357 'host network collection process', ns_name)
358 return
359
Slawek Kaplonski8033af72020-05-05 12:01:37 +0200360 LOG.debug('Namespace %s; IP Addresses:\n%s',
361 ns_name, '\n'.join(str(r) for r in local_ips))
Slawek Kaplonski8033af72020-05-05 12:01:37 +0200362 LOG.debug('Namespace %s; Local routes:\n%s',
363 ns_name, '\n'.join(str(r) for r in local_routes))
Slawek Kaplonski8033af72020-05-05 12:01:37 +0200364 LOG.debug('Namespace %s; Local ARP table:\n%s',
365 ns_name, '\n'.join(str(r) for r in arp_table))
Rodolfo Alonso Hernandezc134ea92021-04-14 15:15:01 +0000366 LOG.debug('Namespace %s; Local iptables:\n%s', ns_name, iptables)
367 LOG.debug('Namespace %s; Listening sockets:\n%s', ns_name, lsockets)
Rodolfo Alonso Hernandez4849f002020-01-16 16:01:10 +0000368
LIU Yulong68ab2452019-05-18 10:19:49 +0800369 def _check_remote_connectivity(self, source, dest, count,
370 should_succeed=True,
Assaf Muller92fdc782018-05-31 10:32:47 -0400371 nic=None, mtu=None, fragmentation=True,
Roman Safronov12663cf2020-07-27 13:11:07 +0300372 timeout=None, pattern=None,
Nurmatov Mamatisa1b1c9d32021-12-27 15:37:03 +0300373 forbid_packet_loss=False,
374 check_response_ip=True):
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900375 """check ping server via source ssh connection
376
377 :param source: RemoteClient: an ssh connection from which to ping
378 :param dest: and IP to ping against
LIU Yulong68ab2452019-05-18 10:19:49 +0800379 :param count: Number of ping packet(s) to send
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900380 :param should_succeed: boolean should ping succeed or not
381 :param nic: specific network interface to ping from
Genadi Chereshnya6d10c6e2017-07-05 11:34:20 +0300382 :param mtu: mtu size for the packet to be sent
383 :param fragmentation: Flag for packet fragmentation
LIU Yulong68ab2452019-05-18 10:19:49 +0800384 :param timeout: Timeout for all ping packet(s) to succeed
Eduardo Olivaresf2b60542020-01-30 09:37:22 +0100385 :param pattern: hex digits included in ICMP messages
Roman Safronov12663cf2020-07-27 13:11:07 +0300386 :param forbid_packet_loss: forbid or allow some lost packets
Nurmatov Mamatisa1b1c9d32021-12-27 15:37:03 +0300387 :param check_response_ip: check response ip
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900388 :returns: boolean -- should_succeed == ping
389 :returns: ping is false if ping failed
390 """
LIU Yulong68ab2452019-05-18 10:19:49 +0800391 def ping_host(source, host, count,
Genadi Chereshnya6d10c6e2017-07-05 11:34:20 +0300392 size=CONF.validation.ping_size, nic=None, mtu=None,
Eduardo Olivaresf2b60542020-01-30 09:37:22 +0100393 fragmentation=True, pattern=None):
Assaf Muller92fdc782018-05-31 10:32:47 -0400394 IP_VERSION_4 = neutron_lib_constants.IP_VERSION_4
395 IP_VERSION_6 = neutron_lib_constants.IP_VERSION_6
396
397 # Use 'ping6' for IPv6 addresses, 'ping' for IPv4 and hostnames
398 ip_version = (
399 IP_VERSION_6 if netaddr.valid_ipv6(host) else IP_VERSION_4)
400 cmd = (
401 'ping6' if ip_version == IP_VERSION_6 else 'ping')
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900402 if nic:
403 cmd = 'sudo {cmd} -I {nic}'.format(cmd=cmd, nic=nic)
Genadi Chereshnya6d10c6e2017-07-05 11:34:20 +0300404 if mtu:
405 if not fragmentation:
406 cmd += ' -M do'
407 size = str(net_utils.get_ping_payload_size(
Assaf Muller92fdc782018-05-31 10:32:47 -0400408 mtu=mtu, ip_version=ip_version))
Eduardo Olivaresf2b60542020-01-30 09:37:22 +0100409 if pattern:
410 cmd += ' -p {pattern}'.format(pattern=pattern)
Maciej Józefczyk3c324e02020-03-16 10:52:08 +0000411 cmd += ' -c{0} -W{0} -s{1} {2}'.format(count, size, host)
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900412 return source.exec_command(cmd)
413
414 def ping_remote():
415 try:
LIU Yulong68ab2452019-05-18 10:19:49 +0800416 result = ping_host(source, dest, count, nic=nic, mtu=mtu,
Eduardo Olivaresf2b60542020-01-30 09:37:22 +0100417 fragmentation=fragmentation,
418 pattern=pattern)
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900419
420 except lib_exc.SSHExecCommandFailed:
421 LOG.warning('Failed to ping IP: %s via a ssh connection '
422 'from: %s.', dest, source.host)
423 return not should_succeed
424 LOG.debug('ping result: %s', result)
Assaf Muller92fdc782018-05-31 10:32:47 -0400425
Roman Safronov12663cf2020-07-27 13:11:07 +0300426 if forbid_packet_loss and ' 0% packet loss' not in result:
427 LOG.debug('Packet loss detected')
428 return not should_succeed
429
Nurmatov Mamatisa1b1c9d32021-12-27 15:37:03 +0300430 if (check_response_ip and
431 validators.validate_ip_address(dest) is None):
Assaf Muller92fdc782018-05-31 10:32:47 -0400432 # Assert that the return traffic was from the correct
433 # source address.
434 from_source = 'from %s' % dest
435 self.assertIn(from_source, result)
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900436 return should_succeed
437
Assaf Muller92fdc782018-05-31 10:32:47 -0400438 return test_utils.call_until_true(
439 ping_remote, timeout or CONF.validation.ping_timeout, 1)
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900440
441 def check_remote_connectivity(self, source, dest, should_succeed=True,
Slawek Kaplonskib07251f2018-05-16 12:21:50 +0200442 nic=None, mtu=None, fragmentation=True,
LIU Yulong68ab2452019-05-18 10:19:49 +0800443 servers=None, timeout=None,
Eduardo Olivaresf2b60542020-01-30 09:37:22 +0100444 ping_count=CONF.validation.ping_count,
Nurmatov Mamatisa1b1c9d32021-12-27 15:37:03 +0300445 pattern=None, forbid_packet_loss=False,
446 check_response_ip=True):
Slawek Kaplonskib07251f2018-05-16 12:21:50 +0200447 try:
448 self.assertTrue(self._check_remote_connectivity(
LIU Yulong68ab2452019-05-18 10:19:49 +0800449 source, dest, ping_count, should_succeed, nic, mtu,
450 fragmentation,
Roman Safronov12663cf2020-07-27 13:11:07 +0300451 timeout=timeout, pattern=pattern,
Nurmatov Mamatisa1b1c9d32021-12-27 15:37:03 +0300452 forbid_packet_loss=forbid_packet_loss,
453 check_response_ip=check_response_ip))
Eduardo Olivares46fa4242022-04-18 12:47:43 +0200454 except SSH_EXC_TUPLE as ssh_e:
Slawek Kaplonskib07251f2018-05-16 12:21:50 +0200455 LOG.debug(ssh_e)
456 self._log_console_output(servers)
Slawek Kaplonski8cccfe02020-09-29 22:34:09 +0200457 self._log_local_network_status()
Slawek Kaplonskib07251f2018-05-16 12:21:50 +0200458 raise
459 except AssertionError:
460 self._log_console_output(servers)
Slawek Kaplonski8cccfe02020-09-29 22:34:09 +0200461 self._log_local_network_status()
Slawek Kaplonskib07251f2018-05-16 12:21:50 +0200462 raise
Chandan Kumarc125fd12017-11-15 19:41:01 +0530463
464 def ping_ip_address(self, ip_address, should_succeed=True,
465 ping_timeout=None, mtu=None):
466 # the code is taken from tempest/scenario/manager.py in tempest git
467 timeout = ping_timeout or CONF.validation.ping_timeout
468 cmd = ['ping', '-c1', '-w1']
469
470 if mtu:
471 cmd += [
472 # don't fragment
473 '-M', 'do',
474 # ping receives just the size of ICMP payload
475 '-s', str(net_utils.get_ping_payload_size(mtu, 4))
476 ]
477 cmd.append(ip_address)
478
479 def ping():
480 proc = subprocess.Popen(cmd,
481 stdout=subprocess.PIPE,
482 stderr=subprocess.PIPE)
483 proc.communicate()
484
485 return (proc.returncode == 0) == should_succeed
486
487 caller = test_utils.find_test_caller()
488 LOG.debug('%(caller)s begins to ping %(ip)s in %(timeout)s sec and the'
489 ' expected result is %(should_succeed)s', {
490 'caller': caller, 'ip': ip_address, 'timeout': timeout,
491 'should_succeed':
492 'reachable' if should_succeed else 'unreachable'
493 })
494 result = test_utils.call_until_true(ping, timeout, 1)
Manjeet Singh Bhatia8bbf8992019-03-04 11:59:57 -0800495
496 # To make sure ping_ip_address called by test works
497 # as expected.
498 self.assertTrue(result)
499
Chandan Kumarc125fd12017-11-15 19:41:01 +0530500 LOG.debug('%(caller)s finishes ping %(ip)s in %(timeout)s sec and the '
501 'ping result is %(result)s', {
502 'caller': caller, 'ip': ip_address, 'timeout': timeout,
503 'result': 'expected' if result else 'unexpected'
504 })
505 return result
Federico Ressie7417b72018-05-30 05:50:58 +0200506
507 def wait_for_server_status(self, server, status, client=None, **kwargs):
508 """Waits for a server to reach a given status.
509
510 :param server: mapping having schema {'id': <server_id>}
511 :param status: string status to wait for (es: 'ACTIVE')
512 :param clien: servers client (self.os_primary.servers_client as
513 default value)
514 """
515
516 client = client or self.os_primary.servers_client
517 waiters.wait_for_server_status(client, server['id'], status, **kwargs)
518
519 def wait_for_server_active(self, server, client=None):
520 """Waits for a server to reach active status.
521
522 :param server: mapping having schema {'id': <server_id>}
523 :param clien: servers client (self.os_primary.servers_client as
524 default value)
525 """
526 self.wait_for_server_status(
527 server, constants.SERVER_STATUS_ACTIVE, client)
Jakub Libosvar031fd5a2019-07-15 16:10:07 +0000528
Slawek Kaplonski2211eab2020-10-20 16:43:53 +0200529 def wait_for_guest_os_ready(self, server, client=None):
530 if not CONF.compute_feature_enabled.console_output:
531 LOG.debug('Console output not supported, cannot check if server '
Rodolfo Alonso Hernandezdff870b2020-11-06 08:41:44 +0000532 '%s is ready.', server['id'])
Slawek Kaplonski2211eab2020-10-20 16:43:53 +0200533 return
534
535 client = client or self.os_primary.servers_client
536
537 def system_booted():
538 console_output = client.get_console_output(server['id'])['output']
539 for line in console_output.split('\n'):
540 if 'login:' in line.lower():
541 return True
542 return False
543
544 try:
ccamposr1bd28ae2021-11-29 10:06:02 +0100545 utils.wait_until_true(system_booted, timeout=90, sleep=5)
Slawek Kaplonski2211eab2020-10-20 16:43:53 +0200546 except utils.WaitTimeout:
547 LOG.debug("No correct output in console of server %s found. "
548 "Guest operating system status can't be checked.",
549 server['id'])
550
Flavio Fernandesa1952c62020-10-02 06:39:08 -0400551 def check_servers_hostnames(self, servers, timeout=None, log_errors=True,
552 external_port=None):
Jakub Libosvar031fd5a2019-07-15 16:10:07 +0000553 """Compare hostnames of given servers with their names."""
554 try:
555 for server in servers:
556 kwargs = {}
nfridmand8969542020-06-02 14:59:09 +0300557 if timeout:
558 kwargs['timeout'] = timeout
Jakub Libosvar031fd5a2019-07-15 16:10:07 +0000559 try:
Flavio Fernandesa1952c62020-10-02 06:39:08 -0400560 kwargs['port'] = external_port or (
Slawek Kaplonskic4e963e2019-09-11 22:55:34 +0200561 server['port_forwarding_tcp']['external_port'])
Jakub Libosvar031fd5a2019-07-15 16:10:07 +0000562 except KeyError:
563 pass
564 ssh_client = ssh.Client(
565 self.fip['floating_ip_address'],
566 CONF.validation.image_ssh_user,
567 pkey=self.keypair['private_key'],
568 **kwargs)
569 self.assertIn(server['name'],
Rodolfo Alonso Hernandezaf394dd2020-11-12 14:26:13 +0000570 ssh_client.get_hostname())
Eduardo Olivares46fa4242022-04-18 12:47:43 +0200571 except SSH_EXC_TUPLE as ssh_e:
Jakub Libosvar031fd5a2019-07-15 16:10:07 +0000572 LOG.debug(ssh_e)
573 if log_errors:
574 self._log_console_output(servers)
Slawek Kaplonski8cccfe02020-09-29 22:34:09 +0200575 self._log_local_network_status()
Jakub Libosvar031fd5a2019-07-15 16:10:07 +0000576 raise
577 except AssertionError as assert_e:
578 LOG.debug(assert_e)
579 if log_errors:
580 self._log_console_output(servers)
Slawek Kaplonski8cccfe02020-09-29 22:34:09 +0200581 self._log_local_network_status()
Jakub Libosvar031fd5a2019-07-15 16:10:07 +0000582 raise
Slawek Kaplonskic4e963e2019-09-11 22:55:34 +0200583
Slawek Kaplonskifd4141f2020-03-14 14:34:00 +0100584 def ensure_nc_listen(self, ssh_client, port, protocol, echo_msg=None,
585 servers=None):
586 """Ensure that nc server listening on the given TCP/UDP port is up.
587
588 Listener is created always on remote host.
589 """
590 def spawn_and_check_process():
591 self.nc_listen(ssh_client, port, protocol, echo_msg, servers)
592 return utils.process_is_running(ssh_client, "nc")
593
594 utils.wait_until_true(spawn_and_check_process)
595
596 def nc_listen(self, ssh_client, port, protocol, echo_msg=None,
597 servers=None):
Slawek Kaplonskic4e963e2019-09-11 22:55:34 +0200598 """Create nc server listening on the given TCP/UDP port.
599
600 Listener is created always on remote host.
601 """
Slawek Kaplonskic4e963e2019-09-11 22:55:34 +0200602 try:
Slawek Kaplonskiaf83e832020-02-05 12:11:54 +0100603 return ssh_client.execute_script(
604 get_ncat_server_cmd(port, protocol, echo_msg),
Flavio Fernandesb056ac22020-07-01 14:57:13 -0400605 become_root=True, combine_stderr=True)
Eduardo Olivares46fa4242022-04-18 12:47:43 +0200606 except SSH_EXC_TUPLE as ssh_e:
Slawek Kaplonskic4e963e2019-09-11 22:55:34 +0200607 LOG.debug(ssh_e)
Slawek Kaplonskifd4141f2020-03-14 14:34:00 +0100608 self._log_console_output(servers)
Slawek Kaplonski8cccfe02020-09-29 22:34:09 +0200609 self._log_local_network_status()
Slawek Kaplonskic4e963e2019-09-11 22:55:34 +0200610 raise
611
612 def nc_client(self, ip_address, port, protocol):
613 """Check connectivity to TCP/UDP port at host via nc.
614
615 Client is always executed locally on host where tests are executed.
616 """
Slawek Kaplonskiaf83e832020-02-05 12:11:54 +0100617 cmd = get_ncat_client_cmd(ip_address, port, protocol)
Slawek Kaplonskic4e963e2019-09-11 22:55:34 +0200618 result = shell.execute_local_command(cmd)
619 self.assertEqual(0, result.exit_status)
620 return result.stdout
elajkat1f275e42021-10-15 12:47:55 +0200621
622 def _ensure_public_router(self, client=None, tenant_id=None):
623 """Retrieve a router for the given tenant id.
624
625 If a public router has been configured, it will be returned.
626
627 If a public router has not been configured, but a public
628 network has, a tenant router will be created and returned that
629 routes traffic to the public network.
630 """
631 if not client:
632 client = self.client
633 if not tenant_id:
634 tenant_id = client.tenant_id
635 router_id = CONF.network.public_router_id
636 network_id = CONF.network.public_network_id
637 if router_id:
638 body = client.show_router(router_id)
639 return body['router']
640 elif network_id:
641 router = self.create_router_by_client()
642 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
643 client.delete_router, router['id'])
644 kwargs = {'external_gateway_info': dict(network_id=network_id)}
645 router = client.update_router(router['id'], **kwargs)['router']
646 return router
647 else:
648 raise Exception("Neither of 'public_router_id' or "
649 "'public_network_id' has been defined.")
650
651 def _update_router_admin_state(self, router, admin_state_up):
652 kwargs = dict(admin_state_up=admin_state_up)
653 router = self.client.update_router(
654 router['id'], **kwargs)['router']
655 self.assertEqual(admin_state_up, router['admin_state_up'])