blob: eab6b3892f9fca963f9c5532ab835ce2bebf97dd [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:
Yogesh1f931ff2015-09-29 23:41:02 -0400471 client = cls.shares_v2_client
Marc Koderer0abc93b2015-07-15 09:18:35 +0200472 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
Yogesh1f931ff2015-09-29 23:41:02 -0400524 def get_pools_for_replication_domain(self):
525 # Get the list of pools for the replication domain
526 pools = self.admin_client.list_pools(detail=True)['pools']
527 instance_host = self.shares[0]['host']
528 host_pool = [p for p in pools if p['name'] == instance_host][0]
529 rep_domain = host_pool['capabilities']['replication_domain']
530 pools_in_rep_domain = [p for p in pools if p['capabilities'][
531 'replication_domain'] == rep_domain]
532 return rep_domain, pools_in_rep_domain
533
Yogeshbdb88102015-09-29 23:41:02 -0400534 @classmethod
535 def create_share_replica(cls, share_id, availability_zone, client=None,
536 cleanup_in_class=False, cleanup=True):
537 client = client or cls.shares_v2_client
538 replica = client.create_share_replica(share_id, availability_zone)
539 resource = {
540 "type": "share_replica",
541 "id": replica["id"],
542 "client": client,
543 "share_id": share_id,
544 }
545 # NOTE(Yogi1): Cleanup needs to be disabled during promotion tests.
546 if cleanup:
547 if cleanup_in_class:
548 cls.class_resources.insert(0, resource)
549 else:
550 cls.method_resources.insert(0, resource)
551 client.wait_for_share_replica_status(
552 replica["id"], constants.STATUS_AVAILABLE)
553 return replica
554
555 @classmethod
556 def delete_share_replica(cls, replica_id, client=None):
557 client = client or cls.shares_v2_client
Yogesh1f931ff2015-09-29 23:41:02 -0400558 try:
559 client.delete_share_replica(replica_id)
560 client.wait_for_resource_deletion(replica_id=replica_id)
561 except exceptions.NotFound:
562 pass
Yogeshbdb88102015-09-29 23:41:02 -0400563
564 @classmethod
565 def promote_share_replica(cls, replica_id, client=None):
566 client = client or cls.shares_v2_client
567 replica = client.promote_share_replica(replica_id)
568 client.wait_for_share_replica_status(
569 replica["id"],
570 constants.REPLICATION_STATE_ACTIVE,
571 status_attr="replica_state")
572 return replica
573
574 @classmethod
Marc Koderer0abc93b2015-07-15 09:18:35 +0200575 def create_share_network(cls, client=None,
576 cleanup_in_class=False, **kwargs):
577 if client is None:
578 client = cls.shares_client
579 share_network = client.create_share_network(**kwargs)
580 resource = {
581 "type": "share_network",
582 "id": share_network["id"],
583 "client": client,
584 }
585 if cleanup_in_class:
586 cls.class_resources.insert(0, resource)
587 else:
588 cls.method_resources.insert(0, resource)
589 return share_network
590
591 @classmethod
592 def create_security_service(cls, ss_type="ldap", client=None,
593 cleanup_in_class=False, **kwargs):
594 if client is None:
595 client = cls.shares_client
596 security_service = client.create_security_service(ss_type, **kwargs)
597 resource = {
598 "type": "security_service",
599 "id": security_service["id"],
600 "client": client,
601 }
602 if cleanup_in_class:
603 cls.class_resources.insert(0, resource)
604 else:
605 cls.method_resources.insert(0, resource)
606 return security_service
607
608 @classmethod
609 def create_share_type(cls, name, is_public=True, client=None,
610 cleanup_in_class=True, **kwargs):
611 if client is None:
Valeriy Ponomaryova14c2252015-10-29 13:34:32 +0200612 client = cls.shares_v2_client
Marc Koderer0abc93b2015-07-15 09:18:35 +0200613 share_type = client.create_share_type(name, is_public, **kwargs)
614 resource = {
615 "type": "share_type",
616 "id": share_type["share_type"]["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_type
624
625 @staticmethod
626 def add_required_extra_specs_to_dict(extra_specs=None):
Valeriy Ponomaryovad55dc52015-09-23 13:54:00 +0300627 dhss = six.text_type(CONF.share.multitenancy_enabled)
628 snapshot_support = six.text_type(
629 CONF.share.capability_snapshot_support)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200630 required = {
Valeriy Ponomaryovad55dc52015-09-23 13:54:00 +0300631 "driver_handles_share_servers": dhss,
632 "snapshot_support": snapshot_support,
Marc Koderer0abc93b2015-07-15 09:18:35 +0200633 }
634 if extra_specs:
635 required.update(extra_specs)
636 return required
637
638 @classmethod
639 def clear_isolated_creds(cls, creds=None):
640 if creds is None:
641 creds = cls.method_isolated_creds
642 for ic in creds:
643 if "deleted" not in ic.keys():
644 ic["deleted"] = False
645 if not ic["deleted"]:
646 with handle_cleanup_exceptions():
647 ic["method"]()
648 ic["deleted"] = True
649
650 @classmethod
Yogesh1f931ff2015-09-29 23:41:02 -0400651 def clear_share_replicas(cls, share_id, client=None):
652 client = client or cls.shares_v2_client
653 share_replicas = client.list_share_replicas(
654 share_id=share_id)
655
656 for replica in share_replicas:
657 try:
658 cls.delete_share_replica(replica['id'])
659 except exceptions.BadRequest:
660 # Ignore the exception due to deletion of last active replica
661 pass
662
663 @classmethod
Marc Koderer0abc93b2015-07-15 09:18:35 +0200664 def clear_resources(cls, resources=None):
665 """Deletes resources, that were created in test suites.
666
667 This method tries to remove resources from resource list,
668 if it is not found, assumed it was deleted in test itself.
669 It is expected, that all resources were added as LIFO
670 due to restriction of deletion resources, that is in the chain.
671
672 :param resources: dict with keys 'type','id','client' and 'deleted'
673 """
674
675 if resources is None:
676 resources = cls.method_resources
677 for res in resources:
678 if "deleted" not in res.keys():
679 res["deleted"] = False
680 if "client" not in res.keys():
681 res["client"] = cls.shares_client
682 if not(res["deleted"]):
683 res_id = res['id']
684 client = res["client"]
685 with handle_cleanup_exceptions():
686 if res["type"] is "share":
Yogesh1f931ff2015-09-29 23:41:02 -0400687 cls.clear_share_replicas(res_id)
Andrew Kerrbf31e912015-07-29 10:39:38 -0400688 cg_id = res.get('consistency_group_id')
689 if cg_id:
690 params = {'consistency_group_id': cg_id}
Clinton Knighte5c8f092015-08-27 15:00:23 -0400691 client.delete_share(res_id, params=params)
692 else:
693 client.delete_share(res_id)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200694 client.wait_for_resource_deletion(share_id=res_id)
695 elif res["type"] is "snapshot":
696 client.delete_snapshot(res_id)
697 client.wait_for_resource_deletion(snapshot_id=res_id)
698 elif res["type"] is "share_network":
699 client.delete_share_network(res_id)
700 client.wait_for_resource_deletion(sn_id=res_id)
701 elif res["type"] is "security_service":
702 client.delete_security_service(res_id)
703 client.wait_for_resource_deletion(ss_id=res_id)
704 elif res["type"] is "share_type":
705 client.delete_share_type(res_id)
706 client.wait_for_resource_deletion(st_id=res_id)
Andrew Kerrbf31e912015-07-29 10:39:38 -0400707 elif res["type"] is "consistency_group":
708 client.delete_consistency_group(res_id)
709 client.wait_for_resource_deletion(cg_id=res_id)
710 elif res["type"] is "cgsnapshot":
711 client.delete_cgsnapshot(res_id)
712 client.wait_for_resource_deletion(cgsnapshot_id=res_id)
Yogeshbdb88102015-09-29 23:41:02 -0400713 elif res["type"] is "share_replica":
714 client.delete_share_replica(res_id)
715 client.wait_for_resource_deletion(replica_id=res_id)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200716 else:
huayue97bacbf2016-01-04 09:57:39 +0800717 LOG.warning("Provided unsupported resource type for "
718 "cleanup '%s'. Skipping." % res["type"])
Marc Koderer0abc93b2015-07-15 09:18:35 +0200719 res["deleted"] = True
720
721 @classmethod
722 def generate_share_network_data(self):
723 data = {
724 "name": data_utils.rand_name("sn-name"),
725 "description": data_utils.rand_name("sn-desc"),
726 "neutron_net_id": data_utils.rand_name("net-id"),
727 "neutron_subnet_id": data_utils.rand_name("subnet-id"),
728 }
729 return data
730
731 @classmethod
732 def generate_security_service_data(self):
733 data = {
734 "name": data_utils.rand_name("ss-name"),
735 "description": data_utils.rand_name("ss-desc"),
Valeriy Ponomaryovfcde7712015-12-14 18:06:13 +0200736 "dns_ip": utils.rand_ip(),
737 "server": utils.rand_ip(),
Marc Koderer0abc93b2015-07-15 09:18:35 +0200738 "domain": data_utils.rand_name("ss-domain"),
739 "user": data_utils.rand_name("ss-user"),
740 "password": data_utils.rand_name("ss-password"),
741 }
742 return data
743
744 # Useful assertions
745 def assertDictMatch(self, d1, d2, approx_equal=False, tolerance=0.001):
746 """Assert two dicts are equivalent.
747
748 This is a 'deep' match in the sense that it handles nested
749 dictionaries appropriately.
750
751 NOTE:
752
753 If you don't care (or don't know) a given value, you can specify
754 the string DONTCARE as the value. This will cause that dict-item
755 to be skipped.
756
757 """
758 def raise_assertion(msg):
759 d1str = str(d1)
760 d2str = str(d2)
761 base_msg = ('Dictionaries do not match. %(msg)s d1: %(d1str)s '
762 'd2: %(d2str)s' %
763 {"msg": msg, "d1str": d1str, "d2str": d2str})
764 raise AssertionError(base_msg)
765
766 d1keys = set(d1.keys())
767 d2keys = set(d2.keys())
768 if d1keys != d2keys:
769 d1only = d1keys - d2keys
770 d2only = d2keys - d1keys
771 raise_assertion('Keys in d1 and not d2: %(d1only)s. '
772 'Keys in d2 and not d1: %(d2only)s' %
773 {"d1only": d1only, "d2only": d2only})
774
775 for key in d1keys:
776 d1value = d1[key]
777 d2value = d2[key]
778 try:
779 error = abs(float(d1value) - float(d2value))
780 within_tolerance = error <= tolerance
781 except (ValueError, TypeError):
daiki kato6914b1a2016-03-16 17:16:57 +0900782 # If both values aren't convertible to float, just ignore
Marc Koderer0abc93b2015-07-15 09:18:35 +0200783 # ValueError if arg is a str, TypeError if it's something else
784 # (like None)
785 within_tolerance = False
786
787 if hasattr(d1value, 'keys') and hasattr(d2value, 'keys'):
788 self.assertDictMatch(d1value, d2value)
789 elif 'DONTCARE' in (d1value, d2value):
790 continue
791 elif approx_equal and within_tolerance:
792 continue
793 elif d1value != d2value:
794 raise_assertion("d1['%(key)s']=%(d1value)s != "
795 "d2['%(key)s']=%(d2value)s" %
796 {
797 "key": key,
798 "d1value": d1value,
799 "d2value": d2value
800 })
801
802
803class BaseSharesAltTest(BaseSharesTest):
804 """Base test case class for all Shares Alt API tests."""
805
806 @classmethod
807 def resource_setup(cls):
808 cls.username = CONF.identity.alt_username
809 cls.password = CONF.identity.alt_password
810 cls.tenant_name = CONF.identity.alt_tenant_name
811 cls.verify_nonempty(cls.username, cls.password, cls.tenant_name)
812 cls.os = clients.AltManager()
813 alt_share_network_id = CONF.share.alt_share_network_id
814 cls.os.shares_client.share_network_id = alt_share_network_id
Clinton Knighte5c8f092015-08-27 15:00:23 -0400815 cls.os.shares_v2_client.share_network_id = alt_share_network_id
Marc Koderer0abc93b2015-07-15 09:18:35 +0200816 super(BaseSharesAltTest, cls).resource_setup()
817
818
819class BaseSharesAdminTest(BaseSharesTest):
820 """Base test case class for all Shares Admin API tests."""
821
822 @classmethod
823 def resource_setup(cls):
Sam Wanb5047aa2015-10-08 05:37:43 -0400824 if hasattr(CONF.identity, 'admin_username'):
825 cls.username = CONF.identity.admin_username
826 cls.password = CONF.identity.admin_password
827 cls.tenant_name = CONF.identity.admin_tenant_name
828 else:
829 cls.username = CONF.auth.admin_username
830 cls.password = CONF.auth.admin_password
831 cls.tenant_name = CONF.auth.admin_tenant_name
Marc Koderer0abc93b2015-07-15 09:18:35 +0200832 cls.verify_nonempty(cls.username, cls.password, cls.tenant_name)
833 cls.os = clients.AdminManager()
834 admin_share_network_id = CONF.share.admin_share_network_id
835 cls.os.shares_client.share_network_id = admin_share_network_id
Clinton Knighte5c8f092015-08-27 15:00:23 -0400836 cls.os.shares_v2_client.share_network_id = admin_share_network_id
Marc Koderer0abc93b2015-07-15 09:18:35 +0200837 super(BaseSharesAdminTest, cls).resource_setup()