views.py 21.1 KB
Newer Older
1
# -*- coding: utf-8 -*-
Dudás Ádám committed
2 3
from datetime import datetime
from django.conf import settings
Őry Máté committed
4
from datetime import timedelta as td
Dudás Ádám committed
5 6 7 8
from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User
from django.contrib import messages
from django.core.exceptions import PermissionDenied
9
from django.core import signing, urlresolvers
Dudás Ádám committed
10 11 12 13 14 15 16 17 18 19 20 21
from django.core.mail import mail_managers, send_mail
from django.db import transaction
from django.forms import ModelForm, Textarea
from django.http import Http404
from django.shortcuts import render, render_to_response, get_object_or_404, redirect
from django.template import RequestContext
from django.template.loader import render_to_string
from django.utils.decorators import method_decorator
from django.utils.translation import get_language as lang
from django.utils.translation import ugettext_lazy as _
from django.views.decorators.http import *
from django.views.generic import *
22
from firewall.tasks import *
23
from cloud.settings import store_settings
Dudás Ádám committed
24
from one.models import *
Dányi Bence committed
25
from school.models import *
Dudás Ádám committed
26
import django.contrib.auth as auth
27
import json
28 29 30
import logging

logger = logging.getLogger(__name__)
Dudás Ádám committed
31 32 33 34 35

def _list_instances(request):
    instances = Instance.objects.exclude(state='DONE').filter(owner=request.user)
    for i in instances:
        i.update_state()
36
    instances = instances.exclude(state='DONE')
Dudás Ádám committed
37 38 39 40 41
    return instances

@require_GET
@login_required
def home(request):
42
    instances = _list_instances(request)
43 44 45 46
    shares = [s for s in request.user.person_set.all()[0].get_shares()]
    for i, s in enumerate(shares):
        s.running_shared = s.instance_set.all().exclude(state="DONE").filter(owner=request.user).count()
        shares[i] = s
47 48 49 50 51
    try:
        details = UserCloudDetails.objects.get(user=request.user)
    except UserCloudDetails.DoesNotExist:
        details = UserCloudDetails(user=request.user)
        details.save()
x committed
52 53 54 55
    try:
        generated_public_key = details.ssh_key.id
    except:
        generated_public_key = -1
56
    return render_to_response("home.html", RequestContext(request, {
57
        'instances': instances,
58
        'shares': shares,
59 60
        'templates': Template.objects.filter(state='READY'),
        'mytemplates': Template.objects.filter(owner=request.user),
Dányi Bence committed
61
        'groups': request.user.person_set.all()[0].owned_groups.all(),
62
        'semesters': Semester.objects.all(),
63
        'userdetails': details,
x committed
64
        'keys': request.user.sshkey_set.exclude(id=generated_public_key).all(),
65
        'storeserv': store_settings['store_public'],
Dudás Ádám committed
66 67
        }))

68 69 70 71 72 73 74
@login_required
def ajax_template_delete(request):
    try:
        template_id = request.POST['id']
    except:
        return HttpResponse(unicode(_("Invalid template ID.")), status=404)
    template = get_object_or_404(Template, id=template_id)
75
    if template.running_instances() > 0:
76 77 78
        return HttpResponse(unicode(_("There are running instances of this template.")), status=404)
    elif template.share_set.exists():
        return HttpResponse(unicode(_("Template is still shared.")), status=404)
79 80
    elif template.owner != request.user:
        return HttpResponse(unicode(_("You don't have permission to delete this template.")), status=404)
81
    else:
82 83 84 85
        if template.safe_delete():
            return HttpResponse(unicode(_("Template successfully deleted.")))
        else:
            return HttpResponse(unicode(_("Unexpected error happened.")), status=404)
86

87 88 89 90 91 92
def ajax_template_name_unique(request, name):
    s = "True"
    if Template.objects.filter(name=name).exists():
        s = "False"
    return HttpResponse(s)

