Add rw functional tests for shares

Add base client methods for shares and use them in rw functional tests.

Partially implements bp rw-functional-tests

Change-Id: Ibc611ed5c54d2635f36018f3590ca56f4f2eb7dd
This commit is contained in:
Valeriy Ponomaryov 2015-04-29 19:36:23 +03:00
parent c668f00d5b
commit ada9825bf6
8 changed files with 540 additions and 8 deletions

View File

@ -26,7 +26,7 @@ sudo chown -R jenkins:stack .
# Create manilaclient config file
touch $MANILACLIENT_CONF
# Import 'iniset' func from devstack functions
# Import functions from devstack
source $BASE/new/devstack/functions
# Set options to config client.
@ -46,11 +46,43 @@ iniset $MANILACLIENT_CONF DEFAULT admin_auth_url $OS_AUTH_URL
SUPPRESS_ERRORS=${SUPPRESS_ERRORS_IN_CLEANUP:-True}
iniset $MANILACLIENT_CONF DEFAULT suppress_errors_in_cleanup $SUPPRESS_ERRORS
# Create share network and use it for functional tests if required
USE_SHARE_NETWORK=$(trueorfalse True USE_SHARE_NETWORK)
if [[ ${USE_SHARE_NETWORK} = True ]]; then
SHARE_NETWORK_NAME=${SHARE_NETWORK_NAME:-ci}
if [[ "$JOB_NAME" =~ "neutron" ]]; then
DEFAULT_NEUTRON_NET=$(neutron net-show private -c id -f value)
DEFAULT_NEUTRON_SUBNET=$(neutron subnet-show private-subnet -c id -f value)
NEUTRON_NET=${NEUTRON_NET:-$DEFAULT_NEUTRON_NET}
NEUTRON_SUBNET=${NEUTRON_SUBNET:-$DEFAULT_NEUTRON_SUBNET}
manila share-network-create \
--name $SHARE_NETWORK_NAME \
--neutron-net $NEUTRON_NET \
--neutron-subnet $NEUTRON_SUBNET
else
DEFAULT_NOVA_NET=$(nova net-list | grep private | awk '{print $2}')
NOVA_NET=${NOVA_NET:-$DEFAULT_NOVA_NET}
manila share-network-create \
--name $SHARE_NETWORK_NAME \
--nova-net $NOVA_NET
fi
iniset $MANILACLIENT_CONF DEFAULT share_network $SHARE_NETWORK_NAME
iniset $MANILACLIENT_CONF DEFAULT admin_share_network $SHARE_NETWORK_NAME
fi
# Set share type if required
if [[ "$SHARE_TYPE" ]]; then
iniset $MANILACLIENT_CONF DEFAULT share_type $SHARE_TYPE
fi
# let us control if we die or not
set +o errexit
CONCURRENCY=${CONCURRENCY:-8}
# Run functional tests
sudo -H -u jenkins tox -e functional -v
sudo -H -u jenkins tox -e functional -v -- --concurrency=$CONCURRENCY
EXIT_CODE=$?
if [ -d ".testrepository" ] ; then

View File

@ -66,6 +66,33 @@ base_opts = [
"or not."),
]
share_opts = [
cfg.StrOpt("share_network",
default=None,
help="Share network Name or ID, that will be used for shares. "
"Some backend drivers require a share network for share "
"creation."),
cfg.StrOpt("admin_share_network",
default=None,
help="Share network Name or ID, that will be used for shares "
"in admin tenant."),
cfg.StrOpt("share_type",
default=None,
help="Share type Name or ID, that will be used with share "
"creation scheduling. Optional."),
cfg.ListOpt("enable_protocols",
default=["nfs", "cifs"],
help="List of all enabled protocols. The first protocol in "
"the list will be used as the default protocol."),
cfg.IntOpt("build_interval",
default=3,
help="Time in seconds between share availability checks."),
cfg.IntOpt("build_timeout",
default=500,
help="Timeout in seconds to wait for a share to become "
"available."),
]
# 2. Generate config
PROJECT_NAME = 'manilaclient'
@ -99,6 +126,7 @@ else:
CONF.register_opts(auth_opts)
CONF.register_opts(base_opts)
CONF.register_opts(share_opts)
# 4. Define list_opts for config sample generator
@ -108,6 +136,7 @@ def list_opts():
opts = [
(None, copy.deepcopy(auth_opts)),
(None, copy.deepcopy(base_opts)),
(None, copy.deepcopy(share_opts)),
]
opts.extend(log_options.list_opts())
return opts

