blob: 7f961685e3a2028c64922aa6d9d90aefb38c59b2 [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
Daniel Mellado3c0aeab2016-01-29 11:30:25 +000071 @classmethod
72 def get_client_manager(cls, credential_type=None, roles=None,
73 force_new=None):
Genadi Chereshnyacc395c02016-07-25 12:17:37 +030074 manager = super(BaseNetworkTest, cls).get_client_manager(
75 credential_type=credential_type,
76 roles=roles,
77 force_new=force_new
78 )
Daniel Mellado3c0aeab2016-01-29 11:30:25 +000079 # Neutron uses a different clients manager than the one in the Tempest
Jens Harbott860b46a2017-11-15 21:23:15 +000080 # save the original in case mixed tests need it
81 if credential_type == 'primary':
82 cls.os_tempest = manager
Daniel Mellado3c0aeab2016-01-29 11:30:25 +000083 return clients.Manager(manager.credentials)
84
85 @classmethod
86 def skip_checks(cls):
87 super(BaseNetworkTest, cls).skip_checks()
88 if not CONF.service_available.neutron:
89 raise cls.skipException("Neutron support is required")
Federico Ressi0ddc93b2018-04-09 12:01:48 +020090 if (cls._ip_version == const.IP_VERSION_6 and
91 not CONF.network_feature_enabled.ipv6):
Daniel Mellado3c0aeab2016-01-29 11:30:25 +000092 raise cls.skipException("IPv6 Tests are disabled.")
Jakub Libosvar1982aa12017-05-30 11:15:33 +000093 for req_ext in getattr(cls, 'required_extensions', []):
Chandan Kumarc125fd12017-11-15 19:41:01 +053094 if not tutils.is_extension_enabled(req_ext, 'network'):
Jakub Libosvar1982aa12017-05-30 11:15:33 +000095 msg = "%s extension not enabled." % req_ext
96 raise cls.skipException(msg)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +000097
98 @classmethod
99 def setup_credentials(cls):
100 # Create no network resources for these test.
101 cls.set_network_resources()
102 super(BaseNetworkTest, cls).setup_credentials()
103
104 @classmethod
105 def setup_clients(cls):
106 super(BaseNetworkTest, cls).setup_clients()
fumihiko kakumaa216fc12017-07-14 10:43:29 +0900107 cls.client = cls.os_primary.network_client
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000108
109 @classmethod
110 def resource_setup(cls):
111 super(BaseNetworkTest, cls).resource_setup()
112
113 cls.networks = []
Miguel Lavalle124378b2016-09-21 16:41:47 -0500114 cls.admin_networks = []
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000115 cls.subnets = []
Kevin Bentonba3651c2017-09-01 17:13:01 -0700116 cls.admin_subnets = []
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000117 cls.ports = []
118 cls.routers = []
119 cls.floating_ips = []
Slawek Kaplonski003fcae2019-05-26 22:38:35 +0200120 cls.port_forwardings = []
Nurmatov Mamatisa3f2bbb52021-10-20 14:33:54 +0300121 cls.local_ips = []
122 cls.local_ip_associations = []
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000123 cls.metering_labels = []
124 cls.service_profiles = []
125 cls.flavors = []
126 cls.metering_label_rules = []
127 cls.qos_rules = []
128 cls.qos_policies = []
129 cls.ethertype = "IPv" + str(cls._ip_version)
Miguel Lavalleb1c7a3d2021-01-31 19:05:22 -0600130 cls.address_groups = []
131 cls.admin_address_groups = []
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000132 cls.address_scopes = []
133 cls.admin_address_scopes = []
134 cls.subnetpools = []
135 cls.admin_subnetpools = []
Itzik Brownbac51dc2016-10-31 12:25:04 +0000136 cls.security_groups = []
Dongcan Ye2de722e2018-07-04 11:01:37 +0000137 cls.admin_security_groups = []
Slawek Kaplonskiaa22c9e2023-05-18 18:59:26 +0200138 cls.sg_rule_templates = []
Chandan Kumarc125fd12017-11-15 19:41:01 +0530139 cls.projects = []
Nguyen Phuong An67993fc2017-11-24 11:30:25 +0700140 cls.log_objects = []
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200141 cls.reserved_subnet_cidrs = set()
Federico Ressiab286e42018-06-19 09:52:10 +0200142 cls.keypairs = []
Federico Ressi82e83e32018-07-03 14:19:55 +0200143 cls.trunks = []
Kailun Qineaaf9782018-12-20 04:45:01 +0800144 cls.network_segment_ranges = []
Harald Jensåsc9782fa2019-06-03 22:35:41 +0200145 cls.conntrack_helpers = []
yangjianfeng2936a292022-02-04 11:22:11 +0800146 cls.ndp_proxies = []
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000147
148 @classmethod
yangjianfeng23e40c22020-11-22 08:42:18 +0000149 def reserve_external_subnet_cidrs(cls):
150 client = cls.os_admin.network_client
151 ext_nets = client.list_networks(
152 **{"router:external": True})['networks']
153 for ext_net in ext_nets:
154 ext_subnets = client.list_subnets(
155 network_id=ext_net['id'])['subnets']
156 for ext_subnet in ext_subnets:
157 cls.reserve_subnet_cidr(ext_subnet['cidr'])
158
159 @classmethod
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000160 def resource_cleanup(cls):
161 if CONF.service_available.neutron:
Federico Ressi82e83e32018-07-03 14:19:55 +0200162 # Clean up trunks
163 for trunk in cls.trunks:
164 cls._try_delete_resource(cls.delete_trunk, trunk)
165
yangjianfeng2936a292022-02-04 11:22:11 +0800166 # Clean up ndp proxy
167 for ndp_proxy in cls.ndp_proxies:
168 cls._try_delete_resource(cls.delete_ndp_proxy, ndp_proxy)
169
Slawek Kaplonski003fcae2019-05-26 22:38:35 +0200170 # Clean up port forwardings
171 for pf in cls.port_forwardings:
172 cls._try_delete_resource(cls.delete_port_forwarding, pf)
173
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000174 # Clean up floating IPs
175 for floating_ip in cls.floating_ips:
Federico Ressia69dcd52018-07-06 09:45:34 +0200176 cls._try_delete_resource(cls.delete_floatingip, floating_ip)
177
Nurmatov Mamatisa3f2bbb52021-10-20 14:33:54 +0300178 # Clean up Local IP Associations
179 for association in cls.local_ip_associations:
180 cls._try_delete_resource(cls.delete_local_ip_association,
181 association)
182 # Clean up Local IPs
183 for local_ip in cls.local_ips:
184 cls._try_delete_resource(cls.delete_local_ip,
185 local_ip)
186
Harald Jensåsc9782fa2019-06-03 22:35:41 +0200187 # Clean up conntrack helpers
188 for cth in cls.conntrack_helpers:
189 cls._try_delete_resource(cls.delete_conntrack_helper, cth)
190
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000191 # Clean up routers
192 for router in cls.routers:
193 cls._try_delete_resource(cls.delete_router,
194 router)
195 # Clean up metering label rules
196 for metering_label_rule in cls.metering_label_rules:
197 cls._try_delete_resource(
198 cls.admin_client.delete_metering_label_rule,
199 metering_label_rule['id'])
200 # Clean up metering labels
201 for metering_label in cls.metering_labels:
202 cls._try_delete_resource(
203 cls.admin_client.delete_metering_label,
204 metering_label['id'])
205 # Clean up flavors
206 for flavor in cls.flavors:
207 cls._try_delete_resource(
208 cls.admin_client.delete_flavor,
209 flavor['id'])
210 # Clean up service profiles
211 for service_profile in cls.service_profiles:
212 cls._try_delete_resource(
213 cls.admin_client.delete_service_profile,
214 service_profile['id'])
215 # Clean up ports
216 for port in cls.ports:
217 cls._try_delete_resource(cls.client.delete_port,
218 port['id'])
219 # Clean up subnets
220 for subnet in cls.subnets:
221 cls._try_delete_resource(cls.client.delete_subnet,
222 subnet['id'])
Kevin Bentonba3651c2017-09-01 17:13:01 -0700223 # Clean up admin subnets
224 for subnet in cls.admin_subnets:
225 cls._try_delete_resource(cls.admin_client.delete_subnet,
226 subnet['id'])
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000227 # Clean up networks
228 for network in cls.networks:
Federico Ressi61b564e2018-07-06 08:10:31 +0200229 cls._try_delete_resource(cls.delete_network, network)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000230
Miguel Lavalle124378b2016-09-21 16:41:47 -0500231 # Clean up admin networks
232 for network in cls.admin_networks:
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000233 cls._try_delete_resource(cls.admin_client.delete_network,
234 network['id'])
235
Itzik Brownbac51dc2016-10-31 12:25:04 +0000236 # Clean up security groups
Federico Ressi4c590d72018-10-10 14:01:08 +0200237 for security_group in cls.security_groups:
238 cls._try_delete_resource(cls.delete_security_group,
239 security_group)
Itzik Brownbac51dc2016-10-31 12:25:04 +0000240
Dongcan Ye2de722e2018-07-04 11:01:37 +0000241 # Clean up admin security groups
Federico Ressi4c590d72018-10-10 14:01:08 +0200242 for security_group in cls.admin_security_groups:
243 cls._try_delete_resource(cls.delete_security_group,
244 security_group,
245 client=cls.admin_client)
Dongcan Ye2de722e2018-07-04 11:01:37 +0000246
Slawek Kaplonskiaa22c9e2023-05-18 18:59:26 +0200247 # Clean up security group rule templates
248 for sg_rule_template in cls.sg_rule_templates:
249 cls._try_delete_resource(
250 cls.admin_client.delete_default_security_group_rule,
251 sg_rule_template['id'])
252
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000253 for subnetpool in cls.subnetpools:
254 cls._try_delete_resource(cls.client.delete_subnetpool,
255 subnetpool['id'])
256
257 for subnetpool in cls.admin_subnetpools:
258 cls._try_delete_resource(cls.admin_client.delete_subnetpool,
259 subnetpool['id'])
260
261 for address_scope in cls.address_scopes:
262 cls._try_delete_resource(cls.client.delete_address_scope,
263 address_scope['id'])
264
265 for address_scope in cls.admin_address_scopes:
266 cls._try_delete_resource(
267 cls.admin_client.delete_address_scope,
268 address_scope['id'])
269
Chandan Kumarc125fd12017-11-15 19:41:01 +0530270 for project in cls.projects:
271 cls._try_delete_resource(
272 cls.identity_admin_client.delete_project,
273 project['id'])
274
Sławek Kapłońskie100c4d2017-08-23 21:18:34 +0000275 # Clean up QoS rules
276 for qos_rule in cls.qos_rules:
277 cls._try_delete_resource(cls.admin_client.delete_qos_rule,
278 qos_rule['id'])
279 # Clean up QoS policies
280 # as all networks and ports are already removed, QoS policies
281 # shouldn't be "in use"
282 for qos_policy in cls.qos_policies:
283 cls._try_delete_resource(cls.admin_client.delete_qos_policy,
284 qos_policy['id'])
285
Nguyen Phuong An67993fc2017-11-24 11:30:25 +0700286 # Clean up log_objects
287 for log_object in cls.log_objects:
288 cls._try_delete_resource(cls.admin_client.delete_log,
289 log_object['id'])
290
Federico Ressiab286e42018-06-19 09:52:10 +0200291 for keypair in cls.keypairs:
292 cls._try_delete_resource(cls.delete_keypair, keypair)
293
Kailun Qineaaf9782018-12-20 04:45:01 +0800294 # Clean up network_segment_ranges
295 for network_segment_range in cls.network_segment_ranges:
296 cls._try_delete_resource(
297 cls.admin_client.delete_network_segment_range,
298 network_segment_range['id'])
299
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000300 super(BaseNetworkTest, cls).resource_cleanup()
301
302 @classmethod
303 def _try_delete_resource(cls, delete_callable, *args, **kwargs):
304 """Cleanup resources in case of test-failure
305
306 Some resources are explicitly deleted by the test.
307 If the test failed to delete a resource, this method will execute
308 the appropriate delete methods. Otherwise, the method ignores NotFound
309 exceptions thrown for resources that were correctly deleted by the
310 test.
311
312 :param delete_callable: delete method
313 :param args: arguments for delete method
314 :param kwargs: keyword arguments for delete method
315 """
316 try:
317 delete_callable(*args, **kwargs)
318 # if resource is not found, this means it was deleted in the test
319 except lib_exc.NotFound:
320 pass
321
322 @classmethod
Federico Ressi61b564e2018-07-06 08:10:31 +0200323 def create_network(cls, network_name=None, client=None, external=None,
324 shared=None, provider_network_type=None,
325 provider_physical_network=None,
326 provider_segmentation_id=None, **kwargs):
327 """Create a network.
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000328
Federico Ressi61b564e2018-07-06 08:10:31 +0200329 When client is not provider and admin_client is attribute is not None
330 (for example when using BaseAdminNetworkTest base class) and using any
331 of the convenience parameters (external, shared, provider_network_type,
332 provider_physical_network and provider_segmentation_id) it silently
333 uses admin_client. If the network is not shared then it uses the same
334 project_id as regular client.
335
336 :param network_name: Human-readable name of the network
337
338 :param client: client to be used for connecting to network service
339
340 :param external: indicates whether the network has an external routing
341 facility that's not managed by the networking service.
342
343 :param shared: indicates whether this resource is shared across all
344 projects. By default, only administrative users can change this value.
345 If True and admin_client attribute is not None, then the network is
346 created under administrative project.
347
348 :param provider_network_type: the type of physical network that this
349 network should be mapped to. For example, 'flat', 'vlan', 'vxlan', or
350 'gre'. Valid values depend on a networking back-end.
351
352 :param provider_physical_network: the physical network where this
353 network should be implemented. The Networking API v2.0 does not provide
354 a way to list available physical networks. For example, the Open
355 vSwitch plug-in configuration file defines a symbolic name that maps to
356 specific bridges on each compute host.
357
358 :param provider_segmentation_id: The ID of the isolated segment on the
359 physical network. The network_type attribute defines the segmentation
360 model. For example, if the network_type value is 'vlan', this ID is a
361 vlan identifier. If the network_type value is 'gre', this ID is a gre
362 key.
363
364 :param **kwargs: extra parameters to be forwarded to network service
365 """
366
367 name = (network_name or kwargs.pop('name', None) or
368 data_utils.rand_name('test-network-'))
369
370 # translate convenience parameters
371 admin_client_required = False
372 if provider_network_type:
373 admin_client_required = True
374 kwargs['provider:network_type'] = provider_network_type
375 if provider_physical_network:
376 admin_client_required = True
377 kwargs['provider:physical_network'] = provider_physical_network
378 if provider_segmentation_id:
379 admin_client_required = True
380 kwargs['provider:segmentation_id'] = provider_segmentation_id
381 if external is not None:
382 admin_client_required = True
383 kwargs['router:external'] = bool(external)
384 if shared is not None:
385 admin_client_required = True
386 kwargs['shared'] = bool(shared)
387
388 if not client:
389 if admin_client_required and cls.admin_client:
390 # For convenience silently switch to admin client
391 client = cls.admin_client
392 if not shared:
393 # Keep this network visible from current project
394 project_id = (kwargs.get('project_id') or
395 kwargs.get('tenant_id') or
Takashi Kajinamida451772023-03-22 00:19:39 +0900396 cls.client.project_id)
Federico Ressi61b564e2018-07-06 08:10:31 +0200397 kwargs.update(project_id=project_id, tenant_id=project_id)
398 else:
399 # Use default client
400 client = cls.client
401
402 network = client.create_network(name=name, **kwargs)['network']
403 network['client'] = client
404 cls.networks.append(network)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000405 return network
406
407 @classmethod
Federico Ressi61b564e2018-07-06 08:10:31 +0200408 def delete_network(cls, network, client=None):
409 client = client or network.get('client') or cls.client
410 client.delete_network(network['id'])
411
412 @classmethod
413 def create_shared_network(cls, network_name=None, **kwargs):
414 return cls.create_network(name=network_name, shared=True, **kwargs)
Miguel Lavalle124378b2016-09-21 16:41:47 -0500415
416 @classmethod
Sławek Kapłońskid98e27d2018-05-07 16:16:28 +0200417 def create_subnet(cls, network, gateway='', cidr=None, mask_bits=None,
Federico Ressi98f20ec2018-05-11 06:09:49 +0200418 ip_version=None, client=None, reserve_cidr=True,
Rodolfo Alonso Hernandez780d81e2024-01-14 10:02:13 +0000419 allocation_pool_size=None, **kwargs):
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200420 """Wrapper utility that returns a test subnet.
421
422 Convenient wrapper for client.create_subnet method. It reserves and
423 allocates CIDRs to avoid creating overlapping subnets.
424
425 :param network: network where to create the subnet
426 network['id'] must contain the ID of the network
427
428 :param gateway: gateway IP address
429 It can be a str or a netaddr.IPAddress
430 If gateway is not given, then it will use default address for
431 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 +0200432 if gateway is given as None then no gateway will be assigned
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200433
434 :param cidr: CIDR of the subnet to create
435 It can be either None, a str or a netaddr.IPNetwork instance
436
437 :param mask_bits: CIDR prefix length
438 It can be either None or a numeric value.
439 If cidr parameter is given then mask_bits is used to determinate a
440 sequence of valid CIDR to use as generated.
441 Please see netaddr.IPNetwork.subnet method documentation[1]
442
443 :param ip_version: ip version of generated subnet CIDRs
444 It can be None, IP_VERSION_4 or IP_VERSION_6
445 It has to match given either given CIDR and gateway
446
447 :param ip_version: numeric value (either IP_VERSION_4 or IP_VERSION_6)
448 this value must match CIDR and gateway IP versions if any of them is
449 given
450
451 :param client: client to be used to connect to network service
452
Federico Ressi98f20ec2018-05-11 06:09:49 +0200453 :param reserve_cidr: if True then it reserves assigned CIDR to avoid
454 using the same CIDR for further subnets in the scope of the same
455 test case class
456
Rodolfo Alonso Hernandez780d81e2024-01-14 10:02:13 +0000457 :param allocation_pool_size: if the CIDR is not defined, this method
458 will assign one in ``get_subnet_cidrs``. Once done, the allocation pool
459 will be defined reserving the number of IP addresses requested,
460 starting from the end of the assigned CIDR.
461
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200462 :param **kwargs: optional parameters to be forwarded to wrapped method
463
464 [1] http://netaddr.readthedocs.io/en/latest/tutorial_01.html#supernets-and-subnets # noqa
465 """
Rodolfo Alonso Hernandez780d81e2024-01-14 10:02:13 +0000466 def allocation_pool(cidr, pool_size):
467 start = str(netaddr.IPAddress(cidr.last) - pool_size)
468 end = str(netaddr.IPAddress(cidr.last) - 1)
469 return {'start': start, 'end': end}
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000470
471 # allow tests to use admin client
472 if not client:
473 client = cls.client
474
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200475 if gateway:
476 gateway_ip = netaddr.IPAddress(gateway)
477 if ip_version:
478 if ip_version != gateway_ip.version:
479 raise ValueError(
480 "Gateway IP version doesn't match IP version")
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000481 else:
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200482 ip_version = gateway_ip.version
Sławek Kapłońskid98e27d2018-05-07 16:16:28 +0200483 else:
484 ip_version = ip_version or cls._ip_version
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200485
486 for subnet_cidr in cls.get_subnet_cidrs(
487 ip_version=ip_version, cidr=cidr, mask_bits=mask_bits):
Federico Ressi98f20ec2018-05-11 06:09:49 +0200488 if gateway is not None:
489 kwargs['gateway_ip'] = str(gateway or (subnet_cidr.ip + 1))
Slawek Kaplonski21f53422018-11-02 16:02:09 +0100490 else:
491 kwargs['gateway_ip'] = None
Rodolfo Alonso Hernandez780d81e2024-01-14 10:02:13 +0000492 if allocation_pool_size:
493 kwargs['allocation_pools'] = [
494 allocation_pool(subnet_cidr, allocation_pool_size)]
Federico Ressi98f20ec2018-05-11 06:09:49 +0200495 try:
496 body = client.create_subnet(
497 network_id=network['id'],
498 cidr=str(subnet_cidr),
499 ip_version=subnet_cidr.version,
500 **kwargs)
501 break
502 except lib_exc.BadRequest as e:
503 if 'overlaps with another subnet' not in str(e):
504 raise
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000505 else:
506 message = 'Available CIDR for subnet creation could not be found'
507 raise ValueError(message)
508 subnet = body['subnet']
Kevin Bentonba3651c2017-09-01 17:13:01 -0700509 if client is cls.client:
510 cls.subnets.append(subnet)
511 else:
512 cls.admin_subnets.append(subnet)
Federico Ressi98f20ec2018-05-11 06:09:49 +0200513 if reserve_cidr:
514 cls.reserve_subnet_cidr(subnet_cidr)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000515 return subnet
516
517 @classmethod
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200518 def reserve_subnet_cidr(cls, addr, **ipnetwork_kwargs):
519 """Reserve given subnet CIDR making sure it is not used by create_subnet
520
521 :param addr: the CIDR address to be reserved
522 It can be a str or netaddr.IPNetwork instance
523
524 :param **ipnetwork_kwargs: optional netaddr.IPNetwork constructor
525 parameters
526 """
527
528 if not cls.try_reserve_subnet_cidr(addr, **ipnetwork_kwargs):
Bernard Cafarellic3bec862020-09-10 13:59:49 +0200529 raise ValueError('Subnet CIDR already reserved: {0!r}'.format(
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200530 addr))
531
532 @classmethod
533 def try_reserve_subnet_cidr(cls, addr, **ipnetwork_kwargs):
534 """Reserve given subnet CIDR if it hasn't been reserved before
535
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 :return: True if it wasn't reserved before, False elsewhere.
543 """
544
545 subnet_cidr = netaddr.IPNetwork(addr, **ipnetwork_kwargs)
546 if subnet_cidr in cls.reserved_subnet_cidrs:
547 return False
548 else:
549 cls.reserved_subnet_cidrs.add(subnet_cidr)
550 return True
551
552 @classmethod
553 def get_subnet_cidrs(
554 cls, cidr=None, mask_bits=None, ip_version=None):
555 """Iterate over a sequence of unused subnet CIDR for IP version
556
557 :param cidr: CIDR of the subnet to create
558 It can be either None, a str or a netaddr.IPNetwork instance
559
560 :param mask_bits: CIDR prefix length
561 It can be either None or a numeric value.
562 If cidr parameter is given then mask_bits is used to determinate a
563 sequence of valid CIDR to use as generated.
564 Please see netaddr.IPNetwork.subnet method documentation[1]
565
566 :param ip_version: ip version of generated subnet CIDRs
567 It can be None, IP_VERSION_4 or IP_VERSION_6
568 It has to match given CIDR if given
569
570 :return: iterator over reserved CIDRs of type netaddr.IPNetwork
571
572 [1] http://netaddr.readthedocs.io/en/latest/tutorial_01.html#supernets-and-subnets # noqa
573 """
574
575 if cidr:
576 # Generate subnet CIDRs starting from given CIDR
577 # checking it is of requested IP version
578 cidr = netaddr.IPNetwork(cidr, version=ip_version)
579 else:
580 # Generate subnet CIDRs starting from configured values
581 ip_version = ip_version or cls._ip_version
582 if ip_version == const.IP_VERSION_4:
583 mask_bits = mask_bits or config.safe_get_config_value(
584 'network', 'project_network_mask_bits')
585 cidr = netaddr.IPNetwork(config.safe_get_config_value(
586 'network', 'project_network_cidr'))
587 elif ip_version == const.IP_VERSION_6:
588 mask_bits = config.safe_get_config_value(
589 'network', 'project_network_v6_mask_bits')
590 cidr = netaddr.IPNetwork(config.safe_get_config_value(
591 'network', 'project_network_v6_cidr'))
592 else:
593 raise ValueError('Invalid IP version: {!r}'.format(ip_version))
594
595 if mask_bits:
596 subnet_cidrs = cidr.subnet(mask_bits)
597 else:
598 subnet_cidrs = iter([cidr])
599
600 for subnet_cidr in subnet_cidrs:
601 if subnet_cidr not in cls.reserved_subnet_cidrs:
602 yield subnet_cidr
603
604 @classmethod
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000605 def create_port(cls, network, **kwargs):
606 """Wrapper utility that returns a test port."""
Edan Davidd75e48e2018-01-03 02:49:52 -0500607 if CONF.network.port_vnic_type and 'binding:vnic_type' not in kwargs:
608 kwargs['binding:vnic_type'] = CONF.network.port_vnic_type
Glenn Van de Water5d9b1402020-09-16 15:14:14 +0200609 if CONF.network.port_profile and 'binding:profile' not in kwargs:
610 kwargs['binding:profile'] = CONF.network.port_profile
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000611 body = cls.client.create_port(network_id=network['id'],
612 **kwargs)
613 port = body['port']
614 cls.ports.append(port)
615 return port
616
617 @classmethod
618 def update_port(cls, port, **kwargs):
619 """Wrapper utility that updates a test port."""
620 body = cls.client.update_port(port['id'],
621 **kwargs)
622 return body['port']
623
624 @classmethod
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300625 def _create_router_with_client(
626 cls, client, router_name=None, admin_state_up=False,
627 external_network_id=None, enable_snat=None, **kwargs
628 ):
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000629 ext_gw_info = {}
630 if external_network_id:
631 ext_gw_info['network_id'] = external_network_id
YAMAMOTO Takashi9bd4f972017-06-20 12:49:30 +0900632 if enable_snat is not None:
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000633 ext_gw_info['enable_snat'] = enable_snat
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300634 body = client.create_router(
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000635 router_name, external_gateway_info=ext_gw_info,
636 admin_state_up=admin_state_up, **kwargs)
637 router = body['router']
638 cls.routers.append(router)
639 return router
640
641 @classmethod
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300642 def create_router(cls, *args, **kwargs):
643 return cls._create_router_with_client(cls.client, *args, **kwargs)
644
645 @classmethod
646 def create_admin_router(cls, *args, **kwargs):
rajat294495c042017-06-28 15:37:16 +0530647 return cls._create_router_with_client(cls.os_admin.network_client,
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300648 *args, **kwargs)
649
650 @classmethod
Federico Ressia69dcd52018-07-06 09:45:34 +0200651 def create_floatingip(cls, external_network_id=None, port=None,
652 client=None, **kwargs):
653 """Creates a floating IP.
654
655 Create a floating IP and schedule it for later deletion.
656 If a client is passed, then it is used for deleting the IP too.
657
658 :param external_network_id: network ID where to create
659 By default this is 'CONF.network.public_network_id'.
660
661 :param port: port to bind floating IP to
662 This is translated to 'port_id=port['id']'
663 By default it is None.
664
665 :param client: network client to be used for creating and cleaning up
666 the floating IP.
667
668 :param **kwargs: additional creation parameters to be forwarded to
669 networking server.
670 """
671
672 client = client or cls.client
673 external_network_id = (external_network_id or
674 cls.external_network_id)
675
676 if port:
Federico Ressi47f6ae42018-09-24 16:19:14 +0200677 port_id = kwargs.setdefault('port_id', port['id'])
678 if port_id != port['id']:
679 message = "Port ID specified twice: {!s} != {!s}".format(
680 port_id, port['id'])
681 raise ValueError(message)
Federico Ressia69dcd52018-07-06 09:45:34 +0200682
683 fip = client.create_floatingip(external_network_id,
684 **kwargs)['floatingip']
685
686 # save client to be used later in cls.delete_floatingip
687 # for final cleanup
688 fip['client'] = client
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000689 cls.floating_ips.append(fip)
690 return fip
691
692 @classmethod
Federico Ressia69dcd52018-07-06 09:45:34 +0200693 def delete_floatingip(cls, floating_ip, client=None):
694 """Delete floating IP
695
696 :param client: Client to be used
697 If client is not given it will use the client used to create
698 the floating IP, or cls.client if unknown.
699 """
700
701 client = client or floating_ip.get('client') or cls.client
702 client.delete_floatingip(floating_ip['id'])
703
704 @classmethod
Slawek Kaplonski003fcae2019-05-26 22:38:35 +0200705 def create_port_forwarding(cls, fip_id, internal_port_id,
706 internal_port, external_port,
707 internal_ip_address=None, protocol="tcp",
708 client=None):
709 """Creates a port forwarding.
710
711 Create a port forwarding and schedule it for later deletion.
712 If a client is passed, then it is used for deleting the PF too.
713
714 :param fip_id: The ID of the floating IP address.
715
716 :param internal_port_id: The ID of the Neutron port associated to
717 the floating IP port forwarding.
718
719 :param internal_port: The TCP/UDP/other protocol port number of the
720 Neutron port fixed IP address associated to the floating ip
721 port forwarding.
722
723 :param external_port: The TCP/UDP/other protocol port number of
724 the port forwarding floating IP address.
725
726 :param internal_ip_address: The fixed IPv4 address of the Neutron
727 port associated to the floating IP port forwarding.
728
729 :param protocol: The IP protocol used in the floating IP port
730 forwarding.
731
732 :param client: network client to be used for creating and cleaning up
733 the floating IP port forwarding.
734 """
735
736 client = client or cls.client
737
738 pf = client.create_port_forwarding(
739 fip_id, internal_port_id, internal_port, external_port,
740 internal_ip_address, protocol)['port_forwarding']
741
742 # save ID of floating IP associated with port forwarding for final
743 # cleanup
744 pf['floatingip_id'] = fip_id
745
746 # save client to be used later in cls.delete_port_forwarding
747 # for final cleanup
748 pf['client'] = client
749 cls.port_forwardings.append(pf)
750 return pf
751
752 @classmethod
Flavio Fernandesa1952c62020-10-02 06:39:08 -0400753 def update_port_forwarding(cls, fip_id, pf_id, client=None, **kwargs):
754 """Wrapper utility for update_port_forwarding."""
755 client = client or cls.client
756 return client.update_port_forwarding(fip_id, pf_id, **kwargs)
757
758 @classmethod
Slawek Kaplonski003fcae2019-05-26 22:38:35 +0200759 def delete_port_forwarding(cls, pf, client=None):
760 """Delete port forwarding
761
762 :param client: Client to be used
763 If client is not given it will use the client used to create
764 the port forwarding, or cls.client if unknown.
765 """
766
767 client = client or pf.get('client') or cls.client
768 client.delete_port_forwarding(pf['floatingip_id'], pf['id'])
769
Nurmatov Mamatisa3f2bbb52021-10-20 14:33:54 +0300770 def create_local_ip(cls, network_id=None,
771 client=None, **kwargs):
772 """Creates a Local IP.
773
774 Create a Local IP and schedule it for later deletion.
775 If a client is passed, then it is used for deleting the IP too.
776
777 :param network_id: network ID where to create
778 By default this is 'CONF.network.public_network_id'.
779
780 :param client: network client to be used for creating and cleaning up
781 the Local IP.
782
783 :param **kwargs: additional creation parameters to be forwarded to
784 networking server.
785 """
786
787 client = client or cls.client
788 network_id = (network_id or
789 cls.external_network_id)
790
791 local_ip = client.create_local_ip(network_id,
792 **kwargs)['local_ip']
793
794 # save client to be used later in cls.delete_local_ip
795 # for final cleanup
796 local_ip['client'] = client
797 cls.local_ips.append(local_ip)
798 return local_ip
799
800 @classmethod
801 def delete_local_ip(cls, local_ip, client=None):
802 """Delete Local IP
803
804 :param client: Client to be used
805 If client is not given it will use the client used to create
806 the Local IP, or cls.client if unknown.
807 """
808
809 client = client or local_ip.get('client') or cls.client
810 client.delete_local_ip(local_ip['id'])
811
812 @classmethod
813 def create_local_ip_association(cls, local_ip_id, fixed_port_id,
814 fixed_ip_address=None, client=None):
815 """Creates a Local IP association.
816
817 Create a Local IP Association and schedule it for later deletion.
818 If a client is passed, then it is used for deleting the association
819 too.
820
821 :param local_ip_id: The ID of the Local IP.
822
823 :param fixed_port_id: The ID of the Neutron port
824 to be associated with the Local IP
825
826 :param fixed_ip_address: The fixed IPv4 address of the Neutron
827 port to be associated with the Local IP
828
829 :param client: network client to be used for creating and cleaning up
830 the Local IP Association.
831 """
832
833 client = client or cls.client
834
835 association = client.create_local_ip_association(
836 local_ip_id, fixed_port_id,
837 fixed_ip_address)['port_association']
838
839 # save ID of Local IP for final cleanup
840 association['local_ip_id'] = local_ip_id
841
842 # save client to be used later in
843 # cls.delete_local_ip_association for final cleanup
844 association['client'] = client
845 cls.local_ip_associations.append(association)
846 return association
847
848 @classmethod
849 def delete_local_ip_association(cls, association, client=None):
850
851 """Delete Local IP Association
852
853 :param client: Client to be used
854 If client is not given it will use the client used to create
855 the local IP association, or cls.client if unknown.
856 """
857
858 client = client or association.get('client') or cls.client
859 client.delete_local_ip_association(association['local_ip_id'],
860 association['fixed_port_id'])
861
Slawek Kaplonski003fcae2019-05-26 22:38:35 +0200862 @classmethod
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000863 def create_router_interface(cls, router_id, subnet_id):
864 """Wrapper utility that returns a router interface."""
865 interface = cls.client.add_router_interface_with_subnet_id(
866 router_id, subnet_id)
867 return interface
868
869 @classmethod
Bence Romsics46bd3af2019-09-13 10:52:41 +0200870 def add_extra_routes_atomic(cls, *args, **kwargs):
871 return cls.client.add_extra_routes_atomic(*args, **kwargs)
872
873 @classmethod
874 def remove_extra_routes_atomic(cls, *args, **kwargs):
875 return cls.client.remove_extra_routes_atomic(*args, **kwargs)
876
877 @classmethod
Sławek Kapłońskiff294062016-12-04 15:00:54 +0000878 def get_supported_qos_rule_types(cls):
879 body = cls.client.list_qos_rule_types()
880 return [rule_type['type'] for rule_type in body['rule_types']]
881
882 @classmethod
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200883 def create_qos_policy(cls, name, description=None, shared=False,
Rodolfo Alonso Hernandeze2d062f2020-01-14 17:11:42 +0000884 project_id=None, is_default=False):
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000885 """Wrapper utility that returns a test QoS policy."""
886 body = cls.admin_client.create_qos_policy(
Rodolfo Alonso Hernandeze2d062f2020-01-14 17:11:42 +0000887 name, description, shared, project_id, is_default)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000888 qos_policy = body['policy']
889 cls.qos_policies.append(qos_policy)
890 return qos_policy
891
892 @classmethod
elajkatdbb0b482021-05-04 17:20:07 +0200893 def create_qos_dscp_marking_rule(cls, policy_id, dscp_mark):
894 """Wrapper utility that creates and returns a QoS dscp rule."""
895 body = cls.admin_client.create_dscp_marking_rule(
896 policy_id, dscp_mark)
897 qos_rule = body['dscp_marking_rule']
898 cls.qos_rules.append(qos_rule)
899 return qos_rule
900
901 @classmethod
Jakub Libosvar83704832017-12-06 16:02:28 +0000902 def delete_router(cls, router, client=None):
903 client = client or cls.client
Aditya Vaja49819a72018-11-26 14:20:10 -0800904 if 'routes' in router:
905 client.remove_router_extra_routes(router['id'])
Jakub Libosvar83704832017-12-06 16:02:28 +0000906 body = client.list_router_interfaces(router['id'])
Chandan Kumarc125fd12017-11-15 19:41:01 +0530907 interfaces = [port for port in body['ports']
908 if port['device_owner'] in const.ROUTER_INTERFACE_OWNERS]
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000909 for i in interfaces:
910 try:
Jakub Libosvar83704832017-12-06 16:02:28 +0000911 client.remove_router_interface_with_subnet_id(
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000912 router['id'], i['fixed_ips'][0]['subnet_id'])
913 except lib_exc.NotFound:
914 pass
Jakub Libosvar83704832017-12-06 16:02:28 +0000915 client.delete_router(router['id'])
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000916
917 @classmethod
918 def create_address_scope(cls, name, is_admin=False, **kwargs):
919 if is_admin:
920 body = cls.admin_client.create_address_scope(name=name, **kwargs)
921 cls.admin_address_scopes.append(body['address_scope'])
922 else:
923 body = cls.client.create_address_scope(name=name, **kwargs)
924 cls.address_scopes.append(body['address_scope'])
925 return body['address_scope']
926
927 @classmethod
Igor Malinovskiyb80f1d02020-03-06 13:39:52 +0200928 def create_subnetpool(cls, name, is_admin=False, client=None, **kwargs):
929 if client is None:
930 client = cls.admin_client if is_admin else cls.client
931
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000932 if is_admin:
Igor Malinovskiyb80f1d02020-03-06 13:39:52 +0200933 body = client.create_subnetpool(name, **kwargs)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000934 cls.admin_subnetpools.append(body['subnetpool'])
935 else:
Igor Malinovskiyb80f1d02020-03-06 13:39:52 +0200936 body = client.create_subnetpool(name, **kwargs)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000937 cls.subnetpools.append(body['subnetpool'])
938 return body['subnetpool']
939
Chandan Kumarc125fd12017-11-15 19:41:01 +0530940 @classmethod
Miguel Lavalleb1c7a3d2021-01-31 19:05:22 -0600941 def create_address_group(cls, name, is_admin=False, **kwargs):
942 if is_admin:
943 body = cls.admin_client.create_address_group(name=name, **kwargs)
944 cls.admin_address_groups.append(body['address_group'])
945 else:
946 body = cls.client.create_address_group(name=name, **kwargs)
947 cls.address_groups.append(body['address_group'])
948 return body['address_group']
949
950 @classmethod
Chandan Kumarc125fd12017-11-15 19:41:01 +0530951 def create_project(cls, name=None, description=None):
952 test_project = name or data_utils.rand_name('test_project_')
953 test_description = description or data_utils.rand_name('desc_')
954 project = cls.identity_admin_client.create_project(
955 name=test_project,
956 description=test_description)['project']
957 cls.projects.append(project)
Dongcan Ye2de722e2018-07-04 11:01:37 +0000958 # Create a project will create a default security group.
Dongcan Ye2de722e2018-07-04 11:01:37 +0000959 sgs_list = cls.admin_client.list_security_groups(
960 tenant_id=project['id'])['security_groups']
Federico Ressi4c590d72018-10-10 14:01:08 +0200961 for security_group in sgs_list:
962 # Make sure delete_security_group method will use
963 # the admin client for this group
964 security_group['client'] = cls.admin_client
965 cls.security_groups.append(security_group)
Chandan Kumarc125fd12017-11-15 19:41:01 +0530966 return project
967
968 @classmethod
Federico Ressi4c590d72018-10-10 14:01:08 +0200969 def create_security_group(cls, name=None, project=None, client=None,
970 **kwargs):
971 if project:
972 client = client or cls.admin_client
973 project_id = kwargs.setdefault('project_id', project['id'])
974 tenant_id = kwargs.setdefault('tenant_id', project['id'])
975 if project_id != project['id'] or tenant_id != project['id']:
976 raise ValueError('Project ID specified multiple times')
977 else:
978 client = client or cls.client
979
980 name = name or data_utils.rand_name(cls.__name__)
981 security_group = client.create_security_group(name=name, **kwargs)[
982 'security_group']
983 security_group['client'] = client
984 cls.security_groups.append(security_group)
985 return security_group
986
987 @classmethod
988 def delete_security_group(cls, security_group, client=None):
989 client = client or security_group.get('client') or cls.client
990 client.delete_security_group(security_group['id'])
991
992 @classmethod
Slawek Kaplonskiaa22c9e2023-05-18 18:59:26 +0200993 def get_security_group(cls, name='default', client=None):
994 client = client or cls.client
995 security_groups = client.list_security_groups()['security_groups']
996 for security_group in security_groups:
997 if security_group['name'] == name:
998 return security_group
999 raise ValueError("No such security group named {!r}".format(name))
1000
1001 @classmethod
Federico Ressi4c590d72018-10-10 14:01:08 +02001002 def create_security_group_rule(cls, security_group=None, project=None,
1003 client=None, ip_version=None, **kwargs):
1004 if project:
1005 client = client or cls.admin_client
1006 project_id = kwargs.setdefault('project_id', project['id'])
1007 tenant_id = kwargs.setdefault('tenant_id', project['id'])
1008 if project_id != project['id'] or tenant_id != project['id']:
1009 raise ValueError('Project ID specified multiple times')
1010
1011 if 'security_group_id' not in kwargs:
1012 security_group = (security_group or
1013 cls.get_security_group(client=client))
1014
1015 if security_group:
1016 client = client or security_group.get('client')
1017 security_group_id = kwargs.setdefault('security_group_id',
1018 security_group['id'])
1019 if security_group_id != security_group['id']:
1020 raise ValueError('Security group ID specified multiple times.')
1021
1022 ip_version = ip_version or cls._ip_version
1023 default_params = (
1024 constants.DEFAULT_SECURITY_GROUP_RULE_PARAMS[ip_version])
Slawek Kaplonski83979b92022-12-15 14:15:12 +01001025 if (('remote_address_group_id' in kwargs or
1026 'remote_group_id' in kwargs) and
1027 'remote_ip_prefix' in default_params):
Miguel Lavalleb1c7a3d2021-01-31 19:05:22 -06001028 default_params.pop('remote_ip_prefix')
Federico Ressi4c590d72018-10-10 14:01:08 +02001029 for key, value in default_params.items():
1030 kwargs.setdefault(key, value)
1031
1032 client = client or cls.client
1033 return client.create_security_group_rule(**kwargs)[
1034 'security_group_rule']
1035
1036 @classmethod
Slawek Kaplonskiaa22c9e2023-05-18 18:59:26 +02001037 def create_default_security_group_rule(cls, **kwargs):
1038 body = cls.admin_client.create_default_security_group_rule(**kwargs)
1039 default_sg_rule = body['default_security_group_rule']
1040 cls.sg_rule_templates.append(default_sg_rule)
1041 return default_sg_rule
Chandan Kumarc125fd12017-11-15 19:41:01 +05301042
Federico Ressiab286e42018-06-19 09:52:10 +02001043 @classmethod
1044 def create_keypair(cls, client=None, name=None, **kwargs):
1045 client = client or cls.os_primary.keypairs_client
1046 name = name or data_utils.rand_name('keypair-test')
1047 keypair = client.create_keypair(name=name, **kwargs)['keypair']
1048
1049 # save client for later cleanup
1050 keypair['client'] = client
1051 cls.keypairs.append(keypair)
1052 return keypair
1053
1054 @classmethod
1055 def delete_keypair(cls, keypair, client=None):
1056 client = (client or keypair.get('client') or
1057 cls.os_primary.keypairs_client)
1058 client.delete_keypair(keypair_name=keypair['name'])
1059
Federico Ressi82e83e32018-07-03 14:19:55 +02001060 @classmethod
1061 def create_trunk(cls, port=None, subports=None, client=None, **kwargs):
1062 """Create network trunk
1063
1064 :param port: dictionary containing parent port ID (port['id'])
1065 :param client: client to be used for connecting to networking service
1066 :param **kwargs: extra parameters to be forwarded to network service
1067
1068 :returns: dictionary containing created trunk details
1069 """
1070 client = client or cls.client
1071
1072 if port:
1073 kwargs['port_id'] = port['id']
1074
1075 trunk = client.create_trunk(subports=subports, **kwargs)['trunk']
1076 # Save client reference for later deletion
1077 trunk['client'] = client
1078 cls.trunks.append(trunk)
1079 return trunk
1080
1081 @classmethod
Huifeng Le1c9f40b2018-11-07 01:14:21 +08001082 def delete_trunk(cls, trunk, client=None, detach_parent_port=True):
Federico Ressi82e83e32018-07-03 14:19:55 +02001083 """Delete network trunk
1084
1085 :param trunk: dictionary containing trunk ID (trunk['id'])
1086
1087 :param client: client to be used for connecting to networking service
1088 """
1089 client = client or trunk.get('client') or cls.client
1090 trunk.update(client.show_trunk(trunk['id'])['trunk'])
1091
1092 if not trunk['admin_state_up']:
1093 # Cannot touch trunk before admin_state_up is True
1094 client.update_trunk(trunk['id'], admin_state_up=True)
1095 if trunk['sub_ports']:
1096 # Removes trunk ports before deleting it
1097 cls._try_delete_resource(client.remove_subports, trunk['id'],
1098 trunk['sub_ports'])
1099
1100 # we have to detach the interface from the server before
1101 # the trunk can be deleted.
1102 parent_port = {'id': trunk['port_id']}
1103
1104 def is_parent_port_detached():
1105 parent_port.update(client.show_port(parent_port['id'])['port'])
1106 return not parent_port['device_id']
1107
Huifeng Le1c9f40b2018-11-07 01:14:21 +08001108 if detach_parent_port and not is_parent_port_detached():
Federico Ressi82e83e32018-07-03 14:19:55 +02001109 # this could probably happen when trunk is deleted and parent port
1110 # has been assigned to a VM that is still running. Here we are
1111 # assuming that device_id points to such VM.
1112 cls.os_primary.compute.InterfacesClient().delete_interface(
1113 parent_port['device_id'], parent_port['id'])
1114 utils.wait_until_true(is_parent_port_detached)
1115
1116 client.delete_trunk(trunk['id'])
1117
Harald Jensåsc9782fa2019-06-03 22:35:41 +02001118 @classmethod
1119 def create_conntrack_helper(cls, router_id, helper, protocol, port,
1120 client=None):
1121 """Create a conntrack helper
1122
1123 Create a conntrack helper and schedule it for later deletion. If a
1124 client is passed, then it is used for deleteing the CTH too.
1125
1126 :param router_id: The ID of the Neutron router associated to the
1127 conntrack helper.
1128
1129 :param helper: The conntrack helper module alias
1130
1131 :param protocol: The conntrack helper IP protocol used in the conntrack
1132 helper.
1133
1134 :param port: The conntrack helper IP protocol port number for the
1135 conntrack helper.
1136
1137 :param client: network client to be used for creating and cleaning up
1138 the conntrack helper.
1139 """
1140
1141 client = client or cls.client
1142
1143 cth = client.create_conntrack_helper(router_id, helper, protocol,
1144 port)['conntrack_helper']
1145
1146 # save ID of router associated with conntrack helper for final cleanup
1147 cth['router_id'] = router_id
1148
1149 # save client to be used later in cls.delete_conntrack_helper for final
1150 # cleanup
1151 cth['client'] = client
1152 cls.conntrack_helpers.append(cth)
1153 return cth
1154
1155 @classmethod
1156 def delete_conntrack_helper(cls, cth, client=None):
1157 """Delete conntrack helper
1158
1159 :param client: Client to be used
1160 If client is not given it will use the client used to create the
1161 conntrack helper, or cls.client if unknown.
1162 """
1163
1164 client = client or cth.get('client') or cls.client
1165 client.delete_conntrack_helper(cth['router_id'], cth['id'])
1166
yangjianfeng2936a292022-02-04 11:22:11 +08001167 @classmethod
1168 def create_ndp_proxy(cls, router_id, port_id, client=None, **kwargs):
1169 """Creates a ndp proxy.
1170
1171 Create a ndp proxy and schedule it for later deletion.
1172 If a client is passed, then it is used for deleting the NDP proxy too.
1173
1174 :param router_id: router ID where to create the ndp proxy.
1175
1176 :param port_id: port ID which the ndp proxy associate with
1177
1178 :param client: network client to be used for creating and cleaning up
1179 the ndp proxy.
1180
1181 :param **kwargs: additional creation parameters to be forwarded to
1182 networking server.
1183 """
1184 client = client or cls.client
1185
1186 data = {'router_id': router_id, 'port_id': port_id}
1187 if kwargs:
1188 data.update(kwargs)
1189 ndp_proxy = client.create_ndp_proxy(**data)['ndp_proxy']
1190
1191 # save client to be used later in cls.delete_ndp_proxy
1192 # for final cleanup
1193 ndp_proxy['client'] = client
1194 cls.ndp_proxies.append(ndp_proxy)
1195 return ndp_proxy
1196
1197 @classmethod
1198 def delete_ndp_proxy(cls, ndp_proxy, client=None):
1199 """Delete ndp proxy
1200
1201 :param client: Client to be used
1202 If client is not given it will use the client used to create
1203 the ndp proxy, or cls.client if unknown.
1204 """
1205 client = client or ndp_proxy.get('client') or cls.client
1206 client.delete_ndp_proxy(ndp_proxy['id'])
1207
Daniel Mellado3c0aeab2016-01-29 11:30:25 +00001208
1209class BaseAdminNetworkTest(BaseNetworkTest):
1210
1211 credentials = ['primary', 'admin']
1212
1213 @classmethod
1214 def setup_clients(cls):
1215 super(BaseAdminNetworkTest, cls).setup_clients()
fumihiko kakumaa216fc12017-07-14 10:43:29 +09001216 cls.admin_client = cls.os_admin.network_client
Jakub Libosvarf5758012017-08-15 13:45:30 +00001217 cls.identity_admin_client = cls.os_admin.projects_client
Daniel Mellado3c0aeab2016-01-29 11:30:25 +00001218
1219 @classmethod
1220 def create_metering_label(cls, name, description):
1221 """Wrapper utility that returns a test metering label."""
1222 body = cls.admin_client.create_metering_label(
1223 description=description,
1224 name=data_utils.rand_name("metering-label"))
1225 metering_label = body['metering_label']
1226 cls.metering_labels.append(metering_label)
1227 return metering_label
1228
1229 @classmethod
1230 def create_metering_label_rule(cls, remote_ip_prefix, direction,
1231 metering_label_id):
1232 """Wrapper utility that returns a test metering label rule."""
1233 body = cls.admin_client.create_metering_label_rule(
1234 remote_ip_prefix=remote_ip_prefix, direction=direction,
1235 metering_label_id=metering_label_id)
1236 metering_label_rule = body['metering_label_rule']
1237 cls.metering_label_rules.append(metering_label_rule)
1238 return metering_label_rule
1239
1240 @classmethod
Kailun Qineaaf9782018-12-20 04:45:01 +08001241 def create_network_segment_range(cls, name, shared,
1242 project_id, network_type,
1243 physical_network, minimum,
1244 maximum):
1245 """Wrapper utility that returns a test network segment range."""
1246 network_segment_range_args = {'name': name,
1247 'shared': shared,
1248 'project_id': project_id,
1249 'network_type': network_type,
1250 'physical_network': physical_network,
1251 'minimum': minimum,
1252 'maximum': maximum}
1253 body = cls.admin_client.create_network_segment_range(
1254 **network_segment_range_args)
1255 network_segment_range = body['network_segment_range']
1256 cls.network_segment_ranges.append(network_segment_range)
1257 return network_segment_range
1258
1259 @classmethod
Daniel Mellado3c0aeab2016-01-29 11:30:25 +00001260 def create_flavor(cls, name, description, service_type):
1261 """Wrapper utility that returns a test flavor."""
1262 body = cls.admin_client.create_flavor(
1263 description=description, service_type=service_type,
1264 name=name)
1265 flavor = body['flavor']
1266 cls.flavors.append(flavor)
1267 return flavor
1268
1269 @classmethod
1270 def create_service_profile(cls, description, metainfo, driver):
1271 """Wrapper utility that returns a test service profile."""
1272 body = cls.admin_client.create_service_profile(
1273 driver=driver, metainfo=metainfo, description=description)
1274 service_profile = body['service_profile']
1275 cls.service_profiles.append(service_profile)
1276 return service_profile
1277
1278 @classmethod
Nguyen Phuong An67993fc2017-11-24 11:30:25 +07001279 def create_log(cls, name, description=None,
1280 resource_type='security_group', resource_id=None,
1281 target_id=None, event='ALL', enabled=True):
1282 """Wrapper utility that returns a test log object."""
1283 log_args = {'name': name,
Nguyen Phuong An67993fc2017-11-24 11:30:25 +07001284 'resource_type': resource_type,
1285 'resource_id': resource_id,
1286 'target_id': target_id,
1287 'event': event,
1288 'enabled': enabled}
Slawek Kaplonskid9fe3022021-08-11 15:25:16 +02001289 if description:
1290 log_args['description'] = description
Nguyen Phuong An67993fc2017-11-24 11:30:25 +07001291 body = cls.admin_client.create_log(**log_args)
1292 log_object = body['log']
1293 cls.log_objects.append(log_object)
1294 return log_object
1295
1296 @classmethod
Daniel Mellado3c0aeab2016-01-29 11:30:25 +00001297 def get_unused_ip(cls, net_id, ip_version=None):
Gary Kotton011345f2016-06-15 08:04:31 -07001298 """Get an unused ip address in a allocation pool of net"""
Daniel Mellado3c0aeab2016-01-29 11:30:25 +00001299 body = cls.admin_client.list_ports(network_id=net_id)
1300 ports = body['ports']
1301 used_ips = []
1302 for port in ports:
1303 used_ips.extend(
1304 [fixed_ip['ip_address'] for fixed_ip in port['fixed_ips']])
1305 body = cls.admin_client.list_subnets(network_id=net_id)
1306 subnets = body['subnets']
1307
1308 for subnet in subnets:
1309 if ip_version and subnet['ip_version'] != ip_version:
1310 continue
1311 cidr = subnet['cidr']
1312 allocation_pools = subnet['allocation_pools']
1313 iterators = []
1314 if allocation_pools:
1315 for allocation_pool in allocation_pools:
1316 iterators.append(netaddr.iter_iprange(
1317 allocation_pool['start'], allocation_pool['end']))
1318 else:
1319 net = netaddr.IPNetwork(cidr)
1320
1321 def _iterip():
1322 for ip in net:
1323 if ip not in (net.network, net.broadcast):
1324 yield ip
1325 iterators.append(iter(_iterip()))
1326
1327 for iterator in iterators:
1328 for ip in iterator:
1329 if str(ip) not in used_ips:
1330 return str(ip)
1331
1332 message = (
1333 "net(%s) has no usable IP address in allocation pools" % net_id)
1334 raise exceptions.InvalidConfiguration(message)
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001335
Lajos Katona2f904652018-08-23 14:04:56 +02001336 @classmethod
1337 def create_provider_network(cls, physnet_name, start_segmentation_id,
1338 max_attempts=30):
1339 segmentation_id = start_segmentation_id
Lajos Katona7eb67252019-01-14 12:55:35 +01001340 for attempts in range(max_attempts):
Lajos Katona2f904652018-08-23 14:04:56 +02001341 try:
Lajos Katona7eb67252019-01-14 12:55:35 +01001342 return cls.create_network(
Lajos Katona2f904652018-08-23 14:04:56 +02001343 name=data_utils.rand_name('test_net'),
1344 shared=True,
1345 provider_network_type='vlan',
1346 provider_physical_network=physnet_name,
1347 provider_segmentation_id=segmentation_id)
Lajos Katona2f904652018-08-23 14:04:56 +02001348 except lib_exc.Conflict:
Lajos Katona2f904652018-08-23 14:04:56 +02001349 segmentation_id += 1
1350 if segmentation_id > 4095:
1351 raise lib_exc.TempestException(
1352 "No free segmentation id was found for provider "
1353 "network creation!")
1354 time.sleep(CONF.network.build_interval)
Lajos Katona7eb67252019-01-14 12:55:35 +01001355 LOG.exception("Failed to create provider network after "
1356 "%d attempts", max_attempts)
1357 raise lib_exc.TimeoutException
Lajos Katona2f904652018-08-23 14:04:56 +02001358
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001359
Sławek Kapłońskiff294062016-12-04 15:00:54 +00001360def require_qos_rule_type(rule_type):
1361 def decorator(f):
1362 @functools.wraps(f)
1363 def wrapper(self, *func_args, **func_kwargs):
1364 if rule_type not in self.get_supported_qos_rule_types():
1365 raise self.skipException(
1366 "%s rule type is required." % rule_type)
1367 return f(self, *func_args, **func_kwargs)
1368 return wrapper
1369 return decorator
1370
1371
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001372def _require_sorting(f):
1373 @functools.wraps(f)
1374 def inner(self, *args, **kwargs):
Chandan Kumarc125fd12017-11-15 19:41:01 +05301375 if not tutils.is_extension_enabled("sorting", "network"):
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001376 self.skipTest('Sorting feature is required')
1377 return f(self, *args, **kwargs)
1378 return inner
1379
1380
1381def _require_pagination(f):
1382 @functools.wraps(f)
1383 def inner(self, *args, **kwargs):
Chandan Kumarc125fd12017-11-15 19:41:01 +05301384 if not tutils.is_extension_enabled("pagination", "network"):
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001385 self.skipTest('Pagination feature is required')
1386 return f(self, *args, **kwargs)
1387 return inner
1388
1389
1390class BaseSearchCriteriaTest(BaseNetworkTest):
1391
1392 # This should be defined by subclasses to reflect resource name to test
1393 resource = None
1394
Armando Migliaccio57581c62016-07-01 10:13:19 -07001395 field = 'name'
1396
Ihar Hrachyshkaa8fe5a12016-05-24 14:50:58 +02001397 # NOTE(ihrachys): some names, like those starting with an underscore (_)
1398 # are sorted differently depending on whether the plugin implements native
1399 # sorting support, or not. So we avoid any such cases here, sticking to
1400 # alphanumeric. Also test a case when there are multiple resources with the
1401 # same name
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001402 resource_names = ('test1', 'abc1', 'test10', '123test') + ('test1',)
1403
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001404 force_tenant_isolation = True
1405
Ihar Hrachyshkaa8fe5a12016-05-24 14:50:58 +02001406 list_kwargs = {}
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001407
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001408 list_as_admin = False
1409
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001410 def assertSameOrder(self, original, actual):
1411 # gracefully handle iterators passed
1412 original = list(original)
1413 actual = list(actual)
1414 self.assertEqual(len(original), len(actual))
1415 for expected, res in zip(original, actual):
Armando Migliaccio57581c62016-07-01 10:13:19 -07001416 self.assertEqual(expected[self.field], res[self.field])
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001417
1418 @utils.classproperty
1419 def plural_name(self):
1420 return '%ss' % self.resource
1421
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001422 @property
1423 def list_client(self):
1424 return self.admin_client if self.list_as_admin else self.client
1425
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001426 def list_method(self, *args, **kwargs):
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001427 method = getattr(self.list_client, 'list_%s' % self.plural_name)
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001428 kwargs.update(self.list_kwargs)
1429 return method(*args, **kwargs)
1430
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001431 def get_bare_url(self, url):
1432 base_url = self.client.base_url
zheng.yong74e760a2019-05-22 14:16:14 +08001433 base_url_normalized = utils.normalize_url(base_url)
1434 url_normalized = utils.normalize_url(url)
1435 self.assertTrue(url_normalized.startswith(base_url_normalized))
1436 return url_normalized[len(base_url_normalized):]
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001437
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001438 @classmethod
1439 def _extract_resources(cls, body):
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001440 return body[cls.plural_name]
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001441
1442 def _test_list_sorts(self, direction):
1443 sort_args = {
1444 'sort_dir': direction,
Armando Migliaccio57581c62016-07-01 10:13:19 -07001445 'sort_key': self.field
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001446 }
1447 body = self.list_method(**sort_args)
1448 resources = self._extract_resources(body)
1449 self.assertNotEmpty(
1450 resources, "%s list returned is empty" % self.resource)
Armando Migliaccio57581c62016-07-01 10:13:19 -07001451 retrieved_names = [res[self.field] for res in resources]
Martin Kopec71a73242024-01-17 12:02:24 +01001452 # sort without taking into account whether the network is named with
1453 # a capital letter or not
1454 expected = sorted(retrieved_names, key=lambda v: v.upper())
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001455 if direction == constants.SORT_DIRECTION_DESC:
1456 expected = list(reversed(expected))
1457 self.assertEqual(expected, retrieved_names)
1458
1459 @_require_sorting
1460 def _test_list_sorts_asc(self):
1461 self._test_list_sorts(constants.SORT_DIRECTION_ASC)
1462
1463 @_require_sorting
1464 def _test_list_sorts_desc(self):
1465 self._test_list_sorts(constants.SORT_DIRECTION_DESC)
1466
1467 @_require_pagination
1468 def _test_list_pagination(self):
1469 for limit in range(1, len(self.resource_names) + 1):
1470 pagination_args = {
1471 'limit': limit,
1472 }
1473 body = self.list_method(**pagination_args)
1474 resources = self._extract_resources(body)
1475 self.assertEqual(limit, len(resources))
1476
1477 @_require_pagination
1478 def _test_list_no_pagination_limit_0(self):
1479 pagination_args = {
1480 'limit': 0,
1481 }
1482 body = self.list_method(**pagination_args)
1483 resources = self._extract_resources(body)
Béla Vancsicsf1806182016-08-23 07:36:18 +02001484 self.assertGreaterEqual(len(resources), len(self.resource_names))
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001485
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001486 def _test_list_pagination_iteratively(self, lister):
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001487 # first, collect all resources for later comparison
1488 sort_args = {
1489 'sort_dir': constants.SORT_DIRECTION_ASC,
Armando Migliaccio57581c62016-07-01 10:13:19 -07001490 'sort_key': self.field
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001491 }
1492 body = self.list_method(**sort_args)
1493 expected_resources = self._extract_resources(body)
1494 self.assertNotEmpty(expected_resources)
1495
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001496 resources = lister(
1497 len(expected_resources), sort_args
1498 )
1499
1500 # finally, compare that the list retrieved in one go is identical to
1501 # the one containing pagination results
1502 self.assertSameOrder(expected_resources, resources)
1503
1504 def _list_all_with_marker(self, niterations, sort_args):
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001505 # paginate resources one by one, using last fetched resource as a
1506 # marker
1507 resources = []
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001508 for i in range(niterations):
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001509 pagination_args = sort_args.copy()
1510 pagination_args['limit'] = 1
1511 if resources:
1512 pagination_args['marker'] = resources[-1]['id']
1513 body = self.list_method(**pagination_args)
1514 resources_ = self._extract_resources(body)
1515 self.assertEqual(1, len(resources_))
1516 resources.extend(resources_)
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001517 return resources
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001518
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001519 @_require_pagination
1520 @_require_sorting
1521 def _test_list_pagination_with_marker(self):
1522 self._test_list_pagination_iteratively(self._list_all_with_marker)
1523
1524 def _list_all_with_hrefs(self, niterations, sort_args):
1525 # paginate resources one by one, using next href links
1526 resources = []
1527 prev_links = {}
1528
1529 for i in range(niterations):
1530 if prev_links:
1531 uri = self.get_bare_url(prev_links['next'])
1532 else:
Ihar Hrachyshka7f79fe62016-06-07 21:23:44 +02001533 sort_args.update(self.list_kwargs)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001534 uri = self.list_client.build_uri(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001535 self.plural_name, limit=1, **sort_args)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001536 prev_links, body = self.list_client.get_uri_with_links(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001537 self.plural_name, uri
1538 )
1539 resources_ = self._extract_resources(body)
1540 self.assertEqual(1, len(resources_))
1541 resources.extend(resources_)
1542
1543 # The last element is empty and does not contain 'next' link
1544 uri = self.get_bare_url(prev_links['next'])
1545 prev_links, body = self.client.get_uri_with_links(
1546 self.plural_name, uri
1547 )
1548 self.assertNotIn('next', prev_links)
1549
1550 # Now walk backwards and compare results
1551 resources2 = []
1552 for i in range(niterations):
1553 uri = self.get_bare_url(prev_links['previous'])
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001554 prev_links, body = self.list_client.get_uri_with_links(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001555 self.plural_name, uri
1556 )
1557 resources_ = self._extract_resources(body)
1558 self.assertEqual(1, len(resources_))
1559 resources2.extend(resources_)
1560
1561 self.assertSameOrder(resources, reversed(resources2))
1562
1563 return resources
1564
1565 @_require_pagination
1566 @_require_sorting
1567 def _test_list_pagination_with_href_links(self):
1568 self._test_list_pagination_iteratively(self._list_all_with_hrefs)
1569
1570 @_require_pagination
1571 @_require_sorting
1572 def _test_list_pagination_page_reverse_with_href_links(
1573 self, direction=constants.SORT_DIRECTION_ASC):
1574 pagination_args = {
1575 'sort_dir': direction,
Armando Migliaccio57581c62016-07-01 10:13:19 -07001576 'sort_key': self.field,
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001577 }
1578 body = self.list_method(**pagination_args)
1579 expected_resources = self._extract_resources(body)
1580
1581 page_size = 2
1582 pagination_args['limit'] = page_size
1583
1584 prev_links = {}
1585 resources = []
1586 num_resources = len(expected_resources)
1587 niterations = int(math.ceil(float(num_resources) / page_size))
1588 for i in range(niterations):
1589 if prev_links:
1590 uri = self.get_bare_url(prev_links['previous'])
1591 else:
Ihar Hrachyshka7f79fe62016-06-07 21:23:44 +02001592 pagination_args.update(self.list_kwargs)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001593 uri = self.list_client.build_uri(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001594 self.plural_name, page_reverse=True, **pagination_args)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001595 prev_links, body = self.list_client.get_uri_with_links(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001596 self.plural_name, uri
1597 )
1598 resources_ = self._extract_resources(body)
Béla Vancsicsf1806182016-08-23 07:36:18 +02001599 self.assertGreaterEqual(page_size, len(resources_))
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001600 resources.extend(reversed(resources_))
1601
1602 self.assertSameOrder(expected_resources, reversed(resources))
1603
1604 @_require_pagination
1605 @_require_sorting
1606 def _test_list_pagination_page_reverse_asc(self):
1607 self._test_list_pagination_page_reverse(
1608 direction=constants.SORT_DIRECTION_ASC)
1609
1610 @_require_pagination
1611 @_require_sorting
1612 def _test_list_pagination_page_reverse_desc(self):
1613 self._test_list_pagination_page_reverse(
1614 direction=constants.SORT_DIRECTION_DESC)
1615
1616 def _test_list_pagination_page_reverse(self, direction):
1617 pagination_args = {
1618 'sort_dir': direction,
Armando Migliaccio57581c62016-07-01 10:13:19 -07001619 'sort_key': self.field,
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001620 'limit': 3,
1621 }
1622 body = self.list_method(**pagination_args)
1623 expected_resources = self._extract_resources(body)
1624
1625 pagination_args['limit'] -= 1
1626 pagination_args['marker'] = expected_resources[-1]['id']
1627 pagination_args['page_reverse'] = True
1628 body = self.list_method(**pagination_args)
1629
1630 self.assertSameOrder(
1631 # the last entry is not included in 2nd result when used as a
1632 # marker
1633 expected_resources[:-1],
1634 self._extract_resources(body))
Victor Morales1be97b42016-09-05 08:50:06 -05001635
Hongbin Lu54f55922018-07-12 19:05:39 +00001636 @tutils.requires_ext(extension="filter-validation", service="network")
1637 def _test_list_validation_filters(
1638 self, validation_args, filter_is_valid=True):
1639 if not filter_is_valid:
1640 self.assertRaises(lib_exc.BadRequest, self.list_method,
1641 **validation_args)
1642 else:
1643 body = self.list_method(**validation_args)
1644 resources = self._extract_resources(body)
1645 for resource in resources:
1646 self.assertIn(resource['name'], self.resource_names)