Add rw functional tests for public share types

Add base client methods for share types and use them in rw functional tests.
This commit covers case of 'public' share types.

Partially implements bp rw-functional-tests

Change-Id: Ia35dbe7f42ada319853642b893bc5c2fa2db4175
This commit is contained in:
Valeriy Ponomaryov
2015-04-21 16:42:05 +03:00
parent b44218141c
commit 4243d8731b
7 changed files with 328 additions and 27 deletions

View File

@@ -42,6 +42,10 @@ iniset $MANILACLIENT_CONF DEFAULT admin_tenant_name $OS_TENANT_NAME
iniset $MANILACLIENT_CONF DEFAULT admin_password $OS_PASSWORD
iniset $MANILACLIENT_CONF DEFAULT admin_auth_url $OS_AUTH_URL
# Suppress errors in cleanup of resources
SUPPRESS_ERRORS=${SUPPRESS_ERRORS_IN_CLEANUP:-True}
iniset $MANILACLIENT_CONF DEFAULT suppress_errors_in_cleanup $SUPPRESS_ERRORS
# let us control if we die or not
set +o errexit

View File

@@ -17,6 +17,7 @@ import copy
import os
from oslo.config import cfg
import oslo_log._options as log_options
# 1. Define opts
@@ -59,6 +60,10 @@ base_opts = [
'OS_MANILA_EXEC_DIR',
os.path.join(os.path.abspath('.'), '.tox/functional/bin')),
help="The path to manilaclient to be executed."),
cfg.BoolOpt("suppress_errors_in_cleanup",
default=True,
help="Whether to suppress errors with clean up operation "
"or not."),
]
# 2. Generate config
@@ -99,8 +104,10 @@ CONF.register_opts(base_opts)
def list_opts():
"""Return a list of oslo.config options available in Manilaclient."""
return [
"""Return a list of oslo_config options available in Manilaclient."""
opts = [
(None, copy.deepcopy(auth_opts)),
(None, copy.deepcopy(base_opts)),
]
opts.extend(log_options.list_opts())
return opts

View File

@@ -13,36 +13,138 @@
# License for the specific language governing permissions and limitations
# under the License.
import os
import traceback
from oslo_log import log
from tempest_lib.cli import base
from tempest_lib import exceptions as lib_exc
from manilaclient import config
from manilaclient.tests.functional import client
CONF = config.CONF
LOG = log.getLogger(__name__)
class handle_cleanup_exceptions(object):
"""Handle exceptions raised with cleanup operations.
Always suppress errors when lib_exc.NotFound or lib_exc.Forbidden
are raised.
Suppress all other exceptions only in case config opt
'suppress_errors_in_cleanup' is True.
"""
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, exc_traceback):
if not (isinstance(exc_value,
(lib_exc.NotFound, lib_exc.Forbidden)) or
CONF.suppress_errors_in_cleanup):
return False # Do not suppress error if any
if exc_traceback:
LOG.error("Suppressed cleanup error: "
"\n%s" % traceback.format_exc())
return True # Suppress error if any
class BaseTestCase(base.ClientTestBase):
def _get_clients(self):
cli_dir = os.environ.get(
'OS_MANILA_EXEC_DIR',
os.path.join(os.path.abspath('.'), '.tox/functional/bin'))
clients = {
'admin': client.ManilaCLIClient(
username=CONF.admin_username,
password=CONF.admin_password,
tenant_name=CONF.admin_tenant_name,
uri=CONF.admin_auth_url or CONF.auth_url,
cli_dir=cli_dir,
),
'user': client.ManilaCLIClient(
username=CONF.username,
password=CONF.password,
tenant_name=CONF.tenant_name,
uri=CONF.auth_url,
cli_dir=cli_dir,
),
# Will be cleaned up after test suite run
class_resources = []
# Will be cleaned up after single test run
method_resources = []
def setUp(self):
super(BaseTestCase, self).setUp()
self.addCleanup(self.clear_resources)
@classmethod
def tearDownClass(cls):
super(BaseTestCase, cls).tearDownClass()
cls.clear_resources(cls.class_resources)
@classmethod
def clear_resources(cls, resources=None):
"""Deletes resources, that were created in test suites.
This method tries to remove resources from resource list,
if it is not found, assume it was deleted in test itself.
It is expected, that all resources were added as LIFO
due to restriction of deletion resources, that are in the chain.
:param resources: dict with keys 'type','id','client' and 'deleted'
"""
if resources is None:
resources = cls.method_resources
for res in resources:
if "deleted" not in res:
res["deleted"] = False
if "client" not in res:
res["client"] = cls.get_cleanup_client()
if not(res["deleted"]):
res_id = res['id']
client = res["client"]
with handle_cleanup_exceptions():
# TODO(vponomaryov): add support for other resources
if res["type"] is "share_type":
client.delete_share_type(res_id)
client.wait_for_share_type_deletion(res_id)
else:
LOG.warn("Provided unsupported resource type for "
"cleanup '%s'. Skipping." % res["type"])
res["deleted"] = True
@classmethod
def get_admin_client(cls):
return client.ManilaCLIClient(
username=CONF.admin_username,
password=CONF.admin_password,
tenant_name=CONF.admin_tenant_name,
uri=CONF.admin_auth_url or CONF.auth_url,
cli_dir=CONF.manila_exec_dir)
@classmethod
def get_user_client(cls):
return client.ManilaCLIClient(
username=CONF.username,
password=CONF.password,
tenant_name=CONF.tenant_name,
uri=CONF.auth_url,
cli_dir=CONF.manila_exec_dir)
@property
def admin_client(self):
if not hasattr(self, '_admin_client'):
self._admin_client = self.get_admin_client()
return self._admin_client
@property
def user_client(self):
if not hasattr(self, '_user_client'):
self._user_client = self.get_user_client()
return self._user_client
def _get_clients(self):
return {'admin': self.admin_client, 'user': self.user_client}
def create_share_type(self, name=None, driver_handles_share_servers=True,
is_public=True, client=None, cleanup_in_class=True):
if client is None:
client = self.admin_client
share_type = client.create_share_type(
name=name,
driver_handles_share_servers=driver_handles_share_servers,
is_public=is_public)
resource = {
"type": "share_type",
"id": share_type["ID"],
"client": client,
}
return clients
if cleanup_in_class:
self.class_resources.insert(0, resource)
else:
self.method_resources.insert(0, resource)
return share_type

View File

@@ -13,7 +13,17 @@
# License for the specific language governing permissions and limitations
# under the License.
import time
import six
from tempest_lib.cli import base
from tempest_lib.cli import output_parser
from tempest_lib.common.utils import data_utils
from tempest_lib import exceptions as tempest_lib_exc
from manilaclient.tests.functional import exceptions
SHARE_TYPE = 'share_type'
class ManilaCLIClient(base.CLIClient):
@@ -39,3 +49,108 @@ class ManilaCLIClient(base.CLIClient):
flags += ' --endpoint-type %s' % endpoint_type
return self.cmd_with_auth(
'manila', action, flags, params, fail_ok, merge_stderr)
def wait_for_resource_deletion(self, res_type, res_id, interval=3,
timeout=180):
"""Resource deletion waiter.
:param res_type: text -- type of resource. Supported only 'share_type'.
Other types support is TODO.
:param res_id: text -- ID of resource to use for deletion check
:param interval: int -- interval between requests in seconds
:param timeout: int -- total time in seconds to wait for deletion
"""
# TODO(vponomaryov): add support for other resource types
if res_type == SHARE_TYPE:
func = self.is_share_type_deleted
else:
raise exceptions.InvalidResource(message=res_type)
end_loop_time = time.time() + timeout
deleted = func(res_id)
while not (deleted or time.time() > end_loop_time):
time.sleep(interval)
deleted = func(res_id)
if not deleted:
raise exceptions.ResourceReleaseFailed(
res_type=res_type, res_id=res_id)
def create_share_type(self, name=None, driver_handles_share_servers=True,
is_public=True):
"""Creates share type.
:param name: text -- name of share type to use, if not set then
autogenerated will be used
:param driver_handles_share_servers: bool/str -- boolean or its
string alias. Default is True.
:param is_public: bool/str -- boolean or its string alias. Default is
True.
"""
if name is None:
name = data_utils.rand_name('manilaclient_functional_test')
dhss = driver_handles_share_servers
if not isinstance(dhss, six.string_types):
dhss = six.text_type(dhss)
if not isinstance(is_public, six.string_types):
is_public = six.text_type(is_public)
cmd = 'type-create %(name)s %(dhss)s --is-public %(is_public)s' % {
'name': name, 'dhss': dhss, 'is_public': is_public}
share_type_raw = self.manila(cmd)
# NOTE(vponomaryov): share type creation response is "list"-like with
# only one element:
# [{
# 'ID': '%id%',
# 'Name': '%name%',
# 'Visibility': 'public',
# 'is_default': '-',
# 'required_extra_specs': 'driver_handles_share_servers : False',
# }]
share_type = output_parser.listing(share_type_raw)[0]
return share_type
def delete_share_type(self, share_type):
"""Deletes share type by its Name or ID."""
try:
return self.manila('type-delete %s' % share_type)
except tempest_lib_exc.CommandFailed as e:
not_found_msg = 'No sharetype with a name or ID'
if not_found_msg in e.stderr:
# Assuming it was deleted in tests
raise tempest_lib_exc.NotFound()
raise
def list_share_types(self, list_all=True):
"""List share types.
:param list_all: bool -- whether to list all share types or only public
"""
cmd = 'type-list'
if list_all:
cmd += ' --all'
share_types_raw = self.manila(cmd)
share_types = output_parser.listing(share_types_raw)
return share_types
def is_share_type_deleted(self, share_type):
"""Says whether share type is deleted or not.
:param share_type: text -- Name or ID of share type
"""
# NOTE(vponomaryov): we use 'list' operation because there is no
# 'get/show' operation for share-types available for CLI
share_types = self.list_share_types(list_all=True)
for list_element in share_types:
if share_type in (list_element['ID'], list_element['Name']):
return False
return True
def wait_for_share_type_deletion(self, share_type):
"""Wait for share type deletion by its Name or ID.
:param share_type: text -- Name or ID of share type
"""
self.wait_for_resource_deletion(
SHARE_TYPE, res_id=share_type, interval=2, timeout=6)

View File

@@ -0,0 +1,28 @@
# Copyright 2015 Mirantis Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from tempest_lib import exceptions
"""
Exceptions for functional tests.
"""
class ResourceReleaseFailed(exceptions.TempestException):
message = "Failed to release resource '%(res_type)s' with id '%(res_id)s'."
class InvalidResource(exceptions.TempestException):
message = "Provided invalid resource: %(message)s"

View File

@@ -14,17 +14,61 @@
# under the License.
import ddt
from oslo_utils import strutils
import six
from tempest_lib.common.utils import data_utils
from manilaclient.tests.functional import base
@ddt.ddt
class ManilaClientTestShareTypesReadOnly(base.BaseTestCase):
class ShareTypesReadOnlyTest(base.BaseTestCase):
@ddt.data('admin', 'user')
def test_share_type_list(self, role):
self.clients[role].manila('type-list')
@ddt.data('admin')
def test_extra_specs_list(self, role):
self.clients[role].manila('extra-specs-list')
def test_extra_specs_list(self):
self.admin_client.manila('extra-specs-list')
@ddt.ddt
class ShareTypesReadWriteTest(base.BaseTestCase):
@ddt.data('false', 'False', '0', 'True', 'true', '1')
def test_create_delete_public_share_type(self, dhss):
share_type_name = data_utils.rand_name('manilaclient_functional_test')
dhss_expected = 'driver_handles_share_servers : %s' % six.text_type(
strutils.bool_from_string(dhss))
# Create share type
share_type = self.create_share_type(
name=share_type_name,
driver_handles_share_servers=dhss,
is_public=True)
# Verify response body
keys = (
'ID', 'Name', 'Visibility', 'is_default', 'required_extra_specs')
for key in keys:
self.assertIn(key, share_type)
self.assertEqual(share_type_name, share_type['Name'])
self.assertEqual(dhss_expected, share_type['required_extra_specs'])
self.assertEqual('public', share_type['Visibility'].lower())
self.assertEqual('-', share_type['is_default'])
# Verify that it is listed with common 'type-list' operation.
share_types = self.admin_client.list_share_types(list_all=False)
self.assertTrue(
any(share_type['ID'] == st['ID'] for st in share_types))
# Delete share type
self.admin_client.delete_share_type(share_type['ID'])
# Wait for share type deletion
self.admin_client.wait_for_share_type_deletion(share_type['ID'])
# Verify that it is not listed with common 'type-list' operation.
share_types = self.admin_client.list_share_types(list_all=False)
self.assertFalse(
any(share_type['ID'] == st['ID'] for st in share_types))

View File

@@ -8,6 +8,7 @@ pbr>=0.6,!=0.7,<1.0
argparse
iso8601>=0.1.9
oslo.config>=1.9.3 # Apache-2.0
oslo.log>=1.0.0 # Apache-2.0
oslo.serialization>=1.4.0 # Apache-2.0
oslo.utils>=1.4.0 # Apache-2.0
PrettyTable>=0.7,<0.8