merged opnnebula api changes
This commit is contained in:
commit
8980f6b2fc
19 changed files with 458 additions and 888 deletions
|
@ -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"),
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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):
|
||||||
|
|
||||||
|
|
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',
|
||||||
|
),
|
||||||
|
]
|
21
hosting/migrations/0039_hostingorder_price.py
Normal file
21
hosting/migrations/0039_hostingorder_price.py
Normal 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,
|
||||||
|
),
|
||||||
|
]
|
|
@ -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'))
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
|
@ -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" %}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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 %}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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%}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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 %}
|
||||||
|
|
251
hosting/views.py
251
hosting/views.py
|
@ -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
|
||||||
|
|
|
@ -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,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)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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
18
opennebula_api/urls.py
Normal 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)
|
|
@ -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)
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue