Skip to content
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
18 changes: 17 additions & 1 deletion internal/controller/httpproxy_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -799,7 +799,23 @@ func (r *HTTPProxyReconciler) collectDesiredResources(
}

for backendIndex, backend := range rule.Backends {
if backend.Connector != nil {
// Offline-connector handling differs by emission mode:
//
// * EPP mode (legacy): emit a backend-less route rule. EG translates
// it into a virtual_host, and the connector EPP
// (buildConnectorOfflineEnvoyPatches) inserts the direct_response 503
// "Tunnel not online" CONNECT route at the front.
// * Extension-server mode: the ext-server keys its offline-503 handling
// on the presence of a connector *cluster*, which EG only emits when
// the route rule carries a backendRef. So we must NOT null the
// backendRef here — fall through and emit the same connector.local
// placeholder EndpointSlice + backendRef as the online case. The
// ext-server then sees the cluster, classifies the connector offline
// from the replicated Connector Ready condition, and inserts the 503
// route itself. Nulling the backendRef leaves EG with a backend-less
// route, which it renders as a bare direct_response 500 (no offline
// page) — the regression this guards against.
if backend.Connector != nil && r.Config.Gateway.IsEPPEmissionEnabled() {
ready, err := connectorReady(ctx, cl, httpProxy.Namespace, backend.Connector.Name)
if err != nil {
return nil, err
Expand Down
91 changes: 89 additions & 2 deletions internal/controller/httpproxy_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -553,6 +553,11 @@ func TestHTTPProxyReconcile(t *testing.T) {
Name: "connector-1",
}
})
connectorModeBHTTPProxy := newHTTPProxy(func(h *networkingv1alpha.HTTPProxy) {
h.Spec.Rules[0].Backends[0].Connector = &networkingv1alpha.ConnectorReference{
Name: "connector-1",
}
})

connectorDownstreamObjects := func(proxy *networkingv1alpha.HTTPProxy) []client.Object {
return []client.Object{
Expand All @@ -571,6 +576,7 @@ func TestHTTPProxyReconcile(t *testing.T) {
existingObjects []client.Object
downstreamObjects []client.Object
namespaceUID string
eppEmissionDisabled bool
postCreateGatewayStatus func(*gatewayv1.Gateway)
expectedError bool
expectedConditions []metav1.Condition
Expand Down Expand Up @@ -781,6 +787,82 @@ func TestHTTPProxyReconcile(t *testing.T) {
}
},
},
{
// Regression: in extension-server mode (EPP emission disabled) an
// offline connector must keep its backendRef so EG renders a connector
// cluster. The ext-server keys its offline-503 "Tunnel not online" route
// on that cluster. Nulling the backendRef (the EPP-mode behavior) leaves
// EG with a backend-less route, which it renders as a bare
// direct_response 500 with no offline page.
name: "offline connector keeps backendRef in extension-server mode",
httpProxy: connectorModeBHTTPProxy,
downstreamObjects: connectorDownstreamObjects(connectorModeBHTTPProxy),
namespaceUID: string(connectorNamespaceUID),
eppEmissionDisabled: true,
existingObjects: []client.Object{
&networkingv1alpha1.Connector{
ObjectMeta: metav1.ObjectMeta{
Name: "connector-1",
Namespace: "test",
},
Status: networkingv1alpha1.ConnectorStatus{
Conditions: []metav1.Condition{
{
Type: networkingv1alpha1.ConnectorConditionReady,
Status: metav1.ConditionFalse,
Reason: networkingv1alpha1.ConnectorReasonNotReady,
},
},
},
},
},
postCreateGatewayStatus: func(g *gatewayv1.Gateway) {
setGatewayProgrammedWithDefaultHTTPSListener(g)
},
expectedError: false,
expectedConditions: []metav1.Condition{
{
Type: networkingv1alpha.HTTPProxyConditionAccepted,
Status: metav1.ConditionTrue,
Reason: networkingv1alpha.HTTPProxyReasonAccepted,
},
{
Type: networkingv1alpha.HTTPProxyConditionProgrammed,
Status: metav1.ConditionTrue,
Reason: networkingv1alpha.HTTPProxyReasonProgrammed,
},
},
assert: func(t *testContext, cl client.Client, httpProxy *networkingv1alpha.HTTPProxy) {
ctx := context.Background()

// Extension-server mode emits no EnvoyPatchPolicy at all.
var patchList envoygatewayv1alpha1.EnvoyPatchPolicyList
assert.NoError(t, t.downstreamClient.List(ctx, &patchList))
assert.Empty(t, patchList.Items, "no EPP should be emitted in extension-server mode")

// The offline connector route MUST retain a backendRef pointing at
// the placeholder EndpointSlice so EG renders a connector cluster.
httpRoute := &gatewayv1.HTTPRoute{}
assert.NoError(t, cl.Get(ctx, client.ObjectKeyFromObject(httpProxy), httpRoute))
if assert.Len(t, httpRoute.Spec.Rules, 1) {
if assert.Len(t, httpRoute.Spec.Rules[0].BackendRefs, 1,
"offline connector route must keep a backendRef in extension-server mode") {
br := httpRoute.Spec.Rules[0].BackendRefs[0]
assert.Equal(t, gatewayv1.Kind("EndpointSlice"), *br.Kind)
}
}

// The connector.local placeholder EndpointSlice must exist.
var sliceList discoveryv1.EndpointSliceList
assert.NoError(t, cl.List(ctx, &sliceList, client.InNamespace(httpProxy.Namespace)))
if assert.Len(t, sliceList.Items, 1) {
if assert.NotEmpty(t, sliceList.Items[0].Endpoints) &&
assert.NotEmpty(t, sliceList.Items[0].Endpoints[0].Addresses) {
assert.Equal(t, "connector.local", sliceList.Items[0].Endpoints[0].Addresses[0])
}
}
},
},
{
name: "connector patch policy removed when connector cleared",
httpProxy: connectorClearedHTTPProxy,
Expand Down Expand Up @@ -1278,15 +1360,20 @@ func TestHTTPProxyReconcile(t *testing.T) {

mgr := &fakeMockManager{cl: fakeClient}

caseConfig := testConfig
if tt.eppEmissionDisabled {
caseConfig.Gateway.EPPEmissionEnabled = ptr.To(false)
}

reconciler := &HTTPProxyReconciler{
mgr: mgr,
Config: testConfig,
Config: caseConfig,
DownstreamCluster: &fakeCluster{cl: fakeDownstreamClient},
}

gatewayReconciler := &GatewayReconciler{
mgr: mgr,
Config: testConfig,
Config: caseConfig,
DownstreamCluster: &fakeCluster{cl: fakeDownstreamClient},
}

Expand Down
Loading