views.py 33 KB
Newer Older
1 2
from os import getenv
import json
Őry Máté committed
3
import logging
4 5 6
import re

from django.contrib.auth.models import User, Group
7
from django.contrib.messages import warning
8
from django.core.exceptions import (
9
    PermissionDenied, SuspiciousOperation,
10
)
11 12
from django.core import signing
from django.core.urlresolvers import reverse, reverse_lazy
13
from django.http import HttpResponse, HttpResponseRedirect, Http404
14
from django.shortcuts import redirect, render
15
from django.views.decorators.http import require_POST
16
from django.views.generic.detail import SingleObjectMixin
17
from django.views.generic import (TemplateView, DetailView, View, DeleteView,
18
                                  UpdateView, CreateView)
19 20
from django.contrib import messages
from django.utils.translation import ugettext as _
21

22
from django.forms.models import inlineformset_factory
23
from django_tables2 import SingleTableView
24
from braces.views import LoginRequiredMixin, SuperuserRequiredMixin
Kálmán Viktor committed
25

26
from .forms import VmCreateForm, TemplateForm, LeaseForm, NodeForm, HostForm
27 28
from .tables import (VmListTable, NodeListTable, NodeVmListTable,
                     TemplateListTable, LeaseListTable)
29
from vm.models import (Instance, InstanceTemplate, InterfaceTemplate,
30
                       InstanceActivity, Node, instance_activity, Lease)
31
from firewall.models import Vlan, Host, Rule
32
from storage.models import Disk
33

Őry Máté committed
34
logger = logging.getLogger(__name__)
35

36

37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
# 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


55
class IndexView(LoginRequiredMixin, TemplateView):
Kálmán Viktor committed
56
    template_name = "dashboard/index.html"
57

58
    def get_context_data(self, **kwargs):
59 60 61 62 63
        if self.request.user.is_authenticated():
            user = self.request.user
        else:
            user = None

64 65 66
        instances = Instance.get_objects_with_level(
            'user', user).filter(destroyed=None)

67 68
        context = super(IndexView, self).get_context_data(**kwargs)
        context.update({
69
            'instances': instances[:5],
70
            'more_instances': instances.count() - len(instances[:5])
71 72
        })

73 74
        nodes = Node.objects.all()
        context.update({
75 76 77 78 79 80 81 82 83
            'nodes': nodes[:10],
            'more_nodes': nodes.count() - len(nodes[: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)
            }
84 85
        })

86
        context.update({
87 88
            'running_vms': instances.filter(state='RUNNING'),
            'running_vm_num': instances.filter(state='RUNNING').count(),
89 90
            'stopped_vm_num': instances.exclude(
                state__in=['RUNNING', 'NOSTATE']).count()
91 92 93
        })
        return context

94

95 96 97 98 99 100 101 102 103 104
def get_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])}


105
class CheckedDetailView(LoginRequiredMixin, DetailView):
106 107 108 109 110 111 112 113 114 115 116
    read_level = 'user'

    def get_context_data(self, **kwargs):
        context = super(CheckedDetailView, self).get_context_data(**kwargs)
        instance = context['instance']
        if not instance.has_level(self.request.user, self.read_level):
            raise PermissionDenied()
        return context


class VmDetailView(CheckedDetailView):
Kálmán Viktor committed
117
    template_name = "dashboard/vm-detail.html"
118
    model = Instance
119 120

    def get_context_data(self, **kwargs):
121
        context = super(VmDetailView, self).get_context_data(**kwargs)
122 123 124
        instance = context['instance']
        if instance.node:
            port = instance.vnc_port
125
            host = str(instance.node.host.ipv4)
126
            value = signing.dumps({'host': host,
127 128
                                   'port': port},
                                  key=getenv("PROXY_SECRET", 'asdasd')),
129 130 131
            context.update({
                'vnc_url': '%s' % value
            })
132 133 134 135 136

        # activity data
        ia = InstanceActivity.objects.filter(
            instance=self.object, parent=None
        ).order_by('-started').select_related()
137

138
        context['activity'] = ia
139
        context['acl'] = get_acl_data(instance)
140
        return context
Kálmán Viktor committed
141

142 143 144 145 146
    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)

147 148 149
        if request.POST.get('new_name'):
            return self.__set_name(request)

Kálmán Viktor committed
150 151 152 153 154 155
        if request.POST.get('new_tag') is not None:
            return self.__add_tag(request)

        if request.POST.get("to_remove") is not None:
            return self.__remove_tag(request)

156 157 158
        if request.POST.get("port") is not None:
            return self.__add_port(request)

