blob: ef409f49276e6fa2bf4f594d74e430f50d0dde35 [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,
178 admin_creds=common_creds.get_configured_credentials(
179 'identity_admin'))
Marc Koderer0abc93b2015-07-15 09:18:35 +0200180 if "admin" in type_of_creds:
181 creds = ic.get_admin_creds()
182 elif "alt" in type_of_creds:
183 creds = ic.get_alt_creds()
184 else:
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +0300185 creds = ic.get_credentials(type_of_creds)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200186 ic.type_of_creds = type_of_creds
187
188 # create client with isolated creds
189 os = clients.Manager(credentials=creds)
Clinton Knighte5c8f092015-08-27 15:00:23 -0400190 if client_version == '1':
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +0300191 client = shares_client.SharesClient(os.auth_provider)
Clinton Knighte5c8f092015-08-27 15:00:23 -0400192 elif client_version == '2':
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +0300193 client = shares_v2_client.SharesV2Client(os.auth_provider)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200194
195 # Set place where will be deleted isolated creds
196 ic_res = {
Valeriy Ponomaryov48a2bd72015-11-05 13:22:44 +0200197 "method": ic.clear_creds,
Marc Koderer0abc93b2015-07-15 09:18:35 +0200198 "deleted": False,
199 }
200 if cleanup_in_class:
201 cls.class_isolated_creds.insert(0, ic_res)
202 else:
203 cls.method_isolated_creds.insert(0, ic_res)
204
205 # Provide share network
206 if CONF.share.multitenancy_enabled:
207 if not CONF.service_available.neutron:
208 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:
239 if not CONF.service_available.neutron:
240 raise cls.skipException("Neutron support is required")
241 share_network_id = cls.provide_share_network(
242 cls.shares_v2_client, os.networks_client)
243 cls.shares_client.share_network_id = share_network_id
244 cls.shares_v2_client.share_network_id = share_network_id
245
246 @classmethod
Marc Koderer0abc93b2015-07-15 09:18:35 +0200247 def resource_setup(cls):
248 if not (any(p in CONF.share.enable_protocols
249 for p in cls.protocols) and
250 CONF.service_available.manila):
251 skip_msg = "Manila is disabled"
252 raise cls.skipException(skip_msg)
253 super(BaseSharesTest, cls).resource_setup()
Marc Koderer0abc93b2015-07-15 09:18:35 +0200254
255 def setUp(self):
256 super(BaseSharesTest, self).setUp()
Marc Koderer0abc93b2015-07-15 09:18:35 +0200257 self.addCleanup(self.clear_isolated_creds)
Valeriy Ponomaryovdd162cb2016-01-20 19:09:49 +0200258 self.addCleanup(self.clear_resources)
Valeriy Ponomaryov2abf5d72016-06-01 18:30:12 +0300259 verify_test_has_appropriate_tags(self)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200260
261 @classmethod
262 def resource_cleanup(cls):
263 super(BaseSharesTest, cls).resource_cleanup()
264 cls.clear_resources(cls.class_resources)
265 cls.clear_isolated_creds(cls.class_isolated_creds)
266
267 @classmethod
268 @network_synchronized
Valeriy Ponomaryov48a2bd72015-11-05 13:22:44 +0200269 def provide_share_network(cls, shares_client, networks_client,
Marc Koderer0abc93b2015-07-15 09:18:35 +0200270 isolated_creds_client=None):
271 """Used for finding/creating share network for multitenant driver.
272
273 This method creates/gets entity share-network for one tenant. This
274 share-network will be used for creation of service vm.
275
276 :param shares_client: shares client, which requires share-network
Valeriy Ponomaryov48a2bd72015-11-05 13:22:44 +0200277 :param networks_client: network client from same tenant as shares
278 :param isolated_creds_client: DynamicCredentialProvider instance
Marc Koderer0abc93b2015-07-15 09:18:35 +0200279 If provided, then its networking will be used if needed.
280 If not provided, then common network will be used if needed.
281 :returns: str -- share network id for shares_client tenant
282 :returns: None -- if single-tenant driver used
283 """
284
285 sc = shares_client
286
287 if not CONF.share.multitenancy_enabled:
288 # Assumed usage of a single-tenant driver
289 share_network_id = None
290 elif sc.share_network_id:
291 # Share-network already exists, use it
292 share_network_id = sc.share_network_id
293 else:
294 net_id = subnet_id = share_network_id = None
295
296 if not isolated_creds_client:
297 # Search for networks, created in previous runs
298 search_word = "reusable"
299 sn_name = "autogenerated_by_tempest_%s" % search_word
300 service_net_name = "share-service"
Valeriy Ponomaryov48a2bd72015-11-05 13:22:44 +0200301 networks = networks_client.list_networks()
Marc Koderer0abc93b2015-07-15 09:18:35 +0200302 if "networks" in networks.keys():
303 networks = networks["networks"]
304 for network in networks:
305 if (service_net_name in network["name"] and
306 sc.tenant_id == network['tenant_id']):
307 net_id = network["id"]
308 if len(network["subnets"]) > 0:
309 subnet_id = network["subnets"][0]
310 break
311
312 # Create suitable network
313 if (net_id is None or subnet_id is None):
Valeriy Ponomaryov48a2bd72015-11-05 13:22:44 +0200314 ic = dynamic_creds.DynamicCredentialProvider(
315 identity_version=CONF.identity.auth_version,
316 name=service_net_name,
317 admin_role=CONF.identity.admin_role,
Sam Wanc7b7f1f2015-11-25 00:22:28 -0500318 admin_creds=common_creds.get_configured_credentials(
319 'identity_admin'))
Marc Koderer0abc93b2015-07-15 09:18:35 +0200320 net_data = ic._create_network_resources(sc.tenant_id)
321 network, subnet, router = net_data
322 net_id = network["id"]
323 subnet_id = subnet["id"]
324
325 # Try get suitable share-network
326 share_networks = sc.list_share_networks_with_detail()
327 for sn in share_networks:
328 if (net_id == sn["neutron_net_id"] and
329 subnet_id == sn["neutron_subnet_id"] and
330 sn["name"] and search_word in sn["name"]):
331 share_network_id = sn["id"]
332 break
333 else:
334 sn_name = "autogenerated_by_tempest_for_isolated_creds"
335 # Use precreated network and subnet from isolated creds
336 net_id = isolated_creds_client.get_credentials(
337 isolated_creds_client.type_of_creds).network['id']
338 subnet_id = isolated_creds_client.get_credentials(
339 isolated_creds_client.type_of_creds).subnet['id']
340
341 # Create suitable share-network
342 if share_network_id is None:
343 sn_desc = "This share-network was created by tempest"
344 sn = sc.create_share_network(name=sn_name,
345 description=sn_desc,
346 neutron_net_id=net_id,
347 neutron_subnet_id=subnet_id)
348 share_network_id = sn["id"]
349
350 return share_network_id
351
352 @classmethod
353 def _create_share(cls, share_protocol=None, size=1, name=None,
354 snapshot_id=None, description=None, metadata=None,
355 share_network_id=None, share_type_id=None,
Andrew Kerrbf31e912015-07-29 10:39:38 -0400356 consistency_group_id=None, client=None,
Clinton Knighte5c8f092015-08-27 15:00:23 -0400357 cleanup_in_class=True, is_public=False, **kwargs):
Valeriy Ponomaryov1aaa72d2015-09-08 12:59:41 +0300358 client = client or cls.shares_v2_client
Marc Koderer0abc93b2015-07-15 09:18:35 +0200359 description = description or "Tempest's share"
360 share_network_id = share_network_id or client.share_network_id or None
361 metadata = metadata or {}
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
457 try:
458 d["kwargs"]["client"].wait_for_share_status(
459 d["share"]["id"], "available")
460 d["available"] = True
461 except (share_exceptions.ShareBuildErrorException,
462 exceptions.TimeoutException) as e:
463 if CONF.share.share_creation_retry_number > d["cnt"]:
464 d["cnt"] += 1
465 msg = ("Share '%s' failed to be built. "
466 "Trying create another." % d["share"]["id"])
467 LOG.error(msg)
468 LOG.error(e)
469 d["share"] = cls._create_share(
470 *d["args"], **d["kwargs"])
471 else:
472 raise e
473
474 return [d["share"] for d in data]
475
476 @classmethod
Andrew Kerrbf31e912015-07-29 10:39:38 -0400477 def create_consistency_group(cls, client=None, cleanup_in_class=True,
478 share_network_id=None, **kwargs):
Clinton Knighte5c8f092015-08-27 15:00:23 -0400479 client = client or cls.shares_v2_client
Goutham Pacha Ravi9221f5e2016-04-21 13:17:49 -0400480 if kwargs.get('source_cgsnapshot_id') is None:
481 kwargs['share_network_id'] = (share_network_id or
482 client.share_network_id or None)
Andrew Kerrbf31e912015-07-29 10:39:38 -0400483 consistency_group = client.create_consistency_group(**kwargs)
484 resource = {
485 "type": "consistency_group",
486 "id": consistency_group["id"],
487 "client": client}
488 if cleanup_in_class:
489 cls.class_resources.insert(0, resource)
490 else:
491 cls.method_resources.insert(0, resource)
492
493 if kwargs.get('source_cgsnapshot_id'):
494 new_cg_shares = client.list_shares(
495 detailed=True,
496 params={'consistency_group_id': consistency_group['id']})
497
498 for share in new_cg_shares:
499 resource = {"type": "share",
500 "id": share["id"],
501 "client": client,
502 "consistency_group_id": share.get(
503 'consistency_group_id')}
504 if cleanup_in_class:
505 cls.class_resources.insert(0, resource)
506 else:
507 cls.method_resources.insert(0, resource)
508
509 client.wait_for_consistency_group_status(consistency_group['id'],
510 'available')
511 return consistency_group
512
513 @classmethod
Marc Koderer0abc93b2015-07-15 09:18:35 +0200514 def create_snapshot_wait_for_active(cls, share_id, name=None,
515 description=None, force=False,
516 client=None, cleanup_in_class=True):
517 if client is None:
Yogesh1f931ff2015-09-29 23:41:02 -0400518 client = cls.shares_v2_client
Marc Koderer0abc93b2015-07-15 09:18:35 +0200519 if description is None:
520 description = "Tempest's snapshot"
521 snapshot = client.create_snapshot(share_id, name, description, force)
522 resource = {
523 "type": "snapshot",
524 "id": snapshot["id"],
525 "client": client,
526 }
527 if cleanup_in_class:
528 cls.class_resources.insert(0, resource)
529 else:
530 cls.method_resources.insert(0, resource)
531 client.wait_for_snapshot_status(snapshot["id"], "available")
532 return snapshot
533
534 @classmethod
Andrew Kerrbf31e912015-07-29 10:39:38 -0400535 def create_cgsnapshot_wait_for_active(cls, consistency_group_id,
536 name=None, description=None,
Clinton Knighte5c8f092015-08-27 15:00:23 -0400537 client=None, cleanup_in_class=True,
538 **kwargs):
539 client = client or cls.shares_v2_client
Andrew Kerrbf31e912015-07-29 10:39:38 -0400540 if description is None:
541 description = "Tempest's cgsnapshot"
Clinton Knighte5c8f092015-08-27 15:00:23 -0400542 cgsnapshot = client.create_cgsnapshot(consistency_group_id,
543 name=name,
544 description=description,
545 **kwargs)
Andrew Kerrbf31e912015-07-29 10:39:38 -0400546 resource = {
547 "type": "cgsnapshot",
548 "id": cgsnapshot["id"],
549 "client": client,
550 }
551 if cleanup_in_class:
552 cls.class_resources.insert(0, resource)
553 else:
554 cls.method_resources.insert(0, resource)
555 client.wait_for_cgsnapshot_status(cgsnapshot["id"], "available")
556 return cgsnapshot
557
558 @classmethod
Yogeshbdb88102015-09-29 23:41:02 -0400559 def get_availability_zones(cls, client=None):
560 """List the availability zones for "manila-share" services
561
562 that are currently in "up" state.
563 """
564 client = client or cls.shares_v2_client
565 cls.services = client.list_services()
566 zones = [service['zone'] for service in cls.services if
567 service['binary'] == "manila-share" and
568 service['state'] == 'up']
569 return zones
570
Yogesh1f931ff2015-09-29 23:41:02 -0400571 def get_pools_for_replication_domain(self):
572 # Get the list of pools for the replication domain
573 pools = self.admin_client.list_pools(detail=True)['pools']
574 instance_host = self.shares[0]['host']
575 host_pool = [p for p in pools if p['name'] == instance_host][0]
576 rep_domain = host_pool['capabilities']['replication_domain']
577 pools_in_rep_domain = [p for p in pools if p['capabilities'][
578 'replication_domain'] == rep_domain]
579 return rep_domain, pools_in_rep_domain
580
Yogeshbdb88102015-09-29 23:41:02 -0400581 @classmethod
582 def create_share_replica(cls, share_id, availability_zone, client=None,
583 cleanup_in_class=False, cleanup=True):
584 client = client or cls.shares_v2_client
585 replica = client.create_share_replica(share_id, availability_zone)
586 resource = {
587 "type": "share_replica",
588 "id": replica["id"],
589 "client": client,
590 "share_id": share_id,
591 }
592 # NOTE(Yogi1): Cleanup needs to be disabled during promotion tests.
593 if cleanup:
594 if cleanup_in_class:
595 cls.class_resources.insert(0, resource)
596 else:
597 cls.method_resources.insert(0, resource)
598 client.wait_for_share_replica_status(
599 replica["id"], constants.STATUS_AVAILABLE)
600 return replica
601
602 @classmethod
603 def delete_share_replica(cls, replica_id, client=None):
604 client = client or cls.shares_v2_client
Yogesh1f931ff2015-09-29 23:41:02 -0400605 try:
606 client.delete_share_replica(replica_id)
607 client.wait_for_resource_deletion(replica_id=replica_id)
608 except exceptions.NotFound:
609 pass
Yogeshbdb88102015-09-29 23:41:02 -0400610
611 @classmethod
612 def promote_share_replica(cls, replica_id, client=None):
613 client = client or cls.shares_v2_client
614 replica = client.promote_share_replica(replica_id)
615 client.wait_for_share_replica_status(
616 replica["id"],
617 constants.REPLICATION_STATE_ACTIVE,
618 status_attr="replica_state")
619 return replica
620
621 @classmethod
Marc Koderer0abc93b2015-07-15 09:18:35 +0200622 def create_share_network(cls, client=None,
623 cleanup_in_class=False, **kwargs):
624 if client is None:
625 client = cls.shares_client
626 share_network = client.create_share_network(**kwargs)
627 resource = {
628 "type": "share_network",
629 "id": share_network["id"],
630 "client": client,
631 }
632 if cleanup_in_class:
633 cls.class_resources.insert(0, resource)
634 else:
635 cls.method_resources.insert(0, resource)
636 return share_network
637
638 @classmethod
639 def create_security_service(cls, ss_type="ldap", client=None,
640 cleanup_in_class=False, **kwargs):
641 if client is None:
642 client = cls.shares_client
643 security_service = client.create_security_service(ss_type, **kwargs)
644 resource = {
645 "type": "security_service",
646 "id": security_service["id"],
647 "client": client,
648 }
649 if cleanup_in_class:
650 cls.class_resources.insert(0, resource)
651 else:
652 cls.method_resources.insert(0, resource)
653 return security_service
654
655 @classmethod
656 def create_share_type(cls, name, is_public=True, client=None,
657 cleanup_in_class=True, **kwargs):
658 if client is None:
Valeriy Ponomaryova14c2252015-10-29 13:34:32 +0200659 client = cls.shares_v2_client
Marc Koderer0abc93b2015-07-15 09:18:35 +0200660 share_type = client.create_share_type(name, is_public, **kwargs)
661 resource = {
662 "type": "share_type",
663 "id": share_type["share_type"]["id"],
664 "client": client,
665 }
666 if cleanup_in_class:
667 cls.class_resources.insert(0, resource)
668 else:
669 cls.method_resources.insert(0, resource)
670 return share_type
671
672 @staticmethod
673 def add_required_extra_specs_to_dict(extra_specs=None):
Valeriy Ponomaryovad55dc52015-09-23 13:54:00 +0300674 dhss = six.text_type(CONF.share.multitenancy_enabled)
675 snapshot_support = six.text_type(
676 CONF.share.capability_snapshot_support)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200677 required = {
Valeriy Ponomaryovad55dc52015-09-23 13:54:00 +0300678 "driver_handles_share_servers": dhss,
679 "snapshot_support": snapshot_support,
Marc Koderer0abc93b2015-07-15 09:18:35 +0200680 }
681 if extra_specs:
682 required.update(extra_specs)
683 return required
684
685 @classmethod
686 def clear_isolated_creds(cls, creds=None):
687 if creds is None:
688 creds = cls.method_isolated_creds
689 for ic in creds:
690 if "deleted" not in ic.keys():
691 ic["deleted"] = False
692 if not ic["deleted"]:
693 with handle_cleanup_exceptions():
694 ic["method"]()
695 ic["deleted"] = True
696
697 @classmethod
Yogesh1f931ff2015-09-29 23:41:02 -0400698 def clear_share_replicas(cls, share_id, client=None):
699 client = client or cls.shares_v2_client
700 share_replicas = client.list_share_replicas(
701 share_id=share_id)
702
703 for replica in share_replicas:
704 try:
705 cls.delete_share_replica(replica['id'])
706 except exceptions.BadRequest:
707 # Ignore the exception due to deletion of last active replica
708 pass
709
710 @classmethod
Marc Koderer0abc93b2015-07-15 09:18:35 +0200711 def clear_resources(cls, resources=None):
712 """Deletes resources, that were created in test suites.
713
714 This method tries to remove resources from resource list,
715 if it is not found, assumed it was deleted in test itself.
716 It is expected, that all resources were added as LIFO
717 due to restriction of deletion resources, that is in the chain.
718
719 :param resources: dict with keys 'type','id','client' and 'deleted'
720 """
721
722 if resources is None:
723 resources = cls.method_resources
724 for res in resources:
725 if "deleted" not in res.keys():
726 res["deleted"] = False
727 if "client" not in res.keys():
728 res["client"] = cls.shares_client
729 if not(res["deleted"]):
730 res_id = res['id']
731 client = res["client"]
732 with handle_cleanup_exceptions():
733 if res["type"] is "share":
Yogesh1f931ff2015-09-29 23:41:02 -0400734 cls.clear_share_replicas(res_id)
Andrew Kerrbf31e912015-07-29 10:39:38 -0400735 cg_id = res.get('consistency_group_id')
736 if cg_id:
737 params = {'consistency_group_id': cg_id}
Clinton Knighte5c8f092015-08-27 15:00:23 -0400738 client.delete_share(res_id, params=params)
739 else:
740 client.delete_share(res_id)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200741 client.wait_for_resource_deletion(share_id=res_id)
742 elif res["type"] is "snapshot":
743 client.delete_snapshot(res_id)
744 client.wait_for_resource_deletion(snapshot_id=res_id)
745 elif res["type"] is "share_network":
746 client.delete_share_network(res_id)
747 client.wait_for_resource_deletion(sn_id=res_id)
748 elif res["type"] is "security_service":
749 client.delete_security_service(res_id)
750 client.wait_for_resource_deletion(ss_id=res_id)
751 elif res["type"] is "share_type":
752 client.delete_share_type(res_id)
753 client.wait_for_resource_deletion(st_id=res_id)
Andrew Kerrbf31e912015-07-29 10:39:38 -0400754 elif res["type"] is "consistency_group":
755 client.delete_consistency_group(res_id)
756 client.wait_for_resource_deletion(cg_id=res_id)
757 elif res["type"] is "cgsnapshot":
758 client.delete_cgsnapshot(res_id)
759 client.wait_for_resource_deletion(cgsnapshot_id=res_id)
Yogeshbdb88102015-09-29 23:41:02 -0400760 elif res["type"] is "share_replica":
761 client.delete_share_replica(res_id)
762 client.wait_for_resource_deletion(replica_id=res_id)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200763 else:
huayue97bacbf2016-01-04 09:57:39 +0800764 LOG.warning("Provided unsupported resource type for "
765 "cleanup '%s'. Skipping." % res["type"])
Marc Koderer0abc93b2015-07-15 09:18:35 +0200766 res["deleted"] = True
767
768 @classmethod
769 def generate_share_network_data(self):
770 data = {
771 "name": data_utils.rand_name("sn-name"),
772 "description": data_utils.rand_name("sn-desc"),
773 "neutron_net_id": data_utils.rand_name("net-id"),
774 "neutron_subnet_id": data_utils.rand_name("subnet-id"),
775 }
776 return data
777
778 @classmethod
779 def generate_security_service_data(self):
780 data = {
781 "name": data_utils.rand_name("ss-name"),
782 "description": data_utils.rand_name("ss-desc"),
Valeriy Ponomaryovfcde7712015-12-14 18:06:13 +0200783 "dns_ip": utils.rand_ip(),
784 "server": utils.rand_ip(),
Marc Koderer0abc93b2015-07-15 09:18:35 +0200785 "domain": data_utils.rand_name("ss-domain"),
786 "user": data_utils.rand_name("ss-user"),
787 "password": data_utils.rand_name("ss-password"),
788 }
789 return data
790
791 # Useful assertions
792 def assertDictMatch(self, d1, d2, approx_equal=False, tolerance=0.001):
793 """Assert two dicts are equivalent.
794
795 This is a 'deep' match in the sense that it handles nested
796 dictionaries appropriately.
797
798 NOTE:
799
800 If you don't care (or don't know) a given value, you can specify
801 the string DONTCARE as the value. This will cause that dict-item
802 to be skipped.
803
804 """
805 def raise_assertion(msg):
806 d1str = str(d1)
807 d2str = str(d2)
808 base_msg = ('Dictionaries do not match. %(msg)s d1: %(d1str)s '
809 'd2: %(d2str)s' %
810 {"msg": msg, "d1str": d1str, "d2str": d2str})
811 raise AssertionError(base_msg)
812
813 d1keys = set(d1.keys())
814 d2keys = set(d2.keys())
815 if d1keys != d2keys:
816 d1only = d1keys - d2keys
817 d2only = d2keys - d1keys
818 raise_assertion('Keys in d1 and not d2: %(d1only)s. '
819 'Keys in d2 and not d1: %(d2only)s' %
820 {"d1only": d1only, "d2only": d2only})
821
822 for key in d1keys:
823 d1value = d1[key]
824 d2value = d2[key]
825 try:
826 error = abs(float(d1value) - float(d2value))
827 within_tolerance = error <= tolerance
828 except (ValueError, TypeError):
daiki kato6914b1a2016-03-16 17:16:57 +0900829 # If both values aren't convertible to float, just ignore
Marc Koderer0abc93b2015-07-15 09:18:35 +0200830 # ValueError if arg is a str, TypeError if it's something else
831 # (like None)
832 within_tolerance = False
833
834 if hasattr(d1value, 'keys') and hasattr(d2value, 'keys'):
835 self.assertDictMatch(d1value, d2value)
836 elif 'DONTCARE' in (d1value, d2value):
837 continue
838 elif approx_equal and within_tolerance:
839 continue
840 elif d1value != d2value:
841 raise_assertion("d1['%(key)s']=%(d1value)s != "
842 "d2['%(key)s']=%(d2value)s" %
843 {
844 "key": key,
845 "d1value": d1value,
846 "d2value": d2value
847 })
848
849
850class BaseSharesAltTest(BaseSharesTest):
851 """Base test case class for all Shares Alt API tests."""
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +0300852 credentials = ('alt', )
Marc Koderer0abc93b2015-07-15 09:18:35 +0200853
854
855class BaseSharesAdminTest(BaseSharesTest):
856 """Base test case class for all Shares Admin API tests."""
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +0300857 credentials = ('admin', )
858
859
860class BaseSharesMixedTest(BaseSharesTest):
861 """Base test case class for all Shares API tests with all user roles."""
862 credentials = ('primary', 'alt', 'admin')
Marc Koderer0abc93b2015-07-15 09:18:35 +0200863
864 @classmethod
Valeriy Ponomaryov39cdf722016-05-30 18:16:15 +0300865 def setup_clients(cls):
866 super(BaseSharesMixedTest, cls).setup_clients()
867 cls.admin_shares_client = shares_client.SharesClient(
868 cls.os_admin.auth_provider)
869 cls.admin_shares_v2_client = shares_v2_client.SharesV2Client(
870 cls.os_admin.auth_provider)
871 cls.alt_shares_client = shares_client.SharesClient(
872 cls.os_alt.auth_provider)
873 cls.alt_shares_v2_client = shares_v2_client.SharesV2Client(
874 cls.os_alt.auth_provider)
875
876 if CONF.share.multitenancy_enabled:
877 admin_share_network_id = cls.provide_share_network(
878 cls.admin_shares_v2_client, cls.os_admin.networks_client)
879 cls.admin_shares_client.share_network_id = admin_share_network_id
880 cls.admin_shares_v2_client.share_network_id = (
881 admin_share_network_id)
882
883 alt_share_network_id = cls.provide_share_network(
884 cls.alt_shares_v2_client, cls.os_alt.networks_client)
885 cls.alt_shares_client.share_network_id = alt_share_network_id
886 cls.alt_shares_v2_client.share_network_id = alt_share_network_id