blob: 28d556a91c3456c04deb56dfb43fd4800b593c4a [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,
419 **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
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200457 :param **kwargs: optional parameters to be forwarded to wrapped method
458
459 [1] http://netaddr.readthedocs.io/en/latest/tutorial_01.html#supernets-and-subnets # noqa
460 """
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000461
462 # allow tests to use admin client
463 if not client:
464 client = cls.client
465
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200466 if gateway:
467 gateway_ip = netaddr.IPAddress(gateway)
468 if ip_version:
469 if ip_version != gateway_ip.version:
470 raise ValueError(
471 "Gateway IP version doesn't match IP version")
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000472 else:
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200473 ip_version = gateway_ip.version
Sławek Kapłońskid98e27d2018-05-07 16:16:28 +0200474 else:
475 ip_version = ip_version or cls._ip_version
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200476
477 for subnet_cidr in cls.get_subnet_cidrs(
478 ip_version=ip_version, cidr=cidr, mask_bits=mask_bits):
Federico Ressi98f20ec2018-05-11 06:09:49 +0200479 if gateway is not None:
480 kwargs['gateway_ip'] = str(gateway or (subnet_cidr.ip + 1))
Slawek Kaplonski21f53422018-11-02 16:02:09 +0100481 else:
482 kwargs['gateway_ip'] = None
Federico Ressi98f20ec2018-05-11 06:09:49 +0200483 try:
484 body = client.create_subnet(
485 network_id=network['id'],
486 cidr=str(subnet_cidr),
487 ip_version=subnet_cidr.version,
488 **kwargs)
489 break
490 except lib_exc.BadRequest as e:
491 if 'overlaps with another subnet' not in str(e):
492 raise
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000493 else:
494 message = 'Available CIDR for subnet creation could not be found'
495 raise ValueError(message)
496 subnet = body['subnet']
Kevin Bentonba3651c2017-09-01 17:13:01 -0700497 if client is cls.client:
498 cls.subnets.append(subnet)
499 else:
500 cls.admin_subnets.append(subnet)
Federico Ressi98f20ec2018-05-11 06:09:49 +0200501 if reserve_cidr:
502 cls.reserve_subnet_cidr(subnet_cidr)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000503 return subnet
504
505 @classmethod
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200506 def reserve_subnet_cidr(cls, addr, **ipnetwork_kwargs):
507 """Reserve given subnet CIDR making sure it is not used by create_subnet
508
509 :param addr: the CIDR address to be reserved
510 It can be a str or netaddr.IPNetwork instance
511
512 :param **ipnetwork_kwargs: optional netaddr.IPNetwork constructor
513 parameters
514 """
515
516 if not cls.try_reserve_subnet_cidr(addr, **ipnetwork_kwargs):
Bernard Cafarellic3bec862020-09-10 13:59:49 +0200517 raise ValueError('Subnet CIDR already reserved: {0!r}'.format(
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200518 addr))
519
520 @classmethod
521 def try_reserve_subnet_cidr(cls, addr, **ipnetwork_kwargs):
522 """Reserve given subnet CIDR if it hasn't been reserved before
523
524 :param addr: the CIDR address to be reserved
525 It can be a str or netaddr.IPNetwork instance
526
527 :param **ipnetwork_kwargs: optional netaddr.IPNetwork constructor
528 parameters
529
530 :return: True if it wasn't reserved before, False elsewhere.
531 """
532
533 subnet_cidr = netaddr.IPNetwork(addr, **ipnetwork_kwargs)
534 if subnet_cidr in cls.reserved_subnet_cidrs:
535 return False
536 else:
537 cls.reserved_subnet_cidrs.add(subnet_cidr)
538 return True
539
540 @classmethod
541 def get_subnet_cidrs(
542 cls, cidr=None, mask_bits=None, ip_version=None):
543 """Iterate over a sequence of unused subnet CIDR for IP version
544
545 :param cidr: CIDR of the subnet to create
546 It can be either None, a str or a netaddr.IPNetwork instance
547
548 :param mask_bits: CIDR prefix length
549 It can be either None or a numeric value.
550 If cidr parameter is given then mask_bits is used to determinate a
551 sequence of valid CIDR to use as generated.
552 Please see netaddr.IPNetwork.subnet method documentation[1]
553
554 :param ip_version: ip version of generated subnet CIDRs
555 It can be None, IP_VERSION_4 or IP_VERSION_6
556 It has to match given CIDR if given
557
558 :return: iterator over reserved CIDRs of type netaddr.IPNetwork
559
560 [1] http://netaddr.readthedocs.io/en/latest/tutorial_01.html#supernets-and-subnets # noqa
561 """
562
563 if cidr:
564 # Generate subnet CIDRs starting from given CIDR
565 # checking it is of requested IP version
566 cidr = netaddr.IPNetwork(cidr, version=ip_version)
567 else:
568 # Generate subnet CIDRs starting from configured values
569 ip_version = ip_version or cls._ip_version
570 if ip_version == const.IP_VERSION_4:
571 mask_bits = mask_bits or config.safe_get_config_value(
572 'network', 'project_network_mask_bits')
573 cidr = netaddr.IPNetwork(config.safe_get_config_value(
574 'network', 'project_network_cidr'))
575 elif ip_version == const.IP_VERSION_6:
576 mask_bits = config.safe_get_config_value(
577 'network', 'project_network_v6_mask_bits')
578 cidr = netaddr.IPNetwork(config.safe_get_config_value(
579 'network', 'project_network_v6_cidr'))
580 else:
581 raise ValueError('Invalid IP version: {!r}'.format(ip_version))
582
583 if mask_bits:
584 subnet_cidrs = cidr.subnet(mask_bits)
585 else:
586 subnet_cidrs = iter([cidr])
587
588 for subnet_cidr in subnet_cidrs:
589 if subnet_cidr not in cls.reserved_subnet_cidrs:
590 yield subnet_cidr
591
592 @classmethod
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000593 def create_port(cls, network, **kwargs):
594 """Wrapper utility that returns a test port."""
Edan Davidd75e48e2018-01-03 02:49:52 -0500595 if CONF.network.port_vnic_type and 'binding:vnic_type' not in kwargs:
596 kwargs['binding:vnic_type'] = CONF.network.port_vnic_type
Glenn Van de Water5d9b1402020-09-16 15:14:14 +0200597 if CONF.network.port_profile and 'binding:profile' not in kwargs:
598 kwargs['binding:profile'] = CONF.network.port_profile
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000599 body = cls.client.create_port(network_id=network['id'],
600 **kwargs)
601 port = body['port']
602 cls.ports.append(port)
603 return port
604
605 @classmethod
606 def update_port(cls, port, **kwargs):
607 """Wrapper utility that updates a test port."""
608 body = cls.client.update_port(port['id'],
609 **kwargs)
610 return body['port']
611
612 @classmethod
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300613 def _create_router_with_client(
614 cls, client, router_name=None, admin_state_up=False,
615 external_network_id=None, enable_snat=None, **kwargs
616 ):
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000617 ext_gw_info = {}
618 if external_network_id:
619 ext_gw_info['network_id'] = external_network_id
YAMAMOTO Takashi9bd4f972017-06-20 12:49:30 +0900620 if enable_snat is not None:
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000621 ext_gw_info['enable_snat'] = enable_snat
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300622 body = client.create_router(
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000623 router_name, external_gateway_info=ext_gw_info,
624 admin_state_up=admin_state_up, **kwargs)
625 router = body['router']
626 cls.routers.append(router)
627 return router
628
629 @classmethod
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300630 def create_router(cls, *args, **kwargs):
631 return cls._create_router_with_client(cls.client, *args, **kwargs)
632
633 @classmethod
634 def create_admin_router(cls, *args, **kwargs):
rajat294495c042017-06-28 15:37:16 +0530635 return cls._create_router_with_client(cls.os_admin.network_client,
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300636 *args, **kwargs)
637
638 @classmethod
Federico Ressia69dcd52018-07-06 09:45:34 +0200639 def create_floatingip(cls, external_network_id=None, port=None,
640 client=None, **kwargs):
641 """Creates a floating IP.
642
643 Create a floating IP and schedule it for later deletion.
644 If a client is passed, then it is used for deleting the IP too.
645
646 :param external_network_id: network ID where to create
647 By default this is 'CONF.network.public_network_id'.
648
649 :param port: port to bind floating IP to
650 This is translated to 'port_id=port['id']'
651 By default it is None.
652
653 :param client: network client to be used for creating and cleaning up
654 the floating IP.
655
656 :param **kwargs: additional creation parameters to be forwarded to
657 networking server.
658 """
659
660 client = client or cls.client
661 external_network_id = (external_network_id or
662 cls.external_network_id)
663
664 if port:
Federico Ressi47f6ae42018-09-24 16:19:14 +0200665 port_id = kwargs.setdefault('port_id', port['id'])
666 if port_id != port['id']:
667 message = "Port ID specified twice: {!s} != {!s}".format(
668 port_id, port['id'])
669 raise ValueError(message)
Federico Ressia69dcd52018-07-06 09:45:34 +0200670
671 fip = client.create_floatingip(external_network_id,
672 **kwargs)['floatingip']
673
674 # save client to be used later in cls.delete_floatingip
675 # for final cleanup
676 fip['client'] = client
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000677 cls.floating_ips.append(fip)
678 return fip
679
680 @classmethod
Federico Ressia69dcd52018-07-06 09:45:34 +0200681 def delete_floatingip(cls, floating_ip, client=None):
682 """Delete floating IP
683
684 :param client: Client to be used
685 If client is not given it will use the client used to create
686 the floating IP, or cls.client if unknown.
687 """
688
689 client = client or floating_ip.get('client') or cls.client
690 client.delete_floatingip(floating_ip['id'])
691
692 @classmethod
Slawek Kaplonski003fcae2019-05-26 22:38:35 +0200693 def create_port_forwarding(cls, fip_id, internal_port_id,
694 internal_port, external_port,
695 internal_ip_address=None, protocol="tcp",
696 client=None):
697 """Creates a port forwarding.
698
699 Create a port forwarding and schedule it for later deletion.
700 If a client is passed, then it is used for deleting the PF too.
701
702 :param fip_id: The ID of the floating IP address.
703
704 :param internal_port_id: The ID of the Neutron port associated to
705 the floating IP port forwarding.
706
707 :param internal_port: The TCP/UDP/other protocol port number of the
708 Neutron port fixed IP address associated to the floating ip
709 port forwarding.
710
711 :param external_port: The TCP/UDP/other protocol port number of
712 the port forwarding floating IP address.
713
714 :param internal_ip_address: The fixed IPv4 address of the Neutron
715 port associated to the floating IP port forwarding.
716
717 :param protocol: The IP protocol used in the floating IP port
718 forwarding.
719
720 :param client: network client to be used for creating and cleaning up
721 the floating IP port forwarding.
722 """
723
724 client = client or cls.client
725
726 pf = client.create_port_forwarding(
727 fip_id, internal_port_id, internal_port, external_port,
728 internal_ip_address, protocol)['port_forwarding']
729
730 # save ID of floating IP associated with port forwarding for final
731 # cleanup
732 pf['floatingip_id'] = fip_id
733
734 # save client to be used later in cls.delete_port_forwarding
735 # for final cleanup
736 pf['client'] = client
737 cls.port_forwardings.append(pf)
738 return pf
739
740 @classmethod
Flavio Fernandesa1952c62020-10-02 06:39:08 -0400741 def update_port_forwarding(cls, fip_id, pf_id, client=None, **kwargs):
742 """Wrapper utility for update_port_forwarding."""
743 client = client or cls.client
744 return client.update_port_forwarding(fip_id, pf_id, **kwargs)
745
746 @classmethod
Slawek Kaplonski003fcae2019-05-26 22:38:35 +0200747 def delete_port_forwarding(cls, pf, client=None):
748 """Delete port forwarding
749
750 :param client: Client to be used
751 If client is not given it will use the client used to create
752 the port forwarding, or cls.client if unknown.
753 """
754
755 client = client or pf.get('client') or cls.client
756 client.delete_port_forwarding(pf['floatingip_id'], pf['id'])
757
Nurmatov Mamatisa3f2bbb52021-10-20 14:33:54 +0300758 def create_local_ip(cls, network_id=None,
759 client=None, **kwargs):
760 """Creates a Local IP.
761
762 Create a Local IP and schedule it for later deletion.
763 If a client is passed, then it is used for deleting the IP too.
764
765 :param network_id: network ID where to create
766 By default this is 'CONF.network.public_network_id'.
767
768 :param client: network client to be used for creating and cleaning up
769 the Local IP.
770
771 :param **kwargs: additional creation parameters to be forwarded to
772 networking server.
773 """
774
775 client = client or cls.client
776 network_id = (network_id or
777 cls.external_network_id)
778
779 local_ip = client.create_local_ip(network_id,
780 **kwargs)['local_ip']
781
782 # save client to be used later in cls.delete_local_ip
783 # for final cleanup
784 local_ip['client'] = client
785 cls.local_ips.append(local_ip)
786 return local_ip
787
788 @classmethod
789 def delete_local_ip(cls, local_ip, client=None):
790 """Delete Local IP
791
792 :param client: Client to be used
793 If client is not given it will use the client used to create
794 the Local IP, or cls.client if unknown.
795 """
796
797 client = client or local_ip.get('client') or cls.client
798 client.delete_local_ip(local_ip['id'])
799
800 @classmethod
801 def create_local_ip_association(cls, local_ip_id, fixed_port_id,
802 fixed_ip_address=None, client=None):
803 """Creates a Local IP association.
804
805 Create a Local IP Association and schedule it for later deletion.
806 If a client is passed, then it is used for deleting the association
807 too.
808
809 :param local_ip_id: The ID of the Local IP.
810
811 :param fixed_port_id: The ID of the Neutron port
812 to be associated with the Local IP
813
814 :param fixed_ip_address: The fixed IPv4 address of the Neutron
815 port to be associated with the Local IP
816
817 :param client: network client to be used for creating and cleaning up
818 the Local IP Association.
819 """
820
821 client = client or cls.client
822
823 association = client.create_local_ip_association(
824 local_ip_id, fixed_port_id,
825 fixed_ip_address)['port_association']
826
827 # save ID of Local IP for final cleanup
828 association['local_ip_id'] = local_ip_id
829
830 # save client to be used later in
831 # cls.delete_local_ip_association for final cleanup
832 association['client'] = client
833 cls.local_ip_associations.append(association)
834 return association
835
836 @classmethod
837 def delete_local_ip_association(cls, association, client=None):
838
839 """Delete Local IP Association
840
841 :param client: Client to be used
842 If client is not given it will use the client used to create
843 the local IP association, or cls.client if unknown.
844 """
845
846 client = client or association.get('client') or cls.client
847 client.delete_local_ip_association(association['local_ip_id'],
848 association['fixed_port_id'])
849
Slawek Kaplonski003fcae2019-05-26 22:38:35 +0200850 @classmethod
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000851 def create_router_interface(cls, router_id, subnet_id):
852 """Wrapper utility that returns a router interface."""
853 interface = cls.client.add_router_interface_with_subnet_id(
854 router_id, subnet_id)
855 return interface
856
857 @classmethod
Bence Romsics46bd3af2019-09-13 10:52:41 +0200858 def add_extra_routes_atomic(cls, *args, **kwargs):
859 return cls.client.add_extra_routes_atomic(*args, **kwargs)
860
861 @classmethod
862 def remove_extra_routes_atomic(cls, *args, **kwargs):
863 return cls.client.remove_extra_routes_atomic(*args, **kwargs)
864
865 @classmethod
Sławek Kapłońskiff294062016-12-04 15:00:54 +0000866 def get_supported_qos_rule_types(cls):
867 body = cls.client.list_qos_rule_types()
868 return [rule_type['type'] for rule_type in body['rule_types']]
869
870 @classmethod
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200871 def create_qos_policy(cls, name, description=None, shared=False,
Rodolfo Alonso Hernandeze2d062f2020-01-14 17:11:42 +0000872 project_id=None, is_default=False):
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000873 """Wrapper utility that returns a test QoS policy."""
874 body = cls.admin_client.create_qos_policy(
Rodolfo Alonso Hernandeze2d062f2020-01-14 17:11:42 +0000875 name, description, shared, project_id, is_default)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000876 qos_policy = body['policy']
877 cls.qos_policies.append(qos_policy)
878 return qos_policy
879
880 @classmethod
elajkatdbb0b482021-05-04 17:20:07 +0200881 def create_qos_dscp_marking_rule(cls, policy_id, dscp_mark):
882 """Wrapper utility that creates and returns a QoS dscp rule."""
883 body = cls.admin_client.create_dscp_marking_rule(
884 policy_id, dscp_mark)
885 qos_rule = body['dscp_marking_rule']
886 cls.qos_rules.append(qos_rule)
887 return qos_rule
888
889 @classmethod
Jakub Libosvar83704832017-12-06 16:02:28 +0000890 def delete_router(cls, router, client=None):
891 client = client or cls.client
Aditya Vaja49819a72018-11-26 14:20:10 -0800892 if 'routes' in router:
893 client.remove_router_extra_routes(router['id'])
Jakub Libosvar83704832017-12-06 16:02:28 +0000894 body = client.list_router_interfaces(router['id'])
Chandan Kumarc125fd12017-11-15 19:41:01 +0530895 interfaces = [port for port in body['ports']
896 if port['device_owner'] in const.ROUTER_INTERFACE_OWNERS]
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000897 for i in interfaces:
898 try:
Jakub Libosvar83704832017-12-06 16:02:28 +0000899 client.remove_router_interface_with_subnet_id(
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000900 router['id'], i['fixed_ips'][0]['subnet_id'])
901 except lib_exc.NotFound:
902 pass
Jakub Libosvar83704832017-12-06 16:02:28 +0000903 client.delete_router(router['id'])
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000904
905 @classmethod
906 def create_address_scope(cls, name, is_admin=False, **kwargs):
907 if is_admin:
908 body = cls.admin_client.create_address_scope(name=name, **kwargs)
909 cls.admin_address_scopes.append(body['address_scope'])
910 else:
911 body = cls.client.create_address_scope(name=name, **kwargs)
912 cls.address_scopes.append(body['address_scope'])
913 return body['address_scope']
914
915 @classmethod
Igor Malinovskiyb80f1d02020-03-06 13:39:52 +0200916 def create_subnetpool(cls, name, is_admin=False, client=None, **kwargs):
917 if client is None:
918 client = cls.admin_client if is_admin else cls.client
919
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000920 if is_admin:
Igor Malinovskiyb80f1d02020-03-06 13:39:52 +0200921 body = client.create_subnetpool(name, **kwargs)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000922 cls.admin_subnetpools.append(body['subnetpool'])
923 else:
Igor Malinovskiyb80f1d02020-03-06 13:39:52 +0200924 body = client.create_subnetpool(name, **kwargs)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000925 cls.subnetpools.append(body['subnetpool'])
926 return body['subnetpool']
927
Chandan Kumarc125fd12017-11-15 19:41:01 +0530928 @classmethod
Miguel Lavalleb1c7a3d2021-01-31 19:05:22 -0600929 def create_address_group(cls, name, is_admin=False, **kwargs):
930 if is_admin:
931 body = cls.admin_client.create_address_group(name=name, **kwargs)
932 cls.admin_address_groups.append(body['address_group'])
933 else:
934 body = cls.client.create_address_group(name=name, **kwargs)
935 cls.address_groups.append(body['address_group'])
936 return body['address_group']
937
938 @classmethod
Chandan Kumarc125fd12017-11-15 19:41:01 +0530939 def create_project(cls, name=None, description=None):
940 test_project = name or data_utils.rand_name('test_project_')
941 test_description = description or data_utils.rand_name('desc_')
942 project = cls.identity_admin_client.create_project(
943 name=test_project,
944 description=test_description)['project']
945 cls.projects.append(project)
Dongcan Ye2de722e2018-07-04 11:01:37 +0000946 # Create a project will create a default security group.
Dongcan Ye2de722e2018-07-04 11:01:37 +0000947 sgs_list = cls.admin_client.list_security_groups(
948 tenant_id=project['id'])['security_groups']
Federico Ressi4c590d72018-10-10 14:01:08 +0200949 for security_group in sgs_list:
950 # Make sure delete_security_group method will use
951 # the admin client for this group
952 security_group['client'] = cls.admin_client
953 cls.security_groups.append(security_group)
Chandan Kumarc125fd12017-11-15 19:41:01 +0530954 return project
955
956 @classmethod
Federico Ressi4c590d72018-10-10 14:01:08 +0200957 def create_security_group(cls, name=None, project=None, client=None,
958 **kwargs):
959 if project:
960 client = client or cls.admin_client
961 project_id = kwargs.setdefault('project_id', project['id'])
962 tenant_id = kwargs.setdefault('tenant_id', project['id'])
963 if project_id != project['id'] or tenant_id != project['id']:
964 raise ValueError('Project ID specified multiple times')
965 else:
966 client = client or cls.client
967
968 name = name or data_utils.rand_name(cls.__name__)
969 security_group = client.create_security_group(name=name, **kwargs)[
970 'security_group']
971 security_group['client'] = client
972 cls.security_groups.append(security_group)
973 return security_group
974
975 @classmethod
976 def delete_security_group(cls, security_group, client=None):
977 client = client or security_group.get('client') or cls.client
978 client.delete_security_group(security_group['id'])
979
980 @classmethod
Slawek Kaplonskiaa22c9e2023-05-18 18:59:26 +0200981 def get_security_group(cls, name='default', client=None):
982 client = client or cls.client
983 security_groups = client.list_security_groups()['security_groups']
984 for security_group in security_groups:
985 if security_group['name'] == name:
986 return security_group
987 raise ValueError("No such security group named {!r}".format(name))
988
989 @classmethod
Federico Ressi4c590d72018-10-10 14:01:08 +0200990 def create_security_group_rule(cls, security_group=None, project=None,
991 client=None, ip_version=None, **kwargs):
992 if project:
993 client = client or cls.admin_client
994 project_id = kwargs.setdefault('project_id', project['id'])
995 tenant_id = kwargs.setdefault('tenant_id', project['id'])
996 if project_id != project['id'] or tenant_id != project['id']:
997 raise ValueError('Project ID specified multiple times')
998
999 if 'security_group_id' not in kwargs:
1000 security_group = (security_group or
1001 cls.get_security_group(client=client))
1002
1003 if security_group:
1004 client = client or security_group.get('client')
1005 security_group_id = kwargs.setdefault('security_group_id',
1006 security_group['id'])
1007 if security_group_id != security_group['id']:
1008 raise ValueError('Security group ID specified multiple times.')
1009
1010 ip_version = ip_version or cls._ip_version
1011 default_params = (
1012 constants.DEFAULT_SECURITY_GROUP_RULE_PARAMS[ip_version])
Slawek Kaplonski83979b92022-12-15 14:15:12 +01001013 if (('remote_address_group_id' in kwargs or
1014 'remote_group_id' in kwargs) and
1015 'remote_ip_prefix' in default_params):
Miguel Lavalleb1c7a3d2021-01-31 19:05:22 -06001016 default_params.pop('remote_ip_prefix')
Federico Ressi4c590d72018-10-10 14:01:08 +02001017 for key, value in default_params.items():
1018 kwargs.setdefault(key, value)
1019
1020 client = client or cls.client
1021 return client.create_security_group_rule(**kwargs)[
1022 'security_group_rule']
1023
1024 @classmethod
Slawek Kaplonskiaa22c9e2023-05-18 18:59:26 +02001025 def create_default_security_group_rule(cls, **kwargs):
1026 body = cls.admin_client.create_default_security_group_rule(**kwargs)
1027 default_sg_rule = body['default_security_group_rule']
1028 cls.sg_rule_templates.append(default_sg_rule)
1029 return default_sg_rule
Chandan Kumarc125fd12017-11-15 19:41:01 +05301030
Federico Ressiab286e42018-06-19 09:52:10 +02001031 @classmethod
1032 def create_keypair(cls, client=None, name=None, **kwargs):
1033 client = client or cls.os_primary.keypairs_client
1034 name = name or data_utils.rand_name('keypair-test')
1035 keypair = client.create_keypair(name=name, **kwargs)['keypair']
1036
1037 # save client for later cleanup
1038 keypair['client'] = client
1039 cls.keypairs.append(keypair)
1040 return keypair
1041
1042 @classmethod
1043 def delete_keypair(cls, keypair, client=None):
1044 client = (client or keypair.get('client') or
1045 cls.os_primary.keypairs_client)
1046 client.delete_keypair(keypair_name=keypair['name'])
1047
Federico Ressi82e83e32018-07-03 14:19:55 +02001048 @classmethod
1049 def create_trunk(cls, port=None, subports=None, client=None, **kwargs):
1050 """Create network trunk
1051
1052 :param port: dictionary containing parent port ID (port['id'])
1053 :param client: client to be used for connecting to networking service
1054 :param **kwargs: extra parameters to be forwarded to network service
1055
1056 :returns: dictionary containing created trunk details
1057 """
1058 client = client or cls.client
1059
1060 if port:
1061 kwargs['port_id'] = port['id']
1062
1063 trunk = client.create_trunk(subports=subports, **kwargs)['trunk']
1064 # Save client reference for later deletion
1065 trunk['client'] = client
1066 cls.trunks.append(trunk)
1067 return trunk
1068
1069 @classmethod
Huifeng Le1c9f40b2018-11-07 01:14:21 +08001070 def delete_trunk(cls, trunk, client=None, detach_parent_port=True):
Federico Ressi82e83e32018-07-03 14:19:55 +02001071 """Delete network trunk
1072
1073 :param trunk: dictionary containing trunk ID (trunk['id'])
1074
1075 :param client: client to be used for connecting to networking service
1076 """
1077 client = client or trunk.get('client') or cls.client
1078 trunk.update(client.show_trunk(trunk['id'])['trunk'])
1079
1080 if not trunk['admin_state_up']:
1081 # Cannot touch trunk before admin_state_up is True
1082 client.update_trunk(trunk['id'], admin_state_up=True)
1083 if trunk['sub_ports']:
1084 # Removes trunk ports before deleting it
1085 cls._try_delete_resource(client.remove_subports, trunk['id'],
1086 trunk['sub_ports'])
1087
1088 # we have to detach the interface from the server before
1089 # the trunk can be deleted.
1090 parent_port = {'id': trunk['port_id']}
1091
1092 def is_parent_port_detached():
1093 parent_port.update(client.show_port(parent_port['id'])['port'])
1094 return not parent_port['device_id']
1095
Huifeng Le1c9f40b2018-11-07 01:14:21 +08001096 if detach_parent_port and not is_parent_port_detached():
Federico Ressi82e83e32018-07-03 14:19:55 +02001097 # this could probably happen when trunk is deleted and parent port
1098 # has been assigned to a VM that is still running. Here we are
1099 # assuming that device_id points to such VM.
1100 cls.os_primary.compute.InterfacesClient().delete_interface(
1101 parent_port['device_id'], parent_port['id'])
1102 utils.wait_until_true(is_parent_port_detached)
1103
1104 client.delete_trunk(trunk['id'])
1105
Harald Jensåsc9782fa2019-06-03 22:35:41 +02001106 @classmethod
1107 def create_conntrack_helper(cls, router_id, helper, protocol, port,
1108 client=None):
1109 """Create a conntrack helper
1110
1111 Create a conntrack helper and schedule it for later deletion. If a
1112 client is passed, then it is used for deleteing the CTH too.
1113
1114 :param router_id: The ID of the Neutron router associated to the
1115 conntrack helper.
1116
1117 :param helper: The conntrack helper module alias
1118
1119 :param protocol: The conntrack helper IP protocol used in the conntrack
1120 helper.
1121
1122 :param port: The conntrack helper IP protocol port number for the
1123 conntrack helper.
1124
1125 :param client: network client to be used for creating and cleaning up
1126 the conntrack helper.
1127 """
1128
1129 client = client or cls.client
1130
1131 cth = client.create_conntrack_helper(router_id, helper, protocol,
1132 port)['conntrack_helper']
1133
1134 # save ID of router associated with conntrack helper for final cleanup
1135 cth['router_id'] = router_id
1136
1137 # save client to be used later in cls.delete_conntrack_helper for final
1138 # cleanup
1139 cth['client'] = client
1140 cls.conntrack_helpers.append(cth)
1141 return cth
1142
1143 @classmethod
1144 def delete_conntrack_helper(cls, cth, client=None):
1145 """Delete conntrack helper
1146
1147 :param client: Client to be used
1148 If client is not given it will use the client used to create the
1149 conntrack helper, or cls.client if unknown.
1150 """
1151
1152 client = client or cth.get('client') or cls.client
1153 client.delete_conntrack_helper(cth['router_id'], cth['id'])
1154
yangjianfeng2936a292022-02-04 11:22:11 +08001155 @classmethod
1156 def create_ndp_proxy(cls, router_id, port_id, client=None, **kwargs):
1157 """Creates a ndp proxy.
1158
1159 Create a ndp proxy and schedule it for later deletion.
1160 If a client is passed, then it is used for deleting the NDP proxy too.
1161
1162 :param router_id: router ID where to create the ndp proxy.
1163
1164 :param port_id: port ID which the ndp proxy associate with
1165
1166 :param client: network client to be used for creating and cleaning up
1167 the ndp proxy.
1168
1169 :param **kwargs: additional creation parameters to be forwarded to
1170 networking server.
1171 """
1172 client = client or cls.client
1173
1174 data = {'router_id': router_id, 'port_id': port_id}
1175 if kwargs:
1176 data.update(kwargs)
1177 ndp_proxy = client.create_ndp_proxy(**data)['ndp_proxy']
1178
1179 # save client to be used later in cls.delete_ndp_proxy
1180 # for final cleanup
1181 ndp_proxy['client'] = client
1182 cls.ndp_proxies.append(ndp_proxy)
1183 return ndp_proxy
1184
1185 @classmethod
1186 def delete_ndp_proxy(cls, ndp_proxy, client=None):
1187 """Delete ndp proxy
1188
1189 :param client: Client to be used
1190 If client is not given it will use the client used to create
1191 the ndp proxy, or cls.client if unknown.
1192 """
1193 client = client or ndp_proxy.get('client') or cls.client
1194 client.delete_ndp_proxy(ndp_proxy['id'])
1195
Daniel Mellado3c0aeab2016-01-29 11:30:25 +00001196
1197class BaseAdminNetworkTest(BaseNetworkTest):
1198
1199 credentials = ['primary', 'admin']
1200
1201 @classmethod
1202 def setup_clients(cls):
1203 super(BaseAdminNetworkTest, cls).setup_clients()
fumihiko kakumaa216fc12017-07-14 10:43:29 +09001204 cls.admin_client = cls.os_admin.network_client
Jakub Libosvarf5758012017-08-15 13:45:30 +00001205 cls.identity_admin_client = cls.os_admin.projects_client
Daniel Mellado3c0aeab2016-01-29 11:30:25 +00001206
1207 @classmethod
1208 def create_metering_label(cls, name, description):
1209 """Wrapper utility that returns a test metering label."""
1210 body = cls.admin_client.create_metering_label(
1211 description=description,
1212 name=data_utils.rand_name("metering-label"))
1213 metering_label = body['metering_label']
1214 cls.metering_labels.append(metering_label)
1215 return metering_label
1216
1217 @classmethod
1218 def create_metering_label_rule(cls, remote_ip_prefix, direction,
1219 metering_label_id):
1220 """Wrapper utility that returns a test metering label rule."""
1221 body = cls.admin_client.create_metering_label_rule(
1222 remote_ip_prefix=remote_ip_prefix, direction=direction,
1223 metering_label_id=metering_label_id)
1224 metering_label_rule = body['metering_label_rule']
1225 cls.metering_label_rules.append(metering_label_rule)
1226 return metering_label_rule
1227
1228 @classmethod
Kailun Qineaaf9782018-12-20 04:45:01 +08001229 def create_network_segment_range(cls, name, shared,
1230 project_id, network_type,
1231 physical_network, minimum,
1232 maximum):
1233 """Wrapper utility that returns a test network segment range."""
1234 network_segment_range_args = {'name': name,
1235 'shared': shared,
1236 'project_id': project_id,
1237 'network_type': network_type,
1238 'physical_network': physical_network,
1239 'minimum': minimum,
1240 'maximum': maximum}
1241 body = cls.admin_client.create_network_segment_range(
1242 **network_segment_range_args)
1243 network_segment_range = body['network_segment_range']
1244 cls.network_segment_ranges.append(network_segment_range)
1245 return network_segment_range
1246
1247 @classmethod
Daniel Mellado3c0aeab2016-01-29 11:30:25 +00001248 def create_flavor(cls, name, description, service_type):
1249 """Wrapper utility that returns a test flavor."""
1250 body = cls.admin_client.create_flavor(
1251 description=description, service_type=service_type,
1252 name=name)
1253 flavor = body['flavor']
1254 cls.flavors.append(flavor)
1255 return flavor
1256
1257 @classmethod
1258 def create_service_profile(cls, description, metainfo, driver):
1259 """Wrapper utility that returns a test service profile."""
1260 body = cls.admin_client.create_service_profile(
1261 driver=driver, metainfo=metainfo, description=description)
1262 service_profile = body['service_profile']
1263 cls.service_profiles.append(service_profile)
1264 return service_profile
1265
1266 @classmethod
Nguyen Phuong An67993fc2017-11-24 11:30:25 +07001267 def create_log(cls, name, description=None,
1268 resource_type='security_group', resource_id=None,
1269 target_id=None, event='ALL', enabled=True):
1270 """Wrapper utility that returns a test log object."""
1271 log_args = {'name': name,
Nguyen Phuong An67993fc2017-11-24 11:30:25 +07001272 'resource_type': resource_type,
1273 'resource_id': resource_id,
1274 'target_id': target_id,
1275 'event': event,
1276 'enabled': enabled}
Slawek Kaplonskid9fe3022021-08-11 15:25:16 +02001277 if description:
1278 log_args['description'] = description
Nguyen Phuong An67993fc2017-11-24 11:30:25 +07001279 body = cls.admin_client.create_log(**log_args)
1280 log_object = body['log']
1281 cls.log_objects.append(log_object)
1282 return log_object
1283
1284 @classmethod
Daniel Mellado3c0aeab2016-01-29 11:30:25 +00001285 def get_unused_ip(cls, net_id, ip_version=None):
Gary Kotton011345f2016-06-15 08:04:31 -07001286 """Get an unused ip address in a allocation pool of net"""
Daniel Mellado3c0aeab2016-01-29 11:30:25 +00001287 body = cls.admin_client.list_ports(network_id=net_id)
1288 ports = body['ports']
1289 used_ips = []
1290 for port in ports:
1291 used_ips.extend(
1292 [fixed_ip['ip_address'] for fixed_ip in port['fixed_ips']])
1293 body = cls.admin_client.list_subnets(network_id=net_id)
1294 subnets = body['subnets']
1295
1296 for subnet in subnets:
1297 if ip_version and subnet['ip_version'] != ip_version:
1298 continue
1299 cidr = subnet['cidr']
1300 allocation_pools = subnet['allocation_pools']
1301 iterators = []
1302 if allocation_pools:
1303 for allocation_pool in allocation_pools:
1304 iterators.append(netaddr.iter_iprange(
1305 allocation_pool['start'], allocation_pool['end']))
1306 else:
1307 net = netaddr.IPNetwork(cidr)
1308
1309 def _iterip():
1310 for ip in net:
1311 if ip not in (net.network, net.broadcast):
1312 yield ip
1313 iterators.append(iter(_iterip()))
1314
1315 for iterator in iterators:
1316 for ip in iterator:
1317 if str(ip) not in used_ips:
1318 return str(ip)
1319
1320 message = (
1321 "net(%s) has no usable IP address in allocation pools" % net_id)
1322 raise exceptions.InvalidConfiguration(message)
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001323
Lajos Katona2f904652018-08-23 14:04:56 +02001324 @classmethod
1325 def create_provider_network(cls, physnet_name, start_segmentation_id,
1326 max_attempts=30):
1327 segmentation_id = start_segmentation_id
Lajos Katona7eb67252019-01-14 12:55:35 +01001328 for attempts in range(max_attempts):
Lajos Katona2f904652018-08-23 14:04:56 +02001329 try:
Lajos Katona7eb67252019-01-14 12:55:35 +01001330 return cls.create_network(
Lajos Katona2f904652018-08-23 14:04:56 +02001331 name=data_utils.rand_name('test_net'),
1332 shared=True,
1333 provider_network_type='vlan',
1334 provider_physical_network=physnet_name,
1335 provider_segmentation_id=segmentation_id)
Lajos Katona2f904652018-08-23 14:04:56 +02001336 except lib_exc.Conflict:
Lajos Katona2f904652018-08-23 14:04:56 +02001337 segmentation_id += 1
1338 if segmentation_id > 4095:
1339 raise lib_exc.TempestException(
1340 "No free segmentation id was found for provider "
1341 "network creation!")
1342 time.sleep(CONF.network.build_interval)
Lajos Katona7eb67252019-01-14 12:55:35 +01001343 LOG.exception("Failed to create provider network after "
1344 "%d attempts", max_attempts)
1345 raise lib_exc.TimeoutException
Lajos Katona2f904652018-08-23 14:04:56 +02001346
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001347
Sławek Kapłońskiff294062016-12-04 15:00:54 +00001348def require_qos_rule_type(rule_type):
1349 def decorator(f):
1350 @functools.wraps(f)
1351 def wrapper(self, *func_args, **func_kwargs):
1352 if rule_type not in self.get_supported_qos_rule_types():
1353 raise self.skipException(
1354 "%s rule type is required." % rule_type)
1355 return f(self, *func_args, **func_kwargs)
1356 return wrapper
1357 return decorator
1358
1359
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001360def _require_sorting(f):
1361 @functools.wraps(f)
1362 def inner(self, *args, **kwargs):
Chandan Kumarc125fd12017-11-15 19:41:01 +05301363 if not tutils.is_extension_enabled("sorting", "network"):
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001364 self.skipTest('Sorting feature is required')
1365 return f(self, *args, **kwargs)
1366 return inner
1367
1368
1369def _require_pagination(f):
1370 @functools.wraps(f)
1371 def inner(self, *args, **kwargs):
Chandan Kumarc125fd12017-11-15 19:41:01 +05301372 if not tutils.is_extension_enabled("pagination", "network"):
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001373 self.skipTest('Pagination feature is required')
1374 return f(self, *args, **kwargs)
1375 return inner
1376
1377
1378class BaseSearchCriteriaTest(BaseNetworkTest):
1379
1380 # This should be defined by subclasses to reflect resource name to test
1381 resource = None
1382
Armando Migliaccio57581c62016-07-01 10:13:19 -07001383 field = 'name'
1384
Ihar Hrachyshkaa8fe5a12016-05-24 14:50:58 +02001385 # NOTE(ihrachys): some names, like those starting with an underscore (_)
1386 # are sorted differently depending on whether the plugin implements native
1387 # sorting support, or not. So we avoid any such cases here, sticking to
1388 # alphanumeric. Also test a case when there are multiple resources with the
1389 # same name
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001390 resource_names = ('test1', 'abc1', 'test10', '123test') + ('test1',)
1391
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001392 force_tenant_isolation = True
1393
Ihar Hrachyshkaa8fe5a12016-05-24 14:50:58 +02001394 list_kwargs = {}
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001395
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001396 list_as_admin = False
1397
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001398 def assertSameOrder(self, original, actual):
1399 # gracefully handle iterators passed
1400 original = list(original)
1401 actual = list(actual)
1402 self.assertEqual(len(original), len(actual))
1403 for expected, res in zip(original, actual):
Armando Migliaccio57581c62016-07-01 10:13:19 -07001404 self.assertEqual(expected[self.field], res[self.field])
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001405
1406 @utils.classproperty
1407 def plural_name(self):
1408 return '%ss' % self.resource
1409
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001410 @property
1411 def list_client(self):
1412 return self.admin_client if self.list_as_admin else self.client
1413
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001414 def list_method(self, *args, **kwargs):
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001415 method = getattr(self.list_client, 'list_%s' % self.plural_name)
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001416 kwargs.update(self.list_kwargs)
1417 return method(*args, **kwargs)
1418
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001419 def get_bare_url(self, url):
1420 base_url = self.client.base_url
zheng.yong74e760a2019-05-22 14:16:14 +08001421 base_url_normalized = utils.normalize_url(base_url)
1422 url_normalized = utils.normalize_url(url)
1423 self.assertTrue(url_normalized.startswith(base_url_normalized))
1424 return url_normalized[len(base_url_normalized):]
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001425
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001426 @classmethod
1427 def _extract_resources(cls, body):
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001428 return body[cls.plural_name]
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001429
1430 def _test_list_sorts(self, direction):
1431 sort_args = {
1432 'sort_dir': direction,
Armando Migliaccio57581c62016-07-01 10:13:19 -07001433 'sort_key': self.field
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001434 }
1435 body = self.list_method(**sort_args)
1436 resources = self._extract_resources(body)
1437 self.assertNotEmpty(
1438 resources, "%s list returned is empty" % self.resource)
Armando Migliaccio57581c62016-07-01 10:13:19 -07001439 retrieved_names = [res[self.field] for res in resources]
Martin Kopec71a73242024-01-17 12:02:24 +01001440 # sort without taking into account whether the network is named with
1441 # a capital letter or not
1442 expected = sorted(retrieved_names, key=lambda v: v.upper())
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001443 if direction == constants.SORT_DIRECTION_DESC:
1444 expected = list(reversed(expected))
1445 self.assertEqual(expected, retrieved_names)
1446
1447 @_require_sorting
1448 def _test_list_sorts_asc(self):
1449 self._test_list_sorts(constants.SORT_DIRECTION_ASC)
1450
1451 @_require_sorting
1452 def _test_list_sorts_desc(self):
1453 self._test_list_sorts(constants.SORT_DIRECTION_DESC)
1454
1455 @_require_pagination
1456 def _test_list_pagination(self):
1457 for limit in range(1, len(self.resource_names) + 1):
1458 pagination_args = {
1459 'limit': limit,
1460 }
1461 body = self.list_method(**pagination_args)
1462 resources = self._extract_resources(body)
1463 self.assertEqual(limit, len(resources))
1464
1465 @_require_pagination
1466 def _test_list_no_pagination_limit_0(self):
1467 pagination_args = {
1468 'limit': 0,
1469 }
1470 body = self.list_method(**pagination_args)
1471 resources = self._extract_resources(body)
Béla Vancsicsf1806182016-08-23 07:36:18 +02001472 self.assertGreaterEqual(len(resources), len(self.resource_names))
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001473
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001474 def _test_list_pagination_iteratively(self, lister):
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001475 # first, collect all resources for later comparison
1476 sort_args = {
1477 'sort_dir': constants.SORT_DIRECTION_ASC,
Armando Migliaccio57581c62016-07-01 10:13:19 -07001478 'sort_key': self.field
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001479 }
1480 body = self.list_method(**sort_args)
1481 expected_resources = self._extract_resources(body)
1482 self.assertNotEmpty(expected_resources)
1483
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001484 resources = lister(
1485 len(expected_resources), sort_args
1486 )
1487
1488 # finally, compare that the list retrieved in one go is identical to
1489 # the one containing pagination results
1490 self.assertSameOrder(expected_resources, resources)
1491
1492 def _list_all_with_marker(self, niterations, sort_args):
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001493 # paginate resources one by one, using last fetched resource as a
1494 # marker
1495 resources = []
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001496 for i in range(niterations):
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001497 pagination_args = sort_args.copy()
1498 pagination_args['limit'] = 1
1499 if resources:
1500 pagination_args['marker'] = resources[-1]['id']
1501 body = self.list_method(**pagination_args)
1502 resources_ = self._extract_resources(body)
1503 self.assertEqual(1, len(resources_))
1504 resources.extend(resources_)
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001505 return resources
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001506
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001507 @_require_pagination
1508 @_require_sorting
1509 def _test_list_pagination_with_marker(self):
1510 self._test_list_pagination_iteratively(self._list_all_with_marker)
1511
1512 def _list_all_with_hrefs(self, niterations, sort_args):
1513 # paginate resources one by one, using next href links
1514 resources = []
1515 prev_links = {}
1516
1517 for i in range(niterations):
1518 if prev_links:
1519 uri = self.get_bare_url(prev_links['next'])
1520 else:
Ihar Hrachyshka7f79fe62016-06-07 21:23:44 +02001521 sort_args.update(self.list_kwargs)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001522 uri = self.list_client.build_uri(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001523 self.plural_name, limit=1, **sort_args)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001524 prev_links, body = self.list_client.get_uri_with_links(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001525 self.plural_name, uri
1526 )
1527 resources_ = self._extract_resources(body)
1528 self.assertEqual(1, len(resources_))
1529 resources.extend(resources_)
1530
1531 # The last element is empty and does not contain 'next' link
1532 uri = self.get_bare_url(prev_links['next'])
1533 prev_links, body = self.client.get_uri_with_links(
1534 self.plural_name, uri
1535 )
1536 self.assertNotIn('next', prev_links)
1537
1538 # Now walk backwards and compare results
1539 resources2 = []
1540 for i in range(niterations):
1541 uri = self.get_bare_url(prev_links['previous'])
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001542 prev_links, body = self.list_client.get_uri_with_links(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001543 self.plural_name, uri
1544 )
1545 resources_ = self._extract_resources(body)
1546 self.assertEqual(1, len(resources_))
1547 resources2.extend(resources_)
1548
1549 self.assertSameOrder(resources, reversed(resources2))
1550
1551 return resources
1552
1553 @_require_pagination
1554 @_require_sorting
1555 def _test_list_pagination_with_href_links(self):
1556 self._test_list_pagination_iteratively(self._list_all_with_hrefs)
1557
1558 @_require_pagination
1559 @_require_sorting
1560 def _test_list_pagination_page_reverse_with_href_links(
1561 self, direction=constants.SORT_DIRECTION_ASC):
1562 pagination_args = {
1563 'sort_dir': direction,
Armando Migliaccio57581c62016-07-01 10:13:19 -07001564 'sort_key': self.field,
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001565 }
1566 body = self.list_method(**pagination_args)
1567 expected_resources = self._extract_resources(body)
1568
1569 page_size = 2
1570 pagination_args['limit'] = page_size
1571
1572 prev_links = {}
1573 resources = []
1574 num_resources = len(expected_resources)
1575 niterations = int(math.ceil(float(num_resources) / page_size))
1576 for i in range(niterations):
1577 if prev_links:
1578 uri = self.get_bare_url(prev_links['previous'])
1579 else:
Ihar Hrachyshka7f79fe62016-06-07 21:23:44 +02001580 pagination_args.update(self.list_kwargs)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001581 uri = self.list_client.build_uri(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001582 self.plural_name, page_reverse=True, **pagination_args)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001583 prev_links, body = self.list_client.get_uri_with_links(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001584 self.plural_name, uri
1585 )
1586 resources_ = self._extract_resources(body)
Béla Vancsicsf1806182016-08-23 07:36:18 +02001587 self.assertGreaterEqual(page_size, len(resources_))
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001588 resources.extend(reversed(resources_))
1589
1590 self.assertSameOrder(expected_resources, reversed(resources))
1591
1592 @_require_pagination
1593 @_require_sorting
1594 def _test_list_pagination_page_reverse_asc(self):
1595 self._test_list_pagination_page_reverse(
1596 direction=constants.SORT_DIRECTION_ASC)
1597
1598 @_require_pagination
1599 @_require_sorting
1600 def _test_list_pagination_page_reverse_desc(self):
1601 self._test_list_pagination_page_reverse(
1602 direction=constants.SORT_DIRECTION_DESC)
1603
1604 def _test_list_pagination_page_reverse(self, direction):
1605 pagination_args = {
1606 'sort_dir': direction,
Armando Migliaccio57581c62016-07-01 10:13:19 -07001607 'sort_key': self.field,
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001608 'limit': 3,
1609 }
1610 body = self.list_method(**pagination_args)
1611 expected_resources = self._extract_resources(body)
1612
1613 pagination_args['limit'] -= 1
1614 pagination_args['marker'] = expected_resources[-1]['id']
1615 pagination_args['page_reverse'] = True
1616 body = self.list_method(**pagination_args)
1617
1618 self.assertSameOrder(
1619 # the last entry is not included in 2nd result when used as a
1620 # marker
1621 expected_resources[:-1],
1622 self._extract_resources(body))
Victor Morales1be97b42016-09-05 08:50:06 -05001623
Hongbin Lu54f55922018-07-12 19:05:39 +00001624 @tutils.requires_ext(extension="filter-validation", service="network")
1625 def _test_list_validation_filters(
1626 self, validation_args, filter_is_valid=True):
1627 if not filter_is_valid:
1628 self.assertRaises(lib_exc.BadRequest, self.list_method,
1629 **validation_args)
1630 else:
1631 body = self.list_method(**validation_args)
1632 resources = self._extract_resources(body)
1633 for resource in resources:
1634 self.assertIn(resource['name'], self.resource_names)