Skip to content

Commit 34b13bc

Browse files
committed
feat: fallback to endpoints when endpointslice disable
Signed-off-by: Ashing Zheng <[email protected]>
1 parent e61d276 commit 34b13bc

File tree

6 files changed

+494
-69
lines changed

6 files changed

+494
-69
lines changed

internal/controller/apisixroute_controller.go

Lines changed: 85 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,17 @@ type ApisixRouteReconciler struct {
5959
Provider provider.Provider
6060
Updater status.Updater
6161
Readier readiness.ReadinessManager
62+
63+
// supportsEndpointSlice indicates whether the cluster supports EndpointSlice API
64+
supportsEndpointSlice bool
6265
}
6366

6467
// SetupWithManager sets up the controller with the Manager.
6568
func (r *ApisixRouteReconciler) SetupWithManager(mgr ctrl.Manager) error {
66-
return ctrl.NewControllerManagedBy(mgr).
69+
// Check and store EndpointSlice API support
70+
r.supportsEndpointSlice = pkgutils.HasAPIResource(mgr, &discoveryv1.EndpointSlice{})
71+
72+
bdr := ctrl.NewControllerManagedBy(mgr).
6773
For(&apiv2.ApisixRoute{}).
6874
WithEventFilter(
6975
predicate.Or(
@@ -81,10 +87,21 @@ func (r *ApisixRouteReconciler) SetupWithManager(mgr ctrl.Manager) error {
8187
).
8288
Watches(&v1alpha1.GatewayProxy{},
8389
handler.EnqueueRequestsFromMapFunc(r.listApisixRouteForGatewayProxy),
84-
).
85-
Watches(&discoveryv1.EndpointSlice{},
90+
)
91+
92+
// Conditionally watch EndpointSlice or Endpoints based on cluster API support
93+
if r.supportsEndpointSlice {
94+
bdr = bdr.Watches(&discoveryv1.EndpointSlice{},
8695
handler.EnqueueRequestsFromMapFunc(r.listApisixRoutesForService),
87-
).
96+
)
97+
} else {
98+
r.Log.Info("EndpointSlice API not available, falling back to Endpoints API for service discovery")
99+
bdr = bdr.Watches(&corev1.Endpoints{},
100+
handler.EnqueueRequestsFromMapFunc(r.listApisixRoutesForEndpoints),
101+
)
102+
}
103+
104+
return bdr.
88105
Watches(&corev1.Secret{},
89106
handler.EnqueueRequestsFromMapFunc(r.listApisixRoutesForSecret),
90107
).
@@ -341,23 +358,46 @@ func (r *ApisixRouteReconciler) validateBackends(ctx context.Context, tc *provid
341358
}
342359
tc.Services[serviceNN] = &service
343360

344-
var endpoints discoveryv1.EndpointSliceList
345-
if err := r.List(ctx, &endpoints,
346-
client.InNamespace(service.Namespace),
347-
client.MatchingLabels{
348-
discoveryv1.LabelServiceName: service.Name,
349-
},
350-
); err != nil {
351-
return types.ReasonError{
352-
Reason: string(apiv2.ConditionReasonInvalidSpec),
353-
Message: fmt.Sprintf("failed to list endpoint slices: %v", err),
361+
// Conditionally collect EndpointSlice or Endpoints based on cluster API support
362+
if r.supportsEndpointSlice {
363+
var endpoints discoveryv1.EndpointSliceList
364+
if err := r.List(ctx, &endpoints,
365+
client.InNamespace(service.Namespace),
366+
client.MatchingLabels{
367+
discoveryv1.LabelServiceName: service.Name,
368+
},
369+
); err != nil {
370+
return types.ReasonError{
371+
Reason: string(apiv2.ConditionReasonInvalidSpec),
372+
Message: fmt.Sprintf("failed to list endpoint slices: %v", err),
373+
}
354374
}
355-
}
356375

357-
// backend.subset specifies a subset of upstream nodes.
358-
// It specifies that the target pod's label should be a superset of the subset labels of the ApisixUpstream of the serviceName
359-
subsetLabels := r.getSubsetLabels(tc, serviceNN, backend)
360-
tc.EndpointSlices[serviceNN] = r.filterEndpointSlicesBySubsetLabels(ctx, endpoints.Items, subsetLabels)
376+
// backend.subset specifies a subset of upstream nodes.
377+
// It specifies that the target pod's label should be a superset of the subset labels of the ApisixUpstream of the serviceName
378+
subsetLabels := r.getSubsetLabels(tc, serviceNN, backend)
379+
tc.EndpointSlices[serviceNN] = r.filterEndpointSlicesBySubsetLabels(ctx, endpoints.Items, subsetLabels)
380+
} else {
381+
// Fallback to Endpoints API for Kubernetes 1.18 compatibility
382+
var ep corev1.Endpoints
383+
if err := r.Get(ctx, serviceNN, &ep); err != nil {
384+
if client.IgnoreNotFound(err) != nil {
385+
return types.ReasonError{
386+
Reason: string(apiv2.ConditionReasonInvalidSpec),
387+
Message: fmt.Sprintf("failed to get endpoints: %v", err),
388+
}
389+
}
390+
// If endpoints not found, create empty EndpointSlice list
391+
tc.EndpointSlices[serviceNN] = []discoveryv1.EndpointSlice{}
392+
} else {
393+
// Convert Endpoints to EndpointSlice format for internal consistency
394+
convertedEndpointSlices := pkgutils.ConvertEndpointsToEndpointSlice(&ep)
395+
396+
// Apply subset filtering to converted EndpointSlices
397+
subsetLabels := r.getSubsetLabels(tc, serviceNN, backend)
398+
tc.EndpointSlices[serviceNN] = r.filterEndpointSlicesBySubsetLabels(ctx, convertedEndpointSlices, subsetLabels)
399+
}
400+
}
361401
}
362402

363403
return nil
@@ -443,6 +483,32 @@ func (r *ApisixRouteReconciler) listApisixRoutesForService(ctx context.Context,
443483
return pkgutils.DedupComparable(requests)
444484
}
445485

486+
// listApisixRoutesForEndpoints handles Endpoints objects and converts them to ApisixRoute reconcile requests.
487+
// This function provides backward compatibility for Kubernetes 1.18 clusters that don't support EndpointSlice.
488+
func (r *ApisixRouteReconciler) listApisixRoutesForEndpoints(ctx context.Context, obj client.Object) []reconcile.Request {
489+
endpoint, ok := obj.(*corev1.Endpoints)
490+
if !ok {
491+
return nil
492+
}
493+
494+
var (
495+
namespace = endpoint.GetNamespace()
496+
serviceName = endpoint.GetName() // For Endpoints, the name is the service name
497+
arList apiv2.ApisixRouteList
498+
)
499+
if err := r.List(ctx, &arList, client.MatchingFields{
500+
indexer.ServiceIndexRef: indexer.GenIndexKey(namespace, serviceName),
501+
}); err != nil {
502+
r.Log.Error(err, "failed to list apisixroutes by service", "service", serviceName)
503+
return nil
504+
}
505+
requests := make([]reconcile.Request, 0, len(arList.Items))
506+
for _, ar := range arList.Items {
507+
requests = append(requests, reconcile.Request{NamespacedName: utils.NamespacedName(&ar)})
508+
}
509+
return pkgutils.DedupComparable(requests)
510+
}
511+
446512
func (r *ApisixRouteReconciler) listApisixRoutesForSecret(ctx context.Context, obj client.Object) []reconcile.Request {
447513
secret, ok := obj.(*corev1.Secret)
448514
if !ok {

internal/controller/gatewayproxy_controller.go

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import (
3838
"github.com/apache/apisix-ingress-controller/internal/controller/indexer"
3939
"github.com/apache/apisix-ingress-controller/internal/provider"
4040
"github.com/apache/apisix-ingress-controller/internal/utils"
41+
pkgutils "github.com/apache/apisix-ingress-controller/pkg/utils"
4142
)
4243

4344
// GatewayProxyController reconciles a GatewayProxy object.
@@ -47,10 +48,16 @@ type GatewayProxyController struct {
4748
Scheme *runtime.Scheme
4849
Log logr.Logger
4950
Provider provider.Provider
51+
52+
// supportsEndpointSlice indicates whether the cluster supports EndpointSlice API
53+
supportsEndpointSlice bool
5054
}
5155

5256
func (r *GatewayProxyController) SetupWithManager(mrg ctrl.Manager) error {
53-
return ctrl.NewControllerManagedBy(mrg).
57+
// Check and store EndpointSlice API support
58+
r.supportsEndpointSlice = pkgutils.HasAPIResourceWithLogger(mrg, &discoveryv1.EndpointSlice{}, r.Log.WithName("api-detection"))
59+
60+
bdr := ctrl.NewControllerManagedBy(mrg).
5461
For(&v1alpha1.GatewayProxy{}).
5562
WithEventFilter(
5663
predicate.Or(
@@ -60,10 +67,21 @@ func (r *GatewayProxyController) SetupWithManager(mrg ctrl.Manager) error {
6067
).
6168
Watches(&corev1.Service{},
6269
handler.EnqueueRequestsFromMapFunc(r.listGatewayProxiesForProviderService),
63-
).
64-
Watches(&discoveryv1.EndpointSlice{},
70+
)
71+
72+
// Conditionally watch EndpointSlice or Endpoints based on cluster API support
73+
if r.supportsEndpointSlice {
74+
bdr = bdr.Watches(&discoveryv1.EndpointSlice{},
6575
handler.EnqueueRequestsFromMapFunc(r.listGatewayProxiesForProviderEndpointSlice),
66-
).
76+
)
77+
} else {
78+
r.Log.Info("EndpointSlice API not available, falling back to Endpoints API for provider service discovery")
79+
bdr = bdr.Watches(&corev1.Endpoints{},
80+
handler.EnqueueRequestsFromMapFunc(r.listGatewayProxiesForProviderEndpoints),
81+
)
82+
}
83+
84+
return bdr.
6785
Watches(&corev1.Secret{},
6886
handler.EnqueueRequestsFromMapFunc(r.listGatewayProxiesForSecret),
6987
).
@@ -93,10 +111,10 @@ func (r *GatewayProxyController) Reconcile(ctx context.Context, req ctrl.Request
93111
if providerService == nil {
94112
tctx.EndpointSlices[req.NamespacedName] = nil
95113
} else {
96-
if err := addProviderEndpointsToTranslateContext(tctx, r.Client, types.NamespacedName{
114+
if err := addProviderEndpointsToTranslateContextWithEndpointSliceSupport(tctx, r.Client, types.NamespacedName{
97115
Namespace: gp.Namespace,
98116
Name: providerService.Name,
99-
}); err != nil {
117+
}, r.supportsEndpointSlice); err != nil {
100118
return reconcile.Result{}, err
101119
}
102120
}
@@ -174,6 +192,20 @@ func (r *GatewayProxyController) listGatewayProxiesForProviderEndpointSlice(ctx
174192
})
175193
}
176194

195+
// listGatewayProxiesForProviderEndpoints handles Endpoints objects and converts them to GatewayProxy reconcile requests.
196+
// This function provides backward compatibility for Kubernetes 1.18 clusters that don't support EndpointSlice.
197+
func (r *GatewayProxyController) listGatewayProxiesForProviderEndpoints(ctx context.Context, obj client.Object) (requests []reconcile.Request) {
198+
endpoint, ok := obj.(*corev1.Endpoints)
199+
if !ok {
200+
r.Log.Error(errors.New("unexpected object type"), "failed to convert object to Endpoints")
201+
return nil
202+
}
203+
204+
return ListRequests(ctx, r.Client, r.Log, &v1alpha1.GatewayProxyList{}, client.MatchingFields{
205+
indexer.ServiceIndexRef: indexer.GenIndexKey(endpoint.GetNamespace(), endpoint.GetName()),
206+
})
207+
}
208+
177209
func (r *GatewayProxyController) listGatewayProxiesForSecret(ctx context.Context, object client.Object) []reconcile.Request {
178210
secret, ok := object.(*corev1.Secret)
179211
if !ok {

internal/controller/httproute_controller.go

Lines changed: 81 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ import (
5252
"github.com/apache/apisix-ingress-controller/internal/provider"
5353
"github.com/apache/apisix-ingress-controller/internal/types"
5454
"github.com/apache/apisix-ingress-controller/internal/utils"
55+
pkgutils "github.com/apache/apisix-ingress-controller/pkg/utils"
5556
)
5657

5758
// HTTPRouteReconciler reconciles a GatewayClass object.
@@ -67,18 +68,35 @@ type HTTPRouteReconciler struct { //nolint:revive
6768

6869
Updater status.Updater
6970
Readier readiness.ReadinessManager
71+
72+
// supportsEndpointSlice indicates whether the cluster supports EndpointSlice API
73+
supportsEndpointSlice bool
7074
}
7175

7276
// SetupWithManager sets up the controller with the Manager.
7377
func (r *HTTPRouteReconciler) SetupWithManager(mgr ctrl.Manager) error {
7478
r.genericEvent = make(chan event.GenericEvent, 100)
7579

80+
// Check and store EndpointSlice API support
81+
r.supportsEndpointSlice = pkgutils.HasAPIResourceWithLogger(mgr, &discoveryv1.EndpointSlice{}, r.Log.WithName("api-detection"))
82+
7683
bdr := ctrl.NewControllerManagedBy(mgr).
7784
For(&gatewayv1.HTTPRoute{}).
78-
WithEventFilter(predicate.GenerationChangedPredicate{}).
79-
Watches(&discoveryv1.EndpointSlice{},
85+
WithEventFilter(predicate.GenerationChangedPredicate{})
86+
87+
// Conditionally watch EndpointSlice or Endpoints based on cluster API support
88+
if r.supportsEndpointSlice {
89+
bdr = bdr.Watches(&discoveryv1.EndpointSlice{},
8090
handler.EnqueueRequestsFromMapFunc(r.listHTTPRoutesByServiceBef),
81-
).
91+
)
92+
} else {
93+
r.Log.Info("EndpointSlice API not available, falling back to Endpoints API for service discovery")
94+
bdr = bdr.Watches(&corev1.Endpoints{},
95+
handler.EnqueueRequestsFromMapFunc(r.listHTTPRoutesByServiceForEndpoints),
96+
)
97+
}
98+
99+
bdr = bdr.
82100
Watches(&v1alpha1.PluginConfig{},
83101
handler.EnqueueRequestsFromMapFunc(r.listHTTPRoutesByExtensionRef),
84102
).
@@ -288,6 +306,36 @@ func (r *HTTPRouteReconciler) listHTTPRoutesByServiceBef(ctx context.Context, ob
288306
return requests
289307
}
290308

309+
// listHTTPRoutesByServiceForEndpoints handles Endpoints objects and converts them to HTTPRoute reconcile requests.
310+
// This function provides backward compatibility for Kubernetes 1.18 clusters that don't support EndpointSlice.
311+
func (r *HTTPRouteReconciler) listHTTPRoutesByServiceForEndpoints(ctx context.Context, obj client.Object) []reconcile.Request {
312+
endpoint, ok := obj.(*corev1.Endpoints)
313+
if !ok {
314+
r.Log.Error(fmt.Errorf("unexpected object type"), "failed to convert object to Endpoints")
315+
return nil
316+
}
317+
namespace := endpoint.GetNamespace()
318+
serviceName := endpoint.GetName() // For Endpoints, the name is the service name
319+
320+
hrList := &gatewayv1.HTTPRouteList{}
321+
if err := r.List(ctx, hrList, client.MatchingFields{
322+
indexer.ServiceIndexRef: indexer.GenIndexKey(namespace, serviceName),
323+
}); err != nil {
324+
r.Log.Error(err, "failed to list httproutes by service", "service", serviceName)
325+
return nil
326+
}
327+
requests := make([]reconcile.Request, 0, len(hrList.Items))
328+
for _, hr := range hrList.Items {
329+
requests = append(requests, reconcile.Request{
330+
NamespacedName: client.ObjectKey{
331+
Namespace: hr.Namespace,
332+
Name: hr.Name,
333+
},
334+
})
335+
}
336+
return requests
337+
}
338+
291339
func (r *HTTPRouteReconciler) listHTTPRoutesByExtensionRef(ctx context.Context, obj client.Object) []reconcile.Request {
292340
pluginconfig, ok := obj.(*v1alpha1.PluginConfig)
293341
if !ok {
@@ -520,19 +568,37 @@ func (r *HTTPRouteReconciler) processHTTPRouteBackendRefs(tctx *provider.Transla
520568
}
521569
tctx.Services[targetNN] = &service
522570

523-
endpointSliceList := new(discoveryv1.EndpointSliceList)
524-
if err := r.List(tctx, endpointSliceList,
525-
client.InNamespace(targetNN.Namespace),
526-
client.MatchingLabels{
527-
discoveryv1.LabelServiceName: targetNN.Name,
528-
},
529-
); err != nil {
530-
r.Log.Error(err, "failed to list endpoint slices", "Service", targetNN)
531-
terr = err
532-
continue
571+
// Conditionally collect EndpointSlice or Endpoints based on cluster API support
572+
if r.supportsEndpointSlice {
573+
endpointSliceList := new(discoveryv1.EndpointSliceList)
574+
if err := r.List(tctx, endpointSliceList,
575+
client.InNamespace(targetNN.Namespace),
576+
client.MatchingLabels{
577+
discoveryv1.LabelServiceName: targetNN.Name,
578+
},
579+
); err != nil {
580+
r.Log.Error(err, "failed to list endpoint slices", "Service", targetNN)
581+
terr = err
582+
continue
583+
}
584+
tctx.EndpointSlices[targetNN] = endpointSliceList.Items
585+
} else {
586+
// Fallback to Endpoints API for Kubernetes 1.18 compatibility
587+
var endpoints corev1.Endpoints
588+
if err := r.Get(tctx, targetNN, &endpoints); err != nil {
589+
if client.IgnoreNotFound(err) != nil {
590+
r.Log.Error(err, "failed to get endpoints", "Service", targetNN)
591+
terr = err
592+
continue
593+
}
594+
// If endpoints not found, create empty EndpointSlice list
595+
tctx.EndpointSlices[targetNN] = []discoveryv1.EndpointSlice{}
596+
} else {
597+
// Convert Endpoints to EndpointSlice format for internal consistency
598+
convertedEndpointSlices := pkgutils.ConvertEndpointsToEndpointSlice(&endpoints)
599+
tctx.EndpointSlices[targetNN] = convertedEndpointSlices
600+
}
533601
}
534-
535-
tctx.EndpointSlices[targetNN] = endpointSliceList.Items
536602
}
537603
return terr
538604
}

0 commit comments

Comments
 (0)