Merge branch 'task/5509/add-keys-to-opennebula-user' into 'master'
Save user's key in opennebula See merge request ungleich-public/dynamicweb!704
This commit is contained in:
commit
59a78dd8bb
10 changed files with 195 additions and 94 deletions
|
@ -186,3 +186,8 @@ footer .dcl-link-separator::before {
|
|||
background: transparent !important;
|
||||
resize: none;
|
||||
}
|
||||
|
||||
.existing-keys-title {
|
||||
font-weight: bold;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
|
|
@ -8,7 +8,6 @@ from django.core.mail import EmailMessage
|
|||
from django.core.urlresolvers import reverse
|
||||
from django.utils import translation
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from time import sleep
|
||||
|
||||
from dynamicweb.celery import app
|
||||
from hosting.models import HostingOrder
|
||||
|
@ -16,7 +15,7 @@ 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, ping_ok
|
||||
get_all_public_keys, get_or_create_vm_detail
|
||||
)
|
||||
from utils.mailer import BaseEmail
|
||||
from utils.stripe_utils import StripeUtils
|
||||
|
@ -79,10 +78,14 @@ def create_vm_task(self, vm_template_id, user, specs, template, order_id):
|
|||
# Create OpenNebulaManager
|
||||
manager = OpenNebulaManager(email=on_user, password=on_pass)
|
||||
|
||||
custom_user = CustomUser.objects.get(email=user.get('email'))
|
||||
pub_keys = get_all_public_keys(custom_user)
|
||||
if manager.email != settings.OPENNEBULA_USERNAME:
|
||||
manager.save_key_in_opennebula_user('\n'.join(pub_keys))
|
||||
vm_id = manager.create_vm(
|
||||
template_id=vm_template_id,
|
||||
specs=specs,
|
||||
ssh_key=settings.ONEADMIN_USER_SSH_PUBLIC_KEY,
|
||||
ssh_key='\n'.join(pub_keys),
|
||||
vm_name=vm_name
|
||||
)
|
||||
|
||||
|
@ -188,65 +191,9 @@ def create_vm_task(self, vm_template_id, user, specs, template, order_id):
|
|||
email = BaseEmail(**email_data)
|
||||
email.send()
|
||||
|
||||
# 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 vm_ipv6 is not None:
|
||||
custom_user = CustomUser.objects.get(email=user.get('email'))
|
||||
if vm_id > 0:
|
||||
get_or_create_vm_detail(custom_user, manager, vm_id)
|
||||
if custom_user is not None:
|
||||
public_keys = get_all_public_keys(custom_user)
|
||||
keys = [{'value': key, 'state': True} for key in
|
||||
public_keys]
|
||||
if len(keys) > 0:
|
||||
logger.debug(
|
||||
"Calling configure on {host} for "
|
||||
"{num_keys} keys".format(
|
||||
host=vm_ipv6, num_keys=len(keys)
|
||||
)
|
||||
)
|
||||
# Let's wait until the IP responds to ping before we
|
||||
# run the cdist configure on the host
|
||||
did_manage_public_key = False
|
||||
for i in range(0, 15):
|
||||
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()
|
||||
else:
|
||||
logger.debug("VM's ipv6 is None. Hence not created VMDetail")
|
||||
except Exception as e:
|
||||
logger.error(str(e))
|
||||
try:
|
||||
|
|
|
@ -134,6 +134,38 @@
|
|||
</div>
|
||||
<form id="virtual_machine_create_form" action="" method="POST">
|
||||
{% csrf_token %}
|
||||
{% if generic_payment_details %}
|
||||
{% else %}
|
||||
{% comment %}
|
||||
We are in VM buy flow and we want user to click the "Place order" button.
|
||||
At this point, we also want the user to input the SSH key for the VM.
|
||||
{% endcomment %}
|
||||
|
||||
{% if messages %}
|
||||
<div class="alert alert-warning">
|
||||
{% for message in messages %}
|
||||
<span>{{ message }}</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="dashboard-container-head">
|
||||
<h2 class="dashboard-title-thin"><i class="fa fa-key" aria-hidden="true"></i> {% trans "Add your public SSH key" %}</h2>
|
||||
</div>
|
||||
<div class="existing-keys">
|
||||
{% if keys|length > 0 %}
|
||||
<div class="existing-keys-title">Existing keys</div>
|
||||
{% endif %}
|
||||
{% for key in keys %}
|
||||
<textarea class="form-control input-no-border" style="width: 100%" readonly rows="6">
|
||||
{{key}}
|
||||
</textarea>
|
||||
<br/>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% for field in form %}
|
||||
{% bootstrap_field field %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
<div class="row">
|
||||
<div class="col-sm-8">
|
||||
{% if generic_payment_details %}
|
||||
|
|
|
@ -13,7 +13,8 @@ from django.views.decorators.cache import cache_control
|
|||
from django.views.generic import FormView, CreateView, DetailView
|
||||
|
||||
from hosting.forms import (
|
||||
HostingUserLoginForm, GenericPaymentForm, ProductPaymentForm
|
||||
HostingUserLoginForm, GenericPaymentForm, ProductPaymentForm,
|
||||
UserHostingKeyForm
|
||||
)
|
||||
from hosting.models import (
|
||||
HostingBill, HostingOrder, UserCardDetail, GenericProduct
|
||||
|
@ -24,7 +25,7 @@ from utils.forms import (
|
|||
BillingAddressForm, BillingAddressFormSignup, UserBillingAddressForm,
|
||||
BillingAddress
|
||||
)
|
||||
from utils.hosting_utils import get_vm_price_with_vat
|
||||
from utils.hosting_utils import get_vm_price_with_vat, get_all_public_keys
|
||||
from utils.stripe_utils import StripeUtils
|
||||
from utils.tasks import send_plain_email_task
|
||||
from .cms_models import DCLCalculatorPluginModel
|
||||
|
@ -529,12 +530,18 @@ class PaymentOrderView(FormView):
|
|||
return self.render_to_response(context)
|
||||
|
||||
|
||||
class OrderConfirmationView(DetailView):
|
||||
class OrderConfirmationView(DetailView, FormView):
|
||||
form_class = UserHostingKeyForm
|
||||
template_name = "datacenterlight/order_detail.html"
|
||||
payment_template_name = 'datacenterlight/landing_payment.html'
|
||||
context_object_name = "order"
|
||||
model = HostingOrder
|
||||
|
||||
def get_form_kwargs(self):
|
||||
kwargs = super(OrderConfirmationView, self).get_form_kwargs()
|
||||
kwargs.update({'request': self.request})
|
||||
return kwargs
|
||||
|
||||
@cache_control(no_cache=True, must_revalidate=True, no_store=True)
|
||||
def get(self, request, *args, **kwargs):
|
||||
context = {}
|
||||
|
@ -567,6 +574,8 @@ class OrderConfirmationView(DetailView):
|
|||
else:
|
||||
context.update({
|
||||
'vm': request.session.get('specs'),
|
||||
'form': UserHostingKeyForm(request=self.request),
|
||||
'keys': get_all_public_keys(self.request.user)
|
||||
})
|
||||
context.update({
|
||||
'site_url': reverse('datacenterlight:index'),
|
||||
|
@ -579,6 +588,31 @@ class OrderConfirmationView(DetailView):
|
|||
return render(request, self.template_name, context)
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
# Check ssh public key and then proceed
|
||||
form = self.get_form()
|
||||
required = True
|
||||
|
||||
# SSH key validation is required only if the user doesn't have an
|
||||
# existing key and user has input some value in the add ssh key fields
|
||||
if (len(get_all_public_keys(self.request.user)) > 0 and
|
||||
(len(form.data.get('public_key')) == 0 and
|
||||
len(form.data.get('name')) == 0)):
|
||||
required = False
|
||||
form.fields['name'].required = required
|
||||
form.fields['public_key'].required = required
|
||||
if not form.is_valid():
|
||||
response = {
|
||||
'status': False,
|
||||
'msg_title': str(_('SSH key related error occurred')),
|
||||
'msg_body': "<br/>".join([str(v) for k,v in form.errors.items()]),
|
||||
}
|
||||
return JsonResponse(response)
|
||||
|
||||
if required:
|
||||
# We have a valid SSH key from the user, save it in opennebula and
|
||||
# db and proceed further
|
||||
form.save()
|
||||
|
||||
user = request.session.get('user')
|
||||
stripe_api_cus_id = request.session.get('customer')
|
||||
stripe_utils = StripeUtils()
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import datetime
|
||||
import logging
|
||||
import subprocess
|
||||
|
||||
import tempfile
|
||||
|
||||
from django import forms
|
||||
from django.conf import settings
|
||||
from django.contrib.auth import authenticate
|
||||
|
@ -187,7 +187,8 @@ class UserHostingKeyForm(forms.ModelForm):
|
|||
alerts the user of it.
|
||||
:return:
|
||||
"""
|
||||
if 'generate' in self.request.POST:
|
||||
if ('generate' in self.request.POST
|
||||
or not self.fields['public_key'].required):
|
||||
return self.data.get('public_key')
|
||||
KEY_ERROR_MESSAGE = _("Please input a proper SSH key")
|
||||
openssh_pubkey_str = self.data.get('public_key').strip()
|
||||
|
@ -214,6 +215,10 @@ class UserHostingKeyForm(forms.ModelForm):
|
|||
return openssh_pubkey_str
|
||||
|
||||
def clean_name(self):
|
||||
INVALID_NAME_MESSAGE = _("Comma not accepted in the name of the key")
|
||||
if "," in self.data.get('name'):
|
||||
logger.debug(INVALID_NAME_MESSAGE)
|
||||
raise forms.ValidationError(INVALID_NAME_MESSAGE)
|
||||
return self.data.get('name')
|
||||
|
||||
def clean_user(self):
|
||||
|
|
|
@ -109,8 +109,11 @@ $(document).ready(function() {
|
|||
modal_btn = $('#createvm-modal-done-btn');
|
||||
$('#createvm-modal-title').text(data.msg_title);
|
||||
$('#createvm-modal-body').html(data.msg_body);
|
||||
modal_btn.attr('href', data.redirect)
|
||||
.removeClass('hide');
|
||||
if (data.redirect) {
|
||||
modal_btn.attr('href', data.redirect).removeClass('hide');
|
||||
} else {
|
||||
modal_btn.attr('href', "");
|
||||
}
|
||||
if (data.status === true) {
|
||||
fa_icon.attr('class', 'checkmark');
|
||||
} else {
|
||||
|
|
|
@ -198,6 +198,35 @@
|
|||
{% block submit_btn %}
|
||||
<form method="post" id="virtual_machine_create_form">
|
||||
{% csrf_token %}
|
||||
{% comment %}
|
||||
We are in VM buy flow and we want user to click the "Place order" button.
|
||||
At this point, we also want the user to input the SSH key for the VM.
|
||||
{% endcomment %}
|
||||
|
||||
{% if messages %}
|
||||
<div class="alert alert-warning">
|
||||
{% for message in messages %}
|
||||
<span>{{ message }}</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="dashboard-container-head">
|
||||
<h2 class="dashboard-title-thin"><i class="fa fa-key" aria-hidden="true"></i> {% trans "Add your public SSH key" %}</h2>
|
||||
</div>
|
||||
<div class="existing-keys">
|
||||
{% if keys|length > 0 %}
|
||||
<div class="existing-keys-title">Existing keys</div>
|
||||
{% endif %}
|
||||
{% for key in keys %}
|
||||
<textarea class="form-control input-no-border" style="width: 100%" readonly rows="6">
|
||||
{{key}}
|
||||
</textarea>
|
||||
<br/>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% for field in form %}
|
||||
{% bootstrap_field field %}
|
||||
{% endfor %}
|
||||
<div class="row">
|
||||
<div class="col-sm-8">
|
||||
<div class="dcl-place-order-text">{% blocktrans with vm_price=vm.total_price|floatformat:2|intcomma %}By clicking "Place order" this plan will charge your credit card account with {{ vm_price }} CHF/month{% endblocktrans %}.</div>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
from django.conf.urls import url
|
||||
from django.contrib.auth import views as auth_views
|
||||
|
||||
from .views import (
|
||||
DjangoHostingView, RailsHostingView, PaymentVMView, NodeJSHostingView,
|
||||
LoginView, SignupView, SignupValidateView, SignupValidatedView, IndexView,
|
||||
|
@ -12,7 +13,6 @@ from .views import (
|
|||
InvoiceListView, InvoiceDetailView, CheckUserVM
|
||||
)
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
url(r'index/?$', IndexView.as_view(), name='index'),
|
||||
url(r'django/?$', DjangoHostingView.as_view(), name='djangohosting'),
|
||||
|
|
|
@ -49,6 +49,7 @@ from utils.forms import (
|
|||
BillingAddressForm, PasswordResetRequestForm, UserBillingAddressForm,
|
||||
ResendActivationEmailForm
|
||||
)
|
||||
from utils.hosting_utils import get_all_public_keys
|
||||
from utils.hosting_utils import get_vm_price_with_vat, HostingUtils
|
||||
from utils.mailer import BaseEmail
|
||||
from utils.stripe_utils import StripeUtils
|
||||
|
@ -466,7 +467,9 @@ class SSHKeyDeleteView(LoginRequiredMixin, DeleteView):
|
|||
pk = self.kwargs.get('pk')
|
||||
# Get user ssh key
|
||||
public_key = UserHostingKey.objects.get(pk=pk).public_key
|
||||
manager.manage_public_key([{'value': public_key, 'state': False}])
|
||||
keys = UserHostingKey.objects.filter(user=self.request.user)
|
||||
keys_to_save = [k.public_key for k in keys if k.public_key != public_key]
|
||||
manager.save_key_in_opennebula_user('\n'.join(keys_to_save), update_type=0)
|
||||
|
||||
return super(SSHKeyDeleteView, self).delete(request, *args, **kwargs)
|
||||
|
||||
|
@ -515,8 +518,8 @@ class SSHKeyChoiceView(LoginRequiredMixin, View):
|
|||
email=owner.email,
|
||||
password=owner.password
|
||||
)
|
||||
public_key_str = public_key.decode()
|
||||
manager.manage_public_key([{'value': public_key_str, 'state': True}])
|
||||
keys = get_all_public_keys(request.user)
|
||||
manager.save_key_in_opennebula_user('\n'.join(keys))
|
||||
return redirect(reverse_lazy('hosting:ssh_keys'), foo='bar')
|
||||
|
||||
|
||||
|
@ -566,10 +569,8 @@ class SSHKeyCreateView(LoginRequiredMixin, FormView):
|
|||
email=owner.email,
|
||||
password=owner.password
|
||||
)
|
||||
public_key = form.cleaned_data['public_key']
|
||||
if type(public_key) is bytes:
|
||||
public_key = public_key.decode()
|
||||
manager.manage_public_key([{'value': public_key, 'state': True}])
|
||||
keys_to_save = get_all_public_keys(self.request.user)
|
||||
manager.save_key_in_opennebula_user('\n'.join(keys_to_save))
|
||||
return HttpResponseRedirect(self.success_url)
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
|
@ -837,13 +838,19 @@ class PaymentVMView(LoginRequiredMixin, FormView):
|
|||
return self.form_invalid(form)
|
||||
|
||||
|
||||
class OrdersHostingDetailView(LoginRequiredMixin, DetailView):
|
||||
class OrdersHostingDetailView(LoginRequiredMixin, DetailView, FormView):
|
||||
form_class = UserHostingKeyForm
|
||||
template_name = "hosting/order_detail.html"
|
||||
context_object_name = "order"
|
||||
login_url = reverse_lazy('hosting:login')
|
||||
permission_required = ['view_hostingorder']
|
||||
model = HostingOrder
|
||||
|
||||
def get_form_kwargs(self):
|
||||
kwargs = super(OrdersHostingDetailView, self).get_form_kwargs()
|
||||
kwargs.update({'request': self.request})
|
||||
return kwargs
|
||||
|
||||
def get_object(self, queryset=None):
|
||||
order_id = self.kwargs.get('pk')
|
||||
try:
|
||||
|
@ -868,6 +875,8 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView):
|
|||
|
||||
if self.request.GET.get('page') == 'payment':
|
||||
context['page_header_text'] = _('Confirm Order')
|
||||
context['form'] = UserHostingKeyForm(request=self.request)
|
||||
context['keys'] = get_all_public_keys(self.request.user)
|
||||
else:
|
||||
context['page_header_text'] = _('Invoice')
|
||||
if not self.request.user.has_perm(
|
||||
|
@ -993,6 +1002,31 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView):
|
|||
|
||||
@method_decorator(decorators)
|
||||
def post(self, request):
|
||||
# Check ssh public key and then proceed
|
||||
form = self.get_form()
|
||||
required = True
|
||||
|
||||
# SSH key validation is required only if the user doesn't have an
|
||||
# existing key and user has input some value in the add ssh key fields
|
||||
if (len(get_all_public_keys(self.request.user)) > 0 and
|
||||
(len(form.data.get('public_key')) == 0 and
|
||||
len(form.data.get('name')) == 0)):
|
||||
required = False
|
||||
form.fields['name'].required = required
|
||||
form.fields['public_key'].required = required
|
||||
if not form.is_valid():
|
||||
response = {
|
||||
'status': False,
|
||||
'msg_title': str(_('SSH key related error occurred')),
|
||||
'msg_body': "<br/>".join([str(v) for k,v in form.errors.items()]),
|
||||
}
|
||||
return JsonResponse(response)
|
||||
|
||||
if required:
|
||||
# We have a valid SSH key from the user, save it in opennebula and
|
||||
# db and proceed further
|
||||
form.save()
|
||||
|
||||
template = request.session.get('template')
|
||||
specs = request.session.get('specs')
|
||||
stripe_utils = StripeUtils()
|
||||
|
@ -1573,7 +1607,8 @@ class VirtualMachineView(LoginRequiredMixin, View):
|
|||
'virtual_machine': serializer.data,
|
||||
'order': HostingOrder.objects.get(
|
||||
vm_id=serializer.data['vm_id']
|
||||
)
|
||||
),
|
||||
'keys': UserHostingKey.objects.filter(user=request.user)
|
||||
}
|
||||
except Exception as ex:
|
||||
logger.debug("Exception generated {}".format(str(ex)))
|
||||
|
@ -1639,7 +1674,7 @@ class VirtualMachineView(LoginRequiredMixin, View):
|
|||
"manager.delete_vm returned False. Hence, error making "
|
||||
"xml-rpc call to delete vm failed."
|
||||
)
|
||||
response['text'] = ugettext('Error terminating VM') + vm.id
|
||||
response['text'] = str(_('Error terminating VM')) + str(vm.id)
|
||||
else:
|
||||
for t in range(15):
|
||||
try:
|
||||
|
|
|
@ -207,22 +207,8 @@ class OpenNebulaManager():
|
|||
else:
|
||||
vm_pool.info()
|
||||
return vm_pool
|
||||
except AttributeError:
|
||||
logger.error(
|
||||
'Could not connect via client, using oneadmin instead')
|
||||
try:
|
||||
vm_pool = oca.VirtualMachinePool(self.oneadmin_client)
|
||||
if infoextended:
|
||||
vm_pool.infoextended(
|
||||
filter=-1, # User's resources and any of his groups
|
||||
vm_state=-1 # Look for VMs in any state, except DONE
|
||||
)
|
||||
else:
|
||||
vm_pool.info(filter=-2)
|
||||
return vm_pool
|
||||
except:
|
||||
raise ConnectionRefusedError
|
||||
|
||||
except AttributeError as ae:
|
||||
logger.error("AttributeError : %s" % str(ae))
|
||||
except ConnectionRefusedError:
|
||||
logger.error(
|
||||
'Could not connect to host: {host} via protocol {protocol}'.format(
|
||||
|
@ -377,6 +363,31 @@ class OpenNebulaManager():
|
|||
|
||||
return vm_terminated
|
||||
|
||||
def save_key_in_opennebula_user(self, ssh_key, update_type=1):
|
||||
"""
|
||||
Save the given ssh key in OpenNebula user
|
||||
|
||||
# Update type: 0: Replace the whole template.
|
||||
1: Merge new template with the existing one.
|
||||
:param ssh_key: The ssh key to be saved
|
||||
:param update_type: The update type as explained above
|
||||
|
||||
:return:
|
||||
"""
|
||||
return_value = self.oneadmin_client.call(
|
||||
'user.update',
|
||||
self.opennebula_user.id,
|
||||
'<CONTEXT><SSH_PUBLIC_KEY>%s</SSH_PUBLIC_KEY></CONTEXT>' % ssh_key,
|
||||
update_type
|
||||
)
|
||||
if type(return_value) == int:
|
||||
logger.debug(
|
||||
"Saved the key in opennebula successfully : %s" % return_value)
|
||||
else:
|
||||
logger.error(
|
||||
"Could not save the key in opennebula. %s" % return_value)
|
||||
return
|
||||
|
||||
def _get_template_pool(self):
|
||||
try:
|
||||
template_pool = oca.VmTemplatePool(self.oneadmin_client)
|
||||
|
|
Loading…
Reference in a new issue