diff --git a/uncloud/README.md b/uncloud/README.md index 6d5f1c8..e0c0d10 100644 --- a/uncloud/README.md +++ b/uncloud/README.md @@ -48,3 +48,18 @@ Installing the postgresql service is os dependent, but some hints: cp `uncloud/secrets_sample.py` to `uncloud/secrets.py` and replace the sample values with real values. + + +## Flows / Orders + +### Creating a VMHost + + + +### Creating a VM + +* Create a VMHost +* Create a VM on a VMHost + + +### Creating a VM Snapshot diff --git a/uncloud/uncloud/settings.py b/uncloud/uncloud/settings.py index 624c9bb..614cd25 100644 --- a/uncloud/uncloud/settings.py +++ b/uncloud/uncloud/settings.py @@ -62,6 +62,7 @@ INSTALLED_APPS = [ 'rest_framework', 'uncloud_api', 'uncloud_auth', + 'uncloud_vm', 'opennebula' ] diff --git a/uncloud/uncloud/urls.py b/uncloud/uncloud/urls.py index 60054c4..1fe8833 100644 --- a/uncloud/uncloud/urls.py +++ b/uncloud/uncloud/urls.py @@ -17,20 +17,26 @@ from django.contrib import admin from django.urls import path, include from rest_framework import routers -from uncloud_api import views +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'users', views.UserViewSet) +router.register(r'users', apiviews.UserViewSet) router.register(r'opennebula', oneviews.VMViewSet, basename='opennebula') router.register(r'opennebula_raw', oneviews.RawVMViewSet) +router.register(r'vmsnapshot', apiviews.VMSnapshotView, basename='vmsnapshot') + +# admin/staff urls +router.register(r'admin/vmhost', vmviews.VMHostViewSet) + # 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), - path('products/', views.ProductsView.as_view(), name='products'), + path('products/', apiviews.ProductsView.as_view(), name='products'), path('api-auth/', include('rest_framework.urls', namespace='rest_framework')) ] diff --git a/uncloud/uncloud_api/migrations/0003_auto_20200225_1950.py b/uncloud/uncloud_api/migrations/0003_auto_20200225_1950.py new file mode 100644 index 0000000..be7624c --- /dev/null +++ b/uncloud/uncloud_api/migrations/0003_auto_20200225_1950.py @@ -0,0 +1,36 @@ +# 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/models.py b/uncloud/uncloud_api/models.py index 6affaa3..acc3c63 100644 --- a/uncloud/uncloud_api/models.py +++ b/uncloud/uncloud_api/models.py @@ -34,7 +34,8 @@ from django.contrib.auth import get_user_model 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) + on_delete=models.CASCADE, + editable=False) # override these fields by default @@ -52,8 +53,8 @@ class Product(models.Model): ) # This is calculated by each product and saved in the DB - recurring_price = models.FloatField() - one_time_price = models.FloatField() + recurring_price = models.FloatField(editable=False) + one_time_price = models.FloatField(editable=False) @@ -69,14 +70,13 @@ class VMSnapshotProduct(Product): price_per_gb_hdd = 1.5/100 # This we need to get from the VM - gb_ssd = models.FloatField() - gb_hdd = models.FloatField() + gb_ssd = models.FloatField(editable=False) + gb_hdd = models.FloatField(editable=False) - vm_uuid = models.UUIDField(default=uuid.uuid4, editable=False) + vm_uuid = models.UUIDField() # Need to setup recurring_price and one_time_price and recurring period - sample_ssd = 10 sample_hdd = 100 @@ -137,13 +137,3 @@ class Feature(models.Model): def __str__(self): return "'{}' - '{}'".format(self.product, self.name) - - -# 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) - -# product = models.ForeignKey(Product, -# on_delete=models.CASCADE) diff --git a/uncloud/uncloud_api/serializers.py b/uncloud/uncloud_api/serializers.py index 1573bf0..a3a8386 100644 --- a/uncloud/uncloud_api/serializers.py +++ b/uncloud/uncloud_api/serializers.py @@ -3,17 +3,25 @@ from django.contrib.auth import get_user_model from rest_framework import serializers +from .models import VMSnapshotProduct + class UserSerializer(serializers.HyperlinkedModelSerializer): class Meta: model = get_user_model() fields = ['url', 'username', 'email', 'groups'] - class GroupSerializer(serializers.HyperlinkedModelSerializer): class Meta: model = Group fields = ['url', 'name'] -class VMSnapshotSerializer(serializers.Serializer): - pass +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 c8ffca7..b71b3d2 100644 --- a/uncloud/uncloud_api/views.py +++ b/uncloud/uncloud_api/views.py @@ -3,14 +3,21 @@ from django.contrib.auth import get_user_model from django.contrib.auth.models import Group from rest_framework import viewsets, permissions, generics -from .serializers import UserSerializer, GroupSerializer + 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 + + import inspect import sys import re + + class UserViewSet(viewsets.ModelViewSet): """ @@ -27,10 +34,40 @@ class UserViewSet(viewsets.ModelViewSet): # DEL /vm/snapshot/ => delete # create-list -> get, post => ListCreateAPIView # del on other! -class VMSnapshotView(generics.ListCreateAPIView): - #lookup_field = 'uuid' +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) + # Next: create /order/ urls # Next: strip off "Product" at the end diff --git a/uncloud/uncloud_vm/migrations/0001_initial.py b/uncloud/uncloud_vm/migrations/0001_initial.py new file mode 100644 index 0000000..dc4d657 --- /dev/null +++ b/uncloud/uncloud_vm/migrations/0001_initial.py @@ -0,0 +1,75 @@ +# 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 +import uuid + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='VMDiskProduct', + fields=[ + ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('size_in_gb', models.FloatField()), + ('storage_class', models.CharField(choices=[('hdd', 'HDD'), ('ssd', 'SSD')], default='ssd', max_length=32)), + ], + ), + migrations.CreateModel( + name='VMHost', + fields=[ + ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('hostname', models.CharField(max_length=253)), + ('physical_cores', models.IntegerField()), + ('usable_cores', models.IntegerField()), + ('usable_ram_in_gb', models.FloatField()), + ('status', models.CharField(choices=[('pending', 'Pending'), ('active', 'Active'), ('unusable', 'Unusable')], default='pending', max_length=32)), + ], + ), + migrations.CreateModel( + name='VMProduct', + fields=[ + ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('cores', models.IntegerField()), + ('ram_in_gb', models.FloatField()), + ('owner', models.ForeignKey(editable=False, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ('vmhost', models.ForeignKey(editable=False, on_delete=django.db.models.deletion.CASCADE, to='uncloud_vm.VMHost')), + ], + ), + migrations.CreateModel( + name='OperatingSystemDisk', + fields=[ + ('vmdiskproduct_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='uncloud_vm.VMDiskProduct')), + ('os_name', models.CharField(max_length=128)), + ], + bases=('uncloud_vm.vmdiskproduct',), + ), + migrations.CreateModel( + name='VMWithOSProduct', + fields=[ + ('vmproduct_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='uncloud_vm.VMProduct')), + ], + bases=('uncloud_vm.vmproduct',), + ), + migrations.CreateModel( + name='VMNetworkCard', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('mac_address', models.IntegerField()), + ('vm', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='uncloud_vm.VMProduct')), + ], + ), + migrations.AddField( + model_name='vmdiskproduct', + name='vm', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='uncloud_vm.VMProduct'), + ), + ] diff --git a/uncloud/uncloud_vm/migrations/0002_auto_20200225_1952.py b/uncloud/uncloud_vm/migrations/0002_auto_20200225_1952.py new file mode 100644 index 0000000..46a207b --- /dev/null +++ b/uncloud/uncloud_vm/migrations/0002_auto_20200225_1952.py @@ -0,0 +1,38 @@ +# Generated by Django 3.0.3 on 2020-02-25 19:52 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('uncloud_vm', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='vmhost', + name='hostname', + field=models.CharField(max_length=253, unique=True), + ), + migrations.AlterField( + model_name='vmhost', + name='physical_cores', + field=models.IntegerField(default=0), + ), + migrations.AlterField( + model_name='vmhost', + name='status', + field=models.CharField(choices=[('pending', 'Pending'), ('active', 'Active'), ('unusable', 'Unusable'), ('deleted', 'Deleted')], default='pending', max_length=32), + ), + migrations.AlterField( + model_name='vmhost', + name='usable_cores', + field=models.IntegerField(default=0), + ), + migrations.AlterField( + model_name='vmhost', + name='usable_ram_in_gb', + field=models.FloatField(default=0), + ), + ] diff --git a/uncloud/uncloud_vm/migrations/__init__.py b/uncloud/uncloud_vm/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/uncloud/uncloud_vm/models.py b/uncloud/uncloud_vm/models.py index faf61b0..f79caf3 100644 --- a/uncloud/uncloud_vm/models.py +++ b/uncloud/uncloud_vm/models.py @@ -1,20 +1,22 @@ from django.db import models +from django.contrib.auth import get_user_model +import uuid class VMHost(models.Model): uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) # 253 is the maximum DNS name length - hostname = models.CharField(max_length=253) + hostname = models.CharField(max_length=253, unique=True) # indirectly gives a maximum number of cores / VM - f.i. 32 - physical_cores = models.IntegerField() + physical_cores = models.IntegerField(default=0) # determines the maximum usable cores - f.i. 320 if you overbook by a factor of 10 - usable_cores = models.IntegerField() + usable_cores = models.IntegerField(default=0) # ram that can be used of the server - usable_ram_in_gb = models.FloatField() + usable_ram_in_gb = models.FloatField(default=0) status = models.CharField(max_length=32, @@ -22,24 +24,33 @@ class VMHost(models.Model): ('pending', 'Pending'), ('active', 'Active'), ('unusable', 'Unusable'), + ('deleted', 'Deleted'), ), default='pending' ) -class VM(models.Model): - uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) - owner = models.ForeignKey(get_user_model(), on_delete=models.CASCADE) +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) + vmhost = models.ForeignKey(VMHost, + on_delete=models.CASCADE, + editable=False) cores = models.IntegerField() ram_in_gb = models.FloatField() - vmhost = models.ForeignKey(VMHost, on_delete=models.CASCADE) +class VMWithOSProduct(VMProduct): + pass -class VMDisk(models.Model): +class VMDiskProduct(models.Model): uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) - vm = models.ForeignKey(VM, on_delete=models.CASCADE) + vm = models.ForeignKey(VMProduct, on_delete=models.CASCADE) size_in_gb = models.FloatField() storage_class = models.CharField(max_length=32, @@ -49,3 +60,12 @@ class VMDisk(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) + + +class VMNetworkCard(models.Model): + vm = models.ForeignKey(VMProduct, on_delete=models.CASCADE) + mac_address = models.IntegerField() diff --git a/uncloud/uncloud_vm/serializers.py b/uncloud/uncloud_vm/serializers.py new file mode 100644 index 0000000..1279df2 --- /dev/null +++ b/uncloud/uncloud_vm/serializers.py @@ -0,0 +1,9 @@ +from django.contrib.auth import get_user_model + +from rest_framework import serializers +from .models import VMHost + +class VMHostSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = VMHost + fields = '__all__' diff --git a/uncloud/uncloud_vm/views.py b/uncloud/uncloud_vm/views.py index aa5855c..7b4d7a2 100644 --- a/uncloud/uncloud_vm/views.py +++ b/uncloud/uncloud_vm/views.py @@ -1,24 +1,18 @@ from django.shortcuts import render - from django.contrib.auth.models import User from django.shortcuts import get_object_or_404 -from myapps.serializers import UserSerializer -from rest_framework import viewsets + +from rest_framework import viewsets, permissions from rest_framework.response import Response + from opennebula.models import VM as OpenNebulaVM -class VMViewSet(viewsets.ViewSet): - def list(self, request): - queryset = User.objects.all() - serializer = UserSerializer(queryset, many=True) - return Response(serializer.data) +from .models import VMHost +from .serializers import VMHostSerializer - def retrieve(self, request, pk=None): - queryset = User.objects.all() - user = get_object_or_404(queryset, pk=pk) - serializer = UserSerializer(user) - return Response(serializer.data) - - permission_classes = [permissions.IsAuthenticated] +class VMHostViewSet(viewsets.ModelViewSet): + serializer_class = VMHostSerializer + queryset = VMHost.objects.all() + permission_classes = [permissions.IsAdminUser]