From 1ca247148c79620213b31260dabfbef90fb338d3 Mon Sep 17 00:00:00 2001 From: Nico Schottelius Date: Thu, 27 Feb 2020 11:21:38 +0100 Subject: [PATCH 01/23] [uncloud_pay] add "prototype" --- uncloud/uncloud_pay/__init__.py | 0 uncloud/uncloud_pay/admin.py | 3 + uncloud/uncloud_pay/apps.py | 5 ++ uncloud/uncloud_pay/migrations/__init__.py | 0 uncloud/uncloud_pay/models.py | 91 ++++++++++++++++++++++ uncloud/uncloud_pay/tests.py | 3 + uncloud/uncloud_pay/views.py | 55 +++++++++++++ 7 files changed, 157 insertions(+) create mode 100644 uncloud/uncloud_pay/__init__.py create mode 100644 uncloud/uncloud_pay/admin.py create mode 100644 uncloud/uncloud_pay/apps.py create mode 100644 uncloud/uncloud_pay/migrations/__init__.py create mode 100644 uncloud/uncloud_pay/models.py create mode 100644 uncloud/uncloud_pay/tests.py create mode 100644 uncloud/uncloud_pay/views.py diff --git a/uncloud/uncloud_pay/__init__.py b/uncloud/uncloud_pay/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/uncloud/uncloud_pay/admin.py b/uncloud/uncloud_pay/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/uncloud/uncloud_pay/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/uncloud/uncloud_pay/apps.py b/uncloud/uncloud_pay/apps.py new file mode 100644 index 0000000..051ffb4 --- /dev/null +++ b/uncloud/uncloud_pay/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class UncloudPayConfig(AppConfig): + name = 'uncloud_pay' diff --git a/uncloud/uncloud_pay/migrations/__init__.py b/uncloud/uncloud_pay/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/uncloud/uncloud_pay/models.py b/uncloud/uncloud_pay/models.py new file mode 100644 index 0000000..6910d58 --- /dev/null +++ b/uncloud/uncloud_pay/models.py @@ -0,0 +1,91 @@ +from django.db import models +from django.contrib.auth import get_user_model + +# Create your models here. + + +class Bill(models.Model): + owner = models.ForeignKey(get_user_model(), + on_delete=models.CASCADE, + editable=False) + + creation_date = models.DateTimeField() + starting_date = models.DateTimeField() + ending_date = models.DateTimeField() + due_date = models.DateField() + + paid = models.BooleanField(default=False) + valid = models.BooleanField(default=True) + + @property + def amount(self): + # iterate over all related orders + pass + + +class Order(models.Model): + uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) + owner = models.ForeignKey(get_user_model(), + on_delete=models.CASCADE, + editable=False) + + creation_date = models.DateTimeField() + starting_date = models.DateTimeField() + ending_date = models.DateTimeField(blank=True, + null=True) + + bill = models.ManyToManyField(Bill, + on_delete=models.CASCADE, + editable=False, + blank=True, + null=True) + + + recurring_price = models.FloatField(editable=False) + one_time_price = models.FloatField(editable=False) + + recurring_period = models.CharField(max_length=32, + choices = ( + ('onetime', 'Onetime'), + ('per_year', 'Per Year'), + ('per_month', 'Per Month'), + ('per_week', 'Per Week'), + ('per_day', 'Per Day'), + ('per_hour', 'Per Hour'), + ('per_minute', 'Per Minute'), + ('per_second', 'Per Second'), + ), + default='onetime' + + ) + + # def amount(self): + # amount = recurring_price + # if recurring and first_month: + # amount += one_time_price + + # return amount # you get the picture + + + +class Payment(models.Model): + uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) + + owner = models.ForeignKey(get_user_model(), + on_delete=models.CASCADE, + editable=False) + + amount = models.DecimalField( + default=0.0, + validators=[MinValueValidator(0)]) + + source = models.CharField(max_length=256, + choices = ( + ('wire', 'Wire Transfer'), + ('strip', 'Stripe'), + ('voucher', 'Voucher'), + ('referral', 'Referral'), + ('unknown', 'Unknown') + ), + default='unknown') + timestamp = models.DateTimeField(editable=False) diff --git a/uncloud/uncloud_pay/tests.py b/uncloud/uncloud_pay/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/uncloud/uncloud_pay/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/uncloud/uncloud_pay/views.py b/uncloud/uncloud_pay/views.py new file mode 100644 index 0000000..b52a2b6 --- /dev/null +++ b/uncloud/uncloud_pay/views.py @@ -0,0 +1,55 @@ +from django.shortcuts import render + +# Create your views here. + + +# to be implemented +class BalanceViewSet(viewsets.ModelViewSet): + # here we return a number + # number = sum(payments) - sum(bills) + + bills = Bills.objects.filter(owner=self.request.user) + payments = Payment.objects.filter(owner=self.request.user) + + # sum_paid = sum([ amount for amount payments..,. ]) # you get the picture + # sum_to_be_paid = sum([ amount for amount bills..,. ]) # you get the picture + + +class Bills(viewset.ModelViewSet): + def unpaid(self, request): + return Bills.objects.filter(owner=self.request.user, paid=False) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + serializer_class = BillSerializer + permission_classes = [permissions.IsAuthenticated] + http_method_names = ['get'] + + def get_queryset(self): + return self.request.user.get_bills() From a58a3612544565178a77a81beec4e6dbfec591d3 Mon Sep 17 00:00:00 2001 From: Nico Schottelius Date: Thu, 27 Feb 2020 11:36:50 +0100 Subject: [PATCH 02/23] Move snapshot to _pay and _vm --- uncloud/README.md | 6 ++ uncloud/uncloud_api/models.py | 139 ----------------------------- uncloud/uncloud_api/serializers.py | 12 --- uncloud/uncloud_api/views.py | 42 +-------- uncloud/uncloud_pay/models.py | 28 ++++++ uncloud/uncloud_vm/models.py | 45 ++++++++++ uncloud/uncloud_vm/serializers.py | 10 +++ uncloud/uncloud_vm/views.py | 50 +++++++++++ 8 files changed, 140 insertions(+), 192 deletions(-) delete mode 100644 uncloud/uncloud_api/models.py diff --git a/uncloud/README.md b/uncloud/README.md index 67f960f..19896d9 100644 --- a/uncloud/README.md +++ b/uncloud/README.md @@ -51,6 +51,12 @@ Installing the postgresql service is os dependent, but some hints: * Alpine: `apk add postgresql-server && rc-update add postgresql && rc-service postgresql start` * Debian/Devuan: `apt install postgresql` +After postresql is started, apply the migrations: + +``` +python manage.py migrate +``` + ### Secrets cp `uncloud/secrets_sample.py` to `uncloud/secrets.py` and replace the diff --git a/uncloud/uncloud_api/models.py b/uncloud/uncloud_api/models.py deleted file mode 100644 index 6a6f9c8..0000000 --- a/uncloud/uncloud_api/models.py +++ /dev/null @@ -1,139 +0,0 @@ -import uuid - -from django.db import models -from django.contrib.auth import get_user_model - -# Product in DB vs. product in code -# DB: -# - need to define params (+param types) in db -> messy? -# - get /products/ is easy / automatic -# -# code -# - can have serializer/verification of fields easily in DRF -# - can have per product side effects / extra code running -# - might (??) make features easier?? -# - how to setup / query the recurring period (?) -# - could get products list via getattr() + re ...Product() classes -# -> this could include the url for ordering => /order/vm_snapshot (params) -# ---> this would work with urlpatterns - -# Combination: create specific product in DB (?) -# - a table per product (?) with 1 entry? - -# Orders -# define state in DB -# select a price from a product => product might change, order stays -# params: -# - the product uuid or name (?) => productuuid -# - the product parameters => for each feature -# - -# logs -# Should have a log = ... => 1:n field for most models! - -class Product(models.Model): - uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) - owner = models.ForeignKey(get_user_model(), - on_delete=models.CASCADE, - editable=False) - - # override these fields by default - - description = "" - recurring_period = "not_recurring" - - status = models.CharField(max_length=256, - choices = ( - ('pending', 'Pending'), - ('being_created', 'Being created'), - ('active', 'Active'), - ('deleted', 'Deleted') - ), - default='pending' - ) - - # This is calculated by each product and saved in the DB - recurring_price = models.FloatField(editable=False) - one_time_price = models.FloatField(editable=False) - - # FIXME: need recurring_time_frame - - class Meta: - abstract = True - - def __str__(self): - return "{}".format(self.name) - - -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_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()) - - - - - - - - -class Feature(models.Model): - uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) - name = models.CharField(max_length=256) - - recurring_price = models.FloatField(default=0) - one_time_price = models.FloatField() - - product = models.ForeignKey(Product, on_delete=models.CASCADE) - - # params for "cpu": cpu_count -> int - # each feature can only have one parameters - # could call this "value" and set whether it is user usable - # has_value = True/False - # value = string -> int (?) - # value_int - # value_str - # value_float - - class Meta: - abstract = True - - def __str__(self): - return "'{}' - '{}'".format(self.product, self.name) diff --git a/uncloud/uncloud_api/serializers.py b/uncloud/uncloud_api/serializers.py index 7dc3686..cd7fd14 100644 --- a/uncloud/uncloud_api/serializers.py +++ b/uncloud/uncloud_api/serializers.py @@ -3,8 +3,6 @@ from django.contrib.auth import get_user_model from rest_framework import serializers -from .models import VMSnapshotProduct - class UserSerializer(serializers.ModelSerializer): class Meta: model = get_user_model() @@ -14,13 +12,3 @@ class GroupSerializer(serializers.HyperlinkedModelSerializer): class Meta: model = Group fields = ['url', 'name'] - -class VMSnapshotSerializer(serializers.HyperlinkedModelSerializer): - class Meta: - model = VMSnapshotProduct - fields = ['uuid', 'status', 'recurring_price', 'one_time_price' ] - -class VMSnapshotCreateSerializer(serializers.HyperlinkedModelSerializer): - class Meta: - model = VMSnapshotProduct - fields = '__all__' diff --git a/uncloud/uncloud_api/views.py b/uncloud/uncloud_api/views.py index eb4cc77..7e5c6f9 100644 --- a/uncloud/uncloud_api/views.py +++ b/uncloud/uncloud_api/views.py @@ -17,46 +17,6 @@ import sys import re -# POST /vm/snapshot/ vmuuid=... => create snapshot, returns snapshot uuid -# GET /vm/snapshot => list -# DEL /vm/snapshot/ => delete -# create-list -> get, post => ListCreateAPIView -# del on other! -class VMSnapshotView(viewsets.ViewSet): - permission_classes = [permissions.IsAuthenticated] - - def list(self, request): - queryset = VMSnapshotProduct.objects.filter(owner=request.user) - serializer = VMSnapshotSerializer(queryset, many=True, context={'request': request}) - return Response(serializer.data) - - def retrieve(self, request, pk=None): - queryset = VMSnapshotProduct.objects.filter(owner=request.user) - vm = get_object_or_404(queryset, pk=pk) - serializer = VMSnapshotSerializer(vm, context={'request': request}) - return Response(serializer.data) - - def create(self, request): - print(request.data) - serializer = VMSnapshotCreateSerializer(data=request.data) - - serializer.gb_ssd = 12 - serializer.gb_hdd = 120 - print("F") - serializer.is_valid(raise_exception=True) - - print(serializer) - print("A") - serializer.save() - print("B") - - - # snapshot = VMSnapshotProduct(owner=request.user, - # **serialzer.data) - - return Response(serializer.data) - - # maybe drop or not --- we need something to guide the user! # class ProductsViewSet(viewsets.ViewSet): @@ -91,4 +51,4 @@ class UserViewSet(viewsets.ModelViewSet): permission_classes = [permissions.IsAuthenticated] def get_queryset(self): - return self.request.user + return self.request.user \ No newline at end of file diff --git a/uncloud/uncloud_pay/models.py b/uncloud/uncloud_pay/models.py index 6910d58..831710b 100644 --- a/uncloud/uncloud_pay/models.py +++ b/uncloud/uncloud_pay/models.py @@ -89,3 +89,31 @@ class Payment(models.Model): ), default='unknown') timestamp = models.DateTimeField(editable=False) + + + + +class Product(models.Model): + uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) + owner = models.ForeignKey(get_user_model(), + on_delete=models.CASCADE, + editable=False) + + description = "" + + status = models.CharField(max_length=256, + choices = ( + ('pending', 'Pending'), + ('being_created', 'Being created'), + ('active', 'Active'), + ('deleted', 'Deleted') + ), + default='pending' + ) + + order = models.ForeignKey(Order, + on_delete=models.CASCADE, + editable=False) + + class Meta: + abstract = True diff --git a/uncloud/uncloud_vm/models.py b/uncloud/uncloud_vm/models.py index f4b68dd..12d188e 100644 --- a/uncloud/uncloud_vm/models.py +++ b/uncloud/uncloud_vm/models.py @@ -2,6 +2,8 @@ from django.db import models from django.contrib.auth import get_user_model import uuid +from uncloud_pay.models import Product + class VMHost(models.Model): uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) @@ -71,3 +73,46 @@ class OperatingSystemDisk(VMDiskProduct): class VMNetworkCard(models.Model): vm = models.ForeignKey(VMProduct, on_delete=models.CASCADE) mac_address = models.IntegerField() + + +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_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 4154aee..d5549ad 100644 --- a/uncloud/uncloud_vm/serializers.py +++ b/uncloud/uncloud_vm/serializers.py @@ -13,3 +13,13 @@ class VMProductSerializer(serializers.HyperlinkedModelSerializer): class Meta: model = VMProduct fields = '__all__' + +class VMSnapshotProductSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = VMSnapshotProduct + fields = ['uuid', 'status', 'recurring_price', 'one_time_price' ] + +class VMSnapshotProductCreateSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = VMSnapshotProduct + fields = '__all__' diff --git a/uncloud/uncloud_vm/views.py b/uncloud/uncloud_vm/views.py index 91e81e1..4f2f9f4 100644 --- a/uncloud/uncloud_vm/views.py +++ b/uncloud/uncloud_vm/views.py @@ -27,3 +27,53 @@ class VMProductViewSet(viewsets.ModelViewSet): serializer.save(owner=request.user) return Response(serializer.data) + + +class VMSnapshotProductViewSet(viewsets.ModelViewSet): + permission_classes = [permissions.IsAuthenticated] + serializer_class = VMSnapshotProductSerializer + + def get_queryset(self): + return VMSnapshotProduct.objects.filter(owner=self.request.user) + + def create(self, request): + serializer = VMProductSerializer(data=request.data, context={'request': request}) + serializer.is_valid(raise_exception=True) + serializer.save(owner=request.user) + + return Response(serializer.data) + + +class VMSnapshotProductView(viewsets.ViewSet): + permission_classes = [permissions.IsAuthenticated] + + def list(self, request): + queryset = VMSnapshotProduct.objects.filter(owner=request.user) + serializer = VMSnapshotSerializer(queryset, many=True, context={'request': request}) + return Response(serializer.data) + + def retrieve(self, request, pk=None): + queryset = VMSnapshotProduct.objects.filter(owner=request.user) + vm = get_object_or_404(queryset, pk=pk) + serializer = VMSnapshotSerializer(vm, context={'request': request}) + return Response(serializer.data) + + def create(self, request): + print(request.data) + serializer = VMSnapshotCreateSerializer(data=request.data) + + serializer.gb_ssd = 12 + serializer.gb_hdd = 120 + print("F") + serializer.is_valid(raise_exception=True) + + print(serializer) + print("A") + serializer.save() + print("B") + + + # snapshot = VMSnapshotProduct(owner=request.user, + # **serialzer.data) + + return Response(serializer.data) From aa59b05a2dedfd65651d62bc00994d03cf34694b Mon Sep 17 00:00:00 2001 From: Nico Schottelius Date: Thu, 27 Feb 2020 11:40:36 +0100 Subject: [PATCH 03/23] cleanup urls --- uncloud/uncloud/urls.py | 7 +------ uncloud/uncloud_api/serializers.py | 6 ------ uncloud/uncloud_api/views.py | 7 ++----- 3 files changed, 3 insertions(+), 17 deletions(-) diff --git a/uncloud/uncloud/urls.py b/uncloud/uncloud/urls.py index 23392c5..1e4c9d0 100644 --- a/uncloud/uncloud/urls.py +++ b/uncloud/uncloud/urls.py @@ -18,14 +18,12 @@ from django.urls import path, include from rest_framework import routers -from uncloud_api import views as apiviews from uncloud_vm import views as vmviews from opennebula import views as oneviews router = routers.DefaultRouter() -router.register(r'user', apiviews.UserViewSet, basename='user') - +# user / regular urls router.register(r'vm/snapshot', apiviews.VMSnapshotView, basename='VMSnapshot') router.register(r'vm/vm', vmviews.VMProductViewSet, basename='vmproduct') @@ -35,10 +33,7 @@ router.register(r'admin/opennebula', oneviews.VMViewSet, basename='opennebula') router.register(r'admin/opennebula_raw', oneviews.RawVMViewSet) -# Wire up our API using automatic URL routing. -# Additionally, we include login URLs for the browsable API. urlpatterns = [ path('', include(router.urls)), - path('admin/', admin.site.urls), # login to django itself path('api-auth/', include('rest_framework.urls', namespace='rest_framework')) # for login to REST API ] diff --git a/uncloud/uncloud_api/serializers.py b/uncloud/uncloud_api/serializers.py index cd7fd14..89f4e83 100644 --- a/uncloud/uncloud_api/serializers.py +++ b/uncloud/uncloud_api/serializers.py @@ -1,4 +1,3 @@ -from django.contrib.auth.models import Group from django.contrib.auth import get_user_model from rest_framework import serializers @@ -7,8 +6,3 @@ class UserSerializer(serializers.ModelSerializer): class Meta: model = get_user_model() fields = ['url', 'username', 'email'] - -class GroupSerializer(serializers.HyperlinkedModelSerializer): - class Meta: - model = Group - fields = ['url', 'name'] diff --git a/uncloud/uncloud_api/views.py b/uncloud/uncloud_api/views.py index 7e5c6f9..c90b963 100644 --- a/uncloud/uncloud_api/views.py +++ b/uncloud/uncloud_api/views.py @@ -7,10 +7,7 @@ from rest_framework import viewsets, permissions, generics from rest_framework.views import APIView from rest_framework.response import Response -from uncloud_vm.models import VMProduct -from .models import VMSnapshotProduct -from .serializers import UserSerializer, GroupSerializer, VMSnapshotSerializer, VMSnapshotCreateSerializer - +from .serializers import UserSerializer import inspect import sys @@ -51,4 +48,4 @@ class UserViewSet(viewsets.ModelViewSet): permission_classes = [permissions.IsAuthenticated] def get_queryset(self): - return self.request.user \ No newline at end of file + return self.request.user From 11d629bb512854560cd5f720fd22921dca18c6ef Mon Sep 17 00:00:00 2001 From: Nico Schottelius Date: Thu, 27 Feb 2020 11:42:42 +0100 Subject: [PATCH 04/23] [uncloud_api] completely remove it --- uncloud/uncloud_api/__init__.py | 0 uncloud/uncloud_api/admin.py | 6 --- uncloud/uncloud_api/apps.py | 5 -- uncloud/uncloud_api/management/__init__.py | 0 .../management/commands/__init__.py | 0 .../uncloud_api/management/commands/hack.py | 26 ---------- .../management/commands/snapshot.py | 29 ----------- .../uncloud_api/migrations/0001_initial.py | 31 ----------- .../0002_vmsnapshotproduct_vm_uuid.py | 19 ------- .../migrations/0003_auto_20200225_1950.py | 36 ------------- uncloud/uncloud_api/migrations/__init__.py | 0 uncloud/uncloud_api/serializers.py | 8 --- uncloud/uncloud_api/tests.py | 3 -- uncloud/uncloud_api/views.py | 51 ------------------- 14 files changed, 214 deletions(-) delete mode 100644 uncloud/uncloud_api/__init__.py delete mode 100644 uncloud/uncloud_api/admin.py delete mode 100644 uncloud/uncloud_api/apps.py delete mode 100644 uncloud/uncloud_api/management/__init__.py delete mode 100644 uncloud/uncloud_api/management/commands/__init__.py delete mode 100644 uncloud/uncloud_api/management/commands/hack.py delete mode 100644 uncloud/uncloud_api/management/commands/snapshot.py delete mode 100644 uncloud/uncloud_api/migrations/0001_initial.py delete mode 100644 uncloud/uncloud_api/migrations/0002_vmsnapshotproduct_vm_uuid.py delete mode 100644 uncloud/uncloud_api/migrations/0003_auto_20200225_1950.py delete mode 100644 uncloud/uncloud_api/migrations/__init__.py delete mode 100644 uncloud/uncloud_api/serializers.py delete mode 100644 uncloud/uncloud_api/tests.py delete mode 100644 uncloud/uncloud_api/views.py diff --git a/uncloud/uncloud_api/__init__.py b/uncloud/uncloud_api/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/uncloud/uncloud_api/admin.py b/uncloud/uncloud_api/admin.py deleted file mode 100644 index d242668..0000000 --- a/uncloud/uncloud_api/admin.py +++ /dev/null @@ -1,6 +0,0 @@ -from django.contrib import admin - -from .models import Product, Feature - -#admin.site.register(Product) -#admin.site.register(Feature) diff --git a/uncloud/uncloud_api/apps.py b/uncloud/uncloud_api/apps.py deleted file mode 100644 index 6830fa2..0000000 --- a/uncloud/uncloud_api/apps.py +++ /dev/null @@ -1,5 +0,0 @@ -from django.apps import AppConfig - - -class ApiConfig(AppConfig): - name = 'uncloud_api' diff --git a/uncloud/uncloud_api/management/__init__.py b/uncloud/uncloud_api/management/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/uncloud/uncloud_api/management/commands/__init__.py b/uncloud/uncloud_api/management/commands/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/uncloud/uncloud_api/management/commands/hack.py b/uncloud/uncloud_api/management/commands/hack.py deleted file mode 100644 index e129952..0000000 --- a/uncloud/uncloud_api/management/commands/hack.py +++ /dev/null @@ -1,26 +0,0 @@ -import time -from django.conf import settings -from django.core.management.base import BaseCommand - -import uncloud_api.models - -import inspect -import sys -import re - -class Command(BaseCommand): - args = '' - help = 'hacking - only use if you are Nico' - - def add_arguments(self, parser): - parser.add_argument('command', type=str, help='Command') - - def handle(self, *args, **options): - getattr(self, options['command'])(**options) - - @classmethod - def classtest(cls, **_): - clsmembers = inspect.getmembers(sys.modules['uncloud_api.models'], inspect.isclass) - for name, c in clsmembers: - if re.match(r'.+Product$', name): - print("{} -> {}".format(name, c)) diff --git a/uncloud/uncloud_api/management/commands/snapshot.py b/uncloud/uncloud_api/management/commands/snapshot.py deleted file mode 100644 index 41d0e38..0000000 --- a/uncloud/uncloud_api/management/commands/snapshot.py +++ /dev/null @@ -1,29 +0,0 @@ -import time -from django.conf import settings -from django.core.management.base import BaseCommand - -from uncloud_api import models - - -class Command(BaseCommand): - args = '' - help = 'VM Snapshot support' - - def add_arguments(self, parser): - parser.add_argument('command', type=str, help='Command') - - def handle(self, *args, **options): - print("Snapshotting") - #getattr(self, options['command'])(**options) - - @classmethod - def monitor(cls, **_): - while True: - try: - tweets = models.Reply.get_target_tweets() - responses = models.Reply.objects.values_list('tweet_id', flat=True) - new_tweets = [x for x in tweets if x.id not in responses] - models.Reply.send(new_tweets) - except TweepError as e: - print(e) - time.sleep(60) diff --git a/uncloud/uncloud_api/migrations/0001_initial.py b/uncloud/uncloud_api/migrations/0001_initial.py deleted file mode 100644 index 67bdd2e..0000000 --- a/uncloud/uncloud_api/migrations/0001_initial.py +++ /dev/null @@ -1,31 +0,0 @@ -# Generated by Django 3.0.3 on 2020-02-23 17:12 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion -import uuid - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.CreateModel( - name='VMSnapshotProduct', - fields=[ - ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), - ('status', models.CharField(choices=[('pending', 'Pending'), ('being_created', 'Being created'), ('active', 'Active'), ('deleted', 'Deleted')], default='pending', max_length=256)), - ('gb_ssd', models.FloatField()), - ('gb_hdd', models.FloatField()), - ('owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), - ], - options={ - 'abstract': False, - }, - ), - ] diff --git a/uncloud/uncloud_api/migrations/0002_vmsnapshotproduct_vm_uuid.py b/uncloud/uncloud_api/migrations/0002_vmsnapshotproduct_vm_uuid.py deleted file mode 100644 index b35317e..0000000 --- a/uncloud/uncloud_api/migrations/0002_vmsnapshotproduct_vm_uuid.py +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Django 3.0.3 on 2020-02-25 18:16 - -from django.db import migrations, models -import uuid - - -class Migration(migrations.Migration): - - dependencies = [ - ('uncloud_api', '0001_initial'), - ] - - operations = [ - migrations.AddField( - model_name='vmsnapshotproduct', - name='vm_uuid', - field=models.UUIDField(default=uuid.uuid4, editable=False), - ), - ] diff --git a/uncloud/uncloud_api/migrations/0003_auto_20200225_1950.py b/uncloud/uncloud_api/migrations/0003_auto_20200225_1950.py deleted file mode 100644 index be7624c..0000000 --- a/uncloud/uncloud_api/migrations/0003_auto_20200225_1950.py +++ /dev/null @@ -1,36 +0,0 @@ -# Generated by Django 3.0.3 on 2020-02-25 19:50 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('uncloud_api', '0002_vmsnapshotproduct_vm_uuid'), - ] - - operations = [ - migrations.AlterField( - model_name='vmsnapshotproduct', - name='gb_hdd', - field=models.FloatField(editable=False), - ), - migrations.AlterField( - model_name='vmsnapshotproduct', - name='gb_ssd', - field=models.FloatField(editable=False), - ), - migrations.AlterField( - model_name='vmsnapshotproduct', - name='owner', - field=models.ForeignKey(editable=False, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), - ), - migrations.AlterField( - model_name='vmsnapshotproduct', - name='vm_uuid', - field=models.UUIDField(), - ), - ] diff --git a/uncloud/uncloud_api/migrations/__init__.py b/uncloud/uncloud_api/migrations/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/uncloud/uncloud_api/serializers.py b/uncloud/uncloud_api/serializers.py deleted file mode 100644 index 89f4e83..0000000 --- a/uncloud/uncloud_api/serializers.py +++ /dev/null @@ -1,8 +0,0 @@ -from django.contrib.auth import get_user_model - -from rest_framework import serializers - -class UserSerializer(serializers.ModelSerializer): - class Meta: - model = get_user_model() - fields = ['url', 'username', 'email'] diff --git a/uncloud/uncloud_api/tests.py b/uncloud/uncloud_api/tests.py deleted file mode 100644 index 7ce503c..0000000 --- a/uncloud/uncloud_api/tests.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.test import TestCase - -# Create your tests here. diff --git a/uncloud/uncloud_api/views.py b/uncloud/uncloud_api/views.py deleted file mode 100644 index c90b963..0000000 --- a/uncloud/uncloud_api/views.py +++ /dev/null @@ -1,51 +0,0 @@ -from django.shortcuts import render -from django.contrib.auth import get_user_model -from django.contrib.auth.models import Group - -from rest_framework import viewsets, permissions, generics - -from rest_framework.views import APIView -from rest_framework.response import Response - -from .serializers import UserSerializer - -import inspect -import sys -import re - - - -# maybe drop or not --- we need something to guide the user! -# class ProductsViewSet(viewsets.ViewSet): -# permission_classes = [permissions.IsAuthenticated] - -# def list(self, request): - -# clsmembers = [] -# for modules in [ 'uncloud_api.models', 'uncloud_vm.models' ]: -# clsmembers.extend(inspect.getmembers(sys.modules[modules], inspect.isclass)) - - -# products = [] -# for name, c in clsmembers: -# # Include everything that ends in Product, but not Product itself -# m = re.match(r'(?P.+)Product$', name) -# if m: -# products.append({ -# 'name': m.group('pname'), -# 'description': c.description, -# 'recurring_period': c.recurring_period, -# 'pricing_model': c.pricing_model() -# } -# ) - - -# return Response(products) - - -class UserViewSet(viewsets.ModelViewSet): - serializer_class = UserSerializer - permission_classes = [permissions.IsAuthenticated] - - def get_queryset(self): - return self.request.user From 06ab21c577052f5a3fafee8b653bf9ea6d44a04d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Floure?= Date: Thu, 27 Feb 2020 11:59:28 +0100 Subject: [PATCH 05/23] Fix python errors on latest hack commits => make runserver happy again --- uncloud/uncloud/settings.py | 1 + uncloud/uncloud/urls.py | 7 +- uncloud/uncloud_api/admin.py | 2 - .../0002_vmsnapshotproduct_vm_uuid.py | 19 ------ .../migrations/0003_auto_20200225_1950.py | 36 ---------- uncloud/uncloud_api/migrations/__init__.py | 0 .../uncloud_pay/migrations/0001_initial.py | 56 ++++++++++++++++ uncloud/uncloud_pay/models.py | 8 ++- uncloud/uncloud_pay/serializers.py | 12 ++++ uncloud/uncloud_pay/views.py | 65 ++++++++----------- .../migrations/0004_vmsnapshotproduct.py} | 14 ++-- uncloud/uncloud_vm/serializers.py | 2 +- uncloud/uncloud_vm/views.py | 6 +- 13 files changed, 121 insertions(+), 107 deletions(-) delete mode 100644 uncloud/uncloud_api/migrations/0002_vmsnapshotproduct_vm_uuid.py delete mode 100644 uncloud/uncloud_api/migrations/0003_auto_20200225_1950.py delete mode 100644 uncloud/uncloud_api/migrations/__init__.py create mode 100644 uncloud/uncloud_pay/migrations/0001_initial.py create mode 100644 uncloud/uncloud_pay/serializers.py rename uncloud/{uncloud_api/migrations/0001_initial.py => uncloud_vm/migrations/0004_vmsnapshotproduct.py} (57%) diff --git a/uncloud/uncloud/settings.py b/uncloud/uncloud/settings.py index 614cd25..05c4f35 100644 --- a/uncloud/uncloud/settings.py +++ b/uncloud/uncloud/settings.py @@ -61,6 +61,7 @@ INSTALLED_APPS = [ 'django.contrib.staticfiles', 'rest_framework', 'uncloud_api', + 'uncloud_pay', 'uncloud_auth', 'uncloud_vm', 'opennebula' diff --git a/uncloud/uncloud/urls.py b/uncloud/uncloud/urls.py index 1e4c9d0..79958c5 100644 --- a/uncloud/uncloud/urls.py +++ b/uncloud/uncloud/urls.py @@ -19,14 +19,19 @@ from django.urls import path, include from rest_framework import routers from uncloud_vm import views as vmviews +from uncloud_pay import views as payviews from opennebula import views as oneviews router = routers.DefaultRouter() # user / regular urls -router.register(r'vm/snapshot', apiviews.VMSnapshotView, basename='VMSnapshot') +router.register(r'vm/snapshot', vmviews.VMSnapshotProductView, basename='VMSnapshot') router.register(r'vm/vm', vmviews.VMProductViewSet, basename='vmproduct') +# Pay +router.register(r'bill', payviews.BillViewSet, basename='bill') +router.register(r'payment', payviews.PaymentViewSet, basename='payment') + # admin/staff urls router.register(r'admin/vmhost', vmviews.VMHostViewSet) router.register(r'admin/opennebula', oneviews.VMViewSet, basename='opennebula') diff --git a/uncloud/uncloud_api/admin.py b/uncloud/uncloud_api/admin.py index d242668..03246ec 100644 --- a/uncloud/uncloud_api/admin.py +++ b/uncloud/uncloud_api/admin.py @@ -1,6 +1,4 @@ from django.contrib import admin -from .models import Product, Feature - #admin.site.register(Product) #admin.site.register(Feature) diff --git a/uncloud/uncloud_api/migrations/0002_vmsnapshotproduct_vm_uuid.py b/uncloud/uncloud_api/migrations/0002_vmsnapshotproduct_vm_uuid.py deleted file mode 100644 index b35317e..0000000 --- a/uncloud/uncloud_api/migrations/0002_vmsnapshotproduct_vm_uuid.py +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Django 3.0.3 on 2020-02-25 18:16 - -from django.db import migrations, models -import uuid - - -class Migration(migrations.Migration): - - dependencies = [ - ('uncloud_api', '0001_initial'), - ] - - operations = [ - migrations.AddField( - model_name='vmsnapshotproduct', - name='vm_uuid', - field=models.UUIDField(default=uuid.uuid4, editable=False), - ), - ] diff --git a/uncloud/uncloud_api/migrations/0003_auto_20200225_1950.py b/uncloud/uncloud_api/migrations/0003_auto_20200225_1950.py deleted file mode 100644 index be7624c..0000000 --- a/uncloud/uncloud_api/migrations/0003_auto_20200225_1950.py +++ /dev/null @@ -1,36 +0,0 @@ -# Generated by Django 3.0.3 on 2020-02-25 19:50 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('uncloud_api', '0002_vmsnapshotproduct_vm_uuid'), - ] - - operations = [ - migrations.AlterField( - model_name='vmsnapshotproduct', - name='gb_hdd', - field=models.FloatField(editable=False), - ), - migrations.AlterField( - model_name='vmsnapshotproduct', - name='gb_ssd', - field=models.FloatField(editable=False), - ), - migrations.AlterField( - model_name='vmsnapshotproduct', - name='owner', - field=models.ForeignKey(editable=False, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), - ), - migrations.AlterField( - model_name='vmsnapshotproduct', - name='vm_uuid', - field=models.UUIDField(), - ), - ] diff --git a/uncloud/uncloud_api/migrations/__init__.py b/uncloud/uncloud_api/migrations/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/uncloud/uncloud_pay/migrations/0001_initial.py b/uncloud/uncloud_pay/migrations/0001_initial.py new file mode 100644 index 0000000..6e57c59 --- /dev/null +++ b/uncloud/uncloud_pay/migrations/0001_initial.py @@ -0,0 +1,56 @@ +# Generated by Django 3.0.3 on 2020-02-27 10:50 + +from django.conf import settings +import django.core.validators +from django.db import migrations, models +import django.db.models.deletion +import uuid + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Bill', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('creation_date', models.DateTimeField()), + ('starting_date', models.DateTimeField()), + ('ending_date', models.DateTimeField()), + ('due_date', models.DateField()), + ('paid', models.BooleanField(default=False)), + ('valid', models.BooleanField(default=True)), + ('owner', models.ForeignKey(editable=False, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.CreateModel( + name='Payment', + fields=[ + ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('amount', models.DecimalField(decimal_places=2, default=0.0, max_digits=10, validators=[django.core.validators.MinValueValidator(0)])), + ('source', models.CharField(choices=[('wire', 'Wire Transfer'), ('strip', 'Stripe'), ('voucher', 'Voucher'), ('referral', 'Referral'), ('unknown', 'Unknown')], default='unknown', max_length=256)), + ('timestamp', models.DateTimeField(editable=False)), + ('owner', models.ForeignKey(editable=False, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.CreateModel( + name='Order', + fields=[ + ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('creation_date', models.DateTimeField()), + ('starting_date', models.DateTimeField()), + ('ending_date', models.DateTimeField(blank=True, null=True)), + ('recurring_price', models.FloatField(editable=False)), + ('one_time_price', models.FloatField(editable=False)), + ('recurring_period', models.CharField(choices=[('onetime', 'Onetime'), ('per_year', 'Per Year'), ('per_month', 'Per Month'), ('per_week', 'Per Week'), ('per_day', 'Per Day'), ('per_hour', 'Per Hour'), ('per_minute', 'Per Minute'), ('per_second', 'Per Second')], default='onetime', max_length=32)), + ('bill', models.ManyToManyField(blank=True, editable=False, null=True, to='uncloud_pay.Bill')), + ('owner', models.ForeignKey(editable=False, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/uncloud/uncloud_pay/models.py b/uncloud/uncloud_pay/models.py index 831710b..71653fa 100644 --- a/uncloud/uncloud_pay/models.py +++ b/uncloud/uncloud_pay/models.py @@ -1,8 +1,11 @@ from django.db import models from django.contrib.auth import get_user_model +from django.core.validators import MinValueValidator -# Create your models here. +import uuid +AMOUNT_MAX_DIGITS=10 +AMOUNT_DECIMALS=2 class Bill(models.Model): owner = models.ForeignKey(get_user_model(), @@ -35,7 +38,6 @@ class Order(models.Model): null=True) bill = models.ManyToManyField(Bill, - on_delete=models.CASCADE, editable=False, blank=True, null=True) @@ -77,6 +79,8 @@ class Payment(models.Model): amount = models.DecimalField( default=0.0, + max_digits=AMOUNT_MAX_DIGITS, + decimal_places=AMOUNT_DECIMALS, validators=[MinValueValidator(0)]) source = models.CharField(max_length=256, diff --git a/uncloud/uncloud_pay/serializers.py b/uncloud/uncloud_pay/serializers.py new file mode 100644 index 0000000..e11544b --- /dev/null +++ b/uncloud/uncloud_pay/serializers.py @@ -0,0 +1,12 @@ +from rest_framework import serializers +from .models import Bill, Payment + +class BillSerializer(serializers.ModelSerializer): + class Meta: + model = Bill + fields = ['user', 'amount'] + +class PaymentSerializer(serializers.ModelSerializer): + class Meta: + model = Payment + fields = ['user', 'amount', 'source', 'timestamp'] diff --git a/uncloud/uncloud_pay/views.py b/uncloud/uncloud_pay/views.py index b52a2b6..8fc02ea 100644 --- a/uncloud/uncloud_pay/views.py +++ b/uncloud/uncloud_pay/views.py @@ -1,55 +1,46 @@ from django.shortcuts import render +from rest_framework import viewsets, permissions -# Create your views here. +from .models import Bill, Payment +from .serializers import BillSerializer, PaymentSerializer # to be implemented -class BalanceViewSet(viewsets.ModelViewSet): +class BalanceViewSet(viewsets.ViewSet): # here we return a number # number = sum(payments) - sum(bills) - bills = Bills.objects.filter(owner=self.request.user) - payments = Payment.objects.filter(owner=self.request.user) + #bills = Bill.objects.filter(owner=self.request.user) + #payments = Payment.objects.filter(owner=self.request.user) # sum_paid = sum([ amount for amount payments..,. ]) # you get the picture # sum_to_be_paid = sum([ amount for amount bills..,. ]) # you get the picture + pass -class Bills(viewset.ModelViewSet): - def unpaid(self, request): - return Bills.objects.filter(owner=self.request.user, paid=False) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +class BillViewSet(viewsets.ModelViewSet): serializer_class = BillSerializer permission_classes = [permissions.IsAuthenticated] http_method_names = ['get'] def get_queryset(self): - return self.request.user.get_bills() + return Bill.objects.filter(owner=self.request.user) + + def unpaid(self, request): + return Bill.objects.filter(owner=self.request.user, paid=False) + +class PaymentViewSet(viewsets.ModelViewSet): + serializer_class = PaymentSerializer + permission_classes = [permissions.IsAuthenticated] + http_method_names = ['get', 'post'] + + def get_queryset(self): + return Payment.objects.filter(user=self.request.user) + + def create(self, request): + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + serializer.save(user=request.user,timestamp=datetime.now()) + + headers = self.get_success_headers(serializer.data) + return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers) diff --git a/uncloud/uncloud_api/migrations/0001_initial.py b/uncloud/uncloud_vm/migrations/0004_vmsnapshotproduct.py similarity index 57% rename from uncloud/uncloud_api/migrations/0001_initial.py rename to uncloud/uncloud_vm/migrations/0004_vmsnapshotproduct.py index 67bdd2e..13840b5 100644 --- a/uncloud/uncloud_api/migrations/0001_initial.py +++ b/uncloud/uncloud_vm/migrations/0004_vmsnapshotproduct.py @@ -1,4 +1,4 @@ -# Generated by Django 3.0.3 on 2020-02-23 17:12 +# Generated by Django 3.0.3 on 2020-02-27 10:50 from django.conf import settings from django.db import migrations, models @@ -8,10 +8,10 @@ import uuid class Migration(migrations.Migration): - initial = True - dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('uncloud_pay', '0001_initial'), + ('uncloud_vm', '0003_auto_20200225_2028'), ] operations = [ @@ -20,9 +20,11 @@ class Migration(migrations.Migration): fields=[ ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), ('status', models.CharField(choices=[('pending', 'Pending'), ('being_created', 'Being created'), ('active', 'Active'), ('deleted', 'Deleted')], default='pending', max_length=256)), - ('gb_ssd', models.FloatField()), - ('gb_hdd', models.FloatField()), - ('owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ('gb_ssd', models.FloatField(editable=False)), + ('gb_hdd', models.FloatField(editable=False)), + ('vm_uuid', models.UUIDField()), + ('order', models.ForeignKey(editable=False, on_delete=django.db.models.deletion.CASCADE, to='uncloud_pay.Order')), + ('owner', models.ForeignKey(editable=False, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), ], options={ 'abstract': False, diff --git a/uncloud/uncloud_vm/serializers.py b/uncloud/uncloud_vm/serializers.py index d5549ad..232e954 100644 --- a/uncloud/uncloud_vm/serializers.py +++ b/uncloud/uncloud_vm/serializers.py @@ -1,7 +1,7 @@ from django.contrib.auth import get_user_model from rest_framework import serializers -from .models import VMHost, VMProduct +from .models import VMHost, VMProduct, VMSnapshotProduct class VMHostSerializer(serializers.HyperlinkedModelSerializer): class Meta: diff --git a/uncloud/uncloud_vm/views.py b/uncloud/uncloud_vm/views.py index 4f2f9f4..cb87e9d 100644 --- a/uncloud/uncloud_vm/views.py +++ b/uncloud/uncloud_vm/views.py @@ -7,7 +7,7 @@ from rest_framework import viewsets, permissions from rest_framework.response import Response from .models import VMHost, VMProduct -from .serializers import VMHostSerializer, VMProductSerializer +from .serializers import VMHostSerializer, VMProductSerializer, VMSnapshotProductSerializer class VMHostViewSet(viewsets.ModelViewSet): serializer_class = VMHostSerializer @@ -49,13 +49,13 @@ class VMSnapshotProductView(viewsets.ViewSet): def list(self, request): queryset = VMSnapshotProduct.objects.filter(owner=request.user) - serializer = VMSnapshotSerializer(queryset, many=True, context={'request': request}) + serializer = VMSnapshotProductSerializer(queryset, many=True, context={'request': request}) return Response(serializer.data) def retrieve(self, request, pk=None): queryset = VMSnapshotProduct.objects.filter(owner=request.user) vm = get_object_or_404(queryset, pk=pk) - serializer = VMSnapshotSerializer(vm, context={'request': request}) + serializer = VMSnapshotProductSerializer(vm, context={'request': request}) return Response(serializer.data) def create(self, request): From fd648ade6579334b953fe3944b25f1b489974d42 Mon Sep 17 00:00:00 2001 From: Nico Schottelius Date: Thu, 27 Feb 2020 12:02:41 +0100 Subject: [PATCH 06/23] ++cleanup Signed-off-by: Nico Schottelius --- uncloud/uncloud/settings.py | 1 - uncloud/uncloud_vm/views.py | 45 ++++++++----------------------------- 2 files changed, 9 insertions(+), 37 deletions(-) diff --git a/uncloud/uncloud/settings.py b/uncloud/uncloud/settings.py index 614cd25..899de1b 100644 --- a/uncloud/uncloud/settings.py +++ b/uncloud/uncloud/settings.py @@ -60,7 +60,6 @@ INSTALLED_APPS = [ 'django.contrib.messages', 'django.contrib.staticfiles', 'rest_framework', - 'uncloud_api', 'uncloud_auth', 'uncloud_vm', 'opennebula' diff --git a/uncloud/uncloud_vm/views.py b/uncloud/uncloud_vm/views.py index 4f2f9f4..aabf8c5 100644 --- a/uncloud/uncloud_vm/views.py +++ b/uncloud/uncloud_vm/views.py @@ -6,7 +6,9 @@ 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 +from .models import VMHost, VMProduct. VMSnapshotProduct +from uncloud_pay.models import Order + from .serializers import VMHostSerializer, VMProductSerializer class VMHostViewSet(viewsets.ModelViewSet): @@ -14,6 +16,7 @@ class VMHostViewSet(viewsets.ModelViewSet): queryset = VMHost.objects.all() permission_classes = [permissions.IsAdminUser] + class VMProductViewSet(viewsets.ModelViewSet): permission_classes = [permissions.IsAuthenticated] serializer_class = VMProductSerializer @@ -37,43 +40,13 @@ class VMSnapshotProductViewSet(viewsets.ModelViewSet): return VMSnapshotProduct.objects.filter(owner=self.request.user) def create(self, request): + serializer = VMProductSerializer(data=request.data, context={'request': request}) serializer.is_valid(raise_exception=True) + + # Create order + order = Order() + serializer.save(owner=request.user) return Response(serializer.data) - - -class VMSnapshotProductView(viewsets.ViewSet): - permission_classes = [permissions.IsAuthenticated] - - def list(self, request): - queryset = VMSnapshotProduct.objects.filter(owner=request.user) - serializer = VMSnapshotSerializer(queryset, many=True, context={'request': request}) - return Response(serializer.data) - - def retrieve(self, request, pk=None): - queryset = VMSnapshotProduct.objects.filter(owner=request.user) - vm = get_object_or_404(queryset, pk=pk) - serializer = VMSnapshotSerializer(vm, context={'request': request}) - return Response(serializer.data) - - def create(self, request): - print(request.data) - serializer = VMSnapshotCreateSerializer(data=request.data) - - serializer.gb_ssd = 12 - serializer.gb_hdd = 120 - print("F") - serializer.is_valid(raise_exception=True) - - print(serializer) - print("A") - serializer.save() - print("B") - - - # snapshot = VMSnapshotProduct(owner=request.user, - # **serialzer.data) - - return Response(serializer.data) From 41a5eae8796876f847d87106fdddfc3612be65e7 Mon Sep 17 00:00:00 2001 From: Nico Schottelius Date: Thu, 27 Feb 2020 12:09:29 +0100 Subject: [PATCH 07/23] cleanup views/vmsnapshot --- uncloud/uncloud/urls.py | 2 +- uncloud/uncloud_vm/views.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/uncloud/uncloud/urls.py b/uncloud/uncloud/urls.py index 79958c5..a02f24a 100644 --- a/uncloud/uncloud/urls.py +++ b/uncloud/uncloud/urls.py @@ -25,7 +25,7 @@ from opennebula import views as oneviews router = routers.DefaultRouter() # user / regular urls -router.register(r'vm/snapshot', vmviews.VMSnapshotProductView, basename='VMSnapshot') +router.register(r'vm/snapshot', vmviews.VMSnapshotProductViewSet, basename='VMSnapshot') router.register(r'vm/vm', vmviews.VMProductViewSet, basename='vmproduct') # Pay diff --git a/uncloud/uncloud_vm/views.py b/uncloud/uncloud_vm/views.py index 55b607f..c82dff3 100644 --- a/uncloud/uncloud_vm/views.py +++ b/uncloud/uncloud_vm/views.py @@ -6,7 +6,7 @@ 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 from uncloud_pay.models import Order from .serializers import VMHostSerializer, VMProductSerializer, VMSnapshotProductSerializer From f358acca058ab55e51067dad2d9cfb02e441acfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Floure?= Date: Thu, 27 Feb 2020 12:10:26 +0100 Subject: [PATCH 08/23] Fix payment creation --- uncloud/uncloud_pay/serializers.py | 4 ++-- uncloud/uncloud_pay/views.py | 8 +++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/uncloud/uncloud_pay/serializers.py b/uncloud/uncloud_pay/serializers.py index e11544b..024fe3f 100644 --- a/uncloud/uncloud_pay/serializers.py +++ b/uncloud/uncloud_pay/serializers.py @@ -4,9 +4,9 @@ from .models import Bill, Payment class BillSerializer(serializers.ModelSerializer): class Meta: model = Bill - fields = ['user', 'amount'] + fields = ['owner', 'amount'] class PaymentSerializer(serializers.ModelSerializer): class Meta: model = Payment - fields = ['user', 'amount', 'source', 'timestamp'] + fields = ['owner', 'amount', 'source', 'timestamp'] diff --git a/uncloud/uncloud_pay/views.py b/uncloud/uncloud_pay/views.py index 8fc02ea..8f37814 100644 --- a/uncloud/uncloud_pay/views.py +++ b/uncloud/uncloud_pay/views.py @@ -1,8 +1,10 @@ from django.shortcuts import render -from rest_framework import viewsets, permissions +from rest_framework import viewsets, permissions, status +from rest_framework.response import Response from .models import Bill, Payment from .serializers import BillSerializer, PaymentSerializer +from datetime import datetime # to be implemented @@ -35,12 +37,12 @@ class PaymentViewSet(viewsets.ModelViewSet): http_method_names = ['get', 'post'] def get_queryset(self): - return Payment.objects.filter(user=self.request.user) + return Payment.objects.filter(owner=self.request.user) def create(self, request): serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) - serializer.save(user=request.user,timestamp=datetime.now()) + serializer.save(owner=request.user,timestamp=datetime.now()) headers = self.get_success_headers(serializer.data) return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers) From b9b605f407ed53d26418c2475ef928504411d92e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Floure?= Date: Thu, 27 Feb 2020 12:21:25 +0100 Subject: [PATCH 09/23] Add ADMIN endpoints for bills and payments --- uncloud/uncloud/urls.py | 2 ++ uncloud/uncloud_pay/serializers.py | 2 +- uncloud/uncloud_pay/views.py | 39 +++++++++++++++++++++++++----- 3 files changed, 36 insertions(+), 7 deletions(-) diff --git a/uncloud/uncloud/urls.py b/uncloud/uncloud/urls.py index 79958c5..341f81a 100644 --- a/uncloud/uncloud/urls.py +++ b/uncloud/uncloud/urls.py @@ -33,6 +33,8 @@ router.register(r'bill', payviews.BillViewSet, basename='bill') router.register(r'payment', payviews.PaymentViewSet, basename='payment') # admin/staff urls +router.register(r'admin/bill', payviews.AdminBillViewSet, basename='admin/bill') +router.register(r'admin/payment', payviews.AdminPaymentViewSet, basename='admin/payment') router.register(r'admin/vmhost', vmviews.VMHostViewSet) router.register(r'admin/opennebula', oneviews.VMViewSet, basename='opennebula') router.register(r'admin/opennebula_raw', oneviews.RawVMViewSet) diff --git a/uncloud/uncloud_pay/serializers.py b/uncloud/uncloud_pay/serializers.py index 024fe3f..f4fd565 100644 --- a/uncloud/uncloud_pay/serializers.py +++ b/uncloud/uncloud_pay/serializers.py @@ -4,7 +4,7 @@ from .models import Bill, Payment class BillSerializer(serializers.ModelSerializer): class Meta: model = Bill - fields = ['owner', 'amount'] + fields = ['owner', 'amount', 'due_date', 'creation_date', 'starting_date', 'ending_date', 'paid'] class PaymentSerializer(serializers.ModelSerializer): class Meta: diff --git a/uncloud/uncloud_pay/views.py b/uncloud/uncloud_pay/views.py index 8f37814..d824d27 100644 --- a/uncloud/uncloud_pay/views.py +++ b/uncloud/uncloud_pay/views.py @@ -6,8 +6,9 @@ from .models import Bill, Payment from .serializers import BillSerializer, PaymentSerializer from datetime import datetime +### +# Standard user views: -# to be implemented class BalanceViewSet(viewsets.ViewSet): # here we return a number # number = sum(payments) - sum(bills) @@ -20,10 +21,9 @@ class BalanceViewSet(viewsets.ViewSet): pass -class BillViewSet(viewsets.ModelViewSet): +class BillViewSet(viewsets.ReadOnlyModelViewSet): serializer_class = BillSerializer permission_classes = [permissions.IsAuthenticated] - http_method_names = ['get'] def get_queryset(self): return Bill.objects.filter(owner=self.request.user) @@ -31,10 +31,19 @@ class BillViewSet(viewsets.ModelViewSet): def unpaid(self, request): return Bill.objects.filter(owner=self.request.user, paid=False) -class PaymentViewSet(viewsets.ModelViewSet): +class PaymentViewSet(viewsets.ReadOnlyModelViewSet): + serializer_class = PaymentSerializer + permission_classes = [permissions.IsAuthenticated] + + def get_queryset(self): + return Payment.objects.filter(owner=self.request.user) + +### +# Admin views. + +class AdminPaymentViewSet(viewsets.ModelViewSet): serializer_class = PaymentSerializer permission_classes = [permissions.IsAuthenticated] - http_method_names = ['get', 'post'] def get_queryset(self): return Payment.objects.filter(owner=self.request.user) @@ -42,7 +51,25 @@ class PaymentViewSet(viewsets.ModelViewSet): def create(self, request): serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) - serializer.save(owner=request.user,timestamp=datetime.now()) + serializer.save(timestamp=datetime.now()) + + headers = self.get_success_headers(serializer.data) + return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers) + +class AdminBillViewSet(viewsets.ModelViewSet): + serializer_class = BillSerializer + permission_classes = [permissions.IsAuthenticated] + + def get_queryset(self): + return Bill.objects.filter(owner=self.request.user) + + def unpaid(self, request): + return Bill.objects.filter(owner=self.request.user, paid=False) + + def create(self, request): + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + serializer.save(created_at=datetime.now()) headers = self.get_success_headers(serializer.data) return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers) From 225f20c91b423b2b4a04a7cb83e7d2df03258047 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Floure?= Date: Thu, 27 Feb 2020 12:21:52 +0100 Subject: [PATCH 10/23] Fix typo in payment source model --- uncloud/uncloud_pay/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/uncloud/uncloud_pay/models.py b/uncloud/uncloud_pay/models.py index 71653fa..6a33fd5 100644 --- a/uncloud/uncloud_pay/models.py +++ b/uncloud/uncloud_pay/models.py @@ -86,7 +86,7 @@ class Payment(models.Model): source = models.CharField(max_length=256, choices = ( ('wire', 'Wire Transfer'), - ('strip', 'Stripe'), + ('stripe', 'Stripe'), ('voucher', 'Voucher'), ('referral', 'Referral'), ('unknown', 'Unknown') From a9aac394866a5df90ac0c3945a5d749631cdc7b0 Mon Sep 17 00:00:00 2001 From: Nico Schottelius Date: Thu, 27 Feb 2020 12:31:20 +0100 Subject: [PATCH 11/23] Create a vmsnapshot + associated order --- uncloud/uncloud/urls.py | 2 +- uncloud/uncloud_vm/serializers.py | 7 +------ uncloud/uncloud_vm/views.py | 21 ++++++++++++++++++--- 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/uncloud/uncloud/urls.py b/uncloud/uncloud/urls.py index a02f24a..d6d3b7d 100644 --- a/uncloud/uncloud/urls.py +++ b/uncloud/uncloud/urls.py @@ -25,7 +25,7 @@ from opennebula import views as oneviews router = routers.DefaultRouter() # user / regular urls -router.register(r'vm/snapshot', vmviews.VMSnapshotProductViewSet, basename='VMSnapshot') +router.register(r'vm/snapshot', vmviews.VMSnapshotProductViewSet, basename='vmsnapshotproduct') router.register(r'vm/vm', vmviews.VMProductViewSet, basename='vmproduct') # Pay diff --git a/uncloud/uncloud_vm/serializers.py b/uncloud/uncloud_vm/serializers.py index 232e954..c1eafe2 100644 --- a/uncloud/uncloud_vm/serializers.py +++ b/uncloud/uncloud_vm/serializers.py @@ -14,12 +14,7 @@ class VMProductSerializer(serializers.HyperlinkedModelSerializer): model = VMProduct fields = '__all__' -class VMSnapshotProductSerializer(serializers.HyperlinkedModelSerializer): - class Meta: - model = VMSnapshotProduct - fields = ['uuid', 'status', 'recurring_price', 'one_time_price' ] - -class VMSnapshotProductCreateSerializer(serializers.HyperlinkedModelSerializer): +class VMSnapshotProductSerializer(serializers.ModelSerializer): class Meta: model = VMSnapshotProduct fields = '__all__' diff --git a/uncloud/uncloud_vm/views.py b/uncloud/uncloud_vm/views.py index c82dff3..53986b4 100644 --- a/uncloud/uncloud_vm/views.py +++ b/uncloud/uncloud_vm/views.py @@ -11,6 +11,8 @@ from uncloud_pay.models import Order from .serializers import VMHostSerializer, VMProductSerializer, VMSnapshotProductSerializer +import datetime + class VMHostViewSet(viewsets.ModelViewSet): serializer_class = VMHostSerializer queryset = VMHost.objects.all() @@ -40,12 +42,25 @@ class VMSnapshotProductViewSet(viewsets.ModelViewSet): return VMSnapshotProduct.objects.filter(owner=self.request.user) def create(self, request): - serializer = VMProductSerializer(data=request.data, context={'request': request}) + serializer = VMSnapshotProductSerializer(data=request.data, context={'request': request}) serializer.is_valid(raise_exception=True) + print(serializer) # Create order - #order = Order() + now = datetime.datetime.now() + order = Order(owner=request.user, + creation_date=now, + starting_date=now, + recurring_price=20, + one_time_price=0, + recurring_period="per_month") + order.save() + print(order) - serializer.save(owner=request.user) + # FIXME: calculate the gb_* values + serializer.save(owner=request.user, + order=order, + gb_ssd=12, + gb_hdd=20) return Response(serializer.data) From 7bf4f2adb22e8665d87a7b011f85ba0368bd43e4 Mon Sep 17 00:00:00 2001 From: Nico Schottelius Date: Thu, 27 Feb 2020 12:36:33 +0100 Subject: [PATCH 12/23] --debug Signed-off-by: Nico Schottelius --- uncloud/uncloud_vm/views.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/uncloud/uncloud_vm/views.py b/uncloud/uncloud_vm/views.py index 53986b4..444d134 100644 --- a/uncloud/uncloud_vm/views.py +++ b/uncloud/uncloud_vm/views.py @@ -45,7 +45,6 @@ class VMSnapshotProductViewSet(viewsets.ModelViewSet): serializer = VMSnapshotProductSerializer(data=request.data, context={'request': request}) serializer.is_valid(raise_exception=True) - print(serializer) # Create order now = datetime.datetime.now() order = Order(owner=request.user, @@ -55,7 +54,6 @@ class VMSnapshotProductViewSet(viewsets.ModelViewSet): one_time_price=0, recurring_period="per_month") order.save() - print(order) # FIXME: calculate the gb_* values serializer.save(owner=request.user, From f5eadd6ddbbcbd08a65a1beb8f3796bb6aa5a9ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Floure?= Date: Thu, 27 Feb 2020 12:38:04 +0100 Subject: [PATCH 13/23] Move user view to uncloud_pay --- uncloud/uncloud/urls.py | 1 + uncloud/uncloud_api/views.py | 6 +++++- uncloud/uncloud_pay/serializers.py | 9 +++++++++ uncloud/uncloud_pay/views.py | 15 ++++++++++++++- 4 files changed, 29 insertions(+), 2 deletions(-) diff --git a/uncloud/uncloud/urls.py b/uncloud/uncloud/urls.py index 341f81a..358e4c7 100644 --- a/uncloud/uncloud/urls.py +++ b/uncloud/uncloud/urls.py @@ -29,6 +29,7 @@ router.register(r'vm/snapshot', vmviews.VMSnapshotProductView, basename='VMSnaps 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') router.register(r'payment', payviews.PaymentViewSet, basename='payment') diff --git a/uncloud/uncloud_api/views.py b/uncloud/uncloud_api/views.py index c90b963..18cc324 100644 --- a/uncloud/uncloud_api/views.py +++ b/uncloud/uncloud_api/views.py @@ -43,9 +43,13 @@ import re # return Response(products) -class UserViewSet(viewsets.ModelViewSet): +class UserViewSet(viewsets.ReadOnlyModelViewSet): serializer_class = UserSerializer permission_classes = [permissions.IsAuthenticated] def get_queryset(self): return self.request.user + + @action(detail=True) + def balance(self, request): + return Response(status=status.HTTP_204_NO_CONTENT) diff --git a/uncloud/uncloud_pay/serializers.py b/uncloud/uncloud_pay/serializers.py index f4fd565..5bb22ec 100644 --- a/uncloud/uncloud_pay/serializers.py +++ b/uncloud/uncloud_pay/serializers.py @@ -1,3 +1,4 @@ +from django.contrib.auth import get_user_model from rest_framework import serializers from .models import Bill, Payment @@ -10,3 +11,11 @@ class PaymentSerializer(serializers.ModelSerializer): class Meta: model = Payment fields = ['owner', 'amount', 'source', 'timestamp'] + +class UserSerializer(serializers.ModelSerializer): + class Meta: + model = get_user_model() + fields = ['username', 'email'] + + def get_balance(self, obj): + return 666 diff --git a/uncloud/uncloud_pay/views.py b/uncloud/uncloud_pay/views.py index d824d27..5111f6c 100644 --- a/uncloud/uncloud_pay/views.py +++ b/uncloud/uncloud_pay/views.py @@ -1,9 +1,11 @@ from django.shortcuts import render +from django.contrib.auth import get_user_model from rest_framework import viewsets, permissions, status from rest_framework.response import Response +from rest_framework.decorators import action from .models import Bill, Payment -from .serializers import BillSerializer, PaymentSerializer +from .serializers import BillSerializer, PaymentSerializer, UserSerializer from datetime import datetime ### @@ -38,6 +40,17 @@ class PaymentViewSet(viewsets.ReadOnlyModelViewSet): def get_queryset(self): return Payment.objects.filter(owner=self.request.user) +class UserViewSet(viewsets.ReadOnlyModelViewSet): + serializer_class = UserSerializer + permission_classes = [permissions.IsAuthenticated] + + def get_queryset(self): + return get_user_model().objects.all() + + @action(detail=True) + def balance(self, request): + return Response(status=status.HTTP_204_NO_CONTENT) + ### # Admin views. From b722f30ea5048a305d627a083d0abfd310fa8092 Mon Sep 17 00:00:00 2001 From: Nico Schottelius Date: Thu, 27 Feb 2020 12:42:09 +0100 Subject: [PATCH 14/23] ++doc --- uncloud/README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/uncloud/README.md b/uncloud/README.md index 19896d9..1e71f6f 100644 --- a/uncloud/README.md +++ b/uncloud/README.md @@ -76,3 +76,12 @@ sample values with real values. ### Creating a VM Snapshot + + +## Working Beta APIs + +### Snapshotting + +``` +http -a nicoschottelius:$(pass ungleich.ch/nico.schottelius@ungleich.ch) http://localhost:8000/vm/snapshot/ vm_uuid=$(uuidgen) +``` From 1ff5702ce3cd217f2fd26442c76a466fe558d1ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Floure?= Date: Thu, 27 Feb 2020 12:42:24 +0100 Subject: [PATCH 15/23] Expose Order model --- uncloud/uncloud/urls.py | 2 ++ uncloud/uncloud_pay/serializers.py | 11 +++++++++-- uncloud/uncloud_pay/views.py | 22 ++++++++++++++++++---- 3 files changed, 29 insertions(+), 6 deletions(-) diff --git a/uncloud/uncloud/urls.py b/uncloud/uncloud/urls.py index 358e4c7..9ea7c6a 100644 --- a/uncloud/uncloud/urls.py +++ b/uncloud/uncloud/urls.py @@ -31,11 +31,13 @@ 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') +router.register(r'order', payviews.OrderViewSet, basename='order') router.register(r'payment', payviews.PaymentViewSet, basename='payment') # admin/staff urls router.register(r'admin/bill', payviews.AdminBillViewSet, basename='admin/bill') router.register(r'admin/payment', payviews.AdminPaymentViewSet, basename='admin/payment') +router.register(r'admin/order', payviews.AdminOrderViewSet, basename='admin/order') router.register(r'admin/vmhost', vmviews.VMHostViewSet) router.register(r'admin/opennebula', oneviews.VMViewSet, basename='opennebula') router.register(r'admin/opennebula_raw', oneviews.RawVMViewSet) diff --git a/uncloud/uncloud_pay/serializers.py b/uncloud/uncloud_pay/serializers.py index 5bb22ec..be00a0c 100644 --- a/uncloud/uncloud_pay/serializers.py +++ b/uncloud/uncloud_pay/serializers.py @@ -1,17 +1,24 @@ from django.contrib.auth import get_user_model from rest_framework import serializers -from .models import Bill, Payment +from .models import Bill, Payment, Order class BillSerializer(serializers.ModelSerializer): class Meta: model = Bill - fields = ['owner', 'amount', 'due_date', 'creation_date', 'starting_date', 'ending_date', 'paid'] + fields = ['owner', 'amount', 'due_date', 'creation_date', + 'starting_date', 'ending_date', 'paid'] class PaymentSerializer(serializers.ModelSerializer): class Meta: model = Payment fields = ['owner', 'amount', 'source', 'timestamp'] +class OrderSerializer(serializers.ModelSerializer): + class Meta: + model = Order + fields = ['owner', 'creation_date', 'starting_date', 'ending_date', + 'bill', 'recurring_price', 'one_time_price', 'recurring_period'] + class UserSerializer(serializers.ModelSerializer): class Meta: model = get_user_model() diff --git a/uncloud/uncloud_pay/views.py b/uncloud/uncloud_pay/views.py index 5111f6c..ae88861 100644 --- a/uncloud/uncloud_pay/views.py +++ b/uncloud/uncloud_pay/views.py @@ -4,8 +4,8 @@ from rest_framework import viewsets, permissions, status from rest_framework.response import Response from rest_framework.decorators import action -from .models import Bill, Payment -from .serializers import BillSerializer, PaymentSerializer, UserSerializer +from .models import Bill, Payment, Order +from .serializers import BillSerializer, PaymentSerializer, UserSerializer, OrderSerializer from datetime import datetime ### @@ -40,6 +40,13 @@ class PaymentViewSet(viewsets.ReadOnlyModelViewSet): def get_queryset(self): return Payment.objects.filter(owner=self.request.user) +class OrderViewSet(viewsets.ReadOnlyModelViewSet): + serializer_class = OrderSerializer + permission_classes = [permissions.IsAuthenticated] + + def get_queryset(self): + return Order.objects.filter(owner=self.request.user) + class UserViewSet(viewsets.ReadOnlyModelViewSet): serializer_class = UserSerializer permission_classes = [permissions.IsAuthenticated] @@ -59,7 +66,7 @@ class AdminPaymentViewSet(viewsets.ModelViewSet): permission_classes = [permissions.IsAuthenticated] def get_queryset(self): - return Payment.objects.filter(owner=self.request.user) + return Payment.objects.all() def create(self, request): serializer = self.get_serializer(data=request.data) @@ -74,7 +81,7 @@ class AdminBillViewSet(viewsets.ModelViewSet): permission_classes = [permissions.IsAuthenticated] def get_queryset(self): - return Bill.objects.filter(owner=self.request.user) + return Bill.objects.all() def unpaid(self, request): return Bill.objects.filter(owner=self.request.user, paid=False) @@ -86,3 +93,10 @@ class AdminBillViewSet(viewsets.ModelViewSet): headers = self.get_success_headers(serializer.data) return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers) + +class AdminOrderViewSet(viewsets.ModelViewSet): + serializer_class = OrderSerializer + permission_classes = [permissions.IsAuthenticated] + + def get_queryset(self): + return Order.objects.all() From 70a4fe4d9008b5f6be753d1130e6d331ba457d6f Mon Sep 17 00:00:00 2001 From: Nico Schottelius Date: Thu, 27 Feb 2020 12:45:54 +0100 Subject: [PATCH 16/23] order: serialize all fields --- uncloud/README.md | 6 ++++++ uncloud/uncloud_pay/serializers.py | 3 +-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/uncloud/README.md b/uncloud/README.md index b1d98a5..390a3af 100644 --- a/uncloud/README.md +++ b/uncloud/README.md @@ -82,6 +82,12 @@ sample values with real values. These APIs can be used for internal testing. +### URL Overview + +``` +http -a nicoschottelius:$(pass ungleich.ch/nico.schottelius@ungleich.ch) http://localhost:8000 +``` + ### Snapshotting ``` diff --git a/uncloud/uncloud_pay/serializers.py b/uncloud/uncloud_pay/serializers.py index be00a0c..130f683 100644 --- a/uncloud/uncloud_pay/serializers.py +++ b/uncloud/uncloud_pay/serializers.py @@ -16,8 +16,7 @@ class PaymentSerializer(serializers.ModelSerializer): class OrderSerializer(serializers.ModelSerializer): class Meta: model = Order - fields = ['owner', 'creation_date', 'starting_date', 'ending_date', - 'bill', 'recurring_price', 'one_time_price', 'recurring_period'] + fields = '__all__' class UserSerializer(serializers.ModelSerializer): class Meta: From bd6008462d9eb6823f9db90ed8ee895d474cb1a0 Mon Sep 17 00:00:00 2001 From: Nico Schottelius Date: Thu, 27 Feb 2020 15:29:05 +0100 Subject: [PATCH 17/23] add template for uncloud_net Signed-off-by: Nico Schottelius --- uncloud/uncloud_net/__init__.py | 0 uncloud/uncloud_net/admin.py | 3 +++ uncloud/uncloud_net/apps.py | 5 +++++ uncloud/uncloud_net/migrations/__init__.py | 0 uncloud/uncloud_net/models.py | 3 +++ uncloud/uncloud_net/tests.py | 3 +++ uncloud/uncloud_net/views.py | 3 +++ 7 files changed, 17 insertions(+) create mode 100644 uncloud/uncloud_net/__init__.py create mode 100644 uncloud/uncloud_net/admin.py create mode 100644 uncloud/uncloud_net/apps.py create mode 100644 uncloud/uncloud_net/migrations/__init__.py create mode 100644 uncloud/uncloud_net/models.py create mode 100644 uncloud/uncloud_net/tests.py create mode 100644 uncloud/uncloud_net/views.py diff --git a/uncloud/uncloud_net/__init__.py b/uncloud/uncloud_net/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/uncloud/uncloud_net/admin.py b/uncloud/uncloud_net/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/uncloud/uncloud_net/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/uncloud/uncloud_net/apps.py b/uncloud/uncloud_net/apps.py new file mode 100644 index 0000000..489beb1 --- /dev/null +++ b/uncloud/uncloud_net/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class UncloudNetConfig(AppConfig): + name = 'uncloud_net' diff --git a/uncloud/uncloud_net/migrations/__init__.py b/uncloud/uncloud_net/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/uncloud/uncloud_net/models.py b/uncloud/uncloud_net/models.py new file mode 100644 index 0000000..71a8362 --- /dev/null +++ b/uncloud/uncloud_net/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/uncloud/uncloud_net/tests.py b/uncloud/uncloud_net/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/uncloud/uncloud_net/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/uncloud/uncloud_net/views.py b/uncloud/uncloud_net/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/uncloud/uncloud_net/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. From 288a65f2192a93aaa2660b244be67b9bb8faee4a Mon Sep 17 00:00:00 2001 From: Nico Schottelius Date: Thu, 27 Feb 2020 15:29:15 +0100 Subject: [PATCH 18/23] ++update Signed-off-by: Nico Schottelius --- .../migrations/0002_auto_20200227_1230.py | 18 ++++++++++ .../migrations/0005_auto_20200227_1230.py | 36 +++++++++++++++++++ uncloud/uncloud_vm/models.py | 15 ++++---- uncloud/uncloud_vm/serializers.py | 10 ++++++ uncloud/uncloud_vm/views.py | 13 ++++++- 5 files changed, 83 insertions(+), 9 deletions(-) create mode 100644 uncloud/uncloud_pay/migrations/0002_auto_20200227_1230.py create mode 100644 uncloud/uncloud_vm/migrations/0005_auto_20200227_1230.py diff --git a/uncloud/uncloud_pay/migrations/0002_auto_20200227_1230.py b/uncloud/uncloud_pay/migrations/0002_auto_20200227_1230.py new file mode 100644 index 0000000..0643e9a --- /dev/null +++ b/uncloud/uncloud_pay/migrations/0002_auto_20200227_1230.py @@ -0,0 +1,18 @@ +# Generated by Django 3.0.3 on 2020-02-27 12:30 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('uncloud_pay', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='payment', + name='source', + field=models.CharField(choices=[('wire', 'Wire Transfer'), ('stripe', 'Stripe'), ('voucher', 'Voucher'), ('referral', 'Referral'), ('unknown', 'Unknown')], default='unknown', max_length=256), + ), + ] diff --git a/uncloud/uncloud_vm/migrations/0005_auto_20200227_1230.py b/uncloud/uncloud_vm/migrations/0005_auto_20200227_1230.py new file mode 100644 index 0000000..1bd711b --- /dev/null +++ b/uncloud/uncloud_vm/migrations/0005_auto_20200227_1230.py @@ -0,0 +1,36 @@ +# Generated by Django 3.0.3 on 2020-02-27 12:30 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('uncloud_pay', '0002_auto_20200227_1230'), + ('uncloud_vm', '0004_vmsnapshotproduct'), + ] + + operations = [ + migrations.RemoveField( + model_name='vmsnapshotproduct', + name='vm_uuid', + ), + migrations.AddField( + model_name='vmproduct', + name='order', + field=models.ForeignKey(default=0, editable=False, on_delete=django.db.models.deletion.CASCADE, to='uncloud_pay.Order'), + preserve_default=False, + ), + migrations.AddField( + model_name='vmproduct', + name='status', + field=models.CharField(choices=[('pending', 'Pending'), ('being_created', 'Being created'), ('active', 'Active'), ('deleted', 'Deleted')], default='pending', max_length=256), + ), + migrations.AddField( + model_name='vmsnapshotproduct', + name='vm', + field=models.ForeignKey(default=0, on_delete=django.db.models.deletion.CASCADE, to='uncloud_vm.VMProduct'), + preserve_default=False, + ), + ] diff --git a/uncloud/uncloud_vm/models.py b/uncloud/uncloud_vm/models.py index 12d188e..4ebae25 100644 --- a/uncloud/uncloud_vm/models.py +++ b/uncloud/uncloud_vm/models.py @@ -32,13 +32,7 @@ class VMHost(models.Model): ) -class VMProduct(models.Model): - uuid = models.UUIDField(primary_key=True, - default=uuid.uuid4, - editable=False) - owner = models.ForeignKey(get_user_model(), - on_delete=models.CASCADE, - editable=False) +class VMProduct(Product): vmhost = models.ForeignKey(VMHost, on_delete=models.CASCADE, editable=False, @@ -72,8 +66,12 @@ class OperatingSystemDisk(VMDiskProduct): class VMNetworkCard(models.Model): vm = models.ForeignKey(VMProduct, on_delete=models.CASCADE) + mac_address = models.IntegerField() + ip_address = models.GenericIPAddressField(blank=True, + null=True) + class VMSnapshotProduct(Product): price_per_gb_ssd = 0.35 @@ -83,7 +81,8 @@ class VMSnapshotProduct(Product): gb_ssd = models.FloatField(editable=False) gb_hdd = models.FloatField(editable=False) - vm_uuid = models.UUIDField() + vm = models.ForeignKey(VMProduct, on_delete=models.CASCADE) + #vm_uuid = models.UUIDField() # Need to setup recurring_price and one_time_price and recurring period diff --git a/uncloud/uncloud_vm/serializers.py b/uncloud/uncloud_vm/serializers.py index c1eafe2..b247709 100644 --- a/uncloud/uncloud_vm/serializers.py +++ b/uncloud/uncloud_vm/serializers.py @@ -14,7 +14,17 @@ class VMProductSerializer(serializers.HyperlinkedModelSerializer): model = VMProduct fields = '__all__' + +# def create(self, validated_data): +# return VMSnapshotProduct() + class VMSnapshotProductSerializer(serializers.ModelSerializer): class Meta: model = VMSnapshotProduct fields = '__all__' + + + # verify that vm.owner == user.request + def validate_vm(self, value): + print(value) + return True diff --git a/uncloud/uncloud_vm/views.py b/uncloud/uncloud_vm/views.py index 444d134..7e517f5 100644 --- a/uncloud/uncloud_vm/views.py +++ b/uncloud/uncloud_vm/views.py @@ -11,6 +11,7 @@ from uncloud_pay.models import Order from .serializers import VMHostSerializer, VMProductSerializer, VMSnapshotProductSerializer + import datetime class VMHostViewSet(viewsets.ModelViewSet): @@ -29,7 +30,17 @@ class VMProductViewSet(viewsets.ModelViewSet): def create(self, request): serializer = VMProductSerializer(data=request.data, context={'request': request}) serializer.is_valid(raise_exception=True) - serializer.save(owner=request.user) + # Create order + now = datetime.datetime.now() + order = Order(owner=request.user, + creation_date=now, + starting_date=now, + recurring_price=20, + one_time_price=0, + recurring_period="per_month") + order.save() + + serializer.save(owner=request.user, order=order) return Response(serializer.data) From 89215e47b6daba0b860eb3d389ec3b1109231dde Mon Sep 17 00:00:00 2001 From: Nico Schottelius Date: Fri, 28 Feb 2020 09:34:29 +0100 Subject: [PATCH 19/23] phase in mac --- uncloud/uncloud_net/models.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/uncloud/uncloud_net/models.py b/uncloud/uncloud_net/models.py index 71a8362..6d0c742 100644 --- a/uncloud/uncloud_net/models.py +++ b/uncloud/uncloud_net/models.py @@ -1,3 +1,4 @@ from django.db import models -# Create your models here. +class MACAdress(models.Model): + prefix = 0x420000000000 From bcbd6f6f8339e7489be0b7e126df6f208dd8465a Mon Sep 17 00:00:00 2001 From: Nico Schottelius Date: Sat, 29 Feb 2020 16:45:52 +0100 Subject: [PATCH 20/23] 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) From 6a38e4e0a44576c829685ed503cbca61cbc5b1f5 Mon Sep 17 00:00:00 2001 From: Nico Schottelius Date: Sat, 29 Feb 2020 17:00:13 +0100 Subject: [PATCH 21/23] add url for importing disk image Signed-off-by: Nico Schottelius --- .../migrations/0007_auto_20200229_1559.py | 23 +++++++++++++++++++ uncloud/uncloud_vm/models.py | 9 ++++++-- uncloud/uncloud_vm/serializers.py | 9 ++++++-- uncloud/uncloud_vm/views.py | 10 ++++++-- 4 files changed, 45 insertions(+), 6 deletions(-) create mode 100644 uncloud/uncloud_vm/migrations/0007_auto_20200229_1559.py diff --git a/uncloud/uncloud_vm/migrations/0007_auto_20200229_1559.py b/uncloud/uncloud_vm/migrations/0007_auto_20200229_1559.py new file mode 100644 index 0000000..6e08c0c --- /dev/null +++ b/uncloud/uncloud_vm/migrations/0007_auto_20200229_1559.py @@ -0,0 +1,23 @@ +# Generated by Django 3.0.3 on 2020-02-29 15:59 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('uncloud_vm', '0006_auto_20200229_1545'), + ] + + operations = [ + migrations.AddField( + model_name='vmdiskimageproduct', + name='import_url', + field=models.URLField(blank=True, null=True), + ), + migrations.AlterField( + model_name='vmdiskimageproduct', + name='size_in_gb', + field=models.FloatField(blank=True, null=True), + ), + ] diff --git a/uncloud/uncloud_vm/models.py b/uncloud/uncloud_vm/models.py index b585cb9..f2cbf13 100644 --- a/uncloud/uncloud_vm/models.py +++ b/uncloud/uncloud_vm/models.py @@ -55,7 +55,9 @@ class VMDiskImageProduct(models.Model): """ - uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) + uuid = models.UUIDField(primary_key=True, + default=uuid.uuid4, + editable=False) owner = models.ForeignKey(get_user_model(), on_delete=models.CASCADE, editable=False) @@ -64,7 +66,10 @@ class VMDiskImageProduct(models.Model): is_os_image = models.BooleanField(default=False) is_public = models.BooleanField(default=False) - size_in_gb = models.FloatField() + size_in_gb = models.FloatField(null=True, + blank=True) + import_url = models.URLField(null=True, + blank=True) storage_class = models.CharField(max_length=32, diff --git a/uncloud/uncloud_vm/serializers.py b/uncloud/uncloud_vm/serializers.py index a64fdd0..f8618ee 100644 --- a/uncloud/uncloud_vm/serializers.py +++ b/uncloud/uncloud_vm/serializers.py @@ -3,6 +3,13 @@ from django.contrib.auth import get_user_model from rest_framework import serializers from .models import VMHost, VMProduct, VMSnapshotProduct, VMDiskProduct, VMDiskImageProduct +GB_SSD_PER_DAY=0.012 +GB_HDD_PER_DAY=0.0006 + +GB_SSD_PER_DAY=0.012 +GB_HDD_PER_DAY=0.0006 + + class VMHostSerializer(serializers.ModelSerializer): class Meta: model = VMHost @@ -32,11 +39,9 @@ class VMSnapshotProductSerializer(serializers.ModelSerializer): # verify that vm.owner == user.request def validate_vm(self, value): - 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: diff --git a/uncloud/uncloud_vm/views.py b/uncloud/uncloud_vm/views.py index b9d80f9..851041e 100644 --- a/uncloud/uncloud_vm/views.py +++ b/uncloud/uncloud_vm/views.py @@ -26,7 +26,14 @@ class VMDiskImageProductMineViewSet(viewsets.ModelViewSet): def get_queryset(self): return VMDiskImageProduct.objects.filter(owner=self.request.user) -class VMDiskImageProductPublicViewSet(viewsets.ModelViewSet): + def create(self, request): + serializer = VMProductSerializer(data=request.data, context={'request': request}) + serializer.is_valid(raise_exception=True) + serializer.save(owner=request.user) + return Response(serializer.data) + + +class VMDiskImageProductPublicViewSet(viewsets.ReadOnlyModelViewSet): permission_classes = [permissions.IsAuthenticated] serializer_class = VMDiskImageProductSerializer @@ -55,7 +62,6 @@ class VMProductViewSet(viewsets.ModelViewSet): order.save() serializer.save(owner=request.user, order=order) - return Response(serializer.data) From 5c33bc5c02411778e354b322cdcd258b8b33ffc8 Mon Sep 17 00:00:00 2001 From: Nico Schottelius Date: Sat, 29 Feb 2020 17:57:57 +0100 Subject: [PATCH 22/23] support creating disks Signed-off-by: Nico Schottelius --- uncloud/uncloud/urls.py | 15 +++++++++++- uncloud/uncloud_vm/models.py | 32 +++++++++++++------------ uncloud/uncloud_vm/serializers.py | 2 ++ uncloud/uncloud_vm/views.py | 40 +++++++++++++++++++++++++++++-- 4 files changed, 71 insertions(+), 18 deletions(-) diff --git a/uncloud/uncloud/urls.py b/uncloud/uncloud/urls.py index 40b3b20..02862a1 100644 --- a/uncloud/uncloud/urls.py +++ b/uncloud/uncloud/urls.py @@ -26,14 +26,27 @@ router = routers.DefaultRouter() # user / regular urls router.register(r'vm/snapshot', vmviews.VMSnapshotProductViewSet, basename='vmsnapshotproduct') +router.register(r'vm/disk', vmviews.VMDiskProductViewSet, basename='vmdiskproduct') router.register(r'vm/image/mine', vmviews.VMDiskImageProductMineViewSet, basename='vmdiskimagemineproduct') router.register(r'vm/image/public', vmviews.VMDiskImageProductPublicViewSet, basename='vmdiskimagepublicproduct') +# images the provider provides :-) +# router.register(r'vm/image/official', vmviews.VMDiskImageProductPublicViewSet, basename='vmdiskimagepublicproduct') + + -#router.register(r'vm/disk', vmviews.VMDiskProductViewSet, basename='vmdiskproduct') router.register(r'vm/vm', vmviews.VMProductViewSet, basename='vmproduct') +# TBD +#router.register(r'vm/disk', vmviews.VMDiskProductViewSet, basename='vmdiskproduct') + +# creates VM from os image +#router.register(r'vm/ipv6onlyvm', vmviews.VMProductViewSet, basename='vmproduct') +# ... AND adds IPv4 mapping +#router.register(r'vm/dualstackvm', vmviews.VMProductViewSet, basename='vmproduct') + +# allow vm creation from own images # Pay diff --git a/uncloud/uncloud_vm/models.py b/uncloud/uncloud_vm/models.py index f2cbf13..7aac05b 100644 --- a/uncloud/uncloud_vm/models.py +++ b/uncloud/uncloud_vm/models.py @@ -4,6 +4,16 @@ import uuid from uncloud_pay.models import Product +STATUS_CHOICES = ( + ('pending', 'Pending'), # Initial state + ('creating', 'Creating'), # Creating VM/image/etc. + ('active', 'Active'), # Is usable / active + ('disabled', 'Disabled'), # Is usable, but cannot be used for new things + ('unusable', 'Unusable'), # Has some kind of error + ('deleted', 'Deleted'), # Does not exist anymore, only DB entry as a log +) + +STATUS_DEFAULT='pending' class VMHost(models.Model): uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) @@ -22,13 +32,8 @@ class VMHost(models.Model): status = models.CharField(max_length=32, - choices = ( - ('pending', 'Pending'), - ('active', 'Active'), - ('unusable', 'Unusable'), - ('deleted', 'Deleted'), - ), - default='pending' + choices=STATUS_CHOICES, + default=STATUS_DEFAULT ) @@ -80,13 +85,10 @@ class VMDiskImageProduct(models.Model): default='ssd' ) - # source = models.CharField(max_length=32, - # choices = ( - # ('url', 'HDD'), - # ('ssd', 'SSD'), - # ), - # default='ssd' - # ) + status = models.CharField(max_length=32, + choices=STATUS_CHOICES, + default=STATUS_DEFAULT + ) class VMDiskProduct(models.Model): """ @@ -105,7 +107,7 @@ class VMDiskProduct(models.Model): vm = models.ForeignKey(VMProduct, on_delete=models.CASCADE) image = models.ForeignKey(VMDiskImageProduct, on_delete=models.CASCADE) - size_in_gb = models.FloatField() + size_in_gb = models.FloatField(blank=True) class VMNetworkCard(models.Model): diff --git a/uncloud/uncloud_vm/serializers.py b/uncloud/uncloud_vm/serializers.py index f8618ee..07d6c51 100644 --- a/uncloud/uncloud_vm/serializers.py +++ b/uncloud/uncloud_vm/serializers.py @@ -22,6 +22,8 @@ class VMProductSerializer(serializers.ModelSerializer): fields = '__all__' class VMDiskProductSerializer(serializers.ModelSerializer): +# vm = VMProductSerializer() + class Meta: model = VMDiskProduct fields = '__all__' diff --git a/uncloud/uncloud_vm/views.py b/uncloud/uncloud_vm/views.py index 851041e..62edaa0 100644 --- a/uncloud/uncloud_vm/views.py +++ b/uncloud/uncloud_vm/views.py @@ -5,11 +5,13 @@ from django.shortcuts import get_object_or_404 from rest_framework import viewsets, permissions from rest_framework.response import Response +from rest_framework.exceptions import ValidationError + from .models import VMHost, VMProduct, VMSnapshotProduct, VMDiskProduct, VMDiskImageProduct from uncloud_pay.models import Order -from .serializers import VMHostSerializer, VMProductSerializer, VMSnapshotProductSerializer, VMDiskImageProductSerializer +from .serializers import VMHostSerializer, VMProductSerializer, VMSnapshotProductSerializer, VMDiskImageProductSerializer, VMDiskProductSerializer import datetime @@ -27,8 +29,14 @@ class VMDiskImageProductMineViewSet(viewsets.ModelViewSet): return VMDiskImageProduct.objects.filter(owner=self.request.user) def create(self, request): - serializer = VMProductSerializer(data=request.data, context={'request': request}) + serializer = VMDiskImageProductSerializer(data=request.data, context={'request': request}) serializer.is_valid(raise_exception=True) + + # did not specify size NOR import url? + if not serializer.validated_data['size_in_gb']: + if not serializer.validated_data['import_url']: + raise ValidationError(detail={ 'error_mesage': 'Specify either import_url or size_in_gb' }) + serializer.save(owner=request.user) return Response(serializer.data) @@ -40,6 +48,34 @@ class VMDiskImageProductPublicViewSet(viewsets.ReadOnlyModelViewSet): def get_queryset(self): return VMDiskImageProduct.objects.filter(is_public=True) +class VMDiskProductViewSet(viewsets.ModelViewSet): + """ + Let a user modify their own VMDisks + """ + permission_classes = [permissions.IsAuthenticated] + serializer_class = VMDiskProductSerializer + + def get_queryset(self): + return VMDiskProduct.objects.filter(owner=self.request.user) + + def create(self, request): + serializer = VMDiskProductSerializer(data=request.data, context={'request': request}) + serializer.is_valid(raise_exception=True) + + # get disk size from image, if not specified + if not 'size_in_gb' in serializer.validated_data: + size_in_gb = serializer.validated_data['image'].size_in_gb + else: + size_in_gb = serializer.validated_data['size_in_gb'] + + if size_in_gb < serializer.validated_data['image'].size_in_gb: + raise ValidationError(detail={ 'error_mesage': 'Size is smaller than original image' }) + + + serializer.save(owner=request.user, size_in_gb=size_in_gb) + return Response(serializer.data) + + class VMProductViewSet(viewsets.ModelViewSet): permission_classes = [permissions.IsAuthenticated] From 4115eed2a8398144ef1f1faba628e9a9d2c1edfd Mon Sep 17 00:00:00 2001 From: Nico Schottelius Date: Sat, 29 Feb 2020 17:58:10 +0100 Subject: [PATCH 23/23] +migration Signed-off-by: Nico Schottelius --- .../migrations/0008_auto_20200229_1611.py | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 uncloud/uncloud_vm/migrations/0008_auto_20200229_1611.py diff --git a/uncloud/uncloud_vm/migrations/0008_auto_20200229_1611.py b/uncloud/uncloud_vm/migrations/0008_auto_20200229_1611.py new file mode 100644 index 0000000..8a9be67 --- /dev/null +++ b/uncloud/uncloud_vm/migrations/0008_auto_20200229_1611.py @@ -0,0 +1,23 @@ +# Generated by Django 3.0.3 on 2020-02-29 16:11 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('uncloud_vm', '0007_auto_20200229_1559'), + ] + + operations = [ + migrations.AddField( + model_name='vmdiskimageproduct', + name='status', + field=models.CharField(choices=[('pending', 'Pending'), ('creating', 'Creating'), ('active', 'Active'), ('disabled', 'Disabled'), ('unusable', 'Unusable'), ('deleted', 'Deleted')], default='pending', max_length=32), + ), + migrations.AlterField( + model_name='vmhost', + name='status', + field=models.CharField(choices=[('pending', 'Pending'), ('creating', 'Creating'), ('active', 'Active'), ('disabled', 'Disabled'), ('unusable', 'Unusable'), ('deleted', 'Deleted')], default='pending', max_length=32), + ), + ]