blob: b9bf36f8df657ff5db92a378fd45ab76e553b374 [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
77def get_ncat_client_cmd(ip_address, port, protocol):
78 udp = ''
79 if protocol.lower() == neutron_lib_constants.PROTO_NAME_UDP:
80 udp = '-u'
81 cmd = 'echo "knock knock" | nc '
82 ncat_version = get_ncat_version()
Rodolfo Alonso Hernandez13546b82022-05-12 23:56:47 +000083 if ncat_version > packaging_version.Version('7.60'):
Slawek Kaplonskiaf83e832020-02-05 12:11:54 +010084 cmd += '-z '
85 cmd += '-w 1 %(udp)s %(host)s %(port)s' % {
86 'udp': udp, 'host': ip_address, 'port': port}
87 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
Roee Agiman6a0a18a2017-11-16 11:51:56 +0200193 def create_pingable_secgroup_rule(cls, secgroup_id=None,
194 client=None):
Federico Ressi4c590d72018-10-10 14:01:08 +0200195 """This rule is intended to permit inbound ping
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900196
Federico Ressi4c590d72018-10-10 14:01:08 +0200197 """
198 return cls.create_security_group_rule(
199 security_group_id=secgroup_id, client=client,
200 protocol=neutron_lib_constants.PROTO_NAME_ICMP,
201 direction=neutron_lib_constants.INGRESS_DIRECTION)
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900202
203 @classmethod
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300204 def create_router_by_client(cls, is_admin=False, **kwargs):
205 kwargs.update({'router_name': data_utils.rand_name('router'),
206 'admin_state_up': True,
207 'external_network_id': CONF.network.public_network_id})
208 if not is_admin:
209 router = cls.create_router(**kwargs)
210 else:
211 router = cls.create_admin_router(**kwargs)
Alex Stafeyevc4d9c352016-12-12 04:13:33 -0500212 LOG.debug("Created router %s", router['name'])
Slawek Kaplonskiedf3cba2021-04-21 10:34:02 +0200213 cls._wait_for_router_ha_active(router['id'])
Itzik Browne67ebb52016-05-15 05:34:41 +0000214 return router
215
ccamposre6b10042021-02-12 12:26:08 +0100216 @classmethod
Slawek Kaplonskiedf3cba2021-04-21 10:34:02 +0200217 def _wait_for_router_ha_active(cls, router_id):
218 router = cls.os_admin.network_client.show_router(router_id)['router']
219 if not router.get('ha'):
220 return
221
222 def _router_active_on_l3_agent():
223 agents = cls.os_admin.network_client.list_l3_agents_hosting_router(
224 router_id)['agents']
225 return "active" in [agent['ha_state'] for agent in agents]
226
227 error_msg = (
228 "Router %s is not active on any of the L3 agents" % router_id)
Slawek Kaplonskibb1532a2021-06-17 13:45:56 +0000229 # NOTE(slaweq): timeout here should be lower for sure, but due to
230 # the bug https://launchpad.net/bugs/1923633 let's wait even 10
231 # minutes until router will be active on some of the L3 agents
232 utils.wait_until_true(_router_active_on_l3_agent,
233 timeout=600, sleep=5,
234 exception=lib_exc.TimeoutException(error_msg))
Slawek Kaplonskiedf3cba2021-04-21 10:34:02 +0200235
236 @classmethod
ccamposre6b10042021-02-12 12:26:08 +0100237 def skip_if_no_extension_enabled_in_l3_agents(cls, extension):
238 l3_agents = cls.os_admin.network_client.list_agents(
239 binary='neutron-l3-agent')['agents']
240 if not l3_agents:
241 # the tests should not be skipped when neutron-l3-agent does not
242 # exist (this validation doesn't apply to the setups like
243 # e.g. ML2/OVN)
244 return
245 for agent in l3_agents:
246 if extension in agent['configurations'].get('extensions', []):
247 return
248 raise cls.skipTest("No L3 agent with '%s' extension enabled found." %
249 extension)
250
Federico Ressibf877c82018-08-22 08:36:37 +0200251 @removals.remove(version='Stein',
252 message="Please use create_floatingip method instead of "
253 "create_and_associate_floatingip.")
Roee Agiman6a0a18a2017-11-16 11:51:56 +0200254 def create_and_associate_floatingip(self, port_id, client=None):
255 client = client or self.os_primary.network_client
Federico Ressibf877c82018-08-22 08:36:37 +0200256 return self.create_floatingip(port_id=port_id, client=client)
Itzik Browne67ebb52016-05-15 05:34:41 +0000257
Hongbin Lu965b03d2018-04-25 22:32:30 +0000258 def create_interface(cls, server_id, port_id, client=None):
259 client = client or cls.os_primary.interfaces_client
260 body = client.create_interface(server_id, port_id=port_id)
261 return body['interfaceAttachment']
262
263 def delete_interface(cls, server_id, port_id, client=None):
264 client = client or cls.os_primary.interfaces_client
265 client.delete_interface(server_id, port_id=port_id)
266
Brian Haleyd11f4ec2019-08-13 12:09:57 -0400267 def setup_network_and_server(self, router=None, server_name=None,
268 network=None, **kwargs):
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300269 """Create network resources and a server.
Itzik Brownbac51dc2016-10-31 12:25:04 +0000270
271 Creating a network, subnet, router, keypair, security group
272 and a server.
273 """
Assaf Mullerd54ae6c2018-05-31 11:38:00 -0400274 self.network = network or self.create_network()
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +0000275 LOG.debug("Created network %s", self.network['name'])
276 self.subnet = self.create_subnet(self.network)
277 LOG.debug("Created subnet %s", self.subnet['id'])
Itzik Brown1ef813a2016-06-06 12:56:21 +0000278
Brian Haleyf86ac2e2017-06-21 10:43:50 -0400279 secgroup = self.os_primary.network_client.create_security_group(
Chandan Kumarc125fd12017-11-15 19:41:01 +0530280 name=data_utils.rand_name('secgroup'))
Alex Stafeyevc4d9c352016-12-12 04:13:33 -0500281 LOG.debug("Created security group %s",
282 secgroup['security_group']['name'])
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +0000283 self.security_groups.append(secgroup['security_group'])
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300284 if not router:
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +0000285 router = self.create_router_by_client(**kwargs)
286 self.create_router_interface(router['id'], self.subnet['id'])
287 self.keypair = self.create_keypair()
288 self.create_loginable_secgroup_rule(
Itzik Brownbac51dc2016-10-31 12:25:04 +0000289 secgroup_id=secgroup['security_group']['id'])
Assaf Muller92fdc782018-05-31 10:32:47 -0400290
291 server_kwargs = {
292 'flavor_ref': CONF.compute.flavor_ref,
293 'image_ref': CONF.compute.image_ref,
294 'key_name': self.keypair['name'],
295 'networks': [{'uuid': self.network['id']}],
296 'security_groups': [{'name': secgroup['security_group']['name']}],
297 }
298 if server_name is not None:
299 server_kwargs['name'] = server_name
300
301 self.server = self.create_server(**server_kwargs)
Jakub Libosvar1345d9d2017-06-09 13:59:05 +0000302 self.port = self.client.list_ports(network_id=self.network['id'],
303 device_id=self.server[
304 'server']['id'])['ports'][0]
Federico Ressibf877c82018-08-22 08:36:37 +0200305 self.fip = self.create_floatingip(port=self.port)
Jakub Libosvarc0c2f1d2017-01-31 12:12:21 -0500306
Bence Romsics2abbc922020-09-30 16:10:07 +0200307 def check_connectivity(self, host, ssh_user=None, ssh_key=None,
308 servers=None, ssh_timeout=None, ssh_client=None):
309 # Either ssh_client or ssh_user+ssh_key is mandatory.
310 if ssh_client is None:
311 ssh_client = ssh.Client(host, ssh_user,
312 pkey=ssh_key, timeout=ssh_timeout)
Jakub Libosvarc0c2f1d2017-01-31 12:12:21 -0500313 try:
314 ssh_client.test_connection_auth()
Eduardo Olivares46fa4242022-04-18 12:47:43 +0200315 except SSH_EXC_TUPLE as ssh_e:
Jakub Libosvarc0c2f1d2017-01-31 12:12:21 -0500316 LOG.debug(ssh_e)
317 self._log_console_output(servers)
Rodolfo Alonso Hernandez4849f002020-01-16 16:01:10 +0000318 self._log_local_network_status()
Jakub Libosvarc0c2f1d2017-01-31 12:12:21 -0500319 raise
320
321 def _log_console_output(self, servers=None):
322 if not CONF.compute_feature_enabled.console_output:
323 LOG.debug('Console output not supported, cannot log')
324 return
325 if not servers:
Brian Haleyf86ac2e2017-06-21 10:43:50 -0400326 servers = self.os_primary.servers_client.list_servers()
Jakub Libosvarc0c2f1d2017-01-31 12:12:21 -0500327 servers = servers['servers']
328 for server in servers:
Slawek Kaplonskicff79232020-03-03 14:12:18 +0100329 # NOTE(slaweq): sometimes servers are passed in dictionary with
330 # "server" key as first level key and in other cases it may be that
331 # it is just the "inner" dict without "server" key. Lets try to
332 # handle both cases
333 server = server.get("server") or server
Jakub Libosvarc0c2f1d2017-01-31 12:12:21 -0500334 try:
335 console_output = (
Brian Haleyf86ac2e2017-06-21 10:43:50 -0400336 self.os_primary.servers_client.get_console_output(
Jakub Libosvarc0c2f1d2017-01-31 12:12:21 -0500337 server['id'])['output'])
338 LOG.debug('Console output for %s\nbody=\n%s',
339 server['id'], console_output)
340 except lib_exc.NotFound:
341 LOG.debug("Server %s disappeared(deleted) while looking "
342 "for the console log", server['id'])
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900343
Rodolfo Alonso Hernandez4849f002020-01-16 16:01:10 +0000344 def _log_local_network_status(self):
Slawek Kaplonski8033af72020-05-05 12:01:37 +0200345 self._log_ns_network_status()
346 for ns_name in ip_utils.IPCommand().list_namespaces():
347 self._log_ns_network_status(ns_name=ns_name)
348
349 def _log_ns_network_status(self, ns_name=None):
Rodolfo Alonso Hernandez9817d4f2020-11-17 08:50:50 +0000350 try:
351 local_ips = ip_utils.IPCommand(namespace=ns_name).list_addresses()
352 local_routes = ip_utils.IPCommand(namespace=ns_name).list_routes()
Rodolfo Alonso Hernandezc134ea92021-04-14 15:15:01 +0000353 arp_table = ip_utils.arp_table(namespace=ns_name)
354 iptables = ip_utils.list_iptables(namespace=ns_name)
355 lsockets = ip_utils.list_listening_sockets(namespace=ns_name)
Rodolfo Alonso Hernandez9817d4f2020-11-17 08:50:50 +0000356 except exceptions.ShellCommandFailed:
357 LOG.debug('Namespace %s has been deleted synchronously during the '
358 'host network collection process', ns_name)
359 return
360
Slawek Kaplonski8033af72020-05-05 12:01:37 +0200361 LOG.debug('Namespace %s; IP Addresses:\n%s',
362 ns_name, '\n'.join(str(r) for r in local_ips))
Slawek Kaplonski8033af72020-05-05 12:01:37 +0200363 LOG.debug('Namespace %s; Local routes:\n%s',
364 ns_name, '\n'.join(str(r) for r in local_routes))
Slawek Kaplonski8033af72020-05-05 12:01:37 +0200365 LOG.debug('Namespace %s; Local ARP table:\n%s',
366 ns_name, '\n'.join(str(r) for r in arp_table))
Rodolfo Alonso Hernandezc134ea92021-04-14 15:15:01 +0000367 LOG.debug('Namespace %s; Local iptables:\n%s', ns_name, iptables)
368 LOG.debug('Namespace %s; Listening sockets:\n%s', ns_name, lsockets)
Rodolfo Alonso Hernandez4849f002020-01-16 16:01:10 +0000369
LIU Yulong68ab2452019-05-18 10:19:49 +0800370 def _check_remote_connectivity(self, source, dest, count,
371 should_succeed=True,
Assaf Muller92fdc782018-05-31 10:32:47 -0400372 nic=None, mtu=None, fragmentation=True,
Roman Safronov12663cf2020-07-27 13:11:07 +0300373 timeout=None, pattern=None,
Nurmatov Mamatisa1b1c9d32021-12-27 15:37:03 +0300374 forbid_packet_loss=False,
375 check_response_ip=True):
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900376 """check ping server via source ssh connection
377
378 :param source: RemoteClient: an ssh connection from which to ping
379 :param dest: and IP to ping against
LIU Yulong68ab2452019-05-18 10:19:49 +0800380 :param count: Number of ping packet(s) to send
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900381 :param should_succeed: boolean should ping succeed or not
382 :param nic: specific network interface to ping from
Genadi Chereshnya6d10c6e2017-07-05 11:34:20 +0300383 :param mtu: mtu size for the packet to be sent
384 :param fragmentation: Flag for packet fragmentation
LIU Yulong68ab2452019-05-18 10:19:49 +0800385 :param timeout: Timeout for all ping packet(s) to succeed
Eduardo Olivaresf2b60542020-01-30 09:37:22 +0100386 :param pattern: hex digits included in ICMP messages
Roman Safronov12663cf2020-07-27 13:11:07 +0300387 :param forbid_packet_loss: forbid or allow some lost packets
Nurmatov Mamatisa1b1c9d32021-12-27 15:37:03 +0300388 :param check_response_ip: check response ip
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900389 :returns: boolean -- should_succeed == ping
390 :returns: ping is false if ping failed
391 """
LIU Yulong68ab2452019-05-18 10:19:49 +0800392 def ping_host(source, host, count,
Genadi Chereshnya6d10c6e2017-07-05 11:34:20 +0300393 size=CONF.validation.ping_size, nic=None, mtu=None,
Eduardo Olivaresf2b60542020-01-30 09:37:22 +0100394 fragmentation=True, pattern=None):
Assaf Muller92fdc782018-05-31 10:32:47 -0400395 IP_VERSION_4 = neutron_lib_constants.IP_VERSION_4
396 IP_VERSION_6 = neutron_lib_constants.IP_VERSION_6
397
398 # Use 'ping6' for IPv6 addresses, 'ping' for IPv4 and hostnames
399 ip_version = (
400 IP_VERSION_6 if netaddr.valid_ipv6(host) else IP_VERSION_4)
401 cmd = (
402 'ping6' if ip_version == IP_VERSION_6 else 'ping')
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900403 if nic:
404 cmd = 'sudo {cmd} -I {nic}'.format(cmd=cmd, nic=nic)
Genadi Chereshnya6d10c6e2017-07-05 11:34:20 +0300405 if mtu:
406 if not fragmentation:
407 cmd += ' -M do'
408 size = str(net_utils.get_ping_payload_size(
Assaf Muller92fdc782018-05-31 10:32:47 -0400409 mtu=mtu, ip_version=ip_version))
Eduardo Olivaresf2b60542020-01-30 09:37:22 +0100410 if pattern:
411 cmd += ' -p {pattern}'.format(pattern=pattern)
Maciej Józefczyk3c324e02020-03-16 10:52:08 +0000412 cmd += ' -c{0} -W{0} -s{1} {2}'.format(count, size, host)
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900413 return source.exec_command(cmd)
414
415 def ping_remote():
416 try:
LIU Yulong68ab2452019-05-18 10:19:49 +0800417 result = ping_host(source, dest, count, nic=nic, mtu=mtu,
Eduardo Olivaresf2b60542020-01-30 09:37:22 +0100418 fragmentation=fragmentation,
419 pattern=pattern)
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900420
421 except lib_exc.SSHExecCommandFailed:
422 LOG.warning('Failed to ping IP: %s via a ssh connection '
423 'from: %s.', dest, source.host)
424 return not should_succeed
425 LOG.debug('ping result: %s', result)
Assaf Muller92fdc782018-05-31 10:32:47 -0400426
Roman Safronov12663cf2020-07-27 13:11:07 +0300427 if forbid_packet_loss and ' 0% packet loss' not in result:
428 LOG.debug('Packet loss detected')
429 return not should_succeed
430
Nurmatov Mamatisa1b1c9d32021-12-27 15:37:03 +0300431 if (check_response_ip and
432 validators.validate_ip_address(dest) is None):
Assaf Muller92fdc782018-05-31 10:32:47 -0400433 # Assert that the return traffic was from the correct
434 # source address.
435 from_source = 'from %s' % dest
436 self.assertIn(from_source, result)
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900437 return should_succeed
438
Assaf Muller92fdc782018-05-31 10:32:47 -0400439 return test_utils.call_until_true(
440 ping_remote, timeout or CONF.validation.ping_timeout, 1)
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900441
442 def check_remote_connectivity(self, source, dest, should_succeed=True,
Slawek Kaplonskib07251f2018-05-16 12:21:50 +0200443 nic=None, mtu=None, fragmentation=True,
LIU Yulong68ab2452019-05-18 10:19:49 +0800444 servers=None, timeout=None,
Eduardo Olivaresf2b60542020-01-30 09:37:22 +0100445 ping_count=CONF.validation.ping_count,
Nurmatov Mamatisa1b1c9d32021-12-27 15:37:03 +0300446 pattern=None, forbid_packet_loss=False,
447 check_response_ip=True):
Slawek Kaplonskib07251f2018-05-16 12:21:50 +0200448 try:
449 self.assertTrue(self._check_remote_connectivity(
LIU Yulong68ab2452019-05-18 10:19:49 +0800450 source, dest, ping_count, should_succeed, nic, mtu,
451 fragmentation,
Roman Safronov12663cf2020-07-27 13:11:07 +0300452 timeout=timeout, pattern=pattern,
Nurmatov Mamatisa1b1c9d32021-12-27 15:37:03 +0300453 forbid_packet_loss=forbid_packet_loss,
454 check_response_ip=check_response_ip))
Eduardo Olivares46fa4242022-04-18 12:47:43 +0200455 except SSH_EXC_TUPLE as ssh_e:
Slawek Kaplonskib07251f2018-05-16 12:21:50 +0200456 LOG.debug(ssh_e)
457 self._log_console_output(servers)
Slawek Kaplonski8cccfe02020-09-29 22:34:09 +0200458 self._log_local_network_status()
Slawek Kaplonskib07251f2018-05-16 12:21:50 +0200459 raise
460 except AssertionError:
461 self._log_console_output(servers)
Slawek Kaplonski8cccfe02020-09-29 22:34:09 +0200462 self._log_local_network_status()
Slawek Kaplonskib07251f2018-05-16 12:21:50 +0200463 raise
Chandan Kumarc125fd12017-11-15 19:41:01 +0530464
465 def ping_ip_address(self, ip_address, should_succeed=True,
466 ping_timeout=None, mtu=None):
467 # the code is taken from tempest/scenario/manager.py in tempest git
468 timeout = ping_timeout or CONF.validation.ping_timeout
469 cmd = ['ping', '-c1', '-w1']
470
471 if mtu:
472 cmd += [
473 # don't fragment
474 '-M', 'do',
475 # ping receives just the size of ICMP payload
476 '-s', str(net_utils.get_ping_payload_size(mtu, 4))
477 ]
478 cmd.append(ip_address)
479
480 def ping():
481 proc = subprocess.Popen(cmd,
482 stdout=subprocess.PIPE,
483 stderr=subprocess.PIPE)
484 proc.communicate()
485
486 return (proc.returncode == 0) == should_succeed
487
488 caller = test_utils.find_test_caller()
489 LOG.debug('%(caller)s begins to ping %(ip)s in %(timeout)s sec and the'
490 ' expected result is %(should_succeed)s', {
491 'caller': caller, 'ip': ip_address, 'timeout': timeout,
492 'should_succeed':
493 'reachable' if should_succeed else 'unreachable'
494 })
495 result = test_utils.call_until_true(ping, timeout, 1)
Manjeet Singh Bhatia8bbf8992019-03-04 11:59:57 -0800496
497 # To make sure ping_ip_address called by test works
498 # as expected.
499 self.assertTrue(result)
500
Chandan Kumarc125fd12017-11-15 19:41:01 +0530501 LOG.debug('%(caller)s finishes ping %(ip)s in %(timeout)s sec and the '
502 'ping result is %(result)s', {
503 'caller': caller, 'ip': ip_address, 'timeout': timeout,
504 'result': 'expected' if result else 'unexpected'
505 })
506 return result
Federico Ressie7417b72018-05-30 05:50:58 +0200507
508 def wait_for_server_status(self, server, status, client=None, **kwargs):
509 """Waits for a server to reach a given status.
510
511 :param server: mapping having schema {'id': <server_id>}
512 :param status: string status to wait for (es: 'ACTIVE')
513 :param clien: servers client (self.os_primary.servers_client as
514 default value)
515 """
516
517 client = client or self.os_primary.servers_client
518 waiters.wait_for_server_status(client, server['id'], status, **kwargs)
519
520 def wait_for_server_active(self, server, client=None):
521 """Waits for a server to reach active status.
522
523 :param server: mapping having schema {'id': <server_id>}
524 :param clien: servers client (self.os_primary.servers_client as
525 default value)
526 """
527 self.wait_for_server_status(
528 server, constants.SERVER_STATUS_ACTIVE, client)
Jakub Libosvar031fd5a2019-07-15 16:10:07 +0000529
Slawek Kaplonski2211eab2020-10-20 16:43:53 +0200530 def wait_for_guest_os_ready(self, server, client=None):
531 if not CONF.compute_feature_enabled.console_output:
532 LOG.debug('Console output not supported, cannot check if server '
Rodolfo Alonso Hernandezdff870b2020-11-06 08:41:44 +0000533 '%s is ready.', server['id'])
Slawek Kaplonski2211eab2020-10-20 16:43:53 +0200534 return
535
536 client = client or self.os_primary.servers_client
537
538 def system_booted():
539 console_output = client.get_console_output(server['id'])['output']
540 for line in console_output.split('\n'):
541 if 'login:' in line.lower():
542 return True
543 return False
544
545 try:
ccamposr1bd28ae2021-11-29 10:06:02 +0100546 utils.wait_until_true(system_booted, timeout=90, sleep=5)
Slawek Kaplonski2211eab2020-10-20 16:43:53 +0200547 except utils.WaitTimeout:
548 LOG.debug("No correct output in console of server %s found. "
549 "Guest operating system status can't be checked.",
550 server['id'])
551
Flavio Fernandesa1952c62020-10-02 06:39:08 -0400552 def check_servers_hostnames(self, servers, timeout=None, log_errors=True,
553 external_port=None):
Jakub Libosvar031fd5a2019-07-15 16:10:07 +0000554 """Compare hostnames of given servers with their names."""
555 try:
556 for server in servers:
557 kwargs = {}
nfridmand8969542020-06-02 14:59:09 +0300558 if timeout:
559 kwargs['timeout'] = timeout
Jakub Libosvar031fd5a2019-07-15 16:10:07 +0000560 try:
Flavio Fernandesa1952c62020-10-02 06:39:08 -0400561 kwargs['port'] = external_port or (
Slawek Kaplonskic4e963e2019-09-11 22:55:34 +0200562 server['port_forwarding_tcp']['external_port'])
Jakub Libosvar031fd5a2019-07-15 16:10:07 +0000563 except KeyError:
564 pass
565 ssh_client = ssh.Client(
566 self.fip['floating_ip_address'],
567 CONF.validation.image_ssh_user,
568 pkey=self.keypair['private_key'],
569 **kwargs)
570 self.assertIn(server['name'],
Rodolfo Alonso Hernandezaf394dd2020-11-12 14:26:13 +0000571 ssh_client.get_hostname())
Eduardo Olivares46fa4242022-04-18 12:47:43 +0200572 except SSH_EXC_TUPLE as ssh_e:
Jakub Libosvar031fd5a2019-07-15 16:10:07 +0000573 LOG.debug(ssh_e)
574 if log_errors:
575 self._log_console_output(servers)
Slawek Kaplonski8cccfe02020-09-29 22:34:09 +0200576 self._log_local_network_status()
Jakub Libosvar031fd5a2019-07-15 16:10:07 +0000577 raise
578 except AssertionError as assert_e:
579 LOG.debug(assert_e)
580 if log_errors:
581 self._log_console_output(servers)
Slawek Kaplonski8cccfe02020-09-29 22:34:09 +0200582 self._log_local_network_status()
Jakub Libosvar031fd5a2019-07-15 16:10:07 +0000583 raise
Slawek Kaplonskic4e963e2019-09-11 22:55:34 +0200584
Slawek Kaplonskifd4141f2020-03-14 14:34:00 +0100585 def ensure_nc_listen(self, ssh_client, port, protocol, echo_msg=None,
586 servers=None):
587 """Ensure that nc server listening on the given TCP/UDP port is up.
588
589 Listener is created always on remote host.
590 """
591 def spawn_and_check_process():
592 self.nc_listen(ssh_client, port, protocol, echo_msg, servers)
593 return utils.process_is_running(ssh_client, "nc")
594
595 utils.wait_until_true(spawn_and_check_process)
596
597 def nc_listen(self, ssh_client, port, protocol, echo_msg=None,
598 servers=None):
Slawek Kaplonskic4e963e2019-09-11 22:55:34 +0200599 """Create nc server listening on the given TCP/UDP port.
600
601 Listener is created always on remote host.
602 """
Slawek Kaplonskic4e963e2019-09-11 22:55:34 +0200603 try:
Slawek Kaplonskiaf83e832020-02-05 12:11:54 +0100604 return ssh_client.execute_script(
605 get_ncat_server_cmd(port, protocol, echo_msg),
Flavio Fernandesb056ac22020-07-01 14:57:13 -0400606 become_root=True, combine_stderr=True)
Eduardo Olivares46fa4242022-04-18 12:47:43 +0200607 except SSH_EXC_TUPLE as ssh_e:
Slawek Kaplonskic4e963e2019-09-11 22:55:34 +0200608 LOG.debug(ssh_e)
Slawek Kaplonskifd4141f2020-03-14 14:34:00 +0100609 self._log_console_output(servers)
Slawek Kaplonski8cccfe02020-09-29 22:34:09 +0200610 self._log_local_network_status()
Slawek Kaplonskic4e963e2019-09-11 22:55:34 +0200611 raise
612
613 def nc_client(self, ip_address, port, protocol):
614 """Check connectivity to TCP/UDP port at host via nc.
615
616 Client is always executed locally on host where tests are executed.
617 """
Slawek Kaplonskiaf83e832020-02-05 12:11:54 +0100618 cmd = get_ncat_client_cmd(ip_address, port, protocol)
Slawek Kaplonskic4e963e2019-09-11 22:55:34 +0200619 result = shell.execute_local_command(cmd)
620 self.assertEqual(0, result.exit_status)
621 return result.stdout
elajkat1f275e42021-10-15 12:47:55 +0200622
623 def _ensure_public_router(self, client=None, tenant_id=None):
624 """Retrieve a router for the given tenant id.
625
626 If a public router has been configured, it will be returned.
627
628 If a public router has not been configured, but a public
629 network has, a tenant router will be created and returned that
630 routes traffic to the public network.
631 """
632 if not client:
633 client = self.client
634 if not tenant_id:
635 tenant_id = client.tenant_id
636 router_id = CONF.network.public_router_id
637 network_id = CONF.network.public_network_id
638 if router_id:
639 body = client.show_router(router_id)
640 return body['router']
641 elif network_id:
642 router = self.create_router_by_client()
643 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
644 client.delete_router, router['id'])
645 kwargs = {'external_gateway_info': dict(network_id=network_id)}
646 router = client.update_router(router['id'], **kwargs)['router']
647 return router
648 else:
649 raise Exception("Neither of 'public_router_id' or "
650 "'public_network_id' has been defined.")
651
652 def _update_router_admin_state(self, router, admin_state_up):
653 kwargs = dict(admin_state_up=admin_state_up)
654 router = self.client.update_router(
655 router['id'], **kwargs)['router']
656 self.assertEqual(admin_state_up, router['admin_state_up'])