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 }