Skip to content

Commit 3ffd95c

Browse files
committed
✨ Use aggregated discovery if available
This change makes the `RESTMapper` use [AggredatedDiscovery][0] if available. `AggregatedDiscovery` is beta and thus enabled by default since Kube 1.28. What this means in particular is that during startup, we will always do a request to `/api` and `/apis`. If `AggregatedDiscovery` is enabled, that is all we have to do. If not, we have to do a third request. The behavior for reloading remains the same: Do one request unless the request didn't include a version. In that case we have to find the group. If the group is unknown, we will keep on doing a request to `/api` and one to `/apis` to find it. If it is found we are again done in the `AggregatedDiscovery` case and have to do a third request otherwise. All in all this means that with `AggregatedDiscovery` enabled, the only case where this is worse is if the client interacts with only one `GroupVersion`, as we end up doing one additional api request. If it is disabled, we effectively waste two api requests. [0]: kubernetes/enhancements#3352
1 parent abb2d86 commit 3ffd95c

File tree

4 files changed

+857
-729
lines changed

4 files changed

+857
-729
lines changed

pkg/client/apiutil/apimachinery_test.go

+119-115
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package apiutil_test
1818

1919
import (
2020
"context"
21+
"strconv"
2122
"testing"
2223

2324
gmg "github.com/onsi/gomega"
@@ -32,127 +33,130 @@ import (
3233
)
3334

3435
func TestApiMachinery(t *testing.T) {
35-
restCfg, tearDownFn := setupEnvtest(t)
36-
defer tearDownFn(t)
37-
38-
// Details of the GVK registered at initialization.
39-
initialGvk := metav1.GroupVersionKind{
40-
Group: "crew.example.com",
41-
Version: "v1",
42-
Kind: "Driver",
43-
}
36+
for _, aggregatedDiscovery := range []bool{true, false} {
37+
t.Run("aggregatedDiscovery="+strconv.FormatBool(aggregatedDiscovery), func(t *testing.T) {
38+
restCfg := setupEnvtest(t, !aggregatedDiscovery)
4439

45-
// A set of GVKs to register at runtime with varying properties.
46-
runtimeGvks := []struct {
47-
name string
48-
gvk metav1.GroupVersionKind
49-
plural string
50-
}{
51-
{
52-
name: "new Kind and Version added to existing Group",
53-
gvk: metav1.GroupVersionKind{
54-
Group: "crew.example.com",
55-
Version: "v1alpha1",
56-
Kind: "Passenger",
57-
},
58-
plural: "passengers",
59-
},
60-
{
61-
name: "new Kind added to existing Group and Version",
62-
gvk: metav1.GroupVersionKind{
40+
// Details of the GVK registered at initialization.
41+
initialGvk := metav1.GroupVersionKind{
6342
Group: "crew.example.com",
6443
Version: "v1",
65-
Kind: "Garage",
66-
},
67-
plural: "garages",
68-
},
69-
{
70-
name: "new GVK",
71-
gvk: metav1.GroupVersionKind{
72-
Group: "inventory.example.com",
73-
Version: "v1",
74-
Kind: "Taxi",
75-
},
76-
plural: "taxis",
77-
},
78-
}
44+
Kind: "Driver",
45+
}
46+
47+
// A set of GVKs to register at runtime with varying properties.
48+
runtimeGvks := []struct {
49+
name string
50+
gvk metav1.GroupVersionKind
51+
plural string
52+
}{
53+
{
54+
name: "new Kind and Version added to existing Group",
55+
gvk: metav1.GroupVersionKind{
56+
Group: "crew.example.com",
57+
Version: "v1alpha1",
58+
Kind: "Passenger",
59+
},
60+
plural: "passengers",
61+
},
62+
{
63+
name: "new Kind added to existing Group and Version",
64+
gvk: metav1.GroupVersionKind{
65+
Group: "crew.example.com",
66+
Version: "v1",
67+
Kind: "Garage",
68+
},
69+
plural: "garages",
70+
},
71+
{
72+
name: "new GVK",
73+
gvk: metav1.GroupVersionKind{
74+
Group: "inventory.example.com",
75+
Version: "v1",
76+
Kind: "Taxi",
77+
},
78+
plural: "taxis",
79+
},
80+
}
81+
82+
t.Run("IsGVKNamespaced should report scope for GVK registered at initialization", func(t *testing.T) {
83+
g := gmg.NewWithT(t)
84+
85+
httpClient, err := rest.HTTPClientFor(restCfg)
86+
g.Expect(err).NotTo(gmg.HaveOccurred())
7987

80-
t.Run("IsGVKNamespaced should report scope for GVK registered at initialization", func(t *testing.T) {
81-
g := gmg.NewWithT(t)
82-
83-
httpClient, err := rest.HTTPClientFor(restCfg)
84-
g.Expect(err).NotTo(gmg.HaveOccurred())
85-
86-
lazyRestMapper, err := apiutil.NewDynamicRESTMapper(restCfg, httpClient)
87-
g.Expect(err).NotTo(gmg.HaveOccurred())
88-
89-
s := scheme.Scheme
90-
err = apiextensionsv1.AddToScheme(s)
91-
g.Expect(err).NotTo(gmg.HaveOccurred())
92-
93-
// Query the scope of a GVK that was registered at initialization.
94-
scope, err := apiutil.IsGVKNamespaced(
95-
schema.GroupVersionKind(initialGvk),
96-
lazyRestMapper,
97-
)
98-
g.Expect(err).NotTo(gmg.HaveOccurred())
99-
g.Expect(scope).To(gmg.BeTrue())
100-
})
101-
102-
for _, runtimeGvk := range runtimeGvks {
103-
t.Run("IsGVKNamespaced should report scope for "+runtimeGvk.name, func(t *testing.T) {
104-
g := gmg.NewWithT(t)
105-
ctx := context.Background()
106-
107-
httpClient, err := rest.HTTPClientFor(restCfg)
108-
g.Expect(err).NotTo(gmg.HaveOccurred())
109-
110-
lazyRestMapper, err := apiutil.NewDynamicRESTMapper(restCfg, httpClient)
111-
g.Expect(err).NotTo(gmg.HaveOccurred())
112-
113-
s := scheme.Scheme
114-
err = apiextensionsv1.AddToScheme(s)
115-
g.Expect(err).NotTo(gmg.HaveOccurred())
116-
117-
c, err := client.New(restCfg, client.Options{Scheme: s})
118-
g.Expect(err).NotTo(gmg.HaveOccurred())
119-
120-
// Run a valid query to initialize cache.
121-
scope, err := apiutil.IsGVKNamespaced(
122-
schema.GroupVersionKind(initialGvk),
123-
lazyRestMapper,
124-
)
125-
g.Expect(err).NotTo(gmg.HaveOccurred())
126-
g.Expect(scope).To(gmg.BeTrue())
127-
128-
// Register a new CRD at runtime.
129-
crd := newCRD(ctx, g, c, runtimeGvk.gvk.Group, runtimeGvk.gvk.Kind, runtimeGvk.plural)
130-
version := crd.Spec.Versions[0]
131-
version.Name = runtimeGvk.gvk.Version
132-
version.Storage = true
133-
version.Served = true
134-
crd.Spec.Versions = []apiextensionsv1.CustomResourceDefinitionVersion{version}
135-
crd.Spec.Scope = apiextensionsv1.NamespaceScoped
136-
137-
g.Expect(c.Create(ctx, crd)).To(gmg.Succeed())
138-
t.Cleanup(func() {
139-
g.Expect(c.Delete(ctx, crd)).To(gmg.Succeed())
140-
})
88+
lazyRestMapper, err := apiutil.NewDynamicRESTMapper(restCfg, httpClient)
89+
g.Expect(err).NotTo(gmg.HaveOccurred())
90+
91+
s := scheme.Scheme
92+
err = apiextensionsv1.AddToScheme(s)
93+
g.Expect(err).NotTo(gmg.HaveOccurred())
14194

142-
// Wait until the CRD is registered.
143-
g.Eventually(func(g gmg.Gomega) {
144-
isRegistered, err := isCrdRegistered(restCfg, runtimeGvk.gvk)
95+
// Query the scope of a GVK that was registered at initialization.
96+
scope, err := apiutil.IsGVKNamespaced(
97+
schema.GroupVersionKind(initialGvk),
98+
lazyRestMapper,
99+
)
145100
g.Expect(err).NotTo(gmg.HaveOccurred())
146-
g.Expect(isRegistered).To(gmg.BeTrue())
147-
}).Should(gmg.Succeed(), "GVK should be available")
148-
149-
// Query the scope of the GVK registered at runtime.
150-
scope, err = apiutil.IsGVKNamespaced(
151-
schema.GroupVersionKind(runtimeGvk.gvk),
152-
lazyRestMapper,
153-
)
154-
g.Expect(err).NotTo(gmg.HaveOccurred())
155-
g.Expect(scope).To(gmg.BeTrue())
101+
g.Expect(scope).To(gmg.BeTrue())
102+
})
103+
104+
for _, runtimeGvk := range runtimeGvks {
105+
t.Run("IsGVKNamespaced should report scope for "+runtimeGvk.name, func(t *testing.T) {
106+
g := gmg.NewWithT(t)
107+
ctx := context.Background()
108+
109+
httpClient, err := rest.HTTPClientFor(restCfg)
110+
g.Expect(err).NotTo(gmg.HaveOccurred())
111+
112+
lazyRestMapper, err := apiutil.NewDynamicRESTMapper(restCfg, httpClient)
113+
g.Expect(err).NotTo(gmg.HaveOccurred())
114+
115+
s := scheme.Scheme
116+
err = apiextensionsv1.AddToScheme(s)
117+
g.Expect(err).NotTo(gmg.HaveOccurred())
118+
119+
c, err := client.New(restCfg, client.Options{Scheme: s})
120+
g.Expect(err).NotTo(gmg.HaveOccurred())
121+
122+
// Run a valid query to initialize cache.
123+
scope, err := apiutil.IsGVKNamespaced(
124+
schema.GroupVersionKind(initialGvk),
125+
lazyRestMapper,
126+
)
127+
g.Expect(err).NotTo(gmg.HaveOccurred())
128+
g.Expect(scope).To(gmg.BeTrue())
129+
130+
// Register a new CRD at runtime.
131+
crd := newCRD(ctx, g, c, runtimeGvk.gvk.Group, runtimeGvk.gvk.Kind, runtimeGvk.plural)
132+
version := crd.Spec.Versions[0]
133+
version.Name = runtimeGvk.gvk.Version
134+
version.Storage = true
135+
version.Served = true
136+
crd.Spec.Versions = []apiextensionsv1.CustomResourceDefinitionVersion{version}
137+
crd.Spec.Scope = apiextensionsv1.NamespaceScoped
138+
139+
g.Expect(c.Create(ctx, crd)).To(gmg.Succeed())
140+
t.Cleanup(func() {
141+
g.Expect(c.Delete(ctx, crd)).To(gmg.Succeed())
142+
})
143+
144+
// Wait until the CRD is registered.
145+
g.Eventually(func(g gmg.Gomega) {
146+
isRegistered, err := isCrdRegistered(restCfg, runtimeGvk.gvk)
147+
g.Expect(err).NotTo(gmg.HaveOccurred())
148+
g.Expect(isRegistered).To(gmg.BeTrue())
149+
}).Should(gmg.Succeed(), "GVK should be available")
150+
151+
// Query the scope of the GVK registered at runtime.
152+
scope, err = apiutil.IsGVKNamespaced(
153+
schema.GroupVersionKind(runtimeGvk.gvk),
154+
lazyRestMapper,
155+
)
156+
g.Expect(err).NotTo(gmg.HaveOccurred())
157+
g.Expect(scope).To(gmg.BeTrue())
158+
})
159+
}
156160
})
157161
}
158162
}

0 commit comments

Comments
 (0)