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)