blob: 0395251fae962e66681cba9db0003975543c9310 [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
Yogeshbdb88102015-09-29 23:41:02 -040031from manila_tempest_tests.common import constants
Marc Koderer0abc93b2015-07-15 09:18:35 +020032from manila_tempest_tests import share_exceptions
Valeriy Ponomaryovfcde7712015-12-14 18:06:13 +020033from manila_tempest_tests import utils
Marc Koderer0abc93b2015-07-15 09:18:35 +020034
35CONF = config.CONF
36LOG = log.getLogger(__name__)
37
38
39class handle_cleanup_exceptions(object):
40 """Handle exceptions raised with cleanup operations.
41
42 Always suppress errors when exceptions.NotFound or exceptions.Forbidden
43 are raised.
44 Suppress all other exceptions only in case config opt
45 'suppress_errors_in_cleanup' in config group 'share' is True.
46 """
47
48 def __enter__(self):
49 return self
50
51 def __exit__(self, exc_type, exc_value, exc_traceback):
52 if not (isinstance(exc_value,
53 (exceptions.NotFound, exceptions.Forbidden)) or
54 CONF.share.suppress_errors_in_cleanup):
55 return False # Do not suppress error if any
56 if exc_traceback:
57 LOG.error("Suppressed cleanup error in Manila: "
58 "\n%s" % traceback.format_exc())
59 return True # Suppress error if any
60
61
62def network_synchronized(f):
63
64 def wrapped_func(self, *args, **kwargs):
65 with_isolated_creds = True if len(args) > 2 else False
66 no_lock_required = kwargs.get(
67 "isolated_creds_client", with_isolated_creds)
68 if no_lock_required:
69 # Usage of not reusable network. No need in lock.
70 return f(self, *args, **kwargs)
71
72 # Use lock assuming reusage of common network.
73 @lockutils.synchronized("manila_network_lock", external=True)
74 def source_func(self, *args, **kwargs):
75 return f(self, *args, **kwargs)
76
77 return source_func(self, *args, **kwargs)
78
79 return wrapped_func
80
81
Valeriy Ponomaryovfcde7712015-12-14 18:06:13 +020082skip_if_microversion_not_supported = utils.skip_if_microversion_not_supported
Xing Yang69b00b52015-11-22 16:10:44 -050083skip_if_microversion_lt = utils.skip_if_microversion_lt
Valeriy Ponomaryova14c2252015-10-29 13:34:32 +020084
85
Marc Koderer0abc93b2015-07-15 09:18:35 +020086class BaseSharesTest(test.BaseTestCase):
87 """Base test case class for all Manila API tests."""
88
89 force_tenant_isolation = False
John Spray061b1452015-11-18 13:15:32 +000090 protocols = ["nfs", "cifs", "glusterfs", "hdfs", "cephfs"]
Marc Koderer0abc93b2015-07-15 09:18:35 +020091
92 # Will be cleaned up in resource_cleanup
93 class_resources = []
94
95 # Will be cleaned up in tearDown method
96 method_resources = []
97
98 # Will be cleaned up in resource_cleanup
99 class_isolated_creds = []
100
101 # Will be cleaned up in tearDown method
102 method_isolated_creds = []
103
Valeriy Ponomaryova14c2252015-10-29 13:34:32 +0200104 def skip_if_microversion_not_supported(self, microversion):
Valeriy Ponomaryovfcde7712015-12-14 18:06:13 +0200105 if not utils.is_microversion_supported(microversion):
Valeriy Ponomaryova14c2252015-10-29 13:34:32 +0200106 raise self.skipException(
107 "Microversion '%s' is not supported." % microversion)
108
Xing Yang69b00b52015-11-22 16:10:44 -0500109 def skip_if_microversion_lt(self, microversion):
110 if utils.is_microversion_lt(CONF.share.max_api_microversion,
111 microversion):
112 raise self.skipException(
113 "Microversion must be greater than or equal to '%s'." %
114 microversion)
115
Marc Koderer0abc93b2015-07-15 09:18:35 +0200116 @classmethod
117 def get_client_with_isolated_creds(cls,
118 name=None,
119 type_of_creds="admin",
Clinton Knighte5c8f092015-08-27 15:00:23 -0400120 cleanup_in_class=False,
121 client_version='1'):
Marc Koderer0abc93b2015-07-15 09:18:35 +0200122 """Creates isolated creds.
123
124 :param name: name, will be used for naming ic and related stuff
125 :param type_of_creds: admin, alt or primary
126 :param cleanup_in_class: defines place where to delete
127 :returns: SharesClient -- shares client with isolated creds.
128 :returns: To client added dict attr 'creds' with
129 :returns: key elements 'tenant' and 'user'.
130 """
131 if name is None:
132 # Get name of test method
133 name = inspect.stack()[1][3]
134 if len(name) > 32:
135 name = name[0:32]
136
137 # Choose type of isolated creds
Valeriy Ponomaryov48a2bd72015-11-05 13:22:44 +0200138 ic = dynamic_creds.DynamicCredentialProvider(
139 identity_version=CONF.identity.auth_version,
140 name=name,
Sam Wanc7b7f1f2015-11-25 00:22:28 -0500141 admin_role=CONF.identity.admin_role,
142 admin_creds=common_creds.get_configured_credentials(
143 'identity_admin'))
Marc Koderer0abc93b2015-07-15 09:18:35 +0200144 if "admin" in type_of_creds:
145 creds = ic.get_admin_creds()
146 elif "alt" in type_of_creds:
147 creds = ic.get_alt_creds()
148 else:
149 creds = ic.self.get_credentials(type_of_creds)
150 ic.type_of_creds = type_of_creds
151
152 # create client with isolated creds
153 os = clients.Manager(credentials=creds)
Clinton Knighte5c8f092015-08-27 15:00:23 -0400154 if client_version == '1':
155 client = os.shares_client
156 elif client_version == '2':
157 client = os.shares_v2_client
Marc Koderer0abc93b2015-07-15 09:18:35 +0200158
159 # Set place where will be deleted isolated creds
160 ic_res = {
Valeriy Ponomaryov48a2bd72015-11-05 13:22:44 +0200161 "method": ic.clear_creds,
Marc Koderer0abc93b2015-07-15 09:18:35 +0200162 "deleted": False,
163 }
164 if cleanup_in_class:
165 cls.class_isolated_creds.insert(0, ic_res)
166 else:
167 cls.method_isolated_creds.insert(0, ic_res)
168
169 # Provide share network
170 if CONF.share.multitenancy_enabled:
171 if not CONF.service_available.neutron:
172 raise cls.skipException("Neutron support is required")
Valeriy Ponomaryov48a2bd72015-11-05 13:22:44 +0200173 nc = os.networks_client
Marc Koderer0abc93b2015-07-15 09:18:35 +0200174 share_network_id = cls.provide_share_network(client, nc, ic)
175 client.share_network_id = share_network_id
176 resource = {
177 "type": "share_network",
178 "id": client.share_network_id,
179 "client": client,
180 }
181 if cleanup_in_class:
182 cls.class_resources.insert(0, resource)
183 else:
184 cls.method_resources.insert(0, resource)
185 return client
186
187 @classmethod
188 def verify_nonempty(cls, *args):
189 if not all(args):
190 msg = "Missing API credentials in configuration."
191 raise cls.skipException(msg)
192
193 @classmethod
194 def resource_setup(cls):
195 if not (any(p in CONF.share.enable_protocols
196 for p in cls.protocols) and
197 CONF.service_available.manila):
198 skip_msg = "Manila is disabled"
199 raise cls.skipException(skip_msg)
200 super(BaseSharesTest, cls).resource_setup()
201 if not hasattr(cls, "os"):
202 cls.username = CONF.identity.username
203 cls.password = CONF.identity.password
204 cls.tenant_name = CONF.identity.tenant_name
205 cls.verify_nonempty(cls.username, cls.password, cls.tenant_name)
206 cls.os = clients.Manager()
207 if CONF.share.multitenancy_enabled:
208 if not CONF.service_available.neutron:
209 raise cls.skipException("Neutron support is required")
210 sc = cls.os.shares_client
Valeriy Ponomaryov48a2bd72015-11-05 13:22:44 +0200211 nc = cls.os.networks_client
Marc Koderer0abc93b2015-07-15 09:18:35 +0200212 share_network_id = cls.provide_share_network(sc, nc)
213 cls.os.shares_client.share_network_id = share_network_id
Clinton Knighte5c8f092015-08-27 15:00:23 -0400214 cls.os.shares_v2_client.share_network_id = share_network_id
Marc Koderer0abc93b2015-07-15 09:18:35 +0200215 cls.shares_client = cls.os.shares_client
Clinton Knighte5c8f092015-08-27 15:00:23 -0400216 cls.shares_v2_client = cls.os.shares_v2_client
Marc Koderer0abc93b2015-07-15 09:18:35 +0200217
218 def setUp(self):
219 super(BaseSharesTest, self).setUp()
Marc Koderer0abc93b2015-07-15 09:18:35 +0200220 self.addCleanup(self.clear_isolated_creds)
Valeriy Ponomaryovdd162cb2016-01-20 19:09:49 +0200221 self.addCleanup(self.clear_resources)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200222
223 @classmethod
224 def resource_cleanup(cls):
225 super(BaseSharesTest, cls).resource_cleanup()
226 cls.clear_resources(cls.class_resources)
227 cls.clear_isolated_creds(cls.class_isolated_creds)
228
229 @classmethod
230 @network_synchronized
Valeriy Ponomaryov48a2bd72015-11-05 13:22:44 +0200231 def provide_share_network(cls, shares_client, networks_client,
Marc Koderer0abc93b2015-07-15 09:18:35 +0200232 isolated_creds_client=None):
233 """Used for finding/creating share network for multitenant driver.
234
235 This method creates/gets entity share-network for one tenant. This
236 share-network will be used for creation of service vm.
237
238 :param shares_client: shares client, which requires share-network
Valeriy Ponomaryov48a2bd72015-11-05 13:22:44 +0200239 :param networks_client: network client from same tenant as shares
240 :param isolated_creds_client: DynamicCredentialProvider instance
Marc Koderer0abc93b2015-07-15 09:18:35 +0200241 If provided, then its networking will be used if needed.
242 If not provided, then common network will be used if needed.
243 :returns: str -- share network id for shares_client tenant
244 :returns: None -- if single-tenant driver used
245 """
246
247 sc = shares_client
248
249 if not CONF.share.multitenancy_enabled:
250 # Assumed usage of a single-tenant driver
251 share_network_id = None
252 elif sc.share_network_id:
253 # Share-network already exists, use it
254 share_network_id = sc.share_network_id
255 else:
256 net_id = subnet_id = share_network_id = None
257
258 if not isolated_creds_client:
259 # Search for networks, created in previous runs
260 search_word = "reusable"
261 sn_name = "autogenerated_by_tempest_%s" % search_word
262 service_net_name = "share-service"
Valeriy Ponomaryov48a2bd72015-11-05 13:22:44 +0200263 networks = networks_client.list_networks()
Marc Koderer0abc93b2015-07-15 09:18:35 +0200264 if "networks" in networks.keys():
265 networks = networks["networks"]
266 for network in networks:
267 if (service_net_name in network["name"] and
268 sc.tenant_id == network['tenant_id']):
269 net_id = network["id"]
270 if len(network["subnets"]) > 0:
271 subnet_id = network["subnets"][0]
272 break
273
274 # Create suitable network
275 if (net_id is None or subnet_id is None):
Valeriy Ponomaryov48a2bd72015-11-05 13:22:44 +0200276 ic = dynamic_creds.DynamicCredentialProvider(
277 identity_version=CONF.identity.auth_version,
278 name=service_net_name,
279 admin_role=CONF.identity.admin_role,
Sam Wanc7b7f1f2015-11-25 00:22:28 -0500280 admin_creds=common_creds.get_configured_credentials(
281 'identity_admin'))
Marc Koderer0abc93b2015-07-15 09:18:35 +0200282 net_data = ic._create_network_resources(sc.tenant_id)
283 network, subnet, router = net_data
284 net_id = network["id"]
285 subnet_id = subnet["id"]
286
287 # Try get suitable share-network
288 share_networks = sc.list_share_networks_with_detail()
289 for sn in share_networks:
290 if (net_id == sn["neutron_net_id"] and
291 subnet_id == sn["neutron_subnet_id"] and
292 sn["name"] and search_word in sn["name"]):
293 share_network_id = sn["id"]
294 break
295 else:
296 sn_name = "autogenerated_by_tempest_for_isolated_creds"
297 # Use precreated network and subnet from isolated creds
298 net_id = isolated_creds_client.get_credentials(
299 isolated_creds_client.type_of_creds).network['id']
300 subnet_id = isolated_creds_client.get_credentials(
301 isolated_creds_client.type_of_creds).subnet['id']
302
303 # Create suitable share-network
304 if share_network_id is None:
305 sn_desc = "This share-network was created by tempest"
306 sn = sc.create_share_network(name=sn_name,
307 description=sn_desc,
308 neutron_net_id=net_id,
309 neutron_subnet_id=subnet_id)
310 share_network_id = sn["id"]
311
312 return share_network_id
313
314 @classmethod
315 def _create_share(cls, share_protocol=None, size=1, name=None,
316 snapshot_id=None, description=None, metadata=None,
317 share_network_id=None, share_type_id=None,
Andrew Kerrbf31e912015-07-29 10:39:38 -0400318 consistency_group_id=None, client=None,
Clinton Knighte5c8f092015-08-27 15:00:23 -0400319 cleanup_in_class=True, is_public=False, **kwargs):
Valeriy Ponomaryov1aaa72d2015-09-08 12:59:41 +0300320 client = client or cls.shares_v2_client
Marc Koderer0abc93b2015-07-15 09:18:35 +0200321 description = description or "Tempest's share"
322 share_network_id = share_network_id or client.share_network_id or None
323 metadata = metadata or {}
Clinton Knighte5c8f092015-08-27 15:00:23 -0400324 kwargs.update({
Marc Koderer0abc93b2015-07-15 09:18:35 +0200325 'share_protocol': share_protocol,
326 'size': size,
327 'name': name,
328 'snapshot_id': snapshot_id,
329 'description': description,
330 'metadata': metadata,
331 'share_network_id': share_network_id,
332 'share_type_id': share_type_id,
333 'is_public': is_public,
Clinton Knighte5c8f092015-08-27 15:00:23 -0400334 })
Andrew Kerrbf31e912015-07-29 10:39:38 -0400335 if consistency_group_id:
336 kwargs['consistency_group_id'] = consistency_group_id
337
Marc Koderer0abc93b2015-07-15 09:18:35 +0200338 share = client.create_share(**kwargs)
Andrew Kerrbf31e912015-07-29 10:39:38 -0400339 resource = {"type": "share", "id": share["id"], "client": client,
340 "consistency_group_id": consistency_group_id}
Marc Koderer0abc93b2015-07-15 09:18:35 +0200341 cleanup_list = (cls.class_resources if cleanup_in_class else
342 cls.method_resources)
343 cleanup_list.insert(0, resource)
344 return share
345
346 @classmethod
Rodrigo Barbierie3305122016-02-03 14:32:24 -0200347 def migrate_share(cls, share_id, dest_host, client=None, notify=True,
348 wait_for_status='migration_success', **kwargs):
Clinton Knighte5c8f092015-08-27 15:00:23 -0400349 client = client or cls.shares_v2_client
Rodrigo Barbierie3305122016-02-03 14:32:24 -0200350 client.migrate_share(share_id, dest_host, notify, **kwargs)
351 share = client.wait_for_migration_status(
352 share_id, dest_host, wait_for_status,
353 version=kwargs.get('version'))
354 return share
355
356 @classmethod
357 def migration_complete(cls, share_id, dest_host, client=None, **kwargs):
358 client = client or cls.shares_v2_client
359 client.migration_complete(share_id, **kwargs)
360 share = client.wait_for_migration_status(
361 share_id, dest_host, 'migration_success',
362 version=kwargs.get('version'))
Rodrigo Barbierib7137ad2015-09-06 22:53:16 -0300363 return share
364
365 @classmethod
Marc Koderer0abc93b2015-07-15 09:18:35 +0200366 def create_share(cls, *args, **kwargs):
367 """Create one share and wait for available state. Retry if allowed."""
368 result = cls.create_shares([{"args": args, "kwargs": kwargs}])
369 return result[0]
370
371 @classmethod
372 def create_shares(cls, share_data_list):
373 """Creates several shares in parallel with retries.
374
375 Use this method when you want to create more than one share at same
376 time. Especially if config option 'share.share_creation_retry_number'
377 has value more than zero (0).
378 All shares will be expected to have 'available' status with or without
379 recreation else error will be raised.
380
381 :param share_data_list: list -- list of dictionaries with 'args' and
382 'kwargs' for '_create_share' method of this base class.
383 example of data:
384 share_data_list=[{'args': ['quuz'], 'kwargs': {'foo': 'bar'}}}]
385 :returns: list -- list of shares created using provided data.
386 """
387
388 data = [copy.deepcopy(d) for d in share_data_list]
389 for d in data:
390 if not isinstance(d, dict):
391 raise exceptions.TempestException(
392 "Expected 'dict', got '%s'" % type(d))
393 if "args" not in d:
394 d["args"] = []
395 if "kwargs" not in d:
396 d["kwargs"] = {}
397 if len(d) > 2:
398 raise exceptions.TempestException(
399 "Expected only 'args' and 'kwargs' keys. "
400 "Provided %s" % list(d))
401 d["kwargs"]["client"] = d["kwargs"].get(
Valeriy Ponomaryov1aaa72d2015-09-08 12:59:41 +0300402 "client", cls.shares_v2_client)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200403 d["share"] = cls._create_share(*d["args"], **d["kwargs"])
404 d["cnt"] = 0
405 d["available"] = False
406
407 while not all(d["available"] for d in data):
408 for d in data:
409 if d["available"]:
410 continue
411 try:
412 d["kwargs"]["client"].wait_for_share_status(
413 d["share"]["id"], "available")
414 d["available"] = True
415 except (share_exceptions.ShareBuildErrorException,
416 exceptions.TimeoutException) as e:
417 if CONF.share.share_creation_retry_number > d["cnt"]:
418 d["cnt"] += 1
419 msg = ("Share '%s' failed to be built. "
420 "Trying create another." % d["share"]["id"])
421 LOG.error(msg)
422 LOG.error(e)
423 d["share"] = cls._create_share(
424 *d["args"], **d["kwargs"])
425 else:
426 raise e
427
428 return [d["share"] for d in data]
429
430 @classmethod
Andrew Kerrbf31e912015-07-29 10:39:38 -0400431 def create_consistency_group(cls, client=None, cleanup_in_class=True,
432 share_network_id=None, **kwargs):
Clinton Knighte5c8f092015-08-27 15:00:23 -0400433 client = client or cls.shares_v2_client
Andrew Kerrbf31e912015-07-29 10:39:38 -0400434 kwargs['share_network_id'] = (share_network_id or
435 client.share_network_id or None)
436 consistency_group = client.create_consistency_group(**kwargs)
437 resource = {
438 "type": "consistency_group",
439 "id": consistency_group["id"],
440 "client": client}
441 if cleanup_in_class:
442 cls.class_resources.insert(0, resource)
443 else:
444 cls.method_resources.insert(0, resource)
445
446 if kwargs.get('source_cgsnapshot_id'):
447 new_cg_shares = client.list_shares(
448 detailed=True,
449 params={'consistency_group_id': consistency_group['id']})
450
451 for share in new_cg_shares:
452 resource = {"type": "share",
453 "id": share["id"],
454 "client": client,
455 "consistency_group_id": share.get(
456 'consistency_group_id')}
457 if cleanup_in_class:
458 cls.class_resources.insert(0, resource)
459 else:
460 cls.method_resources.insert(0, resource)
461
462 client.wait_for_consistency_group_status(consistency_group['id'],
463 'available')
464 return consistency_group
465
466 @classmethod
Marc Koderer0abc93b2015-07-15 09:18:35 +0200467 def create_snapshot_wait_for_active(cls, share_id, name=None,
468 description=None, force=False,
469 client=None, cleanup_in_class=True):
470 if client is None:
471 client = cls.shares_client
472 if description is None:
473 description = "Tempest's snapshot"
474 snapshot = client.create_snapshot(share_id, name, description, force)
475 resource = {
476 "type": "snapshot",
477 "id": snapshot["id"],
478 "client": client,
479 }
480 if cleanup_in_class:
481 cls.class_resources.insert(0, resource)
482 else:
483 cls.method_resources.insert(0, resource)
484 client.wait_for_snapshot_status(snapshot["id"], "available")
485 return snapshot
486
487 @classmethod
Andrew Kerrbf31e912015-07-29 10:39:38 -0400488 def create_cgsnapshot_wait_for_active(cls, consistency_group_id,
489 name=None, description=None,
Clinton Knighte5c8f092015-08-27 15:00:23 -0400490 client=None, cleanup_in_class=True,
491 **kwargs):
492 client = client or cls.shares_v2_client
Andrew Kerrbf31e912015-07-29 10:39:38 -0400493 if description is None:
494 description = "Tempest's cgsnapshot"
Clinton Knighte5c8f092015-08-27 15:00:23 -0400495 cgsnapshot = client.create_cgsnapshot(consistency_group_id,
496 name=name,
497 description=description,
498 **kwargs)
Andrew Kerrbf31e912015-07-29 10:39:38 -0400499 resource = {
500 "type": "cgsnapshot",
501 "id": cgsnapshot["id"],
502 "client": client,
503 }
504 if cleanup_in_class:
505 cls.class_resources.insert(0, resource)
506 else:
507 cls.method_resources.insert(0, resource)
508 client.wait_for_cgsnapshot_status(cgsnapshot["id"], "available")
509 return cgsnapshot
510
511 @classmethod
Yogeshbdb88102015-09-29 23:41:02 -0400512 def get_availability_zones(cls, client=None):
513 """List the availability zones for "manila-share" services
514
515 that are currently in "up" state.
516 """
517 client = client or cls.shares_v2_client
518 cls.services = client.list_services()
519 zones = [service['zone'] for service in cls.services if
520 service['binary'] == "manila-share" and
521 service['state'] == 'up']
522 return zones
523
524 @classmethod
525 def create_share_replica(cls, share_id, availability_zone, client=None,
526 cleanup_in_class=False, cleanup=True):
527 client = client or cls.shares_v2_client
528 replica = client.create_share_replica(share_id, availability_zone)
529 resource = {
530 "type": "share_replica",
531 "id": replica["id"],
532 "client": client,
533 "share_id": share_id,
534 }
535 # NOTE(Yogi1): Cleanup needs to be disabled during promotion tests.
536 if cleanup:
537 if cleanup_in_class:
538 cls.class_resources.insert(0, resource)
539 else:
540 cls.method_resources.insert(0, resource)
541 client.wait_for_share_replica_status(
542 replica["id"], constants.STATUS_AVAILABLE)
543 return replica
544
545 @classmethod
546 def delete_share_replica(cls, replica_id, client=None):
547 client = client or cls.shares_v2_client
548 client.delete_share_replica(replica_id)
549 client.wait_for_resource_deletion(replica_id=replica_id)
550
551 @classmethod
552 def promote_share_replica(cls, replica_id, client=None):
553 client = client or cls.shares_v2_client
554 replica = client.promote_share_replica(replica_id)
555 client.wait_for_share_replica_status(
556 replica["id"],
557 constants.REPLICATION_STATE_ACTIVE,
558 status_attr="replica_state")
559 return replica
560
561 @classmethod
Marc Koderer0abc93b2015-07-15 09:18:35 +0200562 def create_share_network(cls, client=None,
563 cleanup_in_class=False, **kwargs):
564 if client is None:
565 client = cls.shares_client
566 share_network = client.create_share_network(**kwargs)
567 resource = {
568 "type": "share_network",
569 "id": share_network["id"],
570 "client": client,
571 }
572 if cleanup_in_class:
573 cls.class_resources.insert(0, resource)
574 else:
575 cls.method_resources.insert(0, resource)
576 return share_network
577
578 @classmethod
579 def create_security_service(cls, ss_type="ldap", client=None,
580 cleanup_in_class=False, **kwargs):
581 if client is None:
582 client = cls.shares_client
583 security_service = client.create_security_service(ss_type, **kwargs)
584 resource = {
585 "type": "security_service",
586 "id": security_service["id"],
587 "client": client,
588 }
589 if cleanup_in_class:
590 cls.class_resources.insert(0, resource)
591 else:
592 cls.method_resources.insert(0, resource)
593 return security_service
594
595 @classmethod
596 def create_share_type(cls, name, is_public=True, client=None,
597 cleanup_in_class=True, **kwargs):
598 if client is None:
Valeriy Ponomaryova14c2252015-10-29 13:34:32 +0200599 client = cls.shares_v2_client
Marc Koderer0abc93b2015-07-15 09:18:35 +0200600 share_type = client.create_share_type(name, is_public, **kwargs)
601 resource = {
602 "type": "share_type",
603 "id": share_type["share_type"]["id"],
604 "client": client,
605 }
606 if cleanup_in_class:
607 cls.class_resources.insert(0, resource)
608 else:
609 cls.method_resources.insert(0, resource)
610 return share_type
611
612 @staticmethod
613 def add_required_extra_specs_to_dict(extra_specs=None):
Valeriy Ponomaryovad55dc52015-09-23 13:54:00 +0300614 dhss = six.text_type(CONF.share.multitenancy_enabled)
615 snapshot_support = six.text_type(
616 CONF.share.capability_snapshot_support)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200617 required = {
Valeriy Ponomaryovad55dc52015-09-23 13:54:00 +0300618 "driver_handles_share_servers": dhss,
619 "snapshot_support": snapshot_support,
Marc Koderer0abc93b2015-07-15 09:18:35 +0200620 }
621 if extra_specs:
622 required.update(extra_specs)
623 return required
624
625 @classmethod
626 def clear_isolated_creds(cls, creds=None):
627 if creds is None:
628 creds = cls.method_isolated_creds
629 for ic in creds:
630 if "deleted" not in ic.keys():
631 ic["deleted"] = False
632 if not ic["deleted"]:
633 with handle_cleanup_exceptions():
634 ic["method"]()
635 ic["deleted"] = True
636
637 @classmethod
638 def clear_resources(cls, resources=None):
639 """Deletes resources, that were created in test suites.
640
641 This method tries to remove resources from resource list,
642 if it is not found, assumed it was deleted in test itself.
643 It is expected, that all resources were added as LIFO
644 due to restriction of deletion resources, that is in the chain.
645
646 :param resources: dict with keys 'type','id','client' and 'deleted'
647 """
648
649 if resources is None:
650 resources = cls.method_resources
651 for res in resources:
652 if "deleted" not in res.keys():
653 res["deleted"] = False
654 if "client" not in res.keys():
655 res["client"] = cls.shares_client
656 if not(res["deleted"]):
657 res_id = res['id']
658 client = res["client"]
659 with handle_cleanup_exceptions():
660 if res["type"] is "share":
Andrew Kerrbf31e912015-07-29 10:39:38 -0400661 cg_id = res.get('consistency_group_id')
662 if cg_id:
663 params = {'consistency_group_id': cg_id}
Clinton Knighte5c8f092015-08-27 15:00:23 -0400664 client.delete_share(res_id, params=params)
665 else:
666 client.delete_share(res_id)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200667 client.wait_for_resource_deletion(share_id=res_id)
668 elif res["type"] is "snapshot":
669 client.delete_snapshot(res_id)
670 client.wait_for_resource_deletion(snapshot_id=res_id)
671 elif res["type"] is "share_network":
672 client.delete_share_network(res_id)
673 client.wait_for_resource_deletion(sn_id=res_id)
674 elif res["type"] is "security_service":
675 client.delete_security_service(res_id)
676 client.wait_for_resource_deletion(ss_id=res_id)
677 elif res["type"] is "share_type":
678 client.delete_share_type(res_id)
679 client.wait_for_resource_deletion(st_id=res_id)
Andrew Kerrbf31e912015-07-29 10:39:38 -0400680 elif res["type"] is "consistency_group":
681 client.delete_consistency_group(res_id)
682 client.wait_for_resource_deletion(cg_id=res_id)
683 elif res["type"] is "cgsnapshot":
684 client.delete_cgsnapshot(res_id)
685 client.wait_for_resource_deletion(cgsnapshot_id=res_id)
Yogeshbdb88102015-09-29 23:41:02 -0400686 elif res["type"] is "share_replica":
687 client.delete_share_replica(res_id)
688 client.wait_for_resource_deletion(replica_id=res_id)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200689 else:
huayue97bacbf2016-01-04 09:57:39 +0800690 LOG.warning("Provided unsupported resource type for "
691 "cleanup '%s'. Skipping." % res["type"])
Marc Koderer0abc93b2015-07-15 09:18:35 +0200692 res["deleted"] = True
693
694 @classmethod
695 def generate_share_network_data(self):
696 data = {
697 "name": data_utils.rand_name("sn-name"),
698 "description": data_utils.rand_name("sn-desc"),
699 "neutron_net_id": data_utils.rand_name("net-id"),
700 "neutron_subnet_id": data_utils.rand_name("subnet-id"),
701 }
702 return data
703
704 @classmethod
705 def generate_security_service_data(self):
706 data = {
707 "name": data_utils.rand_name("ss-name"),
708 "description": data_utils.rand_name("ss-desc"),
Valeriy Ponomaryovfcde7712015-12-14 18:06:13 +0200709 "dns_ip": utils.rand_ip(),
710 "server": utils.rand_ip(),
Marc Koderer0abc93b2015-07-15 09:18:35 +0200711 "domain": data_utils.rand_name("ss-domain"),
712 "user": data_utils.rand_name("ss-user"),
713 "password": data_utils.rand_name("ss-password"),
714 }
715 return data
716
717 # Useful assertions
718 def assertDictMatch(self, d1, d2, approx_equal=False, tolerance=0.001):
719 """Assert two dicts are equivalent.
720
721 This is a 'deep' match in the sense that it handles nested
722 dictionaries appropriately.
723
724 NOTE:
725
726 If you don't care (or don't know) a given value, you can specify
727 the string DONTCARE as the value. This will cause that dict-item
728 to be skipped.
729
730 """
731 def raise_assertion(msg):
732 d1str = str(d1)
733 d2str = str(d2)
734 base_msg = ('Dictionaries do not match. %(msg)s d1: %(d1str)s '
735 'd2: %(d2str)s' %
736 {"msg": msg, "d1str": d1str, "d2str": d2str})
737 raise AssertionError(base_msg)
738
739 d1keys = set(d1.keys())
740 d2keys = set(d2.keys())
741 if d1keys != d2keys:
742 d1only = d1keys - d2keys
743 d2only = d2keys - d1keys
744 raise_assertion('Keys in d1 and not d2: %(d1only)s. '
745 'Keys in d2 and not d1: %(d2only)s' %
746 {"d1only": d1only, "d2only": d2only})
747
748 for key in d1keys:
749 d1value = d1[key]
750 d2value = d2[key]
751 try:
752 error = abs(float(d1value) - float(d2value))
753 within_tolerance = error <= tolerance
754 except (ValueError, TypeError):
755 # If both values aren't convertable to float, just ignore
756 # ValueError if arg is a str, TypeError if it's something else
757 # (like None)
758 within_tolerance = False
759
760 if hasattr(d1value, 'keys') and hasattr(d2value, 'keys'):
761 self.assertDictMatch(d1value, d2value)
762 elif 'DONTCARE' in (d1value, d2value):
763 continue
764 elif approx_equal and within_tolerance:
765 continue
766 elif d1value != d2value:
767 raise_assertion("d1['%(key)s']=%(d1value)s != "
768 "d2['%(key)s']=%(d2value)s" %
769 {
770 "key": key,
771 "d1value": d1value,
772 "d2value": d2value
773 })
774
775
776class BaseSharesAltTest(BaseSharesTest):
777 """Base test case class for all Shares Alt API tests."""
778
779 @classmethod
780 def resource_setup(cls):
781 cls.username = CONF.identity.alt_username
782 cls.password = CONF.identity.alt_password
783 cls.tenant_name = CONF.identity.alt_tenant_name
784 cls.verify_nonempty(cls.username, cls.password, cls.tenant_name)
785 cls.os = clients.AltManager()
786 alt_share_network_id = CONF.share.alt_share_network_id
787 cls.os.shares_client.share_network_id = alt_share_network_id
Clinton Knighte5c8f092015-08-27 15:00:23 -0400788 cls.os.shares_v2_client.share_network_id = alt_share_network_id
Marc Koderer0abc93b2015-07-15 09:18:35 +0200789 super(BaseSharesAltTest, cls).resource_setup()
790
791
792class BaseSharesAdminTest(BaseSharesTest):
793 """Base test case class for all Shares Admin API tests."""
794
795 @classmethod
796 def resource_setup(cls):
Sam Wanb5047aa2015-10-08 05:37:43 -0400797 if hasattr(CONF.identity, 'admin_username'):
798 cls.username = CONF.identity.admin_username
799 cls.password = CONF.identity.admin_password
800 cls.tenant_name = CONF.identity.admin_tenant_name
801 else:
802 cls.username = CONF.auth.admin_username
803 cls.password = CONF.auth.admin_password
804 cls.tenant_name = CONF.auth.admin_tenant_name
Marc Koderer0abc93b2015-07-15 09:18:35 +0200805 cls.verify_nonempty(cls.username, cls.password, cls.tenant_name)
806 cls.os = clients.AdminManager()
807 admin_share_network_id = CONF.share.admin_share_network_id
808 cls.os.shares_client.share_network_id = admin_share_network_id
Clinton Knighte5c8f092015-08-27 15:00:23 -0400809 cls.os.shares_v2_client.share_network_id = admin_share_network_id
Marc Koderer0abc93b2015-07-15 09:18:35 +0200810 super(BaseSharesAdminTest, cls).resource_setup()