merged opnnebula api changes

This commit is contained in:
Levi 2017-05-12 12:13:18 -05:00
commit 8980f6b2fc
19 changed files with 458 additions and 888 deletions

View file

@ -12,6 +12,8 @@ import debug_toolbar
urlpatterns = [ url(r'^index.html$', LandingView.as_view()), urlpatterns = [ url(r'^index.html$', LandingView.as_view()),
url(r'^hosting/', include('hosting.urls', namespace="hosting")), url(r'^hosting/', include('hosting.urls', namespace="hosting")),
url(r'^open_api/', include('opennebula_api.urls',
namespace='opennebula_api')),
url(r'^railshosting/', RailsHostingView.as_view(), name="rails.hosting"), url(r'^railshosting/', RailsHostingView.as_view(), name="rails.hosting"),
url(r'^nodehosting/', NodeJSHostingView.as_view(), name="node.hosting"), url(r'^nodehosting/', NodeJSHostingView.as_view(), name="node.hosting"),
url(r'^djangohosting/', DjangoHostingView.as_view(), name="django.hosting"), url(r'^djangohosting/', DjangoHostingView.as_view(), name="django.hosting"),

View file

@ -4,99 +4,8 @@ from django.core.urlresolvers import reverse
from utils.mailer import BaseEmail from utils.mailer import BaseEmail
from .forms import HostingOrderAdminForm from .models import HostingOrder, HostingBill
from .models import VirtualMachineType, VirtualMachinePlan, HostingOrder, \
ManageVM, HostingBill
from .opennebula_functions import HostingManageVMAdmin
class HostingOrderAdmin(admin.ModelAdmin): admin.site.register(HostingOrder)
# 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(HostingBill) admin.site.register(HostingBill)

View file

@ -7,39 +7,7 @@ from django.contrib.auth import authenticate
from utils.stripe_utils import StripeUtils from utils.stripe_utils import StripeUtils
from .models import HostingOrder, VirtualMachinePlan, UserHostingKey from .models import HostingOrder, 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
class HostingUserLoginForm(forms.Form): 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

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.4 on 2017-05-12 16:37
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('hosting', '0038_auto_20170512_1006'),
]
operations = [
migrations.AddField(
model_name='hostingorder',
name='price',
field=models.FloatField(default=0),
preserve_default=False,
),
]

View file

@ -1,35 +1,20 @@
from django.shortcuts import redirect from django.shortcuts import redirect
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from .models import VirtualMachinePlan, VirtualMachineType
class ProcessVMSelectionMixin(object): class ProcessVMSelectionMixin(object):
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
configuration = request.POST.get('configuration') #configuration = request.POST.get('configuration')
configuration_display = dict(VirtualMachinePlan.VM_CONFIGURATION).get(configuration) #configuration_display = dict(VirtualMachinePlan.VM_CONFIGURATION).get(configuration)
vm_template = request.POST.get('vm_template') vm_template_id = request.POST.get('vm_template_id')
vm_type = VirtualMachineType.objects.get(id=vm_template)
vm_specs = vm_type.get_specs()
vm_specs.update({ vm_specs.update({
'configuration_display': configuration_display, 'configuration_display': configuration_display,
'configuration': configuration, 'configuration': configuration,
'final_price': vm_type.final_price, 'vm_template_id': vm_template_id
'vm_template': vm_template
}) })
# 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 request.session['vm_specs'] = vm_specs
if not request.user.is_authenticated(): if not request.user.is_authenticated():
request.session['vm_specs'] = vm_specs
request.session['next'] = reverse('hosting:payment') request.session['next'] = reverse('hosting:payment')
return redirect(reverse('hosting:login')) return redirect(reverse('hosting:login'))
return redirect(reverse('hosting:payment')) return redirect(reverse('hosting:payment'))

View file

@ -1,5 +1,7 @@
import os import os
import socket import socket
import logging
import oca import oca
from django.db import models from django.db import models
@ -19,252 +21,15 @@ from .managers import VMPlansManager
from oca.exceptions import OpenNebulaException from oca.exceptions import OpenNebulaException
from oca.pool import WrongNameError from oca.pool import WrongNameError
import logging
logger = logging.getLogger(__name__) 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
}
@classmethod
def get_vm_templates(self, user):
opennebula_client = OpenNebulaManager(
email=user.email,
password=user.password,
)
templates = opennebula_client.get_vm_templates()
for template in templates:
print(OpenNebulaManager.parse_vm(template))
return templates
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, vm_id):
self.status = self.CANCELED_STATUS
self.save(update_fields=['status'])
@classmethod
def terminate_opennebula_vm(self, user, vm_id):
opennebula_client = OpenNebulaManager(
user.email,
user.password,
)
return opennebula_client.terminate_vm(vm_id)
@classmethod
def create_opennebula_vm(self, user, specs):
# import pdb
# pdb.set_trace()
# Init opennebula manager using given user
opennebula_client = OpenNebulaManager(
user.email,
user.password,
)
# 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,
)
# 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,
)
# 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): class HostingOrder(AssignPermissionsMixin, models.Model):
ORDER_APPROVED_STATUS = 'Approved' ORDER_APPROVED_STATUS = 'Approved'
ORDER_DECLINED_STATUS = 'Declined' ORDER_DECLINED_STATUS = 'Declined'
vm_plan = models.ForeignKey(VirtualMachinePlan, related_name='hosting_orders') vm_id = models.IntegerField(default=0)
customer = models.ForeignKey(StripeCustomer) customer = models.ForeignKey(StripeCustomer)
billing_address = models.ForeignKey(BillingAddress) billing_address = models.ForeignKey(BillingAddress)
created_at = models.DateTimeField(auto_now_add=True) created_at = models.DateTimeField(auto_now_add=True)
@ -272,6 +37,7 @@ class HostingOrder(AssignPermissionsMixin, models.Model):
last4 = models.CharField(max_length=4) last4 = models.CharField(max_length=4)
cc_brand = models.CharField(max_length=10) cc_brand = models.CharField(max_length=10)
stripe_charge_id = models.CharField(max_length=100, null=True) stripe_charge_id = models.CharField(max_length=100, null=True)
price = models.FloatField()
permissions = ('view_hostingorder',) permissions = ('view_hostingorder',)
@ -288,9 +54,13 @@ class HostingOrder(AssignPermissionsMixin, models.Model):
return self.ORDER_APPROVED_STATUS if self.approved else self.ORDER_DECLINED_STATUS return self.ORDER_APPROVED_STATUS if self.approved else self.ORDER_DECLINED_STATUS
@classmethod @classmethod
def create(cls, vm_plan=None, customer=None, billing_address=None): def create(cls, price=None, vm_id=None, customer=None, billing_address=None):
instance = cls.objects.create(vm_plan=vm_plan, customer=customer, instance = cls.objects.create(
billing_address=billing_address) price=price,
vm_id=vm_id,
customer=customer,
billing_address=billing_address
)
instance.assign_permissions(customer.user) instance.assign_permissions(customer.user)
return instance return instance
@ -336,17 +106,6 @@ class UserHostingKey(models.Model):
# self.save(update_fields=['public_key']) # self.save(update_fields=['public_key'])
return private_key, 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): class HostingBill(AssignPermissionsMixin, models.Model):
customer = models.ForeignKey(StripeCustomer) customer = models.ForeignKey(StripeCustomer)
billing_address = models.ForeignKey(BillingAddress) billing_address = models.ForeignKey(BillingAddress)
@ -367,32 +126,3 @@ class HostingBill(AssignPermissionsMixin, models.Model):
instance = cls.objects.create(customer=customer, billing_address=billing_address) instance = cls.objects.create(customer=customer, billing_address=billing_address)
return instance 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>
<div class="row"> <div class="row">
<div class="col-sm-6"> <div class="col-sm-6">
{% trans "CH02 ............" %} {% trans "CH02 0900 0000 6071 8848 8%}
</div> </div>
<div class="col-sm-6"> <div class="col-sm-6">
{% trans "POFICHBEXXX" %} {% trans "POFICHBEXXX" %}

View file

@ -12,15 +12,19 @@
{% csrf_token %} {% csrf_token %}
<div class="form-group"> <div class="form-group">
Select VM: Select VM:
<select name="vm_template"> <select name="vm_template_id">
{% for vm in vm_types %} {% 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}} GiB,
SSD: {{template.disk_size}} GiB
</option>
{% endfor %} {% endfor %}
</select> </select>
</div> </div>
<div class="form-group"> <!-- <div class="form-group">
Select VM Configuration: Select VM Configuration:
<select name="configuration"> <select name="configuration">
{% for config in configuration_options %} {% for config in configuration_options %}
@ -29,7 +33,7 @@
{% endfor %} {% endfor %}
</select> </select>
</div> </div> -->
<div class="form-group"> <div class="form-group">
<button class="btn btn-success" >{% trans "Start VM"%} </button> <button class="btn btn-success" >{% trans "Start VM"%} </button>
</div> </div>

