Commit 5912d4e8 by Kálmán Viktor

Merge branch 'master' into feature-template-wizard

Conflicts:
	circle/dashboard/urls.py
	requirements/local.txt
parents 0a56f429 c7b205a4
......@@ -177,14 +177,15 @@ class AclBase(Model):
@classmethod
def get_objects_with_level(cls, level, user,
group_also=True, owner_also=False):
group_also=True, owner_also=False,
disregard_superuser=False):
logger.debug('%s.get_objects_with_level(%s,%s) called',
unicode(cls), unicode(level), unicode(user))
if user is None or not user.is_authenticated():
return cls.objects.none()
if getattr(user, 'is_superuser', False):
if getattr(user, 'is_superuser', False) and not disregard_superuser:
logger.debug('- superuser granted')
return cls.objects
return cls.objects.all()
if isinstance(level, basestring):
level = cls.get_level_object(level)
logger.debug("- level set by str: %s", unicode(level))
......
......@@ -161,6 +161,20 @@ class AclUserTest(TestCase):
self.assertItemsEqual(
TestModel.get_objects_with_level('alfa', self.u2), [i2])
def test_get_objects_with_level_for_superuser(self):
i1 = TestModel.objects.create(normal_field='Hello1')
i2 = TestModel.objects.create(normal_field='Hello2')
i1.set_level(self.u1, 'alfa')
i2.set_level(self.us, 'alfa')
self.assertItemsEqual(
TestModel.get_objects_with_level('alfa', self.u1), [i1])
self.assertItemsEqual(
TestModel.get_objects_with_level('alfa', self.us), [i1, i2])
self.assertItemsEqual(
TestModel.get_objects_with_level('alfa', self.us,
disregard_superuser=True), [i2])
def test_get_objects_with_level_for_group(self):
i1 = TestModel.objects.create(normal_field='Hello1')
i2 = TestModel.objects.create(normal_field='Hello2')
......
from base import * # noqa
from .base import * # noqa
########## TEST SETTINGS
TEST_RUNNER = 'discover_runner.DiscoverRunner'
TEST_DISCOVER_TOP_LEVEL = SITE_ROOT
TEST_DISCOVER_ROOT = SITE_ROOT
TEST_DISCOVER_PATTERN = "test_*.py"
########## IN-MEMORY TEST DATABASE
DATABASES = {
"default": {
......@@ -21,7 +16,6 @@ SOUTH_TESTS_MIGRATE = False
INSTALLED_APPS += (
'acl.tests',
'django_nose',
)
CACHES = {
......
......@@ -62,8 +62,9 @@ class Operation(object):
For more information, check the synchronous call's documentation.
"""
logger.info("%s called asynchronously with the following parameters: "
"%r", self.__class__.__name__, kwargs)
logger.info("%s called asynchronously on %s with the following "
"parameters: %r", self.__class__.__name__, self.subject,
kwargs)
activity = self.__prelude(kwargs)
return self.async_operation.apply_async(args=(self.id,
self.subject.pk,
......@@ -84,8 +85,9 @@ class Operation(object):
* user: The User invoking the operation. If this argument is not
present, it'll be provided with a default value of None.
"""
logger.info("%s called (synchronously) with the following parameters: "
"%r", self.__class__.__name__, kwargs)
logger.info("%s called (synchronously) on %s with the following "
"parameters: %r", self.__class__.__name__, self.subject,
kwargs)
activity = self.__prelude(kwargs)
return self._exec_op(activity=activity, **kwargs)
......
......@@ -187,7 +187,7 @@ html {
height: 300px;
}
#vm-details-rename, #vm-details-rename *, #vm-details-h1-name, #vm-list-rename, #vm-list-rename *,
#vm-details-rename, #vm-details-h1-name, #vm-details-rename ,
#node-details-rename, #node-details-rename *, #node-details-h1-name, #node-list-rename, #node-list-rename *#group-details-rename, #group-details-rename *, #group-details-h1-name, #group-list-rename, #group-list-rename * {
display: inline;
......@@ -197,7 +197,11 @@ html {
display: none;
}
#vm-details-rename-name, #node-details-rename-name, #group-details-rename-name {
.vm-details-home-name {
max-width: 401px;
}
#node-details-rename-name, #group-details-rename-name {
max-width: 160px;
}
......@@ -516,3 +520,23 @@ footer a, footer a:hover, footer a:visited {
overflow: hidden;
padding-left: 10px;
}
#vm-details-home-description {
display: inline-block;
position: relative;
}
#vm-details-home-description textarea {
min-width: 240px;
min-height: 250px;
}
.vm-details-home-edit-description-click, .vm-details-home-edit-name-click {
cursor: pointer;
}
.vm-details-description-submit {
position: absolute;
bottom: 10px;
right: 20px;
}
......@@ -321,7 +321,8 @@ function deleteObject(data) {
// no need to remove them from DOM
$('a[data-disk-pk="' + data.pk + '"]').parent("li").fadeOut();
$('a[data-disk-pk="' + data.pk + '"]').parent("h4").fadeOut();
} else {
}
else {
$('a[data-'+data['type']+'-pk="' + data['pk'] + '"]').closest('tr').fadeOut(function() {
$(this).remove();
});
......
......@@ -31,33 +31,6 @@ $(function() {
return false;
});
/* rename */
$("#vm-details-h1-name, .vm-details-rename-button").click(function() {
$("#vm-details-h1-name").hide();
$("#vm-details-rename").css('display', 'inline');
$("#vm-details-rename-name").focus();
});
/* rename ajax */
$('#vm-details-rename-submit').click(function() {
var name = $('#vm-details-rename-name').val();
$.ajax({
method: 'POST',
url: location.href,
data: {'new_name': name},
headers: {"X-CSRFToken": getCookie('csrftoken')},
success: function(data, textStatus, xhr) {
$("#vm-details-h1-name").html(data['new_name']).show();
$('#vm-details-rename').hide();
// addMessage(data['message'], "success");
},
error: function(xhr, textStatus, error) {
addMessage("Error during renaming!", "danger");
}
});
return false;
});
/* remove tag */
$('.vm-details-remove-tag').click(function() {
var to_remove = $.trim($(this).parent('div').text());
......@@ -150,8 +123,7 @@ $(function() {
/* add network button */
$("#vm-details-network-add").click(function() {
$("#vm-details-network-add-for-form").html($("#vm-details-network-add-form").html());
$('input[name="new_network_managed"]').tooltip();
$("#vm-details-network-add-form").toggle();
return false;
});
......@@ -165,6 +137,126 @@ $(function() {
$(".vm-details-help-button").click(function() {
$(".vm-details-help").stop().slideToggle();
});
/* for interface remove buttons */
$('.interface-remove').click(function() {
var interface_pk = $(this).data('interface-pk');
addModalConfirmation(removeInterface,
{ 'url': '/dashboard/interface/' + interface_pk + '/delete/',
'data': [],
'pk': interface_pk,
'type': "interface",
});
return false;
});
/* removing interface post */
function removeInterface(data) {
$.ajax({
type: 'POST',
url: data['url'],
headers: {"X-CSRFToken": getCookie('csrftoken')},
success: function(re, textStatus, xhr) {
/* remove the html element */
$('a[data-interface-pk="' + data.pk + '"]').closest("div").fadeOut();
/* add the removed element to the list */
network_select = $('select[name="new_network_vlan"]');
name_html = (re.removed_network.managed ? "": "") + " " + re.removed_network.vlan;
option_html = '<option value="' + re.removed_network.vlan_pk + '">' + name_html + '</option>';
// if it's -1 then it's a dummy placeholder so we can use .html
if($("option", network_select)[0].value === "-1") {
network_select.html(option_html);
network_select.next("div").children("button").prop("disabled", false);
} else {
network_select.append(option_html);
}
},
error: function(xhr, textStatus, error) {
addMessage('Uh oh :(', 'danger')
}
});
}
/* rename */
$("#vm-details-h1-name, .vm-details-rename-button").click(function() {
$("#vm-details-h1-name").hide();
$("#vm-details-rename").css('display', 'inline');
$("#vm-details-rename-name").focus();
});
/* rename in home tab */
$(".vm-details-home-edit-name-click").click(function() {
$(".vm-details-home-edit-name-click").hide();
$("#vm-details-home-rename").show();
$("input", $("#vm-details-home-rename")).focus();
});
/* rename ajax */
$('.vm-details-rename-submit').click(function() {
var name = $(this).parent("span").prev("input").val();
$.ajax({
method: 'POST',
url: location.href,
data: {'new_name': name},
headers: {"X-CSRFToken": getCookie('csrftoken')},
success: function(data, textStatus, xhr) {
$(".vm-details-home-edit-name").text(data['new_name']).show();
$(".vm-details-home-edit-name").parent("div").show();
$(".vm-details-home-edit-name-click").show();
$(".vm-details-home-rename-form-div").hide();
// update the inputs too
$(".vm-details-rename-submit").parent("span").prev("input").val(data['new_name']);
},
error: function(xhr, textStatus, error) {
addMessage("Error during renaming!", "danger");
}
});
return false;
});
/* update description click */
$(".vm-details-home-edit-description-click").click(function() {
$(".vm-details-home-edit-description-click").hide();
$("#vm-details-home-description").show();
return false;
});
/* description update ajax */
$('.vm-details-description-submit').click(function() {
var description = $(this).prev("textarea").val();
$.ajax({
method: 'POST',
url: location.href,
data: {'new_description': description},
headers: {"X-CSRFToken": getCookie('csrftoken')},
success: function(data, textStatus, xhr) {
var new_desc = data['new_description'];
/* we can't simply use $.text, because we need new lines */
var tagsToReplace = {
'&': "&amp;",
'<': "&lt;",
'>': "&gt;",
};
new_desc = new_desc.replace(/[&<>]/g, function(tag) {
return tagsToReplace[tag] || tag;
});
$(".vm-details-home-edit-description")
.html(new_desc.replace(/\n/g, "<br />"));
$(".vm-details-home-edit-description-click").show();
$("#vm-details-home-description").hide();
// update the textareia
$("vm-details-home-description textarea").text(data['new_description']);
},
error: function(xhr, textStatus, error) {
addMessage("Error during renaming!", "danger");
}
});
return false;
});
});
......
......@@ -36,14 +36,18 @@
{% include "dashboard/vm-detail/_operations.html" %}
</div>
<h1>
<div id="vm-details-rename">
<div id="vm-details-rename" class="vm-details-home-rename-form-div">
<form action="" method="POST" id="vm-details-rename-form">
{% csrf_token %}
<input id="vm-details-rename-name" class="form-control" name="new_name" type="text" value="{{ instance.name }}"/>
<button type="submit" id="vm-details-rename-submit" class="btn">{% trans "Rename" %}</button>
<div class="input-group vm-details-home-name">
<input id="vm-details-rename-name" class="form-control input-sm" name="new_name" type="text" value="{{ instance.name }}"/>
<span class="input-group-btn">
<button type="submit" class="btn btn-sm vm-details-rename-submit">{% trans "Rename" %}</button>
</span>
</div>
</form>
</div>
<div id="vm-details-h1-name">
<div id="vm-details-h1-name" class="vm-details-home-edit-name">
{{ instance.name }}
</div>
<small>{{ instance.primary_host.get_fqdn }}</small>
......
......@@ -4,8 +4,46 @@
<dl>
<dt>{% trans "System" %}:</dt>
<dd><i class="icon-{{ os_type_icon }}"></i> {{ instance.system }}</dd>
<dt style="margin-top: 5px;">{% trans "Description" %}:</dt>
<dd><small>{{ instance.description }}</small></dd>
<dt style="margin-top: 5px;">
{% trans "Name" %}:
<a href="#" class="vm-details-home-edit-name-click"><i class="icon-pencil"></i></a>
</dt>
<dd>
<div class="vm-details-home-edit-name-click">
<small class="vm-details-home-edit-name">{{ instance.name }}</small>
</div>
<div class="js-hidden vm-details-home-rename-form-div" id="vm-details-home-rename">
<form method="POST">
{% csrf_token %}
<div class="input-group">
<input type="text" name="new_name" value="{{ instance.name }}" class="form-control input-sm"/>
<span class="input-group-btn">
<button type="submit" class="btn btn-success btn-sm vm-details-rename-submit">
<i class="icon-pencil"></i> {% trans "Rename" %}
</button>
</span>
</div>
</form>
</div>
</dd>
<dt style="margin-top: 5px;">
{% trans "Description" %}:
<a href="#" class="vm-details-home-edit-description-click"><i class="icon-pencil"></i></a>
</dt>
<dd>
{% csrf_token %}
<div class="vm-details-home-edit-description-click">
<small class="vm-details-home-edit-description">{{ instance.description|linebreaks }}</small>
</div>
<div id="vm-details-home-description" class="js-hidden">
<form method="POST">
<textarea name="new_description" class="form-control">{{ instance.description }}</textarea>
<button type="submit" class="btn btn-xs btn-success vm-details-description-submit">
<i class="icon-pencil"></i> {% trans "Update" %}
</button>
</form>
</div>
</dd>
</dl>
<h4>{% trans "Expiration" %} {% if instance.is_expiring %}<i class="icon-warning-sign text-danger"></i>{% endif %}
......
......@@ -6,13 +6,50 @@
{% trans "Interfaces" %}
</h2>
<div class="row" id="vm-details-network-add-for-form">
<div class="js-hidden row" id="vm-details-network-add-form">
<div class="col-md-12">
<div>
<hr />
<h3>
{% trans "Add new network interface!" %}
</h3>
<form method="POST" action="">
{% csrf_token %}
<div class="input-group" style="max-width: 330px;">
<select name="new_network_vlan" class="form-control font-awesome-font">
{% for v in vlans %}
<option value="{{ v.pk }}">
{% if v.managed %}
&#xf0ac;
{% else %}
&#xf0c1;
{% endif %}
{{ v.name }}
</option>
{% empty %}
<option value="-1">No more networks!</option>
{% endfor %}
</select>
<div class="input-group-btn">
<button {% if vlans|length == 0 %}disabled{% endif %}
type="submit" class="btn btn-success"><i class="icon-plus-sign"></i></button>
</div>
</div>
</form>
<hr />
</div>
</div>
</div>
{% for i in instance.interface_set.all %}
<div>
<h3 class="list-group-item-heading dashboard-vm-details-network-h3">
<i class="icon-{% if i.host %}globe{% else %}link{% endif %}"></i> {{ i.vlan.name }}
{% if not i.host %}(unmanaged) <a href="#" class="btn btn-danger btn-xs">{% trans "remove" %}</a>{% endif %}
{% if not i.host%}({% trans "unmanaged" %}){% endif %}
<a href="{% url "dashboard.views.interface-delete" pk=i.pk %}?next={{ request.path }}" class="btn btn-danger btn-xs interface-remove"
data-interface-pk="{{ i.pk }}">
{% trans "remove" %}
</a>
</h3>
{% if i.host %}
<div class="row">
......@@ -109,31 +146,5 @@
</div>
</div>
{% endif %}
{% endfor %}
<div class="js-hidden row" id="vm-details-network-add-form">
<div class="col-md-12">
<div>
<hr />
<h3>
{% trans "Add new network interface!" %}
</h3>
<form method="POST" action="">
{% csrf_token %}
<div class="input-group" style="max-width: 330px;">
<select name="new_network_vlan" class="form-control">
{% if vlans|length == 0 %}
<option value="-1">No more networks!</option>
{% endif %}
{% for v in vlans %}
<option value="{{ v.pk }}">{{ v.name }}</option>
{% endfor %}
</select>
<div class="input-group-btn">
<button type="submit" class="btn btn-success"><i class="icon-plus-sign"></i></button>
</div>
</div>
</form>
<hr />
</div>
</div>
</div>
{% endfor %}
import json
from unittest import skip
from django.test import TestCase
from django.test.client import Client
from django.contrib.auth.models import User, Group
from django.core.exceptions import SuspiciousOperation
from django.core.urlresolvers import reverse
from django.contrib.auth.models import Permission
from django.contrib.auth import authenticate
......@@ -174,6 +175,46 @@ class VmDetailTest(LoginMixin, TestCase):
self.assertEqual(response.status_code, 302)
self.assertEqual(inst.interface_set.count(), interface_count + 1)
def test_permitted_network_delete(self):
c = Client()
self.login(c, "user1")
inst = Instance.objects.get(pk=1)
inst.set_level(self.u1, 'owner')
inst.add_interface(vlan=Vlan.objects.get(pk=1), user=self.us)
iface_count = inst.interface_set.count()
c.post("/dashboard/interface/1/delete/")
self.assertEqual(inst.interface_set.count(), iface_count - 1)
def test_permitted_network_delete_w_ajax(self):
c = Client()
self.login(c, "user1")
inst = Instance.objects.get(pk=1)
inst.set_level(self.u1, 'owner')
vlan = Vlan.objects.get(pk=1)
inst.add_interface(vlan=vlan, user=self.us)
iface_count = inst.interface_set.count()
response = c.post("/dashboard/interface/1/delete/",
HTTP_X_REQUESTED_WITH='XMLHttpRequest')
removed_network = json.loads(response.content)['removed_network']
self.assertEqual(removed_network['vlan'], vlan.name)
self.assertEqual(removed_network['vlan_pk'], vlan.pk)
self.assertEqual(removed_network['managed'], vlan.managed)
self.assertEqual(inst.interface_set.count(), iface_count - 1)
def test_unpermitted_network_delete(self):
c = Client()
self.login(c, "user1")
inst = Instance.objects.get(pk=1)
inst.set_level(self.u1, 'user')
inst.add_interface(vlan=Vlan.objects.get(pk=1), user=self.us)
iface_count = inst.interface_set.count()
response = c.post("/dashboard/interface/1/delete/")
self.assertEqual(iface_count, inst.interface_set.count())
self.assertEqual(response.status_code, 403)
def test_create_vm_w_unpermitted_network(self):
c = Client()
self.login(c, 'user2')
......@@ -559,6 +600,41 @@ class VmDetailTest(LoginMixin, TestCase):
self.assertEqual(response.status_code, 302)
self.assertEqual(instance_count + 2, Instance.objects.all().count())
def test_unpermitted_description_update(self):
c = Client()
self.login(c, "user1")
inst = Instance.objects.get(pk=1)
inst.set_level(self.u2, 'owner')
inst.set_level(self.u1, 'user')
old_desc = inst.description
response = c.post("/dashboard/vm/1/", {'new_description': 'test1234'})
self.assertEqual(response.status_code, 403)
self.assertEqual(Instance.objects.get(pk=1).description, old_desc)
def test_permitted_description_update_w_ajax(self):
c = Client()
self.login(c, "user1")
inst = Instance.objects.get(pk=1)
inst.set_level(self.u1, 'owner')
response = c.post("/dashboard/vm/1/", {'new_description': "naonyo"},
HTTP_X_REQUESTED_WITH='XMLHttpRequest')
self.assertEqual(response.status_code, 200)
self.assertEqual(json.loads(response.content)['new_description'],
"naonyo")
self.assertEqual(Instance.objects.get(pk=1).description, "naonyo")
def test_permitted_description_update(self):
c = Client()
self.login(c, "user1")
inst = Instance.objects.get(pk=1)
inst.set_level(self.u1, 'owner')
response = c.post("/dashboard/vm/1/", {'new_description': "naonyo"})
self.assertEqual(response.status_code, 302)
self.assertEqual(Instance.objects.get(pk=1).description, "naonyo")
class NodeDetailTest(LoginMixin, TestCase):
fixtures = ['test-vm-fixture.json', 'node.json']
......@@ -820,8 +896,8 @@ class TransferOwnershipViewTest(LoginMixin, TestCase):
c2 = self.u2.notification_set.count()
c = Client()
self.login(c, 'user2')
with self.assertRaises(SuspiciousOperation):
c.post('/dashboard/vm/1/tx/')
response = c.post('/dashboard/vm/1/tx/')
assert response.status_code == 400
self.assertEqual(self.u2.notification_set.count(), c2)
def test_owned_offer(self):
......
......@@ -12,7 +12,7 @@ from .views import (
TemplateDelete, TemplateDetail, TemplateList, TransferOwnershipConfirmView,
TransferOwnershipView, vm_activity, VmCreate, VmDelete, VmDetailView,
VmDetailVncTokenView, VmGraphView, VmList, VmMassDelete, VmMigrateView,
VmRenewView, DiskRemoveView, get_disk_download_status,
VmRenewView, DiskRemoveView, get_disk_download_status, InterfaceDeleteView,
TemplateChoose, TemplateClone,
)
......@@ -114,6 +114,9 @@ urlpatterns = patterns(
url(r'^disk/(?P<pk>\d+)/status/$', get_disk_download_status,
name="dashboard.views.disk-status"),
url(r'^interface/(?P<pk>\d+)/delete/$', InterfaceDeleteView.as_view(),
name="dashboard.views.interface-delete"),
url(r'^profile/$', MyPreferencesView.as_view(),
name="dashboard.views.profile"),
)
......@@ -30,9 +30,8 @@ from django.template import RequestContext
from django.forms.models import inlineformset_factory
from django_tables2 import SingleTableView
from braces.views import (
LoginRequiredMixin, SuperuserRequiredMixin, AccessMixin
)
from braces.views import LoginRequiredMixin, SuperuserRequiredMixin
from braces.views._access import AccessMixin
from .forms import (
CircleAuthenticationForm, DiskAddForm, HostForm, LeaseForm, MyProfileForm,
......@@ -90,7 +89,7 @@ class IndexView(LoginRequiredMixin, TemplateView):
# instances
favs = Instance.objects.filter(favourite__user=self.request.user)
instances = Instance.get_objects_with_level(
'user', user).filter(destroyed_at=None)
'user', user, disregard_superuser=True).filter(destroyed_at=None)
display = list(favs) + list(set(instances) - set(favs))
for d in display:
d.fav = True if d in favs else False
......@@ -216,7 +215,7 @@ class VmDetailView(CheckedDetailView):
context['vlans'] = Vlan.get_objects_with_level(
'user', self.request.user
).exclude(
).exclude( # exclude already added interfaces
pk__in=Interface.objects.filter(
instance=self.get_object()).values_list("vlan", flat=True)
).all()
......@@ -239,6 +238,7 @@ class VmDetailView(CheckedDetailView):
options = {
'change_password': self.__change_password,
'new_name': self.__set_name,
'new_description': self.__set_description,
'new_tag': self.__add_tag,
'to_remove': self.__remove_tag,
'port': self.__add_port,
......@@ -309,8 +309,30 @@ class VmDetailView(CheckedDetailView):
)
else:
messages.success(request, success_message)
return redirect(reverse_lazy("dashboard.views.detail",
kwargs={'pk': self.object.pk}))
return redirect(self.object.get_absolute_url())
def __set_description(self, request):
self.object = self.get_object()
if not self.object.has_level(request.user, 'owner'):
raise PermissionDenied()
new_description = request.POST.get("new_description")
Instance.objects.filter(pk=self.object.pk).update(
**{'description': new_description})
success_message = _("VM description successfully updated!")
if request.is_ajax():
response = {
'message': success_message,
'new_description': new_description,
}
return HttpResponse(
json.dumps(response),
content_type="application/json"
)
else:
messages.success(request, success_message)
return redirect(self.object.get_absolute_url())
def __add_tag(self, request):
new_tag = request.POST.get('new_tag')
......@@ -393,7 +415,7 @@ class VmDetailView(CheckedDetailView):
if not self.object.has_level(request.user, 'owner'):
raise PermissionDenied()
vlan = Vlan.objects.get(pk=request.POST.get("new_network_vlan"))
vlan = get_object_or_404(Vlan, pk=request.POST.get("new_network_vlan"))
if not vlan.has_level(request.user, 'user'):
raise PermissionDenied()
try:
......@@ -481,6 +503,7 @@ class OperationView(DetailView):
class VmOperationView(OperationView):
model = Instance
context_object_name = 'instance' # much simpler to mock object
class VmMigrateView(VmOperationView):
......@@ -2353,6 +2376,7 @@ def get_disk_download_status(request, pk):
class InstanceActivityDetail(SuperuserRequiredMixin, DetailView):
model = InstanceActivity
context_object_name = 'instanceactivity' # much simpler to mock object
template_name = 'dashboard/instanceactivity_detail.html'
def get_context_data(self, **kwargs):
......@@ -2362,3 +2386,53 @@ class InstanceActivityDetail(SuperuserRequiredMixin, DetailView):
order_by('-started').select_related('user').
prefetch_related('children'))
return ctx
class InterfaceDeleteView(DeleteView):
model = Interface
def get_template_names(self):
if self.request.is_ajax():
return ['dashboard/confirm/ajax-delete.html']
else:
return ['dashboard/confirm/base-delete.html']
def get_context_data(self, **kwargs):
context = super(InterfaceDeleteView, self).get_context_data(**kwargs)
interface = self.get_object()
context['text'] = _("Are you sure you want to remove this interface "
"from <strong>%(vm)s</strong>?" %
{'vm': interface.instance.name})
return context
def delete(self, request, *args, **kwargs):
self.object = self.get_object()
instance = self.object.instance
if not instance.has_level(request.user, "owner"):
raise PermissionDenied()
instance.remove_interface(interface=self.object, user=request.user)
success_url = self.get_success_url()
success_message = _("Interface successfully deleted!")
if request.is_ajax():
return HttpResponse(
json.dumps(
{'message': success_message,
'removed_network': {
'vlan': self.object.vlan.name,
'vlan_pk': self.object.vlan.pk,
'managed': self.object.host is not None,
}}),
content_type="application/json",
)
else:
messages.success(request, success_message)
return HttpResponseRedirect("%s#network" % success_url)
def get_success_url(self):
redirect = self.request.POST.get("next")
if redirect:
return redirect
self.object.instance.get_absolute_url()
......@@ -2,7 +2,7 @@ import re
import logging
from collections import OrderedDict
from netaddr import IPAddress, AddrFormatError
from datetime import datetime, timedelta
from datetime import timedelta
from itertools import product
from .models import (Host, Rule, Vlan, Domain, Record, BlacklistItem,
......@@ -11,6 +11,7 @@ from .iptables import IptRule, IptChain
import django.conf
from django.db.models import Q
from django.template import loader, Context
from django.utils import timezone
settings = django.conf.settings.FIREWALL_SETTINGS
......@@ -134,7 +135,7 @@ class BuildFirewall:
def ipset():
week = datetime.now() - timedelta(days=2)
week = timezone.now() - timedelta(days=2)
filter_ban = (Q(type='tempban', modified_at__gte=week) |
Q(type='permban'))
return BlacklistItem.objects.filter(filter_ban).values('ipv4', 'reason')
......
......@@ -52,6 +52,8 @@
<ul class="nav navbar-nav">
{% include "network/menu.html" %}
</ul>
<a class="navbar-brand pull-right" href="{% url "dashboard.index" %}"
style="color: white; font-size: 10px;"><i class="icon-dashboard"></i> {% trans "dashboard" %}</a>
</div><!-- .collapse .navbar-collapse -->
</div><!-- navbar navbar-inverse navbar-fixed-top -->
<div class="container">
......@@ -75,7 +77,7 @@
<div class="footer-container container">
<footer>
<p class="pull-right"><a href="#">Vissza az oldal tetejére</a></p>
<p>&copy; 2013 BME Közigazgatási Informatikai Központ
<p>&copy; {{ COMPANY_NAME }}
</footer>
</div><!-- .footer-container .container -->
......
......@@ -46,18 +46,13 @@ def restore(disk, user):
disk.restore(task_uuid=restore.request.id, user=user)
class CreateFromURLTask(AbortableTask):
def __init__(self):
self.bind(celery)
def run(self, **kwargs):
Disk = kwargs.pop('cls')
Disk.create_from_url(url=kwargs.pop('url'),
task_uuid=create_from_url.request.id,
abortable_task=self,
**kwargs)
create_from_url = CreateFromURLTask()
@celery.task(base=AbortableTask, bind=True)
def create_from_url(self, **kwargs):
Disk = kwargs.pop('cls')
Disk.create_from_url(url=kwargs.pop('url'),
task_uuid=self.request.id,
abortable_task=self,
**kwargs)
@celery.task
......
......@@ -53,6 +53,7 @@ class Interface(Model):
class Meta:
app_label = 'vm'
db_table = 'vm_interface'
ordering = ("-vlan__managed", )
def __unicode__(self):
return 'cloud-' + str(self.instance.id) + '-' + str(self.vlan.vid)
......
......@@ -85,7 +85,6 @@ class DeployOperation(InstanceOperation):
id = 'deploy'
name = _("deploy")
description = _("Deploy new virtual machine with network.")
icon = 'play'
def on_commit(self, activity):
activity.resultant_state = 'RUNNING'
......@@ -122,7 +121,6 @@ class DestroyOperation(InstanceOperation):
id = 'destroy'
name = _("destroy")
description = _("Destroy virtual machine and its networks.")
icon = 'remove'
def on_commit(self, activity):
activity.resultant_state = 'DESTROYED'
......@@ -164,7 +162,6 @@ class MigrateOperation(InstanceOperation):
id = 'migrate'
name = _("migrate")
description = _("Live migrate running VM to another node.")
icon = 'truck'
def _operation(self, activity, user, system, to_node=None, timeout=120):
if not to_node:
......@@ -195,7 +192,6 @@ class RebootOperation(InstanceOperation):
id = 'reboot'
name = _("reboot")
description = _("Reboot virtual machine with Ctrl+Alt+Del signal.")
icon = 'refresh'
def _operation(self, activity, user, system, timeout=5):
self.instance.reboot_vm(timeout=timeout)
......@@ -226,7 +222,6 @@ class ResetOperation(InstanceOperation):
id = 'reset'
name = _("reset")
description = _("Reset virtual machine (reset button).")
icon = 'bolt'
def _operation(self, activity, user, system, timeout=5):
self.instance.reset_vm(timeout=timeout)
......@@ -243,7 +238,6 @@ class SaveAsTemplateOperation(InstanceOperation):
Template can be shared with groups and users.
Users can instantiate Virtual Machines from Templates.
""")
icon = 'save'
@staticmethod
def _rename(name):
......@@ -319,7 +313,6 @@ class ShutdownOperation(InstanceOperation):
id = 'shutdown'
name = _("shutdown")
description = _("Shutdown virtual machine with ACPI signal.")
icon = 'off'
def check_precond(self):
super(ShutdownOperation, self).check_precond()
......@@ -349,7 +342,6 @@ class ShutOffOperation(InstanceOperation):
id = 'shut_off'
name = _("shut off")
description = _("Shut off VM (plug-out).")
icon = 'ban-circle'
def on_commit(self, activity):
activity.resultant_state = 'STOPPED'
......@@ -376,7 +368,6 @@ class SleepOperation(InstanceOperation):
id = 'sleep'
name = _("sleep")
description = _("Suspend virtual machine with memory dump.")
icon = 'moon'
def check_precond(self):
super(SleepOperation, self).check_precond()
......@@ -416,7 +407,6 @@ class WakeUpOperation(InstanceOperation):
Power on Virtual Machine and load its memory from dump.
""")
icon = 'sun'
def check_precond(self):
super(WakeUpOperation, self).check_precond()
......
from datetime import datetime
from mock import Mock, MagicMock, patch, call
import types
from django.contrib.auth.models import User
from django.test import TestCase
......@@ -180,14 +181,18 @@ class InstanceActivityTestCase(TestCase):
def test_create_no_concurrency_check(self):
instance = MagicMock(spec=Instance)
instance.activity_log.filter.return_value.exists.return_value = True
with patch.object(InstanceActivity, '__new__'):
try:
InstanceActivity.create('test', instance,
concurrency_check=False)
except ActivityInProgressError:
raise AssertionError("'create' method checked for concurrent "
"activities.")
mock_instance_activity_cls = MagicMock(spec=InstanceActivity,
ACTIVITY_CODE_BASE='test')
original_create = InstanceActivity.create
mocked_create = types.MethodType(original_create.im_func,
mock_instance_activity_cls,
original_create.im_class)
try:
mocked_create('test', instance, concurrency_check=False)
except ActivityInProgressError:
raise AssertionError("'create' method checked for concurrent "
"activities.")
def test_create_sub_concurrency_check(self):
iaobj = MagicMock(spec=InstanceActivity)
......@@ -201,10 +206,10 @@ class InstanceActivityTestCase(TestCase):
iaobj.activity_code = 'test'
iaobj.children.filter.return_value.exists.return_value = True
with patch.object(InstanceActivity, '__new__'):
create_sub_func = InstanceActivity.create_sub
with patch('vm.models.activity.InstanceActivity'):
try:
InstanceActivity.create_sub(iaobj, 'test',
concurrency_check=False)
create_sub_func(iaobj, 'test', concurrency_check=False)
except ActivityInProgressError:
raise AssertionError("'create_sub' method checked for "
"concurrent activities.")
......
# This file is here because many Platforms as a Service look for
# requirements.txt in the root directory of a project.
# File is here because many Platforms as a Service look for
# requirements.txt in the root directory of a project.
-r requirements/production.txt
Django==1.5.2
amqp==1.4.5
anyjson==0.3.3
billiard==3.3.0.17
bpython==0.12
celery==3.0.23
django-braces==1.2.2
django-celery==3.0.23
celery==3.1.11
django-braces==1.4.0
django-celery==3.1.10
django-crispy-forms==1.4.0
django-model-utils==1.4.0
django-model-utils==2.0.3
django-sizefield==0.4
django-tables2==0.14.0
django-taggit==0.11.2
django-tables2==0.15.0
django-taggit==0.12
Django==1.6.3
docutils==0.11
Jinja2==2.7.2
kombu==3.0.15
logutils==0.3.3
netaddr==0.7.10
MarkupSafe==0.21
netaddr==0.7.11
nose==1.3.1
pip-tools==0.3.4
psycopg2==2.5.2
Pygments==1.6
pylibmc==1.3.0
python-dateutil==2.2
pytz==2014.2
requests==2.2.1
South==0.8.1
psycopg2==2.5.1
pylibmc
simplejson==3.4.0
six==1.6.1
South==0.8.4
sqlparse==0.1.11
# Local development dependencies go here
-r base.txt
coverage==3.6
django-discover-runner==0.4
django-debug-toolbar==0.9.4
Sphinx==1.2b1
coverage==3.7.1
django-debug-toolbar==1.1
Sphinx==1.2.2
django-statici18n==1.1
# Pro-tip: Try not to put anything here. There should be no dependency in
# production that isn't in development.
# production that isn't in development.
-r base.txt
gunicorn==0.17.4
uWSGI==2.0.3
uWSGI==2.0.4
# Test dependencies go here.
-r base.txt
coverage==3.6
django-discover-runner==0.4
django-nose==1.2
mock==1.0.1
coverage==3.7.1
factory-boy==2.3.1
mock==1.0.1
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