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
No related branches found
No related tags found
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
supports_interface_auth = True,
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
Container.objects.create(user = None,
name = 'SSH server',
......
......@@ -144,10 +144,9 @@ class Container(models.Model):
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))
@ property
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)
return color_map[color_map_index]
......
......@@ -115,6 +115,7 @@
{% csrf_token %}
<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_arch" value="{{container_arch}}">
<input type="hidden" name="task_computing_uuid" value="{{computing.uuid}}">
<input type="submit" value="Choose" class="btn btn-connect">
</form>
......
......@@ -109,7 +109,7 @@
<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 style="padding:10px; height: 110px; vertical-align: middle; ">
......@@ -125,7 +125,7 @@
<div class="image-version-box">
<!-- <font style="font-family:monospace; font-size:1.2em"></font> -->
<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>
......
{% 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 @@
<div style="float:left; background:#ffffff; margin-left:10px; margin-right:10px; margin-top:0px; margin-bottom:20px">
<h4>Software container</h4>
<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>
{% endif %}
......@@ -36,7 +36,7 @@
<h4>Computing resource</h4>
<div class="row" style="padding:5px">
{% 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 %}
</div>
</div>
......@@ -76,6 +76,7 @@
<form action="/new_task/" method="POST">
{% csrf_token %}
<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="task_name" value="{{ data.task_name }}" />
<input type="hidden" name="task_container_uuid" value="{{ data.task_container.uuid }}" />
......
......@@ -9,8 +9,12 @@
<div class="dashboard">
<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>
{% endif %}
{% else %}
{% if data.mode == 'new_task' %}
<h1>New Task</h1>
......@@ -66,10 +70,16 @@
{% if data.container %}
{% include "components/container.html" with container=data.container details=True %}
{% 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 %}
{% include "components/container.html" with container=container %}
{% endfor %}
{% endif %}
{% endif %}
</div>
{% if not data.container %}
......
......@@ -2,6 +2,7 @@ import os
import uuid
import json
import subprocess
import base64
from django.conf import settings
from django.shortcuts import render
from django.contrib.auth import authenticate, login, logout
......@@ -10,7 +11,7 @@ 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, 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 .exceptions import ErrorMessage
......@@ -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))
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
def get_task_computing(request):
task_computing_uuid = request.POST.get('task_computing_uuid', None)
......@@ -496,8 +505,9 @@ def new_task(request):
elif step == 'two':
# Get software container
# Get software container and arch
data['task_container'] = get_task_container(request)
data['task_container_arch'] = get_task_container_arch(request)
# List all computing resources
data['computings'] = list(Computing.objects.filter(group=None)) + list(Computing.objects.filter(group__user=request.user))
......@@ -507,8 +517,9 @@ def new_task(request):
elif step == 'three':
# Get software container
# Get software container and arch
data['task_container'] = get_task_container(request)
data['task_container_arch'] = get_task_container_arch(request)
# Get computing resource
data['task_computing'] = get_task_computing(request)
......@@ -523,8 +534,9 @@ def new_task(request):
elif step == 'last':
# Get software container
# Get software container and arch
data['task_container'] = get_task_container(request)
data['task_container_arch'] = get_task_container_arch(request)
# Get computing resource
data['task_computing'] = get_task_computing(request)
......@@ -691,8 +703,11 @@ def software(request):
data['profile'] = Profile.objects.get(user=request.user)
# 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)
details = booleanize(request.GET.get('details', False))
# Get filter/search if any
search_text = request.POST.get('search_text', '')
......@@ -701,6 +716,7 @@ def software(request):
# Set back to page data
data['search_owner'] = search_owner
data['search_text'] = search_text
data['details'] = details
# Are we using this page as first step of a new task?
data['mode'] = request.GET.get('mode', None)
......@@ -708,14 +724,14 @@ def software(request):
data['mode'] = request.POST.get('mode', None)
# Do we have to operate on a specific container?
if uuid:
# Do we have to operate on a specific container, or family of containers?
if container_uuid:
try:
# Get the container (raises if none available including no permission)
try:
container = Container.objects.get(uuid=uuid)
container = Container.objects.get(uuid=container_uuid)
except Container.DoesNotExist:
raise ErrorMessage('Container does not exists or no access rights')
if container.user and container.user != request.user:
......@@ -736,6 +752,17 @@ def software(request):
logger.error('Error in getting container with uuid="{}" or performing the required action: "{}"'.format(uuid, e))
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)
if search_text:
......@@ -746,15 +773,61 @@ def software(request):
user_containers = Container.objects.filter(user=request.user)
platform_containers = Container.objects.filter(user=None)
# Filter by owner
# Ok, nilter by owner
if search_owner != 'All':
if search_owner == 'User':
platform_containers =[]
if search_owner == 'Platform':
user_containers = []
# Create all container list
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})
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment