blob: 474f20351dcbd6486d8c9cd8d1c237ca6a8b49c6 [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
Federico Ressia69dcd52018-07-06 09:45:34 +020066 external_network_id = CONF.network.public_network_id
67
Daniel Mellado3c0aeab2016-01-29 11:30:25 +000068 @classmethod
69 def get_client_manager(cls, credential_type=None, roles=None,
70 force_new=None):
Genadi Chereshnyacc395c02016-07-25 12:17:37 +030071 manager = super(BaseNetworkTest, cls).get_client_manager(
72 credential_type=credential_type,
73 roles=roles,
74 force_new=force_new
75 )
Daniel Mellado3c0aeab2016-01-29 11:30:25 +000076 # Neutron uses a different clients manager than the one in the Tempest
Jens Harbott860b46a2017-11-15 21:23:15 +000077 # save the original in case mixed tests need it
78 if credential_type == 'primary':
79 cls.os_tempest = manager
Daniel Mellado3c0aeab2016-01-29 11:30:25 +000080 return clients.Manager(manager.credentials)
81
82 @classmethod
83 def skip_checks(cls):
84 super(BaseNetworkTest, cls).skip_checks()
85 if not CONF.service_available.neutron:
86 raise cls.skipException("Neutron support is required")
Federico Ressi0ddc93b2018-04-09 12:01:48 +020087 if (cls._ip_version == const.IP_VERSION_6 and
88 not CONF.network_feature_enabled.ipv6):
Daniel Mellado3c0aeab2016-01-29 11:30:25 +000089 raise cls.skipException("IPv6 Tests are disabled.")
Jakub Libosvar1982aa12017-05-30 11:15:33 +000090 for req_ext in getattr(cls, 'required_extensions', []):
Chandan Kumarc125fd12017-11-15 19:41:01 +053091 if not tutils.is_extension_enabled(req_ext, 'network'):
Jakub Libosvar1982aa12017-05-30 11:15:33 +000092 msg = "%s extension not enabled." % req_ext
93 raise cls.skipException(msg)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +000094
95 @classmethod
96 def setup_credentials(cls):
97 # Create no network resources for these test.
98 cls.set_network_resources()
99 super(BaseNetworkTest, cls).setup_credentials()
100
101 @classmethod
102 def setup_clients(cls):
103 super(BaseNetworkTest, cls).setup_clients()
fumihiko kakumaa216fc12017-07-14 10:43:29 +0900104 cls.client = cls.os_primary.network_client
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000105
106 @classmethod
107 def resource_setup(cls):
108 super(BaseNetworkTest, cls).resource_setup()
109
110 cls.networks = []
Miguel Lavalle124378b2016-09-21 16:41:47 -0500111 cls.admin_networks = []
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000112 cls.subnets = []
Kevin Bentonba3651c2017-09-01 17:13:01 -0700113 cls.admin_subnets = []
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000114 cls.ports = []
115 cls.routers = []
116 cls.floating_ips = []
117 cls.metering_labels = []
118 cls.service_profiles = []
119 cls.flavors = []
120 cls.metering_label_rules = []
121 cls.qos_rules = []
122 cls.qos_policies = []
123 cls.ethertype = "IPv" + str(cls._ip_version)
124 cls.address_scopes = []
125 cls.admin_address_scopes = []
126 cls.subnetpools = []
127 cls.admin_subnetpools = []
Itzik Brownbac51dc2016-10-31 12:25:04 +0000128 cls.security_groups = []
Chandan Kumarc125fd12017-11-15 19:41:01 +0530129 cls.projects = []
Nguyen Phuong An67993fc2017-11-24 11:30:25 +0700130 cls.log_objects = []
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200131 cls.reserved_subnet_cidrs = set()
Federico Ressiab286e42018-06-19 09:52:10 +0200132 cls.keypairs = []
Federico Ressi82e83e32018-07-03 14:19:55 +0200133 cls.trunks = []
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000134
135 @classmethod
136 def resource_cleanup(cls):
137 if CONF.service_available.neutron:
Federico Ressi82e83e32018-07-03 14:19:55 +0200138 # Clean up trunks
139 for trunk in cls.trunks:
140 cls._try_delete_resource(cls.delete_trunk, trunk)
141
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000142 # Clean up floating IPs
143 for floating_ip in cls.floating_ips:
Federico Ressia69dcd52018-07-06 09:45:34 +0200144 cls._try_delete_resource(cls.delete_floatingip, floating_ip)
145
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000146 # Clean up routers
147 for router in cls.routers:
148 cls._try_delete_resource(cls.delete_router,
149 router)
150 # Clean up metering label rules
151 for metering_label_rule in cls.metering_label_rules:
152 cls._try_delete_resource(
153 cls.admin_client.delete_metering_label_rule,
154 metering_label_rule['id'])
155 # Clean up metering labels
156 for metering_label in cls.metering_labels:
157 cls._try_delete_resource(
158 cls.admin_client.delete_metering_label,
159 metering_label['id'])
160 # Clean up flavors
161 for flavor in cls.flavors:
162 cls._try_delete_resource(
163 cls.admin_client.delete_flavor,
164 flavor['id'])
165 # Clean up service profiles
166 for service_profile in cls.service_profiles:
167 cls._try_delete_resource(
168 cls.admin_client.delete_service_profile,
169 service_profile['id'])
170 # Clean up ports
171 for port in cls.ports:
172 cls._try_delete_resource(cls.client.delete_port,
173 port['id'])
174 # Clean up subnets
175 for subnet in cls.subnets:
176 cls._try_delete_resource(cls.client.delete_subnet,
177 subnet['id'])
Kevin Bentonba3651c2017-09-01 17:13:01 -0700178 # Clean up admin subnets
179 for subnet in cls.admin_subnets:
180 cls._try_delete_resource(cls.admin_client.delete_subnet,
181 subnet['id'])
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000182 # Clean up networks
183 for network in cls.networks:
Federico Ressi61b564e2018-07-06 08:10:31 +0200184 cls._try_delete_resource(cls.delete_network, network)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000185
Miguel Lavalle124378b2016-09-21 16:41:47 -0500186 # Clean up admin networks
187 for network in cls.admin_networks:
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000188 cls._try_delete_resource(cls.admin_client.delete_network,
189 network['id'])
190
Itzik Brownbac51dc2016-10-31 12:25:04 +0000191 # Clean up security groups
192 for secgroup in cls.security_groups:
193 cls._try_delete_resource(cls.client.delete_security_group,
194 secgroup['id'])
195
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000196 for subnetpool in cls.subnetpools:
197 cls._try_delete_resource(cls.client.delete_subnetpool,
198 subnetpool['id'])
199
200 for subnetpool in cls.admin_subnetpools:
201 cls._try_delete_resource(cls.admin_client.delete_subnetpool,
202 subnetpool['id'])
203
204 for address_scope in cls.address_scopes:
205 cls._try_delete_resource(cls.client.delete_address_scope,
206 address_scope['id'])
207
208 for address_scope in cls.admin_address_scopes:
209 cls._try_delete_resource(
210 cls.admin_client.delete_address_scope,
211 address_scope['id'])
212
Chandan Kumarc125fd12017-11-15 19:41:01 +0530213 for project in cls.projects:
214 cls._try_delete_resource(
215 cls.identity_admin_client.delete_project,
216 project['id'])
217
Sławek Kapłońskie100c4d2017-08-23 21:18:34 +0000218 # Clean up QoS rules
219 for qos_rule in cls.qos_rules:
220 cls._try_delete_resource(cls.admin_client.delete_qos_rule,
221 qos_rule['id'])
222 # Clean up QoS policies
223 # as all networks and ports are already removed, QoS policies
224 # shouldn't be "in use"
225 for qos_policy in cls.qos_policies:
226 cls._try_delete_resource(cls.admin_client.delete_qos_policy,
227 qos_policy['id'])
228
Nguyen Phuong An67993fc2017-11-24 11:30:25 +0700229 # Clean up log_objects
230 for log_object in cls.log_objects:
231 cls._try_delete_resource(cls.admin_client.delete_log,
232 log_object['id'])
233
Federico Ressiab286e42018-06-19 09:52:10 +0200234 for keypair in cls.keypairs:
235 cls._try_delete_resource(cls.delete_keypair, keypair)
236
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000237 super(BaseNetworkTest, cls).resource_cleanup()
238
239 @classmethod
240 def _try_delete_resource(cls, delete_callable, *args, **kwargs):
241 """Cleanup resources in case of test-failure
242
243 Some resources are explicitly deleted by the test.
244 If the test failed to delete a resource, this method will execute
245 the appropriate delete methods. Otherwise, the method ignores NotFound
246 exceptions thrown for resources that were correctly deleted by the
247 test.
248
249 :param delete_callable: delete method
250 :param args: arguments for delete method
251 :param kwargs: keyword arguments for delete method
252 """
253 try:
254 delete_callable(*args, **kwargs)
255 # if resource is not found, this means it was deleted in the test
256 except lib_exc.NotFound:
257 pass
258
259 @classmethod
Federico Ressi61b564e2018-07-06 08:10:31 +0200260 def create_network(cls, network_name=None, client=None, external=None,
261 shared=None, provider_network_type=None,
262 provider_physical_network=None,
263 provider_segmentation_id=None, **kwargs):
264 """Create a network.
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000265
Federico Ressi61b564e2018-07-06 08:10:31 +0200266 When client is not provider and admin_client is attribute is not None
267 (for example when using BaseAdminNetworkTest base class) and using any
268 of the convenience parameters (external, shared, provider_network_type,
269 provider_physical_network and provider_segmentation_id) it silently
270 uses admin_client. If the network is not shared then it uses the same
271 project_id as regular client.
272
273 :param network_name: Human-readable name of the network
274
275 :param client: client to be used for connecting to network service
276
277 :param external: indicates whether the network has an external routing
278 facility that's not managed by the networking service.
279
280 :param shared: indicates whether this resource is shared across all
281 projects. By default, only administrative users can change this value.
282 If True and admin_client attribute is not None, then the network is
283 created under administrative project.
284
285 :param provider_network_type: the type of physical network that this
286 network should be mapped to. For example, 'flat', 'vlan', 'vxlan', or
287 'gre'. Valid values depend on a networking back-end.
288
289 :param provider_physical_network: the physical network where this
290 network should be implemented. The Networking API v2.0 does not provide
291 a way to list available physical networks. For example, the Open
292 vSwitch plug-in configuration file defines a symbolic name that maps to
293 specific bridges on each compute host.
294
295 :param provider_segmentation_id: The ID of the isolated segment on the
296 physical network. The network_type attribute defines the segmentation
297 model. For example, if the network_type value is 'vlan', this ID is a
298 vlan identifier. If the network_type value is 'gre', this ID is a gre
299 key.
300
301 :param **kwargs: extra parameters to be forwarded to network service
302 """
303
304 name = (network_name or kwargs.pop('name', None) or
305 data_utils.rand_name('test-network-'))
306
307 # translate convenience parameters
308 admin_client_required = False
309 if provider_network_type:
310 admin_client_required = True
311 kwargs['provider:network_type'] = provider_network_type
312 if provider_physical_network:
313 admin_client_required = True
314 kwargs['provider:physical_network'] = provider_physical_network
315 if provider_segmentation_id:
316 admin_client_required = True
317 kwargs['provider:segmentation_id'] = provider_segmentation_id
318 if external is not None:
319 admin_client_required = True
320 kwargs['router:external'] = bool(external)
321 if shared is not None:
322 admin_client_required = True
323 kwargs['shared'] = bool(shared)
324
325 if not client:
326 if admin_client_required and cls.admin_client:
327 # For convenience silently switch to admin client
328 client = cls.admin_client
329 if not shared:
330 # Keep this network visible from current project
331 project_id = (kwargs.get('project_id') or
332 kwargs.get('tenant_id') or
333 cls.client.tenant_id)
334 kwargs.update(project_id=project_id, tenant_id=project_id)
335 else:
336 # Use default client
337 client = cls.client
338
339 network = client.create_network(name=name, **kwargs)['network']
340 network['client'] = client
341 cls.networks.append(network)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000342 return network
343
344 @classmethod
Federico Ressi61b564e2018-07-06 08:10:31 +0200345 def delete_network(cls, network, client=None):
346 client = client or network.get('client') or cls.client
347 client.delete_network(network['id'])
348
349 @classmethod
350 def create_shared_network(cls, network_name=None, **kwargs):
351 return cls.create_network(name=network_name, shared=True, **kwargs)
Miguel Lavalle124378b2016-09-21 16:41:47 -0500352
353 @classmethod
354 def create_network_keystone_v3(cls, network_name=None, project_id=None,
355 tenant_id=None, client=None):
Federico Ressi61b564e2018-07-06 08:10:31 +0200356 params = {}
357 if project_id:
358 params['project_id'] = project_id
359 if tenant_id:
360 params['tenant_id'] = tenant_id
361 return cls.create_network(name=network_name, client=client, **params)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000362
363 @classmethod
Sławek Kapłońskid98e27d2018-05-07 16:16:28 +0200364 def create_subnet(cls, network, gateway='', cidr=None, mask_bits=None,
Federico Ressi98f20ec2018-05-11 06:09:49 +0200365 ip_version=None, client=None, reserve_cidr=True,
366 **kwargs):
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200367 """Wrapper utility that returns a test subnet.
368
369 Convenient wrapper for client.create_subnet method. It reserves and
370 allocates CIDRs to avoid creating overlapping subnets.
371
372 :param network: network where to create the subnet
373 network['id'] must contain the ID of the network
374
375 :param gateway: gateway IP address
376 It can be a str or a netaddr.IPAddress
377 If gateway is not given, then it will use default address for
378 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 +0200379 if gateway is given as None then no gateway will be assigned
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200380
381 :param cidr: CIDR of the subnet to create
382 It can be either None, a str or a netaddr.IPNetwork instance
383
384 :param mask_bits: CIDR prefix length
385 It can be either None or a numeric value.
386 If cidr parameter is given then mask_bits is used to determinate a
387 sequence of valid CIDR to use as generated.
388 Please see netaddr.IPNetwork.subnet method documentation[1]
389
390 :param ip_version: ip version of generated subnet CIDRs
391 It can be None, IP_VERSION_4 or IP_VERSION_6
392 It has to match given either given CIDR and gateway
393
394 :param ip_version: numeric value (either IP_VERSION_4 or IP_VERSION_6)
395 this value must match CIDR and gateway IP versions if any of them is
396 given
397
398 :param client: client to be used to connect to network service
399
Federico Ressi98f20ec2018-05-11 06:09:49 +0200400 :param reserve_cidr: if True then it reserves assigned CIDR to avoid
401 using the same CIDR for further subnets in the scope of the same
402 test case class
403
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200404 :param **kwargs: optional parameters to be forwarded to wrapped method
405
406 [1] http://netaddr.readthedocs.io/en/latest/tutorial_01.html#supernets-and-subnets # noqa
407 """
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000408
409 # allow tests to use admin client
410 if not client:
411 client = cls.client
412
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200413 if gateway:
414 gateway_ip = netaddr.IPAddress(gateway)
415 if ip_version:
416 if ip_version != gateway_ip.version:
417 raise ValueError(
418 "Gateway IP version doesn't match IP version")
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000419 else:
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200420 ip_version = gateway_ip.version
Sławek Kapłońskid98e27d2018-05-07 16:16:28 +0200421 else:
422 ip_version = ip_version or cls._ip_version
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200423
424 for subnet_cidr in cls.get_subnet_cidrs(
425 ip_version=ip_version, cidr=cidr, mask_bits=mask_bits):
Federico Ressi98f20ec2018-05-11 06:09:49 +0200426 if gateway is not None:
427 kwargs['gateway_ip'] = str(gateway or (subnet_cidr.ip + 1))
428 try:
429 body = client.create_subnet(
430 network_id=network['id'],
431 cidr=str(subnet_cidr),
432 ip_version=subnet_cidr.version,
433 **kwargs)
434 break
435 except lib_exc.BadRequest as e:
436 if 'overlaps with another subnet' not in str(e):
437 raise
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000438 else:
439 message = 'Available CIDR for subnet creation could not be found'
440 raise ValueError(message)
441 subnet = body['subnet']
Kevin Bentonba3651c2017-09-01 17:13:01 -0700442 if client is cls.client:
443 cls.subnets.append(subnet)
444 else:
445 cls.admin_subnets.append(subnet)
Federico Ressi98f20ec2018-05-11 06:09:49 +0200446 if reserve_cidr:
447 cls.reserve_subnet_cidr(subnet_cidr)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000448 return subnet
449
450 @classmethod
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200451 def reserve_subnet_cidr(cls, addr, **ipnetwork_kwargs):
452 """Reserve given subnet CIDR making sure it is not used by create_subnet
453
454 :param addr: the CIDR address to be reserved
455 It can be a str or netaddr.IPNetwork instance
456
457 :param **ipnetwork_kwargs: optional netaddr.IPNetwork constructor
458 parameters
459 """
460
461 if not cls.try_reserve_subnet_cidr(addr, **ipnetwork_kwargs):
462 raise ValueError('Subnet CIDR already reserved: %r'.format(
463 addr))
464
465 @classmethod
466 def try_reserve_subnet_cidr(cls, addr, **ipnetwork_kwargs):
467 """Reserve given subnet CIDR if it hasn't been reserved before
468
469 :param addr: the CIDR address to be reserved
470 It can be a str or netaddr.IPNetwork instance
471
472 :param **ipnetwork_kwargs: optional netaddr.IPNetwork constructor
473 parameters
474
475 :return: True if it wasn't reserved before, False elsewhere.
476 """
477
478 subnet_cidr = netaddr.IPNetwork(addr, **ipnetwork_kwargs)
479 if subnet_cidr in cls.reserved_subnet_cidrs:
480 return False
481 else:
482 cls.reserved_subnet_cidrs.add(subnet_cidr)
483 return True
484
485 @classmethod
486 def get_subnet_cidrs(
487 cls, cidr=None, mask_bits=None, ip_version=None):
488 """Iterate over a sequence of unused subnet CIDR for IP version
489
490 :param cidr: CIDR of the subnet to create
491 It can be either None, a str or a netaddr.IPNetwork instance
492
493 :param mask_bits: CIDR prefix length
494 It can be either None or a numeric value.
495 If cidr parameter is given then mask_bits is used to determinate a
496 sequence of valid CIDR to use as generated.
497 Please see netaddr.IPNetwork.subnet method documentation[1]
498
499 :param ip_version: ip version of generated subnet CIDRs
500 It can be None, IP_VERSION_4 or IP_VERSION_6
501 It has to match given CIDR if given
502
503 :return: iterator over reserved CIDRs of type netaddr.IPNetwork
504
505 [1] http://netaddr.readthedocs.io/en/latest/tutorial_01.html#supernets-and-subnets # noqa
506 """
507
508 if cidr:
509 # Generate subnet CIDRs starting from given CIDR
510 # checking it is of requested IP version
511 cidr = netaddr.IPNetwork(cidr, version=ip_version)
512 else:
513 # Generate subnet CIDRs starting from configured values
514 ip_version = ip_version or cls._ip_version
515 if ip_version == const.IP_VERSION_4:
516 mask_bits = mask_bits or config.safe_get_config_value(
517 'network', 'project_network_mask_bits')
518 cidr = netaddr.IPNetwork(config.safe_get_config_value(
519 'network', 'project_network_cidr'))
520 elif ip_version == const.IP_VERSION_6:
521 mask_bits = config.safe_get_config_value(
522 'network', 'project_network_v6_mask_bits')
523 cidr = netaddr.IPNetwork(config.safe_get_config_value(
524 'network', 'project_network_v6_cidr'))
525 else:
526 raise ValueError('Invalid IP version: {!r}'.format(ip_version))
527
528 if mask_bits:
529 subnet_cidrs = cidr.subnet(mask_bits)
530 else:
531 subnet_cidrs = iter([cidr])
532
533 for subnet_cidr in subnet_cidrs:
534 if subnet_cidr not in cls.reserved_subnet_cidrs:
535 yield subnet_cidr
536
537 @classmethod
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000538 def create_port(cls, network, **kwargs):
539 """Wrapper utility that returns a test port."""
Edan Davidd75e48e2018-01-03 02:49:52 -0500540 if CONF.network.port_vnic_type and 'binding:vnic_type' not in kwargs:
541 kwargs['binding:vnic_type'] = CONF.network.port_vnic_type
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000542 body = cls.client.create_port(network_id=network['id'],
543 **kwargs)
544 port = body['port']
545 cls.ports.append(port)
546 return port
547
548 @classmethod
549 def update_port(cls, port, **kwargs):
550 """Wrapper utility that updates a test port."""
551 body = cls.client.update_port(port['id'],
552 **kwargs)
553 return body['port']
554
555 @classmethod
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300556 def _create_router_with_client(
557 cls, client, router_name=None, admin_state_up=False,
558 external_network_id=None, enable_snat=None, **kwargs
559 ):
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000560 ext_gw_info = {}
561 if external_network_id:
562 ext_gw_info['network_id'] = external_network_id
YAMAMOTO Takashi9bd4f972017-06-20 12:49:30 +0900563 if enable_snat is not None:
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000564 ext_gw_info['enable_snat'] = enable_snat
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300565 body = client.create_router(
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000566 router_name, external_gateway_info=ext_gw_info,
567 admin_state_up=admin_state_up, **kwargs)
568 router = body['router']
569 cls.routers.append(router)
570 return router
571
572 @classmethod
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300573 def create_router(cls, *args, **kwargs):
574 return cls._create_router_with_client(cls.client, *args, **kwargs)
575
576 @classmethod
577 def create_admin_router(cls, *args, **kwargs):
rajat294495c042017-06-28 15:37:16 +0530578 return cls._create_router_with_client(cls.os_admin.network_client,
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300579 *args, **kwargs)
580
581 @classmethod
Federico Ressia69dcd52018-07-06 09:45:34 +0200582 def create_floatingip(cls, external_network_id=None, port=None,
583 client=None, **kwargs):
584 """Creates a floating IP.
585
586 Create a floating IP and schedule it for later deletion.
587 If a client is passed, then it is used for deleting the IP too.
588
589 :param external_network_id: network ID where to create
590 By default this is 'CONF.network.public_network_id'.
591
592 :param port: port to bind floating IP to
593 This is translated to 'port_id=port['id']'
594 By default it is None.
595
596 :param client: network client to be used for creating and cleaning up
597 the floating IP.
598
599 :param **kwargs: additional creation parameters to be forwarded to
600 networking server.
601 """
602
603 client = client or cls.client
604 external_network_id = (external_network_id or
605 cls.external_network_id)
606
607 if port:
608 kwargs['port_id'] = port['id']
609
610 fip = client.create_floatingip(external_network_id,
611 **kwargs)['floatingip']
612
613 # save client to be used later in cls.delete_floatingip
614 # for final cleanup
615 fip['client'] = client
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000616 cls.floating_ips.append(fip)
617 return fip
618
619 @classmethod
Federico Ressia69dcd52018-07-06 09:45:34 +0200620 def delete_floatingip(cls, floating_ip, client=None):
621 """Delete floating IP
622
623 :param client: Client to be used
624 If client is not given it will use the client used to create
625 the floating IP, or cls.client if unknown.
626 """
627
628 client = client or floating_ip.get('client') or cls.client
629 client.delete_floatingip(floating_ip['id'])
630
631 @classmethod
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000632 def create_router_interface(cls, router_id, subnet_id):
633 """Wrapper utility that returns a router interface."""
634 interface = cls.client.add_router_interface_with_subnet_id(
635 router_id, subnet_id)
636 return interface
637
638 @classmethod
Sławek Kapłońskiff294062016-12-04 15:00:54 +0000639 def get_supported_qos_rule_types(cls):
640 body = cls.client.list_qos_rule_types()
641 return [rule_type['type'] for rule_type in body['rule_types']]
642
643 @classmethod
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200644 def create_qos_policy(cls, name, description=None, shared=False,
Hirofumi Ichihara39a6ee12017-08-23 13:55:12 +0900645 tenant_id=None, is_default=False):
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000646 """Wrapper utility that returns a test QoS policy."""
647 body = cls.admin_client.create_qos_policy(
Hirofumi Ichihara39a6ee12017-08-23 13:55:12 +0900648 name, description, shared, tenant_id, is_default)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000649 qos_policy = body['policy']
650 cls.qos_policies.append(qos_policy)
651 return qos_policy
652
653 @classmethod
Sławek Kapłoński153f3452017-03-24 22:04:53 +0000654 def create_qos_bandwidth_limit_rule(cls, policy_id, max_kbps,
655 max_burst_kbps,
Chandan Kumarc125fd12017-11-15 19:41:01 +0530656 direction=const.EGRESS_DIRECTION):
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000657 """Wrapper utility that returns a test QoS bandwidth limit rule."""
658 body = cls.admin_client.create_bandwidth_limit_rule(
Sławek Kapłoński153f3452017-03-24 22:04:53 +0000659 policy_id, max_kbps, max_burst_kbps, direction)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000660 qos_rule = body['bandwidth_limit_rule']
661 cls.qos_rules.append(qos_rule)
662 return qos_rule
663
664 @classmethod
Jakub Libosvar83704832017-12-06 16:02:28 +0000665 def delete_router(cls, router, client=None):
666 client = client or cls.client
667 body = client.list_router_interfaces(router['id'])
Chandan Kumarc125fd12017-11-15 19:41:01 +0530668 interfaces = [port for port in body['ports']
669 if port['device_owner'] in const.ROUTER_INTERFACE_OWNERS]
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000670 for i in interfaces:
671 try:
Jakub Libosvar83704832017-12-06 16:02:28 +0000672 client.remove_router_interface_with_subnet_id(
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000673 router['id'], i['fixed_ips'][0]['subnet_id'])
674 except lib_exc.NotFound:
675 pass
Jakub Libosvar83704832017-12-06 16:02:28 +0000676 client.delete_router(router['id'])
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000677
678 @classmethod
679 def create_address_scope(cls, name, is_admin=False, **kwargs):
680 if is_admin:
681 body = cls.admin_client.create_address_scope(name=name, **kwargs)
682 cls.admin_address_scopes.append(body['address_scope'])
683 else:
684 body = cls.client.create_address_scope(name=name, **kwargs)
685 cls.address_scopes.append(body['address_scope'])
686 return body['address_scope']
687
688 @classmethod
689 def create_subnetpool(cls, name, is_admin=False, **kwargs):
690 if is_admin:
691 body = cls.admin_client.create_subnetpool(name, **kwargs)
692 cls.admin_subnetpools.append(body['subnetpool'])
693 else:
694 body = cls.client.create_subnetpool(name, **kwargs)
695 cls.subnetpools.append(body['subnetpool'])
696 return body['subnetpool']
697
Chandan Kumarc125fd12017-11-15 19:41:01 +0530698 @classmethod
699 def create_project(cls, name=None, description=None):
700 test_project = name or data_utils.rand_name('test_project_')
701 test_description = description or data_utils.rand_name('desc_')
702 project = cls.identity_admin_client.create_project(
703 name=test_project,
704 description=test_description)['project']
705 cls.projects.append(project)
706 return project
707
708 @classmethod
709 def create_security_group(cls, name, **kwargs):
710 body = cls.client.create_security_group(name=name, **kwargs)
711 cls.security_groups.append(body['security_group'])
712 return body['security_group']
713
Federico Ressiab286e42018-06-19 09:52:10 +0200714 @classmethod
715 def create_keypair(cls, client=None, name=None, **kwargs):
716 client = client or cls.os_primary.keypairs_client
717 name = name or data_utils.rand_name('keypair-test')
718 keypair = client.create_keypair(name=name, **kwargs)['keypair']
719
720 # save client for later cleanup
721 keypair['client'] = client
722 cls.keypairs.append(keypair)
723 return keypair
724
725 @classmethod
726 def delete_keypair(cls, keypair, client=None):
727 client = (client or keypair.get('client') or
728 cls.os_primary.keypairs_client)
729 client.delete_keypair(keypair_name=keypair['name'])
730
Federico Ressi82e83e32018-07-03 14:19:55 +0200731 @classmethod
732 def create_trunk(cls, port=None, subports=None, client=None, **kwargs):
733 """Create network trunk
734
735 :param port: dictionary containing parent port ID (port['id'])
736 :param client: client to be used for connecting to networking service
737 :param **kwargs: extra parameters to be forwarded to network service
738
739 :returns: dictionary containing created trunk details
740 """
741 client = client or cls.client
742
743 if port:
744 kwargs['port_id'] = port['id']
745
746 trunk = client.create_trunk(subports=subports, **kwargs)['trunk']
747 # Save client reference for later deletion
748 trunk['client'] = client
749 cls.trunks.append(trunk)
750 return trunk
751
752 @classmethod
753 def delete_trunk(cls, trunk, client=None):
754 """Delete network trunk
755
756 :param trunk: dictionary containing trunk ID (trunk['id'])
757
758 :param client: client to be used for connecting to networking service
759 """
760 client = client or trunk.get('client') or cls.client
761 trunk.update(client.show_trunk(trunk['id'])['trunk'])
762
763 if not trunk['admin_state_up']:
764 # Cannot touch trunk before admin_state_up is True
765 client.update_trunk(trunk['id'], admin_state_up=True)
766 if trunk['sub_ports']:
767 # Removes trunk ports before deleting it
768 cls._try_delete_resource(client.remove_subports, trunk['id'],
769 trunk['sub_ports'])
770
771 # we have to detach the interface from the server before
772 # the trunk can be deleted.
773 parent_port = {'id': trunk['port_id']}
774
775 def is_parent_port_detached():
776 parent_port.update(client.show_port(parent_port['id'])['port'])
777 return not parent_port['device_id']
778
779 if not is_parent_port_detached():
780 # this could probably happen when trunk is deleted and parent port
781 # has been assigned to a VM that is still running. Here we are
782 # assuming that device_id points to such VM.
783 cls.os_primary.compute.InterfacesClient().delete_interface(
784 parent_port['device_id'], parent_port['id'])
785 utils.wait_until_true(is_parent_port_detached)
786
787 client.delete_trunk(trunk['id'])
788
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000789
790class BaseAdminNetworkTest(BaseNetworkTest):
791
792 credentials = ['primary', 'admin']
793
794 @classmethod
795 def setup_clients(cls):
796 super(BaseAdminNetworkTest, cls).setup_clients()
fumihiko kakumaa216fc12017-07-14 10:43:29 +0900797 cls.admin_client = cls.os_admin.network_client
Jakub Libosvarf5758012017-08-15 13:45:30 +0000798 cls.identity_admin_client = cls.os_admin.projects_client
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000799
800 @classmethod
801 def create_metering_label(cls, name, description):
802 """Wrapper utility that returns a test metering label."""
803 body = cls.admin_client.create_metering_label(
804 description=description,
805 name=data_utils.rand_name("metering-label"))
806 metering_label = body['metering_label']
807 cls.metering_labels.append(metering_label)
808 return metering_label
809
810 @classmethod
811 def create_metering_label_rule(cls, remote_ip_prefix, direction,
812 metering_label_id):
813 """Wrapper utility that returns a test metering label rule."""
814 body = cls.admin_client.create_metering_label_rule(
815 remote_ip_prefix=remote_ip_prefix, direction=direction,
816 metering_label_id=metering_label_id)
817 metering_label_rule = body['metering_label_rule']
818 cls.metering_label_rules.append(metering_label_rule)
819 return metering_label_rule
820
821 @classmethod
822 def create_flavor(cls, name, description, service_type):
823 """Wrapper utility that returns a test flavor."""
824 body = cls.admin_client.create_flavor(
825 description=description, service_type=service_type,
826 name=name)
827 flavor = body['flavor']
828 cls.flavors.append(flavor)
829 return flavor
830
831 @classmethod
832 def create_service_profile(cls, description, metainfo, driver):
833 """Wrapper utility that returns a test service profile."""
834 body = cls.admin_client.create_service_profile(
835 driver=driver, metainfo=metainfo, description=description)
836 service_profile = body['service_profile']
837 cls.service_profiles.append(service_profile)
838 return service_profile
839
840 @classmethod
Nguyen Phuong An67993fc2017-11-24 11:30:25 +0700841 def create_log(cls, name, description=None,
842 resource_type='security_group', resource_id=None,
843 target_id=None, event='ALL', enabled=True):
844 """Wrapper utility that returns a test log object."""
845 log_args = {'name': name,
846 'description': description,
847 'resource_type': resource_type,
848 'resource_id': resource_id,
849 'target_id': target_id,
850 'event': event,
851 'enabled': enabled}
852 body = cls.admin_client.create_log(**log_args)
853 log_object = body['log']
854 cls.log_objects.append(log_object)
855 return log_object
856
857 @classmethod
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000858 def get_unused_ip(cls, net_id, ip_version=None):
Gary Kotton011345f2016-06-15 08:04:31 -0700859 """Get an unused ip address in a allocation pool of net"""
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000860 body = cls.admin_client.list_ports(network_id=net_id)
861 ports = body['ports']
862 used_ips = []
863 for port in ports:
864 used_ips.extend(
865 [fixed_ip['ip_address'] for fixed_ip in port['fixed_ips']])
866 body = cls.admin_client.list_subnets(network_id=net_id)
867 subnets = body['subnets']
868
869 for subnet in subnets:
870 if ip_version and subnet['ip_version'] != ip_version:
871 continue
872 cidr = subnet['cidr']
873 allocation_pools = subnet['allocation_pools']
874 iterators = []
875 if allocation_pools:
876 for allocation_pool in allocation_pools:
877 iterators.append(netaddr.iter_iprange(
878 allocation_pool['start'], allocation_pool['end']))
879 else:
880 net = netaddr.IPNetwork(cidr)
881
882 def _iterip():
883 for ip in net:
884 if ip not in (net.network, net.broadcast):
885 yield ip
886 iterators.append(iter(_iterip()))
887
888 for iterator in iterators:
889 for ip in iterator:
890 if str(ip) not in used_ips:
891 return str(ip)
892
893 message = (
894 "net(%s) has no usable IP address in allocation pools" % net_id)
895 raise exceptions.InvalidConfiguration(message)
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200896
897
Sławek Kapłońskiff294062016-12-04 15:00:54 +0000898def require_qos_rule_type(rule_type):
899 def decorator(f):
900 @functools.wraps(f)
901 def wrapper(self, *func_args, **func_kwargs):
902 if rule_type not in self.get_supported_qos_rule_types():
903 raise self.skipException(
904 "%s rule type is required." % rule_type)
905 return f(self, *func_args, **func_kwargs)
906 return wrapper
907 return decorator
908
909
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200910def _require_sorting(f):
911 @functools.wraps(f)
912 def inner(self, *args, **kwargs):
Chandan Kumarc125fd12017-11-15 19:41:01 +0530913 if not tutils.is_extension_enabled("sorting", "network"):
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200914 self.skipTest('Sorting feature is required')
915 return f(self, *args, **kwargs)
916 return inner
917
918
919def _require_pagination(f):
920 @functools.wraps(f)
921 def inner(self, *args, **kwargs):
Chandan Kumarc125fd12017-11-15 19:41:01 +0530922 if not tutils.is_extension_enabled("pagination", "network"):
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200923 self.skipTest('Pagination feature is required')
924 return f(self, *args, **kwargs)
925 return inner
926
927
928class BaseSearchCriteriaTest(BaseNetworkTest):
929
930 # This should be defined by subclasses to reflect resource name to test
931 resource = None
932
Armando Migliaccio57581c62016-07-01 10:13:19 -0700933 field = 'name'
934
Ihar Hrachyshkaa8fe5a12016-05-24 14:50:58 +0200935 # NOTE(ihrachys): some names, like those starting with an underscore (_)
936 # are sorted differently depending on whether the plugin implements native
937 # sorting support, or not. So we avoid any such cases here, sticking to
938 # alphanumeric. Also test a case when there are multiple resources with the
939 # same name
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200940 resource_names = ('test1', 'abc1', 'test10', '123test') + ('test1',)
941
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200942 force_tenant_isolation = True
943
Ihar Hrachyshkaa8fe5a12016-05-24 14:50:58 +0200944 list_kwargs = {}
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200945
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200946 list_as_admin = False
947
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200948 def assertSameOrder(self, original, actual):
949 # gracefully handle iterators passed
950 original = list(original)
951 actual = list(actual)
952 self.assertEqual(len(original), len(actual))
953 for expected, res in zip(original, actual):
Armando Migliaccio57581c62016-07-01 10:13:19 -0700954 self.assertEqual(expected[self.field], res[self.field])
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200955
956 @utils.classproperty
957 def plural_name(self):
958 return '%ss' % self.resource
959
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200960 @property
961 def list_client(self):
962 return self.admin_client if self.list_as_admin else self.client
963
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200964 def list_method(self, *args, **kwargs):
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200965 method = getattr(self.list_client, 'list_%s' % self.plural_name)
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200966 kwargs.update(self.list_kwargs)
967 return method(*args, **kwargs)
968
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200969 def get_bare_url(self, url):
970 base_url = self.client.base_url
971 self.assertTrue(url.startswith(base_url))
972 return url[len(base_url):]
973
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200974 @classmethod
975 def _extract_resources(cls, body):
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200976 return body[cls.plural_name]
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200977
978 def _test_list_sorts(self, direction):
979 sort_args = {
980 'sort_dir': direction,
Armando Migliaccio57581c62016-07-01 10:13:19 -0700981 'sort_key': self.field
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200982 }
983 body = self.list_method(**sort_args)
984 resources = self._extract_resources(body)
985 self.assertNotEmpty(
986 resources, "%s list returned is empty" % self.resource)
Armando Migliaccio57581c62016-07-01 10:13:19 -0700987 retrieved_names = [res[self.field] for res in resources]
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200988 expected = sorted(retrieved_names)
989 if direction == constants.SORT_DIRECTION_DESC:
990 expected = list(reversed(expected))
991 self.assertEqual(expected, retrieved_names)
992
993 @_require_sorting
994 def _test_list_sorts_asc(self):
995 self._test_list_sorts(constants.SORT_DIRECTION_ASC)
996
997 @_require_sorting
998 def _test_list_sorts_desc(self):
999 self._test_list_sorts(constants.SORT_DIRECTION_DESC)
1000
1001 @_require_pagination
1002 def _test_list_pagination(self):
1003 for limit in range(1, len(self.resource_names) + 1):
1004 pagination_args = {
1005 'limit': limit,
1006 }
1007 body = self.list_method(**pagination_args)
1008 resources = self._extract_resources(body)
1009 self.assertEqual(limit, len(resources))
1010
1011 @_require_pagination
1012 def _test_list_no_pagination_limit_0(self):
1013 pagination_args = {
1014 'limit': 0,
1015 }
1016 body = self.list_method(**pagination_args)
1017 resources = self._extract_resources(body)
Béla Vancsicsf1806182016-08-23 07:36:18 +02001018 self.assertGreaterEqual(len(resources), len(self.resource_names))
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001019
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001020 def _test_list_pagination_iteratively(self, lister):
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001021 # first, collect all resources for later comparison
1022 sort_args = {
1023 'sort_dir': constants.SORT_DIRECTION_ASC,
Armando Migliaccio57581c62016-07-01 10:13:19 -07001024 'sort_key': self.field
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001025 }
1026 body = self.list_method(**sort_args)
1027 expected_resources = self._extract_resources(body)
1028 self.assertNotEmpty(expected_resources)
1029
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001030 resources = lister(
1031 len(expected_resources), sort_args
1032 )
1033
1034 # finally, compare that the list retrieved in one go is identical to
1035 # the one containing pagination results
1036 self.assertSameOrder(expected_resources, resources)
1037
1038 def _list_all_with_marker(self, niterations, sort_args):
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001039 # paginate resources one by one, using last fetched resource as a
1040 # marker
1041 resources = []
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001042 for i in range(niterations):
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001043 pagination_args = sort_args.copy()
1044 pagination_args['limit'] = 1
1045 if resources:
1046 pagination_args['marker'] = resources[-1]['id']
1047 body = self.list_method(**pagination_args)
1048 resources_ = self._extract_resources(body)
1049 self.assertEqual(1, len(resources_))
1050 resources.extend(resources_)
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001051 return resources
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001052
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001053 @_require_pagination
1054 @_require_sorting
1055 def _test_list_pagination_with_marker(self):
1056 self._test_list_pagination_iteratively(self._list_all_with_marker)
1057
1058 def _list_all_with_hrefs(self, niterations, sort_args):
1059 # paginate resources one by one, using next href links
1060 resources = []
1061 prev_links = {}
1062
1063 for i in range(niterations):
1064 if prev_links:
1065 uri = self.get_bare_url(prev_links['next'])
1066 else:
Ihar Hrachyshka7f79fe62016-06-07 21:23:44 +02001067 sort_args.update(self.list_kwargs)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001068 uri = self.list_client.build_uri(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001069 self.plural_name, limit=1, **sort_args)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001070 prev_links, body = self.list_client.get_uri_with_links(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001071 self.plural_name, uri
1072 )
1073 resources_ = self._extract_resources(body)
1074 self.assertEqual(1, len(resources_))
1075 resources.extend(resources_)
1076
1077 # The last element is empty and does not contain 'next' link
1078 uri = self.get_bare_url(prev_links['next'])
1079 prev_links, body = self.client.get_uri_with_links(
1080 self.plural_name, uri
1081 )
1082 self.assertNotIn('next', prev_links)
1083
1084 # Now walk backwards and compare results
1085 resources2 = []
1086 for i in range(niterations):
1087 uri = self.get_bare_url(prev_links['previous'])
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001088 prev_links, body = self.list_client.get_uri_with_links(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001089 self.plural_name, uri
1090 )
1091 resources_ = self._extract_resources(body)
1092 self.assertEqual(1, len(resources_))
1093 resources2.extend(resources_)
1094
1095 self.assertSameOrder(resources, reversed(resources2))
1096
1097 return resources
1098
1099 @_require_pagination
1100 @_require_sorting
1101 def _test_list_pagination_with_href_links(self):
1102 self._test_list_pagination_iteratively(self._list_all_with_hrefs)
1103
1104 @_require_pagination
1105 @_require_sorting
1106 def _test_list_pagination_page_reverse_with_href_links(
1107 self, direction=constants.SORT_DIRECTION_ASC):
1108 pagination_args = {
1109 'sort_dir': direction,
Armando Migliaccio57581c62016-07-01 10:13:19 -07001110 'sort_key': self.field,
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001111 }
1112 body = self.list_method(**pagination_args)
1113 expected_resources = self._extract_resources(body)
1114
1115 page_size = 2
1116 pagination_args['limit'] = page_size
1117
1118 prev_links = {}
1119 resources = []
1120 num_resources = len(expected_resources)
1121 niterations = int(math.ceil(float(num_resources) / page_size))
1122 for i in range(niterations):
1123 if prev_links:
1124 uri = self.get_bare_url(prev_links['previous'])
1125 else:
Ihar Hrachyshka7f79fe62016-06-07 21:23:44 +02001126 pagination_args.update(self.list_kwargs)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001127 uri = self.list_client.build_uri(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001128 self.plural_name, page_reverse=True, **pagination_args)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001129 prev_links, body = self.list_client.get_uri_with_links(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001130 self.plural_name, uri
1131 )
1132 resources_ = self._extract_resources(body)
Béla Vancsicsf1806182016-08-23 07:36:18 +02001133 self.assertGreaterEqual(page_size, len(resources_))
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001134 resources.extend(reversed(resources_))
1135
1136 self.assertSameOrder(expected_resources, reversed(resources))
1137
1138 @_require_pagination
1139 @_require_sorting
1140 def _test_list_pagination_page_reverse_asc(self):
1141 self._test_list_pagination_page_reverse(
1142 direction=constants.SORT_DIRECTION_ASC)
1143
1144 @_require_pagination
1145 @_require_sorting
1146 def _test_list_pagination_page_reverse_desc(self):
1147 self._test_list_pagination_page_reverse(
1148 direction=constants.SORT_DIRECTION_DESC)
1149
1150 def _test_list_pagination_page_reverse(self, direction):
1151 pagination_args = {
1152 'sort_dir': direction,
Armando Migliaccio57581c62016-07-01 10:13:19 -07001153 'sort_key': self.field,
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001154 'limit': 3,
1155 }
1156 body = self.list_method(**pagination_args)
1157 expected_resources = self._extract_resources(body)
1158
1159 pagination_args['limit'] -= 1
1160 pagination_args['marker'] = expected_resources[-1]['id']
1161 pagination_args['page_reverse'] = True
1162 body = self.list_method(**pagination_args)
1163
1164 self.assertSameOrder(
1165 # the last entry is not included in 2nd result when used as a
1166 # marker
1167 expected_resources[:-1],
1168 self._extract_resources(body))
Victor Morales1be97b42016-09-05 08:50:06 -05001169
Hongbin Lu54f55922018-07-12 19:05:39 +00001170 @tutils.requires_ext(extension="filter-validation", service="network")
1171 def _test_list_validation_filters(
1172 self, validation_args, filter_is_valid=True):
1173 if not filter_is_valid:
1174 self.assertRaises(lib_exc.BadRequest, self.list_method,
1175 **validation_args)
1176 else:
1177 body = self.list_method(**validation_args)
1178 resources = self._extract_resources(body)
1179 for resource in resources:
1180 self.assertIn(resource['name'], self.resource_names)