blob: 6700131b157246224eb9e8460b9ef75e6d63f84f [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.
Federico Ressi0ddc93b2018-04-09 12:01:48 +020061 _ip_version = const.IP_VERSION_4
Daniel Mellado3c0aeab2016-01-29 11:30:25 +000062
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")
Federico Ressi0ddc93b2018-04-09 12:01:48 +020082 if (cls._ip_version == const.IP_VERSION_6 and
83 not CONF.network_feature_enabled.ipv6):
Daniel Mellado3c0aeab2016-01-29 11:30:25 +000084 raise cls.skipException("IPv6 Tests are disabled.")
Jakub Libosvar1982aa12017-05-30 11:15:33 +000085 for req_ext in getattr(cls, 'required_extensions', []):
Chandan Kumarc125fd12017-11-15 19:41:01 +053086 if not tutils.is_extension_enabled(req_ext, 'network'):
Jakub Libosvar1982aa12017-05-30 11:15:33 +000087 msg = "%s extension not enabled." % req_ext
88 raise cls.skipException(msg)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +000089
90 @classmethod
91 def setup_credentials(cls):
92 # Create no network resources for these test.
93 cls.set_network_resources()
94 super(BaseNetworkTest, cls).setup_credentials()
95
96 @classmethod
97 def setup_clients(cls):
98 super(BaseNetworkTest, cls).setup_clients()
fumihiko kakumaa216fc12017-07-14 10:43:29 +090099 cls.client = cls.os_primary.network_client
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000100
101 @classmethod
102 def resource_setup(cls):
103 super(BaseNetworkTest, cls).resource_setup()
104
105 cls.networks = []
Miguel Lavalle124378b2016-09-21 16:41:47 -0500106 cls.admin_networks = []
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000107 cls.subnets = []
Kevin Bentonba3651c2017-09-01 17:13:01 -0700108 cls.admin_subnets = []
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000109 cls.ports = []
110 cls.routers = []
111 cls.floating_ips = []
112 cls.metering_labels = []
113 cls.service_profiles = []
114 cls.flavors = []
115 cls.metering_label_rules = []
116 cls.qos_rules = []
117 cls.qos_policies = []
118 cls.ethertype = "IPv" + str(cls._ip_version)
119 cls.address_scopes = []
120 cls.admin_address_scopes = []
121 cls.subnetpools = []
122 cls.admin_subnetpools = []
Itzik Brownbac51dc2016-10-31 12:25:04 +0000123 cls.security_groups = []
Chandan Kumarc125fd12017-11-15 19:41:01 +0530124 cls.projects = []
Nguyen Phuong An67993fc2017-11-24 11:30:25 +0700125 cls.log_objects = []
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200126 cls.reserved_subnet_cidrs = set()
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000127
128 @classmethod
129 def resource_cleanup(cls):
130 if CONF.service_available.neutron:
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000131 # Clean up floating IPs
132 for floating_ip in cls.floating_ips:
133 cls._try_delete_resource(cls.client.delete_floatingip,
134 floating_ip['id'])
135 # Clean up routers
136 for router in cls.routers:
137 cls._try_delete_resource(cls.delete_router,
138 router)
139 # Clean up metering label rules
140 for metering_label_rule in cls.metering_label_rules:
141 cls._try_delete_resource(
142 cls.admin_client.delete_metering_label_rule,
143 metering_label_rule['id'])
144 # Clean up metering labels
145 for metering_label in cls.metering_labels:
146 cls._try_delete_resource(
147 cls.admin_client.delete_metering_label,
148 metering_label['id'])
149 # Clean up flavors
150 for flavor in cls.flavors:
151 cls._try_delete_resource(
152 cls.admin_client.delete_flavor,
153 flavor['id'])
154 # Clean up service profiles
155 for service_profile in cls.service_profiles:
156 cls._try_delete_resource(
157 cls.admin_client.delete_service_profile,
158 service_profile['id'])
159 # Clean up ports
160 for port in cls.ports:
161 cls._try_delete_resource(cls.client.delete_port,
162 port['id'])
163 # Clean up subnets
164 for subnet in cls.subnets:
165 cls._try_delete_resource(cls.client.delete_subnet,
166 subnet['id'])
Kevin Bentonba3651c2017-09-01 17:13:01 -0700167 # Clean up admin subnets
168 for subnet in cls.admin_subnets:
169 cls._try_delete_resource(cls.admin_client.delete_subnet,
170 subnet['id'])
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000171 # Clean up networks
172 for network in cls.networks:
173 cls._try_delete_resource(cls.client.delete_network,
174 network['id'])
175
Miguel Lavalle124378b2016-09-21 16:41:47 -0500176 # Clean up admin networks
177 for network in cls.admin_networks:
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000178 cls._try_delete_resource(cls.admin_client.delete_network,
179 network['id'])
180
Itzik Brownbac51dc2016-10-31 12:25:04 +0000181 # Clean up security groups
182 for secgroup in cls.security_groups:
183 cls._try_delete_resource(cls.client.delete_security_group,
184 secgroup['id'])
185
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000186 for subnetpool in cls.subnetpools:
187 cls._try_delete_resource(cls.client.delete_subnetpool,
188 subnetpool['id'])
189
190 for subnetpool in cls.admin_subnetpools:
191 cls._try_delete_resource(cls.admin_client.delete_subnetpool,
192 subnetpool['id'])
193
194 for address_scope in cls.address_scopes:
195 cls._try_delete_resource(cls.client.delete_address_scope,
196 address_scope['id'])
197
198 for address_scope in cls.admin_address_scopes:
199 cls._try_delete_resource(
200 cls.admin_client.delete_address_scope,
201 address_scope['id'])
202
Chandan Kumarc125fd12017-11-15 19:41:01 +0530203 for project in cls.projects:
204 cls._try_delete_resource(
205 cls.identity_admin_client.delete_project,
206 project['id'])
207
Sławek Kapłońskie100c4d2017-08-23 21:18:34 +0000208 # Clean up QoS rules
209 for qos_rule in cls.qos_rules:
210 cls._try_delete_resource(cls.admin_client.delete_qos_rule,
211 qos_rule['id'])
212 # Clean up QoS policies
213 # as all networks and ports are already removed, QoS policies
214 # shouldn't be "in use"
215 for qos_policy in cls.qos_policies:
216 cls._try_delete_resource(cls.admin_client.delete_qos_policy,
217 qos_policy['id'])
218
Nguyen Phuong An67993fc2017-11-24 11:30:25 +0700219 # Clean up log_objects
220 for log_object in cls.log_objects:
221 cls._try_delete_resource(cls.admin_client.delete_log,
222 log_object['id'])
223
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000224 super(BaseNetworkTest, cls).resource_cleanup()
225
226 @classmethod
227 def _try_delete_resource(cls, delete_callable, *args, **kwargs):
228 """Cleanup resources in case of test-failure
229
230 Some resources are explicitly deleted by the test.
231 If the test failed to delete a resource, this method will execute
232 the appropriate delete methods. Otherwise, the method ignores NotFound
233 exceptions thrown for resources that were correctly deleted by the
234 test.
235
236 :param delete_callable: delete method
237 :param args: arguments for delete method
238 :param kwargs: keyword arguments for delete method
239 """
240 try:
241 delete_callable(*args, **kwargs)
242 # if resource is not found, this means it was deleted in the test
243 except lib_exc.NotFound:
244 pass
245
246 @classmethod
Sergey Belousa627ed92016-10-07 14:29:07 +0300247 def create_network(cls, network_name=None, client=None, **kwargs):
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000248 """Wrapper utility that returns a test network."""
249 network_name = network_name or data_utils.rand_name('test-network-')
250
Sergey Belousa627ed92016-10-07 14:29:07 +0300251 client = client or cls.client
252 body = client.create_network(name=network_name, **kwargs)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000253 network = body['network']
Sławek Kapłońskia694a5f2017-08-24 19:51:22 +0000254 if client is cls.client:
255 cls.networks.append(network)
256 else:
257 cls.admin_networks.append(network)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000258 return network
259
260 @classmethod
261 def create_shared_network(cls, network_name=None, **post_body):
262 network_name = network_name or data_utils.rand_name('sharednetwork-')
263 post_body.update({'name': network_name, 'shared': True})
264 body = cls.admin_client.create_network(**post_body)
265 network = body['network']
Miguel Lavalle124378b2016-09-21 16:41:47 -0500266 cls.admin_networks.append(network)
267 return network
268
269 @classmethod
270 def create_network_keystone_v3(cls, network_name=None, project_id=None,
271 tenant_id=None, client=None):
272 """Wrapper utility that creates a test network with project_id."""
273 client = client or cls.client
274 network_name = network_name or data_utils.rand_name(
275 'test-network-with-project_id')
276 project_id = cls.client.tenant_id
277 body = client.create_network_keystone_v3(network_name, project_id,
278 tenant_id)
279 network = body['network']
280 if client is cls.client:
281 cls.networks.append(network)
282 else:
283 cls.admin_networks.append(network)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000284 return network
285
286 @classmethod
Sławek Kapłońskid98e27d2018-05-07 16:16:28 +0200287 def create_subnet(cls, network, gateway='', cidr=None, mask_bits=None,
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000288 ip_version=None, client=None, **kwargs):
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200289 """Wrapper utility that returns a test subnet.
290
291 Convenient wrapper for client.create_subnet method. It reserves and
292 allocates CIDRs to avoid creating overlapping subnets.
293
294 :param network: network where to create the subnet
295 network['id'] must contain the ID of the network
296
297 :param gateway: gateway IP address
298 It can be a str or a netaddr.IPAddress
299 If gateway is not given, then it will use default address for
300 given subnet CIDR, like "192.168.0.1" for "192.168.0.0/24" CIDR
Sławek Kapłońskid98e27d2018-05-07 16:16:28 +0200301 if gateway is given as None then no gateway will be assigned
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200302
303 :param cidr: CIDR of the subnet to create
304 It can be either None, a str or a netaddr.IPNetwork instance
305
306 :param mask_bits: CIDR prefix length
307 It can be either None or a numeric value.
308 If cidr parameter is given then mask_bits is used to determinate a
309 sequence of valid CIDR to use as generated.
310 Please see netaddr.IPNetwork.subnet method documentation[1]
311
312 :param ip_version: ip version of generated subnet CIDRs
313 It can be None, IP_VERSION_4 or IP_VERSION_6
314 It has to match given either given CIDR and gateway
315
316 :param ip_version: numeric value (either IP_VERSION_4 or IP_VERSION_6)
317 this value must match CIDR and gateway IP versions if any of them is
318 given
319
320 :param client: client to be used to connect to network service
321
322 :param **kwargs: optional parameters to be forwarded to wrapped method
323
324 [1] http://netaddr.readthedocs.io/en/latest/tutorial_01.html#supernets-and-subnets # noqa
325 """
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000326
327 # allow tests to use admin client
328 if not client:
329 client = cls.client
330
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200331 if gateway:
332 gateway_ip = netaddr.IPAddress(gateway)
333 if ip_version:
334 if ip_version != gateway_ip.version:
335 raise ValueError(
336 "Gateway IP version doesn't match IP version")
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000337 else:
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200338 ip_version = gateway_ip.version
Sławek Kapłońskid98e27d2018-05-07 16:16:28 +0200339 else:
340 ip_version = ip_version or cls._ip_version
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200341
342 for subnet_cidr in cls.get_subnet_cidrs(
343 ip_version=ip_version, cidr=cidr, mask_bits=mask_bits):
344 if cls.try_reserve_subnet_cidr(subnet_cidr):
Sławek Kapłońskid98e27d2018-05-07 16:16:28 +0200345 if gateway is not None:
346 kwargs['gateway_ip'] = str(gateway or (subnet_cidr.ip + 1))
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200347 try:
348 body = client.create_subnet(
349 network_id=network['id'],
350 cidr=str(subnet_cidr),
351 ip_version=subnet_cidr.version,
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200352 **kwargs)
353 break
354 except lib_exc.BadRequest as e:
355 if 'overlaps with another subnet' not in str(e):
356 raise
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000357 else:
358 message = 'Available CIDR for subnet creation could not be found'
359 raise ValueError(message)
360 subnet = body['subnet']
Kevin Bentonba3651c2017-09-01 17:13:01 -0700361 if client is cls.client:
362 cls.subnets.append(subnet)
363 else:
364 cls.admin_subnets.append(subnet)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000365 return subnet
366
367 @classmethod
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200368 def reserve_subnet_cidr(cls, addr, **ipnetwork_kwargs):
369 """Reserve given subnet CIDR making sure it is not used by create_subnet
370
371 :param addr: the CIDR address to be reserved
372 It can be a str or netaddr.IPNetwork instance
373
374 :param **ipnetwork_kwargs: optional netaddr.IPNetwork constructor
375 parameters
376 """
377
378 if not cls.try_reserve_subnet_cidr(addr, **ipnetwork_kwargs):
379 raise ValueError('Subnet CIDR already reserved: %r'.format(
380 addr))
381
382 @classmethod
383 def try_reserve_subnet_cidr(cls, addr, **ipnetwork_kwargs):
384 """Reserve given subnet CIDR if it hasn't been reserved before
385
386 :param addr: the CIDR address to be reserved
387 It can be a str or netaddr.IPNetwork instance
388
389 :param **ipnetwork_kwargs: optional netaddr.IPNetwork constructor
390 parameters
391
392 :return: True if it wasn't reserved before, False elsewhere.
393 """
394
395 subnet_cidr = netaddr.IPNetwork(addr, **ipnetwork_kwargs)
396 if subnet_cidr in cls.reserved_subnet_cidrs:
397 return False
398 else:
399 cls.reserved_subnet_cidrs.add(subnet_cidr)
400 return True
401
402 @classmethod
403 def get_subnet_cidrs(
404 cls, cidr=None, mask_bits=None, ip_version=None):
405 """Iterate over a sequence of unused subnet CIDR for IP version
406
407 :param cidr: CIDR of the subnet to create
408 It can be either None, a str or a netaddr.IPNetwork instance
409
410 :param mask_bits: CIDR prefix length
411 It can be either None or a numeric value.
412 If cidr parameter is given then mask_bits is used to determinate a
413 sequence of valid CIDR to use as generated.
414 Please see netaddr.IPNetwork.subnet method documentation[1]
415
416 :param ip_version: ip version of generated subnet CIDRs
417 It can be None, IP_VERSION_4 or IP_VERSION_6
418 It has to match given CIDR if given
419
420 :return: iterator over reserved CIDRs of type netaddr.IPNetwork
421
422 [1] http://netaddr.readthedocs.io/en/latest/tutorial_01.html#supernets-and-subnets # noqa
423 """
424
425 if cidr:
426 # Generate subnet CIDRs starting from given CIDR
427 # checking it is of requested IP version
428 cidr = netaddr.IPNetwork(cidr, version=ip_version)
429 else:
430 # Generate subnet CIDRs starting from configured values
431 ip_version = ip_version or cls._ip_version
432 if ip_version == const.IP_VERSION_4:
433 mask_bits = mask_bits or config.safe_get_config_value(
434 'network', 'project_network_mask_bits')
435 cidr = netaddr.IPNetwork(config.safe_get_config_value(
436 'network', 'project_network_cidr'))
437 elif ip_version == const.IP_VERSION_6:
438 mask_bits = config.safe_get_config_value(
439 'network', 'project_network_v6_mask_bits')
440 cidr = netaddr.IPNetwork(config.safe_get_config_value(
441 'network', 'project_network_v6_cidr'))
442 else:
443 raise ValueError('Invalid IP version: {!r}'.format(ip_version))
444
445 if mask_bits:
446 subnet_cidrs = cidr.subnet(mask_bits)
447 else:
448 subnet_cidrs = iter([cidr])
449
450 for subnet_cidr in subnet_cidrs:
451 if subnet_cidr not in cls.reserved_subnet_cidrs:
452 yield subnet_cidr
453
454 @classmethod
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000455 def create_port(cls, network, **kwargs):
456 """Wrapper utility that returns a test port."""
Edan Davidd75e48e2018-01-03 02:49:52 -0500457 if CONF.network.port_vnic_type and 'binding:vnic_type' not in kwargs:
458 kwargs['binding:vnic_type'] = CONF.network.port_vnic_type
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000459 body = cls.client.create_port(network_id=network['id'],
460 **kwargs)
461 port = body['port']
462 cls.ports.append(port)
463 return port
464
465 @classmethod
466 def update_port(cls, port, **kwargs):
467 """Wrapper utility that updates a test port."""
468 body = cls.client.update_port(port['id'],
469 **kwargs)
470 return body['port']
471
472 @classmethod
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300473 def _create_router_with_client(
474 cls, client, router_name=None, admin_state_up=False,
475 external_network_id=None, enable_snat=None, **kwargs
476 ):
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000477 ext_gw_info = {}
478 if external_network_id:
479 ext_gw_info['network_id'] = external_network_id
YAMAMOTO Takashi9bd4f972017-06-20 12:49:30 +0900480 if enable_snat is not None:
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000481 ext_gw_info['enable_snat'] = enable_snat
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300482 body = client.create_router(
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000483 router_name, external_gateway_info=ext_gw_info,
484 admin_state_up=admin_state_up, **kwargs)
485 router = body['router']
486 cls.routers.append(router)
487 return router
488
489 @classmethod
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300490 def create_router(cls, *args, **kwargs):
491 return cls._create_router_with_client(cls.client, *args, **kwargs)
492
493 @classmethod
494 def create_admin_router(cls, *args, **kwargs):
rajat294495c042017-06-28 15:37:16 +0530495 return cls._create_router_with_client(cls.os_admin.network_client,
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300496 *args, **kwargs)
497
498 @classmethod
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000499 def create_floatingip(cls, external_network_id):
500 """Wrapper utility that returns a test floating IP."""
501 body = cls.client.create_floatingip(
502 floating_network_id=external_network_id)
503 fip = body['floatingip']
504 cls.floating_ips.append(fip)
505 return fip
506
507 @classmethod
508 def create_router_interface(cls, router_id, subnet_id):
509 """Wrapper utility that returns a router interface."""
510 interface = cls.client.add_router_interface_with_subnet_id(
511 router_id, subnet_id)
512 return interface
513
514 @classmethod
Sławek Kapłońskiff294062016-12-04 15:00:54 +0000515 def get_supported_qos_rule_types(cls):
516 body = cls.client.list_qos_rule_types()
517 return [rule_type['type'] for rule_type in body['rule_types']]
518
519 @classmethod
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200520 def create_qos_policy(cls, name, description=None, shared=False,
Hirofumi Ichihara39a6ee12017-08-23 13:55:12 +0900521 tenant_id=None, is_default=False):
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000522 """Wrapper utility that returns a test QoS policy."""
523 body = cls.admin_client.create_qos_policy(
Hirofumi Ichihara39a6ee12017-08-23 13:55:12 +0900524 name, description, shared, tenant_id, is_default)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000525 qos_policy = body['policy']
526 cls.qos_policies.append(qos_policy)
527 return qos_policy
528
529 @classmethod
Sławek Kapłoński153f3452017-03-24 22:04:53 +0000530 def create_qos_bandwidth_limit_rule(cls, policy_id, max_kbps,
531 max_burst_kbps,
Chandan Kumarc125fd12017-11-15 19:41:01 +0530532 direction=const.EGRESS_DIRECTION):
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000533 """Wrapper utility that returns a test QoS bandwidth limit rule."""
534 body = cls.admin_client.create_bandwidth_limit_rule(
Sławek Kapłoński153f3452017-03-24 22:04:53 +0000535 policy_id, max_kbps, max_burst_kbps, direction)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000536 qos_rule = body['bandwidth_limit_rule']
537 cls.qos_rules.append(qos_rule)
538 return qos_rule
539
540 @classmethod
Jakub Libosvar83704832017-12-06 16:02:28 +0000541 def delete_router(cls, router, client=None):
542 client = client or cls.client
543 body = client.list_router_interfaces(router['id'])
Chandan Kumarc125fd12017-11-15 19:41:01 +0530544 interfaces = [port for port in body['ports']
545 if port['device_owner'] in const.ROUTER_INTERFACE_OWNERS]
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000546 for i in interfaces:
547 try:
Jakub Libosvar83704832017-12-06 16:02:28 +0000548 client.remove_router_interface_with_subnet_id(
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000549 router['id'], i['fixed_ips'][0]['subnet_id'])
550 except lib_exc.NotFound:
551 pass
Jakub Libosvar83704832017-12-06 16:02:28 +0000552 client.delete_router(router['id'])
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000553
554 @classmethod
555 def create_address_scope(cls, name, is_admin=False, **kwargs):
556 if is_admin:
557 body = cls.admin_client.create_address_scope(name=name, **kwargs)
558 cls.admin_address_scopes.append(body['address_scope'])
559 else:
560 body = cls.client.create_address_scope(name=name, **kwargs)
561 cls.address_scopes.append(body['address_scope'])
562 return body['address_scope']
563
564 @classmethod
565 def create_subnetpool(cls, name, is_admin=False, **kwargs):
566 if is_admin:
567 body = cls.admin_client.create_subnetpool(name, **kwargs)
568 cls.admin_subnetpools.append(body['subnetpool'])
569 else:
570 body = cls.client.create_subnetpool(name, **kwargs)
571 cls.subnetpools.append(body['subnetpool'])
572 return body['subnetpool']
573
Chandan Kumarc125fd12017-11-15 19:41:01 +0530574 @classmethod
575 def create_project(cls, name=None, description=None):
576 test_project = name or data_utils.rand_name('test_project_')
577 test_description = description or data_utils.rand_name('desc_')
578 project = cls.identity_admin_client.create_project(
579 name=test_project,
580 description=test_description)['project']
581 cls.projects.append(project)
582 return project
583
584 @classmethod
585 def create_security_group(cls, name, **kwargs):
586 body = cls.client.create_security_group(name=name, **kwargs)
587 cls.security_groups.append(body['security_group'])
588 return body['security_group']
589
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000590
591class BaseAdminNetworkTest(BaseNetworkTest):
592
593 credentials = ['primary', 'admin']
594
595 @classmethod
596 def setup_clients(cls):
597 super(BaseAdminNetworkTest, cls).setup_clients()
fumihiko kakumaa216fc12017-07-14 10:43:29 +0900598 cls.admin_client = cls.os_admin.network_client
Jakub Libosvarf5758012017-08-15 13:45:30 +0000599 cls.identity_admin_client = cls.os_admin.projects_client
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000600
601 @classmethod
602 def create_metering_label(cls, name, description):
603 """Wrapper utility that returns a test metering label."""
604 body = cls.admin_client.create_metering_label(
605 description=description,
606 name=data_utils.rand_name("metering-label"))
607 metering_label = body['metering_label']
608 cls.metering_labels.append(metering_label)
609 return metering_label
610
611 @classmethod
612 def create_metering_label_rule(cls, remote_ip_prefix, direction,
613 metering_label_id):
614 """Wrapper utility that returns a test metering label rule."""
615 body = cls.admin_client.create_metering_label_rule(
616 remote_ip_prefix=remote_ip_prefix, direction=direction,
617 metering_label_id=metering_label_id)
618 metering_label_rule = body['metering_label_rule']
619 cls.metering_label_rules.append(metering_label_rule)
620 return metering_label_rule
621
622 @classmethod
623 def create_flavor(cls, name, description, service_type):
624 """Wrapper utility that returns a test flavor."""
625 body = cls.admin_client.create_flavor(
626 description=description, service_type=service_type,
627 name=name)
628 flavor = body['flavor']
629 cls.flavors.append(flavor)
630 return flavor
631
632 @classmethod
633 def create_service_profile(cls, description, metainfo, driver):
634 """Wrapper utility that returns a test service profile."""
635 body = cls.admin_client.create_service_profile(
636 driver=driver, metainfo=metainfo, description=description)
637 service_profile = body['service_profile']
638 cls.service_profiles.append(service_profile)
639 return service_profile
640
641 @classmethod
Nguyen Phuong An67993fc2017-11-24 11:30:25 +0700642 def create_log(cls, name, description=None,
643 resource_type='security_group', resource_id=None,
644 target_id=None, event='ALL', enabled=True):
645 """Wrapper utility that returns a test log object."""
646 log_args = {'name': name,
647 'description': description,
648 'resource_type': resource_type,
649 'resource_id': resource_id,
650 'target_id': target_id,
651 'event': event,
652 'enabled': enabled}
653 body = cls.admin_client.create_log(**log_args)
654 log_object = body['log']
655 cls.log_objects.append(log_object)
656 return log_object
657
658 @classmethod
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000659 def get_unused_ip(cls, net_id, ip_version=None):
Gary Kotton011345f2016-06-15 08:04:31 -0700660 """Get an unused ip address in a allocation pool of net"""
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000661 body = cls.admin_client.list_ports(network_id=net_id)
662 ports = body['ports']
663 used_ips = []
664 for port in ports:
665 used_ips.extend(
666 [fixed_ip['ip_address'] for fixed_ip in port['fixed_ips']])
667 body = cls.admin_client.list_subnets(network_id=net_id)
668 subnets = body['subnets']
669
670 for subnet in subnets:
671 if ip_version and subnet['ip_version'] != ip_version:
672 continue
673 cidr = subnet['cidr']
674 allocation_pools = subnet['allocation_pools']
675 iterators = []
676 if allocation_pools:
677 for allocation_pool in allocation_pools:
678 iterators.append(netaddr.iter_iprange(
679 allocation_pool['start'], allocation_pool['end']))
680 else:
681 net = netaddr.IPNetwork(cidr)
682
683 def _iterip():
684 for ip in net:
685 if ip not in (net.network, net.broadcast):
686 yield ip
687 iterators.append(iter(_iterip()))
688
689 for iterator in iterators:
690 for ip in iterator:
691 if str(ip) not in used_ips:
692 return str(ip)
693
694 message = (
695 "net(%s) has no usable IP address in allocation pools" % net_id)
696 raise exceptions.InvalidConfiguration(message)
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200697
698
Sławek Kapłońskiff294062016-12-04 15:00:54 +0000699def require_qos_rule_type(rule_type):
700 def decorator(f):
701 @functools.wraps(f)
702 def wrapper(self, *func_args, **func_kwargs):
703 if rule_type not in self.get_supported_qos_rule_types():
704 raise self.skipException(
705 "%s rule type is required." % rule_type)
706 return f(self, *func_args, **func_kwargs)
707 return wrapper
708 return decorator
709
710
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200711def _require_sorting(f):
712 @functools.wraps(f)
713 def inner(self, *args, **kwargs):
Chandan Kumarc125fd12017-11-15 19:41:01 +0530714 if not tutils.is_extension_enabled("sorting", "network"):
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200715 self.skipTest('Sorting feature is required')
716 return f(self, *args, **kwargs)
717 return inner
718
719
720def _require_pagination(f):
721 @functools.wraps(f)
722 def inner(self, *args, **kwargs):
Chandan Kumarc125fd12017-11-15 19:41:01 +0530723 if not tutils.is_extension_enabled("pagination", "network"):
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200724 self.skipTest('Pagination feature is required')
725 return f(self, *args, **kwargs)
726 return inner
727
728
729class BaseSearchCriteriaTest(BaseNetworkTest):
730
731 # This should be defined by subclasses to reflect resource name to test
732 resource = None
733
Armando Migliaccio57581c62016-07-01 10:13:19 -0700734 field = 'name'
735
Ihar Hrachyshkaa8fe5a12016-05-24 14:50:58 +0200736 # NOTE(ihrachys): some names, like those starting with an underscore (_)
737 # are sorted differently depending on whether the plugin implements native
738 # sorting support, or not. So we avoid any such cases here, sticking to
739 # alphanumeric. Also test a case when there are multiple resources with the
740 # same name
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200741 resource_names = ('test1', 'abc1', 'test10', '123test') + ('test1',)
742
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200743 force_tenant_isolation = True
744
Ihar Hrachyshkaa8fe5a12016-05-24 14:50:58 +0200745 list_kwargs = {}
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200746
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200747 list_as_admin = False
748
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200749 def assertSameOrder(self, original, actual):
750 # gracefully handle iterators passed
751 original = list(original)
752 actual = list(actual)
753 self.assertEqual(len(original), len(actual))
754 for expected, res in zip(original, actual):
Armando Migliaccio57581c62016-07-01 10:13:19 -0700755 self.assertEqual(expected[self.field], res[self.field])
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200756
757 @utils.classproperty
758 def plural_name(self):
759 return '%ss' % self.resource
760
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200761 @property
762 def list_client(self):
763 return self.admin_client if self.list_as_admin else self.client
764
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200765 def list_method(self, *args, **kwargs):
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200766 method = getattr(self.list_client, 'list_%s' % self.plural_name)
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200767 kwargs.update(self.list_kwargs)
768 return method(*args, **kwargs)
769
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200770 def get_bare_url(self, url):
771 base_url = self.client.base_url
772 self.assertTrue(url.startswith(base_url))
773 return url[len(base_url):]
774
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200775 @classmethod
776 def _extract_resources(cls, body):
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200777 return body[cls.plural_name]
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200778
779 def _test_list_sorts(self, direction):
780 sort_args = {
781 'sort_dir': direction,
Armando Migliaccio57581c62016-07-01 10:13:19 -0700782 'sort_key': self.field
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200783 }
784 body = self.list_method(**sort_args)
785 resources = self._extract_resources(body)
786 self.assertNotEmpty(
787 resources, "%s list returned is empty" % self.resource)
Armando Migliaccio57581c62016-07-01 10:13:19 -0700788 retrieved_names = [res[self.field] for res in resources]
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200789 expected = sorted(retrieved_names)
790 if direction == constants.SORT_DIRECTION_DESC:
791 expected = list(reversed(expected))
792 self.assertEqual(expected, retrieved_names)
793
794 @_require_sorting
795 def _test_list_sorts_asc(self):
796 self._test_list_sorts(constants.SORT_DIRECTION_ASC)
797
798 @_require_sorting
799 def _test_list_sorts_desc(self):
800 self._test_list_sorts(constants.SORT_DIRECTION_DESC)
801
802 @_require_pagination
803 def _test_list_pagination(self):
804 for limit in range(1, len(self.resource_names) + 1):
805 pagination_args = {
806 'limit': limit,
807 }
808 body = self.list_method(**pagination_args)
809 resources = self._extract_resources(body)
810 self.assertEqual(limit, len(resources))
811
812 @_require_pagination
813 def _test_list_no_pagination_limit_0(self):
814 pagination_args = {
815 'limit': 0,
816 }
817 body = self.list_method(**pagination_args)
818 resources = self._extract_resources(body)
Béla Vancsicsf1806182016-08-23 07:36:18 +0200819 self.assertGreaterEqual(len(resources), len(self.resource_names))
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200820
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200821 def _test_list_pagination_iteratively(self, lister):
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200822 # first, collect all resources for later comparison
823 sort_args = {
824 'sort_dir': constants.SORT_DIRECTION_ASC,
Armando Migliaccio57581c62016-07-01 10:13:19 -0700825 'sort_key': self.field
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200826 }
827 body = self.list_method(**sort_args)
828 expected_resources = self._extract_resources(body)
829 self.assertNotEmpty(expected_resources)
830
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200831 resources = lister(
832 len(expected_resources), sort_args
833 )
834
835 # finally, compare that the list retrieved in one go is identical to
836 # the one containing pagination results
837 self.assertSameOrder(expected_resources, resources)
838
839 def _list_all_with_marker(self, niterations, sort_args):
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200840 # paginate resources one by one, using last fetched resource as a
841 # marker
842 resources = []
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200843 for i in range(niterations):
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200844 pagination_args = sort_args.copy()
845 pagination_args['limit'] = 1
846 if resources:
847 pagination_args['marker'] = resources[-1]['id']
848 body = self.list_method(**pagination_args)
849 resources_ = self._extract_resources(body)
850 self.assertEqual(1, len(resources_))
851 resources.extend(resources_)
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200852 return resources
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200853
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200854 @_require_pagination
855 @_require_sorting
856 def _test_list_pagination_with_marker(self):
857 self._test_list_pagination_iteratively(self._list_all_with_marker)
858
859 def _list_all_with_hrefs(self, niterations, sort_args):
860 # paginate resources one by one, using next href links
861 resources = []
862 prev_links = {}
863
864 for i in range(niterations):
865 if prev_links:
866 uri = self.get_bare_url(prev_links['next'])
867 else:
Ihar Hrachyshka7f79fe62016-06-07 21:23:44 +0200868 sort_args.update(self.list_kwargs)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200869 uri = self.list_client.build_uri(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200870 self.plural_name, limit=1, **sort_args)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200871 prev_links, body = self.list_client.get_uri_with_links(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200872 self.plural_name, uri
873 )
874 resources_ = self._extract_resources(body)
875 self.assertEqual(1, len(resources_))
876 resources.extend(resources_)
877
878 # The last element is empty and does not contain 'next' link
879 uri = self.get_bare_url(prev_links['next'])
880 prev_links, body = self.client.get_uri_with_links(
881 self.plural_name, uri
882 )
883 self.assertNotIn('next', prev_links)
884
885 # Now walk backwards and compare results
886 resources2 = []
887 for i in range(niterations):
888 uri = self.get_bare_url(prev_links['previous'])
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200889 prev_links, body = self.list_client.get_uri_with_links(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200890 self.plural_name, uri
891 )
892 resources_ = self._extract_resources(body)
893 self.assertEqual(1, len(resources_))
894 resources2.extend(resources_)
895
896 self.assertSameOrder(resources, reversed(resources2))
897
898 return resources
899
900 @_require_pagination
901 @_require_sorting
902 def _test_list_pagination_with_href_links(self):
903 self._test_list_pagination_iteratively(self._list_all_with_hrefs)
904
905 @_require_pagination
906 @_require_sorting
907 def _test_list_pagination_page_reverse_with_href_links(
908 self, direction=constants.SORT_DIRECTION_ASC):
909 pagination_args = {
910 'sort_dir': direction,
Armando Migliaccio57581c62016-07-01 10:13:19 -0700911 'sort_key': self.field,
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200912 }
913 body = self.list_method(**pagination_args)
914 expected_resources = self._extract_resources(body)
915
916 page_size = 2
917 pagination_args['limit'] = page_size
918
919 prev_links = {}
920 resources = []
921 num_resources = len(expected_resources)
922 niterations = int(math.ceil(float(num_resources) / page_size))
923 for i in range(niterations):
924 if prev_links:
925 uri = self.get_bare_url(prev_links['previous'])
926 else:
Ihar Hrachyshka7f79fe62016-06-07 21:23:44 +0200927 pagination_args.update(self.list_kwargs)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200928 uri = self.list_client.build_uri(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200929 self.plural_name, page_reverse=True, **pagination_args)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200930 prev_links, body = self.list_client.get_uri_with_links(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200931 self.plural_name, uri
932 )
933 resources_ = self._extract_resources(body)
Béla Vancsicsf1806182016-08-23 07:36:18 +0200934 self.assertGreaterEqual(page_size, len(resources_))
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200935 resources.extend(reversed(resources_))
936
937 self.assertSameOrder(expected_resources, reversed(resources))
938
939 @_require_pagination
940 @_require_sorting
941 def _test_list_pagination_page_reverse_asc(self):
942 self._test_list_pagination_page_reverse(
943 direction=constants.SORT_DIRECTION_ASC)
944
945 @_require_pagination
946 @_require_sorting
947 def _test_list_pagination_page_reverse_desc(self):
948 self._test_list_pagination_page_reverse(
949 direction=constants.SORT_DIRECTION_DESC)
950
951 def _test_list_pagination_page_reverse(self, direction):
952 pagination_args = {
953 'sort_dir': direction,
Armando Migliaccio57581c62016-07-01 10:13:19 -0700954 'sort_key': self.field,
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200955 'limit': 3,
956 }
957 body = self.list_method(**pagination_args)
958 expected_resources = self._extract_resources(body)
959
960 pagination_args['limit'] -= 1
961 pagination_args['marker'] = expected_resources[-1]['id']
962 pagination_args['page_reverse'] = True
963 body = self.list_method(**pagination_args)
964
965 self.assertSameOrder(
966 # the last entry is not included in 2nd result when used as a
967 # marker
968 expected_resources[:-1],
969 self._extract_resources(body))
Victor Morales1be97b42016-09-05 08:50:06 -0500970
971 def _test_list_validation_filters(self):
972 validation_args = {
973 'unknown_filter': 'value',
974 }
975 body = self.list_method(**validation_args)
976 resources = self._extract_resources(body)
977 for resource in resources:
978 self.assertIn(resource['name'], self.resource_names)