blob: 8db5108c9cba29968a3e8d3ce6949ea82754bba4 [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
72 return clients.Manager(manager.credentials)
73
74 @classmethod
75 def skip_checks(cls):
76 super(BaseNetworkTest, cls).skip_checks()
77 if not CONF.service_available.neutron:
78 raise cls.skipException("Neutron support is required")
79 if cls._ip_version == 6 and not CONF.network_feature_enabled.ipv6:
80 raise cls.skipException("IPv6 Tests are disabled.")
Jakub Libosvar1982aa12017-05-30 11:15:33 +000081 for req_ext in getattr(cls, 'required_extensions', []):
Chandan Kumarc125fd12017-11-15 19:41:01 +053082 if not tutils.is_extension_enabled(req_ext, 'network'):
Jakub Libosvar1982aa12017-05-30 11:15:33 +000083 msg = "%s extension not enabled." % req_ext
84 raise cls.skipException(msg)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +000085
86 @classmethod
87 def setup_credentials(cls):
88 # Create no network resources for these test.
89 cls.set_network_resources()
90 super(BaseNetworkTest, cls).setup_credentials()
91
92 @classmethod
93 def setup_clients(cls):
94 super(BaseNetworkTest, cls).setup_clients()
fumihiko kakumaa216fc12017-07-14 10:43:29 +090095 cls.client = cls.os_primary.network_client
Daniel Mellado3c0aeab2016-01-29 11:30:25 +000096
97 @classmethod
98 def resource_setup(cls):
99 super(BaseNetworkTest, cls).resource_setup()
100
101 cls.networks = []
Miguel Lavalle124378b2016-09-21 16:41:47 -0500102 cls.admin_networks = []
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000103 cls.subnets = []
Kevin Bentonba3651c2017-09-01 17:13:01 -0700104 cls.admin_subnets = []
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000105 cls.ports = []
106 cls.routers = []
107 cls.floating_ips = []
108 cls.metering_labels = []
109 cls.service_profiles = []
110 cls.flavors = []
111 cls.metering_label_rules = []
112 cls.qos_rules = []
113 cls.qos_policies = []
114 cls.ethertype = "IPv" + str(cls._ip_version)
115 cls.address_scopes = []
116 cls.admin_address_scopes = []
117 cls.subnetpools = []
118 cls.admin_subnetpools = []
Itzik Brownbac51dc2016-10-31 12:25:04 +0000119 cls.security_groups = []
Chandan Kumarc125fd12017-11-15 19:41:01 +0530120 cls.projects = []
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000121
122 @classmethod
123 def resource_cleanup(cls):
124 if CONF.service_available.neutron:
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000125 # Clean up floating IPs
126 for floating_ip in cls.floating_ips:
127 cls._try_delete_resource(cls.client.delete_floatingip,
128 floating_ip['id'])
129 # Clean up routers
130 for router in cls.routers:
131 cls._try_delete_resource(cls.delete_router,
132 router)
133 # Clean up metering label rules
134 for metering_label_rule in cls.metering_label_rules:
135 cls._try_delete_resource(
136 cls.admin_client.delete_metering_label_rule,
137 metering_label_rule['id'])
138 # Clean up metering labels
139 for metering_label in cls.metering_labels:
140 cls._try_delete_resource(
141 cls.admin_client.delete_metering_label,
142 metering_label['id'])
143 # Clean up flavors
144 for flavor in cls.flavors:
145 cls._try_delete_resource(
146 cls.admin_client.delete_flavor,
147 flavor['id'])
148 # Clean up service profiles
149 for service_profile in cls.service_profiles:
150 cls._try_delete_resource(
151 cls.admin_client.delete_service_profile,
152 service_profile['id'])
153 # Clean up ports
154 for port in cls.ports:
155 cls._try_delete_resource(cls.client.delete_port,
156 port['id'])
157 # Clean up subnets
158 for subnet in cls.subnets:
159 cls._try_delete_resource(cls.client.delete_subnet,
160 subnet['id'])
Kevin Bentonba3651c2017-09-01 17:13:01 -0700161 # Clean up admin subnets
162 for subnet in cls.admin_subnets:
163 cls._try_delete_resource(cls.admin_client.delete_subnet,
164 subnet['id'])
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000165 # Clean up networks
166 for network in cls.networks:
167 cls._try_delete_resource(cls.client.delete_network,
168 network['id'])
169
Miguel Lavalle124378b2016-09-21 16:41:47 -0500170 # Clean up admin networks
171 for network in cls.admin_networks:
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000172 cls._try_delete_resource(cls.admin_client.delete_network,
173 network['id'])
174
Itzik Brownbac51dc2016-10-31 12:25:04 +0000175 # Clean up security groups
176 for secgroup in cls.security_groups:
177 cls._try_delete_resource(cls.client.delete_security_group,
178 secgroup['id'])
179
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000180 for subnetpool in cls.subnetpools:
181 cls._try_delete_resource(cls.client.delete_subnetpool,
182 subnetpool['id'])
183
184 for subnetpool in cls.admin_subnetpools:
185 cls._try_delete_resource(cls.admin_client.delete_subnetpool,
186 subnetpool['id'])
187
188 for address_scope in cls.address_scopes:
189 cls._try_delete_resource(cls.client.delete_address_scope,
190 address_scope['id'])
191
192 for address_scope in cls.admin_address_scopes:
193 cls._try_delete_resource(
194 cls.admin_client.delete_address_scope,
195 address_scope['id'])
196
Chandan Kumarc125fd12017-11-15 19:41:01 +0530197 for project in cls.projects:
198 cls._try_delete_resource(
199 cls.identity_admin_client.delete_project,
200 project['id'])
201
Sławek Kapłońskie100c4d2017-08-23 21:18:34 +0000202 # Clean up QoS rules
203 for qos_rule in cls.qos_rules:
204 cls._try_delete_resource(cls.admin_client.delete_qos_rule,
205 qos_rule['id'])
206 # Clean up QoS policies
207 # as all networks and ports are already removed, QoS policies
208 # shouldn't be "in use"
209 for qos_policy in cls.qos_policies:
210 cls._try_delete_resource(cls.admin_client.delete_qos_policy,
211 qos_policy['id'])
212
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000213 super(BaseNetworkTest, cls).resource_cleanup()
214
215 @classmethod
216 def _try_delete_resource(cls, delete_callable, *args, **kwargs):
217 """Cleanup resources in case of test-failure
218
219 Some resources are explicitly deleted by the test.
220 If the test failed to delete a resource, this method will execute
221 the appropriate delete methods. Otherwise, the method ignores NotFound
222 exceptions thrown for resources that were correctly deleted by the
223 test.
224
225 :param delete_callable: delete method
226 :param args: arguments for delete method
227 :param kwargs: keyword arguments for delete method
228 """
229 try:
230 delete_callable(*args, **kwargs)
231 # if resource is not found, this means it was deleted in the test
232 except lib_exc.NotFound:
233 pass
234
235 @classmethod
Sergey Belousa627ed92016-10-07 14:29:07 +0300236 def create_network(cls, network_name=None, client=None, **kwargs):
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000237 """Wrapper utility that returns a test network."""
238 network_name = network_name or data_utils.rand_name('test-network-')
239
Sergey Belousa627ed92016-10-07 14:29:07 +0300240 client = client or cls.client
241 body = client.create_network(name=network_name, **kwargs)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000242 network = body['network']
Sławek Kapłońskia694a5f2017-08-24 19:51:22 +0000243 if client is cls.client:
244 cls.networks.append(network)
245 else:
246 cls.admin_networks.append(network)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000247 return network
248
249 @classmethod
250 def create_shared_network(cls, network_name=None, **post_body):
251 network_name = network_name or data_utils.rand_name('sharednetwork-')
252 post_body.update({'name': network_name, 'shared': True})
253 body = cls.admin_client.create_network(**post_body)
254 network = body['network']
Miguel Lavalle124378b2016-09-21 16:41:47 -0500255 cls.admin_networks.append(network)
256 return network
257
258 @classmethod
259 def create_network_keystone_v3(cls, network_name=None, project_id=None,
260 tenant_id=None, client=None):
261 """Wrapper utility that creates a test network with project_id."""
262 client = client or cls.client
263 network_name = network_name or data_utils.rand_name(
264 'test-network-with-project_id')
265 project_id = cls.client.tenant_id
266 body = client.create_network_keystone_v3(network_name, project_id,
267 tenant_id)
268 network = body['network']
269 if client is cls.client:
270 cls.networks.append(network)
271 else:
272 cls.admin_networks.append(network)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000273 return network
274
275 @classmethod
276 def create_subnet(cls, network, gateway='', cidr=None, mask_bits=None,
277 ip_version=None, client=None, **kwargs):
278 """Wrapper utility that returns a test subnet."""
279
280 # allow tests to use admin client
281 if not client:
282 client = cls.client
283
284 # The cidr and mask_bits depend on the ip version.
285 ip_version = ip_version if ip_version is not None else cls._ip_version
286 gateway_not_set = gateway == ''
287 if ip_version == 4:
288 cidr = cidr or netaddr.IPNetwork(
289 config.safe_get_config_value(
290 'network', 'project_network_cidr'))
291 mask_bits = (
292 mask_bits or config.safe_get_config_value(
293 'network', 'project_network_mask_bits'))
294 elif ip_version == 6:
295 cidr = (
296 cidr or netaddr.IPNetwork(
297 config.safe_get_config_value(
298 'network', 'project_network_v6_cidr')))
299 mask_bits = (
300 mask_bits or config.safe_get_config_value(
301 'network', 'project_network_v6_mask_bits'))
302 # Find a cidr that is not in use yet and create a subnet with it
303 for subnet_cidr in cidr.subnet(mask_bits):
304 if gateway_not_set:
305 gateway_ip = str(netaddr.IPAddress(subnet_cidr) + 1)
306 else:
307 gateway_ip = gateway
308 try:
309 body = client.create_subnet(
310 network_id=network['id'],
311 cidr=str(subnet_cidr),
312 ip_version=ip_version,
313 gateway_ip=gateway_ip,
314 **kwargs)
315 break
316 except lib_exc.BadRequest as e:
317 is_overlapping_cidr = 'overlaps with another subnet' in str(e)
318 if not is_overlapping_cidr:
319 raise
320 else:
321 message = 'Available CIDR for subnet creation could not be found'
322 raise ValueError(message)
323 subnet = body['subnet']
Kevin Bentonba3651c2017-09-01 17:13:01 -0700324 if client is cls.client:
325 cls.subnets.append(subnet)
326 else:
327 cls.admin_subnets.append(subnet)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000328 return subnet
329
330 @classmethod
331 def create_port(cls, network, **kwargs):
332 """Wrapper utility that returns a test port."""
333 body = cls.client.create_port(network_id=network['id'],
334 **kwargs)
335 port = body['port']
336 cls.ports.append(port)
337 return port
338
339 @classmethod
340 def update_port(cls, port, **kwargs):
341 """Wrapper utility that updates a test port."""
342 body = cls.client.update_port(port['id'],
343 **kwargs)
344 return body['port']
345
346 @classmethod
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300347 def _create_router_with_client(
348 cls, client, router_name=None, admin_state_up=False,
349 external_network_id=None, enable_snat=None, **kwargs
350 ):
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000351 ext_gw_info = {}
352 if external_network_id:
353 ext_gw_info['network_id'] = external_network_id
YAMAMOTO Takashi9bd4f972017-06-20 12:49:30 +0900354 if enable_snat is not None:
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000355 ext_gw_info['enable_snat'] = enable_snat
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300356 body = client.create_router(
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000357 router_name, external_gateway_info=ext_gw_info,
358 admin_state_up=admin_state_up, **kwargs)
359 router = body['router']
360 cls.routers.append(router)
361 return router
362
363 @classmethod
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300364 def create_router(cls, *args, **kwargs):
365 return cls._create_router_with_client(cls.client, *args, **kwargs)
366
367 @classmethod
368 def create_admin_router(cls, *args, **kwargs):
rajat294495c042017-06-28 15:37:16 +0530369 return cls._create_router_with_client(cls.os_admin.network_client,
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300370 *args, **kwargs)
371
372 @classmethod
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000373 def create_floatingip(cls, external_network_id):
374 """Wrapper utility that returns a test floating IP."""
375 body = cls.client.create_floatingip(
376 floating_network_id=external_network_id)
377 fip = body['floatingip']
378 cls.floating_ips.append(fip)
379 return fip
380
381 @classmethod
382 def create_router_interface(cls, router_id, subnet_id):
383 """Wrapper utility that returns a router interface."""
384 interface = cls.client.add_router_interface_with_subnet_id(
385 router_id, subnet_id)
386 return interface
387
388 @classmethod
Sławek Kapłońskiff294062016-12-04 15:00:54 +0000389 def get_supported_qos_rule_types(cls):
390 body = cls.client.list_qos_rule_types()
391 return [rule_type['type'] for rule_type in body['rule_types']]
392
393 @classmethod
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200394 def create_qos_policy(cls, name, description=None, shared=False,
Hirofumi Ichihara39a6ee12017-08-23 13:55:12 +0900395 tenant_id=None, is_default=False):
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000396 """Wrapper utility that returns a test QoS policy."""
397 body = cls.admin_client.create_qos_policy(
Hirofumi Ichihara39a6ee12017-08-23 13:55:12 +0900398 name, description, shared, tenant_id, is_default)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000399 qos_policy = body['policy']
400 cls.qos_policies.append(qos_policy)
401 return qos_policy
402
403 @classmethod
Sławek Kapłoński153f3452017-03-24 22:04:53 +0000404 def create_qos_bandwidth_limit_rule(cls, policy_id, max_kbps,
405 max_burst_kbps,
Chandan Kumarc125fd12017-11-15 19:41:01 +0530406 direction=const.EGRESS_DIRECTION):
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000407 """Wrapper utility that returns a test QoS bandwidth limit rule."""
408 body = cls.admin_client.create_bandwidth_limit_rule(
Sławek Kapłoński153f3452017-03-24 22:04:53 +0000409 policy_id, max_kbps, max_burst_kbps, direction)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000410 qos_rule = body['bandwidth_limit_rule']
411 cls.qos_rules.append(qos_rule)
412 return qos_rule
413
414 @classmethod
415 def delete_router(cls, router):
416 body = cls.client.list_router_interfaces(router['id'])
Chandan Kumarc125fd12017-11-15 19:41:01 +0530417 interfaces = [port for port in body['ports']
418 if port['device_owner'] in const.ROUTER_INTERFACE_OWNERS]
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000419 for i in interfaces:
420 try:
421 cls.client.remove_router_interface_with_subnet_id(
422 router['id'], i['fixed_ips'][0]['subnet_id'])
423 except lib_exc.NotFound:
424 pass
425 cls.client.delete_router(router['id'])
426
427 @classmethod
428 def create_address_scope(cls, name, is_admin=False, **kwargs):
429 if is_admin:
430 body = cls.admin_client.create_address_scope(name=name, **kwargs)
431 cls.admin_address_scopes.append(body['address_scope'])
432 else:
433 body = cls.client.create_address_scope(name=name, **kwargs)
434 cls.address_scopes.append(body['address_scope'])
435 return body['address_scope']
436
437 @classmethod
438 def create_subnetpool(cls, name, is_admin=False, **kwargs):
439 if is_admin:
440 body = cls.admin_client.create_subnetpool(name, **kwargs)
441 cls.admin_subnetpools.append(body['subnetpool'])
442 else:
443 body = cls.client.create_subnetpool(name, **kwargs)
444 cls.subnetpools.append(body['subnetpool'])
445 return body['subnetpool']
446
Chandan Kumarc125fd12017-11-15 19:41:01 +0530447 @classmethod
448 def create_project(cls, name=None, description=None):
449 test_project = name or data_utils.rand_name('test_project_')
450 test_description = description or data_utils.rand_name('desc_')
451 project = cls.identity_admin_client.create_project(
452 name=test_project,
453 description=test_description)['project']
454 cls.projects.append(project)
455 return project
456
457 @classmethod
458 def create_security_group(cls, name, **kwargs):
459 body = cls.client.create_security_group(name=name, **kwargs)
460 cls.security_groups.append(body['security_group'])
461 return body['security_group']
462
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000463
464class BaseAdminNetworkTest(BaseNetworkTest):
465
466 credentials = ['primary', 'admin']
467
468 @classmethod
469 def setup_clients(cls):
470 super(BaseAdminNetworkTest, cls).setup_clients()
fumihiko kakumaa216fc12017-07-14 10:43:29 +0900471 cls.admin_client = cls.os_admin.network_client
Jakub Libosvarf5758012017-08-15 13:45:30 +0000472 cls.identity_admin_client = cls.os_admin.projects_client
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000473
474 @classmethod
475 def create_metering_label(cls, name, description):
476 """Wrapper utility that returns a test metering label."""
477 body = cls.admin_client.create_metering_label(
478 description=description,
479 name=data_utils.rand_name("metering-label"))
480 metering_label = body['metering_label']
481 cls.metering_labels.append(metering_label)
482 return metering_label
483
484 @classmethod
485 def create_metering_label_rule(cls, remote_ip_prefix, direction,
486 metering_label_id):
487 """Wrapper utility that returns a test metering label rule."""
488 body = cls.admin_client.create_metering_label_rule(
489 remote_ip_prefix=remote_ip_prefix, direction=direction,
490 metering_label_id=metering_label_id)
491 metering_label_rule = body['metering_label_rule']
492 cls.metering_label_rules.append(metering_label_rule)
493 return metering_label_rule
494
495 @classmethod
496 def create_flavor(cls, name, description, service_type):
497 """Wrapper utility that returns a test flavor."""
498 body = cls.admin_client.create_flavor(
499 description=description, service_type=service_type,
500 name=name)
501 flavor = body['flavor']
502 cls.flavors.append(flavor)
503 return flavor
504
505 @classmethod
506 def create_service_profile(cls, description, metainfo, driver):
507 """Wrapper utility that returns a test service profile."""
508 body = cls.admin_client.create_service_profile(
509 driver=driver, metainfo=metainfo, description=description)
510 service_profile = body['service_profile']
511 cls.service_profiles.append(service_profile)
512 return service_profile
513
514 @classmethod
515 def get_unused_ip(cls, net_id, ip_version=None):
Gary Kotton011345f2016-06-15 08:04:31 -0700516 """Get an unused ip address in a allocation pool of net"""
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000517 body = cls.admin_client.list_ports(network_id=net_id)
518 ports = body['ports']
519 used_ips = []
520 for port in ports:
521 used_ips.extend(
522 [fixed_ip['ip_address'] for fixed_ip in port['fixed_ips']])
523 body = cls.admin_client.list_subnets(network_id=net_id)
524 subnets = body['subnets']
525
526 for subnet in subnets:
527 if ip_version and subnet['ip_version'] != ip_version:
528 continue
529 cidr = subnet['cidr']
530 allocation_pools = subnet['allocation_pools']
531 iterators = []
532 if allocation_pools:
533 for allocation_pool in allocation_pools:
534 iterators.append(netaddr.iter_iprange(
535 allocation_pool['start'], allocation_pool['end']))
536 else:
537 net = netaddr.IPNetwork(cidr)
538
539 def _iterip():
540 for ip in net:
541 if ip not in (net.network, net.broadcast):
542 yield ip
543 iterators.append(iter(_iterip()))
544
545 for iterator in iterators:
546 for ip in iterator:
547 if str(ip) not in used_ips:
548 return str(ip)
549
550 message = (
551 "net(%s) has no usable IP address in allocation pools" % net_id)
552 raise exceptions.InvalidConfiguration(message)
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200553
554
Sławek Kapłońskiff294062016-12-04 15:00:54 +0000555def require_qos_rule_type(rule_type):
556 def decorator(f):
557 @functools.wraps(f)
558 def wrapper(self, *func_args, **func_kwargs):
559 if rule_type not in self.get_supported_qos_rule_types():
560 raise self.skipException(
561 "%s rule type is required." % rule_type)
562 return f(self, *func_args, **func_kwargs)
563 return wrapper
564 return decorator
565
566
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200567def _require_sorting(f):
568 @functools.wraps(f)
569 def inner(self, *args, **kwargs):
Chandan Kumarc125fd12017-11-15 19:41:01 +0530570 if not tutils.is_extension_enabled("sorting", "network"):
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200571 self.skipTest('Sorting feature is required')
572 return f(self, *args, **kwargs)
573 return inner
574
575
576def _require_pagination(f):
577 @functools.wraps(f)
578 def inner(self, *args, **kwargs):
Chandan Kumarc125fd12017-11-15 19:41:01 +0530579 if not tutils.is_extension_enabled("pagination", "network"):
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200580 self.skipTest('Pagination feature is required')
581 return f(self, *args, **kwargs)
582 return inner
583
584
585class BaseSearchCriteriaTest(BaseNetworkTest):
586
587 # This should be defined by subclasses to reflect resource name to test
588 resource = None
589
Armando Migliaccio57581c62016-07-01 10:13:19 -0700590 field = 'name'
591
Ihar Hrachyshkaa8fe5a12016-05-24 14:50:58 +0200592 # NOTE(ihrachys): some names, like those starting with an underscore (_)
593 # are sorted differently depending on whether the plugin implements native
594 # sorting support, or not. So we avoid any such cases here, sticking to
595 # alphanumeric. Also test a case when there are multiple resources with the
596 # same name
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200597 resource_names = ('test1', 'abc1', 'test10', '123test') + ('test1',)
598
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200599 force_tenant_isolation = True
600
Ihar Hrachyshkaa8fe5a12016-05-24 14:50:58 +0200601 list_kwargs = {}
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200602
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200603 list_as_admin = False
604
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200605 def assertSameOrder(self, original, actual):
606 # gracefully handle iterators passed
607 original = list(original)
608 actual = list(actual)
609 self.assertEqual(len(original), len(actual))
610 for expected, res in zip(original, actual):
Armando Migliaccio57581c62016-07-01 10:13:19 -0700611 self.assertEqual(expected[self.field], res[self.field])
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200612
613 @utils.classproperty
614 def plural_name(self):
615 return '%ss' % self.resource
616
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200617 @property
618 def list_client(self):
619 return self.admin_client if self.list_as_admin else self.client
620
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200621 def list_method(self, *args, **kwargs):
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200622 method = getattr(self.list_client, 'list_%s' % self.plural_name)
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200623 kwargs.update(self.list_kwargs)
624 return method(*args, **kwargs)
625
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200626 def get_bare_url(self, url):
627 base_url = self.client.base_url
628 self.assertTrue(url.startswith(base_url))
629 return url[len(base_url):]
630
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200631 @classmethod
632 def _extract_resources(cls, body):
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200633 return body[cls.plural_name]
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200634
635 def _test_list_sorts(self, direction):
636 sort_args = {
637 'sort_dir': direction,
Armando Migliaccio57581c62016-07-01 10:13:19 -0700638 'sort_key': self.field
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200639 }
640 body = self.list_method(**sort_args)
641 resources = self._extract_resources(body)
642 self.assertNotEmpty(
643 resources, "%s list returned is empty" % self.resource)
Armando Migliaccio57581c62016-07-01 10:13:19 -0700644 retrieved_names = [res[self.field] for res in resources]
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200645 expected = sorted(retrieved_names)
646 if direction == constants.SORT_DIRECTION_DESC:
647 expected = list(reversed(expected))
648 self.assertEqual(expected, retrieved_names)
649
650 @_require_sorting
651 def _test_list_sorts_asc(self):
652 self._test_list_sorts(constants.SORT_DIRECTION_ASC)
653
654 @_require_sorting
655 def _test_list_sorts_desc(self):
656 self._test_list_sorts(constants.SORT_DIRECTION_DESC)
657
658 @_require_pagination
659 def _test_list_pagination(self):
660 for limit in range(1, len(self.resource_names) + 1):
661 pagination_args = {
662 'limit': limit,
663 }
664 body = self.list_method(**pagination_args)
665 resources = self._extract_resources(body)
666 self.assertEqual(limit, len(resources))
667
668 @_require_pagination
669 def _test_list_no_pagination_limit_0(self):
670 pagination_args = {
671 'limit': 0,
672 }
673 body = self.list_method(**pagination_args)
674 resources = self._extract_resources(body)
Béla Vancsicsf1806182016-08-23 07:36:18 +0200675 self.assertGreaterEqual(len(resources), len(self.resource_names))
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200676
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200677 def _test_list_pagination_iteratively(self, lister):
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200678 # first, collect all resources for later comparison
679 sort_args = {
680 'sort_dir': constants.SORT_DIRECTION_ASC,
Armando Migliaccio57581c62016-07-01 10:13:19 -0700681 'sort_key': self.field
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200682 }
683 body = self.list_method(**sort_args)
684 expected_resources = self._extract_resources(body)
685 self.assertNotEmpty(expected_resources)
686
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200687 resources = lister(
688 len(expected_resources), sort_args
689 )
690
691 # finally, compare that the list retrieved in one go is identical to
692 # the one containing pagination results
693 self.assertSameOrder(expected_resources, resources)
694
695 def _list_all_with_marker(self, niterations, sort_args):
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200696 # paginate resources one by one, using last fetched resource as a
697 # marker
698 resources = []
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200699 for i in range(niterations):
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200700 pagination_args = sort_args.copy()
701 pagination_args['limit'] = 1
702 if resources:
703 pagination_args['marker'] = resources[-1]['id']
704 body = self.list_method(**pagination_args)
705 resources_ = self._extract_resources(body)
706 self.assertEqual(1, len(resources_))
707 resources.extend(resources_)
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200708 return resources
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200709
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200710 @_require_pagination
711 @_require_sorting
712 def _test_list_pagination_with_marker(self):
713 self._test_list_pagination_iteratively(self._list_all_with_marker)
714
715 def _list_all_with_hrefs(self, niterations, sort_args):
716 # paginate resources one by one, using next href links
717 resources = []
718 prev_links = {}
719
720 for i in range(niterations):
721 if prev_links:
722 uri = self.get_bare_url(prev_links['next'])
723 else:
Ihar Hrachyshka7f79fe62016-06-07 21:23:44 +0200724 sort_args.update(self.list_kwargs)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200725 uri = self.list_client.build_uri(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200726 self.plural_name, limit=1, **sort_args)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200727 prev_links, body = self.list_client.get_uri_with_links(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200728 self.plural_name, uri
729 )
730 resources_ = self._extract_resources(body)
731 self.assertEqual(1, len(resources_))
732 resources.extend(resources_)
733
734 # The last element is empty and does not contain 'next' link
735 uri = self.get_bare_url(prev_links['next'])
736 prev_links, body = self.client.get_uri_with_links(
737 self.plural_name, uri
738 )
739 self.assertNotIn('next', prev_links)
740
741 # Now walk backwards and compare results
742 resources2 = []
743 for i in range(niterations):
744 uri = self.get_bare_url(prev_links['previous'])
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200745 prev_links, body = self.list_client.get_uri_with_links(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200746 self.plural_name, uri
747 )
748 resources_ = self._extract_resources(body)
749 self.assertEqual(1, len(resources_))
750 resources2.extend(resources_)
751
752 self.assertSameOrder(resources, reversed(resources2))
753
754 return resources
755
756 @_require_pagination
757 @_require_sorting
758 def _test_list_pagination_with_href_links(self):
759 self._test_list_pagination_iteratively(self._list_all_with_hrefs)
760
761 @_require_pagination
762 @_require_sorting
763 def _test_list_pagination_page_reverse_with_href_links(
764 self, direction=constants.SORT_DIRECTION_ASC):
765 pagination_args = {
766 'sort_dir': direction,
Armando Migliaccio57581c62016-07-01 10:13:19 -0700767 'sort_key': self.field,
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200768 }
769 body = self.list_method(**pagination_args)
770 expected_resources = self._extract_resources(body)
771
772 page_size = 2
773 pagination_args['limit'] = page_size
774
775 prev_links = {}
776 resources = []
777 num_resources = len(expected_resources)
778 niterations = int(math.ceil(float(num_resources) / page_size))
779 for i in range(niterations):
780 if prev_links:
781 uri = self.get_bare_url(prev_links['previous'])
782 else:
Ihar Hrachyshka7f79fe62016-06-07 21:23:44 +0200783 pagination_args.update(self.list_kwargs)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200784 uri = self.list_client.build_uri(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200785 self.plural_name, page_reverse=True, **pagination_args)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200786 prev_links, body = self.list_client.get_uri_with_links(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200787 self.plural_name, uri
788 )
789 resources_ = self._extract_resources(body)
Béla Vancsicsf1806182016-08-23 07:36:18 +0200790 self.assertGreaterEqual(page_size, len(resources_))
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200791 resources.extend(reversed(resources_))
792
793 self.assertSameOrder(expected_resources, reversed(resources))
794
795 @_require_pagination
796 @_require_sorting
797 def _test_list_pagination_page_reverse_asc(self):
798 self._test_list_pagination_page_reverse(
799 direction=constants.SORT_DIRECTION_ASC)
800
801 @_require_pagination
802 @_require_sorting
803 def _test_list_pagination_page_reverse_desc(self):
804 self._test_list_pagination_page_reverse(
805 direction=constants.SORT_DIRECTION_DESC)
806
807 def _test_list_pagination_page_reverse(self, direction):
808 pagination_args = {
809 'sort_dir': direction,
Armando Migliaccio57581c62016-07-01 10:13:19 -0700810 'sort_key': self.field,
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200811 'limit': 3,
812 }
813 body = self.list_method(**pagination_args)
814 expected_resources = self._extract_resources(body)
815
816 pagination_args['limit'] -= 1
817 pagination_args['marker'] = expected_resources[-1]['id']
818 pagination_args['page_reverse'] = True
819 body = self.list_method(**pagination_args)
820
821 self.assertSameOrder(
822 # the last entry is not included in 2nd result when used as a
823 # marker
824 expected_resources[:-1],
825 self._extract_resources(body))
Victor Morales1be97b42016-09-05 08:50:06 -0500826
827 def _test_list_validation_filters(self):
828 validation_args = {
829 'unknown_filter': 'value',
830 }
831 body = self.list_method(**validation_args)
832 resources = self._extract_resources(body)
833 for resource in resources:
834 self.assertIn(resource['name'], self.resource_names)