Merge pull request #235 from levivm/opennebula_api

Opennebula api
This commit is contained in:
Noe 2017-05-13 05:07:53 +02:00 committed by GitHub
commit 8f3125d2f7
15 changed files with 424 additions and 95 deletions

View file

@ -69,7 +69,9 @@ class UserHostingKeyForm(forms.ModelForm):
# print(self.fields) # print(self.fields)
def clean_name(self): def clean_name(self):
return ''.join(random.choice(string.ascii_lowercase) for i in range(7)) return "dcl-priv-key-%s" % (
''.join(random.choice(string.ascii_lowercase) for i in range(7))
)
def clean_user(self): def clean_user(self):
return self.request.user return self.request.user
@ -77,8 +79,6 @@ class UserHostingKeyForm(forms.ModelForm):
def clean(self): def clean(self):
cleaned_data = self.cleaned_data cleaned_data = self.cleaned_data
print(cleaned_data)
if not cleaned_data.get('public_key'): if not cleaned_data.get('public_key'):
private_key, public_key = UserHostingKey.generate_keys() private_key, public_key = UserHostingKey.generate_keys()
cleaned_data.update({ cleaned_data.update({

View file

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.4 on 2017-05-12 16:37
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('hosting', '0038_auto_20170512_1006'),
]
operations = [
migrations.AddField(
model_name='hostingorder',
name='price',
field=models.FloatField(default=0),
preserve_default=False,
),
]

View file

@ -1,5 +1,7 @@
import os import os
import socket import socket
import logging
import oca import oca
from django.db import models from django.db import models
@ -19,9 +21,9 @@ from .managers import VMPlansManager
from oca.exceptions import OpenNebulaException from oca.exceptions import OpenNebulaException
from oca.pool import WrongNameError from oca.pool import WrongNameError
import logging
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class HostingOrder(AssignPermissionsMixin, models.Model): class HostingOrder(AssignPermissionsMixin, models.Model):
ORDER_APPROVED_STATUS = 'Approved' ORDER_APPROVED_STATUS = 'Approved'
@ -35,6 +37,7 @@ class HostingOrder(AssignPermissionsMixin, models.Model):
last4 = models.CharField(max_length=4) last4 = models.CharField(max_length=4)
cc_brand = models.CharField(max_length=10) cc_brand = models.CharField(max_length=10)
stripe_charge_id = models.CharField(max_length=100, null=True) stripe_charge_id = models.CharField(max_length=100, null=True)
price = models.FloatField()
permissions = ('view_hostingorder',) permissions = ('view_hostingorder',)
@ -51,9 +54,13 @@ class HostingOrder(AssignPermissionsMixin, models.Model):
return self.ORDER_APPROVED_STATUS if self.approved else self.ORDER_DECLINED_STATUS return self.ORDER_APPROVED_STATUS if self.approved else self.ORDER_DECLINED_STATUS
@classmethod @classmethod
def create(cls, vm_plan=None, customer=None, billing_address=None): def create(cls, price=None, vm_id=None, customer=None, billing_address=None):
instance = cls.objects.create(vm_plan=vm_plan, customer=customer, instance = cls.objects.create(
billing_address=billing_address) price=price,
vm_id=vm_id,
customer=customer,
billing_address=billing_address
)
instance.assign_permissions(customer.user) instance.assign_permissions(customer.user)
return instance return instance
@ -67,6 +74,12 @@ class HostingOrder(AssignPermissionsMixin, models.Model):
self.cc_brand = stripe_charge.source.brand self.cc_brand = stripe_charge.source.brand
self.save() self.save()
def get_cc_data(self):
return {
'last4': self.last4,
'cc_brand': self.cc_brand,
} if self.last4 and self.cc_brand else None
class UserHostingKey(models.Model): class UserHostingKey(models.Model):
user = models.ForeignKey(CustomUser) user = models.ForeignKey(CustomUser)

View file

@ -35,7 +35,7 @@ class OpenNebulaManager:
'11': 'CLONING_FAILURE', '11': 'CLONING_FAILURE',
} }
def __init__(self, email=None, password=None, create_user=True): def __init__(self, email=None, password=None):
# Get oneadmin client # Get oneadmin client
self.oneadmin_client = self._get_opennebula_client( self.oneadmin_client = self._get_opennebula_client(
@ -43,9 +43,6 @@ class OpenNebulaManager:
settings.OPENNEBULA_PASSWORD settings.OPENNEBULA_PASSWORD
) )
if not create_user:
return
# Get or create oppenebula user using given credentials # Get or create oppenebula user using given credentials
self.opennebula_user = self._get_or_create_user( self.opennebula_user = self._get_or_create_user(
email, email,
@ -121,9 +118,17 @@ class OpenNebulaManager:
return vm_data return vm_data
def change_user_password(self, new_password):
self.oneadmin_client.call(
oca.User.METHODS['passwd'],
self.opennebula_user.id,
new_password
)
def create_vm(self, specs): def create_vm(self, specs):
vm_id = None vm_id = None
try: try:
# We do have the vm_template param set. Get and parse it # We do have the vm_template param set. Get and parse it
# and check it to be in the desired range. # and check it to be in the desired range.
# We have 8 possible VM templates for the moment which are 1x, 2x, 4x ... # We have 8 possible VM templates for the moment which are 1x, 2x, 4x ...
@ -136,6 +141,9 @@ class OpenNebulaManager:
<TYPE>{disk_type}</TYPE> <TYPE>{disk_type}</TYPE>
<SIZE>{size}</SIZE> <SIZE>{size}</SIZE>
</DISK> </DISK>
<CONTEXT>
<SSH_PUBLIC_KEY>{ssh_key}</SSH_PUBLIC_KEY>
</CONTEXT>
</VM> </VM>
""" """
vm_id = oca.VirtualMachine.allocate( vm_id = oca.VirtualMachine.allocate(
@ -145,7 +153,8 @@ class OpenNebulaManager:
vcpu=specs.get('cores'), vcpu=specs.get('cores'),
cpu=0.1 * specs.get('cores'), cpu=0.1 * specs.get('cores'),
disk_type='fs', disk_type='fs',
size=10000 * specs.get('disk_size') size=10000 * specs.get('disk_size'),
ssh_key=specs.get('ssh_key')
) )
) )
@ -155,10 +164,6 @@ class OpenNebulaManager:
self.opennebula_user.id, self.opennebula_user.id,
self.opennebula_user.group_ids[0] self.opennebula_user.group_ids[0]
) )
# oca.VirtualMachine.chown(
# vm_id,
# )
except socket.timeout as socket_err: except socket.timeout as socket_err:
logger.error("Socket timeout error: {0}".format(socket_err)) logger.error("Socket timeout error: {0}".format(socket_err))
@ -171,6 +176,34 @@ class OpenNebulaManager:
return vm_id return vm_id
def terminate_vm(self, vm_id):
TERMINATE_ACTION = 'terminate'
vm_terminated = False
try:
self.oneadmin_client.call(
oca.VirtualMachine.METHODS['action'],
TERMINATE_ACTION,
int(vm_id),
)
vm_terminated = True
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_terminated
def get_vm_templates(self):
template_pool = oca.VmTemplatePool(self.oneadmin_client)
template_pool.info()
return template_pool
def get_vm(self, email, vm_id): def get_vm(self, email, vm_id):
# Get vm's # Get vm's
vms = self.get_vms(email) vms = self.get_vms(email)
@ -198,9 +231,6 @@ class OpenNebulaManager:
return vm_pool return vm_pool
class HostingManageVMAdmin(admin.ModelAdmin): class HostingManageVMAdmin(admin.ModelAdmin):
client = None client = None
oneadmin_client = None oneadmin_client = None