View file

@ -49,17 +49,13 @@
<h3><b>{% trans "Order summary"%}</b></h3> <h3><b>{% trans "Order summary"%}</b></h3>
<hr> <hr>
<div class="content"> <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> <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> <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> <hr>
<p><b>{% trans "Memory"%}</b> <span class="pull-right">{{order.vm_plan.memory}} GiB</span></p> <h4>{% trans "Total"%}<p class="pull-right"><b>{{vm.price}} CHF</b></p></h4>
<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>
</div> </div>
<br/> <br/>
{% url 'hosting:payment' as payment_url %} {% url 'hosting:payment' as payment_url %}

View file

@ -100,15 +100,14 @@
<div class="content"> <div class="content">
<!-- <p><b>Type</b> <span class="pull-right">{{request.session.vm_specs.location_code}}</span></p> --> <!-- <p><b>Type</b> <span class="pull-right">{{request.session.vm_specs.location_code}}</span></p> -->
<!-- <hr> --> <!-- <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> <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> <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> <hr>
<p><b>Disk space</b> <span class="pull-right">{{request.session.vm_specs.disk_size}} GiB</span></p> <h4>Total<p
<hr> class="pull-right"><b>{{request.session.template.price }} CHF</b></p></h4>
<h4>Total<p class="pull-right"><b>{{request.session.vm_specs.final_price}} CHF</b></p></h4>
</div> </div>
</div> </div>
</div> </div>

View file

@ -25,12 +25,6 @@
{% trans "Billing"%} {% trans "Billing"%}
</a> </a>
</li> </li>
<li>
<a href="#orders-v" data-toggle="tab">
<i class="fa fa-credit-card"></i>
{% trans "Orders"%}
</a>
</li>
<li> <li>
<a href="#status-v" data-toggle="tab"> <a href="#status-v" data-toggle="tab">
<i class="fa fa-signal" aria-hidden="true"></i> {% trans "Status"%} <i class="fa fa-signal" aria-hidden="true"></i> {% trans "Status"%}
@ -109,55 +103,21 @@
</div> </div>
</div> </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="tab-pane" id="status-v">
<div class="row "> <div class="row ">
<div class="col-md-12 inline-headers"> <div class="col-md-12 inline-headers">
<h3>{% trans "Current status"%}</h3> <h3>{% trans "Current status"%}</h3>
<div class="pull-right space-above"> <div class="pull-right space-above">
{% if virtual_machine.state == 'ACTIVE' %} {% if virtual_machine.state == 'PENDING' %}
<span class="h3 label label-success"><strong> {{virtual_machine.state}}</strong></span> <span class="label
{% elif virtual_machine.state == 'POWEROFF' %} label-warning"><strong>Pending</strong></span>
<span class="h3 label label-danger"><strong>{{virtual_machine.state}}</strong></span> {% elif virtual_machine.state == 'ACTIVE' %}
{% else %} <span class="label
<span class="h3 label label-warning"><strong>{{virtual_machine.state}}</strong></span> label-success"><strong>Online</strong></span>
{% elif virtual_machine.state == 'FAILED'%}
<span class="label
label-danger"><strong>Failed</strong></span>
{% endif %} {% endif %}
</div> </div>
</div> </div>
@ -166,11 +126,12 @@
<div class="row"> <div class="row">
<div class="col-md-12 space-above-big"> <div class="col-md-12 space-above-big">
<div class="pull-right"> <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 %} {% csrf_token %}
</form> </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 "Terminate 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 "Terminate Virtual Machine"%}</button>
</div> </div>
@ -194,7 +155,7 @@
{% trans "Terminate your Virtual Machine"%} {% trans "Terminate your Virtual Machine"%}
</div> </div>
<div class="modal-body"> <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>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">{% trans "Cancel"%}</button> <button type="button" class="btn btn-default" data-dismiss="modal">{% trans "Cancel"%}</button>
@ -220,12 +181,3 @@
</div> </div>
{%endblock%} {%endblock%}

View file

@ -30,15 +30,15 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for vm in vms_opennebula %} {% for vm in vms %}
<tr> <tr>
<td scope="row">{{vm.name}}</td> <td scope="row">{{vm.vm_id}}</td>
<td>{{vm.price|floatformat:2}} CHF</td> <td>{{vm.price}} CHF</td>
<td> <td>
{% if vm.state == 'ACTIVE' %} {% if vm.state == 'ACTIVE' %}
<span class="h3 label label-success"><strong> {{vm.state}}</strong></span> <span class="h3 label label-success"><strong> {{vm.state}}</strong></span>
{% elif vm.state == 'POWEROFF' %} {% elif vm.state == 'FAILED' %}
<span class="h3 label label-danger"><strong>{{vm.state}}</strong></span> <span class="h3 label label-danger"><strong>{{vm.state}}</strong></span>
{% else %} {% else %}
<span class="h3 label label-warning"><strong>{{vm.state}}</strong></span> <span class="h3 label label-warning"><strong>{{vm.state}}</strong></span>
@ -46,7 +46,8 @@
</td> </td>
<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> </td>
</tr> </tr>
{% endfor %} {% endfor %}

View file

