merged master

This commit is contained in:
Arvind Tiwari 2017-09-20 23:21:24 +05:30
commit fc03df4652
15 changed files with 836 additions and 284 deletions

View file

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-09-11 23:47+0530\n"
"POT-Creation-Date: 2017-09-16 14:09+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -18,6 +18,10 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#, python-format
msgid "Your New VM %(vm_name)s at Data Center Light"
msgstr "Deine neue VM %(vm_name)s bei Data Center Light"
msgid "Enter name"
msgstr "Name"

View file

@ -1,15 +1,22 @@
from dynamicweb.celery import app
from datetime import datetime
from celery.exceptions import MaxRetriesExceededError
from celery.utils.log import get_task_logger
from celery import current_task
from django.conf import settings
from django.core.mail import EmailMessage
from django.utils import translation
from django.utils.translation import ugettext_lazy as _
from dynamicweb.celery import app
from hosting.models import HostingOrder, HostingBill
from membership.models import StripeCustomer, CustomUser
from opennebula_api.models import OpenNebulaManager
from opennebula_api.serializers import VirtualMachineSerializer
from hosting.models import HostingOrder, HostingBill
from utils.hosting_utils import get_all_public_keys
from utils.forms import UserBillingAddressForm
from datetime import datetime
from membership.models import StripeCustomer
from django.core.mail import EmailMessage
from utils.mailer import BaseEmail
from utils.models import BillingAddress
from celery.exceptions import MaxRetriesExceededError
logger = get_task_logger(__name__)
@ -45,25 +52,36 @@ def create_vm_task(self, vm_template_id, user, specs, template,
stripe_customer_id, billing_address_data,
billing_address_id,
charge, cc_details):
logger.debug("Running create_vm_task on {}".format(current_task.request.hostname))
vm_id = None
try:
final_price = specs.get('price')
billing_address = BillingAddress.objects.filter(
id=billing_address_id).first()
customer = StripeCustomer.objects.filter(id=stripe_customer_id).first()
# Create OpenNebulaManager
manager = OpenNebulaManager(email=settings.OPENNEBULA_USERNAME,
password=settings.OPENNEBULA_PASSWORD)
# Create a vm using oneadmin, also specify the name
if 'pass' in user:
on_user = user.get('email')
on_pass = user.get('pass')
logger.debug("Using user {user} to create VM".format(user=on_user))
vm_name = None
else:
on_user = settings.OPENNEBULA_USERNAME
on_pass = settings.OPENNEBULA_PASSWORD
logger.debug("Using OpenNebula admin user to create VM")
vm_name = "{email}-{template_name}-{date}".format(
email=user.get('email'),
template_name=template.get('name'),
date=int(datetime.now().strftime("%s")))
# Create OpenNebulaManager
manager = OpenNebulaManager(email=on_user, password=on_pass)
vm_id = manager.create_vm(
template_id=vm_template_id,
specs=specs,
ssh_key=settings.ONEADMIN_USER_SSH_PUBLIC_KEY,
vm_name="{email}-{template_name}-{date}".format(
email=user.get('email'),
template_name=template.get('name'),
date=int(datetime.now().strftime("%s")))
vm_name=vm_name
)
if vm_id is None:
@ -122,6 +140,54 @@ def create_vm_task(self, vm_template_id, user, specs, template,
}
email = EmailMessage(**email_data)
email.send()
if 'pass' in user:
lang = 'en-us'
if user.get('language') is not None:
logger.debug("Language is set to {}".format(user.get('language')))
lang = user.get('language')
translation.activate(lang)
# Send notification to the user as soon as VM has been booked
context = {
'vm': vm,
'order': order,
'base_url': "{0}://{1}".format(user.get('request_scheme'),
user.get('request_host')),
'page_header': _(
'Your New VM %(vm_name)s at Data Center Light') % {
'vm_name': vm.get('name')}
}
email_data = {
'subject': context.get('page_header'),
'to': user.get('email'),
'context': context,
'template_name': 'new_booked_vm',
'template_path': 'hosting/emails/',
'from_address': settings.DCL_SUPPORT_FROM_ADDRESS,
}
email = BaseEmail(**email_data)
email.send()
# try to see if we have the IP and that if the ssh keys can
# be configured
new_host = manager.get_primary_ipv4(vm_id)
logger.debug("New VM ID is {vm_id}".format(vm_id=vm_id))
if new_host is not None:
custom_user = CustomUser.objects.get(email=user.get('email'))
if custom_user is not None:
public_keys = get_all_public_keys(custom_user)
keys = [{'value': key, 'state': True} for key in
public_keys]
if len(keys) > 0:
logger.debug(
"Calling configure on {host} for {num_keys} keys".format(
host=new_host, num_keys=len(keys)))
# Let's delay the task by 75 seconds to be sure
# that we run the cdist configure after the host
# is up
manager.manage_public_key(keys,
hosts=[new_host],
countdown=75)
except Exception as e:
logger.error(str(e))
try:
@ -134,8 +200,8 @@ def create_vm_task(self, vm_template_id, user, specs, template,
email_data = {
'subject': '{} CELERY TASK ERROR: {}'.format(settings.DCL_TEXT,
msg_text),
'from_email': settings.DCL_SUPPORT_FROM_ADDRESS,
'to': ['info@ungleich.ch'],
'from_email': current_task.request.hostname,
'to': settings.DCL_ERROR_EMAILS_TO_LIST,
'body': ',\n'.join(str(i) for i in self.request.args)
}
email = EmailMessage(**email_data)

View file

@ -115,8 +115,8 @@ class CeleryTaskTestCase(TestCase):
'response_object').stripe_plan_id}])
stripe_subscription_obj = subscription_result.get('response_object')
# Check if the subscription was approved and is active
if stripe_subscription_obj is None or \
stripe_subscription_obj.status != 'active':
if stripe_subscription_obj is None \
or stripe_subscription_obj.status != 'active':
msg = subscription_result.get('error')
raise Exception("Creating subscription failed: {}".format(msg))

View file

