blob: d5cc439ab497515ffe942cb8e458043fd453e89f [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
Kiran Pawar59745062021-11-01 12:37:49 +0000659 def get_pools_for_replication_domain(self, share=None):
Yogesh1f931ff2015-09-29 23:41:02 -0400660 # Get the list of pools for the replication domain
661 pools = self.admin_client.list_pools(detail=True)['pools']
Kiran Pawar59745062021-11-01 12:37:49 +0000662 if share:
663 instance_host = self.admin_client.get_share(
664 share['id'])['share']['host']
665 else:
666 instance_host = self.admin_client.get_share(
667 self.shares[0]['id'])['share']['host']
Yogesh1f931ff2015-09-29 23:41:02 -0400668 host_pool = [p for p in pools if p['name'] == instance_host][0]
669 rep_domain = host_pool['capabilities']['replication_domain']
670 pools_in_rep_domain = [p for p in pools if p['capabilities'][
671 'replication_domain'] == rep_domain]
672 return rep_domain, pools_in_rep_domain
673
Yogeshbdb88102015-09-29 23:41:02 -0400674 @classmethod
debeltrami0d523bb2020-08-20 12:48:49 +0000675 def create_share_replica(cls, share_id, availability_zone=None,
676 client=None, cleanup_in_class=False,
677 cleanup=True,
Kiran Pawar59745062021-11-01 12:37:49 +0000678 version=CONF.share.max_api_microversion,
679 scheduler_hints=None):
Yogeshbdb88102015-09-29 23:41:02 -0400680 client = client or cls.shares_v2_client
Douglas Viroelbd4e78c2019-09-02 17:16:30 -0300681 replica = client.create_share_replica(
lkuchlan86f24322021-04-27 14:23:05 +0300682 share_id, availability_zone=availability_zone,
Kiran Pawar59745062021-11-01 12:37:49 +0000683 version=version, scheduler_hints=scheduler_hints)['share_replica']
Yogeshbdb88102015-09-29 23:41:02 -0400684 resource = {
685 "type": "share_replica",
686 "id": replica["id"],
687 "client": client,
688 "share_id": share_id,
689 }
690 # NOTE(Yogi1): Cleanup needs to be disabled during promotion tests.
691 if cleanup:
692 if cleanup_in_class:
693 cls.class_resources.insert(0, resource)
694 else:
695 cls.method_resources.insert(0, resource)
lkuchlanf7fc5b62021-01-26 14:53:43 +0200696 waiters.wait_for_resource_status(
697 client, replica["id"], constants.STATUS_AVAILABLE,
698 resource_name='share_replica')
Yogeshbdb88102015-09-29 23:41:02 -0400699 return replica
700
701 @classmethod
silvacarlossd354d672020-08-23 18:49:52 +0000702 def delete_share_replica(cls, replica_id, client=None,
703 version=CONF.share.max_api_microversion):
Yogeshbdb88102015-09-29 23:41:02 -0400704 client = client or cls.shares_v2_client
Yogesh1f931ff2015-09-29 23:41:02 -0400705 try:
silvacarlossd354d672020-08-23 18:49:52 +0000706 client.delete_share_replica(replica_id, version=version)
Yogesh1f931ff2015-09-29 23:41:02 -0400707 client.wait_for_resource_deletion(replica_id=replica_id)
708 except exceptions.NotFound:
709 pass
Yogeshbdb88102015-09-29 23:41:02 -0400710
711 @classmethod
silvacarlossd354d672020-08-23 18:49:52 +0000712 def promote_share_replica(cls, replica_id, client=None,
713 version=CONF.share.max_api_microversion):
Yogeshbdb88102015-09-29 23:41:02 -0400714 client = client or cls.shares_v2_client
lkuchlan86f24322021-04-27 14:23:05 +0300715 replica = client.promote_share_replica(
716 replica_id, version=version)['share_replica']
lkuchlanf7fc5b62021-01-26 14:53:43 +0200717 waiters.wait_for_resource_status(
lkuchlan540e74a2021-01-19 18:08:25 +0200718 client, replica["id"], constants.REPLICATION_STATE_ACTIVE,
lkuchlanf7fc5b62021-01-26 14:53:43 +0200719 resource_name='share_replica', status_attr="replica_state")
Yogeshbdb88102015-09-29 23:41:02 -0400720 return replica
721
Goutham Pacha Ravi1727f7a2020-05-22 13:41:40 -0700722 @classmethod
723 def _get_access_rule_data_from_config(cls):
yogeshdb32f462016-09-28 15:09:50 -0400724 """Get the first available access type/to combination from config.
725
726 This method opportunistically picks the first configured protocol
727 to create the share. Do not use this method in tests where you need
728 to test depth and breadth in the access types and access recipients.
729 """
Goutham Pacha Ravi1727f7a2020-05-22 13:41:40 -0700730 protocol = cls.shares_v2_client.share_protocol
yogeshdb32f462016-09-28 15:09:50 -0400731
732 if protocol in CONF.share.enable_ip_rules_for_protocols:
733 access_type = "ip"
734 access_to = utils.rand_ip()
735 elif protocol in CONF.share.enable_user_rules_for_protocols:
736 access_type = "user"
737 access_to = CONF.share.username_for_user_rules
738 elif protocol in CONF.share.enable_cert_rules_for_protocols:
739 access_type = "cert"
740 access_to = "client3.com"
741 elif protocol in CONF.share.enable_cephx_rules_for_protocols:
742 access_type = "cephx"
lkuchlan5af7cb42020-07-14 18:05:09 +0300743 access_to = data_utils.rand_name(
744 cls.__class__.__name__ + '-cephx-id')
yogeshdb32f462016-09-28 15:09:50 -0400745 else:
746 message = "Unrecognized protocol and access rules configuration."
Goutham Pacha Ravi1727f7a2020-05-22 13:41:40 -0700747 raise cls.skipException(message)
yogeshdb32f462016-09-28 15:09:50 -0400748
749 return access_type, access_to
750
Yogeshbdb88102015-09-29 23:41:02 -0400751 @classmethod
Marc Koderer0abc93b2015-07-15 09:18:35 +0200752 def create_share_network(cls, client=None,
debeltrami1753a592020-05-11 18:27:30 +0000753 cleanup_in_class=False,
754 add_security_services=True, **kwargs):
755
Marc Koderer0abc93b2015-07-15 09:18:35 +0200756 if client is None:
757 client = cls.shares_client
lkuchlan86f24322021-04-27 14:23:05 +0300758 share_network = client.create_share_network(**kwargs)['share_network']
Marc Koderer0abc93b2015-07-15 09:18:35 +0200759 resource = {
760 "type": "share_network",
761 "id": share_network["id"],
762 "client": client,
763 }
debeltrami1753a592020-05-11 18:27:30 +0000764
Marc Koderer0abc93b2015-07-15 09:18:35 +0200765 if cleanup_in_class:
766 cls.class_resources.insert(0, resource)
767 else:
768 cls.method_resources.insert(0, resource)
debeltrami1753a592020-05-11 18:27:30 +0000769
770 if add_security_services:
771 cls.provide_and_associate_security_services(
772 client, share_network["id"], cleanup_in_class=cleanup_in_class)
773
Marc Koderer0abc93b2015-07-15 09:18:35 +0200774 return share_network
775
776 @classmethod
debeltrami1753a592020-05-11 18:27:30 +0000777 def create_share_network_subnet(cls,
778 client=None,
779 cleanup_in_class=False,
780 **kwargs):
Douglas Viroelb7e27e72019-08-06 19:40:37 -0300781 if client is None:
782 client = cls.shares_v2_client
lkuchlan86f24322021-04-27 14:23:05 +0300783 share_network_subnet = client.create_subnet(
784 **kwargs)['share_network_subnet']
Douglas Viroelb7e27e72019-08-06 19:40:37 -0300785 resource = {
786 "type": "share-network-subnet",
787 "id": share_network_subnet["id"],
788 "extra_params": {
789 "share_network_id": share_network_subnet["share_network_id"]
790 },
791 "client": client,
792 }
793 if cleanup_in_class:
794 cls.class_resources.insert(0, resource)
795 else:
796 cls.method_resources.insert(0, resource)
797 return share_network_subnet
798
799 @classmethod
Marc Koderer0abc93b2015-07-15 09:18:35 +0200800 def create_security_service(cls, ss_type="ldap", client=None,
801 cleanup_in_class=False, **kwargs):
802 if client is None:
803 client = cls.shares_client
lkuchlan86f24322021-04-27 14:23:05 +0300804 security_service = client.create_security_service(
805 ss_type, **kwargs)['security_service']
Marc Koderer0abc93b2015-07-15 09:18:35 +0200806 resource = {
807 "type": "security_service",
808 "id": security_service["id"],
809 "client": client,
810 }
811 if cleanup_in_class:
812 cls.class_resources.insert(0, resource)
813 else:
814 cls.method_resources.insert(0, resource)
815 return security_service
816
817 @classmethod
haixin0d1d29f2019-08-02 16:50:45 +0800818 def update_share_type(cls, share_type_id, name=None,
819 is_public=None, description=None,
820 client=None):
821 if client is None:
822 client = cls.shares_v2_client
823 share_type = client.update_share_type(share_type_id, name,
824 is_public, description)
825 return share_type
826
Goutham Pacha Raviaf448262020-06-29 14:24:13 -0700827 @classmethod
828 def update_quotas(cls, project_id, user_id=None, cleanup=True,
829 client=None, **kwargs):
830 client = client or cls.shares_v2_client
831 updated_quotas = client.update_quotas(project_id,
832 user_id=user_id,
lkuchlan86f24322021-04-27 14:23:05 +0300833 **kwargs)['quota_set']
Goutham Pacha Raviaf448262020-06-29 14:24:13 -0700834 resource = {
835 "type": "quotas",
836 "id": project_id,
837 "client": client,
838 "user_id": user_id,
839 }
840 if cleanup:
841 cls.method_resources.insert(0, resource)
842 return updated_quotas
843
Marc Koderer0abc93b2015-07-15 09:18:35 +0200844 @classmethod
Yogesh1f931ff2015-09-29 23:41:02 -0400845 def clear_share_replicas(cls, share_id, client=None):
846 client = client or cls.shares_v2_client
847 share_replicas = client.list_share_replicas(
lkuchlan86f24322021-04-27 14:23:05 +0300848 share_id=share_id)['share_replicas']
Yogesh1f931ff2015-09-29 23:41:02 -0400849
850 for replica in share_replicas:
851 try:
852 cls.delete_share_replica(replica['id'])
853 except exceptions.BadRequest:
854 # Ignore the exception due to deletion of last active replica
855 pass
856
857 @classmethod
Marc Koderer0abc93b2015-07-15 09:18:35 +0200858 def clear_resources(cls, resources=None):
859 """Deletes resources, that were created in test suites.
860
861 This method tries to remove resources from resource list,
862 if it is not found, assumed it was deleted in test itself.
863 It is expected, that all resources were added as LIFO
864 due to restriction of deletion resources, that is in the chain.
865
866 :param resources: dict with keys 'type','id','client' and 'deleted'
867 """
Marc Koderer0abc93b2015-07-15 09:18:35 +0200868 if resources is None:
869 resources = cls.method_resources
870 for res in resources:
871 if "deleted" not in res.keys():
872 res["deleted"] = False
873 if "client" not in res.keys():
874 res["client"] = cls.shares_client
875 if not(res["deleted"]):
876 res_id = res['id']
877 client = res["client"]
878 with handle_cleanup_exceptions():
Andreas Jaeger0cb685b2020-04-01 13:38:38 +0200879 if res["type"] == "share":
Yogesh1f931ff2015-09-29 23:41:02 -0400880 cls.clear_share_replicas(res_id)
Andrew Kerrb8436922016-06-01 15:32:43 -0400881 share_group_id = res.get('share_group_id')
882 if share_group_id:
883 params = {'share_group_id': share_group_id}
Clinton Knighte5c8f092015-08-27 15:00:23 -0400884 client.delete_share(res_id, params=params)
885 else:
886 client.delete_share(res_id)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200887 client.wait_for_resource_deletion(share_id=res_id)
Andreas Jaeger0cb685b2020-04-01 13:38:38 +0200888 elif res["type"] == "snapshot":
Marc Koderer0abc93b2015-07-15 09:18:35 +0200889 client.delete_snapshot(res_id)
890 client.wait_for_resource_deletion(snapshot_id=res_id)
Andreas Jaeger0cb685b2020-04-01 13:38:38 +0200891 elif (res["type"] == "share_network" and
yogesh06f519f2017-06-26 13:16:12 -0400892 res_id != CONF.share.share_network_id):
Victoria Martinez de la Cruzcad92012018-06-08 14:46:35 -0400893 client.delete_share_network(res_id)
894 client.wait_for_resource_deletion(sn_id=res_id)
debeltrami1753a592020-05-11 18:27:30 +0000895 elif res["type"] == "dissociate_security_service":
896 sn_id = res["extra_params"]["share_network_id"]
897 client.remove_sec_service_from_share_network(
898 sn_id=sn_id, ss_id=res_id
899 )
Andreas Jaeger0cb685b2020-04-01 13:38:38 +0200900 elif res["type"] == "security_service":
Marc Koderer0abc93b2015-07-15 09:18:35 +0200901 client.delete_security_service(res_id)
902 client.wait_for_resource_deletion(ss_id=res_id)
Andreas Jaeger0cb685b2020-04-01 13:38:38 +0200903 elif res["type"] == "share_type":
Marc Koderer0abc93b2015-07-15 09:18:35 +0200904 client.delete_share_type(res_id)
905 client.wait_for_resource_deletion(st_id=res_id)
Andreas Jaeger0cb685b2020-04-01 13:38:38 +0200906 elif res["type"] == "share_group":
Andrew Kerrb8436922016-06-01 15:32:43 -0400907 client.delete_share_group(res_id)
908 client.wait_for_resource_deletion(
909 share_group_id=res_id)
Andreas Jaeger0cb685b2020-04-01 13:38:38 +0200910 elif res["type"] == "share_group_type":
Andrew Kerrb8436922016-06-01 15:32:43 -0400911 client.delete_share_group_type(res_id)
912 client.wait_for_resource_deletion(
913 share_group_type_id=res_id)
Andreas Jaeger0cb685b2020-04-01 13:38:38 +0200914 elif res["type"] == "share_group_snapshot":
Andrew Kerrb8436922016-06-01 15:32:43 -0400915 client.delete_share_group_snapshot(res_id)
916 client.wait_for_resource_deletion(
917 share_group_snapshot_id=res_id)
Andreas Jaeger0cb685b2020-04-01 13:38:38 +0200918 elif res["type"] == "share_replica":
Yogeshbdb88102015-09-29 23:41:02 -0400919 client.delete_share_replica(res_id)
920 client.wait_for_resource_deletion(replica_id=res_id)
Andreas Jaeger0cb685b2020-04-01 13:38:38 +0200921 elif res["type"] == "share_network_subnet":
Douglas Viroelb7e27e72019-08-06 19:40:37 -0300922 sn_id = res["extra_params"]["share_network_id"]
923 client.delete_subnet(sn_id, res_id)
924 client.wait_for_resource_deletion(
925 share_network_subnet_id=res_id,
926 sn_id=sn_id)
Goutham Pacha Raviaf448262020-06-29 14:24:13 -0700927 elif res["type"] == "quotas":
928 user_id = res.get('user_id')
929 client.reset_quotas(res_id, user_id=user_id)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200930 else:
huayue97bacbf2016-01-04 09:57:39 +0800931 LOG.warning("Provided unsupported resource type for "
junbolib236c242017-07-18 18:12:37 +0800932 "cleanup '%s'. Skipping.", res["type"])
Marc Koderer0abc93b2015-07-15 09:18:35 +0200933 res["deleted"] = True
934
935 @classmethod
936 def generate_share_network_data(self):
937 data = {
938 "name": data_utils.rand_name("sn-name"),
939 "description": data_utils.rand_name("sn-desc"),
940 "neutron_net_id": data_utils.rand_name("net-id"),
941 "neutron_subnet_id": data_utils.rand_name("subnet-id"),
942 }
943 return data
944
945 @classmethod
Douglas Viroelb7e27e72019-08-06 19:40:37 -0300946 def generate_subnet_data(self):
947 data = {
948 "neutron_net_id": data_utils.rand_name("net-id"),
949 "neutron_subnet_id": data_utils.rand_name("subnet-id"),
950 }
951 return data
952
953 @classmethod
Maurice Schreiber5ac37172018-02-01 15:17:31 +0100954 def generate_security_service_data(self, set_ou=False):
Marc Koderer0abc93b2015-07-15 09:18:35 +0200955 data = {
956 "name": data_utils.rand_name("ss-name"),
957 "description": data_utils.rand_name("ss-desc"),
Valeriy Ponomaryovfcde7712015-12-14 18:06:13 +0200958 "dns_ip": utils.rand_ip(),
959 "server": utils.rand_ip(),
Marc Koderer0abc93b2015-07-15 09:18:35 +0200960 "domain": data_utils.rand_name("ss-domain"),
961 "user": data_utils.rand_name("ss-user"),
962 "password": data_utils.rand_name("ss-password"),
963 }
Maurice Schreiber5ac37172018-02-01 15:17:31 +0100964 if set_ou:
965 data["ou"] = data_utils.rand_name("ss-ou")
966
Marc Koderer0abc93b2015-07-15 09:18:35 +0200967 return data
968
969 # Useful assertions
970 def assertDictMatch(self, d1, d2, approx_equal=False, tolerance=0.001):
971 """Assert two dicts are equivalent.
972
973 This is a 'deep' match in the sense that it handles nested
974 dictionaries appropriately.
975
976 NOTE:
977
978 If you don't care (or don't know) a given value, you can specify
979 the string DONTCARE as the value. This will cause that dict-item
980 to be skipped.
981
982 """
983 def raise_assertion(msg):
984 d1str = str(d1)
985 d2str = str(d2)
986 base_msg = ('Dictionaries do not match. %(msg)s d1: %(d1str)s '
987 'd2: %(d2str)s' %
988 {"msg": msg, "d1str": d1str, "d2str": d2str})
989 raise AssertionError(base_msg)
990
991 d1keys = set(d1.keys())
992 d2keys = set(d2.keys())
993 if d1keys != d2keys:
994 d1only = d1keys - d2keys
995 d2only = d2keys - d1keys
996 raise_assertion('Keys in d1 and not d2: %(d1only)s. '
997 'Keys in d2 and not d1: %(d2only)s' %
998 {"d1only": d1only, "d2only": d2only})
999
1000 for key in d1keys:
1001 d1value = d1[key]
1002 d2value = d2[key]
1003 try:
1004 error = abs(float(d1value) - float(d2value))
1005 within_tolerance = error <= tolerance
1006 except (ValueError, TypeError):
daiki kato6914b1a2016-03-16 17:16:57 +09001007 # If both values aren't convertible to float, just ignore
Marc Koderer0abc93b2015-07-15 09:18:35 +02001008 # ValueError if arg is a str, TypeError if it's something else
1009 # (like None)
1010 within_tolerance = False
1011
1012 if hasattr(d1value, 'keys') and hasattr(d2value, 'keys'):
1013 self.assertDictMatch(d1value, d2value)
1014 elif 'DONTCARE' in (d1value, d2value):
1015 continue
1016 elif approx_equal and within_tolerance:
1017 continue
1018 elif d1value != d2value:
1019 raise_assertion("d1['%(key)s']=%(d1value)s != "
1020 "d2['%(key)s']=%(d2value)s" %
1021 {
1022 "key": key,
1023 "d1value": d1value,
1024 "d2value": d2value
1025 })
1026
Alex Meadeba8a1602016-05-06 09:33:09 -04001027 def create_user_message(self):
1028 """Trigger a 'no valid host' situation to generate a message."""
1029 extra_specs = {
1030 'vendor_name': 'foobar',
1031 'driver_handles_share_servers': CONF.share.multitenancy_enabled,
1032 }
1033 share_type_name = data_utils.rand_name("share-type")
1034
1035 bogus_type = self.create_share_type(
Goutham Pacha Raviaf448262020-06-29 14:24:13 -07001036 client=self.admin_shares_v2_client,
Alex Meadeba8a1602016-05-06 09:33:09 -04001037 name=share_type_name,
1038 extra_specs=extra_specs)['share_type']
1039
1040 params = {'share_type_id': bogus_type['id'],
1041 'share_network_id': self.shares_v2_client.share_network_id}
lkuchlan86f24322021-04-27 14:23:05 +03001042 share = self.shares_v2_client.create_share(**params)['share']
Alex Meadeba8a1602016-05-06 09:33:09 -04001043 self.addCleanup(self.shares_v2_client.delete_share, share['id'])
lkuchlanf7fc5b62021-01-26 14:53:43 +02001044 waiters.wait_for_resource_status(
lkuchlan540e74a2021-01-19 18:08:25 +02001045 self.shares_v2_client, share['id'], "error")
1046 return waiters.wait_for_message(self.shares_v2_client, share['id'])
Alex Meadeba8a1602016-05-06 09:33:09 -04001047
lkuchlan5af7cb42020-07-14 18:05:09 +03001048 def allow_access(self, share_id, client=None, access_type=None,
1049 access_level='rw', access_to=None, status='active',
1050 raise_rule_in_error_state=True, cleanup=True):
1051
1052 client = client or self.shares_v2_client
1053 a_type, a_to = self._get_access_rule_data_from_config()
1054 access_type = access_type or a_type
1055 access_to = access_to or a_to
1056
1057 rule = client.create_access_rule(share_id, access_type, access_to,
lkuchlan86f24322021-04-27 14:23:05 +03001058 access_level)['access']
lkuchlanf7fc5b62021-01-26 14:53:43 +02001059 waiters.wait_for_resource_status(
1060 client, share_id, status, resource_name='access_rule',
1061 rule_id=rule['id'],
1062 raise_rule_in_error_state=raise_rule_in_error_state)
lkuchlan5af7cb42020-07-14 18:05:09 +03001063 if cleanup:
1064 self.addCleanup(client.wait_for_resource_deletion,
1065 rule_id=rule['id'], share_id=share_id)
1066 self.addCleanup(client.delete_access_rule, share_id, rule['id'])
1067 return rule
1068
Marc Koderer0abc93b2015-07-15 09:18:35 +02001069
Marc Koderer0abc93b2015-07-15 09:18:35 +02001070class BaseSharesAdminTest(BaseSharesTest):
1071 """Base test case class for all Shares Admin API tests."""
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +03001072 credentials = ('admin', )
1073
Victoria Martinez de la Cruzf6bc6fa2018-02-01 11:27:00 -05001074 @classmethod
1075 def setup_clients(cls):
1076 super(BaseSharesAdminTest, cls).setup_clients()
1077 # Initialise share clients
lkuchlan3af822b2021-06-06 10:35:30 +03001078 cls.admin_shares_client = cls.os_admin.share_v1.SharesClient()
Victoria Martinez de la Cruzf6bc6fa2018-02-01 11:27:00 -05001079 cls.admin_shares_v2_client = cls.os_admin.share_v2.SharesV2Client()
1080
lkuchlan3af822b2021-06-06 10:35:30 +03001081 @staticmethod
1082 def add_extra_specs_to_dict(extra_specs=None):
1083 """Add any required extra-specs to share type dictionary"""
haixin48895812020-09-30 13:50:37 +08001084 dhss = str(CONF.share.multitenancy_enabled)
lkuchlan3af822b2021-06-06 10:35:30 +03001085 extra_specs_dict = {"driver_handles_share_servers": dhss}
1086 if extra_specs:
1087 extra_specs_dict.update(extra_specs)
Felipe Rodrigues6e566772021-08-23 14:57:50 -03001088 if CONF.share.capability_thin_provisioned:
1089 extra_specs_dict['thin_provisioning'] = 'True'
lkuchlan3af822b2021-06-06 10:35:30 +03001090 return extra_specs_dict
1091
Victoria Martinez de la Cruzf6bc6fa2018-02-01 11:27:00 -05001092 @classmethod
lkuchlan3af822b2021-06-06 10:35:30 +03001093 def create_share_type(cls, name=None, is_public=True, client=None,
1094 cleanup_in_class=True, extra_specs=None, **kwargs):
1095 name = name or data_utils.rand_name(
1096 cls.__class__.__name__ + 'share-type')
1097 client = client or cls.admin_shares_v2_client
1098 extra_specs = cls.add_extra_specs_to_dict(extra_specs=extra_specs)
1099 share_type = client.create_share_type(name, is_public,
1100 extra_specs=extra_specs,
1101 **kwargs)['share_type']
1102 resource = {
1103 "type": "share_type",
1104 "id": share_type["id"],
1105 "client": client,
1106 }
1107 if cleanup_in_class:
1108 cls.class_resources.insert(0, resource)
1109 else:
1110 cls.method_resources.insert(0, resource)
1111 return share_type
Victoria Martinez de la Cruzf6bc6fa2018-02-01 11:27:00 -05001112
1113 @classmethod
1114 def _create_share_group_type(cls):
1115 share_group_type_name = data_utils.rand_name("unique_sgtype_name")
1116 return cls.create_share_group_type(
1117 name=share_group_type_name, share_types=[cls.share_type_id],
1118 client=cls.admin_shares_v2_client)
1119
Lucio Seki37056942019-01-24 15:40:20 -02001120 def _create_share_for_manage(self):
1121 creation_data = {
lkuchlan3af822b2021-06-06 10:35:30 +03001122 'share_type_id': self.st['id'],
Lucio Seki37056942019-01-24 15:40:20 -02001123 'share_protocol': self.protocol,
1124 }
1125
1126 share = self.create_share(**creation_data)
lkuchlan86f24322021-04-27 14:23:05 +03001127 share = self.shares_v2_client.get_share(share['id'])['share']
Lucio Seki37056942019-01-24 15:40:20 -02001128
1129 if utils.is_microversion_ge(CONF.share.max_api_microversion, "2.9"):
lkuchlan86f24322021-04-27 14:23:05 +03001130 el = self.shares_v2_client.list_share_export_locations(
1131 share["id"])['export_locations']
Lucio Seki37056942019-01-24 15:40:20 -02001132 share["export_locations"] = el
1133
1134 return share
1135
1136 def _unmanage_share_and_wait(self, share):
1137 self.shares_v2_client.unmanage_share(share['id'])
1138 self.shares_v2_client.wait_for_resource_deletion(share_id=share['id'])
1139
1140 def _reset_state_and_delete_share(self, share):
1141 self.shares_v2_client.reset_state(share['id'])
1142 self._delete_share_and_wait(share)
1143
1144 def _delete_snapshot_and_wait(self, snap):
1145 self.shares_v2_client.delete_snapshot(snap['id'])
1146 self.shares_v2_client.wait_for_resource_deletion(
1147 snapshot_id=snap['id']
1148 )
1149 self.assertRaises(exceptions.NotFound,
1150 self.shares_v2_client.get_snapshot,
1151 snap['id'])
1152
1153 def _delete_share_and_wait(self, share):
1154 self.shares_v2_client.delete_share(share['id'])
1155 self.shares_v2_client.wait_for_resource_deletion(share_id=share['id'])
1156 self.assertRaises(exceptions.NotFound,
1157 self.shares_v2_client.get_share,
1158 share['id'])
1159
1160 def _manage_share(self, share, name, description, share_server_id):
1161 managed_share = self.shares_v2_client.manage_share(
1162 service_host=share['host'],
1163 export_path=share['export_locations'][0],
1164 protocol=share['share_proto'],
lkuchlan3af822b2021-06-06 10:35:30 +03001165 share_type_id=self.share_type['id'],
Lucio Seki37056942019-01-24 15:40:20 -02001166 name=name,
1167 description=description,
1168 share_server_id=share_server_id
lkuchlan86f24322021-04-27 14:23:05 +03001169 )['share']
lkuchlanf7fc5b62021-01-26 14:53:43 +02001170 waiters.wait_for_resource_status(
lkuchlan540e74a2021-01-19 18:08:25 +02001171 self.shares_v2_client, managed_share['id'],
1172 constants.STATUS_AVAILABLE
Lucio Seki37056942019-01-24 15:40:20 -02001173 )
1174
1175 return managed_share
1176
1177 def _unmanage_share_server_and_wait(self, server):
1178 self.shares_v2_client.unmanage_share_server(server['id'])
1179 self.shares_v2_client.wait_for_resource_deletion(
1180 server_id=server['id']
1181 )
1182
1183 def _manage_share_server(self, share_server, fields=None):
1184 params = fields or {}
Douglas Viroelb7e27e72019-08-06 19:40:37 -03001185 subnet_id = params.get('share_network_subnet_id', None)
Lucio Seki37056942019-01-24 15:40:20 -02001186 managed_share_server = self.shares_v2_client.manage_share_server(
1187 params.get('host', share_server['host']),
1188 params.get('share_network_id', share_server['share_network_id']),
1189 params.get('identifier', share_server['identifier']),
Douglas Viroelb7e27e72019-08-06 19:40:37 -03001190 share_network_subnet_id=subnet_id,
lkuchlan86f24322021-04-27 14:23:05 +03001191 )['share_server']
lkuchlanf7fc5b62021-01-26 14:53:43 +02001192 waiters.wait_for_resource_status(
lkuchlan540e74a2021-01-19 18:08:25 +02001193 self.shares_v2_client, managed_share_server['id'],
lkuchlanf7fc5b62021-01-26 14:53:43 +02001194 constants.SERVER_STATE_ACTIVE, resource_name='share_server'
Lucio Seki37056942019-01-24 15:40:20 -02001195 )
1196
1197 return managed_share_server
1198
1199 def _delete_share_server_and_wait(self, share_server_id):
1200 self.shares_v2_client.delete_share_server(
1201 share_server_id
1202 )
1203 self.shares_v2_client.wait_for_resource_deletion(
1204 server_id=share_server_id)
1205
lkuchlan3af822b2021-06-06 10:35:30 +03001206 def create_user_message(self):
1207 """Trigger a 'no valid host' situation to generate a message."""
1208 extra_specs = {
1209 'vendor_name': 'foobar',
1210 'driver_handles_share_servers': CONF.share.multitenancy_enabled,
1211 }
1212 share_type_name = data_utils.rand_name("share-type")
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +03001213
lkuchlan3af822b2021-06-06 10:35:30 +03001214 bogus_type = self.create_share_type(
1215 client=self.admin_shares_v2_client,
1216 name=share_type_name,
1217 extra_specs=extra_specs)
1218
1219 params = {'share_type_id': bogus_type['id'],
1220 'share_network_id': self.shares_v2_client.share_network_id}
lkuchlan86f24322021-04-27 14:23:05 +03001221 share = self.shares_v2_client.create_share(**params)['share']
lkuchlan3af822b2021-06-06 10:35:30 +03001222 self.addCleanup(self.shares_v2_client.delete_share, share['id'])
1223 waiters.wait_for_resource_status(
1224 self.shares_v2_client, share['id'], "error")
1225 return waiters.wait_for_message(self.shares_v2_client, share['id'])
1226
1227
1228class BaseSharesMixedTest(BaseSharesAdminTest):
1229 """Base test case class for all Shares API tests with all user roles.
1230
1231 Tests deriving from this class can use the primary project's clients
1232 (self.shares_client, self.shares_v2_client) and the alt project user's
1233 clients (self.alt_shares_client, self.alt_shares_v2_client) to perform
1234 API calls and validations. Although admin clients are available for use,
1235 their use should be limited to performing bootstrapping (e.g., creating
1236 a share type, or resetting state of a resource, etc.). No API validation
1237 must be performed against admin APIs. Use BaseAdminTest as a base class
1238 for such tests.
1239 """
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +03001240 credentials = ('primary', 'alt', 'admin')
Marc Koderer0abc93b2015-07-15 09:18:35 +02001241
Goutham Pacha Ravic678e212020-03-20 11:13:47 -07001242 # Will be cleaned up in resource_cleanup if the class
1243 class_project_users_created = []
1244
1245 @classmethod
1246 def resource_cleanup(cls):
1247 cls.clear_project_users(cls.class_project_users_created)
1248 super(BaseSharesMixedTest, cls).resource_cleanup()
1249
1250 @classmethod
1251 def clear_project_users(cls, users=None):
1252 users = users or cls.class_project_users_created
1253 for user in users:
1254 with handle_cleanup_exceptions():
1255 cls.os_admin.creds_client.delete_user(user['id'])
1256
Marc Koderer0abc93b2015-07-15 09:18:35 +02001257 @classmethod
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +03001258 def setup_clients(cls):
1259 super(BaseSharesMixedTest, cls).setup_clients()
Andrea Frittoli (andreaf)369391a2016-06-27 18:59:13 +01001260 cls.alt_shares_client = cls.os_alt.share_v1.SharesClient()
1261 cls.alt_shares_v2_client = cls.os_alt.share_v2.SharesV2Client()
1262 # Initialise network clients
1263 cls.os_admin.networks_client = cls.os_admin.network.NetworksClient()
1264 cls.os_alt.networks_client = cls.os_alt.network.NetworksClient()
Goutham Pacha Ravic678e212020-03-20 11:13:47 -07001265 # Initialise identity clients
1266 cls.admin_project = cls.os_admin.auth_provider.auth_data[1]['project']
1267 identity_clients = getattr(
1268 cls.os_admin, 'identity_%s' % CONF.identity.auth_version)
1269 cls.os_admin.identity_client = identity_clients.IdentityClient()
1270 cls.os_admin.projects_client = identity_clients.ProjectsClient()
1271 cls.os_admin.users_client = identity_clients.UsersClient()
1272 cls.os_admin.roles_client = identity_clients.RolesClient()
1273 cls.os_admin.domains_client = (
1274 cls.os_admin.identity_v3.DomainsClient() if
1275 CONF.identity.auth_version == 'v3' else None)
Goutham Pacha Ravi10d49492021-02-23 16:05:21 -08001276 cls.admin_project_member_client = cls.create_user_and_get_client(
1277 project=cls.admin_project, add_member_role=True)
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +03001278
1279 if CONF.share.multitenancy_enabled:
1280 admin_share_network_id = cls.provide_share_network(
1281 cls.admin_shares_v2_client, cls.os_admin.networks_client)
1282 cls.admin_shares_client.share_network_id = admin_share_network_id
1283 cls.admin_shares_v2_client.share_network_id = (
1284 admin_share_network_id)
1285
1286 alt_share_network_id = cls.provide_share_network(
1287 cls.alt_shares_v2_client, cls.os_alt.networks_client)
1288 cls.alt_shares_client.share_network_id = alt_share_network_id
1289 cls.alt_shares_v2_client.share_network_id = alt_share_network_id
Victoria Martinez de la Cruzf6bc6fa2018-02-01 11:27:00 -05001290
1291 @classmethod
Goutham Pacha Ravi10d49492021-02-23 16:05:21 -08001292 def create_user_and_get_client(cls, project=None, add_member_role=True):
Goutham Pacha Ravic678e212020-03-20 11:13:47 -07001293 """Create a user in specified project & set share clients for user
1294
1295 The user will have all roles specified in tempest.conf
1296 :param: project: a dictionary with project ID and name, if not
1297 specified, the value will be cls.admin_project
1298 """
1299 project_domain_name = (
1300 cls.os_admin.identity_client.auth_provider.credentials.get(
1301 'project_domain_name', 'Default'))
1302 cls.os_admin.creds_client = cred_client.get_creds_client(
1303 cls.os_admin.identity_client, cls.os_admin.projects_client,
1304 cls.os_admin.users_client, cls.os_admin.roles_client,
1305 cls.os_admin.domains_client, project_domain_name)
1306
1307 # User info
1308 project = project or cls.admin_project
1309 username = data_utils.rand_name('manila_%s' % project['id'])
1310 password = data_utils.rand_password()
1311 email = '%s@example.org' % username
1312
1313 user = cls.os_admin.creds_client.create_user(
1314 username, password, project, email)
1315 cls.class_project_users_created.append(user)
1316
Goutham Pacha Ravi10d49492021-02-23 16:05:21 -08001317 tempest_roles_to_assign = CONF.auth.tempest_roles or []
1318 if "member" not in tempest_roles_to_assign and add_member_role:
1319 tempest_roles_to_assign.append("member")
1320
1321 for role in tempest_roles_to_assign:
1322 cls.os_admin.creds_client.assign_user_role(user, project, role)
Goutham Pacha Ravic678e212020-03-20 11:13:47 -07001323
1324 user_creds = cls.os_admin.creds_client.get_credentials(
1325 user, project, password)
1326 os = clients.Clients(user_creds)
1327 os.shares_v1_client = os.share_v1.SharesClient()
1328 os.shares_v2_client = os.share_v2.SharesV2Client()
1329 return os