From 119a2a63f2230f53e849141d8083e7c862f9440b Mon Sep 17 00:00:00 2001 From: Stefano Alberto Russo <stefano.russo@gmail.com> Date: Thu, 20 Feb 2020 14:57:24 +0100 Subject: [PATCH] Added preliminary support for viewing task terminal output (log). move from short_uuid to id. Minor fixes. --- images/webapp/code/rosetta/base_app/models.py | 11 +- .../base_app/templates/containers.html | 94 +++++++++--- .../rosetta/base_app/templates/header.html | 4 + .../rosetta/base_app/templates/task_log.html | 51 +++++++ .../rosetta/base_app/templates/tasks.html | 26 ++-- images/webapp/code/rosetta/base_app/views.py | 138 ++++++++++++------ images/webapp/code/rosetta/urls.py | 1 + 7 files changed, 251 insertions(+), 74 deletions(-) create mode 100644 images/webapp/code/rosetta/base_app/templates/task_log.html diff --git a/images/webapp/code/rosetta/base_app/models.py b/images/webapp/code/rosetta/base_app/models.py index 9a20326..f9c811c 100644 --- a/images/webapp/code/rosetta/base_app/models.py +++ b/images/webapp/code/rosetta/base_app/models.py @@ -106,10 +106,11 @@ class Task(models.Model): self.save() @property - def short_uuid(self): + def id(self): return str(self.uuid).split('-')[0] + #========================= # Containers #========================= @@ -128,4 +129,12 @@ class Container(models.Model): def __str__(self): return str('Container of type "{}" with image "{}" from registry "{}" of user "{}"'.format(self.type, self.image, self.registry, self.user)) + @property + def id(self): + return str(self.uuid).split('-')[0] + + #@property + #def name(self): + # return self.image.split(':')[0].replace('_',' ').replace('-', ' ').replace('/', ' ').title() + diff --git a/images/webapp/code/rosetta/base_app/templates/containers.html b/images/webapp/code/rosetta/base_app/templates/containers.html index f5b893c..f94eb33 100644 --- a/images/webapp/code/rosetta/base_app/templates/containers.html +++ b/images/webapp/code/rosetta/base_app/templates/containers.html @@ -8,30 +8,78 @@ <div class="container"> <div class="dashboard"> <div class="span8 offset2"> - <h1>Container List</h1> + <h1><a href="/containers">Container List</a> {% if data.container %}> {{ data.container.id }} {% endif %} </h1> <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 /> + + {% else %} + {% for container in data.platform_containers %} + <table class="dashboard"> <tr> - <td><b>Container image</b></td> - <td>{{ container.image }} (platform)</td> + <td><b>ID</b></td> + <td><a href="?uuid={{ container.uuid }}">{{ container.id }}</a></td> </tr> <tr> - <td><b>Container type</b></td> + <td><b>Image</b></td> + <td>{{ container.image }}</td> + </tr> + + <tr> + <td><b>Type</b></td> <td>{{ container.type }}</td> </tr> <tr> - <td><b>Container registry</b></td> + <td><b>Owner</b></td> + <td>Platform</td> + </tr> + + <tr> + <td><b>Registry</b></td> <td>{{ container.registry }}</td> </tr> <tr> - <td><b>Container service ports</b></td> + <td><b>Service port(s)</b></td> <td>{{ container.service_ports}}</td> </tr> @@ -44,22 +92,32 @@ <table class="dashboard"> <tr> - <td><b>Container image</b></td> - <td>{{ container.image }} (user)</td> + <td><b>ID</b></td> + <td><a href="?uuid={{ container.uuid }}">{{ container.id }}</a></td> </tr> <tr> - <td><b>Container type</b></td> + <td><b>Image</b></td> + <td>{{ container.image }}</td> + </tr> + + <tr> + <td><b>Type</b></td> <td>{{ container.type }}</td> </tr> <tr> - <td><b>Container registry</b></td> + <td><b>Owner</b></td> + <td>{{container.user}}</td> + </tr> + + <tr> + <td><b>Registry</b></td> <td>{{ container.registry }}</td> </tr> <tr> - <td><b>Container service ports</b></td> + <td><b>Service port(s)</b></td> <td>{{ container.service_ports}}</td> </tr> @@ -74,13 +132,13 @@ <br /> <a href="/add_container">Add new...</a> - <br/> - <br/> - <br/> - <br/> - <br/> - <br/> + {% endif %} + <br /> + <br /> + <br /> + <br /> + </div> </div> </div> diff --git a/images/webapp/code/rosetta/base_app/templates/header.html b/images/webapp/code/rosetta/base_app/templates/header.html index 33ecdac..29d46e1 100644 --- a/images/webapp/code/rosetta/base_app/templates/header.html +++ b/images/webapp/code/rosetta/base_app/templates/header.html @@ -9,6 +9,10 @@ <meta name="description" content=""> <meta name="author" content=""> + {% if refresh %} + <meta http-equiv="refresh" content="{{refresh}}" > + {% endif %} + {% if data.title %} <title>{{data.title}}</title> {% else %} diff --git a/images/webapp/code/rosetta/base_app/templates/task_log.html b/images/webapp/code/rosetta/base_app/templates/task_log.html new file mode 100644 index 0000000..071f285 --- /dev/null +++ b/images/webapp/code/rosetta/base_app/templates/task_log.html @@ -0,0 +1,51 @@ +{% load static %} +{% include "header.html" with refresh=data.refresh %} +{% include "navigation.html" with main_path='/main/' %} + +<br/> +<br/> + +<div class="container"> + <div class="dashboard"> + <div class="span8 offset2"> + + <h1><a href="/tasks/">Task List</a> > {{ data.task.id }} Log</h1> + + <hr> + + + <b>ID:</b> {{ data.task.id }} + <b>Status:</b> {{ data.task.status }} + <b>Auto refresh:{{data.refresh}}</b> + {% if not data.refresh %} OFF {% else %} <a href="?uuid={{data.task.uuid}}">OFF</a> {% endif %} | + {% if data.refresh == "3" %} 3s {% else %} <a href="?uuid={{data.task.uuid}}&refresh=3">3s</a> {% endif %} | + {% if data.refresh == "10" %} 10s{% else %} <a href="?uuid={{data.task.uuid}}}&refresh=10">10s</a> {% endif %} | + {% if data.refresh == "60" %} 60s{% else %} <a href="?uuid={{data.task.uuid}}}&refresh=60">60s</a> {% endif %} + + <pre id="output" style="border: 1px solid #a0a0a0; width: 100%; height: 500px; background-color:black; color:white; white-space: pre-wrap; white-space: -moz-pre-wrap; white-space: white-space: -o-pre-wrap; word-wrap: break-word;">{{ data.log }}</pre> + + + <br/> + <br/> + <br/> + <br/> + <br/> + <br/> + + </div> + </div> +</div> + +{% include "footer.html" %} + + +<script> +jQuery( function(){ + var pre = jQuery("#output"); + pre.scrollTop( pre.prop("scrollHeight") ); + }); +</script> + + + + diff --git a/images/webapp/code/rosetta/base_app/templates/tasks.html b/images/webapp/code/rosetta/base_app/templates/tasks.html index 743bc12..f517882 100644 --- a/images/webapp/code/rosetta/base_app/templates/tasks.html +++ b/images/webapp/code/rosetta/base_app/templates/tasks.html @@ -8,12 +8,8 @@ <div class="container"> <div class="dashboard"> <div class="span8 offset2"> - {% if not data.sid %} <h1>Task List</h1> - {% else %} - <h1>Task Info</h1> - {% endif %} - + <hr> @@ -21,27 +17,27 @@ <table class="dashboard"> <tr> - <td><b>Task id</b></td> - <td>{{ task.short_uuid }}</td> + <td><b>ID</b></td> + <td>{{ task.id }}</td> </tr> <tr> - <td><b>Task name</b></td> + <td><b>Name</b></td> <td>{{ task.name }}</td> </tr> <tr> - <td><b>Task container</b></td> - <td>{{ task.container.image }}</td> + <td><b>Container</b></td> + <td><a href="">{{ task.container.image }}</a></td> </tr> <tr> - <td><b>Task status</b></td> + <td><b>Status</b></td> <td>{{ task.status }}</td> </tr> <tr> - <td><b>Task created</b></td> + <td><b>Created at</b></td> <td>{{ task.created }}</td> </tr> @@ -51,12 +47,12 @@ </tr> --> <tr> - <td><b>Task ip</b></td> + <td><b>IP</b></td> <td>{{ task.ip}}</td> </tr> <tr> - <td><b>Task port</b></td> + <td><b>Port</b></td> <td>{{ task.port }}</td> </tr> @@ -83,8 +79,10 @@ {% 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> diff --git a/images/webapp/code/rosetta/base_app/views.py b/images/webapp/code/rosetta/base_app/views.py index 3fdb3b4..8e62bb5 100644 --- a/images/webapp/code/rosetta/base_app/views.py +++ b/images/webapp/code/rosetta/base_app/views.py @@ -404,9 +404,6 @@ def tasks(request): # Delete task.delete() - # Unset uuid to load the list again - uuid = None - except Exception as e: data['error'] = 'Error in deleting the task' logger.error('Error in deleting task with uuid="{}": "{}"'.format(uuid, e)) @@ -434,7 +431,8 @@ def tasks(request): logger.debug(stop_command) out = os_shell(stop_command, capture=True) if out.exit_code != 0: - raise Exception(out.stderr) + if not 'No such process' in out.stderr: + raise Exception(out.stderr) else: data['error']= 'Don\'t know how to stop tasks on "{}" compute resource.'.format(task.compute) @@ -469,9 +467,6 @@ def tasks(request): logger.error('Error in stopping task with uuid="{}": "{}"'.format(uuid, e)) return render(request, 'error.html', {'data': data}) - # Unset uuid to load the list again - uuid = None - elif action=='connect': # Get the task (raises if none available including no permission) @@ -524,26 +519,17 @@ def tasks(request): else: 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)) - # Get all task(s) - if uuid: - try: - tasks = [Task.objects.get(user=request.user, uuid=uuid)] - except Exception as e: - data['error'] = 'Error in getting info for Task "{}"'.format(uuid) - logger.error('Error in getting Virtual Device with uuid="{}": "{}"'.format(uuid, e)) - return render(request, 'error.html', {'data': data}) - else: - try: - tasks = Task.objects.filter(user=request.user).order_by('created') - except Exception as e: - data['error'] = 'Error in getting Virtual Devices info' - logger.error('Error in getting Virtual Devices: "{}"'.format(e)) - return render(request, 'error.html', {'data': data}) + # 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: @@ -660,7 +646,7 @@ def create_task(request): # 1) Run the singularity container on slurmclusterworker-one (non blocking) - run_command = 'ssh -4 -o StrictHostKeyChecking=no slurmclusterworker-one "export SINGULARITY_NOHTTPS=true && exec nohup singularity run --pid --writable-tmpfs --containall --cleanenv docker://dregistry:5000/rosetta/metadesktop &> /dev/null & echo \$!"' + run_command = 'ssh -4 -o StrictHostKeyChecking=no slurmclusterworker-one "export SINGULARITY_NOHTTPS=true && exec nohup singularity run --pid --writable-tmpfs --containall --cleanenv docker://dregistry:5000/rosetta/metadesktop &> /tmp/{}.log & echo \$!"'.format(task.uuid) out = os_shell(run_command, capture=True) if out.exit_code != 0: raise Exception(out.stderr) @@ -701,6 +687,71 @@ def create_task(request): return render(request, 'create_task.html', {'data': data}) +#========================= +# Task log +#========================= + +@private_view +def task_log(request): + + # Init data + data={} + data['user'] = request.user + data['profile'] = Profile.objects.get(user=request.user) + data['title'] = 'Tasks' + + # Get uuid and refresh if any + uuid = request.GET.get('uuid', None) + refresh = request.GET.get('refresh', None) + + if not uuid: + return render(request, 'error.html', {'data': 'uuid not set'}) + + # Get the task (raises if none available including no permission) + task = Task.objects.get(user=request.user, uuid=uuid) + + # Set back task and refresh + data['task'] = task + data['refresh'] = refresh + + # Get the log + try: + + 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) + + elif task.compute == 'demoremote': + + # View the Singularity container log + view_log_command = 'ssh -4 -o StrictHostKeyChecking=no slurmclusterworker-one "cat /tmp/{}.log"'.format(task.uuid) + 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 + + else: + data['error']= 'Don\'t know how to view task logs on "{}" compute resource.'.format(task.compute) + return render(request, 'error.html', {'data': data}) + + except Exception as e: + data['error'] = 'Error in viewing task log' + logger.error('Error in viewing task log with uuid="{}": "{}"'.format(uuid, e)) + return render(request, 'error.html', {'data': data}) + + return render(request, 'task_log.html', {'data': data}) + + + #========================= @@ -714,35 +765,40 @@ def containers(request): data={} data['user'] = request.user data['profile'] = Profile.objects.get(user=request.user) - data['title'] = 'Containers' - data['name'] = request.POST.get('name',None) # Get action if any action = request.GET.get('action', None) - uuid = request.GET.get('uuid', None) + uuid = request.GET.get('uuid', None) + # Do we have to operate on a specific container? + if uuid: + try: - if action and uuid: + # Get the task (raises if none available including no permission) + container = Container.objects.get(uuid=uuid) + data['container'] = container - if action=='delete': - try: - # Get the task (raises if none available including no permission) - container = Container.objects.get(user=request.user, uuid=uuid) + if action and action=='delete': # Delete container.delete() - # Unset uuid to load the list again - uuid = None + # Unset container to load the list again + data['container'] = None - except Exception as e: - data['error'] = 'Error in deleting the container' - logger.error('Error in deleting task with uuid="{}": "{}"'.format(uuid, e)) - return render(request, 'error.html', {'data': data}) + 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}) - # 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) + # 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) + + logger.debug(data) return render(request, 'containers.html', {'data': data}) diff --git a/images/webapp/code/rosetta/urls.py b/images/webapp/code/rosetta/urls.py index 694dc8f..c9ed565 100644 --- a/images/webapp/code/rosetta/urls.py +++ b/images/webapp/code/rosetta/urls.py @@ -42,6 +42,7 @@ urlpatterns = [ url(r'^account/$', base_app_views.account), url(r'^tasks/$', base_app_views.tasks), url(r'^create_task/$', base_app_views.create_task), + url(r'^task_log/$', base_app_views.task_log), url(r'^computes/$', base_app_views.computes), url(r'^add_compute/$', base_app_views.add_compute), url(r'^containers/$', base_app_views.containers), -- GitLab