diff --git a/uncloud/uncloud/urls.py b/uncloud/uncloud/urls.py index 1fe8833..23392c5 100644 --- a/uncloud/uncloud/urls.py +++ b/uncloud/uncloud/urls.py @@ -23,20 +23,22 @@ from uncloud_vm import views as vmviews from opennebula import views as oneviews router = routers.DefaultRouter() -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') + +router.register(r'user', apiviews.UserViewSet, basename='user') + +router.register(r'vm/snapshot', apiviews.VMSnapshotView, basename='VMSnapshot') +router.register(r'vm/vm', vmviews.VMProductViewSet, basename='vmproduct') # admin/staff urls router.register(r'admin/vmhost', vmviews.VMHostViewSet) +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), - path('products/', apiviews.ProductsView.as_view(), name='products'), - path('api-auth/', include('rest_framework.urls', namespace='rest_framework')) + 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 a3a8386..7dc3686 100644 --- a/uncloud/uncloud_api/serializers.py +++ b/uncloud/uncloud_api/serializers.py @@ -5,11 +5,10 @@ from rest_framework import serializers from .models import VMSnapshotProduct - -class UserSerializer(serializers.HyperlinkedModelSerializer): +class UserSerializer(serializers.ModelSerializer): class Meta: model = get_user_model() - fields = ['url', 'username', 'email', 'groups'] + fields = ['url', 'username', 'email'] class GroupSerializer(serializers.HyperlinkedModelSerializer): class Meta: diff --git a/uncloud/uncloud_api/views.py b/uncloud/uncloud_api/views.py index b71b3d2..eb4cc77 100644 --- a/uncloud/uncloud_api/views.py +++ b/uncloud/uncloud_api/views.py @@ -17,18 +17,6 @@ import sys import re - -class UserViewSet(viewsets.ModelViewSet): - - """ - API endpoint that allows users to be viewed or edited. - """ - queryset = get_user_model().objects.all().order_by('-date_joined') - serializer_class = UserSerializer - - permission_classes = [permissions.IsAuthenticated] - - # POST /vm/snapshot/ vmuuid=... => create snapshot, returns snapshot uuid # GET /vm/snapshot => list # DEL /vm/snapshot/ => delete @@ -69,23 +57,38 @@ class VMSnapshotView(viewsets.ViewSet): return Response(serializer.data) -# Next: create /order/ urls -# Next: strip off "Product" at the end -class ProductsView(APIView): - def get(self, request, format=None): - clsmembers = inspect.getmembers(sys.modules['uncloud_api.models'], 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() - } - ) + +# 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)) - return Response(products) +# 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 diff --git a/uncloud/uncloud_vm/management/commands/schedulevms.py b/uncloud/uncloud_vm/management/commands/schedulevms.py new file mode 100644 index 0000000..836e100 --- /dev/null +++ b/uncloud/uncloud_vm/management/commands/schedulevms.py @@ -0,0 +1,21 @@ +import json + +import uncloud.secrets as secrets + +from django.core.management.base import BaseCommand +from django.contrib.auth import get_user_model + +from uncloud_vm.models import VMProduct, VMHost + +class Command(BaseCommand): + help = 'Select VM Host for VMs' + + def add_arguments(self, parser): + pass + + def handle(self, *args, **options): + pending_vms = VMProduct.objects.filter(vmhost__isnull=True) + vmhosts = VMHost.objects.filter(status='active') + for vm in pending_vms: + print(vm) + # FIXME: implement smart placement diff --git a/uncloud/uncloud_vm/management/commands/vmhealth.py b/uncloud/uncloud_vm/management/commands/vmhealth.py new file mode 100644 index 0000000..6109af7 --- /dev/null +++ b/uncloud/uncloud_vm/management/commands/vmhealth.py @@ -0,0 +1,24 @@ +import json + +from django.core.management.base import BaseCommand +from django.contrib.auth import get_user_model + +from uncloud_vm.models import VMProduct, VMHost + +class Command(BaseCommand): + help = 'Check health of VMs and VMHosts' + + def add_arguments(self, parser): + pass + + def handle(self, *args, **options): + pending_vms = VMProduct.objects.filter(vmhost__isnull=True) + vmhosts = VMHost.objects.filter(status='active') + + # 1. Check that all active hosts reported back N seconds ago + # 2. Check that no VM is running on a dead host + # 3. Migrate VMs if necessary + # 4. Check that no VMs have been pending for longer than Y seconds + + + print("Nothing is good, you should implement me") diff --git a/uncloud/uncloud_vm/migrations/0003_auto_20200225_2028.py b/uncloud/uncloud_vm/migrations/0003_auto_20200225_2028.py new file mode 100644 index 0000000..a4e5976 --- /dev/null +++ b/uncloud/uncloud_vm/migrations/0003_auto_20200225_2028.py @@ -0,0 +1,19 @@ +# Generated by Django 3.0.3 on 2020-02-25 20:28 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('uncloud_vm', '0002_auto_20200225_1952'), + ] + + operations = [ + migrations.AlterField( + model_name='vmproduct', + name='vmhost', + field=models.ForeignKey(blank=True, editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, to='uncloud_vm.VMHost'), + ), + ] diff --git a/uncloud/uncloud_vm/models.py b/uncloud/uncloud_vm/models.py index f79caf3..f4b68dd 100644 --- a/uncloud/uncloud_vm/models.py +++ b/uncloud/uncloud_vm/models.py @@ -39,7 +39,9 @@ class VMProduct(models.Model): editable=False) vmhost = models.ForeignKey(VMHost, on_delete=models.CASCADE, - editable=False) + editable=False, + blank=True, + null=True) cores = models.IntegerField() ram_in_gb = models.FloatField() diff --git a/uncloud/uncloud_vm/serializers.py b/uncloud/uncloud_vm/serializers.py index 1279df2..4154aee 100644 --- a/uncloud/uncloud_vm/serializers.py +++ b/uncloud/uncloud_vm/serializers.py @@ -1,9 +1,15 @@ from django.contrib.auth import get_user_model from rest_framework import serializers -from .models import VMHost +from .models import VMHost, VMProduct class VMHostSerializer(serializers.HyperlinkedModelSerializer): class Meta: model = VMHost fields = '__all__' + + +class VMProductSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = VMProduct + fields = '__all__' diff --git a/uncloud/uncloud_vm/views.py b/uncloud/uncloud_vm/views.py index 7b4d7a2..91e81e1 100644 --- a/uncloud/uncloud_vm/views.py +++ b/uncloud/uncloud_vm/views.py @@ -6,13 +6,24 @@ from django.shortcuts import get_object_or_404 from rest_framework import viewsets, permissions from rest_framework.response import Response - -from opennebula.models import VM as OpenNebulaVM - -from .models import VMHost -from .serializers import VMHostSerializer +from .models import VMHost, VMProduct +from .serializers import VMHostSerializer, VMProductSerializer class VMHostViewSet(viewsets.ModelViewSet): serializer_class = VMHostSerializer queryset = VMHost.objects.all() permission_classes = [permissions.IsAdminUser] + +class VMProductViewSet(viewsets.ModelViewSet): + permission_classes = [permissions.IsAuthenticated] + serializer_class = VMProductSerializer + + def get_queryset(self): + return VMProduct.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)