Merged upstream master into task/3772/hosting_billing_monthly_subscription
This commit is contained in:
		
				commit
				
					
						805f1129e8
					
				
			
		
					 11 changed files with 371 additions and 75 deletions
				
			
		|  | @ -115,8 +115,8 @@ class CeleryTaskTestCase(TestCase): | |||
|                 'response_object').stripe_plan_id}]) | ||||
|         stripe_subscription_obj = subscription_result.get('response_object') | ||||
|         # Check if the subscription was approved and is active | ||||
|         if stripe_subscription_obj is None or \ | ||||
|                         stripe_subscription_obj.status != 'active': | ||||
|         if stripe_subscription_obj is None \ | ||||
|                 or stripe_subscription_obj.status != 'active': | ||||
|             msg = subscription_result.get('error') | ||||
|             raise Exception("Creating subscription failed: {}".format(msg)) | ||||
| 
 | ||||
|  |  | |||
|  | @ -559,7 +559,7 @@ CELERY_RESULT_BACKEND = env('CELERY_RESULT_BACKEND') | |||
| CELERY_ACCEPT_CONTENT = ['application/json'] | ||||
| CELERY_TASK_SERIALIZER = 'json' | ||||
| CELERY_RESULT_SERIALIZER = 'json' | ||||
| CELERY_TIMEZONE = 'Europe/Zurich' | ||||
| #CELERY_TIMEZONE = 'Europe/Zurich' | ||||
| CELERY_MAX_RETRIES = int_env('CELERY_MAX_RETRIES', 5) | ||||
| 
 | ||||
| ENABLE_DEBUG_LOGGING = bool_env('ENABLE_DEBUG_LOGGING') | ||||
|  | @ -585,6 +585,9 @@ if ENABLE_DEBUG_LOGGING: | |||
|         }, | ||||
|     } | ||||
| 
 | ||||
| TEST_MANAGE_SSH_KEY_PUBKEY = env('TEST_MANAGE_SSH_KEY_PUBKEY') | ||||
| TEST_MANAGE_SSH_KEY_HOST = env('TEST_MANAGE_SSH_KEY_HOST') | ||||
| 
 | ||||
| DEBUG = bool_env('DEBUG') | ||||
| 
 | ||||
| if DEBUG: | ||||
|  |  | |||
|  | @ -1,16 +1,22 @@ | |||
| import datetime | ||||
| import logging | ||||
| import subprocess | ||||
| 
 | ||||
| import tempfile | ||||
| from django import forms | ||||
| from membership.models import CustomUser | ||||
| from django.contrib.auth import authenticate | ||||
| 
 | ||||
| from django.utils.translation import ugettext_lazy as _ | ||||
| 
 | ||||
| from membership.models import CustomUser | ||||
| from utils.hosting_utils import get_all_public_keys | ||||
| from .models import UserHostingKey | ||||
| 
 | ||||
| logger = logging.getLogger(__name__) | ||||
| 
 | ||||
| 
 | ||||
| def generate_ssh_key_name(): | ||||
|     return 'dcl-generated-key-' + datetime.datetime.now().strftime('%m%d%y%H%M') | ||||
|     return 'dcl-generated-key-' + datetime.datetime.now().strftime( | ||||
|         '%m%d%y%H%M') | ||||
| 
 | ||||
| 
 | ||||
| class HostingUserLoginForm(forms.Form): | ||||
|  | @ -38,9 +44,7 @@ class HostingUserLoginForm(forms.Form): | |||
|             CustomUser.objects.get(email=email) | ||||
|             return email | ||||
|         except CustomUser.DoesNotExist: | ||||
|             raise forms.ValidationError("User does not exist") | ||||
|         else: | ||||
|             return email | ||||
|             raise forms.ValidationError(_("User does not exist")) | ||||
| 
 | ||||
| 
 | ||||
| class HostingUserSignupForm(forms.ModelForm): | ||||
|  | @ -51,7 +55,8 @@ class HostingUserSignupForm(forms.ModelForm): | |||
|         model = CustomUser | ||||
|         fields = ['name', 'email', 'password'] | ||||
|         widgets = { | ||||
|             'name': forms.TextInput(attrs={'placeholder': 'Enter your name or company name'}), | ||||
|             'name': forms.TextInput( | ||||
|                 attrs={'placeholder': 'Enter your name or company name'}), | ||||
|         } | ||||
| 
 | ||||
|     def clean_confirm_password(self): | ||||
|  | @ -65,19 +70,55 @@ class HostingUserSignupForm(forms.ModelForm): | |||
| class UserHostingKeyForm(forms.ModelForm): | ||||
|     private_key = forms.CharField(widget=forms.HiddenInput(), required=False) | ||||
|     public_key = forms.CharField(widget=forms.Textarea( | ||||
|         attrs={'class': 'form_public_key', 'placeholder': _('Paste here your public key')}), | ||||
|         attrs={'class': 'form_public_key', | ||||
|                'placeholder': _('Paste here your public key')}), | ||||
|         required=False, | ||||
|     ) | ||||
|     user = forms.models.ModelChoiceField(queryset=CustomUser.objects.all(), | ||||
|                                          required=False, widget=forms.HiddenInput()) | ||||
|                                          required=False, | ||||
|                                          widget=forms.HiddenInput()) | ||||
|     name = forms.CharField(required=False, widget=forms.TextInput( | ||||
|         attrs={'class': 'form_key_name', 'placeholder': _('Give a name to your key')})) | ||||
|         attrs={'class': 'form_key_name', | ||||
|                'placeholder': _('Give a name to your key')})) | ||||
| 
 | ||||
|     def __init__(self, *args, **kwargs): | ||||
|         self.request = kwargs.pop("request") | ||||
|         super(UserHostingKeyForm, self).__init__(*args, **kwargs) | ||||
|         self.fields['name'].label = _('Key name') | ||||
| 
 | ||||
|     def clean_public_key(self): | ||||
|         """ | ||||
|         Validates a public ssh key using `ssh-keygen -lf key.pub` | ||||
|         Also checks if a given key already exists in the database and | ||||
|         alerts the user of it. | ||||
|         :return: | ||||
|         """ | ||||
|         if 'generate' in self.request.POST: | ||||
|             return self.data.get('public_key') | ||||
|         KEY_ERROR_MESSAGE = _("Please input a proper SSH key") | ||||
|         openssh_pubkey_str = self.data.get('public_key').strip() | ||||
| 
 | ||||
|         if openssh_pubkey_str in get_all_public_keys(self.request.user): | ||||
|             key_name = UserHostingKey.objects.filter( | ||||
|                 user_id=self.request.user.id, | ||||
|                 public_key=openssh_pubkey_str).first().name | ||||
|             KEY_EXISTS_MESSAGE = _( | ||||
|                 "This key exists already with the name \"%(name)s\"") % { | ||||
|                                      'name': key_name} | ||||
|             raise forms.ValidationError(KEY_EXISTS_MESSAGE) | ||||
| 
 | ||||
|         with tempfile.NamedTemporaryFile(delete=True) as tmp_public_key_file: | ||||
|             tmp_public_key_file.write(openssh_pubkey_str.encode('utf-8')) | ||||
|             tmp_public_key_file.flush() | ||||
|             try: | ||||
|                 subprocess.check_output( | ||||
|                     ['ssh-keygen', '-lf', tmp_public_key_file.name]) | ||||
|             except subprocess.CalledProcessError as cpe: | ||||
|                 logger.debug( | ||||
|                     "Not a correct ssh format {error}".format(error=str(cpe))) | ||||
|                 raise forms.ValidationError(KEY_ERROR_MESSAGE) | ||||
|         return openssh_pubkey_str | ||||
| 
 | ||||
|     def clean_name(self): | ||||
|         return self.data.get('name') | ||||
| 
 | ||||
|  |  | |||
|  | @ -8,7 +8,7 @@ msgid "" | |||
| msgstr "" | ||||
| "Project-Id-Version: PACKAGE VERSION\n" | ||||
| "Report-Msgid-Bugs-To: \n" | ||||
| "POT-Creation-Date: 2017-09-09 06:04+0000\n" | ||||
| "POT-Creation-Date: 2017-09-14 12:27+0000\n" | ||||
| "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" | ||||
| "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" | ||||
| "Language-Team: LANGUAGE <LL@li.org>\n" | ||||
|  | @ -24,6 +24,9 @@ msgstr "Dein Benutzername und/oder Dein Passwort ist falsch." | |||
| msgid "Your account is not activated yet." | ||||
| msgstr "Dein Account wurde noch nicht aktiviert." | ||||
| 
 | ||||
| msgid "User does not exist" | ||||
| msgstr "Der Benutzer existiert nicht" | ||||
| 
 | ||||
| msgid "Paste here your public key" | ||||
| msgstr "Füge deinen Public Key ein" | ||||
| 
 | ||||
|  | @ -33,6 +36,13 @@ msgstr "Gebe deinem SSH-Key einen Name" | |||
| msgid "Key name" | ||||
| msgstr "Key-Name" | ||||
| 
 | ||||
| msgid "Please input a proper SSH key" | ||||
| msgstr "Bitte verwende einen gültigen SSH-Key" | ||||
| 
 | ||||
| #, python-format | ||||
| msgid "This key exists already with the name \"%(name)s\"" | ||||
| msgstr "Der SSH-Key mit dem Name \"%(name)s\" existiert bereits" | ||||
| 
 | ||||
| msgid "All Rights Reserved" | ||||
| msgstr "Alle Rechte vorbehalten" | ||||
| 
 | ||||
|  |  | |||
|  | @ -11,7 +11,6 @@ from .views import ( | |||
|     SSHKeyChoiceView, DashboardView, SettingsView) | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| urlpatterns = [ | ||||
|     url(r'index/?$', IndexView.as_view(), name='index'), | ||||
|     url(r'django/?$', DjangoHostingView.as_view(), name='djangohosting'), | ||||
|  |  | |||
|  | @ -1,3 +1,4 @@ | |||
| import logging | ||||
| import uuid | ||||
| 
 | ||||
| from django.conf import settings | ||||
|  | @ -17,7 +18,7 @@ from django.views.generic import View, CreateView, FormView, ListView, \ | |||
|     DetailView, \ | ||||
|     DeleteView, TemplateView, UpdateView | ||||
| from guardian.mixins import PermissionRequiredMixin | ||||
| from oca.pool import WrongNameError, WrongIdError | ||||
| from oca.pool import WrongIdError | ||||
| from stored_messages.api import mark_read | ||||
| from stored_messages.models import Message | ||||
| from stored_messages.settings import stored_messages_settings | ||||
|  | @ -38,8 +39,11 @@ from .forms import HostingUserSignupForm, HostingUserLoginForm, \ | |||
| from .mixins import ProcessVMSelectionMixin | ||||
| from .models import HostingOrder, HostingBill, HostingPlan, UserHostingKey | ||||
| 
 | ||||
| CONNECTION_ERROR = "Your VMs cannot be displayed at the moment due to a backend \ | ||||
|                     connection error. please try again in a few minutes." | ||||
| logger = logging.getLogger(__name__) | ||||
| 
 | ||||
| CONNECTION_ERROR = "Your VMs cannot be displayed at the moment due to a \ | ||||
|                     backend connection error. please try again in a few \ | ||||
|                     minutes." | ||||
| 
 | ||||
| 
 | ||||
| class DashboardView(View): | ||||
|  | @ -370,17 +374,14 @@ class SSHKeyDeleteView(LoginRequiredMixin, DeleteView): | |||
| 
 | ||||
|     def delete(self, request, *args, **kwargs): | ||||
|         owner = self.request.user | ||||
|         manager = OpenNebulaManager() | ||||
|         manager = OpenNebulaManager( | ||||
|             email=owner.email, | ||||
|             password=owner.password | ||||
|         ) | ||||
|         pk = self.kwargs.get('pk') | ||||
|         # Get user ssh key | ||||
|         public_key = UserHostingKey.objects.get(pk=pk).public_key | ||||
|         # Add ssh key to user | ||||
|         try: | ||||
|             manager.remove_public_key(user=owner, public_key=public_key) | ||||
|         except ConnectionError: | ||||
|             pass | ||||
|         except WrongNameError: | ||||
|             pass | ||||
|         manager.manage_public_key([{'value': public_key, 'state': False}]) | ||||
| 
 | ||||
|         return super(SSHKeyDeleteView, self).delete(request, *args, **kwargs) | ||||
| 
 | ||||
|  | @ -421,6 +422,13 @@ class SSHKeyChoiceView(LoginRequiredMixin, View): | |||
|             user=request.user, public_key=public_key, name=name) | ||||
|         filename = name + '_' + str(uuid.uuid4())[:8] + '_private.pem' | ||||
|         ssh_key.private_key.save(filename, content) | ||||
|         owner = self.request.user | ||||
|         manager = OpenNebulaManager( | ||||
|             email=owner.email, | ||||
|             password=owner.password | ||||
|         ) | ||||
|         public_key_str = public_key.decode() | ||||
|         manager.manage_public_key([{'value': public_key_str, 'state': True}]) | ||||
|         return redirect(reverse_lazy('hosting:ssh_keys'), foo='bar') | ||||
| 
 | ||||
| 
 | ||||
|  | @ -465,23 +473,17 @@ class SSHKeyCreateView(LoginRequiredMixin, FormView): | |||
|             }) | ||||
| 
 | ||||
|         owner = self.request.user | ||||
|         manager = OpenNebulaManager() | ||||
| 
 | ||||
|         # Get user ssh key | ||||
|         public_key = str(form.cleaned_data.get('public_key', '')) | ||||
|         # Add ssh key to user | ||||
|         try: | ||||
|             manager.add_public_key( | ||||
|                 user=owner, public_key=public_key, merge=True) | ||||
|         except ConnectionError: | ||||
|             pass | ||||
|         except WrongNameError: | ||||
|             pass | ||||
| 
 | ||||
|         manager = OpenNebulaManager( | ||||
|             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}]) | ||||
|         return HttpResponseRedirect(self.success_url) | ||||
| 
 | ||||
|     def post(self, request, *args, **kwargs): | ||||
|         print(self.request.POST.dict()) | ||||
|         form = self.get_form() | ||||
|         required = 'add_ssh' in self.request.POST | ||||
|         form.fields['name'].required = required | ||||
|  | @ -920,7 +922,8 @@ class VirtualMachineView(LoginRequiredMixin, View): | |||
|                 'order': HostingOrder.objects.get( | ||||
|                     vm_id=serializer.data['vm_id']) | ||||
|             } | ||||
|         except: | ||||
|         except Exception as ex: | ||||
|             logger.debug("Exception generated {}".format(str(ex))) | ||||
|             pass | ||||
| 
 | ||||
|         return render(request, self.template_name, context) | ||||
|  |  | |||
|  | @ -1,13 +1,14 @@ | |||
| import oca | ||||
| import socket | ||||
| import logging | ||||
| import socket | ||||
| 
 | ||||
| from oca.pool import WrongNameError, WrongIdError | ||||
| from oca.exceptions import OpenNebulaException | ||||
| 
 | ||||
| import oca | ||||
| from django.conf import settings | ||||
| from oca.exceptions import OpenNebulaException | ||||
| from oca.pool import WrongNameError, WrongIdError | ||||
| 
 | ||||
| from hosting.models import HostingOrder | ||||
| from utils.models import CustomUser | ||||
| from utils.tasks import save_ssh_key, save_ssh_key_error_handler | ||||
| from .exceptions import KeyExistsError, UserExistsError, UserCredentialError | ||||
| 
 | ||||
| logger = logging.getLogger(__name__) | ||||
|  | @ -17,7 +18,8 @@ class OpenNebulaManager(): | |||
|     """This class represents an opennebula manager.""" | ||||
| 
 | ||||
|     def __init__(self, email=None, password=None): | ||||
| 
 | ||||
|         self.email = email | ||||
|         self.password = password | ||||
|         # Get oneadmin client | ||||
|         self.oneadmin_client = self._get_opennebula_client( | ||||
|             settings.OPENNEBULA_USERNAME, | ||||
|  | @ -122,14 +124,17 @@ class OpenNebulaManager(): | |||
| 
 | ||||
|         except WrongNameError: | ||||
|             user_id = self.oneadmin_client.call(oca.User.METHODS['allocate'], | ||||
|                                                 user.email, user.password, 'core') | ||||
|             logger.debug('Created a user for CustomObject: {user} with user id = {u_id}', | ||||
|                                                 user.email, user.password, | ||||
|                                                 'core') | ||||
|             logger.debug( | ||||
|                 'Created a user for CustomObject: {user} with user id = {u_id}', | ||||
|                 user=user, | ||||
|                 u_id=user_id | ||||
|             ) | ||||
|             return user_id | ||||
|         except ConnectionRefusedError: | ||||
|             logger.error('Could not connect to host: {host} via protocol {protocol}'.format( | ||||
|             logger.error( | ||||
|                 'Could not connect to host: {host} via protocol {protocol}'.format( | ||||
|                     host=settings.OPENNEBULA_DOMAIN, | ||||
|                     protocol=settings.OPENNEBULA_PROTOCOL) | ||||
|             ) | ||||
|  | @ -141,7 +146,8 @@ class OpenNebulaManager(): | |||
|             opennebula_user = user_pool.get_by_name(email) | ||||
|             return opennebula_user | ||||
|         except WrongNameError as wrong_name_err: | ||||
|             opennebula_user = self.oneadmin_client.call(oca.User.METHODS['allocate'], email, | ||||
|             opennebula_user = self.oneadmin_client.call( | ||||
|                 oca.User.METHODS['allocate'], email, | ||||
|                 password, 'core') | ||||
|             logger.debug( | ||||
|                 "User {0} does not exist. Created the user. User id = {1}", | ||||
|  | @ -150,7 +156,8 @@ class OpenNebulaManager(): | |||
|             ) | ||||
|             return opennebula_user | ||||
|         except ConnectionRefusedError: | ||||
|             logger.info('Could not connect to host: {host} via protocol {protocol}'.format( | ||||
|             logger.info( | ||||
|                 'Could not connect to host: {host} via protocol {protocol}'.format( | ||||
|                     host=settings.OPENNEBULA_DOMAIN, | ||||
|                     protocol=settings.OPENNEBULA_PROTOCOL) | ||||
|             ) | ||||
|  | @ -161,7 +168,8 @@ class OpenNebulaManager(): | |||
|             user_pool = oca.UserPool(self.oneadmin_client) | ||||
|             user_pool.info() | ||||
|         except ConnectionRefusedError: | ||||
|             logger.info('Could not connect to host: {host} via protocol {protocol}'.format( | ||||
|             logger.info( | ||||
|                 'Could not connect to host: {host} via protocol {protocol}'.format( | ||||
|                     host=settings.OPENNEBULA_DOMAIN, | ||||
|                     protocol=settings.OPENNEBULA_PROTOCOL) | ||||
|             ) | ||||
|  | @ -183,7 +191,8 @@ class OpenNebulaManager(): | |||
|                 raise ConnectionRefusedError | ||||
| 
 | ||||
|         except ConnectionRefusedError: | ||||
|             logger.info('Could not connect to host: {host} via protocol {protocol}'.format( | ||||
|             logger.info( | ||||
|                 'Could not connect to host: {host} via protocol {protocol}'.format( | ||||
|                     host=settings.OPENNEBULA_DOMAIN, | ||||
|                     protocol=settings.OPENNEBULA_PROTOCOL) | ||||
|             ) | ||||
|  | @ -208,6 +217,33 @@ class OpenNebulaManager(): | |||
|         except: | ||||
|             raise ConnectionRefusedError | ||||
| 
 | ||||
|     def get_primary_ipv4(self, vm_id): | ||||
|         """ | ||||
|         Returns the primary IPv4 of the given vm. | ||||
|         To be changed later. | ||||
| 
 | ||||
|         :return: An IP address string, if it exists else returns None | ||||
|         """ | ||||
|         all_ipv4s = self.get_vm_ipv4_addresses(vm_id) | ||||
|         if len(all_ipv4s) > 0: | ||||
|             return all_ipv4s[0] | ||||
|         else: | ||||
|             return None | ||||
| 
 | ||||
|     def get_vm_ipv4_addresses(self, vm_id): | ||||
|         """ | ||||
|         Returns a list of IPv4 addresses of the given vm | ||||
| 
 | ||||
|         :param vm_id: The ID of the vm | ||||
|         :return: | ||||
|         """ | ||||
|         ipv4s = [] | ||||
|         vm = self.get_vm(vm_id) | ||||
|         for nic in vm.template.nics: | ||||
|             if hasattr(nic, 'ip'): | ||||
|                 ipv4s.append(nic.ip) | ||||
|         return ipv4s | ||||
| 
 | ||||
|     def create_vm(self, template_id, specs, ssh_key=None, vm_name=None): | ||||
| 
 | ||||
|         template = self.get_template(template_id) | ||||
|  | @ -258,7 +294,8 @@ class OpenNebulaManager(): | |||
| 
 | ||||
|         vm_specs += "<CONTEXT>" | ||||
|         if ssh_key: | ||||
|             vm_specs += "<SSH_PUBLIC_KEY>{ssh}</SSH_PUBLIC_KEY>".format(ssh=ssh_key) | ||||
|             vm_specs += "<SSH_PUBLIC_KEY>{ssh}</SSH_PUBLIC_KEY>".format( | ||||
|                 ssh=ssh_key) | ||||
|         vm_specs += """<NETWORK>YES</NETWORK> | ||||
|                    </CONTEXT> | ||||
|                 </TEMPLATE> | ||||
|  | @ -312,7 +349,9 @@ class OpenNebulaManager(): | |||
|             template_pool.info() | ||||
|             return template_pool | ||||
|         except ConnectionRefusedError: | ||||
|             logger.info('Could not connect to host: {host} via protocol {protocol}'.format( | ||||
|             logger.info( | ||||
|                 """Could not connect to host: {host} via protocol | ||||
|                  {protocol}""".format( | ||||
|                     host=settings.OPENNEBULA_DOMAIN, | ||||
|                     protocol=settings.OPENNEBULA_PROTOCOL) | ||||
|             ) | ||||
|  | @ -347,7 +386,8 @@ class OpenNebulaManager(): | |||
|         except: | ||||
|             raise ConnectionRefusedError | ||||
| 
 | ||||
|     def create_template(self, name, cores, memory, disk_size, core_price, memory_price, | ||||
|     def create_template(self, name, cores, memory, disk_size, core_price, | ||||
|                         memory_price, | ||||
|                         disk_size_price, ssh=''): | ||||
|         """Create and add a new template to opennebula. | ||||
|         :param name:      A string representation describing the template. | ||||
|  | @ -490,3 +530,57 @@ class OpenNebulaManager(): | |||
| 
 | ||||
|         except ConnectionError: | ||||
|             raise | ||||
| 
 | ||||
|     def manage_public_key(self, keys, hosts=None, countdown=0): | ||||
|         """ | ||||
|         A function that manages the supplied keys in the | ||||
|         authorized_keys file of the given list of hosts. If hosts | ||||
|         parameter is not supplied, all hosts of this customer | ||||
|         will be configured with the supplied keys | ||||
| 
 | ||||
|         :param keys: A list of ssh keys that are to be added/removed | ||||
|                      A key should be a dict of the form | ||||
|                      { | ||||
|                        'value': 'sha-.....', # public key as string | ||||
|                        'state': True         # whether key is to be added or | ||||
|                      }                       # removed | ||||
|         :param hosts: A list of hosts IP addresses | ||||
|         :param countdown: Parameter to be passed to celery apply_async | ||||
|                Allows to delay a task by `countdown` number of seconds | ||||
|         :return: | ||||
|         """ | ||||
|         if hosts is None: | ||||
|             hosts = self.get_all_hosts() | ||||
| 
 | ||||
|         if len(hosts) > 0 and len(keys) > 0: | ||||
|             save_ssh_key.apply_async((hosts, keys), countdown=countdown, | ||||
|                                      link_error=save_ssh_key_error_handler.s()) | ||||
|         else: | ||||
|             logger.debug( | ||||
|                 "Keys and/or hosts are empty, so not managing any keys") | ||||
| 
 | ||||
|     def get_all_hosts(self): | ||||
|         """ | ||||
|         A utility function to obtain all hosts of this owner | ||||
|         :return: A list of hosts IP addresses, empty if none exist | ||||
|         """ | ||||
|         owner = CustomUser.objects.filter( | ||||
|             email=self.email).first() | ||||
|         all_orders = HostingOrder.objects.filter(customer__user=owner) | ||||
|         hosts = [] | ||||
|         if len(all_orders) > 0: | ||||
|             logger.debug("The user {} has 1 or more VMs. We need to configure " | ||||
|                          "the ssh keys.".format(self.email)) | ||||
|             for order in all_orders: | ||||
|                 try: | ||||
|                     vm = self.get_vm(order.vm_id) | ||||
|                     for nic in vm.template.nics: | ||||
|                         if hasattr(nic, 'ip'): | ||||
|                             hosts.append(nic.ip) | ||||
|                 except WrongIdError: | ||||
|                     logger.debug( | ||||
|                         "VM with ID {} does not exist".format(order.vm_id)) | ||||
|         else: | ||||
|             logger.debug("The user {} has no VMs. We don't need to configure " | ||||
|                          "the ssh keys.".format(self.email)) | ||||
|         return hosts | ||||
|  |  | |||
|  | @ -96,3 +96,5 @@ pyflakes==1.5.0 | |||
| billiard==3.5.0.3 | ||||
| amqp==2.2.1 | ||||
| vine==1.1.4 | ||||
| #git+https://github.com/ungleich/cdist.git#egg=cdist | ||||
| file:///home/app/cdist#egg=cdist | ||||
|  |  | |||
							
								
								
									
										11
									
								
								utils/hosting_utils.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								utils/hosting_utils.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,11 @@ | |||
| from hosting.models import UserHostingKey | ||||
| 
 | ||||
| 
 | ||||
| def get_all_public_keys(customer): | ||||
|     """ | ||||
|     Returns all the public keys of the user | ||||
|     :param customer: The customer whose public keys are needed | ||||
|     :return: A list of public keys | ||||
|     """ | ||||
|     return UserHostingKey.objects.filter(user_id=customer.id).values_list( | ||||
|         "public_key", flat=True) | ||||
|  | @ -1,8 +1,14 @@ | |||
| import tempfile | ||||
| 
 | ||||
| import cdist | ||||
| from cdist.integration import configure_hosts_simple | ||||
| from celery.result import AsyncResult | ||||
| from celery.utils.log import get_task_logger | ||||
| from django.conf import settings | ||||
| from dynamicweb.celery import app | ||||
| from django.core.mail import EmailMessage | ||||
| 
 | ||||
| from dynamicweb.celery import app | ||||
| 
 | ||||
| logger = get_task_logger(__name__) | ||||
| 
 | ||||
| 
 | ||||
|  | @ -18,3 +24,72 @@ def send_plain_email_task(self, email_data): | |||
|     """ | ||||
|     email = EmailMessage(**email_data) | ||||
|     email.send() | ||||
| 
 | ||||
| 
 | ||||
| @app.task(bind=True, max_retries=settings.CELERY_MAX_RETRIES) | ||||
| def save_ssh_key(self, hosts, keys): | ||||
|     """ | ||||
|     Saves ssh key into the VMs of a user using cdist | ||||
| 
 | ||||
|     :param hosts: A list of hosts to be configured | ||||
|     :param keys: A list of keys to be added. A key should be dict of the | ||||
|            form    { | ||||
|                        'value': 'sha-.....', # public key as string | ||||
|                        'state': True         # whether key is to be added or | ||||
|                     }                        # removed | ||||
|     """ | ||||
|     logger.debug("""Running save_ssh_key task for | ||||
|                     Hosts: {hosts_str} | ||||
|                     Keys: {keys_str}""".format(hosts_str=", ".join(hosts), | ||||
|                                                keys_str=", ".join([ | ||||
|                                                    "{value}->{state}".format( | ||||
|                                                        value=key.get('value'), | ||||
|                                                        state=str( | ||||
|                                                            key.get('state'))) | ||||
|                                                    for key in keys])) | ||||
|                  ) | ||||
|     return_value = True | ||||
|     with tempfile.NamedTemporaryFile(delete=True) as tmp_manifest: | ||||
|         # Generate manifest to be used for configuring the hosts | ||||
|         lines_list = [ | ||||
|             '  --key "{key}" --state {state} \\\n'.format( | ||||
|                 key=key['value'], | ||||
|                 state='present' if key['state'] else 'absent' | ||||
|             ).encode('utf-8') | ||||
|             for key in keys] | ||||
|         lines_list.insert(0, b'__ssh_authorized_keys root \\\n') | ||||
|         tmp_manifest.writelines(lines_list) | ||||
|         tmp_manifest.flush() | ||||
|         try: | ||||
|             configure_hosts_simple(hosts, | ||||
|                                    tmp_manifest.name, | ||||
|                                    verbose=cdist.argparse.VERBOSE_TRACE) | ||||
|         except Exception as cdist_exception: | ||||
|             logger.error(cdist_exception) | ||||
|             return_value = False | ||||
|             email_data = { | ||||
|                 'subject': "celery save_ssh_key error - task id {0}".format( | ||||
|                     self.request.id.__str__()), | ||||
|                 'from_email': settings.DCL_SUPPORT_FROM_ADDRESS, | ||||
|                 'to': ['info@ungleich.ch'], | ||||
|                 'body': "Task Id: {0}\nResult: {1}\nTraceback: {2}".format( | ||||
|                     self.request.id.__str__(), False, str(cdist_exception)), | ||||
|             } | ||||
|             send_plain_email_task(email_data) | ||||
|     return return_value | ||||
| 
 | ||||
| 
 | ||||
| @app.task | ||||
| def save_ssh_key_error_handler(uuid): | ||||
|     result = AsyncResult(uuid) | ||||
|     exc = result.get(propagate=False) | ||||
|     logger.error('Task {0} raised exception: {1!r}\n{2!r}'.format( | ||||
|         uuid, exc, result.traceback)) | ||||
|     email_data = { | ||||
|         'subject': "[celery error] Save SSH key error {0}".format(uuid), | ||||
|         'from_email': settings.DCL_SUPPORT_FROM_ADDRESS, | ||||
|         'to': ['info@ungleich.ch'], | ||||
|         'body': "Task Id: {0}\nResult: {1}\nTraceback: {2}".format( | ||||
|             uuid, exc, result.traceback), | ||||
|     } | ||||
|     send_plain_email_task(email_data) | ||||
|  |  | |||
|  | @ -1,16 +1,20 @@ | |||
| import uuid | ||||
| from time import sleep | ||||
| from unittest.mock import patch | ||||
| 
 | ||||
| import stripe | ||||
| from celery.result import AsyncResult | ||||
| from django.conf import settings | ||||
| from django.http.request import HttpRequest | ||||
| from django.test import Client | ||||
| from django.test import TestCase | ||||
| from django.test import TestCase, override_settings | ||||
| from unittest import skipIf | ||||
| from model_mommy import mommy | ||||
| 
 | ||||
| from datacenterlight.models import StripePlan | ||||
| from membership.models import StripeCustomer | ||||
| from utils.stripe_utils import StripeUtils | ||||
| from django.conf import settings | ||||
| from .tasks import save_ssh_key | ||||
| 
 | ||||
| 
 | ||||
| class BaseTestCase(TestCase): | ||||
|  | @ -235,3 +239,57 @@ class StripePlanTestCase(TestStripeCustomerDescription): | |||
|                 'response_object').stripe_plan_id}]) | ||||
|         self.assertIsNone(result.get('response_object'), None) | ||||
|         self.assertIsNotNone(result.get('error')) | ||||
| 
 | ||||
| 
 | ||||
| class SaveSSHKeyTestCase(TestCase): | ||||
|     """ | ||||
|     A test case to test the celery save_ssh_key task | ||||
|     """ | ||||
| 
 | ||||
|     @override_settings( | ||||
|         task_eager_propagates=True, | ||||
|         task_always_eager=True, | ||||
|     ) | ||||
|     def setUp(self): | ||||
|         self.public_key = settings.TEST_MANAGE_SSH_KEY_PUBKEY | ||||
|         self.hosts = settings.TEST_MANAGE_SSH_KEY_HOST | ||||
| 
 | ||||
|     @skipIf(settings.TEST_MANAGE_SSH_KEY_PUBKEY is None or | ||||
|             settings.TEST_MANAGE_SSH_KEY_PUBKEY == "" or | ||||
|             settings.TEST_MANAGE_SSH_KEY_HOST is None or | ||||
|             settings.TEST_MANAGE_SSH_KEY_HOST is "", | ||||
|             """Skipping test_save_ssh_key_add because either host | ||||
|              or public key were not specified or were empty""") | ||||
|     def test_save_ssh_key_add(self): | ||||
|         async_task = save_ssh_key.delay([self.hosts], | ||||
|                                         [{'value': self.public_key, | ||||
|                                           'state': True}]) | ||||
|         save_ssh_key_result = None | ||||
|         for i in range(0, 10): | ||||
|             sleep(5) | ||||
|             res = AsyncResult(async_task.task_id) | ||||
|             if type(res.result) is bool: | ||||
|                 save_ssh_key_result = res.result | ||||
|                 break | ||||
|         self.assertIsNotNone(save_ssh_key, "save_ssh_key_result is None") | ||||
|         self.assertTrue(save_ssh_key_result, "save_ssh_key_result is False") | ||||
| 
 | ||||
|     @skipIf(settings.TEST_MANAGE_SSH_KEY_PUBKEY is None or | ||||
|             settings.TEST_MANAGE_SSH_KEY_PUBKEY == "" or | ||||
|             settings.TEST_MANAGE_SSH_KEY_HOST is None or | ||||
|             settings.TEST_MANAGE_SSH_KEY_HOST is "", | ||||
|             """Skipping test_save_ssh_key_add because either host | ||||
|              or public key were not specified or were empty""") | ||||
|     def test_save_ssh_key_remove(self): | ||||
|         async_task = save_ssh_key.delay([self.hosts], | ||||
|                                         [{'value': self.public_key, | ||||
|                                           'state': False}]) | ||||
|         save_ssh_key_result = None | ||||
|         for i in range(0, 10): | ||||
|             sleep(5) | ||||
|             res = AsyncResult(async_task.task_id) | ||||
|             if type(res.result) is bool: | ||||
|                 save_ssh_key_result = res.result | ||||
|                 break | ||||
|         self.assertIsNotNone(save_ssh_key, "save_ssh_key_result is None") | ||||
|         self.assertTrue(save_ssh_key_result, "save_ssh_key_result is False") | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue