blob: ec6417455f6f3439c0e8b331d065ecd72b9baf0a [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
Sam Wanc7b7f1f2015-11-25 00:22:28 -050024from tempest.common import credentials_factory as common_creds
Valeriy Ponomaryov48a2bd72015-11-05 13:22:44 +020025from tempest.common import dynamic_creds
26from tempest import config
Ben Swartzlander1c4ff522016-03-02 22:16:23 -050027from tempest.lib.common.utils import data_utils
28from tempest.lib import exceptions
Valeriy Ponomaryov48a2bd72015-11-05 13:22:44 +020029from tempest import test
Marc Koderer0abc93b2015-07-15 09:18:35 +020030
31from manila_tempest_tests import clients_share as clients
Yogeshbdb88102015-09-29 23:41:02 -040032from manila_tempest_tests.common import constants
Marc Koderer0abc93b2015-07-15 09:18:35 +020033from manila_tempest_tests import share_exceptions
Valeriy Ponomaryovfcde7712015-12-14 18:06:13 +020034from manila_tempest_tests import utils
Marc Koderer0abc93b2015-07-15 09:18:35 +020035
36CONF = config.CONF
37LOG = log.getLogger(__name__)
38
Valeriy Ponomaryov2abf5d72016-06-01 18:30:12 +030039# Test tags related to test direction
40TAG_POSITIVE = "positive"
41TAG_NEGATIVE = "negative"
42
43# Test tags related to service involvement
44TAG_API = "api"
45TAG_BACKEND = "backend"
46TAG_API_WITH_BACKEND = "api_with_backend"
47
48TAGS_MAPPER = {
49 "p": TAG_POSITIVE,
50 "n": TAG_NEGATIVE,
51 "a": TAG_API,
52 "b": TAG_BACKEND,
53 "ab": TAG_API_WITH_BACKEND,
54}
55TAGS_PATTERN = re.compile(
56 r"(?=.*\[.*\b(%(p)s|%(n)s)\b.*\])(?=.*\[.*\b(%(a)s|%(b)s|%(ab)s)\b.*\])" %
57 TAGS_MAPPER)
58
59
60def verify_test_has_appropriate_tags(self):
61 if not TAGS_PATTERN.match(self.id()):
62 msg = (
63 "Required attributes either not set or set improperly. "
64 "Two test attributes are expected:\n"
65 " - one of '%(p)s' or '%(n)s' and \n"
66 " - one of '%(a)s', '%(b)s' or '%(ab)s'."
67 ) % TAGS_MAPPER
68 raise self.failureException(msg)
69
Marc Koderer0abc93b2015-07-15 09:18:35 +020070
71class handle_cleanup_exceptions(object):
72 """Handle exceptions raised with cleanup operations.
73
74 Always suppress errors when exceptions.NotFound or exceptions.Forbidden
75 are raised.
76 Suppress all other exceptions only in case config opt
77 'suppress_errors_in_cleanup' in config group 'share' is True.
78 """
79
80 def __enter__(self):
81 return self
82
83 def __exit__(self, exc_type, exc_value, exc_traceback):
84 if not (isinstance(exc_value,
85 (exceptions.NotFound, exceptions.Forbidden)) or
86 CONF.share.suppress_errors_in_cleanup):
87 return False # Do not suppress error if any
88 if exc_traceback:
89 LOG.error("Suppressed cleanup error in Manila: "
90 "\n%s" % traceback.format_exc())
91 return True # Suppress error if any
92
93
94def network_synchronized(f):
95
96 def wrapped_func(self, *args, **kwargs):
97 with_isolated_creds = True if len(args) > 2 else False
98 no_lock_required = kwargs.get(
99 "isolated_creds_client", with_isolated_creds)
100 if no_lock_required:
101 # Usage of not reusable network. No need in lock.
102 return f(self, *args, **kwargs)
103
104 # Use lock assuming reusage of common network.
105 @lockutils.synchronized("manila_network_lock", external=True)
106 def source_func(self, *args, **kwargs):
107 return f(self, *args, **kwargs)
108
109 return source_func(self, *args, **kwargs)
110
111 return wrapped_func
112
113
Valeriy Ponomaryovfcde7712015-12-14 18:06:13 +0200114skip_if_microversion_not_supported = utils.skip_if_microversion_not_supported
Xing Yang69b00b52015-11-22 16:10:44 -0500115skip_if_microversion_lt = utils.skip_if_microversion_lt
Valeriy Ponomaryova14c2252015-10-29 13:34:32 +0200116
117
Marc Koderer0abc93b2015-07-15 09:18:35 +0200118class BaseSharesTest(test.BaseTestCase):
119 """Base test case class for all Manila API tests."""
120
121 force_tenant_isolation = False
John Spray061b1452015-11-18 13:15:32 +0000122 protocols = ["nfs", "cifs", "glusterfs", "hdfs", "cephfs"]
Marc Koderer0abc93b2015-07-15 09:18:35 +0200123
124 # Will be cleaned up in resource_cleanup
125 class_resources = []
126
127 # Will be cleaned up in tearDown method
128 method_resources = []
129
130 # Will be cleaned up in resource_cleanup
131 class_isolated_creds = []
132
133 # Will be cleaned up in tearDown method
134 method_isolated_creds = []
135
Valeriy Ponomaryova14c2252015-10-29 13:34:32 +0200136 def skip_if_microversion_not_supported(self, microversion):
Valeriy Ponomaryovfcde7712015-12-14 18:06:13 +0200137 if not utils.is_microversion_supported(microversion):
Valeriy Ponomaryova14c2252015-10-29 13:34:32 +0200138 raise self.skipException(
139 "Microversion '%s' is not supported." % microversion)
140
Xing Yang69b00b52015-11-22 16:10:44 -0500141 def skip_if_microversion_lt(self, microversion):
142 if utils.is_microversion_lt(CONF.share.max_api_microversion,
143 microversion):
144 raise self.skipException(
145 "Microversion must be greater than or equal to '%s'." %
146 microversion)
147
Marc Koderer0abc93b2015-07-15 09:18:35 +0200148 @classmethod
149 def get_client_with_isolated_creds(cls,
150 name=None,
151 type_of_creds="admin",
Clinton Knighte5c8f092015-08-27 15:00:23 -0400152 cleanup_in_class=False,
153 client_version='1'):
Marc Koderer0abc93b2015-07-15 09:18:35 +0200154 """Creates isolated creds.
155
156 :param name: name, will be used for naming ic and related stuff
157 :param type_of_creds: admin, alt or primary
158 :param cleanup_in_class: defines place where to delete
159 :returns: SharesClient -- shares client with isolated creds.
160 :returns: To client added dict attr 'creds' with
161 :returns: key elements 'tenant' and 'user'.
162 """
163 if name is None:
164 # Get name of test method
165 name = inspect.stack()[1][3]
166 if len(name) > 32:
167 name = name[0:32]
168
169 # Choose type of isolated creds
Valeriy Ponomaryov48a2bd72015-11-05 13:22:44 +0200170 ic = dynamic_creds.DynamicCredentialProvider(
171 identity_version=CONF.identity.auth_version,
172 name=name,
Sam Wanc7b7f1f2015-11-25 00:22:28 -0500173 admin_role=CONF.identity.admin_role,
174 admin_creds=common_creds.get_configured_credentials(
175 'identity_admin'))
Marc Koderer0abc93b2015-07-15 09:18:35 +0200176 if "admin" in type_of_creds:
177 creds = ic.get_admin_creds()
178 elif "alt" in type_of_creds:
179 creds = ic.get_alt_creds()
180 else:
181 creds = ic.self.get_credentials(type_of_creds)
182 ic.type_of_creds = type_of_creds
183
184 # create client with isolated creds
185 os = clients.Manager(credentials=creds)
Clinton Knighte5c8f092015-08-27 15:00:23 -0400186 if client_version == '1':
187 client = os.shares_client
188 elif client_version == '2':
189 client = os.shares_v2_client
Marc Koderer0abc93b2015-07-15 09:18:35 +0200190
191 # Set place where will be deleted isolated creds
192 ic_res = {
Valeriy Ponomaryov48a2bd72015-11-05 13:22:44 +0200193 "method": ic.clear_creds,
Marc Koderer0abc93b2015-07-15 09:18:35 +0200194 "deleted": False,
195 }
196 if cleanup_in_class:
197 cls.class_isolated_creds.insert(0, ic_res)
198 else:
199 cls.method_isolated_creds.insert(0, ic_res)
200
201 # Provide share network
202 if CONF.share.multitenancy_enabled:
203 if not CONF.service_available.neutron:
204 raise cls.skipException("Neutron support is required")
Valeriy Ponomaryov48a2bd72015-11-05 13:22:44 +0200205 nc = os.networks_client
Marc Koderer0abc93b2015-07-15 09:18:35 +0200206 share_network_id = cls.provide_share_network(client, nc, ic)
207 client.share_network_id = share_network_id
208 resource = {
209 "type": "share_network",
210 "id": client.share_network_id,
211 "client": client,
212 }
213 if cleanup_in_class:
214 cls.class_resources.insert(0, resource)
215 else:
216 cls.method_resources.insert(0, resource)
217 return client
218
219 @classmethod
220 def verify_nonempty(cls, *args):
221 if not all(args):
222 msg = "Missing API credentials in configuration."
223 raise cls.skipException(msg)
224
225 @classmethod
226 def resource_setup(cls):
227 if not (any(p in CONF.share.enable_protocols
228 for p in cls.protocols) and
229 CONF.service_available.manila):
230 skip_msg = "Manila is disabled"
231 raise cls.skipException(skip_msg)
232 super(BaseSharesTest, cls).resource_setup()
233 if not hasattr(cls, "os"):
234 cls.username = CONF.identity.username
235 cls.password = CONF.identity.password
Valeriy Ponomaryov1950cb82016-04-11 14:02:29 +0300236 cls.project_name = CONF.identity.project_name
237 cls.verify_nonempty(cls.username, cls.password, cls.project_name)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200238 cls.os = clients.Manager()
239 if CONF.share.multitenancy_enabled:
240 if not CONF.service_available.neutron:
241 raise cls.skipException("Neutron support is required")
242 sc = cls.os.shares_client
Valeriy Ponomaryov48a2bd72015-11-05 13:22:44 +0200243 nc = cls.os.networks_client
Marc Koderer0abc93b2015-07-15 09:18:35 +0200244 share_network_id = cls.provide_share_network(sc, nc)
245 cls.os.shares_client.share_network_id = share_network_id
Clinton Knighte5c8f092015-08-27 15:00:23 -0400246 cls.os.shares_v2_client.share_network_id = share_network_id
Marc Koderer0abc93b2015-07-15 09:18:35 +0200247 cls.shares_client = cls.os.shares_client
Clinton Knighte5c8f092015-08-27 15:00:23 -0400248 cls.shares_v2_client = cls.os.shares_v2_client
Marc Koderer0abc93b2015-07-15 09:18:35 +0200249
250 def setUp(self):
251 super(BaseSharesTest, self).setUp()
Marc Koderer0abc93b2015-07-15 09:18:35 +0200252 self.addCleanup(self.clear_isolated_creds)
Valeriy Ponomaryovdd162cb2016-01-20 19:09:49 +0200253 self.addCleanup(self.clear_resources)
Valeriy Ponomaryov2abf5d72016-06-01 18:30:12 +0300254 verify_test_has_appropriate_tags(self)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200255
256 @classmethod
257 def resource_cleanup(cls):
258 super(BaseSharesTest, cls).resource_cleanup()
259 cls.clear_resources(cls.class_resources)
260 cls.clear_isolated_creds(cls.class_isolated_creds)
261
262 @classmethod
263 @network_synchronized
Valeriy Ponomaryov48a2bd72015-11-05 13:22:44 +0200264 def provide_share_network(cls, shares_client, networks_client,
Marc Koderer0abc93b2015-07-15 09:18:35 +0200265 isolated_creds_client=None):
266 """Used for finding/creating share network for multitenant driver.
267
268 This method creates/gets entity share-network for one tenant. This
269 share-network will be used for creation of service vm.
270
271 :param shares_client: shares client, which requires share-network
Valeriy Ponomaryov48a2bd72015-11-05 13:22:44 +0200272 :param networks_client: network client from same tenant as shares
273 :param isolated_creds_client: DynamicCredentialProvider instance
Marc Koderer0abc93b2015-07-15 09:18:35 +0200274 If provided, then its networking will be used if needed.
275 If not provided, then common network will be used if needed.
276 :returns: str -- share network id for shares_client tenant
277 :returns: None -- if single-tenant driver used
278 """
279
280 sc = shares_client
281
282 if not CONF.share.multitenancy_enabled:
283 # Assumed usage of a single-tenant driver
284 share_network_id = None
285 elif sc.share_network_id:
286 # Share-network already exists, use it
287 share_network_id = sc.share_network_id
288 else:
289 net_id = subnet_id = share_network_id = None
290
291 if not isolated_creds_client:
292 # Search for networks, created in previous runs
293 search_word = "reusable"
294 sn_name = "autogenerated_by_tempest_%s" % search_word
295 service_net_name = "share-service"
Valeriy Ponomaryov48a2bd72015-11-05 13:22:44 +0200296 networks = networks_client.list_networks()
Marc Koderer0abc93b2015-07-15 09:18:35 +0200297 if "networks" in networks.keys():
298 networks = networks["networks"]
299 for network in networks:
300 if (service_net_name in network["name"] and
301 sc.tenant_id == network['tenant_id']):
302 net_id = network["id"]
303 if len(network["subnets"]) > 0:
304 subnet_id = network["subnets"][0]
305 break
306
307 # Create suitable network
308 if (net_id is None or subnet_id is None):
Valeriy Ponomaryov48a2bd72015-11-05 13:22:44 +0200309 ic = dynamic_creds.DynamicCredentialProvider(
310 identity_version=CONF.identity.auth_version,
311 name=service_net_name,
312 admin_role=CONF.identity.admin_role,
Sam Wanc7b7f1f2015-11-25 00:22:28 -0500313 admin_creds=common_creds.get_configured_credentials(
314 'identity_admin'))
Marc Koderer0abc93b2015-07-15 09:18:35 +0200315 net_data = ic._create_network_resources(sc.tenant_id)
316 network, subnet, router = net_data
317 net_id = network["id"]
318 subnet_id = subnet["id"]
319
320 # Try get suitable share-network
321 share_networks = sc.list_share_networks_with_detail()
322 for sn in share_networks:
323 if (net_id == sn["neutron_net_id"] and
324 subnet_id == sn["neutron_subnet_id"] and
325 sn["name"] and search_word in sn["name"]):
326 share_network_id = sn["id"]
327 break
328 else:
329 sn_name = "autogenerated_by_tempest_for_isolated_creds"
330 # Use precreated network and subnet from isolated creds
331 net_id = isolated_creds_client.get_credentials(
332 isolated_creds_client.type_of_creds).network['id']
333 subnet_id = isolated_creds_client.get_credentials(
334 isolated_creds_client.type_of_creds).subnet['id']
335
336 # Create suitable share-network
337 if share_network_id is None:
338 sn_desc = "This share-network was created by tempest"
339 sn = sc.create_share_network(name=sn_name,
340 description=sn_desc,
341 neutron_net_id=net_id,
342 neutron_subnet_id=subnet_id)
343 share_network_id = sn["id"]
344
345 return share_network_id
346
347 @classmethod
348 def _create_share(cls, share_protocol=None, size=1, name=None,
349 snapshot_id=None, description=None, metadata=None,
350 share_network_id=None, share_type_id=None,
Andrew Kerrbf31e912015-07-29 10:39:38 -0400351 consistency_group_id=None, client=None,
Clinton Knighte5c8f092015-08-27 15:00:23 -0400352 cleanup_in_class=True, is_public=False, **kwargs):
Valeriy Ponomaryov1aaa72d2015-09-08 12:59:41 +0300353 client = client or cls.shares_v2_client
Marc Koderer0abc93b2015-07-15 09:18:35 +0200354 description = description or "Tempest's share"
355 share_network_id = share_network_id or client.share_network_id or None
356 metadata = metadata or {}
Clinton Knighte5c8f092015-08-27 15:00:23 -0400357 kwargs.update({
Marc Koderer0abc93b2015-07-15 09:18:35 +0200358 'share_protocol': share_protocol,
359 'size': size,
360 'name': name,
361 'snapshot_id': snapshot_id,
362 'description': description,
363 'metadata': metadata,
364 'share_network_id': share_network_id,
365 'share_type_id': share_type_id,
366 'is_public': is_public,
Clinton Knighte5c8f092015-08-27 15:00:23 -0400367 })
Andrew Kerrbf31e912015-07-29 10:39:38 -0400368 if consistency_group_id:
369 kwargs['consistency_group_id'] = consistency_group_id
370
Marc Koderer0abc93b2015-07-15 09:18:35 +0200371 share = client.create_share(**kwargs)
Andrew Kerrbf31e912015-07-29 10:39:38 -0400372 resource = {"type": "share", "id": share["id"], "client": client,
373 "consistency_group_id": consistency_group_id}
Marc Koderer0abc93b2015-07-15 09:18:35 +0200374 cleanup_list = (cls.class_resources if cleanup_in_class else
375 cls.method_resources)
376 cleanup_list.insert(0, resource)
377 return share
378
379 @classmethod
Rodrigo Barbierie3305122016-02-03 14:32:24 -0200380 def migrate_share(cls, share_id, dest_host, client=None, notify=True,
381 wait_for_status='migration_success', **kwargs):
Clinton Knighte5c8f092015-08-27 15:00:23 -0400382 client = client or cls.shares_v2_client
Rodrigo Barbierie3305122016-02-03 14:32:24 -0200383 client.migrate_share(share_id, dest_host, notify, **kwargs)
384 share = client.wait_for_migration_status(
385 share_id, dest_host, wait_for_status,
386 version=kwargs.get('version'))
387 return share
388
389 @classmethod
390 def migration_complete(cls, share_id, dest_host, client=None, **kwargs):
391 client = client or cls.shares_v2_client
392 client.migration_complete(share_id, **kwargs)
393 share = client.wait_for_migration_status(
394 share_id, dest_host, 'migration_success',
395 version=kwargs.get('version'))
Rodrigo Barbierib7137ad2015-09-06 22:53:16 -0300396 return share
397
398 @classmethod
Marc Koderer0abc93b2015-07-15 09:18:35 +0200399 def create_share(cls, *args, **kwargs):
400 """Create one share and wait for available state. Retry if allowed."""
401 result = cls.create_shares([{"args": args, "kwargs": kwargs}])
402 return result[0]
403
404 @classmethod
405 def create_shares(cls, share_data_list):
406 """Creates several shares in parallel with retries.
407
408 Use this method when you want to create more than one share at same
409 time. Especially if config option 'share.share_creation_retry_number'
410 has value more than zero (0).
411 All shares will be expected to have 'available' status with or without
412 recreation else error will be raised.
413
414 :param share_data_list: list -- list of dictionaries with 'args' and
415 'kwargs' for '_create_share' method of this base class.
416 example of data:
417 share_data_list=[{'args': ['quuz'], 'kwargs': {'foo': 'bar'}}}]
418 :returns: list -- list of shares created using provided data.
419 """
420
421 data = [copy.deepcopy(d) for d in share_data_list]
422 for d in data:
423 if not isinstance(d, dict):
424 raise exceptions.TempestException(
425 "Expected 'dict', got '%s'" % type(d))
426 if "args" not in d:
427 d["args"] = []
428 if "kwargs" not in d:
429 d["kwargs"] = {}
430 if len(d) > 2:
431 raise exceptions.TempestException(
432 "Expected only 'args' and 'kwargs' keys. "
433 "Provided %s" % list(d))
434 d["kwargs"]["client"] = d["kwargs"].get(
Valeriy Ponomaryov1aaa72d2015-09-08 12:59:41 +0300435 "client", cls.shares_v2_client)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200436 d["share"] = cls._create_share(*d["args"], **d["kwargs"])
437 d["cnt"] = 0
438 d["available"] = False
439
440 while not all(d["available"] for d in data):
441 for d in data:
442 if d["available"]:
443 continue
444 try:
445 d["kwargs"]["client"].wait_for_share_status(
446 d["share"]["id"], "available")
447 d["available"] = True
448 except (share_exceptions.ShareBuildErrorException,
449 exceptions.TimeoutException) as e:
450 if CONF.share.share_creation_retry_number > d["cnt"]:
451 d["cnt"] += 1
452 msg = ("Share '%s' failed to be built. "
453 "Trying create another." % d["share"]["id"])
454 LOG.error(msg)
455 LOG.error(e)
456 d["share"] = cls._create_share(
457 *d["args"], **d["kwargs"])
458 else:
459 raise e
460
461 return [d["share"] for d in data]
462
463 @classmethod
Andrew Kerrbf31e912015-07-29 10:39:38 -0400464 def create_consistency_group(cls, client=None, cleanup_in_class=True,
465 share_network_id=None, **kwargs):
Clinton Knighte5c8f092015-08-27 15:00:23 -0400466 client = client or cls.shares_v2_client
Goutham Pacha Ravi9221f5e2016-04-21 13:17:49 -0400467 if kwargs.get('source_cgsnapshot_id') is None:
468 kwargs['share_network_id'] = (share_network_id or
469 client.share_network_id or None)
Andrew Kerrbf31e912015-07-29 10:39:38 -0400470 consistency_group = client.create_consistency_group(**kwargs)
471 resource = {
472 "type": "consistency_group",
473 "id": consistency_group["id"],
474 "client": client}
475 if cleanup_in_class:
476 cls.class_resources.insert(0, resource)
477 else:
478 cls.method_resources.insert(0, resource)
479
480 if kwargs.get('source_cgsnapshot_id'):
481 new_cg_shares = client.list_shares(
482 detailed=True,
483 params={'consistency_group_id': consistency_group['id']})
484
485 for share in new_cg_shares:
486 resource = {"type": "share",
487 "id": share["id"],
488 "client": client,
489 "consistency_group_id": share.get(
490 'consistency_group_id')}
491 if cleanup_in_class:
492 cls.class_resources.insert(0, resource)
493 else:
494 cls.method_resources.insert(0, resource)
495
496 client.wait_for_consistency_group_status(consistency_group['id'],
497 'available')
498 return consistency_group
499
500 @classmethod
Marc Koderer0abc93b2015-07-15 09:18:35 +0200501 def create_snapshot_wait_for_active(cls, share_id, name=None,
502 description=None, force=False,
503 client=None, cleanup_in_class=True):
504 if client is None:
Yogesh1f931ff2015-09-29 23:41:02 -0400505 client = cls.shares_v2_client
Marc Koderer0abc93b2015-07-15 09:18:35 +0200506 if description is None:
507 description = "Tempest's snapshot"
508 snapshot = client.create_snapshot(share_id, name, description, force)
509 resource = {
510 "type": "snapshot",
511 "id": snapshot["id"],
512 "client": client,
513 }
514 if cleanup_in_class:
515 cls.class_resources.insert(0, resource)
516 else:
517 cls.method_resources.insert(0, resource)
518 client.wait_for_snapshot_status(snapshot["id"], "available")
519 return snapshot
520
521 @classmethod
Andrew Kerrbf31e912015-07-29 10:39:38 -0400522 def create_cgsnapshot_wait_for_active(cls, consistency_group_id,
523 name=None, description=None,
Clinton Knighte5c8f092015-08-27 15:00:23 -0400524 client=None, cleanup_in_class=True,
525 **kwargs):
526 client = client or cls.shares_v2_client
Andrew Kerrbf31e912015-07-29 10:39:38 -0400527 if description is None:
528 description = "Tempest's cgsnapshot"
Clinton Knighte5c8f092015-08-27 15:00:23 -0400529 cgsnapshot = client.create_cgsnapshot(consistency_group_id,
530 name=name,
531 description=description,
532 **kwargs)
Andrew Kerrbf31e912015-07-29 10:39:38 -0400533 resource = {
534 "type": "cgsnapshot",
535 "id": cgsnapshot["id"],
536 "client": client,
537 }
538 if cleanup_in_class:
539 cls.class_resources.insert(0, resource)
540 else:
541 cls.method_resources.insert(0, resource)
542 client.wait_for_cgsnapshot_status(cgsnapshot["id"], "available")
543 return cgsnapshot
544
545 @classmethod
Yogeshbdb88102015-09-29 23:41:02 -0400546 def get_availability_zones(cls, client=None):
547 """List the availability zones for "manila-share" services
548
549 that are currently in "up" state.
550 """
551 client = client or cls.shares_v2_client
552 cls.services = client.list_services()
553 zones = [service['zone'] for service in cls.services if
554 service['binary'] == "manila-share" and
555 service['state'] == 'up']
556 return zones
557
Yogesh1f931ff2015-09-29 23:41:02 -0400558 def get_pools_for_replication_domain(self):
559 # Get the list of pools for the replication domain
560 pools = self.admin_client.list_pools(detail=True)['pools']
561 instance_host = self.shares[0]['host']
562 host_pool = [p for p in pools if p['name'] == instance_host][0]
563 rep_domain = host_pool['capabilities']['replication_domain']
564 pools_in_rep_domain = [p for p in pools if p['capabilities'][
565 'replication_domain'] == rep_domain]
566 return rep_domain, pools_in_rep_domain
567
Yogeshbdb88102015-09-29 23:41:02 -0400568 @classmethod
569 def create_share_replica(cls, share_id, availability_zone, client=None,
570 cleanup_in_class=False, cleanup=True):
571 client = client or cls.shares_v2_client
572 replica = client.create_share_replica(share_id, availability_zone)
573 resource = {
574 "type": "share_replica",
575 "id": replica["id"],
576 "client": client,
577 "share_id": share_id,
578 }
579 # NOTE(Yogi1): Cleanup needs to be disabled during promotion tests.
580 if cleanup:
581 if cleanup_in_class:
582 cls.class_resources.insert(0, resource)
583 else:
584 cls.method_resources.insert(0, resource)
585 client.wait_for_share_replica_status(
586 replica["id"], constants.STATUS_AVAILABLE)
587 return replica
588
589 @classmethod
590 def delete_share_replica(cls, replica_id, client=None):
591 client = client or cls.shares_v2_client
Yogesh1f931ff2015-09-29 23:41:02 -0400592 try:
593 client.delete_share_replica(replica_id)
594 client.wait_for_resource_deletion(replica_id=replica_id)
595 except exceptions.NotFound:
596 pass
Yogeshbdb88102015-09-29 23:41:02 -0400597
598 @classmethod
599 def promote_share_replica(cls, replica_id, client=None):
600 client = client or cls.shares_v2_client
601 replica = client.promote_share_replica(replica_id)
602 client.wait_for_share_replica_status(
603 replica["id"],
604 constants.REPLICATION_STATE_ACTIVE,
605 status_attr="replica_state")
606 return replica
607
608 @classmethod
Marc Koderer0abc93b2015-07-15 09:18:35 +0200609 def create_share_network(cls, client=None,
610 cleanup_in_class=False, **kwargs):
611 if client is None:
612 client = cls.shares_client
613 share_network = client.create_share_network(**kwargs)
614 resource = {
615 "type": "share_network",
616 "id": share_network["id"],
617 "client": client,
618 }
619 if cleanup_in_class:
620 cls.class_resources.insert(0, resource)
621 else:
622 cls.method_resources.insert(0, resource)
623 return share_network
624
625 @classmethod
626 def create_security_service(cls, ss_type="ldap", client=None,
627 cleanup_in_class=False, **kwargs):
628 if client is None:
629 client = cls.shares_client
630 security_service = client.create_security_service(ss_type, **kwargs)
631 resource = {
632 "type": "security_service",
633 "id": security_service["id"],
634 "client": client,
635 }
636 if cleanup_in_class:
637 cls.class_resources.insert(0, resource)
638 else:
639 cls.method_resources.insert(0, resource)
640 return security_service
641
642 @classmethod
643 def create_share_type(cls, name, is_public=True, client=None,
644 cleanup_in_class=True, **kwargs):
645 if client is None:
Valeriy Ponomaryova14c2252015-10-29 13:34:32 +0200646 client = cls.shares_v2_client
Marc Koderer0abc93b2015-07-15 09:18:35 +0200647 share_type = client.create_share_type(name, is_public, **kwargs)
648 resource = {
649 "type": "share_type",
650 "id": share_type["share_type"]["id"],
651 "client": client,
652 }
653 if cleanup_in_class:
654 cls.class_resources.insert(0, resource)
655 else:
656 cls.method_resources.insert(0, resource)
657 return share_type
658
659 @staticmethod
660 def add_required_extra_specs_to_dict(extra_specs=None):
Valeriy Ponomaryovad55dc52015-09-23 13:54:00 +0300661 dhss = six.text_type(CONF.share.multitenancy_enabled)
662 snapshot_support = six.text_type(
663 CONF.share.capability_snapshot_support)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200664 required = {
Valeriy Ponomaryovad55dc52015-09-23 13:54:00 +0300665 "driver_handles_share_servers": dhss,
666 "snapshot_support": snapshot_support,
Marc Koderer0abc93b2015-07-15 09:18:35 +0200667 }
668 if extra_specs:
669 required.update(extra_specs)
670 return required
671
672 @classmethod
673 def clear_isolated_creds(cls, creds=None):
674 if creds is None:
675 creds = cls.method_isolated_creds
676 for ic in creds:
677 if "deleted" not in ic.keys():
678 ic["deleted"] = False
679 if not ic["deleted"]:
680 with handle_cleanup_exceptions():
681 ic["method"]()
682 ic["deleted"] = True
683
684 @classmethod
Yogesh1f931ff2015-09-29 23:41:02 -0400685 def clear_share_replicas(cls, share_id, client=None):
686 client = client or cls.shares_v2_client
687 share_replicas = client.list_share_replicas(
688 share_id=share_id)
689
690 for replica in share_replicas:
691 try:
692 cls.delete_share_replica(replica['id'])
693 except exceptions.BadRequest:
694 # Ignore the exception due to deletion of last active replica
695 pass
696
697 @classmethod
Marc Koderer0abc93b2015-07-15 09:18:35 +0200698 def clear_resources(cls, resources=None):
699 """Deletes resources, that were created in test suites.
700
701 This method tries to remove resources from resource list,
702 if it is not found, assumed it was deleted in test itself.
703 It is expected, that all resources were added as LIFO
704 due to restriction of deletion resources, that is in the chain.
705
706 :param resources: dict with keys 'type','id','client' and 'deleted'
707 """
708
709 if resources is None:
710 resources = cls.method_resources
711 for res in resources:
712 if "deleted" not in res.keys():
713 res["deleted"] = False
714 if "client" not in res.keys():
715 res["client"] = cls.shares_client
716 if not(res["deleted"]):
717 res_id = res['id']
718 client = res["client"]
719 with handle_cleanup_exceptions():
720 if res["type"] is "share":
Yogesh1f931ff2015-09-29 23:41:02 -0400721 cls.clear_share_replicas(res_id)
Andrew Kerrbf31e912015-07-29 10:39:38 -0400722 cg_id = res.get('consistency_group_id')
723 if cg_id:
724 params = {'consistency_group_id': cg_id}
Clinton Knighte5c8f092015-08-27 15:00:23 -0400725 client.delete_share(res_id, params=params)
726 else:
727 client.delete_share(res_id)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200728 client.wait_for_resource_deletion(share_id=res_id)
729 elif res["type"] is "snapshot":
730 client.delete_snapshot(res_id)
731 client.wait_for_resource_deletion(snapshot_id=res_id)
732 elif res["type"] is "share_network":
733 client.delete_share_network(res_id)
734 client.wait_for_resource_deletion(sn_id=res_id)
735 elif res["type"] is "security_service":
736 client.delete_security_service(res_id)
737 client.wait_for_resource_deletion(ss_id=res_id)
738 elif res["type"] is "share_type":
739 client.delete_share_type(res_id)
740 client.wait_for_resource_deletion(st_id=res_id)
Andrew Kerrbf31e912015-07-29 10:39:38 -0400741 elif res["type"] is "consistency_group":
742 client.delete_consistency_group(res_id)
743 client.wait_for_resource_deletion(cg_id=res_id)
744 elif res["type"] is "cgsnapshot":
745 client.delete_cgsnapshot(res_id)
746 client.wait_for_resource_deletion(cgsnapshot_id=res_id)
Yogeshbdb88102015-09-29 23:41:02 -0400747 elif res["type"] is "share_replica":
748 client.delete_share_replica(res_id)
749 client.wait_for_resource_deletion(replica_id=res_id)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200750 else:
huayue97bacbf2016-01-04 09:57:39 +0800751 LOG.warning("Provided unsupported resource type for "
752 "cleanup '%s'. Skipping." % res["type"])
Marc Koderer0abc93b2015-07-15 09:18:35 +0200753 res["deleted"] = True
754
755 @classmethod
756 def generate_share_network_data(self):
757 data = {
758 "name": data_utils.rand_name("sn-name"),
759 "description": data_utils.rand_name("sn-desc"),
760 "neutron_net_id": data_utils.rand_name("net-id"),
761 "neutron_subnet_id": data_utils.rand_name("subnet-id"),
762 }
763 return data
764
765 @classmethod
766 def generate_security_service_data(self):
767 data = {
768 "name": data_utils.rand_name("ss-name"),
769 "description": data_utils.rand_name("ss-desc"),
Valeriy Ponomaryovfcde7712015-12-14 18:06:13 +0200770 "dns_ip": utils.rand_ip(),
771 "server": utils.rand_ip(),
Marc Koderer0abc93b2015-07-15 09:18:35 +0200772 "domain": data_utils.rand_name("ss-domain"),
773 "user": data_utils.rand_name("ss-user"),
774 "password": data_utils.rand_name("ss-password"),
775 }
776 return data
777
778 # Useful assertions
779 def assertDictMatch(self, d1, d2, approx_equal=False, tolerance=0.001):
780 """Assert two dicts are equivalent.
781
782 This is a 'deep' match in the sense that it handles nested
783 dictionaries appropriately.
784
785 NOTE:
786
787 If you don't care (or don't know) a given value, you can specify
788 the string DONTCARE as the value. This will cause that dict-item
789 to be skipped.
790
791 """
792 def raise_assertion(msg):
793 d1str = str(d1)
794 d2str = str(d2)
795 base_msg = ('Dictionaries do not match. %(msg)s d1: %(d1str)s '
796 'd2: %(d2str)s' %
797 {"msg": msg, "d1str": d1str, "d2str": d2str})
798 raise AssertionError(base_msg)
799
800 d1keys = set(d1.keys())
801 d2keys = set(d2.keys())
802 if d1keys != d2keys:
803 d1only = d1keys - d2keys
804 d2only = d2keys - d1keys
805 raise_assertion('Keys in d1 and not d2: %(d1only)s. '
806 'Keys in d2 and not d1: %(d2only)s' %
807 {"d1only": d1only, "d2only": d2only})
808
809 for key in d1keys:
810 d1value = d1[key]
811 d2value = d2[key]
812 try:
813 error = abs(float(d1value) - float(d2value))
814 within_tolerance = error <= tolerance
815 except (ValueError, TypeError):
daiki kato6914b1a2016-03-16 17:16:57 +0900816 # If both values aren't convertible to float, just ignore
Marc Koderer0abc93b2015-07-15 09:18:35 +0200817 # ValueError if arg is a str, TypeError if it's something else
818 # (like None)
819 within_tolerance = False
820
821 if hasattr(d1value, 'keys') and hasattr(d2value, 'keys'):
822 self.assertDictMatch(d1value, d2value)
823 elif 'DONTCARE' in (d1value, d2value):
824 continue
825 elif approx_equal and within_tolerance:
826 continue
827 elif d1value != d2value:
828 raise_assertion("d1['%(key)s']=%(d1value)s != "
829 "d2['%(key)s']=%(d2value)s" %
830 {
831 "key": key,
832 "d1value": d1value,
833 "d2value": d2value
834 })
835
836
837class BaseSharesAltTest(BaseSharesTest):
838 """Base test case class for all Shares Alt API tests."""
839
840 @classmethod
841 def resource_setup(cls):
842 cls.username = CONF.identity.alt_username
843 cls.password = CONF.identity.alt_password
Valeriy Ponomaryov1950cb82016-04-11 14:02:29 +0300844 cls.project_name = CONF.identity.alt_project_name
845 cls.verify_nonempty(cls.username, cls.password, cls.project_name)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200846 cls.os = clients.AltManager()
847 alt_share_network_id = CONF.share.alt_share_network_id
848 cls.os.shares_client.share_network_id = alt_share_network_id
Clinton Knighte5c8f092015-08-27 15:00:23 -0400849 cls.os.shares_v2_client.share_network_id = alt_share_network_id
Marc Koderer0abc93b2015-07-15 09:18:35 +0200850 super(BaseSharesAltTest, cls).resource_setup()
851
852
853class BaseSharesAdminTest(BaseSharesTest):
854 """Base test case class for all Shares Admin API tests."""
855
856 @classmethod
857 def resource_setup(cls):
Sam Wanb5047aa2015-10-08 05:37:43 -0400858 if hasattr(CONF.identity, 'admin_username'):
859 cls.username = CONF.identity.admin_username
860 cls.password = CONF.identity.admin_password
Valeriy Ponomaryov1950cb82016-04-11 14:02:29 +0300861 cls.project_name = CONF.identity.admin_project_name
Sam Wanb5047aa2015-10-08 05:37:43 -0400862 else:
863 cls.username = CONF.auth.admin_username
864 cls.password = CONF.auth.admin_password
Valeriy Ponomaryov1950cb82016-04-11 14:02:29 +0300865 cls.project_name = CONF.auth.admin_project_name
866 cls.verify_nonempty(cls.username, cls.password, cls.project_name)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200867 cls.os = clients.AdminManager()
868 admin_share_network_id = CONF.share.admin_share_network_id
869 cls.os.shares_client.share_network_id = admin_share_network_id
Clinton Knighte5c8f092015-08-27 15:00:23 -0400870 cls.os.shares_v2_client.share_network_id = admin_share_network_id
Marc Koderer0abc93b2015-07-15 09:18:35 +0200871 super(BaseSharesAdminTest, cls).resource_setup()