blob: a599196cee9a5f1e2ea99bb9946e656b794a402d [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:
206 if not CONF.service_available.neutron:
207 raise cls.skipException("Neutron support is required")
Valeriy Ponomaryov48a2bd72015-11-05 13:22:44 +0200208 nc = os.networks_client
Marc Koderer0abc93b2015-07-15 09:18:35 +0200209 share_network_id = cls.provide_share_network(client, nc, ic)
210 client.share_network_id = share_network_id
211 resource = {
212 "type": "share_network",
213 "id": client.share_network_id,
214 "client": client,
215 }
216 if cleanup_in_class:
217 cls.class_resources.insert(0, resource)
218 else:
219 cls.method_resources.insert(0, resource)
220 return client
221
222 @classmethod
223 def verify_nonempty(cls, *args):
224 if not all(args):
225 msg = "Missing API credentials in configuration."
226 raise cls.skipException(msg)
227
228 @classmethod
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +0300229 def setup_clients(cls):
230 super(BaseSharesTest, cls).setup_clients()
231 os = getattr(cls, 'os_%s' % cls.credentials[0])
232 os.shares_client = shares_client.SharesClient(os.auth_provider)
233 cls.shares_client = os.shares_client
234 os.shares_v2_client = shares_v2_client.SharesV2Client(
235 os.auth_provider)
236 cls.shares_v2_client = os.shares_v2_client
237 if CONF.share.multitenancy_enabled:
238 if not CONF.service_available.neutron:
239 raise cls.skipException("Neutron support is required")
240 share_network_id = cls.provide_share_network(
241 cls.shares_v2_client, os.networks_client)
242 cls.shares_client.share_network_id = share_network_id
243 cls.shares_v2_client.share_network_id = share_network_id
244
245 @classmethod
Marc Koderer0abc93b2015-07-15 09:18:35 +0200246 def resource_setup(cls):
247 if not (any(p in CONF.share.enable_protocols
248 for p in cls.protocols) and
249 CONF.service_available.manila):
250 skip_msg = "Manila is disabled"
251 raise cls.skipException(skip_msg)
252 super(BaseSharesTest, cls).resource_setup()
Marc Koderer0abc93b2015-07-15 09:18:35 +0200253
254 def setUp(self):
255 super(BaseSharesTest, self).setUp()
Marc Koderer0abc93b2015-07-15 09:18:35 +0200256 self.addCleanup(self.clear_isolated_creds)
Valeriy Ponomaryovdd162cb2016-01-20 19:09:49 +0200257 self.addCleanup(self.clear_resources)
Valeriy Ponomaryov2abf5d72016-06-01 18:30:12 +0300258 verify_test_has_appropriate_tags(self)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200259
260 @classmethod
261 def resource_cleanup(cls):
262 super(BaseSharesTest, cls).resource_cleanup()
263 cls.clear_resources(cls.class_resources)
264 cls.clear_isolated_creds(cls.class_isolated_creds)
265
266 @classmethod
267 @network_synchronized
Valeriy Ponomaryov48a2bd72015-11-05 13:22:44 +0200268 def provide_share_network(cls, shares_client, networks_client,
Marc Koderer0abc93b2015-07-15 09:18:35 +0200269 isolated_creds_client=None):
270 """Used for finding/creating share network for multitenant driver.
271
272 This method creates/gets entity share-network for one tenant. This
273 share-network will be used for creation of service vm.
274
275 :param shares_client: shares client, which requires share-network
Valeriy Ponomaryov48a2bd72015-11-05 13:22:44 +0200276 :param networks_client: network client from same tenant as shares
277 :param isolated_creds_client: DynamicCredentialProvider instance
Marc Koderer0abc93b2015-07-15 09:18:35 +0200278 If provided, then its networking will be used if needed.
279 If not provided, then common network will be used if needed.
280 :returns: str -- share network id for shares_client tenant
281 :returns: None -- if single-tenant driver used
282 """
283
284 sc = shares_client
285
286 if not CONF.share.multitenancy_enabled:
287 # Assumed usage of a single-tenant driver
288 share_network_id = None
289 elif sc.share_network_id:
290 # Share-network already exists, use it
291 share_network_id = sc.share_network_id
292 else:
293 net_id = subnet_id = share_network_id = None
294
295 if not isolated_creds_client:
296 # Search for networks, created in previous runs
297 search_word = "reusable"
298 sn_name = "autogenerated_by_tempest_%s" % search_word
299 service_net_name = "share-service"
Valeriy Ponomaryov48a2bd72015-11-05 13:22:44 +0200300 networks = networks_client.list_networks()
Marc Koderer0abc93b2015-07-15 09:18:35 +0200301 if "networks" in networks.keys():
302 networks = networks["networks"]
303 for network in networks:
304 if (service_net_name in network["name"] and
305 sc.tenant_id == network['tenant_id']):
306 net_id = network["id"]
307 if len(network["subnets"]) > 0:
308 subnet_id = network["subnets"][0]
309 break
310
311 # Create suitable network
312 if (net_id is None or subnet_id is None):
Valeriy Ponomaryov48a2bd72015-11-05 13:22:44 +0200313 ic = dynamic_creds.DynamicCredentialProvider(
314 identity_version=CONF.identity.auth_version,
315 name=service_net_name,
316 admin_role=CONF.identity.admin_role,
Valeriy Ponomaryov0ddd29b2016-06-07 17:49:31 +0300317 admin_creds=(
318 common_creds.get_configured_admin_credentials()))
Marc Koderer0abc93b2015-07-15 09:18:35 +0200319 net_data = ic._create_network_resources(sc.tenant_id)
320 network, subnet, router = net_data
321 net_id = network["id"]
322 subnet_id = subnet["id"]
323
324 # Try get suitable share-network
325 share_networks = sc.list_share_networks_with_detail()
326 for sn in share_networks:
327 if (net_id == sn["neutron_net_id"] and
328 subnet_id == sn["neutron_subnet_id"] and
329 sn["name"] and search_word in sn["name"]):
330 share_network_id = sn["id"]
331 break
332 else:
333 sn_name = "autogenerated_by_tempest_for_isolated_creds"
334 # Use precreated network and subnet from isolated creds
335 net_id = isolated_creds_client.get_credentials(
336 isolated_creds_client.type_of_creds).network['id']
337 subnet_id = isolated_creds_client.get_credentials(
338 isolated_creds_client.type_of_creds).subnet['id']
339
340 # Create suitable share-network
341 if share_network_id is None:
342 sn_desc = "This share-network was created by tempest"
343 sn = sc.create_share_network(name=sn_name,
344 description=sn_desc,
345 neutron_net_id=net_id,
346 neutron_subnet_id=subnet_id)
347 share_network_id = sn["id"]
348
349 return share_network_id
350
351 @classmethod
marcusvrne0d7cfd2016-06-24 12:27:55 -0300352 def _create_share(cls, share_protocol=None, size=None, name=None,
Marc Koderer0abc93b2015-07-15 09:18:35 +0200353 snapshot_id=None, description=None, metadata=None,
354 share_network_id=None, share_type_id=None,
Andrew Kerrbf31e912015-07-29 10:39:38 -0400355 consistency_group_id=None, client=None,
Clinton Knighte5c8f092015-08-27 15:00:23 -0400356 cleanup_in_class=True, is_public=False, **kwargs):
Valeriy Ponomaryov1aaa72d2015-09-08 12:59:41 +0300357 client = client or cls.shares_v2_client
Marc Koderer0abc93b2015-07-15 09:18:35 +0200358 description = description or "Tempest's share"
359 share_network_id = share_network_id or client.share_network_id or None
360 metadata = metadata or {}
marcusvrne0d7cfd2016-06-24 12:27:55 -0300361 size = size or CONF.share.share_size
Clinton Knighte5c8f092015-08-27 15:00:23 -0400362 kwargs.update({
Marc Koderer0abc93b2015-07-15 09:18:35 +0200363 'share_protocol': share_protocol,
364 'size': size,
365 'name': name,
366 'snapshot_id': snapshot_id,
367 'description': description,
368 'metadata': metadata,
369 'share_network_id': share_network_id,
370 'share_type_id': share_type_id,
371 'is_public': is_public,
Clinton Knighte5c8f092015-08-27 15:00:23 -0400372 })
Andrew Kerrbf31e912015-07-29 10:39:38 -0400373 if consistency_group_id:
374 kwargs['consistency_group_id'] = consistency_group_id
375
Marc Koderer0abc93b2015-07-15 09:18:35 +0200376 share = client.create_share(**kwargs)
Andrew Kerrbf31e912015-07-29 10:39:38 -0400377 resource = {"type": "share", "id": share["id"], "client": client,
378 "consistency_group_id": consistency_group_id}
Marc Koderer0abc93b2015-07-15 09:18:35 +0200379 cleanup_list = (cls.class_resources if cleanup_in_class else
380 cls.method_resources)
381 cleanup_list.insert(0, resource)
382 return share
383
384 @classmethod
Rodrigo Barbierie3305122016-02-03 14:32:24 -0200385 def migrate_share(cls, share_id, dest_host, client=None, notify=True,
386 wait_for_status='migration_success', **kwargs):
Clinton Knighte5c8f092015-08-27 15:00:23 -0400387 client = client or cls.shares_v2_client
Rodrigo Barbierie3305122016-02-03 14:32:24 -0200388 client.migrate_share(share_id, dest_host, notify, **kwargs)
389 share = client.wait_for_migration_status(
390 share_id, dest_host, wait_for_status,
391 version=kwargs.get('version'))
392 return share
393
394 @classmethod
395 def migration_complete(cls, share_id, dest_host, client=None, **kwargs):
396 client = client or cls.shares_v2_client
397 client.migration_complete(share_id, **kwargs)
398 share = client.wait_for_migration_status(
399 share_id, dest_host, 'migration_success',
400 version=kwargs.get('version'))
Rodrigo Barbierib7137ad2015-09-06 22:53:16 -0300401 return share
402
403 @classmethod
Marc Koderer0abc93b2015-07-15 09:18:35 +0200404 def create_share(cls, *args, **kwargs):
405 """Create one share and wait for available state. Retry if allowed."""
406 result = cls.create_shares([{"args": args, "kwargs": kwargs}])
407 return result[0]
408
409 @classmethod
410 def create_shares(cls, share_data_list):
411 """Creates several shares in parallel with retries.
412
413 Use this method when you want to create more than one share at same
414 time. Especially if config option 'share.share_creation_retry_number'
415 has value more than zero (0).
416 All shares will be expected to have 'available' status with or without
417 recreation else error will be raised.
418
419 :param share_data_list: list -- list of dictionaries with 'args' and
420 'kwargs' for '_create_share' method of this base class.
421 example of data:
422 share_data_list=[{'args': ['quuz'], 'kwargs': {'foo': 'bar'}}}]
423 :returns: list -- list of shares created using provided data.
424 """
425
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +0300426 for d in share_data_list:
Marc Koderer0abc93b2015-07-15 09:18:35 +0200427 if not isinstance(d, dict):
428 raise exceptions.TempestException(
429 "Expected 'dict', got '%s'" % type(d))
430 if "args" not in d:
431 d["args"] = []
432 if "kwargs" not in d:
433 d["kwargs"] = {}
434 if len(d) > 2:
435 raise exceptions.TempestException(
436 "Expected only 'args' and 'kwargs' keys. "
437 "Provided %s" % list(d))
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +0300438
439 data = []
440 for d in share_data_list:
441 client = d["kwargs"].pop("client", cls.shares_v2_client)
442 local_d = {
443 "args": d["args"],
444 "kwargs": copy.deepcopy(d["kwargs"]),
445 }
446 local_d["kwargs"]["client"] = client
447 local_d["share"] = cls._create_share(
448 *local_d["args"], **local_d["kwargs"])
449 local_d["cnt"] = 0
450 local_d["available"] = False
451 data.append(local_d)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200452
453 while not all(d["available"] for d in data):
454 for d in data:
455 if d["available"]:
456 continue
Valeriy Ponomaryov1a3e3382016-06-08 15:17:16 +0300457 client = d["kwargs"]["client"]
458 share_id = d["share"]["id"]
Marc Koderer0abc93b2015-07-15 09:18:35 +0200459 try:
Valeriy Ponomaryov1a3e3382016-06-08 15:17:16 +0300460 client.wait_for_share_status(share_id, "available")
Marc Koderer0abc93b2015-07-15 09:18:35 +0200461 d["available"] = True
462 except (share_exceptions.ShareBuildErrorException,
463 exceptions.TimeoutException) as e:
464 if CONF.share.share_creation_retry_number > d["cnt"]:
465 d["cnt"] += 1
466 msg = ("Share '%s' failed to be built. "
Valeriy Ponomaryov1a3e3382016-06-08 15:17:16 +0300467 "Trying create another." % share_id)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200468 LOG.error(msg)
469 LOG.error(e)
Valeriy Ponomaryov1a3e3382016-06-08 15:17:16 +0300470 cg_id = d["kwargs"].get("consistency_group_id")
471 if cg_id:
472 # NOTE(vponomaryov): delete errored share
473 # immediately in case share is part of CG.
474 client.delete_share(
475 share_id,
476 params={"consistency_group_id": cg_id})
477 client.wait_for_resource_deletion(
478 share_id=share_id)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200479 d["share"] = cls._create_share(
480 *d["args"], **d["kwargs"])
481 else:
482 raise e
483
484 return [d["share"] for d in data]
485
486 @classmethod
Andrew Kerrbf31e912015-07-29 10:39:38 -0400487 def create_consistency_group(cls, client=None, cleanup_in_class=True,
488 share_network_id=None, **kwargs):
Clinton Knighte5c8f092015-08-27 15:00:23 -0400489 client = client or cls.shares_v2_client
Goutham Pacha Ravi9221f5e2016-04-21 13:17:49 -0400490 if kwargs.get('source_cgsnapshot_id') is None:
491 kwargs['share_network_id'] = (share_network_id or
492 client.share_network_id or None)
Andrew Kerrbf31e912015-07-29 10:39:38 -0400493 consistency_group = client.create_consistency_group(**kwargs)
494 resource = {
495 "type": "consistency_group",
496 "id": consistency_group["id"],
497 "client": client}
498 if cleanup_in_class:
499 cls.class_resources.insert(0, resource)
500 else:
501 cls.method_resources.insert(0, resource)
502
503 if kwargs.get('source_cgsnapshot_id'):
504 new_cg_shares = client.list_shares(
505 detailed=True,
506 params={'consistency_group_id': consistency_group['id']})
507
508 for share in new_cg_shares:
509 resource = {"type": "share",
510 "id": share["id"],
511 "client": client,
512 "consistency_group_id": share.get(
513 'consistency_group_id')}
514 if cleanup_in_class:
515 cls.class_resources.insert(0, resource)
516 else:
517 cls.method_resources.insert(0, resource)
518
519 client.wait_for_consistency_group_status(consistency_group['id'],
520 'available')
521 return consistency_group
522
523 @classmethod
Marc Koderer0abc93b2015-07-15 09:18:35 +0200524 def create_snapshot_wait_for_active(cls, share_id, name=None,
525 description=None, force=False,
526 client=None, cleanup_in_class=True):
527 if client is None:
Yogesh1f931ff2015-09-29 23:41:02 -0400528 client = cls.shares_v2_client
Marc Koderer0abc93b2015-07-15 09:18:35 +0200529 if description is None:
530 description = "Tempest's snapshot"
531 snapshot = client.create_snapshot(share_id, name, description, force)
532 resource = {
533 "type": "snapshot",
534 "id": snapshot["id"],
535 "client": client,
536 }
537 if cleanup_in_class:
538 cls.class_resources.insert(0, resource)
539 else:
540 cls.method_resources.insert(0, resource)
541 client.wait_for_snapshot_status(snapshot["id"], "available")
542 return snapshot
543
544 @classmethod
Andrew Kerrbf31e912015-07-29 10:39:38 -0400545 def create_cgsnapshot_wait_for_active(cls, consistency_group_id,
546 name=None, description=None,
Clinton Knighte5c8f092015-08-27 15:00:23 -0400547 client=None, cleanup_in_class=True,
548 **kwargs):
549 client = client or cls.shares_v2_client
Andrew Kerrbf31e912015-07-29 10:39:38 -0400550 if description is None:
551 description = "Tempest's cgsnapshot"
Clinton Knighte5c8f092015-08-27 15:00:23 -0400552 cgsnapshot = client.create_cgsnapshot(consistency_group_id,
553 name=name,
554 description=description,
555 **kwargs)
Andrew Kerrbf31e912015-07-29 10:39:38 -0400556 resource = {
557 "type": "cgsnapshot",
558 "id": cgsnapshot["id"],
559 "client": client,
560 }
561 if cleanup_in_class:
562 cls.class_resources.insert(0, resource)
563 else:
564 cls.method_resources.insert(0, resource)
565 client.wait_for_cgsnapshot_status(cgsnapshot["id"], "available")
566 return cgsnapshot
567
568 @classmethod
Yogeshbdb88102015-09-29 23:41:02 -0400569 def get_availability_zones(cls, client=None):
570 """List the availability zones for "manila-share" services
571
572 that are currently in "up" state.
573 """
574 client = client or cls.shares_v2_client
575 cls.services = client.list_services()
576 zones = [service['zone'] for service in cls.services if
577 service['binary'] == "manila-share" and
578 service['state'] == 'up']
579 return zones
580
Yogesh1f931ff2015-09-29 23:41:02 -0400581 def get_pools_for_replication_domain(self):
582 # Get the list of pools for the replication domain
583 pools = self.admin_client.list_pools(detail=True)['pools']
584 instance_host = self.shares[0]['host']
585 host_pool = [p for p in pools if p['name'] == instance_host][0]
586 rep_domain = host_pool['capabilities']['replication_domain']
587 pools_in_rep_domain = [p for p in pools if p['capabilities'][
588 'replication_domain'] == rep_domain]
589 return rep_domain, pools_in_rep_domain
590
Yogeshbdb88102015-09-29 23:41:02 -0400591 @classmethod
592 def create_share_replica(cls, share_id, availability_zone, client=None,
593 cleanup_in_class=False, cleanup=True):
594 client = client or cls.shares_v2_client
595 replica = client.create_share_replica(share_id, availability_zone)
596 resource = {
597 "type": "share_replica",
598 "id": replica["id"],
599 "client": client,
600 "share_id": share_id,
601 }
602 # NOTE(Yogi1): Cleanup needs to be disabled during promotion tests.
603 if cleanup:
604 if cleanup_in_class:
605 cls.class_resources.insert(0, resource)
606 else:
607 cls.method_resources.insert(0, resource)
608 client.wait_for_share_replica_status(
609 replica["id"], constants.STATUS_AVAILABLE)
610 return replica
611
612 @classmethod
613 def delete_share_replica(cls, replica_id, client=None):
614 client = client or cls.shares_v2_client
Yogesh1f931ff2015-09-29 23:41:02 -0400615 try:
616 client.delete_share_replica(replica_id)
617 client.wait_for_resource_deletion(replica_id=replica_id)
618 except exceptions.NotFound:
619 pass
Yogeshbdb88102015-09-29 23:41:02 -0400620
621 @classmethod
622 def promote_share_replica(cls, replica_id, client=None):
623 client = client or cls.shares_v2_client
624 replica = client.promote_share_replica(replica_id)
625 client.wait_for_share_replica_status(
626 replica["id"],
627 constants.REPLICATION_STATE_ACTIVE,
628 status_attr="replica_state")
629 return replica
630
631 @classmethod
Marc Koderer0abc93b2015-07-15 09:18:35 +0200632 def create_share_network(cls, client=None,
633 cleanup_in_class=False, **kwargs):
634 if client is None:
635 client = cls.shares_client
636 share_network = client.create_share_network(**kwargs)
637 resource = {
638 "type": "share_network",
639 "id": share_network["id"],
640 "client": client,
641 }
642 if cleanup_in_class:
643 cls.class_resources.insert(0, resource)
644 else:
645 cls.method_resources.insert(0, resource)
646 return share_network
647
648 @classmethod
649 def create_security_service(cls, ss_type="ldap", client=None,
650 cleanup_in_class=False, **kwargs):
651 if client is None:
652 client = cls.shares_client
653 security_service = client.create_security_service(ss_type, **kwargs)
654 resource = {
655 "type": "security_service",
656 "id": security_service["id"],
657 "client": client,
658 }
659 if cleanup_in_class:
660 cls.class_resources.insert(0, resource)
661 else:
662 cls.method_resources.insert(0, resource)
663 return security_service
664
665 @classmethod
666 def create_share_type(cls, name, is_public=True, client=None,
667 cleanup_in_class=True, **kwargs):
668 if client is None:
Valeriy Ponomaryova14c2252015-10-29 13:34:32 +0200669 client = cls.shares_v2_client
Marc Koderer0abc93b2015-07-15 09:18:35 +0200670 share_type = client.create_share_type(name, is_public, **kwargs)
671 resource = {
672 "type": "share_type",
673 "id": share_type["share_type"]["id"],
674 "client": client,
675 }
676 if cleanup_in_class:
677 cls.class_resources.insert(0, resource)
678 else:
679 cls.method_resources.insert(0, resource)
680 return share_type
681
682 @staticmethod
683 def add_required_extra_specs_to_dict(extra_specs=None):
Valeriy Ponomaryovad55dc52015-09-23 13:54:00 +0300684 dhss = six.text_type(CONF.share.multitenancy_enabled)
685 snapshot_support = six.text_type(
686 CONF.share.capability_snapshot_support)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200687 required = {
Valeriy Ponomaryovad55dc52015-09-23 13:54:00 +0300688 "driver_handles_share_servers": dhss,
689 "snapshot_support": snapshot_support,
Marc Koderer0abc93b2015-07-15 09:18:35 +0200690 }
691 if extra_specs:
692 required.update(extra_specs)
693 return required
694
695 @classmethod
696 def clear_isolated_creds(cls, creds=None):
697 if creds is None:
698 creds = cls.method_isolated_creds
699 for ic in creds:
700 if "deleted" not in ic.keys():
701 ic["deleted"] = False
702 if not ic["deleted"]:
703 with handle_cleanup_exceptions():
704 ic["method"]()
705 ic["deleted"] = True
706
707 @classmethod
Yogesh1f931ff2015-09-29 23:41:02 -0400708 def clear_share_replicas(cls, share_id, client=None):
709 client = client or cls.shares_v2_client
710 share_replicas = client.list_share_replicas(
711 share_id=share_id)
712
713 for replica in share_replicas:
714 try:
715 cls.delete_share_replica(replica['id'])
716 except exceptions.BadRequest:
717 # Ignore the exception due to deletion of last active replica
718 pass
719
720 @classmethod
Marc Koderer0abc93b2015-07-15 09:18:35 +0200721 def clear_resources(cls, resources=None):
722 """Deletes resources, that were created in test suites.
723
724 This method tries to remove resources from resource list,
725 if it is not found, assumed it was deleted in test itself.
726 It is expected, that all resources were added as LIFO
727 due to restriction of deletion resources, that is in the chain.
728
729 :param resources: dict with keys 'type','id','client' and 'deleted'
730 """
731
732 if resources is None:
733 resources = cls.method_resources
734 for res in resources:
735 if "deleted" not in res.keys():
736 res["deleted"] = False
737 if "client" not in res.keys():
738 res["client"] = cls.shares_client
739 if not(res["deleted"]):
740 res_id = res['id']
741 client = res["client"]
742 with handle_cleanup_exceptions():
743 if res["type"] is "share":
Yogesh1f931ff2015-09-29 23:41:02 -0400744 cls.clear_share_replicas(res_id)
Andrew Kerrbf31e912015-07-29 10:39:38 -0400745 cg_id = res.get('consistency_group_id')
746 if cg_id:
747 params = {'consistency_group_id': cg_id}
Clinton Knighte5c8f092015-08-27 15:00:23 -0400748 client.delete_share(res_id, params=params)
749 else:
750 client.delete_share(res_id)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200751 client.wait_for_resource_deletion(share_id=res_id)
752 elif res["type"] is "snapshot":
753 client.delete_snapshot(res_id)
754 client.wait_for_resource_deletion(snapshot_id=res_id)
755 elif res["type"] is "share_network":
756 client.delete_share_network(res_id)
757 client.wait_for_resource_deletion(sn_id=res_id)
758 elif res["type"] is "security_service":
759 client.delete_security_service(res_id)
760 client.wait_for_resource_deletion(ss_id=res_id)
761 elif res["type"] is "share_type":
762 client.delete_share_type(res_id)
763 client.wait_for_resource_deletion(st_id=res_id)
Andrew Kerrbf31e912015-07-29 10:39:38 -0400764 elif res["type"] is "consistency_group":
765 client.delete_consistency_group(res_id)
766 client.wait_for_resource_deletion(cg_id=res_id)
767 elif res["type"] is "cgsnapshot":
768 client.delete_cgsnapshot(res_id)
769 client.wait_for_resource_deletion(cgsnapshot_id=res_id)
Yogeshbdb88102015-09-29 23:41:02 -0400770 elif res["type"] is "share_replica":
771 client.delete_share_replica(res_id)
772 client.wait_for_resource_deletion(replica_id=res_id)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200773 else:
huayue97bacbf2016-01-04 09:57:39 +0800774 LOG.warning("Provided unsupported resource type for "
775 "cleanup '%s'. Skipping." % res["type"])
Marc Koderer0abc93b2015-07-15 09:18:35 +0200776 res["deleted"] = True
777
778 @classmethod
779 def generate_share_network_data(self):
780 data = {
781 "name": data_utils.rand_name("sn-name"),
782 "description": data_utils.rand_name("sn-desc"),
783 "neutron_net_id": data_utils.rand_name("net-id"),
784 "neutron_subnet_id": data_utils.rand_name("subnet-id"),
785 }
786 return data
787
788 @classmethod
789 def generate_security_service_data(self):
790 data = {
791 "name": data_utils.rand_name("ss-name"),
792 "description": data_utils.rand_name("ss-desc"),
Valeriy Ponomaryovfcde7712015-12-14 18:06:13 +0200793 "dns_ip": utils.rand_ip(),
794 "server": utils.rand_ip(),
Marc Koderer0abc93b2015-07-15 09:18:35 +0200795 "domain": data_utils.rand_name("ss-domain"),
796 "user": data_utils.rand_name("ss-user"),
797 "password": data_utils.rand_name("ss-password"),
798 }
799 return data
800
801 # Useful assertions
802 def assertDictMatch(self, d1, d2, approx_equal=False, tolerance=0.001):
803 """Assert two dicts are equivalent.
804
805 This is a 'deep' match in the sense that it handles nested
806 dictionaries appropriately.
807
808 NOTE:
809
810 If you don't care (or don't know) a given value, you can specify
811 the string DONTCARE as the value. This will cause that dict-item
812 to be skipped.
813
814 """
815 def raise_assertion(msg):
816 d1str = str(d1)
817 d2str = str(d2)
818 base_msg = ('Dictionaries do not match. %(msg)s d1: %(d1str)s '
819 'd2: %(d2str)s' %
820 {"msg": msg, "d1str": d1str, "d2str": d2str})
821 raise AssertionError(base_msg)
822
823 d1keys = set(d1.keys())
824 d2keys = set(d2.keys())
825 if d1keys != d2keys:
826 d1only = d1keys - d2keys
827 d2only = d2keys - d1keys
828 raise_assertion('Keys in d1 and not d2: %(d1only)s. '
829 'Keys in d2 and not d1: %(d2only)s' %
830 {"d1only": d1only, "d2only": d2only})
831
832 for key in d1keys:
833 d1value = d1[key]
834 d2value = d2[key]
835 try:
836 error = abs(float(d1value) - float(d2value))
837 within_tolerance = error <= tolerance
838 except (ValueError, TypeError):
daiki kato6914b1a2016-03-16 17:16:57 +0900839 # If both values aren't convertible to float, just ignore
Marc Koderer0abc93b2015-07-15 09:18:35 +0200840 # ValueError if arg is a str, TypeError if it's something else
841 # (like None)
842 within_tolerance = False
843
844 if hasattr(d1value, 'keys') and hasattr(d2value, 'keys'):
845 self.assertDictMatch(d1value, d2value)
846 elif 'DONTCARE' in (d1value, d2value):
847 continue
848 elif approx_equal and within_tolerance:
849 continue
850 elif d1value != d2value:
851 raise_assertion("d1['%(key)s']=%(d1value)s != "
852 "d2['%(key)s']=%(d2value)s" %
853 {
854 "key": key,
855 "d1value": d1value,
856 "d2value": d2value
857 })
858
859
860class BaseSharesAltTest(BaseSharesTest):
861 """Base test case class for all Shares Alt API tests."""
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +0300862 credentials = ('alt', )
Marc Koderer0abc93b2015-07-15 09:18:35 +0200863
864
865class BaseSharesAdminTest(BaseSharesTest):
866 """Base test case class for all Shares Admin API tests."""
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +0300867 credentials = ('admin', )
868
869
870class BaseSharesMixedTest(BaseSharesTest):
871 """Base test case class for all Shares API tests with all user roles."""
872 credentials = ('primary', 'alt', 'admin')
Marc Koderer0abc93b2015-07-15 09:18:35 +0200873
874 @classmethod
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +0300875 def setup_clients(cls):
876 super(BaseSharesMixedTest, cls).setup_clients()
877 cls.admin_shares_client = shares_client.SharesClient(
878 cls.os_admin.auth_provider)
879 cls.admin_shares_v2_client = shares_v2_client.SharesV2Client(
880 cls.os_admin.auth_provider)
881 cls.alt_shares_client = shares_client.SharesClient(
882 cls.os_alt.auth_provider)
883 cls.alt_shares_v2_client = shares_v2_client.SharesV2Client(
884 cls.os_alt.auth_provider)
885
886 if CONF.share.multitenancy_enabled:
887 admin_share_network_id = cls.provide_share_network(
888 cls.admin_shares_v2_client, cls.os_admin.networks_client)
889 cls.admin_shares_client.share_network_id = admin_share_network_id
890 cls.admin_shares_v2_client.share_network_id = (
891 admin_share_network_id)
892
893 alt_share_network_id = cls.provide_share_network(
894 cls.alt_shares_v2_client, cls.os_alt.networks_client)
895 cls.alt_shares_client.share_network_id = alt_share_network_id
896 cls.alt_shares_v2_client.share_network_id = alt_share_network_id