blob: de7c542ffab6da688f3b0d1a9e458258a21688d8 [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
Andrew Kerr40df1d72015-09-28 13:22:33 -040018import random
Marc Koderer0abc93b2015-07-15 09:18:35 +020019import traceback
20
21from oslo_concurrency import lockutils
22from oslo_log import log
23import six
Valeriy Ponomaryov48a2bd72015-11-05 13:22:44 +020024from tempest.common import dynamic_creds
25from tempest import config
26from tempest import test
Marc Koderer0abc93b2015-07-15 09:18:35 +020027from tempest_lib.common.utils import data_utils
28from tempest_lib import exceptions
Valeriy Ponomaryova14c2252015-10-29 13:34:32 +020029import testtools
Marc Koderer0abc93b2015-07-15 09:18:35 +020030
31from manila_tempest_tests import clients_share as clients
32from manila_tempest_tests import share_exceptions
33
34CONF = config.CONF
35LOG = log.getLogger(__name__)
36
37
Andrew Kerr40df1d72015-09-28 13:22:33 -040038def rand_ip():
39 """This uses the TEST-NET-3 range of reserved IP addresses.
40
41 Using this range, which are reserved solely for use in
42 documentation and example source code, should avoid any potential
43 conflicts in real-world testing.
44 """
45 TEST_NET_3 = '203.0.113.'
46 final_octet = six.text_type(random.randint(0, 255))
47 return TEST_NET_3 + final_octet
48
49
Marc Koderer0abc93b2015-07-15 09:18:35 +020050class handle_cleanup_exceptions(object):
51 """Handle exceptions raised with cleanup operations.
52
53 Always suppress errors when exceptions.NotFound or exceptions.Forbidden
54 are raised.
55 Suppress all other exceptions only in case config opt
56 'suppress_errors_in_cleanup' in config group 'share' is True.
57 """
58
59 def __enter__(self):
60 return self
61
62 def __exit__(self, exc_type, exc_value, exc_traceback):
63 if not (isinstance(exc_value,
64 (exceptions.NotFound, exceptions.Forbidden)) or
65 CONF.share.suppress_errors_in_cleanup):
66 return False # Do not suppress error if any
67 if exc_traceback:
68 LOG.error("Suppressed cleanup error in Manila: "
69 "\n%s" % traceback.format_exc())
70 return True # Suppress error if any
71
72
73def network_synchronized(f):
74
75 def wrapped_func(self, *args, **kwargs):
76 with_isolated_creds = True if len(args) > 2 else False
77 no_lock_required = kwargs.get(
78 "isolated_creds_client", with_isolated_creds)
79 if no_lock_required:
80 # Usage of not reusable network. No need in lock.
81 return f(self, *args, **kwargs)
82
83 # Use lock assuming reusage of common network.
84 @lockutils.synchronized("manila_network_lock", external=True)
85 def source_func(self, *args, **kwargs):
86 return f(self, *args, **kwargs)
87
88 return source_func(self, *args, **kwargs)
89
90 return wrapped_func
91
92
Valeriy Ponomaryova14c2252015-10-29 13:34:32 +020093def is_microversion_supported(microversion):
94 if (float(microversion) > float(CONF.share.max_api_microversion) or
95 float(microversion) < float(CONF.share.min_api_microversion)):
96 return False
97 return True
98
99
100def skip_if_microversion_not_supported(microversion):
101 """Decorator for tests that are microversion-specific."""
102 if not is_microversion_supported(microversion):
103 reason = ("Skipped. Test requires microversion '%s'." % microversion)
104 return testtools.skip(reason)
105 return lambda f: f
106
107
Marc Koderer0abc93b2015-07-15 09:18:35 +0200108class BaseSharesTest(test.BaseTestCase):
109 """Base test case class for all Manila API tests."""
110
111 force_tenant_isolation = False
112 protocols = ["nfs", "cifs", "glusterfs", "hdfs"]
113
114 # Will be cleaned up in resource_cleanup
115 class_resources = []
116
117 # Will be cleaned up in tearDown method
118 method_resources = []
119
120 # Will be cleaned up in resource_cleanup
121 class_isolated_creds = []
122
123 # Will be cleaned up in tearDown method
124 method_isolated_creds = []
125
Valeriy Ponomaryova14c2252015-10-29 13:34:32 +0200126 def skip_if_microversion_not_supported(self, microversion):
127 if not is_microversion_supported(microversion):
128 raise self.skipException(
129 "Microversion '%s' is not supported." % microversion)
130
Marc Koderer0abc93b2015-07-15 09:18:35 +0200131 @classmethod
132 def get_client_with_isolated_creds(cls,
133 name=None,
134 type_of_creds="admin",
Clinton Knighte5c8f092015-08-27 15:00:23 -0400135 cleanup_in_class=False,
136 client_version='1'):
Marc Koderer0abc93b2015-07-15 09:18:35 +0200137 """Creates isolated creds.
138
139 :param name: name, will be used for naming ic and related stuff
140 :param type_of_creds: admin, alt or primary
141 :param cleanup_in_class: defines place where to delete
142 :returns: SharesClient -- shares client with isolated creds.
143 :returns: To client added dict attr 'creds' with
144 :returns: key elements 'tenant' and 'user'.
145 """
146 if name is None:
147 # Get name of test method
148 name = inspect.stack()[1][3]
149 if len(name) > 32:
150 name = name[0:32]
151
152 # Choose type of isolated creds
Valeriy Ponomaryov48a2bd72015-11-05 13:22:44 +0200153 ic = dynamic_creds.DynamicCredentialProvider(
154 identity_version=CONF.identity.auth_version,
155 name=name,
156 admin_role=CONF.identity.admin_role)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200157 if "admin" in type_of_creds:
158 creds = ic.get_admin_creds()
159 elif "alt" in type_of_creds:
160 creds = ic.get_alt_creds()
161 else:
162 creds = ic.self.get_credentials(type_of_creds)
163 ic.type_of_creds = type_of_creds
164
165 # create client with isolated creds
166 os = clients.Manager(credentials=creds)
Clinton Knighte5c8f092015-08-27 15:00:23 -0400167 if client_version == '1':
168 client = os.shares_client
169 elif client_version == '2':
170 client = os.shares_v2_client
Marc Koderer0abc93b2015-07-15 09:18:35 +0200171
172 # Set place where will be deleted isolated creds
173 ic_res = {
Valeriy Ponomaryov48a2bd72015-11-05 13:22:44 +0200174 "method": ic.clear_creds,
Marc Koderer0abc93b2015-07-15 09:18:35 +0200175 "deleted": False,
176 }
177 if cleanup_in_class:
178 cls.class_isolated_creds.insert(0, ic_res)
179 else:
180 cls.method_isolated_creds.insert(0, ic_res)
181
182 # Provide share network
183 if CONF.share.multitenancy_enabled:
184 if not CONF.service_available.neutron:
185 raise cls.skipException("Neutron support is required")
Valeriy Ponomaryov48a2bd72015-11-05 13:22:44 +0200186 nc = os.networks_client
Marc Koderer0abc93b2015-07-15 09:18:35 +0200187 share_network_id = cls.provide_share_network(client, nc, ic)
188 client.share_network_id = share_network_id
189 resource = {
190 "type": "share_network",
191 "id": client.share_network_id,
192 "client": client,
193 }
194 if cleanup_in_class:
195 cls.class_resources.insert(0, resource)
196 else:
197 cls.method_resources.insert(0, resource)
198 return client
199
200 @classmethod
201 def verify_nonempty(cls, *args):
202 if not all(args):
203 msg = "Missing API credentials in configuration."
204 raise cls.skipException(msg)
205
206 @classmethod
207 def resource_setup(cls):
208 if not (any(p in CONF.share.enable_protocols
209 for p in cls.protocols) and
210 CONF.service_available.manila):
211 skip_msg = "Manila is disabled"
212 raise cls.skipException(skip_msg)
213 super(BaseSharesTest, cls).resource_setup()
214 if not hasattr(cls, "os"):
215 cls.username = CONF.identity.username
216 cls.password = CONF.identity.password
217 cls.tenant_name = CONF.identity.tenant_name
218 cls.verify_nonempty(cls.username, cls.password, cls.tenant_name)
219 cls.os = clients.Manager()
220 if CONF.share.multitenancy_enabled:
221 if not CONF.service_available.neutron:
222 raise cls.skipException("Neutron support is required")
223 sc = cls.os.shares_client
Valeriy Ponomaryov48a2bd72015-11-05 13:22:44 +0200224 nc = cls.os.networks_client
Marc Koderer0abc93b2015-07-15 09:18:35 +0200225 share_network_id = cls.provide_share_network(sc, nc)
226 cls.os.shares_client.share_network_id = share_network_id
Clinton Knighte5c8f092015-08-27 15:00:23 -0400227 cls.os.shares_v2_client.share_network_id = share_network_id
Marc Koderer0abc93b2015-07-15 09:18:35 +0200228 cls.shares_client = cls.os.shares_client
Clinton Knighte5c8f092015-08-27 15:00:23 -0400229 cls.shares_v2_client = cls.os.shares_v2_client
Marc Koderer0abc93b2015-07-15 09:18:35 +0200230
231 def setUp(self):
232 super(BaseSharesTest, self).setUp()
233 self.addCleanup(self.clear_resources)
234 self.addCleanup(self.clear_isolated_creds)
235
236 @classmethod
237 def resource_cleanup(cls):
238 super(BaseSharesTest, cls).resource_cleanup()
239 cls.clear_resources(cls.class_resources)
240 cls.clear_isolated_creds(cls.class_isolated_creds)
241
242 @classmethod
243 @network_synchronized
Valeriy Ponomaryov48a2bd72015-11-05 13:22:44 +0200244 def provide_share_network(cls, shares_client, networks_client,
Marc Koderer0abc93b2015-07-15 09:18:35 +0200245 isolated_creds_client=None):
246 """Used for finding/creating share network for multitenant driver.
247
248 This method creates/gets entity share-network for one tenant. This
249 share-network will be used for creation of service vm.
250
251 :param shares_client: shares client, which requires share-network
Valeriy Ponomaryov48a2bd72015-11-05 13:22:44 +0200252 :param networks_client: network client from same tenant as shares
253 :param isolated_creds_client: DynamicCredentialProvider instance
Marc Koderer0abc93b2015-07-15 09:18:35 +0200254 If provided, then its networking will be used if needed.
255 If not provided, then common network will be used if needed.
256 :returns: str -- share network id for shares_client tenant
257 :returns: None -- if single-tenant driver used
258 """
259
260 sc = shares_client
261
262 if not CONF.share.multitenancy_enabled:
263 # Assumed usage of a single-tenant driver
264 share_network_id = None
265 elif sc.share_network_id:
266 # Share-network already exists, use it
267 share_network_id = sc.share_network_id
268 else:
269 net_id = subnet_id = share_network_id = None
270
271 if not isolated_creds_client:
272 # Search for networks, created in previous runs
273 search_word = "reusable"
274 sn_name = "autogenerated_by_tempest_%s" % search_word
275 service_net_name = "share-service"
Valeriy Ponomaryov48a2bd72015-11-05 13:22:44 +0200276 networks = networks_client.list_networks()
Marc Koderer0abc93b2015-07-15 09:18:35 +0200277 if "networks" in networks.keys():
278 networks = networks["networks"]
279 for network in networks:
280 if (service_net_name in network["name"] and
281 sc.tenant_id == network['tenant_id']):
282 net_id = network["id"]
283 if len(network["subnets"]) > 0:
284 subnet_id = network["subnets"][0]
285 break
286
287 # Create suitable network
288 if (net_id is None or subnet_id is None):
Valeriy Ponomaryov48a2bd72015-11-05 13:22:44 +0200289 ic = dynamic_creds.DynamicCredentialProvider(
290 identity_version=CONF.identity.auth_version,
291 name=service_net_name,
292 admin_role=CONF.identity.admin_role,
293 )
Marc Koderer0abc93b2015-07-15 09:18:35 +0200294 net_data = ic._create_network_resources(sc.tenant_id)
295 network, subnet, router = net_data
296 net_id = network["id"]
297 subnet_id = subnet["id"]
298
299 # Try get suitable share-network
300 share_networks = sc.list_share_networks_with_detail()
301 for sn in share_networks:
302 if (net_id == sn["neutron_net_id"] and
303 subnet_id == sn["neutron_subnet_id"] and
304 sn["name"] and search_word in sn["name"]):
305 share_network_id = sn["id"]
306 break
307 else:
308 sn_name = "autogenerated_by_tempest_for_isolated_creds"
309 # Use precreated network and subnet from isolated creds
310 net_id = isolated_creds_client.get_credentials(
311 isolated_creds_client.type_of_creds).network['id']
312 subnet_id = isolated_creds_client.get_credentials(
313 isolated_creds_client.type_of_creds).subnet['id']
314
315 # Create suitable share-network
316 if share_network_id is None:
317 sn_desc = "This share-network was created by tempest"
318 sn = sc.create_share_network(name=sn_name,
319 description=sn_desc,
320 neutron_net_id=net_id,
321 neutron_subnet_id=subnet_id)
322 share_network_id = sn["id"]
323
324 return share_network_id
325
326 @classmethod
327 def _create_share(cls, share_protocol=None, size=1, name=None,
328 snapshot_id=None, description=None, metadata=None,
329 share_network_id=None, share_type_id=None,
Andrew Kerrbf31e912015-07-29 10:39:38 -0400330 consistency_group_id=None, client=None,
Clinton Knighte5c8f092015-08-27 15:00:23 -0400331 cleanup_in_class=True, is_public=False, **kwargs):
Valeriy Ponomaryov1aaa72d2015-09-08 12:59:41 +0300332 client = client or cls.shares_v2_client
Marc Koderer0abc93b2015-07-15 09:18:35 +0200333 description = description or "Tempest's share"
334 share_network_id = share_network_id or client.share_network_id or None
335 metadata = metadata or {}
Clinton Knighte5c8f092015-08-27 15:00:23 -0400336 kwargs.update({
Marc Koderer0abc93b2015-07-15 09:18:35 +0200337 'share_protocol': share_protocol,
338 'size': size,
339 'name': name,
340 'snapshot_id': snapshot_id,
341 'description': description,
342 'metadata': metadata,
343 'share_network_id': share_network_id,
344 'share_type_id': share_type_id,
345 'is_public': is_public,
Clinton Knighte5c8f092015-08-27 15:00:23 -0400346 })
Andrew Kerrbf31e912015-07-29 10:39:38 -0400347 if consistency_group_id:
348 kwargs['consistency_group_id'] = consistency_group_id
349
Marc Koderer0abc93b2015-07-15 09:18:35 +0200350 share = client.create_share(**kwargs)
Andrew Kerrbf31e912015-07-29 10:39:38 -0400351 resource = {"type": "share", "id": share["id"], "client": client,
352 "consistency_group_id": consistency_group_id}
Marc Koderer0abc93b2015-07-15 09:18:35 +0200353 cleanup_list = (cls.class_resources if cleanup_in_class else
354 cls.method_resources)
355 cleanup_list.insert(0, resource)
356 return share
357
358 @classmethod
Clinton Knighte5c8f092015-08-27 15:00:23 -0400359 def migrate_share(cls, share_id, dest_host, client=None, **kwargs):
360 client = client or cls.shares_v2_client
361 client.migrate_share(share_id, dest_host, **kwargs)
Rodrigo Barbierib7137ad2015-09-06 22:53:16 -0300362 share = client.wait_for_migration_completed(share_id, dest_host)
363 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
Marc Koderer0abc93b2015-07-15 09:18:35 +0200512 def create_share_network(cls, client=None,
513 cleanup_in_class=False, **kwargs):
514 if client is None:
515 client = cls.shares_client
516 share_network = client.create_share_network(**kwargs)
517 resource = {
518 "type": "share_network",
519 "id": share_network["id"],
520 "client": client,
521 }
522 if cleanup_in_class:
523 cls.class_resources.insert(0, resource)
524 else:
525 cls.method_resources.insert(0, resource)
526 return share_network
527
528 @classmethod
529 def create_security_service(cls, ss_type="ldap", client=None,
530 cleanup_in_class=False, **kwargs):
531 if client is None:
532 client = cls.shares_client
533 security_service = client.create_security_service(ss_type, **kwargs)
534 resource = {
535 "type": "security_service",
536 "id": security_service["id"],
537 "client": client,
538 }
539 if cleanup_in_class:
540 cls.class_resources.insert(0, resource)
541 else:
542 cls.method_resources.insert(0, resource)
543 return security_service
544
545 @classmethod
546 def create_share_type(cls, name, is_public=True, client=None,
547 cleanup_in_class=True, **kwargs):
548 if client is None:
Valeriy Ponomaryova14c2252015-10-29 13:34:32 +0200549 client = cls.shares_v2_client
Marc Koderer0abc93b2015-07-15 09:18:35 +0200550 share_type = client.create_share_type(name, is_public, **kwargs)
551 resource = {
552 "type": "share_type",
553 "id": share_type["share_type"]["id"],
554 "client": client,
555 }
556 if cleanup_in_class:
557 cls.class_resources.insert(0, resource)
558 else:
559 cls.method_resources.insert(0, resource)
560 return share_type
561
562 @staticmethod
563 def add_required_extra_specs_to_dict(extra_specs=None):
Valeriy Ponomaryovad55dc52015-09-23 13:54:00 +0300564 dhss = six.text_type(CONF.share.multitenancy_enabled)
565 snapshot_support = six.text_type(
566 CONF.share.capability_snapshot_support)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200567 required = {
Valeriy Ponomaryovad55dc52015-09-23 13:54:00 +0300568 "driver_handles_share_servers": dhss,
569 "snapshot_support": snapshot_support,
Marc Koderer0abc93b2015-07-15 09:18:35 +0200570 }
571 if extra_specs:
572 required.update(extra_specs)
573 return required
574
575 @classmethod
576 def clear_isolated_creds(cls, creds=None):
577 if creds is None:
578 creds = cls.method_isolated_creds
579 for ic in creds:
580 if "deleted" not in ic.keys():
581 ic["deleted"] = False
582 if not ic["deleted"]:
583 with handle_cleanup_exceptions():
584 ic["method"]()
585 ic["deleted"] = True
586
587 @classmethod
588 def clear_resources(cls, resources=None):
589 """Deletes resources, that were created in test suites.
590
591 This method tries to remove resources from resource list,
592 if it is not found, assumed it was deleted in test itself.
593 It is expected, that all resources were added as LIFO
594 due to restriction of deletion resources, that is in the chain.
595
596 :param resources: dict with keys 'type','id','client' and 'deleted'
597 """
598
599 if resources is None:
600 resources = cls.method_resources
601 for res in resources:
602 if "deleted" not in res.keys():
603 res["deleted"] = False
604 if "client" not in res.keys():
605 res["client"] = cls.shares_client
606 if not(res["deleted"]):
607 res_id = res['id']
608 client = res["client"]
609 with handle_cleanup_exceptions():
610 if res["type"] is "share":
Andrew Kerrbf31e912015-07-29 10:39:38 -0400611 cg_id = res.get('consistency_group_id')
612 if cg_id:
613 params = {'consistency_group_id': cg_id}
Clinton Knighte5c8f092015-08-27 15:00:23 -0400614 client.delete_share(res_id, params=params)
615 else:
616 client.delete_share(res_id)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200617 client.wait_for_resource_deletion(share_id=res_id)
618 elif res["type"] is "snapshot":
619 client.delete_snapshot(res_id)
620 client.wait_for_resource_deletion(snapshot_id=res_id)
621 elif res["type"] is "share_network":
622 client.delete_share_network(res_id)
623 client.wait_for_resource_deletion(sn_id=res_id)
624 elif res["type"] is "security_service":
625 client.delete_security_service(res_id)
626 client.wait_for_resource_deletion(ss_id=res_id)
627 elif res["type"] is "share_type":
628 client.delete_share_type(res_id)
629 client.wait_for_resource_deletion(st_id=res_id)
Andrew Kerrbf31e912015-07-29 10:39:38 -0400630 elif res["type"] is "consistency_group":
631 client.delete_consistency_group(res_id)
632 client.wait_for_resource_deletion(cg_id=res_id)
633 elif res["type"] is "cgsnapshot":
634 client.delete_cgsnapshot(res_id)
635 client.wait_for_resource_deletion(cgsnapshot_id=res_id)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200636 else:
637 LOG.warn("Provided unsupported resource type for "
638 "cleanup '%s'. Skipping." % res["type"])
639 res["deleted"] = True
640
641 @classmethod
642 def generate_share_network_data(self):
643 data = {
644 "name": data_utils.rand_name("sn-name"),
645 "description": data_utils.rand_name("sn-desc"),
646 "neutron_net_id": data_utils.rand_name("net-id"),
647 "neutron_subnet_id": data_utils.rand_name("subnet-id"),
648 }
649 return data
650
651 @classmethod
652 def generate_security_service_data(self):
653 data = {
654 "name": data_utils.rand_name("ss-name"),
655 "description": data_utils.rand_name("ss-desc"),
Andrew Kerr40df1d72015-09-28 13:22:33 -0400656 "dns_ip": rand_ip(),
657 "server": rand_ip(),
Marc Koderer0abc93b2015-07-15 09:18:35 +0200658 "domain": data_utils.rand_name("ss-domain"),
659 "user": data_utils.rand_name("ss-user"),
660 "password": data_utils.rand_name("ss-password"),
661 }
662 return data
663
664 # Useful assertions
665 def assertDictMatch(self, d1, d2, approx_equal=False, tolerance=0.001):
666 """Assert two dicts are equivalent.
667
668 This is a 'deep' match in the sense that it handles nested
669 dictionaries appropriately.
670
671 NOTE:
672
673 If you don't care (or don't know) a given value, you can specify
674 the string DONTCARE as the value. This will cause that dict-item
675 to be skipped.
676
677 """
678 def raise_assertion(msg):
679 d1str = str(d1)
680 d2str = str(d2)
681 base_msg = ('Dictionaries do not match. %(msg)s d1: %(d1str)s '
682 'd2: %(d2str)s' %
683 {"msg": msg, "d1str": d1str, "d2str": d2str})
684 raise AssertionError(base_msg)
685
686 d1keys = set(d1.keys())
687 d2keys = set(d2.keys())
688 if d1keys != d2keys:
689 d1only = d1keys - d2keys
690 d2only = d2keys - d1keys
691 raise_assertion('Keys in d1 and not d2: %(d1only)s. '
692 'Keys in d2 and not d1: %(d2only)s' %
693 {"d1only": d1only, "d2only": d2only})
694
695 for key in d1keys:
696 d1value = d1[key]
697 d2value = d2[key]
698 try:
699 error = abs(float(d1value) - float(d2value))
700 within_tolerance = error <= tolerance
701 except (ValueError, TypeError):
702 # If both values aren't convertable to float, just ignore
703 # ValueError if arg is a str, TypeError if it's something else
704 # (like None)
705 within_tolerance = False
706
707 if hasattr(d1value, 'keys') and hasattr(d2value, 'keys'):
708 self.assertDictMatch(d1value, d2value)
709 elif 'DONTCARE' in (d1value, d2value):
710 continue
711 elif approx_equal and within_tolerance:
712 continue
713 elif d1value != d2value:
714 raise_assertion("d1['%(key)s']=%(d1value)s != "
715 "d2['%(key)s']=%(d2value)s" %
716 {
717 "key": key,
718 "d1value": d1value,
719 "d2value": d2value
720 })
721
722
723class BaseSharesAltTest(BaseSharesTest):
724 """Base test case class for all Shares Alt API tests."""
725
726 @classmethod
727 def resource_setup(cls):
728 cls.username = CONF.identity.alt_username
729 cls.password = CONF.identity.alt_password
730 cls.tenant_name = CONF.identity.alt_tenant_name
731 cls.verify_nonempty(cls.username, cls.password, cls.tenant_name)
732 cls.os = clients.AltManager()
733 alt_share_network_id = CONF.share.alt_share_network_id
734 cls.os.shares_client.share_network_id = alt_share_network_id
Clinton Knighte5c8f092015-08-27 15:00:23 -0400735 cls.os.shares_v2_client.share_network_id = alt_share_network_id
Marc Koderer0abc93b2015-07-15 09:18:35 +0200736 super(BaseSharesAltTest, cls).resource_setup()
737
738
739class BaseSharesAdminTest(BaseSharesTest):
740 """Base test case class for all Shares Admin API tests."""
741
742 @classmethod
743 def resource_setup(cls):
Sam Wanb5047aa2015-10-08 05:37:43 -0400744 if hasattr(CONF.identity, 'admin_username'):
745 cls.username = CONF.identity.admin_username
746 cls.password = CONF.identity.admin_password
747 cls.tenant_name = CONF.identity.admin_tenant_name
748 else:
749 cls.username = CONF.auth.admin_username
750 cls.password = CONF.auth.admin_password
751 cls.tenant_name = CONF.auth.admin_tenant_name
Marc Koderer0abc93b2015-07-15 09:18:35 +0200752 cls.verify_nonempty(cls.username, cls.password, cls.tenant_name)
753 cls.os = clients.AdminManager()
754 admin_share_network_id = CONF.share.admin_share_network_id
755 cls.os.shares_client.share_network_id = admin_share_network_id
Clinton Knighte5c8f092015-08-27 15:00:23 -0400756 cls.os.shares_v2_client.share_network_id = admin_share_network_id
Marc Koderer0abc93b2015-07-15 09:18:35 +0200757 super(BaseSharesAdminTest, cls).resource_setup()