Browse Source

Split up code. Put OpenStack in own application

V1
Joshua Rubingh 1 year ago
parent
commit
b6acf2e523
  1. 1
      VRE/VRE/settings.py
  2. 1
      VRE/apps/openstack/__init__.py
  3. 24
      VRE/apps/openstack/admin.py
  4. 31
      VRE/apps/openstack/apps.py
  5. 67
      VRE/apps/openstack/fixtures/openstack_initial_data.json
  6. 50
      VRE/apps/openstack/migrations/0001_initial.py
  7. 0
      VRE/apps/openstack/migrations/__init__.py
  8. 125
      VRE/apps/openstack/models.py
  9. 58
      VRE/apps/openstack/signals.py
  10. 69
      VRE/apps/openstack/tasks.py
  11. 3
      VRE/apps/openstack/tests.py
  12. 3
      VRE/apps/virtual_machine/apps.py
  13. 17
      VRE/apps/virtual_machine/migrations/0003_remove_virtualmachine_remote_id.py
  14. 18
      VRE/apps/virtual_machine/models.py
  15. 45
      VRE/apps/virtual_machine/signals.py
  16. 6
      VRE/apps/vrw/admin.py
  17. 101
      VRE/apps/vrw/fixtures/vrw_initial_data.json
  18. 20
      VRE/apps/vrw/migrations/0003_auto_20210415_1324.py
  19. 2
      VRE/apps/vrw/models.py
  20. 6
      VRE/apps/vrw/signals.py
  21. 2
      VRE/lib/models/admin.py
  22. 29
      doc/OpenStack.rst
  23. BIN
      doc/documentation.pdf
  24. 1
      doc/index.rst

1
VRE/VRE/settings.py

