Skip to content
Merged
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
2 changes: 1 addition & 1 deletion internal/adc/translator/grpcroute.go
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ func (t *Translator) TranslateGRPCRoute(tctx *provider.TranslateContext, grpcRou
continue
}

t.AttachBackendTrafficPolicyToUpstream(backend.BackendRef, tctx.BackendTrafficPolicies, upstream)
t.AttachBackendTrafficPolicyToUpstream(backend.BackendRef, tctx.BackendTrafficPolicies, upstream, tctx.Services)
upstream.Nodes = upNodes

var (
Expand Down
2 changes: 1 addition & 1 deletion internal/adc/translator/httproute.go
Original file line number Diff line number Diff line change
Expand Up @@ -555,7 +555,7 @@ func (t *Translator) translateBackendsToUpstreams(
enableWebsocket = ptr.To(true)
}

t.AttachBackendTrafficPolicyToUpstream(backend.BackendRef, tctx.BackendTrafficPolicies, upstream)
t.AttachBackendTrafficPolicyToUpstream(backend.BackendRef, tctx.BackendTrafficPolicies, upstream, tctx.Services)
upstream.Nodes = upNodes
if upstream.Scheme == "" {
upstream.Scheme = appProtocolToUpstreamScheme(protocol)
Expand Down
122 changes: 122 additions & 0 deletions internal/adc/translator/httproute_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -506,3 +506,125 @@ func TestAttachBackendTrafficPolicyHealthCheck(t *testing.T) {
})
}
}

func TestAttachBackendTrafficPolicyToUpstreamSectionName(t *testing.T) {
const (
namespace = "default"
serviceName = "backend"
webPort = int32(80)
webName = "web"
adminPort = int32(9000)
adminName = "admin"
)

serviceKey := types.NamespacedName{Namespace: namespace, Name: serviceName}
services := map[types.NamespacedName]*corev1.Service{
serviceKey: {
ObjectMeta: metav1.ObjectMeta{Name: serviceName, Namespace: namespace},
Spec: corev1.ServiceSpec{
Ports: []corev1.ServicePort{
{Name: webName, Port: webPort},
{Name: adminName, Port: adminPort},
},
},
},
}

newRef := func(port int32) gatewayv1.BackendRef {
return gatewayv1.BackendRef{
BackendObjectReference: gatewayv1.BackendObjectReference{
Name: gatewayv1.ObjectName(serviceName),
Namespace: ptr.To(gatewayv1.Namespace(namespace)),
Port: ptr.To(gatewayv1.PortNumber(port)),
},
}
}

newPolicy := func(name, sectionName, scheme string) *v1alpha1.BackendTrafficPolicy {
targetRef := v1alpha1.BackendPolicyTargetReferenceWithSectionName{
LocalPolicyTargetReference: gatewayv1alpha2.LocalPolicyTargetReference{
Name: gatewayv1alpha2.ObjectName(serviceName),
Kind: gatewayv1alpha2.Kind(internaltypes.KindService),
},
}
if sectionName != "" {
targetRef.SectionName = ptr.To(gatewayv1alpha2.SectionName(sectionName))
}
return &v1alpha1.BackendTrafficPolicy{
ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace},
Spec: v1alpha1.BackendTrafficPolicySpec{
TargetRefs: []v1alpha1.BackendPolicyTargetReferenceWithSectionName{targetRef},
Scheme: scheme,
},
}
}

