blob: 6a75bee06d483475db029f0d7c777dfd14c78a1d [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
Valeriy Ponomaryov1950cb82016-04-11 14:02:29 +0300204 cls.project_name = CONF.identity.project_name
205 cls.verify_nonempty(cls.username, cls.password, cls.project_name)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200206 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
Goutham Pacha Ravi9221f5e2016-04-21 13:17:49 -0400434 if kwargs.get('source_cgsnapshot_id') is None:
435 kwargs['share_network_id'] = (share_network_id or
436 client.share_network_id or None)
Andrew Kerrbf31e912015-07-29 10:39:38 -0400437 consistency_group = client.create_consistency_group(**kwargs)
438 resource = {
439 "type": "consistency_group",
440 "id": consistency_group["id"],
441 "client": client}
442 if cleanup_in_class:
443 cls.class_resources.insert(0, resource)
444 else:
445 cls.method_resources.insert(0, resource)
446
447 if kwargs.get('source_cgsnapshot_id'):
448 new_cg_shares = client.list_shares(
449 detailed=True,
450 params={'consistency_group_id': consistency_group['id']})
451
452 for share in new_cg_shares:
453 resource = {"type": "share",
454 "id": share["id"],
455 "client": client,
456 "consistency_group_id": share.get(
457 'consistency_group_id')}
458 if cleanup_in_class:
459 cls.class_resources.insert(0, resource)
460 else:
461 cls.method_resources.insert(0, resource)
462
463 client.wait_for_consistency_group_status(consistency_group['id'],
464 'available')
465 return consistency_group
466
467 @classmethod
Marc Koderer0abc93b2015-07-15 09:18:35 +0200468 def create_snapshot_wait_for_active(cls, share_id, name=None,
469 description=None, force=False,
470 client=None, cleanup_in_class=True):
471 if client is None:
Yogesh1f931ff2015-09-29 23:41:02 -0400472 client = cls.shares_v2_client
Marc Koderer0abc93b2015-07-15 09:18:35 +0200473 if description is None:
474 description = "Tempest's snapshot"
475 snapshot = client.create_snapshot(share_id, name, description, force)
476 resource = {
477 "type": "snapshot",
478 "id": snapshot["id"],
479 "client": client,
480 }
481 if cleanup_in_class:
482 cls.class_resources.insert(0, resource)
483 else:
484 cls.method_resources.insert(0, resource)
485 client.wait_for_snapshot_status(snapshot["id"], "available")
486 return snapshot
487
488 @classmethod
Andrew Kerrbf31e912015-07-29 10:39:38 -0400489 def create_cgsnapshot_wait_for_active(cls, consistency_group_id,
490 name=None, description=None,
Clinton Knighte5c8f092015-08-27 15:00:23 -0400491 client=None, cleanup_in_class=True,
492 **kwargs):
493 client = client or cls.shares_v2_client
Andrew Kerrbf31e912015-07-29 10:39:38 -0400494 if description is None:
495 description = "Tempest's cgsnapshot"
Clinton Knighte5c8f092015-08-27 15:00:23 -0400496 cgsnapshot = client.create_cgsnapshot(consistency_group_id,
497 name=name,
498 description=description,
499 **kwargs)
Andrew Kerrbf31e912015-07-29 10:39:38 -0400500 resource = {
501 "type": "cgsnapshot",
502 "id": cgsnapshot["id"],
503 "client": client,
504 }
505 if cleanup_in_class:
506 cls.class_resources.insert(0, resource)
507 else:
508 cls.method_resources.insert(0, resource)
509 client.wait_for_cgsnapshot_status(cgsnapshot["id"], "available")
510 return cgsnapshot
511
512 @classmethod
Yogeshbdb88102015-09-29 23:41:02 -0400513 def get_availability_zones(cls, client=None):
514 """List the availability zones for "manila-share" services
515
516 that are currently in "up" state.
517 """
518 client = client or cls.shares_v2_client
519 cls.services = client.list_services()
520 zones = [service['zone'] for service in cls.services if
521 service['binary'] == "manila-share" and
522 service['state'] == 'up']
523 return zones
524
Yogesh1f931ff2015-09-29 23:41:02 -0400525 def get_pools_for_replication_domain(self):
526 # Get the list of pools for the replication domain
527 pools = self.admin_client.list_pools(detail=True)['pools']
528 instance_host = self.shares[0]['host']
529 host_pool = [p for p in pools if p['name'] == instance_host][0]
530 rep_domain = host_pool['capabilities']['replication_domain']
531 pools_in_rep_domain = [p for p in pools if p['capabilities'][
532 'replication_domain'] == rep_domain]
533 return rep_domain, pools_in_rep_domain
534
Yogeshbdb88102015-09-29 23:41:02 -0400535 @classmethod
536 def create_share_replica(cls, share_id, availability_zone, client=None,
537 cleanup_in_class=False, cleanup=True):
538 client = client or cls.shares_v2_client
539 replica = client.create_share_replica(share_id, availability_zone)
540 resource = {
541 "type": "share_replica",
542 "id": replica["id"],
543 "client": client,
544 "share_id": share_id,
545 }
546 # NOTE(Yogi1): Cleanup needs to be disabled during promotion tests.
547 if cleanup:
548 if cleanup_in_class:
549 cls.class_resources.insert(0, resource)
550 else:
551 cls.method_resources.insert(0, resource)
552 client.wait_for_share_replica_status(
553 replica["id"], constants.STATUS_AVAILABLE)
554 return replica
555
556 @classmethod
557 def delete_share_replica(cls, replica_id, client=None):
558 client = client or cls.shares_v2_client
Yogesh1f931ff2015-09-29 23:41:02 -0400559 try:
560 client.delete_share_replica(replica_id)
561 client.wait_for_resource_deletion(replica_id=replica_id)
562 except exceptions.NotFound:
563 pass
Yogeshbdb88102015-09-29 23:41:02 -0400564
565 @classmethod
566 def promote_share_replica(cls, replica_id, client=None):
567 client = client or cls.shares_v2_client
568 replica = client.promote_share_replica(replica_id)
569 client.wait_for_share_replica_status(
570 replica["id"],
571 constants.REPLICATION_STATE_ACTIVE,
572 status_attr="replica_state")
573 return replica
574
575 @classmethod
Marc Koderer0abc93b2015-07-15 09:18:35 +0200576 def create_share_network(cls, client=None,
577 cleanup_in_class=False, **kwargs):
578 if client is None:
579 client = cls.shares_client
580 share_network = client.create_share_network(**kwargs)
581 resource = {
582 "type": "share_network",
583 "id": share_network["id"],
584 "client": client,
585 }
586 if cleanup_in_class:
587 cls.class_resources.insert(0, resource)
588 else:
589 cls.method_resources.insert(0, resource)
590 return share_network
591
592 @classmethod
593 def create_security_service(cls, ss_type="ldap", client=None,
594 cleanup_in_class=False, **kwargs):
595 if client is None:
596 client = cls.shares_client
597 security_service = client.create_security_service(ss_type, **kwargs)
598 resource = {
599 "type": "security_service",
600 "id": security_service["id"],
601 "client": client,
602 }
603 if cleanup_in_class:
604 cls.class_resources.insert(0, resource)
605 else:
606 cls.method_resources.insert(0, resource)
607 return security_service
608
609 @classmethod
610 def create_share_type(cls, name, is_public=True, client=None,
611 cleanup_in_class=True, **kwargs):
612 if client is None:
Valeriy Ponomaryova14c2252015-10-29 13:34:32 +0200613 client = cls.shares_v2_client
Marc Koderer0abc93b2015-07-15 09:18:35 +0200614 share_type = client.create_share_type(name, is_public, **kwargs)
615 resource = {
616 "type": "share_type",
617 "id": share_type["share_type"]["id"],
618 "client": client,
619 }
620 if cleanup_in_class:
621 cls.class_resources.insert(0, resource)
622 else:
623 cls.method_resources.insert(0, resource)
624 return share_type
625
626 @staticmethod
627 def add_required_extra_specs_to_dict(extra_specs=None):
Valeriy Ponomaryovad55dc52015-09-23 13:54:00 +0300628 dhss = six.text_type(CONF.share.multitenancy_enabled)
629 snapshot_support = six.text_type(
630 CONF.share.capability_snapshot_support)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200631 required = {
Valeriy Ponomaryovad55dc52015-09-23 13:54:00 +0300632 "driver_handles_share_servers": dhss,
633 "snapshot_support": snapshot_support,
Marc Koderer0abc93b2015-07-15 09:18:35 +0200634 }
635 if extra_specs:
636 required.update(extra_specs)
637 return required
638
639 @classmethod
640 def clear_isolated_creds(cls, creds=None):
641 if creds is None:
642 creds = cls.method_isolated_creds
643 for ic in creds:
644 if "deleted" not in ic.keys():
645 ic["deleted"] = False
646 if not ic["deleted"]:
647 with handle_cleanup_exceptions():
648 ic["method"]()
649 ic["deleted"] = True
650
651 @classmethod
Yogesh1f931ff2015-09-29 23:41:02 -0400652 def clear_share_replicas(cls, share_id, client=None):
653 client = client or cls.shares_v2_client
654 share_replicas = client.list_share_replicas(
655 share_id=share_id)
656
657 for replica in share_replicas:
658 try:
659 cls.delete_share_replica(replica['id'])
660 except exceptions.BadRequest:
661 # Ignore the exception due to deletion of last active replica
662 pass
663
664 @classmethod
Marc Koderer0abc93b2015-07-15 09:18:35 +0200665 def clear_resources(cls, resources=None):
666 """Deletes resources, that were created in test suites.
667
668 This method tries to remove resources from resource list,
669 if it is not found, assumed it was deleted in test itself.
670 It is expected, that all resources were added as LIFO
671 due to restriction of deletion resources, that is in the chain.
672
673 :param resources: dict with keys 'type','id','client' and 'deleted'
674 """
675
676 if resources is None:
677 resources = cls.method_resources
678 for res in resources:
679 if "deleted" not in res.keys():
680 res["deleted"] = False
681 if "client" not in res.keys():
682 res["client"] = cls.shares_client
683 if not(res["deleted"]):
684 res_id = res['id']
685 client = res["client"]
686 with handle_cleanup_exceptions():
687 if res["type"] is "share":
Yogesh1f931ff2015-09-29 23:41:02 -0400688 cls.clear_share_replicas(res_id)
Andrew Kerrbf31e912015-07-29 10:39:38 -0400689 cg_id = res.get('consistency_group_id')
690 if cg_id:
691 params = {'consistency_group_id': cg_id}
Clinton Knighte5c8f092015-08-27 15:00:23 -0400692 client.delete_share(res_id, params=params)
693 else:
694 client.delete_share(res_id)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200695 client.wait_for_resource_deletion(share_id=res_id)
696 elif res["type"] is "snapshot":
697 client.delete_snapshot(res_id)
698 client.wait_for_resource_deletion(snapshot_id=res_id)
699 elif res["type"] is "share_network":
700 client.delete_share_network(res_id)
701 client.wait_for_resource_deletion(sn_id=res_id)
702 elif res["type"] is "security_service":
703 client.delete_security_service(res_id)
704 client.wait_for_resource_deletion(ss_id=res_id)
705 elif res["type"] is "share_type":
706 client.delete_share_type(res_id)
707 client.wait_for_resource_deletion(st_id=res_id)
Andrew Kerrbf31e912015-07-29 10:39:38 -0400708 elif res["type"] is "consistency_group":
709 client.delete_consistency_group(res_id)
710 client.wait_for_resource_deletion(cg_id=res_id)
711 elif res["type"] is "cgsnapshot":
712 client.delete_cgsnapshot(res_id)
713 client.wait_for_resource_deletion(cgsnapshot_id=res_id)
Yogeshbdb88102015-09-29 23:41:02 -0400714 elif res["type"] is "share_replica":
715 client.delete_share_replica(res_id)
716 client.wait_for_resource_deletion(replica_id=res_id)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200717 else:
huayue97bacbf2016-01-04 09:57:39 +0800718 LOG.warning("Provided unsupported resource type for "
719 "cleanup '%s'. Skipping." % res["type"])
Marc Koderer0abc93b2015-07-15 09:18:35 +0200720 res["deleted"] = True
721
722 @classmethod
723 def generate_share_network_data(self):
724 data = {
725 "name": data_utils.rand_name("sn-name"),
726 "description": data_utils.rand_name("sn-desc"),
727 "neutron_net_id": data_utils.rand_name("net-id"),
728 "neutron_subnet_id": data_utils.rand_name("subnet-id"),
729 }
730 return data
731
732 @classmethod
733 def generate_security_service_data(self):
734 data = {
735 "name": data_utils.rand_name("ss-name"),
736 "description": data_utils.rand_name("ss-desc"),
Valeriy Ponomaryovfcde7712015-12-14 18:06:13 +0200737 "dns_ip": utils.rand_ip(),
738 "server": utils.rand_ip(),
Marc Koderer0abc93b2015-07-15 09:18:35 +0200739 "domain": data_utils.rand_name("ss-domain"),
740 "user": data_utils.rand_name("ss-user"),
741 "password": data_utils.rand_name("ss-password"),
742 }
743 return data
744
745 # Useful assertions
746 def assertDictMatch(self, d1, d2, approx_equal=False, tolerance=0.001):
747 """Assert two dicts are equivalent.
748
749 This is a 'deep' match in the sense that it handles nested
750 dictionaries appropriately.
751
752 NOTE:
753
754 If you don't care (or don't know) a given value, you can specify
755 the string DONTCARE as the value. This will cause that dict-item
756 to be skipped.
757
758 """
759 def raise_assertion(msg):
760 d1str = str(d1)
761 d2str = str(d2)
762 base_msg = ('Dictionaries do not match. %(msg)s d1: %(d1str)s '
763 'd2: %(d2str)s' %
764 {"msg": msg, "d1str": d1str, "d2str": d2str})
765 raise AssertionError(base_msg)
766
767 d1keys = set(d1.keys())
768 d2keys = set(d2.keys())
769 if d1keys != d2keys:
770 d1only = d1keys - d2keys
771 d2only = d2keys - d1keys
772 raise_assertion('Keys in d1 and not d2: %(d1only)s. '
773 'Keys in d2 and not d1: %(d2only)s' %
774 {"d1only": d1only, "d2only": d2only})
775
776 for key in d1keys:
777 d1value = d1[key]
778 d2value = d2[key]
779 try:
780 error = abs(float(d1value) - float(d2value))
781 within_tolerance = error <= tolerance
782 except (ValueError, TypeError):
daiki kato6914b1a2016-03-16 17:16:57 +0900783 # If both values aren't convertible to float, just ignore
Marc Koderer0abc93b2015-07-15 09:18:35 +0200784 # ValueError if arg is a str, TypeError if it's something else
785 # (like None)
786 within_tolerance = False
787
788 if hasattr(d1value, 'keys') and hasattr(d2value, 'keys'):
789 self.assertDictMatch(d1value, d2value)
790 elif 'DONTCARE' in (d1value, d2value):
791 continue
792 elif approx_equal and within_tolerance:
793 continue
794 elif d1value != d2value:
795 raise_assertion("d1['%(key)s']=%(d1value)s != "
796 "d2['%(key)s']=%(d2value)s" %
797 {
798 "key": key,
799 "d1value": d1value,
800 "d2value": d2value
801 })
802
803
804class BaseSharesAltTest(BaseSharesTest):
805 """Base test case class for all Shares Alt API tests."""
806
807 @classmethod
808 def resource_setup(cls):
809 cls.username = CONF.identity.alt_username
810 cls.password = CONF.identity.alt_password
Valeriy Ponomaryov1950cb82016-04-11 14:02:29 +0300811 cls.project_name = CONF.identity.alt_project_name
812 cls.verify_nonempty(cls.username, cls.password, cls.project_name)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200813 cls.os = clients.AltManager()
814 alt_share_network_id = CONF.share.alt_share_network_id
815 cls.os.shares_client.share_network_id = alt_share_network_id
Clinton Knighte5c8f092015-08-27 15:00:23 -0400816 cls.os.shares_v2_client.share_network_id = alt_share_network_id
Marc Koderer0abc93b2015-07-15 09:18:35 +0200817 super(BaseSharesAltTest, cls).resource_setup()
818
819
820class BaseSharesAdminTest(BaseSharesTest):
821 """Base test case class for all Shares Admin API tests."""
822
823 @classmethod
824 def resource_setup(cls):
Sam Wanb5047aa2015-10-08 05:37:43 -0400825 if hasattr(CONF.identity, 'admin_username'):
826 cls.username = CONF.identity.admin_username
827 cls.password = CONF.identity.admin_password
Valeriy Ponomaryov1950cb82016-04-11 14:02:29 +0300828 cls.project_name = CONF.identity.admin_project_name
Sam Wanb5047aa2015-10-08 05:37:43 -0400829 else:
830 cls.username = CONF.auth.admin_username
831 cls.password = CONF.auth.admin_password
Valeriy Ponomaryov1950cb82016-04-11 14:02:29 +0300832 cls.project_name = CONF.auth.admin_project_name
833 cls.verify_nonempty(cls.username, cls.password, cls.project_name)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200834 cls.os = clients.AdminManager()
835 admin_share_network_id = CONF.share.admin_share_network_id
836 cls.os.shares_client.share_network_id = admin_share_network_id
Clinton Knighte5c8f092015-08-27 15:00:23 -0400837 cls.os.shares_v2_client.share_network_id = admin_share_network_id
Marc Koderer0abc93b2015-07-15 09:18:35 +0200838 super(BaseSharesAdminTest, cls).resource_setup()