diff --git a/services/webapp/code/rosetta/core_app/admin.py b/services/webapp/code/rosetta/core_app/admin.py index 0edd07f57dd33f616b95bea77f6c3fe4a632ffbf..1e09ed48918f33f19babb36ecb10ec470a4b1c8c 100644 --- a/services/webapp/code/rosetta/core_app/admin.py +++ b/services/webapp/code/rosetta/core_app/admin.py @@ -1,13 +1,13 @@ from django.contrib import admin -from .models import Profile, LoginToken, Task, Container, Computing, ComputingSysConf, ComputingUserConf, Storage, KeyPair, Text +from .models import Profile, LoginToken, Task, Container, Computing, ComputingConf, ComputingUserConf, Storage, KeyPair, Text admin.site.register(Profile) admin.site.register(LoginToken) admin.site.register(Task) admin.site.register(Container) admin.site.register(Computing) -admin.site.register(ComputingSysConf) +admin.site.register(ComputingConf) admin.site.register(ComputingUserConf) admin.site.register(Storage) admin.site.register(KeyPair) diff --git a/services/webapp/code/rosetta/core_app/api.py b/services/webapp/code/rosetta/core_app/api.py index 4633c79327742196d75a8409acd429e6736a43c4..8f8f4e4bd74f8c3885b23245d76bda383029313a 100644 --- a/services/webapp/code/rosetta/core_app/api.py +++ b/services/webapp/code/rosetta/core_app/api.py @@ -11,7 +11,7 @@ from django.conf import settings from rest_framework.response import Response from rest_framework import status, serializers, viewsets from rest_framework.views import APIView -from .utils import format_exception, send_email, os_shell, now_t +from .utils import format_exception, send_email, os_shell, now_t, get_ssh_access_mode_credentials from .models import Profile, Task, TaskStatuses, Computing, Storage, KeyPair from .exceptions import ConsistencyException import json @@ -368,51 +368,28 @@ class FileManagerAPI(PrivateGETAPI, PrivatePOSTAPI): source = source.replace('\ ', '\\\\\\ ') else: dest = dest.replace('\ ', '\\\\\\ ') - - # Get user key - user_keys = KeyPair.objects.get(user=user, default=True) - - # Get computing host - computing.attach_user_conf(user) - computing_host = computing.conf.get('host') - computing_user = computing.conf.get('user') - - if not computing_host: - raise Exception('No computing host?!') - - if not computing_user: - raise Exception('No computing user?!') + + # Get credentials + computing_user, computing_host, computing_keys = get_ssh_access_mode_credentials(computing, user) # Command if mode=='get': - command = 'scp -o LogLevel=ERROR -i {} -4 -o StrictHostKeyChecking=no {}@{}:{} {}'.format(user_keys.private_key_file, computing_user, computing_host, source, dest) + command = 'scp -o LogLevel=ERROR -i {} -4 -o StrictHostKeyChecking=no {}@{}:{} {}'.format(computing_keys.private_key_file, computing_user, computing_host, source, dest) elif mode == 'put': - command = 'scp -o LogLevel=ERROR -i {} -4 -o StrictHostKeyChecking=no {} {}@{}:{}'.format(user_keys.private_key_file, source, computing_user, computing_host, dest) + command = 'scp -o LogLevel=ERROR -i {} -4 -o StrictHostKeyChecking=no {} {}@{}:{}'.format(computing_keys.private_key_file, source, computing_user, computing_host, dest) else: raise ValueError('Unknown mode "{}"'.format(mode)) return command - def ssh_command(self, command, user, computing): - # Get user key - user_keys = KeyPair.objects.get(user=user, default=True) - - # Get computing host - computing.attach_user_conf(user) - computing_host = computing.conf.get('host') - computing_user = computing.conf.get('user') - - if not computing_host: - raise Exception('No computing host?!') - - if not computing_user: - raise Exception('No computing user?!') + # Get credentials + computing_user, computing_host, computing_keys = get_ssh_access_mode_credentials(computing, user) # Command - command = 'ssh -o LogLevel=ERROR -i {} -4 -o StrictHostKeyChecking=no {}@{} "{}"'.format(user_keys.private_key_file, computing_user, computing_host, command) + command = 'ssh -o LogLevel=ERROR -i {} -4 -o StrictHostKeyChecking=no {}@{} "{}"'.format(computing_keys.private_key_file, computing_user, computing_host, command) return command @@ -441,7 +418,11 @@ class FileManagerAPI(PrivateGETAPI, PrivatePOSTAPI): if storage.access_through_computing: computing = storage.computing computing.attach_user_conf(user) - base_path_expanded = base_path_expanded.replace('$SSH_USER', computing.conf.get('user')) + if computing.auth_mode == 'user_keys': + base_path_expanded = base_path_expanded.replace('$SSH_USER', computing.user_conf.get('user')) + else: + base_path_expanded = base_path_expanded.replace('$SSH_USER', computing.conf.get('user')) + else: raise NotImplementedError('Accessing a storage with ssh+cli without going through its computing resource is not implemented') if '$USER' in base_path_expanded: diff --git a/services/webapp/code/rosetta/core_app/computing_managers.py b/services/webapp/code/rosetta/core_app/computing_managers.py index a5a211659f75e60154d88029cde303eb50468767..2ce933f1b19eea309d9446ac569c2db64b78835a 100644 --- a/services/webapp/code/rosetta/core_app/computing_managers.py +++ b/services/webapp/code/rosetta/core_app/computing_managers.py @@ -1,5 +1,5 @@ from .models import TaskStatuses, KeyPair, Task, Storage -from .utils import os_shell +from .utils import os_shell, get_ssh_access_mode_credentials from .exceptions import ErrorMessage, ConsistencyException from django.conf import settings @@ -180,15 +180,8 @@ class SSHSingleNodeComputingManager(SingleNodeComputingManager, SSHComputingMana def _start_task(self, task, **kwargs): logger.debug('Starting a remote task "{}"'.format(self.computing)) - # Get computing user and host - computing_user = self.computing.conf.get('user') - computing_host = self.computing.conf.get('host') - - # Get user keys - if self.computing.requires_user_keys: - user_keys = KeyPair.objects.get(user=task.user, default=True) - else: - raise NotImplementedError('Remote tasks not requiring keys are not yet supported') + # Get credentials + computing_user, computing_host, computing_keys = get_ssh_access_mode_credentials(self.computing, task.user) # Get webapp conn string from.utils import get_webapp_conn_string @@ -238,7 +231,7 @@ class SSHSingleNodeComputingManager(SingleNodeComputingManager, SSHComputingMana else: binds += ',{}:{}'.format(expanded_base_path, expanded_bind_path) - run_command = 'ssh -o LogLevel=ERROR -i {} -4 -o StrictHostKeyChecking=no {}@{} '.format(user_keys.private_key_file, computing_user, computing_host) + run_command = 'ssh -o LogLevel=ERROR -i {} -4 -o StrictHostKeyChecking=no {}@{} '.format(computing_keys.private_key_file, computing_user, computing_host) run_command += '/bin/bash -c \'"rm -rf /tmp/{}_data && mkdir -p /tmp/{}_data/tmp && mkdir -p /tmp/{}_data/home && chmod 700 /tmp/{}_data && '.format(task.uuid, task.uuid, task.uuid, task.uuid) run_command += 'wget {}/api/v1/base/agent/?task_uuid={} -O /tmp/{}_data/agent.py &> /dev/null && export BASE_PORT=\$(python /tmp/{}_data/agent.py 2> /tmp/{}_data/task.log) && '.format(webapp_conn_string, task.uuid, task.uuid, task.uuid, task.uuid) run_command += 'export SINGULARITY_NOHTTPS=true && export SINGULARITYENV_BASE_PORT=\$BASE_PORT && {} '.format(authstring) @@ -271,18 +264,11 @@ class SSHSingleNodeComputingManager(SingleNodeComputingManager, SSHComputingMana def _stop_task(self, task, **kwargs): - # Get user keys - if self.computing.requires_user_keys: - user_keys = KeyPair.objects.get(user=task.user, default=True) - else: - raise NotImplementedError('Remote tasks not requiring keys are not yet supported') - - # Get computing host - host = self.computing.conf.get('host') - user = self.computing.conf.get('user') + # Get credentials + computing_user, computing_host, computing_keys = get_ssh_access_mode_credentials(self.computing, task.user) # Stop the task remotely - stop_command = 'ssh -o LogLevel=ERROR -i {} -4 -o StrictHostKeyChecking=no {}@{} \'/bin/bash -c "kill -9 {}"\''.format(user_keys.private_key_file, user, host, task.id) + stop_command = 'ssh -o LogLevel=ERROR -i {} -4 -o StrictHostKeyChecking=no {}@{} \'/bin/bash -c "kill -9 {}"\''.format(computing_keys.private_key_file, computing_user, computing_host, task.id) out = os_shell(stop_command, capture=True) if out.exit_code != 0: if not 'No such process' in out.stderr: @@ -294,19 +280,12 @@ class SSHSingleNodeComputingManager(SingleNodeComputingManager, SSHComputingMana def _get_task_log(self, task, **kwargs): - - # Get user keys - if self.computing.requires_user_keys: - user_keys = KeyPair.objects.get(user=task.user, default=True) - else: - raise NotImplementedError('Remote tasks not requiring keys are not yet supported') - - # Get computing host - host = self.computing.conf.get('host') - user = self.computing.conf.get('user') + # Get credentials + computing_user, computing_host, computing_keys = get_ssh_access_mode_credentials(self.computing, task.user) + # View log remotely - view_log_command = 'ssh -o LogLevel=ERROR -i {} -4 -o StrictHostKeyChecking=no {}@{} \'/bin/bash -c "cat /tmp/{}_data/task.log"\''.format(user_keys.private_key_file, user, host, task.uuid) + view_log_command = 'ssh -o LogLevel=ERROR -i {} -4 -o StrictHostKeyChecking=no {}@{} \'/bin/bash -c "cat /tmp/{}_data/task.log"\''.format(computing_keys.private_key_file, computing_user, computing_host, task.uuid) out = os_shell(view_log_command, capture=True) if out.exit_code != 0: @@ -321,15 +300,8 @@ class SlurmSSHClusterComputingManager(ClusterComputingManager, SSHComputingManag def _start_task(self, task, **kwargs): logger.debug('Starting a remote task "{}"'.format(self.computing)) - # Get computing host - computing_host = self.computing.conf.get('host') - computing_user = self.computing.conf.get('user') - - # Get user keys - if self.computing.requires_user_keys: - user_keys = KeyPair.objects.get(user=task.user, default=True) - else: - raise NotImplementedError('Remote tasks not requiring keys are not yet supported') + # Get credentials + computing_user, computing_host, computing_keys = get_ssh_access_mode_credentials(self.computing, task.user) # Get webapp conn string from.utils import get_webapp_conn_string @@ -399,7 +371,7 @@ class SlurmSSHClusterComputingManager(ClusterComputingManager, SSHComputingManag else: binds += ',{}:{}'.format(expanded_base_path, expanded_bind_path) - run_command = 'ssh -o LogLevel=ERROR -i {} -4 -o StrictHostKeyChecking=no {}@{} '.format(user_keys.private_key_file, computing_user, computing_host) + run_command = 'ssh -o LogLevel=ERROR -i {} -4 -o StrictHostKeyChecking=no {}@{} '.format(computing_keys.private_key_file, computing_user, computing_host) run_command += '\'bash -c "echo \\"#!/bin/bash\nwget {}/api/v1/base/agent/?task_uuid={} -O \$HOME/agent_{}.py &> \$HOME/{}.log && export BASE_PORT=\\\\\\$(python \$HOME/agent_{}.py 2> \$HOME/{}.log) && '.format(webapp_conn_string, task.uuid, task.uuid, task.uuid, task.uuid, task.uuid) run_command += 'export SINGULARITY_NOHTTPS=true && export SINGULARITYENV_BASE_PORT=\\\\\\$BASE_PORT && {} '.format(authstring) 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) @@ -442,18 +414,11 @@ class SlurmSSHClusterComputingManager(ClusterComputingManager, SSHComputingManag def _stop_task(self, task, **kwargs): - # Get user keys - if self.computing.requires_user_keys: - user_keys = KeyPair.objects.get(user=task.user, default=True) - else: - raise NotImplementedError('Remote tasks not requiring keys are not yet supported') - - # Get computing host - host = self.computing.conf.get('host') - user = self.computing.conf.get('user') + # Get credentials + computing_user, computing_host, computing_keys = get_ssh_access_mode_credentials(self.computing, task.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.id) + stop_command = 'ssh -o LogLevel=ERROR -i {} -4 -o StrictHostKeyChecking=no {}@{} \'/bin/bash -c "scancel {}"\''.format(computing_keys.private_key_file, computing_user, computing_host, task.id) out = os_shell(stop_command, capture=True) if out.exit_code != 0: raise Exception(out.stderr) @@ -465,18 +430,11 @@ class SlurmSSHClusterComputingManager(ClusterComputingManager, SSHComputingManag def _get_task_log(self, task, **kwargs): - # Get user keys - if self.computing.requires_user_keys: - user_keys = KeyPair.objects.get(user=task.user, default=True) - else: - raise NotImplementedError('Remote tasks not requiring keys are not yet supported') - - # Get computing host - host = self.computing.conf.get('host') - user = self.computing.conf.get('user') + # Get credentials + computing_user, computing_host, computing_keys = get_ssh_access_mode_credentials(self.computing, task.user) # View log remotely - view_log_command = 'ssh -o LogLevel=ERROR -i {} -4 -o StrictHostKeyChecking=no {}@{} \'/bin/bash -c "cat \$HOME/{}.log"\''.format(user_keys.private_key_file, user, host, task.uuid) + view_log_command = 'ssh -o LogLevel=ERROR -i {} -4 -o StrictHostKeyChecking=no {}@{} \'/bin/bash -c "cat \$HOME/{}.log"\''.format(computing_keys.private_key_file, computing_user, computing_host, task.uuid) out = os_shell(view_log_command, capture=True) if out.exit_code != 0: @@ -485,183 +443,3 @@ class SlurmSSHClusterComputingManager(ClusterComputingManager, SSHComputingManag return out.stdout -# TODO: rename the following as "ssh+ssh" access mode? Ore somethign similar? -# class RemotehopComputingManager(ComputingManager): -# -# def _start_task(self, task, **kwargs): -# logger.debug('Starting a remote task "{}"'.format(self.computing)) -# -# # Get computing params -# first_host = self.computing.conf.get('first_host') -# first_user = self.computing.conf.get('first_user') -# second_host = self.computing.conf.get('second_host') -# second_user = self.computing.conf.get('second_user') -# setup_command = self.computing.conf.get('setup_command') -# -# # TODO: De hard-code -# use_agent = False -# -# # Get user keys -# if self.computing.requires_user_keys: -# user_keys = KeyPair.objects.get(user=task.user, default=True) -# else: -# raise NotImplementedError('Remote tasks not requiring keys are not yet supported') -# -# # Get webapp conn string -# from.utils import get_webapp_conn_string -# webapp_conn_string = get_webapp_conn_string() -# -# # Run the container on the host (non blocking) -# if task.container.type == 'singularity': -# -# task.tid = task.uuid -# task.save() -# -# # Set pass if any -# if task.auth_pass: -# authstring = ' export SINGULARITYENV_AUTH_PASS={} && '.format(task.auth_pass) -# else: -# authstring = '' -# -# # 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') -# else: -# binds = self.computing.conf.get('binds') -# if not binds: -# binds = '' -# else: -# binds = '-B {}'.format(binds) -# -# # Manage task extra binds -# if task.extra_binds: -# if not binds: -# binds = '-B {}'.format(task.extra_binds) -# else: -# binds += ',{}'.format(task.extra_binds) -# -# run_command = 'ssh -o LogLevel=ERROR -i {} -4 -o StrictHostKeyChecking=no {}@{} '.format(user_keys.private_key_file, first_user, first_host) -# run_command += '"ssh -4 -o StrictHostKeyChecking=no {}@{} /bin/bash -c \''.format(second_user, second_host) -# -# if use_agent: -# run_command += '\'wget {}/api/v1/base/agent/?task_uuid={} -O \$HOME/agent_{}.py &> /dev/null && export BASE_PORT=\$(python \$HOME/agent_{}.py 2> \$HOME/{}.log) && '.format(webapp_conn_string, task.uuid, task.uuid, task.uuid, task.uuid) -# if setup_command: -# run_command += setup_command + ' && ' -# run_command += '\'export SINGULARITY_NOHTTPS=true && export SINGULARITYENV_BASE_PORT=\$BASE_PORT && {} '.format(authstring) -# 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) -# else: -# run_command += ' : && ' # Trick to prevent some issues in exporting variables -# if setup_command: -# run_command += setup_command + ' && ' -# run_command += 'export SINGULARITY_NOHTTPS=true && export SINGULARITYENV_BASE_PORT={} && {} '.format(task.port, authstring) -# 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) -# -# # Set registry -# if task.container.registry == 'docker_local': -# raise Exception('This computing resource does not support local Docker registries yet') -# # 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 & echo \$!\'"'.format(registry, task.container.image, task.uuid) -# -# else: -# raise NotImplementedError('Container {} not supported'.format(task.container.type)) -# -# out = os_shell(run_command, capture=True) -# if out.exit_code != 0: -# raise Exception(out.stderr) -# -# # Log -# logger.debug('Shell exec output: "{}"'.format(out)) -# -# -# # Load back the task to avoid concurrency problems in the agent call -# task_uuid = task.uuid -# task = Task.objects.get(uuid=task_uuid) -# -# # Save pid echoed by the command above -# task_pid = out.stdout -# -# # Set fields -# task.status = TaskStatuses.running -# task.pid = task_pid -# task.ip = second_host -# -# # Save -# task.save() -# -# -# def _stop_task(self, task, **kwargs): -# -# # Get user keys -# if self.computing.requires_user_keys: -# user_keys = KeyPair.objects.get(user=task.user, default=True) -# else: -# raise NotImplementedError('Remote tasks not requiring keys are not yet supported') -# -# # Get computing params -# first_host = self.computing.conf.get('first_host') -# first_user = self.computing.conf.get('first_user') -# second_host = self.computing.conf.get('second_host') -# second_user = self.computing.conf.get('second_user') -# -# # Stop the task remotely -# stop_command = 'ssh -o LogLevel=ERROR -i {} -4 -o StrictHostKeyChecking=no {}@{} '.format(user_keys.private_key_file, first_user, first_host) -# stop_command += '"ssh -4 -o StrictHostKeyChecking=no {}@{} '.format(second_user, second_host) -# stop_command += 'kill -9 {}"'.format(task.pid) -# -# out = os_shell(stop_command, capture=True) -# if out.exit_code != 0: -# if not 'No such process' in out.stderr: -# raise Exception(out.stderr) -# -# # Set task as stopped -# task.status = TaskStatuses.stopped -# task.save() -# -# -# def _get_task_log(self, task, **kwargs): -# -# # Get user keys -# if self.computing.requires_user_keys: -# user_keys = KeyPair.objects.get(user=task.user, default=True) -# else: -# raise NotImplementedError('Remote tasks not requiring keys are not yet supported') -# -# # Get computing params -# first_host = self.computing.conf.get('first_host') -# first_user = self.computing.conf.get('first_user') -# second_host = self.computing.conf.get('second_host') -# second_user = self.computing.conf.get('second_user') -# -# # View log remotely -# view_log_command = 'ssh -o LogLevel=ERROR -i {} -4 -o StrictHostKeyChecking=no {}@{} '.format(user_keys.private_key_file, first_user, first_host) -# view_log_command += '"ssh -4 -o StrictHostKeyChecking=no {}@{} '.format(second_user, second_host) -# view_log_command += 'cat \\\\\\$HOME/{}.log"'.format(task.uuid) -# -# out = os_shell(view_log_command, capture=True) -# if out.exit_code != 0: -# raise Exception(out.stderr) -# else: -# return out.stdout - - - - - - - - - - - - - diff --git a/services/webapp/code/rosetta/core_app/management/commands/core_app_populate.py b/services/webapp/code/rosetta/core_app/management/commands/core_app_populate.py index 46f32b81f4f07e43a28c9b85a1454a1301073fd8..e54bc154e6d35ba458e430e357f9301d65363146 100644 --- a/services/webapp/code/rosetta/core_app/management/commands/core_app_populate.py +++ b/services/webapp/code/rosetta/core_app/management/commands/core_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, Computing, ComputingSysConf, ComputingUserConf, Storage, KeyPair, Text +from ...models import Profile, Container, Computing, ComputingConf, ComputingUserConf, Storage, KeyPair, Text class Command(BaseCommand): help = 'Adds the admin superuser with \'a\' password.' @@ -24,7 +24,7 @@ class Command(BaseCommand): #===================== try: testuser = User.objects.get(username='testuser') - print('Not creating test user as it already exist') + print('Not creating test user as it already exists') except User.DoesNotExist: print('Creating test user with default password') @@ -38,11 +38,28 @@ class Command(BaseCommand): Profile.objects.create(user=testuser, authtoken='129aac94-284a-4476-953c-ffa4349b4a50') # Create default keys - print('Creating testuser defualt keys') + print('Creating testuser default keys') KeyPair.objects.create(user = testuser, - default = True, - private_key_file = '/rosetta/.ssh/id_rsa', - public_key_file = '/rosetta/.ssh/id_rsa.pub') + default = True, + private_key_file = '/rosetta/.ssh/id_rsa', + public_key_file = '/rosetta/.ssh/id_rsa.pub') + + + #===================== + # Platform keys + #===================== + # TODO: create a different pair + try: + testuser = KeyPair.objects.get(user=None, default=True) + print('Not creating default platform keys as they already exist') + + except KeyPair.DoesNotExist: + print('Creating platform default keys') + KeyPair.objects.create(user = None, + default = True, + private_key_file = '/rosetta/.ssh/id_rsa', + public_key_file = '/rosetta/.ssh/id_rsa.pub') + #===================== # Default home text @@ -183,9 +200,6 @@ class Command(BaseCommand): access_mode = 'internal', auth_mode = 'internal', wms = None, - requires_sys_conf = False, - requires_user_conf = False, - requires_user_keys = False, container_runtimes = 'docker') @@ -197,14 +211,11 @@ class Command(BaseCommand): access_mode = 'ssh+cli', auth_mode = 'user_keys', wms = None, - requires_sys_conf = True, - requires_user_conf = True, - requires_user_keys = True, container_runtimes = 'singularity') - ComputingSysConf.objects.create(computing = demo_singlenode_computing, - data = {'host': 'slurmclusterworker-one', - 'binds': '/shared/data/users:/shared/data/users,/shared/scratch:/shared/scratch'}) + ComputingConf.objects.create(computing = demo_singlenode_computing, + data = {'host': 'slurmclusterworker-one', + 'binds': '/shared/data/users:/shared/data/users,/shared/scratch:/shared/scratch'}) ComputingUserConf.objects.create(user = testuser, computing = demo_singlenode_computing, @@ -219,14 +230,11 @@ class Command(BaseCommand): access_mode = 'ssh+cli', auth_mode = 'user_keys', wms = 'slurm', - requires_sys_conf = True, - requires_user_conf = True, - requires_user_keys = True, container_runtimes = 'singularity') - ComputingSysConf.objects.create(computing = demo_slurm_computing, - data = {'host': 'slurmclustermaster-main', 'default_partition': 'partition1', - 'binds': '/shared/data/users:/shared/data/users,/shared/scratch:/shared/scratch'}) + ComputingConf.objects.create(computing = demo_slurm_computing, + data = {'host': 'slurmclustermaster-main', 'default_partition': 'partition1', + 'binds': '/shared/data/users:/shared/data/users,/shared/scratch:/shared/scratch'}) ComputingUserConf.objects.create(user = testuser, computing = demo_slurm_computing, diff --git a/services/webapp/code/rosetta/core_app/migrations/0012_remove_task_extra_binds.py b/services/webapp/code/rosetta/core_app/migrations/0012_remove_task_extra_binds.py new file mode 100644 index 0000000000000000000000000000000000000000..d76bc5fe573621ce4884adeb51608b11971b0628 --- /dev/null +++ b/services/webapp/code/rosetta/core_app/migrations/0012_remove_task_extra_binds.py @@ -0,0 +1,17 @@ +# Generated by Django 2.2.1 on 2021-11-08 14:52 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('core_app', '0011_storage'), + ] + + operations = [ + migrations.RemoveField( + model_name='task', + name='extra_binds', + ), + ] diff --git a/services/webapp/code/rosetta/core_app/migrations/0013_auto_20211108_1529.py b/services/webapp/code/rosetta/core_app/migrations/0013_auto_20211108_1529.py new file mode 100644 index 0000000000000000000000000000000000000000..10d1fbb181ee8891a40f0e7faea4a6955196740a --- /dev/null +++ b/services/webapp/code/rosetta/core_app/migrations/0013_auto_20211108_1529.py @@ -0,0 +1,43 @@ +# Generated by Django 2.2.1 on 2021-11-08 15:29 + +import django.contrib.postgres.fields.jsonb +from django.db import migrations, models +import django.db.models.deletion +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + ('core_app', '0012_remove_task_extra_binds'), + ] + + operations = [ + migrations.CreateModel( + name='ComputingConf', + fields=[ + ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('data', django.contrib.postgres.fields.jsonb.JSONField(blank=True, null=True)), + ], + ), + migrations.RemoveField( + model_name='computing', + name='requires_sys_conf', + ), + migrations.RemoveField( + model_name='computing', + name='requires_user_conf', + ), + migrations.RemoveField( + model_name='computing', + name='requires_user_keys', + ), + migrations.DeleteModel( + name='ComputingSysConf', + ), + migrations.AddField( + model_name='computingconf', + name='computing', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='related_computing_conf', to='core_app.Computing'), + ), + ] diff --git a/services/webapp/code/rosetta/core_app/migrations/0014_auto_20211108_1548.py b/services/webapp/code/rosetta/core_app/migrations/0014_auto_20211108_1548.py new file mode 100644 index 0000000000000000000000000000000000000000..98aa509849d51bc5964c7ad1a6c4e10d4f437709 --- /dev/null +++ b/services/webapp/code/rosetta/core_app/migrations/0014_auto_20211108_1548.py @@ -0,0 +1,20 @@ +# Generated by Django 2.2.1 on 2021-11-08 15:48 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('core_app', '0013_auto_20211108_1529'), + ] + + operations = [ + migrations.AlterField( + model_name='keypair', + name='user', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/services/webapp/code/rosetta/core_app/models.py b/services/webapp/code/rosetta/core_app/models.py index a5c5caab6b3027557ab79c18481c5730acd62c8b..eee49ad033c79d2efd24224dc66006df897460db 100644 --- a/services/webapp/code/rosetta/core_app/models.py +++ b/services/webapp/code/rosetta/core_app/models.py @@ -135,13 +135,9 @@ class Computing(models.Model): name = models.CharField('Name', max_length=255, blank=False, null=False) description = models.TextField('Description', blank=True, null=True) - # Standalone / sluster + # Tye (standalone / cluster) 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('Access (control) mode', max_length=36, blank=False, null=False) auth_mode = models.CharField('Auth mode', max_length=36, blank=False, null=False) @@ -198,7 +194,7 @@ class Computing(models.Model): #======================= - # Sys & user conf + # Conf & user conf #======================= def attach_user_conf(self, user): @@ -210,9 +206,17 @@ class Computing(models.Model): self._user_conf_data = None @property - def sys_conf(self): - return self.related_sys_conf.get().data + def conf(self): + try: + return self.related_computing_conf.get().data + except: + return {} + #TODO: add a setter and start removing the ComputingConf model + @property + def conf_as_json(self): + return json.dumps(self.conf) + @property def user_conf(self): try: @@ -220,39 +224,17 @@ class Computing(models.Model): except AttributeError: raise ConsistencyException('User conf has to been attached, cannot proceed.') - @property - def sys_conf_as_json(self): - return json.dumps(self.sys_conf) - @property def user_conf_as_json(self): return json.dumps(self.user_conf) - @property - def conf(self): - - if not self.requires_user_conf: - conf_tmp = self.sys_conf - else: - try: - # Copy the conf or the original user conf will be affected by the overwrite below - conf_tmp = {key:value for key, value in self._user_conf_data.items()} - except AttributeError: - raise ConsistencyException('User conf has not been attached, cannot proceed.') - - # Now add (overwrite) with the sys conf - sys_conf = self.sys_conf - for key in sys_conf: - conf_tmp[key] = sys_conf[key] - - return conf_tmp -class ComputingSysConf(models.Model): +class ComputingConf(models.Model): uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) - computing = models.ForeignKey(Computing, related_name='related_sys_conf', on_delete=models.CASCADE) + computing = models.ForeignKey(Computing, related_name='related_computing_conf', on_delete=models.CASCADE) data = JSONField(blank=True, null=True) @@ -316,13 +298,11 @@ class Task(models.Model): 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('Extra binds', max_length=4096, blank=True, null=True) + # Computing options + # TODO: add the option for selecting the runtime as advanced option when creating the task? 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'] @@ -440,7 +420,7 @@ class Storage(models.Model): class KeyPair(models.Model): uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) - user = models.ForeignKey(User, related_name='+', on_delete=models.CASCADE, null=False) + user = models.ForeignKey(User, related_name='+', on_delete=models.CASCADE, blank=True, null=True) private_key_file = models.CharField('Private key file', max_length=4096, blank=False, null=False) public_key_file = models.CharField('Public key file', max_length=4096, blank=False, null=False) diff --git a/services/webapp/code/rosetta/core_app/templates/components/computing.html b/services/webapp/code/rosetta/core_app/templates/components/computing.html index f6f647a949d692278ac8954f402bfc9606e518cc..d906e9592bb65b367f6e32dc87a8dcf84f304727 100644 --- a/services/webapp/code/rosetta/core_app/templates/components/computing.html +++ b/services/webapp/code/rosetta/core_app/templates/components/computing.html @@ -49,15 +49,6 @@ <td>{% if data.computing.user %}{{ data.computing.user }}{% else %}platform{% endif %}</td> </tr> - <tr> - <td><b>Requires</b></td> - <td> - Sys conf: {{ data.computing.requires_sys_conf }} <br/> - User conf: {{ data.computing.requires_user_conf }} <br/> - User keys: {{ data.computing.requires_user_keys }} - </td> - </tr> - <tr> <td><b>Container runtimes</b></td> <td>{{ data.computing.container_runtimes }}</td> @@ -65,7 +56,7 @@ <tr> <td><b>Sys Conf</b></td> - <td>{{ data.computing.sys_conf_as_json }} {% if request.user.is_superuser %} [<a href="/edit_computing_conf?type=sys&computing_uuid={{ data.computing.uuid}}">Edit</a>] {% endif %}</td> + <td>{{ data.computing.conf_as_json }} {% if request.user.is_superuser %} [<a href="/edit_computing_conf?type=sys&computing_uuid={{ data.computing.uuid}}">Edit</a>] {% endif %}</td> </tr> <tr> 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 1dcb6d83269541c45f5b2f1af73c506d683272b4..8508df3ffbf58f5a6154d619a92c093f1dfb4d04 100644 --- a/services/webapp/code/rosetta/core_app/templates/components/task.html +++ b/services/webapp/code/rosetta/core_app/templates/components/task.html @@ -120,11 +120,6 @@ <td>{{ task.created }}</td> </tr> - <tr> - <td><b>Extra binds</b></td> - <td>{{ task.extra_binds }}</td> - </tr> - <tr> <td><b>Interface IP</b></td> <td>{{ task.interface_ip }}</td> 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 9ff63a05c03cb401a935f8af5cf5b03a6c85097f..8eedd2ca85aff248dbfa210bd3264af1b13044b3 100644 --- a/services/webapp/code/rosetta/core_app/templates/create_task.html +++ b/services/webapp/code/rosetta/core_app/templates/create_task.html @@ -108,16 +108,6 @@ </td> </tr> - <!-- {% if data.task_computing.supports_custom_binds %} - <tr> - <td valign="top" style="width:180px"><b>Extra binds</b></td> - <td> - <input type="text" name="extra_binds" value="" placeholder="" size="40" /><br> -<p style="line-height: 0.95"><font size=-1>For this computing resource, you can set extra FS binds on top of the ones already define by the administrators. Format is <i>host_directory:container_directory</i>, comma separated.</font></p> - </td> - </tr> - {% endif %} - --> {% if request.user.profile.is_power_user %} <tr> diff --git a/services/webapp/code/rosetta/core_app/templates/files.html b/services/webapp/code/rosetta/core_app/templates/files.html index 6790061998a3d79ca3c4dc34a5a1fbc061e8a7b5..fee103b589546dd3866b3ba9b9a63138c4ede4dc 100644 --- a/services/webapp/code/rosetta/core_app/templates/files.html +++ b/services/webapp/code/rosetta/core_app/templates/files.html @@ -10,7 +10,7 @@ <div class="span8 offset2" style="height:100%;"> - <h1>Files</h1> + <h1>Storages</h1> <hr/> diff --git a/services/webapp/code/rosetta/core_app/templates/navigation.html b/services/webapp/code/rosetta/core_app/templates/navigation.html index f55251056ff62ee8d2a2119ebe919bd5ab4831a1..4d80f42adebd3f570419a6929bdc1a7bbabaaf83 100644 --- a/services/webapp/code/rosetta/core_app/templates/navigation.html +++ b/services/webapp/code/rosetta/core_app/templates/navigation.html @@ -23,12 +23,15 @@ <li> <a href="/computings" onclick = $("#menu-close").click(); >Computing</a> </li> + <li> - <a href="/tasks" onclick = $("#menu-close").click(); >Tasks</a> + <a href="/files" onclick = $("#menu-close").click(); >Storage</a> </li> + <li> - <a href="/files" onclick = $("#menu-close").click(); >Files</a> + <a href="/tasks" onclick = $("#menu-close").click(); >Tasks</a> </li> + <li> <a href="/account" onclick = $("#menu-close").click(); >Account</a> </li> diff --git a/services/webapp/code/rosetta/core_app/tests/test_models.py b/services/webapp/code/rosetta/core_app/tests/test_models.py index 0654b17555d8a3c2bc036b913dfefec6fdc09941..508ff70901119a82f4766c29e1591b2f32b79801 100644 --- a/services/webapp/code/rosetta/core_app/tests/test_models.py +++ b/services/webapp/code/rosetta/core_app/tests/test_models.py @@ -3,7 +3,7 @@ import json from django.contrib.auth.models import User from .common import BaseAPITestCase -from ..models import Profile, Computing, ComputingSysConf +from ..models import Profile, Computing, ComputingConf class Modeltest(BaseAPITestCase): @@ -22,8 +22,8 @@ class Modeltest(BaseAPITestCase): computing = Computing.objects.create(name='MyComp', type='remote') - computingSysConf = ComputingSysConf.objects.create(computing=computing, data={'myvar':42}) + computingSysConf = ComputingConf.objects.create(computing=computing, data={'myvar':42}) - self.assertEqual(ComputingSysConf.objects.all()[0].data, {'myvar':42}) + self.assertEqual(ComputingConf.objects.all()[0].data, {'myvar':42}) diff --git a/services/webapp/code/rosetta/core_app/utils.py b/services/webapp/code/rosetta/core_app/utils.py index 2e7801640e3e4628661fd337e59bf444753932b5..057675338720817e7279e2f69b3fd37779a26bf6 100644 --- a/services/webapp/code/rosetta/core_app/utils.py +++ b/services/webapp/code/rosetta/core_app/utils.py @@ -696,7 +696,27 @@ Listen '''+str(task.tcp_tunnel_port)+''' - +def get_ssh_access_mode_credentials(computing, user): + from .models import KeyPair + # Get computing host + computing_host = computing.conf.get('host') + if not computing_host: + raise Exception('No computing host?!') + + # Get computing user and keys + if computing.auth_mode == 'user_keys': + computing.attach_user_conf(user) + computing_user = computing.user_conf.get('user') + # Get user key + computing_keys = KeyPair.objects.get(user=user, default=True) + elif computing.auth_mode == 'platform_keys': + computing_user = computing.conf.get('user') + computing_keys = KeyPair.objects.get(user=None, default=True) + else: + raise NotImplementedError('Auth modes other than user_keys and platform_keys not supported.') + if not computing_user: + raise Exception('No computing user?!') + return (computing_user, computing_host, computing_keys) diff --git a/services/webapp/code/rosetta/core_app/views.py b/services/webapp/code/rosetta/core_app/views.py index 685b60cd0e22e4b47c9fcf66bc4fe6f9ec5dfba4..ebb3eded12357a1c994b645d4ff506ac47bac85e 100644 --- a/services/webapp/code/rosetta/core_app/views.py +++ b/services/webapp/code/rosetta/core_app/views.py @@ -9,7 +9,7 @@ from django.http import HttpResponse, HttpResponseRedirect from django.contrib.auth.models import User from django.shortcuts import redirect from django.db.models import Q -from .models import Profile, LoginToken, Task, TaskStatuses, Container, Computing, KeyPair, ComputingSysConf, ComputingUserConf, Text +from .models import Profile, LoginToken, Task, TaskStatuses, Container, Computing, KeyPair, ComputingConf, ComputingUserConf, Text from .utils import send_email, format_exception, timezonize, os_shell, booleanize, debug_param, get_task_tunnel_host, get_task_proxy_host, random_username, setup_tunnel_and_proxy, finalize_user_creation from .decorators import public_view, private_view from .exceptions import ErrorMessage @@ -551,7 +551,6 @@ def create_task(request): computing_cpus = request.POST.get('computing_cpus', None) computing_memory = request.POST.get('computing_memory', None) computing_partition = request.POST.get('computing_partition', None) - extra_binds = request.POST.get('extra_binds', None) computing_options = {} if computing_cpus: @@ -573,9 +572,6 @@ def create_task(request): # Attach user config to computing task.computing.attach_user_conf(task.user) - # Set extra binds if any: - task.extra_binds = extra_binds - # Save the task before starting it, or the computing manager will not be able to work properly task.save() @@ -924,11 +920,11 @@ def edit_computing_conf(request): try: computing = Computing.objects.get(uuid=computing_uuid) data['computing'] = computing - except ComputingSysConf.DoesNotExist: + except ComputingConf.DoesNotExist: raise Exception('Unknown computing "{}"'.format(computing_uuid)) # Get computing conf - computingSysConf, _ = ComputingSysConf.objects.get_or_create(computing=computing) + computingSysConf, _ = ComputingConf.objects.get_or_create(computing=computing) # Edit conf? if new_conf: