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 = "<br /> ".join(["<strong>%s</strong>: %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)