2020-03-02 11:54:36 +00:00
|
|
|
import uuid
|
|
|
|
|
2020-02-23 13:07:37 +00:00
|
|
|
from django.db import models
|
2020-02-26 10:31:17 +00:00
|
|
|
from django.contrib.auth import get_user_model
|
2020-03-02 11:54:36 +00:00
|
|
|
|
2020-02-27 19:29:50 +00:00
|
|
|
from uncloud_pay.models import Product, RecurringPeriod
|
2020-03-22 19:55:11 +00:00
|
|
|
from uncloud.models import UncloudModel, UncloudStatus
|
2020-03-21 10:59:04 +00:00
|
|
|
|
2020-02-27 19:29:50 +00:00
|
|
|
import uncloud_pay.models as pay_models
|
2020-03-05 13:21:10 +00:00
|
|
|
import uncloud_storage.models
|
2020-03-02 06:17:04 +00:00
|
|
|
|
2020-03-22 19:55:11 +00:00
|
|
|
class VMCluster(UncloudModel):
|
|
|
|
uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
|
|
|
name = models.CharField(max_length=128, unique=True)
|
2020-03-02 11:54:36 +00:00
|
|
|
|
2020-02-23 13:07:37 +00:00
|
|
|
|
2020-03-21 10:59:04 +00:00
|
|
|
class VMHost(UncloudModel):
|
2020-02-23 13:07:37 +00:00
|
|
|
uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
2020-02-26 10:31:17 +00:00
|
|
|
|
|
|
|
# 253 is the maximum DNS name length
|
|
|
|
hostname = models.CharField(max_length=253, unique=True)
|
|
|
|
|
2020-03-22 19:55:11 +00:00
|
|
|
vmcluster = models.ForeignKey(
|
|
|
|
VMCluster, on_delete=models.CASCADE, editable=False, blank=True, null=True
|
|
|
|
)
|
|
|
|
|
2020-02-26 10:31:17 +00:00
|
|
|
# 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)
|
|
|
|
|
2020-03-02 12:20:30 +00:00
|
|
|
status = models.CharField(
|
2020-03-22 19:55:11 +00:00
|
|
|
max_length=32, choices=UncloudStatus.choices, default=UncloudStatus.PENDING
|
2020-03-02 12:20:30 +00:00
|
|
|
)
|
2020-02-26 10:31:17 +00:00
|
|
|
|
2020-03-05 13:58:45 +00:00
|
|
|
@property
|
|
|
|
def vms(self):
|
|
|
|
return VMProduct.objects.filter(vmhost=self)
|
2020-03-03 18:46:39 +00:00
|
|
|
|
2020-03-17 18:07:00 +00:00
|
|
|
@property
|
|
|
|
def used_ram_in_gb(self):
|
|
|
|
return sum([vm.ram_in_gb for vm in VMProduct.objects.filter(vmhost=self)])
|
|
|
|
|
2020-03-05 14:06:34 +00:00
|
|
|
@property
|
|
|
|
def available_ram_in_gb(self):
|
2020-03-22 19:55:11 +00:00
|
|
|
return self.usable_ram_in_gb - self.used_ram_in_gb
|
2020-03-05 14:06:34 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def available_cores(self):
|
|
|
|
return self.usable_cores - sum([vm.cores for vm in self.vms ])
|
|
|
|
|
2020-02-26 10:31:17 +00:00
|
|
|
|
2020-03-02 06:17:04 +00:00
|
|
|
class VMProduct(Product):
|
2020-03-02 12:20:30 +00:00
|
|
|
vmhost = models.ForeignKey(
|
|
|
|
VMHost, on_delete=models.CASCADE, editable=False, blank=True, null=True
|
|
|
|
)
|
2020-02-23 13:07:37 +00:00
|
|
|
|
2020-03-22 19:55:11 +00:00
|
|
|
vmcluster = models.ForeignKey(
|
|
|
|
VMCluster, on_delete=models.CASCADE, editable=False, blank=True, null=True
|
|
|
|
)
|
|
|
|
|
2020-03-05 13:21:10 +00:00
|
|
|
# VM-specific. The name is only intended for customers: it's a pain to
|
2020-02-28 14:07:20 +00:00
|
|
|
# remember IDs (speaking from experience as ungleich customer)!
|
2020-03-05 13:21:10 +00:00
|
|
|
name = models.CharField(max_length=32, blank=True, null=True)
|
2020-02-23 13:07:37 +00:00
|
|
|
cores = models.IntegerField()
|
2020-02-26 10:31:17 +00:00
|
|
|
ram_in_gb = models.FloatField()
|
2020-03-17 14:40:08 +00:00
|
|
|
|
2020-02-26 10:31:17 +00:00
|
|
|
|
2020-02-27 19:29:50 +00:00
|
|
|
def recurring_price(self, recurring_period=RecurringPeriod.PER_MONTH):
|
2020-03-03 09:14:56 +00:00
|
|
|
# TODO: move magic numbers in variables
|
2020-02-27 19:29:50 +00:00
|
|
|
if recurring_period == RecurringPeriod.PER_MONTH:
|
2020-03-05 13:00:14 +00:00
|
|
|
return self.cores * 3 + self.ram_in_gb * 4
|
2020-03-03 09:14:56 +00:00
|
|
|
elif recurring_period == RecurringPeriod.PER_HOUR:
|
2020-03-05 13:00:14 +00:00
|
|
|
return self.cores * 4.0/(30 * 24) + self.ram_in_gb * 4.5/(30* 24)
|
2020-03-05 15:22:41 +00:00
|
|
|
elif recurring_period == RecurringPeriod.PER_YEAR:
|
|
|
|
return (self.cores * 2.5 + self.ram_in_gb * 3.5) * 12
|
2020-02-27 16:13:56 +00:00
|
|
|
else:
|
|
|
|
raise Exception('Invalid recurring period for VM Product pricing.')
|
|
|
|
|
2020-03-05 22:55:33 +00:00
|
|
|
def __str__(self):
|
|
|
|
return "VM {} ({}): {} cores {} gb ram".format(self.uuid,
|
|
|
|
self.name,
|
|
|
|
self.cores,
|
|
|
|
self.ram_in_gb)
|
2020-03-02 08:25:03 +00:00
|
|
|
@property
|
|
|
|
def description(self):
|
|
|
|
return "Virtual machine '{}': {} core(s), {}GB memory".format(
|
|
|
|
self.name, self.cores, self.ram_in_gb)
|
2020-02-23 13:07:37 +00:00
|
|
|
|
2020-03-03 09:51:16 +00:00
|
|
|
@staticmethod
|
|
|
|
def allowed_recurring_periods():
|
|
|
|
return list(filter(
|
2020-03-05 15:22:41 +00:00
|
|
|
lambda pair: pair[0] in [RecurringPeriod.PER_YEAR,
|
|
|
|
RecurringPeriod.PER_MONTH, RecurringPeriod.PER_HOUR],
|
2020-03-03 09:51:16 +00:00
|
|
|
RecurringPeriod.choices))
|
2020-02-26 10:31:17 +00:00
|
|
|
|
|
|
|
class VMWithOSProduct(VMProduct):
|
|
|
|
pass
|
|
|
|
|
2020-03-02 06:17:04 +00:00
|
|
|
|
2020-03-21 10:59:04 +00:00
|
|
|
class VMDiskImageProduct(UncloudModel):
|
2020-03-02 06:17:04 +00:00
|
|
|
"""
|
|
|
|
Images are used for cloning/linking.
|
|
|
|
|
|
|
|
They are the base for images.
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
2020-03-02 12:05:23 +00:00
|
|
|
uuid = models.UUIDField(
|
|
|
|
primary_key=True, default=uuid.uuid4, editable=False
|
|
|
|
)
|
|
|
|
owner = models.ForeignKey(
|
|
|
|
get_user_model(), on_delete=models.CASCADE, editable=False
|
|
|
|
)
|
2020-03-02 06:17:04 +00:00
|
|
|
|
|
|
|
name = models.CharField(max_length=256)
|
|
|
|
is_os_image = models.BooleanField(default=False)
|
2020-03-22 16:30:55 +00:00
|
|
|
is_public = models.BooleanField(default=False, editable=False) # only allow admins to set this
|
2020-03-02 06:17:04 +00:00
|
|
|
|
2020-03-02 11:54:36 +00:00
|
|
|
size_in_gb = models.FloatField(null=True, blank=True)
|
|
|
|
import_url = models.URLField(null=True, blank=True)
|
2020-03-03 18:46:39 +00:00
|
|
|
image_source = models.CharField(max_length=128, null=True)
|
|
|
|
image_source_type = models.CharField(max_length=128, null=True)
|
2020-03-05 13:21:10 +00:00
|
|
|
|
|
|
|
storage_class = models.CharField(max_length=32,
|
|
|
|
choices = uncloud_storage.models.StorageClass.choices,
|
|
|
|
default = uncloud_storage.models.StorageClass.SSD)
|
2020-02-26 10:31:17 +00:00
|
|
|
|
2020-03-02 11:54:36 +00:00
|
|
|
status = models.CharField(
|
2020-03-22 19:55:11 +00:00
|
|
|
max_length=32, choices=UncloudStatus.choices, default=UncloudStatus.PENDING
|
2020-03-02 06:17:04 +00:00
|
|
|
)
|
|
|
|
|
2020-03-06 10:10:20 +00:00
|
|
|
def __str__(self):
|
|
|
|
return "VMDiskImage {} ({}): {} gb".format(self.uuid,
|
|
|
|
self.name,
|
|
|
|
self.size_in_gb)
|
|
|
|
|
|
|
|
|
2020-03-02 11:54:36 +00:00
|
|
|
|
2020-03-21 10:59:04 +00:00
|
|
|
class VMDiskProduct(UncloudModel):
|
2020-03-02 06:17:04 +00:00
|
|
|
"""
|
|
|
|
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)
|
|
|
|
|
2020-03-18 14:43:01 +00:00
|
|
|
vm = models.ForeignKey(VMProduct,
|
|
|
|
related_name='disks',
|
|
|
|
on_delete=models.CASCADE)
|
2020-03-02 06:17:04 +00:00
|
|
|
image = models.ForeignKey(VMDiskImageProduct, on_delete=models.CASCADE)
|
|
|
|
|
|
|
|
size_in_gb = models.FloatField(blank=True)
|
2020-02-23 13:07:37 +00:00
|
|
|
|
2020-03-02 11:54:36 +00:00
|
|
|
# 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)
|
|
|
|
|
2020-02-23 13:07:37 +00:00
|
|
|
|
2020-02-26 10:31:17 +00:00
|
|
|
class VMNetworkCard(models.Model):
|
2020-03-02 11:54:36 +00:00
|
|
|
vm = models.ForeignKey(VMProduct, on_delete=models.CASCADE)
|
2020-03-02 06:17:04 +00:00
|
|
|
|
2020-03-03 18:46:39 +00:00
|
|
|
mac_address = models.BigIntegerField()
|
2020-03-02 06:17:04 +00:00
|
|
|
|
|
|
|
ip_address = models.GenericIPAddressField(blank=True,
|
|
|
|
null=True)
|
|
|
|
|
|
|
|
|
|
|
|
class VMSnapshotProduct(Product):
|
|
|
|
gb_ssd = models.FloatField(editable=False)
|
|
|
|
gb_hdd = models.FloatField(editable=False)
|
|
|
|
|
2020-03-18 14:19:06 +00:00
|
|
|
vm = models.ForeignKey(VMProduct,
|
|
|
|
related_name='snapshots',
|
|
|
|
on_delete=models.CASCADE)
|