Add v2 Manila API path as base for microversions

To prevent a microversioned client from managing a non-microversioned
Manila server, Manila must update its REST endpoints by adding /v2 for
all microversioned APIs.

This commit does the following:

* Add /v2 to the URL map, connected to all the same /v1 API methods
* Renumber the microversion sequence starting from 2.0
* Update the versions API to reflect v2
* Publish the new endpoint to Keystone in the DevStack plug-in
* Update relevant documentation
* Update Tempest tests for microversions
APIImpact
Co-Authored-By: Andrew Kerr <andrew.kerr@netapp.com>
Closes-Bug: 1488624
Change-Id: I56a516b5f81914557dd2465746629431cfd6deac
This commit is contained in:
Clinton Knight 2015-08-27 15:00:23 -04:00 committed by Andrew Kerr
parent 3e0284b524
commit dddc068879
38 changed files with 1383 additions and 836 deletions

View File

@ -434,11 +434,19 @@ function create_manila_accounts {
create_service_user "manila"
if [[ "$KEYSTONE_CATALOG_BACKEND" = 'sql' ]]; then
# Set up Manila v1 service and endpoint
get_or_create_service "manila" "share" "Manila Shared Filesystem Service"
get_or_create_endpoint "share" "$REGION_NAME" \
"$MANILA_SERVICE_PROTOCOL://$MANILA_SERVICE_HOST:$MANILA_SERVICE_PORT/v1/\$(tenant_id)s" \
"$MANILA_SERVICE_PROTOCOL://$MANILA_SERVICE_HOST:$MANILA_SERVICE_PORT/v1/\$(tenant_id)s" \
"$MANILA_SERVICE_PROTOCOL://$MANILA_SERVICE_HOST:$MANILA_SERVICE_PORT/v1/\$(tenant_id)s"
# Set up Manila v2 service and endpoint
get_or_create_service "manilav2" "sharev2" "Manila Shared Filesystem Service V2"
get_or_create_endpoint "sharev2" "$REGION_NAME" \
"$MANILA_SERVICE_PROTOCOL://$MANILA_SERVICE_HOST:$MANILA_SERVICE_PORT/v2/\$(tenant_id)s" \
"$MANILA_SERVICE_PROTOCOL://$MANILA_SERVICE_HOST:$MANILA_SERVICE_PORT/v2/\$(tenant_id)s" \
"$MANILA_SERVICE_PROTOCOL://$MANILA_SERVICE_HOST:$MANILA_SERVICE_PORT/v2/\$(tenant_id)s"
fi
}

View File

@ -132,6 +132,11 @@ Here are the registration steps, similar to those of Cinder:
--type share \
--description "OpenStack Shared Filesystems"
$ keystone service-create \
--name manilav2 \
--type sharev2 \
--description "OpenStack Shared Filesystems V2"
Result::
@ -145,6 +150,16 @@ Result::
| type | share |
+-------------+----------------------------------+
+-------------+----------------------------------+
| Property | Value |
+-------------+----------------------------------+
| description | OpenStack Shared Filesystems V2 |
| enabled | True |
| id | 2840d1e7b033437f8776a7bd5045b28d |
| name | manilav2 |
| type | sharev2 |
+-------------+----------------------------------+
4) Create the Share Filesystems service API endpoints::
@ -155,6 +170,13 @@ Result::
--adminurl http://controller:8786/v1/%\(tenant_id\)s \
--region regionOne
$ keystone endpoint-create \
--service-id $(keystone service-list | awk '/ sharev2 / {print $2}') \
--publicurl http://controller:8786/v2/%\(tenant_id\)s \
--internalurl http://controller:8786/v2/%\(tenant_id\)s \
--adminurl http://controller:8786/v2/%\(tenant_id\)s \
--region regionOne
Result::
+-------------+-----------------------------------------+
@ -168,6 +190,17 @@ Result::
| service_id | 4c13e9ff7ec04f4e95a26f72ecdf9919 |
+-------------+-----------------------------------------+
+-------------+-----------------------------------------+
| Property | Value |
+-------------+-----------------------------------------+
| adminurl | http://controller:8786/v2/%(tenant_id)s |
| id | 63ddffd27e8c4c62b4ffb228083325e6 |
| internalurl | http://controller:8786/v2/%(tenant_id)s |
| publicurl | http://controller:8786/v2/%(tenant_id)s |
| region | regionOne |
| service_id | 2840d1e7b033437f8776a7bd5045b28d |
+-------------+-----------------------------------------+
.. note::
Port 8786 is the default port for Manila. It may be changed to any
other port, but this change should also be made in the Manila configuration

View File

@ -5,13 +5,14 @@
[composite:osapi_share]
use = call:manila.api:root_app_factory
/: apiversions
/v1: openstack_share_api_v1
/v1: openstack_share_api
/v2: openstack_share_api
[composite:openstack_share_api_v1]
[composite:openstack_share_api]
use = call:manila.api.middleware.auth:pipeline_factory
noauth = faultwrap ssl sizelimit noauth apiv1
keystone = faultwrap ssl sizelimit authtoken keystonecontext apiv1
keystone_nolimit = faultwrap ssl sizelimit authtoken keystonecontext apiv1
noauth = faultwrap ssl sizelimit noauth api
keystone = faultwrap ssl sizelimit authtoken keystonecontext api
keystone_nolimit = faultwrap ssl sizelimit authtoken keystonecontext api
[filter:faultwrap]
paste.filter_factory = manila.api.middleware.fault:FaultWrapper.factory
@ -25,7 +26,7 @@ paste.filter_factory = oslo_middleware.sizelimit:RequestBodySizeLimiter.factory
[filter:ssl]
paste.filter_factory = oslo_middleware.ssl:SSLMiddleware.factory
[app:apiv1]
[app:api]
paste.app_factory = manila.api.v1.router:APIRouter.factory
[pipeline:apiversions]

View File

@ -167,15 +167,15 @@ class CGAdminController(AdminController):
def _delete(self, context, resource, force=True):
db.consistency_group_destroy(context.elevated(), resource['id'])
@wsgi.Controller.api_version('2.4', experimental=True)
@wsgi.action('os-reset_status')
@wsgi.response(202)
@wsgi.Controller.api_version('1.5', experimental=True)
def cg_reset_status(self, req, id, body):
super(CGAdminController, self)._reset_status(req, id, body)
@wsgi.Controller.api_version('2.4', experimental=True)
@wsgi.action('os-force_delete')
@wsgi.response(202)
@wsgi.Controller.api_version('1.5', experimental=True)
def cg_force_delete(self, req, id, body):
super(CGAdminController, self)._force_delete(req, id, body)
@ -198,15 +198,15 @@ class CGSnapshotAdminController(AdminController):
def _delete(self, context, resource, force=True):
db.cgsnapshot_destroy(context.elevated(), resource['id'])
@wsgi.Controller.api_version('2.4', experimental=True)
@wsgi.action('os-reset_status')
@wsgi.response(202)
@wsgi.Controller.api_version('1.5', experimental=True)
def cgsnapshot_reset_status(self, req, id, body):
super(CGSnapshotAdminController, self)._reset_status(req, id, body)
@wsgi.Controller.api_version('2.4', experimental=True)
@wsgi.action('os-force_delete')
@wsgi.response(202)
@wsgi.Controller.api_version('1.5', experimental=True)
def cgsnapshot_force_delete(self, req, id, body):
super(CGSnapshotAdminController, self)._force_delete(req, id, body)

View File

@ -45,20 +45,20 @@ REST_API_VERSION_HISTORY = """
REST API Version History:
* 1.0 - Initial version. Includes all V1 APIs and extensions in Kilo.
* 1.1 - Versions API updated to reflect beginning of microversions epoch.
* 1.2 - Share create() doesn't ignore availability_zone field of share.
* 1.3 - Snapshots become optional feature.
* 1.4 - Share instances admin API
* 1.5 - Consistency Group support
* 1.6 - Share Migration admin API
* 2.0 - Versions API updated to reflect beginning of microversions epoch.
* 2.1 - Share create() doesn't ignore availability_zone field of share.
* 2.2 - Snapshots become optional feature.
* 2.3 - Share instances admin API
* 2.4 - Consistency Group support
* 2.5 - Share Migration admin API
"""
# The minimum and maximum versions of the API supported
# The default api version request is defined to be the
# the minimum version of the API supported.
_MIN_API_VERSION = "1.0"
_MAX_API_VERSION = "1.6"
_MIN_API_VERSION = "2.0"
_MAX_API_VERSION = "2.5"
DEFAULT_API_VERSION = _MIN_API_VERSION

View File

@ -8,15 +8,15 @@ user documentation.
1.0
---
The 1.0 Manila API includes all v1 core APIs existing prior to
the introduction of microversions.
the introduction of microversions. The /v1 URL is used to call
1.0 APIs, and microversions headers sent to this endpoint are
ignored.
1.1
2.0
---
This is the initial version of the Manila API which supports
microversions.
microversions. The /v2 URL is used to call 2.x APIs.
A user can specify a header in the API request::
@ -24,33 +24,35 @@ user documentation.
where ``<version>`` is any valid api version for this API.
If no version is specified then the API will behave as version 1.0
If no version is specified then the API will behave as if version 2.0
was requested.
The only API change in version 1.1 is versions, i.e.
GET http://localhost:8786/, which now returns the minimum and
current microversion values.
The only API change in version 2.0 is versions, i.e.
GET http://localhost:8786/, which now returns information about
both 1.0 and 2.x versions and their respective /v1 and /v2 endpoints.
1.2
All other 2.0 APIs are functionally identical to version 1.0.
2.1
---
Share create() method doesn't ignore availability_zone field of provided
share.
1.3
2.2
---
Snapshots become optional and share payload now has
boolean attr 'snapshot_support'.
1.4
2.3
---
Share instances admin API and update of Admin Actions extension.
1.5
2.4
---
Consistency groups support. /consistency-groups and /cgsnapshots are
implemented. AdminActions 'os-force_delete and' 'os-reset_status' have been
updated for both new resources.
1.6
2.5
---
Share Migration admin API.

View File

@ -51,6 +51,8 @@ VER_METHOD_ATTR = 'versioned_methods'
API_VERSION_REQUEST_HEADER = 'X-OpenStack-Manila-API-Version'
EXPERIMENTAL_API_REQUEST_HEADER = 'X-OpenStack-Manila-API-Experimental'
V1_SCRIPT_NAME = '/v1'
class Request(webob.Request):
"""Add some OpenStack API-specific logic to the base webob.Request."""
@ -207,30 +209,40 @@ class Request(webob.Request):
return content_type
def set_api_version_request(self):
"""Set API version request based on the request header information."""
if API_VERSION_REQUEST_HEADER in self.headers:
hdr_string = self.headers[API_VERSION_REQUEST_HEADER]
# 'latest' is a special keyword which is equivalent to requesting
# the maximum version of the API supported
if hdr_string == 'latest':
self.api_version_request = api_version.max_api_version()
"""Set API version request based on the request header information.
Microversions starts with /v2, so if a client sends a /v1 URL, then
ignore the headers and request 1.0 APIs.
"""
if not self.script_name:
self.api_version_request = api_version.APIVersionRequest()
elif self.script_name == V1_SCRIPT_NAME:
self.api_version_request = api_version.APIVersionRequest('1.0')
else:
if API_VERSION_REQUEST_HEADER in self.headers:
hdr_string = self.headers[API_VERSION_REQUEST_HEADER]
# 'latest' is a special keyword which is equivalent to
# requesting the maximum version of the API supported
if hdr_string == 'latest':
self.api_version_request = api_version.max_api_version()
else:
self.api_version_request = api_version.APIVersionRequest(
hdr_string)
# Check that the version requested is within the global
# minimum/maximum of supported API versions
if not self.api_version_request.matches(
api_version.min_api_version(),
api_version.max_api_version()):
raise exception.InvalidGlobalAPIVersion(
req_ver=self.api_version_request.get_string(),
min_ver=api_version.min_api_version().get_string(),
max_ver=api_version.max_api_version().get_string())
else:
self.api_version_request = api_version.APIVersionRequest(
hdr_string)
# Check that the version requested is within the global
# minimum/maximum of supported API versions
if not self.api_version_request.matches(
api_version.min_api_version(),
api_version.max_api_version()):
raise exception.InvalidGlobalAPIVersion(
req_ver=self.api_version_request.get_string(),
min_ver=api_version.min_api_version().get_string(),
max_ver=api_version.max_api_version().get_string())
else:
self.api_version_request = api_version.APIVersionRequest(
api_version.DEFAULT_API_VERSION)
api_version.DEFAULT_API_VERSION)
# Check if experimental API was requested
if EXPERIMENTAL_API_REQUEST_HEADER in self.headers:

View File

@ -41,7 +41,7 @@ class CGSnapshotController(wsgi.Controller):
super(CGSnapshotController, self).__init__()
self.cg_api = cg_api.API()
@wsgi.Controller.api_version('1.5', experimental=True)
@wsgi.Controller.api_version('2.4', experimental=True)
def show(self, req, id):
"""Return data about the given cgsnapshot."""
context = req.environ['manila.context']
@ -54,7 +54,7 @@ class CGSnapshotController(wsgi.Controller):
return self._view_builder.detail(req, cg)
@wsgi.Controller.api_version('1.5', experimental=True)
@wsgi.Controller.api_version('2.4', experimental=True)
def delete(self, req, id):
"""Delete a cgsnapshot."""
context = req.environ['manila.context']
@ -75,12 +75,12 @@ class CGSnapshotController(wsgi.Controller):
return webob.Response(status_int=202)
@wsgi.Controller.api_version('1.5', experimental=True)
@wsgi.Controller.api_version('2.4', experimental=True)
def index(self, req):
"""Returns a summary list of cgsnapshots."""
return self._get_cgs(req, is_detail=False)
@wsgi.Controller.api_version('1.5', experimental=True)
@wsgi.Controller.api_version('2.4', experimental=True)
def detail(self, req):
"""Returns a detailed list of cgsnapshots."""
return self._get_cgs(req, is_detail=True)
@ -107,7 +107,7 @@ class CGSnapshotController(wsgi.Controller):
snaps = self._view_builder.summary_list(req, limited_list)
return snaps
@wsgi.Controller.api_version('1.5', experimental=True)
@wsgi.Controller.api_version('2.4', experimental=True)
def update(self, req, id, body):
"""Update a cgsnapshot."""
context = req.environ['manila.context']
@ -135,7 +135,7 @@ class CGSnapshotController(wsgi.Controller):
cg = self.cg_api.update_cgsnapshot(context, cg, cg_data)
return self._view_builder.detail(req, cg)
@wsgi.Controller.api_version('1.5', experimental=True)
@wsgi.Controller.api_version('2.4', experimental=True)
@wsgi.response(202)
def create(self, req, body):
"""Creates a new cgsnapshot."""
@ -174,7 +174,7 @@ class CGSnapshotController(wsgi.Controller):
return self._view_builder.detail(req, dict(six.iteritems(
new_snapshot)))
@wsgi.Controller.api_version('1.5', experimental=True)
@wsgi.Controller.api_version('2.4', experimental=True)
def members(self, req, id):
"""Returns a list of cgsnapshot members."""
context = req.environ['manila.context']

View File

@ -42,7 +42,7 @@ class CGController(wsgi.Controller):
super(CGController, self).__init__()
self.cg_api = cg_api.API()
@wsgi.Controller.api_version('1.5', experimental=True)
@wsgi.Controller.api_version('2.4', experimental=True)
def show(self, req, id):
"""Return data about the given CG."""
context = req.environ['manila.context']
@ -55,7 +55,7 @@ class CGController(wsgi.Controller):
return self._view_builder.detail(req, cg)
@wsgi.Controller.api_version('1.5', experimental=True)
@wsgi.Controller.api_version('2.4', experimental=True)
def delete(self, req, id):
"""Delete a CG."""
context = req.environ['manila.context']
@ -76,12 +76,12 @@ class CGController(wsgi.Controller):
return webob.Response(status_int=202)
@wsgi.Controller.api_version('1.5', experimental=True)
@wsgi.Controller.api_version('2.4', experimental=True)
def index(self, req):
"""Returns a summary list of shares."""
return self._get_cgs(req, is_detail=False)
@wsgi.Controller.api_version('1.5', experimental=True)
@wsgi.Controller.api_version('2.4', experimental=True)
def detail(self, req):
"""Returns a detailed list of shares."""
return self._get_cgs(req, is_detail=True)
@ -108,7 +108,7 @@ class CGController(wsgi.Controller):
cgs = self._view_builder.summary_list(req, limited_list)
return cgs
@wsgi.Controller.api_version('1.5', experimental=True)
@wsgi.Controller.api_version('2.4', experimental=True)
def update(self, req, id, body):
"""Update a share."""
context = req.environ['manila.context']
@ -136,7 +136,7 @@ class CGController(wsgi.Controller):
cg = self.cg_api.update(context, cg, cg_data)
return self._view_builder.detail(req, cg)
@wsgi.Controller.api_version('1.5', experimental=True)
@wsgi.Controller.api_version('2.4', experimental=True)
@wsgi.response(202)
def create(self, req, body):
"""Creates a new share."""

