sigs.k8s.io/cluster-api-provider-azure@v1.17.0/exp/controllers/helpers.go (about)

     1  /*
     2  Copyright 2020 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package controllers
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  
    23  	"github.com/go-logr/logr"
    24  	"github.com/google/go-cmp/cmp"
    25  	"github.com/pkg/errors"
    26  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    27  	"k8s.io/apimachinery/pkg/runtime"
    28  	"k8s.io/apimachinery/pkg/runtime/schema"
    29  	"k8s.io/apimachinery/pkg/types"
    30  	infrav1 "sigs.k8s.io/cluster-api-provider-azure/api/v1beta1"
    31  	"sigs.k8s.io/cluster-api-provider-azure/controllers"
    32  	infrav1exp "sigs.k8s.io/cluster-api-provider-azure/exp/api/v1beta1"
    33  	"sigs.k8s.io/cluster-api-provider-azure/util/reconciler"
    34  	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
    35  	expv1 "sigs.k8s.io/cluster-api/exp/api/v1beta1"
    36  	"sigs.k8s.io/cluster-api/util"
    37  	ctrl "sigs.k8s.io/controller-runtime"
    38  	"sigs.k8s.io/controller-runtime/pkg/client"
    39  	"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
    40  	"sigs.k8s.io/controller-runtime/pkg/event"
    41  	"sigs.k8s.io/controller-runtime/pkg/handler"
    42  	"sigs.k8s.io/controller-runtime/pkg/predicate"
    43  	"sigs.k8s.io/controller-runtime/pkg/reconcile"
    44  )
    45  
    46  // AzureClusterToAzureMachinePoolsMapper creates a mapping handler to transform AzureClusters into AzureMachinePools. The transform
    47  // requires AzureCluster to map to the owning Cluster, then from the Cluster, collect the MachinePools belonging to the cluster,
    48  // then finally projecting the infrastructure reference to the AzureMachinePool.
    49  func AzureClusterToAzureMachinePoolsMapper(ctx context.Context, c client.Client, scheme *runtime.Scheme, log logr.Logger) (handler.MapFunc, error) {
    50  	gvk, err := apiutil.GVKForObject(new(infrav1exp.AzureMachinePool), scheme)
    51  	if err != nil {
    52  		return nil, errors.Wrap(err, "failed to find GVK for AzureMachinePool")
    53  	}
    54  
    55  	return func(ctx context.Context, o client.Object) []ctrl.Request {
    56  		ctx, cancel := context.WithTimeout(ctx, reconciler.DefaultMappingTimeout)
    57  		defer cancel()
    58  
    59  		azCluster, ok := o.(*infrav1.AzureCluster)
    60  		if !ok {
    61  			log.Error(errors.Errorf("expected an AzureCluster, got %T instead", o), "failed to map AzureCluster")
    62  			return nil
    63  		}
    64  
    65  		log := log.WithValues("AzureCluster", azCluster.Name, "Namespace", azCluster.Namespace)
    66  
    67  		// Don't handle deleted AzureClusters
    68  		if !azCluster.ObjectMeta.DeletionTimestamp.IsZero() {
    69  			log.V(4).Info("AzureCluster has a deletion timestamp, skipping mapping.")
    70  			return nil
    71  		}
    72  
    73  		clusterName, ok := controllers.GetOwnerClusterName(azCluster.ObjectMeta)
    74  		if !ok {
    75  			log.V(4).Info("unable to get the owner cluster")
    76  			return nil
    77  		}
    78  
    79  		machineList := &expv1.MachinePoolList{}
    80  		machineList.SetGroupVersionKind(gvk)
    81  		// list all of the requested objects within the cluster namespace with the cluster name label
    82  		if err := c.List(ctx, machineList, client.InNamespace(azCluster.Namespace), client.MatchingLabels{clusterv1.ClusterNameLabel: clusterName}); err != nil {
    83  			log.V(4).Info(fmt.Sprintf("unable to list machine pools in cluster %s", clusterName))
    84  			return nil
    85  		}
    86  
    87  		mapFunc := MachinePoolToInfrastructureMapFunc(gvk, log)
    88  		var results []ctrl.Request
    89  		for _, machine := range machineList.Items {
    90  			m := machine
    91  			azureMachines := mapFunc(ctx, &m)
    92  			results = append(results, azureMachines...)
    93  		}
    94  
    95  		return results
    96  	}, nil
    97  }
    98  
    99  // AzureManagedControlPlaneToAzureMachinePoolsMapper creates a mapping handler to transform AzureManagedControlPlanes into AzureMachinePools. The transform
   100  // requires AzureManagedControlPlane to map to the owning Cluster, then from the Cluster, collect the MachinePools belonging to the cluster,
   101  // then finally projecting the infrastructure reference to the AzureMachinePool.
   102  func AzureManagedControlPlaneToAzureMachinePoolsMapper(ctx context.Context, c client.Client, scheme *runtime.Scheme, log logr.Logger) (handler.MapFunc, error) {
   103  	gvk, err := apiutil.GVKForObject(new(infrav1exp.AzureMachinePool), scheme)
   104  	if err != nil {
   105  		return nil, errors.Wrap(err, "failed to find GVK for AzureMachinePool")
   106  	}
   107  
   108  	return func(ctx context.Context, o client.Object) []ctrl.Request {
   109  		ctx, cancel := context.WithTimeout(ctx, reconciler.DefaultMappingTimeout)
   110  		defer cancel()
   111  
   112  		azControlPlane, ok := o.(*infrav1.AzureManagedControlPlane)
   113  		if !ok {
   114  			log.Error(errors.Errorf("expected an AzureManagedControlPlane, got %T instead", o), "failed to map AzureManagedControlPlane")
   115  			return nil
   116  		}
   117  
   118  		log := log.WithValues("AzureManagedControlPlane", azControlPlane.Name, "Namespace", azControlPlane.Namespace)
   119  
   120  		// Don't handle deleted AzureManagedControlPlane
   121  		if !azControlPlane.ObjectMeta.DeletionTimestamp.IsZero() {
   122  			log.V(4).Info("AzureManagedControlPlane has a deletion timestamp, skipping mapping.")
   123  			return nil
   124  		}
   125  
   126  		clusterName, ok := controllers.GetOwnerClusterName(azControlPlane.ObjectMeta)
   127  		if !ok {
   128  			log.V(4).Info("unable to get the owner cluster")
   129  			return nil
   130  		}
   131  
   132  		machineList := &expv1.MachinePoolList{}
   133  		machineList.SetGroupVersionKind(gvk)
   134  		// list all of the requested objects within the cluster namespace with the cluster name label
   135  		if err := c.List(ctx, machineList, client.InNamespace(azControlPlane.Namespace), client.MatchingLabels{clusterv1.ClusterNameLabel: clusterName}); err != nil {
   136  			log.V(4).Info(fmt.Sprintf("unable to list machine pools in cluster %s", clusterName))
   137  			return nil
   138  		}
   139  
   140  		mapFunc := MachinePoolToInfrastructureMapFunc(gvk, log)
   141  		var results []ctrl.Request
   142  		for _, machine := range machineList.Items {
   143  			m := machine
   144  			azureMachines := mapFunc(ctx, &m)
   145  			results = append(results, azureMachines...)
   146  		}
   147  
   148  		return results
   149  	}, nil
   150  }
   151  
   152  // AzureMachinePoolMachineMapper creates a mapping handler to transform AzureMachinePoolMachine to AzureMachinePools.
   153  func AzureMachinePoolMachineMapper(scheme *runtime.Scheme, log logr.Logger) handler.MapFunc {
   154  	return func(ctx context.Context, o client.Object) []ctrl.Request {
   155  		gvk, err := apiutil.GVKForObject(new(infrav1exp.AzureMachinePool), scheme)
   156  		if err != nil {
   157  			log.Error(errors.WithStack(err), "failed to find GVK for AzureMachinePool")
   158  			return nil
   159  		}
   160  
   161  		azureMachinePoolMachine, ok := o.(*infrav1exp.AzureMachinePoolMachine)
   162  		if !ok {
   163  			log.Error(errors.Errorf("expected an AzureCluster, got %T instead", o), "failed to map AzureMachinePoolMachine")
   164  			return nil
   165  		}
   166  
   167  		log := log.WithValues("AzureMachinePoolMachine", azureMachinePoolMachine.Name, "Namespace", azureMachinePoolMachine.Namespace)
   168  		for _, ref := range azureMachinePoolMachine.OwnerReferences {
   169  			if ref.Kind != gvk.Kind {
   170  				continue
   171  			}
   172  
   173  			gv, err := schema.ParseGroupVersion(ref.APIVersion)
   174  			if err != nil {
   175  				log.Error(errors.WithStack(err), "unable to parse group version", "APIVersion", ref.APIVersion)
   176  				return nil
   177  			}
   178  
   179  			if gv.Group == gvk.Group {
   180  				return []ctrl.Request{
   181  					{
   182  						NamespacedName: types.NamespacedName{
   183  							Name:      ref.Name,
   184  							Namespace: azureMachinePoolMachine.Namespace,
   185  						},
   186  					},
   187  				}
   188  			}
   189  		}
   190  
   191  		return nil
   192  	}
   193  }
   194  
   195  // MachinePoolToInfrastructureMapFunc returns a handler.MapFunc that watches for
   196  // MachinePool events and returns reconciliation requests for an infrastructure provider object.
   197  func MachinePoolToInfrastructureMapFunc(gvk schema.GroupVersionKind, log logr.Logger) handler.MapFunc {
   198  	return func(ctx context.Context, o client.Object) []reconcile.Request {
   199  		m, ok := o.(*expv1.MachinePool)
   200  		if !ok {
   201  			log.V(4).Info("attempt to map incorrect type", "type", fmt.Sprintf("%T", o))
   202  			return nil
   203  		}
   204  
   205  		gk := gvk.GroupKind()
   206  		ref := m.Spec.Template.Spec.InfrastructureRef
   207  		// Return early if the GroupKind doesn't match what we expect.
   208  		infraGK := ref.GroupVersionKind().GroupKind()
   209  		if gk != infraGK {
   210  			log.V(4).Info("gk does not match", "gk", gk, "infraGK", infraGK)
   211  			return nil
   212  		}
   213  
   214  		return []reconcile.Request{
   215  			{
   216  				NamespacedName: client.ObjectKey{
   217  					Namespace: m.Namespace,
   218  					Name:      ref.Name,
   219  				},
   220  			},
   221  		}
   222  	}
   223  }
   224  
   225  // AzureClusterToAzureMachinePoolsFunc is a handler.MapFunc to be used to enqueue
   226  // requests for reconciliation of AzureMachinePools.
   227  func AzureClusterToAzureMachinePoolsFunc(ctx context.Context, c client.Client, log logr.Logger) handler.MapFunc {
   228  	return func(ctx context.Context, o client.Object) []reconcile.Request {
   229  		ctx, cancel := context.WithTimeout(ctx, reconciler.DefaultMappingTimeout)
   230  		defer cancel()
   231  
   232  		ac, ok := o.(*infrav1.AzureCluster)
   233  		if !ok {
   234  			log.Error(errors.Errorf("expected a AzureCluster but got a %T", o), "failed to get AzureCluster")
   235  			return nil
   236  		}
   237  		logWithValues := log.WithValues("AzureCluster", ac.Name, "Namespace", ac.Namespace)
   238  
   239  		cluster, err := util.GetOwnerCluster(ctx, c, ac.ObjectMeta)
   240  		switch {
   241  		case apierrors.IsNotFound(err) || cluster == nil:
   242  			logWithValues.V(4).Info("owning cluster not found")
   243  			return nil
   244  		case err != nil:
   245  			logWithValues.Error(err, "failed to get owning cluster")
   246  			return nil
   247  		}
   248  
   249  		labels := map[string]string{clusterv1.ClusterNameLabel: cluster.Name}
   250  		ampl := &infrav1exp.AzureMachinePoolList{}
   251  		if err := c.List(ctx, ampl, client.InNamespace(ac.Namespace), client.MatchingLabels(labels)); err != nil {
   252  			logWithValues.Error(err, "failed to list AzureMachinePools")
   253  			return nil
   254  		}
   255  
   256  		var result []reconcile.Request
   257  		for _, m := range ampl.Items {
   258  			result = append(result, reconcile.Request{
   259  				NamespacedName: client.ObjectKey{
   260  					Namespace: m.Namespace,
   261  					Name:      m.Name,
   262  				},
   263  			})
   264  		}
   265  
   266  		return result
   267  	}
   268  }
   269  
   270  // AzureMachinePoolToAzureMachinePoolMachines maps an AzureMachinePool to its child AzureMachinePoolMachines through
   271  // Cluster and MachinePool labels.
   272  func AzureMachinePoolToAzureMachinePoolMachines(ctx context.Context, c client.Client, log logr.Logger) handler.MapFunc {
   273  	return func(ctx context.Context, o client.Object) []reconcile.Request {
   274  		ctx, cancel := context.WithTimeout(ctx, reconciler.DefaultMappingTimeout)
   275  		defer cancel()
   276  
   277  		amp, ok := o.(*infrav1exp.AzureMachinePool)
   278  		if !ok {
   279  			log.Error(errors.Errorf("expected a AzureMachinePool but got a %T", o), "failed to get AzureMachinePool")
   280  			return nil
   281  		}
   282  		logWithValues := log.WithValues("AzureMachinePool", amp.Name, "Namespace", amp.Namespace)
   283  
   284  		labels := map[string]string{
   285  			clusterv1.ClusterNameLabel:      amp.Labels[clusterv1.ClusterNameLabel],
   286  			infrav1exp.MachinePoolNameLabel: amp.Name,
   287  		}
   288  		ampml := &infrav1exp.AzureMachinePoolMachineList{}
   289  		if err := c.List(ctx, ampml, client.InNamespace(amp.Namespace), client.MatchingLabels(labels)); err != nil {
   290  			logWithValues.Error(err, "failed to list AzureMachinePoolMachines")
   291  			return nil
   292  		}
   293  
   294  		logWithValues.Info("mapping from AzureMachinePool", "count", len(ampml.Items))
   295  		var result []reconcile.Request
   296  		for _, m := range ampml.Items {
   297  			result = append(result, reconcile.Request{
   298  				NamespacedName: client.ObjectKey{
   299  					Namespace: m.Namespace,
   300  					Name:      m.Name,
   301  				},
   302  			})
   303  		}
   304  
   305  		return result
   306  	}
   307  }
   308  
   309  // MachinePoolModelHasChanged predicates any events based on changes to the AzureMachinePool model.
   310  func MachinePoolModelHasChanged(logger logr.Logger) predicate.Funcs {
   311  	return predicate.Funcs{
   312  		UpdateFunc: func(e event.UpdateEvent) bool {
   313  			log := logger.WithValues("predicate", "MachinePoolModelHasChanged", "eventType", "update")
   314  
   315  			oldAmp, ok := e.ObjectOld.(*infrav1exp.AzureMachinePool)
   316  			if !ok {
   317  				log.V(4).Info("Expected AzureMachinePool", "type", e.ObjectOld.GetObjectKind().GroupVersionKind().String())
   318  				return false
   319  			}
   320  			log = log.WithValues("namespace", oldAmp.Namespace, "azureMachinePool", oldAmp.Name)
   321  
   322  			newAmp := e.ObjectNew.(*infrav1exp.AzureMachinePool)
   323  
   324  			// if any of these are not equal, run the update
   325  			shouldUpdate := !cmp.Equal(oldAmp.Spec.Identity, newAmp.Spec.Identity) ||
   326  				!cmp.Equal(oldAmp.Spec.Template, newAmp.Spec.Template) ||
   327  				!cmp.Equal(oldAmp.Spec.UserAssignedIdentities, newAmp.Spec.UserAssignedIdentities) ||
   328  				!cmp.Equal(oldAmp.Status.ProvisioningState, newAmp.Status.ProvisioningState)
   329  
   330  			// if shouldUpdate {
   331  			log.Info("machine pool predicate", "shouldUpdate", shouldUpdate)
   332  			//}
   333  			return shouldUpdate
   334  		},
   335  		CreateFunc:  func(e event.CreateEvent) bool { return false },
   336  		DeleteFunc:  func(e event.DeleteEvent) bool { return false },
   337  		GenericFunc: func(e event.GenericEvent) bool { return false },
   338  	}
   339  }
   340  
   341  // MachinePoolMachineHasStateOrVersionChange predicates any events based on changes to the AzureMachinePoolMachine status
   342  // relevant for the AzureMachinePool controller.
   343  func MachinePoolMachineHasStateOrVersionChange(logger logr.Logger) predicate.Funcs {
   344  	return predicate.Funcs{
   345  		UpdateFunc: func(e event.UpdateEvent) bool {
   346  			log := logger.WithValues("predicate", "MachinePoolModelHasChanged", "eventType", "update")
   347  
   348  			oldAmp, ok := e.ObjectOld.(*infrav1exp.AzureMachinePoolMachine)
   349  			if !ok {
   350  				log.V(4).Info("Expected AzureMachinePoolMachine", "type", e.ObjectOld.GetObjectKind().GroupVersionKind().String())
   351  				return false
   352  			}
   353  			log = log.WithValues("namespace", oldAmp.Namespace, "machinePoolMachine", oldAmp.Name)
   354  
   355  			newAmp := e.ObjectNew.(*infrav1exp.AzureMachinePoolMachine)
   356  
   357  			// if any of these are not equal, run the update
   358  			shouldUpdate := oldAmp.Status.LatestModelApplied != newAmp.Status.LatestModelApplied ||
   359  				oldAmp.Status.Version != newAmp.Status.Version ||
   360  				oldAmp.Status.ProvisioningState != newAmp.Status.ProvisioningState ||
   361  				oldAmp.Status.Ready != newAmp.Status.Ready
   362  
   363  			if shouldUpdate {
   364  				log.Info("machine pool machine predicate", "shouldUpdate", shouldUpdate)
   365  			}
   366  			return shouldUpdate
   367  		},
   368  		CreateFunc:  func(e event.CreateEvent) bool { return false },
   369  		DeleteFunc:  func(e event.DeleteEvent) bool { return false },
   370  		GenericFunc: func(e event.GenericEvent) bool { return false },
   371  	}
   372  }
   373  
   374  // BootstrapConfigToInfrastructureMapFunc returns a handler.ToRequestsFunc that watches for <Bootstrap>Config events and returns.
   375  func BootstrapConfigToInfrastructureMapFunc(ctx context.Context, c client.Client, log logr.Logger) handler.MapFunc {
   376  	return func(ctx context.Context, o client.Object) []reconcile.Request {
   377  		ctx, cancel := context.WithTimeout(ctx, reconciler.DefaultMappingTimeout)
   378  		defer cancel()
   379  
   380  		mpKey := client.ObjectKey{
   381  			Namespace: o.GetNamespace(),
   382  			Name:      o.GetName(),
   383  		}
   384  
   385  		// fetch MachinePool to get reference
   386  		mp := &expv1.MachinePool{}
   387  		if err := c.Get(ctx, mpKey, mp); err != nil {
   388  			if !apierrors.IsNotFound(err) {
   389  				log.Error(err, "failed to fetch MachinePool to validate Bootstrap.ConfigRef")
   390  			}
   391  			return []reconcile.Request{}
   392  		}
   393  
   394  		ref := mp.Spec.Template.Spec.Bootstrap.ConfigRef
   395  		if ref == nil {
   396  			log.V(4).Info("fetched MachinePool has no Bootstrap.ConfigRef")
   397  			return []reconcile.Request{}
   398  		}
   399  		sameKind := ref.Kind != o.GetObjectKind().GroupVersionKind().Kind
   400  		sameName := ref.Name == o.GetName()
   401  		sameNamespace := ref.Namespace == o.GetNamespace()
   402  		if !sameKind || !sameName || !sameNamespace {
   403  			log.V(4).Info("Bootstrap.ConfigRef does not match",
   404  				"sameKind", sameKind,
   405  				"ref kind", ref.Kind,
   406  				"other kind", o.GetObjectKind().GroupVersionKind().Kind,
   407  				"sameName", sameName,
   408  				"sameNamespace", sameNamespace,
   409  			)
   410  			return []reconcile.Request{}
   411  		}
   412  
   413  		key := client.ObjectKeyFromObject(o)
   414  		log.V(4).Info("adding KubeadmConfig to watch", "key", key)
   415  
   416  		return []reconcile.Request{
   417  			{
   418  				NamespacedName: key,
   419  			},
   420  		}
   421  	}
   422  }