View file

@ -25,6 +25,27 @@ $( document ).ready(function() {
}); });
var hasCreditcard = window.hasCreditcard || false;
console.log("has creditcard", hasCreditcard);
// hasCreditcard= true;
var submit_form_btn = $('#payment_button_with_creditcard');
submit_form_btn.on('click', submit_payment);
function submit_payment(e){
e.preventDefault();
console.log("creditcard sdasd");
// if (hasCreditcard) {
$('#billing-form').submit();
console.log("has creditcard2");
// }
// $form.submit();
}
var $form = $('#payment-form'); var $form = $('#payment-form');
$form.submit(payWithStripe); $form.submit(payWithStripe);

View file

@ -17,14 +17,14 @@
<option value="{{template.id}}"> <option value="{{template.id}}">
CORE: {{template.cores}}, CORE: {{template.cores}},
RAM: {{template.memory}},r RAM: {{template.memory}} GiB,
SSD: {{template.disk_size}} SSD: {{template.disk_size}} GiB
</option> </option>
{% endfor %} {% endfor %}
</select> </select>
</div> </div>
<div class="form-group"> <!-- <div class="form-group">
Select VM Configuration: Select VM Configuration:
<select name="configuration"> <select name="configuration">
{% for config in configuration_options %} {% for config in configuration_options %}
@ -33,7 +33,7 @@
{% endfor %} {% endfor %}
</select> </select>
</div> </div> -->
<div class="form-group"> <div class="form-group">
<button class="btn btn-success" >{% trans "Start VM"%} </button> <button class="btn btn-success" >{% trans "Start VM"%} </button>
</div> </div>

View file

