blob: 4bcc6d23f9fd8365116e712f4d61e93df184275a [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
Chandan Kumarc125fd12017-11-15 19:41:01 +053021from neutron_lib import constants as const
Lajos Katona2f904652018-08-23 14:04:56 +020022from oslo_log import log
Chandan Kumarc125fd12017-11-15 19:41:01 +053023from tempest.common import utils as tutils
Daniel Mellado3c0aeab2016-01-29 11:30:25 +000024from tempest.lib.common.utils import data_utils
25from tempest.lib import exceptions as lib_exc
26from tempest import test
27
Chandan Kumar667d3d32017-09-22 12:24:06 +053028from neutron_tempest_plugin.api import clients
29from neutron_tempest_plugin.common import constants
30from neutron_tempest_plugin.common import utils
31from neutron_tempest_plugin import config
32from neutron_tempest_plugin import exceptions
Daniel Mellado3c0aeab2016-01-29 11:30:25 +000033
34CONF = config.CONF
35
Lajos Katona2f904652018-08-23 14:04:56 +020036LOG = log.getLogger(__name__)
37
Daniel Mellado3c0aeab2016-01-29 11:30:25 +000038
39class BaseNetworkTest(test.BaseTestCase):
40
Brian Haleyae328b92018-10-09 19:51:54 -040041 """Base class for Neutron tests that use the Tempest Neutron REST client
Daniel Mellado3c0aeab2016-01-29 11:30:25 +000042
43 Per the Neutron API Guide, API v1.x was removed from the source code tree
44 (docs.openstack.org/api/openstack-network/2.0/content/Overview-d1e71.html)
45 Therefore, v2.x of the Neutron API is assumed. It is also assumed that the
46 following options are defined in the [network] section of etc/tempest.conf:
47
48 project_network_cidr with a block of cidr's from which smaller blocks
49 can be allocated for tenant networks
50
51 project_network_mask_bits with the mask bits to be used to partition
52 the block defined by tenant-network_cidr
53
54 Finally, it is assumed that the following option is defined in the
55 [service_available] section of etc/tempest.conf
56
57 neutron as True
58 """
59
60 force_tenant_isolation = False
61 credentials = ['primary']
62
63 # Default to ipv4.
Federico Ressi0ddc93b2018-04-09 12:01:48 +020064 _ip_version = const.IP_VERSION_4
Daniel Mellado3c0aeab2016-01-29 11:30:25 +000065
Federico Ressi61b564e2018-07-06 08:10:31 +020066 # Derive from BaseAdminNetworkTest class to have this initialized
67 admin_client = None
68
Federico Ressia69dcd52018-07-06 09:45:34 +020069 external_network_id = CONF.network.public_network_id
70
Maor Blausteinfc274a02024-02-08 13:35:15 +020071 __is_driver_ovn = None
72
73 @classmethod
74 def _is_driver_ovn(cls):
75 ovn_agents = cls.os_admin.network_client.list_agents(
76 binary='ovn-controller')['agents']
77 return len(ovn_agents) > 0
78
79 @property
80 def is_driver_ovn(self):
81 if self.__is_driver_ovn is None:
82 if hasattr(self, 'os_admin'):
83 self.__is_driver_ovn = self._is_driver_ovn()
84 return self.__is_driver_ovn
85
Daniel Mellado3c0aeab2016-01-29 11:30:25 +000086 @classmethod
87 def get_client_manager(cls, credential_type=None, roles=None,
88 force_new=None):
Genadi Chereshnyacc395c02016-07-25 12:17:37 +030089 manager = super(BaseNetworkTest, cls).get_client_manager(
90 credential_type=credential_type,
91 roles=roles,
92 force_new=force_new
93 )
Daniel Mellado3c0aeab2016-01-29 11:30:25 +000094 # Neutron uses a different clients manager than the one in the Tempest
Jens Harbott860b46a2017-11-15 21:23:15 +000095 # save the original in case mixed tests need it
96 if credential_type == 'primary':
97 cls.os_tempest = manager
Daniel Mellado3c0aeab2016-01-29 11:30:25 +000098 return clients.Manager(manager.credentials)
99
100 @classmethod
101 def skip_checks(cls):
102 super(BaseNetworkTest, cls).skip_checks()
103 if not CONF.service_available.neutron:
104 raise cls.skipException("Neutron support is required")
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200105 if (cls._ip_version == const.IP_VERSION_6 and
106 not CONF.network_feature_enabled.ipv6):
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000107 raise cls.skipException("IPv6 Tests are disabled.")
Jakub Libosvar1982aa12017-05-30 11:15:33 +0000108 for req_ext in getattr(cls, 'required_extensions', []):
Chandan Kumarc125fd12017-11-15 19:41:01 +0530109 if not tutils.is_extension_enabled(req_ext, 'network'):
Jakub Libosvar1982aa12017-05-30 11:15:33 +0000110 msg = "%s extension not enabled." % req_ext
111 raise cls.skipException(msg)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000112
113 @classmethod
114 def setup_credentials(cls):
115 # Create no network resources for these test.
116 cls.set_network_resources()
117 super(BaseNetworkTest, cls).setup_credentials()
118
119 @classmethod
120 def setup_clients(cls):
121 super(BaseNetworkTest, cls).setup_clients()
fumihiko kakumaa216fc12017-07-14 10:43:29 +0900122 cls.client = cls.os_primary.network_client
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000123
124 @classmethod
125 def resource_setup(cls):
126 super(BaseNetworkTest, cls).resource_setup()
127
128 cls.networks = []
Miguel Lavalle124378b2016-09-21 16:41:47 -0500129 cls.admin_networks = []
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000130 cls.subnets = []
Kevin Bentonba3651c2017-09-01 17:13:01 -0700131 cls.admin_subnets = []
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000132 cls.ports = []
133 cls.routers = []
134 cls.floating_ips = []
Slawek Kaplonski003fcae2019-05-26 22:38:35 +0200135 cls.port_forwardings = []
Nurmatov Mamatisa3f2bbb52021-10-20 14:33:54 +0300136 cls.local_ips = []
137 cls.local_ip_associations = []
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000138 cls.metering_labels = []
139 cls.service_profiles = []
140 cls.flavors = []
141 cls.metering_label_rules = []
142 cls.qos_rules = []
143 cls.qos_policies = []
144 cls.ethertype = "IPv" + str(cls._ip_version)
Miguel Lavalleb1c7a3d2021-01-31 19:05:22 -0600145 cls.address_groups = []
146 cls.admin_address_groups = []
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000147 cls.address_scopes = []
148 cls.admin_address_scopes = []
149 cls.subnetpools = []
150 cls.admin_subnetpools = []
Itzik Brownbac51dc2016-10-31 12:25:04 +0000151 cls.security_groups = []
Dongcan Ye2de722e2018-07-04 11:01:37 +0000152 cls.admin_security_groups = []
Slawek Kaplonskiaa22c9e2023-05-18 18:59:26 +0200153 cls.sg_rule_templates = []
Chandan Kumarc125fd12017-11-15 19:41:01 +0530154 cls.projects = []
Nguyen Phuong An67993fc2017-11-24 11:30:25 +0700155 cls.log_objects = []
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200156 cls.reserved_subnet_cidrs = set()
Federico Ressiab286e42018-06-19 09:52:10 +0200157 cls.keypairs = []
Federico Ressi82e83e32018-07-03 14:19:55 +0200158 cls.trunks = []
Kailun Qineaaf9782018-12-20 04:45:01 +0800159 cls.network_segment_ranges = []
Harald Jensåsc9782fa2019-06-03 22:35:41 +0200160 cls.conntrack_helpers = []
yangjianfeng2936a292022-02-04 11:22:11 +0800161 cls.ndp_proxies = []
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000162
163 @classmethod
yangjianfeng23e40c22020-11-22 08:42:18 +0000164 def reserve_external_subnet_cidrs(cls):
165 client = cls.os_admin.network_client
166 ext_nets = client.list_networks(
167 **{"router:external": True})['networks']
168 for ext_net in ext_nets:
169 ext_subnets = client.list_subnets(
170 network_id=ext_net['id'])['subnets']
171 for ext_subnet in ext_subnets:
172 cls.reserve_subnet_cidr(ext_subnet['cidr'])
173
174 @classmethod
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000175 def resource_cleanup(cls):
176 if CONF.service_available.neutron:
Federico Ressi82e83e32018-07-03 14:19:55 +0200177 # Clean up trunks
178 for trunk in cls.trunks:
179 cls._try_delete_resource(cls.delete_trunk, trunk)
180
yangjianfeng2936a292022-02-04 11:22:11 +0800181 # Clean up ndp proxy
182 for ndp_proxy in cls.ndp_proxies:
183 cls._try_delete_resource(cls.delete_ndp_proxy, ndp_proxy)
184
Slawek Kaplonski003fcae2019-05-26 22:38:35 +0200185 # Clean up port forwardings
186 for pf in cls.port_forwardings:
187 cls._try_delete_resource(cls.delete_port_forwarding, pf)
188
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000189 # Clean up floating IPs
190 for floating_ip in cls.floating_ips:
Federico Ressia69dcd52018-07-06 09:45:34 +0200191 cls._try_delete_resource(cls.delete_floatingip, floating_ip)
192
Nurmatov Mamatisa3f2bbb52021-10-20 14:33:54 +0300193 # Clean up Local IP Associations
194 for association in cls.local_ip_associations:
195 cls._try_delete_resource(cls.delete_local_ip_association,
196 association)
197 # Clean up Local IPs
198 for local_ip in cls.local_ips:
199 cls._try_delete_resource(cls.delete_local_ip,
200 local_ip)
201
Harald Jensåsc9782fa2019-06-03 22:35:41 +0200202 # Clean up conntrack helpers
203 for cth in cls.conntrack_helpers:
204 cls._try_delete_resource(cls.delete_conntrack_helper, cth)
205
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000206 # Clean up routers
207 for router in cls.routers:
208 cls._try_delete_resource(cls.delete_router,
209 router)
210 # Clean up metering label rules
211 for metering_label_rule in cls.metering_label_rules:
212 cls._try_delete_resource(
213 cls.admin_client.delete_metering_label_rule,
214 metering_label_rule['id'])
215 # Clean up metering labels
216 for metering_label in cls.metering_labels:
217 cls._try_delete_resource(
218 cls.admin_client.delete_metering_label,
219 metering_label['id'])
220 # Clean up flavors
221 for flavor in cls.flavors:
222 cls._try_delete_resource(
223 cls.admin_client.delete_flavor,
224 flavor['id'])
225 # Clean up service profiles
226 for service_profile in cls.service_profiles:
227 cls._try_delete_resource(
228 cls.admin_client.delete_service_profile,
229 service_profile['id'])
230 # Clean up ports
231 for port in cls.ports:
232 cls._try_delete_resource(cls.client.delete_port,
233 port['id'])
234 # Clean up subnets
235 for subnet in cls.subnets:
236 cls._try_delete_resource(cls.client.delete_subnet,
237 subnet['id'])
Kevin Bentonba3651c2017-09-01 17:13:01 -0700238 # Clean up admin subnets
239 for subnet in cls.admin_subnets:
240 cls._try_delete_resource(cls.admin_client.delete_subnet,
241 subnet['id'])
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000242 # Clean up networks
243 for network in cls.networks:
Federico Ressi61b564e2018-07-06 08:10:31 +0200244 cls._try_delete_resource(cls.delete_network, network)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000245
Miguel Lavalle124378b2016-09-21 16:41:47 -0500246 # Clean up admin networks
247 for network in cls.admin_networks:
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000248 cls._try_delete_resource(cls.admin_client.delete_network,
249 network['id'])
250
Itzik Brownbac51dc2016-10-31 12:25:04 +0000251 # Clean up security groups
Federico Ressi4c590d72018-10-10 14:01:08 +0200252 for security_group in cls.security_groups:
253 cls._try_delete_resource(cls.delete_security_group,
254 security_group)
Itzik Brownbac51dc2016-10-31 12:25:04 +0000255
Dongcan Ye2de722e2018-07-04 11:01:37 +0000256 # Clean up admin security groups
Federico Ressi4c590d72018-10-10 14:01:08 +0200257 for security_group in cls.admin_security_groups:
258 cls._try_delete_resource(cls.delete_security_group,
259 security_group,
260 client=cls.admin_client)
Dongcan Ye2de722e2018-07-04 11:01:37 +0000261
Slawek Kaplonskiaa22c9e2023-05-18 18:59:26 +0200262 # Clean up security group rule templates
263 for sg_rule_template in cls.sg_rule_templates:
264 cls._try_delete_resource(
265 cls.admin_client.delete_default_security_group_rule,
266 sg_rule_template['id'])
267
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000268 for subnetpool in cls.subnetpools:
269 cls._try_delete_resource(cls.client.delete_subnetpool,
270 subnetpool['id'])
271
272 for subnetpool in cls.admin_subnetpools:
273 cls._try_delete_resource(cls.admin_client.delete_subnetpool,
274 subnetpool['id'])
275
276 for address_scope in cls.address_scopes:
277 cls._try_delete_resource(cls.client.delete_address_scope,
278 address_scope['id'])
279
280 for address_scope in cls.admin_address_scopes:
281 cls._try_delete_resource(
282 cls.admin_client.delete_address_scope,
283 address_scope['id'])
284
Chandan Kumarc125fd12017-11-15 19:41:01 +0530285 for project in cls.projects:
286 cls._try_delete_resource(
287 cls.identity_admin_client.delete_project,
288 project['id'])
289
Sławek Kapłońskie100c4d2017-08-23 21:18:34 +0000290 # Clean up QoS rules
291 for qos_rule in cls.qos_rules:
292 cls._try_delete_resource(cls.admin_client.delete_qos_rule,
293 qos_rule['id'])
294 # Clean up QoS policies
295 # as all networks and ports are already removed, QoS policies
296 # shouldn't be "in use"
297 for qos_policy in cls.qos_policies:
298 cls._try_delete_resource(cls.admin_client.delete_qos_policy,
299 qos_policy['id'])
300
Nguyen Phuong An67993fc2017-11-24 11:30:25 +0700301 # Clean up log_objects
302 for log_object in cls.log_objects:
303 cls._try_delete_resource(cls.admin_client.delete_log,
304 log_object['id'])
305
Federico Ressiab286e42018-06-19 09:52:10 +0200306 for keypair in cls.keypairs:
307 cls._try_delete_resource(cls.delete_keypair, keypair)
308
Kailun Qineaaf9782018-12-20 04:45:01 +0800309 # Clean up network_segment_ranges
310 for network_segment_range in cls.network_segment_ranges:
311 cls._try_delete_resource(
312 cls.admin_client.delete_network_segment_range,
313 network_segment_range['id'])
314
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000315 super(BaseNetworkTest, cls).resource_cleanup()
316
317 @classmethod
318 def _try_delete_resource(cls, delete_callable, *args, **kwargs):
319 """Cleanup resources in case of test-failure
320
321 Some resources are explicitly deleted by the test.
322 If the test failed to delete a resource, this method will execute
323 the appropriate delete methods. Otherwise, the method ignores NotFound
324 exceptions thrown for resources that were correctly deleted by the
325 test.
326
327 :param delete_callable: delete method
328 :param args: arguments for delete method
329 :param kwargs: keyword arguments for delete method
330 """
331 try:
332 delete_callable(*args, **kwargs)
333 # if resource is not found, this means it was deleted in the test
334 except lib_exc.NotFound:
335 pass
336
337 @classmethod
Federico Ressi61b564e2018-07-06 08:10:31 +0200338 def create_network(cls, network_name=None, client=None, external=None,
339 shared=None, provider_network_type=None,
340 provider_physical_network=None,
341 provider_segmentation_id=None, **kwargs):
342 """Create a network.
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000343
Federico Ressi61b564e2018-07-06 08:10:31 +0200344 When client is not provider and admin_client is attribute is not None
345 (for example when using BaseAdminNetworkTest base class) and using any
346 of the convenience parameters (external, shared, provider_network_type,
347 provider_physical_network and provider_segmentation_id) it silently
348 uses admin_client. If the network is not shared then it uses the same
349 project_id as regular client.
350
351 :param network_name: Human-readable name of the network
352
353 :param client: client to be used for connecting to network service
354
355 :param external: indicates whether the network has an external routing
356 facility that's not managed by the networking service.
357
358 :param shared: indicates whether this resource is shared across all
359 projects. By default, only administrative users can change this value.
360 If True and admin_client attribute is not None, then the network is
361 created under administrative project.
362
363 :param provider_network_type: the type of physical network that this
364 network should be mapped to. For example, 'flat', 'vlan', 'vxlan', or
365 'gre'. Valid values depend on a networking back-end.
366
367 :param provider_physical_network: the physical network where this
368 network should be implemented. The Networking API v2.0 does not provide
369 a way to list available physical networks. For example, the Open
370 vSwitch plug-in configuration file defines a symbolic name that maps to
371 specific bridges on each compute host.
372
373 :param provider_segmentation_id: The ID of the isolated segment on the
374 physical network. The network_type attribute defines the segmentation
375 model. For example, if the network_type value is 'vlan', this ID is a
376 vlan identifier. If the network_type value is 'gre', this ID is a gre
377 key.
378
379 :param **kwargs: extra parameters to be forwarded to network service
380 """
381
382 name = (network_name or kwargs.pop('name', None) or
383 data_utils.rand_name('test-network-'))
384
385 # translate convenience parameters
386 admin_client_required = False
387 if provider_network_type:
388 admin_client_required = True
389 kwargs['provider:network_type'] = provider_network_type
390 if provider_physical_network:
391 admin_client_required = True
392 kwargs['provider:physical_network'] = provider_physical_network
393 if provider_segmentation_id:
394 admin_client_required = True
395 kwargs['provider:segmentation_id'] = provider_segmentation_id
396 if external is not None:
397 admin_client_required = True
398 kwargs['router:external'] = bool(external)
399 if shared is not None:
400 admin_client_required = True
401 kwargs['shared'] = bool(shared)
402
403 if not client:
404 if admin_client_required and cls.admin_client:
405 # For convenience silently switch to admin client
406 client = cls.admin_client
407 if not shared:
408 # Keep this network visible from current project
409 project_id = (kwargs.get('project_id') or
410 kwargs.get('tenant_id') or
Takashi Kajinamida451772023-03-22 00:19:39 +0900411 cls.client.project_id)
Federico Ressi61b564e2018-07-06 08:10:31 +0200412 kwargs.update(project_id=project_id, tenant_id=project_id)
413 else:
414 # Use default client
415 client = cls.client
416
417 network = client.create_network(name=name, **kwargs)['network']
418 network['client'] = client
419 cls.networks.append(network)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000420 return network
421
422 @classmethod
Federico Ressi61b564e2018-07-06 08:10:31 +0200423 def delete_network(cls, network, client=None):
424 client = client or network.get('client') or cls.client
425 client.delete_network(network['id'])
426
427 @classmethod
428 def create_shared_network(cls, network_name=None, **kwargs):
429 return cls.create_network(name=network_name, shared=True, **kwargs)
Miguel Lavalle124378b2016-09-21 16:41:47 -0500430
431 @classmethod
Sławek Kapłońskid98e27d2018-05-07 16:16:28 +0200432 def create_subnet(cls, network, gateway='', cidr=None, mask_bits=None,
Federico Ressi98f20ec2018-05-11 06:09:49 +0200433 ip_version=None, client=None, reserve_cidr=True,
Rodolfo Alonso Hernandez780d81e2024-01-14 10:02:13 +0000434 allocation_pool_size=None, **kwargs):
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200435 """Wrapper utility that returns a test subnet.
436
437 Convenient wrapper for client.create_subnet method. It reserves and
438 allocates CIDRs to avoid creating overlapping subnets.
439
440 :param network: network where to create the subnet
441 network['id'] must contain the ID of the network
442
443 :param gateway: gateway IP address
444 It can be a str or a netaddr.IPAddress
445 If gateway is not given, then it will use default address for
446 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 +0200447 if gateway is given as None then no gateway will be assigned
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200448
449 :param cidr: CIDR of the subnet to create
450 It can be either None, a str or a netaddr.IPNetwork instance
451
452 :param mask_bits: CIDR prefix length
453 It can be either None or a numeric value.
454 If cidr parameter is given then mask_bits is used to determinate a
455 sequence of valid CIDR to use as generated.
456 Please see netaddr.IPNetwork.subnet method documentation[1]
457
458 :param ip_version: ip version of generated subnet CIDRs
459 It can be None, IP_VERSION_4 or IP_VERSION_6
460 It has to match given either given CIDR and gateway
461
462 :param ip_version: numeric value (either IP_VERSION_4 or IP_VERSION_6)
463 this value must match CIDR and gateway IP versions if any of them is
464 given
465
466 :param client: client to be used to connect to network service
467
Federico Ressi98f20ec2018-05-11 06:09:49 +0200468 :param reserve_cidr: if True then it reserves assigned CIDR to avoid
469 using the same CIDR for further subnets in the scope of the same
470 test case class
471
Rodolfo Alonso Hernandez780d81e2024-01-14 10:02:13 +0000472 :param allocation_pool_size: if the CIDR is not defined, this method
473 will assign one in ``get_subnet_cidrs``. Once done, the allocation pool
474 will be defined reserving the number of IP addresses requested,
475 starting from the end of the assigned CIDR.
476
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200477 :param **kwargs: optional parameters to be forwarded to wrapped method
478
479 [1] http://netaddr.readthedocs.io/en/latest/tutorial_01.html#supernets-and-subnets # noqa
480 """
Rodolfo Alonso Hernandez780d81e2024-01-14 10:02:13 +0000481 def allocation_pool(cidr, pool_size):
482 start = str(netaddr.IPAddress(cidr.last) - pool_size)
483 end = str(netaddr.IPAddress(cidr.last) - 1)
484 return {'start': start, 'end': end}
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000485
486 # allow tests to use admin client
487 if not client:
488 client = cls.client
489
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200490 if gateway:
491 gateway_ip = netaddr.IPAddress(gateway)
492 if ip_version:
493 if ip_version != gateway_ip.version:
494 raise ValueError(
495 "Gateway IP version doesn't match IP version")
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000496 else:
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200497 ip_version = gateway_ip.version
Sławek Kapłońskid98e27d2018-05-07 16:16:28 +0200498 else:
499 ip_version = ip_version or cls._ip_version
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200500
501 for subnet_cidr in cls.get_subnet_cidrs(
502 ip_version=ip_version, cidr=cidr, mask_bits=mask_bits):
Federico Ressi98f20ec2018-05-11 06:09:49 +0200503 if gateway is not None:
504 kwargs['gateway_ip'] = str(gateway or (subnet_cidr.ip + 1))
Slawek Kaplonski21f53422018-11-02 16:02:09 +0100505 else:
506 kwargs['gateway_ip'] = None
Rodolfo Alonso Hernandez780d81e2024-01-14 10:02:13 +0000507 if allocation_pool_size:
508 kwargs['allocation_pools'] = [
509 allocation_pool(subnet_cidr, allocation_pool_size)]
Federico Ressi98f20ec2018-05-11 06:09:49 +0200510 try:
511 body = client.create_subnet(
512 network_id=network['id'],
513 cidr=str(subnet_cidr),
514 ip_version=subnet_cidr.version,
515 **kwargs)
516 break
517 except lib_exc.BadRequest as e:
518 if 'overlaps with another subnet' not in str(e):
519 raise
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000520 else:
521 message = 'Available CIDR for subnet creation could not be found'
522 raise ValueError(message)
523 subnet = body['subnet']
Kevin Bentonba3651c2017-09-01 17:13:01 -0700524 if client is cls.client:
525 cls.subnets.append(subnet)
526 else:
527 cls.admin_subnets.append(subnet)
Federico Ressi98f20ec2018-05-11 06:09:49 +0200528 if reserve_cidr:
529 cls.reserve_subnet_cidr(subnet_cidr)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000530 return subnet
531
532 @classmethod
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200533 def reserve_subnet_cidr(cls, addr, **ipnetwork_kwargs):
Slawek Kaplonski17e95512024-05-02 14:12:31 +0200534 """Reserve given subnet CIDR making sure it's not used by create_subnet
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200535
536 :param addr: the CIDR address to be reserved
537 It can be a str or netaddr.IPNetwork instance
538
539 :param **ipnetwork_kwargs: optional netaddr.IPNetwork constructor
540 parameters
541 """
542
543 if not cls.try_reserve_subnet_cidr(addr, **ipnetwork_kwargs):
Bernard Cafarellic3bec862020-09-10 13:59:49 +0200544 raise ValueError('Subnet CIDR already reserved: {0!r}'.format(
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200545 addr))
546
547 @classmethod
548 def try_reserve_subnet_cidr(cls, addr, **ipnetwork_kwargs):
549 """Reserve given subnet CIDR if it hasn't been reserved before
550
551 :param addr: the CIDR address to be reserved
552 It can be a str or netaddr.IPNetwork instance
553
554 :param **ipnetwork_kwargs: optional netaddr.IPNetwork constructor
555 parameters
556
557 :return: True if it wasn't reserved before, False elsewhere.
558 """
559
560 subnet_cidr = netaddr.IPNetwork(addr, **ipnetwork_kwargs)
561 if subnet_cidr in cls.reserved_subnet_cidrs:
562 return False
563 else:
564 cls.reserved_subnet_cidrs.add(subnet_cidr)
565 return True
566
567 @classmethod
568 def get_subnet_cidrs(
569 cls, cidr=None, mask_bits=None, ip_version=None):
570 """Iterate over a sequence of unused subnet CIDR for IP version
571
572 :param cidr: CIDR of the subnet to create
573 It can be either None, a str or a netaddr.IPNetwork instance
574
575 :param mask_bits: CIDR prefix length
576 It can be either None or a numeric value.
577 If cidr parameter is given then mask_bits is used to determinate a
578 sequence of valid CIDR to use as generated.
579 Please see netaddr.IPNetwork.subnet method documentation[1]
580
581 :param ip_version: ip version of generated subnet CIDRs
582 It can be None, IP_VERSION_4 or IP_VERSION_6
583 It has to match given CIDR if given
584
585 :return: iterator over reserved CIDRs of type netaddr.IPNetwork
586
587 [1] http://netaddr.readthedocs.io/en/latest/tutorial_01.html#supernets-and-subnets # noqa
588 """
589
590 if cidr:
591 # Generate subnet CIDRs starting from given CIDR
592 # checking it is of requested IP version
593 cidr = netaddr.IPNetwork(cidr, version=ip_version)
594 else:
595 # Generate subnet CIDRs starting from configured values
596 ip_version = ip_version or cls._ip_version
597 if ip_version == const.IP_VERSION_4:
Takashi Kajinami938f2c72024-11-23 02:33:10 +0900598 mask_bits = mask_bits or CONF.network.project_network_mask_bits
599 cidr = netaddr.IPNetwork(CONF.network.project_network_cidr)
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200600 elif ip_version == const.IP_VERSION_6:
Takashi Kajinami938f2c72024-11-23 02:33:10 +0900601 mask_bits = CONF.network.project_network_v6_mask_bits
602 cidr = netaddr.IPNetwork(CONF.network.project_network_v6_cidr)
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200603 else:
604 raise ValueError('Invalid IP version: {!r}'.format(ip_version))
605
606 if mask_bits:
607 subnet_cidrs = cidr.subnet(mask_bits)
608 else:
609 subnet_cidrs = iter([cidr])
610
611 for subnet_cidr in subnet_cidrs:
612 if subnet_cidr not in cls.reserved_subnet_cidrs:
613 yield subnet_cidr
614
615 @classmethod
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000616 def create_port(cls, network, **kwargs):
617 """Wrapper utility that returns a test port."""
Edan Davidd75e48e2018-01-03 02:49:52 -0500618 if CONF.network.port_vnic_type and 'binding:vnic_type' not in kwargs:
619 kwargs['binding:vnic_type'] = CONF.network.port_vnic_type
Glenn Van de Water5d9b1402020-09-16 15:14:14 +0200620 if CONF.network.port_profile and 'binding:profile' not in kwargs:
621 kwargs['binding:profile'] = CONF.network.port_profile
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000622 body = cls.client.create_port(network_id=network['id'],
623 **kwargs)
624 port = body['port']
625 cls.ports.append(port)
626 return port
627
628 @classmethod
629 def update_port(cls, port, **kwargs):
630 """Wrapper utility that updates a test port."""
631 body = cls.client.update_port(port['id'],
632 **kwargs)
633 return body['port']
634
635 @classmethod
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300636 def _create_router_with_client(
637 cls, client, router_name=None, admin_state_up=False,
638 external_network_id=None, enable_snat=None, **kwargs
639 ):
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000640 ext_gw_info = {}
641 if external_network_id:
642 ext_gw_info['network_id'] = external_network_id
YAMAMOTO Takashi9bd4f972017-06-20 12:49:30 +0900643 if enable_snat is not None:
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000644 ext_gw_info['enable_snat'] = enable_snat
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300645 body = client.create_router(
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000646 router_name, external_gateway_info=ext_gw_info,
647 admin_state_up=admin_state_up, **kwargs)
648 router = body['router']
649 cls.routers.append(router)
650 return router
651
652 @classmethod
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300653 def create_router(cls, *args, **kwargs):
654 return cls._create_router_with_client(cls.client, *args, **kwargs)
655
656 @classmethod
657 def create_admin_router(cls, *args, **kwargs):
rajat294495c042017-06-28 15:37:16 +0530658 return cls._create_router_with_client(cls.os_admin.network_client,
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300659 *args, **kwargs)
660
661 @classmethod
Federico Ressia69dcd52018-07-06 09:45:34 +0200662 def create_floatingip(cls, external_network_id=None, port=None,
663 client=None, **kwargs):
664 """Creates a floating IP.
665
666 Create a floating IP and schedule it for later deletion.
667 If a client is passed, then it is used for deleting the IP too.
668
669 :param external_network_id: network ID where to create
670 By default this is 'CONF.network.public_network_id'.
671
672 :param port: port to bind floating IP to
673 This is translated to 'port_id=port['id']'
674 By default it is None.
675
676 :param client: network client to be used for creating and cleaning up
677 the floating IP.
678
679 :param **kwargs: additional creation parameters to be forwarded to
680 networking server.
681 """
682
683 client = client or cls.client
684 external_network_id = (external_network_id or
685 cls.external_network_id)
686
687 if port:
Federico Ressi47f6ae42018-09-24 16:19:14 +0200688 port_id = kwargs.setdefault('port_id', port['id'])
689 if port_id != port['id']:
690 message = "Port ID specified twice: {!s} != {!s}".format(
691 port_id, port['id'])
692 raise ValueError(message)
Federico Ressia69dcd52018-07-06 09:45:34 +0200693
694 fip = client.create_floatingip(external_network_id,
695 **kwargs)['floatingip']
696
697 # save client to be used later in cls.delete_floatingip
698 # for final cleanup
699 fip['client'] = client
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000700 cls.floating_ips.append(fip)
701 return fip
702
703 @classmethod
Federico Ressia69dcd52018-07-06 09:45:34 +0200704 def delete_floatingip(cls, floating_ip, client=None):
705 """Delete floating IP
706
707 :param client: Client to be used
708 If client is not given it will use the client used to create
709 the floating IP, or cls.client if unknown.
710 """
711
712 client = client or floating_ip.get('client') or cls.client
713 client.delete_floatingip(floating_ip['id'])
714
715 @classmethod
Slawek Kaplonski003fcae2019-05-26 22:38:35 +0200716 def create_port_forwarding(cls, fip_id, internal_port_id,
717 internal_port, external_port,
718 internal_ip_address=None, protocol="tcp",
719 client=None):
720 """Creates a port forwarding.
721
722 Create a port forwarding and schedule it for later deletion.
723 If a client is passed, then it is used for deleting the PF too.
724
725 :param fip_id: The ID of the floating IP address.
726
727 :param internal_port_id: The ID of the Neutron port associated to
728 the floating IP port forwarding.
729
730 :param internal_port: The TCP/UDP/other protocol port number of the
731 Neutron port fixed IP address associated to the floating ip
732 port forwarding.
733
734 :param external_port: The TCP/UDP/other protocol port number of
735 the port forwarding floating IP address.
736
737 :param internal_ip_address: The fixed IPv4 address of the Neutron
738 port associated to the floating IP port forwarding.
739
740 :param protocol: The IP protocol used in the floating IP port
741 forwarding.
742
743 :param client: network client to be used for creating and cleaning up
744 the floating IP port forwarding.
745 """
746
747 client = client or cls.client
748
749 pf = client.create_port_forwarding(
750 fip_id, internal_port_id, internal_port, external_port,
751 internal_ip_address, protocol)['port_forwarding']
752
753 # save ID of floating IP associated with port forwarding for final
754 # cleanup
755 pf['floatingip_id'] = fip_id
756
757 # save client to be used later in cls.delete_port_forwarding
758 # for final cleanup
759 pf['client'] = client
760 cls.port_forwardings.append(pf)
761 return pf
762
763 @classmethod
Flavio Fernandesa1952c62020-10-02 06:39:08 -0400764 def update_port_forwarding(cls, fip_id, pf_id, client=None, **kwargs):
765 """Wrapper utility for update_port_forwarding."""
766 client = client or cls.client
767 return client.update_port_forwarding(fip_id, pf_id, **kwargs)
768
769 @classmethod
Slawek Kaplonski003fcae2019-05-26 22:38:35 +0200770 def delete_port_forwarding(cls, pf, client=None):
771 """Delete port forwarding
772
773 :param client: Client to be used
774 If client is not given it will use the client used to create
775 the port forwarding, or cls.client if unknown.
776 """
777
778 client = client or pf.get('client') or cls.client
779 client.delete_port_forwarding(pf['floatingip_id'], pf['id'])
780
Nurmatov Mamatisa3f2bbb52021-10-20 14:33:54 +0300781 def create_local_ip(cls, network_id=None,
782 client=None, **kwargs):
783 """Creates a Local IP.
784
785 Create a Local IP and schedule it for later deletion.
786 If a client is passed, then it is used for deleting the IP too.
787
788 :param network_id: network ID where to create
789 By default this is 'CONF.network.public_network_id'.
790
791 :param client: network client to be used for creating and cleaning up
792 the Local IP.
793
794 :param **kwargs: additional creation parameters to be forwarded to
795 networking server.
796 """
797
798 client = client or cls.client
799 network_id = (network_id or
800 cls.external_network_id)
801
802 local_ip = client.create_local_ip(network_id,
803 **kwargs)['local_ip']
804
805 # save client to be used later in cls.delete_local_ip
806 # for final cleanup
807 local_ip['client'] = client
808 cls.local_ips.append(local_ip)
809 return local_ip
810
811 @classmethod
812 def delete_local_ip(cls, local_ip, client=None):
813 """Delete Local IP
814
815 :param client: Client to be used
816 If client is not given it will use the client used to create
817 the Local IP, or cls.client if unknown.
818 """
819
820 client = client or local_ip.get('client') or cls.client
821 client.delete_local_ip(local_ip['id'])
822
823 @classmethod
824 def create_local_ip_association(cls, local_ip_id, fixed_port_id,
825 fixed_ip_address=None, client=None):
826 """Creates a Local IP association.
827
828 Create a Local IP Association and schedule it for later deletion.
829 If a client is passed, then it is used for deleting the association
830 too.
831
832 :param local_ip_id: The ID of the Local IP.
833
834 :param fixed_port_id: The ID of the Neutron port
835 to be associated with the Local IP
836
837 :param fixed_ip_address: The fixed IPv4 address of the Neutron
838 port to be associated with the Local IP
839
840 :param client: network client to be used for creating and cleaning up
841 the Local IP Association.
842 """
843
844 client = client or cls.client
845
846 association = client.create_local_ip_association(
847 local_ip_id, fixed_port_id,
848 fixed_ip_address)['port_association']
849
850 # save ID of Local IP for final cleanup
851 association['local_ip_id'] = local_ip_id
852
853 # save client to be used later in
854 # cls.delete_local_ip_association for final cleanup
855 association['client'] = client
856 cls.local_ip_associations.append(association)
857 return association
858
859 @classmethod
860 def delete_local_ip_association(cls, association, client=None):
861
862 """Delete Local IP Association
863
864 :param client: Client to be used
865 If client is not given it will use the client used to create
866 the local IP association, or cls.client if unknown.
867 """
868
869 client = client or association.get('client') or cls.client
870 client.delete_local_ip_association(association['local_ip_id'],
871 association['fixed_port_id'])
872
Slawek Kaplonski003fcae2019-05-26 22:38:35 +0200873 @classmethod
Frode Nordahl1bb8e622023-10-16 15:16:34 +0200874 def create_router_interface(cls, router_id, subnet_id, client=None):
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000875 """Wrapper utility that returns a router interface."""
Frode Nordahl1bb8e622023-10-16 15:16:34 +0200876 client = client or cls.client
877 interface = client.add_router_interface_with_subnet_id(
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000878 router_id, subnet_id)
879 return interface
880
881 @classmethod
Bence Romsics46bd3af2019-09-13 10:52:41 +0200882 def add_extra_routes_atomic(cls, *args, **kwargs):
883 return cls.client.add_extra_routes_atomic(*args, **kwargs)
884
885 @classmethod
886 def remove_extra_routes_atomic(cls, *args, **kwargs):
887 return cls.client.remove_extra_routes_atomic(*args, **kwargs)
888
889 @classmethod
Sławek Kapłońskiff294062016-12-04 15:00:54 +0000890 def get_supported_qos_rule_types(cls):
891 body = cls.client.list_qos_rule_types()
892 return [rule_type['type'] for rule_type in body['rule_types']]
893
894 @classmethod
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200895 def create_qos_policy(cls, name, description=None, shared=False,
Rodolfo Alonso Hernandeze2d062f2020-01-14 17:11:42 +0000896 project_id=None, is_default=False):
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000897 """Wrapper utility that returns a test QoS policy."""
898 body = cls.admin_client.create_qos_policy(
Rodolfo Alonso Hernandeze2d062f2020-01-14 17:11:42 +0000899 name, description, shared, project_id, is_default)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000900 qos_policy = body['policy']
901 cls.qos_policies.append(qos_policy)
902 return qos_policy
903
904 @classmethod
elajkatdbb0b482021-05-04 17:20:07 +0200905 def create_qos_dscp_marking_rule(cls, policy_id, dscp_mark):
906 """Wrapper utility that creates and returns a QoS dscp rule."""
907 body = cls.admin_client.create_dscp_marking_rule(
908 policy_id, dscp_mark)
909 qos_rule = body['dscp_marking_rule']
910 cls.qos_rules.append(qos_rule)
911 return qos_rule
912
913 @classmethod
Jakub Libosvar83704832017-12-06 16:02:28 +0000914 def delete_router(cls, router, client=None):
915 client = client or cls.client
Aditya Vaja49819a72018-11-26 14:20:10 -0800916 if 'routes' in router:
917 client.remove_router_extra_routes(router['id'])
Jakub Libosvar83704832017-12-06 16:02:28 +0000918 body = client.list_router_interfaces(router['id'])
Chandan Kumarc125fd12017-11-15 19:41:01 +0530919 interfaces = [port for port in body['ports']
920 if port['device_owner'] in const.ROUTER_INTERFACE_OWNERS]
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000921 for i in interfaces:
922 try:
Jakub Libosvar83704832017-12-06 16:02:28 +0000923 client.remove_router_interface_with_subnet_id(
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000924 router['id'], i['fixed_ips'][0]['subnet_id'])
925 except lib_exc.NotFound:
926 pass
Jakub Libosvar83704832017-12-06 16:02:28 +0000927 client.delete_router(router['id'])
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000928
929 @classmethod
930 def create_address_scope(cls, name, is_admin=False, **kwargs):
931 if is_admin:
932 body = cls.admin_client.create_address_scope(name=name, **kwargs)
933 cls.admin_address_scopes.append(body['address_scope'])
934 else:
935 body = cls.client.create_address_scope(name=name, **kwargs)
936 cls.address_scopes.append(body['address_scope'])
937 return body['address_scope']
938
939 @classmethod
Igor Malinovskiyb80f1d02020-03-06 13:39:52 +0200940 def create_subnetpool(cls, name, is_admin=False, client=None, **kwargs):
941 if client is None:
942 client = cls.admin_client if is_admin else cls.client
943
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000944 if is_admin:
Igor Malinovskiyb80f1d02020-03-06 13:39:52 +0200945 body = client.create_subnetpool(name, **kwargs)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000946 cls.admin_subnetpools.append(body['subnetpool'])
947 else:
Igor Malinovskiyb80f1d02020-03-06 13:39:52 +0200948 body = client.create_subnetpool(name, **kwargs)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000949 cls.subnetpools.append(body['subnetpool'])
950 return body['subnetpool']
951
Chandan Kumarc125fd12017-11-15 19:41:01 +0530952 @classmethod
Miguel Lavalleb1c7a3d2021-01-31 19:05:22 -0600953 def create_address_group(cls, name, is_admin=False, **kwargs):
954 if is_admin:
955 body = cls.admin_client.create_address_group(name=name, **kwargs)
956 cls.admin_address_groups.append(body['address_group'])
957 else:
958 body = cls.client.create_address_group(name=name, **kwargs)
959 cls.address_groups.append(body['address_group'])
960 return body['address_group']
961
962 @classmethod
Chandan Kumarc125fd12017-11-15 19:41:01 +0530963 def create_project(cls, name=None, description=None):
964 test_project = name or data_utils.rand_name('test_project_')
965 test_description = description or data_utils.rand_name('desc_')
966 project = cls.identity_admin_client.create_project(
967 name=test_project,
968 description=test_description)['project']
969 cls.projects.append(project)
Dongcan Ye2de722e2018-07-04 11:01:37 +0000970 # Create a project will create a default security group.
Dongcan Ye2de722e2018-07-04 11:01:37 +0000971 sgs_list = cls.admin_client.list_security_groups(
972 tenant_id=project['id'])['security_groups']
Federico Ressi4c590d72018-10-10 14:01:08 +0200973 for security_group in sgs_list:
974 # Make sure delete_security_group method will use
975 # the admin client for this group
976 security_group['client'] = cls.admin_client
977 cls.security_groups.append(security_group)
Chandan Kumarc125fd12017-11-15 19:41:01 +0530978 return project
979
980 @classmethod
Federico Ressi4c590d72018-10-10 14:01:08 +0200981 def create_security_group(cls, name=None, project=None, client=None,
982 **kwargs):
983 if project:
984 client = client or cls.admin_client
985 project_id = kwargs.setdefault('project_id', project['id'])
986 tenant_id = kwargs.setdefault('tenant_id', project['id'])
987 if project_id != project['id'] or tenant_id != project['id']:
988 raise ValueError('Project ID specified multiple times')
989 else:
990 client = client or cls.client
991
992 name = name or data_utils.rand_name(cls.__name__)
993 security_group = client.create_security_group(name=name, **kwargs)[
994 'security_group']
995 security_group['client'] = client
996 cls.security_groups.append(security_group)
997 return security_group
998
999 @classmethod
1000 def delete_security_group(cls, security_group, client=None):
1001 client = client or security_group.get('client') or cls.client
1002 client.delete_security_group(security_group['id'])
1003
1004 @classmethod
Slawek Kaplonskiaa22c9e2023-05-18 18:59:26 +02001005 def get_security_group(cls, name='default', client=None):
1006 client = client or cls.client
1007 security_groups = client.list_security_groups()['security_groups']
1008 for security_group in security_groups:
1009 if security_group['name'] == name:
1010 return security_group
1011 raise ValueError("No such security group named {!r}".format(name))
1012
1013 @classmethod
Federico Ressi4c590d72018-10-10 14:01:08 +02001014 def create_security_group_rule(cls, security_group=None, project=None,
1015 client=None, ip_version=None, **kwargs):
1016 if project:
1017 client = client or cls.admin_client
1018 project_id = kwargs.setdefault('project_id', project['id'])
1019 tenant_id = kwargs.setdefault('tenant_id', project['id'])
1020 if project_id != project['id'] or tenant_id != project['id']:
1021 raise ValueError('Project ID specified multiple times')
1022
1023 if 'security_group_id' not in kwargs:
1024 security_group = (security_group or
1025 cls.get_security_group(client=client))
1026
1027 if security_group:
1028 client = client or security_group.get('client')
1029 security_group_id = kwargs.setdefault('security_group_id',
1030 security_group['id'])
1031 if security_group_id != security_group['id']:
1032 raise ValueError('Security group ID specified multiple times.')
1033
1034 ip_version = ip_version or cls._ip_version
1035 default_params = (
1036 constants.DEFAULT_SECURITY_GROUP_RULE_PARAMS[ip_version])
Slawek Kaplonski83979b92022-12-15 14:15:12 +01001037 if (('remote_address_group_id' in kwargs or
1038 'remote_group_id' in kwargs) and
1039 'remote_ip_prefix' in default_params):
Miguel Lavalleb1c7a3d2021-01-31 19:05:22 -06001040 default_params.pop('remote_ip_prefix')
Federico Ressi4c590d72018-10-10 14:01:08 +02001041 for key, value in default_params.items():
1042 kwargs.setdefault(key, value)
1043
1044 client = client or cls.client
1045 return client.create_security_group_rule(**kwargs)[
1046 'security_group_rule']
1047
1048 @classmethod
Slawek Kaplonskiaa22c9e2023-05-18 18:59:26 +02001049 def create_default_security_group_rule(cls, **kwargs):
1050 body = cls.admin_client.create_default_security_group_rule(**kwargs)
1051 default_sg_rule = body['default_security_group_rule']
1052 cls.sg_rule_templates.append(default_sg_rule)
1053 return default_sg_rule
Chandan Kumarc125fd12017-11-15 19:41:01 +05301054
Federico Ressiab286e42018-06-19 09:52:10 +02001055 @classmethod
1056 def create_keypair(cls, client=None, name=None, **kwargs):
1057 client = client or cls.os_primary.keypairs_client
1058 name = name or data_utils.rand_name('keypair-test')
1059 keypair = client.create_keypair(name=name, **kwargs)['keypair']
1060
1061 # save client for later cleanup
1062 keypair['client'] = client
1063 cls.keypairs.append(keypair)
1064 return keypair
1065
1066 @classmethod
1067 def delete_keypair(cls, keypair, client=None):
1068 client = (client or keypair.get('client') or
1069 cls.os_primary.keypairs_client)
1070 client.delete_keypair(keypair_name=keypair['name'])
1071
Federico Ressi82e83e32018-07-03 14:19:55 +02001072 @classmethod
1073 def create_trunk(cls, port=None, subports=None, client=None, **kwargs):
1074 """Create network trunk
1075
1076 :param port: dictionary containing parent port ID (port['id'])
1077 :param client: client to be used for connecting to networking service
1078 :param **kwargs: extra parameters to be forwarded to network service
1079
1080 :returns: dictionary containing created trunk details
1081 """
1082 client = client or cls.client
1083
1084 if port:
1085 kwargs['port_id'] = port['id']
1086
1087 trunk = client.create_trunk(subports=subports, **kwargs)['trunk']
1088 # Save client reference for later deletion
1089 trunk['client'] = client
1090 cls.trunks.append(trunk)
1091 return trunk
1092
1093 @classmethod
Huifeng Le1c9f40b2018-11-07 01:14:21 +08001094 def delete_trunk(cls, trunk, client=None, detach_parent_port=True):
Federico Ressi82e83e32018-07-03 14:19:55 +02001095 """Delete network trunk
1096
1097 :param trunk: dictionary containing trunk ID (trunk['id'])
1098
1099 :param client: client to be used for connecting to networking service
1100 """
1101 client = client or trunk.get('client') or cls.client
1102 trunk.update(client.show_trunk(trunk['id'])['trunk'])
1103
1104 if not trunk['admin_state_up']:
1105 # Cannot touch trunk before admin_state_up is True
1106 client.update_trunk(trunk['id'], admin_state_up=True)
1107 if trunk['sub_ports']:
1108 # Removes trunk ports before deleting it
1109 cls._try_delete_resource(client.remove_subports, trunk['id'],
1110 trunk['sub_ports'])
1111
1112 # we have to detach the interface from the server before
1113 # the trunk can be deleted.
1114 parent_port = {'id': trunk['port_id']}
1115
1116 def is_parent_port_detached():
1117 parent_port.update(client.show_port(parent_port['id'])['port'])
1118 return not parent_port['device_id']
1119
Huifeng Le1c9f40b2018-11-07 01:14:21 +08001120 if detach_parent_port and not is_parent_port_detached():
Federico Ressi82e83e32018-07-03 14:19:55 +02001121 # this could probably happen when trunk is deleted and parent port
1122 # has been assigned to a VM that is still running. Here we are
1123 # assuming that device_id points to such VM.
1124 cls.os_primary.compute.InterfacesClient().delete_interface(
1125 parent_port['device_id'], parent_port['id'])
1126 utils.wait_until_true(is_parent_port_detached)
1127
1128 client.delete_trunk(trunk['id'])
1129
Harald Jensåsc9782fa2019-06-03 22:35:41 +02001130 @classmethod
1131 def create_conntrack_helper(cls, router_id, helper, protocol, port,
1132 client=None):
1133 """Create a conntrack helper
1134
1135 Create a conntrack helper and schedule it for later deletion. If a
1136 client is passed, then it is used for deleteing the CTH too.
1137
1138 :param router_id: The ID of the Neutron router associated to the
1139 conntrack helper.
1140
1141 :param helper: The conntrack helper module alias
1142
1143 :param protocol: The conntrack helper IP protocol used in the conntrack
1144 helper.
1145
1146 :param port: The conntrack helper IP protocol port number for the
1147 conntrack helper.
1148
1149 :param client: network client to be used for creating and cleaning up
1150 the conntrack helper.
1151 """
1152
1153 client = client or cls.client
1154
1155 cth = client.create_conntrack_helper(router_id, helper, protocol,
1156 port)['conntrack_helper']
1157
1158 # save ID of router associated with conntrack helper for final cleanup
1159 cth['router_id'] = router_id
1160
1161 # save client to be used later in cls.delete_conntrack_helper for final
1162 # cleanup
1163 cth['client'] = client
1164 cls.conntrack_helpers.append(cth)
1165 return cth
1166
1167 @classmethod
1168 def delete_conntrack_helper(cls, cth, client=None):
1169 """Delete conntrack helper
1170
1171 :param client: Client to be used
1172 If client is not given it will use the client used to create the
1173 conntrack helper, or cls.client if unknown.
1174 """
1175
1176 client = client or cth.get('client') or cls.client
1177 client.delete_conntrack_helper(cth['router_id'], cth['id'])
1178
yangjianfeng2936a292022-02-04 11:22:11 +08001179 @classmethod
1180 def create_ndp_proxy(cls, router_id, port_id, client=None, **kwargs):
1181 """Creates a ndp proxy.
1182
1183 Create a ndp proxy and schedule it for later deletion.
1184 If a client is passed, then it is used for deleting the NDP proxy too.
1185
1186 :param router_id: router ID where to create the ndp proxy.
1187
1188 :param port_id: port ID which the ndp proxy associate with
1189
1190 :param client: network client to be used for creating and cleaning up
1191 the ndp proxy.
1192
1193 :param **kwargs: additional creation parameters to be forwarded to
1194 networking server.
1195 """
1196 client = client or cls.client
1197
1198 data = {'router_id': router_id, 'port_id': port_id}
1199 if kwargs:
1200 data.update(kwargs)
1201 ndp_proxy = client.create_ndp_proxy(**data)['ndp_proxy']
1202
1203 # save client to be used later in cls.delete_ndp_proxy
1204 # for final cleanup
1205 ndp_proxy['client'] = client
1206 cls.ndp_proxies.append(ndp_proxy)
1207 return ndp_proxy
1208
1209 @classmethod
1210 def delete_ndp_proxy(cls, ndp_proxy, client=None):
1211 """Delete ndp proxy
1212
1213 :param client: Client to be used
1214 If client is not given it will use the client used to create
1215 the ndp proxy, or cls.client if unknown.
1216 """
1217 client = client or ndp_proxy.get('client') or cls.client
1218 client.delete_ndp_proxy(ndp_proxy['id'])
1219
Rodolfo Alonso Hernandez8f726122024-06-24 18:20:15 +00001220 @classmethod
1221 def get_loaded_network_extensions(cls):
1222 """Return the network service loaded extensions
1223
1224 :return: list of strings with the alias of the network service loaded
1225 extensions.
1226 """
1227 body = cls.client.list_extensions()
1228 return [net_ext['alias'] for net_ext in body['extensions']]
1229
Daniel Mellado3c0aeab2016-01-29 11:30:25 +00001230
1231class BaseAdminNetworkTest(BaseNetworkTest):
1232
1233 credentials = ['primary', 'admin']
1234
1235 @classmethod
1236 def setup_clients(cls):
1237 super(BaseAdminNetworkTest, cls).setup_clients()
fumihiko kakumaa216fc12017-07-14 10:43:29 +09001238 cls.admin_client = cls.os_admin.network_client
Jakub Libosvarf5758012017-08-15 13:45:30 +00001239 cls.identity_admin_client = cls.os_admin.projects_client
Daniel Mellado3c0aeab2016-01-29 11:30:25 +00001240
1241 @classmethod
1242 def create_metering_label(cls, name, description):
1243 """Wrapper utility that returns a test metering label."""
1244 body = cls.admin_client.create_metering_label(
1245 description=description,
1246 name=data_utils.rand_name("metering-label"))
1247 metering_label = body['metering_label']
1248 cls.metering_labels.append(metering_label)
1249 return metering_label
1250
1251 @classmethod
1252 def create_metering_label_rule(cls, remote_ip_prefix, direction,
1253 metering_label_id):
1254 """Wrapper utility that returns a test metering label rule."""
1255 body = cls.admin_client.create_metering_label_rule(
1256 remote_ip_prefix=remote_ip_prefix, direction=direction,
1257 metering_label_id=metering_label_id)
1258 metering_label_rule = body['metering_label_rule']
1259 cls.metering_label_rules.append(metering_label_rule)
1260 return metering_label_rule
1261
1262 @classmethod
Kailun Qineaaf9782018-12-20 04:45:01 +08001263 def create_network_segment_range(cls, name, shared,
1264 project_id, network_type,
1265 physical_network, minimum,
1266 maximum):
1267 """Wrapper utility that returns a test network segment range."""
1268 network_segment_range_args = {'name': name,
1269 'shared': shared,
1270 'project_id': project_id,
1271 'network_type': network_type,
1272 'physical_network': physical_network,
1273 'minimum': minimum,
1274 'maximum': maximum}
1275 body = cls.admin_client.create_network_segment_range(
1276 **network_segment_range_args)
1277 network_segment_range = body['network_segment_range']
1278 cls.network_segment_ranges.append(network_segment_range)
1279 return network_segment_range
1280
1281 @classmethod
Daniel Mellado3c0aeab2016-01-29 11:30:25 +00001282 def create_flavor(cls, name, description, service_type):
1283 """Wrapper utility that returns a test flavor."""
1284 body = cls.admin_client.create_flavor(
1285 description=description, service_type=service_type,
1286 name=name)
1287 flavor = body['flavor']
1288 cls.flavors.append(flavor)
1289 return flavor
1290
1291 @classmethod
1292 def create_service_profile(cls, description, metainfo, driver):
1293 """Wrapper utility that returns a test service profile."""
1294 body = cls.admin_client.create_service_profile(
1295 driver=driver, metainfo=metainfo, description=description)
1296 service_profile = body['service_profile']
1297 cls.service_profiles.append(service_profile)
1298 return service_profile
1299
1300 @classmethod
Nguyen Phuong An67993fc2017-11-24 11:30:25 +07001301 def create_log(cls, name, description=None,
1302 resource_type='security_group', resource_id=None,
1303 target_id=None, event='ALL', enabled=True):
1304 """Wrapper utility that returns a test log object."""
1305 log_args = {'name': name,
Nguyen Phuong An67993fc2017-11-24 11:30:25 +07001306 'resource_type': resource_type,
1307 'resource_id': resource_id,
1308 'target_id': target_id,
1309 'event': event,
1310 'enabled': enabled}
Slawek Kaplonskid9fe3022021-08-11 15:25:16 +02001311 if description:
1312 log_args['description'] = description
Nguyen Phuong An67993fc2017-11-24 11:30:25 +07001313 body = cls.admin_client.create_log(**log_args)
1314 log_object = body['log']
1315 cls.log_objects.append(log_object)
1316 return log_object
1317
1318 @classmethod
Daniel Mellado3c0aeab2016-01-29 11:30:25 +00001319 def get_unused_ip(cls, net_id, ip_version=None):
Gary Kotton011345f2016-06-15 08:04:31 -07001320 """Get an unused ip address in a allocation pool of net"""
Daniel Mellado3c0aeab2016-01-29 11:30:25 +00001321 body = cls.admin_client.list_ports(network_id=net_id)
1322 ports = body['ports']
1323 used_ips = []
1324 for port in ports:
1325 used_ips.extend(
1326 [fixed_ip['ip_address'] for fixed_ip in port['fixed_ips']])
1327 body = cls.admin_client.list_subnets(network_id=net_id)
1328 subnets = body['subnets']
1329
1330 for subnet in subnets:
1331 if ip_version and subnet['ip_version'] != ip_version:
1332 continue
1333 cidr = subnet['cidr']
1334 allocation_pools = subnet['allocation_pools']
1335 iterators = []
1336 if allocation_pools:
1337 for allocation_pool in allocation_pools:
1338 iterators.append(netaddr.iter_iprange(
1339 allocation_pool['start'], allocation_pool['end']))
1340 else:
1341 net = netaddr.IPNetwork(cidr)
1342
1343 def _iterip():
1344 for ip in net:
1345 if ip not in (net.network, net.broadcast):
1346 yield ip
1347 iterators.append(iter(_iterip()))
1348
1349 for iterator in iterators:
1350 for ip in iterator:
1351 if str(ip) not in used_ips:
1352 return str(ip)
1353
1354 message = (
1355 "net(%s) has no usable IP address in allocation pools" % net_id)
1356 raise exceptions.InvalidConfiguration(message)
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001357
Lajos Katona2f904652018-08-23 14:04:56 +02001358 @classmethod
1359 def create_provider_network(cls, physnet_name, start_segmentation_id,
Frode Nordahl1bb8e622023-10-16 15:16:34 +02001360 max_attempts=30, external=False):
Lajos Katona2f904652018-08-23 14:04:56 +02001361 segmentation_id = start_segmentation_id
Lajos Katona7eb67252019-01-14 12:55:35 +01001362 for attempts in range(max_attempts):
Lajos Katona2f904652018-08-23 14:04:56 +02001363 try:
Lajos Katona7eb67252019-01-14 12:55:35 +01001364 return cls.create_network(
Lajos Katona2f904652018-08-23 14:04:56 +02001365 name=data_utils.rand_name('test_net'),
Frode Nordahl1bb8e622023-10-16 15:16:34 +02001366 shared=not external,
1367 external=external,
Lajos Katona2f904652018-08-23 14:04:56 +02001368 provider_network_type='vlan',
1369 provider_physical_network=physnet_name,
1370 provider_segmentation_id=segmentation_id)
Lajos Katona2f904652018-08-23 14:04:56 +02001371 except lib_exc.Conflict:
Lajos Katona2f904652018-08-23 14:04:56 +02001372 segmentation_id += 1
1373 if segmentation_id > 4095:
1374 raise lib_exc.TempestException(
1375 "No free segmentation id was found for provider "
1376 "network creation!")
1377 time.sleep(CONF.network.build_interval)
Lajos Katona7eb67252019-01-14 12:55:35 +01001378 LOG.exception("Failed to create provider network after "
1379 "%d attempts", max_attempts)
1380 raise lib_exc.TimeoutException
Lajos Katona2f904652018-08-23 14:04:56 +02001381
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001382
Sławek Kapłońskiff294062016-12-04 15:00:54 +00001383def require_qos_rule_type(rule_type):
1384 def decorator(f):
1385 @functools.wraps(f)
1386 def wrapper(self, *func_args, **func_kwargs):
1387 if rule_type not in self.get_supported_qos_rule_types():
1388 raise self.skipException(
1389 "%s rule type is required." % rule_type)
1390 return f(self, *func_args, **func_kwargs)
1391 return wrapper
1392 return decorator
1393
1394
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001395def _require_sorting(f):
1396 @functools.wraps(f)
1397 def inner(self, *args, **kwargs):
Chandan Kumarc125fd12017-11-15 19:41:01 +05301398 if not tutils.is_extension_enabled("sorting", "network"):
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001399 self.skipTest('Sorting feature is required')
1400 return f(self, *args, **kwargs)
1401 return inner
1402
1403
1404def _require_pagination(f):
1405 @functools.wraps(f)
1406 def inner(self, *args, **kwargs):
Chandan Kumarc125fd12017-11-15 19:41:01 +05301407 if not tutils.is_extension_enabled("pagination", "network"):
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001408 self.skipTest('Pagination feature is required')
1409 return f(self, *args, **kwargs)
1410 return inner
1411
1412
1413class BaseSearchCriteriaTest(BaseNetworkTest):
1414
1415 # This should be defined by subclasses to reflect resource name to test
1416 resource = None
1417
Armando Migliaccio57581c62016-07-01 10:13:19 -07001418 field = 'name'
1419
Ihar Hrachyshkaa8fe5a12016-05-24 14:50:58 +02001420 # NOTE(ihrachys): some names, like those starting with an underscore (_)
1421 # are sorted differently depending on whether the plugin implements native
1422 # sorting support, or not. So we avoid any such cases here, sticking to
1423 # alphanumeric. Also test a case when there are multiple resources with the
1424 # same name
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001425 resource_names = ('test1', 'abc1', 'test10', '123test') + ('test1',)
1426
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001427 force_tenant_isolation = True
1428
Ihar Hrachyshkaa8fe5a12016-05-24 14:50:58 +02001429 list_kwargs = {}
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001430
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001431 list_as_admin = False
1432
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001433 def assertSameOrder(self, original, actual):
1434 # gracefully handle iterators passed
1435 original = list(original)
1436 actual = list(actual)
1437 self.assertEqual(len(original), len(actual))
1438 for expected, res in zip(original, actual):
Armando Migliaccio57581c62016-07-01 10:13:19 -07001439 self.assertEqual(expected[self.field], res[self.field])
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001440
1441 @utils.classproperty
1442 def plural_name(self):
1443 return '%ss' % self.resource
1444
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001445 @property
1446 def list_client(self):
1447 return self.admin_client if self.list_as_admin else self.client
1448
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001449 def list_method(self, *args, **kwargs):
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001450 method = getattr(self.list_client, 'list_%s' % self.plural_name)
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001451 kwargs.update(self.list_kwargs)
1452 return method(*args, **kwargs)
1453
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001454 def get_bare_url(self, url):
1455 base_url = self.client.base_url
zheng.yong74e760a2019-05-22 14:16:14 +08001456 base_url_normalized = utils.normalize_url(base_url)
1457 url_normalized = utils.normalize_url(url)
1458 self.assertTrue(url_normalized.startswith(base_url_normalized))
1459 return url_normalized[len(base_url_normalized):]
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001460
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001461 @classmethod
1462 def _extract_resources(cls, body):
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001463 return body[cls.plural_name]
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001464
yatinkarel02743812024-08-13 18:14:37 +05301465 @classmethod
1466 def _test_resources(cls, resources):
1467 return [res for res in resources if res["name"] in cls.resource_names]
1468
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001469 def _test_list_sorts(self, direction):
1470 sort_args = {
1471 'sort_dir': direction,
Armando Migliaccio57581c62016-07-01 10:13:19 -07001472 'sort_key': self.field
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001473 }
1474 body = self.list_method(**sort_args)
1475 resources = self._extract_resources(body)
1476 self.assertNotEmpty(
1477 resources, "%s list returned is empty" % self.resource)
Armando Migliaccio57581c62016-07-01 10:13:19 -07001478 retrieved_names = [res[self.field] for res in resources]
Martin Kopec71a73242024-01-17 12:02:24 +01001479 # sort without taking into account whether the network is named with
1480 # a capital letter or not
1481 expected = sorted(retrieved_names, key=lambda v: v.upper())
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001482 if direction == constants.SORT_DIRECTION_DESC:
1483 expected = list(reversed(expected))
1484 self.assertEqual(expected, retrieved_names)
1485
1486 @_require_sorting
1487 def _test_list_sorts_asc(self):
1488 self._test_list_sorts(constants.SORT_DIRECTION_ASC)
1489
1490 @_require_sorting
1491 def _test_list_sorts_desc(self):
1492 self._test_list_sorts(constants.SORT_DIRECTION_DESC)
1493
1494 @_require_pagination
1495 def _test_list_pagination(self):
1496 for limit in range(1, len(self.resource_names) + 1):
1497 pagination_args = {
1498 'limit': limit,
1499 }
1500 body = self.list_method(**pagination_args)
1501 resources = self._extract_resources(body)
1502 self.assertEqual(limit, len(resources))
1503
1504 @_require_pagination
1505 def _test_list_no_pagination_limit_0(self):
1506 pagination_args = {
1507 'limit': 0,
1508 }
1509 body = self.list_method(**pagination_args)
1510 resources = self._extract_resources(body)
Béla Vancsicsf1806182016-08-23 07:36:18 +02001511 self.assertGreaterEqual(len(resources), len(self.resource_names))
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001512
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001513 def _test_list_pagination_iteratively(self, lister):
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001514 # first, collect all resources for later comparison
1515 sort_args = {
1516 'sort_dir': constants.SORT_DIRECTION_ASC,
Armando Migliaccio57581c62016-07-01 10:13:19 -07001517 'sort_key': self.field
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001518 }
1519 body = self.list_method(**sort_args)
yatinkarel02743812024-08-13 18:14:37 +05301520 total_resources = self._extract_resources(body)
1521 expected_resources = self._test_resources(total_resources)
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001522 self.assertNotEmpty(expected_resources)
1523
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001524 resources = lister(
yatinkarel02743812024-08-13 18:14:37 +05301525 len(total_resources), sort_args
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001526 )
1527
1528 # finally, compare that the list retrieved in one go is identical to
1529 # the one containing pagination results
1530 self.assertSameOrder(expected_resources, resources)
1531
1532 def _list_all_with_marker(self, niterations, sort_args):
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001533 # paginate resources one by one, using last fetched resource as a
1534 # marker
1535 resources = []
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001536 for i in range(niterations):
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001537 pagination_args = sort_args.copy()
1538 pagination_args['limit'] = 1
1539 if resources:
1540 pagination_args['marker'] = resources[-1]['id']
1541 body = self.list_method(**pagination_args)
1542 resources_ = self._extract_resources(body)
yatinkarela8c221c2024-08-26 16:40:38 +05301543 # Empty resource list can be returned when any concurrent
1544 # tests delete them
1545 self.assertGreaterEqual(1, len(resources_))
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001546 resources.extend(resources_)
yatinkarel02743812024-08-13 18:14:37 +05301547 return self._test_resources(resources)
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001548
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001549 @_require_pagination
1550 @_require_sorting
1551 def _test_list_pagination_with_marker(self):
1552 self._test_list_pagination_iteratively(self._list_all_with_marker)
1553
1554 def _list_all_with_hrefs(self, niterations, sort_args):
1555 # paginate resources one by one, using next href links
1556 resources = []
1557 prev_links = {}
1558
1559 for i in range(niterations):
1560 if prev_links:
1561 uri = self.get_bare_url(prev_links['next'])
1562 else:
Ihar Hrachyshka7f79fe62016-06-07 21:23:44 +02001563 sort_args.update(self.list_kwargs)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001564 uri = self.list_client.build_uri(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001565 self.plural_name, limit=1, **sort_args)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001566 prev_links, body = self.list_client.get_uri_with_links(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001567 self.plural_name, uri
1568 )
1569 resources_ = self._extract_resources(body)
yatinkarela8c221c2024-08-26 16:40:38 +05301570 # Empty resource list can be returned when any concurrent
1571 # tests delete them
1572 self.assertGreaterEqual(1, len(resources_))
yatinkarel02743812024-08-13 18:14:37 +05301573 resources.extend(self._test_resources(resources_))
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001574
1575 # The last element is empty and does not contain 'next' link
1576 uri = self.get_bare_url(prev_links['next'])
1577 prev_links, body = self.client.get_uri_with_links(
1578 self.plural_name, uri
1579 )
1580 self.assertNotIn('next', prev_links)
1581
1582 # Now walk backwards and compare results
1583 resources2 = []
1584 for i in range(niterations):
1585 uri = self.get_bare_url(prev_links['previous'])
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001586 prev_links, body = self.list_client.get_uri_with_links(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001587 self.plural_name, uri
1588 )
1589 resources_ = self._extract_resources(body)
yatinkarela8c221c2024-08-26 16:40:38 +05301590 # Empty resource list can be returned when any concurrent
1591 # tests delete them
1592 self.assertGreaterEqual(1, len(resources_))
yatinkarel02743812024-08-13 18:14:37 +05301593 resources2.extend(self._test_resources(resources_))
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001594
1595 self.assertSameOrder(resources, reversed(resources2))
1596
1597 return resources
1598
1599 @_require_pagination
1600 @_require_sorting
1601 def _test_list_pagination_with_href_links(self):
1602 self._test_list_pagination_iteratively(self._list_all_with_hrefs)
1603
1604 @_require_pagination
1605 @_require_sorting
1606 def _test_list_pagination_page_reverse_with_href_links(
1607 self, direction=constants.SORT_DIRECTION_ASC):
1608 pagination_args = {
1609 'sort_dir': direction,
Armando Migliaccio57581c62016-07-01 10:13:19 -07001610 'sort_key': self.field,
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001611 }
1612 body = self.list_method(**pagination_args)
yatinkarel02743812024-08-13 18:14:37 +05301613 total_resources = self._extract_resources(body)
1614 expected_resources = self._test_resources(total_resources)
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001615
1616 page_size = 2
1617 pagination_args['limit'] = page_size
1618
1619 prev_links = {}
1620 resources = []
yatinkarel02743812024-08-13 18:14:37 +05301621 num_resources = len(total_resources)
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001622 niterations = int(math.ceil(float(num_resources) / page_size))
1623 for i in range(niterations):
1624 if prev_links:
1625 uri = self.get_bare_url(prev_links['previous'])
1626 else:
Ihar Hrachyshka7f79fe62016-06-07 21:23:44 +02001627 pagination_args.update(self.list_kwargs)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001628 uri = self.list_client.build_uri(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001629 self.plural_name, page_reverse=True, **pagination_args)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001630 prev_links, body = self.list_client.get_uri_with_links(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001631 self.plural_name, uri
1632 )
yatinkarel02743812024-08-13 18:14:37 +05301633 resources_ = self._test_resources(self._extract_resources(body))
Béla Vancsicsf1806182016-08-23 07:36:18 +02001634 self.assertGreaterEqual(page_size, len(resources_))
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001635 resources.extend(reversed(resources_))
1636
1637 self.assertSameOrder(expected_resources, reversed(resources))
1638
1639 @_require_pagination
1640 @_require_sorting
1641 def _test_list_pagination_page_reverse_asc(self):
1642 self._test_list_pagination_page_reverse(
1643 direction=constants.SORT_DIRECTION_ASC)
1644
1645 @_require_pagination
1646 @_require_sorting
1647 def _test_list_pagination_page_reverse_desc(self):
1648 self._test_list_pagination_page_reverse(
1649 direction=constants.SORT_DIRECTION_DESC)
1650
1651 def _test_list_pagination_page_reverse(self, direction):
1652 pagination_args = {
1653 'sort_dir': direction,
Armando Migliaccio57581c62016-07-01 10:13:19 -07001654 'sort_key': self.field,
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001655 'limit': 3,
1656 }
1657 body = self.list_method(**pagination_args)
1658 expected_resources = self._extract_resources(body)
1659
1660 pagination_args['limit'] -= 1
1661 pagination_args['marker'] = expected_resources[-1]['id']
1662 pagination_args['page_reverse'] = True
1663 body = self.list_method(**pagination_args)
1664
1665 self.assertSameOrder(
1666 # the last entry is not included in 2nd result when used as a
1667 # marker
1668 expected_resources[:-1],
1669 self._extract_resources(body))
Victor Morales1be97b42016-09-05 08:50:06 -05001670
Hongbin Lu54f55922018-07-12 19:05:39 +00001671 @tutils.requires_ext(extension="filter-validation", service="network")
1672 def _test_list_validation_filters(
1673 self, validation_args, filter_is_valid=True):
1674 if not filter_is_valid:
1675 self.assertRaises(lib_exc.BadRequest, self.list_method,
1676 **validation_args)
1677 else:
1678 body = self.list_method(**validation_args)
1679 resources = self._extract_resources(body)
1680 for resource in resources:
1681 self.assertIn(resource['name'], self.resource_names)