Make VM order-able again
This commit is contained in:
		
					parent
					
						
							
								cec4263621
							
						
					
				
			
			
				commit
				
					
						a15952862a
					
				
			
		
					 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 |  | ||||||
|         if recurring_period == RecurringPeriod.PER_MONTH: |  | ||||||
|         return (self.size_in_gb / 10) * 3.5 |         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…
	
	Add table
		Add a link
		
	
		Reference in a new issue