From 130c00c8ee38baca2453d7dcb248f832f8a7ca6a Mon Sep 17 00:00:00 2001 From: Modulos Date: Fri, 12 May 2017 12:07:05 +0200 Subject: [PATCH] API Integration Please review carefully. --- hosting/admin.py | 95 +------ hosting/forms.py | 34 +-- hosting/migrations/0038_auto_20170512_1006.py | 34 +++ hosting/mixins.py | 23 +- hosting/models.py | 254 +----------------- hosting/templates/hosting/bill_detail.html | 2 +- .../hosting/create_virtual_machine.html | 12 +- hosting/templates/hosting/order_detail.html | 12 +- hosting/templates/hosting/payment.html | 11 +- .../hosting/virtual_machine_detail.html | 75 +----- .../templates/hosting/virtual_machines.html | 9 +- hosting/views.py | 203 ++++++-------- opennebula_api/migrations/0001_initial.py | 40 --- .../migrations/0002_auto_20170511_0246.py | 20 -- .../migrations/0003_virtualmachine_owner.py | 24 -- opennebula_api/models.py | 155 +++-------- opennebula_api/permissions.py | 10 - opennebula_api/serializers.py | 141 +++++----- opennebula_api/views.py | 46 +++- 19 files changed, 310 insertions(+), 890 deletions(-) create mode 100644 hosting/migrations/0038_auto_20170512_1006.py delete mode 100644 opennebula_api/migrations/0001_initial.py delete mode 100644 opennebula_api/migrations/0002_auto_20170511_0246.py delete mode 100644 opennebula_api/migrations/0003_virtualmachine_owner.py delete mode 100644 opennebula_api/permissions.py diff --git a/hosting/admin.py b/hosting/admin.py index ee4e7415..4f2f15e1 100644 --- a/hosting/admin.py +++ b/hosting/admin.py @@ -4,99 +4,8 @@ from django.core.urlresolvers import reverse from utils.mailer import BaseEmail -from .forms import HostingOrderAdminForm -from .models import VirtualMachineType, VirtualMachinePlan, HostingOrder, \ - ManageVM, HostingBill -from .opennebula_functions import HostingManageVMAdmin +from .models import HostingOrder, HostingBill -class HostingOrderAdmin(admin.ModelAdmin): - # fields = ('slug', 'imdb_link', 'start', 'finish', 'added_by') - list_display = ('id', 'created_at', 'plan', 'user') - search_fields = ['vm_plan__id', 'customer__user__email'] - - def save_model(self, request, obj, form, change): - if not change: - customer = form.cleaned_data.get('customer') - - # Get and set billing address from the lastest charged order - last_order = HostingOrder.objects.filter(customer=customer).latest('id') - billing_address = last_order.billing_address - obj.billing_address = billing_address - - charge = form.cleaned_data.get('charge') - # Associate an order with a stripe payment - obj.set_stripe_charge(charge) - - # If the Stripe payment was successed, set order status approved - obj.set_approved() - - # Assigning permissions - obj.assign_permissions(customer.user) - - context = { - 'order': obj, - 'vm': obj.vm_plan, - 'base_url': "{0}://{1}".format(request.scheme, request.get_host()) - } - email_data = { - 'subject': 'Your VM plan has been charged', - 'to': obj.customer.user.email, - 'context': context, - 'template_name': 'vm_charged', - 'template_path': 'hosting/emails/' - } - email = BaseEmail(**email_data) - email.send() - - obj.save() - return obj - - def get_form(self, request, obj=None, **kwargs): - if obj is None: - kwargs['form'] = HostingOrderAdminForm - return super(HostingOrderAdmin, self).get_form(request, obj, **kwargs) - - def user(self, obj): - email = obj.customer.user.email - user_url = reverse("admin:membership_customuser_change", args=[obj.customer.user.id]) - return format_html("{email}", url=user_url, email=email) - - def plan(self, obj): - vm_name = obj.vm_plan.name - vm_url = reverse("admin:hosting_virtualmachineplan_change", args=[obj.vm_plan.id]) - return format_html("{vm_name}", url=vm_url, vm_name=vm_name) - - plan.short_description = "Virtual Machine Plan" - - -class VirtualMachinePlanAdmin(admin.ModelAdmin): - list_display = ('name', 'id', 'email') - - def email(self, obj): - return obj.hosting_orders.latest('id').customer.user.email - - def save_model(self, request, obj, form, change): - email = self.email(obj) - if 'status' in form.changed_data: - context = { - 'vm': obj, - 'base_url': "{0}://{1}".format(request.scheme, request.get_host()) - } - email_data = { - 'subject': 'Your VM has been activated', - 'to': email, - 'context': context, - 'template_name': 'vm_status_changed', - 'template_path': 'hosting/emails/' - } - email = BaseEmail(**email_data) - email.send() - obj.save() - - -admin.site.register(HostingOrder, HostingOrderAdmin) -admin.site.register(VirtualMachineType) -admin.site.register(VirtualMachinePlan, VirtualMachinePlanAdmin) -admin.site.register(ManageVM, HostingManageVMAdmin) +admin.site.register(HostingOrder) admin.site.register(HostingBill) diff --git a/hosting/forms.py b/hosting/forms.py index 7323bdf3..a8a60678 100644 --- a/hosting/forms.py +++ b/hosting/forms.py @@ -7,39 +7,7 @@ from django.contrib.auth import authenticate from utils.stripe_utils import StripeUtils -from .models import HostingOrder, VirtualMachinePlan, UserHostingKey - - -class HostingOrderAdminForm(forms.ModelForm): - - class Meta: - model = HostingOrder - fields = ['vm_plan', 'customer'] - - def clean(self): - customer = self.cleaned_data.get('customer') - vm_plan = self.cleaned_data.get('vm_plan') - - if vm_plan.status == VirtualMachinePlan.CANCELED_STATUS: - raise forms.ValidationError("""You can't make a charge over - a canceled virtual machine plan""") - - if not customer: - raise forms.ValidationError("""You need select a costumer""") - - # Make a charge to the customer - stripe_utils = StripeUtils() - charge_response = stripe_utils.make_charge(customer=customer.stripe_id, - amount=vm_plan.price) - charge = charge_response.get('response_object') - if not charge: - raise forms.ValidationError(charge_response.get('error')) - - self.cleaned_data.update({ - 'charge': charge - }) - return self.cleaned_data - +from .models import HostingOrder, UserHostingKey class HostingUserLoginForm(forms.Form): diff --git a/hosting/migrations/0038_auto_20170512_1006.py b/hosting/migrations/0038_auto_20170512_1006.py new file mode 100644 index 00000000..716faa38 --- /dev/null +++ b/hosting/migrations/0038_auto_20170512_1006.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2017-05-12 10:06 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('hosting', '0037_merge'), + ] + + operations = [ + migrations.RemoveField( + model_name='virtualmachineplan', + name='vm_type', + ), + migrations.RemoveField( + model_name='hostingorder', + name='vm_plan', + ), + migrations.AddField( + model_name='hostingorder', + name='vm_id', + field=models.IntegerField(default=0), + ), + migrations.DeleteModel( + name='VirtualMachinePlan', + ), + migrations.DeleteModel( + name='VirtualMachineType', + ), + ] diff --git a/hosting/mixins.py b/hosting/mixins.py index 404c4cb9..359b1434 100644 --- a/hosting/mixins.py +++ b/hosting/mixins.py @@ -1,35 +1,20 @@ from django.shortcuts import redirect from django.core.urlresolvers import reverse -from .models import VirtualMachinePlan, VirtualMachineType class ProcessVMSelectionMixin(object): def post(self, request, *args, **kwargs): - configuration = request.POST.get('configuration') - configuration_display = dict(VirtualMachinePlan.VM_CONFIGURATION).get(configuration) - vm_template = request.POST.get('vm_template') - vm_type = VirtualMachineType.objects.get(id=vm_template) - vm_specs = vm_type.get_specs() + #configuration = request.POST.get('configuration') + #configuration_display = dict(VirtualMachinePlan.VM_CONFIGURATION).get(configuration) + vm_template_id = request.POST.get('vm_template_id') vm_specs.update({ 'configuration_display': configuration_display, 'configuration': configuration, - 'final_price': vm_type.final_price, - 'vm_template': vm_template + 'vm_template_id': vm_template_id }) - # vm_specs = { - # # 'cores': request.POST.get('cores'), - # # 'memory': request.POST.get('memory'), - # # 'disk_size': request.POST.get('disk_space'), - # # 'hosting_company': request.POST.get('hosting_company'), - # # 'location_code': request.POST.get('location_code'), - # # 'configuration': hosting, - # # 'configuration_detail': configuration_detail, - # 'final_price': request.POST.get('final_price') - # } request.session['vm_specs'] = vm_specs if not request.user.is_authenticated(): - request.session['vm_specs'] = vm_specs request.session['next'] = reverse('hosting:payment') return redirect(reverse('hosting:login')) return redirect(reverse('hosting:payment')) diff --git a/hosting/models.py b/hosting/models.py index b59b7f5a..6eb0aa24 100644 --- a/hosting/models.py +++ b/hosting/models.py @@ -22,224 +22,12 @@ from oca.pool import WrongNameError import logging logger = logging.getLogger(__name__) -class VirtualMachineType(models.Model): - - description = models.TextField() - base_price = models.FloatField() - memory_price = models.FloatField() - core_price = models.FloatField() - disk_size_price = models.FloatField() - cores = models.IntegerField() - memory = models.IntegerField() - disk_size = models.IntegerField() - - def __str__(self): - return "VM Type %s" % (self.id) - - @cached_property - def final_price(self): - price = self.cores * self.core_price - price += self.memory * self.memory_price - price += self.disk_size * self.disk_size_price - return price - - @classmethod - def get_serialized_vm_types(cls): - return [vm.get_serialized_data() - for vm in cls.objects.all()] - - def calculate_price(self): - price = self.cores * self.core_price - price += self.memory * self.memory_price - price += self.disk_size * self.disk_size_price - # price += self.base_price - return price - - # @classmethod - # def get_price(cls, vm_template): - # return cls.BASE_PRICE * vm_template - - def get_specs(self): - return { - 'memory': self.memory, - 'cores': self.cores, - 'disk_size': self.disk_size - } - - # def calculate_price(self, vm_template): - # price = self.base_price * vm_template - # return price - - # def defeault_price(self): - # price = self.base_price - # price += self.core_price - # price += self.memory_price - # price += self.disk_size_price * 10 - # return price - - def get_serialized_data(self): - return { - 'description': self.description, - 'core_price': self.core_price, - 'disk_size_price': self.disk_size_price, - 'memory_price': self.memory_price, - 'id': self.id, - 'final_price': self.final_price, - 'cores': self.cores, - 'memory': self.memory, - 'disk_size': self.disk_size - - } - - -class VirtualMachinePlan(AssignPermissionsMixin, models.Model): - - PENDING_STATUS = 'pending' - ONLINE_STATUS = 'online' - CANCELED_STATUS = 'canceled' - - VM_STATUS_CHOICES = ( - (PENDING_STATUS, 'Pending for activation'), - (ONLINE_STATUS, 'Online'), - (CANCELED_STATUS, 'Canceled') - ) - - # DJANGO = 'django' - # RAILS = 'rails' - # NODEJS = 'nodejs' - - # VM_CONFIGURATION = ( - # (DJANGO, 'Ubuntu 14.04, Django'), - # (RAILS, 'Ubuntu 14.04, Rails'), - # (NODEJS, 'Debian, NodeJS'), - # ) - - VM_CONFIGURATION = ( - ('debian', 'Debian 8'), - ('ubuntu', 'Ubuntu 16.06'), - ('devuan', 'Devuan 1'), - ('centos', 'CentOS 7') - ) - - permissions = ('view_virtualmachineplan', - 'cancel_virtualmachineplan', - 'change_virtualmachineplan') - - cores = models.IntegerField() - memory = models.IntegerField() - disk_size = models.IntegerField() - vm_type = models.ForeignKey(VirtualMachineType, null=True) - price = models.FloatField() - public_key = models.TextField(blank=True) - status = models.CharField(max_length=20, choices=VM_STATUS_CHOICES, default=PENDING_STATUS) - ip = models.CharField(max_length=50, blank=True) - configuration = models.CharField(max_length=20, choices=VM_CONFIGURATION) - opennebula_id = models.IntegerField(null=True) - - objects = VMPlansManager() - - class Meta: - permissions = ( - ('view_virtualmachineplan', 'View Virtual Machine Plan'), - ('cancel_virtualmachineplan', 'Cancel Virtual Machine Plan'), - ) - - def __str__(self): - return self.name - - # @cached_property - # def hosting_company_name(self): - # return self.vm_type.get_hosting_company_display() - - # @cached_property - # def location(self): - # return self.vm_type.get_location_display() - - @cached_property - def name(self): - name = 'vm-%s' % self.id - return name - - @cached_property - def notifications(self): - stripe_customer = StripeCustomer.objects.get(hostingorder__vm_plan=self) - backend = stored_messages_settings.STORAGE_BACKEND() - messages = backend.inbox_list(stripe_customer.user) - return messages - - @classmethod - def create(cls, data, user): - instance = cls.objects.create(**data) - instance.assign_permissions(user) - return instance - - def cancel_plan(self): - self.status = self.CANCELED_STATUS - self.save(update_fields=['status']) - - @classmethod - def create_opennebula_vm(self, user, specs): - - # Init opennebula manager using given user - opennebula_client = OpenNebulaManager( - user.email, - user.password[0:20], - create_user=True - ) - - # Create a vm in opennebula using given specs - vm = opennebula_client.create_vm(specs) - return vm - - @classmethod - def get_vm(self, user, vm_id): - # Get opennebula client - opennebula_client = OpenNebulaManager( - email=user.email, - password=user.password[:20], - ) - - # Get vm given the id - vm = opennebula_client.get_vm( - user.email, - vm_id - ) - - # Parse vm data - vm_data = OpenNebulaManager.parse_vm(vm) - - return vm_data - - @classmethod - def get_vms(self, user): - - # Get opennebula client - opennebula_client = OpenNebulaManager( - email=user.email, - password=user.password[:20], - ) - - # Get vm pool - vm_pool = opennebula_client.get_vms(user.email) - - # Reset total price - self.total_price = 0 - vms = [] - # Add vm in vm_pool to context - for vm in vm_pool: - vm_data = OpenNebulaManager.parse_vm(vm) - vms.append(vm_data) - # self.total_price += price - # self.save() - return vms - - class HostingOrder(AssignPermissionsMixin, models.Model): ORDER_APPROVED_STATUS = 'Approved' ORDER_DECLINED_STATUS = 'Declined' - vm_plan = models.ForeignKey(VirtualMachinePlan, related_name='hosting_orders') + vm_id = models.IntegerField(default=0) customer = models.ForeignKey(StripeCustomer) billing_address = models.ForeignKey(BillingAddress) created_at = models.DateTimeField(auto_now_add=True) @@ -305,17 +93,6 @@ class UserHostingKey(models.Model): # self.save(update_fields=['public_key']) return private_key, public_key - -class ManageVM(models.Model): - def has_add_permission(self, request): - return False - - def has_delete_permission(self, request, obj=None): - return False - - class Meta: - managed = False - class HostingBill(AssignPermissionsMixin, models.Model): customer = models.ForeignKey(StripeCustomer) billing_address = models.ForeignKey(BillingAddress) @@ -336,32 +113,3 @@ class HostingBill(AssignPermissionsMixin, models.Model): instance = cls.objects.create(customer=customer, billing_address=billing_address) return instance - def get_vms(self): - email = self.customer.user.email - # Get opennebula client - opennebula_client = OpenNebulaManager(create_user=False) - - # Get vm pool - vm_pool = opennebula_client.get_vms(email) - - # Reset total price - self.total_price = 0 - vms = [] - # Add vm in vm_pool to context - for vm in vm_pool: - vm_data = OpenNebulaManager.parse_vm(vm) - self.total_price += vm_data['price'] - vms.append(vm_data) - self.save() - return vms - - - -def get_user_opennebula_password(): - ''' - TODO: Implement the way we obtain the user's opennebula password - ''' - pw = os.environ.get('OPENNEBULA_USER_PW') - if pw is None: - raise Exception("Define OPENNEBULA_USER_PW env variable") - return pw diff --git a/hosting/templates/hosting/bill_detail.html b/hosting/templates/hosting/bill_detail.html index 2d7f04b4..9e92d0e9 100644 --- a/hosting/templates/hosting/bill_detail.html +++ b/hosting/templates/hosting/bill_detail.html @@ -78,7 +78,7 @@
- {% trans "CH02 ............" %} + {% trans "CH02 0900 0000 6071 8848 8%}
{% trans "POFICHBEXXX" %} diff --git a/hosting/templates/hosting/create_virtual_machine.html b/hosting/templates/hosting/create_virtual_machine.html index 5d67b305..33c792e4 100644 --- a/hosting/templates/hosting/create_virtual_machine.html +++ b/hosting/templates/hosting/create_virtual_machine.html @@ -12,10 +12,14 @@ {% csrf_token %}
Select VM: - + {% for template in templates %} - + {% endfor %} @@ -42,4 +46,4 @@
-{%endblock%} \ No newline at end of file +{%endblock%} diff --git a/hosting/templates/hosting/order_detail.html b/hosting/templates/hosting/order_detail.html index 5eb9bac1..868680aa 100644 --- a/hosting/templates/hosting/order_detail.html +++ b/hosting/templates/hosting/order_detail.html @@ -49,17 +49,13 @@

{% trans "Order summary"%}


-

{% trans "Type"%} {{order.vm_plan.hosting_company_name}}

+

{% trans "Cores"%} {{vm.cores}}


-

{% trans "Configuration"%} {{order.vm_plan.get_configuration_display}}

+

{% trans "Memory"%} {{vm.memory}} GiB


-

{% trans "Cores"%} {{order.vm_plan.cores}}

+

{% trans "Disk space"%} {{vm.disk_size}} GiB


-

{% trans "Memory"%} {{order.vm_plan.memory}} GiB

-
-

{% trans "Disk space"%} {{order.vm_plan.disk_size}} GiB

-
-

{% trans "Total"%}

{{order.vm_plan.price}} CHF

+

{% trans "Total"%}

{{vm.price}} CHF


{% url 'hosting:payment' as payment_url %} diff --git a/hosting/templates/hosting/payment.html b/hosting/templates/hosting/payment.html index b0a09812..c30fe3f1 100644 --- a/hosting/templates/hosting/payment.html +++ b/hosting/templates/hosting/payment.html @@ -88,15 +88,14 @@
-

Cores {{request.session.vm_specs.cores}}

+

Cores {{request.session.template.cores}}


-

Configuration {{request.session.vm_specs.configuration_display}}

-
-

Memory {{request.session.vm_specs.memory}} GiB

+

Memory {{request.session.template.memory}} GiB


-

Disk space {{request.session.vm_specs.disk_size}} GiB

+

Disk space {{request.session.template.disk_size}} GiB


-

Total

{{request.session.vm_specs.final_price}} CHF

+

Total

{{request.session.template.price }} CHF

diff --git a/hosting/templates/hosting/virtual_machine_detail.html b/hosting/templates/hosting/virtual_machine_detail.html index e0909b24..a4ad37d4 100644 --- a/hosting/templates/hosting/virtual_machine_detail.html +++ b/hosting/templates/hosting/virtual_machine_detail.html @@ -25,12 +25,6 @@ {% trans "Billing"%} -
  • - - - {% trans "Orders"%} - -
  • {% trans "Status"%} @@ -109,54 +103,20 @@ -
    -
    -
    - -

    Orders

    -
    - - - - - - - - - - - {% for order in virtual_machine.hosting_orders.all %} - - - - - - - - {% endfor %} - -
    #{% trans "Date"%}{% trans "Amount"%}{% trans "Status"%}
    {{order.id}}{{order.created_at}}{{order.vm_plan.price}} CHF{% if order.approved %} - {% trans "Approved"%} - {% else%} - {% trans "Declined"%} - {% endif%} - - -
    -
    -
    -
    -{%endblock%} \ No newline at end of file +{%endblock%} diff --git a/hosting/views.py b/hosting/views.py index cf1cf879..4a6ad3b8 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -19,16 +19,18 @@ from stored_messages.models import Message from stored_messages.api import mark_read - from membership.models import CustomUser, StripeCustomer from utils.stripe_utils import StripeUtils from utils.forms import BillingAddressForm, PasswordResetRequestForm from utils.views import PasswordResetViewMixin, PasswordResetConfirmViewMixin, LoginViewMixin from utils.mailer import BaseEmail -from .models import VirtualMachineType, VirtualMachinePlan, HostingOrder, HostingBill, UserHostingKey +from .models import HostingOrder, HostingBill, UserHostingKey from .forms import HostingUserSignupForm, HostingUserLoginForm, UserHostingKeyForm from .mixins import ProcessVMSelectionMixin -from .opennebula_functions import HostingManageVMAdmin, OpenNebulaManager + +from opennebula_api.models import OpenNebulaManager +from opennebula_api.serializers import VirtualMachineSerializer,\ + VirtualMachineTemplateSerializer from oca.exceptions import OpenNebulaException from oca.pool import WrongNameError @@ -282,48 +284,26 @@ class PaymentVMView(LoginRequiredMixin, FormView): if form.is_valid(): context = self.get_context_data() - specifications = request.session.get('vm_specs') + specifications = request.session.get('template') - vm_template = specifications.get('vm_template', 1) - - vm_type = VirtualMachineType.objects.get(id=vm_template) - - specs = vm_type.get_specs() - - final_price = vm_type.calculate_price() - - plan_data = { - 'vm_type': vm_type, - 'configuration': specifications.get( - 'configuration', - 'django' - ), - 'price': final_price - } - - plan_data.update(specs) + vm_template_id = specifications.get('id', 1) + + final_price = specifications.get('price', 1) token = form.cleaned_data.get('token') + owner = self.request.user + # Get or create stripe customer - customer = StripeCustomer.get_or_create(email=self.request.user.email, + customer = StripeCustomer.get_or_create(email=owner.email, token=token) if not customer: form.add_error("__all__", "Invalid credit card") return self.render_to_response(self.get_context_data(form=form)) - # Create Virtual Machine Plan - plan = VirtualMachinePlan.create(plan_data, request.user) - # Create Billing Address billing_address = form.save() - # Create a Hosting Order - order = HostingOrder.create(vm_plan=plan, customer=customer, - billing_address=billing_address) - # Create a Hosting Bill - bill = HostingBill.create(customer=customer, billing_address=billing_address) - # Make stripe charge to a customer stripe_utils = StripeUtils() charge_response = stripe_utils.make_charge(amount=final_price, @@ -340,24 +320,34 @@ class PaymentVMView(LoginRequiredMixin, FormView): charge = charge_response.get('response_object') + # Create OpenNebulaManager + + manager = OpenNebulaManager(email=owner.email, + password=owner.password[0:20], + create_user=True) + template = manager.get_template(vm_template_id) + + # Create a vm using logged user + vm_id = manager.create_vm(vm_template_id) + # Create a Hosting Order + order = HostingOrder.create(vm_id=vm_id, customer=customer, + billing_address=billing_address) + # Create a Hosting Bill + bill = HostingBill.create(customer=customer, billing_address=billing_address) + + # Associate an order with a stripe payment order.set_stripe_charge(charge) # If the Stripe payment was successed, set order status approved order.set_approved() - # Create a vm using logged user - oppennebula_vm_id = VirtualMachinePlan.create_opennebula_vm( - self.request.user, - specs - ) + vm = VirtualMachineSerializer(manager.get_vm(vm_id)).data - plan.oppenebula_id = oppennebula_vm_id - plan.save() # Send notification to ungleich as soon as VM has been booked context = { - 'vm': plan, + 'vm': vm, 'order': order, 'base_url': "{0}://{1}".format(request.scheme, request.get_host()) @@ -384,6 +374,18 @@ class OrdersHostingDetailView(PermissionRequiredMixin, LoginRequiredMixin, Detai permission_required = ['view_hostingorder'] model = HostingOrder + def get_context_data(self, **kwargs): + # Get context + context = super(DetailView, self).get_context_data(**kwargs) + obj = self.get_object() + owner = self.request.user + manager = OpenNebulaManager(email=owner.email, + password=owner.password[0:20], + create_user=True) + vm = manager.get_vm(obj.vm_id) + context['vm'] = VirtualMachineSerializer(vm).data + + class OrdersHostingListView(LoginRequiredMixin, ListView): template_name = "hosting/orders.html" login_url = reverse_lazy('hosting:login') @@ -408,24 +410,17 @@ class VirtualMachinesPlanListView(LoginRequiredMixin, ListView): template_name = "hosting/virtual_machines.html" login_url = reverse_lazy('hosting:login') context_object_name = "vms" - model = VirtualMachinePlan paginate_by = 10 ordering = '-id' - def get_context_data(self, **kwargs): - context = super(VirtualMachinesPlanListView, self).get_context_data(**kwargs) - context.update({ - 'vms_opennebula': VirtualMachinePlan.get_vms(self.request.user) - }) - return context - def get_queryset(self): - # hosting_admin = HostingManageVMAdmin.__new__(HostingManageVMAdmin) - # print(hosting_admin.show_vms_view(self.request)) - # print(VirtualMachinePlan.get_vms(self.request.user.)) - user = self.request.user - self.queryset = VirtualMachinePlan.objects.active(user) - return super(VirtualMachinesPlanListView, self).get_queryset() + owner = self.request.user + manager = OpenNebulaManager(email=owner.email, + password=owner.password[0:20], + create_user=True) + queryset = manager.get_vms() + serializer = VirtualMachineSerializer(queryset, many=True) + return serializer.data class CreateVirtualMachinesView(LoginRequiredMixin, View): @@ -433,92 +428,56 @@ class CreateVirtualMachinesView(LoginRequiredMixin, View): login_url = reverse_lazy('hosting:login') def get(self, request, *args, **kwargs): + #TODO: Replace with OpenNebulaManager.get_apps + templates = OpenNebulaManager().get_templates() + data = VirtualMachineTemplateSerializer(templates, many=True).data context = { - 'vm_types': VirtualMachineType.get_serialized_vm_types(), - 'configuration_options': VirtualMachinePlan.VM_CONFIGURATION + 'templates': data, + #'configuration_options': VirtualMachinePlan.VM_CONFIGURATION } # context = {} return render(request, self.template_name, context) def post(self, request): - configuration = request.POST.get('configuration') - configuration_display = dict(VirtualMachinePlan.VM_CONFIGURATION).get(configuration) - vm_template = request.POST.get('vm_template') - vm_type = VirtualMachineType.objects.get(id=vm_template) - vm_specs = vm_type.get_specs() - vm_specs.update({ - 'configuration_display': configuration_display, - 'configuration': configuration, - 'final_price': vm_type.final_price, - 'vm_template': vm_template - }) - request.session['vm_specs'] = vm_specs + #XXX: Fix this! + #configuration = request.POST.get('configuration') + #configuration_display = dict(VirtualMachinePlan.VM_CONFIGURATION).get(configuration) + template_id = int(request.POST.get('vm_template_id')) + template = OpenNebulaManager().get_template(template_id) + data = VirtualMachineTemplateSerializer(template).data + vm_specs = { + #'configuration_display': configuration_display, + #'configuration': configuration, + 'template': data, + } + request.session['template'] = data return redirect(reverse('hosting:payment')) - # def get_queryset(self): - # # hosting_admin = HostingManageVMAdmin.__new__(HostingManageVMAdmin) - # # print(hosting_admin.show_vms(self.request)) - # user = self.request.user - # self.queryset = VirtualMachinePlan.objects.active(user) - # return super(VirtualMachinesPlanListView, self).get_queryset() - -class VirtualMachineView(PermissionRequiredMixin, LoginRequiredMixin, View): +class VirtualMachineView(LoginRequiredMixin, View): template_name = "hosting/virtual_machine_detail.html" login_url = reverse_lazy('hosting:login') - # model = VirtualMachinePlan - # context_object_name = "virtual_machine" - permission_required = ['view_virtualmachineplan', 'cancel_virtualmachineplan'] - # fields = '__all__' - - # def get_context_data(self, **kwargs): - # vm_plan = get_object() - # context = super(VirtualMachineView, self).get_context_data(**kwargs) - # context.update({ - # 'opennebula_vm': VirtualMachinePlan.get_vm( - # self.request.user.email, - # opennebula_id - # ) - # }) - # return context - - # def get_object(self, queryset=None): - # # if queryset is None: - # # queryset = self.get_queryset() - # # Next, try looking up by primary key. - # vm_id = self.kwargs.get(self.pk_url_kwarg) - # try: - # return VirtualMachinePlan.get_vm( - # self.request.user.email, - # vm_id - # ) - # except Exception as error: - # raise Http404() - - # def get_success_url(self): - # vm = self.get_object() - # final_url = "%s%s" % (reverse('hosting:virtual_machines', kwargs={'pk': vm.id}), - # '#status-v') - # return final_url def get(self, request, *args, **kwargs): + owner = self.request.user + manager = OpenNebulaManager(email=owner.email, + password=owner.password[0:20], + create_user=True) vm_id = self.kwargs.get('pk') try: - opennebula_vm = VirtualMachinePlan.get_vm( - self.request.user, - vm_id - ) + vm = manager.get_vm(vm_id) + serializer = VirtualMachineSerializer(vm) except Exception as error: print(error) raise Http404() context = { - 'virtual_machine': opennebula_vm, + 'virtual_machine': serializer.data, } - # context = {} return render(request, self.template_name, context) def post(self, *args, **kwargs): + #TODO: add api to OpenNebulaManager vm = self.get_object() vm.cancel_plan() @@ -564,10 +523,14 @@ class HostingBillDetailView(PermissionRequiredMixin, LoginRequiredMixin, DetailV def get_context_data(self, **kwargs): # Get context context = super(DetailView, self).get_context_data(**kwargs) + + owner = self.request.user + manager = OpenNebulaManager(email=owner.email, + password=owner.password[0:20], + create_user=True) # Get vms - try: - context['vms'] = self.get_object().get_vms() - except: - pass + queryset = manager.get_vms() + vms = VirtualMachineSerializer(queryset, many=True).data + context['vms'] = vms return context diff --git a/opennebula_api/migrations/0001_initial.py b/opennebula_api/migrations/0001_initial.py deleted file mode 100644 index 78747cd2..00000000 --- a/opennebula_api/migrations/0001_initial.py +++ /dev/null @@ -1,40 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.9.4 on 2017-05-09 14:36 -from __future__ import unicode_literals - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ] - - operations = [ - migrations.CreateModel( - name='VirtualMachine', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('opennebula_id', models.IntegerField()), - ], - ), - migrations.CreateModel( - name='VirtualMachineTemplate', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('opennebula_id', models.IntegerField()), - ('base_price', models.FloatField()), - ('memory_price', models.FloatField()), - ('core_price', models.FloatField()), - ('disk_size_price', models.FloatField()), - ], - ), - migrations.AddField( - model_name='virtualmachine', - name='template', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='opennebula_api.VirtualMachineTemplate'), - ), - ] diff --git a/opennebula_api/migrations/0002_auto_20170511_0246.py b/opennebula_api/migrations/0002_auto_20170511_0246.py deleted file mode 100644 index 0d6b7287..00000000 --- a/opennebula_api/migrations/0002_auto_20170511_0246.py +++ /dev/null @@ -1,20 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.9.4 on 2017-05-11 02:46 -from __future__ import unicode_literals - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('opennebula_api', '0001_initial'), - ] - - operations = [ - migrations.RenameField( - model_name='virtualmachine', - old_name='template', - new_name='vm_template', - ), - ] diff --git a/opennebula_api/migrations/0003_virtualmachine_owner.py b/opennebula_api/migrations/0003_virtualmachine_owner.py deleted file mode 100644 index 415ddb9b..00000000 --- a/opennebula_api/migrations/0003_virtualmachine_owner.py +++ /dev/null @@ -1,24 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.9.4 on 2017-05-11 09:06 -from __future__ import unicode_literals - -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), - ('opennebula_api', '0002_auto_20170511_0246'), - ] - - operations = [ - migrations.AddField( - model_name='virtualmachine', - name='owner', - field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, related_name='virtual_machines', to=settings.AUTH_USER_MODEL), - preserve_default=False, - ), - ] diff --git a/opennebula_api/models.py b/opennebula_api/models.py index 87fd867a..4ec99673 100644 --- a/opennebula_api/models.py +++ b/opennebula_api/models.py @@ -1,124 +1,11 @@ import oca import socket -from django.db import models from django.conf import settings from django.utils.functional import cached_property -from membership.models import CustomUser - from oca.pool import WrongNameError -class VirtualMachineTemplate(models.Model): - """This class represents an opennebula template.""" - opennebula_id = models.IntegerField() - base_price = models.FloatField() - memory_price = models.FloatField() - core_price = models.FloatField() - disk_size_price = models.FloatField() - - @cached_property - def template(self): - template = OpenNebulaManager()._get_template(self.opennebula_id) - return template - - def calculate_price(self): - template = self.template.template - - price = int(template.vcpu) * self.core_price - price += int(template.memory) / 1024 * self.memory_price - try: - price += int(template.disk.size) / 1024 * self.disk_size_price - except AttributeError: - for disk in template.disks: - price += int(disk.size) / 1024 * self.disk_size_price - return price - - def get_name(self): - - template = self.template - return template.name - - def get_cores(self): - - template = self.template.template - return int(template.vcpu) - - def get_disk_size(self): - - template = self.template.template - disk_size = 0 - for disk in template.disks: - disk_size += int(disk.size) - return disk_size / 1024 - - def get_memory(self): - - template = self.template.template - return int(template.memory) / 1024 - -class VirtualMachine(models.Model): - """This class represents an opennebula virtual machine.""" - opennebula_id = models.IntegerField() - owner = models.ForeignKey(CustomUser, related_name='virtual_machines') - vm_template = models.ForeignKey(VirtualMachineTemplate) - - VM_STATE = { - '0': 'INIT', - '1': 'PENDING', - '2': 'HOLD', - '3': 'ACTIVE', - '4': 'STOPPED', - '5': 'SUSPENDED', - '6': 'DONE', - '8': 'POWEROFF', - '9': 'UNDEPLOYED', - '10': 'CLONING', - '11': 'CLONING_FAILURE', - } - - @cached_property - def vm(self): - manager = OpenNebulaManager(email=self.owner.email, - password=self.owner.password[0:20], - create_user=True) - vm = manager._get_vm(self.opennebula_id) - return vm - - def get_name(self): - - return self.vm.name - - def get_cores(self): - - return self.vm_template.get_cores() - - def get_disk_size(self): - - return self.vm_template.get_disk_size() - - def get_memory(self): - - return self.vm_template.get_memory() - - def get_id(self): - - return self.vm.id - - def get_ip(self): - - try: - return self.vm.user_template.ungleich_public_ip - except AttributeError: - return '-' - - def get_state(self): - - return self.VM_STATE.get(str(self.vm.state)) - - def get_price(self): - return self.vm_template.calculate_price() - class OpenNebulaManager(): """This class represents an opennebula manager.""" @@ -204,12 +91,15 @@ class OpenNebulaManager(): raise ConnectionRefusedError return vm_pool + def get_vms(self): + return self._get_vm_pool() - def _get_vm(self, vm_id): + def get_vm(self, vm_id): vm_pool = self._get_vm_pool() - return vm_pool.get_by_id(vm_id) + return vm_pool.get_by_id(int(vm_id)) - def create_vm(self, template_id): + #TODO: get app with id + def create_vm(self, template_id, app_id=None): vm_id = self.oneadmin_client.call( oca.VmTemplate.METHODS['instantiate'], template_id, @@ -232,7 +122,7 @@ class OpenNebulaManager(): self.oneadmin_client.call( oca.VirtualMachine.METHODS['action'], 'terminate', - vm_id + int(vm_id) ) def _get_template_pool(self): @@ -248,19 +138,31 @@ class OpenNebulaManager(): raise ConnectionRefusedError return template_pool - def _get_template(self, template_id): + def get_templates(self): + public_templates = [ + template + for template in self._get_template_pool() + if 'public-' in template.name + ] + return public_templates + def get_template(self, template_id): template_pool = self._get_template_pool() return template_pool.get_by_id(template_id) - def create_template(self, name, cores, memory, disk_size): + def create_template(self, name, cores, memory, disk_size, core_price, memory_price, + disk_size_price, ssh='' ): """Create and add a new template to opennebula. :param name: A string representation describing the template. Used as label in view. :param cores: Amount of virtual cpu cores for the VM. - :param memory: Amount of RAM for the VM (MB) - :param disk_size: Amount of disk space for VM (MB) + :param memory: Amount of RAM for the VM (GB) + :param disk_size: Amount of disk space for VM (GB) + :param core_price: Price of virtual cpu for the VM per core. + :param memory_price: Price of RAM for the VM per GB + :param disk_size_price: Price of disk space for VM per GB + :param ssh: User public ssh key """ template_string_formatter = """ """ template_id = oca.VmTemplate.allocate( @@ -281,7 +187,12 @@ class OpenNebulaManager(): vcpu=cores, cpu=0.1*cores, size=1024 * disk_size, - memory=1024 * memory + memory=1024 * memory, + # * 10 because we set cpu to *0.1 + cpu_cost=10*core_price, + memory_cost=memory_price, + disk_cost=disk_size_price, + ssh=ssh ) ) diff --git a/opennebula_api/permissions.py b/opennebula_api/permissions.py deleted file mode 100644 index aebdabc3..00000000 --- a/opennebula_api/permissions.py +++ /dev/null @@ -1,10 +0,0 @@ -from rest_framework.permissions import BasePermission - -from .models import VirtualMachine - -class IsOwner(BasePermission): - - def has_object_permission(self, request, view, obj): - if isinstance(obj, VirtualMachine): - return obj.owner == request.user - return obj.owner == request.user diff --git a/opennebula_api/serializers.py b/opennebula_api/serializers.py index df587821..8ca95013 100644 --- a/opennebula_api/serializers.py +++ b/opennebula_api/serializers.py @@ -3,98 +3,117 @@ import oca from rest_framework import serializers from oca import OpenNebulaException +from oca.template import VmTemplate -from .models import VirtualMachine, VirtualMachineTemplate, OpenNebulaManager +from .models import OpenNebulaManager -class VirtualMachineTemplateSerializer(serializers.ModelSerializer): +class VirtualMachineTemplateSerializer(serializers.Serializer): """Serializer to map the virtual machine template instance into JSON format.""" - cores = serializers.IntegerField(source='get_cores') - name = serializers.CharField(source='get_name') - disk_size = serializers.IntegerField(source='get_disk_size') - memory = serializers.IntegerField(source='get_memory') + id = serializers.IntegerField(read_only=True) + name = serializers.CharField() + cores = serializers.IntegerField(source='template.vcpu') + disk = serializers.IntegerField(write_only=True) + disk_size = serializers.SerializerMethodField() + memory = serializers.SerializerMethodField() + core_price = serializers.FloatField(source='template.cpu_cost') + disk_size_price = serializers.FloatField(source='template.disk_cost') + memory_price = serializers.FloatField(source='template.memory_cost') + price = serializers.SerializerMethodField() - class Meta: - model = VirtualMachineTemplate - fields = ('id', 'name', 'cores', 'memory', 'disk_size', 'base_price', - 'core_price', 'memory_price', 'disk_size_price', 'opennebula_id') - read_only_fields = ('opennebula_id', ) - - def validate(self, data): - # Create the opennebula model - cores = data.pop('get_cores') - name = data.pop('get_name') - disk_size = data.pop('get_disk_size') - memory = data.pop('get_memory') + def create(self, validated_data): + data = validated_data + template = data.pop('template') + cores = template.pop('vcpu') + name = data.pop('name') + disk_size = data.pop('disk') + memory = template.pop('memory') + core_price = template.pop('cpu_cost') + memory_price = template.pop('memory_cost') + disk_size_price = template.pop('disk_cost') manager = OpenNebulaManager(create_user = False) try: opennebula_id = manager.create_template(name=name, cores=cores, memory=memory, - disk_size=disk_size) - data.update({'opennebula_id':opennebula_id}) + disk_size=disk_size, + core_price=core_price, + disk_size_price=disk_size_price, + memory_price=memory_price) except OpenNebulaException as err: raise serializers.ValidationError("OpenNebulaException occured. {0}".format(err)) - return data + return manager.get_template(template_id=opennebula_id) - def create(self, validated_data): - return VirtualMachineTemplate.objects.create(**validated_data) + def get_disk_size(self, obj): + template = obj.template + disk_size = 0 + for disk in template.disks: + disk_size += int(disk.size) + return disk_size / 1024 -class TemplatePrimaryKeyRelatedField(serializers.PrimaryKeyRelatedField): - def display_value(self, instance): - return 'Template: {}'.format(instance.get_name()) + def get_price(self, obj): + template = obj.template + price = float(template.cpu) * float(template.cpu_cost) + price += (int(template.memory)/1024 * float(template.memory_cost)) + for disk in template.disks: + price += int(disk.size)/1024 * float(template.disk_cost) + return price -class VirtualMachineSerializer(serializers.ModelSerializer): + def get_memory(self, obj): + return int(obj.template.memory)/1024 + +class VirtualMachineSerializer(serializers.Serializer): """Serializer to map the virtual machine instance into JSON format.""" - #TODO: Maybe we can change to template.get_cores - cores = serializers.IntegerField(read_only=True, source='get_cores') - name = serializers.CharField(read_only=True, source='get_name') - disk_size = serializers.IntegerField(read_only=True, source='get_disk_size') - memory = serializers.IntegerField(read_only=True, source='get_memory') - #TODO: See if we can change to IPAddressField - ip = serializers.CharField(read_only=True, source='get_ip') - deploy_id = serializers.IntegerField(read_only=True, source='get_deploy_id') - vm_id = serializers.IntegerField(read_only=True, source='get_vm_id') - state = serializers.CharField(read_only=True, source='get_state') - price = serializers.FloatField(read_only=True, source='get_price') + name = serializers.CharField(read_only=True) + cores = serializers.IntegerField(read_only=True, source='template.vcpu') - owner = serializers.ReadOnlyField(source='owner.name') + disk_size = serializers.SerializerMethodField() + memory = serializers.SerializerMethodField() + ip = serializers.CharField(read_only=True, + source='user_template.ungleich_public_ip', + default='-') + vm_id = serializers.IntegerField(read_only=True, source='id') + state = serializers.CharField(read_only=True, source='str_state') + price = serializers.SerializerMethodField() - vm_template = VirtualMachineTemplateSerializer(read_only=True) - - vm_template_id = TemplatePrimaryKeyRelatedField( - queryset=VirtualMachineTemplate.objects.all(), - source='vm_template' + template_id = serializers.ChoiceField( + choices=[(key.id, key.name) for key in + OpenNebulaManager().get_templates()], + source='template.template_id', + write_only=True ) - class Meta: - model = VirtualMachine - fields = ('id', 'opennebula_id', 'vm_template', 'vm_template_id', 'cores', 'name', - 'disk_size', 'memory', 'ip', 'deploy_id', 'state', 'vm_id', - 'price', 'owner') - read_only_fields = ('opennebula_id', ) + def create(self, validated_data): + owner = validated_data['owner'] + template_id = validated_data['template']['template_id'] - def validate(self, data): - # Create the opennebula model - try: - template_id = data['vm_template'].opennebula_id - owner = self.context.get('request').user manager = OpenNebulaManager(email=owner.email, password=owner.password[0:20], create_user = True) opennebula_id = manager.create_vm(template_id) - data.update({'opennebula_id':opennebula_id}) except OpenNebulaException as err: raise serializers.ValidationError("OpenNebulaException occured. {0}".format(err)) - return data + return manager.get_vm(opennebula_id) - def create(self, validated_data): - return VirtualMachine.objects.create(**validated_data) + def get_memory(self, obj): + return int(obj.template.memory)/1024 - def update(self, instance, validated_data): - pass + def get_disk_size(self, obj): + template = obj.template + disk_size = 0 + for disk in template.disks: + disk_size += int(disk.size) + return disk_size / 1024 + + def get_price(self, obj): + template = obj.template + price = float(template.cpu) * float(template.cpu_cost) + price += (int(template.memory)/1024 * float(template.memory_cost)) + for disk in template.disks: + price += int(disk.size)/1024 * float(template.disk_cost) + return price diff --git a/opennebula_api/views.py b/opennebula_api/views.py index 563f6b8c..c537e2e5 100644 --- a/opennebula_api/views.py +++ b/opennebula_api/views.py @@ -11,17 +11,20 @@ from guardian.mixins import PermissionRequiredMixin from .serializers import VirtualMachineTemplateSerializer, \ VirtualMachineSerializer -from .models import VirtualMachineTemplate, VirtualMachine, OpenNebulaManager -from .permissions import IsOwner +from .models import OpenNebulaManager class TemplateCreateView(generics.ListCreateAPIView): """This class handles the GET and POST requests.""" - queryset = VirtualMachineTemplate.objects.all() serializer_class = VirtualMachineTemplateSerializer permission_classes = (permissions.IsAuthenticated, permissions.IsAdminUser) + def get_queryset(self): + manager = OpenNebulaManager() + return manager.get_templates() + + def perform_create(self, serializer): """Save the post data when creating a new template.""" serializer.save() @@ -29,15 +32,24 @@ class TemplateCreateView(generics.ListCreateAPIView): class TemplateDetailsView(generics.RetrieveUpdateDestroyAPIView): """This class handles the http GET, PUT and DELETE requests.""" - queryset = VirtualMachineTemplate.objects.all() serializer_class = VirtualMachineTemplateSerializer permission_classes = (permissions.IsAuthenticated) + def get_queryset(self): + manager = OpenNebulaManager() + return manager.get_templates() + class VmCreateView(generics.ListCreateAPIView): """This class handles the GET and POST requests.""" - queryset = VirtualMachine.objects.all() serializer_class = VirtualMachineSerializer - permission_classes = (permissions.IsAuthenticated, IsOwner) + permission_classes = (permissions.IsAuthenticated, ) + + def get_queryset(self): + owner = self.request.user + manager = OpenNebulaManager(email=owner.email, + password=owner.password[0:20], + create_user=True) + return manager.get_vms() def perform_create(self, serializer): """Save the post data when creating a new template.""" @@ -45,16 +57,28 @@ class VmCreateView(generics.ListCreateAPIView): class VmDetailsView(generics.RetrieveUpdateDestroyAPIView): """This class handles the http GET, PUT and DELETE requests.""" - permission_classes = (permissions.IsAuthenticated, IsOwner) + permission_classes = (permissions.IsAuthenticated, ) - queryset = VirtualMachine.objects.all() serializer_class = VirtualMachineSerializer + def get_queryset(self): + owner = self.request.user + manager = OpenNebulaManager(email=owner.email, + password=owner.password[0:20], + create_user=True) + return manager.get_vms() + + def get_object(self): + owner = self.request.user + manager = OpenNebulaManager(email=owner.email, + password=owner.password[0:20], + create_user=True) + return manager.get_vm(self.kwargs.get('pk')) + def perform_destroy(self, instance): - owner = instance.owner + owner = self.request.user manager = OpenNebulaManager(email=owner.email, password=owner.password[0:20], create_user = True) - manager.delete_vm(instance.opennebula_id) - instance.delete() + manager.delete_vm(instance.id)