blob: 0601e32db2898bfa9e0917412b7c453bea9b8073 [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):
Bernard Cafarellic3bec862020-09-10 13:59:49 +0200515 raise ValueError('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
Jakub Libosvarf5758012017-08-15 13:45:30 +00001196 cls.identity_admin_client = cls.os_admin.projects_client
Daniel Mellado3c0aeab2016-01-29 11:30:25 +00001197
1198 @classmethod
1199 def create_metering_label(cls, name, description):
1200 """Wrapper utility that returns a test metering label."""
1201 body = cls.admin_client.create_metering_label(
1202 description=description,
1203 name=data_utils.rand_name("metering-label"))
1204 metering_label = body['metering_label']
1205 cls.metering_labels.append(metering_label)
1206 return metering_label
1207
1208 @classmethod
1209 def create_metering_label_rule(cls, remote_ip_prefix, direction,
1210 metering_label_id):
1211 """Wrapper utility that returns a test metering label rule."""
1212 body = cls.admin_client.create_metering_label_rule(
1213 remote_ip_prefix=remote_ip_prefix, direction=direction,
1214 metering_label_id=metering_label_id)
1215 metering_label_rule = body['metering_label_rule']
1216 cls.metering_label_rules.append(metering_label_rule)
1217 return metering_label_rule
1218
1219 @classmethod
Kailun Qineaaf9782018-12-20 04:45:01 +08001220 def create_network_segment_range(cls, name, shared,
1221 project_id, network_type,
1222 physical_network, minimum,
1223 maximum):
1224 """Wrapper utility that returns a test network segment range."""
1225 network_segment_range_args = {'name': name,
1226 'shared': shared,
1227 'project_id': project_id,
1228 'network_type': network_type,
1229 'physical_network': physical_network,
1230 'minimum': minimum,
1231 'maximum': maximum}
1232 body = cls.admin_client.create_network_segment_range(
1233 **network_segment_range_args)
1234 network_segment_range = body['network_segment_range']
1235 cls.network_segment_ranges.append(network_segment_range)
1236 return network_segment_range
1237
1238 @classmethod
Daniel Mellado3c0aeab2016-01-29 11:30:25 +00001239 def create_flavor(cls, name, description, service_type):
1240 """Wrapper utility that returns a test flavor."""
1241 body = cls.admin_client.create_flavor(
1242 description=description, service_type=service_type,
1243 name=name)
1244 flavor = body['flavor']
1245 cls.flavors.append(flavor)
1246 return flavor
1247
1248 @classmethod
1249 def create_service_profile(cls, description, metainfo, driver):
1250 """Wrapper utility that returns a test service profile."""
1251 body = cls.admin_client.create_service_profile(
1252 driver=driver, metainfo=metainfo, description=description)
1253 service_profile = body['service_profile']
1254 cls.service_profiles.append(service_profile)
1255 return service_profile
1256
1257 @classmethod
Nguyen Phuong An67993fc2017-11-24 11:30:25 +07001258 def create_log(cls, name, description=None,
1259 resource_type='security_group', resource_id=None,
1260 target_id=None, event='ALL', enabled=True):
1261 """Wrapper utility that returns a test log object."""
1262 log_args = {'name': name,
Nguyen Phuong An67993fc2017-11-24 11:30:25 +07001263 'resource_type': resource_type,
1264 'resource_id': resource_id,
1265 'target_id': target_id,
1266 'event': event,
1267 'enabled': enabled}
Slawek Kaplonskid9fe3022021-08-11 15:25:16 +02001268 if description:
1269 log_args['description'] = description
Nguyen Phuong An67993fc2017-11-24 11:30:25 +07001270 body = cls.admin_client.create_log(**log_args)
1271 log_object = body['log']
1272 cls.log_objects.append(log_object)
1273 return log_object
1274
1275 @classmethod
Daniel Mellado3c0aeab2016-01-29 11:30:25 +00001276 def get_unused_ip(cls, net_id, ip_version=None):
Gary Kotton011345f2016-06-15 08:04:31 -07001277 """Get an unused ip address in a allocation pool of net"""
Daniel Mellado3c0aeab2016-01-29 11:30:25 +00001278 body = cls.admin_client.list_ports(network_id=net_id)
1279 ports = body['ports']
1280 used_ips = []
1281 for port in ports:
1282 used_ips.extend(
1283 [fixed_ip['ip_address'] for fixed_ip in port['fixed_ips']])
1284 body = cls.admin_client.list_subnets(network_id=net_id)
1285 subnets = body['subnets']
1286
1287 for subnet in subnets:
1288 if ip_version and subnet['ip_version'] != ip_version:
1289 continue
1290 cidr = subnet['cidr']
1291 allocation_pools = subnet['allocation_pools']
1292 iterators = []
1293 if allocation_pools:
1294 for allocation_pool in allocation_pools:
1295 iterators.append(netaddr.iter_iprange(
1296 allocation_pool['start'], allocation_pool['end']))
1297 else:
1298 net = netaddr.IPNetwork(cidr)
1299
1300 def _iterip():
1301 for ip in net:
1302 if ip not in (net.network, net.broadcast):
1303 yield ip
1304 iterators.append(iter(_iterip()))
1305
1306 for iterator in iterators:
1307 for ip in iterator:
1308 if str(ip) not in used_ips:
1309 return str(ip)
1310
1311 message = (
1312 "net(%s) has no usable IP address in allocation pools" % net_id)
1313 raise exceptions.InvalidConfiguration(message)
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001314
Lajos Katona2f904652018-08-23 14:04:56 +02001315 @classmethod
1316 def create_provider_network(cls, physnet_name, start_segmentation_id,
1317 max_attempts=30):
1318 segmentation_id = start_segmentation_id
Lajos Katona7eb67252019-01-14 12:55:35 +01001319 for attempts in range(max_attempts):
Lajos Katona2f904652018-08-23 14:04:56 +02001320 try:
Lajos Katona7eb67252019-01-14 12:55:35 +01001321 return cls.create_network(
Lajos Katona2f904652018-08-23 14:04:56 +02001322 name=data_utils.rand_name('test_net'),
1323 shared=True,
1324 provider_network_type='vlan',
1325 provider_physical_network=physnet_name,
1326 provider_segmentation_id=segmentation_id)
Lajos Katona2f904652018-08-23 14:04:56 +02001327 except lib_exc.Conflict:
Lajos Katona2f904652018-08-23 14:04:56 +02001328 segmentation_id += 1
1329 if segmentation_id > 4095:
1330 raise lib_exc.TempestException(
1331 "No free segmentation id was found for provider "
1332 "network creation!")
1333 time.sleep(CONF.network.build_interval)
Lajos Katona7eb67252019-01-14 12:55:35 +01001334 LOG.exception("Failed to create provider network after "
1335 "%d attempts", max_attempts)
1336 raise lib_exc.TimeoutException
Lajos Katona2f904652018-08-23 14:04:56 +02001337
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001338
Sławek Kapłońskiff294062016-12-04 15:00:54 +00001339def require_qos_rule_type(rule_type):
1340 def decorator(f):
1341 @functools.wraps(f)
1342 def wrapper(self, *func_args, **func_kwargs):
1343 if rule_type not in self.get_supported_qos_rule_types():
1344 raise self.skipException(
1345 "%s rule type is required." % rule_type)
1346 return f(self, *func_args, **func_kwargs)
1347 return wrapper
1348 return decorator
1349
1350
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001351def _require_sorting(f):
1352 @functools.wraps(f)
1353 def inner(self, *args, **kwargs):
Chandan Kumarc125fd12017-11-15 19:41:01 +05301354 if not tutils.is_extension_enabled("sorting", "network"):
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001355 self.skipTest('Sorting feature is required')
1356 return f(self, *args, **kwargs)
1357 return inner
1358
1359
1360def _require_pagination(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("pagination", "network"):
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001364 self.skipTest('Pagination feature is required')
1365 return f(self, *args, **kwargs)
1366 return inner
1367
1368
1369class BaseSearchCriteriaTest(BaseNetworkTest):
1370
1371 # This should be defined by subclasses to reflect resource name to test
1372 resource = None
1373
Armando Migliaccio57581c62016-07-01 10:13:19 -07001374 field = 'name'
1375
Ihar Hrachyshkaa8fe5a12016-05-24 14:50:58 +02001376 # NOTE(ihrachys): some names, like those starting with an underscore (_)
1377 # are sorted differently depending on whether the plugin implements native
1378 # sorting support, or not. So we avoid any such cases here, sticking to
1379 # alphanumeric. Also test a case when there are multiple resources with the
1380 # same name
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001381 resource_names = ('test1', 'abc1', 'test10', '123test') + ('test1',)
1382
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001383 force_tenant_isolation = True
1384
Ihar Hrachyshkaa8fe5a12016-05-24 14:50:58 +02001385 list_kwargs = {}
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001386
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001387 list_as_admin = False
1388
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001389 def assertSameOrder(self, original, actual):
1390 # gracefully handle iterators passed
1391 original = list(original)
1392 actual = list(actual)
1393 self.assertEqual(len(original), len(actual))
1394 for expected, res in zip(original, actual):
Armando Migliaccio57581c62016-07-01 10:13:19 -07001395 self.assertEqual(expected[self.field], res[self.field])
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001396
1397 @utils.classproperty
1398 def plural_name(self):
1399 return '%ss' % self.resource
1400
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001401 @property
1402 def list_client(self):
1403 return self.admin_client if self.list_as_admin else self.client
1404
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001405 def list_method(self, *args, **kwargs):
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001406 method = getattr(self.list_client, 'list_%s' % self.plural_name)
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001407 kwargs.update(self.list_kwargs)
1408 return method(*args, **kwargs)
1409
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001410 def get_bare_url(self, url):
1411 base_url = self.client.base_url
zheng.yong74e760a2019-05-22 14:16:14 +08001412 base_url_normalized = utils.normalize_url(base_url)
1413 url_normalized = utils.normalize_url(url)
1414 self.assertTrue(url_normalized.startswith(base_url_normalized))
1415 return url_normalized[len(base_url_normalized):]
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001416
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001417 @classmethod
1418 def _extract_resources(cls, body):
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001419 return body[cls.plural_name]
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001420
1421 def _test_list_sorts(self, direction):
1422 sort_args = {
1423 'sort_dir': direction,
Armando Migliaccio57581c62016-07-01 10:13:19 -07001424 'sort_key': self.field
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001425 }
1426 body = self.list_method(**sort_args)
1427 resources = self._extract_resources(body)
1428 self.assertNotEmpty(
1429 resources, "%s list returned is empty" % self.resource)
Armando Migliaccio57581c62016-07-01 10:13:19 -07001430 retrieved_names = [res[self.field] for res in resources]
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001431 expected = sorted(retrieved_names)
1432 if direction == constants.SORT_DIRECTION_DESC:
1433 expected = list(reversed(expected))
1434 self.assertEqual(expected, retrieved_names)
1435
1436 @_require_sorting
1437 def _test_list_sorts_asc(self):
1438 self._test_list_sorts(constants.SORT_DIRECTION_ASC)
1439
1440 @_require_sorting
1441 def _test_list_sorts_desc(self):
1442 self._test_list_sorts(constants.SORT_DIRECTION_DESC)
1443
1444 @_require_pagination
1445 def _test_list_pagination(self):
1446 for limit in range(1, len(self.resource_names) + 1):
1447 pagination_args = {
1448 'limit': limit,
1449 }
1450 body = self.list_method(**pagination_args)
1451 resources = self._extract_resources(body)
1452 self.assertEqual(limit, len(resources))
1453
1454 @_require_pagination
1455 def _test_list_no_pagination_limit_0(self):
1456 pagination_args = {
1457 'limit': 0,
1458 }
1459 body = self.list_method(**pagination_args)
1460 resources = self._extract_resources(body)
Béla Vancsicsf1806182016-08-23 07:36:18 +02001461 self.assertGreaterEqual(len(resources), len(self.resource_names))
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001462
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001463 def _test_list_pagination_iteratively(self, lister):
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001464 # first, collect all resources for later comparison
1465 sort_args = {
1466 'sort_dir': constants.SORT_DIRECTION_ASC,
Armando Migliaccio57581c62016-07-01 10:13:19 -07001467 'sort_key': self.field
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001468 }
1469 body = self.list_method(**sort_args)
1470 expected_resources = self._extract_resources(body)
1471 self.assertNotEmpty(expected_resources)
1472
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001473 resources = lister(
1474 len(expected_resources), sort_args
1475 )
1476
1477 # finally, compare that the list retrieved in one go is identical to
1478 # the one containing pagination results
1479 self.assertSameOrder(expected_resources, resources)
1480
1481 def _list_all_with_marker(self, niterations, sort_args):
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001482 # paginate resources one by one, using last fetched resource as a
1483 # marker
1484 resources = []
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001485 for i in range(niterations):
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001486 pagination_args = sort_args.copy()
1487 pagination_args['limit'] = 1
1488 if resources:
1489 pagination_args['marker'] = resources[-1]['id']
1490 body = self.list_method(**pagination_args)
1491 resources_ = self._extract_resources(body)
1492 self.assertEqual(1, len(resources_))
1493 resources.extend(resources_)
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001494 return resources
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001495
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001496 @_require_pagination
1497 @_require_sorting
1498 def _test_list_pagination_with_marker(self):
1499 self._test_list_pagination_iteratively(self._list_all_with_marker)
1500
1501 def _list_all_with_hrefs(self, niterations, sort_args):
1502 # paginate resources one by one, using next href links
1503 resources = []
1504 prev_links = {}
1505
1506 for i in range(niterations):
1507 if prev_links:
1508 uri = self.get_bare_url(prev_links['next'])
1509 else:
Ihar Hrachyshka7f79fe62016-06-07 21:23:44 +02001510 sort_args.update(self.list_kwargs)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001511 uri = self.list_client.build_uri(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001512 self.plural_name, limit=1, **sort_args)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001513 prev_links, body = self.list_client.get_uri_with_links(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001514 self.plural_name, uri
1515 )
1516 resources_ = self._extract_resources(body)
1517 self.assertEqual(1, len(resources_))
1518 resources.extend(resources_)
1519
1520 # The last element is empty and does not contain 'next' link
1521 uri = self.get_bare_url(prev_links['next'])
1522 prev_links, body = self.client.get_uri_with_links(
1523 self.plural_name, uri
1524 )
1525 self.assertNotIn('next', prev_links)
1526
1527 # Now walk backwards and compare results
1528 resources2 = []
1529 for i in range(niterations):
1530 uri = self.get_bare_url(prev_links['previous'])
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001531 prev_links, body = self.list_client.get_uri_with_links(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001532 self.plural_name, uri
1533 )
1534 resources_ = self._extract_resources(body)
1535 self.assertEqual(1, len(resources_))
1536 resources2.extend(resources_)
1537
1538 self.assertSameOrder(resources, reversed(resources2))
1539
1540 return resources
1541
1542 @_require_pagination
1543 @_require_sorting
1544 def _test_list_pagination_with_href_links(self):
1545 self._test_list_pagination_iteratively(self._list_all_with_hrefs)
1546
1547 @_require_pagination
1548 @_require_sorting
1549 def _test_list_pagination_page_reverse_with_href_links(
1550 self, direction=constants.SORT_DIRECTION_ASC):
1551 pagination_args = {
1552 'sort_dir': direction,
Armando Migliaccio57581c62016-07-01 10:13:19 -07001553 'sort_key': self.field,
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001554 }
1555 body = self.list_method(**pagination_args)
1556 expected_resources = self._extract_resources(body)
1557
1558 page_size = 2
1559 pagination_args['limit'] = page_size
1560
1561 prev_links = {}
1562 resources = []
1563 num_resources = len(expected_resources)
1564 niterations = int(math.ceil(float(num_resources) / page_size))
1565 for i in range(niterations):
1566 if prev_links:
1567 uri = self.get_bare_url(prev_links['previous'])
1568 else:
Ihar Hrachyshka7f79fe62016-06-07 21:23:44 +02001569 pagination_args.update(self.list_kwargs)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001570 uri = self.list_client.build_uri(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001571 self.plural_name, page_reverse=True, **pagination_args)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001572 prev_links, body = self.list_client.get_uri_with_links(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001573 self.plural_name, uri
1574 )
1575 resources_ = self._extract_resources(body)
Béla Vancsicsf1806182016-08-23 07:36:18 +02001576 self.assertGreaterEqual(page_size, len(resources_))
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001577 resources.extend(reversed(resources_))
1578
1579 self.assertSameOrder(expected_resources, reversed(resources))
1580
1581 @_require_pagination
1582 @_require_sorting
1583 def _test_list_pagination_page_reverse_asc(self):
1584 self._test_list_pagination_page_reverse(
1585 direction=constants.SORT_DIRECTION_ASC)
1586
1587 @_require_pagination
1588 @_require_sorting
1589 def _test_list_pagination_page_reverse_desc(self):
1590 self._test_list_pagination_page_reverse(
1591 direction=constants.SORT_DIRECTION_DESC)
1592
1593 def _test_list_pagination_page_reverse(self, direction):
1594 pagination_args = {
1595 'sort_dir': direction,
Armando Migliaccio57581c62016-07-01 10:13:19 -07001596 'sort_key': self.field,
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001597 'limit': 3,
1598 }
1599 body = self.list_method(**pagination_args)
1600 expected_resources = self._extract_resources(body)
1601
1602 pagination_args['limit'] -= 1
1603 pagination_args['marker'] = expected_resources[-1]['id']
1604 pagination_args['page_reverse'] = True
1605 body = self.list_method(**pagination_args)
1606
1607 self.assertSameOrder(
1608 # the last entry is not included in 2nd result when used as a
1609 # marker
1610 expected_resources[:-1],
1611 self._extract_resources(body))
Victor Morales1be97b42016-09-05 08:50:06 -05001612
Hongbin Lu54f55922018-07-12 19:05:39 +00001613 @tutils.requires_ext(extension="filter-validation", service="network")
1614 def _test_list_validation_filters(
1615 self, validation_args, filter_is_valid=True):
1616 if not filter_is_valid:
1617 self.assertRaises(lib_exc.BadRequest, self.list_method,
1618 **validation_args)
1619 else:
1620 body = self.list_method(**validation_args)
1621 resources = self._extract_resources(body)
1622 for resource in resources:
1623 self.assertIn(resource['name'], self.resource_names)