From 1d70563ea209501f90022809e937ebc6e5b96fb7 Mon Sep 17 00:00:00 2001 From: PCoder Date: Fri, 10 May 2019 09:19:31 +0200 Subject: [PATCH 01/44] Save user's key in opennebula --- datacenterlight/tasks.py | 49 +--------------------------------------- hosting/views.py | 13 +++++++---- opennebula_api/models.py | 25 ++++++++++++++++++++ 3 files changed, 35 insertions(+), 52 deletions(-) diff --git a/datacenterlight/tasks.py b/datacenterlight/tasks.py index 5f12b7df..12c1cf38 100644 --- a/datacenterlight/tasks.py +++ b/datacenterlight/tasks.py @@ -197,54 +197,7 @@ def create_vm_task(self, vm_template_id, user, specs, template, order_id): 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() + manager.save_key_in_opennebula('\n'.join(public_keys)) else: logger.debug("VM's ipv6 is None. Hence not created VMDetail") except Exception as e: diff --git a/hosting/views.py b/hosting/views.py index 92dd5aa8..209df30b 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -36,6 +36,7 @@ from datacenterlight.cms_models import DCLCalculatorPluginModel from datacenterlight.models import VMTemplate, VMPricing from datacenterlight.utils import create_vm, get_cms_integration from hosting.models import UserCardDetail +from utils.hosting_utils import get_all_public_keys from membership.models import CustomUser, StripeCustomer from opennebula_api.models import OpenNebulaManager from opennebula_api.serializers import ( @@ -460,7 +461,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] + manager.save_key_in_opennebula('\n'.join(keys_to_save)) return super(SSHKeyDeleteView, self).delete(request, *args, **kwargs) @@ -509,8 +512,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('\n'.join(keys)) return redirect(reverse_lazy('hosting:ssh_keys'), foo='bar') @@ -563,7 +566,9 @@ class SSHKeyCreateView(LoginRequiredMixin, FormView): 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 = UserHostingKey.objects.filter(user=self.request.user) + keys_to_save = [k.public_key for k in keys] + manager.save_key_in_opennebula('\n'.join(keys_to_save)) return HttpResponseRedirect(self.success_url) def post(self, request, *args, **kwargs): diff --git a/opennebula_api/models.py b/opennebula_api/models.py index a333aa23..5a838c02 100644 --- a/opennebula_api/models.py +++ b/opennebula_api/models.py @@ -371,6 +371,31 @@ class OpenNebulaManager(): return vm_terminated + def save_key_in_opennebula(self, ssh_key): + """ + 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 + :return: + """ + UPDATE_TYPE = 1 + return_value = self.client.call( + 'one.user.update', + self.email, + self.password, + '%s' % 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) From c92b8c6facdff3e8f82cb91475ae14afbe2ac107 Mon Sep 17 00:00:00 2001 From: PCoder Date: Fri, 10 May 2019 23:51:05 +0200 Subject: [PATCH 02/44] one. is appended by oca --- opennebula_api/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opennebula_api/models.py b/opennebula_api/models.py index 5a838c02..8564332c 100644 --- a/opennebula_api/models.py +++ b/opennebula_api/models.py @@ -382,7 +382,7 @@ class OpenNebulaManager(): """ UPDATE_TYPE = 1 return_value = self.client.call( - 'one.user.update', + 'user.update', self.email, self.password, '%s' % ssh_key, From 85136d80ccea0ee2e13b55677a2bd7e0febc04a8 Mon Sep 17 00:00:00 2001 From: PCoder Date: Fri, 10 May 2019 23:57:52 +0200 Subject: [PATCH 03/44] Pass the opennebula user id as the object id --- opennebula_api/models.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/opennebula_api/models.py b/opennebula_api/models.py index 8564332c..8b4517e5 100644 --- a/opennebula_api/models.py +++ b/opennebula_api/models.py @@ -383,8 +383,7 @@ class OpenNebulaManager(): UPDATE_TYPE = 1 return_value = self.client.call( 'user.update', - self.email, - self.password, + self.opennebula_user.id, '%s' % ssh_key, UPDATE_TYPE ) From 6a1faa52e470a3923a331ce45ad99df84a9b9d72 Mon Sep 17 00:00:00 2001 From: PCoder Date: Sat, 11 May 2019 00:25:49 +0200 Subject: [PATCH 04/44] Set user's own ssh keys when creating VM --- datacenterlight/tasks.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/datacenterlight/tasks.py b/datacenterlight/tasks.py index 12c1cf38..a4d2ebe7 100644 --- a/datacenterlight/tasks.py +++ b/datacenterlight/tasks.py @@ -79,10 +79,12 @@ 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) 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 ) @@ -193,7 +195,6 @@ def create_vm_task(self, vm_template_id, user, specs, template, order_id): 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')) get_or_create_vm_detail(custom_user, manager, vm_id) if custom_user is not None: public_keys = get_all_public_keys(custom_user) From 5146daa6806152d5d100f8da6d095b856f562d2b Mon Sep 17 00:00:00 2001 From: PCoder Date: Sat, 11 May 2019 00:31:25 +0200 Subject: [PATCH 05/44] Use SSH_PUBLIC_KEY within CONTEXT --- opennebula_api/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opennebula_api/models.py b/opennebula_api/models.py index 8b4517e5..7873413b 100644 --- a/opennebula_api/models.py +++ b/opennebula_api/models.py @@ -384,7 +384,7 @@ class OpenNebulaManager(): return_value = self.client.call( 'user.update', self.opennebula_user.id, - '%s' % ssh_key, + '%s' % ssh_key, UPDATE_TYPE ) if type(return_value) == int: From 3602bb0eb7e98a02bc1d32e65b55cfeb4240d0d5 Mon Sep 17 00:00:00 2001 From: PCoder Date: Sat, 11 May 2019 01:46:28 +0200 Subject: [PATCH 06/44] Add save_key_in_vm_template and get_all_vmids methods --- opennebula_api/models.py | 39 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/opennebula_api/models.py b/opennebula_api/models.py index 7873413b..61d08d1a 100644 --- a/opennebula_api/models.py +++ b/opennebula_api/models.py @@ -371,7 +371,31 @@ class OpenNebulaManager(): return vm_terminated - def save_key_in_opennebula(self, ssh_key): + def save_key_in_vm_template(self, vm_id, ssh_key): + """ + Update the template of a given VM and set the ssh key of the user + :param vm_id: the identifier of the VM object + :param ssh_key: a newline(\n) separated ssh key string that needs to be + set in the VM template + :return: + """ + UPDATE_TYPE = 1 + return_value = self.client.call( + 'vm.updateconf', + vm_id, + '%s' % ssh_key, + UPDATE_TYPE + ) + if type(return_value) == int: + logger.debug( + "Saved the key in VM Template success : %s" % return_value) + else: + logger.error( + "Could not save the key in VM Template. %s" % return_value) + + return + + def save_key_in_opennebula_user(self, ssh_key): """ Save the given ssh key in OpenNebula user @@ -613,6 +637,19 @@ class OpenNebulaManager(): "Keys and/or hosts are empty, so not managing any keys" ) + def get_all_vmids(self): + owner = CustomUser.objects.filter(email=self.email).first() + all_orders = HostingOrder.objects.filter(customer__user=owner) + vm_ids = [] + if len(all_orders) > 0: + logger.debug("The user {} has 1 or more VMs. We need to configure " + "the ssh keys.".format(self.email)) + vm_ids = [order.vm_id for order in all_orders] + else: + logger.debug("The user {} has no VMs. We don't need to configure " + "the ssh keys.".format(self.email)) + return vm_ids + def get_all_hosts(self): """ A utility function to obtain all hosts of this owner From 65c9ccb671fa8e3d690b4cf01b47a99c8c72234c Mon Sep 17 00:00:00 2001 From: PCoder Date: Sat, 11 May 2019 01:54:35 +0200 Subject: [PATCH 07/44] Use save_key_in_opennebula_user and save_key_in_vm_template --- hosting/views.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/hosting/views.py b/hosting/views.py index 209df30b..450875e5 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -463,7 +463,11 @@ class SSHKeyDeleteView(LoginRequiredMixin, DeleteView): public_key = UserHostingKey.objects.get(pk=pk).public_key keys = UserHostingKey.objects.filter(user=self.request.user) keys_to_save = [k.public_key for k in keys if k != public_key] - manager.save_key_in_opennebula('\n'.join(keys_to_save)) + manager.save_key_in_opennebula_user('\n'.join(keys_to_save)) + vm_ids = manager.get_all_vmids() + if len(vm_ids) > 0 and len(keys_to_save) > 0: + for vm_id in vm_ids: + manager.save_key_in_vm_template(vm_id, '\n'.join(keys_to_save)) return super(SSHKeyDeleteView, self).delete(request, *args, **kwargs) @@ -513,7 +517,11 @@ class SSHKeyChoiceView(LoginRequiredMixin, View): password=owner.password ) keys = get_all_public_keys(request.user) - manager.save_key_in_opennebula('\n'.join(keys)) + vm_ids = manager.get_all_vmids() + manager.save_key_in_opennebula_user('\n'.join(keys)) + if len(vm_ids) > 0 and len(keys) > 0: + for vm_id in vm_ids: + manager.save_key_in_vm_template(vm_id, '\n'.join(keys)) return redirect(reverse_lazy('hosting:ssh_keys'), foo='bar') @@ -563,12 +571,13 @@ 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() keys = UserHostingKey.objects.filter(user=self.request.user) keys_to_save = [k.public_key for k in keys] - manager.save_key_in_opennebula('\n'.join(keys_to_save)) + manager.save_key_in_opennebula_user('\n'.join(keys_to_save)) + vm_ids = manager.get_all_vmids() + if len(vm_ids) > 0 and len(keys) > 0: + for vm_id in vm_ids: + manager.save_key_in_vm_template(vm_id, '\n'.join(keys_to_save)) return HttpResponseRedirect(self.success_url) def post(self, request, *args, **kwargs): From 0b85784fd3fa5726fadd63c56fdd9d718d2d6058 Mon Sep 17 00:00:00 2001 From: PCoder Date: Sat, 11 May 2019 01:56:00 +0200 Subject: [PATCH 08/44] No need to manage ssh keys after VM is created The ssh keys are added at the time the VM is created or later --- datacenterlight/tasks.py | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/datacenterlight/tasks.py b/datacenterlight/tasks.py index a4d2ebe7..808f6c61 100644 --- a/datacenterlight/tasks.py +++ b/datacenterlight/tasks.py @@ -190,17 +190,8 @@ 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: - get_or_create_vm_detail(custom_user, manager, vm_id) - if custom_user is not None: - public_keys = get_all_public_keys(custom_user) - manager.save_key_in_opennebula('\n'.join(public_keys)) - else: - logger.debug("VM's ipv6 is None. Hence not created VMDetail") + get_or_create_vm_detail(custom_user, manager, vm_id) except Exception as e: logger.error(str(e)) try: From 7f6d4c1c53304aa80d032d1df1ed20001e7aca21 Mon Sep 17 00:00:00 2001 From: PCoder Date: Sat, 11 May 2019 02:23:51 +0200 Subject: [PATCH 09/44] Refactor get_all_vmids -> get_all_active_vmids We now get this info from opennebula --- hosting/views.py | 6 +++--- opennebula_api/models.py | 31 ++++++++----------------------- 2 files changed, 11 insertions(+), 26 deletions(-) diff --git a/hosting/views.py b/hosting/views.py index 450875e5..f1a6dd9c 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -464,7 +464,7 @@ class SSHKeyDeleteView(LoginRequiredMixin, DeleteView): keys = UserHostingKey.objects.filter(user=self.request.user) keys_to_save = [k.public_key for k in keys if k != public_key] manager.save_key_in_opennebula_user('\n'.join(keys_to_save)) - vm_ids = manager.get_all_vmids() + vm_ids = manager.get_vms() if len(vm_ids) > 0 and len(keys_to_save) > 0: for vm_id in vm_ids: manager.save_key_in_vm_template(vm_id, '\n'.join(keys_to_save)) @@ -517,7 +517,7 @@ class SSHKeyChoiceView(LoginRequiredMixin, View): password=owner.password ) keys = get_all_public_keys(request.user) - vm_ids = manager.get_all_vmids() + vm_ids = manager.get_all_active_vmids() manager.save_key_in_opennebula_user('\n'.join(keys)) if len(vm_ids) > 0 and len(keys) > 0: for vm_id in vm_ids: @@ -574,7 +574,7 @@ class SSHKeyCreateView(LoginRequiredMixin, FormView): keys = UserHostingKey.objects.filter(user=self.request.user) keys_to_save = [k.public_key for k in keys] manager.save_key_in_opennebula_user('\n'.join(keys_to_save)) - vm_ids = manager.get_all_vmids() + vm_ids = manager.get_all_active_vmids() if len(vm_ids) > 0 and len(keys) > 0: for vm_id in vm_ids: manager.save_key_in_vm_template(vm_id, '\n'.join(keys_to_save)) diff --git a/opennebula_api/models.py b/opennebula_api/models.py index 61d08d1a..819bd803 100644 --- a/opennebula_api/models.py +++ b/opennebula_api/models.py @@ -168,7 +168,7 @@ class OpenNebulaManager(): raise return user_pool - def _get_vm_pool(self, infoextended=True): + def _get_vm_pool(self, infoextended=True, vm_state=-1): """ # filter: # -4: Resources belonging to the user’s primary group @@ -201,8 +201,8 @@ class OpenNebulaManager(): vm_pool = oca.VirtualMachinePool(self.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 + filter=-1, # User's resources and any of his groups + vm_state=vm_state ) else: vm_pool.info() @@ -210,13 +210,6 @@ class OpenNebulaManager(): except AttributeError: logger.error( 'Could not connect via client, using oneadmin instead') - try: - vm_pool = oca.VirtualMachinePool(self.oneadmin_client) - vm_pool.info(filter=-2) - return vm_pool - except: - raise ConnectionRefusedError - except ConnectionRefusedError: logger.error( 'Could not connect to host: {host} via protocol {protocol}'.format( @@ -228,9 +221,9 @@ class OpenNebulaManager(): except: raise ConnectionRefusedError - def get_vms(self): + def get_vms(self, vm_state=-1): try: - return self._get_vm_pool() + return self._get_vm_pool(vm_state=vm_state) except ConnectionRefusedError: raise ConnectionRefusedError @@ -637,17 +630,9 @@ class OpenNebulaManager(): "Keys and/or hosts are empty, so not managing any keys" ) - def get_all_vmids(self): - owner = CustomUser.objects.filter(email=self.email).first() - all_orders = HostingOrder.objects.filter(customer__user=owner) - vm_ids = [] - if len(all_orders) > 0: - logger.debug("The user {} has 1 or more VMs. We need to configure " - "the ssh keys.".format(self.email)) - vm_ids = [order.vm_id for order in all_orders] - else: - logger.debug("The user {} has no VMs. We don't need to configure " - "the ssh keys.".format(self.email)) + def get_all_active_vmids(self): + vm_pool = self.get_vms(vm_state=3) # get active vms of the user + vm_ids = [vm.id for vm in vm_pool] return vm_ids def get_all_hosts(self): From b189371a7b6050fb9854b4f6bbfb507d5073ee8f Mon Sep 17 00:00:00 2001 From: PCoder Date: Sat, 11 May 2019 02:38:16 +0200 Subject: [PATCH 10/44] Call get_all_active_vmids to get the active vmids --- hosting/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hosting/views.py b/hosting/views.py index f1a6dd9c..5a3e06b2 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -464,7 +464,7 @@ class SSHKeyDeleteView(LoginRequiredMixin, DeleteView): keys = UserHostingKey.objects.filter(user=self.request.user) keys_to_save = [k.public_key for k in keys if k != public_key] manager.save_key_in_opennebula_user('\n'.join(keys_to_save)) - vm_ids = manager.get_vms() + vm_ids = manager.get_all_active_vmids() if len(vm_ids) > 0 and len(keys_to_save) > 0: for vm_id in vm_ids: manager.save_key_in_vm_template(vm_id, '\n'.join(keys_to_save)) From 3133bde0e9879909aa68fcd4fe3fd4b18f15f150 Mon Sep 17 00:00:00 2001 From: PCoder Date: Sat, 11 May 2019 09:15:08 +0200 Subject: [PATCH 11/44] Don't set the key in the live template --- hosting/views.py | 15 +-------------- opennebula_api/models.py | 20 +++++++------------- 2 files changed, 8 insertions(+), 27 deletions(-) diff --git a/hosting/views.py b/hosting/views.py index 5a3e06b2..dc09644e 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -464,10 +464,6 @@ class SSHKeyDeleteView(LoginRequiredMixin, DeleteView): keys = UserHostingKey.objects.filter(user=self.request.user) keys_to_save = [k.public_key for k in keys if k != public_key] manager.save_key_in_opennebula_user('\n'.join(keys_to_save)) - vm_ids = manager.get_all_active_vmids() - if len(vm_ids) > 0 and len(keys_to_save) > 0: - for vm_id in vm_ids: - manager.save_key_in_vm_template(vm_id, '\n'.join(keys_to_save)) return super(SSHKeyDeleteView, self).delete(request, *args, **kwargs) @@ -517,11 +513,7 @@ class SSHKeyChoiceView(LoginRequiredMixin, View): password=owner.password ) keys = get_all_public_keys(request.user) - vm_ids = manager.get_all_active_vmids() manager.save_key_in_opennebula_user('\n'.join(keys)) - if len(vm_ids) > 0 and len(keys) > 0: - for vm_id in vm_ids: - manager.save_key_in_vm_template(vm_id, '\n'.join(keys)) return redirect(reverse_lazy('hosting:ssh_keys'), foo='bar') @@ -571,13 +563,8 @@ class SSHKeyCreateView(LoginRequiredMixin, FormView): email=owner.email, password=owner.password ) - keys = UserHostingKey.objects.filter(user=self.request.user) - keys_to_save = [k.public_key for k in keys] + keys_to_save = get_all_public_keys(self.request.user) manager.save_key_in_opennebula_user('\n'.join(keys_to_save)) - vm_ids = manager.get_all_active_vmids() - if len(vm_ids) > 0 and len(keys) > 0: - for vm_id in vm_ids: - manager.save_key_in_vm_template(vm_id, '\n'.join(keys_to_save)) return HttpResponseRedirect(self.success_url) def post(self, request, *args, **kwargs): diff --git a/opennebula_api/models.py b/opennebula_api/models.py index 819bd803..01bd065a 100644 --- a/opennebula_api/models.py +++ b/opennebula_api/models.py @@ -168,7 +168,7 @@ class OpenNebulaManager(): raise return user_pool - def _get_vm_pool(self, infoextended=True, vm_state=-1): + def _get_vm_pool(self, infoextended=True): """ # filter: # -4: Resources belonging to the user’s primary group @@ -201,15 +201,14 @@ class OpenNebulaManager(): vm_pool = oca.VirtualMachinePool(self.client) if infoextended: vm_pool.infoextended( - filter=-1, # User's resources and any of his groups - vm_state=vm_state + 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() return vm_pool - except AttributeError: - logger.error( - 'Could not connect via client, using oneadmin instead') + except AttributeError as ae: + logger.error("AttributeError : %s" % str(ae)) except ConnectionRefusedError: logger.error( 'Could not connect to host: {host} via protocol {protocol}'.format( @@ -221,9 +220,9 @@ class OpenNebulaManager(): except: raise ConnectionRefusedError - def get_vms(self, vm_state=-1): + def get_vms(self): try: - return self._get_vm_pool(vm_state=vm_state) + return self._get_vm_pool() except ConnectionRefusedError: raise ConnectionRefusedError @@ -630,11 +629,6 @@ class OpenNebulaManager(): "Keys and/or hosts are empty, so not managing any keys" ) - def get_all_active_vmids(self): - vm_pool = self.get_vms(vm_state=3) # get active vms of the user - vm_ids = [vm.id for vm in vm_pool] - return vm_ids - def get_all_hosts(self): """ A utility function to obtain all hosts of this owner From 7e538bf37b86ddd8ffdafff495e9bad04ca4be7b Mon Sep 17 00:00:00 2001 From: PCoder Date: Sun, 12 May 2019 19:12:34 +0200 Subject: [PATCH 12/44] Add ssh_key_added_to_vm.{html,txt} email templates --- .../hosting/emails/ssh_key_added_to_vm.html | 51 +++++++++++++++++++ .../hosting/emails/ssh_key_added_to_vm.txt | 11 ++++ 2 files changed, 62 insertions(+) create mode 100644 hosting/templates/hosting/emails/ssh_key_added_to_vm.html create mode 100644 hosting/templates/hosting/emails/ssh_key_added_to_vm.txt diff --git a/hosting/templates/hosting/emails/ssh_key_added_to_vm.html b/hosting/templates/hosting/emails/ssh_key_added_to_vm.html new file mode 100644 index 00000000..086c3479 --- /dev/null +++ b/hosting/templates/hosting/emails/ssh_key_added_to_vm.html @@ -0,0 +1,51 @@ +{% load static i18n %} + + + + + + + {% blocktrans %}SSH key(s) {{keys}} added to your VM {{vm_name}}{% endblocktrans %} + + + + + + + + + + + + + + + + + + + + + +
+ +
+

{% blocktrans %}SSH keys on your VM {{ vm_name }}{% endblocktrans %}

+
+

+ {% blocktrans %}You initiated adding the keys {{keys}} to your VM {{ vm_name }}!{% endblocktrans %} +

+

+ {% blocktrans %}This email is to notify you that it was accomplished successfully.{% endblocktrans %} +

+

+ {% blocktrans %}You can view your VM detail by clicking the button below.{% endblocktrans %} +

+
+ {% trans "View Detail" %} +
+

{% trans "Your Data Center Light Team" %}

+
+ + + \ No newline at end of file diff --git a/hosting/templates/hosting/emails/ssh_key_added_to_vm.txt b/hosting/templates/hosting/emails/ssh_key_added_to_vm.txt new file mode 100644 index 00000000..ede56e3a --- /dev/null +++ b/hosting/templates/hosting/emails/ssh_key_added_to_vm.txt @@ -0,0 +1,11 @@ +{% load i18n %} + +{% blocktrans %}{% blocktrans %}SSH key(s) {{keys}} added to your VM {{vm_name}}{% endblocktrans %} + +{% blocktrans %}You initiated adding the keys {{keys}} to your VM {{ vm_name }}!{% endblocktrans %} +{% blocktrans %}This email is to notify you that it was accomplished successfully.{% endblocktrans %} +{% blocktrans %}You can view your VM detail by clicking the button below.{% endblocktrans %} + +{{ base_url }}{{ vm_detail_url }} + +{% trans "Your Data Center Light Team" %} \ No newline at end of file From 67d789ebdb5f23058a34807c0193ad235d6559a7 Mon Sep 17 00:00:00 2001 From: PCoder Date: Sun, 12 May 2019 19:13:22 +0200 Subject: [PATCH 13/44] Implement save_ssh_key_in_vm_template_task A celery task which first sends a power off signal to the VM with the given ID and polls until it is powered off. Then, it updates the VM template with the given ssh keys of the user and resumes it. User is notified once the VM template has been updated. --- datacenterlight/tasks.py | 87 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 84 insertions(+), 3 deletions(-) diff --git a/datacenterlight/tasks.py b/datacenterlight/tasks.py index 808f6c61..6bf14825 100644 --- a/datacenterlight/tasks.py +++ b/datacenterlight/tasks.py @@ -1,4 +1,5 @@ from datetime import datetime +from time import sleep from celery import current_task from celery.exceptions import MaxRetriesExceededError @@ -8,7 +9,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 +16,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 @@ -176,7 +176,7 @@ def create_vm_task(self, vm_template_id, user, specs, template, order_id): kwargs={'pk': order_id}), 'page_header': _( 'Your New VM %(vm_name)s at Data Center Light') % { - 'vm_name': vm.get('name')}, + 'vm_name': vm.get('name')}, 'vm_name': vm.get('name') } email_data = { @@ -213,3 +213,84 @@ def create_vm_task(self, vm_template_id, user, specs, template, order_id): return return vm_id + + +@app.task(bind=True, max_retries=settings.CELERY_MAX_RETRIES) +def save_ssh_key_in_vm_template_task(self, user, vm_id, ssh_key_str): + logger.debug("Inside save_ssh_key_in_vm_template_task %s" % vm_id) + + on_user = user.get('email') + on_pass = user.get('pass') + + if on_user is None or on_pass is None: + logger.error( + "Either email or password not supplied. Can't save ssh key" + ) + return + + manager = OpenNebulaManager(email=on_user, password=on_pass) + + # poweroff the vm + vm = manager.power_off_vm(vm_id) + + powered_off = False + for t in range(15): + vm = manager.get_vm(vm_id) + if vm.str_state == 'POWEROFF': + logger.debug( + "VM %s has been powered off. Now adding ssh keys" % vm.id + ) + powered_off = True + break + else: + logger.debug( + "VM {} has state {}. Waiting 2 more seconds to see if it " + "powers off".format(vm.id, vm.str_state) + ) + sleep(2) + + if powered_off: + logger.debug( + "VM %s was powered off by api call" % vm.id + ) + if manager.save_key_in_vm_template(vm_id=vm_id, ssh_key=ssh_key_str) > 0: + logger.debug( + "Added ssh_keys of user %s to VM %s successfully" % + (on_user, vm_id) + ) + manager.resume(vm_id) + lang = 'en-us' + if user.get('language') is not None: + logger.debug( + "Language is set to {}".format(user.get('language'))) + lang = user.get('language') + translation.activate(lang) + # Send notification to the user as soon as VM has been booked + context = { + 'page_header': str(_("Adding of SSH key completed")), + 'base_url': "{0}://{1}".format(user.get('request_scheme'), + user.get('request_host')), + 'vm_detail_url': reverse('hosting:virtual_machines', + kwargs={'pk': vm_id}), + 'vm_name': vm.name + } + email_data = { + 'subject': context.get('page_header'), + 'to': user.get('email'), + 'context': context, + 'template_name': 'new_booked_vm', + 'template_path': 'hosting/emails/', + 'from_address': settings.DCL_SUPPORT_FROM_ADDRESS, + } + email = BaseEmail(**email_data) + email.send() + else: + logger.error( + "There was an error updating ssh keys of the VM %s" % vm_id + ) + else: + logger.error( + "VM {} did not poweroff within 30 seconds after the poweroff api " + "call. Please, ask the admin to poweroff and add the key " + "manually.".format(vm_id) + ) From 61127e56ca84c530d79909a06375164e67ef4c6c Mon Sep 17 00:00:00 2001 From: PCoder Date: Sun, 12 May 2019 19:16:53 +0200 Subject: [PATCH 14/44] Update virtual_machine_detail.html template To show the Add SSH key button and the modal that pops up after clicking it. --- .../hosting/virtual_machine_detail.html | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/hosting/templates/hosting/virtual_machine_detail.html b/hosting/templates/hosting/virtual_machine_detail.html index ce02036f..8b6537fe 100644 --- a/hosting/templates/hosting/virtual_machine_detail.html +++ b/hosting/templates/hosting/virtual_machine_detail.html @@ -80,6 +80,21 @@ + {% if keys|length > 0 %} + {% if not virtual_machine.status == 'canceled' %} +
+ +
+ {% trans "Sorry, there was an unexpected error. Kindly retry." %} +
+
+ {% endif %} + {% else %} + + {% endif %} +

{% trans "Support / Contact" %}

@@ -98,6 +113,41 @@ {% trans "BACK TO LIST" %}
+ + + From 641c556bb65602fc24dbb1fffdba68086aed0591 Mon Sep 17 00:00:00 2001 From: PCoder Date: Sun, 12 May 2019 21:16:46 +0200 Subject: [PATCH 25/44] Simplify code --- .../hosting/js/virtual_machine_detail.js | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/hosting/static/hosting/js/virtual_machine_detail.js b/hosting/static/hosting/js/virtual_machine_detail.js index 32ae3f53..5fb9f6e5 100644 --- a/hosting/static/hosting/js/virtual_machine_detail.js +++ b/hosting/static/hosting/js/virtual_machine_detail.js @@ -81,16 +81,7 @@ $(document).ready(function() { }) }); - $('#modal-add-ssh-key').on('hidden.bs.modal', function () { - location.reload(); - }); - - haveResponse = false; - $('#modal-add-ssh-key').on('click', '.btn-ok', function(e) { - if(haveResponse) { - console.log("We had response, so reload"); - location.reload(); - } + $('#modal-add-ssh-key-button').click(function(e) { var url = $('#add_ssh_key_to_vm_form').attr('action'); console.log("Url to POST " + url); @@ -106,7 +97,7 @@ $(document).ready(function() { console.log("Encoded data = " + encoded_data); fa_icon = $('#ssh-key-modal-icon'); - modal_btn = $('#ssh-key-modal-button'); + modal_btn = $('#modal-add-ssh-key-button'); modal_btn.prop("disabled", true); modal_btn.html(''); $.post(url, {selected_key: encoded_data}) @@ -115,7 +106,7 @@ $(document).ready(function() { modal_btn.prop("disabled", false); modal_btn.html("OK"); if (data.status === true) { - fa_icon.html(''); + fa_icon.html(''); } else { fa_icon.html(''); modal_btn.attr('class', '').addClass('btn btn-danger btn-ok btn-wide'); @@ -136,7 +127,9 @@ $(document).ready(function() { }) .always(function () { console.log("changing href to location: " + location); - haveResponse = true; + $('#modal-add-ssh-key-button').unbind('click').click(function () { + location.reload(); + }); }) }); From 9fd396363f79aa594e74b487e9a709e56b4f8db8 Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 13 May 2019 07:13:49 +0200 Subject: [PATCH 26/44] Center the add ssh key nicely --- hosting/static/hosting/css/virtual-machine.css | 5 +++++ hosting/templates/hosting/virtual_machine_detail.html | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/hosting/static/hosting/css/virtual-machine.css b/hosting/static/hosting/css/virtual-machine.css index 726b0f35..abd459a0 100644 --- a/hosting/static/hosting/css/virtual-machine.css +++ b/hosting/static/hosting/css/virtual-machine.css @@ -199,6 +199,11 @@ /* text-align: center; */ } +.vm-add-ssh-key { + margin: 25px 0 30px; + /* text-align: center; */ +} + @media(min-width: 768px) { .vm-detail-contain { display: flex; diff --git a/hosting/templates/hosting/virtual_machine_detail.html b/hosting/templates/hosting/virtual_machine_detail.html index 1aad409a..edbc71f9 100644 --- a/hosting/templates/hosting/virtual_machine_detail.html +++ b/hosting/templates/hosting/virtual_machine_detail.html @@ -82,7 +82,7 @@ {% if keys|length > 0 %} {% if not virtual_machine.status == 'canceled' %} -
+
{% trans "Sorry, there was an unexpected error. Kindly retry." %} From caa01f344ff69f11a5b18e6356944a29386416dd Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 13 May 2019 07:56:46 +0200 Subject: [PATCH 27/44] Change poweroff to poweroff_hard Issue with poweroff: Executing poweroff not always seems to work. Sometimes, the VM is tries to SHUTDOWN but times out. poweroff-hard seems to poweroff all the time. --- opennebula_api/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opennebula_api/models.py b/opennebula_api/models.py index 7348d538..ef07d4ce 100644 --- a/opennebula_api/models.py +++ b/opennebula_api/models.py @@ -367,7 +367,7 @@ class OpenNebulaManager(): vm = None try: vm = self.get_vm(vm_id) - vm.poweroff() + vm.poweroff_hard() except socket.timeout as socket_err: logger.error("Socket timeout error: {0}".format(socket_err)) except OpenNebulaException as opennebula_err: From 72ea362d0143f28f1e2ebea0f575d5608f74c1a7 Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 13 May 2019 08:01:11 +0200 Subject: [PATCH 28/44] Remove duplicated blocktrans in txt email template --- hosting/templates/hosting/emails/ssh_key_added_to_vm.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hosting/templates/hosting/emails/ssh_key_added_to_vm.txt b/hosting/templates/hosting/emails/ssh_key_added_to_vm.txt index ede56e3a..665936cb 100644 --- a/hosting/templates/hosting/emails/ssh_key_added_to_vm.txt +++ b/hosting/templates/hosting/emails/ssh_key_added_to_vm.txt @@ -1,6 +1,6 @@ {% load i18n %} -{% blocktrans %}{% blocktrans %}SSH key(s) {{keys}} added to your VM {{vm_name}}{% endblocktrans %} +{% blocktrans %}SSH key(s) {{keys}} added to your VM {{vm_name}}{% endblocktrans %} {% blocktrans %}You initiated adding the keys {{keys}} to your VM {{ vm_name }}!{% endblocktrans %} {% blocktrans %}This email is to notify you that it was accomplished successfully.{% endblocktrans %} From c99e943ebcec10140b89453f225167beb16c334f Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 10 Jun 2019 09:23:48 +0200 Subject: [PATCH 29/44] Use oneadmin_client to update a user's ssh key --- opennebula_api/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opennebula_api/models.py b/opennebula_api/models.py index ef07d4ce..761c84b7 100644 --- a/opennebula_api/models.py +++ b/opennebula_api/models.py @@ -432,7 +432,7 @@ class OpenNebulaManager(): :return: """ - return_value = self.client.call( + return_value = self.oneadmin_client.call( 'user.update', self.opennebula_user.id, '%s' % ssh_key, From 108fbb09b00bf15a77201aa162506d9f326bfa16 Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 24 Jun 2019 04:29:34 +0200 Subject: [PATCH 30/44] Add ssh key form to order_detail page To ask for the SSH key at the time of confirming and placing the order. The order does not proceed until the user provides a valid ssh key. --- .../datacenterlight/order_detail.html | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/datacenterlight/templates/datacenterlight/order_detail.html b/datacenterlight/templates/datacenterlight/order_detail.html index 31933e12..fa2ba7ca 100644 --- a/datacenterlight/templates/datacenterlight/order_detail.html +++ b/datacenterlight/templates/datacenterlight/order_detail.html @@ -134,6 +134,27 @@
{% 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 %} +
+ {% for message in messages %} + {{ message }} + {% endfor %} +
+ {% endif %} +
+

 {% trans "Add your public SSH key" %}

+
+ {% for field in form %} + {% bootstrap_field field %} + {% endfor %} + {% endif %}
{% if generic_payment_details %} From 1e68ecb047076bbcaf461e8646120c565d7c979b Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 24 Jun 2019 04:31:29 +0200 Subject: [PATCH 31/44] Confirm order button close: Redirect only to url specified --- hosting/static/hosting/js/virtual_machine_detail.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/hosting/static/hosting/js/virtual_machine_detail.js b/hosting/static/hosting/js/virtual_machine_detail.js index 5fb9f6e5..e90e73f6 100644 --- a/hosting/static/hosting/js/virtual_machine_detail.js +++ b/hosting/static/hosting/js/virtual_machine_detail.js @@ -161,8 +161,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 { From 87f5bf3dcc61abcc0ae00e0a7aff1aca5ee55007 Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 24 Jun 2019 04:32:27 +0200 Subject: [PATCH 32/44] Pass UserHostingKeyForm to the context of OrderConfirmationView --- datacenterlight/views.py | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 5dc3a3d3..f01766d1 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -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 @@ -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,7 @@ class OrderConfirmationView(DetailView): else: context.update({ 'vm': request.session.get('specs'), + 'form': UserHostingKeyForm(request=self.request), }) context.update({ 'site_url': reverse('datacenterlight:index'), @@ -583,6 +591,24 @@ class OrderConfirmationView(DetailView): stripe_api_cus_id = request.session.get('customer') stripe_utils = StripeUtils() + # Check ssh public key and then proceed + form = self.get_form() + required = 'add_ssh' in self.request.POST + 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': "
".join([str(v) for k,v in form.errors.items()]), + } + return JsonResponse(response) + + # We have a valid SSH key from the user, save it in opennebula and db + # and proceed further + form.save() + user_public_key = form.data["public_key"] + if 'token' in request.session: card_details = stripe_utils.get_cards_details_from_token( request.session.get('token') From 110f29171da1d1aa8b270c75fdae4cc64f94d77c Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 24 Jun 2019 04:33:48 +0200 Subject: [PATCH 33/44] Update user ssh key in opennebula --- datacenterlight/tasks.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/datacenterlight/tasks.py b/datacenterlight/tasks.py index 0ce71577..641bcf6b 100644 --- a/datacenterlight/tasks.py +++ b/datacenterlight/tasks.py @@ -81,6 +81,8 @@ def create_vm_task(self, vm_template_id, user, specs, template, order_id): 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, From ecc26d14e5d6d988a613ca70f98bfa9ce63302d1 Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 24 Jun 2019 17:24:35 +0200 Subject: [PATCH 34/44] Show previous keys if exist in order confirmation --- .../templates/datacenterlight/order_detail.html | 9 +++++++++ datacenterlight/views.py | 4 ++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/datacenterlight/templates/datacenterlight/order_detail.html b/datacenterlight/templates/datacenterlight/order_detail.html index fa2ba7ca..1f01c201 100644 --- a/datacenterlight/templates/datacenterlight/order_detail.html +++ b/datacenterlight/templates/datacenterlight/order_detail.html @@ -151,6 +151,15 @@

 {% trans "Add your public SSH key" %}

+
+ {% if keys|length > 0 %} +
Previous keys
+ {% endif %} + {% for key in keys %} + +
+ {% endfor %} + {% for field in form %} {% bootstrap_field field %} {% endfor %} diff --git a/datacenterlight/views.py b/datacenterlight/views.py index f01766d1..7aa6d8a4 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -25,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 @@ -575,6 +575,7 @@ class OrderConfirmationView(DetailView, FormView): 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'), @@ -607,7 +608,6 @@ class OrderConfirmationView(DetailView, FormView): # We have a valid SSH key from the user, save it in opennebula and db # and proceed further form.save() - user_public_key = form.data["public_key"] if 'token' in request.session: card_details = stripe_utils.get_cards_details_from_token( From 04f1112b098caadbdbcaeee72b7deddad2fe8d8b Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 24 Jun 2019 18:26:45 +0200 Subject: [PATCH 35/44] Add key in the text area --- datacenterlight/templates/datacenterlight/order_detail.html | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/datacenterlight/templates/datacenterlight/order_detail.html b/datacenterlight/templates/datacenterlight/order_detail.html index 1f01c201..85f87168 100644 --- a/datacenterlight/templates/datacenterlight/order_detail.html +++ b/datacenterlight/templates/datacenterlight/order_detail.html @@ -156,7 +156,9 @@
Previous keys
{% endif %} {% for key in keys %} - +
{% endfor %} From ba7ff9e409e484881f0cf3b909c601dff4d74f78 Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 24 Jun 2019 18:39:25 +0200 Subject: [PATCH 36/44] Adjust textarea styles --- datacenterlight/templates/datacenterlight/order_detail.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/datacenterlight/templates/datacenterlight/order_detail.html b/datacenterlight/templates/datacenterlight/order_detail.html index 85f87168..410ac2ff 100644 --- a/datacenterlight/templates/datacenterlight/order_detail.html +++ b/datacenterlight/templates/datacenterlight/order_detail.html @@ -156,7 +156,7 @@
Previous keys
{% endif %} {% for key in keys %} -
From a330dee9a1e61e09e8cbdba42f13c74318938537 Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 24 Jun 2019 18:58:44 +0200 Subject: [PATCH 37/44] Modify style --- datacenterlight/static/datacenterlight/css/common.css | 5 +++++ datacenterlight/templates/datacenterlight/order_detail.html | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/datacenterlight/static/datacenterlight/css/common.css b/datacenterlight/static/datacenterlight/css/common.css index 00ee52cc..b19b5852 100644 --- a/datacenterlight/static/datacenterlight/css/common.css +++ b/datacenterlight/static/datacenterlight/css/common.css @@ -186,3 +186,8 @@ footer .dcl-link-separator::before { background: transparent !important; resize: none; } + +.existing-keys-title { + font-weight: bold; + font-size: 14px; +} diff --git a/datacenterlight/templates/datacenterlight/order_detail.html b/datacenterlight/templates/datacenterlight/order_detail.html index 410ac2ff..bebb4d45 100644 --- a/datacenterlight/templates/datacenterlight/order_detail.html +++ b/datacenterlight/templates/datacenterlight/order_detail.html @@ -151,9 +151,9 @@

 {% trans "Add your public SSH key" %}

-
+
{% if keys|length > 0 %} -
Previous keys
+
Existing keys
{% endif %} {% for key in keys %}
{% endfor %} - +
{% for field in form %} {% bootstrap_field field %} {% endfor %} From 08608c726fb20b84b76aa93a0f7793676da2884f Mon Sep 17 00:00:00 2001 From: PCoder Date: Tue, 25 Jun 2019 02:11:57 +0200 Subject: [PATCH 39/44] Code cleanup: remove updating ssh keys on live VMs --- datacenterlight/tasks.py | 87 +------------------ .../hosting/js/virtual_machine_detail.js | 52 ----------- .../hosting/virtual_machine_detail.html | 35 -------- hosting/urls.py | 4 +- hosting/views.py | 55 ------------ 5 files changed, 4 insertions(+), 229 deletions(-) diff --git a/datacenterlight/tasks.py b/datacenterlight/tasks.py index 641bcf6b..8b4626e8 100644 --- a/datacenterlight/tasks.py +++ b/datacenterlight/tasks.py @@ -1,5 +1,4 @@ from datetime import datetime -from time import sleep from celery import current_task from celery.exceptions import MaxRetriesExceededError @@ -178,7 +177,7 @@ def create_vm_task(self, vm_template_id, user, specs, template, order_id): kwargs={'pk': order_id}), 'page_header': _( 'Your New VM %(vm_name)s at Data Center Light') % { - 'vm_name': vm.get('name')}, + 'vm_name': vm.get('name')}, 'vm_name': vm.get('name') } email_data = { @@ -193,7 +192,8 @@ def create_vm_task(self, vm_template_id, user, specs, template, order_id): email.send() logger.debug("New VM ID is {vm_id}".format(vm_id=vm_id)) - get_or_create_vm_detail(custom_user, manager, vm_id) + if vm_id > 0: + get_or_create_vm_detail(custom_user, manager, vm_id) except Exception as e: logger.error(str(e)) try: @@ -215,84 +215,3 @@ def create_vm_task(self, vm_template_id, user, specs, template, order_id): return return vm_id - - -@app.task(bind=True, max_retries=settings.CELERY_MAX_RETRIES) -def save_ssh_key_in_vm_template_task(self, user, vm_id, ssh_key_str): - logger.debug("Inside save_ssh_key_in_vm_template_task %s" % vm_id) - - on_user = user.get('email') - on_pass = user.get('pass') - - if on_user is None or on_pass is None: - logger.error( - "Either email or password not supplied. Can't save ssh key" - ) - return - - manager = OpenNebulaManager(email=on_user, password=on_pass) - - # poweroff the vm - vm = manager.power_off_vm(vm_id) - - powered_off = False - for t in range(15): - vm = manager.get_vm(vm_id) - if vm.str_state == 'POWEROFF': - logger.debug( - "VM %s has been powered off. Now adding ssh keys" % vm.id - ) - powered_off = True - break - else: - logger.debug( - "VM {} has state {}. Waiting 2 more seconds to see if it " - "powers off".format(vm.id, vm.str_state) - ) - sleep(2) - - if powered_off: - logger.debug( - "VM %s was powered off by api call" % vm.id - ) - if manager.save_key_in_vm_template(vm_id=vm_id, ssh_key=ssh_key_str) > 0: - logger.debug( - "Added ssh_keys of user %s to VM %s successfully" % - (on_user, vm_id) - ) - manager.resume(vm_id) - lang = 'en-us' - if user.get('language') is not None: - logger.debug( - "Language is set to {}".format(user.get('language'))) - lang = user.get('language') - translation.activate(lang) - # Send notification to the user as soon as VM has been booked - context = { - 'page_header': str(_("Adding of SSH key completed")), - 'base_url': "{0}://{1}".format(user.get('request_scheme'), - user.get('request_host')), - 'vm_detail_url': reverse('hosting:virtual_machines', - kwargs={'pk': vm_id}), - 'vm_name': vm.name - } - email_data = { - 'subject': context.get('page_header'), - 'to': user.get('email'), - 'context': context, - 'template_name': 'ssh_key_added_to_vm', - 'template_path': 'hosting/emails/', - 'from_address': settings.DCL_SUPPORT_FROM_ADDRESS, - } - email = BaseEmail(**email_data) - email.send() - else: - logger.error( - "There was an error updating ssh keys of the VM %s" % vm_id - ) - else: - logger.error( - "VM {} did not poweroff within 30 seconds after the poweroff api " - "call. Please, ask the admin to poweroff and add the key " - "manually.".format(vm_id) - ) diff --git a/hosting/static/hosting/js/virtual_machine_detail.js b/hosting/static/hosting/js/virtual_machine_detail.js index e90e73f6..28592883 100644 --- a/hosting/static/hosting/js/virtual_machine_detail.js +++ b/hosting/static/hosting/js/virtual_machine_detail.js @@ -81,58 +81,6 @@ $(document).ready(function() { }) }); - $('#modal-add-ssh-key-button').click(function(e) { - var url = $('#add_ssh_key_to_vm_form').attr('action'); - console.log("Url to POST " + url); - - // Declare a checkbox array - var chkArray = []; - var encoded_data =""; - - // Look for all checkboxes that have a specific class and was checked - $(".chk-ssh-key:checked").each(function() { - chkArray.push($(this).val()); - }); - encoded_data = encodeURIComponent(chkArray.join(",")); - console.log("Encoded data = " + encoded_data); - - fa_icon = $('#ssh-key-modal-icon'); - modal_btn = $('#modal-add-ssh-key-button'); - modal_btn.prop("disabled", true); - modal_btn.html(''); - $.post(url, {selected_key: encoded_data}) - .done(function(data) { - console.log("Request Done"); - modal_btn.prop("disabled", false); - modal_btn.html("OK"); - if (data.status === true) { - fa_icon.html(''); - } else { - fa_icon.html(''); - modal_btn.attr('class', '').addClass('btn btn-danger btn-ok btn-wide'); - } - console.log("title = " + data.msg_title); - console.log("desc = " + data.msg_body); - $('#ssh-key-modal-title').text(data.msg_title); - $('#ssh-key-modal-description').html(data.msg_body); - console.log("Request Done end"); - }) - .fail(function(data) { - console.log("Request Failed"); - console.log("title " + data.msg_title); - console.log("body " + data.msg_body); - modal_btn.attr('class', '').addClass('btn btn-danger btn-ok btn-wide'); - $('#ssh-key-modal-title').text(data.msg_title); - $('#ssh-key-modal-description').html(data.msg_body); - }) - .always(function () { - console.log("changing href to location: " + location); - $('#modal-add-ssh-key-button').unbind('click').click(function () { - location.reload(); - }); - }) - }); - var hash = window.location.hash; hash && $('ul.nav a[href="' + hash + '"]').tab('show'); diff --git a/hosting/templates/hosting/virtual_machine_detail.html b/hosting/templates/hosting/virtual_machine_detail.html index edbc71f9..bf869f91 100644 --- a/hosting/templates/hosting/virtual_machine_detail.html +++ b/hosting/templates/hosting/virtual_machine_detail.html @@ -113,41 +113,6 @@ {% trans "BACK TO LIST" %}
- - -
- {% if keys|length > 0 %} - {% if not virtual_machine.status == 'canceled' %} -
- -
- {% trans "Sorry, there was an unexpected error. Kindly retry." %} -
-
- {% endif %} - {% else %} - - {% endif %} -

{% trans "Support / Contact" %}

diff --git a/opennebula_api/models.py b/opennebula_api/models.py index 761c84b7..478758fc 100644 --- a/opennebula_api/models.py +++ b/opennebula_api/models.py @@ -363,64 +363,6 @@ class OpenNebulaManager(): return vm_terminated - def power_off_vm(self, vm_id): - vm = None - try: - vm = self.get_vm(vm_id) - vm.poweroff_hard() - 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 - - def resume(self, vm_id): - vm = None - try: - vm = self.get_vm(vm_id) - vm.resume() - 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 - - def save_key_in_vm_template(self, vm_id, ssh_key): - """ - Update the template of a given VM and set the ssh key of the user - :param vm_id: the identifier of the VM object - :param ssh_key: a newline(\n) separated ssh key string that needs to be - set in the VM template - :return: - """ - UPDATE_TYPE = 1 - return_value = self.client.call( - 'vm.updateconf', - vm_id, - '%s' % ssh_key, - UPDATE_TYPE - ) - if type(return_value) == int: - logger.debug( - "Saved the key in VM Template success : %s" % return_value) - else: - logger.error( - "Could not save the key in VM Template. %s" % return_value) - - return return_value - def save_key_in_opennebula_user(self, ssh_key, update_type=1): """ Save the given ssh key in OpenNebula user From 9ee1b7c124dd3cd4367198b07ae0939caed91691 Mon Sep 17 00:00:00 2001 From: PCoder Date: Tue, 25 Jun 2019 02:25:17 +0200 Subject: [PATCH 41/44] Make public_key form params mandatory only if existing keys do not exist --- datacenterlight/views.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 7aa6d8a4..914c66eb 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -595,8 +595,12 @@ class OrderConfirmationView(DetailView, FormView): # Check ssh public key and then proceed form = self.get_form() required = 'add_ssh' in self.request.POST - form.fields['name'].required = required - form.fields['public_key'].required = required + + # SSH key is required only if the user doesn't have an existing + # key + if len(get_all_public_keys(self.request.user)) == 0: + form.fields['name'].required = required + form.fields['public_key'].required = required if not form.is_valid(): response = { 'status': False, From 85f7d734424a24dfcd272c36d59f2d6432bdb9dd Mon Sep 17 00:00:00 2001 From: PCoder Date: Tue, 25 Jun 2019 02:32:19 +0200 Subject: [PATCH 42/44] Code cleanup: Remove ssh_key_added_to_vm email templates --- .../hosting/emails/ssh_key_added_to_vm.html | 51 ------------------- .../hosting/emails/ssh_key_added_to_vm.txt | 11 ---- 2 files changed, 62 deletions(-) delete mode 100644 hosting/templates/hosting/emails/ssh_key_added_to_vm.html delete mode 100644 hosting/templates/hosting/emails/ssh_key_added_to_vm.txt diff --git a/hosting/templates/hosting/emails/ssh_key_added_to_vm.html b/hosting/templates/hosting/emails/ssh_key_added_to_vm.html deleted file mode 100644 index 086c3479..00000000 --- a/hosting/templates/hosting/emails/ssh_key_added_to_vm.html +++ /dev/null @@ -1,51 +0,0 @@ -{% load static i18n %} - - - - - - - {% blocktrans %}SSH key(s) {{keys}} added to your VM {{vm_name}}{% endblocktrans %} - - - - - - - - - - - - - - - - - - - - - -
- -
-

{% blocktrans %}SSH keys on your VM {{ vm_name }}{% endblocktrans %}

-
-

- {% blocktrans %}You initiated adding the keys {{keys}} to your VM {{ vm_name }}!{% endblocktrans %} -

-

- {% blocktrans %}This email is to notify you that it was accomplished successfully.{% endblocktrans %} -

-

- {% blocktrans %}You can view your VM detail by clicking the button below.{% endblocktrans %} -

-
- {% trans "View Detail" %} -
-

{% trans "Your Data Center Light Team" %}

-
- - - \ No newline at end of file diff --git a/hosting/templates/hosting/emails/ssh_key_added_to_vm.txt b/hosting/templates/hosting/emails/ssh_key_added_to_vm.txt deleted file mode 100644 index 665936cb..00000000 --- a/hosting/templates/hosting/emails/ssh_key_added_to_vm.txt +++ /dev/null @@ -1,11 +0,0 @@ -{% load i18n %} - -{% blocktrans %}SSH key(s) {{keys}} added to your VM {{vm_name}}{% endblocktrans %} - -{% blocktrans %}You initiated adding the keys {{keys}} to your VM {{ vm_name }}!{% endblocktrans %} -{% blocktrans %}This email is to notify you that it was accomplished successfully.{% endblocktrans %} -{% blocktrans %}You can view your VM detail by clicking the button below.{% endblocktrans %} - -{{ base_url }}{{ vm_detail_url }} - -{% trans "Your Data Center Light Team" %} \ No newline at end of file From 34c917acc266edcf8037c238383ad9f75d31e306 Mon Sep 17 00:00:00 2001 From: PCoder Date: Tue, 25 Jun 2019 03:10:50 +0200 Subject: [PATCH 43/44] Add SSH form to hosting VM buy flow also --- hosting/templates/hosting/order_detail.html | 29 ++++++++++++++++++ hosting/views.py | 33 +++++++++++++++++++-- 2 files changed, 60 insertions(+), 2 deletions(-) diff --git a/hosting/templates/hosting/order_detail.html b/hosting/templates/hosting/order_detail.html index 4a62e9fa..7ab79378 100644 --- a/hosting/templates/hosting/order_detail.html +++ b/hosting/templates/hosting/order_detail.html @@ -198,6 +198,35 @@ {% block submit_btn %}
{% 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 %} +
+ {% for message in messages %} + {{ message }} + {% endfor %} +
+ {% endif %} +
+

 {% trans "Add your public SSH key" %}

+
+
+ {% if keys|length > 0 %} +
Existing keys
+ {% endif %} + {% for key in keys %} + +
+ {% endfor %} +
+ {% for field in form %} + {% bootstrap_field field %} + {% endfor %}
{% 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 %}.
diff --git a/hosting/views.py b/hosting/views.py index 2ef5383d..33a8748e 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -838,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: @@ -869,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( @@ -994,6 +1002,27 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView): @method_decorator(decorators) def post(self, request): + # Check ssh public key and then proceed + form = self.get_form() + required = 'add_ssh' in self.request.POST + + # SSH key is required only if the user doesn't have an existing + # key + if len(get_all_public_keys(self.request.user)) == 0: + 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': "
".join([str(v) for k,v in form.errors.items()]), + } + return JsonResponse(response) + + # 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() @@ -1641,7 +1670,7 @@ class VirtualMachineView(LoginRequiredMixin, View): "manager.delete_vm returned False. Hence, error making " "xml-rpc call to delete vm failed." ) - response['text'] = str(_('Error terminating VM ')) + str(vm.id) + response['text'] = str(_('Error terminating VM')) + str(vm.id) else: for t in range(15): try: From feeb102f9265afd722960d27efdfe93eaf81186d Mon Sep 17 00:00:00 2001 From: PCoder Date: Tue, 25 Jun 2019 03:48:29 +0200 Subject: [PATCH 44/44] Do SSH key validation only if the user doesn't have an existing key and the user has input some value in the add ssh key field --- datacenterlight/views.py | 30 +++++++++++++++++------------- hosting/forms.py | 3 ++- hosting/views.py | 22 +++++++++++++--------- 3 files changed, 32 insertions(+), 23 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 914c66eb..76f50aec 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -588,19 +588,18 @@ class OrderConfirmationView(DetailView, FormView): return render(request, self.template_name, context) def post(self, request, *args, **kwargs): - user = request.session.get('user') - stripe_api_cus_id = request.session.get('customer') - stripe_utils = StripeUtils() - # Check ssh public key and then proceed form = self.get_form() - required = 'add_ssh' in self.request.POST + required = True - # SSH key is required only if the user doesn't have an existing - # key - if len(get_all_public_keys(self.request.user)) == 0: - form.fields['name'].required = required - form.fields['public_key'].required = required + # 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, @@ -609,9 +608,14 @@ class OrderConfirmationView(DetailView, FormView): } return JsonResponse(response) - # We have a valid SSH key from the user, save it in opennebula and db - # and proceed further - form.save() + 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() if 'token' in request.session: card_details = stripe_utils.get_cards_details_from_token( diff --git a/hosting/forms.py b/hosting/forms.py index 576a1996..797bc700 100644 --- a/hosting/forms.py +++ b/hosting/forms.py @@ -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() diff --git a/hosting/views.py b/hosting/views.py index 33a8748e..f5146fbf 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -1004,13 +1004,16 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView, FormView): def post(self, request): # Check ssh public key and then proceed form = self.get_form() - required = 'add_ssh' in self.request.POST + required = True - # SSH key is required only if the user doesn't have an existing - # key - if len(get_all_public_keys(self.request.user)) == 0: - form.fields['name'].required = required - form.fields['public_key'].required = required + # 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, @@ -1019,9 +1022,10 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView, FormView): } return JsonResponse(response) - # We have a valid SSH key from the user, save it in opennebula and db - # and proceed further - form.save() + 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')