mirror of
https://github.com/retspen/webvirtcloud
synced 2026-11-08 19:26:48 +00:00
Web views: - 34 views returned to request.META["HTTP_REFERER"] + "#tab": without a Referer they failed with a TypeError (500), and a Referer from another site was followed (open redirect). They return through _back(), which keeps a same-site Referer and otherwise opens the VM page at the tab. get_safe_redirect uses the same check. Rejected disk options also return to the VM's disks tab instead of the VM list. - migrate_instance returned silently when the target was the current compute or unreachable, so the web view logged "Instance is migrated" and the API answered "instance migrate is started". It now raises; the view reports the error and logs only a completed migration, and an unknown compute id is a 404 instead of a 500. The automatic migration after a clone skips a target that is the current compute. - add_owner and del_owner failed with a 500 without their ids, add_owner also when the user was already an owner, and del_owner deleted the given ownership record even when it belonged to another VM. Ids are validated, a record of another VM is a 404, and a repeated owner is reported. - Broad handlers that hid programming errors are narrowed: clone catches libvirt, value and OS errors (a lock timeout included), ownership lookups catch DoesNotExist. The DRBD status no longer needs its bare except: the VM name is escaped in the regex, which was the only thing that could fail there. API: - A libvirt error under /api/ is JSON with status 400, and a lock timeout 409, instead of the HTML error page. This lives in the existing ExceptionMiddleware, so existing settings need no change. - Volume create/delete on an inactive pool answer 409, and invalid volume data 400 with the serializer errors, instead of 200. The new tests cover every view that returns with _back() without a Referer and with a foreign one, the migrate failures on web and API, the owner ids, the API error format and the volume statuses; nine of the ten fail on the previous code.
203 lines
7.1 KiB
Python
203 lines
7.1 KiB
Python
from admin.permissions import IsSuperUser
|
|
from appsettings.settings import app_settings
|
|
from computes.models import Compute
|
|
from django.shortcuts import get_object_or_404
|
|
from drf_spectacular.utils import extend_schema, inline_serializer
|
|
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 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
|
|
)
|
|
|
|
infoset = {
|
|
"state": conn.is_active(),
|
|
"size": conn.get_size()[0],
|
|
"free": conn.get_size()[1],
|
|
"status": conn.get_status(),
|
|
"path": conn.get_target_path(),
|
|
"type": conn.get_type(),
|
|
"autostart": conn.get_autostart(),
|
|
"volumes": conn.update_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
|
|
)
|
|
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
|
|
)
|
|
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"
|
|
|
|
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
|
|
)
|
|
state = conn.is_active()
|
|
|
|
if state:
|
|
conn.refresh()
|
|
volume_queryset = conn.update_volumes()
|
|
else:
|
|
volume_queryset = None
|
|
conn.close()
|
|
serializer = VolumeSerializer(
|
|
volume_queryset, many=True, context={"request": request}
|
|
)
|
|
|
|
return Response(serializer.data)
|
|
|
|
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
|
|
)
|
|
state = conn.is_active()
|
|
|
|
volume_queryset = conn.get_volume_details(pk) if state else None
|
|
conn.close()
|
|
serializer = VolumeSerializer(
|
|
volume_queryset, many=False, context={"request": request}
|
|
)
|
|
|
|
return Response(serializer.data)
|
|
|
|
@extend_schema(responses={200: StatusSerializer, 400: dict, 409: StatusSerializer})
|
|
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():
|
|
state = conn.is_active()
|
|
if state:
|
|
conn.refresh()
|
|
ret = conn.create_volume(
|
|
serializer.validated_data["name"],
|
|
serializer.validated_data["size"],
|
|
serializer.validated_data["type"],
|
|
serializer.validated_data["meta_prealloc"],
|
|
int(app_settings.INSTANCE_VOLUME_DEFAULT_OWNER_UID),
|
|
int(app_settings.INSTANCE_VOLUME_DEFAULT_OWNER_GID),
|
|
)
|
|
conn.close()
|
|
return Response({"status": "Volume: " + ret + " is created"})
|
|
else:
|
|
return Response({"status": "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: StatusSerializer})
|
|
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
|
|
)
|
|
|
|
if conn.is_active():
|
|
conn.del_volume(pk)
|
|
conn.close()
|
|
return Response({"status": "Volume: " + pk + " is deleted"})
|
|
else:
|
|
return Response({"status": "Pool is not active"}, status=status.HTTP_409_CONFLICT)
|