Commit 3345d3d5 authored by Daniele Venzano's avatar Daniele Venzano

Functional ZApp shop with a directory backend

parent 8bac3eb6
......@@ -43,7 +43,6 @@ def web_init(api_endpoint) -> List[tornado.web.URLSpec]:
tornado.web.url(r'/executions/start', zoe_api.web.executions.ExecutionStartWeb, route_args, name='execution_start'),
tornado.web.url(r'/executions/restart/([0-9]+)', zoe_api.web.executions.ExecutionRestartWeb, route_args, name='execution_restart'),
tornado.web.url(r'/executions/terminate/([0-9]+)', zoe_api.web.executions.ExecutionTerminateWeb, route_args, name='execution_terminate'),
tornado.web.url(r'/executions/delete/([0-9]+)', zoe_api.web.executions.ExecutionDeleteWeb, route_args, name='execution_delete'),
tornado.web.url(r'/executions/inspect/([0-9]+)', zoe_api.web.executions.ExecutionInspectWeb, route_args, name='execution_inspect'),
tornado.web.url(r'/service/logs/([0-9]+)', zoe_api.web.executions.ServiceLogsWeb, route_args, name='service_logs'),
......@@ -51,7 +50,7 @@ def web_init(api_endpoint) -> List[tornado.web.URLSpec]:
tornado.web.url(r'/zapp-shop', zoe_api.web.zapp_shop.ZAppShopHomeWeb, route_args, name='zappshop'),
tornado.web.url(r'/zapp-shop/logo/([a-z\-.]+)', zoe_api.web.zapp_shop.ZAppLogoWeb, route_args, name='zappshop_logo'),
tornado.web.url(r'/zapp-shop/start/([a-z\-.]+)', zoe_api.web.zapp_shop.ZAppStartWeb, route_args, name='zappshop_start')
tornado.web.url(r'/zapp-shop/start/([0-9a-z\-.]+)', zoe_api.web.zapp_shop.ZAppStartWeb, route_args, name='zappshop_start')
]
return web_routes
......
......@@ -108,27 +108,6 @@ class ExecutionTerminateWeb(ZoeRequestHandler):
self.redirect(self.reverse_url('home_user'))
class ExecutionDeleteWeb(ZoeRequestHandler):
"""Handler class"""
def initialize(self, **kwargs):
"""Initializes the request handler."""
super().initialize(**kwargs)
self.api_endpoint = kwargs['api_endpoint'] # type: APIEndpoint
@catch_exceptions
def get(self, execution_id: int):
"""Delete an execution."""
uid, role = get_auth(self)
if uid is None:
return self.redirect(self.get_argument('next', u'/login'))
success, message = self.api_endpoint.execution_delete(uid, role, execution_id)
if not success:
raise zoe_api.exceptions.ZoeException(message)
self.redirect(self.reverse_url('home_user'))
class ExecutionInspectWeb(ZoeRequestHandler):
"""Handler class"""
def initialize(self, **kwargs):
......
......@@ -8,13 +8,13 @@
{% for zapp_category in zapps|sort %}
<div class="zapp-category">
<h3>{{ zapp_category[0].category }}</h3>
{% for zapp in zapp_category %}
<h3>{{ zapp_category }}</h3>
{% for zapp in zapps[zapp_category] %}
<div class="zapp">
<img src="{{ reverse_url("zappshop_logo", zapp.id) }}" alt="logo">
<p>{{ zapp.readable_name }}</p>
<button class="zapp-details" value="rd-{{ zapp.id }}">Details</button>
<button class="zapp-start" value="{{ zapp.id }}">Start</button>
<button class="zapp-start" value="{{ zapp.id }}-{{ zapp.manifest_index }}">Start</button>
<div class="readable_description" id="rd-{{ zapp.id }}">{{ zapp.readable_description|safe }}</div>
</div>
{% endfor %}
......
{% extends "base_user.html" %}
{% block title %}ZApp Shop{% endblock %}
{% block content %}
<h2>{{ zapp.readable_name }} start</h2>
<img src="{{ reverse_url("zappshop_logo", zapp.id) }}" alt="logo">
<div>{{ zapp.readable_description|safe }}</div>
<p>This ZApp is composed by the following services:</p>
<ul>
{% for service in zapp.zoe_description.services %}
<li>{{ service["total_count"] }} {{ service["name"] }} ({{ service["essential_count"] }} essentials)
<ul>
<li>memory: <script>format_bytes({{ service["resources"]["memory"]["max"] }}, 2) </script></li>
<li>cpu cores: {{ service["resources"]["cores"]["max"] }}</li>
</ul></li>
{% endfor %}
</ul>
<p>Parameters:</p>
<form action="{{ reverse_url("zappshop_start", "") }}{{ zapp.id }}-{{ zapp.manifest_index }}" method="post" id="zapp_start_form">
<input type="hidden" name="zapp-id" value="{{ zapp.id }}-{{ zapp.manifest_index }}">
<label>Execution name: <input type="text" name="exec_name"/></label><br/>
{% for param in zapp.parameters %}
<label>{{ param.readable_name }}
<input name="{{ param.name }}" value="{{ param.default if param.default != None }}" required title="{{ param.description }}"/></label><br/>
{% endfor %}
<input type="submit" value="Start"/>
</form>
<script>
$("#login_form").validate();
</script>
{% endblock %}
......@@ -19,7 +19,7 @@ import logging
from zoe_api import zapp_shop
from zoe_api.api_endpoint import APIEndpoint # pylint: disable=unused-import
from zoe_api.web.utils import get_auth_login, get_auth, catch_exceptions
from zoe_api.web.utils import get_auth, catch_exceptions
from zoe_api.web.custom_request_handler import ZoeRequestHandler
log = logging.getLogger(__name__)
......@@ -39,8 +39,7 @@ class ZAppShopHomeWeb(ZoeRequestHandler):
if uid is None:
return self.redirect(self.get_argument('next', u'/login'))
zapp_ids = zapp_shop.zshop_list_apps()
zapps = [zapp_shop.zshop_read_manifest(zapp_id) for zapp_id in zapp_ids]
zapps = zapp_shop.zshop_list_apps()
template_vars = {
"uid": uid,
......@@ -82,12 +81,45 @@ class ZAppStartWeb(ZoeRequestHandler):
if uid is None:
return self.redirect(self.get_argument('next', u'/login'))
zapp_ids = zapp_shop.zshop_list_apps()
zapps = [zapp_shop.zshop_read_manifest(zapp_id) for zapp_id in zapp_ids]
manifest_index = int(zapp_id.split('-')[-1])
zapp_id = "-".join(zapp_id.split('-')[:-1])
zapps = zapp_shop.zshop_read_manifest(zapp_id)
zapp = zapps[manifest_index]
template_vars = {
"uid": uid,
"role": role,
'zapps': zapps,
'zapp': zapp
}
self.render('zapp_shop.html', **template_vars)
self.render('zapp_start.html', **template_vars)
@catch_exceptions
def post(self, zapp_id):
"""Write the parameters in the description and start the ZApp."""
uid, role = get_auth(self)
if uid is None:
return self.redirect(self.get_argument('next', u'/login'))
manifest_index = int(zapp_id.split('-')[-1])
zapp_id = "-".join(zapp_id.split('-')[:-1])
zapps = zapp_shop.zshop_read_manifest(zapp_id)
zapp = zapps[manifest_index]
exec_name = self.get_argument('exec_name')
app_descr = self._set_parameters(zapp.zoe_description, zapp.parameters)
new_id = self.api_endpoint.execution_start(uid, role, exec_name, app_descr)
self.redirect(self.reverse_url('execution_inspect', new_id))
def _set_parameters(self, app_descr, params):
for param in params:
if param.kind == 'environment':
for service in app_descr['services']:
for env in service['environment']:
if env[0] == param.name:
env[1] = self.get_argument(param.name)
else:
log.warning('Unknown parameter kind: {}, ignoring...'.format(param.kind))
return app_descr
......@@ -25,23 +25,34 @@ from zoe_lib.config import get_conf
log = logging.getLogger(__name__)
ZAPP_MANIFEST_VERSION = 1 # The manifest version this Zoe Shop can understand
class ZAppParameter:
"""A ZApp parameter that should be exposed to the user interface."""
def __init__(self, param_manifest):
self.kind = param_manifest['kind']
self.name = param_manifest['name']
self.type = param_manifest['type']
self.readable_name = param_manifest['readable_name']
self.description = param_manifest['description']
self.default = param_manifest['default']
if param_manifest['type'] == 'string': # convert to HTML5 input types
self.type = "text"
elif param_manifest['type'] == 'int':
self.type = "number"
else:
self.type = "text"
class ZApp:
"""A ZApp."""
def __init__(self, zapp_id, manifest, manifest_index):
self.id = zapp_id
self.category = manifest['category']
self.manifest_index = manifest_index
zapp = manifest['zapps'][manifest_index]
self.category = zapp['category']
self.readable_name = zapp['name']
self.readable_description_file = zapp['readable_descr']
self.readable_description = self.read_description()
self.json_file = zapp['description']
self.zoe_description = self.parse_json_description()
......@@ -55,7 +66,7 @@ class ZApp:
def read_description(self):
"""Reads and renders the README.md file."""
mdown = open(os.path.join(get_conf().zapp_shop_path, self.id, "README.md"), 'r', encoding='utf-8').read()
mdown = open(os.path.join(get_conf().zapp_shop_path, self.id, self.readable_description_file), 'r', encoding='utf-8').read()
return markdown.markdown(mdown, extensions=['markdown.extensions.extra', 'markdown.extensions.codehilite'])
def parse_json_description(self):
......@@ -66,13 +77,27 @@ class ZApp:
def zshop_list_apps():
"""List the ZApp repos."""
dirs = [d for d in os.listdir(get_conf().zapp_shop_path) if os.path.isdir(os.path.join(get_conf().zapp_shop_path, d)) and os.path.exists(os.path.join(get_conf().zapp_shop_path, d, "manifest.json"))]
return dirs
zapps = []
for dir in dirs:
zapps += zshop_read_manifest(dir)
zapp_cat = {}
for zapp in zapps:
if zapp.category in zapp_cat:
zapp_cat[zapp.category].append(zapp)
else:
zapp_cat[zapp.category] = [zapp]
return zapp_cat
def zshop_read_manifest(zapp_id):
"""Reads and decodes the manifest file."""
manifest_path = os.path.join(get_conf().zapp_shop_path, zapp_id, "manifest.json")
manifest = json.load(open(manifest_path))
if manifest['version'] != ZAPP_MANIFEST_VERSION:
log.warning("Cannot load ZApp {}, this Zoe understands manifests version {} only".format(zapp_id, ZAPP_MANIFEST_VERSION))
return []
zapps = []
for idx in range(len(manifest['zapps'])):
zapps.append(ZApp(zapp_id, manifest, idx))
......
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 to comment