blob: b2da08047c28d24f957b1515ae418d9051cf5444 [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
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
Valeriy Ponomaryov48a2bd72015-11-05 13:22:44 +0200132 ic = dynamic_creds.DynamicCredentialProvider(
133 identity_version=CONF.identity.auth_version,
134 name=name,
135 admin_role=CONF.identity.admin_role)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200136 if "admin" in type_of_creds:
137 creds = ic.get_admin_creds()
138 elif "alt" in type_of_creds:
139 creds = ic.get_alt_creds()
140 else:
141 creds = ic.self.get_credentials(type_of_creds)
142 ic.type_of_creds = type_of_creds
143
144 # create client with isolated creds
145 os = clients.Manager(credentials=creds)
Clinton Knighte5c8f092015-08-27 15:00:23 -0400146 if client_version == '1':
147 client = os.shares_client
148 elif client_version == '2':
149 client = os.shares_v2_client
Marc Koderer0abc93b2015-07-15 09:18:35 +0200150
151 # Set place where will be deleted isolated creds
152 ic_res = {
Valeriy Ponomaryov48a2bd72015-11-05 13:22:44 +0200153 "method": ic.clear_creds,
Marc Koderer0abc93b2015-07-15 09:18:35 +0200154 "deleted": False,
155 }
156 if cleanup_in_class:
157 cls.class_isolated_creds.insert(0, ic_res)
158 else:
159 cls.method_isolated_creds.insert(0, ic_res)
160
161 # Provide share network
162 if CONF.share.multitenancy_enabled:
163 if not CONF.service_available.neutron:
164 raise cls.skipException("Neutron support is required")
Valeriy Ponomaryov48a2bd72015-11-05 13:22:44 +0200165 nc = os.networks_client
Marc Koderer0abc93b2015-07-15 09:18:35 +0200166 share_network_id = cls.provide_share_network(client, nc, ic)
167 client.share_network_id = share_network_id
168 resource = {
169 "type": "share_network",
170 "id": client.share_network_id,
171 "client": client,
172 }
173 if cleanup_in_class:
174 cls.class_resources.insert(0, resource)
175 else:
176 cls.method_resources.insert(0, resource)
177 return client
178
179 @classmethod
180 def verify_nonempty(cls, *args):
181 if not all(args):
182 msg = "Missing API credentials in configuration."
183 raise cls.skipException(msg)
184
185 @classmethod
186 def resource_setup(cls):
187 if not (any(p in CONF.share.enable_protocols
188 for p in cls.protocols) and
189 CONF.service_available.manila):
190 skip_msg = "Manila is disabled"
191 raise cls.skipException(skip_msg)
192 super(BaseSharesTest, cls).resource_setup()
193 if not hasattr(cls, "os"):
194 cls.username = CONF.identity.username
195 cls.password = CONF.identity.password
196 cls.tenant_name = CONF.identity.tenant_name
197 cls.verify_nonempty(cls.username, cls.password, cls.tenant_name)
198 cls.os = clients.Manager()
199 if CONF.share.multitenancy_enabled:
200 if not CONF.service_available.neutron:
201 raise cls.skipException("Neutron support is required")
202 sc = cls.os.shares_client
Valeriy Ponomaryov48a2bd72015-11-05 13:22:44 +0200203 nc = cls.os.networks_client
Marc Koderer0abc93b2015-07-15 09:18:35 +0200204 share_network_id = cls.provide_share_network(sc, nc)
205 cls.os.shares_client.share_network_id = share_network_id
Clinton Knighte5c8f092015-08-27 15:00:23 -0400206 cls.os.shares_v2_client.share_network_id = share_network_id
Marc Koderer0abc93b2015-07-15 09:18:35 +0200207 cls.shares_client = cls.os.shares_client
Clinton Knighte5c8f092015-08-27 15:00:23 -0400208 cls.shares_v2_client = cls.os.shares_v2_client
Marc Koderer0abc93b2015-07-15 09:18:35 +0200209
210 def setUp(self):
211 super(BaseSharesTest, self).setUp()
212 self.addCleanup(self.clear_resources)
213 self.addCleanup(self.clear_isolated_creds)
214
215 @classmethod
216 def resource_cleanup(cls):
217 super(BaseSharesTest, cls).resource_cleanup()
218 cls.clear_resources(cls.class_resources)
219 cls.clear_isolated_creds(cls.class_isolated_creds)
220
221 @classmethod
222 @network_synchronized
Valeriy Ponomaryov48a2bd72015-11-05 13:22:44 +0200223 def provide_share_network(cls, shares_client, networks_client,
Marc Koderer0abc93b2015-07-15 09:18:35 +0200224 isolated_creds_client=None):
225 """Used for finding/creating share network for multitenant driver.
226
227 This method creates/gets entity share-network for one tenant. This
228 share-network will be used for creation of service vm.
229
230 :param shares_client: shares client, which requires share-network
Valeriy Ponomaryov48a2bd72015-11-05 13:22:44 +0200231 :param networks_client: network client from same tenant as shares
232 :param isolated_creds_client: DynamicCredentialProvider instance
Marc Koderer0abc93b2015-07-15 09:18:35 +0200233 If provided, then its networking will be used if needed.
234 If not provided, then common network will be used if needed.
235 :returns: str -- share network id for shares_client tenant
236 :returns: None -- if single-tenant driver used
237 """
238
239 sc = shares_client
240
241 if not CONF.share.multitenancy_enabled:
242 # Assumed usage of a single-tenant driver
243 share_network_id = None
244 elif sc.share_network_id:
245 # Share-network already exists, use it
246 share_network_id = sc.share_network_id
247 else:
248 net_id = subnet_id = share_network_id = None
249
250 if not isolated_creds_client:
251 # Search for networks, created in previous runs
252 search_word = "reusable"
253 sn_name = "autogenerated_by_tempest_%s" % search_word
254 service_net_name = "share-service"
Valeriy Ponomaryov48a2bd72015-11-05 13:22:44 +0200255 networks = networks_client.list_networks()
Marc Koderer0abc93b2015-07-15 09:18:35 +0200256 if "networks" in networks.keys():
257 networks = networks["networks"]
258 for network in networks:
259 if (service_net_name in network["name"] and
260 sc.tenant_id == network['tenant_id']):
261 net_id = network["id"]
262 if len(network["subnets"]) > 0:
263 subnet_id = network["subnets"][0]
264 break
265
266 # Create suitable network
267 if (net_id is None or subnet_id is None):
Valeriy Ponomaryov48a2bd72015-11-05 13:22:44 +0200268 ic = dynamic_creds.DynamicCredentialProvider(
269 identity_version=CONF.identity.auth_version,
270 name=service_net_name,
271 admin_role=CONF.identity.admin_role,
272 )
Marc Koderer0abc93b2015-07-15 09:18:35 +0200273 net_data = ic._create_network_resources(sc.tenant_id)
274 network, subnet, router = net_data
275 net_id = network["id"]
276 subnet_id = subnet["id"]
277
278 # Try get suitable share-network
279 share_networks = sc.list_share_networks_with_detail()
280 for sn in share_networks:
281 if (net_id == sn["neutron_net_id"] and
282 subnet_id == sn["neutron_subnet_id"] and
283 sn["name"] and search_word in sn["name"]):
284 share_network_id = sn["id"]
285 break
286 else:
287 sn_name = "autogenerated_by_tempest_for_isolated_creds"
288 # Use precreated network and subnet from isolated creds
289 net_id = isolated_creds_client.get_credentials(
290 isolated_creds_client.type_of_creds).network['id']
291 subnet_id = isolated_creds_client.get_credentials(
292 isolated_creds_client.type_of_creds).subnet['id']
293
294 # Create suitable share-network
295 if share_network_id is None:
296 sn_desc = "This share-network was created by tempest"
297 sn = sc.create_share_network(name=sn_name,
298 description=sn_desc,
299 neutron_net_id=net_id,
300 neutron_subnet_id=subnet_id)
301 share_network_id = sn["id"]
302
303 return share_network_id
304
305 @classmethod
306 def _create_share(cls, share_protocol=None, size=1, name=None,
307 snapshot_id=None, description=None, metadata=None,
308 share_network_id=None, share_type_id=None,
Andrew Kerrbf31e912015-07-29 10:39:38 -0400309 consistency_group_id=None, client=None,
Clinton Knighte5c8f092015-08-27 15:00:23 -0400310 cleanup_in_class=True, is_public=False, **kwargs):
Valeriy Ponomaryov1aaa72d2015-09-08 12:59:41 +0300311 client = client or cls.shares_v2_client
Marc Koderer0abc93b2015-07-15 09:18:35 +0200312 description = description or "Tempest's share"
313 share_network_id = share_network_id or client.share_network_id or None
314 metadata = metadata or {}
Clinton Knighte5c8f092015-08-27 15:00:23 -0400315 kwargs.update({
Marc Koderer0abc93b2015-07-15 09:18:35 +0200316 'share_protocol': share_protocol,
317 'size': size,
318 'name': name,
319 'snapshot_id': snapshot_id,
320 'description': description,
321 'metadata': metadata,
322 'share_network_id': share_network_id,
323 'share_type_id': share_type_id,
324 'is_public': is_public,
Clinton Knighte5c8f092015-08-27 15:00:23 -0400325 })
Andrew Kerrbf31e912015-07-29 10:39:38 -0400326 if consistency_group_id:
327 kwargs['consistency_group_id'] = consistency_group_id
328
Marc Koderer0abc93b2015-07-15 09:18:35 +0200329 share = client.create_share(**kwargs)
Andrew Kerrbf31e912015-07-29 10:39:38 -0400330 resource = {"type": "share", "id": share["id"], "client": client,
331 "consistency_group_id": consistency_group_id}
Marc Koderer0abc93b2015-07-15 09:18:35 +0200332 cleanup_list = (cls.class_resources if cleanup_in_class else
333 cls.method_resources)
334 cleanup_list.insert(0, resource)
335 return share
336
337 @classmethod
Clinton Knighte5c8f092015-08-27 15:00:23 -0400338 def migrate_share(cls, share_id, dest_host, client=None, **kwargs):
339 client = client or cls.shares_v2_client
340 client.migrate_share(share_id, dest_host, **kwargs)
Rodrigo Barbierib7137ad2015-09-06 22:53:16 -0300341 share = client.wait_for_migration_completed(share_id, dest_host)
342 return share
343
344 @classmethod
Marc Koderer0abc93b2015-07-15 09:18:35 +0200345 def create_share(cls, *args, **kwargs):
346 """Create one share and wait for available state. Retry if allowed."""
347 result = cls.create_shares([{"args": args, "kwargs": kwargs}])
348 return result[0]
349
350 @classmethod
351 def create_shares(cls, share_data_list):
352 """Creates several shares in parallel with retries.
353
354 Use this method when you want to create more than one share at same
355 time. Especially if config option 'share.share_creation_retry_number'
356 has value more than zero (0).
357 All shares will be expected to have 'available' status with or without
358 recreation else error will be raised.
359
360 :param share_data_list: list -- list of dictionaries with 'args' and
361 'kwargs' for '_create_share' method of this base class.
362 example of data:
363 share_data_list=[{'args': ['quuz'], 'kwargs': {'foo': 'bar'}}}]
364 :returns: list -- list of shares created using provided data.
365 """
366
367 data = [copy.deepcopy(d) for d in share_data_list]
368 for d in data:
369 if not isinstance(d, dict):
370 raise exceptions.TempestException(
371 "Expected 'dict', got '%s'" % type(d))
372 if "args" not in d:
373 d["args"] = []
374 if "kwargs" not in d:
375 d["kwargs"] = {}
376 if len(d) > 2:
377 raise exceptions.TempestException(
378 "Expected only 'args' and 'kwargs' keys. "
379 "Provided %s" % list(d))
380 d["kwargs"]["client"] = d["kwargs"].get(
Valeriy Ponomaryov1aaa72d2015-09-08 12:59:41 +0300381 "client", cls.shares_v2_client)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200382 d["share"] = cls._create_share(*d["args"], **d["kwargs"])
383 d["cnt"] = 0
384 d["available"] = False
385
386 while not all(d["available"] for d in data):
387 for d in data:
388 if d["available"]:
389 continue
390 try:
391 d["kwargs"]["client"].wait_for_share_status(
392 d["share"]["id"], "available")
393 d["available"] = True
394 except (share_exceptions.ShareBuildErrorException,
395 exceptions.TimeoutException) as e:
396 if CONF.share.share_creation_retry_number > d["cnt"]:
397 d["cnt"] += 1
398 msg = ("Share '%s' failed to be built. "
399 "Trying create another." % d["share"]["id"])
400 LOG.error(msg)
401 LOG.error(e)
402 d["share"] = cls._create_share(
403 *d["args"], **d["kwargs"])
404 else:
405 raise e
406
407 return [d["share"] for d in data]
408
409 @classmethod
Andrew Kerrbf31e912015-07-29 10:39:38 -0400410 def create_consistency_group(cls, client=None, cleanup_in_class=True,
411 share_network_id=None, **kwargs):
Clinton Knighte5c8f092015-08-27 15:00:23 -0400412 client = client or cls.shares_v2_client
Andrew Kerrbf31e912015-07-29 10:39:38 -0400413 kwargs['share_network_id'] = (share_network_id or
414 client.share_network_id or None)
415 consistency_group = client.create_consistency_group(**kwargs)
416 resource = {
417 "type": "consistency_group",
418 "id": consistency_group["id"],
419 "client": client}
420 if cleanup_in_class:
421 cls.class_resources.insert(0, resource)
422 else:
423 cls.method_resources.insert(0, resource)
424
425 if kwargs.get('source_cgsnapshot_id'):
426 new_cg_shares = client.list_shares(
427 detailed=True,
428 params={'consistency_group_id': consistency_group['id']})
429
430 for share in new_cg_shares:
431 resource = {"type": "share",
432 "id": share["id"],
433 "client": client,
434 "consistency_group_id": share.get(
435 'consistency_group_id')}
436 if cleanup_in_class:
437 cls.class_resources.insert(0, resource)
438 else:
439 cls.method_resources.insert(0, resource)
440
441 client.wait_for_consistency_group_status(consistency_group['id'],
442 'available')
443 return consistency_group
444
445 @classmethod
Marc Koderer0abc93b2015-07-15 09:18:35 +0200446 def create_snapshot_wait_for_active(cls, share_id, name=None,
447 description=None, force=False,
448 client=None, cleanup_in_class=True):
449 if client is None:
450 client = cls.shares_client
451 if description is None:
452 description = "Tempest's snapshot"
453 snapshot = client.create_snapshot(share_id, name, description, force)
454 resource = {
455 "type": "snapshot",
456 "id": snapshot["id"],
457 "client": client,
458 }
459 if cleanup_in_class:
460 cls.class_resources.insert(0, resource)
461 else:
462 cls.method_resources.insert(0, resource)
463 client.wait_for_snapshot_status(snapshot["id"], "available")
464 return snapshot
465
466 @classmethod
Andrew Kerrbf31e912015-07-29 10:39:38 -0400467 def create_cgsnapshot_wait_for_active(cls, consistency_group_id,
468 name=None, description=None,
Clinton Knighte5c8f092015-08-27 15:00:23 -0400469 client=None, cleanup_in_class=True,
470 **kwargs):
471 client = client or cls.shares_v2_client
Andrew Kerrbf31e912015-07-29 10:39:38 -0400472 if description is None:
473 description = "Tempest's cgsnapshot"
Clinton Knighte5c8f092015-08-27 15:00:23 -0400474 cgsnapshot = client.create_cgsnapshot(consistency_group_id,
475 name=name,
476 description=description,
477 **kwargs)
Andrew Kerrbf31e912015-07-29 10:39:38 -0400478 resource = {
479 "type": "cgsnapshot",
480 "id": cgsnapshot["id"],
481 "client": client,
482 }
483 if cleanup_in_class:
484 cls.class_resources.insert(0, resource)
485 else:
486 cls.method_resources.insert(0, resource)
487 client.wait_for_cgsnapshot_status(cgsnapshot["id"], "available")
488 return cgsnapshot
489
490 @classmethod
Marc Koderer0abc93b2015-07-15 09:18:35 +0200491 def create_share_network(cls, client=None,
492 cleanup_in_class=False, **kwargs):
493 if client is None:
494 client = cls.shares_client
495 share_network = client.create_share_network(**kwargs)
496 resource = {
497 "type": "share_network",
498 "id": share_network["id"],
499 "client": client,
500 }
501 if cleanup_in_class:
502 cls.class_resources.insert(0, resource)
503 else:
504 cls.method_resources.insert(0, resource)
505 return share_network
506
507 @classmethod
508 def create_security_service(cls, ss_type="ldap", client=None,
509 cleanup_in_class=False, **kwargs):
510 if client is None:
511 client = cls.shares_client
512 security_service = client.create_security_service(ss_type, **kwargs)
513 resource = {
514 "type": "security_service",
515 "id": security_service["id"],
516 "client": client,
517 }
518 if cleanup_in_class:
519 cls.class_resources.insert(0, resource)
520 else:
521 cls.method_resources.insert(0, resource)
522 return security_service
523
524 @classmethod
525 def create_share_type(cls, name, is_public=True, client=None,
526 cleanup_in_class=True, **kwargs):
527 if client is None:
528 client = cls.shares_client
529 share_type = client.create_share_type(name, is_public, **kwargs)
530 resource = {
531 "type": "share_type",
532 "id": share_type["share_type"]["id"],
533 "client": client,
534 }
535 if cleanup_in_class:
536 cls.class_resources.insert(0, resource)
537 else:
538 cls.method_resources.insert(0, resource)
539 return share_type
540
541 @staticmethod
542 def add_required_extra_specs_to_dict(extra_specs=None):
Valeriy Ponomaryovad55dc52015-09-23 13:54:00 +0300543 dhss = six.text_type(CONF.share.multitenancy_enabled)
544 snapshot_support = six.text_type(
545 CONF.share.capability_snapshot_support)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200546 required = {
Valeriy Ponomaryovad55dc52015-09-23 13:54:00 +0300547 "driver_handles_share_servers": dhss,
548 "snapshot_support": snapshot_support,
Marc Koderer0abc93b2015-07-15 09:18:35 +0200549 }
550 if extra_specs:
551 required.update(extra_specs)
552 return required
553
554 @classmethod
555 def clear_isolated_creds(cls, creds=None):
556 if creds is None:
557 creds = cls.method_isolated_creds
558 for ic in creds:
559 if "deleted" not in ic.keys():
560 ic["deleted"] = False
561 if not ic["deleted"]:
562 with handle_cleanup_exceptions():
563 ic["method"]()
564 ic["deleted"] = True
565
566 @classmethod
567 def clear_resources(cls, resources=None):
568 """Deletes resources, that were created in test suites.
569
570 This method tries to remove resources from resource list,
571 if it is not found, assumed it was deleted in test itself.
572 It is expected, that all resources were added as LIFO
573 due to restriction of deletion resources, that is in the chain.
574
575 :param resources: dict with keys 'type','id','client' and 'deleted'
576 """
577
578 if resources is None:
579 resources = cls.method_resources
580 for res in resources:
581 if "deleted" not in res.keys():
582 res["deleted"] = False
583 if "client" not in res.keys():
584 res["client"] = cls.shares_client
585 if not(res["deleted"]):
586 res_id = res['id']
587 client = res["client"]
588 with handle_cleanup_exceptions():
589 if res["type"] is "share":
Andrew Kerrbf31e912015-07-29 10:39:38 -0400590 cg_id = res.get('consistency_group_id')
591 if cg_id:
592 params = {'consistency_group_id': cg_id}
Clinton Knighte5c8f092015-08-27 15:00:23 -0400593 client.delete_share(res_id, params=params)
594 else:
595 client.delete_share(res_id)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200596 client.wait_for_resource_deletion(share_id=res_id)
597 elif res["type"] is "snapshot":
598 client.delete_snapshot(res_id)
599 client.wait_for_resource_deletion(snapshot_id=res_id)
600 elif res["type"] is "share_network":
601 client.delete_share_network(res_id)
602 client.wait_for_resource_deletion(sn_id=res_id)
603 elif res["type"] is "security_service":
604 client.delete_security_service(res_id)
605 client.wait_for_resource_deletion(ss_id=res_id)
606 elif res["type"] is "share_type":
607 client.delete_share_type(res_id)
608 client.wait_for_resource_deletion(st_id=res_id)
Andrew Kerrbf31e912015-07-29 10:39:38 -0400609 elif res["type"] is "consistency_group":
610 client.delete_consistency_group(res_id)
611 client.wait_for_resource_deletion(cg_id=res_id)
612 elif res["type"] is "cgsnapshot":
613 client.delete_cgsnapshot(res_id)
614 client.wait_for_resource_deletion(cgsnapshot_id=res_id)
Marc Koderer0abc93b2015-07-15 09:18:35 +0200615 else:
616 LOG.warn("Provided unsupported resource type for "
617 "cleanup '%s'. Skipping." % res["type"])
618 res["deleted"] = True
619
620 @classmethod
621 def generate_share_network_data(self):
622 data = {
623 "name": data_utils.rand_name("sn-name"),
624 "description": data_utils.rand_name("sn-desc"),
625 "neutron_net_id": data_utils.rand_name("net-id"),
626 "neutron_subnet_id": data_utils.rand_name("subnet-id"),
627 }
628 return data
629
630 @classmethod
631 def generate_security_service_data(self):
632 data = {
633 "name": data_utils.rand_name("ss-name"),
634 "description": data_utils.rand_name("ss-desc"),
Andrew Kerr40df1d72015-09-28 13:22:33 -0400635 "dns_ip": rand_ip(),
636 "server": rand_ip(),
Marc Koderer0abc93b2015-07-15 09:18:35 +0200637 "domain": data_utils.rand_name("ss-domain"),
638 "user": data_utils.rand_name("ss-user"),
639 "password": data_utils.rand_name("ss-password"),
640 }
641 return data
642
643 # Useful assertions
644 def assertDictMatch(self, d1, d2, approx_equal=False, tolerance=0.001):
645 """Assert two dicts are equivalent.
646
647 This is a 'deep' match in the sense that it handles nested
648 dictionaries appropriately.
649
650 NOTE:
651
652 If you don't care (or don't know) a given value, you can specify
653 the string DONTCARE as the value. This will cause that dict-item
654 to be skipped.
655
656 """
657 def raise_assertion(msg):
658 d1str = str(d1)
659 d2str = str(d2)
660 base_msg = ('Dictionaries do not match. %(msg)s d1: %(d1str)s '
661 'd2: %(d2str)s' %
662 {"msg": msg, "d1str": d1str, "d2str": d2str})
663 raise AssertionError(base_msg)
664
665 d1keys = set(d1.keys())
666 d2keys = set(d2.keys())
667 if d1keys != d2keys:
668 d1only = d1keys - d2keys
669 d2only = d2keys - d1keys
670 raise_assertion('Keys in d1 and not d2: %(d1only)s. '
671 'Keys in d2 and not d1: %(d2only)s' %
672 {"d1only": d1only, "d2only": d2only})
673
674 for key in d1keys:
675 d1value = d1[key]
676 d2value = d2[key]
677 try:
678 error = abs(float(d1value) - float(d2value))
679 within_tolerance = error <= tolerance
680 except (ValueError, TypeError):
681 # If both values aren't convertable to float, just ignore
682 # ValueError if arg is a str, TypeError if it's something else
683 # (like None)
684 within_tolerance = False
685
686 if hasattr(d1value, 'keys') and hasattr(d2value, 'keys'):
687 self.assertDictMatch(d1value, d2value)
688 elif 'DONTCARE' in (d1value, d2value):
689 continue
690 elif approx_equal and within_tolerance:
691 continue
692 elif d1value != d2value:
693 raise_assertion("d1['%(key)s']=%(d1value)s != "
694 "d2['%(key)s']=%(d2value)s" %
695 {
696 "key": key,
697 "d1value": d1value,
698 "d2value": d2value
699 })
700
701
702class BaseSharesAltTest(BaseSharesTest):
703 """Base test case class for all Shares Alt API tests."""
704
705 @classmethod
706 def resource_setup(cls):
707 cls.username = CONF.identity.alt_username
708 cls.password = CONF.identity.alt_password
709 cls.tenant_name = CONF.identity.alt_tenant_name
710 cls.verify_nonempty(cls.username, cls.password, cls.tenant_name)
711 cls.os = clients.AltManager()
712 alt_share_network_id = CONF.share.alt_share_network_id
713 cls.os.shares_client.share_network_id = alt_share_network_id
Clinton Knighte5c8f092015-08-27 15:00:23 -0400714 cls.os.shares_v2_client.share_network_id = alt_share_network_id
Marc Koderer0abc93b2015-07-15 09:18:35 +0200715 super(BaseSharesAltTest, cls).resource_setup()
716
717
718class BaseSharesAdminTest(BaseSharesTest):
719 """Base test case class for all Shares Admin API tests."""
720
721 @classmethod
722 def resource_setup(cls):
Sam Wanb5047aa2015-10-08 05:37:43 -0400723 if hasattr(CONF.identity, 'admin_username'):
724 cls.username = CONF.identity.admin_username
725 cls.password = CONF.identity.admin_password
726 cls.tenant_name = CONF.identity.admin_tenant_name
727 else:
728 cls.username = CONF.auth.admin_username
729 cls.password = CONF.auth.admin_password
730 cls.tenant_name = CONF.auth.admin_tenant_name
Marc Koderer0abc93b2015-07-15 09:18:35 +0200731 cls.verify_nonempty(cls.username, cls.password, cls.tenant_name)
732 cls.os = clients.AdminManager()
733 admin_share_network_id = CONF.share.admin_share_network_id
734 cls.os.shares_client.share_network_id = admin_share_network_id
Clinton Knighte5c8f092015-08-27 15:00:23 -0400735 cls.os.shares_v2_client.share_network_id = admin_share_network_id
Marc Koderer0abc93b2015-07-15 09:18:35 +0200736 super(BaseSharesAdminTest, cls).resource_setup()