merged master
This commit is contained in:
		
				commit
				
					
						f4766c7d7c
					
				
			
		
					 8 changed files with 470 additions and 216 deletions
				
			
		|  | @ -8,7 +8,7 @@ msgid "" | |||
| msgstr "" | ||||
| "Project-Id-Version: PACKAGE VERSION\n" | ||||
| "Report-Msgid-Bugs-To: \n" | ||||
| "POT-Creation-Date: 2017-09-03 16:44+0000\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" | ||||
| 
 | ||||
|  | @ -183,9 +187,18 @@ msgstr "Kontakt" | |||
| msgid "All Rights Reserved" | ||||
| msgstr "Alle Rechte vorbehalten" | ||||
| 
 | ||||
| msgid "Toggle navigation" | ||||
| msgstr "Konfiguration" | ||||
| 
 | ||||
| msgid "Why Data Center Light?" | ||||
| msgstr "Warum Data Center Light?" | ||||
| 
 | ||||
| msgid "Login" | ||||
| msgstr "" | ||||
| 
 | ||||
| msgid "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) | ||||
|  |  | |||
|  | @ -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: | ||||
|  |  | |||
|  | @ -79,4 +79,35 @@ $(document).ready(function() { | |||
|         $('html,body').scrollTop(scrollmem); | ||||
|     }); | ||||
| 
 | ||||
| }); | ||||
|     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; | ||||
|     }); | ||||
| }); | ||||
|  |  | |||
|  | @ -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; | ||||
| 
 | ||||
|     }; | ||||
| 
 | ||||
|  |  | |||
|  | @ -21,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(), | ||||
|  | @ -40,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, | ||||
|  |  | |||
							
								
								
									
										288
									
								
								hosting/views.py
									
										
									
									
									
								
							
							
						
						
									
										288
									
								
								hosting/views.py
									
										
									
									
									
								
							|  | @ -1,6 +1,6 @@ | |||
| import json | ||||
| import logging | ||||
| import uuid | ||||
| import json | ||||
| from time import sleep | ||||
| 
 | ||||
| from django.conf import settings | ||||
|  | @ -9,32 +9,35 @@ from django.contrib.auth.mixins import LoginRequiredMixin | |||
| 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, HttpResponseRedirect, HttpResponse | ||||
| from django.shortcuts import redirect, 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 ugettext | ||||
| from django.views.generic import View, CreateView, FormView, ListView, \ | ||||
|     DetailView, \ | ||||
|     DeleteView, TemplateView, UpdateView | ||||
| from django.views.generic import ( | ||||
|     View, CreateView, FormView, ListView, DetailView, DeleteView, | ||||
|     TemplateView, UpdateView | ||||
| ) | ||||
| from guardian.mixins import PermissionRequiredMixin | ||||
| 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, \ | ||||
|     VirtualMachineTemplateSerializer | ||||
| from utils.forms import BillingAddressForm, PasswordResetRequestForm, \ | ||||
|     UserBillingAddressForm | ||||
| from utils.hosting_utils import get_all_public_keys | ||||
| from utils.mailer import BaseEmail | ||||
| from utils.stripe_utils import StripeUtils | ||||
| from utils.views import PasswordResetViewMixin, PasswordResetConfirmViewMixin, \ | ||||
|     LoginViewMixin | ||||
| from utils.views import ( | ||||
|     PasswordResetViewMixin, PasswordResetConfirmViewMixin, LoginViewMixin | ||||
| ) | ||||
| from .forms import HostingUserSignupForm, HostingUserLoginForm, \ | ||||
|     UserHostingKeyForm, generate_ssh_key_name | ||||
| from .mixins import ProcessVMSelectionMixin | ||||
|  | @ -607,23 +610,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,115 +627,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) | ||||
| 
 | ||||
|             # Create a vm using logged user | ||||
|             vm_id = manager.create_vm( | ||||
|                 template_id=vm_template_id, | ||||
|                 specs=specs, | ||||
|                 ssh_key=settings.ONEADMIN_USER_SSH_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() | ||||
| 
 | ||||
|             # 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) | ||||
|             if new_host is not None: | ||||
|                 public_keys = get_all_public_keys(owner) | ||||
|                 keys = [{'value': key, 'state': True} for key in public_keys] | ||||
|                 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) | ||||
| 
 | ||||
|             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" | ||||
|  | @ -753,34 +646,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" | ||||
|  |  | |||
|  | @ -3,6 +3,7 @@ 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 django.core.mail import EmailMessage | ||||
|  | @ -38,6 +39,8 @@ def save_ssh_key(self, hosts, keys): | |||
|                        '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), | ||||
|  | @ -70,8 +73,8 @@ def save_ssh_key(self, hosts, keys): | |||
|             email_data = { | ||||
|                 'subject': "celery save_ssh_key error - task id {0}".format( | ||||
|                     self.request.id.__str__()), | ||||
|                 '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': "Task Id: {0}\nResult: {1}\nTraceback: {2}".format( | ||||
|                     self.request.id.__str__(), False, str(cdist_exception)), | ||||
|             } | ||||
|  | @ -87,8 +90,8 @@ def save_ssh_key_error_handler(uuid): | |||
|         uuid, exc, result.traceback)) | ||||
|     email_data = { | ||||
|         'subject': "[celery error] Save SSH key error {0}".format(uuid), | ||||
|         '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': "Task Id: {0}\nResult: {1}\nTraceback: {2}".format( | ||||
|             uuid, exc, result.traceback), | ||||
|     } | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue