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:
parent
8810955c47
commit
f8602d1e90
@ -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
|
||||||
|
@ -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):
|
||||||
|
@ -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")
|
||||||
|
@ -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)
|
||||||
|
@ -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"""
|
||||||
|
@ -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]
|
||||||
|
@ -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())
|
||||||
|
@ -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()
|
||||||
|
Loading…
Reference in New Issue
Block a user