Skip to content

Commit

Permalink
Merge pull request #8918 from sbueringer/pr-cache-mapper
Browse files Browse the repository at this point in the history
🌱 util: cache list calls in cluster to objects mapper
  • Loading branch information
k8s-ci-robot authored Jun 27, 2023
2 parents 34e2fa7 + abe0b00 commit fd0dc60
Show file tree
Hide file tree
Showing 14 changed files with 92 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ func (r *KubeadmConfigReconciler) Reconcile(ctx context.Context, req ctrl.Reques
}

// Look up the owner of this kubeadm config if there is one
configOwner, err := bsutil.GetConfigOwnerFromCache(ctx, r.Client, config)
configOwner, err := bsutil.GetTypedConfigOwner(ctx, r.Client, config)
if apierrors.IsNotFound(err) {
// Could not find the owner yet, this is not an error and will rereconcile when the owner gets set.
return ctrl.Result{}, nil
Expand Down
12 changes: 6 additions & 6 deletions bootstrap/util/configowner.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,15 +127,15 @@ func (co ConfigOwner) KubernetesVersion() string {

// GetConfigOwner returns the Unstructured object owning the current resource
// using the uncached unstructured client. For performance-sensitive uses,
// consider GetConfigOwnerCached.
// consider GetTypedConfigOwner.
func GetConfigOwner(ctx context.Context, c client.Client, obj metav1.Object) (*ConfigOwner, error) {
return getConfigOwner(ctx, c, obj, GetOwnerByRef)
}

// GetConfigOwnerFromCache returns the Unstructured object owning the current
// GetTypedConfigOwner returns the Unstructured object owning the current
// resource. The implementation ensures a typed client is used, so the objects are read from the cache.
func GetConfigOwnerFromCache(ctx context.Context, c client.Client, obj metav1.Object) (*ConfigOwner, error) {
return getConfigOwner(ctx, c, obj, GetOwnerByRefFromCache)
func GetTypedConfigOwner(ctx context.Context, c client.Client, obj metav1.Object) (*ConfigOwner, error) {
return getConfigOwner(ctx, c, obj, GetTypedOwnerByRef)
}

func getConfigOwner(ctx context.Context, c client.Client, obj metav1.Object, getFn func(context.Context, client.Client, *corev1.ObjectReference) (*ConfigOwner, error)) (*ConfigOwner, error) {
Expand Down Expand Up @@ -183,9 +183,9 @@ func GetOwnerByRef(ctx context.Context, c client.Client, ref *corev1.ObjectRefer
return &ConfigOwner{obj}, nil
}

// GetOwnerByRefFromCache finds and returns the owner by looking at the object
// GetTypedOwnerByRef finds and returns the owner by looking at the object
// reference. The implementation ensures a typed client is used, so the objects are read from the cache.
func GetOwnerByRefFromCache(ctx context.Context, c client.Client, ref *corev1.ObjectReference) (*ConfigOwner, error) {
func GetTypedOwnerByRef(ctx context.Context, c client.Client, ref *corev1.ObjectReference) (*ConfigOwner, error) {
obj, err := c.Scheme().New(ref.GroupVersionKind())
if err != nil {
return nil, errors.Wrapf(err, "failed to construct object of type %s", ref.GroupVersionKind())
Expand Down
2 changes: 1 addition & 1 deletion bootstrap/util/configowner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ func TestGetConfigOwner(t *testing.T) {
doTests(t, GetConfigOwner)
})
t.Run("cached", func(t *testing.T) {
doTests(t, GetConfigOwnerFromCache)
doTests(t, GetTypedConfigOwner)
})
}

Expand Down
3 changes: 2 additions & 1 deletion docs/book/src/developer/providers/migrations/v1.4-to-v1.5.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ maintainers of providers and consumers of our Go API.
- clusterctl move is adding the new annotation `clusterctl.cluster.x-k8s.io/delete-for-move` before object deletion.
- Providers running CAPI release-0.3 clusterctl upgrade tests should set `WorkloadKubernetesVersion` field to the maximum workload cluster kubernetes version supported by the old providers in `ClusterctlUpgradeSpecInput`. For more information, please see: https://github.com/kubernetes-sigs/cluster-api/pull/8518#issuecomment-1508064859
- Introduced function `CollectInfrastructureLogs` at the `ClusterLogCollector` interface in `test/framework/cluster_proxy.go` to allow collecting infrastructure related logs during tests.
- A `GetConfigOwnerFromCache` function has been added to the `sigs.k8s.io./cluster-api/bootstrap/util` package. It is equivalent to `GetConfigOwner` except that it uses the cached typed client instead of the uncached unstructured client, so `GetConfigOwnerFromCache` is expected to be more performant.
- A `GetTypedConfigOwner` function has been added to the `sigs.k8s.io./cluster-api/bootstrap/util` package. It is equivalent to `GetConfigOwner` except that it uses the cached typed client instead of the uncached unstructured client, so `GetTypedConfigOwner` is expected to be more performant.
- `ClusterToObjectsMapper` in `sigs.k8s.io./cluster-api/util` has been deprecated, please use `ClusterToTypedObjectsMapper` instead.

### Suggested changes for providers

Expand Down
2 changes: 1 addition & 1 deletion exp/internal/controllers/machinepool_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ type MachinePoolReconciler struct {
}

func (r *MachinePoolReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error {
clusterToMachinePools, err := util.ClusterToObjectsMapper(mgr.GetClient(), &expv1.MachinePoolList{}, mgr.GetScheme())
clusterToMachinePools, err := util.ClusterToTypedObjectsMapper(mgr.GetClient(), &expv1.MachinePoolList{}, mgr.GetScheme())
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion internal/controllers/machine/machine_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ type Reconciler struct {
}

func (r *Reconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error {
clusterToMachines, err := util.ClusterToObjectsMapper(mgr.GetClient(), &clusterv1.MachineList{}, mgr.GetScheme())
clusterToMachines, err := util.ClusterToTypedObjectsMapper(mgr.GetClient(), &clusterv1.MachineList{}, mgr.GetScheme())
if err != nil {
return err
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ type Reconciler struct {
}

func (r *Reconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error {
clusterToMachineDeployments, err := util.ClusterToObjectsMapper(mgr.GetClient(), &clusterv1.MachineDeploymentList{}, mgr.GetScheme())
clusterToMachineDeployments, err := util.ClusterToTypedObjectsMapper(mgr.GetClient(), &clusterv1.MachineDeploymentList{}, mgr.GetScheme())
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion internal/controllers/machineset/machineset_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ type Reconciler struct {
}

func (r *Reconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error {
clusterToMachineSets, err := util.ClusterToObjectsMapper(mgr.GetClient(), &clusterv1.MachineSetList{}, mgr.GetScheme())
clusterToMachineSets, err := util.ClusterToTypedObjectsMapper(mgr.GetClient(), &clusterv1.MachineSetList{}, mgr.GetScheme())
if err != nil {
return err
}
Expand Down
9 changes: 4 additions & 5 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -367,13 +367,12 @@ func setupReconcilers(ctx context.Context, mgr ctrl.Manager) {
Unstructured: true,
},
})
if err != nil {
setupLog.Error(err, "unable to create unstructured caching client")
os.Exit(1)
}

if feature.Gates.Enabled(feature.ClusterTopology) {
if err != nil {
setupLog.Error(err, "unable to create unstructured caching client", "controller", "ClusterTopology")
os.Exit(1)
}

if err := (&controllers.ClusterClassReconciler{
Client: mgr.GetClient(),
APIReader: mgr.GetAPIReader(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ func (r *DockerMachinePoolReconciler) Reconcile(ctx context.Context, req ctrl.Re

// SetupWithManager will add watches for this controller.
func (r *DockerMachinePoolReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error {
clusterToDockerMachinePools, err := util.ClusterToObjectsMapper(mgr.GetClient(), &infraexpv1.DockerMachinePoolList{}, mgr.GetScheme())
clusterToDockerMachinePools, err := util.ClusterToTypedObjectsMapper(mgr.GetClient(), &infraexpv1.DockerMachinePoolList{}, mgr.GetScheme())
if err != nil {
return err
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -422,7 +422,7 @@ func (r *DockerMachineReconciler) reconcileDelete(ctx context.Context, machine *

// SetupWithManager will add watches for this controller.
func (r *DockerMachineReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error {
clusterToDockerMachines, err := util.ClusterToObjectsMapper(mgr.GetClient(), &infrav1.DockerMachineList{}, mgr.GetScheme())
clusterToDockerMachines, err := util.ClusterToTypedObjectsMapper(mgr.GetClient(), &infrav1.DockerMachineList{}, mgr.GetScheme())
if err != nil {
return err
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1130,7 +1130,7 @@ func (r *InMemoryMachineReconciler) reconcileDeleteControllerManager(ctx context

// SetupWithManager will add watches for this controller.
func (r *InMemoryMachineReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error {
clusterToInMemoryMachines, err := util.ClusterToObjectsMapper(mgr.GetClient(), &infrav1.InMemoryMachineList{}, mgr.GetScheme())
clusterToInMemoryMachines, err := util.ClusterToTypedObjectsMapper(mgr.GetClient(), &infrav1.InMemoryMachineList{}, mgr.GetScheme())
if err != nil {
return err
}
Expand Down
70 changes: 70 additions & 0 deletions util/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,10 @@ func (k KubeAwareAPIVersions) Less(i, j int) bool {
// ClusterToObjectsMapper returns a mapper function that gets a cluster and lists all objects for the object passed in
// and returns a list of requests.
// NB: The objects are required to have `clusterv1.ClusterNameLabel` applied.
//
// Deprecated: This function is deprecated and will be removed in a future release, use ClusterToTypedObjectsMapper instead.
// The problem with this function is that it uses UnstructuredList to retrieve objects, with the default client configuration
// this will lead to uncached List calls, which is a major performance issue.
func ClusterToObjectsMapper(c client.Client, ro client.ObjectList, scheme *runtime.Scheme) (handler.MapFunc, error) {
gvk, err := apiutil.GVKForObject(ro, scheme)
if err != nil {
Expand Down Expand Up @@ -513,6 +517,72 @@ func ClusterToObjectsMapper(c client.Client, ro client.ObjectList, scheme *runti
}, nil
}

// ClusterToTypedObjectsMapper returns a mapper function that gets a cluster and lists all objects for the object passed in
// and returns a list of requests.
// Note: This function uses the passed in typed ObjectList and thus with the default client configuration all list calls
// will be cached.
// NB: The objects are required to have `clusterv1.ClusterNameLabel` applied.
func ClusterToTypedObjectsMapper(c client.Client, ro client.ObjectList, scheme *runtime.Scheme) (handler.MapFunc, error) {
gvk, err := apiutil.GVKForObject(ro, scheme)
if err != nil {
return nil, err
}

// Note: we create the typed ObjectList once here, so we don't have to use
// reflection in every execution of the actual event handler.
obj, err := scheme.New(gvk)
if err != nil {
return nil, errors.Wrapf(err, "failed to construct object of type %s", gvk)
}
objectList, ok := obj.(client.ObjectList)
if !ok {
return nil, errors.Errorf("expected objject to be a client.ObjectList, is actually %T", obj)
}

isNamespaced, err := isAPINamespaced(gvk, c.RESTMapper())
if err != nil {
return nil, err
}

return func(ctx context.Context, o client.Object) []ctrl.Request {
cluster, ok := o.(*clusterv1.Cluster)
if !ok {
return nil
}

listOpts := []client.ListOption{
client.MatchingLabels{
clusterv1.ClusterNameLabel: cluster.Name,
},
}

if isNamespaced {
listOpts = append(listOpts, client.InNamespace(cluster.Namespace))
}

objectList = objectList.DeepCopyObject().(client.ObjectList)
if err := c.List(ctx, objectList, listOpts...); err != nil {
return nil
}

objects, err := meta.ExtractList(objectList)
if err != nil {
return nil
}

results := []ctrl.Request{}
for _, obj := range objects {
// Note: We don't check if the type cast succeeds as all items in an client.ObjectList
// are client.Objects.
o := obj.(client.Object)
results = append(results, ctrl.Request{
NamespacedName: client.ObjectKey{Namespace: o.GetNamespace(), Name: o.GetName()},
})
}
return results
}, nil
}

// isAPINamespaced detects if a GroupVersionKind is namespaced.
func isAPINamespaced(gk schema.GroupVersionKind, restmapper meta.RESTMapper) (bool, error) {
restMapping, err := restmapper.RESTMapping(schema.GroupKind{Group: gk.Group, Kind: gk.Kind})
Expand Down
2 changes: 1 addition & 1 deletion util/util_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -739,7 +739,7 @@ func TestClusterToObjectsMapper(t *testing.T) {
restMapper.Add(gvk, meta.RESTScopeNamespace)

client := fake.NewClientBuilder().WithObjects(tc.objects...).WithRESTMapper(restMapper).Build()
f, err := ClusterToObjectsMapper(client, tc.input, scheme)
f, err := ClusterToTypedObjectsMapper(client, tc.input, scheme)
g.Expect(err != nil, err).To(Equal(tc.expectError))
g.Expect(f(ctx, cluster)).To(ConsistOf(tc.output))
}
Expand Down

0 comments on commit fd0dc60

Please sign in to comment.