blob: 825b6e0fa36814090eeb84360f9e5066b4e3e2fa [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
23from tempest.common import isolated_creds # noqa
24from tempest import config # noqa
25from tempest import test # noqa
26from tempest_lib.common.utils import data_utils
27from tempest_lib import exceptions
28
29from manila_tempest_tests import clients_share as clients
30from manila_tempest_tests import share_exceptions
31
32CONF = config.CONF
33LOG = log.getLogger(__name__)
34
35
36class handle_cleanup_exceptions(object):
37 """Handle exceptions raised with cleanup operations.
38
39 Always suppress errors when exceptions.NotFound or exceptions.Forbidden
40 are raised.
41 Suppress all other exceptions only in case config opt
42 'suppress_errors_in_cleanup' in config group 'share' is True.
43 """
44
45 def __enter__(self):
46 return self
47
48 def __exit__(self, exc_type, exc_value, exc_traceback):
49 if not (isinstance(exc_value,
50 (exceptions.NotFound, exceptions.Forbidden)) or
51 CONF.share.suppress_errors_in_cleanup):
52 return False # Do not suppress error if any
53 if exc_traceback:
54 LOG.error("Suppressed cleanup error in Manila: "
55 "\n%s" % traceback.format_exc())
56 return True # Suppress error if any
57
58
59def network_synchronized(f):
60
61 def wrapped_func(self, *args, **kwargs):
62 with_isolated_creds = True if len(args) > 2 else False
63 no_lock_required = kwargs.get(
64 "isolated_creds_client", with_isolated_creds)
65 if no_lock_required:
66 # Usage of not reusable network. No need in lock.
67 return f(self, *args, **kwargs)
68
69 # Use lock assuming reusage of common network.
70 @lockutils.synchronized("manila_network_lock", external=True)
71 def source_func(self, *args, **kwargs):
72 return f(self, *args, **kwargs)
73
74 return source_func(self, *args, **kwargs)
75
76 return wrapped_func
77
78
79class BaseSharesTest(test.BaseTestCase):
80 """Base test case class for all Manila API tests."""
81
82 force_tenant_isolation = False
83 protocols = ["nfs", "cifs", "glusterfs", "hdfs"]
84
85 # Will be cleaned up in resource_cleanup
86 class_resources = []
87
88 # Will be cleaned up in tearDown method
89 method_resources = []
90
91 # Will be cleaned up in resource_cleanup
92 class_isolated_creds = []
93
94 # Will be cleaned up in tearDown method
95 method_isolated_creds = []
96
97 @classmethod
98 def get_client_with_isolated_creds(cls,
99 name=None,
100 type_of_creds="admin",
Clinton Knighte5c8f092015-08-27 15:00:23 -0400101 cleanup_in_class=False,
102 client_version='1'):
Marc Koderer0abc93b2015-07-15 09:18:35 +0200103 """Creates isolated creds.
104
105 :param name: name, will be used for naming ic and related stuff
106 :param type_of_creds: admin, alt or primary
107 :param cleanup_in_class: defines place where to delete
108 :returns: SharesClient -- shares client with isolated creds.
109 :returns: To client added dict attr 'creds' with
110 :returns: key elements 'tenant' and 'user'.
111 """
112 if name is None:
113 # Get name of test method
114 name = inspect.stack()[1][3]
115 if len(name) > 32:
116 name = name[0:32]
117
118 # Choose type of isolated creds
119 ic = isolated_creds.IsolatedCreds(name=name)
120 if "admin" in type_of_creds:
121 creds = ic.get_admin_creds()
122 elif "alt" in type_of_creds:
123 creds = ic.get_alt_creds()
124 else:
125 creds = ic.self.get_credentials(type_of_creds)
126 ic.type_of_creds = type_of_creds
127
128 # create client with isolated creds
129 os = clients.Manager(credentials=creds)
Clinton Knighte5c8f092015-08-27 15:00:23 -0400130 if client_version == '1':
131 client = os.shares_client
132 elif client_version == '2':
133 client = os.shares_v2_client
Marc Koderer0abc93b2015-07-15 09:18:35 +0200134
135 # Set place where will be deleted isolated creds
136 ic_res = {
137 "method": ic.clear_isolated_creds,
138 "deleted": False,
139 }
140 if cleanup_in_class:
141 cls.class_isolated_creds.insert(0, ic_res)
142 else:
143 cls.method_isolated_creds.insert(0, ic_res)
144
145 # Provide share network
146 if CONF.share.multitenancy_enabled:
147 if not CONF.service_available.neutron:
148 raise cls.skipException("Neutron support is required")
149 nc = os.network_client
150 share_network_id = cls.provide_share_network(client, nc, ic)
151 client.share_network_id = share_network_id
152 resource = {
153 "type": "share_network",
154 "id": client.share_network_id,
155 "client": client,
156 }
157 if cleanup_in_class:
158 cls.class_resources.insert(0, resource)
159 else:
160 cls.method_resources.insert(0, resource)
161 return client
162
163 @classmethod
164 def verify_nonempty(cls, *args):
165 if not all(args):
166 msg = "Missing API credentials in configuration."
167 raise cls.skipException(msg)
168
169 @classmethod
170 def resource_setup(cls):
171 if not (any(p in CONF.share.enable_protocols
172 for p in cls.protocols) and
173 CONF.service_available.manila):
174 skip_msg = "Manila is disabled"
175 raise cls.skipException(skip_msg)
176 super(BaseSharesTest, cls).resource_setup()
177 if not hasattr(cls, "os"):
178 cls.username = CONF.identity.username
179 cls.password = CONF.identity.password
180 cls.tenant_name = CONF.identity.tenant_name
181 cls.verify_nonempty(cls.username, cls.password, cls.tenant_name)
182 cls.os = clients.Manager()
183 if CONF.share.multitenancy_enabled:
184 if not CONF.service_available.neutron:
185 raise cls.skipException("Neutron support is required")
186 sc = cls.os.shares_client
187 nc = cls.os.network_client
188 share_network_id = cls.provide_share_network(sc, nc)
189 cls.os.shares_client.share_network_id = share_network_id
Clinton Knighte5c8f092015-08-27 15:00:23 -0400190 cls.os.shares_v2_client.share_network_id = share_network_id
Marc Koderer0abc93b2015-07-15 09:18:35 +0200191 cls.shares_client = cls.os.shares_client
Clinton Knighte5c8f092015-08-27 15:00:23 -0400192 cls.shares_v2_client = cls.os.shares_v2_client
Marc Koderer0abc93b2015-07-15 09:18:35 +0200193
194 def setUp(self):
195 super(BaseSharesTest, self).setUp()
196 self.addCleanup(self.clear_resources)
197 self.addCleanup(self.clear_isolated_creds)
198
199 @classmethod
200 def resource_cleanup(cls):
201 super(BaseSharesTest, cls).resource_cleanup()
202 cls.clear_resources(cls.class_resources)
203 cls.clear_isolated_creds(cls.class_isolated_creds)
204
205 @classmethod
206 @network_synchronized
207 def provide_share_network(cls, shares_client, network_client,
208 isolated_creds_client=None):
209 """Used for finding/creating share network for multitenant driver.
210
211 This method creates/gets entity share-network for one tenant. This
212 share-network will be used for creation of service vm.
213
214 :param shares_client: shares client, which requires share-network
215 :param network_client: network client from same tenant as shares
216 :param isolated_creds_client: IsolatedCreds instance
217 If provided, then its networking will be used if needed.
218 If not provided, then common network will be used if needed.
219 :returns: str -- share network id for shares_client tenant
220 :returns: None -- if single-tenant driver used
221 """
222
223 sc = shares_client
224
225 if not CONF.share.multitenancy_enabled:
226 # Assumed usage of a single-tenant driver
227 share_network_id = None
228 elif sc.share_network_id:
229 # Share-network already exists, use it
230 share_network_id = sc.share_network_id
231 else:
232 net_id = subnet_id = share_network_id = None
233
234 if not isolated_creds_client:
235 # Search for networks, created in previous runs
236 search_word = "reusable"
237 sn_name = "autogenerated_by_tempest_%s" % search_word
238 service_net_name = "share-service"
239 networks = network_client.list_networks()
240 if "networks" in networks.keys():
241 networks = networks["networks"]
242 for network in networks:
243 if (service_net_name in network["name"] and
244 sc.tenant_id == network['tenant_id']):
245 net_id = network["id"]
246 if len(network["subnets"]) > 0:
247 subnet_id = network["subnets"][0]
248 break
249
250 # Create suitable network
251 if (net_id is None or subnet_id is None):
252 ic = isolated_creds.IsolatedCreds(name=service_net_name)
253 net_data = ic._create_network_resources(sc.tenant_id)
254 network, subnet, router = net_data
255 net_id = network["id"]
256 subnet_id = subnet["id"]
257
258 # Try get suitable share-network
259 share_networks = sc.list_share_networks_with_detail()
260 for sn in share_networks:
261 if (net_id == sn["neutron_net_id"] and
262 subnet_id == sn["neutron_subnet_id"] and
263 sn["name"] and search_word in sn["name"]):
264 share_network_id = sn["id"]
265 break
266 else:
267 sn_name = "autogenerated_by_tempest_for_isolated_creds"
268 # Use precreated network and subnet from isolated creds
269 net_id = isolated_creds_client.get_credentials(
270 isolated_creds_client.type_of_creds).network['id']
271 subnet_id = isolated_creds_client.get_credentials(
272 isolated_creds_client.type_of_creds).subnet['id']
273
274 # Create suitable share-network
275 if share_network_id is None:
276 sn_desc = "This share-network was created by tempest"
277 sn = sc.create_share_network(name=sn_name,
278 description=sn_desc,
279 neutron_net_id=net_id,
280 neutron_subnet_id=subnet_id)
281 share_network_id = sn["id"]
282
283 return share_network_id
284
285 @classmethod
286 def _create_share(cls, share_protocol=None, size=1, name=None,
287 snapshot_id=None, description=None, metadata=None,
288 share_network_id=None, share_type_id=None,
Andrew Kerrbf31e912015-07-29 10:39:38 -0400289 consistency_group_id=None, client=None,
Clinton Knighte5c8f092015-08-27 15:00:23 -0400290 cleanup_in_class=True, is_public=False, **kwargs):
Valeriy Ponomaryov1aaa72d2015-09-08 12:59:41 +0300291 client = client or cls.shares_v2_client
Marc Koderer0abc93b2015-07-15 09:18:35 +0200292 description = description or "Tempest's share"
293 share_network_id = share_network_id or client.share_network_id or None
294 metadata = metadata or {}
Clinton Knighte5c8f092015-08-27 15:00:23 -0400295 kwargs.update({
Marc Koderer0abc93b2015-07-15 09:18:35 +0200296 'share_protocol': share_protocol,
297 'size': size,
298 'name': name,
299 'snapshot_id': snapshot_id,
300 'description': description,
301 'metadata': metadata,
302 'share_network_id': share_network_id,
303 'share_type_id': share_type_id,
304 'is_public': is_public,
Clinton Knighte5c8f092015-08-27 15:00:23 -0400305 })
Andrew Kerrbf31e912015-07-29 10:39:38 -0400306 if consistency_group_id:
307 kwargs['consistency_group_id'] = consistency_group_id
308
Marc Koderer0abc93b2015-07-15 09:18:35 +0200309 share = client.create_share(**kwargs)
Andrew Kerrbf31e912015-07-29 10:39:38 -0400310 resource = {"type": "share", "id": share["id"], "client": client,
311 "consistency_group_id": consistency_group_id}
Marc Koderer0abc93b2015-07-15 09:18:35 +0200312 cleanup_list = (cls.class_resources if cleanup_in_class else
313 cls.method_resources)
314 cleanup_list.insert(0, resource)
315 return share
316
317 @classmethod
Clinton Knighte5c8f092015-08-27 15:00:23 -0400318 def migrate_share(cls, share_id, dest_host, client=None, **kwargs):
319 client = client or cls.shares_v2_client
320 client.migrate_share(share_id, dest_host, **kwargs)
Rodrigo Barbierib7137ad2015-09-06 22:53:16 -0300321 share = client.wait_for_migration_completed(share_id, dest_host)
322 return share
323
324 @classmethod
Marc Koderer0abc93b2015-07-15 09:18:35 +0200325 def create_share(cls, *args, **kwargs):
326 """Create one share and wait for available state. Retry if allowed."""
327 result = cls.create_shares([{"args": args, "kwargs": kwargs}])
328 return result[0]
329
330 @classmethod
331 def create_shares(cls, share_data_list):
332 """Creates several shares in parallel with retries.
333
334 Use this method when you want to create more than one share at same
335 time. Especially if config option 'share.share_creation_retry_number'
336 has value more than zero (0).
337 All shares will be expected to have 'available' status with or without
338 recreation else error will be raised.
339
340 :param share_data_list: list -- list of dictionaries with 'args' and
341 'kwargs' for '_create_share' method of this base class.
342 example of data:
343 share_data_list=[{'args': ['quuz'], 'kwargs': {'foo': 'bar'}}}]
344 :returns: list -- list of shares created using provided data.
345 """
346
347 data = [copy.deepcopy(d) for d in share_data_list]
348 for d in data:
349 if not isinstance(d, dict):
350 raise exceptions.TempestException(
351 "Expected 'dict', got '%s'" % type(d))
352 if "args" not in d:
353 d["args"] = []
354 if "kwargs" not in d:
355 d["kwargs"] = {}
356 if len(d) > 2:
357 raise exceptions.TempestException(
358 "Expected only 'args' and 'kwargs' keys. "
359 "Provided %s" % list(d))
360 d["kwargs"]["client"] = d["kwargs"].get(
Valeriy Ponomaryov1aaa72d2015-09-08 12:59:41 +0300361 "client", cls.shares_v2_client)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200362 d["share"] = cls._create_share(*d["args"], **d["kwargs"])
363 d["cnt"] = 0
364 d["available"] = False
365
366 while not all(d["available"] for d in data):
367 for d in data:
368 if d["available"]:
369 continue
370 try:
371 d["kwargs"]["client"].wait_for_share_status(
372 d["share"]["id"], "available")
373 d["available"] = True
374 except (share_exceptions.ShareBuildErrorException,
375 exceptions.TimeoutException) as e:
376 if CONF.share.share_creation_retry_number > d["cnt"]:
377 d["cnt"] += 1
378 msg = ("Share '%s' failed to be built. "
379 "Trying create another." % d["share"]["id"])
380 LOG.error(msg)
381 LOG.error(e)
382 d["share"] = cls._create_share(
383 *d["args"], **d["kwargs"])
384 else:
385 raise e
386
387 return [d["share"] for d in data]
388
389 @classmethod
Andrew Kerrbf31e912015-07-29 10:39:38 -0400390 def create_consistency_group(cls, client=None, cleanup_in_class=True,
391 share_network_id=None, **kwargs):
Clinton Knighte5c8f092015-08-27 15:00:23 -0400392 client = client or cls.shares_v2_client
Andrew Kerrbf31e912015-07-29 10:39:38 -0400393 kwargs['share_network_id'] = (share_network_id or
394 client.share_network_id or None)
395 consistency_group = client.create_consistency_group(**kwargs)
396 resource = {
397 "type": "consistency_group",
398 "id": consistency_group["id"],
399 "client": client}
400 if cleanup_in_class:
401 cls.class_resources.insert(0, resource)
402 else:
403 cls.method_resources.insert(0, resource)
404
405 if kwargs.get('source_cgsnapshot_id'):
406 new_cg_shares = client.list_shares(
407 detailed=True,
408 params={'consistency_group_id': consistency_group['id']})
409
410 for share in new_cg_shares:
411 resource = {"type": "share",
412 "id": share["id"],
413 "client": client,
414 "consistency_group_id": share.get(
415 'consistency_group_id')}
416 if cleanup_in_class:
417 cls.class_resources.insert(0, resource)
418 else:
419 cls.method_resources.insert(0, resource)
420
421 client.wait_for_consistency_group_status(consistency_group['id'],
422 'available')
423 return consistency_group
424
425 @classmethod
Marc Koderer0abc93b2015-07-15 09:18:35 +0200426 def create_snapshot_wait_for_active(cls, share_id, name=None,
427 description=None, force=False,
428 client=None, cleanup_in_class=True):
429 if client is None:
430 client = cls.shares_client
431 if description is None:
432 description = "Tempest's snapshot"
433 snapshot = client.create_snapshot(share_id, name, description, force)
434 resource = {
435 "type": "snapshot",
436 "id": snapshot["id"],
437 "client": client,
438 }
439 if cleanup_in_class:
440 cls.class_resources.insert(0, resource)
441 else:
442 cls.method_resources.insert(0, resource)
443 client.wait_for_snapshot_status(snapshot["id"], "available")
444 return snapshot
445
446 @classmethod
Andrew Kerrbf31e912015-07-29 10:39:38 -0400447 def create_cgsnapshot_wait_for_active(cls, consistency_group_id,
448 name=None, description=None,
Clinton Knighte5c8f092015-08-27 15:00:23 -0400449 client=None, cleanup_in_class=True,
450 **kwargs):
451 client = client or cls.shares_v2_client
Andrew Kerrbf31e912015-07-29 10:39:38 -0400452 if description is None:
453 description = "Tempest's cgsnapshot"
Clinton Knighte5c8f092015-08-27 15:00:23 -0400454 cgsnapshot = client.create_cgsnapshot(consistency_group_id,
455 name=name,
456 description=description,
457 **kwargs)
Andrew Kerrbf31e912015-07-29 10:39:38 -0400458 resource = {
459 "type": "cgsnapshot",
460 "id": cgsnapshot["id"],
461 "client": client,
462 }
463 if cleanup_in_class:
464 cls.class_resources.insert(0, resource)
465 else:
466 cls.method_resources.insert(0, resource)
467 client.wait_for_cgsnapshot_status(cgsnapshot["id"], "available")
468 return cgsnapshot
469
470 @classmethod
Marc Koderer0abc93b2015-07-15 09:18:35 +0200471 def create_share_network(cls, client=None,
472 cleanup_in_class=False, **kwargs):
473 if client is None:
474 client = cls.shares_client
475 share_network = client.create_share_network(**kwargs)
476 resource = {
477 "type": "share_network",
478 "id": share_network["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 return share_network
486
487 @classmethod
488 def create_security_service(cls, ss_type="ldap", client=None,
489 cleanup_in_class=False, **kwargs):
490 if client is None:
491 client = cls.shares_client
492 security_service = client.create_security_service(ss_type, **kwargs)
493 resource = {
494 "type": "security_service",
495 "id": security_service["id"],
496 "client": client,
497 }
498 if cleanup_in_class:
499 cls.class_resources.insert(0, resource)
500 else:
501 cls.method_resources.insert(0, resource)
502 return security_service
503
504 @classmethod
505 def create_share_type(cls, name, is_public=True, client=None,
506 cleanup_in_class=True, **kwargs):
507 if client is None:
508 client = cls.shares_client
509 share_type = client.create_share_type(name, is_public, **kwargs)
510 resource = {
511 "type": "share_type",
512 "id": share_type["share_type"]["id"],
513 "client": client,
514 }
515 if cleanup_in_class:
516 cls.class_resources.insert(0, resource)
517 else:
518 cls.method_resources.insert(0, resource)
519 return share_type
520
521 @staticmethod
522 def add_required_extra_specs_to_dict(extra_specs=None):
523 value = six.text_type(CONF.share.multitenancy_enabled)
524 required = {
525 "driver_handles_share_servers": value,
526 "snapshot_support": 'True',
527 }
528 if extra_specs:
529 required.update(extra_specs)
530 return required
531
532 @classmethod
533 def clear_isolated_creds(cls, creds=None):
534 if creds is None:
535 creds = cls.method_isolated_creds
536 for ic in creds:
537 if "deleted" not in ic.keys():
538 ic["deleted"] = False
539 if not ic["deleted"]:
540 with handle_cleanup_exceptions():
541 ic["method"]()
542 ic["deleted"] = True
543
544 @classmethod
545 def clear_resources(cls, resources=None):
546 """Deletes resources, that were created in test suites.
547
548 This method tries to remove resources from resource list,
549 if it is not found, assumed it was deleted in test itself.
550 It is expected, that all resources were added as LIFO
551 due to restriction of deletion resources, that is in the chain.
552
553 :param resources: dict with keys 'type','id','client' and 'deleted'
554 """
555
556 if resources is None:
557 resources = cls.method_resources
558 for res in resources:
559 if "deleted" not in res.keys():
560 res["deleted"] = False
561 if "client" not in res.keys():
562 res["client"] = cls.shares_client
563 if not(res["deleted"]):
564 res_id = res['id']
565 client = res["client"]
566 with handle_cleanup_exceptions():
567 if res["type"] is "share":
Andrew Kerrbf31e912015-07-29 10:39:38 -0400568 cg_id = res.get('consistency_group_id')
569 if cg_id:
570 params = {'consistency_group_id': cg_id}
Clinton Knighte5c8f092015-08-27 15:00:23 -0400571 client.delete_share(res_id, params=params)
572 else:
573 client.delete_share(res_id)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200574 client.wait_for_resource_deletion(share_id=res_id)
575 elif res["type"] is "snapshot":
576 client.delete_snapshot(res_id)
577 client.wait_for_resource_deletion(snapshot_id=res_id)
578 elif res["type"] is "share_network":
579 client.delete_share_network(res_id)
580 client.wait_for_resource_deletion(sn_id=res_id)
581 elif res["type"] is "security_service":
582 client.delete_security_service(res_id)
583 client.wait_for_resource_deletion(ss_id=res_id)
584 elif res["type"] is "share_type":
585 client.delete_share_type(res_id)
586 client.wait_for_resource_deletion(st_id=res_id)
Andrew Kerrbf31e912015-07-29 10:39:38 -0400587 elif res["type"] is "consistency_group":
588 client.delete_consistency_group(res_id)
589 client.wait_for_resource_deletion(cg_id=res_id)
590 elif res["type"] is "cgsnapshot":
591 client.delete_cgsnapshot(res_id)
592 client.wait_for_resource_deletion(cgsnapshot_id=res_id)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200593 else:
594 LOG.warn("Provided unsupported resource type for "
595 "cleanup '%s'. Skipping." % res["type"])
596 res["deleted"] = True
597
598 @classmethod
599 def generate_share_network_data(self):
600 data = {
601 "name": data_utils.rand_name("sn-name"),
602 "description": data_utils.rand_name("sn-desc"),
603 "neutron_net_id": data_utils.rand_name("net-id"),
604 "neutron_subnet_id": data_utils.rand_name("subnet-id"),
605 }
606 return data
607
608 @classmethod
609 def generate_security_service_data(self):
610 data = {
611 "name": data_utils.rand_name("ss-name"),
612 "description": data_utils.rand_name("ss-desc"),
613 "dns_ip": data_utils.rand_name("ss-dns_ip"),
614 "server": data_utils.rand_name("ss-server"),
615 "domain": data_utils.rand_name("ss-domain"),
616 "user": data_utils.rand_name("ss-user"),
617 "password": data_utils.rand_name("ss-password"),
618 }
619 return data
620
621 # Useful assertions
622 def assertDictMatch(self, d1, d2, approx_equal=False, tolerance=0.001):
623 """Assert two dicts are equivalent.
624
625 This is a 'deep' match in the sense that it handles nested
626 dictionaries appropriately.
627
628 NOTE:
629
630 If you don't care (or don't know) a given value, you can specify
631 the string DONTCARE as the value. This will cause that dict-item
632 to be skipped.
633
634 """
635 def raise_assertion(msg):
636 d1str = str(d1)
637 d2str = str(d2)
638 base_msg = ('Dictionaries do not match. %(msg)s d1: %(d1str)s '
639 'd2: %(d2str)s' %
640 {"msg": msg, "d1str": d1str, "d2str": d2str})
641 raise AssertionError(base_msg)
642
643 d1keys = set(d1.keys())
644 d2keys = set(d2.keys())
645 if d1keys != d2keys:
646 d1only = d1keys - d2keys
647 d2only = d2keys - d1keys
648 raise_assertion('Keys in d1 and not d2: %(d1only)s. '
649 'Keys in d2 and not d1: %(d2only)s' %
650 {"d1only": d1only, "d2only": d2only})
651
652 for key in d1keys:
653 d1value = d1[key]
654 d2value = d2[key]
655 try:
656 error = abs(float(d1value) - float(d2value))
657 within_tolerance = error <= tolerance
658 except (ValueError, TypeError):
659 # If both values aren't convertable to float, just ignore
660 # ValueError if arg is a str, TypeError if it's something else
661 # (like None)
662 within_tolerance = False
663
664 if hasattr(d1value, 'keys') and hasattr(d2value, 'keys'):
665 self.assertDictMatch(d1value, d2value)
666 elif 'DONTCARE' in (d1value, d2value):
667 continue
668 elif approx_equal and within_tolerance:
669 continue
670 elif d1value != d2value:
671 raise_assertion("d1['%(key)s']=%(d1value)s != "
672 "d2['%(key)s']=%(d2value)s" %
673 {
674 "key": key,
675 "d1value": d1value,
676 "d2value": d2value
677 })
678
679
680class BaseSharesAltTest(BaseSharesTest):
681 """Base test case class for all Shares Alt API tests."""
682
683 @classmethod
684 def resource_setup(cls):
685 cls.username = CONF.identity.alt_username
686 cls.password = CONF.identity.alt_password
687 cls.tenant_name = CONF.identity.alt_tenant_name
688 cls.verify_nonempty(cls.username, cls.password, cls.tenant_name)
689 cls.os = clients.AltManager()
690 alt_share_network_id = CONF.share.alt_share_network_id
691 cls.os.shares_client.share_network_id = alt_share_network_id
Clinton Knighte5c8f092015-08-27 15:00:23 -0400692 cls.os.shares_v2_client.share_network_id = alt_share_network_id
Marc Koderer0abc93b2015-07-15 09:18:35 +0200693 super(BaseSharesAltTest, cls).resource_setup()
694
695
696class BaseSharesAdminTest(BaseSharesTest):
697 """Base test case class for all Shares Admin API tests."""
698
699 @classmethod
700 def resource_setup(cls):
701 cls.username = CONF.identity.admin_username
702 cls.password = CONF.identity.admin_password
703 cls.tenant_name = CONF.identity.admin_tenant_name
704 cls.verify_nonempty(cls.username, cls.password, cls.tenant_name)
705 cls.os = clients.AdminManager()
706 admin_share_network_id = CONF.share.admin_share_network_id
707 cls.os.shares_client.share_network_id = admin_share_network_id
Clinton Knighte5c8f092015-08-27 15:00:23 -0400708 cls.os.shares_v2_client.share_network_id = admin_share_network_id
Marc Koderer0abc93b2015-07-15 09:18:35 +0200709 super(BaseSharesAdminTest, cls).resource_setup()