Add timeout and retry to combine_restore kubectl cp#4323
Conversation
kubectl cp streams a tar over the exec channel and can stall intermittently. run_cmd() called subprocess.run() with no timeout, so a stalled copy during a restore hung forever with no output or error (see issue #4322), undermining the disaster-recovery guarantee. - Add an optional timeout to run_cmd() and thread it (plus check_results) through CombineApp.kubectl() and CombineApp.exec(). - Wrap each restore kubectl cp (the database dump and every backend-file item) in kubectl_cp(): bound each attempt with a timeout, retry a few times so a transient stall is recovered, and on repeated failure log the failing item and exit non-zero instead of hanging silently. - Make the timeout and retry count configurable via the kubectl_cp_timeout and kubectl_cp_retries environment variables. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #4323 +/- ##
==========================================
- Coverage 75.96% 66.88% -9.08%
==========================================
Files 305 248 -57
Lines 11384 6303 -5081
Branches 1411 794 -617
==========================================
- Hits 8648 4216 -4432
+ Misses 2332 1826 -506
+ Partials 404 261 -143
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
The backup script's `kubectl cp` calls (database and backend files) can stall on the exec channel exactly like the restore ones. combine_backup.py runs unattended as a cron job, so a silent hang there quietly stops backups from being produced. Lift the restore-only kubectl_cp() into CombineApp.cp_with_retry() so both scripts share one timeout/retry implementation, and wrap combine_backup.py's two copies with it. The kubectl_cp_timeout and kubectl_cp_retries environment variables now configure both backup and restore. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The loop runs the copy 'retries' total times, so the conventional reading of retries (extra tries after the first) was misleading. Rename the parameter, constant, and env var (kubectl_cp_attempts) before the knob becomes part of the ops interface. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
🧹 Nitpick comments (1)
maintenance/scripts/combine_app.py (1)
100-140: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low valueOptional: add a short backoff between retries.
Attempts currently retry immediately after a non-zero return code. A brief sleep between attempts would avoid hammering a transient failure that isn't a timeout. Timeout attempts already have inherent delay, so this mainly helps the fast-fail path.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@maintenance/scripts/combine_app.py` around lines 100 - 140, The retry loop in cp_with_retry currently retries immediately after a non-zero kubectl cp failure, which can rapidly hammer a transient fast-fail path. Add a short sleep between failed attempts in cp_with_retry (for the returncode != 0 case, and optionally after timeouts if desired) before the next iteration, using the existing attempt/attempts logging to keep behavior clear. Keep the change localized to cp_with_retry and preserve the final abort behavior after all attempts are exhausted.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@maintenance/scripts/combine_app.py`:
- Around line 100-140: The retry loop in cp_with_retry currently retries
immediately after a non-zero kubectl cp failure, which can rapidly hammer a
transient fast-fail path. Add a short sleep between failed attempts in
cp_with_retry (for the returncode != 0 case, and optionally after timeouts if
desired) before the next iteration, using the existing attempt/attempts logging
to keep behavior clear. Keep the change localized to cp_with_retry and preserve
the final abort behavior after all attempts are exhausted.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 729281c5-f5f1-4e34-8f56-0b6bcceee7db
📒 Files selected for processing (4)
maintenance/scripts/combine_app.pymaintenance/scripts/combine_backup.pymaintenance/scripts/combine_restore.pymaintenance/scripts/maint_utils.py
Without a delay, a fast-failing kubectl cp exhausts all attempts within the same second, giving a transient failure no time to clear. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fixes
Fixes #4322
Problem
maintenance/scripts/combine_restore.pycopies backend files into the backend pod with a per-projectkubectl cploop (and copies the DB dump the same way).kubectl cpstreams a tar over the exec channel and can stall intermittently. Becausemaint_utils.run_cmd()calledsubprocess.run(...)with notimeout, a stalled copy hung forever with no error and no output. Since this is The Combine's disaster-recovery restore, a silent infinite hang is a serious reliability problem.Changes
maint_utils.py: add an optionaltimeoutparameter torun_cmd(), passed through tosubprocess.run(...). When set, a stalled command is killed andsubprocess.TimeoutExpiredis raised for the caller to handle. DefaultNonepreserves existing behavior.combine_app.py: threadtimeoutthroughCombineApp.exec()andCombineApp.kubectl()(also addingcheck_resultstokubectl()).combine_restore.py: wrap eachkubectl cp(the database dump and every backend-file item) in a newkubectl_cp()helper that:kubectl_cp_timeout(default 300s) andkubectl_cp_retries(default 3) environment variables.I kept the existing per-item
kubectl cploop rather than switching to a singletar-pipe (listed as optional in the issue) to keep the change focused; the timeout/retry guard applies either way.Testing
toxequivalents pass locally on the changed files:isort --check,black --check,flake8, andmypy --platform linux maintenance/scripts(strict) — all clean.run_cmd(..., timeout=1)on a 30s sleep raisesTimeoutExpired(no longer hangs) and still returns output for fast commands.kubectl_cp()succeeds without retry on a clean copy (and passescheck_results=False+timeout), recovers from transient stalls (timeout → timeout → success), and exits non-zero after exhausting retries on repeated failure.🤖 Generated with Claude Code
Devin review: https://app.devin.ai/review/sillsdev/TheCombine/pull/4323
This change is
Summary by CodeRabbit
Bug Fixes
Chores