Skip to content

Commit 3af96af

Browse files
committed
Merge branch 'feat/support_endpoints_fallback' into ci/k8s-1-18
2 parents 04d17bb + d71c521 commit 3af96af

File tree

12 files changed

+1016
-78
lines changed

12 files changed

+1016
-78
lines changed

config/rbac/role.yaml

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,7 @@ rules:
77
- apiGroups:
88
- ""
99
resources:
10-
- events
11-
verbs:
12-
- create
13-
- patch
14-
- apiGroups:
15-
- ""
16-
resources:
10+
- endpoints
1711
- namespaces
1812
- pods
1913
- secrets
@@ -22,6 +16,13 @@ rules:
2216
- get
2317
- list
2418
- watch
19+
- apiGroups:
20+
- ""
21+
resources:
22+
- events
23+
verbs:
24+
- create
25+
- patch
2526
- apiGroups:
2627
- apisix.apache.org
2728
resources:

internal/controller/apisixroute_controller.go

Lines changed: 85 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -64,16 +64,22 @@ type ApisixRouteReconciler struct {
6464
Readier readiness.ReadinessManager
6565

6666
ICGVK schema.GroupVersionKind
67+
// supportsEndpointSlice indicates whether the cluster supports EndpointSlice API
68+
supportsEndpointSlice bool
6769
}
6870

6971
// SetupWithManager sets up the controller with the Manager.
7072
func (r *ApisixRouteReconciler) SetupWithManager(mgr ctrl.Manager) error {
71-
return ctrl.NewControllerManagedBy(mgr).
73+
// Check and store EndpointSlice API support
74+
r.supportsEndpointSlice = pkgutils.HasAPIResource(mgr, &discoveryv1.EndpointSlice{})
75+
76+
bdr := ctrl.NewControllerManagedBy(mgr).
7277
For(&apiv2.ApisixRoute{}).
7378
WithEventFilter(
7479
predicate.Or(
7580
predicate.GenerationChangedPredicate{},
7681
predicate.AnnotationChangedPredicate{},
82+
predicate.NewPredicateFuncs(TypePredicate[*corev1.Endpoints]()),
7783
predicate.NewPredicateFuncs(TypePredicate[*corev1.Secret]()),
7884
),
7985
).
@@ -86,10 +92,21 @@ func (r *ApisixRouteReconciler) SetupWithManager(mgr ctrl.Manager) error {
8692
).
8793
Watches(&v1alpha1.GatewayProxy{},
8894
handler.EnqueueRequestsFromMapFunc(r.listApisixRouteForGatewayProxy),
89-
).
90-
Watches(&discoveryv1.EndpointSlice{},
95+
)
96+
97+
// Conditionally watch EndpointSlice or Endpoints based on cluster API support
98+
if r.supportsEndpointSlice {
99+
bdr = bdr.Watches(&discoveryv1.EndpointSlice{},
91100
handler.EnqueueRequestsFromMapFunc(r.listApisixRoutesForService),
92-
).
101+
)
102+
} else {
103+
r.Log.Info("EndpointSlice API not available, falling back to Endpoints API for service discovery")
104+
bdr = bdr.Watches(&corev1.Endpoints{},
105+
handler.EnqueueRequestsFromMapFunc(r.listApisixRoutesForEndpoints),
106+
)
107+
}
108+
109+
return bdr.
93110
Watches(&corev1.Secret{},
94111
handler.EnqueueRequestsFromMapFunc(r.listApisixRoutesForSecret),
95112
).
@@ -348,23 +365,46 @@ func (r *ApisixRouteReconciler) validateBackends(ctx context.Context, tc *provid
348365
}
349366
tc.Services[serviceNN] = &service
350367

