Merge pull request #8464 from shubham-pampattiwar/obj-status-restore-impl

Allowing Object-Level Resource Status Restore
pull/7597/merge
Wenkai Yin(尹文开) 2025-02-13 13:37:58 +08:00 committed by GitHub
commit e446d92d4c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 156 additions and 3 deletions

View File

@ -0,0 +1 @@
Allowing Object-Level Resource Status Restore

View File

@ -79,6 +79,8 @@ import (
"github.com/vmware-tanzu/velero/pkg/util/results"
)
const ObjectStatusRestoreAnnotationKey = "velero.io/restore-status"
var resourceMustHave = []string{
"datauploads.velero.io",
}
@ -1657,15 +1659,16 @@ func (ctx *restoreContext) restoreItem(obj *unstructured.Unstructured, groupReso
}
// determine whether to restore status according to original GR
shouldRestoreStatus := ctx.resourceStatusIncludesExcludes != nil && ctx.resourceStatusIncludesExcludes.ShouldInclude(groupResource.String())
shouldRestoreStatus := determineRestoreStatus(obj, ctx.resourceStatusIncludesExcludes, groupResource.String(), ctx.log)
if shouldRestoreStatus && statusFieldErr != nil {
err := fmt.Errorf("could not get status to be restored %s: %v", kube.NamespaceAndName(obj), statusFieldErr)
ctx.log.Errorf(err.Error())
errs.Add(namespace, err)
return warnings, errs, itemExists
}
ctx.log.Debugf("status field for %s: exists: %v, should restore: %v", newGR, statusFieldExists, shouldRestoreStatus)
// if it should restore status, run a UpdateStatus
// Proceed with status restoration if decided
if statusFieldExists && shouldRestoreStatus {
if err := unstructured.SetNestedField(obj.Object, objStatus, "status"); err != nil {
ctx.log.Errorf("could not set status field %s: %v", kube.NamespaceAndName(obj), err)
@ -2543,3 +2546,51 @@ func (ctx *restoreContext) handleSkippedPVHasRetainPolicy(
obj = resetVolumeBindingInfo(obj)
return obj, nil
}
func determineRestoreStatus(
obj *unstructured.Unstructured,
resourceIncludesExcludes *collections.IncludesExcludes,
groupResource string,
log logrus.FieldLogger,
) bool {
var shouldRestoreStatus bool
// Determine restore spec behavior
if resourceIncludesExcludes != nil {
shouldRestoreStatus = resourceIncludesExcludes.ShouldInclude(groupResource)
}
// Retrieve annotations
annotations := obj.GetAnnotations()
if annotations == nil {
log.Warnf("No annotations found for %s, using restore spec setting: %v",
kube.NamespaceAndName(obj), shouldRestoreStatus)
return shouldRestoreStatus
}
// Check for object-level annotation
objectAnnotation, annotationExists := annotations[ObjectStatusRestoreAnnotationKey]
if !annotationExists {
log.Debugf("No restore status-specific annotation found for %s, using restore spec setting: %v",
kube.NamespaceAndName(obj), shouldRestoreStatus)
return shouldRestoreStatus
}
normalizedValue := strings.ToLower(strings.TrimSpace(objectAnnotation))
switch normalizedValue {
case "true":
shouldRestoreStatus = true
case "false":
shouldRestoreStatus = false
default:
log.Warnf("Invalid annotation value '%s' on %s, using restore spec setting: %v",
objectAnnotation, kube.NamespaceAndName(obj), shouldRestoreStatus)
}
log.Infof("Final status restore decision for %s: %v (annotation: %v, restore spec: %v)",
kube.NamespaceAndName(obj), shouldRestoreStatus, annotationExists, shouldRestoreStatus)
return shouldRestoreStatus
}

View File

@ -25,6 +25,9 @@ import (
"testing"
"time"
"github.com/vmware-tanzu/velero/pkg/util/boolptr"
"github.com/vmware-tanzu/velero/pkg/util/collections"
snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v7/apis/volumesnapshot/v1"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
@ -4139,3 +4142,101 @@ func TestHasSnapshotDataUpload(t *testing.T) {
})
}
}
func TestDetermineRestoreStatus(t *testing.T) {
tests := []struct {
name string
annotations map[string]string
restoreSpecIncludes *bool
expectedDecision bool
}{
{
name: "No annotation, fallback to restore spec",
annotations: nil,
restoreSpecIncludes: boolptr.True(),
expectedDecision: true,
},
{
name: "No annotation, restore spec excludes",
annotations: nil,
restoreSpecIncludes: boolptr.False(),
expectedDecision: false,
},
{
name: "Annotation explicitly set to true, restore spec is false",
annotations: map[string]string{ObjectStatusRestoreAnnotationKey: "true"},
restoreSpecIncludes: boolptr.False(),
expectedDecision: true,
},
{
name: "Annotation explicitly set to false, restore spec is true",
annotations: map[string]string{ObjectStatusRestoreAnnotationKey: "false"},
restoreSpecIncludes: boolptr.True(),
expectedDecision: false,
},
{
name: "Invalid annotation value, fallback to restore spec",
annotations: map[string]string{ObjectStatusRestoreAnnotationKey: "foo"},
restoreSpecIncludes: boolptr.True(),
expectedDecision: true,
},
{
name: "Empty annotation value, fallback to restore spec",
annotations: map[string]string{ObjectStatusRestoreAnnotationKey: ""},
restoreSpecIncludes: boolptr.False(),
expectedDecision: false,
},
{
name: "Mixed-case annotation value 'True' should be treated as true",
annotations: map[string]string{ObjectStatusRestoreAnnotationKey: "True"},
restoreSpecIncludes: boolptr.True(),
expectedDecision: true,
},
{
name: "Mixed-case annotation value 'FALSE' should be treated as false",
annotations: map[string]string{ObjectStatusRestoreAnnotationKey: "FALSE"},
restoreSpecIncludes: boolptr.True(),
expectedDecision: false,
},
{
name: "Nil IncludesExcludes, but annotation is 'true'",
annotations: map[string]string{ObjectStatusRestoreAnnotationKey: "true"},
restoreSpecIncludes: nil,
expectedDecision: true,
},
{
name: "Nil IncludesExcludes, but annotation is 'false'",
annotations: map[string]string{ObjectStatusRestoreAnnotationKey: "false"},
restoreSpecIncludes: nil,
expectedDecision: false,
},
{
name: "Nil IncludesExcludes, no annotation (default to false)",
annotations: nil,
restoreSpecIncludes: nil,
expectedDecision: false,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
obj := &unstructured.Unstructured{}
obj.SetAnnotations(test.annotations)
var includesExcludes *collections.IncludesExcludes
if test.restoreSpecIncludes != nil {
includesExcludes = collections.NewIncludesExcludes()
if *test.restoreSpecIncludes {
includesExcludes.Includes("*")
} else {
includesExcludes.Excludes("*")
}
}
log := logrus.New()
result := determineRestoreStatus(obj, includesExcludes, "testGroupResource", log)
assert.Equal(t, test.expectedDecision, result)
})
}
}