blob: 3c422b81b2c39b2501a40310989266f19ca4ec66 [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):
Valeriy Ponomaryovad55dc52015-09-23 13:54:00 +0300523 dhss = six.text_type(CONF.share.multitenancy_enabled)
524 snapshot_support = six.text_type(
525 CONF.share.capability_snapshot_support)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200526 required = {
Valeriy Ponomaryovad55dc52015-09-23 13:54:00 +0300527 "driver_handles_share_servers": dhss,
528 "snapshot_support": snapshot_support,
Marc Koderer0abc93b2015-07-15 09:18:35 +0200529 }
530 if extra_specs:
531 required.update(extra_specs)
532 return required
533
534 @classmethod
535 def clear_isolated_creds(cls, creds=None):
536 if creds is None:
537 creds = cls.method_isolated_creds
538 for ic in creds:
539 if "deleted" not in ic.keys():
540 ic["deleted"] = False
541 if not ic["deleted"]:
542 with handle_cleanup_exceptions():
543 ic["method"]()
544 ic["deleted"] = True
545
546 @classmethod
547 def clear_resources(cls, resources=None):
548 """Deletes resources, that were created in test suites.
549
550 This method tries to remove resources from resource list,
551 if it is not found, assumed it was deleted in test itself.
552 It is expected, that all resources were added as LIFO
553 due to restriction of deletion resources, that is in the chain.
554
555 :param resources: dict with keys 'type','id','client' and 'deleted'
556 """
557
558 if resources is None:
559 resources = cls.method_resources
560 for res in resources:
561 if "deleted" not in res.keys():
562 res["deleted"] = False
563 if "client" not in res.keys():
564 res["client"] = cls.shares_client
565 if not(res["deleted"]):
566 res_id = res['id']
567 client = res["client"]
568 with handle_cleanup_exceptions():
569 if res["type"] is "share":
Andrew Kerrbf31e912015-07-29 10:39:38 -0400570 cg_id = res.get('consistency_group_id')
571 if cg_id:
572 params = {'consistency_group_id': cg_id}
Clinton Knighte5c8f092015-08-27 15:00:23 -0400573 client.delete_share(res_id, params=params)
574 else:
575 client.delete_share(res_id)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200576 client.wait_for_resource_deletion(share_id=res_id)
577 elif res["type"] is "snapshot":
578 client.delete_snapshot(res_id)
579 client.wait_for_resource_deletion(snapshot_id=res_id)
580 elif res["type"] is "share_network":
581 client.delete_share_network(res_id)
582 client.wait_for_resource_deletion(sn_id=res_id)
583 elif res["type"] is "security_service":
584 client.delete_security_service(res_id)
585 client.wait_for_resource_deletion(ss_id=res_id)
586 elif res["type"] is "share_type":
587 client.delete_share_type(res_id)
588 client.wait_for_resource_deletion(st_id=res_id)
Andrew Kerrbf31e912015-07-29 10:39:38 -0400589 elif res["type"] is "consistency_group":
590 client.delete_consistency_group(res_id)
591 client.wait_for_resource_deletion(cg_id=res_id)
592 elif res["type"] is "cgsnapshot":
593 client.delete_cgsnapshot(res_id)
594 client.wait_for_resource_deletion(cgsnapshot_id=res_id)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200595 else:
596 LOG.warn("Provided unsupported resource type for "
597 "cleanup '%s'. Skipping." % res["type"])
598 res["deleted"] = True
599
600 @classmethod
601 def generate_share_network_data(self):
602 data = {
603 "name": data_utils.rand_name("sn-name"),
604 "description": data_utils.rand_name("sn-desc"),
605 "neutron_net_id": data_utils.rand_name("net-id"),
606 "neutron_subnet_id": data_utils.rand_name("subnet-id"),
607 }
608 return data
609
610 @classmethod
611 def generate_security_service_data(self):
612 data = {
613 "name": data_utils.rand_name("ss-name"),
614 "description": data_utils.rand_name("ss-desc"),
615 "dns_ip": data_utils.rand_name("ss-dns_ip"),
616 "server": data_utils.rand_name("ss-server"),
617 "domain": data_utils.rand_name("ss-domain"),
618 "user": data_utils.rand_name("ss-user"),
619 "password": data_utils.rand_name("ss-password"),
620 }
621 return data
622
623 # Useful assertions
624 def assertDictMatch(self, d1, d2, approx_equal=False, tolerance=0.001):
625 """Assert two dicts are equivalent.
626
627 This is a 'deep' match in the sense that it handles nested
628 dictionaries appropriately.
629
630 NOTE:
631
632 If you don't care (or don't know) a given value, you can specify
633 the string DONTCARE as the value. This will cause that dict-item
634 to be skipped.
635
636 """
637 def raise_assertion(msg):
638 d1str = str(d1)
639 d2str = str(d2)
640 base_msg = ('Dictionaries do not match. %(msg)s d1: %(d1str)s '
641 'd2: %(d2str)s' %
642 {"msg": msg, "d1str": d1str, "d2str": d2str})
643 raise AssertionError(base_msg)
644
645 d1keys = set(d1.keys())
646 d2keys = set(d2.keys())
647 if d1keys != d2keys:
648 d1only = d1keys - d2keys
649 d2only = d2keys - d1keys
650 raise_assertion('Keys in d1 and not d2: %(d1only)s. '
651 'Keys in d2 and not d1: %(d2only)s' %
652 {"d1only": d1only, "d2only": d2only})
653
654 for key in d1keys:
655 d1value = d1[key]
656 d2value = d2[key]
657 try:
658 error = abs(float(d1value) - float(d2value))
659 within_tolerance = error <= tolerance
660 except (ValueError, TypeError):
661 # If both values aren't convertable to float, just ignore
662 # ValueError if arg is a str, TypeError if it's something else
663 # (like None)
664 within_tolerance = False
665
666 if hasattr(d1value, 'keys') and hasattr(d2value, 'keys'):
667 self.assertDictMatch(d1value, d2value)
668 elif 'DONTCARE' in (d1value, d2value):
669 continue
670 elif approx_equal and within_tolerance:
671 continue
672 elif d1value != d2value:
673 raise_assertion("d1['%(key)s']=%(d1value)s != "
674 "d2['%(key)s']=%(d2value)s" %
675 {
676 "key": key,
677 "d1value": d1value,
678 "d2value": d2value
679 })
680
681
682class BaseSharesAltTest(BaseSharesTest):
683 """Base test case class for all Shares Alt API tests."""
684
685 @classmethod
686 def resource_setup(cls):
687 cls.username = CONF.identity.alt_username
688 cls.password = CONF.identity.alt_password
689 cls.tenant_name = CONF.identity.alt_tenant_name
690 cls.verify_nonempty(cls.username, cls.password, cls.tenant_name)
691 cls.os = clients.AltManager()
692 alt_share_network_id = CONF.share.alt_share_network_id
693 cls.os.shares_client.share_network_id = alt_share_network_id
Clinton Knighte5c8f092015-08-27 15:00:23 -0400694 cls.os.shares_v2_client.share_network_id = alt_share_network_id
Marc Koderer0abc93b2015-07-15 09:18:35 +0200695 super(BaseSharesAltTest, cls).resource_setup()
696
697
698class BaseSharesAdminTest(BaseSharesTest):
699 """Base test case class for all Shares Admin API tests."""
700
701 @classmethod
702 def resource_setup(cls):
703 cls.username = CONF.identity.admin_username
704 cls.password = CONF.identity.admin_password
705 cls.tenant_name = CONF.identity.admin_tenant_name
706 cls.verify_nonempty(cls.username, cls.password, cls.tenant_name)
707 cls.os = clients.AdminManager()
708 admin_share_network_id = CONF.share.admin_share_network_id
709 cls.os.shares_client.share_network_id = admin_share_network_id
Clinton Knighte5c8f092015-08-27 15:00:23 -0400710 cls.os.shares_v2_client.share_network_id = admin_share_network_id
Marc Koderer0abc93b2015-07-15 09:18:35 +0200711 super(BaseSharesAdminTest, cls).resource_setup()