@ -8,7 +8,7 @@
<div class="col-xs-12 col-md-4 col-md-offset-2 billing"> <div class="col-xs-12 col-md-4 col-md-offset-2 billing">
<h3><b>Billing Address</b></h3> <h3><b>Billing Address</b></h3>
<hr> <hr>
<form role="form" id="billing-form" method="post" action="{% url 'hosting:payment' %}" novalidate> <form role="form" id="billing-form" method="post" action="" novalidate>
{% for field in form %} {% for field in form %}
{% csrf_token %} {% csrf_token %}
{% bootstrap_field field show_label=False type='fields'%} {% bootstrap_field field show_label=False type='fields'%}
@ -23,6 +23,17 @@
<hr> <hr>
<div> <div>
<div> <div>
{% if credit_card_data.last4 %}
<form role="form" id="payment-form-with-creditcard"novalidate>
<h5 class="billing-head">Credit Card</h5>
<h5 class="membership-lead">Last 4: *****{{credit_card_data.last4}}</h5>
<h5 class="membership-lead">Type: {{credit_card_data.cc_brand}}</h5>
<input type="hidden" name="credit_card_needed" value="false"/>
</form>
<button id="payment_button_with_creditcard" class="btn btn-success btn-lg btn-block" type="submit">Submit Payment</button>
{% else %}
<form role="form" id="payment-form" novalidate> <form role="form" id="payment-form" novalidate>
<div class="row"> <div class="row">
<div class="col-xs-9 col-md-12"> <div class="col-xs-9 col-md-12">
@ -76,6 +87,7 @@
</form> </form>
{% endif %}
</div> </div>
</div> </div>
</div> </div>
@ -109,6 +121,7 @@
</div> </div>
</div> </div>
<!-- stripe key data --> <!-- stripe key data -->
{% if stripe_key %} {% if stripe_key %}
<script type="text/javascript"> <script type="text/javascript">
@ -116,6 +129,13 @@
</script> </script>
{%endif%} {%endif%}
{% if credit_card_data.last4 and credit_card_data.cc_brand %}
<script type="text/javascript">
(function () {window.hasCreditcard = true;})();
</script>
{%endif%}
{%endblock%} {%endblock%}

View file

@ -79,7 +79,7 @@
<div class="col-md-3"> <div class="col-md-3">
<div class="well text-center"> <div class="well text-center">
<i class="fa fa-hdd-o" aria-hidden="true"></i> {% trans "Disk"%} <br/> <i class="fa fa-hdd-o" aria-hidden="true"></i> {% trans "Disk"%} <br/>
<span class="label label-success">{{virtual_machine.disk_size}} GiB</span> <span class="label label-success">{{virtual_machine.disk_size|floatformat:2}} GiB</span>
</div> </div>
</div> </div>
</div><!--/row--> </div><!--/row-->
@ -107,6 +107,7 @@
<div class="row "> <div class="row ">
<div class="col-md-12 inline-headers"> <div class="col-md-12 inline-headers">
<h3>{% trans "Current status"%}</h3> <h3>{% trans "Current status"%}</h3>
<div class="pull-right space-above"> <div class="pull-right space-above">
{% if virtual_machine.state == 'PENDING' %} {% if virtual_machine.state == 'PENDING' %}
<span class="label <span class="label
@ -130,17 +131,28 @@
{% csrf_token %} {% csrf_token %}
</form> </form>
<button type="text" <button type="text" data-href="{% url 'hosting:virtual_machines' virtual_machine.vm_id %}" data-toggle="modal" data-target="#confirm-cancel" class="btn btn-danger">{% trans "Terminate Virtual Machine"%}</button>
data-href="{% url 'hosting:virtual_machines' virtual_machine.vm_id %}" data-toggle="modal" data-target="#confirm-cancel" class="btn btn-danger">{% trans "Cancel Virtual Machine"%}</button>
</div> </div>
</div> </div>
<div class="col-md-12">
<br/>
{% if messages %}
<div class="alert alert-warning">
{% for message in messages %}
<span>{{ message }}</span>
{% endfor %}
</div>
{% endif %}
</div>
<!-- Cancel Modal --> <!-- Cancel Modal -->
<div class="modal fade" id="confirm-cancel" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true"> <div class="modal fade" id="confirm-cancel" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
{% trans "Cancel your Virtual Machine"%} {% trans "Terminate your Virtual Machine"%}
</div> </div>
<div class="modal-body"> <div class="modal-body">
{% trans "Are you sure do you want to cancel your Virtual Machine "%} {{virtual_machine.name}} {% trans "plan?"%} {% trans "Are you sure do you want to cancel your Virtual Machine "%} {{virtual_machine.name}} {% trans "plan?"%}

View file

