diff --git a/datacenterlight/tasks.py b/datacenterlight/tasks.py index b897c54a..f036a461 100644 --- a/datacenterlight/tasks.py +++ b/datacenterlight/tasks.py @@ -41,13 +41,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, +def create_vm_task(self, vm_template_id, user, specs, template, + stripe_customer_id, billing_address_data, billing_address_id, charge): vm_id = None try: final_price = specs.get('price') - billing_address = BillingAddress.objects.filter(id=billing_address_id).first() + billing_address = BillingAddress.objects.filter( + id=billing_address_id).first() customer = StripeCustomer.objects.filter(id=stripe_customer_id).first() # Create OpenNebulaManager manager = OpenNebulaManager(email=settings.OPENNEBULA_USERNAME, @@ -114,7 +116,8 @@ def create_vm_task(self, vm_template_id, user, specs, template, stripe_customer_ 'subject': settings.DCL_TEXT + " Order from %s" % context['email'], 'from_email': settings.DCL_SUPPORT_FROM_ADDRESS, 'to': ['info@ungleich.ch'], - 'body': "\n".join(["%s=%s" % (k, v) for (k, v) in context.items()]), + 'body': "\n".join( + ["%s=%s" % (k, v) for (k, v) in context.items()]), 'reply_to': [context['email']], } email = EmailMessage(**email_data) @@ -124,11 +127,13 @@ def create_vm_task(self, vm_template_id, user, specs, template, stripe_customer_ try: retry_task(self) except MaxRetriesExceededError: - msg_text = 'Finished {} retries for create_vm_task'.format(self.request.retries) + msg_text = 'Finished {} retries for create_vm_task'.format( + self.request.retries) logger.error(msg_text) # Try sending email and stop email_data = { - 'subject': '{} CELERY TASK ERROR: {}'.format(settings.DCL_TEXT, msg_text), + 'subject': '{} CELERY TASK ERROR: {}'.format(settings.DCL_TEXT, + msg_text), 'from_email': settings.DCL_SUPPORT_FROM_ADDRESS, 'to': ['info@ungleich.ch'], 'body': ',\n'.join(str(i) for i in self.request.args) diff --git a/datacenterlight/tests.py b/datacenterlight/tests.py index a79ca8be..b0768c9a 100644 --- a/datacenterlight/tests.py +++ b/datacenterlight/tests.py @@ -1,3 +1,120 @@ # from django.test import TestCase +from time import sleep + +import stripe +from celery.result import AsyncResult +from django.conf import settings +from django.core.management import call_command # Create your tests here. +from django.test import TestCase, override_settings +from model_mommy import mommy + +from datacenterlight.models import VMTemplate +from datacenterlight.tasks import create_vm_task +from membership.models import StripeCustomer +from opennebula_api.serializers import VMTemplateSerializer +from utils.models import BillingAddress +from utils.stripe_utils import StripeUtils + + +class CeleryTaskTestCase(TestCase): + @override_settings( + task_eager_propagates=True, + task_always_eager=True, + ) + def setUp(self): + self.customer_password = 'test_password' + self.customer_email = 'celery-createvm-task-test@ungleich.ch' + self.customer_name = "Monty Python" + self.user = { + 'email': self.customer_email, + 'name': self.customer_name + } + self.customer = mommy.make('membership.CustomUser') + self.customer.set_password(self.customer_password) + self.customer.email = self.customer_email + self.customer.save() + self.stripe_utils = StripeUtils() + stripe.api_key = settings.STRIPE_API_PRIVATE_KEY_TEST + self.token = stripe.Token.create( + card={ + "number": '4111111111111111', + "exp_month": 12, + "exp_year": 2022, + "cvc": '123' + }, + ) + # Run fetchvmtemplates so that we have the VM templates from + # OpenNebula + call_command('fetchvmtemplates') + + def test_create_vm_task(self): + """Tests the create vm task.""" + + # We create a VM from the first template available to DCL + vm_template = VMTemplate.objects.all().first() + template_data = VMTemplateSerializer(vm_template).data + + # The specs of VM that we want to create + specs = { + 'cpu': 1, + 'memory': 2, + 'disk_size': 10, + 'price': 15, + } + + stripe_customer = StripeCustomer.get_or_create( + email=self.customer_email, + token=self.token) + billing_address = BillingAddress( + cardholder_name=self.customer_name, + postal_code='1232', + country='CH', + street_address='Monty\'s Street', + city='Hollywood') + billing_address.save() + billing_address_data = {'cardholder_name': self.customer_name, + 'postal_code': '1231', + 'country': 'CH', + 'token': self.token, + 'street_address': 'Monty\'s Street', + 'city': 'Hollywood'} + + billing_address_id = billing_address.id + vm_template_id = template_data.get('id', 1) + final_price = specs.get('price') + + # Make stripe charge to a customer + stripe_utils = StripeUtils() + charge_response = stripe_utils.make_charge( + amount=final_price, + customer=stripe_customer.stripe_id) + + # Check if the payment was approved + if not charge_response.get( + 'response_object'): + msg = charge_response.get('error') + raise Exception("make_charge failed: {}".format(msg)) + + charge = charge_response.get('response_object') + async_task = create_vm_task.delay(vm_template_id, self.user, + specs, + template_data, + stripe_customer.id, + billing_address_data, + billing_address_id, + charge) + new_vm_id = 0 + res = None + for i in range(0, 10): + sleep(5) + res = AsyncResult(async_task.task_id) + if res.result is not None and res.result > 0: + new_vm_id = res.result + break + + # We expect a VM to be created within 50 seconds + self.assertGreater(new_vm_id, 0, + "VM could not be created. res._get_task_meta() = {}" + .format(res._get_task_meta())) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index db7f2e53..399b7676 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -18,7 +18,8 @@ from hosting.models import HostingOrder from utils.stripe_utils import StripeUtils from membership.models import CustomUser, StripeCustomer from opennebula_api.models import OpenNebulaManager -from opennebula_api.serializers import VirtualMachineTemplateSerializer, VMTemplateSerializer +from opennebula_api.serializers import VirtualMachineTemplateSerializer, \ + VMTemplateSerializer from datacenterlight.tasks import create_vm_task @@ -35,9 +36,11 @@ class SuccessView(TemplateView): elif 'token' not in request.session: return HttpResponseRedirect(reverse('datacenterlight:payment')) elif 'order_confirmation' not in request.session: - return HttpResponseRedirect(reverse('datacenterlight:order_confirmation')) + return HttpResponseRedirect( + reverse('datacenterlight:order_confirmation')) else: - for session_var in ['specs', 'user', 'template', 'billing_address', 'billing_address_data', + for session_var in ['specs', 'user', 'template', 'billing_address', + 'billing_address_data', 'token', 'customer']: if session_var in request.session: del request.session[session_var] @@ -53,7 +56,8 @@ class PricingView(TemplateView): templates = manager.get_templates() context = { - 'templates': VirtualMachineTemplateSerializer(templates, many=True).data, + 'templates': VirtualMachineTemplateSerializer(templates, + many=True).data, } except: messages.error(request, @@ -77,7 +81,8 @@ class PricingView(TemplateView): manager = OpenNebulaManager() template = manager.get_template(template_id) - request.session['template'] = VirtualMachineTemplateSerializer(template).data + request.session['template'] = VirtualMachineTemplateSerializer( + template).data if not request.user.is_authenticated(): request.session['next'] = reverse('hosting:payment') @@ -99,7 +104,8 @@ class BetaAccessView(FormView): def form_valid(self, form): context = { - 'base_url': "{0}://{1}".format(self.request.scheme, self.request.get_host()) + 'base_url': "{0}://{1}".format(self.request.scheme, + self.request.get_host()) } email_data = { @@ -129,7 +135,8 @@ class BetaAccessView(FormView): email = BaseEmail(**email_data) email.send() - messages.add_message(self.request, messages.SUCCESS, self.success_message) + messages.add_message(self.request, messages.SUCCESS, + self.success_message) return render(self.request, 'datacenterlight/beta_success.html', {}) @@ -154,7 +161,8 @@ class BetaProgramView(CreateView): # data = VirtualMachineTemplateSerializer(templates, many=True).data context.update({ - 'base_url': "{0}://{1}".format(self.request.scheme, self.request.get_host()), + 'base_url': "{0}://{1}".format(self.request.scheme, + self.request.get_host()), 'vms': vms }) return context @@ -164,7 +172,8 @@ class BetaProgramView(CreateView): vms = BetaAccessVM.create(data) context = { - 'base_url': "{0}://{1}".format(self.request.scheme, self.request.get_host()), + 'base_url': "{0}://{1}".format(self.request.scheme, + self.request.get_host()), 'email': data.get('email'), 'name': data.get('name'), 'vms': vms @@ -181,7 +190,8 @@ class BetaProgramView(CreateView): email = BaseEmail(**email_data) email.send() - messages.add_message(self.request, messages.SUCCESS, self.success_message) + messages.add_message(self.request, messages.SUCCESS, + self.success_message) return HttpResponseRedirect(self.get_success_url()) @@ -225,7 +235,8 @@ class IndexView(CreateView): storage_field = forms.IntegerField(validators=[self.validate_storage]) price = request.POST.get('total') template_id = int(request.POST.get('config')) - template = VMTemplate.objects.filter(opennebula_vm_template_id=template_id).first() + template = VMTemplate.objects.filter( + opennebula_vm_template_id=template_id).first() template_data = VMTemplateSerializer(template).data name = request.POST.get('name') @@ -237,36 +248,46 @@ class IndexView(CreateView): cores = cores_field.clean(cores) except ValidationError as err: msg = '{} : {}.'.format(cores, str(err)) - messages.add_message(self.request, messages.ERROR, msg, extra_tags='cores') - return HttpResponseRedirect(reverse('datacenterlight:index') + "#order_form") + messages.add_message(self.request, messages.ERROR, msg, + extra_tags='cores') + return HttpResponseRedirect( + reverse('datacenterlight:index') + "#order_form") try: memory = memory_field.clean(memory) except ValidationError as err: msg = '{} : {}.'.format(memory, str(err)) - messages.add_message(self.request, messages.ERROR, msg, extra_tags='memory') - return HttpResponseRedirect(reverse('datacenterlight:index') + "#order_form") + messages.add_message(self.request, messages.ERROR, msg, + extra_tags='memory') + return HttpResponseRedirect( + reverse('datacenterlight:index') + "#order_form") try: storage = storage_field.clean(storage) except ValidationError as err: msg = '{} : {}.'.format(storage, str(err)) - messages.add_message(self.request, messages.ERROR, msg, extra_tags='storage') - return HttpResponseRedirect(reverse('datacenterlight:index') + "#order_form") + messages.add_message(self.request, messages.ERROR, msg, + extra_tags='storage') + return HttpResponseRedirect( + reverse('datacenterlight:index') + "#order_form") try: name = name_field.clean(name) except ValidationError as err: msg = '{} {}.'.format(name, _('is not a proper name')) - messages.add_message(self.request, messages.ERROR, msg, extra_tags='name') - return HttpResponseRedirect(reverse('datacenterlight:index') + "#order_form") + messages.add_message(self.request, messages.ERROR, msg, + extra_tags='name') + return HttpResponseRedirect( + reverse('datacenterlight:index') + "#order_form") try: email = email_field.clean(email) except ValidationError as err: msg = '{} {}.'.format(email, _('is not a proper email')) - messages.add_message(self.request, messages.ERROR, msg, extra_tags='email') - return HttpResponseRedirect(reverse('datacenterlight:index') + "#order_form") + messages.add_message(self.request, messages.ERROR, msg, + extra_tags='email') + return HttpResponseRedirect( + reverse('datacenterlight:index') + "#order_form") specs = { 'cpu': cores, @@ -293,14 +314,16 @@ class IndexView(CreateView): def get_context_data(self, **kwargs): context = super(IndexView, self).get_context_data(**kwargs) context.update({ - 'base_url': "{0}://{1}".format(self.request.scheme, self.request.get_host()) + 'base_url': "{0}://{1}".format(self.request.scheme, + self.request.get_host()) }) return context def form_valid(self, form): context = { - 'base_url': "{0}://{1}".format(self.request.scheme, self.request.get_host()) + 'base_url': "{0}://{1}".format(self.request.scheme, + self.request.get_host()) } email_data = { @@ -330,7 +353,8 @@ class IndexView(CreateView): email = BaseEmail(**email_data) email.send() - messages.add_message(self.request, messages.SUCCESS, self.success_message) + messages.add_message(self.request, messages.SUCCESS, + self.success_message) return super(IndexView, self).form_valid(form) @@ -403,7 +427,8 @@ class PaymentOrderView(FormView): request.session['billing_address'] = billing_address.id request.session['token'] = token request.session['customer'] = customer.id - return HttpResponseRedirect(reverse('datacenterlight:order_confirmation')) + return HttpResponseRedirect( + reverse('datacenterlight:order_confirmation')) else: return self.form_invalid(form) @@ -423,11 +448,15 @@ class OrderConfirmationView(DetailView): stripe_customer_id = request.session.get('customer') customer = StripeCustomer.objects.filter(id=stripe_customer_id).first() stripe_utils = StripeUtils() - card_details = stripe_utils.get_card_details(customer.stripe_id, request.session.get('token')) - if not card_details.get('response_object') and not card_details.get('paid'): + card_details = stripe_utils.get_card_details(customer.stripe_id, + request.session.get( + 'token')) + if not card_details.get('response_object'): msg = card_details.get('error') - messages.add_message(self.request, messages.ERROR, msg, extra_tags='failed_payment') - return HttpResponseRedirect(reverse('datacenterlight:payment') + '#payment_error') + messages.add_message(self.request, messages.ERROR, msg, + extra_tags='failed_payment') + return HttpResponseRedirect( + reverse('datacenterlight:payment') + '#payment_error') context = { 'site_url': reverse('datacenterlight:index'), 'cc_last4': card_details.get('response_object').get('last4'), @@ -452,13 +481,16 @@ class OrderConfirmationView(DetailView): customer=customer.stripe_id) # Check if the payment was approved - if not charge_response.get('response_object') and not charge_response.get('paid'): + if not charge_response.get('response_object'): msg = charge_response.get('error') - messages.add_message(self.request, messages.ERROR, msg, extra_tags='make_charge_error') - return HttpResponseRedirect(reverse('datacenterlight:payment') + '#payment_error') + messages.add_message(self.request, messages.ERROR, msg, + extra_tags='make_charge_error') + return HttpResponseRedirect( + reverse('datacenterlight:payment') + '#payment_error') charge = charge_response.get('response_object') - create_vm_task.delay(vm_template_id, user, specs, template, stripe_customer_id, billing_address_data, + create_vm_task.delay(vm_template_id, user, specs, template, + stripe_customer_id, billing_address_data, billing_address_id, charge) request.session['order_confirmation'] = True diff --git a/dynamicweb/settings/base.py b/dynamicweb/settings/base.py index a724b38e..7ec18a8b 100644 --- a/dynamicweb/settings/base.py +++ b/dynamicweb/settings/base.py @@ -493,6 +493,7 @@ REGISTRATION_MESSAGE = {'subject': "Validation mail", } STRIPE_API_PRIVATE_KEY = env('STRIPE_API_PRIVATE_KEY') STRIPE_API_PUBLIC_KEY = env('STRIPE_API_PUBLIC_KEY') +STRIPE_API_PRIVATE_KEY_TEST = env('STRIPE_API_PRIVATE_KEY_TEST') ANONYMOUS_USER_NAME = 'anonymous@ungleich.ch' GUARDIAN_GET_INIT_ANONYMOUS_USER = 'membership.models.get_anonymous_user_instance' diff --git a/hosting/views.py b/hosting/views.py index 9cc78bf9..0747f134 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -570,7 +570,7 @@ class PaymentVMView(LoginRequiredMixin, FormView): customer=customer.stripe_id) # Check if the payment was approved - if not charge_response.get('response_object') and not charge_response.get('paid'): + if not charge_response.get('response_object'): msg = charge_response.get('error') messages.add_message(self.request, messages.ERROR, msg, extra_tags='make_charge_error') return HttpResponseRedirect(reverse('hosting:payment') + '#payment_error')