import math from datetime import timedelta from django.utils import timezone from rest_framework import serializers KINDS=['metric','flower-status','collector-status','worker-online','worker-offline','worker-heartbeat','task-sent','task-received','task-started','task-succeeded','task-failed','task-retried','task-revoked','task-rejected'] class MetricPayload(serializers.Serializer): cpu=serializers.FloatField(min_value=0,max_value=100) ram=serializers.FloatField(min_value=0,max_value=100) disk=serializers.FloatField(min_value=0,max_value=100) boot_id=serializers.CharField(max_length=100,required=False) services=serializers.DictField(child=serializers.BooleanField(),required=False) outbox=serializers.IntegerField(min_value=0,required=False) class WorkerPayload(serializers.Serializer): hostname=serializers.CharField(max_length=255) active=serializers.IntegerField(min_value=0,required=False,default=0) processed=serializers.IntegerField(min_value=0,required=False,default=0) class TaskPayload(serializers.Serializer): uuid=serializers.CharField(max_length=200) name=serializers.CharField(max_length=255,required=False,allow_blank=True) hostname=serializers.CharField(max_length=255,required=False,allow_blank=True) runtime=serializers.FloatField(min_value=0,required=False,allow_null=True) retries=serializers.IntegerField(min_value=0,required=False,default=0) class StatusPayload(serializers.Serializer): online=serializers.BooleanField() checked_at=serializers.DateTimeField(required=False) class Envelope(serializers.Serializer): id=serializers.UUIDField() kind=serializers.ChoiceField(choices=KINDS) occurred_at=serializers.DateTimeField() payload=serializers.DictField() def validate(self,data): if data['occurred_at']>timezone.now()+timedelta(minutes=5): raise serializers.ValidationError('Zeitstempel liegt zu weit in der Zukunft') cls=MetricPayload if data['kind']=='metric' else WorkerPayload if data['kind'].startswith('worker-') else TaskPayload if data['kind'].startswith('task-') else StatusPayload s=cls(data=data['payload']); s.is_valid(raise_exception=True) for field in ('cpu','ram','disk','runtime'): value=s.validated_data.get(field) if value is not None and not math.isfinite(value): raise serializers.ValidationError('Messwerte müssen endlich sein') services=s.validated_data.get('services',{}) if len(services)>20 or any(len(k)>50 for k in services): raise serializers.ValidationError('Maximal 20 Dienste mit Namen bis 50 Zeichen') # Use normalized validated values, never the original loosely typed input. normalized=dict(s.data) if any(isinstance(v,float) and not math.isfinite(v) for v in normalized.values()): raise serializers.ValidationError('Messwerte müssen endlich sein') data['payload']={key:value for key,value in normalized.items() if key in data['payload']} return data class Batch(serializers.Serializer): events=Envelope(many=True,allow_empty=False,max_length=500) class ConfigurationSerializer(serializers.Serializer): retention_days=serializers.IntegerField(min_value=7,max_value=3650) heartbeat_timeout=serializers.IntegerField(min_value=30,max_value=3600) cpu_warning=serializers.IntegerField(min_value=1,max_value=100) ram_warning=serializers.IntegerField(min_value=1,max_value=100) disk_warning=serializers.IntegerField(min_value=1,max_value=100)