blob: 2f5446cc05a7b829eb193da09a20ea0c23dcd1a1 [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()
Federico Ressiab286e42018-06-19 09:52:10 +0200127 cls.keypairs = []
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000128
129 @classmethod
130 def resource_cleanup(cls):
131 if CONF.service_available.neutron:
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000132 # Clean up floating IPs
133 for floating_ip in cls.floating_ips:
134 cls._try_delete_resource(cls.client.delete_floatingip,
135 floating_ip['id'])
136 # Clean up routers
137 for router in cls.routers:
138 cls._try_delete_resource(cls.delete_router,
139 router)
140 # Clean up metering label rules
141 for metering_label_rule in cls.metering_label_rules:
142 cls._try_delete_resource(
143 cls.admin_client.delete_metering_label_rule,
144 metering_label_rule['id'])
145 # Clean up metering labels
146 for metering_label in cls.metering_labels:
147 cls._try_delete_resource(
148 cls.admin_client.delete_metering_label,
149 metering_label['id'])
150 # Clean up flavors
151 for flavor in cls.flavors:
152 cls._try_delete_resource(
153 cls.admin_client.delete_flavor,
154 flavor['id'])
155 # Clean up service profiles
156 for service_profile in cls.service_profiles:
157 cls._try_delete_resource(
158 cls.admin_client.delete_service_profile,
159 service_profile['id'])
160 # Clean up ports
161 for port in cls.ports:
162 cls._try_delete_resource(cls.client.delete_port,
163 port['id'])
164 # Clean up subnets
165 for subnet in cls.subnets:
166 cls._try_delete_resource(cls.client.delete_subnet,
167 subnet['id'])
Kevin Bentonba3651c2017-09-01 17:13:01 -0700168 # Clean up admin subnets
169 for subnet in cls.admin_subnets:
170 cls._try_delete_resource(cls.admin_client.delete_subnet,
171 subnet['id'])
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000172 # Clean up networks
173 for network in cls.networks:
174 cls._try_delete_resource(cls.client.delete_network,
175 network['id'])
176
Miguel Lavalle124378b2016-09-21 16:41:47 -0500177 # Clean up admin networks
178 for network in cls.admin_networks:
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000179 cls._try_delete_resource(cls.admin_client.delete_network,
180 network['id'])
181
Itzik Brownbac51dc2016-10-31 12:25:04 +0000182 # Clean up security groups
183 for secgroup in cls.security_groups:
184 cls._try_delete_resource(cls.client.delete_security_group,
185 secgroup['id'])
186
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000187 for subnetpool in cls.subnetpools:
188 cls._try_delete_resource(cls.client.delete_subnetpool,
189 subnetpool['id'])
190
191 for subnetpool in cls.admin_subnetpools:
192 cls._try_delete_resource(cls.admin_client.delete_subnetpool,
193 subnetpool['id'])
194
195 for address_scope in cls.address_scopes:
196 cls._try_delete_resource(cls.client.delete_address_scope,
197 address_scope['id'])
198
199 for address_scope in cls.admin_address_scopes:
200 cls._try_delete_resource(
201 cls.admin_client.delete_address_scope,
202 address_scope['id'])
203
Chandan Kumarc125fd12017-11-15 19:41:01 +0530204 for project in cls.projects:
205 cls._try_delete_resource(
206 cls.identity_admin_client.delete_project,
207 project['id'])
208
Sławek Kapłońskie100c4d2017-08-23 21:18:34 +0000209 # Clean up QoS rules
210 for qos_rule in cls.qos_rules:
211 cls._try_delete_resource(cls.admin_client.delete_qos_rule,
212 qos_rule['id'])
213 # Clean up QoS policies
214 # as all networks and ports are already removed, QoS policies
215 # shouldn't be "in use"
216 for qos_policy in cls.qos_policies:
217 cls._try_delete_resource(cls.admin_client.delete_qos_policy,
218 qos_policy['id'])
219
Nguyen Phuong An67993fc2017-11-24 11:30:25 +0700220 # Clean up log_objects
221 for log_object in cls.log_objects:
222 cls._try_delete_resource(cls.admin_client.delete_log,
223 log_object['id'])
224
Federico Ressiab286e42018-06-19 09:52:10 +0200225 for keypair in cls.keypairs:
226 cls._try_delete_resource(cls.delete_keypair, keypair)
227
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000228 super(BaseNetworkTest, cls).resource_cleanup()
229
230 @classmethod
231 def _try_delete_resource(cls, delete_callable, *args, **kwargs):
232 """Cleanup resources in case of test-failure
233
234 Some resources are explicitly deleted by the test.
235 If the test failed to delete a resource, this method will execute
236 the appropriate delete methods. Otherwise, the method ignores NotFound
237 exceptions thrown for resources that were correctly deleted by the
238 test.
239
240 :param delete_callable: delete method
241 :param args: arguments for delete method
242 :param kwargs: keyword arguments for delete method
243 """
244 try:
245 delete_callable(*args, **kwargs)
246 # if resource is not found, this means it was deleted in the test
247 except lib_exc.NotFound:
248 pass
249
250 @classmethod
Sergey Belousa627ed92016-10-07 14:29:07 +0300251 def create_network(cls, network_name=None, client=None, **kwargs):
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000252 """Wrapper utility that returns a test network."""
253 network_name = network_name or data_utils.rand_name('test-network-')
254
Sergey Belousa627ed92016-10-07 14:29:07 +0300255 client = client or cls.client
256 body = client.create_network(name=network_name, **kwargs)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000257 network = body['network']
Sławek Kapłońskia694a5f2017-08-24 19:51:22 +0000258 if client is cls.client:
259 cls.networks.append(network)
260 else:
261 cls.admin_networks.append(network)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000262 return network
263
264 @classmethod
265 def create_shared_network(cls, network_name=None, **post_body):
266 network_name = network_name or data_utils.rand_name('sharednetwork-')
267 post_body.update({'name': network_name, 'shared': True})
268 body = cls.admin_client.create_network(**post_body)
269 network = body['network']
Miguel Lavalle124378b2016-09-21 16:41:47 -0500270 cls.admin_networks.append(network)
271 return network
272
273 @classmethod
274 def create_network_keystone_v3(cls, network_name=None, project_id=None,
275 tenant_id=None, client=None):
276 """Wrapper utility that creates a test network with project_id."""
277 client = client or cls.client
278 network_name = network_name or data_utils.rand_name(
279 'test-network-with-project_id')
280 project_id = cls.client.tenant_id
281 body = client.create_network_keystone_v3(network_name, project_id,
282 tenant_id)
283 network = body['network']
284 if client is cls.client:
285 cls.networks.append(network)
286 else:
287 cls.admin_networks.append(network)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000288 return network
289
290 @classmethod
Sławek Kapłońskid98e27d2018-05-07 16:16:28 +0200291 def create_subnet(cls, network, gateway='', cidr=None, mask_bits=None,
Federico Ressi98f20ec2018-05-11 06:09:49 +0200292 ip_version=None, client=None, reserve_cidr=True,
293 **kwargs):
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200294 """Wrapper utility that returns a test subnet.
295
296 Convenient wrapper for client.create_subnet method. It reserves and
297 allocates CIDRs to avoid creating overlapping subnets.
298
299 :param network: network where to create the subnet
300 network['id'] must contain the ID of the network
301
302 :param gateway: gateway IP address
303 It can be a str or a netaddr.IPAddress
304 If gateway is not given, then it will use default address for
305 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 +0200306 if gateway is given as None then no gateway will be assigned
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200307
308 :param cidr: CIDR of the subnet to create
309 It can be either None, a str or a netaddr.IPNetwork instance
310
311 :param mask_bits: CIDR prefix length
312 It can be either None or a numeric value.
313 If cidr parameter is given then mask_bits is used to determinate a
314 sequence of valid CIDR to use as generated.
315 Please see netaddr.IPNetwork.subnet method documentation[1]
316
317 :param ip_version: ip version of generated subnet CIDRs
318 It can be None, IP_VERSION_4 or IP_VERSION_6
319 It has to match given either given CIDR and gateway
320
321 :param ip_version: numeric value (either IP_VERSION_4 or IP_VERSION_6)
322 this value must match CIDR and gateway IP versions if any of them is
323 given
324
325 :param client: client to be used to connect to network service
326
Federico Ressi98f20ec2018-05-11 06:09:49 +0200327 :param reserve_cidr: if True then it reserves assigned CIDR to avoid
328 using the same CIDR for further subnets in the scope of the same
329 test case class
330
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200331 :param **kwargs: optional parameters to be forwarded to wrapped method
332
333 [1] http://netaddr.readthedocs.io/en/latest/tutorial_01.html#supernets-and-subnets # noqa
334 """
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000335
336 # allow tests to use admin client
337 if not client:
338 client = cls.client
339
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200340 if gateway:
341 gateway_ip = netaddr.IPAddress(gateway)
342 if ip_version:
343 if ip_version != gateway_ip.version:
344 raise ValueError(
345 "Gateway IP version doesn't match IP version")
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000346 else:
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200347 ip_version = gateway_ip.version
Sławek Kapłońskid98e27d2018-05-07 16:16:28 +0200348 else:
349 ip_version = ip_version or cls._ip_version
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200350
351 for subnet_cidr in cls.get_subnet_cidrs(
352 ip_version=ip_version, cidr=cidr, mask_bits=mask_bits):
Federico Ressi98f20ec2018-05-11 06:09:49 +0200353 if gateway is not None:
354 kwargs['gateway_ip'] = str(gateway or (subnet_cidr.ip + 1))
355 try:
356 body = client.create_subnet(
357 network_id=network['id'],
358 cidr=str(subnet_cidr),
359 ip_version=subnet_cidr.version,
360 **kwargs)
361 break
362 except lib_exc.BadRequest as e:
363 if 'overlaps with another subnet' not in str(e):
364 raise
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000365 else:
366 message = 'Available CIDR for subnet creation could not be found'
367 raise ValueError(message)
368 subnet = body['subnet']
Kevin Bentonba3651c2017-09-01 17:13:01 -0700369 if client is cls.client:
370 cls.subnets.append(subnet)
371 else:
372 cls.admin_subnets.append(subnet)
Federico Ressi98f20ec2018-05-11 06:09:49 +0200373 if reserve_cidr:
374 cls.reserve_subnet_cidr(subnet_cidr)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000375 return subnet
376
377 @classmethod
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200378 def reserve_subnet_cidr(cls, addr, **ipnetwork_kwargs):
379 """Reserve given subnet CIDR making sure it is not used by create_subnet
380
381 :param addr: the CIDR address to be reserved
382 It can be a str or netaddr.IPNetwork instance
383
384 :param **ipnetwork_kwargs: optional netaddr.IPNetwork constructor
385 parameters
386 """
387
388 if not cls.try_reserve_subnet_cidr(addr, **ipnetwork_kwargs):
389 raise ValueError('Subnet CIDR already reserved: %r'.format(
390 addr))
391
392 @classmethod
393 def try_reserve_subnet_cidr(cls, addr, **ipnetwork_kwargs):
394 """Reserve given subnet CIDR if it hasn't been reserved before
395
396 :param addr: the CIDR address to be reserved
397 It can be a str or netaddr.IPNetwork instance
398
399 :param **ipnetwork_kwargs: optional netaddr.IPNetwork constructor
400 parameters
401
402 :return: True if it wasn't reserved before, False elsewhere.
403 """
404
405 subnet_cidr = netaddr.IPNetwork(addr, **ipnetwork_kwargs)
406 if subnet_cidr in cls.reserved_subnet_cidrs:
407 return False
408 else:
409 cls.reserved_subnet_cidrs.add(subnet_cidr)
410 return True
411
412 @classmethod
413 def get_subnet_cidrs(
414 cls, cidr=None, mask_bits=None, ip_version=None):
415 """Iterate over a sequence of unused subnet CIDR for IP version
416
417 :param cidr: CIDR of the subnet to create
418 It can be either None, a str or a netaddr.IPNetwork instance
419
420 :param mask_bits: CIDR prefix length
421 It can be either None or a numeric value.
422 If cidr parameter is given then mask_bits is used to determinate a
423 sequence of valid CIDR to use as generated.
424 Please see netaddr.IPNetwork.subnet method documentation[1]
425
426 :param ip_version: ip version of generated subnet CIDRs
427 It can be None, IP_VERSION_4 or IP_VERSION_6
428 It has to match given CIDR if given
429
430 :return: iterator over reserved CIDRs of type netaddr.IPNetwork
431
432 [1] http://netaddr.readthedocs.io/en/latest/tutorial_01.html#supernets-and-subnets # noqa
433 """
434
435 if cidr:
436 # Generate subnet CIDRs starting from given CIDR
437 # checking it is of requested IP version
438 cidr = netaddr.IPNetwork(cidr, version=ip_version)
439 else:
440 # Generate subnet CIDRs starting from configured values
441 ip_version = ip_version or cls._ip_version
442 if ip_version == const.IP_VERSION_4:
443 mask_bits = mask_bits or config.safe_get_config_value(
444 'network', 'project_network_mask_bits')
445 cidr = netaddr.IPNetwork(config.safe_get_config_value(
446 'network', 'project_network_cidr'))
447 elif ip_version == const.IP_VERSION_6:
448 mask_bits = config.safe_get_config_value(
449 'network', 'project_network_v6_mask_bits')
450 cidr = netaddr.IPNetwork(config.safe_get_config_value(
451 'network', 'project_network_v6_cidr'))
452 else:
453 raise ValueError('Invalid IP version: {!r}'.format(ip_version))
454
455 if mask_bits:
456 subnet_cidrs = cidr.subnet(mask_bits)
457 else:
458 subnet_cidrs = iter([cidr])
459
460 for subnet_cidr in subnet_cidrs:
461 if subnet_cidr not in cls.reserved_subnet_cidrs:
462 yield subnet_cidr
463
464 @classmethod
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000465 def create_port(cls, network, **kwargs):
466 """Wrapper utility that returns a test port."""
Edan Davidd75e48e2018-01-03 02:49:52 -0500467 if CONF.network.port_vnic_type and 'binding:vnic_type' not in kwargs:
468 kwargs['binding:vnic_type'] = CONF.network.port_vnic_type
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000469 body = cls.client.create_port(network_id=network['id'],
470 **kwargs)
471 port = body['port']
472 cls.ports.append(port)
473 return port
474
475 @classmethod
476 def update_port(cls, port, **kwargs):
477 """Wrapper utility that updates a test port."""
478 body = cls.client.update_port(port['id'],
479 **kwargs)
480 return body['port']
481
482 @classmethod
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300483 def _create_router_with_client(
484 cls, client, router_name=None, admin_state_up=False,
485 external_network_id=None, enable_snat=None, **kwargs
486 ):
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000487 ext_gw_info = {}
488 if external_network_id:
489 ext_gw_info['network_id'] = external_network_id
YAMAMOTO Takashi9bd4f972017-06-20 12:49:30 +0900490 if enable_snat is not None:
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000491 ext_gw_info['enable_snat'] = enable_snat
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300492 body = client.create_router(
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000493 router_name, external_gateway_info=ext_gw_info,
494 admin_state_up=admin_state_up, **kwargs)
495 router = body['router']
496 cls.routers.append(router)
497 return router
498
499 @classmethod
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300500 def create_router(cls, *args, **kwargs):
501 return cls._create_router_with_client(cls.client, *args, **kwargs)
502
503 @classmethod
504 def create_admin_router(cls, *args, **kwargs):
rajat294495c042017-06-28 15:37:16 +0530505 return cls._create_router_with_client(cls.os_admin.network_client,
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300506 *args, **kwargs)
507
508 @classmethod
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000509 def create_floatingip(cls, external_network_id):
510 """Wrapper utility that returns a test floating IP."""
511 body = cls.client.create_floatingip(
512 floating_network_id=external_network_id)
513 fip = body['floatingip']
514 cls.floating_ips.append(fip)
515 return fip
516
517 @classmethod
518 def create_router_interface(cls, router_id, subnet_id):
519 """Wrapper utility that returns a router interface."""
520 interface = cls.client.add_router_interface_with_subnet_id(
521 router_id, subnet_id)
522 return interface
523
524 @classmethod
Sławek Kapłońskiff294062016-12-04 15:00:54 +0000525 def get_supported_qos_rule_types(cls):
526 body = cls.client.list_qos_rule_types()
527 return [rule_type['type'] for rule_type in body['rule_types']]
528
529 @classmethod
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200530 def create_qos_policy(cls, name, description=None, shared=False,
Hirofumi Ichihara39a6ee12017-08-23 13:55:12 +0900531 tenant_id=None, is_default=False):
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000532 """Wrapper utility that returns a test QoS policy."""
533 body = cls.admin_client.create_qos_policy(
Hirofumi Ichihara39a6ee12017-08-23 13:55:12 +0900534 name, description, shared, tenant_id, is_default)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000535 qos_policy = body['policy']
536 cls.qos_policies.append(qos_policy)
537 return qos_policy
538
539 @classmethod
Sławek Kapłoński153f3452017-03-24 22:04:53 +0000540 def create_qos_bandwidth_limit_rule(cls, policy_id, max_kbps,
541 max_burst_kbps,
Chandan Kumarc125fd12017-11-15 19:41:01 +0530542 direction=const.EGRESS_DIRECTION):
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000543 """Wrapper utility that returns a test QoS bandwidth limit rule."""
544 body = cls.admin_client.create_bandwidth_limit_rule(
Sławek Kapłoński153f3452017-03-24 22:04:53 +0000545 policy_id, max_kbps, max_burst_kbps, direction)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000546 qos_rule = body['bandwidth_limit_rule']
547 cls.qos_rules.append(qos_rule)
548 return qos_rule
549
550 @classmethod
Jakub Libosvar83704832017-12-06 16:02:28 +0000551 def delete_router(cls, router, client=None):
552 client = client or cls.client
553 body = client.list_router_interfaces(router['id'])
Chandan Kumarc125fd12017-11-15 19:41:01 +0530554 interfaces = [port for port in body['ports']
555 if port['device_owner'] in const.ROUTER_INTERFACE_OWNERS]
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000556 for i in interfaces:
557 try:
Jakub Libosvar83704832017-12-06 16:02:28 +0000558 client.remove_router_interface_with_subnet_id(
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000559 router['id'], i['fixed_ips'][0]['subnet_id'])
560 except lib_exc.NotFound:
561 pass
Jakub Libosvar83704832017-12-06 16:02:28 +0000562 client.delete_router(router['id'])
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000563
564 @classmethod
565 def create_address_scope(cls, name, is_admin=False, **kwargs):
566 if is_admin:
567 body = cls.admin_client.create_address_scope(name=name, **kwargs)
568 cls.admin_address_scopes.append(body['address_scope'])
569 else:
570 body = cls.client.create_address_scope(name=name, **kwargs)
571 cls.address_scopes.append(body['address_scope'])
572 return body['address_scope']
573
574 @classmethod
575 def create_subnetpool(cls, name, is_admin=False, **kwargs):
576 if is_admin:
577 body = cls.admin_client.create_subnetpool(name, **kwargs)
578 cls.admin_subnetpools.append(body['subnetpool'])
579 else:
580 body = cls.client.create_subnetpool(name, **kwargs)
581 cls.subnetpools.append(body['subnetpool'])
582 return body['subnetpool']
583
Chandan Kumarc125fd12017-11-15 19:41:01 +0530584 @classmethod
585 def create_project(cls, name=None, description=None):
586 test_project = name or data_utils.rand_name('test_project_')
587 test_description = description or data_utils.rand_name('desc_')
588 project = cls.identity_admin_client.create_project(
589 name=test_project,
590 description=test_description)['project']
591 cls.projects.append(project)
592 return project
593
594 @classmethod
595 def create_security_group(cls, name, **kwargs):
596 body = cls.client.create_security_group(name=name, **kwargs)
597 cls.security_groups.append(body['security_group'])
598 return body['security_group']
599
Federico Ressiab286e42018-06-19 09:52:10 +0200600 @classmethod
601 def create_keypair(cls, client=None, name=None, **kwargs):
602 client = client or cls.os_primary.keypairs_client
603 name = name or data_utils.rand_name('keypair-test')
604 keypair = client.create_keypair(name=name, **kwargs)['keypair']
605
606 # save client for later cleanup
607 keypair['client'] = client
608 cls.keypairs.append(keypair)
609 return keypair
610
611 @classmethod
612 def delete_keypair(cls, keypair, client=None):
613 client = (client or keypair.get('client') or
614 cls.os_primary.keypairs_client)
615 client.delete_keypair(keypair_name=keypair['name'])
616
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000617
618class BaseAdminNetworkTest(BaseNetworkTest):
619
620 credentials = ['primary', 'admin']
621
622 @classmethod
623 def setup_clients(cls):
624 super(BaseAdminNetworkTest, cls).setup_clients()
fumihiko kakumaa216fc12017-07-14 10:43:29 +0900625 cls.admin_client = cls.os_admin.network_client
Jakub Libosvarf5758012017-08-15 13:45:30 +0000626 cls.identity_admin_client = cls.os_admin.projects_client
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000627
628 @classmethod
629 def create_metering_label(cls, name, description):
630 """Wrapper utility that returns a test metering label."""
631 body = cls.admin_client.create_metering_label(
632 description=description,
633 name=data_utils.rand_name("metering-label"))
634 metering_label = body['metering_label']
635 cls.metering_labels.append(metering_label)
636 return metering_label
637
638 @classmethod
639 def create_metering_label_rule(cls, remote_ip_prefix, direction,
640 metering_label_id):
641 """Wrapper utility that returns a test metering label rule."""
642 body = cls.admin_client.create_metering_label_rule(
643 remote_ip_prefix=remote_ip_prefix, direction=direction,
644 metering_label_id=metering_label_id)
645 metering_label_rule = body['metering_label_rule']
646 cls.metering_label_rules.append(metering_label_rule)
647 return metering_label_rule
648
649 @classmethod
650 def create_flavor(cls, name, description, service_type):
651 """Wrapper utility that returns a test flavor."""
652 body = cls.admin_client.create_flavor(
653 description=description, service_type=service_type,
654 name=name)
655 flavor = body['flavor']
656 cls.flavors.append(flavor)
657 return flavor
658
659 @classmethod
660 def create_service_profile(cls, description, metainfo, driver):
661 """Wrapper utility that returns a test service profile."""
662 body = cls.admin_client.create_service_profile(
663 driver=driver, metainfo=metainfo, description=description)
664 service_profile = body['service_profile']
665 cls.service_profiles.append(service_profile)
666 return service_profile
667
668 @classmethod
Nguyen Phuong An67993fc2017-11-24 11:30:25 +0700669 def create_log(cls, name, description=None,
670 resource_type='security_group', resource_id=None,
671 target_id=None, event='ALL', enabled=True):
672 """Wrapper utility that returns a test log object."""
673 log_args = {'name': name,
674 'description': description,
675 'resource_type': resource_type,
676 'resource_id': resource_id,
677 'target_id': target_id,
678 'event': event,
679 'enabled': enabled}
680 body = cls.admin_client.create_log(**log_args)
681 log_object = body['log']
682 cls.log_objects.append(log_object)
683 return log_object
684
685 @classmethod
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000686 def get_unused_ip(cls, net_id, ip_version=None):
Gary Kotton011345f2016-06-15 08:04:31 -0700687 """Get an unused ip address in a allocation pool of net"""
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000688 body = cls.admin_client.list_ports(network_id=net_id)
689 ports = body['ports']
690 used_ips = []
691 for port in ports:
692 used_ips.extend(
693 [fixed_ip['ip_address'] for fixed_ip in port['fixed_ips']])
694 body = cls.admin_client.list_subnets(network_id=net_id)
695 subnets = body['subnets']
696
697 for subnet in subnets:
698 if ip_version and subnet['ip_version'] != ip_version:
699 continue
700 cidr = subnet['cidr']
701 allocation_pools = subnet['allocation_pools']
702 iterators = []
703 if allocation_pools:
704 for allocation_pool in allocation_pools:
705 iterators.append(netaddr.iter_iprange(
706 allocation_pool['start'], allocation_pool['end']))
707 else:
708 net = netaddr.IPNetwork(cidr)
709
710 def _iterip():
711 for ip in net:
712 if ip not in (net.network, net.broadcast):
713 yield ip
714 iterators.append(iter(_iterip()))
715
716 for iterator in iterators:
717 for ip in iterator:
718 if str(ip) not in used_ips:
719 return str(ip)
720
721 message = (
722 "net(%s) has no usable IP address in allocation pools" % net_id)
723 raise exceptions.InvalidConfiguration(message)
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200724
725
Sławek Kapłońskiff294062016-12-04 15:00:54 +0000726def require_qos_rule_type(rule_type):
727 def decorator(f):
728 @functools.wraps(f)
729 def wrapper(self, *func_args, **func_kwargs):
730 if rule_type not in self.get_supported_qos_rule_types():
731 raise self.skipException(
732 "%s rule type is required." % rule_type)
733 return f(self, *func_args, **func_kwargs)
734 return wrapper
735 return decorator
736
737
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200738def _require_sorting(f):
739 @functools.wraps(f)
740 def inner(self, *args, **kwargs):
Chandan Kumarc125fd12017-11-15 19:41:01 +0530741 if not tutils.is_extension_enabled("sorting", "network"):
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200742 self.skipTest('Sorting feature is required')
743 return f(self, *args, **kwargs)
744 return inner
745
746
747def _require_pagination(f):
748 @functools.wraps(f)
749 def inner(self, *args, **kwargs):
Chandan Kumarc125fd12017-11-15 19:41:01 +0530750 if not tutils.is_extension_enabled("pagination", "network"):
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200751 self.skipTest('Pagination feature is required')
752 return f(self, *args, **kwargs)
753 return inner
754
755
756class BaseSearchCriteriaTest(BaseNetworkTest):
757
758 # This should be defined by subclasses to reflect resource name to test
759 resource = None
760
Armando Migliaccio57581c62016-07-01 10:13:19 -0700761 field = 'name'
762
Ihar Hrachyshkaa8fe5a12016-05-24 14:50:58 +0200763 # NOTE(ihrachys): some names, like those starting with an underscore (_)
764 # are sorted differently depending on whether the plugin implements native
765 # sorting support, or not. So we avoid any such cases here, sticking to
766 # alphanumeric. Also test a case when there are multiple resources with the
767 # same name
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200768 resource_names = ('test1', 'abc1', 'test10', '123test') + ('test1',)
769
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200770 force_tenant_isolation = True
771
Ihar Hrachyshkaa8fe5a12016-05-24 14:50:58 +0200772 list_kwargs = {}
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200773
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200774 list_as_admin = False
775
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200776 def assertSameOrder(self, original, actual):
777 # gracefully handle iterators passed
778 original = list(original)
779 actual = list(actual)
780 self.assertEqual(len(original), len(actual))
781 for expected, res in zip(original, actual):
Armando Migliaccio57581c62016-07-01 10:13:19 -0700782 self.assertEqual(expected[self.field], res[self.field])
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200783
784 @utils.classproperty
785 def plural_name(self):
786 return '%ss' % self.resource
787
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200788 @property
789 def list_client(self):
790 return self.admin_client if self.list_as_admin else self.client
791
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200792 def list_method(self, *args, **kwargs):
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200793 method = getattr(self.list_client, 'list_%s' % self.plural_name)
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200794 kwargs.update(self.list_kwargs)
795 return method(*args, **kwargs)
796
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200797 def get_bare_url(self, url):
798 base_url = self.client.base_url
799 self.assertTrue(url.startswith(base_url))
800 return url[len(base_url):]
801
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200802 @classmethod
803 def _extract_resources(cls, body):
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200804 return body[cls.plural_name]
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200805
806 def _test_list_sorts(self, direction):
807 sort_args = {
808 'sort_dir': direction,
Armando Migliaccio57581c62016-07-01 10:13:19 -0700809 'sort_key': self.field
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200810 }
811 body = self.list_method(**sort_args)
812 resources = self._extract_resources(body)
813 self.assertNotEmpty(
814 resources, "%s list returned is empty" % self.resource)
Armando Migliaccio57581c62016-07-01 10:13:19 -0700815 retrieved_names = [res[self.field] for res in resources]
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200816 expected = sorted(retrieved_names)
817 if direction == constants.SORT_DIRECTION_DESC:
818 expected = list(reversed(expected))
819 self.assertEqual(expected, retrieved_names)
820
821 @_require_sorting
822 def _test_list_sorts_asc(self):
823 self._test_list_sorts(constants.SORT_DIRECTION_ASC)
824
825 @_require_sorting
826 def _test_list_sorts_desc(self):
827 self._test_list_sorts(constants.SORT_DIRECTION_DESC)
828
829 @_require_pagination
830 def _test_list_pagination(self):
831 for limit in range(1, len(self.resource_names) + 1):
832 pagination_args = {
833 'limit': limit,
834 }
835 body = self.list_method(**pagination_args)
836 resources = self._extract_resources(body)
837 self.assertEqual(limit, len(resources))
838
839 @_require_pagination
840 def _test_list_no_pagination_limit_0(self):
841 pagination_args = {
842 'limit': 0,
843 }
844 body = self.list_method(**pagination_args)
845 resources = self._extract_resources(body)
Béla Vancsicsf1806182016-08-23 07:36:18 +0200846 self.assertGreaterEqual(len(resources), len(self.resource_names))
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200847
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200848 def _test_list_pagination_iteratively(self, lister):
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200849 # first, collect all resources for later comparison
850 sort_args = {
851 'sort_dir': constants.SORT_DIRECTION_ASC,
Armando Migliaccio57581c62016-07-01 10:13:19 -0700852 'sort_key': self.field
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200853 }
854 body = self.list_method(**sort_args)
855 expected_resources = self._extract_resources(body)
856 self.assertNotEmpty(expected_resources)
857
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200858 resources = lister(
859 len(expected_resources), sort_args
860 )
861
862 # finally, compare that the list retrieved in one go is identical to
863 # the one containing pagination results
864 self.assertSameOrder(expected_resources, resources)
865
866 def _list_all_with_marker(self, niterations, sort_args):
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200867 # paginate resources one by one, using last fetched resource as a
868 # marker
869 resources = []
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200870 for i in range(niterations):
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200871 pagination_args = sort_args.copy()
872 pagination_args['limit'] = 1
873 if resources:
874 pagination_args['marker'] = resources[-1]['id']
875 body = self.list_method(**pagination_args)
876 resources_ = self._extract_resources(body)
877 self.assertEqual(1, len(resources_))
878 resources.extend(resources_)
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200879 return resources
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200880
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200881 @_require_pagination
882 @_require_sorting
883 def _test_list_pagination_with_marker(self):
884 self._test_list_pagination_iteratively(self._list_all_with_marker)
885
886 def _list_all_with_hrefs(self, niterations, sort_args):
887 # paginate resources one by one, using next href links
888 resources = []
889 prev_links = {}
890
891 for i in range(niterations):
892 if prev_links:
893 uri = self.get_bare_url(prev_links['next'])
894 else:
Ihar Hrachyshka7f79fe62016-06-07 21:23:44 +0200895 sort_args.update(self.list_kwargs)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200896 uri = self.list_client.build_uri(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200897 self.plural_name, limit=1, **sort_args)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200898 prev_links, body = self.list_client.get_uri_with_links(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200899 self.plural_name, uri
900 )
901 resources_ = self._extract_resources(body)
902 self.assertEqual(1, len(resources_))
903 resources.extend(resources_)
904
905 # The last element is empty and does not contain 'next' link
906 uri = self.get_bare_url(prev_links['next'])
907 prev_links, body = self.client.get_uri_with_links(
908 self.plural_name, uri
909 )
910 self.assertNotIn('next', prev_links)
911
912 # Now walk backwards and compare results
913 resources2 = []
914 for i in range(niterations):
915 uri = self.get_bare_url(prev_links['previous'])
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200916 prev_links, body = self.list_client.get_uri_with_links(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200917 self.plural_name, uri
918 )
919 resources_ = self._extract_resources(body)
920 self.assertEqual(1, len(resources_))
921 resources2.extend(resources_)
922
923 self.assertSameOrder(resources, reversed(resources2))
924
925 return resources
926
927 @_require_pagination
928 @_require_sorting
929 def _test_list_pagination_with_href_links(self):
930 self._test_list_pagination_iteratively(self._list_all_with_hrefs)
931
932 @_require_pagination
933 @_require_sorting
934 def _test_list_pagination_page_reverse_with_href_links(
935 self, direction=constants.SORT_DIRECTION_ASC):
936 pagination_args = {
937 'sort_dir': direction,
Armando Migliaccio57581c62016-07-01 10:13:19 -0700938 'sort_key': self.field,
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200939 }
940 body = self.list_method(**pagination_args)
941 expected_resources = self._extract_resources(body)
942
943 page_size = 2
944 pagination_args['limit'] = page_size
945
946 prev_links = {}
947 resources = []
948 num_resources = len(expected_resources)
949 niterations = int(math.ceil(float(num_resources) / page_size))
950 for i in range(niterations):
951 if prev_links:
952 uri = self.get_bare_url(prev_links['previous'])
953 else:
Ihar Hrachyshka7f79fe62016-06-07 21:23:44 +0200954 pagination_args.update(self.list_kwargs)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200955 uri = self.list_client.build_uri(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200956 self.plural_name, page_reverse=True, **pagination_args)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200957 prev_links, body = self.list_client.get_uri_with_links(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200958 self.plural_name, uri
959 )
960 resources_ = self._extract_resources(body)
Béla Vancsicsf1806182016-08-23 07:36:18 +0200961 self.assertGreaterEqual(page_size, len(resources_))
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200962 resources.extend(reversed(resources_))
963
964 self.assertSameOrder(expected_resources, reversed(resources))
965
966 @_require_pagination
967 @_require_sorting
968 def _test_list_pagination_page_reverse_asc(self):
969 self._test_list_pagination_page_reverse(
970 direction=constants.SORT_DIRECTION_ASC)
971
972 @_require_pagination
973 @_require_sorting
974 def _test_list_pagination_page_reverse_desc(self):
975 self._test_list_pagination_page_reverse(
976 direction=constants.SORT_DIRECTION_DESC)
977
978 def _test_list_pagination_page_reverse(self, direction):
979 pagination_args = {
980 'sort_dir': direction,
Armando Migliaccio57581c62016-07-01 10:13:19 -0700981 'sort_key': self.field,
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200982 'limit': 3,
983 }
984 body = self.list_method(**pagination_args)
985 expected_resources = self._extract_resources(body)
986
987 pagination_args['limit'] -= 1
988 pagination_args['marker'] = expected_resources[-1]['id']
989 pagination_args['page_reverse'] = True
990 body = self.list_method(**pagination_args)
991
992 self.assertSameOrder(
993 # the last entry is not included in 2nd result when used as a
994 # marker
995 expected_resources[:-1],
996 self._extract_resources(body))
Victor Morales1be97b42016-09-05 08:50:06 -0500997
998 def _test_list_validation_filters(self):
999 validation_args = {
1000 'unknown_filter': 'value',
1001 }
1002 body = self.list_method(**validation_args)
1003 resources = self._extract_resources(body)
1004 for resource in resources:
1005 self.assertIn(resource['name'], self.resource_names)