1818from api import models
1919from api .exceptions import DryccException
2020from api .schemas .rules import SCHEMA as RULES_SCHEMA
21+ from api .schemas .volumes import SCHEMA as VOLUMES_SCHEMA
2122from api .schemas .autoscale import SCHEMA as AUTOSCALE_SCHEMA
2223from api .schemas .healthcheck import SCHEMA as HEALTHCHECK_SCHEMA
2324
2425
2526User = get_user_model ()
2627logger = logging .getLogger (__name__ )
2728SERVICE_PROTOCOL_MATCH = re .compile (r'^(TCP|UDP|SCTP)$' )
28- SERVICE_PROTOCOL_MISMATCH_MSG = "the service protocol only supports TCP, UDP, and SCTP"
29+ SERVICE_PROTOCOL_MISMATCH_MSG = (
30+ "the service protocol only supports: %s" % SERVICE_PROTOCOL_MATCH .pattern )
2931GATEWAY_PROTOCOL_MATCH = re .compile (r'^(HTTP|HTTPS|TCP|TLS|UDP)$' )
30- GATEWAY_PROTOCOL_MISMATCH_MSG = "the gateway protocol only supports HTTP, HTTPS, TCP, TLS and UDP"
32+ GATEWAY_PROTOCOL_MISMATCH_MSG = (
33+ "the gateway protocol only supports: %s" % GATEWAY_PROTOCOL_MATCH .pattern )
3134ROUTE_PROTOCOL_MATCH = re .compile (r'^(HTTPRoute|TCPRoute|UDPRoute|TLSRoute)$' )
32- ROUTE_PROTOCOL_MISMATCH_MSG = "the route kind only supports HTTPRoute, TCPRoute, UDPRoute, and TLSRoute" # noqa
35+ ROUTE_PROTOCOL_MISMATCH_MSG = (
36+ "the route kind only supports: %s" % ROUTE_PROTOCOL_MATCH .pattern )
3337PROCTYPE_MATCH = re .compile (r'^(?P<type>[a-z0-9]+(\-[a-z0-9]+)*)(?<!-canary)$' )
34- PROCTYPE_MISMATCH_MSG = "Process types can only contain lowercase alphanumeric characters"
38+ PROCTYPE_MISMATCH_MSG = "Process types can only supports: %s" % PROCTYPE_MATCH . pattern
3539MEMLIMIT_MATCH = re .compile (r'^(?P<mem>([1-9][0-9]*[mgMG]))$' , re .IGNORECASE )
40+ MEMLIMIT_MISMATCH_MSG = (
41+ "Memory limit format: <number><unit>, "
42+ "where unit = M or G"
43+ )
3644CPUSHARE_MATCH = re .compile (r'^(?P<cpu>([-+]?[1-9][0-9]*[m]?))$' )
45+ CPUSHARE_MISMATCH_MSG = "CPU limit format: <value>, where value must be a numeric"
3746TAGVAL_MATCH = re .compile (r'^(?:[a-zA-Z\d][-\.\w]{0,61})?[a-zA-Z\d]$' )
3847CONFIGKEY_MATCH = re .compile (r'^[a-z_]+[a-z0-9_]*$' , re .IGNORECASE )
48+ CONFIGKEY_MISMATCH_MSG = (
49+ "Config keys must start with a letter or underscore and "
50+ "only contain [A-z0-9_]"
51+ )
52+
3953TERMINATION_GRACE_PERIOD_MATCH = re .compile (r'^[0-9]*$' )
54+ TERMINATION_GRACE_PERIOD_MISMATCH_MSG = (
55+ "Termination Grace Period format: %s" % TERMINATION_GRACE_PERIOD_MATCH .pattern )
56+ VOLUME_TYPE_MATCH = re .compile (r'^(csi|nfs)$' )
57+ VOLUME_TYPE_MISMATCH_MSG = "Volume type pattern: %s" % VOLUME_TYPE_MATCH .pattern
4058VOLUME_SIZE_MATCH = re .compile (r'^(?P<volume>([1-9][0-9]*[gG]))$' , re .IGNORECASE )
41- VOLUME_PATH = re .compile (r'^\/(\w+\/?)+$' , re .IGNORECASE )
42- METRIC_EVERY = re .compile (r'^[1-9][0-9]*m$' )
59+ VOLUME_SIZE_MISMATCH_MSG = (
60+ "Volume size limit format: <number><unit> or <number><unit>/<number><unit>, "
61+ "where unit = G, range: %sG~%sG"
62+ ) % (settings .KUBERNETES_LIMITS_MAX_VOLUME , settings .KUBERNETES_LIMITS_MIN_VOLUME )
63+ VOLUME_PATH_MATCH = re .compile (r'^\/(\w+\/?)+$' , re .IGNORECASE )
64+ METRIC_EVERY_MATCH = re .compile (r'^[1-9][0-9]*m$' )
4365
4466
4567class JSONFieldSerializer (serializers .JSONField ):
@@ -179,9 +201,7 @@ def validate_values(data):
179201 continue
180202
181203 if not re .match (CONFIGKEY_MATCH , key ):
182- raise serializers .ValidationError (
183- "Config keys must start with a letter or underscore and "
184- "only contain [A-z0-9_]" )
204+ raise serializers .ValidationError (CONFIGKEY_MISMATCH_MSG )
185205
186206 # Validate PORT
187207 if key == 'PORT' :
@@ -231,9 +251,7 @@ def validate_memory(data):
231251 raise serializers .ValidationError (PROCTYPE_MISMATCH_MSG )
232252
233253 if not re .match (MEMLIMIT_MATCH , str (value )):
234- raise serializers .ValidationError (
235- "Memory limit format: <number><unit>, "
236- "where unit = M or G" )
254+ raise serializers .ValidationError (MEMLIMIT_MISMATCH_MSG )
237255 range_error = "Memory setting is not in allowed range: %sM~%sM" % (
238256 min_memory , max_memory )
239257 memory_size = int (value [:- 1 ]) * 1024 if value .endswith ("G" ) else int (value [:- 1 ])
@@ -255,8 +273,7 @@ def validate_cpu(data):
255273
256274 shares = re .match (CPUSHARE_MATCH , str (value ))
257275 if not shares :
258- raise serializers .ValidationError (
259- "CPU limit format: <value>, where value must be a numeric" )
276+ raise serializers .ValidationError (CPUSHARE_MISMATCH_MSG )
260277 range_error = "CPU setting is not in allowed range: %sm~%sm" % (
261278 min_cpu , max_cpu )
262279 cpu_size = int (value ) * 1000 if value .isdigit () else int (value [:- 1 ])
@@ -322,9 +339,7 @@ def validate_registry(data):
322339 continue
323340
324341 if not re .match (CONFIGKEY_MATCH , key ):
325- raise serializers .ValidationError (
326- "Config keys must start with a letter or underscore and "
327- "only contain [A-z0-9_]" )
342+ raise serializers .ValidationError (CONFIGKEY_MISMATCH_MSG )
328343
329344 return data
330345
@@ -560,28 +575,27 @@ class VolumeSerializer(serializers.ModelSerializer):
560575 app = serializers .SlugRelatedField (slug_field = 'id' , queryset = models .app .App .objects .all ())
561576 owner = serializers .ReadOnlyField (source = 'owner.username' )
562577 name = serializers .CharField ()
563- size = serializers .CharField ()
578+ size = serializers .CharField (required = False )
564579 path = JSONFieldSerializer (required = False , binary = True )
580+ type = serializers .CharField (required = False )
581+ parameters = serializers .JSONField (required = False )
565582
566583 class Meta :
567584 """Metadata options for a :class:`AppVolumeSerializer`."""
568585 model = models .volume .Volume
569586 fields = '__all__'
570587
571- @ staticmethod
572- def validate_size ( data ):
588+ def validate_size ( self , data ):
589+ # check size format
573590 if not re .match (VOLUME_SIZE_MATCH , data ):
574- raise serializers .ValidationError (
575- "Volume size limit format: <number><unit> or <number><unit>/<number><unit>, "
576- "where unit = G" )
591+ raise serializers .ValidationError (VOLUME_SIZE_MISMATCH_MSG )
592+ # check volume size
593+ volume_size = int ( data [: - 1 ] )
577594 max_volume = settings .KUBERNETES_LIMITS_MAX_VOLUME
578595 # The minimum limit memory is equal to the memory allocated by default
579596 min_volume = settings .KUBERNETES_LIMITS_MIN_VOLUME
580- range_error = "Volume setting is not in allowed range: %sG~%sG" % (
581- min_volume , max_volume )
582- volume_size = int (data [:- 1 ])
583597 if volume_size < min_volume or volume_size > max_volume :
584- raise serializers .ValidationError (range_error )
598+ raise serializers .ValidationError (VOLUME_SIZE_MISMATCH_MSG )
585599 return data .upper ()
586600
587601 @staticmethod
@@ -595,7 +609,7 @@ def validate_path(data):
595609 new_data [key ] = value
596610 continue
597611
598- if not re .match (VOLUME_PATH , str (value )):
612+ if not re .match (VOLUME_PATH_MATCH , str (value )):
599613 raise serializers .ValidationError (
600614 "Volume path format: /path" )
601615 if value .endswith ("/" ):
@@ -604,6 +618,23 @@ def validate_path(data):
604618 logger .debug (f"mount validate_path new_data: { new_data } " )
605619 return new_data
606620
621+ def validate_type (self , data ):
622+ if not re .match (VOLUME_TYPE_MATCH , data ):
623+ raise serializers .ValidationError (VOLUME_TYPE_MISMATCH_MSG )
624+ elif data != "csi" and not self .initial_data .get ("parameters" , None ):
625+ raise serializers .ValidationError (
626+ "parameters cannot be empty when the type is not csi." )
627+ return data
628+
629+ @staticmethod
630+ def validate_parameters (data ):
631+ try :
632+ jsonschema .validate (data , VOLUMES_SCHEMA )
633+ except jsonschema .ValidationError as e :
634+ raise serializers .ValidationError (
635+ "could not validate {}: {}" .format (data , e .message ))
636+ return data
637+
607638
608639class ResourceSerializer (serializers .ModelSerializer ):
609640 """Serialize a :class:`~api.models.resource.Resource` model."""
@@ -641,9 +672,9 @@ class MetricSerializer(serializers.Serializer):
641672 every = serializers .CharField (max_length = 50 , required = False , default = '5m' )
642673
643674 def validate (self , attrs ):
644- if not re .match (METRIC_EVERY , attrs ["every" ]):
675+ if not re .match (METRIC_EVERY_MATCH , attrs ["every" ]):
645676 raise serializers .ValidationError (
646- "The format of every is:%s" % METRIC_EVERY .pattern
677+ "The format of every is:%s" % METRIC_EVERY_MATCH .pattern
647678 )
648679 interval = attrs .get ("stop" ) - attrs .get ("start" )
649680 if interval < 0 or interval > 3600 * 24 :
0 commit comments