blob: 0e875c24434f84ad898490a5f53f299f332098f8 [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
Marc Koderer0abc93b2015-07-15 09:18:35 +020097class BaseSharesTest(test.BaseTestCase):
98 """Base test case class for all Manila API tests."""
99
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +0300100 credentials = ('primary', )
Marc Koderer0abc93b2015-07-15 09:18:35 +0200101 force_tenant_isolation = False
Vitaliy Levitksicfebfff2016-12-15 16:16:35 +0200102 protocols = ["nfs", "cifs", "glusterfs", "hdfs", "cephfs", "maprfs"]
Marc Koderer0abc93b2015-07-15 09:18:35 +0200103
104 # Will be cleaned up in resource_cleanup
105 class_resources = []
106
107 # Will be cleaned up in tearDown method
108 method_resources = []
109
Andrea Frittoli (andreaf)369391a2016-06-27 18:59:13 +0100110 # NOTE(andreaf) Override the client manager class to be used, so that
111 # a stable class is used, which includes plugin registered services as well
112 client_manager = clients.Clients
113
Marc Koderer0abc93b2015-07-15 09:18:35 +0200114 @classmethod
Daniel Melladoe5269142017-01-12 12:17:58 +0000115 def skip_checks(cls):
116 super(BaseSharesTest, cls).skip_checks()
117 if not CONF.service_available.manila:
118 raise cls.skipException("Manila support is required")
lkuchlana3b6f7a2020-01-07 10:45:45 +0200119 if not any(p in CONF.share.enable_protocols for p in cls.protocols):
120 skip_msg = "%s tests are disabled" % CONF.share.enable_protocols
121 raise cls.skipException(skip_msg)
Daniel Melladoe5269142017-01-12 12:17:58 +0000122
123 @classmethod
Marc Koderer0abc93b2015-07-15 09:18:35 +0200124 def verify_nonempty(cls, *args):
125 if not all(args):
126 msg = "Missing API credentials in configuration."
127 raise cls.skipException(msg)
128
129 @classmethod
Goutham Pacha Ravi4a0b7322020-06-30 19:42:45 -0700130 def setup_credentials(cls):
131 # This call is used to tell the credential allocator to create
132 # network resources for this test case. NOTE: it must go before the
133 # super call, to override decisions in the base classes.
134 network_resources = {}
135 if (CONF.share.multitenancy_enabled and
136 CONF.share.create_networks_when_multitenancy_enabled):
137 # We're testing a DHSS=True driver, and manila is configured with
138 # NeutronNetworkPlugin (or a derivative) that supports creating
139 # share networks with project neutron networks, so lets ask for
140 # neutron network resources to be created with test credentials
141 network_resources.update({'network': True,
142 'subnet': True,
143 'router': True})
144 cls.set_network_resources(**network_resources)
145 super(BaseSharesTest, cls).setup_credentials()
146
147 @classmethod
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +0300148 def setup_clients(cls):
149 super(BaseSharesTest, cls).setup_clients()
150 os = getattr(cls, 'os_%s' % cls.credentials[0])
Andrea Frittoli (andreaf)369391a2016-06-27 18:59:13 +0100151 # Initialise share clients for test credentials
152 cls.shares_client = os.share_v1.SharesClient()
153 cls.shares_v2_client = os.share_v2.SharesV2Client()
154 # Initialise network clients for test credentials
Goutham Pacha Ravi4a0b7322020-06-30 19:42:45 -0700155 cls.networks_client = None
156 cls.subnets_client = None
Andrea Frittoli (andreaf)369391a2016-06-27 18:59:13 +0100157 if CONF.service_available.neutron:
158 cls.networks_client = os.network.NetworksClient()
159 cls.subnets_client = os.network.SubnetsClient()
Valeriy Ponomaryov4fb305f2016-10-21 13:46:47 +0300160
Goutham Pacha Ravi4a0b7322020-06-30 19:42:45 -0700161 # If DHSS=True, create a share network and set it in the client
162 # for easy access.
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +0300163 if CONF.share.multitenancy_enabled:
Valeriy Ponomaryovc5dae272016-06-10 18:29:24 +0300164 if (not CONF.service_available.neutron and
165 CONF.share.create_networks_when_multitenancy_enabled):
Goutham Pacha Ravi4a0b7322020-06-30 19:42:45 -0700166 raise cls.skipException(
167 "Neutron support is required when "
168 "CONF.share.create_networks_when_multitenancy_enabled "
169 "is set to True")
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +0300170 share_network_id = cls.provide_share_network(
Goutham Pacha Ravi4a0b7322020-06-30 19:42:45 -0700171 cls.shares_client, cls.networks_client)
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +0300172 cls.shares_client.share_network_id = share_network_id
173 cls.shares_v2_client.share_network_id = share_network_id
174
Marc Koderer0abc93b2015-07-15 09:18:35 +0200175 def setUp(self):
176 super(BaseSharesTest, self).setUp()
Valeriy Ponomaryovdd162cb2016-01-20 19:09:49 +0200177 self.addCleanup(self.clear_resources)
Valeriy Ponomaryov2abf5d72016-06-01 18:30:12 +0300178 verify_test_has_appropriate_tags(self)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200179
180 @classmethod
181 def resource_cleanup(cls):
Marc Koderer0abc93b2015-07-15 09:18:35 +0200182 cls.clear_resources(cls.class_resources)
Sam Wan241029c2016-07-26 03:37:42 -0400183 super(BaseSharesTest, cls).resource_cleanup()
Marc Koderer0abc93b2015-07-15 09:18:35 +0200184
185 @classmethod
debeltrami1753a592020-05-11 18:27:30 +0000186 def provide_and_associate_security_services(
187 cls, shares_client, share_network_id, cleanup_in_class=True):
188 """Creates a security service and associates to a share network.
189
190 This method creates security services based on the Multiopt
191 defined in tempest configuration named security_service. When this
192 configuration is not provided, the method will return None.
193 After the security service creation, this method also associates
194 the security service to a share network.
195
196 :param shares_client: shares client, which requires the provisioning
197 :param share_network_id: id of the share network to associate the
198 security service
199 :param cleanup_in_class: if the security service and the association
200 will be removed in the method teardown or class teardown
201 :returns: None -- if the security service configuration is not
202 defined
203 """
204
205 ss_configs = CONF.share.security_service
206 if not ss_configs:
207 return
208
209 for ss_config in ss_configs:
210 ss_name = "ss_autogenerated_by_tempest_%s" % (
211 ss_config.get("ss_type"))
212
213 ss_params = {
214 "name": ss_name,
215 "dns_ip": ss_config.get("ss_dns_ip"),
216 "server": ss_config.get("ss_server"),
217 "domain": ss_config.get("ss_domain"),
218 "user": ss_config.get("ss_user"),
219 "password": ss_config.get("ss_password")
220 }
221 ss_type = ss_config.get("ss_type")
222 security_service = cls.create_security_service(
223 ss_type,
224 client=shares_client,
225 cleanup_in_class=cleanup_in_class,
226 **ss_params)
227
228 cls.add_sec_service_to_share_network(
229 shares_client, share_network_id,
230 security_service["id"],
231 cleanup_in_class=cleanup_in_class)
232
233 @classmethod
234 def add_sec_service_to_share_network(
235 cls, client, share_network_id,
236 security_service_id, cleanup_in_class=True):
237 """Associates a security service to a share network.
238
239 This method associates a security service provided by
240 the security service configuration with a specific
241 share network.
242
243 :param share_network_id: the share network id to be
244 associate with a given security service
245 :param security_service_id: the security service id
246 to be associate with a given share network
247 :param cleanup_in_class: if the resources will be
248 dissociate in the method teardown or class teardown
249 """
250
251 client.add_sec_service_to_share_network(
252 share_network_id,
253 security_service_id)
254 resource = {
255 "type": "dissociate_security_service",
256 "id": security_service_id,
257 "extra_params": {
258 "share_network_id": share_network_id
259 },
260 "client": client,
261 }
262
263 if cleanup_in_class:
264 cls.class_resources.insert(0, resource)
265 else:
266 cls.method_resources.insert(0, resource)
267
268 @classmethod
Valeriy Ponomaryov48a2bd72015-11-05 13:22:44 +0200269 def provide_share_network(cls, shares_client, networks_client,
Rodrigo Barbieri58d9de32016-09-06 13:16:47 -0300270 ignore_multitenancy_config=False):
Goutham Pacha Ravi4a0b7322020-06-30 19:42:45 -0700271 """Get or create share network for DHSS=True drivers
Marc Koderer0abc93b2015-07-15 09:18:35 +0200272
Goutham Pacha Ravi4a0b7322020-06-30 19:42:45 -0700273 When testing DHSS=True (multitenancy_enabled) drivers, shares must
274 be requested on share networks.
Marc Koderer0abc93b2015-07-15 09:18:35 +0200275 :returns: str -- share network id for shares_client tenant
Goutham Pacha Ravi4a0b7322020-06-30 19:42:45 -0700276 :returns: None -- if single-tenant driver (DHSS=False) is used
Marc Koderer0abc93b2015-07-15 09:18:35 +0200277 """
278
Rodrigo Barbieri58d9de32016-09-06 13:16:47 -0300279 if (not ignore_multitenancy_config and
280 not CONF.share.multitenancy_enabled):
Goutham Pacha Ravi4a0b7322020-06-30 19:42:45 -0700281 # Assumed usage of a single-tenant driver (DHSS=False)
debeltrami1753a592020-05-11 18:27:30 +0000282 return None
Goutham Pacha Raviaf448262020-06-29 14:24:13 -0700283
Goutham Pacha Ravi4a0b7322020-06-30 19:42:45 -0700284 if shares_client.share_network_id:
285 # Share-network already exists, use it
286 return shares_client.share_network_id
debeltrami1753a592020-05-11 18:27:30 +0000287
Goutham Pacha Ravi4a0b7322020-06-30 19:42:45 -0700288 sn_name = "autogenerated_by_tempest"
289 sn_desc = "This share-network was created by tempest"
Rodrigo Barbieri58d9de32016-09-06 13:16:47 -0300290
Goutham Pacha Ravi4a0b7322020-06-30 19:42:45 -0700291 if not CONF.share.create_networks_when_multitenancy_enabled:
292 # We need a new share network, but don't need to associate
293 # any neutron networks to it - this configuration is used
294 # when manila is configured with "StandaloneNetworkPlugin"
295 # or "NeutronSingleNetworkPlugin" where all tenants share
296 # a single backend network where shares are exported.
297 sn = cls.create_share_network(cleanup_in_class=True,
298 client=shares_client,
299 add_security_services=True,
300 name=sn_name,
301 description=sn_desc)
302 return sn['id']
Marc Koderer0abc93b2015-07-15 09:18:35 +0200303
Goutham Pacha Ravi4a0b7322020-06-30 19:42:45 -0700304 # Retrieve non-public network list owned by the tenant
305 filters = {'project_id': shares_client.tenant_id,
306 'shared': False}
307 tenant_networks = (
308 networks_client.list_networks(**filters).get('networks', [])
309 )
310 tenant_networks_with_subnet = (
311 [n for n in tenant_networks if n['subnets']]
312 )
debeltrami1753a592020-05-11 18:27:30 +0000313
Goutham Pacha Ravi4a0b7322020-06-30 19:42:45 -0700314 if not tenant_networks_with_subnet:
315 # This can only occur if using tempest's pre-provisioned
316 # credentials and not allocating networks to them
317 raise cls.skipException(
318 "Test credentials must provide at least one "
319 "non-shared project network with a valid subnet when "
320 "CONF.share.create_networks_when_multitenancy_enabled is "
321 "set to True.")
debeltrami1753a592020-05-11 18:27:30 +0000322
Goutham Pacha Ravi4a0b7322020-06-30 19:42:45 -0700323 net_id = tenant_networks_with_subnet[0]['id']
324 subnet_id = tenant_networks_with_subnet[0]['subnets'][0]
debeltrami1753a592020-05-11 18:27:30 +0000325
Goutham Pacha Ravi4a0b7322020-06-30 19:42:45 -0700326 # Create suitable share-network
327 sn = cls.create_share_network(cleanup_in_class=True,
328 client=shares_client,
329 add_security_services=True,
330 name=sn_name,
331 description=sn_desc,
332 neutron_net_id=net_id,
333 neutron_subnet_id=subnet_id)
debeltrami1753a592020-05-11 18:27:30 +0000334
Goutham Pacha Ravi4a0b7322020-06-30 19:42:45 -0700335 return sn['id']
Marc Koderer0abc93b2015-07-15 09:18:35 +0200336
337 @classmethod
marcusvrne0d7cfd2016-06-24 12:27:55 -0300338 def _create_share(cls, share_protocol=None, size=None, name=None,
Marc Koderer0abc93b2015-07-15 09:18:35 +0200339 snapshot_id=None, description=None, metadata=None,
340 share_network_id=None, share_type_id=None,
Andrew Kerrb8436922016-06-01 15:32:43 -0400341 share_group_id=None, client=None,
Clinton Knighte5c8f092015-08-27 15:00:23 -0400342 cleanup_in_class=True, is_public=False, **kwargs):
Valeriy Ponomaryov1aaa72d2015-09-08 12:59:41 +0300343 client = client or cls.shares_v2_client
Marc Koderer0abc93b2015-07-15 09:18:35 +0200344 description = description or "Tempest's share"
yogesh06f519f2017-06-26 13:16:12 -0400345 share_network_id = (share_network_id or
346 CONF.share.share_network_id or
347 client.share_network_id or None)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200348 metadata = metadata or {}
marcusvrne0d7cfd2016-06-24 12:27:55 -0300349 size = size or CONF.share.share_size
Clinton Knighte5c8f092015-08-27 15:00:23 -0400350 kwargs.update({
Marc Koderer0abc93b2015-07-15 09:18:35 +0200351 'share_protocol': share_protocol,
352 'size': size,
353 'name': name,
354 'snapshot_id': snapshot_id,
355 'description': description,
356 'metadata': metadata,
357 'share_network_id': share_network_id,
358 'share_type_id': share_type_id,
359 'is_public': is_public,
Clinton Knighte5c8f092015-08-27 15:00:23 -0400360 })
Andrew Kerrb8436922016-06-01 15:32:43 -0400361 if share_group_id:
362 kwargs['share_group_id'] = share_group_id
Andrew Kerrbf31e912015-07-29 10:39:38 -0400363
lkuchlan86f24322021-04-27 14:23:05 +0300364 share = client.create_share(**kwargs)['share']
Andrew Kerrbf31e912015-07-29 10:39:38 -0400365 resource = {"type": "share", "id": share["id"], "client": client,
Andrew Kerrb8436922016-06-01 15:32:43 -0400366 "share_group_id": share_group_id}
Marc Koderer0abc93b2015-07-15 09:18:35 +0200367 cleanup_list = (cls.class_resources if cleanup_in_class else
368 cls.method_resources)
369 cleanup_list.insert(0, resource)
370 return share
371
372 @classmethod
Rodrigo Barbieri427bc052016-06-06 17:10:06 -0300373 def migrate_share(
374 cls, share_id, dest_host, wait_for_status, client=None,
Rodrigo Barbieri027df982016-11-24 15:52:03 -0200375 force_host_assisted_migration=False, writable=False,
376 nondisruptive=False, preserve_metadata=False,
377 preserve_snapshots=False, new_share_network_id=None,
Rodrigo Barbierid38d2f52016-07-19 22:24:56 -0300378 new_share_type_id=None, **kwargs):
Clinton Knighte5c8f092015-08-27 15:00:23 -0400379 client = client or cls.shares_v2_client
Rodrigo Barbieri427bc052016-06-06 17:10:06 -0300380 client.migrate_share(
381 share_id, dest_host,
382 force_host_assisted_migration=force_host_assisted_migration,
Rodrigo Barbieri027df982016-11-24 15:52:03 -0200383 writable=writable, preserve_metadata=preserve_metadata,
384 nondisruptive=nondisruptive, preserve_snapshots=preserve_snapshots,
Rodrigo Barbieri427bc052016-06-06 17:10:06 -0300385 new_share_network_id=new_share_network_id,
Rodrigo Barbierid38d2f52016-07-19 22:24:56 -0300386 new_share_type_id=new_share_type_id, **kwargs)
lkuchlan540e74a2021-01-19 18:08:25 +0200387 share = waiters.wait_for_migration_status(
388 client, share_id, dest_host, wait_for_status, **kwargs)
Rodrigo Barbierie3305122016-02-03 14:32:24 -0200389 return share
390
391 @classmethod
392 def migration_complete(cls, share_id, dest_host, client=None, **kwargs):
393 client = client or cls.shares_v2_client
394 client.migration_complete(share_id, **kwargs)
lkuchlan540e74a2021-01-19 18:08:25 +0200395 share = waiters.wait_for_migration_status(
396 client, share_id, dest_host, 'migration_success', **kwargs)
Rodrigo Barbierib7137ad2015-09-06 22:53:16 -0300397 return share
398
399 @classmethod
Rodrigo Barbieric9abf282016-08-24 22:01:31 -0300400 def migration_cancel(cls, share_id, dest_host, client=None, **kwargs):
401 client = client or cls.shares_v2_client
402 client.migration_cancel(share_id, **kwargs)
lkuchlan540e74a2021-01-19 18:08:25 +0200403 share = waiters.wait_for_migration_status(
404 client, share_id, dest_host, 'migration_cancelled', **kwargs)
Rodrigo Barbieric9abf282016-08-24 22:01:31 -0300405 return share
406
407 @classmethod
Marc Koderer0abc93b2015-07-15 09:18:35 +0200408 def create_share(cls, *args, **kwargs):
409 """Create one share and wait for available state. Retry if allowed."""
410 result = cls.create_shares([{"args": args, "kwargs": kwargs}])
411 return result[0]
412
413 @classmethod
414 def create_shares(cls, share_data_list):
415 """Creates several shares in parallel with retries.
416
417 Use this method when you want to create more than one share at same
418 time. Especially if config option 'share.share_creation_retry_number'
419 has value more than zero (0).
420 All shares will be expected to have 'available' status with or without
421 recreation else error will be raised.
422
423 :param share_data_list: list -- list of dictionaries with 'args' and
424 'kwargs' for '_create_share' method of this base class.
425 example of data:
426 share_data_list=[{'args': ['quuz'], 'kwargs': {'foo': 'bar'}}}]
427 :returns: list -- list of shares created using provided data.
428 """
429
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +0300430 for d in share_data_list:
Marc Koderer0abc93b2015-07-15 09:18:35 +0200431 if not isinstance(d, dict):
432 raise exceptions.TempestException(
433 "Expected 'dict', got '%s'" % type(d))
434 if "args" not in d:
435 d["args"] = []
436 if "kwargs" not in d:
437 d["kwargs"] = {}
438 if len(d) > 2:
439 raise exceptions.TempestException(
440 "Expected only 'args' and 'kwargs' keys. "
441 "Provided %s" % list(d))
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +0300442
443 data = []
444 for d in share_data_list:
445 client = d["kwargs"].pop("client", cls.shares_v2_client)
yogeshdb32f462016-09-28 15:09:50 -0400446 wait_for_status = d["kwargs"].pop("wait_for_status", True)
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +0300447 local_d = {
448 "args": d["args"],
449 "kwargs": copy.deepcopy(d["kwargs"]),
450 }
451 local_d["kwargs"]["client"] = client
452 local_d["share"] = cls._create_share(
453 *local_d["args"], **local_d["kwargs"])
454 local_d["cnt"] = 0
455 local_d["available"] = False
yogeshdb32f462016-09-28 15:09:50 -0400456 local_d["wait_for_status"] = wait_for_status
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +0300457 data.append(local_d)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200458
459 while not all(d["available"] for d in data):
460 for d in data:
yogeshdb32f462016-09-28 15:09:50 -0400461 if not d["wait_for_status"]:
462 d["available"] = True
Marc Koderer0abc93b2015-07-15 09:18:35 +0200463 if d["available"]:
464 continue
Valeriy Ponomaryov1a3e3382016-06-08 15:17:16 +0300465 client = d["kwargs"]["client"]
466 share_id = d["share"]["id"]
Marc Koderer0abc93b2015-07-15 09:18:35 +0200467 try:
lkuchlanf7fc5b62021-01-26 14:53:43 +0200468 waiters.wait_for_resource_status(
lkuchlan540e74a2021-01-19 18:08:25 +0200469 client, share_id, "available")
Marc Koderer0abc93b2015-07-15 09:18:35 +0200470 d["available"] = True
471 except (share_exceptions.ShareBuildErrorException,
472 exceptions.TimeoutException) as e:
473 if CONF.share.share_creation_retry_number > d["cnt"]:
474 d["cnt"] += 1
475 msg = ("Share '%s' failed to be built. "
Valeriy Ponomaryov1a3e3382016-06-08 15:17:16 +0300476 "Trying create another." % share_id)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200477 LOG.error(msg)
478 LOG.error(e)
Valeriy Ponomaryov1a3e3382016-06-08 15:17:16 +0300479 cg_id = d["kwargs"].get("consistency_group_id")
480 if cg_id:
481 # NOTE(vponomaryov): delete errored share
482 # immediately in case share is part of CG.
483 client.delete_share(
484 share_id,
485 params={"consistency_group_id": cg_id})
486 client.wait_for_resource_deletion(
487 share_id=share_id)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200488 d["share"] = cls._create_share(
489 *d["args"], **d["kwargs"])
490 else:
gecong197358663802016-08-25 11:08:45 +0800491 raise
Marc Koderer0abc93b2015-07-15 09:18:35 +0200492
493 return [d["share"] for d in data]
494
495 @classmethod
Andrew Kerrb8436922016-06-01 15:32:43 -0400496 def create_share_group(cls, client=None, cleanup_in_class=True,
497 share_network_id=None, **kwargs):
Clinton Knighte5c8f092015-08-27 15:00:23 -0400498 client = client or cls.shares_v2_client
Andrew Kerrb8436922016-06-01 15:32:43 -0400499 if kwargs.get('source_share_group_snapshot_id') is None:
Goutham Pacha Ravi9221f5e2016-04-21 13:17:49 -0400500 kwargs['share_network_id'] = (share_network_id or
501 client.share_network_id or None)
lkuchlan86f24322021-04-27 14:23:05 +0300502 share_group = client.create_share_group(**kwargs)['share_group']
Andrew Kerrbf31e912015-07-29 10:39:38 -0400503 resource = {
Andrew Kerrb8436922016-06-01 15:32:43 -0400504 "type": "share_group",
505 "id": share_group["id"],
506 "client": client,
507 }
Andrew Kerrbf31e912015-07-29 10:39:38 -0400508 if cleanup_in_class:
509 cls.class_resources.insert(0, resource)
510 else:
511 cls.method_resources.insert(0, resource)
512
Andrew Kerrb8436922016-06-01 15:32:43 -0400513 if kwargs.get('source_share_group_snapshot_id'):
514 new_share_group_shares = client.list_shares(
Andrew Kerrbf31e912015-07-29 10:39:38 -0400515 detailed=True,
lkuchlan86f24322021-04-27 14:23:05 +0300516 params={'share_group_id': share_group['id']})['shares']
Andrew Kerrbf31e912015-07-29 10:39:38 -0400517
Andrew Kerrb8436922016-06-01 15:32:43 -0400518 for share in new_share_group_shares:
Andrew Kerrbf31e912015-07-29 10:39:38 -0400519 resource = {"type": "share",
520 "id": share["id"],
521 "client": client,
Andrew Kerrb8436922016-06-01 15:32:43 -0400522 "share_group_id": share.get("share_group_id")}
Andrew Kerrbf31e912015-07-29 10:39:38 -0400523 if cleanup_in_class:
524 cls.class_resources.insert(0, resource)
525 else:
526 cls.method_resources.insert(0, resource)
527
lkuchlanf7fc5b62021-01-26 14:53:43 +0200528 waiters.wait_for_resource_status(
529 client, share_group['id'], 'available',
530 resource_name='share_group')
Andrew Kerrb8436922016-06-01 15:32:43 -0400531 return share_group
532
533 @classmethod
534 def create_share_group_type(cls, name=None, share_types=(), is_public=None,
535 group_specs=None, client=None,
536 cleanup_in_class=True, **kwargs):
537 client = client or cls.shares_v2_client
Valeriy Ponomaryove92f09f2017-03-16 17:25:47 +0300538 if (group_specs is None and
539 CONF.share.capability_sg_consistent_snapshot_support):
Valeriy Ponomaryov3c188932017-03-15 19:06:23 +0300540 group_specs = {
541 'consistent_snapshot_support': (
542 CONF.share.capability_sg_consistent_snapshot_support),
543 }
Andrew Kerrb8436922016-06-01 15:32:43 -0400544 share_group_type = client.create_share_group_type(
545 name=name,
546 share_types=share_types,
547 is_public=is_public,
548 group_specs=group_specs,
lkuchlan86f24322021-04-27 14:23:05 +0300549 **kwargs)['share_group_type']
Andrew Kerrb8436922016-06-01 15:32:43 -0400550 resource = {
551 "type": "share_group_type",
552 "id": share_group_type["id"],
553 "client": client,
554 }
555 if cleanup_in_class:
556 cls.class_resources.insert(0, resource)
557 else:
558 cls.method_resources.insert(0, resource)
559 return share_group_type
Andrew Kerrbf31e912015-07-29 10:39:38 -0400560
561 @classmethod
Marc Koderer0abc93b2015-07-15 09:18:35 +0200562 def create_snapshot_wait_for_active(cls, share_id, name=None,
563 description=None, force=False,
564 client=None, cleanup_in_class=True):
565 if client is None:
Yogesh1f931ff2015-09-29 23:41:02 -0400566 client = cls.shares_v2_client
Marc Koderer0abc93b2015-07-15 09:18:35 +0200567 if description is None:
568 description = "Tempest's snapshot"
lkuchlan86f24322021-04-27 14:23:05 +0300569 snapshot = client.create_snapshot(
570 share_id, name, description, force)['snapshot']
Marc Koderer0abc93b2015-07-15 09:18:35 +0200571 resource = {
572 "type": "snapshot",
573 "id": snapshot["id"],
574 "client": client,
575 }
576 if cleanup_in_class:
577 cls.class_resources.insert(0, resource)
578 else:
579 cls.method_resources.insert(0, resource)
lkuchlanf7fc5b62021-01-26 14:53:43 +0200580 waiters.wait_for_resource_status(client, snapshot["id"], "available",
581 resource_name='snapshot')
Marc Koderer0abc93b2015-07-15 09:18:35 +0200582 return snapshot
583
584 @classmethod
Andrew Kerrb8436922016-06-01 15:32:43 -0400585 def create_share_group_snapshot_wait_for_active(
586 cls, share_group_id, name=None, description=None, client=None,
587 cleanup_in_class=True, **kwargs):
Clinton Knighte5c8f092015-08-27 15:00:23 -0400588 client = client or cls.shares_v2_client
Andrew Kerrbf31e912015-07-29 10:39:38 -0400589 if description is None:
Andrew Kerrb8436922016-06-01 15:32:43 -0400590 description = "Tempest's share group snapshot"
591 sg_snapshot = client.create_share_group_snapshot(
lkuchlan86f24322021-04-27 14:23:05 +0300592 share_group_id, name=name, description=description,
593 **kwargs)['share_group_snapshot']
Andrew Kerrbf31e912015-07-29 10:39:38 -0400594 resource = {
Andrew Kerrb8436922016-06-01 15:32:43 -0400595 "type": "share_group_snapshot",
596 "id": sg_snapshot["id"],
Andrew Kerrbf31e912015-07-29 10:39:38 -0400597 "client": client,
598 }
599 if cleanup_in_class:
600 cls.class_resources.insert(0, resource)
601 else:
602 cls.method_resources.insert(0, resource)
lkuchlanf7fc5b62021-01-26 14:53:43 +0200603 waiters.wait_for_resource_status(
604 client, sg_snapshot["id"], "available",
605 resource_name="share_group_snapshot")
Andrew Kerrb8436922016-06-01 15:32:43 -0400606 return sg_snapshot
Andrew Kerrbf31e912015-07-29 10:39:38 -0400607
608 @classmethod
Goutham Pacha Ravi839c98b2019-01-14 23:16:23 -0800609 def get_availability_zones(cls, client=None, backends=None):
Yogeshbdb88102015-09-29 23:41:02 -0400610 """List the availability zones for "manila-share" services
611
612 that are currently in "up" state.
613 """
Goutham Pacha Ravi839c98b2019-01-14 23:16:23 -0800614 client = client or cls.admin_shares_v2_client
615 backends = (
616 '|'.join(['^%s$' % backend for backend in backends])
617 if backends else '.*'
618 )
lkuchlan86f24322021-04-27 14:23:05 +0300619 cls.services = client.list_services()['services']
Yogeshbdb88102015-09-29 23:41:02 -0400620 zones = [service['zone'] for service in cls.services if
Goutham Pacha Ravi839c98b2019-01-14 23:16:23 -0800621 service['binary'] == 'manila-share' and
622 service['state'] == 'up' and
623 re.search(backends, service['host'])]
Douglas Viroel161f1802020-04-25 17:18:14 -0300624 return list(set(zones))
Yogeshbdb88102015-09-29 23:41:02 -0400625
Goutham Pacha Ravi839c98b2019-01-14 23:16:23 -0800626 @classmethod
627 def get_pools_matching_share_type(cls, share_type, client=None):
628 client = client or cls.admin_shares_v2_client
629 if utils.is_microversion_supported('2.23'):
630 return client.list_pools(
andrebeltrami3b4d4852020-02-04 19:11:54 +0000631 detail=True,
Goutham Pacha Ravi839c98b2019-01-14 23:16:23 -0800632 search_opts={'share_type': share_type['id']})['pools']
633
634 pools = client.list_pools(detail=True)['pools']
635 share_type = client.get_share_type(share_type['id'])['share_type']
636 extra_specs = {}
637 for k, v in share_type['extra_specs'].items():
638 extra_specs[k] = (
639 True if six.text_type(v).lower() == 'true'
640 else False if six.text_type(v).lower() == 'false' else v
641 )
642 return [
643 pool for pool in pools if all(y in pool['capabilities'].items()
644 for y in extra_specs.items())
645 ]
646
647 @classmethod
648 def get_availability_zones_matching_share_type(cls, share_type,
649 client=None):
650
651 client = client or cls.admin_shares_v2_client
652 pools_matching_share_type = cls.get_pools_matching_share_type(
653 share_type, client=client)
654 backends_matching_share_type = set(
655 [pool['name'].split("#")[0] for pool in pools_matching_share_type]
656 )
657 azs = cls.get_availability_zones(backends=backends_matching_share_type)
658 return azs
659
Yogesh1f931ff2015-09-29 23:41:02 -0400660 def get_pools_for_replication_domain(self):
661 # Get the list of pools for the replication domain
662 pools = self.admin_client.list_pools(detail=True)['pools']
Ben Swartzlander7150c652017-02-13 22:31:18 -0500663 instance_host = self.admin_client.get_share(
lkuchlan86f24322021-04-27 14:23:05 +0300664 self.shares[0]['id'])['share']['host']
Yogesh1f931ff2015-09-29 23:41:02 -0400665 host_pool = [p for p in pools if p['name'] == instance_host][0]
666 rep_domain = host_pool['capabilities']['replication_domain']
667 pools_in_rep_domain = [p for p in pools if p['capabilities'][
668 'replication_domain'] == rep_domain]
669 return rep_domain, pools_in_rep_domain
670
Yogeshbdb88102015-09-29 23:41:02 -0400671 @classmethod
debeltrami0d523bb2020-08-20 12:48:49 +0000672 def create_share_replica(cls, share_id, availability_zone=None,
673 client=None, cleanup_in_class=False,
674 cleanup=True,
silvacarlossd354d672020-08-23 18:49:52 +0000675 version=CONF.share.max_api_microversion):
Yogeshbdb88102015-09-29 23:41:02 -0400676 client = client or cls.shares_v2_client
Douglas Viroelbd4e78c2019-09-02 17:16:30 -0300677 replica = client.create_share_replica(
lkuchlan86f24322021-04-27 14:23:05 +0300678 share_id, availability_zone=availability_zone,
679 version=version)['share_replica']
Yogeshbdb88102015-09-29 23:41:02 -0400680 resource = {
681 "type": "share_replica",
682 "id": replica["id"],
683 "client": client,
684 "share_id": share_id,
685 }
686 # NOTE(Yogi1): Cleanup needs to be disabled during promotion tests.
687 if cleanup:
688 if cleanup_in_class:
689 cls.class_resources.insert(0, resource)
690 else:
691 cls.method_resources.insert(0, resource)
lkuchlanf7fc5b62021-01-26 14:53:43 +0200692 waiters.wait_for_resource_status(
693 client, replica["id"], constants.STATUS_AVAILABLE,
694 resource_name='share_replica')
Yogeshbdb88102015-09-29 23:41:02 -0400695 return replica
696
697 @classmethod
silvacarlossd354d672020-08-23 18:49:52 +0000698 def delete_share_replica(cls, replica_id, client=None,
699 version=CONF.share.max_api_microversion):
Yogeshbdb88102015-09-29 23:41:02 -0400700 client = client or cls.shares_v2_client
Yogesh1f931ff2015-09-29 23:41:02 -0400701 try:
silvacarlossd354d672020-08-23 18:49:52 +0000702 client.delete_share_replica(replica_id, version=version)
Yogesh1f931ff2015-09-29 23:41:02 -0400703 client.wait_for_resource_deletion(replica_id=replica_id)
704 except exceptions.NotFound:
705 pass
Yogeshbdb88102015-09-29 23:41:02 -0400706
707 @classmethod
silvacarlossd354d672020-08-23 18:49:52 +0000708 def promote_share_replica(cls, replica_id, client=None,
709 version=CONF.share.max_api_microversion):
Yogeshbdb88102015-09-29 23:41:02 -0400710 client = client or cls.shares_v2_client
lkuchlan86f24322021-04-27 14:23:05 +0300711 replica = client.promote_share_replica(
712 replica_id, version=version)['share_replica']
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
lkuchlan86f24322021-04-27 14:23:05 +0300754 share_network = client.create_share_network(**kwargs)['share_network']
Marc Koderer0abc93b2015-07-15 09:18:35 +0200755 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
lkuchlan86f24322021-04-27 14:23:05 +0300779 share_network_subnet = client.create_subnet(
780 **kwargs)['share_network_subnet']
Douglas Viroelb7e27e72019-08-06 19:40:37 -0300781 resource = {
782 "type": "share-network-subnet",
783 "id": share_network_subnet["id"],
784 "extra_params": {
785 "share_network_id": share_network_subnet["share_network_id"]
786 },
787 "client": client,
788 }
789 if cleanup_in_class:
790 cls.class_resources.insert(0, resource)
791 else:
792 cls.method_resources.insert(0, resource)
793 return share_network_subnet
794
795 @classmethod
Marc Koderer0abc93b2015-07-15 09:18:35 +0200796 def create_security_service(cls, ss_type="ldap", client=None,
797 cleanup_in_class=False, **kwargs):
798 if client is None:
799 client = cls.shares_client
lkuchlan86f24322021-04-27 14:23:05 +0300800 security_service = client.create_security_service(
801 ss_type, **kwargs)['security_service']
Marc Koderer0abc93b2015-07-15 09:18:35 +0200802 resource = {
803 "type": "security_service",
804 "id": security_service["id"],
805 "client": client,
806 }
807 if cleanup_in_class:
808 cls.class_resources.insert(0, resource)
809 else:
810 cls.method_resources.insert(0, resource)
811 return security_service
812
813 @classmethod
haixin0d1d29f2019-08-02 16:50:45 +0800814 def update_share_type(cls, share_type_id, name=None,
815 is_public=None, description=None,
816 client=None):
817 if client is None:
818 client = cls.shares_v2_client
819 share_type = client.update_share_type(share_type_id, name,
820 is_public, description)
821 return share_type
822
Goutham Pacha Raviaf448262020-06-29 14:24:13 -0700823 @classmethod
824 def update_quotas(cls, project_id, user_id=None, cleanup=True,
825 client=None, **kwargs):
826 client = client or cls.shares_v2_client
827 updated_quotas = client.update_quotas(project_id,
828 user_id=user_id,
lkuchlan86f24322021-04-27 14:23:05 +0300829 **kwargs)['quota_set']
Goutham Pacha Raviaf448262020-06-29 14:24:13 -0700830 resource = {
831 "type": "quotas",
832 "id": project_id,
833 "client": client,
834 "user_id": user_id,
835 }
836 if cleanup:
837 cls.method_resources.insert(0, resource)
838 return updated_quotas
839
Marc Koderer0abc93b2015-07-15 09:18:35 +0200840 @classmethod
Yogesh1f931ff2015-09-29 23:41:02 -0400841 def clear_share_replicas(cls, share_id, client=None):
842 client = client or cls.shares_v2_client
843 share_replicas = client.list_share_replicas(
lkuchlan86f24322021-04-27 14:23:05 +0300844 share_id=share_id)['share_replicas']
Yogesh1f931ff2015-09-29 23:41:02 -0400845
846 for replica in share_replicas:
847 try:
848 cls.delete_share_replica(replica['id'])
849 except exceptions.BadRequest:
850 # Ignore the exception due to deletion of last active replica
851 pass
852
853 @classmethod
Marc Koderer0abc93b2015-07-15 09:18:35 +0200854 def clear_resources(cls, resources=None):
855 """Deletes resources, that were created in test suites.
856
857 This method tries to remove resources from resource list,
858 if it is not found, assumed it was deleted in test itself.
859 It is expected, that all resources were added as LIFO
860 due to restriction of deletion resources, that is in the chain.
861
862 :param resources: dict with keys 'type','id','client' and 'deleted'
863 """
Marc Koderer0abc93b2015-07-15 09:18:35 +0200864 if resources is None:
865 resources = cls.method_resources
866 for res in resources:
867 if "deleted" not in res.keys():
868 res["deleted"] = False
869 if "client" not in res.keys():
870 res["client"] = cls.shares_client
871 if not(res["deleted"]):
872 res_id = res['id']
873 client = res["client"]
874 with handle_cleanup_exceptions():
Andreas Jaeger0cb685b2020-04-01 13:38:38 +0200875 if res["type"] == "share":
Yogesh1f931ff2015-09-29 23:41:02 -0400876 cls.clear_share_replicas(res_id)
Andrew Kerrb8436922016-06-01 15:32:43 -0400877 share_group_id = res.get('share_group_id')
878 if share_group_id:
879 params = {'share_group_id': share_group_id}
Clinton Knighte5c8f092015-08-27 15:00:23 -0400880 client.delete_share(res_id, params=params)
881 else:
882 client.delete_share(res_id)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200883 client.wait_for_resource_deletion(share_id=res_id)
Andreas Jaeger0cb685b2020-04-01 13:38:38 +0200884 elif res["type"] == "snapshot":
Marc Koderer0abc93b2015-07-15 09:18:35 +0200885 client.delete_snapshot(res_id)
886 client.wait_for_resource_deletion(snapshot_id=res_id)
Andreas Jaeger0cb685b2020-04-01 13:38:38 +0200887 elif (res["type"] == "share_network" and
yogesh06f519f2017-06-26 13:16:12 -0400888 res_id != CONF.share.share_network_id):
Victoria Martinez de la Cruzcad92012018-06-08 14:46:35 -0400889 client.delete_share_network(res_id)
890 client.wait_for_resource_deletion(sn_id=res_id)
debeltrami1753a592020-05-11 18:27:30 +0000891 elif res["type"] == "dissociate_security_service":
892 sn_id = res["extra_params"]["share_network_id"]
893 client.remove_sec_service_from_share_network(
894 sn_id=sn_id, ss_id=res_id
895 )
Andreas Jaeger0cb685b2020-04-01 13:38:38 +0200896 elif res["type"] == "security_service":
Marc Koderer0abc93b2015-07-15 09:18:35 +0200897 client.delete_security_service(res_id)
898 client.wait_for_resource_deletion(ss_id=res_id)
Andreas Jaeger0cb685b2020-04-01 13:38:38 +0200899 elif res["type"] == "share_type":
Marc Koderer0abc93b2015-07-15 09:18:35 +0200900 client.delete_share_type(res_id)
901 client.wait_for_resource_deletion(st_id=res_id)
Andreas Jaeger0cb685b2020-04-01 13:38:38 +0200902 elif res["type"] == "share_group":
Andrew Kerrb8436922016-06-01 15:32:43 -0400903 client.delete_share_group(res_id)
904 client.wait_for_resource_deletion(
905 share_group_id=res_id)
Andreas Jaeger0cb685b2020-04-01 13:38:38 +0200906 elif res["type"] == "share_group_type":
Andrew Kerrb8436922016-06-01 15:32:43 -0400907 client.delete_share_group_type(res_id)
908 client.wait_for_resource_deletion(
909 share_group_type_id=res_id)
Andreas Jaeger0cb685b2020-04-01 13:38:38 +0200910 elif res["type"] == "share_group_snapshot":
Andrew Kerrb8436922016-06-01 15:32:43 -0400911 client.delete_share_group_snapshot(res_id)
912 client.wait_for_resource_deletion(
913 share_group_snapshot_id=res_id)
Andreas Jaeger0cb685b2020-04-01 13:38:38 +0200914 elif res["type"] == "share_replica":
Yogeshbdb88102015-09-29 23:41:02 -0400915 client.delete_share_replica(res_id)
916 client.wait_for_resource_deletion(replica_id=res_id)
Andreas Jaeger0cb685b2020-04-01 13:38:38 +0200917 elif res["type"] == "share_network_subnet":
Douglas Viroelb7e27e72019-08-06 19:40:37 -0300918 sn_id = res["extra_params"]["share_network_id"]
919 client.delete_subnet(sn_id, res_id)
920 client.wait_for_resource_deletion(
921 share_network_subnet_id=res_id,
922 sn_id=sn_id)
Goutham Pacha Raviaf448262020-06-29 14:24:13 -0700923 elif res["type"] == "quotas":
924 user_id = res.get('user_id')
925 client.reset_quotas(res_id, user_id=user_id)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200926 else:
huayue97bacbf2016-01-04 09:57:39 +0800927 LOG.warning("Provided unsupported resource type for "
junbolib236c242017-07-18 18:12:37 +0800928 "cleanup '%s'. Skipping.", res["type"])
Marc Koderer0abc93b2015-07-15 09:18:35 +0200929 res["deleted"] = True
930
931 @classmethod
932 def generate_share_network_data(self):
933 data = {
934 "name": data_utils.rand_name("sn-name"),
935 "description": data_utils.rand_name("sn-desc"),
936 "neutron_net_id": data_utils.rand_name("net-id"),
937 "neutron_subnet_id": data_utils.rand_name("subnet-id"),
938 }
939 return data
940
941 @classmethod
Douglas Viroelb7e27e72019-08-06 19:40:37 -0300942 def generate_subnet_data(self):
943 data = {
944 "neutron_net_id": data_utils.rand_name("net-id"),
945 "neutron_subnet_id": data_utils.rand_name("subnet-id"),
946 }
947 return data
948
949 @classmethod
Maurice Schreiber5ac37172018-02-01 15:17:31 +0100950 def generate_security_service_data(self, set_ou=False):
Marc Koderer0abc93b2015-07-15 09:18:35 +0200951 data = {
952 "name": data_utils.rand_name("ss-name"),
953 "description": data_utils.rand_name("ss-desc"),
Valeriy Ponomaryovfcde7712015-12-14 18:06:13 +0200954 "dns_ip": utils.rand_ip(),
955 "server": utils.rand_ip(),
Marc Koderer0abc93b2015-07-15 09:18:35 +0200956 "domain": data_utils.rand_name("ss-domain"),
957 "user": data_utils.rand_name("ss-user"),
958 "password": data_utils.rand_name("ss-password"),
959 }
Maurice Schreiber5ac37172018-02-01 15:17:31 +0100960 if set_ou:
961 data["ou"] = data_utils.rand_name("ss-ou")
962
Marc Koderer0abc93b2015-07-15 09:18:35 +0200963 return data
964
965 # Useful assertions
966 def assertDictMatch(self, d1, d2, approx_equal=False, tolerance=0.001):
967 """Assert two dicts are equivalent.
968
969 This is a 'deep' match in the sense that it handles nested
970 dictionaries appropriately.
971
972 NOTE:
973
974 If you don't care (or don't know) a given value, you can specify
975 the string DONTCARE as the value. This will cause that dict-item
976 to be skipped.
977
978 """
979 def raise_assertion(msg):
980 d1str = str(d1)
981 d2str = str(d2)
982 base_msg = ('Dictionaries do not match. %(msg)s d1: %(d1str)s '
983 'd2: %(d2str)s' %
984 {"msg": msg, "d1str": d1str, "d2str": d2str})
985 raise AssertionError(base_msg)
986
987 d1keys = set(d1.keys())
988 d2keys = set(d2.keys())
989 if d1keys != d2keys:
990 d1only = d1keys - d2keys
991 d2only = d2keys - d1keys
992 raise_assertion('Keys in d1 and not d2: %(d1only)s. '
993 'Keys in d2 and not d1: %(d2only)s' %
994 {"d1only": d1only, "d2only": d2only})
995
996 for key in d1keys:
997 d1value = d1[key]
998 d2value = d2[key]
999 try:
1000 error = abs(float(d1value) - float(d2value))
1001 within_tolerance = error <= tolerance
1002 except (ValueError, TypeError):
daiki kato6914b1a2016-03-16 17:16:57 +09001003 # If both values aren't convertible to float, just ignore
Marc Koderer0abc93b2015-07-15 09:18:35 +02001004 # ValueError if arg is a str, TypeError if it's something else
1005 # (like None)
1006 within_tolerance = False
1007
1008 if hasattr(d1value, 'keys') and hasattr(d2value, 'keys'):
1009 self.assertDictMatch(d1value, d2value)
1010 elif 'DONTCARE' in (d1value, d2value):
1011 continue
1012 elif approx_equal and within_tolerance:
1013 continue
1014 elif d1value != d2value:
1015 raise_assertion("d1['%(key)s']=%(d1value)s != "
1016 "d2['%(key)s']=%(d2value)s" %
1017 {
1018 "key": key,
1019 "d1value": d1value,
1020 "d2value": d2value
1021 })
1022
Alex Meadeba8a1602016-05-06 09:33:09 -04001023 def create_user_message(self):
1024 """Trigger a 'no valid host' situation to generate a message."""
1025 extra_specs = {
1026 'vendor_name': 'foobar',
1027 'driver_handles_share_servers': CONF.share.multitenancy_enabled,
1028 }
1029 share_type_name = data_utils.rand_name("share-type")
1030
1031 bogus_type = self.create_share_type(
Goutham Pacha Raviaf448262020-06-29 14:24:13 -07001032 client=self.admin_shares_v2_client,
Alex Meadeba8a1602016-05-06 09:33:09 -04001033 name=share_type_name,
1034 extra_specs=extra_specs)['share_type']
1035
1036 params = {'share_type_id': bogus_type['id'],
1037 'share_network_id': self.shares_v2_client.share_network_id}
lkuchlan86f24322021-04-27 14:23:05 +03001038 share = self.shares_v2_client.create_share(**params)['share']
Alex Meadeba8a1602016-05-06 09:33:09 -04001039 self.addCleanup(self.shares_v2_client.delete_share, share['id'])
lkuchlanf7fc5b62021-01-26 14:53:43 +02001040 waiters.wait_for_resource_status(
lkuchlan540e74a2021-01-19 18:08:25 +02001041 self.shares_v2_client, share['id'], "error")
1042 return waiters.wait_for_message(self.shares_v2_client, share['id'])
Alex Meadeba8a1602016-05-06 09:33:09 -04001043
lkuchlan5af7cb42020-07-14 18:05:09 +03001044 def allow_access(self, share_id, client=None, access_type=None,
1045 access_level='rw', access_to=None, status='active',
1046 raise_rule_in_error_state=True, cleanup=True):
1047
1048 client = client or self.shares_v2_client
1049 a_type, a_to = self._get_access_rule_data_from_config()
1050 access_type = access_type or a_type
1051 access_to = access_to or a_to
1052
1053 rule = client.create_access_rule(share_id, access_type, access_to,
lkuchlan86f24322021-04-27 14:23:05 +03001054 access_level)['access']
lkuchlanf7fc5b62021-01-26 14:53:43 +02001055 waiters.wait_for_resource_status(
1056 client, share_id, status, resource_name='access_rule',
1057 rule_id=rule['id'],
1058 raise_rule_in_error_state=raise_rule_in_error_state)
lkuchlan5af7cb42020-07-14 18:05:09 +03001059 if cleanup:
1060 self.addCleanup(client.wait_for_resource_deletion,
1061 rule_id=rule['id'], share_id=share_id)
1062 self.addCleanup(client.delete_access_rule, share_id, rule['id'])
1063 return rule
1064
Marc Koderer0abc93b2015-07-15 09:18:35 +02001065
Marc Koderer0abc93b2015-07-15 09:18:35 +02001066class BaseSharesAdminTest(BaseSharesTest):
1067 """Base test case class for all Shares Admin API tests."""
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +03001068 credentials = ('admin', )
1069
Victoria Martinez de la Cruzf6bc6fa2018-02-01 11:27:00 -05001070 @classmethod
1071 def setup_clients(cls):
1072 super(BaseSharesAdminTest, cls).setup_clients()
1073 # Initialise share clients
lkuchlan3af822b2021-06-06 10:35:30 +03001074 cls.admin_shares_client = cls.os_admin.share_v1.SharesClient()
Victoria Martinez de la Cruzf6bc6fa2018-02-01 11:27:00 -05001075 cls.admin_shares_v2_client = cls.os_admin.share_v2.SharesV2Client()
1076
lkuchlan3af822b2021-06-06 10:35:30 +03001077 @staticmethod
1078 def add_extra_specs_to_dict(extra_specs=None):
1079 """Add any required extra-specs to share type dictionary"""
1080 dhss = six.text_type(CONF.share.multitenancy_enabled)
1081 extra_specs_dict = {"driver_handles_share_servers": dhss}
1082 if extra_specs:
1083 extra_specs_dict.update(extra_specs)
1084 return extra_specs_dict
1085
Victoria Martinez de la Cruzf6bc6fa2018-02-01 11:27:00 -05001086 @classmethod
lkuchlan3af822b2021-06-06 10:35:30 +03001087 def create_share_type(cls, name=None, is_public=True, client=None,
1088 cleanup_in_class=True, extra_specs=None, **kwargs):
1089 name = name or data_utils.rand_name(
1090 cls.__class__.__name__ + 'share-type')
1091 client = client or cls.admin_shares_v2_client
1092 extra_specs = cls.add_extra_specs_to_dict(extra_specs=extra_specs)
1093 share_type = client.create_share_type(name, is_public,
1094 extra_specs=extra_specs,
1095 **kwargs)['share_type']
1096 resource = {
1097 "type": "share_type",
1098 "id": share_type["id"],
1099 "client": client,
1100 }
1101 if cleanup_in_class:
1102 cls.class_resources.insert(0, resource)
1103 else:
1104 cls.method_resources.insert(0, resource)
1105 return share_type
Victoria Martinez de la Cruzf6bc6fa2018-02-01 11:27:00 -05001106
1107 @classmethod
1108 def _create_share_group_type(cls):
1109 share_group_type_name = data_utils.rand_name("unique_sgtype_name")
1110 return cls.create_share_group_type(
1111 name=share_group_type_name, share_types=[cls.share_type_id],
1112 client=cls.admin_shares_v2_client)
1113
Lucio Seki37056942019-01-24 15:40:20 -02001114 def _create_share_for_manage(self):
1115 creation_data = {
lkuchlan3af822b2021-06-06 10:35:30 +03001116 'share_type_id': self.st['id'],
Lucio Seki37056942019-01-24 15:40:20 -02001117 'share_protocol': self.protocol,
1118 }
1119
1120 share = self.create_share(**creation_data)
lkuchlan86f24322021-04-27 14:23:05 +03001121 share = self.shares_v2_client.get_share(share['id'])['share']
Lucio Seki37056942019-01-24 15:40:20 -02001122
1123 if utils.is_microversion_ge(CONF.share.max_api_microversion, "2.9"):
lkuchlan86f24322021-04-27 14:23:05 +03001124 el = self.shares_v2_client.list_share_export_locations(
1125 share["id"])['export_locations']
Lucio Seki37056942019-01-24 15:40:20 -02001126 share["export_locations"] = el
1127
1128 return share
1129
1130 def _unmanage_share_and_wait(self, share):
1131 self.shares_v2_client.unmanage_share(share['id'])
1132 self.shares_v2_client.wait_for_resource_deletion(share_id=share['id'])
1133
1134 def _reset_state_and_delete_share(self, share):
1135 self.shares_v2_client.reset_state(share['id'])
1136 self._delete_share_and_wait(share)
1137
1138 def _delete_snapshot_and_wait(self, snap):
1139 self.shares_v2_client.delete_snapshot(snap['id'])
1140 self.shares_v2_client.wait_for_resource_deletion(
1141 snapshot_id=snap['id']
1142 )
1143 self.assertRaises(exceptions.NotFound,
1144 self.shares_v2_client.get_snapshot,
1145 snap['id'])
1146
1147 def _delete_share_and_wait(self, share):
1148 self.shares_v2_client.delete_share(share['id'])
1149 self.shares_v2_client.wait_for_resource_deletion(share_id=share['id'])
1150 self.assertRaises(exceptions.NotFound,
1151 self.shares_v2_client.get_share,
1152 share['id'])
1153
1154 def _manage_share(self, share, name, description, share_server_id):
1155 managed_share = self.shares_v2_client.manage_share(
1156 service_host=share['host'],
1157 export_path=share['export_locations'][0],
1158 protocol=share['share_proto'],
lkuchlan3af822b2021-06-06 10:35:30 +03001159 share_type_id=self.share_type['id'],
Lucio Seki37056942019-01-24 15:40:20 -02001160 name=name,
1161 description=description,
1162 share_server_id=share_server_id
lkuchlan86f24322021-04-27 14:23:05 +03001163 )['share']
lkuchlanf7fc5b62021-01-26 14:53:43 +02001164 waiters.wait_for_resource_status(
lkuchlan540e74a2021-01-19 18:08:25 +02001165 self.shares_v2_client, managed_share['id'],
1166 constants.STATUS_AVAILABLE
Lucio Seki37056942019-01-24 15:40:20 -02001167 )
1168
1169 return managed_share
1170
1171 def _unmanage_share_server_and_wait(self, server):
1172 self.shares_v2_client.unmanage_share_server(server['id'])
1173 self.shares_v2_client.wait_for_resource_deletion(
1174 server_id=server['id']
1175 )
1176
1177 def _manage_share_server(self, share_server, fields=None):
1178 params = fields or {}
Douglas Viroelb7e27e72019-08-06 19:40:37 -03001179 subnet_id = params.get('share_network_subnet_id', None)
Lucio Seki37056942019-01-24 15:40:20 -02001180 managed_share_server = self.shares_v2_client.manage_share_server(
1181 params.get('host', share_server['host']),
1182 params.get('share_network_id', share_server['share_network_id']),
1183 params.get('identifier', share_server['identifier']),
Douglas Viroelb7e27e72019-08-06 19:40:37 -03001184 share_network_subnet_id=subnet_id,
lkuchlan86f24322021-04-27 14:23:05 +03001185 )['share_server']
lkuchlanf7fc5b62021-01-26 14:53:43 +02001186 waiters.wait_for_resource_status(
lkuchlan540e74a2021-01-19 18:08:25 +02001187 self.shares_v2_client, managed_share_server['id'],
lkuchlanf7fc5b62021-01-26 14:53:43 +02001188 constants.SERVER_STATE_ACTIVE, resource_name='share_server'
Lucio Seki37056942019-01-24 15:40:20 -02001189 )
1190
1191 return managed_share_server
1192
1193 def _delete_share_server_and_wait(self, share_server_id):
1194 self.shares_v2_client.delete_share_server(
1195 share_server_id
1196 )
1197 self.shares_v2_client.wait_for_resource_deletion(
1198 server_id=share_server_id)
1199
lkuchlan3af822b2021-06-06 10:35:30 +03001200 def create_user_message(self):
1201 """Trigger a 'no valid host' situation to generate a message."""
1202 extra_specs = {
1203 'vendor_name': 'foobar',
1204 'driver_handles_share_servers': CONF.share.multitenancy_enabled,
1205 }
1206 share_type_name = data_utils.rand_name("share-type")
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +03001207
lkuchlan3af822b2021-06-06 10:35:30 +03001208 bogus_type = self.create_share_type(
1209 client=self.admin_shares_v2_client,
1210 name=share_type_name,
1211 extra_specs=extra_specs)
1212
1213 params = {'share_type_id': bogus_type['id'],
1214 'share_network_id': self.shares_v2_client.share_network_id}
lkuchlan86f24322021-04-27 14:23:05 +03001215 share = self.shares_v2_client.create_share(**params)['share']
lkuchlan3af822b2021-06-06 10:35:30 +03001216 self.addCleanup(self.shares_v2_client.delete_share, share['id'])
1217 waiters.wait_for_resource_status(
1218 self.shares_v2_client, share['id'], "error")
1219 return waiters.wait_for_message(self.shares_v2_client, share['id'])
1220
1221
1222class BaseSharesMixedTest(BaseSharesAdminTest):
1223 """Base test case class for all Shares API tests with all user roles.
1224
1225 Tests deriving from this class can use the primary project's clients
1226 (self.shares_client, self.shares_v2_client) and the alt project user's
1227 clients (self.alt_shares_client, self.alt_shares_v2_client) to perform
1228 API calls and validations. Although admin clients are available for use,
1229 their use should be limited to performing bootstrapping (e.g., creating
1230 a share type, or resetting state of a resource, etc.). No API validation
1231 must be performed against admin APIs. Use BaseAdminTest as a base class
1232 for such tests.
1233 """
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +03001234 credentials = ('primary', 'alt', 'admin')
Marc Koderer0abc93b2015-07-15 09:18:35 +02001235
Goutham Pacha Ravic678e212020-03-20 11:13:47 -07001236 # Will be cleaned up in resource_cleanup if the class
1237 class_project_users_created = []
1238
1239 @classmethod
1240 def resource_cleanup(cls):
1241 cls.clear_project_users(cls.class_project_users_created)
1242 super(BaseSharesMixedTest, cls).resource_cleanup()
1243
1244 @classmethod
1245 def clear_project_users(cls, users=None):
1246 users = users or cls.class_project_users_created
1247 for user in users:
1248 with handle_cleanup_exceptions():
1249 cls.os_admin.creds_client.delete_user(user['id'])
1250
Marc Koderer0abc93b2015-07-15 09:18:35 +02001251 @classmethod
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +03001252 def setup_clients(cls):
1253 super(BaseSharesMixedTest, cls).setup_clients()
Andrea Frittoli (andreaf)369391a2016-06-27 18:59:13 +01001254 cls.alt_shares_client = cls.os_alt.share_v1.SharesClient()
1255 cls.alt_shares_v2_client = cls.os_alt.share_v2.SharesV2Client()
1256 # Initialise network clients
1257 cls.os_admin.networks_client = cls.os_admin.network.NetworksClient()
1258 cls.os_alt.networks_client = cls.os_alt.network.NetworksClient()
Goutham Pacha Ravic678e212020-03-20 11:13:47 -07001259 # Initialise identity clients
1260 cls.admin_project = cls.os_admin.auth_provider.auth_data[1]['project']
1261 identity_clients = getattr(
1262 cls.os_admin, 'identity_%s' % CONF.identity.auth_version)
1263 cls.os_admin.identity_client = identity_clients.IdentityClient()
1264 cls.os_admin.projects_client = identity_clients.ProjectsClient()
1265 cls.os_admin.users_client = identity_clients.UsersClient()
1266 cls.os_admin.roles_client = identity_clients.RolesClient()
1267 cls.os_admin.domains_client = (
1268 cls.os_admin.identity_v3.DomainsClient() if
1269 CONF.identity.auth_version == 'v3' else None)
Goutham Pacha Ravi10d49492021-02-23 16:05:21 -08001270 cls.admin_project_member_client = cls.create_user_and_get_client(
1271 project=cls.admin_project, add_member_role=True)
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +03001272
1273 if CONF.share.multitenancy_enabled:
1274 admin_share_network_id = cls.provide_share_network(
1275 cls.admin_shares_v2_client, cls.os_admin.networks_client)
1276 cls.admin_shares_client.share_network_id = admin_share_network_id
1277 cls.admin_shares_v2_client.share_network_id = (
1278 admin_share_network_id)
1279
1280 alt_share_network_id = cls.provide_share_network(
1281 cls.alt_shares_v2_client, cls.os_alt.networks_client)
1282 cls.alt_shares_client.share_network_id = alt_share_network_id
1283 cls.alt_shares_v2_client.share_network_id = alt_share_network_id
Victoria Martinez de la Cruzf6bc6fa2018-02-01 11:27:00 -05001284
1285 @classmethod
Goutham Pacha Ravi10d49492021-02-23 16:05:21 -08001286 def create_user_and_get_client(cls, project=None, add_member_role=True):
Goutham Pacha Ravic678e212020-03-20 11:13:47 -07001287 """Create a user in specified project & set share clients for user
1288
1289 The user will have all roles specified in tempest.conf
1290 :param: project: a dictionary with project ID and name, if not
1291 specified, the value will be cls.admin_project
1292 """
1293 project_domain_name = (
1294 cls.os_admin.identity_client.auth_provider.credentials.get(
1295 'project_domain_name', 'Default'))
1296 cls.os_admin.creds_client = cred_client.get_creds_client(
1297 cls.os_admin.identity_client, cls.os_admin.projects_client,
1298 cls.os_admin.users_client, cls.os_admin.roles_client,
1299 cls.os_admin.domains_client, project_domain_name)
1300
1301 # User info
1302 project = project or cls.admin_project
1303 username = data_utils.rand_name('manila_%s' % project['id'])
1304 password = data_utils.rand_password()
1305 email = '%s@example.org' % username
1306
1307 user = cls.os_admin.creds_client.create_user(
1308 username, password, project, email)
1309 cls.class_project_users_created.append(user)
1310
Goutham Pacha Ravi10d49492021-02-23 16:05:21 -08001311 tempest_roles_to_assign = CONF.auth.tempest_roles or []
1312 if "member" not in tempest_roles_to_assign and add_member_role:
1313 tempest_roles_to_assign.append("member")
1314
1315 for role in tempest_roles_to_assign:
1316 cls.os_admin.creds_client.assign_user_role(user, project, role)
Goutham Pacha Ravic678e212020-03-20 11:13:47 -07001317
1318 user_creds = cls.os_admin.creds_client.get_credentials(
1319 user, project, password)
1320 os = clients.Clients(user_creds)
1321 os.shares_v1_client = os.share_v1.SharesClient()
1322 os.shares_v2_client = os.share_v2.SharesV2Client()
1323 return os