159 160 161 162
    def __set_resources(self, request):
        self.object = self.get_object()
        if not self.object.has_level(request.user, 'owner'):
            raise PermissionDenied()
163 164
        if not request.user.has_perm('vm.change_resources'):
            raise PermissionDenied()
165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184

        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}))

185 186
    def __set_name(self, request):
        self.object = self.get_object()
187 188
        if not self.object.has_level(request.user, 'owner'):
            raise PermissionDenied()
189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208
        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}))

Kálmán Viktor committed
209 210 211
    def __add_tag(self, request):
        new_tag = request.POST.get('new_tag')
        self.object = self.get_object()
212 213
        if not self.object.has_level(request.user, 'owner'):
            raise PermissionDenied()
Kálmán Viktor committed
214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233

        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()
234 235
            if not self.object.has_level(request.user, 'owner'):
                raise PermissionDenied()
Kálmán Viktor committed
236 237 238 239 240 241 242 243 244 245 246 247

            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"
            )

248 249
    def __add_port(self, request):
        object = self.get_object()
250 251
        if (not object.has_level(request.user, 'owner') or
                not request.user.has_perm('vm.config_ports')):
252
            raise PermissionDenied()
253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273

        port = request.POST.get("port")
        proto = request.POST.get("proto")

        try:
            error = None
            host = Host.objects.get(pk=request.POST.get("host_pk"))
            host.add_port(proto, private=port)
        except Host.DoesNotExist:
            error = _("Host not found!")
        except Exception, e:
            error = u', '.join(e.messages)

        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}))

Kálmán Viktor committed
274

275
class NodeDetailView(LoginRequiredMixin, SuperuserRequiredMixin, DetailView):
276 277 278 279 280
    template_name = "dashboard/node-detail.html"
    model = Node

    def get_context_data(self, **kwargs):
        context = super(NodeDetailView, self).get_context_data(**kwargs)
281 282
        instances = Instance.active.filter(node=self.object)
        context['table'] = NodeVmListTable(instances)
283 284
        return context

285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311
    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")
        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}))


312
class AclUpdateView(LoginRequiredMixin, View, SingleObjectMixin):
313

314
    def post(self, request, *args, **kwargs):
315
        instance = self.get_object()
316 317
        if not (instance.has_level(request.user, "owner") or
                getattr(instance, 'owner', None) == request.user):
Őry Máté committed
318 319
            logger.warning('Tried to set permissions of %s by non-owner %s.',
                           unicode(instance), unicode(request.user))
320
            raise PermissionDenied()
321 322 323 324 325
        self.set_levels(request, instance)
        self.add_levels(request, instance)
        return redirect(instance)

    def set_levels(self, request, instance):
326 327 328
        for key, value in request.POST.items():
            m = re.match('perm-([ug])-(\d+)', key)
            if m:
329 330 331 332 333 334
                typ, id = m.groups()
                entity = {'u': User, 'g': Group}[typ].objects.get(id=id)
                if instance.owner == entity:
                    logger.info("Tried to set owner's acl level for %s by %s.",
                                unicode(instance), unicode(request.user))
                    continue
335
                instance.set_level(entity, value)
Őry Máté committed
336 337 338
                logger.info("Set %s's acl level for %s to %s by %s.",
                            unicode(entity), unicode(instance),
                            value, unicode(request.user))
339

340
    def add_levels(self, request, instance):
341 342
        name = request.POST['perm-new-name']
        value = request.POST['perm-new']
343 344 345 346 347 348
        if not name:
            return
        try:
            entity = User.objects.get(username=name)
        except User.DoesNotExist:
            entity = None
349 350
            try:
                entity = Group.objects.get(name=name)
351 352 353 354
            except Group.DoesNotExist:
                warning(request, _('User or group "%s" not found.') % name)
                return

355
        instance.set_level(entity, value)
Őry Máté committed
356 357 358
        logger.info("Set %s's new acl level for %s to %s by %s.",
                    unicode(entity), unicode(instance),
                    value, unicode(request.user))
359

Kálmán Viktor committed
360

361 362 363 364 365 366
class TemplateCreate(SuccessMessageMixin, CreateView):
    model = InstanceTemplate
    form_class = TemplateForm
    template_name = "dashboard/template-create.html"
    success_message = _("Successfully created a new template!")

367 368 369 370 371 372 373 374 375
    def get(self, *args, **kwargs):
        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)
        return kwargs

376 377 378 379
    def get_success_url(self):
        return reverse_lazy("dashboard.views.template-list")