@ -50,6 +50,7 @@ INSTALLED_APPS = [ @@ -50,6 +50,7 @@ INSTALLED_APPS = [
'apps.study',
'apps.virtual_machine',
'apps.vrw',
'apps.openstack',
'djoser',
'rest_framework',

1
VRE/apps/openstack/__init__.py

@ -0,0 +1 @@ @@ -0,0 +1 @@
default_app_config = 'apps.openstack.apps.OpenstackConfig'

24
VRE/apps/openstack/admin.py

@ -0,0 +1,24 @@ @@ -0,0 +1,24 @@
from django.contrib import admin
from safedelete.admin import SafeDeleteAdmin
from .models import WorkspacePart, Workspace
from lib.models.admin import VMRelatedSelectWidget
# Register your models here.
@admin.register(WorkspacePart)
class OpenstackPartAdmin(SafeDeleteAdmin):
list_display = ('name', 'created_at') + SafeDeleteAdmin.list_display
search_fields = ('name', )
ordering = ('-created_at', 'name', )
readonly_fields = ('created_at', 'updated_at')
def formfield_for_dbfield(self, db_field, **kwargs):
if db_field.name == 'content_type':
kwargs['widget'] = VMRelatedSelectWidget
return super().formfield_for_dbfield(db_field,**kwargs)
@admin.register(Workspace)
class OpenstackWorkspaceAdmin(SafeDeleteAdmin):
list_display = ('virtual_machine', 'created_at') + SafeDeleteAdmin.list_display
ordering = ('-created_at', )
readonly_fields = ('created_at', 'updated_at')

31
VRE/apps/openstack/apps.py

@ -0,0 +1,31 @@ @@ -0,0 +1,31 @@
from django.apps import AppConfig
from django.conf import settings
from django.utils.translation import ugettext_lazy as _
from datetime import timedelta
class OpenstackConfig(AppConfig):
"""This is the default configuration for the OpenStack application.
The settings can be configured in the main settings.py file of the project.
Args:
settings.OPENSTACK_MACHINE_ACTIVE_DURATION (timedelta): This is the duration of which a :term:`VPS` is available. Default 1 year.
"""
name = 'apps.openstack'
label = 'openstack'
verbose_name = _('OpenStack')
verbose_name_plural = _('OpenStack')
try:
assert settings.OPENSTACK_MACHINE_ACTIVE_DURATION
except AttributeError:
# We only load this setting, if it is not available in the overall settings.py file
settings.OPENSTACK_MACHINE_ACTIVE_DURATION = timedelta(days=365)
def ready(self):
"""
Load custom :attr:`~apps.openstack.signals` for creating :term:`VPS` models
"""
import apps.openstack.signals

67
VRE/apps/openstack/fixtures/openstack_initial_data.json

@ -0,0 +1,67 @@ @@ -0,0 +1,67 @@
[
{
"model": "openstack.workspacepart",
"pk": 1,
"fields": {
"deleted": null,
"created_at": "2021-04-15T08:07:32.636Z",
"updated_at": "2021-04-15T08:07:32.636Z",
"cloud_id": "3918e28f-d1ee-4f8b-aebe-d49b256de249",
"name": "Private network (internal)",
"content_type": 17,
"object_id": 1
}
},
{
"model": "openstack.workspacepart",
"pk": 2,
"fields": {
"deleted": null,
"created_at": "2021-04-15T08:07:57.293Z",
"updated_at": "2021-04-15T08:07:57.293Z",
"cloud_id": "136fa66d-4887-4a88-912f-9209dc3c6c45",
"name": "Public network (vlan16)",
"content_type": 17,
"object_id": 2
}
},
{
"model": "openstack.workspacepart",
"pk": 3,
"fields": {
"deleted": null,
"created_at": "2021-04-15T08:09:00.238Z",
"updated_at": "2021-04-15T08:09:00.238Z",
"cloud_id": "ea70716d-7a9b-4345-9987-a96acc8e8949",
"name": "Ubuntu 18.04 LTS",
"content_type": 18,
"object_id": 1
}
},
{
"model": "openstack.workspacepart",
"pk": 4,
"fields": {
"deleted": null,
"created_at": "2021-04-15T08:10:01.328Z",
"updated_at": "2021-04-15T13:23:45.707Z",
"cloud_id": "c2ad7a1e-937f-445e-b33a-b604711e2468",
"name": "Basic profile",
"content_type": 20,
"object_id": 1
}
},
{
"model": "openstack.workspace",
"pk": 8,
"fields": {
"deleted": null,
"created_at": "2021-04-15T08:53:59.211Z",
"updated_at": "2021-04-15T09:58:35.280Z",
"cloud_id": "8ed99d27-33b8-4b77-b408-a6c0423d0b2e",
"starting_at": "2021-04-15T08:53:59.208Z",
"ending_at": "2022-04-15T08:53:59.208Z",
"status": "NEW"
}
}
]

50
VRE/apps/openstack/migrations/0001_initial.py

@ -0,0 +1,50 @@ @@ -0,0 +1,50 @@
# Generated by Django 3.1.7 on 2021-04-15 13:28
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
('virtual_machine', '0003_remove_virtualmachine_remote_id'),
('contenttypes', '0002_remove_content_type_name'),
]
operations = [
migrations.CreateModel(
name='Workspace',
fields=[
('deleted', models.DateTimeField(editable=False, null=True)),
('created_at', models.DateTimeField(auto_now_add=True, help_text='The date and time this model has been created', verbose_name='Date created')),
('updated_at', models.DateTimeField(auto_now=True, help_text='The date and time this model has been updated', verbose_name='Date updated')),
('cloud_id', models.CharField(blank=True, help_text='The ID on the cloud platform', max_length=50, null=True)),
('virtual_machine', models.OneToOneField(help_text='The virtual machine configuration.', on_delete=django.db.models.deletion.CASCADE, primary_key=True, related_name='workspace_openstack', serialize=False, to='virtual_machine.virtualmachine')),
('starting_at', models.DateTimeField(help_text='Enter the start date when this workspace should be created', verbose_name='Starting at')),
('ending_at', models.DateTimeField(help_text='Enter the end date when this workspace should be terminated', verbose_name='Ending at')),
('status', models.CharField(choices=[('NEW', 'New'), ('UPDATING', 'Updating'), ('DONE', 'Done'), ('ERROR', 'Error'), ('OFFLINE', 'Offline'), ('DELETE', 'Delete'), ('TERMINATED', 'Terminated')], default='NEW', help_text='The status of the workspace.', max_length=10, verbose_name='Status')),
],
options={
'verbose_name': 'virtual workspace',
'verbose_name_plural': 'virtual workspaces',
},
),
migrations.CreateModel(
name='WorkspacePart',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('deleted', models.DateTimeField(editable=False, null=True)),
('created_at', models.DateTimeField(auto_now_add=True, help_text='The date and time this model has been created', verbose_name='Date created')),
('updated_at', models.DateTimeField(auto_now=True, help_text='The date and time this model has been updated', verbose_name='Date updated')),
('cloud_id', models.CharField(blank=True, help_text='The ID on the cloud platform', max_length=50, null=True)),
('name', models.CharField(help_text='Human readable name for this workspace part.', max_length=100, verbose_name='Name')),
('object_id', models.PositiveIntegerField()),
('content_type', models.ForeignKey(help_text='Select the Virtual Machine part that correspondents with this workspace part.', limit_choices_to={'app_label': 'virtual_machine'}, on_delete=django.db.models.deletion.CASCADE, related_name='openstack_ct', to='contenttypes.contenttype')),
],
options={
'abstract': False,
},
),
]

