Refactoring

- enable public/private routes exectuion
	- implementing new DB model - routes
	- refactoring service model to allow sub-apps
	- examples/hello-lambda.sh was changed to reflect new routes API
This commit is contained in:
Denis Makogon 2016-11-16 18:27:02 +02:00
parent 4471a4081a
commit 68b54f28bb
19 changed files with 312 additions and 124 deletions

1
.gitignore vendored
View File

@ -13,3 +13,4 @@ python-troveclient.iml
# Files created by releasenotes build # Files created by releasenotes build
releasenotes/build releasenotes/build
.coverage.* .coverage.*
*.json

View File

@ -123,12 +123,14 @@ In [examples](examples/) folder you can find a script that examines available AP
* `LAOS_API_URL` - Project LaOS API endpoint * `LAOS_API_URL` - Project LaOS API endpoint
* `OS_AUTH_URL` - OpenStack Auth URL * `OS_AUTH_URL` - OpenStack Auth URL
* `OS_PROJECT_ID` - it can be found in OpenStack Dashboard or in CLI * `OS_PROJECT_ID` - it can be found in OpenStack Dashboard or in CLI
* `OS_USERNAME` - OpenStack project-aligned username
Along with that, you need to adjust [token_request.json](examples/token_request.json) in order to retrieve X-Auth-Token for further authentication against OpenStack and LaOS API service. * `OS_PASSWORD` - OpenStack project-aligned user password
* `OS_DOMAIN` - OpenStack project domain name
* `OS_PROJECT_NAME` - OpenStack project name
Then just run script: Then just run script:
OS_AUTH_URL=http://192.168.0.112:5000/v3 OS_PROJECT_ID=8fb76785313a4500ac5367eb44a31677 ./hello-lambda.sh OS_AUTH_URL=http://192.168.0.112:5000/v3 OS_PROJECT_ID=8fb76785313a4500ac5367eb44a31677 OS_USERNAME=admin OS_PASSWORD=root OS_DOMAIN=default OS_PROJECT_NAME=admin ./examples/hello-lambda.sh
Please note, that given values are project-specific, so they can't be reused. Please note, that given values are project-specific, so they can't be reused.

View File

