blob: 33a403f867f84f029ec2dce8f42face6f9beb557 [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
Vasyl Saienko19fa0b52021-10-22 14:34:41 +0300109 # NOTE(vsaienko): when using static accounts we need
110 # to fill *_id information like project_id, user_id
111 # by authenticating in keystone
112 cls.client.auth_provider.get_token()
113
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000114 @classmethod
115 def resource_setup(cls):
116 super(BaseNetworkTest, cls).resource_setup()
117
118 cls.networks = []
Miguel Lavalle124378b2016-09-21 16:41:47 -0500119 cls.admin_networks = []
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000120 cls.subnets = []
Kevin Bentonba3651c2017-09-01 17:13:01 -0700121 cls.admin_subnets = []
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000122 cls.ports = []
123 cls.routers = []
124 cls.floating_ips = []
Slawek Kaplonski003fcae2019-05-26 22:38:35 +0200125 cls.port_forwardings = []
Nurmatov Mamatisa3f2bbb52021-10-20 14:33:54 +0300126 cls.local_ips = []
127 cls.local_ip_associations = []
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000128 cls.metering_labels = []
129 cls.service_profiles = []
130 cls.flavors = []
131 cls.metering_label_rules = []
132 cls.qos_rules = []
133 cls.qos_policies = []
134 cls.ethertype = "IPv" + str(cls._ip_version)
Miguel Lavalleb1c7a3d2021-01-31 19:05:22 -0600135 cls.address_groups = []
136 cls.admin_address_groups = []
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000137 cls.address_scopes = []
138 cls.admin_address_scopes = []
139 cls.subnetpools = []
140 cls.admin_subnetpools = []
Itzik Brownbac51dc2016-10-31 12:25:04 +0000141 cls.security_groups = []
Dongcan Ye2de722e2018-07-04 11:01:37 +0000142 cls.admin_security_groups = []
Chandan Kumarc125fd12017-11-15 19:41:01 +0530143 cls.projects = []
Nguyen Phuong An67993fc2017-11-24 11:30:25 +0700144 cls.log_objects = []
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200145 cls.reserved_subnet_cidrs = set()
Federico Ressiab286e42018-06-19 09:52:10 +0200146 cls.keypairs = []
Federico Ressi82e83e32018-07-03 14:19:55 +0200147 cls.trunks = []
Kailun Qineaaf9782018-12-20 04:45:01 +0800148 cls.network_segment_ranges = []
Harald Jensåsc9782fa2019-06-03 22:35:41 +0200149 cls.conntrack_helpers = []
yangjianfeng2936a292022-02-04 11:22:11 +0800150 cls.ndp_proxies = []
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000151
152 @classmethod
yangjianfeng23e40c22020-11-22 08:42:18 +0000153 def reserve_external_subnet_cidrs(cls):
154 client = cls.os_admin.network_client
155 ext_nets = client.list_networks(
156 **{"router:external": True})['networks']
157 for ext_net in ext_nets:
158 ext_subnets = client.list_subnets(
159 network_id=ext_net['id'])['subnets']
160 for ext_subnet in ext_subnets:
161 cls.reserve_subnet_cidr(ext_subnet['cidr'])
162
163 @classmethod
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000164 def resource_cleanup(cls):
165 if CONF.service_available.neutron:
Federico Ressi82e83e32018-07-03 14:19:55 +0200166 # Clean up trunks
167 for trunk in cls.trunks:
168 cls._try_delete_resource(cls.delete_trunk, trunk)
169
yangjianfeng2936a292022-02-04 11:22:11 +0800170 # Clean up ndp proxy
171 for ndp_proxy in cls.ndp_proxies:
172 cls._try_delete_resource(cls.delete_ndp_proxy, ndp_proxy)
173
Slawek Kaplonski003fcae2019-05-26 22:38:35 +0200174 # Clean up port forwardings
175 for pf in cls.port_forwardings:
176 cls._try_delete_resource(cls.delete_port_forwarding, pf)
177
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000178 # Clean up floating IPs
179 for floating_ip in cls.floating_ips:
Federico Ressia69dcd52018-07-06 09:45:34 +0200180 cls._try_delete_resource(cls.delete_floatingip, floating_ip)
181
Nurmatov Mamatisa3f2bbb52021-10-20 14:33:54 +0300182 # Clean up Local IP Associations
183 for association in cls.local_ip_associations:
184 cls._try_delete_resource(cls.delete_local_ip_association,
185 association)
186 # Clean up Local IPs
187 for local_ip in cls.local_ips:
188 cls._try_delete_resource(cls.delete_local_ip,
189 local_ip)
190
Harald Jensåsc9782fa2019-06-03 22:35:41 +0200191 # Clean up conntrack helpers
192 for cth in cls.conntrack_helpers:
193 cls._try_delete_resource(cls.delete_conntrack_helper, cth)
194
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000195 # Clean up routers
196 for router in cls.routers:
197 cls._try_delete_resource(cls.delete_router,
198 router)
199 # Clean up metering label rules
200 for metering_label_rule in cls.metering_label_rules:
201 cls._try_delete_resource(
202 cls.admin_client.delete_metering_label_rule,
203 metering_label_rule['id'])
204 # Clean up metering labels
205 for metering_label in cls.metering_labels:
206 cls._try_delete_resource(
207 cls.admin_client.delete_metering_label,
208 metering_label['id'])
209 # Clean up flavors
210 for flavor in cls.flavors:
211 cls._try_delete_resource(
212 cls.admin_client.delete_flavor,
213 flavor['id'])
214 # Clean up service profiles
215 for service_profile in cls.service_profiles:
216 cls._try_delete_resource(
217 cls.admin_client.delete_service_profile,
218 service_profile['id'])
219 # Clean up ports
220 for port in cls.ports:
221 cls._try_delete_resource(cls.client.delete_port,
222 port['id'])
223 # Clean up subnets
224 for subnet in cls.subnets:
225 cls._try_delete_resource(cls.client.delete_subnet,
226 subnet['id'])
Kevin Bentonba3651c2017-09-01 17:13:01 -0700227 # Clean up admin subnets
228 for subnet in cls.admin_subnets:
229 cls._try_delete_resource(cls.admin_client.delete_subnet,
230 subnet['id'])
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000231 # Clean up networks
232 for network in cls.networks:
Federico Ressi61b564e2018-07-06 08:10:31 +0200233 cls._try_delete_resource(cls.delete_network, network)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000234
Miguel Lavalle124378b2016-09-21 16:41:47 -0500235 # Clean up admin networks
236 for network in cls.admin_networks:
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000237 cls._try_delete_resource(cls.admin_client.delete_network,
238 network['id'])
239
Itzik Brownbac51dc2016-10-31 12:25:04 +0000240 # Clean up security groups
Federico Ressi4c590d72018-10-10 14:01:08 +0200241 for security_group in cls.security_groups:
242 cls._try_delete_resource(cls.delete_security_group,
243 security_group)
Itzik Brownbac51dc2016-10-31 12:25:04 +0000244
Dongcan Ye2de722e2018-07-04 11:01:37 +0000245 # Clean up admin security groups
Federico Ressi4c590d72018-10-10 14:01:08 +0200246 for security_group in cls.admin_security_groups:
247 cls._try_delete_resource(cls.delete_security_group,
248 security_group,
249 client=cls.admin_client)
Dongcan Ye2de722e2018-07-04 11:01:37 +0000250
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000251 for subnetpool in cls.subnetpools:
252 cls._try_delete_resource(cls.client.delete_subnetpool,
253 subnetpool['id'])
254
255 for subnetpool in cls.admin_subnetpools:
256 cls._try_delete_resource(cls.admin_client.delete_subnetpool,
257 subnetpool['id'])
258
259 for address_scope in cls.address_scopes:
260 cls._try_delete_resource(cls.client.delete_address_scope,
261 address_scope['id'])
262
263 for address_scope in cls.admin_address_scopes:
264 cls._try_delete_resource(
265 cls.admin_client.delete_address_scope,
266 address_scope['id'])
267
Chandan Kumarc125fd12017-11-15 19:41:01 +0530268 for project in cls.projects:
269 cls._try_delete_resource(
270 cls.identity_admin_client.delete_project,
271 project['id'])
272
Sławek Kapłońskie100c4d2017-08-23 21:18:34 +0000273 # Clean up QoS rules
274 for qos_rule in cls.qos_rules:
275 cls._try_delete_resource(cls.admin_client.delete_qos_rule,
276 qos_rule['id'])
277 # Clean up QoS policies
278 # as all networks and ports are already removed, QoS policies
279 # shouldn't be "in use"
280 for qos_policy in cls.qos_policies:
281 cls._try_delete_resource(cls.admin_client.delete_qos_policy,
282 qos_policy['id'])
283
Nguyen Phuong An67993fc2017-11-24 11:30:25 +0700284 # Clean up log_objects
285 for log_object in cls.log_objects:
286 cls._try_delete_resource(cls.admin_client.delete_log,
287 log_object['id'])
288
Federico Ressiab286e42018-06-19 09:52:10 +0200289 for keypair in cls.keypairs:
290 cls._try_delete_resource(cls.delete_keypair, keypair)
291
Kailun Qineaaf9782018-12-20 04:45:01 +0800292 # Clean up network_segment_ranges
293 for network_segment_range in cls.network_segment_ranges:
294 cls._try_delete_resource(
295 cls.admin_client.delete_network_segment_range,
296 network_segment_range['id'])
297
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000298 super(BaseNetworkTest, cls).resource_cleanup()
299
300 @classmethod
301 def _try_delete_resource(cls, delete_callable, *args, **kwargs):
302 """Cleanup resources in case of test-failure
303
304 Some resources are explicitly deleted by the test.
305 If the test failed to delete a resource, this method will execute
306 the appropriate delete methods. Otherwise, the method ignores NotFound
307 exceptions thrown for resources that were correctly deleted by the
308 test.
309
310 :param delete_callable: delete method
311 :param args: arguments for delete method
312 :param kwargs: keyword arguments for delete method
313 """
314 try:
315 delete_callable(*args, **kwargs)
316 # if resource is not found, this means it was deleted in the test
317 except lib_exc.NotFound:
318 pass
319
320 @classmethod
Federico Ressi61b564e2018-07-06 08:10:31 +0200321 def create_network(cls, network_name=None, client=None, external=None,
322 shared=None, provider_network_type=None,
323 provider_physical_network=None,
324 provider_segmentation_id=None, **kwargs):
325 """Create a network.
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000326
Federico Ressi61b564e2018-07-06 08:10:31 +0200327 When client is not provider and admin_client is attribute is not None
328 (for example when using BaseAdminNetworkTest base class) and using any
329 of the convenience parameters (external, shared, provider_network_type,
330 provider_physical_network and provider_segmentation_id) it silently
331 uses admin_client. If the network is not shared then it uses the same
332 project_id as regular client.
333
334 :param network_name: Human-readable name of the network
335
336 :param client: client to be used for connecting to network service
337
338 :param external: indicates whether the network has an external routing
339 facility that's not managed by the networking service.
340
341 :param shared: indicates whether this resource is shared across all
342 projects. By default, only administrative users can change this value.
343 If True and admin_client attribute is not None, then the network is
344 created under administrative project.
345
346 :param provider_network_type: the type of physical network that this
347 network should be mapped to. For example, 'flat', 'vlan', 'vxlan', or
348 'gre'. Valid values depend on a networking back-end.
349
350 :param provider_physical_network: the physical network where this
351 network should be implemented. The Networking API v2.0 does not provide
352 a way to list available physical networks. For example, the Open
353 vSwitch plug-in configuration file defines a symbolic name that maps to
354 specific bridges on each compute host.
355
356 :param provider_segmentation_id: The ID of the isolated segment on the
357 physical network. The network_type attribute defines the segmentation
358 model. For example, if the network_type value is 'vlan', this ID is a
359 vlan identifier. If the network_type value is 'gre', this ID is a gre
360 key.
361
362 :param **kwargs: extra parameters to be forwarded to network service
363 """
364
365 name = (network_name or kwargs.pop('name', None) or
366 data_utils.rand_name('test-network-'))
367
368 # translate convenience parameters
369 admin_client_required = False
370 if provider_network_type:
371 admin_client_required = True
372 kwargs['provider:network_type'] = provider_network_type
373 if provider_physical_network:
374 admin_client_required = True
375 kwargs['provider:physical_network'] = provider_physical_network
376 if provider_segmentation_id:
377 admin_client_required = True
378 kwargs['provider:segmentation_id'] = provider_segmentation_id
379 if external is not None:
380 admin_client_required = True
381 kwargs['router:external'] = bool(external)
382 if shared is not None:
383 admin_client_required = True
384 kwargs['shared'] = bool(shared)
385
386 if not client:
387 if admin_client_required and cls.admin_client:
388 # For convenience silently switch to admin client
389 client = cls.admin_client
390 if not shared:
391 # Keep this network visible from current project
392 project_id = (kwargs.get('project_id') or
393 kwargs.get('tenant_id') or
394 cls.client.tenant_id)
395 kwargs.update(project_id=project_id, tenant_id=project_id)
396 else:
397 # Use default client
398 client = cls.client
399
400 network = client.create_network(name=name, **kwargs)['network']
401 network['client'] = client
402 cls.networks.append(network)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000403 return network
404
405 @classmethod
Federico Ressi61b564e2018-07-06 08:10:31 +0200406 def delete_network(cls, network, client=None):
407 client = client or network.get('client') or cls.client
408 client.delete_network(network['id'])
409
410 @classmethod
411 def create_shared_network(cls, network_name=None, **kwargs):
412 return cls.create_network(name=network_name, shared=True, **kwargs)
Miguel Lavalle124378b2016-09-21 16:41:47 -0500413
414 @classmethod
Sławek Kapłońskid98e27d2018-05-07 16:16:28 +0200415 def create_subnet(cls, network, gateway='', cidr=None, mask_bits=None,
Federico Ressi98f20ec2018-05-11 06:09:49 +0200416 ip_version=None, client=None, reserve_cidr=True,
417 **kwargs):
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200418 """Wrapper utility that returns a test subnet.
419
420 Convenient wrapper for client.create_subnet method. It reserves and
421 allocates CIDRs to avoid creating overlapping subnets.
422
423 :param network: network where to create the subnet
424 network['id'] must contain the ID of the network
425
426 :param gateway: gateway IP address
427 It can be a str or a netaddr.IPAddress
428 If gateway is not given, then it will use default address for
429 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 +0200430 if gateway is given as None then no gateway will be assigned
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200431
432 :param cidr: CIDR of the subnet to create
433 It can be either None, a str or a netaddr.IPNetwork instance
434
435 :param mask_bits: CIDR prefix length
436 It can be either None or a numeric value.
437 If cidr parameter is given then mask_bits is used to determinate a
438 sequence of valid CIDR to use as generated.
439 Please see netaddr.IPNetwork.subnet method documentation[1]
440
441 :param ip_version: ip version of generated subnet CIDRs
442 It can be None, IP_VERSION_4 or IP_VERSION_6
443 It has to match given either given CIDR and gateway
444
445 :param ip_version: numeric value (either IP_VERSION_4 or IP_VERSION_6)
446 this value must match CIDR and gateway IP versions if any of them is
447 given
448
449 :param client: client to be used to connect to network service
450
Federico Ressi98f20ec2018-05-11 06:09:49 +0200451 :param reserve_cidr: if True then it reserves assigned CIDR to avoid
452 using the same CIDR for further subnets in the scope of the same
453 test case class
454
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200455 :param **kwargs: optional parameters to be forwarded to wrapped method
456
457 [1] http://netaddr.readthedocs.io/en/latest/tutorial_01.html#supernets-and-subnets # noqa
458 """
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000459
460 # allow tests to use admin client
461 if not client:
462 client = cls.client
463
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200464 if gateway:
465 gateway_ip = netaddr.IPAddress(gateway)
466 if ip_version:
467 if ip_version != gateway_ip.version:
468 raise ValueError(
469 "Gateway IP version doesn't match IP version")
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000470 else:
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200471 ip_version = gateway_ip.version
Sławek Kapłońskid98e27d2018-05-07 16:16:28 +0200472 else:
473 ip_version = ip_version or cls._ip_version
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200474
475 for subnet_cidr in cls.get_subnet_cidrs(
476 ip_version=ip_version, cidr=cidr, mask_bits=mask_bits):
Federico Ressi98f20ec2018-05-11 06:09:49 +0200477 if gateway is not None:
478 kwargs['gateway_ip'] = str(gateway or (subnet_cidr.ip + 1))
Slawek Kaplonski21f53422018-11-02 16:02:09 +0100479 else:
480 kwargs['gateway_ip'] = None
Federico Ressi98f20ec2018-05-11 06:09:49 +0200481 try:
482 body = client.create_subnet(
483 network_id=network['id'],
484 cidr=str(subnet_cidr),
485 ip_version=subnet_cidr.version,
486 **kwargs)
487 break
488 except lib_exc.BadRequest as e:
489 if 'overlaps with another subnet' not in str(e):
490 raise
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000491 else:
492 message = 'Available CIDR for subnet creation could not be found'
493 raise ValueError(message)
494 subnet = body['subnet']
Kevin Bentonba3651c2017-09-01 17:13:01 -0700495 if client is cls.client:
496 cls.subnets.append(subnet)
497 else:
498 cls.admin_subnets.append(subnet)
Federico Ressi98f20ec2018-05-11 06:09:49 +0200499 if reserve_cidr:
500 cls.reserve_subnet_cidr(subnet_cidr)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000501 return subnet
502
503 @classmethod
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200504 def reserve_subnet_cidr(cls, addr, **ipnetwork_kwargs):
505 """Reserve given subnet CIDR making sure it is not used by create_subnet
506
507 :param addr: the CIDR address to be reserved
508 It can be a str or netaddr.IPNetwork instance
509
510 :param **ipnetwork_kwargs: optional netaddr.IPNetwork constructor
511 parameters
512 """
513
514 if not cls.try_reserve_subnet_cidr(addr, **ipnetwork_kwargs):
Vasyl Saienko214943d2023-10-25 12:22:05 +0000515 LOG.info('Subnet CIDR already reserved: {0!r}'.format(
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200516 addr))
517
518 @classmethod
519 def try_reserve_subnet_cidr(cls, addr, **ipnetwork_kwargs):
520 """Reserve given subnet CIDR if it hasn't been reserved before
521
522 :param addr: the CIDR address to be reserved
523 It can be a str or netaddr.IPNetwork instance
524
525 :param **ipnetwork_kwargs: optional netaddr.IPNetwork constructor
526 parameters
527
528 :return: True if it wasn't reserved before, False elsewhere.
529 """
530
531 subnet_cidr = netaddr.IPNetwork(addr, **ipnetwork_kwargs)
532 if subnet_cidr in cls.reserved_subnet_cidrs:
533 return False
534 else:
535 cls.reserved_subnet_cidrs.add(subnet_cidr)
536 return True
537
538 @classmethod
539 def get_subnet_cidrs(
540 cls, cidr=None, mask_bits=None, ip_version=None):
541 """Iterate over a sequence of unused subnet CIDR for IP version
542
543 :param cidr: CIDR of the subnet to create
544 It can be either None, a str or a netaddr.IPNetwork instance
545
546 :param mask_bits: CIDR prefix length
547 It can be either None or a numeric value.
548 If cidr parameter is given then mask_bits is used to determinate a
549 sequence of valid CIDR to use as generated.
550 Please see netaddr.IPNetwork.subnet method documentation[1]
551
552 :param ip_version: ip version of generated subnet CIDRs
553 It can be None, IP_VERSION_4 or IP_VERSION_6
554 It has to match given CIDR if given
555
556 :return: iterator over reserved CIDRs of type netaddr.IPNetwork
557
558 [1] http://netaddr.readthedocs.io/en/latest/tutorial_01.html#supernets-and-subnets # noqa
559 """
560
561 if cidr:
562 # Generate subnet CIDRs starting from given CIDR
563 # checking it is of requested IP version
564 cidr = netaddr.IPNetwork(cidr, version=ip_version)
565 else:
566 # Generate subnet CIDRs starting from configured values
567 ip_version = ip_version or cls._ip_version
568 if ip_version == const.IP_VERSION_4:
569 mask_bits = mask_bits or config.safe_get_config_value(
570 'network', 'project_network_mask_bits')
571 cidr = netaddr.IPNetwork(config.safe_get_config_value(
572 'network', 'project_network_cidr'))
573 elif ip_version == const.IP_VERSION_6:
574 mask_bits = config.safe_get_config_value(
575 'network', 'project_network_v6_mask_bits')
576 cidr = netaddr.IPNetwork(config.safe_get_config_value(
577 'network', 'project_network_v6_cidr'))
578 else:
579 raise ValueError('Invalid IP version: {!r}'.format(ip_version))
580
581 if mask_bits:
582 subnet_cidrs = cidr.subnet(mask_bits)
583 else:
584 subnet_cidrs = iter([cidr])
585
586 for subnet_cidr in subnet_cidrs:
587 if subnet_cidr not in cls.reserved_subnet_cidrs:
588 yield subnet_cidr
589
590 @classmethod
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000591 def create_port(cls, network, **kwargs):
592 """Wrapper utility that returns a test port."""
Edan Davidd75e48e2018-01-03 02:49:52 -0500593 if CONF.network.port_vnic_type and 'binding:vnic_type' not in kwargs:
594 kwargs['binding:vnic_type'] = CONF.network.port_vnic_type
Glenn Van de Water5d9b1402020-09-16 15:14:14 +0200595 if CONF.network.port_profile and 'binding:profile' not in kwargs:
596 kwargs['binding:profile'] = CONF.network.port_profile
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000597 body = cls.client.create_port(network_id=network['id'],
598 **kwargs)
599 port = body['port']
600 cls.ports.append(port)
601 return port
602
603 @classmethod
604 def update_port(cls, port, **kwargs):
605 """Wrapper utility that updates a test port."""
606 body = cls.client.update_port(port['id'],
607 **kwargs)
608 return body['port']
609
610 @classmethod
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300611 def _create_router_with_client(
612 cls, client, router_name=None, admin_state_up=False,
613 external_network_id=None, enable_snat=None, **kwargs
614 ):
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000615 ext_gw_info = {}
616 if external_network_id:
617 ext_gw_info['network_id'] = external_network_id
YAMAMOTO Takashi9bd4f972017-06-20 12:49:30 +0900618 if enable_snat is not None:
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000619 ext_gw_info['enable_snat'] = enable_snat
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300620 body = client.create_router(
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000621 router_name, external_gateway_info=ext_gw_info,
622 admin_state_up=admin_state_up, **kwargs)
623 router = body['router']
624 cls.routers.append(router)
625 return router
626
627 @classmethod
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300628 def create_router(cls, *args, **kwargs):
629 return cls._create_router_with_client(cls.client, *args, **kwargs)
630
631 @classmethod
632 def create_admin_router(cls, *args, **kwargs):
rajat294495c042017-06-28 15:37:16 +0530633 return cls._create_router_with_client(cls.os_admin.network_client,
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300634 *args, **kwargs)
635
636 @classmethod
Federico Ressia69dcd52018-07-06 09:45:34 +0200637 def create_floatingip(cls, external_network_id=None, port=None,
638 client=None, **kwargs):
639 """Creates a floating IP.
640
641 Create a floating IP and schedule it for later deletion.
642 If a client is passed, then it is used for deleting the IP too.
643
644 :param external_network_id: network ID where to create
645 By default this is 'CONF.network.public_network_id'.
646
647 :param port: port to bind floating IP to
648 This is translated to 'port_id=port['id']'
649 By default it is None.
650
651 :param client: network client to be used for creating and cleaning up
652 the floating IP.
653
654 :param **kwargs: additional creation parameters to be forwarded to
655 networking server.
656 """
657
658 client = client or cls.client
659 external_network_id = (external_network_id or
660 cls.external_network_id)
661
662 if port:
Federico Ressi47f6ae42018-09-24 16:19:14 +0200663 port_id = kwargs.setdefault('port_id', port['id'])
664 if port_id != port['id']:
665 message = "Port ID specified twice: {!s} != {!s}".format(
666 port_id, port['id'])
667 raise ValueError(message)
Federico Ressia69dcd52018-07-06 09:45:34 +0200668
669 fip = client.create_floatingip(external_network_id,
670 **kwargs)['floatingip']
671
672 # save client to be used later in cls.delete_floatingip
673 # for final cleanup
674 fip['client'] = client
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000675 cls.floating_ips.append(fip)
676 return fip
677
678 @classmethod
Federico Ressia69dcd52018-07-06 09:45:34 +0200679 def delete_floatingip(cls, floating_ip, client=None):
680 """Delete floating IP
681
682 :param client: Client to be used
683 If client is not given it will use the client used to create
684 the floating IP, or cls.client if unknown.
685 """
686
687 client = client or floating_ip.get('client') or cls.client
688 client.delete_floatingip(floating_ip['id'])
689
690 @classmethod
Slawek Kaplonski003fcae2019-05-26 22:38:35 +0200691 def create_port_forwarding(cls, fip_id, internal_port_id,
692 internal_port, external_port,
693 internal_ip_address=None, protocol="tcp",
694 client=None):
695 """Creates a port forwarding.
696
697 Create a port forwarding and schedule it for later deletion.
698 If a client is passed, then it is used for deleting the PF too.
699
700 :param fip_id: The ID of the floating IP address.
701
702 :param internal_port_id: The ID of the Neutron port associated to
703 the floating IP port forwarding.
704
705 :param internal_port: The TCP/UDP/other protocol port number of the
706 Neutron port fixed IP address associated to the floating ip
707 port forwarding.
708
709 :param external_port: The TCP/UDP/other protocol port number of
710 the port forwarding floating IP address.
711
712 :param internal_ip_address: The fixed IPv4 address of the Neutron
713 port associated to the floating IP port forwarding.
714
715 :param protocol: The IP protocol used in the floating IP port
716 forwarding.
717
718 :param client: network client to be used for creating and cleaning up
719 the floating IP port forwarding.
720 """
721
722 client = client or cls.client
723
724 pf = client.create_port_forwarding(
725 fip_id, internal_port_id, internal_port, external_port,
726 internal_ip_address, protocol)['port_forwarding']
727
728 # save ID of floating IP associated with port forwarding for final
729 # cleanup
730 pf['floatingip_id'] = fip_id
731
732 # save client to be used later in cls.delete_port_forwarding
733 # for final cleanup
734 pf['client'] = client
735 cls.port_forwardings.append(pf)
736 return pf
737
738 @classmethod
Flavio Fernandesa1952c62020-10-02 06:39:08 -0400739 def update_port_forwarding(cls, fip_id, pf_id, client=None, **kwargs):
740 """Wrapper utility for update_port_forwarding."""
741 client = client or cls.client
742 return client.update_port_forwarding(fip_id, pf_id, **kwargs)
743
744 @classmethod
Slawek Kaplonski003fcae2019-05-26 22:38:35 +0200745 def delete_port_forwarding(cls, pf, client=None):
746 """Delete port forwarding
747
748 :param client: Client to be used
749 If client is not given it will use the client used to create
750 the port forwarding, or cls.client if unknown.
751 """
752
753 client = client or pf.get('client') or cls.client
754 client.delete_port_forwarding(pf['floatingip_id'], pf['id'])
755
Nurmatov Mamatisa3f2bbb52021-10-20 14:33:54 +0300756 def create_local_ip(cls, network_id=None,
757 client=None, **kwargs):
758 """Creates a Local IP.
759
760 Create a Local IP and schedule it for later deletion.
761 If a client is passed, then it is used for deleting the IP too.
762
763 :param network_id: network ID where to create
764 By default this is 'CONF.network.public_network_id'.
765
766 :param client: network client to be used for creating and cleaning up
767 the Local IP.
768
769 :param **kwargs: additional creation parameters to be forwarded to
770 networking server.
771 """
772
773 client = client or cls.client
774 network_id = (network_id or
775 cls.external_network_id)
776
777 local_ip = client.create_local_ip(network_id,
778 **kwargs)['local_ip']
779
780 # save client to be used later in cls.delete_local_ip
781 # for final cleanup
782 local_ip['client'] = client
783 cls.local_ips.append(local_ip)
784 return local_ip
785
786 @classmethod
787 def delete_local_ip(cls, local_ip, client=None):
788 """Delete Local IP
789
790 :param client: Client to be used
791 If client is not given it will use the client used to create
792 the Local IP, or cls.client if unknown.
793 """
794
795 client = client or local_ip.get('client') or cls.client
796 client.delete_local_ip(local_ip['id'])
797
798 @classmethod
799 def create_local_ip_association(cls, local_ip_id, fixed_port_id,
800 fixed_ip_address=None, client=None):
801 """Creates a Local IP association.
802
803 Create a Local IP Association and schedule it for later deletion.
804 If a client is passed, then it is used for deleting the association
805 too.
806
807 :param local_ip_id: The ID of the Local IP.
808
809 :param fixed_port_id: The ID of the Neutron port
810 to be associated with the Local IP
811
812 :param fixed_ip_address: The fixed IPv4 address of the Neutron
813 port to be associated with the Local IP
814
815 :param client: network client to be used for creating and cleaning up
816 the Local IP Association.
817 """
818
819 client = client or cls.client
820
821 association = client.create_local_ip_association(
822 local_ip_id, fixed_port_id,
823 fixed_ip_address)['port_association']
824
825 # save ID of Local IP for final cleanup
826 association['local_ip_id'] = local_ip_id
827
828 # save client to be used later in
829 # cls.delete_local_ip_association for final cleanup
830 association['client'] = client
831 cls.local_ip_associations.append(association)
832 return association
833
834 @classmethod
835 def delete_local_ip_association(cls, association, client=None):
836
837 """Delete Local IP Association
838
839 :param client: Client to be used
840 If client is not given it will use the client used to create
841 the local IP association, or cls.client if unknown.
842 """
843
844 client = client or association.get('client') or cls.client
845 client.delete_local_ip_association(association['local_ip_id'],
846 association['fixed_port_id'])
847
Slawek Kaplonski003fcae2019-05-26 22:38:35 +0200848 @classmethod
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000849 def create_router_interface(cls, router_id, subnet_id):
850 """Wrapper utility that returns a router interface."""
851 interface = cls.client.add_router_interface_with_subnet_id(
852 router_id, subnet_id)
853 return interface
854
855 @classmethod
Bence Romsics46bd3af2019-09-13 10:52:41 +0200856 def add_extra_routes_atomic(cls, *args, **kwargs):
857 return cls.client.add_extra_routes_atomic(*args, **kwargs)
858
859 @classmethod
860 def remove_extra_routes_atomic(cls, *args, **kwargs):
861 return cls.client.remove_extra_routes_atomic(*args, **kwargs)
862
863 @classmethod
Sławek Kapłońskiff294062016-12-04 15:00:54 +0000864 def get_supported_qos_rule_types(cls):
865 body = cls.client.list_qos_rule_types()
866 return [rule_type['type'] for rule_type in body['rule_types']]
867
868 @classmethod
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200869 def create_qos_policy(cls, name, description=None, shared=False,
Rodolfo Alonso Hernandeze2d062f2020-01-14 17:11:42 +0000870 project_id=None, is_default=False):
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000871 """Wrapper utility that returns a test QoS policy."""
872 body = cls.admin_client.create_qos_policy(
Rodolfo Alonso Hernandeze2d062f2020-01-14 17:11:42 +0000873 name, description, shared, project_id, is_default)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000874 qos_policy = body['policy']
875 cls.qos_policies.append(qos_policy)
876 return qos_policy
877
878 @classmethod
elajkatdbb0b482021-05-04 17:20:07 +0200879 def create_qos_dscp_marking_rule(cls, policy_id, dscp_mark):
880 """Wrapper utility that creates and returns a QoS dscp rule."""
881 body = cls.admin_client.create_dscp_marking_rule(
882 policy_id, dscp_mark)
883 qos_rule = body['dscp_marking_rule']
884 cls.qos_rules.append(qos_rule)
885 return qos_rule
886
887 @classmethod
Jakub Libosvar83704832017-12-06 16:02:28 +0000888 def delete_router(cls, router, client=None):
889 client = client or cls.client
Aditya Vaja49819a72018-11-26 14:20:10 -0800890 if 'routes' in router:
891 client.remove_router_extra_routes(router['id'])
Jakub Libosvar83704832017-12-06 16:02:28 +0000892 body = client.list_router_interfaces(router['id'])
Chandan Kumarc125fd12017-11-15 19:41:01 +0530893 interfaces = [port for port in body['ports']
894 if port['device_owner'] in const.ROUTER_INTERFACE_OWNERS]
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000895 for i in interfaces:
896 try:
Jakub Libosvar83704832017-12-06 16:02:28 +0000897 client.remove_router_interface_with_subnet_id(
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000898 router['id'], i['fixed_ips'][0]['subnet_id'])
899 except lib_exc.NotFound:
900 pass
Jakub Libosvar83704832017-12-06 16:02:28 +0000901 client.delete_router(router['id'])
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000902
903 @classmethod
904 def create_address_scope(cls, name, is_admin=False, **kwargs):
905 if is_admin:
906 body = cls.admin_client.create_address_scope(name=name, **kwargs)
907 cls.admin_address_scopes.append(body['address_scope'])
908 else:
909 body = cls.client.create_address_scope(name=name, **kwargs)
910 cls.address_scopes.append(body['address_scope'])
911 return body['address_scope']
912
913 @classmethod
Igor Malinovskiyb80f1d02020-03-06 13:39:52 +0200914 def create_subnetpool(cls, name, is_admin=False, client=None, **kwargs):
915 if client is None:
916 client = cls.admin_client if is_admin else cls.client
917
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000918 if is_admin:
Igor Malinovskiyb80f1d02020-03-06 13:39:52 +0200919 body = client.create_subnetpool(name, **kwargs)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000920 cls.admin_subnetpools.append(body['subnetpool'])
921 else:
Igor Malinovskiyb80f1d02020-03-06 13:39:52 +0200922 body = client.create_subnetpool(name, **kwargs)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000923 cls.subnetpools.append(body['subnetpool'])
924 return body['subnetpool']
925
Chandan Kumarc125fd12017-11-15 19:41:01 +0530926 @classmethod
Miguel Lavalleb1c7a3d2021-01-31 19:05:22 -0600927 def create_address_group(cls, name, is_admin=False, **kwargs):
928 if is_admin:
929 body = cls.admin_client.create_address_group(name=name, **kwargs)
930 cls.admin_address_groups.append(body['address_group'])
931 else:
932 body = cls.client.create_address_group(name=name, **kwargs)
933 cls.address_groups.append(body['address_group'])
934 return body['address_group']
935
936 @classmethod
Chandan Kumarc125fd12017-11-15 19:41:01 +0530937 def create_project(cls, name=None, description=None):
938 test_project = name or data_utils.rand_name('test_project_')
939 test_description = description or data_utils.rand_name('desc_')
940 project = cls.identity_admin_client.create_project(
941 name=test_project,
942 description=test_description)['project']
943 cls.projects.append(project)
Dongcan Ye2de722e2018-07-04 11:01:37 +0000944 # Create a project will create a default security group.
Dongcan Ye2de722e2018-07-04 11:01:37 +0000945 sgs_list = cls.admin_client.list_security_groups(
946 tenant_id=project['id'])['security_groups']
Federico Ressi4c590d72018-10-10 14:01:08 +0200947 for security_group in sgs_list:
948 # Make sure delete_security_group method will use
949 # the admin client for this group
950 security_group['client'] = cls.admin_client
951 cls.security_groups.append(security_group)
Chandan Kumarc125fd12017-11-15 19:41:01 +0530952 return project
953
954 @classmethod
Federico Ressi4c590d72018-10-10 14:01:08 +0200955 def create_security_group(cls, name=None, project=None, client=None,
956 **kwargs):
957 if project:
958 client = client or cls.admin_client
959 project_id = kwargs.setdefault('project_id', project['id'])
960 tenant_id = kwargs.setdefault('tenant_id', project['id'])
961 if project_id != project['id'] or tenant_id != project['id']:
962 raise ValueError('Project ID specified multiple times')
963 else:
964 client = client or cls.client
965
966 name = name or data_utils.rand_name(cls.__name__)
967 security_group = client.create_security_group(name=name, **kwargs)[
968 'security_group']
969 security_group['client'] = client
970 cls.security_groups.append(security_group)
971 return security_group
972
973 @classmethod
974 def delete_security_group(cls, security_group, client=None):
975 client = client or security_group.get('client') or cls.client
976 client.delete_security_group(security_group['id'])
977
978 @classmethod
979 def create_security_group_rule(cls, security_group=None, project=None,
980 client=None, ip_version=None, **kwargs):
981 if project:
982 client = client or cls.admin_client
983 project_id = kwargs.setdefault('project_id', project['id'])
984 tenant_id = kwargs.setdefault('tenant_id', project['id'])
985 if project_id != project['id'] or tenant_id != project['id']:
986 raise ValueError('Project ID specified multiple times')
987
988 if 'security_group_id' not in kwargs:
989 security_group = (security_group or
990 cls.get_security_group(client=client))
991
992 if security_group:
993 client = client or security_group.get('client')
994 security_group_id = kwargs.setdefault('security_group_id',
995 security_group['id'])
996 if security_group_id != security_group['id']:
997 raise ValueError('Security group ID specified multiple times.')
998
999 ip_version = ip_version or cls._ip_version
1000 default_params = (
1001 constants.DEFAULT_SECURITY_GROUP_RULE_PARAMS[ip_version])
Slawek Kaplonski83979b92022-12-15 14:15:12 +01001002 if (('remote_address_group_id' in kwargs or
1003 'remote_group_id' in kwargs) and
1004 'remote_ip_prefix' in default_params):
Miguel Lavalleb1c7a3d2021-01-31 19:05:22 -06001005 default_params.pop('remote_ip_prefix')
Federico Ressi4c590d72018-10-10 14:01:08 +02001006 for key, value in default_params.items():
1007 kwargs.setdefault(key, value)
1008
1009 client = client or cls.client
1010 return client.create_security_group_rule(**kwargs)[
1011 'security_group_rule']
1012
1013 @classmethod
1014 def get_security_group(cls, name='default', client=None):
1015 client = client or cls.client
1016 security_groups = client.list_security_groups()['security_groups']
1017 for security_group in security_groups:
1018 if security_group['name'] == name:
1019 return security_group
1020 raise ValueError("No such security group named {!r}".format(name))
Chandan Kumarc125fd12017-11-15 19:41:01 +05301021
Federico Ressiab286e42018-06-19 09:52:10 +02001022 @classmethod
1023 def create_keypair(cls, client=None, name=None, **kwargs):
1024 client = client or cls.os_primary.keypairs_client
1025 name = name or data_utils.rand_name('keypair-test')
1026 keypair = client.create_keypair(name=name, **kwargs)['keypair']
1027
1028 # save client for later cleanup
1029 keypair['client'] = client
1030 cls.keypairs.append(keypair)
1031 return keypair
1032
1033 @classmethod
1034 def delete_keypair(cls, keypair, client=None):
1035 client = (client or keypair.get('client') or
1036 cls.os_primary.keypairs_client)
1037 client.delete_keypair(keypair_name=keypair['name'])
1038
Federico Ressi82e83e32018-07-03 14:19:55 +02001039 @classmethod
1040 def create_trunk(cls, port=None, subports=None, client=None, **kwargs):
1041 """Create network trunk
1042
1043 :param port: dictionary containing parent port ID (port['id'])
1044 :param client: client to be used for connecting to networking service
1045 :param **kwargs: extra parameters to be forwarded to network service
1046
1047 :returns: dictionary containing created trunk details
1048 """
1049 client = client or cls.client
1050
1051 if port:
1052 kwargs['port_id'] = port['id']
1053
1054 trunk = client.create_trunk(subports=subports, **kwargs)['trunk']
1055 # Save client reference for later deletion
1056 trunk['client'] = client
1057 cls.trunks.append(trunk)
1058 return trunk
1059
1060 @classmethod
Huifeng Le1c9f40b2018-11-07 01:14:21 +08001061 def delete_trunk(cls, trunk, client=None, detach_parent_port=True):
Federico Ressi82e83e32018-07-03 14:19:55 +02001062 """Delete network trunk
1063
1064 :param trunk: dictionary containing trunk ID (trunk['id'])
1065
1066 :param client: client to be used for connecting to networking service
1067 """
1068 client = client or trunk.get('client') or cls.client
1069 trunk.update(client.show_trunk(trunk['id'])['trunk'])
1070
1071 if not trunk['admin_state_up']:
1072 # Cannot touch trunk before admin_state_up is True
1073 client.update_trunk(trunk['id'], admin_state_up=True)
1074 if trunk['sub_ports']:
1075 # Removes trunk ports before deleting it
1076 cls._try_delete_resource(client.remove_subports, trunk['id'],
1077 trunk['sub_ports'])
1078
1079 # we have to detach the interface from the server before
1080 # the trunk can be deleted.
1081 parent_port = {'id': trunk['port_id']}
1082
1083 def is_parent_port_detached():
1084 parent_port.update(client.show_port(parent_port['id'])['port'])
1085 return not parent_port['device_id']
1086
Huifeng Le1c9f40b2018-11-07 01:14:21 +08001087 if detach_parent_port and not is_parent_port_detached():
Federico Ressi82e83e32018-07-03 14:19:55 +02001088 # this could probably happen when trunk is deleted and parent port
1089 # has been assigned to a VM that is still running. Here we are
1090 # assuming that device_id points to such VM.
1091 cls.os_primary.compute.InterfacesClient().delete_interface(
1092 parent_port['device_id'], parent_port['id'])
1093 utils.wait_until_true(is_parent_port_detached)
1094
1095 client.delete_trunk(trunk['id'])
1096
Harald Jensåsc9782fa2019-06-03 22:35:41 +02001097 @classmethod
1098 def create_conntrack_helper(cls, router_id, helper, protocol, port,
1099 client=None):
1100 """Create a conntrack helper
1101
1102 Create a conntrack helper and schedule it for later deletion. If a
1103 client is passed, then it is used for deleteing the CTH too.
1104
1105 :param router_id: The ID of the Neutron router associated to the
1106 conntrack helper.
1107
1108 :param helper: The conntrack helper module alias
1109
1110 :param protocol: The conntrack helper IP protocol used in the conntrack
1111 helper.
1112
1113 :param port: The conntrack helper IP protocol port number for the
1114 conntrack helper.
1115
1116 :param client: network client to be used for creating and cleaning up
1117 the conntrack helper.
1118 """
1119
1120 client = client or cls.client
1121
1122 cth = client.create_conntrack_helper(router_id, helper, protocol,
1123 port)['conntrack_helper']
1124
1125 # save ID of router associated with conntrack helper for final cleanup
1126 cth['router_id'] = router_id
1127
1128 # save client to be used later in cls.delete_conntrack_helper for final
1129 # cleanup
1130 cth['client'] = client
1131 cls.conntrack_helpers.append(cth)
1132 return cth
1133
1134 @classmethod
1135 def delete_conntrack_helper(cls, cth, client=None):
1136 """Delete conntrack helper
1137
1138 :param client: Client to be used
1139 If client is not given it will use the client used to create the
1140 conntrack helper, or cls.client if unknown.
1141 """
1142
1143 client = client or cth.get('client') or cls.client
1144 client.delete_conntrack_helper(cth['router_id'], cth['id'])
1145
yangjianfeng2936a292022-02-04 11:22:11 +08001146 @classmethod
1147 def create_ndp_proxy(cls, router_id, port_id, client=None, **kwargs):
1148 """Creates a ndp proxy.
1149
1150 Create a ndp proxy and schedule it for later deletion.
1151 If a client is passed, then it is used for deleting the NDP proxy too.
1152
1153 :param router_id: router ID where to create the ndp proxy.
1154
1155 :param port_id: port ID which the ndp proxy associate with
1156
1157 :param client: network client to be used for creating and cleaning up
1158 the ndp proxy.
1159
1160 :param **kwargs: additional creation parameters to be forwarded to
1161 networking server.
1162 """
1163 client = client or cls.client
1164
1165 data = {'router_id': router_id, 'port_id': port_id}
1166 if kwargs:
1167 data.update(kwargs)
1168 ndp_proxy = client.create_ndp_proxy(**data)['ndp_proxy']
1169
1170 # save client to be used later in cls.delete_ndp_proxy
1171 # for final cleanup
1172 ndp_proxy['client'] = client
1173 cls.ndp_proxies.append(ndp_proxy)
1174 return ndp_proxy
1175
1176 @classmethod
1177 def delete_ndp_proxy(cls, ndp_proxy, client=None):
1178 """Delete ndp proxy
1179
1180 :param client: Client to be used
1181 If client is not given it will use the client used to create
1182 the ndp proxy, or cls.client if unknown.
1183 """
1184 client = client or ndp_proxy.get('client') or cls.client
1185 client.delete_ndp_proxy(ndp_proxy['id'])
1186
Daniel Mellado3c0aeab2016-01-29 11:30:25 +00001187
1188class BaseAdminNetworkTest(BaseNetworkTest):
1189
1190 credentials = ['primary', 'admin']
1191
1192 @classmethod
1193 def setup_clients(cls):
1194 super(BaseAdminNetworkTest, cls).setup_clients()
fumihiko kakumaa216fc12017-07-14 10:43:29 +09001195 cls.admin_client = cls.os_admin.network_client
Michael Polenchuk60d02912022-09-05 13:29:57 +04001196 cls.admin_client.auth_provider.get_token()
Jakub Libosvarf5758012017-08-15 13:45:30 +00001197 cls.identity_admin_client = cls.os_admin.projects_client
Daniel Mellado3c0aeab2016-01-29 11:30:25 +00001198
1199 @classmethod
1200 def create_metering_label(cls, name, description):
1201 """Wrapper utility that returns a test metering label."""
1202 body = cls.admin_client.create_metering_label(
1203 description=description,
1204 name=data_utils.rand_name("metering-label"))
1205 metering_label = body['metering_label']
1206 cls.metering_labels.append(metering_label)
1207 return metering_label
1208
1209 @classmethod
1210 def create_metering_label_rule(cls, remote_ip_prefix, direction,
1211 metering_label_id):
1212 """Wrapper utility that returns a test metering label rule."""
1213 body = cls.admin_client.create_metering_label_rule(
1214 remote_ip_prefix=remote_ip_prefix, direction=direction,
1215 metering_label_id=metering_label_id)
1216 metering_label_rule = body['metering_label_rule']
1217 cls.metering_label_rules.append(metering_label_rule)
1218 return metering_label_rule
1219
1220 @classmethod
Kailun Qineaaf9782018-12-20 04:45:01 +08001221 def create_network_segment_range(cls, name, shared,
1222 project_id, network_type,
1223 physical_network, minimum,
1224 maximum):
1225 """Wrapper utility that returns a test network segment range."""
1226 network_segment_range_args = {'name': name,
1227 'shared': shared,
1228 'project_id': project_id,
1229 'network_type': network_type,
1230 'physical_network': physical_network,
1231 'minimum': minimum,
1232 'maximum': maximum}
1233 body = cls.admin_client.create_network_segment_range(
1234 **network_segment_range_args)
1235 network_segment_range = body['network_segment_range']
1236 cls.network_segment_ranges.append(network_segment_range)
1237 return network_segment_range
1238
1239 @classmethod
Daniel Mellado3c0aeab2016-01-29 11:30:25 +00001240 def create_flavor(cls, name, description, service_type):
1241 """Wrapper utility that returns a test flavor."""
1242 body = cls.admin_client.create_flavor(
1243 description=description, service_type=service_type,
1244 name=name)
1245 flavor = body['flavor']
1246 cls.flavors.append(flavor)
1247 return flavor
1248
1249 @classmethod
1250 def create_service_profile(cls, description, metainfo, driver):
1251 """Wrapper utility that returns a test service profile."""
1252 body = cls.admin_client.create_service_profile(
1253 driver=driver, metainfo=metainfo, description=description)
1254 service_profile = body['service_profile']
1255 cls.service_profiles.append(service_profile)
1256 return service_profile
1257
1258 @classmethod
Nguyen Phuong An67993fc2017-11-24 11:30:25 +07001259 def create_log(cls, name, description=None,
1260 resource_type='security_group', resource_id=None,
1261 target_id=None, event='ALL', enabled=True):
1262 """Wrapper utility that returns a test log object."""
1263 log_args = {'name': name,
Nguyen Phuong An67993fc2017-11-24 11:30:25 +07001264 'resource_type': resource_type,
1265 'resource_id': resource_id,
1266 'target_id': target_id,
1267 'event': event,
1268 'enabled': enabled}
Slawek Kaplonskid9fe3022021-08-11 15:25:16 +02001269 if description:
1270 log_args['description'] = description
Nguyen Phuong An67993fc2017-11-24 11:30:25 +07001271 body = cls.admin_client.create_log(**log_args)
1272 log_object = body['log']
1273 cls.log_objects.append(log_object)
1274 return log_object
1275
1276 @classmethod
Daniel Mellado3c0aeab2016-01-29 11:30:25 +00001277 def get_unused_ip(cls, net_id, ip_version=None):
Gary Kotton011345f2016-06-15 08:04:31 -07001278 """Get an unused ip address in a allocation pool of net"""
Daniel Mellado3c0aeab2016-01-29 11:30:25 +00001279 body = cls.admin_client.list_ports(network_id=net_id)
1280 ports = body['ports']
1281 used_ips = []
1282 for port in ports:
1283 used_ips.extend(
1284 [fixed_ip['ip_address'] for fixed_ip in port['fixed_ips']])
1285 body = cls.admin_client.list_subnets(network_id=net_id)
1286 subnets = body['subnets']
1287
1288 for subnet in subnets:
1289 if ip_version and subnet['ip_version'] != ip_version:
1290 continue
1291 cidr = subnet['cidr']
1292 allocation_pools = subnet['allocation_pools']
1293 iterators = []
1294 if allocation_pools:
1295 for allocation_pool in allocation_pools:
1296 iterators.append(netaddr.iter_iprange(
1297 allocation_pool['start'], allocation_pool['end']))
1298 else:
1299 net = netaddr.IPNetwork(cidr)
1300
1301 def _iterip():
1302 for ip in net:
1303 if ip not in (net.network, net.broadcast):
1304 yield ip
1305 iterators.append(iter(_iterip()))
1306
1307 for iterator in iterators:
1308 for ip in iterator:
1309 if str(ip) not in used_ips:
1310 return str(ip)
1311
1312 message = (
1313 "net(%s) has no usable IP address in allocation pools" % net_id)
1314 raise exceptions.InvalidConfiguration(message)
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001315
Lajos Katona2f904652018-08-23 14:04:56 +02001316 @classmethod
1317 def create_provider_network(cls, physnet_name, start_segmentation_id,
1318 max_attempts=30):
1319 segmentation_id = start_segmentation_id
Lajos Katona7eb67252019-01-14 12:55:35 +01001320 for attempts in range(max_attempts):
Lajos Katona2f904652018-08-23 14:04:56 +02001321 try:
Lajos Katona7eb67252019-01-14 12:55:35 +01001322 return cls.create_network(
Lajos Katona2f904652018-08-23 14:04:56 +02001323 name=data_utils.rand_name('test_net'),
1324 shared=True,
1325 provider_network_type='vlan',
1326 provider_physical_network=physnet_name,
1327 provider_segmentation_id=segmentation_id)
Lajos Katona2f904652018-08-23 14:04:56 +02001328 except lib_exc.Conflict:
Lajos Katona2f904652018-08-23 14:04:56 +02001329 segmentation_id += 1
1330 if segmentation_id > 4095:
1331 raise lib_exc.TempestException(
1332 "No free segmentation id was found for provider "
1333 "network creation!")
1334 time.sleep(CONF.network.build_interval)
Lajos Katona7eb67252019-01-14 12:55:35 +01001335 LOG.exception("Failed to create provider network after "
1336 "%d attempts", max_attempts)
1337 raise lib_exc.TimeoutException
Lajos Katona2f904652018-08-23 14:04:56 +02001338
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001339
Sławek Kapłońskiff294062016-12-04 15:00:54 +00001340def require_qos_rule_type(rule_type):
1341 def decorator(f):
1342 @functools.wraps(f)
1343 def wrapper(self, *func_args, **func_kwargs):
1344 if rule_type not in self.get_supported_qos_rule_types():
1345 raise self.skipException(
1346 "%s rule type is required." % rule_type)
1347 return f(self, *func_args, **func_kwargs)
1348 return wrapper
1349 return decorator
1350
1351
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001352def _require_sorting(f):
1353 @functools.wraps(f)
1354 def inner(self, *args, **kwargs):
Chandan Kumarc125fd12017-11-15 19:41:01 +05301355 if not tutils.is_extension_enabled("sorting", "network"):
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001356 self.skipTest('Sorting feature is required')
1357 return f(self, *args, **kwargs)
1358 return inner
1359
1360
1361def _require_pagination(f):
1362 @functools.wraps(f)
1363 def inner(self, *args, **kwargs):
Chandan Kumarc125fd12017-11-15 19:41:01 +05301364 if not tutils.is_extension_enabled("pagination", "network"):
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001365 self.skipTest('Pagination feature is required')
1366 return f(self, *args, **kwargs)
1367 return inner
1368
1369
1370class BaseSearchCriteriaTest(BaseNetworkTest):
1371
1372 # This should be defined by subclasses to reflect resource name to test
1373 resource = None
1374
Armando Migliaccio57581c62016-07-01 10:13:19 -07001375 field = 'name'
1376
Ihar Hrachyshkaa8fe5a12016-05-24 14:50:58 +02001377 # NOTE(ihrachys): some names, like those starting with an underscore (_)
1378 # are sorted differently depending on whether the plugin implements native
1379 # sorting support, or not. So we avoid any such cases here, sticking to
1380 # alphanumeric. Also test a case when there are multiple resources with the
1381 # same name
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001382 resource_names = ('test1', 'abc1', 'test10', '123test') + ('test1',)
1383
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001384 force_tenant_isolation = True
1385
Ihar Hrachyshkaa8fe5a12016-05-24 14:50:58 +02001386 list_kwargs = {}
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001387
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001388 list_as_admin = False
1389
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001390 def assertSameOrder(self, original, actual):
1391 # gracefully handle iterators passed
1392 original = list(original)
1393 actual = list(actual)
1394 self.assertEqual(len(original), len(actual))
1395 for expected, res in zip(original, actual):
Armando Migliaccio57581c62016-07-01 10:13:19 -07001396 self.assertEqual(expected[self.field], res[self.field])
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001397
1398 @utils.classproperty
1399 def plural_name(self):
1400 return '%ss' % self.resource
1401
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001402 @property
1403 def list_client(self):
1404 return self.admin_client if self.list_as_admin else self.client
1405
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001406 def list_method(self, *args, **kwargs):
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001407 method = getattr(self.list_client, 'list_%s' % self.plural_name)
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001408 kwargs.update(self.list_kwargs)
1409 return method(*args, **kwargs)
1410
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001411 def get_bare_url(self, url):
1412 base_url = self.client.base_url
zheng.yong74e760a2019-05-22 14:16:14 +08001413 base_url_normalized = utils.normalize_url(base_url)
1414 url_normalized = utils.normalize_url(url)
1415 self.assertTrue(url_normalized.startswith(base_url_normalized))
1416 return url_normalized[len(base_url_normalized):]
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001417
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001418 @classmethod
1419 def _extract_resources(cls, body):
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001420 return body[cls.plural_name]
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001421
1422 def _test_list_sorts(self, direction):
1423 sort_args = {
1424 'sort_dir': direction,
Armando Migliaccio57581c62016-07-01 10:13:19 -07001425 'sort_key': self.field
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001426 }
1427 body = self.list_method(**sort_args)
1428 resources = self._extract_resources(body)
1429 self.assertNotEmpty(
1430 resources, "%s list returned is empty" % self.resource)
Armando Migliaccio57581c62016-07-01 10:13:19 -07001431 retrieved_names = [res[self.field] for res in resources]
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001432 expected = sorted(retrieved_names)
1433 if direction == constants.SORT_DIRECTION_DESC:
1434 expected = list(reversed(expected))
1435 self.assertEqual(expected, retrieved_names)
1436
1437 @_require_sorting
1438 def _test_list_sorts_asc(self):
1439 self._test_list_sorts(constants.SORT_DIRECTION_ASC)
1440
1441 @_require_sorting
1442 def _test_list_sorts_desc(self):
1443 self._test_list_sorts(constants.SORT_DIRECTION_DESC)
1444
1445 @_require_pagination
1446 def _test_list_pagination(self):
1447 for limit in range(1, len(self.resource_names) + 1):
1448 pagination_args = {
1449 'limit': limit,
1450 }
1451 body = self.list_method(**pagination_args)
1452 resources = self._extract_resources(body)
1453 self.assertEqual(limit, len(resources))
1454
1455 @_require_pagination
1456 def _test_list_no_pagination_limit_0(self):
1457 pagination_args = {
1458 'limit': 0,
1459 }
1460 body = self.list_method(**pagination_args)
1461 resources = self._extract_resources(body)
Béla Vancsicsf1806182016-08-23 07:36:18 +02001462 self.assertGreaterEqual(len(resources), len(self.resource_names))
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001463
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001464 def _test_list_pagination_iteratively(self, lister):
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001465 # first, collect all resources for later comparison
1466 sort_args = {
1467 'sort_dir': constants.SORT_DIRECTION_ASC,
Armando Migliaccio57581c62016-07-01 10:13:19 -07001468 'sort_key': self.field
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001469 }
1470 body = self.list_method(**sort_args)
1471 expected_resources = self._extract_resources(body)
1472 self.assertNotEmpty(expected_resources)
1473
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001474 resources = lister(
1475 len(expected_resources), sort_args
1476 )
1477
1478 # finally, compare that the list retrieved in one go is identical to
1479 # the one containing pagination results
1480 self.assertSameOrder(expected_resources, resources)
1481
1482 def _list_all_with_marker(self, niterations, sort_args):
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001483 # paginate resources one by one, using last fetched resource as a
1484 # marker
1485 resources = []
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001486 for i in range(niterations):
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001487 pagination_args = sort_args.copy()
1488 pagination_args['limit'] = 1
1489 if resources:
1490 pagination_args['marker'] = resources[-1]['id']
1491 body = self.list_method(**pagination_args)
1492 resources_ = self._extract_resources(body)
1493 self.assertEqual(1, len(resources_))
1494 resources.extend(resources_)
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001495 return resources
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001496
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001497 @_require_pagination
1498 @_require_sorting
1499 def _test_list_pagination_with_marker(self):
1500 self._test_list_pagination_iteratively(self._list_all_with_marker)
1501
1502 def _list_all_with_hrefs(self, niterations, sort_args):
1503 # paginate resources one by one, using next href links
1504 resources = []
1505 prev_links = {}
1506
1507 for i in range(niterations):
1508 if prev_links:
1509 uri = self.get_bare_url(prev_links['next'])
1510 else:
Ihar Hrachyshka7f79fe62016-06-07 21:23:44 +02001511 sort_args.update(self.list_kwargs)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001512 uri = self.list_client.build_uri(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001513 self.plural_name, limit=1, **sort_args)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001514 prev_links, body = self.list_client.get_uri_with_links(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001515 self.plural_name, uri
1516 )
1517 resources_ = self._extract_resources(body)
1518 self.assertEqual(1, len(resources_))
1519 resources.extend(resources_)
1520
1521 # The last element is empty and does not contain 'next' link
1522 uri = self.get_bare_url(prev_links['next'])
1523 prev_links, body = self.client.get_uri_with_links(
1524 self.plural_name, uri
1525 )
1526 self.assertNotIn('next', prev_links)
1527
1528 # Now walk backwards and compare results
1529 resources2 = []
1530 for i in range(niterations):
1531 uri = self.get_bare_url(prev_links['previous'])
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001532 prev_links, body = self.list_client.get_uri_with_links(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001533 self.plural_name, uri
1534 )
1535 resources_ = self._extract_resources(body)
1536 self.assertEqual(1, len(resources_))
1537 resources2.extend(resources_)
1538
1539 self.assertSameOrder(resources, reversed(resources2))
1540
1541 return resources
1542
1543 @_require_pagination
1544 @_require_sorting
1545 def _test_list_pagination_with_href_links(self):
1546 self._test_list_pagination_iteratively(self._list_all_with_hrefs)
1547
1548 @_require_pagination
1549 @_require_sorting
1550 def _test_list_pagination_page_reverse_with_href_links(
1551 self, direction=constants.SORT_DIRECTION_ASC):
1552 pagination_args = {
1553 'sort_dir': direction,
Armando Migliaccio57581c62016-07-01 10:13:19 -07001554 'sort_key': self.field,
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001555 }
1556 body = self.list_method(**pagination_args)
1557 expected_resources = self._extract_resources(body)
1558
1559 page_size = 2
1560 pagination_args['limit'] = page_size
1561
1562 prev_links = {}
1563 resources = []
1564 num_resources = len(expected_resources)
1565 niterations = int(math.ceil(float(num_resources) / page_size))
1566 for i in range(niterations):
1567 if prev_links:
1568 uri = self.get_bare_url(prev_links['previous'])
1569 else:
Ihar Hrachyshka7f79fe62016-06-07 21:23:44 +02001570 pagination_args.update(self.list_kwargs)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001571 uri = self.list_client.build_uri(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001572 self.plural_name, page_reverse=True, **pagination_args)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001573 prev_links, body = self.list_client.get_uri_with_links(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001574 self.plural_name, uri
1575 )
1576 resources_ = self._extract_resources(body)
Béla Vancsicsf1806182016-08-23 07:36:18 +02001577 self.assertGreaterEqual(page_size, len(resources_))
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001578 resources.extend(reversed(resources_))
1579
1580 self.assertSameOrder(expected_resources, reversed(resources))
1581
1582 @_require_pagination
1583 @_require_sorting
1584 def _test_list_pagination_page_reverse_asc(self):
1585 self._test_list_pagination_page_reverse(
1586 direction=constants.SORT_DIRECTION_ASC)
1587
1588 @_require_pagination
1589 @_require_sorting
1590 def _test_list_pagination_page_reverse_desc(self):
1591 self._test_list_pagination_page_reverse(
1592 direction=constants.SORT_DIRECTION_DESC)
1593
1594 def _test_list_pagination_page_reverse(self, direction):
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 'limit': 3,
1599 }
1600 body = self.list_method(**pagination_args)
1601 expected_resources = self._extract_resources(body)
1602
1603 pagination_args['limit'] -= 1
1604 pagination_args['marker'] = expected_resources[-1]['id']
1605 pagination_args['page_reverse'] = True
1606 body = self.list_method(**pagination_args)
1607
1608 self.assertSameOrder(
1609 # the last entry is not included in 2nd result when used as a
1610 # marker
1611 expected_resources[:-1],
1612 self._extract_resources(body))
Victor Morales1be97b42016-09-05 08:50:06 -05001613
Hongbin Lu54f55922018-07-12 19:05:39 +00001614 @tutils.requires_ext(extension="filter-validation", service="network")
1615 def _test_list_validation_filters(
1616 self, validation_args, filter_is_valid=True):
1617 if not filter_is_valid:
1618 self.assertRaises(lib_exc.BadRequest, self.list_method,
1619 **validation_args)
1620 else:
1621 body = self.list_method(**validation_args)
1622 resources = self._extract_resources(body)
1623 for resource in resources:
1624 self.assertIn(resource['name'], self.resource_names)