Skip to content

Commit be71e2c

Browse files
committed
UPSTREAM: <carry>: make RESTMapper in client cluster-aware
Signed-off-by: Dr. Stefan Schimanski <[email protected]>
1 parent a3df1ba commit be71e2c

File tree

10 files changed

+356
-98
lines changed

10 files changed

+356
-98
lines changed

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ require (
3131
)
3232

3333
require (
34+
github.com/hashicorp/golang-lru/v2 v2.0.7
3435
github.com/kcp-dev/apimachinery/v2 v2.0.0-alpha.0.0.20230926071920-57d168bcbe34
3536
github.com/kcp-dev/logicalcluster/v3 v3.0.5
3637
)

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
6565
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
6666
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms=
6767
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg=
68+
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
69+
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
6870
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
6971
github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
7072
github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=

pkg/client/client.go

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,10 @@ import (
2222
"fmt"
2323
"net/http"
2424
"strings"
25+
"time"
2526

27+
"github.com/hashicorp/golang-lru/v2/expirable"
28+
"github.com/kcp-dev/logicalcluster/v3"
2629
"k8s.io/apimachinery/pkg/api/meta"
2730
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2831
"k8s.io/apimachinery/pkg/runtime"
@@ -47,6 +50,10 @@ type Options struct {
4750
// Mapper, if provided, will be used to map GroupVersionKinds to Resources
4851
Mapper meta.RESTMapper
4952

53+
// MapperWithContext, if provided, will be used to map GroupVersionKinds to Resources.
54+
// This overrides Mapper if set.
55+
MapperWithContext func(context.Context) (meta.RESTMapper, error)
56+
5057
// Cache, if provided, is used to read objects from the cache.
5158
Cache *CacheOptions
5259

@@ -170,15 +177,20 @@ func newClient(config *rest.Config, options Options) (*client, error) {
170177
}
171178
}
172179

180+
// Init a MapperWithContext if none provided
181+
if options.MapperWithContext == nil {
182+
options.MapperWithContext = func(context.Context) (meta.RESTMapper, error) { return options.Mapper, nil }
183+
}
184+
173185
resources := &clientRestResources{
174186
httpClient: options.HTTPClient,
175187
config: config,
176188
scheme: options.Scheme,
177-
mapper: options.Mapper,
189+
mapper: options.MapperWithContext,
178190
codecs: serializer.NewCodecFactory(options.Scheme),
179191

180-
structuredResourceByType: make(map[schema.GroupVersionKind]*resourceMeta),
181-
unstructuredResourceByType: make(map[schema.GroupVersionKind]*resourceMeta),
192+
structuredResourceByType: expirable.NewLRU[logicalcluster.Path, map[schema.GroupVersionKind]*resourceMeta](1000, nil, 10*time.Minute),
193+
unstructuredResourceByType: expirable.NewLRU[logicalcluster.Path, map[schema.GroupVersionKind]*resourceMeta](1000, nil, 10*time.Minute),
182194
}
183195

184196
rawMetaClient, err := metadata.NewForConfigAndClient(metadata.ConfigFor(config), options.HTTPClient)
@@ -197,7 +209,7 @@ func newClient(config *rest.Config, options Options) (*client, error) {
197209
},
198210
metadataClient: metadataClient{
199211
client: rawMetaClient,
200-
restMapper: options.Mapper,
212+
restMapper: options.MapperWithContext,
201213
},
202214
scheme: options.Scheme,
203215
mapper: options.Mapper,

pkg/client/client_rest_resources.go

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,19 +17,25 @@ limitations under the License.
1717
package client
1818

1919
import (
20+
"context"
2021
"net/http"
2122
"strings"
2223
"sync"
2324

25+
"github.com/hashicorp/golang-lru/v2/expirable"
26+
"github.com/kcp-dev/logicalcluster/v3"
2427
"k8s.io/apimachinery/pkg/api/meta"
2528
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2629
"k8s.io/apimachinery/pkg/runtime"
2730
"k8s.io/apimachinery/pkg/runtime/schema"
2831
"k8s.io/apimachinery/pkg/runtime/serializer"
2932
"k8s.io/client-go/rest"
3033
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
34+
"sigs.k8s.io/controller-runtime/pkg/kontext"
3135
)
3236

37+
type resourceLRU = *expirable.LRU[logicalcluster.Path, map[schema.GroupVersionKind]*resourceMeta]
38+
3339
// clientRestResources creates and stores rest clients and metadata for Kubernetes types.
3440
type clientRestResources struct {
3541
// httpClient is the http client to use for requests
@@ -42,21 +48,21 @@ type clientRestResources struct {
4248
scheme *runtime.Scheme
4349

4450
// mapper maps GroupVersionKinds to Resources
45-
mapper meta.RESTMapper
51+
mapper func(ctx context.Context) (meta.RESTMapper, error)
4652

4753
// codecs are used to create a REST client for a gvk
4854
codecs serializer.CodecFactory
4955

5056
// structuredResourceByType stores structured type metadata
51-
structuredResourceByType map[schema.GroupVersionKind]*resourceMeta
57+
structuredResourceByType resourceLRU
5258
// unstructuredResourceByType stores unstructured type metadata
53-
unstructuredResourceByType map[schema.GroupVersionKind]*resourceMeta
59+
unstructuredResourceByType resourceLRU
5460
mu sync.RWMutex
5561
}
5662

5763
// newResource maps obj to a Kubernetes Resource and constructs a client for that Resource.
5864
// If the object is a list, the resource represents the item's type instead.
59-
func (c *clientRestResources) newResource(gvk schema.GroupVersionKind, isList, isUnstructured bool) (*resourceMeta, error) {
65+
func (c *clientRestResources) newResource(ctx context.Context, gvk schema.GroupVersionKind, isList, isUnstructured bool) (*resourceMeta, error) {
6066
if strings.HasSuffix(gvk.Kind, "List") && isList {
6167
// if this was a list, treat it as a request for the item's resource
6268
gvk.Kind = gvk.Kind[:len(gvk.Kind)-4]
@@ -66,7 +72,11 @@ func (c *clientRestResources) newResource(gvk schema.GroupVersionKind, isList, i
6672
if err != nil {
6773
return nil, err
6874
}
69-
mapping, err := c.mapper.RESTMapping(gvk.GroupKind(), gvk.Version)
75+
mapper, err := c.mapper(ctx)
76+
if err != nil {
77+
return nil, err
78+
}
79+
mapping, err := mapper.RESTMapping(gvk.GroupKind(), gvk.Version)
7080
if err != nil {
7181
return nil, err
7282
}
@@ -75,7 +85,7 @@ func (c *clientRestResources) newResource(gvk schema.GroupVersionKind, isList, i
7585

7686
// getResource returns the resource meta information for the given type of object.
7787
// If the object is a list, the resource represents the item's type instead.
78-
func (c *clientRestResources) getResource(obj runtime.Object) (*resourceMeta, error) {
88+
func (c *clientRestResources) getResource(ctx context.Context, obj runtime.Object) (*resourceMeta, error) {
7989
gvk, err := apiutil.GVKForObject(obj, c.scheme)
8090
if err != nil {
8191
return nil, err
@@ -86,9 +96,15 @@ func (c *clientRestResources) getResource(obj runtime.Object) (*resourceMeta, er
8696
// It's better to do creation work twice than to not let multiple
8797
// people make requests at once
8898
c.mu.RLock()
89-
resourceByType := c.structuredResourceByType
99+
resourceByClusterAndType := c.structuredResourceByType
90100
if isUnstructured {
91-
resourceByType = c.unstructuredResourceByType
101+
resourceByClusterAndType = c.unstructuredResourceByType
102+
}
103+
cluster, _ := kontext.ClusterFrom(ctx)
104+
resourceByType, found := resourceByClusterAndType.Get(cluster.Path())
105+
if !found {
106+
resourceByType = make(map[schema.GroupVersionKind]*resourceMeta)
107+
resourceByClusterAndType.Add(cluster.Path(), resourceByType)
92108
}
93109
r, known := resourceByType[gvk]
94110
c.mu.RUnlock()
@@ -100,7 +116,7 @@ func (c *clientRestResources) getResource(obj runtime.Object) (*resourceMeta, er
100116
// Initialize a new Client
101117
c.mu.Lock()
102118
defer c.mu.Unlock()
103-
r, err = c.newResource(gvk, meta.IsListType(obj), isUnstructured)
119+
r, err = c.newResource(ctx, gvk, meta.IsListType(obj), isUnstructured)
104120
if err != nil {
105121
return nil, err
106122
}
@@ -109,8 +125,8 @@ func (c *clientRestResources) getResource(obj runtime.Object) (*resourceMeta, er
109125
}
110126

111127
// getObjMeta returns objMeta containing both type and object metadata and state.
112-
func (c *clientRestResources) getObjMeta(obj runtime.Object) (*objMeta, error) {
113-
r, err := c.getResource(obj)
128+
func (c *clientRestResources) getObjMeta(ctx context.Context, obj runtime.Object) (*objMeta, error) {
129+
r, err := c.getResource(ctx, obj)
114130
if err != nil {
115131
return nil, err
116132
}

pkg/client/metadata_client.go

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,15 @@ import (
3535
// metadataClient is a client that reads & writes metadata-only requests to/from the API server.
3636
type metadataClient struct {
3737
client metadata.Interface
38-
restMapper meta.RESTMapper
38+
restMapper func(ctx context.Context) (meta.RESTMapper, error)
3939
}
4040

41-
func (mc *metadataClient) getResourceInterface(gvk schema.GroupVersionKind, ns string) (metadata.ResourceInterface, error) {
42-
mapping, err := mc.restMapper.RESTMapping(gvk.GroupKind(), gvk.Version)
41+
func (mc *metadataClient) getResourceInterface(ctx context.Context, gvk schema.GroupVersionKind, ns string) (metadata.ResourceInterface, error) {
42+
mapper, err := mc.restMapper(ctx)
43+
if err != nil {
44+
return nil, err
45+
}
46+
mapping, err := mapper.RESTMapping(gvk.GroupKind(), gvk.Version)
4347
if err != nil {
4448
return nil, err
4549
}
@@ -56,7 +60,7 @@ func (mc *metadataClient) Delete(ctx context.Context, obj Object, opts ...Delete
5660
return fmt.Errorf("metadata client did not understand object: %T", obj)
5761
}
5862

59-
resInt, err := mc.getResourceInterface(metadata.GroupVersionKind(), metadata.Namespace)
63+
resInt, err := mc.getResourceInterface(ctx, metadata.GroupVersionKind(), metadata.Namespace)
6064
if err != nil {
6165
return err
6266
}
@@ -77,7 +81,7 @@ func (mc *metadataClient) DeleteAllOf(ctx context.Context, obj Object, opts ...D
7781
deleteAllOfOpts := DeleteAllOfOptions{}
7882
deleteAllOfOpts.ApplyOptions(opts)
7983

80-
resInt, err := mc.getResourceInterface(metadata.GroupVersionKind(), deleteAllOfOpts.ListOptions.Namespace)
84+
resInt, err := mc.getResourceInterface(ctx, metadata.GroupVersionKind(), deleteAllOfOpts.ListOptions.Namespace)
8185
if err != nil {
8286
return err
8387
}
@@ -93,7 +97,7 @@ func (mc *metadataClient) Patch(ctx context.Context, obj Object, patch Patch, op
9397
}
9498

9599
gvk := metadata.GroupVersionKind()
96-
resInt, err := mc.getResourceInterface(gvk, metadata.Namespace)
100+
resInt, err := mc.getResourceInterface(ctx, gvk, metadata.Namespace)
97101
if err != nil {
98102
return err
99103
}
@@ -127,7 +131,7 @@ func (mc *metadataClient) Get(ctx context.Context, key ObjectKey, obj Object, op
127131
getOpts := GetOptions{}
128132
getOpts.ApplyOptions(opts)
129133

130-
resInt, err := mc.getResourceInterface(gvk, key.Namespace)
134+
resInt, err := mc.getResourceInterface(ctx, gvk, key.Namespace)
131135
if err != nil {
132136
return err
133137
}
@@ -154,7 +158,7 @@ func (mc *metadataClient) List(ctx context.Context, obj ObjectList, opts ...List
154158
listOpts := ListOptions{}
155159
listOpts.ApplyOptions(opts)
156160

157-
resInt, err := mc.getResourceInterface(gvk, listOpts.Namespace)
161+
resInt, err := mc.getResourceInterface(ctx, gvk, listOpts.Namespace)
158162
if err != nil {
159163
return err
160164
}
@@ -175,7 +179,7 @@ func (mc *metadataClient) PatchSubResource(ctx context.Context, obj Object, subR
175179
}
176180

177181
gvk := metadata.GroupVersionKind()
178-
resInt, err := mc.getResourceInterface(gvk, metadata.Namespace)
182+
resInt, err := mc.getResourceInterface(ctx, gvk, metadata.Namespace)
179183
if err != nil {
180184
return err
181185
}

pkg/client/typed_client.go

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ type typedClient struct {
3232

3333
// Create implements client.Client.
3434
func (c *typedClient) Create(ctx context.Context, obj Object, opts ...CreateOption) error {
35-
o, err := c.resources.getObjMeta(obj)
35+
o, err := c.resources.getObjMeta(ctx, obj)
3636
if err != nil {
3737
return err
3838
}
@@ -51,7 +51,7 @@ func (c *typedClient) Create(ctx context.Context, obj Object, opts ...CreateOpti
5151

5252
// Update implements client.Client.
5353
func (c *typedClient) Update(ctx context.Context, obj Object, opts ...UpdateOption) error {
54-
o, err := c.resources.getObjMeta(obj)
54+
o, err := c.resources.getObjMeta(ctx, obj)
5555
if err != nil {
5656
return err
5757
}
@@ -71,7 +71,7 @@ func (c *typedClient) Update(ctx context.Context, obj Object, opts ...UpdateOpti
7171

7272
// Delete implements client.Client.
7373
func (c *typedClient) Delete(ctx context.Context, obj Object, opts ...DeleteOption) error {
74-
o, err := c.resources.getObjMeta(obj)
74+
o, err := c.resources.getObjMeta(ctx, obj)
7575
if err != nil {
7676
return err
7777
}
@@ -90,7 +90,7 @@ func (c *typedClient) Delete(ctx context.Context, obj Object, opts ...DeleteOpti
9090

9191
// DeleteAllOf implements client.Client.
9292
func (c *typedClient) DeleteAllOf(ctx context.Context, obj Object, opts ...DeleteAllOfOption) error {
93-
o, err := c.resources.getObjMeta(obj)
93+
o, err := c.resources.getObjMeta(ctx, obj)
9494
if err != nil {
9595
return err
9696
}
@@ -109,7 +109,7 @@ func (c *typedClient) DeleteAllOf(ctx context.Context, obj Object, opts ...Delet
109109

110110
// Patch implements client.Client.
111111
func (c *typedClient) Patch(ctx context.Context, obj Object, patch Patch, opts ...PatchOption) error {
112-
o, err := c.resources.getObjMeta(obj)
112+
o, err := c.resources.getObjMeta(ctx, obj)
113113
if err != nil {
114114
return err
115115
}
@@ -134,7 +134,7 @@ func (c *typedClient) Patch(ctx context.Context, obj Object, patch Patch, opts .
134134

135135
// Get implements client.Client.
136136
func (c *typedClient) Get(ctx context.Context, key ObjectKey, obj Object, opts ...GetOption) error {
137-
r, err := c.resources.getResource(obj)
137+
r, err := c.resources.getResource(ctx, obj)
138138
if err != nil {
139139
return err
140140
}
@@ -149,7 +149,7 @@ func (c *typedClient) Get(ctx context.Context, key ObjectKey, obj Object, opts .
149149

150150
// List implements client.Client.
151151
func (c *typedClient) List(ctx context.Context, obj ObjectList, opts ...ListOption) error {
152-
r, err := c.resources.getResource(obj)
152+
r, err := c.resources.getResource(ctx, obj)
153153
if err != nil {
154154
return err
155155
}
@@ -166,7 +166,7 @@ func (c *typedClient) List(ctx context.Context, obj ObjectList, opts ...ListOpti
166166
}
167167

168168
func (c *typedClient) GetSubResource(ctx context.Context, obj, subResourceObj Object, subResource string, opts ...SubResourceGetOption) error {
169-
o, err := c.resources.getObjMeta(obj)
169+
o, err := c.resources.getObjMeta(ctx, obj)
170170
if err != nil {
171171
return err
172172
}
@@ -189,7 +189,7 @@ func (c *typedClient) GetSubResource(ctx context.Context, obj, subResourceObj Ob
189189
}
190190

191191
func (c *typedClient) CreateSubResource(ctx context.Context, obj Object, subResourceObj Object, subResource string, opts ...SubResourceCreateOption) error {
192-
o, err := c.resources.getObjMeta(obj)
192+
o, err := c.resources.getObjMeta(ctx, obj)
193193
if err != nil {
194194
return err
195195
}
@@ -214,7 +214,7 @@ func (c *typedClient) CreateSubResource(ctx context.Context, obj Object, subReso
214214

215215
// UpdateSubResource used by SubResourceWriter to write status.
216216
func (c *typedClient) UpdateSubResource(ctx context.Context, obj Object, subResource string, opts ...SubResourceUpdateOption) error {
217-
o, err := c.resources.getObjMeta(obj)
217+
o, err := c.resources.getObjMeta(ctx, obj)
218218
if err != nil {
219219
return err
220220
}
@@ -249,7 +249,7 @@ func (c *typedClient) UpdateSubResource(ctx context.Context, obj Object, subReso
249249

250250
// PatchSubResource used by SubResourceWriter to write subresource.
251251
func (c *typedClient) PatchSubResource(ctx context.Context, obj Object, subResource string, patch Patch, opts ...SubResourcePatchOption) error {
252-
o, err := c.resources.getObjMeta(obj)
252+
o, err := c.resources.getObjMeta(ctx, obj)
253253
if err != nil {
254254
return err
255255
}

0 commit comments

Comments
 (0)