blob: 178bf9965cb4ec94e185a0fc626f2280377dee85 [file] [log] [blame]
Daniel Mellado3c0aeab2016-01-29 11:30:25 +00001# Copyright 2012 OpenStack Foundation
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.
15
Ihar Hrachyshka59382252016-04-05 15:54:33 +020016import functools
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +020017import math
Lajos Katona2f904652018-08-23 14:04:56 +020018import time
Ihar Hrachyshka59382252016-04-05 15:54:33 +020019
Daniel Mellado3c0aeab2016-01-29 11:30:25 +000020import netaddr
Rodolfo Alonso Hernandez80df3662025-08-28 09:04:14 +000021from neutron_lib._i18n import _
Chandan Kumarc125fd12017-11-15 19:41:01 +053022from neutron_lib import constants as const
Lajos Katona2f904652018-08-23 14:04:56 +020023from oslo_log import log
Chandan Kumarc125fd12017-11-15 19:41:01 +053024from tempest.common import utils as tutils
Daniel Mellado3c0aeab2016-01-29 11:30:25 +000025from tempest.lib.common.utils import data_utils
26from tempest.lib import exceptions as lib_exc
27from tempest import test
28
Chandan Kumar667d3d32017-09-22 12:24:06 +053029from neutron_tempest_plugin.api import clients
30from neutron_tempest_plugin.common import constants
31from neutron_tempest_plugin.common import utils
32from neutron_tempest_plugin import config
33from neutron_tempest_plugin import exceptions
Daniel Mellado3c0aeab2016-01-29 11:30:25 +000034
35CONF = config.CONF
36
Lajos Katona2f904652018-08-23 14:04:56 +020037LOG = log.getLogger(__name__)
38
Daniel Mellado3c0aeab2016-01-29 11:30:25 +000039
40class BaseNetworkTest(test.BaseTestCase):
41
Brian Haleyae328b92018-10-09 19:51:54 -040042 """Base class for Neutron tests that use the Tempest Neutron REST client
Daniel Mellado3c0aeab2016-01-29 11:30:25 +000043
44 Per the Neutron API Guide, API v1.x was removed from the source code tree
45 (docs.openstack.org/api/openstack-network/2.0/content/Overview-d1e71.html)
46 Therefore, v2.x of the Neutron API is assumed. It is also assumed that the
47 following options are defined in the [network] section of etc/tempest.conf:
48
49 project_network_cidr with a block of cidr's from which smaller blocks
50 can be allocated for tenant networks
51
52 project_network_mask_bits with the mask bits to be used to partition
53 the block defined by tenant-network_cidr
54
55 Finally, it is assumed that the following option is defined in the
56 [service_available] section of etc/tempest.conf
57
58 neutron as True
59 """
60
61 force_tenant_isolation = False
62 credentials = ['primary']
63
64 # Default to ipv4.
Federico Ressi0ddc93b2018-04-09 12:01:48 +020065 _ip_version = const.IP_VERSION_4
Daniel Mellado3c0aeab2016-01-29 11:30:25 +000066
Federico Ressi61b564e2018-07-06 08:10:31 +020067 # Derive from BaseAdminNetworkTest class to have this initialized
68 admin_client = None
69
Federico Ressia69dcd52018-07-06 09:45:34 +020070 external_network_id = CONF.network.public_network_id
71
Maor Blausteinfc274a02024-02-08 13:35:15 +020072 __is_driver_ovn = None
73
74 @classmethod
75 def _is_driver_ovn(cls):
76 ovn_agents = cls.os_admin.network_client.list_agents(
77 binary='ovn-controller')['agents']
78 return len(ovn_agents) > 0
79
80 @property
81 def is_driver_ovn(self):
82 if self.__is_driver_ovn is None:
83 if hasattr(self, 'os_admin'):
84 self.__is_driver_ovn = self._is_driver_ovn()
85 return self.__is_driver_ovn
86
Daniel Mellado3c0aeab2016-01-29 11:30:25 +000087 @classmethod
88 def get_client_manager(cls, credential_type=None, roles=None,
89 force_new=None):
Genadi Chereshnyacc395c02016-07-25 12:17:37 +030090 manager = super(BaseNetworkTest, cls).get_client_manager(
91 credential_type=credential_type,
92 roles=roles,
93 force_new=force_new
94 )
Daniel Mellado3c0aeab2016-01-29 11:30:25 +000095 # Neutron uses a different clients manager than the one in the Tempest
Jens Harbott860b46a2017-11-15 21:23:15 +000096 # save the original in case mixed tests need it
97 if credential_type == 'primary':
98 cls.os_tempest = manager
Daniel Mellado3c0aeab2016-01-29 11:30:25 +000099 return clients.Manager(manager.credentials)
100
101 @classmethod
102 def skip_checks(cls):
103 super(BaseNetworkTest, cls).skip_checks()
104 if not CONF.service_available.neutron:
105 raise cls.skipException("Neutron support is required")
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200106 if (cls._ip_version == const.IP_VERSION_6 and
107 not CONF.network_feature_enabled.ipv6):
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000108 raise cls.skipException("IPv6 Tests are disabled.")
Jakub Libosvar1982aa12017-05-30 11:15:33 +0000109 for req_ext in getattr(cls, 'required_extensions', []):
Chandan Kumarc125fd12017-11-15 19:41:01 +0530110 if not tutils.is_extension_enabled(req_ext, 'network'):
Jakub Libosvar1982aa12017-05-30 11:15:33 +0000111 msg = "%s extension not enabled." % req_ext
112 raise cls.skipException(msg)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000113
114 @classmethod
115 def setup_credentials(cls):
116 # Create no network resources for these test.
117 cls.set_network_resources()
118 super(BaseNetworkTest, cls).setup_credentials()
119
120 @classmethod
121 def setup_clients(cls):
122 super(BaseNetworkTest, cls).setup_clients()
fumihiko kakumaa216fc12017-07-14 10:43:29 +0900123 cls.client = cls.os_primary.network_client
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000124
125 @classmethod
126 def resource_setup(cls):
127 super(BaseNetworkTest, cls).resource_setup()
128
129 cls.networks = []
Miguel Lavalle124378b2016-09-21 16:41:47 -0500130 cls.admin_networks = []
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000131 cls.subnets = []
Kevin Bentonba3651c2017-09-01 17:13:01 -0700132 cls.admin_subnets = []
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000133 cls.ports = []
134 cls.routers = []
135 cls.floating_ips = []
Slawek Kaplonski003fcae2019-05-26 22:38:35 +0200136 cls.port_forwardings = []
Nurmatov Mamatisa3f2bbb52021-10-20 14:33:54 +0300137 cls.local_ips = []
138 cls.local_ip_associations = []
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000139 cls.metering_labels = []
140 cls.service_profiles = []
141 cls.flavors = []
142 cls.metering_label_rules = []
143 cls.qos_rules = []
144 cls.qos_policies = []
145 cls.ethertype = "IPv" + str(cls._ip_version)
Miguel Lavalleb1c7a3d2021-01-31 19:05:22 -0600146 cls.address_groups = []
147 cls.admin_address_groups = []
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000148 cls.address_scopes = []
149 cls.admin_address_scopes = []
150 cls.subnetpools = []
151 cls.admin_subnetpools = []
Itzik Brownbac51dc2016-10-31 12:25:04 +0000152 cls.security_groups = []
Dongcan Ye2de722e2018-07-04 11:01:37 +0000153 cls.admin_security_groups = []
Slawek Kaplonskiaa22c9e2023-05-18 18:59:26 +0200154 cls.sg_rule_templates = []
Chandan Kumarc125fd12017-11-15 19:41:01 +0530155 cls.projects = []
Nguyen Phuong An67993fc2017-11-24 11:30:25 +0700156 cls.log_objects = []
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200157 cls.reserved_subnet_cidrs = set()
Federico Ressiab286e42018-06-19 09:52:10 +0200158 cls.keypairs = []
Federico Ressi82e83e32018-07-03 14:19:55 +0200159 cls.trunks = []
Kailun Qineaaf9782018-12-20 04:45:01 +0800160 cls.network_segment_ranges = []
Harald Jensåsc9782fa2019-06-03 22:35:41 +0200161 cls.conntrack_helpers = []
yangjianfeng2936a292022-02-04 11:22:11 +0800162 cls.ndp_proxies = []
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000163
164 @classmethod
yangjianfeng23e40c22020-11-22 08:42:18 +0000165 def reserve_external_subnet_cidrs(cls):
166 client = cls.os_admin.network_client
167 ext_nets = client.list_networks(
168 **{"router:external": True})['networks']
169 for ext_net in ext_nets:
170 ext_subnets = client.list_subnets(
171 network_id=ext_net['id'])['subnets']
172 for ext_subnet in ext_subnets:
173 cls.reserve_subnet_cidr(ext_subnet['cidr'])
174
175 @classmethod
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000176 def resource_cleanup(cls):
177 if CONF.service_available.neutron:
Federico Ressi82e83e32018-07-03 14:19:55 +0200178 # Clean up trunks
179 for trunk in cls.trunks:
180 cls._try_delete_resource(cls.delete_trunk, trunk)
181
yangjianfeng2936a292022-02-04 11:22:11 +0800182 # Clean up ndp proxy
183 for ndp_proxy in cls.ndp_proxies:
184 cls._try_delete_resource(cls.delete_ndp_proxy, ndp_proxy)
185
Slawek Kaplonski003fcae2019-05-26 22:38:35 +0200186 # Clean up port forwardings
187 for pf in cls.port_forwardings:
188 cls._try_delete_resource(cls.delete_port_forwarding, pf)
189
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000190 # Clean up floating IPs
191 for floating_ip in cls.floating_ips:
Federico Ressia69dcd52018-07-06 09:45:34 +0200192 cls._try_delete_resource(cls.delete_floatingip, floating_ip)
193
Nurmatov Mamatisa3f2bbb52021-10-20 14:33:54 +0300194 # Clean up Local IP Associations
195 for association in cls.local_ip_associations:
196 cls._try_delete_resource(cls.delete_local_ip_association,
197 association)
198 # Clean up Local IPs
199 for local_ip in cls.local_ips:
200 cls._try_delete_resource(cls.delete_local_ip,
201 local_ip)
202
Harald Jensåsc9782fa2019-06-03 22:35:41 +0200203 # Clean up conntrack helpers
204 for cth in cls.conntrack_helpers:
205 cls._try_delete_resource(cls.delete_conntrack_helper, cth)
206
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000207 # Clean up routers
208 for router in cls.routers:
209 cls._try_delete_resource(cls.delete_router,
210 router)
211 # Clean up metering label rules
212 for metering_label_rule in cls.metering_label_rules:
213 cls._try_delete_resource(
214 cls.admin_client.delete_metering_label_rule,
215 metering_label_rule['id'])
216 # Clean up metering labels
217 for metering_label in cls.metering_labels:
218 cls._try_delete_resource(
219 cls.admin_client.delete_metering_label,
220 metering_label['id'])
221 # Clean up flavors
222 for flavor in cls.flavors:
223 cls._try_delete_resource(
224 cls.admin_client.delete_flavor,
225 flavor['id'])
226 # Clean up service profiles
227 for service_profile in cls.service_profiles:
228 cls._try_delete_resource(
229 cls.admin_client.delete_service_profile,
230 service_profile['id'])
231 # Clean up ports
232 for port in cls.ports:
233 cls._try_delete_resource(cls.client.delete_port,
234 port['id'])
235 # Clean up subnets
236 for subnet in cls.subnets:
237 cls._try_delete_resource(cls.client.delete_subnet,
238 subnet['id'])
Kevin Bentonba3651c2017-09-01 17:13:01 -0700239 # Clean up admin subnets
240 for subnet in cls.admin_subnets:
241 cls._try_delete_resource(cls.admin_client.delete_subnet,
242 subnet['id'])
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000243 # Clean up networks
244 for network in cls.networks:
Federico Ressi61b564e2018-07-06 08:10:31 +0200245 cls._try_delete_resource(cls.delete_network, network)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000246
Miguel Lavalle124378b2016-09-21 16:41:47 -0500247 # Clean up admin networks
248 for network in cls.admin_networks:
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000249 cls._try_delete_resource(cls.admin_client.delete_network,
250 network['id'])
251
Itzik Brownbac51dc2016-10-31 12:25:04 +0000252 # Clean up security groups
Federico Ressi4c590d72018-10-10 14:01:08 +0200253 for security_group in cls.security_groups:
254 cls._try_delete_resource(cls.delete_security_group,
255 security_group)
Itzik Brownbac51dc2016-10-31 12:25:04 +0000256
Dongcan Ye2de722e2018-07-04 11:01:37 +0000257 # Clean up admin security groups
Federico Ressi4c590d72018-10-10 14:01:08 +0200258 for security_group in cls.admin_security_groups:
259 cls._try_delete_resource(cls.delete_security_group,
260 security_group,
261 client=cls.admin_client)
Dongcan Ye2de722e2018-07-04 11:01:37 +0000262
Slawek Kaplonskiaa22c9e2023-05-18 18:59:26 +0200263 # Clean up security group rule templates
264 for sg_rule_template in cls.sg_rule_templates:
265 cls._try_delete_resource(
266 cls.admin_client.delete_default_security_group_rule,
267 sg_rule_template['id'])
268
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000269 for subnetpool in cls.subnetpools:
270 cls._try_delete_resource(cls.client.delete_subnetpool,
271 subnetpool['id'])
272
273 for subnetpool in cls.admin_subnetpools:
274 cls._try_delete_resource(cls.admin_client.delete_subnetpool,
275 subnetpool['id'])
276
277 for address_scope in cls.address_scopes:
278 cls._try_delete_resource(cls.client.delete_address_scope,
279 address_scope['id'])
280
281 for address_scope in cls.admin_address_scopes:
282 cls._try_delete_resource(
283 cls.admin_client.delete_address_scope,
284 address_scope['id'])
285
Chandan Kumarc125fd12017-11-15 19:41:01 +0530286 for project in cls.projects:
287 cls._try_delete_resource(
288 cls.identity_admin_client.delete_project,
289 project['id'])
290
Sławek Kapłońskie100c4d2017-08-23 21:18:34 +0000291 # Clean up QoS rules
292 for qos_rule in cls.qos_rules:
293 cls._try_delete_resource(cls.admin_client.delete_qos_rule,
294 qos_rule['id'])
295 # Clean up QoS policies
296 # as all networks and ports are already removed, QoS policies
297 # shouldn't be "in use"
298 for qos_policy in cls.qos_policies:
299 cls._try_delete_resource(cls.admin_client.delete_qos_policy,
300 qos_policy['id'])
301
Nguyen Phuong An67993fc2017-11-24 11:30:25 +0700302 # Clean up log_objects
303 for log_object in cls.log_objects:
304 cls._try_delete_resource(cls.admin_client.delete_log,
305 log_object['id'])
306
Federico Ressiab286e42018-06-19 09:52:10 +0200307 for keypair in cls.keypairs:
308 cls._try_delete_resource(cls.delete_keypair, keypair)
309
Kailun Qineaaf9782018-12-20 04:45:01 +0800310 # Clean up network_segment_ranges
311 for network_segment_range in cls.network_segment_ranges:
312 cls._try_delete_resource(
313 cls.admin_client.delete_network_segment_range,
314 network_segment_range['id'])
315
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000316 super(BaseNetworkTest, cls).resource_cleanup()
317
318 @classmethod
319 def _try_delete_resource(cls, delete_callable, *args, **kwargs):
320 """Cleanup resources in case of test-failure
321
322 Some resources are explicitly deleted by the test.
323 If the test failed to delete a resource, this method will execute
324 the appropriate delete methods. Otherwise, the method ignores NotFound
325 exceptions thrown for resources that were correctly deleted by the
326 test.
327
328 :param delete_callable: delete method
329 :param args: arguments for delete method
330 :param kwargs: keyword arguments for delete method
331 """
332 try:
333 delete_callable(*args, **kwargs)
334 # if resource is not found, this means it was deleted in the test
335 except lib_exc.NotFound:
336 pass
337
338 @classmethod
Federico Ressi61b564e2018-07-06 08:10:31 +0200339 def create_network(cls, network_name=None, client=None, external=None,
340 shared=None, provider_network_type=None,
341 provider_physical_network=None,
342 provider_segmentation_id=None, **kwargs):
343 """Create a network.
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000344
Federico Ressi61b564e2018-07-06 08:10:31 +0200345 When client is not provider and admin_client is attribute is not None
346 (for example when using BaseAdminNetworkTest base class) and using any
347 of the convenience parameters (external, shared, provider_network_type,
348 provider_physical_network and provider_segmentation_id) it silently
349 uses admin_client. If the network is not shared then it uses the same
350 project_id as regular client.
351
352 :param network_name: Human-readable name of the network
353
354 :param client: client to be used for connecting to network service
355
356 :param external: indicates whether the network has an external routing
357 facility that's not managed by the networking service.
358
359 :param shared: indicates whether this resource is shared across all
360 projects. By default, only administrative users can change this value.
361 If True and admin_client attribute is not None, then the network is
362 created under administrative project.
363
364 :param provider_network_type: the type of physical network that this
365 network should be mapped to. For example, 'flat', 'vlan', 'vxlan', or
366 'gre'. Valid values depend on a networking back-end.
367
368 :param provider_physical_network: the physical network where this
369 network should be implemented. The Networking API v2.0 does not provide
370 a way to list available physical networks. For example, the Open
371 vSwitch plug-in configuration file defines a symbolic name that maps to
372 specific bridges on each compute host.
373
374 :param provider_segmentation_id: The ID of the isolated segment on the
375 physical network. The network_type attribute defines the segmentation
376 model. For example, if the network_type value is 'vlan', this ID is a
377 vlan identifier. If the network_type value is 'gre', this ID is a gre
378 key.
379
380 :param **kwargs: extra parameters to be forwarded to network service
381 """
382
383 name = (network_name or kwargs.pop('name', None) or
384 data_utils.rand_name('test-network-'))
385
386 # translate convenience parameters
387 admin_client_required = False
388 if provider_network_type:
389 admin_client_required = True
390 kwargs['provider:network_type'] = provider_network_type
391 if provider_physical_network:
392 admin_client_required = True
393 kwargs['provider:physical_network'] = provider_physical_network
394 if provider_segmentation_id:
395 admin_client_required = True
396 kwargs['provider:segmentation_id'] = provider_segmentation_id
397 if external is not None:
398 admin_client_required = True
399 kwargs['router:external'] = bool(external)
400 if shared is not None:
401 admin_client_required = True
402 kwargs['shared'] = bool(shared)
403
404 if not client:
405 if admin_client_required and cls.admin_client:
406 # For convenience silently switch to admin client
407 client = cls.admin_client
408 if not shared:
409 # Keep this network visible from current project
410 project_id = (kwargs.get('project_id') or
411 kwargs.get('tenant_id') or
Takashi Kajinamida451772023-03-22 00:19:39 +0900412 cls.client.project_id)
Federico Ressi61b564e2018-07-06 08:10:31 +0200413 kwargs.update(project_id=project_id, tenant_id=project_id)
414 else:
415 # Use default client
416 client = cls.client
417
418 network = client.create_network(name=name, **kwargs)['network']
419 network['client'] = client
420 cls.networks.append(network)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000421 return network
422
423 @classmethod
Federico Ressi61b564e2018-07-06 08:10:31 +0200424 def delete_network(cls, network, client=None):
425 client = client or network.get('client') or cls.client
426 client.delete_network(network['id'])
427
428 @classmethod
429 def create_shared_network(cls, network_name=None, **kwargs):
430 return cls.create_network(name=network_name, shared=True, **kwargs)
Miguel Lavalle124378b2016-09-21 16:41:47 -0500431
432 @classmethod
Sławek Kapłońskid98e27d2018-05-07 16:16:28 +0200433 def create_subnet(cls, network, gateway='', cidr=None, mask_bits=None,
Federico Ressi98f20ec2018-05-11 06:09:49 +0200434 ip_version=None, client=None, reserve_cidr=True,
Rodolfo Alonso Hernandez780d81e2024-01-14 10:02:13 +0000435 allocation_pool_size=None, **kwargs):
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200436 """Wrapper utility that returns a test subnet.
437
438 Convenient wrapper for client.create_subnet method. It reserves and
439 allocates CIDRs to avoid creating overlapping subnets.
440
441 :param network: network where to create the subnet
442 network['id'] must contain the ID of the network
443
444 :param gateway: gateway IP address
445 It can be a str or a netaddr.IPAddress
446 If gateway is not given, then it will use default address for
447 given subnet CIDR, like "192.168.0.1" for "192.168.0.0/24" CIDR
Sławek Kapłońskid98e27d2018-05-07 16:16:28 +0200448 if gateway is given as None then no gateway will be assigned
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200449
450 :param cidr: CIDR of the subnet to create
451 It can be either None, a str or a netaddr.IPNetwork instance
452
453 :param mask_bits: CIDR prefix length
454 It can be either None or a numeric value.
455 If cidr parameter is given then mask_bits is used to determinate a
456 sequence of valid CIDR to use as generated.
457 Please see netaddr.IPNetwork.subnet method documentation[1]
458
459 :param ip_version: ip version of generated subnet CIDRs
460 It can be None, IP_VERSION_4 or IP_VERSION_6
461 It has to match given either given CIDR and gateway
462
463 :param ip_version: numeric value (either IP_VERSION_4 or IP_VERSION_6)
464 this value must match CIDR and gateway IP versions if any of them is
465 given
466
467 :param client: client to be used to connect to network service
468
Federico Ressi98f20ec2018-05-11 06:09:49 +0200469 :param reserve_cidr: if True then it reserves assigned CIDR to avoid
470 using the same CIDR for further subnets in the scope of the same
471 test case class
472
Rodolfo Alonso Hernandez780d81e2024-01-14 10:02:13 +0000473 :param allocation_pool_size: if the CIDR is not defined, this method
474 will assign one in ``get_subnet_cidrs``. Once done, the allocation pool
475 will be defined reserving the number of IP addresses requested,
476 starting from the end of the assigned CIDR.
477
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200478 :param **kwargs: optional parameters to be forwarded to wrapped method
479
480 [1] http://netaddr.readthedocs.io/en/latest/tutorial_01.html#supernets-and-subnets # noqa
481 """
Rodolfo Alonso Hernandez780d81e2024-01-14 10:02:13 +0000482 def allocation_pool(cidr, pool_size):
483 start = str(netaddr.IPAddress(cidr.last) - pool_size)
484 end = str(netaddr.IPAddress(cidr.last) - 1)
485 return {'start': start, 'end': end}
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000486
487 # allow tests to use admin client
488 if not client:
489 client = cls.client
490
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200491 if gateway:
492 gateway_ip = netaddr.IPAddress(gateway)
493 if ip_version:
494 if ip_version != gateway_ip.version:
495 raise ValueError(
Rodolfo Alonso Hernandez80df3662025-08-28 09:04:14 +0000496 _("Gateway IP version doesn't match IP version"))
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000497 else:
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200498 ip_version = gateway_ip.version
Sławek Kapłońskid98e27d2018-05-07 16:16:28 +0200499 else:
500 ip_version = ip_version or cls._ip_version
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200501
502 for subnet_cidr in cls.get_subnet_cidrs(
503 ip_version=ip_version, cidr=cidr, mask_bits=mask_bits):
Federico Ressi98f20ec2018-05-11 06:09:49 +0200504 if gateway is not None:
505 kwargs['gateway_ip'] = str(gateway or (subnet_cidr.ip + 1))
Slawek Kaplonski21f53422018-11-02 16:02:09 +0100506 else:
507 kwargs['gateway_ip'] = None
Rodolfo Alonso Hernandez780d81e2024-01-14 10:02:13 +0000508 if allocation_pool_size:
509 kwargs['allocation_pools'] = [
510 allocation_pool(subnet_cidr, allocation_pool_size)]
Federico Ressi98f20ec2018-05-11 06:09:49 +0200511 try:
512 body = client.create_subnet(
513 network_id=network['id'],
514 cidr=str(subnet_cidr),
515 ip_version=subnet_cidr.version,
516 **kwargs)
517 break
518 except lib_exc.BadRequest as e:
519 if 'overlaps with another subnet' not in str(e):
520 raise
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000521 else:
522 message = 'Available CIDR for subnet creation could not be found'
523 raise ValueError(message)
524 subnet = body['subnet']
Kevin Bentonba3651c2017-09-01 17:13:01 -0700525 if client is cls.client:
526 cls.subnets.append(subnet)
527 else:
528 cls.admin_subnets.append(subnet)
Federico Ressi98f20ec2018-05-11 06:09:49 +0200529 if reserve_cidr:
530 cls.reserve_subnet_cidr(subnet_cidr)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000531 return subnet
532
533 @classmethod
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200534 def reserve_subnet_cidr(cls, addr, **ipnetwork_kwargs):
Slawek Kaplonski17e95512024-05-02 14:12:31 +0200535 """Reserve given subnet CIDR making sure it's not used by create_subnet
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200536
537 :param addr: the CIDR address to be reserved
538 It can be a str or netaddr.IPNetwork instance
539
540 :param **ipnetwork_kwargs: optional netaddr.IPNetwork constructor
541 parameters
542 """
543
544 if not cls.try_reserve_subnet_cidr(addr, **ipnetwork_kwargs):
Rodolfo Alonso Hernandez80df3662025-08-28 09:04:14 +0000545 raise ValueError(_('Subnet CIDR already reserved: {0!r}'.format(
546 addr)))
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200547
548 @classmethod
549 def try_reserve_subnet_cidr(cls, addr, **ipnetwork_kwargs):
550 """Reserve given subnet CIDR if it hasn't been reserved before
551
552 :param addr: the CIDR address to be reserved
553 It can be a str or netaddr.IPNetwork instance
554
555 :param **ipnetwork_kwargs: optional netaddr.IPNetwork constructor
556 parameters
557
558 :return: True if it wasn't reserved before, False elsewhere.
559 """
560
561 subnet_cidr = netaddr.IPNetwork(addr, **ipnetwork_kwargs)
562 if subnet_cidr in cls.reserved_subnet_cidrs:
563 return False
564 else:
565 cls.reserved_subnet_cidrs.add(subnet_cidr)
566 return True
567
568 @classmethod
569 def get_subnet_cidrs(
570 cls, cidr=None, mask_bits=None, ip_version=None):
571 """Iterate over a sequence of unused subnet CIDR for IP version
572
573 :param cidr: CIDR of the subnet to create
574 It can be either None, a str or a netaddr.IPNetwork instance
575
576 :param mask_bits: CIDR prefix length
577 It can be either None or a numeric value.
578 If cidr parameter is given then mask_bits is used to determinate a
579 sequence of valid CIDR to use as generated.
580 Please see netaddr.IPNetwork.subnet method documentation[1]
581
582 :param ip_version: ip version of generated subnet CIDRs
583 It can be None, IP_VERSION_4 or IP_VERSION_6
584 It has to match given CIDR if given
585
586 :return: iterator over reserved CIDRs of type netaddr.IPNetwork
587
588 [1] http://netaddr.readthedocs.io/en/latest/tutorial_01.html#supernets-and-subnets # noqa
589 """
590
591 if cidr:
592 # Generate subnet CIDRs starting from given CIDR
593 # checking it is of requested IP version
594 cidr = netaddr.IPNetwork(cidr, version=ip_version)
595 else:
596 # Generate subnet CIDRs starting from configured values
597 ip_version = ip_version or cls._ip_version
598 if ip_version == const.IP_VERSION_4:
Takashi Kajinami938f2c72024-11-23 02:33:10 +0900599 mask_bits = mask_bits or CONF.network.project_network_mask_bits
600 cidr = netaddr.IPNetwork(CONF.network.project_network_cidr)
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200601 elif ip_version == const.IP_VERSION_6:
Takashi Kajinami938f2c72024-11-23 02:33:10 +0900602 mask_bits = CONF.network.project_network_v6_mask_bits
603 cidr = netaddr.IPNetwork(CONF.network.project_network_v6_cidr)
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200604 else:
Rodolfo Alonso Hernandez80df3662025-08-28 09:04:14 +0000605 raise ValueError(_(
606 'Invalid IP version: {!r}'.format(ip_version)))
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200607
608 if mask_bits:
609 subnet_cidrs = cidr.subnet(mask_bits)
610 else:
611 subnet_cidrs = iter([cidr])
612
613 for subnet_cidr in subnet_cidrs:
614 if subnet_cidr not in cls.reserved_subnet_cidrs:
615 yield subnet_cidr
616
617 @classmethod
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000618 def create_port(cls, network, **kwargs):
619 """Wrapper utility that returns a test port."""
Edan Davidd75e48e2018-01-03 02:49:52 -0500620 if CONF.network.port_vnic_type and 'binding:vnic_type' not in kwargs:
621 kwargs['binding:vnic_type'] = CONF.network.port_vnic_type
Glenn Van de Water5d9b1402020-09-16 15:14:14 +0200622 if CONF.network.port_profile and 'binding:profile' not in kwargs:
623 kwargs['binding:profile'] = CONF.network.port_profile
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000624 body = cls.client.create_port(network_id=network['id'],
625 **kwargs)
626 port = body['port']
627 cls.ports.append(port)
628 return port
629
630 @classmethod
631 def update_port(cls, port, **kwargs):
632 """Wrapper utility that updates a test port."""
633 body = cls.client.update_port(port['id'],
634 **kwargs)
635 return body['port']
636
637 @classmethod
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300638 def _create_router_with_client(
639 cls, client, router_name=None, admin_state_up=False,
640 external_network_id=None, enable_snat=None, **kwargs
641 ):
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000642 ext_gw_info = {}
643 if external_network_id:
644 ext_gw_info['network_id'] = external_network_id
YAMAMOTO Takashi9bd4f972017-06-20 12:49:30 +0900645 if enable_snat is not None:
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000646 ext_gw_info['enable_snat'] = enable_snat
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300647 body = client.create_router(
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000648 router_name, external_gateway_info=ext_gw_info,
649 admin_state_up=admin_state_up, **kwargs)
650 router = body['router']
651 cls.routers.append(router)
652 return router
653
654 @classmethod
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300655 def create_router(cls, *args, **kwargs):
656 return cls._create_router_with_client(cls.client, *args, **kwargs)
657
658 @classmethod
659 def create_admin_router(cls, *args, **kwargs):
rajat294495c042017-06-28 15:37:16 +0530660 return cls._create_router_with_client(cls.os_admin.network_client,
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300661 *args, **kwargs)
662
663 @classmethod
Federico Ressia69dcd52018-07-06 09:45:34 +0200664 def create_floatingip(cls, external_network_id=None, port=None,
665 client=None, **kwargs):
666 """Creates a floating IP.
667
668 Create a floating IP and schedule it for later deletion.
669 If a client is passed, then it is used for deleting the IP too.
670
671 :param external_network_id: network ID where to create
672 By default this is 'CONF.network.public_network_id'.
673
674 :param port: port to bind floating IP to
675 This is translated to 'port_id=port['id']'
676 By default it is None.
677
678 :param client: network client to be used for creating and cleaning up
679 the floating IP.
680
681 :param **kwargs: additional creation parameters to be forwarded to
682 networking server.
683 """
684
685 client = client or cls.client
686 external_network_id = (external_network_id or
687 cls.external_network_id)
688
689 if port:
Federico Ressi47f6ae42018-09-24 16:19:14 +0200690 port_id = kwargs.setdefault('port_id', port['id'])
691 if port_id != port['id']:
Rodolfo Alonso Hernandez80df3662025-08-28 09:04:14 +0000692 message = _(
693 "Port ID specified twice: {!s} != {!s}".format(
694 port_id, port['id']))
Federico Ressi47f6ae42018-09-24 16:19:14 +0200695 raise ValueError(message)
Federico Ressia69dcd52018-07-06 09:45:34 +0200696
697 fip = client.create_floatingip(external_network_id,
698 **kwargs)['floatingip']
699
700 # save client to be used later in cls.delete_floatingip
701 # for final cleanup
702 fip['client'] = client
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000703 cls.floating_ips.append(fip)
704 return fip
705
706 @classmethod
Federico Ressia69dcd52018-07-06 09:45:34 +0200707 def delete_floatingip(cls, floating_ip, client=None):
708 """Delete floating IP
709
710 :param client: Client to be used
711 If client is not given it will use the client used to create
712 the floating IP, or cls.client if unknown.
713 """
714
715 client = client or floating_ip.get('client') or cls.client
716 client.delete_floatingip(floating_ip['id'])
717
718 @classmethod
Slawek Kaplonski003fcae2019-05-26 22:38:35 +0200719 def create_port_forwarding(cls, fip_id, internal_port_id,
720 internal_port, external_port,
721 internal_ip_address=None, protocol="tcp",
722 client=None):
723 """Creates a port forwarding.
724
725 Create a port forwarding and schedule it for later deletion.
726 If a client is passed, then it is used for deleting the PF too.
727
728 :param fip_id: The ID of the floating IP address.
729
730 :param internal_port_id: The ID of the Neutron port associated to
731 the floating IP port forwarding.
732
733 :param internal_port: The TCP/UDP/other protocol port number of the
734 Neutron port fixed IP address associated to the floating ip
735 port forwarding.
736
737 :param external_port: The TCP/UDP/other protocol port number of
738 the port forwarding floating IP address.
739
740 :param internal_ip_address: The fixed IPv4 address of the Neutron
741 port associated to the floating IP port forwarding.
742
743 :param protocol: The IP protocol used in the floating IP port
744 forwarding.
745
746 :param client: network client to be used for creating and cleaning up
747 the floating IP port forwarding.
748 """
749
750 client = client or cls.client
751
752 pf = client.create_port_forwarding(
753 fip_id, internal_port_id, internal_port, external_port,
754 internal_ip_address, protocol)['port_forwarding']
755
756 # save ID of floating IP associated with port forwarding for final
757 # cleanup
758 pf['floatingip_id'] = fip_id
759
760 # save client to be used later in cls.delete_port_forwarding
761 # for final cleanup
762 pf['client'] = client
763 cls.port_forwardings.append(pf)
764 return pf
765
766 @classmethod
Flavio Fernandesa1952c62020-10-02 06:39:08 -0400767 def update_port_forwarding(cls, fip_id, pf_id, client=None, **kwargs):
768 """Wrapper utility for update_port_forwarding."""
769 client = client or cls.client
770 return client.update_port_forwarding(fip_id, pf_id, **kwargs)
771
772 @classmethod
Slawek Kaplonski003fcae2019-05-26 22:38:35 +0200773 def delete_port_forwarding(cls, pf, client=None):
774 """Delete port forwarding
775
776 :param client: Client to be used
777 If client is not given it will use the client used to create
778 the port forwarding, or cls.client if unknown.
779 """
780
781 client = client or pf.get('client') or cls.client
782 client.delete_port_forwarding(pf['floatingip_id'], pf['id'])
783
Nurmatov Mamatisa3f2bbb52021-10-20 14:33:54 +0300784 def create_local_ip(cls, network_id=None,
785 client=None, **kwargs):
786 """Creates a Local IP.
787
788 Create a Local IP and schedule it for later deletion.
789 If a client is passed, then it is used for deleting the IP too.
790
791 :param network_id: network ID where to create
792 By default this is 'CONF.network.public_network_id'.
793
794 :param client: network client to be used for creating and cleaning up
795 the Local IP.
796
797 :param **kwargs: additional creation parameters to be forwarded to
798 networking server.
799 """
800
801 client = client or cls.client
802 network_id = (network_id or
803 cls.external_network_id)
804
805 local_ip = client.create_local_ip(network_id,
806 **kwargs)['local_ip']
807
808 # save client to be used later in cls.delete_local_ip
809 # for final cleanup
810 local_ip['client'] = client
811 cls.local_ips.append(local_ip)
812 return local_ip
813
814 @classmethod
815 def delete_local_ip(cls, local_ip, client=None):
816 """Delete Local IP
817
818 :param client: Client to be used
819 If client is not given it will use the client used to create
820 the Local IP, or cls.client if unknown.
821 """
822
823 client = client or local_ip.get('client') or cls.client
824 client.delete_local_ip(local_ip['id'])
825
826 @classmethod
827 def create_local_ip_association(cls, local_ip_id, fixed_port_id,
828 fixed_ip_address=None, client=None):
829 """Creates a Local IP association.
830
831 Create a Local IP Association and schedule it for later deletion.
832 If a client is passed, then it is used for deleting the association
833 too.
834
835 :param local_ip_id: The ID of the Local IP.
836
837 :param fixed_port_id: The ID of the Neutron port
838 to be associated with the Local IP
839
840 :param fixed_ip_address: The fixed IPv4 address of the Neutron
841 port to be associated with the Local IP
842
843 :param client: network client to be used for creating and cleaning up
844 the Local IP Association.
845 """
846
847 client = client or cls.client
848
849 association = client.create_local_ip_association(
850 local_ip_id, fixed_port_id,
851 fixed_ip_address)['port_association']
852
853 # save ID of Local IP for final cleanup
854 association['local_ip_id'] = local_ip_id
855
856 # save client to be used later in
857 # cls.delete_local_ip_association for final cleanup
858 association['client'] = client
859 cls.local_ip_associations.append(association)
860 return association
861
862 @classmethod
863 def delete_local_ip_association(cls, association, client=None):
864
865 """Delete Local IP Association
866
867 :param client: Client to be used
868 If client is not given it will use the client used to create
869 the local IP association, or cls.client if unknown.
870 """
871
872 client = client or association.get('client') or cls.client
873 client.delete_local_ip_association(association['local_ip_id'],
874 association['fixed_port_id'])
875
Slawek Kaplonski003fcae2019-05-26 22:38:35 +0200876 @classmethod
Frode Nordahl1bb8e622023-10-16 15:16:34 +0200877 def create_router_interface(cls, router_id, subnet_id, client=None):
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000878 """Wrapper utility that returns a router interface."""
Frode Nordahl1bb8e622023-10-16 15:16:34 +0200879 client = client or cls.client
880 interface = client.add_router_interface_with_subnet_id(
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000881 router_id, subnet_id)
882 return interface
883
884 @classmethod
Bence Romsics46bd3af2019-09-13 10:52:41 +0200885 def add_extra_routes_atomic(cls, *args, **kwargs):
886 return cls.client.add_extra_routes_atomic(*args, **kwargs)
887
888 @classmethod
889 def remove_extra_routes_atomic(cls, *args, **kwargs):
890 return cls.client.remove_extra_routes_atomic(*args, **kwargs)
891
892 @classmethod
Sławek Kapłońskiff294062016-12-04 15:00:54 +0000893 def get_supported_qos_rule_types(cls):
894 body = cls.client.list_qos_rule_types()
895 return [rule_type['type'] for rule_type in body['rule_types']]
896
897 @classmethod
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200898 def create_qos_policy(cls, name, description=None, shared=False,
Rodolfo Alonso Hernandeze2d062f2020-01-14 17:11:42 +0000899 project_id=None, is_default=False):
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000900 """Wrapper utility that returns a test QoS policy."""
901 body = cls.admin_client.create_qos_policy(
Rodolfo Alonso Hernandeze2d062f2020-01-14 17:11:42 +0000902 name, description, shared, project_id, is_default)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000903 qos_policy = body['policy']
904 cls.qos_policies.append(qos_policy)
905 return qos_policy
906
907 @classmethod
elajkatdbb0b482021-05-04 17:20:07 +0200908 def create_qos_dscp_marking_rule(cls, policy_id, dscp_mark):
909 """Wrapper utility that creates and returns a QoS dscp rule."""
910 body = cls.admin_client.create_dscp_marking_rule(
911 policy_id, dscp_mark)
912 qos_rule = body['dscp_marking_rule']
913 cls.qos_rules.append(qos_rule)
914 return qos_rule
915
916 @classmethod
Jakub Libosvar83704832017-12-06 16:02:28 +0000917 def delete_router(cls, router, client=None):
918 client = client or cls.client
Aditya Vaja49819a72018-11-26 14:20:10 -0800919 if 'routes' in router:
920 client.remove_router_extra_routes(router['id'])
Jakub Libosvar83704832017-12-06 16:02:28 +0000921 body = client.list_router_interfaces(router['id'])
Chandan Kumarc125fd12017-11-15 19:41:01 +0530922 interfaces = [port for port in body['ports']
923 if port['device_owner'] in const.ROUTER_INTERFACE_OWNERS]
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000924 for i in interfaces:
925 try:
Jakub Libosvar83704832017-12-06 16:02:28 +0000926 client.remove_router_interface_with_subnet_id(
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000927 router['id'], i['fixed_ips'][0]['subnet_id'])
928 except lib_exc.NotFound:
929 pass
Jakub Libosvar83704832017-12-06 16:02:28 +0000930 client.delete_router(router['id'])
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000931
932 @classmethod
933 def create_address_scope(cls, name, is_admin=False, **kwargs):
934 if is_admin:
935 body = cls.admin_client.create_address_scope(name=name, **kwargs)
936 cls.admin_address_scopes.append(body['address_scope'])
937 else:
938 body = cls.client.create_address_scope(name=name, **kwargs)
939 cls.address_scopes.append(body['address_scope'])
940 return body['address_scope']
941
942 @classmethod
Igor Malinovskiyb80f1d02020-03-06 13:39:52 +0200943 def create_subnetpool(cls, name, is_admin=False, client=None, **kwargs):
944 if client is None:
945 client = cls.admin_client if is_admin else cls.client
946
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000947 if is_admin:
Igor Malinovskiyb80f1d02020-03-06 13:39:52 +0200948 body = client.create_subnetpool(name, **kwargs)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000949 cls.admin_subnetpools.append(body['subnetpool'])
950 else:
Igor Malinovskiyb80f1d02020-03-06 13:39:52 +0200951 body = client.create_subnetpool(name, **kwargs)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000952 cls.subnetpools.append(body['subnetpool'])
953 return body['subnetpool']
954
Chandan Kumarc125fd12017-11-15 19:41:01 +0530955 @classmethod
Miguel Lavalleb1c7a3d2021-01-31 19:05:22 -0600956 def create_address_group(cls, name, is_admin=False, **kwargs):
957 if is_admin:
958 body = cls.admin_client.create_address_group(name=name, **kwargs)
959 cls.admin_address_groups.append(body['address_group'])
960 else:
961 body = cls.client.create_address_group(name=name, **kwargs)
962 cls.address_groups.append(body['address_group'])
963 return body['address_group']
964
965 @classmethod
Chandan Kumarc125fd12017-11-15 19:41:01 +0530966 def create_project(cls, name=None, description=None):
967 test_project = name or data_utils.rand_name('test_project_')
968 test_description = description or data_utils.rand_name('desc_')
969 project = cls.identity_admin_client.create_project(
970 name=test_project,
971 description=test_description)['project']
972 cls.projects.append(project)
Dongcan Ye2de722e2018-07-04 11:01:37 +0000973 # Create a project will create a default security group.
Dongcan Ye2de722e2018-07-04 11:01:37 +0000974 sgs_list = cls.admin_client.list_security_groups(
975 tenant_id=project['id'])['security_groups']
Federico Ressi4c590d72018-10-10 14:01:08 +0200976 for security_group in sgs_list:
977 # Make sure delete_security_group method will use
978 # the admin client for this group
979 security_group['client'] = cls.admin_client
980 cls.security_groups.append(security_group)
Chandan Kumarc125fd12017-11-15 19:41:01 +0530981 return project
982
983 @classmethod
Federico Ressi4c590d72018-10-10 14:01:08 +0200984 def create_security_group(cls, name=None, project=None, client=None,
985 **kwargs):
986 if project:
987 client = client or cls.admin_client
988 project_id = kwargs.setdefault('project_id', project['id'])
989 tenant_id = kwargs.setdefault('tenant_id', project['id'])
990 if project_id != project['id'] or tenant_id != project['id']:
Rodolfo Alonso Hernandez80df3662025-08-28 09:04:14 +0000991 raise ValueError(_('Project ID specified multiple times'))
Federico Ressi4c590d72018-10-10 14:01:08 +0200992 else:
993 client = client or cls.client
994
995 name = name or data_utils.rand_name(cls.__name__)
996 security_group = client.create_security_group(name=name, **kwargs)[
997 'security_group']
998 security_group['client'] = client
999 cls.security_groups.append(security_group)
1000 return security_group
1001
1002 @classmethod
1003 def delete_security_group(cls, security_group, client=None):
1004 client = client or security_group.get('client') or cls.client
1005 client.delete_security_group(security_group['id'])
1006
1007 @classmethod
Slawek Kaplonskiaa22c9e2023-05-18 18:59:26 +02001008 def get_security_group(cls, name='default', client=None):
1009 client = client or cls.client
1010 security_groups = client.list_security_groups()['security_groups']
1011 for security_group in security_groups:
1012 if security_group['name'] == name:
1013 return security_group
Rodolfo Alonso Hernandez80df3662025-08-28 09:04:14 +00001014 raise ValueError(_("No such security group named {!r}".format(name)))
Slawek Kaplonskiaa22c9e2023-05-18 18:59:26 +02001015
1016 @classmethod
Federico Ressi4c590d72018-10-10 14:01:08 +02001017 def create_security_group_rule(cls, security_group=None, project=None,
1018 client=None, ip_version=None, **kwargs):
1019 if project:
1020 client = client or cls.admin_client
1021 project_id = kwargs.setdefault('project_id', project['id'])
1022 tenant_id = kwargs.setdefault('tenant_id', project['id'])
1023 if project_id != project['id'] or tenant_id != project['id']:
Rodolfo Alonso Hernandez80df3662025-08-28 09:04:14 +00001024 raise ValueError(_('Project ID specified multiple times'))
Federico Ressi4c590d72018-10-10 14:01:08 +02001025
1026 if 'security_group_id' not in kwargs:
1027 security_group = (security_group or
1028 cls.get_security_group(client=client))
1029
1030 if security_group:
1031 client = client or security_group.get('client')
1032 security_group_id = kwargs.setdefault('security_group_id',
1033 security_group['id'])
1034 if security_group_id != security_group['id']:
Rodolfo Alonso Hernandez80df3662025-08-28 09:04:14 +00001035 raise ValueError(
1036 _('Security group ID specified multiple times.'))
Federico Ressi4c590d72018-10-10 14:01:08 +02001037
1038 ip_version = ip_version or cls._ip_version
1039 default_params = (
1040 constants.DEFAULT_SECURITY_GROUP_RULE_PARAMS[ip_version])
Slawek Kaplonski83979b92022-12-15 14:15:12 +01001041 if (('remote_address_group_id' in kwargs or
1042 'remote_group_id' in kwargs) and
1043 'remote_ip_prefix' in default_params):
Miguel Lavalleb1c7a3d2021-01-31 19:05:22 -06001044 default_params.pop('remote_ip_prefix')
Federico Ressi4c590d72018-10-10 14:01:08 +02001045 for key, value in default_params.items():
1046 kwargs.setdefault(key, value)
1047
1048 client = client or cls.client
1049 return client.create_security_group_rule(**kwargs)[
1050 'security_group_rule']
1051
1052 @classmethod
Slawek Kaplonskiaa22c9e2023-05-18 18:59:26 +02001053 def create_default_security_group_rule(cls, **kwargs):
1054 body = cls.admin_client.create_default_security_group_rule(**kwargs)
1055 default_sg_rule = body['default_security_group_rule']
1056 cls.sg_rule_templates.append(default_sg_rule)
1057 return default_sg_rule
Chandan Kumarc125fd12017-11-15 19:41:01 +05301058
Federico Ressiab286e42018-06-19 09:52:10 +02001059 @classmethod
1060 def create_keypair(cls, client=None, name=None, **kwargs):
1061 client = client or cls.os_primary.keypairs_client
1062 name = name or data_utils.rand_name('keypair-test')
1063 keypair = client.create_keypair(name=name, **kwargs)['keypair']
1064
1065 # save client for later cleanup
1066 keypair['client'] = client
1067 cls.keypairs.append(keypair)
1068 return keypair
1069
1070 @classmethod
1071 def delete_keypair(cls, keypair, client=None):
1072 client = (client or keypair.get('client') or
1073 cls.os_primary.keypairs_client)
1074 client.delete_keypair(keypair_name=keypair['name'])
1075
Federico Ressi82e83e32018-07-03 14:19:55 +02001076 @classmethod
1077 def create_trunk(cls, port=None, subports=None, client=None, **kwargs):
1078 """Create network trunk
1079
1080 :param port: dictionary containing parent port ID (port['id'])
1081 :param client: client to be used for connecting to networking service
1082 :param **kwargs: extra parameters to be forwarded to network service
1083
1084 :returns: dictionary containing created trunk details
1085 """
1086 client = client or cls.client
1087
1088 if port:
1089 kwargs['port_id'] = port['id']
1090
1091 trunk = client.create_trunk(subports=subports, **kwargs)['trunk']
1092 # Save client reference for later deletion
1093 trunk['client'] = client
1094 cls.trunks.append(trunk)
1095 return trunk
1096
1097 @classmethod
Huifeng Le1c9f40b2018-11-07 01:14:21 +08001098 def delete_trunk(cls, trunk, client=None, detach_parent_port=True):
Federico Ressi82e83e32018-07-03 14:19:55 +02001099 """Delete network trunk
1100
1101 :param trunk: dictionary containing trunk ID (trunk['id'])
1102
1103 :param client: client to be used for connecting to networking service
1104 """
1105 client = client or trunk.get('client') or cls.client
1106 trunk.update(client.show_trunk(trunk['id'])['trunk'])
1107
1108 if not trunk['admin_state_up']:
1109 # Cannot touch trunk before admin_state_up is True
1110 client.update_trunk(trunk['id'], admin_state_up=True)
1111 if trunk['sub_ports']:
1112 # Removes trunk ports before deleting it
1113 cls._try_delete_resource(client.remove_subports, trunk['id'],
1114 trunk['sub_ports'])
1115
1116 # we have to detach the interface from the server before
1117 # the trunk can be deleted.
1118 parent_port = {'id': trunk['port_id']}
1119
1120 def is_parent_port_detached():
1121 parent_port.update(client.show_port(parent_port['id'])['port'])
1122 return not parent_port['device_id']
1123
Huifeng Le1c9f40b2018-11-07 01:14:21 +08001124 if detach_parent_port and not is_parent_port_detached():
Federico Ressi82e83e32018-07-03 14:19:55 +02001125 # this could probably happen when trunk is deleted and parent port
1126 # has been assigned to a VM that is still running. Here we are
1127 # assuming that device_id points to such VM.
1128 cls.os_primary.compute.InterfacesClient().delete_interface(
1129 parent_port['device_id'], parent_port['id'])
1130 utils.wait_until_true(is_parent_port_detached)
1131
1132 client.delete_trunk(trunk['id'])
1133
Harald Jensåsc9782fa2019-06-03 22:35:41 +02001134 @classmethod
1135 def create_conntrack_helper(cls, router_id, helper, protocol, port,
1136 client=None):
1137 """Create a conntrack helper
1138
1139 Create a conntrack helper and schedule it for later deletion. If a
1140 client is passed, then it is used for deleteing the CTH too.
1141
1142 :param router_id: The ID of the Neutron router associated to the
1143 conntrack helper.
1144
1145 :param helper: The conntrack helper module alias
1146
1147 :param protocol: The conntrack helper IP protocol used in the conntrack
1148 helper.
1149
1150 :param port: The conntrack helper IP protocol port number for the
1151 conntrack helper.
1152
1153 :param client: network client to be used for creating and cleaning up
1154 the conntrack helper.
1155 """
1156
1157 client = client or cls.client
1158
1159 cth = client.create_conntrack_helper(router_id, helper, protocol,
1160 port)['conntrack_helper']
1161
1162 # save ID of router associated with conntrack helper for final cleanup
1163 cth['router_id'] = router_id
1164
1165 # save client to be used later in cls.delete_conntrack_helper for final
1166 # cleanup
1167 cth['client'] = client
1168 cls.conntrack_helpers.append(cth)
1169 return cth
1170
1171 @classmethod
1172 def delete_conntrack_helper(cls, cth, client=None):
1173 """Delete conntrack helper
1174
1175 :param client: Client to be used
1176 If client is not given it will use the client used to create the
1177 conntrack helper, or cls.client if unknown.
1178 """
1179
1180 client = client or cth.get('client') or cls.client
1181 client.delete_conntrack_helper(cth['router_id'], cth['id'])
1182
yangjianfeng2936a292022-02-04 11:22:11 +08001183 @classmethod
1184 def create_ndp_proxy(cls, router_id, port_id, client=None, **kwargs):
1185 """Creates a ndp proxy.
1186
1187 Create a ndp proxy and schedule it for later deletion.
1188 If a client is passed, then it is used for deleting the NDP proxy too.
1189
1190 :param router_id: router ID where to create the ndp proxy.
1191
1192 :param port_id: port ID which the ndp proxy associate with
1193
1194 :param client: network client to be used for creating and cleaning up
1195 the ndp proxy.
1196
1197 :param **kwargs: additional creation parameters to be forwarded to
1198 networking server.
1199 """
1200 client = client or cls.client
1201
1202 data = {'router_id': router_id, 'port_id': port_id}
1203 if kwargs:
1204 data.update(kwargs)
1205 ndp_proxy = client.create_ndp_proxy(**data)['ndp_proxy']
1206
1207 # save client to be used later in cls.delete_ndp_proxy
1208 # for final cleanup
1209 ndp_proxy['client'] = client
1210 cls.ndp_proxies.append(ndp_proxy)
1211 return ndp_proxy
1212
1213 @classmethod
1214 def delete_ndp_proxy(cls, ndp_proxy, client=None):
1215 """Delete ndp proxy
1216
1217 :param client: Client to be used
1218 If client is not given it will use the client used to create
1219 the ndp proxy, or cls.client if unknown.
1220 """
1221 client = client or ndp_proxy.get('client') or cls.client
1222 client.delete_ndp_proxy(ndp_proxy['id'])
1223
Rodolfo Alonso Hernandez8f726122024-06-24 18:20:15 +00001224 @classmethod
1225 def get_loaded_network_extensions(cls):
1226 """Return the network service loaded extensions
1227
1228 :return: list of strings with the alias of the network service loaded
1229 extensions.
1230 """
1231 body = cls.client.list_extensions()
1232 return [net_ext['alias'] for net_ext in body['extensions']]
1233
Daniel Mellado3c0aeab2016-01-29 11:30:25 +00001234
1235class BaseAdminNetworkTest(BaseNetworkTest):
1236
1237 credentials = ['primary', 'admin']
1238
1239 @classmethod
1240 def setup_clients(cls):
1241 super(BaseAdminNetworkTest, cls).setup_clients()
fumihiko kakumaa216fc12017-07-14 10:43:29 +09001242 cls.admin_client = cls.os_admin.network_client
Jakub Libosvarf5758012017-08-15 13:45:30 +00001243 cls.identity_admin_client = cls.os_admin.projects_client
Daniel Mellado3c0aeab2016-01-29 11:30:25 +00001244
1245 @classmethod
1246 def create_metering_label(cls, name, description):
1247 """Wrapper utility that returns a test metering label."""
1248 body = cls.admin_client.create_metering_label(
1249 description=description,
1250 name=data_utils.rand_name("metering-label"))
1251 metering_label = body['metering_label']
1252 cls.metering_labels.append(metering_label)
1253 return metering_label
1254
1255 @classmethod
1256 def create_metering_label_rule(cls, remote_ip_prefix, direction,
1257 metering_label_id):
1258 """Wrapper utility that returns a test metering label rule."""
1259 body = cls.admin_client.create_metering_label_rule(
1260 remote_ip_prefix=remote_ip_prefix, direction=direction,
1261 metering_label_id=metering_label_id)
1262 metering_label_rule = body['metering_label_rule']
1263 cls.metering_label_rules.append(metering_label_rule)
1264 return metering_label_rule
1265
1266 @classmethod
Kailun Qineaaf9782018-12-20 04:45:01 +08001267 def create_network_segment_range(cls, name, shared,
1268 project_id, network_type,
1269 physical_network, minimum,
1270 maximum):
1271 """Wrapper utility that returns a test network segment range."""
1272 network_segment_range_args = {'name': name,
1273 'shared': shared,
1274 'project_id': project_id,
1275 'network_type': network_type,
1276 'physical_network': physical_network,
1277 'minimum': minimum,
1278 'maximum': maximum}
1279 body = cls.admin_client.create_network_segment_range(
1280 **network_segment_range_args)
1281 network_segment_range = body['network_segment_range']
1282 cls.network_segment_ranges.append(network_segment_range)
1283 return network_segment_range
1284
1285 @classmethod
Daniel Mellado3c0aeab2016-01-29 11:30:25 +00001286 def create_flavor(cls, name, description, service_type):
1287 """Wrapper utility that returns a test flavor."""
1288 body = cls.admin_client.create_flavor(
1289 description=description, service_type=service_type,
1290 name=name)
1291 flavor = body['flavor']
1292 cls.flavors.append(flavor)
1293 return flavor
1294
1295 @classmethod
1296 def create_service_profile(cls, description, metainfo, driver):
1297 """Wrapper utility that returns a test service profile."""
1298 body = cls.admin_client.create_service_profile(
1299 driver=driver, metainfo=metainfo, description=description)
1300 service_profile = body['service_profile']
1301 cls.service_profiles.append(service_profile)
1302 return service_profile
1303
1304 @classmethod
Nguyen Phuong An67993fc2017-11-24 11:30:25 +07001305 def create_log(cls, name, description=None,
1306 resource_type='security_group', resource_id=None,
1307 target_id=None, event='ALL', enabled=True):
1308 """Wrapper utility that returns a test log object."""
1309 log_args = {'name': name,
Nguyen Phuong An67993fc2017-11-24 11:30:25 +07001310 'resource_type': resource_type,
1311 'resource_id': resource_id,
1312 'target_id': target_id,
1313 'event': event,
1314 'enabled': enabled}
Slawek Kaplonskid9fe3022021-08-11 15:25:16 +02001315 if description:
1316 log_args['description'] = description
Nguyen Phuong An67993fc2017-11-24 11:30:25 +07001317 body = cls.admin_client.create_log(**log_args)
1318 log_object = body['log']
1319 cls.log_objects.append(log_object)
1320 return log_object
1321
1322 @classmethod
Daniel Mellado3c0aeab2016-01-29 11:30:25 +00001323 def get_unused_ip(cls, net_id, ip_version=None):
Gary Kotton011345f2016-06-15 08:04:31 -07001324 """Get an unused ip address in a allocation pool of net"""
Daniel Mellado3c0aeab2016-01-29 11:30:25 +00001325 body = cls.admin_client.list_ports(network_id=net_id)
1326 ports = body['ports']
1327 used_ips = []
1328 for port in ports:
1329 used_ips.extend(
1330 [fixed_ip['ip_address'] for fixed_ip in port['fixed_ips']])
1331 body = cls.admin_client.list_subnets(network_id=net_id)
1332 subnets = body['subnets']
1333
1334 for subnet in subnets:
1335 if ip_version and subnet['ip_version'] != ip_version:
1336 continue
1337 cidr = subnet['cidr']
1338 allocation_pools = subnet['allocation_pools']
1339 iterators = []
1340 if allocation_pools:
1341 for allocation_pool in allocation_pools:
1342 iterators.append(netaddr.iter_iprange(
1343 allocation_pool['start'], allocation_pool['end']))
1344 else:
1345 net = netaddr.IPNetwork(cidr)
1346
1347 def _iterip():
1348 for ip in net:
1349 if ip not in (net.network, net.broadcast):
1350 yield ip
1351 iterators.append(iter(_iterip()))
1352
1353 for iterator in iterators:
1354 for ip in iterator:
1355 if str(ip) not in used_ips:
1356 return str(ip)
1357
1358 message = (
1359 "net(%s) has no usable IP address in allocation pools" % net_id)
1360 raise exceptions.InvalidConfiguration(message)
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001361
Lajos Katona2f904652018-08-23 14:04:56 +02001362 @classmethod
1363 def create_provider_network(cls, physnet_name, start_segmentation_id,
Frode Nordahl1bb8e622023-10-16 15:16:34 +02001364 max_attempts=30, external=False):
Lajos Katona2f904652018-08-23 14:04:56 +02001365 segmentation_id = start_segmentation_id
Lajos Katona7eb67252019-01-14 12:55:35 +01001366 for attempts in range(max_attempts):
Lajos Katona2f904652018-08-23 14:04:56 +02001367 try:
Lajos Katona7eb67252019-01-14 12:55:35 +01001368 return cls.create_network(
Lajos Katona2f904652018-08-23 14:04:56 +02001369 name=data_utils.rand_name('test_net'),
Frode Nordahl1bb8e622023-10-16 15:16:34 +02001370 shared=not external,
1371 external=external,
Lajos Katona2f904652018-08-23 14:04:56 +02001372 provider_network_type='vlan',
1373 provider_physical_network=physnet_name,
1374 provider_segmentation_id=segmentation_id)
Lajos Katona2f904652018-08-23 14:04:56 +02001375 except lib_exc.Conflict:
Lajos Katona2f904652018-08-23 14:04:56 +02001376 segmentation_id += 1
1377 if segmentation_id > 4095:
1378 raise lib_exc.TempestException(
1379 "No free segmentation id was found for provider "
1380 "network creation!")
1381 time.sleep(CONF.network.build_interval)
Lajos Katona7eb67252019-01-14 12:55:35 +01001382 LOG.exception("Failed to create provider network after "
1383 "%d attempts", max_attempts)
1384 raise lib_exc.TimeoutException
Lajos Katona2f904652018-08-23 14:04:56 +02001385
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001386
Sławek Kapłońskiff294062016-12-04 15:00:54 +00001387def require_qos_rule_type(rule_type):
1388 def decorator(f):
1389 @functools.wraps(f)
1390 def wrapper(self, *func_args, **func_kwargs):
1391 if rule_type not in self.get_supported_qos_rule_types():
1392 raise self.skipException(
1393 "%s rule type is required." % rule_type)
1394 return f(self, *func_args, **func_kwargs)
1395 return wrapper
1396 return decorator
1397
1398
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001399def _require_sorting(f):
1400 @functools.wraps(f)
1401 def inner(self, *args, **kwargs):
Chandan Kumarc125fd12017-11-15 19:41:01 +05301402 if not tutils.is_extension_enabled("sorting", "network"):
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001403 self.skipTest('Sorting feature is required')
1404 return f(self, *args, **kwargs)
1405 return inner
1406
1407
1408def _require_pagination(f):
1409 @functools.wraps(f)
1410 def inner(self, *args, **kwargs):
Chandan Kumarc125fd12017-11-15 19:41:01 +05301411 if not tutils.is_extension_enabled("pagination", "network"):
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001412 self.skipTest('Pagination feature is required')
1413 return f(self, *args, **kwargs)
1414 return inner
1415
1416
1417class BaseSearchCriteriaTest(BaseNetworkTest):
1418
1419 # This should be defined by subclasses to reflect resource name to test
1420 resource = None
1421
Armando Migliaccio57581c62016-07-01 10:13:19 -07001422 field = 'name'
1423
Ihar Hrachyshkaa8fe5a12016-05-24 14:50:58 +02001424 # NOTE(ihrachys): some names, like those starting with an underscore (_)
1425 # are sorted differently depending on whether the plugin implements native
1426 # sorting support, or not. So we avoid any such cases here, sticking to
1427 # alphanumeric. Also test a case when there are multiple resources with the
1428 # same name
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001429 resource_names = ('test1', 'abc1', 'test10', '123test') + ('test1',)
1430
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001431 force_tenant_isolation = True
1432
Ihar Hrachyshkaa8fe5a12016-05-24 14:50:58 +02001433 list_kwargs = {}
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001434
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001435 list_as_admin = False
1436
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001437 def assertSameOrder(self, original, actual):
1438 # gracefully handle iterators passed
1439 original = list(original)
1440 actual = list(actual)
1441 self.assertEqual(len(original), len(actual))
1442 for expected, res in zip(original, actual):
Armando Migliaccio57581c62016-07-01 10:13:19 -07001443 self.assertEqual(expected[self.field], res[self.field])
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001444
1445 @utils.classproperty
1446 def plural_name(self):
1447 return '%ss' % self.resource
1448
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001449 @property
1450 def list_client(self):
1451 return self.admin_client if self.list_as_admin else self.client
1452
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001453 def list_method(self, *args, **kwargs):
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001454 method = getattr(self.list_client, 'list_%s' % self.plural_name)
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001455 kwargs.update(self.list_kwargs)
1456 return method(*args, **kwargs)
1457
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001458 def get_bare_url(self, url):
1459 base_url = self.client.base_url
zheng.yong74e760a2019-05-22 14:16:14 +08001460 base_url_normalized = utils.normalize_url(base_url)
1461 url_normalized = utils.normalize_url(url)
1462 self.assertTrue(url_normalized.startswith(base_url_normalized))
1463 return url_normalized[len(base_url_normalized):]
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001464
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001465 @classmethod
1466 def _extract_resources(cls, body):
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001467 return body[cls.plural_name]
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001468
yatinkarel02743812024-08-13 18:14:37 +05301469 @classmethod
1470 def _test_resources(cls, resources):
1471 return [res for res in resources if res["name"] in cls.resource_names]
1472
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001473 def _test_list_sorts(self, direction):
1474 sort_args = {
1475 'sort_dir': direction,
Armando Migliaccio57581c62016-07-01 10:13:19 -07001476 'sort_key': self.field
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001477 }
1478 body = self.list_method(**sort_args)
1479 resources = self._extract_resources(body)
1480 self.assertNotEmpty(
1481 resources, "%s list returned is empty" % self.resource)
Armando Migliaccio57581c62016-07-01 10:13:19 -07001482 retrieved_names = [res[self.field] for res in resources]
Martin Kopec71a73242024-01-17 12:02:24 +01001483 # sort without taking into account whether the network is named with
1484 # a capital letter or not
1485 expected = sorted(retrieved_names, key=lambda v: v.upper())
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001486 if direction == constants.SORT_DIRECTION_DESC:
1487 expected = list(reversed(expected))
1488 self.assertEqual(expected, retrieved_names)
1489
1490 @_require_sorting
1491 def _test_list_sorts_asc(self):
1492 self._test_list_sorts(constants.SORT_DIRECTION_ASC)
1493
1494 @_require_sorting
1495 def _test_list_sorts_desc(self):
1496 self._test_list_sorts(constants.SORT_DIRECTION_DESC)
1497
1498 @_require_pagination
1499 def _test_list_pagination(self):
1500 for limit in range(1, len(self.resource_names) + 1):
1501 pagination_args = {
1502 'limit': limit,
1503 }
1504 body = self.list_method(**pagination_args)
1505 resources = self._extract_resources(body)
1506 self.assertEqual(limit, len(resources))
1507
1508 @_require_pagination
1509 def _test_list_no_pagination_limit_0(self):
1510 pagination_args = {
1511 'limit': 0,
1512 }
1513 body = self.list_method(**pagination_args)
1514 resources = self._extract_resources(body)
Béla Vancsicsf1806182016-08-23 07:36:18 +02001515 self.assertGreaterEqual(len(resources), len(self.resource_names))
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001516
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001517 def _test_list_pagination_iteratively(self, lister):
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001518 # first, collect all resources for later comparison
1519 sort_args = {
1520 'sort_dir': constants.SORT_DIRECTION_ASC,
Armando Migliaccio57581c62016-07-01 10:13:19 -07001521 'sort_key': self.field
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001522 }
1523 body = self.list_method(**sort_args)
yatinkarel02743812024-08-13 18:14:37 +05301524 total_resources = self._extract_resources(body)
1525 expected_resources = self._test_resources(total_resources)
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001526 self.assertNotEmpty(expected_resources)
1527
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001528 resources = lister(
yatinkarel02743812024-08-13 18:14:37 +05301529 len(total_resources), sort_args
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001530 )
1531
1532 # finally, compare that the list retrieved in one go is identical to
1533 # the one containing pagination results
1534 self.assertSameOrder(expected_resources, resources)
1535
1536 def _list_all_with_marker(self, niterations, sort_args):
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001537 # paginate resources one by one, using last fetched resource as a
1538 # marker
1539 resources = []
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001540 for i in range(niterations):
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001541 pagination_args = sort_args.copy()
1542 pagination_args['limit'] = 1
1543 if resources:
1544 pagination_args['marker'] = resources[-1]['id']
1545 body = self.list_method(**pagination_args)
1546 resources_ = self._extract_resources(body)
yatinkarela8c221c2024-08-26 16:40:38 +05301547 # Empty resource list can be returned when any concurrent
1548 # tests delete them
1549 self.assertGreaterEqual(1, len(resources_))
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001550 resources.extend(resources_)
yatinkarel02743812024-08-13 18:14:37 +05301551 return self._test_resources(resources)
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001552
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001553 @_require_pagination
1554 @_require_sorting
1555 def _test_list_pagination_with_marker(self):
1556 self._test_list_pagination_iteratively(self._list_all_with_marker)
1557
1558 def _list_all_with_hrefs(self, niterations, sort_args):
1559 # paginate resources one by one, using next href links
1560 resources = []
1561 prev_links = {}
1562
1563 for i in range(niterations):
1564 if prev_links:
1565 uri = self.get_bare_url(prev_links['next'])
1566 else:
Ihar Hrachyshka7f79fe62016-06-07 21:23:44 +02001567 sort_args.update(self.list_kwargs)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001568 uri = self.list_client.build_uri(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001569 self.plural_name, limit=1, **sort_args)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001570 prev_links, body = self.list_client.get_uri_with_links(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001571 self.plural_name, uri
1572 )
1573 resources_ = self._extract_resources(body)
yatinkarela8c221c2024-08-26 16:40:38 +05301574 # Empty resource list can be returned when any concurrent
1575 # tests delete them
1576 self.assertGreaterEqual(1, len(resources_))
yatinkarel02743812024-08-13 18:14:37 +05301577 resources.extend(self._test_resources(resources_))
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001578
1579 # The last element is empty and does not contain 'next' link
1580 uri = self.get_bare_url(prev_links['next'])
1581 prev_links, body = self.client.get_uri_with_links(
1582 self.plural_name, uri
1583 )
1584 self.assertNotIn('next', prev_links)
1585
1586 # Now walk backwards and compare results
1587 resources2 = []
1588 for i in range(niterations):
1589 uri = self.get_bare_url(prev_links['previous'])
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001590 prev_links, body = self.list_client.get_uri_with_links(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001591 self.plural_name, uri
1592 )
1593 resources_ = self._extract_resources(body)
yatinkarela8c221c2024-08-26 16:40:38 +05301594 # Empty resource list can be returned when any concurrent
1595 # tests delete them
1596 self.assertGreaterEqual(1, len(resources_))
yatinkarel02743812024-08-13 18:14:37 +05301597 resources2.extend(self._test_resources(resources_))
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001598
1599 self.assertSameOrder(resources, reversed(resources2))
1600
1601 return resources
1602
1603 @_require_pagination
1604 @_require_sorting
1605 def _test_list_pagination_with_href_links(self):
1606 self._test_list_pagination_iteratively(self._list_all_with_hrefs)
1607
1608 @_require_pagination
1609 @_require_sorting
1610 def _test_list_pagination_page_reverse_with_href_links(
1611 self, direction=constants.SORT_DIRECTION_ASC):
1612 pagination_args = {
1613 'sort_dir': direction,
Armando Migliaccio57581c62016-07-01 10:13:19 -07001614 'sort_key': self.field,
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001615 }
1616 body = self.list_method(**pagination_args)
yatinkarel02743812024-08-13 18:14:37 +05301617 total_resources = self._extract_resources(body)
1618 expected_resources = self._test_resources(total_resources)
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001619
1620 page_size = 2
1621 pagination_args['limit'] = page_size
1622
1623 prev_links = {}
1624 resources = []
yatinkarel02743812024-08-13 18:14:37 +05301625 num_resources = len(total_resources)
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001626 niterations = int(math.ceil(float(num_resources) / page_size))
1627 for i in range(niterations):
1628 if prev_links:
1629 uri = self.get_bare_url(prev_links['previous'])
1630 else:
Ihar Hrachyshka7f79fe62016-06-07 21:23:44 +02001631 pagination_args.update(self.list_kwargs)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001632 uri = self.list_client.build_uri(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001633 self.plural_name, page_reverse=True, **pagination_args)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001634 prev_links, body = self.list_client.get_uri_with_links(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001635 self.plural_name, uri
1636 )
yatinkarel02743812024-08-13 18:14:37 +05301637 resources_ = self._test_resources(self._extract_resources(body))
Béla Vancsicsf1806182016-08-23 07:36:18 +02001638 self.assertGreaterEqual(page_size, len(resources_))
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001639 resources.extend(reversed(resources_))
1640
1641 self.assertSameOrder(expected_resources, reversed(resources))
1642
1643 @_require_pagination
1644 @_require_sorting
1645 def _test_list_pagination_page_reverse_asc(self):
1646 self._test_list_pagination_page_reverse(
1647 direction=constants.SORT_DIRECTION_ASC)
1648
1649 @_require_pagination
1650 @_require_sorting
1651 def _test_list_pagination_page_reverse_desc(self):
1652 self._test_list_pagination_page_reverse(
1653 direction=constants.SORT_DIRECTION_DESC)
1654
1655 def _test_list_pagination_page_reverse(self, direction):
1656 pagination_args = {
1657 'sort_dir': direction,
Armando Migliaccio57581c62016-07-01 10:13:19 -07001658 'sort_key': self.field,
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001659 'limit': 3,
1660 }
1661 body = self.list_method(**pagination_args)
1662 expected_resources = self._extract_resources(body)
1663
1664 pagination_args['limit'] -= 1
1665 pagination_args['marker'] = expected_resources[-1]['id']
1666 pagination_args['page_reverse'] = True
1667 body = self.list_method(**pagination_args)
1668
1669 self.assertSameOrder(
1670 # the last entry is not included in 2nd result when used as a
1671 # marker
1672 expected_resources[:-1],
1673 self._extract_resources(body))
Victor Morales1be97b42016-09-05 08:50:06 -05001674
Hongbin Lu54f55922018-07-12 19:05:39 +00001675 @tutils.requires_ext(extension="filter-validation", service="network")
1676 def _test_list_validation_filters(
1677 self, validation_args, filter_is_valid=True):
1678 if not filter_is_valid:
1679 self.assertRaises(lib_exc.BadRequest, self.list_method,
1680 **validation_args)
1681 else:
1682 body = self.list_method(**validation_args)
1683 resources = self._extract_resources(body)
1684 for resource in resources:
1685 self.assertIn(resource['name'], self.resource_names)