Skip to content
Commits on Source (2)
...@@ -44,10 +44,10 @@ ...@@ -44,10 +44,10 @@
<!-- <a href="/computing/?uuid={{ task.computing.uuid }}" no_style="color:{{task.computing.color}}"><i class="fa fa-external-link" ></i></a><br/> --> <!-- <a href="/computing/?uuid={{ task.computing.uuid }}" no_style="color:{{task.computing.color}}"><i class="fa fa-external-link" ></i></a><br/> -->
<div style="margin-top:2px"> <div style="margin-top:2px">
{% if task.verified_status == "running" %} {% if task.status == "running" %}
<b>Status:</b> <font color="green">running</font> <b>Status:</b> <font color="green">running</font>
{% else %} {% else %}
<b>Status:</b> {{ task.verified_status }} <b>Status:</b> {{ task.status }}
{% endif %} {% endif %}
</div> </div>
</div> </div>
...@@ -63,8 +63,8 @@ ...@@ -63,8 +63,8 @@
<!-- Connect --> <!-- Connect -->
{% if task.interface_port %} {% if task.interface_port %}
{% if task.verified_status == "running" %} {% if task.status == "running" %}
<a href="/task_connect/?uuid={{task.uuid}}" class="btn btn-connect" target="_blank">Connect</a> <a href="/task_connect/?uuid={{task.uuid}}" class="btn btn-connect">Connect</a>
{% else %} {% else %}
<a href="" class="btn btn-disabled">Connect</a> <a href="" class="btn btn-disabled">Connect</a>
{% endif %} {% endif %}
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
{% include "navigation.html"%} {% include "navigation.html"%}
<!-- with body_args="style='background: #202020'" --> <!-- with body_args="style='background: #202020'" -->
<center> <center>
<div style="width:370px;"> <div style="width:370px;">
<form class="form-signin" role="form" action='/direct_connect/{{data.task.uuid}}/' method='POST'> <form class="form-signin" role="form" action='/direct_connect/{{data.task.uuid}}/' method='POST'>
{% csrf_token %} {% csrf_token %}
...@@ -14,6 +15,14 @@ ...@@ -14,6 +15,14 @@
<p style="font-size: 16px;"> <p style="font-size: 16px;">
<br /> <br />
{% if not data.task.interface_status == 'running' %}
<br/>
<div class="alert alert-warning" role="alert"><i class="fa fa-warning"></i> the task interface is not up, cannot connect.</div>
Please check the <a href="/task_log/?uuid={{ data.task.uuid }}&action=viewlog">task logs</a>.
<br/><br/>
<i>Note: if you just launched the task, this alert might be due to the normal task startup time.</i>
{% else %}
{% if not data.task.requires_proxy_auth %} {% if not data.task.requires_proxy_auth %}
{% if data.task.container.interface_auth_user %} {% if data.task.container.interface_auth_user %}
User: <input style="margin-bottom:15px;" type="username" class="form-control" value="{{ data.task.container.interface_auth_user }}"name='username' readonly > User: <input style="margin-bottom:15px;" type="username" class="form-control" value="{{ data.task.container.interface_auth_user }}"name='username' readonly >
...@@ -75,14 +84,14 @@ ...@@ -75,14 +84,14 @@
<b>Port:</b> <code>{{ data.task.tcp_tunnel_port}}</code> <b>Port:</b> <code>{{ data.task.tcp_tunnel_port}}</code>
</p> </p>
{% endif %} {% endif %}
{% endif%}
</p> </p>
</form> </form>
</div> </div>
<br /><br /> <br /><br />
{% if data.task.interface_status == 'running' %}
{% if data.task.requires_proxy_auth %} {% if data.task.requires_proxy_auth %}
<p style="margin-left:10px; font-size:0.9em; color:rgb(200,200,200); max-width:600px"> <p style="margin-left:10px; font-size:0.9em; color:rgb(200,200,200); max-width:600px">
<i class="fa fa-info-warning" style="color:#337ab7"></i> <i class="fa fa-info-warning" style="color:#337ab7"></i>
...@@ -90,6 +99,7 @@ ...@@ -90,6 +99,7 @@
to a web browser which supports embedding user credentials in the connection URL (as Chorme, Edge or Firefox). to a web browser which supports embedding user credentials in the connection URL (as Chorme, Edge or Firefox).
</p> </p>
{% endif %} {% endif %}
{% endif %}
<br /><br /><br /> <br /><br /><br />
</center> </center>
......
...@@ -32,9 +32,6 @@ color_map = ["#440154", "#440558", "#450a5c", "#450e60", "#451465", "#461969", ...@@ -32,9 +32,6 @@ color_map = ["#440154", "#440558", "#450a5c", "#450e60", "#451465", "#461969",
"#97d73e", "#9ed93a", "#a8db34", "#b0dd31", "#b8de30", "#c3df2e", "#97d73e", "#9ed93a", "#a8db34", "#b0dd31", "#b8de30", "#c3df2e",
"#cbe02d", "#d6e22b", "#e1e329", "#eae428", "#f5e626", "#fde725"] "#cbe02d", "#d6e22b", "#e1e329", "#eae428", "#f5e626", "#fde725"]
#======================
# Utility functions
#======================
def booleanize(*args, **kwargs): def booleanize(*args, **kwargs):
# Handle both single value and kwargs to get arg name # Handle both single value and kwargs to get arg name
...@@ -265,10 +262,6 @@ def get_md5(string): ...@@ -265,10 +262,6 @@ def get_md5(string):
return md5 return md5
#=========================
# Time
#=========================
def timezonize(timezone): def timezonize(timezone):
'''Convert a string representation of a timezone to its pytz object or do nothing if the argument is already a pytz timezone''' '''Convert a string representation of a timezone to its pytz object or do nothing if the argument is already a pytz timezone'''
...@@ -283,14 +276,17 @@ def timezonize(timezone): ...@@ -283,14 +276,17 @@ def timezonize(timezone):
timezone = pytz.timezone(timezone) timezone = pytz.timezone(timezone)
return timezone return timezone
def now_t(): def now_t():
'''Return the current time in epoch seconds''' '''Return the current time in epoch seconds'''
return now_s() return now_s()
def now_s(): def now_s():
'''Return the current time in epoch seconds''' '''Return the current time in epoch seconds'''
return calendar.timegm(now_dt().utctimetuple()) return calendar.timegm(now_dt().utctimetuple())
def now_dt(tzinfo='UTC'): def now_dt(tzinfo='UTC'):
'''Return the current time in datetime format''' '''Return the current time in datetime format'''
if tzinfo != 'UTC': if tzinfo != 'UTC':
...@@ -335,10 +331,12 @@ def dt(*args, **kwargs): ...@@ -335,10 +331,12 @@ def dt(*args, **kwargs):
return time_dt return time_dt
def get_tz_offset_s(time_dt): def get_tz_offset_s(time_dt):
'''Get the time zone offset in seconds''' '''Get the time zone offset in seconds'''
return s_from_dt(time_dt.replace(tzinfo=pytz.UTC)) - s_from_dt(time_dt) return s_from_dt(time_dt.replace(tzinfo=pytz.UTC)) - s_from_dt(time_dt)
def check_dt_consistency(date_dt): def check_dt_consistency(date_dt):
'''Check that the timezone is consistent with the datetime (some conditions in Python lead to have summertime set in winter)''' '''Check that the timezone is consistent with the datetime (some conditions in Python lead to have summertime set in winter)'''
...@@ -355,6 +353,7 @@ def check_dt_consistency(date_dt): ...@@ -355,6 +353,7 @@ def check_dt_consistency(date_dt):
else: else:
return True return True
def correct_dt_dst(datetime_obj): def correct_dt_dst(datetime_obj):
'''Check that the dst is correct and if not change it''' '''Check that the dst is correct and if not change it'''
...@@ -374,14 +373,17 @@ def correct_dt_dst(datetime_obj): ...@@ -374,14 +373,17 @@ def correct_dt_dst(datetime_obj):
datetime_obj.microsecond, datetime_obj.microsecond,
tzinfo=datetime_obj.tzinfo) tzinfo=datetime_obj.tzinfo)
def change_tz(dt, tz): def change_tz(dt, tz):
return dt.astimezone(timezonize(tz)) return dt.astimezone(timezonize(tz))
def dt_from_t(timestamp_s, tz=None): def dt_from_t(timestamp_s, tz=None):
'''Create a datetime object from an epoch timestamp in seconds. If no timezone is given, UTC is assumed''' '''Create a datetime object from an epoch timestamp in seconds. If no timezone is given, UTC is assumed'''
# TODO: check if uniform everything on this one or not. # TODO: check if uniform everything on this one or not.
return dt_from_s(timestamp_s=timestamp_s, tz=tz) return dt_from_s(timestamp_s=timestamp_s, tz=tz)
def dt_from_s(timestamp_s, tz=None): def dt_from_s(timestamp_s, tz=None):
'''Create a datetime object from an epoch timestamp in seconds. If no timezone is given, UTC is assumed''' '''Create a datetime object from an epoch timestamp in seconds. If no timezone is given, UTC is assumed'''
...@@ -397,6 +399,7 @@ def dt_from_s(timestamp_s, tz=None): ...@@ -397,6 +399,7 @@ def dt_from_s(timestamp_s, tz=None):
return timestamp_dt return timestamp_dt
def s_from_dt(dt): def s_from_dt(dt):
'''Returns seconds with floating point for milliseconds/microseconds.''' '''Returns seconds with floating point for milliseconds/microseconds.'''
if not (isinstance(dt, datetime.datetime)): if not (isinstance(dt, datetime.datetime)):
...@@ -404,6 +407,7 @@ def s_from_dt(dt): ...@@ -404,6 +407,7 @@ def s_from_dt(dt):
microseconds_part = (dt.microsecond/1000000.0) if dt.microsecond else 0 microseconds_part = (dt.microsecond/1000000.0) if dt.microsecond else 0
return ( calendar.timegm(dt.utctimetuple()) + microseconds_part) return ( calendar.timegm(dt.utctimetuple()) + microseconds_part)
def dt_from_str(string, timezone=None): def dt_from_str(string, timezone=None):
# Supported formats on UTC # Supported formats on UTC
...@@ -458,10 +462,12 @@ def dt_from_str(string, timezone=None): ...@@ -458,10 +462,12 @@ def dt_from_str(string, timezone=None):
return dt(year, month, day, hour, minute, second, usecond, offset_s=offset_s) return dt(year, month, day, hour, minute, second, usecond, offset_s=offset_s)
def dt_to_str(dt): def dt_to_str(dt):
'''Return the ISO representation of the datetime as argument''' '''Return the ISO representation of the datetime as argument'''
return dt.isoformat() return dt.isoformat()
class dt_range(object): class dt_range(object):
def __init__(self, from_dt, to_dt, timeSlotSpan): def __init__(self, from_dt, to_dt, timeSlotSpan):
...@@ -489,20 +495,18 @@ class dt_range(object): ...@@ -489,20 +495,18 @@ class dt_range(object):
return self.__next__() return self.__next__()
#================================
# Others
#================================
def debug_param(**kwargs): def debug_param(**kwargs):
for item in kwargs: for item in kwargs:
logger.critical('Param "{}": "{}"'.format(item, kwargs[item])) logger.critical('Param "{}": "{}"'.format(item, kwargs[item]))
def get_my_ip(): def get_my_ip():
import socket import socket
hostname = socket.gethostname() hostname = socket.gethostname()
my_ip = socket.gethostbyname(hostname) my_ip = socket.gethostbyname(hostname)
return my_ip return my_ip
def get_webapp_conn_string(): def get_webapp_conn_string():
webapp_ssl = booleanize(os.environ.get('ROSETTA_WEBAPP_SSL', False)) webapp_ssl = booleanize(os.environ.get('ROSETTA_WEBAPP_SSL', False))
webapp_host = os.environ.get('ROSETTA_WEBAPP_HOST', get_my_ip()) webapp_host = os.environ.get('ROSETTA_WEBAPP_HOST', get_my_ip())
...@@ -513,32 +517,68 @@ def get_webapp_conn_string(): ...@@ -513,32 +517,68 @@ def get_webapp_conn_string():
webapp_conn_string = 'http://{}:{}'.format(webapp_host, webapp_port) webapp_conn_string = 'http://{}:{}'.format(webapp_host, webapp_port)
return webapp_conn_string return webapp_conn_string
def get_platform_registry(): def get_platform_registry():
platform_registry_host = os.environ.get('PLATFORM_REGISTRY_HOST', 'proxy') platform_registry_host = os.environ.get('PLATFORM_REGISTRY_HOST', 'proxy')
platform_registry_port = os.environ.get('PLATFORM_REGISTRY_PORT', '5000') platform_registry_port = os.environ.get('PLATFORM_REGISTRY_PORT', '5000')
platform_registry_conn_string = '{}:{}'.format(platform_registry_host, platform_registry_port) platform_registry_conn_string = '{}:{}'.format(platform_registry_host, platform_registry_port)
return platform_registry_conn_string return platform_registry_conn_string
def get_rosetta_tasks_tunnel_host(): def get_rosetta_tasks_tunnel_host():
# Importing here instead of on top avoids circular dependencies problems when loading booleanize in settings # Importing here instead of on top avoids circular dependencies problems when loading booleanize in settings
from django.conf import settings from django.conf import settings
tunnel_host = os.environ.get('ROSETTA_TASKS_TUNNEL_HOST', settings.ROSETTA_HOST) tunnel_host = os.environ.get('ROSETTA_TASKS_TUNNEL_HOST', settings.ROSETTA_HOST)
return tunnel_host return tunnel_host
def get_rosetta_tasks_proxy_host(): def get_rosetta_tasks_proxy_host():
# Importing here instead of on top avoids circular dependencies problems when loading booleanize in settings # Importing here instead of on top avoids circular dependencies problems when loading booleanize in settings
from django.conf import settings from django.conf import settings
proxy_host = os.environ.get('ROSETTA_TASKS_PROXY_HOST', settings.ROSETTA_HOST) proxy_host = os.environ.get('ROSETTA_TASKS_PROXY_HOST', settings.ROSETTA_HOST)
return proxy_host return proxy_host
def hash_string_to_int(string): def hash_string_to_int(string):
return int(hashlib.sha1(string.encode('utf8')).hexdigest(), 16) return int(hashlib.sha1(string.encode('utf8')).hexdigest(), 16)
def get_ssh_access_mode_credentials(computing, user):
from .models import KeyPair
# Get computing host
try:
computing_host = computing.conf.get('host')
except AttributeError:
computing_host = None
if not computing_host:
raise ValueError('No computing host?!')
# Get computing (SSH) port
try:
computing_port = computing.conf.get('port')
except AttributeError:
computing_port = 22
if not computing_host:
computing_port = 22
# Get computing user and keys
if computing.auth_mode == 'user_keys':
computing_user = user.profile.get_extra_conf('computing_user', computing)
if not computing_user:
raise ValueError('No \'computing_user\' parameter found for computing resource \'{}\' in user profile'.format(computing.name))
# 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 ValueError('No \'user\' parameter found for computing resource \'{}\' in its configuration'.format(computing.name))
return (computing_user, computing_host, computing_port, computing_keys)
#================================
# Tunnel (and proxy) setup
#================================
def setup_tunnel_and_proxy(task): def setup_tunnel_and_proxy(task):
...@@ -602,7 +642,13 @@ def setup_tunnel_and_proxy(task): ...@@ -602,7 +642,13 @@ def setup_tunnel_and_proxy(task):
tunnel_command= 'ssh -4 -i {} -o StrictHostKeyChecking=no -nNT -L 0.0.0.0:{}:{}:{} {}@{} & '.format(user_keys.private_key_file, task.tcp_tunnel_port, task.interface_ip, task.interface_port, first_user, first_host) tunnel_command= 'ssh -4 -i {} -o StrictHostKeyChecking=no -nNT -L 0.0.0.0:{}:{}:{} {}@{} & '.format(user_keys.private_key_file, task.tcp_tunnel_port, task.interface_ip, task.interface_port, first_user, first_host)
else: else:
tunnel_command= 'ssh -4 -o StrictHostKeyChecking=no -nNT -L 0.0.0.0:{}:{}:{} localhost & '.format(task.tcp_tunnel_port, task.interface_ip, task.interface_port)
if task.computing.access_mode.startswith('ssh'):
computing_user, computing_host, computing_port, computing_keys = get_ssh_access_mode_credentials(task.computing, task.user)
tunnel_command = 'ssh -p {} -o LogLevel=ERROR -i {} -4 -o StrictHostKeyChecking=no -o ConnectTimeout=10 '.format(computing_port, computing_keys.private_key_file)
tunnel_command += '-nNT -L 0.0.0.0:{}:{}:{} {}@{}'.format(task.tcp_tunnel_port, task.interface_ip, task.interface_port, computing_user, computing_host)
else:
tunnel_command= 'ssh -4 -o StrictHostKeyChecking=no -nNT -L 0.0.0.0:{}:{}:{} localhost & '.format(task.tcp_tunnel_port, task.interface_ip, task.interface_port)
background_tunnel_command = 'nohup {} >/dev/null 2>&1 &'.format(tunnel_command) background_tunnel_command = 'nohup {} >/dev/null 2>&1 &'.format(tunnel_command)
...@@ -713,46 +759,6 @@ Listen '''+str(task.tcp_tunnel_port)+''' ...@@ -713,46 +759,6 @@ Listen '''+str(task.tcp_tunnel_port)+'''
raise ErrorMessage('Something went wrong when loading the task proxy conf') raise ErrorMessage('Something went wrong when loading the task proxy conf')
def get_ssh_access_mode_credentials(computing, user):
from .models import KeyPair
# Get computing host
try:
computing_host = computing.conf.get('host')
except AttributeError:
computing_host = None
if not computing_host:
raise ValueError('No computing host?!')
# Get computing (SSH) port
try:
computing_port = computing.conf.get('port')
except AttributeError:
computing_port = 22
if not computing_host:
computing_port = 22
# Get computing user and keys
if computing.auth_mode == 'user_keys':
computing_user = user.profile.get_extra_conf('computing_user', computing)
if not computing_user:
raise ValueError('No \'computing_user\' parameter found for computing resource \'{}\' in user profile'.format(computing.name))
# 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 ValueError('No \'user\' parameter found for computing resource \'{}\' in its configuration'.format(computing.name))
return (computing_user, computing_host, computing_port, computing_keys)
def sanitize_container_env_vars(env_vars): def sanitize_container_env_vars(env_vars):
for env_var in env_vars: for env_var in env_vars:
......
...@@ -339,38 +339,10 @@ def account(request): ...@@ -339,38 +339,10 @@ def account(request):
#========================= #=========================
# Tasks view # Tasks view
#========================= #=========================
def set_verified_status(task):
# Chech status with ping
if task.status == 'running':
logger.debug('Task is running, check if startup completed')
logger.debug('Trying to establish connection on: "{}:{}"'.format(task.interface_ip,task.interface_port))
s = socket.socket()
try:
s.settimeout(1)
s.connect((task.interface_ip, task.interface_port))
# Not necessary, we just check that the container interfcae is up
#if not s.recv(10):
# logger.debug('No data read from socket')
# raise Exception('Could not read any data from socket')
except Exception as e:
logger.debug('Could not connect to socket')
if (pytz.UTC.localize(datetime.datetime.now())-task.created) > datetime.timedelta(hours=1):
task.verified_status = 'not working / killed'
else:
task.verified_status = 'starting up...'
else:
task.verified_status = 'running'
finally:
s.close()
else:
task.verified_status = task.status
@private_view @private_view
def tasks(request): def tasks(request):
...@@ -397,8 +369,7 @@ def tasks(request): ...@@ -397,8 +369,7 @@ def tasks(request):
task = Task.objects.get(user=request.user, uuid=uuid) task = Task.objects.get(user=request.user, uuid=uuid)
except Task.DoesNotExist: except Task.DoesNotExist:
raise ErrorMessage('Task does not exists or no access rights') raise ErrorMessage('Task does not exists or no access rights')
set_verified_status(task)
data['task'] = task data['task'] = task
# Task actions # Task actions
...@@ -479,7 +450,6 @@ def tasks(request): ...@@ -479,7 +450,6 @@ def tasks(request):
# Update task statuses # Update task statuses
for task in tasks: for task in tasks:
task.update_status() task.update_status()
set_verified_status(task)
# Set task and tasks variables # Set task and tasks variables
data['task'] = None data['task'] = None
...@@ -1154,7 +1124,6 @@ def task_connect(request): ...@@ -1154,7 +1124,6 @@ def task_connect(request):
if not task_uuid: if not task_uuid:
raise ErrorMessage('Empty task uuid') raise ErrorMessage('Empty task uuid')
# Get the task # Get the task
task = Task.objects.get(uuid=task_uuid) task = Task.objects.get(uuid=task_uuid)
...@@ -1164,6 +1133,28 @@ def task_connect(request): ...@@ -1164,6 +1133,28 @@ def task_connect(request):
# Ensure that the tunnel and proxy are set up # Ensure that the tunnel and proxy are set up
setup_tunnel_and_proxy(task) setup_tunnel_and_proxy(task)
# Check if task interface is up
if task.status == 'running':
logger.debug('Checking if task interface is running by trying to establish connection via local tunnel on port "{}"'.format(task.tcp_tunnel_port))
s = socket.socket()
try:
s.settimeout(1)
s.connect(('127.0.0.1', task.tcp_tunnel_port))
# Not necessary, we just check that the container interfcae is up
#if not s.recv(10):
# logger.debug('No data read from socket')
# raise Exception('Could not read any data from socket')
except Exception:
logger.debug('Could not connect to task interface')
task.interface_status = 'unknown'
else:
logger.debug('task interface is answering')
task.interface_status = 'running'
finally:
s.close()
else:
task.interface_status = 'unknown'
data ={} data ={}
data['task'] = task data['task'] = task
......