From 793be25ac9aa1a993f43b381eb00b11e6acf99ab Mon Sep 17 00:00:00 2001 From: vinod pandarinathan <vpandari@cisco.com> Date: Fri, 12 Jun 2015 10:44:55 -0700 Subject: [PATCH] Pecan based REST controller infra Implements: blueprint rest-controller Change-Id: I4f2f1465ba4436c0fb12372190a4cddfd59b3871 --- cloudpulse/api/controllers/__init__.py | 0 cloudpulse/api/controllers/base.py | 49 +++++++++++++ cloudpulse/api/controllers/link.py | 57 +++++++++++++++ cloudpulse/api/controllers/root.py | 97 ++++++++++++++++++++++++++ 4 files changed, 203 insertions(+) create mode 100644 cloudpulse/api/controllers/__init__.py create mode 100644 cloudpulse/api/controllers/base.py create mode 100644 cloudpulse/api/controllers/link.py create mode 100644 cloudpulse/api/controllers/root.py diff --git a/cloudpulse/api/controllers/__init__.py b/cloudpulse/api/controllers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/cloudpulse/api/controllers/base.py b/cloudpulse/api/controllers/base.py new file mode 100644 index 0000000..dda630c --- /dev/null +++ b/cloudpulse/api/controllers/base.py @@ -0,0 +1,49 @@ +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import datetime + +import wsme +from wsme import types as wtypes + + +class APIBase(wtypes.Base): + + # TBD + created_at = wsme.wsattr(datetime.datetime, readonly=True) + """The time in UTC at which the object is created""" + + # #TBD + updated_at = wsme.wsattr(datetime.datetime, readonly=True) + """The time in UTC at which the object is updated""" + + def as_dict(self): + """Render this object as a dict of its fields.""" + return dict((k, getattr(self, k)) + for k in self.fields + if hasattr(self, k) and + getattr(self, k) != wsme.Unset) + + def unset_fields_except(self, except_list=None): + """Unset fields so they don't appear in the message body. + + :param except_list: A list of fields that won't be touched. + + """ + if except_list is None: + except_list = [] + + for k in self.as_dict(): + if k not in except_list: + setattr(self, k, wsme.Unset) diff --git a/cloudpulse/api/controllers/link.py b/cloudpulse/api/controllers/link.py new file mode 100644 index 0000000..1a4e950 --- /dev/null +++ b/cloudpulse/api/controllers/link.py @@ -0,0 +1,57 @@ +# Copyright 2013 Red Hat, Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from cloudpulse.api.controllers import base +import pecan +from wsme import types as wtypes + + +def build_url(resource, resource_args, bookmark=False, base_url=None): + if base_url is None: + base_url = pecan.request.host_url + + template = '%(url)s/%(res)s' if bookmark else '%(url)s/v1/%(res)s' + # FIXME(lucasagomes): I'm getting a 404 when doing a GET on + # a nested resource that the URL ends with a '/'. + # https://groups.google.com/forum/#!topic/pecan-dev/QfSeviLg5qs + template += '%(args)s' if resource_args.startswith('?') else '/%(args)s' + return template % {'url': base_url, 'res': resource, 'args': resource_args} + + +class Link(base.APIBase): + """A link representation.""" + + href = wtypes.text + """The url of a link.""" + + rel = wtypes.text + """The name of a link.""" + + type = wtypes.text + """Indicates the type of document/link.""" + + @staticmethod + def make_link(rel_name, url, resource, resource_args, + bookmark=False, type=wtypes.Unset): + href = build_url(resource, resource_args, + bookmark=bookmark, base_url=url) + return Link(href=href, rel=rel_name, type=type) + + @classmethod + def sample(cls): + sample = cls(href="http://localhost:6385/chassis/" + "eaaca217-e7d8-47b4-bb41-3f99f20eed89", + rel="bookmark") + return sample diff --git a/cloudpulse/api/controllers/root.py b/cloudpulse/api/controllers/root.py new file mode 100644 index 0000000..60836a7 --- /dev/null +++ b/cloudpulse/api/controllers/root.py @@ -0,0 +1,97 @@ +# -*- encoding: utf-8 -*- +# +# Copyright © 2012 New Dream Network, LLC (DreamHost) +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import pecan +from pecan import rest +from wsme import types as wtypes +import wsmeext.pecan as wsme_pecan + +from cloudpulse.api.controllers import base +from cloudpulse.api.controllers import link +from cloudpulse.api.controllers import v1 + + +class Version(base.APIBase): + """An API version representation.""" + + id = wtypes.text + """The ID of the version, also acts as the release number""" + + links = [link.Link] + """A Link that point to a specific version of the API""" + + @staticmethod + def convert(id): + version = Version() + version.id = id + version.links = [link.Link.make_link('self', pecan.request.host_url, + id, '', bookmark=True)] + return version + + +class Root(base.APIBase): + + name = wtypes.text + """The name of the API""" + + description = wtypes.text + """Some information about this API""" + + versions = [Version] + """Links to all the versions available in this API""" + + default_version = Version + """A link to the default version of the API""" + + @staticmethod + def convert(): + root = Root() + root.name = "Cloud Pulse API" + root.description = ("Cloud Pulse is an OpenStack project which " + "aims to provide openstack health service.") + root.versions = [Version.convert('v1')] + root.default_version = Version.convert('v1') + return root + + +class RootController(rest.RestController): + + _versions = ['v1'] + """All supported API versions""" + + _default_version = 'v1' + """The default API version""" + + v1 = v1.Controller() + + @wsme_pecan.wsexpose(Root) + def get(self): + # NOTE: The reason why convert() it's being called for every + # request is because we need to get the host url from + # the request object to make the links. + return Root.convert() + + @pecan.expose() + def _route(self, args): + """Overrides the default routing behavior. + + It redirects the request to the default version of the cloudpulse API + if the version number is not specified in the url. + """ + + if args[0] and args[0] not in self._versions: + args = [self._default_version] + args + return super(RootController, self)._route(args)