11import logging
2+ import threading
23from django .db import models
34from django .conf import settings
4- from django .shortcuts import get_object_or_404
55from django .contrib .auth import get_user_model
66from django .db .models import Q
7+ from django .http import Http404
78
89from api .exceptions import ServiceUnavailable
910from scheduler import KubeException
1011
11- from .base import AuditedModel , DEFAULT_HTTP_PORT , DEFAULT_HTTPS_PORT , PTYPE_MAX_LENGTH
12+ from .base import AuditedModel , DEFAULT_HTTP_PORT , DEFAULT_HTTPS_PORT
1213
1314User = get_user_model ()
1415logger = logging .getLogger (__name__ )
@@ -167,6 +168,7 @@ class Meta:
167168
168169
169170class Route (AuditedModel ):
171+ CACHE = threading .local ()
170172 PROTOCOLS_CHOICES = {
171173 "TLSRoute" : ("TCP" , ),
172174 "TCPRoute" : ("TCP" , ),
@@ -180,11 +182,21 @@ class Route(AuditedModel):
180182 kind = models .CharField (max_length = 15 , choices = [
181183 (key , '/' .join (value )) for key , value in PROTOCOLS_CHOICES .items ()])
182184 name = models .CharField (max_length = 63 , db_index = True )
183- port = models .PositiveIntegerField ()
184185 rules = models .JSONField (default = list )
185186 routable = models .BooleanField (default = True )
186187 parent_refs = models .JSONField (default = list )
187- ptype = models .CharField (max_length = PTYPE_MAX_LENGTH )
188+
189+ @property
190+ def services (self ):
191+ key = f"{ self .app .id } _{ self .name } "
192+ if not hasattr (self .CACHE , key ):
193+ service_names = set ()
194+ for rule in self .rules :
195+ for backend in rule ['backendRefs' ]:
196+ service_names .add (backend ['name' ])
197+ setattr (self .CACHE , key ,
198+ [s for s in self .app .service_set .all () if s .name in service_names ])
199+ return getattr (self .CACHE , key )
188200
189201 @property
190202 def protocols (self ):
@@ -195,30 +207,47 @@ def protocols(self):
195207 @property
196208 def hostnames (self ):
197209 return [domain .domain for domain in self .app .domain_set .filter (
198- ptype = self .ptype )]
210+ ptype__in = [s .ptype for s in self .services ])]
211+
212+ @property
213+ def cleaned_rules (self ):
214+ services , rules = self .services , []
215+ for rule in self .rules :
216+ backend_refs = []
217+ for backend_ref in rule ["backendRefs" ]:
218+ for service in services :
219+ ports = [item ["port" ] for item in service .ports ]
220+ if backend_ref ["port" ] in ports and backend_ref ["name" ] == service .name :
221+ backend_refs .append (backend_ref )
222+ if backend_refs :
223+ rule ['backendRefs' ] = backend_refs
224+ rules .append (rule )
225+ return rules
199226
200227 @property
201228 def tls_force_hostnames (self ):
202229 tls = self .app .tls_set .latest ()
203- q = Q (ptype = self .ptype )
230+ q = Q (ptype__int = [ s .ptype for s in self . services ] )
204231 if not tls .certs_auto_enabled :
205232 q &= Q (certificate__isnull = False )
206233 domains = self .app .domain_set .filter (q )
207234 return [domain .domain for domain in domains ]
208235
209- @property
210- def default_rules (self ):
211- service = get_object_or_404 (self .app .service_set , ptype = self .ptype )
212- backend_refs = []
213- for item in service .ports :
214- if item ["port" ] == self .port :
215- backend_refs .append ({
216- "kind" : "Service" ,
217- "name" : str (service ),
218- "port" : item ["port" ],
219- "weight" : 100 ,
220- })
221- return [{"backendRefs" : backend_refs }]
236+ def check_rules (self , rules ):
237+ for rule in rules :
238+ for backend_ref in rule ["backendRefs" ]:
239+ kind = backend_ref .get ("kind" , "Service" )
240+ if kind != "Service" :
241+ return False , {"detail" : "BackendRef only supports service kind" }
242+ has_service = False
243+ for service in self .services :
244+ ports = [item ["port" ] for item in service .ports ]
245+ if backend_ref ["port" ] in ports and backend_ref ["name" ] == service .name :
246+ has_service = True
247+ break
248+ if not has_service :
249+ return False , {"detail" : f"service { backend_ref ['name' ]} not exists" }
250+ return True , ""
222251
223252 def log (self , message , level = logging .INFO ):
224253 """Logs a message in the context of this service.
@@ -231,17 +260,6 @@ def log(self, message, level=logging.INFO):
231260 """
232261 logger .log (level , "[{}]: {}" .format (self .app .id , message ))
233262
234- def check_rules (self ):
235- service = self .app .service_set .filter (
236- ptype = self .ptype ).first ()
237- ports = [item ["port" ] for item in service .ports ]
238- for rule in self .rules :
239- for backend_ref in rule ["backendRefs" ]:
240- port = backend_ref ["port" ]
241- if port not in ports or backend_ref ["name" ] != str (service ):
242- return False , {"detail" : "backendRefs associated with incorrect service" }
243- return True , ""
244-
245263 def refresh_to_k8s (self ):
246264 if self .routable :
247265 parent_refs , http_parent_refs = self ._get_all_parent_refs ()
@@ -290,9 +308,12 @@ def detach(self, gateway_name, port):
290308
291309 def save (self , * args , ** kwargs ):
292310 self .change_default_tls ()
293- ok , msg = self .check_rules ()
294- if not ok :
295- raise ValueError (msg )
311+ cleaned_rules = self .cleaned_rules
312+ if not cleaned_rules :
313+ msg = f"route { self .name } no available backend"
314+ self .log (msg , level = logging .ERROR )
315+ raise Http404 (msg )
316+ self .rules = cleaned_rules
296317 super ().save (* args , ** kwargs )
297318 self .refresh_to_k8s ()
298319
0 commit comments