blob: e080d42a526a9c5ff055515d3f1d7edca1f2eedb [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
389 cls.client.tenant_id)
390 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])
Miguel Lavalleb1c7a3d2021-01-31 19:05:22 -0600997 if ('remote_address_group_id' in kwargs and 'remote_ip_prefix' in
998 default_params):
999 default_params.pop('remote_ip_prefix')
Federico Ressi4c590d72018-10-10 14:01:08 +02001000 for key, value in default_params.items():
1001 kwargs.setdefault(key, value)
1002
1003 client = client or cls.client
1004 return client.create_security_group_rule(**kwargs)[
1005 'security_group_rule']
1006
1007 @classmethod
1008 def get_security_group(cls, name='default', client=None):
1009 client = client or cls.client
1010 security_groups = client.list_security_groups()['security_groups']
1011 for security_group in security_groups:
1012 if security_group['name'] == name:
1013 return security_group
1014 raise ValueError("No such security group named {!r}".format(name))
Chandan Kumarc125fd12017-11-15 19:41:01 +05301015
Federico Ressiab286e42018-06-19 09:52:10 +02001016 @classmethod
1017 def create_keypair(cls, client=None, name=None, **kwargs):
1018 client = client or cls.os_primary.keypairs_client
1019 name = name or data_utils.rand_name('keypair-test')
1020 keypair = client.create_keypair(name=name, **kwargs)['keypair']
1021
1022 # save client for later cleanup
1023 keypair['client'] = client
1024 cls.keypairs.append(keypair)
1025 return keypair
1026
1027 @classmethod
1028 def delete_keypair(cls, keypair, client=None):
1029 client = (client or keypair.get('client') or
1030 cls.os_primary.keypairs_client)
1031 client.delete_keypair(keypair_name=keypair['name'])
1032
Federico Ressi82e83e32018-07-03 14:19:55 +02001033 @classmethod
1034 def create_trunk(cls, port=None, subports=None, client=None, **kwargs):
1035 """Create network trunk
1036
1037 :param port: dictionary containing parent port ID (port['id'])
1038 :param client: client to be used for connecting to networking service
1039 :param **kwargs: extra parameters to be forwarded to network service
1040
1041 :returns: dictionary containing created trunk details
1042 """
1043 client = client or cls.client
1044
1045 if port:
1046 kwargs['port_id'] = port['id']
1047
1048 trunk = client.create_trunk(subports=subports, **kwargs)['trunk']
1049 # Save client reference for later deletion
1050 trunk['client'] = client
1051 cls.trunks.append(trunk)
1052 return trunk
1053
1054 @classmethod
Huifeng Le1c9f40b2018-11-07 01:14:21 +08001055 def delete_trunk(cls, trunk, client=None, detach_parent_port=True):
Federico Ressi82e83e32018-07-03 14:19:55 +02001056 """Delete network trunk
1057
1058 :param trunk: dictionary containing trunk ID (trunk['id'])
1059
1060 :param client: client to be used for connecting to networking service
1061 """
1062 client = client or trunk.get('client') or cls.client
1063 trunk.update(client.show_trunk(trunk['id'])['trunk'])
1064
1065 if not trunk['admin_state_up']:
1066 # Cannot touch trunk before admin_state_up is True
1067 client.update_trunk(trunk['id'], admin_state_up=True)
1068 if trunk['sub_ports']:
1069 # Removes trunk ports before deleting it
1070 cls._try_delete_resource(client.remove_subports, trunk['id'],
1071 trunk['sub_ports'])
1072
1073 # we have to detach the interface from the server before
1074 # the trunk can be deleted.
1075 parent_port = {'id': trunk['port_id']}
1076
1077 def is_parent_port_detached():
1078 parent_port.update(client.show_port(parent_port['id'])['port'])
1079 return not parent_port['device_id']
1080
Huifeng Le1c9f40b2018-11-07 01:14:21 +08001081 if detach_parent_port and not is_parent_port_detached():
Federico Ressi82e83e32018-07-03 14:19:55 +02001082 # this could probably happen when trunk is deleted and parent port
1083 # has been assigned to a VM that is still running. Here we are
1084 # assuming that device_id points to such VM.
1085 cls.os_primary.compute.InterfacesClient().delete_interface(
1086 parent_port['device_id'], parent_port['id'])
1087 utils.wait_until_true(is_parent_port_detached)
1088
1089 client.delete_trunk(trunk['id'])
1090
Harald Jensåsc9782fa2019-06-03 22:35:41 +02001091 @classmethod
1092 def create_conntrack_helper(cls, router_id, helper, protocol, port,
1093 client=None):
1094 """Create a conntrack helper
1095
1096 Create a conntrack helper and schedule it for later deletion. If a
1097 client is passed, then it is used for deleteing the CTH too.
1098
1099 :param router_id: The ID of the Neutron router associated to the
1100 conntrack helper.
1101
1102 :param helper: The conntrack helper module alias
1103
1104 :param protocol: The conntrack helper IP protocol used in the conntrack
1105 helper.
1106
1107 :param port: The conntrack helper IP protocol port number for the
1108 conntrack helper.
1109
1110 :param client: network client to be used for creating and cleaning up
1111 the conntrack helper.
1112 """
1113
1114 client = client or cls.client
1115
1116 cth = client.create_conntrack_helper(router_id, helper, protocol,
1117 port)['conntrack_helper']
1118
1119 # save ID of router associated with conntrack helper for final cleanup
1120 cth['router_id'] = router_id
1121
1122 # save client to be used later in cls.delete_conntrack_helper for final
1123 # cleanup
1124 cth['client'] = client
1125 cls.conntrack_helpers.append(cth)
1126 return cth
1127
1128 @classmethod
1129 def delete_conntrack_helper(cls, cth, client=None):
1130 """Delete conntrack helper
1131
1132 :param client: Client to be used
1133 If client is not given it will use the client used to create the
1134 conntrack helper, or cls.client if unknown.
1135 """
1136
1137 client = client or cth.get('client') or cls.client
1138 client.delete_conntrack_helper(cth['router_id'], cth['id'])
1139
yangjianfeng2936a292022-02-04 11:22:11 +08001140 @classmethod
1141 def create_ndp_proxy(cls, router_id, port_id, client=None, **kwargs):
1142 """Creates a ndp proxy.
1143
1144 Create a ndp proxy and schedule it for later deletion.
1145 If a client is passed, then it is used for deleting the NDP proxy too.
1146
1147 :param router_id: router ID where to create the ndp proxy.
1148
1149 :param port_id: port ID which the ndp proxy associate with
1150
1151 :param client: network client to be used for creating and cleaning up
1152 the ndp proxy.
1153
1154 :param **kwargs: additional creation parameters to be forwarded to
1155 networking server.
1156 """
1157 client = client or cls.client
1158
1159 data = {'router_id': router_id, 'port_id': port_id}
1160 if kwargs:
1161 data.update(kwargs)
1162 ndp_proxy = client.create_ndp_proxy(**data)['ndp_proxy']
1163
1164 # save client to be used later in cls.delete_ndp_proxy
1165 # for final cleanup
1166 ndp_proxy['client'] = client
1167 cls.ndp_proxies.append(ndp_proxy)
1168 return ndp_proxy
1169
1170 @classmethod
1171 def delete_ndp_proxy(cls, ndp_proxy, client=None):
1172 """Delete ndp proxy
1173
1174 :param client: Client to be used
1175 If client is not given it will use the client used to create
1176 the ndp proxy, or cls.client if unknown.
1177 """
1178 client = client or ndp_proxy.get('client') or cls.client
1179 client.delete_ndp_proxy(ndp_proxy['id'])
1180
Daniel Mellado3c0aeab2016-01-29 11:30:25 +00001181
1182class BaseAdminNetworkTest(BaseNetworkTest):
1183
1184 credentials = ['primary', 'admin']
1185
1186 @classmethod
1187 def setup_clients(cls):
1188 super(BaseAdminNetworkTest, cls).setup_clients()
fumihiko kakumaa216fc12017-07-14 10:43:29 +09001189 cls.admin_client = cls.os_admin.network_client
Jakub Libosvarf5758012017-08-15 13:45:30 +00001190 cls.identity_admin_client = cls.os_admin.projects_client
Daniel Mellado3c0aeab2016-01-29 11:30:25 +00001191
1192 @classmethod
1193 def create_metering_label(cls, name, description):
1194 """Wrapper utility that returns a test metering label."""
1195 body = cls.admin_client.create_metering_label(
1196 description=description,
1197 name=data_utils.rand_name("metering-label"))
1198 metering_label = body['metering_label']
1199 cls.metering_labels.append(metering_label)
1200 return metering_label
1201
1202 @classmethod
1203 def create_metering_label_rule(cls, remote_ip_prefix, direction,
1204 metering_label_id):
1205 """Wrapper utility that returns a test metering label rule."""
1206 body = cls.admin_client.create_metering_label_rule(
1207 remote_ip_prefix=remote_ip_prefix, direction=direction,
1208 metering_label_id=metering_label_id)
1209 metering_label_rule = body['metering_label_rule']
1210 cls.metering_label_rules.append(metering_label_rule)
1211 return metering_label_rule
1212
1213 @classmethod
Kailun Qineaaf9782018-12-20 04:45:01 +08001214 def create_network_segment_range(cls, name, shared,
1215 project_id, network_type,
1216 physical_network, minimum,
1217 maximum):
1218 """Wrapper utility that returns a test network segment range."""
1219 network_segment_range_args = {'name': name,
1220 'shared': shared,
1221 'project_id': project_id,
1222 'network_type': network_type,
1223 'physical_network': physical_network,
1224 'minimum': minimum,
1225 'maximum': maximum}
1226 body = cls.admin_client.create_network_segment_range(
1227 **network_segment_range_args)
1228 network_segment_range = body['network_segment_range']
1229 cls.network_segment_ranges.append(network_segment_range)
1230 return network_segment_range
1231
1232 @classmethod
Daniel Mellado3c0aeab2016-01-29 11:30:25 +00001233 def create_flavor(cls, name, description, service_type):
1234 """Wrapper utility that returns a test flavor."""
1235 body = cls.admin_client.create_flavor(
1236 description=description, service_type=service_type,
1237 name=name)
1238 flavor = body['flavor']
1239 cls.flavors.append(flavor)
1240 return flavor
1241
1242 @classmethod
1243 def create_service_profile(cls, description, metainfo, driver):
1244 """Wrapper utility that returns a test service profile."""
1245 body = cls.admin_client.create_service_profile(
1246 driver=driver, metainfo=metainfo, description=description)
1247 service_profile = body['service_profile']
1248 cls.service_profiles.append(service_profile)
1249 return service_profile
1250
1251 @classmethod
Nguyen Phuong An67993fc2017-11-24 11:30:25 +07001252 def create_log(cls, name, description=None,
1253 resource_type='security_group', resource_id=None,
1254 target_id=None, event='ALL', enabled=True):
1255 """Wrapper utility that returns a test log object."""
1256 log_args = {'name': name,
Nguyen Phuong An67993fc2017-11-24 11:30:25 +07001257 'resource_type': resource_type,
1258 'resource_id': resource_id,
1259 'target_id': target_id,
1260 'event': event,
1261 'enabled': enabled}
Slawek Kaplonskid9fe3022021-08-11 15:25:16 +02001262 if description:
1263 log_args['description'] = description
Nguyen Phuong An67993fc2017-11-24 11:30:25 +07001264 body = cls.admin_client.create_log(**log_args)
1265 log_object = body['log']
1266 cls.log_objects.append(log_object)
1267 return log_object
1268
1269 @classmethod
Daniel Mellado3c0aeab2016-01-29 11:30:25 +00001270 def get_unused_ip(cls, net_id, ip_version=None):
Gary Kotton011345f2016-06-15 08:04:31 -07001271 """Get an unused ip address in a allocation pool of net"""
Daniel Mellado3c0aeab2016-01-29 11:30:25 +00001272 body = cls.admin_client.list_ports(network_id=net_id)
1273 ports = body['ports']
1274 used_ips = []
1275 for port in ports:
1276 used_ips.extend(
1277 [fixed_ip['ip_address'] for fixed_ip in port['fixed_ips']])
1278 body = cls.admin_client.list_subnets(network_id=net_id)
1279 subnets = body['subnets']
1280
1281 for subnet in subnets:
1282 if ip_version and subnet['ip_version'] != ip_version:
1283 continue
1284 cidr = subnet['cidr']
1285 allocation_pools = subnet['allocation_pools']
1286 iterators = []
1287 if allocation_pools:
1288 for allocation_pool in allocation_pools:
1289 iterators.append(netaddr.iter_iprange(
1290 allocation_pool['start'], allocation_pool['end']))
1291 else:
1292 net = netaddr.IPNetwork(cidr)
1293
1294 def _iterip():
1295 for ip in net:
1296 if ip not in (net.network, net.broadcast):
1297 yield ip
1298 iterators.append(iter(_iterip()))
1299
1300 for iterator in iterators:
1301 for ip in iterator:
1302 if str(ip) not in used_ips:
1303 return str(ip)
1304
1305 message = (
1306 "net(%s) has no usable IP address in allocation pools" % net_id)
1307 raise exceptions.InvalidConfiguration(message)
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001308
Lajos Katona2f904652018-08-23 14:04:56 +02001309 @classmethod
1310 def create_provider_network(cls, physnet_name, start_segmentation_id,
1311 max_attempts=30):
1312 segmentation_id = start_segmentation_id
Lajos Katona7eb67252019-01-14 12:55:35 +01001313 for attempts in range(max_attempts):
Lajos Katona2f904652018-08-23 14:04:56 +02001314 try:
Lajos Katona7eb67252019-01-14 12:55:35 +01001315 return cls.create_network(
Lajos Katona2f904652018-08-23 14:04:56 +02001316 name=data_utils.rand_name('test_net'),
1317 shared=True,
1318 provider_network_type='vlan',
1319 provider_physical_network=physnet_name,
1320 provider_segmentation_id=segmentation_id)
Lajos Katona2f904652018-08-23 14:04:56 +02001321 except lib_exc.Conflict:
Lajos Katona2f904652018-08-23 14:04:56 +02001322 segmentation_id += 1
1323 if segmentation_id > 4095:
1324 raise lib_exc.TempestException(
1325 "No free segmentation id was found for provider "
1326 "network creation!")
1327 time.sleep(CONF.network.build_interval)
Lajos Katona7eb67252019-01-14 12:55:35 +01001328 LOG.exception("Failed to create provider network after "
1329 "%d attempts", max_attempts)
1330 raise lib_exc.TimeoutException
Lajos Katona2f904652018-08-23 14:04:56 +02001331
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001332
Sławek Kapłońskiff294062016-12-04 15:00:54 +00001333def require_qos_rule_type(rule_type):
1334 def decorator(f):
1335 @functools.wraps(f)
1336 def wrapper(self, *func_args, **func_kwargs):
1337 if rule_type not in self.get_supported_qos_rule_types():
1338 raise self.skipException(
1339 "%s rule type is required." % rule_type)
1340 return f(self, *func_args, **func_kwargs)
1341 return wrapper
1342 return decorator
1343
1344
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001345def _require_sorting(f):
1346 @functools.wraps(f)
1347 def inner(self, *args, **kwargs):
Chandan Kumarc125fd12017-11-15 19:41:01 +05301348 if not tutils.is_extension_enabled("sorting", "network"):
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001349 self.skipTest('Sorting feature is required')
1350 return f(self, *args, **kwargs)
1351 return inner
1352
1353
1354def _require_pagination(f):
1355 @functools.wraps(f)
1356 def inner(self, *args, **kwargs):
Chandan Kumarc125fd12017-11-15 19:41:01 +05301357 if not tutils.is_extension_enabled("pagination", "network"):
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001358 self.skipTest('Pagination feature is required')
1359 return f(self, *args, **kwargs)
1360 return inner
1361
1362
1363class BaseSearchCriteriaTest(BaseNetworkTest):
1364
1365 # This should be defined by subclasses to reflect resource name to test
1366 resource = None
1367
Armando Migliaccio57581c62016-07-01 10:13:19 -07001368 field = 'name'
1369
Ihar Hrachyshkaa8fe5a12016-05-24 14:50:58 +02001370 # NOTE(ihrachys): some names, like those starting with an underscore (_)
1371 # are sorted differently depending on whether the plugin implements native
1372 # sorting support, or not. So we avoid any such cases here, sticking to
1373 # alphanumeric. Also test a case when there are multiple resources with the
1374 # same name
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001375 resource_names = ('test1', 'abc1', 'test10', '123test') + ('test1',)
1376
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001377 force_tenant_isolation = True
1378
Ihar Hrachyshkaa8fe5a12016-05-24 14:50:58 +02001379 list_kwargs = {}
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001380
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001381 list_as_admin = False
1382
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001383 def assertSameOrder(self, original, actual):
1384 # gracefully handle iterators passed
1385 original = list(original)
1386 actual = list(actual)
1387 self.assertEqual(len(original), len(actual))
1388 for expected, res in zip(original, actual):
Armando Migliaccio57581c62016-07-01 10:13:19 -07001389 self.assertEqual(expected[self.field], res[self.field])
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001390
1391 @utils.classproperty
1392 def plural_name(self):
1393 return '%ss' % self.resource
1394
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001395 @property
1396 def list_client(self):
1397 return self.admin_client if self.list_as_admin else self.client
1398
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001399 def list_method(self, *args, **kwargs):
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001400 method = getattr(self.list_client, 'list_%s' % self.plural_name)
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001401 kwargs.update(self.list_kwargs)
1402 return method(*args, **kwargs)
1403
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001404 def get_bare_url(self, url):
1405 base_url = self.client.base_url
zheng.yong74e760a2019-05-22 14:16:14 +08001406 base_url_normalized = utils.normalize_url(base_url)
1407 url_normalized = utils.normalize_url(url)
1408 self.assertTrue(url_normalized.startswith(base_url_normalized))
1409 return url_normalized[len(base_url_normalized):]
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001410
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001411 @classmethod
1412 def _extract_resources(cls, body):
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001413 return body[cls.plural_name]
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001414
1415 def _test_list_sorts(self, direction):
1416 sort_args = {
1417 'sort_dir': direction,
Armando Migliaccio57581c62016-07-01 10:13:19 -07001418 'sort_key': self.field
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001419 }
1420 body = self.list_method(**sort_args)
1421 resources = self._extract_resources(body)
1422 self.assertNotEmpty(
1423 resources, "%s list returned is empty" % self.resource)
Armando Migliaccio57581c62016-07-01 10:13:19 -07001424 retrieved_names = [res[self.field] for res in resources]
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001425 expected = sorted(retrieved_names)
1426 if direction == constants.SORT_DIRECTION_DESC:
1427 expected = list(reversed(expected))
1428 self.assertEqual(expected, retrieved_names)
1429
1430 @_require_sorting
1431 def _test_list_sorts_asc(self):
1432 self._test_list_sorts(constants.SORT_DIRECTION_ASC)
1433
1434 @_require_sorting
1435 def _test_list_sorts_desc(self):
1436 self._test_list_sorts(constants.SORT_DIRECTION_DESC)
1437
1438 @_require_pagination
1439 def _test_list_pagination(self):
1440 for limit in range(1, len(self.resource_names) + 1):
1441 pagination_args = {
1442 'limit': limit,
1443 }
1444 body = self.list_method(**pagination_args)
1445 resources = self._extract_resources(body)
1446 self.assertEqual(limit, len(resources))
1447
1448 @_require_pagination
1449 def _test_list_no_pagination_limit_0(self):
1450 pagination_args = {
1451 'limit': 0,
1452 }
1453 body = self.list_method(**pagination_args)
1454 resources = self._extract_resources(body)
Béla Vancsicsf1806182016-08-23 07:36:18 +02001455 self.assertGreaterEqual(len(resources), len(self.resource_names))
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001456
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001457 def _test_list_pagination_iteratively(self, lister):
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001458 # first, collect all resources for later comparison
1459 sort_args = {
1460 'sort_dir': constants.SORT_DIRECTION_ASC,
Armando Migliaccio57581c62016-07-01 10:13:19 -07001461 'sort_key': self.field
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001462 }
1463 body = self.list_method(**sort_args)
1464 expected_resources = self._extract_resources(body)
1465 self.assertNotEmpty(expected_resources)
1466
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001467 resources = lister(
1468 len(expected_resources), sort_args
1469 )
1470
1471 # finally, compare that the list retrieved in one go is identical to
1472 # the one containing pagination results
1473 self.assertSameOrder(expected_resources, resources)
1474
1475 def _list_all_with_marker(self, niterations, sort_args):
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001476 # paginate resources one by one, using last fetched resource as a
1477 # marker
1478 resources = []
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001479 for i in range(niterations):
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001480 pagination_args = sort_args.copy()
1481 pagination_args['limit'] = 1
1482 if resources:
1483 pagination_args['marker'] = resources[-1]['id']
1484 body = self.list_method(**pagination_args)
1485 resources_ = self._extract_resources(body)
1486 self.assertEqual(1, len(resources_))
1487 resources.extend(resources_)
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001488 return resources
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001489
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001490 @_require_pagination
1491 @_require_sorting
1492 def _test_list_pagination_with_marker(self):
1493 self._test_list_pagination_iteratively(self._list_all_with_marker)
1494
1495 def _list_all_with_hrefs(self, niterations, sort_args):
1496 # paginate resources one by one, using next href links
1497 resources = []
1498 prev_links = {}
1499
1500 for i in range(niterations):
1501 if prev_links:
1502 uri = self.get_bare_url(prev_links['next'])
1503 else:
Ihar Hrachyshka7f79fe62016-06-07 21:23:44 +02001504 sort_args.update(self.list_kwargs)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001505 uri = self.list_client.build_uri(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001506 self.plural_name, limit=1, **sort_args)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001507 prev_links, body = self.list_client.get_uri_with_links(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001508 self.plural_name, uri
1509 )
1510 resources_ = self._extract_resources(body)
1511 self.assertEqual(1, len(resources_))
1512 resources.extend(resources_)
1513
1514 # The last element is empty and does not contain 'next' link
1515 uri = self.get_bare_url(prev_links['next'])
1516 prev_links, body = self.client.get_uri_with_links(
1517 self.plural_name, uri
1518 )
1519 self.assertNotIn('next', prev_links)
1520
1521 # Now walk backwards and compare results
1522 resources2 = []
1523 for i in range(niterations):
1524 uri = self.get_bare_url(prev_links['previous'])
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001525 prev_links, body = self.list_client.get_uri_with_links(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001526 self.plural_name, uri
1527 )
1528 resources_ = self._extract_resources(body)
1529 self.assertEqual(1, len(resources_))
1530 resources2.extend(resources_)
1531
1532 self.assertSameOrder(resources, reversed(resources2))
1533
1534 return resources
1535
1536 @_require_pagination
1537 @_require_sorting
1538 def _test_list_pagination_with_href_links(self):
1539 self._test_list_pagination_iteratively(self._list_all_with_hrefs)
1540
1541 @_require_pagination
1542 @_require_sorting
1543 def _test_list_pagination_page_reverse_with_href_links(
1544 self, direction=constants.SORT_DIRECTION_ASC):
1545 pagination_args = {
1546 'sort_dir': direction,
Armando Migliaccio57581c62016-07-01 10:13:19 -07001547 'sort_key': self.field,
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001548 }
1549 body = self.list_method(**pagination_args)
1550 expected_resources = self._extract_resources(body)
1551
1552 page_size = 2
1553 pagination_args['limit'] = page_size
1554
1555 prev_links = {}
1556 resources = []
1557 num_resources = len(expected_resources)
1558 niterations = int(math.ceil(float(num_resources) / page_size))
1559 for i in range(niterations):
1560 if prev_links:
1561 uri = self.get_bare_url(prev_links['previous'])
1562 else:
Ihar Hrachyshka7f79fe62016-06-07 21:23:44 +02001563 pagination_args.update(self.list_kwargs)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001564 uri = self.list_client.build_uri(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001565 self.plural_name, page_reverse=True, **pagination_args)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001566 prev_links, body = self.list_client.get_uri_with_links(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001567 self.plural_name, uri
1568 )
1569 resources_ = self._extract_resources(body)
Béla Vancsicsf1806182016-08-23 07:36:18 +02001570 self.assertGreaterEqual(page_size, len(resources_))
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001571 resources.extend(reversed(resources_))
1572
1573 self.assertSameOrder(expected_resources, reversed(resources))
1574
1575 @_require_pagination
1576 @_require_sorting
1577 def _test_list_pagination_page_reverse_asc(self):
1578 self._test_list_pagination_page_reverse(
1579 direction=constants.SORT_DIRECTION_ASC)
1580
1581 @_require_pagination
1582 @_require_sorting
1583 def _test_list_pagination_page_reverse_desc(self):
1584 self._test_list_pagination_page_reverse(
1585 direction=constants.SORT_DIRECTION_DESC)
1586
1587 def _test_list_pagination_page_reverse(self, direction):
1588 pagination_args = {
1589 'sort_dir': direction,
Armando Migliaccio57581c62016-07-01 10:13:19 -07001590 'sort_key': self.field,
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001591 'limit': 3,
1592 }
1593 body = self.list_method(**pagination_args)
1594 expected_resources = self._extract_resources(body)
1595
1596 pagination_args['limit'] -= 1
1597 pagination_args['marker'] = expected_resources[-1]['id']
1598 pagination_args['page_reverse'] = True
1599 body = self.list_method(**pagination_args)
1600
1601 self.assertSameOrder(
1602 # the last entry is not included in 2nd result when used as a
1603 # marker
1604 expected_resources[:-1],
1605 self._extract_resources(body))
Victor Morales1be97b42016-09-05 08:50:06 -05001606
Hongbin Lu54f55922018-07-12 19:05:39 +00001607 @tutils.requires_ext(extension="filter-validation", service="network")
1608 def _test_list_validation_filters(
1609 self, validation_args, filter_is_valid=True):
1610 if not filter_is_valid:
1611 self.assertRaises(lib_exc.BadRequest, self.list_method,
1612 **validation_args)
1613 else:
1614 body = self.list_method(**validation_args)
1615 resources = self._extract_resources(body)
1616 for resource in resources:
1617 self.assertIn(resource['name'], self.resource_names)