View File

@ -95,6 +95,9 @@ class BaseTestCase(base.ClientTestBase):
elif res["type"] is "share_network":
client.delete_share_network(res_id)
client.wait_for_share_network_deletion(res_id)
elif res["type"] is "share":
client.delete_share(res_id)
client.wait_for_share_deletion(res_id)
else:
LOG.warn("Provided unsupported resource type for "
"cleanup '%s'. Skipping." % res["type"])
@ -102,21 +105,27 @@ class BaseTestCase(base.ClientTestBase):
@classmethod
def get_admin_client(cls):
return client.ManilaCLIClient(
manilaclient = 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)
# Set specific for admin project share network
manilaclient.share_network = CONF.admin_share_network
return manilaclient
@classmethod
def get_user_client(cls):
return client.ManilaCLIClient(
manilaclient = client.ManilaCLIClient(
username=CONF.username,
password=CONF.password,
tenant_name=CONF.tenant_name,
uri=CONF.auth_url,
cli_dir=CONF.manila_exec_dir)
# Set specific for user project share network
manilaclient.share_network = CONF.share_network
return manilaclient
@property
def admin_client(self):
@ -133,10 +142,11 @@ class BaseTestCase(base.ClientTestBase):
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,
@classmethod
def create_share_type(cls, name=None, driver_handles_share_servers=True,
is_public=True, client=None, cleanup_in_class=True):
if client is None:
client = self.admin_client
client = cls.get_admin_client()
share_type = client.create_share_type(
name=name,
driver_handles_share_servers=driver_handles_share_servers,
@ -147,9 +157,9 @@ class BaseTestCase(base.ClientTestBase):
"client": client,
}
if cleanup_in_class:
self.class_resources.insert(0, resource)
cls.class_resources.insert(0, resource)
else:
self.method_resources.insert(0, resource)
cls.method_resources.insert(0, resource)
return share_type
@classmethod
@ -175,3 +185,40 @@ class BaseTestCase(base.ClientTestBase):
else:
cls.method_resources.insert(0, resource)
return share_network
@classmethod
def create_share(cls, share_protocol=None, size=None, share_network=None,
share_type=None, name=None, description=None,
public=False, snapshot=None, metadata=None,
client=None, cleanup_in_class=False,
wait_for_creation=True):
if client is None:
client = cls.get_admin_client()
data = {
'share_protocol': share_protocol or client.share_protocol,
'size': size or 1,
'name': name,
'description': description,
'public': public,
'snapshot': snapshot,
'metadata': metadata,
}
share_network = share_network or client.share_network
share_type = share_type or CONF.share_type
if share_network:
data['share_network'] = share_network
if share_type:
data['share_type'] = share_type
share = client.create_share(**data)
resource = {
"type": "share",
"id": share["id"],
"client": client,
}
if cleanup_in_class:
cls.class_resources.insert(0, resource)
else:
cls.method_resources.insert(0, resource)
if wait_for_creation:
client.wait_for_share_status(share['id'], 'available')
return share

View File

@ -16,15 +16,19 @@
import re
import time
from oslo_utils import strutils
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 import config
from manilaclient.tests.functional import exceptions
from manilaclient.tests.functional import utils
CONF = config.CONF
SHARE = 'share'
SHARE_TYPE = 'share_type'
SHARE_NETWORK = 'share_network'
@ -43,8 +47,32 @@ def not_found_wrapper(f):
return wrapped_func
def forbidden_wrapper(f):
def wrapped_func(self, *args, **kwargs):
try:
return f(self, *args, **kwargs)
except tempest_lib_exc.CommandFailed as e:
if re.search('HTTP 403', e.stderr):
# Raise appropriate 'Forbidden' error.
raise tempest_lib_exc.Forbidden()
raise
return wrapped_func
class ManilaCLIClient(base.CLIClient):
def __init__(self, *args, **kwargs):
super(ManilaCLIClient, self).__init__(*args, **kwargs)
if CONF.enable_protocols:
self.share_protocol = CONF.enable_protocols[0]
else:
msg = "Configuration option 'enable_protocols' is not defined."
raise exceptions.InvalidConfiguration(reason=msg)
self.build_interval = CONF.build_interval
self.build_timeout = CONF.build_timeout
def manila(self, action, flags='', params='', fail_ok=False,
endpoint_type='publicURL', merge_stderr=False):
"""Executes manila command for the given action.
@ -82,6 +110,8 @@ class ManilaCLIClient(base.CLIClient):
func = self.is_share_type_deleted
elif res_type == SHARE_NETWORK:
func = self.is_share_network_deleted
elif res_type == SHARE:
func = self.is_share_deleted
else:
raise exceptions.InvalidResource(message=res_type)
@ -149,6 +179,17 @@ class ManilaCLIClient(base.CLIClient):
share_types = output_parser.listing(share_types_raw)
return share_types
def get_share_type(self, share_type):
"""Get share type.
:param share_type: str -- Name or ID of share type
"""
share_types = self.list_share_types(True)
for st in share_types:
if share_type in (st['ID'], st['Name']):
return st
raise tempest_lib_exc.NotFound()
def is_share_type_deleted(self, share_type):
"""Says whether share type is deleted or not.
@ -369,3 +410,158 @@ class ManilaCLIClient(base.CLIClient):
"""
self.wait_for_resource_deletion(
SHARE_NETWORK, res_id=share_network, interval=2, timeout=6)
# Shares
def create_share(self, share_protocol, size, share_network=None,
share_type=None, name=None, description=None,
public=False, snapshot=None, metadata=None):
"""Creates a share.
:param share_protocol: str -- share protocol of a share.
:param size: int/str -- desired size of a share.
:param share_network: str -- Name or ID of share network to use.
:param share_type: str -- Name or ID of share type to use.
:param name: str -- desired name of new share.
:param description: str -- desired description of new share.
:param public: bool -- should a share be public or not.
Default is False.
:param snapshot: str -- Name or ID of a snapshot to use as source.
:param metadata: dict -- key-value data to provide with share creation.
"""
cmd = 'create %(share_protocol)s %(size)s ' % {
'share_protocol': share_protocol, 'size': size}
if share_network is not None:
cmd += '--share-network %s ' % share_network
if share_type is not None:
cmd += '--share-type %s ' % share_type
if name is None:
name = data_utils.rand_name('autotest_share_name')
cmd += '--name %s ' % name
if description is None:
description = data_utils.rand_name('autotest_share_description')
cmd += '--description %s ' % description
if public:
cmd += '--public'
if snapshot is not None:
cmd += '--snapshot %s ' % snapshot
if metadata:
metadata_cli = ''
for k, v in metadata.items():
metadata_cli += '%(k)s=%(v)s ' % {'k': k, 'v': v}
if metadata_cli:
cmd += '--metadata %s ' % metadata_cli
share_raw = self.manila(cmd)
share = output_parser.details(share_raw)
return share
@not_found_wrapper
def get_share(self, share):
"""Returns a share by its Name or ID."""
share_raw = self.manila('show %s' % share)
share = output_parser.details(share_raw)
return share
@not_found_wrapper
def update_share(self, share, name=None, description=None,
is_public=False):
"""Updates a share.
:param share: str -- name or ID of a share that should be updated.
:param name: str -- desired name of new share.
:param description: str -- desired description of new share.
:param is_public: bool -- should a share be public or not.
Default is False.
"""
cmd = 'update %s ' % share
if name:
cmd += '--name %s ' % name
if description:
cmd += '--description %s ' % description
is_public = strutils.bool_from_string(is_public, strict=True)
cmd += '--is-public %s ' % is_public
return self.manila(cmd)
@not_found_wrapper
@forbidden_wrapper
def delete_share(self, shares):
"""Deletes share[s] by Names or IDs.
:param shares: either str or list of str that can be either Name
or ID of a share(s) that should be deleted.
"""
if not isinstance(shares, list):
shares = [shares]
cmd = 'delete '
for share in shares:
cmd += '%s ' % share
return self.manila(cmd)
def list_shares(self, all_tenants=False, filters=None):
"""List shares.
:param all_tenants: bool -- whether to list shares that belong
only to current project or for all projects.
:param filters: dict -- filters for listing of shares.
Example, input:
{'project_id': 'foo'}
{-'project_id': 'foo'}
{--'project_id': 'foo'}
{'project-id': 'foo'}
will be transformed to filter parameter "--project-id=foo"
"""
cmd = 'list '
if all_tenants:
cmd += '--all-tenants '
if filters and isinstance(filters, dict):
for k, v in filters.items():
cmd += '%(k)s=%(v)s ' % {
'k': self._stranslate_to_cli_optional_param(k), 'v': v}
shares_raw = self.manila(cmd)
shares = utils.listing(shares_raw)
return shares
def is_share_deleted(self, share):
"""Says whether share is deleted or not.
:param share: str -- Name or ID of share
"""
try:
self.get_share(share)
return False
except tempest_lib_exc.NotFound:
return True
def wait_for_share_deletion(self, share):
"""Wait for share deletion by its Name or ID.
:param share: str -- Name or ID of share
"""
self.wait_for_resource_deletion(
SHARE, res_id=share, interval=5, timeout=300)
def wait_for_share_status(self, share, status):
"""Waits for a share to reach a given status."""
body = self.get_share(share)
share_name = body['name']
share_status = body['status']
start = int(time.time())
while share_status != status:
time.sleep(self.build_interval)
body = self.get_share(share)
share_status = body['status']
if share_status == status:
return
elif 'error' in share_status.lower():
raise exceptions.ShareBuildErrorException(share=share)
if int(time.time()) - start >= self.build_timeout:
message = (
"Share %(share_name)s failed to reach %(status)s status "
"within the required time (%(build_timeout)s s)." % {
"share_name": share_name, "status": status,
"build_timeout": self.build_timeout})
raise tempest_lib_exc.TimeoutException(message)

View File

@ -34,3 +34,11 @@ class InvalidData(exceptions.TempestException):
class ShareTypeNotFound(exceptions.NotFound):
message = "Share type '%(share_type)s' was not found"
class InvalidConfiguration(exceptions.TempestException):
message = "Invalid configuration: %(reason)s"
class ShareBuildErrorException(exceptions.TempestException):
message = "Share %(share)s failed to build and is in ERROR status"

View File

@ -0,0 +1,105 @@
# 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.common.utils import data_utils
from manilaclient import config
from manilaclient.tests.functional import base
CONF = config.CONF
class SharesReadWriteBase(base.BaseTestCase):
protocol = None
@classmethod
def setUpClass(cls):
super(SharesReadWriteBase, cls).setUpClass()
if cls.protocol not in CONF.enable_protocols:
message = "%s tests are disabled" % cls.protocol
raise cls.skipException(message)
cls.name = data_utils.rand_name('autotest_share_name')
cls.description = data_utils.rand_name('autotest_share_description')
# NOTE(vponomaryov): following share is used only in one test
# until tests for snapshots appear.
cls.share = cls.create_share(
share_protocol=cls.protocol,
size=1,
name=cls.name,
description=cls.description,
client=cls.get_user_client(),
cleanup_in_class=True)
def test_create_delete_share(self):
name = data_utils.rand_name('autotest_share_name')
create = self.create_share(
self.protocol, name=name, client=self.user_client)
self.assertEqual(name, create['name'])
self.assertEqual('1', create['size'])
self.assertEqual(self.protocol.upper(), create['share_proto'])
self.user_client.delete_share(create['id'])
self.user_client.wait_for_share_deletion(create['id'])
def test_create_update_share(self):
name = data_utils.rand_name('autotest_share_name')
new_name = 'new_' + name
description = data_utils.rand_name('autotest_share_description')
new_description = 'new_' + description
create = self.create_share(
self.protocol, name=name, description=description,
client=self.user_client)
self.assertEqual(name, create['name'])
self.assertEqual(description, create['description'])
self.assertEqual('False', create['is_public'])
self.user_client.update_share(
create['id'], new_name, new_description, True)
get = self.user_client.get_share(create['id'])
self.assertEqual(new_name, get['name'])
self.assertEqual(new_description, get['description'])
self.assertEqual('True', get['is_public'])
def test_get_share(self):
get = self.user_client.get_share(self.share['id'])
self.assertEqual(self.name, get['name'])
self.assertEqual(self.description, get['description'])
self.assertEqual('1', get['size'])
self.assertEqual(self.protocol.upper(), get['share_proto'])
self.assertTrue(get.get('export_locations', []) > 0)
class NFSSharesReadWriteTest(SharesReadWriteBase):
protocol = 'nfs'
class CIFSSharesReadWriteTest(SharesReadWriteBase):
protocol = 'cifs'
class GlusterFSSharesReadWriteTest(SharesReadWriteBase):
protocol = 'glusterfs'
class HDFSSharesReadWriteTest(SharesReadWriteBase):
protocol = 'hdfs'

View File

@ -14,10 +14,15 @@
# under the License.
import ddt
from tempest_lib.common.utils import data_utils
from tempest_lib import exceptions
import testtools
from manilaclient import config
from manilaclient.tests.functional import base
CONF = config.CONF
@ddt.ddt
class SharesListReadOnlyTest(base.BaseTestCase):
@ -89,3 +94,112 @@ class SharesListReadOnlyTest(base.BaseTestCase):
@ddt.data('admin', 'user')
def test_snapshot_list_filter_by_status(self, role):
self.clients[role].manila('snapshot-list', params='--status status')
@ddt.ddt
class SharesListReadWriteTest(base.BaseTestCase):
@classmethod
def setUpClass(cls):
super(SharesListReadWriteTest, cls).setUpClass()
cls.private_name = data_utils.rand_name('autotest_share_name')
cls.private_description = data_utils.rand_name(
'autotest_share_description')
cls.public_name = data_utils.rand_name('autotest_public_share_name')
cls.public_description = data_utils.rand_name(
'autotest_public_share_description')
cls.private_share = cls.create_share(
name=cls.private_name,
description=cls.private_description,
public=False,
cleanup_in_class=True,
client=cls.get_user_client(),
wait_for_creation=False)
cls.public_share = cls.create_share(
name=cls.public_name,
description=cls.public_description,
public=True,
client=cls.get_user_client(),
cleanup_in_class=True)
for share_id in (cls.private_share['id'], cls.public_share['id']):
cls.get_admin_client().wait_for_share_status(share_id, 'available')
def _list_shares(self, filters=None):
filters = filters or dict()
shares = self.user_client.list_shares(filters=filters)
self.assertTrue(len(shares) > 1)
for s_id in (self.private_share['id'], self.public_share['id']):
self.assertTrue(any(s_id == s['ID'] for s in shares))
if filters:
for share in shares:
try:
get = self.user_client.get_share(share['ID'])
except exceptions.NotFound:
# NOTE(vponomaryov): Case when some share was deleted
# between our 'list' and 'get' requests. Skip such case.
# It occurs with concurrently running tests.
continue
for k, v in filters.items():
if k in ('share_network', 'share-network'):
k = 'share_network_id'
if v != 'deleting' and get[k] == 'deleting':
continue
self.assertEqual(v, get[k])
def test_list_shares(self):
self._list_shares()
def test_list_shares_for_all_tenants(self):
shares = self.user_client.list_shares(True)
self.assertTrue(len(shares) > 1)
for s_id in (self.private_share['id'], self.public_share['id']):
self.assertTrue(any(s_id == s['ID'] for s in shares))
def test_list_shares_by_name(self):
shares = self.user_client.list_shares(
filters={'name': self.private_name})
self.assertEqual(1, len(shares))
self.assertTrue(
any(self.private_share['id'] == s['ID'] for s in shares))
for share in shares:
get = self.user_client.get_share(share['ID'])
self.assertEqual(self.private_name, get['name'])
def test_list_shares_by_share_type(self):
share_type_name = self.user_client.get_share_type(
self.private_share['share_type'])['Name']
self._list_shares({'share_type': share_type_name})
def test_list_shares_by_status(self):
self._list_shares({'status': 'available'})
def test_list_shares_by_project_id(self):
project_id = self.user_client.get_project_id(
self.admin_client.tenant_name)
self._list_shares({'project_id': project_id})
@testtools.skipUnless(
CONF.share_network, "Usage of Share networks is disabled")
def test_list_shares_by_share_network(self):
share_network_id = self.user_client.get_share_network(
CONF.share_network)['id']
self._list_shares({'share_network': share_network_id})
def test_list_shares_by_host(self):
get = self.user_client.get_share(self.private_share['id'])
self._list_shares({'host': get['host']})
@ddt.data(
{'limit': 1},
{'limit': 2},
{'limit': 1, 'offset': 1},
{'limit': 2, 'offset': 0},
)
def test_list_shares_with_limit(self, filters):
shares = self.user_client.list_shares(filters=filters)
self.assertEqual(filters['limit'], len(shares))

View File

@ -28,6 +28,7 @@ commands = python setup.py build_sphinx
setenv =
VIRTUAL_ENV = {envdir}
OS_TEST_PATH = ./manilaclient/tests/functional
OS_TEST_TIMEOUT = 500
OS_MANILA_EXEC_DIR = {envdir}/bin
commands =
{envdir}/bin/python setup.py install