@@ -240,58 +240,6 @@ pub async fn reconcile(replica: Arc<PostgresPhysicalReplica>, ctx: Arc<Context>)
240240
241241 ctx. metrics . switchovers_total . inc ( ) ;
242242
243- // Trigger FDW setup in overlay database if configured
244- if replica. spec . overlay_database . is_some ( ) {
245- let pg_version = replica
246- . status
247- . as_ref ( )
248- . and_then ( |s| s. overlay_postgres_version . clone ( ) )
249- . unwrap_or_else ( || "17" . to_string ( ) ) ;
250- let old_restore = replica
251- . status
252- . as_ref ( )
253- . and_then ( |s| s. overlay_fdw_restore . as_deref ( ) ) ;
254-
255- match overlay:: run_fdw_setup (
256- client,
257- & namespace,
258- & replica,
259- & switching_name,
260- & pg_version,
261- old_restore,
262- )
263- . await
264- {
265- Ok ( ( ) ) => {
266- info ! (
267- replica = name,
268- restore = switching_name,
269- "FDW setup job created for overlay switchover"
270- ) ;
271- let patch = serde_json:: json!( {
272- "status" : {
273- "overlayFdwRestore" : switching_name,
274- }
275- } ) ;
276- replicas
277- . patch_status (
278- & name,
279- & PatchParams :: apply ( "postgres-restore-operator" ) ,
280- & Patch :: Merge ( & patch) ,
281- )
282- . await ?;
283- }
284- Err ( e) => {
285- warn ! (
286- replica = name,
287- restore = switching_name,
288- error = %e,
289- "failed to create FDW setup job for overlay"
290- ) ;
291- }
292- }
293- }
294-
295243 // Send notifications
296244 send_restore_notifications (
297245 client,
@@ -322,29 +270,106 @@ pub async fn reconcile(replica: Arc<PostgresPhysicalReplica>, ctx: Arc<Context>)
322270 return Ok ( Action :: requeue ( Duration :: from_secs ( 10 ) ) ) ;
323271 }
324272
325- // 8. Clean up old restores after grace period
326- // Safety check: if the previous restore's schemas are still imported in the overlay,
327- // they should have been swapped out by the switchover step above.
328- if replica. spec . overlay_database . is_some ( )
329- && let ( Some ( prev_name) , Some ( fdw_restore) ) = (
330- replica
331- . status
332- . as_ref ( )
333- . and_then ( |s| s. previous_restore . as_ref ( ) ) ,
334- replica
335- . status
336- . as_ref ( )
337- . and_then ( |s| s. overlay_fdw_restore . as_ref ( ) ) ,
338- ) && prev_name == fdw_restore
339- {
340- warn ! (
341- replica = name,
342- previous_restore = prev_name,
343- fdw_restore = fdw_restore,
344- "previous restore still has FDW schemas imported — switchover may not have completed FDW swap"
345- ) ;
273+ // 7b. Ensure FDW setup is current for overlay databases.
274+ // On each reconciliation, if currentRestore != overlayFdwRestore, we drive
275+ // the fdw-setup Job to completion, retrying on failure.
276+ if replica. spec . overlay_database . is_some ( ) {
277+ let current_restore = replica
278+ . status
279+ . as_ref ( )
280+ . and_then ( |s| s. current_restore . as_ref ( ) ) ;
281+ let fdw_restore = replica
282+ . status
283+ . as_ref ( )
284+ . and_then ( |s| s. overlay_fdw_restore . as_ref ( ) ) ;
285+
286+ if let Some ( current) = current_restore
287+ && fdw_restore. as_ref ( ) != Some ( & current)
288+ {
289+ let job_name = format ! ( "{name}-fdw-setup" ) ;
290+ let jobs: Api < Job > = Api :: namespaced ( client. clone ( ) , & namespace) ;
291+
292+ match jobs. get_opt ( & job_name) . await ? {
293+ Some ( job) => {
294+ let succeeded = job. status . as_ref ( ) . and_then ( |s| s. succeeded ) . unwrap_or ( 0 ) ;
295+ let failed = job. status . as_ref ( ) . and_then ( |s| s. failed ) . unwrap_or ( 0 ) ;
296+ let backoff_limit =
297+ job. spec . as_ref ( ) . and_then ( |s| s. backoff_limit ) . unwrap_or ( 3 ) ;
298+
299+ if succeeded > 0 {
300+ info ! ( replica = name, restore = current, "FDW setup job succeeded" ) ;
301+ if let Err ( e) = jobs. delete ( & job_name, & Default :: default ( ) ) . await {
302+ warn ! ( job = job_name, error = %e, "failed to delete completed FDW setup job" ) ;
303+ }
304+ let replicas: Api < PostgresPhysicalReplica > =
305+ Api :: namespaced ( client. clone ( ) , & namespace) ;
306+ let patch = serde_json:: json!( {
307+ "status" : {
308+ "overlayFdwRestore" : current,
309+ }
310+ } ) ;
311+ replicas
312+ . patch_status (
313+ & name,
314+ & PatchParams :: apply ( "postgres-restore-operator" ) ,
315+ & Patch :: Merge ( & patch) ,
316+ )
317+ . await ?;
318+ } else if failed > backoff_limit {
319+ warn ! (
320+ replica = name,
321+ restore = current,
322+ "FDW setup job failed, deleting for retry"
323+ ) ;
324+ let dp = kube:: api:: DeleteParams {
325+ propagation_policy : Some ( kube:: api:: PropagationPolicy :: Background ) ,
326+ ..Default :: default ( )
327+ } ;
328+ if let Err ( e) = jobs. delete ( & job_name, & dp) . await {
329+ warn ! ( job = job_name, error = %e, "failed to delete failed FDW setup job" ) ;
330+ }
331+ return Ok ( Action :: requeue ( Duration :: from_secs ( 30 ) ) ) ;
332+ } else {
333+ return Ok ( Action :: requeue ( Duration :: from_secs ( 10 ) ) ) ;
334+ }
335+ }
336+ None => {
337+ let pg_version = replica
338+ . status
339+ . as_ref ( )
340+ . and_then ( |s| s. overlay_postgres_version . clone ( ) )
341+ . unwrap_or_else ( || "17" . to_string ( ) ) ;
342+
343+ match overlay:: run_fdw_setup (
344+ client,
345+ & namespace,
346+ & replica,
347+ current,
348+ & pg_version,
349+ fdw_restore. map ( |s| s. as_str ( ) ) ,
350+ )
351+ . await
352+ {
353+ Ok ( ( ) ) => {
354+ info ! ( replica = name, restore = current, "created FDW setup job" ) ;
355+ }
356+ Err ( e) => {
357+ warn ! (
358+ replica = name,
359+ restore = current,
360+ error = %e,
361+ "failed to create FDW setup job"
362+ ) ;
363+ }
364+ }
365+ return Ok ( Action :: requeue ( Duration :: from_secs ( 10 ) ) ) ;
366+ }
367+ }
368+ }
346369 }
347370
371+ // 8. Clean up old restores after grace period
372+
348373 if let Some ( prev_name) = replica
349374 . status
350375 . as_ref ( )
0 commit comments