Merge master into task/3747/multiple_cards_support

This commit is contained in:
PCoder 2018-07-03 21:29:04 +02:00
commit 6d2b011925
14 changed files with 371 additions and 153 deletions

View file

@ -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 1.9: 2018-05-16
* #4559: [cms] enable discount on cms calculator * #4559: [cms] enable discount on cms calculator
1.8: 2018-05-01 1.8: 2018-05-01

View file

@ -2,6 +2,9 @@ from cms.extensions import PageExtension
from cms.extensions.extension_pool import extension_pool from cms.extensions.extension_pool import extension_pool
from cms.models.fields import PlaceholderField from cms.models.fields import PlaceholderField
from cms.models.pluginmodel import CMSPlugin 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.contrib.sites.models import Site
from django.db import models from django.db import models
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
@ -292,6 +295,36 @@ class DCLSectionPromoPluginModel(CMSPlugin):
return extra_classes 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): class DCLCalculatorPluginModel(CMSPlugin):
pricing = models.ForeignKey( pricing = models.ForeignKey(
VMPricing, VMPricing,
@ -303,3 +336,17 @@ class DCLCalculatorPluginModel(CMSPlugin):
max_length=50, choices=VMTemplate.VM_TYPE_CHOICES, max_length=50, choices=VMTemplate.VM_TYPE_CHOICES,
default=VMTemplate.PUBLIC 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."
)

View file

@ -88,6 +88,12 @@ class DCLCalculatorPlugin(CMSPluginBase):
context = super(DCLCalculatorPlugin, self).render( context = super(DCLCalculatorPlugin, self).render(
context, instance, placeholder context, instance, placeholder
) )
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( context['templates'] = VMTemplate.objects.filter(
vm_type=instance.vm_type vm_type=instance.vm_type
) )

View file

@ -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),
),
]

View file

