From 15bdf1578b978af07be155a32db95790356c5d72 Mon Sep 17 00:00:00 2001 From: Stefano Alberto Russo <stefano.russo@gmail.com> Date: Wed, 3 Nov 2021 23:56:37 +0100 Subject: [PATCH] Fixes and imporvements --- services/webapp/code/rosetta/core_app/api.py | 22 +-- .../rosetta/core_app/computing_managers.py | 46 ++--- .../migrations/0009_auto_20211103_2256.py | 169 ++++++++++++++++++ .../webapp/code/rosetta/core_app/models.py | 75 ++++---- .../core_app/templates/add_container.html | 2 +- .../core_app/templates/components/task.html | 80 ++------- .../core_app/templates/create_task.html | 120 +++++-------- .../core_app/templates/task_connect.html | 6 +- .../rosetta/core_app/templates/tasks.html | 4 +- .../webapp/code/rosetta/core_app/views.py | 6 +- 10 files changed, 307 insertions(+), 223 deletions(-) create mode 100644 services/webapp/code/rosetta/core_app/migrations/0009_auto_20211103_2256.py diff --git a/services/webapp/code/rosetta/core_app/api.py b/services/webapp/code/rosetta/core_app/api.py index 680042d..e279fdf 100644 --- a/services/webapp/code/rosetta/core_app/api.py +++ b/services/webapp/code/rosetta/core_app/api.py @@ -305,25 +305,25 @@ print(port) elif action=='set_ip_port': - task_ip = request.GET.get('ip', None) - if not task_ip: - return HttpResponse('IP not valid (got "{}")'.format(task_ip)) + task_interface_ip = request.GET.get('ip', None) + if not task_interface_ip: + return HttpResponse('IP not valid (got "{}")'.format(task_interface_ip)) - task_port = request.GET.get('port', None) - if not task_port: - return HttpResponse('Port not valid (got "{}")'.format(task_port)) + task_interface_port = request.GET.get('port', None) + if not task_interface_port: + return HttpResponse('Port not valid (got "{}")'.format(task_interface_port)) try: - int(task_port) + int(task_interface_port) except (TypeError, ValueError): - return HttpResponse('Port not valid (got "{}")'.format(task_port)) + return HttpResponse('Port not valid (got "{}")'.format(task_interface_port)) # Set fields - logger.info('Setting task "{}" to ip "{}" and port "{}"'.format(task.uuid, task_ip, task_port)) + logger.info('Setting task "{}" to ip "{}" and port "{}"'.format(task.uuid, task_interface_ip, task_interface_port)) task.status = TaskStatuses.running - task.ip = task_ip + task.interface_ip = task_interface_ip if task.container.supports_custom_interface_port: - task.port = int(task_port) + task.interface_port = int(task_interface_port) task.save() # Notify the user that the task called back home diff --git a/services/webapp/code/rosetta/core_app/computing_managers.py b/services/webapp/code/rosetta/core_app/computing_managers.py index f33bee8..7b752df 100644 --- a/services/webapp/code/rosetta/core_app/computing_managers.py +++ b/services/webapp/code/rosetta/core_app/computing_managers.py @@ -102,13 +102,13 @@ class InternalSingleNodeComputingManager(SingleNodeComputingManager): run_command += ' -v {}/user-{}:/data'.format(settings.LOCAL_USER_DATA_DIR, task.user.id) # Set registry string - if task.container.registry == 'local': - registry_string = 'localhost:5000/' - else: - registry_string = 'docker.io/' + #if task.container.registry == 'local': + # registry_string = 'localhost:5000/' + #else: + # registry_string = 'docker.io/' # Host name, image entry command - run_command += ' -h task-{} -d -t {}{}'.format(task.uuid, registry_string, task.container.image) + run_command += ' -h task-{} -d -t {}/{}:{}'.format(task.uuid, task.container.registry, task.container.image, task.container.tag) # Debug logger.debug('Running new task with command="{}"'.format(run_command)) @@ -348,17 +348,16 @@ class SlurmSSHClusterComputingManager(ClusterComputingManager, SSHComputingManag sbatch_args += ' --output=\$HOME/{}.log --error=\$HOME/{}.log '.format(task.uuid, task.uuid) # Submit the job - if task.container.type == 'singularity': + if task.computing.default_container_runtime == 'singularity': #if not task.container.supports_custom_interface_port: # raise Exception('This task does not support dynamic port allocation and is therefore not supported using singularity on Slurm') # Set pass if any - if task.auth_pass: - authstring = ' export SINGULARITYENV_AUTH_PASS={} && '.format(task.auth_pass) - else: - authstring = '' - + authstring = '' + if not task.requires_proxy_auth and task.password: + authstring = ' export SINGULARITYENV_AUTH_PASS={} && '.format(task.password) + # Set binds, only from sys config if the resource is not owned by the user if self.computing.user != task.user: binds = self.computing.sys_conf.get('binds') @@ -382,24 +381,11 @@ class SlurmSSHClusterComputingManager(ClusterComputingManager, SSHComputingManag run_command += 'rm -rf /tmp/{}_data && mkdir -p /tmp/{}_data/tmp &>> \$HOME/{}.log && mkdir -p /tmp/{}_data/home &>> \$HOME/{}.log && chmod 700 /tmp/{}_data && '.format(task.uuid, task.uuid, task.uuid, task.uuid, task.uuid, task.uuid) run_command += 'exec nohup singularity run {} --pid --writable-tmpfs --no-home --home=/home/metauser --workdir /tmp/{}_data/tmp -B/tmp/{}_data/home:/home --containall --cleanenv '.format(binds, task.uuid, task.uuid) - # Double to escape for Pythom, six for shell (double times three as \\\ escapes a single slash in shell) + # Double to escape for Python, six for shell (double times three as \\\ escapes a single slash in shell) + run_command+='docker://{}/{}:{} &> \$HOME/{}.log\\" > \$HOME/{}.sh && sbatch {} \$HOME/{}.sh"\''.format(task.container.registry, task.container.image, task.container.tag, task.uuid, task.uuid, sbatch_args, task.uuid) - # Set registry - if task.container.registry == 'docker_local': - # Get local Docker registry conn string - from.utils import get_local_docker_registry_conn_string - local_docker_registry_conn_string = get_local_docker_registry_conn_string() - registry = 'docker://{}/'.format(local_docker_registry_conn_string) - elif task.container.registry == 'docker_hub': - registry = 'docker://' - else: - raise NotImplementedError('Registry {} not supported'.format(task.container.registry)) - - run_command+='{}{} &> \$HOME/{}.log\\" > \$HOME/{}.sh && sbatch {} \$HOME/{}.sh"\''.format(registry, task.container.image, task.uuid, task.uuid, sbatch_args, task.uuid) - - else: - raise NotImplementedError('Container {} not supported'.format(task.container.type)) + raise NotImplementedError('Default container runtime "{}" not supported'.format(task.computing.default_container_runtime)) out = os_shell(run_command, capture=True) if out.exit_code != 0: @@ -419,8 +405,8 @@ class SlurmSSHClusterComputingManager(ClusterComputingManager, SSHComputingManag task_uuid = task.uuid task = Task.objects.get(uuid=task_uuid) - # Save job id as task pid - task.pid = job_id + # Save job id as task id + task.id = job_id # Set status (only fi we get here before the agent which sets the status as running via the API) if task.status != TaskStatuses.running: @@ -443,7 +429,7 @@ class SlurmSSHClusterComputingManager(ClusterComputingManager, SSHComputingManag user = self.computing.conf.get('user') # Stop the task remotely - stop_command = 'ssh -o LogLevel=ERROR -i {} -4 -o StrictHostKeyChecking=no {}@{} \'/bin/bash -c "scancel {}"\''.format(user_keys.private_key_file, user, host, task.pid) + stop_command = 'ssh -o LogLevel=ERROR -i {} -4 -o StrictHostKeyChecking=no {}@{} \'/bin/bash -c "scancel {}"\''.format(user_keys.private_key_file, user, host, task.id) out = os_shell(stop_command, capture=True) if out.exit_code != 0: raise Exception(out.stderr) diff --git a/services/webapp/code/rosetta/core_app/migrations/0009_auto_20211103_2256.py b/services/webapp/code/rosetta/core_app/migrations/0009_auto_20211103_2256.py new file mode 100644 index 0000000..70021da --- /dev/null +++ b/services/webapp/code/rosetta/core_app/migrations/0009_auto_20211103_2256.py @@ -0,0 +1,169 @@ +# Generated by Django 2.2.1 on 2021-11-03 22:56 + +import django.contrib.postgres.fields.jsonb +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core_app', '0008_auto_20211103_1232'), + ] + + operations = [ + migrations.AlterField( + model_name='computing', + name='access_mode', + field=models.CharField(max_length=36, verbose_name='Access (control) mode'), + ), + migrations.AlterField( + model_name='computing', + name='auth_mode', + field=models.CharField(max_length=36, verbose_name='Auth mode'), + ), + migrations.AlterField( + model_name='computing', + name='container_runtimes', + field=models.CharField(max_length=256, verbose_name='Container runtimes'), + ), + migrations.AlterField( + model_name='computing', + name='description', + field=models.TextField(blank=True, null=True, verbose_name='Description'), + ), + migrations.AlterField( + model_name='computing', + name='name', + field=models.CharField(max_length=255, verbose_name='Name'), + ), + migrations.AlterField( + model_name='computing', + name='type', + field=models.CharField(max_length=255, verbose_name='Type'), + ), + migrations.AlterField( + model_name='computing', + name='wms', + field=models.CharField(blank=True, max_length=36, null=True, verbose_name='Workload management system'), + ), + migrations.AlterField( + model_name='container', + name='arch', + field=models.CharField(default='x86_64', max_length=36, verbose_name='Architecture'), + ), + migrations.AlterField( + model_name='container', + name='description', + field=models.TextField(blank=True, null=True, verbose_name='Description'), + ), + migrations.AlterField( + model_name='container', + name='image', + field=models.CharField(max_length=255, verbose_name='Image'), + ), + migrations.AlterField( + model_name='container', + name='interface_port', + field=models.IntegerField(blank=True, null=True, verbose_name='Interface port'), + ), + migrations.AlterField( + model_name='container', + name='interface_protocol', + field=models.CharField(blank=True, max_length=36, null=True, verbose_name='Interface protocol'), + ), + migrations.AlterField( + model_name='container', + name='interface_transport', + field=models.CharField(blank=True, max_length=36, null=True, verbose_name='Interface transport'), + ), + migrations.AlterField( + model_name='container', + name='name', + field=models.CharField(max_length=255, verbose_name='Name'), + ), + migrations.AlterField( + model_name='container', + name='os', + field=models.CharField(default='linux', max_length=36, verbose_name='Operating system'), + ), + migrations.AlterField( + model_name='container', + name='registry', + field=models.CharField(max_length=255, verbose_name='Registry'), + ), + migrations.AlterField( + model_name='container', + name='supports_custom_interface_port', + field=models.BooleanField(default=False, verbose_name='Supports custom interface port'), + ), + migrations.AlterField( + model_name='container', + name='supports_interface_auth', + field=models.BooleanField(default=False, verbose_name='Supports interface auth'), + ), + migrations.AlterField( + model_name='container', + name='tag', + field=models.CharField(default='latest', max_length=255, verbose_name='Tag'), + ), + migrations.AlterField( + model_name='task', + name='auth_token', + field=models.CharField(blank=True, max_length=36, null=True, verbose_name='Auth token'), + ), + migrations.AlterField( + model_name='task', + name='computing_options', + field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, null=True, verbose_name='Computing options'), + ), + migrations.AlterField( + model_name='task', + name='extra_binds', + field=models.CharField(blank=True, max_length=4096, null=True, verbose_name='Extra binds'), + ), + migrations.AlterField( + model_name='task', + name='id', + field=models.CharField(blank=True, max_length=64, null=True, verbose_name='ID'), + ), + migrations.AlterField( + model_name='task', + name='interface_ip', + field=models.CharField(blank=True, max_length=36, null=True, verbose_name='Interface IP address'), + ), + migrations.AlterField( + model_name='task', + name='interface_port', + field=models.IntegerField(blank=True, null=True, verbose_name='Interface port'), + ), + migrations.AlterField( + model_name='task', + name='name', + field=models.CharField(max_length=36, verbose_name='Name'), + ), + migrations.AlterField( + model_name='task', + name='requires_proxy', + field=models.BooleanField(verbose_name='Requires proxy'), + ), + migrations.AlterField( + model_name='task', + name='requires_proxy_auth', + field=models.BooleanField(verbose_name='Requires proxy auth'), + ), + migrations.AlterField( + model_name='task', + name='requires_tcp_tunnel', + field=models.BooleanField(verbose_name='Requires a TCP tunnel'), + ), + migrations.AlterField( + model_name='task', + name='status', + field=models.CharField(blank=True, max_length=36, null=True, verbose_name='Status'), + ), + migrations.AlterField( + model_name='task', + name='tcp_tunnel_port', + field=models.IntegerField(blank=True, null=True, verbose_name='TCP tunnel port'), + ), + ] diff --git a/services/webapp/code/rosetta/core_app/models.py b/services/webapp/code/rosetta/core_app/models.py index 3ed1316..bd53d68 100644 --- a/services/webapp/code/rosetta/core_app/models.py +++ b/services/webapp/code/rosetta/core_app/models.py @@ -80,29 +80,29 @@ class Container(models.Model): # If a container has no user, it will be available to anyone. Can be created, edited and deleted only by admins. # Generic attributes - name = models.CharField('Container Name', max_length=255, blank=False, null=False) - description = models.TextField('Container description', blank=True, null=True) + name = models.CharField('Name', max_length=255, blank=False, null=False) + description = models.TextField('Description', blank=True, null=True) # Registry-related attributes - registry = models.CharField('Container registry', max_length=255, blank=False, null=False) - image = models.CharField('Container image', max_length=255, blank=False, null=False) - tag = models.CharField('Container image', max_length=255, blank=False, null=False, default='latest') + registry = models.CharField('Registry', max_length=255, blank=False, null=False) + image = models.CharField('Image', max_length=255, blank=False, null=False) + tag = models.CharField('Tag', max_length=255, blank=False, null=False, default='latest') # Platform-related - arch = models.CharField('Container architecture', max_length=36, blank=False, null=False, default='x86_64') - os = models.CharField('Container operating system', max_length=36, blank=False, null=False, default='linux') + arch = models.CharField('Architecture', max_length=36, blank=False, null=False, default='x86_64') + os = models.CharField('Operating system', max_length=36, blank=False, null=False, default='linux') # TODO: do we want more control with respect to kernel, CPUs, instruction sets? # requires = i.e. kernel > 3, intel, AVX2 # Port, protocol and transport for the container interface - interface_port = models.IntegerField('Container interface port', blank=True, null=True) - interface_protocol = models.CharField('Container interface protocol', max_length=36, blank=True, null=True) - interface_transport = models.CharField('Container interface protocol', max_length=36, blank=True, null=True) + interface_port = models.IntegerField('Interface port', blank=True, null=True) + interface_protocol = models.CharField('Interface protocol', max_length=36, blank=True, null=True) + interface_transport = models.CharField('Interface transport', max_length=36, blank=True, null=True) # Capabilities - supports_custom_interface_port = models.BooleanField('Does the container support setting a custom interface port?', default=False) # BASE_PORT - supports_interface_auth = models.BooleanField('Does the container interface support authentication?', default=False) # AUTH_USER / AUTH_PASS + supports_custom_interface_port = models.BooleanField('Supports custom interface port', default=False) # BASE_PORT + supports_interface_auth = models.BooleanField('Supports interface auth', default=False) # AUTH_USER / AUTH_PASS class Meta: ordering = ['name'] @@ -131,23 +131,23 @@ class Computing(models.Model): user = models.ForeignKey(User, related_name='+', on_delete=models.CASCADE, blank=True, 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) - description = models.TextField('Container description', blank=True, null=True) + name = models.CharField('Name', max_length=255, blank=False, null=False) + description = models.TextField('Description', blank=True, null=True) # Standalone / sluster - type = models.CharField('Computing Type', max_length=255, blank=False, null=False) + type = models.CharField('Type', max_length=255, blank=False, null=False) requires_sys_conf = models.BooleanField(default=False) requires_user_conf = models.BooleanField(default=False) requires_user_keys = models.BooleanField(default=False) # Interfce and interaction definition - access_mode = models.CharField('Computing resource access (control) mode', max_length=36, blank=False, null=False) - auth_mode = models.CharField('Computing resource authentication mode', max_length=36, blank=False, null=False) - wms = models.CharField('Computing resource WMS', max_length=36, blank=True, null=True) + access_mode = models.CharField('Access (control) mode', max_length=36, blank=False, null=False) + auth_mode = models.CharField('Auth mode', max_length=36, blank=False, null=False) + wms = models.CharField('Workload management system', max_length=36, blank=True, null=True) # Supported container runtimes - container_runtimes = models.CharField('Computing resource container runtimes', max_length=256, blank=False, null=False) + container_runtimes = models.CharField('Container runtimes', max_length=256, blank=False, null=False) class Meta: ordering = ['name'] @@ -168,6 +168,10 @@ class Computing(models.Model): color_map_index = string_int_hash % len(color_map) return color_map[color_map_index] + @property + def default_container_runtime(self): + return str(self.container_runtimes).split(',')[0] + #======================= # Computing manager @@ -181,11 +185,11 @@ class Computing(models.Model): try: return self._manager except AttributeError: - if self.type == 'cluster' and self.access_mode == 'ssh+cli' and self.access_mode == 'user_keys' and self.wms == 'slurm': + if self.type == 'cluster' and self.access_mode == 'ssh+cli' and self.auth_mode == 'user_keys' and self.wms == 'slurm': self._manager = computing_managers.SlurmSSHClusterComputingManager(self) - elif self.type == 'standalone' and self.access_mode == 'ssh+cli' and self.access_mode == 'user_keys' and self.wms is None: + elif self.type == 'standalone' and self.access_mode == 'ssh+cli' and self.auth_mode == 'user_keys' and self.wms is None: self._manager = computing_managers.SSHSingleNodeComputingManager(self) - elif self.type == 'standalone' and self.access_mode == 'internal' and self.access_mode == 'internal' and self.wms is None: + elif self.type == 'standalone' and self.access_mode == 'internal' and self.auth_mode == 'internal' and self.wms is None: self._manager = computing_managers.InternalSingleNodeComputingManager(self) else: raise ConsistencyException('Don\'t know how to instantiate a computing manager for computing resource of type "{}", access mode "{}" and WMS "{}"'.format(self.type, self.access_mode, self.wms)) @@ -286,34 +290,37 @@ 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) - name = models.CharField('Task name', max_length=36, blank=False, null=False) + name = models.CharField('Name', max_length=36, blank=False, null=False) # Task management - id = models.CharField('Task ID', max_length=64, blank=True, null=True) # i.e. Slurm job id, singularity PID, docker hash - status = models.CharField('Task status', max_length=36, blank=True, null=True) + id = models.CharField('ID', max_length=64, blank=True, null=True) # i.e. Slurm job id, singularity PID, docker hash + status = models.CharField('Status', max_length=36, blank=True, null=True) created = models.DateTimeField('Created on', default=timezone.now) # How to reach the task interface. The IP has to be intended either as the container IP if this is directly # reachable (i.e. using a Docker or Kubernetes network) or as the host IP address, depending on the # computing resource and its computing manager/WMS/container runtime. The port is to be intended # as the port where the task interface is exposed on its IP address. - interface_ip = models.CharField('Task interface ip address', max_length=36, blank=True, null=True) - interface_port = models.IntegerField('Task interface port', blank=True, null=True) + interface_ip = models.CharField('Interface IP address', max_length=36, blank=True, null=True) + interface_port = models.IntegerField('Interface port', blank=True, null=True) # Task access - requires_tcp_tunnel = models.BooleanField('Does the task require a tunnel to be opened for accessing its interface?') - tcp_tunnel_port = models.IntegerField('Task tunnel port', blank=True, null=True) - requires_proxy = models.BooleanField('Does the task require a proxy for accessing its interface?') - requires_proxy_auth = models.BooleanField('Does the task require interface authentication to be enforced at proxy-level?') - auth_token = models.CharField('A one-time token for proxy or interface authentication', max_length=36, blank=True, null=True) + requires_tcp_tunnel = models.BooleanField('Requires a TCP tunnel') + tcp_tunnel_port = models.IntegerField('TCP tunnel port', blank=True, null=True) + requires_proxy = models.BooleanField('Requires proxy') + requires_proxy_auth = models.BooleanField('Requires proxy auth') + auth_token = models.CharField('Auth token', max_length=36, blank=True, null=True) # A one-time token for proxy or interface authentication # Links computing = models.ForeignKey(Computing, related_name='+', on_delete=models.CASCADE) container = models.ForeignKey('Container', on_delete=models.CASCADE, related_name='+') # Extra - extra_binds = models.CharField('Task container extra binds', max_length=4096, blank=True, null=True) - computing_options = JSONField('Task computing options', blank=True, null=True) # i.e. CPUs, RAM, cluster partition etc. TODO: why here? + extra_binds = models.CharField('Extra binds', max_length=4096, blank=True, null=True) + computing_options = JSONField('Computing options', blank=True, null=True) # i.e. CPUs, RAM, cluster partition etc. TODO: why here? + + # TODO: add the option for selecting the runtime as advanced option when creating the task + #container_runtime class Meta: ordering = ['-created'] diff --git a/services/webapp/code/rosetta/core_app/templates/add_container.html b/services/webapp/code/rosetta/core_app/templates/add_container.html index c62fca0..ebbb51b 100644 --- a/services/webapp/code/rosetta/core_app/templates/add_container.html +++ b/services/webapp/code/rosetta/core_app/templates/add_container.html @@ -78,7 +78,7 @@ </table> - <a href="javascript:void(0);" id="show_button" onclick="toggle_visibility('advanced_div')">Show advanced...</a> + <a href="javascript:void(0);" id="show_button" onclick="toggle_visibility('advanced_div')">Advanced...</a> <div id="advanced_div" style="display:none; width:360px;"> diff --git a/services/webapp/code/rosetta/core_app/templates/components/task.html b/services/webapp/code/rosetta/core_app/templates/components/task.html index ccf47d3..5f6b3ea 100644 --- a/services/webapp/code/rosetta/core_app/templates/components/task.html +++ b/services/webapp/code/rosetta/core_app/templates/components/task.html @@ -1,8 +1,9 @@ {% if data.task %} - <div style="width:350px; float:left; border: #e0e0e0 solid 1px; margin:10px; background:#f8f8f8; margin-bottom:20px"> + <center> + <div style="width:350px; display:block; border: #e0e0e0 solid 1px; margin:10px; background:#f8f8f8; margin-bottom:20px"> <div style="margin-top:5px; padding:10px; text-align:center; border-bottom: #f8f8f8 solid 1px; "> - <b>Summary</b> + <b>Task {{data.task.name}} summary</b> </div> {% else %} <div style="width:350px; float:left; border: #e0e0e0 solid 1px; margin:10px; background:#f8f8f8; margin-bottom:20px"> @@ -30,7 +31,7 @@ <!-- <b>Container:</b> {{ task.container.name }} <span style="font-size:14px; background-color:{{task.container.color}}"> </span> <br/> <b>Computing:</b> {{ task.computing.name }} <span style="font-size:14px; background-color:{{task.computing.color}}"> </span><br/> --> - <b>Container:</b> + <b>Software:</b> <a href="/containers/?uuid={{ task.container.uuid }}" style="color:{{task.container.color}}">{{ task.container.name }}</a> {% if task.container.type == 'docker' %}<img src="/static/img/docker-logo.svg" style="height:18px; width:18px; margin-bottom:4px" />{% endif %} {% if task.container.type == 'singularity' %}<img src="/static/img/singularity-logo.svg" style="height:18px; width:18px; margin-bottom:4px" />{% endif %} @@ -81,7 +82,8 @@ {% if data.task %} - <div style="float:left; max-width:450px"> + <br/> + <div style="display:block"; max-width:450px"> <table class="dashboard" style="margin:10px"> <!-- <tr> @@ -109,14 +111,9 @@ </tr> <tr> - <td><b>Image</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> <tr> <td><b>Created at</b></td> @@ -129,13 +126,13 @@ </tr> <tr> - <td><b>Host</b></td> - <td>{{ task.ip }}</td> + <td><b>Interface IP</b></td> + <td>{{ task.interface_ip }}</td> </tr> <tr> - <td><b>Port</b></td> - <td>{{ task.port }}</td> + <td><b>Interface port</b></td> + <td>{{ task.interface_port }}</td> </tr> <tr> @@ -151,63 +148,20 @@ {% endif %} - <td style="padding-right:0"><b>Sharable link</b> + <!-- <tr><td style="padding-right:0"><b>Sharable link</b> <td>{% if task.status == "running" %}<a href="{{ task.sharable_link }}">{{ task.sharable_link }}</a>{% else %}N.A. (task not running) {% endif %}</td> - </tr> + </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 %} - <a href="?uuid={{task.uuid}}&action=delete&details=False">Delete</a> - - {% if task.port %} - {% if task.status == "running" %} - | <a href="?uuid={{task.uuid}}&action=connect">Connect</a> - {% else %} - | <font color="#c0c0c0">Connect</font> - {% endif%} - {% endif %} - - {% if task.status == "created" %} - | <font color="#c0c0c0">View Log</font> - {% else %} - | <a href="/task_log/?uuid={{task.uuid}}&action=viewlog">View Log</a> - {% endif %} - - </td> - - </tr> - --> </table> - <p style="margin-left:10px; font-size:0.9em; color:#484848"> + <!-- <p style="margin-left:10px; font-size:0.9em; color:#484848"> <i class="fa fa-info-circle" style="color:#337ab7"></i> You can share a direct link with other people, but remember that if you have no authentication in place anyone will be able to access. - </p> + </p> --> </div> - - - - - - - - - - - - - - - + </center> {% endif %} \ No newline at end of file diff --git a/services/webapp/code/rosetta/core_app/templates/create_task.html b/services/webapp/code/rosetta/core_app/templates/create_task.html index 1ece6bd..123360c 100644 --- a/services/webapp/code/rosetta/core_app/templates/create_task.html +++ b/services/webapp/code/rosetta/core_app/templates/create_task.html @@ -69,21 +69,9 @@ <div style="width:300px; background:#ffffff; margin-left:10px; margin-right:10px; margin-top:10px; margin-bottom:0px"> - <h4>Options and confirm</h4> - <div style="width:640px; float:left; border: #e0e0e0 solid 0px; margin:10px; background:#f8f8f8; margin-bottom:15px"> + <h4>Details and confirm</h4> + <div style="width:640px; float:left; border: #e0e0e0 solid 0px; margin:10px; ; margin-bottom:15px"> - - {% if data.task_container.type == 'singularity' and not data.task_container.supports_custom_interface_port %} - <div> <p style="font-size:15px; max-width:700px; margin-bottom:20px; margin-left:5px"> - <i class="fa fa-exclamation-triangle" style="color:orange"></i> This container does not support dynamic ports and you are running it with Singularity, without network insulation. This means that if the container port is already occupied, it will not be able to start. - </p></div> - {% endif %} - - {% if data.task_container.ports and not data.task_container.supports_pass_auth %} - <div> <p style="font-size:15px; max-width:700px; margin-bottom:20px; margin-left:5px"> - <i class="fa fa-exclamation-triangle" style="color:orange"></i> This container does not support configuring any authentication. This means that unless it is built-in within the container, anyone running on the same network will be able to access it. - </p></div> - {% endif %} <form action="/create_task/" method="POST"> {% csrf_token %} @@ -93,63 +81,28 @@ <input type="hidden" name="task_container_uuid" value="{{ data.task_container.uuid }}" /> <input type="hidden" name="task_computing_uuid" value="{{ data.task_computing.uuid }}" /> - <table class="dashboard" style="max-width:700px"> - <tr><td colspan="2"></td></tr> - {% if data.task_container.supports_user_auth %} - <tr> - <td><b>Task user</b></td> - <td> - <input type="text" name="auth_user" value="" placeholder="" size="23" /> - </td> - </tr> - {% endif %} - - {% if data.task_container.supports_custom_interface_port and data.task_computing.type == 'remotehop' %} - <tr> - <td valign="top" style="width:180px"><b>Set custom port </b></td> - <td> - <input type="text" name="task_base_port" value="" placeholder="" size="23" style="margin-bottom:5px"/><br> - <p style="line-height: 0.95"><font size=-1>This container supports dynamic ports and you can thus set a custom port (>5900) to avoid clashes with services already running on the computing resource.</font></p> - </td> - </tr> - {% endif %} + <table class="dashboard" style="max-width:700px; margin-bottom:20px"> + <tr><td colspan="2"></td></tr> - {% if data.task_container.supports_interface_auth %} - <tr> <td valign="top"><b>Task name</b></td> - <td><input type="text" name="task_name" value="" placeholder="" size="" required /></td> + <td><input type="text" name="task_name" value="" placeholder="" size="37" required /></td> </tr> <tr> - <td valign="top"><b>Task password</b></td> + <td valign="top"><b>Task password</b></td> <td> - <input type="password" name="task_auth_password" placeholder="{{data.task_auth_token}}" size="23" /><br> + <input type="password" name="task_auth_password" placeholder="{{data.task_auth_token}}" size="37" /><br> <input type="hidden" name="task_auth_token" value="{{data.task_auth_token}}"> - <!-- IF NOT USING HTTP PROTOCOL: --> - <!-- <font size=-1>Use a non-sensitive password as it will be stored in plain text. 6 chars min.</font> --> - <!-- !!!!!!!!!!! - - Non serve differenziare, lascia che venga generato un token anche dove la pass deve essere immessa nel task (i.e. VNC ma - non con la password a soli 6 caratteri, ssh รจ buon esempio). Metti il mneu advances che si apre. - - Vedi: https://superuser.com/questions/493583/how-to-connect-to-ssh-with-character-in-username - - - --> - <font size=-1>A one-time task password. By default set to a randomly generated token, and automatically handled by Rosetta. Can be also set to a custom one, which in this case will be required to be entered manually when connecting to the task (as it will not be stored by Rosetta). Six characters minimum.</font> - - <!-- f not set, Rosetta will generate a random token and automatically feed it to the task when you connect. use Use a non-sensitive password as it will be stored in plain text. 6 chars min.</font> --> - <!-- TODO: do the /connect with the redirect thing --> - + <!-- <font size=-1>A one-time task password. By default set to a randomly generated token, and automatically handled by Rosetta. Can be also set to a custom one, which in this case will be required to be entered manually when connecting to the task (as it will not be stored by Rosetta). Six characters minimum.</font> --> + <font size=-1>A one-time task password. By default set to a randomly generated token, and automatically handled by Rosetta. Six chars min.</font> + </td> </tr> - {% endif %} - - - {% if data.task_container.type == 'singularity' %} + + <!-- {% if data.task_computing.supports_custom_binds %} <tr> <td valign="top" style="width:180px"><b>Extra binds</b></td> <td> @@ -158,6 +111,7 @@ </td> </tr> {% endif %} + --> <tr> <td><b>Access method</b></td><td> @@ -169,16 +123,17 @@ </td> </tr> - <!-- <tr> - <td><b>Run using</b></td><td> + <tr> + <td><b>Container runtime</b></td><td> <select name="run_using" > - <option value="docker" selected>Docker</option> - <option value="singularity">Singularity</option> + <option value="default" selected>Default</option> + <!-- <option value="docker" selected>default</option> --> + <!-- <option value="singularity">Singularity</option> --> </select> </td> - </tr> --> + </tr> - {% if data.task_computing.access_method == 'slurm+ssh' %} + {% if data.task_computing.wms == 'slurm' %} <tr> <td><b>Computing options</b></td> <td> @@ -192,18 +147,33 @@ {% endif %} <tr><td colspan=2> - <table><tr><td style="border: 1px solid lightgray;" > - I understand that files saved or modified in this container, if not explicitly saved to a persistent storage, will be LOST when the task ends. - </td><td style="border: 1px solid lightgray;" > - <input class="form-check-input" type="checkbox" value="" id="invalidCheck" required> - </td></table> + </td></tr> - <tr> - <td colspan=2 align=center style="padding:20px"> - <input type="submit" value="Create task"> - </td> - </tr> + </table> + + + {% if data.task_computing.default_container_runtime == 'singularity' and not data.task_container.supports_custom_interface_port %} + <div> <p style="font-size:15px; max-width:700px; margin-bottom:20px; margin-left:5px"> + <i class="fa fa-exclamation-triangle" style="color:orange"></i> This container does not support custom interface ports and the computing resource you selected might use a container runtime which does not support port mapping (Singularity). In this case, if the container interface port is already allocated, the task will fail to start. + </p></div> + {% endif %} + + <!-- {% if data.task_container.interface_port and not data.task_container.supports_interface_auth %} + <div> <p style="font-size:15px; max-width:700px; margin-bottom:20px; margin-left:5px"> + <i class="fa fa-exclamation-triangle" style="color:orange"></i> This container does not support configuring any authentication. This means that unless it is somehow built-in within the container, anyone will be able to access it. + </p></div> + {% endif %} --> + + <table><tr><td style="border: 1px solid lightgray;" > + I understand that files saved or modified in this container, if not explicitly saved to a persistent storage, will be LOST upon task completition. + </td><td style="border: 1px solid lightgray;" > + <input class="form-check-input" type="checkbox" value="" id="invalidCheck" required> + </td></table> + + <div style="width:640px; text-align:center; padding:20px"> + <input type="submit" value="Create task"> + </div> </form> </div> </div> diff --git a/services/webapp/code/rosetta/core_app/templates/task_connect.html b/services/webapp/code/rosetta/core_app/templates/task_connect.html index 4dc1290..6d96124 100644 --- a/services/webapp/code/rosetta/core_app/templates/task_connect.html +++ b/services/webapp/code/rosetta/core_app/templates/task_connect.html @@ -24,7 +24,7 @@ Password: <input type="password" class="form-control" placeholder="" value="placeholder" name='password' disabled> <p style="margin-left:10px; font-size:0.9em; color:#484848"> <i class="fa fa-info-circle" style="color:#337ab7"></i> - This task will require manually entering the password set up when creating the task + This task will require to manually enter the password set up when creating the task </p> {% endif %} @@ -34,13 +34,13 @@ Password: <input type="text" class="form-control" placeholder="" value="{{data.task.auth_token}}" name='password' disabled> <p style="margin-left:10px; font-size:0.9em; color:#484848"> <i class="fa fa-info-circle" style="color:#337ab7"></i> - This task will require manually entering the password token automatically generated when creating the task + This task will require to manually enter the above user and password token </p> {% else %} Password: <input type="password" class="form-control" placeholder="" value="placeholder" name='password' disabled> <p style="margin-left:10px; font-size:0.9em; color:#484848"> <i class="fa fa-info-circle" style="color:#337ab7"></i> - This task will require manually entering the password set up when creating the task + This task will require to manually enter the above user and the password set up when creating the task </p> {% endif %} diff --git a/services/webapp/code/rosetta/core_app/templates/tasks.html b/services/webapp/code/rosetta/core_app/templates/tasks.html index 36fd8e1..e76a186 100644 --- a/services/webapp/code/rosetta/core_app/templates/tasks.html +++ b/services/webapp/code/rosetta/core_app/templates/tasks.html @@ -25,10 +25,12 @@ {% endfor %} {% endif %} </div> - + + {% if not data.task %} <div class="row" style="padding:10px; padding-left:15px"> <a href="/create_task">New task...</a> </div> + {% endif %} <br/> <br/> diff --git a/services/webapp/code/rosetta/core_app/views.py b/services/webapp/code/rosetta/core_app/views.py index 360114c..bc01e87 100644 --- a/services/webapp/code/rosetta/core_app/views.py +++ b/services/webapp/code/rosetta/core_app/views.py @@ -465,6 +465,7 @@ def create_task(request): # Get computing resource data['task_computing'] = get_task_computing(request) + # Generate random auth token data['task_auth_token'] = str(uuid.uuid4()) # Set current and next step @@ -539,11 +540,6 @@ def create_task(request): # Attach user config to computing task.computing.attach_user_conf(task.user) - # Set port if not dynamic ports - if not task.container.supports_custom_interface_port: - if task.container.ports: - task.port = task.container.port - # Set extra binds if any: task.extra_binds = extra_binds -- GitLab