from os import getenv
import json
import logging
import re
from datetime import datetime
import requests
from django.conf import settings
from django.contrib.auth.models import User, Group
from django.contrib.auth.views import login, redirect_to_login
from django.contrib.messages import warning
from django.core.exceptions import (
PermissionDenied, SuspiciousOperation,
)
from django.core import signing
from django.core.urlresolvers import reverse, reverse_lazy
from django.db.models import Count
from django.http import HttpResponse, HttpResponseRedirect, Http404
from django.shortcuts import redirect, render, get_object_or_404
from django.views.decorators.http import require_GET
from django.views.generic.detail import SingleObjectMixin
from django.views.generic import (TemplateView, DetailView, View, DeleteView,
UpdateView, CreateView, ListView)
from django.contrib import messages
from django.utils.translation import ugettext as _
from django.template.defaultfilters import title as title_filter
from django.template.loader import render_to_string
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 .forms import (
CircleAuthenticationForm, DiskAddForm, HostForm, LeaseForm, NodeForm,
TemplateForm, TraitForm, VmCustomizeForm,
)
from .tables import (NodeListTable, NodeVmListTable,
TemplateListTable, LeaseListTable, GroupListTable,)
from vm.models import (
Instance, instance_activity, InstanceActivity, InstanceTemplate, Interface,
InterfaceTemplate, Lease, Node, NodeActivity, Trait,
)
from firewall.models import Vlan, Host, Rule
from dashboard.models import Favourite, Profile
logger = logging.getLogger(__name__)
def search_user(keyword):
try:
return User.objects.get(username=keyword)
except User.DoesNotExist:
try:
return User.objects.get(profile__org_id=keyword)
except User.DoesNotExist:
return User.objects.get(email=keyword)
# github.com/django/django/blob/stable/1.6.x/django/contrib/messages/views.py
class SuccessMessageMixin(object):
"""
Adds a success message on successful form submission.
"""
success_message = ''
def form_valid(self, form):
response = super(SuccessMessageMixin, self).form_valid(form)
success_message = self.get_success_message(form.cleaned_data)
if success_message:
messages.success(self.request, success_message)
return response
def get_success_message(self, cleaned_data):
return self.success_message % cleaned_data
class IndexView(LoginRequiredMixin, TemplateView):
template_name = "dashboard/index.html"
def get_context_data(self, **kwargs):
if self.request.user.is_authenticated():
user = self.request.user
else:
user = None
context = super(IndexView, self).get_context_data(**kwargs)
favs = Instance.objects.filter(favourite__user=self.request.user)
instances = Instance.get_objects_with_level(
'user', user).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
context.update({
'instances': display[:5],
'more_instances': instances.count() - len(instances[:5])
})
if user is not None:
context['new_notifications'] = user.notification_set.filter(
status="new").count()
nodes = Node.objects.all()
groups = Group.objects.all()
context.update({
'nodes': nodes[:10],
'more_nodes': nodes.count() - len(nodes[:10]),
'groups': groups[:10],
'more_groups': groups.count() - len(groups[:10]),
'sum_node_num': nodes.count(),
'node_num': {
'running': Node.get_state_count(True, True),
'missing': Node.get_state_count(False, True),
'disabled': Node.get_state_count(True, False),
'offline': Node.get_state_count(False, False)
}
})
running = instances.filter(status='RUNNING')
stopped = instances.exclude(status__in=('RUNNING', 'NOSTATE'))
context.update({
'running_vms': running[:20],
'running_vm_num': running.count(),
'stopped_vm_num': stopped.count()
})
context['templates'] = InstanceTemplate.objects.all()[:5]
return context
def get_vm_acl_data(obj):
levels = obj.ACL_LEVELS
users = obj.get_users_with_level()
users = [{'user': u, 'level': l} for u, l in users]
groups = obj.get_groups_with_level()
groups = [{'group': g, 'level': l} for g, l in groups]
return {'users': users, 'groups': groups, 'levels': levels,
'url': reverse('dashboard.views.vm-acl', args=[obj.pk])}
def get_group_acl_data(obj):
aclobj = obj.profile
levels = aclobj.ACL_LEVELS
users = aclobj.get_users_with_level()
users = [{'user': u, 'level': l} for u, l in users]
groups = aclobj.get_groups_with_level()
groups = [{'group': g, 'level': l} for g, l in groups]
return {'users': users, 'groups': groups, 'levels': levels,
'url': reverse('dashboard.views.group-acl', args=[obj.pk])}
class CheckedDetailView(LoginRequiredMixin, DetailView):
read_level = 'user'
def get_has_level(self):
return self.object.has_level
def get_context_data(self, **kwargs):
context = super(CheckedDetailView, self).get_context_data(**kwargs)
if not self.get_has_level()(self.request.user, self.read_level):
raise PermissionDenied()
return context
class VmDetailVncTokenView(CheckedDetailView):
template_name = "dashboard/vm-detail.html"
model = Instance
def get(self, request, **kwargs):
self.object = self.get_object()
if not self.object.has_level(request.user, 'operator'):
raise PermissionDenied()
if self.object.node:
port = self.object.vnc_port
host = str(self.object.node.host.ipv4)
value = signing.dumps({'host': host,
'port': port},
key=getenv("PROXY_SECRET", 'asdasd')),
return HttpResponse('vnc/?d=%s' % value)
else:
raise Http404()
class VmDetailView(CheckedDetailView):
template_name = "dashboard/vm-detail.html"
model = Instance
def get_context_data(self, **kwargs):
context = super(VmDetailView, self).get_context_data(**kwargs)
instance = context['instance']
context.update({
'graphite_enabled': VmGraphView.get_graphite_url() is not None,
'vnc_url': reverse_lazy("dashboard.views.detail-vnc",
kwargs={'pk': self.object.pk})
})
# activity data
context['activities'] = (
InstanceActivity.objects.filter(
instance=self.object, parent=None).
order_by('-started').
select_related('user').prefetch_related('children'))
context['vlans'] = Vlan.get_objects_with_level(
'user', self.request.user
).exclude(
pk__in=Interface.objects.filter(
instance=self.get_object()).values_list("vlan", flat=True)
).all()
context['acl'] = get_vm_acl_data(instance)
context['forms'] = {
'disk_add_form': DiskAddForm(
user=self.request.user,
is_template=False, object_pk=self.get_object().pk,
prefix="disk"),
}
context['os_type_icon'] = instance.os_type.replace("unknown",
"question")
return context
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 = {
'change_password': self.__change_password,
'new_name': self.__set_name,
'new_tag': self.__add_tag,
'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:
return v(request)
def __change_password(self, request):
self.object = self.get_object()
if not self.object.has_level(request.user, 'owner'):
raise PermissionDenied()
self.object.change_password(user=request.user)
messages.success(request, _("Password changed!"))
if request.is_ajax():
return HttpResponse("Success!")
else:
return redirect(reverse_lazy("dashboard.views.detail",
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'),
'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):
self.object = self.get_object()
if not self.object.has_level(request.user, 'owner'):
raise PermissionDenied()
new_name = request.POST.get("new_name")
Instance.objects.filter(pk=self.object.pk).update(
**{'name': new_name})
success_message = _("VM successfully renamed!")
if request.is_ajax():
response = {
'message': success_message,
'new_name': new_name,
'vm_pk': self.object.pk
}
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 __add_tag(self, request):
new_tag = request.POST.get('new_tag')
self.object = self.get_object()
if not self.object.has_level(request.user, 'owner'):
raise PermissionDenied()
if len(new_tag) < 1:
message = u"Please input something!"
elif len(new_tag) > 20:
message = u"Tag name is too long!"
else:
self.object.tags.add(new_tag)
try:
messages.error(request, message)
except:
pass
return redirect(reverse_lazy("dashboard.views.detail",
kwargs={'pk': self.object.pk}))
def __remove_tag(self, request):
try:
to_remove = request.POST.get('to_remove')
self.object = self.get_object()
if not self.object.has_level(request.user, 'owner'):
raise PermissionDenied()
self.object.tags.remove(to_remove)
message = u"Success"
except: # note this won't really happen
message = u"Not success"
if request.is_ajax():
return HttpResponse(
json.dumps({'message': message}),
content_type="application=json"
)
else:
return redirect(reverse_lazy("dashboard.views.detail",
kwargs={'pk': self.object.pk}))
def __add_port(self, request):
object = self.get_object()
if (not object.has_level(request.user, 'owner') or
not request.user.has_perm('vm.config_ports')):
raise PermissionDenied()
port = request.POST.get("port")
proto = request.POST.get("proto")
try:
error = None
interfaces = object.interface_set.all()
host = Host.objects.get(pk=request.POST.get("host_pk"),
interface__in=interfaces)
host.add_port(proto, private=port)
except Host.DoesNotExist:
logger.error('Tried to add port to nonexistent host %d. User: %s. '
'Instance: %s', request.POST.get("host_pk"),
unicode(request.user), object)
raise PermissionDenied()
except ValueError:
error = _("There is a problem with your input!")
except Exception as e:
error = _("Unknown error.")
logger.error(e)
if request.is_ajax():
pass
else:
if error:
messages.error(request, error)
return redirect(reverse_lazy("dashboard.views.detail",
kwargs={'pk': self.get_object().pk}))
def __new_network(self, request):
self.object = self.get_object()
if not self.object.has_level(request.user, 'owner'):
raise PermissionDenied()
vlan = Vlan.objects.get(pk=request.POST.get("new_network_vlan"))
if not vlan.has_level(request.user, 'user'):
raise PermissionDenied()
try:
Interface.create(vlan=vlan, instance=self.object,
managed=vlan.managed, owner=request.user)
messages.success(request, _("Successfully added new interface!"))
except Exception, e:
error = u' '.join(e.messages)
messages.error(request, error)
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
)
template = self.object.save_as_template(name=new_name,
owner=request.user)
messages.success(request, _("Instance successfully saved as template, "
"please rename it!"))
return redirect(reverse_lazy("dashboard.views.template-detail",
kwargs={'pk': template.pk}))
def __shut_down(self, request):
self.object = self.get_object()
if not self.object.has_level(request.user, 'owner'):
raise PermissionDenied()
self.object.shutdown_async(request.user)
return redirect("%s#activity" % self.object.get_absolute_url())
def __sleep(self, request):
self.object = self.get_object()
if not self.object.has_level(request.user, 'owner'):
raise PermissionDenied()
self.object.sleep_async(request.user)
return redirect("%s#activity" % self.object.get_absolute_url())
def __wake_up(self, request):
self.object = self.get_object()
if not self.object.has_level(request.user, 'owner'):
raise PermissionDenied()
self.object.wake_up_async(request.user)
return redirect("%s#activity" % self.object.get_absolute_url())
def __deploy(self, request):
self.object = self.get_object()
if not self.object.has_level(request.user, 'owner'):
raise PermissionDenied()
self.object.deploy_async(request.user)
return redirect("%s#activity" % self.object.get_absolute_url())
def __reset(self, request):
self.object = self.get_object()
if not self.object.has_level(request.user, 'owner'):
raise PermissionDenied()
self.object.reset_async(request.user)
return redirect("%s#activity" % self.object.get_absolute_url())
def __reboot(self, request):
self.object = self.get_object()
if not self.object.has_level(request.user, 'owner'):
raise PermissionDenied()
self.object.reboot_async(request.user)
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()
self.object.shut_off_async(request.user)
return redirect("%s#activity" % self.object.get_absolute_url())
class NodeDetailView(LoginRequiredMixin, SuperuserRequiredMixin, DetailView):
template_name = "dashboard/node-detail.html"
model = Node
form = None
form_class = TraitForm
def get_context_data(self, form=None, **kwargs):
if form is None:
form = self.form_class()
context = super(NodeDetailView, self).get_context_data(**kwargs)
instances = Instance.active.filter(node=self.object)
context['table'] = NodeVmListTable(instances)
na = NodeActivity.objects.filter(
node=self.object, parent=None
).order_by('-started').select_related()
context['activities'] = na
context['trait_form'] = form
context['graphite_enabled'] = (
NodeGraphView.get_graphite_url() is not None)
return context
def post(self, request, *args, **kwargs):
print request.POST
if request.POST.get('new_name'):
return self.__set_name(request)
if request.POST.get('change_status') is not None:
return self.__set_status(request)
if request.POST.get('to_remove'):
return self.__remove_trait(request)
return redirect(reverse_lazy("dashboard.views.node-detail",
kwargs={'pk': self.get_object().pk}))
def __set_name(self, request):
self.object = self.get_object()
new_name = request.POST.get("new_name")
Node.objects.filter(pk=self.object.pk).update(
**{'name': new_name})
success_message = _("Node successfully renamed!")
if request.is_ajax():
response = {
'message': success_message,
'new_name': new_name,
'node_pk': self.object.pk
}
return HttpResponse(
json.dumps(response),
content_type="application/json"
)
else:
messages.success(request, success_message)
return redirect(reverse_lazy("dashboard.views.node-detail",
kwargs={'pk': self.object.pk}))
def __set_status(self, request):
self.object = self.get_object()
if not self.object.enabled:
self.object.enable(user=request.user)
else:
self.object.disable(user=request.user)
success_message = _("Node successfully changed status!")
if request.is_ajax():
response = {
'message': success_message,
'node_pk': self.object.pk
}
return HttpResponse(
json.dumps(response),
content_type="application/json"
)
else:
messages.success(request, success_message)
return redirect(reverse_lazy("dashboard.views.node-detail",
kwargs={'pk': self.object.pk}))
def __remove_trait(self, request):
try:
to_remove = request.POST.get('to_remove')
self.object = self.get_object()
self.object.traits.remove(to_remove)
message = u"Success"
except: # note this won't really happen
message = u"Not success"
if request.is_ajax():
return HttpResponse(
json.dumps({'message': message}),
content_type="application/json"
)
else:
return redirect(self.object.get_absolute_url())
class GroupDetailView(CheckedDetailView):
template_name = "dashboard/group-detail.html"
model = Group
def get_has_level(self):
return self.object.profile.has_level
def get_context_data(self, **kwargs):
context = super(GroupDetailView, self).get_context_data(**kwargs)
context['group'] = self.object
context['users'] = self.object.user_set.all()
context['acl'] = get_group_acl_data(self.object)
return context
def post(self, request, *args, **kwargs):
if request.POST.get('new_name'):
return self.__set_name(request)
def __set_name(self, request):
self.object = self.get_object()
new_name = request.POST.get("new_name")
Group.objects.filter(pk=self.object.pk).update(
**{'name': new_name})
success_message = _("Group successfully renamed!")
if request.is_ajax():
response = {
'message': success_message,
'new_name': new_name,
'group_pk': self.object.pk
}
return HttpResponse(
json.dumps(response),
content_type="application/json"
)
else:
messages.success(request, success_message)
return redirect(reverse_lazy("dashboard.views.group-detail",
kwargs={'pk': self.object.pk}))
class AclUpdateView(LoginRequiredMixin, View, SingleObjectMixin):
def post(self, request, *args, **kwargs):
instance = self.get_object()
if not (instance.has_level(request.user, "owner") or
getattr(instance, 'owner', None) == request.user):
logger.warning('Tried to set permissions of %s by non-owner %s.',
unicode(instance), unicode(request.user))
raise PermissionDenied()
self.set_levels(request, instance)
self.add_levels(request, instance)
return redirect(instance)
def set_levels(self, request, instance):
for key, value in request.POST.items():
m = re.match('perm-([ug])-(\d+)', key)
if m:
typ, id = m.groups()
entity = {'u': User, 'g': Group}[typ].objects.get(id=id)
if getattr(instance, "owner", None) == entity:
logger.info("Tried to set owner's acl level for %s by %s.",
unicode(instance), unicode(request.user))
continue
instance.set_level(entity, value)
logger.info("Set %s's acl level for %s to %s by %s.",
unicode(entity), unicode(instance),
value, unicode(request.user))
def add_levels(self, request, instance):
name = request.POST['perm-new-name']
value = request.POST['perm-new']
if not name:
return
try:
entity = User.objects.get(username=name)
except User.DoesNotExist:
entity = None
try:
entity = Group.objects.get(name=name)
except Group.DoesNotExist:
warning(request, _('User or group "%s" not found.') % name)
return
instance.set_level(entity, value)
logger.info("Set %s's new acl level for %s to %s by %s.",
unicode(entity), unicode(instance),
value, unicode(request.user))
class TemplateAclUpdateView(AclUpdateView):
model = InstanceTemplate
def post(self, request, *args, **kwargs):
template = self.get_object()
if not (template.has_level(request.user, "owner") or
getattr(template, 'owner', None) == request.user):
logger.warning('Tried to set permissions of %s by non-owner %s.',
unicode(template), unicode(request.user))
raise PermissionDenied()
name = request.POST['perm-new-name']
if (User.objects.filter(username=name).count() +
Group.objects.filter(name=name).count() < 1
and len(name) > 0):
warning(request, _('User or group "%s" not found.') % name)
else:
self.set_levels(request, template)
self.add_levels(request, template)
post_for_disk = request.POST.copy()
post_for_disk['perm-new'] = 'user'
request.POST = post_for_disk
for d in template.disks.all():
self.add_levels(request, d)
return redirect(reverse("dashboard.views.template-detail",
kwargs=self.kwargs))
class GroupAclUpdateView(AclUpdateView):
model = Group
def post(self, request, *args, **kwargs):
instance = self.get_object().profile
if not (instance.has_level(request.user, "owner") or
getattr(instance, 'owner', None) == request.user):
logger.warning('Tried to set permissions of %s by non-owner %s.',
unicode(instance), unicode(request.user))
raise PermissionDenied()
name = request.POST['perm-new-name']
if (User.objects.filter(username=name).count() +
Group.objects.filter(name=name).count() < 1
and len(name) > 0):
warning(request, _('User or group "%s" not found.') % name)
else:
self.set_levels(request, instance)
self.add_levels(request, instance)
# return redirect(self.profile)
return redirect(reverse("dashboard.views.group-detail",
kwargs=self.kwargs))
def repost(self, request, *args, **kwargs):
group = self.get_object()
if not (group.profile.has_level(request.user, "owner") or
getattr(group.profile, 'owner', None) == request.user):
logger.warning('Tried to set permissions of %s by non-owner %s.',
unicode(group), unicode(request.user))
raise PermissionDenied()
name = request.POST['perm-new-name']
if (User.objects.filter(username=name).count() +
Group.objects.filter(name=name).count() < 1
and len(name) > 0):
warning(request, _('User or group "%s" not found.') % name)
else:
self.set_levels(request, group.profile)
self.add_levels(request, group.profile)
return redirect(reverse("dashboard.views.group-detail",
kwargs=self.kwargs))
class TemplateCreate(SuccessMessageMixin, CreateView):
model = InstanceTemplate
form_class = TemplateForm
template_name = "dashboard/template-create.html"
success_message = _("Successfully created a new template!")
def get(self, *args, **kwargs):
if not self.request.user.has_perm('vm.create_template'):
raise PermissionDenied()
self.parent = self.request.GET.get("parent")
return super(TemplateCreate, self).get(*args, **kwargs)
def get_form_kwargs(self):
kwargs = super(TemplateCreate, self).get_form_kwargs()
kwargs['parent'] = getattr(self, "parent", None)
kwargs['user'] = self.request.user
return kwargs
def post(self, request, *args, **kwargs):
if not self.request.user.has_perm('vm.create_template'):
raise PermissionDenied()
form = self.form_class(request.POST, user=request.user)
if not form.is_valid():
return self.get(request, form, *args, **kwargs)
post = form.cleaned_data
for disk in post['disks']:
if not disk.has_level(request.user, 'user'):
raise PermissionDenied()
return super(TemplateCreate, self).post(self, request, args, kwargs)
def get_success_url(self):
return reverse_lazy("dashboard.views.template-list")
class TemplateDetail(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
model = InstanceTemplate
template_name = "dashboard/template-edit.html"
form_class = TemplateForm
success_message = _("Successfully modified template!")
def get(self, request, *args, **kwargs):
template = InstanceTemplate.objects.get(pk=kwargs['pk'])
if not template.has_level(request.user, 'user'):
raise PermissionDenied()
if request.is_ajax():
template = {
'num_cores': template.num_cores,
'ram_size': template.ram_size,
'priority': template.priority,
'arch': template.arch,
'description': template.description,
'system': template.system,
'name': template.name,
'disks': [{'pk': d.pk, 'name': d.name}
for d in template.disks.all()],
'network': [
{'vlan_pk': i.vlan.pk, 'vlan': i.vlan.name,
'managed': i.managed}
for i in InterfaceTemplate.objects.filter(
template=self.get_object()).all()
]
}
return HttpResponse(json.dumps(template),
content_type="application/json")
else:
return super(TemplateDetail, self).get(request, *args, **kwargs)
def get_context_data(self, **kwargs):
obj = self.get_object()
context = super(TemplateDetail, self).get_context_data(**kwargs)
context['acl'] = get_vm_acl_data(obj)
context['disks'] = obj.disks.all()
context['disk_add_form'] = DiskAddForm(
user=self.request.user,
is_template=True,
object_pk=obj.pk,
prefix="disk",
)
return context
def get_success_url(self):
return reverse_lazy("dashboard.views.template-detail",
kwargs=self.kwargs)
def post(self, request, *args, **kwargs):
template = self.get_object()
if not template.has_level(request.user, 'owner'):
raise PermissionDenied()
for disk in self.get_object().disks.all():
if not disk.has_level(request.user, 'user'):
raise PermissionDenied()
return super(TemplateDetail, self).post(self, request, args, kwargs)
def get_form_kwargs(self):
kwargs = super(TemplateDetail, self).get_form_kwargs()
kwargs['user'] = self.request.user
return kwargs
class TemplateList(LoginRequiredMixin, SingleTableView):
template_name = "dashboard/template-list.html"
model = InstanceTemplate
table_class = TemplateListTable
table_pagination = False
def get_context_data(self, *args, **kwargs):
context = super(TemplateList, self).get_context_data(*args, **kwargs)
context['lease_table'] = LeaseListTable(Lease.objects.all())
return context
def get_queryset(self):
logger.debug('TemplateList.get_queryset() called. User: %s',
unicode(self.request.user))
return InstanceTemplate.get_objects_with_level(
'user', self.request.user).all()
class TemplateDelete(LoginRequiredMixin, DeleteView):
model = InstanceTemplate
def get_success_url(self):
return reverse("dashboard.views.template-list")
def get_template_names(self):
if self.request.is_ajax():
return ['dashboard/confirm/ajax-delete.html']
else:
return ['dashboard/confirm/base-delete.html']
def delete(self, request, *args, **kwargs):
object = self.get_object()
if not object.has_level(request.user, 'owner'):
raise PermissionDenied()
object.delete()
success_url = self.get_success_url()
success_message = _("Template successfully deleted!")
if request.is_ajax():
return HttpResponse(
json.dumps({'message': success_message}),
content_type="application/json",
)
else:
messages.success(request, success_message)
return HttpResponseRedirect(success_url)
class VmList(LoginRequiredMixin, ListView):
template_name = "dashboard/vm-list.html"
def get(self, *args, **kwargs):
if self.request.is_ajax():
favs = Instance.objects.filter(
favourite__user=self.request.user).values_list('pk', flat=True)
instances = Instance.get_objects_with_level(
'user', self.request.user).filter(
destroyed_at=None).all()
instances = [{
'pk': i.pk,
'name': i.name,
'state': i.state,
'fav': i.pk in favs} for i in instances]
return HttpResponse(
json.dumps(list(instances)), # instances is ValuesQuerySet
content_type="application/json",
)
else:
return super(VmList, self).get(*args, **kwargs)
def get_queryset(self):
logger.debug('VmList.get_queryset() called. User: %s',
unicode(self.request.user))
queryset = Instance.get_objects_with_level(
'user', self.request.user).filter(destroyed_at=None)
s = self.request.GET.get("s")
if s:
queryset = queryset.filter(name__icontains=s)
return queryset.select_related('owner', 'node')
class NodeList(LoginRequiredMixin, SuperuserRequiredMixin, SingleTableView):
template_name = "dashboard/node-list.html"
table_class = NodeListTable
table_pagination = False
def get_queryset(self):
return Node.objects.annotate(
number_of_VMs=Count('instance_set')).select_related('host')
class GroupList(LoginRequiredMixin, SuperuserRequiredMixin, SingleTableView):
template_name = "dashboard/group-list.html"
model = Group
table_class = GroupListTable
table_pagination = False
class GroupUserDelete(LoginRequiredMixin, SuperuserRequiredMixin, DeleteView):
"""This stuff deletes the group.
"""
model = User
template_name = "dashboard/confirm/base-delete.html"
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):
# this is redundant now, but if we wanna add more to print
# we'll need this
context = super(GroupUserDelete, self).get_context_data(**kwargs)
return context
# github.com/django/django/blob/master/django/views/generic/edit.py#L245
def delete(self, request, *args, **kwargs):
object = self.get_object()
object.delete()
success_url = self.get_success_url()
success_message = _("Group successfully deleted!")
if request.is_ajax():
if request.POST.get('redirect').lower() == "true":
messages.success(request, success_message)
return HttpResponse(
json.dumps({'message': success_message}),
content_type="application/json",
)
else:
messages.success(request, success_message)
return HttpResponseRedirect(success_url)
def get_success_url(self):
next = self.request.POST.get('next')
if next:
return next
else:
return reverse_lazy('dashboard.index')
class GroupDelete(LoginRequiredMixin, SuperuserRequiredMixin, DeleteView):
"""This stuff deletes the group.
"""
model = Group
template_name = "dashboard/confirm/base-delete.html"
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):
# this is redundant now, but if we wanna add more to print
# we'll need this
context = super(GroupDelete, self).get_context_data(**kwargs)
return context
# github.com/django/django/blob/master/django/views/generic/edit.py#L245
def delete(self, request, *args, **kwargs):
object = self.get_object()
object.delete()
success_url = self.get_success_url()
success_message = _("Group successfully deleted!")
if request.is_ajax():
if request.POST.get('redirect').lower() == "true":
messages.success(request, success_message)
return HttpResponse(
json.dumps({'message': success_message}),
content_type="application/json",
)
else:
messages.success(request, success_message)
return HttpResponseRedirect(success_url)
def get_success_url(self):
next = self.request.POST.get('next')
if next:
return next
else:
return reverse_lazy('dashboard.index')
class VmCreate(LoginRequiredMixin, TemplateView):
form_class = VmCustomizeForm
form = None
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):
form_error = form is not None
template = (form.template.pk if form_error
else request.GET.get("template"))
templates = InstanceTemplate.get_objects_with_level('user',
request.user)
if form is None and template:
form = self.form_class(user=request.user,
template=templates.get(pk=template))
context = self.get_context_data(**kwargs)
if template:
context.update({
'template': 'dashboard/_vm-create-2.html',
'box_title': _('Customize VM'),
'ajax_title': False,
'vm_create_form': form,
'template_o': templates.get(pk=template),
})
else:
context.update({
'template': 'dashboard/_vm-create-1.html',
'box_title': _('Create a VM'),
'ajax_title': False,
'templates': templates.all(),
})
return self.render_to_response(context)
def __create_normal(self, request, *args, **kwargs):
user = request.user
template = InstanceTemplate.objects.get(
pk=request.POST.get("template"))
# permission check
if not template.has_level(request.user, 'user'):
raise PermissionDenied()
inst = Instance.create_from_template(
template=template, owner=user)
return self.__deploy(request, inst)
def __create_customized(self, request, *args, **kwargs):
user = request.user
form = self.form_class(
request.POST, user=request.user,
template=InstanceTemplate.objects.get(
pk=request.POST.get("template")
)
)
if not form.is_valid():
return self.get(request, form, *args, **kwargs)
post = form.cleaned_data
template = InstanceTemplate.objects.get(pk=post['template'])
# permission check
if not template.has_level(user, 'user'):
raise PermissionDenied()
if request.user.has_perm('vm.set_resources'):
ikwargs = {
'name': post['name'],
'num_cores': post['cpu_count'],
'ram_size': post['ram_size'],
'priority': post['cpu_priority'],
}
networks = [InterfaceTemplate(vlan=l, managed=l.managed)
for l in post['networks']]
disks = post['disks']
inst = Instance.create_from_template(
template=template, owner=user, networks=networks,
disks=disks, **ikwargs)
return self.__deploy(request, inst)
else:
raise PermissionDenied()
def __deploy(self, request, instance, *args, **kwargs):
instance.deploy_async(user=request.user)
messages.success(request, _('VM successfully created!'))
path = instance.get_absolute_url()
if request.is_ajax():
return HttpResponse(json.dumps({'redirect': path}),
content_type="application/json")
else:
return redirect("%s#activity" % path)
def post(self, request, *args, **kwargs):
user = request.user
# limit chekcs
try:
limit = user.profile.instance_limit
except Exception as e:
logger.debug('No profile or instance limit: %s', e)
else:
current = Instance.active.filter(owner=user).count()
logger.debug('current use: %d, limit: %d', current, limit)
if limit < current:
messages.error(request,
_('Instance limit (%d) exceeded.') % limit)
if request.is_ajax():
return HttpResponse(json.dumps({'redirect': '/'}),
content_type="application/json")
else:
return redirect('/')
create_func = (self.__create_normal if
request.POST.get("customized") is None else
self.__create_customized)
return create_func(request, *args, **kwargs)
class NodeCreate(LoginRequiredMixin, SuperuserRequiredMixin, TemplateView):
form_class = HostForm
hostform = None
formset_class = inlineformset_factory(Host, Node, form=NodeForm, extra=1)
formset = None
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, hostform=None, formset=None, *args, **kwargs):
if hostform is None:
hostform = self.form_class()
if formset is None:
formset = self.formset_class(instance=Host())
context = self.get_context_data(**kwargs)
context.update({
'template': 'dashboard/node-create.html',
'box_title': 'Create a Node',
'hostform': hostform,
'formset': formset,
})
return self.render_to_response(context)
def get_context_data(self, **kwargs):
context = super(NodeCreate, self).get_context_data(**kwargs)
# TODO acl
context.update({
})
return context
# TODO handle not ajax posts
def post(self, request, *args, **kwargs):
if not self.request.user.is_authenticated():
raise PermissionDenied()
hostform = self.form_class(request.POST)
formset = self.formset_class(request.POST, Host())
if not hostform.is_valid():
return self.get(request, hostform, formset, *args, **kwargs)
hostform.setowner(request.user)
savedform = hostform.save(commit=False)
formset = self.formset_class(request.POST, instance=savedform)
if not formset.is_valid():
return self.get(request, hostform, formset, *args, **kwargs)
savedform.save()
nodemodel = formset.save()
messages.success(request, _('Node successfully created!'))
path = nodemodel[0].get_absolute_url()
if request.is_ajax():
return HttpResponse(json.dumps({'redirect': path}),
content_type="application/json")
else:
return redirect(path)
class VmDelete(LoginRequiredMixin, DeleteView):
model = Instance
template_name = "dashboard/confirm/base-delete.html"
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_success_url(self):
next = self.request.POST.get('next')
if next:
return next
else:
return reverse_lazy('dashboard.index')
def get_context_data(self, **kwargs):
object = self.get_object()
if not object.has_level(self.request.user, 'owner'):
raise PermissionDenied()
# this is redundant now, but if we wanna add more to print
# we'll need this
context = super(VmDelete, self).get_context_data(**kwargs)
return context
# github.com/django/django/blob/master/django/views/generic/edit.py#L245
def delete(self, request, *args, **kwargs):
object = self.get_object()
if not object.has_level(request.user, 'owner'):
raise PermissionDenied()
object.destroy_async(user=request.user)
success_url = self.get_success_url()
success_message = _("VM successfully deleted!")
if request.is_ajax():
if request.POST.get('redirect').lower() == "true":
messages.success(request, success_message)
return HttpResponse(
json.dumps({'message': success_message}),
content_type="application/json",
)
else:
messages.success(request, success_message)
return HttpResponseRedirect(success_url)
class NodeDelete(LoginRequiredMixin, SuperuserRequiredMixin, DeleteView):
"""This stuff deletes the node.
"""
model = Node
template_name = "dashboard/confirm/base-delete.html"
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):
# this is redundant now, but if we wanna add more to print
# we'll need this
context = super(NodeDelete, self).get_context_data(**kwargs)
return context
# github.com/django/django/blob/master/django/views/generic/edit.py#L245
def delete(self, request, *args, **kwargs):
object = self.get_object()
object.delete()
success_url = self.get_success_url()
success_message = _("Node successfully deleted!")
if request.is_ajax():
if request.POST.get('redirect').lower() == "true":
messages.success(request, success_message)
return HttpResponse(
json.dumps({'message': success_message}),
content_type="application/json",
)
else:
messages.success(request, success_message)
return HttpResponseRedirect(success_url)
def get_success_url(self):
next = self.request.POST.get('next')
if next:
return next
else:
return reverse_lazy('dashboard.index')
class NodeAddTraitView(SuperuserRequiredMixin, DetailView):
model = Node
template_name = "dashboard/node-add-trait.html"
def get_success_url(self):
next = self.request.GET.get('next')
if next:
return next
else:
return self.object.get_absolute_url()
def get_context_data(self, **kwargs):
self.object = self.get_object()
context = super(NodeAddTraitView, self).get_context_data(**kwargs)
context['form'] = (TraitForm(self.request.POST) if self.request.POST
else TraitForm())
return context
def post(self, request, pk, *args, **kwargs):
context = self.get_context_data(**kwargs)
form = context['form']
if form.is_valid():
node = self.object
n = form.cleaned_data['name']
trait, created = Trait.objects.get_or_create(name=n)
node.traits.add(trait)
success_message = _("Trait successfully added to node.")
messages.success(request, success_message)
return redirect(self.get_success_url())
else:
return self.get(self, request, pk, *args, **kwargs)
class NodeStatus(LoginRequiredMixin, SuperuserRequiredMixin, DetailView):
template_name = "dashboard/confirm/node-status.html"
model = Node
def get_template_names(self):
if self.request.is_ajax():
return ['dashboard/confirm/ajax-node-status.html']
else:
return ['dashboard/confirm/node-status.html']
def get_success_url(self):
next = self.request.GET.get('next')
if next:
return next
else:
return reverse_lazy("dashboard.views.node-detail",
kwargs={'pk': self.object.pk})
def get_context_data(self, **kwargs):
context = super(NodeStatus, self).get_context_data(**kwargs)
if self.object.enabled:
context['status'] = "disable"
else:
context['status'] = "enable"
return context
def post(self, request, *args, **kwargs):
if request.POST.get('change_status') is not None:
return self.__set_status(request)
return redirect(reverse_lazy("dashboard.views.node-detail",
kwargs={'pk': self.get_object().pk}))
def __set_status(self, request):
self.object = self.get_object()
if not self.object.enabled:
self.object.enable(user=request.user)
else:
self.object.disable(user=request.user)
success_message = _("Node successfully changed status!")
if request.is_ajax():
response = {
'message': success_message,
'node_pk': self.object.pk
}
return HttpResponse(
json.dumps(response),
content_type="application/json"
)
else:
messages.success(request, success_message)
return redirect(self.get_success_url())
class PortDelete(LoginRequiredMixin, DeleteView):
model = Rule
pk_url_kwarg = 'rule'
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(PortDelete, self).get_context_data(**kwargs)
rule = kwargs.get('object')
instance = rule.host.interface_set.get().instance
context['title'] = _("Port delete confirmation")
context['text'] = _("Are you sure you want to close %(port)d/"
"%(proto)s on %(vm)s?" % {'port': rule.dport,
'proto': rule.proto,
'vm': instance})
return context
def delete(self, request, *args, **kwargs):
rule = Rule.objects.get(pk=kwargs.get("rule"))
instance = rule.host.interface_set.get().instance
if not instance.has_level(request.user, 'owner'):
raise PermissionDenied()
super(PortDelete, self).delete(request, *args, **kwargs)
success_url = self.get_success_url()
success_message = _("Port successfully removed!")
if request.is_ajax():
return HttpResponse(
json.dumps({'message': success_message}),
content_type="application/json",
)
else:
messages.success(request, success_message)
return HttpResponseRedirect("%s#network" % success_url)
def get_success_url(self):
return reverse_lazy('dashboard.views.detail',
kwargs={'pk': self.kwargs.get("pk")})
class VmMassDelete(LoginRequiredMixin, View):
def get(self, request, *args, **kwargs):
vms = request.GET.getlist('v[]')
objects = Instance.objects.filter(pk__in=vms)
return render(request, "dashboard/confirm/mass-delete.html",
{'objects': objects})
def post(self, request, *args, **kwargs):
vms = request.POST.getlist('vms')
names = []
if vms is not None:
for i in Instance.objects.filter(pk__in=vms):
if not i.has_level(request.user, 'owner'):
logger.info('Tried to delete instance #%d without owner '
'permission by %s.', i.pk,
unicode(request.user))
raise PermissionDenied() # no need for rollback or proper
# error message, this can't
# normally happen.
i.destroy_async(request.user)
names.append(i.name)
success_message = _("Mass delete complete, the following VMs were "
"deleted: %s!") % u', '.join(names)
# we can get this only via AJAX ...
if request.is_ajax():
return HttpResponse(
json.dumps({'message': success_message}),
content_type="application/json"
)
else:
messages.success(request, success_message)
next = request.GET.get('next')
return redirect(next if next else reverse_lazy('dashboard.index'))
class LeaseCreate(LoginRequiredMixin, SuperuserRequiredMixin,
SuccessMessageMixin, CreateView):
model = Lease
form_class = LeaseForm
template_name = "dashboard/lease-create.html"
success_message = _("Successfully created a new lease!")
def get_success_url(self):
return reverse_lazy("dashboard.views.template-list")
class LeaseDetail(LoginRequiredMixin, SuperuserRequiredMixin,
SuccessMessageMixin, UpdateView):
model = Lease
form_class = LeaseForm
template_name = "dashboard/lease-edit.html"
success_message = _("Successfully modified lease!")
def get_success_url(self):
return reverse_lazy("dashboard.views.lease-detail", kwargs=self.kwargs)
class LeaseDelete(LoginRequiredMixin, SuperuserRequiredMixin, DeleteView):
model = Lease
def get_success_url(self):
return reverse("dashboard.views.template-list")
def get_template_names(self):
if self.request.is_ajax():
return ['dashboard/confirm/ajax-delete.html']
else:
return ['dashboard/confirm/base-delete.html']
def delete(self, request, *args, **kwargs):
object = self.get_object()
object.delete()
success_url = self.get_success_url()
success_message = _("Lease successfully deleted!")
if request.is_ajax():
return HttpResponse(
json.dumps({'message': success_message}),
content_type="application/json",
)
else:
messages.success(request, success_message)
return HttpResponseRedirect(success_url)
@require_GET
def vm_activity(request, pk):
instance = Instance.objects.get(pk=pk)
if not instance.has_level(request.user, 'owner'):
raise PermissionDenied()
response = {}
only_state = request.GET.get("only_state")
response['state'] = instance.state
if only_state is not None and only_state == "false": # instance activity
context = {
'activities': InstanceActivity.objects.filter(
instance=instance, parent=None
).order_by('-started').select_related()
}
activities = render_to_string(
"dashboard/vm-detail/_activity-timeline.html",
RequestContext(request, context),
)
response['activities'] = activities
return HttpResponse(
json.dumps(response),
content_type="application/json"
)
class FavouriteView(TemplateView):
def post(self, *args, **kwargs):
user = self.request.user
vm = Instance.objects.get(pk=self.request.POST.get("vm"))
try:
Favourite.objects.get(instance=vm, user=user).delete()
return HttpResponse("Deleted!")
except Favourite.DoesNotExist:
Favourite(instance=vm, user=user).save()
return HttpResponse("Added!")
class TransferOwnershipView(LoginRequiredMixin, DetailView):
model = Instance
template_name = 'dashboard/vm-detail/tx-owner.html'
def post(self, request, *args, **kwargs):
try:
new_owner = search_user(request.POST['name'])
except User.DoesNotExist:
messages.error(request, _('Can not find specified user.'))
return self.get(request, *args, **kwargs)
except KeyError:
raise SuspiciousOperation()
obj = self.get_object()
if not (obj.owner == request.user or
request.user.is_superuser):
raise PermissionDenied()
token = signing.dumps((obj.pk, new_owner.pk),
salt=TransferOwnershipConfirmView.get_salt())
token_path = reverse(
'dashboard.views.vm-transfer-ownership-confirm', args=[token])
try:
new_owner.profile.notify(
_('Ownership offer'),
'dashboard/notifications/ownership-offer.html',
{'instance': obj, 'token': token_path})
except Profile.DoesNotExist:
messages.error(request, _('Can not notify selected user.'))
else:
messages.success(request,
_('User %s is notified about the offer.') % (
unicode(new_owner), ))
return redirect(reverse_lazy("dashboard.views.detail",
kwargs={'pk': obj.pk}))
class AbstractVmFunctionView(AccessMixin, View):
"""Abstract instance-action view.
User can do the action with a valid token or if has at least required_level
ACL level for the instance.
Children should at least implement/add template_name, success_message,
url_name, and do_action().
"""
token_max_age = 3 * 24 * 3600
required_level = 'owner'
success_message = _("Failed to perform requested action.")
@classmethod
def check_acl(cls, instance, user):
if not instance.has_level(user, cls.required_level):
raise PermissionDenied()
@classmethod
def get_salt(cls):
return unicode(cls)
@classmethod
def get_token(cls, instance, user, *args):
t = tuple([getattr(i, 'pk', i) for i in [instance, user] + list(args)])
return signing.dumps(t, salt=cls.get_salt())
@classmethod
def get_token_url(cls, instance, user, *args):
key = cls.get_token(instance, user, *args)
args = (instance.pk, key) + args
return reverse(cls.url_name, args=args)
# this wont work, CBVs suck: reverse(cls.as_view(), args=args)
def get_template_names(self):
return [self.template_name]
def get(self, request, pk, key=None, *args, **kwargs):
class LoginNeeded(Exception):
pass
pk = int(pk)
instance = get_object_or_404(Instance, pk=pk)
try:
if key:
logger.debug('Confirm dialog for token %s.', key)
try:
self.validate_key(pk, key)
except signing.SignatureExpired:
messages.error(request, _(
'The token has expired, please log in.'))
raise LoginNeeded()
self.key = key
else:
if not request.user.is_authenticated():
raise LoginNeeded()
self.check_acl(instance, request.user)
except LoginNeeded:
return redirect_to_login(request.get_full_path(),
self.get_login_url(),
self.get_redirect_field_name())
except SuspiciousOperation as e:
messages.error(request, _('This token is invalid.'))
logger.warning('This token %s is invalid. %s', key, unicode(e))
raise PermissionDenied()
return render(request, self.get_template_names(),
self.get_context(instance))
def post(self, request, pk, key=None, *args, **kwargs):
class LoginNeeded(Exception):
pass
pk = int(pk)
instance = get_object_or_404(Instance, pk=pk)
try:
if not request.user.is_authenticated() and key:
try:
user = self.validate_key(pk, key)
except signing.SignatureExpired:
messages.error(request, _(
'The token has expired, please log in.'))
raise LoginNeeded()
self.key = key
else:
user = request.user
self.check_acl(instance, request.user)
except LoginNeeded:
return redirect_to_login(request.get_full_path(),
self.get_login_url(),
self.get_redirect_field_name())
except SuspiciousOperation as e:
messages.error(request, _('This token is invalid.'))
logger.warning('This token %s is invalid. %s', key, unicode(e))
raise PermissionDenied()
if self.do_action(instance, user):
messages.success(request, self.success_message)
else:
messages.error(request, self.fail_message)
return HttpResponseRedirect(instance.get_absolute_url())
def validate_key(self, pk, key):
"""Get object based on signed token.
"""
try:
data = signing.loads(key, salt=self.get_salt())
logger.debug('Token data: %s', unicode(data))
instance, user = data
logger.debug('Extracted token data: instance: %s, user: %s',
unicode(instance), unicode(user))
except (signing.BadSignature, ValueError, TypeError) as e:
logger.warning('Tried invalid token. Token: %s, user: %s. %s',
key, unicode(self.request.user), unicode(e))
raise SuspiciousOperation()
try:
instance, user = signing.loads(key, max_age=self.token_max_age,
salt=self.get_salt())
logger.debug('Extracted non-expired token data: %s, %s',
unicode(instance), unicode(user))
except signing.BadSignature as e:
raise signing.SignatureExpired()
if pk != instance:
logger.debug('pk (%d) != instance (%d)', pk, instance)
raise SuspiciousOperation()
user = User.objects.get(pk=user)
return user
def do_action(self, instance, user): # noqa
raise NotImplementedError('Please override do_action(instance, user)')
def get_context(self, instance):
context = {'instance': instance}
if getattr(self, 'key', None) is not None:
context['key'] = self.key
return context
class VmRenewView(AbstractVmFunctionView):
"""User can renew an instance."""
template_name = 'dashboard/confirm/base-renew.html'
success_message = _("Virtual machine is successfully renewed.")
url_name = 'dashboard.views.vm-renew'
def get_context(self, instance):
context = super(VmRenewView, self).get_context(instance)
(context['time_of_suspend'],
context['time_of_delete']) = instance.get_renew_times()
return context
def do_action(self, instance, user):
instance.renew(user=user)
logger.info('Instance %s renewed by %s.', unicode(instance),
unicode(user))
return True
class TransferOwnershipConfirmView(LoginRequiredMixin, View):
"""User can accept an ownership offer."""
max_age = 3 * 24 * 3600
success_message = _("Ownership successfully transferred to you.")
@classmethod
def get_salt(cls):
return unicode(cls)
def get(self, request, key, *args, **kwargs):
"""Confirm ownership transfer based on token.
"""
logger.debug('Confirm dialog for token %s.', key)
try:
instance, new_owner = self.get_instance(key, request.user)
except PermissionDenied:
messages.error(request, _('This token is for an other user.'))
raise
except SuspiciousOperation:
messages.error(request, _('This token is invalid or has expired.'))
raise PermissionDenied()
return render(request,
"dashboard/confirm/base-transfer-ownership.html",
dictionary={'instance': instance, 'key': key})
def post(self, request, key, *args, **kwargs):
"""Really transfer ownership based on token.
"""
instance, owner = self.get_instance(key, request.user)
old = instance.owner
with instance_activity(code_suffix='ownership-transferred',
instance=instance, user=request.user):
instance.owner = request.user
instance.clean()
instance.save()
messages.success(request, self.success_message)
logger.info('Ownership of %s transferred from %s to %s.',
unicode(instance), unicode(old), unicode(request.user))
if old.profile:
old.profile.notify(
_('Ownership accepted'),
'dashboard/notifications/ownership-accepted.html',
{'instance': instance})
return HttpResponseRedirect(instance.get_absolute_url())
def get_instance(self, key, user):
"""Get object based on signed token.
"""
try:
instance, new_owner = (
signing.loads(key, max_age=self.max_age,
salt=self.get_salt()))
except (signing.BadSignature, ValueError, TypeError) as e:
logger.error('Tried invalid token. Token: %s, user: %s. %s',
key, unicode(user), unicode(e))
raise SuspiciousOperation()
try:
instance = Instance.objects.get(id=instance)
except Instance.DoesNotExist as e:
logger.error('Tried token to nonexistent instance %d. '
'Token: %s, user: %s. %s',
instance, key, unicode(user), unicode(e))
raise Http404()
if new_owner != user.pk:
logger.error('%s (%d) tried the token for %s. Token: %s.',
unicode(user), user.pk, new_owner, key)
raise PermissionDenied()
return (instance, new_owner)
class GraphViewBase(LoginRequiredMixin, View):
@staticmethod
def get_graphite_url():
graphite_host = getenv("GRAPHITE_HOST", None)
graphite_port = getenv("GRAPHITE_PORT", None)
if (graphite_host in ['', None] or graphite_port in ['', None]):
logger.debug('GRAPHITE_HOST is empty.')
return None
return 'http://%s:%s' % (graphite_host, graphite_port)
def get(self, request, pk, metric, time, *args, **kwargs):
graphite_url = GraphViewBase.get_graphite_url()
if graphite_url is None:
raise Http404()
if metric not in self.metrics.keys():
raise SuspiciousOperation()
try:
instance = self.get_object(request, pk)
except self.model.DoesNotExist:
raise Http404()
prefix = self.get_prefix(instance)
target = self.metrics[metric] % {'prefix': prefix}
title = self.get_title(instance, metric)
params = {'target': target,
'from': '-%s' % time,
'title': title.encode('UTF-8'),
'width': '500',
'height': '200'}
response = requests.post('%s/render/' % graphite_url, data=params)
return HttpResponse(response.content, mimetype="image/png")
def get_prefix(self, instance):
raise NotImplementedError("Subclass must implement abstract method")
def get_title(self, instance, metric):
raise NotImplementedError("Subclass must implement abstract method")
def get_object(self, request, pk):
instance = self.model.objects.get(id=pk)
if not instance.has_level(request.user, 'user'):
raise PermissionDenied()
return instance
class VmGraphView(GraphViewBase):
metrics = {
'cpu': ('cactiStyle(alias(nonNegativeDerivative(%(prefix)s.cpu.usage),'
'"cpu usage (%%)"))'),
'memory': ('cactiStyle(alias(%(prefix)s.memory.usage,'
'"memory usage (%%)"))'),
'network': (
'group('
'aliasSub(nonNegativeDerivative(%(prefix)s.network.bytes_recv*),'
' ".*-(\d+)\\)", "out (vlan \\1)"),'
'aliasSub(nonNegativeDerivative(%(prefix)s.network.bytes_sent*),'
' ".*-(\d+)\\)", "in (vlan \\1)"))'),
}
model = Instance
def get_prefix(self, instance):
return 'vm.%s' % instance.vm_name
def get_title(self, instance, metric):
return '%s (%s) - %s' % (instance.name, instance.vm_name, metric)
class NodeGraphView(SuperuserRequiredMixin, GraphViewBase):
metrics = {
'cpu': ('cactiStyle(alias(nonNegativeDerivative(%(prefix)s.cpu.times),'
'"cpu usage (%%)"))'),
'memory': ('cactiStyle(alias(%(prefix)s.memory.usage,'
'"memory usage (%%)"))'),
'network': ('cactiStyle(aliasByMetric('
'nonNegativeDerivative(%(prefix)s.network.bytes_*)))'),
}
model = Node
def get_prefix(self, instance):
return 'circle.%s' % instance.name
def get_title(self, instance, metric):
return '%s - %s' % (instance.name, metric)
def get_object(self, request, pk):
return self.model.objects.get(id=pk)
class NotificationView(LoginRequiredMixin, TemplateView):
def get_template_names(self):
if self.request.is_ajax():
return ['dashboard/_notifications-timeline.html']
else:
return ['dashboard/notifications.html']
def get_context_data(self, *args, **kwargs):
context = super(NotificationView, self).get_context_data(
*args, **kwargs)
# we need to convert it to list, otherwise it's gonna be
# similar to a QuerySet and update everything to
# read status after get
n = 10 if self.request.is_ajax() else 1000
context['notifications'] = list(
self.request.user.notification_set.values()[:n])
return context
def get(self, *args, **kwargs):
response = super(NotificationView, self).get(*args, **kwargs)
un = self.request.user.notification_set.filter(status="new")
for u in un:
u.status = "read"
u.save()
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 = {
'saml2': hasattr(settings, "SAML_CONFIG")
}
return login(request, authentication_form=authentication_form,
extra_context=extra_context)
class DiskAddView(TemplateView):
def post(self, *args, **kwargs):
is_template = self.request.POST.get("disk-is_template")
object_pk = self.request.POST.get("disk-object_pk")
is_template = int(is_template) == 1
if is_template:
obj = InstanceTemplate.objects.get(pk=object_pk)
else:
obj = Instance.objects.get(pk=object_pk)
if not obj.has_level(self.request.user, 'owner'):
raise PermissionDenied()
form = DiskAddForm(
self.request.POST,
user=self.request.user,
is_template=is_template, object_pk=object_pk,
prefix="disk"
)
if form.is_valid():
if form.cleaned_data.get("size"):
messages.success(self.request, _("Disk successfully added!"))
else:
messages.success(self.request, _("Disk download started!"))
form.save()
else:
error = "
".join(["%s: %s" %
(title_filter(i[0]), i[1][0])
for i in form.errors.items()])
messages.error(self.request, error)
if is_template:
r = obj.get_absolute_url()
else:
r = obj.get_absolute_url()
r = "%s#resources" % r
return redirect(r)