improveded mobile responsiveness

This commit is contained in:
Henry Bravo 2017-06-03 21:19:13 -05:00
commit 9b121cc702
17 changed files with 466 additions and 383 deletions

View file

@ -5,3 +5,10 @@
* [datacenterlight] Fix initially shown price
1.0.2: 2017-05-28
* [datacenterlight] Fixed login redirecting to blank page after logout
1.0.3: 2017-06-02
* [datacenterlight] Hotfix, remove footer on mobile devices
next
* [opennebula_api] Improve testing, add ssh key functions
* [opennebula_api] Remove template views
* [datacenterlight] Allow user to have multiple ssh keys

View file

@ -636,24 +636,22 @@ h6 {
border-bottom: 1px solid rgba(128, 128, 128, 0.3);
position: relative;
display: flex;
justify-content: center;
justify-content: space-around;
align-items: center;
}
.price-calc-section .card .description span{
font-size: 20px;
.price-calc-section .card .description span {
font-size: 18px;
margin-left: 4px;
margin-left: 0px;
/* justify-self: start; */
width: 30%;
text-align: left;
}
.price-calc-section .card .description input{
font-size: 20px;
text-align: center;
width: 70px;
}
.price-calc-section .card .description #coreValue{
width: 50px;
}
.price-calc-section .card .description #ramValue{
width: 50px;
width: 60px;
}
.price-calc-section .card .description i{
color: #29427A;
@ -797,6 +795,9 @@ h6 {
.split-section {
padding: 10px 0;
}
.split-section .icon-section {
min-height: 160px;
}
.split-section .icon-section i{
font-size: 120px;
}
@ -836,7 +837,10 @@ h6 {
left: 50%;
transform: translate(-50%, 0);
}
.contact-section .card .social a {
color: #29427A;
font-size: 30px;
}
.price-calc-section{
flex-direction: column;
padding: 60px 10px !important;
@ -866,6 +870,12 @@ h6 {
text-align: center;
}
.price-calc-section .card .description input {
font-size: 17px;
text-align: center;
width: 60px;
}
}
@ -888,8 +898,8 @@ h6 {
line-height: 25px;
}
.price-calc-section .card .description span {
font-size: 17px;
margin-left: 4px;
font-size: 15px;
margin-left: 0px;
}
}

View file

@ -235,7 +235,6 @@
<div class="col-xs-12 col-md-6 text">
<h2 class="section-heading">{% trans "We are cutting down the costs significantly!" %}</h2>
<p class="lead">{% trans "Affordable VM hosting based in Switzerland" %}</p>
<a href="#" class="btn btn-info btn-lg">{% trans "More Info" %}</a>
</div>
<div class="col-xs-12 col-md-6 hero-feature">

View file

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

View file

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-06-01 11:20-0500\n"
"POT-Creation-Date: 2017-06-01 21:03+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"
@ -18,55 +18,64 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: templates/hosting/base_short.html:68 templates/hosting/base_short.html:139
#: 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"
msgstr ""
#: templates/hosting/base_short.html:73 templates/hosting/base_short.html:145
#: templates/hosting/orders.html.py:12
#: hosting/templates/hosting/base_short.html:73
#: hosting/templates/hosting/base_short.html:145
#: hosting/templates/hosting/orders.html:12
msgid "My Orders"
msgstr ""
#: templates/hosting/base_short.html:78 templates/hosting/base_short.html:152
#: hosting/templates/hosting/base_short.html:78
#: hosting/templates/hosting/base_short.html:152
msgid "Keys"
msgstr ""
#: templates/hosting/base_short.html:83 templates/hosting/base_short.html:158
#: hosting/templates/hosting/base_short.html:83
#: hosting/templates/hosting/base_short.html:158
msgid "Notifications "
msgstr ""
#: templates/hosting/base_short.html:90
#: hosting/templates/hosting/base_short.html:90
msgid "Logout"
msgstr ""
#: templates/hosting/base_short.html:95
#: hosting/templates/hosting/base_short.html:95
msgid "How it works"
msgstr ""
#: templates/hosting/base_short.html:98
#: hosting/templates/hosting/base_short.html:98
msgid "Your infrastructure"
msgstr ""
#: templates/hosting/base_short.html:101
#: hosting/templates/hosting/base_short.html:101
msgid "Our inftrastructure"
msgstr ""
#: templates/hosting/base_short.html:104
#: hosting/templates/hosting/base_short.html:104
msgid "Pricing"
msgstr ""
#: templates/hosting/base_short.html:107
#: hosting/templates/hosting/base_short.html:107
msgid "Contact"
msgstr ""
#: templates/hosting/base_short.html:110
#: templates/hosting/confirm_reset_password.html:38
#: templates/hosting/login.html:31 templates/hosting/login.html.py:40
#: templates/hosting/reset_password.html:30 templates/hosting/signup.html:29
#: hosting/templates/hosting/base_short.html:110
#: hosting/templates/hosting/login.html:32
#: hosting/templates/hosting/login.html:41
#: hosting/templates/hosting/reset_password.html:31
#: hosting/templates/hosting/signup.html:30
msgid "Login"
msgstr ""
#: templates/hosting/base_short.html:134
#: hosting/templates/hosting/base_short.html:134
msgid "Home"
msgstr ""
@ -135,8 +144,8 @@ msgstr ""
msgid "Customers"
msgstr ""
#: templates/hosting/bills.html:16
#: templates/hosting/virtual_machine_key.html:45
#: hosting/templates/hosting/bills.html:16
#: hosting/templates/hosting/virtual_machine_key.html:42
msgid "Name"
msgstr ""
@ -168,13 +177,14 @@ msgstr "Ihre VM in der Schweiz"
msgid "Set your new password"
msgstr ""
#: templates/hosting/confirm_reset_password.html:29
#: templates/hosting/reset_password.html:21
#: hosting/templates/hosting/confirm_reset_password.html:28
#: hosting/templates/hosting/reset_password.html:22
msgid "Reset"
msgstr ""
#: templates/hosting/confirm_reset_password.html:35
#: templates/hosting/reset_password.html:27 templates/hosting/signup.html:26
#: hosting/templates/hosting/confirm_reset_password.html:32
#: hosting/templates/hosting/reset_password.html:28
#: hosting/templates/hosting/signup.html:27
msgid "Already have an account ?"
msgstr ""
@ -210,20 +220,27 @@ msgstr ""
msgid "The %(site_name)s team"
msgstr ""
#: templates/hosting/login.html:25
#: 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"
msgstr ""
#: templates/hosting/login.html:48
#: hosting/templates/hosting/login.html:49
msgid "Don't have an account yet ? "
msgstr ""
#: templates/hosting/login.html:51 templates/hosting/signup.html.py:12
#: templates/hosting/signup.html:20
#: hosting/templates/hosting/login.html:52
#: hosting/templates/hosting/signup.html:13
#: hosting/templates/hosting/signup.html:21
msgid "Sign up"
msgstr ""
#: templates/hosting/login.html:53
#: hosting/templates/hosting/login.html:54
msgid "Forgot your password ? "
msgstr ""
@ -298,10 +315,10 @@ msgstr ""
msgid "Amount"
msgstr ""
#: templates/hosting/orders.html:19
#: templates/hosting/virtual_machine_detail.html:30
#: templates/hosting/virtual_machine_key.html:47
#: templates/hosting/virtual_machines.html:31
#: hosting/templates/hosting/orders.html:19
#: hosting/templates/hosting/virtual_machine_detail.html:30
#: hosting/templates/hosting/virtual_machine_key.html:44
#: hosting/templates/hosting/virtual_machines.html:31
msgid "Status"
msgstr ""
@ -333,7 +350,7 @@ msgstr ""
msgid "Delete"
msgstr ""
#: templates/hosting/reset_password.html:13
#: hosting/templates/hosting/reset_password.html:14
msgid "Reset your password"
msgstr ""
@ -389,50 +406,46 @@ msgstr ""
msgid "Access Key"
msgstr ""
#: templates/hosting/virtual_machine_key.html:22
#: hosting/templates/hosting/virtual_machine_key.html:25
msgid "Upload your own key. "
msgstr ""
#: templates/hosting/virtual_machine_key.html:29
msgid "Upload Key"
msgstr ""
#: templates/hosting/virtual_machine_key.html:33
#: hosting/templates/hosting/virtual_machine_key.html:29
msgid "Or generate a new key pair."
msgstr ""
#: templates/hosting/virtual_machine_key.html:37
#: hosting/templates/hosting/virtual_machine_key.html:31
msgid "Generate Key Pair"
msgstr ""
#: templates/hosting/virtual_machine_key.html:46
#: hosting/templates/hosting/virtual_machine_key.html:43
msgid "Created at"
msgstr ""
#: templates/hosting/virtual_machine_key.html:68
#: templates/hosting/virtual_machine_key.html:81
#: hosting/templates/hosting/virtual_machine_key.html:66
#: hosting/templates/hosting/virtual_machine_key.html:79
msgid "Warning!"
msgstr ""
#: 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"
msgstr ""
#: templates/hosting/virtual_machine_key.html:76
#: hosting/templates/hosting/virtual_machine_key.html:74
msgid "Copy to Clipboard"
msgstr ""
#: templates/hosting/virtual_machine_key.html:77
#: hosting/templates/hosting/virtual_machine_key.html:75
msgid "Download"
msgstr ""
#: templates/hosting/virtual_machine_key.html:81
#: hosting/templates/hosting/virtual_machine_key.html:79
msgid ""
"Your SSH private key was already generated and downloaded, if you lost it, "
"contact us. "
msgstr ""
#: templates/hosting/virtual_machine_key.html:84
#: hosting/templates/hosting/virtual_machine_key.html:82
msgid "Generate my key"
msgstr ""

View file

@ -50,5 +50,8 @@
select {
width: 280px;
}
.content-dashboard {
width: 90%;
}
}

