diff --git a/services/webapp/code/rosetta/core_app/api.py b/services/webapp/code/rosetta/core_app/api.py index 87aa59dd44d07d0965ac5f3d45f006299dbce338..a16731c5c62bb142800822b9e21c94e2a856f4ed 100644 --- a/services/webapp/code/rosetta/core_app/api.py +++ b/services/webapp/code/rosetta/core_app/api.py @@ -307,7 +307,8 @@ print(port) logger.info('Setting task "{}" to ip "{}" and port "{}"'.format(task.uuid, task_ip, task_port)) task.status = TaskStatuses.running task.ip = task_ip - task.port = int(task_port) + if task.container.supports_dynamic_ports: + task.port = int(task_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 22134d477a5d685e74f00d349a06e817c4ab83a9..6efb0ad9cd8ccb32d1fb469aaaf1762d53d4fcd2 100644 --- a/services/webapp/code/rosetta/core_app/computing_managers.py +++ b/services/webapp/code/rosetta/core_app/computing_managers.py @@ -317,8 +317,8 @@ class SlurmComputingManager(ComputingManager): # Submit the job if task.container.type == 'singularity': - if not task.container.supports_dynamic_ports: - raise Exception('This task does not support dynamic port allocation and is therefore not supported using singularity on Slurm') + #if not task.container.supports_dynamic_ports: + # 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: diff --git a/services/webapp/code/rosetta/core_app/models.py b/services/webapp/code/rosetta/core_app/models.py index 0a8b110f1f56a7bd98facf94ce7720c536f5ee33..0af86c0077d3a5fc5a33187c3628578d3acca048 100644 --- a/services/webapp/code/rosetta/core_app/models.py +++ b/services/webapp/code/rosetta/core_app/models.py @@ -80,7 +80,7 @@ class Container(models.Model): 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) - ports = models.CharField('Container service ports', max_length=36, blank=True, null=True) + ports = models.CharField('Container ports', max_length=36, blank=True, null=True) # Capabilities supports_dynamic_ports = models.BooleanField(default=False) @@ -106,6 +106,13 @@ class Container(models.Model): color_map_index = string_int_hash % len(color_map) return color_map[color_map_index] + @property + def port(self): + if not self.ports: + return None + return(int(self.ports.split(',')[0])) + + #========================= diff --git a/services/webapp/code/rosetta/core_app/templates/components/container.html b/services/webapp/code/rosetta/core_app/templates/components/container.html index ec9c7f64d76af73a54427e3cae012ee4de94ed89..4422753b2da977683adb6501384e95459e83b8c4 100644 --- a/services/webapp/code/rosetta/core_app/templates/components/container.html +++ b/services/webapp/code/rosetta/core_app/templates/components/container.html @@ -105,7 +105,7 @@ </div> <div style="margin-bottom:10px; text-align:center"> - <a href="/create_task?container_uuid={{ container.uuid }}" class="btn btn-light" style="border: #c0c0c0 1px solid"> <i class="fa fa-play" style="color:green"></i></a> + <a href="/create_task?task_container_uuid={{ container.uuid }}" class="btn btn-light" style="border: #c0c0c0 1px solid"> <i class="fa fa-play" style="color:green"></i></a> </div> 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 73d39702a3ddf4a5166f03b31711a732447e4ab5..74470872c5da8724cc42166e3a1fc704bc3f39c1 100644 --- a/services/webapp/code/rosetta/core_app/templates/components/task.html +++ b/services/webapp/code/rosetta/core_app/templates/components/task.html @@ -53,7 +53,7 @@ <tr> <td><b>IP</b></td> - <td>{{ task.ip}}</td> + <td>{{ task.ip }}</td> </tr> <tr> @@ -83,16 +83,23 @@ {% 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%} - - | <a href="/task_log/?uuid={{task.uuid}}&action=viewlog">View Log</a> + {% 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> @@ -144,23 +151,28 @@ <div style="margin-bottom:10px; text-align:center; padding:5px"> - <!-- Stop / Delete --> - {% if task.status == "stopped" %} + <!-- Stop / Delete / Cancel --> + {% if task.status == "stopped" or task.status == "created" %} <a href="?uuid={{task.uuid}}&action=delete&fromlist=True" class="btn btn-action">Delete</a> - {% else %} + {% else %} <a href="?uuid={{task.uuid}}&action=stop&fromlist=True" class="btn btn-action">Stop</a> {% endif %} <!-- Connect --> + {% if task.port %} {% if task.status == "running" %} <a href="?uuid={{task.uuid}}&action=connect" class="btn btn-connect">Connect</a> {% else %} <a href="" class="btn btn-disabled">Connect</a> {% endif %} + {% endif %} <!-- View log --> + {% if task.status == "created" %} + <a href="" class="btn btn-disabled">Logs</a> + {% else %} <a href="/task_log/?uuid={{task.uuid}}&action=viewlog" class="btn btn-action">Logs</a> - + {% endif %} </div> 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 ead1b8df35ccc890add09466bf1346b2137edbe8..533fff8939210371a5baed24762d485547eb98d0 100644 --- a/services/webapp/code/rosetta/core_app/templates/create_task.html +++ b/services/webapp/code/rosetta/core_app/templates/create_task.html @@ -23,7 +23,7 @@ <table class="dashboard" style="max-width:700px"> - <tr><td></td></tr> + <tr><td colspan="2"></td></tr> <tr> <td><b>Task name </b></td> @@ -34,9 +34,9 @@ <tr> <td><b>Task container</b></td><td> - {% if data.container %} + {% if data.task_container %} <select name="task_container_uuid"> - <option value="{{data.container.uuid}}" selected>{{data.container.name}} ({{data.container.type.title}})</option> + <option value="{{data.task_container.uuid}}" selected>{{data.task_container.name}} ({{data.task_container.type.title}})</option> </select> {% else %} <select name="task_container_uuid" > @@ -45,18 +45,16 @@ {% endfor %} </select> {% endif %} - <!-- | <a href="/add_container">Add new...</a> --> </td> </tr> <tr> <td><b>Computing resource</b></td><td> - <select name="task_computing" > + <select name="task_computing_uuid" > {% for computing in data.computings %}} - <option value="{{ computing.uuid }}">{{ computing.name}}</option> <!-- ({% if computing.user %}{{ computing.user }}{% else %}platform{% endif %}) --> + <option value="{{ computing.uuid }}">{{ computing.name}}</option> {% endfor %} </select> - <!-- | <a href="/add_computing">Add new...</a>--> </td> </tr> @@ -70,30 +68,60 @@ {% elif data.step == 'two' %} - <h3>Step 2: authentication and computing details</h3> + <h3>Step 2: add authentication and computing details</h3> <br/> + <table class="dashboard" style="max-width:700px"> + <tr><td colspan="2"></td></tr> + + <tr> + <td><b>Task name </b></td> + <td> + <input type="text" name="task_name_RECAP" value="{{ data.task_name }}" placeholder="" size="23" disabled /> + </td> + </tr> + + <tr> + <td><b>Task container</b></td><td> + <select name="task_container_uuid_RECAP"> + <option value="" selected>{{data.task_container.name}} ({{data.task_container.type.title}})</option> + </select> + </td> + </tr> + + <tr> + <td><b>Computing resource</b></td><td> + <select name="task_computing_uuid_RECAP" > + <option value="">{{ data.task_computing.name}}</option> + </select> + </td> + </tr> + + </table> - {% if data.task.container.type == 'singularity' and not data.task.container.supports_dynamic_ports %} - <div> <p style="font-size:15px; max-width:700px; margin-bottom:20px"> + <br> + {% if data.task_container.type == 'singularity' and not data.task_container.supports_dynamic_ports %} + <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"> - <i class="fa fa-exclamation-triangle" style="color:orange"></i> This container does not support any authentication. This means that anyone running on the same network will be able to access it services. + {% 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 %} <input type="hidden" name="step" value="two" /> - <input type="hidden" name="task_uuid" value="{{ data.task.uuid }}" /> + <input type="hidden" name="task_name" value="{{ data.task_name }}" /> + <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></td></tr> - {% if data.task.container.supports_user_auth %} + <tr><td colspan="2"></td></tr> + {% if data.task_container.supports_user_auth %} <tr> <td><b>Task user</b></td> <td> @@ -102,7 +130,7 @@ </tr> {% endif %} - {% if data.task.container.supports_pass_auth %} + {% if data.task_container.supports_pass_auth %} <tr> <td valign="top"><b>Set task password</b></td> <td> @@ -131,7 +159,7 @@ </td> </tr> --> - {% if data.task.computing.type == 'slurm' %} + {% if data.task_computing.type == 'slurm' %} <tr> <td><b>Computing options</b></td> <td> diff --git a/services/webapp/code/rosetta/core_app/views.py b/services/webapp/code/rosetta/core_app/views.py index 9d6a91f72a2a062d66d2def0cb35af377a95bd2a..c80aba8b06f5e540544efc553118885b22e1d20b 100644 --- a/services/webapp/code/rosetta/core_app/views.py +++ b/services/webapp/code/rosetta/core_app/views.py @@ -466,12 +466,12 @@ def create_task(request): step = request.POST.get('step', None) # Container uuid if any - container_uuid = request.GET.get('container_uuid', None) + container_uuid = request.GET.get('task_container_uuid', None) if container_uuid: try: - data['container'] = Container.objects.get(uuid=container_uuid, user=request.user) + data['task_container'] = Container.objects.get(uuid=container_uuid, user=request.user) except Container.DoesNotExist: - data['container'] = Container.objects.get(uuid=container_uuid, user=None) + data['task_container'] = Container.objects.get(uuid=container_uuid, user=None) else: # Get containers data['containers'] = list(Container.objects.filter(user=None)) + list(Container.objects.filter(user=request.user)) @@ -479,12 +479,15 @@ def create_task(request): # Get computings data['computings'] = list(Computing.objects.filter(user=None)) + list(Computing.objects.filter(user=request.user)) - - if step == 'one': + # Handle step + if step: - # We have a step one submitted, get the first tab parameters + # Task name task_name = request.POST.get('task_name', None) + if not task_name: + raise ErrorMessage('Missing task name') + data['task_name'] = task_name # Task container task_container_uuid = request.POST.get('task_container_uuid', None) @@ -495,9 +498,10 @@ def create_task(request): task_container = Container.objects.get(uuid=task_container_uuid, user=request.user) except Container.DoesNotExist: raise Exception('Consistency error, container with uuid "{}" does not exists or user "{}" does not have access rights'.format(task_container_uuid, request.user.email)) + data['task_container'] = task_container - # task computing - task_computing_uuid = request.POST.get('task_computing', None) + # Task computing + task_computing_uuid = request.POST.get('task_computing_uuid', None) try: task_computing = Computing.objects.get(uuid=task_computing_uuid, user=None) except Computing.DoesNotExist: @@ -505,87 +509,86 @@ def create_task(request): task_computing = Computing.objects.get(uuid=task_computing_uuid, user=request.user) except Computing.DoesNotExist: raise Exception('Consistency error, computing with uuid "{}" does not exists or user "{}" does not have access rights'.format(task_computing_uuid, request.user.email)) + data['task_computing'] = task_computing + + # Handle step one/two + if step == 'one': + + # Set step and task uuid + data['step'] = 'two' + + elif step == 'two': + + # Generate the task uuid + task_uuid = str(uuid.uuid4()) + + # Create the task object + task = Task(uuid = task_uuid, + user = request.user, + name = task_name, + status = TaskStatuses.created, + container = task_container, + computing = task_computing) + + # Add auth + task.auth_user = request.POST.get('auth_user', None) + task.auth_pass = request.POST.get('auth_password', None) + task.access_method = request.POST.get('access_method', None) + + # Cheks + if task.auth_pass and len(task.auth_pass) < 6: + raise ErrorMessage('Task password must be at least 6 chars') + + # Computing options # TODO: This is hardcoded thinking about Slurm + computing_cpus = request.POST.get('computing_cpus', None) + computing_memory = request.POST.get('computing_memory', None) + computing_partition = request.POST.get('computing_partition', None) + + computing_options = {} + if computing_cpus: + try: + int(computing_cpus) + except: + raise Exception('Cannot convert computing_cpus to int') + computing_options['cpus'] = int(computing_cpus) + + if computing_memory: + computing_options['memory'] = computing_memory + + if computing_partition: + computing_options['partition'] = computing_partition + + if computing_options: + task.computing_options = computing_options + + # Attach user config to computing + task.computing.attach_user_conf_data(task.user) + # Set port if not dynamic ports + if not task.container.supports_dynamic_ports: + if task.container.ports: + task.port = task.container.port - # Generate the task uuid - task_uuid = str(uuid.uuid4()) - - # Create the task object - task = Task(uuid = task_uuid, - user = request.user, - name = task_name, - status = TaskStatuses.created, - container = task_container, - computing = task_computing) - - # Save the task in the cache - _task_cache[task_uuid] = task - - # Set step and task uuid - data['step'] = 'two' - data['task'] = task - - elif step == 'two': - - # Get back the task - task_uuid = request.POST.get('task_uuid', None) - task = _task_cache[task_uuid] - - # Add auth - task.auth_user = request.POST.get('auth_user', None) - task.auth_pass = request.POST.get('auth_password', None) - task.access_method = request.POST.get('access_method', None) - - # Cheks - if task.auth_pass and len(task.auth_pass) < 6: - raise ErrorMessage('Task password must be at least 6 chars') - - # Computing options # TODO: This is hardcoded thinking about Slurm - computing_cpus = request.POST.get('computing_cpus', None) - computing_memory = request.POST.get('computing_memory', None) - computing_partition = request.POST.get('computing_partition', None) - - computing_options = {} - if computing_cpus: + # Save the task before starting it, or the computing manager will not be able to work properly + task.save() + + # Start the task try: - int(computing_cpus) + task.computing.manager.start_task(task) except: - raise Exception('Cannot convert computing_cpus to int') - computing_options['cpus'] = int(computing_cpus) - - if computing_memory: - computing_options['memory'] = computing_memory - - if computing_partition: - computing_options['partition'] = computing_partition - - if computing_options: - task.computing_options = computing_options - - logger.debug('computing_options="{}"'.format(computing_options)) - - # Save the task in the DB - task.save() - - # Attach user config to computing - task.computing.attach_user_conf_data(task.user) - - # Start the task - #try: - task.computing.manager.start_task(task) - #except: - # task.delete() - # raise + # Delete the task if could not start it + task.delete() + + # ..and re-raise + raise - # Set step - data['step'] = 'created' + # Set step + data['step'] = 'created' else: # Set step data['step'] = 'one' - - return render(request, 'create_task.html', {'data': data})