Commit f02141dd by Simon János

initial rest api implementation

parent b927b8e6
Pipeline #291 passed with stage
in 52 seconds
Circle Orchestrator
\ No newline at end of file
## Circle Orchestrator
### How to build
#### Build requirements
* python 2.7
* python 3.4
* tox
#### Build
```
$ cd orchestrator
$ tox
```
### How to run
```
$ .tox/py27/bin/orchestrator
```
or optionally
```
$ .tox/py27/bin/orchestrator --http-host=0.0.0.0 --http-port=8080
```
import json
import sys
from wsgiref import simple_server
import falcon
from oslo_config import cfg
from oslo_config.cfg import StrOpt, IntOpt
from oslo_log import log
from orchestrator.model.resources import ResourceGroup, InvalidResourceException, Resource
HTTP_SERVER_OPTS = [
StrOpt('host', default='127.0.0.1'),
IntOpt('port', default=8000)
]
cfg.CONF.register_cli_opts(HTTP_SERVER_OPTS, 'http')
log.register_options(cfg.CONF)
log.setup(cfg.CONF, 'orchestrator')
class Stack(object):
def __init__(self):
self.logger = log.getLogger()
self.stacks = {}
def on_get(self, _request, response, stack_id=None):
if stack_id is not None:
if stack_id in self.stacks:
response.body = json.dumps(self.stacks[stack_id], default=Resource.json_encoder)
else:
raise falcon.HTTPNotFound()
response.body = json.dumps(list(self.stacks.values()), default=Resource.json_encoder)
def on_post(self, request, response):
if request.content_length:
req = request.stream.read().decode('utf8')
stack = ResourceGroup(json.loads(req))
self.stacks[stack.id] = stack
response.body = str(stack)
else:
raise falcon.HTTPBadRequest('Empty request received')
def on_put(self, request, response, stack_id):
if stack_id in self.stacks.keys():
update_body = request.stream.read().decode('utf8')
updated_stack = json.loads(update_body)
diff = self.stacks[stack_id].diff(ResourceGroup(updated_stack), flat_leaves=False)
response.body = json.dumps(diff, default=Resource.json_encoder)
self.stacks[stack_id] = updated_stack
else:
raise falcon.HTTPNotFound()
def on_delete(self, _request, _response, stack_id):
if stack_id in self.stacks.keys():
del self.stacks[stack_id]
else:
raise falcon.HTTPNotFound()
class LoggingMiddleware(object):
logger = log.getLogger()
def process_request(self, request, *_):
self.logger.info('Request received: %s %s', request.method, request.path)
def process_response(self, _, response, *__):
if response.body:
self.logger.info('Sending response (%s): %s', response.status, response.body)
def handle_method_not_allowed(*args):
params = args[3]
raise falcon.HTTPMethodNotAllowed(['GET', 'PUT', 'DELETE'] if params else ['GET', 'POST'])
def handle_bad_request(*args):
exception = args[0]
raise falcon.HTTPBadRequest(description=str(exception))
def create_app():
stack_controller = Stack()
app = falcon.API(middleware=[LoggingMiddleware()])
app.add_route('/stacks', stack_controller)
app.add_route('/stacks/{stack_id}', stack_controller)
app.add_error_handler(TypeError, handle_method_not_allowed)
app.add_error_handler(InvalidResourceException, handle_bad_request)
return app
def main(args=None):
cfg.CONF(args[1:] if args else None)
app = create_app()
httpd = simple_server.make_server(cfg.CONF.http.host, cfg.CONF.http.port, app)
httpd.serve_forever()
if __name__ == '__main__':
main(sys.argv)
from setuptools import setup
from setuptools import setup, find_packages
setup(
name="orchestrator",
......@@ -8,5 +8,10 @@ setup(
description="Orchestrator for Circle cloud",
license="GPLv3",
url="http://circlecloud.org",
packages=['orchestrator', 'tests']
packages=find_packages(),
entry_points={
'console_scripts': [
'orchestrator = orchestrator.api.stacks:main'
]
}
)
import json
from falcon import HTTP_OK, HTTP_METHOD_NOT_ALLOWED, HTTP_BAD_REQUEST, HTTP_NOT_FOUND
from falcon.testing import TestCase
from orchestrator.api import stacks
class StacksControllerTest(TestCase):
def setUp(self):
super(StacksControllerTest, self).setUp()
self.app = stacks.create_app()
def test_stacks_api_allowed_methods_on_endpoints(self):
self.assertEqual(HTTP_OK, self.simulate_get('/stacks').status)
self.assertEqual(HTTP_NOT_FOUND, self.simulate_put('/stacks/42').status)
self.assertEqual(HTTP_NOT_FOUND, self.simulate_delete('/stacks/42').status)
self.assertEqual(HTTP_NOT_FOUND, self.simulate_get('/stacks/42').status)
self.assertEqual(HTTP_BAD_REQUEST, self.simulate_post('/stacks').status)
self.assertEqual(HTTP_METHOD_NOT_ALLOWED, self.simulate_post('/stacks/42').status)
self.assertEqual(HTTP_METHOD_NOT_ALLOWED, self.simulate_put('/stacks').status)
self.assertEqual(HTTP_METHOD_NOT_ALLOWED, self.simulate_delete('/stacks').status)
def test_stacks_api_create(self):
# given
stack_request_body = {
'id': 'root',
'type': 'group',
'resources': [
{
'id': 'instance-1',
'type': 'instance'
},
{
'id': 'level-1',
'type': 'group',
'resources': [
{
'id': 'instance-2',
'type': 'instance'
},
{
'id': 'instance-3',
'type': 'instance'
}
]
}
]
}
# when
result = self.simulate_post('/stacks', body=json.dumps(stack_request_body))
# then
self.assertEqual(sort_resources(stack_request_body), sort_resources(result.json))
def test_stacks_api_create_with_empty_json(self):
# given
empty_request_body = '{}'
# when
result = self.simulate_post('/stacks', body=empty_request_body, headers={'content-type': 'application/json'})
# then
self.assertIn('id', result.json.keys())
self.assertIn('type', result.json.keys())
self.assertIn('resources', result.json.keys())
self.assertEqual('group', result.json['type'])
self.assertEqual([], result.json['resources'])
def test_stacks_api_create_with_empty_body(self):
# when
result = self.simulate_post('/stacks', headers={'content-type': 'application/json'})
# then
self.assertEqual(HTTP_BAD_REQUEST, result.status)
def test_stacks_api_create_invalid_input(self):
# given
invalid_request_bodies = [
json.dumps({'invalid': 'value'}),
json.dumps({'resources': [{'invalid': 'value'}]})
]
for body in invalid_request_bodies:
# when
result = self.simulate_post('/stacks', body=body, headers={'content-type': 'application/json'})
# then
self.assertEqual(HTTP_BAD_REQUEST, result.status)
def test_stacks_api_update(self):
# given
stack_id = 'some_resource_group'
resource_id = 'new_resource'
group1 = {'id': stack_id, 'type': 'group', 'resources': []}
resource = {'id': resource_id, 'type': 'instance'}
group2 = {'id': stack_id, 'type': 'group', 'resources': [resource]}
expected_diff = {
'added': {
'%s.%s' % (stack_id, resource_id): resource
},
'removed': {}
}
# when
self.simulate_post('/stacks', body=json.dumps(group1))
put_result = self.simulate_put('/stacks/%s' % stack_id, body=json.dumps(group2))
get_result = self.simulate_get('/stacks')
# then
self.assertEqual(expected_diff, put_result.json)
self.assertIn(group2, get_result.json)
def test_stacks_api_delete(self):
# given
resource_id = 'some_resource'
resource = {'id': resource_id, 'type': 'instance'}
# when
self.simulate_post('/stacks', body=json.dumps(resource))
list_result_before_delete = self.simulate_get('/stacks')
delete_result = self.simulate_delete('/stacks/%s' % resource_id)
list_result_after_delete = self.simulate_get('/stacks')
# then
self.assertEqual([resource], list_result_before_delete.json)
self.assertEqual(HTTP_OK, delete_result.status)
self.assertEqual([], list_result_after_delete.json)
def test_stacks_api_get_list(self):
# given
resource1 = {'id': 'some_resource', 'type': 'instance'}
resource2 = {'id': 'some_other_resource', 'type': 'instance'}
# when
self.simulate_post('/stacks', body=json.dumps(resource1))
self.simulate_post('/stacks', body=json.dumps(resource2))
result = self.simulate_get('/stacks').json
# then
self.assertEqual(2, len(result))
self.assertIn(resource1, result)
self.assertIn(resource2, result)
def test_stacks_api_get_one(self):
# given
resource_id = 'some_resource'
resource = {'id': resource_id, 'type': 'instance'}
# when
self.simulate_post('/stacks', body=json.dumps(resource))
result = self.simulate_get('/stacks/%s' % resource_id).json
# then
self.assertEqual([resource], result)
def sort_resources(group):
try:
for resource in group['resources']:
sort_resources(resource)
group['resources'].sort(key=lambda r: r['id'])
except (KeyError, AttributeError):
pass
......@@ -5,6 +5,9 @@ envlist = py27, py34
production =
enum34
voluptuous
oslo.config
oslo.log
falcon
; celery
test =
pytest
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment