diff --git a/hosting/admin.py b/hosting/admin.py index 4f2f15e1..c38fa8d0 100644 --- a/hosting/admin.py +++ b/hosting/admin.py @@ -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) diff --git a/hosting/migrations/0040_hostingplan.py b/hosting/migrations/0040_hostingplan.py new file mode 100644 index 00000000..cb48ccd6 --- /dev/null +++ b/hosting/migrations/0040_hostingplan.py @@ -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)), + ], + ), + ] diff --git a/hosting/models.py b/hosting/models.py index 6be6c246..f8f180a5 100644 --- a/hosting/models.py +++ b/hosting/models.py @@ -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' diff --git a/hosting/templates/hosting/bill_detail.html b/hosting/templates/hosting/bill_detail.html index 9e92d0e9..e34b2f11 100644 --- a/hosting/templates/hosting/bill_detail.html +++ b/hosting/templates/hosting/bill_detail.html @@ -4,7 +4,7 @@ {% block content %}
-
+
{# Adress bar #}
@@ -78,14 +78,14 @@
- {% trans "CH02 0900 0000 6071 8848 8%} + {% trans "CH02 0900 0000 6071 8848 8" %}
{% trans "POFICHBEXXX" %}
-
+
{% endblock %} diff --git a/hosting/templates/hosting/create_virtual_machine.html b/hosting/templates/hosting/create_virtual_machine.html index 405b2988..67a61ead 100644 --- a/hosting/templates/hosting/create_virtual_machine.html +++ b/hosting/templates/hosting/create_virtual_machine.html @@ -11,26 +11,22 @@
{% csrf_token %}
- Select VM: + Select VM Template:
Select VM Configuration: - + {% for config in configuration_options %} + {% endfor %}
diff --git a/hosting/templates/hosting/orders.html b/hosting/templates/hosting/orders.html index 7d1009ea..16a739a0 100644 --- a/hosting/templates/hosting/orders.html +++ b/hosting/templates/hosting/orders.html @@ -25,7 +25,7 @@ {{ order.id }} {{ order.created_at }} - {{ order.vm_plan.price }} CHF + {{ order.price }} CHF {% if order.approved %} {% trans "Approved"%} {% else %} @@ -99,4 +99,4 @@
-{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/hosting/templates/hosting/payment.html b/hosting/templates/hosting/payment.html index bb80d566..459dcf0b 100644 --- a/hosting/templates/hosting/payment.html +++ b/hosting/templates/hosting/payment.html @@ -100,14 +100,17 @@
-

Cores {{request.session.template.cores}}

+

Cores {{request.session.specs.cpu|floatformat}}


-

Memory {{request.session.template.memory}} GiB

+

Memory {{request.session.specs.memory|floatformat}} GiB


-

Disk space {{request.session.template.disk_size}} GiB

+

Disk space {{request.session.specs.disk_size|floatformat}} GiB


Total

{{request.session.template.price }} CHF

+ class="pull-right">{{request.session.specs.price }} CHF

diff --git a/hosting/views.py b/hosting/views.py index a40e14b9..b1f993ee 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -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') @@ -455,12 +454,14 @@ 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 diff --git a/opennebula_api/models.py b/opennebula_api/models.py index f543d0ec..eb6d64a2 100644 --- a/opennebula_api/models.py +++ b/opennebula_api/models.py @@ -109,37 +109,64 @@ class OpenNebulaManager(): except: return None - def create_vm(self, template_id, image_id=None, ssh_key=None): - extra_template_formater = """ - {ssh_key} - - - {image_id} - - """ + 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 = """ + """ + 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 - diff --git a/opennebula_api/serializers.py b/opennebula_api/serializers.py index 86da2f0b..600f6fc3 100644 --- a/opennebula_api/serializers.py +++ b/opennebula_api/serializers.py @@ -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() -