blob: 03ad0851508cb749cf697b84654fb4709deefd2a [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
Lajos Katona2f904652018-08-23 14:04:56 +020017import itertools
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +020018import math
Lajos Katona2f904652018-08-23 14:04:56 +020019import time
Ihar Hrachyshka59382252016-04-05 15:54:33 +020020
Daniel Mellado3c0aeab2016-01-29 11:30:25 +000021import netaddr
Chandan Kumarc125fd12017-11-15 19:41:01 +053022from neutron_lib import constants as const
Lajos Katona2f904652018-08-23 14:04:56 +020023from oslo_log import log
Chandan Kumarc125fd12017-11-15 19:41:01 +053024from tempest.common import utils as tutils
Daniel Mellado3c0aeab2016-01-29 11:30:25 +000025from tempest.lib.common.utils import data_utils
26from tempest.lib import exceptions as lib_exc
27from tempest import test
28
Chandan Kumar667d3d32017-09-22 12:24:06 +053029from neutron_tempest_plugin.api import clients
30from neutron_tempest_plugin.common import constants
31from neutron_tempest_plugin.common import utils
32from neutron_tempest_plugin import config
33from neutron_tempest_plugin import exceptions
Daniel Mellado3c0aeab2016-01-29 11:30:25 +000034
35CONF = config.CONF
36
Lajos Katona2f904652018-08-23 14:04:56 +020037LOG = log.getLogger(__name__)
38
Daniel Mellado3c0aeab2016-01-29 11:30:25 +000039
40class BaseNetworkTest(test.BaseTestCase):
41
Brian Haleyae328b92018-10-09 19:51:54 -040042 """Base class for Neutron tests that use the Tempest Neutron REST client
Daniel Mellado3c0aeab2016-01-29 11:30:25 +000043
44 Per the Neutron API Guide, API v1.x was removed from the source code tree
45 (docs.openstack.org/api/openstack-network/2.0/content/Overview-d1e71.html)
46 Therefore, v2.x of the Neutron API is assumed. It is also assumed that the
47 following options are defined in the [network] section of etc/tempest.conf:
48
49 project_network_cidr with a block of cidr's from which smaller blocks
50 can be allocated for tenant networks
51
52 project_network_mask_bits with the mask bits to be used to partition
53 the block defined by tenant-network_cidr
54
55 Finally, it is assumed that the following option is defined in the
56 [service_available] section of etc/tempest.conf
57
58 neutron as True
59 """
60
61 force_tenant_isolation = False
62 credentials = ['primary']
63
64 # Default to ipv4.
Federico Ressi0ddc93b2018-04-09 12:01:48 +020065 _ip_version = const.IP_VERSION_4
Daniel Mellado3c0aeab2016-01-29 11:30:25 +000066
Federico Ressi61b564e2018-07-06 08:10:31 +020067 # Derive from BaseAdminNetworkTest class to have this initialized
68 admin_client = None
69
Federico Ressia69dcd52018-07-06 09:45:34 +020070 external_network_id = CONF.network.public_network_id
71
Daniel Mellado3c0aeab2016-01-29 11:30:25 +000072 @classmethod
73 def get_client_manager(cls, credential_type=None, roles=None,
74 force_new=None):
Genadi Chereshnyacc395c02016-07-25 12:17:37 +030075 manager = super(BaseNetworkTest, cls).get_client_manager(
76 credential_type=credential_type,
77 roles=roles,
78 force_new=force_new
79 )
Daniel Mellado3c0aeab2016-01-29 11:30:25 +000080 # Neutron uses a different clients manager than the one in the Tempest
Jens Harbott860b46a2017-11-15 21:23:15 +000081 # save the original in case mixed tests need it
82 if credential_type == 'primary':
83 cls.os_tempest = manager
Daniel Mellado3c0aeab2016-01-29 11:30:25 +000084 return clients.Manager(manager.credentials)
85
86 @classmethod
87 def skip_checks(cls):
88 super(BaseNetworkTest, cls).skip_checks()
89 if not CONF.service_available.neutron:
90 raise cls.skipException("Neutron support is required")
Federico Ressi0ddc93b2018-04-09 12:01:48 +020091 if (cls._ip_version == const.IP_VERSION_6 and
92 not CONF.network_feature_enabled.ipv6):
Daniel Mellado3c0aeab2016-01-29 11:30:25 +000093 raise cls.skipException("IPv6 Tests are disabled.")
Jakub Libosvar1982aa12017-05-30 11:15:33 +000094 for req_ext in getattr(cls, 'required_extensions', []):
Chandan Kumarc125fd12017-11-15 19:41:01 +053095 if not tutils.is_extension_enabled(req_ext, 'network'):
Jakub Libosvar1982aa12017-05-30 11:15:33 +000096 msg = "%s extension not enabled." % req_ext
97 raise cls.skipException(msg)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +000098
99 @classmethod
100 def setup_credentials(cls):
101 # Create no network resources for these test.
102 cls.set_network_resources()
103 super(BaseNetworkTest, cls).setup_credentials()
104
105 @classmethod
106 def setup_clients(cls):
107 super(BaseNetworkTest, cls).setup_clients()
fumihiko kakumaa216fc12017-07-14 10:43:29 +0900108 cls.client = cls.os_primary.network_client
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000109
110 @classmethod
111 def resource_setup(cls):
112 super(BaseNetworkTest, cls).resource_setup()
113
114 cls.networks = []
Miguel Lavalle124378b2016-09-21 16:41:47 -0500115 cls.admin_networks = []
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000116 cls.subnets = []
Kevin Bentonba3651c2017-09-01 17:13:01 -0700117 cls.admin_subnets = []
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000118 cls.ports = []
119 cls.routers = []
120 cls.floating_ips = []
121 cls.metering_labels = []
122 cls.service_profiles = []
123 cls.flavors = []
124 cls.metering_label_rules = []
125 cls.qos_rules = []
126 cls.qos_policies = []
127 cls.ethertype = "IPv" + str(cls._ip_version)
128 cls.address_scopes = []
129 cls.admin_address_scopes = []
130 cls.subnetpools = []
131 cls.admin_subnetpools = []
Itzik Brownbac51dc2016-10-31 12:25:04 +0000132 cls.security_groups = []
Dongcan Ye2de722e2018-07-04 11:01:37 +0000133 cls.admin_security_groups = []
Chandan Kumarc125fd12017-11-15 19:41:01 +0530134 cls.projects = []
Nguyen Phuong An67993fc2017-11-24 11:30:25 +0700135 cls.log_objects = []
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200136 cls.reserved_subnet_cidrs = set()
Federico Ressiab286e42018-06-19 09:52:10 +0200137 cls.keypairs = []
Federico Ressi82e83e32018-07-03 14:19:55 +0200138 cls.trunks = []
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000139
140 @classmethod
141 def resource_cleanup(cls):
142 if CONF.service_available.neutron:
Federico Ressi82e83e32018-07-03 14:19:55 +0200143 # Clean up trunks
144 for trunk in cls.trunks:
145 cls._try_delete_resource(cls.delete_trunk, trunk)
146
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000147 # Clean up floating IPs
148 for floating_ip in cls.floating_ips:
Federico Ressia69dcd52018-07-06 09:45:34 +0200149 cls._try_delete_resource(cls.delete_floatingip, floating_ip)
150
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000151 # Clean up routers
152 for router in cls.routers:
153 cls._try_delete_resource(cls.delete_router,
154 router)
155 # Clean up metering label rules
156 for metering_label_rule in cls.metering_label_rules:
157 cls._try_delete_resource(
158 cls.admin_client.delete_metering_label_rule,
159 metering_label_rule['id'])
160 # Clean up metering labels
161 for metering_label in cls.metering_labels:
162 cls._try_delete_resource(
163 cls.admin_client.delete_metering_label,
164 metering_label['id'])
165 # Clean up flavors
166 for flavor in cls.flavors:
167 cls._try_delete_resource(
168 cls.admin_client.delete_flavor,
169 flavor['id'])
170 # Clean up service profiles
171 for service_profile in cls.service_profiles:
172 cls._try_delete_resource(
173 cls.admin_client.delete_service_profile,
174 service_profile['id'])
175 # Clean up ports
176 for port in cls.ports:
177 cls._try_delete_resource(cls.client.delete_port,
178 port['id'])
179 # Clean up subnets
180 for subnet in cls.subnets:
181 cls._try_delete_resource(cls.client.delete_subnet,
182 subnet['id'])
Kevin Bentonba3651c2017-09-01 17:13:01 -0700183 # Clean up admin subnets
184 for subnet in cls.admin_subnets:
185 cls._try_delete_resource(cls.admin_client.delete_subnet,
186 subnet['id'])
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000187 # Clean up networks
188 for network in cls.networks:
Federico Ressi61b564e2018-07-06 08:10:31 +0200189 cls._try_delete_resource(cls.delete_network, network)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000190
Miguel Lavalle124378b2016-09-21 16:41:47 -0500191 # Clean up admin networks
192 for network in cls.admin_networks:
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000193 cls._try_delete_resource(cls.admin_client.delete_network,
194 network['id'])
195
Itzik Brownbac51dc2016-10-31 12:25:04 +0000196 # Clean up security groups
Federico Ressi4c590d72018-10-10 14:01:08 +0200197 for security_group in cls.security_groups:
198 cls._try_delete_resource(cls.delete_security_group,
199 security_group)
Itzik Brownbac51dc2016-10-31 12:25:04 +0000200
Dongcan Ye2de722e2018-07-04 11:01:37 +0000201 # Clean up admin security groups
Federico Ressi4c590d72018-10-10 14:01:08 +0200202 for security_group in cls.admin_security_groups:
203 cls._try_delete_resource(cls.delete_security_group,
204 security_group,
205 client=cls.admin_client)
Dongcan Ye2de722e2018-07-04 11:01:37 +0000206
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000207 for subnetpool in cls.subnetpools:
208 cls._try_delete_resource(cls.client.delete_subnetpool,
209 subnetpool['id'])
210
211 for subnetpool in cls.admin_subnetpools:
212 cls._try_delete_resource(cls.admin_client.delete_subnetpool,
213 subnetpool['id'])
214
215 for address_scope in cls.address_scopes:
216 cls._try_delete_resource(cls.client.delete_address_scope,
217 address_scope['id'])
218
219 for address_scope in cls.admin_address_scopes:
220 cls._try_delete_resource(
221 cls.admin_client.delete_address_scope,
222 address_scope['id'])
223
Chandan Kumarc125fd12017-11-15 19:41:01 +0530224 for project in cls.projects:
225 cls._try_delete_resource(
226 cls.identity_admin_client.delete_project,
227 project['id'])
228
Sławek Kapłońskie100c4d2017-08-23 21:18:34 +0000229 # Clean up QoS rules
230 for qos_rule in cls.qos_rules:
231 cls._try_delete_resource(cls.admin_client.delete_qos_rule,
232 qos_rule['id'])
233 # Clean up QoS policies
234 # as all networks and ports are already removed, QoS policies
235 # shouldn't be "in use"
236 for qos_policy in cls.qos_policies:
237 cls._try_delete_resource(cls.admin_client.delete_qos_policy,
238 qos_policy['id'])
239
Nguyen Phuong An67993fc2017-11-24 11:30:25 +0700240 # Clean up log_objects
241 for log_object in cls.log_objects:
242 cls._try_delete_resource(cls.admin_client.delete_log,
243 log_object['id'])
244
Federico Ressiab286e42018-06-19 09:52:10 +0200245 for keypair in cls.keypairs:
246 cls._try_delete_resource(cls.delete_keypair, keypair)
247
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000248 super(BaseNetworkTest, cls).resource_cleanup()
249
250 @classmethod
251 def _try_delete_resource(cls, delete_callable, *args, **kwargs):
252 """Cleanup resources in case of test-failure
253
254 Some resources are explicitly deleted by the test.
255 If the test failed to delete a resource, this method will execute
256 the appropriate delete methods. Otherwise, the method ignores NotFound
257 exceptions thrown for resources that were correctly deleted by the
258 test.
259
260 :param delete_callable: delete method
261 :param args: arguments for delete method
262 :param kwargs: keyword arguments for delete method
263 """
264 try:
265 delete_callable(*args, **kwargs)
266 # if resource is not found, this means it was deleted in the test
267 except lib_exc.NotFound:
268 pass
269
270 @classmethod
Federico Ressi61b564e2018-07-06 08:10:31 +0200271 def create_network(cls, network_name=None, client=None, external=None,
272 shared=None, provider_network_type=None,
273 provider_physical_network=None,
274 provider_segmentation_id=None, **kwargs):
275 """Create a network.
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000276
Federico Ressi61b564e2018-07-06 08:10:31 +0200277 When client is not provider and admin_client is attribute is not None
278 (for example when using BaseAdminNetworkTest base class) and using any
279 of the convenience parameters (external, shared, provider_network_type,
280 provider_physical_network and provider_segmentation_id) it silently
281 uses admin_client. If the network is not shared then it uses the same
282 project_id as regular client.
283
284 :param network_name: Human-readable name of the network
285
286 :param client: client to be used for connecting to network service
287
288 :param external: indicates whether the network has an external routing
289 facility that's not managed by the networking service.
290
291 :param shared: indicates whether this resource is shared across all
292 projects. By default, only administrative users can change this value.
293 If True and admin_client attribute is not None, then the network is
294 created under administrative project.
295
296 :param provider_network_type: the type of physical network that this
297 network should be mapped to. For example, 'flat', 'vlan', 'vxlan', or
298 'gre'. Valid values depend on a networking back-end.
299
300 :param provider_physical_network: the physical network where this
301 network should be implemented. The Networking API v2.0 does not provide
302 a way to list available physical networks. For example, the Open
303 vSwitch plug-in configuration file defines a symbolic name that maps to
304 specific bridges on each compute host.
305
306 :param provider_segmentation_id: The ID of the isolated segment on the
307 physical network. The network_type attribute defines the segmentation
308 model. For example, if the network_type value is 'vlan', this ID is a
309 vlan identifier. If the network_type value is 'gre', this ID is a gre
310 key.
311
312 :param **kwargs: extra parameters to be forwarded to network service
313 """
314
315 name = (network_name or kwargs.pop('name', None) or
316 data_utils.rand_name('test-network-'))
317
318 # translate convenience parameters
319 admin_client_required = False
320 if provider_network_type:
321 admin_client_required = True
322 kwargs['provider:network_type'] = provider_network_type
323 if provider_physical_network:
324 admin_client_required = True
325 kwargs['provider:physical_network'] = provider_physical_network
326 if provider_segmentation_id:
327 admin_client_required = True
328 kwargs['provider:segmentation_id'] = provider_segmentation_id
329 if external is not None:
330 admin_client_required = True
331 kwargs['router:external'] = bool(external)
332 if shared is not None:
333 admin_client_required = True
334 kwargs['shared'] = bool(shared)
335
336 if not client:
337 if admin_client_required and cls.admin_client:
338 # For convenience silently switch to admin client
339 client = cls.admin_client
340 if not shared:
341 # Keep this network visible from current project
342 project_id = (kwargs.get('project_id') or
343 kwargs.get('tenant_id') or
344 cls.client.tenant_id)
345 kwargs.update(project_id=project_id, tenant_id=project_id)
346 else:
347 # Use default client
348 client = cls.client
349
350 network = client.create_network(name=name, **kwargs)['network']
351 network['client'] = client
352 cls.networks.append(network)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000353 return network
354
355 @classmethod
Federico Ressi61b564e2018-07-06 08:10:31 +0200356 def delete_network(cls, network, client=None):
357 client = client or network.get('client') or cls.client
358 client.delete_network(network['id'])
359
360 @classmethod
361 def create_shared_network(cls, network_name=None, **kwargs):
362 return cls.create_network(name=network_name, shared=True, **kwargs)
Miguel Lavalle124378b2016-09-21 16:41:47 -0500363
364 @classmethod
365 def create_network_keystone_v3(cls, network_name=None, project_id=None,
366 tenant_id=None, client=None):
Federico Ressi61b564e2018-07-06 08:10:31 +0200367 params = {}
368 if project_id:
369 params['project_id'] = project_id
370 if tenant_id:
371 params['tenant_id'] = tenant_id
372 return cls.create_network(name=network_name, client=client, **params)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000373
374 @classmethod
Sławek Kapłońskid98e27d2018-05-07 16:16:28 +0200375 def create_subnet(cls, network, gateway='', cidr=None, mask_bits=None,
Federico Ressi98f20ec2018-05-11 06:09:49 +0200376 ip_version=None, client=None, reserve_cidr=True,
377 **kwargs):
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200378 """Wrapper utility that returns a test subnet.
379
380 Convenient wrapper for client.create_subnet method. It reserves and
381 allocates CIDRs to avoid creating overlapping subnets.
382
383 :param network: network where to create the subnet
384 network['id'] must contain the ID of the network
385
386 :param gateway: gateway IP address
387 It can be a str or a netaddr.IPAddress
388 If gateway is not given, then it will use default address for
389 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 +0200390 if gateway is given as None then no gateway will be assigned
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200391
392 :param cidr: CIDR of the subnet to create
393 It can be either None, a str or a netaddr.IPNetwork instance
394
395 :param mask_bits: CIDR prefix length
396 It can be either None or a numeric value.
397 If cidr parameter is given then mask_bits is used to determinate a
398 sequence of valid CIDR to use as generated.
399 Please see netaddr.IPNetwork.subnet method documentation[1]
400
401 :param ip_version: ip version of generated subnet CIDRs
402 It can be None, IP_VERSION_4 or IP_VERSION_6
403 It has to match given either given CIDR and gateway
404
405 :param ip_version: numeric value (either IP_VERSION_4 or IP_VERSION_6)
406 this value must match CIDR and gateway IP versions if any of them is
407 given
408
409 :param client: client to be used to connect to network service
410
Federico Ressi98f20ec2018-05-11 06:09:49 +0200411 :param reserve_cidr: if True then it reserves assigned CIDR to avoid
412 using the same CIDR for further subnets in the scope of the same
413 test case class
414
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200415 :param **kwargs: optional parameters to be forwarded to wrapped method
416
417 [1] http://netaddr.readthedocs.io/en/latest/tutorial_01.html#supernets-and-subnets # noqa
418 """
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000419
420 # allow tests to use admin client
421 if not client:
422 client = cls.client
423
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200424 if gateway:
425 gateway_ip = netaddr.IPAddress(gateway)
426 if ip_version:
427 if ip_version != gateway_ip.version:
428 raise ValueError(
429 "Gateway IP version doesn't match IP version")
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000430 else:
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200431 ip_version = gateway_ip.version
Sławek Kapłońskid98e27d2018-05-07 16:16:28 +0200432 else:
433 ip_version = ip_version or cls._ip_version
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200434
435 for subnet_cidr in cls.get_subnet_cidrs(
436 ip_version=ip_version, cidr=cidr, mask_bits=mask_bits):
Federico Ressi98f20ec2018-05-11 06:09:49 +0200437 if gateway is not None:
438 kwargs['gateway_ip'] = str(gateway or (subnet_cidr.ip + 1))
Slawek Kaplonski21f53422018-11-02 16:02:09 +0100439 else:
440 kwargs['gateway_ip'] = None
Federico Ressi98f20ec2018-05-11 06:09:49 +0200441 try:
442 body = client.create_subnet(
443 network_id=network['id'],
444 cidr=str(subnet_cidr),
445 ip_version=subnet_cidr.version,
446 **kwargs)
447 break
448 except lib_exc.BadRequest as e:
449 if 'overlaps with another subnet' not in str(e):
450 raise
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000451 else:
452 message = 'Available CIDR for subnet creation could not be found'
453 raise ValueError(message)
454 subnet = body['subnet']
Kevin Bentonba3651c2017-09-01 17:13:01 -0700455 if client is cls.client:
456 cls.subnets.append(subnet)
457 else:
458 cls.admin_subnets.append(subnet)
Federico Ressi98f20ec2018-05-11 06:09:49 +0200459 if reserve_cidr:
460 cls.reserve_subnet_cidr(subnet_cidr)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000461 return subnet
462
463 @classmethod
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200464 def reserve_subnet_cidr(cls, addr, **ipnetwork_kwargs):
465 """Reserve given subnet CIDR making sure it is not used by create_subnet
466
467 :param addr: the CIDR address to be reserved
468 It can be a str or netaddr.IPNetwork instance
469
470 :param **ipnetwork_kwargs: optional netaddr.IPNetwork constructor
471 parameters
472 """
473
474 if not cls.try_reserve_subnet_cidr(addr, **ipnetwork_kwargs):
475 raise ValueError('Subnet CIDR already reserved: %r'.format(
476 addr))
477
478 @classmethod
479 def try_reserve_subnet_cidr(cls, addr, **ipnetwork_kwargs):
480 """Reserve given subnet CIDR if it hasn't been reserved before
481
482 :param addr: the CIDR address to be reserved
483 It can be a str or netaddr.IPNetwork instance
484
485 :param **ipnetwork_kwargs: optional netaddr.IPNetwork constructor
486 parameters
487
488 :return: True if it wasn't reserved before, False elsewhere.
489 """
490
491 subnet_cidr = netaddr.IPNetwork(addr, **ipnetwork_kwargs)
492 if subnet_cidr in cls.reserved_subnet_cidrs:
493 return False
494 else:
495 cls.reserved_subnet_cidrs.add(subnet_cidr)
496 return True
497
498 @classmethod
499 def get_subnet_cidrs(
500 cls, cidr=None, mask_bits=None, ip_version=None):
501 """Iterate over a sequence of unused subnet CIDR for IP version
502
503 :param cidr: CIDR of the subnet to create
504 It can be either None, a str or a netaddr.IPNetwork instance
505
506 :param mask_bits: CIDR prefix length
507 It can be either None or a numeric value.
508 If cidr parameter is given then mask_bits is used to determinate a
509 sequence of valid CIDR to use as generated.
510 Please see netaddr.IPNetwork.subnet method documentation[1]
511
512 :param ip_version: ip version of generated subnet CIDRs
513 It can be None, IP_VERSION_4 or IP_VERSION_6
514 It has to match given CIDR if given
515
516 :return: iterator over reserved CIDRs of type netaddr.IPNetwork
517
518 [1] http://netaddr.readthedocs.io/en/latest/tutorial_01.html#supernets-and-subnets # noqa
519 """
520
521 if cidr:
522 # Generate subnet CIDRs starting from given CIDR
523 # checking it is of requested IP version
524 cidr = netaddr.IPNetwork(cidr, version=ip_version)
525 else:
526 # Generate subnet CIDRs starting from configured values
527 ip_version = ip_version or cls._ip_version
528 if ip_version == const.IP_VERSION_4:
529 mask_bits = mask_bits or config.safe_get_config_value(
530 'network', 'project_network_mask_bits')
531 cidr = netaddr.IPNetwork(config.safe_get_config_value(
532 'network', 'project_network_cidr'))
533 elif ip_version == const.IP_VERSION_6:
534 mask_bits = config.safe_get_config_value(
535 'network', 'project_network_v6_mask_bits')
536 cidr = netaddr.IPNetwork(config.safe_get_config_value(
537 'network', 'project_network_v6_cidr'))
538 else:
539 raise ValueError('Invalid IP version: {!r}'.format(ip_version))
540
541 if mask_bits:
542 subnet_cidrs = cidr.subnet(mask_bits)
543 else:
544 subnet_cidrs = iter([cidr])
545
546 for subnet_cidr in subnet_cidrs:
547 if subnet_cidr not in cls.reserved_subnet_cidrs:
548 yield subnet_cidr
549
550 @classmethod
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000551 def create_port(cls, network, **kwargs):
552 """Wrapper utility that returns a test port."""
Edan Davidd75e48e2018-01-03 02:49:52 -0500553 if CONF.network.port_vnic_type and 'binding:vnic_type' not in kwargs:
554 kwargs['binding:vnic_type'] = CONF.network.port_vnic_type
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000555 body = cls.client.create_port(network_id=network['id'],
556 **kwargs)
557 port = body['port']
558 cls.ports.append(port)
559 return port
560
561 @classmethod
562 def update_port(cls, port, **kwargs):
563 """Wrapper utility that updates a test port."""
564 body = cls.client.update_port(port['id'],
565 **kwargs)
566 return body['port']
567
568 @classmethod
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300569 def _create_router_with_client(
570 cls, client, router_name=None, admin_state_up=False,
571 external_network_id=None, enable_snat=None, **kwargs
572 ):
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000573 ext_gw_info = {}
574 if external_network_id:
575 ext_gw_info['network_id'] = external_network_id
YAMAMOTO Takashi9bd4f972017-06-20 12:49:30 +0900576 if enable_snat is not None:
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000577 ext_gw_info['enable_snat'] = enable_snat
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300578 body = client.create_router(
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000579 router_name, external_gateway_info=ext_gw_info,
580 admin_state_up=admin_state_up, **kwargs)
581 router = body['router']
582 cls.routers.append(router)
583 return router
584
585 @classmethod
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300586 def create_router(cls, *args, **kwargs):
587 return cls._create_router_with_client(cls.client, *args, **kwargs)
588
589 @classmethod
590 def create_admin_router(cls, *args, **kwargs):
rajat294495c042017-06-28 15:37:16 +0530591 return cls._create_router_with_client(cls.os_admin.network_client,
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300592 *args, **kwargs)
593
594 @classmethod
Federico Ressia69dcd52018-07-06 09:45:34 +0200595 def create_floatingip(cls, external_network_id=None, port=None,
596 client=None, **kwargs):
597 """Creates a floating IP.
598
599 Create a floating IP and schedule it for later deletion.
600 If a client is passed, then it is used for deleting the IP too.
601
602 :param external_network_id: network ID where to create
603 By default this is 'CONF.network.public_network_id'.
604
605 :param port: port to bind floating IP to
606 This is translated to 'port_id=port['id']'
607 By default it is None.
608
609 :param client: network client to be used for creating and cleaning up
610 the floating IP.
611
612 :param **kwargs: additional creation parameters to be forwarded to
613 networking server.
614 """
615
616 client = client or cls.client
617 external_network_id = (external_network_id or
618 cls.external_network_id)
619
620 if port:
Federico Ressi47f6ae42018-09-24 16:19:14 +0200621 port_id = kwargs.setdefault('port_id', port['id'])
622 if port_id != port['id']:
623 message = "Port ID specified twice: {!s} != {!s}".format(
624 port_id, port['id'])
625 raise ValueError(message)
Federico Ressia69dcd52018-07-06 09:45:34 +0200626
627 fip = client.create_floatingip(external_network_id,
628 **kwargs)['floatingip']
629
630 # save client to be used later in cls.delete_floatingip
631 # for final cleanup
632 fip['client'] = client
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000633 cls.floating_ips.append(fip)
634 return fip
635
636 @classmethod
Federico Ressia69dcd52018-07-06 09:45:34 +0200637 def delete_floatingip(cls, floating_ip, client=None):
638 """Delete floating IP
639
640 :param client: Client to be used
641 If client is not given it will use the client used to create
642 the floating IP, or cls.client if unknown.
643 """
644
645 client = client or floating_ip.get('client') or cls.client
646 client.delete_floatingip(floating_ip['id'])
647
648 @classmethod
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000649 def create_router_interface(cls, router_id, subnet_id):
650 """Wrapper utility that returns a router interface."""
651 interface = cls.client.add_router_interface_with_subnet_id(
652 router_id, subnet_id)
653 return interface
654
655 @classmethod
Sławek Kapłońskiff294062016-12-04 15:00:54 +0000656 def get_supported_qos_rule_types(cls):
657 body = cls.client.list_qos_rule_types()
658 return [rule_type['type'] for rule_type in body['rule_types']]
659
660 @classmethod
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200661 def create_qos_policy(cls, name, description=None, shared=False,
Hirofumi Ichihara39a6ee12017-08-23 13:55:12 +0900662 tenant_id=None, is_default=False):
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000663 """Wrapper utility that returns a test QoS policy."""
664 body = cls.admin_client.create_qos_policy(
Hirofumi Ichihara39a6ee12017-08-23 13:55:12 +0900665 name, description, shared, tenant_id, is_default)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000666 qos_policy = body['policy']
667 cls.qos_policies.append(qos_policy)
668 return qos_policy
669
670 @classmethod
Sławek Kapłoński153f3452017-03-24 22:04:53 +0000671 def create_qos_bandwidth_limit_rule(cls, policy_id, max_kbps,
672 max_burst_kbps,
Chandan Kumarc125fd12017-11-15 19:41:01 +0530673 direction=const.EGRESS_DIRECTION):
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000674 """Wrapper utility that returns a test QoS bandwidth limit rule."""
675 body = cls.admin_client.create_bandwidth_limit_rule(
Sławek Kapłoński153f3452017-03-24 22:04:53 +0000676 policy_id, max_kbps, max_burst_kbps, direction)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000677 qos_rule = body['bandwidth_limit_rule']
678 cls.qos_rules.append(qos_rule)
679 return qos_rule
680
681 @classmethod
Lajos Katona2f904652018-08-23 14:04:56 +0200682 def create_qos_minimum_bandwidth_rule(cls, policy_id, min_kbps,
683 direction=const.EGRESS_DIRECTION):
684 """Wrapper utility that creates and returns a QoS min bw rule."""
685 body = cls.admin_client.create_minimum_bandwidth_rule(
686 policy_id, direction, min_kbps)
687 qos_rule = body['minimum_bandwidth_rule']
688 cls.qos_rules.append(qos_rule)
689 return qos_rule
690
691 @classmethod
Jakub Libosvar83704832017-12-06 16:02:28 +0000692 def delete_router(cls, router, client=None):
693 client = client or cls.client
Aditya Vaja49819a72018-11-26 14:20:10 -0800694 if 'routes' in router:
695 client.remove_router_extra_routes(router['id'])
Jakub Libosvar83704832017-12-06 16:02:28 +0000696 body = client.list_router_interfaces(router['id'])
Chandan Kumarc125fd12017-11-15 19:41:01 +0530697 interfaces = [port for port in body['ports']
698 if port['device_owner'] in const.ROUTER_INTERFACE_OWNERS]
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000699 for i in interfaces:
700 try:
Jakub Libosvar83704832017-12-06 16:02:28 +0000701 client.remove_router_interface_with_subnet_id(
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000702 router['id'], i['fixed_ips'][0]['subnet_id'])
703 except lib_exc.NotFound:
704 pass
Jakub Libosvar83704832017-12-06 16:02:28 +0000705 client.delete_router(router['id'])
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000706
707 @classmethod
708 def create_address_scope(cls, name, is_admin=False, **kwargs):
709 if is_admin:
710 body = cls.admin_client.create_address_scope(name=name, **kwargs)
711 cls.admin_address_scopes.append(body['address_scope'])
712 else:
713 body = cls.client.create_address_scope(name=name, **kwargs)
714 cls.address_scopes.append(body['address_scope'])
715 return body['address_scope']
716
717 @classmethod
718 def create_subnetpool(cls, name, is_admin=False, **kwargs):
719 if is_admin:
720 body = cls.admin_client.create_subnetpool(name, **kwargs)
721 cls.admin_subnetpools.append(body['subnetpool'])
722 else:
723 body = cls.client.create_subnetpool(name, **kwargs)
724 cls.subnetpools.append(body['subnetpool'])
725 return body['subnetpool']
726
Chandan Kumarc125fd12017-11-15 19:41:01 +0530727 @classmethod
728 def create_project(cls, name=None, description=None):
729 test_project = name or data_utils.rand_name('test_project_')
730 test_description = description or data_utils.rand_name('desc_')
731 project = cls.identity_admin_client.create_project(
732 name=test_project,
733 description=test_description)['project']
734 cls.projects.append(project)
Dongcan Ye2de722e2018-07-04 11:01:37 +0000735 # Create a project will create a default security group.
Dongcan Ye2de722e2018-07-04 11:01:37 +0000736 sgs_list = cls.admin_client.list_security_groups(
737 tenant_id=project['id'])['security_groups']
Federico Ressi4c590d72018-10-10 14:01:08 +0200738 for security_group in sgs_list:
739 # Make sure delete_security_group method will use
740 # the admin client for this group
741 security_group['client'] = cls.admin_client
742 cls.security_groups.append(security_group)
Chandan Kumarc125fd12017-11-15 19:41:01 +0530743 return project
744
745 @classmethod
Federico Ressi4c590d72018-10-10 14:01:08 +0200746 def create_security_group(cls, name=None, project=None, client=None,
747 **kwargs):
748 if project:
749 client = client or cls.admin_client
750 project_id = kwargs.setdefault('project_id', project['id'])
751 tenant_id = kwargs.setdefault('tenant_id', project['id'])
752 if project_id != project['id'] or tenant_id != project['id']:
753 raise ValueError('Project ID specified multiple times')
754 else:
755 client = client or cls.client
756
757 name = name or data_utils.rand_name(cls.__name__)
758 security_group = client.create_security_group(name=name, **kwargs)[
759 'security_group']
760 security_group['client'] = client
761 cls.security_groups.append(security_group)
762 return security_group
763
764 @classmethod
765 def delete_security_group(cls, security_group, client=None):
766 client = client or security_group.get('client') or cls.client
767 client.delete_security_group(security_group['id'])
768
769 @classmethod
770 def create_security_group_rule(cls, security_group=None, project=None,
771 client=None, ip_version=None, **kwargs):
772 if project:
773 client = client or cls.admin_client
774 project_id = kwargs.setdefault('project_id', project['id'])
775 tenant_id = kwargs.setdefault('tenant_id', project['id'])
776 if project_id != project['id'] or tenant_id != project['id']:
777 raise ValueError('Project ID specified multiple times')
778
779 if 'security_group_id' not in kwargs:
780 security_group = (security_group or
781 cls.get_security_group(client=client))
782
783 if security_group:
784 client = client or security_group.get('client')
785 security_group_id = kwargs.setdefault('security_group_id',
786 security_group['id'])
787 if security_group_id != security_group['id']:
788 raise ValueError('Security group ID specified multiple times.')
789
790 ip_version = ip_version or cls._ip_version
791 default_params = (
792 constants.DEFAULT_SECURITY_GROUP_RULE_PARAMS[ip_version])
793 for key, value in default_params.items():
794 kwargs.setdefault(key, value)
795
796 client = client or cls.client
797 return client.create_security_group_rule(**kwargs)[
798 'security_group_rule']
799
800 @classmethod
801 def get_security_group(cls, name='default', client=None):
802 client = client or cls.client
803 security_groups = client.list_security_groups()['security_groups']
804 for security_group in security_groups:
805 if security_group['name'] == name:
806 return security_group
807 raise ValueError("No such security group named {!r}".format(name))
Chandan Kumarc125fd12017-11-15 19:41:01 +0530808
Federico Ressiab286e42018-06-19 09:52:10 +0200809 @classmethod
810 def create_keypair(cls, client=None, name=None, **kwargs):
811 client = client or cls.os_primary.keypairs_client
812 name = name or data_utils.rand_name('keypair-test')
813 keypair = client.create_keypair(name=name, **kwargs)['keypair']
814
815 # save client for later cleanup
816 keypair['client'] = client
817 cls.keypairs.append(keypair)
818 return keypair
819
820 @classmethod
821 def delete_keypair(cls, keypair, client=None):
822 client = (client or keypair.get('client') or
823 cls.os_primary.keypairs_client)
824 client.delete_keypair(keypair_name=keypair['name'])
825
Federico Ressi82e83e32018-07-03 14:19:55 +0200826 @classmethod
827 def create_trunk(cls, port=None, subports=None, client=None, **kwargs):
828 """Create network trunk
829
830 :param port: dictionary containing parent port ID (port['id'])
831 :param client: client to be used for connecting to networking service
832 :param **kwargs: extra parameters to be forwarded to network service
833
834 :returns: dictionary containing created trunk details
835 """
836 client = client or cls.client
837
838 if port:
839 kwargs['port_id'] = port['id']
840
841 trunk = client.create_trunk(subports=subports, **kwargs)['trunk']
842 # Save client reference for later deletion
843 trunk['client'] = client
844 cls.trunks.append(trunk)
845 return trunk
846
847 @classmethod
Huifeng Le1c9f40b2018-11-07 01:14:21 +0800848 def delete_trunk(cls, trunk, client=None, detach_parent_port=True):
Federico Ressi82e83e32018-07-03 14:19:55 +0200849 """Delete network trunk
850
851 :param trunk: dictionary containing trunk ID (trunk['id'])
852
853 :param client: client to be used for connecting to networking service
854 """
855 client = client or trunk.get('client') or cls.client
856 trunk.update(client.show_trunk(trunk['id'])['trunk'])
857
858 if not trunk['admin_state_up']:
859 # Cannot touch trunk before admin_state_up is True
860 client.update_trunk(trunk['id'], admin_state_up=True)
861 if trunk['sub_ports']:
862 # Removes trunk ports before deleting it
863 cls._try_delete_resource(client.remove_subports, trunk['id'],
864 trunk['sub_ports'])
865
866 # we have to detach the interface from the server before
867 # the trunk can be deleted.
868 parent_port = {'id': trunk['port_id']}
869
870 def is_parent_port_detached():
871 parent_port.update(client.show_port(parent_port['id'])['port'])
872 return not parent_port['device_id']
873
Huifeng Le1c9f40b2018-11-07 01:14:21 +0800874 if detach_parent_port and not is_parent_port_detached():
Federico Ressi82e83e32018-07-03 14:19:55 +0200875 # this could probably happen when trunk is deleted and parent port
876 # has been assigned to a VM that is still running. Here we are
877 # assuming that device_id points to such VM.
878 cls.os_primary.compute.InterfacesClient().delete_interface(
879 parent_port['device_id'], parent_port['id'])
880 utils.wait_until_true(is_parent_port_detached)
881
882 client.delete_trunk(trunk['id'])
883
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000884
885class BaseAdminNetworkTest(BaseNetworkTest):
886
887 credentials = ['primary', 'admin']
888
889 @classmethod
890 def setup_clients(cls):
891 super(BaseAdminNetworkTest, cls).setup_clients()
fumihiko kakumaa216fc12017-07-14 10:43:29 +0900892 cls.admin_client = cls.os_admin.network_client
Jakub Libosvarf5758012017-08-15 13:45:30 +0000893 cls.identity_admin_client = cls.os_admin.projects_client
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000894
895 @classmethod
896 def create_metering_label(cls, name, description):
897 """Wrapper utility that returns a test metering label."""
898 body = cls.admin_client.create_metering_label(
899 description=description,
900 name=data_utils.rand_name("metering-label"))
901 metering_label = body['metering_label']
902 cls.metering_labels.append(metering_label)
903 return metering_label
904
905 @classmethod
906 def create_metering_label_rule(cls, remote_ip_prefix, direction,
907 metering_label_id):
908 """Wrapper utility that returns a test metering label rule."""
909 body = cls.admin_client.create_metering_label_rule(
910 remote_ip_prefix=remote_ip_prefix, direction=direction,
911 metering_label_id=metering_label_id)
912 metering_label_rule = body['metering_label_rule']
913 cls.metering_label_rules.append(metering_label_rule)
914 return metering_label_rule
915
916 @classmethod
917 def create_flavor(cls, name, description, service_type):
918 """Wrapper utility that returns a test flavor."""
919 body = cls.admin_client.create_flavor(
920 description=description, service_type=service_type,
921 name=name)
922 flavor = body['flavor']
923 cls.flavors.append(flavor)
924 return flavor
925
926 @classmethod
927 def create_service_profile(cls, description, metainfo, driver):
928 """Wrapper utility that returns a test service profile."""
929 body = cls.admin_client.create_service_profile(
930 driver=driver, metainfo=metainfo, description=description)
931 service_profile = body['service_profile']
932 cls.service_profiles.append(service_profile)
933 return service_profile
934
935 @classmethod
Nguyen Phuong An67993fc2017-11-24 11:30:25 +0700936 def create_log(cls, name, description=None,
937 resource_type='security_group', resource_id=None,
938 target_id=None, event='ALL', enabled=True):
939 """Wrapper utility that returns a test log object."""
940 log_args = {'name': name,
941 'description': description,
942 'resource_type': resource_type,
943 'resource_id': resource_id,
944 'target_id': target_id,
945 'event': event,
946 'enabled': enabled}
947 body = cls.admin_client.create_log(**log_args)
948 log_object = body['log']
949 cls.log_objects.append(log_object)
950 return log_object
951
952 @classmethod
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000953 def get_unused_ip(cls, net_id, ip_version=None):
Gary Kotton011345f2016-06-15 08:04:31 -0700954 """Get an unused ip address in a allocation pool of net"""
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000955 body = cls.admin_client.list_ports(network_id=net_id)
956 ports = body['ports']
957 used_ips = []
958 for port in ports:
959 used_ips.extend(
960 [fixed_ip['ip_address'] for fixed_ip in port['fixed_ips']])
961 body = cls.admin_client.list_subnets(network_id=net_id)
962 subnets = body['subnets']
963
964 for subnet in subnets:
965 if ip_version and subnet['ip_version'] != ip_version:
966 continue
967 cidr = subnet['cidr']
968 allocation_pools = subnet['allocation_pools']
969 iterators = []
970 if allocation_pools:
971 for allocation_pool in allocation_pools:
972 iterators.append(netaddr.iter_iprange(
973 allocation_pool['start'], allocation_pool['end']))
974 else:
975 net = netaddr.IPNetwork(cidr)
976
977 def _iterip():
978 for ip in net:
979 if ip not in (net.network, net.broadcast):
980 yield ip
981 iterators.append(iter(_iterip()))
982
983 for iterator in iterators:
984 for ip in iterator:
985 if str(ip) not in used_ips:
986 return str(ip)
987
988 message = (
989 "net(%s) has no usable IP address in allocation pools" % net_id)
990 raise exceptions.InvalidConfiguration(message)
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200991
Lajos Katona2f904652018-08-23 14:04:56 +0200992 @classmethod
993 def create_provider_network(cls, physnet_name, start_segmentation_id,
994 max_attempts=30):
995 segmentation_id = start_segmentation_id
996 for attempts in itertools.count():
997 try:
998 prov_network = cls.create_network(
999 name=data_utils.rand_name('test_net'),
1000 shared=True,
1001 provider_network_type='vlan',
1002 provider_physical_network=physnet_name,
1003 provider_segmentation_id=segmentation_id)
1004 break
1005 except lib_exc.Conflict:
1006 if attempts > max_attempts:
1007 LOG.exception("Failed to create provider network after "
1008 "%d attempts", attempts)
1009 raise lib_exc.TimeoutException
1010 segmentation_id += 1
1011 if segmentation_id > 4095:
1012 raise lib_exc.TempestException(
1013 "No free segmentation id was found for provider "
1014 "network creation!")
1015 time.sleep(CONF.network.build_interval)
1016 return prov_network
1017
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001018
Sławek Kapłońskiff294062016-12-04 15:00:54 +00001019def require_qos_rule_type(rule_type):
1020 def decorator(f):
1021 @functools.wraps(f)
1022 def wrapper(self, *func_args, **func_kwargs):
1023 if rule_type not in self.get_supported_qos_rule_types():
1024 raise self.skipException(
1025 "%s rule type is required." % rule_type)
1026 return f(self, *func_args, **func_kwargs)
1027 return wrapper
1028 return decorator
1029
1030
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001031def _require_sorting(f):
1032 @functools.wraps(f)
1033 def inner(self, *args, **kwargs):
Chandan Kumarc125fd12017-11-15 19:41:01 +05301034 if not tutils.is_extension_enabled("sorting", "network"):
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001035 self.skipTest('Sorting feature is required')
1036 return f(self, *args, **kwargs)
1037 return inner
1038
1039
1040def _require_pagination(f):
1041 @functools.wraps(f)
1042 def inner(self, *args, **kwargs):
Chandan Kumarc125fd12017-11-15 19:41:01 +05301043 if not tutils.is_extension_enabled("pagination", "network"):
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001044 self.skipTest('Pagination feature is required')
1045 return f(self, *args, **kwargs)
1046 return inner
1047
1048
1049class BaseSearchCriteriaTest(BaseNetworkTest):
1050
1051 # This should be defined by subclasses to reflect resource name to test
1052 resource = None
1053
Armando Migliaccio57581c62016-07-01 10:13:19 -07001054 field = 'name'
1055
Ihar Hrachyshkaa8fe5a12016-05-24 14:50:58 +02001056 # NOTE(ihrachys): some names, like those starting with an underscore (_)
1057 # are sorted differently depending on whether the plugin implements native
1058 # sorting support, or not. So we avoid any such cases here, sticking to
1059 # alphanumeric. Also test a case when there are multiple resources with the
1060 # same name
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001061 resource_names = ('test1', 'abc1', 'test10', '123test') + ('test1',)
1062
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001063 force_tenant_isolation = True
1064
Ihar Hrachyshkaa8fe5a12016-05-24 14:50:58 +02001065 list_kwargs = {}
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001066
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001067 list_as_admin = False
1068
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001069 def assertSameOrder(self, original, actual):
1070 # gracefully handle iterators passed
1071 original = list(original)
1072 actual = list(actual)
1073 self.assertEqual(len(original), len(actual))
1074 for expected, res in zip(original, actual):
Armando Migliaccio57581c62016-07-01 10:13:19 -07001075 self.assertEqual(expected[self.field], res[self.field])
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001076
1077 @utils.classproperty
1078 def plural_name(self):
1079 return '%ss' % self.resource
1080
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001081 @property
1082 def list_client(self):
1083 return self.admin_client if self.list_as_admin else self.client
1084
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001085 def list_method(self, *args, **kwargs):
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001086 method = getattr(self.list_client, 'list_%s' % self.plural_name)
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001087 kwargs.update(self.list_kwargs)
1088 return method(*args, **kwargs)
1089
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001090 def get_bare_url(self, url):
1091 base_url = self.client.base_url
1092 self.assertTrue(url.startswith(base_url))
1093 return url[len(base_url):]
1094
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001095 @classmethod
1096 def _extract_resources(cls, body):
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001097 return body[cls.plural_name]
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001098
1099 def _test_list_sorts(self, direction):
1100 sort_args = {
1101 'sort_dir': direction,
Armando Migliaccio57581c62016-07-01 10:13:19 -07001102 'sort_key': self.field
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001103 }
1104 body = self.list_method(**sort_args)
1105 resources = self._extract_resources(body)
1106 self.assertNotEmpty(
1107 resources, "%s list returned is empty" % self.resource)
Armando Migliaccio57581c62016-07-01 10:13:19 -07001108 retrieved_names = [res[self.field] for res in resources]
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001109 expected = sorted(retrieved_names)
1110 if direction == constants.SORT_DIRECTION_DESC:
1111 expected = list(reversed(expected))
1112 self.assertEqual(expected, retrieved_names)
1113
1114 @_require_sorting
1115 def _test_list_sorts_asc(self):
1116 self._test_list_sorts(constants.SORT_DIRECTION_ASC)
1117
1118 @_require_sorting
1119 def _test_list_sorts_desc(self):
1120 self._test_list_sorts(constants.SORT_DIRECTION_DESC)
1121
1122 @_require_pagination
1123 def _test_list_pagination(self):
1124 for limit in range(1, len(self.resource_names) + 1):
1125 pagination_args = {
1126 'limit': limit,
1127 }
1128 body = self.list_method(**pagination_args)
1129 resources = self._extract_resources(body)
1130 self.assertEqual(limit, len(resources))
1131
1132 @_require_pagination
1133 def _test_list_no_pagination_limit_0(self):
1134 pagination_args = {
1135 'limit': 0,
1136 }
1137 body = self.list_method(**pagination_args)
1138 resources = self._extract_resources(body)
Béla Vancsicsf1806182016-08-23 07:36:18 +02001139 self.assertGreaterEqual(len(resources), len(self.resource_names))
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001140
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001141 def _test_list_pagination_iteratively(self, lister):
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001142 # first, collect all resources for later comparison
1143 sort_args = {
1144 'sort_dir': constants.SORT_DIRECTION_ASC,
Armando Migliaccio57581c62016-07-01 10:13:19 -07001145 'sort_key': self.field
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001146 }
1147 body = self.list_method(**sort_args)
1148 expected_resources = self._extract_resources(body)
1149 self.assertNotEmpty(expected_resources)
1150
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001151 resources = lister(
1152 len(expected_resources), sort_args
1153 )
1154
1155 # finally, compare that the list retrieved in one go is identical to
1156 # the one containing pagination results
1157 self.assertSameOrder(expected_resources, resources)
1158
1159 def _list_all_with_marker(self, niterations, sort_args):
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001160 # paginate resources one by one, using last fetched resource as a
1161 # marker
1162 resources = []
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001163 for i in range(niterations):
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001164 pagination_args = sort_args.copy()
1165 pagination_args['limit'] = 1
1166 if resources:
1167 pagination_args['marker'] = resources[-1]['id']
1168 body = self.list_method(**pagination_args)
1169 resources_ = self._extract_resources(body)
1170 self.assertEqual(1, len(resources_))
1171 resources.extend(resources_)
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001172 return resources
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001173
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001174 @_require_pagination
1175 @_require_sorting
1176 def _test_list_pagination_with_marker(self):
1177 self._test_list_pagination_iteratively(self._list_all_with_marker)
1178
1179 def _list_all_with_hrefs(self, niterations, sort_args):
1180 # paginate resources one by one, using next href links
1181 resources = []
1182 prev_links = {}
1183
1184 for i in range(niterations):
1185 if prev_links:
1186 uri = self.get_bare_url(prev_links['next'])
1187 else:
Ihar Hrachyshka7f79fe62016-06-07 21:23:44 +02001188 sort_args.update(self.list_kwargs)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001189 uri = self.list_client.build_uri(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001190 self.plural_name, limit=1, **sort_args)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001191 prev_links, body = self.list_client.get_uri_with_links(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001192 self.plural_name, uri
1193 )
1194 resources_ = self._extract_resources(body)
1195 self.assertEqual(1, len(resources_))
1196 resources.extend(resources_)
1197
1198 # The last element is empty and does not contain 'next' link
1199 uri = self.get_bare_url(prev_links['next'])
1200 prev_links, body = self.client.get_uri_with_links(
1201 self.plural_name, uri
1202 )
1203 self.assertNotIn('next', prev_links)
1204
1205 # Now walk backwards and compare results
1206 resources2 = []
1207 for i in range(niterations):
1208 uri = self.get_bare_url(prev_links['previous'])
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001209 prev_links, body = self.list_client.get_uri_with_links(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001210 self.plural_name, uri
1211 )
1212 resources_ = self._extract_resources(body)
1213 self.assertEqual(1, len(resources_))
1214 resources2.extend(resources_)
1215
1216 self.assertSameOrder(resources, reversed(resources2))
1217
1218 return resources
1219
1220 @_require_pagination
1221 @_require_sorting
1222 def _test_list_pagination_with_href_links(self):
1223 self._test_list_pagination_iteratively(self._list_all_with_hrefs)
1224
1225 @_require_pagination
1226 @_require_sorting
1227 def _test_list_pagination_page_reverse_with_href_links(
1228 self, direction=constants.SORT_DIRECTION_ASC):
1229 pagination_args = {
1230 'sort_dir': direction,
Armando Migliaccio57581c62016-07-01 10:13:19 -07001231 'sort_key': self.field,
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001232 }
1233 body = self.list_method(**pagination_args)
1234 expected_resources = self._extract_resources(body)
1235
1236 page_size = 2
1237 pagination_args['limit'] = page_size
1238
1239 prev_links = {}
1240 resources = []
1241 num_resources = len(expected_resources)
1242 niterations = int(math.ceil(float(num_resources) / page_size))
1243 for i in range(niterations):
1244 if prev_links:
1245 uri = self.get_bare_url(prev_links['previous'])
1246 else:
Ihar Hrachyshka7f79fe62016-06-07 21:23:44 +02001247 pagination_args.update(self.list_kwargs)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001248 uri = self.list_client.build_uri(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001249 self.plural_name, page_reverse=True, **pagination_args)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001250 prev_links, body = self.list_client.get_uri_with_links(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001251 self.plural_name, uri
1252 )
1253 resources_ = self._extract_resources(body)
Béla Vancsicsf1806182016-08-23 07:36:18 +02001254 self.assertGreaterEqual(page_size, len(resources_))
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001255 resources.extend(reversed(resources_))
1256
1257 self.assertSameOrder(expected_resources, reversed(resources))
1258
1259 @_require_pagination
1260 @_require_sorting
1261 def _test_list_pagination_page_reverse_asc(self):
1262 self._test_list_pagination_page_reverse(
1263 direction=constants.SORT_DIRECTION_ASC)
1264
1265 @_require_pagination
1266 @_require_sorting
1267 def _test_list_pagination_page_reverse_desc(self):
1268 self._test_list_pagination_page_reverse(
1269 direction=constants.SORT_DIRECTION_DESC)
1270
1271 def _test_list_pagination_page_reverse(self, direction):
1272 pagination_args = {
1273 'sort_dir': direction,
Armando Migliaccio57581c62016-07-01 10:13:19 -07001274 'sort_key': self.field,
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001275 'limit': 3,
1276 }
1277 body = self.list_method(**pagination_args)
1278 expected_resources = self._extract_resources(body)
1279
1280 pagination_args['limit'] -= 1
1281 pagination_args['marker'] = expected_resources[-1]['id']
1282 pagination_args['page_reverse'] = True
1283 body = self.list_method(**pagination_args)
1284
1285 self.assertSameOrder(
1286 # the last entry is not included in 2nd result when used as a
1287 # marker
1288 expected_resources[:-1],
1289 self._extract_resources(body))
Victor Morales1be97b42016-09-05 08:50:06 -05001290
Hongbin Lu54f55922018-07-12 19:05:39 +00001291 @tutils.requires_ext(extension="filter-validation", service="network")
1292 def _test_list_validation_filters(
1293 self, validation_args, filter_is_valid=True):
1294 if not filter_is_valid:
1295 self.assertRaises(lib_exc.BadRequest, self.list_method,
1296 **validation_args)
1297 else:
1298 body = self.list_method(**validation_args)
1299 resources = self._extract_resources(body)
1300 for resource in resources:
1301 self.assertIn(resource['name'], self.resource_names)