View File

@ -39,7 +39,7 @@ class ShareInstancesController(wsgi.Controller):
except exception.PolicyNotAuthorized:
raise exc.HTTPForbidden()
@wsgi.Controller.api_version("1.4")
@wsgi.Controller.api_version("2.3")
def index(self, req):
context = req.environ['manila.context']
self._authorize(context, 'index')
@ -47,7 +47,7 @@ class ShareInstancesController(wsgi.Controller):
instances = db.share_instances_get_all(context)
return self._view_builder.detail_list(req, instances)
@wsgi.Controller.api_version("1.4")
@wsgi.Controller.api_version("2.3")
def show(self, req, id):
context = req.environ['manila.context']
self._authorize(context, 'show')
@ -59,7 +59,7 @@ class ShareInstancesController(wsgi.Controller):
return self._view_builder.detail(req, instance)
@wsgi.Controller.api_version("1.4")
@wsgi.Controller.api_version("2.3")
def get_share_instances(self, req, share_id):
context = req.environ['manila.context']
self._authorize(context, 'index')

View File

@ -89,7 +89,7 @@ class ShareController(wsgi.Controller):
return webob.Response(status_int=202)
@wsgi.Controller.api_version("1.6", None, True)
@wsgi.Controller.api_version("2.5", None, True)
@wsgi.action("os-migrate_share")
def migrate_share(self, req, id, body):
"""Migrate a share to the specified host."""
@ -205,11 +205,11 @@ class ShareController(wsgi.Controller):
share.update(update_dict)
return self._view_builder.detail(req, share)
@wsgi.Controller.api_version("1.5")
@wsgi.Controller.api_version("2.4")
def create(self, req, body):
return self._create(req, body)
@wsgi.Controller.api_version("1.0", "1.4") # noqa
@wsgi.Controller.api_version("1.0", "2.3") # noqa
def create(self, req, body): # pylint: disable=E0102
# Remove consistency group attributes
share = body.get('share', {})

View File

@ -26,27 +26,35 @@ from manila.api.views import versions as views_versions
CONF = cfg.CONF
_LINKS = [{
'rel': 'describedby',
'type': 'text/html',
'href': 'http://docs.openstack.org/',
}]
_MEDIA_TYPES = [{
'base': 'application/json',
'type': 'application/vnd.openstack.share+json;version=1',
}]
_KNOWN_VERSIONS = {
'v1.0': {
'id': 'v1.0',
'status': 'SUPPORTED',
'version': '',
'min_version': '',
'updated': '2015-08-27T11:33:21Z',
'links': _LINKS,
'media-types': _MEDIA_TYPES,
},
'v2.0': {
'id': 'v2.0',
'status': 'CURRENT',
'version': api_version_request._MAX_API_VERSION,
'min_version': api_version_request._MIN_API_VERSION,
'updated': '2015-07-30T11:33:21Z',
'links': [
{
'rel': 'describedby',
'type': 'text/html',
'href': 'http://docs.openstack.org/',
},
],
'media-types': [
{
'base': 'application/json',
'type': 'application/vnd.openstack.share+json;version=1',
}
],
'updated': '2015-08-27T11:33:21Z',
'links': _LINKS,
'media-types': _MEDIA_TYPES,
},
}
@ -60,7 +68,7 @@ class VersionsRouter(openstack.APIRouter):
self.resources['versions'] = create_resource()
mapper.connect('versions', '/',
controller=self.resources['versions'],
action='index')
action='all')
mapper.redirect('', '/')
@ -71,16 +79,27 @@ class VersionsController(wsgi.Controller):
@wsgi.Controller.api_version('1.0', '1.0')
def index(self, req):
"""Return all versions."""
"""Return versions supported prior to the microversions epoch."""
builder = views_versions.get_view_builder(req)
known_versions = copy.deepcopy(_KNOWN_VERSIONS)
known_versions['v1.0'].pop('min_version')
known_versions['v1.0'].pop('version')
known_versions.pop('v2.0')
return builder.build_versions(known_versions)
@wsgi.Controller.api_version('1.1') # noqa
@wsgi.Controller.api_version('2.0') # noqa
def index(self, req): # pylint: disable=E0102
"""Return all versions."""
"""Return versions supported after the start of microversions."""
builder = views_versions.get_view_builder(req)
known_versions = copy.deepcopy(_KNOWN_VERSIONS)
known_versions.pop('v1.0')
return builder.build_versions(known_versions)
# NOTE (cknight): Calling the versions API without
# /v1 or /v2 in the URL will lead to this unversioned
# method, which should always return info about all
# available versions.
@wsgi.response(300)
def all(self, req):
"""Return all known versions."""
builder = views_versions.get_view_builder(req)
known_versions = copy.deepcopy(_KNOWN_VERSIONS)
return builder.build_versions(known_versions)

View File

@ -85,7 +85,7 @@ class ViewBuilder(common.ViewBuilder):
share_dict['share_server_id'] = share.get('share_server_id')
return {'share': share_dict}
@common.ViewBuilder.versioned_method("1.5")
@common.ViewBuilder.versioned_method("2.4")
def add_consistency_group_fields(self, share_dict, share):
share_dict['consistency_group_id'] = share.get(
'consistency_group_id')

View File

