Commit 069f491e by Őry Máté

vm: add InstanceActivity.is_abortable_for and ~_user

parent 028e8896
...@@ -9,8 +9,8 @@ ...@@ -9,8 +9,8 @@
{{ a.get_readable_name }}{% if user.is_superuser %}</a>{% endif %} {{ a.get_readable_name }}{% if user.is_superuser %}</a>{% endif %}
</strong> </strong>
{{ a.started|date:"Y-m-d H:i" }}{% if a.user %}, {{ a.user }}{% endif %} {{ a.started|date:"Y-m-d H:i" }}{% if a.user %}, {{ a.user }}{% endif %}
{% if user.is_superuser and a.is_abortable %} {% if a.is_abortable_for_user %}
<form action="" method="POST" class="pull-right"> <form action="{{ a.instance.get_absolute_url }}" method="POST" class="pull-right">
{% csrf_token %} {% csrf_token %}
<input type="hidden" name="abort_operation"/> <input type="hidden" name="abort_operation"/>
<input type="hidden" name="activity" value="{{ a.pk }}"/> <input type="hidden" name="activity" value="{{ a.pk }}"/>
......
...@@ -427,11 +427,11 @@ class VmDetailView(CheckedDetailView): ...@@ -427,11 +427,11 @@ class VmDetailView(CheckedDetailView):
def __abort_operation(self, request): def __abort_operation(self, request):
self.object = self.get_object() self.object = self.get_object()
if not request.user.is_superuser:
raise PermissionDenied()
activity = get_object_or_404(InstanceActivity, activity = get_object_or_404(InstanceActivity,
pk=request.POST.get("activity")) pk=request.POST.get("activity"))
if not activity.is_abortable_for(request.user):
raise PermissionDenied()
activity.abort() activity.abort()
return redirect("%s#activity" % self.object.get_absolute_url()) return redirect("%s#activity" % self.object.get_absolute_url())
......
...@@ -107,6 +107,13 @@ class InstanceActivity(ActivityModel): ...@@ -107,6 +107,13 @@ class InstanceActivity(ActivityModel):
op = self.instance.get_operation_from_activity_code(self.activity_code) op = self.instance.get_operation_from_activity_code(self.activity_code)
return self.task_uuid and op and op.abortable and not self.finished return self.task_uuid and op and op.abortable and not self.finished
def is_abortable_for(self, user):
"""Can the given user abort the activity?
"""
return self.is_abortable and (
user.is_superuser or user in (self.instance.owner, self.user))
@property @property
def is_aborted(self): def is_aborted(self):
"""Has the activity been aborted? """Has the activity been aborted?
......
from __future__ import absolute_import, unicode_literals from __future__ import absolute_import, unicode_literals
from datetime import timedelta from datetime import timedelta
from functools import partial
from importlib import import_module from importlib import import_module
from logging import getLogger from logging import getLogger
from string import ascii_lowercase from string import ascii_lowercase
...@@ -886,4 +887,8 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin, ...@@ -886,4 +887,8 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin,
acts = (self.activity_log.filter(parent=None). acts = (self.activity_log.filter(parent=None).
order_by('-started'). order_by('-started').
select_related('user').prefetch_related('children')) select_related('user').prefetch_related('children'))
if user is not None:
for i in acts:
i.is_abortable_for_user = partial(i.is_abortable_for,
user=user)
return acts return acts
...@@ -262,6 +262,45 @@ class InstanceActivityTestCase(TestCase): ...@@ -262,6 +262,45 @@ class InstanceActivityTestCase(TestCase):
self.assertEquals(expected, self.assertEquals(expected,
InstanceActivity.is_aborted.fget(iaobj)) InstanceActivity.is_aborted.fget(iaobj))
def test_is_abortable_for_activity_owner_if_not_abortable(self):
iaobj = MagicMock(spec=InstanceActivity, is_abortable=False,
user=MagicMock(spec=User, is_superuser=False))
self.assertFalse(InstanceActivity.is_abortable_for(iaobj, iaobj.user))
def test_is_abortable_for_instance_owner(self):
get_op = MagicMock(return_value=MagicMock(abortable=True))
instance = MagicMock(get_operation_from_activity_code=get_op,
owner=MagicMock(spec=User, is_superuser=False))
iaobj = MagicMock(spec=InstanceActivity, activity_code='test',
finished=False, instance=instance, task_uuid='test',
user=MagicMock(spec=User, is_superuser=False))
self.assertTrue(
InstanceActivity.is_abortable_for(iaobj, iaobj.instance.owner))
def test_is_abortable_for_activity_owner(self):
get_op = MagicMock(return_value=MagicMock(abortable=True))
instance = MagicMock(get_operation_from_activity_code=get_op)
iaobj = MagicMock(spec=InstanceActivity, activity_code='test',
finished=False, instance=instance, task_uuid='test',
user=MagicMock(spec=User, is_superuser=False))
self.assertTrue(InstanceActivity.is_abortable_for(iaobj, iaobj.user))
def test_not_abortable_for_foreign(self):
get_op = MagicMock(return_value=MagicMock(abortable=True))
instance = MagicMock(get_operation_from_activity_code=get_op)
iaobj = MagicMock(spec=InstanceActivity, activity_code='test',
finished=False, instance=instance, task_uuid='test')
self.assertFalse(InstanceActivity.is_abortable_for(
iaobj, MagicMock(spec=User, is_superuser=False)))
def test_is_abortable_for_superuser(self):
get_op = MagicMock(return_value=MagicMock(abortable=True))
instance = MagicMock(get_operation_from_activity_code=get_op)
iaobj = MagicMock(spec=InstanceActivity, activity_code='test',
finished=False, instance=instance, task_uuid='test')
su = MagicMock(spec=User, is_superuser=True)
self.assertTrue(InstanceActivity.is_abortable_for(iaobj, su))
def test_disable_enabled(self): def test_disable_enabled(self):
node = MagicMock(spec=Node, enabled=True) node = MagicMock(spec=Node, enabled=True)
with patch('vm.models.node.node_activity') as nac: with patch('vm.models.node.node_activity') as nac:
......
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