blob: 6b483485d0764b5ed2addda802e7d40ef52310c5 [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)
234 cls.shares_client = os.shares_client
235 os.shares_v2_client = shares_v2_client.SharesV2Client(
236 os.auth_provider)
237 cls.shares_v2_client = os.shares_v2_client
238 if CONF.share.multitenancy_enabled:
Valeriy Ponomaryovc5dae272016-06-10 18:29:24 +0300239 if (not CONF.service_available.neutron and
240 CONF.share.create_networks_when_multitenancy_enabled):
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +0300241 raise cls.skipException("Neutron support is required")
242 share_network_id = cls.provide_share_network(
243 cls.shares_v2_client, os.networks_client)
244 cls.shares_client.share_network_id = share_network_id
245 cls.shares_v2_client.share_network_id = share_network_id
246
247 @classmethod
Marc Koderer0abc93b2015-07-15 09:18:35 +0200248 def resource_setup(cls):
249 if not (any(p in CONF.share.enable_protocols
250 for p in cls.protocols) and
251 CONF.service_available.manila):
252 skip_msg = "Manila is disabled"
253 raise cls.skipException(skip_msg)
254 super(BaseSharesTest, cls).resource_setup()
Marc Koderer0abc93b2015-07-15 09:18:35 +0200255
256 def setUp(self):
257 super(BaseSharesTest, self).setUp()
Marc Koderer0abc93b2015-07-15 09:18:35 +0200258 self.addCleanup(self.clear_isolated_creds)
Valeriy Ponomaryovdd162cb2016-01-20 19:09:49 +0200259 self.addCleanup(self.clear_resources)
Valeriy Ponomaryov2abf5d72016-06-01 18:30:12 +0300260 verify_test_has_appropriate_tags(self)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200261
262 @classmethod
263 def resource_cleanup(cls):
Marc Koderer0abc93b2015-07-15 09:18:35 +0200264 cls.clear_resources(cls.class_resources)
265 cls.clear_isolated_creds(cls.class_isolated_creds)
Sam Wan241029c2016-07-26 03:37:42 -0400266 super(BaseSharesTest, cls).resource_cleanup()
Marc Koderer0abc93b2015-07-15 09:18:35 +0200267
268 @classmethod
269 @network_synchronized
Valeriy Ponomaryov48a2bd72015-11-05 13:22:44 +0200270 def provide_share_network(cls, shares_client, networks_client,
Rodrigo Barbieri58d9de32016-09-06 13:16:47 -0300271 isolated_creds_client=None,
272 ignore_multitenancy_config=False):
Marc Koderer0abc93b2015-07-15 09:18:35 +0200273 """Used for finding/creating share network for multitenant driver.
274
275 This method creates/gets entity share-network for one tenant. This
276 share-network will be used for creation of service vm.
277
278 :param shares_client: shares client, which requires share-network
Valeriy Ponomaryov48a2bd72015-11-05 13:22:44 +0200279 :param networks_client: network client from same tenant as shares
280 :param isolated_creds_client: DynamicCredentialProvider instance
Marc Koderer0abc93b2015-07-15 09:18:35 +0200281 If provided, then its networking will be used if needed.
282 If not provided, then common network will be used if needed.
Rodrigo Barbieri58d9de32016-09-06 13:16:47 -0300283 :param ignore_multitenancy_config: provide a share network regardless
284 of 'multitenancy_enabled' configuration value.
Marc Koderer0abc93b2015-07-15 09:18:35 +0200285 :returns: str -- share network id for shares_client tenant
286 :returns: None -- if single-tenant driver used
287 """
288
289 sc = shares_client
Valeriy Ponomaryovc5dae272016-06-10 18:29:24 +0300290 search_word = "reusable"
291 sn_name = "autogenerated_by_tempest_%s" % search_word
Marc Koderer0abc93b2015-07-15 09:18:35 +0200292
Rodrigo Barbieri58d9de32016-09-06 13:16:47 -0300293 if (not ignore_multitenancy_config and
294 not CONF.share.multitenancy_enabled):
Marc Koderer0abc93b2015-07-15 09:18:35 +0200295 # Assumed usage of a single-tenant driver
296 share_network_id = None
Marc Koderer0abc93b2015-07-15 09:18:35 +0200297 else:
Rodrigo Barbieri58d9de32016-09-06 13:16:47 -0300298 if sc.share_network_id:
299 # Share-network already exists, use it
300 share_network_id = sc.share_network_id
301 elif not CONF.share.create_networks_when_multitenancy_enabled:
302 share_network_id = None
Marc Koderer0abc93b2015-07-15 09:18:35 +0200303
304 # Try get suitable share-network
305 share_networks = sc.list_share_networks_with_detail()
306 for sn in share_networks:
Rodrigo Barbieri58d9de32016-09-06 13:16:47 -0300307 if (sn["neutron_net_id"] is None and
308 sn["neutron_subnet_id"] is None and
Marc Koderer0abc93b2015-07-15 09:18:35 +0200309 sn["name"] and search_word in sn["name"]):
310 share_network_id = sn["id"]
311 break
Marc Koderer0abc93b2015-07-15 09:18:35 +0200312
Rodrigo Barbieri58d9de32016-09-06 13:16:47 -0300313 # Create new share-network if one was not found
314 if share_network_id is None:
315 sn_desc = "This share-network was created by tempest"
316 sn = sc.create_share_network(name=sn_name,
317 description=sn_desc)
318 share_network_id = sn["id"]
319 else:
320 net_id = subnet_id = share_network_id = None
321
322 if not isolated_creds_client:
323 # Search for networks, created in previous runs
324 service_net_name = "share-service"
325 networks = networks_client.list_networks()
326 if "networks" in networks.keys():
327 networks = networks["networks"]
328 for network in networks:
329 if (service_net_name in network["name"] and
330 sc.tenant_id == network['tenant_id']):
331 net_id = network["id"]
332 if len(network["subnets"]) > 0:
333 subnet_id = network["subnets"][0]
334 break
335
336 # Create suitable network
337 if net_id is None or subnet_id is None:
338 ic = dynamic_creds.DynamicCredentialProvider(
339 identity_version=CONF.identity.auth_version,
340 name=service_net_name,
341 admin_role=CONF.identity.admin_role,
342 admin_creds=(
343 common_creds.
344 get_configured_admin_credentials()))
345 net_data = ic._create_network_resources(sc.tenant_id)
346 network, subnet, router = net_data
347 net_id = network["id"]
348 subnet_id = subnet["id"]
349
350 # Try get suitable share-network
351 share_networks = sc.list_share_networks_with_detail()
352 for sn in share_networks:
353 if (net_id == sn["neutron_net_id"] and
354 subnet_id == sn["neutron_subnet_id"] and
355 sn["name"] and search_word in sn["name"]):
356 share_network_id = sn["id"]
357 break
358 else:
359 sn_name = "autogenerated_by_tempest_for_isolated_creds"
360 # Use precreated network and subnet from isolated creds
361 net_id = isolated_creds_client.get_credentials(
362 isolated_creds_client.type_of_creds).network['id']
363 subnet_id = isolated_creds_client.get_credentials(
364 isolated_creds_client.type_of_creds).subnet['id']
365
366 # Create suitable share-network
367 if share_network_id is None:
368 sn_desc = "This share-network was created by tempest"
369 sn = sc.create_share_network(name=sn_name,
370 description=sn_desc,
371 neutron_net_id=net_id,
372 neutron_subnet_id=subnet_id)
373 share_network_id = sn["id"]
Marc Koderer0abc93b2015-07-15 09:18:35 +0200374
375 return share_network_id
376
377 @classmethod
marcusvrne0d7cfd2016-06-24 12:27:55 -0300378 def _create_share(cls, share_protocol=None, size=None, name=None,
Marc Koderer0abc93b2015-07-15 09:18:35 +0200379 snapshot_id=None, description=None, metadata=None,
380 share_network_id=None, share_type_id=None,
Andrew Kerrbf31e912015-07-29 10:39:38 -0400381 consistency_group_id=None, client=None,
Clinton Knighte5c8f092015-08-27 15:00:23 -0400382 cleanup_in_class=True, is_public=False, **kwargs):
Valeriy Ponomaryov1aaa72d2015-09-08 12:59:41 +0300383 client = client or cls.shares_v2_client
Marc Koderer0abc93b2015-07-15 09:18:35 +0200384 description = description or "Tempest's share"
385 share_network_id = share_network_id or client.share_network_id or None
386 metadata = metadata or {}
marcusvrne0d7cfd2016-06-24 12:27:55 -0300387 size = size or CONF.share.share_size
Clinton Knighte5c8f092015-08-27 15:00:23 -0400388 kwargs.update({
Marc Koderer0abc93b2015-07-15 09:18:35 +0200389 'share_protocol': share_protocol,
390 'size': size,
391 'name': name,
392 'snapshot_id': snapshot_id,
393 'description': description,
394 'metadata': metadata,
395 'share_network_id': share_network_id,
396 'share_type_id': share_type_id,
397 'is_public': is_public,
Clinton Knighte5c8f092015-08-27 15:00:23 -0400398 })
Andrew Kerrbf31e912015-07-29 10:39:38 -0400399 if consistency_group_id:
400 kwargs['consistency_group_id'] = consistency_group_id
401
Marc Koderer0abc93b2015-07-15 09:18:35 +0200402 share = client.create_share(**kwargs)
Andrew Kerrbf31e912015-07-29 10:39:38 -0400403 resource = {"type": "share", "id": share["id"], "client": client,
404 "consistency_group_id": consistency_group_id}
Marc Koderer0abc93b2015-07-15 09:18:35 +0200405 cleanup_list = (cls.class_resources if cleanup_in_class else
406 cls.method_resources)
407 cleanup_list.insert(0, resource)
408 return share
409
410 @classmethod
Rodrigo Barbieri427bc052016-06-06 17:10:06 -0300411 def migrate_share(
412 cls, share_id, dest_host, wait_for_status, client=None,
413 force_host_assisted_migration=False, new_share_network_id=None,
Rodrigo Barbierid38d2f52016-07-19 22:24:56 -0300414 new_share_type_id=None, **kwargs):
Clinton Knighte5c8f092015-08-27 15:00:23 -0400415 client = client or cls.shares_v2_client
Rodrigo Barbieri427bc052016-06-06 17:10:06 -0300416 client.migrate_share(
417 share_id, dest_host,
418 force_host_assisted_migration=force_host_assisted_migration,
419 new_share_network_id=new_share_network_id,
420 writable=False, preserve_metadata=False, nondisruptive=False,
Rodrigo Barbierid38d2f52016-07-19 22:24:56 -0300421 new_share_type_id=new_share_type_id, **kwargs)
Rodrigo Barbierie3305122016-02-03 14:32:24 -0200422 share = client.wait_for_migration_status(
Rodrigo Barbieri427bc052016-06-06 17:10:06 -0300423 share_id, dest_host, wait_for_status, **kwargs)
Rodrigo Barbierie3305122016-02-03 14:32:24 -0200424 return share
425
426 @classmethod
427 def migration_complete(cls, share_id, dest_host, client=None, **kwargs):
428 client = client or cls.shares_v2_client
429 client.migration_complete(share_id, **kwargs)
430 share = client.wait_for_migration_status(
Rodrigo Barbieri427bc052016-06-06 17:10:06 -0300431 share_id, dest_host, 'migration_success', **kwargs)
Rodrigo Barbierib7137ad2015-09-06 22:53:16 -0300432 return share
433
434 @classmethod
Rodrigo Barbieric9abf282016-08-24 22:01:31 -0300435 def migration_cancel(cls, share_id, dest_host, client=None, **kwargs):
436 client = client or cls.shares_v2_client
437 client.migration_cancel(share_id, **kwargs)
438 share = client.wait_for_migration_status(
439 share_id, dest_host, 'migration_cancelled', **kwargs)
440 return share
441
442 @classmethod
Marc Koderer0abc93b2015-07-15 09:18:35 +0200443 def create_share(cls, *args, **kwargs):
444 """Create one share and wait for available state. Retry if allowed."""
445 result = cls.create_shares([{"args": args, "kwargs": kwargs}])
446 return result[0]
447
448 @classmethod
449 def create_shares(cls, share_data_list):
450 """Creates several shares in parallel with retries.
451
452 Use this method when you want to create more than one share at same
453 time. Especially if config option 'share.share_creation_retry_number'
454 has value more than zero (0).
455 All shares will be expected to have 'available' status with or without
456 recreation else error will be raised.
457
458 :param share_data_list: list -- list of dictionaries with 'args' and
459 'kwargs' for '_create_share' method of this base class.
460 example of data:
461 share_data_list=[{'args': ['quuz'], 'kwargs': {'foo': 'bar'}}}]
462 :returns: list -- list of shares created using provided data.
463 """
464
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +0300465 for d in share_data_list:
Marc Koderer0abc93b2015-07-15 09:18:35 +0200466 if not isinstance(d, dict):
467 raise exceptions.TempestException(
468 "Expected 'dict', got '%s'" % type(d))
469 if "args" not in d:
470 d["args"] = []
471 if "kwargs" not in d:
472 d["kwargs"] = {}
473 if len(d) > 2:
474 raise exceptions.TempestException(
475 "Expected only 'args' and 'kwargs' keys. "
476 "Provided %s" % list(d))
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +0300477
478 data = []
479 for d in share_data_list:
480 client = d["kwargs"].pop("client", cls.shares_v2_client)
481 local_d = {
482 "args": d["args"],
483 "kwargs": copy.deepcopy(d["kwargs"]),
484 }
485 local_d["kwargs"]["client"] = client
486 local_d["share"] = cls._create_share(
487 *local_d["args"], **local_d["kwargs"])
488 local_d["cnt"] = 0
489 local_d["available"] = False
490 data.append(local_d)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200491
492 while not all(d["available"] for d in data):
493 for d in data:
494 if d["available"]:
495 continue
Valeriy Ponomaryov1a3e3382016-06-08 15:17:16 +0300496 client = d["kwargs"]["client"]
497 share_id = d["share"]["id"]
Marc Koderer0abc93b2015-07-15 09:18:35 +0200498 try:
Valeriy Ponomaryov1a3e3382016-06-08 15:17:16 +0300499 client.wait_for_share_status(share_id, "available")
Marc Koderer0abc93b2015-07-15 09:18:35 +0200500 d["available"] = True
501 except (share_exceptions.ShareBuildErrorException,
502 exceptions.TimeoutException) as e:
503 if CONF.share.share_creation_retry_number > d["cnt"]:
504 d["cnt"] += 1
505 msg = ("Share '%s' failed to be built. "
Valeriy Ponomaryov1a3e3382016-06-08 15:17:16 +0300506 "Trying create another." % share_id)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200507 LOG.error(msg)
508 LOG.error(e)
Valeriy Ponomaryov1a3e3382016-06-08 15:17:16 +0300509 cg_id = d["kwargs"].get("consistency_group_id")
510 if cg_id:
511 # NOTE(vponomaryov): delete errored share
512 # immediately in case share is part of CG.
513 client.delete_share(
514 share_id,
515 params={"consistency_group_id": cg_id})
516 client.wait_for_resource_deletion(
517 share_id=share_id)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200518 d["share"] = cls._create_share(
519 *d["args"], **d["kwargs"])
520 else:
gecong197358663802016-08-25 11:08:45 +0800521 raise
Marc Koderer0abc93b2015-07-15 09:18:35 +0200522
523 return [d["share"] for d in data]
524
525 @classmethod
Andrew Kerrbf31e912015-07-29 10:39:38 -0400526 def create_consistency_group(cls, client=None, cleanup_in_class=True,
527 share_network_id=None, **kwargs):
Clinton Knighte5c8f092015-08-27 15:00:23 -0400528 client = client or cls.shares_v2_client
Goutham Pacha Ravi9221f5e2016-04-21 13:17:49 -0400529 if kwargs.get('source_cgsnapshot_id') is None:
530 kwargs['share_network_id'] = (share_network_id or
531 client.share_network_id or None)
Andrew Kerrbf31e912015-07-29 10:39:38 -0400532 consistency_group = client.create_consistency_group(**kwargs)
533 resource = {
534 "type": "consistency_group",
535 "id": consistency_group["id"],
536 "client": client}
537 if cleanup_in_class:
538 cls.class_resources.insert(0, resource)
539 else:
540 cls.method_resources.insert(0, resource)
541
542 if kwargs.get('source_cgsnapshot_id'):
543 new_cg_shares = client.list_shares(
544 detailed=True,
545 params={'consistency_group_id': consistency_group['id']})
546
547 for share in new_cg_shares:
548 resource = {"type": "share",
549 "id": share["id"],
550 "client": client,
551 "consistency_group_id": share.get(
552 'consistency_group_id')}
553 if cleanup_in_class:
554 cls.class_resources.insert(0, resource)
555 else:
556 cls.method_resources.insert(0, resource)
557
558 client.wait_for_consistency_group_status(consistency_group['id'],
559 'available')
560 return consistency_group
561
562 @classmethod
Marc Koderer0abc93b2015-07-15 09:18:35 +0200563 def create_snapshot_wait_for_active(cls, share_id, name=None,
564 description=None, force=False,
565 client=None, cleanup_in_class=True):
566 if client is None:
Yogesh1f931ff2015-09-29 23:41:02 -0400567 client = cls.shares_v2_client
Marc Koderer0abc93b2015-07-15 09:18:35 +0200568 if description is None:
569 description = "Tempest's snapshot"
570 snapshot = client.create_snapshot(share_id, name, description, force)
571 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)
580 client.wait_for_snapshot_status(snapshot["id"], "available")
581 return snapshot
582
583 @classmethod
Andrew Kerrbf31e912015-07-29 10:39:38 -0400584 def create_cgsnapshot_wait_for_active(cls, consistency_group_id,
585 name=None, description=None,
Clinton Knighte5c8f092015-08-27 15:00:23 -0400586 client=None, cleanup_in_class=True,
587 **kwargs):
588 client = client or cls.shares_v2_client
Andrew Kerrbf31e912015-07-29 10:39:38 -0400589 if description is None:
590 description = "Tempest's cgsnapshot"
Clinton Knighte5c8f092015-08-27 15:00:23 -0400591 cgsnapshot = client.create_cgsnapshot(consistency_group_id,
592 name=name,
593 description=description,
594 **kwargs)
Andrew Kerrbf31e912015-07-29 10:39:38 -0400595 resource = {
596 "type": "cgsnapshot",
597 "id": cgsnapshot["id"],
598 "client": client,
599 }
600 if cleanup_in_class:
601 cls.class_resources.insert(0, resource)
602 else:
603 cls.method_resources.insert(0, resource)
604 client.wait_for_cgsnapshot_status(cgsnapshot["id"], "available")
605 return cgsnapshot
606
607 @classmethod
Yogeshbdb88102015-09-29 23:41:02 -0400608 def get_availability_zones(cls, client=None):
609 """List the availability zones for "manila-share" services
610
611 that are currently in "up" state.
612 """
613 client = client or cls.shares_v2_client
614 cls.services = client.list_services()
615 zones = [service['zone'] for service in cls.services if
616 service['binary'] == "manila-share" and
617 service['state'] == 'up']
618 return zones
619
Yogesh1f931ff2015-09-29 23:41:02 -0400620 def get_pools_for_replication_domain(self):
621 # Get the list of pools for the replication domain
622 pools = self.admin_client.list_pools(detail=True)['pools']
623 instance_host = self.shares[0]['host']
624 host_pool = [p for p in pools if p['name'] == instance_host][0]
625 rep_domain = host_pool['capabilities']['replication_domain']
626 pools_in_rep_domain = [p for p in pools if p['capabilities'][
627 'replication_domain'] == rep_domain]
628 return rep_domain, pools_in_rep_domain
629
Yogeshbdb88102015-09-29 23:41:02 -0400630 @classmethod
631 def create_share_replica(cls, share_id, availability_zone, client=None,
632 cleanup_in_class=False, cleanup=True):
633 client = client or cls.shares_v2_client
634 replica = client.create_share_replica(share_id, availability_zone)
635 resource = {
636 "type": "share_replica",
637 "id": replica["id"],
638 "client": client,
639 "share_id": share_id,
640 }
641 # NOTE(Yogi1): Cleanup needs to be disabled during promotion tests.
642 if cleanup:
643 if cleanup_in_class:
644 cls.class_resources.insert(0, resource)
645 else:
646 cls.method_resources.insert(0, resource)
647 client.wait_for_share_replica_status(
648 replica["id"], constants.STATUS_AVAILABLE)
649 return replica
650
651 @classmethod
652 def delete_share_replica(cls, replica_id, client=None):
653 client = client or cls.shares_v2_client
Yogesh1f931ff2015-09-29 23:41:02 -0400654 try:
655 client.delete_share_replica(replica_id)
656 client.wait_for_resource_deletion(replica_id=replica_id)
657 except exceptions.NotFound:
658 pass
Yogeshbdb88102015-09-29 23:41:02 -0400659
660 @classmethod
661 def promote_share_replica(cls, replica_id, client=None):
662 client = client or cls.shares_v2_client
663 replica = client.promote_share_replica(replica_id)
664 client.wait_for_share_replica_status(
665 replica["id"],
666 constants.REPLICATION_STATE_ACTIVE,
667 status_attr="replica_state")
668 return replica
669
670 @classmethod
Marc Koderer0abc93b2015-07-15 09:18:35 +0200671 def create_share_network(cls, client=None,
672 cleanup_in_class=False, **kwargs):
673 if client is None:
674 client = cls.shares_client
675 share_network = client.create_share_network(**kwargs)
676 resource = {
677 "type": "share_network",
678 "id": share_network["id"],
679 "client": client,
680 }
681 if cleanup_in_class:
682 cls.class_resources.insert(0, resource)
683 else:
684 cls.method_resources.insert(0, resource)
685 return share_network
686
687 @classmethod
688 def create_security_service(cls, ss_type="ldap", client=None,
689 cleanup_in_class=False, **kwargs):
690 if client is None:
691 client = cls.shares_client
692 security_service = client.create_security_service(ss_type, **kwargs)
693 resource = {
694 "type": "security_service",
695 "id": security_service["id"],
696 "client": client,
697 }
698 if cleanup_in_class:
699 cls.class_resources.insert(0, resource)
700 else:
701 cls.method_resources.insert(0, resource)
702 return security_service
703
704 @classmethod
705 def create_share_type(cls, name, is_public=True, client=None,
706 cleanup_in_class=True, **kwargs):
707 if client is None:
Valeriy Ponomaryova14c2252015-10-29 13:34:32 +0200708 client = cls.shares_v2_client
Marc Koderer0abc93b2015-07-15 09:18:35 +0200709 share_type = client.create_share_type(name, is_public, **kwargs)
710 resource = {
711 "type": "share_type",
712 "id": share_type["share_type"]["id"],
713 "client": client,
714 }
715 if cleanup_in_class:
716 cls.class_resources.insert(0, resource)
717 else:
718 cls.method_resources.insert(0, resource)
719 return share_type
720
721 @staticmethod
722 def add_required_extra_specs_to_dict(extra_specs=None):
Valeriy Ponomaryovad55dc52015-09-23 13:54:00 +0300723 dhss = six.text_type(CONF.share.multitenancy_enabled)
724 snapshot_support = six.text_type(
725 CONF.share.capability_snapshot_support)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200726 required = {
Valeriy Ponomaryovad55dc52015-09-23 13:54:00 +0300727 "driver_handles_share_servers": dhss,
728 "snapshot_support": snapshot_support,
Marc Koderer0abc93b2015-07-15 09:18:35 +0200729 }
730 if extra_specs:
731 required.update(extra_specs)
732 return required
733
734 @classmethod
735 def clear_isolated_creds(cls, creds=None):
736 if creds is None:
737 creds = cls.method_isolated_creds
738 for ic in creds:
739 if "deleted" not in ic.keys():
740 ic["deleted"] = False
741 if not ic["deleted"]:
742 with handle_cleanup_exceptions():
743 ic["method"]()
744 ic["deleted"] = True
745
746 @classmethod
Yogesh1f931ff2015-09-29 23:41:02 -0400747 def clear_share_replicas(cls, share_id, client=None):
748 client = client or cls.shares_v2_client
749 share_replicas = client.list_share_replicas(
750 share_id=share_id)
751
752 for replica in share_replicas:
753 try:
754 cls.delete_share_replica(replica['id'])
755 except exceptions.BadRequest:
756 # Ignore the exception due to deletion of last active replica
757 pass
758
759 @classmethod
Marc Koderer0abc93b2015-07-15 09:18:35 +0200760 def clear_resources(cls, resources=None):
761 """Deletes resources, that were created in test suites.
762
763 This method tries to remove resources from resource list,
764 if it is not found, assumed it was deleted in test itself.
765 It is expected, that all resources were added as LIFO
766 due to restriction of deletion resources, that is in the chain.
767
768 :param resources: dict with keys 'type','id','client' and 'deleted'
769 """
770
771 if resources is None:
772 resources = cls.method_resources
773 for res in resources:
774 if "deleted" not in res.keys():
775 res["deleted"] = False
776 if "client" not in res.keys():
777 res["client"] = cls.shares_client
778 if not(res["deleted"]):
779 res_id = res['id']
780 client = res["client"]
781 with handle_cleanup_exceptions():
782 if res["type"] is "share":
Yogesh1f931ff2015-09-29 23:41:02 -0400783 cls.clear_share_replicas(res_id)
Andrew Kerrbf31e912015-07-29 10:39:38 -0400784 cg_id = res.get('consistency_group_id')
785 if cg_id:
786 params = {'consistency_group_id': cg_id}
Clinton Knighte5c8f092015-08-27 15:00:23 -0400787 client.delete_share(res_id, params=params)
788 else:
789 client.delete_share(res_id)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200790 client.wait_for_resource_deletion(share_id=res_id)
791 elif res["type"] is "snapshot":
792 client.delete_snapshot(res_id)
793 client.wait_for_resource_deletion(snapshot_id=res_id)
794 elif res["type"] is "share_network":
795 client.delete_share_network(res_id)
796 client.wait_for_resource_deletion(sn_id=res_id)
797 elif res["type"] is "security_service":
798 client.delete_security_service(res_id)
799 client.wait_for_resource_deletion(ss_id=res_id)
800 elif res["type"] is "share_type":
801 client.delete_share_type(res_id)
802 client.wait_for_resource_deletion(st_id=res_id)
Andrew Kerrbf31e912015-07-29 10:39:38 -0400803 elif res["type"] is "consistency_group":
804 client.delete_consistency_group(res_id)
805 client.wait_for_resource_deletion(cg_id=res_id)
806 elif res["type"] is "cgsnapshot":
807 client.delete_cgsnapshot(res_id)
808 client.wait_for_resource_deletion(cgsnapshot_id=res_id)
Yogeshbdb88102015-09-29 23:41:02 -0400809 elif res["type"] is "share_replica":
810 client.delete_share_replica(res_id)
811 client.wait_for_resource_deletion(replica_id=res_id)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200812 else:
huayue97bacbf2016-01-04 09:57:39 +0800813 LOG.warning("Provided unsupported resource type for "
814 "cleanup '%s'. Skipping." % res["type"])
Marc Koderer0abc93b2015-07-15 09:18:35 +0200815 res["deleted"] = True
816
817 @classmethod
818 def generate_share_network_data(self):
819 data = {
820 "name": data_utils.rand_name("sn-name"),
821 "description": data_utils.rand_name("sn-desc"),
822 "neutron_net_id": data_utils.rand_name("net-id"),
823 "neutron_subnet_id": data_utils.rand_name("subnet-id"),
824 }
825 return data
826
827 @classmethod
828 def generate_security_service_data(self):
829 data = {
830 "name": data_utils.rand_name("ss-name"),
831 "description": data_utils.rand_name("ss-desc"),
Valeriy Ponomaryovfcde7712015-12-14 18:06:13 +0200832 "dns_ip": utils.rand_ip(),
833 "server": utils.rand_ip(),
Marc Koderer0abc93b2015-07-15 09:18:35 +0200834 "domain": data_utils.rand_name("ss-domain"),
835 "user": data_utils.rand_name("ss-user"),
836 "password": data_utils.rand_name("ss-password"),
837 }
838 return data
839
840 # Useful assertions
841 def assertDictMatch(self, d1, d2, approx_equal=False, tolerance=0.001):
842 """Assert two dicts are equivalent.
843
844 This is a 'deep' match in the sense that it handles nested
845 dictionaries appropriately.
846
847 NOTE:
848
849 If you don't care (or don't know) a given value, you can specify
850 the string DONTCARE as the value. This will cause that dict-item
851 to be skipped.
852
853 """
854 def raise_assertion(msg):
855 d1str = str(d1)
856 d2str = str(d2)
857 base_msg = ('Dictionaries do not match. %(msg)s d1: %(d1str)s '
858 'd2: %(d2str)s' %
859 {"msg": msg, "d1str": d1str, "d2str": d2str})
860 raise AssertionError(base_msg)
861
862 d1keys = set(d1.keys())
863 d2keys = set(d2.keys())
864 if d1keys != d2keys:
865 d1only = d1keys - d2keys
866 d2only = d2keys - d1keys
867 raise_assertion('Keys in d1 and not d2: %(d1only)s. '
868 'Keys in d2 and not d1: %(d2only)s' %
869 {"d1only": d1only, "d2only": d2only})
870
871 for key in d1keys:
872 d1value = d1[key]
873 d2value = d2[key]
874 try:
875 error = abs(float(d1value) - float(d2value))
876 within_tolerance = error <= tolerance
877 except (ValueError, TypeError):
daiki kato6914b1a2016-03-16 17:16:57 +0900878 # If both values aren't convertible to float, just ignore
Marc Koderer0abc93b2015-07-15 09:18:35 +0200879 # ValueError if arg is a str, TypeError if it's something else
880 # (like None)
881 within_tolerance = False
882
883 if hasattr(d1value, 'keys') and hasattr(d2value, 'keys'):
884 self.assertDictMatch(d1value, d2value)
885 elif 'DONTCARE' in (d1value, d2value):
886 continue
887 elif approx_equal and within_tolerance:
888 continue
889 elif d1value != d2value:
890 raise_assertion("d1['%(key)s']=%(d1value)s != "
891 "d2['%(key)s']=%(d2value)s" %
892 {
893 "key": key,
894 "d1value": d1value,
895 "d2value": d2value
896 })
897
898
899class BaseSharesAltTest(BaseSharesTest):
900 """Base test case class for all Shares Alt API tests."""
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +0300901 credentials = ('alt', )
Marc Koderer0abc93b2015-07-15 09:18:35 +0200902
903
904class BaseSharesAdminTest(BaseSharesTest):
905 """Base test case class for all Shares Admin API tests."""
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +0300906 credentials = ('admin', )
907
908
909class BaseSharesMixedTest(BaseSharesTest):
910 """Base test case class for all Shares API tests with all user roles."""
911 credentials = ('primary', 'alt', 'admin')
Marc Koderer0abc93b2015-07-15 09:18:35 +0200912
913 @classmethod
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +0300914 def setup_clients(cls):
915 super(BaseSharesMixedTest, cls).setup_clients()
916 cls.admin_shares_client = shares_client.SharesClient(
917 cls.os_admin.auth_provider)
918 cls.admin_shares_v2_client = shares_v2_client.SharesV2Client(
919 cls.os_admin.auth_provider)
920 cls.alt_shares_client = shares_client.SharesClient(
921 cls.os_alt.auth_provider)
922 cls.alt_shares_v2_client = shares_v2_client.SharesV2Client(
923 cls.os_alt.auth_provider)
924
925 if CONF.share.multitenancy_enabled:
926 admin_share_network_id = cls.provide_share_network(
927 cls.admin_shares_v2_client, cls.os_admin.networks_client)
928 cls.admin_shares_client.share_network_id = admin_share_network_id
929 cls.admin_shares_v2_client.share_network_id = (
930 admin_share_network_id)
931
932 alt_share_network_id = cls.provide_share_network(
933 cls.alt_shares_v2_client, cls.os_alt.networks_client)
934 cls.alt_shares_client.share_network_id = alt_share_network_id
935 cls.alt_shares_v2_client.share_network_id = alt_share_network_id