@ -9,11 +9,18 @@
<form method="POST" action="" > <form method="POST" action="" >
{% csrf_token %} {% csrf_token %}
<h3><i class="fa fa-key" aria-hidden="true"></i>{% trans "Access Key"%} </h3> <h3><i class="fa fa-key" aria-hidden="true"></i>{% trans "Access Key"%} </h3>
{% if messages %}
<div class="alert alert-warning">
{% for message in messages %}
<span>{{ message }}</span>
{% endfor %}
</div>
{% endif %}
<hr/> <hr/>
{% if not user_key %} {% if not user_key %}
<div class="alert alert-warning"> <h3>
{% trans "Upload your own key. "%} {% trans "Upload your own key. "%}
</div> </h3>
<div class="form-group"> <div class="form-group">
<label for="comment">Paste here your public key</label> <label for="comment">Paste here your public key</label>
<textarea class="form-control" rows="6" name="public_key"></textarea> <textarea class="form-control" rows="6" name="public_key"></textarea>
@ -22,10 +29,10 @@
<button class="btn btn-success">{% trans "Upload Key"%} </a> <button class="btn btn-success">{% trans "Upload Key"%} </a>
</div> </div>
<div class="alert alert-warning"> <h3>
{% trans "Or generate a new key pair."%} {% trans "Or generate a new key pair."%}
</div> </h3>
<div class="form-group"> <div class="form-group">
<button class="btn btn-success">{% trans "Generate Key Pair"%} </a> <button class="btn btn-success">{% trans "Generate Key Pair"%} </a>
</div> </div>
@ -58,7 +65,7 @@
{% if private_key %} {% if private_key %}
<div class="alert alert-warning"> <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."%} <strong>{% trans "Warning!"%}</strong>{% trans "You can download your SSH private key once. Don't lost your key"%}
</div> </div>
<div class="form-group"> <div class="form-group">
<textarea class="form-control" rows="6" id="ssh_key" type="hidden" style="display:none">{{private_key}}</textarea> <textarea class="form-control" rows="6" id="ssh_key" type="hidden" style="display:none">{{private_key}}</textarea>
@ -101,6 +108,7 @@
// Remove anchor from body // Remove anchor from body
document.body.removeChild(a); document.body.removeChild(a);
</script> </script>
{%endif%} {%endif%}

View file

@ -11,6 +11,16 @@
<a class="btn btn-success" href="{% url 'hosting:create-virtual-machine' %}" >{% trans "Create VM"%} </a> <a class="btn btn-success" href="{% url 'hosting:create-virtual-machine' %}" >{% trans "Create VM"%} </a>
</p> </p>
<br/> <br/>
<div class="col-md-12">
<br/>
{% if messages %}
<div class="alert alert-warning">
{% for message in messages %}
<span>{{ message }}</span>
{% endfor %}
</div>
{% endif %}
</div>
<thead> <thead>
<tr> <tr>
<th>{% trans "ID"%}</th> <th>{% trans "ID"%}</th>
@ -28,7 +38,7 @@
{% if vm.state == 'ACTIVE' %} {% if vm.state == 'ACTIVE' %}
<span class="h3 label label-success"><strong> {{vm.state}}</strong></span> <span class="h3 label label-success"><strong> {{vm.state}}</strong></span>
{% elif vm.state == 'POWEROFF' %} {% elif vm.state == 'FAILED' %}
<span class="h3 label label-danger"><strong>{{vm.state}}</strong></span> <span class="h3 label label-danger"><strong>{{vm.state}}</strong></span>
{% else %} {% else %}
<span class="h3 label label-warning"><strong>{{vm.state}}</strong></span> <span class="h3 label label-warning"><strong>{{vm.state}}</strong></span>

View file

