blob: 5ce788295ff7e3e0279b2d4b0c3f1f16a489d86f [file] [log] [blame]
Marc Koderer0abc93b2015-07-15 09:18:35 +02001# Copyright 2014 Mirantis Inc.
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
16import copy
Valeriy Ponomaryov2abf5d72016-06-01 18:30:12 +030017import re
Marc Koderer0abc93b2015-07-15 09:18:35 +020018import traceback
19
Marc Koderer0abc93b2015-07-15 09:18:35 +020020from oslo_log import log
Valeriy Ponomaryov48a2bd72015-11-05 13:22:44 +020021from tempest import config
Goutham Pacha Ravic678e212020-03-20 11:13:47 -070022from tempest.lib.common import cred_client
Ben Swartzlander1c4ff522016-03-02 22:16:23 -050023from tempest.lib.common.utils import data_utils
24from tempest.lib import exceptions
Valeriy Ponomaryov48a2bd72015-11-05 13:22:44 +020025from tempest import test
Marc Koderer0abc93b2015-07-15 09:18:35 +020026
Andrea Frittoli (andreaf)369391a2016-06-27 18:59:13 +010027from manila_tempest_tests import clients
Yogeshbdb88102015-09-29 23:41:02 -040028from manila_tempest_tests.common import constants
lkuchlan540e74a2021-01-19 18:08:25 +020029from manila_tempest_tests.common import waiters
Marc Koderer0abc93b2015-07-15 09:18:35 +020030from manila_tempest_tests import share_exceptions
Valeriy Ponomaryovfcde7712015-12-14 18:06:13 +020031from manila_tempest_tests import utils
Marc Koderer0abc93b2015-07-15 09:18:35 +020032
lkuchlan1d1461d2020-08-04 11:19:11 +030033
Marc Koderer0abc93b2015-07-15 09:18:35 +020034CONF = config.CONF
35LOG = log.getLogger(__name__)
36
Valeriy Ponomaryov2abf5d72016-06-01 18:30:12 +030037# Test tags related to test direction
38TAG_POSITIVE = "positive"
39TAG_NEGATIVE = "negative"
40
41# Test tags related to service involvement
Tom Barron69f96962019-07-29 17:07:03 -040042# Only requires that manila-api service running.
Valeriy Ponomaryov2abf5d72016-06-01 18:30:12 +030043TAG_API = "api"
Tom Barron69f96962019-07-29 17:07:03 -040044# Requires all manila services running, intended to test back-end
45# (manila-share) behavior.
Valeriy Ponomaryov2abf5d72016-06-01 18:30:12 +030046TAG_BACKEND = "backend"
Tom Barron69f96962019-07-29 17:07:03 -040047# Requires all manila services running, intended to test API behavior.
Valeriy Ponomaryov2abf5d72016-06-01 18:30:12 +030048TAG_API_WITH_BACKEND = "api_with_backend"
49
50TAGS_MAPPER = {
51 "p": TAG_POSITIVE,
52 "n": TAG_NEGATIVE,
53 "a": TAG_API,
54 "b": TAG_BACKEND,
55 "ab": TAG_API_WITH_BACKEND,
56}
57TAGS_PATTERN = re.compile(
58 r"(?=.*\[.*\b(%(p)s|%(n)s)\b.*\])(?=.*\[.*\b(%(a)s|%(b)s|%(ab)s)\b.*\])" %
59 TAGS_MAPPER)
60
lkuchlanad511b62022-05-08 09:23:23 +030061LATEST_MICROVERSION = CONF.share.max_api_microversion
62
Valeriy Ponomaryov2abf5d72016-06-01 18:30:12 +030063
64def verify_test_has_appropriate_tags(self):
65 if not TAGS_PATTERN.match(self.id()):
66 msg = (
67 "Required attributes either not set or set improperly. "
68 "Two test attributes are expected:\n"
69 " - one of '%(p)s' or '%(n)s' and \n"
70 " - one of '%(a)s', '%(b)s' or '%(ab)s'."
71 ) % TAGS_MAPPER
72 raise self.failureException(msg)
73
Marc Koderer0abc93b2015-07-15 09:18:35 +020074
75class handle_cleanup_exceptions(object):
76 """Handle exceptions raised with cleanup operations.
77
78 Always suppress errors when exceptions.NotFound or exceptions.Forbidden
79 are raised.
80 Suppress all other exceptions only in case config opt
81 'suppress_errors_in_cleanup' in config group 'share' is True.
82 """
83
84 def __enter__(self):
85 return self
86
87 def __exit__(self, exc_type, exc_value, exc_traceback):
88 if not (isinstance(exc_value,
89 (exceptions.NotFound, exceptions.Forbidden)) or
90 CONF.share.suppress_errors_in_cleanup):
91 return False # Do not suppress error if any
92 if exc_traceback:
93 LOG.error("Suppressed cleanup error in Manila: "
junbolib236c242017-07-18 18:12:37 +080094 "\n%s", traceback.format_exc())
Marc Koderer0abc93b2015-07-15 09:18:35 +020095 return True # Suppress error if any
96
97
Marc Koderer0abc93b2015-07-15 09:18:35 +020098class BaseSharesTest(test.BaseTestCase):
99 """Base test case class for all Manila API tests."""
100
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +0300101 credentials = ('primary', )
Marc Koderer0abc93b2015-07-15 09:18:35 +0200102 force_tenant_isolation = False
Vitaliy Levitksicfebfff2016-12-15 16:16:35 +0200103 protocols = ["nfs", "cifs", "glusterfs", "hdfs", "cephfs", "maprfs"]
Marc Koderer0abc93b2015-07-15 09:18:35 +0200104
105 # Will be cleaned up in resource_cleanup
106 class_resources = []
107
108 # Will be cleaned up in tearDown method
109 method_resources = []
110
Andrea Frittoli (andreaf)369391a2016-06-27 18:59:13 +0100111 # NOTE(andreaf) Override the client manager class to be used, so that
112 # a stable class is used, which includes plugin registered services as well
113 client_manager = clients.Clients
114
Marc Koderer0abc93b2015-07-15 09:18:35 +0200115 @classmethod
Daniel Melladoe5269142017-01-12 12:17:58 +0000116 def skip_checks(cls):
117 super(BaseSharesTest, cls).skip_checks()
118 if not CONF.service_available.manila:
119 raise cls.skipException("Manila support is required")
lkuchlana3b6f7a2020-01-07 10:45:45 +0200120 if not any(p in CONF.share.enable_protocols for p in cls.protocols):
121 skip_msg = "%s tests are disabled" % CONF.share.enable_protocols
122 raise cls.skipException(skip_msg)
Daniel Melladoe5269142017-01-12 12:17:58 +0000123
124 @classmethod
Marc Koderer0abc93b2015-07-15 09:18:35 +0200125 def verify_nonempty(cls, *args):
126 if not all(args):
127 msg = "Missing API credentials in configuration."
128 raise cls.skipException(msg)
129
130 @classmethod
Goutham Pacha Ravi4a0b7322020-06-30 19:42:45 -0700131 def setup_credentials(cls):
132 # This call is used to tell the credential allocator to create
133 # network resources for this test case. NOTE: it must go before the
134 # super call, to override decisions in the base classes.
135 network_resources = {}
136 if (CONF.share.multitenancy_enabled and
137 CONF.share.create_networks_when_multitenancy_enabled):
138 # We're testing a DHSS=True driver, and manila is configured with
139 # NeutronNetworkPlugin (or a derivative) that supports creating
140 # share networks with project neutron networks, so lets ask for
141 # neutron network resources to be created with test credentials
142 network_resources.update({'network': True,
143 'subnet': True,
144 'router': True})
145 cls.set_network_resources(**network_resources)
146 super(BaseSharesTest, cls).setup_credentials()
147
148 @classmethod
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +0300149 def setup_clients(cls):
150 super(BaseSharesTest, cls).setup_clients()
151 os = getattr(cls, 'os_%s' % cls.credentials[0])
Andrea Frittoli (andreaf)369391a2016-06-27 18:59:13 +0100152 # Initialise share clients for test credentials
153 cls.shares_client = os.share_v1.SharesClient()
154 cls.shares_v2_client = os.share_v2.SharesV2Client()
155 # Initialise network clients for test credentials
Goutham Pacha Ravi4a0b7322020-06-30 19:42:45 -0700156 cls.networks_client = None
157 cls.subnets_client = None
Andrea Frittoli (andreaf)369391a2016-06-27 18:59:13 +0100158 if CONF.service_available.neutron:
159 cls.networks_client = os.network.NetworksClient()
160 cls.subnets_client = os.network.SubnetsClient()
Valeriy Ponomaryov4fb305f2016-10-21 13:46:47 +0300161
Goutham Pacha Ravi4a0b7322020-06-30 19:42:45 -0700162 # If DHSS=True, create a share network and set it in the client
163 # for easy access.
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +0300164 if CONF.share.multitenancy_enabled:
Valeriy Ponomaryovc5dae272016-06-10 18:29:24 +0300165 if (not CONF.service_available.neutron and
166 CONF.share.create_networks_when_multitenancy_enabled):
Goutham Pacha Ravi4a0b7322020-06-30 19:42:45 -0700167 raise cls.skipException(
168 "Neutron support is required when "
169 "CONF.share.create_networks_when_multitenancy_enabled "
170 "is set to True")
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +0300171 share_network_id = cls.provide_share_network(
Yaguang Tangcc4b8ac2024-12-16 09:55:40 +0800172 cls.shares_v2_client, cls.networks_client)
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +0300173 cls.shares_client.share_network_id = share_network_id
174 cls.shares_v2_client.share_network_id = share_network_id
175
Marc Koderer0abc93b2015-07-15 09:18:35 +0200176 def setUp(self):
177 super(BaseSharesTest, self).setUp()
Valeriy Ponomaryovdd162cb2016-01-20 19:09:49 +0200178 self.addCleanup(self.clear_resources)
Valeriy Ponomaryov2abf5d72016-06-01 18:30:12 +0300179 verify_test_has_appropriate_tags(self)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200180
181 @classmethod
182 def resource_cleanup(cls):
Marc Koderer0abc93b2015-07-15 09:18:35 +0200183 cls.clear_resources(cls.class_resources)
Sam Wan241029c2016-07-26 03:37:42 -0400184 super(BaseSharesTest, cls).resource_cleanup()
Marc Koderer0abc93b2015-07-15 09:18:35 +0200185
186 @classmethod
debeltrami1753a592020-05-11 18:27:30 +0000187 def provide_and_associate_security_services(
Stephen Finucane878716d2025-09-05 08:18:03 +0100188 cls, shares_client, share_network_id, cleanup_in_class=True
189 ):
debeltrami1753a592020-05-11 18:27:30 +0000190 """Creates a security service and associates to a share network.
191
192 This method creates security services based on the Multiopt
193 defined in tempest configuration named security_service. When this
194 configuration is not provided, the method will return None.
195 After the security service creation, this method also associates
196 the security service to a share network.
197
198 :param shares_client: shares client, which requires the provisioning
199 :param share_network_id: id of the share network to associate the
200 security service
201 :param cleanup_in_class: if the security service and the association
202 will be removed in the method teardown or class teardown
203 :returns: None -- if the security service configuration is not
204 defined
205 """
206
207 ss_configs = CONF.share.security_service
208 if not ss_configs:
209 return
210
211 for ss_config in ss_configs:
212 ss_name = "ss_autogenerated_by_tempest_%s" % (
213 ss_config.get("ss_type"))
214
215 ss_params = {
216 "name": ss_name,
217 "dns_ip": ss_config.get("ss_dns_ip"),
218 "server": ss_config.get("ss_server"),
219 "domain": ss_config.get("ss_domain"),
220 "user": ss_config.get("ss_user"),
221 "password": ss_config.get("ss_password")
222 }
223 ss_type = ss_config.get("ss_type")
224 security_service = cls.create_security_service(
225 ss_type,
226 client=shares_client,
227 cleanup_in_class=cleanup_in_class,
228 **ss_params)
229
230 cls.add_sec_service_to_share_network(
231 shares_client, share_network_id,
232 security_service["id"],
233 cleanup_in_class=cleanup_in_class)
234
235 @classmethod
236 def add_sec_service_to_share_network(
237 cls, client, share_network_id,
238 security_service_id, cleanup_in_class=True):
239 """Associates a security service to a share network.
240
241 This method associates a security service provided by
242 the security service configuration with a specific
243 share network.
244
245 :param share_network_id: the share network id to be
246 associate with a given security service
247 :param security_service_id: the security service id
248 to be associate with a given share network
249 :param cleanup_in_class: if the resources will be
250 dissociate in the method teardown or class teardown
251 """
252
253 client.add_sec_service_to_share_network(
254 share_network_id,
255 security_service_id)
256 resource = {
257 "type": "dissociate_security_service",
258 "id": security_service_id,
259 "extra_params": {
260 "share_network_id": share_network_id
261 },
262 "client": client,
263 }
264
265 if cleanup_in_class:
266 cls.class_resources.insert(0, resource)
267 else:
268 cls.method_resources.insert(0, resource)
269
270 @classmethod
Valeriy Ponomaryov48a2bd72015-11-05 13:22:44 +0200271 def provide_share_network(cls, shares_client, networks_client,
Rodrigo Barbieri58d9de32016-09-06 13:16:47 -0300272 ignore_multitenancy_config=False):
Goutham Pacha Ravi4a0b7322020-06-30 19:42:45 -0700273 """Get or create share network for DHSS=True drivers
Marc Koderer0abc93b2015-07-15 09:18:35 +0200274
Goutham Pacha Ravi4a0b7322020-06-30 19:42:45 -0700275 When testing DHSS=True (multitenancy_enabled) drivers, shares must
276 be requested on share networks.
Marc Koderer0abc93b2015-07-15 09:18:35 +0200277 :returns: str -- share network id for shares_client tenant
Goutham Pacha Ravi4a0b7322020-06-30 19:42:45 -0700278 :returns: None -- if single-tenant driver (DHSS=False) is used
Marc Koderer0abc93b2015-07-15 09:18:35 +0200279 """
280
Rodrigo Barbieri58d9de32016-09-06 13:16:47 -0300281 if (not ignore_multitenancy_config and
282 not CONF.share.multitenancy_enabled):
Goutham Pacha Ravi4a0b7322020-06-30 19:42:45 -0700283 # Assumed usage of a single-tenant driver (DHSS=False)
debeltrami1753a592020-05-11 18:27:30 +0000284 return None
Goutham Pacha Raviaf448262020-06-29 14:24:13 -0700285
Goutham Pacha Ravi4a0b7322020-06-30 19:42:45 -0700286 if shares_client.share_network_id:
287 # Share-network already exists, use it
288 return shares_client.share_network_id
debeltrami1753a592020-05-11 18:27:30 +0000289
Goutham Pacha Ravi4a0b7322020-06-30 19:42:45 -0700290 sn_name = "autogenerated_by_tempest"
291 sn_desc = "This share-network was created by tempest"
Rodrigo Barbieri58d9de32016-09-06 13:16:47 -0300292
Goutham Pacha Ravi4a0b7322020-06-30 19:42:45 -0700293 if not CONF.share.create_networks_when_multitenancy_enabled:
294 # We need a new share network, but don't need to associate
295 # any neutron networks to it - this configuration is used
296 # when manila is configured with "StandaloneNetworkPlugin"
297 # or "NeutronSingleNetworkPlugin" where all tenants share
298 # a single backend network where shares are exported.
299 sn = cls.create_share_network(cleanup_in_class=True,
300 client=shares_client,
301 add_security_services=True,
302 name=sn_name,
303 description=sn_desc)
304 return sn['id']
Marc Koderer0abc93b2015-07-15 09:18:35 +0200305
Goutham Pacha Ravi4a0b7322020-06-30 19:42:45 -0700306 # Retrieve non-public network list owned by the tenant
307 filters = {'project_id': shares_client.tenant_id,
308 'shared': False}
309 tenant_networks = (
310 networks_client.list_networks(**filters).get('networks', [])
311 )
312 tenant_networks_with_subnet = (
313 [n for n in tenant_networks if n['subnets']]
314 )
debeltrami1753a592020-05-11 18:27:30 +0000315
Goutham Pacha Ravi4a0b7322020-06-30 19:42:45 -0700316 if not tenant_networks_with_subnet:
317 # This can only occur if using tempest's pre-provisioned
318 # credentials and not allocating networks to them
319 raise cls.skipException(
320 "Test credentials must provide at least one "
321 "non-shared project network with a valid subnet when "
322 "CONF.share.create_networks_when_multitenancy_enabled is "
323 "set to True.")
debeltrami1753a592020-05-11 18:27:30 +0000324
Goutham Pacha Ravi4a0b7322020-06-30 19:42:45 -0700325 net_id = tenant_networks_with_subnet[0]['id']
326 subnet_id = tenant_networks_with_subnet[0]['subnets'][0]
debeltrami1753a592020-05-11 18:27:30 +0000327
Goutham Pacha Ravi4a0b7322020-06-30 19:42:45 -0700328 # Create suitable share-network
329 sn = cls.create_share_network(cleanup_in_class=True,
330 client=shares_client,
331 add_security_services=True,
332 name=sn_name,
333 description=sn_desc,
334 neutron_net_id=net_id,
335 neutron_subnet_id=subnet_id)
debeltrami1753a592020-05-11 18:27:30 +0000336
Goutham Pacha Ravi4a0b7322020-06-30 19:42:45 -0700337 return sn['id']
Marc Koderer0abc93b2015-07-15 09:18:35 +0200338
339 @classmethod
marcusvrne0d7cfd2016-06-24 12:27:55 -0300340 def _create_share(cls, share_protocol=None, size=None, name=None,
Marc Koderer0abc93b2015-07-15 09:18:35 +0200341 snapshot_id=None, description=None, metadata=None,
342 share_network_id=None, share_type_id=None,
Andrew Kerrb8436922016-06-01 15:32:43 -0400343 share_group_id=None, client=None,
Clinton Knighte5c8f092015-08-27 15:00:23 -0400344 cleanup_in_class=True, is_public=False, **kwargs):
Valeriy Ponomaryov1aaa72d2015-09-08 12:59:41 +0300345 client = client or cls.shares_v2_client
Marc Koderer0abc93b2015-07-15 09:18:35 +0200346 description = description or "Tempest's share"
yogesh06f519f2017-06-26 13:16:12 -0400347 share_network_id = (share_network_id or
348 CONF.share.share_network_id or
349 client.share_network_id or None)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200350 metadata = metadata or {}
marcusvrne0d7cfd2016-06-24 12:27:55 -0300351 size = size or CONF.share.share_size
Clinton Knighte5c8f092015-08-27 15:00:23 -0400352 kwargs.update({
Marc Koderer0abc93b2015-07-15 09:18:35 +0200353 'share_protocol': share_protocol,
354 'size': size,
355 'name': name,
356 'snapshot_id': snapshot_id,
357 'description': description,
358 'metadata': metadata,
359 'share_network_id': share_network_id,
360 'share_type_id': share_type_id,
361 'is_public': is_public,
Clinton Knighte5c8f092015-08-27 15:00:23 -0400362 })
Andrew Kerrb8436922016-06-01 15:32:43 -0400363 if share_group_id:
364 kwargs['share_group_id'] = share_group_id
Andrew Kerrbf31e912015-07-29 10:39:38 -0400365
lkuchlan86f24322021-04-27 14:23:05 +0300366 share = client.create_share(**kwargs)['share']
Andrew Kerrbf31e912015-07-29 10:39:38 -0400367 resource = {"type": "share", "id": share["id"], "client": client,
Andrew Kerrb8436922016-06-01 15:32:43 -0400368 "share_group_id": share_group_id}
Marc Koderer0abc93b2015-07-15 09:18:35 +0200369 cleanup_list = (cls.class_resources if cleanup_in_class else
370 cls.method_resources)
371 cleanup_list.insert(0, resource)
372 return share
373
374 @classmethod
Rodrigo Barbieri427bc052016-06-06 17:10:06 -0300375 def migrate_share(
376 cls, share_id, dest_host, wait_for_status, client=None,
Rodrigo Barbieri027df982016-11-24 15:52:03 -0200377 force_host_assisted_migration=False, writable=False,
378 nondisruptive=False, preserve_metadata=False,
379 preserve_snapshots=False, new_share_network_id=None,
Rodrigo Barbierid38d2f52016-07-19 22:24:56 -0300380 new_share_type_id=None, **kwargs):
Clinton Knighte5c8f092015-08-27 15:00:23 -0400381 client = client or cls.shares_v2_client
Rodrigo Barbieri427bc052016-06-06 17:10:06 -0300382 client.migrate_share(
383 share_id, dest_host,
384 force_host_assisted_migration=force_host_assisted_migration,
Rodrigo Barbieri027df982016-11-24 15:52:03 -0200385 writable=writable, preserve_metadata=preserve_metadata,
386 nondisruptive=nondisruptive, preserve_snapshots=preserve_snapshots,
Rodrigo Barbieri427bc052016-06-06 17:10:06 -0300387 new_share_network_id=new_share_network_id,
Rodrigo Barbierid38d2f52016-07-19 22:24:56 -0300388 new_share_type_id=new_share_type_id, **kwargs)
lkuchlan540e74a2021-01-19 18:08:25 +0200389 share = waiters.wait_for_migration_status(
390 client, share_id, dest_host, wait_for_status, **kwargs)
Rodrigo Barbierie3305122016-02-03 14:32:24 -0200391 return share
392
393 @classmethod
394 def migration_complete(cls, share_id, dest_host, client=None, **kwargs):
395 client = client or cls.shares_v2_client
396 client.migration_complete(share_id, **kwargs)
lkuchlan540e74a2021-01-19 18:08:25 +0200397 share = waiters.wait_for_migration_status(
398 client, share_id, dest_host, 'migration_success', **kwargs)
Rodrigo Barbierib7137ad2015-09-06 22:53:16 -0300399 return share
400
401 @classmethod
Rodrigo Barbieric9abf282016-08-24 22:01:31 -0300402 def migration_cancel(cls, share_id, dest_host, client=None, **kwargs):
403 client = client or cls.shares_v2_client
404 client.migration_cancel(share_id, **kwargs)
lkuchlan540e74a2021-01-19 18:08:25 +0200405 share = waiters.wait_for_migration_status(
406 client, share_id, dest_host, 'migration_cancelled', **kwargs)
Rodrigo Barbieric9abf282016-08-24 22:01:31 -0300407 return share
408
409 @classmethod
Marc Koderer0abc93b2015-07-15 09:18:35 +0200410 def create_share(cls, *args, **kwargs):
411 """Create one share and wait for available state. Retry if allowed."""
412 result = cls.create_shares([{"args": args, "kwargs": kwargs}])
413 return result[0]
414
415 @classmethod
416 def create_shares(cls, share_data_list):
417 """Creates several shares in parallel with retries.
418
419 Use this method when you want to create more than one share at same
420 time. Especially if config option 'share.share_creation_retry_number'
421 has value more than zero (0).
422 All shares will be expected to have 'available' status with or without
423 recreation else error will be raised.
424
425 :param share_data_list: list -- list of dictionaries with 'args' and
426 'kwargs' for '_create_share' method of this base class.
427 example of data:
428 share_data_list=[{'args': ['quuz'], 'kwargs': {'foo': 'bar'}}}]
429 :returns: list -- list of shares created using provided data.
430 """
431
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +0300432 for d in share_data_list:
Marc Koderer0abc93b2015-07-15 09:18:35 +0200433 if not isinstance(d, dict):
434 raise exceptions.TempestException(
435 "Expected 'dict', got '%s'" % type(d))
436 if "args" not in d:
437 d["args"] = []
438 if "kwargs" not in d:
439 d["kwargs"] = {}
440 if len(d) > 2:
441 raise exceptions.TempestException(
442 "Expected only 'args' and 'kwargs' keys. "
443 "Provided %s" % list(d))
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +0300444
445 data = []
446 for d in share_data_list:
447 client = d["kwargs"].pop("client", cls.shares_v2_client)
yogeshdb32f462016-09-28 15:09:50 -0400448 wait_for_status = d["kwargs"].pop("wait_for_status", True)
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +0300449 local_d = {
450 "args": d["args"],
451 "kwargs": copy.deepcopy(d["kwargs"]),
452 }
453 local_d["kwargs"]["client"] = client
454 local_d["share"] = cls._create_share(
455 *local_d["args"], **local_d["kwargs"])
456 local_d["cnt"] = 0
457 local_d["available"] = False
yogeshdb32f462016-09-28 15:09:50 -0400458 local_d["wait_for_status"] = wait_for_status
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +0300459 data.append(local_d)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200460
461 while not all(d["available"] for d in data):
462 for d in data:
yogeshdb32f462016-09-28 15:09:50 -0400463 if not d["wait_for_status"]:
464 d["available"] = True
Marc Koderer0abc93b2015-07-15 09:18:35 +0200465 if d["available"]:
466 continue
Valeriy Ponomaryov1a3e3382016-06-08 15:17:16 +0300467 client = d["kwargs"]["client"]
468 share_id = d["share"]["id"]
Marc Koderer0abc93b2015-07-15 09:18:35 +0200469 try:
lkuchlanf7fc5b62021-01-26 14:53:43 +0200470 waiters.wait_for_resource_status(
lkuchlan540e74a2021-01-19 18:08:25 +0200471 client, share_id, "available")
Marc Koderer0abc93b2015-07-15 09:18:35 +0200472 d["available"] = True
473 except (share_exceptions.ShareBuildErrorException,
474 exceptions.TimeoutException) as e:
475 if CONF.share.share_creation_retry_number > d["cnt"]:
476 d["cnt"] += 1
477 msg = ("Share '%s' failed to be built. "
Valeriy Ponomaryov1a3e3382016-06-08 15:17:16 +0300478 "Trying create another." % share_id)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200479 LOG.error(msg)
480 LOG.error(e)
Valeriy Ponomaryov1a3e3382016-06-08 15:17:16 +0300481 cg_id = d["kwargs"].get("consistency_group_id")
482 if cg_id:
483 # NOTE(vponomaryov): delete errored share
484 # immediately in case share is part of CG.
485 client.delete_share(
486 share_id,
487 params={"consistency_group_id": cg_id})
488 client.wait_for_resource_deletion(
489 share_id=share_id)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200490 d["share"] = cls._create_share(
491 *d["args"], **d["kwargs"])
492 else:
gecong197358663802016-08-25 11:08:45 +0800493 raise
Marc Koderer0abc93b2015-07-15 09:18:35 +0200494
495 return [d["share"] for d in data]
496
497 @classmethod
Andrew Kerrb8436922016-06-01 15:32:43 -0400498 def create_share_group(cls, client=None, cleanup_in_class=True,
499 share_network_id=None, **kwargs):
Clinton Knighte5c8f092015-08-27 15:00:23 -0400500 client = client or cls.shares_v2_client
Andrew Kerrb8436922016-06-01 15:32:43 -0400501 if kwargs.get('source_share_group_snapshot_id') is None:
Goutham Pacha Ravi9221f5e2016-04-21 13:17:49 -0400502 kwargs['share_network_id'] = (share_network_id or
503 client.share_network_id or None)
lkuchlan86f24322021-04-27 14:23:05 +0300504 share_group = client.create_share_group(**kwargs)['share_group']
Andrew Kerrbf31e912015-07-29 10:39:38 -0400505 resource = {
Andrew Kerrb8436922016-06-01 15:32:43 -0400506 "type": "share_group",
507 "id": share_group["id"],
508 "client": client,
509 }
Andrew Kerrbf31e912015-07-29 10:39:38 -0400510 if cleanup_in_class:
511 cls.class_resources.insert(0, resource)
512 else:
513 cls.method_resources.insert(0, resource)
514
Andrew Kerrb8436922016-06-01 15:32:43 -0400515 if kwargs.get('source_share_group_snapshot_id'):
516 new_share_group_shares = client.list_shares(
Andrew Kerrbf31e912015-07-29 10:39:38 -0400517 detailed=True,
lkuchlan86f24322021-04-27 14:23:05 +0300518 params={'share_group_id': share_group['id']})['shares']
Andrew Kerrbf31e912015-07-29 10:39:38 -0400519
Andrew Kerrb8436922016-06-01 15:32:43 -0400520 for share in new_share_group_shares:
Andrew Kerrbf31e912015-07-29 10:39:38 -0400521 resource = {"type": "share",
522 "id": share["id"],
523 "client": client,
Andrew Kerrb8436922016-06-01 15:32:43 -0400524 "share_group_id": share.get("share_group_id")}
Andrew Kerrbf31e912015-07-29 10:39:38 -0400525 if cleanup_in_class:
526 cls.class_resources.insert(0, resource)
527 else:
528 cls.method_resources.insert(0, resource)
529
lkuchlanf7fc5b62021-01-26 14:53:43 +0200530 waiters.wait_for_resource_status(
531 client, share_group['id'], 'available',
532 resource_name='share_group')
Andrew Kerrb8436922016-06-01 15:32:43 -0400533 return share_group
534
535 @classmethod
536 def create_share_group_type(cls, name=None, share_types=(), is_public=None,
537 group_specs=None, client=None,
538 cleanup_in_class=True, **kwargs):
539 client = client or cls.shares_v2_client
Valeriy Ponomaryove92f09f2017-03-16 17:25:47 +0300540 if (group_specs is None and
541 CONF.share.capability_sg_consistent_snapshot_support):
Valeriy Ponomaryov3c188932017-03-15 19:06:23 +0300542 group_specs = {
543 'consistent_snapshot_support': (
544 CONF.share.capability_sg_consistent_snapshot_support),
545 }
Andrew Kerrb8436922016-06-01 15:32:43 -0400546 share_group_type = client.create_share_group_type(
547 name=name,
548 share_types=share_types,
549 is_public=is_public,
550 group_specs=group_specs,
lkuchlan86f24322021-04-27 14:23:05 +0300551 **kwargs)['share_group_type']
Andrew Kerrb8436922016-06-01 15:32:43 -0400552 resource = {
553 "type": "share_group_type",
554 "id": share_group_type["id"],
555 "client": client,
556 }
557 if cleanup_in_class:
558 cls.class_resources.insert(0, resource)
559 else:
560 cls.method_resources.insert(0, resource)
561 return share_group_type
Andrew Kerrbf31e912015-07-29 10:39:38 -0400562
563 @classmethod
Marc Koderer0abc93b2015-07-15 09:18:35 +0200564 def create_snapshot_wait_for_active(cls, share_id, name=None,
565 description=None, force=False,
Ashley Rodriguezc45cb4b2022-02-04 21:29:33 +0000566 metadata=None, client=None,
567 cleanup_in_class=True):
Marc Koderer0abc93b2015-07-15 09:18:35 +0200568 if client is None:
Yogesh1f931ff2015-09-29 23:41:02 -0400569 client = cls.shares_v2_client
Marc Koderer0abc93b2015-07-15 09:18:35 +0200570 if description is None:
571 description = "Tempest's snapshot"
lkuchlan86f24322021-04-27 14:23:05 +0300572 snapshot = client.create_snapshot(
Ashley Rodriguezc45cb4b2022-02-04 21:29:33 +0000573 share_id, name, description, force, metadata)['snapshot']
Marc Koderer0abc93b2015-07-15 09:18:35 +0200574 resource = {
575 "type": "snapshot",
576 "id": snapshot["id"],
577 "client": client,
578 }
579 if cleanup_in_class:
580 cls.class_resources.insert(0, resource)
581 else:
582 cls.method_resources.insert(0, resource)
lkuchlanf7fc5b62021-01-26 14:53:43 +0200583 waiters.wait_for_resource_status(client, snapshot["id"], "available",
584 resource_name='snapshot')
Marc Koderer0abc93b2015-07-15 09:18:35 +0200585 return snapshot
586
587 @classmethod
Andrew Kerrb8436922016-06-01 15:32:43 -0400588 def create_share_group_snapshot_wait_for_active(
589 cls, share_group_id, name=None, description=None, client=None,
590 cleanup_in_class=True, **kwargs):
Clinton Knighte5c8f092015-08-27 15:00:23 -0400591 client = client or cls.shares_v2_client
Andrew Kerrbf31e912015-07-29 10:39:38 -0400592 if description is None:
Andrew Kerrb8436922016-06-01 15:32:43 -0400593 description = "Tempest's share group snapshot"
594 sg_snapshot = client.create_share_group_snapshot(
lkuchlan86f24322021-04-27 14:23:05 +0300595 share_group_id, name=name, description=description,
596 **kwargs)['share_group_snapshot']
Andrew Kerrbf31e912015-07-29 10:39:38 -0400597 resource = {
Andrew Kerrb8436922016-06-01 15:32:43 -0400598 "type": "share_group_snapshot",
599 "id": sg_snapshot["id"],
Andrew Kerrbf31e912015-07-29 10:39:38 -0400600 "client": client,
601 }
602 if cleanup_in_class:
603 cls.class_resources.insert(0, resource)
604 else:
605 cls.method_resources.insert(0, resource)
lkuchlanf7fc5b62021-01-26 14:53:43 +0200606 waiters.wait_for_resource_status(
607 client, sg_snapshot["id"], "available",
608 resource_name="share_group_snapshot")
Andrew Kerrb8436922016-06-01 15:32:43 -0400609 return sg_snapshot
Andrew Kerrbf31e912015-07-29 10:39:38 -0400610
611 @classmethod
Goutham Pacha Ravi839c98b2019-01-14 23:16:23 -0800612 def get_availability_zones(cls, client=None, backends=None):
Yogeshbdb88102015-09-29 23:41:02 -0400613 """List the availability zones for "manila-share" services
614
615 that are currently in "up" state.
616 """
Goutham Pacha Ravi839c98b2019-01-14 23:16:23 -0800617 client = client or cls.admin_shares_v2_client
618 backends = (
619 '|'.join(['^%s$' % backend for backend in backends])
620 if backends else '.*'
621 )
lkuchlan86f24322021-04-27 14:23:05 +0300622 cls.services = client.list_services()['services']
Yogeshbdb88102015-09-29 23:41:02 -0400623 zones = [service['zone'] for service in cls.services if
Goutham Pacha Ravi839c98b2019-01-14 23:16:23 -0800624 service['binary'] == 'manila-share' and
625 service['state'] == 'up' and
626 re.search(backends, service['host'])]
Douglas Viroel161f1802020-04-25 17:18:14 -0300627 return list(set(zones))
Yogeshbdb88102015-09-29 23:41:02 -0400628
Goutham Pacha Ravi839c98b2019-01-14 23:16:23 -0800629 @classmethod
630 def get_pools_matching_share_type(cls, share_type, client=None):
631 client = client or cls.admin_shares_v2_client
632 if utils.is_microversion_supported('2.23'):
633 return client.list_pools(
andrebeltrami3b4d4852020-02-04 19:11:54 +0000634 detail=True,
Goutham Pacha Ravi839c98b2019-01-14 23:16:23 -0800635 search_opts={'share_type': share_type['id']})['pools']
636
637 pools = client.list_pools(detail=True)['pools']
638 share_type = client.get_share_type(share_type['id'])['share_type']
639 extra_specs = {}
640 for k, v in share_type['extra_specs'].items():
641 extra_specs[k] = (
haixin48895812020-09-30 13:50:37 +0800642 True if str(v).lower() == 'true'
643 else False if str(v).lower() == 'false' else v
Goutham Pacha Ravi839c98b2019-01-14 23:16:23 -0800644 )
645 return [
646 pool for pool in pools if all(y in pool['capabilities'].items()
647 for y in extra_specs.items())
648 ]
649
650 @classmethod
651 def get_availability_zones_matching_share_type(cls, share_type,
652 client=None):
653
654 client = client or cls.admin_shares_v2_client
655 pools_matching_share_type = cls.get_pools_matching_share_type(
656 share_type, client=client)
657 backends_matching_share_type = set(
658 [pool['name'].split("#")[0] for pool in pools_matching_share_type]
659 )
660 azs = cls.get_availability_zones(backends=backends_matching_share_type)
661 return azs
662
Kiran Pawar59745062021-11-01 12:37:49 +0000663 def get_pools_for_replication_domain(self, share=None):
Yogesh1f931ff2015-09-29 23:41:02 -0400664 # Get the list of pools for the replication domain
665 pools = self.admin_client.list_pools(detail=True)['pools']
Kiran Pawar59745062021-11-01 12:37:49 +0000666 if share:
667 instance_host = self.admin_client.get_share(
668 share['id'])['share']['host']
669 else:
670 instance_host = self.admin_client.get_share(
671 self.shares[0]['id'])['share']['host']
Yogesh1f931ff2015-09-29 23:41:02 -0400672 host_pool = [p for p in pools if p['name'] == instance_host][0]
673 rep_domain = host_pool['capabilities']['replication_domain']
674 pools_in_rep_domain = [p for p in pools if p['capabilities'][
675 'replication_domain'] == rep_domain]
676 return rep_domain, pools_in_rep_domain
677
Yogeshbdb88102015-09-29 23:41:02 -0400678 @classmethod
debeltrami0d523bb2020-08-20 12:48:49 +0000679 def create_share_replica(cls, share_id, availability_zone=None,
kpdev2521fbf2021-08-15 21:54:41 +0200680 scheduler_hints=None,
681 share_network_id=None,
debeltrami0d523bb2020-08-20 12:48:49 +0000682 client=None, cleanup_in_class=False,
683 cleanup=True,
kpdev2521fbf2021-08-15 21:54:41 +0200684 version=CONF.share.max_api_microversion):
Yogeshbdb88102015-09-29 23:41:02 -0400685 client = client or cls.shares_v2_client
Douglas Viroelbd4e78c2019-09-02 17:16:30 -0300686 replica = client.create_share_replica(
lkuchlan86f24322021-04-27 14:23:05 +0300687 share_id, availability_zone=availability_zone,
kpdev2521fbf2021-08-15 21:54:41 +0200688 scheduler_hints=scheduler_hints,
689 share_network_id=share_network_id,
690 version=version)['share_replica']
Yogeshbdb88102015-09-29 23:41:02 -0400691 resource = {
692 "type": "share_replica",
693 "id": replica["id"],
694 "client": client,
695 "share_id": share_id,
696 }
697 # NOTE(Yogi1): Cleanup needs to be disabled during promotion tests.
698 if cleanup:
699 if cleanup_in_class:
700 cls.class_resources.insert(0, resource)
701 else:
702 cls.method_resources.insert(0, resource)
lkuchlanf7fc5b62021-01-26 14:53:43 +0200703 waiters.wait_for_resource_status(
704 client, replica["id"], constants.STATUS_AVAILABLE,
705 resource_name='share_replica')
Yogeshbdb88102015-09-29 23:41:02 -0400706 return replica
707
708 @classmethod
Kiran Pawaredd82c72022-05-07 14:48:36 +0000709 def create_backup_wait_for_active(cls, share_id, client=None,
710 cleanup_in_class=False, cleanup=True,
711 version=CONF.share.max_api_microversion):
712 client = client or cls.shares_v2_client
713 backup_name = data_utils.rand_name('Backup')
714 backup_options = CONF.share.driver_assisted_backup_test_driver_options
715 backup = client.create_share_backup(
716 share_id,
717 name=backup_name,
718 backup_options=backup_options)['share_backup']
719 resource = {
720 "type": "share_backup",
721 "id": backup["id"],
722 "client": client,
723 }
724 if cleanup:
725 if cleanup_in_class:
726 cls.class_resources.insert(0, resource)
727 else:
728 cls.method_resources.insert(0, resource)
729 waiters.wait_for_resource_status(client, backup["id"], "available",
730 resource_name='share_backup')
731 return client.get_share_backup(backup['id'])['share_backup']
732
733 @classmethod
silvacarlossd354d672020-08-23 18:49:52 +0000734 def delete_share_replica(cls, replica_id, client=None,
735 version=CONF.share.max_api_microversion):
Yogeshbdb88102015-09-29 23:41:02 -0400736 client = client or cls.shares_v2_client
Yogesh1f931ff2015-09-29 23:41:02 -0400737 try:
silvacarlossd354d672020-08-23 18:49:52 +0000738 client.delete_share_replica(replica_id, version=version)
Yogesh1f931ff2015-09-29 23:41:02 -0400739 client.wait_for_resource_deletion(replica_id=replica_id)
740 except exceptions.NotFound:
741 pass
Yogeshbdb88102015-09-29 23:41:02 -0400742
743 @classmethod
silvacarlossd354d672020-08-23 18:49:52 +0000744 def promote_share_replica(cls, replica_id, client=None,
745 version=CONF.share.max_api_microversion):
Yogeshbdb88102015-09-29 23:41:02 -0400746 client = client or cls.shares_v2_client
lkuchlan86f24322021-04-27 14:23:05 +0300747 replica = client.promote_share_replica(
748 replica_id, version=version)['share_replica']
lkuchlanf7fc5b62021-01-26 14:53:43 +0200749 waiters.wait_for_resource_status(
lkuchlan540e74a2021-01-19 18:08:25 +0200750 client, replica["id"], constants.REPLICATION_STATE_ACTIVE,
lkuchlanf7fc5b62021-01-26 14:53:43 +0200751 resource_name='share_replica', status_attr="replica_state")
Yogeshbdb88102015-09-29 23:41:02 -0400752 return replica
753
Goutham Pacha Ravi1727f7a2020-05-22 13:41:40 -0700754 @classmethod
Marc Koderer0abc93b2015-07-15 09:18:35 +0200755 def create_share_network(cls, client=None,
debeltrami1753a592020-05-11 18:27:30 +0000756 cleanup_in_class=False,
757 add_security_services=True, **kwargs):
758
Marc Koderer0abc93b2015-07-15 09:18:35 +0200759 if client is None:
Stephen Finucane878716d2025-09-05 08:18:03 +0100760 client = cls.shares_v2_client
lkuchlan86f24322021-04-27 14:23:05 +0300761 share_network = client.create_share_network(**kwargs)['share_network']
Marc Koderer0abc93b2015-07-15 09:18:35 +0200762 resource = {
763 "type": "share_network",
764 "id": share_network["id"],
765 "client": client,
766 }
debeltrami1753a592020-05-11 18:27:30 +0000767
Marc Koderer0abc93b2015-07-15 09:18:35 +0200768 if cleanup_in_class:
769 cls.class_resources.insert(0, resource)
770 else:
771 cls.method_resources.insert(0, resource)
debeltrami1753a592020-05-11 18:27:30 +0000772
773 if add_security_services:
774 cls.provide_and_associate_security_services(
775 client, share_network["id"], cleanup_in_class=cleanup_in_class)
776
Marc Koderer0abc93b2015-07-15 09:18:35 +0200777 return share_network
778
779 @classmethod
debeltrami1753a592020-05-11 18:27:30 +0000780 def create_share_network_subnet(cls,
781 client=None,
782 cleanup_in_class=False,
renanpiranguinho0fec91a2023-03-10 19:58:31 +0000783 metadata=None,
debeltrami1753a592020-05-11 18:27:30 +0000784 **kwargs):
Douglas Viroelb7e27e72019-08-06 19:40:37 -0300785 if client is None:
786 client = cls.shares_v2_client
lkuchlan86f24322021-04-27 14:23:05 +0300787 share_network_subnet = client.create_subnet(
renanpiranguinho0fec91a2023-03-10 19:58:31 +0000788 metadata=metadata, **kwargs)['share_network_subnet']
Douglas Viroelb7e27e72019-08-06 19:40:37 -0300789 resource = {
Goutham Pacha Raviad43e932023-01-16 13:06:46 -0800790 "type": "share_network_subnet",
Douglas Viroelb7e27e72019-08-06 19:40:37 -0300791 "id": share_network_subnet["id"],
792 "extra_params": {
793 "share_network_id": share_network_subnet["share_network_id"]
794 },
795 "client": client,
796 }
797 if cleanup_in_class:
798 cls.class_resources.insert(0, resource)
799 else:
800 cls.method_resources.insert(0, resource)
801 return share_network_subnet
802
803 @classmethod
Marc Koderer0abc93b2015-07-15 09:18:35 +0200804 def create_security_service(cls, ss_type="ldap", client=None,
805 cleanup_in_class=False, **kwargs):
806 if client is None:
Stephen Finucane878716d2025-09-05 08:18:03 +0100807 client = cls.shares_v2_client
lkuchlan86f24322021-04-27 14:23:05 +0300808 security_service = client.create_security_service(
809 ss_type, **kwargs)['security_service']
Marc Koderer0abc93b2015-07-15 09:18:35 +0200810 resource = {
811 "type": "security_service",
812 "id": security_service["id"],
813 "client": client,
814 }
815 if cleanup_in_class:
816 cls.class_resources.insert(0, resource)
817 else:
818 cls.method_resources.insert(0, resource)
819 return security_service
820
821 @classmethod
Goutham Pacha Ravi646fcd22023-07-14 12:47:47 -0700822 def create_resource_lock(cls, resource_id, resource_type='share',
823 resource_action='delete', lock_reason=None,
824 client=None, version=LATEST_MICROVERSION,
825 cleanup_in_class=True):
826 lock_reason = lock_reason or "locked by tempest tests"
827 client = client or cls.shares_v2_client
828
829 lock = client.create_resource_lock(resource_id,
830 resource_type,
831 resource_action=resource_action,
832 lock_reason=lock_reason,
833 version=version)['resource_lock']
834 resource = {
835 "type": "resource_lock",
836 "id": lock["id"],
837 "client": client,
838 }
839 if cleanup_in_class:
840 cls.class_resources.insert(0, resource)
841 else:
842 cls.method_resources.insert(0, resource)
843 return lock
844
845 @classmethod
haixin0d1d29f2019-08-02 16:50:45 +0800846 def update_share_type(cls, share_type_id, name=None,
847 is_public=None, description=None,
848 client=None):
849 if client is None:
850 client = cls.shares_v2_client
851 share_type = client.update_share_type(share_type_id, name,
852 is_public, description)
853 return share_type
854
Goutham Pacha Raviaf448262020-06-29 14:24:13 -0700855 @classmethod
856 def update_quotas(cls, project_id, user_id=None, cleanup=True,
857 client=None, **kwargs):
858 client = client or cls.shares_v2_client
859 updated_quotas = client.update_quotas(project_id,
860 user_id=user_id,
lkuchlan86f24322021-04-27 14:23:05 +0300861 **kwargs)['quota_set']
Goutham Pacha Raviaf448262020-06-29 14:24:13 -0700862 resource = {
863 "type": "quotas",
864 "id": project_id,
865 "client": client,
866 "user_id": user_id,
867 }
868 if cleanup:
869 cls.method_resources.insert(0, resource)
870 return updated_quotas
871
Marc Koderer0abc93b2015-07-15 09:18:35 +0200872 @classmethod
Yogesh1f931ff2015-09-29 23:41:02 -0400873 def clear_share_replicas(cls, share_id, client=None):
874 client = client or cls.shares_v2_client
875 share_replicas = client.list_share_replicas(
lkuchlan86f24322021-04-27 14:23:05 +0300876 share_id=share_id)['share_replicas']
Yogesh1f931ff2015-09-29 23:41:02 -0400877
878 for replica in share_replicas:
879 try:
880 cls.delete_share_replica(replica['id'])
881 except exceptions.BadRequest:
882 # Ignore the exception due to deletion of last active replica
883 pass
884
885 @classmethod
Marc Koderer0abc93b2015-07-15 09:18:35 +0200886 def clear_resources(cls, resources=None):
887 """Deletes resources, that were created in test suites.
888
889 This method tries to remove resources from resource list,
890 if it is not found, assumed it was deleted in test itself.
891 It is expected, that all resources were added as LIFO
892 due to restriction of deletion resources, that is in the chain.
893
894 :param resources: dict with keys 'type','id','client' and 'deleted'
895 """
Marc Koderer0abc93b2015-07-15 09:18:35 +0200896 if resources is None:
897 resources = cls.method_resources
898 for res in resources:
899 if "deleted" not in res.keys():
900 res["deleted"] = False
901 if "client" not in res.keys():
Stephen Finucane878716d2025-09-05 08:18:03 +0100902 res["client"] = cls.shares_v2_client
Felipe Rodrigues9cdeda12023-02-09 21:45:49 -0300903 if not (res["deleted"]):
904 try:
905 res_id = res['id']
906 client = res["client"]
907 with handle_cleanup_exceptions():
908 if res["type"] == "share":
909 cls.clear_share_replicas(res_id)
910 share_group_id = res.get('share_group_id')
911 if share_group_id:
912 params = {'share_group_id': share_group_id}
913 client.delete_share(res_id, params=params)
914 else:
915 client.delete_share(res_id)
916 client.wait_for_resource_deletion(share_id=res_id)
917 elif res["type"] == "snapshot":
918 client.delete_snapshot(res_id)
919 client.wait_for_resource_deletion(
920 snapshot_id=res_id)
921 elif (res["type"] == "share_network" and
922 res_id != CONF.share.share_network_id):
923 client.delete_share_network(res_id)
924 client.wait_for_resource_deletion(sn_id=res_id)
925 elif res["type"] == "dissociate_security_service":
926 sn_id = res["extra_params"]["share_network_id"]
927 client.remove_sec_service_from_share_network(
928 sn_id=sn_id, ss_id=res_id
929 )
930 elif res["type"] == "security_service":
931 client.delete_security_service(res_id)
932 client.wait_for_resource_deletion(ss_id=res_id)
933 elif res["type"] == "share_type":
Goutham Pacha Ravi78daf102025-11-13 14:30:16 -0800934 # Check if there are still shares using this
935 # share type before attempting deletion to avoid
936 # cascading cleanup issues
937 shares_using_type = []
938 try:
939 shares_using_type = client.list_shares(
940 params={'share_type_id': res_id}
941 )['shares']
942 except Exception:
943 pass
944 if shares_using_type:
945 # Skip deletion if any shares exist
946 LOG.warning("Skipping share type deletion "
947 "for %s , still has %d shares "
948 "using it.",
949 res_id,
950 len(shares_using_type))
951 res["deleted"] = True
952 continue
Felipe Rodrigues9cdeda12023-02-09 21:45:49 -0300953 client.delete_share_type(res_id)
954 client.wait_for_resource_deletion(st_id=res_id)
955 elif res["type"] == "share_group":
956 client.delete_share_group(res_id)
957 client.wait_for_resource_deletion(
958 share_group_id=res_id)
959 elif res["type"] == "share_group_type":
960 client.delete_share_group_type(res_id)
961 client.wait_for_resource_deletion(
962 share_group_type_id=res_id)
963 elif res["type"] == "share_group_snapshot":
964 client.delete_share_group_snapshot(res_id)
965 client.wait_for_resource_deletion(
966 share_group_snapshot_id=res_id)
967 elif res["type"] == "share_replica":
968 client.delete_share_replica(res_id)
969 client.wait_for_resource_deletion(
970 replica_id=res_id)
971 elif res["type"] == "share_backup":
972 client.delete_share_backup(res_id)
973 client.wait_for_resource_deletion(backup_id=res_id)
974 elif res["type"] == "share_network_subnet":
975 sn_id = res["extra_params"]["share_network_id"]
976 client.delete_subnet(sn_id, res_id)
977 client.wait_for_resource_deletion(
978 share_network_subnet_id=res_id,
979 sn_id=sn_id)
980 elif res["type"] == "quotas":
981 user_id = res.get('user_id')
982 client.reset_quotas(res_id, user_id=user_id)
983 elif res["type"] == "resource_lock":
984 client.delete_resource_lock(res_id)
Clinton Knighte5c8f092015-08-27 15:00:23 -0400985 else:
Felipe Rodrigues9cdeda12023-02-09 21:45:49 -0300986 LOG.warning("Provided unsupported resource type "
987 "for cleanup '%s'. Skipping.",
988 res["type"])
989 except share_exceptions.ResourceReleaseFailed as e:
990 # Resource is on error deleting state, so we remove it from
991 # the list to delete, since it cannot be deleted anymore.
992 # It raises because the current cleanup class or method
993 # must fail.
994 res["deleted"] = True
995 raise e
Marc Koderer0abc93b2015-07-15 09:18:35 +0200996 res["deleted"] = True
997
Marc Koderer0abc93b2015-07-15 09:18:35 +0200998 # Useful assertions
999 def assertDictMatch(self, d1, d2, approx_equal=False, tolerance=0.001):
1000 """Assert two dicts are equivalent.
1001
1002 This is a 'deep' match in the sense that it handles nested
1003 dictionaries appropriately.
1004
1005 NOTE:
1006
1007 If you don't care (or don't know) a given value, you can specify
1008 the string DONTCARE as the value. This will cause that dict-item
1009 to be skipped.
1010
1011 """
1012 def raise_assertion(msg):
1013 d1str = str(d1)
1014 d2str = str(d2)
1015 base_msg = ('Dictionaries do not match. %(msg)s d1: %(d1str)s '
1016 'd2: %(d2str)s' %
1017 {"msg": msg, "d1str": d1str, "d2str": d2str})
1018 raise AssertionError(base_msg)
1019
1020 d1keys = set(d1.keys())
1021 d2keys = set(d2.keys())
1022 if d1keys != d2keys:
1023 d1only = d1keys - d2keys
1024 d2only = d2keys - d1keys
1025 raise_assertion('Keys in d1 and not d2: %(d1only)s. '
1026 'Keys in d2 and not d1: %(d2only)s' %
1027 {"d1only": d1only, "d2only": d2only})
1028
1029 for key in d1keys:
1030 d1value = d1[key]
1031 d2value = d2[key]
1032 try:
1033 error = abs(float(d1value) - float(d2value))
1034 within_tolerance = error <= tolerance
1035 except (ValueError, TypeError):
daiki kato6914b1a2016-03-16 17:16:57 +09001036 # If both values aren't convertible to float, just ignore
Marc Koderer0abc93b2015-07-15 09:18:35 +02001037 # ValueError if arg is a str, TypeError if it's something else
1038 # (like None)
1039 within_tolerance = False
1040
1041 if hasattr(d1value, 'keys') and hasattr(d2value, 'keys'):
1042 self.assertDictMatch(d1value, d2value)
1043 elif 'DONTCARE' in (d1value, d2value):
1044 continue
1045 elif approx_equal and within_tolerance:
1046 continue
1047 elif d1value != d2value:
1048 raise_assertion("d1['%(key)s']=%(d1value)s != "
1049 "d2['%(key)s']=%(d2value)s" %
1050 {
1051 "key": key,
1052 "d1value": d1value,
1053 "d2value": d2value
1054 })
1055
Alex Meadeba8a1602016-05-06 09:33:09 -04001056 def create_user_message(self):
1057 """Trigger a 'no valid host' situation to generate a message."""
1058 extra_specs = {
1059 'vendor_name': 'foobar',
1060 'driver_handles_share_servers': CONF.share.multitenancy_enabled,
1061 }
1062 share_type_name = data_utils.rand_name("share-type")
1063
1064 bogus_type = self.create_share_type(
Goutham Pacha Raviaf448262020-06-29 14:24:13 -07001065 client=self.admin_shares_v2_client,
Alex Meadeba8a1602016-05-06 09:33:09 -04001066 name=share_type_name,
1067 extra_specs=extra_specs)['share_type']
1068
1069 params = {'share_type_id': bogus_type['id'],
1070 'share_network_id': self.shares_v2_client.share_network_id}
lkuchlan86f24322021-04-27 14:23:05 +03001071 share = self.shares_v2_client.create_share(**params)['share']
Alex Meadeba8a1602016-05-06 09:33:09 -04001072 self.addCleanup(self.shares_v2_client.delete_share, share['id'])
lkuchlanf7fc5b62021-01-26 14:53:43 +02001073 waiters.wait_for_resource_status(
lkuchlan540e74a2021-01-19 18:08:25 +02001074 self.shares_v2_client, share['id'], "error")
1075 return waiters.wait_for_message(self.shares_v2_client, share['id'])
Alex Meadeba8a1602016-05-06 09:33:09 -04001076
lkuchlan5af7cb42020-07-14 18:05:09 +03001077 def allow_access(self, share_id, client=None, access_type=None,
lkuchlanad511b62022-05-08 09:23:23 +03001078 access_level='rw', access_to=None, metadata=None,
1079 version=LATEST_MICROVERSION, status='active',
silvacarlossd10819b2023-07-24 13:53:32 -03001080 raise_rule_in_error_state=True, lock_visibility=False,
1081 lock_deletion=False, cleanup=True):
lkuchlan5af7cb42020-07-14 18:05:09 +03001082
1083 client = client or self.shares_v2_client
lkuchlan4a803162022-11-16 11:34:33 +02001084 a_type, a_to = utils.get_access_rule_data_from_config(
1085 client.share_protocol)
lkuchlan5af7cb42020-07-14 18:05:09 +03001086 access_type = access_type or a_type
1087 access_to = access_to or a_to
1088
lkuchlanad511b62022-05-08 09:23:23 +03001089 kwargs = {
1090 'access_type': access_type,
1091 'access_to': access_to,
1092 'access_level': access_level
1093 }
silvacarlossd10819b2023-07-24 13:53:32 -03001094 delete_kwargs = (
1095 {'unrestrict': True} if lock_deletion else {}
1096 )
lkuchlanad511b62022-05-08 09:23:23 +03001097 if client is self.shares_v2_client:
1098 kwargs.update({'metadata': metadata, 'version': version})
silvacarlossd10819b2023-07-24 13:53:32 -03001099 if lock_visibility:
1100 kwargs.update({'lock_visibility': True})
1101 if lock_deletion:
1102 kwargs.update({'lock_deletion': True})
lkuchlanad511b62022-05-08 09:23:23 +03001103
1104 rule = client.create_access_rule(share_id, **kwargs)['access']
lkuchlanf7fc5b62021-01-26 14:53:43 +02001105 waiters.wait_for_resource_status(
1106 client, share_id, status, resource_name='access_rule',
lkuchlanad511b62022-05-08 09:23:23 +03001107 rule_id=rule['id'], version=version,
lkuchlanf7fc5b62021-01-26 14:53:43 +02001108 raise_rule_in_error_state=raise_rule_in_error_state)
lkuchlan5af7cb42020-07-14 18:05:09 +03001109 if cleanup:
lkuchlanad511b62022-05-08 09:23:23 +03001110 self.addCleanup(
1111 client.wait_for_resource_deletion, rule_id=rule['id'],
1112 share_id=share_id, version=version)
silvacarlossd10819b2023-07-24 13:53:32 -03001113 self.addCleanup(
1114 client.delete_access_rule, share_id, rule['id'],
1115 **delete_kwargs)
lkuchlan5af7cb42020-07-14 18:05:09 +03001116 return rule
1117
Marc Koderer0abc93b2015-07-15 09:18:35 +02001118
Marc Koderer0abc93b2015-07-15 09:18:35 +02001119class BaseSharesAdminTest(BaseSharesTest):
1120 """Base test case class for all Shares Admin API tests."""
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +03001121 credentials = ('admin', )
1122
Victoria Martinez de la Cruzf6bc6fa2018-02-01 11:27:00 -05001123 @classmethod
1124 def setup_clients(cls):
1125 super(BaseSharesAdminTest, cls).setup_clients()
1126 # Initialise share clients
lkuchlan3af822b2021-06-06 10:35:30 +03001127 cls.admin_shares_client = cls.os_admin.share_v1.SharesClient()
Victoria Martinez de la Cruzf6bc6fa2018-02-01 11:27:00 -05001128 cls.admin_shares_v2_client = cls.os_admin.share_v2.SharesV2Client()
1129
lkuchlan3af822b2021-06-06 10:35:30 +03001130 @staticmethod
1131 def add_extra_specs_to_dict(extra_specs=None):
1132 """Add any required extra-specs to share type dictionary"""
haixin48895812020-09-30 13:50:37 +08001133 dhss = str(CONF.share.multitenancy_enabled)
lkuchlan3af822b2021-06-06 10:35:30 +03001134 extra_specs_dict = {"driver_handles_share_servers": dhss}
1135 if extra_specs:
1136 extra_specs_dict.update(extra_specs)
Felipe Rodrigues6e566772021-08-23 14:57:50 -03001137 if CONF.share.capability_thin_provisioned:
1138 extra_specs_dict['thin_provisioning'] = 'True'
lkuchlan3af822b2021-06-06 10:35:30 +03001139 return extra_specs_dict
1140
Victoria Martinez de la Cruzf6bc6fa2018-02-01 11:27:00 -05001141 @classmethod
lkuchlan3af822b2021-06-06 10:35:30 +03001142 def create_share_type(cls, name=None, is_public=True, client=None,
1143 cleanup_in_class=True, extra_specs=None, **kwargs):
1144 name = name or data_utils.rand_name(
1145 cls.__class__.__name__ + 'share-type')
1146 client = client or cls.admin_shares_v2_client
1147 extra_specs = cls.add_extra_specs_to_dict(extra_specs=extra_specs)
1148 share_type = client.create_share_type(name, is_public,
1149 extra_specs=extra_specs,
1150 **kwargs)['share_type']
1151 resource = {
1152 "type": "share_type",
1153 "id": share_type["id"],
1154 "client": client,
1155 }
1156 if cleanup_in_class:
1157 cls.class_resources.insert(0, resource)
1158 else:
1159 cls.method_resources.insert(0, resource)
1160 return share_type
Victoria Martinez de la Cruzf6bc6fa2018-02-01 11:27:00 -05001161
1162 @classmethod
1163 def _create_share_group_type(cls):
1164 share_group_type_name = data_utils.rand_name("unique_sgtype_name")
1165 return cls.create_share_group_type(
1166 name=share_group_type_name, share_types=[cls.share_type_id],
1167 client=cls.admin_shares_v2_client)
1168
Lucio Seki37056942019-01-24 15:40:20 -02001169 def _create_share_for_manage(self):
1170 creation_data = {
lkuchlan3af822b2021-06-06 10:35:30 +03001171 'share_type_id': self.st['id'],
Lucio Seki37056942019-01-24 15:40:20 -02001172 'share_protocol': self.protocol,
1173 }
1174
1175 share = self.create_share(**creation_data)
lkuchlan86f24322021-04-27 14:23:05 +03001176 share = self.shares_v2_client.get_share(share['id'])['share']
Lucio Seki37056942019-01-24 15:40:20 -02001177
1178 if utils.is_microversion_ge(CONF.share.max_api_microversion, "2.9"):
lkuchlan86f24322021-04-27 14:23:05 +03001179 el = self.shares_v2_client.list_share_export_locations(
1180 share["id"])['export_locations']
Lucio Seki37056942019-01-24 15:40:20 -02001181 share["export_locations"] = el
1182
1183 return share
1184
1185 def _unmanage_share_and_wait(self, share):
1186 self.shares_v2_client.unmanage_share(share['id'])
1187 self.shares_v2_client.wait_for_resource_deletion(share_id=share['id'])
1188
1189 def _reset_state_and_delete_share(self, share):
1190 self.shares_v2_client.reset_state(share['id'])
1191 self._delete_share_and_wait(share)
1192
1193 def _delete_snapshot_and_wait(self, snap):
1194 self.shares_v2_client.delete_snapshot(snap['id'])
1195 self.shares_v2_client.wait_for_resource_deletion(
1196 snapshot_id=snap['id']
1197 )
1198 self.assertRaises(exceptions.NotFound,
1199 self.shares_v2_client.get_snapshot,
1200 snap['id'])
1201
1202 def _delete_share_and_wait(self, share):
1203 self.shares_v2_client.delete_share(share['id'])
1204 self.shares_v2_client.wait_for_resource_deletion(share_id=share['id'])
1205 self.assertRaises(exceptions.NotFound,
1206 self.shares_v2_client.get_share,
1207 share['id'])
1208
1209 def _manage_share(self, share, name, description, share_server_id):
1210 managed_share = self.shares_v2_client.manage_share(
1211 service_host=share['host'],
1212 export_path=share['export_locations'][0],
1213 protocol=share['share_proto'],
lkuchlan3af822b2021-06-06 10:35:30 +03001214 share_type_id=self.share_type['id'],
Lucio Seki37056942019-01-24 15:40:20 -02001215 name=name,
1216 description=description,
1217 share_server_id=share_server_id
lkuchlan86f24322021-04-27 14:23:05 +03001218 )['share']
lkuchlanf7fc5b62021-01-26 14:53:43 +02001219 waiters.wait_for_resource_status(
lkuchlan540e74a2021-01-19 18:08:25 +02001220 self.shares_v2_client, managed_share['id'],
1221 constants.STATUS_AVAILABLE
Lucio Seki37056942019-01-24 15:40:20 -02001222 )
1223
1224 return managed_share
1225
1226 def _unmanage_share_server_and_wait(self, server):
1227 self.shares_v2_client.unmanage_share_server(server['id'])
1228 self.shares_v2_client.wait_for_resource_deletion(
1229 server_id=server['id']
1230 )
1231
1232 def _manage_share_server(self, share_server, fields=None):
1233 params = fields or {}
Douglas Viroelb7e27e72019-08-06 19:40:37 -03001234 subnet_id = params.get('share_network_subnet_id', None)
Lucio Seki37056942019-01-24 15:40:20 -02001235 managed_share_server = self.shares_v2_client.manage_share_server(
1236 params.get('host', share_server['host']),
1237 params.get('share_network_id', share_server['share_network_id']),
1238 params.get('identifier', share_server['identifier']),
Douglas Viroelb7e27e72019-08-06 19:40:37 -03001239 share_network_subnet_id=subnet_id,
lkuchlan86f24322021-04-27 14:23:05 +03001240 )['share_server']
lkuchlanf7fc5b62021-01-26 14:53:43 +02001241 waiters.wait_for_resource_status(
lkuchlan540e74a2021-01-19 18:08:25 +02001242 self.shares_v2_client, managed_share_server['id'],
lkuchlanf7fc5b62021-01-26 14:53:43 +02001243 constants.SERVER_STATE_ACTIVE, resource_name='share_server'
Lucio Seki37056942019-01-24 15:40:20 -02001244 )
1245
1246 return managed_share_server
1247
1248 def _delete_share_server_and_wait(self, share_server_id):
1249 self.shares_v2_client.delete_share_server(
1250 share_server_id
1251 )
1252 self.shares_v2_client.wait_for_resource_deletion(
1253 server_id=share_server_id)
1254
lkuchlan3af822b2021-06-06 10:35:30 +03001255 def create_user_message(self):
1256 """Trigger a 'no valid host' situation to generate a message."""
1257 extra_specs = {
1258 'vendor_name': 'foobar',
1259 'driver_handles_share_servers': CONF.share.multitenancy_enabled,
1260 }
1261 share_type_name = data_utils.rand_name("share-type")
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +03001262
lkuchlan3af822b2021-06-06 10:35:30 +03001263 bogus_type = self.create_share_type(
1264 client=self.admin_shares_v2_client,
1265 name=share_type_name,
1266 extra_specs=extra_specs)
1267
1268 params = {'share_type_id': bogus_type['id'],
1269 'share_network_id': self.shares_v2_client.share_network_id}
lkuchlan86f24322021-04-27 14:23:05 +03001270 share = self.shares_v2_client.create_share(**params)['share']
lkuchlan3af822b2021-06-06 10:35:30 +03001271 self.addCleanup(self.shares_v2_client.delete_share, share['id'])
1272 waiters.wait_for_resource_status(
1273 self.shares_v2_client, share['id'], "error")
1274 return waiters.wait_for_message(self.shares_v2_client, share['id'])
1275
1276
1277class BaseSharesMixedTest(BaseSharesAdminTest):
1278 """Base test case class for all Shares API tests with all user roles.
1279
1280 Tests deriving from this class can use the primary project's clients
1281 (self.shares_client, self.shares_v2_client) and the alt project user's
1282 clients (self.alt_shares_client, self.alt_shares_v2_client) to perform
1283 API calls and validations. Although admin clients are available for use,
1284 their use should be limited to performing bootstrapping (e.g., creating
1285 a share type, or resetting state of a resource, etc.). No API validation
1286 must be performed against admin APIs. Use BaseAdminTest as a base class
1287 for such tests.
1288 """
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +03001289 credentials = ('primary', 'alt', 'admin')
Marc Koderer0abc93b2015-07-15 09:18:35 +02001290
Goutham Pacha Ravic678e212020-03-20 11:13:47 -07001291 # Will be cleaned up in resource_cleanup if the class
1292 class_project_users_created = []
1293
1294 @classmethod
1295 def resource_cleanup(cls):
1296 cls.clear_project_users(cls.class_project_users_created)
1297 super(BaseSharesMixedTest, cls).resource_cleanup()
1298
1299 @classmethod
1300 def clear_project_users(cls, users=None):
1301 users = users or cls.class_project_users_created
1302 for user in users:
1303 with handle_cleanup_exceptions():
1304 cls.os_admin.creds_client.delete_user(user['id'])
1305
Marc Koderer0abc93b2015-07-15 09:18:35 +02001306 @classmethod
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +03001307 def setup_clients(cls):
1308 super(BaseSharesMixedTest, cls).setup_clients()
Andrea Frittoli (andreaf)369391a2016-06-27 18:59:13 +01001309 cls.alt_shares_client = cls.os_alt.share_v1.SharesClient()
1310 cls.alt_shares_v2_client = cls.os_alt.share_v2.SharesV2Client()
1311 # Initialise network clients
1312 cls.os_admin.networks_client = cls.os_admin.network.NetworksClient()
1313 cls.os_alt.networks_client = cls.os_alt.network.NetworksClient()
Goutham Pacha Ravic678e212020-03-20 11:13:47 -07001314 # Initialise identity clients
1315 cls.admin_project = cls.os_admin.auth_provider.auth_data[1]['project']
1316 identity_clients = getattr(
1317 cls.os_admin, 'identity_%s' % CONF.identity.auth_version)
hongp5f585fb2024-12-26 17:18:38 +09001318 endpoint_type = CONF.share.endpoint_type
1319 cls.os_admin.identity_client = identity_clients.IdentityClient(
1320 endpoint_type=endpoint_type)
1321 cls.os_admin.projects_client = identity_clients.ProjectsClient(
1322 endpoint_type=endpoint_type)
1323 cls.os_admin.users_client = identity_clients.UsersClient(
1324 endpoint_type=endpoint_type)
1325 cls.os_admin.roles_client = identity_clients.RolesClient(
1326 endpoint_type=endpoint_type)
Goutham Pacha Ravic678e212020-03-20 11:13:47 -07001327 cls.os_admin.domains_client = (
hongp5f585fb2024-12-26 17:18:38 +09001328 cls.os_admin.identity_v3.DomainsClient(
1329 endpoint_type=endpoint_type) if
Goutham Pacha Ravic678e212020-03-20 11:13:47 -07001330 CONF.identity.auth_version == 'v3' else None)
Goutham Pacha Ravi10d49492021-02-23 16:05:21 -08001331 cls.admin_project_member_client = cls.create_user_and_get_client(
1332 project=cls.admin_project, add_member_role=True)
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +03001333
1334 if CONF.share.multitenancy_enabled:
1335 admin_share_network_id = cls.provide_share_network(
1336 cls.admin_shares_v2_client, cls.os_admin.networks_client)
1337 cls.admin_shares_client.share_network_id = admin_share_network_id
1338 cls.admin_shares_v2_client.share_network_id = (
1339 admin_share_network_id)
1340
1341 alt_share_network_id = cls.provide_share_network(
1342 cls.alt_shares_v2_client, cls.os_alt.networks_client)
1343 cls.alt_shares_client.share_network_id = alt_share_network_id
1344 cls.alt_shares_v2_client.share_network_id = alt_share_network_id
Victoria Martinez de la Cruzf6bc6fa2018-02-01 11:27:00 -05001345
1346 @classmethod
Goutham Pacha Ravi10d49492021-02-23 16:05:21 -08001347 def create_user_and_get_client(cls, project=None, add_member_role=True):
Goutham Pacha Ravic678e212020-03-20 11:13:47 -07001348 """Create a user in specified project & set share clients for user
1349
1350 The user will have all roles specified in tempest.conf
1351 :param: project: a dictionary with project ID and name, if not
1352 specified, the value will be cls.admin_project
1353 """
1354 project_domain_name = (
1355 cls.os_admin.identity_client.auth_provider.credentials.get(
1356 'project_domain_name', 'Default'))
1357 cls.os_admin.creds_client = cred_client.get_creds_client(
1358 cls.os_admin.identity_client, cls.os_admin.projects_client,
1359 cls.os_admin.users_client, cls.os_admin.roles_client,
1360 cls.os_admin.domains_client, project_domain_name)
1361
1362 # User info
1363 project = project or cls.admin_project
1364 username = data_utils.rand_name('manila_%s' % project['id'])
1365 password = data_utils.rand_password()
1366 email = '%s@example.org' % username
1367
1368 user = cls.os_admin.creds_client.create_user(
1369 username, password, project, email)
1370 cls.class_project_users_created.append(user)
1371
Goutham Pacha Ravi10d49492021-02-23 16:05:21 -08001372 tempest_roles_to_assign = CONF.auth.tempest_roles or []
1373 if "member" not in tempest_roles_to_assign and add_member_role:
1374 tempest_roles_to_assign.append("member")
1375
1376 for role in tempest_roles_to_assign:
1377 cls.os_admin.creds_client.assign_user_role(user, project, role)
Goutham Pacha Ravic678e212020-03-20 11:13:47 -07001378
1379 user_creds = cls.os_admin.creds_client.get_credentials(
1380 user, project, password)
1381 os = clients.Clients(user_creds)
1382 os.shares_v1_client = os.share_v1.SharesClient()
1383 os.shares_v2_client = os.share_v2.SharesV2Client()
1384 return os