Aleksei Sviridkin abc07a3927 fix(backups): hold a BackupJob while its application is being restored (#4544)
## What this PR does

Nothing on the backup path looks at restores. A Plan creates its
BackupJobs on schedule (`plan_controller.go`), and `BackupJobReconciler`
dispatches them to their driver whatever else is happening to the
application. A backup taken during a restore captures a half-restored
state at best, and for CNPG it does worse.

We hit it on a v1.6.3 cluster. helm-controller applied the restored
Cluster with `bootstrap.newServerName`, and four seconds later a
BackupJob of the same application re-applied the barman plugin with the
source's `serverName`. The restored cluster archived onto the source's
WAL prefix, and its recovery job died with `WAL archive check failed for
server <cluster>: Expected empty archive`. Correcting `serverName` by
hand let the same restore finish, which is what identified the writer.
`currentBarmanServerName` preserves the live value on most paths, but
not in the window where the restored Cluster is being re-rendered. An
on-demand run can be refused by whoever creates it, but a scheduled one
cannot, and there is no `suspend` on a Plan. With an hourly plan and a
CNPG restore allowed thirty minutes, the overlap happens about half the
time.

A BackupJob that has not started yet (`status.startedAt` unset) now
waits while a non-terminal RestoreJob in its namespace writes into the
same application. The target is resolved as the drivers resolve it
(`resolveCNPGRestoreTarget` and the Job-based paths): the referenced
Backup's `applicationRef`, with the target's `kind` and `name` when a
`targetApplicationRef` is set, and the target's API group only when it
names one, so a target without one stays in the Backup's group. A Velero
restore always stays in the Backup's group, since the Velero driver
drops the target's API group and restores the Backup's resources. A
RestoreJob whose Backup is gone is skipped, since the restore controller
fails it on its next pass. `options.targetNamespace` is honoured only
when the Backup was taken by the Velero strategy, the one driver that
reads it: the CNPG and Job-based drivers ignore the option and restore
in place, so their restores hold whatever it says. The held job stays
`Pending` with a `Ready=False` condition, reason `RestoreInProgress`,
naming the restore, and is requeued every 30s. It is not failed: a
scheduled run should still happen once the restore ends. A failed write
of that status is returned, so the manager retries it, and once the
restore ends the condition is removed before the job starts, since it
would otherwise read "waiting" until the driver next writes `Ready`,
which for a healthy run is when the backup completes.

The gate sits after BackupClass resolution and strategy validation, so
third-party strategies are unaffected, and before credentials
projection, so a held job projects nothing.

What happens to a delayed scheduled run: the Plan controller only
decides when to *create* a BackupJob (a slot within `startingDeadline`,
300s, of now). Once created, the job is not subject to that deadline, so
a held run keeps its `<plan>-<unix-minutes>` name and runs once the
restore ends, later than its slot. If the restore outlasts the next
slot, that slot's job is created and held too, and both run after the
restore. Nothing is skipped, and nothing is deduplicated.

Not covered, deliberately:
- A backup already running when a restore starts is not interrupted. Its
driver owns state the gate would strand. The reverse guard, a RestoreJob
waiting for a running BackupJob, would belong in the restore path,
together with a per-application claim that closes the race between the
two checks, and is tracked in #4559.
- A Velero RestoreJob in another namespace that targets this one through
`options.targetNamespace` does not hold this namespace's backups. The
lookup is namespaced.
- The Velero restore path has no deadline, so a stuck VM restore holds
that application's backups until the restore is ended or deleted. That
matches the restore already blocking the application.

Tests: `TestActiveRestoreTargeting` covers the resolution matrix (in
place, to-copy into and out of the application, terminal phases, another
kind or another API group under the same name, a to-copy target
inheriting the Backup's API group, a Velero target naming another API
group, a Velero restore sent to another namespace or naming its own, a
Postgres restore carrying `targetNamespace`, a missing Backup with or
without a target). `TestReconcile_RetriesAHoldItCouldNotRecord` and
`TestReleaseRestoreHold` cover the status writes, and the held-job test
reconciles again after the restore ends to check the condition is gone.
`TestReconcile_HoldsABackupJobWhileItsApplicationIsRestored` is the
behavioural regression: it fails with the gate removed (the job reaches
projection and dispatch). `TestReconcile_DoesNotHoldARunningBackupJob`
checks that a started job is not held.

Backport: a candidate for `release-1.6`, where the reproduction was
seen. `backupjob_controller.go` differs from `main` only in the dispatch
switch and the Go 1.27 modernizers, so the cherry-pick should be clean
or close to it.

### Screenshots

N/A, no UI change.

### Downstream repositories

The diff touches only `internal/backupcontroller/` Go code. It changes
no CRD, API type, chart, values file, hack script or naming, so the
trigger map comes out empty.

- [x] No downstream repository is affected by this change
- [ ] [cozystack/website](https://github.com/cozystack/website) -
follow-up:
- [ ]
[cozystack/terraform-provider-cozystack](https://github.com/cozystack/terraform-provider-cozystack)
- follow-up:
- [ ]
[cozystack/ansible-cozystack](https://github.com/cozystack/ansible-cozystack)
- follow-up:
- [ ] [cozystack/ccp](https://github.com/cozystack/ccp) - follow-up:
- [ ] [cozystack/talm](https://github.com/cozystack/talm) - follow-up:
- [ ] [cozystack/cozyhr](https://github.com/cozystack/cozyhr) -
follow-up:
- [ ] [cozystack/cozy-proxy](https://github.com/cozystack/cozy-proxy) -
follow-up:
- [ ]
[cozystack/cozystack-telemetry-server](https://github.com/cozystack/cozystack-telemetry-server)
- follow-up:
- [ ]
[cozystack/external-apps-example](https://github.com/cozystack/external-apps-example)
- follow-up:
- [ ] [cozystack/examples](https://github.com/cozystack/examples) -
follow-up:
- [ ] [cozystack/community](https://github.com/cozystack/community) -
follow-up:

### Release note

```release-note
fix(backups): a BackupJob that has not started yet, scheduled or on demand, now waits while a RestoreJob is restoring the same application, instead of running against it. It stays Pending with a Ready=False RestoreInProgress condition and runs once the restore ends. This stops a CNPG backup that has not started yet from re-pointing an in-place restored cluster at the source's WAL archive; a backup already running when a restore starts is not held.
```


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Unstarted backup jobs now wait when an active restore targets the same
application. They remain pending and are retried after a short delay,
then can proceed once the restore finishes.
* Backup jobs that have already started continue without being held by
an active restore. Completed or failed restores do not hold pending
backup jobs.
* Restore matching accounts for the specified target and, for Velero
restores, the target namespace. A backup job’s restore hold is cleared
when no matching active restore remains.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-09-29 14:24:41 +03:00
2025-10-08 09:43:34 +05:00
2024-02-08 12:04:32 +01:00

Cozystack Cozystack

GitHub Release Apache-2.0 License Commit activity OpenSSF Best Practices OpenSSF Scorecard CLOMonitor Support

Cozystack

Cozystack is a free and open-source platform and framework for building clouds.

Cozystack is a CNCF Sandbox project that was originally built and sponsored by Ænix.

With Cozystack, you can transform a bunch of servers into an intelligent system with a simple REST API for spawning Kubernetes clusters, Database-as-a-Service, virtual machines, load balancers, HTTP caching services, and other services with ease.

Use Cozystack to build your own cloud or provide a cost-effective development environment.

Cozystack user interface

Use-Cases

Documentation

The documentation is located on the cozystack.io website.

Read the Getting Started section for a quick start.

If you encounter any difficulties, start with the troubleshooting guide and work your way through the process that we've outlined.

Versioning

Versioning adheres to the Semantic Versioning principles.
A full list of the available releases is available in the GitHub repository's Release section.

Contributions

Contributions are highly appreciated and very welcomed!

In case of bugs, please check if the issue has already been opened by checking the GitHub Issues section. If it isn't, you can open a new one. A detailed report will help us replicate it, assess it, and work on a fix.

You are welcome to take on the fix yourself. Commits are used to generate the changelog, and their author will be referenced in it.

If you have a concrete feature request, open an issue. For a cross-cutting or architectural change, open a design proposal in the community repo. For questions or early ideas, use GitHub Discussions. See CONTRIBUTING.md for the full routing.

Community

You are welcome to join us in #cozystack, on either CNCF Slack (get an invite) or Kubernetes Slack (get an invite), in our Telegram group, and at our community meetings. Add them to your Google Calendar or iCal for convenience.

Project documents

Document What it covers
Governance How decisions are made and who makes them
Contributor Ladder How to grow from contributor to maintainer
Maintainers Who maintains the project
Code of Conduct What we expect of each other
Security Policy Reporting a vulnerability, and which releases get fixes
Adopters Who runs Cozystack in production
Roadmap Where the project is going

License

Cozystack is licensed under Apache 2.0.
The code is provided as-is with no warranties.

Commercial Support

A list of companies providing commercial support for this project can be found on official site.

Languages
Go 47.2%
Shell 37.6%
TypeScript 7%
Go Template 5.4%
Makefile 1.4%
Other 1.2%