Skip to content

Commit 1a31c56

Browse files
authored
Merge pull request #3498 from vieux/fix-fake-client-generatename-retry
🐛 fakeclient: retry GenerateName on AlreadyExists collisions
2 parents 6210f84 + 80bc294 commit 1a31c56

2 files changed

Lines changed: 59 additions & 6 deletions

File tree

pkg/client/fake/client.go

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -633,12 +633,12 @@ func (c *fakeClient) Create(ctx context.Context, obj client.Object, opts ...clie
633633
return err
634634
}
635635

636+
var generateNameBase string
636637
if accessor.GetName() == "" && accessor.GetGenerateName() != "" {
637-
base := accessor.GetGenerateName()
638-
if len(base) > maxGeneratedNameLength {
639-
base = base[:maxGeneratedNameLength]
638+
generateNameBase = accessor.GetGenerateName()
639+
if len(generateNameBase) > maxGeneratedNameLength {
640+
generateNameBase = generateNameBase[:maxGeneratedNameLength]
640641
}
641-
accessor.SetName(fmt.Sprintf("%s%s", base, utilrand.String(randomLength)))
642642
}
643643
// Ignore attempts to set deletion timestamp
644644
if !accessor.GetDeletionTimestamp().IsZero() {
@@ -653,10 +653,21 @@ func (c *fakeClient) Create(ctx context.Context, obj client.Object, opts ...clie
653653
c.trackerWriteLock.Lock()
654654
defer c.trackerWriteLock.Unlock()
655655

656-
if err := c.tracker.Create(gvr, obj, accessor.GetNamespace(), *createOptions.AsCreateOptions()); err != nil {
656+
const maxRetries = 7
657+
var createErr error
658+
for range maxRetries {
659+
if generateNameBase != "" {
660+
accessor.SetName(fmt.Sprintf("%s%s", generateNameBase, utilrand.String(randomLength)))
661+
}
662+
createErr = c.tracker.Create(gvr, obj, accessor.GetNamespace(), *createOptions.AsCreateOptions())
663+
if createErr == nil || generateNameBase == "" || !apierrors.IsAlreadyExists(createErr) {
664+
break
665+
}
666+
}
667+
if createErr != nil {
657668
// The managed fields tracker sets gvk even on errors
658669
_ = ensureTypeMeta(obj, gvk)
659-
return err
670+
return createErr
660671
}
661672

662673
if !c.returnManagedFields {

pkg/client/fake/client_test.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ import (
4444
"k8s.io/apimachinery/pkg/runtime/schema"
4545
"k8s.io/apimachinery/pkg/runtime/serializer"
4646
"k8s.io/apimachinery/pkg/types"
47+
"k8s.io/apimachinery/pkg/util/sets"
4748
"k8s.io/apimachinery/pkg/watch"
4849
clientgoapplyconfigurations "k8s.io/client-go/applyconfigurations"
4950
corev1applyconfigurations "k8s.io/client-go/applyconfigurations/core/v1"
@@ -485,6 +486,47 @@ var _ = Describe("Fake client", func() {
485486
Expect(list.Items[0].Name).NotTo(BeEmpty())
486487
})
487488

489+
It("should retry GenerateName on name collision and succeed", func(ctx SpecContext) {
490+
// Create many objects with the same short GenerateName prefix so that
491+
// birthday-paradox collisions are highly likely. The retry loop
492+
// (max 7 attempts per object) must absorb every collision.
493+
const n = 500
494+
names := sets.New[string]()
495+
for i := range n {
496+
cm := &corev1.ConfigMap{
497+
ObjectMeta: metav1.ObjectMeta{
498+
GenerateName: "this-generate-name-prefix-is-longer-than-max-generated-name-length-and-will-be-truncated-",
499+
Namespace: "ns2",
500+
},
501+
}
502+
Expect(cl.Create(ctx, cm)).To(Succeed(), "create #%d failed", i)
503+
Expect(cm.Name).NotTo(BeEmpty())
504+
Expect(cm.Name).To(HaveLen(maxNameLength))
505+
Expect(names.Has(cm.Name)).To(BeFalse(), "duplicate name %q", cm.Name)
506+
names.Insert(cm.Name)
507+
}
508+
})
509+
510+
It("should not retry AlreadyExists when Name is set explicitly", func(ctx SpecContext) {
511+
cm := &corev1.ConfigMap{
512+
ObjectMeta: metav1.ObjectMeta{
513+
Name: "explicit-name",
514+
Namespace: "ns2",
515+
},
516+
}
517+
Expect(cl.Create(ctx, cm)).To(Succeed())
518+
519+
// Second create with the same explicit name must fail immediately.
520+
cm2 := &corev1.ConfigMap{
521+
ObjectMeta: metav1.ObjectMeta{
522+
Name: "explicit-name",
523+
Namespace: "ns2",
524+
},
525+
}
526+
err := cl.Create(ctx, cm2)
527+
Expect(apierrors.IsAlreadyExists(err)).To(BeTrue())
528+
})
529+
488530
It("should be able to Update", func(ctx SpecContext) {
489531
By("Updating a new configmap")
490532
newcm := &corev1.ConfigMap{

0 commit comments

Comments
 (0)