Commit a8fb5a86 by Bach Dániel

Merge branch 'feature-fix-acls'

Conflicts:
	circle/vm/operations.py
parents f7488817 f32bb524
# register a signal do update permissions every migration.
# This is based on app django_extensions update_permissions command
from south.signals import post_migrate
def update_permissions_after_migration(app, **kwargs):
"""
Update app permission just after every migration.
This is based on app django_extensions update_permissions
management command.
"""
from django.conf import settings
from django.db.models import get_app, get_models
from django.contrib.auth.management import create_permissions
create_permissions(get_app(app), get_models(), 2 if settings.DEBUG else 0)
post_migrate.connect(update_permissions_after_migration)
...@@ -20,7 +20,7 @@ from logging import getLogger ...@@ -20,7 +20,7 @@ from logging import getLogger
from .models import activity_context, has_suffix from .models import activity_context, has_suffix
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied, ImproperlyConfigured
logger = getLogger(__name__) logger = getLogger(__name__)
...@@ -30,7 +30,7 @@ class Operation(object): ...@@ -30,7 +30,7 @@ class Operation(object):
"""Base class for VM operations. """Base class for VM operations.
""" """
async_queue = 'localhost.man' async_queue = 'localhost.man'
required_perms = () required_perms = None
do_not_call_in_templates = True do_not_call_in_templates = True
abortable = False abortable = False
has_percentage = False has_percentage = False
...@@ -141,6 +141,9 @@ class Operation(object): ...@@ -141,6 +141,9 @@ class Operation(object):
pass pass
def check_auth(self, user): def check_auth(self, user):
if self.required_perms is None:
raise ImproperlyConfigured(
"Set required_perms to () if none needed.")
if not user.has_perms(self.required_perms): if not user.has_perms(self.required_perms):
raise PermissionDenied("%s doesn't have the required permissions." raise PermissionDenied("%s doesn't have the required permissions."
% user) % user)
......
...@@ -1240,6 +1240,24 @@ ...@@ -1240,6 +1240,24 @@
} }
}, },
{ {
"pk": 1367,
"model": "auth.permission",
"fields": {
"codename": "create_vm",
"name": "Can create a new VM.",
"content_type": 28
}
},
{
"pk": 1368,
"model": "auth.permission",
"fields": {
"codename": "access_console",
"name": "Can access the graphical console of a VM.",
"content_type": 28
}
},
{
"pk": 1, "pk": 1,
"model": "auth.group", "model": "auth.group",
"fields": { "fields": {
......
...@@ -25,6 +25,7 @@ from django.contrib.auth.forms import ( ...@@ -25,6 +25,7 @@ from django.contrib.auth.forms import (
) )
from django.contrib.auth.models import User, Group from django.contrib.auth.models import User, Group
from django.core.validators import URLValidator from django.core.validators import URLValidator
from django.core.exceptions import PermissionDenied, ValidationError
from crispy_forms.helper import FormHelper from crispy_forms.helper import FormHelper
from crispy_forms.layout import ( from crispy_forms.layout import (
...@@ -593,6 +594,17 @@ class TemplateForm(forms.ModelForm): ...@@ -593,6 +594,17 @@ class TemplateForm(forms.ModelForm):
n = self.instance.interface_set.values_list("vlan", flat=True) n = self.instance.interface_set.values_list("vlan", flat=True)
self.initial['networks'] = n self.initial['networks'] = n
self.allowed_fields = (
'name', 'access_method', 'description', 'system', 'tags')
if self.user.has_perm('vm.change_template_resources'):
self.allowed_fields += tuple(set(self.fields.keys()) -
set(['raw_data']))
if self.user.is_superuser:
self.allowed_fields += ('raw_data', )
for name, field in self.fields.items():
if name not in self.allowed_fields:
field.widget.attrs['disabled'] = 'disabled'
if not self.instance.pk and len(self.errors) < 1: if not self.instance.pk and len(self.errors) < 1:
self.instance.priority = 20 self.instance.priority = 20
self.instance.ram_size = 512 self.instance.ram_size = 512
...@@ -603,14 +615,35 @@ class TemplateForm(forms.ModelForm): ...@@ -603,14 +615,35 @@ class TemplateForm(forms.ModelForm):
return User.objects.get(pk=self.instance.owner.pk) return User.objects.get(pk=self.instance.owner.pk)
return self.user return self.user
def clean_raw_data(self): def _clean_fields(self):
# if raw_data has changed and the user is not superuser try:
if "raw_data" in self.changed_data and not self.user.is_superuser: old = InstanceTemplate.objects.get(pk=self.instance.pk)
old_raw_data = InstanceTemplate.objects.get( except InstanceTemplate.DoesNotExist:
pk=self.instance.pk).raw_data old = None
return old_raw_data for name, field in self.fields.items():
else: if name in self.allowed_fields:
return self.cleaned_data['raw_data'] value = field.widget.value_from_datadict(
self.data, self.files, self.add_prefix(name))
try:
if isinstance(field, forms.FileField):
initial = self.initial.get(name, field.initial)
value = field.clean(value, initial)
else:
value = field.clean(value)
self.cleaned_data[name] = value
if hasattr(self, 'clean_%s' % name):
value = getattr(self, 'clean_%s' % name)()
self.cleaned_data[name] = value
except ValidationError as e:
self._errors[name] = self.error_class(e.messages)
if name in self.cleaned_data:
del self.cleaned_data[name]
elif old:
if name == 'networks':
self.cleaned_data[name] = [
i.vlan for i in self.instance.interface_set.all()]
else:
self.cleaned_data[name] = getattr(old, name)
def save(self, commit=True): def save(self, commit=True):
data = self.cleaned_data data = self.cleaned_data
...@@ -624,6 +657,8 @@ class TemplateForm(forms.ModelForm): ...@@ -624,6 +657,8 @@ class TemplateForm(forms.ModelForm):
networks = InterfaceTemplate.objects.filter( networks = InterfaceTemplate.objects.filter(
template=self.instance).values_list("vlan", flat=True) template=self.instance).values_list("vlan", flat=True)
for m in data['networks']: for m in data['networks']:
if not m.has_level(self.user, "user"):
raise PermissionDenied()
if m.pk not in networks: if m.pk not in networks:
InterfaceTemplate(vlan=m, managed=m.managed, InterfaceTemplate(vlan=m, managed=m.managed,
template=self.instance).save() template=self.instance).save()
...@@ -635,10 +670,6 @@ class TemplateForm(forms.ModelForm): ...@@ -635,10 +670,6 @@ class TemplateForm(forms.ModelForm):
@property @property
def helper(self): def helper(self):
kwargs_raw_data = {}
if not self.user.is_superuser:
kwargs_raw_data['readonly'] = None
helper = FormHelper() helper = FormHelper()
helper.layout = Layout( helper.layout = Layout(
Field("name"), Field("name"),
...@@ -690,7 +721,7 @@ class TemplateForm(forms.ModelForm): ...@@ -690,7 +721,7 @@ class TemplateForm(forms.ModelForm):
_("Virtual machine settings"), _("Virtual machine settings"),
Field('access_method'), Field('access_method'),
Field('boot_menu'), Field('boot_menu'),
Field('raw_data', **kwargs_raw_data), Field('raw_data'),
Field('req_traits'), Field('req_traits'),
Field('description'), Field('description'),
Field("parent", type="hidden"), Field("parent", type="hidden"),
......
...@@ -192,6 +192,9 @@ ...@@ -192,6 +192,9 @@
}, },
mousedown: function(ev) { mousedown: function(ev) {
if (this.element[0].disabled) {
return false;
}
// Touch: Get the original event: // Touch: Get the original event:
if (this.touchCapable && ev.type === 'touchstart') { if (this.touchCapable && ev.type === 'touchstart') {
......
...@@ -11,13 +11,14 @@ $(function() { ...@@ -11,13 +11,14 @@ $(function() {
/* save resources */ /* save resources */
$('#vm-details-resources-save').click(function() { $('#vm-details-resources-save').click(function() {
$('i.icon-save', this).removeClass("icon-save").addClass("icon-refresh icon-spin"); $('i.icon-save', this).removeClass("icon-save").addClass("icon-refresh icon-spin");
var vm = $(this).data("vm");
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
url: location.href, url: "/dashboard/vm/" + vm + "/op/resources_change/",
data: $('#vm-details-resources-form').serialize(), data: $('#vm-details-resources-form').serialize(),
success: function(data, textStatus, xhr) { success: function(data, textStatus, xhr) {
addMessage(data['message'], 'success');
$("#vm-details-resources-save i").removeClass('icon-refresh icon-spin').addClass("icon-save"); $("#vm-details-resources-save i").removeClass('icon-refresh icon-spin').addClass("icon-save");
$('a[href="#activity"]').trigger("click");
}, },
error: function(xhr, textStatus, error) { error: function(xhr, textStatus, error) {
$("#vm-details-resources-save i").removeClass('icon-refresh icon-spin').addClass("icon-save"); $("#vm-details-resources-save i").removeClass('icon-refresh icon-spin').addClass("icon-save");
...@@ -330,7 +331,7 @@ function decideActivityRefresh() { ...@@ -330,7 +331,7 @@ function decideActivityRefresh() {
/* unescapes html got via the request, also removes whitespaces and replaces all ' with " */ /* unescapes html got via the request, also removes whitespaces and replaces all ' with " */
function unescapeHTML(html) { function unescapeHTML(html) {
return html.replace(/&lt;/g,'<').replace(/&gt;/g,'>').replace(/&amp;/g,'&').replace(/&ndash;/g, "–").replace(/\//g, "").replace(/'/g, '"').replace(/ /g, ''); return html.replace(/&lt;/g,'<').replace(/&gt;/g,'>').replace(/&amp;/g,'&').replace(/&ndash;/g, "–").replace(/\//g, "").replace(/'/g, '"').replace(/&#39;/g, "'").replace(/ /g, '');
} }
/* the html page contains some tags that were modified via js (titles for example), we delete these /* the html page contains some tags that were modified via js (titles for example), we delete these
...@@ -367,6 +368,14 @@ function checkNewActivity(only_status, runs) { ...@@ -367,6 +368,14 @@ function checkNewActivity(only_status, runs) {
$("[data-target=#_console]").attr("data-toggle", "_pill").attr("href", "#").parent("li").addClass("disabled"); $("[data-target=#_console]").attr("data-toggle", "_pill").attr("href", "#").parent("li").addClass("disabled");
} }
if(data['status'] == "STOPPED") {
$(".enabled-when-stopped").prop("disabled", false);
$(".hide-when-stopped").hide();
} else {
$(".enabled-when-stopped").prop("disabled", true);
$(".hide-when-stopped").show();
}
if(runs > 0 && decideActivityRefresh()) { if(runs > 0 && decideActivityRefresh()) {
setTimeout( setTimeout(
function() {checkNewActivity(only_status, runs + 1)}, function() {checkNewActivity(only_status, runs + 1)},
......
...@@ -16,10 +16,12 @@ ...@@ -16,10 +16,12 @@
<div class="clearfix"></div> <div class="clearfix"></div>
</div> </div>
{% endfor %} {% endfor %}
{% if perms.vm.create_base_template %}
<div class="panel panel-default template-choose-list-element"> <div class="panel panel-default template-choose-list-element">
<input type="radio" name="parent" value="base_vm"/> <input type="radio" name="parent" value="base_vm"/>
{% trans "Create a new base VM without disk" %} {% trans "Create a new base VM without disk" %}
</div> </div>
{% endif %}
<button type="submit" id="template-choose-next-button" class="btn btn-success pull-right">{% trans "Next" %}</button> <button type="submit" id="template-choose-next-button" class="btn btn-success pull-right">{% trans "Next" %}</button>
<div class="clearfix"></div> <div class="clearfix"></div>
</div> </div>
......
{% load i18n %} {% load i18n %}
<div class="btn-toolbar"> <div class="btn-toolbar">
{% if perms.vm.access_console %}
<button id="sendCtrlAltDelButton" class="btn btn-danger btn-sm">{% trans "Send Ctrl+Alt+Del" %}</button> <button id="sendCtrlAltDelButton" class="btn btn-danger btn-sm">{% trans "Send Ctrl+Alt+Del" %}</button>
<button id="sendPasswordButton" class="btn btn-default btn-sm">{% trans "Type password" %}</button> <button id="sendPasswordButton" class="btn btn-default btn-sm">{% trans "Type password" %}</button>
{% endif %}
<button id="getScreenshotButton" class="btn btn-info btn-sm pull-right" data-vm-pk="{{ instance.pk }}"><i class="icon-picture"></i> {% trans "Screenshot" %}</button> <button id="getScreenshotButton" class="btn btn-info btn-sm pull-right" data-vm-pk="{{ instance.pk }}"><i class="icon-picture"></i> {% trans "Screenshot" %}</button>
</div> </div>
{% if perms.vm.access_console %}
<div class="alert alert-info" id="noVNC_status"> <div class="alert alert-info" id="noVNC_status">
</div> </div>
{% endif %}
<div id="vm-console-screenshot"> <div id="vm-console-screenshot">
<button class="btn btn-danger btn-sm pull-right">{% trans "Close" %}</button> <button class="btn btn-danger btn-sm pull-right">{% trans "Close" %}</button>
...@@ -14,6 +18,7 @@ ...@@ -14,6 +18,7 @@
<hr /> <hr />
</div> </div>
{% if perms.vm.access_console %}
<canvas id="noVNC_canvas" width="640px" height="20px">Canvas not supported. <canvas id="noVNC_canvas" width="640px" height="20px">Canvas not supported.
</canvas> </canvas>
...@@ -22,3 +27,4 @@ ...@@ -22,3 +27,4 @@
var INCLUDE_URI = '{{ STATIC_URL }}dashboard/novnc/'; var INCLUDE_URI = '{{ STATIC_URL }}dashboard/novnc/';
var VNC_URL = "{{ vnc_url }}"; var VNC_URL = "{{ vnc_url }}";
</script> </script>
{% endif %}
...@@ -33,11 +33,20 @@ ...@@ -33,11 +33,20 @@
</div> </div>
</p> </p>
{% if can_change_resources %}
<p class="row"> <p class="row">
<div class="col-sm-12"> <div class="col-sm-12">
<button type="submit" class="btn btn-success btn-sm" id="vm-details-resources-save"><i class="icon-save"></i> {% trans "Save resources" %}</button> <button type="submit" class="btn btn-success btn-sm enabled-when-stopped" id="vm-details-resources-save"
data-vm="{{ instance.pk }}"
{% if not op.resources_change %}disabled{% endif %}>
<i class="icon-save"></i> {% trans "Save resources" %}
</button>
<span class="hide-when-stopped"
{% if op.resources_change %}style="display: none;"{% endif %}
>{% trans "Stop your VM to change resources." %}</span>
</div> </div>
</p> </p>
{% endif %}
</form> </form>
<hr /> <hr />
......
<a href="{% url "dashboard.views.vm-migrate" pk=record.pk %}" class="btn btn-default btn-xs vm-migrate" data-vm-pk="{{ record.pk }}" title data-original-title="Migrate"> <a href="{% url "dashboard.vm.op.migrate" pk=record.pk %}" class="btn btn-default btn-xs vm-migrate" data-vm-pk="{{ record.pk }}" title data-original-title="Migrate">
<i class="icon-truck"></i> <i class="icon-truck"></i>
</a> </a>
<a id="vm-list-rename-button" class="btn btn-default btn-xs" title data-original-title="Rename"> <a id="vm-list-rename-button" class="btn btn-default btn-xs" title data-original-title="Rename">
......
...@@ -159,7 +159,7 @@ class VmOperationViewTestCase(unittest.TestCase): ...@@ -159,7 +159,7 @@ class VmOperationViewTestCase(unittest.TestCase):
assert not msg.error.called assert not msg.error.called
def test_migrate_failed(self): def test_migrate_failed(self):
request = FakeRequestFactory(POST={'node': 1}) request = FakeRequestFactory(POST={'node': 1}, superuser=True)
view = vm_ops['migrate'] view = vm_ops['migrate']
with patch.object(view, 'get_object') as go, \ with patch.object(view, 'get_object') as go, \
...@@ -177,7 +177,7 @@ class VmOperationViewTestCase(unittest.TestCase): ...@@ -177,7 +177,7 @@ class VmOperationViewTestCase(unittest.TestCase):
assert msg.error.called assert msg.error.called
def test_migrate_template(self): def test_migrate_template(self):
request = FakeRequestFactory() request = FakeRequestFactory(superuser=True)
view = vm_ops['migrate'] view = vm_ops['migrate']
with patch.object(view, 'get_object') as go: with patch.object(view, 'get_object') as go:
...@@ -190,7 +190,7 @@ class VmOperationViewTestCase(unittest.TestCase): ...@@ -190,7 +190,7 @@ class VmOperationViewTestCase(unittest.TestCase):
view.as_view()(request, pk=1234).render().status_code, 200) view.as_view()(request, pk=1234).render().status_code, 200)
def test_save_as_wo_name(self): def test_save_as_wo_name(self):
request = FakeRequestFactory(POST={}) request = FakeRequestFactory(POST={}, has_perms_mock=True)
view = vm_ops['save_as_template'] view = vm_ops['save_as_template']
with patch.object(view, 'get_object') as go, \ with patch.object(view, 'get_object') as go, \
...@@ -224,7 +224,7 @@ class VmOperationViewTestCase(unittest.TestCase): ...@@ -224,7 +224,7 @@ class VmOperationViewTestCase(unittest.TestCase):
assert not msg.error.called assert not msg.error.called
def test_save_as_template(self): def test_save_as_template(self):
request = FakeRequestFactory() request = FakeRequestFactory(has_perms_mock=True)
view = vm_ops['save_as_template'] view = vm_ops['save_as_template']
with patch.object(view, 'get_object') as go: with patch.object(view, 'get_object') as go:
...@@ -246,6 +246,8 @@ def FakeRequestFactory(*args, **kwargs): ...@@ -246,6 +246,8 @@ def FakeRequestFactory(*args, **kwargs):
user = UserFactory() user = UserFactory()
user.is_authenticated = lambda: kwargs.get('authenticated', True) user.is_authenticated = lambda: kwargs.get('authenticated', True)
user.is_superuser = kwargs.get('superuser', False) user.is_superuser = kwargs.get('superuser', False)
if kwargs.get('has_perms_mock', False):
user.has_perms = MagicMock(return_value=True)
request = HttpRequest() request = HttpRequest()
request.user = user request.user = user
......
...@@ -63,6 +63,8 @@ class VmDetailTest(LoginMixin, TestCase): ...@@ -63,6 +63,8 @@ class VmDetailTest(LoginMixin, TestCase):
self.g1.user_set.add(self.u1) self.g1.user_set.add(self.u1)
self.g1.user_set.add(self.u2) self.g1.user_set.add(self.u2)
self.g1.save() self.g1.save()
self.u1.user_permissions.add(Permission.objects.get(
codename='create_vm'))
settings["default_vlangroup"] = 'public' settings["default_vlangroup"] = 'public'
VlanGroup.objects.create(name='public') VlanGroup.objects.create(name='public')
...@@ -1544,6 +1546,8 @@ class VmDetailVncTest(LoginMixin, TestCase): ...@@ -1544,6 +1546,8 @@ class VmDetailVncTest(LoginMixin, TestCase):
inst.node = Node.objects.all()[0] inst.node = Node.objects.all()[0]
inst.save() inst.save()
inst.set_level(self.u1, 'operator') inst.set_level(self.u1, 'operator')
self.u1.user_permissions.add(Permission.objects.get(
codename='access_console'))
response = c.get('/dashboard/vm/1/vnctoken/') response = c.get('/dashboard/vm/1/vnctoken/')
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
...@@ -1554,6 +1558,8 @@ class VmDetailVncTest(LoginMixin, TestCase): ...@@ -1554,6 +1558,8 @@ class VmDetailVncTest(LoginMixin, TestCase):
inst.node = Node.objects.all()[0] inst.node = Node.objects.all()[0]
inst.save() inst.save()
inst.set_level(self.u1, 'user') inst.set_level(self.u1, 'user')
self.u1.user_permissions.add(Permission.objects.get(
codename='access_console'))
response = c.get('/dashboard/vm/1/vnctoken/') response = c.get('/dashboard/vm/1/vnctoken/')
self.assertEqual(response.status_code, 403) self.assertEqual(response.status_code, 403)
......
...@@ -28,7 +28,7 @@ from .views import ( ...@@ -28,7 +28,7 @@ from .views import (
NotificationView, PortDelete, TemplateAclUpdateView, TemplateCreate, NotificationView, PortDelete, TemplateAclUpdateView, TemplateCreate,
TemplateDelete, TemplateDetail, TemplateList, TransferOwnershipConfirmView, TemplateDelete, TemplateDetail, TemplateList, TransferOwnershipConfirmView,
TransferOwnershipView, vm_activity, VmCreate, VmDelete, VmDetailView, TransferOwnershipView, vm_activity, VmCreate, VmDelete, VmDetailView,
VmDetailVncTokenView, VmGraphView, VmList, VmMassDelete, VmMigrateView, VmDetailVncTokenView, VmGraphView, VmList, VmMassDelete,
VmRenewView, DiskRemoveView, get_disk_download_status, InterfaceDeleteView, VmRenewView, DiskRemoveView, get_disk_download_status, InterfaceDeleteView,
GroupRemoveAclUserView, GroupRemoveAclGroupView, GroupRemoveUserView, GroupRemoveAclUserView, GroupRemoveAclGroupView, GroupRemoveUserView,
GroupRemoveFutureUserView, GroupRemoveFutureUserView,
...@@ -83,8 +83,6 @@ urlpatterns = patterns( ...@@ -83,8 +83,6 @@ urlpatterns = patterns(
url(r'^vm/mass-delete/', VmMassDelete.as_view(), url(r'^vm/mass-delete/', VmMassDelete.as_view(),
name='dashboard.view.mass-delete-vm'), name='dashboard.view.mass-delete-vm'),
url(r'^vm/(?P<pk>\d+)/activity/$', vm_activity), url(r'^vm/(?P<pk>\d+)/activity/$', vm_activity),
url(r'^vm/(?P<pk>\d+)/migrate/$', VmMigrateView.as_view(),
name='dashboard.views.vm-migrate'),
url(r'^vm/(?P<pk>\d+)/renew/((?P<key>.*)/?)$', VmRenewView.as_view(), url(r'^vm/(?P<pk>\d+)/renew/((?P<key>.*)/?)$', VmRenewView.as_view(),
name='dashboard.views.vm-renew'), name='dashboard.views.vm-renew'),
url(r'^vm/activity/(?P<pk>\d+)/$', InstanceActivityDetail.as_view(), url(r'^vm/activity/(?P<pk>\d+)/$', InstanceActivityDetail.as_view(),
......
...@@ -244,6 +244,8 @@ class VmDetailVncTokenView(CheckedDetailView): ...@@ -244,6 +244,8 @@ class VmDetailVncTokenView(CheckedDetailView):
self.object = self.get_object() self.object = self.get_object()
if not self.object.has_level(request.user, 'operator'): if not self.object.has_level(request.user, 'operator'):
raise PermissionDenied() raise PermissionDenied()
if not request.user.has_perm('vm.access_console'):
raise PermissionDenied()
if self.object.node: if self.object.node:
with instance_activity(code_suffix='console-accessed', with instance_activity(code_suffix='console-accessed',
instance=self.object, user=request.user, instance=self.object, user=request.user,
...@@ -294,13 +296,14 @@ class VmDetailView(CheckedDetailView): ...@@ -294,13 +296,14 @@ class VmDetailView(CheckedDetailView):
if self.request.user.is_superuser: if self.request.user.is_superuser:
context['traits_form'] = TraitsForm(instance=instance) context['traits_form'] = TraitsForm(instance=instance)
context['raw_data_form'] = RawDataForm(instance=instance) context['raw_data_form'] = RawDataForm(instance=instance)
# resources change perm
context['can_change_resources'] = self.request.user.has_perm(
"vm.change_resources")
return context return context
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
if (request.POST.get('ram-size') and request.POST.get('cpu-count')
and request.POST.get('cpu-priority')):
return self.__set_resources(request)
options = { options = {
'change_password': self.__change_password, 'change_password': self.__change_password,
'new_name': self.__set_name, 'new_name': self.__set_name,
...@@ -328,33 +331,6 @@ class VmDetailView(CheckedDetailView): ...@@ -328,33 +331,6 @@ class VmDetailView(CheckedDetailView):
return redirect(reverse_lazy("dashboard.views.detail", return redirect(reverse_lazy("dashboard.views.detail",
kwargs={'pk': self.object.pk})) kwargs={'pk': self.object.pk}))
def __set_resources(self, request):
self.object = self.get_object()
if not self.object.has_level(request.user, 'owner'):
raise PermissionDenied()
if not request.user.has_perm('vm.change_resources'):
raise PermissionDenied()
resources = {
'num_cores': request.POST.get('cpu-count'),
'ram_size': request.POST.get('ram-size'),
'max_ram_size': request.POST.get('ram-size'), # TODO: max_ram
'priority': request.POST.get('cpu-priority')
}
Instance.objects.filter(pk=self.object.pk).update(**resources)
success_message = _("Resources successfully updated.")
if request.is_ajax():
response = {'message': success_message}
return HttpResponse(
json.dumps(response),
content_type="application/json"
)
else:
messages.success(request, success_message)
return redirect(reverse_lazy("dashboard.views.detail",
kwargs={'pk': self.object.pk}))
def __set_name(self, request): def __set_name(self, request):
self.object = self.get_object() self.object = self.get_object()
if not self.object.has_level(request.user, 'owner'): if not self.object.has_level(request.user, 'owner'):
...@@ -606,8 +582,9 @@ class VmOperationView(OperationView): ...@@ -606,8 +582,9 @@ class VmOperationView(OperationView):
model = Instance model = Instance
context_object_name = 'instance' # much simpler to mock object context_object_name = 'instance' # much simpler to mock object
def post(self, request, *args, **kwargs): def post(self, request, extra=None, *args, **kwargs):
resp = super(VmOperationView, self).post(request, *args, **kwargs) resp = super(VmOperationView, self).post(request, extra, *args,
**kwargs)
if request.is_ajax(): if request.is_ajax():
store = messages.get_messages(request) store = messages.get_messages(request)
store.used = True store.used = True
...@@ -699,6 +676,29 @@ class VmSaveView(FormOperationMixin, VmOperationView): ...@@ -699,6 +676,29 @@ class VmSaveView(FormOperationMixin, VmOperationView):
effect = 'info' effect = 'info'
form_class = VmSaveForm form_class = VmSaveForm
class VmResourcesChangeView(VmOperationView):
op = 'resources_change'
icon = "save"
show_in_toolbar = False
def post(self, request, extra=None, *args, **kwargs):
if extra is None:
extra = {}
resources = {
'num_cores': "cpu-count",
'priority': "cpu-priority",
'ram_size': "ram-size",
"max_ram_size": "ram-size", # TODO
}
for k, v in resources.iteritems():
extra[k] = request.POST.get(v)
return super(VmResourcesChangeView, self).post(request, extra,
*args, **kwargs)
vm_ops = OrderedDict([ vm_ops = OrderedDict([
('deploy', VmOperationView.factory( ('deploy', VmOperationView.factory(
op='deploy', icon='play', effect='success')), op='deploy', icon='play', effect='success')),
...@@ -1012,7 +1012,7 @@ class GroupAclUpdateView(AclUpdateView): ...@@ -1012,7 +1012,7 @@ class GroupAclUpdateView(AclUpdateView):
kwargs=self.kwargs)) kwargs=self.kwargs))
class TemplateChoose(TemplateView): class TemplateChoose(LoginRequiredMixin, TemplateView):
def get_template_names(self): def get_template_names(self):
if self.request.is_ajax(): if self.request.is_ajax():
...@@ -1045,6 +1045,9 @@ class TemplateChoose(TemplateView): ...@@ -1045,6 +1045,9 @@ class TemplateChoose(TemplateView):
else: else:
template = get_object_or_404(InstanceTemplate, pk=template) template = get_object_or_404(InstanceTemplate, pk=template)
if not template.has_level(request.user, "user"):
raise PermissionDenied()
instance = Instance.create_from_template( instance = Instance.create_from_template(
template=template, owner=request.user, is_base=True) template=template, owner=request.user, is_base=True)
...@@ -1072,7 +1075,7 @@ class TemplateCreate(SuccessMessageMixin, CreateView): ...@@ -1072,7 +1075,7 @@ class TemplateCreate(SuccessMessageMixin, CreateView):
return context return context
def get(self, *args, **kwargs): def get(self, *args, **kwargs):
if not self.request.user.has_perm('vm.create_template'): if not self.request.user.has_perm('vm.create_base_template'):
raise PermissionDenied() raise PermissionDenied()
return super(TemplateCreate, self).get(*args, **kwargs) return super(TemplateCreate, self).get(*args, **kwargs)
...@@ -1083,7 +1086,7 @@ class TemplateCreate(SuccessMessageMixin, CreateView): ...@@ -1083,7 +1086,7 @@ class TemplateCreate(SuccessMessageMixin, CreateView):
return kwargs return kwargs
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
if not self.request.user.has_perm('vm.create_template'): if not self.request.user.has_perm('vm.create_base_template'):
raise PermissionDenied() raise PermissionDenied()
form = self.form_class(request.POST, user=request.user) form = self.form_class(request.POST, user=request.user)
...@@ -1105,8 +1108,6 @@ class TemplateCreate(SuccessMessageMixin, CreateView): ...@@ -1105,8 +1108,6 @@ class TemplateCreate(SuccessMessageMixin, CreateView):
return redirect("%s#resources" % inst.get_absolute_url()) return redirect("%s#resources" % inst.get_absolute_url())
return super(TemplateCreate, self).post(self, request, args, kwargs)
def __create_networks(self, vlans, user): def __create_networks(self, vlans, user):
networks = [] networks = []
for v in vlans: for v in vlans:
...@@ -1167,12 +1168,6 @@ class TemplateDetail(LoginRequiredMixin, SuccessMessageMixin, UpdateView): ...@@ -1167,12 +1168,6 @@ class TemplateDetail(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
template = self.get_object() template = self.get_object()
if not template.has_level(request.user, 'owner'): if not template.has_level(request.user, 'owner'):
raise PermissionDenied() raise PermissionDenied()
for disk in self.get_object().disks.all():