From bcbd6f6f8339e7489be0b7e126df6f208dd8465a Mon Sep 17 00:00:00 2001 From: Nico Schottelius Date: Sat, 29 Feb 2020 16:45:52 +0100 Subject: [PATCH] Introduce disk->image relationship Signed-off-by: Nico Schottelius --- uncloud/uncloud/urls.py | 8 ++ .../migrations/0006_auto_20200229_1545.py | 53 ++++++++++++ uncloud/uncloud_vm/models.py | 86 ++++++++++--------- uncloud/uncloud_vm/serializers.py | 34 ++++++-- uncloud/uncloud_vm/views.py | 36 ++++++-- 5 files changed, 161 insertions(+), 56 deletions(-) create mode 100644 uncloud/uncloud_vm/migrations/0006_auto_20200229_1545.py diff --git a/uncloud/uncloud/urls.py b/uncloud/uncloud/urls.py index 5ee9f07..40b3b20 100644 --- a/uncloud/uncloud/urls.py +++ b/uncloud/uncloud/urls.py @@ -26,8 +26,16 @@ router = routers.DefaultRouter() # user / regular urls router.register(r'vm/snapshot', vmviews.VMSnapshotProductViewSet, basename='vmsnapshotproduct') +router.register(r'vm/image/mine', vmviews.VMDiskImageProductMineViewSet, basename='vmdiskimagemineproduct') +router.register(r'vm/image/public', vmviews.VMDiskImageProductPublicViewSet, basename='vmdiskimagepublicproduct') + + +#router.register(r'vm/disk', vmviews.VMDiskProductViewSet, basename='vmdiskproduct') + router.register(r'vm/vm', vmviews.VMProductViewSet, basename='vmproduct') + + # Pay router.register(r'user', payviews.UserViewSet, basename='user') router.register(r'bill', payviews.BillViewSet, basename='bill') diff --git a/uncloud/uncloud_vm/migrations/0006_auto_20200229_1545.py b/uncloud/uncloud_vm/migrations/0006_auto_20200229_1545.py new file mode 100644 index 0000000..208aeaa --- /dev/null +++ b/uncloud/uncloud_vm/migrations/0006_auto_20200229_1545.py @@ -0,0 +1,53 @@ +# Generated by Django 3.0.3 on 2020-02-29 15:45 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('uncloud_vm', '0005_auto_20200227_1230'), + ] + + operations = [ + migrations.CreateModel( + name='VMDiskImageProduct', + fields=[ + ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=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()), + ('storage_class', models.CharField(choices=[('hdd', 'HDD'), ('ssd', 'SSD')], default='ssd', max_length=32)), + ('owner', models.ForeignKey(editable=False, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.RemoveField( + model_name='vmdiskproduct', + name='storage_class', + ), + migrations.AddField( + model_name='vmdiskproduct', + name='owner', + field=models.ForeignKey(default=1, editable=False, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), + preserve_default=False, + ), + migrations.AddField( + model_name='vmnetworkcard', + name='ip_address', + field=models.GenericIPAddressField(blank=True, null=True), + ), + migrations.DeleteModel( + name='OperatingSystemDisk', + ), + migrations.AddField( + model_name='vmdiskproduct', + name='image', + field=models.ForeignKey(default=0, on_delete=django.db.models.deletion.CASCADE, to='uncloud_vm.VMDiskImageProduct'), + preserve_default=False, + ), + ] diff --git a/uncloud/uncloud_vm/models.py b/uncloud/uncloud_vm/models.py index 4ebae25..b585cb9 100644 --- a/uncloud/uncloud_vm/models.py +++ b/uncloud/uncloud_vm/models.py @@ -46,11 +46,27 @@ class VMProduct(Product): class VMWithOSProduct(VMProduct): pass -class VMDiskProduct(models.Model): + +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) - vm = models.ForeignKey(VMProduct, on_delete=models.CASCADE) + 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() + storage_class = models.CharField(max_length=32, choices = ( ('hdd', 'HDD'), @@ -59,9 +75,32 @@ class VMDiskProduct(models.Model): default='ssd' ) -class OperatingSystemDisk(VMDiskProduct): - """ Defines an Operating System Disk that can be cloned for a VM """ - os_name = models.CharField(max_length=128) + # source = models.CharField(max_length=32, + # choices = ( + # ('url', 'HDD'), + # ('ssd', 'SSD'), + # ), + # default='ssd' + # ) + +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() class VMNetworkCard(models.Model): @@ -74,44 +113,7 @@ class VMNetworkCard(models.Model): class VMSnapshotProduct(Product): - price_per_gb_ssd = 0.35 - price_per_gb_hdd = 1.5/100 - - # This we need to get from the VM gb_ssd = models.FloatField(editable=False) gb_hdd = models.FloatField(editable=False) vm = models.ForeignKey(VMProduct, on_delete=models.CASCADE) - #vm_uuid = models.UUIDField() - - # Need to setup recurring_price and one_time_price and recurring period - - sample_ssd = 10 - sample_hdd = 100 - - def recurring_price(self): - return 0 - - def one_time_price(self): - return 0 - - @classmethod - def sample_price(cls): - return cls.sample_ssd * cls.price_per_gb_ssd + cls.sample_hdd * cls.price_per_gb_hdd - - description = "Create snapshot of a VM" - recurring_period = "monthly" - - @classmethod - def pricing_model(cls): - return """ -Pricing is on monthly basis and storage prices are equivalent to the storage -price in the VM. - -Price per GB SSD is: {} -Price per GB HDD is: {} - - -Sample price for a VM with {} GB SSD and {} GB HDD VM is: {}. -""".format(cls.price_per_gb_ssd, cls.price_per_gb_hdd, - cls.sample_ssd, cls.sample_hdd, cls.sample_price()) diff --git a/uncloud/uncloud_vm/serializers.py b/uncloud/uncloud_vm/serializers.py index b247709..a64fdd0 100644 --- a/uncloud/uncloud_vm/serializers.py +++ b/uncloud/uncloud_vm/serializers.py @@ -1,22 +1,28 @@ from django.contrib.auth import get_user_model from rest_framework import serializers -from .models import VMHost, VMProduct, VMSnapshotProduct +from .models import VMHost, VMProduct, VMSnapshotProduct, VMDiskProduct, VMDiskImageProduct -class VMHostSerializer(serializers.HyperlinkedModelSerializer): +class VMHostSerializer(serializers.ModelSerializer): class Meta: model = VMHost fields = '__all__' -class VMProductSerializer(serializers.HyperlinkedModelSerializer): +class VMProductSerializer(serializers.ModelSerializer): class Meta: model = VMProduct fields = '__all__' +class VMDiskProductSerializer(serializers.ModelSerializer): + class Meta: + model = VMDiskProduct + fields = '__all__' -# def create(self, validated_data): -# return VMSnapshotProduct() +class VMDiskImageProductSerializer(serializers.ModelSerializer): + class Meta: + model = VMDiskImageProduct + fields = '__all__' class VMSnapshotProductSerializer(serializers.ModelSerializer): class Meta: @@ -26,5 +32,19 @@ class VMSnapshotProductSerializer(serializers.ModelSerializer): # verify that vm.owner == user.request def validate_vm(self, value): - print(value) - return True + + if not value.owner == self.context['request'].user: + raise serializers.ValidationError("VM {} not found for owner {}.".format(value, + self.context['request'].user)) + + disks = VMDiskProduct.objects.filter(vm=value) + + if len(disks) == 0: + raise serializers.ValidationError("VM {} does not have any disks, cannot snapshot".format(value.uuid)) + + return value + + pricing = {} + pricing['per_gb_ssd'] = 0.012 + pricing['per_gb_hdd'] = 0.0006 + pricing['recurring_period'] = 'per_day' diff --git a/uncloud/uncloud_vm/views.py b/uncloud/uncloud_vm/views.py index 7e517f5..b9d80f9 100644 --- a/uncloud/uncloud_vm/views.py +++ b/uncloud/uncloud_vm/views.py @@ -6,10 +6,10 @@ from django.shortcuts import get_object_or_404 from rest_framework import viewsets, permissions from rest_framework.response import Response -from .models import VMHost, VMProduct, VMSnapshotProduct +from .models import VMHost, VMProduct, VMSnapshotProduct, VMDiskProduct, VMDiskImageProduct from uncloud_pay.models import Order -from .serializers import VMHostSerializer, VMProductSerializer, VMSnapshotProductSerializer +from .serializers import VMHostSerializer, VMProductSerializer, VMSnapshotProductSerializer, VMDiskImageProductSerializer import datetime @@ -19,6 +19,20 @@ class VMHostViewSet(viewsets.ModelViewSet): queryset = VMHost.objects.all() permission_classes = [permissions.IsAdminUser] +class VMDiskImageProductMineViewSet(viewsets.ModelViewSet): + permission_classes = [permissions.IsAuthenticated] + serializer_class = VMDiskImageProductSerializer + + def get_queryset(self): + return VMDiskImageProduct.objects.filter(owner=self.request.user) + +class VMDiskImageProductPublicViewSet(viewsets.ModelViewSet): + permission_classes = [permissions.IsAuthenticated] + serializer_class = VMDiskImageProductSerializer + + def get_queryset(self): + return VMDiskImageProduct.objects.filter(is_public=True) + class VMProductViewSet(viewsets.ModelViewSet): permission_classes = [permissions.IsAuthenticated] @@ -54,22 +68,30 @@ class VMSnapshotProductViewSet(viewsets.ModelViewSet): def create(self, request): serializer = VMSnapshotProductSerializer(data=request.data, context={'request': request}) + + # This verifies that the VM belongs to the request user serializer.is_valid(raise_exception=True) + disks = VMDiskProduct.objects.filter(vm=serializer.validated_data['vm']) + ssds_size = sum([d.size_in_gb for d in disks if d.storage_class == 'ssd']) + hdds_size = sum([d.size_in_gb for d in disks if d.storage_class == 'hdd']) + + recurring_price = serializer.pricing['per_gb_ssd'] * ssds_size + serializer.pricing['per_gb_hdd'] * hdds_size + recurring_period = serializer.pricing['recurring_period'] + # Create order now = datetime.datetime.now() order = Order(owner=request.user, creation_date=now, starting_date=now, - recurring_price=20, + recurring_price=recurring_price, one_time_price=0, - recurring_period="per_month") + recurring_period=recurring_period) order.save() - # FIXME: calculate the gb_* values serializer.save(owner=request.user, order=order, - gb_ssd=12, - gb_hdd=20) + gb_ssd=ssds_size, + gb_hdd=hdds_size) return Response(serializer.data)