blob: 5a1de9c8627c020b2fcd03c613523bb1b6aab4a6 [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 Saienkoa58672f2021-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):
539 """Reserve given subnet CIDR making sure it is not used by create_subnet
540
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 Saienko7955a772023-10-25 12:22:05 +0000549 LOG.info('Subnet CIDR already reserved: {0!r}'.format(
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200550 addr))
551
552 @classmethod
553 def try_reserve_subnet_cidr(cls, addr, **ipnetwork_kwargs):
554 """Reserve given subnet CIDR if it hasn't been reserved before
555
556 :param addr: the CIDR address to be reserved
557 It can be a str or netaddr.IPNetwork instance
558
559 :param **ipnetwork_kwargs: optional netaddr.IPNetwork constructor
560 parameters
561
562 :return: True if it wasn't reserved before, False elsewhere.
563 """
564
565 subnet_cidr = netaddr.IPNetwork(addr, **ipnetwork_kwargs)
566 if subnet_cidr in cls.reserved_subnet_cidrs:
567 return False
568 else:
569 cls.reserved_subnet_cidrs.add(subnet_cidr)
570 return True
571
572 @classmethod
573 def get_subnet_cidrs(
574 cls, cidr=None, mask_bits=None, ip_version=None):
575 """Iterate over a sequence of unused subnet CIDR for IP version
576
577 :param cidr: CIDR of the subnet to create
578 It can be either None, a str or a netaddr.IPNetwork instance
579
580 :param mask_bits: CIDR prefix length
581 It can be either None or a numeric value.
582 If cidr parameter is given then mask_bits is used to determinate a
583 sequence of valid CIDR to use as generated.
584 Please see netaddr.IPNetwork.subnet method documentation[1]
585
586 :param ip_version: ip version of generated subnet CIDRs
587 It can be None, IP_VERSION_4 or IP_VERSION_6
588 It has to match given CIDR if given
589
590 :return: iterator over reserved CIDRs of type netaddr.IPNetwork
591
592 [1] http://netaddr.readthedocs.io/en/latest/tutorial_01.html#supernets-and-subnets # noqa
593 """
594
595 if cidr:
596 # Generate subnet CIDRs starting from given CIDR
597 # checking it is of requested IP version
598 cidr = netaddr.IPNetwork(cidr, version=ip_version)
599 else:
600 # Generate subnet CIDRs starting from configured values
601 ip_version = ip_version or cls._ip_version
602 if ip_version == const.IP_VERSION_4:
603 mask_bits = mask_bits or config.safe_get_config_value(
604 'network', 'project_network_mask_bits')
605 cidr = netaddr.IPNetwork(config.safe_get_config_value(
606 'network', 'project_network_cidr'))
607 elif ip_version == const.IP_VERSION_6:
608 mask_bits = config.safe_get_config_value(
609 'network', 'project_network_v6_mask_bits')
610 cidr = netaddr.IPNetwork(config.safe_get_config_value(
611 'network', 'project_network_v6_cidr'))
612 else:
613 raise ValueError('Invalid IP version: {!r}'.format(ip_version))
614
615 if mask_bits:
616 subnet_cidrs = cidr.subnet(mask_bits)
617 else:
618 subnet_cidrs = iter([cidr])
619
620 for subnet_cidr in subnet_cidrs:
621 if subnet_cidr not in cls.reserved_subnet_cidrs:
622 yield subnet_cidr
623
624 @classmethod
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000625 def create_port(cls, network, **kwargs):
626 """Wrapper utility that returns a test port."""
Edan Davidd75e48e2018-01-03 02:49:52 -0500627 if CONF.network.port_vnic_type and 'binding:vnic_type' not in kwargs:
628 kwargs['binding:vnic_type'] = CONF.network.port_vnic_type
Glenn Van de Water5d9b1402020-09-16 15:14:14 +0200629 if CONF.network.port_profile and 'binding:profile' not in kwargs:
630 kwargs['binding:profile'] = CONF.network.port_profile
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000631 body = cls.client.create_port(network_id=network['id'],
632 **kwargs)
633 port = body['port']
634 cls.ports.append(port)
635 return port
636
637 @classmethod
638 def update_port(cls, port, **kwargs):
639 """Wrapper utility that updates a test port."""
640 body = cls.client.update_port(port['id'],
641 **kwargs)
642 return body['port']
643
644 @classmethod
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300645 def _create_router_with_client(
646 cls, client, router_name=None, admin_state_up=False,
647 external_network_id=None, enable_snat=None, **kwargs
648 ):
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000649 ext_gw_info = {}
650 if external_network_id:
651 ext_gw_info['network_id'] = external_network_id
YAMAMOTO Takashi9bd4f972017-06-20 12:49:30 +0900652 if enable_snat is not None:
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000653 ext_gw_info['enable_snat'] = enable_snat
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300654 body = client.create_router(
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000655 router_name, external_gateway_info=ext_gw_info,
656 admin_state_up=admin_state_up, **kwargs)
657 router = body['router']
658 cls.routers.append(router)
659 return router
660
661 @classmethod
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300662 def create_router(cls, *args, **kwargs):
663 return cls._create_router_with_client(cls.client, *args, **kwargs)
664
665 @classmethod
666 def create_admin_router(cls, *args, **kwargs):
rajat294495c042017-06-28 15:37:16 +0530667 return cls._create_router_with_client(cls.os_admin.network_client,
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300668 *args, **kwargs)
669
670 @classmethod
Federico Ressia69dcd52018-07-06 09:45:34 +0200671 def create_floatingip(cls, external_network_id=None, port=None,
672 client=None, **kwargs):
673 """Creates a floating IP.
674
675 Create a floating IP and schedule it for later deletion.
676 If a client is passed, then it is used for deleting the IP too.
677
678 :param external_network_id: network ID where to create
679 By default this is 'CONF.network.public_network_id'.
680
681 :param port: port to bind floating IP to
682 This is translated to 'port_id=port['id']'
683 By default it is None.
684
685 :param client: network client to be used for creating and cleaning up
686 the floating IP.
687
688 :param **kwargs: additional creation parameters to be forwarded to
689 networking server.
690 """
691
692 client = client or cls.client
693 external_network_id = (external_network_id or
694 cls.external_network_id)
695
696 if port:
Federico Ressi47f6ae42018-09-24 16:19:14 +0200697 port_id = kwargs.setdefault('port_id', port['id'])
698 if port_id != port['id']:
699 message = "Port ID specified twice: {!s} != {!s}".format(
700 port_id, port['id'])
701 raise ValueError(message)
Federico Ressia69dcd52018-07-06 09:45:34 +0200702
703 fip = client.create_floatingip(external_network_id,
704 **kwargs)['floatingip']
705
706 # save client to be used later in cls.delete_floatingip
707 # for final cleanup
708 fip['client'] = client
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000709 cls.floating_ips.append(fip)
710 return fip
711
712 @classmethod
Federico Ressia69dcd52018-07-06 09:45:34 +0200713 def delete_floatingip(cls, floating_ip, client=None):
714 """Delete floating IP
715
716 :param client: Client to be used
717 If client is not given it will use the client used to create
718 the floating IP, or cls.client if unknown.
719 """
720
721 client = client or floating_ip.get('client') or cls.client
722 client.delete_floatingip(floating_ip['id'])
723
724 @classmethod
Slawek Kaplonski003fcae2019-05-26 22:38:35 +0200725 def create_port_forwarding(cls, fip_id, internal_port_id,
726 internal_port, external_port,
727 internal_ip_address=None, protocol="tcp",
728 client=None):
729 """Creates a port forwarding.
730
731 Create a port forwarding and schedule it for later deletion.
732 If a client is passed, then it is used for deleting the PF too.
733
734 :param fip_id: The ID of the floating IP address.
735
736 :param internal_port_id: The ID of the Neutron port associated to
737 the floating IP port forwarding.
738
739 :param internal_port: The TCP/UDP/other protocol port number of the
740 Neutron port fixed IP address associated to the floating ip
741 port forwarding.
742
743 :param external_port: The TCP/UDP/other protocol port number of
744 the port forwarding floating IP address.
745
746 :param internal_ip_address: The fixed IPv4 address of the Neutron
747 port associated to the floating IP port forwarding.
748
749 :param protocol: The IP protocol used in the floating IP port
750 forwarding.
751
752 :param client: network client to be used for creating and cleaning up
753 the floating IP port forwarding.
754 """
755
756 client = client or cls.client
757
758 pf = client.create_port_forwarding(
759 fip_id, internal_port_id, internal_port, external_port,
760 internal_ip_address, protocol)['port_forwarding']
761
762 # save ID of floating IP associated with port forwarding for final
763 # cleanup
764 pf['floatingip_id'] = fip_id
765
766 # save client to be used later in cls.delete_port_forwarding
767 # for final cleanup
768 pf['client'] = client
769 cls.port_forwardings.append(pf)
770 return pf
771
772 @classmethod
Flavio Fernandesa1952c62020-10-02 06:39:08 -0400773 def update_port_forwarding(cls, fip_id, pf_id, client=None, **kwargs):
774 """Wrapper utility for update_port_forwarding."""
775 client = client or cls.client
776 return client.update_port_forwarding(fip_id, pf_id, **kwargs)
777
778 @classmethod
Slawek Kaplonski003fcae2019-05-26 22:38:35 +0200779 def delete_port_forwarding(cls, pf, client=None):
780 """Delete port forwarding
781
782 :param client: Client to be used
783 If client is not given it will use the client used to create
784 the port forwarding, or cls.client if unknown.
785 """
786
787 client = client or pf.get('client') or cls.client
788 client.delete_port_forwarding(pf['floatingip_id'], pf['id'])
789
Nurmatov Mamatisa3f2bbb52021-10-20 14:33:54 +0300790 def create_local_ip(cls, network_id=None,
791 client=None, **kwargs):
792 """Creates a Local IP.
793
794 Create a Local IP and schedule it for later deletion.
795 If a client is passed, then it is used for deleting the IP too.
796
797 :param network_id: network ID where to create
798 By default this is 'CONF.network.public_network_id'.
799
800 :param client: network client to be used for creating and cleaning up
801 the Local IP.
802
803 :param **kwargs: additional creation parameters to be forwarded to
804 networking server.
805 """
806
807 client = client or cls.client
808 network_id = (network_id or
809 cls.external_network_id)
810
811 local_ip = client.create_local_ip(network_id,
812 **kwargs)['local_ip']
813
814 # save client to be used later in cls.delete_local_ip
815 # for final cleanup
816 local_ip['client'] = client
817 cls.local_ips.append(local_ip)
818 return local_ip
819
820 @classmethod
821 def delete_local_ip(cls, local_ip, client=None):
822 """Delete Local IP
823
824 :param client: Client to be used
825 If client is not given it will use the client used to create
826 the Local IP, or cls.client if unknown.
827 """
828
829 client = client or local_ip.get('client') or cls.client
830 client.delete_local_ip(local_ip['id'])
831
832 @classmethod
833 def create_local_ip_association(cls, local_ip_id, fixed_port_id,
834 fixed_ip_address=None, client=None):
835 """Creates a Local IP association.
836
837 Create a Local IP Association and schedule it for later deletion.
838 If a client is passed, then it is used for deleting the association
839 too.
840
841 :param local_ip_id: The ID of the Local IP.
842
843 :param fixed_port_id: The ID of the Neutron port
844 to be associated with the Local IP
845
846 :param fixed_ip_address: The fixed IPv4 address of the Neutron
847 port to be associated with the Local IP
848
849 :param client: network client to be used for creating and cleaning up
850 the Local IP Association.
851 """
852
853 client = client or cls.client
854
855 association = client.create_local_ip_association(
856 local_ip_id, fixed_port_id,
857 fixed_ip_address)['port_association']
858
859 # save ID of Local IP for final cleanup
860 association['local_ip_id'] = local_ip_id
861
862 # save client to be used later in
863 # cls.delete_local_ip_association for final cleanup
864 association['client'] = client
865 cls.local_ip_associations.append(association)
866 return association
867
868 @classmethod
869 def delete_local_ip_association(cls, association, client=None):
870
871 """Delete Local IP Association
872
873 :param client: Client to be used
874 If client is not given it will use the client used to create
875 the local IP association, or cls.client if unknown.
876 """
877
878 client = client or association.get('client') or cls.client
879 client.delete_local_ip_association(association['local_ip_id'],
880 association['fixed_port_id'])
881
Slawek Kaplonski003fcae2019-05-26 22:38:35 +0200882 @classmethod
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000883 def create_router_interface(cls, router_id, subnet_id):
884 """Wrapper utility that returns a router interface."""
885 interface = cls.client.add_router_interface_with_subnet_id(
886 router_id, subnet_id)
887 return interface
888
889 @classmethod
Bence Romsics46bd3af2019-09-13 10:52:41 +0200890 def add_extra_routes_atomic(cls, *args, **kwargs):
891 return cls.client.add_extra_routes_atomic(*args, **kwargs)
892
893 @classmethod
894 def remove_extra_routes_atomic(cls, *args, **kwargs):
895 return cls.client.remove_extra_routes_atomic(*args, **kwargs)
896
897 @classmethod
Sławek Kapłońskiff294062016-12-04 15:00:54 +0000898 def get_supported_qos_rule_types(cls):
899 body = cls.client.list_qos_rule_types()
900 return [rule_type['type'] for rule_type in body['rule_types']]
901
902 @classmethod
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200903 def create_qos_policy(cls, name, description=None, shared=False,
Rodolfo Alonso Hernandeze2d062f2020-01-14 17:11:42 +0000904 project_id=None, is_default=False):
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000905 """Wrapper utility that returns a test QoS policy."""
906 body = cls.admin_client.create_qos_policy(
Rodolfo Alonso Hernandeze2d062f2020-01-14 17:11:42 +0000907 name, description, shared, project_id, is_default)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000908 qos_policy = body['policy']
909 cls.qos_policies.append(qos_policy)
910 return qos_policy
911
912 @classmethod
elajkatdbb0b482021-05-04 17:20:07 +0200913 def create_qos_dscp_marking_rule(cls, policy_id, dscp_mark):
914 """Wrapper utility that creates and returns a QoS dscp rule."""
915 body = cls.admin_client.create_dscp_marking_rule(
916 policy_id, dscp_mark)
917 qos_rule = body['dscp_marking_rule']
918 cls.qos_rules.append(qos_rule)
919 return qos_rule
920
921 @classmethod
Jakub Libosvar83704832017-12-06 16:02:28 +0000922 def delete_router(cls, router, client=None):
923 client = client or cls.client
Aditya Vaja49819a72018-11-26 14:20:10 -0800924 if 'routes' in router:
925 client.remove_router_extra_routes(router['id'])
Jakub Libosvar83704832017-12-06 16:02:28 +0000926 body = client.list_router_interfaces(router['id'])
Chandan Kumarc125fd12017-11-15 19:41:01 +0530927 interfaces = [port for port in body['ports']
928 if port['device_owner'] in const.ROUTER_INTERFACE_OWNERS]
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000929 for i in interfaces:
930 try:
Jakub Libosvar83704832017-12-06 16:02:28 +0000931 client.remove_router_interface_with_subnet_id(
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000932 router['id'], i['fixed_ips'][0]['subnet_id'])
933 except lib_exc.NotFound:
934 pass
Jakub Libosvar83704832017-12-06 16:02:28 +0000935 client.delete_router(router['id'])
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000936
937 @classmethod
938 def create_address_scope(cls, name, is_admin=False, **kwargs):
939 if is_admin:
940 body = cls.admin_client.create_address_scope(name=name, **kwargs)
941 cls.admin_address_scopes.append(body['address_scope'])
942 else:
943 body = cls.client.create_address_scope(name=name, **kwargs)
944 cls.address_scopes.append(body['address_scope'])
945 return body['address_scope']
946
947 @classmethod
Igor Malinovskiyb80f1d02020-03-06 13:39:52 +0200948 def create_subnetpool(cls, name, is_admin=False, client=None, **kwargs):
949 if client is None:
950 client = cls.admin_client if is_admin else cls.client
951
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000952 if is_admin:
Igor Malinovskiyb80f1d02020-03-06 13:39:52 +0200953 body = client.create_subnetpool(name, **kwargs)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000954 cls.admin_subnetpools.append(body['subnetpool'])
955 else:
Igor Malinovskiyb80f1d02020-03-06 13:39:52 +0200956 body = client.create_subnetpool(name, **kwargs)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000957 cls.subnetpools.append(body['subnetpool'])
958 return body['subnetpool']
959
Chandan Kumarc125fd12017-11-15 19:41:01 +0530960 @classmethod
Miguel Lavalleb1c7a3d2021-01-31 19:05:22 -0600961 def create_address_group(cls, name, is_admin=False, **kwargs):
962 if is_admin:
963 body = cls.admin_client.create_address_group(name=name, **kwargs)
964 cls.admin_address_groups.append(body['address_group'])
965 else:
966 body = cls.client.create_address_group(name=name, **kwargs)
967 cls.address_groups.append(body['address_group'])
968 return body['address_group']
969
970 @classmethod
Chandan Kumarc125fd12017-11-15 19:41:01 +0530971 def create_project(cls, name=None, description=None):
972 test_project = name or data_utils.rand_name('test_project_')
973 test_description = description or data_utils.rand_name('desc_')
974 project = cls.identity_admin_client.create_project(
975 name=test_project,
976 description=test_description)['project']
977 cls.projects.append(project)
Dongcan Ye2de722e2018-07-04 11:01:37 +0000978 # Create a project will create a default security group.
Dongcan Ye2de722e2018-07-04 11:01:37 +0000979 sgs_list = cls.admin_client.list_security_groups(
980 tenant_id=project['id'])['security_groups']
Federico Ressi4c590d72018-10-10 14:01:08 +0200981 for security_group in sgs_list:
982 # Make sure delete_security_group method will use
983 # the admin client for this group
984 security_group['client'] = cls.admin_client
985 cls.security_groups.append(security_group)
Chandan Kumarc125fd12017-11-15 19:41:01 +0530986 return project
987
988 @classmethod
Federico Ressi4c590d72018-10-10 14:01:08 +0200989 def create_security_group(cls, name=None, project=None, client=None,
990 **kwargs):
991 if project:
992 client = client or cls.admin_client
993 project_id = kwargs.setdefault('project_id', project['id'])
994 tenant_id = kwargs.setdefault('tenant_id', project['id'])
995 if project_id != project['id'] or tenant_id != project['id']:
996 raise ValueError('Project ID specified multiple times')
997 else:
998 client = client or cls.client
999
1000 name = name or data_utils.rand_name(cls.__name__)
1001 security_group = client.create_security_group(name=name, **kwargs)[
1002 'security_group']
1003 security_group['client'] = client
1004 cls.security_groups.append(security_group)
1005 return security_group
1006
1007 @classmethod
1008 def delete_security_group(cls, security_group, client=None):
1009 client = client or security_group.get('client') or cls.client
1010 client.delete_security_group(security_group['id'])
1011
1012 @classmethod
Slawek Kaplonskiaa22c9e2023-05-18 18:59:26 +02001013 def get_security_group(cls, name='default', client=None):
1014 client = client or cls.client
1015 security_groups = client.list_security_groups()['security_groups']
1016 for security_group in security_groups:
1017 if security_group['name'] == name:
1018 return security_group
1019 raise ValueError("No such security group named {!r}".format(name))
1020
1021 @classmethod
Federico Ressi4c590d72018-10-10 14:01:08 +02001022 def create_security_group_rule(cls, security_group=None, project=None,
1023 client=None, ip_version=None, **kwargs):
1024 if project:
1025 client = client or cls.admin_client
1026 project_id = kwargs.setdefault('project_id', project['id'])
1027 tenant_id = kwargs.setdefault('tenant_id', project['id'])
1028 if project_id != project['id'] or tenant_id != project['id']:
1029 raise ValueError('Project ID specified multiple times')
1030
1031 if 'security_group_id' not in kwargs:
1032 security_group = (security_group or
1033 cls.get_security_group(client=client))
1034
1035 if security_group:
1036 client = client or security_group.get('client')
1037 security_group_id = kwargs.setdefault('security_group_id',
1038 security_group['id'])
1039 if security_group_id != security_group['id']:
1040 raise ValueError('Security group ID specified multiple times.')
1041
1042 ip_version = ip_version or cls._ip_version
1043 default_params = (
1044 constants.DEFAULT_SECURITY_GROUP_RULE_PARAMS[ip_version])
Slawek Kaplonski83979b92022-12-15 14:15:12 +01001045 if (('remote_address_group_id' in kwargs or
1046 'remote_group_id' in kwargs) and
1047 'remote_ip_prefix' in default_params):
Miguel Lavalleb1c7a3d2021-01-31 19:05:22 -06001048 default_params.pop('remote_ip_prefix')
Federico Ressi4c590d72018-10-10 14:01:08 +02001049 for key, value in default_params.items():
1050 kwargs.setdefault(key, value)
1051
1052 client = client or cls.client
1053 return client.create_security_group_rule(**kwargs)[
1054 'security_group_rule']
1055
1056 @classmethod
Slawek Kaplonskiaa22c9e2023-05-18 18:59:26 +02001057 def create_default_security_group_rule(cls, **kwargs):
1058 body = cls.admin_client.create_default_security_group_rule(**kwargs)
1059 default_sg_rule = body['default_security_group_rule']
1060 cls.sg_rule_templates.append(default_sg_rule)
1061 return default_sg_rule
Chandan Kumarc125fd12017-11-15 19:41:01 +05301062
Federico Ressiab286e42018-06-19 09:52:10 +02001063 @classmethod
1064 def create_keypair(cls, client=None, name=None, **kwargs):
1065 client = client or cls.os_primary.keypairs_client
1066 name = name or data_utils.rand_name('keypair-test')
1067 keypair = client.create_keypair(name=name, **kwargs)['keypair']
1068
1069 # save client for later cleanup
1070 keypair['client'] = client
1071 cls.keypairs.append(keypair)
1072 return keypair
1073
1074 @classmethod
1075 def delete_keypair(cls, keypair, client=None):
1076 client = (client or keypair.get('client') or
1077 cls.os_primary.keypairs_client)
1078 client.delete_keypair(keypair_name=keypair['name'])
1079
Federico Ressi82e83e32018-07-03 14:19:55 +02001080 @classmethod
1081 def create_trunk(cls, port=None, subports=None, client=None, **kwargs):
1082 """Create network trunk
1083
1084 :param port: dictionary containing parent port ID (port['id'])
1085 :param client: client to be used for connecting to networking service
1086 :param **kwargs: extra parameters to be forwarded to network service
1087
1088 :returns: dictionary containing created trunk details
1089 """
1090 client = client or cls.client
1091
1092 if port:
1093 kwargs['port_id'] = port['id']
1094
1095 trunk = client.create_trunk(subports=subports, **kwargs)['trunk']
1096 # Save client reference for later deletion
1097 trunk['client'] = client
1098 cls.trunks.append(trunk)
1099 return trunk
1100
1101 @classmethod
Huifeng Le1c9f40b2018-11-07 01:14:21 +08001102 def delete_trunk(cls, trunk, client=None, detach_parent_port=True):
Federico Ressi82e83e32018-07-03 14:19:55 +02001103 """Delete network trunk
1104
1105 :param trunk: dictionary containing trunk ID (trunk['id'])
1106
1107 :param client: client to be used for connecting to networking service
1108 """
1109 client = client or trunk.get('client') or cls.client
1110 trunk.update(client.show_trunk(trunk['id'])['trunk'])
1111
1112 if not trunk['admin_state_up']:
1113 # Cannot touch trunk before admin_state_up is True
1114 client.update_trunk(trunk['id'], admin_state_up=True)
1115 if trunk['sub_ports']:
1116 # Removes trunk ports before deleting it
1117 cls._try_delete_resource(client.remove_subports, trunk['id'],
1118 trunk['sub_ports'])
1119
1120 # we have to detach the interface from the server before
1121 # the trunk can be deleted.
1122 parent_port = {'id': trunk['port_id']}
1123
1124 def is_parent_port_detached():
1125 parent_port.update(client.show_port(parent_port['id'])['port'])
1126 return not parent_port['device_id']
1127
Huifeng Le1c9f40b2018-11-07 01:14:21 +08001128 if detach_parent_port and not is_parent_port_detached():
Federico Ressi82e83e32018-07-03 14:19:55 +02001129 # this could probably happen when trunk is deleted and parent port
1130 # has been assigned to a VM that is still running. Here we are
1131 # assuming that device_id points to such VM.
1132 cls.os_primary.compute.InterfacesClient().delete_interface(
1133 parent_port['device_id'], parent_port['id'])
1134 utils.wait_until_true(is_parent_port_detached)
1135
1136 client.delete_trunk(trunk['id'])
1137
Harald Jensåsc9782fa2019-06-03 22:35:41 +02001138 @classmethod
1139 def create_conntrack_helper(cls, router_id, helper, protocol, port,
1140 client=None):
1141 """Create a conntrack helper
1142
1143 Create a conntrack helper and schedule it for later deletion. If a
1144 client is passed, then it is used for deleteing the CTH too.
1145
1146 :param router_id: The ID of the Neutron router associated to the
1147 conntrack helper.
1148
1149 :param helper: The conntrack helper module alias
1150
1151 :param protocol: The conntrack helper IP protocol used in the conntrack
1152 helper.
1153
1154 :param port: The conntrack helper IP protocol port number for the
1155 conntrack helper.
1156
1157 :param client: network client to be used for creating and cleaning up
1158 the conntrack helper.
1159 """
1160
1161 client = client or cls.client
1162
1163 cth = client.create_conntrack_helper(router_id, helper, protocol,
1164 port)['conntrack_helper']
1165
1166 # save ID of router associated with conntrack helper for final cleanup
1167 cth['router_id'] = router_id
1168
1169 # save client to be used later in cls.delete_conntrack_helper for final
1170 # cleanup
1171 cth['client'] = client
1172 cls.conntrack_helpers.append(cth)
1173 return cth
1174
1175 @classmethod
1176 def delete_conntrack_helper(cls, cth, client=None):
1177 """Delete conntrack helper
1178
1179 :param client: Client to be used
1180 If client is not given it will use the client used to create the
1181 conntrack helper, or cls.client if unknown.
1182 """
1183
1184 client = client or cth.get('client') or cls.client
1185 client.delete_conntrack_helper(cth['router_id'], cth['id'])
1186
yangjianfeng2936a292022-02-04 11:22:11 +08001187 @classmethod
1188 def create_ndp_proxy(cls, router_id, port_id, client=None, **kwargs):
1189 """Creates a ndp proxy.
1190
1191 Create a ndp proxy and schedule it for later deletion.
1192 If a client is passed, then it is used for deleting the NDP proxy too.
1193
1194 :param router_id: router ID where to create the ndp proxy.
1195
1196 :param port_id: port ID which the ndp proxy associate with
1197
1198 :param client: network client to be used for creating and cleaning up
1199 the ndp proxy.
1200
1201 :param **kwargs: additional creation parameters to be forwarded to
1202 networking server.
1203 """
1204 client = client or cls.client
1205
1206 data = {'router_id': router_id, 'port_id': port_id}
1207 if kwargs:
1208 data.update(kwargs)
1209 ndp_proxy = client.create_ndp_proxy(**data)['ndp_proxy']
1210
1211 # save client to be used later in cls.delete_ndp_proxy
1212 # for final cleanup
1213 ndp_proxy['client'] = client
1214 cls.ndp_proxies.append(ndp_proxy)
1215 return ndp_proxy
1216
1217 @classmethod
1218 def delete_ndp_proxy(cls, ndp_proxy, client=None):
1219 """Delete ndp proxy
1220
1221 :param client: Client to be used
1222 If client is not given it will use the client used to create
1223 the ndp proxy, or cls.client if unknown.
1224 """
1225 client = client or ndp_proxy.get('client') or cls.client
1226 client.delete_ndp_proxy(ndp_proxy['id'])
1227
Daniel Mellado3c0aeab2016-01-29 11:30:25 +00001228
1229class BaseAdminNetworkTest(BaseNetworkTest):
1230
1231 credentials = ['primary', 'admin']
1232
1233 @classmethod
1234 def setup_clients(cls):
1235 super(BaseAdminNetworkTest, cls).setup_clients()
fumihiko kakumaa216fc12017-07-14 10:43:29 +09001236 cls.admin_client = cls.os_admin.network_client
Michael Polenchuk895403f2022-09-05 13:29:57 +04001237 cls.admin_client.auth_provider.get_token()
Jakub Libosvarf5758012017-08-15 13:45:30 +00001238 cls.identity_admin_client = cls.os_admin.projects_client
Daniel Mellado3c0aeab2016-01-29 11:30:25 +00001239
1240 @classmethod
1241 def create_metering_label(cls, name, description):
1242 """Wrapper utility that returns a test metering label."""
1243 body = cls.admin_client.create_metering_label(
1244 description=description,
1245 name=data_utils.rand_name("metering-label"))
1246 metering_label = body['metering_label']
1247 cls.metering_labels.append(metering_label)
1248 return metering_label
1249
1250 @classmethod
1251 def create_metering_label_rule(cls, remote_ip_prefix, direction,
1252 metering_label_id):
1253 """Wrapper utility that returns a test metering label rule."""
1254 body = cls.admin_client.create_metering_label_rule(
1255 remote_ip_prefix=remote_ip_prefix, direction=direction,
1256 metering_label_id=metering_label_id)
1257 metering_label_rule = body['metering_label_rule']
1258 cls.metering_label_rules.append(metering_label_rule)
1259 return metering_label_rule
1260
1261 @classmethod
Kailun Qineaaf9782018-12-20 04:45:01 +08001262 def create_network_segment_range(cls, name, shared,
1263 project_id, network_type,
1264 physical_network, minimum,
1265 maximum):
1266 """Wrapper utility that returns a test network segment range."""
1267 network_segment_range_args = {'name': name,
1268 'shared': shared,
1269 'project_id': project_id,
1270 'network_type': network_type,
1271 'physical_network': physical_network,
1272 'minimum': minimum,
1273 'maximum': maximum}
1274 body = cls.admin_client.create_network_segment_range(
1275 **network_segment_range_args)
1276 network_segment_range = body['network_segment_range']
1277 cls.network_segment_ranges.append(network_segment_range)
1278 return network_segment_range
1279
1280 @classmethod
Daniel Mellado3c0aeab2016-01-29 11:30:25 +00001281 def create_flavor(cls, name, description, service_type):
1282 """Wrapper utility that returns a test flavor."""
1283 body = cls.admin_client.create_flavor(
1284 description=description, service_type=service_type,
1285 name=name)
1286 flavor = body['flavor']
1287 cls.flavors.append(flavor)
1288 return flavor
1289
1290 @classmethod
1291 def create_service_profile(cls, description, metainfo, driver):
1292 """Wrapper utility that returns a test service profile."""
1293 body = cls.admin_client.create_service_profile(
1294 driver=driver, metainfo=metainfo, description=description)
1295 service_profile = body['service_profile']
1296 cls.service_profiles.append(service_profile)
1297 return service_profile
1298
1299 @classmethod
Nguyen Phuong An67993fc2017-11-24 11:30:25 +07001300 def create_log(cls, name, description=None,
1301 resource_type='security_group', resource_id=None,
1302 target_id=None, event='ALL', enabled=True):
1303 """Wrapper utility that returns a test log object."""
1304 log_args = {'name': name,
Nguyen Phuong An67993fc2017-11-24 11:30:25 +07001305 'resource_type': resource_type,
1306 'resource_id': resource_id,
1307 'target_id': target_id,
1308 'event': event,
1309 'enabled': enabled}
Slawek Kaplonskid9fe3022021-08-11 15:25:16 +02001310 if description:
1311 log_args['description'] = description
Nguyen Phuong An67993fc2017-11-24 11:30:25 +07001312 body = cls.admin_client.create_log(**log_args)
1313 log_object = body['log']
1314 cls.log_objects.append(log_object)
1315 return log_object
1316
1317 @classmethod
Daniel Mellado3c0aeab2016-01-29 11:30:25 +00001318 def get_unused_ip(cls, net_id, ip_version=None):
Gary Kotton011345f2016-06-15 08:04:31 -07001319 """Get an unused ip address in a allocation pool of net"""
Daniel Mellado3c0aeab2016-01-29 11:30:25 +00001320 body = cls.admin_client.list_ports(network_id=net_id)
1321 ports = body['ports']
1322 used_ips = []
1323 for port in ports:
1324 used_ips.extend(
1325 [fixed_ip['ip_address'] for fixed_ip in port['fixed_ips']])
1326 body = cls.admin_client.list_subnets(network_id=net_id)
1327 subnets = body['subnets']
1328
1329 for subnet in subnets:
1330 if ip_version and subnet['ip_version'] != ip_version:
1331 continue
1332 cidr = subnet['cidr']
1333 allocation_pools = subnet['allocation_pools']
1334 iterators = []
1335 if allocation_pools:
1336 for allocation_pool in allocation_pools:
1337 iterators.append(netaddr.iter_iprange(
1338 allocation_pool['start'], allocation_pool['end']))
1339 else:
1340 net = netaddr.IPNetwork(cidr)
1341
1342 def _iterip():
1343 for ip in net:
1344 if ip not in (net.network, net.broadcast):
1345 yield ip
1346 iterators.append(iter(_iterip()))
1347
1348 for iterator in iterators:
1349 for ip in iterator:
1350 if str(ip) not in used_ips:
1351 return str(ip)
1352
1353 message = (
1354 "net(%s) has no usable IP address in allocation pools" % net_id)
1355 raise exceptions.InvalidConfiguration(message)
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001356
Lajos Katona2f904652018-08-23 14:04:56 +02001357 @classmethod
1358 def create_provider_network(cls, physnet_name, start_segmentation_id,
1359 max_attempts=30):
1360 segmentation_id = start_segmentation_id
Lajos Katona7eb67252019-01-14 12:55:35 +01001361 for attempts in range(max_attempts):
Lajos Katona2f904652018-08-23 14:04:56 +02001362 try:
Lajos Katona7eb67252019-01-14 12:55:35 +01001363 return cls.create_network(
Lajos Katona2f904652018-08-23 14:04:56 +02001364 name=data_utils.rand_name('test_net'),
1365 shared=True,
1366 provider_network_type='vlan',
1367 provider_physical_network=physnet_name,
1368 provider_segmentation_id=segmentation_id)
Lajos Katona2f904652018-08-23 14:04:56 +02001369 except lib_exc.Conflict:
Lajos Katona2f904652018-08-23 14:04:56 +02001370 segmentation_id += 1
1371 if segmentation_id > 4095:
1372 raise lib_exc.TempestException(
1373 "No free segmentation id was found for provider "
1374 "network creation!")
1375 time.sleep(CONF.network.build_interval)
Lajos Katona7eb67252019-01-14 12:55:35 +01001376 LOG.exception("Failed to create provider network after "
1377 "%d attempts", max_attempts)
1378 raise lib_exc.TimeoutException
Lajos Katona2f904652018-08-23 14:04:56 +02001379
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001380
Sławek Kapłońskiff294062016-12-04 15:00:54 +00001381def require_qos_rule_type(rule_type):
1382 def decorator(f):
1383 @functools.wraps(f)
1384 def wrapper(self, *func_args, **func_kwargs):
1385 if rule_type not in self.get_supported_qos_rule_types():
1386 raise self.skipException(
1387 "%s rule type is required." % rule_type)
1388 return f(self, *func_args, **func_kwargs)
1389 return wrapper
1390 return decorator
1391
1392
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001393def _require_sorting(f):
1394 @functools.wraps(f)
1395 def inner(self, *args, **kwargs):
Chandan Kumarc125fd12017-11-15 19:41:01 +05301396 if not tutils.is_extension_enabled("sorting", "network"):
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001397 self.skipTest('Sorting feature is required')
1398 return f(self, *args, **kwargs)
1399 return inner
1400
1401
1402def _require_pagination(f):
1403 @functools.wraps(f)
1404 def inner(self, *args, **kwargs):
Chandan Kumarc125fd12017-11-15 19:41:01 +05301405 if not tutils.is_extension_enabled("pagination", "network"):
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001406 self.skipTest('Pagination feature is required')
1407 return f(self, *args, **kwargs)
1408 return inner
1409
1410
1411class BaseSearchCriteriaTest(BaseNetworkTest):
1412
1413 # This should be defined by subclasses to reflect resource name to test
1414 resource = None
1415
Armando Migliaccio57581c62016-07-01 10:13:19 -07001416 field = 'name'
1417
Ihar Hrachyshkaa8fe5a12016-05-24 14:50:58 +02001418 # NOTE(ihrachys): some names, like those starting with an underscore (_)
1419 # are sorted differently depending on whether the plugin implements native
1420 # sorting support, or not. So we avoid any such cases here, sticking to
1421 # alphanumeric. Also test a case when there are multiple resources with the
1422 # same name
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001423 resource_names = ('test1', 'abc1', 'test10', '123test') + ('test1',)
1424
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001425 force_tenant_isolation = True
1426
Ihar Hrachyshkaa8fe5a12016-05-24 14:50:58 +02001427 list_kwargs = {}
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001428
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001429 list_as_admin = False
1430
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001431 def assertSameOrder(self, original, actual):
1432 # gracefully handle iterators passed
1433 original = list(original)
1434 actual = list(actual)
1435 self.assertEqual(len(original), len(actual))
1436 for expected, res in zip(original, actual):
Armando Migliaccio57581c62016-07-01 10:13:19 -07001437 self.assertEqual(expected[self.field], res[self.field])
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001438
1439 @utils.classproperty
1440 def plural_name(self):
1441 return '%ss' % self.resource
1442
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001443 @property
1444 def list_client(self):
1445 return self.admin_client if self.list_as_admin else self.client
1446
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001447 def list_method(self, *args, **kwargs):
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001448 method = getattr(self.list_client, 'list_%s' % self.plural_name)
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001449 kwargs.update(self.list_kwargs)
1450 return method(*args, **kwargs)
1451
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001452 def get_bare_url(self, url):
1453 base_url = self.client.base_url
zheng.yong74e760a2019-05-22 14:16:14 +08001454 base_url_normalized = utils.normalize_url(base_url)
1455 url_normalized = utils.normalize_url(url)
1456 self.assertTrue(url_normalized.startswith(base_url_normalized))
1457 return url_normalized[len(base_url_normalized):]
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001458
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001459 @classmethod
1460 def _extract_resources(cls, body):
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001461 return body[cls.plural_name]
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001462
1463 def _test_list_sorts(self, direction):
1464 sort_args = {
1465 'sort_dir': direction,
Armando Migliaccio57581c62016-07-01 10:13:19 -07001466 'sort_key': self.field
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001467 }
1468 body = self.list_method(**sort_args)
1469 resources = self._extract_resources(body)
1470 self.assertNotEmpty(
1471 resources, "%s list returned is empty" % self.resource)
Armando Migliaccio57581c62016-07-01 10:13:19 -07001472 retrieved_names = [res[self.field] for res in resources]
Martin Kopec71a73242024-01-17 12:02:24 +01001473 # sort without taking into account whether the network is named with
1474 # a capital letter or not
1475 expected = sorted(retrieved_names, key=lambda v: v.upper())
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001476 if direction == constants.SORT_DIRECTION_DESC:
1477 expected = list(reversed(expected))
1478 self.assertEqual(expected, retrieved_names)
1479
1480 @_require_sorting
1481 def _test_list_sorts_asc(self):
1482 self._test_list_sorts(constants.SORT_DIRECTION_ASC)
1483
1484 @_require_sorting
1485 def _test_list_sorts_desc(self):
1486 self._test_list_sorts(constants.SORT_DIRECTION_DESC)
1487
1488 @_require_pagination
1489 def _test_list_pagination(self):
1490 for limit in range(1, len(self.resource_names) + 1):
1491 pagination_args = {
1492 'limit': limit,
1493 }
1494 body = self.list_method(**pagination_args)
1495 resources = self._extract_resources(body)
1496 self.assertEqual(limit, len(resources))
1497
1498 @_require_pagination
1499 def _test_list_no_pagination_limit_0(self):
1500 pagination_args = {
1501 'limit': 0,
1502 }
1503 body = self.list_method(**pagination_args)
1504 resources = self._extract_resources(body)
Béla Vancsicsf1806182016-08-23 07:36:18 +02001505 self.assertGreaterEqual(len(resources), len(self.resource_names))
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001506
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001507 def _test_list_pagination_iteratively(self, lister):
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001508 # first, collect all resources for later comparison
1509 sort_args = {
1510 'sort_dir': constants.SORT_DIRECTION_ASC,
Armando Migliaccio57581c62016-07-01 10:13:19 -07001511 'sort_key': self.field
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001512 }
1513 body = self.list_method(**sort_args)
1514 expected_resources = self._extract_resources(body)
1515 self.assertNotEmpty(expected_resources)
1516
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001517 resources = lister(
1518 len(expected_resources), sort_args
1519 )
1520
1521 # finally, compare that the list retrieved in one go is identical to
1522 # the one containing pagination results
1523 self.assertSameOrder(expected_resources, resources)
1524
1525 def _list_all_with_marker(self, niterations, sort_args):
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001526 # paginate resources one by one, using last fetched resource as a
1527 # marker
1528 resources = []
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001529 for i in range(niterations):
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001530 pagination_args = sort_args.copy()
1531 pagination_args['limit'] = 1
1532 if resources:
1533 pagination_args['marker'] = resources[-1]['id']
1534 body = self.list_method(**pagination_args)
1535 resources_ = self._extract_resources(body)
1536 self.assertEqual(1, len(resources_))
1537 resources.extend(resources_)
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001538 return resources
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001539
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001540 @_require_pagination
1541 @_require_sorting
1542 def _test_list_pagination_with_marker(self):
1543 self._test_list_pagination_iteratively(self._list_all_with_marker)
1544
1545 def _list_all_with_hrefs(self, niterations, sort_args):
1546 # paginate resources one by one, using next href links
1547 resources = []
1548 prev_links = {}
1549
1550 for i in range(niterations):
1551 if prev_links:
1552 uri = self.get_bare_url(prev_links['next'])
1553 else:
Ihar Hrachyshka7f79fe62016-06-07 21:23:44 +02001554 sort_args.update(self.list_kwargs)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001555 uri = self.list_client.build_uri(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001556 self.plural_name, limit=1, **sort_args)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001557 prev_links, body = self.list_client.get_uri_with_links(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001558 self.plural_name, uri
1559 )
1560 resources_ = self._extract_resources(body)
1561 self.assertEqual(1, len(resources_))
1562 resources.extend(resources_)
1563
1564 # The last element is empty and does not contain 'next' link
1565 uri = self.get_bare_url(prev_links['next'])
1566 prev_links, body = self.client.get_uri_with_links(
1567 self.plural_name, uri
1568 )
1569 self.assertNotIn('next', prev_links)
1570
1571 # Now walk backwards and compare results
1572 resources2 = []
1573 for i in range(niterations):
1574 uri = self.get_bare_url(prev_links['previous'])
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001575 prev_links, body = self.list_client.get_uri_with_links(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001576 self.plural_name, uri
1577 )
1578 resources_ = self._extract_resources(body)
1579 self.assertEqual(1, len(resources_))
1580 resources2.extend(resources_)
1581
1582 self.assertSameOrder(resources, reversed(resources2))
1583
1584 return resources
1585
1586 @_require_pagination
1587 @_require_sorting
1588 def _test_list_pagination_with_href_links(self):
1589 self._test_list_pagination_iteratively(self._list_all_with_hrefs)
1590
1591 @_require_pagination
1592 @_require_sorting
1593 def _test_list_pagination_page_reverse_with_href_links(
1594 self, direction=constants.SORT_DIRECTION_ASC):
1595 pagination_args = {
1596 'sort_dir': direction,
Armando Migliaccio57581c62016-07-01 10:13:19 -07001597 'sort_key': self.field,
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001598 }
1599 body = self.list_method(**pagination_args)
1600 expected_resources = self._extract_resources(body)
1601
1602 page_size = 2
1603 pagination_args['limit'] = page_size
1604
1605 prev_links = {}
1606 resources = []
1607 num_resources = len(expected_resources)
1608 niterations = int(math.ceil(float(num_resources) / page_size))
1609 for i in range(niterations):
1610 if prev_links:
1611 uri = self.get_bare_url(prev_links['previous'])
1612 else:
Ihar Hrachyshka7f79fe62016-06-07 21:23:44 +02001613 pagination_args.update(self.list_kwargs)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001614 uri = self.list_client.build_uri(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001615 self.plural_name, page_reverse=True, **pagination_args)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001616 prev_links, body = self.list_client.get_uri_with_links(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001617 self.plural_name, uri
1618 )
1619 resources_ = self._extract_resources(body)
Béla Vancsicsf1806182016-08-23 07:36:18 +02001620 self.assertGreaterEqual(page_size, len(resources_))
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001621 resources.extend(reversed(resources_))
1622
1623 self.assertSameOrder(expected_resources, reversed(resources))
1624
1625 @_require_pagination
1626 @_require_sorting
1627 def _test_list_pagination_page_reverse_asc(self):
1628 self._test_list_pagination_page_reverse(
1629 direction=constants.SORT_DIRECTION_ASC)
1630
1631 @_require_pagination
1632 @_require_sorting
1633 def _test_list_pagination_page_reverse_desc(self):
1634 self._test_list_pagination_page_reverse(
1635 direction=constants.SORT_DIRECTION_DESC)
1636
1637 def _test_list_pagination_page_reverse(self, direction):
1638 pagination_args = {
1639 'sort_dir': direction,
Armando Migliaccio57581c62016-07-01 10:13:19 -07001640 'sort_key': self.field,
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001641 'limit': 3,
1642 }
1643 body = self.list_method(**pagination_args)
1644 expected_resources = self._extract_resources(body)
1645
1646 pagination_args['limit'] -= 1
1647 pagination_args['marker'] = expected_resources[-1]['id']
1648 pagination_args['page_reverse'] = True
1649 body = self.list_method(**pagination_args)
1650
1651 self.assertSameOrder(
1652 # the last entry is not included in 2nd result when used as a
1653 # marker
1654 expected_resources[:-1],
1655 self._extract_resources(body))
Victor Morales1be97b42016-09-05 08:50:06 -05001656
Hongbin Lu54f55922018-07-12 19:05:39 +00001657 @tutils.requires_ext(extension="filter-validation", service="network")
1658 def _test_list_validation_filters(
1659 self, validation_args, filter_is_valid=True):
1660 if not filter_is_valid:
1661 self.assertRaises(lib_exc.BadRequest, self.list_method,
1662 **validation_args)
1663 else:
1664 body = self.list_method(**validation_args)
1665 resources = self._extract_resources(body)
1666 for resource in resources:
1667 self.assertIn(resource['name'], self.resource_names)