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 <zhangbailin@inspur.com>
APIImpact

Implements: blueprint update-share-type-name-or-description
Change-Id: I4c7bdd601d48b40c01639b5089d4bff259a7b3af
This commit is contained in:
haixin 2019-07-08 20:31:52 +08:00 committed by Goutham Pacha Ravi
parent f1e10ec2ff
commit 01e89ae26b
16 changed files with 603 additions and 14 deletions

View File

@ -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.

View File

@ -0,0 +1,8 @@
{
"share_type":
{
"share_type_access:is_public": true,
"name": "testing",
"description": "share type description"
}
}

View File

@ -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"
}
}

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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']

View File

@ -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.

View File

@ -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."""

View File

@ -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.")

View File

@ -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,

View File

@ -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:

View File

@ -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))

View File

@ -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):

View File

@ -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):

View File

@ -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.