[Kimchi-devel] [PATCH 04/16] Move basic controller resources to control/base.py
Aline Manera
alinefm at linux.vnet.ibm.com
Thu Dec 26 18:34:15 UTC 2013
On 12/26/2013 04:47 AM, Shu Ming wrote:
> As the other python files named after the class name contained, like
> vms.py vs class VMs, templates.py vs class Templates. We should name
> this file as "collection.py" instead of "base.py".
In fact the file names refer to the kimchi uri:
in vms.py will contain all related to /vms
> 2013/12/24 2:41, Aline Manera:
>> From: Aline Manera <alinefm at br.ibm.com>
>>
>> Resource(), Collection() and AsyncCollection classes are base for all
>> Kimchi
>> resources.
>> Move them to a separated file as they should not be changed with high
>> frequency
>>
>> Signed-off-by: Aline Manera <alinefm at br.ibm.com>
>> ---
>> Makefile.am | 1 +
>> src/kimchi/control/base.py | 290
>> ++++++++++++++++++++++++++++++++++++++++++++
>> 2 files changed, 291 insertions(+)
>> create mode 100644 src/kimchi/control/base.py
>>
>> diff --git a/Makefile.am b/Makefile.am
>> index 65b39b8..997d4cc 100644
>> --- a/Makefile.am
>> +++ b/Makefile.am
>> @@ -45,6 +45,7 @@ PEP8_WHITELIST = \
>> src/kimchi/disks.py \
>> src/kimchi/root.py \
>> src/kimchi/server.py \
>> + src/kimchi/control/base.py \
>> src/kimchi/control/utils.py \
>> plugins/__init__.py \
>> plugins/sample/__init__.py \
>> diff --git a/src/kimchi/control/base.py b/src/kimchi/control/base.py
>> new file mode 100644
>> index 0000000..22452fc
>> --- /dev/null
>> +++ b/src/kimchi/control/base.py
>> @@ -0,0 +1,290 @@
>> +#
>> +# Project Kimchi
>> +#
>> +# Copyright IBM, Corp. 2013
>> +#
>> +# Authors:
>> +# Adam Litke <agl at linux.vnet.ibm.com>
>> +# Aline Manera <alinefm at linix.vnet.ibm.com>
>> +#
>> +# This library is free software; you can redistribute it and/or
>> +# modify it under the terms of the GNU Lesser General Public
>> +# License as published by the Free Software Foundation; either
>> +# version 2.1 of the License, or (at your option) any later version.
>> +#
>> +# This library is distributed in the hope that it will be useful,
>> +# but WITHOUT ANY WARRANTY; without even the implied warranty of
>> +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
>> +# Lesser General Public License for more details.
>> +#
>> +# You should have received a copy of the GNU Lesser General Public
>> +# License along with this library; if not, write to the Free Software
>> +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
>> 02110-1301 USA
>> +
>> +import cherrypy
>> +import urllib2
>> +
>> +
>> +import kimchi.template
>> +from kimchi.control.utils import get_class_name, internal_redirect,
>> model_fn
>> +from kimchi.control.utils import parse_request, validate_method
>> +from kimchi.control.utils import validate_params
>> +from kimchi.exception import InvalidOperation, InvalidParameter
>> +from kimchi.exception import MissingParameter, NotFoundError,
>> OperationFailed
>> +
>> +
>> +class Resource(object):
>> + """
>> + A Resource represents a single entity in the API (such as a Virtual
>> + Machine)
>> +
>> + To create new Resource types, subclass this and change the
>> following things
>> + in the child class:
>> +
>> + - If the Resource requires more than one identifier set
>> self.model_args as
>> + appropriate. This should only be necessary if this Resource
>> is logically
>> + nested. For example: A Storage Volume belongs to a Storage
>> Pool so the
>> + Storage Volume would set model args to (pool_ident,
>> volume_ident).
>> +
>> + - Implement the base operations of 'lookup' and 'delete' in the
>> model(s).
>> +
>> + - Set the 'data' property to a JSON-serializable representation
>> of the
>> + Resource.
>> + """
>> + def __init__(self, model, ident=None):
>> + self.model = model
>> + self.ident = ident
>> + self.model_args = (ident,)
>> + self.update_params = []
>> +
>> + def generate_action_handler(self, instance, action_name,
>> action_args=None):
>> + def wrapper(*args, **kwargs):
>> + validate_method(('POST'))
>> + try:
>> + model_args = list(instance.model_args)
>> + if action_args is not None:
>> + model_args.extend(parse_request()[key]
>> + for key in action_args)
>> + fn = getattr(instance.model, model_fn(instance,
>> action_name))
>> + fn(*model_args)
>> + uri_params = tuple(instance.model_args)
>> + raise internal_redirect(instance.uri_fmt % uri_params)
>> + except MissingParameter, param:
>> + error = "Missing parameter: '%s'" % param
>> + raise cherrypy.HTTPError(400, error)
>> + except InvalidParameter, param:
>> + error = "Invalid parameter: '%s'" % param
>> + raise cherrypy.HTTPError(400, error)
>> + except InvalidOperation, msg:
>> + raise cherrypy.HTTPError(400, "Invalid operation:
>> '%s'" % msg)
>> + except OperationFailed, msg:
>> + raise cherrypy.HTTPError(500, "Operation Failed:
>> '%s'" % msg)
>> + except NotFoundError, msg:
>> + raise cherrypy.HTTPError(404, "Not found: '%s'" % msg)
>> +
>> + wrapper.__name__ = action_name
>> + wrapper.exposed = True
>> + return wrapper
>> +
>> + def lookup(self):
>> + try:
>> + lookup = getattr(self.model, model_fn(self, 'lookup'))
>> + self.info = lookup(*self.model_args)
>> + except AttributeError:
>> + self.info = {}
>> +
>> + def delete(self):
>> + try:
>> + fn = getattr(self.model, model_fn(self, 'delete'))
>> + fn(*self.model_args)
>> + cherrypy.response.status = 204
>> + except AttributeError:
>> + error = "Delete is not allowed for %s" %
>> get_class_name(self)
>> + raise cherrypy.HTTPError(405, error)
>> + except OperationFailed, msg:
>> + raise cherrypy.HTTPError(500, "Operation Failed: '%s'" %
>> msg)
>> + except InvalidOperation, msg:
>> + raise cherrypy.HTTPError(400, "Invalid operation: '%s'"
>> % msg)
>> +
>> + @cherrypy.expose
>> + def index(self):
>> + method = validate_method(('GET', 'DELETE', 'PUT'))
>> + if method == 'GET':
>> + try:
>> + return self.get()
>> + except NotFoundError, msg:
>> + raise cherrypy.HTTPError(404, "Not found: '%s'" % msg)
>> + except InvalidOperation, msg:
>> + raise cherrypy.HTTPError(400, "Invalid operation:
>> '%s'" % msg)
>> + except OperationFailed, msg:
>> + raise cherrypy.HTTPError(406, "Operation failed:
>> '%s'" % msg)
>> + elif method == 'DELETE':
>> + try:
>> + return self.delete()
>> + except NotFoundError, msg:
>> + raise cherrypy.HTTPError(404, "Not found: '%s'" % msg)
>> + elif method == 'PUT':
>> + try:
>> + return self.update()
>> + except InvalidParameter, msg:
>> + raise cherrypy.HTTPError(400, "Invalid parameter:
>> '%s'" % msg)
>> + except InvalidOperation, msg:
>> + raise cherrypy.HTTPError(400, "Invalid operation:
>> '%s'" % msg)
>> + except NotFoundError, msg:
>> + raise cherrypy.HTTPError(404, "Not found: '%s'" % msg)
>> +
>> + def update(self):
>> + try:
>> + update = getattr(self.model, model_fn(self, 'update'))
>> + except AttributeError:
>> + error = "%s does not implement update method"
>> + raise cherrypy.HTTPError(405, error % get_class_name(self))
>> +
>> + params = parse_request()
>> + validate_params(params, self, 'update')
>> +
>> + if self.update_params is not None:
>> + invalids = [v for v in params.keys() if
>> + v not in self.update_params]
>> + if invalids:
>> + error = "%s are not allowed to be updated" % invalids
>> + raise cherrypy.HTTPError(405, error)
>> +
>> + ident = update(self.ident, params)
>> + if ident != self.ident:
>> + uri_params = list(self.model_args[:-1])
>> + uri_params += [urllib2.quote(ident.encode('utf8'))]
>> + raise cherrypy.HTTPRedirect(self.uri_fmt %
>> tuple(uri_params), 303)
>> +
>> + return self.get()
>> +
>> + def get(self):
>> + self.lookup()
>> + return kimchi.template.render(get_class_name(self), self.data)
>> +
>> + @property
>> + def data(self):
>> + """
>> + Override this in inherited classes to provide the Resource
>> + representation as a python dictionary.
>> + """
>> + return {}
>> +
>> +
>> +class Collection(object):
>> + """
>> + A Collection is a container for Resource objects. To create a new
>> + Collection type, subclass this and make the following changes to
>> the child
>> + class:
>> +
>> + - Set self.resource to the type of Resource that this Collection
>> contains
>> +
>> + - Set self.resource_args. This can remain an empty list if the
>> Resources
>> + can be initialized with only one identifier. Otherwise, include
>> + additional values as needed (eg. to identify a parent resource).
>> +
>> + - Set self.model_args. Similar to above, this is needed only if
>> the model
>> + needs additional information to identify this Collection.
>> +
>> + - Implement the base operations of 'create' and 'get_list' in
>> the model.
>> + """
>> + def __init__(self, model):
>> + self.model = model
>> + self.resource = Resource
>> + self.resource_args = []
>> + self.model_args = []
>> +
>> + def create(self, *args):
>> + try:
>> + create = getattr(self.model, model_fn(self, 'create'))
>> + except AttributeError:
>> + error = 'Create is not allowed for %s' %
>> get_class_name(self)
>> + raise cherrypy.HTTPError(405, error)
>> +
>> + params = parse_request()
>> + validate_params(params, self, 'create')
>> + args = self.model_args + [params]
>> + name = create(*args)
>> + cherrypy.response.status = 201
>> + args = self.resource_args + [name]
>> + res = self.resource(self.model, *args)
>> +
>> + return res.get()
>> +
>> + def _get_resources(self):
>> + try:
>> + get_list = getattr(self.model, model_fn(self, 'get_list'))
>> + idents = get_list(*self.model_args)
>> + res_list = []
>> + for ident in idents:
>> + # internal text, get_list changes ident to unicode
>> for sorted
>> + args = self.resource_args + [ident]
>> + res = self.resource(self.model, *args)
>> + res.lookup()
>> + res_list.append(res)
>> + return res_list
>> + except AttributeError:
>> + return []
>> +
>> + def _cp_dispatch(self, vpath):
>> + if vpath:
>> + ident = vpath.pop(0)
>> + # incoming text, from URL, is not unicode, need decode
>> + args = self.resource_args + [ident.decode("utf-8")]
>> + return self.resource(self.model, *args)
>> +
>> + def get(self):
>> + resources = self._get_resources()
>> + data = []
>> + for res in resources:
>> + data.append(res.data)
>> + return kimchi.template.render(get_class_name(self), data)
>> +
>> + @cherrypy.expose
>> + def index(self, *args):
>> + method = validate_method(('GET', 'POST'))
>> + if method == 'GET':
>> + try:
>> + return self.get()
>> + except InvalidOperation, param:
>> + error = "Invalid operation: '%s'" % param
>> + raise cherrypy.HTTPError(400, error)
>> + except NotFoundError, param:
>> + raise cherrypy.HTTPError(404, "Not found: '%s'" %
>> param)
>> +
>> + elif method == 'POST':
>> + try:
>> + return self.create(*args)
>> + except MissingParameter, param:
>> + error = "Missing parameter: '%s'" % param
>> + raise cherrypy.HTTPError(400, error)
>> + except InvalidParameter, param:
>> + error = "Invalid parameter: '%s'" % param
>> + raise cherrypy.HTTPError(400, error)
>> + except OperationFailed, param:
>> + raise cherrypy.HTTPError(500, "Operation Failed:
>> '%s'" % param)
>> + except InvalidOperation, param:
>> + error = "Invalid operation: '%s'" % param
>> + raise cherrypy.HTTPError(400, error)
>> + except NotFoundError, param:
>> + raise cherrypy.HTTPError(404, "Not found: '%s'" %
>> param)
>> +
>> +
>> +class AsyncCollection(Collection):
>> + """
>> + A Collection to create it's resource by asynchronous task
>> + """
>> + def __init__(self, model):
>> + super(AsyncCollection, self).__init__(model)
>> +
>> + def create(self, *args):
>> + try:
>> + create = getattr(self.model, model_fn(self, 'create'))
>> + except AttributeError:
>> + raise cherrypy.HTTPError(405,
>> + 'Create is not allowed for %s' % get_class_name(self))
>> + params = parse_request()
>> + args = self.model_args + [params]
>> + task = create(*args)
>> + cherrypy.response.status = 202
>> + return kimchi.template.render("Task", task)
>
More information about the Kimchi-devel
mailing list