@ -11,7 +11,8 @@ from django.contrib.auth import authenticate, login
from django.contrib import messages from django.contrib import messages
from django.conf import settings from django.conf import settings
from django.shortcuts import redirect from django.shortcuts import redirect
from django.utils.http import urlsafe_base64_decode
from django.contrib.auth.tokens import default_token_generator
from guardian.mixins import PermissionRequiredMixin from guardian.mixins import PermissionRequiredMixin
from stored_messages.settings import stored_messages_settings from stored_messages.settings import stored_messages_settings
@ -21,7 +22,7 @@ from stored_messages.api import mark_read
from membership.models import CustomUser, StripeCustomer from membership.models import CustomUser, StripeCustomer
from utils.stripe_utils import StripeUtils from utils.stripe_utils import StripeUtils
from utils.forms import BillingAddressForm, PasswordResetRequestForm 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, UserHostingKey
@ -162,7 +163,7 @@ class SignupView(CreateView):
model = CustomUser model = CustomUser
def get_success_url(self): def get_success_url(self):
next_url = self.request.session.get('next', reverse_lazy('hosting:signup')) next_url = self.request.session.get('next', reverse_lazy('hosting:virtual_machines'))
return next_url return next_url
def form_valid(self, form): def form_valid(self, form):
@ -188,6 +189,43 @@ class PasswordResetConfirmView(PasswordResetConfirmViewMixin):
template_name = 'hosting/confirm_reset_password.html' template_name = 'hosting/confirm_reset_password.html'
success_url = reverse_lazy('hosting:login') success_url = reverse_lazy('hosting:login')
def post(self, request, uidb64=None, token=None, *arg, **kwargs):
try:
uid = urlsafe_base64_decode(uidb64)
user = CustomUser.objects.get(pk=uid)
opennebula_client = OpenNebulaManager(
email=user.email,
password=user.password,
)
except (TypeError, ValueError, OverflowError, CustomUser.DoesNotExist):
user = None
opennebula_client = None
form = self.form_class(request.POST)
if user is not None and default_token_generator.check_token(user, token):
if form.is_valid():
new_password = form.cleaned_data['new_password2']
user.set_password(new_password)
user.save()
messages.success(request, 'Password has been reset.')
# Change opennebula password
opennebula_client.change_user_password(new_password)
return self.form_valid(form)
else:
messages.error(request, 'Password reset has not been successful.')
form.add_error(None, 'Password reset has not been successful.')
return self.form_invalid(form)
else:
messages.error(request, 'The reset password link is no longer valid.')
form.add_error(None, 'The reset password link is no longer valid.')
return self.form_invalid(form)
class NotificationsView(LoginRequiredMixin, TemplateView): class NotificationsView(LoginRequiredMixin, TemplateView):
template_name = 'hosting/notifications.html' template_name = 'hosting/notifications.html'
@ -228,18 +266,19 @@ class GenerateVMSSHKeysView(LoginRequiredMixin, FormView):
context_object_name = "virtual_machine" context_object_name = "virtual_machine"
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
try:
user_key = UserHostingKey.objects.get(
user=self.request.user
)
except UserHostingKey.DoesNotExist:
user_key = None
context = super( context = super(
GenerateVMSSHKeysView, GenerateVMSSHKeysView,
self self
).get_context_data(**kwargs) ).get_context_data(**kwargs)
try:
user_key = UserHostingKey.objects.get(
user=self.request.user
)
except UserHostingKey.DoesNotExist:
user_key = None
context.update({ context.update({
'user_key': user_key 'user_key': user_key
}) })
@ -258,32 +297,98 @@ class GenerateVMSSHKeysView(LoginRequiredMixin, FormView):
if form.cleaned_data.get('private_key'): if form.cleaned_data.get('private_key'):
context.update({ context.update({
'private_key': form.cleaned_data.get('private_key'), 'private_key': form.cleaned_data.get('private_key'),
'key_name': form.cleaned_data.get('name') 'key_name': form.cleaned_data.get('name'),
'form': UserHostingKeyForm(request=self.request)
}) })
# print("form", form.cleaned_data) # return HttpResponseRedirect(reverse('hosting:key_pair'))
return render(self.request, self.template_name, context) return render(self.request, self.template_name, context)
def post(self, request, *args, **kwargs):
try:
UserHostingKey.objects.get(
user=self.request.user
)
return HttpResponseRedirect(reverse('hosting:key_pair'))
except UserHostingKey.DoesNotExist:
pass
form = self.get_form()
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
class PaymentVMView(LoginRequiredMixin, FormView): class PaymentVMView(LoginRequiredMixin, FormView):
template_name = 'hosting/payment.html' template_name = 'hosting/payment.html'
login_url = reverse_lazy('hosting:login') login_url = reverse_lazy('hosting:login')
form_class = BillingAddressForm form_class = BillingAddressForm
def get_form_kwargs(self):
current_billing_address = self.request.user.billing_addresses.first()
form_kwargs = super(PaymentVMView, self).get_form_kwargs()
if not current_billing_address:
return form_kwargs
form_kwargs.update({
'initial': {
'street_address': current_billing_address.street_address,
'city': current_billing_address.city,
'postal_code': current_billing_address.postal_code,
'country': current_billing_address.country,
}
})
return form_kwargs
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(PaymentVMView, self).get_context_data(**kwargs) context = super(PaymentVMView, self).get_context_data(**kwargs)
# Get user
user = self.request.user
# Get user last order
last_hosting_order = HostingOrder.objects.filter(customer__user=user).last()
# If user has already an hosting order, get the credit card data from it
if last_hosting_order:
credit_card_data = last_hosting_order.get_cc_data()
context.update({
'credit_card_data': credit_card_data if credit_card_data else None,
})
context.update({ context.update({
'stripe_key': settings.STRIPE_API_PUBLIC_KEY 'stripe_key': settings.STRIPE_API_PUBLIC_KEY
}) })
return context return context
def get(self, request, *args, **kwargs):
try:
UserHostingKey.objects.get(
user=self.request.user
)
except UserHostingKey.DoesNotExist:
messages.success(
request,
'In order to create a VM, you create/upload your SSH KEY first.'
)
return HttpResponseRedirect(reverse('hosting:key_pair'))
return self.render_to_response(self.get_context_data())
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
form = self.get_form() form = self.get_form()
if form.is_valid(): if form.is_valid():
# Get billing address data
billing_address_data = form.cleaned_data
context = self.get_context_data() context = self.get_context_data()
specifications = request.session.get('template') specifications = request.session.get('template')
vm_template_id = specifications.get('id', 1) vm_template_id = specifications.get('id', 1)
@ -321,20 +426,45 @@ class PaymentVMView(LoginRequiredMixin, FormView):
charge = charge_response.get('response_object') charge = charge_response.get('response_object')
# Create OpenNebulaManager # Create OpenNebulaManager
manager = OpenNebulaManager(email=owner.email, manager = OpenNebulaManager(email=owner.email,
password=owner.password[0:20], password=owner.password,
create_user=True) create_user=True)
template = manager.get_template(vm_template_id) template = manager.get_template(vm_template_id)
# Get user ssh key
try:
user_key = UserHostingKey.objects.get(
user=self.request.user
)
except UserHostingKey.DoesNotExist:
pass
# Create a vm using logged user # Create a vm using logged user
vm_id = manager.create_vm(vm_template_id) vm_id = manager.create_vm(
vm_template_id,
ssh_key=user_key.public_key
)
# Create a Hosting Order # Create a Hosting Order
order = HostingOrder.create(vm_id=vm_id, customer=customer, order = HostingOrder.create(
billing_address=billing_address) price=final_price,
vm_id=vm_id,
customer=customer,
billing_address=billing_address
)
# Create a Hosting Bill # Create a Hosting Bill
bill = HostingBill.create(customer=customer, billing_address=billing_address) bill = HostingBill.create(customer=customer, billing_address=billing_address)
# Create Billing Address for User if he does not have one
if not customer.user.billing_addresses.count():
billing_address_data.update({
'user': customer.user.id
})
billing_address_user_form = UserBillingAddressForm(billing_address_data)
billing_address_user_form.is_valid()
billing_address_user_form.save()
# Associate an order with a stripe payment # Associate an order with a stripe payment
order.set_stripe_charge(charge) order.set_stripe_charge(charge)
@ -344,7 +474,6 @@ class PaymentVMView(LoginRequiredMixin, FormView):
vm = VirtualMachineSerializer(manager.get_vm(vm_id)).data vm = VirtualMachineSerializer(manager.get_vm(vm_id)).data
# Send notification to ungleich as soon as VM has been booked # Send notification to ungleich as soon as VM has been booked
context = { context = {
'vm': vm, 'vm': vm,
@ -380,10 +509,11 @@ class OrdersHostingDetailView(PermissionRequiredMixin, LoginRequiredMixin, Detai
obj = self.get_object() obj = self.get_object()
owner = self.request.user owner = self.request.user
manager = OpenNebulaManager(email=owner.email, manager = OpenNebulaManager(email=owner.email,
password=owner.password[0:20], password=owner.password,
create_user=True) create_user=True)
vm = manager.get_vm(obj.vm_id) vm = manager.get_vm(obj.vm_id)
context['vm'] = VirtualMachineSerializer(vm).data context['vm'] = VirtualMachineSerializer(vm).data
return context
class OrdersHostingListView(LoginRequiredMixin, ListView): class OrdersHostingListView(LoginRequiredMixin, ListView):
@ -416,7 +546,7 @@ class VirtualMachinesPlanListView(LoginRequiredMixin, ListView):
def get_queryset(self): def get_queryset(self):
owner = self.request.user owner = self.request.user
manager = OpenNebulaManager(email=owner.email, manager = OpenNebulaManager(email=owner.email,
password=owner.password[0:20], password=owner.password,
create_user=True) create_user=True)
queryset = manager.get_vms() queryset = manager.get_vms()
serializer = VirtualMachineSerializer(queryset, many=True) serializer = VirtualMachineSerializer(queryset, many=True)
@ -428,28 +558,32 @@ class CreateVirtualMachinesView(LoginRequiredMixin, View):
login_url = reverse_lazy('hosting:login') login_url = reverse_lazy('hosting:login')
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
try:
UserHostingKey.objects.get(
user=self.request.user
)
except UserHostingKey.DoesNotExist:
messages.success(
request,
'In order to create a VM, you need to create/upload your SSH KEY first.'
)
return HttpResponseRedirect(reverse('hosting:key_pair'))
#TODO: Replace with OpenNebulaManager.get_apps #TODO: Replace with OpenNebulaManager.get_apps
templates = OpenNebulaManager().get_templates() templates = OpenNebulaManager().get_templates()
data = VirtualMachineTemplateSerializer(templates, many=True).data data = VirtualMachineTemplateSerializer(templates, many=True).data
context = { context = {
'templates': data, 'templates': data,
#'configuration_options': VirtualMachinePlan.VM_CONFIGURATION
} }
# context = {} # context = {}
return render(request, self.template_name, context) return render(request, self.template_name, context)
def post(self, request): def post(self, request):
#XXX: Fix this!
#configuration = request.POST.get('configuration')
#configuration_display = dict(VirtualMachinePlan.VM_CONFIGURATION).get(configuration)
template_id = int(request.POST.get('vm_template_id')) template_id = int(request.POST.get('vm_template_id'))
template = OpenNebulaManager().get_template(template_id) template = OpenNebulaManager().get_template(template_id)
data = VirtualMachineTemplateSerializer(template).data data = VirtualMachineTemplateSerializer(template).data
vm_specs = {
#'configuration_display': configuration_display,
#'configuration': configuration,
'template': data,
}
request.session['template'] = data request.session['template'] = data
return redirect(reverse('hosting:payment')) return redirect(reverse('hosting:payment'))
@ -458,28 +592,56 @@ class VirtualMachineView(LoginRequiredMixin, View):
template_name = "hosting/virtual_machine_detail.html" template_name = "hosting/virtual_machine_detail.html"
login_url = reverse_lazy('hosting:login') login_url = reverse_lazy('hosting:login')
def get(self, request, *args, **kwargs): def get_object(self):
owner = self.request.user owner = self.request.user
manager = OpenNebulaManager(email=owner.email, vm = None
password=owner.password[0:20], manager = OpenNebulaManager(
create_user=True) email=owner.email,
password=owner.password,
create_user=True
)
vm_id = self.kwargs.get('pk') vm_id = self.kwargs.get('pk')
try: try:
vm = manager.get_vm(vm_id) vm = manager.get_vm(vm_id)
serializer = VirtualMachineSerializer(vm)
except Exception as error: except Exception as error:
print(error) print(error)
raise Http404() raise Http404()
return vm
def get_success_url(self):
final_url = reverse('hosting:virtual_machines')
return final_url
def get(self, request, *args, **kwargs):
vm = self.get_object()
serializer = VirtualMachineSerializer(vm)
context = { context = {
'virtual_machine': serializer.data, 'virtual_machine': serializer.data,
} }
return render(request, self.template_name, context) return render(request, self.template_name, context)
def post(self, *args, **kwargs): def post(self, request, *args, **kwargs):
#TODO: add api to OpenNebulaManager owner = self.request.user
vm = self.get_object() vm = self.get_object()
vm.cancel_plan()
opennebula_vm_id = self.kwargs.get('pk')
manager = OpenNebulaManager(
email=owner.email,
password=owner.password,
create_user=True
)
terminated = manager.delete_vm(
vm.id
)
if not terminated:
messages.error(
request,
'Error terminating VM %s' % (opennebula_vm_id)
)
return HttpResponseRedirect(self.get_success_url())
context = { context = {
'vm': vm, 'vm': vm,
@ -495,8 +657,14 @@ class VirtualMachineView(LoginRequiredMixin, View):
email = BaseEmail(**email_data) email = BaseEmail(**email_data)
email.send() email.send()
messages.error(
request,
'VM %s terminated successfully' % (opennebula_vm_id)
)
return HttpResponseRedirect(self.get_success_url()) return HttpResponseRedirect(self.get_success_url())
class HostingBillListView(LoginRequiredMixin, ListView): class HostingBillListView(LoginRequiredMixin, ListView):
template_name = "hosting/bills.html" template_name = "hosting/bills.html"
login_url = reverse_lazy('hosting:login') login_url = reverse_lazy('hosting:login')
@ -505,6 +673,7 @@ class HostingBillListView(LoginRequiredMixin, ListView):
paginate_by = 10 paginate_by = 10
ordering = '-id' ordering = '-id'
class HostingBillDetailView(PermissionRequiredMixin, LoginRequiredMixin, DetailView): class HostingBillDetailView(PermissionRequiredMixin, LoginRequiredMixin, DetailView):
template_name = "hosting/bill_detail.html" template_name = "hosting/bill_detail.html"
login_url = reverse_lazy('hosting:login') login_url = reverse_lazy('hosting:login')
@ -526,7 +695,7 @@ class HostingBillDetailView(PermissionRequiredMixin, LoginRequiredMixin, DetailV
owner = self.request.user owner = self.request.user
manager = OpenNebulaManager(email=owner.email, manager = OpenNebulaManager(email=owner.email,
password=owner.password[0:20], password=owner.password,
create_user=True) create_user=True)
# Get vms # Get vms
queryset = manager.get_vms() queryset = manager.get_vms()

View file

@ -1,10 +1,14 @@
import oca import oca
import socket import socket
import logging
from django.conf import settings from django.conf import settings
from django.utils.functional import cached_property from django.utils.functional import cached_property
from oca.pool import WrongNameError from oca.pool import WrongNameError
from oca.exceptions import OpenNebulaException
logger = logging.getLogger(__name__)
class OpenNebulaManager(): class OpenNebulaManager():
"""This class represents an opennebula manager.""" """This class represents an opennebula manager."""
@ -99,13 +103,16 @@ class OpenNebulaManager():
return vm_pool.get_by_id(int(vm_id)) return vm_pool.get_by_id(int(vm_id))
#TODO: get app with id #TODO: get app with id
def create_vm(self, template_id, app_id=None): def create_vm(self, template_id, app_id=None, ssh_key=None):
extra_template = "<CONTEXT><SSH_PUBLIC_KEY>{ssh_key}</SSH_PUBLIC_KEY></CONTEXT>".format(
ssh_key=ssh_key
)
vm_id = self.oneadmin_client.call( vm_id = self.oneadmin_client.call(
oca.VmTemplate.METHODS['instantiate'], oca.VmTemplate.METHODS['instantiate'],
template_id, template_id,
'', '',
False, False,
'' extra_template
) )
try: try:
self.oneadmin_client.call( self.oneadmin_client.call(
@ -119,11 +126,29 @@ class OpenNebulaManager():
return vm_id return vm_id
def delete_vm(self, vm_id): def delete_vm(self, vm_id):
TERMINATE_ACTION = 'terminate'
vm_terminated = False
try:
self.oneadmin_client.call( self.oneadmin_client.call(
oca.VirtualMachine.METHODS['action'], oca.VirtualMachine.METHODS['action'],
'terminate', TERMINATE_ACTION,
int(vm_id) int(vm_id),
) )
vm_terminated = True
except socket.timeout as socket_err:
logger.info("Socket timeout error: {0}".format(socket_err))
print("Socket timeout error: {0}".format(socket_err))
except OpenNebulaException as opennebula_err:
logger.info("OpenNebulaException error: {0}".format(opennebula_err))
print("OpenNebulaException error: {0}".format(opennebula_err))
except OSError as os_err:
logger.info("OSError : {0}".format(os_err))
print("OSError : {0}".format(os_err))
except ValueError as value_err:
logger.info("ValueError : {0}".format(value_err))
print("ValueError : {0}".format(value_err))
return vm_terminated
def _get_template_pool(self): def _get_template_pool(self):
try: try:

View file

@ -91,7 +91,7 @@ class VirtualMachineSerializer(serializers.Serializer):
try: try:
manager = OpenNebulaManager(email=owner.email, manager = OpenNebulaManager(email=owner.email,
password=owner.password[0:20], password=owner.password,
create_user = True) create_user = True)
opennebula_id = manager.create_vm(template_id) opennebula_id = manager.create_vm(template_id)
except OpenNebulaException as err: except OpenNebulaException as err:

View file

@ -47,7 +47,7 @@ class VmCreateView(generics.ListCreateAPIView):
def get_queryset(self): def get_queryset(self):
owner = self.request.user owner = self.request.user
manager = OpenNebulaManager(email=owner.email, manager = OpenNebulaManager(email=owner.email,
password=owner.password[0:20], password=owner.password,
create_user=True) create_user=True)
return manager.get_vms() return manager.get_vms()
@ -64,21 +64,21 @@ class VmDetailsView(generics.RetrieveUpdateDestroyAPIView):
def get_queryset(self): def get_queryset(self):
owner = self.request.user owner = self.request.user
manager = OpenNebulaManager(email=owner.email, manager = OpenNebulaManager(email=owner.email,
password=owner.password[0:20], password=owner.password,
create_user=True) create_user=True)
return manager.get_vms() return manager.get_vms()
def get_object(self): def get_object(self):
owner = self.request.user owner = self.request.user
manager = OpenNebulaManager(email=owner.email, manager = OpenNebulaManager(email=owner.email,
password=owner.password[0:20], password=owner.password,
create_user=True) create_user=True)
return manager.get_vm(self.kwargs.get('pk')) return manager.get_vm(self.kwargs.get('pk'))
def perform_destroy(self, instance): def perform_destroy(self, instance):
owner = self.request.user owner = self.request.user
manager = OpenNebulaManager(email=owner.email, manager = OpenNebulaManager(email=owner.email,
password=owner.password[0:20], password=owner.password,
create_user = True) create_user = True)
manager.delete_vm(instance.id) manager.delete_vm(instance.id)

View file

@ -100,7 +100,7 @@ class EditCreditCardForm(forms.Form):
class BillingAddressForm(forms.ModelForm): class BillingAddressForm(forms.ModelForm):
token = forms.CharField(widget=forms.HiddenInput()) token = forms.CharField(widget=forms.HiddenInput(), required=False)
class Meta: class Meta:
model = BillingAddress model = BillingAddress