blob: 0eae2add03d34ca9685e0104f572b1c847f72951 [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
18import traceback
19
20from oslo_concurrency import lockutils
21from oslo_log import log
22import six
Sam Wanc7b7f1f2015-11-25 00:22:28 -050023from tempest.common import credentials_factory as common_creds
Valeriy Ponomaryov48a2bd72015-11-05 13:22:44 +020024from tempest.common import dynamic_creds
25from tempest import config
Ben Swartzlander1c4ff522016-03-02 22:16:23 -050026from tempest.lib.common.utils import data_utils
27from tempest.lib import exceptions
Valeriy Ponomaryov48a2bd72015-11-05 13:22:44 +020028from tempest import test
Marc Koderer0abc93b2015-07-15 09:18:35 +020029
30from manila_tempest_tests import clients_share as clients
31from manila_tempest_tests import share_exceptions
Valeriy Ponomaryovfcde7712015-12-14 18:06:13 +020032from manila_tempest_tests import utils
Marc Koderer0abc93b2015-07-15 09:18:35 +020033
34CONF = config.CONF
35LOG = log.getLogger(__name__)
36
37
38class handle_cleanup_exceptions(object):
39 """Handle exceptions raised with cleanup operations.
40
41 Always suppress errors when exceptions.NotFound or exceptions.Forbidden
42 are raised.
43 Suppress all other exceptions only in case config opt
44 'suppress_errors_in_cleanup' in config group 'share' is True.
45 """
46
47 def __enter__(self):
48 return self
49
50 def __exit__(self, exc_type, exc_value, exc_traceback):
51 if not (isinstance(exc_value,
52 (exceptions.NotFound, exceptions.Forbidden)) or
53 CONF.share.suppress_errors_in_cleanup):
54 return False # Do not suppress error if any
55 if exc_traceback:
56 LOG.error("Suppressed cleanup error in Manila: "
57 "\n%s" % traceback.format_exc())
58 return True # Suppress error if any
59
60
61def network_synchronized(f):
62
63 def wrapped_func(self, *args, **kwargs):
64 with_isolated_creds = True if len(args) > 2 else False
65 no_lock_required = kwargs.get(
66 "isolated_creds_client", with_isolated_creds)
67 if no_lock_required:
68 # Usage of not reusable network. No need in lock.
69 return f(self, *args, **kwargs)
70
71 # Use lock assuming reusage of common network.
72 @lockutils.synchronized("manila_network_lock", external=True)
73 def source_func(self, *args, **kwargs):
74 return f(self, *args, **kwargs)
75
76 return source_func(self, *args, **kwargs)
77
78 return wrapped_func
79
80
Valeriy Ponomaryovfcde7712015-12-14 18:06:13 +020081skip_if_microversion_not_supported = utils.skip_if_microversion_not_supported
Xing Yang69b00b52015-11-22 16:10:44 -050082skip_if_microversion_lt = utils.skip_if_microversion_lt
Valeriy Ponomaryova14c2252015-10-29 13:34:32 +020083
84
Marc Koderer0abc93b2015-07-15 09:18:35 +020085class BaseSharesTest(test.BaseTestCase):
86 """Base test case class for all Manila API tests."""
87
88 force_tenant_isolation = False
John Spray061b1452015-11-18 13:15:32 +000089 protocols = ["nfs", "cifs", "glusterfs", "hdfs", "cephfs"]
Marc Koderer0abc93b2015-07-15 09:18:35 +020090
91 # Will be cleaned up in resource_cleanup
92 class_resources = []
93
94 # Will be cleaned up in tearDown method
95 method_resources = []
96
97 # Will be cleaned up in resource_cleanup
98 class_isolated_creds = []
99
100 # Will be cleaned up in tearDown method
101 method_isolated_creds = []
102
Valeriy Ponomaryova14c2252015-10-29 13:34:32 +0200103 def skip_if_microversion_not_supported(self, microversion):
Valeriy Ponomaryovfcde7712015-12-14 18:06:13 +0200104 if not utils.is_microversion_supported(microversion):
Valeriy Ponomaryova14c2252015-10-29 13:34:32 +0200105 raise self.skipException(
106 "Microversion '%s' is not supported." % microversion)
107
Xing Yang69b00b52015-11-22 16:10:44 -0500108 def skip_if_microversion_lt(self, microversion):
109 if utils.is_microversion_lt(CONF.share.max_api_microversion,
110 microversion):
111 raise self.skipException(
112 "Microversion must be greater than or equal to '%s'." %
113 microversion)
114
Marc Koderer0abc93b2015-07-15 09:18:35 +0200115 @classmethod
116 def get_client_with_isolated_creds(cls,
117 name=None,
118 type_of_creds="admin",
Clinton Knighte5c8f092015-08-27 15:00:23 -0400119 cleanup_in_class=False,
120 client_version='1'):
Marc Koderer0abc93b2015-07-15 09:18:35 +0200121 """Creates isolated creds.
122
123 :param name: name, will be used for naming ic and related stuff
124 :param type_of_creds: admin, alt or primary
125 :param cleanup_in_class: defines place where to delete
126 :returns: SharesClient -- shares client with isolated creds.
127 :returns: To client added dict attr 'creds' with
128 :returns: key elements 'tenant' and 'user'.
129 """
130 if name is None:
131 # Get name of test method
132 name = inspect.stack()[1][3]
133 if len(name) > 32:
134 name = name[0:32]
135
136 # Choose type of isolated creds
Valeriy Ponomaryov48a2bd72015-11-05 13:22:44 +0200137 ic = dynamic_creds.DynamicCredentialProvider(
138 identity_version=CONF.identity.auth_version,
139 name=name,
Sam Wanc7b7f1f2015-11-25 00:22:28 -0500140 admin_role=CONF.identity.admin_role,
141 admin_creds=common_creds.get_configured_credentials(
142 'identity_admin'))
Marc Koderer0abc93b2015-07-15 09:18:35 +0200143 if "admin" in type_of_creds:
144 creds = ic.get_admin_creds()
145 elif "alt" in type_of_creds:
146 creds = ic.get_alt_creds()
147 else:
148 creds = ic.self.get_credentials(type_of_creds)
149 ic.type_of_creds = type_of_creds
150
151 # create client with isolated creds
152 os = clients.Manager(credentials=creds)
Clinton Knighte5c8f092015-08-27 15:00:23 -0400153 if client_version == '1':
154 client = os.shares_client
155 elif client_version == '2':
156 client = os.shares_v2_client
Marc Koderer0abc93b2015-07-15 09:18:35 +0200157
158 # Set place where will be deleted isolated creds
159 ic_res = {
Valeriy Ponomaryov48a2bd72015-11-05 13:22:44 +0200160 "method": ic.clear_creds,
Marc Koderer0abc93b2015-07-15 09:18:35 +0200161 "deleted": False,
162 }
163 if cleanup_in_class:
164 cls.class_isolated_creds.insert(0, ic_res)
165 else:
166 cls.method_isolated_creds.insert(0, ic_res)
167
168 # Provide share network
169 if CONF.share.multitenancy_enabled:
170 if not CONF.service_available.neutron:
171 raise cls.skipException("Neutron support is required")
Valeriy Ponomaryov48a2bd72015-11-05 13:22:44 +0200172 nc = os.networks_client
Marc Koderer0abc93b2015-07-15 09:18:35 +0200173 share_network_id = cls.provide_share_network(client, nc, ic)
174 client.share_network_id = share_network_id
175 resource = {
176 "type": "share_network",
177 "id": client.share_network_id,
178 "client": client,
179 }
180 if cleanup_in_class:
181 cls.class_resources.insert(0, resource)
182 else:
183 cls.method_resources.insert(0, resource)
184 return client
185
186 @classmethod
187 def verify_nonempty(cls, *args):
188 if not all(args):
189 msg = "Missing API credentials in configuration."
190 raise cls.skipException(msg)
191
192 @classmethod
193 def resource_setup(cls):
194 if not (any(p in CONF.share.enable_protocols
195 for p in cls.protocols) and
196 CONF.service_available.manila):
197 skip_msg = "Manila is disabled"
198 raise cls.skipException(skip_msg)
199 super(BaseSharesTest, cls).resource_setup()
200 if not hasattr(cls, "os"):
201 cls.username = CONF.identity.username
202 cls.password = CONF.identity.password
203 cls.tenant_name = CONF.identity.tenant_name
204 cls.verify_nonempty(cls.username, cls.password, cls.tenant_name)
205 cls.os = clients.Manager()
206 if CONF.share.multitenancy_enabled:
207 if not CONF.service_available.neutron:
208 raise cls.skipException("Neutron support is required")
209 sc = cls.os.shares_client
Valeriy Ponomaryov48a2bd72015-11-05 13:22:44 +0200210 nc = cls.os.networks_client
Marc Koderer0abc93b2015-07-15 09:18:35 +0200211 share_network_id = cls.provide_share_network(sc, nc)
212 cls.os.shares_client.share_network_id = share_network_id
Clinton Knighte5c8f092015-08-27 15:00:23 -0400213 cls.os.shares_v2_client.share_network_id = share_network_id
Marc Koderer0abc93b2015-07-15 09:18:35 +0200214 cls.shares_client = cls.os.shares_client
Clinton Knighte5c8f092015-08-27 15:00:23 -0400215 cls.shares_v2_client = cls.os.shares_v2_client
Marc Koderer0abc93b2015-07-15 09:18:35 +0200216
217 def setUp(self):
218 super(BaseSharesTest, self).setUp()
Marc Koderer0abc93b2015-07-15 09:18:35 +0200219 self.addCleanup(self.clear_isolated_creds)
Valeriy Ponomaryovdd162cb2016-01-20 19:09:49 +0200220 self.addCleanup(self.clear_resources)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200221
222 @classmethod
223 def resource_cleanup(cls):
224 super(BaseSharesTest, cls).resource_cleanup()
225 cls.clear_resources(cls.class_resources)
226 cls.clear_isolated_creds(cls.class_isolated_creds)
227
228 @classmethod
229 @network_synchronized
Valeriy Ponomaryov48a2bd72015-11-05 13:22:44 +0200230 def provide_share_network(cls, shares_client, networks_client,
Marc Koderer0abc93b2015-07-15 09:18:35 +0200231 isolated_creds_client=None):
232 """Used for finding/creating share network for multitenant driver.
233
234 This method creates/gets entity share-network for one tenant. This
235 share-network will be used for creation of service vm.
236
237 :param shares_client: shares client, which requires share-network
Valeriy Ponomaryov48a2bd72015-11-05 13:22:44 +0200238 :param networks_client: network client from same tenant as shares
239 :param isolated_creds_client: DynamicCredentialProvider instance
Marc Koderer0abc93b2015-07-15 09:18:35 +0200240 If provided, then its networking will be used if needed.
241 If not provided, then common network will be used if needed.
242 :returns: str -- share network id for shares_client tenant
243 :returns: None -- if single-tenant driver used
244 """
245
246 sc = shares_client
247
248 if not CONF.share.multitenancy_enabled:
249 # Assumed usage of a single-tenant driver
250 share_network_id = None
251 elif sc.share_network_id:
252 # Share-network already exists, use it
253 share_network_id = sc.share_network_id
254 else:
255 net_id = subnet_id = share_network_id = None
256
257 if not isolated_creds_client:
258 # Search for networks, created in previous runs
259 search_word = "reusable"
260 sn_name = "autogenerated_by_tempest_%s" % search_word
261 service_net_name = "share-service"
Valeriy Ponomaryov48a2bd72015-11-05 13:22:44 +0200262 networks = networks_client.list_networks()
Marc Koderer0abc93b2015-07-15 09:18:35 +0200263 if "networks" in networks.keys():
264 networks = networks["networks"]
265 for network in networks:
266 if (service_net_name in network["name"] and
267 sc.tenant_id == network['tenant_id']):
268 net_id = network["id"]
269 if len(network["subnets"]) > 0:
270 subnet_id = network["subnets"][0]
271 break
272
273 # Create suitable network
274 if (net_id is None or subnet_id is None):
Valeriy Ponomaryov48a2bd72015-11-05 13:22:44 +0200275 ic = dynamic_creds.DynamicCredentialProvider(
276 identity_version=CONF.identity.auth_version,
277 name=service_net_name,
278 admin_role=CONF.identity.admin_role,
Sam Wanc7b7f1f2015-11-25 00:22:28 -0500279 admin_creds=common_creds.get_configured_credentials(
280 'identity_admin'))
Marc Koderer0abc93b2015-07-15 09:18:35 +0200281 net_data = ic._create_network_resources(sc.tenant_id)
282 network, subnet, router = net_data
283 net_id = network["id"]
284 subnet_id = subnet["id"]
285
286 # Try get suitable share-network
287 share_networks = sc.list_share_networks_with_detail()
288 for sn in share_networks:
289 if (net_id == sn["neutron_net_id"] and
290 subnet_id == sn["neutron_subnet_id"] and
291 sn["name"] and search_word in sn["name"]):
292 share_network_id = sn["id"]
293 break
294 else:
295 sn_name = "autogenerated_by_tempest_for_isolated_creds"
296 # Use precreated network and subnet from isolated creds
297 net_id = isolated_creds_client.get_credentials(
298 isolated_creds_client.type_of_creds).network['id']
299 subnet_id = isolated_creds_client.get_credentials(
300 isolated_creds_client.type_of_creds).subnet['id']
301
302 # Create suitable share-network
303 if share_network_id is None:
304 sn_desc = "This share-network was created by tempest"
305 sn = sc.create_share_network(name=sn_name,
306 description=sn_desc,
307 neutron_net_id=net_id,
308 neutron_subnet_id=subnet_id)
309 share_network_id = sn["id"]
310
311 return share_network_id
312
313 @classmethod
314 def _create_share(cls, share_protocol=None, size=1, name=None,
315 snapshot_id=None, description=None, metadata=None,
316 share_network_id=None, share_type_id=None,
Andrew Kerrbf31e912015-07-29 10:39:38 -0400317 consistency_group_id=None, client=None,
Clinton Knighte5c8f092015-08-27 15:00:23 -0400318 cleanup_in_class=True, is_public=False, **kwargs):
Valeriy Ponomaryov1aaa72d2015-09-08 12:59:41 +0300319 client = client or cls.shares_v2_client
Marc Koderer0abc93b2015-07-15 09:18:35 +0200320 description = description or "Tempest's share"
321 share_network_id = share_network_id or client.share_network_id or None
322 metadata = metadata or {}
Clinton Knighte5c8f092015-08-27 15:00:23 -0400323 kwargs.update({
Marc Koderer0abc93b2015-07-15 09:18:35 +0200324 'share_protocol': share_protocol,
325 'size': size,
326 'name': name,
327 'snapshot_id': snapshot_id,
328 'description': description,
329 'metadata': metadata,
330 'share_network_id': share_network_id,
331 'share_type_id': share_type_id,
332 'is_public': is_public,
Clinton Knighte5c8f092015-08-27 15:00:23 -0400333 })
Andrew Kerrbf31e912015-07-29 10:39:38 -0400334 if consistency_group_id:
335 kwargs['consistency_group_id'] = consistency_group_id
336
Marc Koderer0abc93b2015-07-15 09:18:35 +0200337 share = client.create_share(**kwargs)
Andrew Kerrbf31e912015-07-29 10:39:38 -0400338 resource = {"type": "share", "id": share["id"], "client": client,
339 "consistency_group_id": consistency_group_id}
Marc Koderer0abc93b2015-07-15 09:18:35 +0200340 cleanup_list = (cls.class_resources if cleanup_in_class else
341 cls.method_resources)
342 cleanup_list.insert(0, resource)
343 return share
344
345 @classmethod
Rodrigo Barbierie3305122016-02-03 14:32:24 -0200346 def migrate_share(cls, share_id, dest_host, client=None, notify=True,
347 wait_for_status='migration_success', **kwargs):
Clinton Knighte5c8f092015-08-27 15:00:23 -0400348 client = client or cls.shares_v2_client
Rodrigo Barbierie3305122016-02-03 14:32:24 -0200349 client.migrate_share(share_id, dest_host, notify, **kwargs)
350 share = client.wait_for_migration_status(
351 share_id, dest_host, wait_for_status,
352 version=kwargs.get('version'))
353 return share
354
355 @classmethod
356 def migration_complete(cls, share_id, dest_host, client=None, **kwargs):
357 client = client or cls.shares_v2_client
358 client.migration_complete(share_id, **kwargs)
359 share = client.wait_for_migration_status(
360 share_id, dest_host, 'migration_success',
361 version=kwargs.get('version'))
Rodrigo Barbierib7137ad2015-09-06 22:53:16 -0300362 return share
363
364 @classmethod
Marc Koderer0abc93b2015-07-15 09:18:35 +0200365 def create_share(cls, *args, **kwargs):
366 """Create one share and wait for available state. Retry if allowed."""
367 result = cls.create_shares([{"args": args, "kwargs": kwargs}])
368 return result[0]
369
370 @classmethod
371 def create_shares(cls, share_data_list):
372 """Creates several shares in parallel with retries.
373
374 Use this method when you want to create more than one share at same
375 time. Especially if config option 'share.share_creation_retry_number'
376 has value more than zero (0).
377 All shares will be expected to have 'available' status with or without
378 recreation else error will be raised.
379
380 :param share_data_list: list -- list of dictionaries with 'args' and
381 'kwargs' for '_create_share' method of this base class.
382 example of data:
383 share_data_list=[{'args': ['quuz'], 'kwargs': {'foo': 'bar'}}}]
384 :returns: list -- list of shares created using provided data.
385 """
386
387 data = [copy.deepcopy(d) for d in share_data_list]
388 for d in data:
389 if not isinstance(d, dict):
390 raise exceptions.TempestException(
391 "Expected 'dict', got '%s'" % type(d))
392 if "args" not in d:
393 d["args"] = []
394 if "kwargs" not in d:
395 d["kwargs"] = {}
396 if len(d) > 2:
397 raise exceptions.TempestException(
398 "Expected only 'args' and 'kwargs' keys. "
399 "Provided %s" % list(d))
400 d["kwargs"]["client"] = d["kwargs"].get(
Valeriy Ponomaryov1aaa72d2015-09-08 12:59:41 +0300401 "client", cls.shares_v2_client)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200402 d["share"] = cls._create_share(*d["args"], **d["kwargs"])
403 d["cnt"] = 0
404 d["available"] = False
405
406 while not all(d["available"] for d in data):
407 for d in data:
408 if d["available"]:
409 continue
410 try:
411 d["kwargs"]["client"].wait_for_share_status(
412 d["share"]["id"], "available")
413 d["available"] = True
414 except (share_exceptions.ShareBuildErrorException,
415 exceptions.TimeoutException) as e:
416 if CONF.share.share_creation_retry_number > d["cnt"]:
417 d["cnt"] += 1
418 msg = ("Share '%s' failed to be built. "
419 "Trying create another." % d["share"]["id"])
420 LOG.error(msg)
421 LOG.error(e)
422 d["share"] = cls._create_share(
423 *d["args"], **d["kwargs"])
424 else:
425 raise e
426
427 return [d["share"] for d in data]
428
429 @classmethod
Andrew Kerrbf31e912015-07-29 10:39:38 -0400430 def create_consistency_group(cls, client=None, cleanup_in_class=True,
431 share_network_id=None, **kwargs):
Clinton Knighte5c8f092015-08-27 15:00:23 -0400432 client = client or cls.shares_v2_client
Andrew Kerrbf31e912015-07-29 10:39:38 -0400433 kwargs['share_network_id'] = (share_network_id or
434 client.share_network_id or None)
435 consistency_group = client.create_consistency_group(**kwargs)
436 resource = {
437 "type": "consistency_group",
438 "id": consistency_group["id"],
439 "client": client}
440 if cleanup_in_class:
441 cls.class_resources.insert(0, resource)
442 else:
443 cls.method_resources.insert(0, resource)
444
445 if kwargs.get('source_cgsnapshot_id'):
446 new_cg_shares = client.list_shares(
447 detailed=True,
448 params={'consistency_group_id': consistency_group['id']})
449
450 for share in new_cg_shares:
451 resource = {"type": "share",
452 "id": share["id"],
453 "client": client,
454 "consistency_group_id": share.get(
455 'consistency_group_id')}
456 if cleanup_in_class:
457 cls.class_resources.insert(0, resource)
458 else:
459 cls.method_resources.insert(0, resource)
460
461 client.wait_for_consistency_group_status(consistency_group['id'],
462 'available')
463 return consistency_group
464
465 @classmethod
Marc Koderer0abc93b2015-07-15 09:18:35 +0200466 def create_snapshot_wait_for_active(cls, share_id, name=None,
467 description=None, force=False,
468 client=None, cleanup_in_class=True):
469 if client is None:
470 client = cls.shares_client
471 if description is None:
472 description = "Tempest's snapshot"
473 snapshot = client.create_snapshot(share_id, name, description, force)
474 resource = {
475 "type": "snapshot",
476 "id": snapshot["id"],
477 "client": client,
478 }
479 if cleanup_in_class:
480 cls.class_resources.insert(0, resource)
481 else:
482 cls.method_resources.insert(0, resource)
483 client.wait_for_snapshot_status(snapshot["id"], "available")
484 return snapshot
485
486 @classmethod
Andrew Kerrbf31e912015-07-29 10:39:38 -0400487 def create_cgsnapshot_wait_for_active(cls, consistency_group_id,
488 name=None, description=None,
Clinton Knighte5c8f092015-08-27 15:00:23 -0400489 client=None, cleanup_in_class=True,
490 **kwargs):
491 client = client or cls.shares_v2_client
Andrew Kerrbf31e912015-07-29 10:39:38 -0400492 if description is None:
493 description = "Tempest's cgsnapshot"
Clinton Knighte5c8f092015-08-27 15:00:23 -0400494 cgsnapshot = client.create_cgsnapshot(consistency_group_id,
495 name=name,
496 description=description,
497 **kwargs)
Andrew Kerrbf31e912015-07-29 10:39:38 -0400498 resource = {
499 "type": "cgsnapshot",
500 "id": cgsnapshot["id"],
501 "client": client,
502 }
503 if cleanup_in_class:
504 cls.class_resources.insert(0, resource)
505 else:
506 cls.method_resources.insert(0, resource)
507 client.wait_for_cgsnapshot_status(cgsnapshot["id"], "available")
508 return cgsnapshot
509
510 @classmethod
Marc Koderer0abc93b2015-07-15 09:18:35 +0200511 def create_share_network(cls, client=None,
512 cleanup_in_class=False, **kwargs):
513 if client is None:
514 client = cls.shares_client
515 share_network = client.create_share_network(**kwargs)
516 resource = {
517 "type": "share_network",
518 "id": share_network["id"],
519 "client": client,
520 }
521 if cleanup_in_class:
522 cls.class_resources.insert(0, resource)
523 else:
524 cls.method_resources.insert(0, resource)
525 return share_network
526
527 @classmethod
528 def create_security_service(cls, ss_type="ldap", client=None,
529 cleanup_in_class=False, **kwargs):
530 if client is None:
531 client = cls.shares_client
532 security_service = client.create_security_service(ss_type, **kwargs)
533 resource = {
534 "type": "security_service",
535 "id": security_service["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 return security_service
543
544 @classmethod
545 def create_share_type(cls, name, is_public=True, client=None,
546 cleanup_in_class=True, **kwargs):
547 if client is None:
Valeriy Ponomaryova14c2252015-10-29 13:34:32 +0200548 client = cls.shares_v2_client
Marc Koderer0abc93b2015-07-15 09:18:35 +0200549 share_type = client.create_share_type(name, is_public, **kwargs)
550 resource = {
551 "type": "share_type",
552 "id": share_type["share_type"]["id"],
553 "client": client,
554 }
555 if cleanup_in_class:
556 cls.class_resources.insert(0, resource)
557 else:
558 cls.method_resources.insert(0, resource)
559 return share_type
560
561 @staticmethod
562 def add_required_extra_specs_to_dict(extra_specs=None):
Valeriy Ponomaryovad55dc52015-09-23 13:54:00 +0300563 dhss = six.text_type(CONF.share.multitenancy_enabled)
564 snapshot_support = six.text_type(
565 CONF.share.capability_snapshot_support)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200566 required = {
Valeriy Ponomaryovad55dc52015-09-23 13:54:00 +0300567 "driver_handles_share_servers": dhss,
568 "snapshot_support": snapshot_support,
Marc Koderer0abc93b2015-07-15 09:18:35 +0200569 }
570 if extra_specs:
571 required.update(extra_specs)
572 return required
573
574 @classmethod
575 def clear_isolated_creds(cls, creds=None):
576 if creds is None:
577 creds = cls.method_isolated_creds
578 for ic in creds:
579 if "deleted" not in ic.keys():
580 ic["deleted"] = False
581 if not ic["deleted"]:
582 with handle_cleanup_exceptions():
583 ic["method"]()
584 ic["deleted"] = True
585
586 @classmethod
587 def clear_resources(cls, resources=None):
588 """Deletes resources, that were created in test suites.
589
590 This method tries to remove resources from resource list,
591 if it is not found, assumed it was deleted in test itself.
592 It is expected, that all resources were added as LIFO
593 due to restriction of deletion resources, that is in the chain.
594
595 :param resources: dict with keys 'type','id','client' and 'deleted'
596 """
597
598 if resources is None:
599 resources = cls.method_resources
600 for res in resources:
601 if "deleted" not in res.keys():
602 res["deleted"] = False
603 if "client" not in res.keys():
604 res["client"] = cls.shares_client
605 if not(res["deleted"]):
606 res_id = res['id']
607 client = res["client"]
608 with handle_cleanup_exceptions():
609 if res["type"] is "share":
Andrew Kerrbf31e912015-07-29 10:39:38 -0400610 cg_id = res.get('consistency_group_id')
611 if cg_id:
612 params = {'consistency_group_id': cg_id}
Clinton Knighte5c8f092015-08-27 15:00:23 -0400613 client.delete_share(res_id, params=params)
614 else:
615 client.delete_share(res_id)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200616 client.wait_for_resource_deletion(share_id=res_id)
617 elif res["type"] is "snapshot":
618 client.delete_snapshot(res_id)
619 client.wait_for_resource_deletion(snapshot_id=res_id)
620 elif res["type"] is "share_network":
621 client.delete_share_network(res_id)
622 client.wait_for_resource_deletion(sn_id=res_id)
623 elif res["type"] is "security_service":
624 client.delete_security_service(res_id)
625 client.wait_for_resource_deletion(ss_id=res_id)
626 elif res["type"] is "share_type":
627 client.delete_share_type(res_id)
628 client.wait_for_resource_deletion(st_id=res_id)
Andrew Kerrbf31e912015-07-29 10:39:38 -0400629 elif res["type"] is "consistency_group":
630 client.delete_consistency_group(res_id)
631 client.wait_for_resource_deletion(cg_id=res_id)
632 elif res["type"] is "cgsnapshot":
633 client.delete_cgsnapshot(res_id)
634 client.wait_for_resource_deletion(cgsnapshot_id=res_id)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200635 else:
huayue97bacbf2016-01-04 09:57:39 +0800636 LOG.warning("Provided unsupported resource type for "
637 "cleanup '%s'. Skipping." % res["type"])
Marc Koderer0abc93b2015-07-15 09:18:35 +0200638 res["deleted"] = True
639
640 @classmethod
641 def generate_share_network_data(self):
642 data = {
643 "name": data_utils.rand_name("sn-name"),
644 "description": data_utils.rand_name("sn-desc"),
645 "neutron_net_id": data_utils.rand_name("net-id"),
646 "neutron_subnet_id": data_utils.rand_name("subnet-id"),
647 }
648 return data
649
650 @classmethod
651 def generate_security_service_data(self):
652 data = {
653 "name": data_utils.rand_name("ss-name"),
654 "description": data_utils.rand_name("ss-desc"),
Valeriy Ponomaryovfcde7712015-12-14 18:06:13 +0200655 "dns_ip": utils.rand_ip(),
656 "server": utils.rand_ip(),
Marc Koderer0abc93b2015-07-15 09:18:35 +0200657 "domain": data_utils.rand_name("ss-domain"),
658 "user": data_utils.rand_name("ss-user"),
659 "password": data_utils.rand_name("ss-password"),
660 }
661 return data
662
663 # Useful assertions
664 def assertDictMatch(self, d1, d2, approx_equal=False, tolerance=0.001):
665 """Assert two dicts are equivalent.
666
667 This is a 'deep' match in the sense that it handles nested
668 dictionaries appropriately.
669
670 NOTE:
671
672 If you don't care (or don't know) a given value, you can specify
673 the string DONTCARE as the value. This will cause that dict-item
674 to be skipped.
675
676 """
677 def raise_assertion(msg):
678 d1str = str(d1)
679 d2str = str(d2)
680 base_msg = ('Dictionaries do not match. %(msg)s d1: %(d1str)s '
681 'd2: %(d2str)s' %
682 {"msg": msg, "d1str": d1str, "d2str": d2str})
683 raise AssertionError(base_msg)
684
685 d1keys = set(d1.keys())
686 d2keys = set(d2.keys())
687 if d1keys != d2keys:
688 d1only = d1keys - d2keys
689 d2only = d2keys - d1keys
690 raise_assertion('Keys in d1 and not d2: %(d1only)s. '
691 'Keys in d2 and not d1: %(d2only)s' %
692 {"d1only": d1only, "d2only": d2only})
693
694 for key in d1keys:
695 d1value = d1[key]
696 d2value = d2[key]
697 try:
698 error = abs(float(d1value) - float(d2value))
699 within_tolerance = error <= tolerance
700 except (ValueError, TypeError):
701 # If both values aren't convertable to float, just ignore
702 # ValueError if arg is a str, TypeError if it's something else
703 # (like None)
704 within_tolerance = False
705
706 if hasattr(d1value, 'keys') and hasattr(d2value, 'keys'):
707 self.assertDictMatch(d1value, d2value)
708 elif 'DONTCARE' in (d1value, d2value):
709 continue
710 elif approx_equal and within_tolerance:
711 continue
712 elif d1value != d2value:
713 raise_assertion("d1['%(key)s']=%(d1value)s != "
714 "d2['%(key)s']=%(d2value)s" %
715 {
716 "key": key,
717 "d1value": d1value,
718 "d2value": d2value
719 })
720
721
722class BaseSharesAltTest(BaseSharesTest):
723 """Base test case class for all Shares Alt API tests."""
724
725 @classmethod
726 def resource_setup(cls):
727 cls.username = CONF.identity.alt_username
728 cls.password = CONF.identity.alt_password
729 cls.tenant_name = CONF.identity.alt_tenant_name
730 cls.verify_nonempty(cls.username, cls.password, cls.tenant_name)
731 cls.os = clients.AltManager()
732 alt_share_network_id = CONF.share.alt_share_network_id
733 cls.os.shares_client.share_network_id = alt_share_network_id
Clinton Knighte5c8f092015-08-27 15:00:23 -0400734 cls.os.shares_v2_client.share_network_id = alt_share_network_id
Marc Koderer0abc93b2015-07-15 09:18:35 +0200735 super(BaseSharesAltTest, cls).resource_setup()
736
737
738class BaseSharesAdminTest(BaseSharesTest):
739 """Base test case class for all Shares Admin API tests."""
740
741 @classmethod
742 def resource_setup(cls):
Sam Wanb5047aa2015-10-08 05:37:43 -0400743 if hasattr(CONF.identity, 'admin_username'):
744 cls.username = CONF.identity.admin_username
745 cls.password = CONF.identity.admin_password
746 cls.tenant_name = CONF.identity.admin_tenant_name
747 else:
748 cls.username = CONF.auth.admin_username
749 cls.password = CONF.auth.admin_password
750 cls.tenant_name = CONF.auth.admin_tenant_name
Marc Koderer0abc93b2015-07-15 09:18:35 +0200751 cls.verify_nonempty(cls.username, cls.password, cls.tenant_name)
752 cls.os = clients.AdminManager()
753 admin_share_network_id = CONF.share.admin_share_network_id
754 cls.os.shares_client.share_network_id = admin_share_network_id
Clinton Knighte5c8f092015-08-27 15:00:23 -0400755 cls.os.shares_v2_client.share_network_id = admin_share_network_id
Marc Koderer0abc93b2015-07-15 09:18:35 +0200756 super(BaseSharesAdminTest, cls).resource_setup()