@ -1,18 +1,47 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set +x set +x
set +e
export LAOS_API_URL=${LAOS_API_URL:-http://localhost:10001} export LAOS_API_URL=${LAOS_API_URL:-http://localhost:10001}
export OS_AUTH_URL=${OS_AUTH_URL:-http://localhost:5000/v3} export OS_AUTH_URL=${OS_AUTH_URL:-http://localhost:5000/v3}
export OS_USERNAME=${OS_USERNAME:-admin}
export OS_PASSOWRD=${OS_PASSWORD:-root}
export OS_DOMAIN=${OS_DOMAIN:-default}
export OS_PROJECT_ID=${OS_PROJECT_ID:-"dummy_project_id"} export OS_PROJECT_ID=${OS_PROJECT_ID:-"dummy_project_id"}
export OS_TOKEN=`curl -si -d @token_request.json -H "Content-type: application/json" ${OS_AUTH_URL}/auth/tokens | awk '/X-Subject-Token/ {print $2}'`
rm -fr examples/token_request.json
echo -e "{
\"auth\": {
\"identity\": {
\"methods\": [\"password\"],
\"password\": {
\"user\": {
\"name\": \"${OS_USERNAME:-admin}\",
\"domain\": { \"id\": \"${OS_DOMAIN:-default}\" },
\"password\": \"${OS_PASSWORD:-root}\"
}
}
},
\"scope\": {
\"project\": {
\"name\": \"${OS_PROJECT_NAME:-admin}\",
\"domain\": {\"id\": \"${OS_DOMAIN:-default}\" }
}
}
}
}" >> examples/token_request.json
export OS_TOKEN=`curl -si -d @examples/token_request.json -H "Content-type: application/json" ${OS_AUTH_URL}/auth/tokens | awk '/X-Subject-Token/ {print $2}'`
echo -e "Listing apps\n" echo -e "Listing apps\n"
curl ${LAOS_API_URL}/v1/${OS_PROJECT_ID}/apps -H "X-Auth-Token:${OS_TOKEN}" -H "Content-Type: application/json" | python3 -mjson.tool curl ${LAOS_API_URL}/v1/${OS_PROJECT_ID}/apps -H "X-Auth-Token:${OS_TOKEN}" -H "Content-Type: application/json" | python3 -mjson.tool
echo -e "Creating app\n" echo -e "Creating app\n"
curl -X POST -d '{"app":{"name": "testapp"}}' ${LAOS_API_URL}/v1/${PROJECT_ID}/apps -H "X-Auth-Token:${OS_TOKEN}" -H "Content-Type: application/json" | python3 -mjson.tool curl -X POST -d '{"app":{"name": "testapp"}}' ${LAOS_API_URL}/v1/${OS_PROJECT_ID}/apps -H "X-Auth-Token:${OS_TOKEN}" -H "Content-Type: application/json" | python3 -mjson.tool
echo -e "Listing apps\n" echo -e "Listing apps\n"
curl ${LAOS_API_URL}/v1/${OS_PROJECT_ID}/apps -H "X-Auth-Token:${OS_TOKEN}" -H "Content-Type: application/json" | python3 -mjson.tool curl ${LAOS_API_URL}/v1/${OS_PROJECT_ID}/apps -H "X-Auth-Token:${OS_TOKEN}" -H "Content-Type: application/json" | python3 -mjson.tool
@ -20,32 +49,48 @@ curl ${LAOS_API_URL}/v1/${OS_PROJECT_ID}/apps -H "X-Auth-Token:${OS_TOKEN}" -H "
echo -e "Showing app info\n" echo -e "Showing app info\n"
export raw_app_info=`curl localhost:10001/v1/${OS_PROJECT_ID}/apps -H "X-Auth-Token:${OS_TOKEN}" -H "Content-Type: application/json" | python3 -mjson.tool | grep name | awk '{print $2}'` export raw_app_info=`curl localhost:10001/v1/${OS_PROJECT_ID}/apps -H "X-Auth-Token:${OS_TOKEN}" -H "Content-Type: application/json" | python3 -mjson.tool | grep name | awk '{print $2}'`
export app_name=${raw_app_info:1:30} export app_name=${raw_app_info:1:30}
unset $raw_app_info
curl ${LAOS_API_URL}/v1/${OS_PROJECT_ID}/apps/${app_name} -H "X-Auth-Token:${OS_TOKEN}" -H "Content-Type: application/json" | python3 -mjson.tool curl ${LAOS_API_URL}/v1/${OS_PROJECT_ID}/apps/${app_name} -H "X-Auth-Token:${OS_TOKEN}" -H "Content-Type: application/json" | python3 -mjson.tool
echo -e "Listing app routes\n" echo -e "Listing app routes\n"
curl ${LAOS_API_URL}/v1/${PROJECT_ID}/apps/${APP_NAME}/routes -H "X-Auth-Token:${OS_TOKEN}" -H "Content-Type: application/json" | python3 -mjson.tool curl ${LAOS_API_URL}/v1/${OS_PROJECT_ID}/apps/${app_name}/routes -H "X-Auth-Token:${OS_TOKEN}" -H "Content-Type: application/json" | python3 -mjson.tool
echo -e "Creating app sync route\n" echo -e "Creating app sync private route\n"
curl -X POST -d '{"route":{"type": "sync", "path": "/hello-sync", "image": "iron/hello"}}' ${LAOS_API_URL}/v1/${PROJECT_ID}/apps/${APP_NAME}/route -H "X-Auth-Token:${OS_TOKEN}" -H "Content-Type: application/json" curl -X POST -d '{"route":{"type": "sync", "path": "/hello-sync-private", "image": "iron/hello", "is_public": "false" }}' ${LAOS_API_URL}/v1/${OS_PROJECT_ID}/apps/${APP_NAME}/routes -H "X-Auth-Token:${OS_TOKEN}" -H "Content-Type: application/json" | python3 -mjson.tool
echo -e "Creating app sync public route\n"
curl -X POST -d '{"route":{"type": "sync", "path": "/hello-sync-public", "image": "iron/hello", "is_public": "true" }}' ${LAOS_API_URL}/v1/${OS_PROJECT_ID}/apps/${APP_NAME}/routes -H "X-Auth-Token:${OS_TOKEN}" -H "Content-Type: application/json" | python3 -mjson.tool
echo -e "Listing app routes\n" echo -e "Listing app routes\n"
curl ${LAOS_API_URL}/v1/${PROJECT_ID}/apps/${APP_NAME}/routes -H "X-Auth-Token:${OS_TOKEN}" -H "Content-Type: application/json" | python3 -mjson.tool curl ${LAOS_API_URL}/v1/${OS_PROJECT_ID}/apps/${app_name}/routes -H "X-Auth-Token:${OS_TOKEN}" -H "Content-Type: application/json" | python3 -mjson.tool
echo -e "Show app route\n" echo -e "Show app private route\n"
curl ${LAOS_API_URL}/v1/${PROJECT_ID}/apps/${APP_NAME}/routes/hello-sync -H "X-Auth-Token:${OS_TOKEN}" -H "Content-Type: application/json" | python3 -mjson.tool curl ${LAOS_API_URL}/v1/${OS_PROJECT_ID}/apps/${app_name}/routes/hello-sync-private -H "X-Auth-Token:${OS_TOKEN}" -H "Content-Type: application/json" | python3 -mjson.tool
echo -e "Running app sync route\n" echo -e "Show app public route\n"
curl -X POST -d '{"name": "Johnny"}' ${LAOS_API_URL}/r/${PROJECT_ID}/${APP_NAME}/hello-sync -H "X-Auth-Token:${OS_TOKEN}" -H "Content-Type: application/json" | python3 -mjson.tool curl ${LAOS_API_URL}/v1/${OS_PROJECT_ID}/apps/${app_name}/routes/hello-sync-public -H "X-Auth-Token:${OS_TOKEN}" -H "Content-Type: application/json" | python3 -mjson.tool
echo -e "Deleting app route\n" echo -e "Running app sync private route\n"
curl -X DELETE ${LAOS_API_URL}/v1/${PROJECT_ID}/apps/${APP_NAME}/routes/hello_sync -H "X-Auth-Token:${OS_TOKEN}" -H "Content-Type: application/json" | python3 -mjson.tool curl -X POST -d '{"name": "Johnny"}' ${LAOS_API_URL}/private/${OS_PROJECT_ID}/${app_name}/hello-sync-private -H "X-Auth-Token:${OS_TOKEN}" -H "Content-Type: application/json" | python3 -mjson.tool
echo -e "Running app sync public route\n"
curl -X POST -d '{"name": "Johnny"}' ${LAOS_API_URL}/public/${app_name}/hello-sync-public -H "Content-Type: application/json" | python3 -mjson.tool
echo -e "Creating app async route\n" echo -e "Creating app async route\n"
curl -X POST -d '{"route":{"type": "async", "path": "/hello-async", "image": "iron/hello"}}' ${LAOS_API_URL}/v1/${PROJECT_ID}/apps/${APP_NAME}/route -H "X-Auth-Token:${OS_TOKEN}" -H "Content-Type: application/json" | python3 -mjson.tool curl -X POST -d '{"route":{"type": "async", "path": "/hello-async-private", "image": "iron/hello", "is_public": "false"}}' ${LAOS_API_URL}/v1/${OS_PROJECT_ID}/apps/${app_name}/routes -H "X-Auth-Token:${OS_TOKEN}" -H "Content-Type: application/json" | python3 -mjson.tool
echo -e "Running app async route\n" echo -e "Running app async route\n"
curl -X POST -d '{"name": "Johnny"}' ${LAOS_API_URL}/r/${PROJECT_ID}/${APP_NAME}/hello-async -H "X-Auth-Token:${OS_TOKEN}" -H "Content-Type: application/json" | python3 -mjson.tool curl -X POST -d '{"name": "Johnny"}' ${LAOS_API_URL}/private/${OS_PROJECT_ID}/${app_name}/hello-async-private -H "X-Auth-Token:${OS_TOKEN}" -H "Content-Type: application/json" | python3 -mjson.tool
echo -e "Deleting app route\n"
curl -X DELETE ${LAOS_API_URL}/v1/${OS_PROJECT_ID}/apps/${app_name}/routes/hello-sync-public -H "X-Auth-Token:${OS_TOKEN}" -H "Content-Type: application/json" | python3 -mjson.tool
curl -X DELETE ${LAOS_API_URL}/v1/${OS_PROJECT_ID}/apps/${app_name}/routes/hello-sync-private -H "X-Auth-Token:${OS_TOKEN}" -H "Content-Type: application/json" | python3 -mjson.tool
curl -X DELETE ${LAOS_API_URL}/v1/${OS_PROJECT_ID}/apps/${app_name}/routes/hello-async-private -H "X-Auth-Token:${OS_TOKEN}" -H "Content-Type: application/json" | python3 -mjson.tool
echo -e "Listing app routes\n"
curl ${LAOS_API_URL}/v1/${OS_PROJECT_ID}/apps/${app_name}/routes -H "X-Auth-Token:${OS_TOKEN}" -H "Content-Type: application/json" | python3 -mjson.tool
echo -e "Deleting app\n" echo -e "Deleting app\n"
curl -X DELETE ${LAOS_API_URL}/v1/${OS_PROJECT_ID}/apps/${app_name} -H "X-Auth-Token:${OS_TOKEN}" -H "Content-Type: application/json" | python3 -mjson.tool curl -X DELETE ${LAOS_API_URL}/v1/${OS_PROJECT_ID}/apps/${app_name} -H "X-Auth-Token:${OS_TOKEN}" -H "Content-Type: application/json" | python3 -mjson.tool
echo -e "Listing apps\n"
curl ${LAOS_API_URL}/v1/${OS_PROJECT_ID}/apps -H "X-Auth-Token:${OS_TOKEN}" -H "Content-Type: application/json" | python3 -mjson.tool

