1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2026-11-08 11:16:47 +00:00

fix(api): a volume's size is GiB to create and bytes to read, each documented

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.
This commit is contained in:
catborise 2026-11-07 00:19:55 +03:00
parent 0610c23a34
commit 470cc9860e
3 changed files with 30 additions and 10 deletions

View file

@ -7,7 +7,7 @@ class StoragesSerializer(serializers.Serializer):
name = serializers.CharField(max_length=20)
status = serializers.IntegerField()
type = serializers.CharField(max_length=100)
size = serializers.IntegerField()
size = serializers.IntegerField(help_text="Capacity in bytes")
volumes = serializers.IntegerField()
@ -15,8 +15,8 @@ class StorageSerializer(serializers.Serializer):
"""A storage pool's details."""
state = serializers.IntegerField()
size = serializers.IntegerField()
free = serializers.IntegerField()
size = serializers.IntegerField(help_text="Capacity in bytes")
free = serializers.IntegerField(help_text="Free space in bytes")
status = serializers.CharField(max_length=128)
path = serializers.CharField(max_length=128)
type = serializers.CharField(max_length=128)
@ -26,10 +26,20 @@ class StorageSerializer(serializers.Serializer):
class VolumeSerializer(serializers.Serializer):
# what the web form (storages.forms.CreateVolumeForm) takes
"""A volume as the API reads it."""
name = serializers.CharField()
type = serializers.CharField()
allocation = serializers.IntegerField(help_text="Bytes the volume takes in its pool")
size = serializers.IntegerField(help_text="Capacity in bytes")
class CreateVolumeSerializer(serializers.Serializer):
"""A new volume, as the web form (storages.forms.CreateVolumeForm) takes it:
its size in GiB, where a volume read back tells bytes."""
name = serializers.RegexField(r"^[a-zA-Z0-9._-]+$", max_length=120)
type = serializers.ChoiceField(choices=[("qcow2", "qcow2 (recommended)"), ("qcow", "qcow"), ("raw", "raw")],
label="Format")
allocation = serializers.IntegerField(read_only=True)
size = serializers.IntegerField(min_value=1) # GiB
meta_prealloc = serializers.BooleanField(write_only=True, default=False)
size = serializers.IntegerField(min_value=1, help_text="Capacity in GiB")
meta_prealloc = serializers.BooleanField(default=False)

View file

@ -15,7 +15,7 @@ from rest_framework.response import Response
from vrtManager.storage import wvmStorage, wvmStorages
from webvirtcloud.serializers import ErrorSerializer, StatusSerializer
from .serializers import StorageSerializer, StoragesSerializer, VolumeSerializer
from .serializers import CreateVolumeSerializer, StorageSerializer, StoragesSerializer, VolumeSerializer
class StorageViewSet(viewsets.ViewSet):
@ -170,7 +170,7 @@ class VolumeViewSet(viewsets.ViewSet):
return Response(serializer.data)
@extend_schema(responses={200: StatusSerializer, 400: dict, 409: ErrorSerializer})
@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)
@ -178,7 +178,7 @@ class VolumeViewSet(viewsets.ViewSet):
compute.hostname, compute.login, compute.password, compute.type, storage_pk
)
serializer = VolumeSerializer(data=request.data)
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()

View file

@ -36,6 +36,16 @@ class APISchemaTestCase(TestCase):
self.assertEqual(getattr(client, method)("/api/v1/nothere/").status_code, 404)
self.assertEqual(client.get("/api/schema/").status_code, 200)
def test_a_volume_s_size_is_gib_to_create_and_bytes_to_read(self):
# one serializer had both: a size read back and sent again asked 2**30 times as much
schema = SchemaGenerator().get_schema(request=None, public=True)
create = schema["paths"]["/api/v1/computes/{compute_pk}/storages/{storage_pk}/volumes/"]["post"]
self.assertEqual(create["requestBody"]["content"]["application/json"]["schema"]["$ref"],
"#/components/schemas/CreateVolume")
components = schema["components"]["schemas"]
self.assertEqual(components["CreateVolume"]["properties"]["size"]["description"], "Capacity in GiB")
self.assertEqual(components["Volume"]["properties"]["size"]["description"], "Capacity in bytes")
def test_a_refusal_s_body_is_documented_with_its_409(self):
schema = SchemaGenerator().get_schema(request=None, public=True)
poweron = schema["paths"]["/api/v1/computes/{compute_pk}/instances/{id}/poweron/"]["post"]