@ -24,6 +24,9 @@ def get_view_builder(req):
return ViewBuilder(req.application_url)
_URL_SUFFIX = {'v1.0': 'v1', 'v2.0': 'v2'}
class ViewBuilder(object):
def __init__(self, base_url):
"""Initialize ViewBuilder.
@ -45,7 +48,9 @@ class ViewBuilder(object):
def _build_links(self, version_data):
"""Generate a container of links that refer to the provided version."""
links = copy.deepcopy(version_data.get('links', {}))
links.append({'rel': 'self', 'href': self._generate_href()})
version = _URL_SUFFIX.get(version_data['id'])
links.append({'rel': 'self',
'href': self._generate_href(version=version)})
return links
def _generate_href(self, version='v1', path=None):

View File

@ -36,7 +36,7 @@ def app():
# no auth, just let environ['manila.context'] pass through
api = fakes.router.APIRouter()
mapper = fakes.urlmap.URLMap()
mapper['/v1'] = api
mapper['/v2'] = api
return mapper
@ -76,7 +76,7 @@ class AdminActionsTest(test.TestCase):
share = db_utils.create_share(status=constants.STATUS_AVAILABLE,
size='1',
override_defaults=True)
req = webob.Request.blank('/v1/fake/shares/%s/action' % share['id'])
req = webob.Request.blank('/v2/fake/shares/%s/action' % share['id'])
return share, req
def _setup_snapshot_data(self, snapshot=None):
@ -84,7 +84,7 @@ class AdminActionsTest(test.TestCase):
share = db_utils.create_share()
snapshot = db_utils.create_snapshot(
status=constants.STATUS_AVAILABLE, share_id=share['id'])
req = webob.Request.blank('/v1/fake/snapshots/%s/action' %
req = webob.Request.blank('/v2/fake/snapshots/%s/action' %
snapshot['id'])
return snapshot, req
@ -93,16 +93,16 @@ class AdminActionsTest(test.TestCase):
instance = db_utils.create_share(status=constants.STATUS_AVAILABLE,
size='1').instance
req = webob.Request.blank(
'/v1/fake/share_instances/%s/action' % instance['id'])
'/v2/fake/share_instances/%s/action' % instance['id'])
return instance, req
def _setup_cg_data(self, cg=None):
if cg is None:
cg = db_utils.create_consistency_group(
status=constants.STATUS_AVAILABLE)
req = webob.Request.blank('/v1/fake/consistency-groups/%s/action' %
req = webob.Request.blank('/v2/fake/consistency-groups/%s/action' %
cg['id'])
req.headers[wsgi.API_VERSION_REQUEST_HEADER] = '1.5'
req.headers[wsgi.API_VERSION_REQUEST_HEADER] = '2.4'
req.headers[wsgi.EXPERIMENTAL_API_REQUEST_HEADER] = 'True'
return cg, req
@ -111,9 +111,9 @@ class AdminActionsTest(test.TestCase):
if cgsnapshot is None:
cgsnapshot = db_utils.create_cgsnapshot(
'fake_id', status=constants.STATUS_AVAILABLE)
req = webob.Request.blank('/v1/fake/cgsnapshots/%s/action' %
req = webob.Request.blank('/v2/fake/cgsnapshots/%s/action' %
cgsnapshot['id'])
req.headers[wsgi.API_VERSION_REQUEST_HEADER] = '1.5'
req.headers[wsgi.API_VERSION_REQUEST_HEADER] = '2.4'
req.headers[wsgi.EXPERIMENTAL_API_REQUEST_HEADER] = 'True'
return cgsnapshot, req

View File

@ -36,94 +36,81 @@ class VersionsControllerTestCase(test.TestCase):
super(VersionsControllerTestCase, self).setUp()
self.wsgi_apps = (versions.VersionsRouter(), router.APIRouter())
@ddt.data(('', 302), ('/', 200))
@ddt.unpack
def test_versions_return_codes(self, request_path, return_code):
req = fakes.HTTPRequest.blank(request_path)
@ddt.data('1.0', '1.1', '2.0', '3.0')
def test_versions_root(self, version):
req = fakes.HTTPRequest.blank('/', base_url='http://localhost')
req.method = 'GET'
req.content_type = 'application/json'
req.headers = {version_header_name: version}
for app in self.wsgi_apps:
response = req.get_response(app)
self.assertEqual(return_code, response.status_int)
response = req.get_response(versions.VersionsRouter())
self.assertEqual(300, response.status_int)
body = jsonutils.loads(response.body)
version_list = body['versions']
@ddt.data(
('http://localhost/', True),
(None, True),
('http://localhost/', False),
(None, False),
)
@ddt.unpack
def test_versions_index_v10(self, base_url, include_header):
req = fakes.HTTPRequest.blank('/', base_url=base_url)
ids = [v['id'] for v in version_list]
self.assertEqual({'v1.0', 'v2.0'}, set(ids))
self.assertNotIn(version_header_name, response.headers)
self.assertNotIn('Vary', response.headers)
v1 = [v for v in version_list if v['id'] == 'v1.0'][0]
self.assertEqual('', v1.get('min_version'))
self.assertEqual('', v1.get('version'))
v2 = [v for v in version_list if v['id'] == 'v2.0'][0]
self.assertEqual(api_version_request._MIN_API_VERSION,
v2.get('min_version'))
self.assertEqual(api_version_request._MAX_API_VERSION,
v2.get('version'))
@ddt.data('1.0',
'1.1',
api_version_request._MIN_API_VERSION,
api_version_request._MAX_API_VERSION)
def test_versions_v1(self, version):
req = fakes.HTTPRequest.blank('/', base_url='http://localhost/v1')
req.method = 'GET'
req.content_type = 'application/json'
if include_header:
req.headers = {version_header_name: '1.0'}
req.headers = {version_header_name: version}
for app in self.wsgi_apps:
response = req.get_response(app)
body = jsonutils.loads(response.body)
version_list = body['versions']
response = req.get_response(router.APIRouter())
self.assertEqual(200, response.status_int)
body = jsonutils.loads(response.body)
version_list = body['versions']
ids = [v['id'] for v in version_list]
self.assertEqual({'v1.0'}, set(ids))
self.assertEqual('1.0', response.headers[version_header_name])
self.assertEqual(version_header_name, response.headers['Vary'])
self.assertIsNone(version_list[0].get('min_version'))
self.assertIsNone(version_list[0].get('version'))
ids = [v['id'] for v in version_list]
self.assertEqual({'v1.0'}, set(ids))
self.assertEqual('1.0', response.headers[version_header_name])
self.assertEqual(version_header_name, response.headers['Vary'])
self.assertEqual('', version_list[0].get('min_version'))
self.assertEqual('', version_list[0].get('version'))
@ddt.data(
('http://localhost/', '1.1'),
(None, '1.1'),
('http://localhost/', 'latest'),
(None, 'latest')
)
@ddt.unpack
def test_versions_index_v11(self, base_url, req_version):
req = fakes.HTTPRequest.blank('/', base_url=base_url)
@ddt.data(api_version_request._MIN_API_VERSION,
api_version_request._MAX_API_VERSION)
def test_versions_v2(self, version):
req = fakes.HTTPRequest.blank('/', base_url='http://localhost/v2')
req.method = 'GET'
req.content_type = 'application/json'
req.headers = {version_header_name: req_version}
req.headers = {version_header_name: version}
for app in self.wsgi_apps:
response = req.get_response(app)
body = jsonutils.loads(response.body)
version_list = body['versions']
response = req.get_response(router.APIRouter())
self.assertEqual(200, response.status_int)
body = jsonutils.loads(response.body)
version_list = body['versions']
ids = [v['id'] for v in version_list]
self.assertEqual({'v1.0'}, set(ids))
ids = [v['id'] for v in version_list]
self.assertEqual({'v2.0'}, set(ids))
self.assertEqual(version, response.headers[version_header_name])
self.assertEqual(version_header_name, response.headers['Vary'])
if req_version == 'latest':
self.assertEqual(api_version_request._MAX_API_VERSION,
response.headers[version_header_name])
else:
self.assertEqual(req_version,
response.headers[version_header_name])
v2 = [v for v in version_list if v['id'] == 'v2.0'][0]
self.assertEqual(api_version_request._MIN_API_VERSION,
v2.get('min_version'))
self.assertEqual(api_version_request._MAX_API_VERSION,
v2.get('version'))
self.assertEqual(version_header_name, response.headers['Vary'])
self.assertEqual(api_version_request._MIN_API_VERSION,
version_list[0].get('min_version'))
self.assertEqual(api_version_request._MAX_API_VERSION,
version_list[0].get('version'))
@ddt.data('http://localhost/', None)
def test_versions_index_v2(self, base_url):
req = fakes.HTTPRequest.blank('/', base_url=base_url)
req.method = 'GET'
req.content_type = 'application/json'
req.headers = {version_header_name: '2.0'}
for app in self.wsgi_apps:
response = req.get_response(app)
self.assertEqual(406, response.status_int)
self.assertEqual('2.0', response.headers[version_header_name])
self.assertEqual(version_header_name, response.headers['Vary'])
@ddt.data('http://localhost/', None)
def test_versions_index_invalid_version_request(self, base_url):
req = fakes.HTTPRequest.blank('/', base_url=base_url)
def test_versions_version_invalid(self):
req = fakes.HTTPRequest.blank('/', base_url='http://localhost/v2')
req.method = 'GET'
req.content_type = 'application/json'
req.headers = {version_header_name: '2.0.1'}
@ -132,8 +119,6 @@ class VersionsControllerTestCase(test.TestCase):
response = req.get_response(app)
self.assertEqual(400, response.status_int)
self.assertEqual('1.0', response.headers[version_header_name])
self.assertEqual(version_header_name, response.headers['Vary'])
def test_versions_version_not_found(self):
api_version_request_3_0 = api_version_request.APIVersionRequest('3.0')
@ -142,45 +127,59 @@ class VersionsControllerTestCase(test.TestCase):
mock.Mock(return_value=api_version_request_3_0))
class Controller(wsgi.Controller):
@wsgi.Controller.api_version('1.0', '1.0')
@wsgi.Controller.api_version('2.0', '2.0')
def index(self, req):
return 'off'
req = fakes.HTTPRequest.blank('/tests')
req.headers = {version_header_name: '2.0'}
req = fakes.HTTPRequest.blank('/tests', base_url='http://localhost/v2')
req.headers = {version_header_name: '2.5'}
app = fakes.TestRouter(Controller())
response = req.get_response(app)
self.assertEqual(404, response.status_int)
def test_versions_version_not_acceptable(self):
req = fakes.HTTPRequest.blank('/', base_url='http://localhost/v2')
req.method = 'GET'
req.content_type = 'application/json'
req.headers = {version_header_name: '3.0'}
response = req.get_response(router.APIRouter())
self.assertEqual(406, response.status_int)
self.assertEqual('3.0', response.headers[version_header_name])
self.assertEqual(version_header_name, response.headers['Vary'])
@ddt.ddt
class ExperimentalAPITestCase(test.TestCase):
class Controller(wsgi.Controller):
@wsgi.Controller.api_version('1.0', '1.0')
@wsgi.Controller.api_version('2.0', '2.0')
def index(self, req):
return {'fake_key': 'fake_value'}
@wsgi.Controller.api_version('1.1', '1.1', experimental=True) # noqa
@wsgi.Controller.api_version('2.1', '2.1', experimental=True) # noqa
def index(self, req): # pylint: disable=E0102
return {'fake_key': 'fake_value'}
def setUp(self):
super(ExperimentalAPITestCase, self).setUp()
self.app = fakes.TestRouter(ExperimentalAPITestCase.Controller())
self.req = fakes.HTTPRequest.blank('/tests',
base_url='http://localhost/v2')
@ddt.data(True, False)
def test_stable_api_always_called(self, experimental):
req = fakes.HTTPRequest.blank('/tests')
req.headers = {version_header_name: '1.0'}
self.req.headers = {version_header_name: '2.0'}
if experimental:
req.headers[experimental_header_name] = experimental
response = req.get_response(self.app)
self.req.headers[experimental_header_name] = experimental
response = self.req.get_response(self.app)
self.assertEqual(200, response.status_int)
self.assertEqual('1.0', response.headers[version_header_name])
self.assertEqual('2.0', response.headers[version_header_name])
if experimental:
self.assertEqual(experimental,
@ -190,22 +189,20 @@ class ExperimentalAPITestCase(test.TestCase):
def test_experimental_api_called_when_requested(self):
req = fakes.HTTPRequest.blank('/tests')
req.headers = {
version_header_name: '1.1',
self.req.headers = {
version_header_name: '2.1',
experimental_header_name: 'True',
}
response = req.get_response(self.app)
response = self.req.get_response(self.app)
self.assertEqual(200, response.status_int)
self.assertEqual('1.1', response.headers[version_header_name])
self.assertEqual('2.1', response.headers[version_header_name])
self.assertTrue(response.headers.get(experimental_header_name))
def test_experimental_api_not_called_when_not_requested(self):
req = fakes.HTTPRequest.blank('/tests')
req.headers = {version_header_name: '1.1'}
response = req.get_response(self.app)
self.req.headers = {version_header_name: '2.1'}
response = self.req.get_response(self.app)
self.assertEqual(404, response.status_int)
self.assertFalse(experimental_header_name in response.headers)
@ -217,12 +214,11 @@ class ExperimentalAPITestCase(test.TestCase):
'max_api_version',
mock.Mock(return_value=api_version_request_3_0))
req = fakes.HTTPRequest.blank('/tests')
req.headers = {
version_header_name: '1.2',
self.req.headers = {
version_header_name: '2.2',
experimental_header_name: 'True',
}
response = req.get_response(self.app)
response = self.req.get_response(self.app)
self.assertEqual(404, response.status_int)
self.assertTrue(response.headers.get(experimental_header_name))

View File

@ -36,7 +36,7 @@ class CGSnapshotApiTest(test.TestCase):
def setUp(self):
super(CGSnapshotApiTest, self).setUp()
self.controller = cgs.CGSnapshotController()
self.api_version = '1.5'
self.api_version = '2.4'
self.request = fakes.HTTPRequest.blank('/consistency-groups',
version=self.api_version,
experimental=True)

View File

@ -40,7 +40,7 @@ class CGApiTest(test.TestCase):
super(CGApiTest, self).setUp()
self.controller = cgs.CGController()
self.fake_share_type = {'id': six.text_type(uuid.uuid4())}
self.api_version = '1.5'
self.api_version = '2.4'
self.request = fakes.HTTPRequest.blank('/consistency-groups',
version=self.api_version,
experimental=True)

View File

@ -34,7 +34,7 @@ class ShareInstancesApiTest(test.TestCase):
def _get_request(self, uri, context=None):
if context is None:
context = self.context
req = fakes.HTTPRequest.blank('/shares', version="1.4")
req = fakes.HTTPRequest.blank('/shares', version="2.3")
req.environ['manila.context'] = context
return req

View File

@ -142,7 +142,7 @@ class ShareApiTest(test.TestCase):
self.mock_object(share_api.API, 'create', self.create_mock)
body = {"share": copy.deepcopy(self.share)}
req = fakes.HTTPRequest.blank('/shares', version="1.5")
req = fakes.HTTPRequest.blank('/shares', version="2.4")
res_dict = self.controller.create(req, body)
expected = self._get_expected_share_detailed_response(self.share)
@ -213,7 +213,7 @@ class ShareApiTest(test.TestCase):
use_admin_context=True)
req.method = 'POST'
req.headers['content-type'] = 'application/json'
req.api_version_request = api_version.APIVersionRequest('1.6')
req.api_version_request = api_version.APIVersionRequest('2.5')
req.api_version_request.experimental = True
body = {'os-migrate_share': {'host': 'fake_host'}}
self.mock_object(share_api.API, 'migrate_share')
@ -224,7 +224,7 @@ class ShareApiTest(test.TestCase):
use_admin_context=True)
req.method = 'POST'
req.headers['content-type'] = 'application/json'
req.api_version_request = api_version.APIVersionRequest('1.6')
req.api_version_request = api_version.APIVersionRequest('2.5')
req.api_version_request.experimental = True
body = {'os-migrate_share': {'host': 'fake_host'}}
self.mock_object(share_api.API, 'migrate_share')
@ -240,7 +240,7 @@ class ShareApiTest(test.TestCase):
use_admin_context=True)
req.method = 'POST'
req.headers['content-type'] = 'application/json'
req.api_version_request = api_version.APIVersionRequest('1.6')
req.api_version_request = api_version.APIVersionRequest('2.5')
req.api_version_request.experimental = True
body = {'os-migrate_share': {}}
self.mock_object(share_api.API, 'migrate_share')
@ -254,7 +254,7 @@ class ShareApiTest(test.TestCase):
use_admin_context=True)
req.method = 'POST'
req.headers['content-type'] = 'application/json'
req.api_version_request = api_version.APIVersionRequest('1.6')
req.api_version_request = api_version.APIVersionRequest('2.5')
req.api_version_request.experimental = True
body = {'os-migrate_share': {'host': 'fake_host',
'force_host_copy': 'fake'}}
@ -418,7 +418,7 @@ class ShareApiTest(test.TestCase):
self.assertEqual(expected, res_dict)
def test_share_show_with_consistency_group(self):
req = fakes.HTTPRequest.blank('/shares/1', version='1.5')
req = fakes.HTTPRequest.blank('/shares/1', version='2.4')
res_dict = self.controller.show(req, '1')
expected = self._get_expected_share_detailed_response()
expected['share']['consistency_group_id'] = None
@ -489,7 +489,7 @@ class ShareApiTest(test.TestCase):
shr = self.share
body = {"share": shr}
req = fakes.HTTPRequest.blank('/share/1', version="1.5")
req = fakes.HTTPRequest.blank('/share/1', version="2.4")
res_dict = self.controller.update(req, 1, body)
self.assertIsNone(res_dict['share']["consistency_group_id"])
self.assertIsNone(res_dict['share']["source_cgsnapshot_member_id"])
@ -732,7 +732,7 @@ class ShareApiTest(test.TestCase):
stubs.stub_share_get_all_by_project)
env = {'QUERY_STRING': 'name=Share+Test+Name'}
req = fakes.HTTPRequest.blank('/shares/detail', environ=env,
version="1.5")
version="2.4")
res_dict = self.controller.detail(req)
expected = {
'shares': [

View File

@ -17,12 +17,16 @@ from tempest import clients
from tempest.common import cred_provider
from manila_tempest_tests.services.share.json import shares_client
from manila_tempest_tests.services.share.v2.json import shares_client \
as shares_v2_client
class Manager(clients.Manager):
def __init__(self, credentials=None, service=None):
super(Manager, self).__init__(credentials, service)
self.shares_client = shares_client.SharesClient(self.auth_provider)
self.shares_v2_client = shares_v2_client.SharesV2Client(
self.auth_provider)
class AltManager(Manager):

View File

@ -32,11 +32,11 @@ share_group = cfg.OptGroup(name="share", title="Share Service Options")
ShareGroup = [
cfg.StrOpt("min_api_microversion",
default="1.0",
default="2.0",
help="The minimum api microversion is configured to be the "
"value of the minimum microversion supported by Manila."),
cfg.StrOpt("max_api_microversion",
default="1.6",
default="2.5",
help="The maximum api microversion is configured to be the "
"value of the latest microversion supported by Manila."),
cfg.StrOpt("region",

View File

@ -26,13 +26,6 @@ from tempest_lib import exceptions
from manila_tempest_tests import share_exceptions
CONF = config.CONF
LATEST_MICRO_API = {
'X-OpenStack-Manila-API-Version': CONF.share.max_api_microversion,
}
EXPERIMENTAL = {
'X-OpenStack-Manila-API-Experimental': 'True',
'X-OpenStack-Manila-API-Version': CONF.share.max_api_microversion,
}
class SharesClient(rest_client.RestClient):
@ -53,48 +46,11 @@ class SharesClient(rest_client.RestClient):
self.share_network_id = CONF.share.share_network_id
self.build_interval = CONF.share.build_interval
self.build_timeout = CONF.share.build_timeout
self.API_MICROVERSIONS_HEADER = 'x-openstack-manila-api-version'
self.API_MICROVERSIONS_EXPERIMENTAL_HEADER = (
'x-openstack-manila-api-experimental')
def _get_version_dict(self, version):
return {self.API_MICROVERSIONS_HEADER: version}
def migrate_share(self, share_id, host):
post_body = {
'os-migrate_share': {
'host': host,
}
}
body = json.dumps(post_body)
headers = self._get_version_dict('1.6')
headers[self.API_MICROVERSIONS_EXPERIMENTAL_HEADER] = 'true'
return self.post('shares/%s/action' % share_id, body,
headers=headers, extra_headers=True)
def send_microversion_request(self, version=None):
"""Prepare and send the HTTP GET Request to the base URL.
Extracts the base URL from the shares_client endpoint and makes a GET
request with the microversions request header.
"""
headers = self.get_headers()
url, headers, body = self.auth_provider.auth_request(
'GET', 'shares', headers, None, self.filters)
url = '/'.join(url.split('/')[:3]) + '/'
if version:
headers[self.API_MICROVERSIONS_HEADER] = version
resp, resp_body = self.raw_request(url, 'GET', headers=headers)
self.response_checker('GET', resp, resp_body)
resp_body = json.loads(resp_body)
return resp, resp_body
def create_share(self, share_protocol=None, size=1,
name=None, snapshot_id=None, description=None,
metadata=None, share_network_id=None,
share_type_id=None, is_public=False,
consistency_group_id=None):
share_type_id=None, is_public=False):
metadata = metadata or {}
if name is None:
name = data_utils.rand_name("tempest-created-share")
@ -119,18 +75,13 @@ class SharesClient(rest_client.RestClient):
post_body["share"]["share_network_id"] = share_network_id
if share_type_id:
post_body["share"]["share_type"] = share_type_id
if consistency_group_id:
post_body["share"]["consistency_group_id"] = consistency_group_id
body = json.dumps(post_body)
resp, body = self.post("shares", body, headers=LATEST_MICRO_API,
extra_headers=True)
resp, body = self.post("shares", body)
self.expected_success(200, resp.status)
return self._parse_resp(body)
def delete_share(self, share_id, params=None):
uri = "shares/%s" % share_id
uri += '?%s' % (urllib.urlencode(params) if params else '')
resp, body = self.delete(uri)
def delete_share(self, share_id):
resp, body = self.delete("shares/%s" % share_id)
self.expected_success(202, resp.status)
return body
@ -161,8 +112,7 @@ class SharesClient(rest_client.RestClient):
"""Get list of shares w/o filters."""
uri = 'shares/detail' if detailed else 'shares'
uri += '?%s' % urllib.urlencode(params) if params else ''
resp, body = self.get(uri, headers=LATEST_MICRO_API,
extra_headers=True)
resp, body = self.get(uri)
self.expected_success(200, resp.status)
return self._parse_resp(body)
@ -171,29 +121,7 @@ class SharesClient(rest_client.RestClient):
return self.list_shares(detailed=True, params=params)
def get_share(self, share_id):
resp, body = self.get("shares/%s" % share_id, headers=LATEST_MICRO_API,
extra_headers=True)
self.expected_success(200, resp.status)
return self._parse_resp(body)
def get_instances_of_share(self, share_id):
resp, body = self.get("shares/%s/instances" % share_id,
headers=self._get_version_dict('1.4'),
extra_headers=True)
self.expected_success(200, resp.status)
return self._parse_resp(body)
def list_share_instances(self):
resp, body = self.get("share_instances",
headers=self._get_version_dict('1.4'),
extra_headers=True)
self.expected_success(200, resp.status)
return self._parse_resp(body)
def get_share_instance(self, instance_id):
resp, body = self.get("share_instances/%s" % instance_id,
headers=self._get_version_dict('1.4'),
extra_headers=True)
resp, body = self.get("shares/%s" % share_id)
self.expected_success(200, resp.status)
return self._parse_resp(body)
@ -292,30 +220,6 @@ class SharesClient(rest_client.RestClient):
self.expected_success(202, resp.status)
return body
def wait_for_migration_completed(self, share_id, dest_host):
"""Waits for a share to migrate to a certain host."""
share = self.get_share(share_id)
migration_timeout = CONF.share.migration_timeout
start = int(time.time())
while share['task_state'] != 'migration_success':
time.sleep(self.build_interval)
share = self.get_share(share_id)
if share['task_state'] == 'migration_success':
return share
elif share['task_state'] == 'migration_error':
raise share_exceptions.ShareMigrationException(
share_id=share['id'], src=share['host'], dest=dest_host)
elif int(time.time()) - start >= migration_timeout:
message = ('Share %(share_id)s failed to migrate from '
'host %(src)s to host %(dest)s within the required '
'time %(timeout)s.' % {
'src': share['host'],
'dest': dest_host,
'share_id': share['id'],
'timeout': self.build_timeout
})
raise exceptions.TimeoutException(message)
def wait_for_share_status(self, share_id, status):
"""Waits for a share to reach a given status."""
body = self.get_share(share_id)
@ -339,28 +243,6 @@ class SharesClient(rest_client.RestClient):
(share_name, status, self.build_timeout))
raise exceptions.TimeoutException(message)
def wait_for_share_instance_status(self, instance_id, status):
"""Waits for a share to reach a given status."""
body = self.get_share_instance(instance_id)
instance_status = body['status']
start = int(time.time())
while instance_status != status:
time.sleep(self.build_interval)
body = self.get_share(instance_id)
instance_status = body['status']
if instance_status == status:
return
elif 'error' in instance_status.lower():
raise share_exceptions.\
ShareInstanceBuildErrorException(id=instance_id)
if int(time.time()) - start >= self.build_timeout:
message = ('Share instance %s failed to reach %s status within'
' the required time (%s s).' %
(instance_id, status, self.build_timeout))
raise exceptions.TimeoutException(message)
def wait_for_snapshot_status(self, snapshot_id, status):
"""Waits for a snapshot to reach a given status."""
body = self.get_snapshot(snapshot_id)
@ -382,49 +264,6 @@ class SharesClient(rest_client.RestClient):
(snapshot_name, status, self.build_timeout))
raise exceptions.TimeoutException(message)
def wait_for_consistency_group_status(self, consistency_group_id, status):
"""Waits for a consistency group to reach a given status."""
body = self.get_consistency_group(consistency_group_id)
consistency_group_name = body['name']
consistency_group_status = body['status']
start = int(time.time())
while consistency_group_status != status:
time.sleep(self.build_interval)
body = self.get_consistency_group(consistency_group_id)
consistency_group_status = body['status']
if 'error' in consistency_group_status and status != 'error':
raise share_exceptions.ConsistencyGroupBuildErrorException(
consistency_group_id=consistency_group_id)
if int(time.time()) - start >= self.build_timeout:
message = ('Consistency Group %s failed to reach %s status '
'within the required time (%s s).' %
(consistency_group_name, status,
self.build_timeout))
raise exceptions.TimeoutException(message)
def wait_for_cgsnapshot_status(self, cgsnapshot_id, status):
"""Waits for a cgsnapshot to reach a given status."""
body = self.get_cgsnapshot(cgsnapshot_id)
cgsnapshot_name = body['name']
cgsnapshot_status = body['status']
start = int(time.time())
while cgsnapshot_status != status:
time.sleep(self.build_interval)
body = self.get_cgsnapshot(cgsnapshot_id)
cgsnapshot_status = body['status']
if 'error' in cgsnapshot_status and status != 'error':
raise share_exceptions.CGSnapshotBuildErrorException(
cgsnapshot_id=cgsnapshot_id)
if int(time.time()) - start >= self.build_timeout:
message = ('CGSnapshot %s failed to reach %s status '
'within the required time (%s s).' %
(cgsnapshot_name, status, self.build_timeout))
raise exceptions.TimeoutException(message)
def wait_for_access_rule_status(self, share_id, rule_id, status):
"""Waits for an access rule to reach a given status."""
rule_status = "new"
@ -502,8 +341,7 @@ class SharesClient(rest_client.RestClient):
"""Verifies whether provided resource deleted or not.
:param kwargs: dict with expected keys 'share_id', 'snapshot_id',
:param kwargs: 'sn_id', 'ss_id', 'vt_id', 'server_id', 'cg_id',
:param kwargs: and 'cgsnapshot_id'
:param kwargs: 'sn_id', 'ss_id', 'vt_id' and 'server_id'
:raises share_exceptions.InvalidResource
"""
if "share_id" in kwargs:
@ -518,9 +356,6 @@ class SharesClient(rest_client.RestClient):
else:
return self._is_resource_deleted(
self.get_share, kwargs.get("share_id"))
elif "share_instance_id" in kwargs:
return self._is_resource_deleted(
self.get_share_instance, kwargs.get("share_instance_id"))
elif "snapshot_id" in kwargs:
return self._is_resource_deleted(
self.get_snapshot, kwargs.get("snapshot_id"))
@ -539,12 +374,6 @@ class SharesClient(rest_client.RestClient):
elif "server_id" in kwargs:
return self._is_resource_deleted(
self.show_share_server, kwargs.get("server_id"))
elif "cg_id" in kwargs:
return self._is_resource_deleted(
self.get_consistency_group, kwargs.get("cg_id"))
elif "cgsnapshot_id" in kwargs:
return self._is_resource_deleted(
self.get_cgsnapshot, kwargs.get("cgsnapshot_id"))
else:
raise share_exceptions.InvalidResource(
message=six.text_type(kwargs))
@ -598,29 +427,26 @@ class SharesClient(rest_client.RestClient):
self.expected_success(200, resp.status)
return self._parse_resp(body)
def reset_state(self, s_id, status="error", s_type="shares",
headers=None):
"""Resets the state of a share, snapshot, cg, or a cgsnapshot.
def reset_state(self, s_id, status="error", s_type="shares"):
"""Resets the state of a share or a snapshot.
status: available, error, creating, deleting, error_deleting
s_type: shares, snapshots, consistency-groups, cgsnapshots
s_type: shares, snapshots
"""
body = {"os-reset_status": {"status": status}}
body = json.dumps(body)
resp, body = self.post("%s/%s/action" % (s_type, s_id), body,
headers=headers, extra_headers=True)
resp, body = self.post("%s/%s/action" % (s_type, s_id), body)
self.expected_success(202, resp.status)
return body
def force_delete(self, s_id, s_type="shares", headers=None):
def force_delete(self, s_id, s_type="shares"):
"""Force delete share or snapshot.
s_type: shares, snapshots
"""
body = {"os-force_delete": None}
body = json.dumps(body)
resp, body = self.post("%s/%s/action" % (s_type, s_id), body,
headers=headers, extra_headers=True)
resp, body = self.post("%s/%s/action" % (s_type, s_id), body)
self.expected_success(202, resp.status)
return body
@ -925,143 +751,3 @@ class SharesClient(rest_client.RestClient):
resp, body = self.get(uri)
self.expected_success(200, resp.status)
return json.loads(body)
###############
def create_consistency_group(self, name=None, description=None,
share_type_ids=(), share_network_id=None,
source_cgsnapshot_id=None):
"""Create a new consistency group."""
uri = 'consistency-groups'
post_body = {}
if name:
post_body['name'] = name
if description:
post_body['description'] = description
if share_type_ids:
post_body['share_types'] = share_type_ids
if source_cgsnapshot_id:
post_body['source_cgsnapshot_id'] = source_cgsnapshot_id
if share_network_id:
post_body['share_network_id'] = share_network_id
body = json.dumps({'consistency_group': post_body})
resp, body = self.post(uri, body, headers=EXPERIMENTAL,
extra_headers=True)
self.expected_success(202, resp.status)
return self._parse_resp(body)
def delete_consistency_group(self, consistency_group_id):
"""Delete a consistency group."""
uri = 'consistency-groups/%s' % consistency_group_id
resp, body = self.delete(uri, headers=EXPERIMENTAL,
extra_headers=True)
self.expected_success(202, resp.status)
return body
def list_consistency_groups(self, detailed=False, params=None):
"""Get list of consistency groups w/o filters."""
uri = 'consistency-groups%s' % ('/detail' if detailed else '')
uri += '?%s' % (urllib.urlencode(params) if params else '')
resp, body = self.get(uri, headers=EXPERIMENTAL, extra_headers=True)
self.expected_success(200, resp.status)
return self._parse_resp(body)
def get_consistency_group(self, consistency_group_id):
"""Get consistency group info."""
uri = 'consistency-groups/%s' % consistency_group_id
resp, body = self.get(uri, headers=EXPERIMENTAL, extra_headers=True)
self.expected_success(200, resp.status)
return self._parse_resp(body)
def update_consistency_group(self, consistency_group_id, name=None,
description=None, **kwargs):
"""Update an existing consistency group."""
uri = 'consistency-groups/%s' % consistency_group_id
post_body = {}
if name:
post_body['name'] = name
if description:
post_body['description'] = description
if kwargs:
post_body.update(kwargs)
body = json.dumps({'consistency_group': post_body})
resp, body = self.put(uri, body, headers=EXPERIMENTAL,
extra_headers=True)
self.expected_success(200, resp.status)
return self._parse_resp(body)
def consistency_group_reset_state(self, id, status):
self.reset_state(id, status=status,
s_type='consistency-groups', headers=EXPERIMENTAL)
def consistency_group_force_delete(self, id, status):
self.force_delete(id, status=status,
s_type='consistency-groups', headers=EXPERIMENTAL)
###############
def create_cgsnapshot(self, consistency_group_id,
name=None, description=None):
"""Create a new cgsnapshot of an existing consistency group."""
uri = 'cgsnapshots'
post_body = {'consistency_group_id': consistency_group_id}
if name:
post_body['name'] = name
if description:
post_body['description'] = description
body = json.dumps({'cgsnapshot': post_body})
resp, body = self.post(uri, body, headers=EXPERIMENTAL,
extra_headers=True)
self.expected_success(202, resp.status)
return self._parse_resp(body)
def delete_cgsnapshot(self, cgsnapshot_id):
"""Delete an existing cgsnapshot."""
uri = 'cgsnapshots/%s' % cgsnapshot_id
resp, body = self.delete(uri, headers=EXPERIMENTAL, extra_headers=True)
self.expected_success(202, resp.status)
return body
def list_cgsnapshots(self, detailed=False, params=None):
"""Get list of cgsnapshots w/o filters."""
uri = 'cgsnapshots/detail' if detailed else 'cgsnapshots'
uri += '?%s' % (urllib.urlencode(params) if params else '')
resp, body = self.get(uri, headers=EXPERIMENTAL, extra_headers=True)
self.expected_success(200, resp.status)
return self._parse_resp(body)
def list_cgsnapshot_members(self, cgsnapshot_id):
"""Get list of members of a cgsnapshots."""
uri = 'cgsnapshots/%s/members' % cgsnapshot_id
resp, body = self.get(uri, headers=EXPERIMENTAL, extra_headers=True)
self.expected_success(200, resp.status)
return self._parse_resp(body)
def get_cgsnapshot(self, cgsnapshot_id):
"""Get cgsnapshot info."""
uri = 'cgsnapshots/%s' % cgsnapshot_id
resp, body = self.get(uri, headers=EXPERIMENTAL, extra_headers=True)
self.expected_success(200, resp.status)
return self._parse_resp(body)
def update_cgsnapshot(self, cgsnapshot_id, name=None, description=None):
"""Update an existing cgsnapshot."""
uri = 'cgsnapshots/%s' % cgsnapshot_id
post_body = {}
if name:
post_body['name'] = name
if description:
post_body['description'] = description
body = json.dumps({'cgsnapshot': post_body})
resp, body = self.put(uri, body, headers=EXPERIMENTAL,
extra_headers=True)
self.expected_success(200, resp.status)
return self._parse_resp(body)
def cgsnapshot_reset_state(self, id, status):
self.reset_state(id, status=status,
s_type='cgsnapshots', headers=EXPERIMENTAL)
def cgsnapshot_force_delete(self, id, status):
self.force_delete(id, status=status,
s_type='cgsnapshots', headers=EXPERIMENTAL)