tests := []struct {
name string
ref gatewayv1.BackendRef
policies map[types.NamespacedName]*v1alpha1.BackendTrafficPolicy
wantScheme string
}{
{
name: "sectionName matches the backend port name",
ref: newRef(webPort),
policies: map[types.NamespacedName]*v1alpha1.BackendTrafficPolicy{
{Namespace: namespace, Name: "p"}: newPolicy("p", webName, apiv2.SchemeHTTPS),
},
wantScheme: apiv2.SchemeHTTPS,
},
{
name: "sectionName does not match the backend port name",
ref: newRef(adminPort),
policies: map[types.NamespacedName]*v1alpha1.BackendTrafficPolicy{
{Namespace: namespace, Name: "p"}: newPolicy("p", webName, apiv2.SchemeHTTPS),
},
wantScheme: "",
},
{
name: "no sectionName applies to the whole service",
ref: newRef(adminPort),
policies: map[types.NamespacedName]*v1alpha1.BackendTrafficPolicy{
{Namespace: namespace, Name: "p"}: newPolicy("p", "", apiv2.SchemeHTTPS),
},
wantScheme: apiv2.SchemeHTTPS,
},
{
name: "port-specific policy takes precedence over whole-service policy",
ref: newRef(adminPort),
policies: map[types.NamespacedName]*v1alpha1.BackendTrafficPolicy{
{Namespace: namespace, Name: "generic"}: newPolicy("generic", "", apiv2.SchemeHTTP),
{Namespace: namespace, Name: "specific"}: newPolicy("specific", adminName, apiv2.SchemeHTTPS),
},
wantScheme: apiv2.SchemeHTTPS,
},
{
name: "targetRef kind mismatch does not attach to a same-named service",
ref: newRef(webPort),
policies: map[types.NamespacedName]*v1alpha1.BackendTrafficPolicy{
{Namespace: namespace, Name: "p"}: {
ObjectMeta: metav1.ObjectMeta{Name: "p", Namespace: namespace},
Spec: v1alpha1.BackendTrafficPolicySpec{
TargetRefs: []v1alpha1.BackendPolicyTargetReferenceWithSectionName{{
LocalPolicyTargetReference: gatewayv1alpha2.LocalPolicyTargetReference{
Name: gatewayv1alpha2.ObjectName(serviceName),
Kind: gatewayv1alpha2.Kind("ServiceImport"),
},
}},
Scheme: apiv2.SchemeHTTPS,
},
},
},
wantScheme: "",
},
}

