Merge pull request #217 from levivm/opennebula-integration

Opennebula integration
This commit is contained in:
Levi Velázquez 2017-05-06 18:27:35 -05:00 committed by GitHub
commit e1a61ba930
23 changed files with 756 additions and 226 deletions

View file

@ -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']

View file

@ -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)

View file

@ -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)),
],
),
]

View file

@ -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,
),
]

View file

@ -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,
),
]

View file

@ -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',
),
]

View file

@ -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,
),
]

View file

@ -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),
),
]

View file

@ -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'),
),
]

View file

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.4 on 2017-05-06 23:02
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('hosting', '0034_auto_20170504_0331'),
]
operations = [
migrations.AddField(
model_name='virtualmachineplan',
name='opennebula_id',
field=models.IntegerField(default=0),
preserve_default=False,
),
]

View file

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.4 on 2017-05-06 23:12
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('hosting', '0035_virtualmachineplan_opennebula_id'),
]
operations = [
migrations.AlterField(
model_name='virtualmachineplan',
name='opennebula_id',
field=models.IntegerField(null=True),
),
]

View file

@ -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

View file

@ -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,70 +15,71 @@ from .managers import VMPlansManager
class VirtualMachineType(models.Model):
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
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
def defeault_price(self):
price = self.base_price
price += self.core_price
price += self.memory_price
price += self.disk_size_price * 10
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,
'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
}
@ -94,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',
@ -111,12 +119,13 @@ 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)
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()
@ -129,13 +138,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):
@ -155,24 +164,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'])
@ -224,6 +215,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

View file

@ -87,6 +87,48 @@ class HostingManageVMAdmin(admin.ModelAdmin):
)
return TemplateResponse(request, "hosting/managevms.html", context)
def create_vm_view(self, specs):
vm_id = None
try:
# We do have the vm_template param set. Get and parse it
# and check it to be in the desired range.
# We have 8 possible VM templates for the moment which are 1x, 2x, 4x ...
# the basic template of 10GB disk, 1GB ram, 1 vcpu, 0.1 cpu
vm_string_formatter = """<VM>
<MEMORY>{memory}</MEMORY>
<VCPU>{vcpu}</VCPU>
<CPU>{cpu}</CPU>
<DISK>
<TYPE>{disk_type}</TYPE>
<SIZE>{size}</SIZE>
</DISK>
</VM>
"""
vm_id = oca.VirtualMachine.allocate(
self.client,
vm_string_formatter.format(
memory=1024 * specs.get('memory'),
vcpu=specs.get('cores'),
cpu=0.1 * specs.get('cores'),
disk_type='fs',
size=10000 * specs.get('disk_size')
)
)
# message = _("Created with id = " + str(vm_id))
# messages.add_message(request, messages.SUCCESS, message)
except socket.timeout as socket_err:
logger.error("Socket timeout error: {0}".format(socket_err))
except OpenNebulaException as opennebula_err:
logger.error("OpenNebulaException error: {0}".format(opennebula_err))
except OSError as os_err:
logger.error("OSError : {0}".format(os_err))
except ValueError as value_err:
logger.error("ValueError : {0}".format(value_err))
return vm_id
# Creating VM by using method allocate(client, template)
def create_vm(self, request):
# check if the request contains the template parameter, if it is

View file

@ -72,6 +72,11 @@
<i class="fa fa-credit-card"></i> {% trans "My Orders"%}
</a>
</li>
<li>
<a href="{% url 'hosting:key_pair' %}">
<i class="fa fa-key" aria-hidden="true"></i> {% trans "Keys"%}
</a>
</li>
<li>
<a href="{% url 'hosting:notifications' %}">
<i class="fa fa-bell"></i> {% trans "Notifications "%}

View file

@ -0,0 +1,45 @@
{% extends "hosting/base_short.html" %}
{% load staticfiles bootstrap3 i18n %}
{% block content %}
<div>
<div class="container dashboard-container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<h3><i class="fa fa-server" aria-hidden="true"></i> {% trans "New Virtual Machine"%} </h3>
<hr/>
<form method="POST" action="">
{% csrf_token %}
<div class="form-group">
Select VM:
<select name="vm_template">
{% for vm in vm_types %}
<option value="{{vm.id}}">CORE: {{vm.cores}}, RAM: {{vm.memory}}, SSD: {{vm.disk_size}} </option>
{% endfor %}
</select>
</div>
<div class="form-group">
Select VM Configuration:
<select name="configuration">
{% for config in configuration_options %}
<option value="{{config.0}}">{{config.1}} </option>
{% endfor %}
</select>
</div>
<div class="form-group">
<button class="btn btn-success" >{% trans "Start VM"%} </button>
</div>
</form>
</div>
</div>
</div>
</div>
{%endblock%}