View file

@ -344,6 +344,19 @@ h6 {
ul.banner-social-buttons > li:last-child {
margin-bottom: 0;
}
.auth-box .form {
padding: 15px 0px 0 0;
}
.auth-box.sign-up .form {
padding: 15px 0px 0 0;
}
.auth-box .form .form-control {
height: 44px;
font-size: 13px;
}
.auth-container .auth-title {
display: none;
}
}
@media (max-width: 540px) {
.auth-container .auth-title h2{
@ -357,7 +370,6 @@ h6 {
margin-bottom: 50px;
}
.auth-box .form {
padding: 15px;
width: 90%;
}
.auth-box .section-heading {

View file

@ -73,11 +73,7 @@
<i class="fa fa-credit-card"></i> {% trans "My Orders"%}
</a>
</li>
<li>
<a href="{% url 'hosting:key_pair' %}">
<i class="fa fa-key" aria-hidden="true"></i> {% trans "Keys"%}
</a>
</li>
<li>
<a href="{% url 'hosting:notifications' %}">
<i class="fa fa-bell"></i> {% trans "Notifications "%}
@ -88,6 +84,11 @@
<i class="glyphicon glyphicon-user"></i> {{request.user.name}} <span class="caret"></span></a>
<ul id="g-account-menu" class="dropdown-menu" role="menu">
<li><a href="{% url 'hosting:logout' %}"><i class="glyphicon glyphicon-lock"></i>{% trans "Logout"%} </a></li>
<li>
<a href="{% url 'hosting:key_pair' %}">
<i class="fa fa-key"></i> {% trans "Keys"%}
</a>
</li>
</ul>
</li>
<!--
@ -127,42 +128,7 @@
{% if request.user.is_authenticated %}
<footer class="navbar-fixed-bottom">
<div class="container">
<div class="row">
<div class="col-lg-12">
<!-- <ul class="list-inline">
<li>
<a href="#">{% trans "Home"%}</a>
</li>
<li class="footer-menu-divider">&sdot;</li>
<li>
<a href="{% url 'hosting:virtual_machines' %}">
{% trans "My Virtual Machines"%}
</a>
</li>
<li class="footer-menu-divider">&sdot;</li>
<li>
<a href="{% url 'hosting:orders' %}">
{% trans "My Orders"%}
</a>
</li>
<li>&sdot;</li>
<li>
<li>
<a href="{% url 'hosting:key_pair' %}">
{% trans "Keys"%}
</a>
</li>
<li class="footer-menu-divider">&sdot;</li>
<li>
<a href="{% url 'hosting:notifications' %}">
{% trans "Notifications "%}
</a>
</li>
</ul> -->
<p class="copyright text-muted small">Copyright &copy; ungleich GmbH {% now "Y" %}. All Rights Reserved</p>
</div>
</div>
<p class="copyright text-muted small">Copyright &copy; ungleich GmbH {% now "Y" %}. All Rights Reserved</p>
</div>
</footer>
{% endif %}

View file

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

View file

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

View file

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

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 logging
from oca.pool import WrongNameError
from oca.exceptions import OpenNebulaException
from django.conf import settings
from django.utils.functional import cached_property
from oca.pool import WrongNameError
from oca.exceptions import OpenNebulaException
from utils.models import CustomUser
from .exceptions import KeyExistsError, UserExistsError, UserCredentialError
logger = logging.getLogger(__name__)
@ -35,10 +38,33 @@ class OpenNebulaManager():
)
except:
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):
return oca.Client("{0}:{1}".format(
username,
password),
"{protocol}://{domain}:{port}{endpoint}".format(
protocol=settings.OPENNEBULA_PROTOCOL,
@ -47,6 +73,69 @@ class OpenNebulaManager():
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):
try:
user_pool = self._get_user_pool()
@ -77,7 +166,7 @@ class OpenNebulaManager():
host=settings.OPENNEBULA_DOMAIN,
protocol=settings.OPENNEBULA_PROTOCOL)
)
raise ConnectionRefusedError
raise
return user_pool
def _get_vm_pool(self):
@ -171,7 +260,6 @@ class OpenNebulaManager():
<DEV_PREFIX>vd</DEV_PREFIX>
<IMAGE_ID>{image_id}</IMAGE_ID>
</DISK>
</TEMPLATE>
""".format(size=1024 * int(specs['disk_size']),
image_id=image_id)
@ -193,10 +281,18 @@ class OpenNebulaManager():
<IMAGE>{image}</IMAGE>
<IMAGE_UNAME>{image_uname}</IMAGE_UNAME>
</DISK>
</TEMPLATE>
""".format(size=1024 * int(specs['disk_size']),
image=image,
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'],
template.id,
'',
@ -204,25 +300,6 @@ class OpenNebulaManager():
vm_specs,
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(
oca.VirtualMachine.METHODS['action'],
'release',
@ -350,3 +427,85 @@ class OpenNebulaManager():
self.opennebula_user.id,
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):
"""Serializer to map the virtual machine template instance into JSON format."""
id = serializers.IntegerField(read_only=True)
set_name = serializers.CharField(read_only=True, label='Name')
name = serializers.SerializerMethodField()
cores = serializers.SerializerMethodField()
disk = serializers.IntegerField(write_only=True)
disk_size = serializers.SerializerMethodField()
set_memory = serializers.IntegerField(write_only=True, label='Memory')
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):
if hasattr(obj.template, 'vcpu'):
@ -58,24 +32,14 @@ class VirtualMachineTemplateSerializer(serializers.Serializer):
except:
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):
return int(obj.template.memory)/1024
def get_name(self, obj):
return obj.name.strip('public-')
class VirtualMachineSerializer(serializers.Serializer):
"""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 .models import VirtualMachine, VirtualMachineTemplate, OpenNebulaManager
from .models import OpenNebulaManager
from .serializers import VirtualMachineSerializer
from utils.models import CustomUser
class OpenNebulaManagerTestCases(TestCase):
"""This class defines the test suite for the opennebula manager model."""
def setUp(self):
"""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)
def test_model_can_connect_to_server(self):
"""Test the opennebula manager model can connect to a server."""
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_connect_to_server(self):
"""Test the opennebula manager can connect to a server."""
try:
user_pool = self.manager._get_user_pool()
ver = self.manager.oneadmin_client.version()
except:
user_pool = None
self.assertFalse(user_pool is None)
ver = None
self.assertTrue(ver is not None)
def test_model_can_create_user(self):
"""Test the opennebula manager model can create a new user."""
def test_get_user(self):
"""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())
self.manager = OpenNebulaManager(email=self.email,
password=self.password,
create_user=True)
password=self.password)
user_pool = self.manager._get_user_pool()
new_count = len(user_pool)
# Remove the user afterwards
@ -38,105 +57,85 @@ class OpenNebulaManagerTestCases(TestCase):
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):
"""This class defines the test suite for the virtualmachine template model."""
# Cleanup
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):
"""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.manager = OpenNebulaManager(email=None, password=None)
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):
""" Test the serialized object contains no 'public-'."""
""" Test the serialized virtual machine object contains no 'public-'."""
template = self.manager.get_templates().first()
serialized = VirtualMachineTemplateSerializer(template)
self.assertEqual(serialized.data.name, template.name.strip('public-'))
for vm in self.manager.get_vms():
serialized = VirtualMachineSerializer(vm)
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 rest_framework.urlpatterns import format_suffix_patterns
from .views import TemplateCreateView, TemplateDetailsView,\
VmCreateView, VmDetailsView
from .views import VmCreateView, VmDetailsView
urlpatterns = {
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/(?P<pk>[0-9]+)/$', VmDetailsView.as_view(),
name="vm_details"),

View file

@ -20,38 +20,6 @@ class ServiceUnavailable(APIException):
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):
"""This class handles the GET and POST requests."""
serializer_class = VirtualMachineSerializer