93 94
@login_required
def vm_credentials(request, iid):
95 96
    try:
        vm = get_object_or_404(Instance, pk=iid, owner=request.user)
97 98 99
        proto = len(request.META["REMOTE_ADDR"].split('.')) == 1
        vm.hostname = vm.get_connect_host(use_ipv6=proto)
        vm.port = vm.get_port(use_ipv6=proto)
100
        return render_to_response('vm-credentials.html', RequestContext(request, { 'i' : vm }))
101
    except:
102
        return HttpResponse(_("Could not get Virtual Machine credentials."), status=404)
103
        messages.error(request, _('Failed to power off virtual machine.'))
104

105 106
class AjaxTemplateWizard(View):
    def get(self, request, *args, **kwargs):
107 108 109
        return render_to_response('new-template-flow-1.html', RequestContext(request, {
            'templates': [t for t in Template.objects.filter(public=True).all()] +
                         [t for t in Template.objects.filter(owner=request.user).all()],
110 111 112 113 114
            }))
    def post(self, request, *args, **kwargs):
        base = get_object_or_404(Template, id=request.POST['base'])
        if base.owner != request.user and not base.public and not request.user.is_superuser:
            raise PermissionDenied()
115 116 117 118
        try:
            maxshare = Template.objects.order_by('-pk')[0].pk + 1
        except:
            maxshare = 1
119 120 121
        return render_to_response('new-template-flow.html', RequestContext(request, {
            'sizes': InstanceType.objects.all(),
            'base': base,
122
            'maxshare': maxshare,
123 124 125
            }))
ajax_template_wizard = login_required(AjaxTemplateWizard.as_view())

Őry Máté committed
126 127 128

class AjaxShareWizard(View):
    def get(self, request, id, gid=None, *args, **kwargs):
Őry Máté committed
129 130 131
        det = UserCloudDetails.objects.get(user=request.user)
        if det.get_weighted_share_count() >= det.share_quota:
            return HttpResponse(unicode(_('You do not have any free share quota.')))
Őry Máté committed
132 133 134 135 136 137 138 139 140
        types = TYPES_L
        types[0]['default'] = True
        for i, t in enumerate(types):
            t['deletex'] = datetime.now() + td(seconds=1) + t['delete'] if t['delete'] else None
            t['suspendx'] = datetime.now() + td(seconds=1) + t['suspend'] if t['suspend'] else None
            types[i] = t
        if gid:
            gid = get_object_or_404(Group, id=gid)

Őry Máté committed
141
        return render_to_response('new-share.html', RequestContext(request, {
Őry Máté committed
142 143 144 145 146 147
            'base': get_object_or_404(Template, id=id),
            'groups': request.user.person_set.all()[0].owned_groups.all(),
            'types': types,
            'group': gid,
            }))
    def post(self, request, id, gid=None, *args, **kwargs):
Őry Máté committed
148
        det = UserCloudDetails.objects.get(user=request.user)
Őry Máté committed
149 150 151 152 153 154 155 156 157 158 159 160 161 162 163
        base = get_object_or_404(Template, id=id)
        if base.owner != request.user and not base.public and not request.user.is_superuser:
            raise PermissionDenied()
        group = None
        if gid:
            group = get_object_or_404(Group, id=gid)
        else:
            group = get_object_or_404(Group, id=request.POST['group'])

        if not group.owners.filter(user=request.user).exists():
            raise PermissionDenied()
        stype = request.POST['type']
        if not stype in TYPES.keys():
            raise PermissionDenied()
        il = request.POST['instance_limit']
Őry Máté committed
164 165 166
        if det.get_weighted_share_count() + int(il)*base.instance_type.credit > det.share_quota:
            messages.error(request, _('You do not have enough free share quota.'))
            return redirect('/')
Őry Máté committed
167 168
        s = Share.objects.create(name=request.POST['name'], description=request.POST['description'],
                type=stype, instance_limit=il, per_user_limit=request.POST['per_user_limit'],
Őry Máté committed
169
                group=group, template=base, owner=request.user)
Őry Máté committed
170
        messages.success(request, _('Successfully shared %s.') % base)
Őry Máté committed
171
        return redirect(group)
Őry Máté committed
172 173
ajax_share_wizard = login_required(AjaxShareWizard.as_view())

174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210
class AjaxShareEditWizard(View):
    def get(self, request, id, *args, **kwargs):
        det = UserCloudDetails.objects.get(user=request.user)
        if det.get_weighted_share_count() >= det.share_quota:
            return HttpResponse(unicode(_('You do not have any free share quota.')))
        types = TYPES_L
        for i, t in enumerate(types):
            t['deletex'] = datetime.now() + td(seconds=1) + t['delete'] if t['delete'] else None
            t['suspendx'] = datetime.now() + td(seconds=1) + t['suspend'] if t['suspend'] else None
            types[i] = t
        share = get_object_or_404(Share, id=id)
        return render_to_response('edit-share.html', RequestContext(request, {
            'share': share,
            'types': types,
            }))
    def post(self, request, id, *args, **kwargs):
        det = UserCloudDetails.objects.get(user=request.user)
        share = get_object_or_404(Share, id=id)
        if share.owner != request.user and not request.user.is_superuser:
            raise PermissionDenied()
        stype = request.POST['type']
        if not stype in TYPES.keys():
            raise PermissionDenied()
        il = request.POST['instance_limit']
        if det.get_weighted_share_count() + int(il)*share.template.instance_type.credit > det.share_quota:
            messages.error(request, _('You do not have enough free share quota.'))
            return redirect('/')
        share.name=request.POST['name']
        share.description=request.POST['description']
        share.type=stype
        share.instance_limit=il
        share.per_user_limit=request.POST['per_user_limit']
        share.owner=request.user
        share.save()
        messages.success(request, _('Successfully edited share %s.') % share)
        return redirect(share.group)
ajax_share_edit_wizard = login_required(AjaxShareEditWizard.as_view())
Őry Máté committed
211 212


213
@require_POST
Dányi Bence committed
214
@login_required
215 216 217 218 219 220 221 222
def vm_saveas(request, vmid):
    inst = get_object_or_404(Instance, pk=vmid)
    if inst.owner != request.user and not request.user.is_superuser:
        raise PermissionDenied()
    inst.save_as()
    messages.success(request, _("Template is being saved..."))
    return redirect(inst)

223
def vm_new_ajax(request, template):
224
    return vm_new(request, template, redir=False)
Dányi Bence committed
225

226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264
def _redirect_or_201(path, redir):
    if redir:
        return redirect(path)
    else:
        response = HttpResponse("Created", status=201)
        response['Location'] = path
        return response

def _template_for_save(base, request):
    if base.owner != request.user and not base.public and not request.user.is_superuser:
        raise PermissionDenied()
    name = request.POST['name']
    t = Template.objects.create(name=name, disk=base.disk, instance_type_id=request.POST['size'], network=base.network, owner=request.user)
    t.access_type = base.access_type
    t.description = request.POST['description']
    t.system = base.system
    t.save()
    return t

def _check_quota(request, template, share):
    """
    Returns if the given request is permitted to run the new vm.
    """
    det = UserCloudDetails.objects.get(user=request.user)
    if det.get_weighted_instance_count() + template.instance_type.credit >= det.instance_quota:
        messages.error(request, _('You do not have any free quota. You can not launch this until you stop an other instance.'))
        return False
    if share:
        if share.get_running() + 1 > share.instance_limit:
            messages.error(request, _('The share does not have any free quota. You can not launch this until someone stops an instance.'))
            return False
        elif share.get_running_or_stopped(request.user) + 1 > share.per_user_limit:
            messages.error(request, _('You do not have any free quota for this share. You can not launch this until you stop an other instance.'))
            return False
        if not share.group.members.filter(user=request.user) and not share.group.owners.filter(user=request.user):
            messages.error(request, _('You are not a member of the share group.'))
            return False
    return True

