Skip to content

Commit fe5c135

Browse files
authored
feat(apisixupstream): support portLevelSettings (#2582)
1 parent ec81917 commit fe5c135

File tree

8 files changed

+416
-68
lines changed

8 files changed

+416
-68
lines changed

api/v2/apisixroute_types.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,15 @@ type ApisixRouteSpec struct {
3636
IngressClassName string `json:"ingressClassName,omitempty" yaml:"ingressClassName,omitempty"`
3737
// HTTP defines a list of HTTP route rules.
3838
// Each rule specifies conditions to match HTTP requests and how to forward them.
39+
//
40+
// +listType=map
41+
// +listMapKey=name
3942
HTTP []ApisixRouteHTTP `json:"http,omitempty" yaml:"http,omitempty"`
4043
// Stream defines a list of stream route rules.
4144
// Each rule specifies conditions to match TCP/UDP traffic and how to forward them.
45+
//
46+
// +listType=map
47+
// +listMapKey=name
4248
Stream []ApisixRouteStream `json:"stream,omitempty" yaml:"stream,omitempty"`
4349
}
4450

config/crd/bases/apisix.apache.org_apisixroutes.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,9 @@ spec:
346346
- name
347347
type: object
348348
type: array
349+
x-kubernetes-list-map-keys:
350+
- name
351+
x-kubernetes-list-type: map
349352
ingressClassName:
350353
description: |-
351354
IngressClassName is the name of the IngressClass this route belongs to.
@@ -459,6 +462,9 @@ spec:
459462
- protocol
460463
type: object
461464
type: array
465+
x-kubernetes-list-map-keys:
466+
- name
467+
x-kubernetes-list-type: map
462468
type: object
463469
status:
464470
description: ApisixStatus is the status report for Apisix ingress Resources

internal/adc/translator/apisixroute.go

Lines changed: 71 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -211,30 +211,12 @@ func (t *Translator) buildUpstream(tctx *provider.TranslateContext, service *adc
211211
)
212212

213213
for _, backend := range rule.Backends {
214-
var backendErr error
215-
upstream := adc.NewDefaultUpstream()
216214
// try to get the apisixupstream with the same name as the backend service to be upstream config.
217215
// err is ignored because it does not care about the externalNodes of the apisixupstream.
218-
auNN := types.NamespacedName{Namespace: ar.GetNamespace(), Name: backend.ServiceName}
219-
if au, ok := tctx.Upstreams[auNN]; ok {
220-
upstream, _ = t.translateApisixUpstream(tctx, au)
221-
}
222-
223-
if backend.ResolveGranularity == apiv2.ResolveGranularityService {
224-
upstream.Nodes, backendErr = t.translateApisixRouteBackendResolveGranularityService(tctx, utils.NamespacedName(ar), backend)
225-
if backendErr != nil {
226-
t.Log.Error(backendErr, "failed to translate ApisixRoute backend with ResolveGranularity Service")
227-
continue
228-
}
229-
} else {
230-
upstream.Nodes, backendErr = t.translateApisixRouteBackendResolveGranularityEndpoint(tctx, utils.NamespacedName(ar), backend)
231-
if backendErr != nil {
232-
t.Log.Error(backendErr, "failed to translate ApisixRoute backend with ResolveGranularity Endpoint")
233-
continue
234-
}
235-
}
236-
if backend.Weight != nil {
237-
upstream.Labels["meta_weight"] = strconv.FormatInt(int64(*backend.Weight), 10)
216+
upstream, err := t.translateApisixRouteHTTPBackend(tctx, ar, backend)
217+
if err != nil {
218+
t.Log.Error(err, "failed to translate ApisixRoute backend", "backend", backend)
219+
continue
238220
}
239221

240222
upstreamName := adc.ComposeUpstreamName(ar.Namespace, backend.ServiceName, backend.Subset, backend.ServicePort, backend.ResolveGranularity)
@@ -350,6 +332,46 @@ func getPortFromService(svc *v1.Service, backendSvcPort intstr.IntOrString) (int
350332
return port, nil
351333
}
352334

335+
func (t *Translator) translateApisixRouteHTTPBackend(tctx *provider.TranslateContext, ar *apiv2.ApisixRoute, backend apiv2.ApisixRouteHTTPBackend) (*adc.Upstream, error) {
336+
auNN := types.NamespacedName{
337+
Namespace: ar.Namespace,
338+
Name: backend.ServiceName,
339+
}
340+
upstream := adc.NewDefaultUpstream()
341+
if au, ok := tctx.Upstreams[auNN]; ok {
342+
svc := tctx.Services[auNN]
343+
if svc == nil {
344+
return nil, errors.Errorf("service not found, ApisixRoute: %s, Service: %s", utils.NamespacedName(ar).String(), auNN)
345+
}
346+
port, err := getPortFromService(svc, backend.ServicePort)
347+
if err != nil {
348+
return nil, err
349+
}
350+
u, err := t.translateApisixUpstreamForPort(tctx, au, ptr.To(port))
351+
if err != nil {
352+
return nil, err
353+
}
354+
upstream = u
355+
}
356+
var (
357+
err error
358+
nodes adc.UpstreamNodes
359+
)
360+
if backend.ResolveGranularity == apiv2.ResolveGranularityService {
361+
nodes, err = t.translateApisixRouteBackendResolveGranularityService(tctx, auNN, backend)
362+
} else {
363+
nodes, err = t.translateApisixRouteBackendResolveGranularityEndpoint(tctx, auNN, backend)
364+
}
365+
if err != nil {
366+
return nil, err
367+
}
368+
upstream.Nodes = nodes
369+
if backend.Weight != nil {
370+
upstream.Labels["meta_weight"] = strconv.FormatInt(int64(*backend.Weight), 10)
371+
}
372+
return upstream, nil
373+
}
374+
353375
func (t *Translator) translateApisixRouteBackendResolveGranularityService(tctx *provider.TranslateContext, arNN types.NamespacedName, backend apiv2.ApisixRouteHTTPBackend) (adc.UpstreamNodes, error) {
354376
serviceNN := types.NamespacedName{
355377
Namespace: arNN.Namespace,
@@ -433,19 +455,39 @@ func (t *Translator) translateStreamRule(tctx *provider.TranslateContext, ar *ap
433455
svc.ID = id.GenID(svc.Name)
434456
svc.StreamRoutes = append(svc.StreamRoutes, sr)
435457

436-
auNN := types.NamespacedName{Namespace: ar.GetNamespace(), Name: part.Backend.ServiceName}
437-
upstream := adc.NewDefaultUpstream()
438-
if au, ok := tctx.Upstreams[auNN]; ok {
439-
upstream, _ = t.translateApisixUpstream(tctx, au)
440-
}
441-
nodes, err := t.translateApisixRouteStreamBackendResolveGranularity(tctx, utils.NamespacedName(ar), part.Backend)
458+
upstream, err := t.translateApisixRouteStreamBackend(tctx, ar, part.Backend)
442459
if err != nil {
443460
return nil, err
444461
}
445-
upstream.Nodes = nodes
446462
upstream.ID = ""
447463
upstream.Name = ""
448464

449465
svc.Upstream = upstream
450466
return svc, nil
451467
}
468+
469+
func (t *Translator) translateApisixRouteStreamBackend(tctx *provider.TranslateContext, ar *apiv2.ApisixRoute, backend apiv2.ApisixRouteStreamBackend) (*adc.Upstream, error) {
470+
auNN := types.NamespacedName{Namespace: ar.GetNamespace(), Name: backend.ServiceName}
471+
upstream := adc.NewDefaultUpstream()
472+
if au, ok := tctx.Upstreams[auNN]; ok {
473+
service := tctx.Services[auNN]
474+
if service == nil {
475+
return nil, errors.Errorf("service not found, ApisixRoute: %s, Service: %s", utils.NamespacedName(ar), auNN)
476+
}
477+
port, err := getPortFromService(service, backend.ServicePort)
478+
if err != nil {
479+
return nil, err
480+
}
481+
u, err := t.translateApisixUpstreamForPort(tctx, au, ptr.To(port))
482+
if err != nil {
483+
return nil, err
484+
}
485+
upstream = u
486+
}
487+
nodes, err := t.translateApisixRouteStreamBackendResolveGranularity(tctx, utils.NamespacedName(ar), backend)
488+
if err != nil {
489+
return nil, err
490+
}
491+
upstream.Nodes = nodes
492+
return upstream, nil
493+
}

internal/adc/translator/apisixupstream.go

Lines changed: 65 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ package translator
2020
import (
2121
"cmp"
2222
"fmt"
23+
"maps"
2324

2425
"github.com/api7/gopkg/pkg/log"
2526
"github.com/pkg/errors"
@@ -33,50 +34,78 @@ import (
3334
"github.com/apache/apisix-ingress-controller/internal/utils"
3435
)
3536

36-
func (t *Translator) translateApisixUpstream(tctx *provider.TranslateContext, au *apiv2.ApisixUpstream) (ups *adc.Upstream, err error) {
37-
ups = adc.NewDefaultUpstream()
38-
for _, f := range []func(*apiv2.ApisixUpstream, *adc.Upstream) error{
39-
patchApisixUpstreamBasics,
37+
func (t *Translator) translateApisixUpstream(tctx *provider.TranslateContext, au *apiv2.ApisixUpstream) (*adc.Upstream, error) {
38+
return t.translateApisixUpstreamForPort(tctx, au, nil)
39+
}
40+
41+
func (t *Translator) translateApisixUpstreamForPort(tctx *provider.TranslateContext, au *apiv2.ApisixUpstream, port *int32) (*adc.Upstream, error) {
42+
log.Debugw("translating ApisixUpstream", zap.Any("apisixupstream", au), zap.Int32p("port", port))
43+
44+
ups := adc.NewDefaultUpstream()
45+
ups.Name = composeExternalUpstreamName(au)
46+
maps.Copy(ups.Labels, au.Labels)
47+
48+
// translateApisixUpstreamConfig translates the core upstream configuration fields
49+
// from au.Spec.ApisixUpstreamConfig into the ADC upstream.
50+
//
51+
// Note: ExternalNodes is not part of ApisixUpstreamConfig but a separate field
52+
// on ApisixUpstreamSpec, so it is handled separately in translateApisixUpstreamExternalNodes.
53+
if err := translateApisixUpstreamConfig(tctx, &au.Spec.ApisixUpstreamConfig, ups); err != nil {
54+
return nil, err
55+
}
56+
if err := translateApisixUpstreamExternalNodes(tctx, au, ups); err != nil {
57+
return nil, err
58+
}
59+
60+
// If PortLevelSettings is configured and a specific port is provided,
61+
// apply the ApisixUpstreamConfig for the matching port to the upstream.
62+
if len(au.Spec.PortLevelSettings) > 0 && port != nil {
63+
for _, pls := range au.Spec.PortLevelSettings {
64+
if pls.Port != *port {
65+
continue
66+
}
67+
if err := translateApisixUpstreamConfig(tctx, &pls.ApisixUpstreamConfig, ups); err != nil {
68+
return nil, err
69+
}
70+
}
71+
}
72+
73+
log.Debugw("translated ApisixUpstream", zap.Any("upstream", ups))
74+
75+
return ups, nil
76+
}
77+
78+
func translateApisixUpstreamConfig(tctx *provider.TranslateContext, config *apiv2.ApisixUpstreamConfig, ups *adc.Upstream) (err error) {
79+
for _, f := range []func(*apiv2.ApisixUpstreamConfig, *adc.Upstream) error{
4080
translateApisixUpstreamScheme,
4181
translateApisixUpstreamLoadBalancer,
4282
translateApisixUpstreamRetriesAndTimeout,
4383
translateApisixUpstreamPassHost,
4484
translateUpstreamHealthCheck,
4585
translateUpstreamDiscovery,
4686
} {
47-
if err = f(au, ups); err != nil {
87+
if err = f(config, ups); err != nil {
4888
return
4989
}
5090
}
51-
for _, f := range []func(*provider.TranslateContext, *apiv2.ApisixUpstream, *adc.Upstream) error{
91+
for _, f := range []func(*provider.TranslateContext, *apiv2.ApisixUpstreamConfig, *adc.Upstream) error{
5292
translateApisixUpstreamClientTLS,
53-
translateApisixUpstreamExternalNodes,
5493
} {
55-
if err = f(tctx, au, ups); err != nil {
94+
if err = f(tctx, config, ups); err != nil {
5695
return
5796
}
5897
}
5998

60-
log.Debugw("translated ApisixUpstream", zap.Any("upstream", ups),
61-
zap.String("namespace", au.Namespace), zap.String("name", au.Name))
6299
return
63100
}
64101

65-
func patchApisixUpstreamBasics(au *apiv2.ApisixUpstream, ups *adc.Upstream) error {
66-
ups.Name = composeExternalUpstreamName(au)
67-
for k, v := range au.Labels {
68-
ups.Labels[k] = v
69-
}
102+
func translateApisixUpstreamScheme(config *apiv2.ApisixUpstreamConfig, ups *adc.Upstream) error {
103+
ups.Scheme = cmp.Or(config.Scheme, apiv2.SchemeHTTP)
70104
return nil
71105
}
72106

73-
func translateApisixUpstreamScheme(au *apiv2.ApisixUpstream, ups *adc.Upstream) error {
74-
ups.Scheme = cmp.Or(au.Spec.Scheme, apiv2.SchemeHTTP)
75-
return nil
76-
}
77-
78-
func translateApisixUpstreamLoadBalancer(au *apiv2.ApisixUpstream, ups *adc.Upstream) error {
79-
lb := au.Spec.LoadBalancer
107+
func translateApisixUpstreamLoadBalancer(config *apiv2.ApisixUpstreamConfig, ups *adc.Upstream) error {
108+
lb := config.LoadBalancer
80109
if lb == nil || lb.Type == "" {
81110
ups.Type = apiv2.LbRoundRobin
82111
return nil
@@ -107,9 +136,9 @@ func translateApisixUpstreamLoadBalancer(au *apiv2.ApisixUpstream, ups *adc.Upst
107136
return nil
108137
}
109138

110-
func translateApisixUpstreamRetriesAndTimeout(au *apiv2.ApisixUpstream, ups *adc.Upstream) error {
111-
retries := au.Spec.Retries
112-
timeout := au.Spec.Timeout
139+
func translateApisixUpstreamRetriesAndTimeout(config *apiv2.ApisixUpstreamConfig, ups *adc.Upstream) error {
140+
retries := config.Retries
141+
timeout := config.Timeout
113142

114143
if retries != nil && *retries < 0 {
115144
return errors.New("invalid value retries")
@@ -144,15 +173,15 @@ func translateApisixUpstreamRetriesAndTimeout(au *apiv2.ApisixUpstream, ups *adc
144173
return nil
145174
}
146175

147-
func translateApisixUpstreamClientTLS(tctx *provider.TranslateContext, au *apiv2.ApisixUpstream, ups *adc.Upstream) error {
148-
if au.Spec.TLSSecret == nil {
176+
func translateApisixUpstreamClientTLS(tctx *provider.TranslateContext, config *apiv2.ApisixUpstreamConfig, ups *adc.Upstream) error {
177+
if config.TLSSecret == nil {
149178
return nil
150179
}
151180

152181
var (
153182
secretNN = types.NamespacedName{
154-
Namespace: au.Spec.TLSSecret.Namespace,
155-
Name: au.Spec.TLSSecret.Name,
183+
Namespace: config.TLSSecret.Namespace,
184+
Name: config.TLSSecret.Name,
156185
}
157186
)
158187
secret, ok := tctx.Secrets[secretNN]
@@ -173,9 +202,9 @@ func translateApisixUpstreamClientTLS(tctx *provider.TranslateContext, au *apiv2
173202
return nil
174203
}
175204

176-
func translateApisixUpstreamPassHost(au *apiv2.ApisixUpstream, ups *adc.Upstream) error {
177-
ups.PassHost = au.Spec.PassHost
178-
ups.UpstreamHost = au.Spec.UpstreamHost
205+
func translateApisixUpstreamPassHost(config *apiv2.ApisixUpstreamConfig, ups *adc.Upstream) error {
206+
ups.PassHost = config.PassHost
207+
ups.UpstreamHost = config.UpstreamHost
179208

180209
return nil
181210
}
@@ -259,11 +288,8 @@ func translateApisixUpstreamExternalNodesService(tctx *provider.TranslateContext
259288
return nil
260289
}
261290

262-
func translateUpstreamHealthCheck(au *apiv2.ApisixUpstream, ups *adc.Upstream) error {
263-
if au == nil {
264-
return nil
265-
}
266-
healcheck := au.Spec.HealthCheck
291+
func translateUpstreamHealthCheck(config *apiv2.ApisixUpstreamConfig, ups *adc.Upstream) error {
292+
healcheck := config.HealthCheck
267293
if healcheck == nil || (healcheck.Passive == nil && healcheck.Active == nil) {
268294
return nil
269295
}
@@ -346,8 +372,8 @@ func translateUpstreamPassiveHealthCheck(config *apiv2.PassiveHealthCheck) *adc.
346372
return &passive
347373
}
348374

349-
func translateUpstreamDiscovery(au *apiv2.ApisixUpstream, ups *adc.Upstream) error {
350-
discovery := au.Spec.Discovery
375+
func translateUpstreamDiscovery(config *apiv2.ApisixUpstreamConfig, ups *adc.Upstream) error {
376+
discovery := config.Discovery
351377
if discovery == nil {
352378
return nil
353379
}

0 commit comments

Comments
 (0)