Skip to content

Commit 5ab410d

Browse files
committed
Demonstrate that admission cannot ensure field uniqueness.
The only field you can practically guarantee uniqueness on is .metadata.name because it is used to construct the etcd key, and etcd does offer consistency. API servers can't because they can handle more than one concurrent request for the same resource and both can be past admission but before storage. There's also latency between creation and an update to the binding's param informer. The problems gets worse in HA setups when you have multiple kube-apiserver instances.
1 parent e32aa27 commit 5ab410d

File tree

1 file changed

+70
-0
lines changed

1 file changed

+70
-0
lines changed

test/e2e/cluster_extension_admission_test.go

+70
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,19 @@ package e2e
33
import (
44
"context"
55
"fmt"
6+
"net/http"
7+
"sync"
68
"testing"
79

810
"github.com/stretchr/testify/require"
911
"k8s.io/apimachinery/pkg/api/errors"
1012
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1113
"k8s.io/apimachinery/pkg/types"
14+
"k8s.io/client-go/rest"
1215
"sigs.k8s.io/controller-runtime/pkg/client"
1316

1417
ocv1alpha1 "github.com/operator-framework/operator-controller/api/v1alpha1"
18+
"github.com/operator-framework/operator-controller/pkg/scheme"
1519
)
1620

1721
func TestClusterExtensionPackageUniqueness(t *testing.T) {
@@ -96,3 +100,69 @@ func TestClusterExtensionPackageUniqueness(t *testing.T) {
96100
}
97101
require.NoError(t, c.Patch(ctx, intent, client.Apply, client.ForceOwnership, fieldOwner))
98102
}
103+
104+
type synchronizedRoundTripper struct {
105+
ready <-chan struct{}
106+
delegate http.RoundTripper
107+
}
108+
109+
func (rt synchronizedRoundTripper) RoundTrip(r *http.Request) (*http.Response, error) {
110+
// not necessary to reproduce but improves the odds
111+
<-rt.ready
112+
return rt.delegate.RoundTrip(r)
113+
}
114+
115+
func TestClusterExtensionPackageUniquenessConsistency(t *testing.T) {
116+
ctx := context.Background()
117+
118+
if err := c.DeleteAllOf(ctx, &ocv1alpha1.ClusterExtension{}); err != nil {
119+
t.Fatal(err)
120+
}
121+
122+
cfg := rest.CopyConfig(cfg)
123+
ready := make(chan struct{})
124+
cfg.Wrap(func(rt http.RoundTripper) http.RoundTripper {
125+
return synchronizedRoundTripper{delegate: rt, ready: ready}
126+
})
127+
128+
var wg sync.WaitGroup
129+
for i := 0; i < 100; i++ {
130+
wg.Add(1)
131+
go func(i int) {
132+
defer wg.Done()
133+
c, err := client.New(cfg, client.Options{Scheme: scheme.Scheme})
134+
if err != nil {
135+
t.Fatal(err)
136+
}
137+
138+
if err := c.Create(ctx, &ocv1alpha1.ClusterExtension{
139+
ObjectMeta: metav1.ObjectMeta{
140+
Name: fmt.Sprintf("ext-%d", i),
141+
},
142+
Spec: ocv1alpha1.ClusterExtensionSpec{
143+
PackageName: "pkg-x",
144+
},
145+
}); err != nil {
146+
t.Log(err)
147+
}
148+
}(i)
149+
}
150+
close(ready)
151+
wg.Wait()
152+
153+
var l ocv1alpha1.ClusterExtensionList
154+
if err := c.List(ctx, &l); err != nil {
155+
t.Fatal(err)
156+
}
157+
158+
counts := make(map[string]int)
159+
for _, ext := range l.Items {
160+
counts[ext.Spec.PackageName]++
161+
}
162+
163+
for pkg, count := range counts {
164+
if count > 1 {
165+
t.Errorf("duplicate package name: %s (%d duplicates)", pkg, count)
166+
}
167+
}
168+
}

0 commit comments

Comments
 (0)