Dudás Ádám committed
265 266
@require_POST
@login_required
267
def vm_new(request, template=None, share=None, redir=True):
268 269
    base = None
    extra = None
tarokkk committed
270 271 272 273 274
    if template:
        base = get_object_or_404(Template, pk=template)
    else:
        share = get_object_or_404(Share, pk=share)
        base = share.template
275

276
    go = True
277
    if "name" in request.POST:
278 279 280 281 282 283 284
        try:
            base = _template_for_save(base, request)
            extra = "<RECONTEXT>YES</RECONTEXT>"
        except:
            messages.error(request, _('Can not create template.'))
            go = False
    go = go and _check_quota(request, base, share)
285 286 287 288 289 290 291

    if not share and not base.public and base.owner != request.user:
        messages.error(request, _('You have no permission to try this instance without a share. Launch a new instance through a share.'))
        go = False
    type = share.type if share else 'LAB'
    TYPES[type]['suspend']
    time_of_suspend = TYPES[type]['suspend']+datetime.now()
tarokkk committed
292 293 294 295
    if TYPES[type]['delete']:
        time_of_delete = TYPES[type]['delete']+datetime.now()
    else:
        time_of_delete = None
296 297 298 299 300 301 302 303 304 305 306 307
    inst = None
    if go:
        try:
            inst = Instance.submit(base, request.user, extra=extra, share=share)
        except Exception as e:
            logger.error('Failed to create virtual machine.' + unicode(e))
            messages.error(request, _('Failed to create virtual machine.'))
            inst = None
        if inst:
            inst.time_of_suspend = time_of_suspend
            inst.time_of_delete = time_of_delete
            inst.save()
308 309
    elif extra and base:
        base.delete()
310
    return _redirect_or_201(inst.get_absolute_url() if inst else '/', redir)
Dudás Ádám committed
311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326

class VmListView(ListView):
    context_object_name = 'instances'
    template_name = 'list.html'

    def get_queryset(self):
        self.profile = request.user
        return Instance.objects.filter(owner=self.profile)

vm_list = login_required(VmListView.as_view())

@require_safe
@login_required
def vm_show(request, iid):
    inst = get_object_or_404(Instance, id=iid, owner=request.user)
    inst.update_state()
327 328
    if inst.template.state == "SAVING":
        inst.check_if_is_save_as_done()
329 330 331 332
    try:
        ports = inst.firewall_host.list_ports()
    except:
        ports = None
333 334 335 336 337
    try:
        details = UserCloudDetails.objects.get(user=request.user)
    except UserCloudDetails.DoesNotExist:
        details = UserCloudDetails(user=request.user)
        details.save()
338 339 340
    proto = len(request.META["REMOTE_ADDR"].split('.')) == 1
    inst.hostname = inst.get_connect_host(use_ipv6=proto)
    inst.port = inst.get_port(use_ipv6=proto)
Dudás Ádám committed
341
    return render_to_response("show.html", RequestContext(request,{
342 343 344
        'uri': inst.get_connect_uri(),
        'state': inst.state,
        'name': inst.name,
tarokkk committed
345
        'id': int(iid),
Dudás Ádám committed
346 347
        'age': inst.get_age(),
        'instances': _list_instances(request),
Őry Máté committed
348
        'i': inst,
349
        'booting' : not inst.active_since,
350
        'ports': ports,
351
        'userdetails': details
Dudás Ádám committed
352 353
        }))

354 355 356 357 358 359 360
@require_safe
@login_required
def vm_ajax_instance_status(request, iid):
    inst = get_object_or_404(Instance, id=iid, owner=request.user)
    inst.update_state()
    return HttpResponse(json.dumps({'booting': not inst.active_since, 'state': inst.state}))

361 362 363 364 365 366 367
@login_required
def vm_ajax_rename(request, iid):
    inst = get_object_or_404(Instance, id=iid, owner=request.user)
    inst.name = request.POST['name']
    inst.save()
    return HttpResponse(json.dumps({'name': inst.name}))

368 369 370 371 372 373 374 375 376 377 378 379 380
def boot_token(request, token):
    try:
        id = signing.loads(token, salt='activate')
    except:
        return HttpResponse("Invalid token.")
    inst = get_object_or_404(Instance, id=id)
    if inst.active_since:
        return HttpResponse("Already booted?")
    else:
        inst.active_since = datetime.now()
        inst.save()
        return HttpResponse("KTHXBYE")

Őry Máté committed
381 382 383 384
class VmPortAddView(View):
    def post(self, request, iid, *args, **kwargs):
        try:
            public = int(request.POST['public'])
Dányi Bence committed
385

Őry Máté committed
386
            if public >= 22000 and public < 24000:
387
                raise ValidationError(_("Port number is in a restricted domain (22000 to 24000)."))
Őry Máté committed
388
            inst = get_object_or_404(Instance, id=iid, owner=request.user)
389 390 391 392 393
            if inst.template.network.nat:
                private = private=int(request.POST['private'])
            else:
                private = 0
            inst.firewall_host.add_port(proto=request.POST['proto'], public=public, private=private)
394
            messages.success(request, _(u"Port %d successfully added.") % public)
Őry Máté committed
395
        except:
396
            messages.error(request, _(u"Adding port failed."))
Őry Máté committed
397 398 399 400 401 402 403 404 405 406 407 408 409 410 411
#            raise
        return redirect('/vm/show/%d/' % int(iid))

    def get(self, request, iid, *args, **kwargs):
        return redirect('/')

vm_port_add = login_required(VmPortAddView.as_view())

@require_safe
@login_required
@require_GET
def vm_port_del(request, iid, proto, public):
    inst = get_object_or_404(Instance, id=iid, owner=request.user)
    try:
        inst.firewall_host.del_port(proto=proto, public=public)
412
        messages.success(request, _(u"Port %s successfully removed.") % public)
Őry Máté committed
413
    except:
414
        messages.error(request, _(u"Removing port failed."))
Őry Máté committed
415 416
    return redirect('/vm/show/%d/' % int(iid))

Dudás Ádám committed
417 418 419
class VmDeleteView(View):
    def post(self, request, iid, *args, **kwargs):
        try:
420 421 422 423
            inst = get_object_or_404(Instance, id=iid, owner=request.user)
            if inst.template.state != 'READY' and inst.template.owner == request.user:
                inst.template.delete()
            inst.delete()
Dudás Ádám committed
424 425 426
            messages.success(request, _('Virtual machine is successfully deleted.'))
        except:
            messages.error(request, _('Failed to delete virtual machine.'))
427 428 429 430
        if request.is_ajax():
            return HttpResponse("")
        else:
            return redirect('/')
Dudás Ádám committed
431 432 433

    def get(self, request, iid, *args, **kwargs):
        i = get_object_or_404(Instance, id=iid, owner=request.user)
434
        return render_to_response("confirm_delete.html", RequestContext(request, {
Dudás Ádám committed
435 436 437 438
            'i': i}))

vm_delete = login_required(VmDeleteView.as_view())

439
@login_required
440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456
#@require_POST
def vm_unshare(request, id, *args, **kwargs):
    s = get_object_or_404(Share, id=id)
    g = s.group
    if not g.owners.filter(user=request.user).exists():
        raise PermissionDenied()
    try:
        if s.get_running_or_stopped() > 0:
            messages.error(request, _('There are machines running of this share.'))
        else:
            s.delete()
            messages.success(request, _('Share is successfully removed.'))
    except:
        messages.error(request, _('Failed to remove share.'))
    return redirect(g)