View File

@ -0,0 +1,516 @@
# Copyright 2015 Andrew Kerr
# 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.
import json
import time
import urllib
from tempest_lib.common.utils import data_utils
from tempest_lib import exceptions
from manila_tempest_tests.services.share.json import shares_client # noqa
from manila_tempest_tests import share_exceptions
from tempest import config # noqa
CONF = config.CONF
LATEST_MICROVERSION = CONF.share.max_api_microversion
EXPERIMENTAL = {'X-OpenStack-Manila-API-Experimental': 'True'}
class SharesV2Client(shares_client.SharesClient):
"""Tempest REST client for Manila.
It handles shares and access to it in OpenStack.
"""
api_version = 'v2'
def __init__(self, auth_provider):
super(SharesV2Client, self).__init__(auth_provider)
self.API_MICROVERSIONS_HEADER = 'x-openstack-manila-api-version'
def inject_microversion_header(self, headers, version,
extra_headers=False):
"""Inject the required manila microversion header."""
new_headers = self.get_headers()
new_headers[self.API_MICROVERSIONS_HEADER] = version
if extra_headers and headers:
new_headers.update(headers)
elif headers:
new_headers = headers
return new_headers
# Overwrite all http verb calls to inject the micro version header
def post(self, url, body, headers=None, extra_headers=False,
version=LATEST_MICROVERSION):
headers = self.inject_microversion_header(headers, version,
extra_headers=extra_headers)
return super(SharesV2Client, self).post(url, body, headers=headers)
def get(self, url, headers=None, extra_headers=False,
version=LATEST_MICROVERSION):
headers = self.inject_microversion_header(headers, version,
extra_headers=extra_headers)
return super(SharesV2Client, self).get(url, headers=headers)
def delete(self, url, headers=None, body=None, extra_headers=False,
version=LATEST_MICROVERSION):
headers = self.inject_microversion_header(headers, version,
extra_headers=extra_headers)
return super(SharesV2Client, self).delete(url, headers=headers,
body=body)
def patch(self, url, body, headers=None, extra_headers=False,
version=LATEST_MICROVERSION):
headers = self.inject_microversion_header(headers, version,
extra_headers=extra_headers)
return super(SharesV2Client, self).patch(url, body, headers=headers)
def put(self, url, body, headers=None, extra_headers=False,
version=LATEST_MICROVERSION):
headers = self.inject_microversion_header(headers, version,
extra_headers=extra_headers)
return super(SharesV2Client, self).put(url, body, headers=headers)
def head(self, url, headers=None, extra_headers=False,
version=LATEST_MICROVERSION):
headers = self.inject_microversion_header(headers, version,
extra_headers=extra_headers)
return super(SharesV2Client, self).head(url, headers=headers)
def copy(self, url, headers=None, extra_headers=False,
version=LATEST_MICROVERSION):
headers = self.inject_microversion_header(headers, version,
extra_headers=extra_headers)
return super(SharesV2Client, self).copy(url, headers=headers)
def reset_state(self, s_id, status="error", s_type="shares",
headers=None, version=LATEST_MICROVERSION):
"""Resets the state of a share, snapshot, cg, or a cgsnapshot.
status: available, error, creating, deleting, error_deleting
s_type: shares, snapshots, consistency-groups, cgsnapshots
"""
body = {"os-reset_status": {"status": status}}
body = json.dumps(body)
resp, body = self.post("%s/%s/action" % (s_type, s_id), body,
headers=headers, extra_headers=True,
version=version)
self.expected_success(202, resp.status)
return body
def force_delete(self, s_id, s_type="shares", headers=None,
version=LATEST_MICROVERSION):
"""Force delete share or snapshot.
s_type: shares, snapshots
"""
body = {"os-force_delete": None}
body = json.dumps(body)
resp, body = self.post("%s/%s/action" % (s_type, s_id), body,
headers=headers, extra_headers=True,
version=version)
self.expected_success(202, resp.status)
return body
def send_microversion_request(self, version=None, script_name=None):
"""Prepare and send the HTTP GET Request to the base URL.
Extracts the base URL from the shares_client endpoint and makes a GET
request with the microversions request header.
:param version: The string to send for the value of the microversion
header, or None to omit the header.
:param script_name: The first part of the URL (v1 or v2), or None to
omit it.
"""
headers = self.get_headers()
url, headers, body = self.auth_provider.auth_request(
'GET', 'shares', headers, None, self.filters)
url = '/'.join(url.split('/')[:3]) + '/'
if script_name:
url += script_name + '/'
if version:
headers[self.API_MICROVERSIONS_HEADER] = version
resp, resp_body = self.raw_request(url, 'GET', headers=headers)
self.response_checker('GET', resp, resp_body)
resp_body = json.loads(resp_body)
return resp, resp_body
def is_resource_deleted(self, *args, **kwargs):
"""Verifies whether provided resource deleted or not.
:param kwargs: dict with expected keys 'share_id', 'snapshot_id',
:param kwargs: 'sn_id', 'ss_id', 'vt_id' and 'server_id'
:raises share_exceptions.InvalidResource
"""
if "share_instance_id" in kwargs:
return self._is_resource_deleted(
self.get_share_instance, kwargs.get("share_instance_id"))
elif "cg_id" in kwargs:
return self._is_resource_deleted(
self.get_consistency_group, kwargs.get("cg_id"))
elif "cgsnapshot_id" in kwargs:
return self._is_resource_deleted(
self.get_cgsnapshot, kwargs.get("cgsnapshot_id"))
else:
return super(SharesV2Client, self).is_resource_deleted(
*args, **kwargs)
###############
def create_share(self, share_protocol=None, size=1,
name=None, snapshot_id=None, description=None,
metadata=None, share_network_id=None,
share_type_id=None, is_public=False,
consistency_group_id=None, version=LATEST_MICROVERSION):
metadata = metadata or {}
if name is None:
name = data_utils.rand_name("tempest-created-share")
if description is None:
description = data_utils.rand_name("tempest-created-share-desc")
if share_protocol is None:
share_protocol = self.share_protocol
if share_protocol is None:
raise share_exceptions.ShareProtocolNotSpecified()
post_body = {
"share": {
"share_proto": share_protocol,
"description": description,
"snapshot_id": snapshot_id,
"name": name,
"size": size,
"metadata": metadata,
"is_public": is_public,
}
}
if share_network_id:
post_body["share"]["share_network_id"] = share_network_id
if share_type_id:
post_body["share"]["share_type"] = share_type_id
if consistency_group_id:
post_body["share"]["consistency_group_id"] = consistency_group_id
body = json.dumps(post_body)
resp, body = self.post("shares", body, version=version)
self.expected_success(200, resp.status)
return self._parse_resp(body)
def list_shares(self, detailed=False, params=None,
version=LATEST_MICROVERSION):
"""Get list of shares w/o filters."""
uri = 'shares/detail' if detailed else 'shares'
uri += '?%s' % urllib.urlencode(params) if params else ''
resp, body = self.get(uri, version=version)
self.expected_success(200, resp.status)
return self._parse_resp(body)
def list_shares_with_detail(self, params=None,
version=LATEST_MICROVERSION):
"""Get detailed list of shares w/o filters."""
return self.list_shares(detailed=True, params=params, version=version)
def get_share(self, share_id, version=LATEST_MICROVERSION):
resp, body = self.get("shares/%s" % share_id, version=version)
self.expected_success(200, resp.status)
return self._parse_resp(body)
def delete_share(self, share_id, params=None,
version=LATEST_MICROVERSION):
uri = "shares/%s" % share_id
uri += '?%s' % (urllib.urlencode(params) if params else '')
resp, body = self.delete(uri, version=version)
self.expected_success(202, resp.status)
return body
###############
def get_instances_of_share(self, share_id, version=LATEST_MICROVERSION):
resp, body = self.get("shares/%s/instances" % share_id,
version=version)
self.expected_success(200, resp.status)
return self._parse_resp(body)
def list_share_instances(self, version=LATEST_MICROVERSION):
resp, body = self.get("share_instances", version=version)
self.expected_success(200, resp.status)
return self._parse_resp(body)
def get_share_instance(self, instance_id, version=LATEST_MICROVERSION):
resp, body = self.get("share_instances/%s" % instance_id,
version=version)
self.expected_success(200, resp.status)
return self._parse_resp(body)
def wait_for_share_instance_status(self, instance_id, status,
version=LATEST_MICROVERSION):
"""Waits for a share to reach a given status."""
body = self.get_share_instance(instance_id, version=version)
instance_status = body['status']
start = int(time.time())
while instance_status != status:
time.sleep(self.build_interval)
body = self.get_share(instance_id)
instance_status = body['status']
if instance_status == status:
return
elif 'error' in instance_status.lower():
raise share_exceptions. \
ShareInstanceBuildErrorException(id=instance_id)
if int(time.time()) - start >= self.build_timeout:
message = ('Share instance %s failed to reach %s status within'
' the required time (%s s).' %
(instance_id, status, self.build_timeout))
raise exceptions.TimeoutException(message)
###############
def create_consistency_group(self, name=None, description=None,
share_type_ids=(), share_network_id=None,
source_cgsnapshot_id=None,
version=LATEST_MICROVERSION):
"""Create a new consistency group."""
uri = 'consistency-groups'
post_body = {}
if name:
post_body['name'] = name
if description:
post_body['description'] = description
if share_type_ids:
post_body['share_types'] = share_type_ids
if source_cgsnapshot_id:
post_body['source_cgsnapshot_id'] = source_cgsnapshot_id
if share_network_id:
post_body['share_network_id'] = share_network_id
body = json.dumps({'consistency_group': post_body})
resp, body = self.post(uri, body, headers=EXPERIMENTAL,
extra_headers=True, version=version)
self.expected_success(202, resp.status)
return self._parse_resp(body)
def delete_consistency_group(self, consistency_group_id,
version=LATEST_MICROVERSION):
"""Delete a consistency group."""
uri = 'consistency-groups/%s' % consistency_group_id
resp, body = self.delete(uri, headers=EXPERIMENTAL,
extra_headers=True, version=version)
self.expected_success(202, resp.status)
return body
def list_consistency_groups(self, detailed=False, params=None,
version=LATEST_MICROVERSION):
"""Get list of consistency groups w/o filters."""
uri = 'consistency-groups%s' % ('/detail' if detailed else '')
uri += '?%s' % (urllib.urlencode(params) if params else '')
resp, body = self.get(uri, headers=EXPERIMENTAL, extra_headers=True,
version=version)
self.expected_success(200, resp.status)
return self._parse_resp(body)
def get_consistency_group(self, consistency_group_id,
version=LATEST_MICROVERSION):
"""Get consistency group info."""
uri = 'consistency-groups/%s' % consistency_group_id
resp, body = self.get(uri, headers=EXPERIMENTAL, extra_headers=True,
version=version)
self.expected_success(200, resp.status)
return self._parse_resp(body)
def update_consistency_group(self, consistency_group_id, name=None,
description=None,
version=LATEST_MICROVERSION, **kwargs):
"""Update an existing consistency group."""
uri = 'consistency-groups/%s' % consistency_group_id
post_body = {}
if name:
post_body['name'] = name
if description:
post_body['description'] = description
if kwargs:
post_body.update(kwargs)
body = json.dumps({'consistency_group': post_body})
resp, body = self.put(uri, body, headers=EXPERIMENTAL,
extra_headers=True, version=version)
self.expected_success(200, resp.status)
return self._parse_resp(body)
def consistency_group_reset_state(self, id, status,
version=LATEST_MICROVERSION):
self.reset_state(id, status=status,
s_type='consistency-groups', headers=EXPERIMENTAL,
version=version)
def consistency_group_force_delete(self, id, version=LATEST_MICROVERSION):
self.force_delete(id, s_type='consistency-groups',
headers=EXPERIMENTAL, version=version)
def wait_for_consistency_group_status(self, consistency_group_id, status):
"""Waits for a consistency group to reach a given status."""
body = self.get_consistency_group(consistency_group_id)
consistency_group_name = body['name']
consistency_group_status = body['status']
start = int(time.time())
while consistency_group_status != status:
time.sleep(self.build_interval)
body = self.get_consistency_group(consistency_group_id)
consistency_group_status = body['status']
if 'error' in consistency_group_status and status != 'error':
raise share_exceptions.ConsistencyGroupBuildErrorException(
consistency_group_id=consistency_group_id)
if int(time.time()) - start >= self.build_timeout:
message = ('Consistency Group %s failed to reach %s status '
'within the required time (%s s).' %
(consistency_group_name, status,
self.build_timeout))
raise exceptions.TimeoutException(message)
###############
def create_cgsnapshot(self, consistency_group_id,
name=None, description=None,
version=LATEST_MICROVERSION):
"""Create a new cgsnapshot of an existing consistency group."""
uri = 'cgsnapshots'
post_body = {'consistency_group_id': consistency_group_id}
if name:
post_body['name'] = name
if description:
post_body['description'] = description
body = json.dumps({'cgsnapshot': post_body})
resp, body = self.post(uri, body, headers=EXPERIMENTAL,
extra_headers=True, version=version)
self.expected_success(202, resp.status)
return self._parse_resp(body)
def delete_cgsnapshot(self, cgsnapshot_id,
version=LATEST_MICROVERSION):
"""Delete an existing cgsnapshot."""
uri = 'cgsnapshots/%s' % cgsnapshot_id
resp, body = self.delete(uri, headers=EXPERIMENTAL,
extra_headers=True, version=version)
self.expected_success(202, resp.status)
return body
def list_cgsnapshots(self, detailed=False, params=None,
version=LATEST_MICROVERSION):
"""Get list of cgsnapshots w/o filters."""
uri = 'cgsnapshots/detail' if detailed else 'cgsnapshots'
uri += '?%s' % (urllib.urlencode(params) if params else '')
resp, body = self.get(uri, headers=EXPERIMENTAL, extra_headers=True,
version=version)
self.expected_success(200, resp.status)
return self._parse_resp(body)
def list_cgsnapshot_members(self, cgsnapshot_id,
version=LATEST_MICROVERSION):
"""Get list of members of a cgsnapshots."""
uri = 'cgsnapshots/%s/members' % cgsnapshot_id
resp, body = self.get(uri, headers=EXPERIMENTAL, extra_headers=True,
version=version)
self.expected_success(200, resp.status)
return self._parse_resp(body)
def get_cgsnapshot(self, cgsnapshot_id, version=LATEST_MICROVERSION):
"""Get cgsnapshot info."""
uri = 'cgsnapshots/%s' % cgsnapshot_id
resp, body = self.get(uri, headers=EXPERIMENTAL, extra_headers=True,
version=version)
self.expected_success(200, resp.status)
return self._parse_resp(body)
def update_cgsnapshot(self, cgsnapshot_id, name=None, description=None,
version=LATEST_MICROVERSION):
"""Update an existing cgsnapshot."""
uri = 'cgsnapshots/%s' % cgsnapshot_id
post_body = {}
if name:
post_body['name'] = name
if description:
post_body['description'] = description
body = json.dumps({'cgsnapshot': post_body})
resp, body = self.put(uri, body, headers=EXPERIMENTAL,
extra_headers=True, version=version)
self.expected_success(200, resp.status)
return self._parse_resp(body)
def cgsnapshot_reset_state(self, id, status,
version=LATEST_MICROVERSION):
self.reset_state(id, status=status,
s_type='cgsnapshots', headers=EXPERIMENTAL,
version=version)
def cgsnapshot_force_delete(self, id, version=LATEST_MICROVERSION):
self.force_delete(id, s_type='cgsnapshots', headers=EXPERIMENTAL,
version=version)
def wait_for_cgsnapshot_status(self, cgsnapshot_id, status):
"""Waits for a cgsnapshot to reach a given status."""
body = self.get_cgsnapshot(cgsnapshot_id)
cgsnapshot_name = body['name']
cgsnapshot_status = body['status']
start = int(time.time())
while cgsnapshot_status != status:
time.sleep(self.build_interval)
body = self.get_cgsnapshot(cgsnapshot_id)
cgsnapshot_status = body['status']
if 'error' in cgsnapshot_status and status != 'error':
raise share_exceptions.CGSnapshotBuildErrorException(
cgsnapshot_id=cgsnapshot_id)
if int(time.time()) - start >= self.build_timeout:
message = ('CGSnapshot %s failed to reach %s status '
'within the required time (%s s).' %
(cgsnapshot_name, status, self.build_timeout))
raise exceptions.TimeoutException(message)
###############
def migrate_share(self, share_id, host, version=LATEST_MICROVERSION):
post_body = {
'os-migrate_share': {
'host': host,
}
}
body = json.dumps(post_body)
return self.post('shares/%s/action' % share_id, body,
headers=EXPERIMENTAL, extra_headers=True,
version=version)
def wait_for_migration_completed(self, share_id, dest_host):
"""Waits for a share to migrate to a certain host."""
share = self.get_share(share_id)
migration_timeout = CONF.share.migration_timeout
start = int(time.time())
while share['task_state'] != 'migration_success':
time.sleep(self.build_interval)
share = self.get_share(share_id)
if share['task_state'] == 'migration_success':
return share
elif share['task_state'] == 'migration_error':
raise share_exceptions.ShareMigrationException(
share_id=share['id'], src=share['host'], dest=dest_host)
elif int(time.time()) - start >= migration_timeout:
message = ('Share %(share_id)s failed to migrate from '
'host %(src)s to host %(dest)s within the required '
'time %(timeout)s.' % {
'src': share['host'],
'dest': dest_host,
'share_id': share['id'],
'timeout': self.build_timeout
})
raise exceptions.TimeoutException(message)

View File

@ -32,7 +32,7 @@ class AdminActionsTest(base.BaseSharesAdminTest):
cls.bad_status = "error_deleting"
cls.sh = cls.create_share()
cls.sh_instance = (
cls.shares_client.get_instances_of_share(cls.sh["id"])[0]
cls.shares_v2_client.get_instances_of_share(cls.sh["id"])[0]
)
if CONF.share.run_snapshot_tests:
cls.sn = cls.create_snapshot_wait_for_active(cls.sh["id"])
@ -49,7 +49,7 @@ class AdminActionsTest(base.BaseSharesAdminTest):
for status in self.states:
self.shares_client.reset_state(
id, s_type="share_instances", status=status)
self.shares_client.wait_for_share_instance_status(id, status)
self.shares_v2_client.wait_for_share_instance_status(id, status)
@test.attr(type=["gate", ])
@testtools.skipUnless(CONF.share.run_snapshot_tests,
@ -78,7 +78,7 @@ class AdminActionsTest(base.BaseSharesAdminTest):
@test.attr(type=["gate", ])
def test_force_delete_share_instance(self):
share = self.create_share(cleanup_in_class=False)
instances = self.shares_client.get_instances_of_share(share["id"])
instances = self.shares_v2_client.get_instances_of_share(share["id"])
# Check that instance was created
self.assertEqual(1, len(instances))
@ -89,13 +89,13 @@ class AdminActionsTest(base.BaseSharesAdminTest):
instance["id"], s_type="share_instances", status=self.bad_status)
# Check that status was changed
check_status = self.shares_client.get_share_instance(instance["id"])
check_status = self.shares_v2_client.get_share_instance(instance["id"])
self.assertEqual(self.bad_status, check_status["status"])
# Share with status 'error_deleting' should be deleted
self.shares_client.force_delete(
instance["id"], s_type="share_instances")
self.shares_client.wait_for_resource_deletion(
self.shares_v2_client.wait_for_resource_deletion(
share_instance_id=instance["id"])
@test.attr(type=["gate", ])

View File

@ -31,11 +31,12 @@ class AdminActionsNegativeTest(base.BaseSharesAdminTest):
super(AdminActionsNegativeTest, cls).resource_setup()
cls.sh = cls.create_share()
cls.sh_instance = (
cls.shares_client.get_instances_of_share(cls.sh["id"])[0]
cls.shares_v2_client.get_instances_of_share(cls.sh["id"])[0]
)
if CONF.share.run_snapshot_tests:
cls.sn = cls.create_snapshot_wait_for_active(cls.sh["id"])
cls.member_shares_client = clients.Manager().shares_client
cls.member_shares_v2_client = clients.Manager().shares_v2_client
@test.attr(type=["gate", "negative", ])
def test_reset_nonexistent_share_state(self):
@ -149,19 +150,19 @@ class AdminActionsNegativeTest(base.BaseSharesAdminTest):
def test_try_get_share_instance_with_member(self):
# If a non-admin tries to get instance, it should be unauthorized
self.assertRaises(lib_exc.Forbidden,
self.member_shares_client.get_share_instance,
self.member_shares_v2_client.get_share_instance,
self.sh_instance["id"])
@test.attr(type=["gate", "negative", ])
def test_try_list_share_instance_with_member(self):
# If a non-admin tries to list instances, it should be unauthorized
self.assertRaises(lib_exc.Forbidden,
self.member_shares_client.list_share_instances)
self.member_shares_v2_client.list_share_instances)
@test.attr(type=["gate", "negative", ])
def test_try_get_instances_of_share_with_member(self):
# If a non-admin tries to list instances of given share, it should be
# unauthorized
self.assertRaises(lib_exc.Forbidden,
self.member_shares_client.get_instances_of_share,
self.member_shares_v2_client.get_instances_of_share,
self.sh['id'])

View File

@ -45,13 +45,19 @@ class ConsistencyGroupActionsTest(base.BaseSharesAdminTest):
share_type_ids=[cls.share_type['id'], cls.share_type2['id']])
@test.attr(type=["gate", ])
def test_create_cg_from_cgsnapshot_with_multiple_share_types(self):
def test_create_cg_from_cgsnapshot_with_multiple_share_types_v2_4(self):
# Create cgsnapshot
cgsnapshot = self.create_cgsnapshot_wait_for_active(
self.consistency_group["id"], cleanup_in_class=False)
self.consistency_group["id"],
cleanup_in_class=False,
version='2.4',
)
new_consistency_group = self.create_consistency_group(
cleanup_in_class=False, source_cgsnapshot_id=cgsnapshot['id'])
cleanup_in_class=False,
source_cgsnapshot_id=cgsnapshot['id'],
version='2.4',
)
# Verify share_types are the same
expected_types = sorted(self.consistency_group['share_types'])
@ -61,7 +67,7 @@ class ConsistencyGroupActionsTest(base.BaseSharesAdminTest):
expected_types, actual_types))
@test.attr(type=["gate", ])
def test_create_cg_from_multi_typed_populated_cgsnapshot(self):
def test_create_cg_from_multi_typed_populated_cgsnapshot_v2_4(self):
share_name = data_utils.rand_name("tempest-share-name")
share_desc = data_utils.rand_name("tempest-share-description")
share_size = 1
@ -71,7 +77,9 @@ class ConsistencyGroupActionsTest(base.BaseSharesAdminTest):
description=share_desc,
size=share_size,
consistency_group_id=self.consistency_group['id'],
share_type_id=self.share_type['id']
share_type_id=self.share_type['id'],
client=self.shares_v2_client,
version='2.4',
)
share_name2 = data_utils.rand_name("tempest-share-name")
@ -83,11 +91,16 @@ class ConsistencyGroupActionsTest(base.BaseSharesAdminTest):
description=share_desc2,
size=share_size2,
consistency_group_id=self.consistency_group['id'],
share_type_id=self.share_type2['id']
share_type_id=self.share_type2['id'],
client=self.shares_v2_client,
version='2.4',
)
cg_shares = self.shares_client.list_shares(detailed=True, params={
'consistency_group_id': self.consistency_group['id']})
cg_shares = self.shares_v2_client.list_shares(
detailed=True,
params={'consistency_group_id': self.consistency_group['id']},
version='2.4',
)
cg_share_ids = [s['id'] for s in cg_shares]
for share_id in [share['id'], share2['id']]:
@ -101,10 +114,13 @@ class ConsistencyGroupActionsTest(base.BaseSharesAdminTest):
self.consistency_group["id"],
name=cgsnap_name,
description=cgsnap_desc,
cleanup_in_class=False)
cleanup_in_class=False,
version='2.4',
)
self.create_consistency_group(
cleanup_in_class=False, source_cgsnapshot_id=cgsnapshot['id'])
self.create_consistency_group(cleanup_in_class=False,
source_cgsnapshot_id=cgsnapshot['id'],
version='2.4')
# TODO(akerr): Skip until bug 1483886 is resolved
# Verify that the new shares correspond to correct share types

View File

