blob: 7a575f9738f7d07c9b90adb4646c88fb656cb271 [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
Federico Ressi61b564e2018-07-06 08:10:31 +020063 # Derive from BaseAdminNetworkTest class to have this initialized
64 admin_client = None
65
Daniel Mellado3c0aeab2016-01-29 11:30:25 +000066 @classmethod
67 def get_client_manager(cls, credential_type=None, roles=None,
68 force_new=None):
Genadi Chereshnyacc395c02016-07-25 12:17:37 +030069 manager = super(BaseNetworkTest, cls).get_client_manager(
70 credential_type=credential_type,
71 roles=roles,
72 force_new=force_new
73 )
Daniel Mellado3c0aeab2016-01-29 11:30:25 +000074 # Neutron uses a different clients manager than the one in the Tempest
Jens Harbott860b46a2017-11-15 21:23:15 +000075 # save the original in case mixed tests need it
76 if credential_type == 'primary':
77 cls.os_tempest = manager
Daniel Mellado3c0aeab2016-01-29 11:30:25 +000078 return clients.Manager(manager.credentials)
79
80 @classmethod
81 def skip_checks(cls):
82 super(BaseNetworkTest, cls).skip_checks()
83 if not CONF.service_available.neutron:
84 raise cls.skipException("Neutron support is required")
Federico Ressi0ddc93b2018-04-09 12:01:48 +020085 if (cls._ip_version == const.IP_VERSION_6 and
86 not CONF.network_feature_enabled.ipv6):
Daniel Mellado3c0aeab2016-01-29 11:30:25 +000087 raise cls.skipException("IPv6 Tests are disabled.")
Jakub Libosvar1982aa12017-05-30 11:15:33 +000088 for req_ext in getattr(cls, 'required_extensions', []):
Chandan Kumarc125fd12017-11-15 19:41:01 +053089 if not tutils.is_extension_enabled(req_ext, 'network'):
Jakub Libosvar1982aa12017-05-30 11:15:33 +000090 msg = "%s extension not enabled." % req_ext
91 raise cls.skipException(msg)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +000092
93 @classmethod
94 def setup_credentials(cls):
95 # Create no network resources for these test.
96 cls.set_network_resources()
97 super(BaseNetworkTest, cls).setup_credentials()
98
99 @classmethod
100 def setup_clients(cls):
101 super(BaseNetworkTest, cls).setup_clients()
fumihiko kakumaa216fc12017-07-14 10:43:29 +0900102 cls.client = cls.os_primary.network_client
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000103
104 @classmethod
105 def resource_setup(cls):
106 super(BaseNetworkTest, cls).resource_setup()
107
108 cls.networks = []
Miguel Lavalle124378b2016-09-21 16:41:47 -0500109 cls.admin_networks = []
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000110 cls.subnets = []
Kevin Bentonba3651c2017-09-01 17:13:01 -0700111 cls.admin_subnets = []
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000112 cls.ports = []
113 cls.routers = []
114 cls.floating_ips = []
115 cls.metering_labels = []
116 cls.service_profiles = []
117 cls.flavors = []
118 cls.metering_label_rules = []
119 cls.qos_rules = []
120 cls.qos_policies = []
121 cls.ethertype = "IPv" + str(cls._ip_version)
122 cls.address_scopes = []
123 cls.admin_address_scopes = []
124 cls.subnetpools = []
125 cls.admin_subnetpools = []
Itzik Brownbac51dc2016-10-31 12:25:04 +0000126 cls.security_groups = []
Chandan Kumarc125fd12017-11-15 19:41:01 +0530127 cls.projects = []
Nguyen Phuong An67993fc2017-11-24 11:30:25 +0700128 cls.log_objects = []
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200129 cls.reserved_subnet_cidrs = set()
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000130
131 @classmethod
132 def resource_cleanup(cls):
133 if CONF.service_available.neutron:
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000134 # Clean up floating IPs
135 for floating_ip in cls.floating_ips:
136 cls._try_delete_resource(cls.client.delete_floatingip,
137 floating_ip['id'])
138 # Clean up routers
139 for router in cls.routers:
140 cls._try_delete_resource(cls.delete_router,
141 router)
142 # Clean up metering label rules
143 for metering_label_rule in cls.metering_label_rules:
144 cls._try_delete_resource(
145 cls.admin_client.delete_metering_label_rule,
146 metering_label_rule['id'])
147 # Clean up metering labels
148 for metering_label in cls.metering_labels:
149 cls._try_delete_resource(
150 cls.admin_client.delete_metering_label,
151 metering_label['id'])
152 # Clean up flavors
153 for flavor in cls.flavors:
154 cls._try_delete_resource(
155 cls.admin_client.delete_flavor,
156 flavor['id'])
157 # Clean up service profiles
158 for service_profile in cls.service_profiles:
159 cls._try_delete_resource(
160 cls.admin_client.delete_service_profile,
161 service_profile['id'])
162 # Clean up ports
163 for port in cls.ports:
164 cls._try_delete_resource(cls.client.delete_port,
165 port['id'])
166 # Clean up subnets
167 for subnet in cls.subnets:
168 cls._try_delete_resource(cls.client.delete_subnet,
169 subnet['id'])
Kevin Bentonba3651c2017-09-01 17:13:01 -0700170 # Clean up admin subnets
171 for subnet in cls.admin_subnets:
172 cls._try_delete_resource(cls.admin_client.delete_subnet,
173 subnet['id'])
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000174 # Clean up networks
175 for network in cls.networks:
Federico Ressi61b564e2018-07-06 08:10:31 +0200176 cls._try_delete_resource(cls.delete_network, network)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000177
Miguel Lavalle124378b2016-09-21 16:41:47 -0500178 # Clean up admin networks
179 for network in cls.admin_networks:
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000180 cls._try_delete_resource(cls.admin_client.delete_network,
181 network['id'])
182
Itzik Brownbac51dc2016-10-31 12:25:04 +0000183 # Clean up security groups
184 for secgroup in cls.security_groups:
185 cls._try_delete_resource(cls.client.delete_security_group,
186 secgroup['id'])
187
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000188 for subnetpool in cls.subnetpools:
189 cls._try_delete_resource(cls.client.delete_subnetpool,
190 subnetpool['id'])
191
192 for subnetpool in cls.admin_subnetpools:
193 cls._try_delete_resource(cls.admin_client.delete_subnetpool,
194 subnetpool['id'])
195
196 for address_scope in cls.address_scopes:
197 cls._try_delete_resource(cls.client.delete_address_scope,
198 address_scope['id'])
199
200 for address_scope in cls.admin_address_scopes:
201 cls._try_delete_resource(
202 cls.admin_client.delete_address_scope,
203 address_scope['id'])
204
Chandan Kumarc125fd12017-11-15 19:41:01 +0530205 for project in cls.projects:
206 cls._try_delete_resource(
207 cls.identity_admin_client.delete_project,
208 project['id'])
209
Sławek Kapłońskie100c4d2017-08-23 21:18:34 +0000210 # Clean up QoS rules
211 for qos_rule in cls.qos_rules:
212 cls._try_delete_resource(cls.admin_client.delete_qos_rule,
213 qos_rule['id'])
214 # Clean up QoS policies
215 # as all networks and ports are already removed, QoS policies
216 # shouldn't be "in use"
217 for qos_policy in cls.qos_policies:
218 cls._try_delete_resource(cls.admin_client.delete_qos_policy,
219 qos_policy['id'])
220
Nguyen Phuong An67993fc2017-11-24 11:30:25 +0700221 # Clean up log_objects
222 for log_object in cls.log_objects:
223 cls._try_delete_resource(cls.admin_client.delete_log,
224 log_object['id'])
225
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000226 super(BaseNetworkTest, cls).resource_cleanup()
227
228 @classmethod
229 def _try_delete_resource(cls, delete_callable, *args, **kwargs):
230 """Cleanup resources in case of test-failure
231
232 Some resources are explicitly deleted by the test.
233 If the test failed to delete a resource, this method will execute
234 the appropriate delete methods. Otherwise, the method ignores NotFound
235 exceptions thrown for resources that were correctly deleted by the
236 test.
237
238 :param delete_callable: delete method
239 :param args: arguments for delete method
240 :param kwargs: keyword arguments for delete method
241 """
242 try:
243 delete_callable(*args, **kwargs)
244 # if resource is not found, this means it was deleted in the test
245 except lib_exc.NotFound:
246 pass
247
248 @classmethod
Federico Ressi61b564e2018-07-06 08:10:31 +0200249 def create_network(cls, network_name=None, client=None, external=None,
250 shared=None, provider_network_type=None,
251 provider_physical_network=None,
252 provider_segmentation_id=None, **kwargs):
253 """Create a network.
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000254
Federico Ressi61b564e2018-07-06 08:10:31 +0200255 When client is not provider and admin_client is attribute is not None
256 (for example when using BaseAdminNetworkTest base class) and using any
257 of the convenience parameters (external, shared, provider_network_type,
258 provider_physical_network and provider_segmentation_id) it silently
259 uses admin_client. If the network is not shared then it uses the same
260 project_id as regular client.
261
262 :param network_name: Human-readable name of the network
263
264 :param client: client to be used for connecting to network service
265
266 :param external: indicates whether the network has an external routing
267 facility that's not managed by the networking service.
268
269 :param shared: indicates whether this resource is shared across all
270 projects. By default, only administrative users can change this value.
271 If True and admin_client attribute is not None, then the network is
272 created under administrative project.
273
274 :param provider_network_type: the type of physical network that this
275 network should be mapped to. For example, 'flat', 'vlan', 'vxlan', or
276 'gre'. Valid values depend on a networking back-end.
277
278 :param provider_physical_network: the physical network where this
279 network should be implemented. The Networking API v2.0 does not provide
280 a way to list available physical networks. For example, the Open
281 vSwitch plug-in configuration file defines a symbolic name that maps to
282 specific bridges on each compute host.
283
284 :param provider_segmentation_id: The ID of the isolated segment on the
285 physical network. The network_type attribute defines the segmentation
286 model. For example, if the network_type value is 'vlan', this ID is a
287 vlan identifier. If the network_type value is 'gre', this ID is a gre
288 key.
289
290 :param **kwargs: extra parameters to be forwarded to network service
291 """
292
293 name = (network_name or kwargs.pop('name', None) or
294 data_utils.rand_name('test-network-'))
295
296 # translate convenience parameters
297 admin_client_required = False
298 if provider_network_type:
299 admin_client_required = True
300 kwargs['provider:network_type'] = provider_network_type
301 if provider_physical_network:
302 admin_client_required = True
303 kwargs['provider:physical_network'] = provider_physical_network
304 if provider_segmentation_id:
305 admin_client_required = True
306 kwargs['provider:segmentation_id'] = provider_segmentation_id
307 if external is not None:
308 admin_client_required = True
309 kwargs['router:external'] = bool(external)
310 if shared is not None:
311 admin_client_required = True
312 kwargs['shared'] = bool(shared)
313
314 if not client:
315 if admin_client_required and cls.admin_client:
316 # For convenience silently switch to admin client
317 client = cls.admin_client
318 if not shared:
319 # Keep this network visible from current project
320 project_id = (kwargs.get('project_id') or
321 kwargs.get('tenant_id') or
322 cls.client.tenant_id)
323 kwargs.update(project_id=project_id, tenant_id=project_id)
324 else:
325 # Use default client
326 client = cls.client
327
328 network = client.create_network(name=name, **kwargs)['network']
329 network['client'] = client
330 cls.networks.append(network)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000331 return network
332
333 @classmethod
Federico Ressi61b564e2018-07-06 08:10:31 +0200334 def delete_network(cls, network, client=None):
335 client = client or network.get('client') or cls.client
336 client.delete_network(network['id'])
337
338 @classmethod
339 def create_shared_network(cls, network_name=None, **kwargs):
340 return cls.create_network(name=network_name, shared=True, **kwargs)
Miguel Lavalle124378b2016-09-21 16:41:47 -0500341
342 @classmethod
343 def create_network_keystone_v3(cls, network_name=None, project_id=None,
344 tenant_id=None, client=None):
Federico Ressi61b564e2018-07-06 08:10:31 +0200345 params = {}
346 if project_id:
347 params['project_id'] = project_id
348 if tenant_id:
349 params['tenant_id'] = tenant_id
350 return cls.create_network(name=network_name, client=client, **params)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000351
352 @classmethod
Sławek Kapłońskid98e27d2018-05-07 16:16:28 +0200353 def create_subnet(cls, network, gateway='', cidr=None, mask_bits=None,
Federico Ressi98f20ec2018-05-11 06:09:49 +0200354 ip_version=None, client=None, reserve_cidr=True,
355 **kwargs):
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200356 """Wrapper utility that returns a test subnet.
357
358 Convenient wrapper for client.create_subnet method. It reserves and
359 allocates CIDRs to avoid creating overlapping subnets.
360
361 :param network: network where to create the subnet
362 network['id'] must contain the ID of the network
363
364 :param gateway: gateway IP address
365 It can be a str or a netaddr.IPAddress
366 If gateway is not given, then it will use default address for
367 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 +0200368 if gateway is given as None then no gateway will be assigned
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200369
370 :param cidr: CIDR of the subnet to create
371 It can be either None, a str or a netaddr.IPNetwork instance
372
373 :param mask_bits: CIDR prefix length
374 It can be either None or a numeric value.
375 If cidr parameter is given then mask_bits is used to determinate a
376 sequence of valid CIDR to use as generated.
377 Please see netaddr.IPNetwork.subnet method documentation[1]
378
379 :param ip_version: ip version of generated subnet CIDRs
380 It can be None, IP_VERSION_4 or IP_VERSION_6
381 It has to match given either given CIDR and gateway
382
383 :param ip_version: numeric value (either IP_VERSION_4 or IP_VERSION_6)
384 this value must match CIDR and gateway IP versions if any of them is
385 given
386
387 :param client: client to be used to connect to network service
388
Federico Ressi98f20ec2018-05-11 06:09:49 +0200389 :param reserve_cidr: if True then it reserves assigned CIDR to avoid
390 using the same CIDR for further subnets in the scope of the same
391 test case class
392
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200393 :param **kwargs: optional parameters to be forwarded to wrapped method
394
395 [1] http://netaddr.readthedocs.io/en/latest/tutorial_01.html#supernets-and-subnets # noqa
396 """
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000397
398 # allow tests to use admin client
399 if not client:
400 client = cls.client
401
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200402 if gateway:
403 gateway_ip = netaddr.IPAddress(gateway)
404 if ip_version:
405 if ip_version != gateway_ip.version:
406 raise ValueError(
407 "Gateway IP version doesn't match IP version")
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000408 else:
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200409 ip_version = gateway_ip.version
Sławek Kapłońskid98e27d2018-05-07 16:16:28 +0200410 else:
411 ip_version = ip_version or cls._ip_version
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200412
413 for subnet_cidr in cls.get_subnet_cidrs(
414 ip_version=ip_version, cidr=cidr, mask_bits=mask_bits):
Federico Ressi98f20ec2018-05-11 06:09:49 +0200415 if gateway is not None:
416 kwargs['gateway_ip'] = str(gateway or (subnet_cidr.ip + 1))
417 try:
418 body = client.create_subnet(
419 network_id=network['id'],
420 cidr=str(subnet_cidr),
421 ip_version=subnet_cidr.version,
422 **kwargs)
423 break
424 except lib_exc.BadRequest as e:
425 if 'overlaps with another subnet' not in str(e):
426 raise
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000427 else:
428 message = 'Available CIDR for subnet creation could not be found'
429 raise ValueError(message)
430 subnet = body['subnet']
Kevin Bentonba3651c2017-09-01 17:13:01 -0700431 if client is cls.client:
432 cls.subnets.append(subnet)
433 else:
434 cls.admin_subnets.append(subnet)
Federico Ressi98f20ec2018-05-11 06:09:49 +0200435 if reserve_cidr:
436 cls.reserve_subnet_cidr(subnet_cidr)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000437 return subnet
438
439 @classmethod
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200440 def reserve_subnet_cidr(cls, addr, **ipnetwork_kwargs):
441 """Reserve given subnet CIDR making sure it is not used by create_subnet
442
443 :param addr: the CIDR address to be reserved
444 It can be a str or netaddr.IPNetwork instance
445
446 :param **ipnetwork_kwargs: optional netaddr.IPNetwork constructor
447 parameters
448 """
449
450 if not cls.try_reserve_subnet_cidr(addr, **ipnetwork_kwargs):
451 raise ValueError('Subnet CIDR already reserved: %r'.format(
452 addr))
453
454 @classmethod
455 def try_reserve_subnet_cidr(cls, addr, **ipnetwork_kwargs):
456 """Reserve given subnet CIDR if it hasn't been reserved before
457
458 :param addr: the CIDR address to be reserved
459 It can be a str or netaddr.IPNetwork instance
460
461 :param **ipnetwork_kwargs: optional netaddr.IPNetwork constructor
462 parameters
463
464 :return: True if it wasn't reserved before, False elsewhere.
465 """
466
467 subnet_cidr = netaddr.IPNetwork(addr, **ipnetwork_kwargs)
468 if subnet_cidr in cls.reserved_subnet_cidrs:
469 return False
470 else:
471 cls.reserved_subnet_cidrs.add(subnet_cidr)
472 return True
473
474 @classmethod
475 def get_subnet_cidrs(
476 cls, cidr=None, mask_bits=None, ip_version=None):
477 """Iterate over a sequence of unused subnet CIDR for IP version
478
479 :param cidr: CIDR of the subnet to create
480 It can be either None, a str or a netaddr.IPNetwork instance
481
482 :param mask_bits: CIDR prefix length
483 It can be either None or a numeric value.
484 If cidr parameter is given then mask_bits is used to determinate a
485 sequence of valid CIDR to use as generated.
486 Please see netaddr.IPNetwork.subnet method documentation[1]
487
488 :param ip_version: ip version of generated subnet CIDRs
489 It can be None, IP_VERSION_4 or IP_VERSION_6
490 It has to match given CIDR if given
491
492 :return: iterator over reserved CIDRs of type netaddr.IPNetwork
493
494 [1] http://netaddr.readthedocs.io/en/latest/tutorial_01.html#supernets-and-subnets # noqa
495 """
496
497 if cidr:
498 # Generate subnet CIDRs starting from given CIDR
499 # checking it is of requested IP version
500 cidr = netaddr.IPNetwork(cidr, version=ip_version)
501 else:
502 # Generate subnet CIDRs starting from configured values
503 ip_version = ip_version or cls._ip_version
504 if ip_version == const.IP_VERSION_4:
505 mask_bits = mask_bits or config.safe_get_config_value(
506 'network', 'project_network_mask_bits')
507 cidr = netaddr.IPNetwork(config.safe_get_config_value(
508 'network', 'project_network_cidr'))
509 elif ip_version == const.IP_VERSION_6:
510 mask_bits = config.safe_get_config_value(
511 'network', 'project_network_v6_mask_bits')
512 cidr = netaddr.IPNetwork(config.safe_get_config_value(
513 'network', 'project_network_v6_cidr'))
514 else:
515 raise ValueError('Invalid IP version: {!r}'.format(ip_version))
516
517 if mask_bits:
518 subnet_cidrs = cidr.subnet(mask_bits)
519 else:
520 subnet_cidrs = iter([cidr])
521
522 for subnet_cidr in subnet_cidrs:
523 if subnet_cidr not in cls.reserved_subnet_cidrs:
524 yield subnet_cidr
525
526 @classmethod
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000527 def create_port(cls, network, **kwargs):
528 """Wrapper utility that returns a test port."""
Edan Davidd75e48e2018-01-03 02:49:52 -0500529 if CONF.network.port_vnic_type and 'binding:vnic_type' not in kwargs:
530 kwargs['binding:vnic_type'] = CONF.network.port_vnic_type
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000531 body = cls.client.create_port(network_id=network['id'],
532 **kwargs)
533 port = body['port']
534 cls.ports.append(port)
535 return port
536
537 @classmethod
538 def update_port(cls, port, **kwargs):
539 """Wrapper utility that updates a test port."""
540 body = cls.client.update_port(port['id'],
541 **kwargs)
542 return body['port']
543
544 @classmethod
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300545 def _create_router_with_client(
546 cls, client, router_name=None, admin_state_up=False,
547 external_network_id=None, enable_snat=None, **kwargs
548 ):
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000549 ext_gw_info = {}
550 if external_network_id:
551 ext_gw_info['network_id'] = external_network_id
YAMAMOTO Takashi9bd4f972017-06-20 12:49:30 +0900552 if enable_snat is not None:
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000553 ext_gw_info['enable_snat'] = enable_snat
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300554 body = client.create_router(
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000555 router_name, external_gateway_info=ext_gw_info,
556 admin_state_up=admin_state_up, **kwargs)
557 router = body['router']
558 cls.routers.append(router)
559 return router
560
561 @classmethod
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300562 def create_router(cls, *args, **kwargs):
563 return cls._create_router_with_client(cls.client, *args, **kwargs)
564
565 @classmethod
566 def create_admin_router(cls, *args, **kwargs):
rajat294495c042017-06-28 15:37:16 +0530567 return cls._create_router_with_client(cls.os_admin.network_client,
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300568 *args, **kwargs)
569
570 @classmethod
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000571 def create_floatingip(cls, external_network_id):
572 """Wrapper utility that returns a test floating IP."""
573 body = cls.client.create_floatingip(
574 floating_network_id=external_network_id)
575 fip = body['floatingip']
576 cls.floating_ips.append(fip)
577 return fip
578
579 @classmethod
580 def create_router_interface(cls, router_id, subnet_id):
581 """Wrapper utility that returns a router interface."""
582 interface = cls.client.add_router_interface_with_subnet_id(
583 router_id, subnet_id)
584 return interface
585
586 @classmethod
Sławek Kapłońskiff294062016-12-04 15:00:54 +0000587 def get_supported_qos_rule_types(cls):
588 body = cls.client.list_qos_rule_types()
589 return [rule_type['type'] for rule_type in body['rule_types']]
590
591 @classmethod
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200592 def create_qos_policy(cls, name, description=None, shared=False,
Hirofumi Ichihara39a6ee12017-08-23 13:55:12 +0900593 tenant_id=None, is_default=False):
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000594 """Wrapper utility that returns a test QoS policy."""
595 body = cls.admin_client.create_qos_policy(
Hirofumi Ichihara39a6ee12017-08-23 13:55:12 +0900596 name, description, shared, tenant_id, is_default)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000597 qos_policy = body['policy']
598 cls.qos_policies.append(qos_policy)
599 return qos_policy
600
601 @classmethod
Sławek Kapłoński153f3452017-03-24 22:04:53 +0000602 def create_qos_bandwidth_limit_rule(cls, policy_id, max_kbps,
603 max_burst_kbps,
Chandan Kumarc125fd12017-11-15 19:41:01 +0530604 direction=const.EGRESS_DIRECTION):
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000605 """Wrapper utility that returns a test QoS bandwidth limit rule."""
606 body = cls.admin_client.create_bandwidth_limit_rule(
Sławek Kapłoński153f3452017-03-24 22:04:53 +0000607 policy_id, max_kbps, max_burst_kbps, direction)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000608 qos_rule = body['bandwidth_limit_rule']
609 cls.qos_rules.append(qos_rule)
610 return qos_rule
611
612 @classmethod
Jakub Libosvar83704832017-12-06 16:02:28 +0000613 def delete_router(cls, router, client=None):
614 client = client or cls.client
615 body = client.list_router_interfaces(router['id'])
Chandan Kumarc125fd12017-11-15 19:41:01 +0530616 interfaces = [port for port in body['ports']
617 if port['device_owner'] in const.ROUTER_INTERFACE_OWNERS]
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000618 for i in interfaces:
619 try:
Jakub Libosvar83704832017-12-06 16:02:28 +0000620 client.remove_router_interface_with_subnet_id(
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000621 router['id'], i['fixed_ips'][0]['subnet_id'])
622 except lib_exc.NotFound:
623 pass
Jakub Libosvar83704832017-12-06 16:02:28 +0000624 client.delete_router(router['id'])
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000625
626 @classmethod
627 def create_address_scope(cls, name, is_admin=False, **kwargs):
628 if is_admin:
629 body = cls.admin_client.create_address_scope(name=name, **kwargs)
630 cls.admin_address_scopes.append(body['address_scope'])
631 else:
632 body = cls.client.create_address_scope(name=name, **kwargs)
633 cls.address_scopes.append(body['address_scope'])
634 return body['address_scope']
635
636 @classmethod
637 def create_subnetpool(cls, name, is_admin=False, **kwargs):
638 if is_admin:
639 body = cls.admin_client.create_subnetpool(name, **kwargs)
640 cls.admin_subnetpools.append(body['subnetpool'])
641 else:
642 body = cls.client.create_subnetpool(name, **kwargs)
643 cls.subnetpools.append(body['subnetpool'])
644 return body['subnetpool']
645
Chandan Kumarc125fd12017-11-15 19:41:01 +0530646 @classmethod
647 def create_project(cls, name=None, description=None):
648 test_project = name or data_utils.rand_name('test_project_')
649 test_description = description or data_utils.rand_name('desc_')
650 project = cls.identity_admin_client.create_project(
651 name=test_project,
652 description=test_description)['project']
653 cls.projects.append(project)
654 return project
655
656 @classmethod
657 def create_security_group(cls, name, **kwargs):
658 body = cls.client.create_security_group(name=name, **kwargs)
659 cls.security_groups.append(body['security_group'])
660 return body['security_group']
661
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000662
663class BaseAdminNetworkTest(BaseNetworkTest):
664
665 credentials = ['primary', 'admin']
666
667 @classmethod
668 def setup_clients(cls):
669 super(BaseAdminNetworkTest, cls).setup_clients()
fumihiko kakumaa216fc12017-07-14 10:43:29 +0900670 cls.admin_client = cls.os_admin.network_client
Jakub Libosvarf5758012017-08-15 13:45:30 +0000671 cls.identity_admin_client = cls.os_admin.projects_client
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000672
673 @classmethod
674 def create_metering_label(cls, name, description):
675 """Wrapper utility that returns a test metering label."""
676 body = cls.admin_client.create_metering_label(
677 description=description,
678 name=data_utils.rand_name("metering-label"))
679 metering_label = body['metering_label']
680 cls.metering_labels.append(metering_label)
681 return metering_label
682
683 @classmethod
684 def create_metering_label_rule(cls, remote_ip_prefix, direction,
685 metering_label_id):
686 """Wrapper utility that returns a test metering label rule."""
687 body = cls.admin_client.create_metering_label_rule(
688 remote_ip_prefix=remote_ip_prefix, direction=direction,
689 metering_label_id=metering_label_id)
690 metering_label_rule = body['metering_label_rule']
691 cls.metering_label_rules.append(metering_label_rule)
692 return metering_label_rule
693
694 @classmethod
695 def create_flavor(cls, name, description, service_type):
696 """Wrapper utility that returns a test flavor."""
697 body = cls.admin_client.create_flavor(
698 description=description, service_type=service_type,
699 name=name)
700 flavor = body['flavor']
701 cls.flavors.append(flavor)
702 return flavor
703
704 @classmethod
705 def create_service_profile(cls, description, metainfo, driver):
706 """Wrapper utility that returns a test service profile."""
707 body = cls.admin_client.create_service_profile(
708 driver=driver, metainfo=metainfo, description=description)
709 service_profile = body['service_profile']
710 cls.service_profiles.append(service_profile)
711 return service_profile
712
713 @classmethod
Nguyen Phuong An67993fc2017-11-24 11:30:25 +0700714 def create_log(cls, name, description=None,
715 resource_type='security_group', resource_id=None,
716 target_id=None, event='ALL', enabled=True):
717 """Wrapper utility that returns a test log object."""
718 log_args = {'name': name,
719 'description': description,
720 'resource_type': resource_type,
721 'resource_id': resource_id,
722 'target_id': target_id,
723 'event': event,
724 'enabled': enabled}
725 body = cls.admin_client.create_log(**log_args)
726 log_object = body['log']
727 cls.log_objects.append(log_object)
728 return log_object
729
730 @classmethod
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000731 def get_unused_ip(cls, net_id, ip_version=None):
Gary Kotton011345f2016-06-15 08:04:31 -0700732 """Get an unused ip address in a allocation pool of net"""
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000733 body = cls.admin_client.list_ports(network_id=net_id)
734 ports = body['ports']
735 used_ips = []
736 for port in ports:
737 used_ips.extend(
738 [fixed_ip['ip_address'] for fixed_ip in port['fixed_ips']])
739 body = cls.admin_client.list_subnets(network_id=net_id)
740 subnets = body['subnets']
741
742 for subnet in subnets:
743 if ip_version and subnet['ip_version'] != ip_version:
744 continue
745 cidr = subnet['cidr']
746 allocation_pools = subnet['allocation_pools']
747 iterators = []
748 if allocation_pools:
749 for allocation_pool in allocation_pools:
750 iterators.append(netaddr.iter_iprange(
751 allocation_pool['start'], allocation_pool['end']))
752 else:
753 net = netaddr.IPNetwork(cidr)
754
755 def _iterip():
756 for ip in net:
757 if ip not in (net.network, net.broadcast):
758 yield ip
759 iterators.append(iter(_iterip()))
760
761 for iterator in iterators:
762 for ip in iterator:
763 if str(ip) not in used_ips:
764 return str(ip)
765
766 message = (
767 "net(%s) has no usable IP address in allocation pools" % net_id)
768 raise exceptions.InvalidConfiguration(message)
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200769
770
Sławek Kapłońskiff294062016-12-04 15:00:54 +0000771def require_qos_rule_type(rule_type):
772 def decorator(f):
773 @functools.wraps(f)
774 def wrapper(self, *func_args, **func_kwargs):
775 if rule_type not in self.get_supported_qos_rule_types():
776 raise self.skipException(
777 "%s rule type is required." % rule_type)
778 return f(self, *func_args, **func_kwargs)
779 return wrapper
780 return decorator
781
782
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200783def _require_sorting(f):
784 @functools.wraps(f)
785 def inner(self, *args, **kwargs):
Chandan Kumarc125fd12017-11-15 19:41:01 +0530786 if not tutils.is_extension_enabled("sorting", "network"):
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200787 self.skipTest('Sorting feature is required')
788 return f(self, *args, **kwargs)
789 return inner
790
791
792def _require_pagination(f):
793 @functools.wraps(f)
794 def inner(self, *args, **kwargs):
Chandan Kumarc125fd12017-11-15 19:41:01 +0530795 if not tutils.is_extension_enabled("pagination", "network"):
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200796 self.skipTest('Pagination feature is required')
797 return f(self, *args, **kwargs)
798 return inner
799
800
801class BaseSearchCriteriaTest(BaseNetworkTest):
802
803 # This should be defined by subclasses to reflect resource name to test
804 resource = None
805
Armando Migliaccio57581c62016-07-01 10:13:19 -0700806 field = 'name'
807
Ihar Hrachyshkaa8fe5a12016-05-24 14:50:58 +0200808 # NOTE(ihrachys): some names, like those starting with an underscore (_)
809 # are sorted differently depending on whether the plugin implements native
810 # sorting support, or not. So we avoid any such cases here, sticking to
811 # alphanumeric. Also test a case when there are multiple resources with the
812 # same name
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200813 resource_names = ('test1', 'abc1', 'test10', '123test') + ('test1',)
814
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200815 force_tenant_isolation = True
816
Ihar Hrachyshkaa8fe5a12016-05-24 14:50:58 +0200817 list_kwargs = {}
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200818
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200819 list_as_admin = False
820
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200821 def assertSameOrder(self, original, actual):
822 # gracefully handle iterators passed
823 original = list(original)
824 actual = list(actual)
825 self.assertEqual(len(original), len(actual))
826 for expected, res in zip(original, actual):
Armando Migliaccio57581c62016-07-01 10:13:19 -0700827 self.assertEqual(expected[self.field], res[self.field])
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200828
829 @utils.classproperty
830 def plural_name(self):
831 return '%ss' % self.resource
832
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200833 @property
834 def list_client(self):
835 return self.admin_client if self.list_as_admin else self.client
836
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200837 def list_method(self, *args, **kwargs):
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200838 method = getattr(self.list_client, 'list_%s' % self.plural_name)
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200839 kwargs.update(self.list_kwargs)
840 return method(*args, **kwargs)
841
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200842 def get_bare_url(self, url):
843 base_url = self.client.base_url
844 self.assertTrue(url.startswith(base_url))
845 return url[len(base_url):]
846
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200847 @classmethod
848 def _extract_resources(cls, body):
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200849 return body[cls.plural_name]
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200850
851 def _test_list_sorts(self, direction):
852 sort_args = {
853 'sort_dir': direction,
Armando Migliaccio57581c62016-07-01 10:13:19 -0700854 'sort_key': self.field
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200855 }
856 body = self.list_method(**sort_args)
857 resources = self._extract_resources(body)
858 self.assertNotEmpty(
859 resources, "%s list returned is empty" % self.resource)
Armando Migliaccio57581c62016-07-01 10:13:19 -0700860 retrieved_names = [res[self.field] for res in resources]
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200861 expected = sorted(retrieved_names)
862 if direction == constants.SORT_DIRECTION_DESC:
863 expected = list(reversed(expected))
864 self.assertEqual(expected, retrieved_names)
865
866 @_require_sorting
867 def _test_list_sorts_asc(self):
868 self._test_list_sorts(constants.SORT_DIRECTION_ASC)
869
870 @_require_sorting
871 def _test_list_sorts_desc(self):
872 self._test_list_sorts(constants.SORT_DIRECTION_DESC)
873
874 @_require_pagination
875 def _test_list_pagination(self):
876 for limit in range(1, len(self.resource_names) + 1):
877 pagination_args = {
878 'limit': limit,
879 }
880 body = self.list_method(**pagination_args)
881 resources = self._extract_resources(body)
882 self.assertEqual(limit, len(resources))
883
884 @_require_pagination
885 def _test_list_no_pagination_limit_0(self):
886 pagination_args = {
887 'limit': 0,
888 }
889 body = self.list_method(**pagination_args)
890 resources = self._extract_resources(body)
Béla Vancsicsf1806182016-08-23 07:36:18 +0200891 self.assertGreaterEqual(len(resources), len(self.resource_names))
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200892
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200893 def _test_list_pagination_iteratively(self, lister):
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200894 # first, collect all resources for later comparison
895 sort_args = {
896 'sort_dir': constants.SORT_DIRECTION_ASC,
Armando Migliaccio57581c62016-07-01 10:13:19 -0700897 'sort_key': self.field
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200898 }
899 body = self.list_method(**sort_args)
900 expected_resources = self._extract_resources(body)
901 self.assertNotEmpty(expected_resources)
902
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200903 resources = lister(
904 len(expected_resources), sort_args
905 )
906
907 # finally, compare that the list retrieved in one go is identical to
908 # the one containing pagination results
909 self.assertSameOrder(expected_resources, resources)
910
911 def _list_all_with_marker(self, niterations, sort_args):
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200912 # paginate resources one by one, using last fetched resource as a
913 # marker
914 resources = []
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200915 for i in range(niterations):
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200916 pagination_args = sort_args.copy()
917 pagination_args['limit'] = 1
918 if resources:
919 pagination_args['marker'] = resources[-1]['id']
920 body = self.list_method(**pagination_args)
921 resources_ = self._extract_resources(body)
922 self.assertEqual(1, len(resources_))
923 resources.extend(resources_)
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200924 return resources
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200925
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200926 @_require_pagination
927 @_require_sorting
928 def _test_list_pagination_with_marker(self):
929 self._test_list_pagination_iteratively(self._list_all_with_marker)
930
931 def _list_all_with_hrefs(self, niterations, sort_args):
932 # paginate resources one by one, using next href links
933 resources = []
934 prev_links = {}
935
936 for i in range(niterations):
937 if prev_links:
938 uri = self.get_bare_url(prev_links['next'])
939 else:
Ihar Hrachyshka7f79fe62016-06-07 21:23:44 +0200940 sort_args.update(self.list_kwargs)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200941 uri = self.list_client.build_uri(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200942 self.plural_name, limit=1, **sort_args)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200943 prev_links, body = self.list_client.get_uri_with_links(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200944 self.plural_name, uri
945 )
946 resources_ = self._extract_resources(body)
947 self.assertEqual(1, len(resources_))
948 resources.extend(resources_)
949
950 # The last element is empty and does not contain 'next' link
951 uri = self.get_bare_url(prev_links['next'])
952 prev_links, body = self.client.get_uri_with_links(
953 self.plural_name, uri
954 )
955 self.assertNotIn('next', prev_links)
956
957 # Now walk backwards and compare results
958 resources2 = []
959 for i in range(niterations):
960 uri = self.get_bare_url(prev_links['previous'])
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200961 prev_links, body = self.list_client.get_uri_with_links(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200962 self.plural_name, uri
963 )
964 resources_ = self._extract_resources(body)
965 self.assertEqual(1, len(resources_))
966 resources2.extend(resources_)
967
968 self.assertSameOrder(resources, reversed(resources2))
969
970 return resources
971
972 @_require_pagination
973 @_require_sorting
974 def _test_list_pagination_with_href_links(self):
975 self._test_list_pagination_iteratively(self._list_all_with_hrefs)
976
977 @_require_pagination
978 @_require_sorting
979 def _test_list_pagination_page_reverse_with_href_links(
980 self, direction=constants.SORT_DIRECTION_ASC):
981 pagination_args = {
982 'sort_dir': direction,
Armando Migliaccio57581c62016-07-01 10:13:19 -0700983 'sort_key': self.field,
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200984 }
985 body = self.list_method(**pagination_args)
986 expected_resources = self._extract_resources(body)
987
988 page_size = 2
989 pagination_args['limit'] = page_size
990
991 prev_links = {}
992 resources = []
993 num_resources = len(expected_resources)
994 niterations = int(math.ceil(float(num_resources) / page_size))
995 for i in range(niterations):
996 if prev_links:
997 uri = self.get_bare_url(prev_links['previous'])
998 else:
Ihar Hrachyshka7f79fe62016-06-07 21:23:44 +0200999 pagination_args.update(self.list_kwargs)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001000 uri = self.list_client.build_uri(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001001 self.plural_name, page_reverse=True, **pagination_args)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001002 prev_links, body = self.list_client.get_uri_with_links(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001003 self.plural_name, uri
1004 )
1005 resources_ = self._extract_resources(body)
Béla Vancsicsf1806182016-08-23 07:36:18 +02001006 self.assertGreaterEqual(page_size, len(resources_))
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001007 resources.extend(reversed(resources_))
1008
1009 self.assertSameOrder(expected_resources, reversed(resources))
1010
1011 @_require_pagination
1012 @_require_sorting
1013 def _test_list_pagination_page_reverse_asc(self):
1014 self._test_list_pagination_page_reverse(
1015 direction=constants.SORT_DIRECTION_ASC)
1016
1017 @_require_pagination
1018 @_require_sorting
1019 def _test_list_pagination_page_reverse_desc(self):
1020 self._test_list_pagination_page_reverse(
1021 direction=constants.SORT_DIRECTION_DESC)
1022
1023 def _test_list_pagination_page_reverse(self, direction):
1024 pagination_args = {
1025 'sort_dir': direction,
Armando Migliaccio57581c62016-07-01 10:13:19 -07001026 'sort_key': self.field,
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001027 'limit': 3,
1028 }
1029 body = self.list_method(**pagination_args)
1030 expected_resources = self._extract_resources(body)
1031
1032 pagination_args['limit'] -= 1
1033 pagination_args['marker'] = expected_resources[-1]['id']
1034 pagination_args['page_reverse'] = True
1035 body = self.list_method(**pagination_args)
1036
1037 self.assertSameOrder(
1038 # the last entry is not included in 2nd result when used as a
1039 # marker
1040 expected_resources[:-1],
1041 self._extract_resources(body))
Victor Morales1be97b42016-09-05 08:50:06 -05001042
1043 def _test_list_validation_filters(self):
1044 validation_args = {
1045 'unknown_filter': 'value',
1046 }
1047 body = self.list_method(**validation_args)
1048 resources = self._extract_resources(body)
1049 for resource in resources:
1050 self.assertIn(resource['name'], self.resource_names)