blob: 8cd44ee4cf9bf01eedaabe1eba9e965951d02653 [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
Ihar Hrachyshka59382252016-04-05 15:54:33 +020018
Daniel Mellado3c0aeab2016-01-29 11:30:25 +000019import netaddr
Chandan Kumarc125fd12017-11-15 19:41:01 +053020from neutron_lib import constants as const
21from tempest.common import utils as tutils
Daniel Mellado3c0aeab2016-01-29 11:30:25 +000022from tempest.lib.common.utils import data_utils
23from tempest.lib import exceptions as lib_exc
24from tempest import test
25
Chandan Kumar667d3d32017-09-22 12:24:06 +053026from neutron_tempest_plugin.api import clients
27from neutron_tempest_plugin.common import constants
28from neutron_tempest_plugin.common import utils
29from neutron_tempest_plugin import config
30from neutron_tempest_plugin import exceptions
Daniel Mellado3c0aeab2016-01-29 11:30:25 +000031
32CONF = config.CONF
33
34
35class BaseNetworkTest(test.BaseTestCase):
36
37 """
38 Base class for the Neutron tests that use the Tempest Neutron REST client
39
40 Per the Neutron API Guide, API v1.x was removed from the source code tree
41 (docs.openstack.org/api/openstack-network/2.0/content/Overview-d1e71.html)
42 Therefore, v2.x of the Neutron API is assumed. It is also assumed that the
43 following options are defined in the [network] section of etc/tempest.conf:
44
45 project_network_cidr with a block of cidr's from which smaller blocks
46 can be allocated for tenant networks
47
48 project_network_mask_bits with the mask bits to be used to partition
49 the block defined by tenant-network_cidr
50
51 Finally, it is assumed that the following option is defined in the
52 [service_available] section of etc/tempest.conf
53
54 neutron as True
55 """
56
57 force_tenant_isolation = False
58 credentials = ['primary']
59
60 # Default to ipv4.
61 _ip_version = 4
62
63 @classmethod
64 def get_client_manager(cls, credential_type=None, roles=None,
65 force_new=None):
Genadi Chereshnyacc395c02016-07-25 12:17:37 +030066 manager = super(BaseNetworkTest, cls).get_client_manager(
67 credential_type=credential_type,
68 roles=roles,
69 force_new=force_new
70 )
Daniel Mellado3c0aeab2016-01-29 11:30:25 +000071 # Neutron uses a different clients manager than the one in the Tempest
Jens Harbott860b46a2017-11-15 21:23:15 +000072 # save the original in case mixed tests need it
73 if credential_type == 'primary':
74 cls.os_tempest = manager
Daniel Mellado3c0aeab2016-01-29 11:30:25 +000075 return clients.Manager(manager.credentials)
76
77 @classmethod
78 def skip_checks(cls):
79 super(BaseNetworkTest, cls).skip_checks()
80 if not CONF.service_available.neutron:
81 raise cls.skipException("Neutron support is required")
82 if cls._ip_version == 6 and not CONF.network_feature_enabled.ipv6:
83 raise cls.skipException("IPv6 Tests are disabled.")
Jakub Libosvar1982aa12017-05-30 11:15:33 +000084 for req_ext in getattr(cls, 'required_extensions', []):
Chandan Kumarc125fd12017-11-15 19:41:01 +053085 if not tutils.is_extension_enabled(req_ext, 'network'):
Jakub Libosvar1982aa12017-05-30 11:15:33 +000086 msg = "%s extension not enabled." % req_ext
87 raise cls.skipException(msg)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +000088
89 @classmethod
90 def setup_credentials(cls):
91 # Create no network resources for these test.
92 cls.set_network_resources()
93 super(BaseNetworkTest, cls).setup_credentials()
94
95 @classmethod
96 def setup_clients(cls):
97 super(BaseNetworkTest, cls).setup_clients()
fumihiko kakumaa216fc12017-07-14 10:43:29 +090098 cls.client = cls.os_primary.network_client
Daniel Mellado3c0aeab2016-01-29 11:30:25 +000099
100 @classmethod
101 def resource_setup(cls):
102 super(BaseNetworkTest, cls).resource_setup()
103
104 cls.networks = []
Miguel Lavalle124378b2016-09-21 16:41:47 -0500105 cls.admin_networks = []
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000106 cls.subnets = []
Kevin Bentonba3651c2017-09-01 17:13:01 -0700107 cls.admin_subnets = []
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000108 cls.ports = []
109 cls.routers = []
110 cls.floating_ips = []
111 cls.metering_labels = []
112 cls.service_profiles = []
113 cls.flavors = []
114 cls.metering_label_rules = []
115 cls.qos_rules = []
116 cls.qos_policies = []
117 cls.ethertype = "IPv" + str(cls._ip_version)
118 cls.address_scopes = []
119 cls.admin_address_scopes = []
120 cls.subnetpools = []
121 cls.admin_subnetpools = []
Itzik Brownbac51dc2016-10-31 12:25:04 +0000122 cls.security_groups = []
Chandan Kumarc125fd12017-11-15 19:41:01 +0530123 cls.projects = []
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000124
125 @classmethod
126 def resource_cleanup(cls):
127 if CONF.service_available.neutron:
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000128 # Clean up floating IPs
129 for floating_ip in cls.floating_ips:
130 cls._try_delete_resource(cls.client.delete_floatingip,
131 floating_ip['id'])
132 # Clean up routers
133 for router in cls.routers:
134 cls._try_delete_resource(cls.delete_router,
135 router)
136 # Clean up metering label rules
137 for metering_label_rule in cls.metering_label_rules:
138 cls._try_delete_resource(
139 cls.admin_client.delete_metering_label_rule,
140 metering_label_rule['id'])
141 # Clean up metering labels
142 for metering_label in cls.metering_labels:
143 cls._try_delete_resource(
144 cls.admin_client.delete_metering_label,
145 metering_label['id'])
146 # Clean up flavors
147 for flavor in cls.flavors:
148 cls._try_delete_resource(
149 cls.admin_client.delete_flavor,
150 flavor['id'])
151 # Clean up service profiles
152 for service_profile in cls.service_profiles:
153 cls._try_delete_resource(
154 cls.admin_client.delete_service_profile,
155 service_profile['id'])
156 # Clean up ports
157 for port in cls.ports:
158 cls._try_delete_resource(cls.client.delete_port,
159 port['id'])
160 # Clean up subnets
161 for subnet in cls.subnets:
162 cls._try_delete_resource(cls.client.delete_subnet,
163 subnet['id'])
Kevin Bentonba3651c2017-09-01 17:13:01 -0700164 # Clean up admin subnets
165 for subnet in cls.admin_subnets:
166 cls._try_delete_resource(cls.admin_client.delete_subnet,
167 subnet['id'])
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000168 # Clean up networks
169 for network in cls.networks:
170 cls._try_delete_resource(cls.client.delete_network,
171 network['id'])
172
Miguel Lavalle124378b2016-09-21 16:41:47 -0500173 # Clean up admin networks
174 for network in cls.admin_networks:
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000175 cls._try_delete_resource(cls.admin_client.delete_network,
176 network['id'])
177
Itzik Brownbac51dc2016-10-31 12:25:04 +0000178 # Clean up security groups
179 for secgroup in cls.security_groups:
180 cls._try_delete_resource(cls.client.delete_security_group,
181 secgroup['id'])
182
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000183 for subnetpool in cls.subnetpools:
184 cls._try_delete_resource(cls.client.delete_subnetpool,
185 subnetpool['id'])
186
187 for subnetpool in cls.admin_subnetpools:
188 cls._try_delete_resource(cls.admin_client.delete_subnetpool,
189 subnetpool['id'])
190
191 for address_scope in cls.address_scopes:
192 cls._try_delete_resource(cls.client.delete_address_scope,
193 address_scope['id'])
194
195 for address_scope in cls.admin_address_scopes:
196 cls._try_delete_resource(
197 cls.admin_client.delete_address_scope,
198 address_scope['id'])
199
Chandan Kumarc125fd12017-11-15 19:41:01 +0530200 for project in cls.projects:
201 cls._try_delete_resource(
202 cls.identity_admin_client.delete_project,
203 project['id'])
204
Sławek Kapłońskie100c4d2017-08-23 21:18:34 +0000205 # Clean up QoS rules
206 for qos_rule in cls.qos_rules:
207 cls._try_delete_resource(cls.admin_client.delete_qos_rule,
208 qos_rule['id'])
209 # Clean up QoS policies
210 # as all networks and ports are already removed, QoS policies
211 # shouldn't be "in use"
212 for qos_policy in cls.qos_policies:
213 cls._try_delete_resource(cls.admin_client.delete_qos_policy,
214 qos_policy['id'])
215
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000216 super(BaseNetworkTest, cls).resource_cleanup()
217
218 @classmethod
219 def _try_delete_resource(cls, delete_callable, *args, **kwargs):
220 """Cleanup resources in case of test-failure
221
222 Some resources are explicitly deleted by the test.
223 If the test failed to delete a resource, this method will execute
224 the appropriate delete methods. Otherwise, the method ignores NotFound
225 exceptions thrown for resources that were correctly deleted by the
226 test.
227
228 :param delete_callable: delete method
229 :param args: arguments for delete method
230 :param kwargs: keyword arguments for delete method
231 """
232 try:
233 delete_callable(*args, **kwargs)
234 # if resource is not found, this means it was deleted in the test
235 except lib_exc.NotFound:
236 pass
237
238 @classmethod
Sergey Belousa627ed92016-10-07 14:29:07 +0300239 def create_network(cls, network_name=None, client=None, **kwargs):
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000240 """Wrapper utility that returns a test network."""
241 network_name = network_name or data_utils.rand_name('test-network-')
242
Sergey Belousa627ed92016-10-07 14:29:07 +0300243 client = client or cls.client
244 body = client.create_network(name=network_name, **kwargs)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000245 network = body['network']
Sławek Kapłońskia694a5f2017-08-24 19:51:22 +0000246 if client is cls.client:
247 cls.networks.append(network)
248 else:
249 cls.admin_networks.append(network)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000250 return network
251
252 @classmethod
253 def create_shared_network(cls, network_name=None, **post_body):
254 network_name = network_name or data_utils.rand_name('sharednetwork-')
255 post_body.update({'name': network_name, 'shared': True})
256 body = cls.admin_client.create_network(**post_body)
257 network = body['network']
Miguel Lavalle124378b2016-09-21 16:41:47 -0500258 cls.admin_networks.append(network)
259 return network
260
261 @classmethod
262 def create_network_keystone_v3(cls, network_name=None, project_id=None,
263 tenant_id=None, client=None):
264 """Wrapper utility that creates a test network with project_id."""
265 client = client or cls.client
266 network_name = network_name or data_utils.rand_name(
267 'test-network-with-project_id')
268 project_id = cls.client.tenant_id
269 body = client.create_network_keystone_v3(network_name, project_id,
270 tenant_id)
271 network = body['network']
272 if client is cls.client:
273 cls.networks.append(network)
274 else:
275 cls.admin_networks.append(network)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000276 return network
277
278 @classmethod
279 def create_subnet(cls, network, gateway='', cidr=None, mask_bits=None,
280 ip_version=None, client=None, **kwargs):
281 """Wrapper utility that returns a test subnet."""
282
283 # allow tests to use admin client
284 if not client:
285 client = cls.client
286
287 # The cidr and mask_bits depend on the ip version.
288 ip_version = ip_version if ip_version is not None else cls._ip_version
289 gateway_not_set = gateway == ''
290 if ip_version == 4:
291 cidr = cidr or netaddr.IPNetwork(
292 config.safe_get_config_value(
293 'network', 'project_network_cidr'))
294 mask_bits = (
295 mask_bits or config.safe_get_config_value(
296 'network', 'project_network_mask_bits'))
297 elif ip_version == 6:
298 cidr = (
299 cidr or netaddr.IPNetwork(
300 config.safe_get_config_value(
301 'network', 'project_network_v6_cidr')))
302 mask_bits = (
303 mask_bits or config.safe_get_config_value(
304 'network', 'project_network_v6_mask_bits'))
305 # Find a cidr that is not in use yet and create a subnet with it
306 for subnet_cidr in cidr.subnet(mask_bits):
307 if gateway_not_set:
308 gateway_ip = str(netaddr.IPAddress(subnet_cidr) + 1)
309 else:
310 gateway_ip = gateway
311 try:
312 body = client.create_subnet(
313 network_id=network['id'],
314 cidr=str(subnet_cidr),
315 ip_version=ip_version,
316 gateway_ip=gateway_ip,
317 **kwargs)
318 break
319 except lib_exc.BadRequest as e:
320 is_overlapping_cidr = 'overlaps with another subnet' in str(e)
321 if not is_overlapping_cidr:
322 raise
323 else:
324 message = 'Available CIDR for subnet creation could not be found'
325 raise ValueError(message)
326 subnet = body['subnet']
Kevin Bentonba3651c2017-09-01 17:13:01 -0700327 if client is cls.client:
328 cls.subnets.append(subnet)
329 else:
330 cls.admin_subnets.append(subnet)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000331 return subnet
332
333 @classmethod
334 def create_port(cls, network, **kwargs):
335 """Wrapper utility that returns a test port."""
336 body = cls.client.create_port(network_id=network['id'],
337 **kwargs)
338 port = body['port']
339 cls.ports.append(port)
340 return port
341
342 @classmethod
343 def update_port(cls, port, **kwargs):
344 """Wrapper utility that updates a test port."""
345 body = cls.client.update_port(port['id'],
346 **kwargs)
347 return body['port']
348
349 @classmethod
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300350 def _create_router_with_client(
351 cls, client, router_name=None, admin_state_up=False,
352 external_network_id=None, enable_snat=None, **kwargs
353 ):
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000354 ext_gw_info = {}
355 if external_network_id:
356 ext_gw_info['network_id'] = external_network_id
YAMAMOTO Takashi9bd4f972017-06-20 12:49:30 +0900357 if enable_snat is not None:
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000358 ext_gw_info['enable_snat'] = enable_snat
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300359 body = client.create_router(
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000360 router_name, external_gateway_info=ext_gw_info,
361 admin_state_up=admin_state_up, **kwargs)
362 router = body['router']
363 cls.routers.append(router)
364 return router
365
366 @classmethod
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300367 def create_router(cls, *args, **kwargs):
368 return cls._create_router_with_client(cls.client, *args, **kwargs)
369
370 @classmethod
371 def create_admin_router(cls, *args, **kwargs):
rajat294495c042017-06-28 15:37:16 +0530372 return cls._create_router_with_client(cls.os_admin.network_client,
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300373 *args, **kwargs)
374
375 @classmethod
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000376 def create_floatingip(cls, external_network_id):
377 """Wrapper utility that returns a test floating IP."""
378 body = cls.client.create_floatingip(
379 floating_network_id=external_network_id)
380 fip = body['floatingip']
381 cls.floating_ips.append(fip)
382 return fip
383
384 @classmethod
385 def create_router_interface(cls, router_id, subnet_id):
386 """Wrapper utility that returns a router interface."""
387 interface = cls.client.add_router_interface_with_subnet_id(
388 router_id, subnet_id)
389 return interface
390
391 @classmethod
Sławek Kapłońskiff294062016-12-04 15:00:54 +0000392 def get_supported_qos_rule_types(cls):
393 body = cls.client.list_qos_rule_types()
394 return [rule_type['type'] for rule_type in body['rule_types']]
395
396 @classmethod
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200397 def create_qos_policy(cls, name, description=None, shared=False,
Hirofumi Ichihara39a6ee12017-08-23 13:55:12 +0900398 tenant_id=None, is_default=False):
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000399 """Wrapper utility that returns a test QoS policy."""
400 body = cls.admin_client.create_qos_policy(
Hirofumi Ichihara39a6ee12017-08-23 13:55:12 +0900401 name, description, shared, tenant_id, is_default)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000402 qos_policy = body['policy']
403 cls.qos_policies.append(qos_policy)
404 return qos_policy
405
406 @classmethod
Sławek Kapłoński153f3452017-03-24 22:04:53 +0000407 def create_qos_bandwidth_limit_rule(cls, policy_id, max_kbps,
408 max_burst_kbps,
Chandan Kumarc125fd12017-11-15 19:41:01 +0530409 direction=const.EGRESS_DIRECTION):
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000410 """Wrapper utility that returns a test QoS bandwidth limit rule."""
411 body = cls.admin_client.create_bandwidth_limit_rule(
Sławek Kapłoński153f3452017-03-24 22:04:53 +0000412 policy_id, max_kbps, max_burst_kbps, direction)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000413 qos_rule = body['bandwidth_limit_rule']
414 cls.qos_rules.append(qos_rule)
415 return qos_rule
416
417 @classmethod
418 def delete_router(cls, router):
419 body = cls.client.list_router_interfaces(router['id'])
Chandan Kumarc125fd12017-11-15 19:41:01 +0530420 interfaces = [port for port in body['ports']
421 if port['device_owner'] in const.ROUTER_INTERFACE_OWNERS]
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000422 for i in interfaces:
423 try:
424 cls.client.remove_router_interface_with_subnet_id(
425 router['id'], i['fixed_ips'][0]['subnet_id'])
426 except lib_exc.NotFound:
427 pass
428 cls.client.delete_router(router['id'])
429
430 @classmethod
431 def create_address_scope(cls, name, is_admin=False, **kwargs):
432 if is_admin:
433 body = cls.admin_client.create_address_scope(name=name, **kwargs)
434 cls.admin_address_scopes.append(body['address_scope'])
435 else:
436 body = cls.client.create_address_scope(name=name, **kwargs)
437 cls.address_scopes.append(body['address_scope'])
438 return body['address_scope']
439
440 @classmethod
441 def create_subnetpool(cls, name, is_admin=False, **kwargs):
442 if is_admin:
443 body = cls.admin_client.create_subnetpool(name, **kwargs)
444 cls.admin_subnetpools.append(body['subnetpool'])
445 else:
446 body = cls.client.create_subnetpool(name, **kwargs)
447 cls.subnetpools.append(body['subnetpool'])
448 return body['subnetpool']
449
Chandan Kumarc125fd12017-11-15 19:41:01 +0530450 @classmethod
451 def create_project(cls, name=None, description=None):
452 test_project = name or data_utils.rand_name('test_project_')
453 test_description = description or data_utils.rand_name('desc_')
454 project = cls.identity_admin_client.create_project(
455 name=test_project,
456 description=test_description)['project']
457 cls.projects.append(project)
458 return project
459
460 @classmethod
461 def create_security_group(cls, name, **kwargs):
462 body = cls.client.create_security_group(name=name, **kwargs)
463 cls.security_groups.append(body['security_group'])
464 return body['security_group']
465
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000466
467class BaseAdminNetworkTest(BaseNetworkTest):
468
469 credentials = ['primary', 'admin']
470
471 @classmethod
472 def setup_clients(cls):
473 super(BaseAdminNetworkTest, cls).setup_clients()
fumihiko kakumaa216fc12017-07-14 10:43:29 +0900474 cls.admin_client = cls.os_admin.network_client
Jakub Libosvarf5758012017-08-15 13:45:30 +0000475 cls.identity_admin_client = cls.os_admin.projects_client
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000476
477 @classmethod
478 def create_metering_label(cls, name, description):
479 """Wrapper utility that returns a test metering label."""
480 body = cls.admin_client.create_metering_label(
481 description=description,
482 name=data_utils.rand_name("metering-label"))
483 metering_label = body['metering_label']
484 cls.metering_labels.append(metering_label)
485 return metering_label
486
487 @classmethod
488 def create_metering_label_rule(cls, remote_ip_prefix, direction,
489 metering_label_id):
490 """Wrapper utility that returns a test metering label rule."""
491 body = cls.admin_client.create_metering_label_rule(
492 remote_ip_prefix=remote_ip_prefix, direction=direction,
493 metering_label_id=metering_label_id)
494 metering_label_rule = body['metering_label_rule']
495 cls.metering_label_rules.append(metering_label_rule)
496 return metering_label_rule
497
498 @classmethod
499 def create_flavor(cls, name, description, service_type):
500 """Wrapper utility that returns a test flavor."""
501 body = cls.admin_client.create_flavor(
502 description=description, service_type=service_type,
503 name=name)
504 flavor = body['flavor']
505 cls.flavors.append(flavor)
506 return flavor
507
508 @classmethod
509 def create_service_profile(cls, description, metainfo, driver):
510 """Wrapper utility that returns a test service profile."""
511 body = cls.admin_client.create_service_profile(
512 driver=driver, metainfo=metainfo, description=description)
513 service_profile = body['service_profile']
514 cls.service_profiles.append(service_profile)
515 return service_profile
516
517 @classmethod
518 def get_unused_ip(cls, net_id, ip_version=None):
Gary Kotton011345f2016-06-15 08:04:31 -0700519 """Get an unused ip address in a allocation pool of net"""
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000520 body = cls.admin_client.list_ports(network_id=net_id)
521 ports = body['ports']
522 used_ips = []
523 for port in ports:
524 used_ips.extend(
525 [fixed_ip['ip_address'] for fixed_ip in port['fixed_ips']])
526 body = cls.admin_client.list_subnets(network_id=net_id)
527 subnets = body['subnets']
528
529 for subnet in subnets:
530 if ip_version and subnet['ip_version'] != ip_version:
531 continue
532 cidr = subnet['cidr']
533 allocation_pools = subnet['allocation_pools']
534 iterators = []
535 if allocation_pools:
536 for allocation_pool in allocation_pools:
537 iterators.append(netaddr.iter_iprange(
538 allocation_pool['start'], allocation_pool['end']))
539 else:
540 net = netaddr.IPNetwork(cidr)
541
542 def _iterip():
543 for ip in net:
544 if ip not in (net.network, net.broadcast):
545 yield ip
546 iterators.append(iter(_iterip()))
547
548 for iterator in iterators:
549 for ip in iterator:
550 if str(ip) not in used_ips:
551 return str(ip)
552
553 message = (
554 "net(%s) has no usable IP address in allocation pools" % net_id)
555 raise exceptions.InvalidConfiguration(message)
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200556
557
Sławek Kapłońskiff294062016-12-04 15:00:54 +0000558def require_qos_rule_type(rule_type):
559 def decorator(f):
560 @functools.wraps(f)
561 def wrapper(self, *func_args, **func_kwargs):
562 if rule_type not in self.get_supported_qos_rule_types():
563 raise self.skipException(
564 "%s rule type is required." % rule_type)
565 return f(self, *func_args, **func_kwargs)
566 return wrapper
567 return decorator
568
569
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200570def _require_sorting(f):
571 @functools.wraps(f)
572 def inner(self, *args, **kwargs):
Chandan Kumarc125fd12017-11-15 19:41:01 +0530573 if not tutils.is_extension_enabled("sorting", "network"):
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200574 self.skipTest('Sorting feature is required')
575 return f(self, *args, **kwargs)
576 return inner
577
578
579def _require_pagination(f):
580 @functools.wraps(f)
581 def inner(self, *args, **kwargs):
Chandan Kumarc125fd12017-11-15 19:41:01 +0530582 if not tutils.is_extension_enabled("pagination", "network"):
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200583 self.skipTest('Pagination feature is required')
584 return f(self, *args, **kwargs)
585 return inner
586
587
588class BaseSearchCriteriaTest(BaseNetworkTest):
589
590 # This should be defined by subclasses to reflect resource name to test
591 resource = None
592
Armando Migliaccio57581c62016-07-01 10:13:19 -0700593 field = 'name'
594
Ihar Hrachyshkaa8fe5a12016-05-24 14:50:58 +0200595 # NOTE(ihrachys): some names, like those starting with an underscore (_)
596 # are sorted differently depending on whether the plugin implements native
597 # sorting support, or not. So we avoid any such cases here, sticking to
598 # alphanumeric. Also test a case when there are multiple resources with the
599 # same name
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200600 resource_names = ('test1', 'abc1', 'test10', '123test') + ('test1',)
601
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200602 force_tenant_isolation = True
603
Ihar Hrachyshkaa8fe5a12016-05-24 14:50:58 +0200604 list_kwargs = {}
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200605
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200606 list_as_admin = False
607
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200608 def assertSameOrder(self, original, actual):
609 # gracefully handle iterators passed
610 original = list(original)
611 actual = list(actual)
612 self.assertEqual(len(original), len(actual))
613 for expected, res in zip(original, actual):
Armando Migliaccio57581c62016-07-01 10:13:19 -0700614 self.assertEqual(expected[self.field], res[self.field])
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200615
616 @utils.classproperty
617 def plural_name(self):
618 return '%ss' % self.resource
619
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200620 @property
621 def list_client(self):
622 return self.admin_client if self.list_as_admin else self.client
623
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200624 def list_method(self, *args, **kwargs):
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200625 method = getattr(self.list_client, 'list_%s' % self.plural_name)
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200626 kwargs.update(self.list_kwargs)
627 return method(*args, **kwargs)
628
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200629 def get_bare_url(self, url):
630 base_url = self.client.base_url
631 self.assertTrue(url.startswith(base_url))
632 return url[len(base_url):]
633
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200634 @classmethod
635 def _extract_resources(cls, body):
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200636 return body[cls.plural_name]
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200637
638 def _test_list_sorts(self, direction):
639 sort_args = {
640 'sort_dir': direction,
Armando Migliaccio57581c62016-07-01 10:13:19 -0700641 'sort_key': self.field
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200642 }
643 body = self.list_method(**sort_args)
644 resources = self._extract_resources(body)
645 self.assertNotEmpty(
646 resources, "%s list returned is empty" % self.resource)
Armando Migliaccio57581c62016-07-01 10:13:19 -0700647 retrieved_names = [res[self.field] for res in resources]
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200648 expected = sorted(retrieved_names)
649 if direction == constants.SORT_DIRECTION_DESC:
650 expected = list(reversed(expected))
651 self.assertEqual(expected, retrieved_names)
652
653 @_require_sorting
654 def _test_list_sorts_asc(self):
655 self._test_list_sorts(constants.SORT_DIRECTION_ASC)
656
657 @_require_sorting
658 def _test_list_sorts_desc(self):
659 self._test_list_sorts(constants.SORT_DIRECTION_DESC)
660
661 @_require_pagination
662 def _test_list_pagination(self):
663 for limit in range(1, len(self.resource_names) + 1):
664 pagination_args = {
665 'limit': limit,
666 }
667 body = self.list_method(**pagination_args)
668 resources = self._extract_resources(body)
669 self.assertEqual(limit, len(resources))
670
671 @_require_pagination
672 def _test_list_no_pagination_limit_0(self):
673 pagination_args = {
674 'limit': 0,
675 }
676 body = self.list_method(**pagination_args)
677 resources = self._extract_resources(body)
Béla Vancsicsf1806182016-08-23 07:36:18 +0200678 self.assertGreaterEqual(len(resources), len(self.resource_names))
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200679
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200680 def _test_list_pagination_iteratively(self, lister):
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200681 # first, collect all resources for later comparison
682 sort_args = {
683 'sort_dir': constants.SORT_DIRECTION_ASC,
Armando Migliaccio57581c62016-07-01 10:13:19 -0700684 'sort_key': self.field
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200685 }
686 body = self.list_method(**sort_args)
687 expected_resources = self._extract_resources(body)
688 self.assertNotEmpty(expected_resources)
689
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200690 resources = lister(
691 len(expected_resources), sort_args
692 )
693
694 # finally, compare that the list retrieved in one go is identical to
695 # the one containing pagination results
696 self.assertSameOrder(expected_resources, resources)
697
698 def _list_all_with_marker(self, niterations, sort_args):
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200699 # paginate resources one by one, using last fetched resource as a
700 # marker
701 resources = []
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200702 for i in range(niterations):
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200703 pagination_args = sort_args.copy()
704 pagination_args['limit'] = 1
705 if resources:
706 pagination_args['marker'] = resources[-1]['id']
707 body = self.list_method(**pagination_args)
708 resources_ = self._extract_resources(body)
709 self.assertEqual(1, len(resources_))
710 resources.extend(resources_)
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200711 return resources
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200712
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200713 @_require_pagination
714 @_require_sorting
715 def _test_list_pagination_with_marker(self):
716 self._test_list_pagination_iteratively(self._list_all_with_marker)
717
718 def _list_all_with_hrefs(self, niterations, sort_args):
719 # paginate resources one by one, using next href links
720 resources = []
721 prev_links = {}
722
723 for i in range(niterations):
724 if prev_links:
725 uri = self.get_bare_url(prev_links['next'])
726 else:
Ihar Hrachyshka7f79fe62016-06-07 21:23:44 +0200727 sort_args.update(self.list_kwargs)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200728 uri = self.list_client.build_uri(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200729 self.plural_name, limit=1, **sort_args)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200730 prev_links, body = self.list_client.get_uri_with_links(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200731 self.plural_name, uri
732 )
733 resources_ = self._extract_resources(body)
734 self.assertEqual(1, len(resources_))
735 resources.extend(resources_)
736
737 # The last element is empty and does not contain 'next' link
738 uri = self.get_bare_url(prev_links['next'])
739 prev_links, body = self.client.get_uri_with_links(
740 self.plural_name, uri
741 )
742 self.assertNotIn('next', prev_links)
743
744 # Now walk backwards and compare results
745 resources2 = []
746 for i in range(niterations):
747 uri = self.get_bare_url(prev_links['previous'])
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200748 prev_links, body = self.list_client.get_uri_with_links(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200749 self.plural_name, uri
750 )
751 resources_ = self._extract_resources(body)
752 self.assertEqual(1, len(resources_))
753 resources2.extend(resources_)
754
755 self.assertSameOrder(resources, reversed(resources2))
756
757 return resources
758
759 @_require_pagination
760 @_require_sorting
761 def _test_list_pagination_with_href_links(self):
762 self._test_list_pagination_iteratively(self._list_all_with_hrefs)
763
764 @_require_pagination
765 @_require_sorting
766 def _test_list_pagination_page_reverse_with_href_links(
767 self, direction=constants.SORT_DIRECTION_ASC):
768 pagination_args = {
769 'sort_dir': direction,
Armando Migliaccio57581c62016-07-01 10:13:19 -0700770 'sort_key': self.field,
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200771 }
772 body = self.list_method(**pagination_args)
773 expected_resources = self._extract_resources(body)
774
775 page_size = 2
776 pagination_args['limit'] = page_size
777
778 prev_links = {}
779 resources = []
780 num_resources = len(expected_resources)
781 niterations = int(math.ceil(float(num_resources) / page_size))
782 for i in range(niterations):
783 if prev_links:
784 uri = self.get_bare_url(prev_links['previous'])
785 else:
Ihar Hrachyshka7f79fe62016-06-07 21:23:44 +0200786 pagination_args.update(self.list_kwargs)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200787 uri = self.list_client.build_uri(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200788 self.plural_name, page_reverse=True, **pagination_args)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200789 prev_links, body = self.list_client.get_uri_with_links(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200790 self.plural_name, uri
791 )
792 resources_ = self._extract_resources(body)
Béla Vancsicsf1806182016-08-23 07:36:18 +0200793 self.assertGreaterEqual(page_size, len(resources_))
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200794 resources.extend(reversed(resources_))
795
796 self.assertSameOrder(expected_resources, reversed(resources))
797
798 @_require_pagination
799 @_require_sorting
800 def _test_list_pagination_page_reverse_asc(self):
801 self._test_list_pagination_page_reverse(
802 direction=constants.SORT_DIRECTION_ASC)
803
804 @_require_pagination
805 @_require_sorting
806 def _test_list_pagination_page_reverse_desc(self):
807 self._test_list_pagination_page_reverse(
808 direction=constants.SORT_DIRECTION_DESC)
809
810 def _test_list_pagination_page_reverse(self, direction):
811 pagination_args = {
812 'sort_dir': direction,
Armando Migliaccio57581c62016-07-01 10:13:19 -0700813 'sort_key': self.field,
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200814 'limit': 3,
815 }
816 body = self.list_method(**pagination_args)
817 expected_resources = self._extract_resources(body)
818
819 pagination_args['limit'] -= 1
820 pagination_args['marker'] = expected_resources[-1]['id']
821 pagination_args['page_reverse'] = True
822 body = self.list_method(**pagination_args)
823
824 self.assertSameOrder(
825 # the last entry is not included in 2nd result when used as a
826 # marker
827 expected_resources[:-1],
828 self._extract_resources(body))
Victor Morales1be97b42016-09-05 08:50:06 -0500829
830 def _test_list_validation_filters(self):
831 validation_args = {
832 'unknown_filter': 'value',
833 }
834 body = self.list_method(**validation_args)
835 resources = self._extract_resources(body)
836 for resource in resources:
837 self.assertIn(resource['name'], self.resource_names)