blob: f5bb911197434a1159dc2a9fb78c81f35ffbaeb4 [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
Lajos Katona2f904652018-08-23 14:04:56 +020018import time
Ihar Hrachyshka59382252016-04-05 15:54:33 +020019
Daniel Mellado3c0aeab2016-01-29 11:30:25 +000020import netaddr
Chandan Kumarc125fd12017-11-15 19:41:01 +053021from neutron_lib import constants as const
Lajos Katona2f904652018-08-23 14:04:56 +020022from oslo_log import log
Chandan Kumarc125fd12017-11-15 19:41:01 +053023from tempest.common import utils as tutils
Daniel Mellado3c0aeab2016-01-29 11:30:25 +000024from tempest.lib.common.utils import data_utils
25from tempest.lib import exceptions as lib_exc
26from tempest import test
27
Chandan Kumar667d3d32017-09-22 12:24:06 +053028from neutron_tempest_plugin.api import clients
29from neutron_tempest_plugin.common import constants
30from neutron_tempest_plugin.common import utils
31from neutron_tempest_plugin import config
32from neutron_tempest_plugin import exceptions
Daniel Mellado3c0aeab2016-01-29 11:30:25 +000033
34CONF = config.CONF
35
Lajos Katona2f904652018-08-23 14:04:56 +020036LOG = log.getLogger(__name__)
37
Daniel Mellado3c0aeab2016-01-29 11:30:25 +000038
39class BaseNetworkTest(test.BaseTestCase):
40
Brian Haleyae328b92018-10-09 19:51:54 -040041 """Base class for Neutron tests that use the Tempest Neutron REST client
Daniel Mellado3c0aeab2016-01-29 11:30:25 +000042
43 Per the Neutron API Guide, API v1.x was removed from the source code tree
44 (docs.openstack.org/api/openstack-network/2.0/content/Overview-d1e71.html)
45 Therefore, v2.x of the Neutron API is assumed. It is also assumed that the
46 following options are defined in the [network] section of etc/tempest.conf:
47
48 project_network_cidr with a block of cidr's from which smaller blocks
49 can be allocated for tenant networks
50
51 project_network_mask_bits with the mask bits to be used to partition
52 the block defined by tenant-network_cidr
53
54 Finally, it is assumed that the following option is defined in the
55 [service_available] section of etc/tempest.conf
56
57 neutron as True
58 """
59
60 force_tenant_isolation = False
61 credentials = ['primary']
62
63 # Default to ipv4.
Federico Ressi0ddc93b2018-04-09 12:01:48 +020064 _ip_version = const.IP_VERSION_4
Daniel Mellado3c0aeab2016-01-29 11:30:25 +000065
Federico Ressi61b564e2018-07-06 08:10:31 +020066 # Derive from BaseAdminNetworkTest class to have this initialized
67 admin_client = None
68
Federico Ressia69dcd52018-07-06 09:45:34 +020069 external_network_id = CONF.network.public_network_id
70
Maor Blaustein50ca7d72024-02-08 13:35:15 +020071 __is_driver_ovn = None
72
73 @classmethod
74 def _is_driver_ovn(cls):
75 ovn_agents = cls.os_admin.network_client.list_agents(
76 binary='ovn-controller')['agents']
77 return len(ovn_agents) > 0
78
79 @property
80 def is_driver_ovn(self):
81 if self.__is_driver_ovn is None:
82 if hasattr(self, 'os_admin'):
83 self.__is_driver_ovn = self._is_driver_ovn()
84 return self.__is_driver_ovn
85
Daniel Mellado3c0aeab2016-01-29 11:30:25 +000086 @classmethod
87 def get_client_manager(cls, credential_type=None, roles=None,
88 force_new=None):
Genadi Chereshnyacc395c02016-07-25 12:17:37 +030089 manager = super(BaseNetworkTest, cls).get_client_manager(
90 credential_type=credential_type,
91 roles=roles,
92 force_new=force_new
93 )
Daniel Mellado3c0aeab2016-01-29 11:30:25 +000094 # Neutron uses a different clients manager than the one in the Tempest
Jens Harbott860b46a2017-11-15 21:23:15 +000095 # save the original in case mixed tests need it
96 if credential_type == 'primary':
97 cls.os_tempest = manager
Daniel Mellado3c0aeab2016-01-29 11:30:25 +000098 return clients.Manager(manager.credentials)
99
100 @classmethod
101 def skip_checks(cls):
102 super(BaseNetworkTest, cls).skip_checks()
103 if not CONF.service_available.neutron:
104 raise cls.skipException("Neutron support is required")
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200105 if (cls._ip_version == const.IP_VERSION_6 and
106 not CONF.network_feature_enabled.ipv6):
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000107 raise cls.skipException("IPv6 Tests are disabled.")
Jakub Libosvar1982aa12017-05-30 11:15:33 +0000108 for req_ext in getattr(cls, 'required_extensions', []):
Chandan Kumarc125fd12017-11-15 19:41:01 +0530109 if not tutils.is_extension_enabled(req_ext, 'network'):
Jakub Libosvar1982aa12017-05-30 11:15:33 +0000110 msg = "%s extension not enabled." % req_ext
111 raise cls.skipException(msg)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000112
113 @classmethod
114 def setup_credentials(cls):
115 # Create no network resources for these test.
116 cls.set_network_resources()
117 super(BaseNetworkTest, cls).setup_credentials()
118
119 @classmethod
120 def setup_clients(cls):
121 super(BaseNetworkTest, cls).setup_clients()
fumihiko kakumaa216fc12017-07-14 10:43:29 +0900122 cls.client = cls.os_primary.network_client
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000123
Vasyl Saienko19fa0b52021-10-22 14:34:41 +0300124 # NOTE(vsaienko): when using static accounts we need
125 # to fill *_id information like project_id, user_id
126 # by authenticating in keystone
127 cls.client.auth_provider.get_token()
128
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000129 @classmethod
130 def resource_setup(cls):
131 super(BaseNetworkTest, cls).resource_setup()
132
133 cls.networks = []
Miguel Lavalle124378b2016-09-21 16:41:47 -0500134 cls.admin_networks = []
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000135 cls.subnets = []
Kevin Bentonba3651c2017-09-01 17:13:01 -0700136 cls.admin_subnets = []
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000137 cls.ports = []
138 cls.routers = []
139 cls.floating_ips = []
Slawek Kaplonski003fcae2019-05-26 22:38:35 +0200140 cls.port_forwardings = []
Nurmatov Mamatisa3f2bbb52021-10-20 14:33:54 +0300141 cls.local_ips = []
142 cls.local_ip_associations = []
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000143 cls.metering_labels = []
144 cls.service_profiles = []
145 cls.flavors = []
146 cls.metering_label_rules = []
147 cls.qos_rules = []
148 cls.qos_policies = []
149 cls.ethertype = "IPv" + str(cls._ip_version)
Miguel Lavalleb1c7a3d2021-01-31 19:05:22 -0600150 cls.address_groups = []
151 cls.admin_address_groups = []
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000152 cls.address_scopes = []
153 cls.admin_address_scopes = []
154 cls.subnetpools = []
155 cls.admin_subnetpools = []
Itzik Brownbac51dc2016-10-31 12:25:04 +0000156 cls.security_groups = []
Dongcan Ye2de722e2018-07-04 11:01:37 +0000157 cls.admin_security_groups = []
Chandan Kumarc125fd12017-11-15 19:41:01 +0530158 cls.projects = []
Nguyen Phuong An67993fc2017-11-24 11:30:25 +0700159 cls.log_objects = []
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200160 cls.reserved_subnet_cidrs = set()
Federico Ressiab286e42018-06-19 09:52:10 +0200161 cls.keypairs = []
Federico Ressi82e83e32018-07-03 14:19:55 +0200162 cls.trunks = []
Kailun Qineaaf9782018-12-20 04:45:01 +0800163 cls.network_segment_ranges = []
Harald Jensåsc9782fa2019-06-03 22:35:41 +0200164 cls.conntrack_helpers = []
yangjianfeng2936a292022-02-04 11:22:11 +0800165 cls.ndp_proxies = []
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000166
167 @classmethod
yangjianfeng23e40c22020-11-22 08:42:18 +0000168 def reserve_external_subnet_cidrs(cls):
169 client = cls.os_admin.network_client
170 ext_nets = client.list_networks(
171 **{"router:external": True})['networks']
172 for ext_net in ext_nets:
173 ext_subnets = client.list_subnets(
174 network_id=ext_net['id'])['subnets']
175 for ext_subnet in ext_subnets:
176 cls.reserve_subnet_cidr(ext_subnet['cidr'])
177
178 @classmethod
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000179 def resource_cleanup(cls):
180 if CONF.service_available.neutron:
Federico Ressi82e83e32018-07-03 14:19:55 +0200181 # Clean up trunks
182 for trunk in cls.trunks:
183 cls._try_delete_resource(cls.delete_trunk, trunk)
184
yangjianfeng2936a292022-02-04 11:22:11 +0800185 # Clean up ndp proxy
186 for ndp_proxy in cls.ndp_proxies:
187 cls._try_delete_resource(cls.delete_ndp_proxy, ndp_proxy)
188
Slawek Kaplonski003fcae2019-05-26 22:38:35 +0200189 # Clean up port forwardings
190 for pf in cls.port_forwardings:
191 cls._try_delete_resource(cls.delete_port_forwarding, pf)
192
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000193 # Clean up floating IPs
194 for floating_ip in cls.floating_ips:
Federico Ressia69dcd52018-07-06 09:45:34 +0200195 cls._try_delete_resource(cls.delete_floatingip, floating_ip)
196
Nurmatov Mamatisa3f2bbb52021-10-20 14:33:54 +0300197 # Clean up Local IP Associations
198 for association in cls.local_ip_associations:
199 cls._try_delete_resource(cls.delete_local_ip_association,
200 association)
201 # Clean up Local IPs
202 for local_ip in cls.local_ips:
203 cls._try_delete_resource(cls.delete_local_ip,
204 local_ip)
205
Harald Jensåsc9782fa2019-06-03 22:35:41 +0200206 # Clean up conntrack helpers
207 for cth in cls.conntrack_helpers:
208 cls._try_delete_resource(cls.delete_conntrack_helper, cth)
209
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000210 # Clean up routers
211 for router in cls.routers:
212 cls._try_delete_resource(cls.delete_router,
213 router)
214 # Clean up metering label rules
215 for metering_label_rule in cls.metering_label_rules:
216 cls._try_delete_resource(
217 cls.admin_client.delete_metering_label_rule,
218 metering_label_rule['id'])
219 # Clean up metering labels
220 for metering_label in cls.metering_labels:
221 cls._try_delete_resource(
222 cls.admin_client.delete_metering_label,
223 metering_label['id'])
224 # Clean up flavors
225 for flavor in cls.flavors:
226 cls._try_delete_resource(
227 cls.admin_client.delete_flavor,
228 flavor['id'])
229 # Clean up service profiles
230 for service_profile in cls.service_profiles:
231 cls._try_delete_resource(
232 cls.admin_client.delete_service_profile,
233 service_profile['id'])
234 # Clean up ports
235 for port in cls.ports:
236 cls._try_delete_resource(cls.client.delete_port,
237 port['id'])
238 # Clean up subnets
239 for subnet in cls.subnets:
240 cls._try_delete_resource(cls.client.delete_subnet,
241 subnet['id'])
Kevin Bentonba3651c2017-09-01 17:13:01 -0700242 # Clean up admin subnets
243 for subnet in cls.admin_subnets:
244 cls._try_delete_resource(cls.admin_client.delete_subnet,
245 subnet['id'])
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000246 # Clean up networks
247 for network in cls.networks:
Federico Ressi61b564e2018-07-06 08:10:31 +0200248 cls._try_delete_resource(cls.delete_network, network)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000249
Miguel Lavalle124378b2016-09-21 16:41:47 -0500250 # Clean up admin networks
251 for network in cls.admin_networks:
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000252 cls._try_delete_resource(cls.admin_client.delete_network,
253 network['id'])
254
Itzik Brownbac51dc2016-10-31 12:25:04 +0000255 # Clean up security groups
Federico Ressi4c590d72018-10-10 14:01:08 +0200256 for security_group in cls.security_groups:
257 cls._try_delete_resource(cls.delete_security_group,
258 security_group)
Itzik Brownbac51dc2016-10-31 12:25:04 +0000259
Dongcan Ye2de722e2018-07-04 11:01:37 +0000260 # Clean up admin security groups
Federico Ressi4c590d72018-10-10 14:01:08 +0200261 for security_group in cls.admin_security_groups:
262 cls._try_delete_resource(cls.delete_security_group,
263 security_group,
264 client=cls.admin_client)
Dongcan Ye2de722e2018-07-04 11:01:37 +0000265
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000266 for subnetpool in cls.subnetpools:
267 cls._try_delete_resource(cls.client.delete_subnetpool,
268 subnetpool['id'])
269
270 for subnetpool in cls.admin_subnetpools:
271 cls._try_delete_resource(cls.admin_client.delete_subnetpool,
272 subnetpool['id'])
273
274 for address_scope in cls.address_scopes:
275 cls._try_delete_resource(cls.client.delete_address_scope,
276 address_scope['id'])
277
278 for address_scope in cls.admin_address_scopes:
279 cls._try_delete_resource(
280 cls.admin_client.delete_address_scope,
281 address_scope['id'])
282
Chandan Kumarc125fd12017-11-15 19:41:01 +0530283 for project in cls.projects:
284 cls._try_delete_resource(
285 cls.identity_admin_client.delete_project,
286 project['id'])
287
Sławek Kapłońskie100c4d2017-08-23 21:18:34 +0000288 # Clean up QoS rules
289 for qos_rule in cls.qos_rules:
290 cls._try_delete_resource(cls.admin_client.delete_qos_rule,
291 qos_rule['id'])
292 # Clean up QoS policies
293 # as all networks and ports are already removed, QoS policies
294 # shouldn't be "in use"
295 for qos_policy in cls.qos_policies:
296 cls._try_delete_resource(cls.admin_client.delete_qos_policy,
297 qos_policy['id'])
298
Nguyen Phuong An67993fc2017-11-24 11:30:25 +0700299 # Clean up log_objects
300 for log_object in cls.log_objects:
301 cls._try_delete_resource(cls.admin_client.delete_log,
302 log_object['id'])
303
Federico Ressiab286e42018-06-19 09:52:10 +0200304 for keypair in cls.keypairs:
305 cls._try_delete_resource(cls.delete_keypair, keypair)
306
Kailun Qineaaf9782018-12-20 04:45:01 +0800307 # Clean up network_segment_ranges
308 for network_segment_range in cls.network_segment_ranges:
309 cls._try_delete_resource(
310 cls.admin_client.delete_network_segment_range,
311 network_segment_range['id'])
312
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000313 super(BaseNetworkTest, cls).resource_cleanup()
314
315 @classmethod
316 def _try_delete_resource(cls, delete_callable, *args, **kwargs):
317 """Cleanup resources in case of test-failure
318
319 Some resources are explicitly deleted by the test.
320 If the test failed to delete a resource, this method will execute
321 the appropriate delete methods. Otherwise, the method ignores NotFound
322 exceptions thrown for resources that were correctly deleted by the
323 test.
324
325 :param delete_callable: delete method
326 :param args: arguments for delete method
327 :param kwargs: keyword arguments for delete method
328 """
329 try:
330 delete_callable(*args, **kwargs)
331 # if resource is not found, this means it was deleted in the test
332 except lib_exc.NotFound:
333 pass
334
335 @classmethod
Federico Ressi61b564e2018-07-06 08:10:31 +0200336 def create_network(cls, network_name=None, client=None, external=None,
337 shared=None, provider_network_type=None,
338 provider_physical_network=None,
339 provider_segmentation_id=None, **kwargs):
340 """Create a network.
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000341
Federico Ressi61b564e2018-07-06 08:10:31 +0200342 When client is not provider and admin_client is attribute is not None
343 (for example when using BaseAdminNetworkTest base class) and using any
344 of the convenience parameters (external, shared, provider_network_type,
345 provider_physical_network and provider_segmentation_id) it silently
346 uses admin_client. If the network is not shared then it uses the same
347 project_id as regular client.
348
349 :param network_name: Human-readable name of the network
350
351 :param client: client to be used for connecting to network service
352
353 :param external: indicates whether the network has an external routing
354 facility that's not managed by the networking service.
355
356 :param shared: indicates whether this resource is shared across all
357 projects. By default, only administrative users can change this value.
358 If True and admin_client attribute is not None, then the network is
359 created under administrative project.
360
361 :param provider_network_type: the type of physical network that this
362 network should be mapped to. For example, 'flat', 'vlan', 'vxlan', or
363 'gre'. Valid values depend on a networking back-end.
364
365 :param provider_physical_network: the physical network where this
366 network should be implemented. The Networking API v2.0 does not provide
367 a way to list available physical networks. For example, the Open
368 vSwitch plug-in configuration file defines a symbolic name that maps to
369 specific bridges on each compute host.
370
371 :param provider_segmentation_id: The ID of the isolated segment on the
372 physical network. The network_type attribute defines the segmentation
373 model. For example, if the network_type value is 'vlan', this ID is a
374 vlan identifier. If the network_type value is 'gre', this ID is a gre
375 key.
376
377 :param **kwargs: extra parameters to be forwarded to network service
378 """
379
380 name = (network_name or kwargs.pop('name', None) or
381 data_utils.rand_name('test-network-'))
382
383 # translate convenience parameters
384 admin_client_required = False
385 if provider_network_type:
386 admin_client_required = True
387 kwargs['provider:network_type'] = provider_network_type
388 if provider_physical_network:
389 admin_client_required = True
390 kwargs['provider:physical_network'] = provider_physical_network
391 if provider_segmentation_id:
392 admin_client_required = True
393 kwargs['provider:segmentation_id'] = provider_segmentation_id
394 if external is not None:
395 admin_client_required = True
396 kwargs['router:external'] = bool(external)
397 if shared is not None:
398 admin_client_required = True
399 kwargs['shared'] = bool(shared)
400
401 if not client:
402 if admin_client_required and cls.admin_client:
403 # For convenience silently switch to admin client
404 client = cls.admin_client
405 if not shared:
406 # Keep this network visible from current project
407 project_id = (kwargs.get('project_id') or
408 kwargs.get('tenant_id') or
409 cls.client.tenant_id)
410 kwargs.update(project_id=project_id, tenant_id=project_id)
411 else:
412 # Use default client
413 client = cls.client
414
415 network = client.create_network(name=name, **kwargs)['network']
416 network['client'] = client
417 cls.networks.append(network)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000418 return network
419
420 @classmethod
Federico Ressi61b564e2018-07-06 08:10:31 +0200421 def delete_network(cls, network, client=None):
422 client = client or network.get('client') or cls.client
423 client.delete_network(network['id'])
424
425 @classmethod
426 def create_shared_network(cls, network_name=None, **kwargs):
427 return cls.create_network(name=network_name, shared=True, **kwargs)
Miguel Lavalle124378b2016-09-21 16:41:47 -0500428
429 @classmethod
Sławek Kapłońskid98e27d2018-05-07 16:16:28 +0200430 def create_subnet(cls, network, gateway='', cidr=None, mask_bits=None,
Federico Ressi98f20ec2018-05-11 06:09:49 +0200431 ip_version=None, client=None, reserve_cidr=True,
432 **kwargs):
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200433 """Wrapper utility that returns a test subnet.
434
435 Convenient wrapper for client.create_subnet method. It reserves and
436 allocates CIDRs to avoid creating overlapping subnets.
437
438 :param network: network where to create the subnet
439 network['id'] must contain the ID of the network
440
441 :param gateway: gateway IP address
442 It can be a str or a netaddr.IPAddress
443 If gateway is not given, then it will use default address for
444 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 +0200445 if gateway is given as None then no gateway will be assigned
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200446
447 :param cidr: CIDR of the subnet to create
448 It can be either None, a str or a netaddr.IPNetwork instance
449
450 :param mask_bits: CIDR prefix length
451 It can be either None or a numeric value.
452 If cidr parameter is given then mask_bits is used to determinate a
453 sequence of valid CIDR to use as generated.
454 Please see netaddr.IPNetwork.subnet method documentation[1]
455
456 :param ip_version: ip version of generated subnet CIDRs
457 It can be None, IP_VERSION_4 or IP_VERSION_6
458 It has to match given either given CIDR and gateway
459
460 :param ip_version: numeric value (either IP_VERSION_4 or IP_VERSION_6)
461 this value must match CIDR and gateway IP versions if any of them is
462 given
463
464 :param client: client to be used to connect to network service
465
Federico Ressi98f20ec2018-05-11 06:09:49 +0200466 :param reserve_cidr: if True then it reserves assigned CIDR to avoid
467 using the same CIDR for further subnets in the scope of the same
468 test case class
469
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200470 :param **kwargs: optional parameters to be forwarded to wrapped method
471
472 [1] http://netaddr.readthedocs.io/en/latest/tutorial_01.html#supernets-and-subnets # noqa
473 """
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000474
475 # allow tests to use admin client
476 if not client:
477 client = cls.client
478
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200479 if gateway:
480 gateway_ip = netaddr.IPAddress(gateway)
481 if ip_version:
482 if ip_version != gateway_ip.version:
483 raise ValueError(
484 "Gateway IP version doesn't match IP version")
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000485 else:
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200486 ip_version = gateway_ip.version
Sławek Kapłońskid98e27d2018-05-07 16:16:28 +0200487 else:
488 ip_version = ip_version or cls._ip_version
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200489
490 for subnet_cidr in cls.get_subnet_cidrs(
491 ip_version=ip_version, cidr=cidr, mask_bits=mask_bits):
Federico Ressi98f20ec2018-05-11 06:09:49 +0200492 if gateway is not None:
493 kwargs['gateway_ip'] = str(gateway or (subnet_cidr.ip + 1))
Slawek Kaplonski21f53422018-11-02 16:02:09 +0100494 else:
495 kwargs['gateway_ip'] = None
Federico Ressi98f20ec2018-05-11 06:09:49 +0200496 try:
497 body = client.create_subnet(
498 network_id=network['id'],
499 cidr=str(subnet_cidr),
500 ip_version=subnet_cidr.version,
501 **kwargs)
502 break
503 except lib_exc.BadRequest as e:
504 if 'overlaps with another subnet' not in str(e):
505 raise
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000506 else:
507 message = 'Available CIDR for subnet creation could not be found'
508 raise ValueError(message)
509 subnet = body['subnet']
Kevin Bentonba3651c2017-09-01 17:13:01 -0700510 if client is cls.client:
511 cls.subnets.append(subnet)
512 else:
513 cls.admin_subnets.append(subnet)
Federico Ressi98f20ec2018-05-11 06:09:49 +0200514 if reserve_cidr:
515 cls.reserve_subnet_cidr(subnet_cidr)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000516 return subnet
517
518 @classmethod
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200519 def reserve_subnet_cidr(cls, addr, **ipnetwork_kwargs):
520 """Reserve given subnet CIDR making sure it is not used by create_subnet
521
522 :param addr: the CIDR address to be reserved
523 It can be a str or netaddr.IPNetwork instance
524
525 :param **ipnetwork_kwargs: optional netaddr.IPNetwork constructor
526 parameters
527 """
528
529 if not cls.try_reserve_subnet_cidr(addr, **ipnetwork_kwargs):
Vasyl Saienko214943d2023-10-25 12:22:05 +0000530 LOG.info('Subnet CIDR already reserved: {0!r}'.format(
Federico Ressi0ddc93b2018-04-09 12:01:48 +0200531 addr))
532
533 @classmethod
534 def try_reserve_subnet_cidr(cls, addr, **ipnetwork_kwargs):
535 """Reserve given subnet CIDR if it hasn't been reserved before
536
537 :param addr: the CIDR address to be reserved
538 It can be a str or netaddr.IPNetwork instance
539
540 :param **ipnetwork_kwargs: optional netaddr.IPNetwork constructor
541 parameters
542
543 :return: True if it wasn't reserved before, False elsewhere.
544 """
545
546 subnet_cidr = netaddr.IPNetwork(addr, **ipnetwork_kwargs)
547 if subnet_cidr in cls.reserved_subnet_cidrs:
548 return False
549 else:
550 cls.reserved_subnet_cidrs.add(subnet_cidr)
551 return True
552
553 @classmethod
554 def get_subnet_cidrs(
555 cls, cidr=None, mask_bits=None, ip_version=None):
556 """Iterate over a sequence of unused subnet CIDR for IP version
557
558 :param cidr: CIDR of the subnet to create
559 It can be either None, a str or a netaddr.IPNetwork instance
560
561 :param mask_bits: CIDR prefix length
562 It can be either None or a numeric value.
563 If cidr parameter is given then mask_bits is used to determinate a
564 sequence of valid CIDR to use as generated.
565 Please see netaddr.IPNetwork.subnet method documentation[1]
566
567 :param ip_version: ip version of generated subnet CIDRs
568 It can be None, IP_VERSION_4 or IP_VERSION_6
569 It has to match given CIDR if given
570
571 :return: iterator over reserved CIDRs of type netaddr.IPNetwork
572
573 [1] http://netaddr.readthedocs.io/en/latest/tutorial_01.html#supernets-and-subnets # noqa
574 """
575
576 if cidr:
577 # Generate subnet CIDRs starting from given CIDR
578 # checking it is of requested IP version
579 cidr = netaddr.IPNetwork(cidr, version=ip_version)
580 else:
581 # Generate subnet CIDRs starting from configured values
582 ip_version = ip_version or cls._ip_version
583 if ip_version == const.IP_VERSION_4:
584 mask_bits = mask_bits or config.safe_get_config_value(
585 'network', 'project_network_mask_bits')
586 cidr = netaddr.IPNetwork(config.safe_get_config_value(
587 'network', 'project_network_cidr'))
588 elif ip_version == const.IP_VERSION_6:
589 mask_bits = config.safe_get_config_value(
590 'network', 'project_network_v6_mask_bits')
591 cidr = netaddr.IPNetwork(config.safe_get_config_value(
592 'network', 'project_network_v6_cidr'))
593 else:
594 raise ValueError('Invalid IP version: {!r}'.format(ip_version))
595
596 if mask_bits:
597 subnet_cidrs = cidr.subnet(mask_bits)
598 else:
599 subnet_cidrs = iter([cidr])
600
601 for subnet_cidr in subnet_cidrs:
602 if subnet_cidr not in cls.reserved_subnet_cidrs:
603 yield subnet_cidr
604
605 @classmethod
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000606 def create_port(cls, network, **kwargs):
607 """Wrapper utility that returns a test port."""
Edan Davidd75e48e2018-01-03 02:49:52 -0500608 if CONF.network.port_vnic_type and 'binding:vnic_type' not in kwargs:
609 kwargs['binding:vnic_type'] = CONF.network.port_vnic_type
Glenn Van de Water5d9b1402020-09-16 15:14:14 +0200610 if CONF.network.port_profile and 'binding:profile' not in kwargs:
611 kwargs['binding:profile'] = CONF.network.port_profile
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000612 body = cls.client.create_port(network_id=network['id'],
613 **kwargs)
614 port = body['port']
615 cls.ports.append(port)
616 return port
617
618 @classmethod
619 def update_port(cls, port, **kwargs):
620 """Wrapper utility that updates a test port."""
621 body = cls.client.update_port(port['id'],
622 **kwargs)
623 return body['port']
624
625 @classmethod
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300626 def _create_router_with_client(
627 cls, client, router_name=None, admin_state_up=False,
628 external_network_id=None, enable_snat=None, **kwargs
629 ):
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000630 ext_gw_info = {}
631 if external_network_id:
632 ext_gw_info['network_id'] = external_network_id
YAMAMOTO Takashi9bd4f972017-06-20 12:49:30 +0900633 if enable_snat is not None:
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000634 ext_gw_info['enable_snat'] = enable_snat
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300635 body = client.create_router(
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000636 router_name, external_gateway_info=ext_gw_info,
637 admin_state_up=admin_state_up, **kwargs)
638 router = body['router']
639 cls.routers.append(router)
640 return router
641
642 @classmethod
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300643 def create_router(cls, *args, **kwargs):
644 return cls._create_router_with_client(cls.client, *args, **kwargs)
645
646 @classmethod
647 def create_admin_router(cls, *args, **kwargs):
rajat294495c042017-06-28 15:37:16 +0530648 return cls._create_router_with_client(cls.os_admin.network_client,
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300649 *args, **kwargs)
650
651 @classmethod
Federico Ressia69dcd52018-07-06 09:45:34 +0200652 def create_floatingip(cls, external_network_id=None, port=None,
653 client=None, **kwargs):
654 """Creates a floating IP.
655
656 Create a floating IP and schedule it for later deletion.
657 If a client is passed, then it is used for deleting the IP too.
658
659 :param external_network_id: network ID where to create
660 By default this is 'CONF.network.public_network_id'.
661
662 :param port: port to bind floating IP to
663 This is translated to 'port_id=port['id']'
664 By default it is None.
665
666 :param client: network client to be used for creating and cleaning up
667 the floating IP.
668
669 :param **kwargs: additional creation parameters to be forwarded to
670 networking server.
671 """
672
673 client = client or cls.client
674 external_network_id = (external_network_id or
675 cls.external_network_id)
676
677 if port:
Federico Ressi47f6ae42018-09-24 16:19:14 +0200678 port_id = kwargs.setdefault('port_id', port['id'])
679 if port_id != port['id']:
680 message = "Port ID specified twice: {!s} != {!s}".format(
681 port_id, port['id'])
682 raise ValueError(message)
Federico Ressia69dcd52018-07-06 09:45:34 +0200683
684 fip = client.create_floatingip(external_network_id,
685 **kwargs)['floatingip']
686
687 # save client to be used later in cls.delete_floatingip
688 # for final cleanup
689 fip['client'] = client
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000690 cls.floating_ips.append(fip)
691 return fip
692
693 @classmethod
Federico Ressia69dcd52018-07-06 09:45:34 +0200694 def delete_floatingip(cls, floating_ip, client=None):
695 """Delete floating IP
696
697 :param client: Client to be used
698 If client is not given it will use the client used to create
699 the floating IP, or cls.client if unknown.
700 """
701
702 client = client or floating_ip.get('client') or cls.client
703 client.delete_floatingip(floating_ip['id'])
704
705 @classmethod
Slawek Kaplonski003fcae2019-05-26 22:38:35 +0200706 def create_port_forwarding(cls, fip_id, internal_port_id,
707 internal_port, external_port,
708 internal_ip_address=None, protocol="tcp",
709 client=None):
710 """Creates a port forwarding.
711
712 Create a port forwarding and schedule it for later deletion.
713 If a client is passed, then it is used for deleting the PF too.
714
715 :param fip_id: The ID of the floating IP address.
716
717 :param internal_port_id: The ID of the Neutron port associated to
718 the floating IP port forwarding.
719
720 :param internal_port: The TCP/UDP/other protocol port number of the
721 Neutron port fixed IP address associated to the floating ip
722 port forwarding.
723
724 :param external_port: The TCP/UDP/other protocol port number of
725 the port forwarding floating IP address.
726
727 :param internal_ip_address: The fixed IPv4 address of the Neutron
728 port associated to the floating IP port forwarding.
729
730 :param protocol: The IP protocol used in the floating IP port
731 forwarding.
732
733 :param client: network client to be used for creating and cleaning up
734 the floating IP port forwarding.
735 """
736
737 client = client or cls.client
738
739 pf = client.create_port_forwarding(
740 fip_id, internal_port_id, internal_port, external_port,
741 internal_ip_address, protocol)['port_forwarding']
742
743 # save ID of floating IP associated with port forwarding for final
744 # cleanup
745 pf['floatingip_id'] = fip_id
746
747 # save client to be used later in cls.delete_port_forwarding
748 # for final cleanup
749 pf['client'] = client
750 cls.port_forwardings.append(pf)
751 return pf
752
753 @classmethod
Flavio Fernandesa1952c62020-10-02 06:39:08 -0400754 def update_port_forwarding(cls, fip_id, pf_id, client=None, **kwargs):
755 """Wrapper utility for update_port_forwarding."""
756 client = client or cls.client
757 return client.update_port_forwarding(fip_id, pf_id, **kwargs)
758
759 @classmethod
Slawek Kaplonski003fcae2019-05-26 22:38:35 +0200760 def delete_port_forwarding(cls, pf, client=None):
761 """Delete port forwarding
762
763 :param client: Client to be used
764 If client is not given it will use the client used to create
765 the port forwarding, or cls.client if unknown.
766 """
767
768 client = client or pf.get('client') or cls.client
769 client.delete_port_forwarding(pf['floatingip_id'], pf['id'])
770
Nurmatov Mamatisa3f2bbb52021-10-20 14:33:54 +0300771 def create_local_ip(cls, network_id=None,
772 client=None, **kwargs):
773 """Creates a Local IP.
774
775 Create a Local IP and schedule it for later deletion.
776 If a client is passed, then it is used for deleting the IP too.
777
778 :param network_id: network ID where to create
779 By default this is 'CONF.network.public_network_id'.
780
781 :param client: network client to be used for creating and cleaning up
782 the Local IP.
783
784 :param **kwargs: additional creation parameters to be forwarded to
785 networking server.
786 """
787
788 client = client or cls.client
789 network_id = (network_id or
790 cls.external_network_id)
791
792 local_ip = client.create_local_ip(network_id,
793 **kwargs)['local_ip']
794
795 # save client to be used later in cls.delete_local_ip
796 # for final cleanup
797 local_ip['client'] = client
798 cls.local_ips.append(local_ip)
799 return local_ip
800
801 @classmethod
802 def delete_local_ip(cls, local_ip, client=None):
803 """Delete Local IP
804
805 :param client: Client to be used
806 If client is not given it will use the client used to create
807 the Local IP, or cls.client if unknown.
808 """
809
810 client = client or local_ip.get('client') or cls.client
811 client.delete_local_ip(local_ip['id'])
812
813 @classmethod
814 def create_local_ip_association(cls, local_ip_id, fixed_port_id,
815 fixed_ip_address=None, client=None):
816 """Creates a Local IP association.
817
818 Create a Local IP Association and schedule it for later deletion.
819 If a client is passed, then it is used for deleting the association
820 too.
821
822 :param local_ip_id: The ID of the Local IP.
823
824 :param fixed_port_id: The ID of the Neutron port
825 to be associated with the Local IP
826
827 :param fixed_ip_address: The fixed IPv4 address of the Neutron
828 port to be associated with the Local IP
829
830 :param client: network client to be used for creating and cleaning up
831 the Local IP Association.
832 """
833
834 client = client or cls.client
835
836 association = client.create_local_ip_association(
837 local_ip_id, fixed_port_id,
838 fixed_ip_address)['port_association']
839
840 # save ID of Local IP for final cleanup
841 association['local_ip_id'] = local_ip_id
842
843 # save client to be used later in
844 # cls.delete_local_ip_association for final cleanup
845 association['client'] = client
846 cls.local_ip_associations.append(association)
847 return association
848
849 @classmethod
850 def delete_local_ip_association(cls, association, client=None):
851
852 """Delete Local IP Association
853
854 :param client: Client to be used
855 If client is not given it will use the client used to create
856 the local IP association, or cls.client if unknown.
857 """
858
859 client = client or association.get('client') or cls.client
860 client.delete_local_ip_association(association['local_ip_id'],
861 association['fixed_port_id'])
862
Slawek Kaplonski003fcae2019-05-26 22:38:35 +0200863 @classmethod
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000864 def create_router_interface(cls, router_id, subnet_id):
865 """Wrapper utility that returns a router interface."""
866 interface = cls.client.add_router_interface_with_subnet_id(
867 router_id, subnet_id)
868 return interface
869
870 @classmethod
Bence Romsics46bd3af2019-09-13 10:52:41 +0200871 def add_extra_routes_atomic(cls, *args, **kwargs):
872 return cls.client.add_extra_routes_atomic(*args, **kwargs)
873
874 @classmethod
875 def remove_extra_routes_atomic(cls, *args, **kwargs):
876 return cls.client.remove_extra_routes_atomic(*args, **kwargs)
877
878 @classmethod
Sławek Kapłońskiff294062016-12-04 15:00:54 +0000879 def get_supported_qos_rule_types(cls):
880 body = cls.client.list_qos_rule_types()
881 return [rule_type['type'] for rule_type in body['rule_types']]
882
883 @classmethod
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +0200884 def create_qos_policy(cls, name, description=None, shared=False,
Rodolfo Alonso Hernandeze2d062f2020-01-14 17:11:42 +0000885 project_id=None, is_default=False):
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000886 """Wrapper utility that returns a test QoS policy."""
887 body = cls.admin_client.create_qos_policy(
Rodolfo Alonso Hernandeze2d062f2020-01-14 17:11:42 +0000888 name, description, shared, project_id, is_default)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000889 qos_policy = body['policy']
890 cls.qos_policies.append(qos_policy)
891 return qos_policy
892
893 @classmethod
elajkatdbb0b482021-05-04 17:20:07 +0200894 def create_qos_dscp_marking_rule(cls, policy_id, dscp_mark):
895 """Wrapper utility that creates and returns a QoS dscp rule."""
896 body = cls.admin_client.create_dscp_marking_rule(
897 policy_id, dscp_mark)
898 qos_rule = body['dscp_marking_rule']
899 cls.qos_rules.append(qos_rule)
900 return qos_rule
901
902 @classmethod
Jakub Libosvar83704832017-12-06 16:02:28 +0000903 def delete_router(cls, router, client=None):
904 client = client or cls.client
Aditya Vaja49819a72018-11-26 14:20:10 -0800905 if 'routes' in router:
906 client.remove_router_extra_routes(router['id'])
Jakub Libosvar83704832017-12-06 16:02:28 +0000907 body = client.list_router_interfaces(router['id'])
Chandan Kumarc125fd12017-11-15 19:41:01 +0530908 interfaces = [port for port in body['ports']
909 if port['device_owner'] in const.ROUTER_INTERFACE_OWNERS]
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000910 for i in interfaces:
911 try:
Jakub Libosvar83704832017-12-06 16:02:28 +0000912 client.remove_router_interface_with_subnet_id(
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000913 router['id'], i['fixed_ips'][0]['subnet_id'])
914 except lib_exc.NotFound:
915 pass
Jakub Libosvar83704832017-12-06 16:02:28 +0000916 client.delete_router(router['id'])
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000917
918 @classmethod
919 def create_address_scope(cls, name, is_admin=False, **kwargs):
920 if is_admin:
921 body = cls.admin_client.create_address_scope(name=name, **kwargs)
922 cls.admin_address_scopes.append(body['address_scope'])
923 else:
924 body = cls.client.create_address_scope(name=name, **kwargs)
925 cls.address_scopes.append(body['address_scope'])
926 return body['address_scope']
927
928 @classmethod
Igor Malinovskiyb80f1d02020-03-06 13:39:52 +0200929 def create_subnetpool(cls, name, is_admin=False, client=None, **kwargs):
930 if client is None:
931 client = cls.admin_client if is_admin else cls.client
932
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000933 if is_admin:
Igor Malinovskiyb80f1d02020-03-06 13:39:52 +0200934 body = client.create_subnetpool(name, **kwargs)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000935 cls.admin_subnetpools.append(body['subnetpool'])
936 else:
Igor Malinovskiyb80f1d02020-03-06 13:39:52 +0200937 body = client.create_subnetpool(name, **kwargs)
Daniel Mellado3c0aeab2016-01-29 11:30:25 +0000938 cls.subnetpools.append(body['subnetpool'])
939 return body['subnetpool']
940
Chandan Kumarc125fd12017-11-15 19:41:01 +0530941 @classmethod
Miguel Lavalleb1c7a3d2021-01-31 19:05:22 -0600942 def create_address_group(cls, name, is_admin=False, **kwargs):
943 if is_admin:
944 body = cls.admin_client.create_address_group(name=name, **kwargs)
945 cls.admin_address_groups.append(body['address_group'])
946 else:
947 body = cls.client.create_address_group(name=name, **kwargs)
948 cls.address_groups.append(body['address_group'])
949 return body['address_group']
950
951 @classmethod
Chandan Kumarc125fd12017-11-15 19:41:01 +0530952 def create_project(cls, name=None, description=None):
953 test_project = name or data_utils.rand_name('test_project_')
954 test_description = description or data_utils.rand_name('desc_')
955 project = cls.identity_admin_client.create_project(
956 name=test_project,
957 description=test_description)['project']
958 cls.projects.append(project)
Dongcan Ye2de722e2018-07-04 11:01:37 +0000959 # Create a project will create a default security group.
Dongcan Ye2de722e2018-07-04 11:01:37 +0000960 sgs_list = cls.admin_client.list_security_groups(
961 tenant_id=project['id'])['security_groups']
Federico Ressi4c590d72018-10-10 14:01:08 +0200962 for security_group in sgs_list:
963 # Make sure delete_security_group method will use
964 # the admin client for this group
965 security_group['client'] = cls.admin_client
966 cls.security_groups.append(security_group)
Chandan Kumarc125fd12017-11-15 19:41:01 +0530967 return project
968
969 @classmethod
Federico Ressi4c590d72018-10-10 14:01:08 +0200970 def create_security_group(cls, name=None, project=None, client=None,
971 **kwargs):
972 if project:
973 client = client or cls.admin_client
974 project_id = kwargs.setdefault('project_id', project['id'])
975 tenant_id = kwargs.setdefault('tenant_id', project['id'])
976 if project_id != project['id'] or tenant_id != project['id']:
977 raise ValueError('Project ID specified multiple times')
978 else:
979 client = client or cls.client
980
981 name = name or data_utils.rand_name(cls.__name__)
982 security_group = client.create_security_group(name=name, **kwargs)[
983 'security_group']
984 security_group['client'] = client
985 cls.security_groups.append(security_group)
986 return security_group
987
988 @classmethod
989 def delete_security_group(cls, security_group, client=None):
990 client = client or security_group.get('client') or cls.client
991 client.delete_security_group(security_group['id'])
992
993 @classmethod
994 def create_security_group_rule(cls, security_group=None, project=None,
995 client=None, ip_version=None, **kwargs):
996 if project:
997 client = client or cls.admin_client
998 project_id = kwargs.setdefault('project_id', project['id'])
999 tenant_id = kwargs.setdefault('tenant_id', project['id'])
1000 if project_id != project['id'] or tenant_id != project['id']:
1001 raise ValueError('Project ID specified multiple times')
1002
1003 if 'security_group_id' not in kwargs:
1004 security_group = (security_group or
1005 cls.get_security_group(client=client))
1006
1007 if security_group:
1008 client = client or security_group.get('client')
1009 security_group_id = kwargs.setdefault('security_group_id',
1010 security_group['id'])
1011 if security_group_id != security_group['id']:
1012 raise ValueError('Security group ID specified multiple times.')
1013
1014 ip_version = ip_version or cls._ip_version
1015 default_params = (
1016 constants.DEFAULT_SECURITY_GROUP_RULE_PARAMS[ip_version])
Slawek Kaplonski83979b92022-12-15 14:15:12 +01001017 if (('remote_address_group_id' in kwargs or
1018 'remote_group_id' in kwargs) and
1019 'remote_ip_prefix' in default_params):
Miguel Lavalleb1c7a3d2021-01-31 19:05:22 -06001020 default_params.pop('remote_ip_prefix')
Federico Ressi4c590d72018-10-10 14:01:08 +02001021 for key, value in default_params.items():
1022 kwargs.setdefault(key, value)
1023
1024 client = client or cls.client
1025 return client.create_security_group_rule(**kwargs)[
1026 'security_group_rule']
1027
1028 @classmethod
1029 def get_security_group(cls, name='default', client=None):
1030 client = client or cls.client
1031 security_groups = client.list_security_groups()['security_groups']
1032 for security_group in security_groups:
1033 if security_group['name'] == name:
1034 return security_group
1035 raise ValueError("No such security group named {!r}".format(name))
Chandan Kumarc125fd12017-11-15 19:41:01 +05301036
Federico Ressiab286e42018-06-19 09:52:10 +02001037 @classmethod
1038 def create_keypair(cls, client=None, name=None, **kwargs):
1039 client = client or cls.os_primary.keypairs_client
1040 name = name or data_utils.rand_name('keypair-test')
1041 keypair = client.create_keypair(name=name, **kwargs)['keypair']
1042
1043 # save client for later cleanup
1044 keypair['client'] = client
1045 cls.keypairs.append(keypair)
1046 return keypair
1047
1048 @classmethod
1049 def delete_keypair(cls, keypair, client=None):
1050 client = (client or keypair.get('client') or
1051 cls.os_primary.keypairs_client)
1052 client.delete_keypair(keypair_name=keypair['name'])
1053
Federico Ressi82e83e32018-07-03 14:19:55 +02001054 @classmethod
1055 def create_trunk(cls, port=None, subports=None, client=None, **kwargs):
1056 """Create network trunk
1057
1058 :param port: dictionary containing parent port ID (port['id'])
1059 :param client: client to be used for connecting to networking service
1060 :param **kwargs: extra parameters to be forwarded to network service
1061
1062 :returns: dictionary containing created trunk details
1063 """
1064 client = client or cls.client
1065
1066 if port:
1067 kwargs['port_id'] = port['id']
1068
1069 trunk = client.create_trunk(subports=subports, **kwargs)['trunk']
1070 # Save client reference for later deletion
1071 trunk['client'] = client
1072 cls.trunks.append(trunk)
1073 return trunk
1074
1075 @classmethod
Huifeng Le1c9f40b2018-11-07 01:14:21 +08001076 def delete_trunk(cls, trunk, client=None, detach_parent_port=True):
Federico Ressi82e83e32018-07-03 14:19:55 +02001077 """Delete network trunk
1078
1079 :param trunk: dictionary containing trunk ID (trunk['id'])
1080
1081 :param client: client to be used for connecting to networking service
1082 """
1083 client = client or trunk.get('client') or cls.client
1084 trunk.update(client.show_trunk(trunk['id'])['trunk'])
1085
1086 if not trunk['admin_state_up']:
1087 # Cannot touch trunk before admin_state_up is True
1088 client.update_trunk(trunk['id'], admin_state_up=True)
1089 if trunk['sub_ports']:
1090 # Removes trunk ports before deleting it
1091 cls._try_delete_resource(client.remove_subports, trunk['id'],
1092 trunk['sub_ports'])
1093
1094 # we have to detach the interface from the server before
1095 # the trunk can be deleted.
1096 parent_port = {'id': trunk['port_id']}
1097
1098 def is_parent_port_detached():
1099 parent_port.update(client.show_port(parent_port['id'])['port'])
1100 return not parent_port['device_id']
1101
Huifeng Le1c9f40b2018-11-07 01:14:21 +08001102 if detach_parent_port and not is_parent_port_detached():
Federico Ressi82e83e32018-07-03 14:19:55 +02001103 # this could probably happen when trunk is deleted and parent port
1104 # has been assigned to a VM that is still running. Here we are
1105 # assuming that device_id points to such VM.
1106 cls.os_primary.compute.InterfacesClient().delete_interface(
1107 parent_port['device_id'], parent_port['id'])
1108 utils.wait_until_true(is_parent_port_detached)
1109
1110 client.delete_trunk(trunk['id'])
1111
Harald Jensåsc9782fa2019-06-03 22:35:41 +02001112 @classmethod
1113 def create_conntrack_helper(cls, router_id, helper, protocol, port,
1114 client=None):
1115 """Create a conntrack helper
1116
1117 Create a conntrack helper and schedule it for later deletion. If a
1118 client is passed, then it is used for deleteing the CTH too.
1119
1120 :param router_id: The ID of the Neutron router associated to the
1121 conntrack helper.
1122
1123 :param helper: The conntrack helper module alias
1124
1125 :param protocol: The conntrack helper IP protocol used in the conntrack
1126 helper.
1127
1128 :param port: The conntrack helper IP protocol port number for the
1129 conntrack helper.
1130
1131 :param client: network client to be used for creating and cleaning up
1132 the conntrack helper.
1133 """
1134
1135 client = client or cls.client
1136
1137 cth = client.create_conntrack_helper(router_id, helper, protocol,
1138 port)['conntrack_helper']
1139
1140 # save ID of router associated with conntrack helper for final cleanup
1141 cth['router_id'] = router_id
1142
1143 # save client to be used later in cls.delete_conntrack_helper for final
1144 # cleanup
1145 cth['client'] = client
1146 cls.conntrack_helpers.append(cth)
1147 return cth
1148
1149 @classmethod
1150 def delete_conntrack_helper(cls, cth, client=None):
1151 """Delete conntrack helper
1152
1153 :param client: Client to be used
1154 If client is not given it will use the client used to create the
1155 conntrack helper, or cls.client if unknown.
1156 """
1157
1158 client = client or cth.get('client') or cls.client
1159 client.delete_conntrack_helper(cth['router_id'], cth['id'])
1160
yangjianfeng2936a292022-02-04 11:22:11 +08001161 @classmethod
1162 def create_ndp_proxy(cls, router_id, port_id, client=None, **kwargs):
1163 """Creates a ndp proxy.
1164
1165 Create a ndp proxy and schedule it for later deletion.
1166 If a client is passed, then it is used for deleting the NDP proxy too.
1167
1168 :param router_id: router ID where to create the ndp proxy.
1169
1170 :param port_id: port ID which the ndp proxy associate with
1171
1172 :param client: network client to be used for creating and cleaning up
1173 the ndp proxy.
1174
1175 :param **kwargs: additional creation parameters to be forwarded to
1176 networking server.
1177 """
1178 client = client or cls.client
1179
1180 data = {'router_id': router_id, 'port_id': port_id}
1181 if kwargs:
1182 data.update(kwargs)
1183 ndp_proxy = client.create_ndp_proxy(**data)['ndp_proxy']
1184
1185 # save client to be used later in cls.delete_ndp_proxy
1186 # for final cleanup
1187 ndp_proxy['client'] = client
1188 cls.ndp_proxies.append(ndp_proxy)
1189 return ndp_proxy
1190
1191 @classmethod
1192 def delete_ndp_proxy(cls, ndp_proxy, client=None):
1193 """Delete ndp proxy
1194
1195 :param client: Client to be used
1196 If client is not given it will use the client used to create
1197 the ndp proxy, or cls.client if unknown.
1198 """
1199 client = client or ndp_proxy.get('client') or cls.client
1200 client.delete_ndp_proxy(ndp_proxy['id'])
1201
Daniel Mellado3c0aeab2016-01-29 11:30:25 +00001202
1203class BaseAdminNetworkTest(BaseNetworkTest):
1204
1205 credentials = ['primary', 'admin']
1206
1207 @classmethod
1208 def setup_clients(cls):
1209 super(BaseAdminNetworkTest, cls).setup_clients()
fumihiko kakumaa216fc12017-07-14 10:43:29 +09001210 cls.admin_client = cls.os_admin.network_client
Michael Polenchuk60d02912022-09-05 13:29:57 +04001211 cls.admin_client.auth_provider.get_token()
Jakub Libosvarf5758012017-08-15 13:45:30 +00001212 cls.identity_admin_client = cls.os_admin.projects_client
Daniel Mellado3c0aeab2016-01-29 11:30:25 +00001213
1214 @classmethod
1215 def create_metering_label(cls, name, description):
1216 """Wrapper utility that returns a test metering label."""
1217 body = cls.admin_client.create_metering_label(
1218 description=description,
1219 name=data_utils.rand_name("metering-label"))
1220 metering_label = body['metering_label']
1221 cls.metering_labels.append(metering_label)
1222 return metering_label
1223
1224 @classmethod
1225 def create_metering_label_rule(cls, remote_ip_prefix, direction,
1226 metering_label_id):
1227 """Wrapper utility that returns a test metering label rule."""
1228 body = cls.admin_client.create_metering_label_rule(
1229 remote_ip_prefix=remote_ip_prefix, direction=direction,
1230 metering_label_id=metering_label_id)
1231 metering_label_rule = body['metering_label_rule']
1232 cls.metering_label_rules.append(metering_label_rule)
1233 return metering_label_rule
1234
1235 @classmethod
Kailun Qineaaf9782018-12-20 04:45:01 +08001236 def create_network_segment_range(cls, name, shared,
1237 project_id, network_type,
1238 physical_network, minimum,
1239 maximum):
1240 """Wrapper utility that returns a test network segment range."""
1241 network_segment_range_args = {'name': name,
1242 'shared': shared,
1243 'project_id': project_id,
1244 'network_type': network_type,
1245 'physical_network': physical_network,
1246 'minimum': minimum,
1247 'maximum': maximum}
1248 body = cls.admin_client.create_network_segment_range(
1249 **network_segment_range_args)
1250 network_segment_range = body['network_segment_range']
1251 cls.network_segment_ranges.append(network_segment_range)
1252 return network_segment_range
1253
1254 @classmethod
Daniel Mellado3c0aeab2016-01-29 11:30:25 +00001255 def create_flavor(cls, name, description, service_type):
1256 """Wrapper utility that returns a test flavor."""
1257 body = cls.admin_client.create_flavor(
1258 description=description, service_type=service_type,
1259 name=name)
1260 flavor = body['flavor']
1261 cls.flavors.append(flavor)
1262 return flavor
1263
1264 @classmethod
1265 def create_service_profile(cls, description, metainfo, driver):
1266 """Wrapper utility that returns a test service profile."""
1267 body = cls.admin_client.create_service_profile(
1268 driver=driver, metainfo=metainfo, description=description)
1269 service_profile = body['service_profile']
1270 cls.service_profiles.append(service_profile)
1271 return service_profile
1272
1273 @classmethod
Nguyen Phuong An67993fc2017-11-24 11:30:25 +07001274 def create_log(cls, name, description=None,
1275 resource_type='security_group', resource_id=None,
1276 target_id=None, event='ALL', enabled=True):
1277 """Wrapper utility that returns a test log object."""
1278 log_args = {'name': name,
Nguyen Phuong An67993fc2017-11-24 11:30:25 +07001279 'resource_type': resource_type,
1280 'resource_id': resource_id,
1281 'target_id': target_id,
1282 'event': event,
1283 'enabled': enabled}
Slawek Kaplonskid9fe3022021-08-11 15:25:16 +02001284 if description:
1285 log_args['description'] = description
Nguyen Phuong An67993fc2017-11-24 11:30:25 +07001286 body = cls.admin_client.create_log(**log_args)
1287 log_object = body['log']
1288 cls.log_objects.append(log_object)
1289 return log_object
1290
1291 @classmethod
Daniel Mellado3c0aeab2016-01-29 11:30:25 +00001292 def get_unused_ip(cls, net_id, ip_version=None):
Gary Kotton011345f2016-06-15 08:04:31 -07001293 """Get an unused ip address in a allocation pool of net"""
Daniel Mellado3c0aeab2016-01-29 11:30:25 +00001294 body = cls.admin_client.list_ports(network_id=net_id)
1295 ports = body['ports']
1296 used_ips = []
1297 for port in ports:
1298 used_ips.extend(
1299 [fixed_ip['ip_address'] for fixed_ip in port['fixed_ips']])
1300 body = cls.admin_client.list_subnets(network_id=net_id)
1301 subnets = body['subnets']
1302
1303 for subnet in subnets:
1304 if ip_version and subnet['ip_version'] != ip_version:
1305 continue
1306 cidr = subnet['cidr']
1307 allocation_pools = subnet['allocation_pools']
1308 iterators = []
1309 if allocation_pools:
1310 for allocation_pool in allocation_pools:
1311 iterators.append(netaddr.iter_iprange(
1312 allocation_pool['start'], allocation_pool['end']))
1313 else:
1314 net = netaddr.IPNetwork(cidr)
1315
1316 def _iterip():
1317 for ip in net:
1318 if ip not in (net.network, net.broadcast):
1319 yield ip
1320 iterators.append(iter(_iterip()))
1321
1322 for iterator in iterators:
1323 for ip in iterator:
1324 if str(ip) not in used_ips:
1325 return str(ip)
1326
1327 message = (
1328 "net(%s) has no usable IP address in allocation pools" % net_id)
1329 raise exceptions.InvalidConfiguration(message)
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001330
Lajos Katona2f904652018-08-23 14:04:56 +02001331 @classmethod
1332 def create_provider_network(cls, physnet_name, start_segmentation_id,
1333 max_attempts=30):
1334 segmentation_id = start_segmentation_id
Lajos Katona7eb67252019-01-14 12:55:35 +01001335 for attempts in range(max_attempts):
Lajos Katona2f904652018-08-23 14:04:56 +02001336 try:
Lajos Katona7eb67252019-01-14 12:55:35 +01001337 return cls.create_network(
Lajos Katona2f904652018-08-23 14:04:56 +02001338 name=data_utils.rand_name('test_net'),
1339 shared=True,
1340 provider_network_type='vlan',
1341 provider_physical_network=physnet_name,
1342 provider_segmentation_id=segmentation_id)
Lajos Katona2f904652018-08-23 14:04:56 +02001343 except lib_exc.Conflict:
Lajos Katona2f904652018-08-23 14:04:56 +02001344 segmentation_id += 1
1345 if segmentation_id > 4095:
1346 raise lib_exc.TempestException(
1347 "No free segmentation id was found for provider "
1348 "network creation!")
1349 time.sleep(CONF.network.build_interval)
Lajos Katona7eb67252019-01-14 12:55:35 +01001350 LOG.exception("Failed to create provider network after "
1351 "%d attempts", max_attempts)
1352 raise lib_exc.TimeoutException
Lajos Katona2f904652018-08-23 14:04:56 +02001353
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001354
Sławek Kapłońskiff294062016-12-04 15:00:54 +00001355def require_qos_rule_type(rule_type):
1356 def decorator(f):
1357 @functools.wraps(f)
1358 def wrapper(self, *func_args, **func_kwargs):
1359 if rule_type not in self.get_supported_qos_rule_types():
1360 raise self.skipException(
1361 "%s rule type is required." % rule_type)
1362 return f(self, *func_args, **func_kwargs)
1363 return wrapper
1364 return decorator
1365
1366
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001367def _require_sorting(f):
1368 @functools.wraps(f)
1369 def inner(self, *args, **kwargs):
Chandan Kumarc125fd12017-11-15 19:41:01 +05301370 if not tutils.is_extension_enabled("sorting", "network"):
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001371 self.skipTest('Sorting feature is required')
1372 return f(self, *args, **kwargs)
1373 return inner
1374
1375
1376def _require_pagination(f):
1377 @functools.wraps(f)
1378 def inner(self, *args, **kwargs):
Chandan Kumarc125fd12017-11-15 19:41:01 +05301379 if not tutils.is_extension_enabled("pagination", "network"):
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001380 self.skipTest('Pagination feature is required')
1381 return f(self, *args, **kwargs)
1382 return inner
1383
1384
1385class BaseSearchCriteriaTest(BaseNetworkTest):
1386
1387 # This should be defined by subclasses to reflect resource name to test
1388 resource = None
1389
Armando Migliaccio57581c62016-07-01 10:13:19 -07001390 field = 'name'
1391
Ihar Hrachyshkaa8fe5a12016-05-24 14:50:58 +02001392 # NOTE(ihrachys): some names, like those starting with an underscore (_)
1393 # are sorted differently depending on whether the plugin implements native
1394 # sorting support, or not. So we avoid any such cases here, sticking to
1395 # alphanumeric. Also test a case when there are multiple resources with the
1396 # same name
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001397 resource_names = ('test1', 'abc1', 'test10', '123test') + ('test1',)
1398
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001399 force_tenant_isolation = True
1400
Ihar Hrachyshkaa8fe5a12016-05-24 14:50:58 +02001401 list_kwargs = {}
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001402
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001403 list_as_admin = False
1404
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001405 def assertSameOrder(self, original, actual):
1406 # gracefully handle iterators passed
1407 original = list(original)
1408 actual = list(actual)
1409 self.assertEqual(len(original), len(actual))
1410 for expected, res in zip(original, actual):
Armando Migliaccio57581c62016-07-01 10:13:19 -07001411 self.assertEqual(expected[self.field], res[self.field])
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001412
1413 @utils.classproperty
1414 def plural_name(self):
1415 return '%ss' % self.resource
1416
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001417 @property
1418 def list_client(self):
1419 return self.admin_client if self.list_as_admin else self.client
1420
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001421 def list_method(self, *args, **kwargs):
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001422 method = getattr(self.list_client, 'list_%s' % self.plural_name)
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001423 kwargs.update(self.list_kwargs)
1424 return method(*args, **kwargs)
1425
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001426 def get_bare_url(self, url):
1427 base_url = self.client.base_url
zheng.yong74e760a2019-05-22 14:16:14 +08001428 base_url_normalized = utils.normalize_url(base_url)
1429 url_normalized = utils.normalize_url(url)
1430 self.assertTrue(url_normalized.startswith(base_url_normalized))
1431 return url_normalized[len(base_url_normalized):]
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001432
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001433 @classmethod
1434 def _extract_resources(cls, body):
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001435 return body[cls.plural_name]
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001436
1437 def _test_list_sorts(self, direction):
1438 sort_args = {
1439 'sort_dir': direction,
Armando Migliaccio57581c62016-07-01 10:13:19 -07001440 'sort_key': self.field
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001441 }
1442 body = self.list_method(**sort_args)
1443 resources = self._extract_resources(body)
1444 self.assertNotEmpty(
1445 resources, "%s list returned is empty" % self.resource)
Armando Migliaccio57581c62016-07-01 10:13:19 -07001446 retrieved_names = [res[self.field] for res in resources]
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001447 expected = sorted(retrieved_names)
1448 if direction == constants.SORT_DIRECTION_DESC:
1449 expected = list(reversed(expected))
1450 self.assertEqual(expected, retrieved_names)
1451
1452 @_require_sorting
1453 def _test_list_sorts_asc(self):
1454 self._test_list_sorts(constants.SORT_DIRECTION_ASC)
1455
1456 @_require_sorting
1457 def _test_list_sorts_desc(self):
1458 self._test_list_sorts(constants.SORT_DIRECTION_DESC)
1459
1460 @_require_pagination
1461 def _test_list_pagination(self):
1462 for limit in range(1, len(self.resource_names) + 1):
1463 pagination_args = {
1464 'limit': limit,
1465 }
1466 body = self.list_method(**pagination_args)
1467 resources = self._extract_resources(body)
1468 self.assertEqual(limit, len(resources))
1469
1470 @_require_pagination
1471 def _test_list_no_pagination_limit_0(self):
1472 pagination_args = {
1473 'limit': 0,
1474 }
1475 body = self.list_method(**pagination_args)
1476 resources = self._extract_resources(body)
Béla Vancsicsf1806182016-08-23 07:36:18 +02001477 self.assertGreaterEqual(len(resources), len(self.resource_names))
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001478
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001479 def _test_list_pagination_iteratively(self, lister):
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001480 # first, collect all resources for later comparison
1481 sort_args = {
1482 'sort_dir': constants.SORT_DIRECTION_ASC,
Armando Migliaccio57581c62016-07-01 10:13:19 -07001483 'sort_key': self.field
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001484 }
1485 body = self.list_method(**sort_args)
1486 expected_resources = self._extract_resources(body)
1487 self.assertNotEmpty(expected_resources)
1488
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001489 resources = lister(
1490 len(expected_resources), sort_args
1491 )
1492
1493 # finally, compare that the list retrieved in one go is identical to
1494 # the one containing pagination results
1495 self.assertSameOrder(expected_resources, resources)
1496
1497 def _list_all_with_marker(self, niterations, sort_args):
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001498 # paginate resources one by one, using last fetched resource as a
1499 # marker
1500 resources = []
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001501 for i in range(niterations):
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001502 pagination_args = sort_args.copy()
1503 pagination_args['limit'] = 1
1504 if resources:
1505 pagination_args['marker'] = resources[-1]['id']
1506 body = self.list_method(**pagination_args)
1507 resources_ = self._extract_resources(body)
1508 self.assertEqual(1, len(resources_))
1509 resources.extend(resources_)
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001510 return resources
Ihar Hrachyshka59382252016-04-05 15:54:33 +02001511
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001512 @_require_pagination
1513 @_require_sorting
1514 def _test_list_pagination_with_marker(self):
1515 self._test_list_pagination_iteratively(self._list_all_with_marker)
1516
1517 def _list_all_with_hrefs(self, niterations, sort_args):
1518 # paginate resources one by one, using next href links
1519 resources = []
1520 prev_links = {}
1521
1522 for i in range(niterations):
1523 if prev_links:
1524 uri = self.get_bare_url(prev_links['next'])
1525 else:
Ihar Hrachyshka7f79fe62016-06-07 21:23:44 +02001526 sort_args.update(self.list_kwargs)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001527 uri = self.list_client.build_uri(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001528 self.plural_name, limit=1, **sort_args)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001529 prev_links, body = self.list_client.get_uri_with_links(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001530 self.plural_name, uri
1531 )
1532 resources_ = self._extract_resources(body)
1533 self.assertEqual(1, len(resources_))
1534 resources.extend(resources_)
1535
1536 # The last element is empty and does not contain 'next' link
1537 uri = self.get_bare_url(prev_links['next'])
1538 prev_links, body = self.client.get_uri_with_links(
1539 self.plural_name, uri
1540 )
1541 self.assertNotIn('next', prev_links)
1542
1543 # Now walk backwards and compare results
1544 resources2 = []
1545 for i in range(niterations):
1546 uri = self.get_bare_url(prev_links['previous'])
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001547 prev_links, body = self.list_client.get_uri_with_links(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001548 self.plural_name, uri
1549 )
1550 resources_ = self._extract_resources(body)
1551 self.assertEqual(1, len(resources_))
1552 resources2.extend(resources_)
1553
1554 self.assertSameOrder(resources, reversed(resources2))
1555
1556 return resources
1557
1558 @_require_pagination
1559 @_require_sorting
1560 def _test_list_pagination_with_href_links(self):
1561 self._test_list_pagination_iteratively(self._list_all_with_hrefs)
1562
1563 @_require_pagination
1564 @_require_sorting
1565 def _test_list_pagination_page_reverse_with_href_links(
1566 self, direction=constants.SORT_DIRECTION_ASC):
1567 pagination_args = {
1568 'sort_dir': direction,
Armando Migliaccio57581c62016-07-01 10:13:19 -07001569 'sort_key': self.field,
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001570 }
1571 body = self.list_method(**pagination_args)
1572 expected_resources = self._extract_resources(body)
1573
1574 page_size = 2
1575 pagination_args['limit'] = page_size
1576
1577 prev_links = {}
1578 resources = []
1579 num_resources = len(expected_resources)
1580 niterations = int(math.ceil(float(num_resources) / page_size))
1581 for i in range(niterations):
1582 if prev_links:
1583 uri = self.get_bare_url(prev_links['previous'])
1584 else:
Ihar Hrachyshka7f79fe62016-06-07 21:23:44 +02001585 pagination_args.update(self.list_kwargs)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001586 uri = self.list_client.build_uri(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001587 self.plural_name, page_reverse=True, **pagination_args)
Ihar Hrachyshkab7940d92016-06-10 13:44:22 +02001588 prev_links, body = self.list_client.get_uri_with_links(
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001589 self.plural_name, uri
1590 )
1591 resources_ = self._extract_resources(body)
Béla Vancsicsf1806182016-08-23 07:36:18 +02001592 self.assertGreaterEqual(page_size, len(resources_))
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001593 resources.extend(reversed(resources_))
1594
1595 self.assertSameOrder(expected_resources, reversed(resources))
1596
1597 @_require_pagination
1598 @_require_sorting
1599 def _test_list_pagination_page_reverse_asc(self):
1600 self._test_list_pagination_page_reverse(
1601 direction=constants.SORT_DIRECTION_ASC)
1602
1603 @_require_pagination
1604 @_require_sorting
1605 def _test_list_pagination_page_reverse_desc(self):
1606 self._test_list_pagination_page_reverse(
1607 direction=constants.SORT_DIRECTION_DESC)
1608
1609 def _test_list_pagination_page_reverse(self, direction):
1610 pagination_args = {
1611 'sort_dir': direction,
Armando Migliaccio57581c62016-07-01 10:13:19 -07001612 'sort_key': self.field,
Ihar Hrachyshkaaeb03a02016-05-18 20:03:18 +02001613 'limit': 3,
1614 }
1615 body = self.list_method(**pagination_args)
1616 expected_resources = self._extract_resources(body)
1617
1618 pagination_args['limit'] -= 1
1619 pagination_args['marker'] = expected_resources[-1]['id']
1620 pagination_args['page_reverse'] = True
1621 body = self.list_method(**pagination_args)
1622
1623 self.assertSameOrder(
1624 # the last entry is not included in 2nd result when used as a
1625 # marker
1626 expected_resources[:-1],
1627 self._extract_resources(body))
Victor Morales1be97b42016-09-05 08:50:06 -05001628
Hongbin Lu54f55922018-07-12 19:05:39 +00001629 @tutils.requires_ext(extension="filter-validation", service="network")
1630 def _test_list_validation_filters(
1631 self, validation_args, filter_is_valid=True):
1632 if not filter_is_valid:
1633 self.assertRaises(lib_exc.BadRequest, self.list_method,
1634 **validation_args)
1635 else:
1636 body = self.list_method(**validation_args)
1637 resources = self._extract_resources(body)
1638 for resource in resources:
1639 self.assertIn(resource['name'], self.resource_names)