Merge pull request #8464 from shubham-pampattiwar/obj-status-restore-impl
Allowing Object-Level Resource Status Restorepull/7597/merge
commit
e446d92d4c
|
@ -0,0 +1 @@
|
|||
Allowing Object-Level Resource Status Restore
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue