Graduate share replication feature
From this patch, share replication feature is no longer considered experimental. The experimental headers were removed from the share replication APIs. DocImpact Partially-implements: bp graduate-share-replication-feature Change-Id: Idf8af1c96df373fbcbb4024db490cb4dab42faf7
This commit is contained in:
parent
f78e2fdee7
commit
ee5ebc8462
@ -148,13 +148,14 @@ REST_API_VERSION_HISTORY = """
|
|||||||
"progress" which indicates the completion of a share creation
|
"progress" which indicates the completion of a share creation
|
||||||
operation as a percentage.
|
operation as a percentage.
|
||||||
* 2.55 - Share groups feature is no longer considered experimental.
|
* 2.55 - Share groups feature is no longer considered experimental.
|
||||||
|
* 2.56 - Share replication feature is no longer considered experimental.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# The minimum and maximum versions of the API supported
|
# The minimum and maximum versions of the API supported
|
||||||
# The default api version request is defined to be the
|
# The default api version request is defined to be the
|
||||||
# minimum version of the API supported.
|
# minimum version of the API supported.
|
||||||
_MIN_API_VERSION = "2.0"
|
_MIN_API_VERSION = "2.0"
|
||||||
_MAX_API_VERSION = "2.55"
|
_MAX_API_VERSION = "2.56"
|
||||||
DEFAULT_API_VERSION = _MIN_API_VERSION
|
DEFAULT_API_VERSION = _MIN_API_VERSION
|
||||||
|
|
||||||
|
|
||||||
|
@ -305,3 +305,7 @@ user documentation.
|
|||||||
2.55 (Maximum in Ussuri)
|
2.55 (Maximum in Ussuri)
|
||||||
------------------------
|
------------------------
|
||||||
Share groups feature is no longer considered experimental.
|
Share groups feature is no longer considered experimental.
|
||||||
|
|
||||||
|
2.56
|
||||||
|
----
|
||||||
|
Share replication feature is no longer considered experimental.
|
||||||
|
@ -21,6 +21,9 @@ from manila.db import api as db_api
|
|||||||
from manila import exception
|
from manila import exception
|
||||||
from manila.i18n import _
|
from manila.i18n import _
|
||||||
|
|
||||||
|
PRE_GRADUATION_VERSION = '2.55'
|
||||||
|
GRADUATION_VERSION = '2.56'
|
||||||
|
|
||||||
|
|
||||||
class ShareReplicaExportLocationController(wsgi.Controller):
|
class ShareReplicaExportLocationController(wsgi.Controller):
|
||||||
"""The Share Instance Export Locations API controller."""
|
"""The Share Instance Export Locations API controller."""
|
||||||
@ -37,9 +40,19 @@ class ShareReplicaExportLocationController(wsgi.Controller):
|
|||||||
msg = _("Share replica '%s' not found.") % share_replica_id
|
msg = _("Share replica '%s' not found.") % share_replica_id
|
||||||
raise exc.HTTPNotFound(explanation=msg)
|
raise exc.HTTPNotFound(explanation=msg)
|
||||||
|
|
||||||
@wsgi.Controller.api_version('2.47', experimental=True)
|
@wsgi.Controller.api_version(
|
||||||
@wsgi.Controller.authorize
|
'2.47', PRE_GRADUATION_VERSION, experimental=True)
|
||||||
def index(self, req, share_replica_id):
|
def index(self, req, share_replica_id):
|
||||||
|
return self._index(req, share_replica_id)
|
||||||
|
|
||||||
|
# pylint: disable=function-redefined
|
||||||
|
@wsgi.Controller.api_version(GRADUATION_VERSION) # noqa
|
||||||
|
def index(self, req, share_replica_id):
|
||||||
|
return self._index(req, share_replica_id)
|
||||||
|
|
||||||
|
# pylint: enable=function-redefined
|
||||||
|
@wsgi.Controller.authorize('index')
|
||||||
|
def _index(self, req, share_replica_id):
|
||||||
"""Return a list of export locations for the share instance."""
|
"""Return a list of export locations for the share instance."""
|
||||||
context = req.environ['manila.context']
|
context = req.environ['manila.context']
|
||||||
self._verify_share_replica(context, share_replica_id)
|
self._verify_share_replica(context, share_replica_id)
|
||||||
@ -51,9 +64,19 @@ class ShareReplicaExportLocationController(wsgi.Controller):
|
|||||||
return self._view_builder.summary_list(req, export_locations,
|
return self._view_builder.summary_list(req, export_locations,
|
||||||
replica=True)
|
replica=True)
|
||||||
|
|
||||||
@wsgi.Controller.api_version('2.47', experimental=True)
|
@wsgi.Controller.api_version(
|
||||||
@wsgi.Controller.authorize
|
'2.47', PRE_GRADUATION_VERSION, experimental=True)
|
||||||
def show(self, req, share_replica_id, export_location_uuid):
|
def show(self, req, share_replica_id, export_location_uuid):
|
||||||
|
return self._show(req, share_replica_id, export_location_uuid)
|
||||||
|
|
||||||
|
# pylint: disable=function-redefined
|
||||||
|
@wsgi.Controller.api_version(GRADUATION_VERSION) # noqa
|
||||||
|
def show(self, req, share_replica_id, export_location_uuid):
|
||||||
|
return self._show(req, share_replica_id, export_location_uuid)
|
||||||
|
|
||||||
|
# pylint: enable=function-redefined
|
||||||
|
@wsgi.Controller.authorize('show')
|
||||||
|
def _show(self, req, share_replica_id, export_location_uuid):
|
||||||
"""Return data about the requested export location."""
|
"""Return data about the requested export location."""
|
||||||
context = req.environ['manila.context']
|
context = req.environ['manila.context']
|
||||||
self._verify_share_replica(context, share_replica_id)
|
self._verify_share_replica(context, share_replica_id)
|
||||||
|
@ -31,6 +31,8 @@ from manila import share
|
|||||||
|
|
||||||
|
|
||||||
MIN_SUPPORTED_API_VERSION = '2.11'
|
MIN_SUPPORTED_API_VERSION = '2.11'
|
||||||
|
PRE_GRADUATION_VERSION = '2.55'
|
||||||
|
GRADUATION_VERSION = '2.56'
|
||||||
|
|
||||||
|
|
||||||
class ShareReplicationController(wsgi.Controller, wsgi.AdminActionsMixin):
|
class ShareReplicationController(wsgi.Controller, wsgi.AdminActionsMixin):
|
||||||
@ -55,16 +57,28 @@ class ShareReplicationController(wsgi.Controller, wsgi.AdminActionsMixin):
|
|||||||
except exception.ReplicationException as e:
|
except exception.ReplicationException as e:
|
||||||
raise exc.HTTPBadRequest(explanation=six.text_type(e))
|
raise exc.HTTPBadRequest(explanation=six.text_type(e))
|
||||||
|
|
||||||
@wsgi.Controller.api_version(MIN_SUPPORTED_API_VERSION, experimental=True)
|
@wsgi.Controller.api_version(
|
||||||
|
MIN_SUPPORTED_API_VERSION, PRE_GRADUATION_VERSION, experimental=True)
|
||||||
def index(self, req):
|
def index(self, req):
|
||||||
"""Return a summary list of replicas."""
|
"""Return a summary list of replicas."""
|
||||||
return self._get_replicas(req)
|
return self._get_replicas(req)
|
||||||
|
|
||||||
@wsgi.Controller.api_version(MIN_SUPPORTED_API_VERSION, experimental=True)
|
@wsgi.Controller.api_version(GRADUATION_VERSION) # noqa
|
||||||
|
def index(self, req): # pylint: disable=function-redefined
|
||||||
|
"""Return a summary list of replicas."""
|
||||||
|
return self._get_replicas(req)
|
||||||
|
|
||||||
|
@wsgi.Controller.api_version(
|
||||||
|
MIN_SUPPORTED_API_VERSION, PRE_GRADUATION_VERSION, experimental=True)
|
||||||
def detail(self, req):
|
def detail(self, req):
|
||||||
"""Returns a detailed list of replicas."""
|
"""Returns a detailed list of replicas."""
|
||||||
return self._get_replicas(req, is_detail=True)
|
return self._get_replicas(req, is_detail=True)
|
||||||
|
|
||||||
|
@wsgi.Controller.api_version(GRADUATION_VERSION) # noqa
|
||||||
|
def detail(self, req): # pylint: disable=function-redefined
|
||||||
|
"""Returns a detailed list of replicas."""
|
||||||
|
return self._get_replicas(req, is_detail=True)
|
||||||
|
|
||||||
@wsgi.Controller.authorize('get_all')
|
@wsgi.Controller.authorize('get_all')
|
||||||
def _get_replicas(self, req, is_detail=False):
|
def _get_replicas(self, req, is_detail=False):
|
||||||
"""Returns list of replicas."""
|
"""Returns list of replicas."""
|
||||||
@ -89,9 +103,19 @@ class ShareReplicationController(wsgi.Controller, wsgi.AdminActionsMixin):
|
|||||||
|
|
||||||
return replicas
|
return replicas
|
||||||
|
|
||||||
@wsgi.Controller.api_version(MIN_SUPPORTED_API_VERSION, experimental=True)
|
@wsgi.Controller.api_version(
|
||||||
@wsgi.Controller.authorize
|
MIN_SUPPORTED_API_VERSION, PRE_GRADUATION_VERSION, experimental=True)
|
||||||
def show(self, req, id):
|
def show(self, req, id):
|
||||||
|
"""Returns a detailed list of replicas."""
|
||||||
|
return self._show(req, id)
|
||||||
|
|
||||||
|
@wsgi.Controller.api_version(GRADUATION_VERSION) # noqa
|
||||||
|
def show(self, req, id): # pylint: disable=function-redefined
|
||||||
|
"""Returns a detailed list of replicas."""
|
||||||
|
return self._show(req, id)
|
||||||
|
|
||||||
|
@wsgi.Controller.authorize('show')
|
||||||
|
def _show(self, req, id):
|
||||||
"""Return data about the given replica."""
|
"""Return data about the given replica."""
|
||||||
context = req.environ['manila.context']
|
context = req.environ['manila.context']
|
||||||
|
|
||||||
@ -103,10 +127,19 @@ class ShareReplicationController(wsgi.Controller, wsgi.AdminActionsMixin):
|
|||||||
|
|
||||||
return self._view_builder.detail(req, replica)
|
return self._view_builder.detail(req, replica)
|
||||||
|
|
||||||
@wsgi.Controller.api_version(MIN_SUPPORTED_API_VERSION, experimental=True)
|
@wsgi.Controller.api_version(
|
||||||
|
MIN_SUPPORTED_API_VERSION, PRE_GRADUATION_VERSION, experimental=True)
|
||||||
@wsgi.response(202)
|
@wsgi.response(202)
|
||||||
@wsgi.Controller.authorize
|
|
||||||
def create(self, req, body):
|
def create(self, req, body):
|
||||||
|
return self._create(req, body)
|
||||||
|
|
||||||
|
@wsgi.Controller.api_version(GRADUATION_VERSION) # noqa
|
||||||
|
@wsgi.response(202)
|
||||||
|
def create(self, req, body): # pylint: disable=function-redefined
|
||||||
|
return self._create(req, body)
|
||||||
|
|
||||||
|
@wsgi.Controller.authorize('create')
|
||||||
|
def _create(self, req, body):
|
||||||
"""Add a replica to an existing share."""
|
"""Add a replica to an existing share."""
|
||||||
context = req.environ['manila.context']
|
context = req.environ['manila.context']
|
||||||
|
|
||||||
@ -142,9 +175,17 @@ class ShareReplicationController(wsgi.Controller, wsgi.AdminActionsMixin):
|
|||||||
|
|
||||||
return self._view_builder.detail(req, new_replica)
|
return self._view_builder.detail(req, new_replica)
|
||||||
|
|
||||||
@wsgi.Controller.api_version(MIN_SUPPORTED_API_VERSION, experimental=True)
|
@wsgi.Controller.api_version(
|
||||||
@wsgi.Controller.authorize
|
MIN_SUPPORTED_API_VERSION, PRE_GRADUATION_VERSION, experimental=True)
|
||||||
def delete(self, req, id):
|
def delete(self, req, id):
|
||||||
|
return self._delete_share_replica(req, id)
|
||||||
|
|
||||||
|
@wsgi.Controller.api_version(GRADUATION_VERSION) # noqa
|
||||||
|
def delete(self, req, id): # pylint: disable=function-redefined
|
||||||
|
return self._delete_share_replica(req, id)
|
||||||
|
|
||||||
|
@wsgi.Controller.authorize('delete')
|
||||||
|
def _delete_share_replica(self, req, id):
|
||||||
"""Delete a replica."""
|
"""Delete a replica."""
|
||||||
context = req.environ['manila.context']
|
context = req.environ['manila.context']
|
||||||
|
|
||||||
@ -161,11 +202,21 @@ class ShareReplicationController(wsgi.Controller, wsgi.AdminActionsMixin):
|
|||||||
|
|
||||||
return webob.Response(status_int=http_client.ACCEPTED)
|
return webob.Response(status_int=http_client.ACCEPTED)
|
||||||
|
|
||||||
@wsgi.Controller.api_version(MIN_SUPPORTED_API_VERSION, experimental=True)
|
@wsgi.Controller.api_version(
|
||||||
@wsgi.action('promote')
|
MIN_SUPPORTED_API_VERSION, PRE_GRADUATION_VERSION, experimental=True)
|
||||||
@wsgi.response(202)
|
@wsgi.response(202)
|
||||||
@wsgi.Controller.authorize
|
@wsgi.action('promote')
|
||||||
def promote(self, req, id, body):
|
def promote(self, req, id, body):
|
||||||
|
return self._promote(req, id, body)
|
||||||
|
|
||||||
|
@wsgi.Controller.api_version(GRADUATION_VERSION) # noqa
|
||||||
|
@wsgi.response(202)
|
||||||
|
@wsgi.action('promote')
|
||||||
|
def promote(self, req, id, body): # pylint: disable=function-redefined
|
||||||
|
return self._promote(req, id, body)
|
||||||
|
|
||||||
|
@wsgi.Controller.authorize('promote')
|
||||||
|
def _promote(self, req, id, body):
|
||||||
"""Promote a replica to active state."""
|
"""Promote a replica to active state."""
|
||||||
context = req.environ['manila.context']
|
context = req.environ['manila.context']
|
||||||
|
|
||||||
@ -189,30 +240,68 @@ class ShareReplicationController(wsgi.Controller, wsgi.AdminActionsMixin):
|
|||||||
|
|
||||||
return self._view_builder.detail(req, replica)
|
return self._view_builder.detail(req, replica)
|
||||||
|
|
||||||
@wsgi.Controller.api_version(MIN_SUPPORTED_API_VERSION, experimental=True)
|
@wsgi.Controller.api_version(
|
||||||
|
MIN_SUPPORTED_API_VERSION, PRE_GRADUATION_VERSION, experimental=True)
|
||||||
@wsgi.action('reset_status')
|
@wsgi.action('reset_status')
|
||||||
def reset_status(self, req, id, body):
|
def reset_status(self, req, id, body):
|
||||||
"""Reset the 'status' attribute in the database."""
|
"""Reset the 'status' attribute in the database."""
|
||||||
return self._reset_status(req, id, body)
|
return self._reset_status(req, id, body)
|
||||||
|
|
||||||
@wsgi.Controller.api_version(MIN_SUPPORTED_API_VERSION, experimental=True)
|
# pylint: disable=function-redefined
|
||||||
|
@wsgi.Controller.api_version(GRADUATION_VERSION) # noqa
|
||||||
|
@wsgi.action('reset_status')
|
||||||
|
def reset_status(self, req, id, body):
|
||||||
|
"""Reset the 'status' attribute in the database."""
|
||||||
|
return self._reset_status(req, id, body)
|
||||||
|
|
||||||
|
# pylint: enable=function-redefined
|
||||||
|
@wsgi.Controller.api_version(
|
||||||
|
MIN_SUPPORTED_API_VERSION, PRE_GRADUATION_VERSION, experimental=True)
|
||||||
@wsgi.action('force_delete')
|
@wsgi.action('force_delete')
|
||||||
def force_delete(self, req, id, body):
|
def force_delete(self, req, id, body):
|
||||||
"""Force deletion on the database, attempt on the backend."""
|
"""Force deletion on the database, attempt on the backend."""
|
||||||
return self._force_delete(req, id, body)
|
return self._force_delete(req, id, body)
|
||||||
|
|
||||||
@wsgi.Controller.api_version(MIN_SUPPORTED_API_VERSION, experimental=True)
|
# pylint: disable=function-redefined
|
||||||
|
@wsgi.Controller.api_version(GRADUATION_VERSION) # noqa
|
||||||
|
@wsgi.action('force_delete')
|
||||||
|
def force_delete(self, req, id, body):
|
||||||
|
"""Force deletion on the database, attempt on the backend."""
|
||||||
|
return self._force_delete(req, id, body)
|
||||||
|
|
||||||
|
# pylint: enable=function-redefined
|
||||||
|
@wsgi.Controller.api_version(
|
||||||
|
MIN_SUPPORTED_API_VERSION, PRE_GRADUATION_VERSION, experimental=True)
|
||||||
@wsgi.action('reset_replica_state')
|
@wsgi.action('reset_replica_state')
|
||||||
@wsgi.Controller.authorize
|
@wsgi.Controller.authorize
|
||||||
def reset_replica_state(self, req, id, body):
|
def reset_replica_state(self, req, id, body):
|
||||||
"""Reset the 'replica_state' attribute in the database."""
|
"""Reset the 'replica_state' attribute in the database."""
|
||||||
return self._reset_status(req, id, body, status_attr='replica_state')
|
return self._reset_status(req, id, body, status_attr='replica_state')
|
||||||
|
|
||||||
@wsgi.Controller.api_version(MIN_SUPPORTED_API_VERSION, experimental=True)
|
# pylint: disable=function-redefined
|
||||||
@wsgi.action('resync')
|
@wsgi.Controller.api_version(GRADUATION_VERSION) # noqa
|
||||||
@wsgi.response(202)
|
@wsgi.action('reset_replica_state')
|
||||||
@wsgi.Controller.authorize
|
@wsgi.Controller.authorize
|
||||||
|
def reset_replica_state(self, req, id, body):
|
||||||
|
"""Reset the 'replica_state' attribute in the database."""
|
||||||
|
return self._reset_status(req, id, body, status_attr='replica_state')
|
||||||
|
|
||||||
|
# pylint: enable=function-redefined
|
||||||
|
@wsgi.Controller.api_version(
|
||||||
|
MIN_SUPPORTED_API_VERSION, PRE_GRADUATION_VERSION, experimental=True)
|
||||||
|
@wsgi.response(202)
|
||||||
|
@wsgi.action('resync')
|
||||||
def resync(self, req, id, body):
|
def resync(self, req, id, body):
|
||||||
|
return self._resync(req, id, body)
|
||||||
|
|
||||||
|
@wsgi.Controller.api_version(GRADUATION_VERSION) # noqa
|
||||||
|
@wsgi.response(202)
|
||||||
|
@wsgi.action('resync')
|
||||||
|
def resync(self, req, id, body): # pylint: disable=function-redefined
|
||||||
|
return self._resync(req, id, body)
|
||||||
|
|
||||||
|
@wsgi.Controller.authorize('resync')
|
||||||
|
def _resync(self, req, id, body):
|
||||||
"""Attempt to update/sync the replica with its source."""
|
"""Attempt to update/sync the replica with its source."""
|
||||||
context = req.environ['manila.context']
|
context = req.environ['manila.context']
|
||||||
try:
|
try:
|
||||||
|
@ -202,11 +202,19 @@ fixture_reset_replica_status_with_different_roles = (
|
|||||||
'role': 'admin',
|
'role': 'admin',
|
||||||
'valid_code': 202,
|
'valid_code': 202,
|
||||||
'valid_status': constants.STATUS_ERROR,
|
'valid_status': constants.STATUS_ERROR,
|
||||||
|
'microversion': '2.55'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'role': 'admin',
|
||||||
|
'valid_code': 202,
|
||||||
|
'valid_status': constants.STATUS_ERROR,
|
||||||
|
'microversion': '2.56'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'role': 'member',
|
'role': 'member',
|
||||||
'valid_code': 403,
|
'valid_code': 403,
|
||||||
'valid_status': constants.STATUS_AVAILABLE,
|
'valid_status': constants.STATUS_AVAILABLE,
|
||||||
|
'microversion': '2.55'
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -216,26 +224,31 @@ fixture_reset_replica_state_with_different_roles = (
|
|||||||
'role': 'admin',
|
'role': 'admin',
|
||||||
'valid_code': 202,
|
'valid_code': 202,
|
||||||
'valid_status': constants.REPLICA_STATE_ACTIVE,
|
'valid_status': constants.REPLICA_STATE_ACTIVE,
|
||||||
|
'microversion': '2.55'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'role': 'admin',
|
'role': 'admin',
|
||||||
'valid_code': 202,
|
'valid_code': 202,
|
||||||
'valid_status': constants.REPLICA_STATE_OUT_OF_SYNC,
|
'valid_status': constants.REPLICA_STATE_OUT_OF_SYNC,
|
||||||
|
'microversion': '2.56'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'role': 'admin',
|
'role': 'admin',
|
||||||
'valid_code': 202,
|
'valid_code': 202,
|
||||||
'valid_status': constants.REPLICA_STATE_IN_SYNC,
|
'valid_status': constants.REPLICA_STATE_IN_SYNC,
|
||||||
|
'microversion': '2.55'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'role': 'admin',
|
'role': 'admin',
|
||||||
'valid_code': 202,
|
'valid_code': 202,
|
||||||
'valid_status': constants.STATUS_ERROR,
|
'valid_status': constants.STATUS_ERROR,
|
||||||
|
'microversion': '2.56'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'role': 'member',
|
'role': 'member',
|
||||||
'valid_code': 403,
|
'valid_code': 403,
|
||||||
'valid_status': constants.REPLICA_STATE_IN_SYNC,
|
'valid_status': constants.REPLICA_STATE_IN_SYNC,
|
||||||
|
'microversion': '2.55'
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -28,6 +28,9 @@ from manila.tests.api import fakes
|
|||||||
from manila.tests import db_utils
|
from manila.tests import db_utils
|
||||||
|
|
||||||
|
|
||||||
|
GRADUATION_VERSION = '2.56'
|
||||||
|
|
||||||
|
|
||||||
@ddt.ddt
|
@ddt.ddt
|
||||||
class ShareReplicaExportLocationsAPITest(test.TestCase):
|
class ShareReplicaExportLocationsAPITest(test.TestCase):
|
||||||
|
|
||||||
@ -84,8 +87,9 @@ class ShareReplicaExportLocationsAPITest(test.TestCase):
|
|||||||
db.share_export_locations_update(
|
db.share_export_locations_update(
|
||||||
self.ctxt, self.share_replica3.id, replica3_exports)
|
self.ctxt, self.share_replica3.id, replica3_exports)
|
||||||
|
|
||||||
@ddt.data('user', 'admin')
|
@ddt.data(('user', '2.47'), ('admin', GRADUATION_VERSION))
|
||||||
def test_list_and_show(self, role):
|
@ddt.unpack
|
||||||
|
def test_list_and_show(self, role, microversion):
|
||||||
summary_keys = [
|
summary_keys = [
|
||||||
'id', 'path', 'replica_state', 'availability_zone', 'preferred'
|
'id', 'path', 'replica_state', 'availability_zone', 'preferred'
|
||||||
]
|
]
|
||||||
@ -96,12 +100,15 @@ class ShareReplicaExportLocationsAPITest(test.TestCase):
|
|||||||
admin_detail_keys = admin_summary_keys + ['created_at', 'updated_at']
|
admin_detail_keys = admin_summary_keys + ['created_at', 'updated_at']
|
||||||
|
|
||||||
self._test_list_and_show(role, summary_keys, detail_keys,
|
self._test_list_and_show(role, summary_keys, detail_keys,
|
||||||
admin_summary_keys, admin_detail_keys)
|
admin_summary_keys, admin_detail_keys,
|
||||||
|
microversion=microversion)
|
||||||
|
|
||||||
def _test_list_and_show(self, role, summary_keys, detail_keys,
|
def _test_list_and_show(self, role, summary_keys, detail_keys,
|
||||||
admin_summary_keys, admin_detail_keys):
|
admin_summary_keys, admin_detail_keys,
|
||||||
|
microversion='2.47'):
|
||||||
|
|
||||||
req = self._get_request(use_admin_context=(role == 'admin'))
|
req = self._get_request(version=microversion,
|
||||||
|
use_admin_context=(role == 'admin'))
|
||||||
for replica_id in (self.active_replica_id, self.share_replica2.id,
|
for replica_id in (self.active_replica_id, self.share_replica2.id,
|
||||||
self.share_replica3.id):
|
self.share_replica3.id):
|
||||||
index_result = self.controller.index(req, replica_id)
|
index_result = self.controller.index(req, replica_id)
|
||||||
|
@ -21,6 +21,7 @@ from oslo_serialization import jsonutils
|
|||||||
import six
|
import six
|
||||||
from webob import exc
|
from webob import exc
|
||||||
|
|
||||||
|
from manila.api.openstack import api_version_request as api_version
|
||||||
from manila.api.v2 import share_replicas
|
from manila.api.v2 import share_replicas
|
||||||
from manila.common import constants
|
from manila.common import constants
|
||||||
from manila import context
|
from manila import context
|
||||||
@ -33,6 +34,9 @@ from manila.tests import db_utils
|
|||||||
from manila.tests import fake_share
|
from manila.tests import fake_share
|
||||||
|
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
|
CAST_RULES_READONLY_VERSION = '2.30'
|
||||||
|
PRE_GRADUATION_VERSION = '2.55'
|
||||||
|
GRADUATION_VERSION = '2.56'
|
||||||
|
|
||||||
|
|
||||||
@ddt.ddt
|
@ddt.ddt
|
||||||
@ -64,16 +68,24 @@ class ShareReplicasApiTest(test.TestCase):
|
|||||||
kwargs['replica_state'] = constants.REPLICA_STATE_IN_SYNC
|
kwargs['replica_state'] = constants.REPLICA_STATE_IN_SYNC
|
||||||
replica = db_utils.create_share_replica(**kwargs)
|
replica = db_utils.create_share_replica(**kwargs)
|
||||||
path = '/v2/fake/share-replicas/%s/action' % replica['id']
|
path = '/v2/fake/share-replicas/%s/action' % replica['id']
|
||||||
|
microversion = kwargs.get('microversion', self.api_version)
|
||||||
|
experimental = True
|
||||||
|
if (api_version.APIVersionRequest(microversion) >=
|
||||||
|
api_version.APIVersionRequest(GRADUATION_VERSION)):
|
||||||
|
experimental = False
|
||||||
req = fakes.HTTPRequest.blank(path, script_name=path,
|
req = fakes.HTTPRequest.blank(path, script_name=path,
|
||||||
version=self.api_version)
|
version=microversion,
|
||||||
|
experimental=experimental)
|
||||||
req.method = 'POST'
|
req.method = 'POST'
|
||||||
req.headers['content-type'] = 'application/json'
|
req.headers['content-type'] = 'application/json'
|
||||||
req.headers['X-Openstack-Manila-Api-Version'] = self.api_version
|
req.headers['X-Openstack-Manila-Api-Version'] = microversion
|
||||||
req.headers['X-Openstack-Manila-Api-Experimental'] = True
|
req.headers['X-Openstack-Manila-Api-Experimental'] = True
|
||||||
|
|
||||||
return replica, req
|
return replica, req
|
||||||
|
|
||||||
def _get_fake_replica(self, summary=False, admin=False, **values):
|
def _get_fake_replica(
|
||||||
|
self, summary=False, admin=False,
|
||||||
|
microversion=share_replicas.MIN_SUPPORTED_API_VERSION, **values):
|
||||||
replica = fake_share.fake_replica(**values)
|
replica = fake_share.fake_replica(**values)
|
||||||
replica['updated_at'] = '2016-02-11T19:57:56.506805'
|
replica['updated_at'] = '2016-02-11T19:57:56.506805'
|
||||||
expected_keys = {'id', 'share_id', 'status', 'replica_state'}
|
expected_keys = {'id', 'share_id', 'status', 'replica_state'}
|
||||||
@ -92,18 +104,37 @@ class ShareReplicasApiTest(test.TestCase):
|
|||||||
expected_replica['share_server_id'] = replica['share_server_id']
|
expected_replica['share_server_id'] = replica['share_server_id']
|
||||||
expected_replica['host'] = replica['host']
|
expected_replica['host'] = replica['host']
|
||||||
|
|
||||||
|
if (api_version.APIVersionRequest(microversion) >=
|
||||||
|
api_version.APIVersionRequest(CAST_RULES_READONLY_VERSION)
|
||||||
|
and admin):
|
||||||
|
expected_replica['cast_rules_to_readonly'] = False
|
||||||
|
|
||||||
return replica, expected_replica
|
return replica, expected_replica
|
||||||
|
|
||||||
def test_list_replicas_summary(self):
|
def _get_request(self, microversion, is_admin=False):
|
||||||
fake_replica, expected_replica = self._get_fake_replica(summary=True)
|
experimental = (api_version.APIVersionRequest(microversion) <=
|
||||||
|
api_version.APIVersionRequest(GRADUATION_VERSION))
|
||||||
|
req = fakes.HTTPRequest.blank(
|
||||||
|
'/share-replicas', version=microversion,
|
||||||
|
experimental=experimental, use_admin_context=is_admin)
|
||||||
|
|
||||||
|
return req
|
||||||
|
|
||||||
|
@ddt.data((False, PRE_GRADUATION_VERSION), (False, GRADUATION_VERSION))
|
||||||
|
@ddt.unpack
|
||||||
|
def test_list_replicas_summary(self, is_admin, microversion):
|
||||||
|
fake_replica, expected_replica = self._get_fake_replica(
|
||||||
|
summary=True, admin=is_admin, microversion=microversion)
|
||||||
self.mock_object(share_replicas.db, 'share_replicas_get_all',
|
self.mock_object(share_replicas.db, 'share_replicas_get_all',
|
||||||
mock.Mock(return_value=[fake_replica]))
|
mock.Mock(return_value=[fake_replica]))
|
||||||
|
|
||||||
res_dict = self.controller.index(self.replicas_req)
|
req = self._get_request(is_admin=is_admin, microversion=microversion)
|
||||||
|
context = req.environ['manila.context']
|
||||||
|
res_dict = self.controller.index(req)
|
||||||
|
|
||||||
self.assertEqual([expected_replica], res_dict['share_replicas'])
|
self.assertEqual([expected_replica], res_dict['share_replicas'])
|
||||||
self.mock_policy_check.assert_called_once_with(
|
self.mock_policy_check.assert_called_once_with(
|
||||||
self.member_context, self.resource_name, 'get_all')
|
context, self.resource_name, 'get_all')
|
||||||
|
|
||||||
def test_list_share_replicas_summary(self):
|
def test_list_share_replicas_summary(self):
|
||||||
fake_replica, expected_replica = self._get_fake_replica(summary=True)
|
fake_replica, expected_replica = self._get_fake_replica(summary=True)
|
||||||
@ -120,13 +151,15 @@ class ShareReplicasApiTest(test.TestCase):
|
|||||||
self.mock_policy_check.assert_called_once_with(
|
self.mock_policy_check.assert_called_once_with(
|
||||||
req_context, self.resource_name, 'get_all')
|
req_context, self.resource_name, 'get_all')
|
||||||
|
|
||||||
@ddt.data(True, False)
|
@ddt.data((True, PRE_GRADUATION_VERSION), (False, GRADUATION_VERSION))
|
||||||
def test_list_replicas_detail(self, is_admin):
|
@ddt.unpack
|
||||||
fake_replica, expected_replica = self._get_fake_replica(admin=is_admin)
|
def test_list_replicas_detail(self, is_admin, microversion):
|
||||||
|
fake_replica, expected_replica = self._get_fake_replica(
|
||||||
|
admin=is_admin, microversion=microversion)
|
||||||
self.mock_object(share_replicas.db, 'share_replicas_get_all',
|
self.mock_object(share_replicas.db, 'share_replicas_get_all',
|
||||||
mock.Mock(return_value=[fake_replica]))
|
mock.Mock(return_value=[fake_replica]))
|
||||||
|
|
||||||
req = self.replicas_req if not is_admin else self.replicas_req_admin
|
req = self._get_request(is_admin=is_admin, microversion=microversion)
|
||||||
req_context = req.environ['manila.context']
|
req_context = req.environ['manila.context']
|
||||||
|
|
||||||
res_dict = self.controller.detail(req)
|
res_dict = self.controller.detail(req)
|
||||||
@ -244,14 +277,17 @@ class ShareReplicasApiTest(test.TestCase):
|
|||||||
self.mock_policy_check.assert_called_once_with(
|
self.mock_policy_check.assert_called_once_with(
|
||||||
req_context, self.resource_name, 'get_all')
|
req_context, self.resource_name, 'get_all')
|
||||||
|
|
||||||
@ddt.data(True, False)
|
@ddt.data((True, PRE_GRADUATION_VERSION),
|
||||||
def test_show(self, is_admin):
|
(False, GRADUATION_VERSION))
|
||||||
fake_replica, expected_replica = self._get_fake_replica(admin=is_admin)
|
@ddt.unpack
|
||||||
|
def test_show(self, is_admin, microversion):
|
||||||
|
fake_replica, expected_replica = self._get_fake_replica(
|
||||||
|
admin=is_admin, microversion=microversion)
|
||||||
self.mock_object(
|
self.mock_object(
|
||||||
share_replicas.db, 'share_replica_get',
|
share_replicas.db, 'share_replica_get',
|
||||||
mock.Mock(return_value=fake_replica))
|
mock.Mock(return_value=fake_replica))
|
||||||
|
|
||||||
req = self.replicas_req if not is_admin else self.replicas_req_admin
|
req = self._get_request(microversion, is_admin)
|
||||||
req_context = req.environ['manila.context']
|
req_context = req.environ['manila.context']
|
||||||
|
|
||||||
res_dict = self.controller.show(req, fake_replica.get('id'))
|
res_dict = self.controller.show(req, fake_replica.get('id'))
|
||||||
@ -354,10 +390,12 @@ class ShareReplicasApiTest(test.TestCase):
|
|||||||
self.mock_policy_check.assert_called_once_with(
|
self.mock_policy_check.assert_called_once_with(
|
||||||
self.member_context, self.resource_name, 'create')
|
self.member_context, self.resource_name, 'create')
|
||||||
|
|
||||||
@ddt.data(True, False)
|
@ddt.data((True, PRE_GRADUATION_VERSION), (False, GRADUATION_VERSION))
|
||||||
def test_create(self, is_admin):
|
@ddt.unpack
|
||||||
|
def test_create(self, is_admin, microversion):
|
||||||
fake_replica, expected_replica = self._get_fake_replica(
|
fake_replica, expected_replica = self._get_fake_replica(
|
||||||
replication_type='writable', admin=is_admin)
|
replication_type='writable', admin=is_admin,
|
||||||
|
microversion=microversion)
|
||||||
body = {
|
body = {
|
||||||
'share_replica': {
|
'share_replica': {
|
||||||
'share_id': 'FAKE_SHAREID',
|
'share_id': 'FAKE_SHAREID',
|
||||||
@ -372,7 +410,7 @@ class ShareReplicasApiTest(test.TestCase):
|
|||||||
'share_replicas_get_available_active_replica',
|
'share_replicas_get_available_active_replica',
|
||||||
mock.Mock(return_value=[{'id': 'active1'}]))
|
mock.Mock(return_value=[{'id': 'active1'}]))
|
||||||
|
|
||||||
req = self.replicas_req if not is_admin else self.replicas_req_admin
|
req = self._get_request(microversion, is_admin)
|
||||||
req_context = req.environ['manila.context']
|
req_context = req.environ['manila.context']
|
||||||
|
|
||||||
res_dict = self.controller.create(req, body)
|
res_dict = self.controller.create(req, body)
|
||||||
@ -417,20 +455,23 @@ class ShareReplicasApiTest(test.TestCase):
|
|||||||
self.mock_policy_check.assert_called_once_with(
|
self.mock_policy_check.assert_called_once_with(
|
||||||
self.member_context, self.resource_name, 'delete')
|
self.member_context, self.resource_name, 'delete')
|
||||||
|
|
||||||
def test_delete(self):
|
@ddt.data(PRE_GRADUATION_VERSION, GRADUATION_VERSION)
|
||||||
|
def test_delete(self, microversion):
|
||||||
fake_replica = self._get_fake_replica(
|
fake_replica = self._get_fake_replica(
|
||||||
share_id='FAKE_SHARE_ID',
|
share_id='FAKE_SHARE_ID',
|
||||||
replica_state=constants.REPLICA_STATE_ACTIVE)[0]
|
replica_state=constants.REPLICA_STATE_ACTIVE)[0]
|
||||||
|
req = self._get_request(microversion=microversion)
|
||||||
|
context = req.environ['manila.context']
|
||||||
self.mock_object(share_replicas.db, 'share_replica_get',
|
self.mock_object(share_replicas.db, 'share_replica_get',
|
||||||
mock.Mock(return_value=fake_replica))
|
mock.Mock(return_value=fake_replica))
|
||||||
self.mock_object(share.API, 'delete_share_replica')
|
self.mock_object(share.API, 'delete_share_replica')
|
||||||
|
|
||||||
resp = self.controller.delete(
|
resp = self.controller.delete(
|
||||||
self.replicas_req, 'FAKE_REPLICA_ID')
|
req, 'FAKE_REPLICA_ID')
|
||||||
|
|
||||||
self.assertEqual(202, resp.status_code)
|
self.assertEqual(202, resp.status_code)
|
||||||
self.mock_policy_check.assert_called_once_with(
|
self.mock_policy_check.assert_called_once_with(
|
||||||
self.member_context, self.resource_name, 'delete')
|
context, self.resource_name, 'delete')
|
||||||
|
|
||||||
def test_promote_invalid_replica_id(self):
|
def test_promote_invalid_replica_id(self):
|
||||||
body = {'promote': None}
|
body = {'promote': None}
|
||||||
@ -501,25 +542,28 @@ class ShareReplicasApiTest(test.TestCase):
|
|||||||
self.mock_policy_check.assert_called_once_with(
|
self.mock_policy_check.assert_called_once_with(
|
||||||
self.member_context, self.resource_name, 'promote')
|
self.member_context, self.resource_name, 'promote')
|
||||||
|
|
||||||
def test_promote(self):
|
@ddt.data(PRE_GRADUATION_VERSION, GRADUATION_VERSION)
|
||||||
|
def test_promote(self, microversion):
|
||||||
body = {'promote': None}
|
body = {'promote': None}
|
||||||
replica, expected_replica = self._get_fake_replica(
|
replica, expected_replica = self._get_fake_replica(
|
||||||
replica_state=constants.REPLICA_STATE_IN_SYNC)
|
replica_state=constants.REPLICA_STATE_IN_SYNC,
|
||||||
|
microversion=microversion)
|
||||||
self.mock_object(share_replicas.db, 'share_replica_get',
|
self.mock_object(share_replicas.db, 'share_replica_get',
|
||||||
mock.Mock(return_value=replica))
|
mock.Mock(return_value=replica))
|
||||||
mock_api_promote_replica_call = self.mock_object(
|
mock_api_promote_replica_call = self.mock_object(
|
||||||
share.API, 'promote_share_replica',
|
share.API, 'promote_share_replica',
|
||||||
mock.Mock(return_value=replica))
|
mock.Mock(return_value=replica))
|
||||||
|
req = self._get_request(microversion=microversion)
|
||||||
resp = self.controller.promote(self.replicas_req, replica['id'], body)
|
context = req.environ['manila.context']
|
||||||
|
resp = self.controller.promote(req, replica['id'], body)
|
||||||
|
|
||||||
self.assertEqual(expected_replica, resp['share_replica'])
|
self.assertEqual(expected_replica, resp['share_replica'])
|
||||||
self.assertTrue(mock_api_promote_replica_call.called)
|
self.assertTrue(mock_api_promote_replica_call.called)
|
||||||
self.mock_policy_check.assert_called_once_with(
|
self.mock_policy_check.assert_called_once_with(
|
||||||
self.member_context, self.resource_name, 'promote')
|
context, self.resource_name, 'promote')
|
||||||
|
|
||||||
@ddt.data('index', 'detail', 'show', 'create', 'delete', 'promote',
|
@ddt.data('index', 'detail', '_show', '_create', '_delete_share_replica',
|
||||||
'reset_replica_state', 'reset_status', 'resync')
|
'_promote', 'reset_replica_state', 'reset_status', '_resync')
|
||||||
def test_policy_not_authorized(self, method_name):
|
def test_policy_not_authorized(self, method_name):
|
||||||
|
|
||||||
method = getattr(self.controller, method_name)
|
method = getattr(self.controller, method_name)
|
||||||
@ -588,9 +632,10 @@ class ShareReplicasApiTest(test.TestCase):
|
|||||||
@ddt.data(*fakes.fixture_reset_replica_status_with_different_roles)
|
@ddt.data(*fakes.fixture_reset_replica_status_with_different_roles)
|
||||||
@ddt.unpack
|
@ddt.unpack
|
||||||
def test_reset_status_with_different_roles(self, role, valid_code,
|
def test_reset_status_with_different_roles(self, role, valid_code,
|
||||||
valid_status):
|
valid_status, microversion):
|
||||||
context = self._get_context(role)
|
context = self._get_context(role)
|
||||||
replica, action_req = self._create_replica_get_req()
|
replica, action_req = self._create_replica_get_req(
|
||||||
|
microversion=microversion)
|
||||||
|
|
||||||
self._reset_status(context, replica, action_req,
|
self._reset_status(context, replica, action_req,
|
||||||
valid_code=valid_code, status_attr='status',
|
valid_code=valid_code, status_attr='status',
|
||||||
@ -611,10 +656,11 @@ class ShareReplicasApiTest(test.TestCase):
|
|||||||
|
|
||||||
@ddt.data(*fakes.fixture_reset_replica_state_with_different_roles)
|
@ddt.data(*fakes.fixture_reset_replica_state_with_different_roles)
|
||||||
@ddt.unpack
|
@ddt.unpack
|
||||||
def test_reset_replica_state_with_different_roles(self, role, valid_code,
|
def test_reset_replica_state_with_different_roles(
|
||||||
valid_status):
|
self, role, valid_code, valid_status, microversion):
|
||||||
context = self._get_context(role)
|
context = self._get_context(role)
|
||||||
replica, action_req = self._create_replica_get_req()
|
replica, action_req = self._create_replica_get_req(
|
||||||
|
microversion=microversion)
|
||||||
body = {'reset_replica_state': {'replica_state': valid_status}}
|
body = {'reset_replica_state': {'replica_state': valid_status}}
|
||||||
|
|
||||||
self._reset_status(context, replica, action_req,
|
self._reset_status(context, replica, action_req,
|
||||||
@ -655,6 +701,15 @@ class ShareReplicasApiTest(test.TestCase):
|
|||||||
|
|
||||||
self._force_delete(context, req, valid_code=resp_code)
|
self._force_delete(context, req, valid_code=resp_code)
|
||||||
|
|
||||||
|
@ddt.data((PRE_GRADUATION_VERSION, 202),
|
||||||
|
(GRADUATION_VERSION, 202))
|
||||||
|
@ddt.unpack
|
||||||
|
def test_force_delete_replica(self, microversion, resp_code):
|
||||||
|
replica, req = self._create_replica_get_req(microversion=microversion)
|
||||||
|
context = self.admin_context
|
||||||
|
|
||||||
|
self._force_delete(context, req, valid_code=resp_code)
|
||||||
|
|
||||||
def test_force_delete_missing_replica(self):
|
def test_force_delete_missing_replica(self):
|
||||||
replica, req = self._create_replica_get_req()
|
replica, req = self._create_replica_get_req()
|
||||||
share_replicas.db.share_replica_delete(
|
share_replicas.db.share_replica_delete(
|
||||||
@ -701,14 +756,16 @@ class ShareReplicasApiTest(test.TestCase):
|
|||||||
self.assertEqual(400, resp.status_int)
|
self.assertEqual(400, resp.status_int)
|
||||||
share_api_call.assert_called_once_with(self.admin_context, replica)
|
share_api_call.assert_called_once_with(self.admin_context, replica)
|
||||||
|
|
||||||
@ddt.data(constants.REPLICA_STATE_ACTIVE,
|
@ddt.data((constants.REPLICA_STATE_ACTIVE, PRE_GRADUATION_VERSION),
|
||||||
constants.REPLICA_STATE_IN_SYNC,
|
(constants.REPLICA_STATE_IN_SYNC, PRE_GRADUATION_VERSION),
|
||||||
constants.REPLICA_STATE_OUT_OF_SYNC,
|
(constants.REPLICA_STATE_OUT_OF_SYNC, GRADUATION_VERSION),
|
||||||
constants.STATUS_ERROR)
|
(constants.STATUS_ERROR, GRADUATION_VERSION))
|
||||||
def test_resync(self, replica_state):
|
@ddt.unpack
|
||||||
|
def test_resync(self, replica_state, microversion):
|
||||||
|
|
||||||
replica, req = self._create_replica_get_req(
|
replica, req = self._create_replica_get_req(
|
||||||
replica_state=replica_state, host='skywalker@jedi#temple')
|
replica_state=replica_state, host='skywalker@jedi#temple',
|
||||||
|
microversion=microversion)
|
||||||
share_api_call = self.mock_object(
|
share_api_call = self.mock_object(
|
||||||
share.API, 'update_share_replica', mock.Mock(return_value=None))
|
share.API, 'update_share_replica', mock.Mock(return_value=None))
|
||||||
body = {'resync': {}}
|
body = {'resync': {}}
|
||||||
|
@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
prelude: >
|
||||||
|
- |
|
||||||
|
Share replication APIs have graduated from their `experimental feature state
|
||||||
|
<https://docs.openstack.org/manila/latest/contributor/experimental_apis.html>`_
|
||||||
|
from API version ``2.56``. One or more share replicas can be created from a
|
||||||
|
given share. They can also be promoted to be considered the active share,
|
||||||
|
resynchronized and deleted. These actions no longer require the inclusion of
|
||||||
|
``X-OpenStack-Manila-API-Experimental`` header in the API requests.
|
Loading…
Reference in New Issue
Block a user