From 01e89ae26b8a23e9e2336d4ca61e41c2097e2285 Mon Sep 17 00:00:00 2001 From: haixin Date: Mon, 8 Jul 2019 20:31:52 +0800 Subject: [PATCH] Add update share-type API to Share Types Currently, only the name and description and public access of share-type is set when the share-type is created, and not allowed to be edited after the share-type is created. We can only set extra spec for share-type. But not name or description or public access for share-type. Co-Authored-By: Brin Zhang APIImpact Implements: blueprint update-share-type-name-or-description Change-Id: I4c7bdd601d48b40c01639b5089d4bff259a7b3af --- api-ref/source/parameters.yaml | 62 ++++++++ .../samples/share-type-update-request.json | 8 + .../samples/share-type-update-response.json | 38 +++++ api-ref/source/share-types.inc | 70 ++++++++ manila/api/openstack/api_version_request.py | 5 +- .../openstack/rest_api_version_history.rst | 6 + manila/api/v2/share_types.py | 93 ++++++++++- manila/db/api.py | 5 + manila/db/sqlalchemy/api.py | 51 ++++++ manila/exception.py | 4 + manila/policies/share_type.py | 10 ++ manila/share/share_types.py | 18 +++ manila/tests/api/v2/test_share_types.py | 150 +++++++++++++++++- manila/tests/db/sqlalchemy/test_api.py | 34 ++++ manila/tests/share/test_share_types.py | 58 +++++++ ...-name-or-description-a39c5991b930932f.yaml | 5 + 16 files changed, 603 insertions(+), 14 deletions(-) create mode 100644 api-ref/source/samples/share-type-update-request.json create mode 100644 api-ref/source/samples/share-type-update-response.json create mode 100644 releasenotes/notes/bp-update-share-type-name-or-description-a39c5991b930932f.yaml diff --git a/api-ref/source/parameters.yaml b/api-ref/source/parameters.yaml index 2559481b11..cc3131c1ad 100644 --- a/api-ref/source/parameters.yaml +++ b/api-ref/source/parameters.yaml @@ -814,6 +814,13 @@ create_share_from_snapshot_support: required: false type: boolean min_version: 2.24 +create_share_from_snapshot_support_body: + description: | + Boolean extra spec used for filtering of back ends by + their capability to create shares from snapshots. + in: body + required: false + type: boolean created_at: description: | The date and time stamp when the resource was created within the service's @@ -1195,6 +1202,14 @@ is_default_type: required: true type: boolean min_version: 2.46 +is_default_type_body: + description: | + Defines the share type created is default or not. If the returning + value is true, then it is the default share type, otherwise, it is + not default. + in: body + required: true + type: boolean is_group_type_default: description: | Defines the share group type created is default or not. If the @@ -1403,6 +1418,13 @@ mount_snapshot_support: required: false type: boolean min_version: 2.32 +mount_snapshot_support_body: + description: | + Boolean extra spec used for filtering of back ends + by their capability to mount share snapshots. + in: body + required: false + type: boolean name: description: | The user defined name of the resource. @@ -1775,6 +1797,12 @@ replication_type: required: false type: string min_version: 2.11 +replication_type_body: + description: | + The share replication type. + in: body + required: false + type: string request_id_body: description: | The UUID of the request during which the message was created. @@ -1814,6 +1842,13 @@ revert_to_snapshot_support: required: false type: boolean min_version: 2.27 +revert_to_snapshot_support_body: + description: | + Boolean extra spec used for filtering of back ends by their + capability to revert shares to snapshots. + in: body + required: false + type: boolean security_service_dns_ip: description: | The DNS IP address that is used inside the project network. @@ -2371,6 +2406,21 @@ share_type_access:is_public: required: false type: boolean min_version: 2.7 +share_type_access:is_public_body: + description: | + Indicates whether a share type is accessible by all projects (tenants) + in the cloud. + in: body + required: true + type: boolean +share_type_access:is_public_update_request: + description: | + Indicates whether the share type should be accessible by all projects + (tenants) in the cloud. If not specified, the visibility of the share + type is not altered. + in: body + required: false + type: boolean share_type_description: description: | The description of the share type. @@ -2378,6 +2428,12 @@ share_type_description: required: true type: string min_version: 2.41 +share_type_description_body: + description: | + The description of the share type. + in: body + required: true + type: string share_type_description_request: description: | The description of the share type. The value of this field is limited to @@ -2386,6 +2442,12 @@ share_type_description_request: required: false type: string min_version: 2.41 +share_type_description_update_request: + description: | + New description for the share type. + in: body + required: false + type: string share_type_id_body: description: | The UUID of the share type. diff --git a/api-ref/source/samples/share-type-update-request.json b/api-ref/source/samples/share-type-update-request.json new file mode 100644 index 0000000000..3e4650a896 --- /dev/null +++ b/api-ref/source/samples/share-type-update-request.json @@ -0,0 +1,8 @@ +{ + "share_type": + { + "share_type_access:is_public": true, + "name": "testing", + "description": "share type description" + } +} diff --git a/api-ref/source/samples/share-type-update-response.json b/api-ref/source/samples/share-type-update-response.json new file mode 100644 index 0000000000..6f3cbb18ac --- /dev/null +++ b/api-ref/source/samples/share-type-update-response.json @@ -0,0 +1,38 @@ +{ + "share_type": { + "required_extra_specs": { + "driver_handles_share_servers": true + }, + "share_type_access:is_public": true, + "extra_specs": { + "replication_type": "readable", + "driver_handles_share_servers": "True", + "mount_snapshot_support": "False", + "revert_to_snapshot_support": "False", + "create_share_from_snapshot_support": "True", + "snapshot_support": "True" + }, + "id": "7fa1342b-de9d-4d89-bdc8-af67795c0e52", + "name": "testing", + "is_default": false, + "description": "share type description" + }, + "volume_type": { + "required_extra_specs": { + "driver_handles_share_servers": true + }, + "share_type_access:is_public": true, + "extra_specs": { + "replication_type": "readable", + "driver_handles_share_servers": "True", + "mount_snapshot_support": "False", + "revert_to_snapshot_support": "False", + "create_share_from_snapshot_support": "True", + "snapshot_support": "True" + }, + "id": "7fa1342b-de9d-4d89-bdc8-af67795c0e52", + "name": "testing", + "is_default": false, + "description": "share type description" + } +} diff --git a/api-ref/source/share-types.inc b/api-ref/source/share-types.inc index d29a4d2b5b..e72915ecab 100644 --- a/api-ref/source/share-types.inc +++ b/api-ref/source/share-types.inc @@ -602,3 +602,73 @@ Request - project_id: project_id_path - share_type_id: share_type_id + + +Update share type (since API v2.50) +=================================== + +.. rest_method:: PUT /v2/{project_id}/types/{share_type_id} + +.. versionadded:: 2.50 + +Update a share type. Share type extra-specs cannot be updated +with this API. Please use the respective APIs to `set extra specs +<#set-extra-spec-for-share-type>`_ or `unset extra specs +<#unset-an-extra-spec>`_. + +Response codes +-------------- + +.. rest_status_code:: success status.yaml + + - 200 + +.. rest_status_code:: error status.yaml + + - 400 + - 401 + - 403 + - 404 + - 409 + +Request +------- + +.. rest_parameters:: parameters.yaml + + - project_id: project_id_path + - share_type_id: share_type_id + - name: share_type_name_request + - share_type_access:is_public: share_type_access:is_public_update_request + - description: share_type_description_update_request + +Request example +--------------- + +.. literalinclude:: samples/share-type-update-request.json + :language: javascript + +Response parameters +------------------- + +.. rest_parameters:: parameters.yaml + + - id: share_type_id_body + - required_extra_specs: required_extra_specs + - extra_specs: extra_specs + - driver_handles_share_servers: driver_handles_share_servers + - snapshot_support: snapshot_support_1 + - share_type_access:is_public: share_type_access:is_public_body + - name: share_type_name + - replication_type: replication_type_body + - mount_snapshot_support: mount_snapshot_support_body + - revert_to_snapshot_support: revert_to_snapshot_support_body + - create_share_from_snapshot_support: create_share_from_snapshot_support_body + - description: share_type_description_body + - is_default: is_default_type_body + +Response example +---------------- + +.. literalinclude:: samples/share-type-update-response.json + :language: javascript diff --git a/manila/api/openstack/api_version_request.py b/manila/api/openstack/api_version_request.py index 49a72e11d9..ce1bb57de9 100644 --- a/manila/api/openstack/api_version_request.py +++ b/manila/api/openstack/api_version_request.py @@ -134,13 +134,16 @@ REST_API_VERSION_HISTORY = """ * 2.49 - Added Manage/Unmanage Share Server APIs. Updated Manage/Unmanage Shares and Snapshots APIs to work in ``driver_handles_shares_servers`` enabled mode. + * 2.50 - Added update share type API to Share Type APIs. Through this API + we can update the ``name``, ``description`` and/or + ``share_type_access:is_public`` fields of the share type. """ # 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.49" +_MAX_API_VERSION = "2.50" 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 d3668939dc..97b4a86596 100644 --- a/manila/api/openstack/rest_api_version_history.rst +++ b/manila/api/openstack/rest_api_version_history.rst @@ -275,3 +275,9 @@ user documentation. ----------------------- Added Manage/Unmanage Share Server APIs. Updated Manage/Unmanage Shares and Snapshots APIs to work in ``driver_handles_shares_servers`` enabled mode. + +2.50 +---- + Added update share type API to Share Type APIs. We can update the ``name``, + ``description`` and/or ``share_type_access:is_public`` fields of the share + type by the update share type API. diff --git a/manila/api/v2/share_types.py b/manila/api/v2/share_types.py index 76b36bd9a5..1eac205b9c 100644 --- a/manila/api/v2/share_types.py +++ b/manila/api/v2/share_types.py @@ -52,6 +52,10 @@ class ShareTypesController(wsgi.Controller): def _notify_share_type_error(self, context, method, payload): rpc.get_notifier('shareType').error(context, method, payload) + def _notify_share_type_info(self, context, method, share_type): + payload = dict(share_types=share_type) + rpc.get_notifier('shareType').info(context, method, payload) + def _check_body(self, body, action_name): if not self.is_valid_body(body, action_name): raise webob.exc.HTTPBadRequest() @@ -221,9 +225,8 @@ class ShareTypesController(wsgi.Controller): share_type = share_types.get_share_type_by_name(context, name) share_type['required_extra_specs'] = required_extra_specs req.cache_db_share_type(share_type) - notifier_info = dict(share_types=share_type) - rpc.get_notifier('shareType').info( - context, 'share_type.create', notifier_info) + self._notify_share_type_info( + context, 'share_type.create', share_type) except exception.InvalidExtraSpec as e: raise webob.exc.HTTPBadRequest(explanation=six.text_type(e)) @@ -252,9 +255,8 @@ class ShareTypesController(wsgi.Controller): try: share_type = share_types.get_share_type(context, id) share_types.destroy(context, share_type['id']) - notifier_info = dict(share_types=share_type) - rpc.get_notifier('shareType').info( - context, 'share_type.delete', notifier_info) + self._notify_share_type_info( + context, 'share_type.delete', share_type) except exception.ShareTypeInUse as err: notifier_err = dict(id=id, error_message=six.text_type(err)) self._notify_share_type_error(context, 'share_type.delete', @@ -270,6 +272,85 @@ class ShareTypesController(wsgi.Controller): return webob.Response(status_int=http_client.ACCEPTED) + @wsgi.Controller.api_version("2.50") + @wsgi.action("update") + @wsgi.Controller.authorize + def update(self, req, id, body): + """Update name description is_public for a given share type.""" + context = req.environ['manila.context'] + + if (not self.is_valid_body(body, 'share_type') and + not self.is_valid_body(body, 'volume_type')): + raise webob.exc.HTTPBadRequest() + + elif self.is_valid_body(body, 'share_type'): + sha_type = body['share_type'] + else: + sha_type = body['volume_type'] + name = sha_type.get('name') + description = sha_type.get('description') + is_public = sha_type.get('share_type_access:is_public', None) + + if is_public is not None: + try: + is_public = strutils.bool_from_string(is_public, strict=True) + except ValueError: + msg = _("share_type_access:is_public has a non-boolean" + " value.") + raise webob.exc.HTTPBadRequest(explanation=msg) + + # If name specified, name can not be empty or greater than 255. + if name is not None: + if len(name.strip()) == 0: + msg = _("Share type name cannot be empty.") + raise webob.exc.HTTPBadRequest(explanation=msg) + if len(name) > 255: + msg = _("Share type name cannot be greater than 255 " + "characters in length.") + raise webob.exc.HTTPBadRequest(explanation=msg) + + # If description specified, length can not greater than 255. + if description and len(description) > 255: + msg = _("Share type description cannot be greater than 255 " + "characters in length.") + raise webob.exc.HTTPBadRequest(explanation=msg) + + # Name, description and is_public can not be None. + # Specify one of them, or a combination thereof. + if name is None and description is None and is_public is None: + msg = _("Specify share type name, description, " + "share_type_access:is_public or a combination thereof.") + raise webob.exc.HTTPBadRequest(explanation=msg) + + try: + share_types.update(context, id, name, description, + is_public=is_public) + # Get the updated + sha_type = self._show_share_type_details(context, id) + req.cache_resource(sha_type, name='types') + self._notify_share_type_info( + context, 'share_type.update', sha_type) + + except exception.ShareTypeNotFound as err: + notifier_err = {"id": id, "error_message": err} + self._notify_share_type_error( + context, 'share_type.update', notifier_err) + # Not found exception will be handled at the wsgi level + raise + except exception.ShareTypeExists as err: + notifier_err = {"share_type": sha_type, "error_message": err} + self._notify_share_type_error( + context, 'share_type.update', notifier_err) + raise webob.exc.HTTPConflict(explanation=err.msg) + except exception.ShareTypeUpdateFailed as err: + notifier_err = {"share_type": sha_type, "error_message": err} + self._notify_share_type_error( + context, 'share_type.update', notifier_err) + raise webob.exc.HTTPInternalServerError( + explanation=err.msg) + + return self._view_builder.show(req, sha_type) + @wsgi.Controller.authorize('list_project_access') def share_type_access(self, req, id): context = req.environ['manila.context'] diff --git a/manila/db/api.py b/manila/db/api.py index 893bb757c4..b1e9023cb1 100644 --- a/manila/db/api.py +++ b/manila/db/api.py @@ -949,6 +949,11 @@ def share_type_create(context, values, projects=None): return IMPL.share_type_create(context, values, projects) +def share_type_update(context, share_type_id, values): + """Update an exist share type.""" + return IMPL.share_type_update(context, share_type_id, values) + + def share_type_get_all(context, inactive=False, filters=None): """Get all share types. diff --git a/manila/db/sqlalchemy/api.py b/manila/db/sqlalchemy/api.py index 787918240e..c85417e795 100644 --- a/manila/db/sqlalchemy/api.py +++ b/manila/db/sqlalchemy/api.py @@ -210,6 +210,18 @@ def apply_sorting(model, query, sort_key, sort_dir): return query.order_by(sort_method()) +def handle_db_data_error(f): + def wrapper(*args, **kwargs): + try: + return f(*args, **kwargs) + except db_exc.DBDataError: + msg = _('Error writing field to database.') + LOG.exception(msg) + raise exception.Invalid(msg) + + return wrapper + + def model_query(context, model, *args, **kwargs): """Query helper that accounts for context's `read_deleted` field. @@ -3944,6 +3956,45 @@ def _share_type_get_query(context, session=None, read_deleted=None, return query +@handle_db_data_error +@oslo_db_api.wrap_db_retry(max_retries=5, retry_on_deadlock=True) +def _type_update(context, type_id, values, is_group): + + if values.get('name') is None: + values.pop('name', None) + + if is_group: + model = models.ShareGroupTypes + exists_exc = exception.ShareGroupTypeExists + exists_args = {'type_id': values.get('name')} + else: + model = models.ShareTypes + exists_exc = exception.ShareTypeExists + exists_args = {'id': values.get('name')} + + session = get_session() + with session.begin(): + query = model_query(context, model, session=session) + + try: + result = query.filter_by(id=type_id).update(values) + except db_exception.DBDuplicateEntry: + # This exception only occurs if there's a non-deleted + # share/group type which has the same name as the name being + # updated. + raise exists_exc(**exists_args) + + if not result: + if is_group: + raise exception.ShareGroupTypeNotFound(type_id=type_id) + else: + raise exception.ShareTypeNotFound(share_type_id=type_id) + + +def share_type_update(context, share_type_id, values): + _type_update(context, share_type_id, values, is_group=False) + + @require_context def share_type_get_all(context, inactive=False, filters=None): """Returns a dict describing all share_types with name as key.""" diff --git a/manila/exception.py b/manila/exception.py index f65d15a7f7..769fb02fc0 100644 --- a/manila/exception.py +++ b/manila/exception.py @@ -681,6 +681,10 @@ class ShareTypeCreateFailed(ManilaException): "name %(name)s and specs %(extra_specs)s.") +class ShareTypeUpdateFailed(ManilaException): + message = _("Cannot update share_type %(id)s.") + + class ShareGroupTypeCreateFailed(ManilaException): message = _("Cannot create share group type with " "name %(name)s and specs %(group_specs)s.") diff --git a/manila/policies/share_type.py b/manila/policies/share_type.py index b8a3629a0f..74f435735b 100644 --- a/manila/policies/share_type.py +++ b/manila/policies/share_type.py @@ -31,6 +31,16 @@ share_type_policies = [ 'path': '/types', } ]), + policy.DocumentedRuleDefault( + name=BASE_POLICY_NAME % 'update', + check_str=base.RULE_ADMIN_API, + description='Update share type.', + operations=[ + { + 'method': 'PUT', + 'path': '/types/{share_type_id}', + } + ]), policy.DocumentedRuleDefault( name=BASE_POLICY_NAME % 'show', check_str=base.RULE_DEFAULT, diff --git a/manila/share/share_types.py b/manila/share/share_types.py index 4003892b8d..fa35e09c05 100644 --- a/manila/share/share_types.py +++ b/manila/share/share_types.py @@ -70,6 +70,24 @@ def sanitize_extra_specs(extra_specs): return extra_specs +def update(context, id, name, description, is_public=None): + """Update share type by id.""" + values = {} + if name: + values.update({'name': name}) + if description == "": + values.update({'description': None}) + elif description: + values.update({'description': description}) + if is_public is not None: + values.update({'is_public': is_public}) + try: + db.share_type_update(context, id, values) + except db_exception.DBError: + LOG.exception('DB error.') + raise exception.ShareTypeUpdateFailed(id=id) + + def destroy(context, id): """Marks share types as deleted.""" if id is None: diff --git a/manila/tests/api/v2/test_share_types.py b/manila/tests/api/v2/test_share_types.py index 6b7c3936c2..be109fe46b 100644 --- a/manila/tests/api/v2/test_share_types.py +++ b/manila/tests/api/v2/test_share_types.py @@ -19,6 +19,7 @@ import ddt import mock from oslo_config import cfg from oslo_utils import timeutils +import random import webob from manila.api.v2 import share_types as types @@ -45,15 +46,25 @@ def stub_share_type(id): "key5": "value5", constants.ExtraSpecs.DRIVER_HANDLES_SHARE_SERVERS: "true", } - return dict( - id=id, - name='share_type_%s' % str(id), - description='description_%s' % str(id), - extra_specs=specs, - required_extra_specs={ + if id == 4: + name = 'update_share_type_%s' % str(id) + description = 'update_description_%s' % str(id) + is_public = False + else: + name = 'share_type_%s' % str(id) + description = 'description_%s' % str(id) + is_public = True + share_type = { + 'id': id, + 'name': name, + 'description': description, + 'is_public': is_public, + 'extra_specs': specs, + 'required_extra_specs': { constants.ExtraSpecs.DRIVER_HANDLES_SHARE_SERVERS: "true", } - ) + } + return share_type def return_share_types_get_all_types(context, search_opts=None): @@ -102,6 +113,20 @@ def return_share_types_get_share_type(context, id=1): return stub_share_type(int(id)) +def return_share_type_update(context, id=4, name=None, description=None, + is_public=None): + if id == 888: + raise exception.ShareTypeUpdateFailed(id=id) + if id == 999: + raise exception.ShareTypeNotFound(share_type_id=id) + pre_share_type = stub_share_type(int(id)) + new_name = name + new_description = description + return pre_share_type.update({"name": new_name, + "description": new_description, + "is_public": is_public}) + + def return_share_types_get_by_name(context, name): if name == "777": raise exception.ShareTypeNotFoundByName(share_type_name=name) @@ -146,6 +171,28 @@ def make_create_body(name="test_share_1", extra_specs=None, return body +def generate_long_description(des_length=256): + random_str = '' + base_str = 'ABCDEFGHIGKLMNOPQRSTUVWXYZabcdefghigklmnopqrstuvwxyz' + length = len(base_str) - 1 + for i in range(des_length): + random_str += base_str[random.randint(0, length)] + return random_str + + +def make_update_body(name=None, description=None, is_public=None): + body = {"share_type": {}} + if name: + body["share_type"].update({"name": name}) + if description: + body["share_type"].update({"description": description}) + if is_public is not None: + body["share_type"].update( + {"share_type_access:is_public": is_public}) + + return body + + @ddt.ddt class ShareTypesAPITest(test.TestCase): @@ -167,6 +214,9 @@ class ShareTypesAPITest(test.TestCase): self.mock_object( share_types, 'get_share_type', mock.Mock(side_effect=return_share_types_get_share_type)) + self.mock_object( + share_types, 'update', + mock.Mock(side_effect=return_share_type_update)) self.mock_object( share_types, 'destroy', mock.Mock(side_effect=return_share_types_destroy)) @@ -390,6 +440,92 @@ class ShareTypesAPITest(test.TestCase): self.controller._parse_is_public, 'fakefakefake') + @ddt.data( + ("new_name", "new_description", "wrong_bool"), + (" ", "new_description", "true"), + (" ", generate_long_description(256), "true"), + (None, None, None), + ) + @ddt.unpack + def test_share_types_update_with_invalid_parameter( + self, name, description, is_public): + req = fakes.HTTPRequest.blank('/v2/fake/types/4', + version='2.50') + body = make_update_body(name, description, is_public) + self.assertRaises(webob.exc.HTTPBadRequest, + self.controller.update, + req, 4, body) + self.assertEqual(0, len(fake_notifier.NOTIFICATIONS)) + + def test_share_types_update_with_invalid_body(self): + req = fakes.HTTPRequest.blank('/v2/fake/types/4', + version='2.50') + body = {'share_type': 'i_am_invalid_body'} + self.assertRaises(webob.exc.HTTPBadRequest, + self.controller.update, + req, 4, body) + self.assertEqual(0, len(fake_notifier.NOTIFICATIONS)) + + def test_share_types_update(self): + req = fakes.HTTPRequest.blank('/v2/fake/types/4', + version='2.50') + self.assertEqual(0, len(fake_notifier.NOTIFICATIONS)) + body = make_update_body("update_share_type_4", + "update_description_4", + is_public=False) + res_dict = self.controller.update(req, 4, body) + self.assertEqual(1, len(fake_notifier.NOTIFICATIONS)) + self.assertEqual(2, len(res_dict)) + + self.assertEqual('update_share_type_4', res_dict['share_type']['name']) + self.assertEqual('update_share_type_4', + res_dict['volume_type']['name']) + self.assertIs(False, + res_dict['share_type']['share_type_access:is_public']) + + self.assertEqual('update_description_4', + res_dict['share_type']['description']) + self.assertEqual('update_description_4', + res_dict['volume_type']['description']) + + def test_share_types_update_pre_v250(self): + req = fakes.HTTPRequest.blank('/v2/fake/types/4', + version='2.49') + self.assertEqual(0, len(fake_notifier.NOTIFICATIONS)) + body = make_update_body("update_share_type_4", + "update_description_4", + is_public=False) + self.assertRaises(exception.VersionNotFoundForAPIMethod, + self.controller.update, + req, 4, body) + self.assertEqual(0, len(fake_notifier.NOTIFICATIONS)) + + def test_share_types_update_failed(self): + self.assertEqual(0, len(fake_notifier.NOTIFICATIONS)) + req = fakes.HTTPRequest.blank('/v2/fake/types/888', + version='2.50') + body = make_update_body("update_share_type_888", + "update_description_888", + is_public=False) + self.assertRaises(webob.exc.HTTPInternalServerError, + self.controller.update, + req, 888, body) + self.assertEqual(1, len(fake_notifier.NOTIFICATIONS)) + + def test_share_types_update_not_found(self): + self.assertEqual(0, len(fake_notifier.NOTIFICATIONS)) + req = fakes.HTTPRequest.blank('/v2/fake/types/999', + version='2.50') + + body = make_update_body("update_share_type_999", + "update_description_999", + is_public=False) + + self.assertRaises(exception.ShareTypeNotFound, + self.controller.update, + req, 999, body) + self.assertEqual(1, len(fake_notifier.NOTIFICATIONS)) + def test_share_types_delete(self): req = fakes.HTTPRequest.blank('/v2/fake/types/1') self.assertEqual(0, len(fake_notifier.NOTIFICATIONS)) diff --git a/manila/tests/db/sqlalchemy/test_api.py b/manila/tests/db/sqlalchemy/test_api.py index d2040ed613..dbbc8541ec 100644 --- a/manila/tests/db/sqlalchemy/test_api.py +++ b/manila/tests/db/sqlalchemy/test_api.py @@ -3275,6 +3275,40 @@ class ShareTypeAPITestCase(test.TestCase): self.assertRaises(exception.DefaultShareTypeNotConfigured, db_api.share_type_get, self.ctxt, None) + @ddt.data( + {'name': 'st_1', 'description': 'des_1', 'is_public': True}, + {'name': 'st_2', 'description': 'des_2', 'is_public': None}, + {'name': 'st_3', 'description': None, 'is_public': False}, + {'name': None, 'description': 'des_4', 'is_public': True}, + ) + @ddt.unpack + def test_share_type_update(self, name, description, is_public): + values = {} + if name: + values.update({'name': name}) + if description: + values.update({'description': description}) + if is_public is not None: + values.update({'is_public': is_public}) + share_type = db_utils.create_share_type(name='st_name') + db_api.share_type_update(self.ctxt, share_type['id'], values) + updated_st = db_api.share_type_get_by_name_or_id(self.ctxt, + share_type['id']) + if name: + self.assertEqual(name, updated_st['name']) + if description: + self.assertEqual(description, updated_st['description']) + if is_public is not None: + self.assertEqual(is_public, updated_st['is_public']) + + def test_share_type_update_not_found(self): + share_type = db_utils.create_share_type(name='st_update_test') + db_api.share_type_destroy(self.ctxt, share_type['id']) + values = {"name": "not_exist"} + self.assertRaises(exception.ShareTypeNotFound, + db_api.share_type_update, + self.ctxt, share_type['id'], values) + class MessagesDatabaseAPITestCase(test.TestCase): diff --git a/manila/tests/share/test_share_types.py b/manila/tests/share/test_share_types.py index e869a52a21..d8d6b13b9c 100644 --- a/manila/tests/share/test_share_types.py +++ b/manila/tests/share/test_share_types.py @@ -41,6 +41,28 @@ def create_share_type_dict(extra_specs=None): } +def return_share_type_update(context, id, values): + name = values.get('name') + description = values.get('description') + is_public = values.get('is_public') + if id == '444': + raise exception.ShareTypeUpdateFailed(id=id) + else: + st_update = { + 'created_at': datetime.datetime(2019, 9, 9, 14, 40, 31), + 'deleted': '0', + 'deleted_at': None, + 'extra_specs': {u'gold': u'True'}, + 'required_extra_specs': {}, + 'id': id, + 'name': name, + 'is_public': is_public, + 'description': description, + 'updated_at': None + } + return st_update + + @ddt.ddt class ShareTypesTestCase(test.TestCase): @@ -71,6 +93,21 @@ class ShareTypesTestCase(test.TestCase): } } + fake_type_update = { + 'test_type_update': { + 'created_at': datetime.datetime(2019, 9, 9, 14, 40, 31), + 'deleted': '0', + 'deleted_at': None, + 'extra_specs': {u'gold': u'True'}, + 'required_extra_specs': {}, + 'id': '888', + 'name': 'new_name', + 'is_public': True, + 'description': 'new_description', + 'updated_at': None + } + } + fake_r_extra_specs = { u'gold': u'True', u'driver_handles_share_servers': u'True' @@ -254,6 +291,27 @@ class ShareTypesTestCase(test.TestCase): share_types.get_share_type_extra_specs.assert_called_once_with( self.fake_share_type_id) + def test_update_share_type(self): + expected = self.fake_type_update['test_type_update'] + self.mock_object(db, + 'share_type_update', + mock.Mock(side_effect=return_share_type_update)) + self.mock_object(db, + 'share_type_get', + mock.Mock(return_value=expected)) + new_name = "new_name" + new_description = "new_description" + is_public = True + self.assertRaises(exception.ShareTypeUpdateFailed, share_types.update, + self.context, id='444', name=new_name, + description=new_description, is_public=is_public) + share_types.update(self.context, '888', new_name, + new_description, is_public) + st_update = share_types.get_share_type(self.context, '888') + self.assertEqual(new_name, st_update['name']) + self.assertEqual(new_description, st_update['description']) + self.assertEqual(is_public, st_update['is_public']) + @ddt.data({}, {"fake": "fake"}) def test_create_without_required_extra_spec(self, optional_specs): diff --git a/releasenotes/notes/bp-update-share-type-name-or-description-a39c5991b930932f.yaml b/releasenotes/notes/bp-update-share-type-name-or-description-a39c5991b930932f.yaml new file mode 100644 index 0000000000..1e80cb5f87 --- /dev/null +++ b/releasenotes/notes/bp-update-share-type-name-or-description-a39c5991b930932f.yaml @@ -0,0 +1,5 @@ +--- +features: + - The ``name``, ``description`` and/or ``share_type_access:is_public`` + attributes of share types can be updated with API version ``2.50`` + and beyond.