Fix ResourceDeletionStatusTracker key Kind mismatch in terminating-namespace wait (#9987)

EnsureNamespaceExistsAndIsReady wrote the tracker key with namespace.Kind
(getNamespace() sets Kind=Namespace) but read it with clusterNS.Kind
(client.Get strips TypeMeta -> Kind=empty). The keys never matched, so the
skip-path never fired and every item in a terminating namespace paid the full
--terminating-resource-timeout wait (per-resource instead of per-namespace).

Use the passed-in namespace object for Contains so Add/Contains keys match.
Add a regression test that reproduces the production Kind divergence.

Signed-off-by: Shashank1306s <shashasingh@microsoft.com>
Co-authored-by: Shashank1306s <shashasingh@microsoft.com>
Co-authored-by: Priyansh Choudhary <im1706@gmail.com>
This commit is contained in:
Shashank Singh
2026-08-03 23:28:01 -07:00
committed by GitHub
co-authored by Shashank1306s Priyansh Choudhary
parent ff0876a56d
commit de32d93b8e
3 changed files with 38 additions and 1 deletions
+4 -1
View File
@@ -103,7 +103,10 @@ func EnsureNamespaceExistsAndIsReady(namespace *corev1api.Namespace, client core
return true, err
}
if clusterNS != nil && (clusterNS.GetDeletionTimestamp() != nil || clusterNS.Status.Phase == corev1api.NamespaceTerminating) {
if resourceDeletionStatusTracker.Contains(clusterNS.Kind, clusterNS.Name, clusterNS.Name) {
// Use namespace.Kind (not clusterNS.Kind) so this key matches the one Add()
// writes below: client.Get() strips TypeMeta (Kind=""), but getNamespace()
// sets Kind="Namespace". Mismatched keys made Contains never match.
if resourceDeletionStatusTracker.Contains(namespace.Kind, namespace.Name, namespace.Name) {
namespaceAlreadyInDeletionTracker = true
return true, errors.Errorf("namespace %s is already present in the polling set, skipping execution", namespace.Name)
}
+33
View File
@@ -154,6 +154,39 @@ func TestEnsureNamespaceExistsAndIsReady(t *testing.T) {
}
}
// TestEnsureNamespaceExistsAndIsReadyTerminatingTrackerKindMismatch verifies the
// tracker skip-path fires when Add and Contains see different Kind values, as they
// do in production: getNamespace() sets Kind="Namespace" but client.Get() strips it.
func TestEnsureNamespaceExistsAndIsReadyTerminatingTrackerKindMismatch(t *testing.T) {
// Passed-in namespace mirrors getNamespace(): Kind is set.
namespace := &corev1api.Namespace{
TypeMeta: metav1.TypeMeta{Kind: "Namespace", APIVersion: "v1"},
ObjectMeta: metav1.ObjectMeta{Name: "test"},
}
// clusterNS mirrors client.Get(): Kind stripped, phase Terminating.
clusterNS := &corev1api.Namespace{
ObjectMeta: metav1.ObjectMeta{Name: "test"},
Status: corev1api.NamespaceStatus{Phase: corev1api.NamespaceTerminating},
}
nsClient := &velerotest.FakeNamespaceClient{}
defer nsClient.AssertExpectations(t)
nsClient.On("Get", "test", metav1.GetOptions{}).Return(clusterNS, nil)
// Seed the tracker as production Add() does.
tracker := NewResourceDeletionStatusTracker()
tracker.Add(namespace.Kind, namespace.Name, namespace.Name)
result, nsCreated, err := EnsureNamespaceExistsAndIsReady(namespace, nsClient, time.Millisecond, tracker)
assert.False(t, result)
assert.False(t, nsCreated)
// Skip-path must fire, not the full terminating-resource-timeout wait.
require.ErrorContains(t, err, "skipping polling for terminating namespace")
assert.NotContains(t, err.Error(), "timed out waiting for terminating namespace")
}
// TestGetVolumeDirectorySuccess tests that the GetVolumeDirectory function
// returns a volume's name or a volume's name plus '/mount' when a PVC is present.
func TestGetVolumeDirectorySuccess(t *testing.T) {