From ee5ebc8462977a486d931a634606ee424ba990d8 Mon Sep 17 00:00:00 2001 From: silvacarloss Date: Wed, 19 Aug 2020 22:59:27 -0300 Subject: [PATCH] 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 --- manila/api/openstack/api_version_request.py | 3 +- .../openstack/rest_api_version_history.rst | 4 + .../api/v2/share_replica_export_locations.py | 31 +++- manila/api/v2/share_replicas.py | 123 +++++++++++++--- manila/tests/api/fakes.py | 13 ++ .../v2/test_share_replica_export_locations.py | 17 ++- manila/tests/api/v2/test_share_replicas.py | 137 +++++++++++++----- ...-replication-feature-17aec111b6c5bf0f.yaml | 9 ++ 8 files changed, 270 insertions(+), 67 deletions(-) create mode 100644 releasenotes/notes/graduate-share-replication-feature-17aec111b6c5bf0f.yaml diff --git a/manila/api/openstack/api_version_request.py b/manila/api/openstack/api_version_request.py index b13ea7eb1b..7c692634e4 100644 --- a/manila/api/openstack/api_version_request.py +++ b/manila/api/openstack/api_version_request.py @@ -148,13 +148,14 @@ REST_API_VERSION_HISTORY = """ "progress" which indicates the completion of a share creation operation as a percentage. * 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 default api version request is defined to be the # minimum version of the API supported. _MIN_API_VERSION = "2.0" -_MAX_API_VERSION = "2.55" +_MAX_API_VERSION = "2.56" DEFAULT_API_VERSION = _MIN_API_VERSION diff --git a/manila/api/openstack/rest_api_version_history.rst b/manila/api/openstack/rest_api_version_history.rst index 6601701a91..934012e424 100644 --- a/manila/api/openstack/rest_api_version_history.rst +++ b/manila/api/openstack/rest_api_version_history.rst @@ -305,3 +305,7 @@ user documentation. 2.55 (Maximum in Ussuri) ------------------------ Share groups feature is no longer considered experimental. + +2.56 +---- + Share replication feature is no longer considered experimental. diff --git a/manila/api/v2/share_replica_export_locations.py b/manila/api/v2/share_replica_export_locations.py index 7bdacb3088..f94bb976e3 100644 --- a/manila/api/v2/share_replica_export_locations.py +++ b/manila/api/v2/share_replica_export_locations.py @@ -21,6 +21,9 @@ from manila.db import api as db_api from manila import exception from manila.i18n import _ +PRE_GRADUATION_VERSION = '2.55' +GRADUATION_VERSION = '2.56' + class ShareReplicaExportLocationController(wsgi.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 raise exc.HTTPNotFound(explanation=msg) - @wsgi.Controller.api_version('2.47', experimental=True) - @wsgi.Controller.authorize + @wsgi.Controller.api_version( + '2.47', PRE_GRADUATION_VERSION, experimental=True) 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.""" context = req.environ['manila.context'] 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, replica=True) - @wsgi.Controller.api_version('2.47', experimental=True) - @wsgi.Controller.authorize + @wsgi.Controller.api_version( + '2.47', PRE_GRADUATION_VERSION, experimental=True) 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.""" context = req.environ['manila.context'] self._verify_share_replica(context, share_replica_id) diff --git a/manila/api/v2/share_replicas.py b/manila/api/v2/share_replicas.py index 89d388fee3..f107ceec28 100644 --- a/manila/api/v2/share_replicas.py +++ b/manila/api/v2/share_replicas.py @@ -31,6 +31,8 @@ from manila import share MIN_SUPPORTED_API_VERSION = '2.11' +PRE_GRADUATION_VERSION = '2.55' +GRADUATION_VERSION = '2.56' class ShareReplicationController(wsgi.Controller, wsgi.AdminActionsMixin): @@ -55,16 +57,28 @@ class ShareReplicationController(wsgi.Controller, wsgi.AdminActionsMixin): except exception.ReplicationException as 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): """Return a summary list of replicas.""" 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): """Returns a detailed list of replicas.""" 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') def _get_replicas(self, req, is_detail=False): """Returns list of replicas.""" @@ -89,9 +103,19 @@ class ShareReplicationController(wsgi.Controller, wsgi.AdminActionsMixin): return replicas - @wsgi.Controller.api_version(MIN_SUPPORTED_API_VERSION, experimental=True) - @wsgi.Controller.authorize + @wsgi.Controller.api_version( + MIN_SUPPORTED_API_VERSION, PRE_GRADUATION_VERSION, experimental=True) 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.""" context = req.environ['manila.context'] @@ -103,10 +127,19 @@ class ShareReplicationController(wsgi.Controller, wsgi.AdminActionsMixin): 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.Controller.authorize 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.""" context = req.environ['manila.context'] @@ -142,9 +175,17 @@ class ShareReplicationController(wsgi.Controller, wsgi.AdminActionsMixin): return self._view_builder.detail(req, new_replica) - @wsgi.Controller.api_version(MIN_SUPPORTED_API_VERSION, experimental=True) - @wsgi.Controller.authorize + @wsgi.Controller.api_version( + MIN_SUPPORTED_API_VERSION, PRE_GRADUATION_VERSION, experimental=True) 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.""" context = req.environ['manila.context'] @@ -161,11 +202,21 @@ class ShareReplicationController(wsgi.Controller, wsgi.AdminActionsMixin): return webob.Response(status_int=http_client.ACCEPTED) - @wsgi.Controller.api_version(MIN_SUPPORTED_API_VERSION, experimental=True) - @wsgi.action('promote') + @wsgi.Controller.api_version( + MIN_SUPPORTED_API_VERSION, PRE_GRADUATION_VERSION, experimental=True) @wsgi.response(202) - @wsgi.Controller.authorize + @wsgi.action('promote') 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.""" context = req.environ['manila.context'] @@ -189,30 +240,68 @@ class ShareReplicationController(wsgi.Controller, wsgi.AdminActionsMixin): 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') def reset_status(self, req, id, body): """Reset the 'status' attribute in the database.""" 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') def force_delete(self, req, id, body): """Force deletion on the database, attempt on the backend.""" 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.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') - @wsgi.Controller.api_version(MIN_SUPPORTED_API_VERSION, experimental=True) - @wsgi.action('resync') - @wsgi.response(202) + # pylint: disable=function-redefined + @wsgi.Controller.api_version(GRADUATION_VERSION) # noqa + @wsgi.action('reset_replica_state') @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): + 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.""" context = req.environ['manila.context'] try: diff --git a/manila/tests/api/fakes.py b/manila/tests/api/fakes.py index 3a928a29c5..b71ae42d71 100644 --- a/manila/tests/api/fakes.py +++ b/manila/tests/api/fakes.py @@ -202,11 +202,19 @@ fixture_reset_replica_status_with_different_roles = ( 'role': 'admin', 'valid_code': 202, 'valid_status': constants.STATUS_ERROR, + 'microversion': '2.55' + }, + { + 'role': 'admin', + 'valid_code': 202, + 'valid_status': constants.STATUS_ERROR, + 'microversion': '2.56' }, { 'role': 'member', 'valid_code': 403, 'valid_status': constants.STATUS_AVAILABLE, + 'microversion': '2.55' }, ) @@ -216,26 +224,31 @@ fixture_reset_replica_state_with_different_roles = ( 'role': 'admin', 'valid_code': 202, 'valid_status': constants.REPLICA_STATE_ACTIVE, + 'microversion': '2.55' }, { 'role': 'admin', 'valid_code': 202, 'valid_status': constants.REPLICA_STATE_OUT_OF_SYNC, + 'microversion': '2.56' }, { 'role': 'admin', 'valid_code': 202, 'valid_status': constants.REPLICA_STATE_IN_SYNC, + 'microversion': '2.55' }, { 'role': 'admin', 'valid_code': 202, 'valid_status': constants.STATUS_ERROR, + 'microversion': '2.56' }, { 'role': 'member', 'valid_code': 403, 'valid_status': constants.REPLICA_STATE_IN_SYNC, + 'microversion': '2.55' }, ) diff --git a/manila/tests/api/v2/test_share_replica_export_locations.py b/manila/tests/api/v2/test_share_replica_export_locations.py index e13802b6bd..2f0e50694c 100644 --- a/manila/tests/api/v2/test_share_replica_export_locations.py +++ b/manila/tests/api/v2/test_share_replica_export_locations.py @@ -28,6 +28,9 @@ from manila.tests.api import fakes from manila.tests import db_utils +GRADUATION_VERSION = '2.56' + + @ddt.ddt class ShareReplicaExportLocationsAPITest(test.TestCase): @@ -84,8 +87,9 @@ class ShareReplicaExportLocationsAPITest(test.TestCase): db.share_export_locations_update( self.ctxt, self.share_replica3.id, replica3_exports) - @ddt.data('user', 'admin') - def test_list_and_show(self, role): + @ddt.data(('user', '2.47'), ('admin', GRADUATION_VERSION)) + @ddt.unpack + def test_list_and_show(self, role, microversion): summary_keys = [ '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'] 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, - 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, self.share_replica3.id): index_result = self.controller.index(req, replica_id) diff --git a/manila/tests/api/v2/test_share_replicas.py b/manila/tests/api/v2/test_share_replicas.py index 2936485291..b469b4b261 100644 --- a/manila/tests/api/v2/test_share_replicas.py +++ b/manila/tests/api/v2/test_share_replicas.py @@ -21,6 +21,7 @@ from oslo_serialization import jsonutils import six from webob import exc +from manila.api.openstack import api_version_request as api_version from manila.api.v2 import share_replicas from manila.common import constants from manila import context @@ -33,6 +34,9 @@ from manila.tests import db_utils from manila.tests import fake_share CONF = cfg.CONF +CAST_RULES_READONLY_VERSION = '2.30' +PRE_GRADUATION_VERSION = '2.55' +GRADUATION_VERSION = '2.56' @ddt.ddt @@ -64,16 +68,24 @@ class ShareReplicasApiTest(test.TestCase): kwargs['replica_state'] = constants.REPLICA_STATE_IN_SYNC replica = db_utils.create_share_replica(**kwargs) 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, - version=self.api_version) + version=microversion, + experimental=experimental) req.method = 'POST' 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 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['updated_at'] = '2016-02-11T19:57:56.506805' 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['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 - def test_list_replicas_summary(self): - fake_replica, expected_replica = self._get_fake_replica(summary=True) + def _get_request(self, microversion, is_admin=False): + 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', 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.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): 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( req_context, self.resource_name, 'get_all') - @ddt.data(True, False) - def test_list_replicas_detail(self, is_admin): - fake_replica, expected_replica = self._get_fake_replica(admin=is_admin) + @ddt.data((True, PRE_GRADUATION_VERSION), (False, GRADUATION_VERSION)) + @ddt.unpack + 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', 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'] res_dict = self.controller.detail(req) @@ -244,14 +277,17 @@ class ShareReplicasApiTest(test.TestCase): self.mock_policy_check.assert_called_once_with( req_context, self.resource_name, 'get_all') - @ddt.data(True, False) - def test_show(self, is_admin): - fake_replica, expected_replica = self._get_fake_replica(admin=is_admin) + @ddt.data((True, PRE_GRADUATION_VERSION), + (False, GRADUATION_VERSION)) + @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( share_replicas.db, 'share_replica_get', 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'] 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.member_context, self.resource_name, 'create') - @ddt.data(True, False) - def test_create(self, is_admin): + @ddt.data((True, PRE_GRADUATION_VERSION), (False, GRADUATION_VERSION)) + @ddt.unpack + def test_create(self, is_admin, microversion): fake_replica, expected_replica = self._get_fake_replica( - replication_type='writable', admin=is_admin) + replication_type='writable', admin=is_admin, + microversion=microversion) body = { 'share_replica': { 'share_id': 'FAKE_SHAREID', @@ -372,7 +410,7 @@ class ShareReplicasApiTest(test.TestCase): 'share_replicas_get_available_active_replica', 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'] res_dict = self.controller.create(req, body) @@ -417,20 +455,23 @@ class ShareReplicasApiTest(test.TestCase): self.mock_policy_check.assert_called_once_with( 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( share_id='FAKE_SHARE_ID', 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', mock.Mock(return_value=fake_replica)) self.mock_object(share.API, 'delete_share_replica') resp = self.controller.delete( - self.replicas_req, 'FAKE_REPLICA_ID') + req, 'FAKE_REPLICA_ID') self.assertEqual(202, resp.status_code) 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): body = {'promote': None} @@ -501,25 +542,28 @@ class ShareReplicasApiTest(test.TestCase): self.mock_policy_check.assert_called_once_with( 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} 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', mock.Mock(return_value=replica)) mock_api_promote_replica_call = self.mock_object( share.API, 'promote_share_replica', mock.Mock(return_value=replica)) - - resp = self.controller.promote(self.replicas_req, replica['id'], body) + req = self._get_request(microversion=microversion) + context = req.environ['manila.context'] + resp = self.controller.promote(req, replica['id'], body) self.assertEqual(expected_replica, resp['share_replica']) self.assertTrue(mock_api_promote_replica_call.called) 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', - 'reset_replica_state', 'reset_status', 'resync') + @ddt.data('index', 'detail', '_show', '_create', '_delete_share_replica', + '_promote', 'reset_replica_state', 'reset_status', '_resync') def test_policy_not_authorized(self, 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.unpack def test_reset_status_with_different_roles(self, role, valid_code, - valid_status): + valid_status, microversion): 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, 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.unpack - def test_reset_replica_state_with_different_roles(self, role, valid_code, - valid_status): + def test_reset_replica_state_with_different_roles( + self, role, valid_code, valid_status, microversion): 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}} self._reset_status(context, replica, action_req, @@ -655,6 +701,15 @@ class ShareReplicasApiTest(test.TestCase): 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): replica, req = self._create_replica_get_req() share_replicas.db.share_replica_delete( @@ -701,14 +756,16 @@ class ShareReplicasApiTest(test.TestCase): self.assertEqual(400, resp.status_int) share_api_call.assert_called_once_with(self.admin_context, replica) - @ddt.data(constants.REPLICA_STATE_ACTIVE, - constants.REPLICA_STATE_IN_SYNC, - constants.REPLICA_STATE_OUT_OF_SYNC, - constants.STATUS_ERROR) - def test_resync(self, replica_state): + @ddt.data((constants.REPLICA_STATE_ACTIVE, PRE_GRADUATION_VERSION), + (constants.REPLICA_STATE_IN_SYNC, PRE_GRADUATION_VERSION), + (constants.REPLICA_STATE_OUT_OF_SYNC, GRADUATION_VERSION), + (constants.STATUS_ERROR, GRADUATION_VERSION)) + @ddt.unpack + def test_resync(self, replica_state, microversion): 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, 'update_share_replica', mock.Mock(return_value=None)) body = {'resync': {}} diff --git a/releasenotes/notes/graduate-share-replication-feature-17aec111b6c5bf0f.yaml b/releasenotes/notes/graduate-share-replication-feature-17aec111b6c5bf0f.yaml new file mode 100644 index 0000000000..c9b465b63e --- /dev/null +++ b/releasenotes/notes/graduate-share-replication-feature-17aec111b6c5bf0f.yaml @@ -0,0 +1,9 @@ +--- +prelude: > + - | + Share replication APIs have graduated from their `experimental feature state + `_ + 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.