diff --git a/apis/metal/v1alpha4/switchconfig_webhook.go b/apis/metal/v1alpha4/switchconfig_webhook.go index 961fea35..52b52ca5 100644 --- a/apis/metal/v1alpha4/switchconfig_webhook.go +++ b/apis/metal/v1alpha4/switchconfig_webhook.go @@ -4,6 +4,7 @@ package v1alpha4 import ( + "github.com/pkg/errors" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -41,14 +42,24 @@ func (in *SwitchConfig) Default() { var _ webhook.Validator = &SwitchConfig{} // ValidateCreate implements webhook.Validator so a webhook will be registered for the type. -func (in *SwitchConfig) ValidateCreate() (warnings admission.Warnings, err error) { - // todo: validate if label(s) with switch type(s) exist, if type != all in.Spec.Switches is not nil and types in labels match switches selector - return +func (in *SwitchConfig) ValidateCreate() (admission.Warnings, error) { + if in.Spec.IPAM.CarrierSubnets.FieldSelector != nil { + return nil, errors.New("field selector is not applicable for carrier subnets") + } + if in.Spec.IPAM.LoopbackSubnets.FieldSelector != nil { + return nil, errors.New("field selector is not applicable for loopback subnets") + } + return nil, nil } // ValidateUpdate implements webhook.Validator so a webhook will be registered for the type. func (in *SwitchConfig) ValidateUpdate(_ runtime.Object) (warnings admission.Warnings, err error) { - // todo: validate if label(s) with switch type(s) exist, if type != all in.Spec.Switches is not nil and types in labels match switches selector + if in.Spec.IPAM.CarrierSubnets.FieldSelector != nil { + return nil, errors.New("field selector is not applicable for carrier subnets") + } + if in.Spec.IPAM.LoopbackSubnets.FieldSelector != nil { + return nil, errors.New("field selector is not applicable for loopback subnets") + } return } diff --git a/controllers/switch/ipam_controller.go b/controllers/switch/ipam_controller.go index e4d71f08..ee13c56f 100644 --- a/controllers/switch/ipam_controller.go +++ b/controllers/switch/ipam_controller.go @@ -21,11 +21,16 @@ import ( "k8s.io/apimachinery/pkg/selection" "k8s.io/client-go/tools/record" "k8s.io/client-go/tools/reference" + "k8s.io/client-go/util/workqueue" "k8s.io/utils/ptr" ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" + "sigs.k8s.io/controller-runtime/pkg/event" + "sigs.k8s.io/controller-runtime/pkg/handler" "sigs.k8s.io/controller-runtime/pkg/predicate" + "sigs.k8s.io/controller-runtime/pkg/reconcile" metalv1alpha4 "github.com/ironcore-dev/metal/apis/metal/v1alpha4" "github.com/ironcore-dev/metal/pkg/constants" @@ -161,14 +166,86 @@ func (r *IPAMReconciler) SetupWithManager(mgr ctrl.Manager) error { } return ctrl.NewControllerManagedBy(mgr). - For(&metalv1alpha4.NetworkSwitch{}). + For(&metalv1alpha4.NetworkSwitch{}, builder.WithPredicates(labelPredicate)). WithOptions(controller.Options{ RecoverPanic: ptr.To(true), }). - WithEventFilter(predicate.And(labelPredicate)). + // watches for ipam.Subnet objects are required to trigger reconciliation + // in case related ipam.Subnet object defining switch's loopbacks or carrier + // subnet was updated or being deleted + Watches(&ipamv1alpha1.Subnet{}, handler.Funcs{ + UpdateFunc: r.handleSubnetUpdateEvent, + DeleteFunc: r.handleSubnetDeleteEvent, + }). Complete(r) } +func (r *IPAMReconciler) handleSubnetUpdateEvent( + ctx context.Context, + e event.UpdateEvent, + q workqueue.RateLimitingInterface, +) { + r.Log.WithValues("handler", "SubnetUpdateEvent") + subnet, ok := e.ObjectNew.(*ipamv1alpha1.Subnet) + if !ok { + return + } + if subnet.Status.State != ipamv1alpha1.CFinishedSubnetState { + return + } + switches := r.switchesToEnqueueOnSubnetEvent(ctx, subnet) + if switches == nil { + return + } + for _, item := range switches.Items { + q.Add(reconcile.Request{NamespacedName: item.NamespacedName()}) + } +} + +func (r *IPAMReconciler) handleSubnetDeleteEvent(ctx context.Context, e event.DeleteEvent, q workqueue.RateLimitingInterface) { + r.Log.WithValues("handler", "SubnetDeleteEvent") + subnet, ok := e.Object.(*ipamv1alpha1.Subnet) + if !ok { + return + } + if subnet.Status.State != ipamv1alpha1.CFinishedSubnetState { + return + } + switches := r.switchesToEnqueueOnSubnetEvent(ctx, subnet) + if switches == nil { + return + } + for _, item := range switches.Items { + q.Add(reconcile.Request{NamespacedName: item.NamespacedName()}) + } +} + +func (r *IPAMReconciler) switchesToEnqueueOnSubnetEvent( + ctx context.Context, + subnet *ipamv1alpha1.Subnet, +) *metalv1alpha4.NetworkSwitchList { + switches := &metalv1alpha4.NetworkSwitchList{} + if err := r.List(ctx, switches); err != nil { + r.Log.Error(err, "failed to list NetworkSwitch objects") + return nil + } + switchConfigs := &metalv1alpha4.SwitchConfigList{} + if err := r.List(ctx, switchConfigs); err != nil { + r.Log.Error(err, "failed to list SwitchConfig objects") + return nil + } + for _, item := range switchConfigs.Items { + switch { + case switchespkg.IPAMSelectorMatchLabels(nil, item.Spec.IPAM.CarrierSubnets, subnet.Labels): + return switches + case switchespkg.IPAMSelectorMatchLabels(nil, item.Spec.IPAM.LoopbackSubnets, subnet.Labels): + return switches + } + } + + return nil +} + func processLoopbacks( ctx context.Context, obj *metalv1alpha4.NetworkSwitch, @@ -184,11 +261,12 @@ func processLoopbacks( if err := svc.ListIPAMObjects(ctx, obj, params, loopbacks); err != nil { return err } - targetAF := cfg.Spec.IPAM.AddressFamily - afEnabledFlag := existingLoopbacksAddressFamilies(loopbacks, targetAF) - if !switchespkg.AddressFamiliesMatchConfig(true, targetAF.GetIPv6(), afEnabledFlag) { + expectedAF := cfg.Spec.IPAM.AddressFamily + existingAFFlag := existingLoopbacksAddressFamilies(loopbacks, expectedAF) + if missedAFFlag, ok := switchespkg.AddressFamiliesMatchConfig( + true, expectedAF.GetIPv6(), existingAFFlag); !ok { svc.Log.Info("discrepancy between required and existing IP objects in part of address families") - created, err := createIPs(ctx, obj, svc, afEnabledFlag) + created, err := createIPs(ctx, obj, svc, missedAFFlag) if err != nil { return err } @@ -210,12 +288,8 @@ func existingLoopbacksAddressFamilies( if !af.GetIPv6() && item.Status.Reserved.Net.Is6() { continue } - switch { - case item.Status.Reserved.Net.Is4(): - afEnabledFlag = afEnabledFlag | 1 - case item.Status.Reserved.Net.Is6(): - afEnabledFlag = afEnabledFlag | 2 - } + afEnabledFlag = switchespkg.ComputeAFFlag( + item.Status.Reserved.Net.Is4(), item.Status.Reserved.Net.Is6(), afEnabledFlag) } return afEnabledFlag } @@ -311,11 +385,12 @@ func processSouthSubnets( if err := svc.ListIPAMObjects(ctx, obj, params, subnets); err != nil { return err } - targetAF := cfg.Spec.IPAM.AddressFamily - afEnabledFlag := existingSouthSubnetsAddressFamilies(obj, subnets, targetAF) - if !switchespkg.AddressFamiliesMatchConfig(targetAF.GetIPv4(), targetAF.GetIPv6(), afEnabledFlag) { + expectedAF := cfg.Spec.IPAM.AddressFamily + existingAFFlag := existingSouthSubnetsAddressFamilies(obj, subnets, expectedAF) + if missedAFFlag, ok := switchespkg.AddressFamiliesMatchConfig( + expectedAF.GetIPv4(), expectedAF.GetIPv6(), existingAFFlag); !ok { svc.Log.Info("discrepancy between required and existing Subnet objects in part of address families") - created, err := createSubnets(ctx, obj, svc, afEnabledFlag) + created, err := createSubnets(ctx, obj, svc, missedAFFlag) if err != nil { return err } @@ -342,12 +417,8 @@ func existingSouthSubnetsAddressFamilies( if requiredCapacity.Cmp(item.Status.Capacity) >= 0 { continue } - switch item.Status.Type { - case ipamv1alpha1.CIPv4SubnetType: - afEnabledFlag = afEnabledFlag | 1 - case ipamv1alpha1.CIPv6SubnetType: - afEnabledFlag = afEnabledFlag | 2 - } + afEnabledFlag = switchespkg.ComputeAFFlag( + item.Status.Reserved.IsIPv4(), item.Status.Reserved.IsIPv6(), afEnabledFlag) } return afEnabledFlag } diff --git a/controllers/switch/iptrack_controller.go b/controllers/switch/iptrack_controller.go deleted file mode 100644 index 58b3d8fd..00000000 --- a/controllers/switch/iptrack_controller.go +++ /dev/null @@ -1,101 +0,0 @@ -// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and IronCore contributors -// SPDX-License-Identifier: Apache-2.0 - -package v1beta1 - -import ( - "context" - "fmt" - - "github.com/go-logr/logr" - ipamv1alpha1 "github.com/onmetal/ipam/api/v1alpha1" - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - "k8s.io/client-go/tools/record" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/event" - "sigs.k8s.io/controller-runtime/pkg/predicate" - - metalv1alpha4 "github.com/ironcore-dev/metal/apis/metal/v1alpha4" - "github.com/ironcore-dev/metal/pkg/constants" -) - -const ( - IPEventReason = "IpUpdated" - IPEventMessage = "IP object %s changed" -) - -type IPTracker struct { - client.Client - - Log logr.Logger - Scheme *runtime.Scheme - Recorder record.EventRecorder -} - -// +kubebuilder:rbac:groups=metal.ironcore.dev,resources=networkswitches,verbs=get;list;watch -// +kubebuilder:rbac:groups=ipam.onmetal.de,resources=ips,verbs=get;list;watch -// +kubebuilder:rbac:groups=ipam.onmetal.de,resources=ips/status,verbs=get -// +kubebuilder:rbac:groups="",resources=events,verbs=create;patch - -func (r *IPTracker) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - obj := &ipamv1alpha1.IP{} - if err := r.Get(ctx, req.NamespacedName, obj); err != nil { - return ctrl.Result{}, client.IgnoreNotFound(err) - } - owner := obj.Labels[constants.IPAMObjectOwnerLabel] - networkSwitch := &metalv1alpha4.NetworkSwitch{} - if err := r.Get(ctx, types.NamespacedName{Name: owner, Namespace: req.Namespace}, networkSwitch); err != nil { - return ctrl.Result{}, client.IgnoreNotFound(err) - } - r.Recorder.Event(networkSwitch, v1.EventTypeNormal, IPEventReason, fmt.Sprintf(IPEventMessage, req.Name)) - return ctrl.Result{}, nil -} - -func (r *IPTracker) SetupWithManager(mgr ctrl.Manager) error { - labelPredicate, err := predicate.LabelSelectorPredicate(metav1.LabelSelector{ - MatchLabels: map[string]string{constants.IPAMObjectPurposeLabel: constants.IPAMLoopbackPurpose}, - MatchExpressions: []metav1.LabelSelectorRequirement{{ - Key: constants.IPAMObjectOwnerLabel, - Operator: metav1.LabelSelectorOpExists, - }}, - }) - if err != nil { - r.Log.Error(err, "failed to setup predicates") - } - eventPredicate := r.setupPredicates() - - return ctrl.NewControllerManagedBy(mgr). - For(&ipamv1alpha1.IP{}). - WithEventFilter(predicate.And(labelPredicate, eventPredicate)). - Complete(r) -} - -func (r *IPTracker) setupPredicates() predicate.Predicate { - return predicate.Funcs{ - DeleteFunc: r.deleteHandler, - UpdateFunc: r.updateHandler, - } -} - -func (r *IPTracker) updateHandler(e event.UpdateEvent) bool { - ip, ok := e.ObjectNew.(*ipamv1alpha1.IP) - if !ok { - return false - } - return ip.Status.State == ipamv1alpha1.CFinishedIPState -} - -func (r *IPTracker) deleteHandler(e event.DeleteEvent) bool { - ip, ok := e.Object.(*ipamv1alpha1.IP) - if !ok { - return false - } - if e.DeleteStateUnknown { - return true - } - return ip.Status.State == ipamv1alpha1.CFinishedIPState -} diff --git a/controllers/switch/subnettrack_controller.go b/controllers/switch/subnettrack_controller.go deleted file mode 100644 index 7647ae90..00000000 --- a/controllers/switch/subnettrack_controller.go +++ /dev/null @@ -1,101 +0,0 @@ -// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and IronCore contributors -// SPDX-License-Identifier: Apache-2.0 - -package v1beta1 - -import ( - "context" - "fmt" - - "github.com/go-logr/logr" - ipamv1alpha1 "github.com/onmetal/ipam/api/v1alpha1" - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - "k8s.io/client-go/tools/record" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/event" - "sigs.k8s.io/controller-runtime/pkg/predicate" - - metalv1alpha4 "github.com/ironcore-dev/metal/apis/metal/v1alpha4" - "github.com/ironcore-dev/metal/pkg/constants" -) - -const ( - SubnetEventReason = "IpUpdated" - SubnetEventMessage = "IP object %s changed" -) - -type SubnetTracker struct { - client.Client - - Log logr.Logger - Scheme *runtime.Scheme - Recorder record.EventRecorder -} - -// +kubebuilder:rbac:groups=metal.ironcore.dev,resources=networkswitches,verbs=get;list;watch -// +kubebuilder:rbac:groups=ipam.onmetal.de,resources=subnets,verbs=get;list;watch -// +kubebuilder:rbac:groups=ipam.onmetal.de,resources=subnets/status,verbs=get -// +kubebuilder:rbac:groups="",resources=events,verbs=create;patch - -func (r *SubnetTracker) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - obj := &ipamv1alpha1.Subnet{} - if err := r.Get(ctx, req.NamespacedName, obj); err != nil { - return ctrl.Result{}, client.IgnoreNotFound(err) - } - owner := obj.Labels[constants.IPAMObjectOwnerLabel] - networkSwitch := &metalv1alpha4.NetworkSwitch{} - if err := r.Get(ctx, types.NamespacedName{Name: owner, Namespace: req.Namespace}, networkSwitch); err != nil { - return ctrl.Result{}, client.IgnoreNotFound(err) - } - r.Recorder.Event(networkSwitch, v1.EventTypeNormal, SubnetEventReason, fmt.Sprintf(SubnetEventMessage, req.Name)) - return ctrl.Result{}, nil -} - -func (r *SubnetTracker) SetupWithManager(mgr ctrl.Manager) error { - labelPredicate, err := predicate.LabelSelectorPredicate(metav1.LabelSelector{ - MatchLabels: map[string]string{constants.IPAMObjectPurposeLabel: constants.IPAMSouthSubnetPurpose}, - MatchExpressions: []metav1.LabelSelectorRequirement{{ - Key: constants.IPAMObjectOwnerLabel, - Operator: metav1.LabelSelectorOpExists, - }}, - }) - if err != nil { - r.Log.Error(err, "failed to setup predicates") - } - eventPredicate := r.setupPredicates() - - return ctrl.NewControllerManagedBy(mgr). - For(&ipamv1alpha1.Subnet{}). - WithEventFilter(predicate.And(labelPredicate, eventPredicate)). - Complete(r) -} - -func (r *SubnetTracker) setupPredicates() predicate.Predicate { - return predicate.Funcs{ - DeleteFunc: r.deleteHandler, - UpdateFunc: r.updateHandler, - } -} - -func (r *SubnetTracker) updateHandler(e event.UpdateEvent) bool { - subnet, ok := e.ObjectNew.(*ipamv1alpha1.Subnet) - if !ok { - return false - } - return subnet.Status.State == ipamv1alpha1.CFinishedSubnetState -} - -func (r *SubnetTracker) deleteHandler(e event.DeleteEvent) bool { - subnet, ok := e.Object.(*ipamv1alpha1.Subnet) - if !ok { - return false - } - if e.DeleteStateUnknown { - return true - } - return subnet.Status.State == ipamv1alpha1.CFinishedSubnetState -} diff --git a/controllers/switch/suite_test.go b/controllers/switch/suite_test.go index 3da4f714..e0d2570a 100644 --- a/controllers/switch/suite_test.go +++ b/controllers/switch/suite_test.go @@ -142,18 +142,6 @@ var _ = BeforeSuite(func() { Log: ctrl.Log.WithName("controllers").WithName("switch-ipam-reconciler"), SwitchPortsIPAMDisabled: true, }).SetupWithManager(k8sManager)).NotTo(HaveOccurred()) - Expect((&IPTracker{ - Client: k8sManager.GetClient(), - Log: ctrl.Log.WithName("controllers").WithName("ip-tracker"), - Scheme: k8sManager.GetScheme(), - Recorder: k8sManager.GetEventRecorderFor("ip-tracker"), - }).SetupWithManager(k8sManager)).NotTo(HaveOccurred()) - Expect((&SubnetTracker{ - Client: k8sManager.GetClient(), - Log: ctrl.Log.WithName("controllers").WithName("subnet-tracker"), - Scheme: k8sManager.GetScheme(), - Recorder: k8sManager.GetEventRecorderFor("subnet-tracker"), - }).SetupWithManager(k8sManager)).NotTo(HaveOccurred()) Expect((&ipamctrl.SubnetReconciler{ Client: k8sManager.GetClient(), diff --git a/controllers/switch/switch_controller.go b/controllers/switch/switch_controller.go index b31b8c85..05bba1e6 100644 --- a/controllers/switch/switch_controller.go +++ b/controllers/switch/switch_controller.go @@ -8,6 +8,7 @@ import ( "reflect" "github.com/go-logr/logr" + ipamv1alpha1 "github.com/onmetal/ipam/api/v1alpha1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -17,6 +18,7 @@ import ( "k8s.io/client-go/util/workqueue" "k8s.io/utils/ptr" ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" "sigs.k8s.io/controller-runtime/pkg/event" @@ -153,26 +155,39 @@ func (r *SwitchReconciler) SetupWithManager(mgr ctrl.Manager) error { WithOptions(controller.Options{ RecoverPanic: ptr.To(true), }). - WithEventFilter(predicate.And(discoverObjectChangesPredicate)). - // watches for NetworkSwitch objects required, because switches are - // interconnected and changes in configuration of one object + // watches for NetworkSwitch objects are required, because switches + // are interconnected and changes in configuration of one object // might affect another objects. Watches(&metalv1alpha4.NetworkSwitch{}, &handler.Funcs{ UpdateFunc: r.handleSwitchUpdateEvent, DeleteFunc: r.handleSwitchDeleteEvent, - }). - // watches for SwitchConfig objects required, because + }, builder.WithPredicates(discoverObjectChangesPredicate)). + // watches for SwitchConfig objects are required, because // NetworkSwitch objects' configuration is based on config defined // in SwitchConfig objects, so changes must be tracked. Watches(&metalv1alpha4.SwitchConfig{}, &handler.Funcs{ UpdateFunc: r.handleSwitchConfigUpdateEvent, - }). - // watches for Inventory objects required, because + }, builder.WithPredicates(discoverObjectChangesPredicate)). + // watches for Inventory objects are required, because // changes in hardware, especially discovering new // neighbors connected to switch ports must be tracked. Watches(&metalv1alpha4.Inventory{}, &handler.Funcs{ CreateFunc: r.handleInventoryCreateEvent, UpdateFunc: r.handleInventoryUpdateEvent, + }, builder.WithPredicates(discoverObjectChangesPredicate)). + // watches for ipam.IP objects are required to trigger reconciliation + // in case related ipam.IP object defining switch's loopback address + // was updated or being deleted + Watches(&ipamv1alpha1.IP{}, &handler.Funcs{ + UpdateFunc: r.handleIPUpdateEvent, + DeleteFunc: r.handleIPDeleteEvent, + }). + // watches for ipam.Subnet objects are required to trigger reconciliation + // in case related ipam.Subnet object defining switch's south subnet + // was updated or being deleted + Watches(&ipamv1alpha1.Subnet{}, handler.Funcs{ + UpdateFunc: r.handleSubnetUpdateEvent, + DeleteFunc: r.handleSubnetDeleteEvent, }). Complete(r) } @@ -294,6 +309,124 @@ func (r *SwitchReconciler) handleInventoryUpdateEvent(_ context.Context, e event q.Add(reconcile.Request{NamespacedName: client.ObjectKeyFromObject(inventoryNew)}) } +func (r *SwitchReconciler) handleIPUpdateEvent(ctx context.Context, e event.UpdateEvent, q workqueue.RateLimitingInterface) { + r.Log.WithValues("handler", "IPUpdateEvent") + + ip, ok := e.ObjectNew.(*ipamv1alpha1.IP) + if !ok { + return + } + if ip.Status.State != ipamv1alpha1.CFinishedIPState { + return + } + switches := r.switchesToEnqueueOnIPAMEvent(ctx, ip) + if switches == nil { + return + } + for _, item := range switches.Items { + q.Add(reconcile.Request{NamespacedName: item.NamespacedName()}) + } +} + +func (r *SwitchReconciler) handleIPDeleteEvent(ctx context.Context, e event.DeleteEvent, q workqueue.RateLimitingInterface) { + r.Log.WithValues("handler", "IPDeleteEvent") + ip, ok := e.Object.(*ipamv1alpha1.IP) + if !ok { + return + } + if ip.Status.State != ipamv1alpha1.CFinishedIPState { + return + } + switches := r.switchesToEnqueueOnIPAMEvent(ctx, ip) + if switches == nil { + return + } + for _, item := range switches.Items { + q.Add(reconcile.Request{NamespacedName: item.NamespacedName()}) + } +} + +func (r *SwitchReconciler) handleSubnetUpdateEvent( + ctx context.Context, + e event.UpdateEvent, + q workqueue.RateLimitingInterface, +) { + r.Log.WithValues("handler", "SubnetUpdateEvent") + subnet, ok := e.ObjectNew.(*ipamv1alpha1.Subnet) + if !ok { + return + } + if subnet.Status.State != ipamv1alpha1.CFinishedSubnetState { + return + } + switches := r.switchesToEnqueueOnIPAMEvent(ctx, subnet) + if switches == nil { + return + } + for _, item := range switches.Items { + q.Add(reconcile.Request{NamespacedName: item.NamespacedName()}) + } +} + +func (r *SwitchReconciler) handleSubnetDeleteEvent(ctx context.Context, e event.DeleteEvent, q workqueue.RateLimitingInterface) { + r.Log.WithValues("handler", "SubnetDeleteEvent") + subnet, ok := e.Object.(*ipamv1alpha1.Subnet) + if !ok { + return + } + if subnet.Status.State != ipamv1alpha1.CFinishedSubnetState { + return + } + switches := r.switchesToEnqueueOnIPAMEvent(ctx, subnet) + if switches == nil { + return + } + for _, item := range switches.Items { + q.Add(reconcile.Request{NamespacedName: item.NamespacedName()}) + } +} + +func (r *SwitchReconciler) switchesToEnqueueOnIPAMEvent( + ctx context.Context, + obj client.Object, +) *metalv1alpha4.NetworkSwitchList { + _, isIP := obj.(*ipamv1alpha1.IP) + _, isSubnet := obj.(*ipamv1alpha1.Subnet) + + result := &metalv1alpha4.NetworkSwitchList{} + switches := &metalv1alpha4.NetworkSwitchList{} + if err := r.List(ctx, switches); err != nil { + r.Log.Error(err, "failed to list NetworkSwitch objects") + return nil + } + + for _, item := range switches.Items { + nsw := item.DeepCopy() + if item.Status.ConfigRef.Name == "" { + continue + } + config := &metalv1alpha4.SwitchConfig{} + key := types.NamespacedName{Namespace: item.Namespace, Name: item.Status.ConfigRef.Name} + if err := r.Get(ctx, key, config); err != nil { + r.Log.Error(err, "failed to get SwitchConfig object") + continue + } + var selector *metalv1alpha4.IPAMSelectionSpec + switch { + case isIP: + selector = config.Spec.IPAM.LoopbackAddresses + case isSubnet: + selector = config.Spec.IPAM.SouthSubnets + } + if switchespkg.IPAMSelectorMatchLabels(nsw, selector, obj.GetLabels()) { + r.Log.Info("enqueueing network switch", "name", nsw.Name) + result.Items = append(result.Items, *nsw) + } + } + + return result +} + func (r *SwitchReconciler) mapToInventory(ctx context.Context, obj *metalv1alpha4.NetworkSwitch) (bool, error) { inventoryRefDefined := obj.GetInventoryRef() != constants.EmptyString _, inventoriedLabel := obj.Labels[constants.InventoriedLabel] diff --git a/main.go b/main.go index d9f34107..22b3227d 100644 --- a/main.go +++ b/main.go @@ -181,24 +181,6 @@ func startReconcilers( setupLog.Error(err, "unable to create controller", "controller", "NetworkSwitch-IPAM") os.Exit(1) } - if err = (&switchcontroller.IPTracker{ - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controllers").WithName("IPTracker"), - Scheme: mgr.GetScheme(), - Recorder: mgr.GetEventRecorderFor("IPTracker"), - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", "IPTracker") - os.Exit(1) - } - if err = (&switchcontroller.SubnetTracker{ - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controllers").WithName("SubnetTracker"), - Scheme: mgr.GetScheme(), - Recorder: mgr.GetEventRecorderFor("SubnetTracker"), - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", "SubnetTracker") - os.Exit(1) - } if err = (&inventorycontrollers.InventoryReconciler{ Client: mgr.GetClient(), diff --git a/pkg/constants/switch.go b/pkg/constants/switch.go index c4e2c418..44863ac3 100644 --- a/pkg/constants/switch.go +++ b/pkg/constants/switch.go @@ -3,7 +3,7 @@ package constants -const APIVersion string = "v1beta1" +const APIVersion string = "v1alpha4" const ( FECNone string = "none" diff --git a/pkg/switches/environment.go b/pkg/switches/environment.go index 048c2ab6..4a1e7c49 100644 --- a/pkg/switches/environment.go +++ b/pkg/switches/environment.go @@ -158,7 +158,7 @@ func (in *SwitchEnvironmentSvc) GetLoopbacks( afEnabledFlag = afEnabledFlag | 2 } } - afOK := AddressFamiliesMatchConfig(true, cfg.Spec.IPAM.AddressFamily.GetIPv6(), afEnabledFlag) + _, afOK := AddressFamiliesMatchConfig(true, cfg.Spec.IPAM.AddressFamily.GetIPv6(), afEnabledFlag) if len(loopbacks.Items) == 0 || !afOK { return nil } @@ -219,7 +219,7 @@ func (in *SwitchEnvironmentSvc) GetSubnets( afEnabledFlag = afEnabledFlag | 2 } } - afOK := AddressFamiliesMatchConfig(af.GetIPv4(), af.GetIPv6(), afEnabledFlag) + _, afOK := AddressFamiliesMatchConfig(af.GetIPv4(), af.GetIPv6(), afEnabledFlag) if len(subnets.Items) == 0 || !afOK { return nil } @@ -291,10 +291,15 @@ func (in *SwitchEnvironmentSvc) ListIPAMObjects( params *metalv1alpha4.IPAMSelectionSpec, list client.ObjectList, ) error { - selector, err := GetSelectorFromIPAMSpec(obj, params) + labelSelector, err := GetSelectorFromIPAMSpec(obj, params) if err != nil { return err } + selector, err := metav1.LabelSelectorAsSelector(labelSelector) + if err != nil { + return err + } + in.Log.Info("ipam objects lookup", "selector", labelSelector.MatchLabels) opts := &client.ListOptions{ LabelSelector: selector, Namespace: obj.Namespace, diff --git a/pkg/switches/helpers.go b/pkg/switches/helpers.go index 9ad91cd1..5850aef4 100644 --- a/pkg/switches/helpers.go +++ b/pkg/switches/helpers.go @@ -32,8 +32,6 @@ import ( corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/selection" "k8s.io/apimachinery/pkg/util/yaml" "k8s.io/utils/ptr" "sigs.k8s.io/controller-runtime/pkg/client" @@ -453,27 +451,24 @@ func ResultingLabels( } func GetSelectorFromIPAMSpec( - obj *metalv1alpha4.NetworkSwitch, spec *metalv1alpha4.IPAMSelectionSpec) (labels.Selector, error) { - var err error - var selector labels.Selector + obj *metalv1alpha4.NetworkSwitch, + spec *metalv1alpha4.IPAMSelectionSpec, +) (*metav1.LabelSelector, error) { + result := &metav1.LabelSelector{} if spec != nil { - selector, err = metav1.LabelSelectorAsSelector(spec.LabelSelector) - if err != nil { - return nil, err - } + result.MatchLabels = spec.LabelSelector.MatchLabels if spec.FieldSelector == nil { - return selector, nil + return result, nil } ipamLabelFromFieldRef, err := labelFromFieldRef(*obj, spec.FieldSelector) if err != nil { return nil, err } for key, value := range ipamLabelFromFieldRef { - req, _ := labels.NewRequirement(key, selection.In, []string{value}) - selector = selector.Add(*req) + result.MatchLabels[key] = value } } - return selector, nil + return result, nil } func labelFromFieldRef(obj interface{}, src *metalv1alpha4.FieldSelectorSpec) (map[string]string, error) { @@ -542,10 +537,26 @@ func interfaceToMap(i interface{}) (map[string]interface{}, error) { return m, nil } -// func SetState(obj *metalv1alpha4.NetworkSwitch, state, message string) { -// obj.SetState(state) -// obj.SetMessage(message) -// } +func IPAMSelectorMatchLabels( + obj *metalv1alpha4.NetworkSwitch, + sel *metalv1alpha4.IPAMSelectionSpec, + lbl map[string]string, +) bool { + selector, err := GetSelectorFromIPAMSpec(obj, sel) + if err != nil { + return false + } + for k, v := range selector.MatchLabels { + vv, ok := lbl[k] + if !ok { + return false + } + if vv != v { + return false + } + } + return true +} func CalculateASN(loopbacks []*metalv1alpha4.IPAddressSpec) (uint32, error) { var result uint32 = 0 @@ -743,18 +754,33 @@ func GetTotalAddressesCount( return resource.NewQuantity(counter, resource.DecimalSI) } -func AddressFamiliesMatchConfig(ipv4, ipv6 bool, flag int) bool { +func AddressFamiliesMatchConfig(ipv4, ipv6 bool, flag int) (int, bool) { result := true + missedAFFlag := 0 if flag == 0 { - return false + missedAFFlag = ComputeAFFlag(ipv4, ipv6, flag) + return missedAFFlag, false } if ipv4 && flag == 2 { + missedAFFlag = missedAFFlag | 1 result = false } if ipv6 && flag == 1 { + missedAFFlag = missedAFFlag | 2 result = false } - return result + return missedAFFlag, result +} + +func ComputeAFFlag(ipv4, ipv6 bool, flag int) int { + afFlag := flag + if ipv4 { + afFlag = afFlag | 1 + } + if ipv6 { + afFlag = afFlag | 2 + } + return afFlag } func NeighborIsSwitch(nicData *metalv1alpha4.InterfaceSpec) bool {