Merge branch 'main' into report-incremental-fallback

This commit is contained in:
Lyndon-Li
2026-09-09 14:19:59 +08:00
30 changed files with 425 additions and 88 deletions
+1 -1
View File
@@ -194,7 +194,7 @@ jobs:
- name: Install MinIO
run: |
docker run -d --rm -p 9000:9000 -e "MINIO_ROOT_USER=minio" -e "MINIO_ROOT_PASSWORD=minio123" -e "MINIO_DEFAULT_BUCKETS=bucket,additional-bucket" bitnami/minio:local
- uses: helm/kind-action@c72b4750145dbfb1c71734c3782a4db35a1c65c0 # v1 + fix: add curl retry flags (https://github.com/helm/kind-action/pull/165)
- uses: helm/kind-action@06c1ae10762d3b9c1644e7fe69596ae519e015a2 # v1 + fix: add curl retry flags (https://github.com/helm/kind-action/pull/165)
with:
cluster_name: "kind"
version: "v0.32.0"
+4 -4
View File
@@ -28,7 +28,7 @@ Please use the version selector at the top of the site to ensure you are using t
## Troubleshooting
If you encounter issues, review the [troubleshooting docs][30], [file an issue][4], or talk to us on the [#velero channel][25] on the Kubernetes Slack server.
If you encounter issues, review the [troubleshooting docs][30], [file an issue][4], or talk to us on the [#velero-users][25] and [#velero-dev][26] channel on the Kubernetes Slack server.
## Community
@@ -36,8 +36,7 @@ Velero is an open community and we welcome your participation. The best way to g
* Join the [Velero community meetings](https://velero.io/community/), held bi-weekly, alternating between Beijing-friendly and US/Europe-friendly time zones.
* Subscribe to the [project meeting calendar](https://zoom-lfx.platform.linuxfoundation.org/meetings/velero?view=week).
* Watch previous meetings on our [YouTube channel](https://www.youtube.com/playlist?list=PL7bmigfV0EqQRysvqvqOtRNk4L5S7uqwM).
* Chat with us on the [Kubernetes Slack][25] `#velero` channel and join the [mailing list][24].
* Chat with us on the [Kubernetes Slack][25] `#velero-users` channel and join the [mailing list][24].
See the [community page](https://velero.io/community/) for the full schedule and details.
@@ -98,7 +97,8 @@ For website terms of use, trademark policy and other project policies please see
[12]: https://github.com/kubernetes/kubernetes/blob/master/cluster/addons/dns/README.md
[14]: https://github.com/kubernetes/kubernetes
[24]: https://groups.google.com/forum/#!forum/projectvelero
[25]: https://kubernetes.slack.com/messages/velero
[25]: https://kubernetes.slack.com/messages/velero-users
[26]: https://kubernetes.slack.com/messages/velero-dev
[29]: https://velero.io/docs/
[30]: https://velero.io/docs/troubleshooting
[31]: https://velero.io/docs/start-contributing
+1
View File
@@ -0,0 +1 @@
Fix generic CSI changeID retrieval and honor snapshot class deletion policy for CBT retention
+1
View File
@@ -0,0 +1 @@
Add ID to repo snapshot and modify block uploader to use snapshot ID in logs
+1
View File
@@ -0,0 +1 @@
Add MustIncludeAdditionalItemPVCs to help track BIA added PVC's PVB creation.
+5 -5
View File
@@ -49,7 +49,7 @@ require (
golang.org/x/sys v0.47.0
golang.org/x/text v0.41.0
google.golang.org/api v0.283.0
google.golang.org/grpc v1.82.1
google.golang.org/grpc v1.83.1
google.golang.org/protobuf v1.36.12
k8s.io/api v0.36.0
k8s.io/apiextensions-apiserver v0.36.0
@@ -66,7 +66,7 @@ require (
)
require (
cel.dev/expr v0.25.1 // indirect
cel.dev/expr v0.25.2 // indirect
cloud.google.com/go v0.123.0 // indirect
cloud.google.com/go/auth v0.20.0 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
@@ -76,7 +76,7 @@ require (
github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0 // indirect
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v1.7.2 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.32.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.33.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.55.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.55.0 // indirect
github.com/Masterminds/semver/v3 v3.5.0 // indirect
@@ -181,7 +181,7 @@ require (
github.com/prometheus/procfs v0.20.1 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/rs/xid v1.6.0 // indirect
github.com/spiffe/go-spiffe/v2 v2.6.0 // indirect
github.com/spiffe/go-spiffe/v2 v2.7.0 // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/tinylib/msgp v1.6.1 // indirect
github.com/vladimirvivien/gexe v0.4.0 // indirect
@@ -189,7 +189,7 @@ require (
github.com/zeebo/blake3 v0.2.4 // indirect
github.com/zeebo/xxh3 v1.1.0 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/contrib/detectors/gcp v1.43.0 // indirect
go.opentelemetry.io/contrib/detectors/gcp v1.44.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 // indirect
go.opentelemetry.io/otel v1.44.0 // indirect
+10 -10
View File
@@ -1,5 +1,5 @@
cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4=
cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4=
cel.dev/expr v0.25.2 h1:K6j46C81hXtZQfuX60cVWQFBJahKSE2gfRbNuvr5bFs=
cel.dev/expr v0.25.2/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4=
cloud.google.com/go v0.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE=
cloud.google.com/go v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU=
cloud.google.com/go/auth v0.20.0 h1:kXTssoVb4azsVDoUiF8KvxAqrsQcQtB53DcSgta74CA=
@@ -48,8 +48,8 @@ github.com/AzureAD/microsoft-authentication-library-for-go v1.7.2 h1:RHK7bS+HQMs
github.com/AzureAD/microsoft-authentication-library-for-go v1.7.2/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk=
github.com/GehirnInc/crypt v0.0.0-20230320061759-8cc1b52080c5 h1:IEjq88XO4PuBDcvmjQJcQGg+w+UaafSy8G5Kcb5tBhI=
github.com/GehirnInc/crypt v0.0.0-20230320061759-8cc1b52080c5/go.mod h1:exZ0C/1emQJAw5tHOaUDyY1ycttqBAPcxuzf7QbY6ec=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.32.0 h1:rIkQfkCOVKc1OiRCNcSDD8ml5RJlZbH/Xsq7lbpynwc=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.32.0/go.mod h1:RD2SsorTmYhF6HkTmDw7KmPYQk8OBYwTkuasChwv7R4=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.33.0 h1:l7+6kwRMJNwdCvYdDl7Eax+wzEYHSnNY7zrrfbhDdTA=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.33.0/go.mod h1:pJTkW8hEUIIi3Pf65lPZOnn4Y81yCllX6IWk2jNXdkM=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.55.0 h1:UnDZ/zFfG1JhH/DqxIZYU/1CUAlTUScoXD/LcM2Ykk8=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.55.0/go.mod h1:IA1C1U7jO/ENqm/vhi7V9YYpBsp+IMyqNrEN94N7tVc=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.55.0 h1:7t/qx5Ost0s0wbA/VDrByOooURhp+ikYwv20i9Y07TQ=
@@ -417,8 +417,8 @@ github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiT
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spiffe/go-spiffe/v2 v2.6.0 h1:l+DolpxNWYgruGQVV0xsfeya3CsC7m8iBzDnMpsbLuo=
github.com/spiffe/go-spiffe/v2 v2.6.0/go.mod h1:gm2SeUoMZEtpnzPNs2Csc0D/gX33k1xIx7lEzqblHEs=
github.com/spiffe/go-spiffe/v2 v2.7.0 h1:uXe1MflJoHw58wAUvxVlcM7WpKtijWG7I1UidcGh6g4=
github.com/spiffe/go-spiffe/v2 v2.7.0/go.mod h1:47Q0Q9/AqGha8QLHp+kxpH4Wca7X7EnOtlIJy3mxZ3U=
github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs=
github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@@ -466,8 +466,8 @@ github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs=
github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/detectors/gcp v1.43.0 h1:62yY3dT7/ShwOxzA0RsKRgshBmfElKI4d/Myu2OxDFU=
go.opentelemetry.io/contrib/detectors/gcp v1.43.0/go.mod h1:RyaZMFY7yi1kAs45S6mbFGz8O8rqB0dTY14uzvG4LCs=
go.opentelemetry.io/contrib/detectors/gcp v1.44.0 h1:NmLfL734pJhM0JKaYd2Y28+nY9dPRWYAAbxhRCrKXPw=
go.opentelemetry.io/contrib/detectors/gcp v1.44.0/go.mod h1:tNAsgd8avTGke1+MndXlU5Cru4PQ9Ai/cCNWQv/ZJ/s=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0 h1:yI1/OhfEPy7J9eoa6Sj051C7n5dvpj0QX8g4sRchg04=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0/go.mod h1:NoUCKYWK+3ecatC4HjkRktREheMeEtrXoQxrqYFeHSc=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 h1:OyrsyzuttWTSur2qN/Lm0m2a8yqyIjUVBZcxFPuXq2o=
@@ -564,8 +564,8 @@ google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa h1:
google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:q4lMZS6kskjT5HvCPrnnypcDPVJqT/f4nfxmkE7gryY=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa h1:mZHHdPZl0dbGHCflZgAq/Q468DWVFcU2whhB2KAo8fk=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.82.1 h1:NnAxzGRA0677vCa4BUkOAnO5+FfQqVl9iUXeD0IqcGE=
google.golang.org/grpc v1.82.1/go.mod h1:yzTZ1TB1Z3SG+LIYaI+WiE8D5+PZ3ArnrSp8zF3+/ZA=
google.golang.org/grpc v1.83.1 h1:HIO0+BEtBP6soyqvqC8sNUjZ7bTs+0hFQuFF+RAy++Y=
google.golang.org/grpc v1.83.1/go.mod h1:kDyl6SKsiHKt0uylY5gtn5cEjkrIOhQOGDgIc4JGwzQ=
google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc=
google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+18 -4
View File
@@ -38,6 +38,10 @@ type volumeHelperImpl struct {
// pvcPodCache provides cached PVC to Pod mappings for improved performance.
// When there are many PVCs and pods, using this cache avoids O(N*M) lookups.
pvcPodCache *podvolumeutil.PVCPodCache
// pvcMustInclusionTracker provides read-only checks for whether a PVC is included
// in the backup as BIA's additionalItems through annotation
// backup.velero.io/must-include-additional-items.
pvcMustInclusionTracker vhutil.PVCMustInclusionTracker
}
// NewVolumeHelperImpl creates a VolumeHelper without PVC-to-Pod caching.
@@ -52,6 +56,7 @@ func NewVolumeHelperImpl(
client crclient.Client,
defaultVolumesToFSBackup bool,
backupExcludePVC bool,
pvcMustInclusionTracker vhutil.PVCMustInclusionTracker,
) vhutil.VolumeHelper {
// Pass nil namespaces - no cache will be built, so this never fails.
// This is used by plugins that don't need the cache optimization.
@@ -63,6 +68,7 @@ func NewVolumeHelperImpl(
defaultVolumesToFSBackup,
backupExcludePVC,
nil,
pvcMustInclusionTracker,
)
return vh
}
@@ -80,6 +86,7 @@ func NewVolumeHelperImplWithNamespaces(
defaultVolumesToFSBackup bool,
backupExcludePVC bool,
namespaces []string,
pvcMustInclusionTracker vhutil.PVCMustInclusionTracker,
) (vhutil.VolumeHelper, error) {
var pvcPodCache *podvolumeutil.PVCPodCache
if len(namespaces) > 0 {
@@ -98,6 +105,7 @@ func NewVolumeHelperImplWithNamespaces(
defaultVolumesToFSBackup: defaultVolumesToFSBackup,
backupExcludePVC: backupExcludePVC,
pvcPodCache: pvcPodCache,
pvcMustInclusionTracker: pvcMustInclusionTracker,
}, nil
}
@@ -109,6 +117,7 @@ func NewVolumeHelperImplWithCache(
client crclient.Client,
logger logrus.FieldLogger,
pvcPodCache *podvolumeutil.PVCPodCache,
pvcMustInclusionTracker vhutil.PVCMustInclusionTracker,
) (vhutil.VolumeHelper, error) {
resourcePolicies, err := resourcepolicies.GetResourcePoliciesFromBackup(backup, client, logger)
if err != nil {
@@ -123,6 +132,7 @@ func NewVolumeHelperImplWithCache(
defaultVolumesToFSBackup: boolptr.IsSetToTrue(backup.Spec.DefaultVolumesToFsBackup),
backupExcludePVC: boolptr.IsSetToTrue(backup.Spec.SnapshotMoveData),
pvcPodCache: pvcPodCache,
pvcMustInclusionTracker: pvcMustInclusionTracker,
}, nil
}
@@ -260,7 +270,7 @@ func (v *volumeHelperImpl) ShouldPerformSnapshot(obj runtime.Unstructured, group
}
func (v volumeHelperImpl) ShouldPerformFSBackup(volume corev1api.Volume, pod corev1api.Pod) (bool, error) {
if !v.shouldIncludeVolumeInBackup(volume) {
if !v.shouldIncludeVolumeInBackup(volume, pod) {
v.logger.Debugf("skip fs-backup action for pod %s's volume %s, due to not pass volume check.", pod.Namespace+"/"+pod.Name, volume.Name)
return false, nil
}
@@ -442,7 +452,7 @@ func (v *volumeHelperImpl) GetSnapshotClass(obj runtime.Unstructured, groupResou
return action.GetSnapshotClass()
}
func (v *volumeHelperImpl) shouldIncludeVolumeInBackup(vol corev1api.Volume) bool {
func (v *volumeHelperImpl) shouldIncludeVolumeInBackup(vol corev1api.Volume, pod corev1api.Pod) bool {
includeVolumeInBackup := true
// cannot backup hostpath volumes as they are not mounted into /var/lib/kubelet/pods
// and therefore not accessible to the node agent daemon set.
@@ -465,8 +475,12 @@ func (v *volumeHelperImpl) shouldIncludeVolumeInBackup(vol corev1api.Volume) boo
if vol.DownwardAPI != nil {
includeVolumeInBackup = false
}
if vol.PersistentVolumeClaim != nil && v.backupExcludePVC {
includeVolumeInBackup = false
if vol.PersistentVolumeClaim != nil {
if v.backupExcludePVC {
if v.pvcMustInclusionTracker == nil || !v.pvcMustInclusionTracker.IsPVCIncluded(pod.Namespace, vol.PersistentVolumeClaim.ClaimName) {
includeVolumeInBackup = false
}
}
}
// don't include volumes that mount the default service account token.
if strings.HasPrefix(vol.Name, "default-token") {
@@ -35,6 +35,7 @@ import (
"github.com/vmware-tanzu/velero/pkg/kuberesource"
velerotest "github.com/vmware-tanzu/velero/pkg/test"
podvolumeutil "github.com/vmware-tanzu/velero/pkg/util/podvolume"
vhutil "github.com/vmware-tanzu/velero/pkg/util/volumehelper"
)
func TestVolumeHelperImpl_ShouldPerformSnapshot(t *testing.T) {
@@ -329,6 +330,7 @@ func TestVolumeHelperImpl_ShouldPerformSnapshot(t *testing.T) {
fakeClient,
tc.defaultVolumesToFSBackup,
false,
nil,
)
obj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(tc.inputObj)
@@ -345,11 +347,23 @@ func TestVolumeHelperImpl_ShouldPerformSnapshot(t *testing.T) {
}
}
type mockPVCMustInclusionTracker struct {
isPVCIncluded func(namespace, pvcName string) bool
}
func (m *mockPVCMustInclusionTracker) IsPVCIncluded(namespace, pvcName string) bool {
if m.isPVCIncluded == nil {
return false
}
return m.isPVCIncluded(namespace, pvcName)
}
func TestVolumeHelperImpl_ShouldIncludeVolumeInBackup(t *testing.T) {
testCases := []struct {
name string
vol corev1api.Volume
backupExcludePVC bool
isPVCIncluded func(pvcName string) bool
shouldInclude bool
}{
{
@@ -445,6 +459,38 @@ func TestVolumeHelperImpl_ShouldIncludeVolumeInBackup(t *testing.T) {
backupExcludePVC: true,
shouldInclude: false,
},
{
name: "volume has pvc, backupExcludePVC is true, but isPVCIncluded returns true so include",
vol: corev1api.Volume{
Name: "sample-volume",
VolumeSource: corev1api.VolumeSource{
PersistentVolumeClaim: &corev1api.PersistentVolumeClaimVolumeSource{
ClaimName: "sample-pvc",
},
},
},
backupExcludePVC: true,
isPVCIncluded: func(pvcName string) bool {
return pvcName == "sample-pvc"
},
shouldInclude: true,
},
{
name: "volume has pvc, backupExcludePVC is false, isPVCIncluded returns false, but globally included so include",
vol: corev1api.Volume{
Name: "sample-volume",
VolumeSource: corev1api.VolumeSource{
PersistentVolumeClaim: &corev1api.PersistentVolumeClaimVolumeSource{
ClaimName: "sample-pvc",
},
},
},
backupExcludePVC: false,
isPVCIncluded: func(pvcName string) bool {
return false
},
shouldInclude: true,
},
{
name: "volume name has prefix default-token so do not include",
vol: corev1api.Volume{
@@ -480,13 +526,23 @@ func TestVolumeHelperImpl_ShouldIncludeVolumeInBackup(t *testing.T) {
if err != nil {
t.Fatalf("failed to build policy with error %v", err)
}
vh := &volumeHelperImpl{
volumePolicy: p,
snapshotVolumes: ptr.To(true),
logger: velerotest.NewLogger(),
backupExcludePVC: tc.backupExcludePVC,
var tracker vhutil.PVCMustInclusionTracker
if tc.isPVCIncluded != nil {
tracker = &mockPVCMustInclusionTracker{
isPVCIncluded: func(ns, pvcName string) bool {
return tc.isPVCIncluded(pvcName)
},
}
}
actualShouldInclude := vh.shouldIncludeVolumeInBackup(tc.vol)
vh := &volumeHelperImpl{
volumePolicy: p,
snapshotVolumes: ptr.To(true),
logger: velerotest.NewLogger(),
backupExcludePVC: tc.backupExcludePVC,
pvcMustInclusionTracker: tracker,
}
pod := corev1api.Pod{ObjectMeta: metav1.ObjectMeta{Namespace: "default"}}
actualShouldInclude := vh.shouldIncludeVolumeInBackup(tc.vol, pod)
assert.Equalf(t, actualShouldInclude, tc.shouldInclude, "Want shouldInclude as %v; Got actualShouldInclude as %v", tc.shouldInclude, actualShouldInclude)
})
}
@@ -694,6 +750,7 @@ func TestVolumeHelperImpl_ShouldPerformFSBackup(t *testing.T) {
fakeClient,
tc.defaultVolumesToFSBackup,
false,
nil,
)
actualShouldFSBackup, actualError := vh.ShouldPerformFSBackup(tc.pod.Spec.Volumes[0], *tc.pod)
@@ -889,6 +946,7 @@ func TestVolumeHelperImplWithCache_ShouldPerformSnapshot(t *testing.T) {
tc.defaultVolumesToFSBackup,
false,
namespaces,
nil,
)
require.NoError(t, err)
@@ -1041,6 +1099,7 @@ func TestVolumeHelperImplWithCache_ShouldPerformFSBackup(t *testing.T) {
tc.defaultVolumesToFSBackup,
false,
namespaces,
nil,
)
require.NoError(t, err)
@@ -1166,6 +1225,7 @@ volumePolicies:
fakeClient,
logrus.StandardLogger(),
cache,
nil,
)
if tc.expectError {
@@ -1221,7 +1281,7 @@ func TestNewVolumeHelperImplWithCache_UsesCache(t *testing.T) {
},
}
vh, err := NewVolumeHelperImplWithCache(backup, fakeClient, logrus.StandardLogger(), cache)
vh, err := NewVolumeHelperImplWithCache(backup, fakeClient, logrus.StandardLogger(), cache, nil)
require.NoError(t, err)
// Convert PV to unstructured
@@ -1353,6 +1413,7 @@ func TestVolumeHelperImpl_ShouldPerformSnapshot_UnboundPVC(t *testing.T) {
fakeClient,
false,
false,
nil,
)
obj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(tc.inputPVC)
@@ -1530,6 +1591,7 @@ func TestVolumeHelperImpl_ShouldPerformFSBackup_UnboundPVC(t *testing.T) {
fakeClient,
false,
false,
nil,
)
actualShouldFSBackup, actualError := vh.ShouldPerformFSBackup(tc.pod.Spec.Volumes[0], *tc.pod)
@@ -1669,6 +1731,7 @@ func TestGetDataMoverFromActionParameters(t *testing.T) {
fakeClient,
false,
false,
nil,
)
obj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(tc.inputObj)
@@ -1794,6 +1857,7 @@ func TestGetActionParameters(t *testing.T) {
fakeClient,
false,
false,
nil,
)
obj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(tc.inputObj)
@@ -1980,6 +2044,7 @@ func TestShouldPerformCustomAction(t *testing.T) {
fakeClient,
false,
false,
nil,
)
obj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(tc.inputObj)
@@ -2102,6 +2167,7 @@ func TestGetPVAndMatchAction(t *testing.T) {
fakeClient,
false,
false,
nil,
)
obj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(tc.inputObj)
+1
View File
@@ -136,6 +136,7 @@ func (p *pvcBackupItemAction) getVolumeHelperWithCache(backup *velerov1api.Backu
p.crClient,
p.log,
p.pvcPodCache,
nil,
)
if err != nil {
return nil, errors.Wrap(err, "failed to create VolumeHelper")
+3
View File
@@ -485,6 +485,8 @@ func (kb *kubernetesBackupper) BackupWithResolvers(
return err
}
pvcMustInclusionTracker := NewPVCMustInclusionTracker(backupRequest.MustIncludeAdditionalItemPVCs)
volumeHelperImpl, err := volumehelper.NewVolumeHelperImplWithNamespaces(
backupRequest.ResPolicies,
backupRequest.Spec.SnapshotVolumes,
@@ -493,6 +495,7 @@ func (kb *kubernetesBackupper) BackupWithResolvers(
boolptr.IsSetToTrue(backupRequest.Spec.DefaultVolumesToFsBackup),
!backupRequest.ResourceIncludesExcludes.ShouldInclude(kuberesource.PersistentVolumeClaims.String()),
namespaces,
pvcMustInclusionTracker,
)
if err != nil {
log.WithError(err).Error("Failed to build PVC-to-Pod cache for volume policy lookups")
+21
View File
@@ -245,6 +245,7 @@ func (ib *itemBackupper) backupItemInternal(logger logrus.FieldLogger, obj runti
// where it's been backed up from another pod), since we don't need >1 backup per PVC.
for _, volume := range pod.Spec.Volumes {
shouldDoFSBackup, err := ib.volumeHelperImpl.ShouldPerformFSBackup(volume, *pod)
if err != nil {
backupErrs = append(backupErrs, errors.WithStack(err))
}
@@ -480,6 +481,26 @@ func (ib *itemBackupper) executeActions(
delete(u.GetAnnotations(), velerov1api.MustIncludeAdditionalItemAnnotation)
obj = u
// If the BIA specifies that additional items must be included, we track any PVCs returned as additional items.
// This tracking is necessary because the FSB (File System Backup) evaluation for a Pod
// happens before its PVCs are processed. By tracking these explicitly included PVCs here,
// the FSB logic can correctly determine that the PVC will be backed up and therefore
// a PodVolumeBackup should be created.
// We track this unconditionally when mustInclude is true, because fine-grained backup filters
// might exclude a PVC even if it's globally included, but mustInclude overrides those filters.
if mustInclude && ib.backupRequest.MustIncludeAdditionalItemPVCs != nil {
for _, additionalItem := range additionalItemIdentifiers {
if additionalItem.GroupResource == kuberesource.PersistentVolumeClaims {
key := itemKey{
resource: additionalItem.GroupResource.String(),
namespace: additionalItem.Namespace,
name: additionalItem.Name,
}
ib.backupRequest.MustIncludeAdditionalItemPVCs.AddItem(key)
}
}
}
// If async plugin started async operation, add it to the ItemOperations list
// ignore during finalize phase
if operationID != "" {
+50
View File
@@ -0,0 +1,50 @@
/*
Copyright the Velero contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package backup
import (
"github.com/vmware-tanzu/velero/pkg/kuberesource"
vhutil "github.com/vmware-tanzu/velero/pkg/util/volumehelper"
)
// pvcMustInclusionTracker provides read-only checks for whether a PVC is included
// in the backup as BIA's additionalItems through annotation
// backup.velero.io/must-include-additional-items.
type pvcMustInclusionTracker struct {
mustInclude *backedUpItemsMap
}
func NewPVCMustInclusionTracker(mustInclude *backedUpItemsMap) vhutil.PVCMustInclusionTracker {
return &pvcMustInclusionTracker{
mustInclude: mustInclude,
}
}
func (p *pvcMustInclusionTracker) IsPVCIncluded(namespace, pvcName string) bool {
pvcKey := itemKey{
resource: kuberesource.PersistentVolumeClaims.String(),
namespace: namespace,
name: pvcName,
}
// 1. If the PVC was explicitly forced into the backup by a BIA, it will be backed up.
if p.mustInclude != nil && p.mustInclude.Has(pvcKey) {
return true
}
return false
}
@@ -0,0 +1,47 @@
/*
Copyright the Velero contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package backup
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/vmware-tanzu/velero/pkg/kuberesource"
)
func TestPVCMustInclusionTracker_IsPVCIncluded(t *testing.T) {
mustIncludeMap := NewBackedUpItemsMap()
tracker := NewPVCMustInclusionTracker(mustIncludeMap)
pvcKey1 := itemKey{
resource: kuberesource.PersistentVolumeClaims.String(),
namespace: "ns-1",
name: "pvc-1",
}
// Initially neither PVC is included
assert.False(t, tracker.IsPVCIncluded("ns-1", "pvc-1"))
// Add pvc-1 to mustInclude map
mustIncludeMap.AddItem(pvcKey1)
assert.True(t, tracker.IsPVCIncluded("ns-1", "pvc-1"))
// Check a PVC not in any map
assert.False(t, tracker.IsPVCIncluded("ns-1", "pvc-2"))
}
+10 -5
View File
@@ -83,11 +83,16 @@ type Request struct {
VolumeSnapshots SynchronizedVSList
PodVolumeBackups []*velerov1api.PodVolumeBackup
BackedUpItems *backedUpItemsMap
itemOperationsList *[]*itemoperation.BackupOperation
ResPolicies *resourcepolicies.Policies
SkippedPVTracker *skipPVTracker
VolumesInformation volume.BackupVolumesInformation
WorkerPool *ItemBlockWorkerPool
// MustIncludeAdditionalItemPVCs keeps track of PVCs that are returned as additionalItems
// by a BackupItemAction plugin with the must-include annotation. This is specifically
// used to ensure PodVolumeBackups (FSB) are created for these PVCs even when PVCs are
// excluded by global or fine-grained backup resource filters.
MustIncludeAdditionalItemPVCs *backedUpItemsMap
itemOperationsList *[]*itemoperation.BackupOperation
ResPolicies *resourcepolicies.Policies
SkippedPVTracker *skipPVTracker
VolumesInformation volume.BackupVolumesInformation
WorkerPool *ItemBlockWorkerPool
// ClusterScopedFilterMap holds resolved global filters for cluster-scoped resources.
// Key is the resolved group-resource string.
+5 -4
View File
@@ -395,10 +395,11 @@ func (b *backupReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr
func (b *backupReconciler) prepareBackupRequest(ctx context.Context, backup *velerov1api.Backup, logger logrus.FieldLogger) *pkgbackup.Request {
request := &pkgbackup.Request{
Backup: backup.DeepCopy(), // don't modify items in the cache
SkippedPVTracker: pkgbackup.NewSkipPVTracker(),
BackedUpItems: pkgbackup.NewBackedUpItemsMap(),
WorkerPool: pkgbackup.StartItemBlockWorkerPool(ctx, b.itemBlockWorkerCount, logger),
Backup: backup.DeepCopy(), // don't modify items in the cache
SkippedPVTracker: pkgbackup.NewSkipPVTracker(),
BackedUpItems: pkgbackup.NewBackedUpItemsMap(),
MustIncludeAdditionalItemPVCs: pkgbackup.NewBackedUpItemsMap(),
WorkerPool: pkgbackup.StartItemBlockWorkerPool(ctx, b.itemBlockWorkerCount, logger),
}
request.VolumesInformation.Init()
@@ -158,10 +158,11 @@ func (r *backupFinalizerReconciler) Reconcile(ctx context.Context, req ctrl.Requ
}
backupRequest := &pkgbackup.Request{
Backup: backup,
StorageLocation: location,
SkippedPVTracker: pkgbackup.NewSkipPVTracker(),
BackedUpItems: pkgbackup.NewBackedUpItemsMap(),
Backup: backup,
StorageLocation: location,
SkippedPVTracker: pkgbackup.NewSkipPVTracker(),
BackedUpItems: pkgbackup.NewBackedUpItemsMap(),
MustIncludeAdditionalItemPVCs: pkgbackup.NewBackedUpItemsMap(),
}
var outBackupFile *os.File
if len(operations) > 0 {
+23 -1
View File
@@ -496,6 +496,7 @@ func (e *csiSnapshotExposer) CleanUp(ctx context.Context, ownerObject corev1api.
backupPodName := ownerObject.Name
backupPVCName := ownerObject.Name
backupVSName := ownerObject.Name
backupVSCName := ownerObject.Name
kube.DeletePodIfAny(ctx, e.kubeClient.CoreV1(), backupPodName, ownerObject.Namespace, e.log)
kube.DeletePVAndPVCIfAny(ctx, e.kubeClient.CoreV1(), backupPVCName, ownerObject.Namespace, cleanUpTimeout, e.log)
@@ -507,6 +508,13 @@ func (e *csiSnapshotExposer) CleanUp(ctx context.Context, ownerObject corev1api.
csi.DeleteVolumeSnapshotIfAny(ctx, e.csiSnapshotClient, backupVSName, ownerObject.Namespace, e.log)
csi.DeleteVolumeSnapshotIfAny(ctx, e.csiSnapshotClient, vsName, sourceNamespace, e.log)
// The backup VSC is created by Velero as an internal handle to the source
// snapshot. Deleting the backup VS above only cascades to it when its
// deletion policy is Delete, so remove it explicitly to avoid leaking the
// object under a Retain policy. Deleting a Retain VSC drops only the API
// object and leaves the underlying snapshot intact.
csi.DeleteVolumeSnapshotContentIfAny(ctx, e.csiSnapshotClient, backupVSCName, e.log)
}
func getVolumeModeByAccessMode(accessMode string, dataMover string) (corev1api.PersistentVolumeMode, error) {
@@ -571,7 +579,21 @@ func (e *csiSnapshotExposer) createBackupVSC(ctx context.Context, ownerObject co
Source: snapshotv1api.VolumeSnapshotContentSource{
SnapshotHandle: snapshotVSC.Status.SnapshotHandle,
},
DeletionPolicy: snapshotv1api.VolumeSnapshotContentDelete,
// The backup VSC is statically provisioned against the same
// snapshot handle as the source VSC, so both objects refer to one
// physical snapshot. Inherit the source's deletion policy instead
// of forcing Delete, otherwise a user who configured Retain on the
// VolumeSnapshotClass still loses the snapshot when the backup VSC
// is cleaned up.
//
// For Case 2 storages per the design (design/block-data-mover/block-data-mover.md,
// e.g. Ceph RBD), inheriting Retain is not just an option but a requirement for
// incrementals to work at all: rbd snap diff needs the base and target snapshots
// in the same clone chain, so Delete destroys the base as soon as this backup
// completes. The next incremental's delta query then fails and degrades to an
// allocated-blocks backup (see the CBT tier ladder) or, without that fix, a full
// whole-device transfer.
DeletionPolicy: snapshotVSC.Spec.DeletionPolicy,
Driver: snapshotVSC.Spec.Driver,
VolumeSnapshotClassName: snapshotVSC.Spec.VolumeSnapshotClassName,
},
+59
View File
@@ -2537,3 +2537,62 @@ func TestCleanUp_SecretsAndConfigMaps(t *testing.T) {
_, err = fakeKubeClient.CoreV1().Secrets("velero").Get(t.Context(), "other-secret", metav1.GetOptions{})
assert.NoError(t, err, "unrelated secret should not be deleted")
}
func TestCreateBackupVSCDeletionPolicy(t *testing.T) {
tests := []struct {
name string
sourcePolicy snapshotv1api.DeletionPolicy
expectedPolicy snapshotv1api.DeletionPolicy
}{
{
name: "Delete policy is inherited",
sourcePolicy: snapshotv1api.VolumeSnapshotContentDelete,
expectedPolicy: snapshotv1api.VolumeSnapshotContentDelete,
},
{
// The backup VSC points at the same snapshot handle as the source
// VSC, so forcing Delete here would destroy a snapshot the user
// asked to keep.
name: "Retain policy is inherited",
sourcePolicy: snapshotv1api.VolumeSnapshotContentRetain,
expectedPolicy: snapshotv1api.VolumeSnapshotContentRetain,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
handle := "fake-snapshot-handle"
className := "fake-snapshot-class"
sourceVSC := &snapshotv1api.VolumeSnapshotContent{
ObjectMeta: metav1.ObjectMeta{Name: "source-vsc"},
Spec: snapshotv1api.VolumeSnapshotContentSpec{
DeletionPolicy: test.sourcePolicy,
Driver: "fake-driver",
VolumeSnapshotClassName: &className,
},
Status: &snapshotv1api.VolumeSnapshotContentStatus{
SnapshotHandle: &handle,
},
}
exposer := csiSnapshotExposer{
csiSnapshotClient: snapshotFake.NewSimpleClientset().SnapshotV1(),
log: velerotest.NewLogger(),
}
ownerObject := corev1api.ObjectReference{
Name: "fake-du",
Namespace: "velero",
}
vs := &snapshotv1api.VolumeSnapshot{
ObjectMeta: metav1.ObjectMeta{Name: "fake-du", Namespace: "velero"},
}
backupVSC, err := exposer.createBackupVSC(t.Context(), ownerObject, sourceVSC, vs)
require.NoError(t, err)
assert.Equal(t, test.expectedPolicy, backupVSC.Spec.DeletionPolicy)
assert.Equal(t, handle, *backupVSC.Spec.Source.SnapshotHandle)
})
}
}
@@ -93,6 +93,7 @@ func ShouldPerformSnapshotWithVolumeHelper(
crClient,
boolptr.IsSetToTrue(backup.Spec.DefaultVolumesToFsBackup),
true,
nil,
)
return volumeHelperImpl.ShouldPerformSnapshot(unstructured, groupResource)
@@ -111,6 +112,7 @@ func NewVolumeHelperWithNamespaces(
defaultVolumesToFSBackup bool,
backupExcludePVC bool,
namespaces []string,
pvcMustInclusionTracker vhutil.PVCMustInclusionTracker,
) (vhutil.VolumeHelper, error) {
return volumehelper.NewVolumeHelperImplWithNamespaces(
volumePolicy,
@@ -120,6 +122,7 @@ func NewVolumeHelperWithNamespaces(
defaultVolumesToFSBackup,
backupExcludePVC,
namespaces,
pvcMustInclusionTracker,
)
}
@@ -131,11 +134,13 @@ func NewVolumeHelperWithCache(
client crclient.Client,
logger logrus.FieldLogger,
pvcPodCache *podvolumeutil.PVCPodCache,
pvcMustInclusionTracker vhutil.PVCMustInclusionTracker,
) (vhutil.VolumeHelper, error) {
return volumehelper.NewVolumeHelperImplWithCache(
backup,
client,
logger,
pvcPodCache,
pvcMustInclusionTracker,
)
}
@@ -300,6 +300,7 @@ func TestShouldPerformSnapshotWithNonNilVolumeHelper(t *testing.T) {
false, // defaultVolumesToFSBackup
true, // backupExcludePVC
[]string{"default"},
nil,
)
require.NoError(t, err)
require.NotNil(t, vh)
@@ -712,6 +712,7 @@ func (kr *kopiaRepository) GetSnapshot(ctx context.Context, id udmrepo.ID) (udmr
}
return udmrepo.Snapshot{
ID: udmrepo.ID(snap.ID),
Source: snap.Source.Path,
Description: snap.Description,
StartTime: snap.StartTime.ToTime(),
@@ -751,6 +752,7 @@ func (kr *kopiaRepository) ListSnapshot(ctx context.Context, source string) ([]u
snapshots := []udmrepo.Snapshot{}
for _, snap := range mani {
snapshots = append(snapshots, udmrepo.Snapshot{
ID: udmrepo.ID(snap.ID),
Source: snap.Source.Path,
Description: snap.Description,
StartTime: snap.StartTime.ToTime(),
@@ -1609,6 +1609,7 @@ func TestGetSnapshot(t *testing.T) {
snapshotID: udmrepo.ID("fake-id"),
setRepoMock: true,
expectedSnap: udmrepo.Snapshot{
ID: "fake-id",
Source: "fake-source",
Description: "fake-desc",
StartTime: mockMani.StartTime.ToTime(),
@@ -1805,6 +1806,7 @@ func TestListSnapshot(t *testing.T) {
setRepoMock: true,
expectedSnaps: []udmrepo.Snapshot{
{
ID: "fake-id",
Source: "fake-source",
Description: "fake-desc",
StartTime: mockMani.StartTime.ToTime(),
+1
View File
@@ -98,6 +98,7 @@ type Metadata struct {
}
type Snapshot struct {
ID ID
Source string
Description string
StartTime time.Time
+39 -37
View File
@@ -169,50 +169,52 @@ func getParentBackupInfo(ctx context.Context, rep udmrepo.BackupRepo, forceFull
}
var previous *udmrepo.Snapshot
if parentSnapshot != "" {
log.Infof("Loading provided parent snapshot %s", parentSnapshot)
snap, err := rep.GetSnapshot(ctx, udmrepo.ID(parentSnapshot))
if err != nil {
return parentBackupInfo{}, errors.Wrapf(err, "error loading previous snapshot")
if !forceFull {
if parentSnapshot != "" {
snap, err := rep.GetSnapshot(ctx, udmrepo.ID(parentSnapshot))
if err != nil {
log.WithError(err).Warn("Failed to load previous snapshot, fallback to full backup")
} else {
previous = &snap
log.Infof("Using provided parent snapshot %s", parentSnapshot)
}
} else {
log.Infof("Searching for parent snapshot")
snap, err := findPreviousSnapshot(ctx, rep, realSource, snapshotTags, nil, log)
if err != nil {
log.WithError(err).Warn("Failed to search previous snapshot, fallback to full backup")
} else {
previous = &snap
log.Infof("Using previous snapshot %s", snap.ID)
}
}
previous = &snap
} else {
log.Infof("Searching for parent snapshot")
log.Info("Forcing full snapshot")
}
snap, err := findPreviousSnapshot(ctx, rep, realSource, snapshotTags, nil, log)
if err != nil {
return parentBackupInfo{}, errors.Wrapf(err, "error searching previous snapshot")
parentInfo := parentBackupInfo{}
if previous != nil {
if previous.Tags == nil {
log.Warnf("No tag from parent snapshot %s, fallback to full backup", previous.ID)
} else if previous.Tags[uploader.CBTChangeIDTag] == "" {
log.Warnf("No ChangeID tag from parent snapshot %s, fallback to full backup", previous.ID)
} else if previous.Tags[uploader.CBTVolumeIDTag] == "" {
log.Warnf("No VolumeID tag from parent snapshot %s, fallback to full backup", previous.ID)
} else if previous.Tags[uploader.CBTVolumeIDTag] != volumeID {
log.Warnf("VolumeID %s from parent snapshot %s is not expected as %s, fallback to full backup", previous.Tags[uploader.CBTVolumeIDTag], previous.ID, volumeID)
} else if obj, err := loadObjectFromSnapshot(ctx, rep, previous); err != nil {
log.WithError(err).Warnf("Failed to load object from parent snapshot %s, fallback to full backup", previous.ID)
} else {
parentInfo.parentObject = obj
parentInfo.changeID = previous.Tags[uploader.CBTChangeIDTag]
parentInfo.volumeID = previous.Tags[uploader.CBTVolumeIDTag]
log.Infof("Using parent snapshot %s, start time %v, end time %v, description %s", previous.ID, previous.StartTime, previous.EndTime, previous.Description)
}
previous = &snap
}
if previous.Tags == nil {
return parentBackupInfo{}, errors.Errorf("no tag from parent snapshot %s", previous.ID)
}
if previous.Tags[uploader.CBTChangeIDTag] == "" {
return parentBackupInfo{}, errors.Errorf("no ChangeID tag from parent snapshot %s", previous.ID)
}
if previous.Tags[uploader.CBTVolumeIDTag] == "" {
return parentBackupInfo{}, errors.Errorf("no VolumeID tag from parent snapshot %s", previous.ID)
}
if previous.Tags[uploader.CBTVolumeIDTag] != volumeID {
return parentBackupInfo{}, errors.Errorf("VolumeID %s from parent snapshot %s is not expected as %s", previous.Tags[uploader.CBTVolumeIDTag], previous.ID, volumeID)
}
obj, err := loadObjectFromSnapshot(ctx, rep, previous)
if err != nil {
return parentBackupInfo{}, errors.Errorf("error loading object from parent snapshot %s", previous.ID)
}
log.Infof("Using parent snapshot %s, start time %v, end time %v, description %s", previous.ID, previous.StartTime, previous.EndTime, previous.Description)
return parentBackupInfo{
parentObject: obj,
changeID: previous.Tags[uploader.CBTChangeIDTag],
+3 -1
View File
@@ -351,6 +351,7 @@ func TestSnapshotSource(t *testing.T) {
func TestGetParentBackupInfoLogsDiscoveredParentID(t *testing.T) {
const volumeID = "vol-123"
const realSource = "/test/source"
const parentSnapID = "snap-parent-42"
const rootObj = "root-obj-42"
snapshotTags := map[string]string{
@@ -364,6 +365,7 @@ func TestGetParentBackupInfoLogsDiscoveredParentID(t *testing.T) {
repo := udmrepomocks.NewBackupRepo(t)
repo.On("ListSnapshot", mock.Anything, realSource).
Return([]udmrepo.Snapshot{{
ID: parentSnapID,
RootObject: udmrepo.ObjectMetadata{ID: rootObj},
Tags: map[string]string{
uploader.CBTChangeIDTag: "cid-abc",
@@ -389,7 +391,7 @@ func TestGetParentBackupInfoLogsDiscoveredParentID(t *testing.T) {
for _, entry := range hook.AllEntries() {
if strings.HasPrefix(entry.Message, "Using parent snapshot ") {
found = true
assert.Contains(t, entry.Message, rootObj,
assert.Contains(t, entry.Message, parentSnapID,
"parent-selection message must name the discovered snapshot, got %q", entry.Message)
}
}
+5 -5
View File
@@ -167,17 +167,17 @@ func (blkup *blockUploader) Restore(snapshot udmrepo.Snapshot, dest destInfo, bi
meta, err := blkup.repoWriter.ReadMetadata(blkup.ctx, snapshot.RootObject.ID)
if err != nil {
return 0, 0, errors.Wrapf(err, "error reading snapshot metadata for %s", snapshot.Description)
return 0, 0, errors.Wrapf(err, "error reading snapshot metadata for %s", snapshot.ID)
}
if len(meta.SubObjects) != 1 {
return 0, 0, errors.Errorf("unexpected number of bdev object (%d) for snapshot %s", len(meta.SubObjects), snapshot.Description)
return 0, 0, errors.Errorf("unexpected number of bdev object (%d) for snapshot %s", len(meta.SubObjects), snapshot.ID)
}
sourceSize, err := getSourceSize(snapshot)
if err != nil {
sourceSize = meta.SubObjects[0].Size
blkup.log.Warnf("Failed to get source size from snapshot %s, use backup size %v", snapshot.Description, sourceSize)
blkup.log.Warnf("Failed to get source size from snapshot %s, use backup size %v", snapshot.ID, sourceSize)
}
if sourceSize > meta.SubObjects[0].Size {
@@ -667,11 +667,11 @@ func loadObjectFromSnapshot(ctx context.Context, rep udmrepo.BackupRepo, snapsho
meta, err := rep.ReadMetadata(ctx, snapshot.RootObject.ID)
if err != nil {
return "", errors.Wrapf(err, "error reading snapshot metadata for %s", snapshot.Description)
return "", errors.Wrap(err, "error reading snapshot metadata")
}
if len(meta.SubObjects) != 1 {
return "", errors.Errorf("unexpected number of bdev object (%d) for snapshot %s", len(meta.SubObjects), snapshot.Description)
return "", errors.Errorf("unexpected number of bdev object (%d)", len(meta.SubObjects))
}
return meta.SubObjects[0].ID, nil
+5
View File
@@ -64,6 +64,11 @@ func GetCBTInfo(ctx context.Context, kubeClient kubernetes.Interface, log logrus
if vsc.Status != nil && vsc.Status.SnapshotHandle != nil {
cbtInfo.ChangeID = *vsc.Status.SnapshotHandle
} else if vsc.Spec.Source.SnapshotHandle != nil {
// The backup VSC is statically provisioned from the source VSC's
// snapshot handle; its status is populated asynchronously and may
// not be set yet, but the handle is already in the spec.
cbtInfo.ChangeID = *vsc.Spec.Source.SnapshotHandle
}
if pv.Spec.CSI != nil && pv.Spec.CSI.VolumeHandle != "" {
@@ -22,6 +22,13 @@ import (
"k8s.io/apimachinery/pkg/runtime/schema"
)
// PVCMustInclusionTracker provides read-only checks for whether a PVC is included
// in the backup as BIA's additionalItems through annotation
// backup.velero.io/must-include-additional-items.
type PVCMustInclusionTracker interface {
IsPVCIncluded(namespace, pvcName string) bool
}
type VolumeHelper interface {
ShouldPerformSnapshot(obj runtime.Unstructured, groupResource schema.GroupResource) (bool, error)
ShouldPerformFSBackup(volume corev1api.Volume, pod corev1api.Pod) (bool, error)
+17
View File
@@ -65,6 +65,23 @@ order in which item action plugins are invoked. However, if a single binary impl
they may be invoked in the order in which they are registered but it is best to not depend on this
implementation. This is not guaranteed officially and the implementation can change at any time.
### Must-include additional items (Backup Item Actions)
Backup Item Actions may return `AdditionalItems` that Velero backs up as dependencies of the current item.
By default those additional items must still pass the backup's global resource and namespace include/exclude filters.
To force-backup hard dependencies despite those filters, set the following annotation on the `UpdatedItem` returned from `Execute()`:
```
backup.velero.io/must-include-additional-items: "true"
```
Behavior:
- Only the string value `"true"` enables the bypass.
- The annotation applies blanket to all `AdditionalItems` from that BIA invocation (not per-item).
- Velero strips the annotation before saving the item to the backup tarball.
- **Important Note for File System Backup (FSB):** If your plugin returns both a Pod and its associated PersistentVolumeClaims (PVCs) as `AdditionalItems`, and you expect Velero to create PodVolumeBackups (PVBs) for those PVCs using File System Backup, using this annotation ensures Velero correctly evaluates the PVCs for FSB. Velero explicitly tracks PVCs returned as additional items with this annotation, guaranteeing that PVBs are created even if the PVCs are excluded by global or fine-grained backup filters, regardless of the order they are returned in the `AdditionalItems` slice.
### Must-include additional items (Restore Item Actions)
Restore Item Actions may return `AdditionalItems` that Velero restores as dependencies of the current item.