Skip to content

Commit c38b842

Browse files
authored
Merge pull request #112 from datum-cloud/fix/waitforhttpslistener
fix: wait for https listener before applying envoypatchpolicy
2 parents 5a1759e + 2515dd6 commit c38b842

File tree

4 files changed

+390
-21
lines changed

4 files changed

+390
-21
lines changed

internal/controller/httpproxy_controller.go

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -880,15 +880,20 @@ func (r *HTTPProxyReconciler) reconcileConnectorEnvoyPatchPolicy(
880880
return nil, false, nil
881881
}
882882

883-
// Wait for the Gateway to be Programmed before creating the EnvoyPatchPolicy.
884-
// This ensures the RouteConfiguration exists in Envoy's xDS, so the patch
885-
// can be applied immediately rather than waiting for Envoy Gateway to retry.
883+
// Wait for the Gateway and default HTTPS listener to be Programmed before
884+
// creating the EnvoyPatchPolicy. This ensures the target RouteConfiguration
885+
// exists in Envoy's xDS, so the patch can be applied immediately rather than
886+
// waiting for Envoy Gateway to retry.
886887
gatewayProgrammed := apimeta.IsStatusConditionTrue(
887888
gateway.Status.Conditions,
888889
string(gatewayv1.GatewayConditionProgrammed),
889890
)
890-
if !gatewayProgrammed {
891-
// Gateway not yet programmed; requeue will happen when Gateway status changes.
891+
defaultHTTPSListenerProgrammed := gatewayListenerProgrammed(
892+
gateway.Status.Listeners,
893+
gatewayutil.DefaultHTTPSListenerName,
894+
)
895+
if !gatewayProgrammed || !defaultHTTPSListenerProgrammed {
896+
// Gateway/listener not yet programmed; requeue will happen when status changes.
892897
return nil, true, nil
893898
}
894899

@@ -983,6 +988,16 @@ func cleanupConnectorOfflineHTTPRouteFilter(ctx context.Context, cl client.Clien
983988
return cl.Delete(ctx, &filter)
984989
}
985990

991+
func gatewayListenerProgrammed(listeners []gatewayv1.ListenerStatus, listenerName gatewayv1.SectionName) bool {
992+
for _, listener := range listeners {
993+
if listener.Name != listenerName {
994+
continue
995+
}
996+
return apimeta.IsStatusConditionTrue(listener.Conditions, string(gatewayv1.ListenerConditionProgrammed))
997+
}
998+
return false
999+
}
1000+
9861001
func downstreamPatchPolicyReady(policy *envoygatewayv1alpha1.EnvoyPatchPolicy, gatewayClassName string) (bool, string) {
9871002
if policy == nil {
9881003
return false, "Downstream EnvoyPatchPolicy not found"

internal/controller/httpproxy_controller_test.go

Lines changed: 93 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -460,7 +460,67 @@ func TestHTTPProxyReconcile(t *testing.T) {
460460
},
461461
},
462462
postCreateGatewayStatus: func(g *gatewayv1.Gateway) {
463-
// EnvoyPatchPolicy is only created after Gateway is Programmed
463+
setGatewayProgrammedWithDefaultHTTPSListener(g)
464+
},
465+
expectedError: false,
466+
expectedConditions: []metav1.Condition{
467+
{
468+
Type: networkingv1alpha.HTTPProxyConditionAccepted,
469+
Status: metav1.ConditionTrue,
470+
Reason: networkingv1alpha.HTTPProxyReasonAccepted,
471+
},
472+
{
473+
Type: networkingv1alpha.HTTPProxyConditionProgrammed,
474+
Status: metav1.ConditionFalse,
475+
Reason: networkingv1alpha.HTTPProxyReasonPending,
476+
},
477+
},
478+
assert: func(t *testContext, cl client.Client, httpProxy *networkingv1alpha.HTTPProxy) {
479+
var patchList envoygatewayv1alpha1.EnvoyPatchPolicyList
480+
err := t.downstreamClient.List(context.Background(), &patchList)
481+
assert.NoError(t, err)
482+
assert.Len(t, patchList.Items, 1)
483+
assert.Equal(t, fmt.Sprintf("connector-%s", httpProxy.Name), patchList.Items[0].Name)
484+
},
485+
},
486+
{
487+
name: "connector backend waits for default-https listener programmed",
488+
httpProxy: connectorHTTPProxy,
489+
downstreamObjects: connectorDownstreamObjects(connectorHTTPProxy),
490+
namespaceUID: string(connectorNamespaceUID),
491+
existingObjects: []client.Object{
492+
&networkingv1alpha1.Connector{
493+
ObjectMeta: metav1.ObjectMeta{
494+
Name: "connector-1",
495+
Namespace: "test",
496+
},
497+
Status: networkingv1alpha1.ConnectorStatus{
498+
Conditions: []metav1.Condition{
499+
{
500+
Type: networkingv1alpha1.ConnectorConditionReady,
501+
Status: metav1.ConditionTrue,
502+
Reason: networkingv1alpha1.ConnectorReasonReady,
503+
},
504+
},
505+
ConnectionDetails: &networkingv1alpha1.ConnectorConnectionDetails{
506+
Type: networkingv1alpha1.PublicKeyConnectorConnectionType,
507+
PublicKey: &networkingv1alpha1.ConnectorConnectionDetailsPublicKey{
508+
Id: "node-123",
509+
DiscoveryMode: networkingv1alpha1.DNSPublicKeyDiscoveryMode,
510+
HomeRelay: "https://relay.example.test",
511+
Addresses: []networkingv1alpha1.PublicKeyConnectorAddress{
512+
{
513+
Address: "127.0.0.1",
514+
Port: 80,
515+
},
516+
},
517+
},
518+
},
519+
},
520+
},
521+
},
522+
postCreateGatewayStatus: func(g *gatewayv1.Gateway) {
523+
// Gateway-level Programmed is not enough without default-https listener Programmed.
464524
apimeta.SetStatusCondition(&g.Status.Conditions, metav1.Condition{
465525
Type: string(gatewayv1.GatewayConditionProgrammed),
466526
Status: metav1.ConditionTrue,
@@ -485,8 +545,7 @@ func TestHTTPProxyReconcile(t *testing.T) {
485545
var patchList envoygatewayv1alpha1.EnvoyPatchPolicyList
486546
err := t.downstreamClient.List(context.Background(), &patchList)
487547
assert.NoError(t, err)
488-
assert.Len(t, patchList.Items, 1)
489-
assert.Equal(t, fmt.Sprintf("connector-%s", httpProxy.Name), patchList.Items[0].Name)
548+
assert.Len(t, patchList.Items, 0)
490549
},
491550
},
492551
{
@@ -512,12 +571,7 @@ func TestHTTPProxyReconcile(t *testing.T) {
512571
},
513572
},
514573
postCreateGatewayStatus: func(g *gatewayv1.Gateway) {
515-
apimeta.SetStatusCondition(&g.Status.Conditions, metav1.Condition{
516-
Type: string(gatewayv1.GatewayConditionProgrammed),
517-
Status: metav1.ConditionTrue,
518-
ObservedGeneration: g.Generation,
519-
Reason: string(gatewayv1.GatewayReasonProgrammed),
520-
})
574+
setGatewayProgrammedWithDefaultHTTPSListener(g)
521575
},
522576
expectedError: false,
523577
expectedConditions: []metav1.Condition{
@@ -600,13 +654,7 @@ func TestHTTPProxyReconcile(t *testing.T) {
600654
},
601655
},
602656
postCreateGatewayStatus: func(g *gatewayv1.Gateway) {
603-
// EnvoyPatchPolicy is only created after Gateway is Programmed
604-
apimeta.SetStatusCondition(&g.Status.Conditions, metav1.Condition{
605-
Type: string(gatewayv1.GatewayConditionProgrammed),
606-
Status: metav1.ConditionTrue,
607-
ObservedGeneration: g.Generation,
608-
Reason: string(gatewayv1.GatewayReasonProgrammed),
609-
})
657+
setGatewayProgrammedWithDefaultHTTPSListener(g)
610658
},
611659
expectedError: false,
612660
expectedConditions: []metav1.Condition{
@@ -1508,6 +1556,35 @@ func TestHTTPProxyFinalizerCleanup(t *testing.T) {
15081556
}
15091557
}
15101558

1559+
func setGatewayProgrammedWithDefaultHTTPSListener(g *gatewayv1.Gateway) {
1560+
apimeta.SetStatusCondition(&g.Status.Conditions, metav1.Condition{
1561+
Type: string(gatewayv1.GatewayConditionProgrammed),
1562+
Status: metav1.ConditionTrue,
1563+
ObservedGeneration: g.Generation,
1564+
Reason: string(gatewayv1.GatewayReasonProgrammed),
1565+
})
1566+
1567+
defaultHTTPSListenerStatus := gatewayv1.ListenerStatus{
1568+
Name: gatewayutil.DefaultHTTPSListenerName,
1569+
Conditions: []metav1.Condition{
1570+
{
1571+
Type: string(gatewayv1.ListenerConditionProgrammed),
1572+
Status: metav1.ConditionTrue,
1573+
ObservedGeneration: g.Generation,
1574+
Reason: string(gatewayv1.ListenerReasonProgrammed),
1575+
},
1576+
},
1577+
}
1578+
1579+
for i := range g.Status.Listeners {
1580+
if g.Status.Listeners[i].Name == gatewayutil.DefaultHTTPSListenerName {
1581+
g.Status.Listeners[i] = defaultHTTPSListenerStatus
1582+
return
1583+
}
1584+
}
1585+
g.Status.Listeners = append(g.Status.Listeners, defaultHTTPSListenerStatus)
1586+
}
1587+
15111588
func newHTTPProxy(opts ...func(*networkingv1alpha.HTTPProxy)) *networkingv1alpha.HTTPProxy {
15121589
p := &networkingv1alpha.HTTPProxy{
15131590
ObjectMeta: metav1.ObjectMeta{

internal/controller/trafficprotectionpolicy_controller.go

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@ const (
5353
// PolicyReasonWaitingForCertificates indicates that the policy is waiting
5454
// for TLS certificates to become ready before EnvoyPatchPolicies can be created.
5555
PolicyReasonWaitingForCertificates gatewayv1alpha2.PolicyConditionReason = "WaitingForCertificates"
56+
// PolicyReasonWaitingForListenersProgrammed indicates that the policy is waiting
57+
// for HTTPS listeners to be Programmed=True before EnvoyPatchPolicies can be created.
58+
PolicyReasonWaitingForListenersProgrammed gatewayv1alpha2.PolicyConditionReason = "WaitingForListenersProgrammed"
5659
)
5760

5861
// certificateReadinessResult contains the result of checking certificate readiness
@@ -138,6 +141,19 @@ func (r *TrafficProtectionPolicyReconciler) Reconcile(ctx context.Context, req N
138141
return ctrl.Result{}, nil
139142
}
140143

144+
listenerReadiness := r.checkHTTPSListenersProgrammed(attachments)
145+
if !listenerReadiness.AllReady {
146+
logger.Info("waiting for HTTPS listeners to become programmed", "pendingListeners", listenerReadiness.PendingListeners)
147+
r.setWaitingForListenersProgrammedConditions(trafficProtectionPolicies, listenerReadiness.PendingListeners)
148+
149+
if err := r.updateTPPAncestorsStatus(ctx, cl.GetClient(), trafficProtectionPolicies, originalTrafficProtectionPolicies); err != nil {
150+
return ctrl.Result{}, err
151+
}
152+
153+
// Gateway/HTTPRoute watches will trigger reconciliation when listener status changes.
154+
return ctrl.Result{}, nil
155+
}
156+
141157
desiredPolicies, err := r.getDesiredEnvoyPatchPolicies(downstreamNamespaceName, attachments)
142158
if err != nil {
143159
return ctrl.Result{}, err
@@ -367,6 +383,70 @@ func (r *TrafficProtectionPolicyReconciler) setWaitingForCertificatesConditions(
367383
}
368384
}
369385

386+
// checkHTTPSListenersProgrammed checks if all referenced HTTPS listeners are
387+
// Programmed=True on the upstream Gateway status.
388+
func (r *TrafficProtectionPolicyReconciler) checkHTTPSListenersProgrammed(
389+
attachments []policyAttachment,
390+
) *certificateReadinessResult {
391+
pendingListenersSet := sets.New[string]()
392+
393+
for _, attachment := range attachments {
394+
if attachment.Listener != nil {
395+
for _, l := range attachment.Gateway.Spec.Listeners {
396+
if l.Name != *attachment.Listener || l.Protocol != gatewayv1.HTTPSProtocolType {
397+
continue
398+
}
399+
if !gatewayListenerProgrammed(attachment.Gateway.Status.Listeners, l.Name) {
400+
pendingListenersSet.Insert(fmt.Sprintf("%s/%s", attachment.Gateway.Name, l.Name))
401+
}
402+
}
403+
continue
404+
}
405+
406+
for _, l := range attachment.Gateway.Spec.Listeners {
407+
if l.Protocol != gatewayv1.HTTPSProtocolType {
408+
continue
409+
}
410+
if !gatewayListenerProgrammed(attachment.Gateway.Status.Listeners, l.Name) {
411+
pendingListenersSet.Insert(fmt.Sprintf("%s/%s", attachment.Gateway.Name, l.Name))
412+
}
413+
}
414+
}
415+
416+
pendingListeners := sets.List(pendingListenersSet)
417+
sort.Strings(pendingListeners)
418+
419+
return &certificateReadinessResult{
420+
AllReady: len(pendingListeners) == 0,
421+
PendingListeners: pendingListeners,
422+
}
423+
}
424+
425+
// setWaitingForListenersProgrammedConditions sets Accepted=False with reason
426+
// WaitingForListenersProgrammed while HTTPS listeners are not yet Programmed=True.
427+
func (r *TrafficProtectionPolicyReconciler) setWaitingForListenersProgrammedConditions(
428+
policies []*policyContext,
429+
pendingListeners []string,
430+
) {
431+
message := fmt.Sprintf("Waiting for HTTPS listeners to become Programmed=True: %s", strings.Join(pendingListeners, ", "))
432+
433+
for _, policy := range policies {
434+
for _, targetRef := range policy.Spec.TargetRefs {
435+
ancestorRef := getAncestorRefForTarget(policy.Namespace, targetRef)
436+
gatewaystatus.SetConditionForPolicyAncestor(
437+
&policy.Status.PolicyStatus,
438+
ancestorRef,
439+
string(r.Config.Gateway.ControllerName),
440+
gatewayv1alpha2.PolicyConditionAccepted,
441+
metav1.ConditionFalse,
442+
PolicyReasonWaitingForListenersProgrammed,
443+
message,
444+
policy.Generation,
445+
)
446+
}
447+
}
448+
}
449+
370450
func (r *TrafficProtectionPolicyReconciler) ensureHTTPCorazaListenerFilter(ctx context.Context) error {
371451
envoyPatchPolicy := &envoygatewayv1alpha1.EnvoyPatchPolicy{
372452
ObjectMeta: metav1.ObjectMeta{

0 commit comments

Comments
 (0)