Fix 500 error if boolean is_public is passed as string
create and update apis of 'volume-type' and 'group_type' returns 500 error if boolean 'is_public' value is passed in the form of string. Internally, database api doesn't convert is_public from string to boolean and hence it raises following error: DBError: (exceptions.ValueError) invalid literal for int() with base 10 User can pass following valid boolean values to these APIs: '0', 'f', 'false', 'off', 'n', 'no', '1', 't', 'true', 'on', 'y', 'yes' Used cinder.utils.get_bool_param method to convert 'is_public' parameter from string to boolean. For invalid boolean values it will return exception.InvalidParameterValue. APIImpact: Now create and update apis of 'volume-type' and 'group_type' will not return 500 if boolean 'is_public' passed in the form of string. For boolean values other than the specified above these api's will return 400 HTTPBadRequest. Closes-Bug: #1670260 Change-Id: I9337a9182d714d7e56958fd3fb340108110783a7
This commit is contained in:
parent
356cd7ff76
commit
d8928c2067
@ -19,8 +19,6 @@ import six
|
|||||||
from six.moves import http_client
|
from six.moves import http_client
|
||||||
import webob
|
import webob
|
||||||
|
|
||||||
from oslo_utils import strutils
|
|
||||||
|
|
||||||
from cinder.api import extensions
|
from cinder.api import extensions
|
||||||
from cinder.api.openstack import wsgi
|
from cinder.api.openstack import wsgi
|
||||||
from cinder.api.views import types as views_types
|
from cinder.api.views import types as views_types
|
||||||
@ -64,7 +62,8 @@ class VolumeTypesManageController(wsgi.Controller):
|
|||||||
description = vol_type.get('description')
|
description = vol_type.get('description')
|
||||||
specs = vol_type.get('extra_specs', {})
|
specs = vol_type.get('extra_specs', {})
|
||||||
utils.validate_dictionary_string_length(specs)
|
utils.validate_dictionary_string_length(specs)
|
||||||
is_public = vol_type.get('os-volume-type-access:is_public', True)
|
is_public = utils.get_bool_param('os-volume-type-access:is_public',
|
||||||
|
vol_type, True)
|
||||||
|
|
||||||
if name is None or len(name.strip()) == 0:
|
if name is None or len(name.strip()) == 0:
|
||||||
msg = _("Volume type name can not be empty.")
|
msg = _("Volume type name can not be empty.")
|
||||||
@ -77,11 +76,6 @@ class VolumeTypesManageController(wsgi.Controller):
|
|||||||
utils.check_string_length(description, 'Type description',
|
utils.check_string_length(description, 'Type description',
|
||||||
min_length=0, max_length=255)
|
min_length=0, max_length=255)
|
||||||
|
|
||||||
if not strutils.is_valid_boolstr(is_public):
|
|
||||||
msg = _("Invalid value '%s' for is_public. Accepted values: "
|
|
||||||
"True or False.") % is_public
|
|
||||||
raise webob.exc.HTTPBadRequest(explanation=msg)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
volume_types.create(context,
|
volume_types.create(context,
|
||||||
name,
|
name,
|
||||||
@ -129,10 +123,8 @@ class VolumeTypesManageController(wsgi.Controller):
|
|||||||
"a combination thereof.")
|
"a combination thereof.")
|
||||||
raise webob.exc.HTTPBadRequest(explanation=msg)
|
raise webob.exc.HTTPBadRequest(explanation=msg)
|
||||||
|
|
||||||
if is_public is not None and not strutils.is_valid_boolstr(is_public):
|
if is_public is not None:
|
||||||
msg = _("Invalid value '%s' for is_public. Accepted values: "
|
is_public = utils.get_bool_param('is_public', vol_type)
|
||||||
"True or False.") % is_public
|
|
||||||
raise webob.exc.HTTPBadRequest(explanation=msg)
|
|
||||||
|
|
||||||
if name:
|
if name:
|
||||||
utils.check_string_length(name, 'Type name',
|
utils.check_string_length(name, 'Type name',
|
||||||
|
@ -68,7 +68,7 @@ class GroupTypesController(wsgi.Controller):
|
|||||||
name = grp_type.get('name', None)
|
name = grp_type.get('name', None)
|
||||||
description = grp_type.get('description')
|
description = grp_type.get('description')
|
||||||
specs = grp_type.get('group_specs', {})
|
specs = grp_type.get('group_specs', {})
|
||||||
is_public = grp_type.get('is_public', True)
|
is_public = utils.get_bool_param('is_public', grp_type, True)
|
||||||
|
|
||||||
if name is None or len(name.strip()) == 0:
|
if name is None or len(name.strip()) == 0:
|
||||||
msg = _("Group type name can not be empty.")
|
msg = _("Group type name can not be empty.")
|
||||||
@ -127,10 +127,8 @@ class GroupTypesController(wsgi.Controller):
|
|||||||
"a combination thereof.")
|
"a combination thereof.")
|
||||||
raise webob.exc.HTTPBadRequest(explanation=msg)
|
raise webob.exc.HTTPBadRequest(explanation=msg)
|
||||||
|
|
||||||
if is_public is not None and not strutils.is_valid_boolstr(is_public):
|
if is_public is not None:
|
||||||
msg = _("Invalid value '%s' for is_public. Accepted values: "
|
is_public = utils.get_bool_param('is_public', grp_type)
|
||||||
"True or False.") % is_public
|
|
||||||
raise webob.exc.HTTPBadRequest(explanation=msg)
|
|
||||||
|
|
||||||
if name:
|
if name:
|
||||||
utils.check_string_length(name, 'Type name',
|
utils.check_string_length(name, 'Type name',
|
||||||
|
@ -18,6 +18,7 @@ import six
|
|||||||
import webob
|
import webob
|
||||||
|
|
||||||
import ddt
|
import ddt
|
||||||
|
from oslo_utils import strutils
|
||||||
|
|
||||||
from cinder.api.contrib import types_manage
|
from cinder.api.contrib import types_manage
|
||||||
from cinder import context
|
from cinder import context
|
||||||
@ -284,9 +285,31 @@ class VolumeTypesManageApiTest(test.TestCase):
|
|||||||
"description": "test description",
|
"description": "test description",
|
||||||
"extra_specs": {"key1": "value1"}}}
|
"extra_specs": {"key1": "value1"}}}
|
||||||
req = fakes.HTTPRequest.blank('/v2/%s/types' % fake.PROJECT_ID)
|
req = fakes.HTTPRequest.blank('/v2/%s/types' % fake.PROJECT_ID)
|
||||||
self.assertRaises(webob.exc.HTTPBadRequest,
|
self.assertRaises(exception.InvalidParameterValue,
|
||||||
self.controller._create, req, body)
|
self.controller._create, req, body)
|
||||||
|
|
||||||
|
@ddt.data('0', 'f', 'false', 'off', 'n', 'no', '1', 't', 'true', 'on',
|
||||||
|
'y', 'yes')
|
||||||
|
@mock.patch.object(volume_types, "get_volume_type_by_name")
|
||||||
|
@mock.patch.object(volume_types, "create")
|
||||||
|
@mock.patch("cinder.api.openstack.wsgi.Request.cache_resource")
|
||||||
|
@mock.patch("cinder.api.views.types.ViewBuilder.show")
|
||||||
|
def test_create_type_with_valid_is_public_in_string(
|
||||||
|
self, is_public, mock_show, mock_cache_resource,
|
||||||
|
mock_create, mock_get):
|
||||||
|
boolean_is_public = strutils.bool_from_string(is_public)
|
||||||
|
ctxt = context.RequestContext(fake.USER_ID, fake.PROJECT_ID, True)
|
||||||
|
body = {"volume_type": {"name": "vol_type_1",
|
||||||
|
"os-volume-type-access:is_public":
|
||||||
|
is_public,
|
||||||
|
"extra_specs": {"key1": "value1"}}}
|
||||||
|
req = fakes.HTTPRequest.blank('/v2/%s/types' % fake.PROJECT_ID)
|
||||||
|
req.environ['cinder.context'] = ctxt
|
||||||
|
self.controller._create(req, body)
|
||||||
|
mock_create.assert_called_once_with(
|
||||||
|
ctxt, 'vol_type_1', {'key1': 'value1'},
|
||||||
|
boolean_is_public, description=None)
|
||||||
|
|
||||||
def _create_volume_type_bad_body(self, body):
|
def _create_volume_type_bad_body(self, body):
|
||||||
req = fakes.HTTPRequest.blank('/v2/%s/types' % fake.PROJECT_ID)
|
req = fakes.HTTPRequest.blank('/v2/%s/types' % fake.PROJECT_ID)
|
||||||
req.method = 'POST'
|
req.method = 'POST'
|
||||||
@ -374,6 +397,28 @@ class VolumeTypesManageApiTest(test.TestCase):
|
|||||||
(DEFAULT_VOLUME_TYPE, DEFAULT_VOLUME_TYPE),
|
(DEFAULT_VOLUME_TYPE, DEFAULT_VOLUME_TYPE),
|
||||||
'is_public': False})
|
'is_public': False})
|
||||||
|
|
||||||
|
@ddt.data('0', 'f', 'false', 'off', 'n', 'no', '1', 't', 'true', 'on',
|
||||||
|
'y', 'yes')
|
||||||
|
@mock.patch('cinder.volume.volume_types.update')
|
||||||
|
@mock.patch('cinder.volume.volume_types.get_volume_type')
|
||||||
|
@mock.patch("cinder.api.openstack.wsgi.Request.cache_resource")
|
||||||
|
@mock.patch("cinder.api.views.types.ViewBuilder.show")
|
||||||
|
def test_update_with_valid_is_public_in_string(
|
||||||
|
self, is_public, mock_show, mock_cache_resource,
|
||||||
|
mock_get, mock_update):
|
||||||
|
body = {"volume_type": {"is_public": is_public}}
|
||||||
|
req = fakes.HTTPRequest.blank('/v2/%s/types/%s' % (
|
||||||
|
fake.PROJECT_ID, DEFAULT_VOLUME_TYPE))
|
||||||
|
req.method = 'PUT'
|
||||||
|
ctxt = context.RequestContext(fake.USER_ID, fake.PROJECT_ID, True)
|
||||||
|
req.environ['cinder.context'] = ctxt
|
||||||
|
boolean_is_public = strutils.bool_from_string(is_public)
|
||||||
|
|
||||||
|
self.controller._update(req, DEFAULT_VOLUME_TYPE, body)
|
||||||
|
mock_update.assert_called_once_with(
|
||||||
|
ctxt, DEFAULT_VOLUME_TYPE, None, None,
|
||||||
|
is_public=boolean_is_public)
|
||||||
|
|
||||||
@mock.patch('cinder.volume.volume_types.update')
|
@mock.patch('cinder.volume.volume_types.update')
|
||||||
@mock.patch('cinder.volume.volume_types.get_volume_type')
|
@mock.patch('cinder.volume.volume_types.get_volume_type')
|
||||||
def test_update_type_with_description_having_length_zero(
|
def test_update_type_with_description_having_length_zero(
|
||||||
@ -550,7 +595,7 @@ class VolumeTypesManageApiTest(test.TestCase):
|
|||||||
fake.PROJECT_ID, DEFAULT_VOLUME_TYPE))
|
fake.PROJECT_ID, DEFAULT_VOLUME_TYPE))
|
||||||
req.method = 'PUT'
|
req.method = 'PUT'
|
||||||
|
|
||||||
self.assertRaises(webob.exc.HTTPBadRequest,
|
self.assertRaises(exception.InvalidParameterValue,
|
||||||
self.controller._update, req,
|
self.controller._update, req,
|
||||||
DEFAULT_VOLUME_TYPE, body)
|
DEFAULT_VOLUME_TYPE, body)
|
||||||
|
|
||||||
|
@ -15,7 +15,9 @@
|
|||||||
|
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
|
import ddt
|
||||||
import mock
|
import mock
|
||||||
|
from oslo_utils import strutils
|
||||||
from oslo_utils import timeutils
|
from oslo_utils import timeutils
|
||||||
import six
|
import six
|
||||||
import webob
|
import webob
|
||||||
@ -86,6 +88,7 @@ def return_group_types_get_default_not_found():
|
|||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
@ddt.ddt
|
||||||
class GroupTypesApiTest(test.TestCase):
|
class GroupTypesApiTest(test.TestCase):
|
||||||
|
|
||||||
def _create_group_type(self, group_type_name, group_specs=None,
|
def _create_group_type(self, group_type_name, group_specs=None,
|
||||||
@ -112,6 +115,27 @@ class GroupTypesApiTest(test.TestCase):
|
|||||||
[fake.PROJECT_ID])
|
[fake.PROJECT_ID])
|
||||||
self.type_id0 = group_types.get_default_cgsnapshot_type()['id']
|
self.type_id0 = group_types.get_default_cgsnapshot_type()['id']
|
||||||
|
|
||||||
|
@ddt.data('0', 'f', 'false', 'off', 'n', 'no', '1', 't', 'true', 'on',
|
||||||
|
'y', 'yes')
|
||||||
|
@mock.patch.object(group_types, "get_group_type_by_name")
|
||||||
|
@mock.patch.object(group_types, "create")
|
||||||
|
@mock.patch("cinder.api.openstack.wsgi.Request.cache_resource")
|
||||||
|
@mock.patch("cinder.api.views.types.ViewBuilder.show")
|
||||||
|
def test_create_group_type_with_valid_is_public_in_string(
|
||||||
|
self, is_public, mock_show, mock_cache_resource,
|
||||||
|
mock_create, mock_get):
|
||||||
|
boolean_is_public = strutils.bool_from_string(is_public)
|
||||||
|
req = fakes.HTTPRequest.blank('/v3/%s/types' % fake.PROJECT_ID,
|
||||||
|
version=GROUP_TYPE_MICRO_VERSION)
|
||||||
|
req.environ['cinder.context'] = self.ctxt
|
||||||
|
|
||||||
|
body = {"group_type": {"is_public": is_public, "name": "group_type1",
|
||||||
|
"description": None}}
|
||||||
|
self.controller.create(req, body)
|
||||||
|
mock_create.assert_called_once_with(
|
||||||
|
self.ctxt, 'group_type1', {},
|
||||||
|
boolean_is_public, description=None)
|
||||||
|
|
||||||
def test_group_types_index(self):
|
def test_group_types_index(self):
|
||||||
self.mock_object(group_types, 'get_all_group_types',
|
self.mock_object(group_types, 'get_all_group_types',
|
||||||
return_group_types_get_all_types)
|
return_group_types_get_all_types)
|
||||||
@ -260,6 +284,27 @@ class GroupTypesApiTest(test.TestCase):
|
|||||||
self.assertEqual(expect_result[2], res['group_types'][2]['id'])
|
self.assertEqual(expect_result[2], res['group_types'][2]['id'])
|
||||||
self.assertEqual(expect_result[3], res['group_types'][3]['id'])
|
self.assertEqual(expect_result[3], res['group_types'][3]['id'])
|
||||||
|
|
||||||
|
@ddt.data('0', 'f', 'false', 'off', 'n', 'no', '1', 't', 'true', 'on',
|
||||||
|
'y', 'yes')
|
||||||
|
@mock.patch.object(group_types, "get_group_type")
|
||||||
|
@mock.patch.object(group_types, "update")
|
||||||
|
@mock.patch("cinder.api.openstack.wsgi.Request.cache_resource")
|
||||||
|
@mock.patch("cinder.api.views.types.ViewBuilder.show")
|
||||||
|
def test_update_group_type_with_valid_is_public_in_string(
|
||||||
|
self, is_public, mock_show, mock_cache_resource,
|
||||||
|
mock_update, mock_get):
|
||||||
|
boolean_is_public = strutils.bool_from_string(is_public)
|
||||||
|
type_id = six.text_type(uuid.uuid4())
|
||||||
|
req = fakes.HTTPRequest.blank(
|
||||||
|
'/v3/%s/types/%s' % (fake.PROJECT_ID, type_id),
|
||||||
|
version=GROUP_TYPE_MICRO_VERSION)
|
||||||
|
req.environ['cinder.context'] = self.ctxt
|
||||||
|
body = {"group_type": {"is_public": is_public, "name": "group_type1"}}
|
||||||
|
self.controller.update(req, type_id, body)
|
||||||
|
mock_update.assert_called_once_with(
|
||||||
|
self.ctxt, type_id, 'group_type1', None,
|
||||||
|
is_public=boolean_is_public)
|
||||||
|
|
||||||
def test_group_types_show(self):
|
def test_group_types_show(self):
|
||||||
self.mock_object(group_types, 'get_group_type',
|
self.mock_object(group_types, 'get_group_type',
|
||||||
return_group_types_get_group_type)
|
return_group_types_get_group_type)
|
||||||
|
@ -592,11 +592,11 @@ def _get_disk_of_partition(devpath, st=None):
|
|||||||
return (devpath, st)
|
return (devpath, st)
|
||||||
|
|
||||||
|
|
||||||
def get_bool_param(param_string, params):
|
def get_bool_param(param_string, params, default=False):
|
||||||
param = params.get(param_string, False)
|
param = params.get(param_string, default)
|
||||||
if not strutils.is_valid_boolstr(param):
|
if not strutils.is_valid_boolstr(param):
|
||||||
msg = _('Value %(param)s for %(param_string)s is not a '
|
msg = _("Value '%(param)s' for '%(param_string)s' is not "
|
||||||
'boolean.') % {'param': param, 'param_string': param_string}
|
"a boolean.") % {'param': param, 'param_string': param_string}
|
||||||
raise exception.InvalidParameterValue(err=msg)
|
raise exception.InvalidParameterValue(err=msg)
|
||||||
|
|
||||||
return strutils.bool_from_string(param, strict=True)
|
return strutils.bool_from_string(param, strict=True)
|
||||||
|
@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
fixes:
|
||||||
|
- |
|
||||||
|
Fixed issue where ``create`` and ``update`` api's of ``volume-type`` and
|
||||||
|
``group_type`` were returning 500 error if boolean 'is_public' value
|
||||||
|
passed in the form of string. Now user can pass following valid boolean
|
||||||
|
values to these api's:
|
||||||
|
'0', 'f', 'false', 'off', 'n', 'no', '1', 't', 'true', 'on', 'y', 'yes'
|
Loading…
Reference in New Issue
Block a user