Merge upstream master
This commit is contained in:
		
				commit
				
					
						5a2a134070
					
				
			
		
					 14 changed files with 202 additions and 46 deletions
				
			
		
							
								
								
									
										1
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							|  | @ -36,3 +36,4 @@ secret-key | |||
| .env | ||||
| *.mo | ||||
| *.log | ||||
| *.sql | ||||
|  |  | |||
|  | @ -1,3 +1,5 @@ | |||
| Next: | ||||
|     * #3764: [hosting] Show cancelled VMs' invoices  | ||||
| 1.2.3: 2017-09-25 | ||||
|     * #3484: [dcl, hosting] Refactored account activation, password reset, VM order and cancellation email | ||||
|     * #3731: [dcl, hosting] Added cdist ssh key handler  | ||||
|  | @ -8,6 +10,7 @@ | |||
|     * #3777: [hosting] Create new VM calculator added like dcl landing | ||||
|     * #3781: [hosting] Resend activation mail | ||||
|     * #3806: [hosting] Fix can not create VMs after password reset | ||||
|     * #3812: [hosting] Modal check icon made thin and font-size fixed | ||||
|     * Feature: [cms, blog] Added /cms prefix for all the django-cms generated urls | ||||
|     * Bugfix: [dcl, hosting] added host to celery error mails | ||||
|     * Bugfix: [ungleich] Fixed wrong subdomain digitalglarus.ungleich.ch | ||||
|  |  | |||
|  | @ -1653,3 +1653,20 @@ a.list-group-item-danger.active:focus { | |||
| .panel-danger > .panel-heading .badge { | ||||
|     background-color: #eb4d5c; | ||||
| } | ||||
| 
 | ||||
| .checkmark { | ||||
|   display: inline-block; | ||||
| } | ||||
| .checkmark:after { | ||||
|   /*Add another block-level blank space*/ | ||||
|   content: ''; | ||||
|   display: block; | ||||
|   /*Make it a small rectangle so the border will create an L-shape*/ | ||||
|   width: 25px; | ||||
|   height: 60px; | ||||
|   /*Add a white border on the bottom and left, creating that 'L' */ | ||||
|   border: solid #777; | ||||
|   border-width: 0 3px 3px 0; | ||||
|   /*Rotate the L 45 degrees to turn it into a checkmark*/ | ||||
|   transform: rotate(45deg); | ||||
| } | ||||
|  |  | |||
|  | @ -13,7 +13,7 @@ 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 utils.hosting_utils import get_all_public_keys | ||||
| from utils.hosting_utils import get_all_public_keys, get_or_create_vm_detail | ||||
| from utils.forms import UserBillingAddressForm | ||||
| from utils.mailer import BaseEmail | ||||
| from utils.models import BillingAddress | ||||
|  | @ -176,6 +176,7 @@ def create_vm_task(self, vm_template_id, user, specs, template, | |||
|             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')) | ||||
|                 get_or_create_vm_detail(custom_user, manager, vm_id) | ||||
|                 if custom_user is not None: | ||||
|                     public_keys = get_all_public_keys(custom_user) | ||||
|                     keys = [{'value': key, 'state': True} for key in | ||||
|  |  | |||
|  | @ -8,7 +8,7 @@ | |||
|               <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button> | ||||
|             </div> | ||||
|             <div class="modal-body"> | ||||
|               <div class="modal-icon"><i class="fa fa-check" aria-hidden="true"></i></div> | ||||
|               <div class="modal-icon"><i class="checkmark" aria-hidden="true"></i></div> | ||||
|               <h4 class="modal-title">{% trans "Request Sent" %}</h4> | ||||
|               <p class="modal-text">{% trans "Thank you for your subscription! You will receive a confirmation mail from our team" %}</p> | ||||
|             </div> | ||||
|  |  | |||
|  | @ -388,7 +388,7 @@ msgid "Processing..." | |||
| msgstr "Abarbeitung..." | ||||
| 
 | ||||
| msgid "Hold tight, we are processing your request" | ||||
| msgstr "Bitte warten - wir verbeiten Deine Anfrage gerade" | ||||
| msgstr "Bitte warten - wir bearbeiten Deine Anfrage gerade" | ||||
| 
 | ||||
| msgid "Some problem encountered. Please try again later." | ||||
| msgstr "Ein Problem ist aufgetreten. Bitte versuche es später noch einmal." | ||||
|  |  | |||
							
								
								
									
										34
									
								
								hosting/migrations/0043_vmdetail.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								hosting/migrations/0043_vmdetail.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,34 @@ | |||
| # -*- coding: utf-8 -*- | ||||
| # Generated by Django 1.9.4 on 2017-09-24 18:12 | ||||
| from __future__ import unicode_literals | ||||
| 
 | ||||
| from django.conf import settings | ||||
| from django.db import migrations, models | ||||
| import django.db.models.deletion | ||||
| 
 | ||||
| 
 | ||||
| class Migration(migrations.Migration): | ||||
| 
 | ||||
|     dependencies = [ | ||||
|         migrations.swappable_dependency(settings.AUTH_USER_MODEL), | ||||
|         ('hosting', '0042_hostingorder_subscription_id'), | ||||
|     ] | ||||
| 
 | ||||
|     operations = [ | ||||
|         migrations.CreateModel( | ||||
|             name='VMDetail', | ||||
|             fields=[ | ||||
|                 ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | ||||
|                 ('vm_id', models.IntegerField(default=0)), | ||||
|                 ('disk_size', models.FloatField(default=0.0)), | ||||
|                 ('cores', models.FloatField(default=0.0)), | ||||
|                 ('memory', models.FloatField(default=0.0)), | ||||
|                 ('configuration', models.CharField(default='', max_length=25)), | ||||
|                 ('ipv4', models.TextField(default='')), | ||||
|                 ('ipv6', models.TextField(default='')), | ||||
|                 ('created_at', models.DateTimeField(auto_now_add=True)), | ||||
|                 ('terminated_at', models.DateTimeField(null=True)), | ||||
|                 ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), | ||||
|             ], | ||||
|         ), | ||||
|     ] | ||||
|  | @ -159,3 +159,16 @@ class HostingBill(AssignPermissionsMixin, models.Model): | |||
|         instance = cls.objects.create(customer=customer, | ||||
|                                       billing_address=billing_address) | ||||
|         return instance | ||||
| 
 | ||||
| 
 | ||||
| class VMDetail(models.Model): | ||||
|     user = models.ForeignKey(CustomUser) | ||||
|     vm_id = models.IntegerField(default=0) | ||||
|     disk_size = models.FloatField(default=0.0) | ||||
|     cores = models.FloatField(default=0.0) | ||||
|     memory = models.FloatField(default=0.0) | ||||
|     configuration = models.CharField(default='', max_length=25) | ||||
|     ipv4 = models.TextField(default='') | ||||
|     ipv6 = models.TextField(default='') | ||||
|     created_at = models.DateTimeField(auto_now_add=True) | ||||
|     terminated_at = models.DateTimeField(null=True) | ||||
|  |  | |||
|  | @ -870,3 +870,41 @@ a.list-group-item-danger.active:focus { | |||
| .panel-danger > .panel-heading .badge { | ||||
|     background-color: #eb4d5c; | ||||
| } | ||||
| 
 | ||||
| .checkmark { | ||||
|   display: inline-block; | ||||
| } | ||||
| .checkmark:after { | ||||
|   /*Add another block-level blank space*/ | ||||
|   content: ''; | ||||
|   display: block; | ||||
|   /*Make it a small rectangle so the border will create an L-shape*/ | ||||
|   width: 25px; | ||||
|   height: 60px; | ||||
|   /*Add a white border on the bottom and left, creating that 'L' */ | ||||
|   border: solid #777; | ||||
|   border-width: 0 3px 3px 0; | ||||
|   /*Rotate the L 45 degrees to turn it into a checkmark*/ | ||||
|   transform: rotate(45deg); | ||||
| } | ||||
| 
 | ||||
| .closemark { | ||||
|     display: inline-block; | ||||
|     width: 50px; | ||||
|     height: 50px; | ||||
|     position: relative; | ||||
| } | ||||
| .closemark:before, .closemark:after { | ||||
|   position: absolute; | ||||
|   left: 25px; | ||||
|   content: ' '; | ||||
|   height: 50px; | ||||
|   width: 2px; | ||||
|   background-color: #777; | ||||
| } | ||||
| .closemark:before { | ||||
|   transform: rotate(45deg); | ||||
| } | ||||
| .closemark:after { | ||||
|   transform: rotate(-45deg); | ||||
| } | ||||
|  |  | |||
|  | @ -79,7 +79,6 @@ $(document).ready(function() { | |||
|         $('html,body').scrollTop(scrollmem); | ||||
|     }); | ||||
| 
 | ||||
|     $('.modal-text').removeClass('hide'); | ||||
|     var create_vm_form = $('#virtual_machine_create_form'); | ||||
|     create_vm_form.submit(function () { | ||||
|         $('#btn-create-vm').prop('disabled', true); | ||||
|  | @ -90,26 +89,28 @@ $(document).ready(function() { | |||
|             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'); | ||||
|                     fa_icon.attr('class', 'checkmark'); | ||||
|                     // $('.modal-header > .close').removeClass('hidden');
 | ||||
|                     $('#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; | ||||
|                     }) | ||||
|                     $('#createvm-modal-done-btn') | ||||
|                         .attr('href', data.redirect) | ||||
|                         .removeClass('hide'); | ||||
|                 } | ||||
|             }, | ||||
|             error: function (xmlhttprequest, textstatus, message) { | ||||
|                     fa_icon = $('.modal-icon > .fa'); | ||||
|                     fa_icon.attr('class', 'fa fa-times'); | ||||
|                     $('.modal-header > .close').attr('class', 'close'); | ||||
|                     $('.modal-text').addClass('hide'); | ||||
|                     fa_icon.attr('class', 'fa fa-close'); | ||||
|                     if (typeof(create_vm_error_message) !== 'undefined') { | ||||
|                         $('#createvm-modal-title').text(create_vm_error_message); | ||||
|                         $('#createvm-modal-text').text(create_vm_error_message); | ||||
|                     } | ||||
|                     $('#btn-create-vm').prop('disabled', false); | ||||
|                     $('#createvm-modal-close-btn').removeClass('hide'); | ||||
|             } | ||||
|         }); | ||||
|         return false; | ||||
|     }); | ||||
|     $('#createvm-modal').on('hidden.bs.modal', function () { | ||||
|         $(this).find('.modal-footer .btn').addClass('hide'); | ||||
|     }) | ||||
| }); | ||||
|  |  | |||
|  | @ -42,7 +42,9 @@ | |||
|                 <p> | ||||
|                     <strong>{% trans "Status" %}: </strong> | ||||
|                     <strong> | ||||
|                         {% if order.status == 'Approved' %} | ||||
|                         {% if vm.terminated_at %} | ||||
|                             <span class="vm-color-failed">{% trans "Terminated" %}</span> | ||||
|                         {% elif order.status == 'Approved' %} | ||||
|                             <span class="vm-color-online">{% trans "Approved" %}</span> | ||||
|                         {% else %} | ||||
|                             <span class="vm-status-failed">{% trans "Declined" %}</span> | ||||
|  | @ -160,22 +162,19 @@ | |||
|         <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> | ||||
|                     <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"> | ||||
|                         <a id="createvm-modal-done-btn" class="btn btn-danger btn-ok btn-wide hide" href="{% url 'hosting:virtual_machines' %}">{% trans "OK" %}</a> | ||||
|                         <button id="createvm-modal-close-btn" type="button" class="btn btn-danger btn-ok btn-wide hide" data-dismiss="modal" aria-label="create-vm-close">{% trans "Close" %}</button> | ||||
|                     </div> | ||||
|                 </div> | ||||
|             </div> | ||||
|  |  | |||
|  | @ -123,8 +123,9 @@ | |||
| 				<div class="modal-header"> | ||||
| 				</div> | ||||
|         <div class="modal-body"> | ||||
| 					<div class="modal-icon"><i class="fa fa-check" aria-hidden="true"></i></div> | ||||
| 					<h4 class="modal-title" id="ModalLabel">{% blocktrans with machine_name=virtual_machine.name %}Your Virtual Machine <strong>{{machine_name}}</strong> is successfully terminated!{% endblocktrans %}</h4> | ||||
| 					<div class="modal-icon"><i class="checkmark" aria-hidden="true"></i></div> | ||||
| 					<h4 class="modal-title"></h4> | ||||
| 					<div class="modal-text" id="ModalLabel">{% blocktrans with machine_name=virtual_machine.name %}Your Virtual Machine <strong>{{machine_name}}</strong> is successfully terminated!{% endblocktrans %}</div> | ||||
|           <div class="modal-footer"> | ||||
|             <a href="{% url 'hosting:virtual_machines' %}"	class="btn btn-success btn-wide">{% trans "OK" %}</a> | ||||
|           </div> | ||||
|  |  | |||
|  | @ -1,6 +1,7 @@ | |||
| import json | ||||
| import logging | ||||
| import uuid | ||||
| from datetime import datetime | ||||
| from time import sleep | ||||
| 
 | ||||
| from django import forms | ||||
|  | @ -47,10 +48,11 @@ from utils.views import ( | |||
| from .forms import HostingUserSignupForm, HostingUserLoginForm, \ | ||||
|     UserHostingKeyForm, generate_ssh_key_name | ||||
| from .mixins import ProcessVMSelectionMixin | ||||
| from .models import HostingOrder, HostingBill, HostingPlan, UserHostingKey | ||||
| from .models import ( | ||||
|     HostingOrder, HostingBill, HostingPlan, UserHostingKey, VMDetail | ||||
| ) | ||||
| from datacenterlight.models import VMTemplate | ||||
| 
 | ||||
| 
 | ||||
| logger = logging.getLogger(__name__) | ||||
| 
 | ||||
| CONNECTION_ERROR = "Your VMs cannot be displayed at the moment due to a \ | ||||
|  | @ -689,6 +691,10 @@ class OrdersHostingDetailView(LoginRequiredMixin, | |||
| 
 | ||||
|         if obj is not None: | ||||
|             # invoice for previous order | ||||
|             try: | ||||
|                 vm_detail = VMDetail.objects.get(vm_id=obj.vm_id) | ||||
|                 context['vm'] = vm_detail.__dict__ | ||||
|             except VMDetail.DoesNotExist: | ||||
|                 try: | ||||
|                     manager = OpenNebulaManager( | ||||
|                         email=owner.email, password=owner.password | ||||
|  | @ -1055,6 +1061,10 @@ class VirtualMachineView(LoginRequiredMixin, View): | |||
|                 except WrongIdError: | ||||
|                     response['status'] = True | ||||
|                     response['text'] = ugettext('Terminated') | ||||
|                     vm_detail_obj = VMDetail.objects.filter( | ||||
|                         vm_id=opennebula_vm_id).first() | ||||
|                     vm_detail_obj.terminated_at = datetime.utcnow() | ||||
|                     vm_detail_obj.save() | ||||
|                     break | ||||
|                 except BaseException: | ||||
|                     break | ||||
|  |  | |||
|  | @ -1,4 +1,10 @@ | |||
| from hosting.models import UserHostingKey | ||||
| import logging | ||||
| from oca.pool import WrongIdError | ||||
| 
 | ||||
| from hosting.models import UserHostingKey, VMDetail | ||||
| from opennebula_api.serializers import VirtualMachineSerializer | ||||
| 
 | ||||
| logger = logging.getLogger(__name__) | ||||
| 
 | ||||
| 
 | ||||
| def get_all_public_keys(customer): | ||||
|  | @ -11,6 +17,38 @@ def get_all_public_keys(customer): | |||
|         "public_key", flat=True) | ||||
| 
 | ||||
| 
 | ||||
| def get_or_create_vm_detail(user, manager, vm_id): | ||||
|     """ | ||||
|     Returns VMDetail object related to given vm_id. Creates the object | ||||
|      if it does not exist | ||||
| 
 | ||||
|     :param vm_id: The ID of the VM which should be greater than 0. | ||||
|     :param user: The CustomUser object that owns this VM | ||||
|     :param manager: The OpenNebulaManager object | ||||
|     :return: The VMDetail object. None if vm_id is less than or equal to 0. | ||||
|     Also, for the cases where the VMDetail does not exist and we can not | ||||
|     fetch data about the VM from OpenNebula, the function returns None | ||||
|     """ | ||||
|     if vm_id <= 0: | ||||
|         return None | ||||
|     try: | ||||
|         vm_detail_obj = VMDetail.objects.get(vm_id=vm_id) | ||||
|     except VMDetail.DoesNotExist: | ||||
|         try: | ||||
|             vm_obj = manager.get_vm(vm_id) | ||||
|         except (WrongIdError, ConnectionRefusedError) as e: | ||||
|             logger.error(str(e)) | ||||
|             return None | ||||
|         vm = VirtualMachineSerializer(vm_obj).data | ||||
|         vm_detail_obj = VMDetail.objects.create( | ||||
|             user=user, vm_id=vm_id, disk_size=vm['disk_size'], | ||||
|             cores=vm['cores'], memory=vm['memory'], | ||||
|             configuration=vm['configuration'], ipv4=vm['ipv4'], | ||||
|             ipv6=vm['ipv6'] | ||||
|         ) | ||||
|     return vm_detail_obj | ||||
| 
 | ||||
| 
 | ||||
| def get_vm_price(cpu, memory, disk_size): | ||||
|     """ | ||||
|     A helper function that computes price of a VM from given cpu, ram and | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue