Compare commits

..

72 Commits

Author SHA1 Message Date
Xun Jiang
29a9f80f10 Compare affinity by string instead of exactly same compare.
From 1.18.1, Velero adds some default affinity in the backup/restore pod,
so we can't directly compare the whole affinity,
but we can verify if the expected affinity is contained in the pod affinity.

Signed-off-by: Xun Jiang <xun.jiang@broadcom.com>
2026-03-16 10:49:50 +08:00
Xun Jiang/Bruce Jiang
66ac235e1f Merge pull request #9595 from vmware-tanzu/xj014661/main/disable_search_in_site
Some checks failed
Run the E2E test on kind / get-go-version (push) Failing after 1m5s
Run the E2E test on kind / build (push) Has been skipped
Run the E2E test on kind / setup-test-matrix (push) Successful in 3s
Run the E2E test on kind / run-e2e-test (push) Has been skipped
Main CI / get-go-version (push) Successful in 14s
Main CI / Build (push) Failing after 33s
Close stale issues and PRs / stale (push) Successful in 17s
Trivy Nightly Scan / Trivy nightly scan (velero, main) (push) Failing after 1m8s
Trivy Nightly Scan / Trivy nightly scan (velero-plugin-for-aws, main) (push) Failing after 43s
Trivy Nightly Scan / Trivy nightly scan (velero-plugin-for-gcp, main) (push) Failing after 1m5s
Trivy Nightly Scan / Trivy nightly scan (velero-plugin-for-microsoft-azure, main) (push) Failing after 1m13s
Disable Algolia docs search
2026-03-11 11:23:22 +08:00
Shubham Pampattiwar
afe7df17d4 Add itemOperationTimeout to Schedule API type docs (#9599)
The itemOperationTimeout field was missing from the Schedule API type
documentation even though it is supported in the Schedule CRD template.
This led users to believe the field was not available per-schedule.

Fixes #9598

Signed-off-by: Shubham Pampattiwar <spampatt@redhat.com>
2026-03-10 16:12:47 -04:00
Shubham Pampattiwar
a31f4abcb3 Fix DBR stuck when CSI snapshot no longer exists in cloud provider (#9581)
Some checks failed
Run the E2E test on kind / get-go-version (push) Failing after 1m17s
Run the E2E test on kind / build (push) Has been skipped
Run the E2E test on kind / setup-test-matrix (push) Successful in 3s
Run the E2E test on kind / run-e2e-test (push) Has been skipped
Main CI / get-go-version (push) Successful in 19s
Main CI / Build (push) Failing after 37s
Close stale issues and PRs / stale (push) Successful in 18s
Trivy Nightly Scan / Trivy nightly scan (velero, main) (push) Failing after 1m29s
Trivy Nightly Scan / Trivy nightly scan (velero-plugin-for-aws, main) (push) Failing after 40s
Trivy Nightly Scan / Trivy nightly scan (velero-plugin-for-gcp, main) (push) Failing after 50s
Trivy Nightly Scan / Trivy nightly scan (velero-plugin-for-microsoft-azure, main) (push) Failing after 53s
* Fix DBR stuck when CSI snapshot no longer exists in cloud provider

During backup deletion, VolumeSnapshotContentDeleteItemAction creates a
new VSC with the snapshot handle from the backup and polls for readiness.
If the underlying snapshot no longer exists (e.g., deleted externally),
the CSI driver reports Status.Error but checkVSCReadiness() only checks
ReadyToUse, causing it to poll for the full 10-minute timeout instead of
failing fast. Additionally, the newly created VSC is never cleaned up on
failure, leaving orphaned resources in the cluster.

This commit:
- Adds Status.Error detection in checkVSCReadiness() to fail immediately
  on permanent CSI driver errors (e.g., InvalidSnapshot.NotFound)
- Cleans up the dangling VSC when readiness polling fails

Fixes #9579

Signed-off-by: Shubham Pampattiwar <spampatt@redhat.com>

* Add changelog for PR #9581

Signed-off-by: Shubham Pampattiwar <spampatt@redhat.com>

* Fix typo in pod_volume_test.go: colume -> volume

Signed-off-by: Shubham Pampattiwar <spampatt@redhat.com>

---------

Signed-off-by: Shubham Pampattiwar <spampatt@redhat.com>
2026-03-10 13:40:09 -04:00
Xun Jiang/Bruce Jiang
2145c57642 Merge pull request #9562 from hollycai05/add-e2e-test-for-PR9366
Some checks failed
Run the E2E test on kind / get-go-version (push) Failing after 1m4s
Run the E2E test on kind / build (push) Has been skipped
Run the E2E test on kind / setup-test-matrix (push) Successful in 3s
Run the E2E test on kind / run-e2e-test (push) Has been skipped
Main CI / get-go-version (push) Successful in 18s
Main CI / Build (push) Failing after 41s
Add e2e test case for PR 9366
2026-03-10 17:28:23 +08:00
Xun Jiang
a9b3cfa062 Disable Algolia docs search.
Revert PR 6105.

Signed-off-by: Xun Jiang <xun.jiang@broadcom.com>
2026-03-10 16:10:44 +08:00
Wenkai Yin(尹文开)
bca6afada7 Merge pull request #9590 from Lyndon-Li/set-latest-do-to-1.18
Some checks failed
Run the E2E test on kind / get-go-version (push) Failing after 1m27s
Run the E2E test on kind / build (push) Has been skipped
Run the E2E test on kind / setup-test-matrix (push) Successful in 3s
Run the E2E test on kind / run-e2e-test (push) Has been skipped
Main CI / get-go-version (push) Successful in 19s
Main CI / Build (push) Failing after 1m55s
Close stale issues and PRs / stale (push) Successful in 16s
Trivy Nightly Scan / Trivy nightly scan (velero, main) (push) Failing after 1m3s
Trivy Nightly Scan / Trivy nightly scan (velero-plugin-for-aws, main) (push) Failing after 55s
Trivy Nightly Scan / Trivy nightly scan (velero-plugin-for-gcp, main) (push) Failing after 30s
Trivy Nightly Scan / Trivy nightly scan (velero-plugin-for-microsoft-azure, main) (push) Failing after 46s
Issue 9586: set latest doc to 1.18
2026-03-09 17:27:23 +08:00
Lyndon-Li
d1cc303553 issue 9586: set latest doc to 1.18
Signed-off-by: Lyndon-Li <lyonghui@vmware.com>
2026-03-09 15:41:13 +08:00
Xun Jiang/Bruce Jiang
befa61cee1 Merge pull request #9570 from H-M-Quang-Ngo/add-schedule-interval-metric
Add schedule_expected_interval_seconds metric
2026-03-09 15:28:59 +08:00
lyndon-li
245525c26b Merge pull request #9547 from blackpiglet/1.18_add_bia_skip_resource_logic
Some checks failed
Run the E2E test on kind / get-go-version (push) Failing after 1m6s
Run the E2E test on kind / build (push) Has been skipped
Run the E2E test on kind / setup-test-matrix (push) Successful in 2s
Run the E2E test on kind / run-e2e-test (push) Has been skipped
Main CI / get-go-version (push) Successful in 15s
Main CI / Build (push) Failing after 37s
Close stale issues and PRs / stale (push) Successful in 16s
Trivy Nightly Scan / Trivy nightly scan (velero, main) (push) Failing after 1m27s
Trivy Nightly Scan / Trivy nightly scan (velero-plugin-for-aws, main) (push) Failing after 52s
Trivy Nightly Scan / Trivy nightly scan (velero-plugin-for-gcp, main) (push) Failing after 29s
Trivy Nightly Scan / Trivy nightly scan (velero-plugin-for-microsoft-azure, main) (push) Failing after 47s
Add BIA skip resource logic
2026-03-06 12:28:05 +08:00
Xun Jiang/Bruce Jiang
55737b9cf1 Merge pull request #9574 from blackpiglet/xj014661/main/ephemeral_storage_config
Some checks failed
Run the E2E test on kind / get-go-version (push) Failing after 1m24s
Run the E2E test on kind / build (push) Has been skipped
Run the E2E test on kind / setup-test-matrix (push) Successful in 4s
Run the E2E test on kind / run-e2e-test (push) Has been skipped
Main CI / get-go-version (push) Successful in 17s
Main CI / Build (push) Failing after 40s
Close stale issues and PRs / stale (push) Successful in 16s
Trivy Nightly Scan / Trivy nightly scan (velero, main) (push) Failing after 1m28s
Trivy Nightly Scan / Trivy nightly scan (velero-plugin-for-aws, main) (push) Failing after 41s
Trivy Nightly Scan / Trivy nightly scan (velero-plugin-for-gcp, main) (push) Failing after 35s
Trivy Nightly Scan / Trivy nightly scan (velero-plugin-for-microsoft-azure, main) (push) Failing after 25s
Add ephemeral storage limit and request support for data mover and maintenance job
2026-03-05 22:43:16 +08:00
Xun Jiang
ffea850522 Add ephemeral storage limit and request support for data mover and maintenance job.
Signed-off-by: Xun Jiang <xun.jiang@broadcom.com>
2026-03-05 14:22:53 +08:00
Quang
b3aff97684 Merge branch 'main' into add-schedule-interval-metric 2026-03-05 09:15:52 +11:00
testsabirweb
23a3c242fa Add test coverage and fix validation for MRAP ARN bucket names (#9554)
Some checks failed
Run the E2E test on kind / get-go-version (push) Failing after 1m21s
Run the E2E test on kind / build (push) Has been skipped
Run the E2E test on kind / setup-test-matrix (push) Successful in 3s
Run the E2E test on kind / run-e2e-test (push) Has been skipped
Main CI / get-go-version (push) Successful in 13s
Main CI / Build (push) Failing after 43s
Close stale issues and PRs / stale (push) Successful in 16s
Trivy Nightly Scan / Trivy nightly scan (velero, main) (push) Failing after 2m35s
Trivy Nightly Scan / Trivy nightly scan (velero-plugin-for-aws, main) (push) Failing after 1m49s
Trivy Nightly Scan / Trivy nightly scan (velero-plugin-for-gcp, main) (push) Failing after 1m36s
Trivy Nightly Scan / Trivy nightly scan (velero-plugin-for-microsoft-azure, main) (push) Failing after 1m44s
* Issue #9544: Add test coverage and fix validation for MRAP ARN bucket names

S3 Multi-Region Access Point (MRAP) ARNs have the format:
  arn:aws:s3::{account-id}:accesspoint/{mrap-alias}.mrap

These ARNs contain a '/' as part of the ARN path, which caused Velero's
BSL bucket validation to reject them with an error asking the user to
put the value in the Prefix field instead.

Fix the bucket name validation in objectBackupStoreGetter.Get() to
exempt ARNs (identified by the "arn:" prefix) from the slash check,
since slashes are a valid and required part of ARN syntax.

Add unit tests in object_store_mrap_test.go covering:
- A plain MRAP ARN as bucket name succeeds
- A MRAP ARN with a trailing slash is trimmed and accepted

Signed-off-by: Sabir Ali <testsabirweb@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* Address review comments: fix changelog filename and import grouping

Signed-off-by: Sabir Ali <testsabirweb@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* Restrict MRAP ARN bucket validation to arn:aws:s3: prefix

Per review, use HasPrefix(bucket, "arn:aws:s3:") instead of
HasPrefix(bucket, "arn:") so only S3 ARNs (e.g. MRAP) are exempt
from the slash check, not any ARN from other AWS services.

Signed-off-by: Sabir Ali <sabir.ali@spectrocloud.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* Move MRAP bucket tests into TestNewObjectBackupStoreGetter

Consolidate MRAP ARN test cases into the existing table in
object_store_test.go and remove object_store_mrap_test.go.

Signed-off-by: Sabir Ali <sabir.ali@spectrocloud.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Signed-off-by: Sabir Ali <testsabirweb@gmail.com>
Signed-off-by: Sabir Ali <sabir.ali@spectrocloud.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-03-04 15:11:01 +00:00
Xun Jiang/Bruce Jiang
b7bc16f190 Merge pull request #9569 from vmware-tanzu/dependabot/go_modules/go.opentelemetry.io/otel/sdk-1.40.0
Bump go.opentelemetry.io/otel/sdk from 1.38.0 to 1.40.0
2026-03-04 23:00:11 +08:00
dongqingcc
bbec46f6ee Add e2e test case for PR 9366: Use hookIndex for recording multiple restore exec hooks.
Signed-off-by: dongqingcc <dongqingcc@vmware.com>
2026-03-03 17:53:11 +08:00
Quang
475050108b Merge branch 'main' into add-schedule-interval-metric 2026-03-03 01:00:32 +11:00
lyndon-li
b5f7cd92c7 Merge pull request #9571 from Lyndon-Li/fix-compile-error-for-windows
Some checks failed
Run the E2E test on kind / get-go-version (push) Failing after 1m14s
Run the E2E test on kind / build (push) Has been skipped
Run the E2E test on kind / setup-test-matrix (push) Successful in 2s
Run the E2E test on kind / run-e2e-test (push) Has been skipped
Main CI / get-go-version (push) Successful in 17s
Main CI / Build (push) Failing after 34s
Close stale issues and PRs / stale (push) Successful in 20s
Trivy Nightly Scan / Trivy nightly scan (velero, main) (push) Failing after 2m13s
Trivy Nightly Scan / Trivy nightly scan (velero-plugin-for-aws, main) (push) Failing after 1m42s
Trivy Nightly Scan / Trivy nightly scan (velero-plugin-for-gcp, main) (push) Failing after 1m46s
Trivy Nightly Scan / Trivy nightly scan (velero-plugin-for-microsoft-azure, main) (push) Failing after 1m41s
Fix compile error for Windows
2026-03-02 16:43:59 +08:00
Lyndon-Li
ab31b811ee fix compile error for Windows
Signed-off-by: Lyndon-Li <lyonghui@vmware.com>
2026-03-02 15:11:54 +08:00
dependabot[bot]
19360622e7 Bump go.opentelemetry.io/otel/sdk from 1.38.0 to 1.40.0
Some checks failed
Run the E2E test on kind / get-go-version (push) Failing after 1m11s
Run the E2E test on kind / build (push) Has been skipped
Run the E2E test on kind / setup-test-matrix (push) Successful in 3s
Run the E2E test on kind / run-e2e-test (push) Has been skipped
Bumps [go.opentelemetry.io/otel/sdk](https://github.com/open-telemetry/opentelemetry-go) from 1.38.0 to 1.40.0.
- [Release notes](https://github.com/open-telemetry/opentelemetry-go/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-go/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-go/compare/v1.38.0...v1.40.0)

---
updated-dependencies:
- dependency-name: go.opentelemetry.io/otel/sdk
  dependency-version: 1.40.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-02 06:50:57 +00:00
lyndon-li
932d27541c Merge pull request #9561 from Lyndon-Li/uploader-flush-buffer
Some checks failed
Run the E2E test on kind / get-go-version (push) Failing after 1m11s
Run the E2E test on kind / build (push) Has been skipped
Run the E2E test on kind / setup-test-matrix (push) Successful in 3s
Run the E2E test on kind / run-e2e-test (push) Has been skipped
Main CI / get-go-version (push) Successful in 15s
Main CI / Build (push) Failing after 36s
Issue 9460: Uploader flush buffer
2026-03-02 14:49:51 +08:00
Quang
b0642b3078 Merge branch 'main' into add-schedule-interval-metric 2026-03-02 15:23:53 +11:00
Lyndon-Li
9cada8fc11 issue 9460: flush buffer when uploader completes
Signed-off-by: Lyndon-Li <lyonghui@vmware.com>
2026-03-02 11:43:44 +08:00
Wenkai Yin(尹文开)
25d5fa1b88 Merge pull request #9560 from Lyndon-Li/selected-node-to-node-selector
Issue 9475: Selected node to node selector
2026-03-02 11:26:26 +08:00
Quang Ngo
1c08af8461 Add changelog for #9570
Signed-off-by: Quang Ngo <quang.ngo@canonical.com>
2026-03-02 10:49:14 +11:00
Quang Ngo
6c3d81a146 Add schedule_expected_interval_seconds metric
Add a new Prometheus gauge metric that exposes the expected interval
between consecutive scheduled backups. This enables dynamic alerting
thresholds per schedule backups.

Signed-off-by: Quang Ngo <quang.ngo@canonical.com>
2026-03-02 10:20:09 +11:00
Xun Jiang/Bruce Jiang
8f32696449 Merge branch 'main' into 1.18_add_bia_skip_resource_logic 2026-02-27 11:38:27 +08:00
Xun Jiang
3f15e9219f Remove the skipped item from the resource list when it's skipped by BIA.
Signed-off-by: Xun Jiang <xun.jiang@broadcom.com>
2026-02-27 11:37:34 +08:00
Lyndon-Li
544b184d6c Merge branch 'main' into uploader-flush-buffer 2026-02-26 13:38:44 +08:00
Lyndon-Li
250c4db158 node-selector for selected node
Signed-off-by: Lyndon-Li <lyonghui@vmware.com>
2026-02-26 13:34:43 +08:00
Lyndon-Li
f0d81c56e2 Merge branch 'main' into selected-node-to-node-selector 2026-02-26 13:30:47 +08:00
lyndon-li
8b5559274d Merge pull request #9533 from Lyndon-Li/support-customized-host-os
Some checks failed
Run the E2E test on kind / get-go-version (push) Failing after 1m6s
Run the E2E test on kind / build (push) Has been skipped
Run the E2E test on kind / setup-test-matrix (push) Successful in 3s
Run the E2E test on kind / run-e2e-test (push) Has been skipped
Main CI / get-go-version (push) Successful in 12s
Main CI / Build (push) Failing after 46s
Close stale issues and PRs / stale (push) Successful in 15s
Trivy Nightly Scan / Trivy nightly scan (velero, main) (push) Failing after 2m12s
Trivy Nightly Scan / Trivy nightly scan (velero-plugin-for-aws, main) (push) Failing after 1m34s
Trivy Nightly Scan / Trivy nightly scan (velero-plugin-for-gcp, main) (push) Failing after 1m28s
Trivy Nightly Scan / Trivy nightly scan (velero-plugin-for-microsoft-azure, main) (push) Failing after 1m45s
Issue 9496: support customized host os
2026-02-26 12:00:02 +08:00
Lyndon-Li
7235180de4 Merge branch 'main' into support-customized-host-os 2026-02-24 15:40:56 +08:00
Tiger Kaovilai
ba5e7681ff rename malformed changelog file name (#9552)
Some checks failed
Run the E2E test on kind / get-go-version (push) Failing after 1m2s
Run the E2E test on kind / build (push) Has been skipped
Run the E2E test on kind / setup-test-matrix (push) Successful in 4s
Run the E2E test on kind / run-e2e-test (push) Has been skipped
Close stale issues and PRs / stale (push) Successful in 21s
Trivy Nightly Scan / Trivy nightly scan (velero, main) (push) Failing after 2m3s
Trivy Nightly Scan / Trivy nightly scan (velero-plugin-for-aws, main) (push) Failing after 1m29s
Trivy Nightly Scan / Trivy nightly scan (velero-plugin-for-gcp, main) (push) Failing after 1m15s
Trivy Nightly Scan / Trivy nightly scan (velero-plugin-for-microsoft-azure, main) (push) Failing after 1m29s
Signed-off-by: Tiger Kaovilai <tkaovila@redhat.com>
2026-02-19 14:28:15 -05:00
lyndon-li
fc0a16d734 Merge pull request #9548 from Lyndon-Li/doc-for-1.18-2
Some checks failed
Run the E2E test on kind / get-go-version (push) Failing after 1m25s
Run the E2E test on kind / build (push) Has been skipped
Run the E2E test on kind / setup-test-matrix (push) Successful in 4s
Run the E2E test on kind / run-e2e-test (push) Has been skipped
Main CI / get-go-version (push) Successful in 14s
Main CI / Build (push) Failing after 41s
Close stale issues and PRs / stale (push) Successful in 14s
Trivy Nightly Scan / Trivy nightly scan (velero, main) (push) Failing after 2m28s
Trivy Nightly Scan / Trivy nightly scan (velero-plugin-for-aws, main) (push) Failing after 1m38s
Trivy Nightly Scan / Trivy nightly scan (velero-plugin-for-gcp, main) (push) Failing after 1m25s
Trivy Nightly Scan / Trivy nightly scan (velero-plugin-for-microsoft-azure, main) (push) Failing after 1m22s
Update doc link for 1.18
2026-02-13 18:02:40 +08:00
Xun Jiang
bcdee1b116 If BIA return updateObj with SkipFromBackupAnnotation, treat it as skip the resource from backup.
Signed-off-by: Xun Jiang <xun.jiang@broadcom.com>
2026-02-13 17:42:46 +08:00
Lyndon-Li
2a696a4431 update doc link for 1.18
Signed-off-by: Lyndon-Li <lyonghui@vmware.com>
2026-02-13 17:34:36 +08:00
Xun Jiang/Bruce Jiang
991bf1b000 Merge pull request #9545 from Lyndon-Li/add-upgrade-to-1.18-doc
Add upgrade-to-1.18 doc
2026-02-13 16:32:47 +08:00
Lyndon-Li
4d47471932 add upgrade-to-1.18 doc
Signed-off-by: Lyndon-Li <lyonghui@vmware.com>
2026-02-13 16:20:53 +08:00
lyndon-li
0bf968d24d Merge pull request #9532 from Lyndon-Li/issue-fix-9343
Some checks failed
Run the E2E test on kind / get-go-version (push) Failing after 1m33s
Run the E2E test on kind / build (push) Has been skipped
Run the E2E test on kind / setup-test-matrix (push) Successful in 3s
Run the E2E test on kind / run-e2e-test (push) Has been skipped
Main CI / get-go-version (push) Successful in 18s
Main CI / Build (push) Failing after 41s
Issue 9343: include PV topology to data mover pod affinities
2026-02-13 13:14:34 +08:00
Lyndon-Li
05c9a8d8f8 issue 9343: include PV topology to data mover pod affinitiesq
Signed-off-by: Lyndon-Li <lyonghui@vmware.com>
2026-02-13 11:22:32 +08:00
Xun Jiang/Bruce Jiang
bc957a22b7 Merge pull request #9542 from blackpiglet/xj014661/main/cherry_pick_e2e_fixes
[main] cherry pick e2e fixes
2026-02-13 10:24:03 +08:00
Xun Jiang
7e3d66adc7 Fix test case issue and add UT.
Signed-off-by: Xun Jiang <xun.jiang@broadcom.com>
2026-02-12 13:22:18 +08:00
Xun Jiang
710ebb9d92 Update the migration and upgrade test cases.
Modify Dockerfile to fix GitHub CI action error.

Signed-off-by: Xun Jiang <xun.jiang@broadcom.com>
2026-02-12 13:20:34 +08:00
Joseph Antony Vaikath
1315399f35 Support all glob wildcard characters in namespace validation (#9502)
Some checks failed
Run the E2E test on kind / get-go-version (push) Failing after 1m19s
Run the E2E test on kind / build (push) Has been skipped
Run the E2E test on kind / setup-test-matrix (push) Successful in 3s
Run the E2E test on kind / run-e2e-test (push) Has been skipped
build-image / Build (push) Failing after 16s
Main CI / get-go-version (push) Successful in 13s
Main CI / Build (push) Failing after 36s
Close stale issues and PRs / stale (push) Successful in 16s
Trivy Nightly Scan / Trivy nightly scan (velero, main) (push) Failing after 1m56s
Trivy Nightly Scan / Trivy nightly scan (velero-plugin-for-aws, main) (push) Failing after 1m33s
Trivy Nightly Scan / Trivy nightly scan (velero-plugin-for-gcp, main) (push) Failing after 1m30s
Trivy Nightly Scan / Trivy nightly scan (velero-plugin-for-microsoft-azure, main) (push) Failing after 1m40s
* Support all glob wildcard characters in namespace validation

Expand namespace validation to allow all valid glob pattern characters
(*, ?, {}, [], ,) by replacing them with valid characters during RFC 1123
validation. The actual glob pattern validation is handled separately by
the wildcard package.

Also add validation to reject unsupported characters (|, (), !) that are
not valid in glob patterns, and update terminology from "regex" to "glob"
for clarity since this implementation uses glob patterns, not regex.

Changes:
- Replace all glob wildcard characters in validateNamespaceName
- Add test coverage for valid glob patterns in includes/excludes
- Add test coverage for unsupported characters
- Reject exclamation mark (!) in wildcard patterns
- Clarify comments and error messages about glob vs regex

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Signed-off-by: Joseph <jvaikath@redhat.com>

* Changelog

Signed-off-by: Joseph <jvaikath@redhat.com>

* Add documentation: glob patterns are now accepted

Signed-off-by: Joseph <jvaikath@redhat.com>

* Error message fix

Signed-off-by: Joseph <jvaikath@redhat.com>

* Remove negation glob char test

Signed-off-by: Joseph <jvaikath@redhat.com>

* Add bracket pattern validation for namespace glob patterns

Extends wildcard validation to support square bracket patterns [] used in glob character classes. Validates bracket syntax including empty brackets, unclosed brackets, and unmatched brackets. Extracts ValidateNamespaceName as a public function to enable reuse in namespace validation logic.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Signed-off-by: Joseph <jvaikath@redhat.com>

* Reduce scope to *, ?, [ and ]

Signed-off-by: Joseph <jvaikath@redhat.com>

* Fix tests

Signed-off-by: Joseph <jvaikath@redhat.com>

* Add namespace glob patterns documentation page

Adds dedicated documentation explaining supported glob patterns
for namespace include/exclude filtering to help users understand
the wildcard syntax.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Signed-off-by: Joseph <jvaikath@redhat.com>

* Fix build-image Dockerfile envtest download

Replace inaccessible go.kubebuilder.io URL with setup-envtest and update envtest version to 1.33.0 to match Kubernetes v0.33.3 dependencies.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Signed-off-by: Joseph <jvaikath@redhat.com>

* kubebuilder binaries mv

Signed-off-by: Joseph <jvaikath@redhat.com>

* Reject brace patterns and update documentation

Add {, }, and , to unsupported characters list to explicitly reject
brace expansion patterns. Remove { from wildcard detection since these
patterns are not supported in the 1.18 release.

Update all documentation to show supported patterns inline (*, ?, [abc])
with clickable links to the detailed namespace-glob-patterns page.
Simplify YAML comments by removing non-clickable URLs.

Update tests to expect errors when brace patterns are used.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Signed-off-by: Joseph <jvaikath@redhat.com>

* Document brace expansion as unsupported

Add {} and , to the unsupported patterns section to clarify that
brace expansion patterns like {a,b,c} are not supported.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Signed-off-by: Joseph <jvaikath@redhat.com>

* Update tests to expect brace pattern rejection

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Signed-off-by: Joseph <jvaikath@redhat.com>

---------

Signed-off-by: Joseph <jvaikath@redhat.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-11 12:43:55 -05:00
lyndon-li
7af688fbf5 Merge pull request #9508 from kaovilai/9507
Some checks failed
Run the E2E test on kind / get-go-version (push) Failing after 1m41s
Run the E2E test on kind / build (push) Has been skipped
Run the E2E test on kind / setup-test-matrix (push) Successful in 5s
Run the E2E test on kind / run-e2e-test (push) Has been skipped
Main CI / get-go-version (push) Successful in 18s
Main CI / Build (push) Failing after 34s
Close stale issues and PRs / stale (push) Successful in 15s
Trivy Nightly Scan / Trivy nightly scan (velero, main) (push) Failing after 1m58s
Trivy Nightly Scan / Trivy nightly scan (velero-plugin-for-aws, main) (push) Failing after 1m34s
Trivy Nightly Scan / Trivy nightly scan (velero-plugin-for-gcp, main) (push) Failing after 2m0s
Trivy Nightly Scan / Trivy nightly scan (velero-plugin-for-microsoft-azure, main) (push) Failing after 1m45s
Fix VolumePolicy PVC phase condition filter for unbound PVCs (#9507)
2026-02-10 17:53:46 +08:00
Lyndon-Li
41fa774844 support custom os
Signed-off-by: Lyndon-Li <lyonghui@vmware.com>
2026-02-10 13:35:07 +08:00
Lyndon-Li
5121417457 Merge branch 'main' into support-customized-host-os 2026-02-09 18:36:55 +08:00
Lyndon-Li
ece04e6e39 Merge branch 'main' into issue-fix-9343 2026-02-09 18:34:14 +08:00
Tiger Kaovilai
71ddeefcd6 Fix VolumePolicy PVC phase condition filter for unbound PVCs
Use typed error approach: Make GetPVForPVC return ErrPVNotFoundForPVC
when PV is not expected to be found (unbound PVC), then use errors.Is
to check for this error type. When a matching policy exists (e.g.,
pvcPhase: [Pending, Lost] with action: skip), apply the action without
error. When no policy matches, return the original error to preserve
default behavior.

Changes:
- Add ErrPVNotFoundForPVC sentinel error to pvc_pv.go
- Update ShouldPerformSnapshot to handle unbound PVCs with policies
- Update ShouldPerformFSBackup to handle unbound PVCs with policies
- Update item_backupper.go to handle Lost PVCs in tracking functions
- Remove checkPVCOnlySkip helper (no longer needed)
- Update tests to reflect new behavior

Signed-off-by: Tiger Kaovilai <tkaovila@redhat.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-09 01:03:45 -05:00
Xun Jiang/Bruce Jiang
e159992f48 Merge pull request #9529 from Lyndon-Li/move-implemented-design-for-1.18
Some checks failed
Run the E2E test on kind / get-go-version (push) Failing after 1m49s
Run the E2E test on kind / build (push) Has been skipped
Run the E2E test on kind / setup-test-matrix (push) Successful in 3s
Run the E2E test on kind / run-e2e-test (push) Has been skipped
Main CI / get-go-version (push) Successful in 15s
Main CI / Build (push) Failing after 34s
Close stale issues and PRs / stale (push) Successful in 18s
Trivy Nightly Scan / Trivy nightly scan (velero, main) (push) Failing after 1m44s
Trivy Nightly Scan / Trivy nightly scan (velero-plugin-for-aws, main) (push) Failing after 1m25s
Trivy Nightly Scan / Trivy nightly scan (velero-plugin-for-gcp, main) (push) Failing after 1m35s
Trivy Nightly Scan / Trivy nightly scan (velero-plugin-for-microsoft-azure, main) (push) Failing after 1m36s
Move implemented design for 1.18
2026-02-09 10:32:08 +08:00
Lyndon-Li
48b14194df move implemented design for 1.18
Signed-off-by: Lyndon-Li <lyonghui@vmware.com>
2026-02-06 18:46:41 +08:00
lyndon-li
556d5826a8 Merge pull request #9523 from Lyndon-Li/1.18-release-notes
Some checks failed
Run the E2E test on kind / get-go-version (push) Failing after 1m20s
Run the E2E test on kind / build (push) Has been skipped
Run the E2E test on kind / setup-test-matrix (push) Successful in 3s
Run the E2E test on kind / run-e2e-test (push) Has been skipped
Main CI / get-go-version (push) Successful in 15s
Main CI / Build (push) Failing after 38s
1.18 release note and changelog
2026-02-06 13:47:20 +08:00
Lyndon-Li
62939cec18 1.18 release note and changelog
Signed-off-by: Lyndon-Li <lyonghui@vmware.com>
2026-02-06 13:18:59 +08:00
lyndon-li
7d6a10d3ea Merge pull request #9524 from Lyndon-Li/1.18-doc
Add 1.18 doc
2026-02-06 13:16:17 +08:00
Xun Jiang/Bruce Jiang
1c0cf6c51d Merge pull request #9525 from reasonerjt/add-broadcom-to-adopters
Some checks failed
Run the E2E test on kind / get-go-version (push) Failing after 1m38s
Run the E2E test on kind / build (push) Has been skipped
Run the E2E test on kind / setup-test-matrix (push) Successful in 4s
Run the E2E test on kind / run-e2e-test (push) Has been skipped
Main CI / get-go-version (push) Successful in 29s
Main CI / Build (push) Failing after 37s
Close stale issues and PRs / stale (push) Successful in 15s
Trivy Nightly Scan / Trivy nightly scan (velero, main) (push) Failing after 1m42s
Trivy Nightly Scan / Trivy nightly scan (velero-plugin-for-aws, main) (push) Failing after 1m40s
Trivy Nightly Scan / Trivy nightly scan (velero-plugin-for-gcp, main) (push) Failing after 1m29s
Trivy Nightly Scan / Trivy nightly scan (velero-plugin-for-microsoft-azure, main) (push) Failing after 1m23s
Add Broadcom to adopters list
2026-02-05 18:33:27 +08:00
Daniel Jiang
58f0b29091 Add Broadcom to adopters list
Signed-off-by: Daniel Jiang <daniel.jiang@broadcom.com>
2026-02-05 17:24:49 +08:00
Lyndon-Li
5cb4cdba61 add 1.18 doc
Signed-off-by: Lyndon-Li <lyonghui@vmware.com>
2026-02-05 15:23:39 +08:00
Xun Jiang/Bruce Jiang
325eb50480 Merge pull request #9522 from Lyndon-Li/1.18-readme
Update readme for 1.18
2026-02-05 15:15:40 +08:00
Lyndon-Li
993b80a350 update readme for 1.18
Signed-off-by: Lyndon-Li <lyonghui@vmware.com>
2026-02-05 14:22:37 +08:00
Wenkai Yin(尹文开)
a909bd1f85 Merge pull request #9518 from blackpiglet/1.18_fix_cve
Some checks failed
Run the E2E test on kind / get-go-version (push) Failing after 1m24s
Run the E2E test on kind / build (push) Has been skipped
Run the E2E test on kind / setup-test-matrix (push) Successful in 5s
Run the E2E test on kind / run-e2e-test (push) Has been skipped
Main CI / get-go-version (push) Successful in 15s
Main CI / Build (push) Failing after 34s
Close stale issues and PRs / stale (push) Successful in 17s
Trivy Nightly Scan / Trivy nightly scan (velero, main) (push) Failing after 1m53s
Trivy Nightly Scan / Trivy nightly scan (velero-plugin-for-aws, main) (push) Failing after 1m24s
Trivy Nightly Scan / Trivy nightly scan (velero-plugin-for-gcp, main) (push) Failing after 1m20s
Trivy Nightly Scan / Trivy nightly scan (velero-plugin-for-microsoft-azure, main) (push) Failing after 1m21s
Bump golang.org/x/crypto to v0.45.0 to fix CVEs in restic
2026-02-04 17:10:08 +08:00
Xun Jiang/Bruce Jiang
62a47b9fc5 Merge pull request #9521 from reasonerjt/cleanup-maintainers-md
Clean up MAINTAINERS.md
2026-02-04 16:10:42 +08:00
Daniel Jiang
31e9dcbb87 Clean up MAINTAINERS.md
Update the affiliation of maintainers from Broadcom.
Removes the company-specific roles.

Signed-off-by: Daniel Jiang <daniel.jiang@broadcom.com>
2026-02-04 15:34:14 +08:00
Xun Jiang
f824c3ca3b Bump golang.org/x/crypto to v0.45.0 to fix CVEs.
* CVE-2025-47914
* CVE-2025-58181

Signed-off-by: Xun Jiang <xun.jiang@broadcom.com>
2026-02-03 17:23:01 +08:00
Lyndon-Li
18c32ed29c support customized host os
Signed-off-by: Lyndon-Li <lyonghui@vmware.com>
2026-01-27 15:23:25 +08:00
Lyndon-Li
598c8c528b support customized host os - use affinity for host os selection
Signed-off-by: Lyndon-Li <lyonghui@vmware.com>
2026-01-27 14:49:55 +08:00
Lyndon-Li
8f9beb04f0 support customized host os
Signed-off-by: Lyndon-Li <lyonghui@vmware.com>
2026-01-27 14:37:38 +08:00
Lyndon-Li
bb518e6d89 replace nodeName with node selector
Signed-off-by: Lyndon-Li <lyonghui@vmware.com>
2026-01-26 13:58:29 +08:00
Lyndon-Li
89c5182c3c flush volume after restore
Signed-off-by: Lyndon-Li <lyonghui@vmware.com>
2026-01-26 13:17:44 +08:00
Lyndon-Li
d17435542e Merge branch 'main' into uploader-flush-buffer 2026-01-26 11:15:14 +08:00
Lyndon-Li
e3b501d0d9 issue 9343: include PV topology to data mover pod affinities
Signed-off-by: Lyndon-Li <lyonghui@vmware.com>
2026-01-23 15:45:43 +08:00
Lyndon-Li
060b3364f2 uploader flush buffer for restore
Signed-off-by: Lyndon-Li <lyonghui@vmware.com>
2025-12-29 18:19:23 +08:00
246 changed files with 14989 additions and 1402 deletions

View File

@@ -1,197 +0,0 @@
# AI Issue Detection - Examples
This document provides examples to help understand what triggers AI detection.
## Example 1: High AI Score (Score: 6/8) ❌
**This would be flagged:**
```markdown
## Description
When deploying Velero on an EKS cluster with `hostNetwork: true`, the application fails to start.
## Critical Problem
```
time="2026-01-26T16:40:55Z" level=fatal msg="failed to start metrics server"
```
Status: BLOCKER
## Affected Environment
| Parameter | Value |
|----------|----------|
| Cluster | Amazon EKS |
| Velero Version | 1.8.2 |
| Kubernetes | 1.33 |
## Root Cause Analysis
The controller-runtime metrics uses port 8080 as a hardcoded default...
## Resolution Attempts
### Attempt 1: Use extraArgs
Result: Failed
### Attempt 2: Configure metricsAddress
Result: Failed
## Expected Permanent Solution
Velero should:
1. Auto-detect an available port
2. Accept configuring the controller-runtime port
## Questions for Maintainers
1. Why does controller-runtime use hardcoded 8080?
2. Is there a roadmap to support hostNetwork?
## Labels and Metadata
Severity: CRITICAL
```
**Why flagged (Patterns detected: 6/8):**
-`futureDates` - References "2026-01-26" and "Kubernetes 1.33"
-`excessiveHeaders` - 8+ section headers
-`formalPhrases` - "Root Cause Analysis", "Expected Permanent Solution", "Questions for Maintainers", "Labels and Metadata"
-`aiSectionHeaders` - "## Description", "## Critical Problem", "## Affected Environment", "## Resolution Attempts"
-`perfectFormatting` - Perfect table structure
-`genericSolutions` - Mentions "auto-detect"
---
## Example 2: Medium AI Score (Score: 2/8) ✅
**This would NOT be flagged (below threshold):**
```markdown
**What steps did you take and what happened:**
I'm trying to restore a backup but getting this error:
```
error: backup "my-backup" not found
```
**What did you expect to happen:**
The backup should restore successfully
**Environment:**
- Velero version: 1.13.0
- Kubernetes version: 1.28
- Cloud provider: AWS
**Additional context:**
I can see the backup in S3 but Velero doesn't list it. Running `velero backup get` shows no backups.
```
**Why NOT flagged (Patterns detected: 2/8):**
-`futureDates` - Uses realistic versions
-`excessiveHeaders` - Only 3 headers
-`formalPhrases` - No formal AI phrases
-`excessiveTables` - Has a table but only 1
-`perfectFormatting` - Normal formatting
-`aiSectionHeaders` - Standard issue template headers
-`excessiveFormatting` - Has code blocks
-`genericSolutions` - No generic solutions
---
## Example 3: Legitimate Detailed Issue (Score: 3/8) ⚠️
**This would be flagged but is actually legitimate:**
```markdown
## Problem Description
VolumeGroupSnapshot restore fails with Ceph RBD driver.
## Environment
- Velero: 1.14.0
- Kubernetes: 1.28.3
- ODF: 4.14.2 with Ceph RBD CSI driver
## Root Cause
Ceph RBD stores group snapshot metadata in journal as `csi.groupid` omap key. During restore, when creating pre-provisioned VSC, the RBD driver reads this and populates `status.volumeGroupSnapshotHandle`.
The CSI snapshot controller looks for a VGSC with matching handle. Since Velero deletes VGSC after backup, it's not found.
## Reproduction Steps
1. Create backup with VGS
2. Delete namespace
3. Restore backup
4. Observe VS stuck with "cannot find group snapshot"
## Workaround
Create stub VGSC with matching `volumeGroupSnapshotHandle` and patch status.
## Proposed Fix
1. Backup: Capture `volumeGroupSnapshotHandle` in CSISnapshotInfo
2. Restore: Create stub VGSC if handle exists
## Code References
- Ceph RBD: https://github.com/ceph/ceph-csi/blob/devel/internal/rbd/snapshot.go#L167
- Velero deletion: https://github.com/vmware-tanzu/velero/blob/main/pkg/backup/actions/csi/pvc_action.go#L1124
```
**Why flagged (Patterns detected: 3/8):**
-`futureDates` - Uses current versions
-`excessiveHeaders` - Has 6 section headers
-`formalPhrases` - "Root Cause", "Proposed Fix"
-`excessiveTables` - No tables
-`perfectFormatting` - Normal formatting
-`aiSectionHeaders` - Technical, not generic
-`excessiveFormatting` - Reasonable formatting
-`genericSolutions` - Structured solution with code refs
**Maintainer Action**: This is a legitimate, well-researched issue. Verify the details with the contributor and remove the `potential-ai-generated` label.
---
## Example 4: Simple Valid Issue (Score: 0/8) ✅
**This would NOT be flagged:**
```markdown
Velero backup fails with error: `rpc error: code = Unavailable desc = connection error`
Running Velero 1.13 on GKE. Backups were working yesterday but now all fail with this error.
Logs show the node-agent pod is crashing. Any ideas?
```
**Why NOT flagged (Patterns detected: 0/8):**
- All patterns: None detected
---
## Key Takeaways
### Will Trigger Detection ❌
- Future dates/versions (2026+, K8s 1.33+)
- 4+ formal AI phrases
- 8+ section headers
- Perfect table formatting across multiple tables
- Generic AI section titles
- Auto-detect/generic solution patterns
### Will NOT Trigger ✅
- Realistic version numbers
- Actual error messages from real systems
- Normal issue formatting
- Moderate level of detail
- Standard GitHub issue template
### May Trigger (But Legitimate) ⚠️
- Very detailed technical analysis
- Multiple code references
- Well-structured proposals
- Extensive testing documentation
For these cases, maintainers will verify with the contributor and remove the flag once confirmed.

View File

@@ -1,80 +0,0 @@
# AI-Generated Content Detection
This directory contains the AI-generated content detection system for Velero issues.
## Overview
The Velero project has implemented automated detection of potentially AI-generated issues to help maintain quality and ensure that issues describe real, verified problems.
## How It Works
### Detection Workflow
The workflow (`.github/workflows/ai-issue-detector.yml`) runs automatically when:
- A new issue is opened
- An existing issue is edited
### Detection Patterns
The detector analyzes issues for several AI-generation patterns:
1. **Excessive Tables** - More than 5 markdown tables
2. **Excessive Headers** - More than 8 consecutive section headers
3. **Formal Phrases** - Multiple formal section headers typical of AI (e.g., "Root Cause Analysis", "Operational Impact", "Expected Permanent Solution")
4. **Excessive Formatting** - Multiple horizontal rules and perfect formatting
5. **Future Dates** - Version numbers or dates that are unrealistic or in the future
6. **Perfect Formatting** - Overly structured tables with perfect alignment
7. **AI Section Headers** - Generic AI-style headers like "Critical Problem", "Resolution Attempts"
8. **Generic Solutions** - Auto-generated solution patterns with multiple YAML examples
### Scoring System
Each detected pattern adds to the AI score. If the score is 3 or higher (out of 8), the issue is flagged as potentially AI-generated.
### Actions Taken
When an issue is flagged:
1. A `potential-ai-generated` label is added
2. A `needs-triage` label is added
3. An automated comment is posted explaining:
- Why the issue was flagged
- What patterns were detected
- Guidelines for contributors to follow
- Request for verification
## For Contributors
If your issue is flagged:
1. **Don't panic** - This is not an accusation, just a request for verification
2. **Review the guidelines** in our [Code Standards](../site/content/docs/main/code-standards.md#ai-generated-content)
3. **Verify your content**:
- Ensure all version numbers are accurate
- Confirm error messages are from your actual environment
- Remove any placeholder or example content
- Simplify overly structured formatting
4. **Update the issue** with corrections if needed
5. **Comment to confirm** that the issue describes a real problem
## For Maintainers
When reviewing flagged issues:
1. Check if the technical details are realistic and verifiable
2. Look for signs of hallucinated content (fake version numbers, non-existent features)
3. Engage with the issue author to verify the problem
4. Remove the `potential-ai-generated` label once verified
5. Close issues that cannot be verified or describe non-existent problems
## Configuration
The detection patterns can be adjusted in the workflow file if needed. The threshold is currently set at 3 out of 8 patterns to balance false positives with detection accuracy.
## False Positives
The detector may occasionally flag legitimate issues, especially those that are:
- Very detailed and well-structured
- Using formal technical documentation style
- Reporting complex problems with extensive details
This is intentional - we prefer to verify detailed issues rather than miss AI-generated ones.

View File

@@ -1,186 +0,0 @@
# Maintainer Guide: AI-Generated Issue Detection
This guide helps Velero maintainers understand and work with the AI-generated issue detection system.
## Overview
The AI detection system automatically analyzes new and edited issues to identify potential AI-generated content. This helps maintain issue quality and ensures contributors verify their submissions.
## How It Works
### Automatic Detection
When an issue is opened or edited, the workflow:
1. **Analyzes** the issue body for 8 different AI patterns
2. **Calculates** an AI confidence score (0-8)
3. **If score ≥ 3**: Adds labels and posts a comment
4. **If score < 3**: Takes no action (issue proceeds normally)
### Detection Patterns
| Pattern | Description | Weight |
|---------|-------------|--------|
| `excessiveTables` | More than 5 markdown tables | 1 |
| `excessiveHeaders` | More than 8 section headers | 1 |
| `formalPhrases` | 4+ AI-typical phrases (e.g., "Root Cause Analysis") | 1 |
| `excessiveFormatting` | Multiple horizontal rules (---) | 1 |
| `futureDates` | Dates/versions in 2026+ or 2030s | 1 |
| `perfectFormatting` | Multiple identical table structures | 1 |
| `aiSectionHeaders` | 4+ generic AI headers (e.g., "Critical Problem") | 1 |
| `genericSolutions` | Auto-detect patterns with multiple YAML blocks | 1 |
## Working with Flagged Issues
### Step 1: Review the Issue
When you see an issue labeled `potential-ai-generated`:
1. **Read the issue carefully**
2. **Check the detected patterns** (listed in the auto-comment)
3. **Look for red flags**:
- Future version numbers (e.g., "Kubernetes 1.33")
- Future dates (e.g., "2026-01-27")
- Non-existent features or configurations
- Perfect table formatting with no actual content
- Generic solutions that don't match Velero's architecture
### Step 2: Engage with the Contributor
**If the issue seems legitimate but over-formatted:**
```markdown
Thanks for the detailed report! Could you confirm:
1. Are you running Velero version X.Y.Z (you mentioned version A.B.C)?
2. Is the error message exactly as shown?
3. Have you actually tried the workarounds mentioned?
Once verified, we'll remove the AI-generated flag and investigate.
```
**If the issue appears to be unverified AI content:**
```markdown
This issue appears to contain AI-generated content that hasn't been verified.
Please review our [AI contribution guidelines](https://github.com/vmware-tanzu/velero/blob/main/site/content/docs/main/code-standards.md#ai-generated-content) and:
1. Confirm this describes a real problem in your environment
2. Verify all version numbers and error messages
3. Remove any placeholder or example content
4. Test that the issue is reproducible
If you can't verify the issue, please close it. We're happy to help with real problems!
```
### Step 3: Take Action
**For verified issues:**
1. Remove the `potential-ai-generated` label
2. Keep or remove `needs-triage` as appropriate
3. Proceed with normal issue triage
**For unverified/invalid issues:**
1. Request verification (see templates above)
2. If no response after 7 days, consider closing as `stale`
3. If clearly invalid, close with explanation
## Common Patterns
### False Positives (Legitimate Issues)
These may trigger the detector but are usually valid:
- **Very detailed bug reports** with extensive logs and testing
- **Technical design proposals** with multiple sections
- **Well-organized feature requests** with tables and examples
**Action**: Engage with contributor, ask clarifying questions, remove flag if verified.
### True Positives (AI-Generated)
Red flags that indicate unverified AI content:
- **Future version numbers**: "Kubernetes 1.33" (doesn't exist yet)
- **Future dates**: "2026-01-27" (if current date is before)
- **Non-existent features**: References to Velero features that don't exist
- **Generic solutions**: "Auto-detect available port" (not how Velero works)
- **Perfect formatting, wrong content**: Beautiful tables with incorrect info
**Action**: Request verification, ask for actual environment details, consider closing if unverified.
### Edge Cases
**Contributor using AI as a writing assistant:**
- Issue content is verified and accurate
- Just used AI to help structure/format the report
- **Action**: This is acceptable! Remove flag if content is verified.
**Legitimate issue that happens to match patterns:**
- Real problem with detailed analysis
- Includes proper version numbers and logs
- **Action**: Verify with contributor, remove flag once confirmed.
## Statistics and Monitoring
You can search for flagged issues:
```
is:issue label:potential-ai-generated
```
Monitor trends:
- High detection rate → May need to adjust thresholds
- Low detection rate → Patterns working well or need refinement
## Adjusting the System
### Modifying Detection Patterns
Edit `.github/workflows/ai-issue-detector.yml`:
```javascript
// Increase threshold to reduce false positives
if (aiScore >= 4) { // was 3
// Adjust pattern sensitivity
excessiveTables: (issueBody.match(/\|.*\|/g) || []).length > 8, // was 5
```
### Adding New Patterns
Add to the `aiPatterns` object:
```javascript
// Example: Detect excessive use of emojis
excessiveEmojis: (issueBody.match(/[\u{1F300}-\u{1F9FF}]/gu) || []).length > 10,
```
### Disabling the Workflow
Rename or delete `.github/workflows/ai-issue-detector.yml`
## Best Practices
1. **Be courteous**: Contributors may not realize their AI tool generated incorrect info
2. **Verify, don't assume**: Some detailed issues are legitimate
3. **Educate**: Point to the AI guidelines in code-standards.md
4. **Track patterns**: Note common AI-generated patterns for future improvements
5. **Iterate**: Adjust detection thresholds based on false positive rates
## FAQ
**Q: Should we reject all AI-assisted contributions?**
A: No! AI assistance is fine if the contributor verifies accuracy. We only flag unverified AI content.
**Q: What if a contributor is offended by the flag?**
A: Explain it's automated and not personal. We just need verification of technical details.
**Q: Can we automatically close flagged issues?**
A: No. Always engage with the contributor first. Some are legitimate.
**Q: What's an acceptable false positive rate?**
A: Aim for <10%. If higher, increase the threshold from 3 to 4 or 5.
## Support
Questions about the AI detection system? Tag @vmware-tanzu/velero-maintainers in issue #9501.

1
.github/labels.yaml vendored
View File

@@ -41,4 +41,3 @@ kind:
- tech-debt
- usage-error
- voting
- potential-ai-generated

View File

@@ -1,132 +0,0 @@
name: "Detect AI-Generated Issues"
on:
issues:
types: [opened, edited]
jobs:
detect-ai-content:
runs-on: ubuntu-latest
permissions:
issues: write
contents: read
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Analyze issue for AI-generated content
id: analyze
uses: actions/github-script@v7
with:
script: |
const issue = context.payload.issue;
const issueBody = issue.body || '';
const issueTitle = issue.title || '';
// AI detection patterns
const aiPatterns = {
// Overly structured markdown with extensive tables
excessiveTables: (issueBody.match(/\|.*\|/g) || []).length > 5,
// Multiple consecutive headers with consistent formatting
excessiveHeaders: (issueBody.match(/^#{1,6}\s+/gm) || []).length > 8,
// Overly formal language patterns common in AI
formalPhrases: [
'Root Cause Analysis',
'Operational Impact',
'Expected Permanent Solution',
'Questions for Maintainers',
'Labels and Metadata',
'Reference Files',
'Steps to Reproduce'
].filter(phrase => issueBody.includes(phrase)).length > 4,
// Excessive use of emojis or special characters
excessiveFormatting: issueBody.includes('---\n \n---') ||
(issueBody.match(/---/g) || []).length > 4,
// Unrealistic version numbers or dates in the future
futureDates: /202[6-9]|203\d/.test(issueBody),
// Overly detailed technical specs with perfect formatting
perfectFormatting: issueBody.includes('| Parameter | Value |') &&
issueBody.includes('| Aspect | Status | Impact |'),
// Generic AI-style section headers
aiSectionHeaders: [
'## Description',
'## Critical Problem',
'## Affected Environment',
'## Full Helm Configuration',
'## Resolution Attempts',
'## Related Information'
].filter(header => issueBody.includes(header)).length > 4,
// Unusual specificity combined with generic solutions
genericSolutions: issueBody.includes('auto-detect') &&
issueBody.includes('configuration:') &&
(issueBody.match(/```yaml/g) || []).length > 2
};
// Calculate AI score
let aiScore = 0;
let detectedPatterns = [];
for (const [pattern, detected] of Object.entries(aiPatterns)) {
if (detected) {
aiScore++;
detectedPatterns.push(pattern);
}
}
console.log('AI Score: ' + aiScore + '/8');
console.log('Detected patterns: ' + detectedPatterns.join(', '));
// If AI score is high, add label and comment
if (aiScore >= 3) {
// Add label
try {
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
labels: ['needs-triage', 'potential-ai-generated']
});
// Add comment
const confidence = Math.round(aiScore/8 * 100);
const repoPath = context.repo.owner + '/' + context.repo.repo;
const comment = '👋 Thank you for opening this issue!\n\n' +
'This issue has been flagged for review as it may contain AI-generated content (confidence: ' + confidence + '%).\n\n' +
'**Detected patterns:** ' + detectedPatterns.join(', ') + '\n\n' +
'If this issue was created with AI assistance, please review our [AI contribution guidelines](https://github.com/' + repoPath + '/blob/main/site/content/docs/main/code-standards.md#ai-generated-content).\n\n' +
'**Important:**\n' +
'- Please verify all technical details are accurate\n' +
'- Ensure version numbers, dates, and configurations reflect your actual environment\n' +
'- Remove any placeholder or example content\n' +
'- Confirm the issue is reproducible in your environment\n\n' +
'A maintainer will review this issue shortly. If this was flagged in error, please let us know!';
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
body: comment
});
core.setOutput('ai-detected', 'true');
core.setOutput('ai-score', aiScore);
} catch (error) {
console.log('Error adding label or comment:', error);
}
} else {
core.setOutput('ai-detected', 'false');
core.setOutput('ai-score', aiScore);
}
return {
aiDetected: aiScore >= 3,
score: aiScore,
patterns: detectedPatterns
};

View File

@@ -17,6 +17,7 @@ If you're using Velero and want to add your organization to this list,
<a href="https://www.replicated.com/" border="0" target="_blank"><img alt="replicated.com" src="site/static/img/adopters/replicated-logo-red.svg" height="50"></a>
<a href="https://cloudcasa.io/" border="0" target="_blank"><img alt="cloudcasa.io" src="site/static/img/adopters/cloudcasa.svg" height="50"></a>
<a href="https://azure.microsoft.com/" border="0" target="_blank"><img alt="azure.com" src="site/static/img/adopters/azure.svg" height="50"></a>
<a href="https://www.broadcom.com/" border="0" target="_blank"><img alt="broadcom.com" src="site/static/img/adopters/broadcom.svg" height="50"></a>
## Success Stories
Below is a list of adopters of Velero in **production environments** that have
@@ -68,6 +69,9 @@ Replicated uses the Velero open source project to enable snapshots in [KOTS][101
**[Microsoft Azure][105]**<br>
[Azure Backup for AKS][106] is an Azure native, Kubernetes aware, Enterprise ready backup for containerized applications deployed on Azure Kubernetes Service (AKS). AKS Backup utilizes Velero to perform backup and restore operations to protect stateful applications in AKS clusters.<br>
**[Broadcom][107]**<br>
[VMware Cloud Foundation][108] (VCF) offers built-in [vSphere Kubernetes Service][109] (VKS), a Kubernetes runtime that includes a CNCF certified Kubernetes distribution, to deploy and manage containerized workloads. VCF empowers platform engineers with native [Kubernetes multi-cluster management][110] capability for managing Kubernetes (K8s) infrastructure at scale. VCF utilizes Velero for Kubernetes data protection enabling platform engineers to back up and restore containerized workloads manifests & persistent volumes, helping to increase the resiliency of stateful applications in VKS cluster.
## Adding your organization to the list of Velero Adopters
If you are using Velero and would like to be included in the list of `Velero Adopters`, add an SVG version of your logo to the `site/static/img/adopters` directory in this repo and submit a [pull request][3] with your change. Name the image file something that reflects your company (e.g., if your company is called Acme, name the image acme.png). See this for an example [PR][4].
@@ -125,3 +129,8 @@ If you would like to add your logo to a future `Adopters of Velero` section on [
[105]: https://azure.microsoft.com/
[106]: https://learn.microsoft.com/azure/backup/backup-overview
[107]: https://www.broadcom.com/
[108]: https://www.vmware.com/products/cloud-infrastructure/vmware-cloud-foundation
[109]: https://www.vmware.com/products/cloud-infrastructure/vsphere-kubernetes-service
[110]: https://blogs.vmware.com/cloud-foundation/2025/09/29/empowering-platform-engineers-with-native-kubernetes-multi-cluster-management-in-vmware-cloud-foundation/

View File

@@ -7,11 +7,11 @@
| Maintainer | GitHub ID | Affiliation |
|---------------------|---------------------------------------------------------------|--------------------------------------------------|
| Scott Seago | [sseago](https://github.com/sseago) | [OpenShift](https://github.com/openshift) |
| Daniel Jiang | [reasonerjt](https://github.com/reasonerjt) | [VMware](https://www.github.com/vmware/) |
| Wenkai Yin | [ywk253100](https://github.com/ywk253100) | [VMware](https://www.github.com/vmware/) |
| Xun Jiang | [blackpiglet](https://github.com/blackpiglet) | [VMware](https://www.github.com/vmware/) |
| Daniel Jiang | [reasonerjt](https://github.com/reasonerjt) | Broadcom |
| Wenkai Yin | [ywk253100](https://github.com/ywk253100) | Broadcom |
| Xun Jiang | [blackpiglet](https://github.com/blackpiglet) | Broadcom |
| Shubham Pampattiwar | [shubham-pampattiwar](https://github.com/shubham-pampattiwar) | [OpenShift](https://github.com/openshift) |
| Yonghui Li | [Lyndon-Li](https://github.com/Lyndon-Li) | [VMware](https://www.github.com/vmware/) |
| Yonghui Li | [Lyndon-Li](https://github.com/Lyndon-Li) | Broadcom |
| Anshul Ahuja | [anshulahuja98](https://github.com/anshulahuja98) | [Microsoft Azure](https://www.github.com/azure/) |
| Tiger Kaovilai | [kaovilai](https://github.com/kaovilai) | [OpenShift](https://github.com/openshift) |
@@ -27,14 +27,3 @@
* JenTing Hsiao ([jenting](https://github.com/jenting))
* Dave Smith-Uchida ([dsu-igeek](https://github.com/dsu-igeek))
* Ming Qiu ([qiuming-best](https://github.com/qiuming-best))
## Velero Contributors & Stakeholders
| Feature Area | Lead |
|------------------------|:------------------------------------------------------------------------------------:|
| Technical Lead | Daniel Jiang [reasonerjt](https://github.com/reasonerjt) |
| Kubernetes CSI Liaison | |
| Deployment | |
| Community Management | Orlin Vasilev [OrlinVasilev](https://github.com/OrlinVasilev) |
| Product Management | Pradeep Kumar Chaturvedi [pradeepkchaturvedi](https://github.com/pradeepkchaturvedi) |

View File

@@ -42,13 +42,11 @@ The following is a list of the supported Kubernetes versions for each Velero ver
| Velero version | Expected Kubernetes version compatibility | Tested on Kubernetes version |
|----------------|-------------------------------------------|-------------------------------------|
| 1.17 | 1.18-latest | 1.31.7, 1.32.3, 1.33.1, and 1.34.0 |
| 1.18 | 1.18-latest | 1.33.7, 1.34.1, and 1.35.0 |
| 1.17 | 1.18-latest | 1.31.7, 1.32.3, 1.33.1, and 1.34.0 |
| 1.16 | 1.18-latest | 1.31.4, 1.32.3, and 1.33.0 |
| 1.15 | 1.18-latest | 1.28.8, 1.29.8, 1.30.4 and 1.31.1 |
| 1.14 | 1.18-latest | 1.27.9, 1.28.9, and 1.29.4 |
| 1.13 | 1.18-latest | 1.26.5, 1.27.3, 1.27.8, and 1.28.3 |
| 1.12 | 1.18-latest | 1.25.7, 1.26.5, 1.26.7, and 1.27.3 |
| 1.11 | 1.18-latest | 1.23.10, 1.24.9, 1.25.5, and 1.26.1 |
Velero supports IPv4, IPv6, and dual stack environments. Support for this was tested against Velero v1.8.

View File

@@ -0,0 +1,109 @@
## v1.18
### Download
https://github.com/vmware-tanzu/velero/releases/tag/v1.18.0
### Container Image
`velero/velero:v1.18.0`
### Documentation
https://velero.io/docs/v1.18/
### Upgrading
https://velero.io/docs/v1.18/upgrade-to-1.18/
### Highlights
#### Concurrent backup
In v1.18, Velero is capable to process multiple backups concurrently. This is a significant usability improvement, especially for multiple tenants or multiple users case, backups submitted from different users could run their backups simultaneously without interfering with each other.
Check design https://github.com/vmware-tanzu/velero/blob/main/design/Implemented/concurrent-backup-processing.md for more details.
#### Cache volume for data movers
In v1.18, Velero allows users to configure cache volumes for data mover pods during restore for CSI snapshot data movement and fs-backup. This brings below benefits:
- Solve the problem that data mover pods fail to when pod's ephemeral disk is limited
- Solve the problem that multiple data mover pods fail to run concurrently in one node when the node's ephemeral disk is limited
- Working together with backup repository's cache limit configuration, cache volume with appropriate size helps to improve the restore throughput
Check design https://github.com/vmware-tanzu/velero/blob/main/design/Implemented/backup-repo-cache-volume.md for more details.
#### Incremental size for data movers
In v1.18, Velero allows users to observe the incremental size of data movers backups for CSI snapshot data movement and fs-backup, so that users could visually see the data reduction due to incremental backup.
#### Wildcard support for namespaces
In v1.18, Velero allows to use Glob regular expressions for namespace filters during backup and restore, so that users could filter namespaces in a batch manner.
#### VolumePolicy for PVC phase
In v1.18, Velero VolumePolicy supports actions by PVC phase, which help users to do special operations for PVCs with a specific phase, e.g., skip PVCs in Pending/Lost status from the backup.
#### Scalability and Resiliency improvements
##### Prevent Velero server OOM Kill for large backup repositories
In v1.18, some backup repository operations are delay executed out of Velero server, so Velero server won't be OOM Killed.
#### Performance improvement for VolumePolicy
In v1.18, VolumePolicy is enhanced for large number of pods/PVCs so that the performance is significantly improved.
#### Events for data mover pod diagnostic
In v1.18, events are recorded into data mover pod diagnostic, which allows user to see more information for troubleshooting when the data mover pod fails.
### Runtime and dependencies
Golang runtime: 1.25.7
kopia: 0.22.3
### Limitations/Known issues
### Breaking changes
#### Deprecation of PVC selected node feature
According to [Velero deprecation policy](https://github.com/vmware-tanzu/velero/blob/main/GOVERNANCE.md#deprecation-policy), PVC selected node feature is deprecated in v1.18. Velero could appropriately handle PVC's selected-node annotation, so users don't need to do anything particularly.
### All Changes
* Remove backup from running list when backup fails validation (#9498, @sseago)
* Maintenance Job only uses the first element of the LoadAffinity array (#9494, @blackpiglet)
* Fix issue #9478, add diagnose info on expose peek fails (#9481, @Lyndon-Li)
* Add Role, RoleBinding, ClusterRole, and ClusterRoleBinding in restore sequence. (#9474, @blackpiglet)
* Add maintenance job and data mover pod's labels and annotations setting. (#9452, @blackpiglet)
* Fix plugin init container names exceeding DNS-1123 limit (#9445, @mpryc)
* Add PVC-to-Pod cache to improve volume policy performance (#9441, @shubham-pampattiwar)
* Remove VolumeSnapshotClass from CSI B/R process. (#9431, @blackpiglet)
* Use hookIndex for recording multiple restore exec hooks. (#9366, @blackpiglet)
* Sanitize Azure HTTP responses in BSL status messages (#9321, @shubham-pampattiwar)
* Remove labels associated with previous backups (#9206, @Joeavaikath)
* Add VolumePolicy support for PVC Phase conditions to allow skipping Pending PVCs (#9166, @claude)
* feat: Enhance BackupStorageLocation with Secret-based CA certificate support (#9141, @kaovilai)
* Add `--apply` flag to `install` command, allowing usage of Kubernetes apply to make changes to existing installs (#9132, @mjnagel)
* Fix issue #9194, add doc for GOMAXPROCS behavior change (#9420, @Lyndon-Li)
* Apply volume policies to VolumeGroupSnapshot PVC filtering (#9419, @shubham-pampattiwar)
* Fix issue #9276, add doc for cache volume support (#9418, @Lyndon-Li)
* Add Prometheus metrics for maintenance jobs (#9414, @shubham-pampattiwar)
* Fix issue #9400, connect repo first time after creation so that init params could be written (#9407, @Lyndon-Li)
* Cache volume for PVR (#9397, @Lyndon-Li)
* Cache volume support for DataDownload (#9391, @Lyndon-Li)
* don't copy securitycontext from first container if configmap found (#9389, @sseago)
* Refactor repo provider interface for static configuration (#9379, @Lyndon-Li)
* Fix issue #9365, prevent fake completion notification due to multiple update of single PVR (#9375, @Lyndon-Li)
* Add cache volume configuration (#9370, @Lyndon-Li)
* Track actual resource names for GenerateName in restore status (#9368, @shubham-pampattiwar)
* Fix managed fields patch for resources using GenerateName (#9367, @shubham-pampattiwar)
* Support cache volume for generic restore exposer and pod volume exposer (#9362, @Lyndon-Li)
* Add incrementalSize to DU/PVB for reporting new/changed size (#9357, @sseago)
* Add snapshotSize for DataDownload, PodVolumeRestore (#9354, @Lyndon-Li)
* Add cache dir configuration for udmrepo (#9353, @Lyndon-Li)
* Fix the Job build error when BackupReposiotry name longer than 63. (#9350, @blackpiglet)
* Add cache configuration to VGDP (#9342, @Lyndon-Li)
* Fix issue #9332, add bytesDone for cache files (#9333, @Lyndon-Li)
* Fix typos in documentation (#9329, @T4iFooN-IX)
* Concurrent backup processing (#9307, @sseago)
* VerifyJSONConfigs verify every elements in Data. (#9302, @blackpiglet)
* Fix issue #9267, add events to data mover prepare diagnostic (#9296, @Lyndon-Li)
* Add option for privileged fs-backup pod (#9295, @sseago)
* Fix issue #9193, don't connect repo in repo controller (#9291, @Lyndon-Li)
* Implement concurrency control for cache of native VolumeSnapshotter plugin. (#9281, @0xLeo258)
* Fix issue #7904, remove the code and doc for PVC node selection (#9269, @Lyndon-Li)
* Fix schedule controller to prevent backup queue accumulation during extended blocking scenarios by properly handling empty backup phases (#9264, @shubham-pampattiwar)
* Fix repository maintenance jobs to inherit allowlisted tolerations from Velero deployment (#9256, @shubham-pampattiwar)
* Implement wildcard namespace pattern expansion for backup namespace includes/excludes. This change adds support for wildcard patterns (*, ?, [abc], {a,b,c}) in namespace includes and excludes during backup operations (#9255, @Joeavaikath)
* Protect VolumeSnapshot field from race condition during multi-thread backup (#9248, @0xLeo258)
* Update AzureAD Microsoft Authentication Library to v1.5.0 (#9244, @priyansh17)
* Get pod list once per namespace in pvc IBA (#9226, @sseago)
* Fix issue #7725, add design for backup repo cache configuration (#9148, @Lyndon-Li)
* Fix issue #9229, don't attach backupPVC to the source node (#9233, @Lyndon-Li)
* feat: Permit specifying annotations for the BackupPVC (#9173, @clementnuss)

View File

@@ -1 +0,0 @@
Add `--apply` flag to `install` command, allowing usage of Kubernetes apply to make changes to existing installs

View File

@@ -1 +0,0 @@
feat: Enhance BackupStorageLocation with Secret-based CA certificate support

View File

@@ -1 +0,0 @@
Fix issue #7725, add design for backup repo cache configuration

View File

@@ -1 +0,0 @@
Add VolumePolicy support for PVC Phase conditions to allow skipping Pending PVCs

View File

@@ -1 +0,0 @@
feat: Permit specifying annotations for the BackupPVC

View File

@@ -1 +0,0 @@
Remove labels associated with previous backups

View File

@@ -1 +0,0 @@
Get pod list once per namespace in pvc IBA

View File

@@ -1 +0,0 @@
Fix issue #9229, don't attach backupPVC to the source node

View File

@@ -1 +0,0 @@
Update AzureAD Microsoft Authentication Library to v1.5.0

View File

@@ -1 +0,0 @@
Protect VolumeSnapshot field from race condition during multi-thread backup

View File

@@ -1,10 +0,0 @@
Implement wildcard namespace pattern expansion for backup namespace includes/excludes.
This change adds support for wildcard patterns (*, ?, [abc], {a,b,c}) in namespace includes and excludes during backup operations.
When wildcard patterns are detected, they are expanded against the list of active namespaces in the cluster before the backup proceeds.
Key features:
- Wildcard patterns in namespace includes/excludes are automatically detected and expanded
- Pattern validation ensures unsupported patterns (regex, consecutive asterisks) are rejected
- Empty wildcard results (e.g., "invalid*" matching no namespaces) correctly result in empty backups
- Exact namespace names and "*" continue to work as before (no expansion needed)

View File

@@ -1 +0,0 @@
Fix repository maintenance jobs to inherit allowlisted tolerations from Velero deployment

View File

@@ -1 +0,0 @@
Fix schedule controller to prevent backup queue accumulation during extended blocking scenarios by properly handling empty backup phases

View File

@@ -1 +0,0 @@
Fix issue #7904, remove the code and doc for PVC node selection

View File

@@ -1 +0,0 @@
Implement concurrency control for cache of native VolumeSnapshotter plugin.

View File

@@ -1 +0,0 @@
Fix issue #9193, don't connect repo in repo controller

View File

@@ -1 +0,0 @@
Add option for privileged fs-backup pod

View File

@@ -1 +0,0 @@
Fix issue #9267, add events to data mover prepare diagnostic

View File

@@ -1 +0,0 @@
VerifyJSONConfigs verify every elements in Data.

View File

@@ -1 +0,0 @@
Concurrent backup processing

View File

@@ -1 +0,0 @@
Sanitize Azure HTTP responses in BSL status messages

View File

@@ -1 +0,0 @@
Fix typos in documentation

View File

@@ -1 +0,0 @@
Fix issue #9332, add bytesDone for cache files

View File

@@ -1 +0,0 @@
Add cache configuration to VGDP

View File

@@ -1 +0,0 @@
Fix the Job build error when BackupReposiotry name longer than 63.

View File

@@ -1 +0,0 @@
Add cache dir configuration for udmrepo

View File

@@ -1 +0,0 @@
Add snapshotSize for DataDownload, PodVolumeRestore

View File

@@ -1 +0,0 @@
Add incrementalSize to DU/PVB for reporting new/changed size

View File

@@ -1 +0,0 @@
Support cache volume for generic restore exposer and pod volume exposer

View File

@@ -1 +0,0 @@
Use hookIndex for recording multiple restore exec hooks.

View File

@@ -1 +0,0 @@
Fix managed fields patch for resources using GenerateName

View File

@@ -1 +0,0 @@
Track actual resource names for GenerateName in restore status

View File

@@ -1 +0,0 @@
Add cache volume configuration

View File

@@ -1 +0,0 @@
Fix issue #9365, prevent fake completion notification due to multiple update of single PVR

View File

@@ -1 +0,0 @@
Refactor repo provider interface for static configuration

View File

@@ -1 +0,0 @@
don't copy securitycontext from first container if configmap found

View File

@@ -1 +0,0 @@
Cache volume support for DataDownload

View File

@@ -1 +0,0 @@
Cache volume for PVR

View File

@@ -1 +0,0 @@
Fix issue #9400, connect repo first time after creation so that init params could be written

View File

@@ -1 +0,0 @@
Add Prometheus metrics for maintenance jobs

View File

@@ -1 +0,0 @@
Fix issue #9276, add doc for cache volume support

View File

@@ -1 +0,0 @@
Apply volume policies to VolumeGroupSnapshot PVC filtering

View File

@@ -1 +0,0 @@
Fix issue #9194, add doc for GOMAXPROCS behavior change

View File

@@ -1 +0,0 @@
Remove VolumeSnapshotClass from CSI B/R process.

View File

@@ -1 +0,0 @@
Add PVC-to-Pod cache to improve volume policy performance

View File

@@ -1 +0,0 @@
Fix plugin init container names exceeding DNS-1123 limit

View File

@@ -1 +0,0 @@
Add maintenance job and data mover pod's labels and annotations setting.

View File

@@ -1 +0,0 @@
Add Role, RoleBinding, ClusterRole, and ClusterRoleBinding in restore sequence.

View File

@@ -1 +0,0 @@
Fix issue #9478, add diagnose info on expose peek fails

View File

@@ -1 +0,0 @@
Maintenance Job only uses the first element of the LoadAffinity array

View File

@@ -1 +0,0 @@
Remove backup from running list when backup fails validation

View File

@@ -0,0 +1 @@
Support all glob wildcard characters in namespace validation

View File

@@ -0,0 +1 @@
Fix VolumePolicy PVC phase condition filter for unbound PVCs (#9507)

View File

@@ -0,0 +1 @@
Fix issue #9343, include PV topology to data mover pod affinities

View File

@@ -0,0 +1 @@
Fix issue #9496, support customized host os

View File

@@ -0,0 +1 @@
If BIA return updateObj with SkipFromBackupAnnotation, treat it as skip the resource from backup.

View File

@@ -0,0 +1 @@
Issue #9544: Add test coverage for S3 bucket name in MRAP ARN notation and fix bucket validation to accept ARN format

View File

@@ -0,0 +1 @@
Fix issue #9475, use node-selector instead of nodName for generic restore

View File

@@ -0,0 +1 @@
Fix issue #9460, flush buffer before data mover completes

View File

@@ -0,0 +1 @@
Add schedule_expected_interval_seconds metric for dynamic backup alerting thresholds (#9559)

View File

@@ -0,0 +1 @@
Add ephemeral storage limit and request support for data mover and maintenance job

View File

@@ -0,0 +1 @@
Fix DBR stuck when CSI snapshot no longer exists in cloud provider

12
go.mod
View File

@@ -43,6 +43,7 @@ require (
go.uber.org/zap v1.27.1
golang.org/x/mod v0.30.0
golang.org/x/oauth2 v0.33.0
golang.org/x/sys v0.40.0
golang.org/x/text v0.31.0
google.golang.org/api v0.256.0
google.golang.org/grpc v1.77.0
@@ -171,11 +172,11 @@ require (
go.opentelemetry.io/contrib/detectors/gcp v1.38.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect
go.opentelemetry.io/otel v1.38.0 // indirect
go.opentelemetry.io/otel/metric v1.38.0 // indirect
go.opentelemetry.io/otel/sdk v1.38.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.38.0 // indirect
go.opentelemetry.io/otel/trace v1.38.0 // indirect
go.opentelemetry.io/otel v1.40.0 // indirect
go.opentelemetry.io/otel/metric v1.40.0 // indirect
go.opentelemetry.io/otel/sdk v1.40.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.40.0 // indirect
go.opentelemetry.io/otel/trace v1.40.0 // indirect
go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect
go.uber.org/multierr v1.11.0 // indirect
go.yaml.in/yaml/v2 v2.4.3 // indirect
@@ -183,7 +184,6 @@ require (
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
golang.org/x/net v0.47.0 // indirect
golang.org/x/sync v0.18.0 // indirect
golang.org/x/sys v0.38.0 // indirect
golang.org/x/term v0.37.0 // indirect
golang.org/x/time v0.14.0 // indirect
golang.org/x/tools v0.38.0 // indirect

24
go.sum
View File

@@ -748,18 +748,18 @@ go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.6
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q=
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms=
go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g=
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0 h1:rixTyDGXFxRy1xzhKrotaHy3/KXdPhlWARrCgK+eqUY=
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0/go.mod h1:dowW6UsM9MKbJq5JTz2AMVp3/5iW5I/TStsk8S+CfHw=
go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=
go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=
go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
go.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g=
go.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc=
go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8=
go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE=
go.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4AtAlbuWdCYw=
go.opentelemetry.io/otel/sdk/metric v1.40.0/go.mod h1:4Z2bGMf0KSK3uRjlczMOeMhKU2rhUqdWNoKcYrtcBPg=
go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw=
go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA=
go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5/go.mod h1:nmDLcffg48OtT/PSW0Hg7FvpRQsQh5OSqIylirxKC7o=
go.starlark.net v0.0.0-20201006213952-227f4aabceb5/go.mod h1:f0znQkUKRrkk36XxWbGjMqQM8wGv/xHBVE2qc3B5oFU=
go.starlark.net v0.0.0-20230525235612-a134d8f9ddca h1:VdD38733bfYv5tUZwEIskMM93VanwNIi5bIKnDrJdEY=
@@ -969,8 +969,8 @@ golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=

View File

@@ -21,9 +21,11 @@ ENV GO111MODULE=on
ENV GOPROXY=${GOPROXY}
# kubebuilder test bundle is separated from kubebuilder. Need to setup it for CI test.
RUN curl -sSLo envtest-bins.tar.gz https://go.kubebuilder.io/test-tools/1.22.1/linux/$(go env GOARCH) && \
mkdir /usr/local/kubebuilder && \
tar -C /usr/local/kubebuilder --strip-components=1 -zvxf envtest-bins.tar.gz
# Using setup-envtest to download envtest binaries
RUN go install sigs.k8s.io/controller-runtime/tools/setup-envtest@latest && \
mkdir -p /usr/local/kubebuilder/bin && \
ENVTEST_ASSETS_DIR=$(setup-envtest use 1.33.0 --bin-dir /usr/local/kubebuilder/bin -p path) && \
cp -r ${ENVTEST_ASSETS_DIR}/* /usr/local/kubebuilder/bin/
RUN wget --quiet https://github.com/kubernetes-sigs/kubebuilder/releases/download/v3.2.0/kubebuilder_linux_$(go env GOARCH) && \
mv kubebuilder_linux_$(go env GOARCH) /usr/local/kubebuilder/bin/kubebuilder && \

View File

@@ -1,5 +1,5 @@
diff --git a/go.mod b/go.mod
index 5f939c481..6ae17f4a1 100644
index 5f939c481..f6205aa3c 100644
--- a/go.mod
+++ b/go.mod
@@ -24,32 +24,31 @@ require (
@@ -14,13 +14,13 @@ index 5f939c481..6ae17f4a1 100644
- golang.org/x/term v0.4.0
- golang.org/x/text v0.6.0
- google.golang.org/api v0.106.0
+ golang.org/x/crypto v0.36.0
+ golang.org/x/net v0.38.0
+ golang.org/x/crypto v0.45.0
+ golang.org/x/net v0.47.0
+ golang.org/x/oauth2 v0.28.0
+ golang.org/x/sync v0.12.0
+ golang.org/x/sys v0.31.0
+ golang.org/x/term v0.30.0
+ golang.org/x/text v0.23.0
+ golang.org/x/sync v0.18.0
+ golang.org/x/sys v0.38.0
+ golang.org/x/term v0.37.0
+ golang.org/x/text v0.31.0
+ google.golang.org/api v0.114.0
)
@@ -64,11 +64,11 @@ index 5f939c481..6ae17f4a1 100644
)
-go 1.18
+go 1.23.0
+go 1.24.0
+
+toolchain go1.23.7
+toolchain go1.24.11
diff --git a/go.sum b/go.sum
index 026e1d2fa..805792055 100644
index 026e1d2fa..4a37e7ac7 100644
--- a/go.sum
+++ b/go.sum
@@ -1,23 +1,24 @@
@@ -170,8 +170,8 @@ index 026e1d2fa..805792055 100644
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
-golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE=
-golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU=
+golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
+golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
+golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
+golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
@@ -181,8 +181,8 @@ index 026e1d2fa..805792055 100644
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw=
-golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
+golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
+golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
+golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
+golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
-golang.org/x/oauth2 v0.4.0 h1:NF0gk8LVPg1Ml7SSbGyySuoxdsXitj7TvgvuRxIMc/M=
-golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec=
@@ -194,8 +194,8 @@ index 026e1d2fa..805792055 100644
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
-golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
+golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
+golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
+golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -205,21 +205,21 @@ index 026e1d2fa..805792055 100644
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
-golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
+golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
+golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
+golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
-golang.org/x/term v0.4.0 h1:O7UWfv5+A2qiuulQk30kVinPoMtoIPeVaKLEgLpVkvg=
-golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
+golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
+golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
+golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
+golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k=
-golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
+golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
+golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
+golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=

View File

@@ -137,6 +137,10 @@ func (p *volumeSnapshotContentDeleteItemAction) Execute(
return checkVSCReadiness(ctx, &snapCont, p.crClient)
},
); err != nil {
// Clean up the VSC we created since it can't become ready
if deleteErr := p.crClient.Delete(context.TODO(), &snapCont); deleteErr != nil && !apierrors.IsNotFound(deleteErr) {
p.log.WithError(deleteErr).Errorf("Failed to clean up VolumeSnapshotContent %s", snapCont.Name)
}
return errors.Wrapf(err, "fail to wait VolumeSnapshotContent %s becomes ready.", snapCont.Name)
}
@@ -167,6 +171,13 @@ var checkVSCReadiness = func(
return true, nil
}
// Fail fast on permanent CSI driver errors (e.g., InvalidSnapshot.NotFound)
if tmpVSC.Status != nil && tmpVSC.Status.Error != nil && tmpVSC.Status.Error.Message != nil {
return false, errors.Errorf(
"VolumeSnapshotContent %s has error: %s", vsc.Name, *tmpVSC.Status.Error.Message,
)
}
return false, nil
}

View File

@@ -94,6 +94,19 @@ func TestVSCExecute(t *testing.T) {
return false, errors.Errorf("test error case")
},
},
{
name: "Error case with CSI error, dangling VSC should be cleaned up",
vsc: builder.ForVolumeSnapshotContent("bar").ObjectMeta(builder.WithLabelsMap(map[string]string{velerov1api.BackupNameLabel: "backup"})).Status(&snapshotv1api.VolumeSnapshotContentStatus{SnapshotHandle: &snapshotHandleStr}).Result(),
backup: builder.ForBackup("velero", "backup").ObjectMeta(builder.WithAnnotationsMap(map[string]string{velerov1api.ResourceTimeoutAnnotation: "5s"})).Result(),
expectErr: true,
function: func(
ctx context.Context,
vsc *snapshotv1api.VolumeSnapshotContent,
client crclient.Client,
) (bool, error) {
return false, errors.Errorf("VolumeSnapshotContent %s has error: InvalidSnapshot.NotFound", vsc.Name)
},
},
}
for _, test := range tests {
@@ -190,6 +203,24 @@ func TestCheckVSCReadiness(t *testing.T) {
expectErr: false,
ready: false,
},
{
name: "VSC has error from CSI driver",
vsc: &snapshotv1api.VolumeSnapshotContent{
ObjectMeta: metav1.ObjectMeta{
Name: "vsc-1",
Namespace: "velero",
},
Status: &snapshotv1api.VolumeSnapshotContentStatus{
ReadyToUse: boolPtr(false),
Error: &snapshotv1api.VolumeSnapshotError{
Message: stringPtr("InvalidSnapshot.NotFound: The snapshot 'snap-0abc123' does not exist."),
},
},
},
createVSC: true,
expectErr: true,
ready: false,
},
}
for _, test := range tests {
@@ -207,3 +238,11 @@ func TestCheckVSCReadiness(t *testing.T) {
})
}
}
func boolPtr(b bool) *bool {
return &b
}
func stringPtr(s string) *string {
return &s
}

View File

@@ -134,6 +134,7 @@ func (v *volumeHelperImpl) ShouldPerformSnapshot(obj runtime.Unstructured, group
pv := new(corev1api.PersistentVolume)
var err error
var pvNotFoundErr error
if groupResource == kuberesource.PersistentVolumeClaims {
if err = runtime.DefaultUnstructuredConverter.FromUnstructured(obj.UnstructuredContent(), &pvc); err != nil {
v.logger.WithError(err).Error("fail to convert unstructured into PVC")
@@ -142,8 +143,10 @@ func (v *volumeHelperImpl) ShouldPerformSnapshot(obj runtime.Unstructured, group
pv, err = kubeutil.GetPVForPVC(pvc, v.client)
if err != nil {
v.logger.WithError(err).Errorf("fail to get PV for PVC %s", pvc.Namespace+"/"+pvc.Name)
return false, err
// Any error means PV not available - save to return later if no policy matches
v.logger.Debugf("PV not found for PVC %s: %v", pvc.Namespace+"/"+pvc.Name, err)
pvNotFoundErr = err
pv = nil
}
}
@@ -158,7 +161,7 @@ func (v *volumeHelperImpl) ShouldPerformSnapshot(obj runtime.Unstructured, group
vfd := resourcepolicies.NewVolumeFilterData(pv, nil, pvc)
action, err := v.volumePolicy.GetMatchAction(vfd)
if err != nil {
v.logger.WithError(err).Errorf("fail to get VolumePolicy match action for PV %s", pv.Name)
v.logger.WithError(err).Errorf("fail to get VolumePolicy match action for %+v", vfd)
return false, err
}
@@ -167,15 +170,21 @@ func (v *volumeHelperImpl) ShouldPerformSnapshot(obj runtime.Unstructured, group
// If there is no match action, go on to the next check.
if action != nil {
if action.Type == resourcepolicies.Snapshot {
v.logger.Infof(fmt.Sprintf("performing snapshot action for pv %s", pv.Name))
v.logger.Infof("performing snapshot action for %+v", vfd)
return true, nil
} else {
v.logger.Infof("Skip snapshot action for pv %s as the action type is %s", pv.Name, action.Type)
v.logger.Infof("Skip snapshot action for %+v as the action type is %s", vfd, action.Type)
return false, nil
}
}
}
// If resource is PVC, and PV is nil (e.g., Pending/Lost PVC with no matching policy), return the original error
if groupResource == kuberesource.PersistentVolumeClaims && pv == nil && pvNotFoundErr != nil {
v.logger.WithError(pvNotFoundErr).Errorf("fail to get PV for PVC %s", pvc.Namespace+"/"+pvc.Name)
return false, pvNotFoundErr
}
// If this PV is claimed, see if we've already taken a (pod volume backup)
// snapshot of the contents of this PV. If so, don't take a snapshot.
if pv.Spec.ClaimRef != nil {
@@ -209,7 +218,7 @@ func (v *volumeHelperImpl) ShouldPerformSnapshot(obj runtime.Unstructured, group
return true, nil
}
v.logger.Infof(fmt.Sprintf("skipping snapshot action for pv %s possibly due to no volume policy setting or snapshotVolumes is false", pv.Name))
v.logger.Infof("skipping snapshot action for pv %s possibly due to no volume policy setting or snapshotVolumes is false", pv.Name)
return false, nil
}
@@ -219,6 +228,7 @@ func (v volumeHelperImpl) ShouldPerformFSBackup(volume corev1api.Volume, pod cor
return false, nil
}
var pvNotFoundErr error
if v.volumePolicy != nil {
var resource any
var err error
@@ -230,10 +240,13 @@ func (v volumeHelperImpl) ShouldPerformFSBackup(volume corev1api.Volume, pod cor
v.logger.WithError(err).Errorf("fail to get PVC for pod %s", pod.Namespace+"/"+pod.Name)
return false, err
}
resource, err = kubeutil.GetPVForPVC(pvc, v.client)
pvResource, err := kubeutil.GetPVForPVC(pvc, v.client)
if err != nil {
v.logger.WithError(err).Errorf("fail to get PV for PVC %s", pvc.Namespace+"/"+pvc.Name)
return false, err
// Any error means PV not available - save to return later if no policy matches
v.logger.Debugf("PV not found for PVC %s: %v", pvc.Namespace+"/"+pvc.Name, err)
pvNotFoundErr = err
} else {
resource = pvResource
}
}
@@ -260,6 +273,12 @@ func (v volumeHelperImpl) ShouldPerformFSBackup(volume corev1api.Volume, pod cor
return false, nil
}
}
// If no policy matched and PV was not found, return the original error
if pvNotFoundErr != nil {
v.logger.WithError(pvNotFoundErr).Errorf("fail to get PV for PVC %s", pvc.Namespace+"/"+pvc.Name)
return false, pvNotFoundErr
}
}
if v.shouldPerformFSBackupLegacy(volume, pod) {

View File

@@ -286,7 +286,7 @@ func TestVolumeHelperImpl_ShouldPerformSnapshot(t *testing.T) {
expectedErr: false,
},
{
name: "PVC not having PV, return false and error case PV not found",
name: "PVC not having PV, return false and error when no matching policy",
inputObj: builder.ForPersistentVolumeClaim("default", "example-pvc").StorageClass("gp2-csi").Result(),
groupResource: kuberesource.PersistentVolumeClaims,
resourcePolicies: &resourcepolicies.ResourcePolicies{
@@ -1234,3 +1234,312 @@ func TestNewVolumeHelperImplWithCache_UsesCache(t *testing.T) {
require.NoError(t, err)
require.False(t, shouldSnapshot, "Expected snapshot to be skipped due to fs-backup selection via cache")
}
// TestVolumeHelperImpl_ShouldPerformSnapshot_UnboundPVC tests that Pending and Lost PVCs with
// phase-based skip policies don't cause errors when GetPVForPVC would fail.
func TestVolumeHelperImpl_ShouldPerformSnapshot_UnboundPVC(t *testing.T) {
testCases := []struct {
name string
inputPVC *corev1api.PersistentVolumeClaim
resourcePolicies *resourcepolicies.ResourcePolicies
shouldSnapshot bool
expectedErr bool
}{
{
name: "Pending PVC with phase-based skip policy should not error and return false",
inputPVC: builder.ForPersistentVolumeClaim("ns", "pvc-pending").
StorageClass("non-existent-class").
Phase(corev1api.ClaimPending).
Result(),
resourcePolicies: &resourcepolicies.ResourcePolicies{
Version: "v1",
VolumePolicies: []resourcepolicies.VolumePolicy{
{
Conditions: map[string]any{
"pvcPhase": []string{"Pending"},
},
Action: resourcepolicies.Action{
Type: resourcepolicies.Skip,
},
},
},
},
shouldSnapshot: false,
expectedErr: false,
},
{
name: "Pending PVC without matching skip policy should error (no PV)",
inputPVC: builder.ForPersistentVolumeClaim("ns", "pvc-pending-no-policy").
StorageClass("non-existent-class").
Phase(corev1api.ClaimPending).
Result(),
resourcePolicies: &resourcepolicies.ResourcePolicies{
Version: "v1",
VolumePolicies: []resourcepolicies.VolumePolicy{
{
Conditions: map[string]any{
"storageClass": []string{"gp2-csi"},
},
Action: resourcepolicies.Action{
Type: resourcepolicies.Skip,
},
},
},
},
shouldSnapshot: false,
expectedErr: true,
},
{
name: "Lost PVC with phase-based skip policy should not error and return false",
inputPVC: builder.ForPersistentVolumeClaim("ns", "pvc-lost").
StorageClass("some-class").
Phase(corev1api.ClaimLost).
Result(),
resourcePolicies: &resourcepolicies.ResourcePolicies{
Version: "v1",
VolumePolicies: []resourcepolicies.VolumePolicy{
{
Conditions: map[string]any{
"pvcPhase": []string{"Lost"},
},
Action: resourcepolicies.Action{
Type: resourcepolicies.Skip,
},
},
},
},
shouldSnapshot: false,
expectedErr: false,
},
{
name: "Lost PVC with policy for Pending and Lost should not error and return false",
inputPVC: builder.ForPersistentVolumeClaim("ns", "pvc-lost").
StorageClass("some-class").
Phase(corev1api.ClaimLost).
Result(),
resourcePolicies: &resourcepolicies.ResourcePolicies{
Version: "v1",
VolumePolicies: []resourcepolicies.VolumePolicy{
{
Conditions: map[string]any{
"pvcPhase": []string{"Pending", "Lost"},
},
Action: resourcepolicies.Action{
Type: resourcepolicies.Skip,
},
},
},
},
shouldSnapshot: false,
expectedErr: false,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
fakeClient := velerotest.NewFakeControllerRuntimeClient(t)
var p *resourcepolicies.Policies
if tc.resourcePolicies != nil {
p = &resourcepolicies.Policies{}
err := p.BuildPolicy(tc.resourcePolicies)
require.NoError(t, err)
}
vh := NewVolumeHelperImpl(
p,
ptr.To(true),
logrus.StandardLogger(),
fakeClient,
false,
false,
)
obj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(tc.inputPVC)
require.NoError(t, err)
actualShouldSnapshot, actualError := vh.ShouldPerformSnapshot(&unstructured.Unstructured{Object: obj}, kuberesource.PersistentVolumeClaims)
if tc.expectedErr {
require.Error(t, actualError, "Want error; Got nil error")
return
}
require.NoError(t, actualError)
require.Equalf(t, tc.shouldSnapshot, actualShouldSnapshot, "Want shouldSnapshot as %t; Got shouldSnapshot as %t", tc.shouldSnapshot, actualShouldSnapshot)
})
}
}
// TestVolumeHelperImpl_ShouldPerformFSBackup_UnboundPVC tests that Pending and Lost PVCs with
// phase-based skip policies don't cause errors when GetPVForPVC would fail.
func TestVolumeHelperImpl_ShouldPerformFSBackup_UnboundPVC(t *testing.T) {
testCases := []struct {
name string
pod *corev1api.Pod
pvc *corev1api.PersistentVolumeClaim
resourcePolicies *resourcepolicies.ResourcePolicies
shouldFSBackup bool
expectedErr bool
}{
{
name: "Pending PVC with phase-based skip policy should not error and return false",
pod: builder.ForPod("ns", "pod-1").
Volumes(
&corev1api.Volume{
Name: "vol-pending",
VolumeSource: corev1api.VolumeSource{
PersistentVolumeClaim: &corev1api.PersistentVolumeClaimVolumeSource{
ClaimName: "pvc-pending",
},
},
}).Result(),
pvc: builder.ForPersistentVolumeClaim("ns", "pvc-pending").
StorageClass("non-existent-class").
Phase(corev1api.ClaimPending).
Result(),
resourcePolicies: &resourcepolicies.ResourcePolicies{
Version: "v1",
VolumePolicies: []resourcepolicies.VolumePolicy{
{
Conditions: map[string]any{
"pvcPhase": []string{"Pending"},
},
Action: resourcepolicies.Action{
Type: resourcepolicies.Skip,
},
},
},
},
shouldFSBackup: false,
expectedErr: false,
},
{
name: "Pending PVC without matching skip policy should error (no PV)",
pod: builder.ForPod("ns", "pod-1").
Volumes(
&corev1api.Volume{
Name: "vol-pending",
VolumeSource: corev1api.VolumeSource{
PersistentVolumeClaim: &corev1api.PersistentVolumeClaimVolumeSource{
ClaimName: "pvc-pending-no-policy",
},
},
}).Result(),
pvc: builder.ForPersistentVolumeClaim("ns", "pvc-pending-no-policy").
StorageClass("non-existent-class").
Phase(corev1api.ClaimPending).
Result(),
resourcePolicies: &resourcepolicies.ResourcePolicies{
Version: "v1",
VolumePolicies: []resourcepolicies.VolumePolicy{
{
Conditions: map[string]any{
"storageClass": []string{"gp2-csi"},
},
Action: resourcepolicies.Action{
Type: resourcepolicies.Skip,
},
},
},
},
shouldFSBackup: false,
expectedErr: true,
},
{
name: "Lost PVC with phase-based skip policy should not error and return false",
pod: builder.ForPod("ns", "pod-1").
Volumes(
&corev1api.Volume{
Name: "vol-lost",
VolumeSource: corev1api.VolumeSource{
PersistentVolumeClaim: &corev1api.PersistentVolumeClaimVolumeSource{
ClaimName: "pvc-lost",
},
},
}).Result(),
pvc: builder.ForPersistentVolumeClaim("ns", "pvc-lost").
StorageClass("some-class").
Phase(corev1api.ClaimLost).
Result(),
resourcePolicies: &resourcepolicies.ResourcePolicies{
Version: "v1",
VolumePolicies: []resourcepolicies.VolumePolicy{
{
Conditions: map[string]any{
"pvcPhase": []string{"Lost"},
},
Action: resourcepolicies.Action{
Type: resourcepolicies.Skip,
},
},
},
},
shouldFSBackup: false,
expectedErr: false,
},
{
name: "Lost PVC with policy for Pending and Lost should not error and return false",
pod: builder.ForPod("ns", "pod-1").
Volumes(
&corev1api.Volume{
Name: "vol-lost",
VolumeSource: corev1api.VolumeSource{
PersistentVolumeClaim: &corev1api.PersistentVolumeClaimVolumeSource{
ClaimName: "pvc-lost",
},
},
}).Result(),
pvc: builder.ForPersistentVolumeClaim("ns", "pvc-lost").
StorageClass("some-class").
Phase(corev1api.ClaimLost).
Result(),
resourcePolicies: &resourcepolicies.ResourcePolicies{
Version: "v1",
VolumePolicies: []resourcepolicies.VolumePolicy{
{
Conditions: map[string]any{
"pvcPhase": []string{"Pending", "Lost"},
},
Action: resourcepolicies.Action{
Type: resourcepolicies.Skip,
},
},
},
},
shouldFSBackup: false,
expectedErr: false,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
fakeClient := velerotest.NewFakeControllerRuntimeClient(t, tc.pvc)
require.NoError(t, fakeClient.Create(t.Context(), tc.pod))
var p *resourcepolicies.Policies
if tc.resourcePolicies != nil {
p = &resourcepolicies.Policies{}
err := p.BuildPolicy(tc.resourcePolicies)
require.NoError(t, err)
}
vh := NewVolumeHelperImpl(
p,
ptr.To(true),
logrus.StandardLogger(),
fakeClient,
false,
false,
)
actualShouldFSBackup, actualError := vh.ShouldPerformFSBackup(tc.pod.Spec.Volumes[0], *tc.pod)
if tc.expectedErr {
require.Error(t, actualError, "Want error; Got nil error")
return
}
require.NoError(t, actualError)
require.Equalf(t, tc.shouldFSBackup, actualShouldFSBackup, "Want shouldFSBackup as %t; Got shouldFSBackup as %t", tc.shouldFSBackup, actualShouldFSBackup)
})
}
}

View File

@@ -102,6 +102,15 @@ const (
// even if the resource contains a matching selector label.
ExcludeFromBackupLabel = "velero.io/exclude-from-backup"
// SkipFromBackupAnnotation is the annotation used by internal BackupItemActions
// to indicate that a resource should be skipped from backup,
// even if it doesn't have the ExcludeFromBackupLabel.
// This is used in cases where we want to skip backup of a resource based on some logic in a plugin.
//
// Notice: SkipFromBackupAnnotation's priority is higher than MustIncludeAdditionalItemAnnotation.
// If SkipFromBackupAnnotation is set, the resource will be skipped even if MustIncludeAdditionalItemAnnotation is set.
SkipFromBackupAnnotation = "velero.io/skip-from-backup"
// defaultVGSLabelKey is the default label key used to group PVCs under a VolumeGroupSnapshot
DefaultVGSLabelKey = "velero.io/volume-group"

View File

@@ -98,6 +98,14 @@ func (m *backedUpItemsMap) AddItem(key itemKey) {
m.totalItems[key] = struct{}{}
}
func (m *backedUpItemsMap) DeleteItem(key itemKey) {
m.Lock()
defer m.Unlock()
delete(m.backedUpItems, key)
delete(m.totalItems, key)
}
func (m *backedUpItemsMap) AddItemToTotal(key itemKey) {
m.Lock()
defer m.Unlock()

View File

@@ -244,6 +244,14 @@ func (ib *itemBackupper) backupItemInternal(logger logrus.FieldLogger, obj runti
return false, itemFiles, kubeerrs.NewAggregate(backupErrs)
}
// If err is nil and updatedObj is nil, it means the item is skipped by plugin action,
// we should return here to avoid backing up the item, and avoid potential NPE in the following code.
if updatedObj == nil {
log.Infof("Remove item from the backup's backupItems list and totalItems list because it's skipped by plugin action.")
ib.backupRequest.BackedUpItems.DeleteItem(key)
return false, itemFiles, nil
}
itemFiles = append(itemFiles, additionalItemFiles...)
obj = updatedObj
if metadata, err = meta.Accessor(obj); err != nil {
@@ -398,6 +406,13 @@ func (ib *itemBackupper) executeActions(
}
u := &unstructured.Unstructured{Object: updatedItem.UnstructuredContent()}
if _, ok := u.GetAnnotations()[velerov1api.SkipFromBackupAnnotation]; ok {
log.Infof("Resource (groupResource=%s, namespace=%s, name=%s) is skipped from backup by action %s.",
groupResource.String(), namespace, name, actionName)
return nil, itemFiles, nil
}
if actionName == csiBIAPluginName {
if additionalItemIdentifiers == nil && u.GetAnnotations()[velerov1api.SkippedNoCSIPVAnnotation] == "true" {
// snapshot was skipped by CSI plugin
@@ -687,15 +702,14 @@ func (ib *itemBackupper) getMatchAction(obj runtime.Unstructured, groupResource
return nil, errors.WithStack(err)
}
pvName := pvc.Spec.VolumeName
if pvName == "" {
return nil, errors.Errorf("PVC has no volume backing this claim")
}
pv := &corev1api.PersistentVolume{}
if err := ib.kbClient.Get(context.Background(), kbClient.ObjectKey{Name: pvName}, pv); err != nil {
return nil, errors.WithStack(err)
var pv *corev1api.PersistentVolume
if pvName := pvc.Spec.VolumeName; pvName != "" {
pv = &corev1api.PersistentVolume{}
if err := ib.kbClient.Get(context.Background(), kbClient.ObjectKey{Name: pvName}, pv); err != nil {
return nil, errors.WithStack(err)
}
}
// If pv is nil for unbound PVCs - policy matching will use PVC-only conditions
vfd := resourcepolicies.NewVolumeFilterData(pv, nil, pvc)
return ib.backupRequest.ResPolicies.GetMatchAction(vfd)
}
@@ -709,7 +723,10 @@ func (ib *itemBackupper) trackSkippedPV(obj runtime.Unstructured, groupResource
if name, err := getPVName(obj, groupResource); len(name) > 0 && err == nil {
ib.backupRequest.SkippedPVTracker.Track(name, approach, reason)
} else if err != nil {
log.WithError(err).Warnf("unable to get PV name, skip tracking.")
// Log at info level for tracking purposes. This is not an error because
// it's expected for some resources (e.g., PVCs in Pending or Lost phase)
// to not have a PV name. This occurs when volume policy skips unbound PVCs.
log.WithError(err).Infof("unable to get PV name, skip tracking.")
}
}
@@ -719,6 +736,17 @@ func (ib *itemBackupper) unTrackSkippedPV(obj runtime.Unstructured, groupResourc
if name, err := getPVName(obj, groupResource); len(name) > 0 && err == nil {
ib.backupRequest.SkippedPVTracker.Untrack(name)
} else if err != nil {
// For PVCs in Pending or Lost phase, it's expected that there's no PV name.
// Log at debug level instead of warning to reduce noise.
if groupResource == kuberesource.PersistentVolumeClaims {
pvc := new(corev1api.PersistentVolumeClaim)
if convErr := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.UnstructuredContent(), pvc); convErr == nil {
if pvc.Status.Phase == corev1api.ClaimPending || pvc.Status.Phase == corev1api.ClaimLost {
log.WithError(err).Debugf("unable to get PV name for %s PVC, skip untracking.", pvc.Status.Phase)
return
}
}
}
log.WithError(err).Warnf("unable to get PV name, skip untracking.")
}
}

View File

@@ -17,12 +17,15 @@ limitations under the License.
package backup
import (
"bytes"
"testing"
"github.com/sirupsen/logrus"
"github.com/stretchr/testify/require"
"k8s.io/apimachinery/pkg/runtime/schema"
ctrlfake "sigs.k8s.io/controller-runtime/pkg/client/fake"
"github.com/vmware-tanzu/velero/internal/resourcepolicies"
"github.com/vmware-tanzu/velero/pkg/kuberesource"
"github.com/stretchr/testify/assert"
@@ -269,3 +272,225 @@ func TestAddVolumeInfo(t *testing.T) {
})
}
}
func TestGetMatchAction_PendingLostPVC(t *testing.T) {
scheme := runtime.NewScheme()
require.NoError(t, corev1api.AddToScheme(scheme))
// Create resource policies that skip Pending/Lost PVCs
resPolicies := &resourcepolicies.ResourcePolicies{
Version: "v1",
VolumePolicies: []resourcepolicies.VolumePolicy{
{
Conditions: map[string]any{
"pvcPhase": []string{"Pending", "Lost"},
},
Action: resourcepolicies.Action{
Type: resourcepolicies.Skip,
},
},
},
}
policies := &resourcepolicies.Policies{}
err := policies.BuildPolicy(resPolicies)
require.NoError(t, err)
testCases := []struct {
name string
pvc *corev1api.PersistentVolumeClaim
pv *corev1api.PersistentVolume
expectedAction *resourcepolicies.Action
expectError bool
}{
{
name: "Pending PVC with no VolumeName should match pvcPhase policy",
pvc: builder.ForPersistentVolumeClaim("ns", "pending-pvc").
StorageClass("test-sc").
Phase(corev1api.ClaimPending).
Result(),
pv: nil,
expectedAction: &resourcepolicies.Action{Type: resourcepolicies.Skip},
expectError: false,
},
{
name: "Lost PVC with no VolumeName should match pvcPhase policy",
pvc: builder.ForPersistentVolumeClaim("ns", "lost-pvc").
StorageClass("test-sc").
Phase(corev1api.ClaimLost).
Result(),
pv: nil,
expectedAction: &resourcepolicies.Action{Type: resourcepolicies.Skip},
expectError: false,
},
{
name: "Bound PVC with VolumeName and matching PV should not match pvcPhase policy",
pvc: builder.ForPersistentVolumeClaim("ns", "bound-pvc").
StorageClass("test-sc").
VolumeName("test-pv").
Phase(corev1api.ClaimBound).
Result(),
pv: builder.ForPersistentVolume("test-pv").StorageClass("test-sc").Result(),
expectedAction: nil,
expectError: false,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
// Build fake client with PV if present
clientBuilder := ctrlfake.NewClientBuilder().WithScheme(scheme)
if tc.pv != nil {
clientBuilder = clientBuilder.WithObjects(tc.pv)
}
fakeClient := clientBuilder.Build()
ib := &itemBackupper{
kbClient: fakeClient,
backupRequest: &Request{
ResPolicies: policies,
},
}
// Convert PVC to unstructured
pvcData, err := runtime.DefaultUnstructuredConverter.ToUnstructured(tc.pvc)
require.NoError(t, err)
obj := &unstructured.Unstructured{Object: pvcData}
action, err := ib.getMatchAction(obj, kuberesource.PersistentVolumeClaims, csiBIAPluginName)
if tc.expectError {
require.Error(t, err)
} else {
require.NoError(t, err)
}
if tc.expectedAction == nil {
assert.Nil(t, action)
} else {
require.NotNil(t, action)
assert.Equal(t, tc.expectedAction.Type, action.Type)
}
})
}
}
func TestTrackSkippedPV_PendingLostPVC(t *testing.T) {
testCases := []struct {
name string
pvc *corev1api.PersistentVolumeClaim
}{
{
name: "Pending PVC should log at info level",
pvc: builder.ForPersistentVolumeClaim("ns", "pending-pvc").
Phase(corev1api.ClaimPending).
Result(),
},
{
name: "Lost PVC should log at info level",
pvc: builder.ForPersistentVolumeClaim("ns", "lost-pvc").
Phase(corev1api.ClaimLost).
Result(),
},
{
name: "Bound PVC without VolumeName should log at info level",
pvc: builder.ForPersistentVolumeClaim("ns", "bound-pvc").
Phase(corev1api.ClaimBound).
Result(),
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
ib := &itemBackupper{
backupRequest: &Request{
SkippedPVTracker: NewSkipPVTracker(),
},
}
// Set up log capture
logOutput := &bytes.Buffer{}
logger := logrus.New()
logger.SetOutput(logOutput)
logger.SetLevel(logrus.DebugLevel)
// Convert PVC to unstructured
pvcData, err := runtime.DefaultUnstructuredConverter.ToUnstructured(tc.pvc)
require.NoError(t, err)
obj := &unstructured.Unstructured{Object: pvcData}
ib.trackSkippedPV(obj, kuberesource.PersistentVolumeClaims, "", "test reason", logger)
logStr := logOutput.String()
assert.Contains(t, logStr, "level=info")
assert.Contains(t, logStr, "unable to get PV name, skip tracking.")
})
}
}
func TestUnTrackSkippedPV_PendingLostPVC(t *testing.T) {
testCases := []struct {
name string
pvc *corev1api.PersistentVolumeClaim
expectWarningLog bool
expectDebugMessage string
}{
{
name: "Pending PVC should log at debug level, not warning",
pvc: builder.ForPersistentVolumeClaim("ns", "pending-pvc").
Phase(corev1api.ClaimPending).
Result(),
expectWarningLog: false,
expectDebugMessage: "unable to get PV name for Pending PVC, skip untracking.",
},
{
name: "Lost PVC should log at debug level, not warning",
pvc: builder.ForPersistentVolumeClaim("ns", "lost-pvc").
Phase(corev1api.ClaimLost).
Result(),
expectWarningLog: false,
expectDebugMessage: "unable to get PV name for Lost PVC, skip untracking.",
},
{
name: "Bound PVC without VolumeName should log warning",
pvc: builder.ForPersistentVolumeClaim("ns", "bound-pvc").
Phase(corev1api.ClaimBound).
Result(),
expectWarningLog: true,
expectDebugMessage: "",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
ib := &itemBackupper{
backupRequest: &Request{
SkippedPVTracker: NewSkipPVTracker(),
},
}
// Set up log capture
logOutput := &bytes.Buffer{}
logger := logrus.New()
logger.SetOutput(logOutput)
logger.SetLevel(logrus.DebugLevel)
// Convert PVC to unstructured
pvcData, err := runtime.DefaultUnstructuredConverter.ToUnstructured(tc.pvc)
require.NoError(t, err)
obj := &unstructured.Unstructured{Object: pvcData}
ib.unTrackSkippedPV(obj, kuberesource.PersistentVolumeClaims, logger)
logStr := logOutput.String()
if tc.expectWarningLog {
assert.Contains(t, logStr, "level=warning")
assert.Contains(t, logStr, "unable to get PV name, skip untracking.")
} else {
assert.NotContains(t, logStr, "level=warning")
if tc.expectDebugMessage != "" {
assert.Contains(t, logStr, "level=debug")
assert.Contains(t, logStr, tc.expectDebugMessage)
}
}
})
}
}

View File

@@ -275,11 +275,21 @@ func (o *Options) AsVeleroOptions() (*install.VeleroOptions, error) {
return nil, err
}
}
veleroPodResources, err := kubeutil.ParseResourceRequirements(o.VeleroPodCPURequest, o.VeleroPodMemRequest, o.VeleroPodCPULimit, o.VeleroPodMemLimit)
veleroPodResources, err := kubeutil.ParseCPUAndMemoryResources(
o.VeleroPodCPURequest,
o.VeleroPodMemRequest,
o.VeleroPodCPULimit,
o.VeleroPodMemLimit,
)
if err != nil {
return nil, err
}
nodeAgentPodResources, err := kubeutil.ParseResourceRequirements(o.NodeAgentPodCPURequest, o.NodeAgentPodMemRequest, o.NodeAgentPodCPULimit, o.NodeAgentPodMemLimit)
nodeAgentPodResources, err := kubeutil.ParseCPUAndMemoryResources(
o.NodeAgentPodCPURequest,
o.NodeAgentPodMemRequest,
o.NodeAgentPodCPULimit,
o.NodeAgentPodMemLimit,
)
if err != nil {
return nil, err
}

View File

@@ -323,7 +323,25 @@ func (s *nodeAgentServer) run() {
podResources := corev1api.ResourceRequirements{}
if s.dataPathConfigs != nil && s.dataPathConfigs.PodResources != nil {
if res, err := kube.ParseResourceRequirements(s.dataPathConfigs.PodResources.CPURequest, s.dataPathConfigs.PodResources.MemoryRequest, s.dataPathConfigs.PodResources.CPULimit, s.dataPathConfigs.PodResources.MemoryLimit); err != nil {
// To make the PodResources ConfigMap without ephemeral storage request/limit backward compatible,
// need to avoid set value as empty, because empty string will cause parsing error.
ephemeralStorageRequest := constant.DefaultEphemeralStorageRequest
if s.dataPathConfigs.PodResources.EphemeralStorageRequest != "" {
ephemeralStorageRequest = s.dataPathConfigs.PodResources.EphemeralStorageRequest
}
ephemeralStorageLimit := constant.DefaultEphemeralStorageLimit
if s.dataPathConfigs.PodResources.EphemeralStorageLimit != "" {
ephemeralStorageLimit = s.dataPathConfigs.PodResources.EphemeralStorageLimit
}
if res, err := kube.ParseResourceRequirements(
s.dataPathConfigs.PodResources.CPURequest,
s.dataPathConfigs.PodResources.MemoryRequest,
ephemeralStorageRequest,
s.dataPathConfigs.PodResources.CPULimit,
s.dataPathConfigs.PodResources.MemoryLimit,
ephemeralStorageLimit,
); err != nil {
s.logger.WithError(err).Warn("Pod resource requirements are invalid, ignore")
} else {
podResources = res

View File

@@ -23,4 +23,7 @@ const (
PluginCSIPVCRestoreRIA = "velero.io/csi-pvc-restorer"
PluginCsiVolumeSnapshotRestoreRIA = "velero.io/csi-volumesnapshot-restorer"
DefaultEphemeralStorageRequest = "0"
DefaultEphemeralStorageLimit = "0"
)

View File

@@ -129,6 +129,13 @@ func (c *scheduleReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c
} else {
schedule.Status.Phase = velerov1.SchedulePhaseEnabled
schedule.Status.ValidationErrors = nil
// Compute expected interval between consecutive scheduled backup runs.
// Only meaningful when the cron expression is valid.
now := c.clock.Now()
nextRun := cronSchedule.Next(now)
nextNextRun := cronSchedule.Next(nextRun)
c.metrics.SetScheduleExpectedIntervalSeconds(schedule.Name, nextNextRun.Sub(nextRun).Seconds())
}
scheduleNeedsPatch := false

View File

@@ -124,6 +124,15 @@ func (e *csiSnapshotExposer) Expose(ctx context.Context, ownerObject corev1api.O
"owner": ownerObject.Name,
})
volumeTopology, err := kube.GetVolumeTopology(ctx, e.kubeClient.CoreV1(), e.kubeClient.StorageV1(), csiExposeParam.SourcePVName, csiExposeParam.StorageClass)
if err != nil {
return errors.Wrapf(err, "error getting volume topology for PV %s, storage class %s", csiExposeParam.SourcePVName, csiExposeParam.StorageClass)
}
if volumeTopology != nil {
curLog.Infof("Using volume topology %v", volumeTopology)
}
curLog.Info("Exposing CSI snapshot")
volumeSnapshot, err := csi.WaitVolumeSnapshotReady(ctx, e.csiSnapshotClient, csiExposeParam.SnapshotName, csiExposeParam.SourceNamespace, csiExposeParam.ExposeTimeout, curLog)
@@ -254,6 +263,7 @@ func (e *csiSnapshotExposer) Expose(ctx context.Context, ownerObject corev1api.O
csiExposeParam.NodeOS,
csiExposeParam.PriorityClassName,
intoleratableNodes,
volumeTopology,
)
if err != nil {
return errors.Wrap(err, "error to create backup pod")
@@ -320,7 +330,8 @@ func (e *csiSnapshotExposer) GetExposed(ctx context.Context, ownerObject corev1a
curLog.WithField("pod", pod.Name).Infof("Backup volume is found in pod at index %v", i)
var nodeOS *string
if os, found := pod.Spec.NodeSelector[kube.NodeOSLabel]; found {
if pod.Spec.OS != nil {
os := string(pod.Spec.OS.Name)
nodeOS = &os
}
@@ -588,6 +599,7 @@ func (e *csiSnapshotExposer) createBackupPod(
nodeOS string,
priorityClassName string,
intoleratableNodes []string,
volumeTopology *corev1api.NodeSelector,
) (*corev1api.Pod, error) {
podName := ownerObject.Name
@@ -643,6 +655,10 @@ func (e *csiSnapshotExposer) createBackupPod(
args = append(args, podInfo.logFormatArgs...)
args = append(args, podInfo.logLevelArgs...)
if affinity == nil {
affinity = &kube.LoadAffinity{}
}
var securityCtx *corev1api.PodSecurityContext
nodeSelector := map[string]string{}
podOS := corev1api.PodOS{}
@@ -654,9 +670,14 @@ func (e *csiSnapshotExposer) createBackupPod(
},
}
nodeSelector[kube.NodeOSLabel] = kube.NodeOSWindows
podOS.Name = kube.NodeOSWindows
affinity.NodeSelector.MatchExpressions = append(affinity.NodeSelector.MatchExpressions, metav1.LabelSelectorRequirement{
Key: kube.NodeOSLabel,
Values: []string{kube.NodeOSWindows},
Operator: metav1.LabelSelectorOpIn,
})
toleration = append(toleration, []corev1api.Toleration{
{
Key: "os",
@@ -683,11 +704,15 @@ func (e *csiSnapshotExposer) createBackupPod(
}
}
nodeSelector[kube.NodeOSLabel] = kube.NodeOSLinux
podOS.Name = kube.NodeOSLinux
affinity.NodeSelector.MatchExpressions = append(affinity.NodeSelector.MatchExpressions, metav1.LabelSelectorRequirement{
Key: kube.NodeOSLabel,
Values: []string{kube.NodeOSWindows},
Operator: metav1.LabelSelectorOpNotIn,
})
}
var podAffinity *corev1api.Affinity
if len(intoleratableNodes) > 0 {
if affinity == nil {
affinity = &kube.LoadAffinity{}
@@ -700,9 +725,7 @@ func (e *csiSnapshotExposer) createBackupPod(
})
}
if affinity != nil {
podAffinity = kube.ToSystemAffinity([]*kube.LoadAffinity{affinity})
}
podAffinity := kube.ToSystemAffinity(affinity, volumeTopology)
pod := &corev1api.Pod{
ObjectMeta: metav1.ObjectMeta{

View File

@@ -154,6 +154,7 @@ func TestCreateBackupPodWithPriorityClass(t *testing.T) {
kube.NodeOSLinux,
tc.expectedPriorityClass,
nil,
nil,
)
require.NoError(t, err, tc.description)
@@ -239,6 +240,7 @@ func TestCreateBackupPodWithMissingConfigMap(t *testing.T) {
kube.NodeOSLinux,
"", // empty priority class since config map is missing
nil,
nil,
)
// Should succeed even when config map is missing

View File

@@ -68,6 +68,12 @@ func TestExpose(t *testing.T) {
var restoreSize int64 = 123456
scObj := &storagev1api.StorageClass{
ObjectMeta: metav1.ObjectMeta{
Name: "fake-sc",
},
}
snapshotClass := "fake-snapshot-class"
vsObject := &snapshotv1api.VolumeSnapshot{
ObjectMeta: metav1.ObjectMeta{
@@ -199,6 +205,18 @@ func TestExpose(t *testing.T) {
expectedAffinity *corev1api.Affinity
expectedPVCAnnotation map[string]string
}{
{
name: "get volume topology fail",
ownerBackup: backup,
exposeParam: CSISnapshotExposeParam{
SnapshotName: "fake-vs",
OperationTimeout: time.Millisecond,
ExposeTimeout: time.Millisecond,
StorageClass: "fake-sc",
SourcePVName: "fake-pv",
},
err: "error getting volume topology for PV fake-pv, storage class fake-sc: error getting storage class fake-sc: storageclasses.storage.k8s.io \"fake-sc\" not found",
},
{
name: "wait vs ready fail",
ownerBackup: backup,
@@ -206,6 +224,11 @@ func TestExpose(t *testing.T) {
SnapshotName: "fake-vs",
OperationTimeout: time.Millisecond,
ExposeTimeout: time.Millisecond,
StorageClass: "fake-sc",
SourcePVName: "fake-pv",
},
kubeClientObj: []runtime.Object{
scObj,
},
err: "error wait volume snapshot ready: error to get VolumeSnapshot /fake-vs: volumesnapshots.snapshot.storage.k8s.io \"fake-vs\" not found",
},
@@ -217,10 +240,15 @@ func TestExpose(t *testing.T) {
SourceNamespace: "fake-ns",
OperationTimeout: time.Millisecond,
ExposeTimeout: time.Millisecond,
StorageClass: "fake-sc",
SourcePVName: "fake-pv",
},
snapshotClientObj: []runtime.Object{
vsObject,
},
kubeClientObj: []runtime.Object{
scObj,
},
err: "error to get volume snapshot content: error getting volume snapshot content from API: volumesnapshotcontents.snapshot.storage.k8s.io \"fake-vsc\" not found",
},
{
@@ -231,6 +259,8 @@ func TestExpose(t *testing.T) {
SourceNamespace: "fake-ns",
OperationTimeout: time.Millisecond,
ExposeTimeout: time.Millisecond,
StorageClass: "fake-sc",
SourcePVName: "fake-pv",
},
snapshotClientObj: []runtime.Object{
vsObject,
@@ -245,6 +275,9 @@ func TestExpose(t *testing.T) {
},
},
},
kubeClientObj: []runtime.Object{
scObj,
},
err: "error to delete volume snapshot: error to delete volume snapshot: fake-delete-error",
},
{
@@ -255,6 +288,8 @@ func TestExpose(t *testing.T) {
SourceNamespace: "fake-ns",
OperationTimeout: time.Millisecond,
ExposeTimeout: time.Millisecond,
StorageClass: "fake-sc",
SourcePVName: "fake-pv",
},
snapshotClientObj: []runtime.Object{
vsObject,
@@ -269,6 +304,9 @@ func TestExpose(t *testing.T) {
},
},
},
kubeClientObj: []runtime.Object{
scObj,
},
err: "error to delete volume snapshot content: error to delete volume snapshot content: fake-delete-error",
},
{
@@ -279,6 +317,8 @@ func TestExpose(t *testing.T) {
SourceNamespace: "fake-ns",
OperationTimeout: time.Millisecond,
ExposeTimeout: time.Millisecond,
StorageClass: "fake-sc",
SourcePVName: "fake-pv",
},
snapshotClientObj: []runtime.Object{
vsObject,
@@ -293,6 +333,9 @@ func TestExpose(t *testing.T) {
},
},
},
kubeClientObj: []runtime.Object{
scObj,
},
err: "error to create backup volume snapshot: fake-create-error",
},
{
@@ -303,6 +346,8 @@ func TestExpose(t *testing.T) {
SourceNamespace: "fake-ns",
OperationTimeout: time.Millisecond,
ExposeTimeout: time.Millisecond,
StorageClass: "fake-sc",
SourcePVName: "fake-pv",
},
snapshotClientObj: []runtime.Object{
vsObject,
@@ -317,6 +362,9 @@ func TestExpose(t *testing.T) {
},
},
},
kubeClientObj: []runtime.Object{
scObj,
},
err: "error to create backup volume snapshot content: fake-create-error",
},
{
@@ -326,11 +374,16 @@ func TestExpose(t *testing.T) {
SnapshotName: "fake-vs",
SourceNamespace: "fake-ns",
AccessMode: "fake-mode",
StorageClass: "fake-sc",
SourcePVName: "fake-pv",
},
snapshotClientObj: []runtime.Object{
vsObject,
vscObj,
},
kubeClientObj: []runtime.Object{
scObj,
},
err: "error to create backup pvc: unsupported access mode fake-mode",
},
{
@@ -342,6 +395,8 @@ func TestExpose(t *testing.T) {
OperationTimeout: time.Millisecond,
ExposeTimeout: time.Millisecond,
AccessMode: AccessModeFileSystem,
StorageClass: "fake-sc",
SourcePVName: "fake-pv",
},
snapshotClientObj: []runtime.Object{
vsObject,
@@ -356,6 +411,9 @@ func TestExpose(t *testing.T) {
},
},
},
kubeClientObj: []runtime.Object{
scObj,
},
err: "error to create backup pvc: error to create pvc: fake-create-error",
},
{
@@ -367,6 +425,8 @@ func TestExpose(t *testing.T) {
AccessMode: AccessModeFileSystem,
OperationTimeout: time.Millisecond,
ExposeTimeout: time.Millisecond,
StorageClass: "fake-sc",
SourcePVName: "fake-pv",
},
snapshotClientObj: []runtime.Object{
vsObject,
@@ -374,6 +434,7 @@ func TestExpose(t *testing.T) {
},
kubeClientObj: []runtime.Object{
daemonSet,
scObj,
},
kubeReactors: []reactor{
{
@@ -395,6 +456,8 @@ func TestExpose(t *testing.T) {
AccessMode: AccessModeFileSystem,
OperationTimeout: time.Millisecond,
ExposeTimeout: time.Millisecond,
StorageClass: "fake-sc",
SourcePVName: "fake-pv",
},
snapshotClientObj: []runtime.Object{
vsObject,
@@ -402,6 +465,24 @@ func TestExpose(t *testing.T) {
},
kubeClientObj: []runtime.Object{
daemonSet,
scObj,
},
expectedAffinity: &corev1api.Affinity{
NodeAffinity: &corev1api.NodeAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: &corev1api.NodeSelector{
NodeSelectorTerms: []corev1api.NodeSelectorTerm{
{
MatchExpressions: []corev1api.NodeSelectorRequirement{
{
Key: "kubernetes.io/os",
Operator: corev1api.NodeSelectorOpNotIn,
Values: []string{"windows"},
},
},
},
},
},
},
},
},
{
@@ -413,6 +494,8 @@ func TestExpose(t *testing.T) {
AccessMode: AccessModeFileSystem,
OperationTimeout: time.Millisecond,
ExposeTimeout: time.Millisecond,
StorageClass: "fake-sc",
SourcePVName: "fake-pv",
},
snapshotClientObj: []runtime.Object{
vsObject,
@@ -420,6 +503,24 @@ func TestExpose(t *testing.T) {
},
kubeClientObj: []runtime.Object{
daemonSet,
scObj,
},
expectedAffinity: &corev1api.Affinity{
NodeAffinity: &corev1api.NodeAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: &corev1api.NodeSelector{
NodeSelectorTerms: []corev1api.NodeSelectorTerm{
{
MatchExpressions: []corev1api.NodeSelectorRequirement{
{
Key: "kubernetes.io/os",
Operator: corev1api.NodeSelectorOpNotIn,
Values: []string{"windows"},
},
},
},
},
},
},
},
},
{
@@ -432,6 +533,8 @@ func TestExpose(t *testing.T) {
OperationTimeout: time.Millisecond,
ExposeTimeout: time.Millisecond,
VolumeSize: *resource.NewQuantity(567890, ""),
StorageClass: "fake-sc",
SourcePVName: "fake-pv",
},
snapshotClientObj: []runtime.Object{
vsObjectWithoutRestoreSize,
@@ -439,8 +542,26 @@ func TestExpose(t *testing.T) {
},
kubeClientObj: []runtime.Object{
daemonSet,
scObj,
},
expectedVolumeSize: resource.NewQuantity(567890, ""),
expectedAffinity: &corev1api.Affinity{
NodeAffinity: &corev1api.NodeAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: &corev1api.NodeSelector{
NodeSelectorTerms: []corev1api.NodeSelectorTerm{
{
MatchExpressions: []corev1api.NodeSelectorRequirement{
{
Key: "kubernetes.io/os",
Operator: corev1api.NodeSelectorOpNotIn,
Values: []string{"windows"},
},
},
},
},
},
},
},
},
{
name: "backupPod mounts read only backupPVC",
@@ -449,6 +570,7 @@ func TestExpose(t *testing.T) {
SnapshotName: "fake-vs",
SourceNamespace: "fake-ns",
StorageClass: "fake-sc",
SourcePVName: "fake-pv",
AccessMode: AccessModeFileSystem,
OperationTimeout: time.Millisecond,
ExposeTimeout: time.Millisecond,
@@ -465,8 +587,26 @@ func TestExpose(t *testing.T) {
},
kubeClientObj: []runtime.Object{
daemonSet,
scObj,
},
expectedReadOnlyPVC: true,
expectedAffinity: &corev1api.Affinity{
NodeAffinity: &corev1api.NodeAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: &corev1api.NodeSelector{
NodeSelectorTerms: []corev1api.NodeSelectorTerm{
{
MatchExpressions: []corev1api.NodeSelectorRequirement{
{
Key: "kubernetes.io/os",
Operator: corev1api.NodeSelectorOpNotIn,
Values: []string{"windows"},
},
},
},
},
},
},
},
},
{
name: "backupPod mounts read only backupPVC and storageClass specified in backupPVC config",
@@ -475,6 +615,7 @@ func TestExpose(t *testing.T) {
SnapshotName: "fake-vs",
SourceNamespace: "fake-ns",
StorageClass: "fake-sc",
SourcePVName: "fake-pv",
AccessMode: AccessModeFileSystem,
OperationTimeout: time.Millisecond,
ExposeTimeout: time.Millisecond,
@@ -491,9 +632,27 @@ func TestExpose(t *testing.T) {
},
kubeClientObj: []runtime.Object{
daemonSet,
scObj,
},
expectedReadOnlyPVC: true,
expectedBackupPVCStorageClass: "fake-sc-read-only",
expectedAffinity: &corev1api.Affinity{
NodeAffinity: &corev1api.NodeAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: &corev1api.NodeSelector{
NodeSelectorTerms: []corev1api.NodeSelectorTerm{
{
MatchExpressions: []corev1api.NodeSelectorRequirement{
{
Key: "kubernetes.io/os",
Operator: corev1api.NodeSelectorOpNotIn,
Values: []string{"windows"},
},
},
},
},
},
},
},
},
{
name: "backupPod mounts backupPVC with storageClass specified in backupPVC config",
@@ -502,6 +661,7 @@ func TestExpose(t *testing.T) {
SnapshotName: "fake-vs",
SourceNamespace: "fake-ns",
StorageClass: "fake-sc",
SourcePVName: "fake-pv",
AccessMode: AccessModeFileSystem,
OperationTimeout: time.Millisecond,
ExposeTimeout: time.Millisecond,
@@ -517,8 +677,26 @@ func TestExpose(t *testing.T) {
},
kubeClientObj: []runtime.Object{
daemonSet,
scObj,
},
expectedBackupPVCStorageClass: "fake-sc-read-only",
expectedAffinity: &corev1api.Affinity{
NodeAffinity: &corev1api.NodeAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: &corev1api.NodeSelector{
NodeSelectorTerms: []corev1api.NodeSelectorTerm{
{
MatchExpressions: []corev1api.NodeSelectorRequirement{
{
Key: "kubernetes.io/os",
Operator: corev1api.NodeSelectorOpNotIn,
Values: []string{"windows"},
},
},
},
},
},
},
},
},
{
name: "Affinity per StorageClass",
@@ -527,6 +705,7 @@ func TestExpose(t *testing.T) {
SnapshotName: "fake-vs",
SourceNamespace: "fake-ns",
StorageClass: "fake-sc",
SourcePVName: "fake-pv",
AccessMode: AccessModeFileSystem,
OperationTimeout: time.Millisecond,
ExposeTimeout: time.Millisecond,
@@ -551,6 +730,7 @@ func TestExpose(t *testing.T) {
},
kubeClientObj: []runtime.Object{
daemonSet,
scObj,
},
expectedAffinity: &corev1api.Affinity{
NodeAffinity: &corev1api.NodeAffinity{
@@ -563,6 +743,11 @@ func TestExpose(t *testing.T) {
Operator: corev1api.NodeSelectorOpIn,
Values: []string{"Linux"},
},
{
Key: "kubernetes.io/os",
Operator: corev1api.NodeSelectorOpNotIn,
Values: []string{"windows"},
},
},
},
},
@@ -577,6 +762,7 @@ func TestExpose(t *testing.T) {
SnapshotName: "fake-vs",
SourceNamespace: "fake-ns",
StorageClass: "fake-sc",
SourcePVName: "fake-pv",
AccessMode: AccessModeFileSystem,
OperationTimeout: time.Millisecond,
ExposeTimeout: time.Millisecond,
@@ -606,6 +792,7 @@ func TestExpose(t *testing.T) {
},
kubeClientObj: []runtime.Object{
daemonSet,
scObj,
},
expectedBackupPVCStorageClass: "fake-sc-read-only",
expectedAffinity: &corev1api.Affinity{
@@ -619,6 +806,11 @@ func TestExpose(t *testing.T) {
Operator: corev1api.NodeSelectorOpIn,
Values: []string{"amd64"},
},
{
Key: "kubernetes.io/os",
Operator: corev1api.NodeSelectorOpNotIn,
Values: []string{"windows"},
},
},
},
},
@@ -633,6 +825,7 @@ func TestExpose(t *testing.T) {
SnapshotName: "fake-vs",
SourceNamespace: "fake-ns",
StorageClass: "fake-sc",
SourcePVName: "fake-pv",
AccessMode: AccessModeFileSystem,
OperationTimeout: time.Millisecond,
ExposeTimeout: time.Millisecond,
@@ -649,9 +842,26 @@ func TestExpose(t *testing.T) {
},
kubeClientObj: []runtime.Object{
daemonSet,
scObj,
},
expectedBackupPVCStorageClass: "fake-sc-read-only",
expectedAffinity: nil,
expectedAffinity: &corev1api.Affinity{
NodeAffinity: &corev1api.NodeAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: &corev1api.NodeSelector{
NodeSelectorTerms: []corev1api.NodeSelectorTerm{
{
MatchExpressions: []corev1api.NodeSelectorRequirement{
{
Key: "kubernetes.io/os",
Operator: corev1api.NodeSelectorOpNotIn,
Values: []string{"windows"},
},
},
},
},
},
},
},
},
{
name: "IntolerateSourceNode, get source node fail",
@@ -677,6 +887,7 @@ func TestExpose(t *testing.T) {
},
kubeClientObj: []runtime.Object{
daemonSet,
scObj,
},
kubeReactors: []reactor{
{
@@ -687,7 +898,23 @@ func TestExpose(t *testing.T) {
},
},
},
expectedAffinity: nil,
expectedAffinity: &corev1api.Affinity{
NodeAffinity: &corev1api.NodeAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: &corev1api.NodeSelector{
NodeSelectorTerms: []corev1api.NodeSelectorTerm{
{
MatchExpressions: []corev1api.NodeSelectorRequirement{
{
Key: "kubernetes.io/os",
Operator: corev1api.NodeSelectorOpNotIn,
Values: []string{"windows"},
},
},
},
},
},
},
},
expectedPVCAnnotation: nil,
},
{
@@ -714,8 +941,25 @@ func TestExpose(t *testing.T) {
},
kubeClientObj: []runtime.Object{
daemonSet,
scObj,
},
expectedAffinity: &corev1api.Affinity{
NodeAffinity: &corev1api.NodeAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: &corev1api.NodeSelector{
NodeSelectorTerms: []corev1api.NodeSelectorTerm{
{
MatchExpressions: []corev1api.NodeSelectorRequirement{
{
Key: "kubernetes.io/os",
Operator: corev1api.NodeSelectorOpNotIn,
Values: []string{"windows"},
},
},
},
},
},
},
},
expectedAffinity: nil,
expectedPVCAnnotation: map[string]string{util.VSphereCNSFastCloneAnno: "true"},
},
{
@@ -744,6 +988,7 @@ func TestExpose(t *testing.T) {
daemonSet,
volumeAttachement1,
volumeAttachement2,
scObj,
},
expectedAffinity: &corev1api.Affinity{
NodeAffinity: &corev1api.NodeAffinity{
@@ -751,6 +996,11 @@ func TestExpose(t *testing.T) {
NodeSelectorTerms: []corev1api.NodeSelectorTerm{
{
MatchExpressions: []corev1api.NodeSelectorRequirement{
{
Key: "kubernetes.io/os",
Operator: corev1api.NodeSelectorOpNotIn,
Values: []string{"windows"},
},
{
Key: "kubernetes.io/hostname",
Operator: corev1api.NodeSelectorOpNotIn,
@@ -844,6 +1094,8 @@ func TestExpose(t *testing.T) {
if test.expectedAffinity != nil {
assert.Equal(t, test.expectedAffinity, backupPod.Spec.Affinity)
} else {
assert.Nil(t, backupPod.Spec.Affinity)
}
if test.expectedPVCAnnotation != nil {

View File

@@ -493,13 +493,15 @@ func (e *genericRestoreExposer) createRestorePod(
containerName := string(ownerObject.UID)
volumeName := string(ownerObject.UID)
var podAffinity *corev1api.Affinity
if selectedNode == "" {
e.log.Infof("No selected node for restore pod. Try to get affinity from the node-agent config.")
nodeSelector := map[string]string{}
if selectedNode != "" {
affinity = nil
nodeSelector["kubernetes.io/hostname"] = selectedNode
e.log.Infof("Selected node for restore pod. Ignore affinity from the node-agent config.")
}
if affinity != nil {
podAffinity = kube.ToSystemAffinity([]*kube.LoadAffinity{affinity})
}
if affinity == nil {
affinity = &kube.LoadAffinity{}
}
podInfo, err := getInheritedPodInfo(ctx, e.kubeClient, ownerObject.Namespace, nodeOS)
@@ -566,7 +568,6 @@ func (e *genericRestoreExposer) createRestorePod(
args = append(args, podInfo.logLevelArgs...)
var securityCtx *corev1api.PodSecurityContext
nodeSelector := map[string]string{}
podOS := corev1api.PodOS{}
if nodeOS == kube.NodeOSWindows {
userID := "ContainerAdministrator"
@@ -576,9 +577,14 @@ func (e *genericRestoreExposer) createRestorePod(
},
}
nodeSelector[kube.NodeOSLabel] = kube.NodeOSWindows
podOS.Name = kube.NodeOSWindows
affinity.NodeSelector.MatchExpressions = append(affinity.NodeSelector.MatchExpressions, metav1.LabelSelectorRequirement{
Key: kube.NodeOSLabel,
Values: []string{kube.NodeOSWindows},
Operator: metav1.LabelSelectorOpIn,
})
toleration = append(toleration, []corev1api.Toleration{
{
Key: "os",
@@ -599,10 +605,17 @@ func (e *genericRestoreExposer) createRestorePod(
RunAsUser: &userID,
}
nodeSelector[kube.NodeOSLabel] = kube.NodeOSLinux
podOS.Name = kube.NodeOSLinux
affinity.NodeSelector.MatchExpressions = append(affinity.NodeSelector.MatchExpressions, metav1.LabelSelectorRequirement{
Key: kube.NodeOSLabel,
Values: []string{kube.NodeOSWindows},
Operator: metav1.LabelSelectorOpNotIn,
})
}
podAffinity := kube.ToSystemAffinity(affinity, nil)
pod := &corev1api.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: restorePodName,
@@ -656,7 +669,6 @@ func (e *genericRestoreExposer) createRestorePod(
ServiceAccountName: podInfo.serviceAccount,
TerminationGracePeriodSeconds: &gracePeriod,
Volumes: volumes,
NodeName: selectedNode,
RestartPolicy: corev1api.RestartPolicyNever,
SecurityContext: securityCtx,
Tolerations: toleration,

View File

@@ -434,6 +434,8 @@ func (e *podVolumeExposer) createHostingPod(
args = append(args, podInfo.logFormatArgs...)
args = append(args, podInfo.logLevelArgs...)
affinity := &kube.LoadAffinity{}
var securityCtx *corev1api.PodSecurityContext
var containerSecurityCtx *corev1api.SecurityContext
nodeSelector := map[string]string{}
@@ -446,9 +448,14 @@ func (e *podVolumeExposer) createHostingPod(
},
}
nodeSelector[kube.NodeOSLabel] = kube.NodeOSWindows
podOS.Name = kube.NodeOSWindows
affinity.NodeSelector.MatchExpressions = append(affinity.NodeSelector.MatchExpressions, metav1.LabelSelectorRequirement{
Key: kube.NodeOSLabel,
Values: []string{kube.NodeOSWindows},
Operator: metav1.LabelSelectorOpIn,
})
toleration = append(toleration, []corev1api.Toleration{
{
Key: "os",
@@ -472,10 +479,17 @@ func (e *podVolumeExposer) createHostingPod(
Privileged: &privileged,
}
nodeSelector[kube.NodeOSLabel] = kube.NodeOSLinux
podOS.Name = kube.NodeOSLinux
affinity.NodeSelector.MatchExpressions = append(affinity.NodeSelector.MatchExpressions, metav1.LabelSelectorRequirement{
Key: kube.NodeOSLabel,
Values: []string{kube.NodeOSWindows},
Operator: metav1.LabelSelectorOpNotIn,
})
}
podAffinity := kube.ToSystemAffinity(affinity, nil)
pod := &corev1api.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: hostingPodName,
@@ -495,6 +509,7 @@ func (e *podVolumeExposer) createHostingPod(
Spec: corev1api.PodSpec{
NodeSelector: nodeSelector,
OS: &podOS,
Affinity: podAffinity,
Containers: []corev1api.Container{
{
Name: containerName,

View File

@@ -235,12 +235,28 @@ func DaemonSet(namespace string, opts ...podTemplateOption) *appsv1api.DaemonSet
if c.forWindows {
daemonSet.Spec.Template.Spec.SecurityContext = nil
daemonSet.Spec.Template.Spec.Containers[0].SecurityContext = nil
daemonSet.Spec.Template.Spec.NodeSelector = map[string]string{
"kubernetes.io/os": "windows",
}
daemonSet.Spec.Template.Spec.OS = &corev1api.PodOS{
Name: "windows",
}
daemonSet.Spec.Template.Spec.Affinity = &corev1api.Affinity{
NodeAffinity: &corev1api.NodeAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: &corev1api.NodeSelector{
NodeSelectorTerms: []corev1api.NodeSelectorTerm{
{
MatchExpressions: []corev1api.NodeSelectorRequirement{
{
Key: "kubernetes.io/os",
Values: []string{"windows"},
Operator: corev1api.NodeSelectorOpIn,
},
},
},
},
},
},
}
daemonSet.Spec.Template.Spec.Tolerations = []corev1api.Toleration{
{
Key: "os",
@@ -256,11 +272,22 @@ func DaemonSet(namespace string, opts ...podTemplateOption) *appsv1api.DaemonSet
},
}
} else {
daemonSet.Spec.Template.Spec.NodeSelector = map[string]string{
"kubernetes.io/os": "linux",
}
daemonSet.Spec.Template.Spec.OS = &corev1api.PodOS{
Name: "linux",
daemonSet.Spec.Template.Spec.Affinity = &corev1api.Affinity{
NodeAffinity: &corev1api.NodeAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: &corev1api.NodeSelector{
NodeSelectorTerms: []corev1api.NodeSelectorTerm{
{
MatchExpressions: []corev1api.NodeSelectorRequirement{
{
Key: "kubernetes.io/os",
Values: []string{"windows"},
Operator: corev1api.NodeSelectorOpNotIn,
},
},
},
},
},
},
}
}

View File

@@ -34,8 +34,23 @@ func TestDaemonSet(t *testing.T) {
assert.Equal(t, "velero", ds.ObjectMeta.Namespace)
assert.Equal(t, "node-agent", ds.Spec.Template.ObjectMeta.Labels["name"])
assert.Equal(t, "node-agent", ds.Spec.Template.ObjectMeta.Labels["role"])
assert.Equal(t, "linux", ds.Spec.Template.Spec.NodeSelector["kubernetes.io/os"])
assert.Equal(t, "linux", string(ds.Spec.Template.Spec.OS.Name))
assert.Equal(t, &corev1api.Affinity{
NodeAffinity: &corev1api.NodeAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: &corev1api.NodeSelector{
NodeSelectorTerms: []corev1api.NodeSelectorTerm{
{
MatchExpressions: []corev1api.NodeSelectorRequirement{
{
Key: "kubernetes.io/os",
Values: []string{"windows"},
Operator: corev1api.NodeSelectorOpNotIn,
},
},
},
},
},
},
}, ds.Spec.Template.Spec.Affinity)
assert.Equal(t, corev1api.PodSecurityContext{RunAsUser: &userID}, *ds.Spec.Template.Spec.SecurityContext)
assert.Equal(t, corev1api.SecurityContext{Privileged: &boolFalse}, *ds.Spec.Template.Spec.Containers[0].SecurityContext)
assert.Len(t, ds.Spec.Template.Spec.Volumes, 3)
@@ -80,8 +95,24 @@ func TestDaemonSet(t *testing.T) {
assert.Equal(t, "velero", ds.ObjectMeta.Namespace)
assert.Equal(t, "node-agent-windows", ds.Spec.Template.ObjectMeta.Labels["name"])
assert.Equal(t, "node-agent", ds.Spec.Template.ObjectMeta.Labels["role"])
assert.Equal(t, "windows", ds.Spec.Template.Spec.NodeSelector["kubernetes.io/os"])
assert.Equal(t, "windows", string(ds.Spec.Template.Spec.OS.Name))
assert.Equal(t, &corev1api.Affinity{
NodeAffinity: &corev1api.NodeAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: &corev1api.NodeSelector{
NodeSelectorTerms: []corev1api.NodeSelectorTerm{
{
MatchExpressions: []corev1api.NodeSelectorRequirement{
{
Key: "kubernetes.io/os",
Values: []string{"windows"},
Operator: corev1api.NodeSelectorOpIn,
},
},
},
},
},
},
}, ds.Spec.Template.Spec.Affinity)
assert.Equal(t, (*corev1api.PodSecurityContext)(nil), ds.Spec.Template.Spec.SecurityContext)
assert.Equal(t, (*corev1api.SecurityContext)(nil), ds.Spec.Template.Spec.Containers[0].SecurityContext)
}

View File

@@ -364,12 +364,26 @@ func Deployment(namespace string, opts ...podTemplateOption) *appsv1api.Deployme
Spec: corev1api.PodSpec{
RestartPolicy: corev1api.RestartPolicyAlways,
ServiceAccountName: c.serviceAccountName,
NodeSelector: map[string]string{
"kubernetes.io/os": "linux",
},
OS: &corev1api.PodOS{
Name: "linux",
},
Affinity: &corev1api.Affinity{
NodeAffinity: &corev1api.NodeAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: &corev1api.NodeSelector{
NodeSelectorTerms: []corev1api.NodeSelectorTerm{
{
MatchExpressions: []corev1api.NodeSelectorRequirement{
{
Key: "kubernetes.io/os",
Values: []string{"windows"},
Operator: corev1api.NodeSelectorOpNotIn,
},
},
},
},
},
},
},
Containers: []corev1api.Container{
{
Name: "velero",

View File

@@ -100,8 +100,23 @@ func TestDeployment(t *testing.T) {
assert.Len(t, deploy.Spec.Template.Spec.Containers[0].Args, 2)
assert.Equal(t, "--repo-maintenance-job-configmap=test-repo-maintenance-config", deploy.Spec.Template.Spec.Containers[0].Args[1])
assert.Equal(t, "linux", deploy.Spec.Template.Spec.NodeSelector["kubernetes.io/os"])
assert.Equal(t, "linux", string(deploy.Spec.Template.Spec.OS.Name))
assert.Equal(t, &corev1api.Affinity{
NodeAffinity: &corev1api.NodeAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: &corev1api.NodeSelector{
NodeSelectorTerms: []corev1api.NodeSelectorTerm{
{
MatchExpressions: []corev1api.NodeSelectorRequirement{
{
Key: "kubernetes.io/os",
Values: []string{"windows"},
Operator: corev1api.NodeSelectorOpNotIn,
},
},
},
},
},
},
}, deploy.Spec.Template.Spec.Affinity)
}
func TestDeploymentWithPriorityClassName(t *testing.T) {

Some files were not shown because too many files have changed in this diff Show More