Skip to content

Commit 7399a96

Browse files
authored
Merge pull request #4189 from zac-nixon/znixon/gateway-status
[feat gw api] Implement Gateway Status + Some bug fixes for Gateway
2 parents 5ab8d81 + cecfe5b commit 7399a96

23 files changed

+1136
-65
lines changed

controllers/gateway/config_resolver.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,10 @@ func newGatewayConfigResolver() gatewayConfigResolver {
2929
func (resolver *gatewayConfigResolverImpl) getLoadBalancerConfigForGateway(ctx context.Context, k8sClient client.Client, gw *gwv1.Gateway, gwClass *gwv1.GatewayClass) (elbv2gw.LoadBalancerConfiguration, error) {
3030

3131
// If the Gateway Class isn't accepted, we shouldn't try to reconcile this Gateway.
32-
derivedStatus, _ := deriveGatewayClassAcceptedStatus(gwClass)
32+
derivedStatusIndx, ok := deriveAcceptedConditionIndex(gwClass)
3333

34-
if derivedStatus != metav1.ConditionTrue {
35-
return elbv2gw.LoadBalancerConfiguration{}, errors.Errorf("Unable to materialize gateway when gateway class [%s] is not accepted. GatewayClass status is %s", gwClass.Name, derivedStatus)
34+
if !ok || gwClass.Status.Conditions[derivedStatusIndx].Status != metav1.ConditionTrue {
35+
return elbv2gw.LoadBalancerConfiguration{}, errors.Errorf("Unable to materialize gateway when gateway class [%s] is not accepted", gwClass.Name)
3636
}
3737

3838
gatewayClassLBConfig, err := resolver.configResolverFn(ctx, k8sClient, gwClass.Spec.ParametersRef)

controllers/gateway/gateway_controller.go

Lines changed: 70 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@ package gateway
33
import (
44
"context"
55
"fmt"
6+
elbv2types "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2/types"
67
"github.com/go-logr/logr"
78
"github.com/pkg/errors"
89
corev1 "k8s.io/api/core/v1"
10+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
911
"k8s.io/apimachinery/pkg/types"
1012
"k8s.io/apimachinery/pkg/util/sets"
1113
"k8s.io/client-go/tools/record"
@@ -35,6 +37,12 @@ import (
3537
"sigs.k8s.io/controller-runtime/pkg/source"
3638
gwv1 "sigs.k8s.io/gateway-api/apis/v1"
3739
gwalpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
40+
"time"
41+
)
42+
43+
const (
44+
requeueMessage = "Monitoring provisioning state"
45+
statusUpdateRequeueTime = 2 * time.Minute
3846
)
3947

4048
var _ Reconciler = &gatewayReconciler{}
@@ -86,6 +94,7 @@ func newGatewayReconciler(controllerName string, lbType elbv2model.LoadBalancerT
8694
reconcileTracker: reconcileTracker,
8795
cfgResolver: cfgResolver,
8896
routeReconciler: routeReconciler,
97+
gatewayConditionUpdater: prepareGatewayConditionUpdate,
8998
}
9099
}
91100

@@ -107,6 +116,7 @@ type gatewayReconciler struct {
107116
logger logr.Logger
108117
metricsCollector lbcmetrics.MetricCollector
109118
reconcileTracker func(namespaceName types.NamespacedName)
119+
gatewayConditionUpdater func(gw *gwv1.Gateway, targetConditionType string, newStatus metav1.ConditionStatus, reason string, message string) bool
110120

111121
cfgResolver gatewayConfigResolver
112122
routeReconciler routeutils.RouteReconciler
@@ -186,12 +196,23 @@ func (r *gatewayReconciler) reconcileHelper(ctx context.Context, req reconcile.R
186196
mergedLbConfig, err := r.cfgResolver.getLoadBalancerConfigForGateway(ctx, r.k8sClient, gw, gwClass)
187197

188198
if err != nil {
199+
statusErr := r.updateGatewayStatusFailure(ctx, gw, gwv1.GatewayReasonInvalid, err)
200+
if statusErr != nil {
201+
r.logger.Error(statusErr, "Unable to update gateway status on failure to retrieve attached config")
202+
}
189203
return err
190204
}
191205

192206
allRoutes, err := r.gatewayLoader.LoadRoutesForGateway(ctx, *gw, r.routeFilter, r.routeReconciler)
193207

194208
if err != nil {
209+
var loaderErr routeutils.LoaderError
210+
if errors.As(err, &loaderErr) {
211+
statusErr := r.updateGatewayStatusFailure(ctx, gw, loaderErr.GetGatewayReason(), loaderErr)
212+
if statusErr != nil {
213+
r.logger.Error(statusErr, "Unable to update gateway status on failure to build routes")
214+
}
215+
}
195216
return err
196217
}
197218

@@ -248,18 +269,14 @@ func (r *gatewayReconciler) reconcileUpdate(ctx context.Context, gw *gwv1.Gatewa
248269
if err != nil {
249270
return err
250271
}
251-
lbDNS, err := lb.DNSName().Resolve(ctx)
252-
if err != nil {
253-
return err
254-
}
255272

256273
if !backendSGRequired {
257274
if err := r.backendSGProvider.Release(ctx, networking.ResourceTypeGateway, []types.NamespacedName{k8s.NamespacedName(gw)}); err != nil {
258275
return err
259276
}
260277
}
261278

262-
if err = r.updateGatewayStatus(ctx, lbDNS, gw); err != nil {
279+
if err = r.updateGatewayStatusSuccess(ctx, lb.Status, gw); err != nil {
263280
r.eventRecorder.Event(gw, corev1.EventTypeWarning, k8s.GatewayEventReasonFailedUpdateStatus, fmt.Sprintf("Failed update status due to %v", err))
264281
return err
265282
}
@@ -295,28 +312,58 @@ func (r *gatewayReconciler) buildModel(ctx context.Context, gw *gwv1.Gateway, cf
295312
return stack, lb, backendSGRequired, nil
296313
}
297314

298-
func (r *gatewayReconciler) updateGatewayStatus(ctx context.Context, lbDNS string, gw *gwv1.Gateway) error {
299-
// TODO Consider LB ARN.
315+
func (r *gatewayReconciler) updateGatewayStatusSuccess(ctx context.Context, lbStatus *elbv2model.LoadBalancerStatus, gw *gwv1.Gateway) error {
316+
// LB Status should always be set, if it's not, we need to prevent NPE
317+
if lbStatus == nil {
318+
r.logger.Info("Unable to update Gateway Status due to null LB status")
319+
return nil
320+
}
321+
gwOld := gw.DeepCopy()
322+
323+
var needPatch bool
324+
var requeueNeeded bool
325+
if isGatewayProgrammed(*lbStatus) {
326+
needPatch = r.gatewayConditionUpdater(gw, string(gwv1.GatewayConditionProgrammed), metav1.ConditionTrue, string(gwv1.GatewayConditionProgrammed), lbStatus.LoadBalancerARN)
327+
} else {
328+
requeueNeeded = true
329+
}
300330

301-
// Gateway Address Status
331+
needPatch = r.gatewayConditionUpdater(gw, string(gwv1.GatewayConditionAccepted), metav1.ConditionTrue, string(gwv1.GatewayConditionAccepted), "") || needPatch
302332
if len(gw.Status.Addresses) != 1 ||
303-
gw.Status.Addresses[0].Value != "" ||
304-
gw.Status.Addresses[0].Value != lbDNS {
305-
gwOld := gw.DeepCopy()
333+
gw.Status.Addresses[0].Value != lbStatus.DNSName {
306334
ipAddressType := gwv1.HostnameAddressType
307335
gw.Status.Addresses = []gwv1.GatewayStatusAddress{
308336
{
309337
Type: &ipAddressType,
310-
Value: lbDNS,
338+
Value: lbStatus.DNSName,
311339
},
312340
}
341+
needPatch = true
342+
}
343+
344+
if needPatch {
313345
if err := r.k8sClient.Status().Patch(ctx, gw, client.MergeFrom(gwOld)); err != nil {
314346
return errors.Wrapf(err, "failed to update gw status: %v", k8s.NamespacedName(gw))
315347
}
316348
}
317349

318-
// TODO: Listener status ListenerStatus
319-
// https://github.com/aws/aws-application-networking-k8s/blob/main/pkg/controllers/gateway_controller.go#L350
350+
if requeueNeeded {
351+
return runtime.NewRequeueNeededAfter(requeueMessage, statusUpdateRequeueTime)
352+
}
353+
354+
return nil
355+
}
356+
357+
func (r *gatewayReconciler) updateGatewayStatusFailure(ctx context.Context, gw *gwv1.Gateway, reason gwv1.GatewayConditionReason, err error) error {
358+
gwOld := gw.DeepCopy()
359+
360+
needPatch := r.gatewayConditionUpdater(gw, string(gwv1.GatewayConditionAccepted), metav1.ConditionFalse, string(reason), err.Error())
361+
362+
if needPatch {
363+
if err := r.k8sClient.Status().Patch(ctx, gw, client.MergeFrom(gwOld)); err != nil {
364+
return errors.Wrapf(err, "failed to update gw status: %v", k8s.NamespacedName(gw))
365+
}
366+
}
320367

321368
return nil
322369
}
@@ -475,3 +522,12 @@ func (r *gatewayReconciler) setupNLBGatewayControllerWatches(ctrl controller.Con
475522
return nil
476523

477524
}
525+
526+
func isGatewayProgrammed(lbStatus elbv2model.LoadBalancerStatus) bool {
527+
if lbStatus.ProvisioningState == nil {
528+
return false
529+
}
530+
531+
return lbStatus.ProvisioningState.Code == elbv2types.LoadBalancerStateEnumActive || lbStatus.ProvisioningState.Code == elbv2types.LoadBalancerStateEnumActiveImpaired
532+
533+
}

controllers/gateway/utils.go

Lines changed: 66 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,21 @@ import (
1414
"strconv"
1515
"strings"
1616
"time"
17+
"unicode/utf8"
1718
)
1819

1920
const (
2021
gatewayClassAnnotationLastProcessedConfig = "elbv2.k8s.aws/last-processed-config"
2122
gatewayClassAnnotationLastProcessedConfigTimestamp = gatewayClassAnnotationLastProcessedConfig + "-timestamp"
23+
24+
// The max message that can be stored in a condition
25+
maxMessageLength = 32700
2226
)
2327

28+
// updateGatewayClassLastProcessedConfig updates the gateway class annotations with the last processed lb config resource version or "" if no lb config is attached to the gatewayclass
2429
func updateGatewayClassLastProcessedConfig(ctx context.Context, k8sClient client.Client, gwClass *gwv1.GatewayClass, lbConf *elbv2gw.LoadBalancerConfiguration) error {
2530

26-
calculatedVersion := gatewayClassAnnotationLastProcessedConfig
31+
calculatedVersion := ""
2732

2833
if lbConf != nil {
2934
calculatedVersion = lbConf.ResourceVersion
@@ -40,12 +45,16 @@ func updateGatewayClassLastProcessedConfig(ctx context.Context, k8sClient client
4045
}
4146

4247
gwClassOld := gwClass.DeepCopy()
48+
if gwClass.Annotations == nil {
49+
gwClass.Annotations = make(map[string]string)
50+
}
4351
gwClass.Annotations[gatewayClassAnnotationLastProcessedConfig] = calculatedVersion
4452
gwClass.Annotations[gatewayClassAnnotationLastProcessedConfigTimestamp] = strconv.FormatInt(time.Now().Unix(), 10)
4553

4654
return k8sClient.Patch(ctx, gwClass, client.MergeFrom(gwClassOld))
4755
}
4856

57+
// getStoredProcessedConfig retrieves the resource version attached to the lb config referenced by the gateway class or nil if no such mapping exists.
4958
func getStoredProcessedConfig(gwClass *gwv1.GatewayClass) *string {
5059
var storedVersion *string
5160

@@ -58,10 +67,20 @@ func getStoredProcessedConfig(gwClass *gwv1.GatewayClass) *string {
5867
return storedVersion
5968
}
6069

70+
// updateGatewayClassAcceptedCondition updates the 'accepted' condition on the gateway class to the passed in parameters. if no 'Accepted' condition exists, do nothing.
6171
func updateGatewayClassAcceptedCondition(ctx context.Context, k8sClient client.Client, gwClass *gwv1.GatewayClass, newStatus metav1.ConditionStatus, reason string, message string) error {
62-
derivedStatus, indxToUpdate := deriveGatewayClassAcceptedStatus(gwClass)
72+
indxToUpdate, ok := deriveAcceptedConditionIndex(gwClass)
73+
74+
if ok {
75+
76+
storedStatus := gwClass.Status.Conditions[indxToUpdate].Status
77+
storedMessage := gwClass.Status.Conditions[indxToUpdate].Message
78+
storedReason := gwClass.Status.Conditions[indxToUpdate].Reason
79+
80+
if storedStatus == newStatus && storedMessage == message && storedReason == reason {
81+
return nil
82+
}
6383

64-
if indxToUpdate != -1 && derivedStatus != newStatus {
6584
gwClassOld := gwClass.DeepCopy()
6685
gwClass.Status.Conditions[indxToUpdate].LastTransitionTime = metav1.NewTime(time.Now())
6786
gwClass.Status.Conditions[indxToUpdate].ObservedGeneration = gwClass.Generation
@@ -75,15 +94,56 @@ func updateGatewayClassAcceptedCondition(ctx context.Context, k8sClient client.C
7594
return nil
7695
}
7796

78-
func deriveGatewayClassAcceptedStatus(gwClass *gwv1.GatewayClass) (metav1.ConditionStatus, int) {
97+
// prepareGatewayConditionUpdate inserts the necessary data into the condition field of the gateway. The caller should patch the corresponding gateway. Returns false when no change was performed.
98+
func prepareGatewayConditionUpdate(gw *gwv1.Gateway, targetConditionType string, newStatus metav1.ConditionStatus, reason string, message string) bool {
99+
100+
indxToUpdate := -1
101+
var derivedCondition metav1.Condition
102+
for i, condition := range gw.Status.Conditions {
103+
if condition.Type == targetConditionType {
104+
indxToUpdate = i
105+
derivedCondition = condition
106+
break
107+
}
108+
}
109+
110+
// 32768 is the max message limit
111+
truncatedMessage := truncateMessage(message)
112+
113+
if indxToUpdate != -1 {
114+
if derivedCondition.Status != newStatus || derivedCondition.Message != truncatedMessage || derivedCondition.Reason != reason {
115+
gw.Status.Conditions[indxToUpdate].LastTransitionTime = metav1.NewTime(time.Now())
116+
gw.Status.Conditions[indxToUpdate].ObservedGeneration = gw.Generation
117+
gw.Status.Conditions[indxToUpdate].Status = newStatus
118+
gw.Status.Conditions[indxToUpdate].Message = truncatedMessage
119+
gw.Status.Conditions[indxToUpdate].Reason = reason
120+
return true
121+
}
122+
}
123+
return false
124+
}
125+
126+
func truncateMessage(s string) string {
127+
if utf8.RuneCountInString(s) <= maxMessageLength {
128+
return s
129+
}
130+
131+
runes := []rune(s)
132+
return string(runes[:maxMessageLength]) + "..."
133+
}
134+
135+
// deriveAcceptedConditionIndex returns the index of the condition pertaining to the accepted condition.
136+
// -1 if the condition doesn't exist
137+
func deriveAcceptedConditionIndex(gwClass *gwv1.GatewayClass) (int, bool) {
79138
for i, v := range gwClass.Status.Conditions {
80139
if v.Type == string(gwv1.GatewayClassReasonAccepted) {
81-
return v.Status, i
140+
return i, true
82141
}
83142
}
84-
return metav1.ConditionFalse, -1
143+
return -1, false
85144
}
86145

146+
// resolveLoadBalancerConfig returns the lb config referenced in the ParametersReference.
87147
func resolveLoadBalancerConfig(ctx context.Context, k8sClient client.Client, reference *gwv1.ParametersReference) (*elbv2gw.LoadBalancerConfiguration, error) {
88148
var lbConf *elbv2gw.LoadBalancerConfiguration
89149

0 commit comments

Comments
 (0)