blob: 532fa4340e93e44b427c9752d6bc5400c6493429 [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
Vasyl Saienko8245ad02021-10-22 14:34:41 +0300124 # NOTE(vsaienko): when using static accounts we need
125 # to fill *_id information like project_id, user_id
126 # by authenticating in keystone
127 cls.client.auth_provider.get_token()
128
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000129 @classmethod
130 def resource_setup(cls):
131 super(BaseNetworkTest, cls).resource_setup()
132
133 cls.networks = []
Miguel Lavalle124378b2016-09-21 16:41:47 -0500134 cls.admin_networks = []
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000135 cls.subnets = []
Kevin Bentonba3651c2017-09-01 17:13:01 -0700136 cls.admin_subnets = []
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000137 cls.ports = []
138 cls.routers = []
139 cls.floating_ips = []
Slawek Kaplonski003fcae2019-05-26 22:38:35 +0200140 cls.port_forwardings = []
Nurmatov Mamatisa3f2bbb52021-10-20 14:33:54 +0300141 cls.local_ips = []
142 cls.local_ip_associations = []
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000143 cls.metering_labels = []
144 cls.service_profiles = []
145 cls.flavors = []
146 cls.metering_label_rules = []
147 cls.qos_rules = []
148 cls.qos_policies = []
149 cls.ethertype = "IPv" + str(cls._ip_version)
Miguel Lavalleb1c7a3d2021-01-31 19:05:22 -0600150 cls.address_groups = []
151 cls.admin_address_groups = []
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000152 cls.address_scopes = []
153 cls.admin_address_scopes = []
154 cls.subnetpools = []
155 cls.admin_subnetpools = []
Itzik Brownbac51dc2016-10-31 12:25:04 +0000156 cls.security_groups = []
Dongcan Ye2de722e2018-07-04 11:01:37 +0000157 cls.admin_security_groups = []
Slawek Kaplonskiaa22c9e2023-05-18 18:59:26 +0200158 cls.sg_rule_templates = []
Chandan Kumarc125fd12017-11-15 19:41:01 +0530159 cls.projects = []
Nguyen Phuong An67993fc2017-11-24 11:30:25 +0700160 cls.log_objects = []
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200161 cls.reserved_subnet_cidrs = set()
Federico Ressiab286e42018-06-19 09:52:10 +0200162 cls.keypairs = []
Federico Ressi82e83e32018-07-03 14:19:55 +0200163 cls.trunks = []
Kailun Qineaaf9782018-12-20 04:45:01 +0800164 cls.network_segment_ranges = []
Harald Jensåsc9782fa2019-06-03 22:35:41 +0200165 cls.conntrack_helpers = []
yangjianfeng2936a292022-02-04 11:22:11 +0800166 cls.ndp_proxies = []
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000167
168 @classmethod
yangjianfeng23e40c22020-11-22 08:42:18 +0000169 def reserve_external_subnet_cidrs(cls):
170 client = cls.os_admin.network_client
171 ext_nets = client.list_networks(
172 **{"router:external": True})['networks']
173 for ext_net in ext_nets:
174 ext_subnets = client.list_subnets(
175 network_id=ext_net['id'])['subnets']
176 for ext_subnet in ext_subnets:
177 cls.reserve_subnet_cidr(ext_subnet['cidr'])
178
179 @classmethod
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000180 def resource_cleanup(cls):
181 if CONF.service_available.neutron:
Federico Ressi82e83e32018-07-03 14:19:55 +0200182 # Clean up trunks
183 for trunk in cls.trunks:
184 cls._try_delete_resource(cls.delete_trunk, trunk)
185
yangjianfeng2936a292022-02-04 11:22:11 +0800186 # Clean up ndp proxy
187 for ndp_proxy in cls.ndp_proxies:
188 cls._try_delete_resource(cls.delete_ndp_proxy, ndp_proxy)
189
Slawek Kaplonski003fcae2019-05-26 22:38:35 +0200190 # Clean up port forwardings
191 for pf in cls.port_forwardings:
192 cls._try_delete_resource(cls.delete_port_forwarding, pf)
193
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000194 # Clean up floating IPs
195 for floating_ip in cls.floating_ips:
Federico Ressia69dcd52018-07-06 09:45:34 +0200196 cls._try_delete_resource(cls.delete_floatingip, floating_ip)
197
Nurmatov Mamatisa3f2bbb52021-10-20 14:33:54 +0300198 # Clean up Local IP Associations
199 for association in cls.local_ip_associations:
200 cls._try_delete_resource(cls.delete_local_ip_association,
201 association)
202 # Clean up Local IPs
203 for local_ip in cls.local_ips:
204 cls._try_delete_resource(cls.delete_local_ip,
205 local_ip)
206
Harald Jensåsc9782fa2019-06-03 22:35:41 +0200207 # Clean up conntrack helpers
208 for cth in cls.conntrack_helpers:
209 cls._try_delete_resource(cls.delete_conntrack_helper, cth)
210
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000211 # Clean up routers
212 for router in cls.routers:
213 cls._try_delete_resource(cls.delete_router,
214 router)
215 # Clean up metering label rules
216 for metering_label_rule in cls.metering_label_rules:
217 cls._try_delete_resource(
218 cls.admin_client.delete_metering_label_rule,
219 metering_label_rule['id'])
220 # Clean up metering labels
221 for metering_label in cls.metering_labels:
222 cls._try_delete_resource(
223 cls.admin_client.delete_metering_label,
224 metering_label['id'])
225 # Clean up flavors
226 for flavor in cls.flavors:
227 cls._try_delete_resource(
228 cls.admin_client.delete_flavor,
229 flavor['id'])
230 # Clean up service profiles
231 for service_profile in cls.service_profiles:
232 cls._try_delete_resource(
233 cls.admin_client.delete_service_profile,
234 service_profile['id'])
235 # Clean up ports
236 for port in cls.ports:
237 cls._try_delete_resource(cls.client.delete_port,
238 port['id'])
239 # Clean up subnets
240 for subnet in cls.subnets:
241 cls._try_delete_resource(cls.client.delete_subnet,
242 subnet['id'])
Kevin Bentonba3651c2017-09-01 17:13:01 -0700243 # Clean up admin subnets
244 for subnet in cls.admin_subnets:
245 cls._try_delete_resource(cls.admin_client.delete_subnet,
246 subnet['id'])
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000247 # Clean up networks
248 for network in cls.networks:
Federico Ressi61b564e2018-07-06 08:10:31 +0200249 cls._try_delete_resource(cls.delete_network, network)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000250
Miguel Lavalle124378b2016-09-21 16:41:47 -0500251 # Clean up admin networks
252 for network in cls.admin_networks:
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000253 cls._try_delete_resource(cls.admin_client.delete_network,
254 network['id'])
255
Itzik Brownbac51dc2016-10-31 12:25:04 +0000256 # Clean up security groups
Federico Ressi4c590d72018-10-10 14:01:08 +0200257 for security_group in cls.security_groups:
258 cls._try_delete_resource(cls.delete_security_group,
259 security_group)
Itzik Brownbac51dc2016-10-31 12:25:04 +0000260
Dongcan Ye2de722e2018-07-04 11:01:37 +0000261 # Clean up admin security groups
Federico Ressi4c590d72018-10-10 14:01:08 +0200262 for security_group in cls.admin_security_groups:
263 cls._try_delete_resource(cls.delete_security_group,
264 security_group,
265 client=cls.admin_client)
Dongcan Ye2de722e2018-07-04 11:01:37 +0000266
Slawek Kaplonskiaa22c9e2023-05-18 18:59:26 +0200267 # Clean up security group rule templates
268 for sg_rule_template in cls.sg_rule_templates:
269 cls._try_delete_resource(
270 cls.admin_client.delete_default_security_group_rule,
271 sg_rule_template['id'])
272
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000273 for subnetpool in cls.subnetpools:
274 cls._try_delete_resource(cls.client.delete_subnetpool,
275 subnetpool['id'])
276
277 for subnetpool in cls.admin_subnetpools:
278 cls._try_delete_resource(cls.admin_client.delete_subnetpool,
279 subnetpool['id'])
280
281 for address_scope in cls.address_scopes:
282 cls._try_delete_resource(cls.client.delete_address_scope,
283 address_scope['id'])
284
285 for address_scope in cls.admin_address_scopes:
286 cls._try_delete_resource(
287 cls.admin_client.delete_address_scope,
288 address_scope['id'])
289
Chandan Kumarc125fd12017-11-15 19:41:01 +0530290 for project in cls.projects:
291 cls._try_delete_resource(
292 cls.identity_admin_client.delete_project,
293 project['id'])
294
Sławek Kapłońskie100c4d2017-08-23 21:18:34 +0000295 # Clean up QoS rules
296 for qos_rule in cls.qos_rules:
297 cls._try_delete_resource(cls.admin_client.delete_qos_rule,
298 qos_rule['id'])
299 # Clean up QoS policies
300 # as all networks and ports are already removed, QoS policies
301 # shouldn't be "in use"
302 for qos_policy in cls.qos_policies:
303 cls._try_delete_resource(cls.admin_client.delete_qos_policy,
304 qos_policy['id'])
305
Nguyen Phuong An67993fc2017-11-24 11:30:25 +0700306 # Clean up log_objects
307 for log_object in cls.log_objects:
308 cls._try_delete_resource(cls.admin_client.delete_log,
309 log_object['id'])
310
Federico Ressiab286e42018-06-19 09:52:10 +0200311 for keypair in cls.keypairs:
312 cls._try_delete_resource(cls.delete_keypair, keypair)
313
Kailun Qineaaf9782018-12-20 04:45:01 +0800314 # Clean up network_segment_ranges
315 for network_segment_range in cls.network_segment_ranges:
316 cls._try_delete_resource(
317 cls.admin_client.delete_network_segment_range,
318 network_segment_range['id'])
319
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000320 super(BaseNetworkTest, cls).resource_cleanup()
321
322 @classmethod
323 def _try_delete_resource(cls, delete_callable, *args, **kwargs):
324 """Cleanup resources in case of test-failure
325
326 Some resources are explicitly deleted by the test.
327 If the test failed to delete a resource, this method will execute
328 the appropriate delete methods. Otherwise, the method ignores NotFound
329 exceptions thrown for resources that were correctly deleted by the
330 test.
331
332 :param delete_callable: delete method
333 :param args: arguments for delete method
334 :param kwargs: keyword arguments for delete method
335 """
336 try:
337 delete_callable(*args, **kwargs)
338 # if resource is not found, this means it was deleted in the test
339 except lib_exc.NotFound:
340 pass
341
342 @classmethod
Federico Ressi61b564e2018-07-06 08:10:31 +0200343 def create_network(cls, network_name=None, client=None, external=None,
344 shared=None, provider_network_type=None,
345 provider_physical_network=None,
346 provider_segmentation_id=None, **kwargs):
347 """Create a network.
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000348
Federico Ressi61b564e2018-07-06 08:10:31 +0200349 When client is not provider and admin_client is attribute is not None
350 (for example when using BaseAdminNetworkTest base class) and using any
351 of the convenience parameters (external, shared, provider_network_type,
352 provider_physical_network and provider_segmentation_id) it silently
353 uses admin_client. If the network is not shared then it uses the same
354 project_id as regular client.
355
356 :param network_name: Human-readable name of the network
357
358 :param client: client to be used for connecting to network service
359
360 :param external: indicates whether the network has an external routing
361 facility that's not managed by the networking service.
362
363 :param shared: indicates whether this resource is shared across all
364 projects. By default, only administrative users can change this value.
365 If True and admin_client attribute is not None, then the network is
366 created under administrative project.
367
368 :param provider_network_type: the type of physical network that this
369 network should be mapped to. For example, 'flat', 'vlan', 'vxlan', or
370 'gre'. Valid values depend on a networking back-end.
371
372 :param provider_physical_network: the physical network where this
373 network should be implemented. The Networking API v2.0 does not provide
374 a way to list available physical networks. For example, the Open
375 vSwitch plug-in configuration file defines a symbolic name that maps to
376 specific bridges on each compute host.
377
378 :param provider_segmentation_id: The ID of the isolated segment on the
379 physical network. The network_type attribute defines the segmentation
380 model. For example, if the network_type value is 'vlan', this ID is a
381 vlan identifier. If the network_type value is 'gre', this ID is a gre
382 key.
383
384 :param **kwargs: extra parameters to be forwarded to network service
385 """
386
387 name = (network_name or kwargs.pop('name', None) or
388 data_utils.rand_name('test-network-'))
389
390 # translate convenience parameters
391 admin_client_required = False
392 if provider_network_type:
393 admin_client_required = True
394 kwargs['provider:network_type'] = provider_network_type
395 if provider_physical_network:
396 admin_client_required = True
397 kwargs['provider:physical_network'] = provider_physical_network
398 if provider_segmentation_id:
399 admin_client_required = True
400 kwargs['provider:segmentation_id'] = provider_segmentation_id
401 if external is not None:
402 admin_client_required = True
403 kwargs['router:external'] = bool(external)
404 if shared is not None:
405 admin_client_required = True
406 kwargs['shared'] = bool(shared)
407
408 if not client:
409 if admin_client_required and cls.admin_client:
410 # For convenience silently switch to admin client
411 client = cls.admin_client
412 if not shared:
413 # Keep this network visible from current project
414 project_id = (kwargs.get('project_id') or
415 kwargs.get('tenant_id') or
Takashi Kajinamida451772023-03-22 00:19:39 +0900416 cls.client.project_id)
Federico Ressi61b564e2018-07-06 08:10:31 +0200417 kwargs.update(project_id=project_id, tenant_id=project_id)
418 else:
419 # Use default client
420 client = cls.client
421
422 network = client.create_network(name=name, **kwargs)['network']
423 network['client'] = client
424 cls.networks.append(network)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000425 return network
426
427 @classmethod
Federico Ressi61b564e2018-07-06 08:10:31 +0200428 def delete_network(cls, network, client=None):
429 client = client or network.get('client') or cls.client
430 client.delete_network(network['id'])
431
432 @classmethod
433 def create_shared_network(cls, network_name=None, **kwargs):
434 return cls.create_network(name=network_name, shared=True, **kwargs)
Miguel Lavalle124378b2016-09-21 16:41:47 -0500435
436 @classmethod
Sławek Kapłońskid98e27d2018-05-07 16:16:28 +0200437 def create_subnet(cls, network, gateway='', cidr=None, mask_bits=None,
Federico Ressi98f20ec2018-05-11 06:09:49 +0200438 ip_version=None, client=None, reserve_cidr=True,
Rodolfo Alonso Hernandez780d81e2024-01-14 10:02:13 +0000439 allocation_pool_size=None, **kwargs):
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200440 """Wrapper utility that returns a test subnet.
441
442 Convenient wrapper for client.create_subnet method. It reserves and
443 allocates CIDRs to avoid creating overlapping subnets.
444
445 :param network: network where to create the subnet
446 network['id'] must contain the ID of the network
447
448 :param gateway: gateway IP address
449 It can be a str or a netaddr.IPAddress
450 If gateway is not given, then it will use default address for
451 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 +0200452 if gateway is given as None then no gateway will be assigned
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200453
454 :param cidr: CIDR of the subnet to create
455 It can be either None, a str or a netaddr.IPNetwork instance
456
457 :param mask_bits: CIDR prefix length
458 It can be either None or a numeric value.
459 If cidr parameter is given then mask_bits is used to determinate a
460 sequence of valid CIDR to use as generated.
461 Please see netaddr.IPNetwork.subnet method documentation[1]
462
463 :param ip_version: ip version of generated subnet CIDRs
464 It can be None, IP_VERSION_4 or IP_VERSION_6
465 It has to match given either given CIDR and gateway
466
467 :param ip_version: numeric value (either IP_VERSION_4 or IP_VERSION_6)
468 this value must match CIDR and gateway IP versions if any of them is
469 given
470
471 :param client: client to be used to connect to network service
472
Federico Ressi98f20ec2018-05-11 06:09:49 +0200473 :param reserve_cidr: if True then it reserves assigned CIDR to avoid
474 using the same CIDR for further subnets in the scope of the same
475 test case class
476
Rodolfo Alonso Hernandez780d81e2024-01-14 10:02:13 +0000477 :param allocation_pool_size: if the CIDR is not defined, this method
478 will assign one in ``get_subnet_cidrs``. Once done, the allocation pool
479 will be defined reserving the number of IP addresses requested,
480 starting from the end of the assigned CIDR.
481
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200482 :param **kwargs: optional parameters to be forwarded to wrapped method
483
484 [1] http://netaddr.readthedocs.io/en/latest/tutorial_01.html#supernets-and-subnets # noqa
485 """
Rodolfo Alonso Hernandez780d81e2024-01-14 10:02:13 +0000486 def allocation_pool(cidr, pool_size):
487 start = str(netaddr.IPAddress(cidr.last) - pool_size)
488 end = str(netaddr.IPAddress(cidr.last) - 1)
489 return {'start': start, 'end': end}
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000490
491 # allow tests to use admin client
492 if not client:
493 client = cls.client
494
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200495 if gateway:
496 gateway_ip = netaddr.IPAddress(gateway)
497 if ip_version:
498 if ip_version != gateway_ip.version:
499 raise ValueError(
500 "Gateway IP version doesn't match IP version")
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000501 else:
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200502 ip_version = gateway_ip.version
Sławek Kapłońskid98e27d2018-05-07 16:16:28 +0200503 else:
504 ip_version = ip_version or cls._ip_version
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200505
506 for subnet_cidr in cls.get_subnet_cidrs(
507 ip_version=ip_version, cidr=cidr, mask_bits=mask_bits):
Federico Ressi98f20ec2018-05-11 06:09:49 +0200508 if gateway is not None:
509 kwargs['gateway_ip'] = str(gateway or (subnet_cidr.ip + 1))
Slawek Kaplonski21f53422018-11-02 16:02:09 +0100510 else:
511 kwargs['gateway_ip'] = None
Rodolfo Alonso Hernandez780d81e2024-01-14 10:02:13 +0000512 if allocation_pool_size:
513 kwargs['allocation_pools'] = [
514 allocation_pool(subnet_cidr, allocation_pool_size)]
Federico Ressi98f20ec2018-05-11 06:09:49 +0200515 try:
516 body = client.create_subnet(
517 network_id=network['id'],
518 cidr=str(subnet_cidr),
519 ip_version=subnet_cidr.version,
520 **kwargs)
521 break
522 except lib_exc.BadRequest as e:
523 if 'overlaps with another subnet' not in str(e):
524 raise
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000525 else:
526 message = 'Available CIDR for subnet creation could not be found'
527 raise ValueError(message)
528 subnet = body['subnet']
Kevin Bentonba3651c2017-09-01 17:13:01 -0700529 if client is cls.client:
530 cls.subnets.append(subnet)
531 else:
532 cls.admin_subnets.append(subnet)
Federico Ressi98f20ec2018-05-11 06:09:49 +0200533 if reserve_cidr:
534 cls.reserve_subnet_cidr(subnet_cidr)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000535 return subnet
536
537 @classmethod
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200538 def reserve_subnet_cidr(cls, addr, **ipnetwork_kwargs):
Slawek Kaplonski17e95512024-05-02 14:12:31 +0200539 """Reserve given subnet CIDR making sure it's not used by create_subnet
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200540
541 :param addr: the CIDR address to be reserved
542 It can be a str or netaddr.IPNetwork instance
543
544 :param **ipnetwork_kwargs: optional netaddr.IPNetwork constructor
545 parameters
546 """
547
548 if not cls.try_reserve_subnet_cidr(addr, **ipnetwork_kwargs):
Vasyl Saienko52641532023-10-25 12:22:05 +0000549 LOG.info("Subnet CIDR already reserved: %r", addr)
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200550
551 @classmethod
552 def try_reserve_subnet_cidr(cls, addr, **ipnetwork_kwargs):
553 """Reserve given subnet CIDR if it hasn't been reserved before
554
555 :param addr: the CIDR address to be reserved
556 It can be a str or netaddr.IPNetwork instance
557
558 :param **ipnetwork_kwargs: optional netaddr.IPNetwork constructor
559 parameters
560
561 :return: True if it wasn't reserved before, False elsewhere.
562 """
563
564 subnet_cidr = netaddr.IPNetwork(addr, **ipnetwork_kwargs)
565 if subnet_cidr in cls.reserved_subnet_cidrs:
566 return False
567 else:
568 cls.reserved_subnet_cidrs.add(subnet_cidr)
569 return True
570
571 @classmethod
572 def get_subnet_cidrs(
573 cls, cidr=None, mask_bits=None, ip_version=None):
574 """Iterate over a sequence of unused subnet CIDR for IP version
575
576 :param cidr: CIDR of the subnet to create
577 It can be either None, a str or a netaddr.IPNetwork instance
578
579 :param mask_bits: CIDR prefix length
580 It can be either None or a numeric value.
581 If cidr parameter is given then mask_bits is used to determinate a
582 sequence of valid CIDR to use as generated.
583 Please see netaddr.IPNetwork.subnet method documentation[1]
584
585 :param ip_version: ip version of generated subnet CIDRs
586 It can be None, IP_VERSION_4 or IP_VERSION_6
587 It has to match given CIDR if given
588
589 :return: iterator over reserved CIDRs of type netaddr.IPNetwork
590
591 [1] http://netaddr.readthedocs.io/en/latest/tutorial_01.html#supernets-and-subnets # noqa
592 """
593
594 if cidr:
595 # Generate subnet CIDRs starting from given CIDR
596 # checking it is of requested IP version
597 cidr = netaddr.IPNetwork(cidr, version=ip_version)
598 else:
599 # Generate subnet CIDRs starting from configured values
600 ip_version = ip_version or cls._ip_version
601 if ip_version == const.IP_VERSION_4:
Takashi Kajinami938f2c72024-11-23 02:33:10 +0900602 mask_bits = mask_bits or CONF.network.project_network_mask_bits
603 cidr = netaddr.IPNetwork(CONF.network.project_network_cidr)
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200604 elif ip_version == const.IP_VERSION_6:
Takashi Kajinami938f2c72024-11-23 02:33:10 +0900605 mask_bits = CONF.network.project_network_v6_mask_bits
606 cidr = netaddr.IPNetwork(CONF.network.project_network_v6_cidr)
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200607 else:
608 raise ValueError('Invalid IP version: {!r}'.format(ip_version))
609
610 if mask_bits:
611 subnet_cidrs = cidr.subnet(mask_bits)
612 else:
613 subnet_cidrs = iter([cidr])
614
615 for subnet_cidr in subnet_cidrs:
616 if subnet_cidr not in cls.reserved_subnet_cidrs:
617 yield subnet_cidr
618
619 @classmethod
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000620 def create_port(cls, network, **kwargs):
621 """Wrapper utility that returns a test port."""
Edan Davidd75e48e2018-01-03 02:49:52 -0500622 if CONF.network.port_vnic_type and 'binding:vnic_type' not in kwargs:
623 kwargs['binding:vnic_type'] = CONF.network.port_vnic_type
Glenn Van de Water5d9b1402020-09-16 15:14:14 +0200624 if CONF.network.port_profile and 'binding:profile' not in kwargs:
625 kwargs['binding:profile'] = CONF.network.port_profile
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000626 body = cls.client.create_port(network_id=network['id'],
627 **kwargs)
628 port = body['port']
629 cls.ports.append(port)
630 return port
631
632 @classmethod
633 def update_port(cls, port, **kwargs):
634 """Wrapper utility that updates a test port."""
635 body = cls.client.update_port(port['id'],
636 **kwargs)
637 return body['port']
638
639 @classmethod
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300640 def _create_router_with_client(
641 cls, client, router_name=None, admin_state_up=False,
642 external_network_id=None, enable_snat=None, **kwargs
643 ):
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000644 ext_gw_info = {}
645 if external_network_id:
646 ext_gw_info['network_id'] = external_network_id
YAMAMOTO Takashi9bd4f972017-06-20 12:49:30 +0900647 if enable_snat is not None:
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000648 ext_gw_info['enable_snat'] = enable_snat
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300649 body = client.create_router(
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000650 router_name, external_gateway_info=ext_gw_info,
651 admin_state_up=admin_state_up, **kwargs)
652 router = body['router']
653 cls.routers.append(router)
654 return router
655
656 @classmethod
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300657 def create_router(cls, *args, **kwargs):
658 return cls._create_router_with_client(cls.client, *args, **kwargs)
659
660 @classmethod
661 def create_admin_router(cls, *args, **kwargs):
rajat294495c042017-06-28 15:37:16 +0530662 return cls._create_router_with_client(cls.os_admin.network_client,
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300663 *args, **kwargs)
664
665 @classmethod
Federico Ressia69dcd52018-07-06 09:45:34 +0200666 def create_floatingip(cls, external_network_id=None, port=None,
667 client=None, **kwargs):
668 """Creates a floating IP.
669
670 Create a floating IP and schedule it for later deletion.
671 If a client is passed, then it is used for deleting the IP too.
672
673 :param external_network_id: network ID where to create
674 By default this is 'CONF.network.public_network_id'.
675
676 :param port: port to bind floating IP to
677 This is translated to 'port_id=port['id']'
678 By default it is None.
679
680 :param client: network client to be used for creating and cleaning up
681 the floating IP.
682
683 :param **kwargs: additional creation parameters to be forwarded to
684 networking server.
685 """
686
687 client = client or cls.client
688 external_network_id = (external_network_id or
689 cls.external_network_id)
690
691 if port:
Federico Ressi47f6ae42018-09-24 16:19:14 +0200692 port_id = kwargs.setdefault('port_id', port['id'])
693 if port_id != port['id']:
694 message = "Port ID specified twice: {!s} != {!s}".format(
695 port_id, port['id'])
696 raise ValueError(message)
Federico Ressia69dcd52018-07-06 09:45:34 +0200697
698 fip = client.create_floatingip(external_network_id,
699 **kwargs)['floatingip']
700
701 # save client to be used later in cls.delete_floatingip
702 # for final cleanup
703 fip['client'] = client
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000704 cls.floating_ips.append(fip)
705 return fip
706
707 @classmethod
Federico Ressia69dcd52018-07-06 09:45:34 +0200708 def delete_floatingip(cls, floating_ip, client=None):
709 """Delete floating IP
710
711 :param client: Client to be used
712 If client is not given it will use the client used to create
713 the floating IP, or cls.client if unknown.
714 """
715
716 client = client or floating_ip.get('client') or cls.client
717 client.delete_floatingip(floating_ip['id'])
718
719 @classmethod
Slawek Kaplonski003fcae2019-05-26 22:38:35 +0200720 def create_port_forwarding(cls, fip_id, internal_port_id,
721 internal_port, external_port,
722 internal_ip_address=None, protocol="tcp",
723 client=None):
724 """Creates a port forwarding.
725
726 Create a port forwarding and schedule it for later deletion.
727 If a client is passed, then it is used for deleting the PF too.
728
729 :param fip_id: The ID of the floating IP address.
730
731 :param internal_port_id: The ID of the Neutron port associated to
732 the floating IP port forwarding.
733
734 :param internal_port: The TCP/UDP/other protocol port number of the
735 Neutron port fixed IP address associated to the floating ip
736 port forwarding.
737
738 :param external_port: The TCP/UDP/other protocol port number of
739 the port forwarding floating IP address.
740
741 :param internal_ip_address: The fixed IPv4 address of the Neutron
742 port associated to the floating IP port forwarding.
743
744 :param protocol: The IP protocol used in the floating IP port
745 forwarding.
746
747 :param client: network client to be used for creating and cleaning up
748 the floating IP port forwarding.
749 """
750
751 client = client or cls.client
752
753 pf = client.create_port_forwarding(
754 fip_id, internal_port_id, internal_port, external_port,
755 internal_ip_address, protocol)['port_forwarding']
756
757 # save ID of floating IP associated with port forwarding for final
758 # cleanup
759 pf['floatingip_id'] = fip_id
760
761 # save client to be used later in cls.delete_port_forwarding
762 # for final cleanup
763 pf['client'] = client
764 cls.port_forwardings.append(pf)
765 return pf
766
767 @classmethod
Flavio Fernandesa1952c62020-10-02 06:39:08 -0400768 def update_port_forwarding(cls, fip_id, pf_id, client=None, **kwargs):
769 """Wrapper utility for update_port_forwarding."""
770 client = client or cls.client
771 return client.update_port_forwarding(fip_id, pf_id, **kwargs)
772
773 @classmethod
Slawek Kaplonski003fcae2019-05-26 22:38:35 +0200774 def delete_port_forwarding(cls, pf, client=None):
775 """Delete port forwarding
776
777 :param client: Client to be used
778 If client is not given it will use the client used to create
779 the port forwarding, or cls.client if unknown.
780 """
781
782 client = client or pf.get('client') or cls.client
783 client.delete_port_forwarding(pf['floatingip_id'], pf['id'])
784
Nurmatov Mamatisa3f2bbb52021-10-20 14:33:54 +0300785 def create_local_ip(cls, network_id=None,
786 client=None, **kwargs):
787 """Creates a Local IP.
788
789 Create a Local IP and schedule it for later deletion.
790 If a client is passed, then it is used for deleting the IP too.
791
792 :param network_id: network ID where to create
793 By default this is 'CONF.network.public_network_id'.
794
795 :param client: network client to be used for creating and cleaning up
796 the Local IP.
797
798 :param **kwargs: additional creation parameters to be forwarded to
799 networking server.
800 """
801
802 client = client or cls.client
803 network_id = (network_id or
804 cls.external_network_id)
805
806 local_ip = client.create_local_ip(network_id,
807 **kwargs)['local_ip']
808
809 # save client to be used later in cls.delete_local_ip
810 # for final cleanup
811 local_ip['client'] = client
812 cls.local_ips.append(local_ip)
813 return local_ip
814
815 @classmethod
816 def delete_local_ip(cls, local_ip, client=None):
817 """Delete Local IP
818
819 :param client: Client to be used
820 If client is not given it will use the client used to create
821 the Local IP, or cls.client if unknown.
822 """
823
824 client = client or local_ip.get('client') or cls.client
825 client.delete_local_ip(local_ip['id'])
826
827 @classmethod
828 def create_local_ip_association(cls, local_ip_id, fixed_port_id,
829 fixed_ip_address=None, client=None):
830 """Creates a Local IP association.
831
832 Create a Local IP Association and schedule it for later deletion.
833 If a client is passed, then it is used for deleting the association
834 too.
835
836 :param local_ip_id: The ID of the Local IP.
837
838 :param fixed_port_id: The ID of the Neutron port
839 to be associated with the Local IP
840
841 :param fixed_ip_address: The fixed IPv4 address of the Neutron
842 port to be associated with the Local IP
843
844 :param client: network client to be used for creating and cleaning up
845 the Local IP Association.
846 """
847
848 client = client or cls.client
849
850 association = client.create_local_ip_association(
851 local_ip_id, fixed_port_id,
852 fixed_ip_address)['port_association']
853
854 # save ID of Local IP for final cleanup
855 association['local_ip_id'] = local_ip_id
856
857 # save client to be used later in
858 # cls.delete_local_ip_association for final cleanup
859 association['client'] = client
860 cls.local_ip_associations.append(association)
861 return association
862
863 @classmethod
864 def delete_local_ip_association(cls, association, client=None):
865
866 """Delete Local IP Association
867
868 :param client: Client to be used
869 If client is not given it will use the client used to create
870 the local IP association, or cls.client if unknown.
871 """
872
873 client = client or association.get('client') or cls.client
874 client.delete_local_ip_association(association['local_ip_id'],
875 association['fixed_port_id'])
876
Slawek Kaplonski003fcae2019-05-26 22:38:35 +0200877 @classmethod
Frode Nordahl1bb8e622023-10-16 15:16:34 +0200878 def create_router_interface(cls, router_id, subnet_id, client=None):
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000879 """Wrapper utility that returns a router interface."""
Frode Nordahl1bb8e622023-10-16 15:16:34 +0200880 client = client or cls.client
881 interface = client.add_router_interface_with_subnet_id(
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000882 router_id, subnet_id)
883 return interface
884
885 @classmethod
Bence Romsics46bd3af2019-09-13 10:52:41 +0200886 def add_extra_routes_atomic(cls, *args, **kwargs):
887 return cls.client.add_extra_routes_atomic(*args, **kwargs)
888
889 @classmethod
890 def remove_extra_routes_atomic(cls, *args, **kwargs):
891 return cls.client.remove_extra_routes_atomic(*args, **kwargs)
892
893 @classmethod
Sławek Kapłońskiff294062016-12-04 15:00:54 +0000894 def get_supported_qos_rule_types(cls):
895 body = cls.client.list_qos_rule_types()
896 return [rule_type['type'] for rule_type in body['rule_types']]
897
898 @classmethod
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200899 def create_qos_policy(cls, name, description=None, shared=False,
Rodolfo Alonso Hernandeze2d062f2020-01-14 17:11:42 +0000900 project_id=None, is_default=False):
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000901 """Wrapper utility that returns a test QoS policy."""
902 body = cls.admin_client.create_qos_policy(
Rodolfo Alonso Hernandeze2d062f2020-01-14 17:11:42 +0000903 name, description, shared, project_id, is_default)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000904 qos_policy = body['policy']
905 cls.qos_policies.append(qos_policy)
906 return qos_policy
907
908 @classmethod
elajkatdbb0b482021-05-04 17:20:07 +0200909 def create_qos_dscp_marking_rule(cls, policy_id, dscp_mark):
910 """Wrapper utility that creates and returns a QoS dscp rule."""
911 body = cls.admin_client.create_dscp_marking_rule(
912 policy_id, dscp_mark)
913 qos_rule = body['dscp_marking_rule']
914 cls.qos_rules.append(qos_rule)
915 return qos_rule
916
917 @classmethod
Jakub Libosvar83704832017-12-06 16:02:28 +0000918 def delete_router(cls, router, client=None):
919 client = client or cls.client
Aditya Vaja49819a72018-11-26 14:20:10 -0800920 if 'routes' in router:
921 client.remove_router_extra_routes(router['id'])
Jakub Libosvar83704832017-12-06 16:02:28 +0000922 body = client.list_router_interfaces(router['id'])
Chandan Kumarc125fd12017-11-15 19:41:01 +0530923 interfaces = [port for port in body['ports']
924 if port['device_owner'] in const.ROUTER_INTERFACE_OWNERS]
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000925 for i in interfaces:
926 try:
Jakub Libosvar83704832017-12-06 16:02:28 +0000927 client.remove_router_interface_with_subnet_id(
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000928 router['id'], i['fixed_ips'][0]['subnet_id'])
929 except lib_exc.NotFound:
930 pass
Jakub Libosvar83704832017-12-06 16:02:28 +0000931 client.delete_router(router['id'])
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000932
933 @classmethod
934 def create_address_scope(cls, name, is_admin=False, **kwargs):
935 if is_admin:
936 body = cls.admin_client.create_address_scope(name=name, **kwargs)
937 cls.admin_address_scopes.append(body['address_scope'])
938 else:
939 body = cls.client.create_address_scope(name=name, **kwargs)
940 cls.address_scopes.append(body['address_scope'])
941 return body['address_scope']
942
943 @classmethod
Igor Malinovskiyb80f1d02020-03-06 13:39:52 +0200944 def create_subnetpool(cls, name, is_admin=False, client=None, **kwargs):
945 if client is None:
946 client = cls.admin_client if is_admin else cls.client
947
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000948 if is_admin:
Igor Malinovskiyb80f1d02020-03-06 13:39:52 +0200949 body = client.create_subnetpool(name, **kwargs)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000950 cls.admin_subnetpools.append(body['subnetpool'])
951 else:
Igor Malinovskiyb80f1d02020-03-06 13:39:52 +0200952 body = client.create_subnetpool(name, **kwargs)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000953 cls.subnetpools.append(body['subnetpool'])
954 return body['subnetpool']
955
Chandan Kumarc125fd12017-11-15 19:41:01 +0530956 @classmethod
Miguel Lavalleb1c7a3d2021-01-31 19:05:22 -0600957 def create_address_group(cls, name, is_admin=False, **kwargs):
958 if is_admin:
959 body = cls.admin_client.create_address_group(name=name, **kwargs)
960 cls.admin_address_groups.append(body['address_group'])
961 else:
962 body = cls.client.create_address_group(name=name, **kwargs)
963 cls.address_groups.append(body['address_group'])
964 return body['address_group']
965
966 @classmethod
Chandan Kumarc125fd12017-11-15 19:41:01 +0530967 def create_project(cls, name=None, description=None):
968 test_project = name or data_utils.rand_name('test_project_')
969 test_description = description or data_utils.rand_name('desc_')
970 project = cls.identity_admin_client.create_project(
971 name=test_project,
972 description=test_description)['project']
973 cls.projects.append(project)
Dongcan Ye2de722e2018-07-04 11:01:37 +0000974 # Create a project will create a default security group.
Dongcan Ye2de722e2018-07-04 11:01:37 +0000975 sgs_list = cls.admin_client.list_security_groups(
976 tenant_id=project['id'])['security_groups']
Federico Ressi4c590d72018-10-10 14:01:08 +0200977 for security_group in sgs_list:
978 # Make sure delete_security_group method will use
979 # the admin client for this group
980 security_group['client'] = cls.admin_client
981 cls.security_groups.append(security_group)
Chandan Kumarc125fd12017-11-15 19:41:01 +0530982 return project
983
984 @classmethod
Federico Ressi4c590d72018-10-10 14:01:08 +0200985 def create_security_group(cls, name=None, project=None, client=None,
986 **kwargs):
987 if project:
988 client = client or cls.admin_client
989 project_id = kwargs.setdefault('project_id', project['id'])
990 tenant_id = kwargs.setdefault('tenant_id', project['id'])
991 if project_id != project['id'] or tenant_id != project['id']:
992 raise ValueError('Project ID specified multiple times')
993 else:
994 client = client or cls.client
995
996 name = name or data_utils.rand_name(cls.__name__)
997 security_group = client.create_security_group(name=name, **kwargs)[
998 'security_group']
999 security_group['client'] = client
1000 cls.security_groups.append(security_group)
1001 return security_group
1002
1003 @classmethod
1004 def delete_security_group(cls, security_group, client=None):
1005 client = client or security_group.get('client') or cls.client
1006 client.delete_security_group(security_group['id'])
1007
1008 @classmethod
Slawek Kaplonskiaa22c9e2023-05-18 18:59:26 +02001009 def get_security_group(cls, name='default', client=None):
1010 client = client or cls.client
1011 security_groups = client.list_security_groups()['security_groups']
1012 for security_group in security_groups:
1013 if security_group['name'] == name:
1014 return security_group
1015 raise ValueError("No such security group named {!r}".format(name))
1016
1017 @classmethod
Federico Ressi4c590d72018-10-10 14:01:08 +02001018 def create_security_group_rule(cls, security_group=None, project=None,
1019 client=None, ip_version=None, **kwargs):
1020 if project:
1021 client = client or cls.admin_client
1022 project_id = kwargs.setdefault('project_id', project['id'])
1023 tenant_id = kwargs.setdefault('tenant_id', project['id'])
1024 if project_id != project['id'] or tenant_id != project['id']:
1025 raise ValueError('Project ID specified multiple times')
1026
1027 if 'security_group_id' not in kwargs:
1028 security_group = (security_group or
1029 cls.get_security_group(client=client))
1030
1031 if security_group:
1032 client = client or security_group.get('client')
1033 security_group_id = kwargs.setdefault('security_group_id',
1034 security_group['id'])
1035 if security_group_id != security_group['id']:
1036 raise ValueError('Security group ID specified multiple times.')
1037
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
Michael Polenchukccb43fa2022-09-05 13:29:57 +04001243 cls.admin_client.auth_provider.get_token()
Jakub Libosvarf5758012017-08-15 13:45:30 +00001244 cls.identity_admin_client = cls.os_admin.projects_client
Daniel Mellado3c0aeab2016-01-29 11:30:25 +00001245
1246 @classmethod
1247 def create_metering_label(cls, name, description):
1248 """Wrapper utility that returns a test metering label."""
1249 body = cls.admin_client.create_metering_label(
1250 description=description,
1251 name=data_utils.rand_name("metering-label"))
1252 metering_label = body['metering_label']
1253 cls.metering_labels.append(metering_label)
1254 return metering_label
1255
1256 @classmethod
1257 def create_metering_label_rule(cls, remote_ip_prefix, direction,
1258 metering_label_id):
1259 """Wrapper utility that returns a test metering label rule."""
1260 body = cls.admin_client.create_metering_label_rule(
1261 remote_ip_prefix=remote_ip_prefix, direction=direction,
1262 metering_label_id=metering_label_id)
1263 metering_label_rule = body['metering_label_rule']
1264 cls.metering_label_rules.append(metering_label_rule)
1265 return metering_label_rule
1266
1267 @classmethod
Kailun Qineaaf9782018-12-20 04:45:01 +08001268 def create_network_segment_range(cls, name, shared,
1269 project_id, network_type,
1270 physical_network, minimum,
1271 maximum):
1272 """Wrapper utility that returns a test network segment range."""
1273 network_segment_range_args = {'name': name,
1274 'shared': shared,
1275 'project_id': project_id,
1276 'network_type': network_type,
1277 'physical_network': physical_network,
1278 'minimum': minimum,
1279 'maximum': maximum}
1280 body = cls.admin_client.create_network_segment_range(
1281 **network_segment_range_args)
1282 network_segment_range = body['network_segment_range']
1283 cls.network_segment_ranges.append(network_segment_range)
1284 return network_segment_range
1285
1286 @classmethod
Daniel Mellado3c0aeab2016-01-29 11:30:25 +00001287 def create_flavor(cls, name, description, service_type):
1288 """Wrapper utility that returns a test flavor."""
1289 body = cls.admin_client.create_flavor(
1290 description=description, service_type=service_type,
1291 name=name)
1292 flavor = body['flavor']
1293 cls.flavors.append(flavor)
1294 return flavor
1295
1296 @classmethod
1297 def create_service_profile(cls, description, metainfo, driver):
1298 """Wrapper utility that returns a test service profile."""
1299 body = cls.admin_client.create_service_profile(
1300 driver=driver, metainfo=metainfo, description=description)
1301 service_profile = body['service_profile']
1302 cls.service_profiles.append(service_profile)
1303 return service_profile
1304
1305 @classmethod
Nguyen Phuong An67993fc2017-11-24 11:30:25 +07001306 def create_log(cls, name, description=None,
1307 resource_type='security_group', resource_id=None,
1308 target_id=None, event='ALL', enabled=True):
1309 """Wrapper utility that returns a test log object."""
1310 log_args = {'name': name,
Nguyen Phuong An67993fc2017-11-24 11:30:25 +07001311 'resource_type': resource_type,
1312 'resource_id': resource_id,
1313 'target_id': target_id,
1314 'event': event,
1315 'enabled': enabled}
Slawek Kaplonskid9fe3022021-08-11 15:25:16 +02001316 if description:
1317 log_args['description'] = description
Nguyen Phuong An67993fc2017-11-24 11:30:25 +07001318 body = cls.admin_client.create_log(**log_args)
1319 log_object = body['log']
1320 cls.log_objects.append(log_object)
1321 return log_object
1322
1323 @classmethod
Daniel Mellado3c0aeab2016-01-29 11:30:25 +00001324 def get_unused_ip(cls, net_id, ip_version=None):
Gary Kotton011345f2016-06-15 08:04:31 -07001325 """Get an unused ip address in a allocation pool of net"""
Daniel Mellado3c0aeab2016-01-29 11:30:25 +00001326 body = cls.admin_client.list_ports(network_id=net_id)
1327 ports = body['ports']
1328 used_ips = []
1329 for port in ports:
1330 used_ips.extend(
1331 [fixed_ip['ip_address'] for fixed_ip in port['fixed_ips']])
1332 body = cls.admin_client.list_subnets(network_id=net_id)
1333 subnets = body['subnets']
1334
1335 for subnet in subnets:
1336 if ip_version and subnet['ip_version'] != ip_version:
1337 continue
1338 cidr = subnet['cidr']
1339 allocation_pools = subnet['allocation_pools']
1340 iterators = []
1341 if allocation_pools:
1342 for allocation_pool in allocation_pools:
1343 iterators.append(netaddr.iter_iprange(
1344 allocation_pool['start'], allocation_pool['end']))
1345 else:
1346 net = netaddr.IPNetwork(cidr)
1347
1348 def _iterip():
1349 for ip in net:
1350 if ip not in (net.network, net.broadcast):
1351 yield ip
1352 iterators.append(iter(_iterip()))
1353
1354 for iterator in iterators:
1355 for ip in iterator:
1356 if str(ip) not in used_ips:
1357 return str(ip)
1358
1359 message = (
1360 "net(%s) has no usable IP address in allocation pools" % net_id)
1361 raise exceptions.InvalidConfiguration(message)
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001362
Lajos Katona2f904652018-08-23 14:04:56 +02001363 @classmethod
1364 def create_provider_network(cls, physnet_name, start_segmentation_id,
Frode Nordahl1bb8e622023-10-16 15:16:34 +02001365 max_attempts=30, external=False):
Lajos Katona2f904652018-08-23 14:04:56 +02001366 segmentation_id = start_segmentation_id
Lajos Katona7eb67252019-01-14 12:55:35 +01001367 for attempts in range(max_attempts):
Lajos Katona2f904652018-08-23 14:04:56 +02001368 try:
Lajos Katona7eb67252019-01-14 12:55:35 +01001369 return cls.create_network(
Lajos Katona2f904652018-08-23 14:04:56 +02001370 name=data_utils.rand_name('test_net'),
Frode Nordahl1bb8e622023-10-16 15:16:34 +02001371 shared=not external,
1372 external=external,
Lajos Katona2f904652018-08-23 14:04:56 +02001373 provider_network_type='vlan',
1374 provider_physical_network=physnet_name,
1375 provider_segmentation_id=segmentation_id)
Lajos Katona2f904652018-08-23 14:04:56 +02001376 except lib_exc.Conflict:
Lajos Katona2f904652018-08-23 14:04:56 +02001377 segmentation_id += 1
1378 if segmentation_id > 4095:
1379 raise lib_exc.TempestException(
1380 "No free segmentation id was found for provider "
1381 "network creation!")
1382 time.sleep(CONF.network.build_interval)
Lajos Katona7eb67252019-01-14 12:55:35 +01001383 LOG.exception("Failed to create provider network after "
1384 "%d attempts", max_attempts)
1385 raise lib_exc.TimeoutException
Lajos Katona2f904652018-08-23 14:04:56 +02001386
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001387
Sławek Kapłońskiff294062016-12-04 15:00:54 +00001388def require_qos_rule_type(rule_type):
1389 def decorator(f):
1390 @functools.wraps(f)
1391 def wrapper(self, *func_args, **func_kwargs):
1392 if rule_type not in self.get_supported_qos_rule_types():
1393 raise self.skipException(
1394 "%s rule type is required." % rule_type)
1395 return f(self, *func_args, **func_kwargs)
1396 return wrapper
1397 return decorator
1398
1399
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001400def _require_sorting(f):
1401 @functools.wraps(f)
1402 def inner(self, *args, **kwargs):
Chandan Kumarc125fd12017-11-15 19:41:01 +05301403 if not tutils.is_extension_enabled("sorting", "network"):
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001404 self.skipTest('Sorting feature is required')
1405 return f(self, *args, **kwargs)
1406 return inner
1407
1408
1409def _require_pagination(f):
1410 @functools.wraps(f)
1411 def inner(self, *args, **kwargs):
Chandan Kumarc125fd12017-11-15 19:41:01 +05301412 if not tutils.is_extension_enabled("pagination", "network"):
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001413 self.skipTest('Pagination feature is required')
1414 return f(self, *args, **kwargs)
1415 return inner
1416
1417
1418class BaseSearchCriteriaTest(BaseNetworkTest):
1419
1420 # This should be defined by subclasses to reflect resource name to test
1421 resource = None
1422
Armando Migliaccio57581c62016-07-01 10:13:19 -07001423 field = 'name'
1424
Ihar Hrachyshkaa8fe5a12016-05-24 14:50:58 +02001425 # NOTE(ihrachys): some names, like those starting with an underscore (_)
1426 # are sorted differently depending on whether the plugin implements native
1427 # sorting support, or not. So we avoid any such cases here, sticking to
1428 # alphanumeric. Also test a case when there are multiple resources with the
1429 # same name
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001430 resource_names = ('test1', 'abc1', 'test10', '123test') + ('test1',)
1431
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001432 force_tenant_isolation = True
1433
Ihar Hrachyshkaa8fe5a12016-05-24 14:50:58 +02001434 list_kwargs = {}
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001435
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001436 list_as_admin = False
1437
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001438 def assertSameOrder(self, original, actual):
1439 # gracefully handle iterators passed
1440 original = list(original)
1441 actual = list(actual)
1442 self.assertEqual(len(original), len(actual))
1443 for expected, res in zip(original, actual):
Armando Migliaccio57581c62016-07-01 10:13:19 -07001444 self.assertEqual(expected[self.field], res[self.field])
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001445
1446 @utils.classproperty
1447 def plural_name(self):
1448 return '%ss' % self.resource
1449
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001450 @property
1451 def list_client(self):
1452 return self.admin_client if self.list_as_admin else self.client
1453
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001454 def list_method(self, *args, **kwargs):
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001455 method = getattr(self.list_client, 'list_%s' % self.plural_name)
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001456 kwargs.update(self.list_kwargs)
1457 return method(*args, **kwargs)
1458
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001459 def get_bare_url(self, url):
1460 base_url = self.client.base_url
zheng.yong74e760a2019-05-22 14:16:14 +08001461 base_url_normalized = utils.normalize_url(base_url)
1462 url_normalized = utils.normalize_url(url)
1463 self.assertTrue(url_normalized.startswith(base_url_normalized))
1464 return url_normalized[len(base_url_normalized):]
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001465
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001466 @classmethod
1467 def _extract_resources(cls, body):
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001468 return body[cls.plural_name]
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001469
yatinkarel02743812024-08-13 18:14:37 +05301470 @classmethod
1471 def _test_resources(cls, resources):
1472 return [res for res in resources if res["name"] in cls.resource_names]
1473
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001474 def _test_list_sorts(self, direction):
1475 sort_args = {
1476 'sort_dir': direction,
Armando Migliaccio57581c62016-07-01 10:13:19 -07001477 'sort_key': self.field
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001478 }
1479 body = self.list_method(**sort_args)
1480 resources = self._extract_resources(body)
1481 self.assertNotEmpty(
1482 resources, "%s list returned is empty" % self.resource)
Armando Migliaccio57581c62016-07-01 10:13:19 -07001483 retrieved_names = [res[self.field] for res in resources]
Martin Kopec71a73242024-01-17 12:02:24 +01001484 # sort without taking into account whether the network is named with
1485 # a capital letter or not
1486 expected = sorted(retrieved_names, key=lambda v: v.upper())
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001487 if direction == constants.SORT_DIRECTION_DESC:
1488 expected = list(reversed(expected))
1489 self.assertEqual(expected, retrieved_names)
1490
1491 @_require_sorting
1492 def _test_list_sorts_asc(self):
1493 self._test_list_sorts(constants.SORT_DIRECTION_ASC)
1494
1495 @_require_sorting
1496 def _test_list_sorts_desc(self):
1497 self._test_list_sorts(constants.SORT_DIRECTION_DESC)
1498
1499 @_require_pagination
1500 def _test_list_pagination(self):
1501 for limit in range(1, len(self.resource_names) + 1):
1502 pagination_args = {
1503 'limit': limit,
1504 }
1505 body = self.list_method(**pagination_args)
1506 resources = self._extract_resources(body)
1507 self.assertEqual(limit, len(resources))
1508
1509 @_require_pagination
1510 def _test_list_no_pagination_limit_0(self):
1511 pagination_args = {
1512 'limit': 0,
1513 }
1514 body = self.list_method(**pagination_args)
1515 resources = self._extract_resources(body)
Béla Vancsicsf1806182016-08-23 07:36:18 +02001516 self.assertGreaterEqual(len(resources), len(self.resource_names))
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001517
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001518 def _test_list_pagination_iteratively(self, lister):
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001519 # first, collect all resources for later comparison
1520 sort_args = {
1521 'sort_dir': constants.SORT_DIRECTION_ASC,
Armando Migliaccio57581c62016-07-01 10:13:19 -07001522 'sort_key': self.field
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001523 }
1524 body = self.list_method(**sort_args)
yatinkarel02743812024-08-13 18:14:37 +05301525 total_resources = self._extract_resources(body)
1526 expected_resources = self._test_resources(total_resources)
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001527 self.assertNotEmpty(expected_resources)
1528
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001529 resources = lister(
yatinkarel02743812024-08-13 18:14:37 +05301530 len(total_resources), sort_args
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001531 )
1532
1533 # finally, compare that the list retrieved in one go is identical to
1534 # the one containing pagination results
1535 self.assertSameOrder(expected_resources, resources)
1536
1537 def _list_all_with_marker(self, niterations, sort_args):
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001538 # paginate resources one by one, using last fetched resource as a
1539 # marker
1540 resources = []
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001541 for i in range(niterations):
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001542 pagination_args = sort_args.copy()
1543 pagination_args['limit'] = 1
1544 if resources:
1545 pagination_args['marker'] = resources[-1]['id']
1546 body = self.list_method(**pagination_args)
1547 resources_ = self._extract_resources(body)
yatinkarela8c221c2024-08-26 16:40:38 +05301548 # Empty resource list can be returned when any concurrent
1549 # tests delete them
1550 self.assertGreaterEqual(1, len(resources_))
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001551 resources.extend(resources_)
yatinkarel02743812024-08-13 18:14:37 +05301552 return self._test_resources(resources)
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001553
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001554 @_require_pagination
1555 @_require_sorting
1556 def _test_list_pagination_with_marker(self):
1557 self._test_list_pagination_iteratively(self._list_all_with_marker)
1558
1559 def _list_all_with_hrefs(self, niterations, sort_args):
1560 # paginate resources one by one, using next href links
1561 resources = []
1562 prev_links = {}
1563
1564 for i in range(niterations):
1565 if prev_links:
1566 uri = self.get_bare_url(prev_links['next'])
1567 else:
Ihar Hrachyshka7f79fe62016-06-07 21:23:44 +02001568 sort_args.update(self.list_kwargs)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001569 uri = self.list_client.build_uri(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001570 self.plural_name, limit=1, **sort_args)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001571 prev_links, body = self.list_client.get_uri_with_links(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001572 self.plural_name, uri
1573 )
1574 resources_ = self._extract_resources(body)
yatinkarela8c221c2024-08-26 16:40:38 +05301575 # Empty resource list can be returned when any concurrent
1576 # tests delete them
1577 self.assertGreaterEqual(1, len(resources_))
yatinkarel02743812024-08-13 18:14:37 +05301578 resources.extend(self._test_resources(resources_))
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001579
1580 # The last element is empty and does not contain 'next' link
1581 uri = self.get_bare_url(prev_links['next'])
1582 prev_links, body = self.client.get_uri_with_links(
1583 self.plural_name, uri
1584 )
1585 self.assertNotIn('next', prev_links)
1586
1587 # Now walk backwards and compare results
1588 resources2 = []
1589 for i in range(niterations):
1590 uri = self.get_bare_url(prev_links['previous'])
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001591 prev_links, body = self.list_client.get_uri_with_links(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001592 self.plural_name, uri
1593 )
1594 resources_ = self._extract_resources(body)
yatinkarela8c221c2024-08-26 16:40:38 +05301595 # Empty resource list can be returned when any concurrent
1596 # tests delete them
1597 self.assertGreaterEqual(1, len(resources_))
yatinkarel02743812024-08-13 18:14:37 +05301598 resources2.extend(self._test_resources(resources_))
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001599
1600 self.assertSameOrder(resources, reversed(resources2))
1601
1602 return resources
1603
1604 @_require_pagination
1605 @_require_sorting
1606 def _test_list_pagination_with_href_links(self):
1607 self._test_list_pagination_iteratively(self._list_all_with_hrefs)
1608
1609 @_require_pagination
1610 @_require_sorting
1611 def _test_list_pagination_page_reverse_with_href_links(
1612 self, direction=constants.SORT_DIRECTION_ASC):
1613 pagination_args = {
1614 'sort_dir': direction,
Armando Migliaccio57581c62016-07-01 10:13:19 -07001615 'sort_key': self.field,
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001616 }
1617 body = self.list_method(**pagination_args)
yatinkarel02743812024-08-13 18:14:37 +05301618 total_resources = self._extract_resources(body)
1619 expected_resources = self._test_resources(total_resources)
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001620
1621 page_size = 2
1622 pagination_args['limit'] = page_size
1623
1624 prev_links = {}
1625 resources = []
yatinkarel02743812024-08-13 18:14:37 +05301626 num_resources = len(total_resources)
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001627 niterations = int(math.ceil(float(num_resources) / page_size))
1628 for i in range(niterations):
1629 if prev_links:
1630 uri = self.get_bare_url(prev_links['previous'])
1631 else:
Ihar Hrachyshka7f79fe62016-06-07 21:23:44 +02001632 pagination_args.update(self.list_kwargs)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001633 uri = self.list_client.build_uri(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001634 self.plural_name, page_reverse=True, **pagination_args)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001635 prev_links, body = self.list_client.get_uri_with_links(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001636 self.plural_name, uri
1637 )
yatinkarel02743812024-08-13 18:14:37 +05301638 resources_ = self._test_resources(self._extract_resources(body))
Béla Vancsicsf1806182016-08-23 07:36:18 +02001639 self.assertGreaterEqual(page_size, len(resources_))
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001640 resources.extend(reversed(resources_))
1641
1642 self.assertSameOrder(expected_resources, reversed(resources))
1643
1644 @_require_pagination
1645 @_require_sorting
1646 def _test_list_pagination_page_reverse_asc(self):
1647 self._test_list_pagination_page_reverse(
1648 direction=constants.SORT_DIRECTION_ASC)
1649
1650 @_require_pagination
1651 @_require_sorting
1652 def _test_list_pagination_page_reverse_desc(self):
1653 self._test_list_pagination_page_reverse(
1654 direction=constants.SORT_DIRECTION_DESC)
1655
1656 def _test_list_pagination_page_reverse(self, direction):
1657 pagination_args = {
1658 'sort_dir': direction,
Armando Migliaccio57581c62016-07-01 10:13:19 -07001659 'sort_key': self.field,
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001660 'limit': 3,
1661 }
1662 body = self.list_method(**pagination_args)
1663 expected_resources = self._extract_resources(body)
1664
1665 pagination_args['limit'] -= 1
1666 pagination_args['marker'] = expected_resources[-1]['id']
1667 pagination_args['page_reverse'] = True
1668 body = self.list_method(**pagination_args)
1669
1670 self.assertSameOrder(
1671 # the last entry is not included in 2nd result when used as a
1672 # marker
1673 expected_resources[:-1],
1674 self._extract_resources(body))
Victor Morales1be97b42016-09-05 08:50:06 -05001675
Hongbin Lu54f55922018-07-12 19:05:39 +00001676 @tutils.requires_ext(extension="filter-validation", service="network")
1677 def _test_list_validation_filters(
1678 self, validation_args, filter_is_valid=True):
1679 if not filter_is_valid:
1680 self.assertRaises(lib_exc.BadRequest, self.list_method,
1681 **validation_args)
1682 else:
1683 body = self.list_method(**validation_args)
1684 resources = self._extract_resources(body)
1685 for resource in resources:
1686 self.assertIn(resource['name'], self.resource_names)