55
66use gateway_api:: gatewayclasses:: GatewayClass ;
77use gateway_api:: gateways:: Gateway ;
8- use kube:: api:: { Api , Patch , PatchParams } ;
8+ use gateway_api:: httproutes:: HTTPRoute ;
9+ use kube:: api:: { Api , ListParams , Patch , PatchParams } ;
910use kube:: runtime:: controller:: Action ;
1011use kube:: { Client , ResourceExt } ;
1112use serde_json:: json;
@@ -50,16 +51,10 @@ impl GatewayReconciler {
5051
5152 let generation = gateway. metadata . generation . unwrap_or ( 0 ) ;
5253
53- // Skip if we've already programmed this generation
54- if is_already_programmed ( & gateway, generation) {
55- debug ! (
56- name = %name,
57- namespace = %namespace,
58- generation,
59- "Gateway already programmed at this generation"
60- ) ;
61- return Ok ( Action :: await_change ( ) ) ;
62- }
54+ // Note: we do NOT skip based on generation alone here because
55+ // attached route counts can change without the Gateway's generation
56+ // changing (HTTPRoutes are separate resources). We always recompute
57+ // status but use a requeue interval to avoid tight loops.
6358
6459 info ! (
6560 name = %name,
@@ -69,10 +64,13 @@ impl GatewayReconciler {
6964 "Reconciling Gateway"
7065 ) ;
7166
72- // Update Gateway status
67+ // Update Gateway status with listener info and attached route counts
7368 self . update_status ( & gateway, & namespace) . await ?;
7469
75- Ok ( Action :: await_change ( ) )
70+ // Requeue periodically to pick up changes in attached route counts
71+ // (HTTPRoutes are separate resources, their changes don't trigger
72+ // Gateway reconciliation directly)
73+ Ok ( Action :: requeue ( std:: time:: Duration :: from_secs ( 10 ) ) )
7674 }
7775
7876 /// Check if a GatewayClass name belongs to our controller.
@@ -88,6 +86,41 @@ impl GatewayReconciler {
8886 }
8987 }
9088
89+ /// Count attached HTTPRoutes per listener for this Gateway.
90+ async fn count_attached_routes (
91+ & self ,
92+ gw_name : & str ,
93+ gw_namespace : & str ,
94+ ) -> Result < std:: collections:: HashMap < String , i32 > , GatewayError > {
95+ let route_api: Api < HTTPRoute > = Api :: all ( self . client . clone ( ) ) ;
96+ let routes = route_api. list ( & ListParams :: default ( ) ) . await ?;
97+
98+ let mut counts: std:: collections:: HashMap < String , i32 > =
99+ std:: collections:: HashMap :: new ( ) ;
100+
101+ for route in & routes. items {
102+ let parent_refs = route. spec . parent_refs . as_ref ( ) ;
103+ if let Some ( refs) = parent_refs {
104+ for pr in refs {
105+ let route_ns = route. namespace ( ) . unwrap_or_default ( ) ;
106+ let pr_ns = pr. namespace . as_deref ( ) . unwrap_or ( & route_ns) ;
107+ if pr. name == gw_name && pr_ns == gw_namespace {
108+ // If sectionName is specified, count for that listener
109+ // Otherwise, count for all listeners
110+ if let Some ( ref section) = pr. section_name {
111+ * counts. entry ( section. clone ( ) ) . or_insert ( 0 ) += 1 ;
112+ } else {
113+ // Attach to all listeners (use empty string as wildcard key)
114+ * counts. entry ( String :: new ( ) ) . or_insert ( 0 ) += 1 ;
115+ }
116+ }
117+ }
118+ }
119+ }
120+
121+ Ok ( counts)
122+ }
123+
91124 /// Update Gateway status conditions and addresses.
92125 async fn update_status (
93126 & self ,
@@ -98,15 +131,25 @@ impl GatewayReconciler {
98131 let generation = gateway. metadata . generation . unwrap_or ( 0 ) ;
99132 let now = chrono:: Utc :: now ( ) . to_rfc3339 ( ) ;
100133
134+ // Count attached routes per listener
135+ let attached_counts = self . count_attached_routes ( & name, namespace) . await ?;
136+ let wildcard_count = attached_counts. get ( "" ) . copied ( ) . unwrap_or ( 0 ) ;
137+
101138 // Build listener statuses
102139 let listener_statuses: Vec < serde_json:: Value > = gateway
103140 . spec
104141 . listeners
105142 . iter ( )
106143 . map ( |l| {
144+ let listener_count = attached_counts
145+ . get ( & l. name )
146+ . copied ( )
147+ . unwrap_or ( 0 )
148+ + wildcard_count;
149+
107150 json ! ( {
108151 "name" : l. name,
109- "attachedRoutes" : 0 ,
152+ "attachedRoutes" : listener_count ,
110153 "supportedKinds" : supported_route_kinds( & l. protocol) ,
111154 "conditions" : [ {
112155 "type" : "Accepted" ,
@@ -122,6 +165,13 @@ impl GatewayReconciler {
122165 "message" : "Listener programmed in Zentinel" ,
123166 "observedGeneration" : generation,
124167 "lastTransitionTime" : now,
168+ } , {
169+ "type" : "ResolvedRefs" ,
170+ "status" : "True" ,
171+ "reason" : "ResolvedRefs" ,
172+ "message" : "All references resolved" ,
173+ "observedGeneration" : generation,
174+ "lastTransitionTime" : now,
125175 } ]
126176 } )
127177 } )
@@ -140,7 +190,7 @@ impl GatewayReconciler {
140190 "type" : "Programmed" ,
141191 "status" : "True" ,
142192 "reason" : "Programmed" ,
143- "message" : "Gateway programmed — listeners active" ,
193+ "message" : "Gateway programmed, listeners active" ,
144194 "observedGeneration" : generation,
145195 "lastTransitionTime" : now,
146196 } ] ,
@@ -170,28 +220,6 @@ impl GatewayReconciler {
170220 }
171221}
172222
173- /// Check if the Gateway status already has Accepted=True and Programmed=True
174- /// for the current generation.
175- fn is_already_programmed ( gw : & Gateway , generation : i64 ) -> bool {
176- let Some ( ref status) = gw. status else {
177- return false ;
178- } ;
179- let Some ( ref conditions) = status. conditions else {
180- return false ;
181- } ;
182- let has_accepted = conditions. iter ( ) . any ( |c| {
183- c. type_ == "Accepted"
184- && c. status == "True"
185- && c. observed_generation == Some ( generation)
186- } ) ;
187- let has_programmed = conditions. iter ( ) . any ( |c| {
188- c. type_ == "Programmed"
189- && c. status == "True"
190- && c. observed_generation == Some ( generation)
191- } ) ;
192- has_accepted && has_programmed
193- }
194-
195223/// Return the supported route kinds for a given listener protocol.
196224fn supported_route_kinds ( protocol : & str ) -> Vec < serde_json:: Value > {
197225 match protocol {
0 commit comments