Skip to content

[feat gw-api]handle resolvedRef for route status update and update ho… #4210

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 30, 2025
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 apis/gateway/v1beta1/targetgroupconfig_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ const (
ProtocolTCP_UDP Protocol = "TCP_UDP"
)

// +kubebuilder:validation:Enum=http1;http2;grpc
// +kubebuilder:validation:Enum=HTTP1;HTTP2;GRPC
type ProtocolVersion string

const (
Expand Down
12 changes: 6 additions & 6 deletions config/crd/gateway/gateway-crds.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -491,9 +491,9 @@ spec:
description: protocolVersion [HTTP/HTTPS protocol] The protocol
version. The possible values are GRPC , HTTP1 and HTTP2
enum:
- http1
- http2
- grpc
- HTTP1
- HTTP2
- GRPC
type: string
tags:
description: Tags defines list of Tags on target group.
Expand Down Expand Up @@ -694,9 +694,9 @@ spec:
description: protocolVersion [HTTP/HTTPS protocol] The protocol
version. The possible values are GRPC , HTTP1 and HTTP2
enum:
- http1
- http2
- grpc
- HTTP1
- HTTP2
- GRPC
type: string
tags:
description: Tags defines list of Tags on target group.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,9 +186,9 @@ spec:
description: protocolVersion [HTTP/HTTPS protocol] The protocol
version. The possible values are GRPC , HTTP1 and HTTP2
enum:
- http1
- http2
- grpc
- HTTP1
- HTTP2
- GRPC
type: string
tags:
description: Tags defines list of Tags on target group.
Expand Down Expand Up @@ -389,9 +389,9 @@ spec:
description: protocolVersion [HTTP/HTTPS protocol] The protocol
version. The possible values are GRPC , HTTP1 and HTTP2
enum:
- http1
- http2
- grpc
- HTTP1
- HTTP2
- GRPC
type: string
tags:
description: Tags defines list of Tags on target group.
Expand Down
8 changes: 4 additions & 4 deletions controllers/gateway/gateway_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ func (r *gatewayReconciler) reconcileHelper(ctx context.Context, req reconcile.R
mergedLbConfig, err := r.cfgResolver.getLoadBalancerConfigForGateway(ctx, r.k8sClient, gw, gwClass)

if err != nil {
statusErr := r.updateGatewayStatusFailure(ctx, gw, gwv1.GatewayReasonInvalid, err)
statusErr := r.updateGatewayStatusFailure(ctx, gw, gwv1.GatewayReasonInvalid, err.Error())
if statusErr != nil {
r.logger.Error(statusErr, "Unable to update gateway status on failure to retrieve attached config")
}
Expand All @@ -208,7 +208,7 @@ func (r *gatewayReconciler) reconcileHelper(ctx context.Context, req reconcile.R
if err != nil {
var loaderErr routeutils.LoaderError
if errors.As(err, &loaderErr) {
statusErr := r.updateGatewayStatusFailure(ctx, gw, loaderErr.GetGatewayReason(), loaderErr)
statusErr := r.updateGatewayStatusFailure(ctx, gw, loaderErr.GetGatewayReason(), loaderErr.GetGatewayMessage())
if statusErr != nil {
r.logger.Error(statusErr, "Unable to update gateway status on failure to build routes")
}
Expand Down Expand Up @@ -354,10 +354,10 @@ func (r *gatewayReconciler) updateGatewayStatusSuccess(ctx context.Context, lbSt
return nil
}

func (r *gatewayReconciler) updateGatewayStatusFailure(ctx context.Context, gw *gwv1.Gateway, reason gwv1.GatewayConditionReason, err error) error {
func (r *gatewayReconciler) updateGatewayStatusFailure(ctx context.Context, gw *gwv1.Gateway, reason gwv1.GatewayConditionReason, errMessage string) error {
gwOld := gw.DeepCopy()

needPatch := r.gatewayConditionUpdater(gw, string(gwv1.GatewayConditionAccepted), metav1.ConditionFalse, string(reason), err.Error())
needPatch := r.gatewayConditionUpdater(gw, string(gwv1.GatewayConditionAccepted), metav1.ConditionFalse, string(reason), errMessage)

if needPatch {
if err := r.k8sClient.Status().Patch(ctx, gw, client.MergeFrom(gwOld)); err != nil {
Expand Down
294 changes: 294 additions & 0 deletions controllers/gateway/route_reconciler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/log"
gwv1 "sigs.k8s.io/gateway-api/apis/v1"
"testing"
"time"
)

func TestDeferredReconcilerConstructor(t *testing.T) {
Expand Down Expand Up @@ -421,3 +422,296 @@ func TestEnqueue(t *testing.T) {
})
}
}

func Test_updateRouteStatus(t *testing.T) {
tests := []struct {
name string
route client.Object
routeData routeutils.RouteData
validateResult func(t *testing.T, route client.Object)
}{
{
name: "update HTTPRoute status - condition accepted",
route: &gwv1.HTTPRoute{
ObjectMeta: metav1.ObjectMeta{
Name: "test-route",
Namespace: "test-namespace",
},
Spec: gwv1.HTTPRouteSpec{
Hostnames: []gwv1.Hostname{"example.com"},
},
},
routeData: routeutils.RouteData{
RouteStatusInfo: routeutils.RouteStatusInfo{
Accepted: true,
ResolvedRefs: true,
Reason: string(gwv1.RouteConditionAccepted),
Message: "route accepted",
},
ParentRefGateway: routeutils.ParentRefGateway{
Name: "test-gateway",
Namespace: "test-namespace",
},
},
validateResult: func(t *testing.T, route client.Object) {
httpRoute := route.(*gwv1.HTTPRoute)
if httpRoute.Status.Parents == nil {
assert.Len(t, httpRoute.Status.Parents, 0)
}
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
logger := logr.New(&log.NullLogSink{})
reconciler := &routeReconcilerImpl{
logger: logger,
}
err := reconciler.updateRouteStatus(tt.route, tt.routeData)
assert.NoError(t, err)
if tt.validateResult != nil {
tt.validateResult(t, tt.route)
}
})
}
}

func Test_setConditionsWithRouteStatusInfo(t *testing.T) {
route := &gwv1.HTTPRoute{
ObjectMeta: metav1.ObjectMeta{
Generation: 1,
},
}

tests := []struct {
name string
info routeutils.RouteStatusInfo
validateResult func(t *testing.T, conditions []metav1.Condition)
}{
{
name: "accepted true and resolvedRef true",
info: routeutils.RouteStatusInfo{
Accepted: true,
ResolvedRefs: true,
Reason: string(gwv1.RouteConditionAccepted),
Message: "route accepted",
},
validateResult: func(t *testing.T, conditions []metav1.Condition) {
assert.Len(t, conditions, 2)
acceptedCondition := findCondition(conditions, string(gwv1.RouteConditionAccepted))
assert.NotNil(t, acceptedCondition)
assert.Equal(t, metav1.ConditionTrue, acceptedCondition.Status)

resolvedRefCondition := findCondition(conditions, string(gwv1.RouteConditionResolvedRefs))
assert.NotNil(t, resolvedRefCondition)
assert.Equal(t, metav1.ConditionTrue, resolvedRefCondition.Status)
},
},
{
name: "accepted false and resolvedRef true",
info: routeutils.RouteStatusInfo{
Accepted: false,
ResolvedRefs: true,
Reason: string(gwv1.RouteReasonNotAllowedByListeners),
Message: "route not allowed by listeners",
},
validateResult: func(t *testing.T, conditions []metav1.Condition) {
assert.Len(t, conditions, 2)
acceptedCondition := findCondition(conditions, string(gwv1.RouteConditionAccepted))
assert.NotNil(t, acceptedCondition)
assert.Equal(t, metav1.ConditionFalse, acceptedCondition.Status)

resolvedRefCondition := findCondition(conditions, string(gwv1.RouteConditionResolvedRefs))
assert.NotNil(t, resolvedRefCondition)
assert.Equal(t, metav1.ConditionTrue, resolvedRefCondition.Status)
},
},
{
name: "accepted false and resolvedRef false",
info: routeutils.RouteStatusInfo{
Accepted: false,
ResolvedRefs: false,
Reason: string(gwv1.RouteReasonBackendNotFound),
Message: "backend not found",
},
validateResult: func(t *testing.T, conditions []metav1.Condition) {
assert.Len(t, conditions, 2)
acceptedCondition := findCondition(conditions, string(gwv1.RouteConditionAccepted))
assert.NotNil(t, acceptedCondition)
assert.Equal(t, metav1.ConditionFalse, acceptedCondition.Status)

resolvedRefCondition := findCondition(conditions, string(gwv1.RouteConditionResolvedRefs))
assert.NotNil(t, resolvedRefCondition)
assert.Equal(t, metav1.ConditionFalse, resolvedRefCondition.Status)
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
logger := logr.New(&log.NullLogSink{})
reconciler := &routeReconcilerImpl{
logger: logger,
}
parentStatus := &gwv1.RouteParentStatus{}
reconciler.setConditionsWithRouteStatusInfo(route, parentStatus, tt.info)
if tt.validateResult != nil {
tt.validateResult(t, parentStatus.Conditions)
}
})
}
}

func Test_areConditionsEqual(t *testing.T) {
tests := []struct {
name string
oldCon []metav1.Condition
newCon []metav1.Condition
expected bool
}{
{
name: "same conditions - true",
oldCon: []metav1.Condition{
{
Type: string(gwv1.RouteConditionAccepted),
Status: metav1.ConditionTrue,
},
{
Type: string(gwv1.RouteConditionResolvedRefs),
Status: metav1.ConditionTrue,
},
},
newCon: []metav1.Condition{
{
Type: string(gwv1.RouteConditionAccepted),
Status: metav1.ConditionTrue,
},
{
Type: string(gwv1.RouteConditionResolvedRefs),
Status: metav1.ConditionTrue,
},
},
expected: true,
},
{
name: "different conditions - false",
oldCon: []metav1.Condition{
{
Type: string(gwv1.RouteConditionAccepted),
Status: metav1.ConditionTrue,
},
{
Type: string(gwv1.RouteConditionResolvedRefs),
Status: metav1.ConditionTrue,
},
},
newCon: []metav1.Condition{
{
Type: string(gwv1.RouteConditionAccepted),
Status: metav1.ConditionFalse,
},
{
Type: string(gwv1.RouteConditionResolvedRefs),
Status: metav1.ConditionTrue,
},
},
expected: false,
},
{
name: "different conditions on Reason - false",
oldCon: []metav1.Condition{
{
Type: string(gwv1.RouteConditionAccepted),
Status: metav1.ConditionTrue,
Reason: "good",
},
{
Type: string(gwv1.RouteConditionResolvedRefs),
Status: metav1.ConditionTrue,
},
},
newCon: []metav1.Condition{
{
Type: string(gwv1.RouteConditionAccepted),
Status: metav1.ConditionTrue,
Reason: "test-good",
},
{
Type: string(gwv1.RouteConditionResolvedRefs),
Status: metav1.ConditionTrue,
},
},
expected: false,
},
{
name: "different conditions on LastTransitionTime - true",
oldCon: []metav1.Condition{
{
Type: string(gwv1.RouteConditionAccepted),
Status: metav1.ConditionTrue,
LastTransitionTime: metav1.NewTime(time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC)),
},
{
Type: string(gwv1.RouteConditionResolvedRefs),
Status: metav1.ConditionTrue,
},
},
newCon: []metav1.Condition{
{
Type: string(gwv1.RouteConditionAccepted),
Status: metav1.ConditionTrue,
LastTransitionTime: metav1.NewTime(time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC)),
},
{
Type: string(gwv1.RouteConditionResolvedRefs),
Status: metav1.ConditionTrue,
},
},
expected: true,
},
{
name: "different conditions on ObservedGeneration - true",
oldCon: []metav1.Condition{
{
Type: string(gwv1.RouteConditionAccepted),
Status: metav1.ConditionTrue,
ObservedGeneration: 1,
},
{
Type: string(gwv1.RouteConditionResolvedRefs),
Status: metav1.ConditionTrue,
},
},
newCon: []metav1.Condition{
{
Type: string(gwv1.RouteConditionAccepted),
Status: metav1.ConditionTrue,
ObservedGeneration: 2,
},
{
Type: string(gwv1.RouteConditionResolvedRefs),
Status: metav1.ConditionTrue,
},
},
expected: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := areConditionsEqual(tt.oldCon, tt.newCon)
assert.Equal(t, tt.expected, result)
})
}
}

// helper function
func findCondition(conditions []metav1.Condition, conditionType string) *metav1.Condition {
for _, condition := range conditions {
if condition.Type == conditionType {
return &condition
}
}
return nil
}
Loading
Loading