blob: dcf0bed3960b5e161f6807dcf28352499ce02d18 [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
17import inspect
Valeriy Ponomaryov2abf5d72016-06-01 18:30:12 +030018import re
Marc Koderer0abc93b2015-07-15 09:18:35 +020019import traceback
20
21from oslo_concurrency import lockutils
22from oslo_log import log
23import six
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +030024from tempest import clients
Sam Wanc7b7f1f2015-11-25 00:22:28 -050025from tempest.common import credentials_factory as common_creds
Valeriy Ponomaryov48a2bd72015-11-05 13:22:44 +020026from tempest.common import dynamic_creds
27from tempest import config
Ben Swartzlander1c4ff522016-03-02 22:16:23 -050028from tempest.lib.common.utils import data_utils
29from tempest.lib import exceptions
Valeriy Ponomaryov48a2bd72015-11-05 13:22:44 +020030from tempest import test
Marc Koderer0abc93b2015-07-15 09:18:35 +020031
Yogeshbdb88102015-09-29 23:41:02 -040032from manila_tempest_tests.common import constants
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +030033from manila_tempest_tests.services.share.json import shares_client
34from manila_tempest_tests.services.share.v2.json import (
35 shares_client as shares_v2_client)
Marc Koderer0abc93b2015-07-15 09:18:35 +020036from manila_tempest_tests import share_exceptions
Valeriy Ponomaryovfcde7712015-12-14 18:06:13 +020037from manila_tempest_tests import utils
Marc Koderer0abc93b2015-07-15 09:18:35 +020038
39CONF = config.CONF
40LOG = log.getLogger(__name__)
41
Valeriy Ponomaryov2abf5d72016-06-01 18:30:12 +030042# Test tags related to test direction
43TAG_POSITIVE = "positive"
44TAG_NEGATIVE = "negative"
45
46# Test tags related to service involvement
47TAG_API = "api"
48TAG_BACKEND = "backend"
49TAG_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: "
93 "\n%s" % traceback.format_exc())
94 return True # Suppress error if any
95
96
97def network_synchronized(f):
98
99 def wrapped_func(self, *args, **kwargs):
100 with_isolated_creds = True if len(args) > 2 else False
101 no_lock_required = kwargs.get(
102 "isolated_creds_client", with_isolated_creds)
103 if no_lock_required:
104 # Usage of not reusable network. No need in lock.
105 return f(self, *args, **kwargs)
106
107 # Use lock assuming reusage of common network.
108 @lockutils.synchronized("manila_network_lock", external=True)
109 def source_func(self, *args, **kwargs):
110 return f(self, *args, **kwargs)
111
112 return source_func(self, *args, **kwargs)
113
114 return wrapped_func
115
116
Valeriy Ponomaryovfcde7712015-12-14 18:06:13 +0200117skip_if_microversion_not_supported = utils.skip_if_microversion_not_supported
Xing Yang69b00b52015-11-22 16:10:44 -0500118skip_if_microversion_lt = utils.skip_if_microversion_lt
Valeriy Ponomaryova14c2252015-10-29 13:34:32 +0200119
120
Marc Koderer0abc93b2015-07-15 09:18:35 +0200121class BaseSharesTest(test.BaseTestCase):
122 """Base test case class for all Manila API tests."""
123
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +0300124 credentials = ('primary', )
Marc Koderer0abc93b2015-07-15 09:18:35 +0200125 force_tenant_isolation = False
John Spray061b1452015-11-18 13:15:32 +0000126 protocols = ["nfs", "cifs", "glusterfs", "hdfs", "cephfs"]
Marc Koderer0abc93b2015-07-15 09:18:35 +0200127
128 # Will be cleaned up in resource_cleanup
129 class_resources = []
130
131 # Will be cleaned up in tearDown method
132 method_resources = []
133
134 # Will be cleaned up in resource_cleanup
135 class_isolated_creds = []
136
137 # Will be cleaned up in tearDown method
138 method_isolated_creds = []
139
Valeriy Ponomaryova14c2252015-10-29 13:34:32 +0200140 def skip_if_microversion_not_supported(self, microversion):
Valeriy Ponomaryovfcde7712015-12-14 18:06:13 +0200141 if not utils.is_microversion_supported(microversion):
Valeriy Ponomaryova14c2252015-10-29 13:34:32 +0200142 raise self.skipException(
143 "Microversion '%s' is not supported." % microversion)
144
Xing Yang69b00b52015-11-22 16:10:44 -0500145 def skip_if_microversion_lt(self, microversion):
146 if utils.is_microversion_lt(CONF.share.max_api_microversion,
147 microversion):
148 raise self.skipException(
149 "Microversion must be greater than or equal to '%s'." %
150 microversion)
151
Marc Koderer0abc93b2015-07-15 09:18:35 +0200152 @classmethod
153 def get_client_with_isolated_creds(cls,
154 name=None,
155 type_of_creds="admin",
Clinton Knighte5c8f092015-08-27 15:00:23 -0400156 cleanup_in_class=False,
157 client_version='1'):
Marc Koderer0abc93b2015-07-15 09:18:35 +0200158 """Creates isolated creds.
159
160 :param name: name, will be used for naming ic and related stuff
161 :param type_of_creds: admin, alt or primary
162 :param cleanup_in_class: defines place where to delete
163 :returns: SharesClient -- shares client with isolated creds.
164 :returns: To client added dict attr 'creds' with
165 :returns: key elements 'tenant' and 'user'.
166 """
167 if name is None:
168 # Get name of test method
169 name = inspect.stack()[1][3]
170 if len(name) > 32:
171 name = name[0:32]
172
173 # Choose type of isolated creds
Valeriy Ponomaryov48a2bd72015-11-05 13:22:44 +0200174 ic = dynamic_creds.DynamicCredentialProvider(
175 identity_version=CONF.identity.auth_version,
176 name=name,
Sam Wanc7b7f1f2015-11-25 00:22:28 -0500177 admin_role=CONF.identity.admin_role,
Valeriy Ponomaryov0ddd29b2016-06-07 17:49:31 +0300178 admin_creds=common_creds.get_configured_admin_credentials())
Marc Koderer0abc93b2015-07-15 09:18:35 +0200179 if "admin" in type_of_creds:
Marc Koderer5880b362016-07-06 10:59:07 +0200180 creds = ic.get_admin_creds().credentials
Marc Koderer0abc93b2015-07-15 09:18:35 +0200181 elif "alt" in type_of_creds:
Marc Koderer5880b362016-07-06 10:59:07 +0200182 creds = ic.get_alt_creds().credentials
Marc Koderer0abc93b2015-07-15 09:18:35 +0200183 else:
Marc Koderer5880b362016-07-06 10:59:07 +0200184 creds = ic.get_credentials(type_of_creds).credentials
Marc Koderer0abc93b2015-07-15 09:18:35 +0200185 ic.type_of_creds = type_of_creds
186
187 # create client with isolated creds
188 os = clients.Manager(credentials=creds)
Clinton Knighte5c8f092015-08-27 15:00:23 -0400189 if client_version == '1':
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +0300190 client = shares_client.SharesClient(os.auth_provider)
Clinton Knighte5c8f092015-08-27 15:00:23 -0400191 elif client_version == '2':
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +0300192 client = shares_v2_client.SharesV2Client(os.auth_provider)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200193
194 # Set place where will be deleted isolated creds
195 ic_res = {
Valeriy Ponomaryov48a2bd72015-11-05 13:22:44 +0200196 "method": ic.clear_creds,
Marc Koderer0abc93b2015-07-15 09:18:35 +0200197 "deleted": False,
198 }
199 if cleanup_in_class:
200 cls.class_isolated_creds.insert(0, ic_res)
201 else:
202 cls.method_isolated_creds.insert(0, ic_res)
203
204 # Provide share network
205 if CONF.share.multitenancy_enabled:
Valeriy Ponomaryovc5dae272016-06-10 18:29:24 +0300206 if (not CONF.service_available.neutron and
207 CONF.share.create_networks_when_multitenancy_enabled):
Marc Koderer0abc93b2015-07-15 09:18:35 +0200208 raise cls.skipException("Neutron support is required")
Valeriy Ponomaryov48a2bd72015-11-05 13:22:44 +0200209 nc = os.networks_client
Marc Koderer0abc93b2015-07-15 09:18:35 +0200210 share_network_id = cls.provide_share_network(client, nc, ic)
211 client.share_network_id = share_network_id
212 resource = {
213 "type": "share_network",
214 "id": client.share_network_id,
215 "client": client,
216 }
217 if cleanup_in_class:
218 cls.class_resources.insert(0, resource)
219 else:
220 cls.method_resources.insert(0, resource)
221 return client
222
223 @classmethod
224 def verify_nonempty(cls, *args):
225 if not all(args):
226 msg = "Missing API credentials in configuration."
227 raise cls.skipException(msg)
228
229 @classmethod
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +0300230 def setup_clients(cls):
231 super(BaseSharesTest, cls).setup_clients()
232 os = getattr(cls, 'os_%s' % cls.credentials[0])
233 os.shares_client = shares_client.SharesClient(os.auth_provider)
Valeriy Ponomaryov4fb305f2016-10-21 13:46:47 +0300234
235 if CONF.identity.auth_version == 'v3':
236 project_id = os.auth_provider.auth_data[1]['project']['id']
237 else:
238 project_id = os.auth_provider.auth_data[1]['token']['tenant']['id']
239 cls.tenant_id = project_id
240 cls.user_id = os.auth_provider.auth_data[1]['user']['id']
241
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +0300242 cls.shares_client = os.shares_client
243 os.shares_v2_client = shares_v2_client.SharesV2Client(
244 os.auth_provider)
245 cls.shares_v2_client = os.shares_v2_client
246 if CONF.share.multitenancy_enabled:
Valeriy Ponomaryovc5dae272016-06-10 18:29:24 +0300247 if (not CONF.service_available.neutron and
248 CONF.share.create_networks_when_multitenancy_enabled):
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +0300249 raise cls.skipException("Neutron support is required")
250 share_network_id = cls.provide_share_network(
251 cls.shares_v2_client, os.networks_client)
252 cls.shares_client.share_network_id = share_network_id
253 cls.shares_v2_client.share_network_id = share_network_id
254
255 @classmethod
Marc Koderer0abc93b2015-07-15 09:18:35 +0200256 def resource_setup(cls):
257 if not (any(p in CONF.share.enable_protocols
258 for p in cls.protocols) and
259 CONF.service_available.manila):
260 skip_msg = "Manila is disabled"
261 raise cls.skipException(skip_msg)
262 super(BaseSharesTest, cls).resource_setup()
Marc Koderer0abc93b2015-07-15 09:18:35 +0200263
264 def setUp(self):
265 super(BaseSharesTest, self).setUp()
Marc Koderer0abc93b2015-07-15 09:18:35 +0200266 self.addCleanup(self.clear_isolated_creds)
Valeriy Ponomaryovdd162cb2016-01-20 19:09:49 +0200267 self.addCleanup(self.clear_resources)
Valeriy Ponomaryov2abf5d72016-06-01 18:30:12 +0300268 verify_test_has_appropriate_tags(self)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200269
270 @classmethod
271 def resource_cleanup(cls):
Marc Koderer0abc93b2015-07-15 09:18:35 +0200272 cls.clear_resources(cls.class_resources)
273 cls.clear_isolated_creds(cls.class_isolated_creds)
Sam Wan241029c2016-07-26 03:37:42 -0400274 super(BaseSharesTest, cls).resource_cleanup()
Marc Koderer0abc93b2015-07-15 09:18:35 +0200275
276 @classmethod
277 @network_synchronized
Valeriy Ponomaryov48a2bd72015-11-05 13:22:44 +0200278 def provide_share_network(cls, shares_client, networks_client,
Rodrigo Barbieri58d9de32016-09-06 13:16:47 -0300279 isolated_creds_client=None,
280 ignore_multitenancy_config=False):
Marc Koderer0abc93b2015-07-15 09:18:35 +0200281 """Used for finding/creating share network for multitenant driver.
282
283 This method creates/gets entity share-network for one tenant. This
284 share-network will be used for creation of service vm.
285
286 :param shares_client: shares client, which requires share-network
Valeriy Ponomaryov48a2bd72015-11-05 13:22:44 +0200287 :param networks_client: network client from same tenant as shares
288 :param isolated_creds_client: DynamicCredentialProvider instance
Marc Koderer0abc93b2015-07-15 09:18:35 +0200289 If provided, then its networking will be used if needed.
290 If not provided, then common network will be used if needed.
Rodrigo Barbieri58d9de32016-09-06 13:16:47 -0300291 :param ignore_multitenancy_config: provide a share network regardless
292 of 'multitenancy_enabled' configuration value.
Marc Koderer0abc93b2015-07-15 09:18:35 +0200293 :returns: str -- share network id for shares_client tenant
294 :returns: None -- if single-tenant driver used
295 """
296
297 sc = shares_client
Valeriy Ponomaryovc5dae272016-06-10 18:29:24 +0300298 search_word = "reusable"
299 sn_name = "autogenerated_by_tempest_%s" % search_word
Marc Koderer0abc93b2015-07-15 09:18:35 +0200300
Rodrigo Barbieri58d9de32016-09-06 13:16:47 -0300301 if (not ignore_multitenancy_config and
302 not CONF.share.multitenancy_enabled):
Marc Koderer0abc93b2015-07-15 09:18:35 +0200303 # Assumed usage of a single-tenant driver
304 share_network_id = None
Marc Koderer0abc93b2015-07-15 09:18:35 +0200305 else:
Rodrigo Barbieri58d9de32016-09-06 13:16:47 -0300306 if sc.share_network_id:
307 # Share-network already exists, use it
308 share_network_id = sc.share_network_id
309 elif not CONF.share.create_networks_when_multitenancy_enabled:
310 share_network_id = None
Marc Koderer0abc93b2015-07-15 09:18:35 +0200311
312 # Try get suitable share-network
313 share_networks = sc.list_share_networks_with_detail()
314 for sn in share_networks:
Rodrigo Barbieri58d9de32016-09-06 13:16:47 -0300315 if (sn["neutron_net_id"] is None and
316 sn["neutron_subnet_id"] is None and
Marc Koderer0abc93b2015-07-15 09:18:35 +0200317 sn["name"] and search_word in sn["name"]):
318 share_network_id = sn["id"]
319 break
Marc Koderer0abc93b2015-07-15 09:18:35 +0200320
Rodrigo Barbieri58d9de32016-09-06 13:16:47 -0300321 # Create new share-network if one was not found
322 if share_network_id is None:
323 sn_desc = "This share-network was created by tempest"
324 sn = sc.create_share_network(name=sn_name,
325 description=sn_desc)
326 share_network_id = sn["id"]
327 else:
328 net_id = subnet_id = share_network_id = None
329
330 if not isolated_creds_client:
331 # Search for networks, created in previous runs
332 service_net_name = "share-service"
333 networks = networks_client.list_networks()
334 if "networks" in networks.keys():
335 networks = networks["networks"]
336 for network in networks:
337 if (service_net_name in network["name"] and
338 sc.tenant_id == network['tenant_id']):
339 net_id = network["id"]
340 if len(network["subnets"]) > 0:
341 subnet_id = network["subnets"][0]
342 break
343
344 # Create suitable network
345 if net_id is None or subnet_id is None:
346 ic = dynamic_creds.DynamicCredentialProvider(
347 identity_version=CONF.identity.auth_version,
348 name=service_net_name,
349 admin_role=CONF.identity.admin_role,
350 admin_creds=(
351 common_creds.
352 get_configured_admin_credentials()))
353 net_data = ic._create_network_resources(sc.tenant_id)
354 network, subnet, router = net_data
355 net_id = network["id"]
356 subnet_id = subnet["id"]
357
358 # Try get suitable share-network
359 share_networks = sc.list_share_networks_with_detail()
360 for sn in share_networks:
361 if (net_id == sn["neutron_net_id"] and
362 subnet_id == sn["neutron_subnet_id"] and
363 sn["name"] and search_word in sn["name"]):
364 share_network_id = sn["id"]
365 break
366 else:
367 sn_name = "autogenerated_by_tempest_for_isolated_creds"
368 # Use precreated network and subnet from isolated creds
369 net_id = isolated_creds_client.get_credentials(
370 isolated_creds_client.type_of_creds).network['id']
371 subnet_id = isolated_creds_client.get_credentials(
372 isolated_creds_client.type_of_creds).subnet['id']
373
374 # Create suitable share-network
375 if share_network_id is None:
376 sn_desc = "This share-network was created by tempest"
377 sn = sc.create_share_network(name=sn_name,
378 description=sn_desc,
379 neutron_net_id=net_id,
380 neutron_subnet_id=subnet_id)
381 share_network_id = sn["id"]
Marc Koderer0abc93b2015-07-15 09:18:35 +0200382
383 return share_network_id
384
385 @classmethod
marcusvrne0d7cfd2016-06-24 12:27:55 -0300386 def _create_share(cls, share_protocol=None, size=None, name=None,
Marc Koderer0abc93b2015-07-15 09:18:35 +0200387 snapshot_id=None, description=None, metadata=None,
388 share_network_id=None, share_type_id=None,
Andrew Kerrbf31e912015-07-29 10:39:38 -0400389 consistency_group_id=None, client=None,
Clinton Knighte5c8f092015-08-27 15:00:23 -0400390 cleanup_in_class=True, is_public=False, **kwargs):
Valeriy Ponomaryov1aaa72d2015-09-08 12:59:41 +0300391 client = client or cls.shares_v2_client
Marc Koderer0abc93b2015-07-15 09:18:35 +0200392 description = description or "Tempest's share"
393 share_network_id = share_network_id or client.share_network_id or None
394 metadata = metadata or {}
marcusvrne0d7cfd2016-06-24 12:27:55 -0300395 size = size or CONF.share.share_size
Clinton Knighte5c8f092015-08-27 15:00:23 -0400396 kwargs.update({
Marc Koderer0abc93b2015-07-15 09:18:35 +0200397 'share_protocol': share_protocol,
398 'size': size,
399 'name': name,
400 'snapshot_id': snapshot_id,
401 'description': description,
402 'metadata': metadata,
403 'share_network_id': share_network_id,
404 'share_type_id': share_type_id,
405 'is_public': is_public,
Clinton Knighte5c8f092015-08-27 15:00:23 -0400406 })
Andrew Kerrbf31e912015-07-29 10:39:38 -0400407 if consistency_group_id:
408 kwargs['consistency_group_id'] = consistency_group_id
409
Marc Koderer0abc93b2015-07-15 09:18:35 +0200410 share = client.create_share(**kwargs)
Andrew Kerrbf31e912015-07-29 10:39:38 -0400411 resource = {"type": "share", "id": share["id"], "client": client,
412 "consistency_group_id": consistency_group_id}
Marc Koderer0abc93b2015-07-15 09:18:35 +0200413 cleanup_list = (cls.class_resources if cleanup_in_class else
414 cls.method_resources)
415 cleanup_list.insert(0, resource)
416 return share
417
418 @classmethod
Rodrigo Barbieri427bc052016-06-06 17:10:06 -0300419 def migrate_share(
420 cls, share_id, dest_host, wait_for_status, client=None,
421 force_host_assisted_migration=False, new_share_network_id=None,
Rodrigo Barbierid38d2f52016-07-19 22:24:56 -0300422 new_share_type_id=None, **kwargs):
Clinton Knighte5c8f092015-08-27 15:00:23 -0400423 client = client or cls.shares_v2_client
Rodrigo Barbieri427bc052016-06-06 17:10:06 -0300424 client.migrate_share(
425 share_id, dest_host,
426 force_host_assisted_migration=force_host_assisted_migration,
427 new_share_network_id=new_share_network_id,
428 writable=False, preserve_metadata=False, nondisruptive=False,
Rodrigo Barbierid38d2f52016-07-19 22:24:56 -0300429 new_share_type_id=new_share_type_id, **kwargs)
Rodrigo Barbierie3305122016-02-03 14:32:24 -0200430 share = client.wait_for_migration_status(
Rodrigo Barbieri427bc052016-06-06 17:10:06 -0300431 share_id, dest_host, wait_for_status, **kwargs)
Rodrigo Barbierie3305122016-02-03 14:32:24 -0200432 return share
433
434 @classmethod
435 def migration_complete(cls, share_id, dest_host, client=None, **kwargs):
436 client = client or cls.shares_v2_client
437 client.migration_complete(share_id, **kwargs)
438 share = client.wait_for_migration_status(
Rodrigo Barbieri427bc052016-06-06 17:10:06 -0300439 share_id, dest_host, 'migration_success', **kwargs)
Rodrigo Barbierib7137ad2015-09-06 22:53:16 -0300440 return share
441
442 @classmethod
Rodrigo Barbieric9abf282016-08-24 22:01:31 -0300443 def migration_cancel(cls, share_id, dest_host, client=None, **kwargs):
444 client = client or cls.shares_v2_client
445 client.migration_cancel(share_id, **kwargs)
446 share = client.wait_for_migration_status(
447 share_id, dest_host, 'migration_cancelled', **kwargs)
448 return share
449
450 @classmethod
Marc Koderer0abc93b2015-07-15 09:18:35 +0200451 def create_share(cls, *args, **kwargs):
452 """Create one share and wait for available state. Retry if allowed."""
453 result = cls.create_shares([{"args": args, "kwargs": kwargs}])
454 return result[0]
455
456 @classmethod
457 def create_shares(cls, share_data_list):
458 """Creates several shares in parallel with retries.
459
460 Use this method when you want to create more than one share at same
461 time. Especially if config option 'share.share_creation_retry_number'
462 has value more than zero (0).
463 All shares will be expected to have 'available' status with or without
464 recreation else error will be raised.
465
466 :param share_data_list: list -- list of dictionaries with 'args' and
467 'kwargs' for '_create_share' method of this base class.
468 example of data:
469 share_data_list=[{'args': ['quuz'], 'kwargs': {'foo': 'bar'}}}]
470 :returns: list -- list of shares created using provided data.
471 """
472
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +0300473 for d in share_data_list:
Marc Koderer0abc93b2015-07-15 09:18:35 +0200474 if not isinstance(d, dict):
475 raise exceptions.TempestException(
476 "Expected 'dict', got '%s'" % type(d))
477 if "args" not in d:
478 d["args"] = []
479 if "kwargs" not in d:
480 d["kwargs"] = {}
481 if len(d) > 2:
482 raise exceptions.TempestException(
483 "Expected only 'args' and 'kwargs' keys. "
484 "Provided %s" % list(d))
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +0300485
486 data = []
487 for d in share_data_list:
488 client = d["kwargs"].pop("client", cls.shares_v2_client)
489 local_d = {
490 "args": d["args"],
491 "kwargs": copy.deepcopy(d["kwargs"]),
492 }
493 local_d["kwargs"]["client"] = client
494 local_d["share"] = cls._create_share(
495 *local_d["args"], **local_d["kwargs"])
496 local_d["cnt"] = 0
497 local_d["available"] = False
498 data.append(local_d)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200499
500 while not all(d["available"] for d in data):
501 for d in data:
502 if d["available"]:
503 continue
Valeriy Ponomaryov1a3e3382016-06-08 15:17:16 +0300504 client = d["kwargs"]["client"]
505 share_id = d["share"]["id"]
Marc Koderer0abc93b2015-07-15 09:18:35 +0200506 try:
Valeriy Ponomaryov1a3e3382016-06-08 15:17:16 +0300507 client.wait_for_share_status(share_id, "available")
Marc Koderer0abc93b2015-07-15 09:18:35 +0200508 d["available"] = True
509 except (share_exceptions.ShareBuildErrorException,
510 exceptions.TimeoutException) as e:
511 if CONF.share.share_creation_retry_number > d["cnt"]:
512 d["cnt"] += 1
513 msg = ("Share '%s' failed to be built. "
Valeriy Ponomaryov1a3e3382016-06-08 15:17:16 +0300514 "Trying create another." % share_id)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200515 LOG.error(msg)
516 LOG.error(e)
Valeriy Ponomaryov1a3e3382016-06-08 15:17:16 +0300517 cg_id = d["kwargs"].get("consistency_group_id")
518 if cg_id:
519 # NOTE(vponomaryov): delete errored share
520 # immediately in case share is part of CG.
521 client.delete_share(
522 share_id,
523 params={"consistency_group_id": cg_id})
524 client.wait_for_resource_deletion(
525 share_id=share_id)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200526 d["share"] = cls._create_share(
527 *d["args"], **d["kwargs"])
528 else:
gecong197358663802016-08-25 11:08:45 +0800529 raise
Marc Koderer0abc93b2015-07-15 09:18:35 +0200530
531 return [d["share"] for d in data]
532
533 @classmethod
Andrew Kerrbf31e912015-07-29 10:39:38 -0400534 def create_consistency_group(cls, client=None, cleanup_in_class=True,
535 share_network_id=None, **kwargs):
Clinton Knighte5c8f092015-08-27 15:00:23 -0400536 client = client or cls.shares_v2_client
Goutham Pacha Ravi9221f5e2016-04-21 13:17:49 -0400537 if kwargs.get('source_cgsnapshot_id') is None:
538 kwargs['share_network_id'] = (share_network_id or
539 client.share_network_id or None)
Andrew Kerrbf31e912015-07-29 10:39:38 -0400540 consistency_group = client.create_consistency_group(**kwargs)
541 resource = {
542 "type": "consistency_group",
543 "id": consistency_group["id"],
544 "client": client}
545 if cleanup_in_class:
546 cls.class_resources.insert(0, resource)
547 else:
548 cls.method_resources.insert(0, resource)
549
550 if kwargs.get('source_cgsnapshot_id'):
551 new_cg_shares = client.list_shares(
552 detailed=True,
553 params={'consistency_group_id': consistency_group['id']})
554
555 for share in new_cg_shares:
556 resource = {"type": "share",
557 "id": share["id"],
558 "client": client,
559 "consistency_group_id": share.get(
560 'consistency_group_id')}
561 if cleanup_in_class:
562 cls.class_resources.insert(0, resource)
563 else:
564 cls.method_resources.insert(0, resource)
565
566 client.wait_for_consistency_group_status(consistency_group['id'],
567 'available')
568 return consistency_group
569
570 @classmethod
Marc Koderer0abc93b2015-07-15 09:18:35 +0200571 def create_snapshot_wait_for_active(cls, share_id, name=None,
572 description=None, force=False,
573 client=None, cleanup_in_class=True):
574 if client is None:
Yogesh1f931ff2015-09-29 23:41:02 -0400575 client = cls.shares_v2_client
Marc Koderer0abc93b2015-07-15 09:18:35 +0200576 if description is None:
577 description = "Tempest's snapshot"
578 snapshot = client.create_snapshot(share_id, name, description, force)
579 resource = {
580 "type": "snapshot",
581 "id": snapshot["id"],
582 "client": client,
583 }
584 if cleanup_in_class:
585 cls.class_resources.insert(0, resource)
586 else:
587 cls.method_resources.insert(0, resource)
588 client.wait_for_snapshot_status(snapshot["id"], "available")
589 return snapshot
590
591 @classmethod
Andrew Kerrbf31e912015-07-29 10:39:38 -0400592 def create_cgsnapshot_wait_for_active(cls, consistency_group_id,
593 name=None, description=None,
Clinton Knighte5c8f092015-08-27 15:00:23 -0400594 client=None, cleanup_in_class=True,
595 **kwargs):
596 client = client or cls.shares_v2_client
Andrew Kerrbf31e912015-07-29 10:39:38 -0400597 if description is None:
598 description = "Tempest's cgsnapshot"
Clinton Knighte5c8f092015-08-27 15:00:23 -0400599 cgsnapshot = client.create_cgsnapshot(consistency_group_id,
600 name=name,
601 description=description,
602 **kwargs)
Andrew Kerrbf31e912015-07-29 10:39:38 -0400603 resource = {
604 "type": "cgsnapshot",
605 "id": cgsnapshot["id"],
606 "client": client,
607 }
608 if cleanup_in_class:
609 cls.class_resources.insert(0, resource)
610 else:
611 cls.method_resources.insert(0, resource)
612 client.wait_for_cgsnapshot_status(cgsnapshot["id"], "available")
613 return cgsnapshot
614
615 @classmethod
Yogeshbdb88102015-09-29 23:41:02 -0400616 def get_availability_zones(cls, client=None):
617 """List the availability zones for "manila-share" services
618
619 that are currently in "up" state.
620 """
621 client = client or cls.shares_v2_client
622 cls.services = client.list_services()
623 zones = [service['zone'] for service in cls.services if
624 service['binary'] == "manila-share" and
625 service['state'] == 'up']
626 return zones
627
Yogesh1f931ff2015-09-29 23:41:02 -0400628 def get_pools_for_replication_domain(self):
629 # Get the list of pools for the replication domain
630 pools = self.admin_client.list_pools(detail=True)['pools']
631 instance_host = self.shares[0]['host']
632 host_pool = [p for p in pools if p['name'] == instance_host][0]
633 rep_domain = host_pool['capabilities']['replication_domain']
634 pools_in_rep_domain = [p for p in pools if p['capabilities'][
635 'replication_domain'] == rep_domain]
636 return rep_domain, pools_in_rep_domain
637
Yogeshbdb88102015-09-29 23:41:02 -0400638 @classmethod
639 def create_share_replica(cls, share_id, availability_zone, client=None,
640 cleanup_in_class=False, cleanup=True):
641 client = client or cls.shares_v2_client
642 replica = client.create_share_replica(share_id, availability_zone)
643 resource = {
644 "type": "share_replica",
645 "id": replica["id"],
646 "client": client,
647 "share_id": share_id,
648 }
649 # NOTE(Yogi1): Cleanup needs to be disabled during promotion tests.
650 if cleanup:
651 if cleanup_in_class:
652 cls.class_resources.insert(0, resource)
653 else:
654 cls.method_resources.insert(0, resource)
655 client.wait_for_share_replica_status(
656 replica["id"], constants.STATUS_AVAILABLE)
657 return replica
658
659 @classmethod
660 def delete_share_replica(cls, replica_id, client=None):
661 client = client or cls.shares_v2_client
Yogesh1f931ff2015-09-29 23:41:02 -0400662 try:
663 client.delete_share_replica(replica_id)
664 client.wait_for_resource_deletion(replica_id=replica_id)
665 except exceptions.NotFound:
666 pass
Yogeshbdb88102015-09-29 23:41:02 -0400667
668 @classmethod
669 def promote_share_replica(cls, replica_id, client=None):
670 client = client or cls.shares_v2_client
671 replica = client.promote_share_replica(replica_id)
672 client.wait_for_share_replica_status(
673 replica["id"],
674 constants.REPLICATION_STATE_ACTIVE,
675 status_attr="replica_state")
676 return replica
677
678 @classmethod
Marc Koderer0abc93b2015-07-15 09:18:35 +0200679 def create_share_network(cls, client=None,
680 cleanup_in_class=False, **kwargs):
681 if client is None:
682 client = cls.shares_client
683 share_network = client.create_share_network(**kwargs)
684 resource = {
685 "type": "share_network",
686 "id": share_network["id"],
687 "client": client,
688 }
689 if cleanup_in_class:
690 cls.class_resources.insert(0, resource)
691 else:
692 cls.method_resources.insert(0, resource)
693 return share_network
694
695 @classmethod
696 def create_security_service(cls, ss_type="ldap", client=None,
697 cleanup_in_class=False, **kwargs):
698 if client is None:
699 client = cls.shares_client
700 security_service = client.create_security_service(ss_type, **kwargs)
701 resource = {
702 "type": "security_service",
703 "id": security_service["id"],
704 "client": client,
705 }
706 if cleanup_in_class:
707 cls.class_resources.insert(0, resource)
708 else:
709 cls.method_resources.insert(0, resource)
710 return security_service
711
712 @classmethod
713 def create_share_type(cls, name, is_public=True, client=None,
714 cleanup_in_class=True, **kwargs):
715 if client is None:
Valeriy Ponomaryova14c2252015-10-29 13:34:32 +0200716 client = cls.shares_v2_client
Marc Koderer0abc93b2015-07-15 09:18:35 +0200717 share_type = client.create_share_type(name, is_public, **kwargs)
718 resource = {
719 "type": "share_type",
720 "id": share_type["share_type"]["id"],
721 "client": client,
722 }
723 if cleanup_in_class:
724 cls.class_resources.insert(0, resource)
725 else:
726 cls.method_resources.insert(0, resource)
727 return share_type
728
729 @staticmethod
Clinton Knight4699a8c2016-08-16 22:36:13 -0400730 def add_extra_specs_to_dict(extra_specs=None):
731 """Add any required extra-specs to share type dictionary"""
Valeriy Ponomaryovad55dc52015-09-23 13:54:00 +0300732 dhss = six.text_type(CONF.share.multitenancy_enabled)
733 snapshot_support = six.text_type(
734 CONF.share.capability_snapshot_support)
Clinton Knight4699a8c2016-08-16 22:36:13 -0400735 create_from_snapshot_support = six.text_type(
736 CONF.share.capability_create_share_from_snapshot_support)
737
738 extra_specs_dict = {
Valeriy Ponomaryovad55dc52015-09-23 13:54:00 +0300739 "driver_handles_share_servers": dhss,
Marc Koderer0abc93b2015-07-15 09:18:35 +0200740 }
Clinton Knight4699a8c2016-08-16 22:36:13 -0400741
742 optional = {
743 "snapshot_support": snapshot_support,
744 "create_share_from_snapshot_support": create_from_snapshot_support,
745 }
746 # NOTE(gouthamr): In micro-versions < 2.24, snapshot_support is a
747 # required extra-spec
748 extra_specs_dict.update(optional)
749
Marc Koderer0abc93b2015-07-15 09:18:35 +0200750 if extra_specs:
Clinton Knight4699a8c2016-08-16 22:36:13 -0400751 extra_specs_dict.update(extra_specs)
752
753 return extra_specs_dict
Marc Koderer0abc93b2015-07-15 09:18:35 +0200754
755 @classmethod
756 def clear_isolated_creds(cls, creds=None):
757 if creds is None:
758 creds = cls.method_isolated_creds
759 for ic in creds:
760 if "deleted" not in ic.keys():
761 ic["deleted"] = False
762 if not ic["deleted"]:
763 with handle_cleanup_exceptions():
764 ic["method"]()
765 ic["deleted"] = True
766
767 @classmethod
Yogesh1f931ff2015-09-29 23:41:02 -0400768 def clear_share_replicas(cls, share_id, client=None):
769 client = client or cls.shares_v2_client
770 share_replicas = client.list_share_replicas(
771 share_id=share_id)
772
773 for replica in share_replicas:
774 try:
775 cls.delete_share_replica(replica['id'])
776 except exceptions.BadRequest:
777 # Ignore the exception due to deletion of last active replica
778 pass
779
780 @classmethod
Marc Koderer0abc93b2015-07-15 09:18:35 +0200781 def clear_resources(cls, resources=None):
782 """Deletes resources, that were created in test suites.
783
784 This method tries to remove resources from resource list,
785 if it is not found, assumed it was deleted in test itself.
786 It is expected, that all resources were added as LIFO
787 due to restriction of deletion resources, that is in the chain.
788
789 :param resources: dict with keys 'type','id','client' and 'deleted'
790 """
791
792 if resources is None:
793 resources = cls.method_resources
794 for res in resources:
795 if "deleted" not in res.keys():
796 res["deleted"] = False
797 if "client" not in res.keys():
798 res["client"] = cls.shares_client
799 if not(res["deleted"]):
800 res_id = res['id']
801 client = res["client"]
802 with handle_cleanup_exceptions():
803 if res["type"] is "share":
Yogesh1f931ff2015-09-29 23:41:02 -0400804 cls.clear_share_replicas(res_id)
Andrew Kerrbf31e912015-07-29 10:39:38 -0400805 cg_id = res.get('consistency_group_id')
806 if cg_id:
807 params = {'consistency_group_id': cg_id}
Clinton Knighte5c8f092015-08-27 15:00:23 -0400808 client.delete_share(res_id, params=params)
809 else:
810 client.delete_share(res_id)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200811 client.wait_for_resource_deletion(share_id=res_id)
812 elif res["type"] is "snapshot":
813 client.delete_snapshot(res_id)
814 client.wait_for_resource_deletion(snapshot_id=res_id)
815 elif res["type"] is "share_network":
816 client.delete_share_network(res_id)
817 client.wait_for_resource_deletion(sn_id=res_id)
818 elif res["type"] is "security_service":
819 client.delete_security_service(res_id)
820 client.wait_for_resource_deletion(ss_id=res_id)
821 elif res["type"] is "share_type":
822 client.delete_share_type(res_id)
823 client.wait_for_resource_deletion(st_id=res_id)
Andrew Kerrbf31e912015-07-29 10:39:38 -0400824 elif res["type"] is "consistency_group":
825 client.delete_consistency_group(res_id)
826 client.wait_for_resource_deletion(cg_id=res_id)
827 elif res["type"] is "cgsnapshot":
828 client.delete_cgsnapshot(res_id)
829 client.wait_for_resource_deletion(cgsnapshot_id=res_id)
Yogeshbdb88102015-09-29 23:41:02 -0400830 elif res["type"] is "share_replica":
831 client.delete_share_replica(res_id)
832 client.wait_for_resource_deletion(replica_id=res_id)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200833 else:
huayue97bacbf2016-01-04 09:57:39 +0800834 LOG.warning("Provided unsupported resource type for "
835 "cleanup '%s'. Skipping." % res["type"])
Marc Koderer0abc93b2015-07-15 09:18:35 +0200836 res["deleted"] = True
837
838 @classmethod
839 def generate_share_network_data(self):
840 data = {
841 "name": data_utils.rand_name("sn-name"),
842 "description": data_utils.rand_name("sn-desc"),
843 "neutron_net_id": data_utils.rand_name("net-id"),
844 "neutron_subnet_id": data_utils.rand_name("subnet-id"),
845 }
846 return data
847
848 @classmethod
849 def generate_security_service_data(self):
850 data = {
851 "name": data_utils.rand_name("ss-name"),
852 "description": data_utils.rand_name("ss-desc"),
Valeriy Ponomaryovfcde7712015-12-14 18:06:13 +0200853 "dns_ip": utils.rand_ip(),
854 "server": utils.rand_ip(),
Marc Koderer0abc93b2015-07-15 09:18:35 +0200855 "domain": data_utils.rand_name("ss-domain"),
856 "user": data_utils.rand_name("ss-user"),
857 "password": data_utils.rand_name("ss-password"),
858 }
859 return data
860
861 # Useful assertions
862 def assertDictMatch(self, d1, d2, approx_equal=False, tolerance=0.001):
863 """Assert two dicts are equivalent.
864
865 This is a 'deep' match in the sense that it handles nested
866 dictionaries appropriately.
867
868 NOTE:
869
870 If you don't care (or don't know) a given value, you can specify
871 the string DONTCARE as the value. This will cause that dict-item
872 to be skipped.
873
874 """
875 def raise_assertion(msg):
876 d1str = str(d1)
877 d2str = str(d2)
878 base_msg = ('Dictionaries do not match. %(msg)s d1: %(d1str)s '
879 'd2: %(d2str)s' %
880 {"msg": msg, "d1str": d1str, "d2str": d2str})
881 raise AssertionError(base_msg)
882
883 d1keys = set(d1.keys())
884 d2keys = set(d2.keys())
885 if d1keys != d2keys:
886 d1only = d1keys - d2keys
887 d2only = d2keys - d1keys
888 raise_assertion('Keys in d1 and not d2: %(d1only)s. '
889 'Keys in d2 and not d1: %(d2only)s' %
890 {"d1only": d1only, "d2only": d2only})
891
892 for key in d1keys:
893 d1value = d1[key]
894 d2value = d2[key]
895 try:
896 error = abs(float(d1value) - float(d2value))
897 within_tolerance = error <= tolerance
898 except (ValueError, TypeError):
daiki kato6914b1a2016-03-16 17:16:57 +0900899 # If both values aren't convertible to float, just ignore
Marc Koderer0abc93b2015-07-15 09:18:35 +0200900 # ValueError if arg is a str, TypeError if it's something else
901 # (like None)
902 within_tolerance = False
903
904 if hasattr(d1value, 'keys') and hasattr(d2value, 'keys'):
905 self.assertDictMatch(d1value, d2value)
906 elif 'DONTCARE' in (d1value, d2value):
907 continue
908 elif approx_equal and within_tolerance:
909 continue
910 elif d1value != d2value:
911 raise_assertion("d1['%(key)s']=%(d1value)s != "
912 "d2['%(key)s']=%(d2value)s" %
913 {
914 "key": key,
915 "d1value": d1value,
916 "d2value": d2value
917 })
918
919
920class BaseSharesAltTest(BaseSharesTest):
921 """Base test case class for all Shares Alt API tests."""
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +0300922 credentials = ('alt', )
Marc Koderer0abc93b2015-07-15 09:18:35 +0200923
924
925class BaseSharesAdminTest(BaseSharesTest):
926 """Base test case class for all Shares Admin API tests."""
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +0300927 credentials = ('admin', )
928
929
930class BaseSharesMixedTest(BaseSharesTest):
931 """Base test case class for all Shares API tests with all user roles."""
932 credentials = ('primary', 'alt', 'admin')
Marc Koderer0abc93b2015-07-15 09:18:35 +0200933
934 @classmethod
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +0300935 def setup_clients(cls):
936 super(BaseSharesMixedTest, cls).setup_clients()
937 cls.admin_shares_client = shares_client.SharesClient(
938 cls.os_admin.auth_provider)
939 cls.admin_shares_v2_client = shares_v2_client.SharesV2Client(
940 cls.os_admin.auth_provider)
941 cls.alt_shares_client = shares_client.SharesClient(
942 cls.os_alt.auth_provider)
943 cls.alt_shares_v2_client = shares_v2_client.SharesV2Client(
944 cls.os_alt.auth_provider)
945
946 if CONF.share.multitenancy_enabled:
947 admin_share_network_id = cls.provide_share_network(
948 cls.admin_shares_v2_client, cls.os_admin.networks_client)
949 cls.admin_shares_client.share_network_id = admin_share_network_id
950 cls.admin_shares_v2_client.share_network_id = (
951 admin_share_network_id)
952
953 alt_share_network_id = cls.provide_share_network(
954 cls.alt_shares_v2_client, cls.os_alt.networks_client)
955 cls.alt_shares_client.share_network_id = alt_share_network_id
956 cls.alt_shares_v2_client.share_network_id = alt_share_network_id