blob: f7d08ebfdc9b1d196ba4b57479d3c3b7db8f44e2 [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
Rodolfo Alonso Hernandez80df3662025-08-28 09:04:14 +000021from neutron_lib._i18n import _
Assaf Muller92fdc782018-05-31 10:32:47 -040022from neutron_lib.api import validators
23from neutron_lib import constants as neutron_lib_constants
Alex Stafeyevc4d9c352016-12-12 04:13:33 -050024from oslo_log import log
Rodolfo Alonso Hernandez13546b82022-05-12 23:56:47 +000025from packaging import version as packaging_version
Slawek Kaplonski8cccfe02020-09-29 22:34:09 +020026from paramiko import ssh_exception as ssh_exc
Genadi Chereshnya6d10c6e2017-07-05 11:34:20 +030027from tempest.common.utils import net_utils
Itzik Browne67ebb52016-05-15 05:34:41 +000028from tempest.common import waiters
Itzik Browne67ebb52016-05-15 05:34:41 +000029from tempest.lib.common.utils import data_utils
YAMAMOTO Takashi25935722017-01-23 15:34:11 +090030from tempest.lib.common.utils import test_utils
Jakub Libosvarc0c2f1d2017-01-31 12:12:21 -050031from tempest.lib import exceptions as lib_exc
Itzik Browne67ebb52016-05-15 05:34:41 +000032
Chandan Kumar667d3d32017-09-22 12:24:06 +053033from neutron_tempest_plugin.api import base as base_api
Rodolfo Alonso Hernandez4849f002020-01-16 16:01:10 +000034from neutron_tempest_plugin.common import ip as ip_utils
Slawek Kaplonskic4e963e2019-09-11 22:55:34 +020035from neutron_tempest_plugin.common import shell
Chandan Kumar667d3d32017-09-22 12:24:06 +053036from neutron_tempest_plugin.common import ssh
Slawek Kaplonskifd4141f2020-03-14 14:34:00 +010037from neutron_tempest_plugin.common import utils
Chandan Kumar667d3d32017-09-22 12:24:06 +053038from neutron_tempest_plugin import config
Slawek Kaplonskiaf83e832020-02-05 12:11:54 +010039from neutron_tempest_plugin import exceptions
Chandan Kumar667d3d32017-09-22 12:24:06 +053040from neutron_tempest_plugin.scenario import constants
Itzik Browne67ebb52016-05-15 05:34:41 +000041
42CONF = config.CONF
Alex Stafeyevc4d9c352016-12-12 04:13:33 -050043LOG = log.getLogger(__name__)
Eduardo Olivares46fa4242022-04-18 12:47:43 +020044SSH_EXC_TUPLE = (lib_exc.SSHTimeout,
45 ssh_exc.AuthenticationException,
46 ssh_exc.NoValidConnectionsError,
47 ConnectionResetError)
Alex Stafeyevc4d9c352016-12-12 04:13:33 -050048
Itzik Browne67ebb52016-05-15 05:34:41 +000049
Slawek Kaplonskiaf83e832020-02-05 12:11:54 +010050def get_ncat_version(ssh_client=None):
51 cmd = "ncat --version 2>&1"
52 try:
53 version_result = shell.execute(cmd, ssh_client=ssh_client).stdout
54 except exceptions.ShellCommandFailed:
55 m = None
56 else:
57 m = re.match(r"Ncat: Version ([\d.]+) *.", version_result)
58 # NOTE(slaweq): by default lets assume we have ncat 7.60 which is in Ubuntu
59 # 18.04 which is used on u/s gates
Rodolfo Alonso Hernandez13546b82022-05-12 23:56:47 +000060 return packaging_version.Version(m.group(1) if m else '7.60')
Slawek Kaplonskiaf83e832020-02-05 12:11:54 +010061
62
Slawek Kaplonskifd4141f2020-03-14 14:34:00 +010063def get_ncat_server_cmd(port, protocol, msg=None):
Slawek Kaplonskiaf83e832020-02-05 12:11:54 +010064 udp = ''
65 if protocol.lower() == neutron_lib_constants.PROTO_NAME_UDP:
66 udp = '-u'
67 cmd = "nc %(udp)s -p %(port)s -lk " % {
68 'udp': udp, 'port': port}
Slawek Kaplonskifd4141f2020-03-14 14:34:00 +010069 if msg:
70 if CONF.neutron_plugin_options.default_image_is_advanced:
Flavio Fernandesb056ac22020-07-01 14:57:13 -040071 cmd += "-c 'echo %s' " % msg
Slawek Kaplonskifd4141f2020-03-14 14:34:00 +010072 else:
Flavio Fernandesb056ac22020-07-01 14:57:13 -040073 cmd += "-e echo %s " % msg
74 cmd += "< /dev/zero &{0}sleep 0.1{0}".format('\n')
Slawek Kaplonskiaf83e832020-02-05 12:11:54 +010075 return cmd
76
77
Slawek Kaplonski663bbc22023-04-05 16:30:20 +020078def get_ncat_client_cmd(ip_address, port, protocol, ssh_client=None):
Slawek Kaplonskiaf83e832020-02-05 12:11:54 +010079 cmd = 'echo "knock knock" | nc '
Slawek Kaplonskifddcd182023-07-04 11:28:06 +020080 ncat_version = get_ncat_version(ssh_client=ssh_client)
81 if ncat_version > packaging_version.Version('7.60'):
82 cmd += '-d 1 '
Slawek Kaplonskidd5b0e62023-05-31 13:57:25 +020083 if protocol.lower() == neutron_lib_constants.PROTO_NAME_UDP:
84 cmd += '-u '
Slawek Kaplonskidd5b0e62023-05-31 13:57:25 +020085 if ncat_version > packaging_version.Version('7.60'):
86 cmd += '-z '
87 cmd += '-w 1 %(host)s %(port)s' % {'host': ip_address, 'port': port}
Slawek Kaplonskiaf83e832020-02-05 12:11:54 +010088 return cmd
89
90
Itzik Browne67ebb52016-05-15 05:34:41 +000091class BaseTempestTestCase(base_api.BaseNetworkTest):
Itzik Browne67ebb52016-05-15 05:34:41 +000092
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +000093 def create_server(self, flavor_ref, image_ref, key_name, networks,
Roee Agiman6a0a18a2017-11-16 11:51:56 +020094 **kwargs):
Itzik Brownbac51dc2016-10-31 12:25:04 +000095 """Create a server using tempest lib
Brian Haleyaee61ac2018-10-09 20:00:27 -040096
Itzik Brownbac51dc2016-10-31 12:25:04 +000097 All the parameters are the ones used in Compute API
Roee Agiman6a0a18a2017-11-16 11:51:56 +020098 * - Kwargs that require admin privileges
Itzik Brownbac51dc2016-10-31 12:25:04 +000099
100 Args:
101 flavor_ref(str): The flavor of the server to be provisioned.
102 image_ref(str): The image of the server to be provisioned.
103 key_name(str): SSH key to to be used to connect to the
104 provisioned server.
105 networks(list): List of dictionaries where each represent
106 an interface to be attached to the server. For network
107 it should be {'uuid': network_uuid} and for port it should
108 be {'port': port_uuid}
Roee Agiman6a0a18a2017-11-16 11:51:56 +0200109 kwargs:
Itzik Brownbac51dc2016-10-31 12:25:04 +0000110 name(str): Name of the server to be provisioned.
111 security_groups(list): List of dictionaries where
112 the keys is 'name' and the value is the name of
113 the security group. If it's not passed the default
114 security group will be used.
Roee Agiman6a0a18a2017-11-16 11:51:56 +0200115 availability_zone(str)*: The availability zone that
116 the instance will be in.
117 You can request a specific az without actually creating one,
118 Just pass 'X:Y' where X is the default availability
119 zone, and Y is the compute host name.
Itzik Brownbac51dc2016-10-31 12:25:04 +0000120 """
121
Jakub Libosvarffd9b912017-11-16 09:54:14 +0000122 kwargs.setdefault('name', data_utils.rand_name('server-test'))
Itzik Brownbac51dc2016-10-31 12:25:04 +0000123
Jakub Libosvarffd9b912017-11-16 09:54:14 +0000124 # We cannot use setdefault() here because caller could have passed
125 # security_groups=None and we don't want to pass None to
126 # client.create_server()
127 if not kwargs.get('security_groups'):
128 kwargs['security_groups'] = [{'name': 'default'}]
Roee Agiman6a0a18a2017-11-16 11:51:56 +0200129
Slawek Kaplonski2211eab2020-10-20 16:43:53 +0200130 client = kwargs.pop('client', None)
131 if client is None:
132 client = self.os_primary.servers_client
133 if kwargs.get('availability_zone'):
134 client = self.os_admin.servers_client
Roee Agiman6a0a18a2017-11-16 11:51:56 +0200135
Jakub Libosvarffd9b912017-11-16 09:54:14 +0000136 server = client.create_server(
137 flavorRef=flavor_ref,
138 imageRef=image_ref,
139 key_name=key_name,
140 networks=networks,
141 **kwargs)
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +0000142
143 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
Roee Agiman6a0a18a2017-11-16 11:51:56 +0200144 waiters.wait_for_server_termination,
145 client,
146 server['server']['id'])
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +0000147 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
Roee Agiman6a0a18a2017-11-16 11:51:56 +0200148 client.delete_server,
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +0000149 server['server']['id'])
Slawek Kaplonski2211eab2020-10-20 16:43:53 +0200150
151 self.wait_for_server_active(server['server'], client=client)
152 self.wait_for_guest_os_ready(server['server'], client=client)
153
Itzik Browne67ebb52016-05-15 05:34:41 +0000154 return server
155
156 @classmethod
Jens Harbott54357632017-11-21 11:47:06 +0000157 def create_secgroup_rules(cls, rule_list, secgroup_id=None,
158 client=None):
Roee Agiman6a0a18a2017-11-16 11:51:56 +0200159 client = client or cls.os_primary.network_client
Itzik Browne67ebb52016-05-15 05:34:41 +0000160 if not secgroup_id:
161 sgs = client.list_security_groups()['security_groups']
162 for sg in sgs:
163 if sg['name'] == constants.DEFAULT_SECURITY_GROUP:
164 secgroup_id = sg['id']
165 break
Hang Yange6e0ccf2021-02-26 15:07:05 -0600166 resp = []
Itzik Brown1ef813a2016-06-06 12:56:21 +0000167 for rule in rule_list:
168 direction = rule.pop('direction')
Hang Yange6e0ccf2021-02-26 15:07:05 -0600169 resp.append(client.create_security_group_rule(
170 direction=direction,
171 security_group_id=secgroup_id,
172 **rule)['security_group_rule'])
173 return resp
Itzik Brown1ef813a2016-06-06 12:56:21 +0000174
175 @classmethod
Roee Agiman6a0a18a2017-11-16 11:51:56 +0200176 def create_loginable_secgroup_rule(cls, secgroup_id=None,
177 client=None):
Itzik Brown1ef813a2016-06-06 12:56:21 +0000178 """This rule is intended to permit inbound ssh
179
liushya016f7a2024-01-18 11:13:58 +0800180 Allowing ssh traffic from all sources, so no group_id is
Itzik Brown1ef813a2016-06-06 12:56:21 +0000181 provided.
182 Setting a group_id would only permit traffic from ports
183 belonging to the same security group.
184 """
Federico Ressi4c590d72018-10-10 14:01:08 +0200185 return cls.create_security_group_rule(
186 security_group_id=secgroup_id,
187 client=client,
188 protocol=neutron_lib_constants.PROTO_NAME_TCP,
189 direction=neutron_lib_constants.INGRESS_DIRECTION,
190 port_range_min=22,
191 port_range_max=22)
Itzik Browne67ebb52016-05-15 05:34:41 +0000192
193 @classmethod
Slawek Kaplonski83979b92022-12-15 14:15:12 +0100194 def create_ingress_metadata_secgroup_rule(cls, secgroup_id=None):
195 """This rule is intended to permit inbound metadata traffic
196
197 Allowing ingress traffic from metadata server, required only for
198 stateless security groups.
199 """
Maor Blaustein0ea053c2023-02-16 17:23:53 +0200200 # NOTE(slaweq): in case of stateless security groups, there is no
201 # "related" or "established" traffic matching at all so even if
202 # egress traffic to 169.254.169.254 is allowed by default SG, we
203 # need to explicitly allow ingress traffic from the metadata server
204 # to be able to receive responses in the guest vm
205 cls.create_security_group_rule(
206 security_group_id=secgroup_id,
207 direction=neutron_lib_constants.INGRESS_DIRECTION,
208 protocol=neutron_lib_constants.PROTO_NAME_TCP,
209 remote_ip_prefix='169.254.169.254/32',
210 description='metadata out'
211 )
Slawek Kaplonski83979b92022-12-15 14:15:12 +0100212
213 @classmethod
Roee Agiman6a0a18a2017-11-16 11:51:56 +0200214 def create_pingable_secgroup_rule(cls, secgroup_id=None,
215 client=None):
Federico Ressi4c590d72018-10-10 14:01:08 +0200216 """This rule is intended to permit inbound ping
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900217
Federico Ressi4c590d72018-10-10 14:01:08 +0200218 """
219 return cls.create_security_group_rule(
220 security_group_id=secgroup_id, client=client,
221 protocol=neutron_lib_constants.PROTO_NAME_ICMP,
222 direction=neutron_lib_constants.INGRESS_DIRECTION)
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900223
224 @classmethod
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300225 def create_router_by_client(cls, is_admin=False, **kwargs):
226 kwargs.update({'router_name': data_utils.rand_name('router'),
227 'admin_state_up': True,
228 'external_network_id': CONF.network.public_network_id})
229 if not is_admin:
230 router = cls.create_router(**kwargs)
231 else:
232 router = cls.create_admin_router(**kwargs)
Alex Stafeyevc4d9c352016-12-12 04:13:33 -0500233 LOG.debug("Created router %s", router['name'])
Slawek Kaplonskiedf3cba2021-04-21 10:34:02 +0200234 cls._wait_for_router_ha_active(router['id'])
Itzik Browne67ebb52016-05-15 05:34:41 +0000235 return router
236
ccamposre6b10042021-02-12 12:26:08 +0100237 @classmethod
Slawek Kaplonskiedf3cba2021-04-21 10:34:02 +0200238 def _wait_for_router_ha_active(cls, router_id):
239 router = cls.os_admin.network_client.show_router(router_id)['router']
Rodolfo Alonso Hernandez92865882024-03-04 20:30:40 +0000240 if not router.get('ha') or cls.is_driver_ovn:
Slawek Kaplonskiedf3cba2021-04-21 10:34:02 +0200241 return
242
243 def _router_active_on_l3_agent():
244 agents = cls.os_admin.network_client.list_l3_agents_hosting_router(
245 router_id)['agents']
246 return "active" in [agent['ha_state'] for agent in agents]
247
248 error_msg = (
249 "Router %s is not active on any of the L3 agents" % router_id)
Slawek Kaplonskibb1532a2021-06-17 13:45:56 +0000250 # NOTE(slaweq): timeout here should be lower for sure, but due to
251 # the bug https://launchpad.net/bugs/1923633 let's wait even 10
252 # minutes until router will be active on some of the L3 agents
253 utils.wait_until_true(_router_active_on_l3_agent,
254 timeout=600, sleep=5,
255 exception=lib_exc.TimeoutException(error_msg))
Slawek Kaplonskiedf3cba2021-04-21 10:34:02 +0200256
257 @classmethod
ccamposre6b10042021-02-12 12:26:08 +0100258 def skip_if_no_extension_enabled_in_l3_agents(cls, extension):
259 l3_agents = cls.os_admin.network_client.list_agents(
260 binary='neutron-l3-agent')['agents']
261 if not l3_agents:
262 # the tests should not be skipped when neutron-l3-agent does not
263 # exist (this validation doesn't apply to the setups like
264 # e.g. ML2/OVN)
265 return
266 for agent in l3_agents:
267 if extension in agent['configurations'].get('extensions', []):
268 return
269 raise cls.skipTest("No L3 agent with '%s' extension enabled found." %
270 extension)
271
Federico Ressibf877c82018-08-22 08:36:37 +0200272 @removals.remove(version='Stein',
273 message="Please use create_floatingip method instead of "
274 "create_and_associate_floatingip.")
Roee Agiman6a0a18a2017-11-16 11:51:56 +0200275 def create_and_associate_floatingip(self, port_id, client=None):
276 client = client or self.os_primary.network_client
Federico Ressibf877c82018-08-22 08:36:37 +0200277 return self.create_floatingip(port_id=port_id, client=client)
Itzik Browne67ebb52016-05-15 05:34:41 +0000278
Hongbin Lu965b03d2018-04-25 22:32:30 +0000279 def create_interface(cls, server_id, port_id, client=None):
280 client = client or cls.os_primary.interfaces_client
281 body = client.create_interface(server_id, port_id=port_id)
282 return body['interfaceAttachment']
283
284 def delete_interface(cls, server_id, port_id, client=None):
285 client = client or cls.os_primary.interfaces_client
286 client.delete_interface(server_id, port_id=port_id)
287
Brian Haleyd11f4ec2019-08-13 12:09:57 -0400288 def setup_network_and_server(self, router=None, server_name=None,
Slawek Kaplonskiad4ddcb2024-03-14 11:15:59 +0100289 network=None, use_stateless_sg=False,
Frode Nordahl1bb8e622023-10-16 15:16:34 +0200290 create_fip=True, router_client=None,
Slawek Kaplonskiad4ddcb2024-03-14 11:15:59 +0100291 **kwargs):
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300292 """Create network resources and a server.
Itzik Brownbac51dc2016-10-31 12:25:04 +0000293
294 Creating a network, subnet, router, keypair, security group
295 and a server.
296 """
Assaf Mullerd54ae6c2018-05-31 11:38:00 -0400297 self.network = network or self.create_network()
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +0000298 LOG.debug("Created network %s", self.network['name'])
299 self.subnet = self.create_subnet(self.network)
300 LOG.debug("Created subnet %s", self.subnet['id'])
Itzik Brown1ef813a2016-06-06 12:56:21 +0000301
Slawek Kaplonskiad4ddcb2024-03-14 11:15:59 +0100302 sg_args = {
303 'name': data_utils.rand_name('secgroup')
304 }
305 if use_stateless_sg:
306 sg_args['stateful'] = False
Brian Haleyf86ac2e2017-06-21 10:43:50 -0400307 secgroup = self.os_primary.network_client.create_security_group(
Slawek Kaplonskiad4ddcb2024-03-14 11:15:59 +0100308 **sg_args)
Alex Stafeyevc4d9c352016-12-12 04:13:33 -0500309 LOG.debug("Created security group %s",
310 secgroup['security_group']['name'])
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +0000311 self.security_groups.append(secgroup['security_group'])
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300312 if not router:
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +0000313 router = self.create_router_by_client(**kwargs)
Frode Nordahl1bb8e622023-10-16 15:16:34 +0200314 self.create_router_interface(router['id'], self.subnet['id'],
315 client=router_client)
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +0000316 self.keypair = self.create_keypair()
317 self.create_loginable_secgroup_rule(
Itzik Brownbac51dc2016-10-31 12:25:04 +0000318 secgroup_id=secgroup['security_group']['id'])
Slawek Kaplonskiad4ddcb2024-03-14 11:15:59 +0100319 if use_stateless_sg:
320 self.create_ingress_metadata_secgroup_rule(
321 secgroup_id=secgroup['security_group']['id'])
Assaf Muller92fdc782018-05-31 10:32:47 -0400322
323 server_kwargs = {
324 'flavor_ref': CONF.compute.flavor_ref,
325 'image_ref': CONF.compute.image_ref,
326 'key_name': self.keypair['name'],
327 'networks': [{'uuid': self.network['id']}],
328 'security_groups': [{'name': secgroup['security_group']['name']}],
329 }
330 if server_name is not None:
331 server_kwargs['name'] = server_name
332
333 self.server = self.create_server(**server_kwargs)
Jakub Libosvar1345d9d2017-06-09 13:59:05 +0000334 self.port = self.client.list_ports(network_id=self.network['id'],
335 device_id=self.server[
336 'server']['id'])['ports'][0]
Frode Nordahl1bb8e622023-10-16 15:16:34 +0200337
338 if create_fip:
339 self.fip = self.create_floatingip(port=self.port)
Jakub Libosvarc0c2f1d2017-01-31 12:12:21 -0500340
Ihar Hrachyshka2d663552024-08-26 12:47:24 -0400341 def check_connectivity(self, host=None, ssh_user=None, ssh_key=None,
Bence Romsics2abbc922020-09-30 16:10:07 +0200342 servers=None, ssh_timeout=None, ssh_client=None):
343 # Either ssh_client or ssh_user+ssh_key is mandatory.
344 if ssh_client is None:
345 ssh_client = ssh.Client(host, ssh_user,
346 pkey=ssh_key, timeout=ssh_timeout)
Jakub Libosvarc0c2f1d2017-01-31 12:12:21 -0500347 try:
348 ssh_client.test_connection_auth()
Eduardo Olivares46fa4242022-04-18 12:47:43 +0200349 except SSH_EXC_TUPLE as ssh_e:
Jakub Libosvarc0c2f1d2017-01-31 12:12:21 -0500350 LOG.debug(ssh_e)
351 self._log_console_output(servers)
Rodolfo Alonso Hernandez4849f002020-01-16 16:01:10 +0000352 self._log_local_network_status()
Jakub Libosvarc0c2f1d2017-01-31 12:12:21 -0500353 raise
354
355 def _log_console_output(self, servers=None):
356 if not CONF.compute_feature_enabled.console_output:
357 LOG.debug('Console output not supported, cannot log')
358 return
359 if not servers:
Brian Haleyf86ac2e2017-06-21 10:43:50 -0400360 servers = self.os_primary.servers_client.list_servers()
Jakub Libosvarc0c2f1d2017-01-31 12:12:21 -0500361 servers = servers['servers']
362 for server in servers:
Slawek Kaplonskicff79232020-03-03 14:12:18 +0100363 # NOTE(slaweq): sometimes servers are passed in dictionary with
364 # "server" key as first level key and in other cases it may be that
365 # it is just the "inner" dict without "server" key. Lets try to
366 # handle both cases
367 server = server.get("server") or server
Jakub Libosvarc0c2f1d2017-01-31 12:12:21 -0500368 try:
369 console_output = (
Brian Haleyf86ac2e2017-06-21 10:43:50 -0400370 self.os_primary.servers_client.get_console_output(
Jakub Libosvarc0c2f1d2017-01-31 12:12:21 -0500371 server['id'])['output'])
372 LOG.debug('Console output for %s\nbody=\n%s',
373 server['id'], console_output)
374 except lib_exc.NotFound:
375 LOG.debug("Server %s disappeared(deleted) while looking "
376 "for the console log", server['id'])
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900377
Rodolfo Alonso Hernandez4849f002020-01-16 16:01:10 +0000378 def _log_local_network_status(self):
Slawek Kaplonski8033af72020-05-05 12:01:37 +0200379 self._log_ns_network_status()
380 for ns_name in ip_utils.IPCommand().list_namespaces():
381 self._log_ns_network_status(ns_name=ns_name)
382
383 def _log_ns_network_status(self, ns_name=None):
Rodolfo Alonso Hernandez9817d4f2020-11-17 08:50:50 +0000384 try:
385 local_ips = ip_utils.IPCommand(namespace=ns_name).list_addresses()
386 local_routes = ip_utils.IPCommand(namespace=ns_name).list_routes()
Rodolfo Alonso Hernandezc134ea92021-04-14 15:15:01 +0000387 arp_table = ip_utils.arp_table(namespace=ns_name)
388 iptables = ip_utils.list_iptables(namespace=ns_name)
389 lsockets = ip_utils.list_listening_sockets(namespace=ns_name)
Rodolfo Alonso Hernandez9817d4f2020-11-17 08:50:50 +0000390 except exceptions.ShellCommandFailed:
391 LOG.debug('Namespace %s has been deleted synchronously during the '
392 'host network collection process', ns_name)
393 return
394
Slawek Kaplonski8033af72020-05-05 12:01:37 +0200395 LOG.debug('Namespace %s; IP Addresses:\n%s',
396 ns_name, '\n'.join(str(r) for r in local_ips))
Slawek Kaplonski8033af72020-05-05 12:01:37 +0200397 LOG.debug('Namespace %s; Local routes:\n%s',
398 ns_name, '\n'.join(str(r) for r in local_routes))
Slawek Kaplonski8033af72020-05-05 12:01:37 +0200399 LOG.debug('Namespace %s; Local ARP table:\n%s',
400 ns_name, '\n'.join(str(r) for r in arp_table))
Rodolfo Alonso Hernandezc134ea92021-04-14 15:15:01 +0000401 LOG.debug('Namespace %s; Local iptables:\n%s', ns_name, iptables)
402 LOG.debug('Namespace %s; Listening sockets:\n%s', ns_name, lsockets)
Rodolfo Alonso Hernandez4849f002020-01-16 16:01:10 +0000403
LIU Yulong68ab2452019-05-18 10:19:49 +0800404 def _check_remote_connectivity(self, source, dest, count,
405 should_succeed=True,
Assaf Muller92fdc782018-05-31 10:32:47 -0400406 nic=None, mtu=None, fragmentation=True,
Roman Safronov12663cf2020-07-27 13:11:07 +0300407 timeout=None, pattern=None,
Nurmatov Mamatisa1b1c9d32021-12-27 15:37:03 +0300408 forbid_packet_loss=False,
409 check_response_ip=True):
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900410 """check ping server via source ssh connection
411
412 :param source: RemoteClient: an ssh connection from which to ping
413 :param dest: and IP to ping against
LIU Yulong68ab2452019-05-18 10:19:49 +0800414 :param count: Number of ping packet(s) to send
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900415 :param should_succeed: boolean should ping succeed or not
416 :param nic: specific network interface to ping from
Genadi Chereshnya6d10c6e2017-07-05 11:34:20 +0300417 :param mtu: mtu size for the packet to be sent
418 :param fragmentation: Flag for packet fragmentation
LIU Yulong68ab2452019-05-18 10:19:49 +0800419 :param timeout: Timeout for all ping packet(s) to succeed
Eduardo Olivaresf2b60542020-01-30 09:37:22 +0100420 :param pattern: hex digits included in ICMP messages
Roman Safronov12663cf2020-07-27 13:11:07 +0300421 :param forbid_packet_loss: forbid or allow some lost packets
Nurmatov Mamatisa1b1c9d32021-12-27 15:37:03 +0300422 :param check_response_ip: check response ip
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900423 :returns: boolean -- should_succeed == ping
424 :returns: ping is false if ping failed
425 """
LIU Yulong68ab2452019-05-18 10:19:49 +0800426 def ping_host(source, host, count,
Genadi Chereshnya6d10c6e2017-07-05 11:34:20 +0300427 size=CONF.validation.ping_size, nic=None, mtu=None,
Eduardo Olivaresf2b60542020-01-30 09:37:22 +0100428 fragmentation=True, pattern=None):
Assaf Muller92fdc782018-05-31 10:32:47 -0400429 IP_VERSION_4 = neutron_lib_constants.IP_VERSION_4
430 IP_VERSION_6 = neutron_lib_constants.IP_VERSION_6
431
432 # Use 'ping6' for IPv6 addresses, 'ping' for IPv4 and hostnames
433 ip_version = (
434 IP_VERSION_6 if netaddr.valid_ipv6(host) else IP_VERSION_4)
435 cmd = (
436 'ping6' if ip_version == IP_VERSION_6 else 'ping')
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900437 if nic:
438 cmd = 'sudo {cmd} -I {nic}'.format(cmd=cmd, nic=nic)
Genadi Chereshnya6d10c6e2017-07-05 11:34:20 +0300439 if mtu:
440 if not fragmentation:
441 cmd += ' -M do'
442 size = str(net_utils.get_ping_payload_size(
Assaf Muller92fdc782018-05-31 10:32:47 -0400443 mtu=mtu, ip_version=ip_version))
Eduardo Olivaresf2b60542020-01-30 09:37:22 +0100444 if pattern:
445 cmd += ' -p {pattern}'.format(pattern=pattern)
Maciej Józefczyk3c324e02020-03-16 10:52:08 +0000446 cmd += ' -c{0} -W{0} -s{1} {2}'.format(count, size, host)
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900447 return source.exec_command(cmd)
448
449 def ping_remote():
450 try:
LIU Yulong68ab2452019-05-18 10:19:49 +0800451 result = ping_host(source, dest, count, nic=nic, mtu=mtu,
Eduardo Olivaresf2b60542020-01-30 09:37:22 +0100452 fragmentation=fragmentation,
453 pattern=pattern)
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900454
455 except lib_exc.SSHExecCommandFailed:
456 LOG.warning('Failed to ping IP: %s via a ssh connection '
457 'from: %s.', dest, source.host)
458 return not should_succeed
459 LOG.debug('ping result: %s', result)
Assaf Muller92fdc782018-05-31 10:32:47 -0400460
Roman Safronov12663cf2020-07-27 13:11:07 +0300461 if forbid_packet_loss and ' 0% packet loss' not in result:
462 LOG.debug('Packet loss detected')
463 return not should_succeed
464
Nurmatov Mamatisa1b1c9d32021-12-27 15:37:03 +0300465 if (check_response_ip and
466 validators.validate_ip_address(dest) is None):
Assaf Muller92fdc782018-05-31 10:32:47 -0400467 # Assert that the return traffic was from the correct
468 # source address.
469 from_source = 'from %s' % dest
470 self.assertIn(from_source, result)
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900471 return should_succeed
472
Assaf Muller92fdc782018-05-31 10:32:47 -0400473 return test_utils.call_until_true(
474 ping_remote, timeout or CONF.validation.ping_timeout, 1)
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900475
476 def check_remote_connectivity(self, source, dest, should_succeed=True,
Slawek Kaplonskib07251f2018-05-16 12:21:50 +0200477 nic=None, mtu=None, fragmentation=True,
LIU Yulong68ab2452019-05-18 10:19:49 +0800478 servers=None, timeout=None,
Eduardo Olivaresf2b60542020-01-30 09:37:22 +0100479 ping_count=CONF.validation.ping_count,
Nurmatov Mamatisa1b1c9d32021-12-27 15:37:03 +0300480 pattern=None, forbid_packet_loss=False,
481 check_response_ip=True):
Slawek Kaplonskib07251f2018-05-16 12:21:50 +0200482 try:
483 self.assertTrue(self._check_remote_connectivity(
LIU Yulong68ab2452019-05-18 10:19:49 +0800484 source, dest, ping_count, should_succeed, nic, mtu,
485 fragmentation,
Roman Safronov12663cf2020-07-27 13:11:07 +0300486 timeout=timeout, pattern=pattern,
Nurmatov Mamatisa1b1c9d32021-12-27 15:37:03 +0300487 forbid_packet_loss=forbid_packet_loss,
488 check_response_ip=check_response_ip))
Eduardo Olivares46fa4242022-04-18 12:47:43 +0200489 except SSH_EXC_TUPLE as ssh_e:
Slawek Kaplonskib07251f2018-05-16 12:21:50 +0200490 LOG.debug(ssh_e)
491 self._log_console_output(servers)
Slawek Kaplonski8cccfe02020-09-29 22:34:09 +0200492 self._log_local_network_status()
Slawek Kaplonskib07251f2018-05-16 12:21:50 +0200493 raise
494 except AssertionError:
495 self._log_console_output(servers)
Slawek Kaplonski8cccfe02020-09-29 22:34:09 +0200496 self._log_local_network_status()
Slawek Kaplonskib07251f2018-05-16 12:21:50 +0200497 raise
Chandan Kumarc125fd12017-11-15 19:41:01 +0530498
499 def ping_ip_address(self, ip_address, should_succeed=True,
500 ping_timeout=None, mtu=None):
501 # the code is taken from tempest/scenario/manager.py in tempest git
502 timeout = ping_timeout or CONF.validation.ping_timeout
503 cmd = ['ping', '-c1', '-w1']
504
505 if mtu:
506 cmd += [
507 # don't fragment
508 '-M', 'do',
509 # ping receives just the size of ICMP payload
510 '-s', str(net_utils.get_ping_payload_size(mtu, 4))
511 ]
512 cmd.append(ip_address)
513
514 def ping():
515 proc = subprocess.Popen(cmd,
516 stdout=subprocess.PIPE,
517 stderr=subprocess.PIPE)
518 proc.communicate()
519
520 return (proc.returncode == 0) == should_succeed
521
522 caller = test_utils.find_test_caller()
523 LOG.debug('%(caller)s begins to ping %(ip)s in %(timeout)s sec and the'
524 ' expected result is %(should_succeed)s', {
525 'caller': caller, 'ip': ip_address, 'timeout': timeout,
526 'should_succeed':
527 'reachable' if should_succeed else 'unreachable'
528 })
529 result = test_utils.call_until_true(ping, timeout, 1)
Manjeet Singh Bhatia8bbf8992019-03-04 11:59:57 -0800530
531 # To make sure ping_ip_address called by test works
532 # as expected.
533 self.assertTrue(result)
534
Chandan Kumarc125fd12017-11-15 19:41:01 +0530535 LOG.debug('%(caller)s finishes ping %(ip)s in %(timeout)s sec and the '
536 'ping result is %(result)s', {
537 'caller': caller, 'ip': ip_address, 'timeout': timeout,
538 'result': 'expected' if result else 'unexpected'
539 })
540 return result
Federico Ressie7417b72018-05-30 05:50:58 +0200541
542 def wait_for_server_status(self, server, status, client=None, **kwargs):
543 """Waits for a server to reach a given status.
544
545 :param server: mapping having schema {'id': <server_id>}
546 :param status: string status to wait for (es: 'ACTIVE')
547 :param clien: servers client (self.os_primary.servers_client as
548 default value)
549 """
550
551 client = client or self.os_primary.servers_client
552 waiters.wait_for_server_status(client, server['id'], status, **kwargs)
553
554 def wait_for_server_active(self, server, client=None):
555 """Waits for a server to reach active status.
556
557 :param server: mapping having schema {'id': <server_id>}
558 :param clien: servers client (self.os_primary.servers_client as
559 default value)
560 """
561 self.wait_for_server_status(
562 server, constants.SERVER_STATUS_ACTIVE, client)
Jakub Libosvar031fd5a2019-07-15 16:10:07 +0000563
Slawek Kaplonski2211eab2020-10-20 16:43:53 +0200564 def wait_for_guest_os_ready(self, server, client=None):
565 if not CONF.compute_feature_enabled.console_output:
566 LOG.debug('Console output not supported, cannot check if server '
Rodolfo Alonso Hernandezdff870b2020-11-06 08:41:44 +0000567 '%s is ready.', server['id'])
Slawek Kaplonski2211eab2020-10-20 16:43:53 +0200568 return
569
570 client = client or self.os_primary.servers_client
571
572 def system_booted():
573 console_output = client.get_console_output(server['id'])['output']
574 for line in console_output.split('\n'):
575 if 'login:' in line.lower():
576 return True
577 return False
578
579 try:
ccamposr1bd28ae2021-11-29 10:06:02 +0100580 utils.wait_until_true(system_booted, timeout=90, sleep=5)
Slawek Kaplonski2211eab2020-10-20 16:43:53 +0200581 except utils.WaitTimeout:
582 LOG.debug("No correct output in console of server %s found. "
583 "Guest operating system status can't be checked.",
584 server['id'])
585
Flavio Fernandesa1952c62020-10-02 06:39:08 -0400586 def check_servers_hostnames(self, servers, timeout=None, log_errors=True,
587 external_port=None):
Jakub Libosvar031fd5a2019-07-15 16:10:07 +0000588 """Compare hostnames of given servers with their names."""
589 try:
590 for server in servers:
591 kwargs = {}
nfridmand8969542020-06-02 14:59:09 +0300592 if timeout:
593 kwargs['timeout'] = timeout
Jakub Libosvar031fd5a2019-07-15 16:10:07 +0000594 try:
Flavio Fernandesa1952c62020-10-02 06:39:08 -0400595 kwargs['port'] = external_port or (
Slawek Kaplonskic4e963e2019-09-11 22:55:34 +0200596 server['port_forwarding_tcp']['external_port'])
Jakub Libosvar031fd5a2019-07-15 16:10:07 +0000597 except KeyError:
598 pass
599 ssh_client = ssh.Client(
600 self.fip['floating_ip_address'],
601 CONF.validation.image_ssh_user,
602 pkey=self.keypair['private_key'],
603 **kwargs)
604 self.assertIn(server['name'],
Rodolfo Alonso Hernandezaf394dd2020-11-12 14:26:13 +0000605 ssh_client.get_hostname())
Eduardo Olivares46fa4242022-04-18 12:47:43 +0200606 except SSH_EXC_TUPLE as ssh_e:
Jakub Libosvar031fd5a2019-07-15 16:10:07 +0000607 LOG.debug(ssh_e)
608 if log_errors:
609 self._log_console_output(servers)
Slawek Kaplonski8cccfe02020-09-29 22:34:09 +0200610 self._log_local_network_status()
Jakub Libosvar031fd5a2019-07-15 16:10:07 +0000611 raise
612 except AssertionError as assert_e:
613 LOG.debug(assert_e)
614 if log_errors:
615 self._log_console_output(servers)
Slawek Kaplonski8cccfe02020-09-29 22:34:09 +0200616 self._log_local_network_status()
Jakub Libosvar031fd5a2019-07-15 16:10:07 +0000617 raise
Slawek Kaplonskic4e963e2019-09-11 22:55:34 +0200618
Slawek Kaplonskifd4141f2020-03-14 14:34:00 +0100619 def ensure_nc_listen(self, ssh_client, port, protocol, echo_msg=None,
620 servers=None):
621 """Ensure that nc server listening on the given TCP/UDP port is up.
622
623 Listener is created always on remote host.
624 """
625 def spawn_and_check_process():
626 self.nc_listen(ssh_client, port, protocol, echo_msg, servers)
627 return utils.process_is_running(ssh_client, "nc")
628
629 utils.wait_until_true(spawn_and_check_process)
630
631 def nc_listen(self, ssh_client, port, protocol, echo_msg=None,
632 servers=None):
Slawek Kaplonskic4e963e2019-09-11 22:55:34 +0200633 """Create nc server listening on the given TCP/UDP port.
634
635 Listener is created always on remote host.
636 """
Slawek Kaplonskic4e963e2019-09-11 22:55:34 +0200637 try:
Slawek Kaplonskiaf83e832020-02-05 12:11:54 +0100638 return ssh_client.execute_script(
639 get_ncat_server_cmd(port, protocol, echo_msg),
Flavio Fernandesb056ac22020-07-01 14:57:13 -0400640 become_root=True, combine_stderr=True)
Eduardo Olivares46fa4242022-04-18 12:47:43 +0200641 except SSH_EXC_TUPLE as ssh_e:
Slawek Kaplonskic4e963e2019-09-11 22:55:34 +0200642 LOG.debug(ssh_e)
Slawek Kaplonskifd4141f2020-03-14 14:34:00 +0100643 self._log_console_output(servers)
Slawek Kaplonski8cccfe02020-09-29 22:34:09 +0200644 self._log_local_network_status()
Slawek Kaplonskic4e963e2019-09-11 22:55:34 +0200645 raise
646
Slawek Kaplonskib327a632023-02-23 07:59:12 +0100647 def nc_client(self, ip_address, port, protocol, ssh_client=None):
Slawek Kaplonskic4e963e2019-09-11 22:55:34 +0200648 """Check connectivity to TCP/UDP port at host via nc.
649
Slawek Kaplonskib327a632023-02-23 07:59:12 +0100650 If ssh_client is not given, it is executed locally on host where tests
651 are executed. Otherwise ssh_client object is used to execute it.
Slawek Kaplonskic4e963e2019-09-11 22:55:34 +0200652 """
Slawek Kaplonski663bbc22023-04-05 16:30:20 +0200653 cmd = get_ncat_client_cmd(ip_address, port, protocol,
654 ssh_client=ssh_client)
Slawek Kaplonski03bd62e2023-06-02 10:54:05 +0200655 result = shell.execute(cmd, ssh_client=ssh_client, check=False)
Slawek Kaplonskic4e963e2019-09-11 22:55:34 +0200656 return result.stdout
elajkat1f275e42021-10-15 12:47:55 +0200657
658 def _ensure_public_router(self, client=None, tenant_id=None):
659 """Retrieve a router for the given tenant id.
660
661 If a public router has been configured, it will be returned.
662
663 If a public router has not been configured, but a public
664 network has, a tenant router will be created and returned that
665 routes traffic to the public network.
666 """
667 if not client:
668 client = self.client
669 if not tenant_id:
Takashi Kajinamida451772023-03-22 00:19:39 +0900670 tenant_id = client.project_id
elajkat1f275e42021-10-15 12:47:55 +0200671 router_id = CONF.network.public_router_id
672 network_id = CONF.network.public_network_id
673 if router_id:
674 body = client.show_router(router_id)
675 return body['router']
676 elif network_id:
677 router = self.create_router_by_client()
678 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
679 client.delete_router, router['id'])
680 kwargs = {'external_gateway_info': dict(network_id=network_id)}
681 router = client.update_router(router['id'], **kwargs)['router']
682 return router
683 else:
Rodolfo Alonso Hernandez80df3662025-08-28 09:04:14 +0000684 raise Exception(_("Neither of 'public_router_id' or "
685 "'public_network_id' has been defined."))
elajkat1f275e42021-10-15 12:47:55 +0200686
687 def _update_router_admin_state(self, router, admin_state_up):
688 kwargs = dict(admin_state_up=admin_state_up)
689 router = self.client.update_router(
690 router['id'], **kwargs)['router']
691 self.assertEqual(admin_state_up, router['admin_state_up'])
Slawek Kaplonski32b0f8b2023-03-10 17:03:20 +0100692
693 def _check_cmd_installed_on_server(self, ssh_client, server, cmd):
694 try:
695 ssh_client.execute_script('which %s' % cmd)
696 except SSH_EXC_TUPLE as ssh_e:
697 LOG.debug(ssh_e)
698 self._log_console_output([server])
699 self._log_local_network_status()
700 raise
701 except exceptions.SSHScriptFailed:
702 raise self.skipException(
703 "%s is not available on server %s" % (cmd, server['id']))
Frode Nordahl1bb8e622023-10-16 15:16:34 +0200704
705
706class BaseAdminTempestTestCase(base_api.BaseAdminNetworkTest,
707 BaseTempestTestCase):
708 pass