Return 400 HTTP error for invalid flavor attributes

Currently create flavor API raises 500 error if you
pass large value to ram, disk, vcpu, swap, ephemeral,
rxtx_factor flavor properties. All integral flavor
properties are validated against db.MAX_INT(2147483647)
for maximum limit and raised exception.InvalidInput for
invalid input.

Added validation of maximum limit for flavor properties
in schema. Kept the validation of flavor properties as it
is in nova.compute.flavors as it is used by legacy flavor
create. Moved the SQL_SP_FLOAT_MAX constant to nova.db.api
so that it can be used in schema as well as in test files.

APIImpact: Return 400 status code for invalid flavor
properties.

Closes-Bug: #1577727
Change-Id: I4e50534d67ee90c585b6679644e06ee3569c8c97
This commit is contained in:
dineshbhor 2016-04-29 12:46:55 +00:00
parent 14d6a424ff
commit 9e00323621
6 changed files with 55 additions and 20 deletions

View File

@ -15,6 +15,7 @@
import copy
from nova.api.validation import parameter_types
from nova import db
create = {
'type': 'object',
@ -30,17 +31,18 @@ create = {
'minLength': 1, 'maxLength': 255,
'pattern': '^(?! )[a-zA-Z0-9. _-]+(?<! )$'
},
'ram': parameter_types.positive_integer,
'vcpus': parameter_types.positive_integer,
'disk': parameter_types.non_negative_integer,
'ram': parameter_types.flavor_param_positive,
'vcpus': parameter_types.flavor_param_positive,
'disk': parameter_types.flavor_param_non_negative,
'OS-FLV-EXT-DATA:ephemeral':
parameter_types.non_negative_integer,
'swap': parameter_types.non_negative_integer,
parameter_types.flavor_param_non_negative,
'swap': parameter_types.flavor_param_non_negative,
# positive ( > 0) float
'rxtx_factor': {
'type': ['number', 'string'],
'pattern': '^[0-9]+(\.[0-9]+)?$',
'minimum': 0, 'exclusiveMinimum': True
'minimum': 0, 'exclusiveMinimum': True,
'maximum': db.SQL_SP_FLOAT_MAX
},
'os-flavor-access:is_public': parameter_types.boolean,
},

View File

@ -356,3 +356,10 @@ volume_size = {
'minimum': 1,
'maximum': db.MAX_INT
}
flavor_param_positive = copy.deepcopy(volume_size)
flavor_param_non_negative = copy.deepcopy(volume_size)
flavor_param_non_negative['minimum'] = 0

View File

@ -40,15 +40,6 @@ CONF = nova.conf.CONF
# to ascii characters.
VALID_ID_REGEX = re.compile("^[\w\.\- ]*$")
# NOTE(dosaboy): This is supposed to represent the maximum value that we can
# place into a SQL single precision float so that we can check whether values
# are oversize. Postgres and MySQL both define this as their max whereas Sqlite
# uses dynamic typing so this would not apply. Different dbs react in different
# ways to oversize values e.g. postgres will raise an exception while mysql
# will round off the value. Nevertheless we may still want to know prior to
# insert whether the value is oversize.
SQL_SP_FLOAT_MAX = 3.40282e+38
# Validate extra specs key names.
VALID_EXTRASPEC_NAME_REGEX = re.compile(r"[\w\.\- :]+$", re.UNICODE)
@ -143,11 +134,11 @@ def create(name, memory, vcpus, root_gb, ephemeral_gb=0, flavorid=None,
try:
kwargs['rxtx_factor'] = float(kwargs['rxtx_factor'])
if (kwargs['rxtx_factor'] <= 0 or
kwargs['rxtx_factor'] > SQL_SP_FLOAT_MAX):
kwargs['rxtx_factor'] > db.SQL_SP_FLOAT_MAX):
raise ValueError()
except ValueError:
msg = (_("'rxtx_factor' argument must be a float between 0 and %g") %
SQL_SP_FLOAT_MAX)
db.SQL_SP_FLOAT_MAX)
raise exception.InvalidInput(reason=msg)
kwargs['name'] = name

View File

@ -47,6 +47,15 @@ LOG = logging.getLogger(__name__)
# The maximum value a signed INT type may have
MAX_INT = 0x7FFFFFFF
# NOTE(dosaboy): This is supposed to represent the maximum value that we can
# place into a SQL single precision float so that we can check whether values
# are oversize. Postgres and MySQL both define this as their max whereas Sqlite
# uses dynamic typing so this would not apply. Different dbs react in different
# ways to oversize values e.g. postgres will raise an exception while mysql
# will round off the value. Nevertheless we may still want to know prior to
# insert whether the value is oversize or not.
SQL_SP_FLOAT_MAX = 3.40282e+38
###################

