From b49605945a8e58ea33abfd559d9b87a6507519ab Mon Sep 17 00:00:00 2001 From: kpdev <kinpaa@gmail.com> Date: Sat, 14 Aug 2021 19:38:55 +0200 Subject: [PATCH] Add "share-network" option for replica create API. Share replica create API does not allow to specify share network and forces to use parent share's share network. This is problem for some use-cases, e.g. migration from one share network to another share network via replication is not possible. Fixed by allowing to pass 'share-network' option for share replica create API and make sure both parent share-network and user provided share-network will have same security service association. Partial-Bug: #1925486 Change-Id: I9049dcd418fbb16d663ab8ed27b90c765fafc5d3 --- api-ref/source/share-replicas.inc | 11 +++-- manila/api/openstack/api_version_request.py | 3 +- .../openstack/rest_api_version_history.rst | 4 ++ manila/api/v2/share_replicas.py | 21 ++++++-- manila/db/api.py | 6 +++ manila/db/sqlalchemy/api.py | 11 +++++ manila/share/api.py | 15 ++++++ manila/tests/api/v2/test_share_replicas.py | 48 +++++++++++++++++-- manila/tests/db/sqlalchemy/test_api.py | 15 ++++++ ...o-replica-create-api-7d2ff3628e93fc77.yaml | 7 +++ 10 files changed, 127 insertions(+), 14 deletions(-) create mode 100644 releasenotes/notes/bug-1925486-add-share-network-option-to-replica-create-api-7d2ff3628e93fc77.yaml diff --git a/api-ref/source/share-replicas.inc b/api-ref/source/share-replicas.inc index 2fede8b473..b9ad1eaea5 100644 --- a/api-ref/source/share-replicas.inc +++ b/api-ref/source/share-replicas.inc @@ -92,11 +92,12 @@ Request example :language: javascript .. note:: - Since API version 2.51, the parameter ``share_network_id`` - is deprecated. It will be inherited from its parent share, and the - Shared File Systems service will automatically choose which share network - subnet your share replica will be placed, according to the specified - availability zone. + Since API version 2.72, the parameter ``share_network_id`` is added which + was earlier supported but later deprecated from version 2.51. In case, the + parameter is not specified, it will be inherited from its parent share, + and the Shared File Systems service will automatically choose which share + network subnet your share replica will be placed, according to the + specified availability zone. Response parameters diff --git a/manila/api/openstack/api_version_request.py b/manila/api/openstack/api_version_request.py index 44a11edf2a..adcf081f4f 100644 --- a/manila/api/openstack/api_version_request.py +++ b/manila/api/openstack/api_version_request.py @@ -186,6 +186,7 @@ REST_API_VERSION_HISTORY = """ availability zone. Also, users can add subnets for an in-use share network. * 2.71 - Added 'updated_at' field in share instance show API output. + * 2.72 - Added new option ``share-network`` to share replica creare API. """ @@ -193,7 +194,7 @@ REST_API_VERSION_HISTORY = """ # 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.71" +_MAX_API_VERSION = "2.72" 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 5aef3c5c2e..b617f2b456 100644 --- a/manila/api/openstack/rest_api_version_history.rst +++ b/manila/api/openstack/rest_api_version_history.rst @@ -396,3 +396,7 @@ ____ 2.71 ---- Added 'updated_at' field in share instance show API output. + +2.72 +---- + Added 'share_network' option to share replica create API. diff --git a/manila/api/v2/share_replicas.py b/manila/api/v2/share_replicas.py index d9ee628282..638c422462 100644 --- a/manila/api/v2/share_replicas.py +++ b/manila/api/v2/share_replicas.py @@ -21,6 +21,7 @@ import webob from webob import exc from manila.api import common +from manila.api.openstack import api_version_request as api_version from manila.api.openstack import wsgi from manila.api.views import share_replicas as replication_view from manila.common import constants @@ -174,11 +175,23 @@ class ShareReplicationController(wsgi.Controller, wsgi.AdminActionsMixin): "since it has been soft deleted.") % share_id raise exc.HTTPForbidden(explanation=msg) - share_network_id = share_ref.get('share_network_id', None) - + share_network_id = body.get('share_replica').get('share_network_id') if share_network_id: - share_network = db.share_network_get(context, share_network_id) - common.check_share_network_is_active(share_network) + if req.api_version_request < api_version.APIVersionRequest("2.72"): + msg = _("'share_network_id' option is not supported by this " + "microversion. Use 2.72 or greater microversion to " + "be able to use 'share_network_id'.") + raise exc.HTTPBadRequest(explanation=msg) + else: + share_network_id = share_ref.get('share_network_id', None) + + try: + if share_network_id: + share_network = db.share_network_get(context, share_network_id) + common.check_share_network_is_active(share_network) + except exception.ShareNetworkNotFound: + msg = _("No share network exists with ID %s.") + raise exc.HTTPNotFound(explanation=msg % share_network_id) try: new_replica = self.share_api.create_share_replica( diff --git a/manila/db/api.py b/manila/db/api.py index 441431176a..dccdac7851 100644 --- a/manila/db/api.py +++ b/manila/db/api.py @@ -792,6 +792,12 @@ def security_service_get_all_by_project(context, project_id): return IMPL.security_service_get_all_by_project(context, project_id) +def security_service_get_all_by_share_network(context, share_network_id): + """Get all security service DB records for the given share network.""" + return IMPL.security_service_get_all_by_share_network(context, + share_network_id) + + #################### def share_metadata_get(context, share_id): """Get all metadata for a share.""" diff --git a/manila/db/sqlalchemy/api.py b/manila/db/sqlalchemy/api.py index 46458948bd..04ac98f28e 100644 --- a/manila/db/sqlalchemy/api.py +++ b/manila/db/sqlalchemy/api.py @@ -3977,6 +3977,17 @@ def _security_service_get_query(context, session=None, project_only=False): project_only=project_only) +@require_context +def security_service_get_all_by_share_network(context, share_network_id): + session = get_session() + return (model_query(context, models.SecurityService, session=session). + join(models.ShareNetworkSecurityServiceAssociation, + models.SecurityService.id == + models.ShareNetworkSecurityServiceAssociation.security_service_id). + filter_by(share_network_id=share_network_id, deleted=0) + .all()) + + ################### diff --git a/manila/share/api.py b/manila/share/api.py index fc2251090d..ac9a7d451d 100644 --- a/manila/share/api.py +++ b/manila/share/api.py @@ -641,6 +641,21 @@ class API(base.Base): def create_share_replica(self, context, share, availability_zone=None, share_network_id=None, scheduler_hints=None): + parent_share_network_id = share.get('share_network_id') + if (parent_share_network_id and share_network_id and + parent_share_network_id != share_network_id): + parent_share_services = ( + self.db.security_service_get_all_by_share_network( + context, parent_share_network_id)) + share_services = ( + self.db.security_service_get_all_by_share_network( + context, share_network_id)) + for service in parent_share_services: + if service not in share_services: + msg = _("Share and its replica can't be in" + "different authentication domains.") + raise exception.InvalidInput(reason=msg) + if not share.get('replication_type'): msg = _("Replication not supported for share %s.") raise exception.InvalidShare(message=msg % share['id']) diff --git a/manila/tests/api/v2/test_share_replicas.py b/manila/tests/api/v2/test_share_replicas.py index 03f40ff84a..80613a5a48 100644 --- a/manila/tests/api/v2/test_share_replicas.py +++ b/manila/tests/api/v2/test_share_replicas.py @@ -396,6 +396,36 @@ class ShareReplicasApiTest(test.TestCase): self.mock_policy_check.assert_called_once_with( self.member_context, self.resource_name, 'create') + @ddt.data('2.72') + def test_create_invalid_network_id(self, microversion): + fake_replica, _ = self._get_fake_replica( + replication_type='writable') + req = self._get_request(microversion, False) + req_context = req.environ['manila.context'] + + body = { + 'share_replica': { + 'share_id': 'FAKE_SHAREID', + 'availability_zone': 'FAKE_AZ', + 'share_network_id': 'FAKE_NETID' + } + } + mock__view_builder_call = self.mock_object( + share_replicas.replication_view.ReplicationViewBuilder, + 'detail_list') + self.mock_object(share_replicas.db, 'share_get', + mock.Mock(return_value=fake_replica)) + self.mock_object(share_replicas.db, 'share_network_get', + mock.Mock(side_effect=exception.ShareNetworkNotFound( + share_network_id='FAKE_NETID'))) + + self.assertRaises(exc.HTTPNotFound, + self.controller.create, + req, body) + self.assertFalse(mock__view_builder_call.called) + self.mock_policy_check.assert_called_once_with( + req_context, self.resource_name, 'create') + @ddt.data(exception.AvailabilityZoneNotFound, exception.ReplicationException, exception.ShareBusyException) def test_create_exception_path(self, exception_type): @@ -432,19 +462,25 @@ class ShareReplicasApiTest(test.TestCase): common.check_share_network_is_active.assert_called_once_with( share_network) - @ddt.data((True, PRE_GRADUATION_VERSION), (False, GRADUATION_VERSION)) + @ddt.data((True, PRE_GRADUATION_VERSION), (False, GRADUATION_VERSION), + (False, "2.72")) @ddt.unpack def test_create(self, is_admin, microversion): fake_replica, expected_replica = self._get_fake_replica( replication_type='writable', admin=is_admin, microversion=microversion) - share_network = db_utils.create_share_network() body = { 'share_replica': { 'share_id': 'FAKE_SHAREID', 'availability_zone': 'FAKE_AZ' } } + if self.is_microversion_ge(microversion, '2.72'): + body["share_replica"].update({"share_network_id": 'FAKE_NETID'}) + share_network = {'id': 'FAKE_NETID'} + else: + share_network = db_utils.create_share_network() + self.mock_object(share_replicas.db, 'share_get', mock.Mock(return_value=fake_replica)) self.mock_object(share.API, 'create_share_replica', @@ -465,8 +501,12 @@ class ShareReplicasApiTest(test.TestCase): self.assertEqual(expected_replica, res_dict['share_replica']) self.mock_policy_check.assert_called_once_with( req_context, self.resource_name, 'create') - share_replicas.db.share_network_get.assert_called_once_with( - req_context, fake_replica['share_network_id']) + if self.is_microversion_ge(microversion, '2.72'): + share_replicas.db.share_network_get.assert_called_once_with( + req_context, 'FAKE_NETID') + else: + share_replicas.db.share_network_get.assert_called_once_with( + req_context, fake_replica['share_network_id']) common.check_share_network_is_active.assert_called_once_with( share_network) diff --git a/manila/tests/db/sqlalchemy/test_api.py b/manila/tests/db/sqlalchemy/test_api.py index e2207c0b09..568b87a8a7 100644 --- a/manila/tests/db/sqlalchemy/test_api.py +++ b/manila/tests/db/sqlalchemy/test_api.py @@ -3121,6 +3121,21 @@ class SecurityServiceDatabaseAPITestCase(BaseDatabaseAPITestCase): self.fake_context, 'wrong id') + def test_get_all_by_share_network(self): + db_api.security_service_create(self.fake_context, + security_service_dict) + share_nw_dict = {'id': 'fake network id', + 'project_id': 'fake project', + 'user_id': 'fake_user_id'} + db_api.share_network_create(self.fake_context, share_nw_dict) + db_api.share_network_add_security_service( + self.fake_context, + share_nw_dict['id'], security_service_dict['id']) + + result = db_api.security_service_get_all_by_share_network( + self.fake_context, share_nw_dict['id']) + self._check_expected_fields(result[0], security_service_dict) + def test_delete(self): db_api.security_service_create(self.fake_context, security_service_dict) diff --git a/releasenotes/notes/bug-1925486-add-share-network-option-to-replica-create-api-7d2ff3628e93fc77.yaml b/releasenotes/notes/bug-1925486-add-share-network-option-to-replica-create-api-7d2ff3628e93fc77.yaml new file mode 100644 index 0000000000..a4124dcd55 --- /dev/null +++ b/releasenotes/notes/bug-1925486-add-share-network-option-to-replica-create-api-7d2ff3628e93fc77.yaml @@ -0,0 +1,7 @@ +--- +fixes: + - | + `Bug #1925486 <https://bugs.launchpad.net/manila/+bug/1925486>`_ + Share replica create API does not support share network option and uses + parent share's share network. Fixed it to allow any share network by providing + option ``share-network``. Added in API microversion starting with '2.72'.