Merge pull request #298 from Modulos/api/cleanup

Api/cleanup
This commit is contained in:
Levi Velázquez 2017-06-02 09:33:24 -05:00 committed by GitHub
commit 78773eb5c3
12 changed files with 410 additions and 327 deletions

View file

@ -5,3 +5,8 @@
* [datacenterlight] Fix initially shown price * [datacenterlight] Fix initially shown price
1.0.2: 2017-05-28 1.0.2: 2017-05-28
* [datacenterlight] Fixed login redirecting to blank page after logout * [datacenterlight] Fixed login redirecting to blank page after logout
next
* [opennebula_api] Improve testing, add ssh key functions
* [opennebula_api] Remove template views
* [datacenterlight] Allow user to have multiple ssh keys

View file

@ -4,6 +4,7 @@ from django import forms
from membership.models import CustomUser from membership.models import CustomUser
from django.contrib.auth import authenticate from django.contrib.auth import authenticate
from django.utils.translation import ugettext_lazy as _
from utils.stripe_utils import StripeUtils from utils.stripe_utils import StripeUtils
@ -57,21 +58,19 @@ class HostingUserSignupForm(forms.ModelForm):
class UserHostingKeyForm(forms.ModelForm): class UserHostingKeyForm(forms.ModelForm):
private_key = forms.CharField(widget=forms.PasswordInput(), required=False) private_key = forms.CharField(widget=forms.HiddenInput(), required=False)
public_key = forms.CharField(widget=forms.PasswordInput(), required=False) public_key = forms.CharField(widget=forms.Textarea(), required=False,
user = forms.models.ModelChoiceField(queryset=CustomUser.objects.all(), required=False) help_text=_('Paste here your public key'))
name = forms.CharField(required=False) user = forms.models.ModelChoiceField(queryset=CustomUser.objects.all(),
required=False, widget=forms.HiddenInput())
name = forms.CharField(required=True)
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.request = kwargs.pop("request") self.request = kwargs.pop("request")
super(UserHostingKeyForm, self).__init__(*args, **kwargs) super(UserHostingKeyForm, self).__init__(*args, **kwargs)
# self.initial['user'].initial = self.request.user.id
# print(self.fields)
def clean_name(self): def clean_name(self):
return "dcl-priv-key-%s" % ( return self.data.get('name')
''.join(random.choice(string.ascii_lowercase) for i in range(7))
)
def clean_user(self): def clean_user(self):
return self.request.user return self.request.user
@ -90,4 +89,4 @@ class UserHostingKeyForm(forms.ModelForm):
class Meta: class Meta:
model = UserHostingKey model = UserHostingKey
fields = ['user', 'public_key', 'name'] fields = ['user', 'name', 'public_key']

View file

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-05-26 13:35+0000\n" "POT-Creation-Date: 2017-06-01 21:03+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -18,61 +18,64 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: hosting/templates/hosting/base_short.html:67 #: hosting/forms.py:63
msgid "Paste here your public key"
msgstr "Fügen Sie Ihren public key ein"
#: hosting/templates/hosting/base_short.html:68
#: hosting/templates/hosting/base_short.html:139
msgid "My Virtual Machines" msgid "My Virtual Machines"
msgstr "" msgstr ""
#: hosting/templates/hosting/base_short.html:72 #: hosting/templates/hosting/base_short.html:73
#: hosting/templates/hosting/base_short.html:145
#: hosting/templates/hosting/orders.html:12 #: hosting/templates/hosting/orders.html:12
msgid "My Orders" msgid "My Orders"
msgstr "" msgstr ""
#: hosting/templates/hosting/base_short.html:77 #: hosting/templates/hosting/base_short.html:78
#: hosting/templates/hosting/base_short.html:152
msgid "Keys" msgid "Keys"
msgstr "" msgstr ""
#: hosting/templates/hosting/base_short.html:82 #: hosting/templates/hosting/base_short.html:83
#: hosting/templates/hosting/base_short.html:158
msgid "Notifications " msgid "Notifications "
msgstr "" msgstr ""
#: hosting/templates/hosting/base_short.html:89 #: hosting/templates/hosting/base_short.html:90
msgid "Logout" msgid "Logout"
msgstr "" msgstr ""
#: hosting/templates/hosting/base_short.html:94 #: hosting/templates/hosting/base_short.html:95
#: hosting/templates/hosting/base_short.html:136
msgid "How it works" msgid "How it works"
msgstr "" msgstr ""
#: hosting/templates/hosting/base_short.html:97 #: hosting/templates/hosting/base_short.html:98
#: hosting/templates/hosting/base_short.html:139
msgid "Your infrastructure" msgid "Your infrastructure"
msgstr "" msgstr ""
#: hosting/templates/hosting/base_short.html:100 #: hosting/templates/hosting/base_short.html:101
#: hosting/templates/hosting/base_short.html:142
msgid "Our inftrastructure" msgid "Our inftrastructure"
msgstr "" msgstr ""
#: hosting/templates/hosting/base_short.html:103 #: hosting/templates/hosting/base_short.html:104
#: hosting/templates/hosting/base_short.html:145
msgid "Pricing" msgid "Pricing"
msgstr "" msgstr ""
#: hosting/templates/hosting/base_short.html:106 #: hosting/templates/hosting/base_short.html:107
#: hosting/templates/hosting/base_short.html:149
msgid "Contact" msgid "Contact"
msgstr "" msgstr ""
#: hosting/templates/hosting/base_short.html:109 #: hosting/templates/hosting/base_short.html:110
#: hosting/templates/hosting/login.html:29 #: hosting/templates/hosting/login.html:32
#: hosting/templates/hosting/login.html:38 #: hosting/templates/hosting/login.html:41
#: hosting/templates/hosting/reset_password.html:24 #: hosting/templates/hosting/reset_password.html:31
#: hosting/templates/hosting/signup.html:23 #: hosting/templates/hosting/signup.html:30
msgid "Login" msgid "Login"
msgstr "" msgstr ""
#: hosting/templates/hosting/base_short.html:132 #: hosting/templates/hosting/base_short.html:134
msgid "Home" msgid "Home"
msgstr "" msgstr ""
@ -144,7 +147,7 @@ msgid "Customers"
msgstr "" msgstr ""
#: hosting/templates/hosting/bills.html:16 #: hosting/templates/hosting/bills.html:16
#: hosting/templates/hosting/virtual_machine_key.html:45 #: hosting/templates/hosting/virtual_machine_key.html:42
msgid "Name" msgid "Name"
msgstr "" msgstr ""
@ -173,13 +176,13 @@ msgid "Set your new password"
msgstr "" msgstr ""
#: hosting/templates/hosting/confirm_reset_password.html:28 #: hosting/templates/hosting/confirm_reset_password.html:28
#: hosting/templates/hosting/reset_password.html:20 #: hosting/templates/hosting/reset_password.html:22
msgid "Reset" msgid "Reset"
msgstr "" msgstr ""
#: hosting/templates/hosting/confirm_reset_password.html:32 #: hosting/templates/hosting/confirm_reset_password.html:32
#: hosting/templates/hosting/reset_password.html:24 #: hosting/templates/hosting/reset_password.html:28
#: hosting/templates/hosting/signup.html:23 #: hosting/templates/hosting/signup.html:27
msgid "Already have an account ?" msgid "Already have an account ?"
msgstr "" msgstr ""
@ -219,21 +222,27 @@ msgstr ""
msgid "The %(site_name)s team" msgid "The %(site_name)s team"
msgstr "" msgstr ""
#: hosting/templates/hosting/login.html:22 #: hosting/templates/hosting/login.html:10
#: hosting/templates/hosting/reset_password.html:10
#: hosting/templates/hosting/signup.html:9
msgid "Your VM hosted in Switzerland"
msgstr ""
#: hosting/templates/hosting/login.html:26
msgid "You haven been logged out" msgid "You haven been logged out"
msgstr "" msgstr ""
#: hosting/templates/hosting/login.html:44 #: hosting/templates/hosting/login.html:49
msgid "Don't have an account yet ? " msgid "Don't have an account yet ? "
msgstr "" msgstr ""
#: hosting/templates/hosting/login.html:44 #: hosting/templates/hosting/login.html:52
#: hosting/templates/hosting/signup.html:10 #: hosting/templates/hosting/signup.html:13
#: hosting/templates/hosting/signup.html:19 #: hosting/templates/hosting/signup.html:21
msgid "Sign up" msgid "Sign up"
msgstr "" msgstr ""
#: hosting/templates/hosting/login.html:46 #: hosting/templates/hosting/login.html:54
msgid "Forgot your password ? " msgid "Forgot your password ? "
msgstr "" msgstr ""
@ -310,7 +319,7 @@ msgstr ""
#: hosting/templates/hosting/orders.html:19 #: hosting/templates/hosting/orders.html:19
#: hosting/templates/hosting/virtual_machine_detail.html:30 #: hosting/templates/hosting/virtual_machine_detail.html:30
#: hosting/templates/hosting/virtual_machine_key.html:47 #: hosting/templates/hosting/virtual_machine_key.html:44
#: hosting/templates/hosting/virtual_machines.html:31 #: hosting/templates/hosting/virtual_machines.html:31
msgid "Status" msgid "Status"
msgstr "" msgstr ""
@ -344,7 +353,7 @@ msgstr ""
msgid "Delete" msgid "Delete"
msgstr "" msgstr ""
#: hosting/templates/hosting/reset_password.html:11 #: hosting/templates/hosting/reset_password.html:14
msgid "Reset your password" msgid "Reset your password"
msgstr "" msgstr ""
@ -400,50 +409,46 @@ msgstr ""
msgid "Access Key" msgid "Access Key"
msgstr "" msgstr ""
#: hosting/templates/hosting/virtual_machine_key.html:22 #: hosting/templates/hosting/virtual_machine_key.html:25
msgid "Upload your own key. " msgid "Upload your own key. "
msgstr "" msgstr ""
#: hosting/templates/hosting/virtual_machine_key.html:29 #: hosting/templates/hosting/virtual_machine_key.html:29
msgid "Upload Key"
msgstr ""
#: hosting/templates/hosting/virtual_machine_key.html:33
msgid "Or generate a new key pair." msgid "Or generate a new key pair."
msgstr "" msgstr ""
#: hosting/templates/hosting/virtual_machine_key.html:37 #: hosting/templates/hosting/virtual_machine_key.html:31
msgid "Generate Key Pair" msgid "Generate Key Pair"
msgstr "" msgstr ""
#: hosting/templates/hosting/virtual_machine_key.html:46 #: hosting/templates/hosting/virtual_machine_key.html:43
msgid "Created at" msgid "Created at"
msgstr "" msgstr ""
#: hosting/templates/hosting/virtual_machine_key.html:68 #: hosting/templates/hosting/virtual_machine_key.html:66
#: hosting/templates/hosting/virtual_machine_key.html:81 #: hosting/templates/hosting/virtual_machine_key.html:79
msgid "Warning!" msgid "Warning!"
msgstr "" msgstr ""
#: hosting/templates/hosting/virtual_machine_key.html:68 #: hosting/templates/hosting/virtual_machine_key.html:66
msgid "You can download your SSH private key once. Don't lost your key" msgid "You can download your SSH private key once. Don't lost your key"
msgstr "" msgstr ""
#: hosting/templates/hosting/virtual_machine_key.html:76 #: hosting/templates/hosting/virtual_machine_key.html:74
msgid "Copy to Clipboard" msgid "Copy to Clipboard"
msgstr "" msgstr ""
#: hosting/templates/hosting/virtual_machine_key.html:77 #: hosting/templates/hosting/virtual_machine_key.html:75
msgid "Download" msgid "Download"
msgstr "" msgstr ""
#: hosting/templates/hosting/virtual_machine_key.html:81 #: hosting/templates/hosting/virtual_machine_key.html:79
msgid "" msgid ""
"Your SSH private key was already generated and downloaded, if you lost it, " "Your SSH private key was already generated and downloaded, if you lost it, "
"contact us. " "contact us. "
msgstr "" msgstr ""
#: hosting/templates/hosting/virtual_machine_key.html:84 #: hosting/templates/hosting/virtual_machine_key.html:82
msgid "Generate my key" msgid "Generate my key"
msgstr "" msgstr ""

View file

@ -6,7 +6,7 @@
<div class="row"> <div class="row">
<div class="col-md-9 col-md-offset-2"> <div class="col-md-9 col-md-offset-2">
<div class="col-sm-12"> <div class="col-sm-12">
<form method="POST" action="" > <form method="POST" action="" novalidate>
{% csrf_token %} {% csrf_token %}
<h3><i class="fa fa-key" aria-hidden="true"></i>{% trans "Access Key"%} </h3> <h3><i class="fa fa-key" aria-hidden="true"></i>{% trans "Access Key"%} </h3>
{% if messages %} {% if messages %}
@ -15,28 +15,25 @@
<span>{{ message }}</span> <span>{{ message }}</span>
{% endfor %} {% endfor %}
</div> </div>
{% endif %} {% endif %}
<hr/> {% for field in form %}
{% if not user_key %}
<h3> {% bootstrap_field field %}
{% endfor %}
{% buttons %}
<button type="submit" class="btn btn-success">
{% trans "Upload your own key. "%} {% trans "Upload your own key. "%}
</h3> </button>
<div class="form-group"> <br />
<label for="comment">Paste here your public key</label> <br />
<textarea class="form-control" rows="6" name="public_key"></textarea> {% trans "Or generate a new key pair."%} <br />
</div> <br />
<div class="form-group">
<button class="btn btn-success">{% trans "Upload Key"%} </a>
</div>
<h3>
{% trans "Or generate a new key pair."%}
</h3>
<div class="form-group">
<button class="btn btn-success">{% trans "Generate Key Pair"%} </a> <button class="btn btn-success">{% trans "Generate Key Pair"%} </a>
</button>
{% endbuttons %}
<div class="form-group">
</div> </div>
{% else %}
<h5> Use your created key to access to the machine. If you lost it, contact us. </h5> <h5> Use your created key to access to the machine. If you lost it, contact us. </h5>
<table class="table borderless table-hover"> <table class="table borderless table-hover">
<br/> <br/>
@ -49,6 +46,7 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for user_key in keys %}
<tr> <tr>
<td scope="row">{{user_key.name}}</td> <td scope="row">{{user_key.name}}</td>
<td>{{user_key.created_at}}</td> <td>{{user_key.created_at}}</td>
@ -57,9 +55,9 @@
</td> </td>
</tr> </tr>
{% endfor %}
</tbody> </tbody>
</table> </table>
{% endif %}
</form> </form>
{% if private_key %} {% if private_key %}

View file

@ -1,3 +1,5 @@
from django.test import TestCase from django.test import TestCase
# Create your tests here. # Create your tests here.
test_user_can_add_key()

View file

@ -301,16 +301,12 @@ class GenerateVMSSHKeysView(LoginRequiredMixin, FormView):
self self
).get_context_data(**kwargs) ).get_context_data(**kwargs)
try: user_keys = UserHostingKey.objects.filter(
user_key = UserHostingKey.objects.get( user=self.request.user
user=self.request.user )
)
except UserHostingKey.DoesNotExist:
user_key = None
context.update({ context.update({
'user_key': user_key 'keys': user_keys
}) })
return context return context
@ -351,24 +347,14 @@ class GenerateVMSSHKeysView(LoginRequiredMixin, FormView):
opennebula_user = user_pool.get_by_name(owner.email) opennebula_user = user_pool.get_by_name(owner.email)
# Get user ssh key # Get user ssh key
user_key = UserHostingKey.objects.get(user=owner) public_key = form.cleaned_data.get('public_key')
# Add ssh key to user # Add ssh key to user
manager.oneadmin_client.call('user.update', opennebula_user.id, manager.oneadmin_client.call('user.update', opennebula_user.id,
'<CONTEXT><SSH_PUBLIC_KEY>{ssh_key}</SSH_PUBLIC_KEY></CONTEXT>'.format(ssh_key=user_key.public_key)) '<CONTEXT><SSH_PUBLIC_KEY>{key}</SSH_PUBLIC_KEY></CONTEXT>'.format(key=public_key))
return render(self.request, self.template_name, context) return render(self.request, self.template_name, context)
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
try:
UserHostingKey.objects.get(
user=self.request.user
)
return HttpResponseRedirect(reverse('hosting:key_pair'))
except UserHostingKey.DoesNotExist:
pass
form = self.get_form() form = self.get_form()
if form.is_valid(): if form.is_valid():
return self.form_valid(form) return self.form_valid(form)
@ -421,11 +407,7 @@ class PaymentVMView(LoginRequiredMixin, FormView):
return context return context
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
try: if not UserHostingKey.objects.filter( user=self.request.user).exists():
UserHostingKey.objects.get(
user=self.request.user
)
except UserHostingKey.DoesNotExist:
messages.success( messages.success(
request, request,
'In order to create a VM, you create/upload your SSH KEY first.' 'In order to create a VM, you create/upload your SSH KEY first.'
@ -487,14 +469,16 @@ class PaymentVMView(LoginRequiredMixin, FormView):
manager = OpenNebulaManager(email=owner.email, manager = OpenNebulaManager(email=owner.email,
password=owner.password) password=owner.password)
# Get user ssh key # Get user ssh key
try: if not UserHostingKey.objects.filter( user=self.request.user).exists():
user_key = UserHostingKey.objects.get( context.update({
user=self.request.user 'sshError': 'error',
) 'form': form
})
except UserHostingKey.DoesNotExist: return render(request, self.template_name, context)
pass # For now just get first one
user_key = UserHostingKey.objects.filter(
user=self.request.user).first()
# Create a vm using logged user # Create a vm using logged user
vm_id = manager.create_vm( vm_id = manager.create_vm(
template_id=vm_template_id, template_id=vm_template_id,
@ -639,11 +623,7 @@ class CreateVirtualMachinesView(LoginRequiredMixin, View):
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
try: if not UserHostingKey.objects.filter( user=self.request.user).exists():
UserHostingKey.objects.get(
user=self.request.user
)
except UserHostingKey.DoesNotExist:
messages.success( messages.success(
request, request,
'In order to create a VM, you need to create/upload your SSH KEY first.' 'In order to create a VM, you need to create/upload your SSH KEY first.'
@ -667,7 +647,7 @@ class CreateVirtualMachinesView(LoginRequiredMixin, View):
) )
context = { context = {
'error': 'connection' 'error': 'connection'
} }
return render(request, self.template_name, context) return render(request, self.template_name, context)

View file

@ -0,0 +1,9 @@
class KeyExistsError(Exception):
pass
class UserExistsError(Exception):
pass
class UserCredentialError(Exception):
pass

View file

@ -2,12 +2,15 @@ import oca
import socket import socket
import logging import logging
from oca.pool import WrongNameError
from oca.exceptions import OpenNebulaException
from django.conf import settings from django.conf import settings
from django.utils.functional import cached_property from django.utils.functional import cached_property
from oca.pool import WrongNameError from utils.models import CustomUser
from oca.exceptions import OpenNebulaException from .exceptions import KeyExistsError, UserExistsError, UserCredentialError
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -35,10 +38,33 @@ class OpenNebulaManager():
) )
except: except:
pass pass
def _get_client(self, user):
"""Get a opennebula client object for a CustomUser object
Args:
user (CustomUser): dynamicweb CustomUser object
Returns:
oca.Client: Opennebula client object
Raise:
ConnectionError: If the connection to the opennebula server can't be
established
"""
return oca.Client("{0}:{1}".format(
user.email,
user.password),
"{protocol}://{domain}:{port}{endpoint}".format(
protocol=settings.OPENNEBULA_PROTOCOL,
domain=settings.OPENNEBULA_DOMAIN,
port=settings.OPENNEBULA_PORT,
endpoint=settings.OPENNEBULA_ENDPOINT
))
def _get_opennebula_client(self, username, password): def _get_opennebula_client(self, username, password):
return oca.Client("{0}:{1}".format( return oca.Client("{0}:{1}".format(
username, username,
password), password),
"{protocol}://{domain}:{port}{endpoint}".format( "{protocol}://{domain}:{port}{endpoint}".format(
protocol=settings.OPENNEBULA_PROTOCOL, protocol=settings.OPENNEBULA_PROTOCOL,
@ -47,6 +73,69 @@ class OpenNebulaManager():
endpoint=settings.OPENNEBULA_ENDPOINT endpoint=settings.OPENNEBULA_ENDPOINT
)) ))
def _get_user(self, user):
"""Get the corresponding opennebula user for a CustomUser object
Args:
user (CustomUser): dynamicweb CustomUser object
Returns:
oca.User: Opennebula user object
Raise:
WrongNameError: If no openebula user with this credentials exists
ConnectionError: If the connection to the opennebula server can't be
established
"""
user_pool = self._get_user_pool()
return user_pool.get_by_name(user.email)
def create_user(self, user: CustomUser):
"""Create a new opennebula user or a corresponding CustomUser object
Args:
user (CustomUser): dynamicweb CustomUser object
Returns:
int: Return the opennebula user id
Raises:
ConnectionError: If the connection to the opennebula server can't be
established
UserExistsError: If a user with this credeintals already exits on the
server
UserCredentialError: If a user with this email exists but the
password is worng
"""
try:
self._get_user(user)
try:
self._get_client(self, user)
logger.debug('User already exists')
raise UserExistsError()
except OpenNebulaException as err:
logger.error('OpenNebulaException error: {0}'.format(err))
logger.debug('User exists but password is wrong')
raise UserCredentialError()
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=user,
u_id=user_id
)
return user_id
except ConnectionRefusedError:
logger.error('Could not connect to host: {host} via protocol {protocol}'.format(
host=settings.OPENNEBULA_DOMAIN,
protocol=settings.OPENNEBULA_PROTOCOL)
)
raise ConnectionRefusedError
def _get_or_create_user(self, email, password): def _get_or_create_user(self, email, password):
try: try:
user_pool = self._get_user_pool() user_pool = self._get_user_pool()
@ -77,7 +166,7 @@ class OpenNebulaManager():
host=settings.OPENNEBULA_DOMAIN, host=settings.OPENNEBULA_DOMAIN,
protocol=settings.OPENNEBULA_PROTOCOL) protocol=settings.OPENNEBULA_PROTOCOL)
) )
raise ConnectionRefusedError raise
return user_pool return user_pool
def _get_vm_pool(self): def _get_vm_pool(self):
@ -171,7 +260,6 @@ class OpenNebulaManager():
<DEV_PREFIX>vd</DEV_PREFIX> <DEV_PREFIX>vd</DEV_PREFIX>
<IMAGE_ID>{image_id}</IMAGE_ID> <IMAGE_ID>{image_id}</IMAGE_ID>
</DISK> </DISK>
</TEMPLATE>
""".format(size=1024 * int(specs['disk_size']), """.format(size=1024 * int(specs['disk_size']),
image_id=image_id) image_id=image_id)
@ -193,10 +281,18 @@ class OpenNebulaManager():
<IMAGE>{image}</IMAGE> <IMAGE>{image}</IMAGE>
<IMAGE_UNAME>{image_uname}</IMAGE_UNAME> <IMAGE_UNAME>{image_uname}</IMAGE_UNAME>
</DISK> </DISK>
</TEMPLATE>
""".format(size=1024 * int(specs['disk_size']), """.format(size=1024 * int(specs['disk_size']),
image=image, image=image,
image_uname=image_uname) image_uname=image_uname)
if ssh_key:
vm_specs += """<CONTEXT>
<SSH_PUBLIC_KEY>{ssh}</SSH_PUBLIC_KEY>
<NETWORK>YES</NETWORK>
</CONTEXT>
</TEMPLATE>
""".format(ssh=public_key)
vm_id = self.client.call(oca.VmTemplate.METHODS['instantiate'], vm_id = self.client.call(oca.VmTemplate.METHODS['instantiate'],
template.id, template.id,
'', '',
@ -204,25 +300,6 @@ class OpenNebulaManager():
vm_specs, vm_specs,
False) False)
self.oneadmin_client.call(
'vm.update',
vm_id,
"""<CONTEXT>
<SSH_PUBLIC_KEY>{ssh}</SSH_PUBLIC_KEY>
</CONTEXT>
""".format(ssh=ssh_key)
)
try:
self.oneadmin_client.call(
oca.VirtualMachine.METHODS['chown'],
vm_id,
self.opennebula_user.id,
self.opennebula_user.group_ids[0]
)
except AttributeError:
logger.info(
'Could not change owner for vm with id: {}.'.format(vm_id))
self.oneadmin_client.call( self.oneadmin_client.call(
oca.VirtualMachine.METHODS['action'], oca.VirtualMachine.METHODS['action'],
'release', 'release',
@ -350,3 +427,85 @@ class OpenNebulaManager():
self.opennebula_user.id, self.opennebula_user.id,
new_password new_password
) )
def add_public_key(self, user, public_key='', merge=False):
"""
Args:
user (CustomUser): Dynamicweb user
public_key (string): Public key to add to the user
merge (bool): Optional if True the new public key replaces the old
Raises:
KeyExistsError: If replace is False and the user already has a
public key
WrongNameError: If no openebula user with this credentials exists
ConnectionError: If the connection to the opennebula server can't be
established
Returns:
True if public_key was added
"""
# TODO: Check if we can remove this first try because we basically just
# raise the possible Errors
try:
open_user = self._get_user(user)
try:
old_key = open_user.template.ssh_public_key
if not merge:
raise KeyExistsError()
public_key += '\n{key}'.format(key=old_key)
except AttributeError:
pass
self.oneadmin_client.call('user.update', open_user.id,
'<CONTEXT><SSH_PUBLIC_KEY>{key}</SSH_PUBLIC_KEY></CONTEXT>'.format(key=public_key))
return True
except WrongNameError:
raise
except ConnectionError:
raise
def remove_public_key(self, user, public_key=''):
"""
Args:
user (CustomUser): Dynamicweb user
public_key (string): Public key to be removed to the user
Raises:
KeyDoesNotExistsError: If replace is False and the user already has a
public key
WrongNameError: If no openebula user with this credentials exists
ConnectionError: If the connection to the opennebula server can't be
established
Returns:
True if public_key was removed
"""
try:
open_user = self._get_user(user)
try:
old_key = open_user.template.ssh_public_key
if public_key not in old_key:
raise KeyDoesNotExistsError()
if '\n{}'.format(public_key) in old_key:
public_key = old_key.replace('\n{}'.format(public_key), '')
else:
public_key = old_key.replace(public_key, '')
except AttributeError:
raise KeyDoesNotExistsError()
self.oneadmin_client.call('user.update', open_user.id,
'<CONTEXT><SSH_PUBLIC_KEY>{key}</SSH_PUBLIC_KEY></CONTEXT>'.format(key=public_key))
return True
except WrongNameError:
raise
except ConnectionError:
raise

View file

@ -11,36 +11,10 @@ from .models import OpenNebulaManager
class VirtualMachineTemplateSerializer(serializers.Serializer): class VirtualMachineTemplateSerializer(serializers.Serializer):
"""Serializer to map the virtual machine template instance into JSON format.""" """Serializer to map the virtual machine template instance into JSON format."""
id = serializers.IntegerField(read_only=True) id = serializers.IntegerField(read_only=True)
set_name = serializers.CharField(read_only=True, label='Name')
name = serializers.SerializerMethodField() name = serializers.SerializerMethodField()
cores = serializers.SerializerMethodField() cores = serializers.SerializerMethodField()
disk = serializers.IntegerField(write_only=True)
disk_size = serializers.SerializerMethodField() disk_size = serializers.SerializerMethodField()
set_memory = serializers.IntegerField(write_only=True, label='Memory')
memory = serializers.SerializerMethodField() memory = serializers.SerializerMethodField()
price = serializers.SerializerMethodField()
def create(self, validated_data):
data = validated_data
template = data.pop('template')
cores = template.pop('vcpu')
name = data.pop('name')
disk_size = data.pop('disk')
memory = template.pop('memory')
manager = OpenNebulaManager()
try:
opennebula_id = manager.create_template(name=name, cores=cores,
memory=memory,
disk_size=disk_size,
core_price=core_price,
disk_size_price=disk_size_price,
memory_price=memory_price)
except OpenNebulaException as err:
raise serializers.ValidationError("OpenNebulaException occured. {0}".format(err))
return manager.get_template(template_id=opennebula_id)
def get_cores(self, obj): def get_cores(self, obj):
if hasattr(obj.template, 'vcpu'): if hasattr(obj.template, 'vcpu'):
@ -58,24 +32,14 @@ class VirtualMachineTemplateSerializer(serializers.Serializer):
except: except:
return 0 return 0
def get_price(self, obj):
template = obj.template
price = float(template.cpu) * 5.0
price += (int(template.memory)/1024 * 2.0)
try:
for disk in template.disks:
price += int(disk.size)/1024 * 0.6
except:
pass
return price
def get_memory(self, obj): def get_memory(self, obj):
return int(obj.template.memory)/1024 return int(obj.template.memory)/1024
def get_name(self, obj): def get_name(self, obj):
return obj.name.strip('public-') return obj.name.strip('public-')
class VirtualMachineSerializer(serializers.Serializer): class VirtualMachineSerializer(serializers.Serializer):
"""Serializer to map the virtual machine instance into JSON format.""" """Serializer to map the virtual machine instance into JSON format."""

View file

@ -1,35 +1,54 @@
import socket
import random
import string
from django.test import TestCase from django.test import TestCase
from .models import VirtualMachine, VirtualMachineTemplate, OpenNebulaManager
from .models import OpenNebulaManager
from .serializers import VirtualMachineSerializer
from utils.models import CustomUser
class OpenNebulaManagerTestCases(TestCase): class OpenNebulaManagerTestCases(TestCase):
"""This class defines the test suite for the opennebula manager model.""" """This class defines the test suite for the opennebula manager model."""
def setUp(self): def setUp(self):
"""Define the test client and other test variables.""" """Define the test client and other test variables."""
self.cores = 1
self.memory = 1
self.disk_size = 10.0
self.email = 'test@test.com'
self.password = 'testtest'
self.manager = OpenNebulaManager(email=None, password=None, create_user=False) self.email = '{}@ungleich.ch'.format(''.join(random.choices(string.ascii_uppercase, k=10)))
self.password = ''.join(random.choices(string.ascii_uppercase + string.digits, k=20))
self.user = CustomUser.objects.create(name='test', email=self.email,
password=self.password)
self.vm_specs = {}
self.vm_specs['cpu'] = 1
self.vm_specs['memory'] = 2
self.vm_specs['disk_size'] = 10
self.manager = OpenNebulaManager()
def test_model_can_connect_to_server(self): def test_connect_to_server(self):
"""Test the opennebula manager model can connect to a server.""" """Test the opennebula manager can connect to a server."""
try: try:
user_pool = self.manager._get_user_pool() ver = self.manager.oneadmin_client.version()
except: except:
user_pool = None ver = None
self.assertFalse(user_pool is None) self.assertTrue(ver is not None)
def test_model_can_create_user(self): def test_get_user(self):
"""Test the opennebula manager model can create a new user.""" """Test the opennebula manager can get a existing user."""
self.manager.create_user(self.user)
user = self.manager._get_user(self.user)
name = user.name
self.assertNotEqual(name, None)
def test_create_and_delete_user(self):
"""Test the opennebula manager can create and delete a new user."""
old_count = len(self.manager._get_user_pool()) old_count = len(self.manager._get_user_pool())
self.manager = OpenNebulaManager(email=self.email, self.manager = OpenNebulaManager(email=self.email,
password=self.password, password=self.password)
create_user=True)
user_pool = self.manager._get_user_pool() user_pool = self.manager._get_user_pool()
new_count = len(user_pool) new_count = len(user_pool)
# Remove the user afterwards # Remove the user afterwards
@ -38,105 +57,85 @@ class OpenNebulaManagerTestCases(TestCase):
self.assertNotEqual(old_count, new_count) self.assertNotEqual(old_count, new_count)
def test_user_can_login(self):
""" Test the manager can login to a new created user"""
self.manager.create_user(self.user)
user = self.manager._get_user(self.user)
client = self.manager._get_client(self.user)
version = client.version()
class VirtualMachineTemplateTestCase(TestCase): # Cleanup
"""This class defines the test suite for the virtualmachine template model.""" user.delete()
self.assertNotEqual(version, None)
def test_add_public_key_to_user(self):
""" Test the manager can add a new public key to an user """
self.manager.create_user(self.user)
user = self.manager._get_user(self.user)
public_key = 'test'
self.manager.add_public_key(self.user, public_key)
# Fetch new user information from opennebula
user.info()
user_public_key = user.template.ssh_public_key
# Cleanup
user.delete()
self.assertEqual(user_public_key, public_key)
def test_append_public_key_to_user(self):
""" Test the manager can append a new public key to an user """
self.manager.create_user(self.user)
user = self.manager._get_user(self.user)
public_key = 'test'
self.manager.add_public_key(self.user, public_key)
# Fetch new user information from opennebula
user.info()
old_public_key = user.template.ssh_public_key
self.manager.add_public_key(self.user, public_key, merge=True)
user.info()
new_public_key = user.template.ssh_public_key
# Cleanup
user.delete()
self.assertEqual(new_public_key, '{}\n{}'.format(old_public_key,
public_key))
def test_remove_public_key_to_user(self):
""" Test the manager can remove a public key from an user """
self.manager.create_user(self.user)
user = self.manager._get_user(self.user)
public_key = 'test'
self.manager.add_public_key(self.user, public_key)
self.manager.add_public_key(self.user, public_key, merge=True)
user.info()
old_public_key = user.template.ssh_public_key
self.manager.remove_public_key(self.user, public_key)
user.info()
new_public_key = user.template.ssh_public_key
# Cleanup
user.delete()
self.assertEqual(new_public_key,
old_public_key.replace('{}\n'.format(public_key), '', 1))
def test_requires_ssh_key_for_new_vm(self):
"""Test the opennebula manager requires the user to have a ssh key when
creating a new vm"""
class VirtualMachineSerializerTestCase(TestCase):
def setUp(self): def setUp(self):
"""Define the test client and other test variables.""" """Define the test client and other test variables."""
self.template_name = "Standard" self.manager = OpenNebulaManager(email=None, password=None)
self.base_price = 0.0
self.core_price = 5.0
self.memory_price = 2.0
self.disk_size_price = 0.6
self.cores = 1
self.memory = 1
self.disk_size = 10.0
self.manager = OpenNebulaManager(email=None, password=None, create_user=False)
self.opennebula_id = self.manager.create_template(name=self.template_name,
cores=self.cores,
memory=self.memory,
disk_size=self.disk_size)
self.template = VirtualMachineTemplate(opennebula_id=self.opennebula_id,
base_price=self.base_price,
memory_price=self.memory_price,
core_price=self.core_price,
disk_size_price=self.disk_size_price)
def test_model_can_create_a_virtualmachine_template(self):
"""Test the virtualmachine template model can create a template."""
old_count = VirtualMachineTemplate.objects.count()
self.template.save()
new_count = VirtualMachineTemplate.objects.count()
# Remove the template afterwards
template = self.manager._get_template(self.template.opennebula_id)
template.delete()
self.assertNotEqual(old_count, new_count)
def test_model_can_calculate_price(self):
price = self.cores * self.core_price
price += self.memory * self.memory_price
price += self.disk_size * self.disk_size_price
self.assertEqual(price, self.template.calculate_price())
class VirtualMachineTestCase(TestCase):
def setUp(self):
"""Define the test client and other test variables."""
self.template_name = "Standard"
self.base_price = 0.0
self.core_price = 5.0
self.memory_price = 2.0
self.disk_size_price = 0.6
self.cores = 1
self.memory = 1
self.disk_size = 10.0
self.manager = OpenNebulaManager(email=None, password=None, create_user=False)
self.opennebula_id = self.manager.create_template(name=self.template_name,
cores=self.cores,
memory=self.memory,
disk_size=self.disk_size)
self.template = VirtualMachineTemplate(opennebula_id=self.opennebula_id,
base_price=self.base_price,
memory_price=self.memory_price,
core_price=self.core_price,
disk_size_price=self.disk_size_price)
self.template_id = self.template.opennebula_id()
self.opennebula_id = self.manager.create_virtualmachine(template_id=self.template_id)
self.virtualmachine = VirtualMachine(opennebula_id=self.opennebula_id,
template=self.template)
def test_serializer_strips_of_public(self): def test_serializer_strips_of_public(self):
""" Test the serialized object contains no 'public-'.""" """ Test the serialized virtual machine object contains no 'public-'."""
template = self.manager.get_templates().first() for vm in self.manager.get_vms():
serialized = VirtualMachineTemplateSerializer(template) serialized = VirtualMachineSerializer(vm)
self.assertEqual(serialized.data.name, template.name.strip('public-')) self.assertEqual(serialized.data.get('name'), vm.name.strip('public-'))
break
def test_model_can_create_a_virtualmachine(self):
"""Test the virtualmachine model can create a virtualmachine."""
old_count = VirtualMachine.objects.count()
self.virtualmachine.save()
new_count = VirtualMachine.objects.count()
self.assertNotEqual(old_count, new_count)
def test_model_can_create_a_virtualmachine_for_user(self):
pass
def test_model_can_delete_a_virtualmachine(self):
"""Test the virtualmachine model can delete a virtualmachine."""
self.virtualmachine.save()
old_count = VirtualMachine.objects.count()
VirtualMachine.objects.first().delete()
new_count = VirtualMachine.objects.count()
self.assertNotEqual(old_count, new_count)

View file

@ -1,15 +1,10 @@
from django.conf.urls import url, include from django.conf.urls import url, include
from rest_framework.urlpatterns import format_suffix_patterns from rest_framework.urlpatterns import format_suffix_patterns
from .views import TemplateCreateView, TemplateDetailsView,\ from .views import VmCreateView, VmDetailsView
VmCreateView, VmDetailsView
urlpatterns = { urlpatterns = {
url(r'^auth/', include('rest_framework.urls', namespace='rest_framework')), url(r'^auth/', include('rest_framework.urls', namespace='rest_framework')),
url(r'^templates/$', TemplateCreateView.as_view(), name="template_create"),
url(r'^templates/(?P<pk>[0-9]+)/$', TemplateDetailsView.as_view(),
name="templates_details"),
url(r'^vms/$', VmCreateView.as_view(), name="vm_create"), url(r'^vms/$', VmCreateView.as_view(), name="vm_create"),
url(r'^vms/(?P<pk>[0-9]+)/$', VmDetailsView.as_view(), url(r'^vms/(?P<pk>[0-9]+)/$', VmDetailsView.as_view(),
name="vm_details"), name="vm_details"),

View file

@ -20,38 +20,6 @@ class ServiceUnavailable(APIException):
default_code = 'service_unavailable' default_code = 'service_unavailable'
class TemplateCreateView(generics.ListCreateAPIView):
"""This class handles the GET and POST requests."""
serializer_class = VirtualMachineTemplateSerializer
permission_classes = (permissions.IsAuthenticated, permissions.IsAdminUser)
def get_queryset(self):
manager = OpenNebulaManager()
return manager.get_templates()
def perform_create(self, serializer):
"""Save the post data when creating a new template."""
serializer.save()
class TemplateDetailsView(generics.RetrieveUpdateDestroyAPIView):
"""This class handles the http GET, PUT and DELETE requests."""
serializer_class = VirtualMachineTemplateSerializer
permission_classes = (permissions.IsAuthenticated)
def get_queryset(self):
manager = OpenNebulaManager()
# We may have ConnectionRefusedError if we don't have a
# connection to OpenNebula. For now, we raise ServiceUnavailable
try:
templates = manager.get_templates()
except ConnectionRefusedError:
raise ServiceUnavailable
return templates
class VmCreateView(generics.ListCreateAPIView): class VmCreateView(generics.ListCreateAPIView):
"""This class handles the GET and POST requests.""" """This class handles the GET and POST requests."""
serializer_class = VirtualMachineSerializer serializer_class = VirtualMachineSerializer