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
This commit is contained in:
Vladislav Kuzmin 2014-12-26 14:07:32 +04:00 committed by sslypushenko
parent a11dcb8487
commit c76114ea23
11 changed files with 239 additions and 17 deletions

View File

@ -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'}.

0
refstack/api/__init__.py Normal file
View File

21
refstack/tests/unit/tests.py → refstack/api/app.py Executable file → Normal file
View File

@ -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
)

63
refstack/api/config.py Normal file
View File

@ -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')
}
}
}

View File

View File

@ -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'}

View File

@ -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()

View File

@ -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

View File

@ -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)

View File

@ -1 +0,0 @@
__author__ = 'dlenwell'

View File

@ -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