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