@ -1,8 +1,8 @@
from datetime import datetime from datetime import datetime
from celery import current_task
from celery.exceptions import MaxRetriesExceededError from celery.exceptions import MaxRetriesExceededError
from celery.utils.log import get_task_logger from celery.utils.log import get_task_logger
from celery import current_task
from django.conf import settings from django.conf import settings
from django.core.mail import EmailMessage from django.core.mail import EmailMessage
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
@ -10,16 +10,13 @@ from django.utils import translation
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from dynamicweb.celery import app from dynamicweb.celery import app
from hosting.models import HostingOrder, HostingBill from hosting.models import HostingOrder
from membership.models import StripeCustomer, 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
from utils.forms import UserBillingAddressForm
from utils.mailer import BaseEmail from utils.mailer import BaseEmail
from utils.models import BillingAddress
from utils.stripe_utils import StripeUtils from utils.stripe_utils import StripeUtils
from .models import VMPricing from .models import VMPricing
logger = get_task_logger(__name__) 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) @app.task(bind=True, max_retries=settings.CELERY_MAX_RETRIES)
def create_vm_task(self, vm_template_id, user, specs, template, def create_vm_task(self, vm_template_id, user, specs, template, order_id):
stripe_customer_id, billing_address_data,
stripe_subscription_id, cc_details):
logger.debug( logger.debug(
"Running create_vm_task on {}".format(current_task.request.hostname)) "Running create_vm_task on {}".format(current_task.request.hostname))
vm_id = None vm_id = None
try: try:
final_price = (specs.get('total_price') if 'total_price' in specs final_price = (
else specs.get('price')) specs.get('total_price') if 'total_price' in specs
billing_address = BillingAddress( else specs.get('price')
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()
if 'pass' in user: if 'pass' in user:
on_user = user.get('email') 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: if vm_id is None:
raise Exception("Could not create VM") raise Exception("Could not create VM")
vm_pricing = VMPricing.get_vm_pricing_by_name( # Update HostingOrder with the created vm_id
name=specs['pricing_name'] hosting_order = HostingOrder.objects.filter(id=order_id).first()
) if 'pricing_name' in specs else VMPricing.get_default_pricing() error_msg = None
# Create a Hosting Order
order = HostingOrder.create( try:
price=final_price, hosting_order.vm_id = vm_id
vm_id=vm_id, hosting_order.save()
customer=customer, logger.debug(
billing_address=billing_address, "Updated hosting_order {} with vm_id={}".format(
vm_pricing=vm_pricing hosting_order.id, vm_id
) )
# Create a Hosting Bill
HostingBill.create(
customer=customer, billing_address=billing_address
) )
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 = StripeUtils()
stripe_utils.set_subscription_meta_data( result = stripe_utils.set_subscription_metadata(
stripe_subscription_id, {'ID': vm_id} subscription_id=hosting_order.subscription_id,
metadata={"VM_ID": str(vm_id)}
) )
# If the Stripe payment succeeds, set order status approved if result.get('error') is not None:
order.set_approved() 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 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'), 'template': template.get('name'),
'vm_name': vm.get('name'), 'vm_name': vm.get('name'),
'vm_id': vm['vm_id'], '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: if 'pricing_name' in specs:
context['pricing'] = str(VMPricing.get_vm_pricing_by_name( context['pricing'] = str(VMPricing.get_vm_pricing_by_name(
name=specs['pricing_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'), 'base_url': "{0}://{1}".format(user.get('request_scheme'),
user.get('request_host')), user.get('request_host')),
'order_url': reverse('hosting:orders', 'order_url': reverse('hosting:orders',
kwargs={'pk': order.id}), kwargs={'pk': order_id}),
'page_header': _( 'page_header': _(
'Your New VM %(vm_name)s at Data Center Light') % { 'Your New VM %(vm_name)s at Data Center Light') % {
'vm_name': vm.get('name')}, '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 = BaseEmail(**email_data)
email.send() email.send()
# try to see if we have the IP and that if the ssh keys can # try to see if we have the IPv6 of the new vm and that if the ssh
# be configured # keys can be configured
new_host = manager.get_primary_ipv4(vm_id) vm_ipv6 = manager.get_ipv6(vm_id)
logger.debug("New VM ID is {vm_id}".format(vm_id=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')) custom_user = CustomUser.objects.get(email=user.get('email'))
get_or_create_vm_detail(custom_user, manager, vm_id) get_or_create_vm_detail(custom_user, manager, vm_id)
if custom_user is not None: if custom_user is not None:
@ -208,13 +200,15 @@ def create_vm_task(self, vm_template_id, user, specs, template,
logger.debug( logger.debug(
"Calling configure on {host} for " "Calling configure on {host} for "
"{num_keys} keys".format( "{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 # Let's delay the task by 75 seconds to be sure
# that we run the cdist configure after the host # that we run the cdist configure after the host
# is up # is up
manager.manage_public_key(keys, manager.manage_public_key(
hosts=[new_host], keys, hosts=[vm_ipv6], countdown=75
countdown=75) )
except Exception as e: except Exception as e:
logger.error(str(e)) logger.error(str(e))
try: try:

View file

@ -12,9 +12,11 @@ from unittest import skipIf
from datacenterlight.models import VMTemplate from datacenterlight.models import VMTemplate
from datacenterlight.tasks import create_vm_task from datacenterlight.tasks import create_vm_task
from hosting.models import HostingOrder
from membership.models import StripeCustomer from membership.models import StripeCustomer
from opennebula_api.serializers import VMTemplateSerializer from opennebula_api.serializers import VMTemplateSerializer
from utils.hosting_utils import get_vm_price from utils.hosting_utils import get_vm_price
from utils.models import BillingAddress
from utils.stripe_utils import StripeUtils from utils.stripe_utils import StripeUtils
@ -81,11 +83,14 @@ class CeleryTaskTestCase(TestCase):
stripe_customer = StripeCustomer.get_or_create( stripe_customer = StripeCustomer.get_or_create(
email=self.customer_email, email=self.customer_email,
token=self.token) token=self.token
)
card_details = self.stripe_utils.get_card_details( card_details = self.stripe_utils.get_card_details(
stripe_customer.stripe_id, stripe_customer.stripe_id,
self.token) self.token
card_details_dict = card_details.get('response_object') )
card_details_dict = card_details.get('error')
self.assertEquals(card_details_dict, None)
billing_address_data = {'cardholder_name': self.customer_name, billing_address_data = {'cardholder_name': self.customer_name,
'postal_code': '1231', 'postal_code': '1231',
'country': 'CH', 'country': 'CH',
@ -122,10 +127,24 @@ class CeleryTaskTestCase(TestCase):
msg = subscription_result.get('error') msg = subscription_result.get('error')
raise Exception("Creating subscription failed: {}".format(msg)) 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( async_task = create_vm_task.delay(
vm_template_id, self.user, specs, template_data, vm_template_id, self.user, specs, template_data, order.id
stripe_customer.id, billing_address_data,
stripe_subscription_obj.id, card_details_dict
) )
new_vm_id = 0 new_vm_id = 0
res = None res = None

View file

@ -1,6 +1,12 @@
from django.contrib.sites.models import Site 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 .cms_models import CMSIntegration
from .models import VMPricing, VMTemplate
def get_cms_integration(name): def get_cms_integration(name):
@ -12,3 +18,76 @@ def get_cms_integration(name):
except CMSIntegration.DoesNotExist: except CMSIntegration.DoesNotExist:
cms_integration = CMSIntegration.objects.get(name=name, domain=None) cms_integration = CMSIntegration.objects.get(name=name, domain=None)
return cms_integration 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]

View file

@ -1,4 +1,3 @@
import json
import logging import logging
from django import forms from django import forms
@ -7,13 +6,12 @@ from django.contrib import messages
from django.contrib.auth import login, authenticate from django.contrib.auth import login, authenticate
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.core.urlresolvers import reverse 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.shortcuts import render
from django.utils.translation import get_language, ugettext_lazy as _ from django.utils.translation import get_language, ugettext_lazy as _
from django.views.decorators.cache import cache_control from django.views.decorators.cache import cache_control
from django.views.generic import FormView, CreateView, DetailView from django.views.generic import FormView, CreateView, DetailView
from datacenterlight.tasks import create_vm_task
from hosting.forms import HostingUserLoginForm from hosting.forms import HostingUserLoginForm
from hosting.models import HostingOrder from hosting.models import HostingOrder
from membership.models import CustomUser, StripeCustomer 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 utils.tasks import send_plain_email_task
from .forms import ContactForm from .forms import ContactForm
from .models import VMTemplate, VMPricing from .models import VMTemplate, VMPricing
from .utils import get_cms_integration from .utils import get_cms_integration, create_vm
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -417,8 +415,8 @@ class OrderConfirmationView(DetailView):
' On close of this popup, you will be redirected back to' ' On close of this popup, you will be redirected back to'
' the payment page.')) ' the payment page.'))
} }
return HttpResponse(json.dumps(response), return JsonResponse(response)
content_type="application/json")
card_details_dict = card_details.get('response_object') card_details_dict = card_details.get('response_object')
cpu = specs.get('cpu') cpu = specs.get('cpu')
memory = specs.get('memory') memory = specs.get('memory')
@ -458,8 +456,7 @@ class OrderConfirmationView(DetailView):
' On close of this popup, you will be redirected back to' ' On close of this popup, you will be redirected back to'
' the payment page.')) ' the payment page.'))
} }
return HttpResponse(json.dumps(response), return JsonResponse(response)
content_type="application/json")
# Create user if the user is not logged in and if he is not already # Create user if the user is not logged in and if he is not already
# registered # registered
@ -514,14 +511,11 @@ class OrderConfirmationView(DetailView):
'language': get_language(), 'language': get_language(),
} }
create_vm_task.delay(vm_template_id, user, specs, template, create_vm(
stripe_customer_id, billing_address_data, billing_address_data, stripe_customer_id, specs,
stripe_subscription_obj.id, card_details_dict) stripe_subscription_obj, card_details_dict, request,
for session_var in ['specs', 'template', 'billing_address', vm_template_id, template, user
'billing_address_data', )
'token', 'customer', 'pricing_name']:
if session_var in request.session:
del request.session[session_var]
response = { response = {
'status': True, 'status': True,
@ -537,5 +531,4 @@ class OrderConfirmationView(DetailView):
' it is ready.')) ' it is ready.'))
} }
return HttpResponse(json.dumps(response), return JsonResponse(response)
content_type="application/json")

