blob: 36d15d802105713c685920e140a859a6edce2b3e [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
Renjing Xiaoe6e4b892025-12-23 12:46:10 +0000272 @classmethod
273 def setup_advanced_image(cls):
274 if CONF.neutron_plugin_options.default_image_is_advanced:
275 cls.flavor_ref = CONF.compute.flavor_ref
276 cls.image_ref = CONF.compute.image_ref
277 cls.username = CONF.validation.image_ssh_user
278 else:
279 cls.flavor_ref = (
280 CONF.neutron_plugin_options.advanced_image_flavor_ref)
281 cls.image_ref = CONF.neutron_plugin_options.advanced_image_ref
282 cls.username = CONF.neutron_plugin_options.advanced_image_ssh_user
283
Federico Ressibf877c82018-08-22 08:36:37 +0200284 @removals.remove(version='Stein',
285 message="Please use create_floatingip method instead of "
286 "create_and_associate_floatingip.")
Roee Agiman6a0a18a2017-11-16 11:51:56 +0200287 def create_and_associate_floatingip(self, port_id, client=None):
288 client = client or self.os_primary.network_client
Federico Ressibf877c82018-08-22 08:36:37 +0200289 return self.create_floatingip(port_id=port_id, client=client)
Itzik Browne67ebb52016-05-15 05:34:41 +0000290
Hongbin Lu965b03d2018-04-25 22:32:30 +0000291 def create_interface(cls, server_id, port_id, client=None):
292 client = client or cls.os_primary.interfaces_client
293 body = client.create_interface(server_id, port_id=port_id)
294 return body['interfaceAttachment']
295
296 def delete_interface(cls, server_id, port_id, client=None):
297 client = client or cls.os_primary.interfaces_client
298 client.delete_interface(server_id, port_id=port_id)
299
Brian Haleyd11f4ec2019-08-13 12:09:57 -0400300 def setup_network_and_server(self, router=None, server_name=None,
Slawek Kaplonskiad4ddcb2024-03-14 11:15:59 +0100301 network=None, use_stateless_sg=False,
Frode Nordahl1bb8e622023-10-16 15:16:34 +0200302 create_fip=True, router_client=None,
Slawek Kaplonskiad4ddcb2024-03-14 11:15:59 +0100303 **kwargs):
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300304 """Create network resources and a server.
Itzik Brownbac51dc2016-10-31 12:25:04 +0000305
306 Creating a network, subnet, router, keypair, security group
307 and a server.
308 """
Assaf Mullerd54ae6c2018-05-31 11:38:00 -0400309 self.network = network or self.create_network()
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +0000310 LOG.debug("Created network %s", self.network['name'])
311 self.subnet = self.create_subnet(self.network)
312 LOG.debug("Created subnet %s", self.subnet['id'])
Itzik Brown1ef813a2016-06-06 12:56:21 +0000313
Slawek Kaplonskiad4ddcb2024-03-14 11:15:59 +0100314 sg_args = {
315 'name': data_utils.rand_name('secgroup')
316 }
317 if use_stateless_sg:
318 sg_args['stateful'] = False
Brian Haleyf86ac2e2017-06-21 10:43:50 -0400319 secgroup = self.os_primary.network_client.create_security_group(
Slawek Kaplonskiad4ddcb2024-03-14 11:15:59 +0100320 **sg_args)
Alex Stafeyevc4d9c352016-12-12 04:13:33 -0500321 LOG.debug("Created security group %s",
322 secgroup['security_group']['name'])
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +0000323 self.security_groups.append(secgroup['security_group'])
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300324 if not router:
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +0000325 router = self.create_router_by_client(**kwargs)
Frode Nordahl1bb8e622023-10-16 15:16:34 +0200326 self.create_router_interface(router['id'], self.subnet['id'],
327 client=router_client)
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +0000328 self.keypair = self.create_keypair()
329 self.create_loginable_secgroup_rule(
Itzik Brownbac51dc2016-10-31 12:25:04 +0000330 secgroup_id=secgroup['security_group']['id'])
Slawek Kaplonskiad4ddcb2024-03-14 11:15:59 +0100331 if use_stateless_sg:
332 self.create_ingress_metadata_secgroup_rule(
333 secgroup_id=secgroup['security_group']['id'])
Assaf Muller92fdc782018-05-31 10:32:47 -0400334
335 server_kwargs = {
336 'flavor_ref': CONF.compute.flavor_ref,
337 'image_ref': CONF.compute.image_ref,
338 'key_name': self.keypair['name'],
339 'networks': [{'uuid': self.network['id']}],
340 'security_groups': [{'name': secgroup['security_group']['name']}],
341 }
342 if server_name is not None:
343 server_kwargs['name'] = server_name
344
345 self.server = self.create_server(**server_kwargs)
Jakub Libosvar1345d9d2017-06-09 13:59:05 +0000346 self.port = self.client.list_ports(network_id=self.network['id'],
347 device_id=self.server[
348 'server']['id'])['ports'][0]
Frode Nordahl1bb8e622023-10-16 15:16:34 +0200349
350 if create_fip:
351 self.fip = self.create_floatingip(port=self.port)
Jakub Libosvarc0c2f1d2017-01-31 12:12:21 -0500352
Ihar Hrachyshka2d663552024-08-26 12:47:24 -0400353 def check_connectivity(self, host=None, ssh_user=None, ssh_key=None,
Bence Romsics2abbc922020-09-30 16:10:07 +0200354 servers=None, ssh_timeout=None, ssh_client=None):
355 # Either ssh_client or ssh_user+ssh_key is mandatory.
356 if ssh_client is None:
357 ssh_client = ssh.Client(host, ssh_user,
358 pkey=ssh_key, timeout=ssh_timeout)
Jakub Libosvarc0c2f1d2017-01-31 12:12:21 -0500359 try:
360 ssh_client.test_connection_auth()
Eduardo Olivares46fa4242022-04-18 12:47:43 +0200361 except SSH_EXC_TUPLE as ssh_e:
Jakub Libosvarc0c2f1d2017-01-31 12:12:21 -0500362 LOG.debug(ssh_e)
363 self._log_console_output(servers)
Rodolfo Alonso Hernandez4849f002020-01-16 16:01:10 +0000364 self._log_local_network_status()
Jakub Libosvarc0c2f1d2017-01-31 12:12:21 -0500365 raise
366
367 def _log_console_output(self, servers=None):
368 if not CONF.compute_feature_enabled.console_output:
369 LOG.debug('Console output not supported, cannot log')
370 return
371 if not servers:
Brian Haleyf86ac2e2017-06-21 10:43:50 -0400372 servers = self.os_primary.servers_client.list_servers()
Jakub Libosvarc0c2f1d2017-01-31 12:12:21 -0500373 servers = servers['servers']
374 for server in servers:
Slawek Kaplonskicff79232020-03-03 14:12:18 +0100375 # NOTE(slaweq): sometimes servers are passed in dictionary with
376 # "server" key as first level key and in other cases it may be that
377 # it is just the "inner" dict without "server" key. Lets try to
378 # handle both cases
379 server = server.get("server") or server
Jakub Libosvarc0c2f1d2017-01-31 12:12:21 -0500380 try:
381 console_output = (
Brian Haleyf86ac2e2017-06-21 10:43:50 -0400382 self.os_primary.servers_client.get_console_output(
Jakub Libosvarc0c2f1d2017-01-31 12:12:21 -0500383 server['id'])['output'])
384 LOG.debug('Console output for %s\nbody=\n%s',
385 server['id'], console_output)
386 except lib_exc.NotFound:
387 LOG.debug("Server %s disappeared(deleted) while looking "
388 "for the console log", server['id'])
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900389
Rodolfo Alonso Hernandez4849f002020-01-16 16:01:10 +0000390 def _log_local_network_status(self):
Slawek Kaplonski8033af72020-05-05 12:01:37 +0200391 self._log_ns_network_status()
392 for ns_name in ip_utils.IPCommand().list_namespaces():
393 self._log_ns_network_status(ns_name=ns_name)
394
395 def _log_ns_network_status(self, ns_name=None):
Rodolfo Alonso Hernandez9817d4f2020-11-17 08:50:50 +0000396 try:
397 local_ips = ip_utils.IPCommand(namespace=ns_name).list_addresses()
398 local_routes = ip_utils.IPCommand(namespace=ns_name).list_routes()
Rodolfo Alonso Hernandezc134ea92021-04-14 15:15:01 +0000399 arp_table = ip_utils.arp_table(namespace=ns_name)
400 iptables = ip_utils.list_iptables(namespace=ns_name)
401 lsockets = ip_utils.list_listening_sockets(namespace=ns_name)
Rodolfo Alonso Hernandez9817d4f2020-11-17 08:50:50 +0000402 except exceptions.ShellCommandFailed:
403 LOG.debug('Namespace %s has been deleted synchronously during the '
404 'host network collection process', ns_name)
405 return
406
Slawek Kaplonski8033af72020-05-05 12:01:37 +0200407 LOG.debug('Namespace %s; IP Addresses:\n%s',
408 ns_name, '\n'.join(str(r) for r in local_ips))
Slawek Kaplonski8033af72020-05-05 12:01:37 +0200409 LOG.debug('Namespace %s; Local routes:\n%s',
410 ns_name, '\n'.join(str(r) for r in local_routes))
Slawek Kaplonski8033af72020-05-05 12:01:37 +0200411 LOG.debug('Namespace %s; Local ARP table:\n%s',
412 ns_name, '\n'.join(str(r) for r in arp_table))
Rodolfo Alonso Hernandezc134ea92021-04-14 15:15:01 +0000413 LOG.debug('Namespace %s; Local iptables:\n%s', ns_name, iptables)
414 LOG.debug('Namespace %s; Listening sockets:\n%s', ns_name, lsockets)
Rodolfo Alonso Hernandez4849f002020-01-16 16:01:10 +0000415
LIU Yulong68ab2452019-05-18 10:19:49 +0800416 def _check_remote_connectivity(self, source, dest, count,
417 should_succeed=True,
Assaf Muller92fdc782018-05-31 10:32:47 -0400418 nic=None, mtu=None, fragmentation=True,
Roman Safronov12663cf2020-07-27 13:11:07 +0300419 timeout=None, pattern=None,
Nurmatov Mamatisa1b1c9d32021-12-27 15:37:03 +0300420 forbid_packet_loss=False,
421 check_response_ip=True):
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900422 """check ping server via source ssh connection
423
424 :param source: RemoteClient: an ssh connection from which to ping
425 :param dest: and IP to ping against
LIU Yulong68ab2452019-05-18 10:19:49 +0800426 :param count: Number of ping packet(s) to send
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900427 :param should_succeed: boolean should ping succeed or not
428 :param nic: specific network interface to ping from
Genadi Chereshnya6d10c6e2017-07-05 11:34:20 +0300429 :param mtu: mtu size for the packet to be sent
430 :param fragmentation: Flag for packet fragmentation
LIU Yulong68ab2452019-05-18 10:19:49 +0800431 :param timeout: Timeout for all ping packet(s) to succeed
Eduardo Olivaresf2b60542020-01-30 09:37:22 +0100432 :param pattern: hex digits included in ICMP messages
Roman Safronov12663cf2020-07-27 13:11:07 +0300433 :param forbid_packet_loss: forbid or allow some lost packets
Nurmatov Mamatisa1b1c9d32021-12-27 15:37:03 +0300434 :param check_response_ip: check response ip
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900435 :returns: boolean -- should_succeed == ping
436 :returns: ping is false if ping failed
437 """
LIU Yulong68ab2452019-05-18 10:19:49 +0800438 def ping_host(source, host, count,
Genadi Chereshnya6d10c6e2017-07-05 11:34:20 +0300439 size=CONF.validation.ping_size, nic=None, mtu=None,
Eduardo Olivaresf2b60542020-01-30 09:37:22 +0100440 fragmentation=True, pattern=None):
Assaf Muller92fdc782018-05-31 10:32:47 -0400441 IP_VERSION_4 = neutron_lib_constants.IP_VERSION_4
442 IP_VERSION_6 = neutron_lib_constants.IP_VERSION_6
443
444 # Use 'ping6' for IPv6 addresses, 'ping' for IPv4 and hostnames
445 ip_version = (
446 IP_VERSION_6 if netaddr.valid_ipv6(host) else IP_VERSION_4)
447 cmd = (
448 'ping6' if ip_version == IP_VERSION_6 else 'ping')
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900449 if nic:
450 cmd = 'sudo {cmd} -I {nic}'.format(cmd=cmd, nic=nic)
Genadi Chereshnya6d10c6e2017-07-05 11:34:20 +0300451 if mtu:
452 if not fragmentation:
453 cmd += ' -M do'
454 size = str(net_utils.get_ping_payload_size(
Assaf Muller92fdc782018-05-31 10:32:47 -0400455 mtu=mtu, ip_version=ip_version))
Eduardo Olivaresf2b60542020-01-30 09:37:22 +0100456 if pattern:
457 cmd += ' -p {pattern}'.format(pattern=pattern)
Maciej Józefczyk3c324e02020-03-16 10:52:08 +0000458 cmd += ' -c{0} -W{0} -s{1} {2}'.format(count, size, host)
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900459 return source.exec_command(cmd)
460
461 def ping_remote():
462 try:
LIU Yulong68ab2452019-05-18 10:19:49 +0800463 result = ping_host(source, dest, count, nic=nic, mtu=mtu,
Eduardo Olivaresf2b60542020-01-30 09:37:22 +0100464 fragmentation=fragmentation,
465 pattern=pattern)
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900466
467 except lib_exc.SSHExecCommandFailed:
468 LOG.warning('Failed to ping IP: %s via a ssh connection '
469 'from: %s.', dest, source.host)
470 return not should_succeed
471 LOG.debug('ping result: %s', result)
Assaf Muller92fdc782018-05-31 10:32:47 -0400472
Roman Safronov12663cf2020-07-27 13:11:07 +0300473 if forbid_packet_loss and ' 0% packet loss' not in result:
474 LOG.debug('Packet loss detected')
475 return not should_succeed
476
Nurmatov Mamatisa1b1c9d32021-12-27 15:37:03 +0300477 if (check_response_ip and
478 validators.validate_ip_address(dest) is None):
Assaf Muller92fdc782018-05-31 10:32:47 -0400479 # Assert that the return traffic was from the correct
480 # source address.
481 from_source = 'from %s' % dest
482 self.assertIn(from_source, result)
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900483 return should_succeed
484
Assaf Muller92fdc782018-05-31 10:32:47 -0400485 return test_utils.call_until_true(
486 ping_remote, timeout or CONF.validation.ping_timeout, 1)
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900487
488 def check_remote_connectivity(self, source, dest, should_succeed=True,
Slawek Kaplonskib07251f2018-05-16 12:21:50 +0200489 nic=None, mtu=None, fragmentation=True,
LIU Yulong68ab2452019-05-18 10:19:49 +0800490 servers=None, timeout=None,
Eduardo Olivaresf2b60542020-01-30 09:37:22 +0100491 ping_count=CONF.validation.ping_count,
Nurmatov Mamatisa1b1c9d32021-12-27 15:37:03 +0300492 pattern=None, forbid_packet_loss=False,
493 check_response_ip=True):
Slawek Kaplonskib07251f2018-05-16 12:21:50 +0200494 try:
495 self.assertTrue(self._check_remote_connectivity(
LIU Yulong68ab2452019-05-18 10:19:49 +0800496 source, dest, ping_count, should_succeed, nic, mtu,
497 fragmentation,
Roman Safronov12663cf2020-07-27 13:11:07 +0300498 timeout=timeout, pattern=pattern,
Nurmatov Mamatisa1b1c9d32021-12-27 15:37:03 +0300499 forbid_packet_loss=forbid_packet_loss,
500 check_response_ip=check_response_ip))
Eduardo Olivares46fa4242022-04-18 12:47:43 +0200501 except SSH_EXC_TUPLE as ssh_e:
Slawek Kaplonskib07251f2018-05-16 12:21:50 +0200502 LOG.debug(ssh_e)
503 self._log_console_output(servers)
Slawek Kaplonski8cccfe02020-09-29 22:34:09 +0200504 self._log_local_network_status()
Slawek Kaplonskib07251f2018-05-16 12:21:50 +0200505 raise
506 except AssertionError:
507 self._log_console_output(servers)
Slawek Kaplonski8cccfe02020-09-29 22:34:09 +0200508 self._log_local_network_status()
Slawek Kaplonskib07251f2018-05-16 12:21:50 +0200509 raise
Chandan Kumarc125fd12017-11-15 19:41:01 +0530510
511 def ping_ip_address(self, ip_address, should_succeed=True,
512 ping_timeout=None, mtu=None):
513 # the code is taken from tempest/scenario/manager.py in tempest git
514 timeout = ping_timeout or CONF.validation.ping_timeout
515 cmd = ['ping', '-c1', '-w1']
516
517 if mtu:
518 cmd += [
519 # don't fragment
520 '-M', 'do',
521 # ping receives just the size of ICMP payload
522 '-s', str(net_utils.get_ping_payload_size(mtu, 4))
523 ]
524 cmd.append(ip_address)
525
526 def ping():
527 proc = subprocess.Popen(cmd,
528 stdout=subprocess.PIPE,
529 stderr=subprocess.PIPE)
530 proc.communicate()
531
532 return (proc.returncode == 0) == should_succeed
533
534 caller = test_utils.find_test_caller()
535 LOG.debug('%(caller)s begins to ping %(ip)s in %(timeout)s sec and the'
536 ' expected result is %(should_succeed)s', {
537 'caller': caller, 'ip': ip_address, 'timeout': timeout,
538 'should_succeed':
539 'reachable' if should_succeed else 'unreachable'
540 })
541 result = test_utils.call_until_true(ping, timeout, 1)
Manjeet Singh Bhatia8bbf8992019-03-04 11:59:57 -0800542
543 # To make sure ping_ip_address called by test works
544 # as expected.
545 self.assertTrue(result)
546
Chandan Kumarc125fd12017-11-15 19:41:01 +0530547 LOG.debug('%(caller)s finishes ping %(ip)s in %(timeout)s sec and the '
548 'ping result is %(result)s', {
549 'caller': caller, 'ip': ip_address, 'timeout': timeout,
550 'result': 'expected' if result else 'unexpected'
551 })
552 return result
Federico Ressie7417b72018-05-30 05:50:58 +0200553
554 def wait_for_server_status(self, server, status, client=None, **kwargs):
555 """Waits for a server to reach a given status.
556
557 :param server: mapping having schema {'id': <server_id>}
558 :param status: string status to wait for (es: 'ACTIVE')
559 :param clien: servers client (self.os_primary.servers_client as
560 default value)
561 """
562
563 client = client or self.os_primary.servers_client
564 waiters.wait_for_server_status(client, server['id'], status, **kwargs)
565
566 def wait_for_server_active(self, server, client=None):
567 """Waits for a server to reach active status.
568
569 :param server: mapping having schema {'id': <server_id>}
570 :param clien: servers client (self.os_primary.servers_client as
571 default value)
572 """
573 self.wait_for_server_status(
574 server, constants.SERVER_STATUS_ACTIVE, client)
Jakub Libosvar031fd5a2019-07-15 16:10:07 +0000575
Slawek Kaplonski2211eab2020-10-20 16:43:53 +0200576 def wait_for_guest_os_ready(self, server, client=None):
577 if not CONF.compute_feature_enabled.console_output:
578 LOG.debug('Console output not supported, cannot check if server '
Rodolfo Alonso Hernandezdff870b2020-11-06 08:41:44 +0000579 '%s is ready.', server['id'])
Slawek Kaplonski2211eab2020-10-20 16:43:53 +0200580 return
581
582 client = client or self.os_primary.servers_client
583
584 def system_booted():
585 console_output = client.get_console_output(server['id'])['output']
586 for line in console_output.split('\n'):
587 if 'login:' in line.lower():
588 return True
589 return False
590
591 try:
ccamposr1bd28ae2021-11-29 10:06:02 +0100592 utils.wait_until_true(system_booted, timeout=90, sleep=5)
Slawek Kaplonski2211eab2020-10-20 16:43:53 +0200593 except utils.WaitTimeout:
594 LOG.debug("No correct output in console of server %s found. "
595 "Guest operating system status can't be checked.",
596 server['id'])
597
Flavio Fernandesa1952c62020-10-02 06:39:08 -0400598 def check_servers_hostnames(self, servers, timeout=None, log_errors=True,
599 external_port=None):
Jakub Libosvar031fd5a2019-07-15 16:10:07 +0000600 """Compare hostnames of given servers with their names."""
601 try:
602 for server in servers:
603 kwargs = {}
nfridmand8969542020-06-02 14:59:09 +0300604 if timeout:
605 kwargs['timeout'] = timeout
Jakub Libosvar031fd5a2019-07-15 16:10:07 +0000606 try:
Flavio Fernandesa1952c62020-10-02 06:39:08 -0400607 kwargs['port'] = external_port or (
Slawek Kaplonskic4e963e2019-09-11 22:55:34 +0200608 server['port_forwarding_tcp']['external_port'])
Jakub Libosvar031fd5a2019-07-15 16:10:07 +0000609 except KeyError:
610 pass
611 ssh_client = ssh.Client(
612 self.fip['floating_ip_address'],
613 CONF.validation.image_ssh_user,
614 pkey=self.keypair['private_key'],
615 **kwargs)
616 self.assertIn(server['name'],
Rodolfo Alonso Hernandezaf394dd2020-11-12 14:26:13 +0000617 ssh_client.get_hostname())
Eduardo Olivares46fa4242022-04-18 12:47:43 +0200618 except SSH_EXC_TUPLE as ssh_e:
Jakub Libosvar031fd5a2019-07-15 16:10:07 +0000619 LOG.debug(ssh_e)
620 if log_errors:
621 self._log_console_output(servers)
Slawek Kaplonski8cccfe02020-09-29 22:34:09 +0200622 self._log_local_network_status()
Jakub Libosvar031fd5a2019-07-15 16:10:07 +0000623 raise
624 except AssertionError as assert_e:
625 LOG.debug(assert_e)
626 if log_errors:
627 self._log_console_output(servers)
Slawek Kaplonski8cccfe02020-09-29 22:34:09 +0200628 self._log_local_network_status()
Jakub Libosvar031fd5a2019-07-15 16:10:07 +0000629 raise
Slawek Kaplonskic4e963e2019-09-11 22:55:34 +0200630
Slawek Kaplonskifd4141f2020-03-14 14:34:00 +0100631 def ensure_nc_listen(self, ssh_client, port, protocol, echo_msg=None,
632 servers=None):
633 """Ensure that nc server listening on the given TCP/UDP port is up.
634
635 Listener is created always on remote host.
636 """
637 def spawn_and_check_process():
638 self.nc_listen(ssh_client, port, protocol, echo_msg, servers)
639 return utils.process_is_running(ssh_client, "nc")
640
641 utils.wait_until_true(spawn_and_check_process)
642
643 def nc_listen(self, ssh_client, port, protocol, echo_msg=None,
644 servers=None):
Slawek Kaplonskic4e963e2019-09-11 22:55:34 +0200645 """Create nc server listening on the given TCP/UDP port.
646
647 Listener is created always on remote host.
648 """
Slawek Kaplonskic4e963e2019-09-11 22:55:34 +0200649 try:
Slawek Kaplonskiaf83e832020-02-05 12:11:54 +0100650 return ssh_client.execute_script(
651 get_ncat_server_cmd(port, protocol, echo_msg),
Flavio Fernandesb056ac22020-07-01 14:57:13 -0400652 become_root=True, combine_stderr=True)
Eduardo Olivares46fa4242022-04-18 12:47:43 +0200653 except SSH_EXC_TUPLE as ssh_e:
Slawek Kaplonskic4e963e2019-09-11 22:55:34 +0200654 LOG.debug(ssh_e)
Slawek Kaplonskifd4141f2020-03-14 14:34:00 +0100655 self._log_console_output(servers)
Slawek Kaplonski8cccfe02020-09-29 22:34:09 +0200656 self._log_local_network_status()
Slawek Kaplonskic4e963e2019-09-11 22:55:34 +0200657 raise
658
Slawek Kaplonskib327a632023-02-23 07:59:12 +0100659 def nc_client(self, ip_address, port, protocol, ssh_client=None):
Slawek Kaplonskic4e963e2019-09-11 22:55:34 +0200660 """Check connectivity to TCP/UDP port at host via nc.
661
Slawek Kaplonskib327a632023-02-23 07:59:12 +0100662 If ssh_client is not given, it is executed locally on host where tests
663 are executed. Otherwise ssh_client object is used to execute it.
Slawek Kaplonskic4e963e2019-09-11 22:55:34 +0200664 """
Slawek Kaplonski663bbc22023-04-05 16:30:20 +0200665 cmd = get_ncat_client_cmd(ip_address, port, protocol,
666 ssh_client=ssh_client)
Slawek Kaplonski03bd62e2023-06-02 10:54:05 +0200667 result = shell.execute(cmd, ssh_client=ssh_client, check=False)
Slawek Kaplonskic4e963e2019-09-11 22:55:34 +0200668 return result.stdout
elajkat1f275e42021-10-15 12:47:55 +0200669
670 def _ensure_public_router(self, client=None, tenant_id=None):
671 """Retrieve a router for the given tenant id.
672
673 If a public router has been configured, it will be returned.
674
675 If a public router has not been configured, but a public
676 network has, a tenant router will be created and returned that
677 routes traffic to the public network.
678 """
679 if not client:
680 client = self.client
681 if not tenant_id:
Takashi Kajinamida451772023-03-22 00:19:39 +0900682 tenant_id = client.project_id
elajkat1f275e42021-10-15 12:47:55 +0200683 router_id = CONF.network.public_router_id
684 network_id = CONF.network.public_network_id
685 if router_id:
686 body = client.show_router(router_id)
687 return body['router']
688 elif network_id:
689 router = self.create_router_by_client()
690 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
691 client.delete_router, router['id'])
692 kwargs = {'external_gateway_info': dict(network_id=network_id)}
693 router = client.update_router(router['id'], **kwargs)['router']
694 return router
695 else:
Rodolfo Alonso Hernandez80df3662025-08-28 09:04:14 +0000696 raise Exception(_("Neither of 'public_router_id' or "
697 "'public_network_id' has been defined."))
elajkat1f275e42021-10-15 12:47:55 +0200698
699 def _update_router_admin_state(self, router, admin_state_up):
700 kwargs = dict(admin_state_up=admin_state_up)
701 router = self.client.update_router(
702 router['id'], **kwargs)['router']
703 self.assertEqual(admin_state_up, router['admin_state_up'])
Slawek Kaplonski32b0f8b2023-03-10 17:03:20 +0100704
705 def _check_cmd_installed_on_server(self, ssh_client, server, cmd):
706 try:
707 ssh_client.execute_script('which %s' % cmd)
708 except SSH_EXC_TUPLE as ssh_e:
709 LOG.debug(ssh_e)
710 self._log_console_output([server])
711 self._log_local_network_status()
712 raise
713 except exceptions.SSHScriptFailed:
714 raise self.skipException(
715 "%s is not available on server %s" % (cmd, server['id']))
Frode Nordahl1bb8e622023-10-16 15:16:34 +0200716
717
718class BaseAdminTempestTestCase(base_api.BaseAdminNetworkTest,
719 BaseTempestTestCase):
720 pass