mirror of
https://github.com/retspen/webvirtcloud
synced 2026-11-08 11:16:47 +00:00
- libvirt errors all answered 400. A VM, pool or volume the host does not
have now answers 404, a volume in use 409 (vrtManager.util.InUse) and a
host that cannot be reached 503, which a client may retry.
- A template's power on or cycle and a stopped pool answered
{"status": ...} with 409; they answer {"detail": ...}, as every other
refusal. The schema documents these 409s (ErrorSerializer).
- The volume list and a volume of a stopped pool answered 200 with an
empty list or a made-up volume; they answer 409.
- An API URL that names nothing answered the HTML error page; it answers
a JSON 404 after the API's authentication.
- A session that must change its password was redirected to the HTML
form; an API request of it gets the JSON 403 a token gets.
223 lines
8.6 KiB
Python
223 lines
8.6 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 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(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 = VolumeSerializer(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)
|