@ -173,7 +173,8 @@ TEMPLATES = [
os.path.join(PROJECT_DIR, 'nosystemd/templates/'),
os.path.join(PROJECT_DIR,
'ungleich/templates/djangocms_blog/'),
os.path.join(PROJECT_DIR, 'ungleich/templates/cms/ungleichch'),
os.path.join(PROJECT_DIR,
'ungleich/templates/cms/ungleichch'),
os.path.join(PROJECT_DIR, 'ungleich/templates/ungleich'),
os.path.join(PROJECT_DIR,
'ungleich_page/templates/ungleich_page'),
@ -559,9 +560,21 @@ CELERY_RESULT_BACKEND = env('CELERY_RESULT_BACKEND')
CELERY_ACCEPT_CONTENT = ['application/json']
CELERY_TASK_SERIALIZER = 'json'
CELERY_RESULT_SERIALIZER = 'json'
CELERY_TIMEZONE = 'Europe/Zurich'
# CELERY_TIMEZONE = 'Europe/Zurich'
CELERY_MAX_RETRIES = int_env('CELERY_MAX_RETRIES', 5)
DCL_ERROR_EMAILS_TO = env('DCL_ERROR_EMAILS_TO')
DCL_ERROR_EMAILS_TO_LIST = []
if DCL_ERROR_EMAILS_TO is not None:
DCL_ERROR_EMAILS_TO_LIST = [x.strip() for x in
DCL_ERROR_EMAILS_TO.split(
',')] \
if "," in DCL_ERROR_EMAILS_TO else [DCL_ERROR_EMAILS_TO.strip()]
if 'info@ungleich.ch' not in DCL_ERROR_EMAILS_TO_LIST:
DCL_ERROR_EMAILS_TO_LIST.append('info@ungleich.ch')
ENABLE_DEBUG_LOGGING = bool_env('ENABLE_DEBUG_LOGGING')
if ENABLE_DEBUG_LOGGING:
@ -585,6 +598,9 @@ if ENABLE_DEBUG_LOGGING:
},
}
TEST_MANAGE_SSH_KEY_PUBKEY = env('TEST_MANAGE_SSH_KEY_PUBKEY')
TEST_MANAGE_SSH_KEY_HOST = env('TEST_MANAGE_SSH_KEY_HOST')
DEBUG = bool_env('DEBUG')
if DEBUG:

View file

@ -1,16 +1,22 @@
import datetime
import logging
import subprocess
import tempfile
from django import forms
from membership.models import CustomUser
from django.contrib.auth import authenticate
from django.utils.translation import ugettext_lazy as _
from membership.models import CustomUser
from utils.hosting_utils import get_all_public_keys
from .models import UserHostingKey
logger = logging.getLogger(__name__)
def generate_ssh_key_name():
return 'dcl-generated-key-' + datetime.datetime.now().strftime('%m%d%y%H%M')
return 'dcl-generated-key-' + datetime.datetime.now().strftime(
'%m%d%y%H%M')
class HostingUserLoginForm(forms.Form):
@ -38,9 +44,7 @@ class HostingUserLoginForm(forms.Form):
CustomUser.objects.get(email=email)
return email
except CustomUser.DoesNotExist:
raise forms.ValidationError("User does not exist")
else:
return email
raise forms.ValidationError(_("User does not exist"))
class HostingUserSignupForm(forms.ModelForm):
@ -51,7 +55,8 @@ class HostingUserSignupForm(forms.ModelForm):
model = CustomUser
fields = ['name', 'email', 'password']
widgets = {
'name': forms.TextInput(attrs={'placeholder': 'Enter your name or company name'}),
'name': forms.TextInput(
attrs={'placeholder': 'Enter your name or company name'}),
}
def clean_confirm_password(self):
@ -65,19 +70,55 @@ class HostingUserSignupForm(forms.ModelForm):
class UserHostingKeyForm(forms.ModelForm):
private_key = forms.CharField(widget=forms.HiddenInput(), required=False)
public_key = forms.CharField(widget=forms.Textarea(
attrs={'class': 'form_public_key', 'placeholder': _('Paste here your public key')}),
attrs={'class': 'form_public_key',
'placeholder': _('Paste here your public key')}),
required=False,
)
user = forms.models.ModelChoiceField(queryset=CustomUser.objects.all(),
required=False, widget=forms.HiddenInput())
required=False,
widget=forms.HiddenInput())
name = forms.CharField(required=False, widget=forms.TextInput(
attrs={'class': 'form_key_name', 'placeholder': _('Give a name to your key')}))
attrs={'class': 'form_key_name',
'placeholder': _('Give a name to your key')}))
def __init__(self, *args, **kwargs):
self.request = kwargs.pop("request")
super(UserHostingKeyForm, self).__init__(*args, **kwargs)
self.fields['name'].label = _('Key name')
def clean_public_key(self):
"""
Validates a public ssh key using `ssh-keygen -lf key.pub`
Also checks if a given key already exists in the database and
alerts the user of it.
:return:
"""
if 'generate' in self.request.POST:
return self.data.get('public_key')
KEY_ERROR_MESSAGE = _("Please input a proper SSH key")
openssh_pubkey_str = self.data.get('public_key').strip()
if openssh_pubkey_str in get_all_public_keys(self.request.user):
key_name = UserHostingKey.objects.filter(
user_id=self.request.user.id,
public_key=openssh_pubkey_str).first().name
KEY_EXISTS_MESSAGE = _(
"This key exists already with the name \"%(name)s\"") % {
'name': key_name}
raise forms.ValidationError(KEY_EXISTS_MESSAGE)
with tempfile.NamedTemporaryFile(delete=True) as tmp_public_key_file:
tmp_public_key_file.write(openssh_pubkey_str.encode('utf-8'))
tmp_public_key_file.flush()
try:
subprocess.check_output(
['ssh-keygen', '-lf', tmp_public_key_file.name])
except subprocess.CalledProcessError as cpe:
logger.debug(
"Not a correct ssh format {error}".format(error=str(cpe)))
raise forms.ValidationError(KEY_ERROR_MESSAGE)
return openssh_pubkey_str
def clean_name(self):
return self.data.get('name')

View file

@ -8,7 +8,11 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
<<<<<<< HEAD
"POT-Creation-Date: 2017-09-11 23:47+0530\n"
=======
"POT-Creation-Date: 2017-09-14 12:27+0000\n"
>>>>>>> master
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -24,6 +28,9 @@ msgstr "Dein Benutzername und/oder Dein Passwort ist falsch."
msgid "Your account is not activated yet."
msgstr "Dein Account wurde noch nicht aktiviert."
msgid "User does not exist"
msgstr "Der Benutzer existiert nicht"
msgid "Paste here your public key"
msgstr "Füge deinen Public Key ein"
@ -33,6 +40,13 @@ msgstr "Gebe deinem SSH-Key einen Name"
msgid "Key name"
msgstr "Key-Name"
msgid "Please input a proper SSH key"
msgstr "Bitte verwende einen gültigen SSH-Key"
#, python-format
msgid "This key exists already with the name \"%(name)s\""
msgstr "Der SSH-Key mit dem Name \"%(name)s\" existiert bereits"
msgid "All Rights Reserved"
msgstr "Alle Rechte vorbehalten"

View file

