blob: 51a7d3e270d0e23cda9bd49ea64bf28265c4c2c0 [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."""
Edan Davidd75e48e2018-01-03 02:49:52 -0500342 if CONF.network.port_vnic_type and 'binding:vnic_type' not in kwargs:
343 kwargs['binding:vnic_type'] = CONF.network.port_vnic_type
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000344 body = cls.client.create_port(network_id=network['id'],
345 **kwargs)
346 port = body['port']
347 cls.ports.append(port)
348 return port
349
350 @classmethod
351 def update_port(cls, port, **kwargs):
352 """Wrapper utility that updates a test port."""
353 body = cls.client.update_port(port['id'],
354 **kwargs)
355 return body['port']
356
357 @classmethod
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300358 def _create_router_with_client(
359 cls, client, router_name=None, admin_state_up=False,
360 external_network_id=None, enable_snat=None, **kwargs
361 ):
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000362 ext_gw_info = {}
363 if external_network_id:
364 ext_gw_info['network_id'] = external_network_id
YAMAMOTO Takashi9bd4f972017-06-20 12:49:30 +0900365 if enable_snat is not None:
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000366 ext_gw_info['enable_snat'] = enable_snat
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300367 body = client.create_router(
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000368 router_name, external_gateway_info=ext_gw_info,
369 admin_state_up=admin_state_up, **kwargs)
370 router = body['router']
371 cls.routers.append(router)
372 return router
373
374 @classmethod
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300375 def create_router(cls, *args, **kwargs):
376 return cls._create_router_with_client(cls.client, *args, **kwargs)
377
378 @classmethod
379 def create_admin_router(cls, *args, **kwargs):
rajat294495c042017-06-28 15:37:16 +0530380 return cls._create_router_with_client(cls.os_admin.network_client,
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300381 *args, **kwargs)
382
383 @classmethod
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000384 def create_floatingip(cls, external_network_id):
385 """Wrapper utility that returns a test floating IP."""
386 body = cls.client.create_floatingip(
387 floating_network_id=external_network_id)
388 fip = body['floatingip']
389 cls.floating_ips.append(fip)
390 return fip
391
392 @classmethod
393 def create_router_interface(cls, router_id, subnet_id):
394 """Wrapper utility that returns a router interface."""
395 interface = cls.client.add_router_interface_with_subnet_id(
396 router_id, subnet_id)
397 return interface
398
399 @classmethod
Sławek Kapłońskiff294062016-12-04 15:00:54 +0000400 def get_supported_qos_rule_types(cls):
401 body = cls.client.list_qos_rule_types()
402 return [rule_type['type'] for rule_type in body['rule_types']]
403
404 @classmethod
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200405 def create_qos_policy(cls, name, description=None, shared=False,
Hirofumi Ichihara39a6ee12017-08-23 13:55:12 +0900406 tenant_id=None, is_default=False):
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000407 """Wrapper utility that returns a test QoS policy."""
408 body = cls.admin_client.create_qos_policy(
Hirofumi Ichihara39a6ee12017-08-23 13:55:12 +0900409 name, description, shared, tenant_id, is_default)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000410 qos_policy = body['policy']
411 cls.qos_policies.append(qos_policy)
412 return qos_policy
413
414 @classmethod
Sławek Kapłoński153f3452017-03-24 22:04:53 +0000415 def create_qos_bandwidth_limit_rule(cls, policy_id, max_kbps,
416 max_burst_kbps,
Chandan Kumarc125fd12017-11-15 19:41:01 +0530417 direction=const.EGRESS_DIRECTION):
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000418 """Wrapper utility that returns a test QoS bandwidth limit rule."""
419 body = cls.admin_client.create_bandwidth_limit_rule(
Sławek Kapłoński153f3452017-03-24 22:04:53 +0000420 policy_id, max_kbps, max_burst_kbps, direction)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000421 qos_rule = body['bandwidth_limit_rule']
422 cls.qos_rules.append(qos_rule)
423 return qos_rule
424
425 @classmethod
Jakub Libosvar83704832017-12-06 16:02:28 +0000426 def delete_router(cls, router, client=None):
427 client = client or cls.client
428 body = client.list_router_interfaces(router['id'])
Chandan Kumarc125fd12017-11-15 19:41:01 +0530429 interfaces = [port for port in body['ports']
430 if port['device_owner'] in const.ROUTER_INTERFACE_OWNERS]
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000431 for i in interfaces:
432 try:
Jakub Libosvar83704832017-12-06 16:02:28 +0000433 client.remove_router_interface_with_subnet_id(
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000434 router['id'], i['fixed_ips'][0]['subnet_id'])
435 except lib_exc.NotFound:
436 pass
Jakub Libosvar83704832017-12-06 16:02:28 +0000437 client.delete_router(router['id'])
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000438
439 @classmethod
440 def create_address_scope(cls, name, is_admin=False, **kwargs):
441 if is_admin:
442 body = cls.admin_client.create_address_scope(name=name, **kwargs)
443 cls.admin_address_scopes.append(body['address_scope'])
444 else:
445 body = cls.client.create_address_scope(name=name, **kwargs)
446 cls.address_scopes.append(body['address_scope'])
447 return body['address_scope']
448
449 @classmethod
450 def create_subnetpool(cls, name, is_admin=False, **kwargs):
451 if is_admin:
452 body = cls.admin_client.create_subnetpool(name, **kwargs)
453 cls.admin_subnetpools.append(body['subnetpool'])
454 else:
455 body = cls.client.create_subnetpool(name, **kwargs)
456 cls.subnetpools.append(body['subnetpool'])
457 return body['subnetpool']
458
Chandan Kumarc125fd12017-11-15 19:41:01 +0530459 @classmethod
460 def create_project(cls, name=None, description=None):
461 test_project = name or data_utils.rand_name('test_project_')
462 test_description = description or data_utils.rand_name('desc_')
463 project = cls.identity_admin_client.create_project(
464 name=test_project,
465 description=test_description)['project']
466 cls.projects.append(project)
467 return project
468
469 @classmethod
470 def create_security_group(cls, name, **kwargs):
471 body = cls.client.create_security_group(name=name, **kwargs)
472 cls.security_groups.append(body['security_group'])
473 return body['security_group']
474
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000475
476class BaseAdminNetworkTest(BaseNetworkTest):
477
478 credentials = ['primary', 'admin']
479
480 @classmethod
481 def setup_clients(cls):
482 super(BaseAdminNetworkTest, cls).setup_clients()
fumihiko kakumaa216fc12017-07-14 10:43:29 +0900483 cls.admin_client = cls.os_admin.network_client
Jakub Libosvarf5758012017-08-15 13:45:30 +0000484 cls.identity_admin_client = cls.os_admin.projects_client
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000485
486 @classmethod
487 def create_metering_label(cls, name, description):
488 """Wrapper utility that returns a test metering label."""
489 body = cls.admin_client.create_metering_label(
490 description=description,
491 name=data_utils.rand_name("metering-label"))
492 metering_label = body['metering_label']
493 cls.metering_labels.append(metering_label)
494 return metering_label
495
496 @classmethod
497 def create_metering_label_rule(cls, remote_ip_prefix, direction,
498 metering_label_id):
499 """Wrapper utility that returns a test metering label rule."""
500 body = cls.admin_client.create_metering_label_rule(
501 remote_ip_prefix=remote_ip_prefix, direction=direction,
502 metering_label_id=metering_label_id)
503 metering_label_rule = body['metering_label_rule']
504 cls.metering_label_rules.append(metering_label_rule)
505 return metering_label_rule
506
507 @classmethod
508 def create_flavor(cls, name, description, service_type):
509 """Wrapper utility that returns a test flavor."""
510 body = cls.admin_client.create_flavor(
511 description=description, service_type=service_type,
512 name=name)
513 flavor = body['flavor']
514 cls.flavors.append(flavor)
515 return flavor
516
517 @classmethod
518 def create_service_profile(cls, description, metainfo, driver):
519 """Wrapper utility that returns a test service profile."""
520 body = cls.admin_client.create_service_profile(
521 driver=driver, metainfo=metainfo, description=description)
522 service_profile = body['service_profile']
523 cls.service_profiles.append(service_profile)
524 return service_profile
525
526 @classmethod
Nguyen Phuong An67993fc2017-11-24 11:30:25 +0700527 def create_log(cls, name, description=None,
528 resource_type='security_group', resource_id=None,
529 target_id=None, event='ALL', enabled=True):
530 """Wrapper utility that returns a test log object."""
531 log_args = {'name': name,
532 'description': description,
533 'resource_type': resource_type,
534 'resource_id': resource_id,
535 'target_id': target_id,
536 'event': event,
537 'enabled': enabled}
538 body = cls.admin_client.create_log(**log_args)
539 log_object = body['log']
540 cls.log_objects.append(log_object)
541 return log_object
542
543 @classmethod
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000544 def get_unused_ip(cls, net_id, ip_version=None):
Gary Kotton011345f2016-06-15 08:04:31 -0700545 """Get an unused ip address in a allocation pool of net"""
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000546 body = cls.admin_client.list_ports(network_id=net_id)
547 ports = body['ports']
548 used_ips = []
549 for port in ports:
550 used_ips.extend(
551 [fixed_ip['ip_address'] for fixed_ip in port['fixed_ips']])
552 body = cls.admin_client.list_subnets(network_id=net_id)
553 subnets = body['subnets']
554
555 for subnet in subnets:
556 if ip_version and subnet['ip_version'] != ip_version:
557 continue
558 cidr = subnet['cidr']
559 allocation_pools = subnet['allocation_pools']
560 iterators = []
561 if allocation_pools:
562 for allocation_pool in allocation_pools:
563 iterators.append(netaddr.iter_iprange(
564 allocation_pool['start'], allocation_pool['end']))
565 else:
566 net = netaddr.IPNetwork(cidr)
567
568 def _iterip():
569 for ip in net:
570 if ip not in (net.network, net.broadcast):
571 yield ip
572 iterators.append(iter(_iterip()))
573
574 for iterator in iterators:
575 for ip in iterator:
576 if str(ip) not in used_ips:
577 return str(ip)
578
579 message = (
580 "net(%s) has no usable IP address in allocation pools" % net_id)
581 raise exceptions.InvalidConfiguration(message)
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200582
583
Sławek Kapłońskiff294062016-12-04 15:00:54 +0000584def require_qos_rule_type(rule_type):
585 def decorator(f):
586 @functools.wraps(f)
587 def wrapper(self, *func_args, **func_kwargs):
588 if rule_type not in self.get_supported_qos_rule_types():
589 raise self.skipException(
590 "%s rule type is required." % rule_type)
591 return f(self, *func_args, **func_kwargs)
592 return wrapper
593 return decorator
594
595
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200596def _require_sorting(f):
597 @functools.wraps(f)
598 def inner(self, *args, **kwargs):
Chandan Kumarc125fd12017-11-15 19:41:01 +0530599 if not tutils.is_extension_enabled("sorting", "network"):
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200600 self.skipTest('Sorting feature is required')
601 return f(self, *args, **kwargs)
602 return inner
603
604
605def _require_pagination(f):
606 @functools.wraps(f)
607 def inner(self, *args, **kwargs):
Chandan Kumarc125fd12017-11-15 19:41:01 +0530608 if not tutils.is_extension_enabled("pagination", "network"):
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200609 self.skipTest('Pagination feature is required')
610 return f(self, *args, **kwargs)
611 return inner
612
613
614class BaseSearchCriteriaTest(BaseNetworkTest):
615
616 # This should be defined by subclasses to reflect resource name to test
617 resource = None
618
Armando Migliaccio57581c62016-07-01 10:13:19 -0700619 field = 'name'
620
Ihar Hrachyshkaa8fe5a12016-05-24 14:50:58 +0200621 # NOTE(ihrachys): some names, like those starting with an underscore (_)
622 # are sorted differently depending on whether the plugin implements native
623 # sorting support, or not. So we avoid any such cases here, sticking to
624 # alphanumeric. Also test a case when there are multiple resources with the
625 # same name
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200626 resource_names = ('test1', 'abc1', 'test10', '123test') + ('test1',)
627
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200628 force_tenant_isolation = True
629
Ihar Hrachyshkaa8fe5a12016-05-24 14:50:58 +0200630 list_kwargs = {}
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200631
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200632 list_as_admin = False
633
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200634 def assertSameOrder(self, original, actual):
635 # gracefully handle iterators passed
636 original = list(original)
637 actual = list(actual)
638 self.assertEqual(len(original), len(actual))
639 for expected, res in zip(original, actual):
Armando Migliaccio57581c62016-07-01 10:13:19 -0700640 self.assertEqual(expected[self.field], res[self.field])
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200641
642 @utils.classproperty
643 def plural_name(self):
644 return '%ss' % self.resource
645
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200646 @property
647 def list_client(self):
648 return self.admin_client if self.list_as_admin else self.client
649
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200650 def list_method(self, *args, **kwargs):
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200651 method = getattr(self.list_client, 'list_%s' % self.plural_name)
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200652 kwargs.update(self.list_kwargs)
653 return method(*args, **kwargs)
654
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200655 def get_bare_url(self, url):
656 base_url = self.client.base_url
657 self.assertTrue(url.startswith(base_url))
658 return url[len(base_url):]
659
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200660 @classmethod
661 def _extract_resources(cls, body):
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200662 return body[cls.plural_name]
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200663
664 def _test_list_sorts(self, direction):
665 sort_args = {
666 'sort_dir': direction,
Armando Migliaccio57581c62016-07-01 10:13:19 -0700667 'sort_key': self.field
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200668 }
669 body = self.list_method(**sort_args)
670 resources = self._extract_resources(body)
671 self.assertNotEmpty(
672 resources, "%s list returned is empty" % self.resource)
Armando Migliaccio57581c62016-07-01 10:13:19 -0700673 retrieved_names = [res[self.field] for res in resources]
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200674 expected = sorted(retrieved_names)
675 if direction == constants.SORT_DIRECTION_DESC:
676 expected = list(reversed(expected))
677 self.assertEqual(expected, retrieved_names)
678
679 @_require_sorting
680 def _test_list_sorts_asc(self):
681 self._test_list_sorts(constants.SORT_DIRECTION_ASC)
682
683 @_require_sorting
684 def _test_list_sorts_desc(self):
685 self._test_list_sorts(constants.SORT_DIRECTION_DESC)
686
687 @_require_pagination
688 def _test_list_pagination(self):
689 for limit in range(1, len(self.resource_names) + 1):
690 pagination_args = {
691 'limit': limit,
692 }
693 body = self.list_method(**pagination_args)
694 resources = self._extract_resources(body)
695 self.assertEqual(limit, len(resources))
696
697 @_require_pagination
698 def _test_list_no_pagination_limit_0(self):
699 pagination_args = {
700 'limit': 0,
701 }
702 body = self.list_method(**pagination_args)
703 resources = self._extract_resources(body)
Béla Vancsicsf1806182016-08-23 07:36:18 +0200704 self.assertGreaterEqual(len(resources), len(self.resource_names))
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200705
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200706 def _test_list_pagination_iteratively(self, lister):
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200707 # first, collect all resources for later comparison
708 sort_args = {
709 'sort_dir': constants.SORT_DIRECTION_ASC,
Armando Migliaccio57581c62016-07-01 10:13:19 -0700710 'sort_key': self.field
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200711 }
712 body = self.list_method(**sort_args)
713 expected_resources = self._extract_resources(body)
714 self.assertNotEmpty(expected_resources)
715
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200716 resources = lister(
717 len(expected_resources), sort_args
718 )
719
720 # finally, compare that the list retrieved in one go is identical to
721 # the one containing pagination results
722 self.assertSameOrder(expected_resources, resources)
723
724 def _list_all_with_marker(self, niterations, sort_args):
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200725 # paginate resources one by one, using last fetched resource as a
726 # marker
727 resources = []
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200728 for i in range(niterations):
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200729 pagination_args = sort_args.copy()
730 pagination_args['limit'] = 1
731 if resources:
732 pagination_args['marker'] = resources[-1]['id']
733 body = self.list_method(**pagination_args)
734 resources_ = self._extract_resources(body)
735 self.assertEqual(1, len(resources_))
736 resources.extend(resources_)
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200737 return resources
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200738
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200739 @_require_pagination
740 @_require_sorting
741 def _test_list_pagination_with_marker(self):
742 self._test_list_pagination_iteratively(self._list_all_with_marker)
743
744 def _list_all_with_hrefs(self, niterations, sort_args):
745 # paginate resources one by one, using next href links
746 resources = []
747 prev_links = {}
748
749 for i in range(niterations):
750 if prev_links:
751 uri = self.get_bare_url(prev_links['next'])
752 else:
Ihar Hrachyshka7f79fe62016-06-07 21:23:44 +0200753 sort_args.update(self.list_kwargs)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200754 uri = self.list_client.build_uri(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200755 self.plural_name, limit=1, **sort_args)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200756 prev_links, body = self.list_client.get_uri_with_links(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200757 self.plural_name, uri
758 )
759 resources_ = self._extract_resources(body)
760 self.assertEqual(1, len(resources_))
761 resources.extend(resources_)
762
763 # The last element is empty and does not contain 'next' link
764 uri = self.get_bare_url(prev_links['next'])
765 prev_links, body = self.client.get_uri_with_links(
766 self.plural_name, uri
767 )
768 self.assertNotIn('next', prev_links)
769
770 # Now walk backwards and compare results
771 resources2 = []
772 for i in range(niterations):
773 uri = self.get_bare_url(prev_links['previous'])
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200774 prev_links, body = self.list_client.get_uri_with_links(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200775 self.plural_name, uri
776 )
777 resources_ = self._extract_resources(body)
778 self.assertEqual(1, len(resources_))
779 resources2.extend(resources_)
780
781 self.assertSameOrder(resources, reversed(resources2))
782
783 return resources
784
785 @_require_pagination
786 @_require_sorting
787 def _test_list_pagination_with_href_links(self):
788 self._test_list_pagination_iteratively(self._list_all_with_hrefs)
789
790 @_require_pagination
791 @_require_sorting
792 def _test_list_pagination_page_reverse_with_href_links(
793 self, direction=constants.SORT_DIRECTION_ASC):
794 pagination_args = {
795 'sort_dir': direction,
Armando Migliaccio57581c62016-07-01 10:13:19 -0700796 'sort_key': self.field,
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200797 }
798 body = self.list_method(**pagination_args)
799 expected_resources = self._extract_resources(body)
800
801 page_size = 2
802 pagination_args['limit'] = page_size
803
804 prev_links = {}
805 resources = []
806 num_resources = len(expected_resources)
807 niterations = int(math.ceil(float(num_resources) / page_size))
808 for i in range(niterations):
809 if prev_links:
810 uri = self.get_bare_url(prev_links['previous'])
811 else:
Ihar Hrachyshka7f79fe62016-06-07 21:23:44 +0200812 pagination_args.update(self.list_kwargs)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200813 uri = self.list_client.build_uri(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200814 self.plural_name, page_reverse=True, **pagination_args)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200815 prev_links, body = self.list_client.get_uri_with_links(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200816 self.plural_name, uri
817 )
818 resources_ = self._extract_resources(body)
Béla Vancsicsf1806182016-08-23 07:36:18 +0200819 self.assertGreaterEqual(page_size, len(resources_))
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200820 resources.extend(reversed(resources_))
821
822 self.assertSameOrder(expected_resources, reversed(resources))
823
824 @_require_pagination
825 @_require_sorting
826 def _test_list_pagination_page_reverse_asc(self):
827 self._test_list_pagination_page_reverse(
828 direction=constants.SORT_DIRECTION_ASC)
829
830 @_require_pagination
831 @_require_sorting
832 def _test_list_pagination_page_reverse_desc(self):
833 self._test_list_pagination_page_reverse(
834 direction=constants.SORT_DIRECTION_DESC)
835
836 def _test_list_pagination_page_reverse(self, direction):
837 pagination_args = {
838 'sort_dir': direction,
Armando Migliaccio57581c62016-07-01 10:13:19 -0700839 'sort_key': self.field,
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200840 'limit': 3,
841 }
842 body = self.list_method(**pagination_args)
843 expected_resources = self._extract_resources(body)
844
845 pagination_args['limit'] -= 1
846 pagination_args['marker'] = expected_resources[-1]['id']
847 pagination_args['page_reverse'] = True
848 body = self.list_method(**pagination_args)
849
850 self.assertSameOrder(
851 # the last entry is not included in 2nd result when used as a
852 # marker
853 expected_resources[:-1],
854 self._extract_resources(body))
Victor Morales1be97b42016-09-05 08:50:06 -0500855
856 def _test_list_validation_filters(self):
857 validation_args = {
858 'unknown_filter': 'value',
859 }
860 body = self.list_method(**validation_args)
861 resources = self._extract_resources(body)
862 for resource in resources:
863 self.assertIn(resource['name'], self.resource_names)