0
VRE/apps/openstack/migrations/__init__.py

125
VRE/apps/openstack/models.py

@ -0,0 +1,125 @@ @@ -0,0 +1,125 @@
from django.db import models
from django.utils.translation import gettext_lazy as _
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from lib.models.base import MetaDataModel
from lib.models.cloud import CloudBasicDataModel
from apps.virtual_machine.models import VirtualMachine
# Create your models here.
class WorkspacePart(MetaDataModel, CloudBasicDataModel):
"""
A class for creating :term:`VPS` parts as counterparts for the Virtual Machine parts (:class:`~apps.virtual_machine.models.VirtualMachinePart`). This means that we can make translations between the general Virtual Machine Part model and this specific :term:`VPS` Part model.
In order to create :term:`VPS` parts you need to select the Virtual machine part type and item to make a Virtual Machine part translation. Together with a cloud_id you can then make virtual machines with the right configurations.
It will inherit the attributes :attr:`~lib.models.base.MetaDataModel.created_at` and :attr:`~lib.models.base.MetaDataModel.updated_at` from the Abstract model :class:`~lib.models.base.MetaDataModel`
It will inherit the attributes :attr:`~lib.models.cloud.CloudBasicDataModel.cloud_id` from the Abstract model :class:`~lib.models.cloud.CloudBasicDataModel`
Attributes
----------
name : str
The name of :term:`VPS` part. Use a easy to remember name for humans.
content_type : ContentType
Specify the virtual machine part type.
object_id : int
Specify the virtual machine part id
"""
name = models.CharField(_('Name'), max_length=100, help_text=_('Human readable name for this workspace part.'))
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE, limit_choices_to={'app_label': 'virtual_machine'}, help_text=_('Select the Virtual Machine part that correspondents with this workspace part.'), related_name='openstack_ct')
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey('content_type', 'object_id')
def __str__(self):
return f'OpenStack Workspace part: {self.name}'
class WorkspaceStatus(models.TextChoices):
"""
A class for defining :term:`VPS` status as choices. Currently the following statusses are supported:
.. data:: NEW
This is the state where every :term:`VPS` starts with. This means that the :term:`VPS` config is created, but it has not been created yet by the cloud provider
.. data:: UPDATING
The :term:`VPS` is being created on the cloud provider infrastructure.
.. data:: DONE
The :term:`VPS` is created by the cloud provider and can be used by the customer
.. data:: ERROR
The :term:`VPS` could not be created by the cloud provider. There was an (unknown) error.
.. data:: OFFLINE
The :term:`VPS` is offline and not reachable. Reasons are not known. But this can be used for trigger an investigation
.. data:: DELETE
The :term:`VPS` is marked for deleting fromt the cloud platform
.. data:: TERMINATED
The :term:`VPS` is closed/deleted by the cloud provider wihtout reason.
"""
NEW = ('NEW',_('New'))
UPDATING = ('UPDATING',_('Updating'))
DONE = ('DONE',_('Done'))
ERROR = ('ERROR',_('Error'))
OFFLINE = ('OFFLINE',_('Offline'))
DELETE = ('DELETE',_('Delete'))
TERMINATED = ('TERMINATED',_('Terminated'))
class Workspace(MetaDataModel, CloudBasicDataModel):
"""
A class for creating :term:`VPS` from Virtual Machines. When creating a new Workspace with the status :attr:`~WorkspaceStatus.NEW`, it should be picked up by the Workspace system in order to create the new :term:`VPS`.
By default the :term:`VPS` is created by a signal process where it uses the variable ':attr:`~settings.OPENSTACK_MACHINE_ACTIVE_DURATION`' to create :term:`VPS` for 1 year.
It will inherit the attributes :attr:`~lib.models.base.MetaDataModel.created_at` and :attr:`~lib.models.base.MetaDataModel.updated_at` from the Abstract model :class:`~lib.models.base.MetaDataModel`
It will inherit the attributes :attr:`~lib.models.cloud.CloudBasicDataModel.cloud_id` from the Abstract model :class:`~lib.models.cloud.CloudBasicDataModel`
Attributes
----------
virtual_machine : :class:`~apps.virtual_machine.models.VirtualMachine`
The Virtual Machine that needs to be created as a :term:`VPS`
starting_at : Datetime
The date and timestamp when this :term:`VPS` should be created.
ending_at : int
The date and timestamp when this :term:`VPS` should be removed.
status : string
The status of the :term:`VPS`. When created. Default is :attr:`~WorkspaceStatus.NEW`
"""
class Meta:
verbose_name = _('virtual workspace')
verbose_name_plural = _('virtual workspaces')
virtual_machine = models.OneToOneField(VirtualMachine, on_delete=models.CASCADE, primary_key=True, help_text=_('The virtual machine configuration.'), related_name='workspace_openstack')
starting_at = models.DateTimeField(_('Starting at'), help_text=_('Enter the start date when this workspace should be created'))
ending_at = models.DateTimeField(_('Ending at'), help_text=_('Enter the end date when this workspace should be terminated'))
status = models.CharField(_('Status'), default=WorkspaceStatus.NEW, max_length=10, choices=WorkspaceStatus.choices, help_text=_('The status of the workspace.'))
@property
def type(self):
"""Return the translated :term:`VPS` profile name.
Returns:
string: The :attr:`~lib.models.cloud.CloudBasicDataModel.cloud_id` value of the :term:`VPS` part
"""
try:
openstack_part = WorkspacePart.objects.filter(content_type=ContentType.objects.get_for_model(self.virtual_machine.profile),
object_id=self.virtual_machine.profile.pk).get()
return openstack_part.cloud_id
except WorkspacePart.DoesNotExist:
return self.virtual_machine.profile.name

