# Copyright 2014 Budapest University of Technology and Economics (BME IK) # # This file is part of CIRCLE Cloud. # # CIRCLE is free software: you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free # Software Foundation, either version 3 of the License, or (at your option) # any later version. # # CIRCLE is distributed in the hope that it will be useful, but WITHOUT ANY # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more # details. # # You should have received a copy of the GNU General Public License along # with CIRCLE. If not, see <http://www.gnu.org/licenses/>. import os import re import subprocess import sys from datetime import timedelta import json import logging from glob import glob from hashlib import sha256, sha1 from shutil import move, rmtree from string import Template from tarfile import TarFile from time import sleep, time from xml.etree.cElementTree import parse as XMLparse from xml.dom import NotFoundErr from django.conf import settings from django.contrib import messages from django.contrib.auth.models import User from django.contrib.messages.views import SuccessMessageMixin from django.urls import reverse, reverse_lazy from django.core.exceptions import PermissionDenied, SuspiciousOperation from django.db.models import Count from django.http import HttpResponse, HttpResponseRedirect, JsonResponse from django.shortcuts import redirect, get_object_or_404 from django.utils import timezone from django.utils.translation import ugettext as _, ugettext_noop from django.views.generic import ( TemplateView, CreateView, UpdateView, ) #import magic from ..store_api import Store, NoStoreException from braces.views import ( LoginRequiredMixin, PermissionRequiredMixin, ) from django_tables2 import SingleTableView from vm.models import ( InstanceTemplate, InterfaceTemplate, Instance, Lease, InstanceActivity, ExportedVM, OS_TYPES ) from storage.models import Disk, DataStore from ..forms import ( TemplateForm, TemplateListSearchForm, AclUserOrGroupAddForm, LeaseForm, TemplateImportForm, ) from ..tables import TemplateListTable, LeaseListTable from .util import ( AclUpdateView, FilterMixin, TransferOwnershipConfirmView, TransferOwnershipView, DeleteViewBase, GraphMixin ) from dashboard.serializers import InstanceTemplateSerializer, LeaseSerializer, InstanceSerializer from rest_framework.authentication import TokenAuthentication, BasicAuthentication from rest_framework.permissions import IsAdminUser from rest_framework.views import APIView from rest_framework.parsers import JSONParser try: # Python 2: "unicode" is built-in unicode except NameError: unicode = str try: # Python 2: "unicode" is built-in unicode except NameError: unicode = str logger = logging.getLogger(__name__) class TemplateChoose(LoginRequiredMixin, TemplateView): def get_template_names(self): if self.request.is_ajax(): return ['dashboard/_modal.html'] else: return ['dashboard/nojs-wrapper.html'] def get_context_data(self, *args, **kwargs): context = super(TemplateChoose, self).get_context_data(*args, **kwargs) templates = InstanceTemplate.get_objects_with_level("user", self.request.user) context.update({ 'box_title': _('Choose template'), 'ajax_title': True, 'template': "dashboard/_template-choose.html", 'templates': templates.all(), }) return context def post(self, request, *args, **kwargs): if not request.user.has_perm('vm.create_template'): raise PermissionDenied() template = request.POST.get("parent") if template == "base_vm": return redirect(reverse("dashboard.views.template-create")) elif template == "import_vm": return redirect(reverse("dashboard.views.template-import")) elif template is None: messages.warning(request, _("Select an option to proceed.")) return redirect(reverse("dashboard.views.template-choose")) else: template = get_object_or_404(InstanceTemplate, pk=template) if not template.has_level(request.user, "user"): raise PermissionDenied() instance = Instance.create_from_template( template=template, owner=request.user, is_base=True) return redirect(instance.get_absolute_url()) class TemplateCreate(SuccessMessageMixin, CreateView): model = InstanceTemplate form_class = TemplateForm def get_template_names(self): if self.request.is_ajax(): pass else: return ['dashboard/nojs-wrapper.html'] def get_context_data(self, *args, **kwargs): context = super(TemplateCreate, self).get_context_data(*args, **kwargs) num_leases = Lease.get_objects_with_level("operator", self.request.user).count() can_create_leases = self.request.user.has_perm("create_leases") context.update({ 'box_title': _("Create a new base VM"), 'template': "dashboard/_template-create.html", 'show_lease_create': num_leases < 1 and can_create_leases }) return context def get(self, *args, **kwargs): if not self.request.user.has_perm('vm.create_base_template'): raise PermissionDenied() return super(TemplateCreate, self).get(*args, **kwargs) def get_form_kwargs(self): kwargs = super(TemplateCreate, self).get_form_kwargs() kwargs['user'] = self.request.user return kwargs def post(self, request, *args, **kwargs): if not self.request.user.has_perm('vm.create_base_template'): raise PermissionDenied() form = self.form_class(request.POST, user=request.user) if not form.is_valid(): return self.get(request, form, *args, **kwargs) else: post = form.cleaned_data networks = self.__create_networks(post.pop("networks"), request.user) post.pop("parent") post['max_ram_size'] = post['ram_size'] req_traits = post.pop("req_traits") tags = post.pop("tags") post['pw'] = User.objects.make_random_password() post['is_base'] = True inst = Instance.create(params=post, disks=[], networks=networks, tags=tags, req_traits=req_traits) return HttpResponseRedirect("%s#resources" % inst.get_absolute_url()) def __create_networks(self, vlans, user): networks = [] for v in vlans: if not v.has_level(user, "user"): raise PermissionDenied() networks.append(InterfaceTemplate(vlan=v, managed=v.managed)) return networks def get_success_url(self): return reverse_lazy("dashboard.views.template-list") class TemplateImport(LoginRequiredMixin, TemplateView): form_class = TemplateImportForm def get_template_names(self): if self.request.is_ajax(): return ['dashboard/_modal.html'] else: return ['dashboard/nojs-wrapper.html'] def get(self, request, form=None, *args, **kwargs): if not request.user.has_perm('vm.import_template'): raise PermissionDenied() try: Store(request.user) except NoStoreException: raise PermissionDenied if form is None: form = self.form_class(user=request.user) context = self.get_context_data(**kwargs) context.update({ 'template': 'dashboard/template-import.html', 'box_title': _('Import a VM to a template'), 'form': form, 'ajax_title': True, }) return self.render_to_response(context) def post(self, request, *args, **kwargs): if not request.user.has_module_perms('vm.import_template'): raise PermissionDenied() try: store = Store(request.user) except NoStoreException: raise PermissionDenied() form = self.form_class(request.POST, user=request.user) if form.is_valid(): datastore = DataStore.objects.filter(name=settings.EXPORT_DATASTORE).get() ova_path = form.cleaned_data["ova_path"] template_name = form.cleaned_data["template_name"] url, port = store.request_ssh_download(ova_path) ova_filename = re.split('[:/]', url)[-1] tmp_path = os.path.join(datastore.path, "tmp", ova_filename + str(time())) ova_file = os.path.join(tmp_path, ova_filename) try: os.mkdir(os.path.join(datastore.path, "tmp")) except FileExistsError: pass try: os.mkdir(tmp_path) except FileExistsError: pass if settings.STORE_SSH_MODE == "scp": cmdline = ['scp', '-B', '-P', str(port), url, ova_file] if settings.STORE_IDENTITY_FILE is not None and settings.STORE_IDENTITY_FILE != "": cmdline.append("-i") cmdline.append(settings.STORE_IDENTITY_FILE) elif settings.STORE_SSH_MODE == "rsync": cmdline = ["rsync", "-qLS", url, ova_file] cmdline.append("-e") if settings.STORE_IDENTITY_FILE is not None: cmdline.append("ssh -i %s -p %s" % (settings.STORE_IDENTITY_FILE, str(port))) else: cmdline.append("ssh -p %s" % str(port)) else: logger.error("Invalid mode for disk export: %s" % settings.STORE_SSH_MODE) raise Exception("Invalid mode for disk export: %s" % settings.STORE_SSH_MODE) logger.debug("Calling file transfer with command line: %s" % str(cmdline)) try: # let's try the file transfer 5 times, it may be an intermittent network issue for i in range(4, -1, -1): proc = subprocess.Popen(cmdline) while proc.poll() is None: sleep(2) if proc.returncode == 0: break else: logger.error("Copy over ssh failed with return code: %s, will try %s more time(s)..." % (str(proc.returncode), str(i))) if proc.stdout is not None: logger.info(proc.stdout.read()) if proc.stdout is not None: logger.error(proc.stderr.read()) with TarFile.open(name=ova_file, mode="r") as tar: if sys.version_info >= (3, 12): tar.extractall(path=tmp_path, filter='data') else: for tarinfo in tar: if tarinfo.name.startswith("..") or tarinfo.name.startswith("/") or tarinfo.name.find("/..") != -1: raise Exception("import template: invalid path in tar file") if tarinfo.isreg(): tar.extract(tarinfo, path=tmp_path, set_attrs=False) elif tarinfo.isdir(): tar.extract(tarinfo, path=tmp_path, set_attrs=False) else: raise Exception("import template: invalid file type in tar file") os.unlink(ova_file) mf = glob(os.path.join(tmp_path, "*.mf")) if len(mf) > 1: logger.error("import template: Invalid ova: multiple mf files!") messages.error("import template: Invalid ova: multiple mf files!") raise Exception("Invalid ova: multiple mf files") elif len(mf) == 1: with open(os.path.join(tmp_path, mf[0]), 'r') as mf_file: def compute_sha256(file_name): hash_sha256 = sha256() with open(file_name, "rb") as f: for chunk in iter(lambda: f.read(4096), b""): hash_sha256.update(chunk) return hash_sha256.hexdigest() def compute_sha1(file_name): hash_sha1 = sha1() with open(file_name, "rb") as f: for chunk in iter(lambda: f.read(4096), b""): hash_sha1.update(chunk) return hash_sha1.hexdigest() for line in mf_file: if line.startswith("..") or line.startswith("/") or line.find("/..") != -1: logger.error("import template: mf: invalid path in mf file!") messages.error("import template: mf: invalid path in mf file!") raise Exception("import template: mf: invalid path in mf file!") if line.startswith("SHA1"): line_split = line.split("=") filename = line_split[0][4:].strip()[1:-1] hash_value = line_split[1].strip() if compute_sha1(os.path.join(tmp_path, filename)) != hash_value: logger.error("import template: mf: hash check failed!") messages.error("import template: mf: hash check failed!") raise Exception("import template: mf: hash check failed!") else: logger.info("%s passed hash test" % filename) elif line.startswith("SHA256"): line_split = line.split("=") filename = line_split[0][6:].strip()[1:-1] hash_value = line_split[1].strip() if compute_sha256(os.path.join(tmp_path, filename)) != hash_value: logger.error("import template: mf: hash check failed!") messages.error("import template: mf: hash check failed!") else: logger.info("%s passed hash test" % filename) else: logger.error("import template: mf: Invalid hash algorythm!") messages.error("import template: mf: Invalid hash algorythm!") os.unlink(os.path.join(tmp_path, mf[0])) ovf = glob(os.path.join(tmp_path, "*.ovf")) if len(ovf) != 1: logger.error("import template: Invalid ova: multiple ovf files!") messages.error("import template: Invalid ova: multiple ovf files!") raise Exception("Invalid ova: multiple ovf files") xml = XMLparse(ovf[0]) xml_root = xml.getroot() files = {} disks = {} disks_circle = [] xml_references = xml_root.findall("{http://schemas.dmtf.org/ovf/envelope/2}References") if len(xml_references) == 1: for xml_reference in xml_references[0].findall("{http://schemas.dmtf.org/ovf/envelope/2}File"): files[xml_reference.get("{http://schemas.dmtf.org/ovf/envelope/2}id")] = xml_reference.get("{http://schemas.dmtf.org/ovf/envelope/2}href") xml_disk_section = xml_root.findall("{http://schemas.dmtf.org/ovf/envelope/2}DiskSection") if len(xml_disk_section) == 1: for disk in xml_disk_section[0].findall("{http://schemas.dmtf.org/ovf/envelope/2}Disk"): disks[disk.get("{http://schemas.dmtf.org/ovf/envelope/2}diskId")] = {"name": files[disk.get("{http://schemas.dmtf.org/ovf/envelope/2}fileRef")], "type": disk.get("{http://schemas.dmtf.org/ovf/envelope/2}format") } xml_VirtualSystem = xml_root.findall("{http://schemas.dmtf.org/ovf/envelope/2}VirtualSystem")[0] ovf_os = xml_VirtualSystem.findall("{http://schemas.dmtf.org/ovf/envelope/2}OperatingSystemSection")[0].findall("{http://schemas.dmtf.org/ovf/envelope/2}Description")[0].text arch_id = xml_VirtualSystem.findall("{http://schemas.dmtf.org/ovf/envelope/2}OperatingSystemSection")[0].get("{http://schemas.dmtf.org/ovf/envelope/2}id") if arch_id == "102": arch = "x86_64" elif arch_id == 0 or arch_id == 1: arch = "i686" else: os_type: str = OS_TYPES[int(arch_id)] if os_type.endswith("64-Bit"): arch = "x86_64" else: arch = "i686" try: ovf_description = xml_VirtualSystem.findall("{http://schemas.dmtf.org/ovf/envelope/2}Description")[0].text except Exception as e: logger.error("Couldn't load description from ovf: %s" % e) ovf_description = "" xml_hardware = xml_VirtualSystem.findall("{http://schemas.dmtf.org/ovf/envelope/2}VirtualHardwareSection")[0] for item in xml_hardware.iter(): if item.tag == "{http://schemas.dmtf.org/ovf/envelope/2}Item": resource_type = item.findall("{http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_ResourceAllocationSettingData}ResourceType")[0].text # CPU if resource_type == "3": cores = int(item.findall("{http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_ResourceAllocationSettingData}VirtualQuantity")[0].text) logger.info("import ovf: cores: %s" % cores) # memory if resource_type == "4": memory = int(item.findall("{http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_ResourceAllocationSettingData}VirtualQuantity")[0].text) try: unit = item.findall("{http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_ResourceAllocationSettingData}VirtualQuantityUnits")[0].text.lower() except: try: unit = item.findall("{http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_ResourceAllocationSettingData}AllocationUnits")[0].text.lower() except: raise Exception("No unit for memory.") unit_split = unit.split("*") unit_base = unit_split[0].strip() if unit_base in ["bytes", "kilobytes", "megabytes", "gigayytes"]: if unit_base == "kilobytes": memory = memory * 1000 elif unit_base == "megabytes": memory = memory * 1000 * 1000 elif unit_base == "gigabytes": memory = memory * 1000 * 1000 * 1000 else: raise Exception("Invalid unit for memory.") if len(unit_split) == 2: unit_numbers = unit_split[1].strip().split("^") memory = memory * (int(unit_numbers[0].strip()) ** int(unit_numbers[1].strip())) memory = int(memory / 1024 / 1024) logger.info("import ovf: memory: %s MiB" % memory) elif item.tag == "{http://schemas.dmtf.org/ovf/envelope/2}StorageItem": if item.findall("{http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_StorageAllocationSettingData.xsd}ResourceType")[0].text.lower() == str(17): disk_no = item.findall("{http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_StorageAllocationSettingData.xsd}HostResource")[0].text.split("/")[-1] disk_name = item.findall("{http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_StorageAllocationSettingData.xsd}Caption")[0].text datastore = DataStore.objects.filter(name='default').get() circle_disk = Disk.create(datastore=datastore, type="qcow2-norm", name=disk_name) imported_path = os.path.join(tmp_path, disks[disk_no]["name"]) logger.debug("Calling qemu-img with command line: %s" % str(cmdline)) cmdline = ['ionice', '-c', 'idle', 'qemu-img', 'convert', '-m', '4', '-O', 'qcow2', imported_path, os.path.join(datastore.path, circle_disk.filename)] circle_disk.is_ready = True circle_disk.save() logger.debug("Calling qemu-img with command line: %s" % str(cmdline)) subprocess.check_output(cmdline) disks_circle.append(circle_disk) template = InstanceTemplate(name=template_name, access_method='ssh', description=ovf_description, system=ovf_os, num_cores=cores, num_cores_max=cores, ram_size=memory, max_ram_size=memory, arch=arch, priority=0, owner=self.request.user, lease=form.cleaned_data["lease"], ) template.save() for disk in disks_circle: template.disks.add(disk) template.save() return redirect(template.get_absolute_url()) except Exception as e: logger.error(e) raise finally: if os.path.exists(ova_path): os.unlink(ova_path) if os.path.exists(tmp_path): rmtree(tmp_path) else: return self.get(request, form, *args, **kwargs) class TemplateREST(APIView): authentication_classes = [TokenAuthentication,BasicAuthentication] permission_classes = [IsAdminUser] def get(self, request, format=None): if request.query_params.get("name"): try: template = InstanceTemplate.objects.filter(name__istartswith=request.query_params.get('name')).get() serializer = InstanceTemplateSerializer(template, many=False) return JsonResponse(serializer.data, safe=False) except: return JsonResponse(state=411) templates = InstanceTemplate.objects.all() serializer = InstanceTemplateSerializer(templates, many=True) return JsonResponse(serializer.data, safe=False) def post(self, request, format=None): data = JSONParser().parse(request) serializer = InstanceTemplateSerializer(data=data) if serializer.is_valid(): serializer.save() return JsonResponse(serializer.data, status=201) return JsonResponse(serializer.errors, status=400) class GetTemplateREST(APIView): authentication_classes = [TokenAuthentication,BasicAuthentication] permission_classes = [IsAdminUser] def get(self, request, pk, format=None): templates = InstanceTemplate.objects.get(pk=pk) serializer = InstanceTemplateSerializer(templates, many=False) return JsonResponse(serializer.data, safe=False) def delete(self, request, pk, format=None): return JsonResponse(status=400) class InstanceFromTemplateREST(APIView): authentication_classes = [TokenAuthentication,BasicAuthentication] permission_classes = [IsAdminUser] def post(self, request, format=None): data = JSONParser().parse(request) user = User.objects.get(pk=request.user.pk) template = InstanceTemplate.objects.get(pk=data['template']) ikwargs = { 'name': data['name'], 'template': template, 'owner': user, } amount = data.get("amount", 1) if 'num_cores' in data: ikwargs.update({'num_cores':data['num_cores']}) if 'ram_size' in data: ikwargs.update({'ram_size':data['ram_size']}) if 'priority' in data: ikwargs.update({'priority':data['priority']}) if 'max_ram_size' in data: ikwargs.update({'max_ram_size':data['ram_size']}) instances = Instance.mass_create_from_template(amount=amount, **ikwargs) for i in instances: i.deploy._async(user=user) if amount == 1: serializer = InstanceSerializer(instances, many=True) return JsonResponse(serializer.data, status=201, safe=False) serializer = InstanceSerializer(instances, many=True) return JsonResponse(serializer.data, status=201, safe=False) class InstanceFTforUsersREST(APIView): authentication_classes = [TokenAuthentication,BasicAuthentication] permission_classes = [IsAdminUser] def post(self, request, format=None): data = JSONParser().parse(request) template = InstanceTemplate.objects.get(pk=data['template']) ikwargs = { 'name': data['name'], 'users': data['users'], 'template': template, 'operator': data.get('operator', None), 'admin': data.get('admin', None) } if 'num_cores' in data: ikwargs.update({'num_cores':data['num_cores']}) if 'ram_size' in data: ikwargs.update({'ram_size':data['ram_size']}) if 'priority' in data: ikwargs.update({'priority':data['priority']}) if 'max_ram_size' in data: ikwargs.update({'max_ram_size':data['ram_size']}) missing_users, instances = Instance.mass_create_for_users(**ikwargs) serializer = InstanceSerializer(instances, many=True) return JsonResponse(serializer.data, status=201) class InstanceFTforUsersIdREST(APIView): authentication_classes = [TokenAuthentication,BasicAuthentication] permission_classes = [IsAdminUser] def post(self, request, format=None): data = JSONParser().parse(request) template = InstanceTemplate.objects.get(pk=int(data['template'])) ikwargs = { 'name': data['name'], 'users': data['users'], 'template': template, 'operator': data.get('operator', None), 'admin': data.get('admin', None) } if 'num_cores' in data: ikwargs.update({'num_cores':data['num_cores']}) if 'ram_size' in data: ikwargs.update({'ram_size':data['ram_size']}) if 'priority' in data: ikwargs.update({'priority':data['priority']}) if 'max_ram_size' in data: ikwargs.update({'max_ram_size':data['ram_size']}) missing_users, instances = Instance.mass_create_for_users_id(**ikwargs) serializer = InstanceSerializer(instances, many=True) return JsonResponse(serializer.data, status=201, safe=False) class LeaseREST(APIView): authentication_classes = [TokenAuthentication, BasicAuthentication] permission_classes = [IsAdminUser] def get(self, request, format=None): if request.query_params.get('name'): try: template = Lease.objects.filter(name__istartswith=request.query_params.get('name')).get() serializer = LeaseSerializer(template, many=False) return JsonResponse(serializer.data, safe=False) except: return JsonResponse({}, status=404) templates = Lease.objects.all() serializer = LeaseSerializer(templates, many=True) return JsonResponse(serializer.data, safe=False) def post(self, request, format=None): data = JSONParser().parse(request) serializer = LeaseSerializer(data=data) if serializer.is_valid(): serializer.save() return JsonResponse(serializer.data, status=201) return JsonResponse(serializer.errors, status=400) class GetLeaseREST(APIView): authentication_classes = [TokenAuthentication, BasicAuthentication] permission_classes = [IsAdminUser] def get(self, request, pk, format=None): lease = Lease.objects.get(pk=pk) serializer = LeaseSerializer(lease, many=False) return JsonResponse(serializer.data, safe=False) class TemplateAclUpdateView(AclUpdateView): model = InstanceTemplate class TemplateList(LoginRequiredMixin, FilterMixin, SingleTableView): template_name = "dashboard/template-list.html" model = InstanceTemplate table_class = TemplateListTable table_pagination = False allowed_filters = { 'name': "name__icontains", 'tags[]': "tags__name__in", 'tags': "tags__name__in", # for search string 'owner': "owner__username", 'ram': "ram_size", 'ram_size': "ram_size", 'cores': "num_cores", 'num_cores': "num_cores", 'access_method': "access_method__iexact", } def get_context_data(self, *args, **kwargs): context = super(TemplateList, self).get_context_data(*args, **kwargs) user = self.request.user leases_w_operator = Lease.get_objects_with_level("operator", user) context['lease_table'] = LeaseListTable( leases_w_operator, request=self.request, template_name="django_tables2/table_no_page.html", ) context['show_lease_table'] = ( leases_w_operator.count() > 0 or user.has_perm("vm.create_leases") ) context['search_form'] = self.search_form # templates without any instances # [t for t in InstanceTemplate.objects.all() # if t.instance_set.count() < 1] never_instantiated = context['object_list'].annotate( instance_count=Count("instance_set")).filter(instance_count__lt=1) # templates without active virtual machines active_statuses = Instance.STATUS._db_values - set(["DESTROYED"]) templates_wo_instances = context['object_list'].exclude( pk__in=InstanceTemplate.objects.filter( instance_set__status__in=active_statuses) ).exclude(pk__in=never_instantiated) def get_create_acts_younger_than(days): return InstanceActivity.objects.filter( activity_code="vm.Instance.create", finished__gt=timezone.now() - timedelta(days=days)) # templates without active virtual machines # last machine started later than 90 days templates_wo_i_90 = templates_wo_instances.exclude( instance_set__activity_log__in=get_create_acts_younger_than(90)) # templates without active virtual machines # last machine started later than 180 days templates_wo_i_180 = templates_wo_instances.exclude( instance_set__activity_log__in=get_create_acts_younger_than(180)) context['unused_templates'] = { 'never_instantiated': never_instantiated, 'templates_wo_instances': templates_wo_instances, 'templates_wo_instances_90': templates_wo_i_90, 'templates_wo_instances_180': templates_wo_i_180, } return context def get(self, *args, **kwargs): self.search_form = TemplateListSearchForm(self.request.GET) self.search_form.full_clean() if self.request.is_ajax(): templates = [{ 'icon': i.os_type, 'system': i.system, 'url': reverse("dashboard.views.template-detail", kwargs={'pk': i.pk}), 'name': i.name} for i in self.get_queryset()] return HttpResponse( json.dumps(templates), content_type="application/json", ) else: return super(TemplateList, self).get(*args, **kwargs) def create_acl_queryset(self, model): queryset = super(TemplateList, self).create_acl_queryset(model) sql = ("SELECT count(*) FROM vm_instance WHERE " "vm_instance.template_id = vm_instancetemplate.id and " "vm_instance.destroyed_at is null and " "vm_instance.status = 'RUNNING'") queryset = queryset.extra(select={'running': sql}) return queryset def get_queryset(self): logger.debug('TemplateList.get_queryset() called. User: %s', unicode(self.request.user)) qs = self.create_acl_queryset(InstanceTemplate) self.create_fake_get() try: filters, excludes = self.get_queryset_filters() qs = qs.filter(**filters).exclude(**excludes).distinct() except ValueError: messages.error(self.request, _("Error during filtering.")) return qs.select_related("lease", "owner", "owner__profile") class TemplateDelete(DeleteViewBase): model = InstanceTemplate success_message = _("Template successfully deleted.") def get_success_url(self): return reverse("dashboard.views.template-list") def delete_obj(self, request, *args, **kwargs): object = self.get_object() object.destroy_disks() object.delete() class TemplateDetail(LoginRequiredMixin, GraphMixin, 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 = self.get_object() 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'] = AclUpdateView.get_acl_data( obj, self.request.user, 'dashboard.views.template-acl') context['disks'] = obj.disks.all() context['is_owner'] = obj.has_level(self.request.user, 'owner') context['aclform'] = AclUserOrGroupAddForm() context['parent'] = obj.parent context['show_graph'] = obj.has_level(self.request.user, 'operator') 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() 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 DiskRemoveView(DeleteViewBase): model = Disk success_message = _("Disk successfully removed.") def get_queryset(self): qs = super(DiskRemoveView, self).get_queryset() return qs.exclude(template_set=None) def check_auth(self): disk = self.get_object() template = disk.template_set.get() if not template.has_level(self.request.user, 'owner'): raise PermissionDenied() def get_context_data(self, **kwargs): disk = self.get_object() template = disk.template_set.get() context = super(DiskRemoveView, self).get_context_data(**kwargs) context['title'] = _("Disk remove confirmation") context['text'] = _("Are you sure you want to remove " "<strong>%(disk)s</strong> from " "<strong>%(app)s</strong>?" % {'disk': disk, 'app': template} ) return context def delete_obj(self, request, *args, **kwargs): disk = self.get_object() template = disk.template_set.get() template.remove_disk(disk) disk.destroy() def get_success_url(self): return self.request.POST.get("next") or "/" class LeaseCreate(LoginRequiredMixin, PermissionRequiredMixin, SuccessMessageMixin, CreateView): model = Lease form_class = LeaseForm permission_required = 'vm.create_leases' 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") def form_valid(self, form): retval = super(LeaseCreate, self).form_valid(form) self.object.set_level(self.request.user, "owner") return retval class LeaseAclUpdateView(AclUpdateView): model = Lease class LeaseDetail(LoginRequiredMixin, SuccessMessageMixin, UpdateView): model = Lease form_class = LeaseForm template_name = "dashboard/lease-edit.html" success_message = _("Successfully modified lease.") def get_context_data(self, *args, **kwargs): obj = self.get_object() context = super(LeaseDetail, self).get_context_data(*args, **kwargs) context['acl'] = AclUpdateView.get_acl_data( obj, self.request.user, 'dashboard.views.lease-acl') return context def get_success_url(self): return reverse_lazy("dashboard.views.lease-detail", kwargs=self.kwargs) def get(self, request, *args, **kwargs): if not self.get_object().has_level(request.user, "owner"): message = _("Only the owners can modify the selected lease.") messages.warning(request, message) return redirect(reverse_lazy("dashboard.views.template-list")) return super(LeaseDetail, self).get(request, *args, **kwargs) def post(self, request, *args, **kwargs): if not self.get_object().has_level(request.user, "owner"): raise PermissionDenied() return super(LeaseDetail, self).post(request, *args, **kwargs) class LeaseDelete(DeleteViewBase): model = Lease success_message = _("Lease successfully deleted.") def get_success_url(self): return reverse("dashboard.views.template-list") def get_context_data(self, *args, **kwargs): c = super(LeaseDelete, self).get_context_data(*args, **kwargs) lease = self.get_object() templates = lease.instancetemplate_set if templates.count() > 0: text = _("You can't delete this lease because some templates " "are still using it, modify these to proceed: ") c['text'] = text + ", ".join("<strong>%s (#%d)</strong>" "" % (o.name, o.pk) for o in templates.all()) c['disable_submit'] = True return c def delete_obj(self, request, *args, **kwargs): object = self.get_object() if object.instancetemplate_set.count() > 0: raise SuspiciousOperation() object.delete() class TransferTemplateOwnershipConfirmView(TransferOwnershipConfirmView): template = "dashboard/confirm/transfer-template-ownership.html" model = InstanceTemplate class TransferTemplateOwnershipView(TransferOwnershipView): confirm_view = TransferTemplateOwnershipConfirmView model = InstanceTemplate notification_msg = ugettext_noop( '%(owner)s offered you to take the ownership of ' 'his/her template called %(instance)s. ' '<a href="%(token)s" ' 'class="btn btn-success btn-small">Accept</a>') token_url = 'dashboard.views.template-transfer-ownership-confirm' template = "dashboard/template-tx-owner.html" class ExportedVMDelete(DeleteViewBase): model = ExportedVM success_message = _("Exported VM successfully deleted.") def get_success_url(self): #return reverse("dashboard.views.vm-list") return reverse_lazy("dashboard.views.detail", kwargs={'pk': self.vm_pk}) def check_auth(self): if not self.get_object().vm.has_level(self.request.user, self.level): raise PermissionDenied() def delete_obj(self, request, *args, **kwargs): self.get_object().delete()