Commit a78ba80e authored by Daniele Venzano's avatar Daniele Venzano Committed by GitHub

Merge pull request #52 from DistributedSystemsGroup/devel/webui-fixes

Fixes issue #51
parents fa810e0d 86469ea5
......@@ -165,6 +165,27 @@ Will return a JSON document like this::
It is a map with the execution IDs as keys and the full execution details as values.
Starting from verion 0.7 of the API, the execution list can be filtered.
You need to pass via the URL (GET parameters) the criteria to be used for filtering, for example::
curl -u 'username:password' http://bf5:8080/api/<api_version>/execution?status=terminated\&limit=1
Valid criteria that can be used are:
* status: one of submitted, scheduled, starting, error, running, cleaning up, terminated
* name: execution mane
* user_id: user_id owning the execution (admin only)
* limit: limit the number of returned entries
* earlier_than_submit: all execution that where submitted earlier than this timestamp
* earlier_than_start: all execution that started earlier than this timestamp
* earlier_than_end: all execution that ended earlier than this timestamp
* later_than_submit: all execution that where submitted later than this timestamp
* later_than_start: all execution that started later than this timestamp
* later_than_end: all execution that started later than this timestamp
All timestamps should be passed as number of seconds since the epoch (UTC timezone).
Start execution
^^^^^^^^^^^^^^^
......
......@@ -142,7 +142,7 @@ class ExecutionCollectionAPI(RequestHandler):
All timestamps should be passed as number of seconds since the epoch (UTC timezone).
example: curl -u 'username:password' -X GET -H "Content-Type: application/json" -d '{"status":"terminated"}' http://bf5:8080/api/0.6/execution
example: curl -u 'username:password' -X GET 'http://bf5:8080/api/0.6/execution?limit=1&status=terminated'
:return:
"""
......
......@@ -22,7 +22,7 @@
import json
import datetime
from jinja2 import Environment, FileSystemLoader, Markup
from jinja2 import Environment, FileSystemLoader, Markup, TemplateSyntaxError
from tornado.escape import squeeze, linkify, url_escape, xhtml_escape
import tornado.web
......@@ -121,8 +121,11 @@ class ZoeRequestHandler(tornado.web.RequestHandler):
template = self._jinja_env.get_template(template_name)
try:
html = self._render(template, **kwargs)
except Exception:
zoe_api.web.utils.error_page(self, 'Jinja2 template exception', 500)
except TemplateSyntaxError as e:
zoe_api.web.utils.error_page(self, 'Template syntax error at {}:{}:<br> {}'.format(e.name, e.lineno, e.message), 500)
return
except Exception as e:
zoe_api.web.utils.error_page(self, 'Jinja2 template exception: {}'.format(e), 500)
return
self.finish(html)
......
......@@ -33,7 +33,9 @@ class ExecutionDefineWeb(ZoeRequestHandler):
@catch_exceptions
def get(self):
"""Define a new execution."""
get_auth(self)
uid, role_ = get_auth(self)
if uid is None:
return self.redirect(self.get_argument('next', u'/login'))
self.render('execution_new.html')
......@@ -49,6 +51,8 @@ class ExecutionStartWeb(ZoeRequestHandler):
def post(self):
"""Start an execution."""
uid, role = get_auth(self)
if uid is None:
return self.redirect(self.get_argument('next', u'/login'))
app_descr_json = self.request.files['file'][0]['body'].decode('utf-8')
app_descr = json.loads(app_descr_json)
......@@ -70,6 +74,8 @@ class ExecutionRestartWeb(ZoeRequestHandler):
def get(self, execution_id: int):
"""Restart an already defined (and not running) execution."""
uid, role = get_auth(self)
if uid is None:
return self.redirect(self.get_argument('next', u'/login'))
e = self.api_endpoint.execution_by_id(uid, role, execution_id)
new_id = self.api_endpoint.execution_start(uid, role, e.name, e.description)
......@@ -88,6 +94,8 @@ class ExecutionTerminateWeb(ZoeRequestHandler):
def get(self, execution_id: int):
"""Terminate 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_terminate(uid, role, execution_id)
if not success:
......@@ -107,6 +115,8 @@ class ExecutionDeleteWeb(ZoeRequestHandler):
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:
......@@ -126,6 +136,8 @@ class ExecutionInspectWeb(ZoeRequestHandler):
def get(self, execution_id):
"""Gather details about an execution."""
uid, role = get_auth(self)
if uid is None:
return self.redirect(self.get_argument('next', u'/login'))
e = self.api_endpoint.execution_by_id(uid, role, execution_id)
......@@ -133,8 +145,11 @@ class ExecutionInspectWeb(ZoeRequestHandler):
for service in e.services:
services_info.append(self.api_endpoint.service_by_id(uid, role, service.id))
endpoints = self.api_endpoint.execution_endpoints(uid, role, e)[1]
template_vars = {
"e": e,
"services_info": services_info
"services_info": services_info,
"endpoints": endpoints,
}
self.render('execution_inspect.html', **template_vars)
......@@ -33,7 +33,7 @@ class RootWeb(ZoeRequestHandler):
@catch_exceptions
def get(self):
"""Home page without authentication."""
self.render('index.html')
self.redirect("/user")
class LoginWeb(ZoeRequestHandler):
......@@ -72,6 +72,8 @@ class HomeWeb(ZoeRequestHandler):
def get(self):
"""Home page with authentication."""
uid, role = get_auth(self)
if uid is None:
return self.redirect(self.get_argument('next', u'/login'))
if role == 'user' or role == 'admin':
executions = self.api_endpoint.execution_list(uid, role)
......
This diff is collapsed.
......@@ -32,6 +32,10 @@ table.app_list td {
padding-right: 1.5em;
}
table.app_list tr.even {
background-color: #F3FEEA;
}
div.status_line {
float: left;
}
......@@ -62,84 +66,98 @@ span.fakelink {
}
#wrapper {
width: 800px;
width: 800px;
}
#navigation {
background-color: #fff;
border: #ddd 1px solid;
border-radius: 10px;
margin: 10px;
padding: 10px;
background-color: #fff;
border: #ddd 1px solid;
border-radius: 10px;
margin: 10px;
padding: 10px;
}
#navigation li {
margin: 2px 0;
margin: 2px 0;
}
label.error {
color: #ff0000;
margin-left: 10px;
position: relative;
color: #ff0000;
margin-left: 10px;
position: relative;
}
.wizard {
background-color: #fff;
border: #ddd 1px solid;
border-radius: 10px;
margin: 10px;
padding: 10px;
background-color: #fff;
border: #ddd 1px solid;
border-radius: 10px;
margin: 10px;
padding: 10px;
}
.wizard .wizard-header {
background-color: #f4f4f4;
border-bottom: #ddd 1px solid;
border-top-left-radius: 10px;
border-top-right-radius: 10px;
padding: 5px 10px;
margin: 0 0 10px 0;
background-color: #f4f4f4;
border-bottom: #ddd 1px solid;
border-top-left-radius: 10px;
border-top-right-radius: 10px;
padding: 5px 10px;
margin: 0 0 10px 0;
}
.wizard .wizard-step {
margin: 10px 0;
margin: 10px 0;
}
.wizard .wizard-step p {
padding: 5px;
padding: 5px;
}
.navigation {
border-top: #ddd 1px solid;
margin-top: 10px;
padding-top: 10px;
border-top: #ddd 1px solid;
margin-top: 10px;
padding-top: 10px;
}
.navigation ul {
margin: 0;
padding: 0;
list-style: none;
margin: 0;
padding: 0;
list-style: none;
}
.navigation li {
float: left;
margin-right: 10px;
float: left;
margin-right: 10px;
}
.clearfix:before, .clearfix:after {
content: "\0020";
display: block;
height: 0;
visibility: hidden;
content: "\0020";
display: block;
height: 0;
visibility: hidden;
}
.clearfix:after {
clear: both;
clear: both;
}
input {
margin-top: 5px;
margin-top: 5px;
}
section {
padding-bottom: 10px;
}
\ No newline at end of file
}
#loginbox {
width: 25em;
margin: 0 auto;
text-align: center;
}
#loginbox img {
width: 100%;
}
fieldset {
border: 0;
}
......@@ -49,7 +49,6 @@
document.write((bytes / Math.pow(k, i)).toPrecision(dm) + ' ' + sizes[i]);
}
</script>
<h1>Zoe - Analytics on demand</h1>
<div id="content">{% block content %}{% endblock %}</div>
<div id="footer">
{% block footer %}
......
{% extends "base.html" %}
{% block content %}
{{ super() }}
{% endblock %}
{% block footer %}
<p><a href="{{ reverse_url("home_user") }}">Home</a></p>
{{ super() }}
......
{% extends "base_user.html" %}
{% block title %}Inspect execution {{ e.name }}{% endblock %}
{% block content %}
<h1>Zoe - Analytics on demand</h1>
<h2>Detailed information for execution {{ e.name }}</h2>
<div id="contents">
<ul>
......@@ -23,6 +24,19 @@
<p>Error message: <code>{{ e.error_message }}</code></p>
{% endif %}
<div id="endpoints">
<h3>Endpoints</h3>
{% if endpoints|length > 0 %}
<ul>
{% for endp in endpoints|sort %}
<li><a href="{{ endp[1] }}">{{ endp[0] }}</a></li>
{% endfor %}
</ul>
{% else %}
<p>This execution does not have any active endpoint</p>
{% endif %}
</div>
<div id="container_list">
{% if services_info|length > 0 %}
<h3>Services:</h3>
......@@ -32,19 +46,10 @@
<li class="container_name" id="{{ s['id'] }}">{{ s['name'] }}</li>
<ul>
<li>Zoe status: {{ s['status'] }}</li>
<li>Docker status: {{ s['backend_status'] }}</li>
<li>Backend status: {{ s['backend_status'] }}</li>
{% if s['error_message'] is not none %}
<li>Error: {{ s['error_message'] }}</li>
{% endif %}
{% if s['backend_status'] == 'started' %}
{% for p in s['description']['ports'] %}
{% if s['proxy_address'] is not none %}
<li><a href="http://{{ s['proxy_address'] }}">{{ p['name'] }}</a></li>
{% else %}
<li><a> {{ p['name'] }} IP: {{ s['ip_address'] }}</a></li>
{% endif %}
{% endfor %}
{% endif %}
</ul>
{% endfor %}
</ul>
......
{% extends "base_user.html" %}
{% block title %}New execution definition{% endblock %}
{% block content %}
<h1>Zoe - Analytics on demand</h1>
<h1>New execution</h1>
<h2>New execution</h2>
<form method="post" action="{{ reverse_url('execution_start') }}" enctype="multipart/form-data">
<label>Execution name: <input type="text" name="exec_name"></label><br>
<label>Application description: <input type="file" name="file"></label><br>
......
{% extends "base.html" %}
{% extends "base_user.html" %}
{% block title %}Home{% endblock %}
{% block content %}
<h1>Zoe - Analytics on demand</h1>
<div id="my_executions">
<h3>Executions</h3>
......@@ -40,7 +41,7 @@
{% else %}
<td><script>format_timestamp("{{ e.time_end }}")</script></td>
{% endif %}
{% if e.status == "running" or e.status == "submitted" or e.status == "scheduled" %}
{% if e.is_active %}
<td><a href="/executions/terminate/{{ e.id }}">Terminate</a></td>
{% else %}
<td><a href="/executions/restart/{{ e.id }}">Restart</a>,
......
<div id="main-container">
<div id="main">
<h1>
<img alt="zoe dummy login page" src="{{ static_url("img/logo.png") }}">
</h1>
<div id="login-form">
<form action="/login" method="post" id="login_form">
<fieldset>
<label for="username">Username</label>
<input autocapitalize="off" autocorrect="off" class="text-input" id="username" name="username" tabindex="1" type="text" value="">
</fieldset>
{% extends "base.html" %}
{% block title %}Login{% endblock %}
{% block content %}
<fieldset>
<label for="password">Password</label>
<input class="text-input" id="password" name="password" tabindex="2" type="password" value="">
</fieldset>
<fieldset>
<span class="errormessage">{{errormessage}}</span>
</fieldset>
<div id="loginbox">
<img alt="ZOE logo" src="{{ static_url("logo.svg") }}">
<div id="login-form">
<form action="/login" method="post" id="login_form">
<fieldset>
<label for="username">Username:</label>
<input autocapitalize="off" autocorrect="off" class="text-input" id="username" name="username" tabindex="1" type="text" value=""><br>
<div id="form_btn">
<input id="signin-btn" class="btn btn-blue" type="submit" value="Sign In" tabindex="3">
</div>
</form>
</div>
<label for="password">Password:</label>
<input class="text-input" id="password" name="password" tabindex="2" type="password" value="">
</fieldset>
<div id="form_btn">
<input id="signin-btn" class="btn btn-blue" type="submit" value="Login" tabindex="3">
</div>
</div>
</form>
</div>
</div>
{% endblock %}
......@@ -84,7 +84,7 @@ def get_auth(handler: ZoeRequestHandler):
log.info('Authentication done using cookie')
return uid, role
else:
handler.redirect(handler.get_argument('next', u'/login'))
return None, None
def error_page(handler: ZoeRequestHandler, error_message: str, status: int):
......
......@@ -65,7 +65,10 @@ class Execution:
self._status = d['status']
self.error_message = d['error_message']
self.size = self.description['size']
try:
self.size = self.description['size']
except KeyError:
self.size = self.description['priority'] # zapp format v2
self.termination_lock = threading.Lock()
......
......@@ -31,6 +31,9 @@ class ResourceLimits:
elif isinstance(data, ResourceLimits):
self.min = data.min
self.max = data.max
elif isinstance(data, int):
self.min = data
self.max = data
else:
raise TypeError
self.unit = unit
......@@ -113,14 +116,40 @@ class Service:
self.essential = d['essential']
# Fields parsed from the JSON description
self.image_name = self.description['image']
try:
self.image_name = self.description['image']
except KeyError:
self.image_name = self.description['docker_image'] # zapp description v2
self.is_monitor = self.description['monitor']
self.startup_order = self.description['startup_order']
self.environment = self.description['environment']
self.command = self.description['command']
self.resource_reservation = ResourceReservation(self.description['resources'])
self.volumes = [VolumeDescriptionHostPath(v['path'], v['name'], v['read_only']) for v in self.description['volumes']]
self.replicas = self.description['replicas']
try:
self.environment = self.description['environment']
except KeyError:
self.environment = []
try:
self.command = self.description['command']
except KeyError:
self.command = None
try:
self.resource_reservation = ResourceReservation(self.description['resources'])
except KeyError:
self.resource_reservation = ResourceReservation({'memory': self.description['required_resources']['memory'], 'cores': 0}) # ZApp description v2
try:
self.volumes = [VolumeDescriptionHostPath(v['path'], v['name'], v['read_only']) for v in self.description['volumes']]
except KeyError:
self.volumes = []
except TypeError:
self.volumes = [VolumeDescriptionHostPath(v[0], v[1], v[2]) for v in self.description['volumes']]
try:
self.replicas = self.description['replicas']
except KeyError:
self.replicas = 0
def serialize(self):
"""Generates a dictionary that can be serialized in JSON."""
......@@ -186,7 +215,7 @@ class Service:
@property
def user_id(self):
"""Getter for the user_id, that is actually taken form the parent execution."""
"""Getter for the user_id, that is actually taken from the parent execution."""
execution = self.sql_manager.execution_list(only_one=True, id=self.execution_id)
return execution.user_id
......
......@@ -77,7 +77,7 @@ class KubernetesServiceConf:
for prt in ports:
aux = self.conf['spec']['ports'] # type: List[Dict[str, str]]
aux[count]['name'] = 'http'
aux[count]['name'] = 'http-' + str(count)
aux[count]['port'] = prt.number
aux[count]['targetPort'] = prt.number
count += 1
......@@ -184,6 +184,13 @@ class KubernetesReplicationControllerConf:
aux = self.conf['spec']['template'] # type: Dict
aux['spec']['containers'][0]['resources']['limits']['cpu'] = corelimit
def set_spec_container_command(self, command):
"""Setter to set container command"""
aux = self.conf['spec']['template']
aux['spec']['containers'][0]['command'] = []
command_arr = command.split(" ")
aux['spec']['containers'][0]['command'] = command_arr
def set_spec_container_volumes(self, volumes: List[VolumeDescription], name: str):
"""Setter to set container volumes"""
aux = self.conf['spec']['template'] # type: Dict
......@@ -261,6 +268,9 @@ class KubernetesClient:
if len(service_instance.volumes) > 0:
config.set_spec_container_volumes(service_instance.volumes, service_instance.name)
if service_instance.command is not None:
config.set_spec_container_command(service_instance.command)
info = {}
try:
......
......@@ -22,7 +22,7 @@ from zoe_master.backends.kubernetes.api_client import KubernetesClient
from zoe_master.exceptions import ZoeStartExecutionRetryException, ZoeStartExecutionFatalException, ZoeException, ZoeNotEnoughResourcesException
from zoe_master.backends.service_instance import ServiceInstance
import zoe_master.backends.base
from zoe_master.backends.kubernetes.threads import KubernetesMonitor, KubernetesStateSynchronizer
from zoe_master.backends.kubernetes.threads import KubernetesMonitor
from zoe_master.stats import NodeStats, ClusterStats # pylint: disable=unused-import
log = logging.getLogger(__name__)
......@@ -43,13 +43,13 @@ class KubernetesBackend(zoe_master.backends.base.BaseBackend):
"""Initializes Kubernetes backend starting the event monitoring thread."""
global _monitor, _checker
_monitor = KubernetesMonitor(state)
_checker = KubernetesStateSynchronizer(state)
# _checker = KubernetesStateSynchronizer(state)
@classmethod
def shutdown(cls):
"""Performs a clean shutdown of the resources used by Swarm backend."""
_monitor.quit()
_checker.quit()
# _checker.quit()
def spawn_service(self, service_instance: ServiceInstance):
"""Spawn a service, translating a Zoe Service into a Docker container."""
......
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