351-
var endpoints discoveryv1.EndpointSliceList
352-
if err := r.List(ctx, &endpoints,
353-
client.InNamespace(service.Namespace),
354-
client.MatchingLabels{
355-
discoveryv1.LabelServiceName: service.Name,
356-
},
357-
); err != nil {
358-
return types.ReasonError{
359-
Reason: string(apiv2.ConditionReasonInvalidSpec),
360-
Message: fmt.Sprintf("failed to list endpoint slices: %v", err),
368+
// Conditionally collect EndpointSlice or Endpoints based on cluster API support
369+
if r.supportsEndpointSlice {
370+
var endpoints discoveryv1.EndpointSliceList
371+
if err := r.List(ctx, &endpoints,
372+
client.InNamespace(service.Namespace),
373+
client.MatchingLabels{
374+
discoveryv1.LabelServiceName: service.Name,
375+
},
376+
); err != nil {
377+
return types.ReasonError{
378+
Reason: string(apiv2.ConditionReasonInvalidSpec),
379+
Message: fmt.Sprintf("failed to list endpoint slices: %v", err),
380+
}
361381
}
362-
}
363382

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

370410
return nil
@@ -450,6 +490,32 @@ func (r *ApisixRouteReconciler) listApisixRoutesForService(ctx context.Context,
450490
return pkgutils.DedupComparable(requests)
451491
}
452492

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

internal/controller/gatewayproxy_controller.go

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import (
2828
networkingv1beta1 "k8s.io/api/networking/v1beta1"
2929
"k8s.io/apimachinery/pkg/runtime"
3030
"k8s.io/apimachinery/pkg/runtime/schema"
31+
"k8s.io/apimachinery/pkg/types"
3132
k8stypes "k8s.io/apimachinery/pkg/types"
3233
ctrl "sigs.k8s.io/controller-runtime"
3334
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -40,6 +41,7 @@ import (
4041
"github.com/apache/apisix-ingress-controller/internal/controller/indexer"
4142
"github.com/apache/apisix-ingress-controller/internal/provider"
4243
"github.com/apache/apisix-ingress-controller/internal/utils"
44+
pkgutils "github.com/apache/apisix-ingress-controller/pkg/utils"
4345
)
4446

4547
// GatewayProxyController reconciles a GatewayProxy object.
@@ -51,23 +53,40 @@ type GatewayProxyController struct {
5153
Provider provider.Provider
5254

5355
ICGVK schema.GroupVersionKind
56+
// supportsEndpointSlice indicates whether the cluster supports EndpointSlice API
57+
supportsEndpointSlice bool
5458
}
5559

5660
func (r *GatewayProxyController) SetupWithManager(mrg ctrl.Manager) error {
57-
return ctrl.NewControllerManagedBy(mrg).
61+
// Check and store EndpointSlice API support
62+
r.supportsEndpointSlice = pkgutils.HasAPIResource(mrg, &discoveryv1.EndpointSlice{})
63+
64+
bdr := ctrl.NewControllerManagedBy(mrg).
5865
For(&v1alpha1.GatewayProxy{}).
5966
WithEventFilter(
6067
predicate.Or(
6168
predicate.GenerationChangedPredicate{},
69+
predicate.NewPredicateFuncs(TypePredicate[*corev1.Endpoints]()),
6270
predicate.NewPredicateFuncs(TypePredicate[*corev1.Secret]()),
6371
),
6472
).
6573
Watches(&corev1.Service{},
6674
handler.EnqueueRequestsFromMapFunc(r.listGatewayProxiesForProviderService),
67-
).
68-
Watches(&discoveryv1.EndpointSlice{},
75+
)
76+
77+
// Conditionally watch EndpointSlice or Endpoints based on cluster API support
78+
if r.supportsEndpointSlice {
79+
bdr = bdr.Watches(&discoveryv1.EndpointSlice{},
6980
handler.EnqueueRequestsFromMapFunc(r.listGatewayProxiesForProviderEndpointSlice),
70-
).
81+
)
82+
} else {
83+
r.Log.Info("EndpointSlice API not available, falling back to Endpoints API for provider service discovery")
84+
bdr = bdr.Watches(&corev1.Endpoints{},
85+
handler.EnqueueRequestsFromMapFunc(r.listGatewayProxiesForProviderEndpoints),
86+
)
87+
}
88+
89+
return bdr.
7190
Watches(&corev1.Secret{},
7291
handler.EnqueueRequestsFromMapFunc(r.listGatewayProxiesForSecret),
7392
).
@@ -97,10 +116,10 @@ func (r *GatewayProxyController) Reconcile(ctx context.Context, req ctrl.Request
97116
if providerService == nil {
98117
tctx.EndpointSlices[req.NamespacedName] = nil
99118
} else {
100-
if err := addProviderEndpointsToTranslateContext(tctx, r.Client, k8stypes.NamespacedName{
119+
if err := addProviderEndpointsToTranslateContextWithEndpointSliceSupport(tctx, r.Client, types.NamespacedName{
101120
Namespace: gp.Namespace,
102121
Name: providerService.Name,
103-
}); err != nil {
122+
}, r.supportsEndpointSlice); err != nil {
104123
return reconcile.Result{}, err
105124
}
106125
}
@@ -193,6 +212,20 @@ func (r *GatewayProxyController) listGatewayProxiesForProviderEndpointSlice(ctx
193212
})
194213
}
195214

215+
// listGatewayProxiesForProviderEndpoints handles Endpoints objects and converts them to GatewayProxy reconcile requests.
216+
// This function provides backward compatibility for Kubernetes 1.18 clusters that don't support EndpointSlice.
217+
func (r *GatewayProxyController) listGatewayProxiesForProviderEndpoints(ctx context.Context, obj client.Object) (requests []reconcile.Request) {
218+
endpoint, ok := obj.(*corev1.Endpoints)
219+
if !ok {
220+
r.Log.Error(errors.New("unexpected object type"), "failed to convert object to Endpoints")
221+
return nil
222+
}
223+
224+
return ListRequests(ctx, r.Client, r.Log, &v1alpha1.GatewayProxyList{}, client.MatchingFields{
225+
indexer.ServiceIndexRef: indexer.GenIndexKey(endpoint.GetNamespace(), endpoint.GetName()),
226+
})
227+
}
228+
196229
func (r *GatewayProxyController) listGatewayProxiesForSecret(ctx context.Context, object client.Object) []reconcile.Request {
197230
secret, ok := object.(*corev1.Secret)
198231
if !ok {

0 commit comments

Comments
 (0)