blob: ec66818a90582be6b13a764045602590688d60fe [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
Jakub Libosvar83704832017-12-06 16:02:28 +0000415 def delete_router(cls, router, client=None):
416 client = client or cls.client
417 body = client.list_router_interfaces(router['id'])
Chandan Kumarc125fd12017-11-15 19:41:01 +0530418 interfaces = [port for port in body['ports']
419 if port['device_owner'] in const.ROUTER_INTERFACE_OWNERS]
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000420 for i in interfaces:
421 try:
Jakub Libosvar83704832017-12-06 16:02:28 +0000422 client.remove_router_interface_with_subnet_id(
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000423 router['id'], i['fixed_ips'][0]['subnet_id'])
424 except lib_exc.NotFound:
425 pass
Jakub Libosvar83704832017-12-06 16:02:28 +0000426 client.delete_router(router['id'])
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000427
428 @classmethod
429 def create_address_scope(cls, name, is_admin=False, **kwargs):
430 if is_admin:
431 body = cls.admin_client.create_address_scope(name=name, **kwargs)
432 cls.admin_address_scopes.append(body['address_scope'])
433 else:
434 body = cls.client.create_address_scope(name=name, **kwargs)
435 cls.address_scopes.append(body['address_scope'])
436 return body['address_scope']
437
438 @classmethod
439 def create_subnetpool(cls, name, is_admin=False, **kwargs):
440 if is_admin:
441 body = cls.admin_client.create_subnetpool(name, **kwargs)
442 cls.admin_subnetpools.append(body['subnetpool'])
443 else:
444 body = cls.client.create_subnetpool(name, **kwargs)
445 cls.subnetpools.append(body['subnetpool'])
446 return body['subnetpool']
447
Chandan Kumarc125fd12017-11-15 19:41:01 +0530448 @classmethod
449 def create_project(cls, name=None, description=None):
450 test_project = name or data_utils.rand_name('test_project_')
451 test_description = description or data_utils.rand_name('desc_')
452 project = cls.identity_admin_client.create_project(
453 name=test_project,
454 description=test_description)['project']
455 cls.projects.append(project)
456 return project
457
458 @classmethod
459 def create_security_group(cls, name, **kwargs):
460 body = cls.client.create_security_group(name=name, **kwargs)
461 cls.security_groups.append(body['security_group'])
462 return body['security_group']
463
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000464
465class BaseAdminNetworkTest(BaseNetworkTest):
466
467 credentials = ['primary', 'admin']
468
469 @classmethod
470 def setup_clients(cls):
471 super(BaseAdminNetworkTest, cls).setup_clients()
fumihiko kakumaa216fc12017-07-14 10:43:29 +0900472 cls.admin_client = cls.os_admin.network_client
Jakub Libosvarf5758012017-08-15 13:45:30 +0000473 cls.identity_admin_client = cls.os_admin.projects_client
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000474
475 @classmethod
476 def create_metering_label(cls, name, description):
477 """Wrapper utility that returns a test metering label."""
478 body = cls.admin_client.create_metering_label(
479 description=description,
480 name=data_utils.rand_name("metering-label"))
481 metering_label = body['metering_label']
482 cls.metering_labels.append(metering_label)
483 return metering_label
484
485 @classmethod
486 def create_metering_label_rule(cls, remote_ip_prefix, direction,
487 metering_label_id):
488 """Wrapper utility that returns a test metering label rule."""
489 body = cls.admin_client.create_metering_label_rule(
490 remote_ip_prefix=remote_ip_prefix, direction=direction,
491 metering_label_id=metering_label_id)
492 metering_label_rule = body['metering_label_rule']
493 cls.metering_label_rules.append(metering_label_rule)
494 return metering_label_rule
495
496 @classmethod
497 def create_flavor(cls, name, description, service_type):
498 """Wrapper utility that returns a test flavor."""
499 body = cls.admin_client.create_flavor(
500 description=description, service_type=service_type,
501 name=name)
502 flavor = body['flavor']
503 cls.flavors.append(flavor)
504 return flavor
505
506 @classmethod
507 def create_service_profile(cls, description, metainfo, driver):
508 """Wrapper utility that returns a test service profile."""
509 body = cls.admin_client.create_service_profile(
510 driver=driver, metainfo=metainfo, description=description)
511 service_profile = body['service_profile']
512 cls.service_profiles.append(service_profile)
513 return service_profile
514
515 @classmethod
516 def get_unused_ip(cls, net_id, ip_version=None):
Gary Kotton011345f2016-06-15 08:04:31 -0700517 """Get an unused ip address in a allocation pool of net"""
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000518 body = cls.admin_client.list_ports(network_id=net_id)
519 ports = body['ports']
520 used_ips = []
521 for port in ports:
522 used_ips.extend(
523 [fixed_ip['ip_address'] for fixed_ip in port['fixed_ips']])
524 body = cls.admin_client.list_subnets(network_id=net_id)
525 subnets = body['subnets']
526
527 for subnet in subnets:
528 if ip_version and subnet['ip_version'] != ip_version:
529 continue
530 cidr = subnet['cidr']
531 allocation_pools = subnet['allocation_pools']
532 iterators = []
533 if allocation_pools:
534 for allocation_pool in allocation_pools:
535 iterators.append(netaddr.iter_iprange(
536 allocation_pool['start'], allocation_pool['end']))
537 else:
538 net = netaddr.IPNetwork(cidr)
539
540 def _iterip():
541 for ip in net:
542 if ip not in (net.network, net.broadcast):
543 yield ip
544 iterators.append(iter(_iterip()))
545
546 for iterator in iterators:
547 for ip in iterator:
548 if str(ip) not in used_ips:
549 return str(ip)
550
551 message = (
552 "net(%s) has no usable IP address in allocation pools" % net_id)
553 raise exceptions.InvalidConfiguration(message)
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200554
555
Sławek Kapłońskiff294062016-12-04 15:00:54 +0000556def require_qos_rule_type(rule_type):
557 def decorator(f):
558 @functools.wraps(f)
559 def wrapper(self, *func_args, **func_kwargs):
560 if rule_type not in self.get_supported_qos_rule_types():
561 raise self.skipException(
562 "%s rule type is required." % rule_type)
563 return f(self, *func_args, **func_kwargs)
564 return wrapper
565 return decorator
566
567
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200568def _require_sorting(f):
569 @functools.wraps(f)
570 def inner(self, *args, **kwargs):
Chandan Kumarc125fd12017-11-15 19:41:01 +0530571 if not tutils.is_extension_enabled("sorting", "network"):
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200572 self.skipTest('Sorting feature is required')
573 return f(self, *args, **kwargs)
574 return inner
575
576
577def _require_pagination(f):
578 @functools.wraps(f)
579 def inner(self, *args, **kwargs):
Chandan Kumarc125fd12017-11-15 19:41:01 +0530580 if not tutils.is_extension_enabled("pagination", "network"):
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200581 self.skipTest('Pagination feature is required')
582 return f(self, *args, **kwargs)
583 return inner
584
585
586class BaseSearchCriteriaTest(BaseNetworkTest):
587
588 # This should be defined by subclasses to reflect resource name to test
589 resource = None
590
Armando Migliaccio57581c62016-07-01 10:13:19 -0700591 field = 'name'
592
Ihar Hrachyshkaa8fe5a12016-05-24 14:50:58 +0200593 # NOTE(ihrachys): some names, like those starting with an underscore (_)
594 # are sorted differently depending on whether the plugin implements native
595 # sorting support, or not. So we avoid any such cases here, sticking to
596 # alphanumeric. Also test a case when there are multiple resources with the
597 # same name
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200598 resource_names = ('test1', 'abc1', 'test10', '123test') + ('test1',)
599
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200600 force_tenant_isolation = True
601
Ihar Hrachyshkaa8fe5a12016-05-24 14:50:58 +0200602 list_kwargs = {}
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200603
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200604 list_as_admin = False
605
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200606 def assertSameOrder(self, original, actual):
607 # gracefully handle iterators passed
608 original = list(original)
609 actual = list(actual)
610 self.assertEqual(len(original), len(actual))
611 for expected, res in zip(original, actual):
Armando Migliaccio57581c62016-07-01 10:13:19 -0700612 self.assertEqual(expected[self.field], res[self.field])
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200613
614 @utils.classproperty
615 def plural_name(self):
616 return '%ss' % self.resource
617
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200618 @property
619 def list_client(self):
620 return self.admin_client if self.list_as_admin else self.client
621
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200622 def list_method(self, *args, **kwargs):
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200623 method = getattr(self.list_client, 'list_%s' % self.plural_name)
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200624 kwargs.update(self.list_kwargs)
625 return method(*args, **kwargs)
626
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200627 def get_bare_url(self, url):
628 base_url = self.client.base_url
629 self.assertTrue(url.startswith(base_url))
630 return url[len(base_url):]
631
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200632 @classmethod
633 def _extract_resources(cls, body):
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200634 return body[cls.plural_name]
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200635
636 def _test_list_sorts(self, direction):
637 sort_args = {
638 'sort_dir': direction,
Armando Migliaccio57581c62016-07-01 10:13:19 -0700639 'sort_key': self.field
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200640 }
641 body = self.list_method(**sort_args)
642 resources = self._extract_resources(body)
643 self.assertNotEmpty(
644 resources, "%s list returned is empty" % self.resource)
Armando Migliaccio57581c62016-07-01 10:13:19 -0700645 retrieved_names = [res[self.field] for res in resources]
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200646 expected = sorted(retrieved_names)
647 if direction == constants.SORT_DIRECTION_DESC:
648 expected = list(reversed(expected))
649 self.assertEqual(expected, retrieved_names)
650
651 @_require_sorting
652 def _test_list_sorts_asc(self):
653 self._test_list_sorts(constants.SORT_DIRECTION_ASC)
654
655 @_require_sorting
656 def _test_list_sorts_desc(self):
657 self._test_list_sorts(constants.SORT_DIRECTION_DESC)
658
659 @_require_pagination
660 def _test_list_pagination(self):
661 for limit in range(1, len(self.resource_names) + 1):
662 pagination_args = {
663 'limit': limit,
664 }
665 body = self.list_method(**pagination_args)
666 resources = self._extract_resources(body)
667 self.assertEqual(limit, len(resources))
668
669 @_require_pagination
670 def _test_list_no_pagination_limit_0(self):
671 pagination_args = {
672 'limit': 0,
673 }
674 body = self.list_method(**pagination_args)
675 resources = self._extract_resources(body)
Béla Vancsicsf1806182016-08-23 07:36:18 +0200676 self.assertGreaterEqual(len(resources), len(self.resource_names))
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200677
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200678 def _test_list_pagination_iteratively(self, lister):
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200679 # first, collect all resources for later comparison
680 sort_args = {
681 'sort_dir': constants.SORT_DIRECTION_ASC,
Armando Migliaccio57581c62016-07-01 10:13:19 -0700682 'sort_key': self.field
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200683 }
684 body = self.list_method(**sort_args)
685 expected_resources = self._extract_resources(body)
686 self.assertNotEmpty(expected_resources)
687
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200688 resources = lister(
689 len(expected_resources), sort_args
690 )
691
692 # finally, compare that the list retrieved in one go is identical to
693 # the one containing pagination results
694 self.assertSameOrder(expected_resources, resources)
695
696 def _list_all_with_marker(self, niterations, sort_args):
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200697 # paginate resources one by one, using last fetched resource as a
698 # marker
699 resources = []
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200700 for i in range(niterations):
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200701 pagination_args = sort_args.copy()
702 pagination_args['limit'] = 1
703 if resources:
704 pagination_args['marker'] = resources[-1]['id']
705 body = self.list_method(**pagination_args)
706 resources_ = self._extract_resources(body)
707 self.assertEqual(1, len(resources_))
708 resources.extend(resources_)
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200709 return resources
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200710
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200711 @_require_pagination
712 @_require_sorting
713 def _test_list_pagination_with_marker(self):
714 self._test_list_pagination_iteratively(self._list_all_with_marker)
715
716 def _list_all_with_hrefs(self, niterations, sort_args):
717 # paginate resources one by one, using next href links
718 resources = []
719 prev_links = {}
720
721 for i in range(niterations):
722 if prev_links:
723 uri = self.get_bare_url(prev_links['next'])
724 else:
Ihar Hrachyshka7f79fe62016-06-07 21:23:44 +0200725 sort_args.update(self.list_kwargs)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200726 uri = self.list_client.build_uri(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200727 self.plural_name, limit=1, **sort_args)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200728 prev_links, body = self.list_client.get_uri_with_links(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200729 self.plural_name, uri
730 )
731 resources_ = self._extract_resources(body)
732 self.assertEqual(1, len(resources_))
733 resources.extend(resources_)
734
735 # The last element is empty and does not contain 'next' link
736 uri = self.get_bare_url(prev_links['next'])
737 prev_links, body = self.client.get_uri_with_links(
738 self.plural_name, uri
739 )
740 self.assertNotIn('next', prev_links)
741
742 # Now walk backwards and compare results
743 resources2 = []
744 for i in range(niterations):
745 uri = self.get_bare_url(prev_links['previous'])
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200746 prev_links, body = self.list_client.get_uri_with_links(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200747 self.plural_name, uri
748 )
749 resources_ = self._extract_resources(body)
750 self.assertEqual(1, len(resources_))
751 resources2.extend(resources_)
752
753 self.assertSameOrder(resources, reversed(resources2))
754
755 return resources
756
757 @_require_pagination
758 @_require_sorting
759 def _test_list_pagination_with_href_links(self):
760 self._test_list_pagination_iteratively(self._list_all_with_hrefs)
761
762 @_require_pagination
763 @_require_sorting
764 def _test_list_pagination_page_reverse_with_href_links(
765 self, direction=constants.SORT_DIRECTION_ASC):
766 pagination_args = {
767 'sort_dir': direction,
Armando Migliaccio57581c62016-07-01 10:13:19 -0700768 'sort_key': self.field,
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200769 }
770 body = self.list_method(**pagination_args)
771 expected_resources = self._extract_resources(body)
772
773 page_size = 2
774 pagination_args['limit'] = page_size
775
776 prev_links = {}
777 resources = []
778 num_resources = len(expected_resources)
779 niterations = int(math.ceil(float(num_resources) / page_size))
780 for i in range(niterations):
781 if prev_links:
782 uri = self.get_bare_url(prev_links['previous'])
783 else:
Ihar Hrachyshka7f79fe62016-06-07 21:23:44 +0200784 pagination_args.update(self.list_kwargs)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200785 uri = self.list_client.build_uri(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200786 self.plural_name, page_reverse=True, **pagination_args)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200787 prev_links, body = self.list_client.get_uri_with_links(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200788 self.plural_name, uri
789 )
790 resources_ = self._extract_resources(body)
Béla Vancsicsf1806182016-08-23 07:36:18 +0200791 self.assertGreaterEqual(page_size, len(resources_))
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200792 resources.extend(reversed(resources_))
793
794 self.assertSameOrder(expected_resources, reversed(resources))
795
796 @_require_pagination
797 @_require_sorting
798 def _test_list_pagination_page_reverse_asc(self):
799 self._test_list_pagination_page_reverse(
800 direction=constants.SORT_DIRECTION_ASC)
801
802 @_require_pagination
803 @_require_sorting
804 def _test_list_pagination_page_reverse_desc(self):
805 self._test_list_pagination_page_reverse(
806 direction=constants.SORT_DIRECTION_DESC)
807
808 def _test_list_pagination_page_reverse(self, direction):
809 pagination_args = {
810 'sort_dir': direction,
Armando Migliaccio57581c62016-07-01 10:13:19 -0700811 'sort_key': self.field,
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200812 'limit': 3,
813 }
814 body = self.list_method(**pagination_args)
815 expected_resources = self._extract_resources(body)
816
817 pagination_args['limit'] -= 1
818 pagination_args['marker'] = expected_resources[-1]['id']
819 pagination_args['page_reverse'] = True
820 body = self.list_method(**pagination_args)
821
822 self.assertSameOrder(
823 # the last entry is not included in 2nd result when used as a
824 # marker
825 expected_resources[:-1],
826 self._extract_resources(body))
Victor Morales1be97b42016-09-05 08:50:06 -0500827
828 def _test_list_validation_filters(self):
829 validation_args = {
830 'unknown_filter': 'value',
831 }
832 body = self.list_method(**validation_args)
833 resources = self._extract_resources(body)
834 for resource in resources:
835 self.assertIn(resource['name'], self.resource_names)