View file

@ -24,58 +24,17 @@
{% csrf_token %}
<input type="hidden" name="hosting_company" value="{{vm.hosting_company}}">
<input type="hidden" name="location_code" value="{{vm.location_code}}">
<input type="hidden" name="vm_template" value="{{vm.id}}">
<ul class="pricing {% cycle 'p-red' 'p-black' 'p-red' 'p-yel' %}">
<li class="type">
<!-- <img src="http://bread.pp.ua/n/settings_g.svg" alt=""> -->
<h3 >{{vm.location_code}}</h3>
<br/>
<img class="img-responsive" src="{{ STATIC_URL }}hosting/img/{{vm.location_code}}_flag.png" alt="">
</li>
<li>
<!-- Single button -->
<div class="btn-group">
<div class="form-group">
<label for="cores">Location: </label>
{{vm.location}}
</div>
</div>
</li>
<li>
<label for="configuration">Configuration: </label>
{% if select_configuration %}
<select class="form-control" name="configuration" id="{{vm.hosting_company}}-configuration" data-vm-type="{{vm.hosting_company}}">
{% for key,value in configuration_options.items %}
<option value="{{key}}">{{ value }}</option>
{% endfor %}
</select>
{% else %}
<input type="hidden" name="configuration_detail" value="{{configuration_detail}}">
<input type="hidden" name="configuration" value="{{hosting}}">
<!-- Single button -->
<div class="btn-group">
<div class="form-group">
<label>Configuration: </label>
{{configuration_detail}}
</div>
</div>
{% endif %}
</li>
<li>
<!-- Single button -->
<div class="btn-group">
<div class="form-group">
<label for="cores">Cores: </label>
<select class="form-control cores-selector" name="cores" id="{{vm.hosting_company}}-cores" data-vm-type="{{vm.hosting_company}}">
{% with ''|center:10 as range %}
{% for _ in range %}
<option>{{ forloop.counter }}</option>
{% endfor %}
{% endwith %}
</select>
<label for="cores">Cores: {{vm.cores}}</label>
</div>
</div>
@ -83,30 +42,28 @@
<li>
<div class="form-group">
<div class="btn-group">
<label for="memory">Memory: </label>
<select class="form-control memory-selector" name="memory" id="{{vm.hosting_company}}-memory" data-vm-type="{{vm.hosting_company}}">
{% with ''|center:50 as range %}
{% for _ in range %}
<option>{{ forloop.counter }}</option>
{% endfor %}
{% endwith %}
</select>
<span>GiB</span>
<label for="memory">Memory: {{vm.memory}} GiB</label>
</div>
</div>
</li>
<li>
<div class="form-group row">
<div class="col-xs-offset-1 col-xs-9 col-sm-12 col-md-12 col-md-offset-0">
<label for="Disk Size">Disk Size: </label>
<input class="form-control short-input text-center disk-space-selector" name="disk_space" type="number" id="{{vm.hosting_company}}-disk_space" min="10" value="10" step="10" data-vm-type="{{vm.hosting_company}}"/>
<span>GiB</span>
<label for="Disk Size">Disk Size: {{vm.disk_size}} GiB</label>
</div>
</div>
</li>
<li>
<input id="{{vm.hosting_company}}-final-price-input" type="hidden" name="final_price" value="{{vm.default_price|floatformat}}">
<h3 id="{{vm.hosting_company}}-final-price">{{vm.default_price|floatformat}}CHF</h3>
<label for="configuration">Configuration: </label>
<select class="form-control" name="configuration" id="{{vm.hosting_company}}-configuration" data-vm-type="{{vm.hosting_company}}">
{% for key,value in configuration_options.items %}
<option value="{{key}}">{{ value }}</option>
{% endfor %}
</select>
</li>
<li>
<input type="hidden" name="final_price" value="{{vm.final_price|floatformat}}">
<h3 id="{{vm.hosting_company}}-final-price">{{vm.final_price|floatformat}}CHF</h3>
<span>per month</span>
</li>
<li>

View file

@ -65,7 +65,7 @@
{% url 'hosting:payment' as payment_url %}
{% if payment_url in request.META.HTTP_REFERER %}
<div class=" content pull-right">
<a href="{% url 'hosting:virtual_machine_key' order.vm_plan.id %}" ><button class="btn btn-info">{% trans "Finish Configuration"%}</button></a>
<a href="{% url 'hosting:key_pair'%}" ><button class="btn btn-info">{% trans "Finish Configuration"%}</button></a>
</div>
{% endif %}
</div>

View file

@ -86,11 +86,11 @@
<h3><b>Billing Amount</b></h3>
<hr>
<div class="content">
<p><b>Type</b> <span class="pull-right">{{request.session.vm_specs.location_code}}</span></p>
<hr>
<!-- <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.vm_specs.cores}}</span></p>
<hr>
<p><b>Configuration</b> <span class="pull-right">{{request.session.vm_specs.configuration_detail}}</span></p>
<p><b>Configuration</b> <span class="pull-right">{{request.session.vm_specs.configuration_display}}</span></p>
<hr>
<p><b>Memory</b> <span class="pull-right">{{request.session.vm_specs.memory}} GiB</span></p>
<hr>

View file

@ -6,30 +6,75 @@
<div class="row">
<div class="col-md-9 col-md-offset-2">
<div class="col-sm-12">
<h3><i class="fa fa-key" aria-hidden="true"></i>{% trans "SSH Private Key"%} </h3>
<form method="POST" action="" >
{% csrf_token %}
<h3><i class="fa fa-key" aria-hidden="true"></i>{% trans "Access Key"%} </h3>
<hr/>
{% if not user_key %}
<div class="alert alert-warning">
{% trans "Upload your own key. "%}
</div>
<div class="form-group">
<label for="comment">Paste here your public key</label>
<textarea class="form-control" rows="6" name="public_key"></textarea>
</div>
<div class="form-group">
<button class="btn btn-success">{% trans "Upload Key"%} </a>
</div>
<div class="alert alert-warning">
{% trans "Or generate a new key pair."%}
</div>
<div class="form-group">
<button class="btn btn-success">{% trans "Generate Key Pair"%} </a>
</div>
{% else %}
<h5> Use your created key to access to the machine. If you lost it, contact us. </h5>
<table class="table borderless table-hover">
<br/>
<thead>
<tr>
<th>{% trans "Name"%}</th>
<th>{% trans "Created at"%} </th>
<th>{% trans "Status"%} </th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td scope="row">{{user_key.name}}</td>
<td>{{user_key.created_at}}</td>
<td>
<span class="h3 label label-success"><strong>Active</strong></span>
</td>
</tr>
</tbody>
</table>
{% endif %}
</form>
{% if private_key %}
<div class="alert alert-warning">
<strong>{% trans "Warning!"%}</strong>{% trans "You can view your SSH private key once. Copy it or if it wasn't downloaded automatically, just click on Download to start it."%}
</div>
<div class="form-group">
<label for="comment">private_key.pem</label>
<textarea class="form-control" rows="6" id="ssh_key">{{private_key}}</textarea>
<textarea class="form-control" rows="6" id="ssh_key" type="hidden" style="display:none">{{private_key}}</textarea>
</div>
<div class="form-group pull-right">
<!-- <div class="form-group pull-right">
<button type="button" id="copy_to_clipboard" data-clipboard-target="#ssh_key" class="btn btn-warning"
data-toggle="tooltip" data-placement="bottom" title="Copied" data-trigger="click">{% trans "Copy to Clipboard"%}</button>
<button type="button" id="download_ssh_key" class="btn btn-warning">{% trans "Download"%}</button>
</div>
</div> -->
{% else %}
<div class="alert alert-warning">
<!-- <div class="alert alert-warning">
<strong>{% trans "Warning!"%}</strong>{% trans "Your SSH private key was already generated and downloaded, if you lost it, contact us. "%}
</div>
{% endif %}
<a class="btn btn-success" href="{% url 'hosting:virtual_machines' virtual_machine.id %}">{% trans "Go to my Virtual Machine Dashboard"%} </a>
--> {% endif %}
<!-- <a class="btn btn-success" href="{% url 'hosting:virtual_machines' %}">{% trans "Generate my key"%} </a> -->
<div class="clearfix"></div>
</div>
</div>
@ -42,11 +87,12 @@
<!-- Force to download ssh key on page load -->
<script type="text/javascript">
var key = window.document.getElementById('ssh_key');
var key = window.document.getElementById('ssh_key');
var a = window.document.createElement('a');
a.href = window.URL.createObjectURL(new Blob(['key'], {type: 'text'}));
a.download = 'private_key.pem';
a.href = window.URL.createObjectURL(new Blob([key.value], {type: 'text'}));
a.download = '{{key_name}}.pem';
// Append anchor to body.
document.body.appendChild(a);

View file

@ -6,12 +6,14 @@
<div class="row">
<div class="col-md-8 col-md-offset-2">
<table class="table borderless table-hover">
<h3><i class="fa fa-server" aria-hidden="true"></i>{% trans "Virtual Machines"%} </h3>
<h3 class="pull-left"><i class="fa fa-server" aria-hidden="true"></i> {% trans "Virtual Machines"%} </h3>
<p class="pull-right">
<a class="btn btn-success" href="{% url 'hosting:create-virtual-machine' %}" >{% trans "Create VM"%} </a>
</p>
<br/>
<thead>
<tr>
<th>{% trans "ID"%}</th>
<th>{% trans "Location"%} </th>
<th>{% trans "Amount"%}</th>
<th>{% trans "Status"%}</th>
<th></th>
@ -21,7 +23,6 @@
{% for vm in vms %}
<tr>
<td scope="row">{{vm.name}}</td>
<td>{{vm.location}}</td>
<td>{{vm.price}} CHF</td>
<td>

View file

@ -4,7 +4,8 @@ from .views import DjangoHostingView, RailsHostingView, PaymentVMView,\
NodeJSHostingView, LoginView, SignupView, IndexView, \
OrdersHostingListView, OrdersHostingDetailView, VirtualMachinesPlanListView,\
VirtualMachineView, GenerateVMSSHKeysView, OrdersHostingDeleteView, NotificationsView, \
MarkAsReadNotificationView, PasswordResetView, PasswordResetConfirmView, HostingPricingView
MarkAsReadNotificationView, PasswordResetView, PasswordResetConfirmView, HostingPricingView,\
CreateVirtualMachinesView
urlpatterns = [
url(r'index/?$', IndexView.as_view(), name='index'),
@ -16,13 +17,14 @@ urlpatterns = [
url(r'orders/?$', OrdersHostingListView.as_view(), name='orders'),
url(r'orders/(?P<pk>\d+)/?$', OrdersHostingDetailView.as_view(), name='orders'),
url(r'cancel_order/(?P<pk>\d+)/?$', OrdersHostingDeleteView.as_view(), name='delete_order'),
url(r'create-virtual-machine/?$', CreateVirtualMachinesView.as_view(), name='create-virtual-machine'),
url(r'my-virtual-machines/?$', VirtualMachinesPlanListView.as_view(), name='virtual_machines'),
url(r'my-virtual-machines/(?P<pk>\d+)/?$', VirtualMachineView.as_view(),
name='virtual_machines'),
# url(r'my-virtual-machines/(?P<pk>\d+)/delete/?$', VirtualMachineCancelView.as_view(),
# name='virtual_machines_cancel'),
url(r'my-virtual-machines/(?P<pk>\d+)/key/?$', GenerateVMSSHKeysView.as_view(),
name='virtual_machine_key'),
url(r'vm-key-pair/?$', GenerateVMSSHKeysView.as_view(),
name='key_pair'),
url(r'^notifications/$', NotificationsView.as_view(), name='notifications'),
url(r'^notifications/(?P<pk>\d+)/?$', MarkAsReadNotificationView.as_view(),
name='read_notification'),

View file

@ -1,3 +1,4 @@
from collections import namedtuple
from django.shortcuts import render
from django.core.urlresolvers import reverse_lazy, reverse
@ -7,6 +8,8 @@ from django.views.generic import View, CreateView, FormView, ListView, DetailVie
from django.http import HttpResponseRedirect
from django.contrib.auth import authenticate, login
from django.conf import settings
from django.shortcuts import redirect
from guardian.mixins import PermissionRequiredMixin
from stored_messages.settings import stored_messages_settings
@ -14,14 +17,16 @@ from stored_messages.models import Message
from stored_messages.api import mark_read
from membership.models import CustomUser, StripeCustomer
from utils.stripe_utils import StripeUtils
from utils.forms import BillingAddressForm, PasswordResetRequestForm
from utils.views import PasswordResetViewMixin, PasswordResetConfirmViewMixin, LoginViewMixin
from utils.mailer import BaseEmail
from .models import VirtualMachineType, VirtualMachinePlan, HostingOrder
from .forms import HostingUserSignupForm, HostingUserLoginForm
from .models import VirtualMachineType, VirtualMachinePlan, HostingOrder, UserHostingKey
from .forms import HostingUserSignupForm, HostingUserLoginForm, UserHostingKeyForm
from .mixins import ProcessVMSelectionMixin
from .opennebula_functions import HostingManageVMAdmin
class DjangoHostingView(ProcessVMSelectionMixin, View):
@ -38,6 +43,7 @@ class DjangoHostingView(ProcessVMSelectionMixin, View):
'google_analytics': "UA-62285904-6",
'email': "info@django-hosting.ch",
'vm_types': VirtualMachineType.get_serialized_vm_types(),
'configuration_options': dict(VirtualMachinePlan.VM_CONFIGURATION)
}
return context
@ -206,26 +212,52 @@ class MarkAsReadNotificationView(LoginRequiredMixin, UpdateView):
return HttpResponseRedirect(reverse('hosting:notifications'))
class GenerateVMSSHKeysView(LoginRequiredMixin, DetailView):
model = VirtualMachinePlan
class GenerateVMSSHKeysView(LoginRequiredMixin, FormView):
form_class = UserHostingKeyForm
model = UserHostingKey
template_name = 'hosting/virtual_machine_key.html'
success_url = reverse_lazy('hosting:orders')
login_url = reverse_lazy('hosting:login')
context_object_name = "virtual_machine"
def get_context_data(self, **kwargs):
try:
user_key = UserHostingKey.objects.get(
user=self.request.user
)
except UserHostingKey.DoesNotExist:
user_key = None
context = super(
GenerateVMSSHKeysView,
self
).get_context_data(**kwargs)
context.update({
'user_key': user_key
})
context = super(GenerateVMSSHKeysView, self).get_context_data(**kwargs)
vm = self.get_object()
if not vm.public_key:
private_key, public_key = vm.generate_keys()
context.update({
'private_key': private_key,
'public_key': public_key
})
return context
return context
def get_form_kwargs(self):
kwargs = super(GenerateVMSSHKeysView, self).get_form_kwargs()
kwargs.update({'request': self.request})
return kwargs
def form_valid(self, form):
form.save()
context = self.get_context_data()
if form.cleaned_data.get('private_key'):
context.update({
'private_key': form.cleaned_data.get('private_key'),
'key_name': form.cleaned_data.get('name')
})
# print("form", form.cleaned_data)
return render(self.request, self.template_name, context)
class PaymentVMView(LoginRequiredMixin, FormView):
template_name = 'hosting/payment.html'
@ -246,18 +278,26 @@ class PaymentVMView(LoginRequiredMixin, FormView):
if form.is_valid():
context = self.get_context_data()
specifications = request.session.get('vm_specs')
vm_type = specifications.get('hosting_company')
vm = VirtualMachineType.objects.get(hosting_company=vm_type)
final_price = vm.calculate_price(specifications)
vm_template = specifications.get('vm_template', 1)
vm_type = VirtualMachineType.objects.get(id=vm_template)
specs = vm_type.get_specs()
final_price = vm_type.calculate_price()
plan_data = {
'vm_type': vm,
'cores': specifications.get('cores'),
'memory': specifications.get('memory'),
'disk_size': specifications.get('disk_size'),
'configuration': specifications.get('configuration'),
'vm_type': vm_type,
'configuration': specifications.get(
'configuration',
'django'
),
'price': final_price
}
plan_data.update(specs)
token = form.cleaned_data.get('token')
# Get or create stripe customer
@ -299,6 +339,20 @@ class PaymentVMView(LoginRequiredMixin, FormView):
# If the Stripe payment was successed, set order status approved
order.set_approved()
# Create VM using oppenebula functions
# _request = namedtuple('request', 'POST user')
# _request.user = request.user
# user = namedtuple('user', 'email')
# email
# _request.POST = {
# 'vm_template': vm_template
# }
hosting_admin = HostingManageVMAdmin.__new__(HostingManageVMAdmin)
hosting_admin.init_opennebula_client(request)
oppennebula_vm_id = hosting_admin.create_vm_view(vm_type.get_specs())
plan.oppenebula_id = oppennebula_vm_id
# Send notification to ungleich as soon as VM has been booked
context = {
'vm': plan,
@ -358,11 +412,48 @@ class VirtualMachinesPlanListView(LoginRequiredMixin, ListView):
ordering = '-id'
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 CreateVirtualMachinesView(LoginRequiredMixin, View):
template_name = "hosting/create_virtual_machine.html"
login_url = reverse_lazy('hosting:login')
def get(self, request, *args, **kwargs):
context = {
'vm_types': VirtualMachineType.get_serialized_vm_types(),
'configuration_options': VirtualMachinePlan.VM_CONFIGURATION
}
# context = {}
return render(request, self.template_name, context)
def post(self, request):
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
})
request.session['vm_specs'] = vm_specs
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(PermissionRequiredMixin, LoginRequiredMixin, UpdateView):
template_name = "hosting/virtual_machine_detail.html"
login_url = reverse_lazy('hosting:login')