Force user to generate ssh key in order to create a VM #3147. As user I want to terminate a VM using web interface #3148. Change password in opennebula when user change his password on hosting app #3149

This commit is contained in:
Levi 2017-05-12 00:56:35 -05:00
parent 1a6e1a44d8
commit 3873540849
7 changed files with 214 additions and 43 deletions

View file

@ -101,7 +101,9 @@ class UserHostingKeyForm(forms.ModelForm):
# print(self.fields) # print(self.fields)
def clean_name(self): def clean_name(self):
return ''.join(random.choice(string.ascii_lowercase) for i in range(7)) return "dcl-priv-key-%s" % (
''.join(random.choice(string.ascii_lowercase) for i in range(7))
)
def clean_user(self): def clean_user(self):
return self.request.user return self.request.user
@ -109,8 +111,6 @@ class UserHostingKeyForm(forms.ModelForm):
def clean(self): def clean(self):
cleaned_data = self.cleaned_data cleaned_data = self.cleaned_data
print(cleaned_data)
if not cleaned_data.get('public_key'): if not cleaned_data.get('public_key'):
private_key, public_key = UserHostingKey.generate_keys() private_key, public_key = UserHostingKey.generate_keys()
cleaned_data.update({ cleaned_data.update({

View file

@ -185,18 +185,31 @@ class VirtualMachinePlan(AssignPermissionsMixin, models.Model):
instance.assign_permissions(user) instance.assign_permissions(user)
return instance return instance
def cancel_plan(self): def cancel_plan(self, vm_id):
self.status = self.CANCELED_STATUS self.status = self.CANCELED_STATUS
self.save(update_fields=['status']) self.save(update_fields=['status'])
@classmethod
def terminate_opennebula_vm(self, user, vm_id):
opennebula_client = OpenNebulaManager(
user.email,
user.password,
)
return opennebula_client.terminate_vm(vm_id)
@classmethod @classmethod
def create_opennebula_vm(self, user, specs): def create_opennebula_vm(self, user, specs):
# import pdb
# pdb.set_trace()
# Init opennebula manager using given user # Init opennebula manager using given user
opennebula_client = OpenNebulaManager( opennebula_client = OpenNebulaManager(
user.email, user.email,
user.password[0:20], user.password,
create_user=True
) )
# Create a vm in opennebula using given specs # Create a vm in opennebula using given specs

View file

@ -35,7 +35,7 @@ class OpenNebulaManager:
'11': 'CLONING_FAILURE', '11': 'CLONING_FAILURE',
} }
def __init__(self, email=None, password=None, create_user=True): def __init__(self, email=None, password=None):
# Get oneadmin client # Get oneadmin client
self.oneadmin_client = self._get_opennebula_client( self.oneadmin_client = self._get_opennebula_client(
@ -43,9 +43,6 @@ class OpenNebulaManager:
settings.OPENNEBULA_PASSWORD settings.OPENNEBULA_PASSWORD
) )
if not create_user:
return
# Get or create oppenebula user using given credentials # Get or create oppenebula user using given credentials
self.opennebula_user = self._get_or_create_user( self.opennebula_user = self._get_or_create_user(
email, email,
@ -121,9 +118,17 @@ class OpenNebulaManager:
return vm_data return vm_data
def change_user_password(self, new_password):
self.oneadmin_client.call(
oca.User.METHODS['passwd'],
self.opennebula_user.id,
new_password
)
def create_vm(self, specs): def create_vm(self, specs):
vm_id = None vm_id = None
try: try:
# We do have the vm_template param set. Get and parse it # We do have the vm_template param set. Get and parse it
# and check it to be in the desired range. # and check it to be in the desired range.
# We have 8 possible VM templates for the moment which are 1x, 2x, 4x ... # We have 8 possible VM templates for the moment which are 1x, 2x, 4x ...
@ -136,6 +141,9 @@ class OpenNebulaManager:
<TYPE>{disk_type}</TYPE> <TYPE>{disk_type}</TYPE>
<SIZE>{size}</SIZE> <SIZE>{size}</SIZE>
</DISK> </DISK>
<CONTEXT>
<SSH_PUBLIC_KEY>{ssh_key}</SSH_PUBLIC_KEY>
</CONTEXT>
</VM> </VM>
""" """
vm_id = oca.VirtualMachine.allocate( vm_id = oca.VirtualMachine.allocate(
@ -145,7 +153,8 @@ class OpenNebulaManager:
vcpu=specs.get('cores'), vcpu=specs.get('cores'),
cpu=0.1 * specs.get('cores'), cpu=0.1 * specs.get('cores'),
disk_type='fs', disk_type='fs',
size=10000 * specs.get('disk_size') size=10000 * specs.get('disk_size'),
ssh_key=specs.get('ssh_key')
) )
) )
@ -155,10 +164,6 @@ class OpenNebulaManager:
self.opennebula_user.id, self.opennebula_user.id,
self.opennebula_user.group_ids[0] self.opennebula_user.group_ids[0]
) )
# oca.VirtualMachine.chown(
# vm_id,
# )
except socket.timeout as socket_err: except socket.timeout as socket_err:
logger.error("Socket timeout error: {0}".format(socket_err)) logger.error("Socket timeout error: {0}".format(socket_err))
@ -171,6 +176,29 @@ class OpenNebulaManager:
return vm_id return vm_id
def terminate_vm(self, vm_id):
TERMINATE_ACTION = 'terminate'
vm_terminated = False
try:
self.oneadmin_client.call(
oca.VirtualMachine.METHODS['action'],
TERMINATE_ACTION,
int(vm_id),
)
vm_terminated = True
except socket.timeout as socket_err:
logger.error("Socket timeout error: {0}".format(socket_err))
except OpenNebulaException as opennebula_err:
logger.error("OpenNebulaException error: {0}".format(opennebula_err))
except OSError as os_err:
logger.error("OSError : {0}".format(os_err))
except ValueError as value_err:
logger.error("ValueError : {0}".format(value_err))
return vm_terminated
def get_vm_templates(self): def get_vm_templates(self):
template_pool = oca.VmTemplatePool(self.oneadmin_client) template_pool = oca.VmTemplatePool(self.oneadmin_client)
template_pool.info() template_pool.info()

View file

@ -170,16 +170,28 @@
{% csrf_token %} {% csrf_token %}
</form> </form>
<button type="text" data-href="{% url 'hosting:virtual_machines' virtual_machine.id %}" data-toggle="modal" data-target="#confirm-cancel" class="btn btn-danger">{% trans "Cancel Virtual Machine"%}</button> <button type="text" data-href="{% url 'hosting:virtual_machines' virtual_machine.id %}" data-toggle="modal" data-target="#confirm-cancel" class="btn btn-danger">{% trans "Terminate Virtual Machine"%}</button>
</div> </div>
</div> </div>
<div class="col-md-12">
<br/>
{% if messages %}
<div class="alert alert-warning">
{% for message in messages %}
<span>{{ message }}</span>
{% endfor %}
</div>
{% endif %}
</div>
<!-- Cancel Modal --> <!-- Cancel Modal -->
<div class="modal fade" id="confirm-cancel" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true"> <div class="modal fade" id="confirm-cancel" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
{% trans "Cancel your Virtual Machine"%} {% trans "Terminate your Virtual Machine"%}
</div> </div>
<div class="modal-body"> <div class="modal-body">
{% trans "Are you sure do you want to cancel your Virtual Machine "%} {{vm.virtual_machine}} {% trans "plan?"%} {% trans "Are you sure do you want to cancel your Virtual Machine "%} {{vm.virtual_machine}} {% trans "plan?"%}

View file

@ -9,11 +9,18 @@
<form method="POST" action="" > <form method="POST" action="" >
{% csrf_token %} {% csrf_token %}
<h3><i class="fa fa-key" aria-hidden="true"></i>{% trans "Access Key"%} </h3> <h3><i class="fa fa-key" aria-hidden="true"></i>{% trans "Access Key"%} </h3>
{% if messages %}
<div class="alert alert-warning">
{% for message in messages %}
<span>{{ message }}</span>
{% endfor %}
</div>
{% endif %}
<hr/> <hr/>
{% if not user_key %} {% if not user_key %}
<div class="alert alert-warning"> <h3>
{% trans "Upload your own key. "%} {% trans "Upload your own key. "%}
</div> </h3>
<div class="form-group"> <div class="form-group">
<label for="comment">Paste here your public key</label> <label for="comment">Paste here your public key</label>
<textarea class="form-control" rows="6" name="public_key"></textarea> <textarea class="form-control" rows="6" name="public_key"></textarea>
@ -22,10 +29,10 @@
<button class="btn btn-success">{% trans "Upload Key"%} </a> <button class="btn btn-success">{% trans "Upload Key"%} </a>
</div> </div>
<div class="alert alert-warning"> <h3>
{% trans "Or generate a new key pair."%} {% trans "Or generate a new key pair."%}
</div> </h3>
<div class="form-group"> <div class="form-group">
<button class="btn btn-success">{% trans "Generate Key Pair"%} </a> <button class="btn btn-success">{% trans "Generate Key Pair"%} </a>
</div> </div>
@ -58,7 +65,7 @@
{% if private_key %} {% if private_key %}
<div class="alert alert-warning"> <div class="alert alert-warning">
<strong>{% trans "Warning!"%}</strong>{% trans "You can view your SSH private key once. Don't lost your key"%} <strong>{% trans "Warning!"%}</strong>{% trans "You can download your SSH private key once. Don't lost your key"%}
</div> </div>
<div class="form-group"> <div class="form-group">
<textarea class="form-control" rows="6" id="ssh_key" type="hidden" style="display:none">{{private_key}}</textarea> <textarea class="form-control" rows="6" id="ssh_key" type="hidden" style="display:none">{{private_key}}</textarea>

View file

@ -11,6 +11,16 @@
<a class="btn btn-success" href="{% url 'hosting:create-virtual-machine' %}" >{% trans "Create VM"%} </a> <a class="btn btn-success" href="{% url 'hosting:create-virtual-machine' %}" >{% trans "Create VM"%} </a>
</p> </p>
<br/> <br/>
<div class="col-md-12">
<br/>
{% if messages %}
<div class="alert alert-warning">
{% for message in messages %}
<span>{{ message }}</span>
{% endfor %}
</div>
{% endif %}
</div>
<thead> <thead>
<tr> <tr>
<th>{% trans "ID"%}</th> <th>{% trans "ID"%}</th>

View file

@ -11,7 +11,8 @@ from django.contrib.auth import authenticate, login
from django.contrib import messages from django.contrib import messages
from django.conf import settings from django.conf import settings
from django.shortcuts import redirect from django.shortcuts import redirect
from django.utils.http import urlsafe_base64_decode
from django.contrib.auth.tokens import default_token_generator
from guardian.mixins import PermissionRequiredMixin from guardian.mixins import PermissionRequiredMixin
from stored_messages.settings import stored_messages_settings from stored_messages.settings import stored_messages_settings
@ -186,6 +187,43 @@ class PasswordResetConfirmView(PasswordResetConfirmViewMixin):
template_name = 'hosting/confirm_reset_password.html' template_name = 'hosting/confirm_reset_password.html'
success_url = reverse_lazy('hosting:login') success_url = reverse_lazy('hosting:login')
def post(self, request, uidb64=None, token=None, *arg, **kwargs):
try:
uid = urlsafe_base64_decode(uidb64)
user = CustomUser.objects.get(pk=uid)
opennebula_client = OpenNebulaManager(
email=user.email,
password=user.password,
)
except (TypeError, ValueError, OverflowError, CustomUser.DoesNotExist):
user = None
opennebula_client = None
form = self.form_class(request.POST)
if user is not None and default_token_generator.check_token(user, token):
if form.is_valid():
new_password = form.cleaned_data['new_password2']
user.set_password(new_password)
user.save()
messages.success(request, 'Password has been reset.')
# Change opennebula password
opennebula_client.change_user_password(new_password)
return self.form_valid(form)
else:
messages.error(request, 'Password reset has not been successful.')
form.add_error(None, 'Password reset has not been successful.')
return self.form_invalid(form)
else:
messages.error(request, 'The reset password link is no longer valid.')
form.add_error(None, 'The reset password link is no longer valid.')
return self.form_invalid(form)
class NotificationsView(LoginRequiredMixin, TemplateView): class NotificationsView(LoginRequiredMixin, TemplateView):
template_name = 'hosting/notifications.html' template_name = 'hosting/notifications.html'
@ -261,14 +299,6 @@ class GenerateVMSSHKeysView(LoginRequiredMixin, FormView):
'form': UserHostingKeyForm(request=self.request) 'form': UserHostingKeyForm(request=self.request)
}) })
del(context['form'])
context.update({
'form': form
})
form = UserHostingKeyForm(request=self.request)
print("context", context)
# return HttpResponseRedirect(reverse('hosting:key_pair')) # return HttpResponseRedirect(reverse('hosting:key_pair'))
return render(self.request, self.template_name, context) return render(self.request, self.template_name, context)
@ -332,6 +362,21 @@ class PaymentVMView(LoginRequiredMixin, FormView):
return context return context
def get(self, request, *args, **kwargs):
try:
UserHostingKey.objects.get(
user=self.request.user
)
except UserHostingKey.DoesNotExist:
messages.success(
request,
'In order to create a VM, you create/upload your SSH KEY first.'
)
return HttpResponseRedirect(reverse('hosting:key_pair'))
return self.render_to_response(self.get_context_data())
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
form = self.get_form() form = self.get_form()
@ -415,13 +460,27 @@ class PaymentVMView(LoginRequiredMixin, FormView):
# If the Stripe payment was successed, set order status approved # If the Stripe payment was successed, set order status approved
order.set_approved() order.set_approved()
# Get user ssh key
try:
user_key = UserHostingKey.objects.get(
user=self.request.user
)
# Add ssh_key to specs
specs.update({
'ssh_key': user_key.public_key
})
except UserHostingKey.DoesNotExist:
pass
# Create a vm using logged user # Create a vm using logged user
oppennebula_vm_id = VirtualMachinePlan.create_opennebula_vm( opennebula_vm_id = VirtualMachinePlan.create_opennebula_vm(
self.request.user, self.request.user,
specs specs
) )
plan.oppenebula_id = oppennebula_vm_id plan.opennebula_id = opennebula_vm_id
plan.save() plan.save()
# Send notification to ungleich as soon as VM has been booked # Send notification to ungleich as soon as VM has been booked
@ -503,6 +562,18 @@ class CreateVirtualMachinesView(LoginRequiredMixin, View):
login_url = reverse_lazy('hosting:login') login_url = reverse_lazy('hosting:login')
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
try:
UserHostingKey.objects.get(
user=self.request.user
)
except UserHostingKey.DoesNotExist:
messages.success(
request,
'In order to create a VM, you need to create/upload your SSH KEY first.'
)
return HttpResponseRedirect(reverse('hosting:key_pair'))
context = { context = {
'vm_types': VirtualMachineType.get_serialized_vm_types(), 'vm_types': VirtualMachineType.get_serialized_vm_types(),
'configuration_options': VirtualMachinePlan.VM_CONFIGURATION 'configuration_options': VirtualMachinePlan.VM_CONFIGURATION
@ -565,18 +636,26 @@ class VirtualMachineView(PermissionRequiredMixin, LoginRequiredMixin, View):
# except Exception as error: # except Exception as error:
# raise Http404() # raise Http404()
# def get_success_url(self): def get_object(self):
# vm = self.get_object() opennebula_vm_id = self.kwargs.get('pk')
# final_url = "%s%s" % (reverse('hosting:virtual_machines', kwargs={'pk': vm.id}), opennebula_vm = None
# '#status-v') try:
# return final_url opennebula_vm = VirtualMachinePlan.objects.get(opennebula_id=opennebula_vm_id)
except Exception as error:
print(error)
raise Http404()
return opennebula_vm
def get_success_url(self):
final_url = reverse('hosting:virtual_machines')
return final_url
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
vm_id = self.kwargs.get('pk') opennebula_vm_id = self.kwargs.get('pk')
try: try:
opennebula_vm = VirtualMachinePlan.get_vm( opennebula_vm = VirtualMachinePlan.get_vm(
self.request.user, self.request.user,
vm_id opennebula_vm_id
) )
except Exception as error: except Exception as error:
print(error) print(error)
@ -588,9 +667,24 @@ class VirtualMachineView(PermissionRequiredMixin, LoginRequiredMixin, View):
# context = {} # context = {}
return render(request, self.template_name, context) return render(request, self.template_name, context)
def post(self, *args, **kwargs): def post(self, request, *args, **kwargs):
vm = self.get_object() vm = self.get_object()
vm.cancel_plan()
opennebula_vm_id = self.kwargs.get('pk')
terminated = VirtualMachinePlan.terminate_opennebula_vm(
self.request.user,
opennebula_vm_id
)
if not terminated:
messages.error(
request,
'Error terminating VM %s' % (opennebula_vm_id)
)
return HttpResponseRedirect(self.get_success_url())
#vm.cancel_plan()
context = { context = {
'vm': vm, 'vm': vm,
@ -606,8 +700,14 @@ class VirtualMachineView(PermissionRequiredMixin, LoginRequiredMixin, View):
email = BaseEmail(**email_data) email = BaseEmail(**email_data)
email.send() email.send()
messages.error(
request,
'VM %s terminated successfully' % (opennebula_vm_id)
)
return HttpResponseRedirect(self.get_success_url()) return HttpResponseRedirect(self.get_success_url())
class HostingBillListView(LoginRequiredMixin, ListView): class HostingBillListView(LoginRequiredMixin, ListView):
template_name = "hosting/bills.html" template_name = "hosting/bills.html"
login_url = reverse_lazy('hosting:login') login_url = reverse_lazy('hosting:login')
@ -616,6 +716,7 @@ class HostingBillListView(LoginRequiredMixin, ListView):
paginate_by = 10 paginate_by = 10
ordering = '-id' ordering = '-id'
class HostingBillDetailView(PermissionRequiredMixin, LoginRequiredMixin, DetailView): class HostingBillDetailView(PermissionRequiredMixin, LoginRequiredMixin, DetailView):
template_name = "hosting/bill_detail.html" template_name = "hosting/bill_detail.html"
login_url = reverse_lazy('hosting:login') login_url = reverse_lazy('hosting:login')
@ -624,7 +725,7 @@ class HostingBillDetailView(PermissionRequiredMixin, LoginRequiredMixin, DetailV
model = HostingBill model = HostingBill
def get_object(self, queryset=None): def get_object(self, queryset=None):
#Get HostingBill for primary key (Select from customer users) # Get HostingBill for primary key (Select from customer users)
pk = self.kwargs['pk'] pk = self.kwargs['pk']
object = HostingBill.objects.filter(customer__id=pk).first() object = HostingBill.objects.filter(customer__id=pk).first()
if object is None: if object is None: