[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