Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions config/samples/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ exec_adc_timeout: 15s # The timeout for the ADC to execute.
disable_gateway_api: false # Whether to disable the Gateway API support.
# The default value is false.

listener_port_match_mode: "auto" # Mode for injecting server_port route vars from Gateway listener ports.
# - "auto": inject when parentRefs explicitly target listeners (sectionName/port) or when multiple listener ports are matched.
# - "explicit": inject only when parentRefs explicitly target listeners.
# - "off": never inject server_port vars.
# The default value is "auto".

provider:
type: "api7ee"

Expand Down
249 changes: 248 additions & 1 deletion internal/adc/translator/annotations_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,13 @@ import (

"github.com/incubator4/go-resty-expr/expr"
"github.com/stretchr/testify/assert"
"k8s.io/utils/ptr"
gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"

adctypes "github.com/apache/apisix-ingress-controller/api/adc"
"github.com/apache/apisix-ingress-controller/internal/adc/translator/annotations"
"github.com/apache/apisix-ingress-controller/internal/adc/translator/annotations/upstream"
"github.com/apache/apisix-ingress-controller/internal/controller/config"
)

type mockParser struct {
Expand Down Expand Up @@ -342,11 +345,255 @@ func TestTranslateIngressAnnotations(t *testing.T) {

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
translator := &Translator{}
translator := &Translator{ListenerPortMatchMode: config.ListenerPortMatchModeAuto}
result := translator.TranslateIngressAnnotations(tt.anno)

assert.NotNil(t, result)
assert.Equal(t, tt.expected, result)
})
}
}

func TestAddServerPortVars(t *testing.T) {
tests := []struct {
name string
route *adctypes.Route
ports map[int32]struct{}
expected adctypes.Vars
}{
{
name: "empty ports map - no vars added",
route: &adctypes.Route{},
ports: map[int32]struct{}{},
expected: adctypes.Vars(nil),
},
{
name: "single port - uses == operator",
route: &adctypes.Route{},
ports: map[int32]struct{}{
9080: {},
},
expected: adctypes.Vars{
{
{StrVal: "server_port"},
{StrVal: "=="},
{StrVal: "9080"},
},
},
},
{
name: "two ports - uses 'in' operator",
route: &adctypes.Route{},
ports: map[int32]struct{}{
9080: {},
9081: {},
},
expected: adctypes.Vars{
{
{StrVal: "server_port"},
{StrVal: "in"},
{SliceVal: []adctypes.StringOrSlice{
{StrVal: "9080"},
{StrVal: "9081"},
}},
},
},
},
{
name: "three ports - uses 'in' operator",
route: &adctypes.Route{},
ports: map[int32]struct{}{
80: {},
443: {},
9080: {},
},
expected: adctypes.Vars{
{
{StrVal: "server_port"},
{StrVal: "in"},
{SliceVal: []adctypes.StringOrSlice{
{StrVal: "80"},
{StrVal: "443"},
{StrVal: "9080"},
}},
},
},
},
{
name: "vars are appended - preserves existing vars",
route: &adctypes.Route{
Vars: adctypes.Vars{
{
{StrVal: "uri"},
{StrVal: "~~"},
{StrVal: "^/api"},
},
},
},
ports: map[int32]struct{}{
9080: {},
},
expected: adctypes.Vars{
{
{StrVal: "uri"},
{StrVal: "~~"},
{StrVal: "^/api"},
},
{
{StrVal: "server_port"},
{StrVal: "=="},
{StrVal: "9080"},
},
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
addServerPortVars(tt.route, tt.ports)
assert.Equal(t, tt.expected, tt.route.Vars)
})
}
}

func TestShouldInjectServerPortVars(t *testing.T) {
sectionName := gatewayv1.SectionName("http-main")
port := gatewayv1.PortNumber(9080)

tests := []struct {
name string
mode config.ListenerPortMatchMode
parentRefs []gatewayv1.ParentReference
ports map[int32]struct{}
expected bool
}{
{
name: "empty listener ports",
mode: config.ListenerPortMatchModeAuto,
ports: map[int32]struct{}{},
expected: false,
},
{
name: "single port without sectionName",
mode: config.ListenerPortMatchModeAuto,
parentRefs: []gatewayv1.ParentReference{
{Name: "gw"},
},
ports: map[int32]struct{}{
9080: {},
},
expected: false,
},
{
name: "single port with sectionName",
mode: config.ListenerPortMatchModeAuto,
parentRefs: []gatewayv1.ParentReference{
{Name: "gw", SectionName: &sectionName},
},
ports: map[int32]struct{}{
9080: {},
},
expected: true,
},
{
name: "multiple ports without sectionName",
mode: config.ListenerPortMatchModeAuto,
parentRefs: []gatewayv1.ParentReference{
{Name: "gw"},
},
ports: map[int32]struct{}{
9080: {},
9081: {},
},
expected: true,
},
{
name: "explicit mode with multiple ports and no explicit target",
mode: config.ListenerPortMatchModeExplicit,
parentRefs: []gatewayv1.ParentReference{
{Name: "gw"},
},
ports: map[int32]struct{}{
9080: {},
9081: {},
},
expected: false,
},
{
name: "explicit mode with parentRef.port",
mode: config.ListenerPortMatchModeExplicit,
parentRefs: []gatewayv1.ParentReference{
{Name: "gw", Port: &port},
},
ports: map[int32]struct{}{
9080: {},
},
expected: true,
},
{
name: "explicit mode with single port and no explicit target",
mode: config.ListenerPortMatchModeExplicit,
parentRefs: []gatewayv1.ParentReference{
{Name: "gw"},
},
ports: map[int32]struct{}{
9080: {},
},
expected: false,
},
{
name: "off mode ignores explicit target",
mode: config.ListenerPortMatchModeOff,
parentRefs: []gatewayv1.ParentReference{
{Name: "gw", SectionName: &sectionName},
},
ports: map[int32]struct{}{
9080: {},
9081: {},
},
expected: false,
},
{
name: "off mode ignores explicit parentRef.port target",
mode: config.ListenerPortMatchModeOff,
parentRefs: []gatewayv1.ParentReference{
{Name: "gw", Port: &port},
},
ports: map[int32]struct{}{
9080: {},
},
expected: false,
},
{
name: "explicit mode: non-Gateway parentRef with port is not treated as explicit target",
mode: config.ListenerPortMatchModeExplicit,
parentRefs: []gatewayv1.ParentReference{
{Name: "gw"},
{Name: "svc", Kind: ptr.To(gatewayv1.Kind("Service")), Port: &port},
},
ports: map[int32]struct{}{
9080: {},
},
expected: false,
},
{
name: "auto mode: non-Gateway parentRef with port does not trigger single-port injection",
mode: config.ListenerPortMatchModeAuto,
parentRefs: []gatewayv1.ParentReference{
{Name: "gw"},
{Name: "svc", Kind: ptr.To(gatewayv1.Kind("Service")), Port: &port},
},
ports: map[int32]struct{}{
9080: {},
},
expected: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
translator := &Translator{ListenerPortMatchMode: tt.mode}
assert.Equal(t, tt.expected, translator.shouldInjectServerPortVars(tt.parentRefs, tt.ports))
})
}
}
4 changes: 2 additions & 2 deletions internal/adc/translator/apisixconsumer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import (
)

func TestTranslateApisixConsumer_UsesMetadataLabelsWithoutOverwritingControllerLabels(t *testing.T) {
translator := NewTranslator(logr.Discard())
translator := NewTranslator(logr.Discard(), "")
tctx := provider.NewDefaultTranslateContext(context.Background())

consumer := &apiv2.ApisixConsumer{
Expand All @@ -49,7 +49,7 @@ func TestTranslateApisixConsumer_UsesMetadataLabelsWithoutOverwritingControllerL
},
},
Spec: apiv2.ApisixConsumerSpec{
AuthParameter: apiv2.ApisixConsumerAuthParameter{
AuthParameter: &apiv2.ApisixConsumerAuthParameter{
BasicAuth: &apiv2.ApisixConsumerBasicAuth{
Value: &apiv2.ApisixConsumerBasicAuthValue{
Username: "demo",
Expand Down
6 changes: 3 additions & 3 deletions internal/adc/translator/apisixroute_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import (
)

func TestBuildRoute_HostsNotSet(t *testing.T) {
translator := NewTranslator(logr.Discard())
translator := NewTranslator(logr.Discard(), "")

ar := &apiv2.ApisixRoute{
ObjectMeta: metav1.ObjectMeta{
Expand Down Expand Up @@ -60,7 +60,7 @@ func TestBuildRoute_HostsNotSet(t *testing.T) {
}

func TestBuildService_HostsSet(t *testing.T) {
translator := NewTranslator(logr.Discard())
translator := NewTranslator(logr.Discard(), "")

ar := &apiv2.ApisixRoute{
ObjectMeta: metav1.ObjectMeta{
Expand All @@ -84,7 +84,7 @@ func TestBuildService_HostsSet(t *testing.T) {
}

func TestBuildRoute_MetadataLabelsDoNotOverwriteControllerLabels(t *testing.T) {
translator := NewTranslator(logr.Discard())
translator := NewTranslator(logr.Discard(), "")

ar := &apiv2.ApisixRoute{
TypeMeta: metav1.TypeMeta{
Expand Down
2 changes: 1 addition & 1 deletion internal/adc/translator/consumer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import (
)

func TestTranslateConsumerV1alpha1_UsesMetadataLabelsWithoutOverwritingControllerLabels(t *testing.T) {
translator := NewTranslator(logr.Discard())
translator := NewTranslator(logr.Discard(), "")
tctx := provider.NewDefaultTranslateContext(context.Background())

consumer := &v1alpha1.Consumer{
Expand Down
13 changes: 13 additions & 0 deletions internal/adc/translator/grpcroute.go
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,19 @@ func (t *Translator) TranslateGRPCRoute(tctx *provider.TranslateContext, grpcRou

routes = append(routes, route)
}

// Collect unique listener ports for port-based routing.
listenerPorts := make(map[int32]struct{})
for _, listener := range tctx.Listeners {
listenerPorts[int32(listener.Port)] = struct{}{}
}

if t.shouldInjectServerPortVars(tctx.RouteParentRefs, listenerPorts) {
for _, route := range routes {
addServerPortVars(route, listenerPorts)
}
}

service.Routes = routes

result.Services = append(result.Services, service)
Expand Down
Loading
Loading