blob: 6d0eb258ef19c242e0839293a0e6f49811ce34b2 [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,
Marc Koderer0abc93b2015-07-15 09:18:35 +0200271 isolated_creds_client=None):
272 """Used for finding/creating share network for multitenant driver.
273
274 This method creates/gets entity share-network for one tenant. This
275 share-network will be used for creation of service vm.
276
277 :param shares_client: shares client, which requires share-network
Valeriy Ponomaryov48a2bd72015-11-05 13:22:44 +0200278 :param networks_client: network client from same tenant as shares
279 :param isolated_creds_client: DynamicCredentialProvider instance
Marc Koderer0abc93b2015-07-15 09:18:35 +0200280 If provided, then its networking will be used if needed.
281 If not provided, then common network will be used if needed.
282 :returns: str -- share network id for shares_client tenant
283 :returns: None -- if single-tenant driver used
284 """
285
286 sc = shares_client
Valeriy Ponomaryovc5dae272016-06-10 18:29:24 +0300287 search_word = "reusable"
288 sn_name = "autogenerated_by_tempest_%s" % search_word
Marc Koderer0abc93b2015-07-15 09:18:35 +0200289
290 if not CONF.share.multitenancy_enabled:
291 # Assumed usage of a single-tenant driver
292 share_network_id = None
293 elif sc.share_network_id:
294 # Share-network already exists, use it
295 share_network_id = sc.share_network_id
Valeriy Ponomaryovc5dae272016-06-10 18:29:24 +0300296 elif not CONF.share.create_networks_when_multitenancy_enabled:
297 share_network_id = None
298
299 # Try get suitable share-network
300 share_networks = sc.list_share_networks_with_detail()
301 for sn in share_networks:
302 if (sn["neutron_net_id"] is None and
303 sn["neutron_subnet_id"] is None and
304 sn["name"] and search_word in sn["name"]):
305 share_network_id = sn["id"]
306 break
307
308 # Create new share-network if one was not found
309 if share_network_id is None:
310 sn_desc = "This share-network was created by tempest"
311 sn = sc.create_share_network(name=sn_name, description=sn_desc)
312 share_network_id = sn["id"]
Marc Koderer0abc93b2015-07-15 09:18:35 +0200313 else:
314 net_id = subnet_id = share_network_id = None
315
316 if not isolated_creds_client:
317 # Search for networks, created in previous runs
Marc Koderer0abc93b2015-07-15 09:18:35 +0200318 service_net_name = "share-service"
Valeriy Ponomaryov48a2bd72015-11-05 13:22:44 +0200319 networks = networks_client.list_networks()
Marc Koderer0abc93b2015-07-15 09:18:35 +0200320 if "networks" in networks.keys():
321 networks = networks["networks"]
322 for network in networks:
323 if (service_net_name in network["name"] and
324 sc.tenant_id == network['tenant_id']):
325 net_id = network["id"]
326 if len(network["subnets"]) > 0:
327 subnet_id = network["subnets"][0]
328 break
329
330 # Create suitable network
331 if (net_id is None or subnet_id is None):
Valeriy Ponomaryov48a2bd72015-11-05 13:22:44 +0200332 ic = dynamic_creds.DynamicCredentialProvider(
333 identity_version=CONF.identity.auth_version,
334 name=service_net_name,
335 admin_role=CONF.identity.admin_role,
Valeriy Ponomaryov0ddd29b2016-06-07 17:49:31 +0300336 admin_creds=(
337 common_creds.get_configured_admin_credentials()))
Marc Koderer0abc93b2015-07-15 09:18:35 +0200338 net_data = ic._create_network_resources(sc.tenant_id)
339 network, subnet, router = net_data
340 net_id = network["id"]
341 subnet_id = subnet["id"]
342
343 # Try get suitable share-network
344 share_networks = sc.list_share_networks_with_detail()
345 for sn in share_networks:
346 if (net_id == sn["neutron_net_id"] and
347 subnet_id == sn["neutron_subnet_id"] and
348 sn["name"] and search_word in sn["name"]):
349 share_network_id = sn["id"]
350 break
351 else:
352 sn_name = "autogenerated_by_tempest_for_isolated_creds"
353 # Use precreated network and subnet from isolated creds
354 net_id = isolated_creds_client.get_credentials(
355 isolated_creds_client.type_of_creds).network['id']
356 subnet_id = isolated_creds_client.get_credentials(
357 isolated_creds_client.type_of_creds).subnet['id']
358
359 # Create suitable share-network
360 if share_network_id is None:
361 sn_desc = "This share-network was created by tempest"
362 sn = sc.create_share_network(name=sn_name,
363 description=sn_desc,
364 neutron_net_id=net_id,
365 neutron_subnet_id=subnet_id)
366 share_network_id = sn["id"]
367
368 return share_network_id
369
370 @classmethod
marcusvrne0d7cfd2016-06-24 12:27:55 -0300371 def _create_share(cls, share_protocol=None, size=None, name=None,
Marc Koderer0abc93b2015-07-15 09:18:35 +0200372 snapshot_id=None, description=None, metadata=None,
373 share_network_id=None, share_type_id=None,
Andrew Kerrbf31e912015-07-29 10:39:38 -0400374 consistency_group_id=None, client=None,
Clinton Knighte5c8f092015-08-27 15:00:23 -0400375 cleanup_in_class=True, is_public=False, **kwargs):
Valeriy Ponomaryov1aaa72d2015-09-08 12:59:41 +0300376 client = client or cls.shares_v2_client
Marc Koderer0abc93b2015-07-15 09:18:35 +0200377 description = description or "Tempest's share"
378 share_network_id = share_network_id or client.share_network_id or None
379 metadata = metadata or {}
marcusvrne0d7cfd2016-06-24 12:27:55 -0300380 size = size or CONF.share.share_size
Clinton Knighte5c8f092015-08-27 15:00:23 -0400381 kwargs.update({
Marc Koderer0abc93b2015-07-15 09:18:35 +0200382 'share_protocol': share_protocol,
383 'size': size,
384 'name': name,
385 'snapshot_id': snapshot_id,
386 'description': description,
387 'metadata': metadata,
388 'share_network_id': share_network_id,
389 'share_type_id': share_type_id,
390 'is_public': is_public,
Clinton Knighte5c8f092015-08-27 15:00:23 -0400391 })
Andrew Kerrbf31e912015-07-29 10:39:38 -0400392 if consistency_group_id:
393 kwargs['consistency_group_id'] = consistency_group_id
394
Marc Koderer0abc93b2015-07-15 09:18:35 +0200395 share = client.create_share(**kwargs)
Andrew Kerrbf31e912015-07-29 10:39:38 -0400396 resource = {"type": "share", "id": share["id"], "client": client,
397 "consistency_group_id": consistency_group_id}
Marc Koderer0abc93b2015-07-15 09:18:35 +0200398 cleanup_list = (cls.class_resources if cleanup_in_class else
399 cls.method_resources)
400 cleanup_list.insert(0, resource)
401 return share
402
403 @classmethod
Rodrigo Barbieri427bc052016-06-06 17:10:06 -0300404 def migrate_share(
405 cls, share_id, dest_host, wait_for_status, client=None,
406 force_host_assisted_migration=False, new_share_network_id=None,
407 **kwargs):
Clinton Knighte5c8f092015-08-27 15:00:23 -0400408 client = client or cls.shares_v2_client
Rodrigo Barbieri427bc052016-06-06 17:10:06 -0300409 client.migrate_share(
410 share_id, dest_host,
411 force_host_assisted_migration=force_host_assisted_migration,
412 new_share_network_id=new_share_network_id,
413 writable=False, preserve_metadata=False, nondisruptive=False,
414 **kwargs)
Rodrigo Barbierie3305122016-02-03 14:32:24 -0200415 share = client.wait_for_migration_status(
Rodrigo Barbieri427bc052016-06-06 17:10:06 -0300416 share_id, dest_host, wait_for_status, **kwargs)
Rodrigo Barbierie3305122016-02-03 14:32:24 -0200417 return share
418
419 @classmethod
420 def migration_complete(cls, share_id, dest_host, client=None, **kwargs):
421 client = client or cls.shares_v2_client
422 client.migration_complete(share_id, **kwargs)
423 share = client.wait_for_migration_status(
Rodrigo Barbieri427bc052016-06-06 17:10:06 -0300424 share_id, dest_host, 'migration_success', **kwargs)
Rodrigo Barbierib7137ad2015-09-06 22:53:16 -0300425 return share
426
427 @classmethod
Rodrigo Barbieric9abf282016-08-24 22:01:31 -0300428 def migration_cancel(cls, share_id, dest_host, client=None, **kwargs):
429 client = client or cls.shares_v2_client
430 client.migration_cancel(share_id, **kwargs)
431 share = client.wait_for_migration_status(
432 share_id, dest_host, 'migration_cancelled', **kwargs)
433 return share
434
435 @classmethod
Marc Koderer0abc93b2015-07-15 09:18:35 +0200436 def create_share(cls, *args, **kwargs):
437 """Create one share and wait for available state. Retry if allowed."""
438 result = cls.create_shares([{"args": args, "kwargs": kwargs}])
439 return result[0]
440
441 @classmethod
442 def create_shares(cls, share_data_list):
443 """Creates several shares in parallel with retries.
444
445 Use this method when you want to create more than one share at same
446 time. Especially if config option 'share.share_creation_retry_number'
447 has value more than zero (0).
448 All shares will be expected to have 'available' status with or without
449 recreation else error will be raised.
450
451 :param share_data_list: list -- list of dictionaries with 'args' and
452 'kwargs' for '_create_share' method of this base class.
453 example of data:
454 share_data_list=[{'args': ['quuz'], 'kwargs': {'foo': 'bar'}}}]
455 :returns: list -- list of shares created using provided data.
456 """
457
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +0300458 for d in share_data_list:
Marc Koderer0abc93b2015-07-15 09:18:35 +0200459 if not isinstance(d, dict):
460 raise exceptions.TempestException(
461 "Expected 'dict', got '%s'" % type(d))
462 if "args" not in d:
463 d["args"] = []
464 if "kwargs" not in d:
465 d["kwargs"] = {}
466 if len(d) > 2:
467 raise exceptions.TempestException(
468 "Expected only 'args' and 'kwargs' keys. "
469 "Provided %s" % list(d))
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +0300470
471 data = []
472 for d in share_data_list:
473 client = d["kwargs"].pop("client", cls.shares_v2_client)
474 local_d = {
475 "args": d["args"],
476 "kwargs": copy.deepcopy(d["kwargs"]),
477 }
478 local_d["kwargs"]["client"] = client
479 local_d["share"] = cls._create_share(
480 *local_d["args"], **local_d["kwargs"])
481 local_d["cnt"] = 0
482 local_d["available"] = False
483 data.append(local_d)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200484
485 while not all(d["available"] for d in data):
486 for d in data:
487 if d["available"]:
488 continue
Valeriy Ponomaryov1a3e3382016-06-08 15:17:16 +0300489 client = d["kwargs"]["client"]
490 share_id = d["share"]["id"]
Marc Koderer0abc93b2015-07-15 09:18:35 +0200491 try:
Valeriy Ponomaryov1a3e3382016-06-08 15:17:16 +0300492 client.wait_for_share_status(share_id, "available")
Marc Koderer0abc93b2015-07-15 09:18:35 +0200493 d["available"] = True
494 except (share_exceptions.ShareBuildErrorException,
495 exceptions.TimeoutException) as e:
496 if CONF.share.share_creation_retry_number > d["cnt"]:
497 d["cnt"] += 1
498 msg = ("Share '%s' failed to be built. "
Valeriy Ponomaryov1a3e3382016-06-08 15:17:16 +0300499 "Trying create another." % share_id)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200500 LOG.error(msg)
501 LOG.error(e)
Valeriy Ponomaryov1a3e3382016-06-08 15:17:16 +0300502 cg_id = d["kwargs"].get("consistency_group_id")
503 if cg_id:
504 # NOTE(vponomaryov): delete errored share
505 # immediately in case share is part of CG.
506 client.delete_share(
507 share_id,
508 params={"consistency_group_id": cg_id})
509 client.wait_for_resource_deletion(
510 share_id=share_id)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200511 d["share"] = cls._create_share(
512 *d["args"], **d["kwargs"])
513 else:
514 raise e
515
516 return [d["share"] for d in data]
517
518 @classmethod
Andrew Kerrbf31e912015-07-29 10:39:38 -0400519 def create_consistency_group(cls, client=None, cleanup_in_class=True,
520 share_network_id=None, **kwargs):
Clinton Knighte5c8f092015-08-27 15:00:23 -0400521 client = client or cls.shares_v2_client
Goutham Pacha Ravi9221f5e2016-04-21 13:17:49 -0400522 if kwargs.get('source_cgsnapshot_id') is None:
523 kwargs['share_network_id'] = (share_network_id or
524 client.share_network_id or None)
Andrew Kerrbf31e912015-07-29 10:39:38 -0400525 consistency_group = client.create_consistency_group(**kwargs)
526 resource = {
527 "type": "consistency_group",
528 "id": consistency_group["id"],
529 "client": client}
530 if cleanup_in_class:
531 cls.class_resources.insert(0, resource)
532 else:
533 cls.method_resources.insert(0, resource)
534
535 if kwargs.get('source_cgsnapshot_id'):
536 new_cg_shares = client.list_shares(
537 detailed=True,
538 params={'consistency_group_id': consistency_group['id']})
539
540 for share in new_cg_shares:
541 resource = {"type": "share",
542 "id": share["id"],
543 "client": client,
544 "consistency_group_id": share.get(
545 'consistency_group_id')}
546 if cleanup_in_class:
547 cls.class_resources.insert(0, resource)
548 else:
549 cls.method_resources.insert(0, resource)
550
551 client.wait_for_consistency_group_status(consistency_group['id'],
552 'available')
553 return consistency_group
554
555 @classmethod
Marc Koderer0abc93b2015-07-15 09:18:35 +0200556 def create_snapshot_wait_for_active(cls, share_id, name=None,
557 description=None, force=False,
558 client=None, cleanup_in_class=True):
559 if client is None:
Yogesh1f931ff2015-09-29 23:41:02 -0400560 client = cls.shares_v2_client
Marc Koderer0abc93b2015-07-15 09:18:35 +0200561 if description is None:
562 description = "Tempest's snapshot"
563 snapshot = client.create_snapshot(share_id, name, description, force)
564 resource = {
565 "type": "snapshot",
566 "id": snapshot["id"],
567 "client": client,
568 }
569 if cleanup_in_class:
570 cls.class_resources.insert(0, resource)
571 else:
572 cls.method_resources.insert(0, resource)
573 client.wait_for_snapshot_status(snapshot["id"], "available")
574 return snapshot
575
576 @classmethod
Andrew Kerrbf31e912015-07-29 10:39:38 -0400577 def create_cgsnapshot_wait_for_active(cls, consistency_group_id,
578 name=None, description=None,
Clinton Knighte5c8f092015-08-27 15:00:23 -0400579 client=None, cleanup_in_class=True,
580 **kwargs):
581 client = client or cls.shares_v2_client
Andrew Kerrbf31e912015-07-29 10:39:38 -0400582 if description is None:
583 description = "Tempest's cgsnapshot"
Clinton Knighte5c8f092015-08-27 15:00:23 -0400584 cgsnapshot = client.create_cgsnapshot(consistency_group_id,
585 name=name,
586 description=description,
587 **kwargs)
Andrew Kerrbf31e912015-07-29 10:39:38 -0400588 resource = {
589 "type": "cgsnapshot",
590 "id": cgsnapshot["id"],
591 "client": client,
592 }
593 if cleanup_in_class:
594 cls.class_resources.insert(0, resource)
595 else:
596 cls.method_resources.insert(0, resource)
597 client.wait_for_cgsnapshot_status(cgsnapshot["id"], "available")
598 return cgsnapshot
599
600 @classmethod
Yogeshbdb88102015-09-29 23:41:02 -0400601 def get_availability_zones(cls, client=None):
602 """List the availability zones for "manila-share" services
603
604 that are currently in "up" state.
605 """
606 client = client or cls.shares_v2_client
607 cls.services = client.list_services()
608 zones = [service['zone'] for service in cls.services if
609 service['binary'] == "manila-share" and
610 service['state'] == 'up']
611 return zones
612
Yogesh1f931ff2015-09-29 23:41:02 -0400613 def get_pools_for_replication_domain(self):
614 # Get the list of pools for the replication domain
615 pools = self.admin_client.list_pools(detail=True)['pools']
616 instance_host = self.shares[0]['host']
617 host_pool = [p for p in pools if p['name'] == instance_host][0]
618 rep_domain = host_pool['capabilities']['replication_domain']
619 pools_in_rep_domain = [p for p in pools if p['capabilities'][
620 'replication_domain'] == rep_domain]
621 return rep_domain, pools_in_rep_domain
622
Yogeshbdb88102015-09-29 23:41:02 -0400623 @classmethod
624 def create_share_replica(cls, share_id, availability_zone, client=None,
625 cleanup_in_class=False, cleanup=True):
626 client = client or cls.shares_v2_client
627 replica = client.create_share_replica(share_id, availability_zone)
628 resource = {
629 "type": "share_replica",
630 "id": replica["id"],
631 "client": client,
632 "share_id": share_id,
633 }
634 # NOTE(Yogi1): Cleanup needs to be disabled during promotion tests.
635 if cleanup:
636 if cleanup_in_class:
637 cls.class_resources.insert(0, resource)
638 else:
639 cls.method_resources.insert(0, resource)
640 client.wait_for_share_replica_status(
641 replica["id"], constants.STATUS_AVAILABLE)
642 return replica
643
644 @classmethod
645 def delete_share_replica(cls, replica_id, client=None):
646 client = client or cls.shares_v2_client
Yogesh1f931ff2015-09-29 23:41:02 -0400647 try:
648 client.delete_share_replica(replica_id)
649 client.wait_for_resource_deletion(replica_id=replica_id)
650 except exceptions.NotFound:
651 pass
Yogeshbdb88102015-09-29 23:41:02 -0400652
653 @classmethod
654 def promote_share_replica(cls, replica_id, client=None):
655 client = client or cls.shares_v2_client
656 replica = client.promote_share_replica(replica_id)
657 client.wait_for_share_replica_status(
658 replica["id"],
659 constants.REPLICATION_STATE_ACTIVE,
660 status_attr="replica_state")
661 return replica
662
663 @classmethod
Marc Koderer0abc93b2015-07-15 09:18:35 +0200664 def create_share_network(cls, client=None,
665 cleanup_in_class=False, **kwargs):
666 if client is None:
667 client = cls.shares_client
668 share_network = client.create_share_network(**kwargs)
669 resource = {
670 "type": "share_network",
671 "id": share_network["id"],
672 "client": client,
673 }
674 if cleanup_in_class:
675 cls.class_resources.insert(0, resource)
676 else:
677 cls.method_resources.insert(0, resource)
678 return share_network
679
680 @classmethod
681 def create_security_service(cls, ss_type="ldap", client=None,
682 cleanup_in_class=False, **kwargs):
683 if client is None:
684 client = cls.shares_client
685 security_service = client.create_security_service(ss_type, **kwargs)
686 resource = {
687 "type": "security_service",
688 "id": security_service["id"],
689 "client": client,
690 }
691 if cleanup_in_class:
692 cls.class_resources.insert(0, resource)
693 else:
694 cls.method_resources.insert(0, resource)
695 return security_service
696
697 @classmethod
698 def create_share_type(cls, name, is_public=True, client=None,
699 cleanup_in_class=True, **kwargs):
700 if client is None:
Valeriy Ponomaryova14c2252015-10-29 13:34:32 +0200701 client = cls.shares_v2_client
Marc Koderer0abc93b2015-07-15 09:18:35 +0200702 share_type = client.create_share_type(name, is_public, **kwargs)
703 resource = {
704 "type": "share_type",
705 "id": share_type["share_type"]["id"],
706 "client": client,
707 }
708 if cleanup_in_class:
709 cls.class_resources.insert(0, resource)
710 else:
711 cls.method_resources.insert(0, resource)
712 return share_type
713
714 @staticmethod
715 def add_required_extra_specs_to_dict(extra_specs=None):
Valeriy Ponomaryovad55dc52015-09-23 13:54:00 +0300716 dhss = six.text_type(CONF.share.multitenancy_enabled)
717 snapshot_support = six.text_type(
718 CONF.share.capability_snapshot_support)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200719 required = {
Valeriy Ponomaryovad55dc52015-09-23 13:54:00 +0300720 "driver_handles_share_servers": dhss,
721 "snapshot_support": snapshot_support,
Marc Koderer0abc93b2015-07-15 09:18:35 +0200722 }
723 if extra_specs:
724 required.update(extra_specs)
725 return required
726
727 @classmethod
728 def clear_isolated_creds(cls, creds=None):
729 if creds is None:
730 creds = cls.method_isolated_creds
731 for ic in creds:
732 if "deleted" not in ic.keys():
733 ic["deleted"] = False
734 if not ic["deleted"]:
735 with handle_cleanup_exceptions():
736 ic["method"]()
737 ic["deleted"] = True
738
739 @classmethod
Yogesh1f931ff2015-09-29 23:41:02 -0400740 def clear_share_replicas(cls, share_id, client=None):
741 client = client or cls.shares_v2_client
742 share_replicas = client.list_share_replicas(
743 share_id=share_id)
744
745 for replica in share_replicas:
746 try:
747 cls.delete_share_replica(replica['id'])
748 except exceptions.BadRequest:
749 # Ignore the exception due to deletion of last active replica
750 pass
751
752 @classmethod
Marc Koderer0abc93b2015-07-15 09:18:35 +0200753 def clear_resources(cls, resources=None):
754 """Deletes resources, that were created in test suites.
755
756 This method tries to remove resources from resource list,
757 if it is not found, assumed it was deleted in test itself.
758 It is expected, that all resources were added as LIFO
759 due to restriction of deletion resources, that is in the chain.
760
761 :param resources: dict with keys 'type','id','client' and 'deleted'
762 """
763
764 if resources is None:
765 resources = cls.method_resources
766 for res in resources:
767 if "deleted" not in res.keys():
768 res["deleted"] = False
769 if "client" not in res.keys():
770 res["client"] = cls.shares_client
771 if not(res["deleted"]):
772 res_id = res['id']
773 client = res["client"]
774 with handle_cleanup_exceptions():
775 if res["type"] is "share":
Yogesh1f931ff2015-09-29 23:41:02 -0400776 cls.clear_share_replicas(res_id)
Andrew Kerrbf31e912015-07-29 10:39:38 -0400777 cg_id = res.get('consistency_group_id')
778 if cg_id:
779 params = {'consistency_group_id': cg_id}
Clinton Knighte5c8f092015-08-27 15:00:23 -0400780 client.delete_share(res_id, params=params)
781 else:
782 client.delete_share(res_id)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200783 client.wait_for_resource_deletion(share_id=res_id)
784 elif res["type"] is "snapshot":
785 client.delete_snapshot(res_id)
786 client.wait_for_resource_deletion(snapshot_id=res_id)
787 elif res["type"] is "share_network":
788 client.delete_share_network(res_id)
789 client.wait_for_resource_deletion(sn_id=res_id)
790 elif res["type"] is "security_service":
791 client.delete_security_service(res_id)
792 client.wait_for_resource_deletion(ss_id=res_id)
793 elif res["type"] is "share_type":
794 client.delete_share_type(res_id)
795 client.wait_for_resource_deletion(st_id=res_id)
Andrew Kerrbf31e912015-07-29 10:39:38 -0400796 elif res["type"] is "consistency_group":
797 client.delete_consistency_group(res_id)
798 client.wait_for_resource_deletion(cg_id=res_id)
799 elif res["type"] is "cgsnapshot":
800 client.delete_cgsnapshot(res_id)
801 client.wait_for_resource_deletion(cgsnapshot_id=res_id)
Yogeshbdb88102015-09-29 23:41:02 -0400802 elif res["type"] is "share_replica":
803 client.delete_share_replica(res_id)
804 client.wait_for_resource_deletion(replica_id=res_id)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200805 else:
huayue97bacbf2016-01-04 09:57:39 +0800806 LOG.warning("Provided unsupported resource type for "
807 "cleanup '%s'. Skipping." % res["type"])
Marc Koderer0abc93b2015-07-15 09:18:35 +0200808 res["deleted"] = True
809
810 @classmethod
811 def generate_share_network_data(self):
812 data = {
813 "name": data_utils.rand_name("sn-name"),
814 "description": data_utils.rand_name("sn-desc"),
815 "neutron_net_id": data_utils.rand_name("net-id"),
816 "neutron_subnet_id": data_utils.rand_name("subnet-id"),
817 }
818 return data
819
820 @classmethod
821 def generate_security_service_data(self):
822 data = {
823 "name": data_utils.rand_name("ss-name"),
824 "description": data_utils.rand_name("ss-desc"),
Valeriy Ponomaryovfcde7712015-12-14 18:06:13 +0200825 "dns_ip": utils.rand_ip(),
826 "server": utils.rand_ip(),
Marc Koderer0abc93b2015-07-15 09:18:35 +0200827 "domain": data_utils.rand_name("ss-domain"),
828 "user": data_utils.rand_name("ss-user"),
829 "password": data_utils.rand_name("ss-password"),
830 }
831 return data
832
833 # Useful assertions
834 def assertDictMatch(self, d1, d2, approx_equal=False, tolerance=0.001):
835 """Assert two dicts are equivalent.
836
837 This is a 'deep' match in the sense that it handles nested
838 dictionaries appropriately.
839
840 NOTE:
841
842 If you don't care (or don't know) a given value, you can specify
843 the string DONTCARE as the value. This will cause that dict-item
844 to be skipped.
845
846 """
847 def raise_assertion(msg):
848 d1str = str(d1)
849 d2str = str(d2)
850 base_msg = ('Dictionaries do not match. %(msg)s d1: %(d1str)s '
851 'd2: %(d2str)s' %
852 {"msg": msg, "d1str": d1str, "d2str": d2str})
853 raise AssertionError(base_msg)
854
855 d1keys = set(d1.keys())
856 d2keys = set(d2.keys())
857 if d1keys != d2keys:
858 d1only = d1keys - d2keys
859 d2only = d2keys - d1keys
860 raise_assertion('Keys in d1 and not d2: %(d1only)s. '
861 'Keys in d2 and not d1: %(d2only)s' %
862 {"d1only": d1only, "d2only": d2only})
863
864 for key in d1keys:
865 d1value = d1[key]
866 d2value = d2[key]
867 try:
868 error = abs(float(d1value) - float(d2value))
869 within_tolerance = error <= tolerance
870 except (ValueError, TypeError):
daiki kato6914b1a2016-03-16 17:16:57 +0900871 # If both values aren't convertible to float, just ignore
Marc Koderer0abc93b2015-07-15 09:18:35 +0200872 # ValueError if arg is a str, TypeError if it's something else
873 # (like None)
874 within_tolerance = False
875
876 if hasattr(d1value, 'keys') and hasattr(d2value, 'keys'):
877 self.assertDictMatch(d1value, d2value)
878 elif 'DONTCARE' in (d1value, d2value):
879 continue
880 elif approx_equal and within_tolerance:
881 continue
882 elif d1value != d2value:
883 raise_assertion("d1['%(key)s']=%(d1value)s != "
884 "d2['%(key)s']=%(d2value)s" %
885 {
886 "key": key,
887 "d1value": d1value,
888 "d2value": d2value
889 })
890
891
892class BaseSharesAltTest(BaseSharesTest):
893 """Base test case class for all Shares Alt API tests."""
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +0300894 credentials = ('alt', )
Marc Koderer0abc93b2015-07-15 09:18:35 +0200895
896
897class BaseSharesAdminTest(BaseSharesTest):
898 """Base test case class for all Shares Admin API tests."""
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +0300899 credentials = ('admin', )
900
901
902class BaseSharesMixedTest(BaseSharesTest):
903 """Base test case class for all Shares API tests with all user roles."""
904 credentials = ('primary', 'alt', 'admin')
Marc Koderer0abc93b2015-07-15 09:18:35 +0200905
906 @classmethod
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +0300907 def setup_clients(cls):
908 super(BaseSharesMixedTest, cls).setup_clients()
909 cls.admin_shares_client = shares_client.SharesClient(
910 cls.os_admin.auth_provider)
911 cls.admin_shares_v2_client = shares_v2_client.SharesV2Client(
912 cls.os_admin.auth_provider)
913 cls.alt_shares_client = shares_client.SharesClient(
914 cls.os_alt.auth_provider)
915 cls.alt_shares_v2_client = shares_v2_client.SharesV2Client(
916 cls.os_alt.auth_provider)
917
918 if CONF.share.multitenancy_enabled:
919 admin_share_network_id = cls.provide_share_network(
920 cls.admin_shares_v2_client, cls.os_admin.networks_client)
921 cls.admin_shares_client.share_network_id = admin_share_network_id
922 cls.admin_shares_v2_client.share_network_id = (
923 admin_share_network_id)
924
925 alt_share_network_id = cls.provide_share_network(
926 cls.alt_shares_v2_client, cls.os_alt.networks_client)
927 cls.alt_shares_client.share_network_id = alt_share_network_id
928 cls.alt_shares_v2_client.share_network_id = alt_share_network_id