forked from uncloud/uncloud
Make VM order-able again
This commit is contained in:
parent
cec4263621
commit
9410b7c56b
3 changed files with 132 additions and 100 deletions
|
@ -72,6 +72,7 @@ class VMProduct(Product):
|
||||||
primary_disk = models.ForeignKey('VMDiskProduct', on_delete=models.CASCADE, null=True)
|
primary_disk = models.ForeignKey('VMDiskProduct', on_delete=models.CASCADE, null=True)
|
||||||
|
|
||||||
# Default recurring price is PER_MONTH, see uncloud_pay.models.Product.
|
# Default recurring price is PER_MONTH, see uncloud_pay.models.Product.
|
||||||
|
@property
|
||||||
def recurring_price(self):
|
def recurring_price(self):
|
||||||
return self.cores * 3 + self.ram_in_gb * 4
|
return self.cores * 3 + self.ram_in_gb * 4
|
||||||
|
|
||||||
|
@ -153,17 +154,9 @@ class VMDiskProduct(Product):
|
||||||
def description(self):
|
def description(self):
|
||||||
return "Disk for VM '{}': {}GB".format(self.vm.name, self.size_in_gb)
|
return "Disk for VM '{}': {}GB".format(self.vm.name, self.size_in_gb)
|
||||||
|
|
||||||
# TODO: move magic numbers in variables
|
@property
|
||||||
def recurring_price(self, recurring_period=RecurringPeriod.PER_MONTH):
|
def recurring_price(self):
|
||||||
# TODO: move magic numbers in variables
|
return (self.size_in_gb / 10) * 3.5
|
||||||
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.')
|
|
||||||
|
|
||||||
# Sample code for clean method
|
# Sample code for clean method
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,9 @@ from django.contrib.auth import get_user_model
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from .models import VMHost, VMProduct, VMSnapshotProduct, VMDiskProduct, VMDiskImageProduct, VMCluster
|
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_SSD_PER_DAY=0.012
|
||||||
GB_HDD_PER_DAY=0.0006
|
GB_HDD_PER_DAY=0.0006
|
||||||
|
@ -11,6 +13,8 @@ GB_HDD_PER_DAY=0.0006
|
||||||
GB_SSD_PER_DAY=0.012
|
GB_SSD_PER_DAY=0.012
|
||||||
GB_HDD_PER_DAY=0.0006
|
GB_HDD_PER_DAY=0.0006
|
||||||
|
|
||||||
|
###
|
||||||
|
# Admin views.
|
||||||
|
|
||||||
class VMHostSerializer(serializers.HyperlinkedModelSerializer):
|
class VMHostSerializer(serializers.HyperlinkedModelSerializer):
|
||||||
vms = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
|
vms = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
|
||||||
|
@ -26,6 +30,9 @@ class VMClusterSerializer(serializers.HyperlinkedModelSerializer):
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
|
###
|
||||||
|
# Disks.
|
||||||
|
|
||||||
class VMDiskProductSerializer(serializers.ModelSerializer):
|
class VMDiskProductSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = VMDiskProduct
|
model = VMDiskProduct
|
||||||
|
@ -46,30 +53,6 @@ class VMDiskImageProductSerializer(serializers.ModelSerializer):
|
||||||
model = VMDiskImageProduct
|
model = VMDiskImageProduct
|
||||||
fields = '__all__'
|
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 VMSnapshotProductSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = VMSnapshotProduct
|
model = VMSnapshotProduct
|
||||||
|
@ -93,22 +76,61 @@ class VMSnapshotProductSerializer(serializers.ModelSerializer):
|
||||||
pricing['per_gb_hdd'] = 0.0006
|
pricing['per_gb_hdd'] = 0.0006
|
||||||
pricing['recurring_period'] = 'per_day'
|
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):
|
class VMProductSerializer(serializers.HyperlinkedModelSerializer):
|
||||||
# Custom field used at creation (= ordering) only.
|
|
||||||
recurring_period = serializers.ChoiceField(
|
|
||||||
choices=VMProduct.allowed_recurring_periods())
|
|
||||||
primary_disk = CreateVMDiskProductSerializer()
|
primary_disk = CreateVMDiskProductSerializer()
|
||||||
|
snapshots = VMSnapshotProductSerializer(many=True, read_only=True)
|
||||||
|
disks = VMDiskProductSerializer(many=True, read_only=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = VMProduct
|
model = VMProduct
|
||||||
fields = ['uuid', 'order', 'owner', 'status', 'name', \
|
fields = ['uuid', 'order', 'owner', 'status', 'name', 'cores',
|
||||||
'cores', 'ram_in_gb', 'recurring_period', 'primary_disk',
|
'ram_in_gb', 'primary_disk', 'snapshots', 'disks', 'extra_data']
|
||||||
'snapshots', 'disks', 'extra_data' ]
|
|
||||||
read_only_fields = ['uuid', 'order', 'owner', 'status']
|
read_only_fields = ['uuid', 'order', 'owner', 'status']
|
||||||
|
|
||||||
snapshots = VMSnapshotProductSerializer(many=True,
|
class OrderVMProductSerializer(VMProductSerializer):
|
||||||
read_only=True)
|
recurring_period = serializers.ChoiceField(
|
||||||
|
choices=VMProduct.allowed_recurring_periods())
|
||||||
|
|
||||||
disks = VMDiskProductSerializer(many=True,
|
def __init__(self, *args, **kwargs):
|
||||||
read_only=True)
|
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
|
||||||
|
|
|
@ -15,20 +15,12 @@ from uncloud_pay.models import Order
|
||||||
from .serializers import *
|
from .serializers import *
|
||||||
from uncloud_pay.helpers import ProductViewSet
|
from uncloud_pay.helpers import ProductViewSet
|
||||||
|
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
class VMHostViewSet(viewsets.ModelViewSet):
|
###
|
||||||
serializer_class = VMHostSerializer
|
# Generic disk image views. Do not require orders / billing.
|
||||||
queryset = VMHost.objects.all()
|
|
||||||
permission_classes = [permissions.IsAdminUser]
|
|
||||||
|
|
||||||
class VMClusterViewSet(viewsets.ModelViewSet):
|
class VMDiskImageProductViewSet(ProductViewSet):
|
||||||
serializer_class = VMClusterSerializer
|
|
||||||
queryset = VMCluster.objects.all()
|
|
||||||
permission_classes = [permissions.IsAdminUser]
|
|
||||||
|
|
||||||
class VMDiskImageProductViewSet(viewsets.ModelViewSet):
|
|
||||||
permission_classes = [permissions.IsAuthenticated]
|
permission_classes = [permissions.IsAuthenticated]
|
||||||
serializer_class = VMDiskImageProductSerializer
|
serializer_class = VMDiskImageProductSerializer
|
||||||
|
|
||||||
|
@ -53,7 +45,6 @@ class VMDiskImageProductViewSet(viewsets.ModelViewSet):
|
||||||
serializer.save(owner=request.user)
|
serializer.save(owner=request.user)
|
||||||
return Response(serializer.data)
|
return Response(serializer.data)
|
||||||
|
|
||||||
|
|
||||||
class VMDiskImageProductPublicViewSet(viewsets.ReadOnlyModelViewSet):
|
class VMDiskImageProductPublicViewSet(viewsets.ReadOnlyModelViewSet):
|
||||||
permission_classes = [permissions.IsAuthenticated]
|
permission_classes = [permissions.IsAuthenticated]
|
||||||
serializer_class = VMDiskImageProductSerializer
|
serializer_class = VMDiskImageProductSerializer
|
||||||
|
@ -61,6 +52,9 @@ class VMDiskImageProductPublicViewSet(viewsets.ReadOnlyModelViewSet):
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return VMDiskImageProduct.objects.filter(is_public=True)
|
return VMDiskImageProduct.objects.filter(is_public=True)
|
||||||
|
|
||||||
|
###
|
||||||
|
# User VM disk and snapshots.
|
||||||
|
|
||||||
class VMDiskProductViewSet(viewsets.ModelViewSet):
|
class VMDiskProductViewSet(viewsets.ModelViewSet):
|
||||||
"""
|
"""
|
||||||
Let a user modify their own VMDisks
|
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)
|
serializer.save(owner=request.user, size_in_gb=size_in_gb)
|
||||||
return Response(serializer.data)
|
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):
|
class VMSnapshotProductViewSet(viewsets.ModelViewSet):
|
||||||
permission_classes = [permissions.IsAuthenticated]
|
permission_classes = [permissions.IsAuthenticated]
|
||||||
serializer_class = VMSnapshotProductSerializer
|
serializer_class = VMSnapshotProductSerializer
|
||||||
|
@ -176,7 +128,72 @@ class VMSnapshotProductViewSet(viewsets.ModelViewSet):
|
||||||
|
|
||||||
return Response(serializer.data)
|
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:
|
# Also create:
|
||||||
# - /dcl/available_os
|
# - /dcl/available_os
|
||||||
|
|
Loading…
Reference in a new issue