Commit 21a68f69 by Őry Máté

Merge branch 'feature-operations-view'

Add operations to vm-details views

* vm-detail context
* vm-detail templates
* replace op buttons at ajax status update
* add tests
* re-implement migrate op
parents 61c537bd e9c09351
......@@ -13,6 +13,7 @@ class Operation(object):
"""
async_queue = 'localhost.man'
required_perms = ()
do_not_call_in_templates = True
def __call__(self, **kwargs):
return self.call(**kwargs)
......@@ -127,6 +128,19 @@ class OperatedMixin(object):
raise AttributeError("%r object has no attribute %r" %
(self.__class__.__name__, name))
def get_available_operations(self, user):
"""Yield Operations that match permissions of user and preconditions.
"""
for name in getattr(self, operation_registry_name, {}):
try:
op = getattr(self, name)
op.check_auth(user)
op.check_precond()
except:
pass # unavailable
else:
yield op
def register_operation(op_cls, op_id=None, target_cls=None):
"""Register the specified operation with the target class.
......
from __future__ import absolute_import
from datetime import timedelta
from django.contrib.auth.models import User
......
from __future__ import absolute_import
from itertools import chain
from logging import getLogger
......
......@@ -2,22 +2,21 @@
$(function() {
/* vm migrate */
$('.vm-migrate').click(function(e) {
var icon = $(this).children("i");
var vm = $(this).data("vm-pk");
icon.removeClass("icon-truck").addClass("icon-spinner icon-spin");
/* vm operations */
$('#ops').on('click', '.operation.btn', function(e) {
var icon = $(this).children("i").addClass('icon-spinner icon-spin');
$.ajax({
type: 'GET',
url: '/dashboard/vm/' + vm + '/migrate/',
url: $(this).attr('href'),
success: function(data) {
icon.addClass("icon-truck").removeClass("icon-spinner icon-spin");
icon.removeClass("icon-spinner icon-spin");
$('body').append(data);
$('#create-modal').modal('show');
$('#create-modal').on('hidden.bs.modal', function() {
$('#create-modal').remove();
$('#confirmation-modal').modal('show');
$('#confirmation-modal').on('hidden.bs.modal', function() {
$('#confirmation-modal').remove();
});
$('#vm-migrate-node-list li').click(function(e) {
var li = $(this).closest('li');
if (li.find('input').attr('disabled'))
......
......@@ -211,6 +211,7 @@ function checkNewActivity(only_status, runs) {
success: function(data) {
if(!only_status) {
$("#activity-timeline").html(data['activities']);
$("#ops").html(data['ops']);
$("[title]").tooltip();
}
......
from __future__ import absolute_import
from django.contrib.auth.models import Group, User
from django_tables2 import Table, A
from django_tables2.columns import (TemplateColumn, Column, BooleanColumn,
......
{% extends "dashboard/base.html" %}
{% load i18n %}
{% block content %}
<div class="body-content">
<div class="panel panel-default" style="margin-top: 60px;">
<div class="panel-heading">
<h3 class="no-margin">
{% if title %}
{{ title }}
{% else %}
{% trans "Confirmation" %}
{% endif %}
</h3>
</div>
<div class="panel-body">
{{ body|safe|default:"(body missing from context.)" }}
</div>
</div>
{% endblock %}
{% load i18n %}
<div class="modal fade" id="confirmation-modal" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-body">
{{ body|safe|default:"(body missing from context.)" }}
<div class="clearfix"></div>
</div>
<div class="clearfix"></div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div>
{% extends "dashboard/operate.html" %}
{% load i18n %}
{% load sizefieldtags %}
<form method="POST" action="{% url "dashboard.views.vm-migrate" pk=vm.pk %}">
{% csrf_token %}
<ul id="vm-migrate-node-list">
{% with current=vm.node.pk selected=vm.select_node.pk %}
{% block question %}
<p>
{% blocktrans with obj=object op=op.name %}
Choose a compute node to migrate {{obj}} to.
{% endblocktrans %}
</p>
<p class="text-info">{{op.name}}: {{op.description}}</p>
{% endblock %}
{% block formfields %}
<ul id="vm-migrate-node-list" class="list-unstyled">
{% with current=object.node.pk selected=object.select_node.pk %}
{% for n in nodes %}
<li class="panel panel-default"><div class="panel-body">
<label for="migrate-to-{{n.pk}}">
......@@ -22,5 +31,4 @@
{% endfor %}
{% endwith %}
</ul>
<button type="submit" class="btn btn-primary btn-sm"><i class="icon-truck"></i> Migrate</button>
</form>
{% endblock %}
{% load i18n %}
{% block question %}
<p>
{% blocktrans with obj=object op=op.name %}
Do you want to do the following operation on {{obj}}:
<strong>{{op}}</strong>?
{% endblocktrans %}
</p>
<p class="text-info">{{op.name}}: {{op.description}}</p>
{% endblock %}
<form method="POST" action="{{url}}">{% csrf_token %}
{% block formfields %}{% endblock %}
<div class="pull-right">
<a class="btn btn-default" href="{{object.get_absolute_url}}"
data-dismiss="modal">{% trans "Cancel" %}</a>
<button class="btn btn-danger" type="submit">{% if op.icon %}<i class="icon-{{op.icon}}"></i> {% endif %}{{ op|capfirst }}</button>
</div>
</form>
......@@ -6,54 +6,8 @@
{% block content %}
<div class="body-content">
<div class="page-header">
<div class="pull-right" style="padding-top: 15px;">
<form style="display: inline;" method="POST" action="{% url "dashboard.views.detail" pk=instance.pk %}">
{% csrf_token %}
<input type="hidden" name="sleep" />
<button title="{% trans "Sleep" %}" class="btn btn-default btn-xs" type="submit"><i class="icon-moon"></i></button>
</form>
<form style="display: inline;" method="POST" action="{% url "dashboard.views.detail" pk=instance.pk %}">
{% csrf_token %}
<input type="hidden" name="deploy" />
<button title="{% trans "Deploy" %}" class="btn btn-default btn-xs" type="submit"><i class="icon-play"></i></button>
</form>
<form style="display: inline;" method="POST" action="{% url "dashboard.views.detail" pk=instance.pk %}">
{% csrf_token %}
<input type="hidden" name="wake_up" />
<button title="{% trans "Wake up" %}" class="btn btn-default btn-xs" type="submit"><i class="icon-sun"></i></button>
</form>
<form style="display: inline;" method="POST" action="{% url "dashboard.views.detail" pk=instance.pk %}">
{% csrf_token %}
<input type="hidden" name="shut_down" />
<button title="{% trans "Shut down" %}" class="btn btn-default btn-xs" type="submit"><i class="icon-off"></i></button>
</form>
<form style="display: inline;" method="POST" action="{% url "dashboard.views.detail" pk=instance.pk %}">
{% csrf_token %}
<input type="hidden" name="reboot" />
<button title="{% trans "Reboot (ctrl + alt + del)" %}" class="btn btn-default btn-xs" type="submit"><i class="icon-refresh"></i></button>
</form>
<form style="display: inline;" method="POST" action="{% url "dashboard.views.detail" pk=instance.pk %}">
{% csrf_token %}
<input type="hidden" name="reset" />
<button title="{% trans "Reset (power cycle)" %}" class="btn btn-default btn-xs" type="submit"><i class="icon-bolt"></i></button>
</form>
<form style="display: inline;" method="POST" action="{% url "dashboard.views.detail" pk=instance.pk %}">
{% csrf_token %}
<input type="hidden" name="shut_off"/>
<button title="{% trans "Shut off" %}" class="btn btn-default btn-xs" type="submit">
<i class="icon-ban-circle"></i>
</button>
</form>
<a title="Migrate" data-vm-pk="{{ instance.pk }}" href="{% url "dashboard.views.vm-migrate" pk=instance.pk %}" class="btn btn-default btn-xs vm-migrate">
<i class="icon-truck"></i>
</a>
<form style="display: inline;" method="POST" action="{% url "dashboard.views.detail" pk=instance.pk %}">
{% csrf_token %}
<input type="hidden" name="save_as" />
<button title="{% trans "Save as template" %}" class="btn btn-default btn-xs" type="submit"><i class="icon-save"></i></button>
</form>
<a title="{% trans "Destroy" %}" href="{% url "dashboard.views.delete-vm" pk=instance.pk %}" class="btn btn-default btn-xs vm-delete" data-vm-pk="{{ instance.pk }}"><i class="icon-remove"></i></a>
<a title="{% trans "Help" %}" href="#" class="btn btn-default btn-xs vm-details-help-button"><i class="icon-question"></i></a>
<div class="pull-right" style="padding-top: 15px;" id="ops">
{% include "dashboard/vm-detail/_operations.html" %}
</div>
<h1>
<div id="vm-details-rename">
......@@ -68,46 +22,6 @@
</div>
<small>{{ instance.primary_host.get_fqdn }}</small>
</h1>
<div class="vm-details-help js-hidden">
<ul style="list-style: none;">
<li>
<strong>{% trans "Sleep" %}:</strong>
{% trans "Suspend virtual machine with memory dump." %}
</li>
<li>
<strong>{% trans "Wake up" %}:</strong>
{% trans "Wake up suspended machine." %}
</li>
<li>
<strong>{% trans "Shutdown" %}:</strong>
{% trans "Shutdown virtual machine with ACPI signal." %}
</li>
<li>
<strong>{% trans "Reboot (ctrl + alt + del)" %}:</strong>
{% trans "Reboot virtual machine with Ctrl+Alt+Del signal." %}
</li>
<li>
<strong>{% trans "Reset (power cycle)" %}:</strong>
{% trans "Reset virtual machine (reset button)" %}
</li>
<li>
<strong>{% trans "Shut off" %}:</strong>
{% trans "Shut off VM. (plug-out)" %}
</li>
<li>
<strong>{% trans "Migrate" %}:</strong>
{% trans "Live migrate running vm to another node." %}
</li>
<li>
<strong>{% trans "Save as template" %}:</strong>
{% trans "Shut down the virtual machine, and save it as a new template." %}
</li>
<li>
<strong>{% trans "Destroy" %}:</strong>
{% trans "Remove virtual machine and its networks." %}
</li>
</ul>
</div>
<div style="clear: both;"></div>
</div>
<div class="row">
......
{% load i18n %}
{% for op in ops %}
<a href="{{op.get_url}}" class="operation operation-{{op.op}} btn btn-default btn-xs"
title="{{op.name}}: {{op.description}}">
<i class="icon-{{op.icon}}"></i>
<span class="sr-only">{{op.name}}</span>
</a>
{% endfor %}
......@@ -3,10 +3,11 @@ from factory import Factory, Sequence
from mock import patch, MagicMock
from django.contrib.auth.models import User
# from django.core.exceptions import PermissionDenied
from django.core.exceptions import PermissionDenied
from django.http import HttpRequest, Http404
from dashboard.views import InstanceActivityDetail, InstanceActivity
from ..views import InstanceActivityDetail, InstanceActivity
from ..views import vm_ops, Instance
class ViewUserTestCase(unittest.TestCase):
......@@ -36,6 +37,81 @@ class ViewUserTestCase(unittest.TestCase):
self.assertEquals(view(request, pk=1234).render().status_code, 200)
class VmOperationViewTestCase(unittest.TestCase):
def test_available(self):
request = FakeRequestFactory(superuser=True)
view = vm_ops['destroy']
with patch.object(view, 'get_object') as go:
inst = MagicMock(spec=Instance)
inst._meta.object_name = "Instance"
inst.destroy = Instance._ops['destroy'](inst)
go.return_value = inst
self.assertEquals(
view.as_view()(request, pk=1234).render().status_code, 200)
def test_unpermitted(self):
request = FakeRequestFactory()
view = vm_ops['destroy']
with patch.object(view, 'get_object') as go:
inst = MagicMock(spec=Instance)
inst._meta.object_name = "Instance"
inst.destroy = Instance._ops['destroy'](inst)
inst.has_level.return_value = False
go.return_value = inst
with self.assertRaises(PermissionDenied):
view.as_view()(request, pk=1234).render()
def test_migrate(self):
request = FakeRequestFactory(POST={'node': 1})
view = vm_ops['migrate']
with patch.object(view, 'get_object') as go, \
patch('dashboard.views.messages') as msg, \
patch('dashboard.views.get_object_or_404') as go4:
inst = MagicMock(spec=Instance)
inst._meta.object_name = "Instance"
inst.migrate = Instance._ops['migrate'](inst)
inst.migrate.async = MagicMock()
inst.has_level.return_value = True
go.return_value = inst
go4.return_value = MagicMock()
assert view.as_view()(request, pk=1234)['location']
assert not msg.error.called
def test_migrate_failed(self):
request = FakeRequestFactory(POST={'node': 1})
view = vm_ops['migrate']
with patch.object(view, 'get_object') as go, \
patch('dashboard.views.messages') as msg, \
patch('dashboard.views.get_object_or_404') as go4:
inst = MagicMock(spec=Instance)
inst._meta.object_name = "Instance"
inst.migrate = Instance._ops['migrate'](inst)
inst.migrate.async = MagicMock()
inst.migrate.async.side_effect = Exception
inst.has_level.return_value = True
go.return_value = inst
go4.return_value = MagicMock()
assert view.as_view()(request, pk=1234)['location']
assert msg.error.called
def test_migrate_template(self):
request = FakeRequestFactory()
view = vm_ops['migrate']
with patch.object(view, 'get_object') as go:
inst = MagicMock(spec=Instance)
inst._meta.object_name = "Instance"
inst.migrate = Instance._ops['migrate'](inst)
inst.has_level.return_value = True
go.return_value = inst
self.assertEquals(
view.as_view()(request, pk=1234).render().status_code, 200)
def FakeRequestFactory(*args, **kwargs):
''' FakeRequestFactory, FakeMessages and FakeRequestContext are good for
mocking out django views; they are MUCH faster than the Django test client.
......@@ -48,12 +124,12 @@ def FakeRequestFactory(*args, **kwargs):
request = HttpRequest()
request.user = user
request.session = kwargs.get('session', {})
if kwargs.get('POST'):
if kwargs.get('POST') is not None:
request.method = 'POST'
request.POST = kwargs.get('POST')
else:
request.method = 'GET'
request.POST = kwargs.get('GET', {})
request.GET = kwargs.get('GET', {})
return request
......
......@@ -495,9 +495,11 @@ class VmDetailTest(LoginMixin, TestCase):
mock_method.side_effect = inst.wake_up
inst.manual_state_change('RUNNING')
inst.set_level(self.u2, 'owner')
self.assertRaises(inst.WrongStateError, c.post,
"/dashboard/vm/1/", {'wake_up': True})
self.assertEqual(inst.status, 'RUNNING')
with patch('dashboard.views.messages') as msg:
c.post("/dashboard/vm/1/op/wake_up/")
assert msg.error.called
inst = Instance.objects.get(pk=1)
self.assertEqual(inst.status, 'RUNNING') # mocked anyway
assert mock_method.called
def test_permitted_wake_up(self):
......@@ -511,7 +513,9 @@ class VmDetailTest(LoginMixin, TestCase):
inst.get_remote_queue_name = Mock(return_value='test')
inst.manual_state_change('SUSPENDED')
inst.set_level(self.u2, 'owner')
response = c.post("/dashboard/vm/1/", {'wake_up': True})
with patch('dashboard.views.messages') as msg:
response = c.post("/dashboard/vm/1/op/wake_up/")
assert not msg.error.called
self.assertEqual(response.status_code, 302)
self.assertEqual(inst.status, 'RUNNING')
assert new_wake_up.called
......@@ -523,8 +527,11 @@ class VmDetailTest(LoginMixin, TestCase):
inst = Instance.objects.get(pk=1)
inst.manual_state_change('SUSPENDED')
inst.set_level(self.u2, 'user')
response = c.post("/dashboard/vm/1/", {'wake_up': True})
self.assertEqual(response.status_code, 403)
with patch('dashboard.views.messages') as msg:
response = c.post("/dashboard/vm/1/op/wake_up/")
assert msg.error.called
self.assertEqual(response.status_code, 302)
inst = Instance.objects.get(pk=1)
self.assertEqual(inst.status, 'SUSPENDED')
def test_non_existing_template_get(self):
......
from django.conf.urls import patterns, url
from __future__ import absolute_import
from django.conf.urls import patterns, url, include
from vm.models import Instance
from .views import (
......@@ -34,7 +35,7 @@ urlpatterns = patterns(
name="dashboard.views.template-list"),
url(r"^template/delete/(?P<pk>\d+)/$", TemplateDelete.as_view(),
name="dashboard.views.template-delete"),
url(r'^vm/(?P<pk>\d+)/op/', include('dashboard.vm.urls')),
url(r'^vm/(?P<pk>\d+)/remove_port/(?P<rule>\d+)/$', PortDelete.as_view(),
name='dashboard.views.remove-port'),
url(r'^vm/(?P<pk>\d+)/$', VmDetailView.as_view(),
......
from __future__ import unicode_literals
from __future__ import unicode_literals, absolute_import
from os import getenv
import json
import logging
import re
from datetime import datetime
import requests
from django.conf import settings
......@@ -47,7 +46,7 @@ from vm.models import (
)
from storage.models import Disk
from firewall.models import Vlan, Host, Rule
from dashboard.models import Favourite, Profile
from .models import Favourite, Profile
logger = logging.getLogger(__name__)
......@@ -203,7 +202,8 @@ class VmDetailView(CheckedDetailView):
context.update({
'graphite_enabled': VmGraphView.get_graphite_url() is not None,
'vnc_url': reverse_lazy("dashboard.views.detail-vnc",
kwargs={'pk': self.object.pk})
kwargs={'pk': self.object.pk}),
'ops': get_operations(instance, self.request.user),
})
# activity data
......@@ -242,14 +242,6 @@ class VmDetailView(CheckedDetailView):
'to_remove': self.__remove_tag,
'port': self.__add_port,
'new_network_vlan': self.__new_network,
'save_as': self.__save_as,
'shut_down': self.__shut_down,
'sleep': self.__sleep,
'wake_up': self.__wake_up,
'deploy': self.__deploy,
'reset': self.__reset,
'reboot': self.__reboot,
'shut_off': self.__shut_off,
}
for k, v in options.iteritems():
if request.POST.get(k) is not None:
......@@ -414,75 +406,132 @@ class VmDetailView(CheckedDetailView):
return redirect("%s#network" % reverse_lazy(
"dashboard.views.detail", kwargs={'pk': self.object.pk}))
def __save_as(self, request):
self.object = self.get_object()
if not self.object.has_level(request.user, 'owner'):
raise PermissionDenied()
date = datetime.now().strftime("%Y-%m-%d %H:%M")
new_name = "Saved from %s (#%d) at %s" % (
self.object.name, self.object.pk, date
)
self.object.save_as_template.async(name=new_name,
user=request.user)
messages.success(request, _("Saving instance as template!"))
return redirect("%s#activity" % self.object.get_absolute_url())
class OperationView(DetailView):
def __shut_down(self, request):
self.object = self.get_object()
if not self.object.has_level(request.user, 'owner'):
raise PermissionDenied()
template_name = 'dashboard/operate.html'
self.object.shutdown.async(user=request.user)
return redirect("%s#activity" % self.object.get_absolute_url())
@property
def name(self):
return self.get_op().name
def __sleep(self, request):
self.object = self.get_object()
if not self.object.has_level(request.user, 'owner'):
raise PermissionDenied()
@property
def description(self):
return self.get_op().description
self.object.sleep.async(user=request.user)
return redirect("%s#activity" % self.object.get_absolute_url())
@classmethod
def get_urlname(cls):
return 'dashboard.vm.op.%s' % cls.op
def __wake_up(self, request):
self.object = self.get_object()
if not self.object.has_level(request.user, 'owner'):
raise PermissionDenied()
def get_url(self):
return reverse(self.get_urlname(), args=(self.get_object().pk, ))
self.object.wake_up.async(user=request.user)
return redirect("%s#activity" % self.object.get_absolute_url())
def get_wrapper_template_name(self):
if self.request.is_ajax():
return 'dashboard/_modal.html'
else:
return 'dashboard/_base.html'
def __deploy(self, request):
self.object = self.get_object()
if not self.object.has_level(request.user, 'owner'):
raise PermissionDenied()
@classmethod
def get_op_by_object(cls, obj):
return getattr(obj, cls.op)
self.object.deploy.async(user=request.user)
return redirect("%s#activity" % self.object.get_absolute_url())
def get_op(self):
if not hasattr(self, '_opobj'):
setattr(self, '_opobj', getattr(self.get_object(), self.op))
return self._opobj
def __reset(self, request):
self.object = self.get_object()
if not self.object.has_level(request.user, 'owner'):
raise PermissionDenied()
def get_context_data(self, **kwargs):
ctx = super(OperationView, self).get_context_data(**kwargs)
ctx['op'] = self.get_op()
ctx['url'] = self.request.path
return ctx
self.object.reset.async(user=request.user)
return redirect("%s#activity" % self.object.get_absolute_url())
def get(self, request, *args, **kwargs):
self.get_op().check_auth(request.user)
response = super(OperationView, self).get(request, *args, **kwargs)
response.render()
response.content = render_to_string(self.get_wrapper_template_name(),
{'body': response.content})
return response
def __reboot(self, request):
def post(self, request, extra=None, *args, **kwargs):
self.object = self.get_object()
if not self.object.has_level(request.user, 'owner'):
raise PermissionDenied()
self.object.reboot.async(user=request.user)
if extra is None:
extra = {}
try:
self.get_op().async(user=request.user, **extra)
except Exception as e:
messages.error(request, _('Could not start operation.'))
logger.error(e)
return redirect("%s#activity" % self.object.get_absolute_url())
def __shut_off(self, request):
self.object = self.get_object()
if not self.object.has_level(request.user, 'owner'):
raise PermissionDenied()
@classmethod
def factory(cls, op, icon='cog'):
return type(str(cls.__name__ + op),
(cls, ), {'op': op, 'icon': icon})
self.object.shut_off.async(user=request.user)
return redirect("%s#activity" % self.object.get_absolute_url())
@classmethod
def bind_to_object(cls, instance):
v = cls()
v.get_object = lambda: instance
return v
class VmOperationView(OperationView):
model = Instance
class VmMigrateView(VmOperationView):
op = 'migrate'
icon = 'truck'
template_name = 'dashboard/_vm-migrate.html'
def get_context_data(self, **kwargs):
ctx = super(VmOperationView, self).get_context_data(**kwargs)
ctx['nodes'] = [n for n in Node.objects.filter(enabled=True)
if n.state == "ONLINE"]
return ctx
def post(self, request, extra=None, *args, **kwargs):
if extra is None:
extra = {}
node = self.request.POST.get("node")
if node:
node = get_object_or_404(Node, pk=node)
extra["to_node"] = node
return super(VmMigrateView, self).post(request, extra, *args, **kwargs)
vm_ops = {
'reset': VmOperationView.factory(op='reset', icon='bolt'),
'deploy': VmOperationView.factory(op='deploy', icon='play'),
'migrate': VmMigrateView,
'reboot': VmOperationView.factory(op='reboot', icon='refresh'),
'shut_off': VmOperationView.factory(op='shut_off', icon='ban-circle'),
'shutdown': VmOperationView.factory(op='shutdown', icon='off'),
'save_as_template': VmOperationView.factory(
op='save_as_template', icon='save'),
'destroy': VmOperationView.factory(op='destroy', icon='remove'),
'sleep': VmOperationView.factory(op='sleep', icon='moon'),
'wake_up': VmOperationView.factory(op='wake_up', icon='sun'),
}
def get_operations(instance, user):
ops = []
for k, v in vm_ops.iteritems():
try:
op = v.get_op_by_object(instance)
op.check_auth(user)
op.check_precond()
except:
pass # unavailable
else:
ops.append(v.bind_to_object(instance))
return ops
class NodeDetailView(LoginRequiredMixin, SuperuserRequiredMixin, DetailView):
......@@ -1592,23 +1641,28 @@ def vm_activity(request, pk):
raise PermissionDenied()
response = {}
only_status = request.GET.get("only_status")
only_status = request.GET.get("only_status", "false")
response['human_readable_status'] = instance.get_status_display()
response['status'] = instance.status
response['icon'] = instance.get_status_icon()
if only_status == "false": # instance activity
context = {
'instance': instance,
'activities': InstanceActivity.objects.filter(
instance=instance, parent=None
).order_by('-started').select_related()
).order_by('-started').select_related(),
'ops': get_operations(instance, request.user),
}
activities = render_to_string(
response['activities'] = render_to_string(
"dashboard/vm-detail/_activity-timeline.html",
RequestContext(request, context),
)
response['activities'] = activities
response['ops'] = render_to_string(
"dashboard/vm-detail/_operations.html",
RequestContext(request, context),
)
return HttpResponse(
json.dumps(response),
......@@ -2018,40 +2072,6 @@ class NotificationView(LoginRequiredMixin, TemplateView):
return response
class VmMigrateView(SuperuserRequiredMixin, TemplateView):
def get_template_names(self):
if self.request.is_ajax():
return ['dashboard/modal-wrapper.html']
else:
return ['dashboard/nojs-wrapper.html']
def get(self, request, form=None, *args, **kwargs):
context = self.get_context_data(**kwargs)
vm = Instance.objects.get(pk=kwargs['pk'])
context.update({
'template': 'dashboard/_vm-migrate.html',
'box_title': _('Migrate %(name)s' % {'name': vm.name}),
'ajax_title': True,
'vm': vm,
'nodes': [n for n in Node.objects.filter(enabled=True)
if n.state == "ONLINE"]
})
return self.render_to_response(context)
def post(self, *args, **kwargs):
node = self.request.POST.get("node")
vm = Instance.objects.get(pk=kwargs['pk'])
if node:
node = Node.objects.get(pk=node)
vm.migrate.async(to_node=node, user=self.request.user)
else:
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 = {
......
from django.conf.urls import patterns, url
from ..views import vm_ops
urlpatterns = patterns('',
*(url(r'^%s/$' % op, v.as_view(), name=v.get_urlname())
for op, v in vm_ops.iteritems()))
......@@ -60,6 +60,7 @@ 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'
......@@ -96,6 +97,7 @@ 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'
......@@ -136,6 +138,7 @@ 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:
......@@ -166,6 +169,7 @@ 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)
......@@ -179,6 +183,7 @@ 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)
......@@ -195,6 +200,7 @@ class SaveAsTemplateOperation(InstanceOperation):
Template can be shared with groups and users.
Users can instantiate Virtual Machines from Templates.
""")
icon = 'save'
def _operation(self, activity, name, user, system, timeout=300,
with_shutdown=True, **kwargs):
......@@ -260,6 +266,7 @@ 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()
......@@ -289,6 +296,7 @@ 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'
......@@ -315,6 +323,7 @@ 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()
......@@ -354,6 +363,7 @@ class WakeUpOperation(InstanceOperation):
Power on Virtual Machine and load its memory from dump.
""")
icon = 'sun'
def check_precond(self):
super(WakeUpOperation, self).check_precond()
......
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