blob: dbcb1ff3cf0ba05436b03784d3e690fff6bcea22 [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
21import six
Valeriy Ponomaryov48a2bd72015-11-05 13:22:44 +020022from tempest import config
Goutham Pacha Ravic678e212020-03-20 11:13:47 -070023from tempest.lib.common import cred_client
Ben Swartzlander1c4ff522016-03-02 22:16:23 -050024from tempest.lib.common.utils import data_utils
25from tempest.lib import exceptions
Valeriy Ponomaryov48a2bd72015-11-05 13:22:44 +020026from tempest import test
Marc Koderer0abc93b2015-07-15 09:18:35 +020027
Andrea Frittoli (andreaf)369391a2016-06-27 18:59:13 +010028from manila_tempest_tests import clients
Yogeshbdb88102015-09-29 23:41:02 -040029from manila_tempest_tests.common import constants
lkuchlan540e74a2021-01-19 18:08:25 +020030from manila_tempest_tests.common import waiters
Marc Koderer0abc93b2015-07-15 09:18:35 +020031from manila_tempest_tests import share_exceptions
Valeriy Ponomaryovfcde7712015-12-14 18:06:13 +020032from manila_tempest_tests import utils
Marc Koderer0abc93b2015-07-15 09:18:35 +020033
lkuchlan1d1461d2020-08-04 11:19:11 +030034
Marc Koderer0abc93b2015-07-15 09:18:35 +020035CONF = config.CONF
36LOG = log.getLogger(__name__)
37
Valeriy Ponomaryov2abf5d72016-06-01 18:30:12 +030038# Test tags related to test direction
39TAG_POSITIVE = "positive"
40TAG_NEGATIVE = "negative"
41
42# Test tags related to service involvement
Tom Barron69f96962019-07-29 17:07:03 -040043# Only requires that manila-api service running.
Valeriy Ponomaryov2abf5d72016-06-01 18:30:12 +030044TAG_API = "api"
Tom Barron69f96962019-07-29 17:07:03 -040045# Requires all manila services running, intended to test back-end
46# (manila-share) behavior.
Valeriy Ponomaryov2abf5d72016-06-01 18:30:12 +030047TAG_BACKEND = "backend"
Tom Barron69f96962019-07-29 17:07:03 -040048# Requires all manila services running, intended to test API behavior.
Valeriy Ponomaryov2abf5d72016-06-01 18:30:12 +030049TAG_API_WITH_BACKEND = "api_with_backend"
50
51TAGS_MAPPER = {
52 "p": TAG_POSITIVE,
53 "n": TAG_NEGATIVE,
54 "a": TAG_API,
55 "b": TAG_BACKEND,
56 "ab": TAG_API_WITH_BACKEND,
57}
58TAGS_PATTERN = re.compile(
59 r"(?=.*\[.*\b(%(p)s|%(n)s)\b.*\])(?=.*\[.*\b(%(a)s|%(b)s|%(ab)s)\b.*\])" %
60 TAGS_MAPPER)
61
62
63def verify_test_has_appropriate_tags(self):
64 if not TAGS_PATTERN.match(self.id()):
65 msg = (
66 "Required attributes either not set or set improperly. "
67 "Two test attributes are expected:\n"
68 " - one of '%(p)s' or '%(n)s' and \n"
69 " - one of '%(a)s', '%(b)s' or '%(ab)s'."
70 ) % TAGS_MAPPER
71 raise self.failureException(msg)
72
Marc Koderer0abc93b2015-07-15 09:18:35 +020073
74class handle_cleanup_exceptions(object):
75 """Handle exceptions raised with cleanup operations.
76
77 Always suppress errors when exceptions.NotFound or exceptions.Forbidden
78 are raised.
79 Suppress all other exceptions only in case config opt
80 'suppress_errors_in_cleanup' in config group 'share' is True.
81 """
82
83 def __enter__(self):
84 return self
85
86 def __exit__(self, exc_type, exc_value, exc_traceback):
87 if not (isinstance(exc_value,
88 (exceptions.NotFound, exceptions.Forbidden)) or
89 CONF.share.suppress_errors_in_cleanup):
90 return False # Do not suppress error if any
91 if exc_traceback:
92 LOG.error("Suppressed cleanup error in Manila: "
junbolib236c242017-07-18 18:12:37 +080093 "\n%s", traceback.format_exc())
Marc Koderer0abc93b2015-07-15 09:18:35 +020094 return True # Suppress error if any
95
96
Valeriy Ponomaryovfcde7712015-12-14 18:06:13 +020097skip_if_microversion_not_supported = utils.skip_if_microversion_not_supported
Xing Yang69b00b52015-11-22 16:10:44 -050098skip_if_microversion_lt = utils.skip_if_microversion_lt
Valeriy Ponomaryova14c2252015-10-29 13:34:32 +020099
100
Marc Koderer0abc93b2015-07-15 09:18:35 +0200101class BaseSharesTest(test.BaseTestCase):
102 """Base test case class for all Manila API tests."""
103
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +0300104 credentials = ('primary', )
Marc Koderer0abc93b2015-07-15 09:18:35 +0200105 force_tenant_isolation = False
Vitaliy Levitksicfebfff2016-12-15 16:16:35 +0200106 protocols = ["nfs", "cifs", "glusterfs", "hdfs", "cephfs", "maprfs"]
Marc Koderer0abc93b2015-07-15 09:18:35 +0200107
108 # Will be cleaned up in resource_cleanup
109 class_resources = []
110
111 # Will be cleaned up in tearDown method
112 method_resources = []
113
Andrea Frittoli (andreaf)369391a2016-06-27 18:59:13 +0100114 # NOTE(andreaf) Override the client manager class to be used, so that
115 # a stable class is used, which includes plugin registered services as well
116 client_manager = clients.Clients
117
Marc Koderer0abc93b2015-07-15 09:18:35 +0200118 @classmethod
Daniel Melladoe5269142017-01-12 12:17:58 +0000119 def skip_checks(cls):
120 super(BaseSharesTest, cls).skip_checks()
121 if not CONF.service_available.manila:
122 raise cls.skipException("Manila support is required")
lkuchlana3b6f7a2020-01-07 10:45:45 +0200123 if not any(p in CONF.share.enable_protocols for p in cls.protocols):
124 skip_msg = "%s tests are disabled" % CONF.share.enable_protocols
125 raise cls.skipException(skip_msg)
Daniel Melladoe5269142017-01-12 12:17:58 +0000126
127 @classmethod
Marc Koderer0abc93b2015-07-15 09:18:35 +0200128 def verify_nonempty(cls, *args):
129 if not all(args):
130 msg = "Missing API credentials in configuration."
131 raise cls.skipException(msg)
132
133 @classmethod
Goutham Pacha Ravi4a0b7322020-06-30 19:42:45 -0700134 def setup_credentials(cls):
135 # This call is used to tell the credential allocator to create
136 # network resources for this test case. NOTE: it must go before the
137 # super call, to override decisions in the base classes.
138 network_resources = {}
139 if (CONF.share.multitenancy_enabled and
140 CONF.share.create_networks_when_multitenancy_enabled):
141 # We're testing a DHSS=True driver, and manila is configured with
142 # NeutronNetworkPlugin (or a derivative) that supports creating
143 # share networks with project neutron networks, so lets ask for
144 # neutron network resources to be created with test credentials
145 network_resources.update({'network': True,
146 'subnet': True,
147 'router': True})
148 cls.set_network_resources(**network_resources)
149 super(BaseSharesTest, cls).setup_credentials()
150
151 @classmethod
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +0300152 def setup_clients(cls):
153 super(BaseSharesTest, cls).setup_clients()
154 os = getattr(cls, 'os_%s' % cls.credentials[0])
Andrea Frittoli (andreaf)369391a2016-06-27 18:59:13 +0100155 # Initialise share clients for test credentials
156 cls.shares_client = os.share_v1.SharesClient()
157 cls.shares_v2_client = os.share_v2.SharesV2Client()
158 # Initialise network clients for test credentials
Goutham Pacha Ravi4a0b7322020-06-30 19:42:45 -0700159 cls.networks_client = None
160 cls.subnets_client = None
Andrea Frittoli (andreaf)369391a2016-06-27 18:59:13 +0100161 if CONF.service_available.neutron:
162 cls.networks_client = os.network.NetworksClient()
163 cls.subnets_client = os.network.SubnetsClient()
Valeriy Ponomaryov4fb305f2016-10-21 13:46:47 +0300164
Goutham Pacha Ravi4a0b7322020-06-30 19:42:45 -0700165 # If DHSS=True, create a share network and set it in the client
166 # for easy access.
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +0300167 if CONF.share.multitenancy_enabled:
Valeriy Ponomaryovc5dae272016-06-10 18:29:24 +0300168 if (not CONF.service_available.neutron and
169 CONF.share.create_networks_when_multitenancy_enabled):
Goutham Pacha Ravi4a0b7322020-06-30 19:42:45 -0700170 raise cls.skipException(
171 "Neutron support is required when "
172 "CONF.share.create_networks_when_multitenancy_enabled "
173 "is set to True")
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +0300174 share_network_id = cls.provide_share_network(
Goutham Pacha Ravi4a0b7322020-06-30 19:42:45 -0700175 cls.shares_client, cls.networks_client)
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +0300176 cls.shares_client.share_network_id = share_network_id
177 cls.shares_v2_client.share_network_id = share_network_id
178
Marc Koderer0abc93b2015-07-15 09:18:35 +0200179 def setUp(self):
180 super(BaseSharesTest, self).setUp()
Valeriy Ponomaryovdd162cb2016-01-20 19:09:49 +0200181 self.addCleanup(self.clear_resources)
Valeriy Ponomaryov2abf5d72016-06-01 18:30:12 +0300182 verify_test_has_appropriate_tags(self)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200183
184 @classmethod
185 def resource_cleanup(cls):
Marc Koderer0abc93b2015-07-15 09:18:35 +0200186 cls.clear_resources(cls.class_resources)
Sam Wan241029c2016-07-26 03:37:42 -0400187 super(BaseSharesTest, cls).resource_cleanup()
Marc Koderer0abc93b2015-07-15 09:18:35 +0200188
189 @classmethod
debeltrami1753a592020-05-11 18:27:30 +0000190 def provide_and_associate_security_services(
191 cls, shares_client, share_network_id, cleanup_in_class=True):
192 """Creates a security service and associates to a share network.
193
194 This method creates security services based on the Multiopt
195 defined in tempest configuration named security_service. When this
196 configuration is not provided, the method will return None.
197 After the security service creation, this method also associates
198 the security service to a share network.
199
200 :param shares_client: shares client, which requires the provisioning
201 :param share_network_id: id of the share network to associate the
202 security service
203 :param cleanup_in_class: if the security service and the association
204 will be removed in the method teardown or class teardown
205 :returns: None -- if the security service configuration is not
206 defined
207 """
208
209 ss_configs = CONF.share.security_service
210 if not ss_configs:
211 return
212
213 for ss_config in ss_configs:
214 ss_name = "ss_autogenerated_by_tempest_%s" % (
215 ss_config.get("ss_type"))
216
217 ss_params = {
218 "name": ss_name,
219 "dns_ip": ss_config.get("ss_dns_ip"),
220 "server": ss_config.get("ss_server"),
221 "domain": ss_config.get("ss_domain"),
222 "user": ss_config.get("ss_user"),
223 "password": ss_config.get("ss_password")
224 }
225 ss_type = ss_config.get("ss_type")
226 security_service = cls.create_security_service(
227 ss_type,
228 client=shares_client,
229 cleanup_in_class=cleanup_in_class,
230 **ss_params)
231
232 cls.add_sec_service_to_share_network(
233 shares_client, share_network_id,
234 security_service["id"],
235 cleanup_in_class=cleanup_in_class)
236
237 @classmethod
238 def add_sec_service_to_share_network(
239 cls, client, share_network_id,
240 security_service_id, cleanup_in_class=True):
241 """Associates a security service to a share network.
242
243 This method associates a security service provided by
244 the security service configuration with a specific
245 share network.
246
247 :param share_network_id: the share network id to be
248 associate with a given security service
249 :param security_service_id: the security service id
250 to be associate with a given share network
251 :param cleanup_in_class: if the resources will be
252 dissociate in the method teardown or class teardown
253 """
254
255 client.add_sec_service_to_share_network(
256 share_network_id,
257 security_service_id)
258 resource = {
259 "type": "dissociate_security_service",
260 "id": security_service_id,
261 "extra_params": {
262 "share_network_id": share_network_id
263 },
264 "client": client,
265 }
266
267 if cleanup_in_class:
268 cls.class_resources.insert(0, resource)
269 else:
270 cls.method_resources.insert(0, resource)
271
272 @classmethod
Valeriy Ponomaryov48a2bd72015-11-05 13:22:44 +0200273 def provide_share_network(cls, shares_client, networks_client,
Rodrigo Barbieri58d9de32016-09-06 13:16:47 -0300274 ignore_multitenancy_config=False):
Goutham Pacha Ravi4a0b7322020-06-30 19:42:45 -0700275 """Get or create share network for DHSS=True drivers
Marc Koderer0abc93b2015-07-15 09:18:35 +0200276
Goutham Pacha Ravi4a0b7322020-06-30 19:42:45 -0700277 When testing DHSS=True (multitenancy_enabled) drivers, shares must
278 be requested on share networks.
Marc Koderer0abc93b2015-07-15 09:18:35 +0200279 :returns: str -- share network id for shares_client tenant
Goutham Pacha Ravi4a0b7322020-06-30 19:42:45 -0700280 :returns: None -- if single-tenant driver (DHSS=False) is used
Marc Koderer0abc93b2015-07-15 09:18:35 +0200281 """
282
Rodrigo Barbieri58d9de32016-09-06 13:16:47 -0300283 if (not ignore_multitenancy_config and
284 not CONF.share.multitenancy_enabled):
Goutham Pacha Ravi4a0b7322020-06-30 19:42:45 -0700285 # Assumed usage of a single-tenant driver (DHSS=False)
debeltrami1753a592020-05-11 18:27:30 +0000286 return None
Goutham Pacha Raviaf448262020-06-29 14:24:13 -0700287
Goutham Pacha Ravi4a0b7322020-06-30 19:42:45 -0700288 if shares_client.share_network_id:
289 # Share-network already exists, use it
290 return shares_client.share_network_id
debeltrami1753a592020-05-11 18:27:30 +0000291
Goutham Pacha Ravi4a0b7322020-06-30 19:42:45 -0700292 sn_name = "autogenerated_by_tempest"
293 sn_desc = "This share-network was created by tempest"
Rodrigo Barbieri58d9de32016-09-06 13:16:47 -0300294
Goutham Pacha Ravi4a0b7322020-06-30 19:42:45 -0700295 if not CONF.share.create_networks_when_multitenancy_enabled:
296 # We need a new share network, but don't need to associate
297 # any neutron networks to it - this configuration is used
298 # when manila is configured with "StandaloneNetworkPlugin"
299 # or "NeutronSingleNetworkPlugin" where all tenants share
300 # a single backend network where shares are exported.
301 sn = cls.create_share_network(cleanup_in_class=True,
302 client=shares_client,
303 add_security_services=True,
304 name=sn_name,
305 description=sn_desc)
306 return sn['id']
Marc Koderer0abc93b2015-07-15 09:18:35 +0200307
Goutham Pacha Ravi4a0b7322020-06-30 19:42:45 -0700308 # Retrieve non-public network list owned by the tenant
309 filters = {'project_id': shares_client.tenant_id,
310 'shared': False}
311 tenant_networks = (
312 networks_client.list_networks(**filters).get('networks', [])
313 )
314 tenant_networks_with_subnet = (
315 [n for n in tenant_networks if n['subnets']]
316 )
debeltrami1753a592020-05-11 18:27:30 +0000317
Goutham Pacha Ravi4a0b7322020-06-30 19:42:45 -0700318 if not tenant_networks_with_subnet:
319 # This can only occur if using tempest's pre-provisioned
320 # credentials and not allocating networks to them
321 raise cls.skipException(
322 "Test credentials must provide at least one "
323 "non-shared project network with a valid subnet when "
324 "CONF.share.create_networks_when_multitenancy_enabled is "
325 "set to True.")
debeltrami1753a592020-05-11 18:27:30 +0000326
Goutham Pacha Ravi4a0b7322020-06-30 19:42:45 -0700327 net_id = tenant_networks_with_subnet[0]['id']
328 subnet_id = tenant_networks_with_subnet[0]['subnets'][0]
debeltrami1753a592020-05-11 18:27:30 +0000329
Goutham Pacha Ravi4a0b7322020-06-30 19:42:45 -0700330 # Create suitable share-network
331 sn = cls.create_share_network(cleanup_in_class=True,
332 client=shares_client,
333 add_security_services=True,
334 name=sn_name,
335 description=sn_desc,
336 neutron_net_id=net_id,
337 neutron_subnet_id=subnet_id)
debeltrami1753a592020-05-11 18:27:30 +0000338
Goutham Pacha Ravi4a0b7322020-06-30 19:42:45 -0700339 return sn['id']
Marc Koderer0abc93b2015-07-15 09:18:35 +0200340
341 @classmethod
marcusvrne0d7cfd2016-06-24 12:27:55 -0300342 def _create_share(cls, share_protocol=None, size=None, name=None,
Marc Koderer0abc93b2015-07-15 09:18:35 +0200343 snapshot_id=None, description=None, metadata=None,
344 share_network_id=None, share_type_id=None,
Andrew Kerrb8436922016-06-01 15:32:43 -0400345 share_group_id=None, client=None,
Clinton Knighte5c8f092015-08-27 15:00:23 -0400346 cleanup_in_class=True, is_public=False, **kwargs):
Valeriy Ponomaryov1aaa72d2015-09-08 12:59:41 +0300347 client = client or cls.shares_v2_client
Marc Koderer0abc93b2015-07-15 09:18:35 +0200348 description = description or "Tempest's share"
yogesh06f519f2017-06-26 13:16:12 -0400349 share_network_id = (share_network_id or
350 CONF.share.share_network_id or
351 client.share_network_id or None)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200352 metadata = metadata or {}
marcusvrne0d7cfd2016-06-24 12:27:55 -0300353 size = size or CONF.share.share_size
Clinton Knighte5c8f092015-08-27 15:00:23 -0400354 kwargs.update({
Marc Koderer0abc93b2015-07-15 09:18:35 +0200355 'share_protocol': share_protocol,
356 'size': size,
357 'name': name,
358 'snapshot_id': snapshot_id,
359 'description': description,
360 'metadata': metadata,
361 'share_network_id': share_network_id,
362 'share_type_id': share_type_id,
363 'is_public': is_public,
Clinton Knighte5c8f092015-08-27 15:00:23 -0400364 })
Andrew Kerrb8436922016-06-01 15:32:43 -0400365 if share_group_id:
366 kwargs['share_group_id'] = share_group_id
Andrew Kerrbf31e912015-07-29 10:39:38 -0400367
Marc Koderer0abc93b2015-07-15 09:18:35 +0200368 share = client.create_share(**kwargs)
Andrew Kerrbf31e912015-07-29 10:39:38 -0400369 resource = {"type": "share", "id": share["id"], "client": client,
Andrew Kerrb8436922016-06-01 15:32:43 -0400370 "share_group_id": share_group_id}
Marc Koderer0abc93b2015-07-15 09:18:35 +0200371 cleanup_list = (cls.class_resources if cleanup_in_class else
372 cls.method_resources)
373 cleanup_list.insert(0, resource)
374 return share
375
376 @classmethod
Rodrigo Barbieri427bc052016-06-06 17:10:06 -0300377 def migrate_share(
378 cls, share_id, dest_host, wait_for_status, client=None,
Rodrigo Barbieri027df982016-11-24 15:52:03 -0200379 force_host_assisted_migration=False, writable=False,
380 nondisruptive=False, preserve_metadata=False,
381 preserve_snapshots=False, new_share_network_id=None,
Rodrigo Barbierid38d2f52016-07-19 22:24:56 -0300382 new_share_type_id=None, **kwargs):
Clinton Knighte5c8f092015-08-27 15:00:23 -0400383 client = client or cls.shares_v2_client
Rodrigo Barbieri427bc052016-06-06 17:10:06 -0300384 client.migrate_share(
385 share_id, dest_host,
386 force_host_assisted_migration=force_host_assisted_migration,
Rodrigo Barbieri027df982016-11-24 15:52:03 -0200387 writable=writable, preserve_metadata=preserve_metadata,
388 nondisruptive=nondisruptive, preserve_snapshots=preserve_snapshots,
Rodrigo Barbieri427bc052016-06-06 17:10:06 -0300389 new_share_network_id=new_share_network_id,
Rodrigo Barbierid38d2f52016-07-19 22:24:56 -0300390 new_share_type_id=new_share_type_id, **kwargs)
lkuchlan540e74a2021-01-19 18:08:25 +0200391 share = waiters.wait_for_migration_status(
392 client, share_id, dest_host, wait_for_status, **kwargs)
Rodrigo Barbierie3305122016-02-03 14:32:24 -0200393 return share
394
395 @classmethod
396 def migration_complete(cls, share_id, dest_host, client=None, **kwargs):
397 client = client or cls.shares_v2_client
398 client.migration_complete(share_id, **kwargs)
lkuchlan540e74a2021-01-19 18:08:25 +0200399 share = waiters.wait_for_migration_status(
400 client, share_id, dest_host, 'migration_success', **kwargs)
Rodrigo Barbierib7137ad2015-09-06 22:53:16 -0300401 return share
402
403 @classmethod
Rodrigo Barbieric9abf282016-08-24 22:01:31 -0300404 def migration_cancel(cls, share_id, dest_host, client=None, **kwargs):
405 client = client or cls.shares_v2_client
406 client.migration_cancel(share_id, **kwargs)
lkuchlan540e74a2021-01-19 18:08:25 +0200407 share = waiters.wait_for_migration_status(
408 client, share_id, dest_host, 'migration_cancelled', **kwargs)
Rodrigo Barbieric9abf282016-08-24 22:01:31 -0300409 return share
410
411 @classmethod
Marc Koderer0abc93b2015-07-15 09:18:35 +0200412 def create_share(cls, *args, **kwargs):
413 """Create one share and wait for available state. Retry if allowed."""
414 result = cls.create_shares([{"args": args, "kwargs": kwargs}])
415 return result[0]
416
417 @classmethod
418 def create_shares(cls, share_data_list):
419 """Creates several shares in parallel with retries.
420
421 Use this method when you want to create more than one share at same
422 time. Especially if config option 'share.share_creation_retry_number'
423 has value more than zero (0).
424 All shares will be expected to have 'available' status with or without
425 recreation else error will be raised.
426
427 :param share_data_list: list -- list of dictionaries with 'args' and
428 'kwargs' for '_create_share' method of this base class.
429 example of data:
430 share_data_list=[{'args': ['quuz'], 'kwargs': {'foo': 'bar'}}}]
431 :returns: list -- list of shares created using provided data.
432 """
433
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +0300434 for d in share_data_list:
Marc Koderer0abc93b2015-07-15 09:18:35 +0200435 if not isinstance(d, dict):
436 raise exceptions.TempestException(
437 "Expected 'dict', got '%s'" % type(d))
438 if "args" not in d:
439 d["args"] = []
440 if "kwargs" not in d:
441 d["kwargs"] = {}
442 if len(d) > 2:
443 raise exceptions.TempestException(
444 "Expected only 'args' and 'kwargs' keys. "
445 "Provided %s" % list(d))
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +0300446
447 data = []
448 for d in share_data_list:
449 client = d["kwargs"].pop("client", cls.shares_v2_client)
yogeshdb32f462016-09-28 15:09:50 -0400450 wait_for_status = d["kwargs"].pop("wait_for_status", True)
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +0300451 local_d = {
452 "args": d["args"],
453 "kwargs": copy.deepcopy(d["kwargs"]),
454 }
455 local_d["kwargs"]["client"] = client
456 local_d["share"] = cls._create_share(
457 *local_d["args"], **local_d["kwargs"])
458 local_d["cnt"] = 0
459 local_d["available"] = False
yogeshdb32f462016-09-28 15:09:50 -0400460 local_d["wait_for_status"] = wait_for_status
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +0300461 data.append(local_d)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200462
463 while not all(d["available"] for d in data):
464 for d in data:
yogeshdb32f462016-09-28 15:09:50 -0400465 if not d["wait_for_status"]:
466 d["available"] = True
Marc Koderer0abc93b2015-07-15 09:18:35 +0200467 if d["available"]:
468 continue
Valeriy Ponomaryov1a3e3382016-06-08 15:17:16 +0300469 client = d["kwargs"]["client"]
470 share_id = d["share"]["id"]
Marc Koderer0abc93b2015-07-15 09:18:35 +0200471 try:
lkuchlanf7fc5b62021-01-26 14:53:43 +0200472 waiters.wait_for_resource_status(
lkuchlan540e74a2021-01-19 18:08:25 +0200473 client, share_id, "available")
Marc Koderer0abc93b2015-07-15 09:18:35 +0200474 d["available"] = True
475 except (share_exceptions.ShareBuildErrorException,
476 exceptions.TimeoutException) as e:
477 if CONF.share.share_creation_retry_number > d["cnt"]:
478 d["cnt"] += 1
479 msg = ("Share '%s' failed to be built. "
Valeriy Ponomaryov1a3e3382016-06-08 15:17:16 +0300480 "Trying create another." % share_id)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200481 LOG.error(msg)
482 LOG.error(e)
Valeriy Ponomaryov1a3e3382016-06-08 15:17:16 +0300483 cg_id = d["kwargs"].get("consistency_group_id")
484 if cg_id:
485 # NOTE(vponomaryov): delete errored share
486 # immediately in case share is part of CG.
487 client.delete_share(
488 share_id,
489 params={"consistency_group_id": cg_id})
490 client.wait_for_resource_deletion(
491 share_id=share_id)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200492 d["share"] = cls._create_share(
493 *d["args"], **d["kwargs"])
494 else:
gecong197358663802016-08-25 11:08:45 +0800495 raise
Marc Koderer0abc93b2015-07-15 09:18:35 +0200496
497 return [d["share"] for d in data]
498
499 @classmethod
Andrew Kerrb8436922016-06-01 15:32:43 -0400500 def create_share_group(cls, client=None, cleanup_in_class=True,
501 share_network_id=None, **kwargs):
Clinton Knighte5c8f092015-08-27 15:00:23 -0400502 client = client or cls.shares_v2_client
Andrew Kerrb8436922016-06-01 15:32:43 -0400503 if kwargs.get('source_share_group_snapshot_id') is None:
Goutham Pacha Ravi9221f5e2016-04-21 13:17:49 -0400504 kwargs['share_network_id'] = (share_network_id or
505 client.share_network_id or None)
Andrew Kerrb8436922016-06-01 15:32:43 -0400506 share_group = client.create_share_group(**kwargs)
Andrew Kerrbf31e912015-07-29 10:39:38 -0400507 resource = {
Andrew Kerrb8436922016-06-01 15:32:43 -0400508 "type": "share_group",
509 "id": share_group["id"],
510 "client": client,
511 }
Andrew Kerrbf31e912015-07-29 10:39:38 -0400512 if cleanup_in_class:
513 cls.class_resources.insert(0, resource)
514 else:
515 cls.method_resources.insert(0, resource)
516
Andrew Kerrb8436922016-06-01 15:32:43 -0400517 if kwargs.get('source_share_group_snapshot_id'):
518 new_share_group_shares = client.list_shares(
Andrew Kerrbf31e912015-07-29 10:39:38 -0400519 detailed=True,
silvacarloss6e575682020-02-18 19:52:35 -0300520 params={'share_group_id': share_group['id']})
Andrew Kerrbf31e912015-07-29 10:39:38 -0400521
Andrew Kerrb8436922016-06-01 15:32:43 -0400522 for share in new_share_group_shares:
Andrew Kerrbf31e912015-07-29 10:39:38 -0400523 resource = {"type": "share",
524 "id": share["id"],
525 "client": client,
Andrew Kerrb8436922016-06-01 15:32:43 -0400526 "share_group_id": share.get("share_group_id")}
Andrew Kerrbf31e912015-07-29 10:39:38 -0400527 if cleanup_in_class:
528 cls.class_resources.insert(0, resource)
529 else:
530 cls.method_resources.insert(0, resource)
531
lkuchlanf7fc5b62021-01-26 14:53:43 +0200532 waiters.wait_for_resource_status(
533 client, share_group['id'], 'available',
534 resource_name='share_group')
Andrew Kerrb8436922016-06-01 15:32:43 -0400535 return share_group
536
537 @classmethod
538 def create_share_group_type(cls, name=None, share_types=(), is_public=None,
539 group_specs=None, client=None,
540 cleanup_in_class=True, **kwargs):
541 client = client or cls.shares_v2_client
Valeriy Ponomaryove92f09f2017-03-16 17:25:47 +0300542 if (group_specs is None and
543 CONF.share.capability_sg_consistent_snapshot_support):
Valeriy Ponomaryov3c188932017-03-15 19:06:23 +0300544 group_specs = {
545 'consistent_snapshot_support': (
546 CONF.share.capability_sg_consistent_snapshot_support),
547 }
Andrew Kerrb8436922016-06-01 15:32:43 -0400548 share_group_type = client.create_share_group_type(
549 name=name,
550 share_types=share_types,
551 is_public=is_public,
552 group_specs=group_specs,
553 **kwargs)
554 resource = {
555 "type": "share_group_type",
556 "id": share_group_type["id"],
557 "client": client,
558 }
559 if cleanup_in_class:
560 cls.class_resources.insert(0, resource)
561 else:
562 cls.method_resources.insert(0, resource)
563 return share_group_type
Andrew Kerrbf31e912015-07-29 10:39:38 -0400564
565 @classmethod
Marc Koderer0abc93b2015-07-15 09:18:35 +0200566 def create_snapshot_wait_for_active(cls, share_id, name=None,
567 description=None, force=False,
568 client=None, cleanup_in_class=True):
569 if client is None:
Yogesh1f931ff2015-09-29 23:41:02 -0400570 client = cls.shares_v2_client
Marc Koderer0abc93b2015-07-15 09:18:35 +0200571 if description is None:
572 description = "Tempest's snapshot"
573 snapshot = client.create_snapshot(share_id, name, description, force)
574 resource = {
575 "type": "snapshot",
576 "id": snapshot["id"],
577 "client": client,
578 }
579 if cleanup_in_class:
580 cls.class_resources.insert(0, resource)
581 else:
582 cls.method_resources.insert(0, resource)
lkuchlanf7fc5b62021-01-26 14:53:43 +0200583 waiters.wait_for_resource_status(client, snapshot["id"], "available",
584 resource_name='snapshot')
Marc Koderer0abc93b2015-07-15 09:18:35 +0200585 return snapshot
586
587 @classmethod
Andrew Kerrb8436922016-06-01 15:32:43 -0400588 def create_share_group_snapshot_wait_for_active(
589 cls, share_group_id, name=None, description=None, client=None,
590 cleanup_in_class=True, **kwargs):
Clinton Knighte5c8f092015-08-27 15:00:23 -0400591 client = client or cls.shares_v2_client
Andrew Kerrbf31e912015-07-29 10:39:38 -0400592 if description is None:
Andrew Kerrb8436922016-06-01 15:32:43 -0400593 description = "Tempest's share group snapshot"
594 sg_snapshot = client.create_share_group_snapshot(
595 share_group_id, name=name, description=description, **kwargs)
Andrew Kerrbf31e912015-07-29 10:39:38 -0400596 resource = {
Andrew Kerrb8436922016-06-01 15:32:43 -0400597 "type": "share_group_snapshot",
598 "id": sg_snapshot["id"],
Andrew Kerrbf31e912015-07-29 10:39:38 -0400599 "client": client,
600 }
601 if cleanup_in_class:
602 cls.class_resources.insert(0, resource)
603 else:
604 cls.method_resources.insert(0, resource)
lkuchlanf7fc5b62021-01-26 14:53:43 +0200605 waiters.wait_for_resource_status(
606 client, sg_snapshot["id"], "available",
607 resource_name="share_group_snapshot")
Andrew Kerrb8436922016-06-01 15:32:43 -0400608 return sg_snapshot
Andrew Kerrbf31e912015-07-29 10:39:38 -0400609
610 @classmethod
Goutham Pacha Ravi839c98b2019-01-14 23:16:23 -0800611 def get_availability_zones(cls, client=None, backends=None):
Yogeshbdb88102015-09-29 23:41:02 -0400612 """List the availability zones for "manila-share" services
613
614 that are currently in "up" state.
615 """
Goutham Pacha Ravi839c98b2019-01-14 23:16:23 -0800616 client = client or cls.admin_shares_v2_client
617 backends = (
618 '|'.join(['^%s$' % backend for backend in backends])
619 if backends else '.*'
620 )
Yogeshbdb88102015-09-29 23:41:02 -0400621 cls.services = client.list_services()
622 zones = [service['zone'] for service in cls.services if
Goutham Pacha Ravi839c98b2019-01-14 23:16:23 -0800623 service['binary'] == 'manila-share' and
624 service['state'] == 'up' and
625 re.search(backends, service['host'])]
Douglas Viroel161f1802020-04-25 17:18:14 -0300626 return list(set(zones))
Yogeshbdb88102015-09-29 23:41:02 -0400627
Goutham Pacha Ravi839c98b2019-01-14 23:16:23 -0800628 @classmethod
629 def get_pools_matching_share_type(cls, share_type, client=None):
630 client = client or cls.admin_shares_v2_client
631 if utils.is_microversion_supported('2.23'):
632 return client.list_pools(
andrebeltrami3b4d4852020-02-04 19:11:54 +0000633 detail=True,
Goutham Pacha Ravi839c98b2019-01-14 23:16:23 -0800634 search_opts={'share_type': share_type['id']})['pools']
635
636 pools = client.list_pools(detail=True)['pools']
637 share_type = client.get_share_type(share_type['id'])['share_type']
638 extra_specs = {}
639 for k, v in share_type['extra_specs'].items():
640 extra_specs[k] = (
641 True if six.text_type(v).lower() == 'true'
642 else False if six.text_type(v).lower() == 'false' else v
643 )
644 return [
645 pool for pool in pools if all(y in pool['capabilities'].items()
646 for y in extra_specs.items())
647 ]
648
649 @classmethod
650 def get_availability_zones_matching_share_type(cls, share_type,
651 client=None):
652
653 client = client or cls.admin_shares_v2_client
654 pools_matching_share_type = cls.get_pools_matching_share_type(
655 share_type, client=client)
656 backends_matching_share_type = set(
657 [pool['name'].split("#")[0] for pool in pools_matching_share_type]
658 )
659 azs = cls.get_availability_zones(backends=backends_matching_share_type)
660 return azs
661
Yogesh1f931ff2015-09-29 23:41:02 -0400662 def get_pools_for_replication_domain(self):
663 # Get the list of pools for the replication domain
664 pools = self.admin_client.list_pools(detail=True)['pools']
Ben Swartzlander7150c652017-02-13 22:31:18 -0500665 instance_host = self.admin_client.get_share(
666 self.shares[0]['id'])['host']
Yogesh1f931ff2015-09-29 23:41:02 -0400667 host_pool = [p for p in pools if p['name'] == instance_host][0]
668 rep_domain = host_pool['capabilities']['replication_domain']
669 pools_in_rep_domain = [p for p in pools if p['capabilities'][
670 'replication_domain'] == rep_domain]
671 return rep_domain, pools_in_rep_domain
672
Yogeshbdb88102015-09-29 23:41:02 -0400673 @classmethod
debeltrami0d523bb2020-08-20 12:48:49 +0000674 def create_share_replica(cls, share_id, availability_zone=None,
675 client=None, cleanup_in_class=False,
676 cleanup=True,
silvacarlossd354d672020-08-23 18:49:52 +0000677 version=CONF.share.max_api_microversion):
Yogeshbdb88102015-09-29 23:41:02 -0400678 client = client or cls.shares_v2_client
Douglas Viroelbd4e78c2019-09-02 17:16:30 -0300679 replica = client.create_share_replica(
silvacarlossd354d672020-08-23 18:49:52 +0000680 share_id, availability_zone=availability_zone, version=version)
Yogeshbdb88102015-09-29 23:41:02 -0400681 resource = {
682 "type": "share_replica",
683 "id": replica["id"],
684 "client": client,
685 "share_id": share_id,
686 }
687 # NOTE(Yogi1): Cleanup needs to be disabled during promotion tests.
688 if cleanup:
689 if cleanup_in_class:
690 cls.class_resources.insert(0, resource)
691 else:
692 cls.method_resources.insert(0, resource)
lkuchlanf7fc5b62021-01-26 14:53:43 +0200693 waiters.wait_for_resource_status(
694 client, replica["id"], constants.STATUS_AVAILABLE,
695 resource_name='share_replica')
Yogeshbdb88102015-09-29 23:41:02 -0400696 return replica
697
698 @classmethod
silvacarlossd354d672020-08-23 18:49:52 +0000699 def delete_share_replica(cls, replica_id, client=None,
700 version=CONF.share.max_api_microversion):
Yogeshbdb88102015-09-29 23:41:02 -0400701 client = client or cls.shares_v2_client
Yogesh1f931ff2015-09-29 23:41:02 -0400702 try:
silvacarlossd354d672020-08-23 18:49:52 +0000703 client.delete_share_replica(replica_id, version=version)
Yogesh1f931ff2015-09-29 23:41:02 -0400704 client.wait_for_resource_deletion(replica_id=replica_id)
705 except exceptions.NotFound:
706 pass
Yogeshbdb88102015-09-29 23:41:02 -0400707
708 @classmethod
silvacarlossd354d672020-08-23 18:49:52 +0000709 def promote_share_replica(cls, replica_id, client=None,
710 version=CONF.share.max_api_microversion):
Yogeshbdb88102015-09-29 23:41:02 -0400711 client = client or cls.shares_v2_client
silvacarlossd354d672020-08-23 18:49:52 +0000712 replica = client.promote_share_replica(replica_id, version=version)
lkuchlanf7fc5b62021-01-26 14:53:43 +0200713 waiters.wait_for_resource_status(
lkuchlan540e74a2021-01-19 18:08:25 +0200714 client, replica["id"], constants.REPLICATION_STATE_ACTIVE,
lkuchlanf7fc5b62021-01-26 14:53:43 +0200715 resource_name='share_replica', status_attr="replica_state")
Yogeshbdb88102015-09-29 23:41:02 -0400716 return replica
717
Goutham Pacha Ravi1727f7a2020-05-22 13:41:40 -0700718 @classmethod
719 def _get_access_rule_data_from_config(cls):
yogeshdb32f462016-09-28 15:09:50 -0400720 """Get the first available access type/to combination from config.
721
722 This method opportunistically picks the first configured protocol
723 to create the share. Do not use this method in tests where you need
724 to test depth and breadth in the access types and access recipients.
725 """
Goutham Pacha Ravi1727f7a2020-05-22 13:41:40 -0700726 protocol = cls.shares_v2_client.share_protocol
yogeshdb32f462016-09-28 15:09:50 -0400727
728 if protocol in CONF.share.enable_ip_rules_for_protocols:
729 access_type = "ip"
730 access_to = utils.rand_ip()
731 elif protocol in CONF.share.enable_user_rules_for_protocols:
732 access_type = "user"
733 access_to = CONF.share.username_for_user_rules
734 elif protocol in CONF.share.enable_cert_rules_for_protocols:
735 access_type = "cert"
736 access_to = "client3.com"
737 elif protocol in CONF.share.enable_cephx_rules_for_protocols:
738 access_type = "cephx"
lkuchlan5af7cb42020-07-14 18:05:09 +0300739 access_to = data_utils.rand_name(
740 cls.__class__.__name__ + '-cephx-id')
yogeshdb32f462016-09-28 15:09:50 -0400741 else:
742 message = "Unrecognized protocol and access rules configuration."
Goutham Pacha Ravi1727f7a2020-05-22 13:41:40 -0700743 raise cls.skipException(message)
yogeshdb32f462016-09-28 15:09:50 -0400744
745 return access_type, access_to
746
Yogeshbdb88102015-09-29 23:41:02 -0400747 @classmethod
Marc Koderer0abc93b2015-07-15 09:18:35 +0200748 def create_share_network(cls, client=None,
debeltrami1753a592020-05-11 18:27:30 +0000749 cleanup_in_class=False,
750 add_security_services=True, **kwargs):
751
Marc Koderer0abc93b2015-07-15 09:18:35 +0200752 if client is None:
753 client = cls.shares_client
754 share_network = client.create_share_network(**kwargs)
755 resource = {
756 "type": "share_network",
757 "id": share_network["id"],
758 "client": client,
759 }
debeltrami1753a592020-05-11 18:27:30 +0000760
Marc Koderer0abc93b2015-07-15 09:18:35 +0200761 if cleanup_in_class:
762 cls.class_resources.insert(0, resource)
763 else:
764 cls.method_resources.insert(0, resource)
debeltrami1753a592020-05-11 18:27:30 +0000765
766 if add_security_services:
767 cls.provide_and_associate_security_services(
768 client, share_network["id"], cleanup_in_class=cleanup_in_class)
769
Marc Koderer0abc93b2015-07-15 09:18:35 +0200770 return share_network
771
772 @classmethod
debeltrami1753a592020-05-11 18:27:30 +0000773 def create_share_network_subnet(cls,
774 client=None,
775 cleanup_in_class=False,
776 **kwargs):
Douglas Viroelb7e27e72019-08-06 19:40:37 -0300777 if client is None:
778 client = cls.shares_v2_client
779 share_network_subnet = client.create_subnet(**kwargs)
780 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
799 security_service = client.create_security_service(ss_type, **kwargs)
800 resource = {
801 "type": "security_service",
802 "id": security_service["id"],
803 "client": client,
804 }
805 if cleanup_in_class:
806 cls.class_resources.insert(0, resource)
807 else:
808 cls.method_resources.insert(0, resource)
809 return security_service
810
811 @classmethod
812 def create_share_type(cls, name, is_public=True, client=None,
813 cleanup_in_class=True, **kwargs):
814 if client is None:
Valeriy Ponomaryova14c2252015-10-29 13:34:32 +0200815 client = cls.shares_v2_client
Marc Koderer0abc93b2015-07-15 09:18:35 +0200816 share_type = client.create_share_type(name, is_public, **kwargs)
817 resource = {
818 "type": "share_type",
819 "id": share_type["share_type"]["id"],
820 "client": client,
821 }
822 if cleanup_in_class:
823 cls.class_resources.insert(0, resource)
824 else:
825 cls.method_resources.insert(0, resource)
826 return share_type
827
haixin0d1d29f2019-08-02 16:50:45 +0800828 @classmethod
829 def update_share_type(cls, share_type_id, name=None,
830 is_public=None, description=None,
831 client=None):
832 if client is None:
833 client = cls.shares_v2_client
834 share_type = client.update_share_type(share_type_id, name,
835 is_public, description)
836 return share_type
837
Goutham Pacha Raviaf448262020-06-29 14:24:13 -0700838 @classmethod
839 def update_quotas(cls, project_id, user_id=None, cleanup=True,
840 client=None, **kwargs):
841 client = client or cls.shares_v2_client
842 updated_quotas = client.update_quotas(project_id,
843 user_id=user_id,
844 **kwargs)
845 resource = {
846 "type": "quotas",
847 "id": project_id,
848 "client": client,
849 "user_id": user_id,
850 }
851 if cleanup:
852 cls.method_resources.insert(0, resource)
853 return updated_quotas
854
Marc Koderer0abc93b2015-07-15 09:18:35 +0200855 @staticmethod
Clinton Knight4699a8c2016-08-16 22:36:13 -0400856 def add_extra_specs_to_dict(extra_specs=None):
857 """Add any required extra-specs to share type dictionary"""
Valeriy Ponomaryovad55dc52015-09-23 13:54:00 +0300858 dhss = six.text_type(CONF.share.multitenancy_enabled)
859 snapshot_support = six.text_type(
860 CONF.share.capability_snapshot_support)
Clinton Knight4699a8c2016-08-16 22:36:13 -0400861 create_from_snapshot_support = six.text_type(
862 CONF.share.capability_create_share_from_snapshot_support)
863
864 extra_specs_dict = {
Valeriy Ponomaryovad55dc52015-09-23 13:54:00 +0300865 "driver_handles_share_servers": dhss,
Marc Koderer0abc93b2015-07-15 09:18:35 +0200866 }
Clinton Knight4699a8c2016-08-16 22:36:13 -0400867
868 optional = {
869 "snapshot_support": snapshot_support,
870 "create_share_from_snapshot_support": create_from_snapshot_support,
871 }
872 # NOTE(gouthamr): In micro-versions < 2.24, snapshot_support is a
873 # required extra-spec
874 extra_specs_dict.update(optional)
875
Marc Koderer0abc93b2015-07-15 09:18:35 +0200876 if extra_specs:
Clinton Knight4699a8c2016-08-16 22:36:13 -0400877 extra_specs_dict.update(extra_specs)
878
879 return extra_specs_dict
Marc Koderer0abc93b2015-07-15 09:18:35 +0200880
881 @classmethod
Yogesh1f931ff2015-09-29 23:41:02 -0400882 def clear_share_replicas(cls, share_id, client=None):
883 client = client or cls.shares_v2_client
884 share_replicas = client.list_share_replicas(
885 share_id=share_id)
886
887 for replica in share_replicas:
888 try:
889 cls.delete_share_replica(replica['id'])
890 except exceptions.BadRequest:
891 # Ignore the exception due to deletion of last active replica
892 pass
893
894 @classmethod
Marc Koderer0abc93b2015-07-15 09:18:35 +0200895 def clear_resources(cls, resources=None):
896 """Deletes resources, that were created in test suites.
897
898 This method tries to remove resources from resource list,
899 if it is not found, assumed it was deleted in test itself.
900 It is expected, that all resources were added as LIFO
901 due to restriction of deletion resources, that is in the chain.
902
903 :param resources: dict with keys 'type','id','client' and 'deleted'
904 """
Marc Koderer0abc93b2015-07-15 09:18:35 +0200905 if resources is None:
906 resources = cls.method_resources
907 for res in resources:
908 if "deleted" not in res.keys():
909 res["deleted"] = False
910 if "client" not in res.keys():
911 res["client"] = cls.shares_client
912 if not(res["deleted"]):
913 res_id = res['id']
914 client = res["client"]
915 with handle_cleanup_exceptions():
Andreas Jaeger0cb685b2020-04-01 13:38:38 +0200916 if res["type"] == "share":
Yogesh1f931ff2015-09-29 23:41:02 -0400917 cls.clear_share_replicas(res_id)
Andrew Kerrb8436922016-06-01 15:32:43 -0400918 share_group_id = res.get('share_group_id')
919 if share_group_id:
920 params = {'share_group_id': share_group_id}
Clinton Knighte5c8f092015-08-27 15:00:23 -0400921 client.delete_share(res_id, params=params)
922 else:
923 client.delete_share(res_id)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200924 client.wait_for_resource_deletion(share_id=res_id)
Andreas Jaeger0cb685b2020-04-01 13:38:38 +0200925 elif res["type"] == "snapshot":
Marc Koderer0abc93b2015-07-15 09:18:35 +0200926 client.delete_snapshot(res_id)
927 client.wait_for_resource_deletion(snapshot_id=res_id)
Andreas Jaeger0cb685b2020-04-01 13:38:38 +0200928 elif (res["type"] == "share_network" and
yogesh06f519f2017-06-26 13:16:12 -0400929 res_id != CONF.share.share_network_id):
Victoria Martinez de la Cruzcad92012018-06-08 14:46:35 -0400930 client.delete_share_network(res_id)
931 client.wait_for_resource_deletion(sn_id=res_id)
debeltrami1753a592020-05-11 18:27:30 +0000932 elif res["type"] == "dissociate_security_service":
933 sn_id = res["extra_params"]["share_network_id"]
934 client.remove_sec_service_from_share_network(
935 sn_id=sn_id, ss_id=res_id
936 )
Andreas Jaeger0cb685b2020-04-01 13:38:38 +0200937 elif res["type"] == "security_service":
Marc Koderer0abc93b2015-07-15 09:18:35 +0200938 client.delete_security_service(res_id)
939 client.wait_for_resource_deletion(ss_id=res_id)
Andreas Jaeger0cb685b2020-04-01 13:38:38 +0200940 elif res["type"] == "share_type":
Marc Koderer0abc93b2015-07-15 09:18:35 +0200941 client.delete_share_type(res_id)
942 client.wait_for_resource_deletion(st_id=res_id)
Andreas Jaeger0cb685b2020-04-01 13:38:38 +0200943 elif res["type"] == "share_group":
Andrew Kerrb8436922016-06-01 15:32:43 -0400944 client.delete_share_group(res_id)
945 client.wait_for_resource_deletion(
946 share_group_id=res_id)
Andreas Jaeger0cb685b2020-04-01 13:38:38 +0200947 elif res["type"] == "share_group_type":
Andrew Kerrb8436922016-06-01 15:32:43 -0400948 client.delete_share_group_type(res_id)
949 client.wait_for_resource_deletion(
950 share_group_type_id=res_id)
Andreas Jaeger0cb685b2020-04-01 13:38:38 +0200951 elif res["type"] == "share_group_snapshot":
Andrew Kerrb8436922016-06-01 15:32:43 -0400952 client.delete_share_group_snapshot(res_id)
953 client.wait_for_resource_deletion(
954 share_group_snapshot_id=res_id)
Andreas Jaeger0cb685b2020-04-01 13:38:38 +0200955 elif res["type"] == "share_replica":
Yogeshbdb88102015-09-29 23:41:02 -0400956 client.delete_share_replica(res_id)
957 client.wait_for_resource_deletion(replica_id=res_id)
Andreas Jaeger0cb685b2020-04-01 13:38:38 +0200958 elif res["type"] == "share_network_subnet":
Douglas Viroelb7e27e72019-08-06 19:40:37 -0300959 sn_id = res["extra_params"]["share_network_id"]
960 client.delete_subnet(sn_id, res_id)
961 client.wait_for_resource_deletion(
962 share_network_subnet_id=res_id,
963 sn_id=sn_id)
Goutham Pacha Raviaf448262020-06-29 14:24:13 -0700964 elif res["type"] == "quotas":
965 user_id = res.get('user_id')
966 client.reset_quotas(res_id, user_id=user_id)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200967 else:
huayue97bacbf2016-01-04 09:57:39 +0800968 LOG.warning("Provided unsupported resource type for "
junbolib236c242017-07-18 18:12:37 +0800969 "cleanup '%s'. Skipping.", res["type"])
Marc Koderer0abc93b2015-07-15 09:18:35 +0200970 res["deleted"] = True
971
972 @classmethod
973 def generate_share_network_data(self):
974 data = {
975 "name": data_utils.rand_name("sn-name"),
976 "description": data_utils.rand_name("sn-desc"),
977 "neutron_net_id": data_utils.rand_name("net-id"),
978 "neutron_subnet_id": data_utils.rand_name("subnet-id"),
979 }
980 return data
981
982 @classmethod
Douglas Viroelb7e27e72019-08-06 19:40:37 -0300983 def generate_subnet_data(self):
984 data = {
985 "neutron_net_id": data_utils.rand_name("net-id"),
986 "neutron_subnet_id": data_utils.rand_name("subnet-id"),
987 }
988 return data
989
990 @classmethod
Maurice Schreiber5ac37172018-02-01 15:17:31 +0100991 def generate_security_service_data(self, set_ou=False):
Marc Koderer0abc93b2015-07-15 09:18:35 +0200992 data = {
993 "name": data_utils.rand_name("ss-name"),
994 "description": data_utils.rand_name("ss-desc"),
Valeriy Ponomaryovfcde7712015-12-14 18:06:13 +0200995 "dns_ip": utils.rand_ip(),
996 "server": utils.rand_ip(),
Marc Koderer0abc93b2015-07-15 09:18:35 +0200997 "domain": data_utils.rand_name("ss-domain"),
998 "user": data_utils.rand_name("ss-user"),
999 "password": data_utils.rand_name("ss-password"),
1000 }
Maurice Schreiber5ac37172018-02-01 15:17:31 +01001001 if set_ou:
1002 data["ou"] = data_utils.rand_name("ss-ou")
1003
Marc Koderer0abc93b2015-07-15 09:18:35 +02001004 return data
1005
1006 # Useful assertions
1007 def assertDictMatch(self, d1, d2, approx_equal=False, tolerance=0.001):
1008 """Assert two dicts are equivalent.
1009
1010 This is a 'deep' match in the sense that it handles nested
1011 dictionaries appropriately.
1012
1013 NOTE:
1014
1015 If you don't care (or don't know) a given value, you can specify
1016 the string DONTCARE as the value. This will cause that dict-item
1017 to be skipped.
1018
1019 """
1020 def raise_assertion(msg):
1021 d1str = str(d1)
1022 d2str = str(d2)
1023 base_msg = ('Dictionaries do not match. %(msg)s d1: %(d1str)s '
1024 'd2: %(d2str)s' %
1025 {"msg": msg, "d1str": d1str, "d2str": d2str})
1026 raise AssertionError(base_msg)
1027
1028 d1keys = set(d1.keys())
1029 d2keys = set(d2.keys())
1030 if d1keys != d2keys:
1031 d1only = d1keys - d2keys
1032 d2only = d2keys - d1keys
1033 raise_assertion('Keys in d1 and not d2: %(d1only)s. '
1034 'Keys in d2 and not d1: %(d2only)s' %
1035 {"d1only": d1only, "d2only": d2only})
1036
1037 for key in d1keys:
1038 d1value = d1[key]
1039 d2value = d2[key]
1040 try:
1041 error = abs(float(d1value) - float(d2value))
1042 within_tolerance = error <= tolerance
1043 except (ValueError, TypeError):
daiki kato6914b1a2016-03-16 17:16:57 +09001044 # If both values aren't convertible to float, just ignore
Marc Koderer0abc93b2015-07-15 09:18:35 +02001045 # ValueError if arg is a str, TypeError if it's something else
1046 # (like None)
1047 within_tolerance = False
1048
1049 if hasattr(d1value, 'keys') and hasattr(d2value, 'keys'):
1050 self.assertDictMatch(d1value, d2value)
1051 elif 'DONTCARE' in (d1value, d2value):
1052 continue
1053 elif approx_equal and within_tolerance:
1054 continue
1055 elif d1value != d2value:
1056 raise_assertion("d1['%(key)s']=%(d1value)s != "
1057 "d2['%(key)s']=%(d2value)s" %
1058 {
1059 "key": key,
1060 "d1value": d1value,
1061 "d2value": d2value
1062 })
1063
Alex Meadeba8a1602016-05-06 09:33:09 -04001064 def create_user_message(self):
1065 """Trigger a 'no valid host' situation to generate a message."""
1066 extra_specs = {
1067 'vendor_name': 'foobar',
1068 'driver_handles_share_servers': CONF.share.multitenancy_enabled,
1069 }
1070 share_type_name = data_utils.rand_name("share-type")
1071
1072 bogus_type = self.create_share_type(
Goutham Pacha Raviaf448262020-06-29 14:24:13 -07001073 client=self.admin_shares_v2_client,
Alex Meadeba8a1602016-05-06 09:33:09 -04001074 name=share_type_name,
1075 extra_specs=extra_specs)['share_type']
1076
1077 params = {'share_type_id': bogus_type['id'],
1078 'share_network_id': self.shares_v2_client.share_network_id}
1079 share = self.shares_v2_client.create_share(**params)
1080 self.addCleanup(self.shares_v2_client.delete_share, share['id'])
lkuchlanf7fc5b62021-01-26 14:53:43 +02001081 waiters.wait_for_resource_status(
lkuchlan540e74a2021-01-19 18:08:25 +02001082 self.shares_v2_client, share['id'], "error")
1083 return waiters.wait_for_message(self.shares_v2_client, share['id'])
Alex Meadeba8a1602016-05-06 09:33:09 -04001084
lkuchlan5af7cb42020-07-14 18:05:09 +03001085 def allow_access(self, share_id, client=None, access_type=None,
1086 access_level='rw', access_to=None, status='active',
1087 raise_rule_in_error_state=True, cleanup=True):
1088
1089 client = client or self.shares_v2_client
1090 a_type, a_to = self._get_access_rule_data_from_config()
1091 access_type = access_type or a_type
1092 access_to = access_to or a_to
1093
1094 rule = client.create_access_rule(share_id, access_type, access_to,
1095 access_level)
lkuchlanf7fc5b62021-01-26 14:53:43 +02001096 waiters.wait_for_resource_status(
1097 client, share_id, status, resource_name='access_rule',
1098 rule_id=rule['id'],
1099 raise_rule_in_error_state=raise_rule_in_error_state)
lkuchlan5af7cb42020-07-14 18:05:09 +03001100 if cleanup:
1101 self.addCleanup(client.wait_for_resource_deletion,
1102 rule_id=rule['id'], share_id=share_id)
1103 self.addCleanup(client.delete_access_rule, share_id, rule['id'])
1104 return rule
1105
Marc Koderer0abc93b2015-07-15 09:18:35 +02001106
Marc Koderer0abc93b2015-07-15 09:18:35 +02001107class BaseSharesAdminTest(BaseSharesTest):
1108 """Base test case class for all Shares Admin API tests."""
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +03001109 credentials = ('admin', )
1110
Victoria Martinez de la Cruzf6bc6fa2018-02-01 11:27:00 -05001111 @classmethod
1112 def setup_clients(cls):
1113 super(BaseSharesAdminTest, cls).setup_clients()
1114 # Initialise share clients
1115 cls.admin_shares_v2_client = cls.os_admin.share_v2.SharesV2Client()
1116
1117 @classmethod
Goutham Pacha Raviaf448262020-06-29 14:24:13 -07001118 def _create_share_type(cls, is_public=True, specs=None):
Victoria Martinez de la Cruzf6bc6fa2018-02-01 11:27:00 -05001119 name = data_utils.rand_name("unique_st_name")
1120 extra_specs = cls.add_extra_specs_to_dict(specs)
1121 return cls.create_share_type(
Goutham Pacha Raviaf448262020-06-29 14:24:13 -07001122 name, extra_specs=extra_specs, is_public=is_public,
Victoria Martinez de la Cruzf6bc6fa2018-02-01 11:27:00 -05001123 client=cls.admin_shares_v2_client)['share_type']
1124
1125 @classmethod
1126 def _create_share_group_type(cls):
1127 share_group_type_name = data_utils.rand_name("unique_sgtype_name")
1128 return cls.create_share_group_type(
1129 name=share_group_type_name, share_types=[cls.share_type_id],
1130 client=cls.admin_shares_v2_client)
1131
Lucio Seki37056942019-01-24 15:40:20 -02001132 def _create_share_for_manage(self):
1133 creation_data = {
1134 'share_type_id': self.st['share_type']['id'],
1135 'share_protocol': self.protocol,
1136 }
1137
1138 share = self.create_share(**creation_data)
1139 share = self.shares_v2_client.get_share(share['id'])
1140
1141 if utils.is_microversion_ge(CONF.share.max_api_microversion, "2.9"):
1142 el = self.shares_v2_client.list_share_export_locations(share["id"])
1143 share["export_locations"] = el
1144
1145 return share
1146
1147 def _unmanage_share_and_wait(self, share):
1148 self.shares_v2_client.unmanage_share(share['id'])
1149 self.shares_v2_client.wait_for_resource_deletion(share_id=share['id'])
1150
1151 def _reset_state_and_delete_share(self, share):
1152 self.shares_v2_client.reset_state(share['id'])
1153 self._delete_share_and_wait(share)
1154
1155 def _delete_snapshot_and_wait(self, snap):
1156 self.shares_v2_client.delete_snapshot(snap['id'])
1157 self.shares_v2_client.wait_for_resource_deletion(
1158 snapshot_id=snap['id']
1159 )
1160 self.assertRaises(exceptions.NotFound,
1161 self.shares_v2_client.get_snapshot,
1162 snap['id'])
1163
1164 def _delete_share_and_wait(self, share):
1165 self.shares_v2_client.delete_share(share['id'])
1166 self.shares_v2_client.wait_for_resource_deletion(share_id=share['id'])
1167 self.assertRaises(exceptions.NotFound,
1168 self.shares_v2_client.get_share,
1169 share['id'])
1170
1171 def _manage_share(self, share, name, description, share_server_id):
1172 managed_share = self.shares_v2_client.manage_share(
1173 service_host=share['host'],
1174 export_path=share['export_locations'][0],
1175 protocol=share['share_proto'],
1176 share_type_id=self.share_type['share_type']['id'],
1177 name=name,
1178 description=description,
1179 share_server_id=share_server_id
1180 )
lkuchlanf7fc5b62021-01-26 14:53:43 +02001181 waiters.wait_for_resource_status(
lkuchlan540e74a2021-01-19 18:08:25 +02001182 self.shares_v2_client, managed_share['id'],
1183 constants.STATUS_AVAILABLE
Lucio Seki37056942019-01-24 15:40:20 -02001184 )
1185
1186 return managed_share
1187
1188 def _unmanage_share_server_and_wait(self, server):
1189 self.shares_v2_client.unmanage_share_server(server['id'])
1190 self.shares_v2_client.wait_for_resource_deletion(
1191 server_id=server['id']
1192 )
1193
1194 def _manage_share_server(self, share_server, fields=None):
1195 params = fields or {}
Douglas Viroelb7e27e72019-08-06 19:40:37 -03001196 subnet_id = params.get('share_network_subnet_id', None)
Lucio Seki37056942019-01-24 15:40:20 -02001197 managed_share_server = self.shares_v2_client.manage_share_server(
1198 params.get('host', share_server['host']),
1199 params.get('share_network_id', share_server['share_network_id']),
1200 params.get('identifier', share_server['identifier']),
Douglas Viroelb7e27e72019-08-06 19:40:37 -03001201 share_network_subnet_id=subnet_id,
Lucio Seki37056942019-01-24 15:40:20 -02001202 )
lkuchlanf7fc5b62021-01-26 14:53:43 +02001203 waiters.wait_for_resource_status(
lkuchlan540e74a2021-01-19 18:08:25 +02001204 self.shares_v2_client, managed_share_server['id'],
lkuchlanf7fc5b62021-01-26 14:53:43 +02001205 constants.SERVER_STATE_ACTIVE, resource_name='share_server'
Lucio Seki37056942019-01-24 15:40:20 -02001206 )
1207
1208 return managed_share_server
1209
1210 def _delete_share_server_and_wait(self, share_server_id):
1211 self.shares_v2_client.delete_share_server(
1212 share_server_id
1213 )
1214 self.shares_v2_client.wait_for_resource_deletion(
1215 server_id=share_server_id)
1216
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +03001217
1218class BaseSharesMixedTest(BaseSharesTest):
1219 """Base test case class for all Shares API tests with all user roles."""
1220 credentials = ('primary', 'alt', 'admin')
Marc Koderer0abc93b2015-07-15 09:18:35 +02001221
Goutham Pacha Ravic678e212020-03-20 11:13:47 -07001222 # Will be cleaned up in resource_cleanup if the class
1223 class_project_users_created = []
1224
1225 @classmethod
1226 def resource_cleanup(cls):
1227 cls.clear_project_users(cls.class_project_users_created)
1228 super(BaseSharesMixedTest, cls).resource_cleanup()
1229
1230 @classmethod
1231 def clear_project_users(cls, users=None):
1232 users = users or cls.class_project_users_created
1233 for user in users:
1234 with handle_cleanup_exceptions():
1235 cls.os_admin.creds_client.delete_user(user['id'])
1236
Marc Koderer0abc93b2015-07-15 09:18:35 +02001237 @classmethod
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +03001238 def setup_clients(cls):
1239 super(BaseSharesMixedTest, cls).setup_clients()
Andrea Frittoli (andreaf)369391a2016-06-27 18:59:13 +01001240 # Initialise share clients
1241 cls.admin_shares_client = cls.os_admin.share_v1.SharesClient()
1242 cls.admin_shares_v2_client = cls.os_admin.share_v2.SharesV2Client()
1243 cls.alt_shares_client = cls.os_alt.share_v1.SharesClient()
1244 cls.alt_shares_v2_client = cls.os_alt.share_v2.SharesV2Client()
1245 # Initialise network clients
1246 cls.os_admin.networks_client = cls.os_admin.network.NetworksClient()
1247 cls.os_alt.networks_client = cls.os_alt.network.NetworksClient()
Goutham Pacha Ravic678e212020-03-20 11:13:47 -07001248 # Initialise identity clients
1249 cls.admin_project = cls.os_admin.auth_provider.auth_data[1]['project']
1250 identity_clients = getattr(
1251 cls.os_admin, 'identity_%s' % CONF.identity.auth_version)
1252 cls.os_admin.identity_client = identity_clients.IdentityClient()
1253 cls.os_admin.projects_client = identity_clients.ProjectsClient()
1254 cls.os_admin.users_client = identity_clients.UsersClient()
1255 cls.os_admin.roles_client = identity_clients.RolesClient()
1256 cls.os_admin.domains_client = (
1257 cls.os_admin.identity_v3.DomainsClient() if
1258 CONF.identity.auth_version == 'v3' else None)
Goutham Pacha Ravi10d49492021-02-23 16:05:21 -08001259 cls.admin_project_member_client = cls.create_user_and_get_client(
1260 project=cls.admin_project, add_member_role=True)
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +03001261
1262 if CONF.share.multitenancy_enabled:
1263 admin_share_network_id = cls.provide_share_network(
1264 cls.admin_shares_v2_client, cls.os_admin.networks_client)
1265 cls.admin_shares_client.share_network_id = admin_share_network_id
1266 cls.admin_shares_v2_client.share_network_id = (
1267 admin_share_network_id)
1268
1269 alt_share_network_id = cls.provide_share_network(
1270 cls.alt_shares_v2_client, cls.os_alt.networks_client)
1271 cls.alt_shares_client.share_network_id = alt_share_network_id
1272 cls.alt_shares_v2_client.share_network_id = alt_share_network_id
Victoria Martinez de la Cruzf6bc6fa2018-02-01 11:27:00 -05001273
1274 @classmethod
Goutham Pacha Ravi10d49492021-02-23 16:05:21 -08001275 def create_user_and_get_client(cls, project=None, add_member_role=True):
Goutham Pacha Ravic678e212020-03-20 11:13:47 -07001276 """Create a user in specified project & set share clients for user
1277
1278 The user will have all roles specified in tempest.conf
1279 :param: project: a dictionary with project ID and name, if not
1280 specified, the value will be cls.admin_project
1281 """
1282 project_domain_name = (
1283 cls.os_admin.identity_client.auth_provider.credentials.get(
1284 'project_domain_name', 'Default'))
1285 cls.os_admin.creds_client = cred_client.get_creds_client(
1286 cls.os_admin.identity_client, cls.os_admin.projects_client,
1287 cls.os_admin.users_client, cls.os_admin.roles_client,
1288 cls.os_admin.domains_client, project_domain_name)
1289
1290 # User info
1291 project = project or cls.admin_project
1292 username = data_utils.rand_name('manila_%s' % project['id'])
1293 password = data_utils.rand_password()
1294 email = '%s@example.org' % username
1295
1296 user = cls.os_admin.creds_client.create_user(
1297 username, password, project, email)
1298 cls.class_project_users_created.append(user)
1299
Goutham Pacha Ravi10d49492021-02-23 16:05:21 -08001300 tempest_roles_to_assign = CONF.auth.tempest_roles or []
1301 if "member" not in tempest_roles_to_assign and add_member_role:
1302 tempest_roles_to_assign.append("member")
1303
1304 for role in tempest_roles_to_assign:
1305 cls.os_admin.creds_client.assign_user_role(user, project, role)
Goutham Pacha Ravic678e212020-03-20 11:13:47 -07001306
1307 user_creds = cls.os_admin.creds_client.get_credentials(
1308 user, project, password)
1309 os = clients.Clients(user_creds)
1310 os.shares_v1_client = os.share_v1.SharesClient()
1311 os.shares_v2_client = os.share_v2.SharesV2Client()
1312 return os
1313
1314 @classmethod
Goutham Pacha Raviaf448262020-06-29 14:24:13 -07001315 def _create_share_type(cls, is_public=True, specs=None):
Victoria Martinez de la Cruzf6bc6fa2018-02-01 11:27:00 -05001316 name = data_utils.rand_name("unique_st_name")
1317 extra_specs = cls.add_extra_specs_to_dict(specs)
1318 return cls.create_share_type(
Goutham Pacha Raviaf448262020-06-29 14:24:13 -07001319 name, extra_specs=extra_specs, is_public=is_public,
Victoria Martinez de la Cruzf6bc6fa2018-02-01 11:27:00 -05001320 client=cls.admin_shares_v2_client)['share_type']
1321
1322 @classmethod
1323 def _create_share_group_type(cls):
1324 share_group_type_name = data_utils.rand_name("unique_sgtype_name")
1325 return cls.create_share_group_type(
1326 name=share_group_type_name, share_types=[cls.share_type_id],
1327 client=cls.admin_shares_v2_client)