API Integration
Please review carefully.
This commit is contained in:
parent
b7e8eceb25
commit
130c00c8ee
19 changed files with 310 additions and 890 deletions
|
@ -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)
|
||||
|
|
|
@ -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):
|
||||
|
||||
|
|
34
hosting/migrations/0038_auto_20170512_1006.py
Normal file
34
hosting/migrations/0038_auto_20170512_1006.py
Normal 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',
|
||||
),
|
||||
]
|
|
@ -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'))
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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" %}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 %}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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%}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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 %}
|
||||
|
|
201
hosting/views.py
201
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_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
|
||||
|
|
|
@ -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'),
|
||||
),
|
||||
]
|
|
@ -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',
|
||||
),
|
||||
]
|
|
@ -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,
|
||||
),
|
||||
]
|
|
@ -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
|
||||
)
|
||||
)
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
Loading…
Reference in a new issue