Adding validation of the api body

* cleaning up issues with the load instance(s)
* Moving over the validation from creating an instance
* making more __name__ and less "strings"
This commit is contained in:
Craig Vyvial 2012-03-15 14:29:29 -05:00
parent 8810955c47
commit f8602d1e90
8 changed files with 167 additions and 27 deletions

View File

@ -33,9 +33,10 @@ keystone --endpoint http://localhost:35357/v2.0 --token be19c524ddc92109a224 use
--user $REDDWARF_USER \ --user $REDDWARF_USER \
--role $REDDWARF_ROLE --role $REDDWARF_ROLE
# These are the values # These are the values
REDDWARF_TENANT=reddwarf #REDDWARF_TENANT=reddwarf
REDDWARF_TENANT=`keystone --endpoint http://localhost:35357/v2.0 --token be19c524ddc92109a224 tenant-list| grep reddwarf | cut -d ' ' -f 2`
echo $REDDWARF_TENANT echo $REDDWARF_TENANT
REDDWARF_USER=$(mysql keystone -e "select id from user where name='reddwarf';" | awk 'NR==2') REDDWARF_USER=`keystone --endpoint http://localhost:35357/v2.0 --token be19c524ddc92109a224 user-list| grep reddwarf | cut -d ' ' -f 2`
echo $REDDWARF_USER echo $REDDWARF_USER
REDDWARF_TOKEN=$(curl -d '{"auth":{"passwordCredentials":{"username": "reddwarf", "password": "REDDWARF-PASS"},"tenantName":"reddwarf"}}' -H "Content-type: application/json" http://localhost:35357/v2.0/tokens | python -mjson.tool | grep id | tr -s ' ' | cut -d ' ' -f 3 | sed s/\"/''/g | awk 'NR==2' | cut -d ',' -f 1) REDDWARF_TOKEN=$(curl -d '{"auth":{"passwordCredentials":{"username": "reddwarf", "password": "REDDWARF-PASS"},"tenantName":"reddwarf"}}' -H "Content-type: application/json" http://localhost:35357/v2.0/tokens | python -mjson.tool | grep id | tr -s ' ' | cut -d ' ' -f 3 | sed s/\"/''/g | awk 'NR==2' | cut -d ',' -f 1)
echo $REDDWARF_TOKEN echo $REDDWARF_TOKEN

View File

@ -22,7 +22,7 @@ import webob.exc
import wsgi import wsgi
LOG = logging.getLogger("reddwarf.common.auth") LOG = logging.getLogger(__name__)
class AuthorizationMiddleware(wsgi.Middleware): class AuthorizationMiddleware(wsgi.Middleware):

View File

@ -53,3 +53,8 @@ class GuestError(ReddwarfError):
message = _("An error occurred communicating with the guest: " message = _("An error occurred communicating with the guest: "
"%(original_message).") "%(original_message).")
class BadRequest(openstack_exception.MalformedRequestBody):
message = _("Required element/key - %(key)s was not specified")

View File

@ -45,6 +45,7 @@ def load_server(client, uuid):
try: try:
server = client.servers.get(uuid) server = client.servers.get(uuid)
except nova_exceptions.NotFound, e: except nova_exceptions.NotFound, e:
#TODO(cp16net) would this be the wrong id to show the user?
raise rd_exceptions.NotFound(uuid=uuid) raise rd_exceptions.NotFound(uuid=uuid)
except nova_exceptions.ClientException, e: except nova_exceptions.ClientException, e:
raise rd_exceptions.ReddwarfError(str(e)) raise rd_exceptions.ReddwarfError(str(e))
@ -55,6 +56,7 @@ def delete_server(client, server_id):
try: try:
client.servers.delete(server_id) client.servers.delete(server_id)
except nova_exceptions.NotFound, e: except nova_exceptions.NotFound, e:
#TODO(cp16net) would this be the wrong id to show the user?
raise rd_exceptions.NotFound(uuid=server_id) raise rd_exceptions.NotFound(uuid=server_id)
except nova_exceptions.ClientException, e: except nova_exceptions.ClientException, e:
raise rd_exceptions.ReddwarfError() raise rd_exceptions.ReddwarfError()
@ -173,20 +175,23 @@ class Instance(object):
return links return links
class Instances(Instance): class Instances(object):
def __init__(self, context):
#TODO(hub-cap): Fix this, this just cant be right
client = create_nova_client(context)
self._data_object = client.servers.list()
def __iter__(self):
for item in self._data_object:
yield item
@staticmethod @staticmethod
def load(context): def load(context):
raise Exception("Implement this!") if context is None:
raise TypeError("Argument context not defined.")
client = create_nova_client(context)
servers = client.servers.list()
db_infos = DBInstance.find_all()
ret = []
for db in db_infos:
status = InstanceServiceStatus.find_by(instance_id=db.id)
for server in servers:
if server.id == db.compute_instance_id:
ret.append(Instance(context, db, server, status))
break
return ret
class DatabaseModelBase(ModelBase): class DatabaseModelBase(ModelBase):
@ -220,7 +225,7 @@ class DatabaseModelBase(ModelBase):
@classmethod @classmethod
def find_by(cls, **conditions): def find_by(cls, **conditions):
model = cls.get_by(**conditions) model = cls.get_by(**conditions)
if model == None: if model is None:
raise ModelNotFoundError(_("%s Not Found") % cls.__name__) raise ModelNotFoundError(_("%s Not Found") % cls.__name__)
return model return model
@ -228,6 +233,10 @@ class DatabaseModelBase(ModelBase):
def get_by(cls, **kwargs): def get_by(cls, **kwargs):
return db.db_api.find_by(cls, **cls._process_conditions(kwargs)) return db.db_api.find_by(cls, **cls._process_conditions(kwargs))
@classmethod
def find_all(cls, **kwargs):
return db.db_query.find_all(cls, **cls._process_conditions(kwargs))
@classmethod @classmethod
def _process_conditions(cls, raw_conditions): def _process_conditions(cls, raw_conditions):
"""Override in inheritors to format/modify any conditions.""" """Override in inheritors to format/modify any conditions."""
@ -249,10 +258,10 @@ class DBInstance(DatabaseModelBase):
self.set_task_status(task_status) self.set_task_status(task_status)
def _validate(self, errors): def _validate(self, errors):
if self.task_status is None:
errors['task_status'] = "Cannot be none."
if InstanceTask.from_code(self.task_id) is None: if InstanceTask.from_code(self.task_id) is None:
errors['task_id'] = "Not valid." errors['task_id'] = "Not valid."
if self.task_status is None:
errors['task_status'] = "Cannot be none."
def get_task_status(self): def get_task_status(self):
return InstanceTask.from_code(self.task_id) return InstanceTask.from_code(self.task_id)

View File

@ -25,6 +25,7 @@ from reddwarf.common import exception
from reddwarf.common import utils from reddwarf.common import utils
from reddwarf.common import wsgi from reddwarf.common import wsgi
from reddwarf.instance import models, views from reddwarf.instance import models, views
from reddwarf.common import exception as rd_exceptions
CONFIG = config.Config CONFIG = config.Config
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
@ -39,6 +40,7 @@ class BaseController(wsgi.Controller):
], ],
webob.exc.HTTPBadRequest: [ webob.exc.HTTPBadRequest: [
models.InvalidModelError, models.InvalidModelError,
exception.BadRequest,
], ],
webob.exc.HTTPNotFound: [ webob.exc.HTTPNotFound: [
exception.NotFound, exception.NotFound,
@ -58,6 +60,23 @@ class BaseController(wsgi.Controller):
*self.exclude_attr)) *self.exclude_attr))
class api_validation:
""" api validation wrapper """
def __init__(self, action=None):
self.action = action
def __call__(self, f):
"""
Apply validation of the api body
"""
def wrapper(*args, **kwargs):
body = kwargs['body']
if self.action == 'create':
InstanceController._validate(body)
return f(*args, **kwargs)
return wrapper
class InstanceController(BaseController): class InstanceController(BaseController):
"""Controller for instance functionality""" """Controller for instance functionality"""
@ -65,6 +84,7 @@ class InstanceController(BaseController):
"""Return all instances.""" """Return all instances."""
LOG.info("req : '%s'\n\n" % req) LOG.info("req : '%s'\n\n" % req)
LOG.info("Detailing a database instance for tenant '%s'" % tenant_id) LOG.info("Detailing a database instance for tenant '%s'" % tenant_id)
#TODO(cp16net) return a detailed list instead of index
return self.index(req, tenant_id) return self.index(req, tenant_id)
def index(self, req, tenant_id): def index(self, req, tenant_id):
@ -94,6 +114,7 @@ class InstanceController(BaseController):
except exception.ReddwarfError, e: except exception.ReddwarfError, e:
# TODO(hub-cap): come up with a better way than # TODO(hub-cap): come up with a better way than
# this to get the message # this to get the message
LOG.error(e)
return wsgi.Result(str(e), 404) return wsgi.Result(str(e), 404)
# TODO(cp16net): need to set the return code correctly # TODO(cp16net): need to set the return code correctly
return wsgi.Result(views.InstanceView(server).data(), 201) return wsgi.Result(views.InstanceView(server).data(), 201)
@ -107,15 +128,14 @@ class InstanceController(BaseController):
context = rd_context.ReddwarfContext( context = rd_context.ReddwarfContext(
auth_tok=req.headers["X-Auth-Token"], auth_tok=req.headers["X-Auth-Token"],
tenant=tenant_id) tenant=tenant_id)
# TODO(cp16net) : need to handle exceptions here if the delete fails
models.Instance.delete(context=context, uuid=id) models.Instance.delete(context=context, uuid=id)
# TODO(cp16net): need to set the return code correctly # TODO(cp16net): need to set the return code correctly
return wsgi.Result(202) return wsgi.Result(202)
@api_validation(action="create")
def create(self, req, body, tenant_id): def create(self, req, body, tenant_id):
# find the service id (cant be done yet at startup due to # find the service id (cant be done yet at startup due to
# inconsitencies w/ the load app paste # inconsistencies w/ the load app paste
# TODO(hub-cap): figure out how to get this to work in __init__ time # TODO(hub-cap): figure out how to get this to work in __init__ time
# TODO(hub-cap): The problem with this in __init__ is that the paste # TODO(hub-cap): The problem with this in __init__ is that the paste
# config is generated w/ the same config file as the db flags that # config is generated w/ the same config file as the db flags that
@ -141,6 +161,97 @@ class InstanceController(BaseController):
#TODO(cp16net): need to set the return code correctly #TODO(cp16net): need to set the return code correctly
return wsgi.Result(views.InstanceView(instance).data(), 201) return wsgi.Result(views.InstanceView(instance).data(), 201)
@staticmethod
def _validate_empty_body(body):
"""Check that the body is not empty"""
if not body:
msg = "The request contains an empty body"
raise rd_exceptions.ReddwarfError(msg)
@staticmethod
def _validate_volume_size(size):
"""Validate the various possible errors for volume size"""
try:
volume_size = float(size)
except (ValueError, TypeError) as err:
LOG.error(err)
msg = ("Required element/key - instance volume"
"'size' was not specified as a number")
raise rd_exceptions.ReddwarfError(msg)
if int(volume_size) != volume_size or int(volume_size) < 1:
msg = ("Volume 'size' needs to be a positive "
"integer value, %s cannot be accepted."
% volume_size)
raise rd_exceptions.ReddwarfError(msg)
#TODO(cp16net) add in the volume validation when volumes are supported
# max_size = FLAGS.reddwarf_max_accepted_volume_size
# if int(volume_size) > max_size:
# msg = ("Volume 'size' cannot exceed maximum "
# "of %d Gb, %s cannot be accepted."
# % (max_size, volume_size))
# raise rd_exceptions.ReddwarfError(msg)
@staticmethod
def _validate(body):
"""Validate that the request has all the required parameters"""
InstanceController._validate_empty_body(body)
try:
body['instance']
body['instance']['flavorRef']
# TODO(cp16net) add in volume to the mix
# volume_size = body['instance']['volume']['size']
except KeyError as e:
LOG.error("Create Instance Required field(s) - %s" % e)
raise rd_exceptions.ReddwarfError("Required element/key - %s "
"was not specified" % e)
# Instance._validate_volume_size(volume_size)
@staticmethod
def _validate_resize_instance(body):
""" Validate that the resize body has the attributes for flavorRef """
try:
body['resize']
body['resize']['flavorRef']
except KeyError as e:
LOG.error("Resize Instance Required field(s) - %s" % e)
raise rd_exceptions.ReddwarfError("Required element/key - %s "
"was not specified" % e)
@staticmethod
def _validate_single_resize_in_body(body):
# Validate body resize does not have both volume and flavorRef
try:
resize = body['resize']
if 'volume' in resize and 'flavorRef' in resize:
msg = ("Not allowed to resize volume "
"and flavor at the same time")
LOG.error(msg)
raise rd_exceptions.ReddwarfError(msg)
except KeyError as e:
LOG.error("Resize Instance Required field(s) - %s" % e)
raise rd_exceptions.ReddwarfError("Required element/key - %s "
"was not specified" % e)
@staticmethod
def _validate_resize(body, old_volume_size):
"""
We are going to check that volume resizing data is present.
"""
InstanceController._validate_empty_body(body)
try:
body['resize']
body['resize']['volume']
new_volume_size = body['resize']['volume']['size']
except KeyError as e:
LOG.error("Resize Instance Required field(s) - %s" % e)
raise rd_exceptions.ReddwarfError("Required element/key - %s "
"was not specified" % e)
Instance._validate_volume_size(new_volume_size)
if int(new_volume_size) <= old_volume_size:
raise rd_exceptions.ReddwarfError("The new volume 'size' cannot "
"be less than the current volume size "
"of '%s'" % old_volume_size)
class API(wsgi.Router): class API(wsgi.Router):
"""API""" """API"""

View File

@ -578,7 +578,7 @@ class RequestDeserializer(object):
def deserialize_body(self, request, action): def deserialize_body(self, request, action):
if not len(request.body) > 0: if not len(request.body) > 0:
LOG.debug(_("Empty body provided in request")) LOG.debug(_("Empty body provided in request"))
return {} return self._return_empty_body(action)
try: try:
content_type = request.get_content_type() content_type = request.get_content_type()
@ -588,7 +588,7 @@ class RequestDeserializer(object):
if content_type is None: if content_type is None:
LOG.debug(_("No Content-Type provided in request")) LOG.debug(_("No Content-Type provided in request"))
return {} return self._return_empty_body(action)
try: try:
deserializer = self.get_body_deserializer(content_type) deserializer = self.get_body_deserializer(content_type)
@ -598,6 +598,12 @@ class RequestDeserializer(object):
return deserializer.deserialize(request.body, action) return deserializer.deserialize(request.body, action)
def _return_empty_body(self, action):
if action in ["create", "update", "action"]:
return {'body': None}
else:
return {}
def get_body_deserializer(self, content_type): def get_body_deserializer(self, content_type):
try: try:
return self.body_deserializers[content_type] return self.body_deserializers[content_type]

View File

@ -19,6 +19,7 @@ import novaclient
from reddwarf import tests from reddwarf import tests
from reddwarf.common import utils from reddwarf.common import utils
from reddwarf.common import exception
from reddwarf.instance import models from reddwarf.instance import models
from reddwarf.instance.tasks import InstanceTasks from reddwarf.instance.tasks import InstanceTasks
from reddwarf.tests.factories import models as factory_models from reddwarf.tests.factories import models as factory_models
@ -88,3 +89,9 @@ class TestInstance(tests.BaseTest):
self.assertEqual(instance['task_id'], InstanceTasks.BUILDING.code) self.assertEqual(instance['task_id'], InstanceTasks.BUILDING.code)
self.assertEqual(instance['task_description'], self.assertEqual(instance['task_description'],
InstanceTasks.BUILDING.db_text) InstanceTasks.BUILDING.db_text)
def test_create_instance_data_without_flavorref(self):
#todo(cp16net) fix this to work with the factory
self.mock_out_client()
self.FAKE_SERVER.flavor = None
self.assertRaises(exception.BadRequest, factory_models.Instance())

View File

@ -55,11 +55,12 @@ class TestInstanceController(ControllerTestBase):
super(TestInstanceController, self).setUp() super(TestInstanceController, self).setUp()
# TODO(hub-cap): Start testing the failure cases # TODO(hub-cap): Start testing the failure cases
# def test_show_broken(self): def test_show_broken(self):
# response = self.app.get("%s/%s" % (self.instances_path, raise SkipTest()
# self.DUMMY_INSTANCE_ID), response = self.app.get("%s/%s" % (self.instances_path,
# headers={'X-Auth-Token': '123'}) self.DUMMY_INSTANCE_ID),
# self.assertEqual(response.status_int, 404) headers={'X-Auth-Token': '123'})
self.assertEqual(response.status_int, 404)
def test_show(self): def test_show(self):
raise SkipTest() raise SkipTest()