@ -1,18 +1,48 @@
$(document).ready(function () {
var create_vm_form = $('#virtual_machine_create_form');
create_vm_form.submit(function () {
$('#btn-create-vm').prop('disabled', true);
$.ajax({
url: create_vm_form.attr('action'),
type: 'POST',
data: create_vm_form.serialize(),
success: function (data) {
if (data.status === true) {
fa_icon = $('.modal-icon > .fa');
fa_icon.attr('class', 'fa fa-check');
$('.modal-header > .close').attr('class', 'close');
$('#createvm-modal-title').text(data.msg_title);
$('#createvm-modal-body').text(data.msg_body);
$('#createvm-modal').on('hidden.bs.modal', function () {
window.location = data.redirect;
})
}
},
error: function (xmlhttprequest, textstatus, message) {
fa_icon = $('.modal-icon > .fa');
fa_icon.attr('class', 'fa fa-times');
$('.modal-header > .close').attr('class', 'close');
if (typeof(create_vm_error_message) !== 'undefined') {
$('#createvm-modal-title').text(create_vm_error_message);
}
$('#btn-create-vm').prop('disabled', false);
}
});
return false;
});
$( document ).ready(function() {
$('#confirm-cancel').on('click', '.btn-ok', function (e) {
$('#virtual_machine_cancel_form').trigger('submit');
});
$('#confirm-cancel').on('click', '.btn-ok', function(e) {
$('#virtual_machine_cancel_form').trigger('submit');
});
var hash = window.location.hash;
hash && $('ul.nav a[href="' + hash + '"]').tab('show');
var hash = window.location.hash;
hash && $('ul.nav a[href="' + hash + '"]').tab('show');
$('.nav-tabs a').click(function (e) {
$(this).tab('show');
var scrollmem = $('body').scrollTop() || $('html').scrollTop();
window.location.hash = this.hash;
$('html,body').scrollTop(scrollmem);
});
$('.nav-tabs a').click(function (e) {
$(this).tab('show');
var scrollmem = $('body').scrollTop() || $('html').scrollTop();
window.location.hash = this.hash;
$('html,body').scrollTop(scrollmem);
});
});

View file

@ -1,63 +1,100 @@
{% extends "hosting/base_short.html" %}
{% load staticfiles bootstrap3 %}
{% load i18n %}
{% load custom_tags %}
{% block content %}
<div class="order-detail-container">
{% if messages %}
{% if messages %}
<div class="row">
<div class="col-xs-12 col-md-8 col-md-offset-2">
<br/>
<div class="alert alert-warning">
{% for message in messages %}
<span>{{ message }}</span>
{% endfor %}
</div>
<div class="alert alert-warning">
{% for message in messages %}
<span>{{ message }}</span>
{% endfor %}
</div>
</div>
</div>
{% endif %}
{% if not error %}
<div class="row">
<div class="col-xs-12 col-md-8 col-md-offset-2">
<div class="invoice-title">
<h2>{{page_header_text}}</h2><h3 class="pull-right">{% trans "Order #"%} {{order.id}}</h3>
</div>
<hr>
<div class="row">
<div class="col-xs-12 col-md-6 pull-right order-confirm-date">
<div class="invoice-title">
<h2>{{page_header_text}}</h2>
<h3 class="pull-right">
{% if order %}
{% trans "Order #"%} {{order.id}}
{% endif %}
</h3>
</div>
<hr>
<div class="row">
<div class="col-xs-12 col-md-6 pull-right order-confirm-date">
<address>
<strong>{% trans "Date"%}:</strong><br>
<span id="order-created_at">{{order.created_at|date:'Y-m-d H:i'}}</span><br><br>
<span id="order-created_at">
{% if order %}
{{order.created_at|date:'Y-m-d H:i'}}
{% else %}
{% now "Y-m-d H:i" %}
{% endif %}
</span><br><br>
{% if order %}
<strong>{% trans "Status:"%}</strong><br>
{% if order.status == 'Approved' %}
<strong class="text-success">{% trans "Approved" %}</strong>
<strong class="text-success">
{% trans "Approved" %}
</strong>
{% else %}
<strong class="text-danger">{% trans "Declined" %}</strong>
<strong class="text-danger">
{% trans "Declined" %}
</strong>
{% endif %}
<br><br>
{% endif %}
</address>
</div>
<div class="col-xs-12 col-md-6">
<address>
<h3><b>{% trans "Billed To:"%}</b></h3>
{{user.name}}<br>
<div class="col-xs-12 col-md-6">
<address>
<h3><b>{% trans "Billed To:"%}</b></h3>
{% if order %}
{{user.name}}<br>
{{order.billing_address.street_address}},{{order.billing_address.postal_code}}<br>
{{order.billing_address.city}}, {{order.billing_address.country}}.
</address>
</div>
{{order.billing_address.city}},
{{order.billing_address.country}}.
{% else %}
{% with request.session.billing_address_data as billing_address %}
{{billing_address|get_value_from_dict:'cardholder_name'}}<br>
{{billing_address|get_value_from_dict:'street_address'}},
{{billing_address|get_value_from_dict:'postal_code'}}<br>
{{billing_address|get_value_from_dict:'city'}},
{{billing_address|get_value_from_dict:'country'}}.
{% endwith %}
{% endif %}
</address>
</div>
</div>
<div class="row">
<div class="col-xs-6">
<address>
<strong>{% trans "Payment Method:"%}</strong><br>
{{order.cc_brand}} {% trans "ending in" %} **** {{order.last4}}<br>
{{user.email}}
</address>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-xs-6">
<address>
<strong>{% trans "Payment Method:"%}</strong><br>
{% if order %}
{{order.cc_brand}} {% trans "ending in" %} ****
{{order.last4}}<br>
{{user.email}}
{% else %}
{{cc_brand}} {% trans "ending in" %} ****
{{cc_last4}}<br>
{{request.session.user.email}}
{% endif %}
</address>
</div>
</div>
</div>
</div>
<div class="row">
@ -65,33 +102,113 @@
<h3><b>{% trans "Order summary"%}</b></h3>
<hr>
<div class="content">
<p><b>{% trans "Cores"%}</b> <span class="pull-right">{{vm.cores}}</span></p>
{% if request.session.specs %}
{% with request.session.specs as vm %}
<p><b>{% trans "Cores"%}</b>
<span class="pull-right">{{vm.cpu}}</span>
</p>
<hr>
<p><b>{% trans "Memory"%}</b> <span class="pull-right">{{vm.memory}} GB</span></p>
<p><b>{% trans "Memory"%}</b>
<span class="pull-right">{{vm.memory}} GB</span>
</p>
<hr>
<p><b>{% trans "Disk space"%}</b> <span class="pull-right">{{vm.disk_size}} GB</span></p>
<p><b>{% trans "Disk space"%}</b>
<span class="pull-right">{{vm.disk_size}} GB</span>
</p>
<hr>
<h4>{% trans "Total"%}<p class="pull-right"><b>{{vm.price}} CHF</b></p></h4>
<p><b>{% trans "Configuration"%}</b>
<span class="pull-right">{{request.session.template.name}}</span>
</p>
<hr>
<h4>{% trans "Total"%}
<p class="pull-right">
<b>{{vm.price}} CHF</b>
<span class="dcl-price-month"> /{% trans "Month" %}
</span>
</p>
</h4>
{% endwith %}
{% else %}
<p><b>{% trans "Cores"%}</b>
<span class="pull-right">{{vm.cores}}</span>
</p>
<hr>
<p><b>{% trans "Memory"%}</b>
<span class="pull-right">{{vm.memory}} GB</span>
</p>
<hr>
<p><b>{% trans "Disk space"%}</b>
<span class="pull-right">{{vm.disk_size}} GB</span>
</p>
<hr>
<h4>{% trans "Total"%}<p class="pull-right"><b>{{vm.price}}
CHF</b><span
class="dcl-price-month"> /{% trans "Month" %}</span>
</p></h4>
{% endif %}
</div>
<br/>
{% url 'hosting:payment' as payment_url %}
{% if payment_url in request.META.HTTP_REFERER %}
<div class=" content pull-right">
<a href="{% url 'hosting:virtual_machines'%}" ><button class="btn btn-info">{% trans "Finish Configuration"%}</button></a>
</div>
{% if not order %}
<form method="post" id="virtual_machine_create_form">
{% csrf_token %}
<div class="row">
<div class="col-sm-8">
<p class="dcl-place-order-text">{% blocktrans with vm_price=request.session.specs.price %}By clicking "Place order" this plan will charge your credit card account with the fee of {{ vm_price }}CHF/month{% endblocktrans %}.</p>
</div>
<div class="col-sm-4 content">
<button class="btn btn-info pull-right"
id="btn-create-vm"
data-href="{% url 'hosting:order-confirmation' %}"
data-toggle="modal"
data-target="#createvm-modal">
{% trans "Place order"%}
</button>
</div>
</div>
</form>
{% endif %}
</div>
</div>
{% endif %}
</div>
<!-- Create VM Modal -->
<div class="modal fade" id="createvm-modal" tabindex="-1" role="dialog"
aria-hidden="true" data-backdrop="static" data-keyboard="false">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close hidden" data-dismiss="modal"
aria-label="create-vm-close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="modal-icon">
<i class="fa fa-cog fa-spin fa-3x fa-fw"></i>
<span class="sr-only">{% trans "Processing..." %}</span>
</div>
<h4 class="modal-title" id="createvm-modal-title">
</h4>
<div class="modal-text" id="createvm-modal-body">
{% trans "Hold tight, we are processing your request" %}
</div>
<div class="modal-footer">
</div>
</div>
</div>
</div>
</div>
<!-- / Create VM Modal -->
<script type="text/javascript">
{% trans "Some problem encountered. Please try again later." as err_msg %}
var create_vm_error_message = '{{err_msg|safe}}.';
window.onload = function () {
var locale_date = moment.utc(document.getElementById("order-created_at").textContent,'YYYY-MM-DD HH:mm').toDate();
locale_date = moment(locale_date).format("YYYY-MM-DD h:mm:ss a");
document.getElementById('order-created_at').innerHTML = locale_date;
var locale_date = moment.utc(document.getElementById("order-created_at").textContent, 'YYYY-MM-DD HH:mm').toDate();
locale_date = moment(locale_date).format("YYYY-MM-DD h:mm:ss a");
document.getElementById('order-created_at').innerHTML = locale_date;
};

View file

@ -11,7 +11,6 @@ from .views import (
SSHKeyChoiceView, DashboardView, SettingsView)
urlpatterns = [
url(r'index/?$', IndexView.as_view(), name='index'),
url(r'django/?$', DjangoHostingView.as_view(), name='djangohosting'),
@ -22,9 +21,13 @@ urlpatterns = [
url(r'payment/?$', PaymentVMView.as_view(), name='payment'),
url(r'settings/?$', SettingsView.as_view(), name='settings'),
url(r'orders/?$', OrdersHostingListView.as_view(), name='orders'),
url(r'orders/(?P<pk>\d+)/?$', OrdersHostingDetailView.as_view(), name='orders'),
url(r'order-confirmation/?$', OrdersHostingDetailView.as_view(),
name='order-confirmation'),
url(r'orders/(?P<pk>\d+)/?$', OrdersHostingDetailView.as_view(),
name='orders'),
url(r'bills/?$', HostingBillListView.as_view(), name='bills'),
url(r'bills/(?P<pk>\d+)/?$', HostingBillDetailView.as_view(), name='bills'),
url(r'bills/(?P<pk>\d+)/?$', HostingBillDetailView.as_view(),
name='bills'),
url(r'cancel_order/(?P<pk>\d+)/?$',
OrdersHostingDeleteView.as_view(), name='delete_order'),
url(r'create_virtual_machine/?$', CreateVirtualMachinesView.as_view(),
@ -41,13 +44,16 @@ urlpatterns = [
name='delete_ssh_key'),
url(r'create_ssh_key/?$', SSHKeyCreateView.as_view(),
name='create_ssh_key'),
url(r'^notifications/$', NotificationsView.as_view(), name='notifications'),
url(r'^notifications/$', NotificationsView.as_view(),
name='notifications'),
url(r'^notifications/(?P<pk>\d+)/?$', MarkAsReadNotificationView.as_view(),
name='read_notification'),
url(r'login/?$', LoginView.as_view(), name='login'),
url(r'signup/?$', SignupView.as_view(), name='signup'),
url(r'signup-validate/?$', SignupValidateView.as_view(), name='signup-validate'),
url(r'reset-password/?$', PasswordResetView.as_view(), name='reset_password'),
url(r'signup-validate/?$', SignupValidateView.as_view(),
name='signup-validate'),
url(r'reset-password/?$', PasswordResetView.as_view(),
name='reset_password'),
url(r'reset-password-confirm/(?P<uidb64>[0-9A-Za-z]+)-(?P<token>.+)/$',
PasswordResetConfirmView.as_view(), name='reset_password_confirm'),
url(r'^logout/?$', auth_views.logout,

View file

@ -1,3 +1,5 @@
import json
import logging
import uuid
from django.conf import settings
@ -7,21 +9,22 @@ from django.contrib.auth.tokens import default_token_generator
from django.core.files.base import ContentFile
from django.core.urlresolvers import reverse_lazy, reverse
from django.http import Http404
from django.http import HttpResponseRedirect
from django.http import HttpResponseRedirect, HttpResponse
from django.shortcuts import redirect
from django.shortcuts import render
from django.utils.http import urlsafe_base64_decode
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import get_language, ugettext_lazy as _
from django.views.generic import View, CreateView, FormView, ListView, \
DetailView, \
DeleteView, TemplateView, UpdateView
from guardian.mixins import PermissionRequiredMixin
from oca.pool import WrongNameError, WrongIdError
from oca.pool import WrongIdError
from stored_messages.api import mark_read
from stored_messages.models import Message
from stored_messages.settings import stored_messages_settings
from datacenterlight.tasks import create_vm_task
from membership.models import CustomUser, StripeCustomer
from opennebula_api.models import OpenNebulaManager
from opennebula_api.serializers import VirtualMachineSerializer, \
@ -37,8 +40,11 @@ from .forms import HostingUserSignupForm, HostingUserLoginForm, \
from .mixins import ProcessVMSelectionMixin
from .models import HostingOrder, HostingBill, HostingPlan, UserHostingKey
CONNECTION_ERROR = "Your VMs cannot be displayed at the moment due to a backend \
connection error. please try again in a few minutes."
logger = logging.getLogger(__name__)
CONNECTION_ERROR = "Your VMs cannot be displayed at the moment due to a \
backend connection error. please try again in a few \
minutes."
class DashboardView(View):
@ -369,17 +375,14 @@ class SSHKeyDeleteView(LoginRequiredMixin, DeleteView):
def delete(self, request, *args, **kwargs):
owner = self.request.user
manager = OpenNebulaManager()
manager = OpenNebulaManager(
email=owner.email,
password=owner.password
)
pk = self.kwargs.get('pk')
# Get user ssh key
public_key = UserHostingKey.objects.get(pk=pk).public_key
# Add ssh key to user
try:
manager.remove_public_key(user=owner, public_key=public_key)
except ConnectionError:
pass
except WrongNameError:
pass
manager.manage_public_key([{'value': public_key, 'state': False}])
return super(SSHKeyDeleteView, self).delete(request, *args, **kwargs)
@ -420,6 +423,13 @@ class SSHKeyChoiceView(LoginRequiredMixin, View):
user=request.user, public_key=public_key, name=name)
filename = name + '_' + str(uuid.uuid4())[:8] + '_private.pem'
ssh_key.private_key.save(filename, content)
owner = self.request.user
manager = OpenNebulaManager(
email=owner.email,
password=owner.password
)
public_key_str = public_key.decode()
manager.manage_public_key([{'value': public_key_str, 'state': True}])
return redirect(reverse_lazy('hosting:ssh_keys'), foo='bar')
@ -464,23 +474,17 @@ class SSHKeyCreateView(LoginRequiredMixin, FormView):
})
owner = self.request.user
manager = OpenNebulaManager()
# Get user ssh key
public_key = str(form.cleaned_data.get('public_key', ''))
# Add ssh key to user
try:
manager.add_public_key(
user=owner, public_key=public_key, merge=True)
except ConnectionError:
pass
except WrongNameError:
pass
manager = OpenNebulaManager(
email=owner.email,
password=owner.password
)
public_key = form.cleaned_data['public_key']
if type(public_key) is bytes:
public_key = public_key.decode()
manager.manage_public_key([{'value': public_key, 'state': True}])
return HttpResponseRedirect(self.success_url)
def post(self, request, *args, **kwargs):
print(self.request.POST.dict())
form = self.get_form()
required = 'add_ssh' in self.request.POST
form.fields['name'].required = required
@ -603,23 +607,10 @@ class PaymentVMView(LoginRequiredMixin, FormView):
def post(self, request, *args, **kwargs):
form = self.get_form()
if form.is_valid():
# Get billing address data
billing_address_data = form.cleaned_data
context = self.get_context_data()
template = request.session.get('template')
specs = request.session.get('specs')
vm_template_id = template.get('id', 1)
final_price = specs.get('price')
token = form.cleaned_data.get('token')
owner = self.request.user
# Get or create stripe customer
customer = StripeCustomer.get_or_create(email=owner.email,
token=token)
@ -633,106 +624,18 @@ class PaymentVMView(LoginRequiredMixin, FormView):
# Create Billing Address
billing_address = form.save()
# Make stripe charge to a customer
stripe_utils = StripeUtils()
charge_response = stripe_utils.make_charge(amount=final_price,
customer=customer.stripe_id)
# Check if the payment was approved
if not charge_response.get('response_object'):
msg = charge_response.get('error')
messages.add_message(
self.request, messages.ERROR, msg,
extra_tags='make_charge_error')
return HttpResponseRedirect(
reverse('hosting:payment') + '#payment_error')
charge = charge_response.get('response_object')
# Create OpenNebulaManager
manager = OpenNebulaManager(email=owner.email,
password=owner.password)
# Get user ssh key
if not UserHostingKey.objects.filter(
user=self.request.user).exists():
context.update({
'sshError': 'error',
'form': form
})
return render(request, self.template_name, context)
# For now just get first one
user_key = UserHostingKey.objects.filter(
user=self.request.user).first()
# Create a vm using logged user
vm_id = manager.create_vm(
template_id=vm_template_id,
# XXX: Confi
specs=specs,
ssh_key=user_key.public_key,
)
# Create a Hosting Order
order = HostingOrder.create(
price=final_price,
vm_id=vm_id,
customer=customer,
billing_address=billing_address
)
# Create a Hosting 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
order.set_stripe_charge(charge)
# If the Stripe payment was successed, set order status approved
order.set_approved()
vm = VirtualMachineSerializer(manager.get_vm(vm_id)).data
# Send notification to the user as soon as VM has been booked
context = {
'vm': vm,
'order': order,
'base_url': "{0}://{1}".format(request.scheme,
request.get_host()),
'page_header': _(
'Your New VM %(vm_name)s at Data Center Light') % {
'vm_name': vm.get('name')}
}
email_data = {
'subject': context.get('page_header'),
'to': request.user.email,
'context': context,
'template_name': 'new_booked_vm',
'template_path': 'hosting/emails/',
'from_address': settings.DCL_SUPPORT_FROM_ADDRESS,
}
email = BaseEmail(**email_data)
email.send()
return HttpResponseRedirect(
"{url}?{query_params}".format(
url=reverse('hosting:orders', kwargs={'pk': order.id}),
query_params='page=payment'))
request.session['billing_address_data'] = billing_address_data
request.session['billing_address'] = billing_address.id
request.session['token'] = token
request.session['customer'] = customer.id
return HttpResponseRedirect("{url}?{query_params}".format(
url=reverse('hosting:order-confirmation'),
query_params='page=payment'))
else:
return self.form_invalid(form)
class OrdersHostingDetailView(PermissionRequiredMixin, LoginRequiredMixin,
class OrdersHostingDetailView(LoginRequiredMixin,
DetailView):
template_name = "hosting/order_detail.html"
context_object_name = "order"
@ -740,34 +643,145 @@ class OrdersHostingDetailView(PermissionRequiredMixin, LoginRequiredMixin,
permission_required = ['view_hostingorder']
model = HostingOrder
def get_object(self):
return HostingOrder.objects.filter(
pk=self.kwargs.get('pk')) if self.kwargs.get('pk') else None
def get_context_data(self, **kwargs):
# Get context
context = super(DetailView, self).get_context_data(**kwargs)
obj = self.get_object()
owner = self.request.user
manager = OpenNebulaManager(email=owner.email,
password=owner.password)
if 'specs' not in self.request.session:
return HttpResponseRedirect(
reverse('hosting:create_virtual_machine'))
if 'token' not in self.request.session:
return HttpResponseRedirect(reverse('hosting:payment'))
stripe_customer_id = self.request.session.get('customer')
customer = StripeCustomer.objects.filter(id=stripe_customer_id).first()
stripe_utils = StripeUtils()
card_details = stripe_utils.get_card_details(customer.stripe_id,
self.request.session.get(
'token'))
if not card_details.get('response_object'):
msg = card_details.get('error')
messages.add_message(self.request, messages.ERROR, msg,
extra_tags='failed_payment')
return HttpResponseRedirect(
reverse('hosting:payment') + '#payment_error')
if self.request.GET.get('page', '') == 'payment':
context['page_header_text'] = _('Confirm Order')
else:
context['page_header_text'] = _('Invoice')
try:
vm = manager.get_vm(obj.vm_id)
context['vm'] = VirtualMachineSerializer(vm).data
except WrongIdError:
messages.error(self.request,
'The VM you are looking for is unavailable at the moment. \
Please contact Data Center Light support.'
)
self.kwargs['error'] = 'WrongIdError'
context['error'] = 'WrongIdError'
except ConnectionRefusedError:
messages.error(self.request,
_(
'In order to create a VM, you need to create/upload your SSH KEY first.')
)
if obj is not None:
try:
manager = OpenNebulaManager(email=owner.email,
password=owner.password)
vm = manager.get_vm(obj.vm_id)
context['vm'] = VirtualMachineSerializer(vm).data
except WrongIdError:
messages.error(self.request,
'The VM you are looking for is unavailable at the moment. \
Please contact Data Center Light support.'
)
self.kwargs['error'] = 'WrongIdError'
context['error'] = 'WrongIdError'
except ConnectionRefusedError:
messages.error(self.request,
'In order to create a VM, you need to create/upload your SSH KEY first.'
)
else:
context['site_url'] = reverse('hosting:create_virtual_machine')
context['cc_last4'] = card_details.get('response_object').get(
'last4')
context['cc_brand'] = card_details.get('response_object').get(
'cc_brand')
return context
def post(self, request):
template = request.session.get('template')
specs = request.session.get('specs')
stripe_customer_id = request.session.get('customer')
customer = StripeCustomer.objects.filter(id=stripe_customer_id).first()
billing_address_data = request.session.get('billing_address_data')
billing_address_id = request.session.get('billing_address')
vm_template_id = template.get('id', 1)
# Make stripe charge to a customer
stripe_utils = StripeUtils()
card_details = stripe_utils.get_card_details(customer.stripe_id,
request.session.get(
'token'))
if not card_details.get('response_object'):
msg = card_details.get('error')
messages.add_message(self.request, messages.ERROR, msg,
extra_tags='failed_payment')
return HttpResponseRedirect(
reverse('datacenterlight:payment') + '#payment_error')
card_details_dict = card_details.get('response_object')
cpu = specs.get('cpu')
memory = specs.get('memory')
disk_size = specs.get('disk_size')
amount_to_be_charged = (cpu * 5) + (memory * 2) + (disk_size * 0.6)
plan_name = "{cpu} Cores, {memory} GB RAM, {disk_size} GB SSD".format(
cpu=cpu,
memory=memory,
disk_size=disk_size)
stripe_plan_id = StripeUtils.get_stripe_plan_id(cpu=cpu,
ram=memory,
ssd=disk_size,
version=1,
app='dcl')
stripe_plan = stripe_utils.get_or_create_stripe_plan(
amount=amount_to_be_charged,
name=plan_name,
stripe_plan_id=stripe_plan_id)
subscription_result = stripe_utils.subscribe_customer_to_plan(
customer.stripe_id,
[{"plan": stripe_plan.get(
'response_object').stripe_plan_id}])
stripe_subscription_obj = subscription_result.get('response_object')
# Check if the subscription was approved and is active
if stripe_subscription_obj is None or stripe_subscription_obj.status != 'active':
msg = subscription_result.get('error')
messages.add_message(self.request, messages.ERROR, msg,
extra_tags='failed_payment')
return HttpResponseRedirect(
reverse('hosting:payment') + '#payment_error')
user = {
'name': self.request.user.name,
'email': self.request.user.email,
'pass': self.request.user.password,
'request_scheme': request.scheme,
'request_host': request.get_host(),
'language': get_language(),
}
create_vm_task.delay(vm_template_id, user, specs, template,
stripe_customer_id, billing_address_data,
billing_address_id,
stripe_subscription_obj, card_details_dict)
for session_var in ['specs', 'template', 'billing_address',
'billing_address_data',
'token', 'customer']:
if session_var in request.session:
del request.session[session_var]
response = {
'status': True,
'redirect': reverse('hosting:virtual_machines'),
'msg_title': str(_('Thank you for the order.')),
'msg_body': str(_('Your VM will be up and running in a few moments.'
' We will send you a confirmation email as soon as'
' it is ready.'))
}
return HttpResponse(json.dumps(response),
content_type="application/json")
class OrdersHostingListView(LoginRequiredMixin, ListView):
template_name = "hosting/orders.html"
@ -918,7 +932,8 @@ class VirtualMachineView(LoginRequiredMixin, View):
'order': HostingOrder.objects.get(
vm_id=serializer.data['vm_id'])
}
except:
except Exception as ex:
logger.debug("Exception generated {}".format(str(ex)))
pass
return render(request, self.template_name, context)

View file

@ -1,13 +1,14 @@
import oca
import socket
import logging
import socket
from oca.pool import WrongNameError, WrongIdError
from oca.exceptions import OpenNebulaException
import oca
from django.conf import settings
from oca.exceptions import OpenNebulaException
from oca.pool import WrongNameError, WrongIdError
from hosting.models import HostingOrder
from utils.models import CustomUser
from utils.tasks import save_ssh_key, save_ssh_key_error_handler
from .exceptions import KeyExistsError, UserExistsError, UserCredentialError
logger = logging.getLogger(__name__)
@ -17,7 +18,8 @@ class OpenNebulaManager():
"""This class represents an opennebula manager."""
def __init__(self, email=None, password=None):
self.email = email
self.password = password
# Get oneadmin client
self.oneadmin_client = self._get_opennebula_client(
settings.OPENNEBULA_USERNAME,
@ -122,16 +124,19 @@ class OpenNebulaManager():
except WrongNameError:
user_id = self.oneadmin_client.call(oca.User.METHODS['allocate'],
user.email, user.password, 'core')
logger.debug('Created a user for CustomObject: {user} with user id = {u_id}',
user=user,
u_id=user_id
)
user.email, user.password,
'core')
logger.debug(
'Created a user for CustomObject: {user} with user id = {u_id}',
user=user,
u_id=user_id
)
return user_id
except ConnectionRefusedError:
logger.error('Could not connect to host: {host} via protocol {protocol}'.format(
host=settings.OPENNEBULA_DOMAIN,
protocol=settings.OPENNEBULA_PROTOCOL)
logger.error(
'Could not connect to host: {host} via protocol {protocol}'.format(
host=settings.OPENNEBULA_DOMAIN,
protocol=settings.OPENNEBULA_PROTOCOL)
)
raise ConnectionRefusedError
@ -141,8 +146,9 @@ class OpenNebulaManager():
opennebula_user = user_pool.get_by_name(email)
return opennebula_user
except WrongNameError as wrong_name_err:
opennebula_user = self.oneadmin_client.call(oca.User.METHODS['allocate'], email,
password, 'core')
opennebula_user = self.oneadmin_client.call(
oca.User.METHODS['allocate'], email,
password, 'core')
logger.debug(
"User {0} does not exist. Created the user. User id = {1}",
email,
@ -150,9 +156,10 @@ class OpenNebulaManager():
)
return opennebula_user
except ConnectionRefusedError:
logger.info('Could not connect to host: {host} via protocol {protocol}'.format(
host=settings.OPENNEBULA_DOMAIN,
protocol=settings.OPENNEBULA_PROTOCOL)
logger.info(
'Could not connect to host: {host} via protocol {protocol}'.format(
host=settings.OPENNEBULA_DOMAIN,
protocol=settings.OPENNEBULA_PROTOCOL)
)
raise ConnectionRefusedError
@ -161,9 +168,10 @@ class OpenNebulaManager():
user_pool = oca.UserPool(self.oneadmin_client)
user_pool.info()
except ConnectionRefusedError:
logger.info('Could not connect to host: {host} via protocol {protocol}'.format(
host=settings.OPENNEBULA_DOMAIN,
protocol=settings.OPENNEBULA_PROTOCOL)
logger.info(
'Could not connect to host: {host} via protocol {protocol}'.format(
host=settings.OPENNEBULA_DOMAIN,
protocol=settings.OPENNEBULA_PROTOCOL)
)
raise
return user_pool
@ -183,9 +191,10 @@ class OpenNebulaManager():
raise ConnectionRefusedError
except ConnectionRefusedError:
logger.info('Could not connect to host: {host} via protocol {protocol}'.format(
host=settings.OPENNEBULA_DOMAIN,
protocol=settings.OPENNEBULA_PROTOCOL)
logger.info(
'Could not connect to host: {host} via protocol {protocol}'.format(
host=settings.OPENNEBULA_DOMAIN,
protocol=settings.OPENNEBULA_PROTOCOL)
)
raise ConnectionRefusedError
# For now we'll just handle all other errors as connection errors
@ -208,6 +217,33 @@ class OpenNebulaManager():
except:
raise ConnectionRefusedError
def get_primary_ipv4(self, vm_id):
"""
Returns the primary IPv4 of the given vm.
To be changed later.
:return: An IP address string, if it exists else returns None
"""
all_ipv4s = self.get_vm_ipv4_addresses(vm_id)
if len(all_ipv4s) > 0:
return all_ipv4s[0]
else:
return None
def get_vm_ipv4_addresses(self, vm_id):
"""
Returns a list of IPv4 addresses of the given vm
:param vm_id: The ID of the vm
:return:
"""
ipv4s = []
vm = self.get_vm(vm_id)
for nic in vm.template.nics:
if hasattr(nic, 'ip'):
ipv4s.append(nic.ip)
return ipv4s
def create_vm(self, template_id, specs, ssh_key=None, vm_name=None):
template = self.get_template(template_id)
@ -258,7 +294,8 @@ class OpenNebulaManager():
vm_specs += "<CONTEXT>"
if ssh_key:
vm_specs += "<SSH_PUBLIC_KEY>{ssh}</SSH_PUBLIC_KEY>".format(ssh=ssh_key)
vm_specs += "<SSH_PUBLIC_KEY>{ssh}</SSH_PUBLIC_KEY>".format(
ssh=ssh_key)
vm_specs += """<NETWORK>YES</NETWORK>
</CONTEXT>
</TEMPLATE>
@ -312,9 +349,11 @@ class OpenNebulaManager():
template_pool.info()
return template_pool
except ConnectionRefusedError:
logger.info('Could not connect to host: {host} via protocol {protocol}'.format(
host=settings.OPENNEBULA_DOMAIN,
protocol=settings.OPENNEBULA_PROTOCOL)
logger.info(
"""Could not connect to host: {host} via protocol
{protocol}""".format(
host=settings.OPENNEBULA_DOMAIN,
protocol=settings.OPENNEBULA_PROTOCOL)
)
raise ConnectionRefusedError
except:
@ -347,7 +386,8 @@ class OpenNebulaManager():
except:
raise ConnectionRefusedError
def create_template(self, name, cores, memory, disk_size, core_price, memory_price,
def create_template(self, name, cores, memory, disk_size, core_price,
memory_price,
disk_size_price, ssh=''):
"""Create and add a new template to opennebula.
:param name: A string representation describing the template.
@ -490,3 +530,57 @@ class OpenNebulaManager():
except ConnectionError:
raise
def manage_public_key(self, keys, hosts=None, countdown=0):
"""
A function that manages the supplied keys in the
authorized_keys file of the given list of hosts. If hosts
parameter is not supplied, all hosts of this customer
will be configured with the supplied keys
:param keys: A list of ssh keys that are to be added/removed
A key should be a dict of the form
{
'value': 'sha-.....', # public key as string
'state': True # whether key is to be added or
} # removed
:param hosts: A list of hosts IP addresses
:param countdown: Parameter to be passed to celery apply_async
Allows to delay a task by `countdown` number of seconds
:return:
"""
if hosts is None:
hosts = self.get_all_hosts()
if len(hosts) > 0 and len(keys) > 0:
save_ssh_key.apply_async((hosts, keys), countdown=countdown,
link_error=save_ssh_key_error_handler.s())
else:
logger.debug(
"Keys and/or hosts are empty, so not managing any keys")
def get_all_hosts(self):
"""
A utility function to obtain all hosts of this owner
:return: A list of hosts IP addresses, empty if none exist
"""
owner = CustomUser.objects.filter(
email=self.email).first()
all_orders = HostingOrder.objects.filter(customer__user=owner)
hosts = []
if len(all_orders) > 0:
logger.debug("The user {} has 1 or more VMs. We need to configure "
"the ssh keys.".format(self.email))
for order in all_orders:
try:
vm = self.get_vm(order.vm_id)
for nic in vm.template.nics:
if hasattr(nic, 'ip'):
hosts.append(nic.ip)
except WrongIdError:
logger.debug(
"VM with ID {} does not exist".format(order.vm_id))
else:
logger.debug("The user {} has no VMs. We don't need to configure "
"the ssh keys.".format(self.email))
return hosts

View file

@ -96,3 +96,5 @@ pyflakes==1.5.0
billiard==3.5.0.3
amqp==2.2.1
vine==1.1.4
#git+https://github.com/ungleich/cdist.git#egg=cdist
file:///home/app/cdist#egg=cdist

11
utils/hosting_utils.py Normal file
View file

@ -0,0 +1,11 @@
from hosting.models import UserHostingKey
def get_all_public_keys(customer):
"""
Returns all the public keys of the user
:param customer: The customer whose public keys are needed
:return: A list of public keys
"""
return UserHostingKey.objects.filter(user_id=customer.id).values_list(
"public_key", flat=True)

View file

@ -1,8 +1,15 @@
import tempfile
import cdist
from cdist.integration import configure_hosts_simple
from celery.result import AsyncResult
from celery import current_task
from celery.utils.log import get_task_logger
from django.conf import settings
from dynamicweb.celery import app
from django.core.mail import EmailMessage
from dynamicweb.celery import app
logger = get_task_logger(__name__)
@ -18,3 +25,74 @@ def send_plain_email_task(self, email_data):
"""
email = EmailMessage(**email_data)
email.send()
@app.task(bind=True, max_retries=settings.CELERY_MAX_RETRIES)
def save_ssh_key(self, hosts, keys):
"""
Saves ssh key into the VMs of a user using cdist
:param hosts: A list of hosts to be configured
:param keys: A list of keys to be added. A key should be dict of the
form {
'value': 'sha-.....', # public key as string
'state': True # whether key is to be added or
} # removed
"""
logger.debug(
"Running save_ssh_key on {}".format(current_task.request.hostname))
logger.debug("""Running save_ssh_key task for
Hosts: {hosts_str}
Keys: {keys_str}""".format(hosts_str=", ".join(hosts),
keys_str=", ".join([
"{value}->{state}".format(
value=key.get('value'),
state=str(
key.get('state')))
for key in keys]))
)
return_value = True
with tempfile.NamedTemporaryFile(delete=True) as tmp_manifest:
# Generate manifest to be used for configuring the hosts
lines_list = [
' --key "{key}" --state {state} \\\n'.format(
key=key['value'],
state='present' if key['state'] else 'absent'
).encode('utf-8')
for key in keys]
lines_list.insert(0, b'__ssh_authorized_keys root \\\n')
tmp_manifest.writelines(lines_list)
tmp_manifest.flush()
try:
configure_hosts_simple(hosts,
tmp_manifest.name,
verbose=cdist.argparse.VERBOSE_TRACE)
except Exception as cdist_exception:
logger.error(cdist_exception)
return_value = False
email_data = {
'subject': "celery save_ssh_key error - task id {0}".format(
self.request.id.__str__()),
'from_email': current_task.request.hostname,
'to': settings.DCL_ERROR_EMAILS_TO_LIST,
'body': "Task Id: {0}\nResult: {1}\nTraceback: {2}".format(
self.request.id.__str__(), False, str(cdist_exception)),
}
send_plain_email_task(email_data)
return return_value
@app.task
def save_ssh_key_error_handler(uuid):
result = AsyncResult(uuid)
exc = result.get(propagate=False)
logger.error('Task {0} raised exception: {1!r}\n{2!r}'.format(
uuid, exc, result.traceback))
email_data = {
'subject': "[celery error] Save SSH key error {0}".format(uuid),
'from_email': current_task.request.hostname,
'to': settings.DCL_ERROR_EMAILS_TO_LIST,
'body': "Task Id: {0}\nResult: {1}\nTraceback: {2}".format(
uuid, exc, result.traceback),
}
send_plain_email_task(email_data)

View file

@ -1,16 +1,20 @@
import uuid
from time import sleep
from unittest.mock import patch
import stripe
from celery.result import AsyncResult
from django.conf import settings
from django.http.request import HttpRequest
from django.test import Client
from django.test import TestCase
from django.test import TestCase, override_settings
from unittest import skipIf
from model_mommy import mommy
from datacenterlight.models import StripePlan
from membership.models import StripeCustomer
from utils.stripe_utils import StripeUtils
from django.conf import settings
from .tasks import save_ssh_key
class BaseTestCase(TestCase):
@ -235,3 +239,57 @@ class StripePlanTestCase(TestStripeCustomerDescription):
'response_object').stripe_plan_id}])
self.assertIsNone(result.get('response_object'), None)
self.assertIsNotNone(result.get('error'))
class SaveSSHKeyTestCase(TestCase):
"""
A test case to test the celery save_ssh_key task
"""
@override_settings(
task_eager_propagates=True,
task_always_eager=True,
)
def setUp(self):
self.public_key = settings.TEST_MANAGE_SSH_KEY_PUBKEY
self.hosts = settings.TEST_MANAGE_SSH_KEY_HOST
@skipIf(settings.TEST_MANAGE_SSH_KEY_PUBKEY is None or
settings.TEST_MANAGE_SSH_KEY_PUBKEY == "" or
settings.TEST_MANAGE_SSH_KEY_HOST is None or
settings.TEST_MANAGE_SSH_KEY_HOST is "",
"""Skipping test_save_ssh_key_add because either host
or public key were not specified or were empty""")
def test_save_ssh_key_add(self):
async_task = save_ssh_key.delay([self.hosts],
[{'value': self.public_key,
'state': True}])
save_ssh_key_result = None
for i in range(0, 10):
sleep(5)
res = AsyncResult(async_task.task_id)
if type(res.result) is bool:
save_ssh_key_result = res.result
break
self.assertIsNotNone(save_ssh_key, "save_ssh_key_result is None")
self.assertTrue(save_ssh_key_result, "save_ssh_key_result is False")
@skipIf(settings.TEST_MANAGE_SSH_KEY_PUBKEY is None or
settings.TEST_MANAGE_SSH_KEY_PUBKEY == "" or
settings.TEST_MANAGE_SSH_KEY_HOST is None or
settings.TEST_MANAGE_SSH_KEY_HOST is "",
"""Skipping test_save_ssh_key_add because either host
or public key were not specified or were empty""")
def test_save_ssh_key_remove(self):
async_task = save_ssh_key.delay([self.hosts],
[{'value': self.public_key,
'state': False}])
save_ssh_key_result = None
for i in range(0, 10):
sleep(5)
res = AsyncResult(async_task.task_id)
if type(res.result) is bool:
save_ssh_key_result = res.result
break
self.assertIsNotNone(save_ssh_key, "save_ssh_key_result is None")
self.assertTrue(save_ssh_key_result, "save_ssh_key_result is False")