58
VRE/apps/openstack/signals.py

@ -0,0 +1,58 @@ @@ -0,0 +1,58 @@
from django.conf import settings
from django.contrib.contenttypes.models import ContentType
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.utils import timezone
from safedelete.signals import post_softdelete
from apps.virtual_machine.models import VirtualMachine
from .models import Workspace as OpenStackWorkspace, WorkspacePart as OpenStackWorkspacePart
from .tasks import create_virtual_machine_task, delete_virtual_machine_task
@receiver(post_save, sender=VirtualMachine)
def create_virtual_machine_openstack(sender, instance, created, **kwargs):
"""When a new virtual machine is created, this signal will be fired in order to check if a :term:`VPS` needs te be created.
When a VirtualMachine is created for the first time, and does not have a VRW Workspace attached, the software will check on the operating system what to do.
At this point the check if a :term:`VPS` needs te be created is done on the **operating system**. If the selected operating system has a :term:`VPS` Part configured, it is assumed that we need to create the :term:`VPS`.
Args:
sender (class): The modelclass VirtualMachine
instance (VirtualMachine): The Virtual machine that is either created or updated.
created (bool): Is the Virtual Machine created. If false, it is an update.
"""
# When a new virtual machine is created and does not have a VRW Workspace attached to it, it is a candidate for the VRW
if created and not instance.has_workspace and instance.operating_system is not None:
# Here we check if the Operating system is known as VRW part. If so, we create a VRW machine thqt is active for 1 year.
try:
_ = OpenStackWorkspacePart.objects.filter(content_type=ContentType.objects.get_for_model(instance.operating_system),
object_id=instance.operating_system.pk).get()
new_workspace = OpenStackWorkspace(virtual_machine=instance, starting_at=timezone.now(), ending_at=timezone.now() + settings.OPENSTACK_MACHINE_ACTIVE_DURATION)
new_workspace.save()
create_virtual_machine_task(new_workspace.pk)
except OpenStackWorkspacePart.DoesNotExist:
# Not a Linux Operating system, so ignore here
pass
@receiver(post_softdelete, sender=VirtualMachine)
def terminate_virtual_machine(sender, instance, **kwargs):
if hasattr(instance,'workspace_openstack'):
try:
workspace = OpenStackWorkspace.objects.get(pk=instance.workspace_openstack.pk)
# When the workspace is not a OpenStackWorkspace, we get a Does Not Exist exception.
if workspace.cloud_id:
delete_virtual_machine_task(workspace.cloud_id)
# Delete the OpenStackWorkspace that is related to the VirtualMachine
workspace.delete()
except OpenStackWorkspace.DoesNotExist:
# This workspace is not an Openstack Workspace
pass

