Compare commits

..

3 Commits

Author SHA1 Message Date
copilot-swe-agent[bot] 323900adcc Rename changelog to 9898 and verify callers 2026-06-09 22:12:25 +00:00
copilot-swe-agent[bot] 317ffd069f Make ToSystemAffinity deterministic by sorting MatchLabels keys 2026-06-09 22:10:30 +00:00
copilot-swe-agent[bot] d6d9e4ee16 Initial plan 2026-06-09 22:06:02 +00:00
6 changed files with 57 additions and 92 deletions
@@ -1 +0,0 @@
Skip VGS cleanup when backup did not use VolumeGroupSnapshots
+1
View File
@@ -0,0 +1 @@
Make ToSystemAffinity deterministic by sorting MatchLabels keys to avoid spurious affinity spec diffs and restarts
+2 -13
View File
@@ -301,10 +301,8 @@ func (ctx *finalizerContext) execute() (results.Result, results.Result) {
pdpErrs := ctx.patchDynamicPVWithVolumeInfo()
errs.Merge(&pdpErrs)
if ctx.hasVolumeGroupSnapshotHandles() {
vgscWarnings := ctx.cleanupStubVGSC()
warnings.Merge(&vgscWarnings)
}
vgscWarnings := ctx.cleanupStubVGSC()
warnings.Merge(&vgscWarnings)
rehErrs := ctx.WaitRestoreExecHook()
errs.Merge(&rehErrs)
@@ -451,15 +449,6 @@ func (ctx *finalizerContext) patchDynamicPVWithVolumeInfo() (errs results.Result
return errs
}
func (ctx *finalizerContext) hasVolumeGroupSnapshotHandles() bool {
for _, vi := range ctx.volumeInfo {
if vi.CSISnapshotInfo != nil && vi.CSISnapshotInfo.VolumeGroupSnapshotHandle != "" {
return true
}
}
return false
}
// cleanupStubVGSC deletes stub VolumeGroupSnapshotContent objects that were
// created during restore to satisfy CSI controller validation. These stubs are
// labeled with velero.io/restore-name for identification.
@@ -743,83 +743,6 @@ func TestRestoreOperationList(t *testing.T) {
}
}
func TestHasVolumeGroupSnapshotHandles(t *testing.T) {
tests := []struct {
name string
volumeInfo []*volume.BackupVolumeInfo
expected bool
}{
{
name: "nil volumeInfo",
volumeInfo: nil,
expected: false,
},
{
name: "empty volumeInfo",
volumeInfo: []*volume.BackupVolumeInfo{},
expected: false,
},
{
name: "no CSISnapshotInfo",
volumeInfo: []*volume.BackupVolumeInfo{
{PVCName: "pvc-1", BackupMethod: volume.NativeSnapshot},
},
expected: false,
},
{
name: "CSISnapshotInfo with empty VolumeGroupSnapshotHandle",
volumeInfo: []*volume.BackupVolumeInfo{
{
PVCName: "pvc-1",
BackupMethod: volume.CSISnapshot,
CSISnapshotInfo: &volume.CSISnapshotInfo{
SnapshotHandle: "snap-1",
},
},
},
expected: false,
},
{
name: "one volume with VolumeGroupSnapshotHandle",
volumeInfo: []*volume.BackupVolumeInfo{
{
PVCName: "pvc-1",
BackupMethod: volume.CSISnapshot,
CSISnapshotInfo: &volume.CSISnapshotInfo{
SnapshotHandle: "snap-1",
VolumeGroupSnapshotHandle: "vgs-handle-1",
},
},
},
expected: true,
},
{
name: "mixed volumes only one with VolumeGroupSnapshotHandle",
volumeInfo: []*volume.BackupVolumeInfo{
{PVCName: "pvc-1", BackupMethod: volume.NativeSnapshot},
{
PVCName: "pvc-2",
BackupMethod: volume.CSISnapshot,
CSISnapshotInfo: &volume.CSISnapshotInfo{
SnapshotHandle: "snap-2",
VolumeGroupSnapshotHandle: "vgs-handle-2",
},
},
},
expected: true,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
ctx := &finalizerContext{
volumeInfo: tc.volumeInfo,
}
assert.Equal(t, tc.expected, ctx.hasVolumeGroupSnapshotHandles())
})
}
}
func TestCleanupStubVGSC(t *testing.T) {
snapshotHandle1 := "snap-handle-1"
snapshotHandle2 := "snap-handle-2"
+15 -1
View File
@@ -20,6 +20,7 @@ import (
"fmt"
"io"
"os"
"sort"
"strings"
"time"
@@ -236,7 +237,20 @@ func CollectPodLogs(ctx context.Context, podGetter corev1client.CoreV1Interface,
func ToSystemAffinity(loadAffinity *LoadAffinity, volumeTopology *corev1api.NodeSelector) *corev1api.Affinity {
requirements := []corev1api.NodeSelectorRequirement{}
if loadAffinity != nil {
for k, v := range loadAffinity.NodeSelector.MatchLabels {
// MatchLabels is a map, so its iteration order is not deterministic.
// Sort the keys so the generated requirements (and therefore the
// resulting affinity) have a stable order. This output may be embedded
// into objects that are reconciled continuously (e.g. DaemonSet pod
// templates), where an order-only difference would be treated as a spec
// change and trigger unnecessary rollouts/restarts.
keys := make([]string, 0, len(loadAffinity.NodeSelector.MatchLabels))
for k := range loadAffinity.NodeSelector.MatchLabels {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
v := loadAffinity.NodeSelector.MatchLabels[k]
requirements = append(requirements, corev1api.NodeSelectorRequirement{
Key: k,
Values: []string{v},
+39
View File
@@ -834,6 +834,45 @@ func TestToSystemAffinity(t *testing.T) {
},
},
},
{
name: "with multiple match labels are sorted by key",
loadAffinity: &LoadAffinity{
NodeSelector: metav1.LabelSelector{
MatchLabels: map[string]string{
"key-c": "value-c",
"key-a": "value-a",
"key-b": "value-b",
},
},
},
expected: &corev1api.Affinity{
NodeAffinity: &corev1api.NodeAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: &corev1api.NodeSelector{
NodeSelectorTerms: []corev1api.NodeSelectorTerm{
{
MatchExpressions: []corev1api.NodeSelectorRequirement{
{
Key: "key-a",
Values: []string{"value-a"},
Operator: corev1api.NodeSelectorOpIn,
},
{
Key: "key-b",
Values: []string{"value-b"},
Operator: corev1api.NodeSelectorOpIn,
},
{
Key: "key-c",
Values: []string{"value-c"},
Operator: corev1api.NodeSelectorOpIn,
},
},
},
},
},
},
},
},
{
name: "with olume topology",
volumeTopology: &corev1api.NodeSelector{