Skip to content
Snippets Groups Projects
Commit 8a5b8e6f authored by Stefano Alberto Russo's avatar Stefano Alberto Russo
Browse files

Added support for software container families and architectures.

parent f04869f0
Branches
Tags
No related merge requests found
Showing with 288 additions and 31 deletions
...@@ -175,6 +175,37 @@ to provide help, news and informations on your deployment. Or you can just ignor ...@@ -175,6 +175,37 @@ to provide help, news and informations on your deployment. Or you can just ignor
supports_interface_auth = True, supports_interface_auth = True,
interface_auth_user = None) interface_auth_user = None)
# Official Jupyter Lab
Container.objects.create(user = None,
name = 'Jupyter Lab',
description = 'The official Jupyter Lab. The Scipy variant, which includes popular packages from the scientific Python ecosystem.',
registry = 'docker.io',
image = 'jupyter/scipy-notebook',
tag = 'lab-3.2.2',
arch = 'amd64,arm64',
os = 'linux',
interface_port = '8888',
interface_protocol = 'http',
interface_transport = 'tcp/ip',
supports_custom_interface_port = True,
supports_interface_auth = True)
Container.objects.create(user = None,
name = 'Jupyter Lab',
description = 'The official Jupyter Lab. Includes popular packages from the scientific Python ecosystem.',
registry = 'docker.io',
image = 'jupyter/scipy-notebook',
tag = 'lab-3.1.17',
arch = 'amd64,arm64',
os = 'linux',
interface_port = '8888',
interface_protocol = 'http',
interface_transport = 'tcp/ip',
supports_custom_interface_port = True,
supports_interface_auth = True)
# SSH server # SSH server
Container.objects.create(user = None, Container.objects.create(user = None,
name = 'SSH server', name = 'SSH server',
......
...@@ -144,10 +144,9 @@ class Container(models.Model): ...@@ -144,10 +144,9 @@ class Container(models.Model):
user_str = self.user.email if self.user else None user_str = self.user.email if self.user else None
return str('Container "{}" of user "{}" with image "{}" and tag "{}" on registry "{}" '.format(self.name, user_str, self.image, self.tag, self.registry)) return str('Container "{}" of user "{}" with image "{}" and tag "{}" on registry "{}" '.format(self.name, user_str, self.image, self.tag, self.registry))
@ property @ property
def color(self): def color(self):
string_int_hash = hash_string_to_int(self.image + self.tag + self.registry) string_int_hash = hash_string_to_int(self.name + self.registry + self.image)
color_map_index = string_int_hash % len(color_map) color_map_index = string_int_hash % len(color_map)
return color_map[color_map_index] return color_map[color_map_index]
......
...@@ -115,6 +115,7 @@ ...@@ -115,6 +115,7 @@
{% csrf_token %} {% csrf_token %}
<input type="hidden" name="step" value="{{ data.next_step }}" /> <input type="hidden" name="step" value="{{ data.next_step }}" />
<input type="hidden" name="task_container_uuid" value="{{container.uuid}}"> <input type="hidden" name="task_container_uuid" value="{{container.uuid}}">
<input type="hidden" name="task_container_arch" value="{{container_arch}}">
<input type="hidden" name="task_computing_uuid" value="{{computing.uuid}}"> <input type="hidden" name="task_computing_uuid" value="{{computing.uuid}}">
<input type="submit" value="Choose" class="btn btn-connect"> <input type="submit" value="Choose" class="btn btn-connect">
</form> </form>
......
...@@ -109,7 +109,7 @@ ...@@ -109,7 +109,7 @@
<div style="padding:10px; margin-top:5px; text-align:center; border-bottom: {{container.color}} solid 10px; "> <div style="padding:10px; margin-top:5px; text-align:center; border-bottom: {{container.color}} solid 10px; ">
<a href="/software/?uuid={{ container.uuid }}">{{ container.name }}</a>&nbsp; <a href="/software/container_?uuid={{ container.uuid }}">{{ container.name }}</a>&nbsp;
</div> </div>
<div style="padding:10px; height: 110px; vertical-align: middle; "> <div style="padding:10px; height: 110px; vertical-align: middle; ">
...@@ -125,7 +125,7 @@ ...@@ -125,7 +125,7 @@
<div class="image-version-box"> <div class="image-version-box">
<!-- <font style="font-family:monospace; font-size:1.2em"></font> --> <!-- <font style="font-family:monospace; font-size:1.2em"></font> -->
<b>Image:</b> <code>{{ container.image }}</code><br/> <b>Image:</b> <code>{{ container.image }}</code><br/>
<b>Tag:</b> &nbsp;{{ container.tag }} <b>Tag:</b> &nbsp;{{ container.tag }} &nbsp;<b>Arch:</b> {{container_arch}}
</div> </div>
</div> </div>
......
{% if data.details %}
{% for container in container_family.members %}
{% include "components/container.html" with container=container details=data.details %}
{% endfor %}
{% else %}
<div style="width:300px; float:left; border: #e0e0e0 solid 1px; margin:10px; background:#f8f8f8; margin-bottom:15px">
<form action="/new_task" method=GET>
<input type="hidden" name="step" value="two">
<div style="padding:10px; margin-top:5px; text-align:center; border-bottom: {{container_family.color}} solid 10px; ">
<a href="/software/?container_family_id={{ container_family_id }}&details=True">{{ container_family.name }}</a>&nbsp;
</div>
<div style="padding:10px; height: 125px; ">
<!-- <div style="position: relative; top: 50%; transform: translateY(-50%);"> -->
{% if container_family.description %}
<div class="description-box" title="{{ container_family.description }}">
{{ container_family.description }}
</div>
{% else %}
<br/>
{% endif %}
<div class="image-version-box">
<b>Image:</b> <code>{{ container_family.image }}</code><br/>
<div style="margin-top:2px">
<span style="vertical-align:top;"><b>Tag:</b>&nbsp;</span>
<select name="task_container_uuid" style="font-size:0.8em">
{% for container in container_family.members %}
<option value="{{ container.uuid }}">{{ container.tag }}</option>
{% endfor %}
</select>
<span style="vertical-align:top; margin-left:5px"><b>Arch:</b>&nbsp;</span>
<select name="task_container_arch" style="font-size:0.8em">
<option value="auto" selected>auto</option>
{% for arch in container_family.all_archs %}
<option value="{{ arch }}">{{ arch }}</option>
{% endfor %}
</select>
</div>
</div>
<!-- </div> -->
</div>
<div style="margin-bottom:8px; margin-top: 3px; text-align:center">
{% if not disable_play_button %}
<button type="submit" onclick="myFunction()" class="btn-link btn btn-light" style="border: #c0c0c0 1px solid">
&nbsp;<i class="fa fa-play" style="color:green"></i>
</button>
{% endif %}
</form>
</div>
</div>
{% endif %}
\ No newline at end of file
<div style="width:300px; float:left; border: #e0e0e0 solid 1px; margin:10px; background:#f8f8f8; margin-bottom:15px">
<form action="/aaa" method=GET>
<div style="padding:10px; margin-top:5px; text-align:center; border-bottom: {{container.color}} solid 10px; ">
<a href="/software/?container_family={{ family_id }}">{{ container_family.name }}</a>&nbsp;
</div>
<div style="padding:10px; height: 125px; ">
<!-- <div style="position: relative; top: 50%; transform: translateY(-50%);"> -->
{% if container_family.description %}
<div class="description-box" title="{{ container_family.description }}">
{{ container_family.description }}
</div>
{% else %}
<br/>
{% endif %}
<div class="image-version-box">
<b>Image:</b> <code>{{ container_family.image }}</code><br/>
<div style="margin-top:2px">
<span style="vertical-align:top;"><b>Tag:</b>&nbsp;</span>
<select name="task_container_uuid_and_arch" style="font-size:0.8em">
{% for arch,container_by_tags in container_family.container_by_tags_by_arch.items %}
<optgroup label={{ arch }}>
{% for tag,container in container_by_tags.items %}
<option value="{{ container.uuid }}-{{ arch }}">{{ tag }}</option>
{% endfor %}
</optgroup>
{% endfor %}
</select>
</div>
</div>
<!-- </div> -->
</div>
<div style="margin-bottom:8px; margin-top: 3px; text-align:center">
{% if not disable_play_button %}
<button type="submit" onclick="myFunction()" class="btn-link btn btn-light" style="border: #c0c0c0 1px solid">
&nbsp;<i class="fa fa-play" style="color:green"></i>
</button>
{% endif %}
</form>
</div>
</div>
\ No newline at end of file
...@@ -24,7 +24,7 @@ ...@@ -24,7 +24,7 @@
<div style="float:left; background:#ffffff; margin-left:10px; margin-right:10px; margin-top:0px; margin-bottom:20px"> <div style="float:left; background:#ffffff; margin-left:10px; margin-right:10px; margin-top:0px; margin-bottom:20px">
<h4>Software container</h4> <h4>Software container</h4>
<div class="row" style="padding:5px"> <div class="row" style="padding:5px">
{% include "components/container.html" with container=data.task_container disable_play_button=True %} {% include "components/container.html" with container=data.task_container container_arch=data.task_container_arch disable_play_button=True %}
</div> </div>
</div> </div>
{% endif %} {% endif %}
...@@ -36,7 +36,7 @@ ...@@ -36,7 +36,7 @@
<h4>Computing resource</h4> <h4>Computing resource</h4>
<div class="row" style="padding:5px"> <div class="row" style="padding:5px">
{% for computing in data.computings %} {% for computing in data.computings %}
{% include "components/computing.html" with container=data.task_container %} {% include "components/computing.html" with container=data.task_container container_arch=data.task_container_arch %}
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
...@@ -76,6 +76,7 @@ ...@@ -76,6 +76,7 @@
<form action="/new_task/" method="POST"> <form action="/new_task/" method="POST">
{% csrf_token %} {% csrf_token %}
<input type="hidden" name="task_container_uuid" value="{{data.task_container.uuid}}"> <input type="hidden" name="task_container_uuid" value="{{data.task_container.uuid}}">
<input type="hidden" name="task_container_arch" value="{{data.task_container_arch}}">
<input type="hidden" name="step" value="{{ data.next_step }}" /> <input type="hidden" name="step" value="{{ data.next_step }}" />
<input type="hidden" name="task_name" value="{{ data.task_name }}" /> <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_container_uuid" value="{{ data.task_container.uuid }}" />
......
...@@ -9,8 +9,12 @@ ...@@ -9,8 +9,12 @@
<div class="dashboard"> <div class="dashboard">
<div class="span8 offset2"> <div class="span8 offset2">
{% if data.container %} {% if data.details %}
{% if data.container_families %}
<h1><a href="/software">Software containers</a> <span style="font-size:18px"> / {{ data.containers.0.name }}</span></h1>
{% else %}
<h1><a href="/software">Software containers</a> <span style="font-size:18px"> / {{ data.container.name }}</span></h1> <h1><a href="/software">Software containers</a> <span style="font-size:18px"> / {{ data.container.name }}</span></h1>
{% endif %}
{% else %} {% else %}
{% if data.mode == 'new_task' %} {% if data.mode == 'new_task' %}
<h1>New Task</h1> <h1>New Task</h1>
...@@ -66,10 +70,16 @@ ...@@ -66,10 +70,16 @@
{% if data.container %} {% if data.container %}
{% include "components/container.html" with container=data.container details=True %} {% include "components/container.html" with container=data.container details=True %}
{% else %} {% else %}
{% if data.container_families %}
{% for container_family_id, container_family in data.container_families.items %}
{% include "components/container_family.html" with container_family=container_family container_family_id=container_family_id%}
{% endfor %}
{% else %}
{% for container in data.containers %} {% for container in data.containers %}
{% include "components/container.html" with container=container %} {% include "components/container.html" with container=container %}
{% endfor %} {% endfor %}
{% endif %} {% endif %}
{% endif %}
</div> </div>
{% if not data.container %} {% if not data.container %}
......
...@@ -2,6 +2,7 @@ import os ...@@ -2,6 +2,7 @@ import os
import uuid import uuid
import json import json
import subprocess import subprocess
import base64
from django.conf import settings from django.conf import settings
from django.shortcuts import render from django.shortcuts import render
from django.contrib.auth import authenticate, login, logout from django.contrib.auth import authenticate, login, logout
...@@ -10,7 +11,7 @@ from django.contrib.auth.models import User ...@@ -10,7 +11,7 @@ from django.contrib.auth.models import User
from django.shortcuts import redirect from django.shortcuts import redirect
from django.db.models import Q from django.db.models import Q
from .models import Profile, LoginToken, Task, TaskStatuses, Container, Computing, KeyPair, Page from .models import Profile, LoginToken, Task, TaskStatuses, Container, Computing, KeyPair, Page
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 .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, get_md5
from .decorators import public_view, private_view from .decorators import public_view, private_view
from .exceptions import ErrorMessage from .exceptions import ErrorMessage
...@@ -463,6 +464,14 @@ def new_task(request): ...@@ -463,6 +464,14 @@ def new_task(request):
raise Exception('Consistency error, container with uuid "{}" does not exists or user "{}" does not have access rights'.format(task_container_uuid, request.user.email)) raise Exception('Consistency error, container with uuid "{}" does not exists or user "{}" does not have access rights'.format(task_container_uuid, request.user.email))
return task_container return task_container
# Get task container arch helper function
def get_task_container_arch(request):
container_arch = request.POST.get('task_container_arch', None)
if not container_arch:
# At the second step the task uuid is set via a GET request
container_arch = request.GET.get('task_container_arch', None)
return container_arch
# Get task computing helper function # Get task computing helper function
def get_task_computing(request): def get_task_computing(request):
task_computing_uuid = request.POST.get('task_computing_uuid', None) task_computing_uuid = request.POST.get('task_computing_uuid', None)
...@@ -496,8 +505,9 @@ def new_task(request): ...@@ -496,8 +505,9 @@ def new_task(request):
elif step == 'two': elif step == 'two':
# Get software container # Get software container and arch
data['task_container'] = get_task_container(request) data['task_container'] = get_task_container(request)
data['task_container_arch'] = get_task_container_arch(request)
# List all computing resources # List all computing resources
data['computings'] = list(Computing.objects.filter(group=None)) + list(Computing.objects.filter(group__user=request.user)) data['computings'] = list(Computing.objects.filter(group=None)) + list(Computing.objects.filter(group__user=request.user))
...@@ -507,8 +517,9 @@ def new_task(request): ...@@ -507,8 +517,9 @@ def new_task(request):
elif step == 'three': elif step == 'three':
# Get software container # Get software container and arch
data['task_container'] = get_task_container(request) data['task_container'] = get_task_container(request)
data['task_container_arch'] = get_task_container_arch(request)
# Get computing resource # Get computing resource
data['task_computing'] = get_task_computing(request) data['task_computing'] = get_task_computing(request)
...@@ -523,8 +534,9 @@ def new_task(request): ...@@ -523,8 +534,9 @@ def new_task(request):
elif step == 'last': elif step == 'last':
# Get software container # Get software container and arch
data['task_container'] = get_task_container(request) data['task_container'] = get_task_container(request)
data['task_container_arch'] = get_task_container_arch(request)
# Get computing resource # Get computing resource
data['task_computing'] = get_task_computing(request) data['task_computing'] = get_task_computing(request)
...@@ -691,8 +703,11 @@ def software(request): ...@@ -691,8 +703,11 @@ def software(request):
data['profile'] = Profile.objects.get(user=request.user) data['profile'] = Profile.objects.get(user=request.user)
# Get action if any # Get action if any
uuid = request.GET.get('uuid', None) container_uuid = request.GET.get('container_uuid', None)
container_family_id = request.GET.get('container_family_id', None)
action = request.GET.get('action', None) action = request.GET.get('action', None)
details = booleanize(request.GET.get('details', False))
# Get filter/search if any # Get filter/search if any
search_text = request.POST.get('search_text', '') search_text = request.POST.get('search_text', '')
...@@ -701,6 +716,7 @@ def software(request): ...@@ -701,6 +716,7 @@ def software(request):
# Set back to page data # Set back to page data
data['search_owner'] = search_owner data['search_owner'] = search_owner
data['search_text'] = search_text data['search_text'] = search_text
data['details'] = details
# Are we using this page as first step of a new task? # Are we using this page as first step of a new task?
data['mode'] = request.GET.get('mode', None) data['mode'] = request.GET.get('mode', None)
...@@ -708,14 +724,14 @@ def software(request): ...@@ -708,14 +724,14 @@ def software(request):
data['mode'] = request.POST.get('mode', None) data['mode'] = request.POST.get('mode', None)
# Do we have to operate on a specific container? # Do we have to operate on a specific container, or family of containers?
if uuid: if container_uuid:
try: try:
# Get the container (raises if none available including no permission) # Get the container (raises if none available including no permission)
try: try:
container = Container.objects.get(uuid=uuid) container = Container.objects.get(uuid=container_uuid)
except Container.DoesNotExist: except Container.DoesNotExist:
raise ErrorMessage('Container does not exists or no access rights') raise ErrorMessage('Container does not exists or no access rights')
if container.user and container.user != request.user: if container.user and container.user != request.user:
...@@ -736,6 +752,17 @@ def software(request): ...@@ -736,6 +752,17 @@ def software(request):
logger.error('Error in getting container with uuid="{}" or performing the required action: "{}"'.format(uuid, e)) logger.error('Error in getting container with uuid="{}" or performing the required action: "{}"'.format(uuid, e))
return render(request, 'error.html', {'data': data}) return render(request, 'error.html', {'data': data})
# Or, do we have to operate on a container family?
elif container_family_id:
# Get back name, registry and image from contsainer url
container_name, container_registry, container_image = base64.b64decode(container_family_id.encode('utf8')).decode('utf8').split('\t')
# get containers from the DB
user_containers = Container.objects.filter(user=request.user, name=container_name, registry=container_registry, image=container_image)
platform_containers = Container.objects.filter(user=None, name=container_name, registry=container_registry, image=container_image)
else:
# Get containers (fitered by search term, or all) # Get containers (fitered by search term, or all)
if search_text: if search_text:
...@@ -746,15 +773,61 @@ def software(request): ...@@ -746,15 +773,61 @@ def software(request):
user_containers = Container.objects.filter(user=request.user) user_containers = Container.objects.filter(user=request.user)
platform_containers = Container.objects.filter(user=None) platform_containers = Container.objects.filter(user=None)
# Filter by owner
# Ok, nilter by owner
if search_owner != 'All': if search_owner != 'All':
if search_owner == 'User': if search_owner == 'User':
platform_containers =[] platform_containers =[]
if search_owner == 'Platform': if search_owner == 'Platform':
user_containers = [] user_containers = []
# Create all container list
data['containers'] = list(user_containers) + list(platform_containers) data['containers'] = list(user_containers) + list(platform_containers)
# Merge containers with the same name, registry and image
data['container_families'] = {}
# Container family support class
class ContainerFamily(object):
def __init__(self, id, name, registry, image):
self.id = id
self.name = name
self.registry = registry
self.image = image
self.description = None
self.members = []
self.all_archs = []
self.container_by_tags_by_arch = {}
def add(self, container):
self.members.append(container)
if not self.description:
self.description = container.description
for arch in container.arch.split(','):
if not arch in self.all_archs:
self.all_archs.append(arch)
if not arch in self.container_by_tags_by_arch:
self.container_by_tags_by_arch[arch]={}
self.container_by_tags_by_arch[arch][container.tag] = container
@ property
def color(self):
try:
return self.members[0].color
except IndexError:
return '#000000'
# Populate container families
for container in data['containers']:
container_family_id = base64.b64encode('{}\t{}\t{}'.format(container.name, container.registry, container.image).encode('utf8')).decode('utf8')
if container_family_id not in data['container_families']:
data['container_families'][container_family_id] = ContainerFamily(container_family_id, container.name, container.registry, container.image)
data['container_families'][container_family_id].add(container)
return render(request, 'software.html', {'data': data}) return render(request, 'software.html', {'data': data})
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment