diff --git a/services/webapp/code/rosetta/base_app/management/commands/base_app_populate.py b/services/webapp/code/rosetta/base_app/management/commands/base_app_populate.py index 746ee319c46e4cbff782e157d8cc54fc90ef191c..af7f6664adbe8e6b66cb4b5e375537ca29aaaaa2 100644 --- a/services/webapp/code/rosetta/base_app/management/commands/base_app_populate.py +++ b/services/webapp/code/rosetta/base_app/management/commands/base_app_populate.py @@ -1,6 +1,6 @@ from django.core.management.base import BaseCommand from django.contrib.auth.models import User -from ...models import Profile, Container +from ...models import Profile, Container, Computing class Command(BaseCommand): help = 'Adds the admin superuser with \'a\' password.' @@ -71,9 +71,20 @@ class Command(BaseCommand): + # Computing resources + #Computing.objects.create(user = None, + # name = 'L', + # type = '') + # Computing resources + #Computing.objects.create(user = None, + # name = 'L', + # type = '') - + # Computing resources + #Computing.objects.create(user = None, + # name = 'L', + # type = '') diff --git a/services/webapp/code/rosetta/base_app/models.py b/services/webapp/code/rosetta/base_app/models.py index f9c811c35714e31f77d933381174181125e630a6..17a3fbc5e0f1102de38417a0da9c17edabdeeb1e 100644 --- a/services/webapp/code/rosetta/base_app/models.py +++ b/services/webapp/code/rosetta/base_app/models.py @@ -49,20 +49,70 @@ class LoginToken(models.Model): token = models.CharField('Login token', max_length=36) + +#========================= +# Containers +#========================= +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) + # 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) + registry = models.CharField('Container registry', max_length=255, blank=False, null=False) + service_ports = models.CharField('Container service ports', max_length=36, blank=True, null=True) + #private = models.BooleanField('Container is private and needs auth to be pulled from the registry') + + 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() + +#========================= +# Computing resources +#========================= + +# TODO: this must be an abstract class. Or maybe not? Maybe Add ComputingConfiguration/Handler with the relevant fields and methods? +# ...so that can be used as foreign key in the tasks as well? Examples: ComputingConfiguration ComputingType ComputingHandler + +class Computing(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) + # If a compute resource has no user, it will be available to anyone. Can be created, edited and deleted only by admins. + + name = models.CharField('Computing Name', max_length=255, blank=False, null=False) + + def __str__(self): + return str('Computing Resource "{}" of user "{}"'.format(self.name, self.user)) + + @property + def id(self): + return str(self.uuid).split('-')[0] + + #========================= # Tasks #========================= class Task(models.Model): - 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) + 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) + computing = models.ForeignKey(Computing, related_name='+', on_delete=models.CASCADE) + 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 @@ -84,7 +134,7 @@ class Task(models.Model): def update_status(self): - if self.compute == 'local': + if self.computing == 'local': check_command = 'sudo docker inspect --format \'{{.State.Status}}\' ' + self.tid # or, .State.Running out = os_shell(check_command, capture=True) @@ -111,30 +161,4 @@ class Task(models.Model): -#========================= -# Containers -#========================= -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) - - # 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) - registry = models.CharField('Container registry', max_length=255, blank=False, null=False) - service_ports = models.CharField('Container service ports', max_length=36, blank=True, null=True) - #private = models.BooleanField('Container is private and needs auth to be pulled from the registry') - - 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/services/webapp/code/rosetta/base_app/templates/computings.html b/services/webapp/code/rosetta/base_app/templates/computings.html new file mode 100644 index 0000000000000000000000000000000000000000..5ae68940183e6c832939a5f672a0c4260f41004d --- /dev/null +++ b/services/webapp/code/rosetta/base_app/templates/computings.html @@ -0,0 +1,53 @@ +{% load static %} +{% include "header.html" %} +{% include "navigation.html" with main_path='/main/' %} + +<br/> +<br/> + +<div class="container"> + <div class="dashboard"> + <div class="span8 offset2"> + + {% if data.computing %} + <h1><a href="/computings">Computing Resources List</a> > {{ data.computing.id }} </h1> + {% else %} + <h1>Computing Resources List</h1> + {% endif %} + + <hr/> + + {% if data.computing %} + {% include "components/computing.html" with computing=data.computing %} + {% else %} + + {% for computing in data.platform_computings %} + {% include "components/computing.html" with computing=computing %} + <br /> + {% endfor %} + + + {% for computing in data.user_computings %} + {% include "components/computing.html" with computing=computing %} + <br /> + {% endfor %} + + <br /> + <a href="/add_computing">Add new...</a> + + {% endif %} + <br /> + <br /> + <br /> + <br /> + + </div> + </div> +</div> + +{% include "footer.html" %} + + + + + diff --git a/services/webapp/code/rosetta/base_app/templates/create_task.html b/services/webapp/code/rosetta/base_app/templates/create_task.html index ca7fe313ac4d7d854bc2ca26e2cae490a266d893..e6c1ed63459b531eec7ff83295dbc29072233656 100644 --- a/services/webapp/code/rosetta/base_app/templates/create_task.html +++ b/services/webapp/code/rosetta/base_app/templates/create_task.html @@ -64,12 +64,12 @@ <tr> <td><b>Computing resource</b></td><td> - <select name="task_compute" > + <select name="task_computing" > <option value="local" selected>Local</option> <option value="demoremote">Demo remote</option> <option value="demoslurm">Demo Slurm cluster</option> </select> - | <a href="/add_compute">Add new...</a> + | <a href="/add_computing">Add new...</a> </td> </tr> diff --git a/services/webapp/code/rosetta/base_app/templates/navigation.html b/services/webapp/code/rosetta/base_app/templates/navigation.html index b7be43800bf3a7c08f32f015db4ee6d181d70925..d9225a522837e417873501227287e389bd3ea252 100644 --- a/services/webapp/code/rosetta/base_app/templates/navigation.html +++ b/services/webapp/code/rosetta/base_app/templates/navigation.html @@ -22,6 +22,9 @@ {% if user.is_authenticated %} + <li> + <a href="/computings" onclick = $("#menu-close").click(); >Computing</a> + </li> <li> <a href="/containers" onclick = $("#menu-close").click(); >Containers</a> </li> diff --git a/services/webapp/code/rosetta/base_app/views.py b/services/webapp/code/rosetta/base_app/views.py index 964971e482eae8ffb1db6619b20ac9c08517ff83..85f051829536728f6186ae7956bfdc863a825cf8 100644 --- a/services/webapp/code/rosetta/base_app/views.py +++ b/services/webapp/code/rosetta/base_app/views.py @@ -20,7 +20,7 @@ from django.contrib.auth.models import User from django.contrib.auth import update_session_auth_hash # Project imports -from .models import Profile, LoginToken, Task, TaskStatuses, Container +from .models import Profile, LoginToken, Task, TaskStatuses, Container, Computing from .utils import send_email, format_exception, random_username, log_user_activity, timezonize, os_shell, booleanize # Setup logging @@ -427,7 +427,7 @@ def tasks(request): elif action=='stop': # or delete,a and if delete also remove object try: - if task.compute == 'local': + if task.computing == 'local': # Delete the Docker container if standby_supported: @@ -439,7 +439,7 @@ def tasks(request): if out.exit_code != 0: raise Exception(out.stderr) - elif task.compute == 'demoremote': + elif task.computing == 'demoremote': # Stop the task remotely stop_command = 'ssh -4 -o StrictHostKeyChecking=no slurmclusterworker-one "kill -9 {}"'.format(task.pid) @@ -450,7 +450,7 @@ def tasks(request): raise Exception(out.stderr) else: - data['error']= 'Don\'t know how to stop tasks on "{}" compute resource.'.format(task.compute) + data['error']= 'Don\'t know how to stop tasks on "{}" computing resource.'.format(task.computing) return render(request, 'error.html', {'data': data}) # Ok, save status as deleted @@ -485,7 +485,7 @@ def tasks(request): elif action=='connect': # Create task tunnel - if task.compute in ['local', 'demoremote']: + if task.computing in ['local', 'demoremote']: # If there is no tunnel port allocated yet, find one if not task.tunnel_port: @@ -529,7 +529,7 @@ def tasks(request): subprocess.Popen(background_tunnel_command, shell=True) else: - raise ErrorMessage('Connecting to tasks on compute "{}" is not supported yet'.format(task.compute)) + raise ErrorMessage('Connecting to tasks on computing "{}" is not supported yet'.format(task.computing)) # Ok, now redirect to the task through the tunnel from django.shortcuts import redirect @@ -602,9 +602,9 @@ def create_task(request): 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 "{}') + task_computing = request.POST.get('task_computing', None) + if task_computing not in ['local', 'demoremote']: + raise ErrorMessage('Unknown computing resource "{}') # Generate the task uuid str_uuid = str(uuid.uuid4()) @@ -616,12 +616,12 @@ def create_task(request): name = task_name, status = TaskStatuses.created, container = task_container, - compute = task_compute) + computing = task_computing) # Actually start tasks try: - if task_compute == 'local': + if task_computing == 'local': # Get our ip address #import netifaces @@ -668,8 +668,8 @@ def create_task(request): # Save task.save() - elif task_compute == 'demoremote': - logger.debug('Using Demo Remote as compute resource') + elif task_computing == 'demoremote': + logger.debug('Using Demo Remote as computing resource') # 1) Run the singularity container on slurmclusterworker-one (non blocking) @@ -701,7 +701,7 @@ def create_task(request): else: - raise Exception('Consistency exception: invalid compute resource "{}'.format(task_compute)) + raise Exception('Consistency exception: invalid computing resource "{}'.format(task_computing)) except Exception as e: data['error'] = 'Error in creating new Task.' @@ -744,7 +744,7 @@ def task_log(request): # Get the log try: - if task.compute == 'local': + if task.computing == 'local': # View the Docker container log (attach) view_log_command = 'sudo docker logs {}'.format(task.tid,) @@ -755,7 +755,7 @@ def task_log(request): else: data['log'] = out.stdout - elif task.compute == 'demoremote': + elif task.computing == 'demoremote': # View the Singularity container log view_log_command = 'ssh -4 -o StrictHostKeyChecking=no slurmclusterworker-one "cat /tmp/{}.log"'.format(task.uuid) @@ -767,7 +767,7 @@ def task_log(request): data['log'] = out.stdout else: - data['error']= 'Don\'t know how to view task logs on "{}" compute resource.'.format(task.compute) + data['error']= 'Don\'t know how to view task logs on "{}" computing resource.'.format(task.computing) return render(request, 'error.html', {'data': data}) except Exception as e: @@ -899,35 +899,36 @@ def add_container(request): #========================= -# Computes view +# Computings view #========================= @private_view -def computes(request): +def computings(request): # Init data data={} data['user'] = request.user data['profile'] = Profile.objects.get(user=request.user) - data['title'] = 'Add compute' + data['title'] = 'Add computing' data['name'] = request.POST.get('name',None) + + data['computings'] = Computing.objects.all() - - return render(request, 'computes.html', {'data': data}) + return render(request, 'computings.html', {'data': data}) #========================= # Add Compute view #========================= @private_view -def add_compute(request): +def add_computing(request): # Init data data={} data['user'] = request.user data['profile'] = Profile.objects.get(user=request.user) - data['title'] = 'Add compute' + data['title'] = 'Add computing' data['name'] = request.POST.get('name',None) - return render(request, 'add_compute.html', {'data': data}) + return render(request, 'add_computing.html', {'data': data}) diff --git a/services/webapp/code/rosetta/urls.py b/services/webapp/code/rosetta/urls.py index c9ed565efebdb3a0513b6777c8ecab2fbf3908dc..500ab79cd1d029982424559b401bba60c8320205 100644 --- a/services/webapp/code/rosetta/urls.py +++ b/services/webapp/code/rosetta/urls.py @@ -43,8 +43,8 @@ urlpatterns = [ 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'^computings/$', base_app_views.computings), + url(r'^add_computing/$', base_app_views.add_computing), url(r'^containers/$', base_app_views.containers), url(r'^add_container/$', base_app_views.add_container),