380
class TemplateDetail(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
381
    model = InstanceTemplate
382 383 384
    template_name = "dashboard/template-edit.html"
    form_class = TemplateForm
    success_message = _("Successfully modified template!")
385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408

    def get(self, request, *args, **kwargs):
        if request.is_ajax():
            template = InstanceTemplate.objects.get(pk=kwargs['pk'])
            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:
409 410 411 412 413 414 415
            return super(TemplateDetail, self).get(request, *args, **kwargs)

    def get_success_url(self):
        return reverse_lazy("dashboard.views.template-detail",
                            kwargs=self.kwargs)


416
class TemplateList(LoginRequiredMixin, SingleTableView):
417 418 419 420 421 422 423 424 425
    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
426 427


428
class VmList(LoginRequiredMixin, SingleTableView):
Kálmán Viktor committed
429
    template_name = "dashboard/vm-list.html"
430
    table_class = VmListTable
431
    table_pagination = False
432 433 434
    model = Instance

    def get_queryset(self):
435
        logger.debug('VmList.get_queryset() called. User: %s',
436 437 438
                     unicode(self.request.user))
        return Instance.get_objects_with_level(
            'user', self.request.user).filter(destroyed=None).all()
439

440

441
class NodeList(LoginRequiredMixin, SuperuserRequiredMixin, SingleTableView):
442 443 444 445
    template_name = "dashboard/node-list.html"
    model = Node
    table_class = NodeListTable
    table_pagination = False
446

Őry Máté committed
447

448
class VmCreate(LoginRequiredMixin, TemplateView):
449

450 451 452
    form_class = VmCreateForm
    form = None

453 454
    def get_template_names(self):
        if self.request.is_ajax():
455
            return ['dashboard/modal-wrapper.html']
456
        else:
457
            return ['dashboard/nojs-wrapper.html']
458

459 460 461
    def get(self, request, form=None, *args, **kwargs):
        if form is None:
            form = self.form_class()
462
        context = self.get_context_data(**kwargs)
463 464
        context.update({
            'template': 'dashboard/vm-create.html',
465 466 467 468 469
            'box_title': 'Create a VM',
            'templates': InstanceTemplate.objects.all(),
            'vlans': Vlan.objects.all(),
            'disks': Disk.objects.exclude(type="qcow2-snap"),
            'vm_create_form': form,
470
        })
471
        return self.render_to_response(context)
472 473 474

    def get_context_data(self, **kwargs):
        context = super(VmCreate, self).get_context_data(**kwargs)
475
        # TODO acl
476 477 478 479
        context.update({
        })

        return context
480

481
    # TODO handle not ajax posts
482
    def post(self, request, *args, **kwargs):
483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498
        form = self.form_class(request.POST)
        if not form.is_valid():
            return self.get(request, form, *args, **kwargs)
        post = form.cleaned_data
        user = request.user

        template = post['template']
        if request.user.has_perm('vm.set_resources'):
            ikwargs = {
                'num_cores': post['cpu_count'],
                'ram_size': post['ram_size'],
                'priority': post['cpu_priority'],
            }

            networks = (
                [InterfaceTemplate(vlan=l, managed=True)
499
                    for l in post['managed_networks']] +
500 501 502 503 504 505
                [InterfaceTemplate(vlan=l, managed=False)
                    for l in post['unmanaged_networks']])
            disks = post['disks']
            inst = Instance.create_from_template(
                template=template, owner=user, networks=networks,
                disks=disks, **ikwargs)
506
        else:
507 508 509 510 511
            inst = Instance.create_from_template(
                template=template, owner=user)
        inst.deploy_async(user=request.user)
        messages.success(request, _('VM successfully created!'))
        path = inst.get_absolute_url()
512
        if request.is_ajax():
513 514
            return HttpResponse(json.dumps({'redirect': path}),
                                content_type="application/json")
515
        else:
516
            return redirect(path)
Kálmán Viktor committed
517

518

519
class NodeCreate(LoginRequiredMixin, SuperuserRequiredMixin, TemplateView):
520

521 522 523 524 525 526
    form_class = HostForm
    hostform = None

    formset_class = inlineformset_factory(Host, Node, form=NodeForm, extra=1)
    formset = None

527 528 529 530 531 532
    def get_template_names(self):
        if self.request.is_ajax():
            return ['dashboard/modal-wrapper.html']
        else:
            return ['dashboard/nojs-wrapper.html']

533 534 535 536 537
    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())
538 539 540
        context = self.get_context_data(**kwargs)
        context.update({
            'template': 'dashboard/node-create.html',
541 542 543 544
            'box_title': 'Create a Node',
            'hostform': hostform,
            'formset': formset,

545 546 547 548 549 550 551 552 553 554 555 556 557
        })
        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):
558 559
        if not self.request.user.is_authenticated():
            raise PermissionDenied()
560

