blob: 68b568009ff986ed51995bcd2dacc7caa52d805a [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
Jakub Libosvar83704832017-12-06 16:02:28 +0000418 def delete_router(cls, router, client=None):
419 client = client or cls.client
420 body = client.list_router_interfaces(router['id'])
Chandan Kumarc125fd12017-11-15 19:41:01 +0530421 interfaces = [port for port in body['ports']
422 if port['device_owner'] in const.ROUTER_INTERFACE_OWNERS]
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000423 for i in interfaces:
424 try:
Jakub Libosvar83704832017-12-06 16:02:28 +0000425 client.remove_router_interface_with_subnet_id(
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000426 router['id'], i['fixed_ips'][0]['subnet_id'])
427 except lib_exc.NotFound:
428 pass
Jakub Libosvar83704832017-12-06 16:02:28 +0000429 client.delete_router(router['id'])
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000430
431 @classmethod
432 def create_address_scope(cls, name, is_admin=False, **kwargs):
433 if is_admin:
434 body = cls.admin_client.create_address_scope(name=name, **kwargs)
435 cls.admin_address_scopes.append(body['address_scope'])
436 else:
437 body = cls.client.create_address_scope(name=name, **kwargs)
438 cls.address_scopes.append(body['address_scope'])
439 return body['address_scope']
440
441 @classmethod
442 def create_subnetpool(cls, name, is_admin=False, **kwargs):
443 if is_admin:
444 body = cls.admin_client.create_subnetpool(name, **kwargs)
445 cls.admin_subnetpools.append(body['subnetpool'])
446 else:
447 body = cls.client.create_subnetpool(name, **kwargs)
448 cls.subnetpools.append(body['subnetpool'])
449 return body['subnetpool']
450
Chandan Kumarc125fd12017-11-15 19:41:01 +0530451 @classmethod
452 def create_project(cls, name=None, description=None):
453 test_project = name or data_utils.rand_name('test_project_')
454 test_description = description or data_utils.rand_name('desc_')
455 project = cls.identity_admin_client.create_project(
456 name=test_project,
457 description=test_description)['project']
458 cls.projects.append(project)
459 return project
460
461 @classmethod
462 def create_security_group(cls, name, **kwargs):
463 body = cls.client.create_security_group(name=name, **kwargs)
464 cls.security_groups.append(body['security_group'])
465 return body['security_group']
466
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000467
468class BaseAdminNetworkTest(BaseNetworkTest):
469
470 credentials = ['primary', 'admin']
471
472 @classmethod
473 def setup_clients(cls):
474 super(BaseAdminNetworkTest, cls).setup_clients()
fumihiko kakumaa216fc12017-07-14 10:43:29 +0900475 cls.admin_client = cls.os_admin.network_client
Jakub Libosvarf5758012017-08-15 13:45:30 +0000476 cls.identity_admin_client = cls.os_admin.projects_client
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000477
478 @classmethod
479 def create_metering_label(cls, name, description):
480 """Wrapper utility that returns a test metering label."""
481 body = cls.admin_client.create_metering_label(
482 description=description,
483 name=data_utils.rand_name("metering-label"))
484 metering_label = body['metering_label']
485 cls.metering_labels.append(metering_label)
486 return metering_label
487
488 @classmethod
489 def create_metering_label_rule(cls, remote_ip_prefix, direction,
490 metering_label_id):
491 """Wrapper utility that returns a test metering label rule."""
492 body = cls.admin_client.create_metering_label_rule(
493 remote_ip_prefix=remote_ip_prefix, direction=direction,
494 metering_label_id=metering_label_id)
495 metering_label_rule = body['metering_label_rule']
496 cls.metering_label_rules.append(metering_label_rule)
497 return metering_label_rule
498
499 @classmethod
500 def create_flavor(cls, name, description, service_type):
501 """Wrapper utility that returns a test flavor."""
502 body = cls.admin_client.create_flavor(
503 description=description, service_type=service_type,
504 name=name)
505 flavor = body['flavor']
506 cls.flavors.append(flavor)
507 return flavor
508
509 @classmethod
510 def create_service_profile(cls, description, metainfo, driver):
511 """Wrapper utility that returns a test service profile."""
512 body = cls.admin_client.create_service_profile(
513 driver=driver, metainfo=metainfo, description=description)
514 service_profile = body['service_profile']
515 cls.service_profiles.append(service_profile)
516 return service_profile
517
518 @classmethod
519 def get_unused_ip(cls, net_id, ip_version=None):
Gary Kotton011345f2016-06-15 08:04:31 -0700520 """Get an unused ip address in a allocation pool of net"""
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000521 body = cls.admin_client.list_ports(network_id=net_id)
522 ports = body['ports']
523 used_ips = []
524 for port in ports:
525 used_ips.extend(
526 [fixed_ip['ip_address'] for fixed_ip in port['fixed_ips']])
527 body = cls.admin_client.list_subnets(network_id=net_id)
528 subnets = body['subnets']
529
530 for subnet in subnets:
531 if ip_version and subnet['ip_version'] != ip_version:
532 continue
533 cidr = subnet['cidr']
534 allocation_pools = subnet['allocation_pools']
535 iterators = []
536 if allocation_pools:
537 for allocation_pool in allocation_pools:
538 iterators.append(netaddr.iter_iprange(
539 allocation_pool['start'], allocation_pool['end']))
540 else:
541 net = netaddr.IPNetwork(cidr)
542
543 def _iterip():
544 for ip in net:
545 if ip not in (net.network, net.broadcast):
546 yield ip
547 iterators.append(iter(_iterip()))
548
549 for iterator in iterators:
550 for ip in iterator:
551 if str(ip) not in used_ips:
552 return str(ip)
553
554 message = (
555 "net(%s) has no usable IP address in allocation pools" % net_id)
556 raise exceptions.InvalidConfiguration(message)
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200557
558
Sławek Kapłońskiff294062016-12-04 15:00:54 +0000559def require_qos_rule_type(rule_type):
560 def decorator(f):
561 @functools.wraps(f)
562 def wrapper(self, *func_args, **func_kwargs):
563 if rule_type not in self.get_supported_qos_rule_types():
564 raise self.skipException(
565 "%s rule type is required." % rule_type)
566 return f(self, *func_args, **func_kwargs)
567 return wrapper
568 return decorator
569
570
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200571def _require_sorting(f):
572 @functools.wraps(f)
573 def inner(self, *args, **kwargs):
Chandan Kumarc125fd12017-11-15 19:41:01 +0530574 if not tutils.is_extension_enabled("sorting", "network"):
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200575 self.skipTest('Sorting feature is required')
576 return f(self, *args, **kwargs)
577 return inner
578
579
580def _require_pagination(f):
581 @functools.wraps(f)
582 def inner(self, *args, **kwargs):
Chandan Kumarc125fd12017-11-15 19:41:01 +0530583 if not tutils.is_extension_enabled("pagination", "network"):
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200584 self.skipTest('Pagination feature is required')
585 return f(self, *args, **kwargs)
586 return inner
587
588
589class BaseSearchCriteriaTest(BaseNetworkTest):
590
591 # This should be defined by subclasses to reflect resource name to test
592 resource = None
593
Armando Migliaccio57581c62016-07-01 10:13:19 -0700594 field = 'name'
595
Ihar Hrachyshkaa8fe5a12016-05-24 14:50:58 +0200596 # NOTE(ihrachys): some names, like those starting with an underscore (_)
597 # are sorted differently depending on whether the plugin implements native
598 # sorting support, or not. So we avoid any such cases here, sticking to
599 # alphanumeric. Also test a case when there are multiple resources with the
600 # same name
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200601 resource_names = ('test1', 'abc1', 'test10', '123test') + ('test1',)
602
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200603 force_tenant_isolation = True
604
Ihar Hrachyshkaa8fe5a12016-05-24 14:50:58 +0200605 list_kwargs = {}
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200606
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200607 list_as_admin = False
608
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200609 def assertSameOrder(self, original, actual):
610 # gracefully handle iterators passed
611 original = list(original)
612 actual = list(actual)
613 self.assertEqual(len(original), len(actual))
614 for expected, res in zip(original, actual):
Armando Migliaccio57581c62016-07-01 10:13:19 -0700615 self.assertEqual(expected[self.field], res[self.field])
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200616
617 @utils.classproperty
618 def plural_name(self):
619 return '%ss' % self.resource
620
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200621 @property
622 def list_client(self):
623 return self.admin_client if self.list_as_admin else self.client
624
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200625 def list_method(self, *args, **kwargs):
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200626 method = getattr(self.list_client, 'list_%s' % self.plural_name)
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200627 kwargs.update(self.list_kwargs)
628 return method(*args, **kwargs)
629
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200630 def get_bare_url(self, url):
631 base_url = self.client.base_url
632 self.assertTrue(url.startswith(base_url))
633 return url[len(base_url):]
634
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200635 @classmethod
636 def _extract_resources(cls, body):
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200637 return body[cls.plural_name]
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200638
639 def _test_list_sorts(self, direction):
640 sort_args = {
641 'sort_dir': direction,
Armando Migliaccio57581c62016-07-01 10:13:19 -0700642 'sort_key': self.field
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200643 }
644 body = self.list_method(**sort_args)
645 resources = self._extract_resources(body)
646 self.assertNotEmpty(
647 resources, "%s list returned is empty" % self.resource)
Armando Migliaccio57581c62016-07-01 10:13:19 -0700648 retrieved_names = [res[self.field] for res in resources]
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200649 expected = sorted(retrieved_names)
650 if direction == constants.SORT_DIRECTION_DESC:
651 expected = list(reversed(expected))
652 self.assertEqual(expected, retrieved_names)
653
654 @_require_sorting
655 def _test_list_sorts_asc(self):
656 self._test_list_sorts(constants.SORT_DIRECTION_ASC)
657
658 @_require_sorting
659 def _test_list_sorts_desc(self):
660 self._test_list_sorts(constants.SORT_DIRECTION_DESC)
661
662 @_require_pagination
663 def _test_list_pagination(self):
664 for limit in range(1, len(self.resource_names) + 1):
665 pagination_args = {
666 'limit': limit,
667 }
668 body = self.list_method(**pagination_args)
669 resources = self._extract_resources(body)
670 self.assertEqual(limit, len(resources))
671
672 @_require_pagination
673 def _test_list_no_pagination_limit_0(self):
674 pagination_args = {
675 'limit': 0,
676 }
677 body = self.list_method(**pagination_args)
678 resources = self._extract_resources(body)
Béla Vancsicsf1806182016-08-23 07:36:18 +0200679 self.assertGreaterEqual(len(resources), len(self.resource_names))
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200680
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200681 def _test_list_pagination_iteratively(self, lister):
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200682 # first, collect all resources for later comparison
683 sort_args = {
684 'sort_dir': constants.SORT_DIRECTION_ASC,
Armando Migliaccio57581c62016-07-01 10:13:19 -0700685 'sort_key': self.field
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200686 }
687 body = self.list_method(**sort_args)
688 expected_resources = self._extract_resources(body)
689 self.assertNotEmpty(expected_resources)
690
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200691 resources = lister(
692 len(expected_resources), sort_args
693 )
694
695 # finally, compare that the list retrieved in one go is identical to
696 # the one containing pagination results
697 self.assertSameOrder(expected_resources, resources)
698
699 def _list_all_with_marker(self, niterations, sort_args):
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200700 # paginate resources one by one, using last fetched resource as a
701 # marker
702 resources = []
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200703 for i in range(niterations):
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200704 pagination_args = sort_args.copy()
705 pagination_args['limit'] = 1
706 if resources:
707 pagination_args['marker'] = resources[-1]['id']
708 body = self.list_method(**pagination_args)
709 resources_ = self._extract_resources(body)
710 self.assertEqual(1, len(resources_))
711 resources.extend(resources_)
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200712 return resources
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200713
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200714 @_require_pagination
715 @_require_sorting
716 def _test_list_pagination_with_marker(self):
717 self._test_list_pagination_iteratively(self._list_all_with_marker)
718
719 def _list_all_with_hrefs(self, niterations, sort_args):
720 # paginate resources one by one, using next href links
721 resources = []
722 prev_links = {}
723
724 for i in range(niterations):
725 if prev_links:
726 uri = self.get_bare_url(prev_links['next'])
727 else:
Ihar Hrachyshka7f79fe62016-06-07 21:23:44 +0200728 sort_args.update(self.list_kwargs)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200729 uri = self.list_client.build_uri(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200730 self.plural_name, limit=1, **sort_args)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200731 prev_links, body = self.list_client.get_uri_with_links(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200732 self.plural_name, uri
733 )
734 resources_ = self._extract_resources(body)
735 self.assertEqual(1, len(resources_))
736 resources.extend(resources_)
737
738 # The last element is empty and does not contain 'next' link
739 uri = self.get_bare_url(prev_links['next'])
740 prev_links, body = self.client.get_uri_with_links(
741 self.plural_name, uri
742 )
743 self.assertNotIn('next', prev_links)
744
745 # Now walk backwards and compare results
746 resources2 = []
747 for i in range(niterations):
748 uri = self.get_bare_url(prev_links['previous'])
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200749 prev_links, body = self.list_client.get_uri_with_links(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200750 self.plural_name, uri
751 )
752 resources_ = self._extract_resources(body)
753 self.assertEqual(1, len(resources_))
754 resources2.extend(resources_)
755
756 self.assertSameOrder(resources, reversed(resources2))
757
758 return resources
759
760 @_require_pagination
761 @_require_sorting
762 def _test_list_pagination_with_href_links(self):
763 self._test_list_pagination_iteratively(self._list_all_with_hrefs)
764
765 @_require_pagination
766 @_require_sorting
767 def _test_list_pagination_page_reverse_with_href_links(
768 self, direction=constants.SORT_DIRECTION_ASC):
769 pagination_args = {
770 'sort_dir': direction,
Armando Migliaccio57581c62016-07-01 10:13:19 -0700771 'sort_key': self.field,
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200772 }
773 body = self.list_method(**pagination_args)
774 expected_resources = self._extract_resources(body)
775
776 page_size = 2
777 pagination_args['limit'] = page_size
778
779 prev_links = {}
780 resources = []
781 num_resources = len(expected_resources)
782 niterations = int(math.ceil(float(num_resources) / page_size))
783 for i in range(niterations):
784 if prev_links:
785 uri = self.get_bare_url(prev_links['previous'])
786 else:
Ihar Hrachyshka7f79fe62016-06-07 21:23:44 +0200787 pagination_args.update(self.list_kwargs)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200788 uri = self.list_client.build_uri(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200789 self.plural_name, page_reverse=True, **pagination_args)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200790 prev_links, body = self.list_client.get_uri_with_links(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200791 self.plural_name, uri
792 )
793 resources_ = self._extract_resources(body)
Béla Vancsicsf1806182016-08-23 07:36:18 +0200794 self.assertGreaterEqual(page_size, len(resources_))
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200795 resources.extend(reversed(resources_))
796
797 self.assertSameOrder(expected_resources, reversed(resources))
798
799 @_require_pagination
800 @_require_sorting
801 def _test_list_pagination_page_reverse_asc(self):
802 self._test_list_pagination_page_reverse(
803 direction=constants.SORT_DIRECTION_ASC)
804
805 @_require_pagination
806 @_require_sorting
807 def _test_list_pagination_page_reverse_desc(self):
808 self._test_list_pagination_page_reverse(
809 direction=constants.SORT_DIRECTION_DESC)
810
811 def _test_list_pagination_page_reverse(self, direction):
812 pagination_args = {
813 'sort_dir': direction,
Armando Migliaccio57581c62016-07-01 10:13:19 -0700814 'sort_key': self.field,
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200815 'limit': 3,
816 }
817 body = self.list_method(**pagination_args)
818 expected_resources = self._extract_resources(body)
819
820 pagination_args['limit'] -= 1
821 pagination_args['marker'] = expected_resources[-1]['id']
822 pagination_args['page_reverse'] = True
823 body = self.list_method(**pagination_args)
824
825 self.assertSameOrder(
826 # the last entry is not included in 2nd result when used as a
827 # marker
828 expected_resources[:-1],
829 self._extract_resources(body))
Victor Morales1be97b42016-09-05 08:50:06 -0500830
831 def _test_list_validation_filters(self):
832 validation_args = {
833 'unknown_filter': 'value',
834 }
835 body = self.list_method(**validation_args)
836 resources = self._extract_resources(body)
837 for resource in resources:
838 self.assertIn(resource['name'], self.resource_names)