Merge branch 'master' into bugfix/log_vm_terminate_errors
This commit is contained in:
		
				commit
				
					
						8e7789462e
					
				
			
		
					 24 changed files with 284 additions and 68 deletions
				
			
		
							
								
								
									
										20
									
								
								Changelog
									
										
									
									
									
								
							
							
						
						
									
										20
									
								
								Changelog
									
										
									
									
									
								
							|  | @ -1,6 +1,24 @@ | ||||||
|  | 2.2: 2018-09-06 | ||||||
|  |     * bugfix: Include price in the Stripe plan name to make it distinct and to correct pricing since version 1.9 | ||||||
|  | 2.1.2: 2018-08-30 | ||||||
|  |     * bugfix: [blog, comic] Set blog rss feed for all blog templates | ||||||
|  | 2.1.1: 2018-08-24 | ||||||
|  |     * #5487: [hosting] Add explicit warning message for teminating VM (PR #656) | ||||||
|  |     * bugfix: [dg] Send email to admin on dg subscription and increase cc_brand field to 128 characters (PR #652) | ||||||
|  |     * #5458: [admin] Make hostingorder more readable (PR #657) | ||||||
|  |     * bugfix: [CMS templates] Set description meta field of ungleich template (was missing before) and set ungleich glarus ag uniformly as author of various CMS pages (PR #653) | ||||||
|  |     * #5473: Ping a VM before saving ssh key of the user (PR #655) | ||||||
|  | 2.1: 2018-08-21 | ||||||
|  |     * Bugfix: Increase CC brand name fields from 10 to 128 characters (PR #654) | ||||||
|  | 2.0.5: 2018-08-08 | ||||||
|  |     * Fix IPv6 VM name in the billing invoice | ||||||
|  | 2.0.4: 2018-08-07 | ||||||
|  |     * Add RSS feed link to the footer of the blog template (PR #651) | ||||||
|  |     * #5308: [ipv6only] Fix - when creating a VM, the name begins with v6only (PR #649) | ||||||
|  |     * #5293: Use `terminate-hard` action instead of `terminate` in the opennebula call to terminate a vm (PR #650) | ||||||
| 2.0.3: 2018-07-18 | 2.0.3: 2018-07-18 | ||||||
|     * Remove unused /comic url (PR #644) |     * Remove unused /comic url (PR #644) | ||||||
|     * 5126: Allow dynamicweb sites to be iframed on other by setting `X_FRAME_OPTIONS_ALLOW_FROM_URI` (PR #645) |     * #5126: Allow dynamicweb sites to be iframed on other by setting `X_FRAME_OPTIONS_ALLOW_FROM_URI` (PR #645) | ||||||
| 2.0.2: 2018-07-14 | 2.0.2: 2018-07-14 | ||||||
|     * bugfix: [blog] Add missing content block in the blog_ungleich.html template file |     * bugfix: [blog] Add missing content block in the blog_ungleich.html template file | ||||||
| 2.0.1: 2018-07-14 | 2.0.1: 2018-07-14 | ||||||
|  |  | ||||||
|  | @ -8,13 +8,16 @@ from django.core.mail import EmailMessage | ||||||
| from django.core.urlresolvers import reverse | from django.core.urlresolvers import reverse | ||||||
| from django.utils import translation | from django.utils import translation | ||||||
| from django.utils.translation import ugettext_lazy as _ | from django.utils.translation import ugettext_lazy as _ | ||||||
|  | from time import sleep | ||||||
| 
 | 
 | ||||||
| from dynamicweb.celery import app | from dynamicweb.celery import app | ||||||
| from hosting.models import HostingOrder | from hosting.models import HostingOrder | ||||||
| from membership.models import CustomUser | from membership.models import CustomUser | ||||||
| from opennebula_api.models import OpenNebulaManager | from opennebula_api.models import OpenNebulaManager | ||||||
| from opennebula_api.serializers import VirtualMachineSerializer | from opennebula_api.serializers import VirtualMachineSerializer | ||||||
| from utils.hosting_utils import get_all_public_keys, get_or_create_vm_detail | from utils.hosting_utils import ( | ||||||
|  |     get_all_public_keys, get_or_create_vm_detail, ping_ok | ||||||
|  | ) | ||||||
| from utils.mailer import BaseEmail | from utils.mailer import BaseEmail | ||||||
| from utils.stripe_utils import StripeUtils | from utils.stripe_utils import StripeUtils | ||||||
| from .models import VMPricing | from .models import VMPricing | ||||||
|  | @ -203,12 +206,45 @@ def create_vm_task(self, vm_template_id, user, specs, template, order_id): | ||||||
|                                 host=vm_ipv6, num_keys=len(keys) |                                 host=vm_ipv6, num_keys=len(keys) | ||||||
|                             ) |                             ) | ||||||
|                         ) |                         ) | ||||||
|                         # Let's delay the task by 75 seconds to be sure |                         # Let's wait until the IP responds to ping before we | ||||||
|                         # that we run the cdist configure after the host |                         # run the cdist configure on the host | ||||||
|                         # is up |                         did_manage_public_key = False | ||||||
|                         manager.manage_public_key( |                         for i in range(0, 15): | ||||||
|                             keys, hosts=[vm_ipv6], countdown=75 |                             if ping_ok(vm_ipv6): | ||||||
|                         ) |                                 logger.debug( | ||||||
|  |                                     "{} is pingable. Doing a " | ||||||
|  |                                     "manage_public_key".format(vm_ipv6) | ||||||
|  |                                 ) | ||||||
|  |                                 sleep(10) | ||||||
|  |                                 manager.manage_public_key( | ||||||
|  |                                     keys, hosts=[vm_ipv6] | ||||||
|  |                                 ) | ||||||
|  |                                 did_manage_public_key = True | ||||||
|  |                                 break | ||||||
|  |                             else: | ||||||
|  |                                 logger.debug( | ||||||
|  |                                     "Can't ping {}. Wait 5 secs".format( | ||||||
|  |                                         vm_ipv6 | ||||||
|  |                                     ) | ||||||
|  |                                 ) | ||||||
|  |                                 sleep(5) | ||||||
|  |                         if not did_manage_public_key: | ||||||
|  |                             emsg = ("Waited for over 75 seconds for {} to be " | ||||||
|  |                                     "pingable. But the VM was not reachable. " | ||||||
|  |                                     "So, gave up manage_public_key. Please do " | ||||||
|  |                                     "this manually".format(vm_ipv6)) | ||||||
|  |                             logger.error(emsg) | ||||||
|  |                             email_data = { | ||||||
|  |                                 'subject': '{} CELERY TASK INCOMPLETE: {} not ' | ||||||
|  |                                            'pingable for 75 seconds'.format( | ||||||
|  |                                                 settings.DCL_TEXT, vm_ipv6 | ||||||
|  |                                             ), | ||||||
|  |                                 'from_email': current_task.request.hostname, | ||||||
|  |                                 'to': settings.DCL_ERROR_EMAILS_TO_LIST, | ||||||
|  |                                 'body': emsg | ||||||
|  |                             } | ||||||
|  |                             email = EmailMessage(**email_data) | ||||||
|  |                             email.send() | ||||||
|     except Exception as e: |     except Exception as e: | ||||||
|         logger.error(str(e)) |         logger.error(str(e)) | ||||||
|         try: |         try: | ||||||
|  |  | ||||||
|  | @ -8,8 +8,8 @@ | ||||||
|     <meta charset="utf-8"> |     <meta charset="utf-8"> | ||||||
|     <meta http-equiv="X-UA-Compatible" content="IE=edge"> |     <meta http-equiv="X-UA-Compatible" content="IE=edge"> | ||||||
|     <meta name="viewport" content="width=device-width, initial-scale=1"> |     <meta name="viewport" content="width=device-width, initial-scale=1"> | ||||||
|     <meta name="description" content="{% page_attribute 'meta_description' %}"> |  | ||||||
|     <meta name="author" content="ungleich glarus ag"> |     <meta name="author" content="ungleich glarus ag"> | ||||||
|  |     <meta name="description" content="{% page_attribute 'meta_description' %}"> | ||||||
|     <title>{% page_attribute "page_title" %}</title> |     <title>{% page_attribute "page_title" %}</title> | ||||||
| 
 | 
 | ||||||
|     <!-- Vendor CSS --> |     <!-- Vendor CSS --> | ||||||
|  |  | ||||||
|  | @ -105,7 +105,8 @@ class CeleryTaskTestCase(TestCase): | ||||||
|                                             disk_size=disk_size) |                                             disk_size=disk_size) | ||||||
|         plan_name = StripeUtils.get_stripe_plan_name(cpu=cpu, |         plan_name = StripeUtils.get_stripe_plan_name(cpu=cpu, | ||||||
|                                                      memory=memory, |                                                      memory=memory, | ||||||
|                                                      disk_size=disk_size) |                                                      disk_size=disk_size, | ||||||
|  |                                                      price=amount_to_be_charged) | ||||||
|         stripe_plan_id = StripeUtils.get_stripe_plan_id(cpu=cpu, |         stripe_plan_id = StripeUtils.get_stripe_plan_id(cpu=cpu, | ||||||
|                                                         ram=memory, |                                                         ram=memory, | ||||||
|                                                         ssd=disk_size, |                                                         ssd=disk_size, | ||||||
|  |  | ||||||
|  | @ -1,3 +1,4 @@ | ||||||
|  | import logging | ||||||
| from django.contrib.sites.models import Site | from django.contrib.sites.models import Site | ||||||
| 
 | 
 | ||||||
| from datacenterlight.tasks import create_vm_task | from datacenterlight.tasks import create_vm_task | ||||||
|  | @ -8,6 +9,8 @@ from utils.models import BillingAddress | ||||||
| from .cms_models import CMSIntegration | from .cms_models import CMSIntegration | ||||||
| from .models import VMPricing, VMTemplate | from .models import VMPricing, VMTemplate | ||||||
| 
 | 
 | ||||||
|  | logger = logging.getLogger(__name__) | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| def get_cms_integration(name): | def get_cms_integration(name): | ||||||
|     current_site = Site.objects.get_current() |     current_site = Site.objects.get_current() | ||||||
|  |  | ||||||
|  | @ -508,14 +508,20 @@ class OrderConfirmationView(DetailView): | ||||||
|         memory = specs.get('memory') |         memory = specs.get('memory') | ||||||
|         disk_size = specs.get('disk_size') |         disk_size = specs.get('disk_size') | ||||||
|         amount_to_be_charged = specs.get('total_price') |         amount_to_be_charged = specs.get('total_price') | ||||||
|         plan_name = StripeUtils.get_stripe_plan_name(cpu=cpu, |         plan_name = StripeUtils.get_stripe_plan_name( | ||||||
|                                                      memory=memory, |             cpu=cpu, | ||||||
|                                                      disk_size=disk_size) |             memory=memory, | ||||||
|         stripe_plan_id = StripeUtils.get_stripe_plan_id(cpu=cpu, |             disk_size=disk_size, | ||||||
|                                                         ram=memory, |             price=amount_to_be_charged | ||||||
|                                                         ssd=disk_size, |         ) | ||||||
|                                                         version=1, |         stripe_plan_id = StripeUtils.get_stripe_plan_id( | ||||||
|                                                         app='dcl') |             cpu=cpu, | ||||||
|  |             ram=memory, | ||||||
|  |             ssd=disk_size, | ||||||
|  |             version=1, | ||||||
|  |             app='dcl', | ||||||
|  |             price=amount_to_be_charged | ||||||
|  |         ) | ||||||
|         stripe_plan = stripe_utils.get_or_create_stripe_plan( |         stripe_plan = stripe_utils.get_or_create_stripe_plan( | ||||||
|             amount=amount_to_be_charged, |             amount=amount_to_be_charged, | ||||||
|             name=plan_name, |             name=plan_name, | ||||||
|  |  | ||||||
							
								
								
									
										25
									
								
								digitalglarus/migrations/0026_auto_20180824_0739.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								digitalglarus/migrations/0026_auto_20180824_0739.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,25 @@ | ||||||
|  | # -*- coding: utf-8 -*- | ||||||
|  | # Generated by Django 1.9.4 on 2018-08-24 07:39 | ||||||
|  | from __future__ import unicode_literals | ||||||
|  | 
 | ||||||
|  | from django.db import migrations, models | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Migration(migrations.Migration): | ||||||
|  | 
 | ||||||
|  |     dependencies = [ | ||||||
|  |         ('digitalglarus', '0025_membershiporder_stripe_subscription_id'), | ||||||
|  |     ] | ||||||
|  | 
 | ||||||
|  |     operations = [ | ||||||
|  |         migrations.AlterField( | ||||||
|  |             model_name='bookingorder', | ||||||
|  |             name='cc_brand', | ||||||
|  |             field=models.CharField(blank=True, max_length=128), | ||||||
|  |         ), | ||||||
|  |         migrations.AlterField( | ||||||
|  |             model_name='membershiporder', | ||||||
|  |             name='cc_brand', | ||||||
|  |             field=models.CharField(blank=True, max_length=128), | ||||||
|  |         ), | ||||||
|  |     ] | ||||||
|  | @ -39,7 +39,7 @@ class Ordereable(models.Model): | ||||||
|     created_at = models.DateTimeField(auto_now_add=True) |     created_at = models.DateTimeField(auto_now_add=True) | ||||||
|     approved = models.BooleanField(default=False) |     approved = models.BooleanField(default=False) | ||||||
|     last4 = models.CharField(max_length=4, blank=True) |     last4 = models.CharField(max_length=4, blank=True) | ||||||
|     cc_brand = models.CharField(max_length=10, blank=True) |     cc_brand = models.CharField(max_length=128, blank=True) | ||||||
|     stripe_charge_id = models.CharField(max_length=100, null=True) |     stripe_charge_id = models.CharField(max_length=100, null=True) | ||||||
| 
 | 
 | ||||||
|     class Meta: |     class Meta: | ||||||
|  |  | ||||||
|  | @ -492,6 +492,18 @@ class MembershipPaymentView(LoginRequiredMixin, IsNotMemberMixin, FormView): | ||||||
|                 'membership_dates': membership.type.first_month_formated_range |                 'membership_dates': membership.type.first_month_formated_range | ||||||
|             }) |             }) | ||||||
| 
 | 
 | ||||||
|  |             email_to_admin_data = { | ||||||
|  |                 'subject': "New Digital Glarus subscription: {user}".format( | ||||||
|  |                     user=self.request.user.email | ||||||
|  |                 ), | ||||||
|  |                 'from_email': 'info@digitalglarus.ch', | ||||||
|  |                 'to': ['info@ungleich.ch'], | ||||||
|  |                 'body': "\n".join( | ||||||
|  |                     ["%s=%s" % (k, v) for (k, v) in | ||||||
|  |                      order_data.items()]), | ||||||
|  |             } | ||||||
|  |             send_plain_email_task.delay(email_to_admin_data) | ||||||
|  | 
 | ||||||
|             context = { |             context = { | ||||||
|                 'membership': membership, |                 'membership': membership, | ||||||
|                 'order': membership_order, |                 'order': membership_order, | ||||||
|  |  | ||||||
|  | @ -179,9 +179,7 @@ ROOT_URLCONF = 'dynamicweb.urls' | ||||||
| TEMPLATES = [ | TEMPLATES = [ | ||||||
|     { |     { | ||||||
|         'BACKEND': 'django.template.backends.django.DjangoTemplates', |         'BACKEND': 'django.template.backends.django.DjangoTemplates', | ||||||
|         'DIRS': [os.path.join(PROJECT_DIR, 'cms_templates/'), |         'DIRS': [os.path.join(PROJECT_DIR, 'membership'), | ||||||
|                  os.path.join(PROJECT_DIR, 'cms_templates/djangocms_blog/'), |  | ||||||
|                  os.path.join(PROJECT_DIR, 'membership'), |  | ||||||
|                  os.path.join(PROJECT_DIR, 'hosting/templates/'), |                  os.path.join(PROJECT_DIR, 'hosting/templates/'), | ||||||
|                  os.path.join(PROJECT_DIR, 'nosystemd/templates/'), |                  os.path.join(PROJECT_DIR, 'nosystemd/templates/'), | ||||||
|                  os.path.join(PROJECT_DIR, |                  os.path.join(PROJECT_DIR, | ||||||
|  | @ -192,6 +190,8 @@ TEMPLATES = [ | ||||||
|                  os.path.join(PROJECT_DIR, |                  os.path.join(PROJECT_DIR, | ||||||
|                               'ungleich_page/templates/ungleich_page'), |                               'ungleich_page/templates/ungleich_page'), | ||||||
|                  os.path.join(PROJECT_DIR, 'templates/analytics'), |                  os.path.join(PROJECT_DIR, 'templates/analytics'), | ||||||
|  |                  os.path.join(PROJECT_DIR, 'cms_templates/'), | ||||||
|  |                  os.path.join(PROJECT_DIR, 'cms_templates/djangocms_blog/'), | ||||||
|                  ], |                  ], | ||||||
|         'APP_DIRS': True, |         'APP_DIRS': True, | ||||||
|         'OPTIONS': { |         'OPTIONS': { | ||||||
|  |  | ||||||
|  | @ -8,7 +8,7 @@ msgid "" | ||||||
| msgstr "" | msgstr "" | ||||||
| "Project-Id-Version: PACKAGE VERSION\n" | "Project-Id-Version: PACKAGE VERSION\n" | ||||||
| "Report-Msgid-Bugs-To: \n" | "Report-Msgid-Bugs-To: \n" | ||||||
| "POT-Creation-Date: 2018-07-05 23:15+0000\n" | "POT-Creation-Date: 2018-08-24 09:56+0000\n" | ||||||
| "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" | ||||||
| "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" | "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" | ||||||
| "Language-Team: LANGUAGE <LL@li.org>\n" | "Language-Team: LANGUAGE <LL@li.org>\n" | ||||||
|  | @ -290,9 +290,8 @@ msgid "" | ||||||
| "You are not making any payment yet. After placing your order, you will be " | "You are not making any payment yet. After placing your order, you will be " | ||||||
| "taken to the Submit Payment Page." | "taken to the Submit Payment Page." | ||||||
| msgstr "" | msgstr "" | ||||||
| "Es wird noch keine Bezahlung vorgenommen. Die Bezahlung wird erst " | "Es wird noch keine Bezahlung vorgenommen. Die Bezahlung wird erst ausgelöst, " | ||||||
| "ausgelöst, nachdem Du die Bestellung auf der nächsten Seite bestätigt " | "nachdem Du die Bestellung auf der nächsten Seite bestätigt hast." | ||||||
| "hast." |  | ||||||
| 
 | 
 | ||||||
| msgid "SUBMIT" | msgid "SUBMIT" | ||||||
| msgstr "ABSENDEN" | msgstr "ABSENDEN" | ||||||
|  | @ -469,9 +468,9 @@ msgid "" | ||||||
| "database." | "database." | ||||||
| msgstr "" | msgstr "" | ||||||
| "Bitte wähle eine der zuvor genutzten Kreditkarten oder gib Deine " | "Bitte wähle eine der zuvor genutzten Kreditkarten oder gib Deine " | ||||||
| "Kreditkartendetails unten an. Die Bezahlung wird über " | "Kreditkartendetails unten an. Die Bezahlung wird über <a href=\"https://" | ||||||
| "<a href=\"https://stripe.com\" target=\"_blank\">Stripe</a> abgewickelt. " | "stripe.com\" target=\"_blank\">Stripe</a> abgewickelt. Wir speichern Deine " | ||||||
| "Wir speichern Deine Kreditkartendetails nicht in unserer Datenbank." | "Kreditkartendetails nicht in unserer Datenbank." | ||||||
| 
 | 
 | ||||||
| msgid "" | msgid "" | ||||||
| "Please fill in your credit card information below. We are using <a href=" | "Please fill in your credit card information below. We are using <a href=" | ||||||
|  | @ -631,6 +630,12 @@ msgstr "" | ||||||
| "Bitte entschuldige, es scheint ein unerwarteter Fehler aufgetreten zu sein. " | "Bitte entschuldige, es scheint ein unerwarteter Fehler aufgetreten zu sein. " | ||||||
| "Versuche es doch bitte noch einmal." | "Versuche es doch bitte noch einmal." | ||||||
| 
 | 
 | ||||||
|  | msgid "Attention:" | ||||||
|  | msgstr "Achtung:" | ||||||
|  | 
 | ||||||
|  | msgid "terminating VM can not be reverted." | ||||||
|  | msgstr "Das Beenden kann nicht rückgängig gemacht werden." | ||||||
|  | 
 | ||||||
| msgid "Something doesn't work?" | msgid "Something doesn't work?" | ||||||
| msgstr "Etwas funktioniert nicht?" | msgstr "Etwas funktioniert nicht?" | ||||||
| 
 | 
 | ||||||
|  | @ -643,8 +648,12 @@ msgstr "KONTAKT" | ||||||
| msgid "Terminate your Virtual Machine" | msgid "Terminate your Virtual Machine" | ||||||
| msgstr "Deine Virtuelle Maschine beenden" | msgstr "Deine Virtuelle Maschine beenden" | ||||||
| 
 | 
 | ||||||
| msgid "Do you want to cancel your Virtual Machine" | msgid "" | ||||||
| msgstr "Bist Du sicher, dass Du Deine virtuelle Maschine beenden willst" | "Terminated VMs can not be revived and will not be refunded. Do you want to " | ||||||
|  | "terminate your VM?" | ||||||
|  | msgstr "" | ||||||
|  | "Beendete VMs können nicht wiederhergestellt oder erstattet werden. Möchtest " | ||||||
|  | "du die VM beenden?" | ||||||
| 
 | 
 | ||||||
| #, python-format | #, python-format | ||||||
| msgid "" | msgid "" | ||||||
|  | @ -723,8 +732,8 @@ msgstr "Es scheint, als hättest du diese Karte bereits hinzugefügt" | ||||||
| 
 | 
 | ||||||
| #, python-brace-format | #, python-brace-format | ||||||
| msgid "An error occurred while associating the card. Details: {details}" | msgid "An error occurred while associating the card. Details: {details}" | ||||||
| msgstr "Beim Verbinden der Karte ist ein Fehler aufgetreten. Details: " | msgstr "" | ||||||
| "{details}" | "Beim Verbinden der Karte ist ein Fehler aufgetreten. Details: {details}" | ||||||
| 
 | 
 | ||||||
| msgid "Successfully associated the card with your account" | msgid "Successfully associated the card with your account" | ||||||
| msgstr "Die Karte wurde erfolgreich mit deinem Konto verbunden" | msgstr "Die Karte wurde erfolgreich mit deinem Konto verbunden" | ||||||
|  | @ -807,6 +816,9 @@ msgstr "" | ||||||
| "Es gab einen Fehler bei der Bearbeitung Deine Anfrage. Bitte versuche es " | "Es gab einen Fehler bei der Bearbeitung Deine Anfrage. Bitte versuche es " | ||||||
| "noch einmal." | "noch einmal." | ||||||
| 
 | 
 | ||||||
|  | #~ msgid "Do you want to cancel your Virtual Machine" | ||||||
|  | #~ msgstr "Bist Du sicher, dass Du Deine virtuelle Maschine beenden willst" | ||||||
|  | 
 | ||||||
| #~ msgid "Reset your password" | #~ msgid "Reset your password" | ||||||
| #~ msgstr "Passwort zurücksetzen" | #~ msgstr "Passwort zurücksetzen" | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										25
									
								
								hosting/migrations/0047_auto_20180821_1240.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								hosting/migrations/0047_auto_20180821_1240.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,25 @@ | ||||||
|  | # -*- coding: utf-8 -*- | ||||||
|  | # Generated by Django 1.9.4 on 2018-08-21 12:40 | ||||||
|  | from __future__ import unicode_literals | ||||||
|  | 
 | ||||||
|  | from django.db import migrations, models | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Migration(migrations.Migration): | ||||||
|  | 
 | ||||||
|  |     dependencies = [ | ||||||
|  |         ('hosting', '0046_usercarddetail'), | ||||||
|  |     ] | ||||||
|  | 
 | ||||||
|  |     operations = [ | ||||||
|  |         migrations.AlterField( | ||||||
|  |             model_name='hostingorder', | ||||||
|  |             name='cc_brand', | ||||||
|  |             field=models.CharField(max_length=128), | ||||||
|  |         ), | ||||||
|  |         migrations.AlterField( | ||||||
|  |             model_name='usercarddetail', | ||||||
|  |             name='brand', | ||||||
|  |             field=models.CharField(max_length=128), | ||||||
|  |         ), | ||||||
|  |     ] | ||||||
|  | @ -53,9 +53,11 @@ class OrderDetail(AssignPermissionsMixin, models.Model): | ||||||
|     ssd_size = models.IntegerField(default=0) |     ssd_size = models.IntegerField(default=0) | ||||||
| 
 | 
 | ||||||
|     def __str__(self): |     def __str__(self): | ||||||
|         return "%s - %s, %s cores, %s GB RAM, %s GB SSD" % ( |         return "Not available" if self.vm_template is None else ( | ||||||
|             self.vm_template.name, self.vm_template.vm_type, self.cores, |             "%s - %s, %s cores, %s GB RAM, %s GB SSD" % ( | ||||||
|             self.memory, self.ssd_size |                 self.vm_template.name, self.vm_template.vm_type, self.cores, | ||||||
|  |                 self.memory, self.ssd_size | ||||||
|  |             ) | ||||||
|         ) |         ) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -69,7 +71,7 @@ class HostingOrder(AssignPermissionsMixin, models.Model): | ||||||
|     created_at = models.DateTimeField(auto_now_add=True) |     created_at = models.DateTimeField(auto_now_add=True) | ||||||
|     approved = models.BooleanField(default=False) |     approved = models.BooleanField(default=False) | ||||||
|     last4 = models.CharField(max_length=4) |     last4 = models.CharField(max_length=4) | ||||||
|     cc_brand = models.CharField(max_length=10) |     cc_brand = models.CharField(max_length=128) | ||||||
|     stripe_charge_id = models.CharField(max_length=100, null=True) |     stripe_charge_id = models.CharField(max_length=100, null=True) | ||||||
|     price = models.FloatField() |     price = models.FloatField() | ||||||
|     subscription_id = models.CharField(max_length=100, null=True) |     subscription_id = models.CharField(max_length=100, null=True) | ||||||
|  | @ -87,7 +89,11 @@ class HostingOrder(AssignPermissionsMixin, models.Model): | ||||||
|         ) |         ) | ||||||
| 
 | 
 | ||||||
|     def __str__(self): |     def __str__(self): | ||||||
|         return "%s" % (self.id) |         return ("Order Nr: #{} - VM_ID: {} - {} - {} - " | ||||||
|  |                 "Specs: {} - Price: {}").format( | ||||||
|  |             self.id, self.vm_id, self.customer.user.email, self.created_at, | ||||||
|  |             self.order_detail, self.price | ||||||
|  |         ) | ||||||
| 
 | 
 | ||||||
|     @cached_property |     @cached_property | ||||||
|     def status(self): |     def status(self): | ||||||
|  | @ -212,7 +218,7 @@ class UserCardDetail(AssignPermissionsMixin, models.Model): | ||||||
|     permissions = ('view_usercarddetail',) |     permissions = ('view_usercarddetail',) | ||||||
|     stripe_customer = models.ForeignKey(StripeCustomer) |     stripe_customer = models.ForeignKey(StripeCustomer) | ||||||
|     last4 = models.CharField(max_length=4) |     last4 = models.CharField(max_length=4) | ||||||
|     brand = models.CharField(max_length=10) |     brand = models.CharField(max_length=128) | ||||||
|     card_id = models.CharField(max_length=100, blank=True, default='') |     card_id = models.CharField(max_length=100, blank=True, default='') | ||||||
|     fingerprint = models.CharField(max_length=100) |     fingerprint = models.CharField(max_length=100) | ||||||
|     exp_month = models.IntegerField(null=False) |     exp_month = models.IntegerField(null=False) | ||||||
|  |  | ||||||
|  | @ -146,6 +146,10 @@ | ||||||
|   text-align: center; |   text-align: center; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | .vm-vmid-with-warning { | ||||||
|  |   padding: 50px 0 33px !important; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| .vm-vmid .alert { | .vm-vmid .alert { | ||||||
|   margin-top: 15px; |   margin-top: 15px; | ||||||
|   margin-bottom: -60px; |   margin-bottom: -60px; | ||||||
|  | @ -183,6 +187,13 @@ | ||||||
|   margin-top: 25px; |   margin-top: 25px; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | .vm-terminate-warning { | ||||||
|  |   letter-spacing: 0.6px; | ||||||
|  |   font-size: 12px; | ||||||
|  |   font-weight: 400; | ||||||
|  |   color: #373636; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| .vm-contact-us { | .vm-contact-us { | ||||||
|   margin: 25px 0 30px; |   margin: 25px 0 30px; | ||||||
|   /* text-align: center; */ |   /* text-align: center; */ | ||||||
|  |  | ||||||
|  | @ -9,7 +9,7 @@ | ||||||
|     <meta http-equiv="X-UA-Compatible" content="IE=edge"> |     <meta http-equiv="X-UA-Compatible" content="IE=edge"> | ||||||
|     <meta name="viewport" content="width=device-width, initial-scale=1"> |     <meta name="viewport" content="width=device-width, initial-scale=1"> | ||||||
|     <meta name="description" content=""> |     <meta name="description" content=""> | ||||||
|     <meta name="author" content=""> |     <meta name="author" content="ungleich glarus ag"> | ||||||
| 
 | 
 | ||||||
|     <title>{{ domain }} - {{ hosting }} hosting as easy as possible</title> |     <title>{{ domain }} - {{ hosting }} hosting as easy as possible</title> | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -51,7 +51,7 @@ | ||||||
| 			</div> | 			</div> | ||||||
| 			<div class="vm-detail-item"> | 			<div class="vm-detail-item"> | ||||||
| 				<h2 class="vm-detail-title">{% trans "Status" %} <img src="{% static 'hosting/img/connected.svg' %}" class="un-icon"></h2> | 				<h2 class="vm-detail-title">{% trans "Status" %} <img src="{% static 'hosting/img/connected.svg' %}" class="un-icon"></h2> | ||||||
| 				<div class="vm-vmid"> | 				<div class="vm-vmid vm-vmid-with-warning"> | ||||||
| 					<div class="vm-item-subtitle">{% trans "Your VM is" %}</div> | 					<div class="vm-item-subtitle">{% trans "Your VM is" %}</div> | ||||||
| 						<div id="terminate-VM" data-alt="{% trans 'Terminating' %}"> | 						<div id="terminate-VM" data-alt="{% trans 'Terminating' %}"> | ||||||
| 							{% if virtual_machine.state == 'PENDING' %} | 							{% if virtual_machine.state == 'PENDING' %} | ||||||
|  | @ -74,6 +74,10 @@ | ||||||
| 							{% endif %} | 							{% endif %} | ||||||
| 						</div> | 						</div> | ||||||
| 				</div> | 				</div> | ||||||
|  | 				<div class="vm-terminate-warning text-center"> | ||||||
|  | 					<p>{% trans "Attention:" %}</p> | ||||||
|  | 					<p>{% trans "terminating VM can not be reverted." %}</p> | ||||||
|  | 				</div> | ||||||
| 			</div> | 			</div> | ||||||
| 		</div> | 		</div> | ||||||
| 		<div class="vm-contact-us"> | 		<div class="vm-contact-us"> | ||||||
|  | @ -105,7 +109,7 @@ | ||||||
| 					<div class="modal-icon"><i class="fa fa-ban" aria-hidden="true"></i></div> | 					<div class="modal-icon"><i class="fa fa-ban" aria-hidden="true"></i></div> | ||||||
| 					<h4 class="modal-title" id="ModalLabel">{% trans "Terminate your Virtual Machine" %}</h4> | 					<h4 class="modal-title" id="ModalLabel">{% trans "Terminate your Virtual Machine" %}</h4> | ||||||
| 					<div class="modal-text"> | 					<div class="modal-text"> | ||||||
| 						<p>{% trans "Do you want to cancel your Virtual Machine" %} ?</p> | 						<p>{% trans "Terminated VMs can not be revived and will not be refunded. Do you want to terminate your VM?" %}</p> | ||||||
| 						<p><strong>{{virtual_machine.name}}</strong></p> | 						<p><strong>{{virtual_machine.name}}</strong></p> | ||||||
| 					</div> | 					</div> | ||||||
|           <div class="modal-footer"> |           <div class="modal-footer"> | ||||||
|  |  | ||||||
|  | @ -1032,14 +1032,20 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView): | ||||||
|         memory = specs.get('memory') |         memory = specs.get('memory') | ||||||
|         disk_size = specs.get('disk_size') |         disk_size = specs.get('disk_size') | ||||||
|         amount_to_be_charged = specs.get('total_price') |         amount_to_be_charged = specs.get('total_price') | ||||||
|         plan_name = StripeUtils.get_stripe_plan_name(cpu=cpu, |         plan_name = StripeUtils.get_stripe_plan_name( | ||||||
|                                                      memory=memory, |             cpu=cpu, | ||||||
|                                                      disk_size=disk_size) |             memory=memory, | ||||||
|         stripe_plan_id = StripeUtils.get_stripe_plan_id(cpu=cpu, |             disk_size=disk_size, | ||||||
|                                                         ram=memory, |             price=amount_to_be_charged | ||||||
|                                                         ssd=disk_size, |         ) | ||||||
|                                                         version=1, |         stripe_plan_id = StripeUtils.get_stripe_plan_id( | ||||||
|                                                         app='dcl') |             cpu=cpu, | ||||||
|  |             ram=memory, | ||||||
|  |             ssd=disk_size, | ||||||
|  |             version=1, | ||||||
|  |             app='dcl', | ||||||
|  |             price=amount_to_be_charged | ||||||
|  |         ) | ||||||
|         stripe_plan = stripe_utils.get_or_create_stripe_plan( |         stripe_plan = stripe_utils.get_or_create_stripe_plan( | ||||||
|             amount=amount_to_be_charged, |             amount=amount_to_be_charged, | ||||||
|             name=plan_name, |             name=plan_name, | ||||||
|  |  | ||||||
|  | @ -315,7 +315,7 @@ class OpenNebulaManager(): | ||||||
|         return vm_id |         return vm_id | ||||||
| 
 | 
 | ||||||
|     def delete_vm(self, vm_id): |     def delete_vm(self, vm_id): | ||||||
|         TERMINATE_ACTION = 'terminate' |         TERMINATE_ACTION = 'terminate-hard' | ||||||
|         vm_terminated = False |         vm_terminated = False | ||||||
|         try: |         try: | ||||||
|             self.oneadmin_client.call( |             self.oneadmin_client.call( | ||||||
|  |  | ||||||
|  | @ -36,7 +36,10 @@ class VirtualMachineTemplateSerializer(serializers.Serializer): | ||||||
|         return int(obj.template.memory) / 1024 |         return int(obj.template.memory) / 1024 | ||||||
| 
 | 
 | ||||||
|     def get_name(self, obj): |     def get_name(self, obj): | ||||||
|         return obj.name.lstrip('public-') |         if obj.name.startswith('public-'): | ||||||
|  |             return obj.name.lstrip('public-') | ||||||
|  |         else: | ||||||
|  |             return obj.name | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class VirtualMachineSerializer(serializers.Serializer): | class VirtualMachineSerializer(serializers.Serializer): | ||||||
|  | @ -133,7 +136,10 @@ class VirtualMachineSerializer(serializers.Serializer): | ||||||
|     def get_configuration(self, obj): |     def get_configuration(self, obj): | ||||||
|         template_id = obj.template.template_id |         template_id = obj.template.template_id | ||||||
|         template = OpenNebulaManager().get_template(template_id) |         template = OpenNebulaManager().get_template(template_id) | ||||||
|         return template.name.lstrip('public-') |         if template.name.startswith('public-'): | ||||||
|  |             return template.name.lstrip('public-') | ||||||
|  |         else: | ||||||
|  |             return template.name | ||||||
| 
 | 
 | ||||||
|     def get_ipv4(self, obj): |     def get_ipv4(self, obj): | ||||||
|         """ |         """ | ||||||
|  | @ -162,7 +168,10 @@ class VirtualMachineSerializer(serializers.Serializer): | ||||||
|             return '-' |             return '-' | ||||||
| 
 | 
 | ||||||
|     def get_name(self, obj): |     def get_name(self, obj): | ||||||
|         return obj.name.lstrip('public-') |         if obj.name.startswith('public-'): | ||||||
|  |             return obj.name.lstrip('public-') | ||||||
|  |         else: | ||||||
|  |             return obj.name | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class VMTemplateSerializer(serializers.Serializer): | class VMTemplateSerializer(serializers.Serializer): | ||||||
|  |  | ||||||
|  | @ -30,6 +30,14 @@ | ||||||
| 	      </span> | 	      </span> | ||||||
| 	    </a> | 	    </a> | ||||||
| 	  </li> | 	  </li> | ||||||
|  | 	  <li> | ||||||
|  | 	    <a href="https://blog.ungleich.ch/en-us/cms/blog/feed/"> | ||||||
|  | 	      <span class="fa-stack fa-lg"> | ||||||
|  | 		<i class="fa fa-circle fa-stack-2x"></i> | ||||||
|  | 		<i class="fa fa-rss fa-stack-1x fa-inverse"></i> | ||||||
|  | 	      </span> | ||||||
|  | 	    </a> | ||||||
|  | 	  </li> | ||||||
| 	</ul> | 	</ul> | ||||||
| 	<p class="copyright"> | 	<p class="copyright"> | ||||||
| 	  Copyright © ungleich GmbH  {% now "Y" %} | 	  Copyright © ungleich GmbH  {% now "Y" %} | ||||||
|  |  | ||||||
|  | @ -7,7 +7,7 @@ | ||||||
|     <meta charset="utf-8"> |     <meta charset="utf-8"> | ||||||
|     <meta http-equiv="X-UA-Compatible" content="IE=edge"> |     <meta http-equiv="X-UA-Compatible" content="IE=edge"> | ||||||
|     <meta name="viewport" content="width=device-width, initial-scale=1"> |     <meta name="viewport" content="width=device-width, initial-scale=1"> | ||||||
|     <meta name="author" content="ungleich GmbH"> |     <meta name="author" content="ungleich glarus ag"> | ||||||
|     <meta name="description" content="{% page_attribute 'meta_description' %}"> |     <meta name="description" content="{% page_attribute 'meta_description' %}"> | ||||||
|     <title>{% page_attribute "page_title" %}</title> |     <title>{% page_attribute "page_title" %}</title> | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -7,8 +7,9 @@ | ||||||
|     <meta charset="utf-8"> |     <meta charset="utf-8"> | ||||||
|     <meta http-equiv="X-UA-Compatible" content="IE=edge"> |     <meta http-equiv="X-UA-Compatible" content="IE=edge"> | ||||||
|     <meta name="viewport" content="width=device-width, initial-scale=1"> |     <meta name="viewport" content="width=device-width, initial-scale=1"> | ||||||
|     <meta name="description" content=""> |     <meta name="author" content="ungleich glarus ag"> | ||||||
|     <meta name="author" content=""> |     <meta name="description" content="{% page_attribute 'meta_description' %}"> | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
|     <title>{% page_attribute "page_title" %}</title> |     <title>{% page_attribute "page_title" %}</title> | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,5 +1,7 @@ | ||||||
| import decimal | import decimal | ||||||
| import logging | import logging | ||||||
|  | import subprocess | ||||||
|  | 
 | ||||||
| from oca.pool import WrongIdError | from oca.pool import WrongIdError | ||||||
| 
 | 
 | ||||||
| from datacenterlight.models import VMPricing | from datacenterlight.models import VMPricing | ||||||
|  | @ -79,7 +81,7 @@ def get_vm_price(cpu, memory, disk_size, hdd_size=0, pricing_name='default'): | ||||||
|              (decimal.Decimal(hdd_size) * pricing.hdd_unit_price)) |              (decimal.Decimal(hdd_size) * pricing.hdd_unit_price)) | ||||||
|     cents = decimal.Decimal('.01') |     cents = decimal.Decimal('.01') | ||||||
|     price = price.quantize(cents, decimal.ROUND_HALF_UP) |     price = price.quantize(cents, decimal.ROUND_HALF_UP) | ||||||
|     return float(price) |     return round(float(price), 2) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def get_vm_price_with_vat(cpu, memory, ssd_size, hdd_size=0, | def get_vm_price_with_vat(cpu, memory, ssd_size, hdd_size=0, | ||||||
|  | @ -125,9 +127,27 @@ def get_vm_price_with_vat(cpu, memory, ssd_size, hdd_size=0, | ||||||
|     vat = vat.quantize(cents, decimal.ROUND_HALF_UP) |     vat = vat.quantize(cents, decimal.ROUND_HALF_UP) | ||||||
|     discount = { |     discount = { | ||||||
|         'name': pricing.discount_name, |         'name': pricing.discount_name, | ||||||
|         'amount': float(pricing.discount_amount), |         'amount': round(float(pricing.discount_amount),2) | ||||||
|     } |     } | ||||||
|     return float(price), float(vat), float(vat_percent), discount |     return (round(float(price), 2), round(float(vat), 2), | ||||||
|  |             round(float(vat_percent)), discount) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def ping_ok(host_ipv6): | ||||||
|  |     """ | ||||||
|  |     A utility method to check if a host responds to ping requests. Note: the | ||||||
|  |     function relies on `ping6` utility of debian to check. | ||||||
|  | 
 | ||||||
|  |     :param host_ipv6 str type parameter that represets the ipv6 of the host to | ||||||
|  |            checked | ||||||
|  |     :return True if the host responds to ping else returns False | ||||||
|  |     """ | ||||||
|  |     try: | ||||||
|  |         subprocess.check_output("ping6 -c 1 " + host_ipv6, shell=True) | ||||||
|  |     except Exception as ex: | ||||||
|  |         logger.debug(host_ipv6 + " not reachable via ping. Error = " + str(ex)) | ||||||
|  |         return False | ||||||
|  |     return True | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class HostingUtils: | class HostingUtils: | ||||||
|  |  | ||||||
|  | @ -291,7 +291,8 @@ class StripeUtils(object): | ||||||
|         return charge |         return charge | ||||||
| 
 | 
 | ||||||
|     @staticmethod |     @staticmethod | ||||||
|     def get_stripe_plan_id(cpu, ram, ssd, version, app='dcl', hdd=None): |     def get_stripe_plan_id(cpu, ram, ssd, version, app='dcl', hdd=None, | ||||||
|  |                            price=None): | ||||||
|         """ |         """ | ||||||
|         Returns the Stripe plan id string of the form |         Returns the Stripe plan id string of the form | ||||||
|         `dcl-v1-cpu-2-ram-5gb-ssd-10gb` based on the input parameters |         `dcl-v1-cpu-2-ram-5gb-ssd-10gb` based on the input parameters | ||||||
|  | @ -303,6 +304,7 @@ class StripeUtils(object): | ||||||
|         :param version: The version of the Stripe plans |         :param version: The version of the Stripe plans | ||||||
|         :param app: The application to which the stripe plan belongs |         :param app: The application to which the stripe plan belongs | ||||||
|         to. By default it is 'dcl' |         to. By default it is 'dcl' | ||||||
|  |         :param price: The price for this plan | ||||||
|         :return: A string of the form `dcl-v1-cpu-2-ram-5gb-ssd-10gb` |         :return: A string of the form `dcl-v1-cpu-2-ram-5gb-ssd-10gb` | ||||||
|         """ |         """ | ||||||
|         dcl_plan_string = 'cpu-{cpu}-ram-{ram}gb-ssd-{ssd}gb'.format(cpu=cpu, |         dcl_plan_string = 'cpu-{cpu}-ram-{ram}gb-ssd-{ssd}gb'.format(cpu=cpu, | ||||||
|  | @ -314,19 +316,30 @@ class StripeUtils(object): | ||||||
|         stripe_plan_id_string = '{app}-v{version}-{plan}'.format( |         stripe_plan_id_string = '{app}-v{version}-{plan}'.format( | ||||||
|             app=app, |             app=app, | ||||||
|             version=version, |             version=version, | ||||||
|             plan=dcl_plan_string) |             plan=dcl_plan_string | ||||||
|         return stripe_plan_id_string |         ) | ||||||
|  |         if price is not None: | ||||||
|  |             stripe_plan_id_string_with_price = '{}-{}chf'.format( | ||||||
|  |                 stripe_plan_id_string, | ||||||
|  |                 round(price, 2) | ||||||
|  |             ) | ||||||
|  |             return stripe_plan_id_string_with_price | ||||||
|  |         else: | ||||||
|  |             return stripe_plan_id_string | ||||||
| 
 | 
 | ||||||
|     @staticmethod |     @staticmethod | ||||||
|     def get_stripe_plan_name(cpu, memory, disk_size): |     def get_stripe_plan_name(cpu, memory, disk_size, price): | ||||||
|         """ |         """ | ||||||
|         Returns the Stripe plan name |         Returns the Stripe plan name | ||||||
|         :return: |         :return: | ||||||
|         """ |         """ | ||||||
|         return "{cpu} Cores, {memory} GB RAM, {disk_size} GB SSD".format( |         return "{cpu} Cores, {memory} GB RAM, {disk_size} GB SSD, " \ | ||||||
|             cpu=cpu, |                "{price} CHF".format( | ||||||
|             memory=memory, |                     cpu=cpu, | ||||||
|             disk_size=disk_size) |                     memory=memory, | ||||||
|  |                     disk_size=disk_size, | ||||||
|  |                     price=round(price, 2) | ||||||
|  |                 ) | ||||||
| 
 | 
 | ||||||
|     @handleStripeError |     @handleStripeError | ||||||
|     def set_subscription_meta_data(self, subscription_id, meta_data): |     def set_subscription_meta_data(self, subscription_id, meta_data): | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue