From c76114ea23278d3d1539f8b78fb4991caca2f943 Mon Sep 17 00:00:00 2001 From: Vladislav Kuzmin Date: Fri, 26 Dec 2014 14:07:32 +0400 Subject: [PATCH] Add API prototype on Pecan Add API prototype on Pecan. Add functioanal tests. Update documentation for running new API. Delete old API and unit tests for it. Change-Id: I42b3539d8420b2b5edf44d5265654b738e7ffa5e --- doc/refstack.md | 9 +-- refstack/api/__init__.py | 0 refstack/{tests/unit/tests.py => api/app.py} | 21 +++--- refstack/api/config.py | 63 +++++++++++++++++ refstack/api/controllers/__init__.py | 0 refstack/api/controllers/root.py | 28 ++++++++ refstack/api/controllers/v1.py | 32 +++++++++ refstack/tests/api/__init__.py | 72 ++++++++++++++++++++ refstack/tests/api/test_api.py | 29 ++++++++ refstack/tests/unit/__init__.py | 1 - requirements.txt | 1 + 11 files changed, 239 insertions(+), 17 deletions(-) create mode 100644 refstack/api/__init__.py rename refstack/{tests/unit/tests.py => api/app.py} (66%) mode change 100755 => 100644 create mode 100644 refstack/api/config.py create mode 100644 refstack/api/controllers/__init__.py create mode 100644 refstack/api/controllers/root.py create mode 100644 refstack/api/controllers/v1.py create mode 100644 refstack/tests/api/__init__.py create mode 100644 refstack/tests/api/test_api.py delete mode 100644 refstack/tests/unit/__init__.py diff --git a/doc/refstack.md b/doc/refstack.md index e1161f91..bba13671 100755 --- a/doc/refstack.md +++ b/doc/refstack.md @@ -38,10 +38,7 @@ We use nginx and gunicorn, you may use something else if you so desire. For the most basic setup that you can try right now, just kick off gunicorn: -`gunicorn -b 0.0.0.0:8000 refstack.web:app` +`gunicorn_pecan refstack/api/config.py` -To actually configure refstack, check out the config section and -crack open refstack.cfg in your preffered editor. -`vim refstack.cfg` - -Now browse to http://localhost:8000 +Now available http://localhost:8000/ with JSON response {'Root': 'OK'} +and http://localhost:8000/v1/results/ with JSON response {'Results': 'OK'}. diff --git a/refstack/api/__init__.py b/refstack/api/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/refstack/tests/unit/tests.py b/refstack/api/app.py old mode 100755 new mode 100644 similarity index 66% rename from refstack/tests/unit/tests.py rename to refstack/api/app.py index bb8d3ead..b9a7f783 --- a/refstack/tests/unit/tests.py +++ b/refstack/api/app.py @@ -1,5 +1,5 @@ -# -# Copyright (c) 2014 Piston Cloud Computing, Inc. All Rights Reserved. +# Copyright (c) 2015 Mirantis, 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 @@ -12,15 +12,16 @@ # 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 unittest + +from pecan import make_app -class TestSequenceFunctions(unittest.TestCase): +def setup_app(config): - def test_nothing(self): - # make sure the shuffled sequence does not lose any elements - pass + app_conf = dict(config.app) -if __name__ == '__main__': - unittest.main() + return make_app( + app_conf.pop('root'), + logging=getattr(config, 'logging', {}), + **app_conf + ) diff --git a/refstack/api/config.py b/refstack/api/config.py new file mode 100644 index 00000000..5904e332 --- /dev/null +++ b/refstack/api/config.py @@ -0,0 +1,63 @@ +# Copyright (c) 2015 Mirantis, 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. + +"""Configuration for running API. + +Custom Configurations must be in Python dictionary format: + +foo = {'bar':'baz'} + +All configurations are accessible at: +pecan.conf +""" + +# Server Specific Configurations +server = { + 'port': '8000', + 'host': '0.0.0.0' +} + +# Pecan Application Configurations +app = { + 'root': 'refstack.api.controllers.root.RootController', + 'modules': ['refstack.api'], + 'static_root': '%(confdir)s/public', + 'template_path': '%(confdir)s/${package}/templates', + 'debug': False, + 'errors': { + '404': '/error/404', + '__force_dict__': True + } +} + +logging = { + 'loggers': { + 'root': {'level': 'INFO', 'handlers': ['console']}, + 'refstack': {'level': 'DEBUG', 'handlers': ['console']} + }, + 'handlers': { + 'console': { + 'level': 'DEBUG', + 'class': 'logging.StreamHandler', + 'formatter': 'simple' + } + }, + 'formatters': { + 'simple': { + 'format': ('%(asctime)s %(levelname)-5.5s [%(name)s]' + '[%(threadName)s] %(message)s') + } + } +} diff --git a/refstack/api/controllers/__init__.py b/refstack/api/controllers/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/refstack/api/controllers/root.py b/refstack/api/controllers/root.py new file mode 100644 index 00000000..ec414444 --- /dev/null +++ b/refstack/api/controllers/root.py @@ -0,0 +1,28 @@ +# Copyright (c) 2015 Mirantis, 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 pecan import expose + +from refstack.api.controllers import v1 + + +class RootController(object): + + v1 = v1.V1Controller() + + @expose('json') + def index(self): + """root response.""" + return {'Root': 'OK'} diff --git a/refstack/api/controllers/v1.py b/refstack/api/controllers/v1.py new file mode 100644 index 00000000..9b9fe4ca --- /dev/null +++ b/refstack/api/controllers/v1.py @@ -0,0 +1,32 @@ +# Copyright (c) 2015 Mirantis, 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. + +"""Version 1 of the API. +""" +from pecan import expose +from pecan import rest + + +class ResultsController(rest.RestController): + + @expose('json') + def index(self): + return {'Results': 'OK'} + + +class V1Controller(object): + """Version 1 API controller root.""" + + results = ResultsController() diff --git a/refstack/tests/api/__init__.py b/refstack/tests/api/__init__.py new file mode 100644 index 00000000..7b1a6574 --- /dev/null +++ b/refstack/tests/api/__init__.py @@ -0,0 +1,72 @@ +# Copyright (c) 2015 Mirantis, 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. + +"""Base classes for API tests. +""" +from unittest import TestCase +from pecan import set_config +from pecan.testing import load_test_app + + +class FunctionalTest(TestCase): + """ + Used for functional tests where you need to test your + literal application and its integration with the framework. + """ + + def setUp(self): + self.config = { + 'app': { + 'root': 'refstack.api.controllers.root.RootController', + 'modules': ['refstack.api'], + 'static_root': '%(confdir)s/public', + 'template_path': '%(confdir)s/${package}/templates', + } + } + self.app = load_test_app(self.config) + + def tearDown(self): + set_config({}, overwrite=True) + + def get_json(self, url, headers=None, extra_environ=None, + status=None, expect_errors=False, **params): + """Sends HTTP GET request. + + :param url: url path to target service + :param headers: a dictionary of extra headers to send + :param extra_environ: a dictionary of environmental variables that + should be added to the request + :param status: integer or string of the HTTP status code you expect + in response (if not 200 or 3xx). You can also use a + wildcard, like '3*' or '*' + :param expect_errors: boolean value, if this is False, then if + anything is written to environ wsgi.errors it + will be an error. If it is True, then + non-200/3xx responses are also okay + :param params: a query string, or a dictionary that will be encoded + into a query string. You may also include a URL query + string on the url + + """ + response = self.app.get(url, + headers=headers, + extra_environ=extra_environ, + status=status, + expect_errors=expect_errors, + params=params) + + if not expect_errors: + response = response.json + return response diff --git a/refstack/tests/api/test_api.py b/refstack/tests/api/test_api.py new file mode 100644 index 00000000..9f98f87a --- /dev/null +++ b/refstack/tests/api/test_api.py @@ -0,0 +1,29 @@ +# Copyright (c) 2015 Mirantis, 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 refstack.tests import api + + +class TestRefStackApi(api.FunctionalTest): + + def test_root_controller(self): + actual_response = self.get_json('/') + expected_response = {'Root': 'OK'} + self.assertEqual(expected_response, actual_response) + + def test_results_controller(self): + actual_response = self.get_json('/v1/results/') + expected_response = {'Results': 'OK'} + self.assertEqual(expected_response, actual_response) diff --git a/refstack/tests/unit/__init__.py b/refstack/tests/unit/__init__.py deleted file mode 100644 index 97037fd8..00000000 --- a/refstack/tests/unit/__init__.py +++ /dev/null @@ -1 +0,0 @@ -__author__ = 'dlenwell' diff --git a/requirements.txt b/requirements.txt index c2a74cc7..df75748e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,7 @@ SQLAlchemy==0.8.3 alembic==0.5.0 gunicorn==0.17.4 +pecan>=0.8.2 pyOpenSSL==0.13 pycrypto==2.6 requests==1.2.3