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