diff --git a/Changelog b/Changelog index 8146462d..712bce85 100644 --- a/Changelog +++ b/Changelog @@ -1,3 +1,11 @@ +Next: + * #3934: [dcl,hosting] Create HostingOrder outside celery task and add and associate OrderDetail with HostingOrder + * #4890: [hosting] Manage SSH keys using IPv6 of the VM (PR #640) + * bugfix: Fix flake8 error that was ignored in release 1.9.1 +1.9.1: 2018-06-24 + * #4799: [dcl] Show selected vm templates only in calculator (PR #638) + * #4847: [comic] Add google analytics code for comic.ungleich.ch (PR #639) + * feature: add vm_type option to vm_template and dcl calculator to distinguish between public and ipv6only templates (PR #635) 1.9: 2018-05-16 * #4559: [cms] enable discount on cms calculator 1.8: 2018-05-01 diff --git a/datacenterlight/cms_models.py b/datacenterlight/cms_models.py index e1703aaa..62a7b312 100644 --- a/datacenterlight/cms_models.py +++ b/datacenterlight/cms_models.py @@ -2,6 +2,9 @@ from cms.extensions import PageExtension from cms.extensions.extension_pool import extension_pool from cms.models.fields import PlaceholderField from cms.models.pluginmodel import CMSPlugin +from django import forms +from django.conf import settings +from django.contrib.postgres.fields import ArrayField from django.contrib.sites.models import Site from django.db import models from django.utils.safestring import mark_safe @@ -292,6 +295,36 @@ class DCLSectionPromoPluginModel(CMSPlugin): return extra_classes +class MultipleChoiceArrayField(ArrayField): + """ + A field that allows us to store an array of choices. + Uses Django's Postgres ArrayField + and a MultipleChoiceField for its formfield. + """ + VMTemplateChoices = [] + if settings.OPENNEBULA_DOMAIN != 'test_domain': + VMTemplateChoices = list( + ( + str(obj.opennebula_vm_template_id), + (obj.name + ' - ' + VMTemplate.IPV6.title() + if obj.vm_type == VMTemplate.IPV6 else obj.name + ) + ) + for obj in VMTemplate.objects.all() + ) + + def formfield(self, **kwargs): + defaults = { + 'form_class': forms.MultipleChoiceField, + 'choices': self.VMTemplateChoices, + } + defaults.update(kwargs) + # Skip our parent's formfield implementation completely as we don't + # care for it. + # pylint:disable=bad-super-call + return super(ArrayField, self).formfield(**defaults) + + class DCLCalculatorPluginModel(CMSPlugin): pricing = models.ForeignKey( VMPricing, @@ -303,3 +336,17 @@ class DCLCalculatorPluginModel(CMSPlugin): max_length=50, choices=VMTemplate.VM_TYPE_CHOICES, default=VMTemplate.PUBLIC ) + vm_templates_to_show = MultipleChoiceArrayField( + base_field=models.CharField( + blank=True, + max_length=256, + ), + default=list, + blank=True, + help_text="Recommended: If you wish to show all templates of the " + "corresponding VM Type (public/ipv6only), please do not " + "select any of the items in the above field. " + "This will allow any new template(s) added " + "in the backend to be automatically listed in this " + "calculator instance." + ) diff --git a/datacenterlight/cms_plugins.py b/datacenterlight/cms_plugins.py index 769824e0..95a496d8 100644 --- a/datacenterlight/cms_plugins.py +++ b/datacenterlight/cms_plugins.py @@ -88,9 +88,15 @@ class DCLCalculatorPlugin(CMSPluginBase): context = super(DCLCalculatorPlugin, self).render( context, instance, placeholder ) - context['templates'] = VMTemplate.objects.filter( - vm_type=instance.vm_type - ) + ids = instance.vm_templates_to_show + if ids: + context['templates'] = VMTemplate.objects.filter( + vm_type=instance.vm_type + ).filter(opennebula_vm_template_id__in=ids) + else: + context['templates'] = VMTemplate.objects.filter( + vm_type=instance.vm_type + ) return context diff --git a/datacenterlight/migrations/0024_dclcalculatorpluginmodel_vm_templates_to_show.py b/datacenterlight/migrations/0024_dclcalculatorpluginmodel_vm_templates_to_show.py new file mode 100644 index 00000000..65bfce21 --- /dev/null +++ b/datacenterlight/migrations/0024_dclcalculatorpluginmodel_vm_templates_to_show.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2018-06-24 08:23 +from __future__ import unicode_literals + +import datacenterlight.cms_models +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('datacenterlight', '0023_auto_20180524_0349'), + ] + + operations = [ + migrations.AddField( + model_name='dclcalculatorpluginmodel', + name='vm_templates_to_show', + field=datacenterlight.cms_models.MultipleChoiceArrayField(base_field=models.CharField(blank=True, max_length=256), blank=True, default=list, help_text='Recommended: If you wish to show all templates of the corresponding VM Type (public/ipv6only), please do not select any of the items in the above field. This will allow any new template(s) added in the backend to be automatically listed in this calculator instance.', size=None), + ), + ] diff --git a/datacenterlight/tasks.py b/datacenterlight/tasks.py index 3130c21a..281d5f45 100644 --- a/datacenterlight/tasks.py +++ b/datacenterlight/tasks.py @@ -1,8 +1,8 @@ from datetime import datetime +from celery import current_task 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.core.urlresolvers import reverse @@ -10,16 +10,13 @@ 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 hosting.models import HostingOrder +from membership.models import CustomUser from opennebula_api.models import OpenNebulaManager from opennebula_api.serializers import VirtualMachineSerializer 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 from utils.stripe_utils import StripeUtils - from .models import VMPricing logger = get_task_logger(__name__) @@ -52,24 +49,15 @@ def retry_task(task, exception=None): @app.task(bind=True, max_retries=settings.CELERY_MAX_RETRIES) -def create_vm_task(self, vm_template_id, user, specs, template, - stripe_customer_id, billing_address_data, - stripe_subscription_id, cc_details): +def create_vm_task(self, vm_template_id, user, specs, template, order_id): logger.debug( "Running create_vm_task on {}".format(current_task.request.hostname)) vm_id = None try: - final_price = (specs.get('total_price') if 'total_price' in specs - else specs.get('price')) - billing_address = BillingAddress( - cardholder_name=billing_address_data['cardholder_name'], - street_address=billing_address_data['street_address'], - city=billing_address_data['city'], - postal_code=billing_address_data['postal_code'], - country=billing_address_data['country'] + final_price = ( + specs.get('total_price') if 'total_price' in specs + else specs.get('price') ) - billing_address.save() - customer = StripeCustomer.objects.filter(id=stripe_customer_id).first() if 'pass' in user: on_user = user.get('email') @@ -98,42 +86,43 @@ def create_vm_task(self, vm_template_id, user, specs, template, if vm_id is None: raise Exception("Could not create VM") - vm_pricing = VMPricing.get_vm_pricing_by_name( - name=specs['pricing_name'] - ) if 'pricing_name' in specs else VMPricing.get_default_pricing() - # Create a Hosting Order - order = HostingOrder.create( - price=final_price, - vm_id=vm_id, - customer=customer, - billing_address=billing_address, - vm_pricing=vm_pricing - ) + # Update HostingOrder with the created vm_id + hosting_order = HostingOrder.objects.filter(id=order_id).first() + error_msg = None - # Create a Hosting Bill - HostingBill.create( - customer=customer, billing_address=billing_address - ) + try: + hosting_order.vm_id = vm_id + hosting_order.save() + logger.debug( + "Updated hosting_order {} with vm_id={}".format( + hosting_order.id, vm_id + ) + ) + except Exception as ex: + error_msg = ( + "HostingOrder with id {order_id} not found. This means that " + "the hosting order was not created and/or it is/was not " + "associated with VM with id {vm_id}. Details {details}".format( + order_id=order_id, vm_id=vm_id, details=str(ex) + ) + ) + logger.error(error_msg) - # 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 subscription - order.set_subscription_id(stripe_subscription_id, cc_details) stripe_utils = StripeUtils() - stripe_utils.set_subscription_meta_data( - stripe_subscription_id, {'ID': vm_id} + result = stripe_utils.set_subscription_metadata( + subscription_id=hosting_order.subscription_id, + metadata={"VM_ID": str(vm_id)} ) - # If the Stripe payment succeeds, set order status approved - order.set_approved() + if result.get('error') is not None: + emsg = "Could not update subscription metadata for {sub}".format( + sub=hosting_order.subscription_id + ) + logger.error(emsg) + if error_msg: + error_msg += ". " + emsg + else: + error_msg = emsg vm = VirtualMachineSerializer(manager.get_vm(vm_id)).data @@ -147,8 +136,11 @@ def create_vm_task(self, vm_template_id, user, specs, template, 'template': template.get('name'), 'vm_name': vm.get('name'), 'vm_id': vm['vm_id'], - 'order_id': order.id + 'order_id': order_id } + + if error_msg: + context['errors'] = error_msg if 'pricing_name' in specs: context['pricing'] = str(VMPricing.get_vm_pricing_by_name( name=specs['pricing_name'] @@ -176,7 +168,7 @@ def create_vm_task(self, vm_template_id, user, specs, template, 'base_url': "{0}://{1}".format(user.get('request_scheme'), user.get('request_host')), 'order_url': reverse('hosting:orders', - kwargs={'pk': order.id}), + kwargs={'pk': order_id}), 'page_header': _( 'Your New VM %(vm_name)s at Data Center Light') % { 'vm_name': vm.get('name')}, @@ -193,11 +185,11 @@ def create_vm_task(self, vm_template_id, user, specs, template, 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) + # try to see if we have the IPv6 of the new vm and that if the ssh + # keys can be configured + vm_ipv6 = manager.get_ipv6(vm_id) logger.debug("New VM ID is {vm_id}".format(vm_id=vm_id)) - if new_host is not None: + if vm_ipv6 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: @@ -208,13 +200,15 @@ def create_vm_task(self, vm_template_id, user, specs, template, logger.debug( "Calling configure on {host} for " "{num_keys} keys".format( - host=new_host, num_keys=len(keys))) + host=vm_ipv6, 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) + manager.manage_public_key( + keys, hosts=[vm_ipv6], countdown=75 + ) except Exception as e: logger.error(str(e)) try: diff --git a/datacenterlight/tests.py b/datacenterlight/tests.py index d1ce9785..d6cd6adf 100644 --- a/datacenterlight/tests.py +++ b/datacenterlight/tests.py @@ -12,9 +12,11 @@ from unittest import skipIf from datacenterlight.models import VMTemplate from datacenterlight.tasks import create_vm_task +from hosting.models import HostingOrder from membership.models import StripeCustomer from opennebula_api.serializers import VMTemplateSerializer from utils.hosting_utils import get_vm_price +from utils.models import BillingAddress from utils.stripe_utils import StripeUtils @@ -81,11 +83,14 @@ class CeleryTaskTestCase(TestCase): stripe_customer = StripeCustomer.get_or_create( email=self.customer_email, - token=self.token) + token=self.token + ) card_details = self.stripe_utils.get_card_details( stripe_customer.stripe_id, - self.token) - card_details_dict = card_details.get('response_object') + self.token + ) + card_details_dict = card_details.get('error') + self.assertEquals(card_details_dict, None) billing_address_data = {'cardholder_name': self.customer_name, 'postal_code': '1231', 'country': 'CH', @@ -122,10 +127,24 @@ class CeleryTaskTestCase(TestCase): msg = subscription_result.get('error') raise Exception("Creating subscription failed: {}".format(msg)) + billing_address = BillingAddress( + cardholder_name=billing_address_data['cardholder_name'], + street_address=billing_address_data['street_address'], + city=billing_address_data['city'], + postal_code=billing_address_data['postal_code'], + country=billing_address_data['country'] + ) + billing_address.save() + + order = HostingOrder.create( + price=specs['price'], + vm_id=0, + customer=stripe_customer, + billing_address=billing_address + ) + async_task = create_vm_task.delay( - vm_template_id, self.user, specs, template_data, - stripe_customer.id, billing_address_data, - stripe_subscription_obj.id, card_details_dict + vm_template_id, self.user, specs, template_data, order.id ) new_vm_id = 0 res = None diff --git a/datacenterlight/utils.py b/datacenterlight/utils.py index 2efade8e..1c54148e 100644 --- a/datacenterlight/utils.py +++ b/datacenterlight/utils.py @@ -1,6 +1,12 @@ from django.contrib.sites.models import Site +from datacenterlight.tasks import create_vm_task +from hosting.models import HostingOrder, HostingBill, OrderDetail +from membership.models import StripeCustomer +from utils.forms import UserBillingAddressForm +from utils.models import BillingAddress from .cms_models import CMSIntegration +from .models import VMPricing, VMTemplate def get_cms_integration(name): @@ -12,3 +18,76 @@ def get_cms_integration(name): except CMSIntegration.DoesNotExist: cms_integration = CMSIntegration.objects.get(name=name, domain=None) return cms_integration + + +def create_vm(billing_address_data, stripe_customer_id, specs, + stripe_subscription_obj, card_details_dict, request, + vm_template_id, template, user): + billing_address = BillingAddress( + cardholder_name=billing_address_data['cardholder_name'], + street_address=billing_address_data['street_address'], + city=billing_address_data['city'], + postal_code=billing_address_data['postal_code'], + country=billing_address_data['country'] + ) + billing_address.save() + customer = StripeCustomer.objects.filter(id=stripe_customer_id).first() + vm_pricing = ( + VMPricing.get_vm_pricing_by_name(name=specs['pricing_name']) + if 'pricing_name' in specs else + VMPricing.get_default_pricing() + ) + + final_price = ( + specs.get('total_price') + if 'total_price' in specs + else specs.get('price') + ) + + # Create a Hosting Order with vm_id = 0, we shall set it later in + # celery task once the VM instance is up and running + order = HostingOrder.create( + price=final_price, + customer=customer, + billing_address=billing_address, + vm_pricing=vm_pricing + ) + + order_detail_obj, obj_created = OrderDetail.objects.get_or_create( + vm_template=VMTemplate.objects.get( + opennebula_vm_template_id=vm_template_id + ), + cores=specs['cpu'], memory=specs['memory'], ssd_size=specs['disk_size'] + ) + order.order_detail = order_detail_obj + order.save() + + # 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 the given stripe subscription with the order + order.set_subscription_id( + stripe_subscription_obj.id, card_details_dict + ) + + # Set order status approved + order.set_approved() + + create_vm_task.delay(vm_template_id, user, specs, template, order.id) + + for session_var in ['specs', 'template', 'billing_address', + 'billing_address_data', 'card_id', + 'token', 'customer']: + if session_var in request.session: + del request.session[session_var] diff --git a/datacenterlight/views.py b/datacenterlight/views.py index ec10a341..db36d23a 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -1,4 +1,3 @@ -import json import logging from django import forms @@ -7,13 +6,12 @@ from django.contrib import messages from django.contrib.auth import login, authenticate from django.core.exceptions import ValidationError from django.core.urlresolvers import reverse -from django.http import HttpResponseRedirect, HttpResponse +from django.http import HttpResponseRedirect, JsonResponse from django.shortcuts import render from django.utils.translation import get_language, ugettext_lazy as _ from django.views.decorators.cache import cache_control from django.views.generic import FormView, CreateView, DetailView -from datacenterlight.tasks import create_vm_task from hosting.forms import HostingUserLoginForm from hosting.models import HostingOrder from membership.models import CustomUser, StripeCustomer @@ -24,7 +22,7 @@ from utils.stripe_utils import StripeUtils from utils.tasks import send_plain_email_task from .forms import ContactForm from .models import VMTemplate, VMPricing -from .utils import get_cms_integration +from .utils import get_cms_integration, create_vm logger = logging.getLogger(__name__) @@ -417,8 +415,8 @@ class OrderConfirmationView(DetailView): ' On close of this popup, you will be redirected back to' ' the payment page.')) } - return HttpResponse(json.dumps(response), - content_type="application/json") + return JsonResponse(response) + card_details_dict = card_details.get('response_object') cpu = specs.get('cpu') memory = specs.get('memory') @@ -458,8 +456,7 @@ class OrderConfirmationView(DetailView): ' On close of this popup, you will be redirected back to' ' the payment page.')) } - return HttpResponse(json.dumps(response), - content_type="application/json") + return JsonResponse(response) # Create user if the user is not logged in and if he is not already # registered @@ -514,14 +511,11 @@ class OrderConfirmationView(DetailView): 'language': get_language(), } - create_vm_task.delay(vm_template_id, user, specs, template, - stripe_customer_id, billing_address_data, - stripe_subscription_obj.id, card_details_dict) - for session_var in ['specs', 'template', 'billing_address', - 'billing_address_data', - 'token', 'customer', 'pricing_name']: - if session_var in request.session: - del request.session[session_var] + create_vm( + billing_address_data, stripe_customer_id, specs, + stripe_subscription_obj, card_details_dict, request, + vm_template_id, template, user + ) response = { 'status': True, @@ -537,5 +531,4 @@ class OrderConfirmationView(DetailView): ' it is ready.')) } - return HttpResponse(json.dumps(response), - content_type="application/json") + return JsonResponse(response) diff --git a/dynamicweb/settings/base.py b/dynamicweb/settings/base.py index f540e998..75dfaa73 100644 --- a/dynamicweb/settings/base.py +++ b/dynamicweb/settings/base.py @@ -630,6 +630,7 @@ GOOGLE_ANALYTICS_PROPERTY_IDS = { 'ipv6onlyhosting.ch': 'UA-62285904-10', 'ipv6onlyhosting.net': 'UA-62285904-10', 'ipv6onlyhosting.com': 'UA-62285904-10', + 'comic.ungleich.ch': 'UA-62285904-13', '127.0.0.1:8000': 'localhost', 'dynamicweb-development.ungleich.ch': 'development', 'dynamicweb-staging.ungleich.ch': 'staging' diff --git a/hosting/migrations/0045_auto_20180701_2028.py b/hosting/migrations/0045_auto_20180701_2028.py new file mode 100644 index 00000000..39b58aa8 --- /dev/null +++ b/hosting/migrations/0045_auto_20180701_2028.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2018-07-01 20:28 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion +import utils.mixins + + +class Migration(migrations.Migration): + + dependencies = [ + ('datacenterlight', '0024_dclcalculatorpluginmodel_vm_templates_to_show'), + ('hosting', '0044_hostingorder_vm_pricing'), + ] + + operations = [ + migrations.CreateModel( + name='OrderDetail', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('cores', models.IntegerField(default=0)), + ('memory', models.IntegerField(default=0)), + ('hdd_size', models.IntegerField(default=0)), + ('ssd_size', models.IntegerField(default=0)), + ('vm_template', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='datacenterlight.VMTemplate')), + ], + bases=(utils.mixins.AssignPermissionsMixin, models.Model), + ), + migrations.AddField( + model_name='hostingorder', + name='order_detail', + field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='hosting.OrderDetail'), + ), + ] diff --git a/hosting/models.py b/hosting/models.py index 6ab56e6a..e1577bd8 100644 --- a/hosting/models.py +++ b/hosting/models.py @@ -7,7 +7,7 @@ from django.db import models from django.utils import timezone from django.utils.functional import cached_property -from datacenterlight.models import VMPricing +from datacenterlight.models import VMPricing, VMTemplate from membership.models import StripeCustomer, CustomUser from utils.models import BillingAddress from utils.mixins import AssignPermissionsMixin @@ -42,6 +42,23 @@ class HostingPlan(models.Model): return price +class OrderDetail(AssignPermissionsMixin, models.Model): + vm_template = models.ForeignKey( + VMTemplate, blank=True, null=True, default=None, + on_delete=models.SET_NULL + ) + cores = models.IntegerField(default=0) + memory = models.IntegerField(default=0) + hdd_size = models.IntegerField(default=0) + ssd_size = models.IntegerField(default=0) + + def __str__(self): + return "%s - %s, %s cores, %s GB RAM, %s GB SSD" % ( + self.vm_template.name, self.vm_template.vm_type, self.cores, + self.memory, self.ssd_size + ) + + class HostingOrder(AssignPermissionsMixin, models.Model): ORDER_APPROVED_STATUS = 'Approved' ORDER_DECLINED_STATUS = 'Declined' @@ -57,6 +74,10 @@ class HostingOrder(AssignPermissionsMixin, models.Model): price = models.FloatField() subscription_id = models.CharField(max_length=100, null=True) vm_pricing = models.ForeignKey(VMPricing) + order_detail = models.ForeignKey( + OrderDetail, null=True, blank=True, default=None, + on_delete=models.SET_NULL + ) permissions = ('view_hostingorder',) @@ -73,7 +94,7 @@ class HostingOrder(AssignPermissionsMixin, models.Model): return self.ORDER_APPROVED_STATUS if self.approved else self.ORDER_DECLINED_STATUS @classmethod - def create(cls, price=None, vm_id=None, customer=None, + def create(cls, price=None, vm_id=0, customer=None, billing_address=None, vm_pricing=None): instance = cls.objects.create( price=price, diff --git a/hosting/views.py b/hosting/views.py index dea6447d..778b3835 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -1,4 +1,3 @@ -import json import logging import uuid from datetime import datetime @@ -12,14 +11,15 @@ from django.contrib.auth.tokens import default_token_generator from django.core.exceptions import ValidationError from django.core.files.base import ContentFile from django.core.urlresolvers import reverse_lazy, reverse -from django.http import Http404, HttpResponseRedirect, HttpResponse +from django.http import ( + Http404, HttpResponseRedirect, HttpResponse, JsonResponse +) from django.shortcuts import redirect, render -from django.utils.decorators import method_decorator -from django.utils.html import escape from django.utils.http import urlsafe_base64_decode from django.utils.safestring import mark_safe from django.utils.translation import get_language, ugettext_lazy as _ from django.utils.translation import ugettext +from django.utils.decorators import method_decorator from django.views.decorators.cache import never_cache from django.views.generic import ( View, CreateView, FormView, ListView, DetailView, DeleteView, @@ -32,8 +32,7 @@ from stored_messages.models import Message from stored_messages.settings import stored_messages_settings from datacenterlight.models import VMTemplate, VMPricing -from datacenterlight.tasks import create_vm_task -from datacenterlight.utils import get_cms_integration +from datacenterlight.utils import create_vm, get_cms_integration from hosting.models import UserCardDetail from membership.models import CustomUser, StripeCustomer from opennebula_api.models import OpenNebulaManager @@ -580,6 +579,7 @@ class SettingsView(LoginRequiredMixin, FormView): def get_context_data(self, **kwargs): context = super(SettingsView, self).get_context_data(**kwargs) + # Get user user = self.request.user stripe_customer = None if hasattr(user, 'stripecustomer'): @@ -724,6 +724,7 @@ class PaymentVMView(LoginRequiredMixin, FormView): def get_context_data(self, **kwargs): context = super(PaymentVMView, self).get_context_data(**kwargs) + # Get user user = self.request.user if hasattr(user, 'stripecustomer'): stripe_customer = user.stripecustomer @@ -733,8 +734,11 @@ class PaymentVMView(LoginRequiredMixin, FormView): stripe_customer=stripe_customer ) context.update({ + 'stripe_key': settings.STRIPE_API_PUBLIC_KEY, + 'vm_pricing': VMPricing.get_vm_pricing_by_name( + self.request.session.get('specs', {}).get('pricing_name') + ), 'cards_list': cards_list, - 'stripe_key': settings.STRIPE_API_PUBLIC_KEY }) return context @@ -925,6 +929,10 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView): context['cc_last4'] = card_detail.last4 context['cc_brand'] = card_detail.brand 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') context['vm'] = self.request.session.get('specs') return context @@ -1066,9 +1074,8 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView): ' the payment page.') ) } - return HttpResponse( - json.dumps(response), content_type="application/json" - ) + return JsonResponse(response) + if 'token' in request.session: ucd = UserCardDetail.get_or_create_user_card_detail( stripe_customer=self.request.user.stripecustomer, @@ -1086,15 +1093,12 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView): 'request_host': request.get_host(), 'language': get_language(), } - create_vm_task.delay(vm_template_id, user, specs, template, - stripe_customer_id, billing_address_data, - stripe_subscription_obj.id, card_details_dict) - for session_var in ['specs', 'template', 'billing_address', - 'billing_address_data', 'card_id', - 'token', 'customer']: - if session_var in request.session: - del request.session[session_var] + create_vm( + billing_address_data, stripe_customer_id, specs, + stripe_subscription_obj, card_details_dict, request, + vm_template_id, template, user + ) response = { 'status': True, @@ -1106,8 +1110,7 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView): ' it is ready.')) } - return HttpResponse(json.dumps(response), - content_type="application/json") + return JsonResponse(response) class OrdersHostingListView(LoginRequiredMixin, ListView): @@ -1318,10 +1321,7 @@ class VirtualMachineView(LoginRequiredMixin, View): for m in storage: pass storage.used = True - return HttpResponse( - json.dumps({'text': ugettext('Terminated')}), - content_type="application/json" - ) + return JsonResponse({'text': ugettext('Terminated')}) else: return redirect(reverse('hosting:virtual_machines')) elif self.request.is_ajax(): @@ -1453,10 +1453,7 @@ class VirtualMachineView(LoginRequiredMixin, View): ["%s=%s" % (k, v) for (k, v) in admin_email_body.items()]), } send_plain_email_task.delay(email_to_admin_data) - return HttpResponse( - json.dumps(response), - content_type="application/json" - ) + return JsonResponse(response) class HostingBillListView(PermissionRequiredMixin, LoginRequiredMixin, diff --git a/opennebula_api/models.py b/opennebula_api/models.py index 35f3d8e8..29632009 100644 --- a/opennebula_api/models.py +++ b/opennebula_api/models.py @@ -53,27 +53,18 @@ class OpenNebulaManager(): ConnectionError: If the connection to the opennebula server can't be established """ - return oca.Client("{0}:{1}".format( - user.email, - user.password), - "{protocol}://{domain}:{port}{endpoint}".format( - protocol=settings.OPENNEBULA_PROTOCOL, - domain=settings.OPENNEBULA_DOMAIN, - port=settings.OPENNEBULA_PORT, - endpoint=settings.OPENNEBULA_ENDPOINT - )) + return self._get_opennebula_client(user.email, user.password) def _get_opennebula_client(self, username, password): - return oca.Client("{0}:{1}".format( - username, - - password), + return oca.Client( + "{0}:{1}".format(username, password), "{protocol}://{domain}:{port}{endpoint}".format( protocol=settings.OPENNEBULA_PROTOCOL, domain=settings.OPENNEBULA_DOMAIN, port=settings.OPENNEBULA_PORT, endpoint=settings.OPENNEBULA_ENDPOINT - )) + ) + ) def _get_user(self, user): """Get the corresponding opennebula user for a CustomUser object @@ -218,32 +209,31 @@ class OpenNebulaManager(): except: raise ConnectionRefusedError - def get_primary_ipv4(self, vm_id): + def get_ipv6(self, vm_id): """ - Returns the primary IPv4 of the given vm. - To be changed later. + Returns the first IPv6 of the given vm. - :return: An IP address string, if it exists else returns None + :return: An IPv6 address string, if it exists else returns None """ - all_ipv4s = self.get_vm_ipv4_addresses(vm_id) - if len(all_ipv4s) > 0: - return all_ipv4s[0] + ipv6_list = self.get_all_ipv6_addresses(vm_id) + if len(ipv6_list) > 0: + return ipv6_list[0] else: return None - def get_vm_ipv4_addresses(self, vm_id): + def get_all_ipv6_addresses(self, vm_id): """ - Returns a list of IPv4 addresses of the given vm + Returns a list of IPv6 addresses of the given vm :param vm_id: The ID of the vm :return: """ - ipv4s = [] + ipv6_list = [] vm = self.get_vm(vm_id) for nic in vm.template.nics: - if hasattr(nic, 'ip'): - ipv4s.append(nic.ip) - return ipv4s + if hasattr(nic, 'ip6_global'): + ipv6_list.append(nic.ip6_global) + return ipv6_list def create_vm(self, template_id, specs, ssh_key=None, vm_name=None): @@ -438,8 +428,9 @@ class OpenNebulaManager(): return template_id def delete_template(self, template_id): - self.oneadmin_client.call(oca.VmTemplate.METHODS[ - 'delete'], template_id, False) + self.oneadmin_client.call( + oca.VmTemplate.METHODS['delete'], template_id, False + ) def change_user_password(self, passwd_hash): self.oneadmin_client.call( @@ -547,7 +538,7 @@ class OpenNebulaManager(): 'value': 'sha-.....', # public key as string 'state': True # whether key is to be added or } # removed - :param hosts: A list of hosts IP addresses + :param hosts: A list of hosts IPv6 addresses :param countdown: Parameter to be passed to celery apply_async Allows to delay a task by `countdown` number of seconds :return: @@ -560,12 +551,14 @@ class OpenNebulaManager(): link_error=save_ssh_key_error_handler.s()) else: logger.debug( - "Keys and/or hosts are empty, so not managing any keys") + "Keys and/or hosts are empty, so not managing any keys" + ) def get_all_hosts(self): """ A utility function to obtain all hosts of this owner - :return: A list of hosts IP addresses, empty if none exist + :return: A list of IPv6 addresses of all the hosts of this customer or + an empty list if none exist """ owner = CustomUser.objects.filter( email=self.email).first() @@ -576,10 +569,8 @@ class OpenNebulaManager(): "the ssh keys.".format(self.email)) for order in all_orders: try: - vm = self.get_vm(order.vm_id) - for nic in vm.template.nics: - if hasattr(nic, 'ip'): - hosts.append(nic.ip) + ip = self.get_ipv6(order.vm_id) + hosts.append(ip) except WrongIdError: logger.debug( "VM with ID {} does not exist".format(order.vm_id)) diff --git a/utils/stripe_utils.py b/utils/stripe_utils.py index 566979f8..e4b9b02e 100644 --- a/utils/stripe_utils.py +++ b/utils/stripe_utils.py @@ -260,6 +260,12 @@ class StripeUtils(object): ) return subscription_result + @handleStripeError + def set_subscription_metadata(self, subscription_id, metadata): + subscription = stripe.Subscription.retrieve(subscription_id) + subscription.metadata = metadata + subscription.save() + @handleStripeError def unsubscribe_customer(self, subscription_id): """