Skip to content

Commit 75df484

Browse files
AlinsRanronething
authored andcommitted
feat: support resolve svc.ports[].appProtocol (#2601)
Signed-off-by: Ashing Zheng <[email protected]>
1 parent e1ecfeb commit 75df484

File tree

16 files changed

+698
-163
lines changed

16 files changed

+698
-163
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ GO_LDFLAGS ?= "-X=$(VERSYM)=$(VERSION) -X=$(GITSHASYM)=$(GITSHA) -X=$(BUILDOSSYM
5959
# gateway-api
6060
GATEAY_API_VERSION ?= v1.3.0
6161
## https://github.com/kubernetes-sigs/gateway-api/blob/v1.3.0/pkg/features/httproute.go
62-
SUPPORTED_EXTENDED_FEATURES = "HTTPRouteDestinationPortMatching,HTTPRouteMethodMatching,HTTPRoutePortRedirect,HTTPRouteRequestMirror,HTTPRouteSchemeRedirect,GatewayAddressEmpty,HTTPRouteResponseHeaderModification,GatewayPort8080,HTTPRouteHostRewrite,HTTPRouteQueryParamMatching,HTTPRoutePathRewrite"
62+
SUPPORTED_EXTENDED_FEATURES = "HTTPRouteDestinationPortMatching,HTTPRouteMethodMatching,HTTPRoutePortRedirect,HTTPRouteRequestMirror,HTTPRouteSchemeRedirect,GatewayAddressEmpty,HTTPRouteResponseHeaderModification,GatewayPort8080,HTTPRouteHostRewrite,HTTPRouteQueryParamMatching,HTTPRoutePathRewrite,HTTPRouteBackendProtocolWebSocket"
6363
CONFORMANCE_TEST_REPORT_OUTPUT ?= $(DIR)/apisix-ingress-controller-conformance-report.yaml
6464
## https://github.com/kubernetes-sigs/gateway-api/blob/v1.3.0/conformance/utils/suite/profiles.go
6565
CONFORMANCE_PROFILES ?= GATEWAY-HTTP,GATEWAY-GRPC,GATEWAY-TLS

api/v2/apisixroute_types.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ type ApisixRouteHTTP struct {
100100

101101
// Websocket enables or disables websocket support for this route.
102102
// +kubebuilder:validation:Optional
103-
Websocket bool `json:"websocket" yaml:"websocket"`
103+
Websocket *bool `json:"websocket" yaml:"websocket"`
104104
// PluginConfigName specifies the name of the plugin config to apply.
105105
PluginConfigName string `json:"plugin_config_name,omitempty" yaml:"plugin_config_name,omitempty"`
106106
// PluginConfigNamespace specifies the namespace of the plugin config.

api/v2/zz_generated.deepcopy.go

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/adc/translator/apisixroute.go

Lines changed: 61 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import (
2424
"strconv"
2525

2626
"github.com/pkg/errors"
27-
v1 "k8s.io/api/core/v1"
27+
corev1 "k8s.io/api/core/v1"
2828
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2929
"k8s.io/apimachinery/pkg/types"
3030
"k8s.io/apimachinery/pkg/util/intstr"
@@ -70,9 +70,10 @@ func (t *Translator) translateHTTPRule(tctx *provider.TranslateContext, ar *apiv
7070
return nil, err
7171
}
7272

73+
var enableWebsocket *bool
7374
service := t.buildService(ar, rule, ruleIndex)
74-
t.buildRoute(ar, service, rule, plugins, timeout, vars)
75-
t.buildUpstream(tctx, service, ar, rule, ruleIndex)
75+
t.buildRoute(ar, service, rule, plugins, timeout, vars, &enableWebsocket)
76+
t.buildUpstream(tctx, service, ar, rule, ruleIndex, &enableWebsocket)
7677

7778
return service, nil
7879
}
@@ -139,7 +140,7 @@ func (t *Translator) loadRoutePlugins(tctx *provider.TranslateContext, ar *apiv2
139140
}
140141
}
141142

142-
func (t *Translator) buildPluginConfig(plugin apiv2.ApisixRoutePlugin, namespace string, secrets map[types.NamespacedName]*v1.Secret) map[string]any {
143+
func (t *Translator) buildPluginConfig(plugin apiv2.ApisixRoutePlugin, namespace string, secrets map[types.NamespacedName]*corev1.Secret) map[string]any {
143144
config := make(map[string]any)
144145
if len(plugin.Config.Raw) > 0 {
145146
if err := json.Unmarshal(plugin.Config.Raw, &config); err != nil {
@@ -179,13 +180,16 @@ func (t *Translator) addAuthenticationPlugins(rule apiv2.ApisixRouteHTTP, plugin
179180
}
180181
}
181182

182-
func (t *Translator) buildRoute(ar *apiv2.ApisixRoute, service *adc.Service, rule apiv2.ApisixRouteHTTP, plugins adc.Plugins, timeout *adc.Timeout, vars adc.Vars) {
183+
func (t *Translator) buildRoute(ar *apiv2.ApisixRoute, service *adc.Service, rule apiv2.ApisixRouteHTTP, plugins adc.Plugins, timeout *adc.Timeout, vars adc.Vars, enableWebsocket **bool) {
183184
route := adc.NewDefaultRoute()
184185
route.Name = adc.ComposeRouteName(ar.Namespace, ar.Name, rule.Name)
185186
route.ID = id.GenID(route.Name)
186187
route.Desc = "Created by apisix-ingress-controller, DO NOT modify it manually"
187188
route.Labels = label.GenLabel(ar)
188-
route.EnableWebsocket = ptr.To(rule.Websocket)
189+
route.EnableWebsocket = rule.Websocket
190+
if route.EnableWebsocket == nil && *enableWebsocket != nil {
191+
route.EnableWebsocket = *enableWebsocket
192+
}
189193
route.FilterFunc = rule.Match.FilterFunc
190194
route.Hosts = rule.Match.Hosts
191195
route.Methods = rule.Match.Methods
@@ -202,7 +206,7 @@ func (t *Translator) buildRoute(ar *apiv2.ApisixRoute, service *adc.Service, rul
202206
service.Routes = []*adc.Route{route}
203207
}
204208

205-
func (t *Translator) buildUpstream(tctx *provider.TranslateContext, service *adc.Service, ar *apiv2.ApisixRoute, rule apiv2.ApisixRouteHTTP, ruleIndex int) {
209+
func (t *Translator) buildUpstream(tctx *provider.TranslateContext, service *adc.Service, ar *apiv2.ApisixRoute, rule apiv2.ApisixRouteHTTP, ruleIndex int, enableWebsocket **bool) {
206210
var (
207211
upstreams = make([]*adc.Upstream, 0)
208212
weightedUpstreams = make([]adc.TrafficSplitConfigRuleWeightedUpstream, 0)
@@ -211,7 +215,7 @@ func (t *Translator) buildUpstream(tctx *provider.TranslateContext, service *adc
211215
for backendIndex, backend := range rule.Backends {
212216
// try to get the apisixupstream with the same name as the backend service to be upstream config.
213217
// err is ignored because it does not care about the externalNodes of the apisixupstream.
214-
upstream, err := t.translateApisixRouteHTTPBackend(tctx, ar, backend)
218+
upstream, err := t.translateApisixRouteHTTPBackend(tctx, ar, backend, enableWebsocket)
215219
if err != nil {
216220
t.Log.Error(err, "failed to translate ApisixRoute backend", "backend", backend)
217221
continue
@@ -312,7 +316,7 @@ func (t *Translator) buildService(ar *apiv2.ApisixRoute, rule apiv2.ApisixRouteH
312316
return service
313317
}
314318

315-
func getPortFromService(svc *v1.Service, backendSvcPort intstr.IntOrString) (int32, error) {
319+
func getPortFromService(svc *corev1.Service, backendSvcPort intstr.IntOrString) (int32, error) {
316320
var port int32
317321
if backendSvcPort.Type == intstr.Int {
318322
port = int32(backendSvcPort.IntValue())
@@ -332,7 +336,31 @@ func getPortFromService(svc *v1.Service, backendSvcPort intstr.IntOrString) (int
332336
return port, nil
333337
}
334338

335-
func (t *Translator) translateApisixRouteHTTPBackend(tctx *provider.TranslateContext, ar *apiv2.ApisixRoute, backend apiv2.ApisixRouteHTTPBackend) (*adc.Upstream, error) {
339+
func findMatchingServicePort(svc *corev1.Service, backendSvcPort intstr.IntOrString) (*corev1.ServicePort, error) {
340+
var servicePort *corev1.ServicePort
341+
var portNumber int32 = -1
342+
var servicePortName string
343+
switch backendSvcPort.Type {
344+
case intstr.Int:
345+
portNumber = backendSvcPort.IntVal
346+
case intstr.String:
347+
servicePortName = backendSvcPort.StrVal
348+
}
349+
for _, svcPort := range svc.Spec.Ports {
350+
p := svcPort
351+
if p.Port == portNumber || (p.Name != "" && p.Name == servicePortName) {
352+
servicePort = &p
353+
break
354+
}
355+
}
356+
if servicePort == nil {
357+
return nil, errors.Errorf("service port %s not found in service %s", backendSvcPort.String(), svc.Name)
358+
}
359+
360+
return servicePort, nil
361+
}
362+
363+
func (t *Translator) translateApisixRouteHTTPBackend(tctx *provider.TranslateContext, ar *apiv2.ApisixRoute, backend apiv2.ApisixRouteHTTPBackend, enableWebsocket **bool) (*adc.Upstream, error) {
336364
auNN := types.NamespacedName{
337365
Namespace: ar.Namespace,
338366
Name: backend.ServiceName,
@@ -354,50 +382,57 @@ func (t *Translator) translateApisixRouteHTTPBackend(tctx *provider.TranslateCon
354382
upstream = u
355383
}
356384
var (
357-
err error
358-
nodes adc.UpstreamNodes
385+
err error
386+
nodes adc.UpstreamNodes
387+
protocol string
359388
)
360389
if backend.ResolveGranularity == apiv2.ResolveGranularityService {
361-
nodes, err = t.translateApisixRouteBackendResolveGranularityService(tctx, auNN, backend)
390+
nodes, protocol, err = t.translateApisixRouteBackendResolveGranularityService(tctx, auNN, backend)
362391
} else {
363-
nodes, err = t.translateApisixRouteBackendResolveGranularityEndpoint(tctx, auNN, backend)
392+
nodes, protocol, err = t.translateApisixRouteBackendResolveGranularityEndpoint(tctx, auNN, backend)
364393
}
365394
if err != nil {
366395
return nil, err
367396
}
368397
upstream.Nodes = nodes
398+
if upstream.Scheme == "" {
399+
upstream.Scheme = appProtocolToUpstreamScheme(protocol)
400+
}
401+
if protocol == internaltypes.AppProtocolWS || protocol == internaltypes.AppProtocolWSS {
402+
*enableWebsocket = ptr.To(true)
403+
}
369404
if backend.Weight != nil {
370405
upstream.Labels["meta_weight"] = strconv.FormatInt(int64(*backend.Weight), 10)
371406
}
372407
return upstream, nil
373408
}
374409

375-
func (t *Translator) translateApisixRouteBackendResolveGranularityService(tctx *provider.TranslateContext, arNN types.NamespacedName, backend apiv2.ApisixRouteHTTPBackend) (adc.UpstreamNodes, error) {
410+
func (t *Translator) translateApisixRouteBackendResolveGranularityService(tctx *provider.TranslateContext, arNN types.NamespacedName, backend apiv2.ApisixRouteHTTPBackend) (adc.UpstreamNodes, string, error) {
376411
serviceNN := types.NamespacedName{
377412
Namespace: arNN.Namespace,
378413
Name: backend.ServiceName,
379414
}
380415
svc, ok := tctx.Services[serviceNN]
381416
if !ok {
382-
return nil, errors.Errorf("service not found, ApisixRoute: %s, Service: %s", arNN, serviceNN)
417+
return nil, "", errors.Errorf("service not found, ApisixRoute: %s, Service: %s", arNN, serviceNN)
383418
}
384419
if svc.Spec.ClusterIP == "" {
385-
return nil, errors.Errorf("conflict headless service and backend resolve granularity, ApisixRoute: %s, Service: %s", arNN, serviceNN)
420+
return nil, "", errors.Errorf("conflict headless service and backend resolve granularity, ApisixRoute: %s, Service: %s", arNN, serviceNN)
386421
}
387-
port, err := getPortFromService(svc, backend.ServicePort)
422+
port, err := findMatchingServicePort(svc, backend.ServicePort)
388423
if err != nil {
389-
return nil, err
424+
return nil, "", err
390425
}
391426
return adc.UpstreamNodes{
392427
{
393428
Host: svc.Spec.ClusterIP,
394-
Port: int(port),
429+
Port: int(port.Port),
395430
Weight: *cmp.Or(backend.Weight, ptr.To(apiv2.DefaultWeight)),
396431
},
397-
}, nil
432+
}, ptr.Deref(port.AppProtocol, ""), nil
398433
}
399434

400-
func (t *Translator) translateApisixRouteStreamBackendResolveGranularity(tctx *provider.TranslateContext, arNN types.NamespacedName, backend apiv2.ApisixRouteStreamBackend) (adc.UpstreamNodes, error) {
435+
func (t *Translator) translateApisixRouteStreamBackendResolveGranularity(tctx *provider.TranslateContext, arNN types.NamespacedName, backend apiv2.ApisixRouteStreamBackend) (adc.UpstreamNodes, string, error) {
401436
tsBackend := apiv2.ApisixRouteHTTPBackend{
402437
ServiceName: backend.ServiceName,
403438
ServicePort: backend.ServicePort,
@@ -411,18 +446,18 @@ func (t *Translator) translateApisixRouteStreamBackendResolveGranularity(tctx *p
411446
}
412447
}
413448

414-
func (t *Translator) translateApisixRouteBackendResolveGranularityEndpoint(tctx *provider.TranslateContext, arNN types.NamespacedName, backend apiv2.ApisixRouteHTTPBackend) (adc.UpstreamNodes, error) {
449+
func (t *Translator) translateApisixRouteBackendResolveGranularityEndpoint(tctx *provider.TranslateContext, arNN types.NamespacedName, backend apiv2.ApisixRouteHTTPBackend) (adc.UpstreamNodes, string, error) {
415450
serviceNN := types.NamespacedName{
416451
Namespace: arNN.Namespace,
417452
Name: backend.ServiceName,
418453
}
419454
svc, ok := tctx.Services[serviceNN]
420455
if !ok {
421-
return nil, errors.Errorf("service not found, ApisixRoute: %s, Service: %s", arNN, serviceNN)
456+
return nil, "", errors.Errorf("service not found, ApisixRoute: %s, Service: %s", arNN, serviceNN)
422457
}
423458
port, err := getPortFromService(svc, backend.ServicePort)
424459
if err != nil {
425-
return nil, err
460+
return nil, "", err
426461
}
427462
weight := int32(*cmp.Or(backend.Weight, ptr.To(apiv2.DefaultWeight)))
428463
backendRef := gatewayv1.BackendRef{
@@ -484,7 +519,7 @@ func (t *Translator) translateApisixRouteStreamBackend(tctx *provider.TranslateC
484519
}
485520
upstream = u
486521
}
487-
nodes, err := t.translateApisixRouteStreamBackendResolveGranularity(tctx, utils.NamespacedName(ar), backend)
522+
nodes, _, err := t.translateApisixRouteStreamBackendResolveGranularity(tctx, utils.NamespacedName(ar), backend)
488523
if err != nil {
489524
return nil, err
490525
}

internal/adc/translator/gatewayproxy.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ func (t *Translator) TranslateGatewayProxyToConfig(tctx *provider.TranslateConte
9898
if endpoint == nil {
9999
return nil, nil
100100
}
101-
upstreamNodes, err := t.TranslateBackendRefWithFilter(tctx, gatewayv1.BackendRef{
101+
upstreamNodes, _, err := t.TranslateBackendRefWithFilter(tctx, gatewayv1.BackendRef{
102102
BackendObjectReference: gatewayv1.BackendObjectReference{
103103
Name: gatewayv1.ObjectName(provider.ControlPlane.Service.Name),
104104
Namespace: (*gatewayv1.Namespace)(&gatewayProxy.Namespace),

internal/adc/translator/grpcroute.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ func (t *Translator) TranslateGRPCRoute(tctx *provider.TranslateContext, grpcRou
183183
backend.Namespace = &namespace
184184
}
185185
upstream := adctypes.NewDefaultUpstream()
186-
upNodes, err := t.translateBackendRef(tctx, backend.BackendRef, DefaultEndpointFilter)
186+
upNodes, _, err := t.translateBackendRef(tctx, backend.BackendRef, DefaultEndpointFilter)
187187
if err != nil {
188188
backendErr = err
189189
continue

internal/adc/translator/httproute.go

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -392,13 +392,15 @@ func DefaultEndpointFilter(endpoint *discoveryv1.Endpoint) bool {
392392
return true
393393
}
394394

395-
func (t *Translator) TranslateBackendRefWithFilter(tctx *provider.TranslateContext, ref gatewayv1.BackendRef, endpointFilter func(*discoveryv1.Endpoint) bool) (adctypes.UpstreamNodes, error) {
395+
func (t *Translator) TranslateBackendRefWithFilter(tctx *provider.TranslateContext, ref gatewayv1.BackendRef, endpointFilter func(*discoveryv1.Endpoint) bool) (adctypes.UpstreamNodes, string, error) {
396396
return t.translateBackendRef(tctx, ref, endpointFilter)
397397
}
398398

399-
func (t *Translator) translateBackendRef(tctx *provider.TranslateContext, ref gatewayv1.BackendRef, endpointFilter func(*discoveryv1.Endpoint) bool) (adctypes.UpstreamNodes, error) {
399+
func (t *Translator) translateBackendRef(tctx *provider.TranslateContext, ref gatewayv1.BackendRef, endpointFilter func(*discoveryv1.Endpoint) bool) (adctypes.UpstreamNodes, string, error) {
400+
nodes := adctypes.UpstreamNodes{}
401+
var protocol string
400402
if ref.Kind != nil && *ref.Kind != internaltypes.KindService {
401-
return adctypes.UpstreamNodes{}, fmt.Errorf("kind %s is not supported", *ref.Kind)
403+
return nodes, protocol, fmt.Errorf("kind %s is not supported", *ref.Kind)
402404
}
403405

404406
key := types.NamespacedName{
@@ -407,7 +409,7 @@ func (t *Translator) translateBackendRef(tctx *provider.TranslateContext, ref ga
407409
}
408410
service, ok := tctx.Services[key]
409411
if !ok {
410-
return adctypes.UpstreamNodes{}, fmt.Errorf("service %s not found", key)
412+
return nodes, protocol, fmt.Errorf("service %s not found", key)
411413
}
412414

413415
weight := 1
@@ -426,24 +428,26 @@ func (t *Translator) translateBackendRef(tctx *provider.TranslateContext, ref ga
426428
Port: port,
427429
Weight: weight,
428430
},
429-
}, nil
431+
}, protocol, nil
430432
}
431433

432434
var portName *string
433435
if ref.Port != nil {
434436
for _, p := range service.Spec.Ports {
435437
if int(p.Port) == int(*ref.Port) {
436438
portName = ptr.To(p.Name)
439+
protocol = ptr.Deref(p.AppProtocol, "")
437440
break
438441
}
439442
}
440443
if portName == nil {
441-
return adctypes.UpstreamNodes{}, nil
444+
return adctypes.UpstreamNodes{}, protocol, nil
442445
}
443446
}
444447

445448
endpointSlices := tctx.EndpointSlices[key]
446-
return t.translateEndpointSlice(portName, weight, endpointSlices, endpointFilter), nil
449+
nodes = t.translateEndpointSlice(portName, weight, endpointSlices, endpointFilter)
450+
return nodes, protocol, nil
447451
}
448452

449453
// calculateHTTPRoutePriority calculates the priority of the HTTP route.
@@ -545,6 +549,7 @@ func (t *Translator) TranslateHTTPRoute(tctx *provider.TranslateContext, httpRou
545549
upstreams = make([]*adctypes.Upstream, 0)
546550
weightedUpstreams = make([]adctypes.TrafficSplitConfigRuleWeightedUpstream, 0)
547551
backendErr error
552+
enableWebsocket *bool
548553
)
549554

550555
for _, backend := range rule.BackendRefs {
@@ -553,18 +558,21 @@ func (t *Translator) TranslateHTTPRoute(tctx *provider.TranslateContext, httpRou
553558
backend.Namespace = &namespace
554559
}
555560
upstream := adctypes.NewDefaultUpstream()
556-
upNodes, err := t.translateBackendRef(tctx, backend.BackendRef, DefaultEndpointFilter)
561+
upNodes, protocol, err := t.translateBackendRef(tctx, backend.BackendRef, DefaultEndpointFilter)
557562
if err != nil {
558563
backendErr = err
559564
continue
560565
}
561566
if len(upNodes) == 0 {
562567
continue
563568
}
569+
if protocol == internaltypes.AppProtocolWS || protocol == internaltypes.AppProtocolWSS {
570+
enableWebsocket = ptr.To(true)
571+
}
564572

565573
t.AttachBackendTrafficPolicyToUpstream(backend.BackendRef, tctx.BackendTrafficPolicies, upstream)
566574
upstream.Nodes = upNodes
567-
575+
upstream.Scheme = appProtocolToUpstreamScheme(protocol)
568576
var (
569577
kind string
570578
port int32
@@ -685,7 +693,7 @@ func (t *Translator) TranslateHTTPRoute(tctx *provider.TranslateContext, httpRou
685693
route.Name = name
686694
route.ID = id.GenID(name)
687695
route.Labels = labels
688-
route.EnableWebsocket = ptr.To(true)
696+
route.EnableWebsocket = enableWebsocket
689697

690698
// Set the route priority
691699
priority := calculateHTTPRoutePriority(&match, ruleIndex, hosts)
@@ -827,3 +835,18 @@ func (t *Translator) translateHTTPRouteHeaderMatchToVars(header gatewayv1.HTTPHe
827835
}
828836
return HeaderMatchToVars(matchType, string(header.Name), header.Value)
829837
}
838+
839+
func appProtocolToUpstreamScheme(appProtocol string) string {
840+
switch appProtocol {
841+
case internaltypes.AppProtocolHTTP:
842+
return apiv2.SchemeHTTP
843+
case internaltypes.AppProtocolHTTPS:
844+
return apiv2.SchemeHTTPS
845+
case internaltypes.AppProtocolWS:
846+
return apiv2.SchemeHTTP
847+
case internaltypes.AppProtocolWSS:
848+
return apiv2.SchemeHTTPS
849+
default:
850+
return ""
851+
}
852+
}

0 commit comments

Comments
 (0)