blob: 7b333fe2cca60adec31a1877b568caaa30d3b3a5 [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 = []
Nguyen Phuong An67993fc2017-11-24 11:30:25 +0700124 cls.log_objects = []
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000125
126 @classmethod
127 def resource_cleanup(cls):
128 if CONF.service_available.neutron:
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000129 # Clean up floating IPs
130 for floating_ip in cls.floating_ips:
131 cls._try_delete_resource(cls.client.delete_floatingip,
132 floating_ip['id'])
133 # Clean up routers
134 for router in cls.routers:
135 cls._try_delete_resource(cls.delete_router,
136 router)
137 # Clean up metering label rules
138 for metering_label_rule in cls.metering_label_rules:
139 cls._try_delete_resource(
140 cls.admin_client.delete_metering_label_rule,
141 metering_label_rule['id'])
142 # Clean up metering labels
143 for metering_label in cls.metering_labels:
144 cls._try_delete_resource(
145 cls.admin_client.delete_metering_label,
146 metering_label['id'])
147 # Clean up flavors
148 for flavor in cls.flavors:
149 cls._try_delete_resource(
150 cls.admin_client.delete_flavor,
151 flavor['id'])
152 # Clean up service profiles
153 for service_profile in cls.service_profiles:
154 cls._try_delete_resource(
155 cls.admin_client.delete_service_profile,
156 service_profile['id'])
157 # Clean up ports
158 for port in cls.ports:
159 cls._try_delete_resource(cls.client.delete_port,
160 port['id'])
161 # Clean up subnets
162 for subnet in cls.subnets:
163 cls._try_delete_resource(cls.client.delete_subnet,
164 subnet['id'])
Kevin Bentonba3651c2017-09-01 17:13:01 -0700165 # Clean up admin subnets
166 for subnet in cls.admin_subnets:
167 cls._try_delete_resource(cls.admin_client.delete_subnet,
168 subnet['id'])
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000169 # Clean up networks
170 for network in cls.networks:
171 cls._try_delete_resource(cls.client.delete_network,
172 network['id'])
173
Miguel Lavalle124378b2016-09-21 16:41:47 -0500174 # Clean up admin networks
175 for network in cls.admin_networks:
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000176 cls._try_delete_resource(cls.admin_client.delete_network,
177 network['id'])
178
Itzik Brownbac51dc2016-10-31 12:25:04 +0000179 # Clean up security groups
180 for secgroup in cls.security_groups:
181 cls._try_delete_resource(cls.client.delete_security_group,
182 secgroup['id'])
183
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000184 for subnetpool in cls.subnetpools:
185 cls._try_delete_resource(cls.client.delete_subnetpool,
186 subnetpool['id'])
187
188 for subnetpool in cls.admin_subnetpools:
189 cls._try_delete_resource(cls.admin_client.delete_subnetpool,
190 subnetpool['id'])
191
192 for address_scope in cls.address_scopes:
193 cls._try_delete_resource(cls.client.delete_address_scope,
194 address_scope['id'])
195
196 for address_scope in cls.admin_address_scopes:
197 cls._try_delete_resource(
198 cls.admin_client.delete_address_scope,
199 address_scope['id'])
200
Chandan Kumarc125fd12017-11-15 19:41:01 +0530201 for project in cls.projects:
202 cls._try_delete_resource(
203 cls.identity_admin_client.delete_project,
204 project['id'])
205
Sławek Kapłońskie100c4d2017-08-23 21:18:34 +0000206 # Clean up QoS rules
207 for qos_rule in cls.qos_rules:
208 cls._try_delete_resource(cls.admin_client.delete_qos_rule,
209 qos_rule['id'])
210 # Clean up QoS policies
211 # as all networks and ports are already removed, QoS policies
212 # shouldn't be "in use"
213 for qos_policy in cls.qos_policies:
214 cls._try_delete_resource(cls.admin_client.delete_qos_policy,
215 qos_policy['id'])
216
Nguyen Phuong An67993fc2017-11-24 11:30:25 +0700217 # Clean up log_objects
218 for log_object in cls.log_objects:
219 cls._try_delete_resource(cls.admin_client.delete_log,
220 log_object['id'])
221
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000222 super(BaseNetworkTest, cls).resource_cleanup()
223
224 @classmethod
225 def _try_delete_resource(cls, delete_callable, *args, **kwargs):
226 """Cleanup resources in case of test-failure
227
228 Some resources are explicitly deleted by the test.
229 If the test failed to delete a resource, this method will execute
230 the appropriate delete methods. Otherwise, the method ignores NotFound
231 exceptions thrown for resources that were correctly deleted by the
232 test.
233
234 :param delete_callable: delete method
235 :param args: arguments for delete method
236 :param kwargs: keyword arguments for delete method
237 """
238 try:
239 delete_callable(*args, **kwargs)
240 # if resource is not found, this means it was deleted in the test
241 except lib_exc.NotFound:
242 pass
243
244 @classmethod
Sergey Belousa627ed92016-10-07 14:29:07 +0300245 def create_network(cls, network_name=None, client=None, **kwargs):
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000246 """Wrapper utility that returns a test network."""
247 network_name = network_name or data_utils.rand_name('test-network-')
248
Sergey Belousa627ed92016-10-07 14:29:07 +0300249 client = client or cls.client
250 body = client.create_network(name=network_name, **kwargs)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000251 network = body['network']
Sławek Kapłońskia694a5f2017-08-24 19:51:22 +0000252 if client is cls.client:
253 cls.networks.append(network)
254 else:
255 cls.admin_networks.append(network)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000256 return network
257
258 @classmethod
259 def create_shared_network(cls, network_name=None, **post_body):
260 network_name = network_name or data_utils.rand_name('sharednetwork-')
261 post_body.update({'name': network_name, 'shared': True})
262 body = cls.admin_client.create_network(**post_body)
263 network = body['network']
Miguel Lavalle124378b2016-09-21 16:41:47 -0500264 cls.admin_networks.append(network)
265 return network
266
267 @classmethod
268 def create_network_keystone_v3(cls, network_name=None, project_id=None,
269 tenant_id=None, client=None):
270 """Wrapper utility that creates a test network with project_id."""
271 client = client or cls.client
272 network_name = network_name or data_utils.rand_name(
273 'test-network-with-project_id')
274 project_id = cls.client.tenant_id
275 body = client.create_network_keystone_v3(network_name, project_id,
276 tenant_id)
277 network = body['network']
278 if client is cls.client:
279 cls.networks.append(network)
280 else:
281 cls.admin_networks.append(network)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000282 return network
283
284 @classmethod
285 def create_subnet(cls, network, gateway='', cidr=None, mask_bits=None,
286 ip_version=None, client=None, **kwargs):
287 """Wrapper utility that returns a test subnet."""
288
289 # allow tests to use admin client
290 if not client:
291 client = cls.client
292
293 # The cidr and mask_bits depend on the ip version.
294 ip_version = ip_version if ip_version is not None else cls._ip_version
295 gateway_not_set = gateway == ''
296 if ip_version == 4:
297 cidr = cidr or netaddr.IPNetwork(
298 config.safe_get_config_value(
299 'network', 'project_network_cidr'))
300 mask_bits = (
301 mask_bits or config.safe_get_config_value(
302 'network', 'project_network_mask_bits'))
303 elif ip_version == 6:
304 cidr = (
305 cidr or netaddr.IPNetwork(
306 config.safe_get_config_value(
307 'network', 'project_network_v6_cidr')))
308 mask_bits = (
309 mask_bits or config.safe_get_config_value(
310 'network', 'project_network_v6_mask_bits'))
311 # Find a cidr that is not in use yet and create a subnet with it
312 for subnet_cidr in cidr.subnet(mask_bits):
313 if gateway_not_set:
314 gateway_ip = str(netaddr.IPAddress(subnet_cidr) + 1)
315 else:
316 gateway_ip = gateway
317 try:
318 body = client.create_subnet(
319 network_id=network['id'],
320 cidr=str(subnet_cidr),
321 ip_version=ip_version,
322 gateway_ip=gateway_ip,
323 **kwargs)
324 break
325 except lib_exc.BadRequest as e:
326 is_overlapping_cidr = 'overlaps with another subnet' in str(e)
327 if not is_overlapping_cidr:
328 raise
329 else:
330 message = 'Available CIDR for subnet creation could not be found'
331 raise ValueError(message)
332 subnet = body['subnet']
Kevin Bentonba3651c2017-09-01 17:13:01 -0700333 if client is cls.client:
334 cls.subnets.append(subnet)
335 else:
336 cls.admin_subnets.append(subnet)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000337 return subnet
338
339 @classmethod
340 def create_port(cls, network, **kwargs):
341 """Wrapper utility that returns a test port."""
342 body = cls.client.create_port(network_id=network['id'],
343 **kwargs)
344 port = body['port']
345 cls.ports.append(port)
346 return port
347
348 @classmethod
349 def update_port(cls, port, **kwargs):
350 """Wrapper utility that updates a test port."""
351 body = cls.client.update_port(port['id'],
352 **kwargs)
353 return body['port']
354
355 @classmethod
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300356 def _create_router_with_client(
357 cls, client, router_name=None, admin_state_up=False,
358 external_network_id=None, enable_snat=None, **kwargs
359 ):
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000360 ext_gw_info = {}
361 if external_network_id:
362 ext_gw_info['network_id'] = external_network_id
YAMAMOTO Takashi9bd4f972017-06-20 12:49:30 +0900363 if enable_snat is not None:
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000364 ext_gw_info['enable_snat'] = enable_snat
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300365 body = client.create_router(
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000366 router_name, external_gateway_info=ext_gw_info,
367 admin_state_up=admin_state_up, **kwargs)
368 router = body['router']
369 cls.routers.append(router)
370 return router
371
372 @classmethod
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300373 def create_router(cls, *args, **kwargs):
374 return cls._create_router_with_client(cls.client, *args, **kwargs)
375
376 @classmethod
377 def create_admin_router(cls, *args, **kwargs):
rajat294495c042017-06-28 15:37:16 +0530378 return cls._create_router_with_client(cls.os_admin.network_client,
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300379 *args, **kwargs)
380
381 @classmethod
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000382 def create_floatingip(cls, external_network_id):
383 """Wrapper utility that returns a test floating IP."""
384 body = cls.client.create_floatingip(
385 floating_network_id=external_network_id)
386 fip = body['floatingip']
387 cls.floating_ips.append(fip)
388 return fip
389
390 @classmethod
391 def create_router_interface(cls, router_id, subnet_id):
392 """Wrapper utility that returns a router interface."""
393 interface = cls.client.add_router_interface_with_subnet_id(
394 router_id, subnet_id)
395 return interface
396
397 @classmethod
Sławek Kapłońskiff294062016-12-04 15:00:54 +0000398 def get_supported_qos_rule_types(cls):
399 body = cls.client.list_qos_rule_types()
400 return [rule_type['type'] for rule_type in body['rule_types']]
401
402 @classmethod
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200403 def create_qos_policy(cls, name, description=None, shared=False,
Hirofumi Ichihara39a6ee12017-08-23 13:55:12 +0900404 tenant_id=None, is_default=False):
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000405 """Wrapper utility that returns a test QoS policy."""
406 body = cls.admin_client.create_qos_policy(
Hirofumi Ichihara39a6ee12017-08-23 13:55:12 +0900407 name, description, shared, tenant_id, is_default)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000408 qos_policy = body['policy']
409 cls.qos_policies.append(qos_policy)
410 return qos_policy
411
412 @classmethod
Sławek Kapłoński153f3452017-03-24 22:04:53 +0000413 def create_qos_bandwidth_limit_rule(cls, policy_id, max_kbps,
414 max_burst_kbps,
Chandan Kumarc125fd12017-11-15 19:41:01 +0530415 direction=const.EGRESS_DIRECTION):
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000416 """Wrapper utility that returns a test QoS bandwidth limit rule."""
417 body = cls.admin_client.create_bandwidth_limit_rule(
Sławek Kapłoński153f3452017-03-24 22:04:53 +0000418 policy_id, max_kbps, max_burst_kbps, direction)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000419 qos_rule = body['bandwidth_limit_rule']
420 cls.qos_rules.append(qos_rule)
421 return qos_rule
422
423 @classmethod
Jakub Libosvar83704832017-12-06 16:02:28 +0000424 def delete_router(cls, router, client=None):
425 client = client or cls.client
426 body = client.list_router_interfaces(router['id'])
Chandan Kumarc125fd12017-11-15 19:41:01 +0530427 interfaces = [port for port in body['ports']
428 if port['device_owner'] in const.ROUTER_INTERFACE_OWNERS]
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000429 for i in interfaces:
430 try:
Jakub Libosvar83704832017-12-06 16:02:28 +0000431 client.remove_router_interface_with_subnet_id(
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000432 router['id'], i['fixed_ips'][0]['subnet_id'])
433 except lib_exc.NotFound:
434 pass
Jakub Libosvar83704832017-12-06 16:02:28 +0000435 client.delete_router(router['id'])
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000436
437 @classmethod
438 def create_address_scope(cls, name, is_admin=False, **kwargs):
439 if is_admin:
440 body = cls.admin_client.create_address_scope(name=name, **kwargs)
441 cls.admin_address_scopes.append(body['address_scope'])
442 else:
443 body = cls.client.create_address_scope(name=name, **kwargs)
444 cls.address_scopes.append(body['address_scope'])
445 return body['address_scope']
446
447 @classmethod
448 def create_subnetpool(cls, name, is_admin=False, **kwargs):
449 if is_admin:
450 body = cls.admin_client.create_subnetpool(name, **kwargs)
451 cls.admin_subnetpools.append(body['subnetpool'])
452 else:
453 body = cls.client.create_subnetpool(name, **kwargs)
454 cls.subnetpools.append(body['subnetpool'])
455 return body['subnetpool']
456
Chandan Kumarc125fd12017-11-15 19:41:01 +0530457 @classmethod
458 def create_project(cls, name=None, description=None):
459 test_project = name or data_utils.rand_name('test_project_')
460 test_description = description or data_utils.rand_name('desc_')
461 project = cls.identity_admin_client.create_project(
462 name=test_project,
463 description=test_description)['project']
464 cls.projects.append(project)
465 return project
466
467 @classmethod
468 def create_security_group(cls, name, **kwargs):
469 body = cls.client.create_security_group(name=name, **kwargs)
470 cls.security_groups.append(body['security_group'])
471 return body['security_group']
472
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000473
474class BaseAdminNetworkTest(BaseNetworkTest):
475
476 credentials = ['primary', 'admin']
477
478 @classmethod
479 def setup_clients(cls):
480 super(BaseAdminNetworkTest, cls).setup_clients()
fumihiko kakumaa216fc12017-07-14 10:43:29 +0900481 cls.admin_client = cls.os_admin.network_client
Jakub Libosvarf5758012017-08-15 13:45:30 +0000482 cls.identity_admin_client = cls.os_admin.projects_client
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000483
484 @classmethod
485 def create_metering_label(cls, name, description):
486 """Wrapper utility that returns a test metering label."""
487 body = cls.admin_client.create_metering_label(
488 description=description,
489 name=data_utils.rand_name("metering-label"))
490 metering_label = body['metering_label']
491 cls.metering_labels.append(metering_label)
492 return metering_label
493
494 @classmethod
495 def create_metering_label_rule(cls, remote_ip_prefix, direction,
496 metering_label_id):
497 """Wrapper utility that returns a test metering label rule."""
498 body = cls.admin_client.create_metering_label_rule(
499 remote_ip_prefix=remote_ip_prefix, direction=direction,
500 metering_label_id=metering_label_id)
501 metering_label_rule = body['metering_label_rule']
502 cls.metering_label_rules.append(metering_label_rule)
503 return metering_label_rule
504
505 @classmethod
506 def create_flavor(cls, name, description, service_type):
507 """Wrapper utility that returns a test flavor."""
508 body = cls.admin_client.create_flavor(
509 description=description, service_type=service_type,
510 name=name)
511 flavor = body['flavor']
512 cls.flavors.append(flavor)
513 return flavor
514
515 @classmethod
516 def create_service_profile(cls, description, metainfo, driver):
517 """Wrapper utility that returns a test service profile."""
518 body = cls.admin_client.create_service_profile(
519 driver=driver, metainfo=metainfo, description=description)
520 service_profile = body['service_profile']
521 cls.service_profiles.append(service_profile)
522 return service_profile
523
524 @classmethod
Nguyen Phuong An67993fc2017-11-24 11:30:25 +0700525 def create_log(cls, name, description=None,
526 resource_type='security_group', resource_id=None,
527 target_id=None, event='ALL', enabled=True):
528 """Wrapper utility that returns a test log object."""
529 log_args = {'name': name,
530 'description': description,
531 'resource_type': resource_type,
532 'resource_id': resource_id,
533 'target_id': target_id,
534 'event': event,
535 'enabled': enabled}
536 body = cls.admin_client.create_log(**log_args)
537 log_object = body['log']
538 cls.log_objects.append(log_object)
539 return log_object
540
541 @classmethod
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000542 def get_unused_ip(cls, net_id, ip_version=None):
Gary Kotton011345f2016-06-15 08:04:31 -0700543 """Get an unused ip address in a allocation pool of net"""
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000544 body = cls.admin_client.list_ports(network_id=net_id)
545 ports = body['ports']
546 used_ips = []
547 for port in ports:
548 used_ips.extend(
549 [fixed_ip['ip_address'] for fixed_ip in port['fixed_ips']])
550 body = cls.admin_client.list_subnets(network_id=net_id)
551 subnets = body['subnets']
552
553 for subnet in subnets:
554 if ip_version and subnet['ip_version'] != ip_version:
555 continue
556 cidr = subnet['cidr']
557 allocation_pools = subnet['allocation_pools']
558 iterators = []
559 if allocation_pools:
560 for allocation_pool in allocation_pools:
561 iterators.append(netaddr.iter_iprange(
562 allocation_pool['start'], allocation_pool['end']))
563 else:
564 net = netaddr.IPNetwork(cidr)
565
566 def _iterip():
567 for ip in net:
568 if ip not in (net.network, net.broadcast):
569 yield ip
570 iterators.append(iter(_iterip()))
571
572 for iterator in iterators:
573 for ip in iterator:
574 if str(ip) not in used_ips:
575 return str(ip)
576
577 message = (
578 "net(%s) has no usable IP address in allocation pools" % net_id)
579 raise exceptions.InvalidConfiguration(message)
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200580
581
Sławek Kapłońskiff294062016-12-04 15:00:54 +0000582def require_qos_rule_type(rule_type):
583 def decorator(f):
584 @functools.wraps(f)
585 def wrapper(self, *func_args, **func_kwargs):
586 if rule_type not in self.get_supported_qos_rule_types():
587 raise self.skipException(
588 "%s rule type is required." % rule_type)
589 return f(self, *func_args, **func_kwargs)
590 return wrapper
591 return decorator
592
593
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200594def _require_sorting(f):
595 @functools.wraps(f)
596 def inner(self, *args, **kwargs):
Chandan Kumarc125fd12017-11-15 19:41:01 +0530597 if not tutils.is_extension_enabled("sorting", "network"):
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200598 self.skipTest('Sorting feature is required')
599 return f(self, *args, **kwargs)
600 return inner
601
602
603def _require_pagination(f):
604 @functools.wraps(f)
605 def inner(self, *args, **kwargs):
Chandan Kumarc125fd12017-11-15 19:41:01 +0530606 if not tutils.is_extension_enabled("pagination", "network"):
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200607 self.skipTest('Pagination feature is required')
608 return f(self, *args, **kwargs)
609 return inner
610
611
612class BaseSearchCriteriaTest(BaseNetworkTest):
613
614 # This should be defined by subclasses to reflect resource name to test
615 resource = None
616
Armando Migliaccio57581c62016-07-01 10:13:19 -0700617 field = 'name'
618
Ihar Hrachyshkaa8fe5a12016-05-24 14:50:58 +0200619 # NOTE(ihrachys): some names, like those starting with an underscore (_)
620 # are sorted differently depending on whether the plugin implements native
621 # sorting support, or not. So we avoid any such cases here, sticking to
622 # alphanumeric. Also test a case when there are multiple resources with the
623 # same name
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200624 resource_names = ('test1', 'abc1', 'test10', '123test') + ('test1',)
625
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200626 force_tenant_isolation = True
627
Ihar Hrachyshkaa8fe5a12016-05-24 14:50:58 +0200628 list_kwargs = {}
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200629
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200630 list_as_admin = False
631
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200632 def assertSameOrder(self, original, actual):
633 # gracefully handle iterators passed
634 original = list(original)
635 actual = list(actual)
636 self.assertEqual(len(original), len(actual))
637 for expected, res in zip(original, actual):
Armando Migliaccio57581c62016-07-01 10:13:19 -0700638 self.assertEqual(expected[self.field], res[self.field])
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200639
640 @utils.classproperty
641 def plural_name(self):
642 return '%ss' % self.resource
643
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200644 @property
645 def list_client(self):
646 return self.admin_client if self.list_as_admin else self.client
647
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200648 def list_method(self, *args, **kwargs):
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200649 method = getattr(self.list_client, 'list_%s' % self.plural_name)
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200650 kwargs.update(self.list_kwargs)
651 return method(*args, **kwargs)
652
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200653 def get_bare_url(self, url):
654 base_url = self.client.base_url
655 self.assertTrue(url.startswith(base_url))
656 return url[len(base_url):]
657
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200658 @classmethod
659 def _extract_resources(cls, body):
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200660 return body[cls.plural_name]
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200661
662 def _test_list_sorts(self, direction):
663 sort_args = {
664 'sort_dir': direction,
Armando Migliaccio57581c62016-07-01 10:13:19 -0700665 'sort_key': self.field
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200666 }
667 body = self.list_method(**sort_args)
668 resources = self._extract_resources(body)
669 self.assertNotEmpty(
670 resources, "%s list returned is empty" % self.resource)
Armando Migliaccio57581c62016-07-01 10:13:19 -0700671 retrieved_names = [res[self.field] for res in resources]
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200672 expected = sorted(retrieved_names)
673 if direction == constants.SORT_DIRECTION_DESC:
674 expected = list(reversed(expected))
675 self.assertEqual(expected, retrieved_names)
676
677 @_require_sorting
678 def _test_list_sorts_asc(self):
679 self._test_list_sorts(constants.SORT_DIRECTION_ASC)
680
681 @_require_sorting
682 def _test_list_sorts_desc(self):
683 self._test_list_sorts(constants.SORT_DIRECTION_DESC)
684
685 @_require_pagination
686 def _test_list_pagination(self):
687 for limit in range(1, len(self.resource_names) + 1):
688 pagination_args = {
689 'limit': limit,
690 }
691 body = self.list_method(**pagination_args)
692 resources = self._extract_resources(body)
693 self.assertEqual(limit, len(resources))
694
695 @_require_pagination
696 def _test_list_no_pagination_limit_0(self):
697 pagination_args = {
698 'limit': 0,
699 }
700 body = self.list_method(**pagination_args)
701 resources = self._extract_resources(body)
Béla Vancsicsf1806182016-08-23 07:36:18 +0200702 self.assertGreaterEqual(len(resources), len(self.resource_names))
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200703
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200704 def _test_list_pagination_iteratively(self, lister):
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200705 # first, collect all resources for later comparison
706 sort_args = {
707 'sort_dir': constants.SORT_DIRECTION_ASC,
Armando Migliaccio57581c62016-07-01 10:13:19 -0700708 'sort_key': self.field
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200709 }
710 body = self.list_method(**sort_args)
711 expected_resources = self._extract_resources(body)
712 self.assertNotEmpty(expected_resources)
713
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200714 resources = lister(
715 len(expected_resources), sort_args
716 )
717
718 # finally, compare that the list retrieved in one go is identical to
719 # the one containing pagination results
720 self.assertSameOrder(expected_resources, resources)
721
722 def _list_all_with_marker(self, niterations, sort_args):
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200723 # paginate resources one by one, using last fetched resource as a
724 # marker
725 resources = []
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200726 for i in range(niterations):
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200727 pagination_args = sort_args.copy()
728 pagination_args['limit'] = 1
729 if resources:
730 pagination_args['marker'] = resources[-1]['id']
731 body = self.list_method(**pagination_args)
732 resources_ = self._extract_resources(body)
733 self.assertEqual(1, len(resources_))
734 resources.extend(resources_)
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200735 return resources
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200736
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200737 @_require_pagination
738 @_require_sorting
739 def _test_list_pagination_with_marker(self):
740 self._test_list_pagination_iteratively(self._list_all_with_marker)
741
742 def _list_all_with_hrefs(self, niterations, sort_args):
743 # paginate resources one by one, using next href links
744 resources = []
745 prev_links = {}
746
747 for i in range(niterations):
748 if prev_links:
749 uri = self.get_bare_url(prev_links['next'])
750 else:
Ihar Hrachyshka7f79fe62016-06-07 21:23:44 +0200751 sort_args.update(self.list_kwargs)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200752 uri = self.list_client.build_uri(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200753 self.plural_name, limit=1, **sort_args)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200754 prev_links, body = self.list_client.get_uri_with_links(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200755 self.plural_name, uri
756 )
757 resources_ = self._extract_resources(body)
758 self.assertEqual(1, len(resources_))
759 resources.extend(resources_)
760
761 # The last element is empty and does not contain 'next' link
762 uri = self.get_bare_url(prev_links['next'])
763 prev_links, body = self.client.get_uri_with_links(
764 self.plural_name, uri
765 )
766 self.assertNotIn('next', prev_links)
767
768 # Now walk backwards and compare results
769 resources2 = []
770 for i in range(niterations):
771 uri = self.get_bare_url(prev_links['previous'])
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200772 prev_links, body = self.list_client.get_uri_with_links(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200773 self.plural_name, uri
774 )
775 resources_ = self._extract_resources(body)
776 self.assertEqual(1, len(resources_))
777 resources2.extend(resources_)
778
779 self.assertSameOrder(resources, reversed(resources2))
780
781 return resources
782
783 @_require_pagination
784 @_require_sorting
785 def _test_list_pagination_with_href_links(self):
786 self._test_list_pagination_iteratively(self._list_all_with_hrefs)
787
788 @_require_pagination
789 @_require_sorting
790 def _test_list_pagination_page_reverse_with_href_links(
791 self, direction=constants.SORT_DIRECTION_ASC):
792 pagination_args = {
793 'sort_dir': direction,
Armando Migliaccio57581c62016-07-01 10:13:19 -0700794 'sort_key': self.field,
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200795 }
796 body = self.list_method(**pagination_args)
797 expected_resources = self._extract_resources(body)
798
799 page_size = 2
800 pagination_args['limit'] = page_size
801
802 prev_links = {}
803 resources = []
804 num_resources = len(expected_resources)
805 niterations = int(math.ceil(float(num_resources) / page_size))
806 for i in range(niterations):
807 if prev_links:
808 uri = self.get_bare_url(prev_links['previous'])
809 else:
Ihar Hrachyshka7f79fe62016-06-07 21:23:44 +0200810 pagination_args.update(self.list_kwargs)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200811 uri = self.list_client.build_uri(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200812 self.plural_name, page_reverse=True, **pagination_args)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200813 prev_links, body = self.list_client.get_uri_with_links(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200814 self.plural_name, uri
815 )
816 resources_ = self._extract_resources(body)
Béla Vancsicsf1806182016-08-23 07:36:18 +0200817 self.assertGreaterEqual(page_size, len(resources_))
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200818 resources.extend(reversed(resources_))
819
820 self.assertSameOrder(expected_resources, reversed(resources))
821
822 @_require_pagination
823 @_require_sorting
824 def _test_list_pagination_page_reverse_asc(self):
825 self._test_list_pagination_page_reverse(
826 direction=constants.SORT_DIRECTION_ASC)
827
828 @_require_pagination
829 @_require_sorting
830 def _test_list_pagination_page_reverse_desc(self):
831 self._test_list_pagination_page_reverse(
832 direction=constants.SORT_DIRECTION_DESC)
833
834 def _test_list_pagination_page_reverse(self, direction):
835 pagination_args = {
836 'sort_dir': direction,
Armando Migliaccio57581c62016-07-01 10:13:19 -0700837 'sort_key': self.field,
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200838 'limit': 3,
839 }
840 body = self.list_method(**pagination_args)
841 expected_resources = self._extract_resources(body)
842
843 pagination_args['limit'] -= 1
844 pagination_args['marker'] = expected_resources[-1]['id']
845 pagination_args['page_reverse'] = True
846 body = self.list_method(**pagination_args)
847
848 self.assertSameOrder(
849 # the last entry is not included in 2nd result when used as a
850 # marker
851 expected_resources[:-1],
852 self._extract_resources(body))
Victor Morales1be97b42016-09-05 08:50:06 -0500853
854 def _test_list_validation_filters(self):
855 validation_args = {
856 'unknown_filter': 'value',
857 }
858 body = self.list_method(**validation_args)
859 resources = self._extract_resources(body)
860 for resource in resources:
861 self.assertIn(resource['name'], self.resource_names)