Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
  • develop
  • feature/arch_support
  • feature/global_refactoring
  • master
  • v1.0.0
5 results

Target

Select target project
  • exact/Rosetta
1 result
Select Git revision
  • develop
  • feature/arch_support
  • feature/global_refactoring
  • master
  • v1.0.0
5 results
Show changes
Commits on Source (11)
Showing
with 117 additions and 104 deletions
......@@ -13,7 +13,7 @@ from rest_framework import status, serializers, viewsets
from rest_framework.views import APIView
from .utils import format_exception, send_email, os_shell, now_t, get_ssh_access_mode_credentials, get_or_create_container_from_repository, booleanize
from .models import Profile, Task, TaskStatuses, Computing, Storage, KeyPair
from .exceptions import ConsistencyException
from .exceptions import PermissionDenied
import json
# Setup logging
......@@ -153,6 +153,10 @@ class PrivatePOSTAPI(APIView):
# Call API logic
return self._post(request)
except Exception as e:
# TODO: refactor me
if isinstance(e, PermissionDenied):
return error400(format(e))
else:
logger.error(format_exception(e))
return error500('Got error in processing request: {}'.format(e))
......@@ -174,11 +178,14 @@ class PrivateGETAPI(APIView):
# Call API logic
return self._get(request)
except Exception as e:
# TODO: refactor me
if isinstance(e, PermissionDenied):
return error400(format(e))
else:
logger.error(format_exception(e))
return error500('Got error in processing request: {}'.format(e))
#==============================
# User & profile APIs
#==============================
......@@ -259,7 +266,10 @@ logger.info('Reporting for task uuid: "{}"'.format(task_uuid))
# Get IP
ip = socket.gethostbyname(hostname)
if ip == '127.0.1.1':
try:
ip = socket.gethostbyname(hostname+'.local')
except:
pass
logger.info(' - ip: "{}"'.format(ip))
# Get port
......@@ -307,6 +317,9 @@ print(port)
elif action=='set_ip_port':
if task.computing.type == 'standalone':
_, task_interface_ip, _, _ = get_ssh_access_mode_credentials(task.computing, task.user)
else:
task_interface_ip = request.GET.get('ip', None)
if not task_interface_ip:
return HttpResponse('IP not valid (got "{}")'.format(task_interface_ip))
......@@ -397,7 +410,7 @@ class FileManagerAPI(PrivateGETAPI, PrivatePOSTAPI):
if mode=='get':
command = 'scp -P {} -o LogLevel=ERROR -i {} -4 -o StrictHostKeyChecking=no {}@{}:{} {}'.format(computing_port, computing_keys.private_key_file, computing_user, computing_host, source, dest)
elif mode == 'put':
command = 'scp -P -o LogLevel=ERROR -i {} -4 -o StrictHostKeyChecking=no {} {}@{}:{}'.format(computing_port, computing_keys.private_key_file, source, computing_user, computing_host, dest)
command = 'scp -P {} -o LogLevel=ERROR -i {} -4 -o StrictHostKeyChecking=no {} {}@{}:{}'.format(computing_port, computing_keys.private_key_file, source, computing_user, computing_host, dest)
else:
raise ValueError('Unknown mode "{}"'.format(mode))
......@@ -552,7 +565,9 @@ class FileManagerAPI(PrivateGETAPI, PrivatePOSTAPI):
# https://askubuntu.com/questions/1116634/ls-command-show-time-only-in-iso-format
# https://www.howtogeek.com/451022/how-to-use-the-stat-command-on-linux/
command = self.prepare_sh_command('cd {} && stat --printf=\'%F/%s/%Y/%n\\n\' * .*'.format(shell_path), user, storage)
# This potentially raises a "stat: cannot statx '.*': No such file or directory"
#command = self.prepare_sh_command('cd {} && stat --printf=\'%F/%s/%Y/%n\\n\' * .*'.format(shell_path), user, storage)
command = self.prepare_sh_command('cd {} && stat --printf=\'%F/%s/%Y/%n\\n\' *'.format(shell_path), user, storage)
# Execute_command
out = os_shell(command, capture=True)
......@@ -683,6 +698,9 @@ class FileManagerAPI(PrivateGETAPI, PrivatePOSTAPI):
def delete(self, path, user, storage):
if storage.read_only:
raise PermissionDenied('This storage is read-only')
if storage.type == 'generic_posix':
shell_path = self.sanitize_and_prepare_shell_path(path, user, storage)
......@@ -701,6 +719,9 @@ class FileManagerAPI(PrivateGETAPI, PrivatePOSTAPI):
def mkdir(self, path, user, storage, force=False):
if storage.read_only:
raise PermissionDenied('This storage is read-only')
path = self.sanitize_and_prepare_shell_path(path, user, storage)
if storage.type == 'generic_posix':
......@@ -745,6 +766,9 @@ class FileManagerAPI(PrivateGETAPI, PrivatePOSTAPI):
def rename(self, old, new, user, storage):
if storage.read_only:
raise PermissionDenied('This storage is read-only')
if storage.type == 'generic_posix':
old = self.sanitize_and_prepare_shell_path(old, user, storage)
......@@ -766,6 +790,9 @@ class FileManagerAPI(PrivateGETAPI, PrivatePOSTAPI):
def copy(self, source, target, user, storage):
if storage.read_only:
raise PermissionDenied('This storage is read-only')
if storage.type == 'generic_posix':
source = self.sanitize_and_prepare_shell_path(source, user, storage)
......@@ -799,6 +826,9 @@ class FileManagerAPI(PrivateGETAPI, PrivatePOSTAPI):
def scp_to(self, source, target, user, storage, mode='get'):
if storage.read_only:
raise PermissionDenied('This storage is read-only')
source = self.sanitize_shell_path(source) # This is a folder on Rosetta (/tmp)
target = self.sanitize_and_prepare_shell_path(target, user, storage)
......@@ -1205,7 +1235,7 @@ class FileManagerAPI(PrivateGETAPI, PrivatePOSTAPI):
# Remove file
os.remove('/tmp/{}'.format(file_uuid))
if storage.access_mode == 'cli':
elif storage.access_mode == 'cli':
try:
as_user = storage.conf['as_user']
......
......@@ -147,11 +147,17 @@ class InternalStandaloneComputingManager(StandaloneComputingManager):
if '$USER' in expanded_bind_path:
expanded_bind_path = expanded_bind_path.replace('$USER', task.user.username)
# Read only?
if storage.read_only:
mode_string = ':ro'
else:
mode_string = ''
# Add the bind
if not binds:
binds = '-v{}:{}'.format(expanded_base_path, expanded_bind_path)
binds = '-v{}:{}{}'.format(expanded_base_path, expanded_bind_path, mode_string)
else:
binds += ' -v{}:{}'.format(expanded_base_path, expanded_bind_path)
binds += ' -v{}:{}{}'.format(expanded_base_path, expanded_bind_path, mode_string)
# Host name, image entry command
run_command += ' {} -h task-{} --name task-{} -d -t {}/{}:{}'.format(binds, task.short_uuid, task.short_uuid, task.container.registry, task.container.image_name, task.container.image_tag)
......@@ -322,6 +328,7 @@ class SSHStandaloneComputingManager(StandaloneComputingManager, SSHComputingMana
# Handle storages (binds)
binds = ''
storages = Storage.objects.filter(computing=self.computing)
initialize_bind_paths_command = ''
for storage in storages:
if storage.type == 'generic_posix' and storage.bind_path:
......@@ -335,6 +342,8 @@ class SSHStandaloneComputingManager(StandaloneComputingManager, SSHComputingMana
if '$USER' in expanded_base_path:
expanded_base_path = expanded_base_path.replace('$USER', task.user.username)
initialize_bind_paths_command += 'mkdir -p {} && '.format(expanded_base_path)
# Expand the bind_path
expanded_bind_path = storage.bind_path
if '$SSH_USER' in expanded_bind_path:
......@@ -345,17 +354,23 @@ class SSHStandaloneComputingManager(StandaloneComputingManager, SSHComputingMana
if '$USER' in expanded_bind_path:
expanded_bind_path = expanded_bind_path.replace('$USER', task.user.username)
# Read only?
if storage.read_only:
mode_string = ':ro'
else:
mode_string = ''
# Add the bind
if not binds:
binds = '-v{}:{}'.format(expanded_base_path, expanded_bind_path)
binds = '-v{}:{}{}'.format(expanded_base_path, expanded_bind_path, mode_string)
else:
binds += ' -v{}:{}'.format(expanded_base_path, expanded_bind_path)
binds += ' -v{}:{}{}'.format(expanded_base_path, expanded_bind_path, mode_string)
# TODO: remove this hardcoding
prefix = 'sudo' if (computing_host == 'slurmclusterworker' and container_engine=='docker') else ''
run_command = 'ssh -p {} -o LogLevel=ERROR -i {} -4 -o StrictHostKeyChecking=no {}@{} '.format(computing_port, computing_keys.private_key_file, computing_user, computing_host)
run_command += '/bin/bash -c \'"rm -rf /tmp/{}_data && mkdir /tmp/{}_data && chmod 700 /tmp/{}_data && '.format(task.uuid, task.uuid, task.uuid)
run_command += '/bin/bash -c \'"{}rm -rf /tmp/{}_data && mkdir /tmp/{}_data && chmod 700 /tmp/{}_data && '.format(initialize_bind_paths_command, task.uuid, task.uuid, task.uuid)
run_command += 'curl {} {}/api/v1/base/agent/?task_uuid={} -o /tmp/{}_data/agent.py &> /dev/null && export TASK_PORT=\$(python3 /tmp/{}_data/agent.py 2> /tmp/{}_data/task.log) && '.format(CHECK_CURL_CERT_STR, webapp_conn_string, task.uuid, task.uuid, task.uuid, task.uuid)
run_command += 'exec nohup {} {} run -p \$TASK_PORT:{} {} {} {} '.format(prefix, container_engine, task.container.interface_port, authstring, varsstring, binds)
if container_engine == 'podman':
......
......@@ -4,3 +4,6 @@ class ErrorMessage(Exception):
class ConsistencyException(Exception):
pass
class PermissionDenied(Exception):
pass
\ No newline at end of file
# Generated by Django 2.2.1 on 2025-03-05 09:46
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core_app', '0036_container_disable_http_basicauth_embedding'),
]
operations = [
migrations.AddField(
model_name='storage',
name='read_only',
field=models.BooleanField(default=False, verbose_name='Read only? (if supported)'),
),
]
......@@ -387,6 +387,9 @@ class Storage(models.Model):
base_path = models.CharField('Base path', max_length=4096, blank=False, null=False)
bind_path = models.CharField('Bind path', max_length=4096, blank=True, null=True)
# Read only?
read_only = models.BooleanField('Read only? (if supported)', default=False)
# Link with a computing resource
computing = models.ForeignKey(Computing, related_name='storages', on_delete=models.CASCADE, blank=True, null=True) # Make optional?
access_through_computing = models.BooleanField('Access through linked computing resource?', default=False)
......
services/webapp/code/rosetta/core_app/static/favicon/apple-touch-icon.png

24.3 KiB | W: 0px | H: 0px

services/webapp/code/rosetta/core_app/static/favicon/apple-touch-icon.png

11.5 KiB | W: 0px | H: 0px

services/webapp/code/rosetta/core_app/static/favicon/apple-touch-icon.png
services/webapp/code/rosetta/core_app/static/favicon/apple-touch-icon.png
services/webapp/code/rosetta/core_app/static/favicon/apple-touch-icon.png
services/webapp/code/rosetta/core_app/static/favicon/apple-touch-icon.png
  • 2-up
  • Swipe
  • Onion skin
<?xml version="1.0" encoding="utf-8"?>
<browserconfig>
<msapplication>
<tile>
<square150x150logo src="/mstile-150x150.png"/>
<TileColor>#da532c</TileColor>
</tile>
</msapplication>
</browserconfig>
services/webapp/code/rosetta/core_app/static/favicon/favicon-96x96.png

5.86 KiB

services/webapp/code/rosetta/core_app/static/favicon/favicon.ico

11.7 KiB | W: 48px | H: 48px

services/webapp/code/rosetta/core_app/static/favicon/favicon.ico

14.7 KiB | W: 48px | H: 48px

services/webapp/code/rosetta/core_app/static/favicon/favicon.ico
services/webapp/code/rosetta/core_app/static/favicon/favicon.ico
services/webapp/code/rosetta/core_app/static/favicon/favicon.ico
services/webapp/code/rosetta/core_app/static/favicon/favicon.ico
  • 2-up
  • Swipe
  • Onion skin
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:svgjs="http://svgjs.dev/svgjs" width="128" height="128" viewBox="0 0 128 128"><image width="128" height="128" xlink:href="data:image/png;base64,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"></image><style>@media (prefers-color-scheme: light) { :root { filter: none; } }
@media (prefers-color-scheme: dark) { :root { filter: none; } }
</style></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="256.000000pt" height="256.000000pt" viewBox="0 0 256.000000 256.000000"
preserveAspectRatio="xMidYMid meet">
<metadata>
Created by potrace 1.11, written by Peter Selinger 2001-2013
</metadata>
<g transform="translate(0.000000,256.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M1153 2290 c-23 -5 -51 -13 -61 -19 -13 -7 -33 -6 -68 5 -53 17 -126
14 -191 -6 -49 -16 -145 -61 -159 -75 -7 -6 -25 -22 -40 -36 -101 -85 -184
-247 -225 -439 -53 -250 22 -919 118 -1048 21 -28 30 -32 71 -32 l47 -1 -36
-32 c-78 -70 -84 -86 -87 -250 l-4 -147 26 0 c26 0 26 0 11 30 -22 42 -19 148
5 212 12 32 35 67 61 90 63 57 197 101 262 84 13 -3 64 -45 114 -93 123 -119
171 -163 180 -163 5 0 139 114 153 131 3 3 34 31 69 62 49 44 71 57 95 57 53
0 120 -28 160 -67 l38 -37 -4 -105 c-3 -93 -6 -110 -27 -140 -13 -18 -38 -39
-55 -47 -28 -12 -17 -13 109 -14 l140 0 -6 83 c-6 76 5 373 16 407 2 8 9 54
15 103 12 95 24 133 48 142 35 13 54 -41 82 -230 11 -77 26 -222 32 -323 8
-119 16 -182 23 -182 13 0 11 103 -5 325 -6 77 -11 239 -13 360 l-2 220 45 13
c25 7 65 19 90 27 25 8 68 19 95 24 123 24 145 32 145 52 0 16 -7 19 -48 19
-26 0 -74 -7 -105 -15 -32 -8 -68 -15 -81 -15 -13 0 -35 -9 -49 -20 -14 -11
-29 -20 -33 -20 -16 0 -74 -52 -74 -67 0 -28 -33 -42 -96 -43 -79 0 -93 11
-95 79 -2 46 1 56 22 72 13 10 40 19 60 19 l35 0 2 193 c3 267 -15 372 -90
527 -42 86 -63 117 -118 170 -122 118 -232 160 -425 166 -71 1 -149 -1 -172
-6z m-103 -512 c40 -20 130 -42 245 -62 132 -23 249 -62 296 -98 9 -7 19 -35
23 -62 l8 -49 -30 24 c-16 14 -49 33 -75 43 -43 16 -49 16 -104 0 -42 -12 -70
-28 -100 -59 -24 -23 -43 -50 -43 -59 0 -24 13 -19 35 16 28 44 49 49 41 11
-10 -43 19 -111 51 -119 34 -9 122 1 136 15 7 7 12 40 12 77 0 37 4 64 10 64
5 -1 23 -12 40 -25 27 -22 29 -27 18 -54 -9 -24 -8 -29 3 -24 17 6 18 -23 4
-119 -27 -183 -166 -370 -317 -424 -55 -19 -195 -18 -254 3 -27 10 -49 21 -49
25 0 5 -6 8 -13 8 -8 0 -41 25 -75 56 -102 92 -163 216 -181 369 -10 78 -5 98
20 85 15 -8 17 -7 13 5 -4 8 -10 15 -15 15 -5 0 -9 6 -9 14 0 17 56 66 76 66
16 0 19 -16 4 -25 -27 -16 2 -112 37 -127 27 -10 102 -9 122 2 26 13 44 65 38
105 -7 40 -2 42 32 14 16 -13 24 -30 24 -52 -1 -18 2 -25 5 -17 3 8 9 18 15
21 26 18 -56 101 -128 130 -58 23 -156 0 -203 -47 -7 -8 -18 -14 -23 -14 -11
0 -12 37 0 44 4 3 11 21 14 39 11 72 43 121 93 146 26 13 63 41 83 62 l36 37
25 -21 c14 -12 41 -30 60 -39z m-153 -275 c8 -21 -35 -59 -50 -44 -14 14 8 61
28 61 9 0 18 -7 22 -17z m530 1 c4 -10 -4 -25 -21 -40 l-26 -25 0 35 c0 42 34
64 47 30z m373 -894 c0 -37 -7 -37 -50 0 l-35 29 43 1 c40 0 42 -1 42 -30z
m-576 -20 c19 -7 26 -17 26 -35 0 -14 5 -25 11 -25 7 0 1 -11 -12 -24 -36 -36
-101 -38 -133 -6 -57 58 21 123 108 90z"/>
<path d="M1070 1290 c-14 -27 -5 -55 28 -85 21 -20 37 -25 77 -25 57 0 88 17
110 61 12 22 13 34 5 49 -13 24 -14 24 -46 3 -14 -9 -45 -16 -70 -16 -27 0
-51 6 -62 16 -24 22 -29 21 -42 -3z"/>
<path d="M954 1066 c-5 -22 0 -36 27 -69 122 -153 258 -157 384 -14 77 88 59
114 -66 96 -77 -11 -203 -8 -299 7 -36 6 -39 4 -46 -20z"/>
<path d="M2055 1276 c-16 -8 -43 -18 -60 -22 l-30 -7 40 -13 c59 -18 131 -18
139 2 9 23 -12 54 -37 54 -12 0 -35 -7 -52 -14z"/>
</g>
</svg>
{
"name": "",
"short_name": "",
"name": "Rosetta",
"short_name": "Rosetta",
"icons": [
{
"src": "/android-chrome-192x192.png",
"src": "/static/favicon/web-app-manifest-192x192.png",
"sizes": "192x192",
"type": "image/png"
"type": "image/png",
"purpose": "maskable"
},
{
"src": "/android-chrome-256x256.png",
"sizes": "256x256",
"type": "image/png"
"src": "/static/favicon/web-app-manifest-512x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
}
],
"theme_color": "#ffffff",
......
......@@ -39,13 +39,13 @@
<script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script>
<![endif]-->
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
<link rel="manifest" href="/site.webmanifest">
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5">
<meta name="msapplication-TileColor" content="#da532c">
<meta name="theme-color" content="#ffffff">
<!-- Favicon -->
<link rel="icon" type="image/png" href="/static/favicon/favicon-96x96.png" sizes="96x96" />
<link rel="icon" type="image/svg+xml" href="/static/favicon/favicon.svg" />
<link rel="shortcut icon" href="/static/favicon/favicon.ico" />
<link rel="apple-touch-icon" sizes="180x180" href="/static/favicon/apple-touch-icon.png" />
<meta name="apple-mobile-web-app-title" content="Rosetta" />
<link rel="manifest" href="/static/favicon/site.webmanifest" />
<!-- jQuery -->
<script src="/static/js/jquery-1.11.1.js"></script>
......