@ -43,11 +43,13 @@ class ConsistencyGroupsTest(base.BaseSharesAdminTest):
cls.share_type2 = share_type['share_type']
@test.attr(type=["gate", ])
def test_create_cg_with_multiple_share_types(self):
def test_create_cg_with_multiple_share_types_v2_4(self):
# Create a consistency group
consistency_group = self.create_consistency_group(
cleanup_in_class=False, share_type_ids=[self.share_type['id'],
self.share_type2['id']])
cleanup_in_class=False,
share_type_ids=[self.share_type['id'], self.share_type2['id']],
version='2.4',
)
self.assertTrue(CG_REQUIRED_ELEMENTS.issubset(
consistency_group.keys()),

View File

@ -51,6 +51,7 @@ class ConsistencyGroupsNegativeTest(base.BaseSharesAdminTest):
size=cls.share_size,
consistency_group_id=cls.consistency_group['id'],
share_type_id=cls.share_type['id'],
client=cls.shares_v2_client,
)
# Create a cgsnapshot of the consistency group
@ -69,91 +70,105 @@ class ConsistencyGroupsNegativeTest(base.BaseSharesAdminTest):
self.share_type['id'])
@test.attr(type=["negative", "gate", ])
def test_create_share_of_unsupported_type_in_cg(self):
def test_create_share_of_unsupported_type_in_cg_v2_4(self):
# Attempt to create share of default type in the cg
self.assertRaises(exceptions.BadRequest,
self.shares_client.create_share, size=1,
consistency_group_id=self.consistency_group['id'])
self.create_share,
size=1,
consistency_group_id=self.consistency_group['id'],
client=self.shares_v2_client,
version='2.4')
@test.attr(type=["negative", "gate", ])
def test_create_share_in_cg_that_is_not_available(self):
def test_create_share_in_cg_that_is_not_available_v2_4(self):
consistency_group = self.create_consistency_group(
cleanup_in_class=False)
self.addCleanup(self.shares_client.consistency_group_reset_state,
cleanup_in_class=False, version='2.4')
self.addCleanup(self.shares_v2_client.consistency_group_reset_state,
consistency_group['id'],
status='available')
status='available',
version='2.4')
# creating
self.shares_client.consistency_group_reset_state(
consistency_group['id'], status='creating')
self.shares_client.wait_for_consistency_group_status(
self.shares_v2_client.consistency_group_reset_state(
consistency_group['id'], status='creating', version='2.4')
self.shares_v2_client.wait_for_consistency_group_status(
consistency_group['id'], 'creating')
self.assertRaises(exceptions.BadRequest, self.create_share,
name=self.share_name,
description=self.share_desc,
size=self.share_size,
consistency_group_id=consistency_group['id'],
cleanup_in_class=False)
cleanup_in_class=False,
client=self.shares_v2_client,
version='2.4')
# deleting
self.shares_client.consistency_group_reset_state(
consistency_group['id'], status='deleting')
self.shares_client.wait_for_consistency_group_status(
self.shares_v2_client.consistency_group_reset_state(
consistency_group['id'], status='deleting', version='2.4')
self.shares_v2_client.wait_for_consistency_group_status(
consistency_group['id'], 'deleting')
self.assertRaises(exceptions.BadRequest, self.create_share,
name=self.share_name,
description=self.share_desc,
size=self.share_size,
consistency_group_id=consistency_group['id'],
cleanup_in_class=False)
cleanup_in_class=False,
client=self.shares_v2_client,
version='2.4')
# error
self.shares_client.consistency_group_reset_state(
consistency_group['id'], status='error')
self.shares_client.wait_for_consistency_group_status(
self.shares_v2_client.consistency_group_reset_state(
consistency_group['id'], status='error', version='2.4')
self.shares_v2_client.wait_for_consistency_group_status(
consistency_group['id'], 'error')
self.assertRaises(exceptions.BadRequest, self.create_share,
name=self.share_name,
description=self.share_desc,
size=self.share_size,
consistency_group_id=consistency_group['id'],
cleanup_in_class=False)
cleanup_in_class=False,
client=self.shares_v2_client,
version='2.4')
@test.attr(type=["negative", "gate", ])
def test_create_cgsnapshot_of_cg_that_is_not_available(self):
def test_create_cgsnapshot_of_cg_that_is_not_available_v2_4(self):
consistency_group = self.create_consistency_group(
cleanup_in_class=False)
self.addCleanup(self.shares_client.consistency_group_reset_state,
cleanup_in_class=False, version='2.4')
self.addCleanup(self.shares_v2_client.consistency_group_reset_state,
consistency_group['id'],
status='available')
status='available',
version='2.4')
# creating
self.shares_client.consistency_group_reset_state(
consistency_group['id'], status='creating')
self.shares_client.wait_for_consistency_group_status(
self.shares_v2_client.consistency_group_reset_state(
consistency_group['id'], status='creating', version='2.4')
self.shares_v2_client.wait_for_consistency_group_status(
consistency_group['id'], 'creating')
self.assertRaises(exceptions.Conflict,
self.create_cgsnapshot_wait_for_active,
consistency_group['id'],
cleanup_in_class=False)
cleanup_in_class=False,
version='2.4')
# deleting
self.shares_client.consistency_group_reset_state(
consistency_group['id'], status='deleting')
self.shares_client.wait_for_consistency_group_status(
self.shares_v2_client.consistency_group_reset_state(
consistency_group['id'], status='deleting', version='2.4')
self.shares_v2_client.wait_for_consistency_group_status(
consistency_group['id'], 'deleting')
self.assertRaises(exceptions.Conflict,
self.create_cgsnapshot_wait_for_active,
consistency_group['id'],
cleanup_in_class=False)
cleanup_in_class=False,
version='2.4')
# error
self.shares_client.consistency_group_reset_state(
consistency_group['id'], status='error')
self.shares_client.wait_for_consistency_group_status(
self.shares_v2_client.consistency_group_reset_state(
consistency_group['id'], status='error', version='2.4')
self.shares_v2_client.wait_for_consistency_group_status(
consistency_group['id'], 'error')
self.assertRaises(exceptions.Conflict,
self.create_cgsnapshot_wait_for_active,
consistency_group['id'],
cleanup_in_class=False)
cleanup_in_class=False,
version='2.4')
@test.attr(type=["negative", "gate", ])
def test_create_cgsnapshot_of_cg_with_share_in_error_state(self):
consistency_group = self.create_consistency_group()
def test_create_cgsnapshot_of_cg_with_share_in_error_state_v2_4(self):
consistency_group = self.create_consistency_group(version='2.4')
share_name = data_utils.rand_name("tempest-share-name")
share_desc = data_utils.rand_name("tempest-share-description")
share_size = 1
@ -163,65 +178,79 @@ class ConsistencyGroupsNegativeTest(base.BaseSharesAdminTest):
size=share_size,
consistency_group_id=consistency_group['id'],
cleanup_in_class=False,
client=self.shares_v2_client,
version='2.4',
)
self.shares_client.reset_state(s_id=share['id'])
self.shares_client.wait_for_share_status(share['id'], 'error')
self.assertRaises(exceptions.Conflict,
self.create_cgsnapshot_wait_for_active,
consistency_group['id'],
cleanup_in_class=False)
cleanup_in_class=False,
version='2.4')
@test.attr(type=["negative", "gate", ])
def test_delete_cgsnapshot_not_in_available_or_error(self):
def test_delete_cgsnapshot_not_in_available_or_error_v2_4(self):
cgsnapshot = self.create_cgsnapshot_wait_for_active(
self.consistency_group['id'], cleanup_in_class=False)
self.addCleanup(self.shares_client.cgsnapshot_reset_state,
self.consistency_group['id'],
cleanup_in_class=False,
version='2.4',
)
self.addCleanup(self.shares_v2_client.cgsnapshot_reset_state,
cgsnapshot['id'],
status='available')
status='available',
version='2.4')
# creating
self.shares_client.cgsnapshot_reset_state(cgsnapshot['id'],
status='creating')
self.shares_client.wait_for_cgsnapshot_status(cgsnapshot['id'],
'creating')
self.shares_v2_client.cgsnapshot_reset_state(cgsnapshot['id'],
status='creating',
version='2.4')
self.shares_v2_client.wait_for_cgsnapshot_status(cgsnapshot['id'],
'creating')
self.assertRaises(exceptions.Conflict,
self.shares_client.delete_cgsnapshot,
cgsnapshot['id'])
self.shares_v2_client.delete_cgsnapshot,
cgsnapshot['id'],
version='2.4')
# deleting
self.shares_client.cgsnapshot_reset_state(cgsnapshot['id'],
status='deleting')
self.shares_client.wait_for_cgsnapshot_status(cgsnapshot['id'],
'deleting')
self.shares_v2_client.cgsnapshot_reset_state(cgsnapshot['id'],
status='deleting',
version='2.4')
self.shares_v2_client.wait_for_cgsnapshot_status(cgsnapshot['id'],
'deleting')
self.assertRaises(exceptions.Conflict,
self.shares_client.delete_cgsnapshot,
cgsnapshot['id'])
self.shares_v2_client.delete_cgsnapshot,
cgsnapshot['id'],
version='2.4')
@test.attr(type=["negative", "gate", ])
def test_delete_cg_not_in_available_or_error(self):
def test_delete_cg_not_in_available_or_error_v2_4(self):
consistency_group = self.create_consistency_group(
cleanup_in_class=False)
self.addCleanup(self.shares_client.consistency_group_reset_state,
cleanup_in_class=False, version='2.4')
self.addCleanup(self.shares_v2_client.consistency_group_reset_state,
consistency_group['id'],
status='available')
status='available',
version='2.4')
# creating
self.shares_client.consistency_group_reset_state(
consistency_group['id'], status='creating')
self.shares_client.wait_for_consistency_group_status(
self.shares_v2_client.consistency_group_reset_state(
consistency_group['id'], status='creating', version='2.4')
self.shares_v2_client.wait_for_consistency_group_status(
consistency_group['id'], 'creating')
self.assertRaises(exceptions.Conflict,
self.shares_client.delete_consistency_group,
consistency_group['id'])
self.shares_v2_client.delete_consistency_group,
consistency_group['id'],
version='2.4')
# deleting
self.shares_client.consistency_group_reset_state(
consistency_group['id'], status='deleting')
self.shares_client.wait_for_consistency_group_status(
self.shares_v2_client.consistency_group_reset_state(
consistency_group['id'], status='deleting', version='2.4')
self.shares_v2_client.wait_for_consistency_group_status(
consistency_group['id'], 'deleting')
self.assertRaises(exceptions.Conflict,
self.shares_client.delete_consistency_group,
consistency_group['id'])
self.shares_v2_client.delete_consistency_group,
consistency_group['id'],
version='2.4')
@test.attr(type=["negative", "gate", ])
def test_create_cg_with_conflicting_share_types(self):
def test_create_cg_with_conflicting_share_types_v2_4(self):
# Create conflicting share types
name = data_utils.rand_name("tempest-manila")
extra_specs = {"driver_handles_share_servers": False}
@ -237,10 +266,12 @@ class ConsistencyGroupsNegativeTest(base.BaseSharesAdminTest):
self.create_consistency_group,
share_type_ids=[single_tenant_share_type['id'],
multi_tenant_share_type['id']],
cleanup_in_class=False)
cleanup_in_class=False,
version='2.4')
@test.attr(type=["negative", "gate", ])
def test_create_cg_with_multi_tenant_share_type_and_no_share_network(self):
def test_create_cg_with_multi_tenant_share_type_and_no_share_network_v2_4(
self):
# Create multi tenant share type
name = data_utils.rand_name("tempest-manila")
extra_specs = {"driver_handles_share_servers": True}
@ -248,12 +279,15 @@ class ConsistencyGroupsNegativeTest(base.BaseSharesAdminTest):
multi_tenant_share_type = share_type['share_type']
def create_cg():
cg = self.shares_client.create_consistency_group(
share_type_ids=[multi_tenant_share_type['id']])
cg = self.shares_v2_client.create_consistency_group(
share_type_ids=[multi_tenant_share_type['id']],
version='2.4'
)
resource = {
"type": "consistency_group",
"id": cg["id"],
"client": self.shares_client}
"client": self.shares_client
}
self.method_resources.insert(0, resource)
return cg
@ -262,9 +296,10 @@ class ConsistencyGroupsNegativeTest(base.BaseSharesAdminTest):
@test.attr(type=["negative", "gate", ])
def test_update_cg_share_types(self):
consistency_group = self.create_consistency_group(
cleanup_in_class=False)
cleanup_in_class=False, version='2.4')
self.assertRaises(exceptions.BadRequest,
self.shares_client.update_consistency_group,
self.shares_v2_client.update_consistency_group,
consistency_group['id'],
share_types=[self.share_type['id']])
share_types=[self.share_type['id']],
version='2.4')

View File

@ -37,7 +37,7 @@ class MigrationTest(base.BaseSharesAdminTest):
raise cls.skipException(message)
@test.attr(type=["gate", "smoke", ])
def test_migration_empty(self):
def test_migration_empty_v2_5(self):
if not CONF.share.migration_enabled:
raise self.skipException("Migration tests disabled. Skipping.")
@ -60,7 +60,7 @@ class MigrationTest(base.BaseSharesAdminTest):
old_export_location = share['export_locations'][0]
share = self.migrate_share(share['id'], dest_pool)
share = self.migrate_share(share['id'], dest_pool, version='2.5')
self.assertEqual(dest_pool, share['host'])
self.assertNotEqual(old_export_location, share['export_locations'][0])

View File

@ -0,0 +1,78 @@
# Copyright 2015 Andrew Kerr
# 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 import config
from tempest import test
from manila_tempest_tests.tests.api import base
CONF = config.CONF
class ShareInstancesTest(base.BaseSharesAdminTest):
@classmethod
def resource_setup(cls):
super(ShareInstancesTest, cls).resource_setup()
cls.share = cls.create_share()
@test.attr(type=["gate", ])
def test_get_instances_of_share_v2_3(self):
"""Test that we get only the 1 share instance back for the share."""
share_instances = self.shares_v2_client.get_instances_of_share(
self.share['id'], version='2.3'
)
self.assertEqual(1, len(share_instances),
'Too many share instances found; expected 1, '
'found %s' % len(share_instances))
si = share_instances[0]
self.assertEqual(self.share['id'], si['share_id'],
'Share instance %s has incorrect share id value; '
'expected %s, got %s.' % (si['id'],
self.share['id'],
si['share_id']))
@test.attr(type=["gate", ])
def test_list_share_instances_v2_3(self):
"""Test that we get at least the share instance back for the share."""
share_instances = self.shares_v2_client.get_instances_of_share(
self.share['id'], version='2.3'
)
share_ids = [si['share_id'] for si in share_instances]
msg = 'Share instance for share %s was not found.' % self.share['id']
self.assertIn(self.share['id'], share_ids, msg)
@test.attr(type=["gate", ])
def test_get_share_instance_v2_3(self):
"""Test that we get the proper keys back for the instance."""
share_instances = self.shares_v2_client.get_instances_of_share(
self.share['id'], version='2.3'
)
si = self.shares_v2_client.get_share_instance(share_instances[0]['id'],
version='2.3')
expected_keys = ['host', 'share_id', 'id', 'share_network_id',
'status', 'availability_zone', 'share_server_id',
'export_locations', 'export_location', 'created_at']
actual_keys = si.keys()
self.assertEqual(sorted(expected_keys), sorted(actual_keys),
'Share instance %s returned incorrect keys; '
'expected %s, got %s.' % (si['id'],
sorted(expected_keys),
sorted(actual_keys)))

View File

@ -98,7 +98,8 @@ class BaseSharesTest(test.BaseTestCase):
def get_client_with_isolated_creds(cls,
name=None,
type_of_creds="admin",
cleanup_in_class=False):
cleanup_in_class=False,
client_version='1'):
"""Creates isolated creds.
:param name: name, will be used for naming ic and related stuff
@ -126,7 +127,10 @@ class BaseSharesTest(test.BaseTestCase):
# create client with isolated creds
os = clients.Manager(credentials=creds)
client = os.shares_client
if client_version == '1':
client = os.shares_client
elif client_version == '2':
client = os.shares_v2_client
# Set place where will be deleted isolated creds
ic_res = {
@ -183,7 +187,9 @@ class BaseSharesTest(test.BaseTestCase):
nc = cls.os.network_client
share_network_id = cls.provide_share_network(sc, nc)
cls.os.shares_client.share_network_id = share_network_id
cls.os.shares_v2_client.share_network_id = share_network_id
cls.shares_client = cls.os.shares_client
cls.shares_v2_client = cls.os.shares_v2_client
def setUp(self):
super(BaseSharesTest, self).setUp()
@ -281,12 +287,12 @@ class BaseSharesTest(test.BaseTestCase):
snapshot_id=None, description=None, metadata=None,
share_network_id=None, share_type_id=None,
consistency_group_id=None, client=None,
cleanup_in_class=True, is_public=False):
cleanup_in_class=True, is_public=False, **kwargs):
client = client or cls.shares_client
description = description or "Tempest's share"
share_network_id = share_network_id or client.share_network_id or None
metadata = metadata or {}
kwargs = {
kwargs.update({
'share_protocol': share_protocol,
'size': size,
'name': name,
@ -296,7 +302,7 @@ class BaseSharesTest(test.BaseTestCase):
'share_network_id': share_network_id,
'share_type_id': share_type_id,
'is_public': is_public,
}
})
if consistency_group_id:
kwargs['consistency_group_id'] = consistency_group_id
@ -309,9 +315,9 @@ class BaseSharesTest(test.BaseTestCase):
return share
@classmethod
def migrate_share(cls, share_id, dest_host, client=None):
client = client or cls.shares_client
client.migrate_share(share_id, dest_host)
def migrate_share(cls, share_id, dest_host, client=None, **kwargs):
client = client or cls.shares_v2_client
client.migrate_share(share_id, dest_host, **kwargs)
share = client.wait_for_migration_completed(share_id, dest_host)
return share
@ -383,7 +389,7 @@ class BaseSharesTest(test.BaseTestCase):
@classmethod
def create_consistency_group(cls, client=None, cleanup_in_class=True,
share_network_id=None, **kwargs):
client = client or cls.shares_client
client = client or cls.shares_v2_client
kwargs['share_network_id'] = (share_network_id or
client.share_network_id or None)
consistency_group = client.create_consistency_group(**kwargs)
@ -440,12 +446,15 @@ class BaseSharesTest(test.BaseTestCase):
@classmethod
def create_cgsnapshot_wait_for_active(cls, consistency_group_id,
name=None, description=None,
client=None, cleanup_in_class=True):
client = client or cls.shares_client
client=None, cleanup_in_class=True,
**kwargs):
client = client or cls.shares_v2_client
if description is None:
description = "Tempest's cgsnapshot"
cgsnapshot = client.create_cgsnapshot(consistency_group_id, name=name,
description=description)
cgsnapshot = client.create_cgsnapshot(consistency_group_id,
name=name,
description=description,
**kwargs)
resource = {
"type": "cgsnapshot",
"id": cgsnapshot["id"],
@ -556,11 +565,12 @@ class BaseSharesTest(test.BaseTestCase):
client = res["client"]
with handle_cleanup_exceptions():
if res["type"] is "share":
params = None
cg_id = res.get('consistency_group_id')
if cg_id:
params = {'consistency_group_id': cg_id}
client.delete_share(res_id, params=params)
client.delete_share(res_id, params=params)
else:
client.delete_share(res_id)
client.wait_for_resource_deletion(share_id=res_id)
elif res["type"] is "snapshot":
client.delete_snapshot(res_id)
@ -679,6 +689,7 @@ class BaseSharesAltTest(BaseSharesTest):
cls.os = clients.AltManager()
alt_share_network_id = CONF.share.alt_share_network_id
cls.os.shares_client.share_network_id = alt_share_network_id
cls.os.shares_v2_client.share_network_id = alt_share_network_id
super(BaseSharesAltTest, cls).resource_setup()
@ -694,4 +705,5 @@ class BaseSharesAdminTest(BaseSharesTest):
cls.os = clients.AdminManager()
admin_share_network_id = CONF.share.admin_share_network_id
cls.os.shares_client.share_network_id = admin_share_network_id
cls.os.shares_v2_client.share_network_id = admin_share_network_id
super(BaseSharesAdminTest, cls).resource_setup()

View File

@ -57,6 +57,7 @@ class ConsistencyGroupActionsTest(base.BaseSharesTest):
size=cls.share_size,
consistency_group_id=cls.consistency_group['id'],
metadata={'key': 'value'},
client=cls.shares_v2_client,
)
cls.share_name2 = data_utils.rand_name("tempest-share-name")
@ -67,6 +68,7 @@ class ConsistencyGroupActionsTest(base.BaseSharesTest):
description=cls.share_desc2,
size=cls.share_size2,
consistency_group_id=cls.consistency_group['id'],
client=cls.shares_v2_client,
)
cls.cgsnap_name = data_utils.rand_name("tempest-cgsnap-name")
@ -93,6 +95,7 @@ class ConsistencyGroupActionsTest(base.BaseSharesTest):
description=cls.share_desc3,
size=cls.share_size,
consistency_group_id=cls.consistency_group2['id'],
client=cls.shares_v2_client,
)
cls.cgsnap_name2 = data_utils.rand_name("tempest-cgsnap-name")
@ -103,11 +106,11 @@ class ConsistencyGroupActionsTest(base.BaseSharesTest):
description=cls.cgsnap_desc2)
@test.attr(type=["gate", ])
def test_get_consistency_group(self):
def test_get_consistency_group_v2_4(self):
# Get consistency group
consistency_group = self.shares_client.get_consistency_group(
self.consistency_group['id'])
consistency_group = self.shares_v2_client.get_consistency_group(
self.consistency_group['id'], version='2.4')
# Verify keys
actual_keys = set(consistency_group.keys())
@ -129,10 +132,11 @@ class ConsistencyGroupActionsTest(base.BaseSharesTest):
msg)
@test.attr(type=["gate", ])
def test_get_share(self):
def test_get_share_v2_4(self):
# Get share
share = self.shares_client.get_share(self.share['id'])
share = self.shares_v2_client.get_share(self.share['id'],
version='2.4')
# Verify keys
expected_keys = {"status", "description", "links", "availability_zone",
@ -165,10 +169,11 @@ class ConsistencyGroupActionsTest(base.BaseSharesTest):
self.consistency_group["id"], share["consistency_group_id"], msg)
@test.attr(type=["gate", ])
def test_list_consistency_groups(self):
def test_list_consistency_groups_v2_4(self):
# List consistency groups
consistency_groups = self.shares_client.list_consistency_groups()
consistency_groups = self.shares_v2_client.list_consistency_groups(
version='2.4')
# Verify keys
[self.assertEqual(CG_SIMPLE_KEYS, set(cg.keys())) for cg in
@ -184,11 +189,11 @@ class ConsistencyGroupActionsTest(base.BaseSharesTest):
self.assertEqual(1, len(gen), msg)
@test.attr(type=["gate", ])
def test_list_consistency_groups_with_detail(self):
def test_list_consistency_groups_with_detail_v2_4(self):
# List consistency groups
consistency_groups = self.shares_client.list_consistency_groups(
detailed=True)
consistency_groups = self.shares_v2_client.list_consistency_groups(
detailed=True, version='2.4')
# Verify keys
[self.assertTrue(CG_DETAIL_REQUIRED_KEYS.issubset(set(cg.keys())))
@ -204,10 +209,13 @@ class ConsistencyGroupActionsTest(base.BaseSharesTest):
self.assertEqual(1, len(gen), msg)
@test.attr(type=["gate", ])
def test_filter_shares_by_consistency_group_id(self):
def test_filter_shares_by_consistency_group_id_v2_4(self):
shares = self.shares_client.list_shares(detailed=True, params={
'consistency_group_id': self.consistency_group['id']})
shares = self.shares_v2_client.list_shares(
detailed=True,
params={'consistency_group_id': self.consistency_group['id']},
version='2.4'
)
share_ids = [share['id'] for share in shares]
@ -222,10 +230,10 @@ class ConsistencyGroupActionsTest(base.BaseSharesTest):
% (self.share['id'], share_ids))
@test.attr(type=["gate", ])
def test_get_cgsnapshot(self):
def test_get_cgsnapshot_v2_4(self):
# Get consistency group
consistency_group = self.shares_client.get_consistency_group(
self.consistency_group['id'])
consistency_group = self.shares_v2_client.get_consistency_group(
self.consistency_group['id'], version='2.4')
# Verify keys
actual_keys = set(consistency_group.keys())
@ -247,10 +255,10 @@ class ConsistencyGroupActionsTest(base.BaseSharesTest):
msg)
@test.attr(type=["gate", ])
def test_get_cgsnapshot_members(self):
def test_get_cgsnapshot_members_v2_4(self):
cgsnapshot_members = self.shares_client.list_cgsnapshot_members(
self.cgsnapshot['id'])
cgsnapshot_members = self.shares_v2_client.list_cgsnapshot_members(
self.cgsnapshot['id'], version='2.4')
member_share_ids = [member['share_id'] for member in
cgsnapshot_members]
self.assertEqual(2, len(cgsnapshot_members),
@ -272,17 +280,22 @@ class ConsistencyGroupActionsTest(base.BaseSharesTest):
# member['share_type_id'])
@test.attr(type=["gate", "smoke", ])
def test_create_consistency_group_from_populated_cgsnapshot(self):
def test_create_consistency_group_from_populated_cgsnapshot_v2_4(self):
cgsnapshot_members = self.shares_client.list_cgsnapshot_members(
self.cgsnapshot['id'])
cgsnapshot_members = self.shares_v2_client.list_cgsnapshot_members(
self.cgsnapshot['id'], version='2.4')
new_consistency_group = self.create_consistency_group(
cleanup_in_class=False, source_cgsnapshot_id=self.cgsnapshot['id'])
cleanup_in_class=False,
source_cgsnapshot_id=self.cgsnapshot['id'],
version='2.4'
)
new_shares = self.shares_client.list_shares(
new_shares = self.shares_v2_client.list_shares(
params={'consistency_group_id': new_consistency_group['id']},
detailed=True)
detailed=True,
version='2.4'
)
# Verify each new share is available
for share in new_shares:
@ -326,46 +339,58 @@ class ConsistencyGroupRenameTest(base.BaseSharesTest):
)
@test.attr(type=["gate", ])
def test_update_consistency_group(self):
def test_update_consistency_group_v2_4(self):
# Get consistency_group
consistency_group = self.shares_client.get_consistency_group(
self.consistency_group['id'])
consistency_group = self.shares_v2_client.get_consistency_group(
self.consistency_group['id'], version='2.4')
self.assertEqual(self.cg_name, consistency_group["name"])
self.assertEqual(self.cg_desc, consistency_group["description"])
# Update consistency_group
new_name = data_utils.rand_name("tempest-new-name")
new_desc = data_utils.rand_name("tempest-new-description")
updated = self.shares_client.update_consistency_group(
consistency_group["id"], name=new_name, description=new_desc)
updated = self.shares_v2_client.update_consistency_group(
consistency_group["id"],
name=new_name,
description=new_desc,
version='2.4'
)
self.assertEqual(new_name, updated["name"])
self.assertEqual(new_desc, updated["description"])
# Get consistency_group
consistency_group = self.shares_client.get_consistency_group(
self.consistency_group['id'])
consistency_group = self.shares_v2_client.get_consistency_group(
self.consistency_group['id'], version='2.4')
self.assertEqual(new_name, consistency_group["name"])
self.assertEqual(new_desc, consistency_group["description"])
@test.attr(type=["gate", ])
def test_create_update_read_consistency_group_with_unicode(self):
def test_create_update_read_consistency_group_with_unicode_v2_4(self):
value1 = u'ಠ_ಠ'
value2 = u'ಠ_ರೃ'
# Create consistency_group
consistency_group = self.create_consistency_group(
cleanup_in_class=False, name=value1, description=value1)
cleanup_in_class=False,
name=value1,
description=value1,
version='2.4'
)
self.assertEqual(value1, consistency_group["name"])
self.assertEqual(value1, consistency_group["description"])
# Update consistency_group
updated = self.shares_client.update_consistency_group(
consistency_group["id"], name=value2, description=value2)
updated = self.shares_v2_client.update_consistency_group(
consistency_group["id"],
name=value2,
description=value2,
version='2.4'
)
self.assertEqual(value2, updated["name"])
self.assertEqual(value2, updated["description"])
# Get consistency_group
consistency_group = self.shares_client.get_consistency_group(
consistency_group['id'])
consistency_group = self.shares_v2_client.get_consistency_group(
consistency_group['id'], version='2.4')
self.assertEqual(value2, consistency_group["name"])
self.assertEqual(value2, consistency_group["description"])

View File

@ -33,10 +33,10 @@ class ConsistencyGroupsTest(base.BaseSharesTest):
"""Covers consistency group functionality."""
@test.attr(type=["gate", ])
def test_create_populate_delete_consistency_group(self):
def test_create_populate_delete_consistency_group_v2_4(self):
# Create a consistency group
consistency_group = self.create_consistency_group(
cleanup_in_class=False)
cleanup_in_class=False, version='2.4')
self.assertTrue(CG_REQUIRED_ELEMENTS.issubset(
consistency_group.keys()),
'At least one expected element missing from consistency group '
@ -45,31 +45,35 @@ class ConsistencyGroupsTest(base.BaseSharesTest):
"actual": consistency_group.keys()})
# Populate
share = self.create_share(consistency_group_id=consistency_group['id'],
cleanup_in_class=False)
cleanup_in_class=False,
client=self.shares_v2_client,
version='2.4')
# Delete
params = {"consistency_group_id": consistency_group['id']}
self.shares_client.delete_share(share['id'], params=params)
self.shares_v2_client.delete_share(share['id'], params=params,
version='2.4')
self.shares_client.wait_for_resource_deletion(share_id=share['id'])
self.shares_client.delete_consistency_group(consistency_group['id'])
self.shares_client.wait_for_resource_deletion(
self.shares_v2_client.delete_consistency_group(consistency_group['id'],
version='2.4')
self.shares_v2_client.wait_for_resource_deletion(
cg_id=consistency_group['id'])
# Verify
self.assertRaises(lib_exc.NotFound,
self.shares_client.get_consistency_group,
self.shares_v2_client.get_consistency_group,
consistency_group['id'])
self.assertRaises(lib_exc.NotFound,
self.shares_client.get_share,
share['id'])
@test.attr(type=["gate", ])
def test_create_delete_empty_cgsnapshot(self):
def test_create_delete_empty_cgsnapshot_v2_4(self):
# Create base consistency group
consistency_group = self.create_consistency_group(
cleanup_in_class=False)
cleanup_in_class=False, version='2.4')
# Create cgsnapshot
cgsnapshot = self.create_cgsnapshot_wait_for_active(
consistency_group["id"], cleanup_in_class=False)
consistency_group["id"], cleanup_in_class=False, version='2.4')
self.assertTrue(CGSNAPSHOT_REQUIRED_ELEMENTS.issubset(
cgsnapshot.keys()),
@ -78,19 +82,22 @@ class ConsistencyGroupsTest(base.BaseSharesTest):
"expected": CGSNAPSHOT_REQUIRED_ELEMENTS,
"actual": cgsnapshot.keys()})
cgsnapshot_members = self.shares_client.list_cgsnapshot_members(
cgsnapshot['id'])
cgsnapshot_members = self.shares_v2_client.list_cgsnapshot_members(
cgsnapshot['id'], version='2.4')
self.assertEmpty(cgsnapshot_members,
'Expected 0 cgsnapshot members, got %s' % len(
cgsnapshot_members))
# delete snapshot
self.shares_client.delete_cgsnapshot(cgsnapshot["id"])
self.shares_client.wait_for_resource_deletion(
self.shares_v2_client.delete_cgsnapshot(cgsnapshot["id"],
version='2.4')
self.shares_v2_client.wait_for_resource_deletion(
cgsnapshot_id=cgsnapshot["id"])
self.assertRaises(lib_exc.NotFound,
self.shares_client.get_cgsnapshot, cgsnapshot['id'])
self.shares_v2_client.get_cgsnapshot,
cgsnapshot['id'],
version='2.4')
@test.attr(type=["gate", "smoke", ])
def test_create_consistency_group_from_empty_cgsnapshot(self):
@ -102,7 +109,7 @@ class ConsistencyGroupsTest(base.BaseSharesTest):
cgsnapshot = self.create_cgsnapshot_wait_for_active(
consistency_group["id"], cleanup_in_class=False)
cgsnapshot_members = self.shares_client.list_cgsnapshot_members(
cgsnapshot_members = self.shares_v2_client.list_cgsnapshot_members(
cgsnapshot['id'])
self.assertEmpty(cgsnapshot_members,
@ -123,8 +130,8 @@ class ConsistencyGroupsTest(base.BaseSharesTest):
self.assertEqual(new_consistency_group['source_cgsnapshot_id'],
cgsnapshot['id'], msg)
msg = 'Unexpected share_types on new consistency group. Expected %s, ' \
'got %s.' % (consistency_group['share_types'],
new_consistency_group['share_types'])
msg = ('Unexpected share_types on new consistency group. Expected '
'%s, got %s.' % (consistency_group['share_types'],
new_consistency_group['share_types']))
self.assertEqual(sorted(consistency_group['share_types']),
sorted(new_consistency_group['share_types']), msg)

View File

@ -46,7 +46,8 @@ class ConsistencyGroupsNegativeTest(base.BaseSharesTest):
name=cls.share_name,
description=cls.share_desc,
size=cls.share_size,
consistency_group_id=cls.consistency_group['id']
consistency_group_id=cls.consistency_group['id'],
client=cls.shares_v2_client
)
# Create a cgsnapshot of the consistency group
cls.cgsnap_name = data_utils.rand_name("tempest-cgsnap-name")
@ -57,149 +58,174 @@ class ConsistencyGroupsNegativeTest(base.BaseSharesTest):
description=cls.cgsnap_desc)
@test.attr(type=["negative", "smoke", "gate", ])
def test_create_cg_with_invalid_source_cgsnapshot_id_value(
def test_create_cg_with_invalid_source_cgsnapshot_id_value_v2_4(
self):
self.assertRaises(lib_exc.BadRequest,
self.create_consistency_group,
source_cgsnapshot_id='foobar',
cleanup_in_class=False)
cleanup_in_class=False,
version='2.4')
@test.attr(type=["negative", "smoke", "gate", ])
def test_create_cg_with_nonexistent_source_cgsnapshot_id_value(self):
def test_create_cg_with_nonexistent_source_cgsnapshot_id_value_v2_4(self):
self.assertRaises(lib_exc.BadRequest,
self.create_consistency_group,
source_cgsnapshot_id=self.share['id'],
cleanup_in_class=False)
cleanup_in_class=False,
version='2.4')
@test.attr(type=["negative", "smoke", "gate", ])
def test_create_cg_with_invalid_share_network_id_value(
self):
def test_create_cg_with_invalid_share_network_id_value_v2_4(self):
self.assertRaises(lib_exc.BadRequest,
self.create_consistency_group,
share_network_id='foobar',
cleanup_in_class=False)
cleanup_in_class=False,
version='2.4')
@test.attr(type=["negative", "smoke", "gate", ])
def test_create_cg_with_nonexistent_share_network_id_value(self):
def test_create_cg_with_nonexistent_share_network_id_value_v2_4(self):
self.assertRaises(lib_exc.BadRequest,
self.create_consistency_group,
share_network_id=self.share['id'],
cleanup_in_class=False)
cleanup_in_class=False,
version='2.4')
@test.attr(type=["negative", "smoke", "gate", ])
def test_create_cg_with_invalid_share_type_id_value(
self):
def test_create_cg_with_invalid_share_type_id_value_v2_4(self):
self.assertRaises(lib_exc.BadRequest,
self.create_consistency_group,
share_type_ids=['foobar'],
cleanup_in_class=False)
cleanup_in_class=False,
version='2.4')
@test.attr(type=["negative", "smoke", "gate", ])
def test_create_cg_with_nonexistent_share_type_id_value(self):
def test_create_cg_with_nonexistent_share_type_id_value_v2_4(self):
self.assertRaises(lib_exc.BadRequest,
self.create_consistency_group,
share_type_ids=[self.share['id']],
cleanup_in_class=False)
cleanup_in_class=False,
version='2.4')
@test.attr(type=["negative", "smoke", "gate", ])
def test_create_cgsnapshot_with_invalid_cg_id_value(
self):
def test_create_cgsnapshot_with_invalid_cg_id_value_v2_4(self):
self.assertRaises(lib_exc.BadRequest,
self.create_cgsnapshot_wait_for_active,
'foobar',
cleanup_in_class=False)
cleanup_in_class=False,
version='2.4')
@test.attr(type=["negative", "smoke", "gate", ])
def test_create_cgsnapshot_with_nonexistent_cg_id_value(self):
def test_create_cgsnapshot_with_nonexistent_cg_id_value_v2_4(self):
self.assertRaises(lib_exc.BadRequest,
self.create_cgsnapshot_wait_for_active,
self.share['id'],
cleanup_in_class=False)
cleanup_in_class=False,
version='2.4')
@test.attr(type=["negative", "smoke", "gate", ])
def test_get_cg_with_wrong_id(self):
def test_get_cg_with_wrong_id_v2_4(self):
self.assertRaises(lib_exc.NotFound,
self.shares_client.get_consistency_group,
"wrong_consistency_group_id")
self.shares_v2_client.get_consistency_group,
"wrong_consistency_group_id",
version='2.4')
@test.attr(type=["negative", "smoke", "gate", ])
def test_get_cg_without_passing_cg_id(self):
def test_get_cg_without_passing_cg_id_v2_4(self):
self.assertRaises(lib_exc.NotFound,
self.shares_client.get_consistency_group, '')
self.shares_v2_client.get_consistency_group,
'',
version='2.4')
@test.attr(type=["negative", "smoke", "gate", ])
def test_update_cg_with_wrong_id(self):
def test_update_cg_with_wrong_id_v2_4(self):
self.assertRaises(lib_exc.NotFound,
self.shares_client.update_consistency_group,
self.shares_v2_client.update_consistency_group,
'wrong_consistency_group_id',
name='new_name',
description='new_description')
description='new_description',
version='2.4')
@test.attr(type=["negative", "smoke", "gate", ])
def test_delete_cg_with_wrong_id(self):
def test_delete_cg_with_wrong_id_v2_4(self):
self.assertRaises(lib_exc.NotFound,
self.shares_client.delete_consistency_group,
"wrong_consistency_group_id")
self.shares_v2_client.delete_consistency_group,
"wrong_consistency_group_id",
version='2.4')
@test.attr(type=["negative", "smoke", "gate", ])
def test_delete_cg_without_passing_cg_id(self):
def test_delete_cg_without_passing_cg_id_v2_4(self):
self.assertRaises(lib_exc.NotFound,
self.shares_client.delete_consistency_group, '')
self.shares_v2_client.delete_consistency_group,
'',
version='2.4')
@test.attr(type=["negative", "gate", ])
def test_delete_cg_in_use_by_cgsnapshot(self):
def test_delete_cg_in_use_by_cgsnapshot_v2_4(self):
# Attempt delete of share type
self.assertRaises(lib_exc.Conflict,
self.shares_client.delete_consistency_group,
self.consistency_group['id'])
self.shares_v2_client.delete_consistency_group,
self.consistency_group['id'],
version='2.4')
@test.attr(type=["negative", "gate", ])
def test_delete_share_in_use_by_cgsnapshot(self):
def test_delete_share_in_use_by_cgsnapshot_v2_4(self):
# Attempt delete of share type
params = {'consistency_group_id': self.share['consistency_group_id']}
self.assertRaises(lib_exc.Forbidden,
self.shares_client.delete_share,
self.shares_v2_client.delete_share,
self.share['id'],
params=params)
params=params,
version='2.4')
@test.attr(type=["negative", "smoke", "gate", ])
def test_delete_cg_containing_a_share(self):
def test_delete_cg_containing_a_share_v2_4(self):
self.assertRaises(lib_exc.Conflict,
self.shares_client.delete_consistency_group,
self.consistency_group['id'])
self.shares_v2_client.delete_consistency_group,
self.consistency_group['id'],
version='2.4')
# Verify consistency group is not put into error state from conflict
cg = self.shares_client.get_consistency_group(
self.consistency_group['id'])
cg = self.shares_v2_client.get_consistency_group(
self.consistency_group['id'], version='2.4')
self.assertEqual('available', cg['status'])
@test.attr(type=["negative", "smoke", "gate", ])
def test_filter_shares_on_invalid_cg_id(self):
shares = self.shares_client.list_shares(detailed=True, params={
'consistency_group_id': 'foobar'})
def test_filter_shares_on_invalid_cg_id_v2_4(self):
shares = self.shares_v2_client.list_shares(
detailed=True,
params={'consistency_group_id': 'foobar'},
version='2.4'
)
self.assertEqual(0, len(shares), 'Incorrect number of shares '
'returned. Expected 0, got %s.' %
len(shares))
self.assertEqual(0, len(shares),
'Incorrect number of shares returned. Expected 0, '
'got %s.' % len(shares))
@test.attr(type=["negative", "smoke", "gate", ])
def test_filter_shares_on_nonexistent_cg_id(self):
shares = self.shares_client.list_shares(detailed=True, params={
'consistency_group_id': self.share['id']})
def test_filter_shares_on_nonexistent_cg_id_v2_4(self):
shares = self.shares_v2_client.list_shares(
detailed=True,
params={'consistency_group_id': self.share['id']},
version='2.4'
)
self.assertEqual(0, len(shares), 'Incorrect number of shares '
'returned. Expected 0, got %s.' %
len(shares))
self.assertEqual(0, len(shares),
'Incorrect number of shares returned. Expected 0, '
'got %s.' % len(shares))
@test.attr(type=["negative", "smoke", "gate", ])
def test_filter_shares_on_empty_cg_id(self):
def test_filter_shares_on_empty_cg_id_v2_4(self):
consistency_group = self.create_consistency_group(
name='tempest_cg',
description='tempest_cg_desc',
cleanup_in_class=False,
version='2.4',
)
shares = self.shares_v2_client.list_shares(
detailed=True,
params={'consistency_group_id': consistency_group['id']},
version='2.4',
)
shares = self.shares_client.list_shares(detailed=True, params={
'consistency_group_id': consistency_group['id']})
self.assertEqual(0, len(shares), 'Incorrect number of shares '
'returned. Expected 0, got %s.' %
len(shares))
self.assertEqual(0, len(shares),
'Incorrect number of shares returned. Expected 0, '
'got %s.' % len(shares))

View File

@ -1,4 +1,5 @@
# Copyright 2015 Goutham Pacha Ravi
# Copyright 2015 Clinton Knight
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@ -20,90 +21,152 @@ from manila_tempest_tests.tests.api import base
CONF = config.CONF
API_MICROVERSIONS_HEADER_LOWER = 'x-openstack-manila-api-version'
API_MICROVERSIONS_HEADER = 'X-OpenStack-Manila-API-Version'
_MIN_API_VERSION = CONF.share.min_api_microversion
_MAX_API_VERSION = CONF.share.max_api_microversion
class MicroversionsTest(base.BaseSharesTest):
"""Request and validate REST API Microversions.
Sends a HTTP GET request with the base endpoint to request a Microversion.
Sends HTTP GET requests to the version API to validate microversions.
"""
_MIN_API_VERSION = CONF.share.min_api_microversion
_MAX_API_VERSION = CONF.share.max_api_microversion
@test.attr(type=["gate", "smoke", ])
def test_microversions_root_version(self):
resp, resp_body = self.shares_v2_client.send_microversion_request()
self.assertEqual(300, resp.status)
version_list = resp_body['versions']
ids = [v['id'] for v in version_list]
self.assertEqual({'v1.0', 'v2.0'}, set(ids))
self.assertNotIn(API_MICROVERSIONS_HEADER_LOWER, resp)
self.assertNotIn('vary', resp)
v1 = [v for v in version_list if v['id'] == 'v1.0'][0]
self.assertEqual('', v1.get('min_version'))
self.assertEqual('', v1.get('version'))
v2 = [v for v in version_list if v['id'] == 'v2.0'][0]
self.assertEqual(_MIN_API_VERSION, v2.get('min_version'))
self.assertEqual(_MAX_API_VERSION, v2.get('version'))
@test.attr(type=["gate", "smoke", ])
def test_microversions_no_version(self):
resp, resp_body = self.shares_client.send_microversion_request()
def test_microversions_v1_no_version(self):
self.assertEqual(self._MIN_API_VERSION,
resp[self.shares_client.API_MICROVERSIONS_HEADER])
self.assertTrue(len(resp_body['versions']) > 0)
self.assertNotIn('min_version', resp_body['versions'][0])
self.assertNotIn('version', resp_body['versions'][0])
resp, resp_body = self.shares_v2_client.send_microversion_request(
script_name='v1')
self.assertEqual(200, resp.status)
version_list = resp_body['versions']
ids = [v['id'] for v in version_list]
self.assertEqual({'v1.0'}, set(ids))
self.assertEqual('1.0', resp.get(API_MICROVERSIONS_HEADER_LOWER))
self.assertEqual(API_MICROVERSIONS_HEADER, resp.get('vary'))
self.assertEqual('', version_list[0].get('min_version'))
self.assertEqual('', version_list[0].get('version'))
@test.attr(type=["gate", "smoke", ])
def test_microversions_version_min_version(self):
"""Requests base version 1.0."""
def test_microversions_v1_with_version(self):
resp, resp_body = self.shares_client.send_microversion_request(
self._MIN_API_VERSION)
resp, resp_body = self.shares_v2_client.send_microversion_request(
script_name='v1', version='5.0')
self.assertEqual(self._MIN_API_VERSION,
resp[self.shares_client.API_MICROVERSIONS_HEADER])
self.assertTrue(len(resp_body['versions']) > 0)
self.assertNotIn('min_version', resp_body['versions'][0])
self.assertNotIn('version', resp_body['versions'][0])
self.assertEqual(200, resp.status)
version_list = resp_body['versions']
ids = [v['id'] for v in version_list]
self.assertEqual({'v1.0'}, set(ids))
self.assertEqual('1.0', resp.get(API_MICROVERSIONS_HEADER_LOWER))
self.assertEqual(API_MICROVERSIONS_HEADER, resp.get('vary'))
self.assertEqual('', version_list[0].get('min_version'))
self.assertEqual('', version_list[0].get('version'))
@test.attr(type=["gate", "smoke", ])
def test_microversions_version_max_configured_version(self):
"""Requests maximum API microversion.
def test_microversions_v2_no_version(self):
Requests the current maximum API microversion from the Manila API
service, and confirms that version is the same as what Tempest is
configured to request in other versioned API calls.
"""
resp, resp_body = self.shares_v2_client.send_microversion_request(
script_name='v2')
resp, resp_body = self.shares_client.send_microversion_request(
self._MAX_API_VERSION)
self.assertEqual(200, resp.status)
self.assertEqual(self._MAX_API_VERSION,
resp[self.shares_client.API_MICROVERSIONS_HEADER])
self.assertTrue(len(resp_body['versions']) > 0)
self.assertEqual(self._MAX_API_VERSION,
resp_body['versions'][0]['version'])
version_list = resp_body['versions']
ids = [v['id'] for v in version_list]
self.assertEqual({'v2.0'}, set(ids))
self.assertEqual(_MIN_API_VERSION,
resp.get(API_MICROVERSIONS_HEADER_LOWER))
self.assertEqual(API_MICROVERSIONS_HEADER, resp.get('vary'))
self.assertEqual(_MIN_API_VERSION, version_list[0].get('min_version'))
self.assertEqual(_MAX_API_VERSION, version_list[0].get('version'))
@test.attr(type=["gate", "smoke", ])
def test_microversions_version_1_1(self):
"""Requests version 1.1, the first Manila microversion."""
def test_microversions_v2_min_version(self):
resp, resp_body = self.shares_client.send_microversion_request('1.1')
resp, resp_body = self.shares_v2_client.send_microversion_request(
script_name='v2', version=_MIN_API_VERSION)
self.assertEqual('1.1',
resp[self.shares_client.API_MICROVERSIONS_HEADER])
self.assertTrue(len(resp_body['versions']) > 0)
self.assertEqual(self._MIN_API_VERSION,
resp_body['versions'][0]['min_version'])
self.assertEqual(200, resp.status)
version_list = resp_body['versions']
ids = [v['id'] for v in version_list]
self.assertEqual({'v2.0'}, set(ids))
self.assertEqual(_MIN_API_VERSION,
resp.get(API_MICROVERSIONS_HEADER_LOWER))
self.assertEqual(API_MICROVERSIONS_HEADER, resp.get('vary'))
self.assertEqual(_MIN_API_VERSION, version_list[0].get('min_version'))
self.assertEqual(_MAX_API_VERSION, version_list[0].get('version'))
@test.attr(type=["gate", "smoke", ])
def test_microversions_unavailable_versions(self):
"""Requests a version greater than the latest available version."""
def test_microversions_v2_max_version(self):
resp, resp_body = self.shares_client.send_microversion_request('1.1')
self.assertTrue(len(resp_body['versions']) > 0)
major_ver, minor_ver = [int(ver) for ver in
resp_body['versions'][0]['version'].split(".")]
req_version = ('%s.%s' % (major_ver + 1, minor_ver + 1))
resp, _ = self.shares_client.send_microversion_request(req_version)
resp, resp_body = self.shares_v2_client.send_microversion_request(
script_name='v2', version=_MAX_API_VERSION)
self.assertEqual(200, resp.status)
version_list = resp_body['versions']
ids = [v['id'] for v in version_list]
self.assertEqual({'v2.0'}, set(ids))
self.assertEqual(_MAX_API_VERSION,
resp.get(API_MICROVERSIONS_HEADER_LOWER))
self.assertEqual(API_MICROVERSIONS_HEADER, resp.get('vary'))
self.assertEqual(_MIN_API_VERSION, version_list[0].get('min_version'))
self.assertEqual(_MAX_API_VERSION, version_list[0].get('version'))
@test.attr(type=["gate", "smoke", ])
def test_microversions_v2_invalid_version(self):
resp, _ = self.shares_v2_client.send_microversion_request(
script_name='v2', version='1.2.1')
self.assertEqual(400, resp.status)
@test.attr(type=["gate", "smoke", ])
def test_microversions_v2_unacceptable_version(self):
# First get max version from the server
resp, resp_body = self.shares_v2_client.send_microversion_request(
script_name='v2')
self.assertEqual(200, resp.status)
version_list = resp_body['versions']
latest_version = version_list[0].get('version')
major, minor = [int(ver) for ver in latest_version.split(".")]
next_version = ('%s.%s' % (major + 1, minor + 1))
# Request a version that is too high
resp, _ = self.shares_v2_client.send_microversion_request(
script_name='v2', version=next_version)
self.assertEqual(406, resp.status)
@test.attr(type=["gate", "smoke", ])
def test_microversions_invalid_versions(self):
"""Requests invalid versions."""
resp, resp_body = self.shares_client.send_microversion_request('1.2.1')
self.assertEqual(400, resp.status)
resp, _ = self.shares_client.send_microversion_request('None')
self.assertEqual(400, resp.status)