Commit 83f9b7ce by Bach Dániel

Merge branch 'feature-pass-reset-op'

parents 4684a0a9 e601b2bc
......@@ -98,17 +98,12 @@
<dd style="font-size: 10px; text-align: right; padding-top: 8px;">
<a id="vm-details-pw-change" href="#">{% trans "Generate new password!" %}</a>
<div id="vm-details-pw-reset">
{% with op=op.password_reset %}{% if op %}
<a href="{{op.get_url}}" class="btn operation btn-default btn-xs" {% if op.disabled %}disabled{% endif %}>{% trans "Generate new password!" %}</a>
{% endif %}{% endwith %}
<div id="vm-details-pw-confirm"> {% comment %} TODO Couldn't this use a modal? {% endcomment%}
{% trans "Are you sure?" %}
<a href="#" class="vm-details-pw-confirm-choice label label-success" data-choice="1" data-vm="{{ }}">{% trans "Yes" %}</a> /
<a href="#" class="vm-details-pw-confirm-choice label label-danger" data-choice="0">{% trans "No" %}</a>
<div class="input-group" id="dashboard-vm-details-connect-command">
......@@ -143,26 +143,13 @@ class VmDetailTest(LoginMixin, TestCase):
response ='/dashboard/vm/mass-delete/', {'vms': [1]})
self.assertEqual(response.status_code, 302)
def test_permitted_password_change(self):
c = Client()
self.login(c, "user2")
inst = Instance.objects.get(pk=1)
inst.set_level(self.u2, 'owner')
inst.node = Node.objects.all()[0]
password =
response ="/dashboard/vm/1/", {'change_password': True})
self.assertEqual(response.status_code, 302)
self.assertNotEqual(password, Instance.objects.get(pk=1).pw)
def test_unpermitted_password_change(self):
c = Client()
self.login(c, "user2")
inst = Instance.objects.get(pk=1)
inst.set_level(self.u1, 'owner')
password =
response ="/dashboard/vm/1/", {'change_password': True})
response ="/dashboard/vm/1/op/password_reset/")
self.assertEqual(response.status_code, 403)
self.assertEqual(password, Instance.objects.get(pk=1).pw)
......@@ -52,6 +52,7 @@ from django_tables2 import SingleTableView
from braces.views import (LoginRequiredMixin, SuperuserRequiredMixin,
from braces.views._access import AccessMixin
from celery.exceptions import TimeoutError
from django_sshkey.models import UserKey
......@@ -304,7 +305,6 @@ class VmDetailView(CheckedDetailView):
def post(self, request, *args, **kwargs):
options = {
'change_password': self.__change_password,
'new_name': self.__set_name,
'new_description': self.__set_description,
'new_tag': self.__add_tag,
......@@ -319,19 +319,6 @@ class VmDetailView(CheckedDetailView):
raise Http404()
def __change_password(self, request):
self.object = self.get_object()
if not self.object.has_level(request.user, 'owner'):
raise PermissionDenied()
messages.success(request, _("Password changed."))
if request.is_ajax():
return HttpResponse("Success.")
return redirect(reverse_lazy("dashboard.views.detail",
def __set_name(self, request):
self.object = self.get_object()
if not self.object.has_level(request.user, 'owner'):
......@@ -486,6 +473,7 @@ class OperationView(RedirectToLoginMixin, DetailView):
template_name = 'dashboard/operate.html'
show_in_toolbar = True
effect = None
wait_for_result = None
def name(self):
......@@ -547,19 +535,52 @@ class OperationView(RedirectToLoginMixin, DetailView):
return super(OperationView, self).get(request, *args, **kwargs)
def get_response_data(self, result, extra=None, **kwargs):
"""Return serializable data to return to agents requesting json
response to POST"""
if extra is None:
extra = {}
extra["success"] = not isinstance(result, Exception)
extra["done"] = result is not None
return extra
def post(self, request, extra=None, *args, **kwargs):
self.object = self.get_object()
if extra is None:
extra = {}
result = None
self.get_op().async(user=request.user, **extra)
task = self.get_op().async(user=request.user, **extra)
except Exception as e:
messages.error(request, _('Could not start operation.'))
result = e
messages.success(request, _('Operation is started.'))
return redirect("%s#activity" % self.object.get_absolute_url())
wait = self.wait_for_result
if wait:
result = task.get(timeout=wait,
interval=min((wait / 5, .5)))
except TimeoutError:
logger.debug("Result didn't arrive in %ss",
self.wait_for_result, exc_info=True)
except Exception as e:
messages.error(request, _('Operation failed.'))
logger.debug("Operation failed.", exc_info=True)
result = e
messages.success(request, _('Operation succeeded.'))
if result is None:
messages.success(request, _('Operation is started.'))
if "/json" in request.META.get("HTTP_ACCEPT", ""):
data = self.get_response_data(result, post_extra=extra, **kwargs)
return HttpResponse(json.dumps(data),
return redirect("%s#activity" % self.object.get_absolute_url())
def factory(cls, op, icon='cog', effect='info', extra_bases=(), **kwargs):
......@@ -586,6 +607,7 @@ class AjaxOperationMixin(object):
store.used = True
return HttpResponse(
json.dumps({'success': True,
'with_reload': getattr(self, 'with_reload', False),
'messages': [unicode(m) for m in store]}),
......@@ -860,6 +882,9 @@ vm_ops = OrderedDict([
('add_interface', VmAddInterfaceView),
('renew', VmRenewView),
('resources_change', VmResourcesChangeView),
('password_reset', VmOperationView.factory(
op='password_reset', icon='unlock', effect='warning',
show_in_toolbar=False, wait_for_result=0.5, with_reload=True)),
......@@ -13,6 +13,7 @@ from .instance import InstanceTemplate
from .instance import Instance
from .instance import post_state_changed
from .instance import pre_state_changed
from .instance import pwgen
from .network import InterfaceTemplate
from .network import Interface
from .node import Node
......@@ -22,5 +23,5 @@ __all__ = [
'NamedBaseResourceConfig', 'VirtualMachineDescModel', 'InstanceTemplate',
'Instance', 'instance_activity', 'post_state_changed', 'pre_state_changed',
'InterfaceTemplate', 'Interface', 'Trait', 'Node', 'NodeActivity', 'Lease',
'node_activity', 'pwgen'
......@@ -43,7 +43,7 @@ from taggit.managers import TaggableManager
from acl.models import AclBase
from common.models import create_readable
from common.operations import OperatedMixin
from ..tasks import vm_tasks, agent_tasks
from ..tasks import vm_tasks
from .activity import (ActivityInProgressError, instance_activity,
from .common import BaseResourceConfigModel, Lease
......@@ -719,24 +719,6 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin, + lease.suspend_interval, + lease.delete_interval)
def change_password(self, user=None):
"""Generate new password for the vm
:param self: The virtual machine.
:param user: The user who's issuing the command.
""" = pwgen()
with instance_activity(code_suffix='change_password', instance=self,
readable_name=ugettext_noop("change password"),
queue = self.get_remote_queue_name("agent")
def select_node(self):
"""Returns the node the VM should be deployed or migrated to.
......@@ -33,8 +33,9 @@ from .tasks.local_tasks import (
from .models import (
Instance, InstanceActivity, InstanceTemplate, Interface, Node,
NodeActivity, pwgen
from .tasks import agent_tasks
logger = getLogger(__name__)
......@@ -881,3 +882,27 @@ class ResourcesOperation(InstanceOperation):
class PasswordResetOperation(InstanceOperation):
activity_code_suffix = 'Password reset'
id = 'password_reset'
name = _("password reset")
description = _("Password reset")
acl_level = "owner"
required_perms = ()
def check_precond(self):
super(PasswordResetOperation, self).check_precond()
if self.instance.status not in ["RUNNING"]:
raise self.instance.WrongStateError(self.instance)
def _operation(self): = pwgen()
queue = self.instance.get_remote_queue_name("agent")
queue=queue, args=(self.instance.vm_name,
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