View file

@ -630,6 +630,7 @@ GOOGLE_ANALYTICS_PROPERTY_IDS = {
'ipv6onlyhosting.ch': 'UA-62285904-10', 'ipv6onlyhosting.ch': 'UA-62285904-10',
'ipv6onlyhosting.net': 'UA-62285904-10', 'ipv6onlyhosting.net': 'UA-62285904-10',
'ipv6onlyhosting.com': 'UA-62285904-10', 'ipv6onlyhosting.com': 'UA-62285904-10',
'comic.ungleich.ch': 'UA-62285904-13',
'127.0.0.1:8000': 'localhost', '127.0.0.1:8000': 'localhost',
'dynamicweb-development.ungleich.ch': 'development', 'dynamicweb-development.ungleich.ch': 'development',
'dynamicweb-staging.ungleich.ch': 'staging' 'dynamicweb-staging.ungleich.ch': 'staging'

View file

@ -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'),
),
]

View file

@ -7,7 +7,7 @@ from django.db import models
from django.utils import timezone from django.utils import timezone
from django.utils.functional import cached_property 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 membership.models import StripeCustomer, CustomUser
from utils.models import BillingAddress from utils.models import BillingAddress
from utils.mixins import AssignPermissionsMixin from utils.mixins import AssignPermissionsMixin
@ -42,6 +42,23 @@ class HostingPlan(models.Model):
return price 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): class HostingOrder(AssignPermissionsMixin, models.Model):
ORDER_APPROVED_STATUS = 'Approved' ORDER_APPROVED_STATUS = 'Approved'
ORDER_DECLINED_STATUS = 'Declined' ORDER_DECLINED_STATUS = 'Declined'
@ -57,6 +74,10 @@ class HostingOrder(AssignPermissionsMixin, models.Model):
price = models.FloatField() price = models.FloatField()
subscription_id = models.CharField(max_length=100, null=True) subscription_id = models.CharField(max_length=100, null=True)
vm_pricing = models.ForeignKey(VMPricing) vm_pricing = models.ForeignKey(VMPricing)
order_detail = models.ForeignKey(
OrderDetail, null=True, blank=True, default=None,
on_delete=models.SET_NULL
)
permissions = ('view_hostingorder',) 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 return self.ORDER_APPROVED_STATUS if self.approved else self.ORDER_DECLINED_STATUS
@classmethod @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): billing_address=None, vm_pricing=None):
instance = cls.objects.create( instance = cls.objects.create(
price=price, price=price,

View file

@ -1,4 +1,3 @@
import json
import logging import logging
import uuid import uuid
from datetime import datetime 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.exceptions import ValidationError
from django.core.files.base import ContentFile from django.core.files.base import ContentFile
from django.core.urlresolvers import reverse_lazy, reverse 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.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.http import urlsafe_base64_decode
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.utils.translation import get_language, ugettext_lazy as _ from django.utils.translation import get_language, ugettext_lazy as _
from django.utils.translation import ugettext from django.utils.translation import ugettext
from django.utils.decorators import method_decorator
from django.views.decorators.cache import never_cache from django.views.decorators.cache import never_cache
from django.views.generic import ( from django.views.generic import (
View, CreateView, FormView, ListView, DetailView, DeleteView, 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 stored_messages.settings import stored_messages_settings
from datacenterlight.models import VMTemplate, VMPricing from datacenterlight.models import VMTemplate, VMPricing
from datacenterlight.tasks import create_vm_task from datacenterlight.utils import create_vm, get_cms_integration
from datacenterlight.utils import get_cms_integration
from hosting.models import UserCardDetail from hosting.models import UserCardDetail
from membership.models import CustomUser, StripeCustomer from membership.models import CustomUser, StripeCustomer
from opennebula_api.models import OpenNebulaManager from opennebula_api.models import OpenNebulaManager
@ -580,6 +579,7 @@ class SettingsView(LoginRequiredMixin, FormView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(SettingsView, self).get_context_data(**kwargs) context = super(SettingsView, self).get_context_data(**kwargs)
# Get user
user = self.request.user user = self.request.user
stripe_customer = None stripe_customer = None
if hasattr(user, 'stripecustomer'): if hasattr(user, 'stripecustomer'):
@ -724,6 +724,7 @@ class PaymentVMView(LoginRequiredMixin, FormView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(PaymentVMView, self).get_context_data(**kwargs) context = super(PaymentVMView, self).get_context_data(**kwargs)
# Get user
user = self.request.user user = self.request.user
if hasattr(user, 'stripecustomer'): if hasattr(user, 'stripecustomer'):
stripe_customer = user.stripecustomer stripe_customer = user.stripecustomer
@ -733,8 +734,11 @@ class PaymentVMView(LoginRequiredMixin, FormView):
stripe_customer=stripe_customer stripe_customer=stripe_customer
) )
context.update({ 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, 'cards_list': cards_list,
'stripe_key': settings.STRIPE_API_PUBLIC_KEY
}) })
return context return context
@ -925,6 +929,10 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView):
context['cc_last4'] = card_detail.last4 context['cc_last4'] = card_detail.last4
context['cc_brand'] = card_detail.brand context['cc_brand'] = card_detail.brand
context['site_url'] = reverse('hosting:create_virtual_machine') 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') context['vm'] = self.request.session.get('specs')
return context return context
@ -1066,9 +1074,8 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView):
' the payment page.') ' the payment page.')
) )
} }
return HttpResponse( return JsonResponse(response)
json.dumps(response), content_type="application/json"
)
if 'token' in request.session: if 'token' in request.session:
ucd = UserCardDetail.get_or_create_user_card_detail( ucd = UserCardDetail.get_or_create_user_card_detail(
stripe_customer=self.request.user.stripecustomer, stripe_customer=self.request.user.stripecustomer,
@ -1086,15 +1093,12 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView):
'request_host': request.get_host(), 'request_host': request.get_host(),
'language': get_language(), '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', create_vm(
'billing_address_data', 'card_id', billing_address_data, stripe_customer_id, specs,
'token', 'customer']: stripe_subscription_obj, card_details_dict, request,
if session_var in request.session: vm_template_id, template, user
del request.session[session_var] )
response = { response = {
'status': True, 'status': True,
@ -1106,8 +1110,7 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView):
' it is ready.')) ' it is ready.'))
} }
return HttpResponse(json.dumps(response), return JsonResponse(response)
content_type="application/json")
class OrdersHostingListView(LoginRequiredMixin, ListView): class OrdersHostingListView(LoginRequiredMixin, ListView):
@ -1318,10 +1321,7 @@ class VirtualMachineView(LoginRequiredMixin, View):
for m in storage: for m in storage:
pass pass
storage.used = True storage.used = True
return HttpResponse( return JsonResponse({'text': ugettext('Terminated')})
json.dumps({'text': ugettext('Terminated')}),
content_type="application/json"
)
else: else:
return redirect(reverse('hosting:virtual_machines')) return redirect(reverse('hosting:virtual_machines'))
elif self.request.is_ajax(): 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()]), ["%s=%s" % (k, v) for (k, v) in admin_email_body.items()]),
} }
send_plain_email_task.delay(email_to_admin_data) send_plain_email_task.delay(email_to_admin_data)
return HttpResponse( return JsonResponse(response)
json.dumps(response),
content_type="application/json"
)
class HostingBillListView(PermissionRequiredMixin, LoginRequiredMixin, class HostingBillListView(PermissionRequiredMixin, LoginRequiredMixin,

View file

@ -53,27 +53,18 @@ class OpenNebulaManager():
ConnectionError: If the connection to the opennebula server can't be ConnectionError: If the connection to the opennebula server can't be
established established
""" """
return oca.Client("{0}:{1}".format( return self._get_opennebula_client(user.email, user.password)
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
))
def _get_opennebula_client(self, username, password): def _get_opennebula_client(self, username, password):
return oca.Client("{0}:{1}".format( return oca.Client(
username, "{0}:{1}".format(username, password),
password),
"{protocol}://{domain}:{port}{endpoint}".format( "{protocol}://{domain}:{port}{endpoint}".format(
protocol=settings.OPENNEBULA_PROTOCOL, protocol=settings.OPENNEBULA_PROTOCOL,
domain=settings.OPENNEBULA_DOMAIN, domain=settings.OPENNEBULA_DOMAIN,
port=settings.OPENNEBULA_PORT, port=settings.OPENNEBULA_PORT,
endpoint=settings.OPENNEBULA_ENDPOINT endpoint=settings.OPENNEBULA_ENDPOINT
)) )
)
def _get_user(self, user): def _get_user(self, user):
"""Get the corresponding opennebula user for a CustomUser object """Get the corresponding opennebula user for a CustomUser object
@ -218,32 +209,31 @@ class OpenNebulaManager():
except: except:
raise ConnectionRefusedError raise ConnectionRefusedError
def get_primary_ipv4(self, vm_id): def get_ipv6(self, vm_id):
""" """
Returns the primary IPv4 of the given vm. Returns the first IPv6 of the given vm.
To be changed later.
: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) ipv6_list = self.get_all_ipv6_addresses(vm_id)
if len(all_ipv4s) > 0: if len(ipv6_list) > 0:
return all_ipv4s[0] return ipv6_list[0]
else: else:
return None 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 :param vm_id: The ID of the vm
:return: :return:
""" """
ipv4s = [] ipv6_list = []
vm = self.get_vm(vm_id) vm = self.get_vm(vm_id)
for nic in vm.template.nics: for nic in vm.template.nics:
if hasattr(nic, 'ip'): if hasattr(nic, 'ip6_global'):
ipv4s.append(nic.ip) ipv6_list.append(nic.ip6_global)
return ipv4s return ipv6_list
def create_vm(self, template_id, specs, ssh_key=None, vm_name=None): def create_vm(self, template_id, specs, ssh_key=None, vm_name=None):
@ -438,8 +428,9 @@ class OpenNebulaManager():
return template_id return template_id
def delete_template(self, template_id): def delete_template(self, template_id):
self.oneadmin_client.call(oca.VmTemplate.METHODS[ self.oneadmin_client.call(
'delete'], template_id, False) oca.VmTemplate.METHODS['delete'], template_id, False
)
def change_user_password(self, passwd_hash): def change_user_password(self, passwd_hash):
self.oneadmin_client.call( self.oneadmin_client.call(
@ -547,7 +538,7 @@ class OpenNebulaManager():
'value': 'sha-.....', # public key as string 'value': 'sha-.....', # public key as string
'state': True # whether key is to be added or 'state': True # whether key is to be added or
} # removed } # 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 :param countdown: Parameter to be passed to celery apply_async
Allows to delay a task by `countdown` number of seconds Allows to delay a task by `countdown` number of seconds
:return: :return:
@ -560,12 +551,14 @@ class OpenNebulaManager():
link_error=save_ssh_key_error_handler.s()) link_error=save_ssh_key_error_handler.s())
else: else:
logger.debug( 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): def get_all_hosts(self):
""" """
A utility function to obtain all hosts of this owner 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( owner = CustomUser.objects.filter(
email=self.email).first() email=self.email).first()
@ -576,10 +569,8 @@ class OpenNebulaManager():
"the ssh keys.".format(self.email)) "the ssh keys.".format(self.email))
for order in all_orders: for order in all_orders:
try: try:
vm = self.get_vm(order.vm_id) ip = self.get_ipv6(order.vm_id)
for nic in vm.template.nics: hosts.append(ip)
if hasattr(nic, 'ip'):
hosts.append(nic.ip)
except WrongIdError: except WrongIdError:
logger.debug( logger.debug(
"VM with ID {} does not exist".format(order.vm_id)) "VM with ID {} does not exist".format(order.vm_id))

View file

@ -260,6 +260,12 @@ class StripeUtils(object):
) )
return subscription_result return subscription_result
@handleStripeError
def set_subscription_metadata(self, subscription_id, metadata):
subscription = stripe.Subscription.retrieve(subscription_id)
subscription.metadata = metadata
subscription.save()
@handleStripeError @handleStripeError
def unsubscribe_customer(self, subscription_id): def unsubscribe_customer(self, subscription_id):
""" """