Skip to content

Commit 27eb3e9

Browse files
authored
feat: gatewayproxy controller (#2444)
1 parent b46332c commit 27eb3e9

File tree

12 files changed

+484
-15
lines changed

12 files changed

+484
-15
lines changed
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
package controller
19+
20+
import (
21+
"context"
22+
"errors"
23+
24+
"github.com/go-logr/logr"
25+
corev1 "k8s.io/api/core/v1"
26+
discoveryv1 "k8s.io/api/discovery/v1"
27+
networkingv1 "k8s.io/api/networking/v1"
28+
"k8s.io/apimachinery/pkg/runtime"
29+
"k8s.io/apimachinery/pkg/types"
30+
ctrl "sigs.k8s.io/controller-runtime"
31+
"sigs.k8s.io/controller-runtime/pkg/client"
32+
"sigs.k8s.io/controller-runtime/pkg/handler"
33+
"sigs.k8s.io/controller-runtime/pkg/reconcile"
34+
gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
35+
36+
"github.com/apache/apisix-ingress-controller/api/v1alpha1"
37+
"github.com/apache/apisix-ingress-controller/internal/controller/indexer"
38+
"github.com/apache/apisix-ingress-controller/internal/provider"
39+
"github.com/apache/apisix-ingress-controller/internal/utils"
40+
)
41+
42+
// GatewayProxyController reconciles a GatewayProxy object.
43+
type GatewayProxyController struct {
44+
client.Client
45+
46+
Scheme *runtime.Scheme
47+
Log logr.Logger
48+
Provider provider.Provider
49+
}
50+
51+
func (r *GatewayProxyController) SetupWithManager(mrg ctrl.Manager) error {
52+
return ctrl.NewControllerManagedBy(mrg).
53+
For(&v1alpha1.GatewayProxy{}).
54+
Watches(&corev1.Service{},
55+
handler.EnqueueRequestsFromMapFunc(r.listGatewayProxiesForProviderService),
56+
).
57+
Watches(&discoveryv1.EndpointSlice{},
58+
handler.EnqueueRequestsFromMapFunc(r.listGatewayProxiesForProviderEndpointSlice),
59+
).
60+
Watches(&corev1.Secret{},
61+
handler.EnqueueRequestsFromMapFunc(r.listGatewayProxiesForSecret),
62+
).
63+
Complete(r)
64+
}
65+
66+
func (r *GatewayProxyController) Reconcile(ctx context.Context, req ctrl.Request) (reconcile.Result, error) {
67+
var tctx = provider.NewDefaultTranslateContext(ctx)
68+
69+
var gp v1alpha1.GatewayProxy
70+
if err := r.Get(ctx, req.NamespacedName, &gp); err != nil {
71+
if client.IgnoreNotFound(err) == nil {
72+
gp.Namespace = req.Namespace
73+
gp.Name = req.Name
74+
err = r.Provider.Update(ctx, tctx, &gp)
75+
}
76+
return ctrl.Result{}, err
77+
}
78+
79+
// if there is no provider, update with empty translate context
80+
if gp.Spec.Provider == nil || gp.Spec.Provider.ControlPlane == nil {
81+
return reconcile.Result{}, r.Provider.Update(ctx, tctx, &gp)
82+
}
83+
84+
// process endpoints for provider service
85+
providerService := gp.Spec.Provider.ControlPlane.Service
86+
if providerService == nil {
87+
tctx.EndpointSlices[req.NamespacedName] = nil
88+
} else {
89+
if err := addProviderEndpointsToTranslateContext(tctx, r.Client, types.NamespacedName{
90+
Namespace: gp.Namespace,
91+
Name: providerService.Name,
92+
}); err != nil {
93+
return reconcile.Result{}, err
94+
}
95+
}
96+
97+
// process secret for provider auth
98+
auth := gp.Spec.Provider.ControlPlane.Auth
99+
if auth.AdminKey != nil && auth.AdminKey.ValueFrom != nil && auth.AdminKey.ValueFrom.SecretKeyRef != nil {
100+
var (
101+
secret corev1.Secret
102+
secretNN = types.NamespacedName{
103+
Namespace: gp.GetNamespace(),
104+
Name: auth.AdminKey.ValueFrom.SecretKeyRef.Name,
105+
}
106+
)
107+
if err := r.Get(ctx, secretNN, &secret); err != nil {
108+
r.Log.Error(err, "failed to get secret", "secret", secretNN)
109+
return reconcile.Result{}, err
110+
}
111+
tctx.Secrets[secretNN] = &secret
112+
}
113+
114+
// list Gateways that reference the GatewayProxy
115+
var (
116+
gatewayList gatewayv1.GatewayList
117+
ingressClassList networkingv1.IngressClassList
118+
indexKey = indexer.GenIndexKey(gp.GetNamespace(), gp.GetName())
119+
)
120+
if err := r.List(ctx, &gatewayList, client.MatchingFields{indexer.ParametersRef: indexKey}); err != nil {
121+
r.Log.Error(err, "failed to list GatewayList")
122+
return ctrl.Result{}, nil
123+
}
124+
125+
// list IngressClasses that reference the GatewayProxy
126+
if err := r.List(ctx, &ingressClassList, client.MatchingFields{indexer.IngressClassParametersRef: indexKey}); err != nil {
127+
r.Log.Error(err, "failed to list IngressClassList")
128+
return reconcile.Result{}, err
129+
}
130+
131+
// append referrers to translate context
132+
for _, item := range gatewayList.Items {
133+
tctx.GatewayProxyReferrers[req.NamespacedName] = append(tctx.GatewayProxyReferrers[req.NamespacedName], utils.NamespacedNameKind(&item))
134+
}
135+
for _, item := range ingressClassList.Items {
136+
tctx.GatewayProxyReferrers[req.NamespacedName] = append(tctx.GatewayProxyReferrers[req.NamespacedName], utils.NamespacedNameKind(&item))
137+
}
138+
139+
if err := r.Provider.Update(ctx, tctx, &gp); err != nil {
140+
return reconcile.Result{}, err
141+
}
142+
143+
return reconcile.Result{}, nil
144+
}
145+
146+
func (r *GatewayProxyController) listGatewayProxiesForProviderService(ctx context.Context, obj client.Object) (requests []reconcile.Request) {
147+
service, ok := obj.(*corev1.Service)
148+
if !ok {
149+
r.Log.Error(errors.New("unexpected object type"), "failed to convert object to Service")
150+
return nil
151+
}
152+
153+
return ListRequests(ctx, r.Client, r.Log, &v1alpha1.GatewayProxyList{}, client.MatchingFields{
154+
indexer.ServiceIndexRef: indexer.GenIndexKey(service.GetNamespace(), service.GetName()),
155+
})
156+
}
157+
158+
func (r *GatewayProxyController) listGatewayProxiesForProviderEndpointSlice(ctx context.Context, obj client.Object) (requests []reconcile.Request) {
159+
endpointSlice, ok := obj.(*discoveryv1.EndpointSlice)
160+
if !ok {
161+
r.Log.Error(errors.New("unexpected object type"), "failed to convert object to EndpointSlice")
162+
return nil
163+
}
164+
165+
return ListRequests(ctx, r.Client, r.Log, &v1alpha1.GatewayProxyList{}, client.MatchingFields{
166+
indexer.ServiceIndexRef: indexer.GenIndexKey(endpointSlice.GetNamespace(), endpointSlice.Labels[discoveryv1.LabelServiceName]),
167+
})
168+
}
169+
170+
func (r *GatewayProxyController) listGatewayProxiesForSecret(ctx context.Context, object client.Object) []reconcile.Request {
171+
secret, ok := object.(*corev1.Secret)
172+
if !ok {
173+
r.Log.Error(errors.New("unexpected object type"), "failed to convert object to Secret")
174+
return nil
175+
}
176+
return ListRequests(ctx, r.Client, r.Log, &v1alpha1.GatewayProxyList{}, client.MatchingFields{
177+
indexer.SecretIndexRef: indexer.GenIndexKey(secret.GetNamespace(), secret.GetName()),
178+
})
179+
}

internal/controller/indexer/indexer.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,15 @@ func setupIngressClassIndexer(mgr ctrl.Manager) error {
241241
}
242242

243243
func setupGatewayProxyIndexer(mgr ctrl.Manager) error {
244+
if err := mgr.GetFieldIndexer().IndexField(
245+
context.Background(),
246+
&v1alpha1.GatewayProxy{},
247+
ServiceIndexRef,
248+
GatewayProxyServiceIndexFunc,
249+
); err != nil {
250+
return err
251+
}
252+
244253
if err := mgr.GetFieldIndexer().IndexField(
245254
context.Background(),
246255
&v1alpha1.GatewayProxy{},
@@ -272,6 +281,17 @@ func setupGatewayClassIndexer(mgr ctrl.Manager) error {
272281
)
273282
}
274283

284+
func GatewayProxyServiceIndexFunc(rawObj client.Object) []string {
285+
gatewayProxy := rawObj.(*v1alpha1.GatewayProxy)
286+
if gatewayProxy.Spec.Provider != nil &&
287+
gatewayProxy.Spec.Provider.ControlPlane != nil &&
288+
gatewayProxy.Spec.Provider.ControlPlane.Service != nil {
289+
service := gatewayProxy.Spec.Provider.ControlPlane.Service
290+
return []string{GenIndexKey(gatewayProxy.GetNamespace(), service.Name)}
291+
}
292+
return nil
293+
}
294+
275295
func GatewayProxySecretIndexFunc(rawObj client.Object) []string {
276296
gatewayProxy := rawObj.(*v1alpha1.GatewayProxy)
277297
secretKeys := make([]string, 0)

internal/manager/controllers.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,5 +168,11 @@ func setupControllers(ctx context.Context, mgr manager.Manager, pro provider.Pro
168168
Log: ctrl.LoggerFrom(ctx).WithName("controllers").WithName("ApisixUpstream"),
169169
Updater: updater,
170170
},
171+
&controller.GatewayProxyController{
172+
Client: mgr.GetClient(),
173+
Scheme: mgr.GetScheme(),
174+
Log: ctrl.LoggerFrom(ctx).WithName("controllers").WithName("GatewayProxy"),
175+
Provider: pro,
176+
},
171177
}, nil
172178
}

internal/provider/adc/adc.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,8 @@ func (d *adcClient) Update(ctx context.Context, tctx *provider.TranslateContext,
133133
case *apiv2.ApisixConsumer:
134134
result, err = d.translator.TranslateApisixConsumer(tctx, t.DeepCopy())
135135
resourceTypes = append(resourceTypes, "consumer")
136+
case *v1alpha1.GatewayProxy:
137+
return d.updateConfigForGatewayProxy(tctx, t)
136138
}
137139
if err != nil {
138140
return err

internal/provider/adc/config.go

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,20 +18,22 @@
1818
package adc
1919

2020
import (
21+
"errors"
22+
"fmt"
2123
"net"
2224
"slices"
2325
"strconv"
2426

2527
"github.com/api7/gopkg/pkg/log"
26-
"github.com/pkg/errors"
2728
"go.uber.org/zap"
2829
k8stypes "k8s.io/apimachinery/pkg/types"
2930
"k8s.io/utils/ptr"
30-
v1 "sigs.k8s.io/gateway-api/apis/v1"
31+
gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
3132

3233
"github.com/apache/apisix-ingress-controller/api/v1alpha1"
3334
"github.com/apache/apisix-ingress-controller/internal/provider"
3435
"github.com/apache/apisix-ingress-controller/internal/types"
36+
"github.com/apache/apisix-ingress-controller/internal/utils"
3537
)
3638

3739
func (d *adcClient) getConfigsForGatewayProxy(tctx *provider.TranslateContext, gatewayProxy *v1alpha1.GatewayProxy) (*adcConfig, error) {
@@ -87,17 +89,17 @@ func (d *adcClient) getConfigsForGatewayProxy(tctx *provider.TranslateContext, g
8789
}
8890
_, ok := tctx.Services[namespacedName]
8991
if !ok {
90-
return nil, errors.Errorf("no service found for service reference: %s", namespacedName)
92+
return nil, fmt.Errorf("no service found for service reference: %s", namespacedName)
9193
}
9294
endpoint := tctx.EndpointSlices[namespacedName]
9395
if endpoint == nil {
9496
return nil, nil
9597
}
96-
upstreamNodes, err := d.translator.TranslateBackendRef(tctx, v1.BackendRef{
97-
BackendObjectReference: v1.BackendObjectReference{
98-
Name: v1.ObjectName(provider.ControlPlane.Service.Name),
99-
Namespace: (*v1.Namespace)(&gatewayProxy.Namespace),
100-
Port: ptr.To(v1.PortNumber(provider.ControlPlane.Service.Port)),
98+
upstreamNodes, err := d.translator.TranslateBackendRef(tctx, gatewayv1.BackendRef{
99+
BackendObjectReference: gatewayv1.BackendObjectReference{
100+
Name: gatewayv1.ObjectName(provider.ControlPlane.Service.Name),
101+
Namespace: (*gatewayv1.Namespace)(&gatewayProxy.Namespace),
102+
Port: ptr.To(gatewayv1.PortNumber(provider.ControlPlane.Service.Port)),
101103
},
102104
})
103105
if err != nil {
@@ -167,6 +169,32 @@ func (d *adcClient) updateConfigs(rk types.NamespacedNameKind, tctx *provider.Tr
167169
return nil
168170
}
169171

172+
// updateConfigForGatewayProxy update config for all referrers of the GatewayProxy
173+
func (d *adcClient) updateConfigForGatewayProxy(tctx *provider.TranslateContext, gp *v1alpha1.GatewayProxy) error {
174+
d.Lock()
175+
defer d.Unlock()
176+
177+
config, err := d.getConfigsForGatewayProxy(tctx, gp)
178+
if err != nil {
179+
return err
180+
}
181+
182+
referrers := tctx.GatewayProxyReferrers[utils.NamespacedName(gp)]
183+
184+
if config == nil {
185+
for _, ref := range referrers {
186+
delete(d.configs, ref)
187+
}
188+
return nil
189+
}
190+
191+
for _, ref := range referrers {
192+
d.configs[ref] = *config
193+
}
194+
195+
return nil
196+
}
197+
170198
func (d *adcClient) findConfigsToDelete(oldParentRefs, newParentRefs []types.NamespacedNameKind) []adcConfig {
171199
var deleteConfigs []adcConfig
172200
for _, parentRef := range oldParentRefs {

internal/provider/provider.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,9 @@ type TranslateContext struct {
5555
Upstreams map[k8stypes.NamespacedName]*apiv2.ApisixUpstream
5656
GatewayProxies map[types.NamespacedNameKind]v1alpha1.GatewayProxy
5757
ResourceParentRefs map[types.NamespacedNameKind][]types.NamespacedNameKind
58-
HTTPRoutePolicies []v1alpha1.HTTPRoutePolicy
58+
// GatewayProxyReferrers key is GatewayProxy, value is a list of resources that reference this GatewayProxy
59+
GatewayProxyReferrers map[k8stypes.NamespacedName][]types.NamespacedNameKind
60+
HTTPRoutePolicies []v1alpha1.HTTPRoutePolicy
5961

6062
StatusUpdaters []status.Update
6163
}
@@ -72,5 +74,6 @@ func NewDefaultTranslateContext(ctx context.Context) *TranslateContext {
7274
Upstreams: make(map[k8stypes.NamespacedName]*apiv2.ApisixUpstream),
7375
GatewayProxies: make(map[types.NamespacedNameKind]v1alpha1.GatewayProxy),
7476
ResourceParentRefs: make(map[types.NamespacedNameKind][]types.NamespacedNameKind),
77+
GatewayProxyReferrers: make(map[k8stypes.NamespacedName][]types.NamespacedNameKind),
7578
}
7679
}

0 commit comments

Comments
 (0)