diff --git a/hosting/forms.py b/hosting/forms.py index 2a4d67e3..7323bdf3 100644 --- a/hosting/forms.py +++ b/hosting/forms.py @@ -1,10 +1,13 @@ +import random +import string from django import forms from membership.models import CustomUser from django.contrib.auth import authenticate + from utils.stripe_utils import StripeUtils -from .models import HostingOrder, VirtualMachinePlan +from .models import HostingOrder, VirtualMachinePlan, UserHostingKey class HostingOrderAdminForm(forms.ModelForm): @@ -83,3 +86,40 @@ class HostingUserSignupForm(forms.ModelForm): if not confirm_password == password: raise forms.ValidationError("Passwords don't match") return confirm_password + + +class UserHostingKeyForm(forms.ModelForm): + private_key = forms.CharField(widget=forms.PasswordInput(), required=False) + public_key = forms.CharField(widget=forms.PasswordInput(), required=False) + user = forms.models.ModelChoiceField(queryset=CustomUser.objects.all(), required=False) + name = forms.CharField(required=False) + + def __init__(self, *args, **kwargs): + self.request = kwargs.pop("request") + super(UserHostingKeyForm, self).__init__(*args, **kwargs) + # self.initial['user'].initial = self.request.user.id + # print(self.fields) + + def clean_name(self): + return ''.join(random.choice(string.ascii_lowercase) for i in range(7)) + + def clean_user(self): + return self.request.user + + def clean(self): + cleaned_data = self.cleaned_data + + print(cleaned_data) + + if not cleaned_data.get('public_key'): + private_key, public_key = UserHostingKey.generate_keys() + cleaned_data.update({ + 'private_key': private_key, + 'public_key': public_key + }) + + return cleaned_data + + class Meta: + model = UserHostingKey + fields = ['user', 'public_key', 'name'] diff --git a/hosting/management/commands/create_vm_types.py b/hosting/management/commands/create_vm_types.py index 6be49f19..f92cc3a1 100644 --- a/hosting/management/commands/create_vm_types.py +++ b/hosting/management/commands/create_vm_types.py @@ -7,51 +7,100 @@ class Command(BaseCommand): def get_data(self): + return [ + { + 'base_price': 10, + 'core_price': 5, + 'memory_price': 2, + 'disk_size_price': 0.6, + 'cores': 1, + 'memory': 2, + 'disk_size': 10 + }, + { + 'base_price': 10, + 'core_price': 5, + 'memory_price': 2, + 'disk_size_price': 0.6, + 'cores': 1, + 'memory': 2, + 'disk_size': 100 + }, + { + 'base_price': 10, + 'core_price': 5, + 'memory_price': 2, + 'disk_size_price': 0.6, + 'cores': 2, + 'memory': 4, + 'disk_size': 20 + }, + { + 'base_price': 10, + 'core_price': 5, + 'memory_price': 2, + 'disk_size_price': 0.6, + 'cores': 4, + 'memory': 8, + 'disk_size': 40 + }, + { + 'base_price': 10, + 'core_price': 5, + 'memory_price': 2, + 'disk_size_price': 0.6, + 'cores': 16, + 'memory': 8, + 'disk_size': 40 + }, + ] + + hetzner = { 'base_price': 10, - 'core_price': 10, - 'memory_price': 5, - 'disk_size_price': 1, + 'core_price': 5, + 'memory_price': 2, + 'disk_size_price': 0.6, 'description': 'VM auf einzelner HW, Raid1, kein HA', 'location': 'DE' } - return { - # 'hetzner_nug': { - # 'base_price': 5, - # 'memory_price': 2, - # 'core_price': 2, - # 'disk_size_price': 0.5, - # 'description': 'VM ohne Uptime Garantie' - # }, - 'hetzner': hetzner, - # 'hetzner_raid6': { - # 'base_price': hetzner['base_price']*1.2, - # 'core_price': hetzner['core_price']*1.2, - # 'memory_price': hetzner['memory_price']*1.2, - # 'disk_size_price': hetzner['disk_size_price']*1.2, - # 'description': 'VM auf einzelner HW, Raid1, kein HA' + # return { + # # 'hetzner_nug': { + # # 'base_price': 5, + # # 'memory_price': 2, + # # 'core_price': 2, + # # 'disk_size_price': 0.5, + # # 'description': 'VM ohne Uptime Garantie' + # # }, + # 'hetzner': hetzner, + # # 'hetzner_raid6': { + # # 'base_price': hetzner['base_price']*1.2, + # # 'core_price': hetzner['core_price']*1.2, + # # 'memory_price': hetzner['memory_price']*1.2, + # # 'disk_size_price': hetzner['disk_size_price']*1.2, + # # 'description': 'VM auf einzelner HW, Raid1, kein HA' - # }, - # 'hetzner_glusterfs': { - # 'base_price': hetzner['base_price']*1.4, - # 'core_price': hetzner['core_price']*1.4, - # 'memory_price': hetzner['memory_price']*1.4, - # 'disk_size_price': hetzner['disk_size_price']*1.4, - # 'description': 'VM auf einzelner HW, Raid1, kein HA' - # }, - 'bern': { - 'base_price': 12, - 'core_price': 25, - 'memory_price': 7, - 'disk_size_price': 0.70, - 'description': "VM in Bern, HA Setup ohne HA Garantie", - 'location': 'CH', - } - } + # # }, + # # 'hetzner_glusterfs': { + # # 'base_price': hetzner['base_price']*1.4, + # # 'core_price': hetzner['core_price']*1.4, + # # 'memory_price': hetzner['memory_price']*1.4, + # # 'disk_size_price': hetzner['disk_size_price']*1.4, + # # 'description': 'VM auf einzelner HW, Raid1, kein HA' + # # }, + # 'bern': { + # 'base_price': 12, + # 'core_price': 25, + # 'memory_price': 7, + # 'disk_size_price': 0.70, + # 'description': "VM in Bern, HA Setup ohne HA Garantie", + # 'location': 'CH', + # } + # } def handle(self, *args, **options): - data = self.get_data() - [VirtualMachineType.objects.create(hosting_company=key, **data[key]) - for key in data.keys()] + vm_data = self.get_data() + for vm in vm_data: + VirtualMachineType.objects.create(**vm) diff --git a/hosting/migrations/0028_managevm_userhostingkey.py b/hosting/migrations/0028_managevm_userhostingkey.py new file mode 100644 index 00000000..75bf591a --- /dev/null +++ b/hosting/migrations/0028_managevm_userhostingkey.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2017-04-29 18:28 +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('hosting', '0027_auto_20160711_0210'), + ] + + operations = [ + migrations.CreateModel( + name='ManageVM', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ], + options={ + 'managed': False, + }, + ), + migrations.CreateModel( + name='UserHostingKey', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('public_key', models.TextField()), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/hosting/migrations/0029_userhostingkey_created_at.py b/hosting/migrations/0029_userhostingkey_created_at.py new file mode 100644 index 00000000..6ab968fd --- /dev/null +++ b/hosting/migrations/0029_userhostingkey_created_at.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2017-04-30 19:04 +from __future__ import unicode_literals + +import datetime +from django.db import migrations, models +from django.utils.timezone import utc + + +class Migration(migrations.Migration): + + dependencies = [ + ('hosting', '0028_managevm_userhostingkey'), + ] + + operations = [ + migrations.AddField( + model_name='userhostingkey', + name='created_at', + field=models.DateTimeField(auto_now_add=True, default=datetime.datetime(2017, 4, 30, 19, 4, 20, 780173, tzinfo=utc)), + preserve_default=False, + ), + ] diff --git a/hosting/migrations/0030_userhostingkey_name.py b/hosting/migrations/0030_userhostingkey_name.py new file mode 100644 index 00000000..7405d66f --- /dev/null +++ b/hosting/migrations/0030_userhostingkey_name.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2017-04-30 19:09 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('hosting', '0029_userhostingkey_created_at'), + ] + + operations = [ + migrations.AddField( + model_name='userhostingkey', + name='name', + field=models.CharField(default='', max_length=100), + preserve_default=False, + ), + ] diff --git a/hosting/migrations/0031_auto_20170503_0554.py b/hosting/migrations/0031_auto_20170503_0554.py new file mode 100644 index 00000000..acf9ae62 --- /dev/null +++ b/hosting/migrations/0031_auto_20170503_0554.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2017-05-03 05:54 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('hosting', '0030_userhostingkey_name'), + ] + + operations = [ + migrations.RemoveField( + model_name='virtualmachinetype', + name='hosting_company', + ), + migrations.RemoveField( + model_name='virtualmachinetype', + name='location', + ), + ] diff --git a/hosting/migrations/0032_auto_20170504_0315.py b/hosting/migrations/0032_auto_20170504_0315.py new file mode 100644 index 00000000..c16b382a --- /dev/null +++ b/hosting/migrations/0032_auto_20170504_0315.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2017-05-04 03:15 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('hosting', '0031_auto_20170503_0554'), + ] + + operations = [ + migrations.AddField( + model_name='virtualmachinetype', + name='cores', + field=models.IntegerField(default=0), + preserve_default=False, + ), + migrations.AddField( + model_name='virtualmachinetype', + name='disk_size', + field=models.IntegerField(default=0), + preserve_default=False, + ), + migrations.AddField( + model_name='virtualmachinetype', + name='memory', + field=models.IntegerField(default=0), + preserve_default=False, + ), + ] diff --git a/hosting/migrations/0033_virtualmachinetype_configuration.py b/hosting/migrations/0033_virtualmachinetype_configuration.py new file mode 100644 index 00000000..ecd6d5f4 --- /dev/null +++ b/hosting/migrations/0033_virtualmachinetype_configuration.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2017-05-04 03:23 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('hosting', '0032_auto_20170504_0315'), + ] + + operations = [ + migrations.AddField( + model_name='virtualmachinetype', + name='configuration', + field=models.CharField(choices=[('debian', 'Debian 8'), ('ubuntu', 'Ubuntu 16.06'), ('devuan', 'Devuan 1'), ('centos', 'CentOS 7')], default='ubuntu', max_length=10), + ), + ] diff --git a/hosting/migrations/0034_auto_20170504_0331.py b/hosting/migrations/0034_auto_20170504_0331.py new file mode 100644 index 00000000..0dd66012 --- /dev/null +++ b/hosting/migrations/0034_auto_20170504_0331.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2017-05-04 03:31 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('hosting', '0033_virtualmachinetype_configuration'), + ] + + operations = [ + migrations.RemoveField( + model_name='virtualmachinetype', + name='configuration', + ), + migrations.AlterField( + model_name='virtualmachineplan', + name='configuration', + field=models.CharField(choices=[('debian', 'Debian 8'), ('ubuntu', 'Ubuntu 16.06'), ('devuan', 'Devuan 1'), ('centos', 'CentOS 7')], max_length=20), + ), + migrations.AlterField( + model_name='virtualmachineplan', + name='vm_type', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='hosting.VirtualMachineType'), + ), + ] diff --git a/hosting/mixins.py b/hosting/mixins.py index 2f8de3a5..404c4cb9 100644 --- a/hosting/mixins.py +++ b/hosting/mixins.py @@ -1,23 +1,32 @@ from django.shortcuts import redirect from django.core.urlresolvers import reverse -from .models import VirtualMachinePlan +from .models import VirtualMachinePlan, VirtualMachineType class ProcessVMSelectionMixin(object): def post(self, request, *args, **kwargs): - hosting = request.POST.get('configuration') - configuration_detail = dict(VirtualMachinePlan.VM_CONFIGURATION).get(hosting) - 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') - } + configuration = request.POST.get('configuration') + configuration_display = dict(VirtualMachinePlan.VM_CONFIGURATION).get(configuration) + vm_template = request.POST.get('vm_template') + vm_type = VirtualMachineType.objects.get(id=vm_template) + 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 + }) + # 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 if not request.user.is_authenticated(): request.session['vm_specs'] = vm_specs diff --git a/hosting/models.py b/hosting/models.py index 2daab9f8..ec746e22 100644 --- a/hosting/models.py +++ b/hosting/models.py @@ -7,7 +7,7 @@ from django.utils.functional import cached_property from Crypto.PublicKey import RSA from stored_messages.settings import stored_messages_settings -from membership.models import StripeCustomer +from membership.models import StripeCustomer, CustomUser from utils.models import BillingAddress from utils.mixins import AssignPermissionsMixin from .managers import VMPlansManager @@ -15,88 +15,71 @@ from .managers import VMPlansManager class VirtualMachineType(models.Model): - - BASE_PRICE = 0.5 - - HETZNER_NUG = 'hetzner_nug' - HETZNER = 'hetzner' - HETZNER_R6 = 'hetzner_raid6' - HETZNER_G = 'hetzner_glusterfs' - BERN = 'bern' - DE_LOCATION = 'DE' - CH_LOCATION = 'CH' - - HOSTING_TYPES = ( - (HETZNER_NUG, 'Hetzner No Uptime Guarantee'), - (HETZNER, 'Hetzner'), - (HETZNER_R6, 'Hetzner Raid6'), - (HETZNER_G, 'Hetzner Glusterfs'), - (BERN, 'Bern'), - ) - - LOCATIONS_CHOICES = ( - (DE_LOCATION, 'Germany'), - (CH_LOCATION, 'Switzerland'), - ) description = models.TextField() base_price = models.FloatField() memory_price = models.FloatField() core_price = models.FloatField() disk_size_price = models.FloatField() - hosting_company = models.CharField(max_length=30, choices=HOSTING_TYPES) - location = models.CharField(max_length=3, choices=LOCATIONS_CHOICES) + cores = models.IntegerField() + memory = models.IntegerField() + disk_size = models.IntegerField() def __str__(self): - return "%s" % (self.get_hosting_company_display()) + 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, specifications): - # price = float(specifications['cores']) * self.core_price - # price += float(specifications['memory']) * self.memory_price - # price += float(specifications['disk_size']) * self.disk_size_price - # price += self.base_price - # return price + 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 + # @classmethod + # def get_price(cls, vm_template): + # return cls.BASE_PRICE * vm_template - @classmethod - def get_specs(cls, vm_template): + def get_specs(self): return { - 'memory': 1024 * vm_template, - 'cores': 0.1 * vm_template, - 'disk_size': 10000 * vm_template + '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 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 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, - 'base_price': self.base_price, 'core_price': self.core_price, 'disk_size_price': self.disk_size_price, 'memory_price': self.memory_price, - 'hosting_company_name': self.get_hosting_company_display(), - 'hosting_company': self.hosting_company, - 'default_price': self.defeault_price(), - 'location_code': self.location, - 'location': self.get_location_display(), 'id': self.id, + 'final_price': self.final_price, + 'cores': self.cores, + 'memory': self.memory, + 'disk_size': self.disk_size + } @@ -112,14 +95,21 @@ class VirtualMachinePlan(AssignPermissionsMixin, models.Model): (CANCELED_STATUS, 'Canceled') ) - DJANGO = 'django' - RAILS = 'rails' - NODEJS = 'nodejs' + # DJANGO = 'django' + # RAILS = 'rails' + # NODEJS = 'nodejs' + + # VM_CONFIGURATION = ( + # (DJANGO, 'Ubuntu 14.04, Django'), + # (RAILS, 'Ubuntu 14.04, Rails'), + # (NODEJS, 'Debian, NodeJS'), + # ) VM_CONFIGURATION = ( - (DJANGO, 'Ubuntu 14.04, Django'), - (RAILS, 'Ubuntu 14.04, Rails'), - (NODEJS, 'Debian, NodeJS'), + ('debian', 'Debian 8'), + ('ubuntu', 'Ubuntu 16.06'), + ('devuan', 'Devuan 1'), + ('centos', 'CentOS 7') ) permissions = ('view_virtualmachineplan', @@ -129,7 +119,7 @@ class VirtualMachinePlan(AssignPermissionsMixin, models.Model): cores = models.IntegerField() memory = models.IntegerField() disk_size = models.IntegerField() - vm_type = models.ForeignKey(VirtualMachineType) + 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) @@ -147,13 +137,13 @@ class VirtualMachinePlan(AssignPermissionsMixin, models.Model): def __str__(self): return self.name - @cached_property - def hosting_company_name(self): - return self.vm_type.get_hosting_company_display() + # @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 location(self): + # return self.vm_type.get_location_display() @cached_property def name(self): @@ -173,24 +163,6 @@ class VirtualMachinePlan(AssignPermissionsMixin, models.Model): instance.assign_permissions(user) return instance - @staticmethod - def generate_RSA(bits=2048): - ''' - Generate an RSA keypair with an exponent of 65537 in PEM format - param: bits The key length in bits - Return private key and public key - ''' - new_key = RSA.generate(2048, os.urandom) - public_key = new_key.publickey().exportKey("OpenSSH") - private_key = new_key.exportKey("PEM") - return private_key, public_key - - def generate_keys(self): - private_key, public_key = self.generate_RSA() - self.public_key = public_key - self.save(update_fields=['public_key']) - return private_key, public_key - def cancel_plan(self): self.status = self.CANCELED_STATUS self.save(update_fields=['status']) @@ -242,6 +214,32 @@ class HostingOrder(AssignPermissionsMixin, models.Model): self.save() +class UserHostingKey(models.Model): + user = models.ForeignKey(CustomUser) + public_key = models.TextField() + created_at = models.DateTimeField(auto_now_add=True) + name = models.CharField(max_length=100) + + @staticmethod + def generate_RSA(bits=2048): + ''' + Generate an RSA keypair with an exponent of 65537 in PEM format + param: bits The key length in bits + Return private key and public key + ''' + new_key = RSA.generate(2048, os.urandom) + public_key = new_key.publickey().exportKey("OpenSSH") + private_key = new_key.exportKey("PEM") + return private_key, public_key + + @classmethod + def generate_keys(cls): + private_key, public_key = cls.generate_RSA() + # self.public_key = public_key + # self.save(update_fields=['public_key']) + return private_key, public_key + + class ManageVM(models.Model): def has_add_permission(self, request): return False diff --git a/hosting/templates/hosting/base_short.html b/hosting/templates/hosting/base_short.html index 1eacd26a..c6d1772e 100644 --- a/hosting/templates/hosting/base_short.html +++ b/hosting/templates/hosting/base_short.html @@ -72,6 +72,11 @@ {% trans "My Orders"%} +
  • + + {% trans "Keys"%} + +
  • {% trans "Notifications "%} diff --git a/hosting/templates/hosting/create_virtual_machine.html b/hosting/templates/hosting/create_virtual_machine.html new file mode 100644 index 00000000..8f85c486 --- /dev/null +++ b/hosting/templates/hosting/create_virtual_machine.html @@ -0,0 +1,45 @@ +{% extends "hosting/base_short.html" %} +{% load staticfiles bootstrap3 i18n %} +{% block content %} +
    +
    +
    +
    +

    {% trans "New Virtual Machine"%}

    +
    + +
    + {% csrf_token %} +
    + Select VM Type: + +
    +
    + Select VM Configuration: + +
    +
    + +
    +
    + +
    + +
    +
    + +
    + +{%endblock%} \ No newline at end of file diff --git a/hosting/templates/hosting/includes/_pricing.html b/hosting/templates/hosting/includes/_pricing.html index 284f0717..4c95a73e 100644 --- a/hosting/templates/hosting/includes/_pricing.html +++ b/hosting/templates/hosting/includes/_pricing.html @@ -24,59 +24,17 @@ {% csrf_token %} - +