561 562 563 564 565 566 567 568 569 570 571 572 573 574
        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()
575
        if request.is_ajax():
576 577
            return HttpResponse(json.dumps({'redirect': path}),
                                content_type="application/json")
578
        else:
579
            return redirect(path)
580 581


582
class VmDelete(LoginRequiredMixin, DeleteView):
583
    model = Instance
584 585 586 587 588 589 590
    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']
591 592 593 594 595 596 597

    def get_success_url(self):
        next = self.request.POST.get('next')
        if next:
            return next
        else:
            return reverse_lazy('dashboard.index')
598

599
    def get_context_data(self, **kwargs):
600 601 602
        object = self.get_object()
        if not object.has_level(self.request.user, 'owner'):
            raise PermissionDenied()
603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629
        # 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)


630
class NodeDelete(LoginRequiredMixin, SuperuserRequiredMixin, DeleteView):
631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673

    """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')
674

675

676
class PortDelete(LoginRequiredMixin, DeleteView):
677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721
    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")})


722
class VmMassDelete(LoginRequiredMixin, View):
723
    def get(self, request, *args, **kwargs):
724 725 726 727 728
        vms = request.GET.getlist('v[]')
        objects = Instance.objects.filter(pk__in=vms)
        return render(request, "dashboard/confirm/mass-delete.html",
                      {'objects': objects})

729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756
    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'))
757 758


759 760 761 762 763 764 765 766 767 768
class LeaseCreate(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")


769
class LeaseDetail(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
770 771 772 773 774 775 776 777 778
    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)


779 780
@require_POST
def vm_activity(request, pk):
781
    object = Instance.objects.get(pk=pk)
782 783 784
    if not object.has_level(request.user, 'owner'):
        raise PermissionDenied()

785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817
    latest = request.POST.get('latest')
    latest_sub = request.POST.get('latest_sub')

    instance = Instance.objects.get(pk=pk)
    new_sub_activities = InstanceActivity.objects.filter(
        parent=latest, pk__gt=latest_sub,
        instance=instance)
    # new_activities = InstanceActivity.objects.filter(
    #     parent=None, instance=instance, pk__gt=latest).values('finished')
    latest_sub_finished = InstanceActivity.objects.get(pk=latest_sub).finished

    time_string = "%H:%M:%S"
    new_sub_activities = [
        {'name': a.get_readable_name(), 'id': a.pk,
         'finished': None if a.finished is None else a.finished.strftime(
             time_string
         )
         } for a in new_sub_activities
    ]

    response = {
        'new_sub_activities': new_sub_activities,
        # TODO 'new_acitivites': new_activities,
        'is_parent_finished': True if InstanceActivity.objects.get(
            pk=latest).finished is not None else False,
        'latest_sub_finished': None if latest_sub_finished is None else
        latest_sub_finished.strftime(time_string)
    }

    return HttpResponse(
        json.dumps(response),
        content_type="application/json"
    )
818 819


820 821
class TransferOwnershipView(LoginRequiredMixin, DetailView):
    model = Instance
822
    template_name = 'dashboard/vm-detail/tx-owner.html'
823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843

    def post(self, request, *args, **kwargs):
        try:
            new_owner = User.objects.get(username=request.POST['name'])
        except User.DoesNotExist:
            raise Http404()
        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())
        return HttpResponse("%s?key=%s" % (
            reverse('dashboard.views.vm-transfer-ownership-confirm'), token),
            content_type="text/plain")


844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925
class TransferOwnershipConfirmView(LoginRequiredMixin, View):
    max_age = 3 * 24 * 3600
    success_message = _("Ownership successfully transferred.")

    @classmethod
    def get_salt(cls):
        return unicode(cls)

    def get(self, request, *args, **kwargs):
        """Confirm ownership transfer based on token.
        """
        try:
            key = request.GET['key']
            logger.debug('Confirm dialog for token %s.', key)
            instance, new_owner = self.get_instance(key, request.user)
        except KeyError:
            raise Http404()
        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, *args, **kwargs):
        """Really transfer ownership based on token.
        """
        try:
            key = request.POST['key']
            instance, owner = self.get_instance(key, request.user)
        except KeyError:
            logger.debug('Posted to %s without key field.',
                         unicode(self.__class__))
            raise SuspiciousOperation()

        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))
        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 as e:
            logger.error('Tried invalid token. Token: %s, user: %s. %s',
                         key, unicode(user), unicode(e))
            raise SuspiciousOperation()
        except ValueError as e:
            logger.error('Tried invalid token. Token: %s, user: %s. %s',
                         key, unicode(user), unicode(e))
            raise SuspiciousOperation()
        except 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)