blob: 7b91d9421e6da509160111ee2a0347ca2e6750a9 [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 = []
Kailun Qineaaf9782018-12-20 04:45:01 +0800138 cls.network_segment_ranges = []
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
Kailun Qineaaf9782018-12-20 04:45:01 +0800248 # Clean up network_segment_ranges
249 for network_segment_range in cls.network_segment_ranges:
250 cls._try_delete_resource(
251 cls.admin_client.delete_network_segment_range,
252 network_segment_range['id'])
253
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000254 super(BaseNetworkTest, cls).resource_cleanup()
255
256 @classmethod
257 def _try_delete_resource(cls, delete_callable, *args, **kwargs):
258 """Cleanup resources in case of test-failure
259
260 Some resources are explicitly deleted by the test.
261 If the test failed to delete a resource, this method will execute
262 the appropriate delete methods. Otherwise, the method ignores NotFound
263 exceptions thrown for resources that were correctly deleted by the
264 test.
265
266 :param delete_callable: delete method
267 :param args: arguments for delete method
268 :param kwargs: keyword arguments for delete method
269 """
270 try:
271 delete_callable(*args, **kwargs)
272 # if resource is not found, this means it was deleted in the test
273 except lib_exc.NotFound:
274 pass
275
276 @classmethod
Federico Ressi61b564e2018-07-06 08:10:31 +0200277 def create_network(cls, network_name=None, client=None, external=None,
278 shared=None, provider_network_type=None,
279 provider_physical_network=None,
280 provider_segmentation_id=None, **kwargs):
281 """Create a network.
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000282
Federico Ressi61b564e2018-07-06 08:10:31 +0200283 When client is not provider and admin_client is attribute is not None
284 (for example when using BaseAdminNetworkTest base class) and using any
285 of the convenience parameters (external, shared, provider_network_type,
286 provider_physical_network and provider_segmentation_id) it silently
287 uses admin_client. If the network is not shared then it uses the same
288 project_id as regular client.
289
290 :param network_name: Human-readable name of the network
291
292 :param client: client to be used for connecting to network service
293
294 :param external: indicates whether the network has an external routing
295 facility that's not managed by the networking service.
296
297 :param shared: indicates whether this resource is shared across all
298 projects. By default, only administrative users can change this value.
299 If True and admin_client attribute is not None, then the network is
300 created under administrative project.
301
302 :param provider_network_type: the type of physical network that this
303 network should be mapped to. For example, 'flat', 'vlan', 'vxlan', or
304 'gre'. Valid values depend on a networking back-end.
305
306 :param provider_physical_network: the physical network where this
307 network should be implemented. The Networking API v2.0 does not provide
308 a way to list available physical networks. For example, the Open
309 vSwitch plug-in configuration file defines a symbolic name that maps to
310 specific bridges on each compute host.
311
312 :param provider_segmentation_id: The ID of the isolated segment on the
313 physical network. The network_type attribute defines the segmentation
314 model. For example, if the network_type value is 'vlan', this ID is a
315 vlan identifier. If the network_type value is 'gre', this ID is a gre
316 key.
317
318 :param **kwargs: extra parameters to be forwarded to network service
319 """
320
321 name = (network_name or kwargs.pop('name', None) or
322 data_utils.rand_name('test-network-'))
323
324 # translate convenience parameters
325 admin_client_required = False
326 if provider_network_type:
327 admin_client_required = True
328 kwargs['provider:network_type'] = provider_network_type
329 if provider_physical_network:
330 admin_client_required = True
331 kwargs['provider:physical_network'] = provider_physical_network
332 if provider_segmentation_id:
333 admin_client_required = True
334 kwargs['provider:segmentation_id'] = provider_segmentation_id
335 if external is not None:
336 admin_client_required = True
337 kwargs['router:external'] = bool(external)
338 if shared is not None:
339 admin_client_required = True
340 kwargs['shared'] = bool(shared)
341
342 if not client:
343 if admin_client_required and cls.admin_client:
344 # For convenience silently switch to admin client
345 client = cls.admin_client
346 if not shared:
347 # Keep this network visible from current project
348 project_id = (kwargs.get('project_id') or
349 kwargs.get('tenant_id') or
350 cls.client.tenant_id)
351 kwargs.update(project_id=project_id, tenant_id=project_id)
352 else:
353 # Use default client
354 client = cls.client
355
356 network = client.create_network(name=name, **kwargs)['network']
357 network['client'] = client
358 cls.networks.append(network)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000359 return network
360
361 @classmethod
Federico Ressi61b564e2018-07-06 08:10:31 +0200362 def delete_network(cls, network, client=None):
363 client = client or network.get('client') or cls.client
364 client.delete_network(network['id'])
365
366 @classmethod
367 def create_shared_network(cls, network_name=None, **kwargs):
368 return cls.create_network(name=network_name, shared=True, **kwargs)
Miguel Lavalle124378b2016-09-21 16:41:47 -0500369
370 @classmethod
371 def create_network_keystone_v3(cls, network_name=None, project_id=None,
372 tenant_id=None, client=None):
Federico Ressi61b564e2018-07-06 08:10:31 +0200373 params = {}
374 if project_id:
375 params['project_id'] = project_id
376 if tenant_id:
377 params['tenant_id'] = tenant_id
378 return cls.create_network(name=network_name, client=client, **params)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000379
380 @classmethod
Sławek Kapłońskid98e27d2018-05-07 16:16:28 +0200381 def create_subnet(cls, network, gateway='', cidr=None, mask_bits=None,
Federico Ressi98f20ec2018-05-11 06:09:49 +0200382 ip_version=None, client=None, reserve_cidr=True,
383 **kwargs):
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200384 """Wrapper utility that returns a test subnet.
385
386 Convenient wrapper for client.create_subnet method. It reserves and
387 allocates CIDRs to avoid creating overlapping subnets.
388
389 :param network: network where to create the subnet
390 network['id'] must contain the ID of the network
391
392 :param gateway: gateway IP address
393 It can be a str or a netaddr.IPAddress
394 If gateway is not given, then it will use default address for
395 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 +0200396 if gateway is given as None then no gateway will be assigned
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200397
398 :param cidr: CIDR of the subnet to create
399 It can be either None, a str or a netaddr.IPNetwork instance
400
401 :param mask_bits: CIDR prefix length
402 It can be either None or a numeric value.
403 If cidr parameter is given then mask_bits is used to determinate a
404 sequence of valid CIDR to use as generated.
405 Please see netaddr.IPNetwork.subnet method documentation[1]
406
407 :param ip_version: ip version of generated subnet CIDRs
408 It can be None, IP_VERSION_4 or IP_VERSION_6
409 It has to match given either given CIDR and gateway
410
411 :param ip_version: numeric value (either IP_VERSION_4 or IP_VERSION_6)
412 this value must match CIDR and gateway IP versions if any of them is
413 given
414
415 :param client: client to be used to connect to network service
416
Federico Ressi98f20ec2018-05-11 06:09:49 +0200417 :param reserve_cidr: if True then it reserves assigned CIDR to avoid
418 using the same CIDR for further subnets in the scope of the same
419 test case class
420
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200421 :param **kwargs: optional parameters to be forwarded to wrapped method
422
423 [1] http://netaddr.readthedocs.io/en/latest/tutorial_01.html#supernets-and-subnets # noqa
424 """
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000425
426 # allow tests to use admin client
427 if not client:
428 client = cls.client
429
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200430 if gateway:
431 gateway_ip = netaddr.IPAddress(gateway)
432 if ip_version:
433 if ip_version != gateway_ip.version:
434 raise ValueError(
435 "Gateway IP version doesn't match IP version")
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000436 else:
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200437 ip_version = gateway_ip.version
Sławek Kapłońskid98e27d2018-05-07 16:16:28 +0200438 else:
439 ip_version = ip_version or cls._ip_version
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200440
441 for subnet_cidr in cls.get_subnet_cidrs(
442 ip_version=ip_version, cidr=cidr, mask_bits=mask_bits):
Federico Ressi98f20ec2018-05-11 06:09:49 +0200443 if gateway is not None:
444 kwargs['gateway_ip'] = str(gateway or (subnet_cidr.ip + 1))
Slawek Kaplonski21f53422018-11-02 16:02:09 +0100445 else:
446 kwargs['gateway_ip'] = None
Federico Ressi98f20ec2018-05-11 06:09:49 +0200447 try:
448 body = client.create_subnet(
449 network_id=network['id'],
450 cidr=str(subnet_cidr),
451 ip_version=subnet_cidr.version,
452 **kwargs)
453 break
454 except lib_exc.BadRequest as e:
455 if 'overlaps with another subnet' not in str(e):
456 raise
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000457 else:
458 message = 'Available CIDR for subnet creation could not be found'
459 raise ValueError(message)
460 subnet = body['subnet']
Kevin Bentonba3651c2017-09-01 17:13:01 -0700461 if client is cls.client:
462 cls.subnets.append(subnet)
463 else:
464 cls.admin_subnets.append(subnet)
Federico Ressi98f20ec2018-05-11 06:09:49 +0200465 if reserve_cidr:
466 cls.reserve_subnet_cidr(subnet_cidr)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000467 return subnet
468
469 @classmethod
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200470 def reserve_subnet_cidr(cls, addr, **ipnetwork_kwargs):
471 """Reserve given subnet CIDR making sure it is not used by create_subnet
472
473 :param addr: the CIDR address to be reserved
474 It can be a str or netaddr.IPNetwork instance
475
476 :param **ipnetwork_kwargs: optional netaddr.IPNetwork constructor
477 parameters
478 """
479
480 if not cls.try_reserve_subnet_cidr(addr, **ipnetwork_kwargs):
481 raise ValueError('Subnet CIDR already reserved: %r'.format(
482 addr))
483
484 @classmethod
485 def try_reserve_subnet_cidr(cls, addr, **ipnetwork_kwargs):
486 """Reserve given subnet CIDR if it hasn't been reserved before
487
488 :param addr: the CIDR address to be reserved
489 It can be a str or netaddr.IPNetwork instance
490
491 :param **ipnetwork_kwargs: optional netaddr.IPNetwork constructor
492 parameters
493
494 :return: True if it wasn't reserved before, False elsewhere.
495 """
496
497 subnet_cidr = netaddr.IPNetwork(addr, **ipnetwork_kwargs)
498 if subnet_cidr in cls.reserved_subnet_cidrs:
499 return False
500 else:
501 cls.reserved_subnet_cidrs.add(subnet_cidr)
502 return True
503
504 @classmethod
505 def get_subnet_cidrs(
506 cls, cidr=None, mask_bits=None, ip_version=None):
507 """Iterate over a sequence of unused subnet CIDR for IP version
508
509 :param cidr: CIDR of the subnet to create
510 It can be either None, a str or a netaddr.IPNetwork instance
511
512 :param mask_bits: CIDR prefix length
513 It can be either None or a numeric value.
514 If cidr parameter is given then mask_bits is used to determinate a
515 sequence of valid CIDR to use as generated.
516 Please see netaddr.IPNetwork.subnet method documentation[1]
517
518 :param ip_version: ip version of generated subnet CIDRs
519 It can be None, IP_VERSION_4 or IP_VERSION_6
520 It has to match given CIDR if given
521
522 :return: iterator over reserved CIDRs of type netaddr.IPNetwork
523
524 [1] http://netaddr.readthedocs.io/en/latest/tutorial_01.html#supernets-and-subnets # noqa
525 """
526
527 if cidr:
528 # Generate subnet CIDRs starting from given CIDR
529 # checking it is of requested IP version
530 cidr = netaddr.IPNetwork(cidr, version=ip_version)
531 else:
532 # Generate subnet CIDRs starting from configured values
533 ip_version = ip_version or cls._ip_version
534 if ip_version == const.IP_VERSION_4:
535 mask_bits = mask_bits or config.safe_get_config_value(
536 'network', 'project_network_mask_bits')
537 cidr = netaddr.IPNetwork(config.safe_get_config_value(
538 'network', 'project_network_cidr'))
539 elif ip_version == const.IP_VERSION_6:
540 mask_bits = config.safe_get_config_value(
541 'network', 'project_network_v6_mask_bits')
542 cidr = netaddr.IPNetwork(config.safe_get_config_value(
543 'network', 'project_network_v6_cidr'))
544 else:
545 raise ValueError('Invalid IP version: {!r}'.format(ip_version))
546
547 if mask_bits:
548 subnet_cidrs = cidr.subnet(mask_bits)
549 else:
550 subnet_cidrs = iter([cidr])
551
552 for subnet_cidr in subnet_cidrs:
553 if subnet_cidr not in cls.reserved_subnet_cidrs:
554 yield subnet_cidr
555
556 @classmethod
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000557 def create_port(cls, network, **kwargs):
558 """Wrapper utility that returns a test port."""
Edan Davidd75e48e2018-01-03 02:49:52 -0500559 if CONF.network.port_vnic_type and 'binding:vnic_type' not in kwargs:
560 kwargs['binding:vnic_type'] = CONF.network.port_vnic_type
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000561 body = cls.client.create_port(network_id=network['id'],
562 **kwargs)
563 port = body['port']
564 cls.ports.append(port)
565 return port
566
567 @classmethod
568 def update_port(cls, port, **kwargs):
569 """Wrapper utility that updates a test port."""
570 body = cls.client.update_port(port['id'],
571 **kwargs)
572 return body['port']
573
574 @classmethod
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300575 def _create_router_with_client(
576 cls, client, router_name=None, admin_state_up=False,
577 external_network_id=None, enable_snat=None, **kwargs
578 ):
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000579 ext_gw_info = {}
580 if external_network_id:
581 ext_gw_info['network_id'] = external_network_id
YAMAMOTO Takashi9bd4f972017-06-20 12:49:30 +0900582 if enable_snat is not None:
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000583 ext_gw_info['enable_snat'] = enable_snat
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300584 body = client.create_router(
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000585 router_name, external_gateway_info=ext_gw_info,
586 admin_state_up=admin_state_up, **kwargs)
587 router = body['router']
588 cls.routers.append(router)
589 return router
590
591 @classmethod
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300592 def create_router(cls, *args, **kwargs):
593 return cls._create_router_with_client(cls.client, *args, **kwargs)
594
595 @classmethod
596 def create_admin_router(cls, *args, **kwargs):
rajat294495c042017-06-28 15:37:16 +0530597 return cls._create_router_with_client(cls.os_admin.network_client,
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300598 *args, **kwargs)
599
600 @classmethod
Federico Ressia69dcd52018-07-06 09:45:34 +0200601 def create_floatingip(cls, external_network_id=None, port=None,
602 client=None, **kwargs):
603 """Creates a floating IP.
604
605 Create a floating IP and schedule it for later deletion.
606 If a client is passed, then it is used for deleting the IP too.
607
608 :param external_network_id: network ID where to create
609 By default this is 'CONF.network.public_network_id'.
610
611 :param port: port to bind floating IP to
612 This is translated to 'port_id=port['id']'
613 By default it is None.
614
615 :param client: network client to be used for creating and cleaning up
616 the floating IP.
617
618 :param **kwargs: additional creation parameters to be forwarded to
619 networking server.
620 """
621
622 client = client or cls.client
623 external_network_id = (external_network_id or
624 cls.external_network_id)
625
626 if port:
Federico Ressi47f6ae42018-09-24 16:19:14 +0200627 port_id = kwargs.setdefault('port_id', port['id'])
628 if port_id != port['id']:
629 message = "Port ID specified twice: {!s} != {!s}".format(
630 port_id, port['id'])
631 raise ValueError(message)
Federico Ressia69dcd52018-07-06 09:45:34 +0200632
633 fip = client.create_floatingip(external_network_id,
634 **kwargs)['floatingip']
635
636 # save client to be used later in cls.delete_floatingip
637 # for final cleanup
638 fip['client'] = client
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000639 cls.floating_ips.append(fip)
640 return fip
641
642 @classmethod
Federico Ressia69dcd52018-07-06 09:45:34 +0200643 def delete_floatingip(cls, floating_ip, client=None):
644 """Delete floating IP
645
646 :param client: Client to be used
647 If client is not given it will use the client used to create
648 the floating IP, or cls.client if unknown.
649 """
650
651 client = client or floating_ip.get('client') or cls.client
652 client.delete_floatingip(floating_ip['id'])
653
654 @classmethod
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000655 def create_router_interface(cls, router_id, subnet_id):
656 """Wrapper utility that returns a router interface."""
657 interface = cls.client.add_router_interface_with_subnet_id(
658 router_id, subnet_id)
659 return interface
660
661 @classmethod
Sławek Kapłońskiff294062016-12-04 15:00:54 +0000662 def get_supported_qos_rule_types(cls):
663 body = cls.client.list_qos_rule_types()
664 return [rule_type['type'] for rule_type in body['rule_types']]
665
666 @classmethod
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200667 def create_qos_policy(cls, name, description=None, shared=False,
Hirofumi Ichihara39a6ee12017-08-23 13:55:12 +0900668 tenant_id=None, is_default=False):
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000669 """Wrapper utility that returns a test QoS policy."""
670 body = cls.admin_client.create_qos_policy(
Hirofumi Ichihara39a6ee12017-08-23 13:55:12 +0900671 name, description, shared, tenant_id, is_default)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000672 qos_policy = body['policy']
673 cls.qos_policies.append(qos_policy)
674 return qos_policy
675
676 @classmethod
Sławek Kapłoński153f3452017-03-24 22:04:53 +0000677 def create_qos_bandwidth_limit_rule(cls, policy_id, max_kbps,
678 max_burst_kbps,
Chandan Kumarc125fd12017-11-15 19:41:01 +0530679 direction=const.EGRESS_DIRECTION):
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000680 """Wrapper utility that returns a test QoS bandwidth limit rule."""
681 body = cls.admin_client.create_bandwidth_limit_rule(
Sławek Kapłoński153f3452017-03-24 22:04:53 +0000682 policy_id, max_kbps, max_burst_kbps, direction)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000683 qos_rule = body['bandwidth_limit_rule']
684 cls.qos_rules.append(qos_rule)
685 return qos_rule
686
687 @classmethod
Lajos Katona2f904652018-08-23 14:04:56 +0200688 def create_qos_minimum_bandwidth_rule(cls, policy_id, min_kbps,
689 direction=const.EGRESS_DIRECTION):
690 """Wrapper utility that creates and returns a QoS min bw rule."""
691 body = cls.admin_client.create_minimum_bandwidth_rule(
692 policy_id, direction, min_kbps)
693 qos_rule = body['minimum_bandwidth_rule']
694 cls.qos_rules.append(qos_rule)
695 return qos_rule
696
697 @classmethod
Jakub Libosvar83704832017-12-06 16:02:28 +0000698 def delete_router(cls, router, client=None):
699 client = client or cls.client
Aditya Vaja49819a72018-11-26 14:20:10 -0800700 if 'routes' in router:
701 client.remove_router_extra_routes(router['id'])
Jakub Libosvar83704832017-12-06 16:02:28 +0000702 body = client.list_router_interfaces(router['id'])
Chandan Kumarc125fd12017-11-15 19:41:01 +0530703 interfaces = [port for port in body['ports']
704 if port['device_owner'] in const.ROUTER_INTERFACE_OWNERS]
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000705 for i in interfaces:
706 try:
Jakub Libosvar83704832017-12-06 16:02:28 +0000707 client.remove_router_interface_with_subnet_id(
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000708 router['id'], i['fixed_ips'][0]['subnet_id'])
709 except lib_exc.NotFound:
710 pass
Jakub Libosvar83704832017-12-06 16:02:28 +0000711 client.delete_router(router['id'])
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000712
713 @classmethod
714 def create_address_scope(cls, name, is_admin=False, **kwargs):
715 if is_admin:
716 body = cls.admin_client.create_address_scope(name=name, **kwargs)
717 cls.admin_address_scopes.append(body['address_scope'])
718 else:
719 body = cls.client.create_address_scope(name=name, **kwargs)
720 cls.address_scopes.append(body['address_scope'])
721 return body['address_scope']
722
723 @classmethod
724 def create_subnetpool(cls, name, is_admin=False, **kwargs):
725 if is_admin:
726 body = cls.admin_client.create_subnetpool(name, **kwargs)
727 cls.admin_subnetpools.append(body['subnetpool'])
728 else:
729 body = cls.client.create_subnetpool(name, **kwargs)
730 cls.subnetpools.append(body['subnetpool'])
731 return body['subnetpool']
732
Chandan Kumarc125fd12017-11-15 19:41:01 +0530733 @classmethod
734 def create_project(cls, name=None, description=None):
735 test_project = name or data_utils.rand_name('test_project_')
736 test_description = description or data_utils.rand_name('desc_')
737 project = cls.identity_admin_client.create_project(
738 name=test_project,
739 description=test_description)['project']
740 cls.projects.append(project)
Dongcan Ye2de722e2018-07-04 11:01:37 +0000741 # Create a project will create a default security group.
Dongcan Ye2de722e2018-07-04 11:01:37 +0000742 sgs_list = cls.admin_client.list_security_groups(
743 tenant_id=project['id'])['security_groups']
Federico Ressi4c590d72018-10-10 14:01:08 +0200744 for security_group in sgs_list:
745 # Make sure delete_security_group method will use
746 # the admin client for this group
747 security_group['client'] = cls.admin_client
748 cls.security_groups.append(security_group)
Chandan Kumarc125fd12017-11-15 19:41:01 +0530749 return project
750
751 @classmethod
Federico Ressi4c590d72018-10-10 14:01:08 +0200752 def create_security_group(cls, name=None, project=None, client=None,
753 **kwargs):
754 if project:
755 client = client or cls.admin_client
756 project_id = kwargs.setdefault('project_id', project['id'])
757 tenant_id = kwargs.setdefault('tenant_id', project['id'])
758 if project_id != project['id'] or tenant_id != project['id']:
759 raise ValueError('Project ID specified multiple times')
760 else:
761 client = client or cls.client
762
763 name = name or data_utils.rand_name(cls.__name__)
764 security_group = client.create_security_group(name=name, **kwargs)[
765 'security_group']
766 security_group['client'] = client
767 cls.security_groups.append(security_group)
768 return security_group
769
770 @classmethod
771 def delete_security_group(cls, security_group, client=None):
772 client = client or security_group.get('client') or cls.client
773 client.delete_security_group(security_group['id'])
774
775 @classmethod
776 def create_security_group_rule(cls, security_group=None, project=None,
777 client=None, ip_version=None, **kwargs):
778 if project:
779 client = client or cls.admin_client
780 project_id = kwargs.setdefault('project_id', project['id'])
781 tenant_id = kwargs.setdefault('tenant_id', project['id'])
782 if project_id != project['id'] or tenant_id != project['id']:
783 raise ValueError('Project ID specified multiple times')
784
785 if 'security_group_id' not in kwargs:
786 security_group = (security_group or
787 cls.get_security_group(client=client))
788
789 if security_group:
790 client = client or security_group.get('client')
791 security_group_id = kwargs.setdefault('security_group_id',
792 security_group['id'])
793 if security_group_id != security_group['id']:
794 raise ValueError('Security group ID specified multiple times.')
795
796 ip_version = ip_version or cls._ip_version
797 default_params = (
798 constants.DEFAULT_SECURITY_GROUP_RULE_PARAMS[ip_version])
799 for key, value in default_params.items():
800 kwargs.setdefault(key, value)
801
802 client = client or cls.client
803 return client.create_security_group_rule(**kwargs)[
804 'security_group_rule']
805
806 @classmethod
807 def get_security_group(cls, name='default', client=None):
808 client = client or cls.client
809 security_groups = client.list_security_groups()['security_groups']
810 for security_group in security_groups:
811 if security_group['name'] == name:
812 return security_group
813 raise ValueError("No such security group named {!r}".format(name))
Chandan Kumarc125fd12017-11-15 19:41:01 +0530814
Federico Ressiab286e42018-06-19 09:52:10 +0200815 @classmethod
816 def create_keypair(cls, client=None, name=None, **kwargs):
817 client = client or cls.os_primary.keypairs_client
818 name = name or data_utils.rand_name('keypair-test')
819 keypair = client.create_keypair(name=name, **kwargs)['keypair']
820
821 # save client for later cleanup
822 keypair['client'] = client
823 cls.keypairs.append(keypair)
824 return keypair
825
826 @classmethod
827 def delete_keypair(cls, keypair, client=None):
828 client = (client or keypair.get('client') or
829 cls.os_primary.keypairs_client)
830 client.delete_keypair(keypair_name=keypair['name'])
831
Federico Ressi82e83e32018-07-03 14:19:55 +0200832 @classmethod
833 def create_trunk(cls, port=None, subports=None, client=None, **kwargs):
834 """Create network trunk
835
836 :param port: dictionary containing parent port ID (port['id'])
837 :param client: client to be used for connecting to networking service
838 :param **kwargs: extra parameters to be forwarded to network service
839
840 :returns: dictionary containing created trunk details
841 """
842 client = client or cls.client
843
844 if port:
845 kwargs['port_id'] = port['id']
846
847 trunk = client.create_trunk(subports=subports, **kwargs)['trunk']
848 # Save client reference for later deletion
849 trunk['client'] = client
850 cls.trunks.append(trunk)
851 return trunk
852
853 @classmethod
Huifeng Le1c9f40b2018-11-07 01:14:21 +0800854 def delete_trunk(cls, trunk, client=None, detach_parent_port=True):
Federico Ressi82e83e32018-07-03 14:19:55 +0200855 """Delete network trunk
856
857 :param trunk: dictionary containing trunk ID (trunk['id'])
858
859 :param client: client to be used for connecting to networking service
860 """
861 client = client or trunk.get('client') or cls.client
862 trunk.update(client.show_trunk(trunk['id'])['trunk'])
863
864 if not trunk['admin_state_up']:
865 # Cannot touch trunk before admin_state_up is True
866 client.update_trunk(trunk['id'], admin_state_up=True)
867 if trunk['sub_ports']:
868 # Removes trunk ports before deleting it
869 cls._try_delete_resource(client.remove_subports, trunk['id'],
870 trunk['sub_ports'])
871
872 # we have to detach the interface from the server before
873 # the trunk can be deleted.
874 parent_port = {'id': trunk['port_id']}
875
876 def is_parent_port_detached():
877 parent_port.update(client.show_port(parent_port['id'])['port'])
878 return not parent_port['device_id']
879
Huifeng Le1c9f40b2018-11-07 01:14:21 +0800880 if detach_parent_port and not is_parent_port_detached():
Federico Ressi82e83e32018-07-03 14:19:55 +0200881 # this could probably happen when trunk is deleted and parent port
882 # has been assigned to a VM that is still running. Here we are
883 # assuming that device_id points to such VM.
884 cls.os_primary.compute.InterfacesClient().delete_interface(
885 parent_port['device_id'], parent_port['id'])
886 utils.wait_until_true(is_parent_port_detached)
887
888 client.delete_trunk(trunk['id'])
889
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000890
891class BaseAdminNetworkTest(BaseNetworkTest):
892
893 credentials = ['primary', 'admin']
894
895 @classmethod
896 def setup_clients(cls):
897 super(BaseAdminNetworkTest, cls).setup_clients()
fumihiko kakumaa216fc12017-07-14 10:43:29 +0900898 cls.admin_client = cls.os_admin.network_client
Jakub Libosvarf5758012017-08-15 13:45:30 +0000899 cls.identity_admin_client = cls.os_admin.projects_client
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000900
901 @classmethod
902 def create_metering_label(cls, name, description):
903 """Wrapper utility that returns a test metering label."""
904 body = cls.admin_client.create_metering_label(
905 description=description,
906 name=data_utils.rand_name("metering-label"))
907 metering_label = body['metering_label']
908 cls.metering_labels.append(metering_label)
909 return metering_label
910
911 @classmethod
912 def create_metering_label_rule(cls, remote_ip_prefix, direction,
913 metering_label_id):
914 """Wrapper utility that returns a test metering label rule."""
915 body = cls.admin_client.create_metering_label_rule(
916 remote_ip_prefix=remote_ip_prefix, direction=direction,
917 metering_label_id=metering_label_id)
918 metering_label_rule = body['metering_label_rule']
919 cls.metering_label_rules.append(metering_label_rule)
920 return metering_label_rule
921
922 @classmethod
Kailun Qineaaf9782018-12-20 04:45:01 +0800923 def create_network_segment_range(cls, name, shared,
924 project_id, network_type,
925 physical_network, minimum,
926 maximum):
927 """Wrapper utility that returns a test network segment range."""
928 network_segment_range_args = {'name': name,
929 'shared': shared,
930 'project_id': project_id,
931 'network_type': network_type,
932 'physical_network': physical_network,
933 'minimum': minimum,
934 'maximum': maximum}
935 body = cls.admin_client.create_network_segment_range(
936 **network_segment_range_args)
937 network_segment_range = body['network_segment_range']
938 cls.network_segment_ranges.append(network_segment_range)
939 return network_segment_range
940
941 @classmethod
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000942 def create_flavor(cls, name, description, service_type):
943 """Wrapper utility that returns a test flavor."""
944 body = cls.admin_client.create_flavor(
945 description=description, service_type=service_type,
946 name=name)
947 flavor = body['flavor']
948 cls.flavors.append(flavor)
949 return flavor
950
951 @classmethod
952 def create_service_profile(cls, description, metainfo, driver):
953 """Wrapper utility that returns a test service profile."""
954 body = cls.admin_client.create_service_profile(
955 driver=driver, metainfo=metainfo, description=description)
956 service_profile = body['service_profile']
957 cls.service_profiles.append(service_profile)
958 return service_profile
959
960 @classmethod
Nguyen Phuong An67993fc2017-11-24 11:30:25 +0700961 def create_log(cls, name, description=None,
962 resource_type='security_group', resource_id=None,
963 target_id=None, event='ALL', enabled=True):
964 """Wrapper utility that returns a test log object."""
965 log_args = {'name': name,
966 'description': description,
967 'resource_type': resource_type,
968 'resource_id': resource_id,
969 'target_id': target_id,
970 'event': event,
971 'enabled': enabled}
972 body = cls.admin_client.create_log(**log_args)
973 log_object = body['log']
974 cls.log_objects.append(log_object)
975 return log_object
976
977 @classmethod
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000978 def get_unused_ip(cls, net_id, ip_version=None):
Gary Kotton011345f2016-06-15 08:04:31 -0700979 """Get an unused ip address in a allocation pool of net"""
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000980 body = cls.admin_client.list_ports(network_id=net_id)
981 ports = body['ports']
982 used_ips = []
983 for port in ports:
984 used_ips.extend(
985 [fixed_ip['ip_address'] for fixed_ip in port['fixed_ips']])
986 body = cls.admin_client.list_subnets(network_id=net_id)
987 subnets = body['subnets']
988
989 for subnet in subnets:
990 if ip_version and subnet['ip_version'] != ip_version:
991 continue
992 cidr = subnet['cidr']
993 allocation_pools = subnet['allocation_pools']
994 iterators = []
995 if allocation_pools:
996 for allocation_pool in allocation_pools:
997 iterators.append(netaddr.iter_iprange(
998 allocation_pool['start'], allocation_pool['end']))
999 else:
1000 net = netaddr.IPNetwork(cidr)
1001
1002 def _iterip():
1003 for ip in net:
1004 if ip not in (net.network, net.broadcast):
1005 yield ip
1006 iterators.append(iter(_iterip()))
1007
1008 for iterator in iterators:
1009 for ip in iterator:
1010 if str(ip) not in used_ips:
1011 return str(ip)
1012
1013 message = (
1014 "net(%s) has no usable IP address in allocation pools" % net_id)
1015 raise exceptions.InvalidConfiguration(message)
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001016
Lajos Katona2f904652018-08-23 14:04:56 +02001017 @classmethod
1018 def create_provider_network(cls, physnet_name, start_segmentation_id,
1019 max_attempts=30):
1020 segmentation_id = start_segmentation_id
Lajos Katona7eb67252019-01-14 12:55:35 +01001021 for attempts in range(max_attempts):
Lajos Katona2f904652018-08-23 14:04:56 +02001022 try:
Lajos Katona7eb67252019-01-14 12:55:35 +01001023 return cls.create_network(
Lajos Katona2f904652018-08-23 14:04:56 +02001024 name=data_utils.rand_name('test_net'),
1025 shared=True,
1026 provider_network_type='vlan',
1027 provider_physical_network=physnet_name,
1028 provider_segmentation_id=segmentation_id)
Lajos Katona2f904652018-08-23 14:04:56 +02001029 except lib_exc.Conflict:
Lajos Katona2f904652018-08-23 14:04:56 +02001030 segmentation_id += 1
1031 if segmentation_id > 4095:
1032 raise lib_exc.TempestException(
1033 "No free segmentation id was found for provider "
1034 "network creation!")
1035 time.sleep(CONF.network.build_interval)
Lajos Katona7eb67252019-01-14 12:55:35 +01001036 LOG.exception("Failed to create provider network after "
1037 "%d attempts", max_attempts)
1038 raise lib_exc.TimeoutException
Lajos Katona2f904652018-08-23 14:04:56 +02001039
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001040
Sławek Kapłońskiff294062016-12-04 15:00:54 +00001041def require_qos_rule_type(rule_type):
1042 def decorator(f):
1043 @functools.wraps(f)
1044 def wrapper(self, *func_args, **func_kwargs):
1045 if rule_type not in self.get_supported_qos_rule_types():
1046 raise self.skipException(
1047 "%s rule type is required." % rule_type)
1048 return f(self, *func_args, **func_kwargs)
1049 return wrapper
1050 return decorator
1051
1052
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001053def _require_sorting(f):
1054 @functools.wraps(f)
1055 def inner(self, *args, **kwargs):
Chandan Kumarc125fd12017-11-15 19:41:01 +05301056 if not tutils.is_extension_enabled("sorting", "network"):
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001057 self.skipTest('Sorting feature is required')
1058 return f(self, *args, **kwargs)
1059 return inner
1060
1061
1062def _require_pagination(f):
1063 @functools.wraps(f)
1064 def inner(self, *args, **kwargs):
Chandan Kumarc125fd12017-11-15 19:41:01 +05301065 if not tutils.is_extension_enabled("pagination", "network"):
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001066 self.skipTest('Pagination feature is required')
1067 return f(self, *args, **kwargs)
1068 return inner
1069
1070
1071class BaseSearchCriteriaTest(BaseNetworkTest):
1072
1073 # This should be defined by subclasses to reflect resource name to test
1074 resource = None
1075
Armando Migliaccio57581c62016-07-01 10:13:19 -07001076 field = 'name'
1077
Ihar Hrachyshkaa8fe5a12016-05-24 14:50:58 +02001078 # NOTE(ihrachys): some names, like those starting with an underscore (_)
1079 # are sorted differently depending on whether the plugin implements native
1080 # sorting support, or not. So we avoid any such cases here, sticking to
1081 # alphanumeric. Also test a case when there are multiple resources with the
1082 # same name
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001083 resource_names = ('test1', 'abc1', 'test10', '123test') + ('test1',)
1084
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001085 force_tenant_isolation = True
1086
Ihar Hrachyshkaa8fe5a12016-05-24 14:50:58 +02001087 list_kwargs = {}
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001088
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001089 list_as_admin = False
1090
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001091 def assertSameOrder(self, original, actual):
1092 # gracefully handle iterators passed
1093 original = list(original)
1094 actual = list(actual)
1095 self.assertEqual(len(original), len(actual))
1096 for expected, res in zip(original, actual):
Armando Migliaccio57581c62016-07-01 10:13:19 -07001097 self.assertEqual(expected[self.field], res[self.field])
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001098
1099 @utils.classproperty
1100 def plural_name(self):
1101 return '%ss' % self.resource
1102
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001103 @property
1104 def list_client(self):
1105 return self.admin_client if self.list_as_admin else self.client
1106
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001107 def list_method(self, *args, **kwargs):
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001108 method = getattr(self.list_client, 'list_%s' % self.plural_name)
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001109 kwargs.update(self.list_kwargs)
1110 return method(*args, **kwargs)
1111
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001112 def get_bare_url(self, url):
1113 base_url = self.client.base_url
1114 self.assertTrue(url.startswith(base_url))
1115 return url[len(base_url):]
1116
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001117 @classmethod
1118 def _extract_resources(cls, body):
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001119 return body[cls.plural_name]
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001120
1121 def _test_list_sorts(self, direction):
1122 sort_args = {
1123 'sort_dir': direction,
Armando Migliaccio57581c62016-07-01 10:13:19 -07001124 'sort_key': self.field
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001125 }
1126 body = self.list_method(**sort_args)
1127 resources = self._extract_resources(body)
1128 self.assertNotEmpty(
1129 resources, "%s list returned is empty" % self.resource)
Armando Migliaccio57581c62016-07-01 10:13:19 -07001130 retrieved_names = [res[self.field] for res in resources]
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001131 expected = sorted(retrieved_names)
1132 if direction == constants.SORT_DIRECTION_DESC:
1133 expected = list(reversed(expected))
1134 self.assertEqual(expected, retrieved_names)
1135
1136 @_require_sorting
1137 def _test_list_sorts_asc(self):
1138 self._test_list_sorts(constants.SORT_DIRECTION_ASC)
1139
1140 @_require_sorting
1141 def _test_list_sorts_desc(self):
1142 self._test_list_sorts(constants.SORT_DIRECTION_DESC)
1143
1144 @_require_pagination
1145 def _test_list_pagination(self):
1146 for limit in range(1, len(self.resource_names) + 1):
1147 pagination_args = {
1148 'limit': limit,
1149 }
1150 body = self.list_method(**pagination_args)
1151 resources = self._extract_resources(body)
1152 self.assertEqual(limit, len(resources))
1153
1154 @_require_pagination
1155 def _test_list_no_pagination_limit_0(self):
1156 pagination_args = {
1157 'limit': 0,
1158 }
1159 body = self.list_method(**pagination_args)
1160 resources = self._extract_resources(body)
Béla Vancsicsf1806182016-08-23 07:36:18 +02001161 self.assertGreaterEqual(len(resources), len(self.resource_names))
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001162
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001163 def _test_list_pagination_iteratively(self, lister):
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001164 # first, collect all resources for later comparison
1165 sort_args = {
1166 'sort_dir': constants.SORT_DIRECTION_ASC,
Armando Migliaccio57581c62016-07-01 10:13:19 -07001167 'sort_key': self.field
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001168 }
1169 body = self.list_method(**sort_args)
1170 expected_resources = self._extract_resources(body)
1171 self.assertNotEmpty(expected_resources)
1172
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001173 resources = lister(
1174 len(expected_resources), sort_args
1175 )
1176
1177 # finally, compare that the list retrieved in one go is identical to
1178 # the one containing pagination results
1179 self.assertSameOrder(expected_resources, resources)
1180
1181 def _list_all_with_marker(self, niterations, sort_args):
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001182 # paginate resources one by one, using last fetched resource as a
1183 # marker
1184 resources = []
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001185 for i in range(niterations):
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001186 pagination_args = sort_args.copy()
1187 pagination_args['limit'] = 1
1188 if resources:
1189 pagination_args['marker'] = resources[-1]['id']
1190 body = self.list_method(**pagination_args)
1191 resources_ = self._extract_resources(body)
1192 self.assertEqual(1, len(resources_))
1193 resources.extend(resources_)
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001194 return resources
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001195
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001196 @_require_pagination
1197 @_require_sorting
1198 def _test_list_pagination_with_marker(self):
1199 self._test_list_pagination_iteratively(self._list_all_with_marker)
1200
1201 def _list_all_with_hrefs(self, niterations, sort_args):
1202 # paginate resources one by one, using next href links
1203 resources = []
1204 prev_links = {}
1205
1206 for i in range(niterations):
1207 if prev_links:
1208 uri = self.get_bare_url(prev_links['next'])
1209 else:
Ihar Hrachyshka7f79fe62016-06-07 21:23:44 +02001210 sort_args.update(self.list_kwargs)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001211 uri = self.list_client.build_uri(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001212 self.plural_name, limit=1, **sort_args)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001213 prev_links, body = self.list_client.get_uri_with_links(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001214 self.plural_name, uri
1215 )
1216 resources_ = self._extract_resources(body)
1217 self.assertEqual(1, len(resources_))
1218 resources.extend(resources_)
1219
1220 # The last element is empty and does not contain 'next' link
1221 uri = self.get_bare_url(prev_links['next'])
1222 prev_links, body = self.client.get_uri_with_links(
1223 self.plural_name, uri
1224 )
1225 self.assertNotIn('next', prev_links)
1226
1227 # Now walk backwards and compare results
1228 resources2 = []
1229 for i in range(niterations):
1230 uri = self.get_bare_url(prev_links['previous'])
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001231 prev_links, body = self.list_client.get_uri_with_links(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001232 self.plural_name, uri
1233 )
1234 resources_ = self._extract_resources(body)
1235 self.assertEqual(1, len(resources_))
1236 resources2.extend(resources_)
1237
1238 self.assertSameOrder(resources, reversed(resources2))
1239
1240 return resources
1241
1242 @_require_pagination
1243 @_require_sorting
1244 def _test_list_pagination_with_href_links(self):
1245 self._test_list_pagination_iteratively(self._list_all_with_hrefs)
1246
1247 @_require_pagination
1248 @_require_sorting
1249 def _test_list_pagination_page_reverse_with_href_links(
1250 self, direction=constants.SORT_DIRECTION_ASC):
1251 pagination_args = {
1252 'sort_dir': direction,
Armando Migliaccio57581c62016-07-01 10:13:19 -07001253 'sort_key': self.field,
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001254 }
1255 body = self.list_method(**pagination_args)
1256 expected_resources = self._extract_resources(body)
1257
1258 page_size = 2
1259 pagination_args['limit'] = page_size
1260
1261 prev_links = {}
1262 resources = []
1263 num_resources = len(expected_resources)
1264 niterations = int(math.ceil(float(num_resources) / page_size))
1265 for i in range(niterations):
1266 if prev_links:
1267 uri = self.get_bare_url(prev_links['previous'])
1268 else:
Ihar Hrachyshka7f79fe62016-06-07 21:23:44 +02001269 pagination_args.update(self.list_kwargs)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001270 uri = self.list_client.build_uri(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001271 self.plural_name, page_reverse=True, **pagination_args)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001272 prev_links, body = self.list_client.get_uri_with_links(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001273 self.plural_name, uri
1274 )
1275 resources_ = self._extract_resources(body)
Béla Vancsicsf1806182016-08-23 07:36:18 +02001276 self.assertGreaterEqual(page_size, len(resources_))
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001277 resources.extend(reversed(resources_))
1278
1279 self.assertSameOrder(expected_resources, reversed(resources))
1280
1281 @_require_pagination
1282 @_require_sorting
1283 def _test_list_pagination_page_reverse_asc(self):
1284 self._test_list_pagination_page_reverse(
1285 direction=constants.SORT_DIRECTION_ASC)
1286
1287 @_require_pagination
1288 @_require_sorting
1289 def _test_list_pagination_page_reverse_desc(self):
1290 self._test_list_pagination_page_reverse(
1291 direction=constants.SORT_DIRECTION_DESC)
1292
1293 def _test_list_pagination_page_reverse(self, direction):
1294 pagination_args = {
1295 'sort_dir': direction,
Armando Migliaccio57581c62016-07-01 10:13:19 -07001296 'sort_key': self.field,
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001297 'limit': 3,
1298 }
1299 body = self.list_method(**pagination_args)
1300 expected_resources = self._extract_resources(body)
1301
1302 pagination_args['limit'] -= 1
1303 pagination_args['marker'] = expected_resources[-1]['id']
1304 pagination_args['page_reverse'] = True
1305 body = self.list_method(**pagination_args)
1306
1307 self.assertSameOrder(
1308 # the last entry is not included in 2nd result when used as a
1309 # marker
1310 expected_resources[:-1],
1311 self._extract_resources(body))
Victor Morales1be97b42016-09-05 08:50:06 -05001312
Hongbin Lu54f55922018-07-12 19:05:39 +00001313 @tutils.requires_ext(extension="filter-validation", service="network")
1314 def _test_list_validation_filters(
1315 self, validation_args, filter_is_valid=True):
1316 if not filter_is_valid:
1317 self.assertRaises(lib_exc.BadRequest, self.list_method,
1318 **validation_args)
1319 else:
1320 body = self.list_method(**validation_args)
1321 resources = self._extract_resources(body)
1322 for resource in resources:
1323 self.assertIn(resource['name'], self.resource_names)