Skip to content
This repository was archived by the owner on Sep 8, 2025. It is now read-only.

Commit 0e40a7f

Browse files
committed
feat: add improved handling for static gateway addresses
Signed-off-by: Shane Utt <[email protected]>
1 parent babd561 commit 0e40a7f

File tree

3 files changed

+192
-157
lines changed

3 files changed

+192
-157
lines changed

controllers/gateway_controller.go

Lines changed: 43 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,14 @@ import (
44
"context"
55
"fmt"
66
"reflect"
7+
"strings"
8+
"time"
79

810
"github.com/go-logr/logr"
911
"github.com/kong/blixt/pkg/vars"
1012
corev1 "k8s.io/api/core/v1"
1113
"k8s.io/apimachinery/pkg/api/errors"
14+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1215
"k8s.io/apimachinery/pkg/runtime"
1316
"k8s.io/apimachinery/pkg/types"
1417
ctrl "sigs.k8s.io/controller-runtime"
@@ -84,63 +87,49 @@ func (r *GatewayReconciler) gatewayHasMatchingGatewayClass(obj client.Object) bo
8487
func (r *GatewayReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
8588
log := log.FromContext(ctx)
8689

87-
gw := new(gatewayv1beta1.Gateway)
88-
if err := r.Client.Get(ctx, req.NamespacedName, gw); err != nil {
90+
gateway := new(gatewayv1beta1.Gateway)
91+
if err := r.Client.Get(ctx, req.NamespacedName, gateway); err != nil {
8992
if errors.IsNotFound(err) {
9093
log.Info("object enqueued no longer exists, skipping")
9194
return ctrl.Result{}, nil
9295
}
9396
return ctrl.Result{}, err
9497
}
9598

96-
gwc := new(gatewayv1beta1.GatewayClass)
97-
if err := r.Client.Get(ctx, types.NamespacedName{Name: string(gw.Spec.GatewayClassName)}, gwc); err != nil {
99+
gatewayClass := new(gatewayv1beta1.GatewayClass)
100+
if err := r.Client.Get(ctx, types.NamespacedName{Name: string(gateway.Spec.GatewayClassName)}, gatewayClass); err != nil {
98101
if errors.IsNotFound(err) {
99102
return ctrl.Result{}, nil
100103
}
101104
return ctrl.Result{}, err
102105
}
103106

104-
if gwc.Spec.ControllerName != vars.GatewayClassControllerName {
107+
if gatewayClass.Spec.ControllerName != vars.GatewayClassControllerName {
105108
return ctrl.Result{}, nil
106109
}
107110

108-
// determine if the Gateway has been accepted, and if it has not then
109-
// determine whether we will accept it, and if not drop it until it has
110-
// been corrected.
111-
oldGateway := gw.DeepCopy()
112-
isAccepted := isGatewayAccepted(gw)
113-
if !isAccepted {
114-
initGatewayStatus(gw)
115-
setGatewayAcceptance(gw)
116-
factorizeStatus(gw, oldGateway)
117-
118-
oldAccepted := getAcceptedConditionForGateway(oldGateway)
119-
accepted := getAcceptedConditionForGateway(gw)
120-
121-
if !sameConditions(oldAccepted, accepted) {
122-
return ctrl.Result{}, r.Status().Patch(ctx, gw, client.MergeFrom(oldGateway))
123-
}
124-
125-
log.Info("gateway %s/%s is not accepted and will not be provisioned", gw.Namespace, gw.Name)
126-
return ctrl.Result{}, nil
111+
log.Info("found a supported Gateway, determining whether the gateway has been accepted")
112+
oldGateway := gateway.DeepCopy()
113+
if !isGatewayAccepted(gateway) {
114+
log.Info("gateway not yet accepted")
115+
setGatewayListenerStatus(gateway)
116+
setGatewayStatus(gateway)
117+
updateConditionGeneration(gateway)
118+
return ctrl.Result{}, r.Status().Patch(ctx, gateway, client.MergeFrom(oldGateway))
127119
}
128120

129121
log.Info("checking for Service for Gateway")
130-
svc, err := r.getServiceForGateway(ctx, gw)
122+
svc, err := r.getServiceForGateway(ctx, gateway)
131123
if err != nil {
132124
return ctrl.Result{}, err
133125
}
134126
if svc == nil {
135127
log.Info("creating Service for Gateway")
136-
return ctrl.Result{}, r.createServiceForGateway(ctx, gw) // service creation will requeue gateway
128+
return ctrl.Result{}, r.createServiceForGateway(ctx, gateway) // service creation will requeue gateway
137129
}
138130

139131
log.Info("checking Service configuration")
140-
needsUpdate, err := r.ensureServiceConfiguration(ctx, svc, gw)
141-
// in both cases when the service does not exist or an error has been triggered, the Gateway
142-
// must be not ready. This OR condition is redundant, as (needsUpdate == true AND err == nil)
143-
// should never happen, but useful to highlight the purpose.
132+
needsUpdate, err := r.ensureServiceConfiguration(ctx, svc, gateway)
144133
if err != nil {
145134
return ctrl.Result{}, err
146135
}
@@ -151,9 +140,27 @@ func (r *GatewayReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct
151140
log.Info("checking Service status", "namespace", svc.Namespace, "name", svc.Name)
152141
switch t := svc.Spec.Type; t {
153142
case corev1.ServiceTypeLoadBalancer:
143+
if err := r.svcIsHealthy(ctx, svc); err != nil {
144+
// TODO: only handles metallb right now https://github.com/Kong/blixt/issues/96
145+
if strings.Contains(err.Error(), "Failed to allocate IP") {
146+
r.Log.Info("failed to allocate IP for Gateway", gateway.Namespace, gateway.Name)
147+
setCond(gateway, metav1.Condition{
148+
Type: string(gatewayv1beta1.GatewayConditionProgrammed),
149+
ObservedGeneration: gateway.Generation,
150+
Status: metav1.ConditionFalse,
151+
LastTransitionTime: metav1.Now(),
152+
Reason: string(gatewayv1beta1.GatewayReasonAddressNotUsable),
153+
Message: err.Error(),
154+
})
155+
updateConditionGeneration(gateway)
156+
return ctrl.Result{Requeue: true}, r.Status().Patch(ctx, gateway, client.MergeFrom(oldGateway))
157+
}
158+
return ctrl.Result{}, err
159+
}
160+
154161
if svc.Spec.ClusterIP == "" || len(svc.Status.LoadBalancer.Ingress) < 1 {
155162
log.Info("waiting for Service to be ready")
156-
return ctrl.Result{Requeue: true}, nil
163+
return ctrl.Result{RequeueAfter: time.Second}, nil
157164
}
158165
default:
159166
return ctrl.Result{}, fmt.Errorf("found unsupported Service type: %s (only LoadBalancer type is currently supported)", t)
@@ -170,8 +177,9 @@ func (r *GatewayReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct
170177
return ctrl.Result{Requeue: true}, nil
171178
}
172179

173-
log.Info("Service is ready, updating Gateway")
174-
updateGatewayStatus(ctx, gw, svc)
175-
factorizeStatus(gw, oldGateway)
176-
return ctrl.Result{}, r.Status().Patch(ctx, gw, client.MergeFrom(oldGateway))
180+
log.Info("Service is ready, setting Gateway as programmed")
181+
setGatewayStatusAddresses(gateway, svc)
182+
setGatewayListenerConditionsAndProgrammed(gateway)
183+
updateConditionGeneration(gateway)
184+
return ctrl.Result{}, r.Status().Patch(ctx, gateway, client.MergeFrom(oldGateway))
177185
}
Lines changed: 51 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,13 @@
11
package controllers
22

33
import (
4-
"context"
5-
64
corev1 "k8s.io/api/core/v1"
75
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
86
gatewayv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"
97
)
108

11-
// updateGatewayStatus computes the new Gateway status, setting its ready condition and all the
12-
// ready listeners's ready conditions to true, unless a resolvedRefs error is discovered. In
13-
// that case, the proper listener ready condition and the gateway one are set to false.
14-
// The addresses are updated as well.
15-
func updateGatewayStatus(_ context.Context, gateway *gatewayv1beta1.Gateway, svc *corev1.Service) {
16-
// gateway addresses
17-
gwaddrs := make([]gatewayv1beta1.GatewayStatusAddress, 0, len(svc.Status.LoadBalancer.Ingress))
9+
func setGatewayStatusAddresses(gateway *gatewayv1beta1.Gateway, svc *corev1.Service) {
10+
gwaddrs := []gatewayv1beta1.GatewayStatusAddress{}
1811
for _, addr := range svc.Status.LoadBalancer.Ingress {
1912
if addr.IP != "" {
2013
gwaddrs = append(gwaddrs, gatewayv1beta1.GatewayStatusAddress{
@@ -30,17 +23,10 @@ func updateGatewayStatus(_ context.Context, gateway *gatewayv1beta1.Gateway, svc
3023
}
3124
}
3225
gateway.Status.Addresses = gwaddrs
26+
}
3327

34-
// gateway conditions
35-
newGatewayAcceptedCondition := metav1.Condition{
36-
Type: string(gatewayv1beta1.GatewayConditionAccepted),
37-
Status: metav1.ConditionTrue,
38-
Reason: string(gatewayv1beta1.GatewayReasonAccepted),
39-
ObservedGeneration: gateway.Generation,
40-
LastTransitionTime: metav1.Now(),
41-
Message: "blixt controlplane accepts responsibility for the Gateway",
42-
}
43-
newGatewayProgrammedCondition := metav1.Condition{
28+
func setGatewayListenerConditionsAndProgrammed(gateway *gatewayv1beta1.Gateway) {
29+
programmed := metav1.Condition{
4430
Type: string(gatewayv1beta1.GatewayConditionProgrammed),
4531
Status: metav1.ConditionTrue,
4632
Reason: string(gatewayv1beta1.GatewayReasonProgrammed),
@@ -49,7 +35,6 @@ func updateGatewayStatus(_ context.Context, gateway *gatewayv1beta1.Gateway, svc
4935
Message: "the gateway is ready to route traffic",
5036
}
5137

52-
// gateway listeners conditions
5338
listenersStatus := make([]gatewayv1beta1.ListenerStatus, 0, len(gateway.Spec.Listeners))
5439
for _, l := range gateway.Spec.Listeners {
5540
supportedKinds, resolvedRefsCondition := getSupportedKinds(gateway.Generation, l)
@@ -63,6 +48,13 @@ func updateGatewayStatus(_ context.Context, gateway *gatewayv1beta1.Gateway, svc
6348
Name: l.Name,
6449
SupportedKinds: supportedKinds,
6550
Conditions: []metav1.Condition{
51+
{
52+
Type: string(gatewayv1beta1.ListenerConditionAccepted),
53+
Status: metav1.ConditionTrue,
54+
Reason: string(gatewayv1beta1.ListenerReasonAccepted),
55+
ObservedGeneration: gateway.Generation,
56+
LastTransitionTime: metav1.Now(),
57+
},
6658
{
6759
Type: string(gatewayv1beta1.ListenerConditionProgrammed),
6860
Status: metav1.ConditionStatus(listenerProgrammedStatus),
@@ -74,42 +66,16 @@ func updateGatewayStatus(_ context.Context, gateway *gatewayv1beta1.Gateway, svc
7466
},
7567
})
7668
if resolvedRefsCondition.Status == metav1.ConditionFalse {
77-
newGatewayProgrammedCondition.Status = metav1.ConditionFalse
78-
newGatewayProgrammedCondition.Reason = string(gatewayv1beta1.GatewayReasonAddressNotAssigned)
79-
newGatewayProgrammedCondition.Message = "the gateway is not ready to route traffic"
69+
programmed.Status = metav1.ConditionFalse
70+
programmed.Reason = string(gatewayv1beta1.GatewayReasonAddressNotAssigned)
71+
programmed.Message = "the gateway is not ready to route traffic"
8072
}
8173
}
82-
83-
gateway.Status.Conditions = []metav1.Condition{
84-
newGatewayAcceptedCondition,
85-
newGatewayProgrammedCondition,
86-
}
8774
gateway.Status.Listeners = listenersStatus
75+
setCond(gateway, programmed)
8876
}
8977

90-
// initGatewayStatus initializes the GatewayStatus, setting the ready condition to
91-
// not ready and all the listeners ready status to not ready as well.
92-
func initGatewayStatus(gateway *gatewayv1beta1.Gateway) {
93-
gateway.Status = gatewayv1beta1.GatewayStatus{
94-
Conditions: []metav1.Condition{
95-
{
96-
Type: string(gatewayv1beta1.GatewayConditionAccepted),
97-
Status: metav1.ConditionTrue,
98-
Reason: string(gatewayv1beta1.GatewayReasonAccepted),
99-
ObservedGeneration: gateway.Generation,
100-
LastTransitionTime: metav1.Now(),
101-
Message: "blixt controlplane accepts responsibility for the Gateway",
102-
},
103-
{
104-
Type: string(gatewayv1beta1.GatewayConditionProgrammed),
105-
Status: metav1.ConditionFalse,
106-
Reason: string(gatewayv1beta1.GatewayReasonAddressNotAssigned),
107-
ObservedGeneration: gateway.Generation,
108-
LastTransitionTime: metav1.Now(),
109-
Message: "the gateway is not ready to route traffic",
110-
},
111-
},
112-
}
78+
func setGatewayListenerStatus(gateway *gatewayv1beta1.Gateway) {
11379
gateway.Status.Listeners = make([]gatewayv1beta1.ListenerStatus, 0, len(gateway.Spec.Listeners))
11480
for _, l := range gateway.Spec.Listeners {
11581
supportedKinds, resolvedRefsCondition := getSupportedKinds(gateway.Generation, l)
@@ -160,12 +126,12 @@ func getSupportedKinds(generation int64, listener gatewayv1beta1.Listener) (supp
160126
case gatewayv1beta1.HTTPProtocolType:
161127
supportedKinds = append(supportedKinds, gatewayv1beta1.RouteGroupKind{
162128
Group: (*gatewayv1beta1.Group)(&gatewayv1beta1.GroupVersion.Group),
163-
Kind: "TCPRoute",
129+
Kind: "HTTPRoute",
164130
})
165131
case gatewayv1beta1.HTTPSProtocolType:
166132
supportedKinds = append(supportedKinds, gatewayv1beta1.RouteGroupKind{
167133
Group: (*gatewayv1beta1.Group)(&gatewayv1beta1.GroupVersion.Group),
168-
Kind: "TCPRoute",
134+
Kind: "HTTPRoute",
169135
})
170136
default:
171137
resolvedRefsCondition.Status = metav1.ConditionFalse
@@ -188,38 +154,20 @@ func getSupportedKinds(generation int64, listener gatewayv1beta1.Listener) (supp
188154
return supportedKinds, resolvedRefsCondition
189155
}
190156

191-
// factorizeStatus takes the old gateway conditions not transitioned and copies them
157+
// updateConditionGeneration takes the old gateway conditions not transitioned and copies them
192158
// into the new gateway status, so that only the transitioning conditions gets actually patched.
193-
func factorizeStatus(gateway, oldGateway *gatewayv1beta1.Gateway) {
194-
for i, c := range gateway.Status.Conditions {
195-
for _, oldC := range oldGateway.Status.Conditions {
196-
if c.Type == oldC.Type {
197-
if c.Status == oldC.Status && c.Reason == oldC.Reason {
198-
gateway.Status.Conditions[i] = oldC
199-
}
200-
}
201-
}
202-
}
203-
159+
func updateConditionGeneration(gateway *gatewayv1beta1.Gateway) {
204160
for i := 0; i < len(gateway.Status.Conditions); i++ {
205161
gateway.Status.Conditions[0].ObservedGeneration = gateway.Generation
206162
}
207163

208-
for i, l := range gateway.Status.Listeners {
209-
for j, lc := range l.Conditions {
210-
for _, ol := range oldGateway.Status.Listeners {
211-
if ol.Name != l.Name {
212-
continue
213-
}
214-
for _, olc := range ol.Conditions {
215-
if lc.Type == olc.Type {
216-
if lc.Status == olc.Status && lc.Reason == olc.Reason {
217-
gateway.Status.Listeners[i].Conditions[j] = olc
218-
}
219-
}
220-
}
221-
}
164+
for i := 0; i < len(gateway.Status.Listeners); i++ {
165+
updatedListenerConditions := []metav1.Condition{}
166+
for _, cond := range gateway.Status.Listeners[0].Conditions {
167+
cond.ObservedGeneration = gateway.Generation
168+
updatedListenerConditions = append(updatedListenerConditions, cond)
222169
}
170+
gateway.Status.Listeners[0].Conditions = updatedListenerConditions
223171
}
224172
}
225173

@@ -232,28 +180,34 @@ func isGatewayAccepted(gateway *gatewayv1beta1.Gateway) bool {
232180
}
233181

234182
func getAcceptedConditionForGateway(gateway *gatewayv1beta1.Gateway) *metav1.Condition {
235-
for _, c := range gateway.Status.Conditions {
236-
if c.Type == string(gatewayv1beta1.GatewayConditionAccepted) {
237-
return &c
238-
}
239-
}
240-
return nil
183+
return getCond(gateway, string(gatewayv1beta1.GatewayConditionAccepted))
241184
}
242185

243-
// isGatewayProgrammed returns two boolean values:
244-
// - the status of the programmed condition
245-
// - a boolean flag to check if the condition exists
246-
func isGatewayProgrammed(gateway *gatewayv1beta1.Gateway) (status bool, isSet bool) {
247-
for _, c := range gateway.Status.Conditions {
248-
if c.Type == string(gatewayv1beta1.GatewayConditionProgrammed) {
249-
return c.Status == metav1.ConditionTrue, true
186+
func setCond(gateway *gatewayv1beta1.Gateway, setCond metav1.Condition) {
187+
updatedConditions := make([]metav1.Condition, 0, len(gateway.Status.Conditions))
188+
189+
found := false
190+
for _, oldCond := range gateway.Status.Conditions {
191+
if oldCond.Type == setCond.Type {
192+
found = true
193+
updatedConditions = append(updatedConditions, setCond)
194+
} else {
195+
updatedConditions = append(updatedConditions, oldCond)
250196
}
251197
}
252-
return false, false
198+
199+
if !found {
200+
updatedConditions = append(updatedConditions, setCond)
201+
}
202+
203+
gateway.Status.Conditions = updatedConditions
253204
}
254205

255-
// sameConditions returns true if the type, status and reason match for
256-
// the two provided metav1.Conditions.
257-
func sameConditions(cond1, cond2 *metav1.Condition) bool {
258-
return cond1.Type == cond2.Type && cond1.Status == cond2.Status && cond1.Reason == cond2.Reason && cond1.Message == cond2.Message && cond1.ObservedGeneration == cond2.ObservedGeneration
206+
func getCond(gateway *gatewayv1beta1.Gateway, requestedType string) *metav1.Condition {
207+
for _, cond := range gateway.Status.Conditions {
208+
if cond.Type == requestedType {
209+
return &cond
210+
}
211+
}
212+
return nil
259213
}

0 commit comments

Comments
 (0)