blob: ae01d56f3ae8b94a3f492e5ad14fbf54fc7daa82 [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()
Federico Ressiab286e42018-06-19 09:52:10 +0200130 cls.keypairs = []
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000131
132 @classmethod
133 def resource_cleanup(cls):
134 if CONF.service_available.neutron:
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000135 # Clean up floating IPs
136 for floating_ip in cls.floating_ips:
137 cls._try_delete_resource(cls.client.delete_floatingip,
138 floating_ip['id'])
139 # Clean up routers
140 for router in cls.routers:
141 cls._try_delete_resource(cls.delete_router,
142 router)
143 # Clean up metering label rules
144 for metering_label_rule in cls.metering_label_rules:
145 cls._try_delete_resource(
146 cls.admin_client.delete_metering_label_rule,
147 metering_label_rule['id'])
148 # Clean up metering labels
149 for metering_label in cls.metering_labels:
150 cls._try_delete_resource(
151 cls.admin_client.delete_metering_label,
152 metering_label['id'])
153 # Clean up flavors
154 for flavor in cls.flavors:
155 cls._try_delete_resource(
156 cls.admin_client.delete_flavor,
157 flavor['id'])
158 # Clean up service profiles
159 for service_profile in cls.service_profiles:
160 cls._try_delete_resource(
161 cls.admin_client.delete_service_profile,
162 service_profile['id'])
163 # Clean up ports
164 for port in cls.ports:
165 cls._try_delete_resource(cls.client.delete_port,
166 port['id'])
167 # Clean up subnets
168 for subnet in cls.subnets:
169 cls._try_delete_resource(cls.client.delete_subnet,
170 subnet['id'])
Kevin Bentonba3651c2017-09-01 17:13:01 -0700171 # Clean up admin subnets
172 for subnet in cls.admin_subnets:
173 cls._try_delete_resource(cls.admin_client.delete_subnet,
174 subnet['id'])
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000175 # Clean up networks
176 for network in cls.networks:
Federico Ressi61b564e2018-07-06 08:10:31 +0200177 cls._try_delete_resource(cls.delete_network, network)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000178
Miguel Lavalle124378b2016-09-21 16:41:47 -0500179 # Clean up admin networks
180 for network in cls.admin_networks:
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000181 cls._try_delete_resource(cls.admin_client.delete_network,
182 network['id'])
183
Itzik Brownbac51dc2016-10-31 12:25:04 +0000184 # Clean up security groups
185 for secgroup in cls.security_groups:
186 cls._try_delete_resource(cls.client.delete_security_group,
187 secgroup['id'])
188
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000189 for subnetpool in cls.subnetpools:
190 cls._try_delete_resource(cls.client.delete_subnetpool,
191 subnetpool['id'])
192
193 for subnetpool in cls.admin_subnetpools:
194 cls._try_delete_resource(cls.admin_client.delete_subnetpool,
195 subnetpool['id'])
196
197 for address_scope in cls.address_scopes:
198 cls._try_delete_resource(cls.client.delete_address_scope,
199 address_scope['id'])
200
201 for address_scope in cls.admin_address_scopes:
202 cls._try_delete_resource(
203 cls.admin_client.delete_address_scope,
204 address_scope['id'])
205
Chandan Kumarc125fd12017-11-15 19:41:01 +0530206 for project in cls.projects:
207 cls._try_delete_resource(
208 cls.identity_admin_client.delete_project,
209 project['id'])
210
Sławek Kapłońskie100c4d2017-08-23 21:18:34 +0000211 # Clean up QoS rules
212 for qos_rule in cls.qos_rules:
213 cls._try_delete_resource(cls.admin_client.delete_qos_rule,
214 qos_rule['id'])
215 # Clean up QoS policies
216 # as all networks and ports are already removed, QoS policies
217 # shouldn't be "in use"
218 for qos_policy in cls.qos_policies:
219 cls._try_delete_resource(cls.admin_client.delete_qos_policy,
220 qos_policy['id'])
221
Nguyen Phuong An67993fc2017-11-24 11:30:25 +0700222 # Clean up log_objects
223 for log_object in cls.log_objects:
224 cls._try_delete_resource(cls.admin_client.delete_log,
225 log_object['id'])
226
Federico Ressiab286e42018-06-19 09:52:10 +0200227 for keypair in cls.keypairs:
228 cls._try_delete_resource(cls.delete_keypair, keypair)
229
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000230 super(BaseNetworkTest, cls).resource_cleanup()
231
232 @classmethod
233 def _try_delete_resource(cls, delete_callable, *args, **kwargs):
234 """Cleanup resources in case of test-failure
235
236 Some resources are explicitly deleted by the test.
237 If the test failed to delete a resource, this method will execute
238 the appropriate delete methods. Otherwise, the method ignores NotFound
239 exceptions thrown for resources that were correctly deleted by the
240 test.
241
242 :param delete_callable: delete method
243 :param args: arguments for delete method
244 :param kwargs: keyword arguments for delete method
245 """
246 try:
247 delete_callable(*args, **kwargs)
248 # if resource is not found, this means it was deleted in the test
249 except lib_exc.NotFound:
250 pass
251
252 @classmethod
Federico Ressi61b564e2018-07-06 08:10:31 +0200253 def create_network(cls, network_name=None, client=None, external=None,
254 shared=None, provider_network_type=None,
255 provider_physical_network=None,
256 provider_segmentation_id=None, **kwargs):
257 """Create a network.
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000258
Federico Ressi61b564e2018-07-06 08:10:31 +0200259 When client is not provider and admin_client is attribute is not None
260 (for example when using BaseAdminNetworkTest base class) and using any
261 of the convenience parameters (external, shared, provider_network_type,
262 provider_physical_network and provider_segmentation_id) it silently
263 uses admin_client. If the network is not shared then it uses the same
264 project_id as regular client.
265
266 :param network_name: Human-readable name of the network
267
268 :param client: client to be used for connecting to network service
269
270 :param external: indicates whether the network has an external routing
271 facility that's not managed by the networking service.
272
273 :param shared: indicates whether this resource is shared across all
274 projects. By default, only administrative users can change this value.
275 If True and admin_client attribute is not None, then the network is
276 created under administrative project.
277
278 :param provider_network_type: the type of physical network that this
279 network should be mapped to. For example, 'flat', 'vlan', 'vxlan', or
280 'gre'. Valid values depend on a networking back-end.
281
282 :param provider_physical_network: the physical network where this
283 network should be implemented. The Networking API v2.0 does not provide
284 a way to list available physical networks. For example, the Open
285 vSwitch plug-in configuration file defines a symbolic name that maps to
286 specific bridges on each compute host.
287
288 :param provider_segmentation_id: The ID of the isolated segment on the
289 physical network. The network_type attribute defines the segmentation
290 model. For example, if the network_type value is 'vlan', this ID is a
291 vlan identifier. If the network_type value is 'gre', this ID is a gre
292 key.
293
294 :param **kwargs: extra parameters to be forwarded to network service
295 """
296
297 name = (network_name or kwargs.pop('name', None) or
298 data_utils.rand_name('test-network-'))
299
300 # translate convenience parameters
301 admin_client_required = False
302 if provider_network_type:
303 admin_client_required = True
304 kwargs['provider:network_type'] = provider_network_type
305 if provider_physical_network:
306 admin_client_required = True
307 kwargs['provider:physical_network'] = provider_physical_network
308 if provider_segmentation_id:
309 admin_client_required = True
310 kwargs['provider:segmentation_id'] = provider_segmentation_id
311 if external is not None:
312 admin_client_required = True
313 kwargs['router:external'] = bool(external)
314 if shared is not None:
315 admin_client_required = True
316 kwargs['shared'] = bool(shared)
317
318 if not client:
319 if admin_client_required and cls.admin_client:
320 # For convenience silently switch to admin client
321 client = cls.admin_client
322 if not shared:
323 # Keep this network visible from current project
324 project_id = (kwargs.get('project_id') or
325 kwargs.get('tenant_id') or
326 cls.client.tenant_id)
327 kwargs.update(project_id=project_id, tenant_id=project_id)
328 else:
329 # Use default client
330 client = cls.client
331
332 network = client.create_network(name=name, **kwargs)['network']
333 network['client'] = client
334 cls.networks.append(network)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000335 return network
336
337 @classmethod
Federico Ressi61b564e2018-07-06 08:10:31 +0200338 def delete_network(cls, network, client=None):
339 client = client or network.get('client') or cls.client
340 client.delete_network(network['id'])
341
342 @classmethod
343 def create_shared_network(cls, network_name=None, **kwargs):
344 return cls.create_network(name=network_name, shared=True, **kwargs)
Miguel Lavalle124378b2016-09-21 16:41:47 -0500345
346 @classmethod
347 def create_network_keystone_v3(cls, network_name=None, project_id=None,
348 tenant_id=None, client=None):
Federico Ressi61b564e2018-07-06 08:10:31 +0200349 params = {}
350 if project_id:
351 params['project_id'] = project_id
352 if tenant_id:
353 params['tenant_id'] = tenant_id
354 return cls.create_network(name=network_name, client=client, **params)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000355
356 @classmethod
Sławek Kapłońskid98e27d2018-05-07 16:16:28 +0200357 def create_subnet(cls, network, gateway='', cidr=None, mask_bits=None,
Federico Ressi98f20ec2018-05-11 06:09:49 +0200358 ip_version=None, client=None, reserve_cidr=True,
359 **kwargs):
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200360 """Wrapper utility that returns a test subnet.
361
362 Convenient wrapper for client.create_subnet method. It reserves and
363 allocates CIDRs to avoid creating overlapping subnets.
364
365 :param network: network where to create the subnet
366 network['id'] must contain the ID of the network
367
368 :param gateway: gateway IP address
369 It can be a str or a netaddr.IPAddress
370 If gateway is not given, then it will use default address for
371 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 +0200372 if gateway is given as None then no gateway will be assigned
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200373
374 :param cidr: CIDR of the subnet to create
375 It can be either None, a str or a netaddr.IPNetwork instance
376
377 :param mask_bits: CIDR prefix length
378 It can be either None or a numeric value.
379 If cidr parameter is given then mask_bits is used to determinate a
380 sequence of valid CIDR to use as generated.
381 Please see netaddr.IPNetwork.subnet method documentation[1]
382
383 :param ip_version: ip version of generated subnet CIDRs
384 It can be None, IP_VERSION_4 or IP_VERSION_6
385 It has to match given either given CIDR and gateway
386
387 :param ip_version: numeric value (either IP_VERSION_4 or IP_VERSION_6)
388 this value must match CIDR and gateway IP versions if any of them is
389 given
390
391 :param client: client to be used to connect to network service
392
Federico Ressi98f20ec2018-05-11 06:09:49 +0200393 :param reserve_cidr: if True then it reserves assigned CIDR to avoid
394 using the same CIDR for further subnets in the scope of the same
395 test case class
396
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200397 :param **kwargs: optional parameters to be forwarded to wrapped method
398
399 [1] http://netaddr.readthedocs.io/en/latest/tutorial_01.html#supernets-and-subnets # noqa
400 """
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000401
402 # allow tests to use admin client
403 if not client:
404 client = cls.client
405
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200406 if gateway:
407 gateway_ip = netaddr.IPAddress(gateway)
408 if ip_version:
409 if ip_version != gateway_ip.version:
410 raise ValueError(
411 "Gateway IP version doesn't match IP version")
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000412 else:
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200413 ip_version = gateway_ip.version
Sławek Kapłońskid98e27d2018-05-07 16:16:28 +0200414 else:
415 ip_version = ip_version or cls._ip_version
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200416
417 for subnet_cidr in cls.get_subnet_cidrs(
418 ip_version=ip_version, cidr=cidr, mask_bits=mask_bits):
Federico Ressi98f20ec2018-05-11 06:09:49 +0200419 if gateway is not None:
420 kwargs['gateway_ip'] = str(gateway or (subnet_cidr.ip + 1))
421 try:
422 body = client.create_subnet(
423 network_id=network['id'],
424 cidr=str(subnet_cidr),
425 ip_version=subnet_cidr.version,
426 **kwargs)
427 break
428 except lib_exc.BadRequest as e:
429 if 'overlaps with another subnet' not in str(e):
430 raise
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000431 else:
432 message = 'Available CIDR for subnet creation could not be found'
433 raise ValueError(message)
434 subnet = body['subnet']
Kevin Bentonba3651c2017-09-01 17:13:01 -0700435 if client is cls.client:
436 cls.subnets.append(subnet)
437 else:
438 cls.admin_subnets.append(subnet)
Federico Ressi98f20ec2018-05-11 06:09:49 +0200439 if reserve_cidr:
440 cls.reserve_subnet_cidr(subnet_cidr)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000441 return subnet
442
443 @classmethod
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200444 def reserve_subnet_cidr(cls, addr, **ipnetwork_kwargs):
445 """Reserve given subnet CIDR making sure it is not used by create_subnet
446
447 :param addr: the CIDR address to be reserved
448 It can be a str or netaddr.IPNetwork instance
449
450 :param **ipnetwork_kwargs: optional netaddr.IPNetwork constructor
451 parameters
452 """
453
454 if not cls.try_reserve_subnet_cidr(addr, **ipnetwork_kwargs):
455 raise ValueError('Subnet CIDR already reserved: %r'.format(
456 addr))
457
458 @classmethod
459 def try_reserve_subnet_cidr(cls, addr, **ipnetwork_kwargs):
460 """Reserve given subnet CIDR if it hasn't been reserved before
461
462 :param addr: the CIDR address to be reserved
463 It can be a str or netaddr.IPNetwork instance
464
465 :param **ipnetwork_kwargs: optional netaddr.IPNetwork constructor
466 parameters
467
468 :return: True if it wasn't reserved before, False elsewhere.
469 """
470
471 subnet_cidr = netaddr.IPNetwork(addr, **ipnetwork_kwargs)
472 if subnet_cidr in cls.reserved_subnet_cidrs:
473 return False
474 else:
475 cls.reserved_subnet_cidrs.add(subnet_cidr)
476 return True
477
478 @classmethod
479 def get_subnet_cidrs(
480 cls, cidr=None, mask_bits=None, ip_version=None):
481 """Iterate over a sequence of unused subnet CIDR for IP version
482
483 :param cidr: CIDR of the subnet to create
484 It can be either None, a str or a netaddr.IPNetwork instance
485
486 :param mask_bits: CIDR prefix length
487 It can be either None or a numeric value.
488 If cidr parameter is given then mask_bits is used to determinate a
489 sequence of valid CIDR to use as generated.
490 Please see netaddr.IPNetwork.subnet method documentation[1]
491
492 :param ip_version: ip version of generated subnet CIDRs
493 It can be None, IP_VERSION_4 or IP_VERSION_6
494 It has to match given CIDR if given
495
496 :return: iterator over reserved CIDRs of type netaddr.IPNetwork
497
498 [1] http://netaddr.readthedocs.io/en/latest/tutorial_01.html#supernets-and-subnets # noqa
499 """
500
501 if cidr:
502 # Generate subnet CIDRs starting from given CIDR
503 # checking it is of requested IP version
504 cidr = netaddr.IPNetwork(cidr, version=ip_version)
505 else:
506 # Generate subnet CIDRs starting from configured values
507 ip_version = ip_version or cls._ip_version
508 if ip_version == const.IP_VERSION_4:
509 mask_bits = mask_bits or config.safe_get_config_value(
510 'network', 'project_network_mask_bits')
511 cidr = netaddr.IPNetwork(config.safe_get_config_value(
512 'network', 'project_network_cidr'))
513 elif ip_version == const.IP_VERSION_6:
514 mask_bits = config.safe_get_config_value(
515 'network', 'project_network_v6_mask_bits')
516 cidr = netaddr.IPNetwork(config.safe_get_config_value(
517 'network', 'project_network_v6_cidr'))
518 else:
519 raise ValueError('Invalid IP version: {!r}'.format(ip_version))
520
521 if mask_bits:
522 subnet_cidrs = cidr.subnet(mask_bits)
523 else:
524 subnet_cidrs = iter([cidr])
525
526 for subnet_cidr in subnet_cidrs:
527 if subnet_cidr not in cls.reserved_subnet_cidrs:
528 yield subnet_cidr
529
530 @classmethod
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000531 def create_port(cls, network, **kwargs):
532 """Wrapper utility that returns a test port."""
Edan Davidd75e48e2018-01-03 02:49:52 -0500533 if CONF.network.port_vnic_type and 'binding:vnic_type' not in kwargs:
534 kwargs['binding:vnic_type'] = CONF.network.port_vnic_type
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000535 body = cls.client.create_port(network_id=network['id'],
536 **kwargs)
537 port = body['port']
538 cls.ports.append(port)
539 return port
540
541 @classmethod
542 def update_port(cls, port, **kwargs):
543 """Wrapper utility that updates a test port."""
544 body = cls.client.update_port(port['id'],
545 **kwargs)
546 return body['port']
547
548 @classmethod
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300549 def _create_router_with_client(
550 cls, client, router_name=None, admin_state_up=False,
551 external_network_id=None, enable_snat=None, **kwargs
552 ):
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000553 ext_gw_info = {}
554 if external_network_id:
555 ext_gw_info['network_id'] = external_network_id
YAMAMOTO Takashi9bd4f972017-06-20 12:49:30 +0900556 if enable_snat is not None:
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000557 ext_gw_info['enable_snat'] = enable_snat
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300558 body = client.create_router(
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000559 router_name, external_gateway_info=ext_gw_info,
560 admin_state_up=admin_state_up, **kwargs)
561 router = body['router']
562 cls.routers.append(router)
563 return router
564
565 @classmethod
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300566 def create_router(cls, *args, **kwargs):
567 return cls._create_router_with_client(cls.client, *args, **kwargs)
568
569 @classmethod
570 def create_admin_router(cls, *args, **kwargs):
rajat294495c042017-06-28 15:37:16 +0530571 return cls._create_router_with_client(cls.os_admin.network_client,
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300572 *args, **kwargs)
573
574 @classmethod
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000575 def create_floatingip(cls, external_network_id):
576 """Wrapper utility that returns a test floating IP."""
577 body = cls.client.create_floatingip(
578 floating_network_id=external_network_id)
579 fip = body['floatingip']
580 cls.floating_ips.append(fip)
581 return fip
582
583 @classmethod
584 def create_router_interface(cls, router_id, subnet_id):
585 """Wrapper utility that returns a router interface."""
586 interface = cls.client.add_router_interface_with_subnet_id(
587 router_id, subnet_id)
588 return interface
589
590 @classmethod
Sławek Kapłońskiff294062016-12-04 15:00:54 +0000591 def get_supported_qos_rule_types(cls):
592 body = cls.client.list_qos_rule_types()
593 return [rule_type['type'] for rule_type in body['rule_types']]
594
595 @classmethod
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200596 def create_qos_policy(cls, name, description=None, shared=False,
Hirofumi Ichihara39a6ee12017-08-23 13:55:12 +0900597 tenant_id=None, is_default=False):
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000598 """Wrapper utility that returns a test QoS policy."""
599 body = cls.admin_client.create_qos_policy(
Hirofumi Ichihara39a6ee12017-08-23 13:55:12 +0900600 name, description, shared, tenant_id, is_default)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000601 qos_policy = body['policy']
602 cls.qos_policies.append(qos_policy)
603 return qos_policy
604
605 @classmethod
Sławek Kapłoński153f3452017-03-24 22:04:53 +0000606 def create_qos_bandwidth_limit_rule(cls, policy_id, max_kbps,
607 max_burst_kbps,
Chandan Kumarc125fd12017-11-15 19:41:01 +0530608 direction=const.EGRESS_DIRECTION):
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000609 """Wrapper utility that returns a test QoS bandwidth limit rule."""
610 body = cls.admin_client.create_bandwidth_limit_rule(
Sławek Kapłoński153f3452017-03-24 22:04:53 +0000611 policy_id, max_kbps, max_burst_kbps, direction)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000612 qos_rule = body['bandwidth_limit_rule']
613 cls.qos_rules.append(qos_rule)
614 return qos_rule
615
616 @classmethod
Jakub Libosvar83704832017-12-06 16:02:28 +0000617 def delete_router(cls, router, client=None):
618 client = client or cls.client
619 body = client.list_router_interfaces(router['id'])
Chandan Kumarc125fd12017-11-15 19:41:01 +0530620 interfaces = [port for port in body['ports']
621 if port['device_owner'] in const.ROUTER_INTERFACE_OWNERS]
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000622 for i in interfaces:
623 try:
Jakub Libosvar83704832017-12-06 16:02:28 +0000624 client.remove_router_interface_with_subnet_id(
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000625 router['id'], i['fixed_ips'][0]['subnet_id'])
626 except lib_exc.NotFound:
627 pass
Jakub Libosvar83704832017-12-06 16:02:28 +0000628 client.delete_router(router['id'])
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000629
630 @classmethod
631 def create_address_scope(cls, name, is_admin=False, **kwargs):
632 if is_admin:
633 body = cls.admin_client.create_address_scope(name=name, **kwargs)
634 cls.admin_address_scopes.append(body['address_scope'])
635 else:
636 body = cls.client.create_address_scope(name=name, **kwargs)
637 cls.address_scopes.append(body['address_scope'])
638 return body['address_scope']
639
640 @classmethod
641 def create_subnetpool(cls, name, is_admin=False, **kwargs):
642 if is_admin:
643 body = cls.admin_client.create_subnetpool(name, **kwargs)
644 cls.admin_subnetpools.append(body['subnetpool'])
645 else:
646 body = cls.client.create_subnetpool(name, **kwargs)
647 cls.subnetpools.append(body['subnetpool'])
648 return body['subnetpool']
649
Chandan Kumarc125fd12017-11-15 19:41:01 +0530650 @classmethod
651 def create_project(cls, name=None, description=None):
652 test_project = name or data_utils.rand_name('test_project_')
653 test_description = description or data_utils.rand_name('desc_')
654 project = cls.identity_admin_client.create_project(
655 name=test_project,
656 description=test_description)['project']
657 cls.projects.append(project)
658 return project
659
660 @classmethod
661 def create_security_group(cls, name, **kwargs):
662 body = cls.client.create_security_group(name=name, **kwargs)
663 cls.security_groups.append(body['security_group'])
664 return body['security_group']
665
Federico Ressiab286e42018-06-19 09:52:10 +0200666 @classmethod
667 def create_keypair(cls, client=None, name=None, **kwargs):
668 client = client or cls.os_primary.keypairs_client
669 name = name or data_utils.rand_name('keypair-test')
670 keypair = client.create_keypair(name=name, **kwargs)['keypair']
671
672 # save client for later cleanup
673 keypair['client'] = client
674 cls.keypairs.append(keypair)
675 return keypair
676
677 @classmethod
678 def delete_keypair(cls, keypair, client=None):
679 client = (client or keypair.get('client') or
680 cls.os_primary.keypairs_client)
681 client.delete_keypair(keypair_name=keypair['name'])
682
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000683
684class BaseAdminNetworkTest(BaseNetworkTest):
685
686 credentials = ['primary', 'admin']
687
688 @classmethod
689 def setup_clients(cls):
690 super(BaseAdminNetworkTest, cls).setup_clients()
fumihiko kakumaa216fc12017-07-14 10:43:29 +0900691 cls.admin_client = cls.os_admin.network_client
Jakub Libosvarf5758012017-08-15 13:45:30 +0000692 cls.identity_admin_client = cls.os_admin.projects_client
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000693
694 @classmethod
695 def create_metering_label(cls, name, description):
696 """Wrapper utility that returns a test metering label."""
697 body = cls.admin_client.create_metering_label(
698 description=description,
699 name=data_utils.rand_name("metering-label"))
700 metering_label = body['metering_label']
701 cls.metering_labels.append(metering_label)
702 return metering_label
703
704 @classmethod
705 def create_metering_label_rule(cls, remote_ip_prefix, direction,
706 metering_label_id):
707 """Wrapper utility that returns a test metering label rule."""
708 body = cls.admin_client.create_metering_label_rule(
709 remote_ip_prefix=remote_ip_prefix, direction=direction,
710 metering_label_id=metering_label_id)
711 metering_label_rule = body['metering_label_rule']
712 cls.metering_label_rules.append(metering_label_rule)
713 return metering_label_rule
714
715 @classmethod
716 def create_flavor(cls, name, description, service_type):
717 """Wrapper utility that returns a test flavor."""
718 body = cls.admin_client.create_flavor(
719 description=description, service_type=service_type,
720 name=name)
721 flavor = body['flavor']
722 cls.flavors.append(flavor)
723 return flavor
724
725 @classmethod
726 def create_service_profile(cls, description, metainfo, driver):
727 """Wrapper utility that returns a test service profile."""
728 body = cls.admin_client.create_service_profile(
729 driver=driver, metainfo=metainfo, description=description)
730 service_profile = body['service_profile']
731 cls.service_profiles.append(service_profile)
732 return service_profile
733
734 @classmethod
Nguyen Phuong An67993fc2017-11-24 11:30:25 +0700735 def create_log(cls, name, description=None,
736 resource_type='security_group', resource_id=None,
737 target_id=None, event='ALL', enabled=True):
738 """Wrapper utility that returns a test log object."""
739 log_args = {'name': name,
740 'description': description,
741 'resource_type': resource_type,
742 'resource_id': resource_id,
743 'target_id': target_id,
744 'event': event,
745 'enabled': enabled}
746 body = cls.admin_client.create_log(**log_args)
747 log_object = body['log']
748 cls.log_objects.append(log_object)
749 return log_object
750
751 @classmethod
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000752 def get_unused_ip(cls, net_id, ip_version=None):
Gary Kotton011345f2016-06-15 08:04:31 -0700753 """Get an unused ip address in a allocation pool of net"""
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000754 body = cls.admin_client.list_ports(network_id=net_id)
755 ports = body['ports']
756 used_ips = []
757 for port in ports:
758 used_ips.extend(
759 [fixed_ip['ip_address'] for fixed_ip in port['fixed_ips']])
760 body = cls.admin_client.list_subnets(network_id=net_id)
761 subnets = body['subnets']
762
763 for subnet in subnets:
764 if ip_version and subnet['ip_version'] != ip_version:
765 continue
766 cidr = subnet['cidr']
767 allocation_pools = subnet['allocation_pools']
768 iterators = []
769 if allocation_pools:
770 for allocation_pool in allocation_pools:
771 iterators.append(netaddr.iter_iprange(
772 allocation_pool['start'], allocation_pool['end']))
773 else:
774 net = netaddr.IPNetwork(cidr)
775
776 def _iterip():
777 for ip in net:
778 if ip not in (net.network, net.broadcast):
779 yield ip
780 iterators.append(iter(_iterip()))
781
782 for iterator in iterators:
783 for ip in iterator:
784 if str(ip) not in used_ips:
785 return str(ip)
786
787 message = (
788 "net(%s) has no usable IP address in allocation pools" % net_id)
789 raise exceptions.InvalidConfiguration(message)
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200790
791
Sławek Kapłońskiff294062016-12-04 15:00:54 +0000792def require_qos_rule_type(rule_type):
793 def decorator(f):
794 @functools.wraps(f)
795 def wrapper(self, *func_args, **func_kwargs):
796 if rule_type not in self.get_supported_qos_rule_types():
797 raise self.skipException(
798 "%s rule type is required." % rule_type)
799 return f(self, *func_args, **func_kwargs)
800 return wrapper
801 return decorator
802
803
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200804def _require_sorting(f):
805 @functools.wraps(f)
806 def inner(self, *args, **kwargs):
Chandan Kumarc125fd12017-11-15 19:41:01 +0530807 if not tutils.is_extension_enabled("sorting", "network"):
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200808 self.skipTest('Sorting feature is required')
809 return f(self, *args, **kwargs)
810 return inner
811
812
813def _require_pagination(f):
814 @functools.wraps(f)
815 def inner(self, *args, **kwargs):
Chandan Kumarc125fd12017-11-15 19:41:01 +0530816 if not tutils.is_extension_enabled("pagination", "network"):
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200817 self.skipTest('Pagination feature is required')
818 return f(self, *args, **kwargs)
819 return inner
820
821
822class BaseSearchCriteriaTest(BaseNetworkTest):
823
824 # This should be defined by subclasses to reflect resource name to test
825 resource = None
826
Armando Migliaccio57581c62016-07-01 10:13:19 -0700827 field = 'name'
828
Ihar Hrachyshkaa8fe5a12016-05-24 14:50:58 +0200829 # NOTE(ihrachys): some names, like those starting with an underscore (_)
830 # are sorted differently depending on whether the plugin implements native
831 # sorting support, or not. So we avoid any such cases here, sticking to
832 # alphanumeric. Also test a case when there are multiple resources with the
833 # same name
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200834 resource_names = ('test1', 'abc1', 'test10', '123test') + ('test1',)
835
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200836 force_tenant_isolation = True
837
Ihar Hrachyshkaa8fe5a12016-05-24 14:50:58 +0200838 list_kwargs = {}
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200839
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200840 list_as_admin = False
841
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200842 def assertSameOrder(self, original, actual):
843 # gracefully handle iterators passed
844 original = list(original)
845 actual = list(actual)
846 self.assertEqual(len(original), len(actual))
847 for expected, res in zip(original, actual):
Armando Migliaccio57581c62016-07-01 10:13:19 -0700848 self.assertEqual(expected[self.field], res[self.field])
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200849
850 @utils.classproperty
851 def plural_name(self):
852 return '%ss' % self.resource
853
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200854 @property
855 def list_client(self):
856 return self.admin_client if self.list_as_admin else self.client
857
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200858 def list_method(self, *args, **kwargs):
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200859 method = getattr(self.list_client, 'list_%s' % self.plural_name)
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200860 kwargs.update(self.list_kwargs)
861 return method(*args, **kwargs)
862
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200863 def get_bare_url(self, url):
864 base_url = self.client.base_url
865 self.assertTrue(url.startswith(base_url))
866 return url[len(base_url):]
867
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200868 @classmethod
869 def _extract_resources(cls, body):
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200870 return body[cls.plural_name]
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200871
872 def _test_list_sorts(self, direction):
873 sort_args = {
874 'sort_dir': direction,
Armando Migliaccio57581c62016-07-01 10:13:19 -0700875 'sort_key': self.field
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200876 }
877 body = self.list_method(**sort_args)
878 resources = self._extract_resources(body)
879 self.assertNotEmpty(
880 resources, "%s list returned is empty" % self.resource)
Armando Migliaccio57581c62016-07-01 10:13:19 -0700881 retrieved_names = [res[self.field] for res in resources]
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200882 expected = sorted(retrieved_names)
883 if direction == constants.SORT_DIRECTION_DESC:
884 expected = list(reversed(expected))
885 self.assertEqual(expected, retrieved_names)
886
887 @_require_sorting
888 def _test_list_sorts_asc(self):
889 self._test_list_sorts(constants.SORT_DIRECTION_ASC)
890
891 @_require_sorting
892 def _test_list_sorts_desc(self):
893 self._test_list_sorts(constants.SORT_DIRECTION_DESC)
894
895 @_require_pagination
896 def _test_list_pagination(self):
897 for limit in range(1, len(self.resource_names) + 1):
898 pagination_args = {
899 'limit': limit,
900 }
901 body = self.list_method(**pagination_args)
902 resources = self._extract_resources(body)
903 self.assertEqual(limit, len(resources))
904
905 @_require_pagination
906 def _test_list_no_pagination_limit_0(self):
907 pagination_args = {
908 'limit': 0,
909 }
910 body = self.list_method(**pagination_args)
911 resources = self._extract_resources(body)
Béla Vancsicsf1806182016-08-23 07:36:18 +0200912 self.assertGreaterEqual(len(resources), len(self.resource_names))
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200913
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200914 def _test_list_pagination_iteratively(self, lister):
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200915 # first, collect all resources for later comparison
916 sort_args = {
917 'sort_dir': constants.SORT_DIRECTION_ASC,
Armando Migliaccio57581c62016-07-01 10:13:19 -0700918 'sort_key': self.field
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200919 }
920 body = self.list_method(**sort_args)
921 expected_resources = self._extract_resources(body)
922 self.assertNotEmpty(expected_resources)
923
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200924 resources = lister(
925 len(expected_resources), sort_args
926 )
927
928 # finally, compare that the list retrieved in one go is identical to
929 # the one containing pagination results
930 self.assertSameOrder(expected_resources, resources)
931
932 def _list_all_with_marker(self, niterations, sort_args):
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200933 # paginate resources one by one, using last fetched resource as a
934 # marker
935 resources = []
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200936 for i in range(niterations):
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200937 pagination_args = sort_args.copy()
938 pagination_args['limit'] = 1
939 if resources:
940 pagination_args['marker'] = resources[-1]['id']
941 body = self.list_method(**pagination_args)
942 resources_ = self._extract_resources(body)
943 self.assertEqual(1, len(resources_))
944 resources.extend(resources_)
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200945 return resources
Ihar Hrachyshka59382252016-04-05 15:54:33 +0200946
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200947 @_require_pagination
948 @_require_sorting
949 def _test_list_pagination_with_marker(self):
950 self._test_list_pagination_iteratively(self._list_all_with_marker)
951
952 def _list_all_with_hrefs(self, niterations, sort_args):
953 # paginate resources one by one, using next href links
954 resources = []
955 prev_links = {}
956
957 for i in range(niterations):
958 if prev_links:
959 uri = self.get_bare_url(prev_links['next'])
960 else:
Ihar Hrachyshka7f79fe62016-06-07 21:23:44 +0200961 sort_args.update(self.list_kwargs)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200962 uri = self.list_client.build_uri(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200963 self.plural_name, limit=1, **sort_args)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200964 prev_links, body = self.list_client.get_uri_with_links(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200965 self.plural_name, uri
966 )
967 resources_ = self._extract_resources(body)
968 self.assertEqual(1, len(resources_))
969 resources.extend(resources_)
970
971 # The last element is empty and does not contain 'next' link
972 uri = self.get_bare_url(prev_links['next'])
973 prev_links, body = self.client.get_uri_with_links(
974 self.plural_name, uri
975 )
976 self.assertNotIn('next', prev_links)
977
978 # Now walk backwards and compare results
979 resources2 = []
980 for i in range(niterations):
981 uri = self.get_bare_url(prev_links['previous'])
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200982 prev_links, body = self.list_client.get_uri_with_links(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +0200983 self.plural_name, uri
984 )
985 resources_ = self._extract_resources(body)
986 self.assertEqual(1, len(resources_))
987 resources2.extend(resources_)
988
989 self.assertSameOrder(resources, reversed(resources2))
990
991 return resources
992
993 @_require_pagination
994 @_require_sorting
995 def _test_list_pagination_with_href_links(self):
996 self._test_list_pagination_iteratively(self._list_all_with_hrefs)
997
998 @_require_pagination
999 @_require_sorting
1000 def _test_list_pagination_page_reverse_with_href_links(
1001 self, direction=constants.SORT_DIRECTION_ASC):
1002 pagination_args = {
1003 'sort_dir': direction,
Armando Migliaccio57581c62016-07-01 10:13:19 -07001004 'sort_key': self.field,
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001005 }
1006 body = self.list_method(**pagination_args)
1007 expected_resources = self._extract_resources(body)
1008
1009 page_size = 2
1010 pagination_args['limit'] = page_size
1011
1012 prev_links = {}
1013 resources = []
1014 num_resources = len(expected_resources)
1015 niterations = int(math.ceil(float(num_resources) / page_size))
1016 for i in range(niterations):
1017 if prev_links:
1018 uri = self.get_bare_url(prev_links['previous'])
1019 else:
Ihar Hrachyshka7f79fe62016-06-07 21:23:44 +02001020 pagination_args.update(self.list_kwargs)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001021 uri = self.list_client.build_uri(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001022 self.plural_name, page_reverse=True, **pagination_args)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001023 prev_links, body = self.list_client.get_uri_with_links(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001024 self.plural_name, uri
1025 )
1026 resources_ = self._extract_resources(body)
Béla Vancsicsf1806182016-08-23 07:36:18 +02001027 self.assertGreaterEqual(page_size, len(resources_))
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001028 resources.extend(reversed(resources_))
1029
1030 self.assertSameOrder(expected_resources, reversed(resources))
1031
1032 @_require_pagination
1033 @_require_sorting
1034 def _test_list_pagination_page_reverse_asc(self):
1035 self._test_list_pagination_page_reverse(
1036 direction=constants.SORT_DIRECTION_ASC)
1037
1038 @_require_pagination
1039 @_require_sorting
1040 def _test_list_pagination_page_reverse_desc(self):
1041 self._test_list_pagination_page_reverse(
1042 direction=constants.SORT_DIRECTION_DESC)
1043
1044 def _test_list_pagination_page_reverse(self, direction):
1045 pagination_args = {
1046 'sort_dir': direction,
Armando Migliaccio57581c62016-07-01 10:13:19 -07001047 'sort_key': self.field,
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001048 'limit': 3,
1049 }
1050 body = self.list_method(**pagination_args)
1051 expected_resources = self._extract_resources(body)
1052
1053 pagination_args['limit'] -= 1
1054 pagination_args['marker'] = expected_resources[-1]['id']
1055 pagination_args['page_reverse'] = True
1056 body = self.list_method(**pagination_args)
1057
1058 self.assertSameOrder(
1059 # the last entry is not included in 2nd result when used as a
1060 # marker
1061 expected_resources[:-1],
1062 self._extract_resources(body))
Victor Morales1be97b42016-09-05 08:50:06 -05001063
1064 def _test_list_validation_filters(self):
1065 validation_args = {
1066 'unknown_filter': 'value',
1067 }
1068 body = self.list_method(**validation_args)
1069 resources = self._extract_resources(body)
1070 for resource in resources:
1071 self.assertIn(resource['name'], self.resource_names)