Skip to content
Snippets Groups Projects
Commit 5c91a77a authored by Stefano Alberto Russo's avatar Stefano Alberto Russo
Browse files

Decoupled task and container html templates. Added support for Docker logs. Minor fixes.

parent 3ef27a50
No related branches found
No related tags found
No related merge requests found
......@@ -34,9 +34,9 @@
<tr>
<td><b>Registry</b></td><td>
<select name="container_registry" >
<option value="docker_local" selected>Local</option>
<option value="docker_local" selected>Docker Local</option>
<option value="docker_hub">Docker Hub</option>
<option value="singularityhub">Singularity Hub</option>
<option value="singularity_hub">Singularity Hub</option>
</select>
</td>
</tr>
......
<table class="dashboard">
<tr>
<td><b>ID</b></td>
<td><a href="?uuid={{ container.uuid }}">{{ container.id }}</a></td>
</tr>
<tr>
<td><b>Image</b></td>
<td>{{ container.image }}</td>
</tr>
<tr>
<td><b>Type</b></td>
<td>{{ container.type }}</td>
</tr>
<tr>
<td><b>Owner</b></td>
<td>{% if container.user %}{{ container.user }}{% else %}Platform{% endif %}</td>
</tr>
<tr>
<td><b>Registry</b></td>
<td>{{ container.registry }}</td>
</tr>
<tr>
<td><b>Service port(s)</b></td>
<td>{{ container.service_ports}}</td>
</tr>
{% if container.user %}
<tr>
<td><b>Operations</b></td>
<td><a href="?action=delete&uuid={{ container.uuid }}">Delete</a></td>
</tr>
{% endif %}
</table>
<br/>
\ No newline at end of file
<table class="dashboard">
<tr>
<td><b>ID</b></td>
<td><a href="?uuid={{ task.uuid }}">{{ task.id }}</a></td>
</tr>
<tr>
<td><b>Name</b></td>
<td>{{ task.name }}</td>
</tr>
<tr>
<td><b>Container</b></td>
<td><a href="/containers/?uuid={{ task.container.uuid }}">{{ task.container.image }}</a></td>
</tr>
<tr>
<td><b>Status</b></td>
<td>{{ task.status }}</td>
</tr>
<tr>
<td><b>Created at</b></td>
<td>{{ task.created }}</td>
</tr>
<!-- <tr>
<td><b>Task pid</b></td>
<td>{{ task.pid}}</td>
</tr> -->
<tr>
<td><b>IP</b></td>
<td>{{ task.ip}}</td>
</tr>
<tr>
<td><b>Port</b></td>
<td>{{ task.port }}</td>
</tr>
<tr>
<td><b>Task tunnel port</b></td>
<td>{{ task.tunnel_port }}</td>
</tr>
<tr>
<td><b>Operations</b></td>
<td>
{% if task.status == "running" %}
<a href="?uuid={{task.uuid}}&action=stop&details={{details}}">Stop</a> |
{% else %}
<!-- <a href=?uuid={{task.uuid}}&action=start>Start</a> | -->
<font color="#c0c0c0">Stop</font> |
{% endif %}
{% if task.status == "exited" or task.status == "stopped" %}
<a href="?uuid={{task.uuid}}&action=delete&details=False">Delete</a>
{% else %}
<font color="#c0c0c0">Delete</font>
{% endif %}
{% if task.status == "running" %}
| <a href="?uuid={{task.uuid}}&action=connect">Connect</a>
| <a href="/task_log/?uuid={{task.uuid}}&action=viewlog">View Log</a>
{% else %}
| <font color="#c0c0c0">Connect</font>
| <font color="#c0c0c0">View Log</font>
{% endif%}
</td>
</tr>
</table>
<br/>
\ No newline at end of file
......@@ -8,125 +8,27 @@
<div class="container">
<div class="dashboard">
<div class="span8 offset2">
<h1><a href="/containers">Container List</a> {% if data.container %}&gt; {{ data.container.id }} {% endif %} </h1>
{% if data.container %}
<h1><a href="/containers">Container List</a> &gt; {{ data.container.id }} </h1>
{% else %}
<h1>Container List</h1>
{% endif %}
<hr/>
{% if data.container %}
<table class="dashboard">
<tr>
<td><b>ID</b></td>
<td>{{ data.container.id }}</td>
</tr>
<tr>
<td><b>Image</b></td>
<td>{{ data.container.image }}</td>
</tr>
<tr>
<td><b>Type</b></td>
<td>{{ data.container.type }}</td>
</tr>
<tr>
<td><b>Owner</b></td>
<td>{% if data.container.user %}{{data.container.user}}{% else %}Platform{% endif %}</td>
</tr>
<tr>
<td><b>Registry</b></td>
<td>{{ data.container.registry }}</td>
</tr>
<tr>
<td><b>Service port(s)</b></td>
<td>{{ data.container.service_ports}}</td>
</tr>
</table>
<br />
{% include "components/container.html" with container=data.container %}
{% else %}
{% for container in data.platform_containers %}
<table class="dashboard">
<tr>
<td><b>ID</b></td>
<td><a href="?uuid={{ container.uuid }}">{{ container.id }}</a></td>
</tr>
<tr>
<td><b>Image</b></td>
<td>{{ container.image }}</td>
</tr>
<tr>
<td><b>Type</b></td>
<td>{{ container.type }}</td>
</tr>
<tr>
<td><b>Owner</b></td>
<td>Platform</td>
</tr>
<tr>
<td><b>Registry</b></td>
<td>{{ container.registry }}</td>
</tr>
<tr>
<td><b>Service port(s)</b></td>
<td>{{ container.service_ports}}</td>
</tr>
</table>
{% include "components/container.html" with container=container %}
<br />
{% endfor %}
{% for container in data.user_containers %}
<table class="dashboard">
<tr>
<td><b>ID</b></td>
<td><a href="?uuid={{ container.uuid }}">{{ container.id }}</a></td>
</tr>
<tr>
<td><b>Image</b></td>
<td>{{ container.image }}</td>
</tr>
<tr>
<td><b>Type</b></td>
<td>{{ container.type }}</td>
</tr>
<tr>
<td><b>Owner</b></td>
<td>{{container.user}}</td>
</tr>
<tr>
<td><b>Registry</b></td>
<td>{{ container.registry }}</td>
</tr>
<tr>
<td><b>Service port(s)</b></td>
<td>{{ container.service_ports}}</td>
</tr>
<tr>
<td><b>Operations</b></td>
<td><a href="?action=delete&uuid={{ container.uuid }}">Delete</a></td>
</tr>
</table>
{% include "components/container.html" with container=container %}
<br />
{% endfor %}
......
......@@ -8,95 +8,25 @@
<div class="container">
<div class="dashboard">
<div class="span8 offset2">
{% if data.task %}
<h1><a href="/tasks">Task List</a> &gt; {{ data.task.id }} </h1>
{% else %}
<h1>Task List</h1>
{% endif %}
<hr>
{% for task in data.tasks %}
<table class="dashboard">
<tr>
<td><b>ID</b></td>
<td>{{ task.id }}</td>
</tr>
<tr>
<td><b>Name</b></td>
<td>{{ task.name }}</td>
</tr>
<tr>
<td><b>Container</b></td>
<td><a href="">{{ task.container.image }}</a></td>
</tr>
<tr>
<td><b>Status</b></td>
<td>{{ task.status }}</td>
</tr>
<tr>
<td><b>Created at</b></td>
<td>{{ task.created }}</td>
</tr>
<!-- <tr>
<td><b>Task pid</b></td>
<td>{{ task.pid}}</td>
</tr> -->
<tr>
<td><b>IP</b></td>
<td>{{ task.ip}}</td>
</tr>
<tr>
<td><b>Port</b></td>
<td>{{ task.port }}</td>
</tr>
<tr>
<td><b>Task tunnel port</b></td>
<td>{{ task.tunnel_port }}</td>
</tr>
{% if data.task %}
{% include "components/task.html" with task=data.task details=True %}
{% else %}
{% for task in data.tasks %}
{% include "components/task.html" with task=task %}
{% endfor %}
<br />
<a href="/create_task">Add new...</a>
{% endif %}
<tr>
<td><b>Operations</b></td>
<td>
{% if task.status == "running" %}
<a href=?uuid={{task.uuid}}&action=stop>Stop</a> |
{% else %}
<!-- <a href=?uuid={{task.uuid}}&action=start>Start</a> | -->
<font color="#c0c0c0">Stop</font> |
{% endif %}
{% if task.status == "exited" or task.status == "stopped" %}
<a href=?uuid={{task.uuid}}&action=delete>Delete</a>
{% else %}
<font color="#c0c0c0">Delete</font>
{% endif %}
{% if task.status == "running" %}
| <a href=?uuid={{task.uuid}}&action=connect>Connect</a>
| <a href=/task_log/?uuid={{task.uuid}}&action=viewlog>View Log</a>
{% else %}
| <font color="#c0c0c0">Connect</font>
| <font color="#c0c0c0">View Log</font>
{% endif%}
</td>
</tr>
</table>
<br />
{% endfor %}
<br />
{% if not data.sid %}
<a href="/create_task">Add new...</a>
{% endif %}
<br/>
<br/>
<br/>
......
......@@ -21,7 +21,7 @@ from django.contrib.auth import update_session_auth_hash
# Project imports
from .models import Profile, LoginToken, Task, TaskStatuses, Container
from .utils import send_email, format_exception, random_username, log_user_activity, timezonize, os_shell
from .utils import send_email, format_exception, random_username, log_user_activity, timezonize, os_shell, booleanize
# Setup logging
import logging
......@@ -32,7 +32,9 @@ from .exceptions import ErrorMessage, ConsistencyException
# Conf
SUPPORTED_CONTAINER_TYPES = ['docker', 'singularity']
SUPPORTED_REGISTRIES = ['docker_local', 'docker_hub', 'signularity_hub']
SUPPORTED_REGISTRIES = ['docker_local', 'docker_hub', 'singularity_hub']
UNSUPPORTED_TYPES_VS_REGISTRIES = ['docker:singularity_hub']
TASK_DATA_DIR = "/data"
#=========================
......@@ -381,161 +383,186 @@ def tasks(request):
data['title'] = 'Tasks'
# Get action if any
action = request.GET.get('action', None)
uuid = request.GET.get('uuid', None)
action = request.GET.get('action', None)
uuid = request.GET.get('uuid', None)
details = booleanize(request.GET.get('details', None))
# Setting var
standby_supported = False
# Perform actions if required:
if action and uuid:
# Get the task (raises if none available including no permission)
task = Task.objects.get(user=request.user, uuid=uuid)
# Do we have to operate on a specific task?
if uuid:
if action=='delete':
if task.status not in [TaskStatuses.stopped, TaskStatuses.exited]:
data['error'] = 'Can delete only tasks in the stopped state'
return render(request, 'error.html', {'data': data})
try:
# Get the task (raises if none available including no permission)
try:
# Get the task (raises if none available including no permission)
task = Task.objects.get(user=request.user, uuid=uuid)
except Task.DoesNotExist:
raise ErrorMessage('Task does not exists or no access rights')
data['task'] = task
#----------------
# Task actions
#----------------
# Delete
task.delete()
except Exception as e:
data['error'] = 'Error in deleting the task'
logger.error('Error in deleting task with uuid="{}": "{}"'.format(uuid, e))
return render(request, 'error.html', {'data': data})
elif action=='stop': # or delete,a and if delete also remove object
try:
if task.compute == 'local':
# Delete the Docker container
if standby_supported:
stop_command = 'sudo docker stop {}'.format(task.tid)
if action=='delete':
if task.status not in [TaskStatuses.stopped, TaskStatuses.exited]:
data['error'] = 'Can delete only tasks in the stopped state'
return render(request, 'error.html', {'data': data})
try:
# Get the task (raises if none available including no permission)
task = Task.objects.get(user=request.user, uuid=uuid)
# Delete
task.delete()
# Unset task
data['task'] = None
except Exception as e:
data['error'] = 'Error in deleting the task'
logger.error('Error in deleting task with uuid="{}": "{}"'.format(uuid, e))
return render(request, 'error.html', {'data': data})
elif action=='stop': # or delete,a and if delete also remove object
try:
if task.compute == 'local':
# Delete the Docker container
if standby_supported:
stop_command = 'sudo docker stop {}'.format(task.tid)
else:
stop_command = 'sudo docker stop {} && sudo docker rm {}'.format(task.tid,task.tid)
out = os_shell(stop_command, capture=True)
if out.exit_code != 0:
raise Exception(out.stderr)
elif task.compute == 'demoremote':
# Stop the task remotely
stop_command = 'ssh -4 -o StrictHostKeyChecking=no slurmclusterworker-one "kill -9 {}"'.format(task.pid)
logger.debug(stop_command)
out = os_shell(stop_command, capture=True)
if out.exit_code != 0:
if not 'No such process' in out.stderr:
raise Exception(out.stderr)
else:
stop_command = 'sudo docker stop {} && sudo docker rm {}'.format(task.tid,task.tid)
out = os_shell(stop_command, capture=True)
if out.exit_code != 0:
raise Exception(out.stderr)
elif task.compute == 'demoremote':
# Stop the task remotely
stop_command = 'ssh -4 -o StrictHostKeyChecking=no slurmclusterworker-one "kill -9 {}"'.format(task.pid)
logger.debug(stop_command)
out = os_shell(stop_command, capture=True)
if out.exit_code != 0:
if not 'No such process' in out.stderr:
data['error']= 'Don\'t know how to stop tasks on "{}" compute resource.'.format(task.compute)
return render(request, 'error.html', {'data': data})
# Ok, save status as deleted
task.status = 'stopped'
task.save()
# Check if the tunnel is active and if so kill it
logger.debug('Checking if task "{}" has a running tunnel'.format(task.tid))
check_command = 'ps -ef | grep ":'+str(task.tunnel_port)+':'+str(task.ip)+':'+str(task.port)+'" | grep -v grep | awk \'{print $2}\''
logger.debug(check_command)
out = os_shell(check_command, capture=True)
logger.debug(out)
if out.exit_code == 0:
logger.debug('Task "{}" has a running tunnel, killing it'.format(task.tid))
tunnel_pid = out.stdout
# Kill Tunnel command
kill_tunnel_command= 'kill -9 {}'.format(tunnel_pid)
# Log
logger.debug('Killing tunnel with command: {}'.format(kill_tunnel_command))
# Execute
os_shell(kill_tunnel_command, capture=True)
if out.exit_code != 0:
raise Exception(out.stderr)
else:
data['error']= 'Don\'t know how to stop tasks on "{}" compute resource.'.format(task.compute)
except Exception as e:
data['error'] = 'Error in stopping the task'
logger.error('Error in stopping task with uuid="{}": "{}"'.format(uuid, e))
return render(request, 'error.html', {'data': data})
# Ok, save status as deleted
task.status = 'stopped'
task.save()
# Check if the tunnel is active and if so kill it
logger.debug('Checking if task "{}" has a running tunnel'.format(task.tid))
check_command = 'ps -ef | grep ":'+str(task.tunnel_port)+':'+str(task.ip)+':'+str(task.port)+'" | grep -v grep | awk \'{print $2}\''
logger.debug(check_command)
out = os_shell(check_command, capture=True)
logger.debug(out)
if out.exit_code == 0:
logger.debug('Task "{}" has a running tunnel, killing it'.format(task.tid))
tunnel_pid = out.stdout
# Kill Tunnel command
kill_tunnel_command= 'kill -9 {}'.format(tunnel_pid)
# Log
logger.debug('Killing tunnel with command: {}'.format(kill_tunnel_command))
# Execute
os_shell(kill_tunnel_command, capture=True)
if out.exit_code != 0:
raise Exception(out.stderr)
except Exception as e:
data['error'] = 'Error in stopping the task'
logger.error('Error in stopping task with uuid="{}": "{}"'.format(uuid, e))
return render(request, 'error.html', {'data': data})
elif action=='connect':
# Get the task (raises if none available including no permission)
task = Task.objects.get(user=request.user, uuid=uuid)
# Create task tunnel
if task.compute in ['local', 'demoremote']:
# If there is no tunnel port allocated yet, find one
if not task.tunnel_port:
# Get a free port fot the tunnel:
allocated_tunnel_ports = []
for other_task in Task.objects.all():
if other_task.tunnel_port and not other_task.status in [TaskStatuses.exited, TaskStatuses.stopped]:
allocated_tunnel_ports.append(other_task.tunnel_port)
for port in range(7000, 7006):
if not port in allocated_tunnel_ports:
tunnel_port = port
break
if not tunnel_port:
logger.error('Cannot find a free port for the tunnel for task "{}"'.format(task.tid))
raise ErrorMessage('Cannot find a free port for the tunnel to the task')
task.tunnel_port = tunnel_port
task.save()
# Check if the tunnel is active and if not create it
logger.debug('Checking if task "{}" has a running tunnel'.format(task.tid))
out = os_shell('ps -ef | grep ":{}:{}:{}" | grep -v grep'.format(task.tunnel_port, task.ip, task.port), capture=True)
if out.exit_code == 0:
logger.debug('Task "{}" has a running tunnel, using it'.format(task.tid))
elif action=='connect':
# Create task tunnel
if task.compute in ['local', 'demoremote']:
# If there is no tunnel port allocated yet, find one
if not task.tunnel_port:
# Get a free port fot the tunnel:
allocated_tunnel_ports = []
for other_task in Task.objects.all():
if other_task.tunnel_port and not other_task.status in [TaskStatuses.exited, TaskStatuses.stopped]:
allocated_tunnel_ports.append(other_task.tunnel_port)
for port in range(7000, 7006):
if not port in allocated_tunnel_ports:
tunnel_port = port
break
if not tunnel_port:
logger.error('Cannot find a free port for the tunnel for task "{}"'.format(task.tid))
raise ErrorMessage('Cannot find a free port for the tunnel to the task')
task.tunnel_port = tunnel_port
task.save()
# Check if the tunnel is active and if not create it
logger.debug('Checking if task "{}" has a running tunnel'.format(task.tid))
out = os_shell('ps -ef | grep ":{}:{}:{}" | grep -v grep'.format(task.tunnel_port, task.ip, task.port), capture=True)
if out.exit_code == 0:
logger.debug('Task "{}" has a running tunnel, using it'.format(task.tid))
else:
logger.debug('Task "{}" has no running tunnel, creating it'.format(task.tid))
# Tunnel command
tunnel_command= 'ssh -4 -o StrictHostKeyChecking=no -nNT -L 0.0.0.0:{}:{}:{} localhost & '.format(task.tunnel_port, task.ip, task.port)
background_tunnel_command = 'nohup {} >/dev/null 2>&1 &'.format(tunnel_command)
# Log
logger.debug('Opening tunnel with command: {}'.format(background_tunnel_command))
# Execute
subprocess.Popen(background_tunnel_command, shell=True)
else:
logger.debug('Task "{}" has no running tunnel, creating it'.format(task.tid))
# Tunnel command
tunnel_command= 'ssh -4 -o StrictHostKeyChecking=no -nNT -L 0.0.0.0:{}:{}:{} localhost & '.format(task.tunnel_port, task.ip, task.port)
background_tunnel_command = 'nohup {} >/dev/null 2>&1 &'.format(tunnel_command)
# Log
logger.debug('Opening tunnel with command: {}'.format(background_tunnel_command))
# Execute
subprocess.Popen(background_tunnel_command, shell=True)
raise ErrorMessage('Connecting to tasks on compute "{}" is not supported yet'.format(task.compute))
# Ok, now redirect to the task through the tunnel
from django.shortcuts import redirect
return redirect('http://localhost:{}'.format(task.tunnel_port))
else:
raise ErrorMessage('Connecting to tasks on compute "{}" is not supported yet'.format(task.compute))
except Exception as e:
data['error'] = 'Error in getting the task or performing the required action'
logger.error('Error in getting the task with uuid="{}" or performing the required action: "{}"'.format(uuid, e))
return render(request, 'error.html', {'data': data})
# Ok, now redirect to the task through the tunnel
from django.shortcuts import redirect
return redirect('http://localhost:{}'.format(task.tunnel_port))
# Get all tasks
try:
tasks = Task.objects.filter(user=request.user).order_by('created')
except Exception as e:
data['error'] = 'Error in getting Tasks info'
logger.error('Error in getting Virtual Devices: "{}"'.format(e))
return render(request, 'error.html', {'data': data})
# Do we have to list all the tasks?
if not uuid or (uuid and not details):
# Update task statuses
for task in tasks:
task.update_status()
data['tasks'] = tasks
#----------------
# Task list
#----------------
# Get all tasks
try:
tasks = Task.objects.filter(user=request.user).order_by('created')
except Exception as e:
data['error'] = 'Error in getting Tasks info'
logger.error('Error in getting Virtual Devices: "{}"'.format(e))
return render(request, 'error.html', {'data': data})
# Update task statuses
for task in tasks:
task.update_status()
# Set task and tasks variables
data['task'] = None
data['tasks'] = tasks
return render(request, 'tasks.html', {'data': data})
......@@ -719,14 +746,14 @@ def task_log(request):
if task.compute == 'local':
raise NotImplementedError('Not yet')
# View the Docker container log (attach)
#view_log_command = 'sudo docker stop {} && sudo docker rm {}'.format(task.tid,task.tid)
#out = os_shell(view_log_command, capture=True)
#if out.exit_code != 0:
# raise Exception(out.stderr)
view_log_command = 'sudo docker logs {}'.format(task.tid,)
logger.debug(view_log_command)
out = os_shell(view_log_command, capture=True)
if out.exit_code != 0:
raise Exception(out.stderr)
else:
data['log'] = out.stdout
elif task.compute == 'demoremote':
......@@ -772,33 +799,39 @@ def containers(request):
# Do we have to operate on a specific container?
if uuid:
try:
# Get the task (raises if none available including no permission)
container = Container.objects.get(uuid=uuid)
# Get the container (raises if none available including no permission)
try:
container = Container.objects.get(uuid=uuid)
except Container.DoesNotExist:
raise ErrorMessage('Container does not exists or no access rights')
if container.user and container.user != request.user:
raise ErrorMessage('Container does not exists or no access rights')
data['container'] = container
#-------------------
# Container actions
#-------------------
if action and action=='delete':
# Delete
container.delete()
# Unset container to load the list again
data['container'] = None
except Exception as e:
data['error'] = 'Error in getting the container or performing the required action'
logger.error('Error in getting the container with uuid="{}" or performing the required action: "{}"'.format(uuid, e))
return render(request, 'error.html', {'data': data})
# Do we have to get he list of containers?
if not uuid:
# Get containers configured on the platform, both private to this user and public
data['user_containers'] = Container.objects.filter(user=request.user)
data['platform_containers'] = Container.objects.filter(user=None)
#----------------
# Container list
#----------------
logger.debug(data)
# Get containers configured on the platform, both private to this user and public
data['user_containers'] = Container.objects.filter(user=request.user)
data['platform_containers'] = Container.objects.filter(user=None)
return render(request, 'containers.html', {'data': data})
......@@ -827,14 +860,18 @@ def add_container(request):
if not container_type:
raise ErrorMessage('No container type given')
if not container_type in SUPPORTED_CONTAINER_TYPES:
raise ErrorMessage('No valid container type')
raise ErrorMessage('No valid container type, got "{}"'.format(container_type))
# Container registry
container_registry = request.POST.get('container_registry', None)
if not container_registry:
raise ErrorMessage('No registry type given')
if not container_registry in SUPPORTED_REGISTRIES:
raise ErrorMessage('No valid registry')
raise ErrorMessage('No valid container registry, got "{}"'.format(container_registry))
# Check container type vs container registry compatibility
if container_type+':'+container_registry in UNSUPPORTED_TYPES_VS_REGISTRIES:
raise ErrorMessage('Sorry, container type "{}" is not compatible with registry type "{}"'.format(container_type, container_registry))
# Container service ports
container_service_ports = request.POST.get('container_service_ports', None)
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment