API Integration

Please review carefully.
This commit is contained in:
Modulos 2017-05-12 12:07:05 +02:00
parent b7e8eceb25
commit 130c00c8ee
19 changed files with 310 additions and 890 deletions

View file

@ -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("<a href='{url}'>{email}</a>", 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("<a href='{url}'>{vm_name}</a>", 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)

View file

@ -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):

View file

@ -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',
),
]

View file

@ -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'))

View file

@ -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

View file

@ -78,7 +78,7 @@
</div>
<div class="row">
<div class="col-sm-6">
{% trans "CH02 ............" %}
{% trans "CH02 0900 0000 6071 8848 8%}
</div>
<div class="col-sm-6">
{% trans "POFICHBEXXX" %}

View file

@ -12,10 +12,14 @@
{% csrf_token %}
<div class="form-group">
Select VM:
<select name="vm_template">
{% for vm in vm_types %}
<select name="vm_template_id">
{% for template in templates %}
<option value="{{vm.id}}">CORE: {{vm.cores}}, RAM: {{vm.memory}}, SSD: {{vm.disk_size}} </option>
<option value="{{template.id}}">
CORE: {{template.cores}},
RAM: {{template.memory}},r
SSD: {{template.disk_size}}
</option>
{% endfor %}
</select>

View file

@ -49,17 +49,13 @@
<h3><b>{% trans "Order summary"%}</b></h3>
<hr>
<div class="content">
<p><b>{% trans "Type"%}</b> <span class="pull-right">{{order.vm_plan.hosting_company_name}}</span></p>
<p><b>{% trans "Cores"%}</b> <span class="pull-right">{{vm.cores}}</span></p>
<hr>
<p><b>{% trans "Configuration"%}</b> <span class="pull-right">{{order.vm_plan.get_configuration_display}}</span></p>
<p><b>{% trans "Memory"%}</b> <span class="pull-right">{{vm.memory}} GiB</span></p>
<hr>
<p><b>{% trans "Cores"%}</b> <span class="pull-right">{{order.vm_plan.cores}}</span></p>
<p><b>{% trans "Disk space"%}</b> <span class="pull-right">{{vm.disk_size}} GiB</span></p>
<hr>
<p><b>{% trans "Memory"%}</b> <span class="pull-right">{{order.vm_plan.memory}} GiB</span></p>
<hr>
<p><b>{% trans "Disk space"%}</b> <span class="pull-right">{{order.vm_plan.disk_size}} GiB</span></p>
<hr>
<h4>{% trans "Total"%}<p class="pull-right"><b>{{order.vm_plan.price}} CHF</b></p></h4>
<h4>{% trans "Total"%}<p class="pull-right"><b>{{vm.price}} CHF</b></p></h4>
</div>
<br/>
{% url 'hosting:payment' as payment_url %}

View file

@ -88,15 +88,14 @@
<div class="content">
<!-- <p><b>Type</b> <span class="pull-right">{{request.session.vm_specs.location_code}}</span></p> -->
<!-- <hr> -->
<p><b>Cores</b> <span class="pull-right">{{request.session.vm_specs.cores}}</span></p>
<p><b>Cores</b> <span class="pull-right">{{request.session.template.cores}}</span></p>
<hr>
<p><b>Configuration</b> <span class="pull-right">{{request.session.vm_specs.configuration_display}}</span></p>
<p><b>Memory</b> <span class="pull-right">{{request.session.template.memory}} GiB</span></p>
<hr>
<p><b>Memory</b> <span class="pull-right">{{request.session.vm_specs.memory}} GiB</span></p>
<p><b>Disk space</b> <span class="pull-right">{{request.session.template.disk_size}} GiB</span></p>
<hr>
<p><b>Disk space</b> <span class="pull-right">{{request.session.vm_specs.disk_size}} GiB</span></p>
<hr>
<h4>Total<p class="pull-right"><b>{{request.session.vm_specs.final_price}} CHF</b></p></h4>
<h4>Total<p
class="pull-right"><b>{{request.session.template.price }} CHF</b></p></h4>
</div>
</div>
</div>

View file

@ -25,12 +25,6 @@
{% trans "Billing"%}
</a>
</li>
<li>
<a href="#orders-v" data-toggle="tab">
<i class="fa fa-credit-card"></i>
{% trans "Orders"%}
</a>
</li>
<li>
<a href="#status-v" data-toggle="tab">
<i class="fa fa-signal" aria-hidden="true"></i> {% trans "Status"%}
@ -109,54 +103,20 @@
</div>
</div>
</div>
<div class="tab-pane" id="orders-v">
<div class="row">
<div class="col-md-12">
<table class="table borderless table-hover">
<h3>Orders</h3>
<br/>
<thead>
<tr>
<th>#</th>
<th>{% trans "Date"%}</th>
<th>{% trans "Amount"%}</th>
<th>{% trans "Status"%}</th>
<th></th>
</tr>
</thead>
<tbody>
{% for order in virtual_machine.hosting_orders.all %}
<tr>
<td scope="row">{{order.id}}</td>
<td>{{order.created_at}}</td>
<td>{{order.vm_plan.price}} CHF</td>
<td>{% if order.approved %}
<span class="text-success strong">{% trans "Approved"%}</span>
{% else%}
<span class="text-danger strong">{% trans "Declined"%}</span>
{% endif%}
</td>
<td>
<button type="button" class="btn btn-default"><a href="{% url 'hosting:orders' order.id %}">{% trans "View Detail"%}</a></button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div><!--/col-12-->
</div><!--/row-->
</div>
<div class="tab-pane" id="status-v">
<div class="row ">
<div class="col-md-12 inline-headers">
<h3>{% trans "Current status"%}</h3>
<div class="pull-right space-above">
{% if virtual_machine.status == 'pending' %}
<span class="label label-warning"><strong>{{virtual_machine.get_status_display}}</strong></span>
{% elif virtual_machine.status == 'online' %}
<span class="label label-success"><strong>{{virtual_machine.get_status_display}}</strong></span>
{% elif virtual_machine.status == 'canceled'%}
<span class="label label-danger"><strong>{{virtual_machine.get_status_display}}</strong></span>
{% if virtual_machine.state == 'PENDING' %}
<span class="label
label-warning"><strong>Pending</strong></span>
{% elif virtual_machine.state == 'ACTIVE' %}
<span class="label
label-success"><strong>Online</strong></span>
{% elif virtual_machine.state == 'FAILED'%}
<span class="label
label-danger"><strong>Failed</strong></span>
{% endif %}
</div>
</div>
@ -165,11 +125,13 @@
<div class="row">
<div class="col-md-12 space-above-big">
<div class="pull-right">
<form method="POST" id="virtual_machine_cancel_form" class="cancel-form" action="{% url 'hosting:virtual_machines' virtual_machine.id %}">
<form method="POST"
id="virtual_machine_cancel_form" class="cancel-form" action="{% url 'hosting:virtual_machines' virtual_machine.vm_id %}">
{% csrf_token %}
</form>
<button type="text" data-href="{% url 'hosting:virtual_machines' virtual_machine.id %}" data-toggle="modal" data-target="#confirm-cancel" class="btn btn-danger">{% trans "Cancel Virtual Machine"%}</button>
<button type="text"
data-href="{% url 'hosting:virtual_machines' virtual_machine.vm_id %}" data-toggle="modal" data-target="#confirm-cancel" class="btn btn-danger">{% trans "Cancel Virtual Machine"%}</button>
</div>
</div>
@ -181,7 +143,7 @@
{% trans "Cancel your Virtual Machine"%}
</div>
<div class="modal-body">
{% trans "Are you sure do you want to cancel your Virtual Machine "%} {{vm.virtual_machine}} {% trans "plan?"%}
{% trans "Are you sure do you want to cancel your Virtual Machine "%} {{virtual_machine.name}} {% trans "plan?"%}
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">{% trans "Cancel"%}</button>
@ -207,12 +169,3 @@
</div>
{%endblock%}

View file

@ -20,9 +20,9 @@
</tr>
</thead>
<tbody>
{% for vm in vms_opennebula %}
{% for vm in vms %}
<tr>
<td scope="row">{{vm.deploy_id}}</td>
<td scope="row">{{vm.vm_id}}</td>
<td>{{vm.price}} CHF</td>
<td>
@ -36,7 +36,8 @@
</td>
<td>
<button type="button" class="btn btn-default"><a href="{% url 'hosting:virtual_machines' vm.id %}">{% trans "View Detail"%}</a></button>
<button type="button" class="btn btn-default"><a
href="{% url 'hosting:virtual_machines' vm.vm_id %}">{% trans "View Detail"%}</a></button>
</td>
</tr>
{% endfor %}

View file

@ -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_template_id = specifications.get('id', 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)
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

View file

@ -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'),
),
]

View file

@ -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',
),
]

View file

@ -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,
),
]

View file

@ -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>
<NAME>{name}</NAME>
@ -272,6 +174,10 @@ class OpenNebulaManager():
<SIZE>{size}</SIZE>
<DEV_PREFIX>vd</DEV_PREFIX>
</DISK>
<CPU_COST>{cpu_cost}</CPU_COST>
<MEMORY_COST>{memory_cost}</MEMORY_COST>
<DISK_COST>{disk_cost}</DISK_COST>
<SSH_PUBLIC_KEY>{ssh}</SSH_PUBLIC_KEY>
</TEMPLATE>
"""
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
)
)

View file

@ -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

View file

@ -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 validate(self, data):
# Create the opennebula model
def create(self, validated_data):
owner = validated_data['owner']
template_id = validated_data['template']['template_id']
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

View file

@ -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 perform_destroy(self, instance):
owner = instance.owner
def get_queryset(self):
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()
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 = self.request.user
manager = OpenNebulaManager(email=owner.email,
password=owner.password[0:20],
create_user = True)
manager.delete_vm(instance.id)