From e06506cbacffcf383b61ef0255996a008eaf68f6 Mon Sep 17 00:00:00 2001 From: "Jeffrey J. Walls" Date: Tue, 11 Mar 2014 18:35:06 +0000 Subject: [PATCH] Initial commit of a pecan/wsme shell service for graffiti. Implements a simple GET/POST service for a Resource. Nothing of significance, just getting an initial service up and running. Change-Id: I996aff7c0ba4e284dd6c8b681a800ef07ff10789 --- config.py | 67 +++++++++++++++++++++++++++++ graffiti/__init__.py | 19 -------- graffiti/app.py | 41 ++++++++++++++++++ graffiti/controllers/__init__.py | 0 graffiti/controllers/root.py | 34 +++++++++++++++ graffiti/controllers/v1/__init__.py | 0 graffiti/controllers/v1/resource.py | 57 ++++++++++++++++++++++++ graffiti/controllers/versions.py | 21 +++++++++ graffiti/hooks.py | 36 ++++++++++++++++ graffiti/model/__init__.py | 18 ++++++++ graffiti/model/v1/__init__.py | 0 graffiti/model/v1/capability.py | 31 +++++++++++++ graffiti/model/v1/property.py | 27 ++++++++++++ graffiti/model/v1/provider.py | 26 +++++++++++ graffiti/model/v1/requirement.py | 29 +++++++++++++ graffiti/model/v1/resource.py | 40 +++++++++++++++++ graffiti/service.py | 25 +++++++++++ requirements.txt | 4 +- 18 files changed, 455 insertions(+), 20 deletions(-) create mode 100644 config.py create mode 100644 graffiti/app.py create mode 100644 graffiti/controllers/__init__.py create mode 100644 graffiti/controllers/root.py create mode 100644 graffiti/controllers/v1/__init__.py create mode 100644 graffiti/controllers/v1/resource.py create mode 100644 graffiti/controllers/versions.py create mode 100644 graffiti/hooks.py create mode 100644 graffiti/model/__init__.py create mode 100644 graffiti/model/v1/__init__.py create mode 100644 graffiti/model/v1/capability.py create mode 100644 graffiti/model/v1/property.py create mode 100644 graffiti/model/v1/provider.py create mode 100644 graffiti/model/v1/requirement.py create mode 100644 graffiti/model/v1/resource.py create mode 100644 graffiti/service.py diff --git a/config.py b/config.py new file mode 100644 index 0000000..3c41906 --- /dev/null +++ b/config.py @@ -0,0 +1,67 @@ +# Copyright (c) 2014 Hewlett-Packard Development Company, L.P. +# +# 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. + +# Server Specific Configurations +server = { + 'port': '21075', + 'host': '0.0.0.0' +} + +# Pecan Application Configurations +app = { + 'root': 'graffiti.controllers.root.RootController', + 'modules': ['graffiti'], + 'static_root': '%(confdir)s/public', + 'template_path': '%(confdir)s/graffiti/templates', + 'debug': False, + 'errors': { + 404: '/error/404', + '__force_dict__': True + } +} + +logging = { + 'loggers': { + 'root': {'level': 'DEBUG', 'handlers': ['console']}, + 'graffiti': {'level': 'DEBUG', 'handlers': ['console']}, + 'wsme.api': {'level': 'DEBUG', 'handlers': ['console']}, + 'py.warnings': {'handlers': ['console']}, + '__force_dict__': True + }, + 'handlers': { + 'console': { + 'level': 'DEBUG', + 'class': 'logging.StreamHandler', + 'formatter': 'simple' + } + }, + 'formatters': { + 'simple': { + 'format': ('%(asctime)s %(levelname)-5.5s [%(name)s]' + '[%(threadName)s] %(message)s') + } + } +} + +wsme = { + 'debug': True +} + +# Custom Configurations must be in Python dictionary format:: +# +# foo = {'bar':'baz'} +# +# All configurations are accessible at:: +# pecan.conf diff --git a/graffiti/__init__.py b/graffiti/__init__.py index 0e151c2..e69de29 100644 --- a/graffiti/__init__.py +++ b/graffiti/__init__.py @@ -1,19 +0,0 @@ -# -*- coding: utf-8 -*- - -# 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 pbr.version - - -__version__ = pbr.version.VersionInfo( - 'graffiti').version_string() diff --git a/graffiti/app.py b/graffiti/app.py new file mode 100644 index 0000000..25a2ff5 --- /dev/null +++ b/graffiti/app.py @@ -0,0 +1,41 @@ +# Copyright (c) 2014 Hewlett-Packard Development Company, L.P. +# +# 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 pecan import make_app + +from graffiti import model +from graffiti.service import prepare_service + +from graffiti.hooks import CorsHook + +from oslo.config import cfg +CONF = cfg.CONF + + +def setup_app(config): + + model.init_model() + app_conf = dict(config.app) + + prepare_service() + + app_hooks = [CorsHook()] + + return make_app( + app_conf.pop('root'), + logging=getattr(config, 'logging', {}), + hooks=app_hooks, + **app_conf + ) diff --git a/graffiti/controllers/__init__.py b/graffiti/controllers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/graffiti/controllers/root.py b/graffiti/controllers/root.py new file mode 100644 index 0000000..a77d50a --- /dev/null +++ b/graffiti/controllers/root.py @@ -0,0 +1,34 @@ +# Copyright (c) 2014 Hewlett-Packard Development Company, L.P. +# +# 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 pecan import expose +from webob.exc import status_map + +from graffiti.controllers.versions import V1Controller + + +class RootController(object): + + v1 = V1Controller() + + @expose('error.html') + def error(self, status): + try: + status = int(status) + except ValueError as e: # pragma: no cover + print e + status = 500 + message = getattr(status_map.get(status), 'explanation', '') + return dict(status=status, message=message) diff --git a/graffiti/controllers/v1/__init__.py b/graffiti/controllers/v1/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/graffiti/controllers/v1/resource.py b/graffiti/controllers/v1/resource.py new file mode 100644 index 0000000..c3d70e0 --- /dev/null +++ b/graffiti/controllers/v1/resource.py @@ -0,0 +1,57 @@ +# Copyright (c) 2014 Hewlett-Packard Development Company, L.P. +# +# 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 pecan.rest import RestController + +from wsme.api import Response +from wsmeext.pecan import wsexpose + +from graffiti.model.v1.resource import Resource + +resources = [] + + +class ResourceController(RestController): + def __init__(self): + super(ResourceController, self).__init__() + + self.status = 200 + + @wsexpose() + def options(): + pass + + @wsexpose(Resource, unicode) + def get_one(self, id): + global resources + + for res in resources: + if res.id.lower() == id.lower(): + return res + + res = Response(Resource(), status_code=404, error="Resource Not Found") + return res + + @wsexpose([Resource]) + def get_all(self): + global resources + + return resources + + @wsexpose(Resource, Resource) + def post(self, resource): + global resources + + resources.append(resource) diff --git a/graffiti/controllers/versions.py b/graffiti/controllers/versions.py new file mode 100644 index 0000000..f2e297c --- /dev/null +++ b/graffiti/controllers/versions.py @@ -0,0 +1,21 @@ +# Copyright (c) 2014 Hewlett-Packard Development Company, L.P. +# +# 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 graffiti.controllers.v1.resource import ResourceController + + +class V1Controller(object): + + resource = ResourceController() diff --git a/graffiti/hooks.py b/graffiti/hooks.py new file mode 100644 index 0000000..389836c --- /dev/null +++ b/graffiti/hooks.py @@ -0,0 +1,36 @@ +# Copyright (c) 2014 Hewlett-Packard Development Company, L.P. +# +# 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 json + +from pecan.hooks import PecanHook + + +class CorsHook(PecanHook): + def after(self, state): + state.response.headers['Access-Control-Allow-Origin'] = '*' + state.response.headers['Access-Control-Allow-Methods'] = \ + 'GET, PUT, POST, DELETE, OPTIONS' + state.response.headers['Access-Control-Allow-Headers'] = \ + 'origin, authorization, accept, content-type' + + if not state.response.headers['Content-Length']: + state.response.headers['Content-Length'] = \ + str(len(state.response.body)) + + if state.response.headers['Content-Type'].find('json') != -1: + # Sort the Response Body's JSON + json_str = json.loads(state.response.body) + state.response.body = json.dumps(json_str, sort_keys=True) diff --git a/graffiti/model/__init__.py b/graffiti/model/__init__.py new file mode 100644 index 0000000..5b68b9e --- /dev/null +++ b/graffiti/model/__init__.py @@ -0,0 +1,18 @@ +# Copyright (c) 2014 Hewlett-Packard Development Company, L.P. +# +# 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. + + +def init_model(): + pass diff --git a/graffiti/model/v1/__init__.py b/graffiti/model/v1/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/graffiti/model/v1/capability.py b/graffiti/model/v1/capability.py new file mode 100644 index 0000000..b49d5b6 --- /dev/null +++ b/graffiti/model/v1/capability.py @@ -0,0 +1,31 @@ +# Copyright (c) 2014 Hewlett-Packard Development Company, L.P. +# +# 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 wsme +from wsme import types + +from graffiti.model.v1.property import Property + + +class Capability(types.Base): + properties = wsme.wsattr([Property], mandatory=True) + capability_type = wsme.wsattr(types.text, mandatory=True) + capability_type_namespace = wsme.wsattr(types.text, mandatory=True) + + _wsme_attr_order = ('properties', 'capability_type', + 'capability_type_namespace') + + def __init__(self, **kwargs): + super(Capability, self).__init__(**kwargs) diff --git a/graffiti/model/v1/property.py b/graffiti/model/v1/property.py new file mode 100644 index 0000000..19126b8 --- /dev/null +++ b/graffiti/model/v1/property.py @@ -0,0 +1,27 @@ +# Copyright (c) 2014 Hewlett-Packard Development Company, L.P. +# +# 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 wsme +from wsme import types + + +class Property(types.Base): + name = wsme.wsattr(types.text, mandatory=True) + value = wsme.wsattr(types.text, mandatory=True) + + _wsme_attr_order = ('name', 'value') + + def __init__(self, **kwargs): + super(Property, self).__init__(**kwargs) diff --git a/graffiti/model/v1/provider.py b/graffiti/model/v1/provider.py new file mode 100644 index 0000000..77f8457 --- /dev/null +++ b/graffiti/model/v1/provider.py @@ -0,0 +1,26 @@ +# Copyright (c) 2014 Hewlett-Packard Development Company, L.P. +# +# 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 wsme +from wsme import types + + +class Provider(types.Base): + id = wsme.wsattr(types.text, mandatory=True) + + _wsme_attr_order = ('id',) + + def __init__(self, **kwargs): + super(Provider, self).__init__(**kwargs) diff --git a/graffiti/model/v1/requirement.py b/graffiti/model/v1/requirement.py new file mode 100644 index 0000000..c140648 --- /dev/null +++ b/graffiti/model/v1/requirement.py @@ -0,0 +1,29 @@ +# Copyright (c) 2014 Hewlett-Packard Development Company, L.P. +# +# 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 wsme +from wsme import types + + +class Requirement(types.Base): + criterion = wsme.wsattr(types.text, mandatory=True) + capability_type = wsme.wsattr(types.text, mandatory=True) + capability_type_namespace = wsme.wsattr(types.text, mandatory=True) + + _wsme_attr_order = ('criterion', 'capability_type', + 'capability_type_namespace') + + def __init__(self, **kwargs): + super(Requirement, self).__init__(**kwargs) diff --git a/graffiti/model/v1/resource.py b/graffiti/model/v1/resource.py new file mode 100644 index 0000000..a5cf79a --- /dev/null +++ b/graffiti/model/v1/resource.py @@ -0,0 +1,40 @@ +# Copyright (c) 2014 Hewlett-Packard Development Company, L.P. +# +# 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 wsme +from wsme import types + +from graffiti.model.v1.capability import Capability +from graffiti.model.v1.property import Property +from graffiti.model.v1.provider import Provider +from graffiti.model.v1.requirement import Requirement + + +class Resource(types.Base): + id = wsme.wsattr(types.text, mandatory=True) + type = wsme.wsattr(types.text, mandatory=True) + name = wsme.wsattr(types.text, mandatory=True) + description = wsme.wsattr(types.text, mandatory=False) + provider = wsme.wsattr(Provider, mandatory=True) + properties = wsme.wsattr([Property], mandatory=False) + capabilities = wsme.wsattr([Capability], mandatory=True) + requirements = wsme.wsattr([Requirement], mandatory=True) + + _wsme_attr_order = ('id', 'name', 'description', 'type', + 'provider', 'properties', 'capabilities', + 'requirements') + + def __init__(self, **kwargs): + super(Resource, self).__init__(**kwargs) diff --git a/graffiti/service.py b/graffiti/service.py new file mode 100644 index 0000000..1cacfe5 --- /dev/null +++ b/graffiti/service.py @@ -0,0 +1,25 @@ +# Copyright (c) 2014 Hewlett-Packard Development Company, L.P. +# +# 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 sys + +from oslo.config import cfg + + +def prepare_service(argv=None): + if argv is None: + argv = sys.argv + + cfg.CONF(argv[3:], project='graffiti') diff --git a/requirements.txt b/requirements.txt index 8646341..5a37141 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,4 @@ pbr>=0.5.21,<1.0 -Babel>=0.9.6 \ No newline at end of file +Babel>=0.9.6 +pecan>=0.4.4 +WSME>=0.6