Merge branch 'opennebula_api' of github.com:ungleich/dynamicweb into opennebula_api

This commit is contained in:
Levi 2017-05-13 12:22:21 -05:00
commit e6ec14dfe7
10 changed files with 174 additions and 111 deletions

View file

@ -4,8 +4,9 @@ from django.core.urlresolvers import reverse
from utils.mailer import BaseEmail from utils.mailer import BaseEmail
from .models import HostingOrder, HostingBill from .models import HostingOrder, HostingBill, HostingPlan
admin.site.register(HostingOrder) admin.site.register(HostingOrder)
admin.site.register(HostingBill) admin.site.register(HostingBill)
admin.site.register(HostingPlan)

View file

@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.4 on 2017-05-13 11:35
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('hosting', '0039_hostingorder_price'),
]
operations = [
migrations.CreateModel(
name='HostingPlan',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('disk_size', models.FloatField(default=0.0)),
('cpu_cores', models.FloatField(default=0.0)),
('memory', models.FloatField(default=0.0)),
],
),
]

View file

@ -21,6 +21,31 @@ from .managers import VMPlansManager
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class HostingPlan(models.Model):
disk_size = models.FloatField(default=0.0)
cpu_cores = models.FloatField(default=0.0)
memory = models.FloatField(default=0.0)
def serialize(self):
return {
'id': self.id,
'cpu':self.cpu_cores,
'memory': self.memory,
'disk_size': self.disk_size,
'price': self.price(),
}
@classmethod
def get_serialized_configs(cls):
return [cfg.serialize()
for cfg in cls.objects.all()]
def price(self):
price = self.disk_size * 0.2
price += self.cpu_cores * 5
price += self.memory * 2
return price
class HostingOrder(AssignPermissionsMixin, models.Model): class HostingOrder(AssignPermissionsMixin, models.Model):
ORDER_APPROVED_STATUS = 'Approved' ORDER_APPROVED_STATUS = 'Approved'

View file

@ -4,7 +4,7 @@
{% block content %} {% block content %}
<div class="container"> <div class="container">
<div class="orders-container"> <div class="orders-container" style="padding-bottom: 15%">
{# Adress bar #} {# Adress bar #}
<div class="row"> <div class="row">
<div class="invoice-title"> <div class="invoice-title">
@ -78,14 +78,14 @@
</div> </div>
<div class="row"> <div class="row">
<div class="col-sm-6"> <div class="col-sm-6">
{% trans "CH02 0900 0000 6071 8848 8%} {% trans "CH02 0900 0000 6071 8848 8" %}
</div> </div>
<div class="col-sm-6"> <div class="col-sm-6">
{% trans "POFICHBEXXX" %} {% trans "POFICHBEXXX" %}
</div> </div>
</div> </div>
</div> </div>
<div> </div>
</div> </div>
{% endblock %} {% endblock %}

View file

@ -11,26 +11,22 @@
<form method="POST" action=""> <form method="POST" action="">
{% csrf_token %} {% csrf_token %}
<div class="form-group"> <div class="form-group">
Select VM: Select VM Template:
<select name="vm_template_id"> <select name="vm_template_id">
{% for template in templates %} {% for template in templates %}
<option value="{{template.id}}">{{template.name}} </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="vm_image_id"> <select name="configuration">
{% for image in images %} {% for config in configuration_options %}
<option value="{{config.id}}">
<option value="{{image.id}}">{{image.name}} </option> CORE: {{config.cpu|floatformat}},
RAM: {{config.memory|floatformat}} GiB,
SSD: {{config.disk_size|floatformat}} GiB
</option>
{% endfor %} {% endfor %}
</select> </select>
</div> </div>

View file

@ -25,7 +25,7 @@
<tr> <tr>
<td scope="row">{{ order.id }}</td> <td scope="row">{{ order.id }}</td>
<td>{{ order.created_at }}</td> <td>{{ order.created_at }}</td>
<td>{{ order.vm_plan.price }} CHF</td> <td>{{ order.price }} CHF</td>
<td>{% if order.approved %} <td>{% if order.approved %}
<span class="text-success strong">{% trans "Approved"%}</span> <span class="text-success strong">{% trans "Approved"%}</span>
{% else %} {% else %}
@ -99,4 +99,4 @@
</div> </div>
{% endblock %} {% endblock %}

View file

@ -100,14 +100,17 @@
<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.template.cores}}</span></p> <p><b>Cores</b> <span
class="pull-right">{{request.session.specs.cpu|floatformat}}</span></p>
<hr> <hr>
<p><b>Memory</b> <span class="pull-right">{{request.session.template.memory}} GiB</span></p> <p><b>Memory</b> <span
class="pull-right">{{request.session.specs.memory|floatformat}} GiB</span></p>
<hr> <hr>
<p><b>Disk space</b> <span class="pull-right">{{request.session.template.disk_size}} GiB</span></p> <p><b>Disk space</b> <span
class="pull-right">{{request.session.specs.disk_size|floatformat}} GiB</span></p>
<hr> <hr>
<h4>Total<p <h4>Total<p
class="pull-right"><b>{{request.session.template.price }} CHF</b></p></h4> class="pull-right"><b>{{request.session.specs.price }} CHF</b></p></h4>
</div> </div>
</div> </div>
</div> </div>