@ -20,16 +20,18 @@ from stored_messages.models import Message
from stored_messages.api import mark_read from stored_messages.api import mark_read
from membership.models import CustomUser, StripeCustomer from membership.models import CustomUser, StripeCustomer
from utils.stripe_utils import StripeUtils from utils.stripe_utils import StripeUtils
from utils.forms import BillingAddressForm, PasswordResetRequestForm, UserBillingAddressForm from utils.forms import BillingAddressForm, PasswordResetRequestForm, UserBillingAddressForm
from utils.views import PasswordResetViewMixin, PasswordResetConfirmViewMixin, LoginViewMixin from utils.views import PasswordResetViewMixin, PasswordResetConfirmViewMixin, LoginViewMixin
from utils.mailer import BaseEmail 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 .forms import HostingUserSignupForm, HostingUserLoginForm, UserHostingKeyForm
from .mixins import ProcessVMSelectionMixin 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.exceptions import OpenNebulaException
from oca.pool import WrongNameError from oca.pool import WrongNameError
@ -387,57 +389,26 @@ class PaymentVMView(LoginRequiredMixin, FormView):
context = self.get_context_data() 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) final_price = specifications.get('price', 1)
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)
token = form.cleaned_data.get('token') token = form.cleaned_data.get('token')
owner = self.request.user
# Get or create stripe customer # 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) token=token)
if not customer: if not customer:
form.add_error("__all__", "Invalid credit card") form.add_error("__all__", "Invalid credit card")
return self.render_to_response(self.get_context_data(form=form)) 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 # Create Billing Address
billing_address = form.save() billing_address = form.save()
# Create Billing Address for User if he does not have one
if not customer.user.billing_addresses.count():
billing_address_data.update({
'user': customer.user.id
})
billing_address_user_form = UserBillingAddressForm(billing_address_data)
billing_address_user_form.is_valid()
billing_address_user_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 # Make stripe charge to a customer
stripe_utils = StripeUtils() stripe_utils = StripeUtils()
charge_response = stripe_utils.make_charge(amount=final_price, charge_response = stripe_utils.make_charge(amount=final_price,
@ -454,11 +425,11 @@ class PaymentVMView(LoginRequiredMixin, FormView):
charge = charge_response.get('response_object') charge = charge_response.get('response_object')
# Associate an order with a stripe payment # Create OpenNebulaManager
order.set_stripe_charge(charge) manager = OpenNebulaManager(email=owner.email,
password=owner.password,
# If the Stripe payment was successed, set order status approved create_user=True)
order.set_approved() template = manager.get_template(vm_template_id)
# Get user ssh key # Get user ssh key
try: try:
@ -466,26 +437,46 @@ class PaymentVMView(LoginRequiredMixin, FormView):
user=self.request.user user=self.request.user
) )
# Add ssh_key to specs
specs.update({
'ssh_key': user_key.public_key
})
except UserHostingKey.DoesNotExist: except UserHostingKey.DoesNotExist:
pass pass
# Create a vm using logged user # Create a vm using logged user
opennebula_vm_id = VirtualMachinePlan.create_opennebula_vm( vm_id = manager.create_vm(
self.request.user, vm_template_id,
specs ssh_key=user_key.public_key
) )
plan.opennebula_id = opennebula_vm_id # Create a Hosting Order
plan.save() order = HostingOrder.create(
price=final_price,
vm_id=vm_id,
customer=customer,
billing_address=billing_address
)
# Create a Hosting Bill
bill = HostingBill.create(customer=customer, billing_address=billing_address)
# Create Billing Address for User if he does not have one
if not customer.user.billing_addresses.count():
billing_address_data.update({
'user': customer.user.id
})
billing_address_user_form = UserBillingAddressForm(billing_address_data)
billing_address_user_form.is_valid()
billing_address_user_form.save()
# 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()
vm = VirtualMachineSerializer(manager.get_vm(vm_id)).data
# Send notification to ungleich as soon as VM has been booked # Send notification to ungleich as soon as VM has been booked
context = { context = {
'vm': plan, 'vm': vm,
'order': order, 'order': order,
'base_url': "{0}://{1}".format(request.scheme, request.get_host()) 'base_url': "{0}://{1}".format(request.scheme, request.get_host())
@ -512,6 +503,18 @@ class OrdersHostingDetailView(PermissionRequiredMixin, LoginRequiredMixin, Detai
permission_required = ['view_hostingorder'] permission_required = ['view_hostingorder']
model = 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,
create_user=True)
vm = manager.get_vm(obj.vm_id)
context['vm'] = VirtualMachineSerializer(vm).data
return context
class OrdersHostingListView(LoginRequiredMixin, ListView): class OrdersHostingListView(LoginRequiredMixin, ListView):
template_name = "hosting/orders.html" template_name = "hosting/orders.html"
@ -537,24 +540,17 @@ class VirtualMachinesPlanListView(LoginRequiredMixin, ListView):
template_name = "hosting/virtual_machines.html" template_name = "hosting/virtual_machines.html"
login_url = reverse_lazy('hosting:login') login_url = reverse_lazy('hosting:login')
context_object_name = "vms" context_object_name = "vms"
model = VirtualMachinePlan
paginate_by = 10 paginate_by = 10
ordering = '-id' 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): def get_queryset(self):
# hosting_admin = HostingManageVMAdmin.__new__(HostingManageVMAdmin) owner = self.request.user
# print(hosting_admin.show_vms_view(self.request)) manager = OpenNebulaManager(email=owner.email,
# print(VirtualMachinePlan.get_vms(self.request.user.)) password=owner.password,
user = self.request.user create_user=True)
self.queryset = VirtualMachinePlan.objects.active(user) queryset = manager.get_vms()
return super(VirtualMachinesPlanListView, self).get_queryset() serializer = VirtualMachineSerializer(queryset, many=True)
return serializer.data
class CreateVirtualMachinesView(LoginRequiredMixin, View): class CreateVirtualMachinesView(LoginRequiredMixin, View):
@ -574,107 +570,70 @@ class CreateVirtualMachinesView(LoginRequiredMixin, View):
) )
return HttpResponseRedirect(reverse('hosting:key_pair')) return HttpResponseRedirect(reverse('hosting:key_pair'))
#TODO: Replace with OpenNebulaManager.get_apps
templates = OpenNebulaManager().get_templates()
data = VirtualMachineTemplateSerializer(templates, many=True).data
context = { context = {
'vm_types': VirtualMachineType.get_serialized_vm_types(), 'templates': data,
'configuration_options': VirtualMachinePlan.VM_CONFIGURATION
} }
# context = {} # context = {}
return render(request, self.template_name, context) return render(request, self.template_name, context)
def post(self, request): def post(self, request):
configuration = request.POST.get('configuration') template_id = int(request.POST.get('vm_template_id'))
configuration_display = dict(VirtualMachinePlan.VM_CONFIGURATION).get(configuration) template = OpenNebulaManager().get_template(template_id)
vm_template = request.POST.get('vm_template') data = VirtualMachineTemplateSerializer(template).data
vm_type = VirtualMachineType.objects.get(id=vm_template) request.session['template'] = data
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
return redirect(reverse('hosting:payment')) 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(LoginRequiredMixin, View):
class VirtualMachineView(PermissionRequiredMixin, LoginRequiredMixin, View):
template_name = "hosting/virtual_machine_detail.html" template_name = "hosting/virtual_machine_detail.html"
login_url = reverse_lazy('hosting:login') login_url = reverse_lazy('hosting:login')
# model = VirtualMachinePlan
# context_object_name = "virtual_machine"
permission_required = []
# 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_object(self): def get_object(self):
opennebula_vm_id = self.kwargs.get('pk') owner = self.request.user
opennebula_vm = None vm = None
manager = OpenNebulaManager(
email=owner.email,
password=owner.password,
create_user=True
)
vm_id = self.kwargs.get('pk')
try: try:
opennebula_vm = VirtualMachinePlan.objects.get(opennebula_id=opennebula_vm_id) vm = manager.get_vm(vm_id)
except Exception as error: except Exception as error:
print(error) print(error)
raise Http404() raise Http404()
return opennebula_vm return vm
def get_success_url(self): def get_success_url(self):
final_url = reverse('hosting:virtual_machines') final_url = reverse('hosting:virtual_machines')
return final_url return final_url
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
opennebula_vm_id = self.kwargs.get('pk') vm = self.get_object()
try: serializer = VirtualMachineSerializer(vm)
opennebula_vm = VirtualMachinePlan.get_vm(
self.request.user,
opennebula_vm_id
)
except Exception as error:
print(error)
raise Http404()
context = { context = {
'virtual_machine': opennebula_vm, 'virtual_machine': serializer.data,
} }
# context = {}
return render(request, self.template_name, context) return render(request, self.template_name, context)
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
owner = self.request.user
vm = self.get_object() vm = self.get_object()
opennebula_vm_id = self.kwargs.get('pk') opennebula_vm_id = self.kwargs.get('pk')
terminated = VirtualMachinePlan.terminate_opennebula_vm( manager = OpenNebulaManager(
self.request.user, email=owner.email,
opennebula_vm_id password=owner.password,
create_user=True
)
terminated = manager.delete_vm(
vm.id
) )
if not terminated: if not terminated:
@ -684,8 +643,6 @@ class VirtualMachineView(PermissionRequiredMixin, LoginRequiredMixin, View):
) )
return HttpResponseRedirect(self.get_success_url()) return HttpResponseRedirect(self.get_success_url())
#vm.cancel_plan()
context = { context = {
'vm': vm, 'vm': vm,
'base_url': "{0}://{1}".format(self.request.scheme, self.request.get_host()) 'base_url': "{0}://{1}".format(self.request.scheme, self.request.get_host())
@ -735,10 +692,14 @@ class HostingBillDetailView(PermissionRequiredMixin, LoginRequiredMixin, DetailV
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
# Get context # Get context
context = super(DetailView, self).get_context_data(**kwargs) context = super(DetailView, self).get_context_data(**kwargs)
owner = self.request.user
manager = OpenNebulaManager(email=owner.email,
password=owner.password,
create_user=True)
# Get vms # Get vms
try: queryset = manager.get_vms()
context['vms'] = self.get_object().get_vms() vms = VirtualMachineSerializer(queryset, many=True).data
except: context['vms'] = vms
pass
return context 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,110 +1,14 @@
import oca import oca
import socket import socket
import logging
from django.db import models
from django.conf import settings from django.conf import settings
from django.utils.functional import cached_property
from oca.pool import WrongNameError from oca.pool import WrongNameError
from oca.exceptions import OpenNebulaException
class VirtualMachineTemplate(models.Model): logger = logging.getLogger(__name__)
"""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()
def calculate_price(self):
template = OpenNebulaManager()._get_template(self.opennebula_id).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 = OpenNebulaManager()._get_template(template_id=self.opennebula_id)
return template.name
def get_cores(self):
template = OpenNebulaManager()._get_template(template_id=self.opennebula_id).template
return int(template.vcpu)
def get_disk_size(self):
template = OpenNebulaManager()._get_template(template_id=self.opennebula_id).template
disk_size = 0
for disk in template.disks:
disk_size += int(disk.size)
return disk_size / 1024
def get_memory(self):
template = OpenNebulaManager()._get_template(template_id=self.opennebula_id).template
return int(template.memory) / 1024
class VirtualMachine(models.Model):
"""This class represents an opennebula virtual machine."""
opennebula_id = models.IntegerField()
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',
}
def get_name(self):
vm = OpenNebulaManager()._get_vm(vm_id=self.opennebula_id)
return 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):
vm = OpenNebulaManager()._get_vm(vm_id=self.opennebula_id)
return vm.id
def get_ip(self):
vm = OpenNebulaManager()._get_vm(vm_id=self.opennebula_id)
try:
return vm.user_template.ungleich_public_ip
except AttributeError:
return '-'
def get_state(self):
vm = OpenNebulaManager()._get_vm(vm_id=self.opennebula_id)
return self.VM_STATE.get(str(vm.state))
def get_price(self):
return self.vm_template.calculate_price()
class OpenNebulaManager(): class OpenNebulaManager():
"""This class represents an opennebula manager.""" """This class represents an opennebula manager."""
@ -175,8 +79,13 @@ class OpenNebulaManager():
def _get_vm_pool(self): def _get_vm_pool(self):
try: try:
vm_pool = oca.VirtualMachinePool(self.oneadmin_client) vm_pool = oca.VirtualMachinePool(self.client)
vm_pool.info() vm_pool.info()
except AttributeError:
print('Could not connect via client, using oneadmin instead')
vm_pool = oca.VirtualMachinePool(self.oneadmin_client)
vm_pool.info(filter=-2)
#TODO: Replace with logger #TODO: Replace with logger
except ConnectionRefusedError: except ConnectionRefusedError:
print('Could not connect to host: {host} via protocol {protocol}'.format( print('Could not connect to host: {host} via protocol {protocol}'.format(
@ -186,19 +95,25 @@ class OpenNebulaManager():
raise ConnectionRefusedError raise ConnectionRefusedError
return vm_pool 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() vm_pool = self._get_vm_pool()
# Get virtual machines from all users return vm_pool.get_by_id(int(vm_id))
vm_pool.info(filter=-2)
return vm_pool.get_by_id(vm_id)
def create_vm(self, template_id): #TODO: get app with id
template_pool = self._get_template_pool() def create_vm(self, template_id, app_id=None, ssh_key=None):
extra_template = "<CONTEXT><SSH_PUBLIC_KEY>{ssh_key}</SSH_PUBLIC_KEY></CONTEXT>".format(
template = template_pool.get_by_id(template_id) ssh_key=ssh_key
)
vm_id = template.instantiate() vm_id = self.oneadmin_client.call(
oca.VmTemplate.METHODS['instantiate'],
template_id,
'',
False,
extra_template
)
try: try:
self.oneadmin_client.call( self.oneadmin_client.call(
oca.VirtualMachine.METHODS['chown'], oca.VirtualMachine.METHODS['chown'],
@ -207,12 +122,33 @@ class OpenNebulaManager():
self.opennebula_user.group_ids[0] self.opennebula_user.group_ids[0]
) )
except AttributeError: except AttributeError:
pass print('Could not change owner, opennebula_user is not set.')
return vm_id return vm_id
def delete_vm(self, vm_id): def delete_vm(self, vm_id):
vm = self._get_vm(vm_id) TERMINATE_ACTION = 'terminate'
vm.delete() vm_terminated = False
try:
self.oneadmin_client.call(
oca.VirtualMachine.METHODS['action'],
TERMINATE_ACTION,
int(vm_id),
)
vm_terminated = True
except socket.timeout as socket_err:
logger.info("Socket timeout error: {0}".format(socket_err))
print("Socket timeout error: {0}".format(socket_err))
except OpenNebulaException as opennebula_err:
logger.info("OpenNebulaException error: {0}".format(opennebula_err))
print("OpenNebulaException error: {0}".format(opennebula_err))
except OSError as os_err:
logger.info("OSError : {0}".format(os_err))
print("OSError : {0}".format(os_err))
except ValueError as value_err:
logger.info("ValueError : {0}".format(value_err))
print("ValueError : {0}".format(value_err))
return vm_terminated
def _get_template_pool(self): def _get_template_pool(self):
try: try:
@ -227,19 +163,31 @@ class OpenNebulaManager():
raise ConnectionRefusedError raise ConnectionRefusedError
return template_pool 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() template_pool = self._get_template_pool()
return template_pool.get_by_id(template_id) 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. """Create and add a new template to opennebula.
:param name: A string representation describing the template. :param name: A string representation describing the template.
Used as label in view. Used as label in view.
:param cores: Amount of virtual cpu cores for the VM. :param cores: Amount of virtual cpu cores for the VM.
:param memory: Amount of RAM for the VM (MB) :param memory: Amount of RAM for the VM (GB)
:param disk_size: Amount of disk space for VM (MB) :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> template_string_formatter = """<TEMPLATE>
<NAME>{name}</NAME> <NAME>{name}</NAME>
@ -251,6 +199,10 @@ class OpenNebulaManager():
<SIZE>{size}</SIZE> <SIZE>{size}</SIZE>
<DEV_PREFIX>vd</DEV_PREFIX> <DEV_PREFIX>vd</DEV_PREFIX>
</DISK> </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>
""" """
template_id = oca.VmTemplate.allocate( template_id = oca.VmTemplate.allocate(
@ -260,7 +212,12 @@ class OpenNebulaManager():
vcpu=cores, vcpu=cores,
cpu=0.1*cores, cpu=0.1*cores,
size=1024 * disk_size, 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
) )
) )
@ -269,5 +226,3 @@ class OpenNebulaManager():
def delete_template(self, template_id): def delete_template(self, template_id):
self.oneadmin_client.call(oca.VmTemplate.METHODS['delete'], template_id, False) self.oneadmin_client.call(oca.VmTemplate.METHODS['delete'], template_id, False)

View file

@ -3,91 +3,117 @@ import oca
from rest_framework import serializers from rest_framework import serializers
from oca import OpenNebulaException 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.""" """Serializer to map the virtual machine template instance into JSON format."""
cores = serializers.IntegerField(source='get_cores') id = serializers.IntegerField(read_only=True)
name = serializers.CharField(source='get_name') name = serializers.CharField()
disk_size = serializers.IntegerField(source='get_disk_size') cores = serializers.IntegerField(source='template.vcpu')
memory = serializers.IntegerField(source='get_memory') 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: def create(self, validated_data):
model = VirtualMachineTemplate data = validated_data
fields = ('id', 'name', 'cores', 'memory', 'disk_size', 'base_price', template = data.pop('template')
'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')
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) manager = OpenNebulaManager(create_user = False)
try: try:
opennebula_id = manager.create_template(name=name, cores=cores, opennebula_id = manager.create_template(name=name, cores=cores,
memory=memory, memory=memory,
disk_size=disk_size) disk_size=disk_size,
data.update({'opennebula_id':opennebula_id}) core_price=core_price,
disk_size_price=disk_size_price,
memory_price=memory_price)
except OpenNebulaException as err: except OpenNebulaException as err:
raise serializers.ValidationError("OpenNebulaException occured. {0}".format(err)) raise serializers.ValidationError("OpenNebulaException occured. {0}".format(err))
return data return manager.get_template(template_id=opennebula_id)
def create(self, validated_data): def get_disk_size(self, obj):
return VirtualMachineTemplate.objects.create(**validated_data) 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 get_price(self, obj):
def display_value(self, instance): template = obj.template
return 'Template: {}'.format(instance.get_name()) 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.""" """Serializer to map the virtual machine instance into JSON format."""
#TODO: Maybe we can change to template.get_cores name = serializers.CharField(read_only=True)
cores = serializers.IntegerField(read_only=True, source='get_cores') cores = serializers.IntegerField(read_only=True, source='template.vcpu')
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')
vm_template = VirtualMachineTemplateSerializer(read_only=True) 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_id = TemplatePrimaryKeyRelatedField( template_id = serializers.ChoiceField(
queryset=VirtualMachineTemplate.objects.all(), choices=[(key.id, key.name) for key in
source='vm_template' OpenNebulaManager().get_templates()],
source='template.template_id',
write_only=True
) )
class Meta: def create(self, validated_data):
model = VirtualMachine owner = validated_data['owner']
fields = ('id', 'opennebula_id', 'vm_template', 'vm_template_id', 'cores', 'name', template_id = validated_data['template']['template_id']
'disk_size', 'memory', 'ip', 'deploy_id', 'state', 'vm_id',
'price')
read_only_fields = ('opennebula_id', )
def validate(self, data):
# Create the opennebula model
manager = OpenNebulaManager(create_user = False)
try: try:
template_id = data['vm_template'].opennebula_id manager = OpenNebulaManager(email=owner.email,
password=owner.password,
create_user = True)
opennebula_id = manager.create_vm(template_id) opennebula_id = manager.create_vm(template_id)
data.update({'opennebula_id':opennebula_id})
except OpenNebulaException as err: except OpenNebulaException as err:
raise serializers.ValidationError("OpenNebulaException occured. {0}".format(err)) raise serializers.ValidationError("OpenNebulaException occured. {0}".format(err))
return data return manager.get_vm(opennebula_id)
def create(self, validated_data): def get_memory(self, obj):
return VirtualMachine.objects.create(**validated_data) return int(obj.template.memory)/1024
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

18
opennebula_api/urls.py Normal file
View file

@ -0,0 +1,18 @@
from django.conf.urls import url, include
from rest_framework.urlpatterns import format_suffix_patterns
from .views import TemplateCreateView, TemplateDetailsView,\
VmCreateView, VmDetailsView
urlpatterns = {
url(r'^auth/', include('rest_framework.urls', namespace='rest_framework')),
url(r'^templates/$', TemplateCreateView.as_view(), name="template_create"),
url(r'^templates/(?P<pk>[0-9]+)/$', TemplateDetailsView.as_view(),
name="templates_details"),
url(r'^vms/$', VmCreateView.as_view(), name="vm_create"),
url(r'^vms/(?P<pk>[0-9]+)/$', VmDetailsView.as_view(),
name="vm_details"),
}
urlpatterns = format_suffix_patterns(urlpatterns)

View file

@ -1,13 +1,29 @@
from rest_framework import generics from rest_framework import generics
from rest_framework import permissions
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth import authenticate, login
from utils.views import LoginViewMixin
from membership.models import CustomUser, StripeCustomer
from guardian.mixins import PermissionRequiredMixin
from .serializers import VirtualMachineTemplateSerializer, \ from .serializers import VirtualMachineTemplateSerializer, \
VirtualMachineSerializer VirtualMachineSerializer
from .models import VirtualMachineTemplate, VirtualMachine, OpenNebulaManager from .models import OpenNebulaManager
class TemplateCreateView(generics.ListCreateAPIView): class TemplateCreateView(generics.ListCreateAPIView):
"""This class defines the create behavior of our rest api.""" """This class handles the GET and POST requests."""
queryset = VirtualMachineTemplate.objects.all()
serializer_class = VirtualMachineTemplateSerializer serializer_class = VirtualMachineTemplateSerializer
permission_classes = (permissions.IsAuthenticated, permissions.IsAdminUser)
def get_queryset(self):
manager = OpenNebulaManager()
return manager.get_templates()
def perform_create(self, serializer): def perform_create(self, serializer):
"""Save the post data when creating a new template.""" """Save the post data when creating a new template."""
@ -16,20 +32,53 @@ class TemplateCreateView(generics.ListCreateAPIView):
class TemplateDetailsView(generics.RetrieveUpdateDestroyAPIView): class TemplateDetailsView(generics.RetrieveUpdateDestroyAPIView):
"""This class handles the http GET, PUT and DELETE requests.""" """This class handles the http GET, PUT and DELETE requests."""
queryset = VirtualMachineTemplate.objects.all()
serializer_class = VirtualMachineTemplateSerializer serializer_class = VirtualMachineTemplateSerializer
permission_classes = (permissions.IsAuthenticated)
def get_queryset(self):
manager = OpenNebulaManager()
return manager.get_templates()
class VmCreateView(generics.ListCreateAPIView): class VmCreateView(generics.ListCreateAPIView):
"""This class defines the create behavior of our rest api.""" """This class handles the GET and POST requests."""
queryset = VirtualMachine.objects.all()
serializer_class = VirtualMachineSerializer serializer_class = VirtualMachineSerializer
permission_classes = (permissions.IsAuthenticated, )
def get_queryset(self):
owner = self.request.user
manager = OpenNebulaManager(email=owner.email,
password=owner.password,
create_user=True)
return manager.get_vms()
def perform_create(self, serializer): def perform_create(self, serializer):
"""Save the post data when creating a new template.""" """Save the post data when creating a new template."""
serializer.save() serializer.save(owner=self.request.user)
class VmDetailsView(generics.RetrieveUpdateDestroyAPIView): class VmDetailsView(generics.RetrieveUpdateDestroyAPIView):
"""This class handles the http GET, PUT and DELETE requests.""" """This class handles the http GET, PUT and DELETE requests."""
permission_classes = (permissions.IsAuthenticated, )
queryset = VirtualMachine.objects.all()
serializer_class = VirtualMachineSerializer serializer_class = VirtualMachineSerializer
def get_queryset(self):
owner = self.request.user
manager = OpenNebulaManager(email=owner.email,
password=owner.password,
create_user=True)
return manager.get_vms()
def get_object(self):
owner = self.request.user
manager = OpenNebulaManager(email=owner.email,
password=owner.password,
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,
create_user = True)
manager.delete_vm(instance.id)