From 90c98604c7f5b6328ba3c7f378c04bf9d1a54603 Mon Sep 17 00:00:00 2001 From: Stefano Alberto Russo <stefano.russo@gmail.com> Date: Tue, 11 Feb 2020 16:43:02 +0100 Subject: [PATCH] Moved to uuids for primary keys. Added support for deleting a contianer. Minor fixes. --- images/webapp/code/rosetta/base_app/models.py | 29 +- .../base_app/templates/add_container.html | 2 +- .../base_app/templates/containers.html | 7 +- .../base_app/templates/create_task.html | 8 +- images/webapp/code/rosetta/base_app/views.py | 283 ++++++++++-------- 5 files changed, 179 insertions(+), 150 deletions(-) diff --git a/images/webapp/code/rosetta/base_app/models.py b/images/webapp/code/rosetta/base_app/models.py index 6c1603c..9a20326 100644 --- a/images/webapp/code/rosetta/base_app/models.py +++ b/images/webapp/code/rosetta/base_app/models.py @@ -25,6 +25,7 @@ class TaskStatuses(object): #========================= class Profile(models.Model): + uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) user = models.OneToOneField(User, on_delete=models.CASCADE) timezone = models.CharField('User Timezone', max_length=36, default='UTC') authtoken = models.CharField('User auth token', max_length=36, blank=True, null=True) @@ -43,6 +44,7 @@ class Profile(models.Model): #========================= class LoginToken(models.Model): + uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) user = models.OneToOneField(User, on_delete=models.CASCADE) token = models.CharField('Login token', max_length=36) @@ -51,16 +53,16 @@ class LoginToken(models.Model): # Tasks #========================= class Task(models.Model): - user = models.ForeignKey(User, related_name='+', on_delete=models.CASCADE) - tid = models.CharField('Task ID', max_length=64, blank=False, null=False) - uuid = models.CharField('Task UUID', max_length=36, blank=False, null=False) - name = models.CharField('Task name', max_length=36, blank=False, null=False) - status = models.CharField('Task status', max_length=36, blank=True, null=True) - created = models.DateTimeField('Created on', default=timezone.now) - compute = models.CharField('Task compute', max_length=36, blank=True, null=True) - pid = models.IntegerField('Task pid', blank=True, null=True) - port = models.IntegerField('Task port', blank=True, null=True) - ip = models.CharField('Task ip address', max_length=36, blank=True, null=True) + uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) + user = models.ForeignKey(User, related_name='+', on_delete=models.CASCADE) + tid = models.CharField('Task ID', max_length=64, blank=False, null=False) + name = models.CharField('Task name', max_length=36, blank=False, null=False) + status = models.CharField('Task status', max_length=36, blank=True, null=True) + created = models.DateTimeField('Created on', default=timezone.now) + compute = models.CharField('Task compute', max_length=36, blank=True, null=True) + pid = models.IntegerField('Task pid', blank=True, null=True) + port = models.IntegerField('Task port', blank=True, null=True) + ip = models.CharField('Task ip address', max_length=36, blank=True, null=True) tunnel_port = models.IntegerField('Task tunnel port', blank=True, null=True) # Links @@ -105,7 +107,7 @@ class Task(models.Model): @property def short_uuid(self): - return self.uuid.split('-')[0] + return str(self.uuid).split('-')[0] #========================= @@ -113,8 +115,9 @@ class Task(models.Model): #========================= class Container(models.Model): - #uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) - user = models.ForeignKey(User, related_name='+', on_delete=models.CASCADE, null=True) + uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) + user = models.ForeignKey(User, related_name='+', on_delete=models.CASCADE, null=True) + # If a container has no user, it will be available to anyone. Can be created, edited and deleted only by admins. image = models.CharField('Container image', max_length=255, blank=False, null=False) type = models.CharField('Container type', max_length=36, blank=False, null=False) diff --git a/images/webapp/code/rosetta/base_app/templates/add_container.html b/images/webapp/code/rosetta/base_app/templates/add_container.html index 0ca208b..b4758e8 100644 --- a/images/webapp/code/rosetta/base_app/templates/add_container.html +++ b/images/webapp/code/rosetta/base_app/templates/add_container.html @@ -78,7 +78,7 @@ {% else %} - Ok, Container added. Go back to your <a href="/tasks">tasks list</a>. + Ok, Container added. Go back to your <a href="/containers">container list</a>. {% endif %} diff --git a/images/webapp/code/rosetta/base_app/templates/containers.html b/images/webapp/code/rosetta/base_app/templates/containers.html index 24fd1b1..f5b893c 100644 --- a/images/webapp/code/rosetta/base_app/templates/containers.html +++ b/images/webapp/code/rosetta/base_app/templates/containers.html @@ -8,7 +8,7 @@ <div class="container"> <div class="dashboard"> <div class="span8 offset2"> - <h1>Containers List</h1> + <h1>Container List</h1> <hr/> @@ -63,6 +63,11 @@ <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> <br /> {% endfor %} diff --git a/images/webapp/code/rosetta/base_app/templates/create_task.html b/images/webapp/code/rosetta/base_app/templates/create_task.html index 4148c3d..c1cd016 100644 --- a/images/webapp/code/rosetta/base_app/templates/create_task.html +++ b/images/webapp/code/rosetta/base_app/templates/create_task.html @@ -46,15 +46,15 @@ <tr> <td><b>Task container</b></td><td> - <select name="task_container_id" > + <select name="task_container_uuid" > <!-- <option value="metadesktop" selected>Meta Desktop</option> <option value="astroccok">Astrocook</option> <option value="gadgetviewer">Gadget Viewer</option> --> {% for container in data.platform_containers %} - <option value="{{container.id}}">{{container.image}} (platform)</option> --> + <option value="{{container.uuid}}">{{container.image}} (platform)</option> --> {% endfor %} {% for container in data.user_containers %} - <option value="{{container.id}}">{{container.image}} (user)</option> --> + <option value="{{container.uuid}}">{{container.image}} (user)</option> --> {% endfor %} </select> @@ -103,7 +103,7 @@ {% else %} - Ok, task created. Go back to your <a href="/tasks">tasks list</a>. + Ok, task created. Go back to your <a href="/tasks">task list</a>. {% endif %} diff --git a/images/webapp/code/rosetta/base_app/views.py b/images/webapp/code/rosetta/base_app/views.py index e185e0f..3fdb3b4 100644 --- a/images/webapp/code/rosetta/base_app/views.py +++ b/images/webapp/code/rosetta/base_app/views.py @@ -7,7 +7,7 @@ import json import socket import os import subprocess - + # Django imports from django.conf import settings from django.shortcuts import render @@ -32,7 +32,7 @@ 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', 'signularity_hub'] TASK_DATA_DIR = "/data" #========================= @@ -45,7 +45,7 @@ def public_view(wrapped_view): # -------------- START Public/private common code -------------- try: log_user_activity("DEBUG", "Called", request, wrapped_view.__name__) - + # Try to get the templates from view kwargs # Todo: Python3 compatibility: https://stackoverflow.com/questions/2677185/how-can-i-read-a-functions-signature-including-default-argument-values @@ -55,10 +55,10 @@ def public_view(wrapped_view): template = argSpec.defaults[0] else: template = None - + # Call wrapped view data = wrapped_view(request, *argv, **kwargs) - + if not isinstance(data, HttpResponse): if template: #logger.debug('using template + data ("{}","{}")'.format(template,data)) @@ -72,21 +72,21 @@ def public_view(wrapped_view): except Exception as e: if isinstance(e, ErrorMessage): error_text = str(e) - else: - + else: + # Raise te exception if we are in debug mode if settings.DEBUG: raise - + # Otherwise, else: - + # first log the exception logger.error(format_exception(e)) - + # and then mask it. error_text = 'something went wrong' - + data = {'user': request.user, 'title': 'Error', 'error' : 'Error: "{}"'.format(error_text)} @@ -95,7 +95,7 @@ def public_view(wrapped_view): return render(request, template, {'data': data}) else: return render(request, 'error.html', {'data': data}) - # -------------- END Public/private common code -------------- + # -------------- END Public/private common code -------------- return public_view_wrapper # Private view @@ -105,20 +105,20 @@ def private_view(wrapped_view): # -------------- START Public/private common code -------------- log_user_activity("DEBUG", "Called", request, wrapped_view.__name__) try: - + # Try to get the templates from view kwargs # Todo: Python3 compatibility: https://stackoverflow.com/questions/2677185/how-can-i-read-a-functions-signature-including-default-argument-values - + argSpec=inspect.getargspec(wrapped_view) - + if 'template' in argSpec.args: template = argSpec.defaults[0] else: template = None - + # Call wrapped view data = wrapped_view(request, *argv, **kwargs) - + if not isinstance(data, HttpResponse): if template: #logger.debug('using template + data ("{}","{}")'.format(template,data)) @@ -128,25 +128,25 @@ def private_view(wrapped_view): else: #logger.debug('using returned httpresponse') return data - - except Exception as e: + + except Exception as e: if isinstance(e, ErrorMessage): error_text = str(e) - else: - + else: + # Raise te exception if we are in debug mode if settings.DEBUG: raise - + # Otherwise, else: - + # first log the exception logger.error(format_exception(e)) - + # and then mask it. error_text = 'something went wrong' - + data = {'user': request.user, 'title': 'Error', 'error' : 'Error: "{}"'.format(error_text)} @@ -159,7 +159,7 @@ def private_view(wrapped_view): else: log_user_activity("DEBUG", "Redirecting to login since not authenticated", request) - return HttpResponseRedirect('/login') + return HttpResponseRedirect('/login') return private_view_wrapper @@ -167,14 +167,14 @@ def private_view(wrapped_view): @public_view def login_view(request): - + data = {} data['title'] = "{} - Login".format(settings.DJANGO_PROJECT_NAME) # If authenticated user reloads the main URL if request.method == 'GET' and request.user.is_authenticated: return HttpResponseRedirect('/main/') - + # If unauthenticated user tries to log in if request.method == 'POST': if not request.user.is_authenticated: @@ -182,7 +182,7 @@ def login_view(request): password = request.POST.get('password') # Use Django's machinery to attempt to see if the username/password # combination is valid - a User object is returned if it is. - + if "@" in username: # Get the username from the email try: @@ -195,7 +195,7 @@ def login_view(request): # Return here, we don't want to give any hints about existing users data['success'] = 'Ok, if we have your data you will receive a login link by email shortly.' return render(request, 'success.html', {'data': data}) - + if password: user = authenticate(username=username, password=password) if user: @@ -204,16 +204,16 @@ def login_view(request): else: raise ErrorMessage('Check email and password') else: - + # If empty password, send mail with login token logger.debug('Sending login token via mail to {}'.format(user.email)) - + token = uuid.uuid4() - + # Create token or update if existent (and never used) try: loginToken = LoginToken.objects.get(user=user) - except LoginToken.DoesNotExist: + except LoginToken.DoesNotExist: LoginToken.objects.create(user=user, token=token) else: loginToken.token = token @@ -223,49 +223,49 @@ def login_view(request): except Exception as e: logger.error(format_exception(e)) raise ErrorMessage('Something went wrong. Please retry later.') - + # Return here, we don't want to give any hints about existing users data['success'] = 'Ok, if we have your data you will receive a login link by email shortly.' return render(request, 'success.html', {'data': data}) - - + + else: # This should never happen. # User tried to log-in while already logged in: log him out and then render the login - logout(request) - + logout(request) + else: # If we are logging in through a token token = request.GET.get('token', None) if token: - + loginTokens = LoginToken.objects.filter(token=token) - + if not loginTokens: raise ErrorMessage('Token not valid or expired') - - + + if len(loginTokens) > 1: raise Exception('Consistency error: more than one user with the same login token ({})'.format(len(loginTokens))) - + # Use the first and only token (todo: use the objects.get and correctly handle its exceptions) loginToken = loginTokens[0] - + # Get the user from the table user = loginToken.user - + # Set auth backend user.backend = 'django.contrib.auth.backends.ModelBackend' - + # Ok, log in the user login(request, user) loginToken.delete() - + # Now redirect to site return HttpResponseRedirect('/main/') - + # All other cases, render the login page again with no other data than title return render(request, 'login.html', {'data': data}) @@ -277,7 +277,7 @@ def logout_view(request): @public_view def entrypoint(request): - return HttpResponseRedirect('/main/') + return HttpResponseRedirect('/main/') @public_view def main_view(request): @@ -303,7 +303,7 @@ def account(request): try: profile = Profile.objects.get(user=request.user) except Profile.DoesNotExist: - profile = Profile.objects.create(user=request.user) + profile = Profile.objects.create(user=request.user) data['profile'] = profile data['title'] = "{} - Account".format(settings.DJANGO_PROJECT_NAME) @@ -313,50 +313,50 @@ def account(request): edit = request.GET.get('edit', None) data['edit'] = edit value = request.POST.get('value', None) - + # Fix None if value and value.upper() == 'NONE': value = None if edit and edit.upper() == 'NONE': edit = None - + # Edit values if edit and value: try: logger.info('Setting "{}" to "{}"'.format(edit,value)) - + # Timezone if edit=='timezone' and value: # Validate timezonize(value) profile.timezone = value profile.save() - + # Email elif edit=='email' and value: request.user.email=value request.user.save() - + # Password elif edit=='password' and value: request.user.set_password(value) request.user.save() - + # API key elif edit=='apikey' and value: profile.apikey=value profile.save() - + # Plan elif edit=='plan' and value: profile.plan=value profile.save() - + # Generic property elif edit and value: raise Exception('Attribute to change is not valid') - - + + except Exception as e: logger.error(format_exception(e)) data['error'] = 'The property "{}" does not exists or the value "{}" is not valid.'.format(edit, value) @@ -379,7 +379,7 @@ def tasks(request): data['user'] = request.user data['profile'] = Profile.objects.get(user=request.user) data['title'] = 'Tasks' - + # Get action if any action = request.GET.get('action', None) uuid = request.GET.get('uuid', None) @@ -396,11 +396,11 @@ def tasks(request): 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}) + 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() @@ -410,39 +410,38 @@ def tasks(request): 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}) - + return render(request, 'error.html', {'data': data}) + elif action=='stop': # or delete,a and if delete also remove object try: if task.compute == 'local': - str_shortuuid = task.uuid.split('-')[0] - + # 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: + 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 out.exit_code != 0: raise Exception(out.stderr) - + else: data['error']= 'Don\'t know how to stop tasks on "{}" compute resource.'.format(task.compute) - return render(request, 'error.html', {'data': data}) - + return render(request, 'error.html', {'data': data}) + # Ok, save status as deleted - task.status = 'stopped' + task.status = 'stopped' task.save() # Check if the tunnel is active and if so kill it @@ -456,32 +455,32 @@ def tasks(request): 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: + 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}) + 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) 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 there is no tunnel port allocated yet, find one if not task.tunnel_port: # Get a free port fot the tunnel: @@ -489,13 +488,13 @@ def tasks(request): 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)) + 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 @@ -504,24 +503,24 @@ def tasks(request): # 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: raise ErrorMessage('Connecting to tasks on compute "{}" is not supported yet'.format(task.compute)) @@ -537,14 +536,14 @@ def tasks(request): 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}) + 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}) + return render(request, 'error.html', {'data': data}) # Update task statuses for task in tasks: @@ -567,7 +566,7 @@ def create_task(request): data['user'] = request.user data['profile'] = Profile.objects.get(user=request.user) data['title'] = 'New Task' - + # 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) @@ -576,28 +575,28 @@ def create_task(request): task_name = request.POST.get('task_name', None) if task_name: - + # Task container - task_container_id = request.POST.get('task_container_id', None) + task_container_uuid = request.POST.get('task_container_uuid', None) # Get the container object, first try as public and then as private try: - task_container = Container.objects.get(id=task_container_id, user=None) + task_container = Container.objects.get(uuid=task_container_uuid, user=None) except Container.DoesNotExist: try: - task_container = Container.objects.get(id=task_container_id, user=request.user) + task_container = Container.objects.get(uuid=task_container_uuid, user=request.user) except Container.DoesNotExist: - raise Exception('Consistency error, container with id "{}" does not exists or user "{}" does not have access rights'.format(task_container_id, request.user.email)) + raise Exception('Consistency error, container with uuid "{}" does not exists or user "{}" does not have access rights'.format(task_container_uuid, request.user.email)) # Compute task_compute = request.POST.get('task_compute', None) if task_compute not in ['local', 'demoremote']: raise ErrorMessage('Unknown compute resource "{}') - + # Generate the task uuid str_uuid = str(uuid.uuid4()) str_shortuuid = str_uuid.split('-')[0] - + # Create the task object task = Task.objects.create(uuid = str_uuid, user = request.user, @@ -605,54 +604,54 @@ def create_task(request): status = TaskStatuses.created, container = task_container, compute = task_compute) - - + + # Actually start tasks try: - if task_compute == 'local': + if task_compute == 'local': - # Get our ip address + # Get our ip address #import netifaces #netifaces.ifaddresses('eth0') - #backend_ip = netifaces.ifaddresses('eth0')[netifaces.AF_INET][0]['addr'] - - # Init run command #--cap-add=NET_ADMIN --cap-add=NET_RAW + #backend_ip = netifaces.ifaddresses('eth0')[netifaces.AF_INET][0]['addr'] + + # Init run command #--cap-add=NET_ADMIN --cap-add=NET_RAW run_command = 'sudo docker run --network=rosetta_default --name rosetta-task-{}'.format( str_shortuuid) - + # Data volume run_command += ' -v {}/task-{}:/data'.format(TASK_DATA_DIR, str_shortuuid) - + # Set registry string if task.container.registry == 'local': registry_string = 'localhost:5000/' else: registry_string = '' - + # Host name, image entry command run_command += ' -h task-{} -d -t {}{}'.format(str_shortuuid, registry_string, task.container.image) - + # Run the task Debug logger.debug('Running new task with command="{}"'.format(run_command)) out = os_shell(run_command, capture=True) - if out.exit_code != 0: + if out.exit_code != 0: raise Exception(out.stderr) else: task_tid = out.stdout logger.debug('Created task with id: "{}"'.format(task_tid)) - + # Get task IP address out = os_shell('sudo docker inspect --format \'{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}\' ' + task_tid + ' | tail -n1', capture=True) if out.exit_code != 0: raise Exception('Error: ' + out.stderr) task_ip = out.stdout - + # Set fields task.tid = task_tid task.status = TaskStatuses.running task.ip = task_ip task.port = int(task.container.service_ports.split(',')[0]) - + # Save task.save() @@ -663,9 +662,9 @@ 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 \$!"' out = os_shell(run_command, capture=True) - if out.exit_code != 0: + if out.exit_code != 0: raise Exception(out.stderr) - + # Save pid echoed by the command above task_pid = out.stdout @@ -676,17 +675,17 @@ def create_task(request): if out.exit_code != 0: raise Exception('Error: ' + out.stderr) task_ip = out.stdout - + # Set fields task.tid = task.uuid task.status = TaskStatuses.running task.ip = task_ip task.pid = task_pid task.port = int(task.container.service_ports.split(',')[0]) - + # Save task.save() - + else: raise Exception('Consistency exception: invalid compute resource "{}'.format(task_compute)) @@ -695,7 +694,7 @@ def create_task(request): data['error'] = 'Error in creating new Task.' logger.error(e) return render(request, 'error.html', {'data': data}) - + # Set created switch data['created'] = True @@ -715,9 +714,32 @@ def containers(request): data={} data['user'] = request.user data['profile'] = Profile.objects.get(user=request.user) - data['title'] = 'Add compute' + 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) + + + if action and uuid: + + if action=='delete': + try: + # Get the task (raises if none available including no permission) + container = Container.objects.get(user=request.user, uuid=uuid) + + # Delete + container.delete() + + # Unset uuid to load the list again + uuid = 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}) + # 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) @@ -738,12 +760,12 @@ def add_container(request): data['user'] = request.user data['profile'] = Profile.objects.get(user=request.user) data['title'] = 'Add container' - + # Container image if any container_image = request.POST.get('container_image',None) if container_image: - + # Container type container_type = request.POST.get('container_type', None) if not container_type: @@ -760,7 +782,7 @@ def add_container(request): # Container service ports container_service_ports = request.POST.get('container_service_ports', None) - + try: for container_service_port in container_service_ports: int(container_service_port) @@ -796,7 +818,7 @@ def computes(request): data['profile'] = Profile.objects.get(user=request.user) data['title'] = 'Add compute' data['name'] = request.POST.get('name',None) - + return render(request, 'computes.html', {'data': data}) @@ -813,7 +835,6 @@ def add_compute(request): data['profile'] = Profile.objects.get(user=request.user) data['title'] = 'Add compute' data['name'] = request.POST.get('name',None) - - return render(request, 'add_compute.html', {'data': data}) + return render(request, 'add_compute.html', {'data': data}) -- GitLab