blob: e78473d061c04ebb54baecfbba3142b281ed88bd [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.
Rodolfo Alonso Hernandez13546b82022-05-12 23:56:47 +000015
Slawek Kaplonskiaf83e832020-02-05 12:11:54 +010016import 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
Rodolfo Alonso Hernandez13546b82022-05-12 23:56:47 +000024from packaging import version as packaging_version
Slawek Kaplonski8cccfe02020-09-29 22:34:09 +020025from paramiko import ssh_exception as ssh_exc
Genadi Chereshnya6d10c6e2017-07-05 11:34:20 +030026from tempest.common.utils import net_utils
Itzik Browne67ebb52016-05-15 05:34:41 +000027from tempest.common import waiters
Itzik Browne67ebb52016-05-15 05:34:41 +000028from tempest.lib.common.utils import data_utils
YAMAMOTO Takashi25935722017-01-23 15:34:11 +090029from tempest.lib.common.utils import test_utils
Jakub Libosvarc0c2f1d2017-01-31 12:12:21 -050030from tempest.lib import exceptions as lib_exc
Itzik Browne67ebb52016-05-15 05:34:41 +000031
Chandan Kumar667d3d32017-09-22 12:24:06 +053032from neutron_tempest_plugin.api import base as base_api
Rodolfo Alonso Hernandez4849f002020-01-16 16:01:10 +000033from neutron_tempest_plugin.common import ip as ip_utils
Slawek Kaplonskic4e963e2019-09-11 22:55:34 +020034from neutron_tempest_plugin.common import shell
Chandan Kumar667d3d32017-09-22 12:24:06 +053035from neutron_tempest_plugin.common import ssh
Slawek Kaplonskifd4141f2020-03-14 14:34:00 +010036from neutron_tempest_plugin.common import utils
Chandan Kumar667d3d32017-09-22 12:24:06 +053037from neutron_tempest_plugin import config
Slawek Kaplonskiaf83e832020-02-05 12:11:54 +010038from neutron_tempest_plugin import exceptions
Chandan Kumar667d3d32017-09-22 12:24:06 +053039from neutron_tempest_plugin.scenario import constants
Itzik Browne67ebb52016-05-15 05:34:41 +000040
41CONF = config.CONF
Alex Stafeyevc4d9c352016-12-12 04:13:33 -050042LOG = log.getLogger(__name__)
Eduardo Olivares46fa4242022-04-18 12:47:43 +020043SSH_EXC_TUPLE = (lib_exc.SSHTimeout,
44 ssh_exc.AuthenticationException,
45 ssh_exc.NoValidConnectionsError,
46 ConnectionResetError)
Alex Stafeyevc4d9c352016-12-12 04:13:33 -050047
Itzik Browne67ebb52016-05-15 05:34:41 +000048
Slawek Kaplonskiaf83e832020-02-05 12:11:54 +010049def get_ncat_version(ssh_client=None):
50 cmd = "ncat --version 2>&1"
51 try:
52 version_result = shell.execute(cmd, ssh_client=ssh_client).stdout
53 except exceptions.ShellCommandFailed:
54 m = None
55 else:
56 m = re.match(r"Ncat: Version ([\d.]+) *.", version_result)
57 # NOTE(slaweq): by default lets assume we have ncat 7.60 which is in Ubuntu
58 # 18.04 which is used on u/s gates
Rodolfo Alonso Hernandez13546b82022-05-12 23:56:47 +000059 return packaging_version.Version(m.group(1) if m else '7.60')
Slawek Kaplonskiaf83e832020-02-05 12:11:54 +010060
61
Slawek Kaplonskifd4141f2020-03-14 14:34:00 +010062def get_ncat_server_cmd(port, protocol, msg=None):
Slawek Kaplonskiaf83e832020-02-05 12:11:54 +010063 udp = ''
64 if protocol.lower() == neutron_lib_constants.PROTO_NAME_UDP:
65 udp = '-u'
66 cmd = "nc %(udp)s -p %(port)s -lk " % {
67 'udp': udp, 'port': port}
Slawek Kaplonskifd4141f2020-03-14 14:34:00 +010068 if msg:
69 if CONF.neutron_plugin_options.default_image_is_advanced:
Flavio Fernandesb056ac22020-07-01 14:57:13 -040070 cmd += "-c 'echo %s' " % msg
Slawek Kaplonskifd4141f2020-03-14 14:34:00 +010071 else:
Flavio Fernandesb056ac22020-07-01 14:57:13 -040072 cmd += "-e echo %s " % msg
73 cmd += "< /dev/zero &{0}sleep 0.1{0}".format('\n')
Slawek Kaplonskiaf83e832020-02-05 12:11:54 +010074 return cmd
75
76
Slawek Kaplonski663bbc22023-04-05 16:30:20 +020077def get_ncat_client_cmd(ip_address, port, protocol, ssh_client=None):
Slawek Kaplonskiaf83e832020-02-05 12:11:54 +010078 cmd = 'echo "knock knock" | nc '
Slawek Kaplonskifddcd182023-07-04 11:28:06 +020079 ncat_version = get_ncat_version(ssh_client=ssh_client)
80 if ncat_version > packaging_version.Version('7.60'):
81 cmd += '-d 1 '
Slawek Kaplonskidd5b0e62023-05-31 13:57:25 +020082 if protocol.lower() == neutron_lib_constants.PROTO_NAME_UDP:
83 cmd += '-u '
Slawek Kaplonskidd5b0e62023-05-31 13:57:25 +020084 if ncat_version > packaging_version.Version('7.60'):
85 cmd += '-z '
86 cmd += '-w 1 %(host)s %(port)s' % {'host': ip_address, 'port': port}
Slawek Kaplonskiaf83e832020-02-05 12:11:54 +010087 return cmd
88
89
Itzik Browne67ebb52016-05-15 05:34:41 +000090class BaseTempestTestCase(base_api.BaseNetworkTest):
Itzik Browne67ebb52016-05-15 05:34:41 +000091
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +000092 def create_server(self, flavor_ref, image_ref, key_name, networks,
Roee Agiman6a0a18a2017-11-16 11:51:56 +020093 **kwargs):
Itzik Brownbac51dc2016-10-31 12:25:04 +000094 """Create a server using tempest lib
Brian Haleyaee61ac2018-10-09 20:00:27 -040095
Itzik Brownbac51dc2016-10-31 12:25:04 +000096 All the parameters are the ones used in Compute API
Roee Agiman6a0a18a2017-11-16 11:51:56 +020097 * - Kwargs that require admin privileges
Itzik Brownbac51dc2016-10-31 12:25:04 +000098
99 Args:
100 flavor_ref(str): The flavor of the server to be provisioned.
101 image_ref(str): The image of the server to be provisioned.
102 key_name(str): SSH key to to be used to connect to the
103 provisioned server.
104 networks(list): List of dictionaries where each represent
105 an interface to be attached to the server. For network
106 it should be {'uuid': network_uuid} and for port it should
107 be {'port': port_uuid}
Roee Agiman6a0a18a2017-11-16 11:51:56 +0200108 kwargs:
Itzik Brownbac51dc2016-10-31 12:25:04 +0000109 name(str): Name of the server to be provisioned.
110 security_groups(list): List of dictionaries where
111 the keys is 'name' and the value is the name of
112 the security group. If it's not passed the default
113 security group will be used.
Roee Agiman6a0a18a2017-11-16 11:51:56 +0200114 availability_zone(str)*: The availability zone that
115 the instance will be in.
116 You can request a specific az without actually creating one,
117 Just pass 'X:Y' where X is the default availability
118 zone, and Y is the compute host name.
Itzik Brownbac51dc2016-10-31 12:25:04 +0000119 """
120
Jakub Libosvarffd9b912017-11-16 09:54:14 +0000121 kwargs.setdefault('name', data_utils.rand_name('server-test'))
Itzik Brownbac51dc2016-10-31 12:25:04 +0000122
Jakub Libosvarffd9b912017-11-16 09:54:14 +0000123 # We cannot use setdefault() here because caller could have passed
124 # security_groups=None and we don't want to pass None to
125 # client.create_server()
126 if not kwargs.get('security_groups'):
127 kwargs['security_groups'] = [{'name': 'default'}]
Roee Agiman6a0a18a2017-11-16 11:51:56 +0200128
Slawek Kaplonski2211eab2020-10-20 16:43:53 +0200129 client = kwargs.pop('client', None)
130 if client is None:
131 client = self.os_primary.servers_client
132 if kwargs.get('availability_zone'):
133 client = self.os_admin.servers_client
Roee Agiman6a0a18a2017-11-16 11:51:56 +0200134
Jakub Libosvarffd9b912017-11-16 09:54:14 +0000135 server = client.create_server(
136 flavorRef=flavor_ref,
137 imageRef=image_ref,
138 key_name=key_name,
139 networks=networks,
140 **kwargs)
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +0000141
142 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
Roee Agiman6a0a18a2017-11-16 11:51:56 +0200143 waiters.wait_for_server_termination,
144 client,
145 server['server']['id'])
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +0000146 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
Roee Agiman6a0a18a2017-11-16 11:51:56 +0200147 client.delete_server,
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +0000148 server['server']['id'])
Slawek Kaplonski2211eab2020-10-20 16:43:53 +0200149
150 self.wait_for_server_active(server['server'], client=client)
151 self.wait_for_guest_os_ready(server['server'], client=client)
152
Itzik Browne67ebb52016-05-15 05:34:41 +0000153 return server
154
155 @classmethod
Jens Harbott54357632017-11-21 11:47:06 +0000156 def create_secgroup_rules(cls, rule_list, secgroup_id=None,
157 client=None):
Roee Agiman6a0a18a2017-11-16 11:51:56 +0200158 client = client or cls.os_primary.network_client
Itzik Browne67ebb52016-05-15 05:34:41 +0000159 if not secgroup_id:
160 sgs = client.list_security_groups()['security_groups']
161 for sg in sgs:
162 if sg['name'] == constants.DEFAULT_SECURITY_GROUP:
163 secgroup_id = sg['id']
164 break
Hang Yange6e0ccf2021-02-26 15:07:05 -0600165 resp = []
Itzik Brown1ef813a2016-06-06 12:56:21 +0000166 for rule in rule_list:
167 direction = rule.pop('direction')
Hang Yange6e0ccf2021-02-26 15:07:05 -0600168 resp.append(client.create_security_group_rule(
169 direction=direction,
170 security_group_id=secgroup_id,
171 **rule)['security_group_rule'])
172 return resp
Itzik Brown1ef813a2016-06-06 12:56:21 +0000173
174 @classmethod
Roee Agiman6a0a18a2017-11-16 11:51:56 +0200175 def create_loginable_secgroup_rule(cls, secgroup_id=None,
176 client=None):
Itzik Brown1ef813a2016-06-06 12:56:21 +0000177 """This rule is intended to permit inbound ssh
178
179 Allowing ssh traffic traffic from all sources, so no group_id is
180 provided.
181 Setting a group_id would only permit traffic from ports
182 belonging to the same security group.
183 """
Federico Ressi4c590d72018-10-10 14:01:08 +0200184 return cls.create_security_group_rule(
185 security_group_id=secgroup_id,
186 client=client,
187 protocol=neutron_lib_constants.PROTO_NAME_TCP,
188 direction=neutron_lib_constants.INGRESS_DIRECTION,
189 port_range_min=22,
190 port_range_max=22)
Itzik Browne67ebb52016-05-15 05:34:41 +0000191
192 @classmethod
Slawek Kaplonski83979b92022-12-15 14:15:12 +0100193 def create_ingress_metadata_secgroup_rule(cls, secgroup_id=None):
194 """This rule is intended to permit inbound metadata traffic
195
196 Allowing ingress traffic from metadata server, required only for
197 stateless security groups.
198 """
Maor Blaustein0ea053c2023-02-16 17:23:53 +0200199 # NOTE(slaweq): in case of stateless security groups, there is no
200 # "related" or "established" traffic matching at all so even if
201 # egress traffic to 169.254.169.254 is allowed by default SG, we
202 # need to explicitly allow ingress traffic from the metadata server
203 # to be able to receive responses in the guest vm
204 cls.create_security_group_rule(
205 security_group_id=secgroup_id,
206 direction=neutron_lib_constants.INGRESS_DIRECTION,
207 protocol=neutron_lib_constants.PROTO_NAME_TCP,
208 remote_ip_prefix='169.254.169.254/32',
209 description='metadata out'
210 )
Slawek Kaplonski83979b92022-12-15 14:15:12 +0100211
212 @classmethod
Roee Agiman6a0a18a2017-11-16 11:51:56 +0200213 def create_pingable_secgroup_rule(cls, secgroup_id=None,
214 client=None):
Federico Ressi4c590d72018-10-10 14:01:08 +0200215 """This rule is intended to permit inbound ping
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900216
Federico Ressi4c590d72018-10-10 14:01:08 +0200217 """
218 return cls.create_security_group_rule(
219 security_group_id=secgroup_id, client=client,
220 protocol=neutron_lib_constants.PROTO_NAME_ICMP,
221 direction=neutron_lib_constants.INGRESS_DIRECTION)
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900222
223 @classmethod
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300224 def create_router_by_client(cls, is_admin=False, **kwargs):
225 kwargs.update({'router_name': data_utils.rand_name('router'),
226 'admin_state_up': True,
227 'external_network_id': CONF.network.public_network_id})
228 if not is_admin:
229 router = cls.create_router(**kwargs)
230 else:
231 router = cls.create_admin_router(**kwargs)
Alex Stafeyevc4d9c352016-12-12 04:13:33 -0500232 LOG.debug("Created router %s", router['name'])
Slawek Kaplonskiedf3cba2021-04-21 10:34:02 +0200233 cls._wait_for_router_ha_active(router['id'])
Itzik Browne67ebb52016-05-15 05:34:41 +0000234 return router
235
ccamposre6b10042021-02-12 12:26:08 +0100236 @classmethod
Slawek Kaplonskiedf3cba2021-04-21 10:34:02 +0200237 def _wait_for_router_ha_active(cls, router_id):
238 router = cls.os_admin.network_client.show_router(router_id)['router']
Rodolfo Alonso Hernandez92865882024-03-04 20:30:40 +0000239 if not router.get('ha') or cls.is_driver_ovn:
Slawek Kaplonskiedf3cba2021-04-21 10:34:02 +0200240 return
241
242 def _router_active_on_l3_agent():
243 agents = cls.os_admin.network_client.list_l3_agents_hosting_router(
244 router_id)['agents']
245 return "active" in [agent['ha_state'] for agent in agents]
246
247 error_msg = (
248 "Router %s is not active on any of the L3 agents" % router_id)
Slawek Kaplonskibb1532a2021-06-17 13:45:56 +0000249 # NOTE(slaweq): timeout here should be lower for sure, but due to
250 # the bug https://launchpad.net/bugs/1923633 let's wait even 10
251 # minutes until router will be active on some of the L3 agents
252 utils.wait_until_true(_router_active_on_l3_agent,
253 timeout=600, sleep=5,
254 exception=lib_exc.TimeoutException(error_msg))
Slawek Kaplonskiedf3cba2021-04-21 10:34:02 +0200255
256 @classmethod
ccamposre6b10042021-02-12 12:26:08 +0100257 def skip_if_no_extension_enabled_in_l3_agents(cls, extension):
258 l3_agents = cls.os_admin.network_client.list_agents(
259 binary='neutron-l3-agent')['agents']
260 if not l3_agents:
261 # the tests should not be skipped when neutron-l3-agent does not
262 # exist (this validation doesn't apply to the setups like
263 # e.g. ML2/OVN)
264 return
265 for agent in l3_agents:
266 if extension in agent['configurations'].get('extensions', []):
267 return
268 raise cls.skipTest("No L3 agent with '%s' extension enabled found." %
269 extension)
270
Federico Ressibf877c82018-08-22 08:36:37 +0200271 @removals.remove(version='Stein',
272 message="Please use create_floatingip method instead of "
273 "create_and_associate_floatingip.")
Roee Agiman6a0a18a2017-11-16 11:51:56 +0200274 def create_and_associate_floatingip(self, port_id, client=None):
275 client = client or self.os_primary.network_client
Federico Ressibf877c82018-08-22 08:36:37 +0200276 return self.create_floatingip(port_id=port_id, client=client)
Itzik Browne67ebb52016-05-15 05:34:41 +0000277
Hongbin Lu965b03d2018-04-25 22:32:30 +0000278 def create_interface(cls, server_id, port_id, client=None):
279 client = client or cls.os_primary.interfaces_client
280 body = client.create_interface(server_id, port_id=port_id)
281 return body['interfaceAttachment']
282
283 def delete_interface(cls, server_id, port_id, client=None):
284 client = client or cls.os_primary.interfaces_client
285 client.delete_interface(server_id, port_id=port_id)
286
Brian Haleyd11f4ec2019-08-13 12:09:57 -0400287 def setup_network_and_server(self, router=None, server_name=None,
Slawek Kaplonskiad4ddcb2024-03-14 11:15:59 +0100288 network=None, use_stateless_sg=False,
Frode Nordahl1bb8e622023-10-16 15:16:34 +0200289 create_fip=True, router_client=None,
Slawek Kaplonskiad4ddcb2024-03-14 11:15:59 +0100290 **kwargs):
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300291 """Create network resources and a server.
Itzik Brownbac51dc2016-10-31 12:25:04 +0000292
293 Creating a network, subnet, router, keypair, security group
294 and a server.
295 """
Assaf Mullerd54ae6c2018-05-31 11:38:00 -0400296 self.network = network or self.create_network()
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +0000297 LOG.debug("Created network %s", self.network['name'])
298 self.subnet = self.create_subnet(self.network)
299 LOG.debug("Created subnet %s", self.subnet['id'])
Itzik Brown1ef813a2016-06-06 12:56:21 +0000300
Slawek Kaplonskiad4ddcb2024-03-14 11:15:59 +0100301 sg_args = {
302 'name': data_utils.rand_name('secgroup')
303 }
304 if use_stateless_sg:
305 sg_args['stateful'] = False
Brian Haleyf86ac2e2017-06-21 10:43:50 -0400306 secgroup = self.os_primary.network_client.create_security_group(
Slawek Kaplonskiad4ddcb2024-03-14 11:15:59 +0100307 **sg_args)
Alex Stafeyevc4d9c352016-12-12 04:13:33 -0500308 LOG.debug("Created security group %s",
309 secgroup['security_group']['name'])
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +0000310 self.security_groups.append(secgroup['security_group'])
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300311 if not router:
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +0000312 router = self.create_router_by_client(**kwargs)
Frode Nordahl1bb8e622023-10-16 15:16:34 +0200313 self.create_router_interface(router['id'], self.subnet['id'],
314 client=router_client)
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +0000315 self.keypair = self.create_keypair()
316 self.create_loginable_secgroup_rule(
Itzik Brownbac51dc2016-10-31 12:25:04 +0000317 secgroup_id=secgroup['security_group']['id'])
Slawek Kaplonskiad4ddcb2024-03-14 11:15:59 +0100318 if use_stateless_sg:
319 self.create_ingress_metadata_secgroup_rule(
320 secgroup_id=secgroup['security_group']['id'])
Assaf Muller92fdc782018-05-31 10:32:47 -0400321
322 server_kwargs = {
323 'flavor_ref': CONF.compute.flavor_ref,
324 'image_ref': CONF.compute.image_ref,
325 'key_name': self.keypair['name'],
326 'networks': [{'uuid': self.network['id']}],
327 'security_groups': [{'name': secgroup['security_group']['name']}],
328 }
329 if server_name is not None:
330 server_kwargs['name'] = server_name
331
332 self.server = self.create_server(**server_kwargs)
Jakub Libosvar1345d9d2017-06-09 13:59:05 +0000333 self.port = self.client.list_ports(network_id=self.network['id'],
334 device_id=self.server[
335 'server']['id'])['ports'][0]
Frode Nordahl1bb8e622023-10-16 15:16:34 +0200336
337 if create_fip:
338 self.fip = self.create_floatingip(port=self.port)
Jakub Libosvarc0c2f1d2017-01-31 12:12:21 -0500339
Ihar Hrachyshka2d663552024-08-26 12:47:24 -0400340 def check_connectivity(self, host=None, ssh_user=None, ssh_key=None,
Bence Romsics2abbc922020-09-30 16:10:07 +0200341 servers=None, ssh_timeout=None, ssh_client=None):
342 # Either ssh_client or ssh_user+ssh_key is mandatory.
343 if ssh_client is None:
344 ssh_client = ssh.Client(host, ssh_user,
345 pkey=ssh_key, timeout=ssh_timeout)
Jakub Libosvarc0c2f1d2017-01-31 12:12:21 -0500346 try:
347 ssh_client.test_connection_auth()
Eduardo Olivares46fa4242022-04-18 12:47:43 +0200348 except SSH_EXC_TUPLE as ssh_e:
Jakub Libosvarc0c2f1d2017-01-31 12:12:21 -0500349 LOG.debug(ssh_e)
350 self._log_console_output(servers)
Rodolfo Alonso Hernandez4849f002020-01-16 16:01:10 +0000351 self._log_local_network_status()
Jakub Libosvarc0c2f1d2017-01-31 12:12:21 -0500352 raise
353
354 def _log_console_output(self, servers=None):
355 if not CONF.compute_feature_enabled.console_output:
356 LOG.debug('Console output not supported, cannot log')
357 return
358 if not servers:
Brian Haleyf86ac2e2017-06-21 10:43:50 -0400359 servers = self.os_primary.servers_client.list_servers()
Jakub Libosvarc0c2f1d2017-01-31 12:12:21 -0500360 servers = servers['servers']
361 for server in servers:
Slawek Kaplonskicff79232020-03-03 14:12:18 +0100362 # NOTE(slaweq): sometimes servers are passed in dictionary with
363 # "server" key as first level key and in other cases it may be that
364 # it is just the "inner" dict without "server" key. Lets try to
365 # handle both cases
366 server = server.get("server") or server
Jakub Libosvarc0c2f1d2017-01-31 12:12:21 -0500367 try:
368 console_output = (
Brian Haleyf86ac2e2017-06-21 10:43:50 -0400369 self.os_primary.servers_client.get_console_output(
Jakub Libosvarc0c2f1d2017-01-31 12:12:21 -0500370 server['id'])['output'])
371 LOG.debug('Console output for %s\nbody=\n%s',
372 server['id'], console_output)
373 except lib_exc.NotFound:
374 LOG.debug("Server %s disappeared(deleted) while looking "
375 "for the console log", server['id'])
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900376
Rodolfo Alonso Hernandez4849f002020-01-16 16:01:10 +0000377 def _log_local_network_status(self):
Slawek Kaplonski8033af72020-05-05 12:01:37 +0200378 self._log_ns_network_status()
379 for ns_name in ip_utils.IPCommand().list_namespaces():
380 self._log_ns_network_status(ns_name=ns_name)
381
382 def _log_ns_network_status(self, ns_name=None):
Rodolfo Alonso Hernandez9817d4f2020-11-17 08:50:50 +0000383 try:
384 local_ips = ip_utils.IPCommand(namespace=ns_name).list_addresses()
385 local_routes = ip_utils.IPCommand(namespace=ns_name).list_routes()
Rodolfo Alonso Hernandezc134ea92021-04-14 15:15:01 +0000386 arp_table = ip_utils.arp_table(namespace=ns_name)
aarefievbfb3b782026-01-19 09:11:23 -0800387 iptables = None
388 try:
389 iptables = ip_utils.list_iptables(namespace=ns_name)
390 except Exception:
391 LOG.exception('Failed to fetch iptables rules.')
Rodolfo Alonso Hernandezc134ea92021-04-14 15:15:01 +0000392 lsockets = ip_utils.list_listening_sockets(namespace=ns_name)
Rodolfo Alonso Hernandez9817d4f2020-11-17 08:50:50 +0000393 except exceptions.ShellCommandFailed:
394 LOG.debug('Namespace %s has been deleted synchronously during the '
395 'host network collection process', ns_name)
396 return
397
Slawek Kaplonski8033af72020-05-05 12:01:37 +0200398 LOG.debug('Namespace %s; IP Addresses:\n%s',
399 ns_name, '\n'.join(str(r) for r in local_ips))
Slawek Kaplonski8033af72020-05-05 12:01:37 +0200400 LOG.debug('Namespace %s; Local routes:\n%s',
401 ns_name, '\n'.join(str(r) for r in local_routes))
Slawek Kaplonski8033af72020-05-05 12:01:37 +0200402 LOG.debug('Namespace %s; Local ARP table:\n%s',
403 ns_name, '\n'.join(str(r) for r in arp_table))
Rodolfo Alonso Hernandezc134ea92021-04-14 15:15:01 +0000404 LOG.debug('Namespace %s; Local iptables:\n%s', ns_name, iptables)
405 LOG.debug('Namespace %s; Listening sockets:\n%s', ns_name, lsockets)
Rodolfo Alonso Hernandez4849f002020-01-16 16:01:10 +0000406
LIU Yulong68ab2452019-05-18 10:19:49 +0800407 def _check_remote_connectivity(self, source, dest, count,
408 should_succeed=True,
Assaf Muller92fdc782018-05-31 10:32:47 -0400409 nic=None, mtu=None, fragmentation=True,
Roman Safronov12663cf2020-07-27 13:11:07 +0300410 timeout=None, pattern=None,
Nurmatov Mamatisa1b1c9d32021-12-27 15:37:03 +0300411 forbid_packet_loss=False,
412 check_response_ip=True):
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900413 """check ping server via source ssh connection
414
415 :param source: RemoteClient: an ssh connection from which to ping
416 :param dest: and IP to ping against
LIU Yulong68ab2452019-05-18 10:19:49 +0800417 :param count: Number of ping packet(s) to send
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900418 :param should_succeed: boolean should ping succeed or not
419 :param nic: specific network interface to ping from
Genadi Chereshnya6d10c6e2017-07-05 11:34:20 +0300420 :param mtu: mtu size for the packet to be sent
421 :param fragmentation: Flag for packet fragmentation
LIU Yulong68ab2452019-05-18 10:19:49 +0800422 :param timeout: Timeout for all ping packet(s) to succeed
Eduardo Olivaresf2b60542020-01-30 09:37:22 +0100423 :param pattern: hex digits included in ICMP messages
Roman Safronov12663cf2020-07-27 13:11:07 +0300424 :param forbid_packet_loss: forbid or allow some lost packets
Nurmatov Mamatisa1b1c9d32021-12-27 15:37:03 +0300425 :param check_response_ip: check response ip
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900426 :returns: boolean -- should_succeed == ping
427 :returns: ping is false if ping failed
428 """
LIU Yulong68ab2452019-05-18 10:19:49 +0800429 def ping_host(source, host, count,
Genadi Chereshnya6d10c6e2017-07-05 11:34:20 +0300430 size=CONF.validation.ping_size, nic=None, mtu=None,
Eduardo Olivaresf2b60542020-01-30 09:37:22 +0100431 fragmentation=True, pattern=None):
Assaf Muller92fdc782018-05-31 10:32:47 -0400432 IP_VERSION_4 = neutron_lib_constants.IP_VERSION_4
433 IP_VERSION_6 = neutron_lib_constants.IP_VERSION_6
434
435 # Use 'ping6' for IPv6 addresses, 'ping' for IPv4 and hostnames
436 ip_version = (
437 IP_VERSION_6 if netaddr.valid_ipv6(host) else IP_VERSION_4)
438 cmd = (
439 'ping6' if ip_version == IP_VERSION_6 else 'ping')
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900440 if nic:
441 cmd = 'sudo {cmd} -I {nic}'.format(cmd=cmd, nic=nic)
Genadi Chereshnya6d10c6e2017-07-05 11:34:20 +0300442 if mtu:
443 if not fragmentation:
444 cmd += ' -M do'
445 size = str(net_utils.get_ping_payload_size(
Assaf Muller92fdc782018-05-31 10:32:47 -0400446 mtu=mtu, ip_version=ip_version))
Eduardo Olivaresf2b60542020-01-30 09:37:22 +0100447 if pattern:
448 cmd += ' -p {pattern}'.format(pattern=pattern)
Maciej Józefczyk3c324e02020-03-16 10:52:08 +0000449 cmd += ' -c{0} -W{0} -s{1} {2}'.format(count, size, host)
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900450 return source.exec_command(cmd)
451
452 def ping_remote():
453 try:
LIU Yulong68ab2452019-05-18 10:19:49 +0800454 result = ping_host(source, dest, count, nic=nic, mtu=mtu,
Eduardo Olivaresf2b60542020-01-30 09:37:22 +0100455 fragmentation=fragmentation,
456 pattern=pattern)
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900457
458 except lib_exc.SSHExecCommandFailed:
459 LOG.warning('Failed to ping IP: %s via a ssh connection '
460 'from: %s.', dest, source.host)
461 return not should_succeed
462 LOG.debug('ping result: %s', result)
Assaf Muller92fdc782018-05-31 10:32:47 -0400463
Roman Safronov12663cf2020-07-27 13:11:07 +0300464 if forbid_packet_loss and ' 0% packet loss' not in result:
465 LOG.debug('Packet loss detected')
466 return not should_succeed
467
Nurmatov Mamatisa1b1c9d32021-12-27 15:37:03 +0300468 if (check_response_ip and
469 validators.validate_ip_address(dest) is None):
Assaf Muller92fdc782018-05-31 10:32:47 -0400470 # Assert that the return traffic was from the correct
471 # source address.
472 from_source = 'from %s' % dest
473 self.assertIn(from_source, result)
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900474 return should_succeed
475
Assaf Muller92fdc782018-05-31 10:32:47 -0400476 return test_utils.call_until_true(
477 ping_remote, timeout or CONF.validation.ping_timeout, 1)
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900478
479 def check_remote_connectivity(self, source, dest, should_succeed=True,
Slawek Kaplonskib07251f2018-05-16 12:21:50 +0200480 nic=None, mtu=None, fragmentation=True,
LIU Yulong68ab2452019-05-18 10:19:49 +0800481 servers=None, timeout=None,
Eduardo Olivaresf2b60542020-01-30 09:37:22 +0100482 ping_count=CONF.validation.ping_count,
Nurmatov Mamatisa1b1c9d32021-12-27 15:37:03 +0300483 pattern=None, forbid_packet_loss=False,
484 check_response_ip=True):
Slawek Kaplonskib07251f2018-05-16 12:21:50 +0200485 try:
486 self.assertTrue(self._check_remote_connectivity(
LIU Yulong68ab2452019-05-18 10:19:49 +0800487 source, dest, ping_count, should_succeed, nic, mtu,
488 fragmentation,
Roman Safronov12663cf2020-07-27 13:11:07 +0300489 timeout=timeout, pattern=pattern,
Nurmatov Mamatisa1b1c9d32021-12-27 15:37:03 +0300490 forbid_packet_loss=forbid_packet_loss,
491 check_response_ip=check_response_ip))
Eduardo Olivares46fa4242022-04-18 12:47:43 +0200492 except SSH_EXC_TUPLE as ssh_e:
Slawek Kaplonskib07251f2018-05-16 12:21:50 +0200493 LOG.debug(ssh_e)
494 self._log_console_output(servers)
Slawek Kaplonski8cccfe02020-09-29 22:34:09 +0200495 self._log_local_network_status()
Slawek Kaplonskib07251f2018-05-16 12:21:50 +0200496 raise
497 except AssertionError:
498 self._log_console_output(servers)
Slawek Kaplonski8cccfe02020-09-29 22:34:09 +0200499 self._log_local_network_status()
Slawek Kaplonskib07251f2018-05-16 12:21:50 +0200500 raise
Chandan Kumarc125fd12017-11-15 19:41:01 +0530501
502 def ping_ip_address(self, ip_address, should_succeed=True,
503 ping_timeout=None, mtu=None):
504 # the code is taken from tempest/scenario/manager.py in tempest git
505 timeout = ping_timeout or CONF.validation.ping_timeout
506 cmd = ['ping', '-c1', '-w1']
507
508 if mtu:
509 cmd += [
510 # don't fragment
511 '-M', 'do',
512 # ping receives just the size of ICMP payload
513 '-s', str(net_utils.get_ping_payload_size(mtu, 4))
514 ]
515 cmd.append(ip_address)
516
517 def ping():
518 proc = subprocess.Popen(cmd,
519 stdout=subprocess.PIPE,
520 stderr=subprocess.PIPE)
521 proc.communicate()
522
523 return (proc.returncode == 0) == should_succeed
524
525 caller = test_utils.find_test_caller()
526 LOG.debug('%(caller)s begins to ping %(ip)s in %(timeout)s sec and the'
527 ' expected result is %(should_succeed)s', {
528 'caller': caller, 'ip': ip_address, 'timeout': timeout,
529 'should_succeed':
530 'reachable' if should_succeed else 'unreachable'
531 })
532 result = test_utils.call_until_true(ping, timeout, 1)
Manjeet Singh Bhatia8bbf8992019-03-04 11:59:57 -0800533
534 # To make sure ping_ip_address called by test works
535 # as expected.
536 self.assertTrue(result)
537
Chandan Kumarc125fd12017-11-15 19:41:01 +0530538 LOG.debug('%(caller)s finishes ping %(ip)s in %(timeout)s sec and the '
539 'ping result is %(result)s', {
540 'caller': caller, 'ip': ip_address, 'timeout': timeout,
541 'result': 'expected' if result else 'unexpected'
542 })
543 return result
Federico Ressie7417b72018-05-30 05:50:58 +0200544
545 def wait_for_server_status(self, server, status, client=None, **kwargs):
546 """Waits for a server to reach a given status.
547
548 :param server: mapping having schema {'id': <server_id>}
549 :param status: string status to wait for (es: 'ACTIVE')
550 :param clien: servers client (self.os_primary.servers_client as
551 default value)
552 """
553
554 client = client or self.os_primary.servers_client
555 waiters.wait_for_server_status(client, server['id'], status, **kwargs)
556
557 def wait_for_server_active(self, server, client=None):
558 """Waits for a server to reach active status.
559
560 :param server: mapping having schema {'id': <server_id>}
561 :param clien: servers client (self.os_primary.servers_client as
562 default value)
563 """
564 self.wait_for_server_status(
565 server, constants.SERVER_STATUS_ACTIVE, client)
Jakub Libosvar031fd5a2019-07-15 16:10:07 +0000566
Slawek Kaplonski2211eab2020-10-20 16:43:53 +0200567 def wait_for_guest_os_ready(self, server, client=None):
568 if not CONF.compute_feature_enabled.console_output:
569 LOG.debug('Console output not supported, cannot check if server '
Rodolfo Alonso Hernandezdff870b2020-11-06 08:41:44 +0000570 '%s is ready.', server['id'])
Slawek Kaplonski2211eab2020-10-20 16:43:53 +0200571 return
572
573 client = client or self.os_primary.servers_client
574
575 def system_booted():
576 console_output = client.get_console_output(server['id'])['output']
577 for line in console_output.split('\n'):
578 if 'login:' in line.lower():
579 return True
580 return False
581
582 try:
ccamposr1bd28ae2021-11-29 10:06:02 +0100583 utils.wait_until_true(system_booted, timeout=90, sleep=5)
Slawek Kaplonski2211eab2020-10-20 16:43:53 +0200584 except utils.WaitTimeout:
585 LOG.debug("No correct output in console of server %s found. "
586 "Guest operating system status can't be checked.",
587 server['id'])
588
Flavio Fernandesa1952c62020-10-02 06:39:08 -0400589 def check_servers_hostnames(self, servers, timeout=None, log_errors=True,
590 external_port=None):
Jakub Libosvar031fd5a2019-07-15 16:10:07 +0000591 """Compare hostnames of given servers with their names."""
592 try:
593 for server in servers:
594 kwargs = {}
nfridmand8969542020-06-02 14:59:09 +0300595 if timeout:
596 kwargs['timeout'] = timeout
Jakub Libosvar031fd5a2019-07-15 16:10:07 +0000597 try:
Flavio Fernandesa1952c62020-10-02 06:39:08 -0400598 kwargs['port'] = external_port or (
Slawek Kaplonskic4e963e2019-09-11 22:55:34 +0200599 server['port_forwarding_tcp']['external_port'])
Jakub Libosvar031fd5a2019-07-15 16:10:07 +0000600 except KeyError:
601 pass
602 ssh_client = ssh.Client(
603 self.fip['floating_ip_address'],
604 CONF.validation.image_ssh_user,
605 pkey=self.keypair['private_key'],
606 **kwargs)
607 self.assertIn(server['name'],
Rodolfo Alonso Hernandezaf394dd2020-11-12 14:26:13 +0000608 ssh_client.get_hostname())
Eduardo Olivares46fa4242022-04-18 12:47:43 +0200609 except SSH_EXC_TUPLE as ssh_e:
Jakub Libosvar031fd5a2019-07-15 16:10:07 +0000610 LOG.debug(ssh_e)
611 if log_errors:
612 self._log_console_output(servers)
Slawek Kaplonski8cccfe02020-09-29 22:34:09 +0200613 self._log_local_network_status()
Jakub Libosvar031fd5a2019-07-15 16:10:07 +0000614 raise
615 except AssertionError as assert_e:
616 LOG.debug(assert_e)
617 if log_errors:
618 self._log_console_output(servers)
Slawek Kaplonski8cccfe02020-09-29 22:34:09 +0200619 self._log_local_network_status()
Jakub Libosvar031fd5a2019-07-15 16:10:07 +0000620 raise
Slawek Kaplonskic4e963e2019-09-11 22:55:34 +0200621
Slawek Kaplonskifd4141f2020-03-14 14:34:00 +0100622 def ensure_nc_listen(self, ssh_client, port, protocol, echo_msg=None,
623 servers=None):
624 """Ensure that nc server listening on the given TCP/UDP port is up.
625
626 Listener is created always on remote host.
627 """
628 def spawn_and_check_process():
629 self.nc_listen(ssh_client, port, protocol, echo_msg, servers)
630 return utils.process_is_running(ssh_client, "nc")
631
632 utils.wait_until_true(spawn_and_check_process)
633
634 def nc_listen(self, ssh_client, port, protocol, echo_msg=None,
635 servers=None):
Slawek Kaplonskic4e963e2019-09-11 22:55:34 +0200636 """Create nc server listening on the given TCP/UDP port.
637
638 Listener is created always on remote host.
639 """
Slawek Kaplonskic4e963e2019-09-11 22:55:34 +0200640 try:
Slawek Kaplonskiaf83e832020-02-05 12:11:54 +0100641 return ssh_client.execute_script(
642 get_ncat_server_cmd(port, protocol, echo_msg),
Flavio Fernandesb056ac22020-07-01 14:57:13 -0400643 become_root=True, combine_stderr=True)
Eduardo Olivares46fa4242022-04-18 12:47:43 +0200644 except SSH_EXC_TUPLE as ssh_e:
Slawek Kaplonskic4e963e2019-09-11 22:55:34 +0200645 LOG.debug(ssh_e)
Slawek Kaplonskifd4141f2020-03-14 14:34:00 +0100646 self._log_console_output(servers)
Slawek Kaplonski8cccfe02020-09-29 22:34:09 +0200647 self._log_local_network_status()
Slawek Kaplonskic4e963e2019-09-11 22:55:34 +0200648 raise
649
Slawek Kaplonskib327a632023-02-23 07:59:12 +0100650 def nc_client(self, ip_address, port, protocol, ssh_client=None):
Slawek Kaplonskic4e963e2019-09-11 22:55:34 +0200651 """Check connectivity to TCP/UDP port at host via nc.
652
Slawek Kaplonskib327a632023-02-23 07:59:12 +0100653 If ssh_client is not given, it is executed locally on host where tests
654 are executed. Otherwise ssh_client object is used to execute it.
Slawek Kaplonskic4e963e2019-09-11 22:55:34 +0200655 """
Slawek Kaplonski663bbc22023-04-05 16:30:20 +0200656 cmd = get_ncat_client_cmd(ip_address, port, protocol,
657 ssh_client=ssh_client)
Slawek Kaplonski03bd62e2023-06-02 10:54:05 +0200658 result = shell.execute(cmd, ssh_client=ssh_client, check=False)
Slawek Kaplonskic4e963e2019-09-11 22:55:34 +0200659 return result.stdout
elajkat1f275e42021-10-15 12:47:55 +0200660
661 def _ensure_public_router(self, client=None, tenant_id=None):
662 """Retrieve a router for the given tenant id.
663
664 If a public router has been configured, it will be returned.
665
666 If a public router has not been configured, but a public
667 network has, a tenant router will be created and returned that
668 routes traffic to the public network.
669 """
670 if not client:
671 client = self.client
672 if not tenant_id:
Takashi Kajinamida451772023-03-22 00:19:39 +0900673 tenant_id = client.project_id
elajkat1f275e42021-10-15 12:47:55 +0200674 router_id = CONF.network.public_router_id
675 network_id = CONF.network.public_network_id
676 if router_id:
677 body = client.show_router(router_id)
678 return body['router']
679 elif network_id:
680 router = self.create_router_by_client()
681 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
682 client.delete_router, router['id'])
683 kwargs = {'external_gateway_info': dict(network_id=network_id)}
684 router = client.update_router(router['id'], **kwargs)['router']
685 return router
686 else:
687 raise Exception("Neither of 'public_router_id' or "
688 "'public_network_id' has been defined.")
689
690 def _update_router_admin_state(self, router, admin_state_up):
691 kwargs = dict(admin_state_up=admin_state_up)
692 router = self.client.update_router(
693 router['id'], **kwargs)['router']
694 self.assertEqual(admin_state_up, router['admin_state_up'])
Slawek Kaplonski32b0f8b2023-03-10 17:03:20 +0100695
696 def _check_cmd_installed_on_server(self, ssh_client, server, cmd):
697 try:
698 ssh_client.execute_script('which %s' % cmd)
699 except SSH_EXC_TUPLE as ssh_e:
700 LOG.debug(ssh_e)
701 self._log_console_output([server])
702 self._log_local_network_status()
703 raise
704 except exceptions.SSHScriptFailed:
705 raise self.skipException(
706 "%s is not available on server %s" % (cmd, server['id']))
Frode Nordahl1bb8e622023-10-16 15:16:34 +0200707
708
709class BaseAdminTempestTestCase(base_api.BaseAdminNetworkTest,
710 BaseTempestTestCase):
711 pass