from admin.decorators import superuser_only from appsettings.settings import app_settings from computes.models import Compute from computes.utils import libvirt_compute_lock from django.contrib import messages from django.http import HttpResponseRedirect, JsonResponse from django.shortcuts import get_object_or_404, redirect, render from django.urls import reverse from django.utils.translation import gettext_lazy as _ from libvirt import libvirtError from operations import claims, jobs from operations.utils import busy_message from vrtManager.util import OperationError from vrtManager.storage import wvmStorage, wvmStorages from storages.forms import AddStgPool, CloneImage, CreateVolumeForm from storages.upload import write_chunk def _handle_iso_upload(request, conn, compute, pool): file_chunk = request.FILES.get("file") if not file_chunk: return JsonResponse({"error": _("No file chunk was submitted.")}, status=400) try: chunk_index = int(request.POST.get("chunk_index", 0)) total_chunks = int(request.POST.get("total_chunks", 1)) except (ValueError, TypeError): return JsonResponse({"error": _("Invalid chunk parameters.")}, status=400) if chunk_index < 0 or chunk_index >= total_chunks: return JsonResponse({"error": _("Invalid chunk index or total chunks.")}, status=400) try: file_size = int(request.POST.get("file_size", "")) except (ValueError, TypeError): return JsonResponse({"error": _("Invalid file size.")}, status=400) try: op = write_chunk(request.user, conn, compute, pool, request.POST.get("upload_id", ""), {"name": request.POST.get("file_name", ""), "size": file_size, "chunks": total_chunks}, chunk_index, file_chunk, request=request) except claims.Busy as busy: if busy.blocker is not None: # another job holds the pool or the name return JsonResponse({"error": busy_message(busy, request.user)}, status=400) return JsonResponse({"error": _("Upload session is busy. Please retry.")}, status=429) except FileExistsError: return JsonResponse({"error": _("ISO image already exists")}, status=400) except (ValueError, jobs.SubmissionConflict, OperationError) as e: return JsonResponse({"error": str(e)}, status=400) except Exception as e: return JsonResponse({"error": str(e)}, status=500) return JsonResponse({"success": True, "job": str(op.pk)}) @superuser_only def storages(request, compute_id): """ :param request: :param compute_id: :return: """ compute = get_object_or_404(Compute, pk=compute_id) errors = False try: conn = wvmStorages( compute.hostname, compute.login, compute.password, compute.type ) storages = conn.get_storages_info() secrets = conn.get_secrets() if request.method == "POST": if "create" in request.POST: form = AddStgPool(request.POST) if form.is_valid(): data = form.cleaned_data if data["name"] in storages: msg = _("Pool name already use") messages.error(request, msg) errors = True if data["stg_type"] == "rbd": if not data["secret"]: msg = _("You need create secret for pool") messages.error(request, msg) errors = True if ( not data["ceph_pool"] and not data["ceph_host"] and not data["ceph_user"] ): msg = _("You need input all fields for creating ceph pool") messages.error(request, msg) errors = True if not errors: if data["stg_type"] == "rbd": conn.create_storage_ceph( data["stg_type"], data["name"], data["ceph_pool"], data["ceph_host"], data["ceph_user"], data["secret"], ) elif data["stg_type"] == "netfs": conn.create_storage_netfs( data["stg_type"], data["name"], data["netfs_host"], data["source"], data["source_format"], data["target"], ) else: conn.create_storage( data["stg_type"], data["name"], data["source"], data["target"], ) return HttpResponseRedirect( reverse("storage", args=[compute_id, data["name"]]) ) else: for msg_err in form.errors.values(): messages.error(request, msg_err.as_text()) conn.close() except libvirtError as lib_err: messages.error(request, lib_err) return render(request, "storages.html", locals()) @superuser_only def storage(request, compute_id, pool): """ :param request: :param compute_id: :param pool: :return: """ compute = get_object_or_404(Compute, pk=compute_id) meta_prealloc = False form = CreateVolumeForm() conn = wvmStorage( compute.hostname, compute.login, compute.password, compute.type, pool ) if request.method == "POST" and "iso_upload" in request.POST: try: return _handle_iso_upload(request, conn, compute, pool) finally: conn.close() storages = conn.get_storages() state = conn.is_active() try: size, free = conn.get_size() used = size - free if state: percent = (used * 100) // size else: percent = 0 except libvirtError: size, free, used, percent = 0, 0, 0, 0 status = conn.get_status() path = conn.get_target_path() type = conn.get_type() autostart = conn.get_autostart() if state: conn.refresh() volumes = conn.update_volumes() else: volumes = None if request.method == "POST": if "start" in request.POST: conn.start() return HttpResponseRedirect(request.get_full_path()) if "stop" in request.POST: conn.stop() return HttpResponseRedirect(request.get_full_path()) if "delete" in request.POST: conn.delete() return HttpResponseRedirect(reverse("storages", args=[compute_id])) if "set_autostart" in request.POST: conn.set_autostart(1) return HttpResponseRedirect(request.get_full_path()) if "unset_autostart" in request.POST: conn.set_autostart(0) return HttpResponseRedirect(request.get_full_path()) if "del_volume" in request.POST: volname = request.POST.get("volname", "") try: # no VM operation attaches it between the check and the delete with libvirt_compute_lock(compute): conn.del_volume(volname) except OperationError as err: messages.error(request, str(err)) except claims.Busy as busy: messages.error(request, busy_message(busy, request.user)) else: messages.success( request, _("Volume: %(vol)s is deleted.") % {"vol": volname} ) return redirect(reverse("storage", args=[compute.id, pool])) # return HttpResponseRedirect(request.get_full_path()) if "cln_volume" in request.POST: form = CloneImage(request.POST) if form.is_valid(): try: op = jobs.submit("volume.clone", request.user, {"compute": compute.id, "pool": pool, **form.cleaned_data}, request.POST.get("submission_key", "")) except claims.Busy as busy: messages.error(request, busy_message(busy, request.user)) except (jobs.SubmissionConflict, libvirtError) as err: messages.error(request, str(err)) else: messages.info(request, _("The volume is being cloned; follow it in the job panel.")) jobs.remember_job(request, op) else: for msg_err in form.errors.values(): messages.error(request, msg_err.as_text()) return redirect(reverse("storage", args=[compute.id, pool])) job_resource = claims.pool_key(compute.id, pool) conn.close() return render(request, "storage.html", locals()) @superuser_only def create_volume(request, compute_id, pool): """ :param request: :param compute_id: compute id :param pool: pool name :return: """ compute = get_object_or_404(Compute, pk=compute_id) meta_prealloc = False conn = wvmStorage( compute.hostname, compute.login, compute.password, compute.type, pool ) form = CreateVolumeForm(request.POST or None) if form.is_valid(): data = form.cleaned_data if data["meta_prealloc"] and data["format"] == "qcow2": meta_prealloc = True disk_owner_uid = int(app_settings.INSTANCE_VOLUME_DEFAULT_OWNER_UID) disk_owner_gid = int(app_settings.INSTANCE_VOLUME_DEFAULT_OWNER_GID) name = conn.create_volume( data["name"], data["size"], data["format"], meta_prealloc, disk_owner_uid, disk_owner_gid, ) messages.success( request, _("Image file %(name)s is created successfully") % {"name": name} ) else: for msg_err in form.errors.values(): messages.error(request, msg_err.as_text()) return redirect(reverse("storage", args=[compute.id, pool])) def get_volumes(request, compute_id, pool): """ :param request: :param compute_id: compute id :param pool: pool name :return: volumes list of pool """ data = {} compute = get_object_or_404(Compute, pk=compute_id) try: conn = wvmStorage( compute.hostname, compute.login, compute.password, compute.type, pool ) conn.refresh() data["vols"] = sorted(conn.get_volumes()) except libvirtError as err: # do not answer 200 with an empty list when the listing failed: the # caller cannot tell "no volumes" from "could not read the pool" return JsonResponse({"error": str(err)}, status=400) return JsonResponse(data)