Fix DbError when image params are out of range

Image params such as checksum, min_ram and min_disk are validated.
Checksum is expected to be not longer than 32 characters,
min_ram/min_disk can't exceed pow(2, 31) - 1.
A method for mysql_int validation has been moved to glance.common.utils.

Change-Id: I78554ef1ba65fc11da3de56467d6d70bb7991787
Closes-Bug: #1454730
Closes-Bug: #1460060
This commit is contained in:
Inessa Vasilevskaya 2015-05-13 17:44:47 +03:00
parent 4efb56aae9
commit 1badf69b01
5 changed files with 119 additions and 9 deletions

View File

@ -65,9 +65,12 @@ CONF.import_opt('container_formats', 'glance.common.config',
CONF.import_opt('image_property_quota', 'glance.common.config')
def validate_image_meta(req, values):
def _validate_format(req, values):
"""Validates disk_format and container_format fields
name = values.get('name')
Introduced to split too complex validate_image_meta method.
"""
amazon_formats = ('aki', 'ari', 'ami')
disk_format = values.get('disk_format')
container_format = values.get('container_format')
@ -82,13 +85,7 @@ def validate_image_meta(req, values):
"for image.") % container_format
raise HTTPBadRequest(explanation=msg, request=req)
if name and len(name) > 255:
msg = _('Image name too long: %d') % len(name)
raise HTTPBadRequest(explanation=msg, request=req)
amazon_formats = ('aki', 'ari', 'ami')
if disk_format in amazon_formats or container_format in amazon_formats:
if any(f in amazon_formats for f in [disk_format, container_format]):
if disk_format is None:
values['disk_format'] = container_format
elif container_format is None:
@ -100,6 +97,25 @@ def validate_image_meta(req, values):
"and disk formats must match."))
raise HTTPBadRequest(explanation=msg, request=req)
def validate_image_meta(req, values):
_validate_format(req, values)
name = values.get('name')
checksum = values.get('checksum')
if name and len(name) > 255:
msg = _('Image name too long: %d') % len(name)
raise HTTPBadRequest(explanation=msg, request=req)
# check that checksum retrieved is exactly 32 characters
# as long as we expect md5 checksum
# https://bugs.launchpad.net/glance/+bug/1454730
if checksum and len(checksum) > 32:
msg = (_("Invalid checksum '%s': can't exceed 32 characters") %
checksum)
raise HTTPBadRequest(explanation=msg, request=req)
return values

View File

@ -715,6 +715,31 @@ def no_4byte_params(f):
return wrapper
def validate_mysql_int(*args, **kwargs):
"""
Make sure that all arguments are less than 2 ** 31 - 1.
This limitation is introduced because mysql stores INT in 4 bytes.
If the validation fails for some argument, exception.Invalid is raised with
appropriate information.
"""
max_int = (2 ** 31) - 1
for param in args:
if param > max_int:
msg = _("Value %(value)d out of range, "
"must not exceed %(max)d") % {"value": param,
"max": max_int}
raise exception.Invalid(msg)
for param_str in kwargs:
param = kwargs.get(param_str)
if param and param > max_int:
msg = _("'%(param)s' value out of range, "
"must not exceed %(max)d") % {"param": param_str,
"max": max_int}
raise exception.Invalid(msg)
def stash_conf_values():
"""
Make a copy of some of the current global CONF's settings.

View File

@ -675,6 +675,10 @@ def _validate_image(values):
msg = "Invalid image status '%s' for image." % status
raise exception.Invalid(msg)
# validate integer values to eliminate DBError on save
utils.validate_mysql_int(min_disk=values.get('min_disk'),
min_ram=values.get('min_ram'))
return values

View File

@ -222,6 +222,25 @@ class DriverTests(object):
self.assertRaises(exception.Invalid, self.db_api.image_create,
self.context, fixture)
def test_image_create_bad_checksum(self):
# checksum should be no longer than 32 characters
bad_checksum = "42" * 42
fixture = {'checksum': bad_checksum}
self.assertRaises(exception.Invalid, self.db_api.image_create,
self.context, fixture)
# if checksum is not longer than 32 characters but non-ascii ->
# still raise 400
fixture = {'checksum': u'\u042f' * 32}
self.assertRaises(exception.Invalid, self.db_api.image_create,
self.context, fixture)
def test_image_create_bad_int_params(self):
int_too_long = 2 ** 31 + 42
for param in ['min_disk', 'min_ram']:
fixture = {param: int_too_long}
self.assertRaises(exception.Invalid, self.db_api.image_create,
self.context, fixture)
def test_image_create_bad_property(self):
# bad value
fixture = {'status': 'queued',

View File

@ -18,6 +18,8 @@
import hashlib
import httplib2
import sys
from oslo_serialization import jsonutils
from oslo_utils import units
# NOTE(jokke): simplified transition to py3, behaves like py2 xrange
@ -35,6 +37,50 @@ class TestApi(functional.FunctionalTest):
"""Functional tests using httplib2 against the API server"""
def _check_image_create(self, headers, status=201,
image_data="*" * FIVE_KB):
# performs image_create request, checks the response and returns
# content
http = httplib2.Http()
path = "http://%s:%d/v1/images" % ("127.0.0.1", self.api_port)
response, content = http.request(
path, 'POST', headers=headers, body=image_data)
self.assertEqual(status, response.status)
return content
def test_checksum_32_chars_at_image_create(self):
self.cleanup()
self.start_servers(**self.__dict__.copy())
headers = minimal_headers('Image1')
image_data = "*" * FIVE_KB
# checksum can be no longer that 32 characters (String(32))
headers['X-Image-Meta-Checksum'] = 'x' * 42
content = self._check_image_create(headers, 400)
self.assertIn("Invalid checksum", content)
# test positive case as well
headers['X-Image-Meta-Checksum'] = hashlib.md5(image_data).hexdigest()
self._check_image_create(headers)
def test_param_int_too_large_at_create(self):
# currently 2 params min_disk/min_ram can cause DBError on save
self.cleanup()
self.start_servers(**self.__dict__.copy())
# Integer field can't be greater than max 8-byte signed integer
for param in ['min_disk', 'min_ram']:
headers = minimal_headers('Image1')
# check that long numbers result in 400
headers['X-Image-Meta-%s' % param] = str(sys.maxint + 1)
content = self._check_image_create(headers, 400)
self.assertIn("'%s' value out of range" % param, content)
# check that integers over 4 byte result in 400
headers['X-Image-Meta-%s' % param] = str(2 ** 31)
content = self._check_image_create(headers, 400)
self.assertIn("'%s' value out of range" % param, content)
# verify positive case as well
headers['X-Image-Meta-%s' % param] = str((2 ** 31) - 1)
self._check_image_create(headers)
@skip_if_disabled
def test_get_head_simple_post(self):
"""