Commit 1c48024b by Őry Máté

Update to master

Merge branch 'master' into feature-vm-gc

Conflicts:
	circle/dashboard/views.py
parents a24f503e 05e64bb7
=============
cirecle-cloud
=============
============
circle-cloud
============
This is the Django based controller and web portal of the CIRCLE Cloud.
This is the Django based controller and web portal of the CIRCLE Cloud.
\ No newline at end of file
......@@ -339,7 +339,6 @@ if get_env_variable('DJANGO_SAML', 'FALSE') == 'TRUE':
'django.contrib.auth.backends.ModelBackend',
'djangosaml2.backends.Saml2Backend',
)
LOGIN_URL = '/saml2/login/'
remote_metadata = join(SITE_ROOT, 'remote_metadata.xml')
if not isfile(remote_metadata):
......@@ -388,6 +387,8 @@ if get_env_variable('DJANGO_SAML', 'FALSE') == 'TRUE':
'DJANGO_SAML_GROUP_OWNER_ATTRIBUTES', '').split(',')
SAML_CREATE_UNKNOWN_USER = True
if get_env_variable('DJANGO_SAML_ORG_ID_ATTRIBUTE', False) != False:
if get_env_variable('DJANGO_SAML_ORG_ID_ATTRIBUTE', False) is not False:
SAML_ORG_ID_ATTRIBUTE = get_env_variable(
'DJANGO_SAML_ORG_ID_ATTRIBUTE')
LOGIN_REDIRECT_URL = "/"
......@@ -6,6 +6,8 @@ from django.shortcuts import redirect
from django.core.urlresolvers import reverse
from circle.settings.base import get_env_variable
from dashboard.views import circle_login
from dashboard.forms import CirclePasswordResetForm, CircleSetPasswordForm
admin.autodiscover()
......@@ -23,6 +25,19 @@ urlpatterns = patterns(
url(r'^admin/', include(admin.site.urls)),
url(r'^network/', include('network.urls')),
url(r'^dashboard/', include('dashboard.urls')),
url((r'^accounts/reset/(?P<uidb36>[0-9A-Za-z]{1,13})-'
'(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$'),
'django.contrib.auth.views.password_reset_confirm',
{'set_password_form': CircleSetPasswordForm},
name='accounts.password_reset_confirm'
),
url(r'^accounts/password/reset/$', ("django.contrib.auth.views."
"password_reset"),
{'password_reset_form': CirclePasswordResetForm},
name="accounts.password-reset",
),
url(r'^accounts/login/?$', circle_login, name="accounts.login"),
url(r'^accounts/', include('django.contrib.auth.urls')),
)
......
......@@ -2,6 +2,9 @@ from datetime import timedelta
import uuid
from django.contrib.auth.models import User
from django.contrib.auth.forms import (
AuthenticationForm, PasswordResetForm, SetPasswordForm,
)
from crispy_forms.helper import FormHelper
from crispy_forms.layout import (
......@@ -484,6 +487,16 @@ class TemplateForm(forms.ModelForm):
return User.objects.get(pk=self.instance.owner.pk)
return self.user
def clean(self):
cleaned_data = self.cleaned_data
# if raw_data has changed and the user is not superuser
if "raw_data" in self.changed_data and not self.user.is_superuser:
old_raw_data = InstanceTemplate.objects.get(
pk=self.instance.pk).raw_data
cleaned_data['raw_data'] = old_raw_data
return cleaned_data
def save(self, commit=True):
data = self.cleaned_data
self.instance.max_ram_size = data.get('ram_size')
......@@ -509,6 +522,9 @@ class TemplateForm(forms.ModelForm):
@property
def helper(self):
kwargs_raw_data = {}
if not self.user.is_superuser:
kwargs_raw_data['readonly'] = None
helper = FormHelper()
helper.layout = Layout(
Field("name"),
......@@ -560,7 +576,7 @@ class TemplateForm(forms.ModelForm):
"stuff",
Field('access_method'),
Field('boot_menu'),
Field('raw_data'),
Field('raw_data', **kwargs_raw_data),
Field('req_traits'),
Field('description'),
Field("parent", type="hidden"),
......@@ -762,6 +778,93 @@ class DiskAddForm(forms.Form):
return helper
class CircleAuthenticationForm(AuthenticationForm):
# fields: username, password
@property
def helper(self):
helper = FormHelper()
helper.form_show_labels = False
helper.layout = Layout(
AnyTag(
"div",
AnyTag(
"span",
AnyTag(
"i",
css_class="icon-user",
),
css_class="input-group-addon",
),
Field("username", placeholder=_("Username"),
css_class="form-control"),
css_class="input-group",
),
AnyTag(
"div",
AnyTag(
"span",
AnyTag(
"i",
css_class="icon-lock",
),
css_class="input-group-addon",
),
Field("password", placeholder=_("Password"),
css_class="form-control"),
css_class="input-group",
),
)
helper.add_input(Submit("submit", _("Sign in"),
css_class="btn btn-success"))
return helper
class CirclePasswordResetForm(PasswordResetForm):
# fields: email
@property
def helper(self):
helper = FormHelper()
helper.form_show_labels = False
helper.layout = Layout(
AnyTag(
"div",
AnyTag(
"span",
AnyTag(
"i",
css_class="icon-envelope",
),
css_class="input-group-addon",
),
Field("email", placeholder=_("Email address"),
css_class="form-control"),
Div(
AnyTag(
"button",
HTML(_("Reset password")),
css_class="btn btn-success",
),
css_class="input-group-btn",
),
css_class="input-group",
),
)
return helper
class CircleSetPasswordForm(SetPasswordForm):
@property
def helper(self):
helper = FormHelper()
helper.add_input(Submit("submit", _("Change password"),
css_class="btn btn-success change-password",
css_id="submit-password-button"))
return helper
class LinkButton(BaseInput):
"""
......
......@@ -332,8 +332,6 @@ a.hover-black {
}
<<<<<<< HEAD
.notification-messages {
padding: 10px 8px;
width: 350px;
......
......@@ -18,6 +18,15 @@ $(function() {
$('#create-modal').on('hidden.bs.modal', function() {
$('#create-modal').remove();
});
$('#vm-migrate-node-list li').click(function(e) {
var li = $(this).closest('li');
if (li.find('input').attr('disabled'))
return true;
$('#vm-migrate-node-list li').removeClass('panel-primary');
li.addClass('panel-primary').find('input').attr('checked', true);
return false;
});
$('#vm-migrate-node-list li input:checked').closest('li').addClass('panel-primary');
}
});
return false;
......
......@@ -131,7 +131,11 @@ $(function() {
location.reload();
},
error: function(xhr, textStatus, error) {
if (xhr.status == 500) {
addMessage("Internal Server Error", "danger");
} else {
addMessage(xhr.status + " Unknown Error", "danger");
}
}
});
} else {
......
{% load i18n %}
{% load sizefieldtags %}
<form method="POST" action="{% url "dashboard.views.vm-migrate" pk=vm %}">
<form method="POST" action="{% url "dashboard.views.vm-migrate" pk=vm.pk %}">
{% csrf_token %}
<ul id="vm-migrate-node-list">
{% for n in nodes %}
<li>
<strong>{{ n }}</strong>
<input type="radio" name="node" value="{{ n.pk }}" style="float: right;"/>
<span class="vm-migrate-node-property">{% trans "CPU load" %}: {{ n.cpu_usage }}</span>
<span class="vm-migrate-node-property">{% trans "RAM usage" %}: {{ n.byte_ram_usage|filesize }}/{{ n.ram_size|filesize }}</span>
<div style="clear: both;"></div>
</li>
{% endfor %}
{% with current=vm.node.pk selected=vm.select_node.pk %}
{% for n in nodes %}
<li class="panel panel-default"><div class="panel-body">
<label for="migrate-to-{{n.pk}}">
<strong>{{ n }}</strong>
{% if current == n.pk %}<div class="label label-info">{% trans "current" %}</div>{% endif %}
{% if selected == n.pk %}<div class="label label-success">{% trans "recommended" %}</div>{% endif %}
</label>
<input id="migrate-to-{{n.pk}}" type="radio" name="node" value="{{ n.pk }}" style="float: right;"
{% if current == n.pk %}disabled="disabled"{% endif %}
{% if selected == n.pk %}checked="checked"{% endif %} />
<span class="vm-migrate-node-property">{% trans "CPU load" %}: {{ n.cpu_usage }}</span>
<span class="vm-migrate-node-property">{% trans "RAM usage" %}: {{ n.byte_ram_usage|filesize }}/{{ n.ram_size|filesize }}</span>
<div style="clear: both;"></div>
</div></li>
{% endfor %}
{% endwith %}
</ul>
<button type="submit" class="btn btn-primary btn-sm"><i class="icon-truck"></i> Migrate</button>
</form>
......@@ -113,8 +113,11 @@
<i class="icon-tasks icon-2x"></i><br>
{% trans "Resources" %}</a>
</li>
<li {% if not instance.is_console_available %}class="disabled"{% endif %}>
<li {% if not instance.is_console_available %}class="disabled">
<a href="#" data-toggle="pill_" data-target="#_console" class="text-center">
{% else %}>
<a href="#console" data-toggle="pill" data-target="#_console" class="text-center">
{% endif %}
<i class="icon-desktop icon-2x"></i><br>
{% trans "Console" %}</a></li>
<li>
......
......@@ -5,8 +5,10 @@ import re
from datetime import datetime
import requests
from django.conf import settings
from django.contrib.auth.models import User, Group
from django.contrib.auth.views import redirect_to_login
from django.contrib.auth.views import login, redirect_to_login
from django.contrib.auth.views import login
from django.contrib.messages import warning
from django.core.exceptions import (
PermissionDenied, SuspiciousOperation,
......@@ -32,7 +34,7 @@ from braces.views import (
from .forms import (
VmCustomizeForm, TemplateForm, LeaseForm, NodeForm, HostForm,
DiskAddForm,
DiskAddForm, CircleAuthenticationForm,
)
from .tables import (VmListTable, NodeListTable, NodeVmListTable,
TemplateListTable, LeaseListTable, GroupListTable,)
......@@ -45,6 +47,15 @@ from dashboard.models import Favourite, Profile
logger = logging.getLogger(__name__)
def search_user(keyword):
try:
return User.objects.get(username=keyword)
except User.DoesNotExist:
return User.objects.get(email=keyword)
except User.DoesNotExist:
return User.objects.get(profile__org_id=keyword)
# github.com/django/django/blob/stable/1.6.x/django/contrib/messages/views.py
class SuccessMessageMixin(object):
"""
......@@ -1491,11 +1502,7 @@ class TransferOwnershipView(LoginRequiredMixin, DetailView):
def post(self, request, *args, **kwargs):
try:
new_owner = User.objects.get(username=request.POST['name'])
except User.DoesNotExist:
new_owner = User.objects.get(email=request.POST['name'])
except User.DoesNotExist:
new_owner = User.objects.get(profile__org_id=request.POST['name'])
new_owner = search_user(request.POST['name'])
except User.DoesNotExist:
messages.error(request, _('Can not find specified user.'))
return self.get(request, *args, **kwargs)
......@@ -1873,7 +1880,7 @@ class VmMigrateView(SuperuserRequiredMixin, TemplateView):
'template': 'dashboard/_vm-migrate.html',
'box_title': _('Migrate %(name)s' % {'name': vm.name}),
'ajax_title': True,
'vm': kwargs['pk'],
'vm': vm,
'nodes': [n for n in Node.objects.filter(enabled=True)
if n.state == "ONLINE"]
})
......@@ -1890,3 +1897,12 @@ class VmMigrateView(SuperuserRequiredMixin, TemplateView):
messages.error(self.request, _("You didn't select a node!"))
return redirect("%s#activity" % vm.get_absolute_url())
def circle_login(request):
authentication_form = CircleAuthenticationForm
extra_context = {
'saml2': hasattr(settings, "SAML_CONFIG")
}
return login(request, authentication_form=authentication_form,
extra_context=extra_context)
......@@ -2,7 +2,7 @@
from itertools import islice, chain
import logging
from netaddr import IPSet
from netaddr import IPSet, EUI
from django.contrib.auth.models import User
from django.db import models
......@@ -631,6 +631,8 @@ class Host(models.Model):
if self.shared_ip and public:
res = Record.objects.filter(type='A',
address=self.pub_ipv4)
if res.count() < 1:
return unicode(self.pub_ipv4)
else:
res = self.record_set.filter(type='A',
address=self.ipv4)
......@@ -706,6 +708,17 @@ class Host(models.Model):
def get_absolute_url(self):
return ('network.host', None, {'pk': self.pk})
@property
def eui(self):
return EUI(self.mac)
@property
def hw_vendor(self):
try:
return self.eui.oui.registration().org
except:
return None
class Firewall(models.Model):
name = models.CharField(max_length=20, unique=True,
......
from django.test import TestCase
from django.contrib.auth.models import User
from ..admin import HostAdmin
from firewall.models import Vlan, Domain, Host
from firewall.models import Vlan, Domain, Record, Host
from django.forms import ValidationError
......@@ -82,3 +82,32 @@ class GetNewAddressTestCase(TestCase):
Host(hostname='h-6', mac='01:02:03:04:05:06',
ipv4='10.0.0.6', vlan=self.vlan, owner=self.u1).save()
self.assertEqual(self.vlan.get_new_address()['ipv4'], '10.0.0.2')
class HostGetHostnameTestCase(TestCase):
def setUp(self):
self.u1 = User.objects.create(username='user1')
self.u1.save()
self.d = Domain(name='example.org', owner=self.u1)
self.d.save()
Record.objects.all().delete()
self.vlan = Vlan(vid=1, name='test', network4='10.0.0.0/24',
network6='2001:738:2001:4031::/80', domain=self.d,
owner=self.u1, network_type='portforward',
snat_ip='10.1.1.1')
self.vlan.save()
self.h = Host(hostname='h', mac='01:02:03:04:05:00', ipv4='10.0.0.1',
vlan=self.vlan, owner=self.u1, shared_ip=True,
pub_ipv4=self.vlan.snat_ip)
self.h.save()
def test_issue_93_wo_record(self):
self.assertEqual(self.h.get_hostname(proto='ipv4', public=True),
unicode(self.h.pub_ipv4))
def test_issue_93_w_record(self):
self.r = Record(name='vm', type='A', domain=self.d, owner=self.u1,
address=self.vlan.snat_ip)
self.r.save()
self.assertEqual(self.h.get_hostname(proto='ipv4', public=True),
self.r.fqdn)
......@@ -36,6 +36,9 @@ class GroupTable(Table):
class HostTable(Table):
hostname = LinkColumn('network.host', args=[A('pk')])
mac = TemplateColumn(
template_name="network/columns/mac.html"
)
class Meta:
model = Host
......
{% load i18n %}
<span title="{% blocktrans with vendor=record.hw_vendor|default:"n/a" %}Vendor: {{vendor}}{% endblocktrans %}">{{ record.mac }}</span>
{% load i18n %}
{% load staticfiles %}
{% get_current_language as LANGUAGE_CODE %}
<html>
<head>
<meta charset="utf-8">
<title>{% block title %}{% endblock %} | CIRCLE</title>
<meta name="description" content="">
<meta name="author" content="">
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css">
<link href="//netdna.bootstrapcdn.com/font-awesome/3.2.1/css/font-awesome.css" rel="stylesheet">
<style type="text/css">
html, body {
background-color: #eee;
}
body {
margin-top: 40px;
}
.container {
width: 600px;
}
ul, li {
list-style: none;
margin: 0;
padding: 0;
}
.container > .content {
background-color: #fff;
padding: 20px;
-webkit-border-radius: 10px 10px 10px 10px;
-moz-border-radius: 10px 10px 10px 10px;
border-radius: 10px 10px 10px 10px;
-webkit-box-shadow: 0 1px 2px rgba(0,0,0,.15);
-moz-box-shadow: 0 1px 2px rgba(0,0,0,.15);
box-shadow: 0 1px 2px rgba(0,0,0,.15);
}
.login-form-errors .alert {
margin-right: 30px;
margin-left: 30px;
}
.login-form {
margin-top: 40px;
padding: 0 10px;
}
.login-form form {
padding: 0 20px;
}
.input-group {
margin-bottom: 10px;
}
.input-group-addon {
width: 38px;
}
.form-group label {
margin-top: 20px;
}
#submit-password-button {
margin-top: 15px;
}
/* fix for crispy-forms' html */
.form-group {
margin-bottom: 0px;
}
.help-block {
display: none;
}
</style>
</head>
<body>
<div class="container">
<div class="content">
{% block content %}{% endblock %}
</div>
</div> <!-- /container -->
</body>
</html>
{% extends "base.html" %}
{% extends "registration/base.html" %}
{% load i18n %}
{% load staticfiles %}
{% load crispy_forms_tags %}
{% get_current_language as LANGUAGE_CODE %}
{% block title %}{% trans "Login" %}{% endblock %}
{% block content %}
<form action="" method="POST">
{% csrf_token %}
{{ form }}
<input type="submit" value="LOGIN" />
</form>
<div class="row">
{% if form.password.errors or form.username.errors %}
<div class="login-form-errors">
{% include "display-form-errors.html" %}
</div>
{% endif %}
<div class="col-sm-{% if saml2 %}6{% else %}12{% endif %}">
<div class="login-form">
<form action="" method="POST">
{% csrf_token %}
{% crispy form %}
</form>
</div>
</div>
{% if saml2 %}
<div class="col-sm-6">
<h4 style="padding-top: 0; margin-top: 0;">{% trans "Login with SSO" %}</h4>
<a href="{% url "saml2_login" %}">{% trans "Click here!" %}</a>
</div>
{% endif %}
</div>
<div class="row">
<div class="col-sm-12">
<a class="pull-right" href="{% url "accounts.password-reset" %}">{% trans "Forgot your password?" %}</a>
</div>
</div>
{% endblock %}
{% extends "registration/base.html" %}
{% load i18n %}
{% load crispy_forms_tags %}
{% get_current_language as LANGUAGE_CODE %}
{% block title %}{% trans "Password reset complete" %}{% endblock %}
{% block content %}
<div class="row">
<div class="login-form-errors">
{% include "display-form-errors.html" %}
</div>
<div class="col-sm-12">
<div class="alert alert-success">
{% trans "Password change successful!" %}
<a href="{% url "accounts.login" %}">{% trans "Click here to login" %}</a>
</div>
</div>
</div>
{% endblock %}
{% extends "registration/base.html" %}
{% load i18n %}
{% load crispy_forms_tags %}
{% get_current_language as LANGUAGE_CODE %}
{% block title %}{% trans "Password reset confirm" %}{% endblock %}
{% block content %}
<div class="row">
<div class="login-form-errors">
{% include "display-form-errors.html" %}
</div>
<div class="col-sm-12">
<div style="margin: 0 0 25px 0;">
{% blocktrans %}Please enter your new password twice so we can verify you typed it in correctly!{% endblocktrans %}
</div>
{% if form %}
{% crispy form %}
{% else %}
<div class="alert alert-warning">
{% url "accounts.password-reset" as url %}
{% blocktrans with url=url %}This token is expired, please <a href="{{ url }}">request</a> a new password reset link again!{% endblocktrans %}
</div>
{% endif %}
</div>
</div>
{% endblock %}
{% extends "registration/base.html" %}
{% load i18n %}
{% load crispy_forms_tags %}
{% get_current_language as LANGUAGE_CODE %}
{% block title %}{% trans "Password reset done" %}{% endblock %}
{% block content %}
<div class="row">
<div class="login-form-errors">
{% include "display-form-errors.html" %}
</div>
<div class="col-sm-12">
<div class="pull-right"><a href="{% url "accounts.login" %}">{% trans "Back to login" %}</a></div>
{% trans "We have sent you an email about your next steps!" %}
</div>
</div>
{% endblock %}
{% extends "registration/base.html" %}
{% load i18n %}
{% load crispy_forms_tags %}
{% get_current_language as LANGUAGE_CODE %}
{% block title %}{% trans "Password reset" %}{% endblock %}
{% block content %}
<div class="row">
<div class="login-form-errors">
{% include "display-form-errors.html" %}
</div>
<div class="col-sm-12">
<div class="pull-right"><a href="{% url "accounts.login" %}">{% trans "Back to login" %}</a></div>
<h4 style="margin: 0 0 25px 0;">{% blocktrans %}Enter your email address to reset your password!{% endblocktrans %}</h4>
<form action="" method="POST">
{% csrf_token %}
{% crispy form %}
</form>
</div>
</div>
{% endblock %}
......@@ -672,10 +672,13 @@ class Instance(AclBase, VirtualMachineDescModel, TimeStampedModel):
self.pw))
self.save()
def __schedule_vm(self, act):
"""Schedule the virtual machine.
def select_node(self):
"""Returns the node the VM should be deployed or migrated to.
"""
return scheduler.select_node(self, Node.objects.all())
:param self: The virtual machine.
def __schedule_vm(self, act):
"""Schedule the virtual machine as part of a higher level activity.
:param act: Parent activity.
"""
......@@ -685,7 +688,7 @@ class Instance(AclBase, VirtualMachineDescModel, TimeStampedModel):
# Schedule
if self.node is None:
self.node = scheduler.select_node(self, Node.objects.all())
self.node = self.select_node()
self.save()
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment