Files
cozystack/packages/apps/postgres/tests/backup_storage_test.yaml
T
Myasnikov Daniil b83cc087cc docs: move the repo-only guides onto the website
Seven guides in docs/ were written next to the code and left in the
source tree, where no operator looks. The website carries them now, so
a second copy here only lets the two drift again: the Backup Classes
page had already fallen behind its original.

docs/release.md, docs/deprecating-applications.md and the two SeaweedFS
runbooks in docs/operations stay. A migration, a bats suite, a workflow
or AGENTS.md reads each of them by path, which is the test for keeping
a document in this repository.

Assisted-by: LLM
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
2026-10-21 17:50:46 +05:00

397 lines
13 KiB
YAML

suite: backup storage flow (barman-cloud plugin gating, chart Secret, bootstrap guards)
# Locks in the conditional rendering on the cnpg.io Cluster, the barman-cloud
# ObjectStore CR, and the chart-managed <release>-s3-creds Secret:
# - the legacy flow wires spec.plugins + renders the ObjectStore + the Secret
# only when real S3 coordinates are present;
# - the platform useSystemBucket flow leaves them out (the CNPG backup
# driver SSA-patches spec.plugins + the ObjectStore at first BackupJob time);
# - bootstrap.recovery fails fast at render time when it would otherwise
# emit a dangling s3Credentials reference (useSystemBucket, or no creds).
# These are template-rendering contracts, so helm-unittest is the right tool.
templates:
- templates/db.yaml
- templates/backup-secret.yaml
release:
name: pg-test
namespace: tenant-test
set:
_cluster:
scheduling: {}
tests:
- it: "useSystemBucket=true: chart skips spec.plugins, ObjectStore and <release>-s3-creds"
set:
backup:
enabled: true
useSystemBucket: true
asserts:
- notExists:
path: spec.plugins
template: templates/db.yaml
documentSelector:
path: kind
value: Cluster
# No chart-rendered ObjectStore in the platform flow (the driver owns it):
# db.yaml emits only Cluster + WorkloadMonitor.
- hasDocuments:
count: 2
template: templates/db.yaml
- hasDocuments:
count: 0
template: templates/backup-secret.yaml
- it: "legacy backup with inline creds: wires spec.plugins + ObjectStore + <release>-s3-creds"
set:
backup:
enabled: true
useSystemBucket: false
s3AccessKey: AKIAEXAMPLE
s3SecretKey: s3cr3t
asserts:
# The Cluster references the barman-cloud plugin, not native barmanObjectStore.
- notExists:
path: spec.backup.barmanObjectStore
template: templates/db.yaml
documentSelector:
path: kind
value: Cluster
- equal:
path: spec.plugins[0].name
value: barman-cloud.cloudnative-pg.io
template: templates/db.yaml
documentSelector:
path: kind
value: Cluster
- equal:
path: spec.plugins[0].parameters.barmanObjectName
value: pg-test
template: templates/db.yaml
documentSelector:
path: kind
value: Cluster
# The WAL-archive S3 prefix is pinned explicitly to the release name and
# must never change on a live cluster (the driver preserves it).
- equal:
path: spec.plugins[0].parameters.serverName
value: pg-test
template: templates/db.yaml
documentSelector:
path: kind
value: Cluster
# The ObjectStore carries the S3 credentials reference.
- equal:
path: metadata.name
value: pg-test
template: templates/db.yaml
documentSelector:
path: kind
value: ObjectStore
- equal:
path: spec.configuration.s3Credentials.accessKeyId.name
value: pg-test-s3-creds
template: templates/db.yaml
documentSelector:
path: kind
value: ObjectStore
# The barman-cloud sidecar's S3 request checksum is pinned to when_required
# (non-AWS S3 gateways reject the botocore default flexible checksum).
- equal:
path: spec.instanceSidecarConfiguration.env[0].name
value: AWS_REQUEST_CHECKSUM_CALCULATION
template: templates/db.yaml
documentSelector:
path: kind
value: ObjectStore
- equal:
path: spec.instanceSidecarConfiguration.env[0].value
value: when_required
template: templates/db.yaml
documentSelector:
path: kind
value: ObjectStore
- hasDocuments:
count: 1
template: templates/backup-secret.yaml
- equal:
path: metadata.name
value: pg-test-s3-creds
template: templates/backup-secret.yaml
- it: "empty retentionPolicy is omitted from the ObjectStore (plugin CRD rejects \"\")"
# The barman-cloud ObjectStore CRD validates retentionPolicy against
# ^[1-9][0-9]*[dwm]$, so an explicit empty value must be omitted rather than
# rendered as retentionPolicy: "" (which the API server rejects).
set:
backup:
enabled: true
useSystemBucket: false
s3AccessKey: AKIAEXAMPLE
s3SecretKey: s3cr3t
retentionPolicy: ""
asserts:
- notExists:
path: spec.retentionPolicy
template: templates/db.yaml
documentSelector:
path: kind
value: ObjectStore
- it: "backup enabled, no creds, useSystemBucket=false: plugin + ObjectStore not rendered"
set:
backup:
enabled: true
useSystemBucket: false
s3AccessKey: ""
s3SecretKey: ""
asserts:
- notExists:
path: spec.plugins
template: templates/db.yaml
documentSelector:
path: kind
value: Cluster
# No ObjectStore rendered: db.yaml emits only Cluster + WorkloadMonitor.
- hasDocuments:
count: 2
template: templates/db.yaml
- hasDocuments:
count: 0
template: templates/backup-secret.yaml
- it: "bootstrap recovery: externalClusters[].plugin + a -recovery ObjectStore (serverName on the plugin param, not the ObjectStore)"
set:
backup:
enabled: true
useSystemBucket: false
s3AccessKey: AKIAEXAMPLE
s3SecretKey: s3cr3t
destinationPath: s3://bucket/pg-test/
endpointURL: https://s3.example:9000
bootstrap:
enabled: true
oldName: old-pg
serverName: old-server
asserts:
# Recovery uses the barman-cloud plugin, not the removed externalClusters[].barmanObjectStore.
- notExists:
path: spec.externalClusters[0].barmanObjectStore
template: templates/db.yaml
documentSelector:
path: kind
value: Cluster
- equal:
path: spec.externalClusters[0].plugin.name
value: barman-cloud.cloudnative-pg.io
template: templates/db.yaml
documentSelector:
path: kind
value: Cluster
- equal:
path: spec.externalClusters[0].plugin.parameters.barmanObjectName
value: pg-test-recovery
template: templates/db.yaml
documentSelector:
path: kind
value: Cluster
# serverName rides on the plugin parameter...
- equal:
path: spec.externalClusters[0].plugin.parameters.serverName
value: old-server
template: templates/db.yaml
documentSelector:
path: kind
value: Cluster
# ...and the -recovery ObjectStore carries the source S3 config but MUST NOT set
# serverName inside spec.configuration (the plugin forbids it there).
- equal:
path: spec.configuration.destinationPath
value: s3://bucket/pg-test/
template: templates/db.yaml
documentSelector:
path: metadata.name
value: pg-test-recovery
- notExists:
path: spec.configuration.serverName
template: templates/db.yaml
documentSelector:
path: metadata.name
value: pg-test-recovery
# The non-AWS S3 request-checksum pin is applied to the recovery ObjectStore too.
- equal:
path: spec.instanceSidecarConfiguration.env[0].name
value: AWS_REQUEST_CHECKSUM_CALCULATION
template: templates/db.yaml
documentSelector:
path: metadata.name
value: pg-test-recovery
- equal:
path: spec.instanceSidecarConfiguration.env[0].value
value: when_required
template: templates/db.yaml
documentSelector:
path: metadata.name
value: pg-test-recovery
- it: "in-place restore: archiver serverName uses bootstrap.newServerName, distinct from the recovery source"
# Worst case: source serverName equals the restored cluster's own release
# name (pg-test). The chart must archive under bootstrap.newServerName (a
# fresh prefix) while recovering from bootstrap.serverName, or the restore
# would fail barman-cloud-check-wal-archive with "Expected empty archive".
set:
backup:
enabled: true
useSystemBucket: false
s3AccessKey: AKIAEXAMPLE
s3SecretKey: s3cr3t
destinationPath: s3://bucket/pg-test/
endpointURL: https://s3.example:9000
bootstrap:
enabled: true
oldName: pg-test
serverName: pg-test
newServerName: pg-test-restore-deadbeef
asserts:
# WAL archiver writes to the fresh restore prefix, never the source's.
- equal:
path: spec.plugins[0].parameters.serverName
value: pg-test-restore-deadbeef
template: templates/db.yaml
documentSelector:
path: kind
value: Cluster
# Recovery still reads the source prefix.
- equal:
path: spec.externalClusters[0].plugin.parameters.serverName
value: pg-test
template: templates/db.yaml
documentSelector:
path: kind
value: Cluster
- it: "bootstrap recovery without serverName: plugin serverName defaults to oldName (native parity)"
# Native CNPG defaulted an absent serverName to the externalClusters entry
# name (= oldName); the barman-cloud plugin defaults it to the NEW cluster's
# name, which would read the wrong S3 prefix. The chart must therefore
# always emit serverName, defaulting to oldName.
set:
backup:
enabled: true
useSystemBucket: false
s3AccessKey: AKIAEXAMPLE
s3SecretKey: s3cr3t
destinationPath: s3://bucket/pg-test/
endpointURL: https://s3.example:9000
bootstrap:
enabled: true
oldName: old-pg
serverName: ""
asserts:
- equal:
path: spec.externalClusters[0].plugin.parameters.serverName
value: old-pg
template: templates/db.yaml
documentSelector:
path: kind
value: Cluster
- it: "bootstrap + useSystemBucket: render fails fast (lock in existing guard)"
template: templates/db.yaml
set:
backup:
useSystemBucket: true
bootstrap:
enabled: true
oldName: old
asserts:
- failedTemplate:
errorMessage: "postgres: bootstrap.enabled=true is incompatible with backup.useSystemBucket=true. Use a backups.cozystack.io/RestoreJob against the cozy-default BackupClass to restore from the system bucket; see https://cozystack.io/docs/v1/operations/services/backup-classes/."
- it: "bootstrap without credentials: render fails fast"
template: templates/db.yaml
set:
backup:
enabled: false
useSystemBucket: false
s3AccessKey: ""
s3SecretKey: ""
bootstrap:
enabled: true
oldName: old
asserts:
- failedTemplate:
errorMessage: "postgres: bootstrap.enabled=true requires S3 credentials: set backup.s3AccessKey+backup.s3SecretKey, backup.s3CredentialsSecret.name, or backup.useSystemBucket=true (with a backups.cozystack.io/RestoreJob)."
- it: "bootstrap recovery with backupID and recoveryTime: both reach recoveryTarget"
# Without backupID the barman-cloud plugin picks the newest backup ending by
# targetTime, on any timeline, rather than the one the restore asked for.
template: templates/db.yaml
documentSelector:
path: kind
value: Cluster
set:
backup:
enabled: true
useSystemBucket: false
s3AccessKey: AKIAEXAMPLE
s3SecretKey: s3cr3t
destinationPath: s3://bucket/pg-test/
bootstrap:
enabled: true
oldName: pg-test
backupID: "20260921T062713"
recoveryTime: "2026-09-22T10:00:00Z"
asserts:
- equal:
path: spec.bootstrap.recovery.recoveryTarget.backupID
value: "20260921T062713"
# targetTime renders unquoted, so both sides read as a YAML timestamp.
- equal:
path: spec.bootstrap.recovery.recoveryTarget.targetTime
value: 2026-09-22T10:00:00Z
- it: "bootstrap recovery with backupID only: recovery starts from it and replays to the end of the log"
template: templates/db.yaml
documentSelector:
path: kind
value: Cluster
set:
backup:
enabled: true
useSystemBucket: false
s3AccessKey: AKIAEXAMPLE
s3SecretKey: s3cr3t
destinationPath: s3://bucket/pg-test/
bootstrap:
enabled: true
oldName: pg-test
backupID: "20260921T062713"
asserts:
- equal:
path: spec.bootstrap.recovery.recoveryTarget.backupID
value: "20260921T062713"
- notExists:
path: spec.bootstrap.recovery.recoveryTarget.targetTime
- it: "bootstrap recovery with neither: no recoveryTarget, the plugin restores the newest backup"
template: templates/db.yaml
documentSelector:
path: kind
value: Cluster
set:
backup:
enabled: true
useSystemBucket: false
s3AccessKey: AKIAEXAMPLE
s3SecretKey: s3cr3t
destinationPath: s3://bucket/pg-test/
bootstrap:
enabled: true
oldName: pg-test
asserts:
- notExists:
path: spec.bootstrap.recovery.recoveryTarget