Skip to content

Commit 4ca4459

Browse files
committed
fix: properly reconcile fdw status
1 parent 3cd7256 commit 4ca4459

2 files changed

Lines changed: 98 additions & 101 deletions

File tree

src/controllers/overlay.rs

Lines changed: 0 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -684,34 +684,6 @@ pub async fn run_fdw_setup(
684684
Ok(())
685685
}
686686

687-
/// Check if an FDW setup job has completed.
688-
///
689-
/// Returns `Some(true)` if succeeded, `Some(false)` if failed, `None` if still running.
690-
pub async fn check_fdw_setup_job(
691-
client: &Client,
692-
namespace: &str,
693-
replica_name: &str,
694-
) -> Option<bool> {
695-
let job_name = format!("{replica_name}-fdw-setup");
696-
let jobs: Api<Job> = Api::namespaced(client.clone(), namespace);
697-
698-
let job = jobs.get(&job_name).await.ok()?;
699-
let status = job.status.as_ref()?;
700-
701-
let succeeded = status.succeeded.unwrap_or(0);
702-
if succeeded > 0 {
703-
return Some(true);
704-
}
705-
706-
let failed = status.failed.unwrap_or(0);
707-
let backoff_limit = job.spec.as_ref().and_then(|s| s.backoff_limit).unwrap_or(3);
708-
if failed > backoff_limit {
709-
return Some(false);
710-
}
711-
712-
None
713-
}
714-
715687
#[cfg(test)]
716688
mod tests {
717689
use super::*;

src/controllers/replica.rs

Lines changed: 98 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)