blob: 6246eb71e8ddc9dbc3bfe84563294f7255456c75 [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,
Federico Ressi98f20ec2018-05-11 06:09:49 +0200288 ip_version=None, client=None, reserve_cidr=True,
289 **kwargs):
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200290 """Wrapper utility that returns a test subnet.
291
292 Convenient wrapper for client.create_subnet method. It reserves and
293 allocates CIDRs to avoid creating overlapping subnets.
294
295 :param network: network where to create the subnet
296 network['id'] must contain the ID of the network
297
298 :param gateway: gateway IP address
299 It can be a str or a netaddr.IPAddress
300 If gateway is not given, then it will use default address for
301 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 +0200302 if gateway is given as None then no gateway will be assigned
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200303
304 :param cidr: CIDR of the subnet to create
305 It can be either None, a str or a netaddr.IPNetwork instance
306
307 :param mask_bits: CIDR prefix length
308 It can be either None or a numeric value.
309 If cidr parameter is given then mask_bits is used to determinate a
310 sequence of valid CIDR to use as generated.
311 Please see netaddr.IPNetwork.subnet method documentation[1]
312
313 :param ip_version: ip version of generated subnet CIDRs
314 It can be None, IP_VERSION_4 or IP_VERSION_6
315 It has to match given either given CIDR and gateway
316
317 :param ip_version: numeric value (either IP_VERSION_4 or IP_VERSION_6)
318 this value must match CIDR and gateway IP versions if any of them is
319 given
320
321 :param client: client to be used to connect to network service
322
Federico Ressi98f20ec2018-05-11 06:09:49 +0200323 :param reserve_cidr: if True then it reserves assigned CIDR to avoid
324 using the same CIDR for further subnets in the scope of the same
325 test case class
326
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200327 :param **kwargs: optional parameters to be forwarded to wrapped method
328
329 [1] http://netaddr.readthedocs.io/en/latest/tutorial_01.html#supernets-and-subnets # noqa
330 """
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000331
332 # allow tests to use admin client
333 if not client:
334 client = cls.client
335
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200336 if gateway:
337 gateway_ip = netaddr.IPAddress(gateway)
338 if ip_version:
339 if ip_version != gateway_ip.version:
340 raise ValueError(
341 "Gateway IP version doesn't match IP version")
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000342 else:
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200343 ip_version = gateway_ip.version
Sławek Kapłońskid98e27d2018-05-07 16:16:28 +0200344 else:
345 ip_version = ip_version or cls._ip_version
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200346
347 for subnet_cidr in cls.get_subnet_cidrs(
348 ip_version=ip_version, cidr=cidr, mask_bits=mask_bits):
Federico Ressi98f20ec2018-05-11 06:09:49 +0200349 if gateway is not None:
350 kwargs['gateway_ip'] = str(gateway or (subnet_cidr.ip + 1))
351 try:
352 body = client.create_subnet(
353 network_id=network['id'],
354 cidr=str(subnet_cidr),
355 ip_version=subnet_cidr.version,
356 **kwargs)
357 break
358 except lib_exc.BadRequest as e:
359 if 'overlaps with another subnet' not in str(e):
360 raise
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000361 else:
362 message = 'Available CIDR for subnet creation could not be found'
363 raise ValueError(message)
364 subnet = body['subnet']
Kevin Bentonba3651c2017-09-01 17:13:01 -0700365 if client is cls.client:
366 cls.subnets.append(subnet)
367 else:
368 cls.admin_subnets.append(subnet)
Federico Ressi98f20ec2018-05-11 06:09:49 +0200369 if reserve_cidr:
370 cls.reserve_subnet_cidr(subnet_cidr)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000371 return subnet
372
373 @classmethod
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200374 def reserve_subnet_cidr(cls, addr, **ipnetwork_kwargs):
375 """Reserve given subnet CIDR making sure it is not used by create_subnet
376
377 :param addr: the CIDR address to be reserved
378 It can be a str or netaddr.IPNetwork instance
379
380 :param **ipnetwork_kwargs: optional netaddr.IPNetwork constructor
381 parameters
382 """
383
384 if not cls.try_reserve_subnet_cidr(addr, **ipnetwork_kwargs):
385 raise ValueError('Subnet CIDR already reserved: %r'.format(
386 addr))
387
388 @classmethod
389 def try_reserve_subnet_cidr(cls, addr, **ipnetwork_kwargs):
390 """Reserve given subnet CIDR if it hasn't been reserved before
391
392 :param addr: the CIDR address to be reserved
393 It can be a str or netaddr.IPNetwork instance
394
395 :param **ipnetwork_kwargs: optional netaddr.IPNetwork constructor
396 parameters
397
398 :return: True if it wasn't reserved before, False elsewhere.
399 """
400
401 subnet_cidr = netaddr.IPNetwork(addr, **ipnetwork_kwargs)
402 if subnet_cidr in cls.reserved_subnet_cidrs:
403 return False
404 else:
405 cls.reserved_subnet_cidrs.add(subnet_cidr)
406 return True
407
408 @classmethod
409 def get_subnet_cidrs(
410 cls, cidr=None, mask_bits=None, ip_version=None):
411 """Iterate over a sequence of unused subnet CIDR for IP version
412
413 :param cidr: CIDR of the subnet to create
414 It can be either None, a str or a netaddr.IPNetwork instance
415
416 :param mask_bits: CIDR prefix length
417 It can be either None or a numeric value.
418 If cidr parameter is given then mask_bits is used to determinate a
419 sequence of valid CIDR to use as generated.
420 Please see netaddr.IPNetwork.subnet method documentation[1]
421
422 :param ip_version: ip version of generated subnet CIDRs
423 It can be None, IP_VERSION_4 or IP_VERSION_6
424 It has to match given CIDR if given
425
426 :return: iterator over reserved CIDRs of type netaddr.IPNetwork
427
428 [1] http://netaddr.readthedocs.io/en/latest/tutorial_01.html#supernets-and-subnets # noqa
429 """
430
431 if cidr:
432 # Generate subnet CIDRs starting from given CIDR
433 # checking it is of requested IP version
434 cidr = netaddr.IPNetwork(cidr, version=ip_version)
435 else:
436 # Generate subnet CIDRs starting from configured values
437 ip_version = ip_version or cls._ip_version
438 if ip_version == const.IP_VERSION_4:
439 mask_bits = mask_bits or config.safe_get_config_value(
440 'network', 'project_network_mask_bits')
441 cidr = netaddr.IPNetwork(config.safe_get_config_value(
442 'network', 'project_network_cidr'))
443 elif ip_version == const.IP_VERSION_6:
444 mask_bits = config.safe_get_config_value(
445 'network', 'project_network_v6_mask_bits')
446 cidr = netaddr.IPNetwork(config.safe_get_config_value(
447 'network', 'project_network_v6_cidr'))
448 else:
449 raise ValueError('Invalid IP version: {!r}'.format(ip_version))
450
451 if mask_bits:
452 subnet_cidrs = cidr.subnet(mask_bits)
453 else:
454 subnet_cidrs = iter([cidr])
455
456 for subnet_cidr in subnet_cidrs:
457 if subnet_cidr not in cls.reserved_subnet_cidrs:
458 yield subnet_cidr
459
460 @classmethod
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000461 def create_port(cls, network, **kwargs):
462 """Wrapper utility that returns a test port."""
Edan Davidd75e48e2018-01-03 02:49:52 -0500463 if CONF.network.port_vnic_type and 'binding:vnic_type' not in kwargs:
464 kwargs['binding:vnic_type'] = CONF.network.port_vnic_type
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000465 body = cls.client.create_port(network_id=network['id'],
466 **kwargs)
467 port = body['port']
468 cls.ports.append(port)
469 return port
470
471 @classmethod
472 def update_port(cls, port, **kwargs):
473 """Wrapper utility that updates a test port."""
474 body = cls.client.update_port(port['id'],
475 **kwargs)
476 return body['port']
477
478 @classmethod
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300479 def _create_router_with_client(
480 cls, client, router_name=None, admin_state_up=False,
481 external_network_id=None, enable_snat=None, **kwargs
482 ):
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000483 ext_gw_info = {}
484 if external_network_id:
485 ext_gw_info['network_id'] = external_network_id
YAMAMOTO Takashi9bd4f972017-06-20 12:49:30 +0900486 if enable_snat is not None:
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000487 ext_gw_info['enable_snat'] = enable_snat
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300488 body = client.create_router(
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000489 router_name, external_gateway_info=ext_gw_info,
490 admin_state_up=admin_state_up, **kwargs)
491 router = body['router']
492 cls.routers.append(router)
493 return router
494
495 @classmethod
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300496 def create_router(cls, *args, **kwargs):
497 return cls._create_router_with_client(cls.client, *args, **kwargs)
498
499 @classmethod
500 def create_admin_router(cls, *args, **kwargs):
rajat294495c042017-06-28 15:37:16 +0530501 return cls._create_router_with_client(cls.os_admin.network_client,
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300502 *args, **kwargs)
503
504 @classmethod
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000505 def create_floatingip(cls, external_network_id):
506 """Wrapper utility that returns a test floating IP."""
507 body = cls.client.create_floatingip(
508 floating_network_id=external_network_id)
509 fip = body['floatingip']
510 cls.floating_ips.append(fip)
511 return fip
512
513 @classmethod
514 def create_router_interface(cls, router_id, subnet_id):
515 """Wrapper utility that returns a router interface."""
516 interface = cls.client.add_router_interface_with_subnet_id(
517 router_id, subnet_id)
518 return interface
519
520 @classmethod
Sławek Kapłońskiff294062016-12-04 15:00:54 +0000521 def get_supported_qos_rule_types(cls):
522 body = cls.client.list_qos_rule_types()
523 return [rule_type['type'] for rule_type in body['rule_types']]
524
525 @classmethod
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200526 def create_qos_policy(cls, name, description=None, shared=False,
Hirofumi Ichihara39a6ee12017-08-23 13:55:12 +0900527 tenant_id=None, is_default=False):
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000528 """Wrapper utility that returns a test QoS policy."""
529 body = cls.admin_client.create_qos_policy(
Hirofumi Ichihara39a6ee12017-08-23 13:55:12 +0900530 name, description, shared, tenant_id, is_default)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000531 qos_policy = body['policy']
532 cls.qos_policies.append(qos_policy)
533 return qos_policy
534
535 @classmethod
Sławek Kapłoński153f3452017-03-24 22:04:53 +0000536 def create_qos_bandwidth_limit_rule(cls, policy_id, max_kbps,
537 max_burst_kbps,
Chandan Kumarc125fd12017-11-15 19:41:01 +0530538 direction=const.EGRESS_DIRECTION):
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000539 """Wrapper utility that returns a test QoS bandwidth limit rule."""
540 body = cls.admin_client.create_bandwidth_limit_rule(
Sławek Kapłoński153f3452017-03-24 22:04:53 +0000541 policy_id, max_kbps, max_burst_kbps, direction)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000542 qos_rule = body['bandwidth_limit_rule']
543 cls.qos_rules.append(qos_rule)
544 return qos_rule
545
546 @classmethod
Jakub Libosvar83704832017-12-06 16:02:28 +0000547 def delete_router(cls, router, client=None):
548 client = client or cls.client
549 body = client.list_router_interfaces(router['id'])
Chandan Kumarc125fd12017-11-15 19:41:01 +0530550 interfaces = [port for port in body['ports']
551 if port['device_owner'] in const.ROUTER_INTERFACE_OWNERS]
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000552 for i in interfaces:
553 try:
Jakub Libosvar83704832017-12-06 16:02:28 +0000554 client.remove_router_interface_with_subnet_id(
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000555 router['id'], i['fixed_ips'][0]['subnet_id'])
556 except lib_exc.NotFound:
557 pass
Jakub Libosvar83704832017-12-06 16:02:28 +0000558 client.delete_router(router['id'])
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000559
560 @classmethod
561 def create_address_scope(cls, name, is_admin=False, **kwargs):
562 if is_admin:
563 body = cls.admin_client.create_address_scope(name=name, **kwargs)
564 cls.admin_address_scopes.append(body['address_scope'])
565 else:
566 body = cls.client.create_address_scope(name=name, **kwargs)
567 cls.address_scopes.append(body['address_scope'])
568 return body['address_scope']
569
570 @classmethod
571 def create_subnetpool(cls, name, is_admin=False, **kwargs):
572 if is_admin:
573 body = cls.admin_client.create_subnetpool(name, **kwargs)
574 cls.admin_subnetpools.append(body['subnetpool'])
575 else:
576 body = cls.client.create_subnetpool(name, **kwargs)
577 cls.subnetpools.append(body['subnetpool'])
578 return body['subnetpool']
579
Chandan Kumarc125fd12017-11-15 19:41:01 +0530580 @classmethod
581 def create_project(cls, name=None, description=None):
582 test_project = name or data_utils.rand_name('test_project_')
583 test_description = description or data_utils.rand_name('desc_')
584 project = cls.identity_admin_client.create_project(
585 name=test_project,
586 description=test_description)['project']
587 cls.projects.append(project)
588 return project
589
590 @classmethod
591 def create_security_group(cls, name, **kwargs):
592 body = cls.client.create_security_group(name=name, **kwargs)
593 cls.security_groups.append(body['security_group'])
594 return body['security_group']
595
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000596
597class BaseAdminNetworkTest(BaseNetworkTest):
598
599 credentials = ['primary', 'admin']
600
601 @classmethod
602 def setup_clients(cls):
603 super(BaseAdminNetworkTest, cls).setup_clients()
fumihiko kakumaa216fc12017-07-14 10:43:29 +0900604 cls.admin_client = cls.os_admin.network_client
Jakub Libosvarf5758012017-08-15 13:45:30 +0000605 cls.identity_admin_client = cls.os_admin.projects_client
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000606
607 @classmethod
608 def create_metering_label(cls, name, description):
609 """Wrapper utility that returns a test metering label."""
610 body = cls.admin_client.create_metering_label(
611 description=description,
612 name=data_utils.rand_name("metering-label"))
613 metering_label = body['metering_label']
614 cls.metering_labels.append(metering_label)
615 return metering_label
616
617 @classmethod
618 def create_metering_label_rule(cls, remote_ip_prefix, direction,
619 metering_label_id):
620 """Wrapper utility that returns a test metering label rule."""
621 body = cls.admin_client.create_metering_label_rule(
622 remote_ip_prefix=remote_ip_prefix, direction=direction,
623 metering_label_id=metering_label_id)
624 metering_label_rule = body['metering_label_rule']
625 cls.metering_label_rules.append(metering_label_rule)
626 return metering_label_rule
627
628 @classmethod
629 def create_flavor(cls, name, description, service_type):
630 """Wrapper utility that returns a test flavor."""
631 body = cls.admin_client.create_flavor(
632 description=description, service_type=service_type,
633 name=name)
634 flavor = body['flavor']
635 cls.flavors.append(flavor)
636 return flavor
637
638 @classmethod
639 def create_service_profile(cls, description, metainfo, driver):
640 """Wrapper utility that returns a test service profile."""
641 body = cls.admin_client.create_service_profile(
642 driver=driver, metainfo=metainfo, description=description)
643 service_profile = body['service_profile']
644 cls.service_profiles.append(service_profile)
645 return service_profile
646
647 @classmethod
Nguyen Phuong An67993fc2017-11-24 11:30:25 +0700648 def create_log(cls, name, description=None,
649 resource_type='security_group', resource_id=None,
650 target_id=None, event='ALL', enabled=True):
651 """Wrapper utility that returns a test log object."""
652 log_args = {'name': name,
653 'description': description,
654 'resource_type': resource_type,
655 'resource_id': resource_id,
656 'target_id': target_id,
657 'event': event,
658 'enabled': enabled}
659 body = cls.admin_client.create_log(**log_args)
660 log_object = body['log']
661 cls.log_objects.append(log_object)
662 return log_object
663
664 @classmethod
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000665 def get_unused_ip(cls, net_id, ip_version=None):
Gary Kotton011345f2016-06-15 08:04:31 -0700666 """Get an unused ip address in a allocation pool of net"""
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000667 body = cls.admin_client.list_ports(network_id=net_id)
668 ports = body['ports']
669 used_ips = []
670 for port in ports:
671 used_ips.extend(
672 [fixed_ip['ip_address'] for fixed_ip in port['fixed_ips']])
673 body = cls.admin_client.list_subnets(network_id=net_id)
674 subnets = body['subnets']
675
676 for subnet in subnets:
677 if ip_version and subnet['ip_version'] != ip_version:
678 continue
679 cidr = subnet['cidr']
680 allocation_pools = subnet['allocation_pools']
681 iterators = []
682 if allocation_pools:
683 for allocation_pool in allocation_pools:
684 iterators.append(netaddr.iter_iprange(
685 allocation_pool['start'], allocation_pool['end']))
686 else:
687 net = netaddr.IPNetwork(cidr)
688
689 def _iterip():
690 for ip in net:
691 if ip not in (net.network, net.broadcast):
692 yield ip
693 iterators.append(iter(_iterip()))
694
695 for iterator in iterators:
696 for ip in iterator:
697 if str(ip) not in used_ips:
698 return str(ip)
699
700 message = (
701 "net(%s) has no usable IP address in allocation pools" % net_id)
702 raise exceptions.InvalidConfiguration(message)
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200703
704
Sławek Kapłońskiff294062016-12-04 15:00:54 +0000705def require_qos_rule_type(rule_type):
706 def decorator(f):
707 @functools.wraps(f)
708 def wrapper(self, *func_args, **func_kwargs):
709 if rule_type not in self.get_supported_qos_rule_types():
710 raise self.skipException(
711 "%s rule type is required." % rule_type)
712 return f(self, *func_args, **func_kwargs)
713 return wrapper
714 return decorator
715
716
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200717def _require_sorting(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("sorting", "network"):
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200721 self.skipTest('Sorting feature is required')
722 return f(self, *args, **kwargs)
723 return inner
724
725
726def _require_pagination(f):
727 @functools.wraps(f)
728 def inner(self, *args, **kwargs):
Chandan Kumarc125fd12017-11-15 19:41:01 +0530729 if not tutils.is_extension_enabled("pagination", "network"):
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200730 self.skipTest('Pagination feature is required')
731 return f(self, *args, **kwargs)
732 return inner
733
734
735class BaseSearchCriteriaTest(BaseNetworkTest):
736
737 # This should be defined by subclasses to reflect resource name to test
738 resource = None
739
Armando Migliaccio57581c62016-07-01 10:13:19 -0700740 field = 'name'
741
Ihar Hrachyshkaa8fe5a12016-05-24 14:50:58 +0200742 # NOTE(ihrachys): some names, like those starting with an underscore (_)
743 # are sorted differently depending on whether the plugin implements native
744 # sorting support, or not. So we avoid any such cases here, sticking to
745 # alphanumeric. Also test a case when there are multiple resources with the
746 # same name
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200747 resource_names = ('test1', 'abc1', 'test10', '123test') + ('test1',)
748
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200749 force_tenant_isolation = True
750
Ihar Hrachyshkaa8fe5a12016-05-24 14:50:58 +0200751 list_kwargs = {}
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200752
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200753 list_as_admin = False
754
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200755 def assertSameOrder(self, original, actual):
756 # gracefully handle iterators passed
757 original = list(original)
758 actual = list(actual)
759 self.assertEqual(len(original), len(actual))
760 for expected, res in zip(original, actual):
Armando Migliaccio57581c62016-07-01 10:13:19 -0700761 self.assertEqual(expected[self.field], res[self.field])
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200762
763 @utils.classproperty
764 def plural_name(self):
765 return '%ss' % self.resource
766
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200767 @property
768 def list_client(self):
769 return self.admin_client if self.list_as_admin else self.client
770
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200771 def list_method(self, *args, **kwargs):
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200772 method = getattr(self.list_client, 'list_%s' % self.plural_name)
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200773 kwargs.update(self.list_kwargs)
774 return method(*args, **kwargs)
775
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200776 def get_bare_url(self, url):
777 base_url = self.client.base_url
778 self.assertTrue(url.startswith(base_url))
779 return url[len(base_url):]
780
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200781 @classmethod
782 def _extract_resources(cls, body):
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200783 return body[cls.plural_name]
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200784
785 def _test_list_sorts(self, direction):
786 sort_args = {
787 'sort_dir': direction,
Armando Migliaccio57581c62016-07-01 10:13:19 -0700788 'sort_key': self.field
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200789 }
790 body = self.list_method(**sort_args)
791 resources = self._extract_resources(body)
792 self.assertNotEmpty(
793 resources, "%s list returned is empty" % self.resource)
Armando Migliaccio57581c62016-07-01 10:13:19 -0700794 retrieved_names = [res[self.field] for res in resources]
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200795 expected = sorted(retrieved_names)
796 if direction == constants.SORT_DIRECTION_DESC:
797 expected = list(reversed(expected))
798 self.assertEqual(expected, retrieved_names)
799
800 @_require_sorting
801 def _test_list_sorts_asc(self):
802 self._test_list_sorts(constants.SORT_DIRECTION_ASC)
803
804 @_require_sorting
805 def _test_list_sorts_desc(self):
806 self._test_list_sorts(constants.SORT_DIRECTION_DESC)
807
808 @_require_pagination
809 def _test_list_pagination(self):
810 for limit in range(1, len(self.resource_names) + 1):
811 pagination_args = {
812 'limit': limit,
813 }
814 body = self.list_method(**pagination_args)
815 resources = self._extract_resources(body)
816 self.assertEqual(limit, len(resources))
817
818 @_require_pagination
819 def _test_list_no_pagination_limit_0(self):
820 pagination_args = {
821 'limit': 0,
822 }
823 body = self.list_method(**pagination_args)
824 resources = self._extract_resources(body)
Béla Vancsicsf1806182016-08-23 07:36:18 +0200825 self.assertGreaterEqual(len(resources), len(self.resource_names))
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200826
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200827 def _test_list_pagination_iteratively(self, lister):
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200828 # first, collect all resources for later comparison
829 sort_args = {
830 'sort_dir': constants.SORT_DIRECTION_ASC,
Armando Migliaccio57581c62016-07-01 10:13:19 -0700831 'sort_key': self.field
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200832 }
833 body = self.list_method(**sort_args)
834 expected_resources = self._extract_resources(body)
835 self.assertNotEmpty(expected_resources)
836
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200837 resources = lister(
838 len(expected_resources), sort_args
839 )
840
841 # finally, compare that the list retrieved in one go is identical to
842 # the one containing pagination results
843 self.assertSameOrder(expected_resources, resources)
844
845 def _list_all_with_marker(self, niterations, sort_args):
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200846 # paginate resources one by one, using last fetched resource as a
847 # marker
848 resources = []
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200849 for i in range(niterations):
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200850 pagination_args = sort_args.copy()
851 pagination_args['limit'] = 1
852 if resources:
853 pagination_args['marker'] = resources[-1]['id']
854 body = self.list_method(**pagination_args)
855 resources_ = self._extract_resources(body)
856 self.assertEqual(1, len(resources_))
857 resources.extend(resources_)
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200858 return resources
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200859
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200860 @_require_pagination
861 @_require_sorting
862 def _test_list_pagination_with_marker(self):
863 self._test_list_pagination_iteratively(self._list_all_with_marker)
864
865 def _list_all_with_hrefs(self, niterations, sort_args):
866 # paginate resources one by one, using next href links
867 resources = []
868 prev_links = {}
869
870 for i in range(niterations):
871 if prev_links:
872 uri = self.get_bare_url(prev_links['next'])
873 else:
Ihar Hrachyshka7f79fe62016-06-07 21:23:44 +0200874 sort_args.update(self.list_kwargs)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200875 uri = self.list_client.build_uri(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200876 self.plural_name, limit=1, **sort_args)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200877 prev_links, body = self.list_client.get_uri_with_links(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200878 self.plural_name, uri
879 )
880 resources_ = self._extract_resources(body)
881 self.assertEqual(1, len(resources_))
882 resources.extend(resources_)
883
884 # The last element is empty and does not contain 'next' link
885 uri = self.get_bare_url(prev_links['next'])
886 prev_links, body = self.client.get_uri_with_links(
887 self.plural_name, uri
888 )
889 self.assertNotIn('next', prev_links)
890
891 # Now walk backwards and compare results
892 resources2 = []
893 for i in range(niterations):
894 uri = self.get_bare_url(prev_links['previous'])
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200895 prev_links, body = self.list_client.get_uri_with_links(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200896 self.plural_name, uri
897 )
898 resources_ = self._extract_resources(body)
899 self.assertEqual(1, len(resources_))
900 resources2.extend(resources_)
901
902 self.assertSameOrder(resources, reversed(resources2))
903
904 return resources
905
906 @_require_pagination
907 @_require_sorting
908 def _test_list_pagination_with_href_links(self):
909 self._test_list_pagination_iteratively(self._list_all_with_hrefs)
910
911 @_require_pagination
912 @_require_sorting
913 def _test_list_pagination_page_reverse_with_href_links(
914 self, direction=constants.SORT_DIRECTION_ASC):
915 pagination_args = {
916 'sort_dir': direction,
Armando Migliaccio57581c62016-07-01 10:13:19 -0700917 'sort_key': self.field,
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200918 }
919 body = self.list_method(**pagination_args)
920 expected_resources = self._extract_resources(body)
921
922 page_size = 2
923 pagination_args['limit'] = page_size
924
925 prev_links = {}
926 resources = []
927 num_resources = len(expected_resources)
928 niterations = int(math.ceil(float(num_resources) / page_size))
929 for i in range(niterations):
930 if prev_links:
931 uri = self.get_bare_url(prev_links['previous'])
932 else:
Ihar Hrachyshka7f79fe62016-06-07 21:23:44 +0200933 pagination_args.update(self.list_kwargs)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200934 uri = self.list_client.build_uri(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200935 self.plural_name, page_reverse=True, **pagination_args)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200936 prev_links, body = self.list_client.get_uri_with_links(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200937 self.plural_name, uri
938 )
939 resources_ = self._extract_resources(body)
Béla Vancsicsf1806182016-08-23 07:36:18 +0200940 self.assertGreaterEqual(page_size, len(resources_))
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200941 resources.extend(reversed(resources_))
942
943 self.assertSameOrder(expected_resources, reversed(resources))
944
945 @_require_pagination
946 @_require_sorting
947 def _test_list_pagination_page_reverse_asc(self):
948 self._test_list_pagination_page_reverse(
949 direction=constants.SORT_DIRECTION_ASC)
950
951 @_require_pagination
952 @_require_sorting
953 def _test_list_pagination_page_reverse_desc(self):
954 self._test_list_pagination_page_reverse(
955 direction=constants.SORT_DIRECTION_DESC)
956
957 def _test_list_pagination_page_reverse(self, direction):
958 pagination_args = {
959 'sort_dir': direction,
Armando Migliaccio57581c62016-07-01 10:13:19 -0700960 'sort_key': self.field,
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200961 'limit': 3,
962 }
963 body = self.list_method(**pagination_args)
964 expected_resources = self._extract_resources(body)
965
966 pagination_args['limit'] -= 1
967 pagination_args['marker'] = expected_resources[-1]['id']
968 pagination_args['page_reverse'] = True
969 body = self.list_method(**pagination_args)
970
971 self.assertSameOrder(
972 # the last entry is not included in 2nd result when used as a
973 # marker
974 expected_resources[:-1],
975 self._extract_resources(body))
Victor Morales1be97b42016-09-05 08:50:06 -0500976
977 def _test_list_validation_filters(self):
978 validation_args = {
979 'unknown_filter': 'value',
980 }
981 body = self.list_method(**validation_args)
982 resources = self._extract_resources(body)
983 for resource in resources:
984 self.assertIn(resource['name'], self.resource_names)