69
VRE/apps/virtual_machine/tasks.py → VRE/apps/openstack/tasks.py

@ -1,3 +1,5 @@ @@ -1,3 +1,5 @@
from django.contrib.contenttypes.models import ContentType
from huey.contrib.djhuey import periodic_task, task
from lib.cloud.openstack_client import VRE_OpenStackClient
@ -7,12 +9,31 @@ import crypt @@ -7,12 +9,31 @@ import crypt
import string
import random
from .models import VirtualMachine, VirtualNetworkType, VirtualMachineAccess
from apps.virtual_machine.models import VirtualNetworkType, VirtualMachineAccess
from .models import Workspace as OpenStackWorkspace, WorkspacePart as OpenStackWorkspacePart
# Create the virtual machine in the background with a 30 sec delay, so that the model is fully saved (M2M)
@task(delay=30)
def create_virtual_machine_task(virtual_machine_id):
virtual_machine = VirtualMachine.objects.get(id=virtual_machine_id)
def create_virtual_machine_task(workspace_id):
"""
This task will create a new :term:`VPS` with a 30 seconds delay. It will use the requested resources to create a new :term:`VPS`.
The actions that are done here are:
- Generate a new password for authentication on the :term:`VPS`
- Generate a public and private SSH key (RSA 4096 bits) for authentication on the :term:`VPS`
- Create a mount point for the research data
- Add auto mounting of the research data
- Setting up the right network connections
- Store the IPv4 of the new :term:`VPS` for later use.
Args:
workspace_id (int): The ID of the newly created workspace
"""
workspace = OpenStackWorkspace.objects.get(pk=workspace_id)
virtual_machine = workspace.virtual_machine
print(f'Create VPS for: {virtual_machine}')
@ -57,19 +78,31 @@ runcmd: @@ -57,19 +78,31 @@ runcmd:
# Openstack values:
name = f'VRE_{virtual_machine.study.name}_({virtual_machine.researcher.user.get_full_name()})'
flavour = virtual_machine.profile.vm_code
image = virtual_machine.operating_system.vm_code
flavour = OpenStackWorkspacePart.objects.filter(content_type=ContentType.objects.get_for_model(virtual_machine.profile),
object_id=virtual_machine.profile.pk).get()
flavour = flavour.cloud_id
image = OpenStackWorkspacePart.objects.filter(content_type=ContentType.objects.get_for_model(virtual_machine.operating_system),
object_id=virtual_machine.operating_system.pk).get()
image = image.cloud_id
networks = {
'private' : [],
'float' : []
}
# Only add private networks now, the public network should be added as a floating IP
for network in virtual_machine.networks.all():
openstack_network = OpenStackWorkspacePart.objects.filter(content_type=ContentType.objects.get_for_model(network),
object_id=network.pk).get()
if network.network_type == VirtualNetworkType.PRIVATE:
networks['private'].append(network.vm_code)
networks['private'].append(openstack_network.cloud_id)
elif network.network_type == VirtualNetworkType.PUBLIC:
networks['float'].append(network.vm_code)
networks['float'].append(openstack_network.cloud_id)
opts = {
@ -81,13 +114,14 @@ runcmd: @@ -81,13 +114,14 @@ runcmd:
print('Connecting to Openstack')
client = VRE_OpenStackClient('hpc')
print(f'Createing a new VPS... please wait...')
result = client.create_vps(name, image, flavour, networks, opts)
print(result)
virtual_machine.remote_id = result['id']
virtual_machine.save()
workspace.cloud_id = result['id']
workspace.save()
# TODO: figure this out.... why update_or_create does not work... For now, we work around it.
# # TODO: figure this out.... why update_or_create does not work... For now, we work around it.
access, created = VirtualMachineAccess.objects.get_or_create(
researcher = virtual_machine.researcher,
virtual_machine = virtual_machine
@ -98,10 +132,15 @@ runcmd: @@ -98,10 +132,15 @@ runcmd:
access.password = ssh_password
access.save()
print(f'Created VPS {virtual_machine} -> {virtual_machine.remote_id}')
print(f'Created VPS {virtual_machine} -> {workspace.cloud_id}')
@task(delay=30)
def delete_virtual_machine_task(virtual_machine_id):
def delete_virtual_machine_task(cloud_id):
"""
This task will delete a :term:`VPS` with a 30 seconds delay.
Args:
cloud_id (int): The remote ID of the :term:`VPS` that needs to be deleted.
"""
client = VRE_OpenStackClient('hpc')
client.remove_vps(virtual_machine_id)
print(f'Deleted VPS with ID {virtual_machine_id}')
client.remove_vps(cloud_id)

3
VRE/apps/openstack/tests.py

@ -0,0 +1,3 @@ @@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

3
VRE/apps/virtual_machine/apps.py

@ -6,6 +6,3 @@ class VirtualMachineConfig(AppConfig): @@ -6,6 +6,3 @@ class VirtualMachineConfig(AppConfig):
label = 'virtual_machine'
verbose_name = _('Virtual Machine')
verbose_name_plural = _('Virtual Machines')
def ready(self):
import apps.virtual_machine.signals

17
VRE/apps/virtual_machine/migrations/0003_remove_virtualmachine_remote_id.py

@ -0,0 +1,17 @@ @@ -0,0 +1,17 @@
# Generated by Django 3.1.7 on 2021-04-15 13:24
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('virtual_machine', '0002_auto_20210413_0813'),
]
operations = [
migrations.RemoveField(
model_name='virtualmachine',
name='remote_id',
),
]

18
VRE/apps/virtual_machine/models.py

@ -254,8 +254,6 @@ class VirtualMachine(MetaDataModel): @@ -254,8 +254,6 @@ class VirtualMachine(MetaDataModel):
verbose_name = _('virtual machine')
verbose_name_plural = _('virtual machines')
remote_id = models.CharField(max_length=50, blank=True, null=True, help_text=_('The remote ID of this virtual machine on the cloud platform'))
name = models.CharField(max_length=50,help_text=_('Easy to remember name for this virtual machine.'))
researcher = models.ForeignKey(Researcher, on_delete=models.CASCADE, help_text=_('The researcher that own this virtual machine.'))
@ -286,6 +284,22 @@ class VirtualMachine(MetaDataModel): @@ -286,6 +284,22 @@ class VirtualMachine(MetaDataModel):
def __str__(self):
return self.name
@property
def has_workspace(self):
"""This function will look through all the attribute looking for an atribute starting with `workspace_` that has a value. This will mean that there is a virtual workspace created for this machine.
Returns:
bool: Return True when this Virtual Machine has a workspace configured. If not it return False
"""
# Not sure if this is a correct way of doing it. Keeping it as loose coupled as possible
for attr in dir(self):
if attr.startswith('workspace_') and hasattr(self, attr):
return True
return False
@property
def total_memory(self):
"""

45
VRE/apps/virtual_machine/signals.py

@ -1,45 +0,0 @@ @@ -1,45 +0,0 @@
from django.db.models.signals import post_save, post_delete, pre_save
from django.dispatch import receiver
from .models import VirtualMachine, VirtualNetworkType, VirtualMachineAccess
from .tasks import create_virtual_machine_task, delete_virtual_machine_task
# @receiver(pre_save, sender=VirtualMachine)
# def create_virtual_machine(sender, instance, **kwargs):
# """
# | A signal that is fired when a new virtual machine is created in the DB. This will trigger the action to create a VPS on a cloud platform
# Arguments
# ----------
# sender : sender
# The model that has triggered the signal
# instance: :attr:`VirtualMachine`
# The newly created virtual machine object
# created : boolean
# Wether the object was created or updated. When true it is newly created
# """
# print('Virtual machine instance... Here we check if it is VRW (Windows) or HPC (Linux)')
# print(instance)
# print(dir(instance))
# print(instance.operating_system)
# print(dir(instance.operating_system))
# if instance.remote_id:
# # We have already a connection with the cloud instance... For now we do nothing.
# return
# if 'linux' in instance.operating_system.name.lower():
# create_virtual_machine_task(instance.pk)
# @receiver(post_delete, sender=VirtualMachine)
# def terminate_virtual_machine(sender, instance, **kwargs):
# if instance.remote_id:
# # If we have a remote_id in the instance, then we need to delete the VPS from the cloud provider
# pass
# #delete_virtual_machine_task(instance.remote_id)

6
VRE/apps/vrw/admin.py

@ -2,7 +2,7 @@ from django.contrib import admin @@ -2,7 +2,7 @@ from django.contrib import admin
from safedelete.admin import SafeDeleteAdmin
from .models import WorkspacePart, Workspace
from lib.models.admin import VRWRelatedSelectWidget
from lib.models.admin import VMRelatedSelectWidget
# Register your models here.
@admin.register(WorkspacePart)
@ -14,8 +14,8 @@ class VRWPartAdmin(SafeDeleteAdmin): @@ -14,8 +14,8 @@ class VRWPartAdmin(SafeDeleteAdmin):
def formfield_for_dbfield(self, db_field, **kwargs):
if db_field.name == 'content_type':
kwargs['widget'] = VRWRelatedSelectWidget
return super(VRWPartAdmin, self).formfield_for_dbfield(db_field,**kwargs)
kwargs['widget'] = VMRelatedSelectWidget
return super().formfield_for_dbfield(db_field,**kwargs)
@admin.register(Workspace)
class VRWWorkspaceAdmin(SafeDeleteAdmin):

101
VRE/apps/vrw/fixtures/vrw_initial_data.json

@ -1,51 +1,54 @@ @@ -1,51 +1,54 @@
[
{
"model": "vrw.workspacepart",
"pk": 1,
"fields": {
"created_at": "2021-03-29T11:25:46.018Z",
"updated_at": "2021-04-12T08:10:30.941Z",
"cloud_id": "vrw_windows_10",
"name": "Windows 10",
"content_type": 18,
"object_id": 2
{
"model": "vrw.workspacepart",
"pk": 1,
"fields": {
"deleted": null,
"created_at": "2021-03-29T11:25:46.018Z",
"updated_at": "2021-04-15T13:23:05.399Z",
"cloud_id": "vrw_windows_10",
"name": "Windows 10",
"content_type": 18,
"object_id": 2
}
},
{
"model": "vrw.workspacepart",
"pk": 2,
"fields": {
"deleted": null,
"created_at": "2021-04-06T13:28:23.491Z",
"updated_at": "2021-04-15T13:23:30.036Z",
"cloud_id": "basic",
"name": "Basic profile",
"content_type": 20,
"object_id": 1
}
},
{
"model": "vrw.workspacepart",
"pk": 3,
"fields": {
"deleted": null,
"created_at": "2021-04-06T13:28:47.745Z",
"updated_at": "2021-04-15T13:22:58.682Z",
"cloud_id": "premium",
"name": "Premium profile",
"content_type": 20,
"object_id": 2
}
},
{
"model": "vrw.workspace",
"pk": 1,
"fields": {
"deleted": null,
"created_at": "2021-04-06T07:33:01.168Z",
"updated_at": "2021-04-06T07:33:01.168Z",
"cloud_id": null,
"starting_at": "2021-04-06T07:33:01.166Z",
"ending_at": "2022-04-06T07:33:01.166Z",
"status": "NEW"
}
}
},
{
"model": "vrw.workspacepart",
"pk": 2,
"fields": {
"created_at": "2021-04-06T13:28:23.491Z",
"updated_at": "2021-04-12T08:19:13.535Z",
"cloud_id": "basic",
"name": "Basic profile",
"content_type": 20,
"object_id": 1
}
},
{
"model": "vrw.workspacepart",
"pk": 3,
"fields": {
"created_at": "2021-04-06T13:28:47.745Z",
"updated_at": "2021-04-12T08:10:10.278Z",
"cloud_id": "premium",
"name": "Premium profile",
"content_type": 20,
"object_id": 2
}
},
{
"model": "vrw.workspace",
"pk": 1,
"fields": {
"created_at": "2021-04-06T07:33:01.168Z",
"updated_at": "2021-04-06T07:33:01.168Z",
"cloud_id": null,
"starting_at": "2021-04-06T07:33:01.166Z",
"ending_at": "2022-04-06T07:33:01.166Z",
"status": "NEW"
}
}
]
]

20
VRE/apps/vrw/migrations/0003_auto_20210415_1324.py

@ -0,0 +1,20 @@ @@ -0,0 +1,20 @@
# Generated by Django 3.1.7 on 2021-04-15 13:24
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('virtual_machine', '0003_remove_virtualmachine_remote_id'),
('vrw', '0002_auto_20210413_0813'),
]
operations = [
migrations.AlterField(
model_name='workspace',
name='virtual_machine',
field=models.OneToOneField(help_text='The virtual machine configuration.', on_delete=django.db.models.deletion.CASCADE, primary_key=True, related_name='workspace_vrw', serialize=False, to='virtual_machine.virtualmachine'),
),
]

2
VRE/apps/vrw/models.py

@ -102,7 +102,7 @@ class Workspace(MetaDataModel, CloudBasicDataModel): @@ -102,7 +102,7 @@ class Workspace(MetaDataModel, CloudBasicDataModel):
verbose_name = _('virtual workspace')
verbose_name_plural = _('virtual workspaces')
virtual_machine = models.OneToOneField(VirtualMachine, on_delete=models.CASCADE, primary_key=True, help_text=_('The virtual machine configuration.'))
virtual_machine = models.OneToOneField(VirtualMachine, on_delete=models.CASCADE, primary_key=True, help_text=_('The virtual machine configuration.'), related_name="workspace_vrw")
starting_at = models.DateTimeField(_('Starting at'), help_text=_('Enter the start date when this workspace should be created'))
ending_at = models.DateTimeField(_('Ending at'), help_text=_('Enter the end date when this workspace should be terminated'))

6
VRE/apps/vrw/signals.py

@ -21,11 +21,11 @@ def create_virtual_machine_vrw(sender, instance, created, **kwargs): @@ -21,11 +21,11 @@ def create_virtual_machine_vrw(sender, instance, created, **kwargs):
created (bool): Is the Virtual Machine created. If false, it is an update.
"""
# When a new virtual machine is created and does not have a VRW Workspace attached to it, it is a candidate for the VRW
if created and not hasattr(instance,'workspace') and instance.operating_system is not None:
if created and not instance.has_workspace and instance.operating_system is not None:
# Here we check if the Operating system is known as VRW part. If so, we create a VRW machine thqt is active for 1 year.
try:
vrw_part = WorkspacePart.objects.filter(content_type=ContentType.objects.get_for_model(instance.operating_system),
object_id=instance.operating_system.pk).get()
_ = WorkspacePart.objects.filter(content_type=ContentType.objects.get_for_model(instance.operating_system),
object_id=instance.operating_system.pk).get()
new_workspace = Workspace(virtual_machine=instance, starting_at=timezone.now(), ending_at=timezone.now() + settings.VRW_MACHINE_ACTIVE_DURATION)
new_workspace.save()

2
VRE/lib/models/admin.py

@ -2,7 +2,7 @@ from django import forms @@ -2,7 +2,7 @@ from django import forms
from django.contrib.contenttypes.models import ContentType
from django.utils.safestring import mark_safe
class VRWRelatedSelectWidget(forms.Select):
class VMRelatedSelectWidget(forms.Select):
"""This is a custom select widget that makes is possible to change the object id select list based on the chosen content type value.
It is based on the standard select widget. But when we render it, we load some extra data in javascript, that is used for filling the object id select pulldown. This makes is easier to select the right content type and object ids.

29
doc/OpenStack.rst

@ -0,0 +1,29 @@ @@ -0,0 +1,29 @@
=========
OpenStack
=========
This part of the application is for integrating with the OpenStack at HPC for creating Virtual Private Systems (:term:`VPS`). The app will provide a general OpenStack client and some logic for creating VPS-es.
------
Config
------
.. automodule:: apps.openstack.apps
:members:
------
Models
------
.. automodule:: apps.openstack.models
:members:
-------
Signals
-------
.. automodule:: apps.openstack.signals
:members:
--------
Glossary
--------
.. glossary::
VPS
A Virtual Private System. This is a dedicated machine where the researcher can work on remotely.

BIN
doc/documentation.pdf

Binary file not shown.

1
doc/index.rst

@ -23,6 +23,7 @@ The platform can be run inside a docker setup or just local for development. @@ -23,6 +23,7 @@ The platform can be run inside a docker setup or just local for development.
signals
tus
VRW
OpenStack
development
------------------

Loading…
Cancel
Save