From dc34c0ecd48719b5c7cb17c39891ef1e9c179ffb Mon Sep 17 00:00:00 2001 From: Ahmed Bilal <49-ahmedbilal@users.noreply.code.ungleich.ch> Date: Sat, 22 Feb 2020 07:32:52 +0100 Subject: [PATCH] Merge nico/meow-pay into ahmedbilal/meow-pay --- .../opennebula/management/commands/syncvm.py | 8 +- nicohack202002/uncloud/requirements.txt | 5 + .../uncloud/uncloud/secrets_sample.py | 17 +++ nicohack202002/uncloud/uncloud/urls.py | 2 + .../uncloud_api/management/__init__.py | 0 .../management/commands/__init__.py | 0 .../uncloud_api/management/commands/hack.py | 26 +++++ .../management/commands/snapshot.py | 29 +++++ nicohack202002/uncloud/uncloud_api/models.py | 103 ++++++++++++++---- .../uncloud/uncloud_api/serializers.py | 3 + nicohack202002/uncloud/uncloud_api/views.py | 50 ++++++++- notes-nico.org | 9 ++ plan.org | 6 + 13 files changed, 232 insertions(+), 26 deletions(-) create mode 100644 nicohack202002/uncloud/requirements.txt create mode 100644 nicohack202002/uncloud/uncloud/secrets_sample.py create mode 100644 nicohack202002/uncloud/uncloud_api/management/__init__.py create mode 100644 nicohack202002/uncloud/uncloud_api/management/commands/__init__.py create mode 100644 nicohack202002/uncloud/uncloud_api/management/commands/hack.py create mode 100644 nicohack202002/uncloud/uncloud_api/management/commands/snapshot.py create mode 100644 plan.org diff --git a/nicohack202002/uncloud/opennebula/management/commands/syncvm.py b/nicohack202002/uncloud/opennebula/management/commands/syncvm.py index 5ea451d..205b066 100644 --- a/nicohack202002/uncloud/opennebula/management/commands/syncvm.py +++ b/nicohack202002/uncloud/opennebula/management/commands/syncvm.py @@ -9,8 +9,7 @@ from xmltodict import parse from opennebula.models import VM as VMModel -OCA_SESSION_STRING = os.environ.get('OCASECRETS', '') - +import uncloud.secrets class Command(BaseCommand): help = 'Syncronize VM information from OpenNebula' @@ -19,9 +18,9 @@ class Command(BaseCommand): pass def handle(self, *args, **options): - with RPCClient('https://opennebula.ungleich.ch:2634/RPC2') as rpc_client: + with RPCClient(uncloud.secrets.OPENNEBULA_URL) as rpc_client: success, response, *_ = rpc_client.one.vmpool.infoextended( - OCA_SESSION_STRING, -2, -1, -1, -1 + uncloud.secrets.OPENNEBULA_USER_PASS, -2, -1, -1, -1 ) if success: vms = json.loads(json.dumps(parse(response)))['VM_POOL']['VM'] @@ -37,4 +36,3 @@ class Command(BaseCommand): vm_object.save() else: print(response) - diff --git a/nicohack202002/uncloud/requirements.txt b/nicohack202002/uncloud/requirements.txt new file mode 100644 index 0000000..11ab309 --- /dev/null +++ b/nicohack202002/uncloud/requirements.txt @@ -0,0 +1,5 @@ +django +djangorestframework +django-auth-ldap +stripe +xmltodict diff --git a/nicohack202002/uncloud/uncloud/secrets_sample.py b/nicohack202002/uncloud/uncloud/secrets_sample.py new file mode 100644 index 0000000..d145124 --- /dev/null +++ b/nicohack202002/uncloud/uncloud/secrets_sample.py @@ -0,0 +1,17 @@ + + + + + + + + + +# Live/test key from stripe +STRIPE_KEY="" + +# XML-RPC interface of opennebula +OPENNEBULA_URL='https://opennebula.ungleich.ch:2634/RPC2' + +# user:pass for accessing opennebula +OPENNEBULA_USER_PASS='user:password' diff --git a/nicohack202002/uncloud/uncloud/urls.py b/nicohack202002/uncloud/uncloud/urls.py index f5804c9..c7ce9b6 100644 --- a/nicohack202002/uncloud/uncloud/urls.py +++ b/nicohack202002/uncloud/uncloud/urls.py @@ -30,7 +30,9 @@ router.register(r'groups', views.GroupViewSet) urlpatterns = [ path('', include(router.urls)), path('admin/', admin.site.urls), + path('products/', views.ProductsView.as_view(), name='products'), path('api-auth/', include('rest_framework.urls', namespace='rest_framework')), path('vm/list/', oneviews.VMList.as_view(), name='vm_list'), path('vm/detail//', oneviews.VMDetail.as_view(), name='vm_detail'), + ] diff --git a/nicohack202002/uncloud/uncloud_api/management/__init__.py b/nicohack202002/uncloud/uncloud_api/management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/nicohack202002/uncloud/uncloud_api/management/commands/__init__.py b/nicohack202002/uncloud/uncloud_api/management/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/nicohack202002/uncloud/uncloud_api/management/commands/hack.py b/nicohack202002/uncloud/uncloud_api/management/commands/hack.py new file mode 100644 index 0000000..e129952 --- /dev/null +++ b/nicohack202002/uncloud/uncloud_api/management/commands/hack.py @@ -0,0 +1,26 @@ +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/nicohack202002/uncloud/uncloud_api/management/commands/snapshot.py b/nicohack202002/uncloud/uncloud_api/management/commands/snapshot.py new file mode 100644 index 0000000..41d0e38 --- /dev/null +++ b/nicohack202002/uncloud/uncloud_api/management/commands/snapshot.py @@ -0,0 +1,29 @@ +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/nicohack202002/uncloud/uncloud_api/models.py b/nicohack202002/uncloud/uncloud_api/models.py index 9d4291a..fafefe6 100644 --- a/nicohack202002/uncloud/uncloud_api/models.py +++ b/nicohack202002/uncloud/uncloud_api/models.py @@ -3,28 +3,89 @@ 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) - name = models.CharField(max_length=256) + # override these fields by default + description = "" + recurring_period = "not_recurring" - recurring_period = models.CharField(max_length=256, - choices = ( - ("per_year", "Per Year"), - ("per_month", "Per Month"), - ("per_week", "Per Week"), - ("per_day", "Per Day"), - ("per_hour", "Per Hour"), - ("not_recurring", "Not recurring") - ), - default="not_recurring" - ) + status = models.CharField(max_length=256, + choices = ( + ('pending', 'Pending'), + ('being_created', 'Being created'), + ('created_active', 'Created'), + ('deleted', 'Deleted') + ) def __str__(self): return "{}".format(self.name) +class VMSnapshotProduct(Product): + price_per_gb_ssd = 0.35 + price_per_gb_hdd = 1.5/100 + + 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()) + + gb_ssd = models.FloatField() + gb_hdd = models.FloatField() + + class Feature(models.Model): uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) @@ -35,6 +96,15 @@ class Feature(models.Model): 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 + def __str__(self): return "'{}' - '{}'".format(self.product, self.name) @@ -49,10 +119,5 @@ class Order(models.Model): on_delete=models.CASCADE) -class OrderReference(models.Model): - """ - An order can references another product / relate to it. - This model is used for the relation - """ - +class VMSnapshotOrder(Order): pass diff --git a/nicohack202002/uncloud/uncloud_api/serializers.py b/nicohack202002/uncloud/uncloud_api/serializers.py index 57532f2..1573bf0 100644 --- a/nicohack202002/uncloud/uncloud_api/serializers.py +++ b/nicohack202002/uncloud/uncloud_api/serializers.py @@ -14,3 +14,6 @@ class GroupSerializer(serializers.HyperlinkedModelSerializer): class Meta: model = Group fields = ['url', 'name'] + +class VMSnapshotSerializer(serializers.Serializer): + pass diff --git a/nicohack202002/uncloud/uncloud_api/views.py b/nicohack202002/uncloud/uncloud_api/views.py index 88e0543..68963ff 100644 --- a/nicohack202002/uncloud/uncloud_api/views.py +++ b/nicohack202002/uncloud/uncloud_api/views.py @@ -2,9 +2,11 @@ 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 - +from rest_framework import viewsets, permissions, generics from .serializers import UserSerializer, GroupSerializer +from rest_framework.views import APIView +from rest_framework.response import Response + class CreditCardViewSet(viewsets.ModelViewSet): @@ -35,3 +37,47 @@ class GroupViewSet(viewsets.ModelViewSet): serializer_class = GroupSerializer permission_classes = [permissions.IsAuthenticated] + +class GroupViewSet(viewsets.ModelViewSet): + """ + API endpoint that allows groups to be viewed or edited. + """ + queryset = Group.objects.all() + serializer_class = GroupSerializer + + permission_classes = [permissions.IsAuthenticated] + + +# 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(generics.ListCreateAPIView): + #lookup_field = 'uuid' + permission_classes = [permissions.IsAuthenticated] + +import inspect +import sys +import re + +# 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() + } + ) + + + return Response(products) diff --git a/notes-nico.org b/notes-nico.org index 21102f9..93e0c00 100644 --- a/notes-nico.org +++ b/notes-nico.org @@ -1,5 +1,14 @@ * snapshot feature ** product: vm-snapshot +** flow +*** list all my VMs +**** get the uuid of the VM I want to take a snapshot of +*** request a snapshot +``` +vmuuid=$(http nicocustomer +http -a nicocustomer:xxx http://uncloud.ch/vm/create_snapshot uuid= +password=... +``` * steps ** DONE authenticate via ldap CLOSED: [2020-02-20 Thu 19:05] diff --git a/plan.org b/plan.org new file mode 100644 index 0000000..9f172c2 --- /dev/null +++ b/plan.org @@ -0,0 +1,6 @@ +* TODO register CC +* TODO list products +* ahmed +** schemas +*** field: is_valid? - used by schemas +*** definition of a "schema"