View file

@ -25,14 +25,13 @@ 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 HostingOrder, HostingBill, UserHostingKey from .models import HostingOrder, HostingBill, HostingPlan, UserHostingKey
from .forms import HostingUserSignupForm, HostingUserLoginForm, UserHostingKeyForm from .forms import HostingUserSignupForm, HostingUserLoginForm, UserHostingKeyForm
from .mixins import ProcessVMSelectionMixin from .mixins import ProcessVMSelectionMixin
from opennebula_api.models import OpenNebulaManager from opennebula_api.models import OpenNebulaManager
from opennebula_api.serializers import VirtualMachineSerializer,\ from opennebula_api.serializers import VirtualMachineSerializer,\
VirtualMachineTemplateSerializer,\ VirtualMachineTemplateSerializer
ImageSerializer
from oca.exceptions import OpenNebulaException from oca.exceptions import OpenNebulaException
@ -407,12 +406,12 @@ class PaymentVMView(LoginRequiredMixin, FormView):
context = self.get_context_data() context = self.get_context_data()
specifications = request.session.get('template') template = request.session.get('template')
specs = request.session.get('specs')
vm_template_id = specifications.get('id', 1) vm_template_id = template.get('id', 1)
vm_image_id = request.session.get('image').get('id', 1)
final_price = specifications.get('price', 1) final_price = specs.get('price')
token = form.cleaned_data.get('token') token = form.cleaned_data.get('token')
@ -455,12 +454,14 @@ class PaymentVMView(LoginRequiredMixin, FormView):
except UserHostingKey.DoesNotExist: except UserHostingKey.DoesNotExist:
pass pass
# Create a vm using logged user # Create a vm using logged user
vm_id = manager.create_vm( vm_id = manager.create_vm(
template_id=vm_template_id, template_id=vm_template_id,
#XXX: Confi
specs=specs,
ssh_key=user_key.public_key, ssh_key=user_key.public_key,
image_id=vm_image_id,
) )
# Create a Hosting Order # Create a Hosting Order
@ -587,11 +588,11 @@ class CreateVirtualMachinesView(LoginRequiredMixin, View):
manager = OpenNebulaManager() manager = OpenNebulaManager()
templates = manager.get_templates() templates = manager.get_templates()
images = manager.get_images() configuration_options = HostingPlan.get_serialized_configs()
context = { context = {
'templates': VirtualMachineTemplateSerializer(templates, many=True).data, 'templates': VirtualMachineTemplateSerializer(templates, many=True).data,
'images' : ImageSerializer(images, many=True).data 'configuration_options' : configuration_options,
} }
return render(request, self.template_name, context) return render(request, self.template_name, context)
@ -599,10 +600,11 @@ class CreateVirtualMachinesView(LoginRequiredMixin, View):
manager = OpenNebulaManager() manager = OpenNebulaManager()
template_id = request.POST.get('vm_template_id') template_id = request.POST.get('vm_template_id')
template = manager.get_template(template_id) template = manager.get_template(template_id)
image_id = request.POST.get('vm_image_id') configuration_id = int(request.POST.get('configuration'))
image = manager.get_image(image_id) configuration = HostingPlan.objects.get(id=configuration_id)
request.session['template'] = VirtualMachineTemplateSerializer(template).data request.session['template'] = VirtualMachineTemplateSerializer(template).data
request.session['image'] = ImageSerializer(image).data
request.session['specs'] = configuration.serialize()
return redirect(reverse('hosting:payment')) return redirect(reverse('hosting:payment'))
@ -715,6 +717,10 @@ class HostingBillDetailView(PermissionRequiredMixin, LoginRequiredMixin, DetailV
# Get vms # Get vms
queryset = manager.get_vms() queryset = manager.get_vms()
vms = VirtualMachineSerializer(queryset, many=True).data vms = VirtualMachineSerializer(queryset, many=True).data
# Set total price
bill = context['bill']
bill.total_price = 0.0
for vm in vms:
bill.total_price += vm['price']
context['vms'] = vms context['vms'] = vms
return context return context

View file

@ -109,37 +109,64 @@ class OpenNebulaManager():
except: except:
return None return None
def create_vm(self, template_id, image_id=None, ssh_key=None): def create_template(self, name, cores, memory, disk_size, core_price, memory_price,
extra_template_formater = """<CONTEXT> disk_size_price, ssh='' ):
<SSH_PUBLIC_KEY>{ssh_key}</SSH_PUBLIC_KEY> """Create and add a new template to opennebula.
</CONTEXT> :param name: A string representation describing the template.
<DISK> Used as label in view.
<IMAGE_ID>{image_id}</IMAGE_ID> :param cores: Amount of virtual cpu cores for the VM.
</DISK> :param memory: Amount of RAM for the VM (GB)
""" :param disk_size: Amount of disk space for VM (GB)
:param core_price: Price of virtual cpu for the VM per core.
:param memory_price: Price of RAM for the VM per GB
:param disk_size_price: Price of disk space for VM per GB
:param ssh: User public ssh key
"""
template_id = oca.VmTemplate.allocate(
self.oneadmin_client,
template_string_formatter.format(
name=name,
vcpu=cores,
cpu=0.1*cores,
size=1024 * disk_size,
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
)
)
def create_vm(self, template_id, specs, ssh_key=None):
template = self.get_template(template_id) template = self.get_template(template_id)
vm_id = template.instantiate(name ='', pending=False, extra_template='') vm_specs_formatter = """<TEMPLATE>
image = self.get_image(image_id) <MEMORY>{memory}</MEMORY>
<VCPU>{vcpu}</VCPU>
<CPU>{cpu}</CPU>
<DISK>
<TYPE>fs</TYPE>
<SIZE>{size}</SIZE>
<DEV_PREFIX>vd</DEV_PREFIX>
</DISK>
<CONTEXT>
<SSH_PUBLIC_KEY>{ssh}</SSH_PUBLIC_KEY>
</CONTEXT>
</TEMPLATE>
"""
vm_id = template.instantiate(name ='',
pending=False,
extra_template=vm_specs_formatter.format(
vcpu=int(specs['cpu']),
cpu=0.1* int(specs['cpu']),
size=1024 * int(specs['disk_size']),
memory=1024 * int(specs['memory']),
ssh=ssh_key
)
)
image_name = "{image_name}{template_name}{vm_id}".format(
image_name=image.name,
template_name=template.name,
vm_id = vm_id,
)
image_id = image.clone(name=image_name)
self.oneadmin_client.call(
oca.VmTemplate.METHODS['update'],
vm_id,
extra_template_formater.format(
ssh_key=ssh_key,
image_id=image_id
),
# 0 = Replace / 1 = Merge
1,
)
try: try:
self.oneadmin_client.call( self.oneadmin_client.call(
oca.VirtualMachine.METHODS['chown'], oca.VirtualMachine.METHODS['chown'],
@ -176,7 +203,6 @@ class OpenNebulaManager():
try: try:
template_pool = oca.VmTemplatePool(self.oneadmin_client) template_pool = oca.VmTemplatePool(self.oneadmin_client)
template_pool.info() template_pool.info()
#TODO: Replace with logger
except ConnectionRefusedError: except ConnectionRefusedError:
logger.info('Could not connect to host: {host} via protocol {protocol}'.format( logger.info('Could not connect to host: {host} via protocol {protocol}'.format(
host=settings.OPENNEBULA_DOMAIN, host=settings.OPENNEBULA_DOMAIN,
@ -262,38 +288,3 @@ class OpenNebulaManager():
self.opennebula_user.id, self.opennebula_user.id,
new_password new_password
) )
def _get_image_pool(self):
try:
image_pool = oca.ImagePool(self.oneadmin_client)
image_pool.info()
#TODO: Replace with logger
except ConnectionRefusedError:
logger.info('Could not connect to host: {host} via protocol {protocol}'.format(
host=settings.OPENNEBULA_DOMAIN,
protocol=settings.OPENNEBULA_PROTOCOL)
)
raise ConnectionRefusedError
return image_pool
def get_images(self):
try:
public_images = [
image
for image in self._get_image_pool()
if 'public-' in image.name
]
return public_images
except ConnectionRefusedError:
return []
pass
def get_image(self, image_id):
image_id = int(image_id)
try:
image_pool = self._get_image_pool()
return image_pool.get_by_id(image_id)
except:
return None

View file

@ -10,10 +10,12 @@ from .models import OpenNebulaManager
class VirtualMachineTemplateSerializer(serializers.Serializer): 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."""
id = serializers.IntegerField(read_only=True) id = serializers.IntegerField(read_only=True)
name = serializers.CharField() set_name = serializers.CharField(read_only=True, label='Name')
name = serializers.SerializerMethodField()
cores = serializers.IntegerField(source='template.vcpu') cores = serializers.IntegerField(source='template.vcpu')
disk = serializers.IntegerField(write_only=True) disk = serializers.IntegerField(write_only=True)
disk_size = serializers.SerializerMethodField() disk_size = serializers.SerializerMethodField()
set_memory = serializers.IntegerField(write_only=True, label='Memory')
memory = serializers.SerializerMethodField() memory = serializers.SerializerMethodField()
core_price = serializers.FloatField(source='template.cpu_cost') core_price = serializers.FloatField(source='template.cpu_cost')
disk_size_price = serializers.FloatField(source='template.disk_cost') disk_size_price = serializers.FloatField(source='template.disk_cost')
@ -63,20 +65,28 @@ class VirtualMachineTemplateSerializer(serializers.Serializer):
def get_memory(self, obj): def get_memory(self, obj):
return int(obj.template.memory)/1024 return int(obj.template.memory)/1024
def get_name(self, obj):
# TODO: Filter public- away
return obj.name
class VirtualMachineSerializer(serializers.Serializer): class VirtualMachineSerializer(serializers.Serializer):
"""Serializer to map the virtual machine instance into JSON format.""" """Serializer to map the virtual machine instance into JSON format."""
name = serializers.CharField(read_only=True) name = serializers.CharField(read_only=True)
cores = serializers.IntegerField(read_only=True, source='template.vcpu') cores = serializers.IntegerField(source='template.vcpu')
disk = serializers.IntegerField(write_only=True)
set_memory = serializers.IntegerField(write_only=True, label='Memory')
memory = serializers.SerializerMethodField()
disk_size = serializers.SerializerMethodField() disk_size = serializers.SerializerMethodField()
memory = serializers.SerializerMethodField()
ip = serializers.CharField(read_only=True, ip = serializers.CharField(read_only=True,
source='user_template.ungleich_public_ip', source='user_template.ungleich_public_ip',
default='-') default='-')
vm_id = serializers.IntegerField(read_only=True, source='id') vm_id = serializers.IntegerField(read_only=True, source='id')
state = serializers.CharField(read_only=True, source='str_state') state = serializers.CharField(read_only=True, source='str_state')
price = serializers.SerializerMethodField() price = serializers.SerializerMethodField()
ssh_key = serializers.CharField(write_only=True)
template_id = serializers.ChoiceField( template_id = serializers.ChoiceField(
choices=[(key.id, key.name) for key in choices=[(key.id, key.name) for key in
@ -87,13 +97,26 @@ class VirtualMachineSerializer(serializers.Serializer):
def create(self, validated_data): def create(self, validated_data):
owner = validated_data['owner'] owner = validated_data['owner']
ssh_key = validated_data['ssh_key']
cores = validated_data['template']['vcpu']
memory = validated_data['set_memory']
disk = validated_data['disk']
template_id = validated_data['template']['template_id'] template_id = validated_data['template']['template_id']
specs = {
'cpu' : cores,
'disk_size' : disk,
'memory' : memory,
}
try: try:
manager = OpenNebulaManager(email=owner.email, manager = OpenNebulaManager(email=owner.email,
password=owner.password, password=owner.password,
) )
opennebula_id = manager.create_vm(template_id) opennebula_id = manager.create_vm(template_id=template_id,
ssh_key=ssh_key,
specs=specs)
except OpenNebulaException as err: except OpenNebulaException as err:
raise serializers.ValidationError("OpenNebulaException occured. {0}".format(err)) raise serializers.ValidationError("OpenNebulaException occured. {0}".format(err))
@ -116,9 +139,3 @@ class VirtualMachineSerializer(serializers.Serializer):
for disk in template.disks: for disk in template.disks:
price += int(disk.size)/1024 * float(template.disk_cost) price += int(disk.size)/1024 * float(template.disk_cost)
return price return price
class ImageSerializer(serializers.Serializer):
"""Serializer to map the image instance into JSON format."""
id = serializers.IntegerField(read_only=True)
name = serializers.CharField()