VRE Backend API and Scheduler
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

392 lines
18 KiB

from django.conf import settings
from django.db import models
from django.utils.translation import gettext_lazy as _
from django_cryptography.fields import encrypt
from lib.models.base import MetaDataModel
from apps.researcher.models import Researcher
from apps.study.models import Study
from math import pow
from lib.models.cloud import CloudBasicDataModel
from lib.utils.general import file_upload_to
# Create your models here.
class VirtualNetworkType(models.TextChoices):
"""
"""
PRIVATE = ('PRIVATE', _('Private'))
PUBLIC = ('PUBLIC', _('Public'))
class VirtualMachinePart(CloudBasicDataModel, models.Model):
"""
This is a base abstract class for multiple virtual machine models. This model provides default fields
Attributes
----------
name : str
The name of the virtual machine part. This is a free field which will be used for showing to the enduser
is_available : boolean
Is this virtual machine hardware part available for selecting by the enduser. When false, it is not available anymore to the enduser
"""
name = models.CharField(max_length=50, help_text=_('Easy to remember name for this virtual machine part.'))
provider = models.CharField(max_length=50, choices=tuple((provider_id, provider_data['name']) for provider_id, provider_data in settings.CLOUD_PROVIDERS.items()), help_text=_('Cloud provider type.'))
is_available = models.BooleanField(help_text=_('Only selected virtual machine parts can be chosen from.'), default=True)
class Meta:
abstract = True
@property
def cloud_providers(self):
return settings.CLOUD_PROVIDERS
def __str__(self):
provider = _('Unknown')
try:
provider = self.cloud_providers.get(self.provider).get('name')
except:
pass
return f'{self.name} ({provider})'
class VirtualMachineOperatingSystem(MetaDataModel, VirtualMachinePart):
"""
The virtual machine operating system model. This will hold the information of available operating systems that can be used for creating virtual machines
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:`~VirtualMachinePart.name`, :attr:`~VirtualMachinePart.is_available` and :attr:`~VirtualMachinePart.vm_code` from the Abstract model :class:`VirtualMachinePart`
"""
class Meta:
verbose_name = _('virtual machine operating system')
verbose_name_plural = _('virtual machine operating systems')
class VirtualMachineMemory(MetaDataModel, VirtualMachinePart):
"""
The virtual machine memory model. This will hold the information of available memories that can be used for creating virtual machines
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:`~VirtualMachinePart.name`, :attr:`~VirtualMachinePart.is_available` and :attr:`~VirtualMachinePart.vm_code` from the Abstract model :class:`VirtualMachinePart`
"""
class Meta:
verbose_name = _('virtual machine memory')
verbose_name_plural = _('virtual machine memory')
@property
def unit_value(self):
"""This returns the base memory size which we use for calculations
Returns:
float -- base memory size in bytes"""
return pow(1024, 3)
class VirtualMachineNetwork(MetaDataModel, VirtualMachinePart):
"""
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:`~VirtualMachinePart.name`, :attr:`~VirtualMachinePart.is_available` and :attr:`~VirtualMachinePart.vm_code` from the Abstract model :class:`VirtualMachinePart`
"""
class Meta:
verbose_name = _('virtual machine network')
verbose_name_plural = _('virtual machine network')
network_type = models.CharField(_('Network type'), max_length=10, choices=VirtualNetworkType.choices, help_text=_('Network type. Either pirvate or public'))
@property
def unit_value(self):
"""This returns the base storage size which we use for calculations
Returns:
float -- Unit value in Gbps"""
return pow(1024, 3)
class VirtualMachineStorage(MetaDataModel, VirtualMachinePart):
"""
The virtual machine storage model. This will hold the information of available storages that can be used for creating virtual machines
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:`~VirtualMachinePart.name`, :attr:`~VirtualMachinePart.is_available` and :attr:`~VirtualMachinePart.vm_code` from the Abstract model :class:`VirtualMachinePart`
"""
class Meta:
verbose_name = _('virtual machine storage')
verbose_name_plural = _('virtual machine storage')
@property
def unit_value(self):
"""This returns the base storage size which we use for calculations
Returns:
float -- base memory size in bytes"""
return pow(1024, 3)
class VirtualMachineGPU(MetaDataModel, VirtualMachinePart):
"""
The virtual machine operating GPU model. This will hold the information of available GPUs that can be used for creating virtual machines
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:`~VirtualMachinePart.name`, :attr:`~VirtualMachinePart.is_available` and :attr:`~VirtualMachinePart.vm_code` from the Abstract model :class:`VirtualMachinePart`
"""
class Meta:
verbose_name = _('virtual machine GPU')
verbose_name_plural = _('virtual machine GPUs')
class VirtualMachineProfile(MetaDataModel, VirtualMachinePart):
"""
The virtual machine profile. This is a predefined setup which can be used for creating new virtual machines.
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:`~VirtualMachinePart.name`, :attr:`~VirtualMachinePart.is_available` and :attr:`~VirtualMachinePart.vm_code` from the Abstract model :class:`VirtualMachinePart`
Attributes
----------
name : str
The name of the virtual machine profile. This name will be used for showing to the end user.
memory_type : VirtualMachineMemory
The memory type that is used for this profile
memory_amount : int
The amount of memory that is available for this profile using the selected :attr:`memory_type`
storage_type : VirtualMachineStorage
The storage type that is used for this profile
storage_amount : int
The amount of storage that is available for this profile using the selected :attr:`storage_type`
gpu_type : VirtualMachineGPU
The GPU type that is used for this profile
gpu_amount : int
The amount of GPUs that is available for this profile using the selected :attr:`gpu_type`
"""
class Meta:
verbose_name = _('virtual machine profile')
verbose_name_plural = _('virtual machine profiles')
name = models.CharField(max_length=50, help_text=_('Easy to remember name for this virtual machine profile.'))
logo = models.ImageField(_('Logo'), upload_to=file_upload_to, blank=True, null=True)
os = models.ForeignKey(VirtualMachineOperatingSystem, on_delete=models.CASCADE, limit_choices_to={'is_available': True}, help_text=_('Operating system'))
networks = models.ManyToManyField(VirtualMachineNetwork, blank=True, help_text=_('Select the networks that should be connected.'))
memory_type = models.ForeignKey(VirtualMachineMemory, on_delete=models.CASCADE, limit_choices_to={'is_available': True}, help_text=_('Basic memory'))
memory_amount = models.PositiveSmallIntegerField(default=1, help_text=_('Amount of memory. Default is 1'))
storage_type = models.ForeignKey(VirtualMachineStorage, on_delete=models.CASCADE, limit_choices_to={'is_available': True}, help_text=_('Basic disk size'))
storage_amount = models.PositiveSmallIntegerField(default=1, help_text=_('Amount of disk storage. Default is 1'))
gpu_type = models.ForeignKey(VirtualMachineGPU, blank=True, null=True, on_delete=models.CASCADE, limit_choices_to={'is_available': True}, help_text=_('Basic GPU'))
gpu_amount = models.PositiveSmallIntegerField(default=0, help_text=_('Amount of GPUs. Default is 0'))
def __str__(self):
return self.name
@property
def description(self):
"""
The description of this profile in the format '[:attr:`memory_amount`] x [:attr:`memory_type`] memory, [:attr:`storage_amount`] x [:attr:`storage_type`] storage'.
Returns:
str -- small summary of the selected options"""
return f'{self.memory_amount} x {self.memory_type.name} memory, {self.storage_amount} x {self.storage_type.name} storage'
@property
def total_memory(self):
"""
The total amount of memory in bytes for this profile
Returns:
float -- total memory size in bytes
"""
return self.memory_amount * self.memory_type.unit_value
@property
def total_storage(self):
"""
The total amount of storage in bytes for this profile
Returns:
float -- total storage size in bytes
"""
return self.storage_amount * self.storage_type.unit_value
class VirtualMachine(MetaDataModel):
"""
A model which holds a complete virtual machine setup. A virtual machine is linked to a researcher.
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`
Attributes
----------
name : str
The name of the virtual machine. This name will be used for showing to the end user.
researcher : Researcher
The researcher that owns this virtual machine. He/she will can login to this virtual machine.
profile : VirtualMachineProfile
The virtual machine profile that is selected when created.
operating_system : VirtualMachineOperatingSystem
The operating machine that is being used for this virtual machine
base_memory_type : VirtualMachineMemory
The memory type that is used for this virtual machine
base_memory_amount : int
The amount of memory that is available for this virtual machine using the selected :attr:`base_memory_type`
base_storage_type : VirtualMachineStorage
The storage type that is used for this virtual machine
base_storage_amount : int
The amount of storage that is available for this virtual machine using the selected :attr:`storage_type`
additional_gpu_type : VirtualMachineGPU, optional
The GPU type that is used for this virtual machine
additional_gpu_amount : int, optional
The amount of GPUs that is available for this virtual machine using the selected :attr:`additional_gpu_type`
additional_memory_type : VirtualMachineMemory, optional
The additional memory type that is used for this virtual machine
additional_memory_amount : int, optional
The amount of additional memory that is available for this virtual machine using the selected :attr:`additional_memory_type`
additional_storage_type : VirtualMachineStorage, optional
The additional storage type that is used for this virtual machine
additional_storage_amount : int, optional
The amount of additional storage that is available for this virtual machine using the selected :attr:`additional_storage_type`
"""
class Meta:
verbose_name = _('virtual machine')
verbose_name_plural = _('virtual machines')
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.'))
study = models.ForeignKey(Study, on_delete=models.CASCADE, help_text=_('The study for which this virtual machine is used.'))
provider = models.CharField(max_length=50, choices=tuple((provider_id, provider_data['name']) for provider_id, provider_data in settings.CLOUD_PROVIDERS.items()), help_text=_('Cloud provider type.'))
profile = models.ForeignKey(VirtualMachineProfile, on_delete=models.CASCADE, help_text=_('The virtual machine selected profile.'))
networks = models.ManyToManyField(VirtualMachineNetwork, blank=True, help_text=_('Networks connected to this virtual machine.'))
operating_system = models.ForeignKey(VirtualMachineOperatingSystem, on_delete=models.CASCADE, limit_choices_to={'is_available': True}, help_text=_('The operating system for this virtual machine.'))
base_memory_type = models.ForeignKey(VirtualMachineMemory, on_delete=models.CASCADE, limit_choices_to={'is_available': True}, help_text=_('Basic memory'))
base_memory_amount = models.PositiveSmallIntegerField(default=1, help_text=_('Amount of memory. Default is 1'))
base_storage_type = models.ForeignKey(VirtualMachineStorage, on_delete=models.CASCADE, limit_choices_to={'is_available': True}, help_text=_('Basic disk size'))
base_storage_amount = models.PositiveSmallIntegerField(default=1, help_text=_('Amount of disk storage. Default is 1'))
additional_gpu_type = models.ForeignKey(VirtualMachineGPU, blank=True, null=True, on_delete=models.CASCADE, limit_choices_to={'is_available': True}, help_text=_('Additional GPU'))
additional_gpu_amount = models.PositiveSmallIntegerField(default=0, blank=True, help_text=_('Amount of GPUs. Default is 0'))
additional_memory_type = models.ForeignKey(VirtualMachineMemory, blank=True, null=True, related_name='additional_memory', on_delete=models.CASCADE, limit_choices_to={'is_available': True}, help_text=_('Additional memory'))
additional_memory_amount = models.PositiveSmallIntegerField(default=0, blank=True, help_text=_('Amount of memory. Default is 0'))
additional_storage_type = models.ForeignKey(VirtualMachineStorage, blank=True, null=True, related_name='additional_storage', on_delete=models.CASCADE, limit_choices_to={'is_available': True}, help_text=_('Additional storage'))
additional_storage_amount = models.PositiveSmallIntegerField(default=0, blank=True, help_text=_('Amount of storage. Default is 0'))
def __str__(self):
return self.name
@property
def has_workspace(self):
"""This function will look through all the attribute looking for an attribute 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):
"""
The total amount of memory in bytes for this virtual machine. This is the total memory of base memory + additional memory
Returns:
float -- total memory size in bytes
"""
memory = 0
if self.base_memory_type is not None:
memory = self.base_memory_amount * self.base_memory_type.unit_value
if self.additional_memory_type is not None:
memory += self.additional_memory_amount * self.additional_memory_type.unit_value
return memory
@property
def total_storage(self):
"""
The total amount of storage in bytes for this virtual machine. This is the total amount of base storage + additional storage
Returns:
float -- total storage size in bytes
"""
storage = 0
if self.base_storage_type is not None:
storage = self.base_storage_amount * self.base_storage_type.unit_value
if self.additional_storage_type is not None:
storage += self.additional_storage_amount * self.additional_storage_type.unit_value
return storage
class VirtualMachineAccess(MetaDataModel):
class Meta:
verbose_name = _('virtual machine login')
verbose_name_plural = _('virtual machine logins')
constraints = [
models.UniqueConstraint(fields=['researcher', 'virtual_machine'], name='researcher_server_login')
]
researcher = models.ForeignKey(Researcher, on_delete=models.CASCADE, help_text=_('The researcher for which this login is valid for.'))
virtual_machine = models.ForeignKey(VirtualMachine, related_name='access', on_delete=models.CASCADE, help_text=_('The virtual machine to login to.'))
# For 2Factor we use both password and ssh key
login_key = models.TextField(_('Login key'), max_length=2048, help_text=_('The private key to login to the virtual machine.'))
password = encrypt(models.CharField(_('Password'), max_length=50, help_text=_('The SSH password to login.')))
virtual_machine_ip = models.CharField(_('Login IP/hostname'), max_length=1024, help_text=_('The IP address or hostname to login to the virtual machine.'))
@property
def username(self):
"""The username for the login
Returns:
string: the username to login with
"""
return self.researcher.user.username
def __str__(self):
return f'Access login for {self.researcher} to machine {self.virtual_machine}'