mirror of
https://github.com/retspen/webvirtcloud
synced 2026-11-08 19:26:48 +00:00
One serializer created volumes and read them: size was GiB in a request and bytes in an answer, the same field in the schema. A client that sent back a size it read asked for a volume 2**30 times as large. A new volume is CreateVolumeSerializer (size in GiB); a volume read is VolumeSerializer (allocation and size in bytes), and the schema says so, as for the pools' size and free space.
223 lines
8.7 KiB
Python
223 lines
8.7 KiB
Python
import contextlib
|
|
|
|
from admin.permissions import IsSuperUser
|
|
from appsettings.settings import app_settings
|
|
from computes.models import Compute
|
|
from computes.utils import libvirt_compute_lock
|
|
from django.shortcuts import get_object_or_404
|
|
from django.utils.translation import gettext as _
|
|
from drf_spectacular.utils import extend_schema, inline_serializer
|
|
from libvirt import libvirtError
|
|
from logs.audit import audit, failed
|
|
from rest_framework import serializers, status, viewsets
|
|
from rest_framework.decorators import action
|
|
from rest_framework.response import Response
|
|
from vrtManager.storage import wvmStorage, wvmStorages
|
|
from webvirtcloud.serializers import ErrorSerializer, StatusSerializer
|
|
|
|
from .serializers import CreateVolumeSerializer, StorageSerializer, StoragesSerializer, VolumeSerializer
|
|
|
|
|
|
class StorageViewSet(viewsets.ViewSet):
|
|
"""
|
|
A viewset for listing retrieving storages.
|
|
"""
|
|
|
|
permission_classes = [IsSuperUser]
|
|
serializer_class = StoragesSerializer
|
|
lookup_value_converter = "str"
|
|
|
|
def list(self, request, compute_pk=None):
|
|
|
|
compute = get_object_or_404(Compute, pk=compute_pk)
|
|
|
|
conn = wvmStorages(
|
|
compute.hostname, compute.login, compute.password, compute.type
|
|
)
|
|
queryset = conn.get_storages_info()
|
|
|
|
serializer = StoragesSerializer(
|
|
queryset, many=True, context={"request": request}
|
|
)
|
|
|
|
return Response(serializer.data)
|
|
|
|
@extend_schema(responses=StorageSerializer)
|
|
def retrieve(self, request, pk=None, compute_pk=None):
|
|
compute = get_object_or_404(Compute, pk=compute_pk)
|
|
|
|
conn = wvmStorage(
|
|
compute.hostname, compute.login, compute.password, compute.type, pk
|
|
)
|
|
|
|
state = conn.is_active()
|
|
# first: it refreshes the pool, and libvirt measures a pool's space only then;
|
|
# a stopped pool lists none
|
|
volumes = conn.update_volumes() if state else None
|
|
size, free = conn.get_size()
|
|
infoset = {
|
|
"state": state,
|
|
"size": size,
|
|
"free": free,
|
|
"status": conn.get_status(),
|
|
"path": conn.get_target_path(),
|
|
"type": conn.get_type(),
|
|
"autostart": conn.get_autostart(),
|
|
"volumes": volumes,
|
|
}
|
|
|
|
serializer = StorageSerializer(
|
|
infoset, many=False, context={"request": request}
|
|
)
|
|
return Response(serializer.data)
|
|
|
|
@extend_schema(request=None, responses=StatusSerializer)
|
|
@action(detail=True, methods=["post"])
|
|
def start(self, request, pk=None, compute_pk=None):
|
|
compute = get_object_or_404(Compute, pk=compute_pk)
|
|
|
|
conn = wvmStorage(
|
|
compute.hostname, compute.login, compute.password, compute.type, pk
|
|
)
|
|
with audit(request, compute, f"Storage pool {pk}", "start"):
|
|
ret = conn.start()
|
|
conn.close()
|
|
return Response({"status": "Pool start command send: " + str(ret)})
|
|
|
|
@extend_schema(request=None, responses=StatusSerializer)
|
|
@action(detail=True, methods=["post"])
|
|
def stop(self, request, pk=None, compute_pk=None):
|
|
compute = get_object_or_404(Compute, pk=compute_pk)
|
|
|
|
conn = wvmStorage(
|
|
compute.hostname, compute.login, compute.password, compute.type, pk
|
|
)
|
|
with audit(request, compute, f"Storage pool {pk}", "stop"):
|
|
ret = conn.stop()
|
|
conn.close()
|
|
return Response({"status": "Pool stop command send: " + str(ret)})
|
|
|
|
@extend_schema(request=None, responses=StatusSerializer)
|
|
@action(detail=True, methods=["post"])
|
|
def refresh(self, request, pk=None, compute_pk=None):
|
|
compute = get_object_or_404(Compute, pk=compute_pk)
|
|
|
|
conn = wvmStorage(
|
|
compute.hostname, compute.login, compute.password, compute.type, pk
|
|
)
|
|
ret = conn.refresh()
|
|
conn.close()
|
|
return Response({"status": "Pool refresh command send: " + str(ret)})
|
|
|
|
@extend_schema(
|
|
request=None,
|
|
responses=inline_serializer("PoolXML", {"return": serializers.CharField()}),
|
|
)
|
|
@action(detail=True, methods=["post"])
|
|
def XML_description(self, request, pk=None, compute_pk=None):
|
|
compute = get_object_or_404(Compute, pk=compute_pk)
|
|
|
|
conn = wvmStorage(
|
|
compute.hostname, compute.login, compute.password, compute.type, pk
|
|
)
|
|
ret = conn._XMLDesc(0)
|
|
conn.close()
|
|
return Response({"return": str(ret)})
|
|
|
|
|
|
class VolumeViewSet(viewsets.ViewSet):
|
|
|
|
"""
|
|
A simple ViewSet for listing or retrieving Storage Volumes.
|
|
"""
|
|
|
|
permission_classes = [IsSuperUser]
|
|
serializer_class = VolumeSerializer
|
|
lookup_value_converter = "str"
|
|
|
|
@extend_schema(responses={200: VolumeSerializer(many=True), 409: ErrorSerializer})
|
|
def list(self, request, storage_pk=None, compute_pk=None):
|
|
|
|
compute = get_object_or_404(Compute, pk=compute_pk)
|
|
|
|
conn = wvmStorage(
|
|
compute.hostname, compute.login, compute.password, compute.type, storage_pk
|
|
)
|
|
if not conn.is_active():
|
|
return Response({"detail": _("Pool is not active")}, status=status.HTTP_409_CONFLICT)
|
|
volume_queryset = conn.update_volumes()
|
|
conn.close()
|
|
serializer = VolumeSerializer(
|
|
volume_queryset, many=True, context={"request": request}
|
|
)
|
|
|
|
return Response(serializer.data)
|
|
|
|
@extend_schema(responses={200: VolumeSerializer, 409: ErrorSerializer})
|
|
def retrieve(self, request, storage_pk=None, compute_pk=None, pk=None):
|
|
compute = get_object_or_404(Compute, pk=compute_pk)
|
|
|
|
conn = wvmStorage(
|
|
compute.hostname, compute.login, compute.password, compute.type, storage_pk
|
|
)
|
|
if not conn.is_active():
|
|
return Response({"detail": _("Pool is not active")}, status=status.HTTP_409_CONFLICT)
|
|
volume_queryset = conn.get_volume_details(pk)
|
|
conn.close()
|
|
serializer = VolumeSerializer(
|
|
volume_queryset, many=False, context={"request": request}
|
|
)
|
|
|
|
return Response(serializer.data)
|
|
|
|
@extend_schema(request=CreateVolumeSerializer, responses={200: StatusSerializer, 400: dict, 409: ErrorSerializer})
|
|
def create(self, request, storage_pk=None, compute_pk=None):
|
|
compute = get_object_or_404(Compute, pk=compute_pk)
|
|
|
|
conn = wvmStorage(
|
|
compute.hostname, compute.login, compute.password, compute.type, storage_pk
|
|
)
|
|
|
|
serializer = CreateVolumeSerializer(data=request.data)
|
|
if serializer.is_valid():
|
|
subject, action = f"Storage pool {storage_pk}", f"add volume {serializer.validated_data['name']}"
|
|
state = conn.is_active()
|
|
if state:
|
|
with contextlib.suppress(libvirtError): # refused while a volume is cloned in the pool
|
|
conn.refresh()
|
|
with audit(request, compute, subject, action) as entry:
|
|
ret = conn.create_volume(
|
|
serializer.validated_data["name"],
|
|
serializer.validated_data["size"],
|
|
serializer.validated_data["type"],
|
|
serializer.validated_data["meta_prealloc"] and serializer.validated_data["type"] == "qcow2",
|
|
int(app_settings.INSTANCE_VOLUME_DEFAULT_OWNER_UID),
|
|
int(app_settings.INSTANCE_VOLUME_DEFAULT_OWNER_GID),
|
|
)
|
|
entry.action = f"add volume {ret}"
|
|
conn.close()
|
|
return Response({"status": "Volume: " + ret + " is created"})
|
|
else:
|
|
failed(request, compute, subject, action, "pool is not active")
|
|
return Response({"detail": _("Pool is not active")}, status=status.HTTP_409_CONFLICT)
|
|
else:
|
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
|
|
|
@extend_schema(responses={200: StatusSerializer, 409: ErrorSerializer})
|
|
def destroy(self, request, storage_pk=None, compute_pk=None, pk=None):
|
|
compute = get_object_or_404(Compute, pk=compute_pk)
|
|
|
|
conn = wvmStorage(
|
|
compute.hostname, compute.login, compute.password, compute.type, storage_pk
|
|
)
|
|
|
|
subject, action = f"Storage pool {storage_pk}", f"delete volume {pk}"
|
|
if conn.is_active():
|
|
# no VM operation attaches it between the check and the delete
|
|
with audit(request, compute, subject, action), libvirt_compute_lock(compute):
|
|
conn.del_volume(pk)
|
|
conn.close()
|
|
return Response({"status": "Volume: " + pk + " is deleted"})
|
|
else:
|
|
failed(request, compute, subject, action, "pool is not active")
|
|
return Response({"detail": _("Pool is not active")}, status=status.HTTP_409_CONFLICT)
|