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
4 changes: 2 additions & 2 deletions internal/gatewayapi/backendtrafficpolicy.go
Original file line number Diff line number Diff line change
Expand Up @@ -1008,7 +1008,7 @@ func (t *Translator) processBackendTrafficPolicyForRoute(
}

// Do need a section name since the policy is targeting to a route.
ancestorRef := getAncestorRefForPolicy(parentNN, p.SectionName)
ancestorRef := getAncestorRefForRouteParent(parentNN, p)
ancestorRefs = append(ancestorRefs, &ancestorRef)
if parentRefCtx := targetedRoute.GetRouteParentContext(p); parentRefCtx != nil {
parentRefCtxs = append(parentRefCtxs, parentRefCtx)
Expand Down Expand Up @@ -1038,7 +1038,7 @@ func (t *Translator) processBackendTrafficPolicyForRoute(

// ListenerSet-attached Route policies report status against the
// ListenerSet itself.
ancestorRef := getAncestorRefForListenerSetPolicy(parentNN, p.SectionName)
ancestorRef := getAncestorRefForRouteParent(parentNN, p)
ancestorRefs = append(ancestorRefs, &ancestorRef)
if parentRefCtx := targetedRoute.GetRouteParentContext(p); parentRefCtx != nil {
parentRefCtxs = append(parentRefCtxs, parentRefCtx)
Expand Down
5 changes: 1 addition & 4 deletions internal/gatewayapi/contexts.go
Original file line number Diff line number Diff line change
Expand Up @@ -844,10 +844,7 @@ func (r *RouteParentContext) PolicyAncestorRef() gwapiv1.ParentReference {
Namespace: NamespaceDerefOr(r.Namespace, r.routeNamespace()),
Name: string(r.Name),
}
if r.Kind != nil && *r.Kind == resource.KindListenerSet {
return getAncestorRefForListenerSetPolicy(parentNN, r.SectionName)
}
return getAncestorRefForPolicy(parentNN, r.SectionName)
return getAncestorRefForRouteParent(parentNN, *r.ParentReference)
}

func (r *RouteParentContext) routeNamespace() string {
Expand Down
4 changes: 2 additions & 2 deletions internal/gatewayapi/envoyextensionpolicy.go
Original file line number Diff line number Diff line change
Expand Up @@ -457,7 +457,7 @@ func (t *Translator) processEnvoyExtensionPolicyForRoute(
}

// Do need a section name since the policy is targeting to a route
ancestorRef := getAncestorRefForPolicy(parentNN, p.SectionName)
ancestorRef := getAncestorRefForRouteParent(parentNN, p)
ancestorRefs = append(ancestorRefs, &ancestorRef)

// Only process parentRefs that were handled by this translator
Expand Down Expand Up @@ -488,7 +488,7 @@ func (t *Translator) processEnvoyExtensionPolicyForRoute(

// ListenerSet-attached Route policies report status against the
// ListenerSet itself.
ancestorRef := getAncestorRefForListenerSetPolicy(parentNN, p.SectionName)
ancestorRef := getAncestorRefForRouteParent(parentNN, p)
ancestorRefs = append(ancestorRefs, &ancestorRef)

// Only process parentRefs that were handled by this translator
Expand Down
12 changes: 6 additions & 6 deletions internal/gatewayapi/extensionserverpolicy.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (

"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/sets"
Expand Down Expand Up @@ -340,8 +341,7 @@ func (t *Translator) processExtensionServerPolicyForRoute(
}

policyStatus := ExtServerPolicyStatusAsPolicyStatus(policy)
gatewayNN := utils.NamespacedName(gtwCtx)
ancestorRef := getAncestorRefForPolicy(gatewayNN, p.SectionName)
ancestorRef := parentRefCtx.PolicyAncestorRef()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Truncate extension-policy ancestors after splitting by port

When an extension-server policy (for example, via a target selector) covers 17 or more routes whose parentRefs select distinct ports, this port-preserving reference makes SetConditionForPolicyAncestor retain one ancestor per port. That setter intentionally allows 17 entries so a later TruncatePolicyAncestors call can add the Aggregated condition, but ProcessExtensionServerPolicies returns without the truncation performed by the typed policy processors; the emitted status can therefore exceed the Gateway API limit of 16 ancestors and be rejected by a policy CRD using the standard status schema. Truncate the converted unstructured policy statuses before returning them.

Useful? React with 👍 / 👎.


if !supportedRouteKind {
status.SetTranslationErrorForPolicyAncestor(
Expand Down Expand Up @@ -529,10 +529,10 @@ func resolveExtServerPolicyRouteTargetRef(
}

func PolicyStatusToUnstructured(policyStatus gwapiv1.PolicyStatus) map[string]any {
ret := map[string]any{}
// No need to check the marshal/unmarshal error here
d, _ := json.Marshal(policyStatus)
_ = json.Unmarshal(d, &ret)
// Use the unstructured converter rather than a JSON round trip, so that numbers such as the port of an
// ancestorRef are int64 as in any unstructured object, not float64.
// No need to check the error here, the policyStatus is a typed object.
ret, _ := runtime.DefaultUnstructuredConverter.ToUnstructured(&policyStatus)
return ret
}

Expand Down
14 changes: 14 additions & 0 deletions internal/gatewayapi/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -789,6 +789,20 @@ func getAncestorRefForListenerSetPolicy(lsNN types.NamespacedName, sectionName *
}
}

// getAncestorRefForRouteParent returns the Gateway or ListenerSet that a route parentRef refers to
// as an ancestor reference for a policy targeting the route. It keeps the sectionName and port of
// the parentRef, so that parentRefs to different listeners of the same parent get their own ancestors.
func getAncestorRefForRouteParent(parentNN types.NamespacedName, parentRef gwapiv1.ParentReference) gwapiv1.ParentReference {
var ref gwapiv1.ParentReference
if parentRef.Kind != nil && *parentRef.Kind == resource.KindListenerSet {
ref = getAncestorRefForListenerSetPolicy(parentNN, parentRef.SectionName)
} else {
ref = getAncestorRefForPolicy(parentNN, parentRef.SectionName)
}
ref.Port = parentRef.Port
Comment thread
kkk777-7 marked this conversation as resolved.
Resolved
return ref
}

// getAncestorRefForGatewayClassPolicy returns a GatewayClass as an ancestor reference for policy.
// Used when a policy directly targets a GatewayClass, which is only meaningful when the Gateways
// under that class are merged into a single xDS IR.
Expand Down
4 changes: 2 additions & 2 deletions internal/gatewayapi/securitypolicy.go
Original file line number Diff line number Diff line change
Expand Up @@ -470,7 +470,7 @@ func (t *Translator) processSecurityPolicyForRoute(
}

// Do need a section name since the policy is targeting to a route.
ancestorRef := getAncestorRefForPolicy(parentNN, p.SectionName)
ancestorRef := getAncestorRefForRouteParent(parentNN, p)
ancestorRefs = append(ancestorRefs, &ancestorRef)

// Only process parentRefs that were handled by this translator
Expand Down Expand Up @@ -502,7 +502,7 @@ func (t *Translator) processSecurityPolicyForRoute(

// ListenerSet-attached Route policies report status against the
// ListenerSet itself.
ancestorRef := getAncestorRefForListenerSetPolicy(parentNN, p.SectionName)
ancestorRef := getAncestorRefForRouteParent(parentNN, p)
ancestorRefs = append(ancestorRefs, &ancestorRef)

// Only process parentRefs that were handled by this translator
Expand Down
8 changes: 8 additions & 0 deletions internal/gatewayapi/status/policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,14 @@ func ancestorRefsEqual(a, b *gwapiv1.ParentReference) bool {
return false
}

// Compare Port pointers
if (a.Port == nil) != (b.Port == nil) {
return false
}
if a.Port != nil && *a.Port != *b.Port {
return false
}

return true
}

Expand Down
33 changes: 33 additions & 0 deletions internal/gatewayapi/status/policy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,39 @@ func TestSetWarningForPolicyAncestorMergesWarnings(t *testing.T) {
}
}

// TestSetConditionForPolicyAncestorPort verifies that ancestors differing only by port are kept
// apart, as route parentRefs to the same Gateway differing only by port attach to different listeners.
func TestSetConditionForPolicyAncestorPort(t *testing.T) {
const controller = "example.com/controller"
policyStatus := &gwapiv1.PolicyStatus{}

refs := []*gwapiv1.ParentReference{
{Name: "gw"},
{Name: "gw", Port: new(gwapiv1.PortNumber(80))},
{Name: "gw", Port: new(gwapiv1.PortNumber(8080))},
}
for i, ref := range refs {
SetConditionForPolicyAncestor(policyStatus, ref, controller,
gwapiv1.PolicyConditionAccepted, metav1.ConditionTrue, gwapiv1.PolicyReasonAccepted, fmt.Sprintf("msg-%d", i), 1)
}
// Setting a condition on an existing ancestor updates it instead of adding a new one.
SetConditionForPolicyAncestor(policyStatus, &gwapiv1.ParentReference{Name: "gw", Port: new(gwapiv1.PortNumber(80))}, controller,
gwapiv1.PolicyConditionAccepted, metav1.ConditionFalse, gwapiv1.PolicyReasonInvalid, "updated", 1)

assert.Len(t, policyStatus.Ancestors, len(refs))
for i, ref := range refs {
cond := getPolicyAncestorCondition(policyStatus, ref, controller, gwapiv1.PolicyConditionAccepted)
if !assert.NotNil(t, cond) {
continue
}
want := fmt.Sprintf("msg-%d", i)
if i == 1 {
want = "updated"
}
assert.Equal(t, want, cond.Message)
}
}

func TestSetNotMergedForPolicyAncestor(t *testing.T) {
const controller = "example.com/controller"
ref := &gwapiv1.ParentReference{Name: "gw-1"}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
gateways:
- apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
namespace: envoy-gateway
name: gateway-1
spec:
gatewayClassName: envoy-gateway-class
allowedListeners:
namespaces:
from: All
listeners:
- name: http
protocol: HTTP
port: 80
allowedRoutes:
namespaces:
from: All
- name: http-8080
protocol: HTTP
port: 8080
allowedRoutes:
namespaces:
from: All
listenerSets:
- apiVersion: gateway.networking.k8s.io/v1
kind: ListenerSet
metadata:
namespace: envoy-gateway
name: listenerset-1
spec:
parentRef:
name: gateway-1
listeners:
- name: ls-http-8081
protocol: HTTP
port: 8081
allowedRoutes:
namespaces:
from: All
- name: ls-http-8082
protocol: HTTP
port: 8082
allowedRoutes:
namespaces:
from: All
httpRoutes:
- apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
namespace: default
name: httproute-1
spec:
hostnames:
- foo.example.com
parentRefs:
- namespace: envoy-gateway
name: gateway-1
port: 80
- namespace: envoy-gateway
name: gateway-1
port: 8080
rules:
- matches:
- path:
value: "/foo"
backendRefs:
- name: service-1
port: 8080
- apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
namespace: default
name: httproute-2
spec:
hostnames:
- bar.example.com
parentRefs:
- namespace: envoy-gateway
name: listenerset-1
kind: ListenerSet
group: gateway.networking.k8s.io
port: 8081
- namespace: envoy-gateway
name: listenerset-1
kind: ListenerSet
group: gateway.networking.k8s.io
port: 8082
rules:
- matches:
- path:
value: "/bar"
backendRefs:
- name: service-1
port: 8080
extensionServerPolicies:
# The parentRefs differ only by port, so the policy reports an ancestor for each of them.
- apiVersion: foo.example.io/v1alpha1
kind: Bar
metadata:
name: policy-1
namespace: default
spec:
targetRef:
kind: HTTPRoute
group: gateway.networking.k8s.io
name: httproute-1
data: "attached to an httproute with parentRefs differing only by port"

# The parentRefs to the ListenerSet differ only by port, so the policy reports an ancestor for each of them,
# against the ListenerSet rather than its parent Gateway.
- apiVersion: foo.example.io/v1alpha1
kind: Bar
metadata:
name: policy-2
namespace: default
spec:
targetRef:
kind: HTTPRoute
group: gateway.networking.k8s.io
name: httproute-2
data: "attached to an httproute with ListenerSet parentRefs differing only by port"
Loading
Loading