blob: 648c9f8287bd21d4f6c65699109d0d704f22d520 [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
61
62def verify_test_has_appropriate_tags(self):
63 if not TAGS_PATTERN.match(self.id()):
64 msg = (
65 "Required attributes either not set or set improperly. "
66 "Two test attributes are expected:\n"
67 " - one of '%(p)s' or '%(n)s' and \n"
68 " - one of '%(a)s', '%(b)s' or '%(ab)s'."
69 ) % TAGS_MAPPER
70 raise self.failureException(msg)
71
Marc Koderer0abc93b2015-07-15 09:18:35 +020072
73class handle_cleanup_exceptions(object):
74 """Handle exceptions raised with cleanup operations.
75
76 Always suppress errors when exceptions.NotFound or exceptions.Forbidden
77 are raised.
78 Suppress all other exceptions only in case config opt
79 'suppress_errors_in_cleanup' in config group 'share' is True.
80 """
81
82 def __enter__(self):
83 return self
84
85 def __exit__(self, exc_type, exc_value, exc_traceback):
86 if not (isinstance(exc_value,
87 (exceptions.NotFound, exceptions.Forbidden)) or
88 CONF.share.suppress_errors_in_cleanup):
89 return False # Do not suppress error if any
90 if exc_traceback:
91 LOG.error("Suppressed cleanup error in Manila: "
junbolib236c242017-07-18 18:12:37 +080092 "\n%s", traceback.format_exc())
Marc Koderer0abc93b2015-07-15 09:18:35 +020093 return True # Suppress error if any
94
95
Marc Koderer0abc93b2015-07-15 09:18:35 +020096class BaseSharesTest(test.BaseTestCase):
97 """Base test case class for all Manila API tests."""
98
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +030099 credentials = ('primary', )
Marc Koderer0abc93b2015-07-15 09:18:35 +0200100 force_tenant_isolation = False
Vitaliy Levitksicfebfff2016-12-15 16:16:35 +0200101 protocols = ["nfs", "cifs", "glusterfs", "hdfs", "cephfs", "maprfs"]
Marc Koderer0abc93b2015-07-15 09:18:35 +0200102
103 # Will be cleaned up in resource_cleanup
104 class_resources = []
105
106 # Will be cleaned up in tearDown method
107 method_resources = []
108
Andrea Frittoli (andreaf)369391a2016-06-27 18:59:13 +0100109 # NOTE(andreaf) Override the client manager class to be used, so that
110 # a stable class is used, which includes plugin registered services as well
111 client_manager = clients.Clients
112
Marc Koderer0abc93b2015-07-15 09:18:35 +0200113 @classmethod
Daniel Melladoe5269142017-01-12 12:17:58 +0000114 def skip_checks(cls):
115 super(BaseSharesTest, cls).skip_checks()
116 if not CONF.service_available.manila:
117 raise cls.skipException("Manila support is required")
lkuchlana3b6f7a2020-01-07 10:45:45 +0200118 if not any(p in CONF.share.enable_protocols for p in cls.protocols):
119 skip_msg = "%s tests are disabled" % CONF.share.enable_protocols
120 raise cls.skipException(skip_msg)
Daniel Melladoe5269142017-01-12 12:17:58 +0000121
122 @classmethod
Marc Koderer0abc93b2015-07-15 09:18:35 +0200123 def verify_nonempty(cls, *args):
124 if not all(args):
125 msg = "Missing API credentials in configuration."
126 raise cls.skipException(msg)
127
128 @classmethod
Goutham Pacha Ravi4a0b7322020-06-30 19:42:45 -0700129 def setup_credentials(cls):
130 # This call is used to tell the credential allocator to create
131 # network resources for this test case. NOTE: it must go before the
132 # super call, to override decisions in the base classes.
133 network_resources = {}
134 if (CONF.share.multitenancy_enabled and
135 CONF.share.create_networks_when_multitenancy_enabled):
136 # We're testing a DHSS=True driver, and manila is configured with
137 # NeutronNetworkPlugin (or a derivative) that supports creating
138 # share networks with project neutron networks, so lets ask for
139 # neutron network resources to be created with test credentials
140 network_resources.update({'network': True,
141 'subnet': True,
142 'router': True})
143 cls.set_network_resources(**network_resources)
144 super(BaseSharesTest, cls).setup_credentials()
145
146 @classmethod
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +0300147 def setup_clients(cls):
148 super(BaseSharesTest, cls).setup_clients()
149 os = getattr(cls, 'os_%s' % cls.credentials[0])
Andrea Frittoli (andreaf)369391a2016-06-27 18:59:13 +0100150 # Initialise share clients for test credentials
151 cls.shares_client = os.share_v1.SharesClient()
152 cls.shares_v2_client = os.share_v2.SharesV2Client()
153 # Initialise network clients for test credentials
Goutham Pacha Ravi4a0b7322020-06-30 19:42:45 -0700154 cls.networks_client = None
155 cls.subnets_client = None
Andrea Frittoli (andreaf)369391a2016-06-27 18:59:13 +0100156 if CONF.service_available.neutron:
157 cls.networks_client = os.network.NetworksClient()
158 cls.subnets_client = os.network.SubnetsClient()
Valeriy Ponomaryov4fb305f2016-10-21 13:46:47 +0300159
Goutham Pacha Ravi4a0b7322020-06-30 19:42:45 -0700160 # If DHSS=True, create a share network and set it in the client
161 # for easy access.
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +0300162 if CONF.share.multitenancy_enabled:
Valeriy Ponomaryovc5dae272016-06-10 18:29:24 +0300163 if (not CONF.service_available.neutron and
164 CONF.share.create_networks_when_multitenancy_enabled):
Goutham Pacha Ravi4a0b7322020-06-30 19:42:45 -0700165 raise cls.skipException(
166 "Neutron support is required when "
167 "CONF.share.create_networks_when_multitenancy_enabled "
168 "is set to True")
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +0300169 share_network_id = cls.provide_share_network(
Goutham Pacha Ravi4a0b7322020-06-30 19:42:45 -0700170 cls.shares_client, cls.networks_client)
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +0300171 cls.shares_client.share_network_id = share_network_id
172 cls.shares_v2_client.share_network_id = share_network_id
173
Marc Koderer0abc93b2015-07-15 09:18:35 +0200174 def setUp(self):
175 super(BaseSharesTest, self).setUp()
Valeriy Ponomaryovdd162cb2016-01-20 19:09:49 +0200176 self.addCleanup(self.clear_resources)
Valeriy Ponomaryov2abf5d72016-06-01 18:30:12 +0300177 verify_test_has_appropriate_tags(self)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200178
179 @classmethod
180 def resource_cleanup(cls):
Marc Koderer0abc93b2015-07-15 09:18:35 +0200181 cls.clear_resources(cls.class_resources)
Sam Wan241029c2016-07-26 03:37:42 -0400182 super(BaseSharesTest, cls).resource_cleanup()
Marc Koderer0abc93b2015-07-15 09:18:35 +0200183
184 @classmethod
debeltrami1753a592020-05-11 18:27:30 +0000185 def provide_and_associate_security_services(
186 cls, shares_client, share_network_id, cleanup_in_class=True):
187 """Creates a security service and associates to a share network.
188
189 This method creates security services based on the Multiopt
190 defined in tempest configuration named security_service. When this
191 configuration is not provided, the method will return None.
192 After the security service creation, this method also associates
193 the security service to a share network.
194
195 :param shares_client: shares client, which requires the provisioning
196 :param share_network_id: id of the share network to associate the
197 security service
198 :param cleanup_in_class: if the security service and the association
199 will be removed in the method teardown or class teardown
200 :returns: None -- if the security service configuration is not
201 defined
202 """
203
204 ss_configs = CONF.share.security_service
205 if not ss_configs:
206 return
207
208 for ss_config in ss_configs:
209 ss_name = "ss_autogenerated_by_tempest_%s" % (
210 ss_config.get("ss_type"))
211
212 ss_params = {
213 "name": ss_name,
214 "dns_ip": ss_config.get("ss_dns_ip"),
215 "server": ss_config.get("ss_server"),
216 "domain": ss_config.get("ss_domain"),
217 "user": ss_config.get("ss_user"),
218 "password": ss_config.get("ss_password")
219 }
220 ss_type = ss_config.get("ss_type")
221 security_service = cls.create_security_service(
222 ss_type,
223 client=shares_client,
224 cleanup_in_class=cleanup_in_class,
225 **ss_params)
226
227 cls.add_sec_service_to_share_network(
228 shares_client, share_network_id,
229 security_service["id"],
230 cleanup_in_class=cleanup_in_class)
231
232 @classmethod
233 def add_sec_service_to_share_network(
234 cls, client, share_network_id,
235 security_service_id, cleanup_in_class=True):
236 """Associates a security service to a share network.
237
238 This method associates a security service provided by
239 the security service configuration with a specific
240 share network.
241
242 :param share_network_id: the share network id to be
243 associate with a given security service
244 :param security_service_id: the security service id
245 to be associate with a given share network
246 :param cleanup_in_class: if the resources will be
247 dissociate in the method teardown or class teardown
248 """
249
250 client.add_sec_service_to_share_network(
251 share_network_id,
252 security_service_id)
253 resource = {
254 "type": "dissociate_security_service",
255 "id": security_service_id,
256 "extra_params": {
257 "share_network_id": share_network_id
258 },
259 "client": client,
260 }
261
262 if cleanup_in_class:
263 cls.class_resources.insert(0, resource)
264 else:
265 cls.method_resources.insert(0, resource)
266
267 @classmethod
Valeriy Ponomaryov48a2bd72015-11-05 13:22:44 +0200268 def provide_share_network(cls, shares_client, networks_client,
Rodrigo Barbieri58d9de32016-09-06 13:16:47 -0300269 ignore_multitenancy_config=False):
Goutham Pacha Ravi4a0b7322020-06-30 19:42:45 -0700270 """Get or create share network for DHSS=True drivers
Marc Koderer0abc93b2015-07-15 09:18:35 +0200271
Goutham Pacha Ravi4a0b7322020-06-30 19:42:45 -0700272 When testing DHSS=True (multitenancy_enabled) drivers, shares must
273 be requested on share networks.
Marc Koderer0abc93b2015-07-15 09:18:35 +0200274 :returns: str -- share network id for shares_client tenant
Goutham Pacha Ravi4a0b7322020-06-30 19:42:45 -0700275 :returns: None -- if single-tenant driver (DHSS=False) is used
Marc Koderer0abc93b2015-07-15 09:18:35 +0200276 """
277
Rodrigo Barbieri58d9de32016-09-06 13:16:47 -0300278 if (not ignore_multitenancy_config and
279 not CONF.share.multitenancy_enabled):
Goutham Pacha Ravi4a0b7322020-06-30 19:42:45 -0700280 # Assumed usage of a single-tenant driver (DHSS=False)
debeltrami1753a592020-05-11 18:27:30 +0000281 return None
Goutham Pacha Raviaf448262020-06-29 14:24:13 -0700282
Goutham Pacha Ravi4a0b7322020-06-30 19:42:45 -0700283 if shares_client.share_network_id:
284 # Share-network already exists, use it
285 return shares_client.share_network_id
debeltrami1753a592020-05-11 18:27:30 +0000286
Goutham Pacha Ravi4a0b7322020-06-30 19:42:45 -0700287 sn_name = "autogenerated_by_tempest"
288 sn_desc = "This share-network was created by tempest"
Rodrigo Barbieri58d9de32016-09-06 13:16:47 -0300289
Goutham Pacha Ravi4a0b7322020-06-30 19:42:45 -0700290 if not CONF.share.create_networks_when_multitenancy_enabled:
291 # We need a new share network, but don't need to associate
292 # any neutron networks to it - this configuration is used
293 # when manila is configured with "StandaloneNetworkPlugin"
294 # or "NeutronSingleNetworkPlugin" where all tenants share
295 # a single backend network where shares are exported.
296 sn = cls.create_share_network(cleanup_in_class=True,
297 client=shares_client,
298 add_security_services=True,
299 name=sn_name,
300 description=sn_desc)
301 return sn['id']
Marc Koderer0abc93b2015-07-15 09:18:35 +0200302
Goutham Pacha Ravi4a0b7322020-06-30 19:42:45 -0700303 # Retrieve non-public network list owned by the tenant
304 filters = {'project_id': shares_client.tenant_id,
305 'shared': False}
306 tenant_networks = (
307 networks_client.list_networks(**filters).get('networks', [])
308 )
309 tenant_networks_with_subnet = (
310 [n for n in tenant_networks if n['subnets']]
311 )
debeltrami1753a592020-05-11 18:27:30 +0000312
Goutham Pacha Ravi4a0b7322020-06-30 19:42:45 -0700313 if not tenant_networks_with_subnet:
314 # This can only occur if using tempest's pre-provisioned
315 # credentials and not allocating networks to them
316 raise cls.skipException(
317 "Test credentials must provide at least one "
318 "non-shared project network with a valid subnet when "
319 "CONF.share.create_networks_when_multitenancy_enabled is "
320 "set to True.")
debeltrami1753a592020-05-11 18:27:30 +0000321
Goutham Pacha Ravi4a0b7322020-06-30 19:42:45 -0700322 net_id = tenant_networks_with_subnet[0]['id']
323 subnet_id = tenant_networks_with_subnet[0]['subnets'][0]
debeltrami1753a592020-05-11 18:27:30 +0000324
Goutham Pacha Ravi4a0b7322020-06-30 19:42:45 -0700325 # Create suitable share-network
326 sn = cls.create_share_network(cleanup_in_class=True,
327 client=shares_client,
328 add_security_services=True,
329 name=sn_name,
330 description=sn_desc,
331 neutron_net_id=net_id,
332 neutron_subnet_id=subnet_id)
debeltrami1753a592020-05-11 18:27:30 +0000333
Goutham Pacha Ravi4a0b7322020-06-30 19:42:45 -0700334 return sn['id']
Marc Koderer0abc93b2015-07-15 09:18:35 +0200335
336 @classmethod
marcusvrne0d7cfd2016-06-24 12:27:55 -0300337 def _create_share(cls, share_protocol=None, size=None, name=None,
Marc Koderer0abc93b2015-07-15 09:18:35 +0200338 snapshot_id=None, description=None, metadata=None,
339 share_network_id=None, share_type_id=None,
Andrew Kerrb8436922016-06-01 15:32:43 -0400340 share_group_id=None, client=None,
Clinton Knighte5c8f092015-08-27 15:00:23 -0400341 cleanup_in_class=True, is_public=False, **kwargs):
Valeriy Ponomaryov1aaa72d2015-09-08 12:59:41 +0300342 client = client or cls.shares_v2_client
Marc Koderer0abc93b2015-07-15 09:18:35 +0200343 description = description or "Tempest's share"
yogesh06f519f2017-06-26 13:16:12 -0400344 share_network_id = (share_network_id or
345 CONF.share.share_network_id or
346 client.share_network_id or None)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200347 metadata = metadata or {}
marcusvrne0d7cfd2016-06-24 12:27:55 -0300348 size = size or CONF.share.share_size
Clinton Knighte5c8f092015-08-27 15:00:23 -0400349 kwargs.update({
Marc Koderer0abc93b2015-07-15 09:18:35 +0200350 'share_protocol': share_protocol,
351 'size': size,
352 'name': name,
353 'snapshot_id': snapshot_id,
354 'description': description,
355 'metadata': metadata,
356 'share_network_id': share_network_id,
357 'share_type_id': share_type_id,
358 'is_public': is_public,
Clinton Knighte5c8f092015-08-27 15:00:23 -0400359 })
Andrew Kerrb8436922016-06-01 15:32:43 -0400360 if share_group_id:
361 kwargs['share_group_id'] = share_group_id
Andrew Kerrbf31e912015-07-29 10:39:38 -0400362
lkuchlan86f24322021-04-27 14:23:05 +0300363 share = client.create_share(**kwargs)['share']
Andrew Kerrbf31e912015-07-29 10:39:38 -0400364 resource = {"type": "share", "id": share["id"], "client": client,
Andrew Kerrb8436922016-06-01 15:32:43 -0400365 "share_group_id": share_group_id}
Marc Koderer0abc93b2015-07-15 09:18:35 +0200366 cleanup_list = (cls.class_resources if cleanup_in_class else
367 cls.method_resources)
368 cleanup_list.insert(0, resource)
369 return share
370
371 @classmethod
Rodrigo Barbieri427bc052016-06-06 17:10:06 -0300372 def migrate_share(
373 cls, share_id, dest_host, wait_for_status, client=None,
Rodrigo Barbieri027df982016-11-24 15:52:03 -0200374 force_host_assisted_migration=False, writable=False,
375 nondisruptive=False, preserve_metadata=False,
376 preserve_snapshots=False, new_share_network_id=None,
Rodrigo Barbierid38d2f52016-07-19 22:24:56 -0300377 new_share_type_id=None, **kwargs):
Clinton Knighte5c8f092015-08-27 15:00:23 -0400378 client = client or cls.shares_v2_client
Rodrigo Barbieri427bc052016-06-06 17:10:06 -0300379 client.migrate_share(
380 share_id, dest_host,
381 force_host_assisted_migration=force_host_assisted_migration,
Rodrigo Barbieri027df982016-11-24 15:52:03 -0200382 writable=writable, preserve_metadata=preserve_metadata,
383 nondisruptive=nondisruptive, preserve_snapshots=preserve_snapshots,
Rodrigo Barbieri427bc052016-06-06 17:10:06 -0300384 new_share_network_id=new_share_network_id,
Rodrigo Barbierid38d2f52016-07-19 22:24:56 -0300385 new_share_type_id=new_share_type_id, **kwargs)
lkuchlan540e74a2021-01-19 18:08:25 +0200386 share = waiters.wait_for_migration_status(
387 client, share_id, dest_host, wait_for_status, **kwargs)
Rodrigo Barbierie3305122016-02-03 14:32:24 -0200388 return share
389
390 @classmethod
391 def migration_complete(cls, share_id, dest_host, client=None, **kwargs):
392 client = client or cls.shares_v2_client
393 client.migration_complete(share_id, **kwargs)
lkuchlan540e74a2021-01-19 18:08:25 +0200394 share = waiters.wait_for_migration_status(
395 client, share_id, dest_host, 'migration_success', **kwargs)
Rodrigo Barbierib7137ad2015-09-06 22:53:16 -0300396 return share
397
398 @classmethod
Rodrigo Barbieric9abf282016-08-24 22:01:31 -0300399 def migration_cancel(cls, share_id, dest_host, client=None, **kwargs):
400 client = client or cls.shares_v2_client
401 client.migration_cancel(share_id, **kwargs)
lkuchlan540e74a2021-01-19 18:08:25 +0200402 share = waiters.wait_for_migration_status(
403 client, share_id, dest_host, 'migration_cancelled', **kwargs)
Rodrigo Barbieric9abf282016-08-24 22:01:31 -0300404 return share
405
406 @classmethod
Marc Koderer0abc93b2015-07-15 09:18:35 +0200407 def create_share(cls, *args, **kwargs):
408 """Create one share and wait for available state. Retry if allowed."""
409 result = cls.create_shares([{"args": args, "kwargs": kwargs}])
410 return result[0]
411
412 @classmethod
413 def create_shares(cls, share_data_list):
414 """Creates several shares in parallel with retries.
415
416 Use this method when you want to create more than one share at same
417 time. Especially if config option 'share.share_creation_retry_number'
418 has value more than zero (0).
419 All shares will be expected to have 'available' status with or without
420 recreation else error will be raised.
421
422 :param share_data_list: list -- list of dictionaries with 'args' and
423 'kwargs' for '_create_share' method of this base class.
424 example of data:
425 share_data_list=[{'args': ['quuz'], 'kwargs': {'foo': 'bar'}}}]
426 :returns: list -- list of shares created using provided data.
427 """
428
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +0300429 for d in share_data_list:
Marc Koderer0abc93b2015-07-15 09:18:35 +0200430 if not isinstance(d, dict):
431 raise exceptions.TempestException(
432 "Expected 'dict', got '%s'" % type(d))
433 if "args" not in d:
434 d["args"] = []
435 if "kwargs" not in d:
436 d["kwargs"] = {}
437 if len(d) > 2:
438 raise exceptions.TempestException(
439 "Expected only 'args' and 'kwargs' keys. "
440 "Provided %s" % list(d))
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +0300441
442 data = []
443 for d in share_data_list:
444 client = d["kwargs"].pop("client", cls.shares_v2_client)
yogeshdb32f462016-09-28 15:09:50 -0400445 wait_for_status = d["kwargs"].pop("wait_for_status", True)
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +0300446 local_d = {
447 "args": d["args"],
448 "kwargs": copy.deepcopy(d["kwargs"]),
449 }
450 local_d["kwargs"]["client"] = client
451 local_d["share"] = cls._create_share(
452 *local_d["args"], **local_d["kwargs"])
453 local_d["cnt"] = 0
454 local_d["available"] = False
yogeshdb32f462016-09-28 15:09:50 -0400455 local_d["wait_for_status"] = wait_for_status
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +0300456 data.append(local_d)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200457
458 while not all(d["available"] for d in data):
459 for d in data:
yogeshdb32f462016-09-28 15:09:50 -0400460 if not d["wait_for_status"]:
461 d["available"] = True
Marc Koderer0abc93b2015-07-15 09:18:35 +0200462 if d["available"]:
463 continue
Valeriy Ponomaryov1a3e3382016-06-08 15:17:16 +0300464 client = d["kwargs"]["client"]
465 share_id = d["share"]["id"]
Marc Koderer0abc93b2015-07-15 09:18:35 +0200466 try:
lkuchlanf7fc5b62021-01-26 14:53:43 +0200467 waiters.wait_for_resource_status(
lkuchlan540e74a2021-01-19 18:08:25 +0200468 client, share_id, "available")
Marc Koderer0abc93b2015-07-15 09:18:35 +0200469 d["available"] = True
470 except (share_exceptions.ShareBuildErrorException,
471 exceptions.TimeoutException) as e:
472 if CONF.share.share_creation_retry_number > d["cnt"]:
473 d["cnt"] += 1
474 msg = ("Share '%s' failed to be built. "
Valeriy Ponomaryov1a3e3382016-06-08 15:17:16 +0300475 "Trying create another." % share_id)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200476 LOG.error(msg)
477 LOG.error(e)
Valeriy Ponomaryov1a3e3382016-06-08 15:17:16 +0300478 cg_id = d["kwargs"].get("consistency_group_id")
479 if cg_id:
480 # NOTE(vponomaryov): delete errored share
481 # immediately in case share is part of CG.
482 client.delete_share(
483 share_id,
484 params={"consistency_group_id": cg_id})
485 client.wait_for_resource_deletion(
486 share_id=share_id)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200487 d["share"] = cls._create_share(
488 *d["args"], **d["kwargs"])
489 else:
gecong197358663802016-08-25 11:08:45 +0800490 raise
Marc Koderer0abc93b2015-07-15 09:18:35 +0200491
492 return [d["share"] for d in data]
493
494 @classmethod
Andrew Kerrb8436922016-06-01 15:32:43 -0400495 def create_share_group(cls, client=None, cleanup_in_class=True,
496 share_network_id=None, **kwargs):
Clinton Knighte5c8f092015-08-27 15:00:23 -0400497 client = client or cls.shares_v2_client
Andrew Kerrb8436922016-06-01 15:32:43 -0400498 if kwargs.get('source_share_group_snapshot_id') is None:
Goutham Pacha Ravi9221f5e2016-04-21 13:17:49 -0400499 kwargs['share_network_id'] = (share_network_id or
500 client.share_network_id or None)
lkuchlan86f24322021-04-27 14:23:05 +0300501 share_group = client.create_share_group(**kwargs)['share_group']
Andrew Kerrbf31e912015-07-29 10:39:38 -0400502 resource = {
Andrew Kerrb8436922016-06-01 15:32:43 -0400503 "type": "share_group",
504 "id": share_group["id"],
505 "client": client,
506 }
Andrew Kerrbf31e912015-07-29 10:39:38 -0400507 if cleanup_in_class:
508 cls.class_resources.insert(0, resource)
509 else:
510 cls.method_resources.insert(0, resource)
511
Andrew Kerrb8436922016-06-01 15:32:43 -0400512 if kwargs.get('source_share_group_snapshot_id'):
513 new_share_group_shares = client.list_shares(
Andrew Kerrbf31e912015-07-29 10:39:38 -0400514 detailed=True,
lkuchlan86f24322021-04-27 14:23:05 +0300515 params={'share_group_id': share_group['id']})['shares']
Andrew Kerrbf31e912015-07-29 10:39:38 -0400516
Andrew Kerrb8436922016-06-01 15:32:43 -0400517 for share in new_share_group_shares:
Andrew Kerrbf31e912015-07-29 10:39:38 -0400518 resource = {"type": "share",
519 "id": share["id"],
520 "client": client,
Andrew Kerrb8436922016-06-01 15:32:43 -0400521 "share_group_id": share.get("share_group_id")}
Andrew Kerrbf31e912015-07-29 10:39:38 -0400522 if cleanup_in_class:
523 cls.class_resources.insert(0, resource)
524 else:
525 cls.method_resources.insert(0, resource)
526
lkuchlanf7fc5b62021-01-26 14:53:43 +0200527 waiters.wait_for_resource_status(
528 client, share_group['id'], 'available',
529 resource_name='share_group')
Andrew Kerrb8436922016-06-01 15:32:43 -0400530 return share_group
531
532 @classmethod
533 def create_share_group_type(cls, name=None, share_types=(), is_public=None,
534 group_specs=None, client=None,
535 cleanup_in_class=True, **kwargs):
536 client = client or cls.shares_v2_client
Valeriy Ponomaryove92f09f2017-03-16 17:25:47 +0300537 if (group_specs is None and
538 CONF.share.capability_sg_consistent_snapshot_support):
Valeriy Ponomaryov3c188932017-03-15 19:06:23 +0300539 group_specs = {
540 'consistent_snapshot_support': (
541 CONF.share.capability_sg_consistent_snapshot_support),
542 }
Andrew Kerrb8436922016-06-01 15:32:43 -0400543 share_group_type = client.create_share_group_type(
544 name=name,
545 share_types=share_types,
546 is_public=is_public,
547 group_specs=group_specs,
lkuchlan86f24322021-04-27 14:23:05 +0300548 **kwargs)['share_group_type']
Andrew Kerrb8436922016-06-01 15:32:43 -0400549 resource = {
550 "type": "share_group_type",
551 "id": share_group_type["id"],
552 "client": client,
553 }
554 if cleanup_in_class:
555 cls.class_resources.insert(0, resource)
556 else:
557 cls.method_resources.insert(0, resource)
558 return share_group_type
Andrew Kerrbf31e912015-07-29 10:39:38 -0400559
560 @classmethod
Marc Koderer0abc93b2015-07-15 09:18:35 +0200561 def create_snapshot_wait_for_active(cls, share_id, name=None,
562 description=None, force=False,
563 client=None, cleanup_in_class=True):
564 if client is None:
Yogesh1f931ff2015-09-29 23:41:02 -0400565 client = cls.shares_v2_client
Marc Koderer0abc93b2015-07-15 09:18:35 +0200566 if description is None:
567 description = "Tempest's snapshot"
lkuchlan86f24322021-04-27 14:23:05 +0300568 snapshot = client.create_snapshot(
569 share_id, name, description, force)['snapshot']
Marc Koderer0abc93b2015-07-15 09:18:35 +0200570 resource = {
571 "type": "snapshot",
572 "id": snapshot["id"],
573 "client": client,
574 }
575 if cleanup_in_class:
576 cls.class_resources.insert(0, resource)
577 else:
578 cls.method_resources.insert(0, resource)
lkuchlanf7fc5b62021-01-26 14:53:43 +0200579 waiters.wait_for_resource_status(client, snapshot["id"], "available",
580 resource_name='snapshot')
Marc Koderer0abc93b2015-07-15 09:18:35 +0200581 return snapshot
582
583 @classmethod
Andrew Kerrb8436922016-06-01 15:32:43 -0400584 def create_share_group_snapshot_wait_for_active(
585 cls, share_group_id, name=None, description=None, client=None,
586 cleanup_in_class=True, **kwargs):
Clinton Knighte5c8f092015-08-27 15:00:23 -0400587 client = client or cls.shares_v2_client
Andrew Kerrbf31e912015-07-29 10:39:38 -0400588 if description is None:
Andrew Kerrb8436922016-06-01 15:32:43 -0400589 description = "Tempest's share group snapshot"
590 sg_snapshot = client.create_share_group_snapshot(
lkuchlan86f24322021-04-27 14:23:05 +0300591 share_group_id, name=name, description=description,
592 **kwargs)['share_group_snapshot']
Andrew Kerrbf31e912015-07-29 10:39:38 -0400593 resource = {
Andrew Kerrb8436922016-06-01 15:32:43 -0400594 "type": "share_group_snapshot",
595 "id": sg_snapshot["id"],
Andrew Kerrbf31e912015-07-29 10:39:38 -0400596 "client": client,
597 }
598 if cleanup_in_class:
599 cls.class_resources.insert(0, resource)
600 else:
601 cls.method_resources.insert(0, resource)
lkuchlanf7fc5b62021-01-26 14:53:43 +0200602 waiters.wait_for_resource_status(
603 client, sg_snapshot["id"], "available",
604 resource_name="share_group_snapshot")
Andrew Kerrb8436922016-06-01 15:32:43 -0400605 return sg_snapshot
Andrew Kerrbf31e912015-07-29 10:39:38 -0400606
607 @classmethod
Goutham Pacha Ravi839c98b2019-01-14 23:16:23 -0800608 def get_availability_zones(cls, client=None, backends=None):
Yogeshbdb88102015-09-29 23:41:02 -0400609 """List the availability zones for "manila-share" services
610
611 that are currently in "up" state.
612 """
Goutham Pacha Ravi839c98b2019-01-14 23:16:23 -0800613 client = client or cls.admin_shares_v2_client
614 backends = (
615 '|'.join(['^%s$' % backend for backend in backends])
616 if backends else '.*'
617 )
lkuchlan86f24322021-04-27 14:23:05 +0300618 cls.services = client.list_services()['services']
Yogeshbdb88102015-09-29 23:41:02 -0400619 zones = [service['zone'] for service in cls.services if
Goutham Pacha Ravi839c98b2019-01-14 23:16:23 -0800620 service['binary'] == 'manila-share' and
621 service['state'] == 'up' and
622 re.search(backends, service['host'])]
Douglas Viroel161f1802020-04-25 17:18:14 -0300623 return list(set(zones))
Yogeshbdb88102015-09-29 23:41:02 -0400624
Goutham Pacha Ravi839c98b2019-01-14 23:16:23 -0800625 @classmethod
626 def get_pools_matching_share_type(cls, share_type, client=None):
627 client = client or cls.admin_shares_v2_client
628 if utils.is_microversion_supported('2.23'):
629 return client.list_pools(
andrebeltrami3b4d4852020-02-04 19:11:54 +0000630 detail=True,
Goutham Pacha Ravi839c98b2019-01-14 23:16:23 -0800631 search_opts={'share_type': share_type['id']})['pools']
632
633 pools = client.list_pools(detail=True)['pools']
634 share_type = client.get_share_type(share_type['id'])['share_type']
635 extra_specs = {}
636 for k, v in share_type['extra_specs'].items():
637 extra_specs[k] = (
haixin48895812020-09-30 13:50:37 +0800638 True if str(v).lower() == 'true'
639 else False if str(v).lower() == 'false' else v
Goutham Pacha Ravi839c98b2019-01-14 23:16:23 -0800640 )
641 return [
642 pool for pool in pools if all(y in pool['capabilities'].items()
643 for y in extra_specs.items())
644 ]
645
646 @classmethod
647 def get_availability_zones_matching_share_type(cls, share_type,
648 client=None):
649
650 client = client or cls.admin_shares_v2_client
651 pools_matching_share_type = cls.get_pools_matching_share_type(
652 share_type, client=client)
653 backends_matching_share_type = set(
654 [pool['name'].split("#")[0] for pool in pools_matching_share_type]
655 )
656 azs = cls.get_availability_zones(backends=backends_matching_share_type)
657 return azs
658
Yogesh1f931ff2015-09-29 23:41:02 -0400659 def get_pools_for_replication_domain(self):
660 # Get the list of pools for the replication domain
661 pools = self.admin_client.list_pools(detail=True)['pools']
Ben Swartzlander7150c652017-02-13 22:31:18 -0500662 instance_host = self.admin_client.get_share(
lkuchlan86f24322021-04-27 14:23:05 +0300663 self.shares[0]['id'])['share']['host']
Yogesh1f931ff2015-09-29 23:41:02 -0400664 host_pool = [p for p in pools if p['name'] == instance_host][0]
665 rep_domain = host_pool['capabilities']['replication_domain']
666 pools_in_rep_domain = [p for p in pools if p['capabilities'][
667 'replication_domain'] == rep_domain]
668 return rep_domain, pools_in_rep_domain
669
Yogeshbdb88102015-09-29 23:41:02 -0400670 @classmethod
debeltrami0d523bb2020-08-20 12:48:49 +0000671 def create_share_replica(cls, share_id, availability_zone=None,
672 client=None, cleanup_in_class=False,
673 cleanup=True,
silvacarlossd354d672020-08-23 18:49:52 +0000674 version=CONF.share.max_api_microversion):
Yogeshbdb88102015-09-29 23:41:02 -0400675 client = client or cls.shares_v2_client
Douglas Viroelbd4e78c2019-09-02 17:16:30 -0300676 replica = client.create_share_replica(
lkuchlan86f24322021-04-27 14:23:05 +0300677 share_id, availability_zone=availability_zone,
678 version=version)['share_replica']
Yogeshbdb88102015-09-29 23:41:02 -0400679 resource = {
680 "type": "share_replica",
681 "id": replica["id"],
682 "client": client,
683 "share_id": share_id,
684 }
685 # NOTE(Yogi1): Cleanup needs to be disabled during promotion tests.
686 if cleanup:
687 if cleanup_in_class:
688 cls.class_resources.insert(0, resource)
689 else:
690 cls.method_resources.insert(0, resource)
lkuchlanf7fc5b62021-01-26 14:53:43 +0200691 waiters.wait_for_resource_status(
692 client, replica["id"], constants.STATUS_AVAILABLE,
693 resource_name='share_replica')
Yogeshbdb88102015-09-29 23:41:02 -0400694 return replica
695
696 @classmethod
silvacarlossd354d672020-08-23 18:49:52 +0000697 def delete_share_replica(cls, replica_id, client=None,
698 version=CONF.share.max_api_microversion):
Yogeshbdb88102015-09-29 23:41:02 -0400699 client = client or cls.shares_v2_client
Yogesh1f931ff2015-09-29 23:41:02 -0400700 try:
silvacarlossd354d672020-08-23 18:49:52 +0000701 client.delete_share_replica(replica_id, version=version)
Yogesh1f931ff2015-09-29 23:41:02 -0400702 client.wait_for_resource_deletion(replica_id=replica_id)
703 except exceptions.NotFound:
704 pass
Yogeshbdb88102015-09-29 23:41:02 -0400705
706 @classmethod
silvacarlossd354d672020-08-23 18:49:52 +0000707 def promote_share_replica(cls, replica_id, client=None,
708 version=CONF.share.max_api_microversion):
Yogeshbdb88102015-09-29 23:41:02 -0400709 client = client or cls.shares_v2_client
lkuchlan86f24322021-04-27 14:23:05 +0300710 replica = client.promote_share_replica(
711 replica_id, version=version)['share_replica']
lkuchlanf7fc5b62021-01-26 14:53:43 +0200712 waiters.wait_for_resource_status(
lkuchlan540e74a2021-01-19 18:08:25 +0200713 client, replica["id"], constants.REPLICATION_STATE_ACTIVE,
lkuchlanf7fc5b62021-01-26 14:53:43 +0200714 resource_name='share_replica', status_attr="replica_state")
Yogeshbdb88102015-09-29 23:41:02 -0400715 return replica
716
Goutham Pacha Ravi1727f7a2020-05-22 13:41:40 -0700717 @classmethod
718 def _get_access_rule_data_from_config(cls):
yogeshdb32f462016-09-28 15:09:50 -0400719 """Get the first available access type/to combination from config.
720
721 This method opportunistically picks the first configured protocol
722 to create the share. Do not use this method in tests where you need
723 to test depth and breadth in the access types and access recipients.
724 """
Goutham Pacha Ravi1727f7a2020-05-22 13:41:40 -0700725 protocol = cls.shares_v2_client.share_protocol
yogeshdb32f462016-09-28 15:09:50 -0400726
727 if protocol in CONF.share.enable_ip_rules_for_protocols:
728 access_type = "ip"
729 access_to = utils.rand_ip()
730 elif protocol in CONF.share.enable_user_rules_for_protocols:
731 access_type = "user"
732 access_to = CONF.share.username_for_user_rules
733 elif protocol in CONF.share.enable_cert_rules_for_protocols:
734 access_type = "cert"
735 access_to = "client3.com"
736 elif protocol in CONF.share.enable_cephx_rules_for_protocols:
737 access_type = "cephx"
lkuchlan5af7cb42020-07-14 18:05:09 +0300738 access_to = data_utils.rand_name(
739 cls.__class__.__name__ + '-cephx-id')
yogeshdb32f462016-09-28 15:09:50 -0400740 else:
741 message = "Unrecognized protocol and access rules configuration."
Goutham Pacha Ravi1727f7a2020-05-22 13:41:40 -0700742 raise cls.skipException(message)
yogeshdb32f462016-09-28 15:09:50 -0400743
744 return access_type, access_to
745
Yogeshbdb88102015-09-29 23:41:02 -0400746 @classmethod
Marc Koderer0abc93b2015-07-15 09:18:35 +0200747 def create_share_network(cls, client=None,
debeltrami1753a592020-05-11 18:27:30 +0000748 cleanup_in_class=False,
749 add_security_services=True, **kwargs):
750
Marc Koderer0abc93b2015-07-15 09:18:35 +0200751 if client is None:
752 client = cls.shares_client
lkuchlan86f24322021-04-27 14:23:05 +0300753 share_network = client.create_share_network(**kwargs)['share_network']
Marc Koderer0abc93b2015-07-15 09:18:35 +0200754 resource = {
755 "type": "share_network",
756 "id": share_network["id"],
757 "client": client,
758 }
debeltrami1753a592020-05-11 18:27:30 +0000759
Marc Koderer0abc93b2015-07-15 09:18:35 +0200760 if cleanup_in_class:
761 cls.class_resources.insert(0, resource)
762 else:
763 cls.method_resources.insert(0, resource)
debeltrami1753a592020-05-11 18:27:30 +0000764
765 if add_security_services:
766 cls.provide_and_associate_security_services(
767 client, share_network["id"], cleanup_in_class=cleanup_in_class)
768
Marc Koderer0abc93b2015-07-15 09:18:35 +0200769 return share_network
770
771 @classmethod
debeltrami1753a592020-05-11 18:27:30 +0000772 def create_share_network_subnet(cls,
773 client=None,
774 cleanup_in_class=False,
775 **kwargs):
Douglas Viroelb7e27e72019-08-06 19:40:37 -0300776 if client is None:
777 client = cls.shares_v2_client
lkuchlan86f24322021-04-27 14:23:05 +0300778 share_network_subnet = client.create_subnet(
779 **kwargs)['share_network_subnet']
Douglas Viroelb7e27e72019-08-06 19:40:37 -0300780 resource = {
781 "type": "share-network-subnet",
782 "id": share_network_subnet["id"],
783 "extra_params": {
784 "share_network_id": share_network_subnet["share_network_id"]
785 },
786 "client": client,
787 }
788 if cleanup_in_class:
789 cls.class_resources.insert(0, resource)
790 else:
791 cls.method_resources.insert(0, resource)
792 return share_network_subnet
793
794 @classmethod
Marc Koderer0abc93b2015-07-15 09:18:35 +0200795 def create_security_service(cls, ss_type="ldap", client=None,
796 cleanup_in_class=False, **kwargs):
797 if client is None:
798 client = cls.shares_client
lkuchlan86f24322021-04-27 14:23:05 +0300799 security_service = client.create_security_service(
800 ss_type, **kwargs)['security_service']
Marc Koderer0abc93b2015-07-15 09:18:35 +0200801 resource = {
802 "type": "security_service",
803 "id": security_service["id"],
804 "client": client,
805 }
806 if cleanup_in_class:
807 cls.class_resources.insert(0, resource)
808 else:
809 cls.method_resources.insert(0, resource)
810 return security_service
811
812 @classmethod
haixin0d1d29f2019-08-02 16:50:45 +0800813 def update_share_type(cls, share_type_id, name=None,
814 is_public=None, description=None,
815 client=None):
816 if client is None:
817 client = cls.shares_v2_client
818 share_type = client.update_share_type(share_type_id, name,
819 is_public, description)
820 return share_type
821
Goutham Pacha Raviaf448262020-06-29 14:24:13 -0700822 @classmethod
823 def update_quotas(cls, project_id, user_id=None, cleanup=True,
824 client=None, **kwargs):
825 client = client or cls.shares_v2_client
826 updated_quotas = client.update_quotas(project_id,
827 user_id=user_id,
lkuchlan86f24322021-04-27 14:23:05 +0300828 **kwargs)['quota_set']
Goutham Pacha Raviaf448262020-06-29 14:24:13 -0700829 resource = {
830 "type": "quotas",
831 "id": project_id,
832 "client": client,
833 "user_id": user_id,
834 }
835 if cleanup:
836 cls.method_resources.insert(0, resource)
837 return updated_quotas
838
Marc Koderer0abc93b2015-07-15 09:18:35 +0200839 @classmethod
Yogesh1f931ff2015-09-29 23:41:02 -0400840 def clear_share_replicas(cls, share_id, client=None):
841 client = client or cls.shares_v2_client
842 share_replicas = client.list_share_replicas(
lkuchlan86f24322021-04-27 14:23:05 +0300843 share_id=share_id)['share_replicas']
Yogesh1f931ff2015-09-29 23:41:02 -0400844
845 for replica in share_replicas:
846 try:
847 cls.delete_share_replica(replica['id'])
848 except exceptions.BadRequest:
849 # Ignore the exception due to deletion of last active replica
850 pass
851
852 @classmethod
Marc Koderer0abc93b2015-07-15 09:18:35 +0200853 def clear_resources(cls, resources=None):
854 """Deletes resources, that were created in test suites.
855
856 This method tries to remove resources from resource list,
857 if it is not found, assumed it was deleted in test itself.
858 It is expected, that all resources were added as LIFO
859 due to restriction of deletion resources, that is in the chain.
860
861 :param resources: dict with keys 'type','id','client' and 'deleted'
862 """
Marc Koderer0abc93b2015-07-15 09:18:35 +0200863 if resources is None:
864 resources = cls.method_resources
865 for res in resources:
866 if "deleted" not in res.keys():
867 res["deleted"] = False
868 if "client" not in res.keys():
869 res["client"] = cls.shares_client
870 if not(res["deleted"]):
871 res_id = res['id']
872 client = res["client"]
873 with handle_cleanup_exceptions():
Andreas Jaeger0cb685b2020-04-01 13:38:38 +0200874 if res["type"] == "share":
Yogesh1f931ff2015-09-29 23:41:02 -0400875 cls.clear_share_replicas(res_id)
Andrew Kerrb8436922016-06-01 15:32:43 -0400876 share_group_id = res.get('share_group_id')
877 if share_group_id:
878 params = {'share_group_id': share_group_id}
Clinton Knighte5c8f092015-08-27 15:00:23 -0400879 client.delete_share(res_id, params=params)
880 else:
881 client.delete_share(res_id)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200882 client.wait_for_resource_deletion(share_id=res_id)
Andreas Jaeger0cb685b2020-04-01 13:38:38 +0200883 elif res["type"] == "snapshot":
Marc Koderer0abc93b2015-07-15 09:18:35 +0200884 client.delete_snapshot(res_id)
885 client.wait_for_resource_deletion(snapshot_id=res_id)
Andreas Jaeger0cb685b2020-04-01 13:38:38 +0200886 elif (res["type"] == "share_network" and
yogesh06f519f2017-06-26 13:16:12 -0400887 res_id != CONF.share.share_network_id):
Victoria Martinez de la Cruzcad92012018-06-08 14:46:35 -0400888 client.delete_share_network(res_id)
889 client.wait_for_resource_deletion(sn_id=res_id)
debeltrami1753a592020-05-11 18:27:30 +0000890 elif res["type"] == "dissociate_security_service":
891 sn_id = res["extra_params"]["share_network_id"]
892 client.remove_sec_service_from_share_network(
893 sn_id=sn_id, ss_id=res_id
894 )
Andreas Jaeger0cb685b2020-04-01 13:38:38 +0200895 elif res["type"] == "security_service":
Marc Koderer0abc93b2015-07-15 09:18:35 +0200896 client.delete_security_service(res_id)
897 client.wait_for_resource_deletion(ss_id=res_id)
Andreas Jaeger0cb685b2020-04-01 13:38:38 +0200898 elif res["type"] == "share_type":
Marc Koderer0abc93b2015-07-15 09:18:35 +0200899 client.delete_share_type(res_id)
900 client.wait_for_resource_deletion(st_id=res_id)
Andreas Jaeger0cb685b2020-04-01 13:38:38 +0200901 elif res["type"] == "share_group":
Andrew Kerrb8436922016-06-01 15:32:43 -0400902 client.delete_share_group(res_id)
903 client.wait_for_resource_deletion(
904 share_group_id=res_id)
Andreas Jaeger0cb685b2020-04-01 13:38:38 +0200905 elif res["type"] == "share_group_type":
Andrew Kerrb8436922016-06-01 15:32:43 -0400906 client.delete_share_group_type(res_id)
907 client.wait_for_resource_deletion(
908 share_group_type_id=res_id)
Andreas Jaeger0cb685b2020-04-01 13:38:38 +0200909 elif res["type"] == "share_group_snapshot":
Andrew Kerrb8436922016-06-01 15:32:43 -0400910 client.delete_share_group_snapshot(res_id)
911 client.wait_for_resource_deletion(
912 share_group_snapshot_id=res_id)
Andreas Jaeger0cb685b2020-04-01 13:38:38 +0200913 elif res["type"] == "share_replica":
Yogeshbdb88102015-09-29 23:41:02 -0400914 client.delete_share_replica(res_id)
915 client.wait_for_resource_deletion(replica_id=res_id)
Andreas Jaeger0cb685b2020-04-01 13:38:38 +0200916 elif res["type"] == "share_network_subnet":
Douglas Viroelb7e27e72019-08-06 19:40:37 -0300917 sn_id = res["extra_params"]["share_network_id"]
918 client.delete_subnet(sn_id, res_id)
919 client.wait_for_resource_deletion(
920 share_network_subnet_id=res_id,
921 sn_id=sn_id)
Goutham Pacha Raviaf448262020-06-29 14:24:13 -0700922 elif res["type"] == "quotas":
923 user_id = res.get('user_id')
924 client.reset_quotas(res_id, user_id=user_id)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200925 else:
huayue97bacbf2016-01-04 09:57:39 +0800926 LOG.warning("Provided unsupported resource type for "
junbolib236c242017-07-18 18:12:37 +0800927 "cleanup '%s'. Skipping.", res["type"])
Marc Koderer0abc93b2015-07-15 09:18:35 +0200928 res["deleted"] = True
929
930 @classmethod
931 def generate_share_network_data(self):
932 data = {
933 "name": data_utils.rand_name("sn-name"),
934 "description": data_utils.rand_name("sn-desc"),
935 "neutron_net_id": data_utils.rand_name("net-id"),
936 "neutron_subnet_id": data_utils.rand_name("subnet-id"),
937 }
938 return data
939
940 @classmethod
Douglas Viroelb7e27e72019-08-06 19:40:37 -0300941 def generate_subnet_data(self):
942 data = {
943 "neutron_net_id": data_utils.rand_name("net-id"),
944 "neutron_subnet_id": data_utils.rand_name("subnet-id"),
945 }
946 return data
947
948 @classmethod
Maurice Schreiber5ac37172018-02-01 15:17:31 +0100949 def generate_security_service_data(self, set_ou=False):
Marc Koderer0abc93b2015-07-15 09:18:35 +0200950 data = {
951 "name": data_utils.rand_name("ss-name"),
952 "description": data_utils.rand_name("ss-desc"),
Valeriy Ponomaryovfcde7712015-12-14 18:06:13 +0200953 "dns_ip": utils.rand_ip(),
954 "server": utils.rand_ip(),
Marc Koderer0abc93b2015-07-15 09:18:35 +0200955 "domain": data_utils.rand_name("ss-domain"),
956 "user": data_utils.rand_name("ss-user"),
957 "password": data_utils.rand_name("ss-password"),
958 }
Maurice Schreiber5ac37172018-02-01 15:17:31 +0100959 if set_ou:
960 data["ou"] = data_utils.rand_name("ss-ou")
961
Marc Koderer0abc93b2015-07-15 09:18:35 +0200962 return data
963
964 # Useful assertions
965 def assertDictMatch(self, d1, d2, approx_equal=False, tolerance=0.001):
966 """Assert two dicts are equivalent.
967
968 This is a 'deep' match in the sense that it handles nested
969 dictionaries appropriately.
970
971 NOTE:
972
973 If you don't care (or don't know) a given value, you can specify
974 the string DONTCARE as the value. This will cause that dict-item
975 to be skipped.
976
977 """
978 def raise_assertion(msg):
979 d1str = str(d1)
980 d2str = str(d2)
981 base_msg = ('Dictionaries do not match. %(msg)s d1: %(d1str)s '
982 'd2: %(d2str)s' %
983 {"msg": msg, "d1str": d1str, "d2str": d2str})
984 raise AssertionError(base_msg)
985
986 d1keys = set(d1.keys())
987 d2keys = set(d2.keys())
988 if d1keys != d2keys:
989 d1only = d1keys - d2keys
990 d2only = d2keys - d1keys
991 raise_assertion('Keys in d1 and not d2: %(d1only)s. '
992 'Keys in d2 and not d1: %(d2only)s' %
993 {"d1only": d1only, "d2only": d2only})
994
995 for key in d1keys:
996 d1value = d1[key]
997 d2value = d2[key]
998 try:
999 error = abs(float(d1value) - float(d2value))
1000 within_tolerance = error <= tolerance
1001 except (ValueError, TypeError):
daiki kato6914b1a2016-03-16 17:16:57 +09001002 # If both values aren't convertible to float, just ignore
Marc Koderer0abc93b2015-07-15 09:18:35 +02001003 # ValueError if arg is a str, TypeError if it's something else
1004 # (like None)
1005 within_tolerance = False
1006
1007 if hasattr(d1value, 'keys') and hasattr(d2value, 'keys'):
1008 self.assertDictMatch(d1value, d2value)
1009 elif 'DONTCARE' in (d1value, d2value):
1010 continue
1011 elif approx_equal and within_tolerance:
1012 continue
1013 elif d1value != d2value:
1014 raise_assertion("d1['%(key)s']=%(d1value)s != "
1015 "d2['%(key)s']=%(d2value)s" %
1016 {
1017 "key": key,
1018 "d1value": d1value,
1019 "d2value": d2value
1020 })
1021
Alex Meadeba8a1602016-05-06 09:33:09 -04001022 def create_user_message(self):
1023 """Trigger a 'no valid host' situation to generate a message."""
1024 extra_specs = {
1025 'vendor_name': 'foobar',
1026 'driver_handles_share_servers': CONF.share.multitenancy_enabled,
1027 }
1028 share_type_name = data_utils.rand_name("share-type")
1029
1030 bogus_type = self.create_share_type(
Goutham Pacha Raviaf448262020-06-29 14:24:13 -07001031 client=self.admin_shares_v2_client,
Alex Meadeba8a1602016-05-06 09:33:09 -04001032 name=share_type_name,
1033 extra_specs=extra_specs)['share_type']
1034
1035 params = {'share_type_id': bogus_type['id'],
1036 'share_network_id': self.shares_v2_client.share_network_id}
lkuchlan86f24322021-04-27 14:23:05 +03001037 share = self.shares_v2_client.create_share(**params)['share']
Alex Meadeba8a1602016-05-06 09:33:09 -04001038 self.addCleanup(self.shares_v2_client.delete_share, share['id'])
lkuchlanf7fc5b62021-01-26 14:53:43 +02001039 waiters.wait_for_resource_status(
lkuchlan540e74a2021-01-19 18:08:25 +02001040 self.shares_v2_client, share['id'], "error")
1041 return waiters.wait_for_message(self.shares_v2_client, share['id'])
Alex Meadeba8a1602016-05-06 09:33:09 -04001042
lkuchlan5af7cb42020-07-14 18:05:09 +03001043 def allow_access(self, share_id, client=None, access_type=None,
1044 access_level='rw', access_to=None, status='active',
1045 raise_rule_in_error_state=True, cleanup=True):
1046
1047 client = client or self.shares_v2_client
1048 a_type, a_to = self._get_access_rule_data_from_config()
1049 access_type = access_type or a_type
1050 access_to = access_to or a_to
1051
1052 rule = client.create_access_rule(share_id, access_type, access_to,
lkuchlan86f24322021-04-27 14:23:05 +03001053 access_level)['access']
lkuchlanf7fc5b62021-01-26 14:53:43 +02001054 waiters.wait_for_resource_status(
1055 client, share_id, status, resource_name='access_rule',
1056 rule_id=rule['id'],
1057 raise_rule_in_error_state=raise_rule_in_error_state)
lkuchlan5af7cb42020-07-14 18:05:09 +03001058 if cleanup:
1059 self.addCleanup(client.wait_for_resource_deletion,
1060 rule_id=rule['id'], share_id=share_id)
1061 self.addCleanup(client.delete_access_rule, share_id, rule['id'])
1062 return rule
1063
Marc Koderer0abc93b2015-07-15 09:18:35 +02001064
Marc Koderer0abc93b2015-07-15 09:18:35 +02001065class BaseSharesAdminTest(BaseSharesTest):
1066 """Base test case class for all Shares Admin API tests."""
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +03001067 credentials = ('admin', )
1068
Victoria Martinez de la Cruzf6bc6fa2018-02-01 11:27:00 -05001069 @classmethod
1070 def setup_clients(cls):
1071 super(BaseSharesAdminTest, cls).setup_clients()
1072 # Initialise share clients
lkuchlan3af822b2021-06-06 10:35:30 +03001073 cls.admin_shares_client = cls.os_admin.share_v1.SharesClient()
Victoria Martinez de la Cruzf6bc6fa2018-02-01 11:27:00 -05001074 cls.admin_shares_v2_client = cls.os_admin.share_v2.SharesV2Client()
1075
lkuchlan3af822b2021-06-06 10:35:30 +03001076 @staticmethod
1077 def add_extra_specs_to_dict(extra_specs=None):
1078 """Add any required extra-specs to share type dictionary"""
haixin48895812020-09-30 13:50:37 +08001079 dhss = str(CONF.share.multitenancy_enabled)
lkuchlan3af822b2021-06-06 10:35:30 +03001080 extra_specs_dict = {"driver_handles_share_servers": dhss}
1081 if extra_specs:
1082 extra_specs_dict.update(extra_specs)
Felipe Rodrigues6e566772021-08-23 14:57:50 -03001083 if CONF.share.capability_thin_provisioned:
1084 extra_specs_dict['thin_provisioning'] = 'True'
lkuchlan3af822b2021-06-06 10:35:30 +03001085 return extra_specs_dict
1086
Victoria Martinez de la Cruzf6bc6fa2018-02-01 11:27:00 -05001087 @classmethod
lkuchlan3af822b2021-06-06 10:35:30 +03001088 def create_share_type(cls, name=None, is_public=True, client=None,
1089 cleanup_in_class=True, extra_specs=None, **kwargs):
1090 name = name or data_utils.rand_name(
1091 cls.__class__.__name__ + 'share-type')
1092 client = client or cls.admin_shares_v2_client
1093 extra_specs = cls.add_extra_specs_to_dict(extra_specs=extra_specs)
1094 share_type = client.create_share_type(name, is_public,
1095 extra_specs=extra_specs,
1096 **kwargs)['share_type']
1097 resource = {
1098 "type": "share_type",
1099 "id": share_type["id"],
1100 "client": client,
1101 }
1102 if cleanup_in_class:
1103 cls.class_resources.insert(0, resource)
1104 else:
1105 cls.method_resources.insert(0, resource)
1106 return share_type
Victoria Martinez de la Cruzf6bc6fa2018-02-01 11:27:00 -05001107
1108 @classmethod
1109 def _create_share_group_type(cls):
1110 share_group_type_name = data_utils.rand_name("unique_sgtype_name")
1111 return cls.create_share_group_type(
1112 name=share_group_type_name, share_types=[cls.share_type_id],
1113 client=cls.admin_shares_v2_client)
1114
Lucio Seki37056942019-01-24 15:40:20 -02001115 def _create_share_for_manage(self):
1116 creation_data = {
lkuchlan3af822b2021-06-06 10:35:30 +03001117 'share_type_id': self.st['id'],
Lucio Seki37056942019-01-24 15:40:20 -02001118 'share_protocol': self.protocol,
1119 }
1120
1121 share = self.create_share(**creation_data)
lkuchlan86f24322021-04-27 14:23:05 +03001122 share = self.shares_v2_client.get_share(share['id'])['share']
Lucio Seki37056942019-01-24 15:40:20 -02001123
1124 if utils.is_microversion_ge(CONF.share.max_api_microversion, "2.9"):
lkuchlan86f24322021-04-27 14:23:05 +03001125 el = self.shares_v2_client.list_share_export_locations(
1126 share["id"])['export_locations']
Lucio Seki37056942019-01-24 15:40:20 -02001127 share["export_locations"] = el
1128
1129 return share
1130
1131 def _unmanage_share_and_wait(self, share):
1132 self.shares_v2_client.unmanage_share(share['id'])
1133 self.shares_v2_client.wait_for_resource_deletion(share_id=share['id'])
1134
1135 def _reset_state_and_delete_share(self, share):
1136 self.shares_v2_client.reset_state(share['id'])
1137 self._delete_share_and_wait(share)
1138
1139 def _delete_snapshot_and_wait(self, snap):
1140 self.shares_v2_client.delete_snapshot(snap['id'])
1141 self.shares_v2_client.wait_for_resource_deletion(
1142 snapshot_id=snap['id']
1143 )
1144 self.assertRaises(exceptions.NotFound,
1145 self.shares_v2_client.get_snapshot,
1146 snap['id'])
1147
1148 def _delete_share_and_wait(self, share):
1149 self.shares_v2_client.delete_share(share['id'])
1150 self.shares_v2_client.wait_for_resource_deletion(share_id=share['id'])
1151 self.assertRaises(exceptions.NotFound,
1152 self.shares_v2_client.get_share,
1153 share['id'])
1154
1155 def _manage_share(self, share, name, description, share_server_id):
1156 managed_share = self.shares_v2_client.manage_share(
1157 service_host=share['host'],
1158 export_path=share['export_locations'][0],
1159 protocol=share['share_proto'],
lkuchlan3af822b2021-06-06 10:35:30 +03001160 share_type_id=self.share_type['id'],
Lucio Seki37056942019-01-24 15:40:20 -02001161 name=name,
1162 description=description,
1163 share_server_id=share_server_id
lkuchlan86f24322021-04-27 14:23:05 +03001164 )['share']
lkuchlanf7fc5b62021-01-26 14:53:43 +02001165 waiters.wait_for_resource_status(
lkuchlan540e74a2021-01-19 18:08:25 +02001166 self.shares_v2_client, managed_share['id'],
1167 constants.STATUS_AVAILABLE
Lucio Seki37056942019-01-24 15:40:20 -02001168 )
1169
1170 return managed_share
1171
1172 def _unmanage_share_server_and_wait(self, server):
1173 self.shares_v2_client.unmanage_share_server(server['id'])
1174 self.shares_v2_client.wait_for_resource_deletion(
1175 server_id=server['id']
1176 )
1177
1178 def _manage_share_server(self, share_server, fields=None):
1179 params = fields or {}
Douglas Viroelb7e27e72019-08-06 19:40:37 -03001180 subnet_id = params.get('share_network_subnet_id', None)
Lucio Seki37056942019-01-24 15:40:20 -02001181 managed_share_server = self.shares_v2_client.manage_share_server(
1182 params.get('host', share_server['host']),
1183 params.get('share_network_id', share_server['share_network_id']),
1184 params.get('identifier', share_server['identifier']),
Douglas Viroelb7e27e72019-08-06 19:40:37 -03001185 share_network_subnet_id=subnet_id,
lkuchlan86f24322021-04-27 14:23:05 +03001186 )['share_server']
lkuchlanf7fc5b62021-01-26 14:53:43 +02001187 waiters.wait_for_resource_status(
lkuchlan540e74a2021-01-19 18:08:25 +02001188 self.shares_v2_client, managed_share_server['id'],
lkuchlanf7fc5b62021-01-26 14:53:43 +02001189 constants.SERVER_STATE_ACTIVE, resource_name='share_server'
Lucio Seki37056942019-01-24 15:40:20 -02001190 )
1191
1192 return managed_share_server
1193
1194 def _delete_share_server_and_wait(self, share_server_id):
1195 self.shares_v2_client.delete_share_server(
1196 share_server_id
1197 )
1198 self.shares_v2_client.wait_for_resource_deletion(
1199 server_id=share_server_id)
1200
lkuchlan3af822b2021-06-06 10:35:30 +03001201 def create_user_message(self):
1202 """Trigger a 'no valid host' situation to generate a message."""
1203 extra_specs = {
1204 'vendor_name': 'foobar',
1205 'driver_handles_share_servers': CONF.share.multitenancy_enabled,
1206 }
1207 share_type_name = data_utils.rand_name("share-type")
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +03001208
lkuchlan3af822b2021-06-06 10:35:30 +03001209 bogus_type = self.create_share_type(
1210 client=self.admin_shares_v2_client,
1211 name=share_type_name,
1212 extra_specs=extra_specs)
1213
1214 params = {'share_type_id': bogus_type['id'],
1215 'share_network_id': self.shares_v2_client.share_network_id}
lkuchlan86f24322021-04-27 14:23:05 +03001216 share = self.shares_v2_client.create_share(**params)['share']
lkuchlan3af822b2021-06-06 10:35:30 +03001217 self.addCleanup(self.shares_v2_client.delete_share, share['id'])
1218 waiters.wait_for_resource_status(
1219 self.shares_v2_client, share['id'], "error")
1220 return waiters.wait_for_message(self.shares_v2_client, share['id'])
1221
1222
1223class BaseSharesMixedTest(BaseSharesAdminTest):
1224 """Base test case class for all Shares API tests with all user roles.
1225
1226 Tests deriving from this class can use the primary project's clients
1227 (self.shares_client, self.shares_v2_client) and the alt project user's
1228 clients (self.alt_shares_client, self.alt_shares_v2_client) to perform
1229 API calls and validations. Although admin clients are available for use,
1230 their use should be limited to performing bootstrapping (e.g., creating
1231 a share type, or resetting state of a resource, etc.). No API validation
1232 must be performed against admin APIs. Use BaseAdminTest as a base class
1233 for such tests.
1234 """
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +03001235 credentials = ('primary', 'alt', 'admin')
Marc Koderer0abc93b2015-07-15 09:18:35 +02001236
Goutham Pacha Ravic678e212020-03-20 11:13:47 -07001237 # Will be cleaned up in resource_cleanup if the class
1238 class_project_users_created = []
1239
1240 @classmethod
1241 def resource_cleanup(cls):
1242 cls.clear_project_users(cls.class_project_users_created)
1243 super(BaseSharesMixedTest, cls).resource_cleanup()
1244
1245 @classmethod
1246 def clear_project_users(cls, users=None):
1247 users = users or cls.class_project_users_created
1248 for user in users:
1249 with handle_cleanup_exceptions():
1250 cls.os_admin.creds_client.delete_user(user['id'])
1251
Marc Koderer0abc93b2015-07-15 09:18:35 +02001252 @classmethod
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +03001253 def setup_clients(cls):
1254 super(BaseSharesMixedTest, cls).setup_clients()
Andrea Frittoli (andreaf)369391a2016-06-27 18:59:13 +01001255 cls.alt_shares_client = cls.os_alt.share_v1.SharesClient()
1256 cls.alt_shares_v2_client = cls.os_alt.share_v2.SharesV2Client()
1257 # Initialise network clients
1258 cls.os_admin.networks_client = cls.os_admin.network.NetworksClient()
1259 cls.os_alt.networks_client = cls.os_alt.network.NetworksClient()
Goutham Pacha Ravic678e212020-03-20 11:13:47 -07001260 # Initialise identity clients
1261 cls.admin_project = cls.os_admin.auth_provider.auth_data[1]['project']
1262 identity_clients = getattr(
1263 cls.os_admin, 'identity_%s' % CONF.identity.auth_version)
1264 cls.os_admin.identity_client = identity_clients.IdentityClient()
1265 cls.os_admin.projects_client = identity_clients.ProjectsClient()
1266 cls.os_admin.users_client = identity_clients.UsersClient()
1267 cls.os_admin.roles_client = identity_clients.RolesClient()
1268 cls.os_admin.domains_client = (
1269 cls.os_admin.identity_v3.DomainsClient() if
1270 CONF.identity.auth_version == 'v3' else None)
Goutham Pacha Ravi10d49492021-02-23 16:05:21 -08001271 cls.admin_project_member_client = cls.create_user_and_get_client(
1272 project=cls.admin_project, add_member_role=True)
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +03001273
1274 if CONF.share.multitenancy_enabled:
1275 admin_share_network_id = cls.provide_share_network(
1276 cls.admin_shares_v2_client, cls.os_admin.networks_client)
1277 cls.admin_shares_client.share_network_id = admin_share_network_id
1278 cls.admin_shares_v2_client.share_network_id = (
1279 admin_share_network_id)
1280
1281 alt_share_network_id = cls.provide_share_network(
1282 cls.alt_shares_v2_client, cls.os_alt.networks_client)
1283 cls.alt_shares_client.share_network_id = alt_share_network_id
1284 cls.alt_shares_v2_client.share_network_id = alt_share_network_id
Victoria Martinez de la Cruzf6bc6fa2018-02-01 11:27:00 -05001285
1286 @classmethod
Goutham Pacha Ravi10d49492021-02-23 16:05:21 -08001287 def create_user_and_get_client(cls, project=None, add_member_role=True):
Goutham Pacha Ravic678e212020-03-20 11:13:47 -07001288 """Create a user in specified project & set share clients for user
1289
1290 The user will have all roles specified in tempest.conf
1291 :param: project: a dictionary with project ID and name, if not
1292 specified, the value will be cls.admin_project
1293 """
1294 project_domain_name = (
1295 cls.os_admin.identity_client.auth_provider.credentials.get(
1296 'project_domain_name', 'Default'))
1297 cls.os_admin.creds_client = cred_client.get_creds_client(
1298 cls.os_admin.identity_client, cls.os_admin.projects_client,
1299 cls.os_admin.users_client, cls.os_admin.roles_client,
1300 cls.os_admin.domains_client, project_domain_name)
1301
1302 # User info
1303 project = project or cls.admin_project
1304 username = data_utils.rand_name('manila_%s' % project['id'])
1305 password = data_utils.rand_password()
1306 email = '%s@example.org' % username
1307
1308 user = cls.os_admin.creds_client.create_user(
1309 username, password, project, email)
1310 cls.class_project_users_created.append(user)
1311
Goutham Pacha Ravi10d49492021-02-23 16:05:21 -08001312 tempest_roles_to_assign = CONF.auth.tempest_roles or []
1313 if "member" not in tempest_roles_to_assign and add_member_role:
1314 tempest_roles_to_assign.append("member")
1315
1316 for role in tempest_roles_to_assign:
1317 cls.os_admin.creds_client.assign_user_role(user, project, role)
Goutham Pacha Ravic678e212020-03-20 11:13:47 -07001318
1319 user_creds = cls.os_admin.creds_client.get_credentials(
1320 user, project, password)
1321 os = clients.Clients(user_creds)
1322 os.shares_v1_client = os.share_v1.SharesClient()
1323 os.shares_v2_client = os.share_v2.SharesV2Client()
1324 return os