views.py 11.7 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 PermissionDenied
9 10
from django.core import signing
from django.core.urlresolvers import reverse, reverse_lazy
11
from django.http import HttpResponse, HttpResponseRedirect
12
from django.shortcuts import redirect
13
from django.views.decorators.http import require_POST
14
from django.views.generic.detail import SingleObjectMixin
15
from django.views.generic import TemplateView, DetailView, View, DeleteView
16 17
from django.contrib import messages
from django.utils.translation import ugettext as _
18

19
from django_tables2 import SingleTableView
Kálmán Viktor committed
20

21
from .tables import VmListTable
22 23
from vm.models import (Instance, InstanceTemplate, InterfaceTemplate,
                       InstanceActivity)
24
from firewall.models import Vlan
25
from storage.models import Disk
26

Őry Máté committed
27
logger = logging.getLogger(__name__)
28

29 30

class IndexView(TemplateView):
Kálmán Viktor committed
31
    template_name = "dashboard/index.html"
32

33
    def get_context_data(self, **kwargs):
34 35 36 37 38
        if self.request.user.is_authenticated():
            user = self.request.user
        else:
            user = None

39
        instances = Instance.objects.filter(owner=user)
40 41
        context = super(IndexView, self).get_context_data(**kwargs)
        context.update({
42
            'instances': instances[:5],
43
            'more_instances': instances.count() - len(instances[:5])
44 45 46
        })

        context.update({
47 48
            'running_vms': instances.filter(state='RUNNING'),
            'running_vm_num': instances.filter(state='RUNNING').count(),
49 50
            'stopped_vm_num': instances.exclude(
                state__in=['RUNNING', 'NOSTATE']).count()
51 52 53
        })
        return context

54

55 56 57 58 59 60 61 62 63 64
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])}


65 66 67 68 69 70 71 72 73 74 75 76
class CheckedDetailView(DetailView):
    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
77
    template_name = "dashboard/vm-detail.html"
78
    model = Instance
79 80

    def get_context_data(self, **kwargs):
81
        context = super(VmDetailView, self).get_context_data(**kwargs)
82 83 84
        instance = context['instance']
        if instance.node:
            port = instance.vnc_port
85
            host = str(instance.node.host.ipv4)
86
            value = signing.dumps({'host': host,
87 88
                                   'port': port},
                                  key=getenv("PROXY_SECRET", 'asdasd')),
89 90 91
            context.update({
                'vnc_url': '%s' % value
            })
92 93 94 95 96 97

        # activity data
        ia = InstanceActivity.objects.filter(
            instance=self.object, parent=None
        ).order_by('-started').select_related()
        context['activity'] = ia
98
        context['acl'] = get_acl_data(instance)
99
        return context
Kálmán Viktor committed
100 101


102
class AclUpdateView(View, SingleObjectMixin):
103

104
    def post(self, request, *args, **kwargs):
105
        instance = self.get_object()
106 107
        if not (instance.has_level(request.user, "owner") or
                getattr(instance, 'owner', None) == request.user):
Őry Máté committed
108 109
            logger.warning('Tried to set permissions of %s by non-owner %s.',
                           unicode(instance), unicode(request.user))
110
            raise PermissionDenied()
111 112 113 114 115
        self.set_levels(request, instance)
        self.add_levels(request, instance)
        return redirect(instance)

    def set_levels(self, request, instance):
116 117 118 119 120
        for key, value in request.POST.items():
            m = re.match('perm-([ug])-(\d+)', key)
            if m:
                type, id = m.groups()
                entity = {'u': User, 'g': Group}[type].objects.get(id=id)
121
                instance.set_level(entity, value)
Őry Máté committed
122 123 124
                logger.info("Set %s's acl level for %s to %s by %s.",
                            unicode(entity), unicode(instance),
                            value, unicode(request.user))
125

126
    def add_levels(self, request, instance):
127 128
        name = request.POST['perm-new-name']
        value = request.POST['perm-new']
129 130 131 132 133 134
        if not name:
            return
        try:
            entity = User.objects.get(username=name)
        except User.DoesNotExist:
            entity = None
135 136
            try:
                entity = Group.objects.get(name=name)
137 138 139 140
            except Group.DoesNotExist:
                warning(request, _('User or group "%s" not found.') % name)
                return

141
        instance.set_level(entity, value)
Őry Máté committed
142 143 144
        logger.info("Set %s's new acl level for %s to %s by %s.",
                    unicode(entity), unicode(instance),
                    value, unicode(request.user))
145

Kálmán Viktor committed
146

147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176
class TemplateDetail(DetailView):
    model = InstanceTemplate

    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:
            # return super(TemplateDetail, self).get(request, *args, **kwargs)
            return HttpResponse('soon')


177
class VmList(SingleTableView):
Kálmán Viktor committed
178
    template_name = "dashboard/vm-list.html"
179 180
    model = Instance
    table_class = VmListTable
181
    table_pagination = False
182 183 184 185 186 187


class VmCreate(TemplateView):

    def get_template_names(self):
        if self.request.is_ajax():
188
            return ['dashboard/modal-wrapper.html']
189
        else:
190
            return ['dashboard/nojs-wrapper.html']
191 192 193

    def get(self, request, *args, **kwargs):
        context = self.get_context_data(**kwargs)
194 195 196 197
        context.update({
            'template': 'dashboard/vm-create.html',
            'box_title': 'Create a VM'
        })
198
        return self.render_to_response(context)
199 200 201

    def get_context_data(self, **kwargs):
        context = super(VmCreate, self).get_context_data(**kwargs)
202
        # TODO acl
203
        context.update({
204
            'templates': InstanceTemplate.objects.all(),
205
            'vlans': Vlan.objects.all(),
206
            'disks': Disk.objects.exclude(type="qcow2-snap")
207 208 209
        })

        return context
210

211
    # TODO handle not ajax posts
212
    def post(self, request, *args, **kwargs):
213 214 215 216 217
        if self.request.user.is_authenticated():
            user = self.request.user
        else:
            user = None

218 219 220
        resp = {}
        try:
            ikwargs = {
221 222 223
                'num_cores': int(request.POST.get('cpu-count')),
                'ram_size': int(request.POST.get('ram-size')),
                'priority': int(request.POST.get('cpu-priority')),
224 225
            }

226 227 228 229 230 231 232 233 234
            networks = [{'vlan': Vlan.objects.get(pk=l), 'managed': True}
                        for l in request.POST.getlist('managed-vlans')
                        ]
            networks.extend([{'vlan': Vlan.objects.get(pk=l),
                              'managed': False}
                             for l in request.POST.getlist('unmanaged-vlans')
                             ])

            disks = Disk.objects.filter(pk__in=request.POST.getlist('disks'))
235 236
            template = InstanceTemplate.objects.get(
                pk=request.POST.get('template-pk'))
237

238
            inst = Instance.create_from_template(template=template,
239 240
                                                 owner=user, networks=networks,
                                                 disks=disks, **ikwargs)
241
            inst.deploy_async()
242

243
            resp['pk'] = inst.pk
244
            messages.success(request, _('VM successfully created!'))
245 246 247 248
        except InstanceTemplate.DoesNotExist:
            resp['error'] = True
        except:
            resp['error'] = True
249

250 251 252 253 254 255
        if request.is_ajax():
            return HttpResponse(json.dumps(resp),
                                content_type="application/json",
                                status=500 if resp.get('error') else 200)
        else:
            return redirect(reverse_lazy('dashboard.views.detail', resp))
Kálmán Viktor committed
256 257


258 259
class VmDelete(DeleteView):
    model = Instance
260 261 262 263 264 265 266
    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']
267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284

    def get_context_data(self, **kwargs):
        # 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()
        success_url = self.get_success_url()
        success_message = _("VM successfully deleted!")

        if request.is_ajax():
285 286
            if request.POST.get('redirect').lower() == "true":
                messages.success(request, success_message)
287 288 289 290 291 292 293 294 295 296 297 298 299 300
            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')
301 302


303
@require_POST
304 305 306 307 308
def mass_delete_vm(request, **kwargs):
    vms = request.POST.getlist('vms')
    names = []
    if vms is not None:
        for i in Instance.objects.filter(pk__in=vms):
309 310 311 312 313 314
            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.
315 316 317 318
            i.destroy_async()
            names.append(i.name)

    success_message = _("Mass delete complete, the following VMs were " +
319
                        "deleted: %s!" % u', '.join(names))
320 321 322 323 324 325 326 327

    # we can get this only via AJAX ...
    if request.is_ajax():
        return HttpResponse(
            json.dumps({'message': success_message}),
            content_type="application/json"
        )
    else:
328 329 330
        messages.success(request, success_message)
        next = request.GET.get('next')
        return redirect(next if next else reverse_lazy('dashboard.index'))