@@ -5,25 +5,33 @@ package service
55
66import (
77 "context"
8+ "fmt"
89 "time"
910
1011 v1 "k8s.io/api/core/v1"
1112 apierrors "k8s.io/apimachinery/pkg/api/errors"
1213 apimachineryruntime "k8s.io/apimachinery/pkg/runtime"
14+ "k8s.io/apimachinery/pkg/types"
1315 "k8s.io/apimachinery/pkg/util/version"
1416 clientset "k8s.io/client-go/kubernetes"
1517 "k8s.io/client-go/rest"
1618 "k8s.io/client-go/tools/record"
19+ "k8s.io/client-go/util/retry"
1720 ctrl "sigs.k8s.io/controller-runtime"
21+ "sigs.k8s.io/controller-runtime/pkg/builder"
1822 "sigs.k8s.io/controller-runtime/pkg/client"
1923 "sigs.k8s.io/controller-runtime/pkg/controller"
24+ "sigs.k8s.io/controller-runtime/pkg/handler"
25+ "sigs.k8s.io/controller-runtime/pkg/manager"
2026 "sigs.k8s.io/controller-runtime/pkg/webhook"
2127
28+ "github.com/vmware-tanzu/nsx-operator/pkg/apis/vpc/v1alpha1"
2229 "github.com/vmware-tanzu/nsx-operator/pkg/controllers/common"
2330 "github.com/vmware-tanzu/nsx-operator/pkg/logger"
2431 "github.com/vmware-tanzu/nsx-operator/pkg/metrics"
2532 _ "github.com/vmware-tanzu/nsx-operator/pkg/nsx/ratelimiter"
2633 servicecommon "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/common"
34+ "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/dns"
2735)
2836
2937var (
@@ -38,6 +46,7 @@ type ServiceLbReconciler struct {
3846 Client client.Client
3947 Scheme * apimachineryruntime.Scheme
4048 Service * servicecommon.Service
49+ DNS dns.DNSRecordProvider
4150 Recorder record.EventRecorder
4251}
4352
@@ -53,6 +62,14 @@ func updateSuccess(r *ServiceLbReconciler, c context.Context, lbService *v1.Serv
5362 return err
5463}
5564
65+ func (r * ServiceLbReconciler ) deleteDNSForService (ctx context.Context , namespace , name string , op string ) error {
66+ if _ , err := r .DNS .DeleteRecordByOwnerNN (ctx , dns .ResourceKindService , namespace , name ); err != nil {
67+ log .Error (err , "Failed to delete DNS records for Service" , "Namespace" , namespace , "Name" , name , "Operation" , op )
68+ return fmt .Errorf ("deleting DNS records for %s: %w" , op , err )
69+ }
70+ return nil
71+ }
72+
5673func (r * ServiceLbReconciler ) Reconcile (ctx context.Context , req ctrl.Request ) (ctrl.Result , error ) {
5774 service := & v1.Service {}
5875 startTime := time .Now ()
@@ -63,33 +80,51 @@ func (r *ServiceLbReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
6380 if err := r .Client .Get (ctx , req .NamespacedName , service ); err != nil {
6481 if apierrors .IsNotFound (err ) {
6582 log .Info ("Not found LB service" , "req" , req .NamespacedName )
66- return ResultNormal , client .IgnoreNotFound (err )
83+ if err := r .deleteDNSForService (ctx , req .Namespace , req .Name , "deleted Service" ); err != nil {
84+ return common .ResultRequeueAfter10sec , nil
85+ }
86+ return ResultNormal , nil
6787 }
6888 log .Error (err , "Failed to fetch LB service" , "req" , req .NamespacedName )
69- return common .ResultRequeueAfter10sec , err
89+ return common .ResultRequeueAfter10sec , nil
7090 }
7191
72- if service .Spec .Type == v1 .ServiceTypeLoadBalancer {
73- log .Info ("Reconciling LB service" , "LBService" , req .NamespacedName )
74- log .Debug ("Reconciling LB Service" , "name" , service .Name , "version" , service .ResourceVersion , "status" , service .Status )
75- metrics .CounterInc (r .Service .NSXConfig , metrics .ControllerSyncTotal , MetricResType )
76-
77- if service .ObjectMeta .DeletionTimestamp .IsZero () {
78- metrics .CounterInc (r .Service .NSXConfig , metrics .ControllerUpdateTotal , MetricResType )
79- err := updateSuccess (r , ctx , service )
80- if err != nil {
81- log .Error (err , "Failed to update LB service" , "Name" , service .Name , "Namespace" , service .Namespace )
82- return common .ResultRequeueAfter10sec , err
83- }
92+ if service .Spec .Type != v1 .ServiceTypeLoadBalancer || ! service .ObjectMeta .DeletionTimestamp .IsZero () {
93+ // Try to delete DNS records for Service when it is not a LoadBalancer or is marked for deletion
94+ if err := r .clearDNSAndConditionForService (ctx , req .NamespacedName , "non-LB or terminating Service" ); err != nil {
95+ return common .ResultRequeueAfter10sec , nil
8496 }
97+ return ResultNormal , nil
98+ }
99+
100+ log .Info ("Reconciling LB service" , "LBService" , req .NamespacedName )
101+ log .Debug ("Reconciling LB Service" , "name" , service .Name , "version" , service .ResourceVersion , "status" , service .Status )
102+ metrics .CounterInc (r .Service .NSXConfig , metrics .ControllerSyncTotal , MetricResType )
103+
104+ var dnsErr error
105+ if err := r .reconcileLoadBalancerServiceDNS (ctx , service ); err != nil {
106+ log .Error (err , "Failed to reconcile DNS for LoadBalancer Service" , "Name" , service .Name , "Namespace" , service .Namespace )
107+ dnsErr = fmt .Errorf ("reconciling DNS: %w" , err )
108+ }
109+
110+ metrics .CounterInc (r .Service .NSXConfig , metrics .ControllerUpdateTotal , MetricResType )
111+ // Even if DNS reconciliation fails, we must proceed to update the Service status.
112+ // This ensures that successfully allocated LoadBalancer external IPs are properly
113+ // reflected in the Service status, allowing traffic to flow while DNS issues are resolved.
114+ if err := updateSuccess (r , ctx , service ); err != nil {
115+ log .Error (err , "Failed to update LB service" , "Name" , service .Name , "Namespace" , service .Namespace )
116+ return common .ResultRequeueAfter10sec , nil
117+ }
118+
119+ if dnsErr != nil {
120+ return common .ResultRequeueAfter10sec , nil
85121 }
86122
87123 return ResultNormal , nil
88124}
89125
90126func (r * ServiceLbReconciler ) setServiceLbStatus (ctx context.Context , lbService * v1.Service ) error {
91127 ipMode := v1 .LoadBalancerIPModeProxy
92- statusUpdated := false
93128 // If nsx.vmware.com/ingress-ip-mode label with values proxy or vip,
94129 // the LoadBalancer service ipMode status would be set to whatever the label is set to,
95130 // Otherwise, it's set to Proxy by default when unset or other invalid values.
@@ -98,34 +133,48 @@ func (r *ServiceLbReconciler) setServiceLbStatus(ctx context.Context, lbService
98133 ipMode = v1 .LoadBalancerIPModeVIP
99134 }
100135 }
101- for i , ing := range lbService .Status .LoadBalancer .Ingress {
102- if ing .IP != "" {
103- if ing .IPMode == nil || * (ing .IPMode ) != ipMode {
104- lbService .Status .LoadBalancer .Ingress [i ].IPMode = & ipMode
105- statusUpdated = true
136+
137+ return retry .RetryOnConflict (retry .DefaultRetry , func () error {
138+ svc := & v1.Service {}
139+ if err := r .Client .Get (ctx , types.NamespacedName {Name : lbService .Name , Namespace : lbService .Namespace }, svc ); err != nil {
140+ return client .IgnoreNotFound (err )
141+ }
142+
143+ statusUpdated := false
144+ for i , ing := range svc .Status .LoadBalancer .Ingress {
145+ if ing .IP != "" {
146+ if ing .IPMode == nil || * (ing .IPMode ) != ipMode {
147+ svc .Status .LoadBalancer .Ingress [i ].IPMode = & ipMode
148+ statusUpdated = true
149+ }
106150 }
107151 }
108- }
109152
110- if statusUpdated {
111- err := r .Client .Status ().Update (ctx , lbService )
112- if err != nil {
113- log .Error (err , "Failed to update LB service status ipMode" , "Name" , lbService .Name , "Namespace" , lbService .Namespace , "ipMode" , ipMode )
114- return err
153+ if statusUpdated {
154+ err := r .Client .Status ().Update (ctx , svc )
155+ if err != nil {
156+ log .Error (err , "Failed to update LB service status ipMode" , "Name" , svc .Name , "Namespace" , svc .Namespace , "ipMode" , ipMode )
157+ return err
158+ }
159+ log .Info ("Updated LB service status ipMode" , "Name" , svc .Name , "Namespace" , svc .Namespace , "ipMode" , ipMode )
115160 }
116- log .Info ("Updated LB service status ipMode" , "Name" , lbService .Name , "Namespace" , lbService .Namespace , "ipMode" , ipMode )
117- }
118- return nil
161+ return nil
162+ })
119163}
120164
121165func (r * ServiceLbReconciler ) setupWithManager (mgr ctrl.Manager ) error {
122- return ctrl .NewControllerManagedBy (mgr ).
166+ b := ctrl .NewControllerManagedBy (mgr ).
123167 For (& v1.Service {}).
168+ Watches (
169+ & v1alpha1.NetworkInfo {},
170+ handler .EnqueueRequestsFromMapFunc (r .enqueueLBServiceRequestsFromNetworkInfo ),
171+ builder .WithPredicates (predicateNetworkInfoAllowedDNSDomainsChanged ()),
172+ ).
124173 WithOptions (
125174 controller.Options {
126175 MaxConcurrentReconciles : common .NumReconcile (),
127- }).
128- Complete (r )
176+ })
177+ return b . Complete (r )
129178}
130179
131180// Start setup manager
@@ -172,18 +221,36 @@ func (r *ServiceLbReconciler) StartController(mgr ctrl.Manager, _ webhook.Server
172221 log .Error (err , "Failed to create controller" , "controller" , "ServiceLb" )
173222 return err
174223 }
224+ err := mgr .Add (manager .RunnableFunc (func (ctx context.Context ) error {
225+ stop := make (chan bool )
226+ go func () {
227+ <- ctx .Done ()
228+ close (stop )
229+ }()
230+ common .GenericGarbageCollector (stop , servicecommon .GCInterval , r .CollectGarbage )
231+ return nil
232+ }))
233+ if err != nil {
234+ log .Error (err , "Failed to add LB GC to manager" )
235+ return err
236+ }
175237 return nil
176238}
177239
178240func (r * ServiceLbReconciler ) CollectGarbage (ctx context.Context ) error {
179- return nil
241+ return r . collectDNSGarbage ( ctx )
180242}
181243
182- func NewServiceLbReconciler (mgr ctrl.Manager , commonService servicecommon.Service ) * ServiceLbReconciler {
244+ func NewServiceLbReconciler (mgr ctrl.Manager , commonService servicecommon.Service , dnsRecordService * dns. DNSRecordService ) * ServiceLbReconciler {
183245 if isServiceLbStatusIpModeSupported (mgr .GetConfig ()) {
246+ var dnsProv dns.DNSRecordProvider
247+ if dnsRecordService != nil {
248+ dnsProv = dnsRecordService
249+ }
184250 serviceLbReconciler := & ServiceLbReconciler {
185251 Client : mgr .GetClient (),
186252 Scheme : mgr .GetScheme (),
253+ DNS : dnsProv ,
187254 Recorder : mgr .GetEventRecorderFor ("serviceLb-controller" ), //nolint:staticcheck // record.EventRecorder; StatusUpdater not on events.EventRecorder yet
188255 }
189256 serviceLbReconciler .Service = & commonService
0 commit comments