blob: 29431982f6ce67be06bcbb4d4ecf2709abfdeffe [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 = []
120 cls.metering_labels = []
121 cls.service_profiles = []
122 cls.flavors = []
123 cls.metering_label_rules = []
124 cls.qos_rules = []
125 cls.qos_policies = []
126 cls.ethertype = "IPv" + str(cls._ip_version)
127 cls.address_scopes = []
128 cls.admin_address_scopes = []
129 cls.subnetpools = []
130 cls.admin_subnetpools = []
Itzik Brownbac51dc2016-10-31 12:25:04 +0000131 cls.security_groups = []
Dongcan Ye2de722e2018-07-04 11:01:37 +0000132 cls.admin_security_groups = []
Chandan Kumarc125fd12017-11-15 19:41:01 +0530133 cls.projects = []
Nguyen Phuong An67993fc2017-11-24 11:30:25 +0700134 cls.log_objects = []
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200135 cls.reserved_subnet_cidrs = set()
Federico Ressiab286e42018-06-19 09:52:10 +0200136 cls.keypairs = []
Federico Ressi82e83e32018-07-03 14:19:55 +0200137 cls.trunks = []
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000138
139 @classmethod
140 def resource_cleanup(cls):
141 if CONF.service_available.neutron:
Federico Ressi82e83e32018-07-03 14:19:55 +0200142 # Clean up trunks
143 for trunk in cls.trunks:
144 cls._try_delete_resource(cls.delete_trunk, trunk)
145
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000146 # Clean up floating IPs
147 for floating_ip in cls.floating_ips:
Federico Ressia69dcd52018-07-06 09:45:34 +0200148 cls._try_delete_resource(cls.delete_floatingip, floating_ip)
149
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000150 # Clean up routers
151 for router in cls.routers:
152 cls._try_delete_resource(cls.delete_router,
153 router)
154 # Clean up metering label rules
155 for metering_label_rule in cls.metering_label_rules:
156 cls._try_delete_resource(
157 cls.admin_client.delete_metering_label_rule,
158 metering_label_rule['id'])
159 # Clean up metering labels
160 for metering_label in cls.metering_labels:
161 cls._try_delete_resource(
162 cls.admin_client.delete_metering_label,
163 metering_label['id'])
164 # Clean up flavors
165 for flavor in cls.flavors:
166 cls._try_delete_resource(
167 cls.admin_client.delete_flavor,
168 flavor['id'])
169 # Clean up service profiles
170 for service_profile in cls.service_profiles:
171 cls._try_delete_resource(
172 cls.admin_client.delete_service_profile,
173 service_profile['id'])
174 # Clean up ports
175 for port in cls.ports:
176 cls._try_delete_resource(cls.client.delete_port,
177 port['id'])
178 # Clean up subnets
179 for subnet in cls.subnets:
180 cls._try_delete_resource(cls.client.delete_subnet,
181 subnet['id'])
Kevin Bentonba3651c2017-09-01 17:13:01 -0700182 # Clean up admin subnets
183 for subnet in cls.admin_subnets:
184 cls._try_delete_resource(cls.admin_client.delete_subnet,
185 subnet['id'])
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000186 # Clean up networks
187 for network in cls.networks:
Federico Ressi61b564e2018-07-06 08:10:31 +0200188 cls._try_delete_resource(cls.delete_network, network)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000189
Miguel Lavalle124378b2016-09-21 16:41:47 -0500190 # Clean up admin networks
191 for network in cls.admin_networks:
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000192 cls._try_delete_resource(cls.admin_client.delete_network,
193 network['id'])
194
Itzik Brownbac51dc2016-10-31 12:25:04 +0000195 # Clean up security groups
Federico Ressi4c590d72018-10-10 14:01:08 +0200196 for security_group in cls.security_groups:
197 cls._try_delete_resource(cls.delete_security_group,
198 security_group)
Itzik Brownbac51dc2016-10-31 12:25:04 +0000199
Dongcan Ye2de722e2018-07-04 11:01:37 +0000200 # Clean up admin security groups
Federico Ressi4c590d72018-10-10 14:01:08 +0200201 for security_group in cls.admin_security_groups:
202 cls._try_delete_resource(cls.delete_security_group,
203 security_group,
204 client=cls.admin_client)
Dongcan Ye2de722e2018-07-04 11:01:37 +0000205
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000206 for subnetpool in cls.subnetpools:
207 cls._try_delete_resource(cls.client.delete_subnetpool,
208 subnetpool['id'])
209
210 for subnetpool in cls.admin_subnetpools:
211 cls._try_delete_resource(cls.admin_client.delete_subnetpool,
212 subnetpool['id'])
213
214 for address_scope in cls.address_scopes:
215 cls._try_delete_resource(cls.client.delete_address_scope,
216 address_scope['id'])
217
218 for address_scope in cls.admin_address_scopes:
219 cls._try_delete_resource(
220 cls.admin_client.delete_address_scope,
221 address_scope['id'])
222
Chandan Kumarc125fd12017-11-15 19:41:01 +0530223 for project in cls.projects:
224 cls._try_delete_resource(
225 cls.identity_admin_client.delete_project,
226 project['id'])
227
Sławek Kapłońskie100c4d2017-08-23 21:18:34 +0000228 # Clean up QoS rules
229 for qos_rule in cls.qos_rules:
230 cls._try_delete_resource(cls.admin_client.delete_qos_rule,
231 qos_rule['id'])
232 # Clean up QoS policies
233 # as all networks and ports are already removed, QoS policies
234 # shouldn't be "in use"
235 for qos_policy in cls.qos_policies:
236 cls._try_delete_resource(cls.admin_client.delete_qos_policy,
237 qos_policy['id'])
238
Nguyen Phuong An67993fc2017-11-24 11:30:25 +0700239 # Clean up log_objects
240 for log_object in cls.log_objects:
241 cls._try_delete_resource(cls.admin_client.delete_log,
242 log_object['id'])
243
Federico Ressiab286e42018-06-19 09:52:10 +0200244 for keypair in cls.keypairs:
245 cls._try_delete_resource(cls.delete_keypair, keypair)
246
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000247 super(BaseNetworkTest, cls).resource_cleanup()
248
249 @classmethod
250 def _try_delete_resource(cls, delete_callable, *args, **kwargs):
251 """Cleanup resources in case of test-failure
252
253 Some resources are explicitly deleted by the test.
254 If the test failed to delete a resource, this method will execute
255 the appropriate delete methods. Otherwise, the method ignores NotFound
256 exceptions thrown for resources that were correctly deleted by the
257 test.
258
259 :param delete_callable: delete method
260 :param args: arguments for delete method
261 :param kwargs: keyword arguments for delete method
262 """
263 try:
264 delete_callable(*args, **kwargs)
265 # if resource is not found, this means it was deleted in the test
266 except lib_exc.NotFound:
267 pass
268
269 @classmethod
Federico Ressi61b564e2018-07-06 08:10:31 +0200270 def create_network(cls, network_name=None, client=None, external=None,
271 shared=None, provider_network_type=None,
272 provider_physical_network=None,
273 provider_segmentation_id=None, **kwargs):
274 """Create a network.
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000275
Federico Ressi61b564e2018-07-06 08:10:31 +0200276 When client is not provider and admin_client is attribute is not None
277 (for example when using BaseAdminNetworkTest base class) and using any
278 of the convenience parameters (external, shared, provider_network_type,
279 provider_physical_network and provider_segmentation_id) it silently
280 uses admin_client. If the network is not shared then it uses the same
281 project_id as regular client.
282
283 :param network_name: Human-readable name of the network
284
285 :param client: client to be used for connecting to network service
286
287 :param external: indicates whether the network has an external routing
288 facility that's not managed by the networking service.
289
290 :param shared: indicates whether this resource is shared across all
291 projects. By default, only administrative users can change this value.
292 If True and admin_client attribute is not None, then the network is
293 created under administrative project.
294
295 :param provider_network_type: the type of physical network that this
296 network should be mapped to. For example, 'flat', 'vlan', 'vxlan', or
297 'gre'. Valid values depend on a networking back-end.
298
299 :param provider_physical_network: the physical network where this
300 network should be implemented. The Networking API v2.0 does not provide
301 a way to list available physical networks. For example, the Open
302 vSwitch plug-in configuration file defines a symbolic name that maps to
303 specific bridges on each compute host.
304
305 :param provider_segmentation_id: The ID of the isolated segment on the
306 physical network. The network_type attribute defines the segmentation
307 model. For example, if the network_type value is 'vlan', this ID is a
308 vlan identifier. If the network_type value is 'gre', this ID is a gre
309 key.
310
311 :param **kwargs: extra parameters to be forwarded to network service
312 """
313
314 name = (network_name or kwargs.pop('name', None) or
315 data_utils.rand_name('test-network-'))
316
317 # translate convenience parameters
318 admin_client_required = False
319 if provider_network_type:
320 admin_client_required = True
321 kwargs['provider:network_type'] = provider_network_type
322 if provider_physical_network:
323 admin_client_required = True
324 kwargs['provider:physical_network'] = provider_physical_network
325 if provider_segmentation_id:
326 admin_client_required = True
327 kwargs['provider:segmentation_id'] = provider_segmentation_id
328 if external is not None:
329 admin_client_required = True
330 kwargs['router:external'] = bool(external)
331 if shared is not None:
332 admin_client_required = True
333 kwargs['shared'] = bool(shared)
334
335 if not client:
336 if admin_client_required and cls.admin_client:
337 # For convenience silently switch to admin client
338 client = cls.admin_client
339 if not shared:
340 # Keep this network visible from current project
341 project_id = (kwargs.get('project_id') or
342 kwargs.get('tenant_id') or
343 cls.client.tenant_id)
344 kwargs.update(project_id=project_id, tenant_id=project_id)
345 else:
346 # Use default client
347 client = cls.client
348
349 network = client.create_network(name=name, **kwargs)['network']
350 network['client'] = client
351 cls.networks.append(network)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000352 return network
353
354 @classmethod
Federico Ressi61b564e2018-07-06 08:10:31 +0200355 def delete_network(cls, network, client=None):
356 client = client or network.get('client') or cls.client
357 client.delete_network(network['id'])
358
359 @classmethod
360 def create_shared_network(cls, network_name=None, **kwargs):
361 return cls.create_network(name=network_name, shared=True, **kwargs)
Miguel Lavalle124378b2016-09-21 16:41:47 -0500362
363 @classmethod
364 def create_network_keystone_v3(cls, network_name=None, project_id=None,
365 tenant_id=None, client=None):
Federico Ressi61b564e2018-07-06 08:10:31 +0200366 params = {}
367 if project_id:
368 params['project_id'] = project_id
369 if tenant_id:
370 params['tenant_id'] = tenant_id
371 return cls.create_network(name=network_name, client=client, **params)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000372
373 @classmethod
Sławek Kapłońskid98e27d2018-05-07 16:16:28 +0200374 def create_subnet(cls, network, gateway='', cidr=None, mask_bits=None,
Federico Ressi98f20ec2018-05-11 06:09:49 +0200375 ip_version=None, client=None, reserve_cidr=True,
376 **kwargs):
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200377 """Wrapper utility that returns a test subnet.
378
379 Convenient wrapper for client.create_subnet method. It reserves and
380 allocates CIDRs to avoid creating overlapping subnets.
381
382 :param network: network where to create the subnet
383 network['id'] must contain the ID of the network
384
385 :param gateway: gateway IP address
386 It can be a str or a netaddr.IPAddress
387 If gateway is not given, then it will use default address for
388 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 +0200389 if gateway is given as None then no gateway will be assigned
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200390
391 :param cidr: CIDR of the subnet to create
392 It can be either None, a str or a netaddr.IPNetwork instance
393
394 :param mask_bits: CIDR prefix length
395 It can be either None or a numeric value.
396 If cidr parameter is given then mask_bits is used to determinate a
397 sequence of valid CIDR to use as generated.
398 Please see netaddr.IPNetwork.subnet method documentation[1]
399
400 :param ip_version: ip version of generated subnet CIDRs
401 It can be None, IP_VERSION_4 or IP_VERSION_6
402 It has to match given either given CIDR and gateway
403
404 :param ip_version: numeric value (either IP_VERSION_4 or IP_VERSION_6)
405 this value must match CIDR and gateway IP versions if any of them is
406 given
407
408 :param client: client to be used to connect to network service
409
Federico Ressi98f20ec2018-05-11 06:09:49 +0200410 :param reserve_cidr: if True then it reserves assigned CIDR to avoid
411 using the same CIDR for further subnets in the scope of the same
412 test case class
413
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200414 :param **kwargs: optional parameters to be forwarded to wrapped method
415
416 [1] http://netaddr.readthedocs.io/en/latest/tutorial_01.html#supernets-and-subnets # noqa
417 """
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000418
419 # allow tests to use admin client
420 if not client:
421 client = cls.client
422
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200423 if gateway:
424 gateway_ip = netaddr.IPAddress(gateway)
425 if ip_version:
426 if ip_version != gateway_ip.version:
427 raise ValueError(
428 "Gateway IP version doesn't match IP version")
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000429 else:
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200430 ip_version = gateway_ip.version
Sławek Kapłońskid98e27d2018-05-07 16:16:28 +0200431 else:
432 ip_version = ip_version or cls._ip_version
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200433
434 for subnet_cidr in cls.get_subnet_cidrs(
435 ip_version=ip_version, cidr=cidr, mask_bits=mask_bits):
Federico Ressi98f20ec2018-05-11 06:09:49 +0200436 if gateway is not None:
437 kwargs['gateway_ip'] = str(gateway or (subnet_cidr.ip + 1))
Slawek Kaplonski21f53422018-11-02 16:02:09 +0100438 else:
439 kwargs['gateway_ip'] = None
Federico Ressi98f20ec2018-05-11 06:09:49 +0200440 try:
441 body = client.create_subnet(
442 network_id=network['id'],
443 cidr=str(subnet_cidr),
444 ip_version=subnet_cidr.version,
445 **kwargs)
446 break
447 except lib_exc.BadRequest as e:
448 if 'overlaps with another subnet' not in str(e):
449 raise
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000450 else:
451 message = 'Available CIDR for subnet creation could not be found'
452 raise ValueError(message)
453 subnet = body['subnet']
Kevin Bentonba3651c2017-09-01 17:13:01 -0700454 if client is cls.client:
455 cls.subnets.append(subnet)
456 else:
457 cls.admin_subnets.append(subnet)
Federico Ressi98f20ec2018-05-11 06:09:49 +0200458 if reserve_cidr:
459 cls.reserve_subnet_cidr(subnet_cidr)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000460 return subnet
461
462 @classmethod
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200463 def reserve_subnet_cidr(cls, addr, **ipnetwork_kwargs):
464 """Reserve given subnet CIDR making sure it is not used by create_subnet
465
466 :param addr: the CIDR address to be reserved
467 It can be a str or netaddr.IPNetwork instance
468
469 :param **ipnetwork_kwargs: optional netaddr.IPNetwork constructor
470 parameters
471 """
472
473 if not cls.try_reserve_subnet_cidr(addr, **ipnetwork_kwargs):
474 raise ValueError('Subnet CIDR already reserved: %r'.format(
475 addr))
476
477 @classmethod
478 def try_reserve_subnet_cidr(cls, addr, **ipnetwork_kwargs):
479 """Reserve given subnet CIDR if it hasn't been reserved before
480
481 :param addr: the CIDR address to be reserved
482 It can be a str or netaddr.IPNetwork instance
483
484 :param **ipnetwork_kwargs: optional netaddr.IPNetwork constructor
485 parameters
486
487 :return: True if it wasn't reserved before, False elsewhere.
488 """
489
490 subnet_cidr = netaddr.IPNetwork(addr, **ipnetwork_kwargs)
491 if subnet_cidr in cls.reserved_subnet_cidrs:
492 return False
493 else:
494 cls.reserved_subnet_cidrs.add(subnet_cidr)
495 return True
496
497 @classmethod
498 def get_subnet_cidrs(
499 cls, cidr=None, mask_bits=None, ip_version=None):
500 """Iterate over a sequence of unused subnet CIDR for IP version
501
502 :param cidr: CIDR of the subnet to create
503 It can be either None, a str or a netaddr.IPNetwork instance
504
505 :param mask_bits: CIDR prefix length
506 It can be either None or a numeric value.
507 If cidr parameter is given then mask_bits is used to determinate a
508 sequence of valid CIDR to use as generated.
509 Please see netaddr.IPNetwork.subnet method documentation[1]
510
511 :param ip_version: ip version of generated subnet CIDRs
512 It can be None, IP_VERSION_4 or IP_VERSION_6
513 It has to match given CIDR if given
514
515 :return: iterator over reserved CIDRs of type netaddr.IPNetwork
516
517 [1] http://netaddr.readthedocs.io/en/latest/tutorial_01.html#supernets-and-subnets # noqa
518 """
519
520 if cidr:
521 # Generate subnet CIDRs starting from given CIDR
522 # checking it is of requested IP version
523 cidr = netaddr.IPNetwork(cidr, version=ip_version)
524 else:
525 # Generate subnet CIDRs starting from configured values
526 ip_version = ip_version or cls._ip_version
527 if ip_version == const.IP_VERSION_4:
528 mask_bits = mask_bits or config.safe_get_config_value(
529 'network', 'project_network_mask_bits')
530 cidr = netaddr.IPNetwork(config.safe_get_config_value(
531 'network', 'project_network_cidr'))
532 elif ip_version == const.IP_VERSION_6:
533 mask_bits = config.safe_get_config_value(
534 'network', 'project_network_v6_mask_bits')
535 cidr = netaddr.IPNetwork(config.safe_get_config_value(
536 'network', 'project_network_v6_cidr'))
537 else:
538 raise ValueError('Invalid IP version: {!r}'.format(ip_version))
539
540 if mask_bits:
541 subnet_cidrs = cidr.subnet(mask_bits)
542 else:
543 subnet_cidrs = iter([cidr])
544
545 for subnet_cidr in subnet_cidrs:
546 if subnet_cidr not in cls.reserved_subnet_cidrs:
547 yield subnet_cidr
548
549 @classmethod
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000550 def create_port(cls, network, **kwargs):
551 """Wrapper utility that returns a test port."""
Edan Davidd75e48e2018-01-03 02:49:52 -0500552 if CONF.network.port_vnic_type and 'binding:vnic_type' not in kwargs:
553 kwargs['binding:vnic_type'] = CONF.network.port_vnic_type
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000554 body = cls.client.create_port(network_id=network['id'],
555 **kwargs)
556 port = body['port']
557 cls.ports.append(port)
558 return port
559
560 @classmethod
561 def update_port(cls, port, **kwargs):
562 """Wrapper utility that updates a test port."""
563 body = cls.client.update_port(port['id'],
564 **kwargs)
565 return body['port']
566
567 @classmethod
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300568 def _create_router_with_client(
569 cls, client, router_name=None, admin_state_up=False,
570 external_network_id=None, enable_snat=None, **kwargs
571 ):
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000572 ext_gw_info = {}
573 if external_network_id:
574 ext_gw_info['network_id'] = external_network_id
YAMAMOTO Takashi9bd4f972017-06-20 12:49:30 +0900575 if enable_snat is not None:
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000576 ext_gw_info['enable_snat'] = enable_snat
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300577 body = client.create_router(
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000578 router_name, external_gateway_info=ext_gw_info,
579 admin_state_up=admin_state_up, **kwargs)
580 router = body['router']
581 cls.routers.append(router)
582 return router
583
584 @classmethod
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300585 def create_router(cls, *args, **kwargs):
586 return cls._create_router_with_client(cls.client, *args, **kwargs)
587
588 @classmethod
589 def create_admin_router(cls, *args, **kwargs):
rajat294495c042017-06-28 15:37:16 +0530590 return cls._create_router_with_client(cls.os_admin.network_client,
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300591 *args, **kwargs)
592
593 @classmethod
Federico Ressia69dcd52018-07-06 09:45:34 +0200594 def create_floatingip(cls, external_network_id=None, port=None,
595 client=None, **kwargs):
596 """Creates a floating IP.
597
598 Create a floating IP and schedule it for later deletion.
599 If a client is passed, then it is used for deleting the IP too.
600
601 :param external_network_id: network ID where to create
602 By default this is 'CONF.network.public_network_id'.
603
604 :param port: port to bind floating IP to
605 This is translated to 'port_id=port['id']'
606 By default it is None.
607
608 :param client: network client to be used for creating and cleaning up
609 the floating IP.
610
611 :param **kwargs: additional creation parameters to be forwarded to
612 networking server.
613 """
614
615 client = client or cls.client
616 external_network_id = (external_network_id or
617 cls.external_network_id)
618
619 if port:
Federico Ressi47f6ae42018-09-24 16:19:14 +0200620 port_id = kwargs.setdefault('port_id', port['id'])
621 if port_id != port['id']:
622 message = "Port ID specified twice: {!s} != {!s}".format(
623 port_id, port['id'])
624 raise ValueError(message)
Federico Ressia69dcd52018-07-06 09:45:34 +0200625
626 fip = client.create_floatingip(external_network_id,
627 **kwargs)['floatingip']
628
629 # save client to be used later in cls.delete_floatingip
630 # for final cleanup
631 fip['client'] = client
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000632 cls.floating_ips.append(fip)
633 return fip
634
635 @classmethod
Federico Ressia69dcd52018-07-06 09:45:34 +0200636 def delete_floatingip(cls, floating_ip, client=None):
637 """Delete floating IP
638
639 :param client: Client to be used
640 If client is not given it will use the client used to create
641 the floating IP, or cls.client if unknown.
642 """
643
644 client = client or floating_ip.get('client') or cls.client
645 client.delete_floatingip(floating_ip['id'])
646
647 @classmethod
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000648 def create_router_interface(cls, router_id, subnet_id):
649 """Wrapper utility that returns a router interface."""
650 interface = cls.client.add_router_interface_with_subnet_id(
651 router_id, subnet_id)
652 return interface
653
654 @classmethod
Sławek Kapłońskiff294062016-12-04 15:00:54 +0000655 def get_supported_qos_rule_types(cls):
656 body = cls.client.list_qos_rule_types()
657 return [rule_type['type'] for rule_type in body['rule_types']]
658
659 @classmethod
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200660 def create_qos_policy(cls, name, description=None, shared=False,
Hirofumi Ichihara39a6ee12017-08-23 13:55:12 +0900661 tenant_id=None, is_default=False):
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000662 """Wrapper utility that returns a test QoS policy."""
663 body = cls.admin_client.create_qos_policy(
Hirofumi Ichihara39a6ee12017-08-23 13:55:12 +0900664 name, description, shared, tenant_id, is_default)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000665 qos_policy = body['policy']
666 cls.qos_policies.append(qos_policy)
667 return qos_policy
668
669 @classmethod
Sławek Kapłoński153f3452017-03-24 22:04:53 +0000670 def create_qos_bandwidth_limit_rule(cls, policy_id, max_kbps,
671 max_burst_kbps,
Chandan Kumarc125fd12017-11-15 19:41:01 +0530672 direction=const.EGRESS_DIRECTION):
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000673 """Wrapper utility that returns a test QoS bandwidth limit rule."""
674 body = cls.admin_client.create_bandwidth_limit_rule(
Sławek Kapłoński153f3452017-03-24 22:04:53 +0000675 policy_id, max_kbps, max_burst_kbps, direction)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000676 qos_rule = body['bandwidth_limit_rule']
677 cls.qos_rules.append(qos_rule)
678 return qos_rule
679
680 @classmethod
Lajos Katona2f904652018-08-23 14:04:56 +0200681 def create_qos_minimum_bandwidth_rule(cls, policy_id, min_kbps,
682 direction=const.EGRESS_DIRECTION):
683 """Wrapper utility that creates and returns a QoS min bw rule."""
684 body = cls.admin_client.create_minimum_bandwidth_rule(
685 policy_id, direction, min_kbps)
686 qos_rule = body['minimum_bandwidth_rule']
687 cls.qos_rules.append(qos_rule)
688 return qos_rule
689
690 @classmethod
Jakub Libosvar83704832017-12-06 16:02:28 +0000691 def delete_router(cls, router, client=None):
692 client = client or cls.client
Aditya Vaja49819a72018-11-26 14:20:10 -0800693 if 'routes' in router:
694 client.remove_router_extra_routes(router['id'])
Jakub Libosvar83704832017-12-06 16:02:28 +0000695 body = client.list_router_interfaces(router['id'])
Chandan Kumarc125fd12017-11-15 19:41:01 +0530696 interfaces = [port for port in body['ports']
697 if port['device_owner'] in const.ROUTER_INTERFACE_OWNERS]
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000698 for i in interfaces:
699 try:
Jakub Libosvar83704832017-12-06 16:02:28 +0000700 client.remove_router_interface_with_subnet_id(
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000701 router['id'], i['fixed_ips'][0]['subnet_id'])
702 except lib_exc.NotFound:
703 pass
Jakub Libosvar83704832017-12-06 16:02:28 +0000704 client.delete_router(router['id'])
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000705
706 @classmethod
707 def create_address_scope(cls, name, is_admin=False, **kwargs):
708 if is_admin:
709 body = cls.admin_client.create_address_scope(name=name, **kwargs)
710 cls.admin_address_scopes.append(body['address_scope'])
711 else:
712 body = cls.client.create_address_scope(name=name, **kwargs)
713 cls.address_scopes.append(body['address_scope'])
714 return body['address_scope']
715
716 @classmethod
717 def create_subnetpool(cls, name, is_admin=False, **kwargs):
718 if is_admin:
719 body = cls.admin_client.create_subnetpool(name, **kwargs)
720 cls.admin_subnetpools.append(body['subnetpool'])
721 else:
722 body = cls.client.create_subnetpool(name, **kwargs)
723 cls.subnetpools.append(body['subnetpool'])
724 return body['subnetpool']
725
Chandan Kumarc125fd12017-11-15 19:41:01 +0530726 @classmethod
727 def create_project(cls, name=None, description=None):
728 test_project = name or data_utils.rand_name('test_project_')
729 test_description = description or data_utils.rand_name('desc_')
730 project = cls.identity_admin_client.create_project(
731 name=test_project,
732 description=test_description)['project']
733 cls.projects.append(project)
Dongcan Ye2de722e2018-07-04 11:01:37 +0000734 # Create a project will create a default security group.
Dongcan Ye2de722e2018-07-04 11:01:37 +0000735 sgs_list = cls.admin_client.list_security_groups(
736 tenant_id=project['id'])['security_groups']
Federico Ressi4c590d72018-10-10 14:01:08 +0200737 for security_group in sgs_list:
738 # Make sure delete_security_group method will use
739 # the admin client for this group
740 security_group['client'] = cls.admin_client
741 cls.security_groups.append(security_group)
Chandan Kumarc125fd12017-11-15 19:41:01 +0530742 return project
743
744 @classmethod
Federico Ressi4c590d72018-10-10 14:01:08 +0200745 def create_security_group(cls, name=None, project=None, client=None,
746 **kwargs):
747 if project:
748 client = client or cls.admin_client
749 project_id = kwargs.setdefault('project_id', project['id'])
750 tenant_id = kwargs.setdefault('tenant_id', project['id'])
751 if project_id != project['id'] or tenant_id != project['id']:
752 raise ValueError('Project ID specified multiple times')
753 else:
754 client = client or cls.client
755
756 name = name or data_utils.rand_name(cls.__name__)
757 security_group = client.create_security_group(name=name, **kwargs)[
758 'security_group']
759 security_group['client'] = client
760 cls.security_groups.append(security_group)
761 return security_group
762
763 @classmethod
764 def delete_security_group(cls, security_group, client=None):
765 client = client or security_group.get('client') or cls.client
766 client.delete_security_group(security_group['id'])
767
768 @classmethod
769 def create_security_group_rule(cls, security_group=None, project=None,
770 client=None, ip_version=None, **kwargs):
771 if project:
772 client = client or cls.admin_client
773 project_id = kwargs.setdefault('project_id', project['id'])
774 tenant_id = kwargs.setdefault('tenant_id', project['id'])
775 if project_id != project['id'] or tenant_id != project['id']:
776 raise ValueError('Project ID specified multiple times')
777
778 if 'security_group_id' not in kwargs:
779 security_group = (security_group or
780 cls.get_security_group(client=client))
781
782 if security_group:
783 client = client or security_group.get('client')
784 security_group_id = kwargs.setdefault('security_group_id',
785 security_group['id'])
786 if security_group_id != security_group['id']:
787 raise ValueError('Security group ID specified multiple times.')
788
789 ip_version = ip_version or cls._ip_version
790 default_params = (
791 constants.DEFAULT_SECURITY_GROUP_RULE_PARAMS[ip_version])
792 for key, value in default_params.items():
793 kwargs.setdefault(key, value)
794
795 client = client or cls.client
796 return client.create_security_group_rule(**kwargs)[
797 'security_group_rule']
798
799 @classmethod
800 def get_security_group(cls, name='default', client=None):
801 client = client or cls.client
802 security_groups = client.list_security_groups()['security_groups']
803 for security_group in security_groups:
804 if security_group['name'] == name:
805 return security_group
806 raise ValueError("No such security group named {!r}".format(name))
Chandan Kumarc125fd12017-11-15 19:41:01 +0530807
Federico Ressiab286e42018-06-19 09:52:10 +0200808 @classmethod
809 def create_keypair(cls, client=None, name=None, **kwargs):
810 client = client or cls.os_primary.keypairs_client
811 name = name or data_utils.rand_name('keypair-test')
812 keypair = client.create_keypair(name=name, **kwargs)['keypair']
813
814 # save client for later cleanup
815 keypair['client'] = client
816 cls.keypairs.append(keypair)
817 return keypair
818
819 @classmethod
820 def delete_keypair(cls, keypair, client=None):
821 client = (client or keypair.get('client') or
822 cls.os_primary.keypairs_client)
823 client.delete_keypair(keypair_name=keypair['name'])
824
Federico Ressi82e83e32018-07-03 14:19:55 +0200825 @classmethod
826 def create_trunk(cls, port=None, subports=None, client=None, **kwargs):
827 """Create network trunk
828
829 :param port: dictionary containing parent port ID (port['id'])
830 :param client: client to be used for connecting to networking service
831 :param **kwargs: extra parameters to be forwarded to network service
832
833 :returns: dictionary containing created trunk details
834 """
835 client = client or cls.client
836
837 if port:
838 kwargs['port_id'] = port['id']
839
840 trunk = client.create_trunk(subports=subports, **kwargs)['trunk']
841 # Save client reference for later deletion
842 trunk['client'] = client
843 cls.trunks.append(trunk)
844 return trunk
845
846 @classmethod
Huifeng Le1c9f40b2018-11-07 01:14:21 +0800847 def delete_trunk(cls, trunk, client=None, detach_parent_port=True):
Federico Ressi82e83e32018-07-03 14:19:55 +0200848 """Delete network trunk
849
850 :param trunk: dictionary containing trunk ID (trunk['id'])
851
852 :param client: client to be used for connecting to networking service
853 """
854 client = client or trunk.get('client') or cls.client
855 trunk.update(client.show_trunk(trunk['id'])['trunk'])
856
857 if not trunk['admin_state_up']:
858 # Cannot touch trunk before admin_state_up is True
859 client.update_trunk(trunk['id'], admin_state_up=True)
860 if trunk['sub_ports']:
861 # Removes trunk ports before deleting it
862 cls._try_delete_resource(client.remove_subports, trunk['id'],
863 trunk['sub_ports'])
864
865 # we have to detach the interface from the server before
866 # the trunk can be deleted.
867 parent_port = {'id': trunk['port_id']}
868
869 def is_parent_port_detached():
870 parent_port.update(client.show_port(parent_port['id'])['port'])
871 return not parent_port['device_id']
872
Huifeng Le1c9f40b2018-11-07 01:14:21 +0800873 if detach_parent_port and not is_parent_port_detached():
Federico Ressi82e83e32018-07-03 14:19:55 +0200874 # this could probably happen when trunk is deleted and parent port
875 # has been assigned to a VM that is still running. Here we are
876 # assuming that device_id points to such VM.
877 cls.os_primary.compute.InterfacesClient().delete_interface(
878 parent_port['device_id'], parent_port['id'])
879 utils.wait_until_true(is_parent_port_detached)
880
881 client.delete_trunk(trunk['id'])
882
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000883
884class BaseAdminNetworkTest(BaseNetworkTest):
885
886 credentials = ['primary', 'admin']
887
888 @classmethod
889 def setup_clients(cls):
890 super(BaseAdminNetworkTest, cls).setup_clients()
fumihiko kakumaa216fc12017-07-14 10:43:29 +0900891 cls.admin_client = cls.os_admin.network_client
Jakub Libosvarf5758012017-08-15 13:45:30 +0000892 cls.identity_admin_client = cls.os_admin.projects_client
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000893
894 @classmethod
895 def create_metering_label(cls, name, description):
896 """Wrapper utility that returns a test metering label."""
897 body = cls.admin_client.create_metering_label(
898 description=description,
899 name=data_utils.rand_name("metering-label"))
900 metering_label = body['metering_label']
901 cls.metering_labels.append(metering_label)
902 return metering_label
903
904 @classmethod
905 def create_metering_label_rule(cls, remote_ip_prefix, direction,
906 metering_label_id):
907 """Wrapper utility that returns a test metering label rule."""
908 body = cls.admin_client.create_metering_label_rule(
909 remote_ip_prefix=remote_ip_prefix, direction=direction,
910 metering_label_id=metering_label_id)
911 metering_label_rule = body['metering_label_rule']
912 cls.metering_label_rules.append(metering_label_rule)
913 return metering_label_rule
914
915 @classmethod
916 def create_flavor(cls, name, description, service_type):
917 """Wrapper utility that returns a test flavor."""
918 body = cls.admin_client.create_flavor(
919 description=description, service_type=service_type,
920 name=name)
921 flavor = body['flavor']
922 cls.flavors.append(flavor)
923 return flavor
924
925 @classmethod
926 def create_service_profile(cls, description, metainfo, driver):
927 """Wrapper utility that returns a test service profile."""
928 body = cls.admin_client.create_service_profile(
929 driver=driver, metainfo=metainfo, description=description)
930 service_profile = body['service_profile']
931 cls.service_profiles.append(service_profile)
932 return service_profile
933
934 @classmethod
Nguyen Phuong An67993fc2017-11-24 11:30:25 +0700935 def create_log(cls, name, description=None,
936 resource_type='security_group', resource_id=None,
937 target_id=None, event='ALL', enabled=True):
938 """Wrapper utility that returns a test log object."""
939 log_args = {'name': name,
940 'description': description,
941 'resource_type': resource_type,
942 'resource_id': resource_id,
943 'target_id': target_id,
944 'event': event,
945 'enabled': enabled}
946 body = cls.admin_client.create_log(**log_args)
947 log_object = body['log']
948 cls.log_objects.append(log_object)
949 return log_object
950
951 @classmethod
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000952 def get_unused_ip(cls, net_id, ip_version=None):
Gary Kotton011345f2016-06-15 08:04:31 -0700953 """Get an unused ip address in a allocation pool of net"""
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000954 body = cls.admin_client.list_ports(network_id=net_id)
955 ports = body['ports']
956 used_ips = []
957 for port in ports:
958 used_ips.extend(
959 [fixed_ip['ip_address'] for fixed_ip in port['fixed_ips']])
960 body = cls.admin_client.list_subnets(network_id=net_id)
961 subnets = body['subnets']
962
963 for subnet in subnets:
964 if ip_version and subnet['ip_version'] != ip_version:
965 continue
966 cidr = subnet['cidr']
967 allocation_pools = subnet['allocation_pools']
968 iterators = []
969 if allocation_pools:
970 for allocation_pool in allocation_pools:
971 iterators.append(netaddr.iter_iprange(
972 allocation_pool['start'], allocation_pool['end']))
973 else:
974 net = netaddr.IPNetwork(cidr)
975
976 def _iterip():
977 for ip in net:
978 if ip not in (net.network, net.broadcast):
979 yield ip
980 iterators.append(iter(_iterip()))
981
982 for iterator in iterators:
983 for ip in iterator:
984 if str(ip) not in used_ips:
985 return str(ip)
986
987 message = (
988 "net(%s) has no usable IP address in allocation pools" % net_id)
989 raise exceptions.InvalidConfiguration(message)
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200990
Lajos Katona2f904652018-08-23 14:04:56 +0200991 @classmethod
992 def create_provider_network(cls, physnet_name, start_segmentation_id,
993 max_attempts=30):
994 segmentation_id = start_segmentation_id
Lajos Katona7eb67252019-01-14 12:55:35 +0100995 for attempts in range(max_attempts):
Lajos Katona2f904652018-08-23 14:04:56 +0200996 try:
Lajos Katona7eb67252019-01-14 12:55:35 +0100997 return cls.create_network(
Lajos Katona2f904652018-08-23 14:04:56 +0200998 name=data_utils.rand_name('test_net'),
999 shared=True,
1000 provider_network_type='vlan',
1001 provider_physical_network=physnet_name,
1002 provider_segmentation_id=segmentation_id)
Lajos Katona2f904652018-08-23 14:04:56 +02001003 except lib_exc.Conflict:
Lajos Katona2f904652018-08-23 14:04:56 +02001004 segmentation_id += 1
1005 if segmentation_id > 4095:
1006 raise lib_exc.TempestException(
1007 "No free segmentation id was found for provider "
1008 "network creation!")
1009 time.sleep(CONF.network.build_interval)
Lajos Katona7eb67252019-01-14 12:55:35 +01001010 LOG.exception("Failed to create provider network after "
1011 "%d attempts", max_attempts)
1012 raise lib_exc.TimeoutException
Lajos Katona2f904652018-08-23 14:04:56 +02001013
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001014
Sławek Kapłońskiff294062016-12-04 15:00:54 +00001015def require_qos_rule_type(rule_type):
1016 def decorator(f):
1017 @functools.wraps(f)
1018 def wrapper(self, *func_args, **func_kwargs):
1019 if rule_type not in self.get_supported_qos_rule_types():
1020 raise self.skipException(
1021 "%s rule type is required." % rule_type)
1022 return f(self, *func_args, **func_kwargs)
1023 return wrapper
1024 return decorator
1025
1026
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001027def _require_sorting(f):
1028 @functools.wraps(f)
1029 def inner(self, *args, **kwargs):
Chandan Kumarc125fd12017-11-15 19:41:01 +05301030 if not tutils.is_extension_enabled("sorting", "network"):
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001031 self.skipTest('Sorting feature is required')
1032 return f(self, *args, **kwargs)
1033 return inner
1034
1035
1036def _require_pagination(f):
1037 @functools.wraps(f)
1038 def inner(self, *args, **kwargs):
Chandan Kumarc125fd12017-11-15 19:41:01 +05301039 if not tutils.is_extension_enabled("pagination", "network"):
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001040 self.skipTest('Pagination feature is required')
1041 return f(self, *args, **kwargs)
1042 return inner
1043
1044
1045class BaseSearchCriteriaTest(BaseNetworkTest):
1046
1047 # This should be defined by subclasses to reflect resource name to test
1048 resource = None
1049
Armando Migliaccio57581c62016-07-01 10:13:19 -07001050 field = 'name'
1051
Ihar Hrachyshkaa8fe5a12016-05-24 14:50:58 +02001052 # NOTE(ihrachys): some names, like those starting with an underscore (_)
1053 # are sorted differently depending on whether the plugin implements native
1054 # sorting support, or not. So we avoid any such cases here, sticking to
1055 # alphanumeric. Also test a case when there are multiple resources with the
1056 # same name
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001057 resource_names = ('test1', 'abc1', 'test10', '123test') + ('test1',)
1058
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001059 force_tenant_isolation = True
1060
Ihar Hrachyshkaa8fe5a12016-05-24 14:50:58 +02001061 list_kwargs = {}
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001062
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001063 list_as_admin = False
1064
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001065 def assertSameOrder(self, original, actual):
1066 # gracefully handle iterators passed
1067 original = list(original)
1068 actual = list(actual)
1069 self.assertEqual(len(original), len(actual))
1070 for expected, res in zip(original, actual):
Armando Migliaccio57581c62016-07-01 10:13:19 -07001071 self.assertEqual(expected[self.field], res[self.field])
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001072
1073 @utils.classproperty
1074 def plural_name(self):
1075 return '%ss' % self.resource
1076
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001077 @property
1078 def list_client(self):
1079 return self.admin_client if self.list_as_admin else self.client
1080
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001081 def list_method(self, *args, **kwargs):
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001082 method = getattr(self.list_client, 'list_%s' % self.plural_name)
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001083 kwargs.update(self.list_kwargs)
1084 return method(*args, **kwargs)
1085
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001086 def get_bare_url(self, url):
1087 base_url = self.client.base_url
1088 self.assertTrue(url.startswith(base_url))
1089 return url[len(base_url):]
1090
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001091 @classmethod
1092 def _extract_resources(cls, body):
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001093 return body[cls.plural_name]
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001094
1095 def _test_list_sorts(self, direction):
1096 sort_args = {
1097 'sort_dir': direction,
Armando Migliaccio57581c62016-07-01 10:13:19 -07001098 'sort_key': self.field
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001099 }
1100 body = self.list_method(**sort_args)
1101 resources = self._extract_resources(body)
1102 self.assertNotEmpty(
1103 resources, "%s list returned is empty" % self.resource)
Armando Migliaccio57581c62016-07-01 10:13:19 -07001104 retrieved_names = [res[self.field] for res in resources]
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001105 expected = sorted(retrieved_names)
1106 if direction == constants.SORT_DIRECTION_DESC:
1107 expected = list(reversed(expected))
1108 self.assertEqual(expected, retrieved_names)
1109
1110 @_require_sorting
1111 def _test_list_sorts_asc(self):
1112 self._test_list_sorts(constants.SORT_DIRECTION_ASC)
1113
1114 @_require_sorting
1115 def _test_list_sorts_desc(self):
1116 self._test_list_sorts(constants.SORT_DIRECTION_DESC)
1117
1118 @_require_pagination
1119 def _test_list_pagination(self):
1120 for limit in range(1, len(self.resource_names) + 1):
1121 pagination_args = {
1122 'limit': limit,
1123 }
1124 body = self.list_method(**pagination_args)
1125 resources = self._extract_resources(body)
1126 self.assertEqual(limit, len(resources))
1127
1128 @_require_pagination
1129 def _test_list_no_pagination_limit_0(self):
1130 pagination_args = {
1131 'limit': 0,
1132 }
1133 body = self.list_method(**pagination_args)
1134 resources = self._extract_resources(body)
Béla Vancsicsf1806182016-08-23 07:36:18 +02001135 self.assertGreaterEqual(len(resources), len(self.resource_names))
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001136
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001137 def _test_list_pagination_iteratively(self, lister):
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001138 # first, collect all resources for later comparison
1139 sort_args = {
1140 'sort_dir': constants.SORT_DIRECTION_ASC,
Armando Migliaccio57581c62016-07-01 10:13:19 -07001141 'sort_key': self.field
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001142 }
1143 body = self.list_method(**sort_args)
1144 expected_resources = self._extract_resources(body)
1145 self.assertNotEmpty(expected_resources)
1146
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001147 resources = lister(
1148 len(expected_resources), sort_args
1149 )
1150
1151 # finally, compare that the list retrieved in one go is identical to
1152 # the one containing pagination results
1153 self.assertSameOrder(expected_resources, resources)
1154
1155 def _list_all_with_marker(self, niterations, sort_args):
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001156 # paginate resources one by one, using last fetched resource as a
1157 # marker
1158 resources = []
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001159 for i in range(niterations):
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001160 pagination_args = sort_args.copy()
1161 pagination_args['limit'] = 1
1162 if resources:
1163 pagination_args['marker'] = resources[-1]['id']
1164 body = self.list_method(**pagination_args)
1165 resources_ = self._extract_resources(body)
1166 self.assertEqual(1, len(resources_))
1167 resources.extend(resources_)
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001168 return resources
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001169
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001170 @_require_pagination
1171 @_require_sorting
1172 def _test_list_pagination_with_marker(self):
1173 self._test_list_pagination_iteratively(self._list_all_with_marker)
1174
1175 def _list_all_with_hrefs(self, niterations, sort_args):
1176 # paginate resources one by one, using next href links
1177 resources = []
1178 prev_links = {}
1179
1180 for i in range(niterations):
1181 if prev_links:
1182 uri = self.get_bare_url(prev_links['next'])
1183 else:
Ihar Hrachyshka7f79fe62016-06-07 21:23:44 +02001184 sort_args.update(self.list_kwargs)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001185 uri = self.list_client.build_uri(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001186 self.plural_name, limit=1, **sort_args)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001187 prev_links, body = self.list_client.get_uri_with_links(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001188 self.plural_name, uri
1189 )
1190 resources_ = self._extract_resources(body)
1191 self.assertEqual(1, len(resources_))
1192 resources.extend(resources_)
1193
1194 # The last element is empty and does not contain 'next' link
1195 uri = self.get_bare_url(prev_links['next'])
1196 prev_links, body = self.client.get_uri_with_links(
1197 self.plural_name, uri
1198 )
1199 self.assertNotIn('next', prev_links)
1200
1201 # Now walk backwards and compare results
1202 resources2 = []
1203 for i in range(niterations):
1204 uri = self.get_bare_url(prev_links['previous'])
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001205 prev_links, body = self.list_client.get_uri_with_links(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001206 self.plural_name, uri
1207 )
1208 resources_ = self._extract_resources(body)
1209 self.assertEqual(1, len(resources_))
1210 resources2.extend(resources_)
1211
1212 self.assertSameOrder(resources, reversed(resources2))
1213
1214 return resources
1215
1216 @_require_pagination
1217 @_require_sorting
1218 def _test_list_pagination_with_href_links(self):
1219 self._test_list_pagination_iteratively(self._list_all_with_hrefs)
1220
1221 @_require_pagination
1222 @_require_sorting
1223 def _test_list_pagination_page_reverse_with_href_links(
1224 self, direction=constants.SORT_DIRECTION_ASC):
1225 pagination_args = {
1226 'sort_dir': direction,
Armando Migliaccio57581c62016-07-01 10:13:19 -07001227 'sort_key': self.field,
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001228 }
1229 body = self.list_method(**pagination_args)
1230 expected_resources = self._extract_resources(body)
1231
1232 page_size = 2
1233 pagination_args['limit'] = page_size
1234
1235 prev_links = {}
1236 resources = []
1237 num_resources = len(expected_resources)
1238 niterations = int(math.ceil(float(num_resources) / page_size))
1239 for i in range(niterations):
1240 if prev_links:
1241 uri = self.get_bare_url(prev_links['previous'])
1242 else:
Ihar Hrachyshka7f79fe62016-06-07 21:23:44 +02001243 pagination_args.update(self.list_kwargs)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001244 uri = self.list_client.build_uri(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001245 self.plural_name, page_reverse=True, **pagination_args)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001246 prev_links, body = self.list_client.get_uri_with_links(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001247 self.plural_name, uri
1248 )
1249 resources_ = self._extract_resources(body)
Béla Vancsicsf1806182016-08-23 07:36:18 +02001250 self.assertGreaterEqual(page_size, len(resources_))
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001251 resources.extend(reversed(resources_))
1252
1253 self.assertSameOrder(expected_resources, reversed(resources))
1254
1255 @_require_pagination
1256 @_require_sorting
1257 def _test_list_pagination_page_reverse_asc(self):
1258 self._test_list_pagination_page_reverse(
1259 direction=constants.SORT_DIRECTION_ASC)
1260
1261 @_require_pagination
1262 @_require_sorting
1263 def _test_list_pagination_page_reverse_desc(self):
1264 self._test_list_pagination_page_reverse(
1265 direction=constants.SORT_DIRECTION_DESC)
1266
1267 def _test_list_pagination_page_reverse(self, direction):
1268 pagination_args = {
1269 'sort_dir': direction,
Armando Migliaccio57581c62016-07-01 10:13:19 -07001270 'sort_key': self.field,
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001271 'limit': 3,
1272 }
1273 body = self.list_method(**pagination_args)
1274 expected_resources = self._extract_resources(body)
1275
1276 pagination_args['limit'] -= 1
1277 pagination_args['marker'] = expected_resources[-1]['id']
1278 pagination_args['page_reverse'] = True
1279 body = self.list_method(**pagination_args)
1280
1281 self.assertSameOrder(
1282 # the last entry is not included in 2nd result when used as a
1283 # marker
1284 expected_resources[:-1],
1285 self._extract_resources(body))
Victor Morales1be97b42016-09-05 08:50:06 -05001286
Hongbin Lu54f55922018-07-12 19:05:39 +00001287 @tutils.requires_ext(extension="filter-validation", service="network")
1288 def _test_list_validation_filters(
1289 self, validation_args, filter_is_valid=True):
1290 if not filter_is_valid:
1291 self.assertRaises(lib_exc.BadRequest, self.list_method,
1292 **validation_args)
1293 else:
1294 body = self.list_method(**validation_args)
1295 resources = self._extract_resources(body)
1296 for resource in resources:
1297 self.assertIn(resource['name'], self.resource_names)