[Kimchi-devel] [PATCH 04/16] Move basic controller resources to control/base.py

Shu Ming shuming at linux.vnet.ibm.com
Thu Dec 26 06:47:12 UTC 2013


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".
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