uncloud/uncloud/uncloud_vm/models.py

186 lines
6.1 KiB
Python

import uuid
from django.db import models
from django.contrib.auth import get_user_model
# Uncomment if you override model's clean method
# from django.core.exceptions import ValidationError
from uncloud_pay.models import Product, RecurringPeriod
import uncloud_pay.models as pay_models
import uncloud_storage.models
STATUS_CHOICES = (
('pending', 'Pending'), # Initial state
('creating', 'Creating'), # Creating VM/image/etc.
('active', 'Active'), # Is usable / active
('disabled', 'Disabled'), # Is usable, but cannot be used for new things
('unusable', 'Unusable'), # Has some kind of error
('deleted', 'Deleted'), # Does not exist anymore, only DB entry as a log
)
STATUS_DEFAULT = 'pending'
class VMHost(models.Model):
uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
# 253 is the maximum DNS name length
hostname = models.CharField(max_length=253, unique=True)
# indirectly gives a maximum number of cores / VM - f.i. 32
physical_cores = models.IntegerField(default=0)
# determines the maximum usable cores - f.i. 320 if you overbook by a factor of 10
usable_cores = models.IntegerField(default=0)
# ram that can be used of the server
usable_ram_in_gb = models.FloatField(default=0)
status = models.CharField(
max_length=32, choices=STATUS_CHOICES, default=STATUS_DEFAULT
)
@property
def vms(self):
return VMProduct.objects.filter(vmhost=self)
@property
def available_ram_in_gb(self):
return self.usable_ram_in_gb - sum([vm.ram_in_gb for vm in self.vms ])
@property
def available_cores(self):
return self.usable_cores - sum([vm.cores for vm in self.vms ])
class VMProduct(Product):
vmhost = models.ForeignKey(
VMHost, on_delete=models.CASCADE, editable=False, blank=True, null=True
)
# VM-specific. The name is only intended for customers: it's a pain to
# remember IDs (speaking from experience as ungleich customer)!
name = models.CharField(max_length=32, blank=True, null=True)
cores = models.IntegerField()
ram_in_gb = models.FloatField()
def recurring_price(self, recurring_period=RecurringPeriod.PER_MONTH):
# TODO: move magic numbers in variables
if recurring_period == RecurringPeriod.PER_MONTH:
return self.cores * 3 + self.ram_in_gb * 4
elif recurring_period == RecurringPeriod.PER_HOUR:
return self.cores * 4.0/(30 * 24) + self.ram_in_gb * 4.5/(30* 24)
else:
raise Exception('Invalid recurring period for VM Product pricing.')
def __str__(self):
return "VM {} ({}): {} cores {} gb ram".format(self.uuid,
self.name,
self.cores,
self.ram_in_gb)
@property
def description(self):
return "Virtual machine '{}': {} core(s), {}GB memory".format(
self.name, self.cores, self.ram_in_gb)
@staticmethod
def allowed_recurring_periods():
return list(filter(
lambda pair: pair[0] in [RecurringPeriod.PER_MONTH, RecurringPeriod.PER_HOUR],
RecurringPeriod.choices))
class VMWithOSProduct(VMProduct):
pass
class VMDiskImageProduct(models.Model):
"""
Images are used for cloning/linking.
They are the base for images.
"""
uuid = models.UUIDField(
primary_key=True, default=uuid.uuid4, editable=False
)
owner = models.ForeignKey(
get_user_model(), on_delete=models.CASCADE, editable=False
)
name = models.CharField(max_length=256)
is_os_image = models.BooleanField(default=False)
is_public = models.BooleanField(default=False)
size_in_gb = models.FloatField(null=True, blank=True)
import_url = models.URLField(null=True, blank=True)
image_source = models.CharField(max_length=128, null=True)
image_source_type = models.CharField(max_length=128, null=True)
storage_class = models.CharField(max_length=32,
choices = uncloud_storage.models.StorageClass.choices,
default = uncloud_storage.models.StorageClass.SSD)
status = models.CharField(
max_length=32, choices=STATUS_CHOICES, default=STATUS_DEFAULT
)
def __str__(self):
return "VMDiskImage {} ({}): {} gb".format(self.uuid,
self.name,
self.size_in_gb)
class VMDiskProduct(models.Model):
"""
The VMDiskProduct is attached to a VM.
It is based on a VMDiskImageProduct that will be used as a basis.
It can be enlarged, but not shrinked compared to the VMDiskImageProduct.
"""
uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
owner = models.ForeignKey(get_user_model(),
on_delete=models.CASCADE,
editable=False)
vm = models.ForeignKey(VMProduct, on_delete=models.CASCADE)
image = models.ForeignKey(VMDiskImageProduct, on_delete=models.CASCADE)
size_in_gb = models.FloatField(blank=True)
# Sample code for clean method
# Ensures that a VMDiskProduct can only be created from a VMDiskImageProduct
# that is in status 'active'
# def clean(self):
# if self.image.status != 'active':
# raise ValidationError({
# 'image': 'VM Disk must be created from an active disk image.'
# })
def save(self, *args, **kwargs):
self.full_clean()
super().save(*args, **kwargs)
class VMNetworkCard(models.Model):
vm = models.ForeignKey(VMProduct, on_delete=models.CASCADE)
mac_address = models.BigIntegerField()
ip_address = models.GenericIPAddressField(blank=True,
null=True)
class VMSnapshotProduct(Product):
gb_ssd = models.FloatField(editable=False)
gb_hdd = models.FloatField(editable=False)
vm = models.ForeignKey(VMProduct, on_delete=models.CASCADE)