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 .models import HostingOrder, HostingBill
from .models import HostingOrder, HostingBill, HostingPlan
admin.site.register(HostingOrder)
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__)
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):
ORDER_APPROVED_STATUS = 'Approved'

View file

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

View file

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

View file

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

View file

@ -100,14 +100,17 @@
<div class="content">
<!-- <p><b>Type</b> <span class="pull-right">{{request.session.vm_specs.location_code}}</span></p> -->
<!-- <hr> -->
<p><b>Cores</b> <span class="pull-right">{{request.session.template.cores}}</span></p>
<p><b>Cores</b> <span
class="pull-right">{{request.session.specs.cpu|floatformat}}</span></p>
<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>
<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>
<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>

View file

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

View file

@ -109,37 +109,64 @@ class OpenNebulaManager():
except:
return None
def create_vm(self, template_id, image_id=None, ssh_key=None):
extra_template_formater = """<CONTEXT>
<SSH_PUBLIC_KEY>{ssh_key}</SSH_PUBLIC_KEY>
</CONTEXT>
<DISK>
<IMAGE_ID>{image_id}</IMAGE_ID>
</DISK>
"""
def create_template(self, name, cores, memory, disk_size, core_price, memory_price,
disk_size_price, ssh='' ):
"""Create and add a new template to opennebula.
:param name: A string representation describing the template.
Used as label in view.
:param cores: Amount of virtual cpu cores for the VM.
:param memory: Amount of RAM for the VM (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)
vm_id = template.instantiate(name ='', pending=False, extra_template='')
image = self.get_image(image_id)
vm_specs_formatter = """<TEMPLATE>
<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:
self.oneadmin_client.call(
oca.VirtualMachine.METHODS['chown'],
@ -176,7 +203,6 @@ class OpenNebulaManager():
try:
template_pool = oca.VmTemplatePool(self.oneadmin_client)
template_pool.info()
#TODO: Replace with logger
except ConnectionRefusedError:
logger.info('Could not connect to host: {host} via protocol {protocol}'.format(
host=settings.OPENNEBULA_DOMAIN,
@ -262,38 +288,3 @@ class OpenNebulaManager():
self.opennebula_user.id,
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):
"""Serializer to map the virtual machine template instance into JSON format."""
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')
disk = serializers.IntegerField(write_only=True)
disk_size = serializers.SerializerMethodField()
set_memory = serializers.IntegerField(write_only=True, label='Memory')
memory = serializers.SerializerMethodField()
core_price = serializers.FloatField(source='template.cpu_cost')
disk_size_price = serializers.FloatField(source='template.disk_cost')
@ -63,20 +65,28 @@ class VirtualMachineTemplateSerializer(serializers.Serializer):
def get_memory(self, obj):
return int(obj.template.memory)/1024
def get_name(self, obj):
# TODO: Filter public- away
return obj.name
class VirtualMachineSerializer(serializers.Serializer):
"""Serializer to map the virtual machine instance into JSON format."""
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()
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()
ssh_key = serializers.CharField(write_only=True)
template_id = serializers.ChoiceField(
choices=[(key.id, key.name) for key in
@ -87,13 +97,26 @@ class VirtualMachineSerializer(serializers.Serializer):
def create(self, validated_data):
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']
specs = {
'cpu' : cores,
'disk_size' : disk,
'memory' : memory,
}
try:
manager = OpenNebulaManager(email=owner.email,
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:
raise serializers.ValidationError("OpenNebulaException occured. {0}".format(err))
@ -116,9 +139,3 @@ class VirtualMachineSerializer(serializers.Serializer):
for disk in template.disks:
price += int(disk.size)/1024 * float(template.disk_cost)
return price
class ImageSerializer(serializers.Serializer):
"""Serializer to map the image instance into JSON format."""
id = serializers.IntegerField(read_only=True)
name = serializers.CharField()