View File

@ -1,20 +0,0 @@
{
"auth": {
"identity": {
"methods": ["password"],
"password": {
"user": {
"name": "admin",
"domain": { "id": "default" },
"password": "root"
}
}
},
"scope": {
"project": {
"name": "admin",
"domain": { "id": "default" }
}
}
}
}

View File

@ -12,6 +12,8 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import json
from aiohttp import web from aiohttp import web
from laos.api.views import app as app_view from laos.api.views import app as app_view
@ -34,6 +36,10 @@ class AppRouteV1Controller(controllers.ServiceControllerBase):
log, fnclient = c.logger, c.functions_client log, fnclient = c.logger, c.functions_client
project_id = request.match_info.get('project_id') project_id = request.match_info.get('project_id')
app = request.match_info.get('app') app = request.match_info.get('app')
log.info("Listing app {} routes for project: {}."
.format(app, project_id))
if not (await app_model.Apps.exists(app, project_id)): if not (await app_model.Apps.exists(app, project_id)):
return web.json_response(data={ return web.json_response(data={
"error": { "error": {
@ -44,10 +50,20 @@ class AppRouteV1Controller(controllers.ServiceControllerBase):
fn_app_routes = (await (await fnclient.apps.show( fn_app_routes = (await (await fnclient.apps.show(
app, loop=c.event_loop)).routes.list(loop=c.event_loop)) app, loop=c.event_loop)).routes.list(loop=c.event_loop))
for fn_route in fn_app_routes:
stored_route = (await app_model.Routes.find_by(
app_name=app,
project_id=project_id,
path=fn_route.path)).pop()
setattr(fn_route, "is_public", stored_route.public)
api_url = "{}://{}".format(request.scheme, request.host)
log.info("Listing app {} routes for project: {}." log.info("Listing app {} routes for project: {}."
.format(app, project_id)) .format(app, project_id))
return web.json_response(data={ return web.json_response(data={
"routes": app_view.AppRouteView(fn_app_routes).view(), "routes": app_view.AppRouteView(api_url,
project_id,
fn_app_routes).view(),
"message": "Successfully loaded app routes", "message": "Successfully loaded app routes",
}, status=200) }, status=200)
@ -58,6 +74,7 @@ class AppRouteV1Controller(controllers.ServiceControllerBase):
log, fnclient = c.logger, c.functions_client log, fnclient = c.logger, c.functions_client
project_id = request.match_info.get('project_id') project_id = request.match_info.get('project_id')
app = request.match_info.get('app') app = request.match_info.get('app')
if not (await app_model.Apps.exists(app, project_id)): if not (await app_model.Apps.exists(app, project_id)):
return web.json_response(data={ return web.json_response(data={
"error": { "error": {
@ -67,6 +84,8 @@ class AppRouteV1Controller(controllers.ServiceControllerBase):
data = (await request.json())['route'] data = (await request.json())['route']
path = data['path'] path = data['path']
is_public = json.loads(data.get(
'is_public', "false"))
try: try:
fn_app = await fnclient.apps.show(app, loop=c.event_loop) fn_app = await fnclient.apps.show(app, loop=c.event_loop)
@ -95,15 +114,26 @@ class AppRouteV1Controller(controllers.ServiceControllerBase):
} }
}, status=getattr(ex, "status", 500)) }, status=getattr(ex, "status", 500))
new_fn_route = (await (await fnclient.apps.show( new_fn_route = (await fn_app.routes.create(
app, loop=c.event_loop)).routes.create(
**data, loop=c.event_loop)) **data, loop=c.event_loop))
stored_route = await app_model.Routes(
app_name=new_fn_route.appname,
project_id=project_id,
path=new_fn_route.path,
is_public=is_public).save()
log.info("Creating new route in app {} " log.info("Creating new route in app {} "
"for project: {} with data {}" "for project: {} with data {}"
.format(app, project_id, str(data))) .format(app, project_id, str(data)))
api_url = "{}://{}".format(request.scheme, request.host)
setattr(new_fn_route, "is_public", stored_route.public)
view = app_view.AppRouteView(
api_url, project_id, [new_fn_route]).view()
return web.json_response(data={ return web.json_response(data={
"route": app_view.AppRouteView([new_fn_route]).view(), "route": view,
"message": "App route successfully created" "message": "App route successfully created"
}, status=200) }, status=200)
@ -116,6 +146,13 @@ class AppRouteV1Controller(controllers.ServiceControllerBase):
app = request.match_info.get('app') app = request.match_info.get('app')
path = request.match_info.get('route') path = request.match_info.get('route')
if not (await app_model.Apps.exists(app, project_id)):
return web.json_response(data={
"error": {
"message": "App {0} not found".format(app),
}
}, status=404)
try: try:
fn_app = await fnclient.apps.show(app, loop=c.event_loop) fn_app = await fnclient.apps.show(app, loop=c.event_loop)
route = await fn_app.routes.show( route = await fn_app.routes.show(
@ -129,8 +166,20 @@ class AppRouteV1Controller(controllers.ServiceControllerBase):
log.info("Requesting route {} in app {} for project: {}" log.info("Requesting route {} in app {} for project: {}"
.format(path, app, project_id)) .format(path, app, project_id))
api_url = "{}://{}".format(request.scheme, request.host)
stored_route = (await app_model.Routes.find_by(
app_name=app,
project_id=project_id,
path=route.path)).pop()
setattr(route, "is_public", stored_route.public)
return web.json_response(data={ return web.json_response(data={
"route": app_view.AppRouteView([route]).view().pop(), "route": app_view.AppRouteView(api_url,
project_id,
[route]).view().pop(),
"message": "App route successfully loaded" "message": "App route successfully loaded"
}, status=200) }, status=200)
@ -144,10 +193,19 @@ class AppRouteV1Controller(controllers.ServiceControllerBase):
path = request.match_info.get('route') path = request.match_info.get('route')
log.info("Deleting route {} in app {} for project: {}" log.info("Deleting route {} in app {} for project: {}"
.format(path, app, project_id)) .format(path, app, project_id))
if not (await app_model.Apps.exists(app, project_id)):
return web.json_response(data={
"error": {
"message": "App {0} not found".format(app),
}
}, status=404)
try: try:
fn_app = await fnclient.apps.show(app, loop=c.event_loop) fn_app = await fnclient.apps.show(app, loop=c.event_loop)
await fn_app.routes.show("/{}".format(path), loop=c.event_loop) await fn_app.routes.show("/{}".format(path), loop=c.event_loop)
await fn_app.routes.delete("/{}".format(path), loop=c.event_loop) await fn_app.routes.delete("/{}".format(path), loop=c.event_loop)
await app_model.Routes.delete(project_id=project_id, app_name=app)
except Exception as ex: except Exception as ex:
return web.json_response(data={ return web.json_response(data={
"error": { "error": {

View File

@ -18,14 +18,8 @@ from laos.common.base import controllers
from laos.common import config from laos.common import config
class RunnableV1Controller(controllers.ServiceControllerBase): class RunnableMixin(object):
controller_name = "runnable"
# IronFunction uses `r` as runnable instead API version
version = "r"
@controllers.api_action(
method='POST', route='{project_id}/{app}/{route}')
async def run(self, request, **kwargs): async def run(self, request, **kwargs):
c = config.Config.config_instance() c = config.Config.config_instance()
fnclient = c.functions_client fnclient = c.functions_client
@ -68,3 +62,31 @@ class RunnableV1Controller(controllers.ServiceControllerBase):
return _data return _data
return web.json_response(status=200, data=process_result(result)) return web.json_response(status=200, data=process_result(result))
class PublicRunnableV1Controller(controllers.ServiceControllerBase,
RunnableMixin):
controller_name = "public_runnable"
# IronFunction uses `r` as runnable instead API version
version = "r"
@controllers.api_action(
method='POST', route='{app}/{route}')
async def run(self, request, **kwargs):
return await super(PublicRunnableV1Controller,
self).run(request, **kwargs)
class RunnableV1Controller(controllers.ServiceControllerBase,
RunnableMixin):
controller_name = "runnable"
# IronFunction uses `r` as runnable instead API version
version = "r"
@controllers.api_action(
method='POST', route='{project_id}/{app}/{route}')
async def run(self, request, **kwargs):
return await super(RunnableV1Controller,
self).run(request, **kwargs)

View File

@ -27,6 +27,12 @@ class TasksV1Controller(controllers.ServiceControllerBase):
controller_name = "tasks" controller_name = "tasks"
version = "v1" version = "v1"
# TODO(denismakogon):
# - define subapp to process requests to tasks API:
# * extract tasks V1 controller to subapp
# - on each request check if route is public our private
# * reject with 401 if route is private
# * accept with 200 if route is public
@controllers.api_action( @controllers.api_action(
method='GET', route='{project_id}/tasks') method='GET', route='{project_id}/tasks')
async def get(self, request, **kwargs): async def get(self, request, **kwargs):

View File

@ -18,12 +18,13 @@ async def content_type_validator(app: web.Application, handler):
async def middleware_handler(request: web.Request): async def middleware_handler(request: web.Request):
headers = request.headers headers = request.headers
content_type = headers.get("Content-Type") content_type = headers.get("Content-Type")
if "application/json" not in content_type: if request.has_body:
return web.json_response( if "application/json" != content_type:
data={ return web.json_response(
"error": { data={
"message": "Invalid content type" "error": {
} "message": "Invalid content type"
}, status=400) }
}, status=400)
return await handler(request) return await handler(request)
return middleware_handler return middleware_handler

View File

@ -33,16 +33,26 @@ class AppView(object):
class AppRouteView(object): class AppRouteView(object):
def __init__(self, fn_app_routes): def __init__(self, api_url, project_id, fn_app_routes):
self.routes = fn_app_routes self.routes = fn_app_routes
self.api_url = api_url
self.project_id = project_id
def view(self): def view(self):
view = [] view = []
for route in self.routes: for route in self.routes:
if not route.is_public:
path = ("{}/private/{}/{}{}".format(
self.api_url, self.project_id,
route.appname, route.path))
else:
path = ("{}/public/{}{}".format(
self.api_url, route.appname, route.path))
view.append({ view.append({
"path": route.path, "path": path,
"type": route.type, "type": route.type,
"memory": route.memory, "memory": route.memory,
"image": route.image, "image": route.image,
"is_public": route.is_public,
}) })
return view return view

View File

@ -35,14 +35,13 @@ class ServiceControllerBase(object):
method.arg_method, method.arg_method,
method.arg_route] for method in methods] method.arg_route] for method in methods]
def __init__(self, service: web.Application): def __init__(self, sub_service: web.Application):
for fn, http_method, route in self.__get_handlers(): for fn, http_method, route in self.__get_handlers():
proxy_fn = '_'.join([fn.__name__, self.controller_name]) proxy_fn = '_'.join([fn.__name__, self.controller_name])
setattr(self, proxy_fn, fn) setattr(self, proxy_fn, fn)
service.router.add_route(http_method, sub_service.router.add_route(
"/{}/{}".format(self.version, route), http_method, "/{}".format(route),
getattr(self, proxy_fn), getattr(self, proxy_fn), name=proxy_fn)
name=proxy_fn)
def api_action(**outter_kwargs): def api_action(**outter_kwargs):

View File

@ -13,11 +13,8 @@
# under the License. # under the License.
import asyncio import asyncio
import os
import typing
from aiohttp import web from aiohttp import web
from laos.common.base import controllers as c
from laos.common import logger as log from laos.common import logger as log
@ -25,49 +22,61 @@ class AbstractWebServer(object):
def __init__(self, host: str='127.0.0.1', def __init__(self, host: str='127.0.0.1',
port: int= '10001', port: int= '10001',
controllers: typing.List[c.ServiceControllerBase]=None, private_controllers: dict=None,
middlewares: list=None, private_middlewares: list=None,
public_middlewares: list=None,
public_controllers: dict=None,
event_loop: asyncio.AbstractEventLoop=None, event_loop: asyncio.AbstractEventLoop=None,
logger=log.UnifiedLogger( logger=log.UnifiedLogger(
log_to_console=True, log_to_console=True,
level="INFO").setup_logger(__name__)): level="INFO").setup_logger(__name__),
debug=False):
""" """
HTTP server abstraction class HTTP server abstraction class
:param host: :param host: Bind host
:param port: :param port: Bind port
:param controllers: :param private_controllers: private API controllers mapping
:param middlewares: :param private_middlewares: list of private API middleware
:param event_loop: :param public_middlewares:
:param logger: list of public API middleware
:param public_controllers:
public API controllers mapping
:param event_loop: asyncio eventloop
:param logger: logging.Logger
""" """
self.host = host self.host = host
self.port = port self.port = port
self.controllers = controllers
self.event_loop = event_loop self.event_loop = event_loop
self.service = web.Application(
loop=self.event_loop,
debug=os.environ.get('PYTHONASYNCIODEBUG', 0),
middlewares=middlewares if middlewares else [])
self.service_handler = None
self.server = None
self.logger = logger self.logger = logger
def _apply_routers(self): self.root_service = web.Application(
if self.controllers: logger=self.logger,
for controller in self.controllers: loop=self.event_loop,
controller(self.service) debug=debug
)
def shutdown(self): self.register_subapps(private_controllers, private_middlewares)
self.server.close() self.register_subapps(public_controllers, public_middlewares)
self.event_loop.run_until_complete(self.server.wait_closed())
self.event_loop.run_until_complete( def _apply_routers(self, service, controllers):
self.service_handler.finish_connections(1.0)) for controller in controllers:
self.event_loop.run_until_complete(self.service.cleanup()) controller(service)
return service
def register_subapps(self, controllers_mapping: dict, middlewares: list):
if controllers_mapping:
for sub_route, controllers in controllers_mapping.items():
service = self._apply_routers(
web.Application(
logger=self.logger,
loop=self.event_loop,
middlewares=middlewares
if middlewares else []
)
, controllers)
self.root_service.router.add_subapp(
"/{}/".format(sub_route), service)
def initialize(self): def initialize(self):
self._apply_routers() web.run_app(self.root_service, host=self.host, port=self.port,
try: shutdown_timeout=10, access_log=self.logger)
web.run_app(self.service, host=self.host, port=self.port,
shutdown_timeout=10, access_log=self.logger)
except KeyboardInterrupt:
self.shutdown()

View File

@ -76,7 +76,10 @@ class UnifiedLogger(object, metaclass=utils.Singleton):
if 'DEBUG' not in level: if 'DEBUG' not in level:
self.log_formatter = ( self.log_formatter = (
'[%(asctime)s] - ' '[%(asctime)s] - '
'%(message)s') '%(name)s - '
'%(module)s.py:%(lineno)d - '
'%(message)s'
)
else: else:
self. log_formatter = ( self. log_formatter = (
'[%(asctime)s] - ' '[%(asctime)s] - '

View File

@ -27,6 +27,10 @@ class BaseDatabaseModel(object):
DELETE = "DELETE FROM {} {}" DELETE = "DELETE FROM {} {}"
def __init__(self, **kwargs): def __init__(self, **kwargs):
logger = config.Config.config_instance().logger
logger.info("Attempting to create object class instance "
"'{}' with attributes '{}'"
.format(str(self.__class__), str(kwargs)))
self.id = uuid.uuid4().hex self.id = uuid.uuid4().hex
self.created_at = str(datetime.datetime.now()) self.created_at = str(datetime.datetime.now())
self.updated_at = str(datetime.datetime.now()) self.updated_at = str(datetime.datetime.now())
@ -34,30 +38,37 @@ class BaseDatabaseModel(object):
setattr(self, k, v) setattr(self, k, v)
async def save(self): async def save(self):
logger = config.Config.config_instance().logger
insert = self.INSERT.format( insert = self.INSERT.format(
self.table_name, self.table_name,
str(tuple([getattr(self, clmn) for clmn in self.columns])) str(tuple([getattr(self, clmn) for clmn in self.columns]))
) )
print(insert) logger.info("Attempting to save object '{}' "
"using SQL query: {}".format(self.table_name, insert))
async with config.Connection.from_class().acquire() as conn: async with config.Connection.from_class().acquire() as conn:
async with conn.cursor() as cur: async with conn.cursor() as cur:
await cur.execute(insert) await cur.execute(insert)
await conn.commit() await conn.commit()
logger.info("Object saved.")
return self return self
@classmethod @classmethod
async def delete(cls, **kwargs): async def delete(cls, **kwargs):
logger = config.Config.config_instance().logger
delete = cls.DELETE.format( delete = cls.DELETE.format(
cls.table_name, cls.__define_where(**kwargs)) cls.table_name, cls.__define_where(**kwargs))
logger.info("Attempting to delete object '{}' "
"using SQL query: {}".format(cls.table_name, delete))
async with config.Connection.from_class().acquire() as conn: async with config.Connection.from_class().acquire() as conn:
async with conn.cursor() as cur: async with conn.cursor() as cur:
await cur.execute(delete) await cur.execute(delete)
await conn.commit() await conn.commit()
logger.info("Object gone.")
async def update(self, **kwargs): # async def update(self, **kwargs):
async with config.Connection.from_class().acquire() as conn: # async with config.Connection.from_class().acquire() as conn:
async with conn.cursor() as cur: # async with conn.cursor() as cur:
await cur.execute() # await cur.execute()
@classmethod @classmethod
async def exists(cls, name, project_id): async def exists(cls, name, project_id):
@ -81,12 +92,15 @@ class BaseDatabaseModel(object):
@classmethod @classmethod
async def find_by(cls, **kwargs): async def find_by(cls, **kwargs):
logger = config.Config.config_instance().logger
where = cls.__define_where(**kwargs) where = cls.__define_where(**kwargs)
select = cls.SELECT.format(
cls.table_name, where)
logger.info("Attempting to find object(s) '{}' "
"using SQL : {}".format(cls.table_name, select))
async with config.Connection.from_class().acquire() as conn: async with config.Connection.from_class().acquire() as conn:
async with conn.cursor() as cur: async with conn.cursor() as cur:
await cur.execute(cls.SELECT.format( await cur.execute(select)
cls.table_name, where))
results = await cur.fetchall() results = await cur.fetchall()
return [cls.from_tuple(instance) return [cls.from_tuple(instance)
for instance in results] if results else [] for instance in results] if results else []

View File

@ -26,3 +26,20 @@ class Apps(persistence.BaseDatabaseModel):
"updated_at", "updated_at",
"name" "name"
) )
class Routes(persistence.BaseDatabaseModel):
table_name = "routes"
columns = (
"project_id",
"path",
"is_public",
"app_name",
"created_at",
"updated_at",
)
@property
def public(self):
return True if self.is_public else False

View File

@ -35,22 +35,36 @@ class API(service.AbstractWebServer):
def __init__(self, host: str='0.0.0.0', def __init__(self, host: str='0.0.0.0',
port: int=10001, port: int=10001,
loop: asyncio.AbstractEventLoop=asyncio.get_event_loop(), loop: asyncio.AbstractEventLoop=asyncio.get_event_loop(),
logger=None): logger=None,
debug=False):
super(API, self).__init__( super(API, self).__init__(
host=host, host=host,
port=port, port=port,
controllers=[ private_controllers={
apps.AppV1Controller, "v1": [
routes.AppRouteV1Controller, apps.AppV1Controller,
runnable.RunnableV1Controller, routes.AppRouteV1Controller,
tasks.TasksV1Controller, tasks.TasksV1Controller,
], ],
middlewares=[ "private": [
runnable.RunnableV1Controller,
]
},
public_controllers={
"public": [
runnable.PublicRunnableV1Controller,
],
},
private_middlewares=[
keystone.auth_through_token, keystone.auth_through_token,
content_type.content_type_validator, content_type.content_type_validator,
], ],
public_middlewares=[
content_type.content_type_validator,
],
event_loop=loop, event_loop=loop,
logger=logger, logger=logger,
debug=debug,
) )
@ -73,6 +87,7 @@ class API(service.AbstractWebServer):
help='Logging file') help='Logging file')
@click.option('--log-file', default=None, @click.option('--log-file', default=None,
help='Log file path') help='Log file path')
@click.option('--debug', default=False, is_flag=True)
def server(host, port, db_uri, def server(host, port, db_uri,
keystone_endpoint, keystone_endpoint,
functions_host, functions_host,
@ -81,6 +96,7 @@ def server(host, port, db_uri,
functions_api_protocol, functions_api_protocol,
log_level, log_level,
log_file, log_file,
debug,
): ):
""" """
Starts an Project Laos API service Starts an Project Laos API service
@ -88,7 +104,7 @@ def server(host, port, db_uri,
logger = log.UnifiedLogger( logger = log.UnifiedLogger(
log_to_console=True if not log_file else False, log_to_console=True if not log_file else False,
filename=None if not log_file else log_file, filename=None if not log_file else log_file,
level=log_level).setup_logger(__name__) level=log_level).setup_logger(__package__)
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy()) asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
loop = asyncio.get_event_loop() loop = asyncio.get_event_loop()
@ -104,17 +120,14 @@ def server(host, port, db_uri,
config.Config( config.Config(
auth_url=keystone_endpoint, auth_url=keystone_endpoint,
functions_host=functions_host,
functions_port=functions_port,
functions_api_protocol=functions_api_protocol,
functions_api_version=functions_api_version,
functions_client=fnclient, functions_client=fnclient,
logger=logger, logger=logger,
connection=conn, connection=conn,
event_loop=loop, event_loop=loop,
) )
API(host=host, port=port, loop=loop, logger=logger).initialize() API(host=host, port=port, loop=loop,
logger=logger, debug=debug).initialize()
if __name__ == "__main__": if __name__ == "__main__":

0
laos/tests/__init__.py Normal file
View File

View File

@ -27,7 +27,17 @@ def upgrade():
sa.Column('updated_at', sa.String(255)), sa.Column('updated_at', sa.String(255)),
sa.Column('name', sa.String(255), nullable=False, primary_key=True), sa.Column('name', sa.String(255), nullable=False, primary_key=True),
) )
op.create_table(
'routes',
sa.Column('project_id', sa.String(255), nullable=False),
sa.Column('path', sa.String(255), nullable=False, primary_key=True),
sa.Column('is_public', sa.Boolean(create_constraint=False), nullable=False),
sa.Column('app_name', sa.String(255), nullable=False, primary_key=True),
sa.Column('created_at', sa.String(255), nullable=False),
sa.Column('updated_at', sa.String(255), nullable=False),
)
def downgrade(): def downgrade():
op.drop_table('routes')
op.drop_table('apps') op.drop_table('apps')

View File

@ -9,4 +9,3 @@ alembic>=0.8.4 # MIT
click click
keystoneauth1>=2.14.0 # Apache-2.0 keystoneauth1>=2.14.0 # Apache-2.0
python-keystoneclient==3.6.0 python-keystoneclient==3.6.0
SQLAlchemy<1.1.0,>=1.0.10 # MIT

View File

@ -36,7 +36,6 @@ setuptools.setup(
"click", "click",
"keystoneauth1>=2.14.0", "keystoneauth1>=2.14.0",
"python-keystoneclient==3.6.0", "python-keystoneclient==3.6.0",
"SQLAlchemy<1.1.0,>=1.0.10",
], ],
license='License :: OSI Approved :: Apache Software License', license='License :: OSI Approved :: Apache Software License',
classifiers=[ classifiers=[