blob: 3321ad43c2cb8a4489aa05ceda46de98b0dda46b [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
20from tempest.lib.common.utils import data_utils
21from tempest.lib import exceptions as lib_exc
22from tempest import test
23
Ihar Hrachyshka59382252016-04-05 15:54:33 +020024from neutron.common import constants
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +020025from neutron.common import utils
Daniel Mellado3c0aeab2016-01-29 11:30:25 +000026from neutron.tests.tempest.api import clients
27from neutron.tests.tempest import config
28from neutron.tests.tempest import exceptions
29
30CONF = config.CONF
31
32
33class BaseNetworkTest(test.BaseTestCase):
34
35 """
36 Base class for the Neutron tests that use the Tempest Neutron REST client
37
38 Per the Neutron API Guide, API v1.x was removed from the source code tree
39 (docs.openstack.org/api/openstack-network/2.0/content/Overview-d1e71.html)
40 Therefore, v2.x of the Neutron API is assumed. It is also assumed that the
41 following options are defined in the [network] section of etc/tempest.conf:
42
43 project_network_cidr with a block of cidr's from which smaller blocks
44 can be allocated for tenant networks
45
46 project_network_mask_bits with the mask bits to be used to partition
47 the block defined by tenant-network_cidr
48
49 Finally, it is assumed that the following option is defined in the
50 [service_available] section of etc/tempest.conf
51
52 neutron as True
53 """
54
55 force_tenant_isolation = False
56 credentials = ['primary']
57
58 # Default to ipv4.
59 _ip_version = 4
60
61 @classmethod
62 def get_client_manager(cls, credential_type=None, roles=None,
63 force_new=None):
Genadi Chereshnyacc395c02016-07-25 12:17:37 +030064 manager = super(BaseNetworkTest, cls).get_client_manager(
65 credential_type=credential_type,
66 roles=roles,
67 force_new=force_new
68 )
Daniel Mellado3c0aeab2016-01-29 11:30:25 +000069 # Neutron uses a different clients manager than the one in the Tempest
70 return clients.Manager(manager.credentials)
71
72 @classmethod
73 def skip_checks(cls):
74 super(BaseNetworkTest, cls).skip_checks()
75 if not CONF.service_available.neutron:
76 raise cls.skipException("Neutron support is required")
77 if cls._ip_version == 6 and not CONF.network_feature_enabled.ipv6:
78 raise cls.skipException("IPv6 Tests are disabled.")
79
80 @classmethod
81 def setup_credentials(cls):
82 # Create no network resources for these test.
83 cls.set_network_resources()
84 super(BaseNetworkTest, cls).setup_credentials()
85
86 @classmethod
87 def setup_clients(cls):
88 super(BaseNetworkTest, cls).setup_clients()
89 cls.client = cls.os.network_client
90
91 @classmethod
92 def resource_setup(cls):
93 super(BaseNetworkTest, cls).resource_setup()
94
95 cls.networks = []
Miguel Lavalle124378b2016-09-21 16:41:47 -050096 cls.admin_networks = []
Daniel Mellado3c0aeab2016-01-29 11:30:25 +000097 cls.subnets = []
98 cls.ports = []
99 cls.routers = []
100 cls.floating_ips = []
101 cls.metering_labels = []
102 cls.service_profiles = []
103 cls.flavors = []
104 cls.metering_label_rules = []
105 cls.qos_rules = []
106 cls.qos_policies = []
107 cls.ethertype = "IPv" + str(cls._ip_version)
108 cls.address_scopes = []
109 cls.admin_address_scopes = []
110 cls.subnetpools = []
111 cls.admin_subnetpools = []
Itzik Brownbac51dc2016-10-31 12:25:04 +0000112 cls.security_groups = []
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000113
114 @classmethod
115 def resource_cleanup(cls):
116 if CONF.service_available.neutron:
117 # Clean up QoS rules
118 for qos_rule in cls.qos_rules:
119 cls._try_delete_resource(cls.admin_client.delete_qos_rule,
120 qos_rule['id'])
121 # Clean up QoS policies
122 for qos_policy in cls.qos_policies:
123 cls._try_delete_resource(cls.admin_client.delete_qos_policy,
124 qos_policy['id'])
125 # Clean up floating IPs
126 for floating_ip in cls.floating_ips:
127 cls._try_delete_resource(cls.client.delete_floatingip,
128 floating_ip['id'])
129 # Clean up routers
130 for router in cls.routers:
131 cls._try_delete_resource(cls.delete_router,
132 router)
133 # Clean up metering label rules
134 for metering_label_rule in cls.metering_label_rules:
135 cls._try_delete_resource(
136 cls.admin_client.delete_metering_label_rule,
137 metering_label_rule['id'])
138 # Clean up metering labels
139 for metering_label in cls.metering_labels:
140 cls._try_delete_resource(
141 cls.admin_client.delete_metering_label,
142 metering_label['id'])
143 # Clean up flavors
144 for flavor in cls.flavors:
145 cls._try_delete_resource(
146 cls.admin_client.delete_flavor,
147 flavor['id'])
148 # Clean up service profiles
149 for service_profile in cls.service_profiles:
150 cls._try_delete_resource(
151 cls.admin_client.delete_service_profile,
152 service_profile['id'])
153 # Clean up ports
154 for port in cls.ports:
155 cls._try_delete_resource(cls.client.delete_port,
156 port['id'])
157 # Clean up subnets
158 for subnet in cls.subnets:
159 cls._try_delete_resource(cls.client.delete_subnet,
160 subnet['id'])
161 # Clean up networks
162 for network in cls.networks:
163 cls._try_delete_resource(cls.client.delete_network,
164 network['id'])
165
Miguel Lavalle124378b2016-09-21 16:41:47 -0500166 # Clean up admin networks
167 for network in cls.admin_networks:
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000168 cls._try_delete_resource(cls.admin_client.delete_network,
169 network['id'])
170
Itzik Brownbac51dc2016-10-31 12:25:04 +0000171 # Clean up security groups
172 for secgroup in cls.security_groups:
173 cls._try_delete_resource(cls.client.delete_security_group,
174 secgroup['id'])
175
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000176 for subnetpool in cls.subnetpools:
177 cls._try_delete_resource(cls.client.delete_subnetpool,
178 subnetpool['id'])
179
180 for subnetpool in cls.admin_subnetpools:
181 cls._try_delete_resource(cls.admin_client.delete_subnetpool,
182 subnetpool['id'])
183
184 for address_scope in cls.address_scopes:
185 cls._try_delete_resource(cls.client.delete_address_scope,
186 address_scope['id'])
187
188 for address_scope in cls.admin_address_scopes:
189 cls._try_delete_resource(
190 cls.admin_client.delete_address_scope,
191 address_scope['id'])
192
193 super(BaseNetworkTest, cls).resource_cleanup()
194
195 @classmethod
196 def _try_delete_resource(cls, delete_callable, *args, **kwargs):
197 """Cleanup resources in case of test-failure
198
199 Some resources are explicitly deleted by the test.
200 If the test failed to delete a resource, this method will execute
201 the appropriate delete methods. Otherwise, the method ignores NotFound
202 exceptions thrown for resources that were correctly deleted by the
203 test.
204
205 :param delete_callable: delete method
206 :param args: arguments for delete method
207 :param kwargs: keyword arguments for delete method
208 """
209 try:
210 delete_callable(*args, **kwargs)
211 # if resource is not found, this means it was deleted in the test
212 except lib_exc.NotFound:
213 pass
214
215 @classmethod
216 def create_network(cls, network_name=None, **kwargs):
217 """Wrapper utility that returns a test network."""
218 network_name = network_name or data_utils.rand_name('test-network-')
219
220 body = cls.client.create_network(name=network_name, **kwargs)
221 network = body['network']
222 cls.networks.append(network)
223 return network
224
225 @classmethod
226 def create_shared_network(cls, network_name=None, **post_body):
227 network_name = network_name or data_utils.rand_name('sharednetwork-')
228 post_body.update({'name': network_name, 'shared': True})
229 body = cls.admin_client.create_network(**post_body)
230 network = body['network']
Miguel Lavalle124378b2016-09-21 16:41:47 -0500231 cls.admin_networks.append(network)
232 return network
233
234 @classmethod
235 def create_network_keystone_v3(cls, network_name=None, project_id=None,
236 tenant_id=None, client=None):
237 """Wrapper utility that creates a test network with project_id."""
238 client = client or cls.client
239 network_name = network_name or data_utils.rand_name(
240 'test-network-with-project_id')
241 project_id = cls.client.tenant_id
242 body = client.create_network_keystone_v3(network_name, project_id,
243 tenant_id)
244 network = body['network']
245 if client is cls.client:
246 cls.networks.append(network)
247 else:
248 cls.admin_networks.append(network)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000249 return network
250
251 @classmethod
252 def create_subnet(cls, network, gateway='', cidr=None, mask_bits=None,
253 ip_version=None, client=None, **kwargs):
254 """Wrapper utility that returns a test subnet."""
255
256 # allow tests to use admin client
257 if not client:
258 client = cls.client
259
260 # The cidr and mask_bits depend on the ip version.
261 ip_version = ip_version if ip_version is not None else cls._ip_version
262 gateway_not_set = gateway == ''
263 if ip_version == 4:
264 cidr = cidr or netaddr.IPNetwork(
265 config.safe_get_config_value(
266 'network', 'project_network_cidr'))
267 mask_bits = (
268 mask_bits or config.safe_get_config_value(
269 'network', 'project_network_mask_bits'))
270 elif ip_version == 6:
271 cidr = (
272 cidr or netaddr.IPNetwork(
273 config.safe_get_config_value(
274 'network', 'project_network_v6_cidr')))
275 mask_bits = (
276 mask_bits or config.safe_get_config_value(
277 'network', 'project_network_v6_mask_bits'))
278 # Find a cidr that is not in use yet and create a subnet with it
279 for subnet_cidr in cidr.subnet(mask_bits):
280 if gateway_not_set:
281 gateway_ip = str(netaddr.IPAddress(subnet_cidr) + 1)
282 else:
283 gateway_ip = gateway
284 try:
285 body = client.create_subnet(
286 network_id=network['id'],
287 cidr=str(subnet_cidr),
288 ip_version=ip_version,
289 gateway_ip=gateway_ip,
290 **kwargs)
291 break
292 except lib_exc.BadRequest as e:
293 is_overlapping_cidr = 'overlaps with another subnet' in str(e)
294 if not is_overlapping_cidr:
295 raise
296 else:
297 message = 'Available CIDR for subnet creation could not be found'
298 raise ValueError(message)
299 subnet = body['subnet']
300 cls.subnets.append(subnet)
301 return subnet
302
303 @classmethod
304 def create_port(cls, network, **kwargs):
305 """Wrapper utility that returns a test port."""
306 body = cls.client.create_port(network_id=network['id'],
307 **kwargs)
308 port = body['port']
309 cls.ports.append(port)
310 return port
311
312 @classmethod
313 def update_port(cls, port, **kwargs):
314 """Wrapper utility that updates a test port."""
315 body = cls.client.update_port(port['id'],
316 **kwargs)
317 return body['port']
318
319 @classmethod
320 def create_router(cls, router_name=None, admin_state_up=False,
321 external_network_id=None, enable_snat=None,
322 **kwargs):
323 ext_gw_info = {}
324 if external_network_id:
325 ext_gw_info['network_id'] = external_network_id
326 if enable_snat:
327 ext_gw_info['enable_snat'] = enable_snat
328 body = cls.client.create_router(
329 router_name, external_gateway_info=ext_gw_info,
330 admin_state_up=admin_state_up, **kwargs)
331 router = body['router']
332 cls.routers.append(router)
333 return router
334
335 @classmethod
336 def create_floatingip(cls, external_network_id):
337 """Wrapper utility that returns a test floating IP."""
338 body = cls.client.create_floatingip(
339 floating_network_id=external_network_id)
340 fip = body['floatingip']
341 cls.floating_ips.append(fip)
342 return fip
343
344 @classmethod
345 def create_router_interface(cls, router_id, subnet_id):
346 """Wrapper utility that returns a router interface."""
347 interface = cls.client.add_router_interface_with_subnet_id(
348 router_id, subnet_id)
349 return interface
350
351 @classmethod
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200352 def create_qos_policy(cls, name, description=None, shared=False,
353 tenant_id=None):
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000354 """Wrapper utility that returns a test QoS policy."""
355 body = cls.admin_client.create_qos_policy(
356 name, description, shared, tenant_id)
357 qos_policy = body['policy']
358 cls.qos_policies.append(qos_policy)
359 return qos_policy
360
361 @classmethod
362 def create_qos_bandwidth_limit_rule(cls, policy_id,
363 max_kbps, max_burst_kbps):
364 """Wrapper utility that returns a test QoS bandwidth limit rule."""
365 body = cls.admin_client.create_bandwidth_limit_rule(
366 policy_id, max_kbps, max_burst_kbps)
367 qos_rule = body['bandwidth_limit_rule']
368 cls.qos_rules.append(qos_rule)
369 return qos_rule
370
371 @classmethod
372 def delete_router(cls, router):
373 body = cls.client.list_router_interfaces(router['id'])
374 interfaces = body['ports']
375 for i in interfaces:
376 try:
377 cls.client.remove_router_interface_with_subnet_id(
378 router['id'], i['fixed_ips'][0]['subnet_id'])
379 except lib_exc.NotFound:
380 pass
381 cls.client.delete_router(router['id'])
382
383 @classmethod
384 def create_address_scope(cls, name, is_admin=False, **kwargs):
385 if is_admin:
386 body = cls.admin_client.create_address_scope(name=name, **kwargs)
387 cls.admin_address_scopes.append(body['address_scope'])
388 else:
389 body = cls.client.create_address_scope(name=name, **kwargs)
390 cls.address_scopes.append(body['address_scope'])
391 return body['address_scope']
392
393 @classmethod
394 def create_subnetpool(cls, name, is_admin=False, **kwargs):
395 if is_admin:
396 body = cls.admin_client.create_subnetpool(name, **kwargs)
397 cls.admin_subnetpools.append(body['subnetpool'])
398 else:
399 body = cls.client.create_subnetpool(name, **kwargs)
400 cls.subnetpools.append(body['subnetpool'])
401 return body['subnetpool']
402
403
404class BaseAdminNetworkTest(BaseNetworkTest):
405
406 credentials = ['primary', 'admin']
407
408 @classmethod
409 def setup_clients(cls):
410 super(BaseAdminNetworkTest, cls).setup_clients()
411 cls.admin_client = cls.os_adm.network_client
412 cls.identity_admin_client = cls.os_adm.tenants_client
413
414 @classmethod
415 def create_metering_label(cls, name, description):
416 """Wrapper utility that returns a test metering label."""
417 body = cls.admin_client.create_metering_label(
418 description=description,
419 name=data_utils.rand_name("metering-label"))
420 metering_label = body['metering_label']
421 cls.metering_labels.append(metering_label)
422 return metering_label
423
424 @classmethod
425 def create_metering_label_rule(cls, remote_ip_prefix, direction,
426 metering_label_id):
427 """Wrapper utility that returns a test metering label rule."""
428 body = cls.admin_client.create_metering_label_rule(
429 remote_ip_prefix=remote_ip_prefix, direction=direction,
430 metering_label_id=metering_label_id)
431 metering_label_rule = body['metering_label_rule']
432 cls.metering_label_rules.append(metering_label_rule)
433 return metering_label_rule
434
435 @classmethod
436 def create_flavor(cls, name, description, service_type):
437 """Wrapper utility that returns a test flavor."""
438 body = cls.admin_client.create_flavor(
439 description=description, service_type=service_type,
440 name=name)
441 flavor = body['flavor']
442 cls.flavors.append(flavor)
443 return flavor
444
445 @classmethod
446 def create_service_profile(cls, description, metainfo, driver):
447 """Wrapper utility that returns a test service profile."""
448 body = cls.admin_client.create_service_profile(
449 driver=driver, metainfo=metainfo, description=description)
450 service_profile = body['service_profile']
451 cls.service_profiles.append(service_profile)
452 return service_profile
453
454 @classmethod
455 def get_unused_ip(cls, net_id, ip_version=None):
Gary Kotton011345f2016-06-15 08:04:31 -0700456 """Get an unused ip address in a allocation pool of net"""
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000457 body = cls.admin_client.list_ports(network_id=net_id)
458 ports = body['ports']
459 used_ips = []
460 for port in ports:
461 used_ips.extend(
462 [fixed_ip['ip_address'] for fixed_ip in port['fixed_ips']])
463 body = cls.admin_client.list_subnets(network_id=net_id)
464 subnets = body['subnets']
465
466 for subnet in subnets:
467 if ip_version and subnet['ip_version'] != ip_version:
468 continue
469 cidr = subnet['cidr']
470 allocation_pools = subnet['allocation_pools']
471 iterators = []
472 if allocation_pools:
473 for allocation_pool in allocation_pools:
474 iterators.append(netaddr.iter_iprange(
475 allocation_pool['start'], allocation_pool['end']))
476 else:
477 net = netaddr.IPNetwork(cidr)
478
479 def _iterip():
480 for ip in net:
481 if ip not in (net.network, net.broadcast):
482 yield ip
483 iterators.append(iter(_iterip()))
484
485 for iterator in iterators:
486 for ip in iterator:
487 if str(ip) not in used_ips:
488 return str(ip)
489
490 message = (
491 "net(%s) has no usable IP address in allocation pools" % net_id)
492 raise exceptions.InvalidConfiguration(message)
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200493
494
495def _require_sorting(f):
496 @functools.wraps(f)
497 def inner(self, *args, **kwargs):
Ihar Hrachyshka34feb5b2016-06-14 16:16:06 +0200498 if not test.is_extension_enabled("sorting", "network"):
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200499 self.skipTest('Sorting feature is required')
500 return f(self, *args, **kwargs)
501 return inner
502
503
504def _require_pagination(f):
505 @functools.wraps(f)
506 def inner(self, *args, **kwargs):
Ihar Hrachyshka34feb5b2016-06-14 16:16:06 +0200507 if not test.is_extension_enabled("pagination", "network"):
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200508 self.skipTest('Pagination feature is required')
509 return f(self, *args, **kwargs)
510 return inner
511
512
513class BaseSearchCriteriaTest(BaseNetworkTest):
514
515 # This should be defined by subclasses to reflect resource name to test
516 resource = None
517
Armando Migliaccio57581c62016-07-01 10:13:19 -0700518 field = 'name'
519
Ihar Hrachyshkaa8fe5a12016-05-24 14:50:58 +0200520 # NOTE(ihrachys): some names, like those starting with an underscore (_)
521 # are sorted differently depending on whether the plugin implements native
522 # sorting support, or not. So we avoid any such cases here, sticking to
523 # alphanumeric. Also test a case when there are multiple resources with the
524 # same name
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200525 resource_names = ('test1', 'abc1', 'test10', '123test') + ('test1',)
526
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200527 force_tenant_isolation = True
528
Ihar Hrachyshkaa8fe5a12016-05-24 14:50:58 +0200529 list_kwargs = {}
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200530
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200531 list_as_admin = False
532
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200533 def assertSameOrder(self, original, actual):
534 # gracefully handle iterators passed
535 original = list(original)
536 actual = list(actual)
537 self.assertEqual(len(original), len(actual))
538 for expected, res in zip(original, actual):
Armando Migliaccio57581c62016-07-01 10:13:19 -0700539 self.assertEqual(expected[self.field], res[self.field])
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200540
541 @utils.classproperty
542 def plural_name(self):
543 return '%ss' % self.resource
544
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200545 @property
546 def list_client(self):
547 return self.admin_client if self.list_as_admin else self.client
548
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200549 def list_method(self, *args, **kwargs):
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200550 method = getattr(self.list_client, 'list_%s' % self.plural_name)
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200551 kwargs.update(self.list_kwargs)
552 return method(*args, **kwargs)
553
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200554 def get_bare_url(self, url):
555 base_url = self.client.base_url
556 self.assertTrue(url.startswith(base_url))
557 return url[len(base_url):]
558
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200559 @classmethod
560 def _extract_resources(cls, body):
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200561 return body[cls.plural_name]
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200562
563 def _test_list_sorts(self, direction):
564 sort_args = {
565 'sort_dir': direction,
Armando Migliaccio57581c62016-07-01 10:13:19 -0700566 'sort_key': self.field
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200567 }
568 body = self.list_method(**sort_args)
569 resources = self._extract_resources(body)
570 self.assertNotEmpty(
571 resources, "%s list returned is empty" % self.resource)
Armando Migliaccio57581c62016-07-01 10:13:19 -0700572 retrieved_names = [res[self.field] for res in resources]
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200573 expected = sorted(retrieved_names)
574 if direction == constants.SORT_DIRECTION_DESC:
575 expected = list(reversed(expected))
576 self.assertEqual(expected, retrieved_names)
577
578 @_require_sorting
579 def _test_list_sorts_asc(self):
580 self._test_list_sorts(constants.SORT_DIRECTION_ASC)
581
582 @_require_sorting
583 def _test_list_sorts_desc(self):
584 self._test_list_sorts(constants.SORT_DIRECTION_DESC)
585
586 @_require_pagination
587 def _test_list_pagination(self):
588 for limit in range(1, len(self.resource_names) + 1):
589 pagination_args = {
590 'limit': limit,
591 }
592 body = self.list_method(**pagination_args)
593 resources = self._extract_resources(body)
594 self.assertEqual(limit, len(resources))
595
596 @_require_pagination
597 def _test_list_no_pagination_limit_0(self):
598 pagination_args = {
599 'limit': 0,
600 }
601 body = self.list_method(**pagination_args)
602 resources = self._extract_resources(body)
Béla Vancsicsf1806182016-08-23 07:36:18 +0200603 self.assertGreaterEqual(len(resources), len(self.resource_names))
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200604
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200605 def _test_list_pagination_iteratively(self, lister):
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200606 # first, collect all resources for later comparison
607 sort_args = {
608 'sort_dir': constants.SORT_DIRECTION_ASC,
Armando Migliaccio57581c62016-07-01 10:13:19 -0700609 'sort_key': self.field
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200610 }
611 body = self.list_method(**sort_args)
612 expected_resources = self._extract_resources(body)
613 self.assertNotEmpty(expected_resources)
614
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200615 resources = lister(
616 len(expected_resources), sort_args
617 )
618
619 # finally, compare that the list retrieved in one go is identical to
620 # the one containing pagination results
621 self.assertSameOrder(expected_resources, resources)
622
623 def _list_all_with_marker(self, niterations, sort_args):
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200624 # paginate resources one by one, using last fetched resource as a
625 # marker
626 resources = []
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200627 for i in range(niterations):
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200628 pagination_args = sort_args.copy()
629 pagination_args['limit'] = 1
630 if resources:
631 pagination_args['marker'] = resources[-1]['id']
632 body = self.list_method(**pagination_args)
633 resources_ = self._extract_resources(body)
634 self.assertEqual(1, len(resources_))
635 resources.extend(resources_)
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200636 return resources
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200637
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200638 @_require_pagination
639 @_require_sorting
640 def _test_list_pagination_with_marker(self):
641 self._test_list_pagination_iteratively(self._list_all_with_marker)
642
643 def _list_all_with_hrefs(self, niterations, sort_args):
644 # paginate resources one by one, using next href links
645 resources = []
646 prev_links = {}
647
648 for i in range(niterations):
649 if prev_links:
650 uri = self.get_bare_url(prev_links['next'])
651 else:
Ihar Hrachyshka7f79fe62016-06-07 21:23:44 +0200652 sort_args.update(self.list_kwargs)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200653 uri = self.list_client.build_uri(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200654 self.plural_name, limit=1, **sort_args)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200655 prev_links, body = self.list_client.get_uri_with_links(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200656 self.plural_name, uri
657 )
658 resources_ = self._extract_resources(body)
659 self.assertEqual(1, len(resources_))
660 resources.extend(resources_)
661
662 # The last element is empty and does not contain 'next' link
663 uri = self.get_bare_url(prev_links['next'])
664 prev_links, body = self.client.get_uri_with_links(
665 self.plural_name, uri
666 )
667 self.assertNotIn('next', prev_links)
668
669 # Now walk backwards and compare results
670 resources2 = []
671 for i in range(niterations):
672 uri = self.get_bare_url(prev_links['previous'])
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200673 prev_links, body = self.list_client.get_uri_with_links(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200674 self.plural_name, uri
675 )
676 resources_ = self._extract_resources(body)
677 self.assertEqual(1, len(resources_))
678 resources2.extend(resources_)
679
680 self.assertSameOrder(resources, reversed(resources2))
681
682 return resources
683
684 @_require_pagination
685 @_require_sorting
686 def _test_list_pagination_with_href_links(self):
687 self._test_list_pagination_iteratively(self._list_all_with_hrefs)
688
689 @_require_pagination
690 @_require_sorting
691 def _test_list_pagination_page_reverse_with_href_links(
692 self, direction=constants.SORT_DIRECTION_ASC):
693 pagination_args = {
694 'sort_dir': direction,
Armando Migliaccio57581c62016-07-01 10:13:19 -0700695 'sort_key': self.field,
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200696 }
697 body = self.list_method(**pagination_args)
698 expected_resources = self._extract_resources(body)
699
700 page_size = 2
701 pagination_args['limit'] = page_size
702
703 prev_links = {}
704 resources = []
705 num_resources = len(expected_resources)
706 niterations = int(math.ceil(float(num_resources) / page_size))
707 for i in range(niterations):
708 if prev_links:
709 uri = self.get_bare_url(prev_links['previous'])
710 else:
Ihar Hrachyshka7f79fe62016-06-07 21:23:44 +0200711 pagination_args.update(self.list_kwargs)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200712 uri = self.list_client.build_uri(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200713 self.plural_name, page_reverse=True, **pagination_args)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200714 prev_links, body = self.list_client.get_uri_with_links(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200715 self.plural_name, uri
716 )
717 resources_ = self._extract_resources(body)
Béla Vancsicsf1806182016-08-23 07:36:18 +0200718 self.assertGreaterEqual(page_size, len(resources_))
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200719 resources.extend(reversed(resources_))
720
721 self.assertSameOrder(expected_resources, reversed(resources))
722
723 @_require_pagination
724 @_require_sorting
725 def _test_list_pagination_page_reverse_asc(self):
726 self._test_list_pagination_page_reverse(
727 direction=constants.SORT_DIRECTION_ASC)
728
729 @_require_pagination
730 @_require_sorting
731 def _test_list_pagination_page_reverse_desc(self):
732 self._test_list_pagination_page_reverse(
733 direction=constants.SORT_DIRECTION_DESC)
734
735 def _test_list_pagination_page_reverse(self, direction):
736 pagination_args = {
737 'sort_dir': direction,
Armando Migliaccio57581c62016-07-01 10:13:19 -0700738 'sort_key': self.field,
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200739 'limit': 3,
740 }
741 body = self.list_method(**pagination_args)
742 expected_resources = self._extract_resources(body)
743
744 pagination_args['limit'] -= 1
745 pagination_args['marker'] = expected_resources[-1]['id']
746 pagination_args['page_reverse'] = True
747 body = self.list_method(**pagination_args)
748
749 self.assertSameOrder(
750 # the last entry is not included in 2nd result when used as a
751 # marker
752 expected_resources[:-1],
753 self._extract_resources(body))
Victor Morales1be97b42016-09-05 08:50:06 -0500754
755 def _test_list_validation_filters(self):
756 validation_args = {
757 'unknown_filter': 'value',
758 }
759 body = self.list_method(**validation_args)
760 resources = self._extract_resources(body)
761 for resource in resources:
762 self.assertIn(resource['name'], self.resource_names)