@login_required
457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477
@require_POST
def vm_stop(request, iid, *args, **kwargs):
    try:
        get_object_or_404(Instance, id=iid, owner=request.user).stop()
        messages.success(request, _('Virtual machine is successfully stopped.'))
    except:
        messages.error(request, _('Failed to stop virtual machine.'))
    return redirect('/')

@login_required
@require_POST
def vm_resume(request, iid, *args, **kwargs):
    try:
        get_object_or_404(Instance, id=iid, owner=request.user).resume()
        messages.success(request, _('Virtual machine is successfully resumed.'))
    except:
        messages.error(request, _('Failed to resume virtual machine.'))
    return redirect('/')

@login_required
@require_POST
Őry Máté committed
478 479 480 481 482 483 484 485 486 487
def vm_renew(request, which, iid, *args, **kwargs):
    try:
        get_object_or_404(Instance, id=iid, owner=request.user).renew(which)
        messages.success(request, _('Virtual machine is successfully renewed.'))
    except:
        messages.error(request, _('Failed to renew virtual machine.'))
    return redirect('/')

@login_required
@require_POST
488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504
def vm_power_off(request, iid, *args, **kwargs):
    try:
        get_object_or_404(Instance, id=iid, owner=request.user).poweroff()
        messages.success(request, _('Virtual machine is successfully powered off.'))
    except:
        messages.error(request, _('Failed to power off virtual machine.'))
    return redirect('/')

@login_required
@require_POST
def vm_restart(request, iid, *args, **kwargs):
    try:
        get_object_or_404(Instance, id=iid, owner=request.user).restart()
        messages.success(request, _('Virtual machine is successfully restarted.'))
    except:
        messages.error(request, _('Failed to restart virtual machine.'))
    return redirect('/')
Dudás Ádám committed
505

Dányi Bence committed
506 507 508 509 510 511 512
@login_required
@require_POST
def key_add(request):
    try:
        key=SshKey()
        key.key=request.POST['key']
        key.user=request.user
Dányi Bence committed
513
        key.full_clean()
Dányi Bence committed
514
        key.save()
x committed
515
        _update_keys(request.user)
Dányi Bence committed
516
    except ValidationError as e:
517 518 519
        for m in e.messages:
            messages.error(request, m)

x committed
520
    except:
521 522
        messages.error(request, _('Failed to add public key.'))
    else:
Őry Máté committed
523
        messages.success(request, _('Public key successfully added.'))
Dányi Bence committed
524 525 526 527 528 529
    return redirect('/')

@login_required
@require_POST
def key_ajax_delete(request):
    try:
530
        key=get_object_or_404(SshKey, id=request.POST['id'], user=request.user)
Dányi Bence committed
531
        key.delete()
x committed
532
        _update_keys(request.user)
Dányi Bence committed
533 534
    except:
        messages.error(request, _('Failed to delete public key'))
Dányi Bence committed
535 536 537 538 539 540 541 542 543
    return HttpResponse('OK')

@login_required
@require_POST
def key_ajax_reset(request):
    try:
        det=UserCloudDetails.objects.get(user=request.user)
        det.reset_smb()
        det.reset_keys()
x committed
544
        _update_keys(request.user)
Dányi Bence committed
545 546 547
    except:
        messages.error(request, _('Failed to reset keys'))
    return HttpResponse('OK')
Dányi Bence committed
548

x committed
549 550 551 552 553 554 555 556 557 558
def _update_keys(user):
    details = user.cloud_details
    password = details.smb_password
    key_list = []
    for key in user.sshkey_set.all():
        key_list.append(key.key)
    user = user.username
    StoreApi.updateauthorizationinfo(user, password, key_list)


Dudás Ádám committed
559
# vim: et sw=4 ai fenc=utf8 smarttab :