View File

@ -23,6 +23,7 @@ import webob
from nova.api.openstack.compute import flavor_access as flavor_access_v21
from nova.api.openstack.compute import flavor_manage as flavormanage_v21
from nova.compute import flavors
from nova import db
from nova import exception
from nova import test
from nova.tests.unit.api.openstack import fakes
@ -250,6 +251,10 @@ class FlavorManageTestV21(test.NoDBTestCase):
self.request_body['flavor']['ram'] = 0
self._create_flavor_bad_request_case(self.request_body)
def test_create_with_ram_exceed_max_limit(self):
self.request_body['flavor']['ram'] = db.MAX_INT + 1
self._create_flavor_bad_request_case(self.request_body)
def test_create_without_vcpus(self):
del self.request_body['flavor']['vcpus']
self._create_flavor_bad_request_case(self.request_body)
@ -258,6 +263,10 @@ class FlavorManageTestV21(test.NoDBTestCase):
self.request_body['flavor']['vcpus'] = 0
self._create_flavor_bad_request_case(self.request_body)
def test_create_with_vcpus_exceed_max_limit(self):
self.request_body['flavor']['vcpus'] = db.MAX_INT + 1
self._create_flavor_bad_request_case(self.request_body)
def test_create_without_disk(self):
del self.request_body['flavor']['disk']
self._create_flavor_bad_request_case(self.request_body)
@ -266,18 +275,35 @@ class FlavorManageTestV21(test.NoDBTestCase):
self.request_body['flavor']['disk'] = -1
self._create_flavor_bad_request_case(self.request_body)
def test_create_with_disk_exceed_max_limit(self):
self.request_body['flavor']['disk'] = db.MAX_INT + 1
self._create_flavor_bad_request_case(self.request_body)
def test_create_with_minus_ephemeral(self):
self.request_body['flavor']['OS-FLV-EXT-DATA:ephemeral'] = -1
self._create_flavor_bad_request_case(self.request_body)
def test_create_with_ephemeral_exceed_max_limit(self):
self.request_body['flavor'][
'OS-FLV-EXT-DATA:ephemeral'] = db.MAX_INT + 1
self._create_flavor_bad_request_case(self.request_body)
def test_create_with_minus_swap(self):
self.request_body['flavor']['swap'] = -1
self._create_flavor_bad_request_case(self.request_body)
def test_create_with_swap_exceed_max_limit(self):
self.request_body['flavor']['swap'] = db.MAX_INT + 1
self._create_flavor_bad_request_case(self.request_body)
def test_create_with_minus_rxtx_factor(self):
self.request_body['flavor']['rxtx_factor'] = -1
self._create_flavor_bad_request_case(self.request_body)
def test_create_with_rxtx_factor_exceed_max_limit(self):
self.request_body['flavor']['rxtx_factor'] = db.SQL_SP_FLOAT_MAX * 2
self._create_flavor_bad_request_case(self.request_body)
def test_create_with_non_boolean_is_public(self):
self.request_body['flavor']['os-flavor-access:is_public'] = 123
self._create_flavor_bad_request_case(self.request_body)

View File

@ -353,14 +353,14 @@ class CreateInstanceTypeTest(test.TestCase):
db.flavor_get_all(_context)
# We do * 10 since this is an approximation and we need to make sure
# the difference is noticeble.
over_rxtx_factor = flavors.SQL_SP_FLOAT_MAX * 10
over_rxtx_factor = db.SQL_SP_FLOAT_MAX * 10
self.assertInvalidInput('flavor1', 64, 1, 120,
rxtx_factor=over_rxtx_factor)
flavor = flavors.create('flavor2', 64, 1, 120,
rxtx_factor=flavors.SQL_SP_FLOAT_MAX)
self.assertEqual(flavors.SQL_SP_FLOAT_MAX, flavor.rxtx_factor)
rxtx_factor=db.SQL_SP_FLOAT_MAX)
self.assertEqual(db.SQL_SP_FLOAT_MAX, flavor.rxtx_factor)
def test_is_public_must_be_valid_bool_string(self):
self.assertInvalidInput('flavor1', 64, 1, 120, is_public='foo')