From a15952862ad027efe9b823ffea6b0d3fd5bf067b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Floure?= Date: Sat, 18 Apr 2020 13:51:31 +0200 Subject: [PATCH] Make VM order-able again --- .../uncloud/uncloud_vm/models.py | 15 +-- .../uncloud/uncloud_vm/serializers.py | 92 ++++++++----- .../uncloud/uncloud_vm/views.py | 125 ++++++++++-------- 3 files changed, 132 insertions(+), 100 deletions(-) diff --git a/uncloud_django_based/uncloud/uncloud_vm/models.py b/uncloud_django_based/uncloud/uncloud_vm/models.py index f56ed0d..5dacdbe 100644 --- a/uncloud_django_based/uncloud/uncloud_vm/models.py +++ b/uncloud_django_based/uncloud/uncloud_vm/models.py @@ -72,6 +72,7 @@ class VMProduct(Product): primary_disk = models.ForeignKey('VMDiskProduct', on_delete=models.CASCADE, null=True) # Default recurring price is PER_MONTH, see uncloud_pay.models.Product. + @property def recurring_price(self): return self.cores * 3 + self.ram_in_gb * 4 @@ -153,17 +154,9 @@ class VMDiskProduct(Product): def description(self): return "Disk for VM '{}': {}GB".format(self.vm.name, self.size_in_gb) - # TODO: move magic numbers in variables - def recurring_price(self, recurring_period=RecurringPeriod.PER_MONTH): - # TODO: move magic numbers in variables - if recurring_period == RecurringPeriod.PER_MONTH: - return (self.size_in_gb / 10) * 3.5 - if recurring_period == RecurringPeriod.PER_YEAR: - return recurring_price(self, recurring_period.PER_MONTH) * 12 - if recurring_period == RecurringPeriod.PER_HOUR: - return recurring_price(self, recurring_period.PER_MONTH) / 25 - else: - raise Exception('Invalid recurring period for VM Disk Product pricing.') + @property + def recurring_price(self): + return (self.size_in_gb / 10) * 3.5 # Sample code for clean method diff --git a/uncloud_django_based/uncloud/uncloud_vm/serializers.py b/uncloud_django_based/uncloud/uncloud_vm/serializers.py index 9435de2..92c7fe8 100644 --- a/uncloud_django_based/uncloud/uncloud_vm/serializers.py +++ b/uncloud_django_based/uncloud/uncloud_vm/serializers.py @@ -3,7 +3,9 @@ from django.contrib.auth import get_user_model from rest_framework import serializers from .models import VMHost, VMProduct, VMSnapshotProduct, VMDiskProduct, VMDiskImageProduct, VMCluster -from uncloud_pay.models import RecurringPeriod +from uncloud_pay.models import RecurringPeriod, BillingAddress + +# XXX: does not seem to be used? GB_SSD_PER_DAY=0.012 GB_HDD_PER_DAY=0.0006 @@ -11,6 +13,8 @@ GB_HDD_PER_DAY=0.0006 GB_SSD_PER_DAY=0.012 GB_HDD_PER_DAY=0.0006 +### +# Admin views. class VMHostSerializer(serializers.HyperlinkedModelSerializer): vms = serializers.PrimaryKeyRelatedField(many=True, read_only=True) @@ -26,6 +30,9 @@ class VMClusterSerializer(serializers.HyperlinkedModelSerializer): fields = '__all__' +### +# Disks. + class VMDiskProductSerializer(serializers.ModelSerializer): class Meta: model = VMDiskProduct @@ -46,30 +53,6 @@ class VMDiskImageProductSerializer(serializers.ModelSerializer): model = VMDiskImageProduct fields = '__all__' -class DCLVMProductSerializer(serializers.HyperlinkedModelSerializer): - """ - Create an interface similar to standard DCL - """ - - # Custom field used at creation (= ordering) only. - recurring_period = serializers.ChoiceField( - choices=VMProduct.allowed_recurring_periods()) - - os_disk_uuid = serializers.UUIDField() - # os_disk_size = - - class Meta: - model = VMProduct - -class ManagedVMProductSerializer(serializers.ModelSerializer): - """ - Managed VM serializer used in ungleich_service app. - """ - primary_disk = CreateManagedVMDiskProductSerializer() - class Meta: - model = VMProduct - fields = [ 'cores', 'ram_in_gb', 'primary_disk'] - class VMSnapshotProductSerializer(serializers.ModelSerializer): class Meta: model = VMSnapshotProduct @@ -93,22 +76,61 @@ class VMSnapshotProductSerializer(serializers.ModelSerializer): pricing['per_gb_hdd'] = 0.0006 pricing['recurring_period'] = 'per_day' +### +# VMs + +# Helper used in uncloud_service for services allocating VM. +class ManagedVMProductSerializer(serializers.ModelSerializer): + """ + Managed VM serializer used in ungleich_service app. + """ + primary_disk = CreateManagedVMDiskProductSerializer() + class Meta: + model = VMProduct + fields = [ 'cores', 'ram_in_gb', 'primary_disk'] class VMProductSerializer(serializers.HyperlinkedModelSerializer): - # Custom field used at creation (= ordering) only. - recurring_period = serializers.ChoiceField( - choices=VMProduct.allowed_recurring_periods()) primary_disk = CreateVMDiskProductSerializer() + snapshots = VMSnapshotProductSerializer(many=True, read_only=True) + disks = VMDiskProductSerializer(many=True, read_only=True) class Meta: model = VMProduct - fields = ['uuid', 'order', 'owner', 'status', 'name', \ - 'cores', 'ram_in_gb', 'recurring_period', 'primary_disk', - 'snapshots', 'disks', 'extra_data' ] + fields = ['uuid', 'order', 'owner', 'status', 'name', 'cores', + 'ram_in_gb', 'primary_disk', 'snapshots', 'disks', 'extra_data'] read_only_fields = ['uuid', 'order', 'owner', 'status'] - snapshots = VMSnapshotProductSerializer(many=True, - read_only=True) +class OrderVMProductSerializer(VMProductSerializer): + recurring_period = serializers.ChoiceField( + choices=VMProduct.allowed_recurring_periods()) - disks = VMDiskProductSerializer(many=True, - read_only=True) + def __init__(self, *args, **kwargs): + super(VMProductSerializer, self).__init__(*args, **kwargs) + self.fields['billing_address'] = serializers.ChoiceField( + choices=BillingAddress.get_addresses_for( + self.context['request'].user) + ) + + class Meta: + model = VMProductSerializer.Meta.model + fields = VMProductSerializer.Meta.fields + [ + 'recurring_period', 'billing_address' + ] + read_only_fields = VMProductSerializer.Meta.read_only_fields + +# Nico's playground. + +class DCLVMProductSerializer(serializers.HyperlinkedModelSerializer): + """ + Create an interface similar to standard DCL + """ + + # Custom field used at creation (= ordering) only. + recurring_period = serializers.ChoiceField( + choices=VMProduct.allowed_recurring_periods()) + + os_disk_uuid = serializers.UUIDField() + # os_disk_size = + + class Meta: + model = VMProduct diff --git a/uncloud_django_based/uncloud/uncloud_vm/views.py b/uncloud_django_based/uncloud/uncloud_vm/views.py index 71ffe6d..1dead62 100644 --- a/uncloud_django_based/uncloud/uncloud_vm/views.py +++ b/uncloud_django_based/uncloud/uncloud_vm/views.py @@ -15,20 +15,12 @@ from uncloud_pay.models import Order from .serializers import * from uncloud_pay.helpers import ProductViewSet - import datetime -class VMHostViewSet(viewsets.ModelViewSet): - serializer_class = VMHostSerializer - queryset = VMHost.objects.all() - permission_classes = [permissions.IsAdminUser] +### +# Generic disk image views. Do not require orders / billing. -class VMClusterViewSet(viewsets.ModelViewSet): - serializer_class = VMClusterSerializer - queryset = VMCluster.objects.all() - permission_classes = [permissions.IsAdminUser] - -class VMDiskImageProductViewSet(viewsets.ModelViewSet): +class VMDiskImageProductViewSet(ProductViewSet): permission_classes = [permissions.IsAuthenticated] serializer_class = VMDiskImageProductSerializer @@ -53,7 +45,6 @@ class VMDiskImageProductViewSet(viewsets.ModelViewSet): serializer.save(owner=request.user) return Response(serializer.data) - class VMDiskImageProductPublicViewSet(viewsets.ReadOnlyModelViewSet): permission_classes = [permissions.IsAuthenticated] serializer_class = VMDiskImageProductSerializer @@ -61,6 +52,9 @@ class VMDiskImageProductPublicViewSet(viewsets.ReadOnlyModelViewSet): def get_queryset(self): return VMDiskImageProduct.objects.filter(is_public=True) +### +# User VM disk and snapshots. + class VMDiskProductViewSet(viewsets.ModelViewSet): """ Let a user modify their own VMDisks @@ -92,48 +86,6 @@ class VMDiskProductViewSet(viewsets.ModelViewSet): serializer.save(owner=request.user, size_in_gb=size_in_gb) return Response(serializer.data) - - -class VMProductViewSet(ProductViewSet): - permission_classes = [permissions.IsAuthenticated] - serializer_class = VMProductSerializer - - def get_queryset(self): - if self.request.user.is_superuser: - obj = VMProduct.objects.all() - else: - obj = VMProduct.objects.filter(owner=self.request.user) - - return obj - - # Use a database transaction so that we do not get half-created structure - # if something goes wrong. - @transaction.atomic - def create(self, request): - # Extract serializer data. - serializer = VMProductSerializer(data=request.data, context={'request': request}) - serializer.is_valid(raise_exception=True) - order_recurring_period = serializer.validated_data.pop("recurring_period") - - # Create base order. - order = Order( - recurring_period=order_recurring_period, - owner=request.user, - starting_date=timezone.now() - ) - - # Create disk image. - disk = VMDiskProduct(owner=request.user, order=order, - **serializer.validated_data.pop("primary_disk")) - - # Create VM. - vm = serializer.save(owner=request.user, order=order, primary_disk=disk) - disk.vm = vm - disk.save() - - return Response(serializer.data) - - class VMSnapshotProductViewSet(viewsets.ModelViewSet): permission_classes = [permissions.IsAuthenticated] serializer_class = VMSnapshotProductSerializer @@ -176,7 +128,72 @@ class VMSnapshotProductViewSet(viewsets.ModelViewSet): return Response(serializer.data) +### +# User VMs. +class VMProductViewSet(ProductViewSet): + permission_classes = [permissions.IsAuthenticated] + + def get_queryset(self): + if self.request.user.is_superuser: + obj = VMProduct.objects.all() + else: + obj = VMProduct.objects.filter(owner=self.request.user) + + return obj + + def get_serializer_class(self): + if self.action == 'create': + return OrderVMProductSerializer + else: + return VMProductSerializer + + # Use a database transaction so that we do not get half-created structure + # if something goes wrong. + @transaction.atomic + def create(self, request): + # Extract serializer data. + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + order_recurring_period = serializer.validated_data.pop("recurring_period") + order_billing_address = serializer.validated_data.pop("billing_address") + + # Create base order. + order = Order( + recurring_period=order_recurring_period, + billing_address=order_billing_address, + owner=request.user, + starting_date=timezone.now() + ) + order.save() + + # Create disk image. + disk = VMDiskProduct(owner=request.user, order=order, + **serializer.validated_data.pop("primary_disk")) + + # Create VM. + vm = serializer.save(owner=request.user, order=order, primary_disk=disk) + disk.vm = vm + disk.save() + + return Response(VMProductSerializer(vm, context={'request': request}).data) + + +### +# Admin stuff. + +class VMHostViewSet(viewsets.ModelViewSet): + serializer_class = VMHostSerializer + queryset = VMHost.objects.all() + permission_classes = [permissions.IsAdminUser] + +class VMClusterViewSet(viewsets.ModelViewSet): + serializer_class = VMClusterSerializer + queryset = VMCluster.objects.all() + permission_classes = [permissions.IsAdminUser] + +## +# Nico's playground. # Also create: # - /dcl/available_os