translator := NewTranslator(logr.Discard(), "")
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
upstream := adctypes.NewDefaultUpstream()
translator.AttachBackendTrafficPolicyToUpstream(tt.ref, tt.policies, upstream, services)
assert.Equal(t, tt.wantScheme, upstream.Scheme)
})
}
}
2 changes: 1 addition & 1 deletion internal/adc/translator/ingress.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ func (t *Translator) resolveIngressUpstream(
ns = config.ServiceNamespace
}
backendRef := convertBackendRef(ns, backendService.Name, internaltypes.KindService)
t.AttachBackendTrafficPolicyToUpstream(backendRef, tctx.BackendTrafficPolicies, upstream)
t.AttachBackendTrafficPolicyToUpstream(backendRef, tctx.BackendTrafficPolicies, upstream, tctx.Services)
if config != nil {
upConfig := config.Upstream
if upConfig.Scheme != "" {
Expand Down
61 changes: 56 additions & 5 deletions internal/adc/translator/policies.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ package translator
import (
"encoding/json"

corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/utils/ptr"
gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
Expand All @@ -28,6 +29,7 @@ import (
adctypes "github.com/apache/apisix-ingress-controller/api/adc"
"github.com/apache/apisix-ingress-controller/api/v1alpha1"
apiv2 "github.com/apache/apisix-ingress-controller/api/v2"
internaltypes "github.com/apache/apisix-ingress-controller/internal/types"
)

func convertBackendRef(namespace, name, kind string) gatewayv1.BackendRef {
Expand All @@ -38,28 +40,77 @@ func convertBackendRef(namespace, name, kind string) gatewayv1.BackendRef {
return backendRef
}

func (t *Translator) AttachBackendTrafficPolicyToUpstream(ref gatewayv1.BackendRef, policies map[types.NamespacedName]*v1alpha1.BackendTrafficPolicy, upstream *adctypes.Upstream) {
func (t *Translator) AttachBackendTrafficPolicyToUpstream(ref gatewayv1.BackendRef, policies map[types.NamespacedName]*v1alpha1.BackendTrafficPolicy, upstream *adctypes.Upstream, services map[types.NamespacedName]*corev1.Service) {
if len(policies) == 0 {
return
}
var policy *v1alpha1.BackendTrafficPolicy
// Resolve the backend ref group/kind, applying the Gateway API defaults
// (empty group = core, Service kind) so a targetRef is only matched against
// a backend of the same resource type.
refGroup := ""
if ref.Group != nil {
refGroup = string(*ref.Group)
}
refKind := internaltypes.KindService
if ref.Kind != nil {
refKind = string(*ref.Kind)
}
// A targetRef with sectionName scopes the policy to a specific Service port
// (matched by port name). It takes precedence over a whole-Service targetRef
// (no sectionName) that matches the same backend.
var genericPolicy, specificPolicy *v1alpha1.BackendTrafficPolicy
for _, po := range policies {
if ref.Namespace != nil && string(*ref.Namespace) != po.Namespace {
continue
}
for _, targetRef := range po.Spec.TargetRefs {
if ref.Name == targetRef.Name {
policy = po
break
if ref.Name != targetRef.Name {
continue
}
if targetRef.Group != "" && string(targetRef.Group) != refGroup {
continue
}
if targetRef.Kind != "" && string(targetRef.Kind) != refKind {
continue
}
if targetRef.SectionName != nil && *targetRef.SectionName != "" {
if backendRefMatchesSectionName(ref, po.Namespace, string(*targetRef.SectionName), services) {
specificPolicy = po
}
continue
}
genericPolicy = po
}
}
policy := specificPolicy
if policy == nil {
policy = genericPolicy
}
if policy == nil {
return
}
t.attachBackendTrafficPolicyToUpstream(policy, upstream)
}

// backendRefMatchesSectionName reports whether the backend ref resolves to the
// Service port named sectionName. Per the Gateway API policy semantics, when a
// sectionName is specified but cannot be resolved, the policy must not attach.
func backendRefMatchesSectionName(ref gatewayv1.BackendRef, namespace, sectionName string, services map[types.NamespacedName]*corev1.Service) bool {
if ref.Port == nil {
return false
}
svc, ok := services[types.NamespacedName{Namespace: namespace, Name: string(ref.Name)}]
if !ok || svc == nil {
return false
}
for _, port := range svc.Spec.Ports {
if port.Port == int32(*ref.Port) {
return port.Name == sectionName
}
}
return false
}

func (t *Translator) attachBackendTrafficPolicyToUpstream(policy *v1alpha1.BackendTrafficPolicy, upstream *adctypes.Upstream) {
if policy == nil {
return
Expand Down
2 changes: 1 addition & 1 deletion internal/adc/translator/tcproute.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ func (t *Translator) TranslateTCPRoute(tctx *provider.TranslateContext, tcpRoute
continue
}
// TODO: Confirm BackendTrafficPolicy attachment with e2e test case.
t.AttachBackendTrafficPolicyToUpstream(backend, tctx.BackendTrafficPolicies, upstream)
t.AttachBackendTrafficPolicyToUpstream(backend, tctx.BackendTrafficPolicies, upstream, tctx.Services)
upstream.Nodes = upNodes
var (
kind string
Expand Down
2 changes: 1 addition & 1 deletion internal/adc/translator/tlsroute.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ func (t *Translator) TranslateTLSRoute(tctx *provider.TranslateContext, tlsRoute
continue
}
// TODO: Confirm BackendTrafficPolicy attachment with e2e test case.
t.AttachBackendTrafficPolicyToUpstream(backend, tctx.BackendTrafficPolicies, upstream)
t.AttachBackendTrafficPolicyToUpstream(backend, tctx.BackendTrafficPolicies, upstream, tctx.Services)
upstream.Nodes = upNodes
var (
kind string
Expand Down
2 changes: 1 addition & 1 deletion internal/adc/translator/udproute.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ func (t *Translator) TranslateUDPRoute(tctx *provider.TranslateContext, udpRoute
continue
}
// TODO: Confirm BackendTrafficPolicy attachment with e2e test case.
t.AttachBackendTrafficPolicyToUpstream(backend, tctx.BackendTrafficPolicies, upstream)
t.AttachBackendTrafficPolicyToUpstream(backend, tctx.BackendTrafficPolicies, upstream, tctx.Services)
upstream.Nodes = upNodes
var (
kind string
Expand Down
9 changes: 8 additions & 1 deletion internal/controller/policies.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,14 @@ import (
type PolicyTargetKey struct {
NsName types.NamespacedName
GroupKind schema.GroupKind
// SectionName scopes the target to a specific section (for a Service, the
// port name). Policies that target different sections of the same resource
// do not conflict; an empty SectionName targets the whole resource.
SectionName string
}

func (p PolicyTargetKey) String() string {
return p.NsName.String() + "/" + p.GroupKind.String()
return p.NsName.String() + "/" + p.GroupKind.String() + "/" + p.SectionName
}

func BackendTrafficPolicyPredicateFunc(channel chan event.GenericEvent) predicate.Predicate {
Expand Down Expand Up @@ -143,6 +147,9 @@ func ProcessBackendTrafficPolicy(
NsName: types.NamespacedName{Namespace: p.GetNamespace(), Name: string(targetRef.Name)},
GroupKind: schema.GroupKind{Group: "", Kind: internaltypes.KindService},
}
if sectionName != nil {
key.SectionName = string(*sectionName)
}
condition := NewPolicyCondition(policy.Generation, true, "Policy has been accepted")
if sectionName != nil && !servicePortNameMap[fmt.Sprintf("%s/%s/%s", policy.Namespace, string(targetRef.Name), *sectionName)] {
condition = NewPolicyCondition(policy.Generation, false, fmt.Sprintf("No section name %s found in Service %s/%s", *sectionName, policy.Namespace, targetRef.Name))
Expand Down
Loading
Loading