blob: fdd8ba9b0add0ab78ad502da175d21a73b8d76e5 [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
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200287 def create_subnet(cls, network, gateway=None, 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
301
302 :param cidr: CIDR of the subnet to create
303 It can be either None, a str or a netaddr.IPNetwork instance
304
305 :param mask_bits: CIDR prefix length
306 It can be either None or a numeric value.
307 If cidr parameter is given then mask_bits is used to determinate a
308 sequence of valid CIDR to use as generated.
309 Please see netaddr.IPNetwork.subnet method documentation[1]
310
311 :param ip_version: ip version of generated subnet CIDRs
312 It can be None, IP_VERSION_4 or IP_VERSION_6
313 It has to match given either given CIDR and gateway
314
315 :param ip_version: numeric value (either IP_VERSION_4 or IP_VERSION_6)
316 this value must match CIDR and gateway IP versions if any of them is
317 given
318
319 :param client: client to be used to connect to network service
320
321 :param **kwargs: optional parameters to be forwarded to wrapped method
322
323 [1] http://netaddr.readthedocs.io/en/latest/tutorial_01.html#supernets-and-subnets # noqa
324 """
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000325
326 # allow tests to use admin client
327 if not client:
328 client = cls.client
329
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200330 if gateway:
331 gateway_ip = netaddr.IPAddress(gateway)
332 if ip_version:
333 if ip_version != gateway_ip.version:
334 raise ValueError(
335 "Gateway IP version doesn't match IP version")
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000336 else:
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200337 ip_version = gateway_ip.version
338
339 for subnet_cidr in cls.get_subnet_cidrs(
340 ip_version=ip_version, cidr=cidr, mask_bits=mask_bits):
341 if cls.try_reserve_subnet_cidr(subnet_cidr):
342 gateway_ip = gateway or str(subnet_cidr.ip + 1)
343 try:
344 body = client.create_subnet(
345 network_id=network['id'],
346 cidr=str(subnet_cidr),
347 ip_version=subnet_cidr.version,
348 gateway_ip=str(gateway_ip),
349 **kwargs)
350 break
351 except lib_exc.BadRequest as e:
352 if 'overlaps with another subnet' not in str(e):
353 raise
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000354 else:
355 message = 'Available CIDR for subnet creation could not be found'
356 raise ValueError(message)
357 subnet = body['subnet']
Kevin Bentonba3651c2017-09-01 17:13:01 -0700358 if client is cls.client:
359 cls.subnets.append(subnet)
360 else:
361 cls.admin_subnets.append(subnet)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000362 return subnet
363
364 @classmethod
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200365 def reserve_subnet_cidr(cls, addr, **ipnetwork_kwargs):
366 """Reserve given subnet CIDR making sure it is not used by create_subnet
367
368 :param addr: the CIDR address to be reserved
369 It can be a str or netaddr.IPNetwork instance
370
371 :param **ipnetwork_kwargs: optional netaddr.IPNetwork constructor
372 parameters
373 """
374
375 if not cls.try_reserve_subnet_cidr(addr, **ipnetwork_kwargs):
376 raise ValueError('Subnet CIDR already reserved: %r'.format(
377 addr))
378
379 @classmethod
380 def try_reserve_subnet_cidr(cls, addr, **ipnetwork_kwargs):
381 """Reserve given subnet CIDR if it hasn't been reserved before
382
383 :param addr: the CIDR address to be reserved
384 It can be a str or netaddr.IPNetwork instance
385
386 :param **ipnetwork_kwargs: optional netaddr.IPNetwork constructor
387 parameters
388
389 :return: True if it wasn't reserved before, False elsewhere.
390 """
391
392 subnet_cidr = netaddr.IPNetwork(addr, **ipnetwork_kwargs)
393 if subnet_cidr in cls.reserved_subnet_cidrs:
394 return False
395 else:
396 cls.reserved_subnet_cidrs.add(subnet_cidr)
397 return True
398
399 @classmethod
400 def get_subnet_cidrs(
401 cls, cidr=None, mask_bits=None, ip_version=None):
402 """Iterate over a sequence of unused subnet CIDR for IP version
403
404 :param cidr: CIDR of the subnet to create
405 It can be either None, a str or a netaddr.IPNetwork instance
406
407 :param mask_bits: CIDR prefix length
408 It can be either None or a numeric value.
409 If cidr parameter is given then mask_bits is used to determinate a
410 sequence of valid CIDR to use as generated.
411 Please see netaddr.IPNetwork.subnet method documentation[1]
412
413 :param ip_version: ip version of generated subnet CIDRs
414 It can be None, IP_VERSION_4 or IP_VERSION_6
415 It has to match given CIDR if given
416
417 :return: iterator over reserved CIDRs of type netaddr.IPNetwork
418
419 [1] http://netaddr.readthedocs.io/en/latest/tutorial_01.html#supernets-and-subnets # noqa
420 """
421
422 if cidr:
423 # Generate subnet CIDRs starting from given CIDR
424 # checking it is of requested IP version
425 cidr = netaddr.IPNetwork(cidr, version=ip_version)
426 else:
427 # Generate subnet CIDRs starting from configured values
428 ip_version = ip_version or cls._ip_version
429 if ip_version == const.IP_VERSION_4:
430 mask_bits = mask_bits or config.safe_get_config_value(
431 'network', 'project_network_mask_bits')
432 cidr = netaddr.IPNetwork(config.safe_get_config_value(
433 'network', 'project_network_cidr'))
434 elif ip_version == const.IP_VERSION_6:
435 mask_bits = config.safe_get_config_value(
436 'network', 'project_network_v6_mask_bits')
437 cidr = netaddr.IPNetwork(config.safe_get_config_value(
438 'network', 'project_network_v6_cidr'))
439 else:
440 raise ValueError('Invalid IP version: {!r}'.format(ip_version))
441
442 if mask_bits:
443 subnet_cidrs = cidr.subnet(mask_bits)
444 else:
445 subnet_cidrs = iter([cidr])
446
447 for subnet_cidr in subnet_cidrs:
448 if subnet_cidr not in cls.reserved_subnet_cidrs:
449 yield subnet_cidr
450
451 @classmethod
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000452 def create_port(cls, network, **kwargs):
453 """Wrapper utility that returns a test port."""
Edan Davidd75e48e2018-01-03 02:49:52 -0500454 if CONF.network.port_vnic_type and 'binding:vnic_type' not in kwargs:
455 kwargs['binding:vnic_type'] = CONF.network.port_vnic_type
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000456 body = cls.client.create_port(network_id=network['id'],
457 **kwargs)
458 port = body['port']
459 cls.ports.append(port)
460 return port
461
462 @classmethod
463 def update_port(cls, port, **kwargs):
464 """Wrapper utility that updates a test port."""
465 body = cls.client.update_port(port['id'],
466 **kwargs)
467 return body['port']
468
469 @classmethod
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300470 def _create_router_with_client(
471 cls, client, router_name=None, admin_state_up=False,
472 external_network_id=None, enable_snat=None, **kwargs
473 ):
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000474 ext_gw_info = {}
475 if external_network_id:
476 ext_gw_info['network_id'] = external_network_id
YAMAMOTO Takashi9bd4f972017-06-20 12:49:30 +0900477 if enable_snat is not None:
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000478 ext_gw_info['enable_snat'] = enable_snat
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300479 body = client.create_router(
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000480 router_name, external_gateway_info=ext_gw_info,
481 admin_state_up=admin_state_up, **kwargs)
482 router = body['router']
483 cls.routers.append(router)
484 return router
485
486 @classmethod
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300487 def create_router(cls, *args, **kwargs):
488 return cls._create_router_with_client(cls.client, *args, **kwargs)
489
490 @classmethod
491 def create_admin_router(cls, *args, **kwargs):
rajat294495c042017-06-28 15:37:16 +0530492 return cls._create_router_with_client(cls.os_admin.network_client,
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300493 *args, **kwargs)
494
495 @classmethod
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000496 def create_floatingip(cls, external_network_id):
497 """Wrapper utility that returns a test floating IP."""
498 body = cls.client.create_floatingip(
499 floating_network_id=external_network_id)
500 fip = body['floatingip']
501 cls.floating_ips.append(fip)
502 return fip
503
504 @classmethod
505 def create_router_interface(cls, router_id, subnet_id):
506 """Wrapper utility that returns a router interface."""
507 interface = cls.client.add_router_interface_with_subnet_id(
508 router_id, subnet_id)
509 return interface
510
511 @classmethod
Sławek Kapłońskiff294062016-12-04 15:00:54 +0000512 def get_supported_qos_rule_types(cls):
513 body = cls.client.list_qos_rule_types()
514 return [rule_type['type'] for rule_type in body['rule_types']]
515
516 @classmethod
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200517 def create_qos_policy(cls, name, description=None, shared=False,
Hirofumi Ichihara39a6ee12017-08-23 13:55:12 +0900518 tenant_id=None, is_default=False):
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000519 """Wrapper utility that returns a test QoS policy."""
520 body = cls.admin_client.create_qos_policy(
Hirofumi Ichihara39a6ee12017-08-23 13:55:12 +0900521 name, description, shared, tenant_id, is_default)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000522 qos_policy = body['policy']
523 cls.qos_policies.append(qos_policy)
524 return qos_policy
525
526 @classmethod
Sławek Kapłoński153f3452017-03-24 22:04:53 +0000527 def create_qos_bandwidth_limit_rule(cls, policy_id, max_kbps,
528 max_burst_kbps,
Chandan Kumarc125fd12017-11-15 19:41:01 +0530529 direction=const.EGRESS_DIRECTION):
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000530 """Wrapper utility that returns a test QoS bandwidth limit rule."""
531 body = cls.admin_client.create_bandwidth_limit_rule(
Sławek Kapłoński153f3452017-03-24 22:04:53 +0000532 policy_id, max_kbps, max_burst_kbps, direction)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000533 qos_rule = body['bandwidth_limit_rule']
534 cls.qos_rules.append(qos_rule)
535 return qos_rule
536
537 @classmethod
Jakub Libosvar83704832017-12-06 16:02:28 +0000538 def delete_router(cls, router, client=None):
539 client = client or cls.client
540 body = client.list_router_interfaces(router['id'])
Chandan Kumarc125fd12017-11-15 19:41:01 +0530541 interfaces = [port for port in body['ports']
542 if port['device_owner'] in const.ROUTER_INTERFACE_OWNERS]
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000543 for i in interfaces:
544 try:
Jakub Libosvar83704832017-12-06 16:02:28 +0000545 client.remove_router_interface_with_subnet_id(
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000546 router['id'], i['fixed_ips'][0]['subnet_id'])
547 except lib_exc.NotFound:
548 pass
Jakub Libosvar83704832017-12-06 16:02:28 +0000549 client.delete_router(router['id'])
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000550
551 @classmethod
552 def create_address_scope(cls, name, is_admin=False, **kwargs):
553 if is_admin:
554 body = cls.admin_client.create_address_scope(name=name, **kwargs)
555 cls.admin_address_scopes.append(body['address_scope'])
556 else:
557 body = cls.client.create_address_scope(name=name, **kwargs)
558 cls.address_scopes.append(body['address_scope'])
559 return body['address_scope']
560
561 @classmethod
562 def create_subnetpool(cls, name, is_admin=False, **kwargs):
563 if is_admin:
564 body = cls.admin_client.create_subnetpool(name, **kwargs)
565 cls.admin_subnetpools.append(body['subnetpool'])
566 else:
567 body = cls.client.create_subnetpool(name, **kwargs)
568 cls.subnetpools.append(body['subnetpool'])
569 return body['subnetpool']
570
Chandan Kumarc125fd12017-11-15 19:41:01 +0530571 @classmethod
572 def create_project(cls, name=None, description=None):
573 test_project = name or data_utils.rand_name('test_project_')
574 test_description = description or data_utils.rand_name('desc_')
575 project = cls.identity_admin_client.create_project(
576 name=test_project,
577 description=test_description)['project']
578 cls.projects.append(project)
579 return project
580
581 @classmethod
582 def create_security_group(cls, name, **kwargs):
583 body = cls.client.create_security_group(name=name, **kwargs)
584 cls.security_groups.append(body['security_group'])
585 return body['security_group']
586
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000587
588class BaseAdminNetworkTest(BaseNetworkTest):
589
590 credentials = ['primary', 'admin']
591
592 @classmethod
593 def setup_clients(cls):
594 super(BaseAdminNetworkTest, cls).setup_clients()
fumihiko kakumaa216fc12017-07-14 10:43:29 +0900595 cls.admin_client = cls.os_admin.network_client
Jakub Libosvarf5758012017-08-15 13:45:30 +0000596 cls.identity_admin_client = cls.os_admin.projects_client
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000597
598 @classmethod
599 def create_metering_label(cls, name, description):
600 """Wrapper utility that returns a test metering label."""
601 body = cls.admin_client.create_metering_label(
602 description=description,
603 name=data_utils.rand_name("metering-label"))
604 metering_label = body['metering_label']
605 cls.metering_labels.append(metering_label)
606 return metering_label
607
608 @classmethod
609 def create_metering_label_rule(cls, remote_ip_prefix, direction,
610 metering_label_id):
611 """Wrapper utility that returns a test metering label rule."""
612 body = cls.admin_client.create_metering_label_rule(
613 remote_ip_prefix=remote_ip_prefix, direction=direction,
614 metering_label_id=metering_label_id)
615 metering_label_rule = body['metering_label_rule']
616 cls.metering_label_rules.append(metering_label_rule)
617 return metering_label_rule
618
619 @classmethod
620 def create_flavor(cls, name, description, service_type):
621 """Wrapper utility that returns a test flavor."""
622 body = cls.admin_client.create_flavor(
623 description=description, service_type=service_type,
624 name=name)
625 flavor = body['flavor']
626 cls.flavors.append(flavor)
627 return flavor
628
629 @classmethod
630 def create_service_profile(cls, description, metainfo, driver):
631 """Wrapper utility that returns a test service profile."""
632 body = cls.admin_client.create_service_profile(
633 driver=driver, metainfo=metainfo, description=description)
634 service_profile = body['service_profile']
635 cls.service_profiles.append(service_profile)
636 return service_profile
637
638 @classmethod
Nguyen Phuong An67993fc2017-11-24 11:30:25 +0700639 def create_log(cls, name, description=None,
640 resource_type='security_group', resource_id=None,
641 target_id=None, event='ALL', enabled=True):
642 """Wrapper utility that returns a test log object."""
643 log_args = {'name': name,
644 'description': description,
645 'resource_type': resource_type,
646 'resource_id': resource_id,
647 'target_id': target_id,
648 'event': event,
649 'enabled': enabled}
650 body = cls.admin_client.create_log(**log_args)
651 log_object = body['log']
652 cls.log_objects.append(log_object)
653 return log_object
654
655 @classmethod
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000656 def get_unused_ip(cls, net_id, ip_version=None):
Gary Kotton011345f2016-06-15 08:04:31 -0700657 """Get an unused ip address in a allocation pool of net"""
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000658 body = cls.admin_client.list_ports(network_id=net_id)
659 ports = body['ports']
660 used_ips = []
661 for port in ports:
662 used_ips.extend(
663 [fixed_ip['ip_address'] for fixed_ip in port['fixed_ips']])
664 body = cls.admin_client.list_subnets(network_id=net_id)
665 subnets = body['subnets']
666
667 for subnet in subnets:
668 if ip_version and subnet['ip_version'] != ip_version:
669 continue
670 cidr = subnet['cidr']
671 allocation_pools = subnet['allocation_pools']
672 iterators = []
673 if allocation_pools:
674 for allocation_pool in allocation_pools:
675 iterators.append(netaddr.iter_iprange(
676 allocation_pool['start'], allocation_pool['end']))
677 else:
678 net = netaddr.IPNetwork(cidr)
679
680 def _iterip():
681 for ip in net:
682 if ip not in (net.network, net.broadcast):
683 yield ip
684 iterators.append(iter(_iterip()))
685
686 for iterator in iterators:
687 for ip in iterator:
688 if str(ip) not in used_ips:
689 return str(ip)
690
691 message = (
692 "net(%s) has no usable IP address in allocation pools" % net_id)
693 raise exceptions.InvalidConfiguration(message)
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200694
695
Sławek Kapłońskiff294062016-12-04 15:00:54 +0000696def require_qos_rule_type(rule_type):
697 def decorator(f):
698 @functools.wraps(f)
699 def wrapper(self, *func_args, **func_kwargs):
700 if rule_type not in self.get_supported_qos_rule_types():
701 raise self.skipException(
702 "%s rule type is required." % rule_type)
703 return f(self, *func_args, **func_kwargs)
704 return wrapper
705 return decorator
706
707
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200708def _require_sorting(f):
709 @functools.wraps(f)
710 def inner(self, *args, **kwargs):
Chandan Kumarc125fd12017-11-15 19:41:01 +0530711 if not tutils.is_extension_enabled("sorting", "network"):
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200712 self.skipTest('Sorting feature is required')
713 return f(self, *args, **kwargs)
714 return inner
715
716
717def _require_pagination(f):
718 @functools.wraps(f)
719 def inner(self, *args, **kwargs):
Chandan Kumarc125fd12017-11-15 19:41:01 +0530720 if not tutils.is_extension_enabled("pagination", "network"):
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200721 self.skipTest('Pagination feature is required')
722 return f(self, *args, **kwargs)
723 return inner
724
725
726class BaseSearchCriteriaTest(BaseNetworkTest):
727
728 # This should be defined by subclasses to reflect resource name to test
729 resource = None
730
Armando Migliaccio57581c62016-07-01 10:13:19 -0700731 field = 'name'
732
Ihar Hrachyshkaa8fe5a12016-05-24 14:50:58 +0200733 # NOTE(ihrachys): some names, like those starting with an underscore (_)
734 # are sorted differently depending on whether the plugin implements native
735 # sorting support, or not. So we avoid any such cases here, sticking to
736 # alphanumeric. Also test a case when there are multiple resources with the
737 # same name
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200738 resource_names = ('test1', 'abc1', 'test10', '123test') + ('test1',)
739
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200740 force_tenant_isolation = True
741
Ihar Hrachyshkaa8fe5a12016-05-24 14:50:58 +0200742 list_kwargs = {}
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200743
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200744 list_as_admin = False
745
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200746 def assertSameOrder(self, original, actual):
747 # gracefully handle iterators passed
748 original = list(original)
749 actual = list(actual)
750 self.assertEqual(len(original), len(actual))
751 for expected, res in zip(original, actual):
Armando Migliaccio57581c62016-07-01 10:13:19 -0700752 self.assertEqual(expected[self.field], res[self.field])
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200753
754 @utils.classproperty
755 def plural_name(self):
756 return '%ss' % self.resource
757
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200758 @property
759 def list_client(self):
760 return self.admin_client if self.list_as_admin else self.client
761
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200762 def list_method(self, *args, **kwargs):
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200763 method = getattr(self.list_client, 'list_%s' % self.plural_name)
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200764 kwargs.update(self.list_kwargs)
765 return method(*args, **kwargs)
766
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200767 def get_bare_url(self, url):
768 base_url = self.client.base_url
769 self.assertTrue(url.startswith(base_url))
770 return url[len(base_url):]
771
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200772 @classmethod
773 def _extract_resources(cls, body):
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200774 return body[cls.plural_name]
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200775
776 def _test_list_sorts(self, direction):
777 sort_args = {
778 'sort_dir': direction,
Armando Migliaccio57581c62016-07-01 10:13:19 -0700779 'sort_key': self.field
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200780 }
781 body = self.list_method(**sort_args)
782 resources = self._extract_resources(body)
783 self.assertNotEmpty(
784 resources, "%s list returned is empty" % self.resource)
Armando Migliaccio57581c62016-07-01 10:13:19 -0700785 retrieved_names = [res[self.field] for res in resources]
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200786 expected = sorted(retrieved_names)
787 if direction == constants.SORT_DIRECTION_DESC:
788 expected = list(reversed(expected))
789 self.assertEqual(expected, retrieved_names)
790
791 @_require_sorting
792 def _test_list_sorts_asc(self):
793 self._test_list_sorts(constants.SORT_DIRECTION_ASC)
794
795 @_require_sorting
796 def _test_list_sorts_desc(self):
797 self._test_list_sorts(constants.SORT_DIRECTION_DESC)
798
799 @_require_pagination
800 def _test_list_pagination(self):
801 for limit in range(1, len(self.resource_names) + 1):
802 pagination_args = {
803 'limit': limit,
804 }
805 body = self.list_method(**pagination_args)
806 resources = self._extract_resources(body)
807 self.assertEqual(limit, len(resources))
808
809 @_require_pagination
810 def _test_list_no_pagination_limit_0(self):
811 pagination_args = {
812 'limit': 0,
813 }
814 body = self.list_method(**pagination_args)
815 resources = self._extract_resources(body)
Béla Vancsicsf1806182016-08-23 07:36:18 +0200816 self.assertGreaterEqual(len(resources), len(self.resource_names))
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200817
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200818 def _test_list_pagination_iteratively(self, lister):
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200819 # first, collect all resources for later comparison
820 sort_args = {
821 'sort_dir': constants.SORT_DIRECTION_ASC,
Armando Migliaccio57581c62016-07-01 10:13:19 -0700822 'sort_key': self.field
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200823 }
824 body = self.list_method(**sort_args)
825 expected_resources = self._extract_resources(body)
826 self.assertNotEmpty(expected_resources)
827
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200828 resources = lister(
829 len(expected_resources), sort_args
830 )
831
832 # finally, compare that the list retrieved in one go is identical to
833 # the one containing pagination results
834 self.assertSameOrder(expected_resources, resources)
835
836 def _list_all_with_marker(self, niterations, sort_args):
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200837 # paginate resources one by one, using last fetched resource as a
838 # marker
839 resources = []
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200840 for i in range(niterations):
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200841 pagination_args = sort_args.copy()
842 pagination_args['limit'] = 1
843 if resources:
844 pagination_args['marker'] = resources[-1]['id']
845 body = self.list_method(**pagination_args)
846 resources_ = self._extract_resources(body)
847 self.assertEqual(1, len(resources_))
848 resources.extend(resources_)
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200849 return resources
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200850
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200851 @_require_pagination
852 @_require_sorting
853 def _test_list_pagination_with_marker(self):
854 self._test_list_pagination_iteratively(self._list_all_with_marker)
855
856 def _list_all_with_hrefs(self, niterations, sort_args):
857 # paginate resources one by one, using next href links
858 resources = []
859 prev_links = {}
860
861 for i in range(niterations):
862 if prev_links:
863 uri = self.get_bare_url(prev_links['next'])
864 else:
Ihar Hrachyshka7f79fe62016-06-07 21:23:44 +0200865 sort_args.update(self.list_kwargs)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200866 uri = self.list_client.build_uri(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200867 self.plural_name, limit=1, **sort_args)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200868 prev_links, body = self.list_client.get_uri_with_links(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200869 self.plural_name, uri
870 )
871 resources_ = self._extract_resources(body)
872 self.assertEqual(1, len(resources_))
873 resources.extend(resources_)
874
875 # The last element is empty and does not contain 'next' link
876 uri = self.get_bare_url(prev_links['next'])
877 prev_links, body = self.client.get_uri_with_links(
878 self.plural_name, uri
879 )
880 self.assertNotIn('next', prev_links)
881
882 # Now walk backwards and compare results
883 resources2 = []
884 for i in range(niterations):
885 uri = self.get_bare_url(prev_links['previous'])
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200886 prev_links, body = self.list_client.get_uri_with_links(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200887 self.plural_name, uri
888 )
889 resources_ = self._extract_resources(body)
890 self.assertEqual(1, len(resources_))
891 resources2.extend(resources_)
892
893 self.assertSameOrder(resources, reversed(resources2))
894
895 return resources
896
897 @_require_pagination
898 @_require_sorting
899 def _test_list_pagination_with_href_links(self):
900 self._test_list_pagination_iteratively(self._list_all_with_hrefs)
901
902 @_require_pagination
903 @_require_sorting
904 def _test_list_pagination_page_reverse_with_href_links(
905 self, direction=constants.SORT_DIRECTION_ASC):
906 pagination_args = {
907 'sort_dir': direction,
Armando Migliaccio57581c62016-07-01 10:13:19 -0700908 'sort_key': self.field,
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200909 }
910 body = self.list_method(**pagination_args)
911 expected_resources = self._extract_resources(body)
912
913 page_size = 2
914 pagination_args['limit'] = page_size
915
916 prev_links = {}
917 resources = []
918 num_resources = len(expected_resources)
919 niterations = int(math.ceil(float(num_resources) / page_size))
920 for i in range(niterations):
921 if prev_links:
922 uri = self.get_bare_url(prev_links['previous'])
923 else:
Ihar Hrachyshka7f79fe62016-06-07 21:23:44 +0200924 pagination_args.update(self.list_kwargs)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200925 uri = self.list_client.build_uri(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200926 self.plural_name, page_reverse=True, **pagination_args)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200927 prev_links, body = self.list_client.get_uri_with_links(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200928 self.plural_name, uri
929 )
930 resources_ = self._extract_resources(body)
Béla Vancsicsf1806182016-08-23 07:36:18 +0200931 self.assertGreaterEqual(page_size, len(resources_))
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200932 resources.extend(reversed(resources_))
933
934 self.assertSameOrder(expected_resources, reversed(resources))
935
936 @_require_pagination
937 @_require_sorting
938 def _test_list_pagination_page_reverse_asc(self):
939 self._test_list_pagination_page_reverse(
940 direction=constants.SORT_DIRECTION_ASC)
941
942 @_require_pagination
943 @_require_sorting
944 def _test_list_pagination_page_reverse_desc(self):
945 self._test_list_pagination_page_reverse(
946 direction=constants.SORT_DIRECTION_DESC)
947
948 def _test_list_pagination_page_reverse(self, direction):
949 pagination_args = {
950 'sort_dir': direction,
Armando Migliaccio57581c62016-07-01 10:13:19 -0700951 'sort_key': self.field,
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200952 'limit': 3,
953 }
954 body = self.list_method(**pagination_args)
955 expected_resources = self._extract_resources(body)
956
957 pagination_args['limit'] -= 1
958 pagination_args['marker'] = expected_resources[-1]['id']
959 pagination_args['page_reverse'] = True
960 body = self.list_method(**pagination_args)
961
962 self.assertSameOrder(
963 # the last entry is not included in 2nd result when used as a
964 # marker
965 expected_resources[:-1],
966 self._extract_resources(body))
Victor Morales1be97b42016-09-05 08:50:06 -0500967
968 def _test_list_validation_filters(self):
969 validation_args = {
970 'unknown_filter': 'value',
971 }
972 body = self.list_method(**validation_args)
973 resources = self._extract_resources(body)
974 for resource in resources:
975 self.assertIn(resource['name'], self.resource_names)