merged master
This commit is contained in:
		
				commit
				
					
						65618bb2aa
					
				
			
		
					 15 changed files with 834 additions and 286 deletions
				
			
		|  | @ -8,7 +8,7 @@ msgid "" | |||
| msgstr "" | ||||
| "Project-Id-Version: PACKAGE VERSION\n" | ||||
| "Report-Msgid-Bugs-To: \n" | ||||
| "POT-Creation-Date: 2017-09-17 01:31+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" | ||||
| 
 | ||||
|  | @ -184,7 +188,7 @@ msgid "All Rights Reserved" | |||
| msgstr "Alle Rechte vorbehalten" | ||||
| 
 | ||||
| msgid "Toggle navigation" | ||||
| msgstr "umschalten" | ||||
| msgstr "Umschalten" | ||||
| 
 | ||||
| msgid "Why Data Center Light?" | ||||
| msgstr "Warum Data Center Light?" | ||||
|  | @ -193,7 +197,7 @@ msgid "Login" | |||
| msgstr "Anmelden" | ||||
| 
 | ||||
| msgid "Dashboard" | ||||
| msgstr "Dashboard" | ||||
| msgstr "" | ||||
| 
 | ||||
| msgid "Finally, an affordable VM hosting in Switzerland!" | ||||
| msgstr "Endlich: bezahlbares VM Hosting in der Schweiz" | ||||
|  |  | |||
|  | @ -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) | ||||
|  |  | |||
|  | @ -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)) | ||||
| 
 | ||||
|  |  | |||
|  | @ -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: | ||||
|  |  | |||
|  | @ -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') | ||||
| 
 | ||||
|  |  | |||
|  | @ -24,6 +24,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 +36,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" | ||||
| 
 | ||||
|  |  | |||
|  | @ -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); | ||||
|   }); | ||||
| 
 | ||||
| }); | ||||
| }); | ||||
|  |  | |||
|  | @ -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">×</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; | ||||
| 
 | ||||
|     }; | ||||
| 
 | ||||
|  |  | |||
|  | @ -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, | ||||
|  |  | |||
							
								
								
									
										323
									
								
								hosting/views.py
									
										
									
									
									
								
							
							
						
						
									
										323
									
								
								hosting/views.py
									
										
									
									
									
								
							|  | @ -1,3 +1,5 @@ | |||
| import json | ||||
| import logging | ||||
| import uuid | ||||
| 
 | ||||
| from django import forms | ||||
|  | @ -9,21 +11,22 @@ from django.core.exceptions import ValidationError | |||
| 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, \ | ||||
|  | @ -41,8 +44,11 @@ from .models import HostingOrder, HostingBill, HostingPlan, UserHostingKey | |||
| from datacenterlight.models import VMTemplate | ||||
| 
 | ||||
| 
 | ||||
| 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): | ||||
|  | @ -373,17 +379,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) | ||||
| 
 | ||||
|  | @ -424,6 +427,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') | ||||
| 
 | ||||
| 
 | ||||
|  | @ -468,23 +478,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 | ||||
|  | @ -607,23 +611,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) | ||||
|  | @ -637,106 +628,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" | ||||
|  | @ -744,34 +647,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" | ||||
|  | @ -954,7 +968,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) | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -95,4 +95,6 @@ pycodestyle==2.3.1 | |||
| pyflakes==1.5.0 | ||||
| billiard==3.5.0.3 | ||||
| amqp==2.2.1 | ||||
| vine==1.1.4 | ||||
| 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
									
								
							
							
						
						
									
										11
									
								
								utils/hosting_utils.py
									
										
									
									
									
										Normal 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) | ||||
|  | @ -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) | ||||
|  |  | |||
|  | @ -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") | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue