blob: b66fe0db9d4e5caf348b3734dcaba75a9da7c795 [file] [log] [blame]
Daniel Mellado3c0aeab2016-01-29 11:30:25 +00001# Copyright 2012 OpenStack Foundation
2# All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain
6# a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
15
Ihar Hrachyshka59382252016-04-05 15:54:33 +020016import functools
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +020017import math
Lajos Katona2f904652018-08-23 14:04:56 +020018import time
Ihar Hrachyshka59382252016-04-05 15:54:33 +020019
Daniel Mellado3c0aeab2016-01-29 11:30:25 +000020import netaddr
Chandan Kumarc125fd12017-11-15 19:41:01 +053021from neutron_lib import constants as const
Lajos Katona2f904652018-08-23 14:04:56 +020022from oslo_log import log
Chandan Kumarc125fd12017-11-15 19:41:01 +053023from tempest.common import utils as tutils
Daniel Mellado3c0aeab2016-01-29 11:30:25 +000024from tempest.lib.common.utils import data_utils
25from tempest.lib import exceptions as lib_exc
26from tempest import test
27
Chandan Kumar667d3d32017-09-22 12:24:06 +053028from neutron_tempest_plugin.api import clients
29from neutron_tempest_plugin.common import constants
30from neutron_tempest_plugin.common import utils
31from neutron_tempest_plugin import config
32from neutron_tempest_plugin import exceptions
Daniel Mellado3c0aeab2016-01-29 11:30:25 +000033
34CONF = config.CONF
35
Lajos Katona2f904652018-08-23 14:04:56 +020036LOG = log.getLogger(__name__)
37
Daniel Mellado3c0aeab2016-01-29 11:30:25 +000038
39class BaseNetworkTest(test.BaseTestCase):
40
Brian Haleyae328b92018-10-09 19:51:54 -040041 """Base class for Neutron tests that use the Tempest Neutron REST client
Daniel Mellado3c0aeab2016-01-29 11:30:25 +000042
43 Per the Neutron API Guide, API v1.x was removed from the source code tree
44 (docs.openstack.org/api/openstack-network/2.0/content/Overview-d1e71.html)
45 Therefore, v2.x of the Neutron API is assumed. It is also assumed that the
46 following options are defined in the [network] section of etc/tempest.conf:
47
48 project_network_cidr with a block of cidr's from which smaller blocks
49 can be allocated for tenant networks
50
51 project_network_mask_bits with the mask bits to be used to partition
52 the block defined by tenant-network_cidr
53
54 Finally, it is assumed that the following option is defined in the
55 [service_available] section of etc/tempest.conf
56
57 neutron as True
58 """
59
60 force_tenant_isolation = False
61 credentials = ['primary']
62
63 # Default to ipv4.
Federico Ressi0ddc93b2018-04-09 12:01:48 +020064 _ip_version = const.IP_VERSION_4
Daniel Mellado3c0aeab2016-01-29 11:30:25 +000065
Federico Ressi61b564e2018-07-06 08:10:31 +020066 # Derive from BaseAdminNetworkTest class to have this initialized
67 admin_client = None
68
Federico Ressia69dcd52018-07-06 09:45:34 +020069 external_network_id = CONF.network.public_network_id
70
Daniel Mellado3c0aeab2016-01-29 11:30:25 +000071 @classmethod
72 def get_client_manager(cls, credential_type=None, roles=None,
73 force_new=None):
Genadi Chereshnyacc395c02016-07-25 12:17:37 +030074 manager = super(BaseNetworkTest, cls).get_client_manager(
75 credential_type=credential_type,
76 roles=roles,
77 force_new=force_new
78 )
Daniel Mellado3c0aeab2016-01-29 11:30:25 +000079 # Neutron uses a different clients manager than the one in the Tempest
Jens Harbott860b46a2017-11-15 21:23:15 +000080 # save the original in case mixed tests need it
81 if credential_type == 'primary':
82 cls.os_tempest = manager
Daniel Mellado3c0aeab2016-01-29 11:30:25 +000083 return clients.Manager(manager.credentials)
84
85 @classmethod
86 def skip_checks(cls):
87 super(BaseNetworkTest, cls).skip_checks()
88 if not CONF.service_available.neutron:
89 raise cls.skipException("Neutron support is required")
Federico Ressi0ddc93b2018-04-09 12:01:48 +020090 if (cls._ip_version == const.IP_VERSION_6 and
91 not CONF.network_feature_enabled.ipv6):
Daniel Mellado3c0aeab2016-01-29 11:30:25 +000092 raise cls.skipException("IPv6 Tests are disabled.")
Jakub Libosvar1982aa12017-05-30 11:15:33 +000093 for req_ext in getattr(cls, 'required_extensions', []):
Chandan Kumarc125fd12017-11-15 19:41:01 +053094 if not tutils.is_extension_enabled(req_ext, 'network'):
Jakub Libosvar1982aa12017-05-30 11:15:33 +000095 msg = "%s extension not enabled." % req_ext
96 raise cls.skipException(msg)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +000097
98 @classmethod
99 def setup_credentials(cls):
100 # Create no network resources for these test.
101 cls.set_network_resources()
102 super(BaseNetworkTest, cls).setup_credentials()
103
104 @classmethod
105 def setup_clients(cls):
106 super(BaseNetworkTest, cls).setup_clients()
fumihiko kakumaa216fc12017-07-14 10:43:29 +0900107 cls.client = cls.os_primary.network_client
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000108
109 @classmethod
110 def resource_setup(cls):
111 super(BaseNetworkTest, cls).resource_setup()
112
113 cls.networks = []
Miguel Lavalle124378b2016-09-21 16:41:47 -0500114 cls.admin_networks = []
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000115 cls.subnets = []
Kevin Bentonba3651c2017-09-01 17:13:01 -0700116 cls.admin_subnets = []
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000117 cls.ports = []
118 cls.routers = []
119 cls.floating_ips = []
Slawek Kaplonski003fcae2019-05-26 22:38:35 +0200120 cls.port_forwardings = []
Nurmatov Mamatisa3f2bbb52021-10-20 14:33:54 +0300121 cls.local_ips = []
122 cls.local_ip_associations = []
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000123 cls.metering_labels = []
124 cls.service_profiles = []
125 cls.flavors = []
126 cls.metering_label_rules = []
127 cls.qos_rules = []
128 cls.qos_policies = []
129 cls.ethertype = "IPv" + str(cls._ip_version)
Miguel Lavalleb1c7a3d2021-01-31 19:05:22 -0600130 cls.address_groups = []
131 cls.admin_address_groups = []
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000132 cls.address_scopes = []
133 cls.admin_address_scopes = []
134 cls.subnetpools = []
135 cls.admin_subnetpools = []
Itzik Brownbac51dc2016-10-31 12:25:04 +0000136 cls.security_groups = []
Dongcan Ye2de722e2018-07-04 11:01:37 +0000137 cls.admin_security_groups = []
Chandan Kumarc125fd12017-11-15 19:41:01 +0530138 cls.projects = []
Nguyen Phuong An67993fc2017-11-24 11:30:25 +0700139 cls.log_objects = []
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200140 cls.reserved_subnet_cidrs = set()
Federico Ressiab286e42018-06-19 09:52:10 +0200141 cls.keypairs = []
Federico Ressi82e83e32018-07-03 14:19:55 +0200142 cls.trunks = []
Kailun Qineaaf9782018-12-20 04:45:01 +0800143 cls.network_segment_ranges = []
Harald Jensåsc9782fa2019-06-03 22:35:41 +0200144 cls.conntrack_helpers = []
yangjianfeng2936a292022-02-04 11:22:11 +0800145 cls.ndp_proxies = []
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000146
147 @classmethod
yangjianfeng23e40c22020-11-22 08:42:18 +0000148 def reserve_external_subnet_cidrs(cls):
149 client = cls.os_admin.network_client
150 ext_nets = client.list_networks(
151 **{"router:external": True})['networks']
152 for ext_net in ext_nets:
153 ext_subnets = client.list_subnets(
154 network_id=ext_net['id'])['subnets']
155 for ext_subnet in ext_subnets:
156 cls.reserve_subnet_cidr(ext_subnet['cidr'])
157
158 @classmethod
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000159 def resource_cleanup(cls):
160 if CONF.service_available.neutron:
Federico Ressi82e83e32018-07-03 14:19:55 +0200161 # Clean up trunks
162 for trunk in cls.trunks:
163 cls._try_delete_resource(cls.delete_trunk, trunk)
164
yangjianfeng2936a292022-02-04 11:22:11 +0800165 # Clean up ndp proxy
166 for ndp_proxy in cls.ndp_proxies:
167 cls._try_delete_resource(cls.delete_ndp_proxy, ndp_proxy)
168
Slawek Kaplonski003fcae2019-05-26 22:38:35 +0200169 # Clean up port forwardings
170 for pf in cls.port_forwardings:
171 cls._try_delete_resource(cls.delete_port_forwarding, pf)
172
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000173 # Clean up floating IPs
174 for floating_ip in cls.floating_ips:
Federico Ressia69dcd52018-07-06 09:45:34 +0200175 cls._try_delete_resource(cls.delete_floatingip, floating_ip)
176
Nurmatov Mamatisa3f2bbb52021-10-20 14:33:54 +0300177 # Clean up Local IP Associations
178 for association in cls.local_ip_associations:
179 cls._try_delete_resource(cls.delete_local_ip_association,
180 association)
181 # Clean up Local IPs
182 for local_ip in cls.local_ips:
183 cls._try_delete_resource(cls.delete_local_ip,
184 local_ip)
185
Harald Jensåsc9782fa2019-06-03 22:35:41 +0200186 # Clean up conntrack helpers
187 for cth in cls.conntrack_helpers:
188 cls._try_delete_resource(cls.delete_conntrack_helper, cth)
189
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000190 # Clean up routers
191 for router in cls.routers:
192 cls._try_delete_resource(cls.delete_router,
193 router)
194 # Clean up metering label rules
195 for metering_label_rule in cls.metering_label_rules:
196 cls._try_delete_resource(
197 cls.admin_client.delete_metering_label_rule,
198 metering_label_rule['id'])
199 # Clean up metering labels
200 for metering_label in cls.metering_labels:
201 cls._try_delete_resource(
202 cls.admin_client.delete_metering_label,
203 metering_label['id'])
204 # Clean up flavors
205 for flavor in cls.flavors:
206 cls._try_delete_resource(
207 cls.admin_client.delete_flavor,
208 flavor['id'])
209 # Clean up service profiles
210 for service_profile in cls.service_profiles:
211 cls._try_delete_resource(
212 cls.admin_client.delete_service_profile,
213 service_profile['id'])
214 # Clean up ports
215 for port in cls.ports:
216 cls._try_delete_resource(cls.client.delete_port,
217 port['id'])
218 # Clean up subnets
219 for subnet in cls.subnets:
220 cls._try_delete_resource(cls.client.delete_subnet,
221 subnet['id'])
Kevin Bentonba3651c2017-09-01 17:13:01 -0700222 # Clean up admin subnets
223 for subnet in cls.admin_subnets:
224 cls._try_delete_resource(cls.admin_client.delete_subnet,
225 subnet['id'])
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000226 # Clean up networks
227 for network in cls.networks:
Federico Ressi61b564e2018-07-06 08:10:31 +0200228 cls._try_delete_resource(cls.delete_network, network)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000229
Miguel Lavalle124378b2016-09-21 16:41:47 -0500230 # Clean up admin networks
231 for network in cls.admin_networks:
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000232 cls._try_delete_resource(cls.admin_client.delete_network,
233 network['id'])
234
Itzik Brownbac51dc2016-10-31 12:25:04 +0000235 # Clean up security groups
Federico Ressi4c590d72018-10-10 14:01:08 +0200236 for security_group in cls.security_groups:
237 cls._try_delete_resource(cls.delete_security_group,
238 security_group)
Itzik Brownbac51dc2016-10-31 12:25:04 +0000239
Dongcan Ye2de722e2018-07-04 11:01:37 +0000240 # Clean up admin security groups
Federico Ressi4c590d72018-10-10 14:01:08 +0200241 for security_group in cls.admin_security_groups:
242 cls._try_delete_resource(cls.delete_security_group,
243 security_group,
244 client=cls.admin_client)
Dongcan Ye2de722e2018-07-04 11:01:37 +0000245
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000246 for subnetpool in cls.subnetpools:
247 cls._try_delete_resource(cls.client.delete_subnetpool,
248 subnetpool['id'])
249
250 for subnetpool in cls.admin_subnetpools:
251 cls._try_delete_resource(cls.admin_client.delete_subnetpool,
252 subnetpool['id'])
253
254 for address_scope in cls.address_scopes:
255 cls._try_delete_resource(cls.client.delete_address_scope,
256 address_scope['id'])
257
258 for address_scope in cls.admin_address_scopes:
259 cls._try_delete_resource(
260 cls.admin_client.delete_address_scope,
261 address_scope['id'])
262
Chandan Kumarc125fd12017-11-15 19:41:01 +0530263 for project in cls.projects:
264 cls._try_delete_resource(
265 cls.identity_admin_client.delete_project,
266 project['id'])
267
Sławek Kapłońskie100c4d2017-08-23 21:18:34 +0000268 # Clean up QoS rules
269 for qos_rule in cls.qos_rules:
270 cls._try_delete_resource(cls.admin_client.delete_qos_rule,
271 qos_rule['id'])
272 # Clean up QoS policies
273 # as all networks and ports are already removed, QoS policies
274 # shouldn't be "in use"
275 for qos_policy in cls.qos_policies:
276 cls._try_delete_resource(cls.admin_client.delete_qos_policy,
277 qos_policy['id'])
278
Nguyen Phuong An67993fc2017-11-24 11:30:25 +0700279 # Clean up log_objects
280 for log_object in cls.log_objects:
281 cls._try_delete_resource(cls.admin_client.delete_log,
282 log_object['id'])
283
Federico Ressiab286e42018-06-19 09:52:10 +0200284 for keypair in cls.keypairs:
285 cls._try_delete_resource(cls.delete_keypair, keypair)
286
Kailun Qineaaf9782018-12-20 04:45:01 +0800287 # Clean up network_segment_ranges
288 for network_segment_range in cls.network_segment_ranges:
289 cls._try_delete_resource(
290 cls.admin_client.delete_network_segment_range,
291 network_segment_range['id'])
292
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000293 super(BaseNetworkTest, cls).resource_cleanup()
294
295 @classmethod
296 def _try_delete_resource(cls, delete_callable, *args, **kwargs):
297 """Cleanup resources in case of test-failure
298
299 Some resources are explicitly deleted by the test.
300 If the test failed to delete a resource, this method will execute
301 the appropriate delete methods. Otherwise, the method ignores NotFound
302 exceptions thrown for resources that were correctly deleted by the
303 test.
304
305 :param delete_callable: delete method
306 :param args: arguments for delete method
307 :param kwargs: keyword arguments for delete method
308 """
309 try:
310 delete_callable(*args, **kwargs)
311 # if resource is not found, this means it was deleted in the test
312 except lib_exc.NotFound:
313 pass
314
315 @classmethod
Federico Ressi61b564e2018-07-06 08:10:31 +0200316 def create_network(cls, network_name=None, client=None, external=None,
317 shared=None, provider_network_type=None,
318 provider_physical_network=None,
319 provider_segmentation_id=None, **kwargs):
320 """Create a network.
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000321
Federico Ressi61b564e2018-07-06 08:10:31 +0200322 When client is not provider and admin_client is attribute is not None
323 (for example when using BaseAdminNetworkTest base class) and using any
324 of the convenience parameters (external, shared, provider_network_type,
325 provider_physical_network and provider_segmentation_id) it silently
326 uses admin_client. If the network is not shared then it uses the same
327 project_id as regular client.
328
329 :param network_name: Human-readable name of the network
330
331 :param client: client to be used for connecting to network service
332
333 :param external: indicates whether the network has an external routing
334 facility that's not managed by the networking service.
335
336 :param shared: indicates whether this resource is shared across all
337 projects. By default, only administrative users can change this value.
338 If True and admin_client attribute is not None, then the network is
339 created under administrative project.
340
341 :param provider_network_type: the type of physical network that this
342 network should be mapped to. For example, 'flat', 'vlan', 'vxlan', or
343 'gre'. Valid values depend on a networking back-end.
344
345 :param provider_physical_network: the physical network where this
346 network should be implemented. The Networking API v2.0 does not provide
347 a way to list available physical networks. For example, the Open
348 vSwitch plug-in configuration file defines a symbolic name that maps to
349 specific bridges on each compute host.
350
351 :param provider_segmentation_id: The ID of the isolated segment on the
352 physical network. The network_type attribute defines the segmentation
353 model. For example, if the network_type value is 'vlan', this ID is a
354 vlan identifier. If the network_type value is 'gre', this ID is a gre
355 key.
356
357 :param **kwargs: extra parameters to be forwarded to network service
358 """
359
360 name = (network_name or kwargs.pop('name', None) or
361 data_utils.rand_name('test-network-'))
362
363 # translate convenience parameters
364 admin_client_required = False
365 if provider_network_type:
366 admin_client_required = True
367 kwargs['provider:network_type'] = provider_network_type
368 if provider_physical_network:
369 admin_client_required = True
370 kwargs['provider:physical_network'] = provider_physical_network
371 if provider_segmentation_id:
372 admin_client_required = True
373 kwargs['provider:segmentation_id'] = provider_segmentation_id
374 if external is not None:
375 admin_client_required = True
376 kwargs['router:external'] = bool(external)
377 if shared is not None:
378 admin_client_required = True
379 kwargs['shared'] = bool(shared)
380
381 if not client:
382 if admin_client_required and cls.admin_client:
383 # For convenience silently switch to admin client
384 client = cls.admin_client
385 if not shared:
386 # Keep this network visible from current project
387 project_id = (kwargs.get('project_id') or
388 kwargs.get('tenant_id') or
Takashi Kajinamida451772023-03-22 00:19:39 +0900389 cls.client.project_id)
Federico Ressi61b564e2018-07-06 08:10:31 +0200390 kwargs.update(project_id=project_id, tenant_id=project_id)
391 else:
392 # Use default client
393 client = cls.client
394
395 network = client.create_network(name=name, **kwargs)['network']
396 network['client'] = client
397 cls.networks.append(network)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000398 return network
399
400 @classmethod
Federico Ressi61b564e2018-07-06 08:10:31 +0200401 def delete_network(cls, network, client=None):
402 client = client or network.get('client') or cls.client
403 client.delete_network(network['id'])
404
405 @classmethod
406 def create_shared_network(cls, network_name=None, **kwargs):
407 return cls.create_network(name=network_name, shared=True, **kwargs)
Miguel Lavalle124378b2016-09-21 16:41:47 -0500408
409 @classmethod
Sławek Kapłońskid98e27d2018-05-07 16:16:28 +0200410 def create_subnet(cls, network, gateway='', cidr=None, mask_bits=None,
Federico Ressi98f20ec2018-05-11 06:09:49 +0200411 ip_version=None, client=None, reserve_cidr=True,
412 **kwargs):
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200413 """Wrapper utility that returns a test subnet.
414
415 Convenient wrapper for client.create_subnet method. It reserves and
416 allocates CIDRs to avoid creating overlapping subnets.
417
418 :param network: network where to create the subnet
419 network['id'] must contain the ID of the network
420
421 :param gateway: gateway IP address
422 It can be a str or a netaddr.IPAddress
423 If gateway is not given, then it will use default address for
424 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 +0200425 if gateway is given as None then no gateway will be assigned
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200426
427 :param cidr: CIDR of the subnet to create
428 It can be either None, a str or a netaddr.IPNetwork instance
429
430 :param mask_bits: CIDR prefix length
431 It can be either None or a numeric value.
432 If cidr parameter is given then mask_bits is used to determinate a
433 sequence of valid CIDR to use as generated.
434 Please see netaddr.IPNetwork.subnet method documentation[1]
435
436 :param ip_version: ip version of generated subnet CIDRs
437 It can be None, IP_VERSION_4 or IP_VERSION_6
438 It has to match given either given CIDR and gateway
439
440 :param ip_version: numeric value (either IP_VERSION_4 or IP_VERSION_6)
441 this value must match CIDR and gateway IP versions if any of them is
442 given
443
444 :param client: client to be used to connect to network service
445
Federico Ressi98f20ec2018-05-11 06:09:49 +0200446 :param reserve_cidr: if True then it reserves assigned CIDR to avoid
447 using the same CIDR for further subnets in the scope of the same
448 test case class
449
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200450 :param **kwargs: optional parameters to be forwarded to wrapped method
451
452 [1] http://netaddr.readthedocs.io/en/latest/tutorial_01.html#supernets-and-subnets # noqa
453 """
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000454
455 # allow tests to use admin client
456 if not client:
457 client = cls.client
458
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200459 if gateway:
460 gateway_ip = netaddr.IPAddress(gateway)
461 if ip_version:
462 if ip_version != gateway_ip.version:
463 raise ValueError(
464 "Gateway IP version doesn't match IP version")
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000465 else:
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200466 ip_version = gateway_ip.version
Sławek Kapłońskid98e27d2018-05-07 16:16:28 +0200467 else:
468 ip_version = ip_version or cls._ip_version
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200469
470 for subnet_cidr in cls.get_subnet_cidrs(
471 ip_version=ip_version, cidr=cidr, mask_bits=mask_bits):
Federico Ressi98f20ec2018-05-11 06:09:49 +0200472 if gateway is not None:
473 kwargs['gateway_ip'] = str(gateway or (subnet_cidr.ip + 1))
Slawek Kaplonski21f53422018-11-02 16:02:09 +0100474 else:
475 kwargs['gateway_ip'] = None
Federico Ressi98f20ec2018-05-11 06:09:49 +0200476 try:
477 body = client.create_subnet(
478 network_id=network['id'],
479 cidr=str(subnet_cidr),
480 ip_version=subnet_cidr.version,
481 **kwargs)
482 break
483 except lib_exc.BadRequest as e:
484 if 'overlaps with another subnet' not in str(e):
485 raise
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000486 else:
487 message = 'Available CIDR for subnet creation could not be found'
488 raise ValueError(message)
489 subnet = body['subnet']
Kevin Bentonba3651c2017-09-01 17:13:01 -0700490 if client is cls.client:
491 cls.subnets.append(subnet)
492 else:
493 cls.admin_subnets.append(subnet)
Federico Ressi98f20ec2018-05-11 06:09:49 +0200494 if reserve_cidr:
495 cls.reserve_subnet_cidr(subnet_cidr)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000496 return subnet
497
498 @classmethod
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200499 def reserve_subnet_cidr(cls, addr, **ipnetwork_kwargs):
500 """Reserve given subnet CIDR making sure it is not used by create_subnet
501
502 :param addr: the CIDR address to be reserved
503 It can be a str or netaddr.IPNetwork instance
504
505 :param **ipnetwork_kwargs: optional netaddr.IPNetwork constructor
506 parameters
507 """
508
509 if not cls.try_reserve_subnet_cidr(addr, **ipnetwork_kwargs):
Bernard Cafarellic3bec862020-09-10 13:59:49 +0200510 raise ValueError('Subnet CIDR already reserved: {0!r}'.format(
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200511 addr))
512
513 @classmethod
514 def try_reserve_subnet_cidr(cls, addr, **ipnetwork_kwargs):
515 """Reserve given subnet CIDR if it hasn't been reserved before
516
517 :param addr: the CIDR address to be reserved
518 It can be a str or netaddr.IPNetwork instance
519
520 :param **ipnetwork_kwargs: optional netaddr.IPNetwork constructor
521 parameters
522
523 :return: True if it wasn't reserved before, False elsewhere.
524 """
525
526 subnet_cidr = netaddr.IPNetwork(addr, **ipnetwork_kwargs)
527 if subnet_cidr in cls.reserved_subnet_cidrs:
528 return False
529 else:
530 cls.reserved_subnet_cidrs.add(subnet_cidr)
531 return True
532
533 @classmethod
534 def get_subnet_cidrs(
535 cls, cidr=None, mask_bits=None, ip_version=None):
536 """Iterate over a sequence of unused subnet CIDR for IP version
537
538 :param cidr: CIDR of the subnet to create
539 It can be either None, a str or a netaddr.IPNetwork instance
540
541 :param mask_bits: CIDR prefix length
542 It can be either None or a numeric value.
543 If cidr parameter is given then mask_bits is used to determinate a
544 sequence of valid CIDR to use as generated.
545 Please see netaddr.IPNetwork.subnet method documentation[1]
546
547 :param ip_version: ip version of generated subnet CIDRs
548 It can be None, IP_VERSION_4 or IP_VERSION_6
549 It has to match given CIDR if given
550
551 :return: iterator over reserved CIDRs of type netaddr.IPNetwork
552
553 [1] http://netaddr.readthedocs.io/en/latest/tutorial_01.html#supernets-and-subnets # noqa
554 """
555
556 if cidr:
557 # Generate subnet CIDRs starting from given CIDR
558 # checking it is of requested IP version
559 cidr = netaddr.IPNetwork(cidr, version=ip_version)
560 else:
561 # Generate subnet CIDRs starting from configured values
562 ip_version = ip_version or cls._ip_version
563 if ip_version == const.IP_VERSION_4:
564 mask_bits = mask_bits or config.safe_get_config_value(
565 'network', 'project_network_mask_bits')
566 cidr = netaddr.IPNetwork(config.safe_get_config_value(
567 'network', 'project_network_cidr'))
568 elif ip_version == const.IP_VERSION_6:
569 mask_bits = config.safe_get_config_value(
570 'network', 'project_network_v6_mask_bits')
571 cidr = netaddr.IPNetwork(config.safe_get_config_value(
572 'network', 'project_network_v6_cidr'))
573 else:
574 raise ValueError('Invalid IP version: {!r}'.format(ip_version))
575
576 if mask_bits:
577 subnet_cidrs = cidr.subnet(mask_bits)
578 else:
579 subnet_cidrs = iter([cidr])
580
581 for subnet_cidr in subnet_cidrs:
582 if subnet_cidr not in cls.reserved_subnet_cidrs:
583 yield subnet_cidr
584
585 @classmethod
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000586 def create_port(cls, network, **kwargs):
587 """Wrapper utility that returns a test port."""
Edan Davidd75e48e2018-01-03 02:49:52 -0500588 if CONF.network.port_vnic_type and 'binding:vnic_type' not in kwargs:
589 kwargs['binding:vnic_type'] = CONF.network.port_vnic_type
Glenn Van de Water5d9b1402020-09-16 15:14:14 +0200590 if CONF.network.port_profile and 'binding:profile' not in kwargs:
591 kwargs['binding:profile'] = CONF.network.port_profile
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000592 body = cls.client.create_port(network_id=network['id'],
593 **kwargs)
594 port = body['port']
595 cls.ports.append(port)
596 return port
597
598 @classmethod
599 def update_port(cls, port, **kwargs):
600 """Wrapper utility that updates a test port."""
601 body = cls.client.update_port(port['id'],
602 **kwargs)
603 return body['port']
604
605 @classmethod
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300606 def _create_router_with_client(
607 cls, client, router_name=None, admin_state_up=False,
608 external_network_id=None, enable_snat=None, **kwargs
609 ):
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000610 ext_gw_info = {}
611 if external_network_id:
612 ext_gw_info['network_id'] = external_network_id
YAMAMOTO Takashi9bd4f972017-06-20 12:49:30 +0900613 if enable_snat is not None:
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000614 ext_gw_info['enable_snat'] = enable_snat
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300615 body = client.create_router(
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000616 router_name, external_gateway_info=ext_gw_info,
617 admin_state_up=admin_state_up, **kwargs)
618 router = body['router']
619 cls.routers.append(router)
620 return router
621
622 @classmethod
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300623 def create_router(cls, *args, **kwargs):
624 return cls._create_router_with_client(cls.client, *args, **kwargs)
625
626 @classmethod
627 def create_admin_router(cls, *args, **kwargs):
rajat294495c042017-06-28 15:37:16 +0530628 return cls._create_router_with_client(cls.os_admin.network_client,
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300629 *args, **kwargs)
630
631 @classmethod
Federico Ressia69dcd52018-07-06 09:45:34 +0200632 def create_floatingip(cls, external_network_id=None, port=None,
633 client=None, **kwargs):
634 """Creates a floating IP.
635
636 Create a floating IP and schedule it for later deletion.
637 If a client is passed, then it is used for deleting the IP too.
638
639 :param external_network_id: network ID where to create
640 By default this is 'CONF.network.public_network_id'.
641
642 :param port: port to bind floating IP to
643 This is translated to 'port_id=port['id']'
644 By default it is None.
645
646 :param client: network client to be used for creating and cleaning up
647 the floating IP.
648
649 :param **kwargs: additional creation parameters to be forwarded to
650 networking server.
651 """
652
653 client = client or cls.client
654 external_network_id = (external_network_id or
655 cls.external_network_id)
656
657 if port:
Federico Ressi47f6ae42018-09-24 16:19:14 +0200658 port_id = kwargs.setdefault('port_id', port['id'])
659 if port_id != port['id']:
660 message = "Port ID specified twice: {!s} != {!s}".format(
661 port_id, port['id'])
662 raise ValueError(message)
Federico Ressia69dcd52018-07-06 09:45:34 +0200663
664 fip = client.create_floatingip(external_network_id,
665 **kwargs)['floatingip']
666
667 # save client to be used later in cls.delete_floatingip
668 # for final cleanup
669 fip['client'] = client
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000670 cls.floating_ips.append(fip)
671 return fip
672
673 @classmethod
Federico Ressia69dcd52018-07-06 09:45:34 +0200674 def delete_floatingip(cls, floating_ip, client=None):
675 """Delete floating IP
676
677 :param client: Client to be used
678 If client is not given it will use the client used to create
679 the floating IP, or cls.client if unknown.
680 """
681
682 client = client or floating_ip.get('client') or cls.client
683 client.delete_floatingip(floating_ip['id'])
684
685 @classmethod
Slawek Kaplonski003fcae2019-05-26 22:38:35 +0200686 def create_port_forwarding(cls, fip_id, internal_port_id,
687 internal_port, external_port,
688 internal_ip_address=None, protocol="tcp",
689 client=None):
690 """Creates a port forwarding.
691
692 Create a port forwarding and schedule it for later deletion.
693 If a client is passed, then it is used for deleting the PF too.
694
695 :param fip_id: The ID of the floating IP address.
696
697 :param internal_port_id: The ID of the Neutron port associated to
698 the floating IP port forwarding.
699
700 :param internal_port: The TCP/UDP/other protocol port number of the
701 Neutron port fixed IP address associated to the floating ip
702 port forwarding.
703
704 :param external_port: The TCP/UDP/other protocol port number of
705 the port forwarding floating IP address.
706
707 :param internal_ip_address: The fixed IPv4 address of the Neutron
708 port associated to the floating IP port forwarding.
709
710 :param protocol: The IP protocol used in the floating IP port
711 forwarding.
712
713 :param client: network client to be used for creating and cleaning up
714 the floating IP port forwarding.
715 """
716
717 client = client or cls.client
718
719 pf = client.create_port_forwarding(
720 fip_id, internal_port_id, internal_port, external_port,
721 internal_ip_address, protocol)['port_forwarding']
722
723 # save ID of floating IP associated with port forwarding for final
724 # cleanup
725 pf['floatingip_id'] = fip_id
726
727 # save client to be used later in cls.delete_port_forwarding
728 # for final cleanup
729 pf['client'] = client
730 cls.port_forwardings.append(pf)
731 return pf
732
733 @classmethod
Flavio Fernandesa1952c62020-10-02 06:39:08 -0400734 def update_port_forwarding(cls, fip_id, pf_id, client=None, **kwargs):
735 """Wrapper utility for update_port_forwarding."""
736 client = client or cls.client
737 return client.update_port_forwarding(fip_id, pf_id, **kwargs)
738
739 @classmethod
Slawek Kaplonski003fcae2019-05-26 22:38:35 +0200740 def delete_port_forwarding(cls, pf, client=None):
741 """Delete port forwarding
742
743 :param client: Client to be used
744 If client is not given it will use the client used to create
745 the port forwarding, or cls.client if unknown.
746 """
747
748 client = client or pf.get('client') or cls.client
749 client.delete_port_forwarding(pf['floatingip_id'], pf['id'])
750
Nurmatov Mamatisa3f2bbb52021-10-20 14:33:54 +0300751 def create_local_ip(cls, network_id=None,
752 client=None, **kwargs):
753 """Creates a Local IP.
754
755 Create a Local IP and schedule it for later deletion.
756 If a client is passed, then it is used for deleting the IP too.
757
758 :param network_id: network ID where to create
759 By default this is 'CONF.network.public_network_id'.
760
761 :param client: network client to be used for creating and cleaning up
762 the Local IP.
763
764 :param **kwargs: additional creation parameters to be forwarded to
765 networking server.
766 """
767
768 client = client or cls.client
769 network_id = (network_id or
770 cls.external_network_id)
771
772 local_ip = client.create_local_ip(network_id,
773 **kwargs)['local_ip']
774
775 # save client to be used later in cls.delete_local_ip
776 # for final cleanup
777 local_ip['client'] = client
778 cls.local_ips.append(local_ip)
779 return local_ip
780
781 @classmethod
782 def delete_local_ip(cls, local_ip, client=None):
783 """Delete Local IP
784
785 :param client: Client to be used
786 If client is not given it will use the client used to create
787 the Local IP, or cls.client if unknown.
788 """
789
790 client = client or local_ip.get('client') or cls.client
791 client.delete_local_ip(local_ip['id'])
792
793 @classmethod
794 def create_local_ip_association(cls, local_ip_id, fixed_port_id,
795 fixed_ip_address=None, client=None):
796 """Creates a Local IP association.
797
798 Create a Local IP Association and schedule it for later deletion.
799 If a client is passed, then it is used for deleting the association
800 too.
801
802 :param local_ip_id: The ID of the Local IP.
803
804 :param fixed_port_id: The ID of the Neutron port
805 to be associated with the Local IP
806
807 :param fixed_ip_address: The fixed IPv4 address of the Neutron
808 port to be associated with the Local IP
809
810 :param client: network client to be used for creating and cleaning up
811 the Local IP Association.
812 """
813
814 client = client or cls.client
815
816 association = client.create_local_ip_association(
817 local_ip_id, fixed_port_id,
818 fixed_ip_address)['port_association']
819
820 # save ID of Local IP for final cleanup
821 association['local_ip_id'] = local_ip_id
822
823 # save client to be used later in
824 # cls.delete_local_ip_association for final cleanup
825 association['client'] = client
826 cls.local_ip_associations.append(association)
827 return association
828
829 @classmethod
830 def delete_local_ip_association(cls, association, client=None):
831
832 """Delete Local IP Association
833
834 :param client: Client to be used
835 If client is not given it will use the client used to create
836 the local IP association, or cls.client if unknown.
837 """
838
839 client = client or association.get('client') or cls.client
840 client.delete_local_ip_association(association['local_ip_id'],
841 association['fixed_port_id'])
842
Slawek Kaplonski003fcae2019-05-26 22:38:35 +0200843 @classmethod
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000844 def create_router_interface(cls, router_id, subnet_id):
845 """Wrapper utility that returns a router interface."""
846 interface = cls.client.add_router_interface_with_subnet_id(
847 router_id, subnet_id)
848 return interface
849
850 @classmethod
Bence Romsics46bd3af2019-09-13 10:52:41 +0200851 def add_extra_routes_atomic(cls, *args, **kwargs):
852 return cls.client.add_extra_routes_atomic(*args, **kwargs)
853
854 @classmethod
855 def remove_extra_routes_atomic(cls, *args, **kwargs):
856 return cls.client.remove_extra_routes_atomic(*args, **kwargs)
857
858 @classmethod
Sławek Kapłońskiff294062016-12-04 15:00:54 +0000859 def get_supported_qos_rule_types(cls):
860 body = cls.client.list_qos_rule_types()
861 return [rule_type['type'] for rule_type in body['rule_types']]
862
863 @classmethod
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200864 def create_qos_policy(cls, name, description=None, shared=False,
Rodolfo Alonso Hernandeze2d062f2020-01-14 17:11:42 +0000865 project_id=None, is_default=False):
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000866 """Wrapper utility that returns a test QoS policy."""
867 body = cls.admin_client.create_qos_policy(
Rodolfo Alonso Hernandeze2d062f2020-01-14 17:11:42 +0000868 name, description, shared, project_id, is_default)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000869 qos_policy = body['policy']
870 cls.qos_policies.append(qos_policy)
871 return qos_policy
872
873 @classmethod
elajkatdbb0b482021-05-04 17:20:07 +0200874 def create_qos_dscp_marking_rule(cls, policy_id, dscp_mark):
875 """Wrapper utility that creates and returns a QoS dscp rule."""
876 body = cls.admin_client.create_dscp_marking_rule(
877 policy_id, dscp_mark)
878 qos_rule = body['dscp_marking_rule']
879 cls.qos_rules.append(qos_rule)
880 return qos_rule
881
882 @classmethod
Jakub Libosvar83704832017-12-06 16:02:28 +0000883 def delete_router(cls, router, client=None):
884 client = client or cls.client
Aditya Vaja49819a72018-11-26 14:20:10 -0800885 if 'routes' in router:
886 client.remove_router_extra_routes(router['id'])
Jakub Libosvar83704832017-12-06 16:02:28 +0000887 body = client.list_router_interfaces(router['id'])
Chandan Kumarc125fd12017-11-15 19:41:01 +0530888 interfaces = [port for port in body['ports']
889 if port['device_owner'] in const.ROUTER_INTERFACE_OWNERS]
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000890 for i in interfaces:
891 try:
Jakub Libosvar83704832017-12-06 16:02:28 +0000892 client.remove_router_interface_with_subnet_id(
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000893 router['id'], i['fixed_ips'][0]['subnet_id'])
894 except lib_exc.NotFound:
895 pass
Jakub Libosvar83704832017-12-06 16:02:28 +0000896 client.delete_router(router['id'])
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000897
898 @classmethod
899 def create_address_scope(cls, name, is_admin=False, **kwargs):
900 if is_admin:
901 body = cls.admin_client.create_address_scope(name=name, **kwargs)
902 cls.admin_address_scopes.append(body['address_scope'])
903 else:
904 body = cls.client.create_address_scope(name=name, **kwargs)
905 cls.address_scopes.append(body['address_scope'])
906 return body['address_scope']
907
908 @classmethod
Igor Malinovskiyb80f1d02020-03-06 13:39:52 +0200909 def create_subnetpool(cls, name, is_admin=False, client=None, **kwargs):
910 if client is None:
911 client = cls.admin_client if is_admin else cls.client
912
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000913 if is_admin:
Igor Malinovskiyb80f1d02020-03-06 13:39:52 +0200914 body = client.create_subnetpool(name, **kwargs)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000915 cls.admin_subnetpools.append(body['subnetpool'])
916 else:
Igor Malinovskiyb80f1d02020-03-06 13:39:52 +0200917 body = client.create_subnetpool(name, **kwargs)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000918 cls.subnetpools.append(body['subnetpool'])
919 return body['subnetpool']
920
Chandan Kumarc125fd12017-11-15 19:41:01 +0530921 @classmethod
Miguel Lavalleb1c7a3d2021-01-31 19:05:22 -0600922 def create_address_group(cls, name, is_admin=False, **kwargs):
923 if is_admin:
924 body = cls.admin_client.create_address_group(name=name, **kwargs)
925 cls.admin_address_groups.append(body['address_group'])
926 else:
927 body = cls.client.create_address_group(name=name, **kwargs)
928 cls.address_groups.append(body['address_group'])
929 return body['address_group']
930
931 @classmethod
Chandan Kumarc125fd12017-11-15 19:41:01 +0530932 def create_project(cls, name=None, description=None):
933 test_project = name or data_utils.rand_name('test_project_')
934 test_description = description or data_utils.rand_name('desc_')
935 project = cls.identity_admin_client.create_project(
936 name=test_project,
937 description=test_description)['project']
938 cls.projects.append(project)
Dongcan Ye2de722e2018-07-04 11:01:37 +0000939 # Create a project will create a default security group.
Dongcan Ye2de722e2018-07-04 11:01:37 +0000940 sgs_list = cls.admin_client.list_security_groups(
941 tenant_id=project['id'])['security_groups']
Federico Ressi4c590d72018-10-10 14:01:08 +0200942 for security_group in sgs_list:
943 # Make sure delete_security_group method will use
944 # the admin client for this group
945 security_group['client'] = cls.admin_client
946 cls.security_groups.append(security_group)
Chandan Kumarc125fd12017-11-15 19:41:01 +0530947 return project
948
949 @classmethod
Federico Ressi4c590d72018-10-10 14:01:08 +0200950 def create_security_group(cls, name=None, project=None, client=None,
951 **kwargs):
952 if project:
953 client = client or cls.admin_client
954 project_id = kwargs.setdefault('project_id', project['id'])
955 tenant_id = kwargs.setdefault('tenant_id', project['id'])
956 if project_id != project['id'] or tenant_id != project['id']:
957 raise ValueError('Project ID specified multiple times')
958 else:
959 client = client or cls.client
960
961 name = name or data_utils.rand_name(cls.__name__)
962 security_group = client.create_security_group(name=name, **kwargs)[
963 'security_group']
964 security_group['client'] = client
965 cls.security_groups.append(security_group)
966 return security_group
967
968 @classmethod
969 def delete_security_group(cls, security_group, client=None):
970 client = client or security_group.get('client') or cls.client
971 client.delete_security_group(security_group['id'])
972
973 @classmethod
974 def create_security_group_rule(cls, security_group=None, project=None,
975 client=None, ip_version=None, **kwargs):
976 if project:
977 client = client or cls.admin_client
978 project_id = kwargs.setdefault('project_id', project['id'])
979 tenant_id = kwargs.setdefault('tenant_id', project['id'])
980 if project_id != project['id'] or tenant_id != project['id']:
981 raise ValueError('Project ID specified multiple times')
982
983 if 'security_group_id' not in kwargs:
984 security_group = (security_group or
985 cls.get_security_group(client=client))
986
987 if security_group:
988 client = client or security_group.get('client')
989 security_group_id = kwargs.setdefault('security_group_id',
990 security_group['id'])
991 if security_group_id != security_group['id']:
992 raise ValueError('Security group ID specified multiple times.')
993
994 ip_version = ip_version or cls._ip_version
995 default_params = (
996 constants.DEFAULT_SECURITY_GROUP_RULE_PARAMS[ip_version])
Slawek Kaplonski83979b92022-12-15 14:15:12 +0100997 if (('remote_address_group_id' in kwargs or
998 'remote_group_id' in kwargs) and
999 'remote_ip_prefix' in default_params):
Miguel Lavalleb1c7a3d2021-01-31 19:05:22 -06001000 default_params.pop('remote_ip_prefix')
Federico Ressi4c590d72018-10-10 14:01:08 +02001001 for key, value in default_params.items():
1002 kwargs.setdefault(key, value)
1003
1004 client = client or cls.client
1005 return client.create_security_group_rule(**kwargs)[
1006 'security_group_rule']
1007
1008 @classmethod
1009 def get_security_group(cls, name='default', client=None):
1010 client = client or cls.client
1011 security_groups = client.list_security_groups()['security_groups']
1012 for security_group in security_groups:
1013 if security_group['name'] == name:
1014 return security_group
1015 raise ValueError("No such security group named {!r}".format(name))
Chandan Kumarc125fd12017-11-15 19:41:01 +05301016
Federico Ressiab286e42018-06-19 09:52:10 +02001017 @classmethod
1018 def create_keypair(cls, client=None, name=None, **kwargs):
1019 client = client or cls.os_primary.keypairs_client
1020 name = name or data_utils.rand_name('keypair-test')
1021 keypair = client.create_keypair(name=name, **kwargs)['keypair']
1022
1023 # save client for later cleanup
1024 keypair['client'] = client
1025 cls.keypairs.append(keypair)
1026 return keypair
1027
1028 @classmethod
1029 def delete_keypair(cls, keypair, client=None):
1030 client = (client or keypair.get('client') or
1031 cls.os_primary.keypairs_client)
1032 client.delete_keypair(keypair_name=keypair['name'])
1033
Federico Ressi82e83e32018-07-03 14:19:55 +02001034 @classmethod
1035 def create_trunk(cls, port=None, subports=None, client=None, **kwargs):
1036 """Create network trunk
1037
1038 :param port: dictionary containing parent port ID (port['id'])
1039 :param client: client to be used for connecting to networking service
1040 :param **kwargs: extra parameters to be forwarded to network service
1041
1042 :returns: dictionary containing created trunk details
1043 """
1044 client = client or cls.client
1045
1046 if port:
1047 kwargs['port_id'] = port['id']
1048
1049 trunk = client.create_trunk(subports=subports, **kwargs)['trunk']
1050 # Save client reference for later deletion
1051 trunk['client'] = client
1052 cls.trunks.append(trunk)
1053 return trunk
1054
1055 @classmethod
Huifeng Le1c9f40b2018-11-07 01:14:21 +08001056 def delete_trunk(cls, trunk, client=None, detach_parent_port=True):
Federico Ressi82e83e32018-07-03 14:19:55 +02001057 """Delete network trunk
1058
1059 :param trunk: dictionary containing trunk ID (trunk['id'])
1060
1061 :param client: client to be used for connecting to networking service
1062 """
1063 client = client or trunk.get('client') or cls.client
1064 trunk.update(client.show_trunk(trunk['id'])['trunk'])
1065
1066 if not trunk['admin_state_up']:
1067 # Cannot touch trunk before admin_state_up is True
1068 client.update_trunk(trunk['id'], admin_state_up=True)
1069 if trunk['sub_ports']:
1070 # Removes trunk ports before deleting it
1071 cls._try_delete_resource(client.remove_subports, trunk['id'],
1072 trunk['sub_ports'])
1073
1074 # we have to detach the interface from the server before
1075 # the trunk can be deleted.
1076 parent_port = {'id': trunk['port_id']}
1077
1078 def is_parent_port_detached():
1079 parent_port.update(client.show_port(parent_port['id'])['port'])
1080 return not parent_port['device_id']
1081
Huifeng Le1c9f40b2018-11-07 01:14:21 +08001082 if detach_parent_port and not is_parent_port_detached():
Federico Ressi82e83e32018-07-03 14:19:55 +02001083 # this could probably happen when trunk is deleted and parent port
1084 # has been assigned to a VM that is still running. Here we are
1085 # assuming that device_id points to such VM.
1086 cls.os_primary.compute.InterfacesClient().delete_interface(
1087 parent_port['device_id'], parent_port['id'])
1088 utils.wait_until_true(is_parent_port_detached)
1089
1090 client.delete_trunk(trunk['id'])
1091
Harald Jensåsc9782fa2019-06-03 22:35:41 +02001092 @classmethod
1093 def create_conntrack_helper(cls, router_id, helper, protocol, port,
1094 client=None):
1095 """Create a conntrack helper
1096
1097 Create a conntrack helper and schedule it for later deletion. If a
1098 client is passed, then it is used for deleteing the CTH too.
1099
1100 :param router_id: The ID of the Neutron router associated to the
1101 conntrack helper.
1102
1103 :param helper: The conntrack helper module alias
1104
1105 :param protocol: The conntrack helper IP protocol used in the conntrack
1106 helper.
1107
1108 :param port: The conntrack helper IP protocol port number for the
1109 conntrack helper.
1110
1111 :param client: network client to be used for creating and cleaning up
1112 the conntrack helper.
1113 """
1114
1115 client = client or cls.client
1116
1117 cth = client.create_conntrack_helper(router_id, helper, protocol,
1118 port)['conntrack_helper']
1119
1120 # save ID of router associated with conntrack helper for final cleanup
1121 cth['router_id'] = router_id
1122
1123 # save client to be used later in cls.delete_conntrack_helper for final
1124 # cleanup
1125 cth['client'] = client
1126 cls.conntrack_helpers.append(cth)
1127 return cth
1128
1129 @classmethod
1130 def delete_conntrack_helper(cls, cth, client=None):
1131 """Delete conntrack helper
1132
1133 :param client: Client to be used
1134 If client is not given it will use the client used to create the
1135 conntrack helper, or cls.client if unknown.
1136 """
1137
1138 client = client or cth.get('client') or cls.client
1139 client.delete_conntrack_helper(cth['router_id'], cth['id'])
1140
yangjianfeng2936a292022-02-04 11:22:11 +08001141 @classmethod
1142 def create_ndp_proxy(cls, router_id, port_id, client=None, **kwargs):
1143 """Creates a ndp proxy.
1144
1145 Create a ndp proxy and schedule it for later deletion.
1146 If a client is passed, then it is used for deleting the NDP proxy too.
1147
1148 :param router_id: router ID where to create the ndp proxy.
1149
1150 :param port_id: port ID which the ndp proxy associate with
1151
1152 :param client: network client to be used for creating and cleaning up
1153 the ndp proxy.
1154
1155 :param **kwargs: additional creation parameters to be forwarded to
1156 networking server.
1157 """
1158 client = client or cls.client
1159
1160 data = {'router_id': router_id, 'port_id': port_id}
1161 if kwargs:
1162 data.update(kwargs)
1163 ndp_proxy = client.create_ndp_proxy(**data)['ndp_proxy']
1164
1165 # save client to be used later in cls.delete_ndp_proxy
1166 # for final cleanup
1167 ndp_proxy['client'] = client
1168 cls.ndp_proxies.append(ndp_proxy)
1169 return ndp_proxy
1170
1171 @classmethod
1172 def delete_ndp_proxy(cls, ndp_proxy, client=None):
1173 """Delete ndp proxy
1174
1175 :param client: Client to be used
1176 If client is not given it will use the client used to create
1177 the ndp proxy, or cls.client if unknown.
1178 """
1179 client = client or ndp_proxy.get('client') or cls.client
1180 client.delete_ndp_proxy(ndp_proxy['id'])
1181
Daniel Mellado3c0aeab2016-01-29 11:30:25 +00001182
1183class BaseAdminNetworkTest(BaseNetworkTest):
1184
1185 credentials = ['primary', 'admin']
1186
1187 @classmethod
1188 def setup_clients(cls):
1189 super(BaseAdminNetworkTest, cls).setup_clients()
fumihiko kakumaa216fc12017-07-14 10:43:29 +09001190 cls.admin_client = cls.os_admin.network_client
Jakub Libosvarf5758012017-08-15 13:45:30 +00001191 cls.identity_admin_client = cls.os_admin.projects_client
Daniel Mellado3c0aeab2016-01-29 11:30:25 +00001192
1193 @classmethod
1194 def create_metering_label(cls, name, description):
1195 """Wrapper utility that returns a test metering label."""
1196 body = cls.admin_client.create_metering_label(
1197 description=description,
1198 name=data_utils.rand_name("metering-label"))
1199 metering_label = body['metering_label']
1200 cls.metering_labels.append(metering_label)
1201 return metering_label
1202
1203 @classmethod
1204 def create_metering_label_rule(cls, remote_ip_prefix, direction,
1205 metering_label_id):
1206 """Wrapper utility that returns a test metering label rule."""
1207 body = cls.admin_client.create_metering_label_rule(
1208 remote_ip_prefix=remote_ip_prefix, direction=direction,
1209 metering_label_id=metering_label_id)
1210 metering_label_rule = body['metering_label_rule']
1211 cls.metering_label_rules.append(metering_label_rule)
1212 return metering_label_rule
1213
1214 @classmethod
Kailun Qineaaf9782018-12-20 04:45:01 +08001215 def create_network_segment_range(cls, name, shared,
1216 project_id, network_type,
1217 physical_network, minimum,
1218 maximum):
1219 """Wrapper utility that returns a test network segment range."""
1220 network_segment_range_args = {'name': name,
1221 'shared': shared,
1222 'project_id': project_id,
1223 'network_type': network_type,
1224 'physical_network': physical_network,
1225 'minimum': minimum,
1226 'maximum': maximum}
1227 body = cls.admin_client.create_network_segment_range(
1228 **network_segment_range_args)
1229 network_segment_range = body['network_segment_range']
1230 cls.network_segment_ranges.append(network_segment_range)
1231 return network_segment_range
1232
1233 @classmethod
Daniel Mellado3c0aeab2016-01-29 11:30:25 +00001234 def create_flavor(cls, name, description, service_type):
1235 """Wrapper utility that returns a test flavor."""
1236 body = cls.admin_client.create_flavor(
1237 description=description, service_type=service_type,
1238 name=name)
1239 flavor = body['flavor']
1240 cls.flavors.append(flavor)
1241 return flavor
1242
1243 @classmethod
1244 def create_service_profile(cls, description, metainfo, driver):
1245 """Wrapper utility that returns a test service profile."""
1246 body = cls.admin_client.create_service_profile(
1247 driver=driver, metainfo=metainfo, description=description)
1248 service_profile = body['service_profile']
1249 cls.service_profiles.append(service_profile)
1250 return service_profile
1251
1252 @classmethod
Nguyen Phuong An67993fc2017-11-24 11:30:25 +07001253 def create_log(cls, name, description=None,
1254 resource_type='security_group', resource_id=None,
1255 target_id=None, event='ALL', enabled=True):
1256 """Wrapper utility that returns a test log object."""
1257 log_args = {'name': name,
Nguyen Phuong An67993fc2017-11-24 11:30:25 +07001258 'resource_type': resource_type,
1259 'resource_id': resource_id,
1260 'target_id': target_id,
1261 'event': event,
1262 'enabled': enabled}
Slawek Kaplonskid9fe3022021-08-11 15:25:16 +02001263 if description:
1264 log_args['description'] = description
Nguyen Phuong An67993fc2017-11-24 11:30:25 +07001265 body = cls.admin_client.create_log(**log_args)
1266 log_object = body['log']
1267 cls.log_objects.append(log_object)
1268 return log_object
1269
1270 @classmethod
Daniel Mellado3c0aeab2016-01-29 11:30:25 +00001271 def get_unused_ip(cls, net_id, ip_version=None):
Gary Kotton011345f2016-06-15 08:04:31 -07001272 """Get an unused ip address in a allocation pool of net"""
Daniel Mellado3c0aeab2016-01-29 11:30:25 +00001273 body = cls.admin_client.list_ports(network_id=net_id)
1274 ports = body['ports']
1275 used_ips = []
1276 for port in ports:
1277 used_ips.extend(
1278 [fixed_ip['ip_address'] for fixed_ip in port['fixed_ips']])
1279 body = cls.admin_client.list_subnets(network_id=net_id)
1280 subnets = body['subnets']
1281
1282 for subnet in subnets:
1283 if ip_version and subnet['ip_version'] != ip_version:
1284 continue
1285 cidr = subnet['cidr']
1286 allocation_pools = subnet['allocation_pools']
1287 iterators = []
1288 if allocation_pools:
1289 for allocation_pool in allocation_pools:
1290 iterators.append(netaddr.iter_iprange(
1291 allocation_pool['start'], allocation_pool['end']))
1292 else:
1293 net = netaddr.IPNetwork(cidr)
1294
1295 def _iterip():
1296 for ip in net:
1297 if ip not in (net.network, net.broadcast):
1298 yield ip
1299 iterators.append(iter(_iterip()))
1300
1301 for iterator in iterators:
1302 for ip in iterator:
1303 if str(ip) not in used_ips:
1304 return str(ip)
1305
1306 message = (
1307 "net(%s) has no usable IP address in allocation pools" % net_id)
1308 raise exceptions.InvalidConfiguration(message)
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001309
Lajos Katona2f904652018-08-23 14:04:56 +02001310 @classmethod
1311 def create_provider_network(cls, physnet_name, start_segmentation_id,
1312 max_attempts=30):
1313 segmentation_id = start_segmentation_id
Lajos Katona7eb67252019-01-14 12:55:35 +01001314 for attempts in range(max_attempts):
Lajos Katona2f904652018-08-23 14:04:56 +02001315 try:
Lajos Katona7eb67252019-01-14 12:55:35 +01001316 return cls.create_network(
Lajos Katona2f904652018-08-23 14:04:56 +02001317 name=data_utils.rand_name('test_net'),
1318 shared=True,
1319 provider_network_type='vlan',
1320 provider_physical_network=physnet_name,
1321 provider_segmentation_id=segmentation_id)
Lajos Katona2f904652018-08-23 14:04:56 +02001322 except lib_exc.Conflict:
Lajos Katona2f904652018-08-23 14:04:56 +02001323 segmentation_id += 1
1324 if segmentation_id > 4095:
1325 raise lib_exc.TempestException(
1326 "No free segmentation id was found for provider "
1327 "network creation!")
1328 time.sleep(CONF.network.build_interval)
Lajos Katona7eb67252019-01-14 12:55:35 +01001329 LOG.exception("Failed to create provider network after "
1330 "%d attempts", max_attempts)
1331 raise lib_exc.TimeoutException
Lajos Katona2f904652018-08-23 14:04:56 +02001332
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001333
Sławek Kapłońskiff294062016-12-04 15:00:54 +00001334def require_qos_rule_type(rule_type):
1335 def decorator(f):
1336 @functools.wraps(f)
1337 def wrapper(self, *func_args, **func_kwargs):
1338 if rule_type not in self.get_supported_qos_rule_types():
1339 raise self.skipException(
1340 "%s rule type is required." % rule_type)
1341 return f(self, *func_args, **func_kwargs)
1342 return wrapper
1343 return decorator
1344
1345
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001346def _require_sorting(f):
1347 @functools.wraps(f)
1348 def inner(self, *args, **kwargs):
Chandan Kumarc125fd12017-11-15 19:41:01 +05301349 if not tutils.is_extension_enabled("sorting", "network"):
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001350 self.skipTest('Sorting feature is required')
1351 return f(self, *args, **kwargs)
1352 return inner
1353
1354
1355def _require_pagination(f):
1356 @functools.wraps(f)
1357 def inner(self, *args, **kwargs):
Chandan Kumarc125fd12017-11-15 19:41:01 +05301358 if not tutils.is_extension_enabled("pagination", "network"):
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001359 self.skipTest('Pagination feature is required')
1360 return f(self, *args, **kwargs)
1361 return inner
1362
1363
1364class BaseSearchCriteriaTest(BaseNetworkTest):
1365
1366 # This should be defined by subclasses to reflect resource name to test
1367 resource = None
1368
Armando Migliaccio57581c62016-07-01 10:13:19 -07001369 field = 'name'
1370
Ihar Hrachyshkaa8fe5a12016-05-24 14:50:58 +02001371 # NOTE(ihrachys): some names, like those starting with an underscore (_)
1372 # are sorted differently depending on whether the plugin implements native
1373 # sorting support, or not. So we avoid any such cases here, sticking to
1374 # alphanumeric. Also test a case when there are multiple resources with the
1375 # same name
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001376 resource_names = ('test1', 'abc1', 'test10', '123test') + ('test1',)
1377
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001378 force_tenant_isolation = True
1379
Ihar Hrachyshkaa8fe5a12016-05-24 14:50:58 +02001380 list_kwargs = {}
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001381
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001382 list_as_admin = False
1383
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001384 def assertSameOrder(self, original, actual):
1385 # gracefully handle iterators passed
1386 original = list(original)
1387 actual = list(actual)
1388 self.assertEqual(len(original), len(actual))
1389 for expected, res in zip(original, actual):
Armando Migliaccio57581c62016-07-01 10:13:19 -07001390 self.assertEqual(expected[self.field], res[self.field])
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001391
1392 @utils.classproperty
1393 def plural_name(self):
1394 return '%ss' % self.resource
1395
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001396 @property
1397 def list_client(self):
1398 return self.admin_client if self.list_as_admin else self.client
1399
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001400 def list_method(self, *args, **kwargs):
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001401 method = getattr(self.list_client, 'list_%s' % self.plural_name)
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001402 kwargs.update(self.list_kwargs)
1403 return method(*args, **kwargs)
1404
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001405 def get_bare_url(self, url):
1406 base_url = self.client.base_url
zheng.yong74e760a2019-05-22 14:16:14 +08001407 base_url_normalized = utils.normalize_url(base_url)
1408 url_normalized = utils.normalize_url(url)
1409 self.assertTrue(url_normalized.startswith(base_url_normalized))
1410 return url_normalized[len(base_url_normalized):]
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001411
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001412 @classmethod
1413 def _extract_resources(cls, body):
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001414 return body[cls.plural_name]
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001415
1416 def _test_list_sorts(self, direction):
1417 sort_args = {
1418 'sort_dir': direction,
Armando Migliaccio57581c62016-07-01 10:13:19 -07001419 'sort_key': self.field
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001420 }
1421 body = self.list_method(**sort_args)
1422 resources = self._extract_resources(body)
1423 self.assertNotEmpty(
1424 resources, "%s list returned is empty" % self.resource)
Armando Migliaccio57581c62016-07-01 10:13:19 -07001425 retrieved_names = [res[self.field] for res in resources]
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001426 expected = sorted(retrieved_names)
1427 if direction == constants.SORT_DIRECTION_DESC:
1428 expected = list(reversed(expected))
1429 self.assertEqual(expected, retrieved_names)
1430
1431 @_require_sorting
1432 def _test_list_sorts_asc(self):
1433 self._test_list_sorts(constants.SORT_DIRECTION_ASC)
1434
1435 @_require_sorting
1436 def _test_list_sorts_desc(self):
1437 self._test_list_sorts(constants.SORT_DIRECTION_DESC)
1438
1439 @_require_pagination
1440 def _test_list_pagination(self):
1441 for limit in range(1, len(self.resource_names) + 1):
1442 pagination_args = {
1443 'limit': limit,
1444 }
1445 body = self.list_method(**pagination_args)
1446 resources = self._extract_resources(body)
1447 self.assertEqual(limit, len(resources))
1448
1449 @_require_pagination
1450 def _test_list_no_pagination_limit_0(self):
1451 pagination_args = {
1452 'limit': 0,
1453 }
1454 body = self.list_method(**pagination_args)
1455 resources = self._extract_resources(body)
Béla Vancsicsf1806182016-08-23 07:36:18 +02001456 self.assertGreaterEqual(len(resources), len(self.resource_names))
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001457
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001458 def _test_list_pagination_iteratively(self, lister):
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001459 # first, collect all resources for later comparison
1460 sort_args = {
1461 'sort_dir': constants.SORT_DIRECTION_ASC,
Armando Migliaccio57581c62016-07-01 10:13:19 -07001462 'sort_key': self.field
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001463 }
1464 body = self.list_method(**sort_args)
1465 expected_resources = self._extract_resources(body)
1466 self.assertNotEmpty(expected_resources)
1467
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001468 resources = lister(
1469 len(expected_resources), sort_args
1470 )
1471
1472 # finally, compare that the list retrieved in one go is identical to
1473 # the one containing pagination results
1474 self.assertSameOrder(expected_resources, resources)
1475
1476 def _list_all_with_marker(self, niterations, sort_args):
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001477 # paginate resources one by one, using last fetched resource as a
1478 # marker
1479 resources = []
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001480 for i in range(niterations):
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001481 pagination_args = sort_args.copy()
1482 pagination_args['limit'] = 1
1483 if resources:
1484 pagination_args['marker'] = resources[-1]['id']
1485 body = self.list_method(**pagination_args)
1486 resources_ = self._extract_resources(body)
1487 self.assertEqual(1, len(resources_))
1488 resources.extend(resources_)
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001489 return resources
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001490
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001491 @_require_pagination
1492 @_require_sorting
1493 def _test_list_pagination_with_marker(self):
1494 self._test_list_pagination_iteratively(self._list_all_with_marker)
1495
1496 def _list_all_with_hrefs(self, niterations, sort_args):
1497 # paginate resources one by one, using next href links
1498 resources = []
1499 prev_links = {}
1500
1501 for i in range(niterations):
1502 if prev_links:
1503 uri = self.get_bare_url(prev_links['next'])
1504 else:
Ihar Hrachyshka7f79fe62016-06-07 21:23:44 +02001505 sort_args.update(self.list_kwargs)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001506 uri = self.list_client.build_uri(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001507 self.plural_name, limit=1, **sort_args)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001508 prev_links, body = self.list_client.get_uri_with_links(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001509 self.plural_name, uri
1510 )
1511 resources_ = self._extract_resources(body)
1512 self.assertEqual(1, len(resources_))
1513 resources.extend(resources_)
1514
1515 # The last element is empty and does not contain 'next' link
1516 uri = self.get_bare_url(prev_links['next'])
1517 prev_links, body = self.client.get_uri_with_links(
1518 self.plural_name, uri
1519 )
1520 self.assertNotIn('next', prev_links)
1521
1522 # Now walk backwards and compare results
1523 resources2 = []
1524 for i in range(niterations):
1525 uri = self.get_bare_url(prev_links['previous'])
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001526 prev_links, body = self.list_client.get_uri_with_links(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001527 self.plural_name, uri
1528 )
1529 resources_ = self._extract_resources(body)
1530 self.assertEqual(1, len(resources_))
1531 resources2.extend(resources_)
1532
1533 self.assertSameOrder(resources, reversed(resources2))
1534
1535 return resources
1536
1537 @_require_pagination
1538 @_require_sorting
1539 def _test_list_pagination_with_href_links(self):
1540 self._test_list_pagination_iteratively(self._list_all_with_hrefs)
1541
1542 @_require_pagination
1543 @_require_sorting
1544 def _test_list_pagination_page_reverse_with_href_links(
1545 self, direction=constants.SORT_DIRECTION_ASC):
1546 pagination_args = {
1547 'sort_dir': direction,
Armando Migliaccio57581c62016-07-01 10:13:19 -07001548 'sort_key': self.field,
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001549 }
1550 body = self.list_method(**pagination_args)
1551 expected_resources = self._extract_resources(body)
1552
1553 page_size = 2
1554 pagination_args['limit'] = page_size
1555
1556 prev_links = {}
1557 resources = []
1558 num_resources = len(expected_resources)
1559 niterations = int(math.ceil(float(num_resources) / page_size))
1560 for i in range(niterations):
1561 if prev_links:
1562 uri = self.get_bare_url(prev_links['previous'])
1563 else:
Ihar Hrachyshka7f79fe62016-06-07 21:23:44 +02001564 pagination_args.update(self.list_kwargs)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001565 uri = self.list_client.build_uri(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001566 self.plural_name, page_reverse=True, **pagination_args)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001567 prev_links, body = self.list_client.get_uri_with_links(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001568 self.plural_name, uri
1569 )
1570 resources_ = self._extract_resources(body)
Béla Vancsicsf1806182016-08-23 07:36:18 +02001571 self.assertGreaterEqual(page_size, len(resources_))
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001572 resources.extend(reversed(resources_))
1573
1574 self.assertSameOrder(expected_resources, reversed(resources))
1575
1576 @_require_pagination
1577 @_require_sorting
1578 def _test_list_pagination_page_reverse_asc(self):
1579 self._test_list_pagination_page_reverse(
1580 direction=constants.SORT_DIRECTION_ASC)
1581
1582 @_require_pagination
1583 @_require_sorting
1584 def _test_list_pagination_page_reverse_desc(self):
1585 self._test_list_pagination_page_reverse(
1586 direction=constants.SORT_DIRECTION_DESC)
1587
1588 def _test_list_pagination_page_reverse(self, direction):
1589 pagination_args = {
1590 'sort_dir': direction,
Armando Migliaccio57581c62016-07-01 10:13:19 -07001591 'sort_key': self.field,
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001592 'limit': 3,
1593 }
1594 body = self.list_method(**pagination_args)
1595 expected_resources = self._extract_resources(body)
1596
1597 pagination_args['limit'] -= 1
1598 pagination_args['marker'] = expected_resources[-1]['id']
1599 pagination_args['page_reverse'] = True
1600 body = self.list_method(**pagination_args)
1601
1602 self.assertSameOrder(
1603 # the last entry is not included in 2nd result when used as a
1604 # marker
1605 expected_resources[:-1],
1606 self._extract_resources(body))
Victor Morales1be97b42016-09-05 08:50:06 -05001607
Hongbin Lu54f55922018-07-12 19:05:39 +00001608 @tutils.requires_ext(extension="filter-validation", service="network")
1609 def _test_list_validation_filters(
1610 self, validation_args, filter_is_valid=True):
1611 if not filter_is_valid:
1612 self.assertRaises(lib_exc.BadRequest, self.list_method,
1613 **validation_args)
1614 else:
1615 body = self.list_method(**validation_args)
1616 resources = self._extract_resources(body)
1617 for resource in resources:
1618 self.assertIn(resource['name'], self.resource_names)