Skip to content

Commit add5375

Browse files
committed
fix(gateway): harden conformance validation
1 parent 41a77b0 commit add5375

10 files changed

Lines changed: 318 additions & 67 deletions

File tree

Makefile

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
.PHONY: build test benchmarks conformance e2e-smoke
22

33
CLUSTER_NAME ?= nantian-conformance
4+
CONFORMANCE_TIMEOUT ?= 300
5+
CONTROL_PLANE_IMAGE ?= ghcr.io/nantian-gw/nantian-controlplane:latest
6+
DATA_PLANE_IMAGE ?= ghcr.io/nantian-gw/dataplane:latest
7+
DASHBOARD_IMAGE ?= ghcr.io/nantian-gw/dashboard:latest
8+
CONFORMANCE_ECHO_BASIC_IMAGE ?= gcr.io/k8s-staging-gateway-api/echo-basic:v20260204-monthly-2026.01-60-g28382302
9+
CONFORMANCE_COREDNS_IMAGE ?= registry.k8s.io/coredns/coredns:v1.12.2
10+
CONFORMANCE_ECHO_ADVANCED_IMAGE ?= gcr.io/k8s-staging-gateway-api/echo-advanced:v20240412-v1.0.0-394-g40c666fd
11+
CONFORMANCE_TEST_IMAGES ?= $(CONFORMANCE_ECHO_BASIC_IMAGE) $(CONFORMANCE_COREDNS_IMAGE) $(CONFORMANCE_ECHO_ADVANCED_IMAGE)
412
GATEWAY_API_CRDS_STANDARD ?= https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.5.1/standard-install.yaml
513
GATEWAY_API_CRDS_EXPERIMENTAL ?= https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.5.1/experimental-install.yaml
614

@@ -15,15 +23,15 @@ benchmarks:
1523

1624
conformance:
1725
@echo "=== Creating kind cluster: $(CLUSTER_NAME) ==="
18-
kind create cluster --name $(CLUSTER_NAME) --wait 5m
19-
kubectl wait --for=condition=ready node --all --timeout=2m
26+
CLUSTER_NAME=$(CLUSTER_NAME) scripts/ci/create-kind-cluster.sh
2027
@echo "=== Installing Gateway API CRDs ==="
2128
GATEWAY_API_CHANNEL=experimental scripts/ci/install-gateway-api-crds.sh
29+
@echo "=== Preloading nantian-gw images ==="
30+
CONFORMANCE_TEST_IMAGES="$(CONFORMANCE_TEST_IMAGES)" CLUSTER_NAME=$(CLUSTER_NAME) CONTROL_PLANE_IMAGE=$(CONTROL_PLANE_IMAGE) DATA_PLANE_IMAGE=$(DATA_PLANE_IMAGE) DASHBOARD_IMAGE=$(DASHBOARD_IMAGE) scripts/ci/load-kind-images.sh
2231
@echo "=== Deploying nantian-gw ==="
23-
kustomize build deploy/kubernetes/overlays/kind-conformance --load-restrictor LoadRestrictionsNone | kubectl apply -f -
24-
kubectl wait --for=condition=ready pod --all -n nantian-gw --timeout=180s
32+
CONTROL_PLANE_IMAGE=$(CONTROL_PLANE_IMAGE) DATA_PLANE_IMAGE=$(DATA_PLANE_IMAGE) DASHBOARD_IMAGE=$(DASHBOARD_IMAGE) TIMEOUT=$(CONFORMANCE_TIMEOUT)s scripts/ci/deploy-kind-conformance.sh
2533
@echo "=== Running conformance tests ==="
26-
go test -tags=conformance -count=1 -v -timeout 30m ./conformance/
34+
go test -tags=conformance -count=1 -v -timeout 30m ./conformance/ -args -gateway-class nantian-gw
2735
@echo "=== Cleaning up ==="
2836
kind delete cluster --name $(CLUSTER_NAME)
2937

deploy/kubernetes/overlays/kind-conformance/dataplane-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ accessLog:
1616
path: "stdout"
1717
format: "%START_TIME% %CLIENT_IP% %HOST% %METHOD% %PATH% %ROUTE_NAME% %BACKEND% %STATUS% %LATENCY_MS%"
1818
runtime:
19-
httpListenAddr: "0.0.0.0:10080"
19+
httpListenAddr: "0.0.0.0:80"
2020
enableIpv6: true
2121
enableHttp3: false
2222
tlsMinVersion: "1.2"

docs/gateway-api-feature-support.md

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ This page summarizes Nantian Gateway's current support status for upstream Gatew
55
- part of an upstream `ExtendedFeatures` set, or
66
- marked with upstream `experimental` channel metadata.
77

8-
It is intentionally narrower than a full product compatibility guide. Core features such as `Gateway`, `HTTPRoute`, `GRPCRoute`, `Mesh`, `ReferenceGrant`, `BackendTLSPolicy`, and the base `TLSRoute` feature are not repeated here.
8+
It is intentionally narrower than a full product compatibility guide. Core features such as `Gateway`, `HTTPRoute`, `Mesh`, `ReferenceGrant`, and the base `TLSRoute` feature are not repeated here.
99

1010
## How To Read This Matrix
1111

@@ -25,15 +25,15 @@ For conformance runs, `ALL_FEATURES=true` expands the suite to the repository-su
2525
|---|---|---|---|---|---|
2626
| GatewayPort8080 | Gateway ext | standard | Yes | Yes | Supported |
2727
| GatewayStaticAddresses | Gateway ext | standard | Yes | Yes | Supported |
28-
| GatewayHTTPListenerIsolation | Gateway ext | standard | Yes | Yes | Supported |
28+
| GatewayHTTPListenerIsolation | Gateway ext | standard | No | No | Not advertised pending conformance |
2929
| GatewayHTTPSListenerDetectMisdirectedRequests | Gateway ext | standard | Yes | Yes | Supported |
3030
| GatewayInfrastructurePropagation | Gateway ext | standard | Yes | Yes | Supported |
3131
| GatewayAddressEmpty | Gateway ext | standard | Yes | Yes | Supported |
3232
| ListenerSet | Gateway ext | standard | No | Yes | Experimental runtime only |
3333
| GatewayBackendClientCertificate | Gateway ext | standard | Yes | Yes | Supported |
3434
| GatewayFrontendClientCertificateValidation | Gateway ext | standard | No | No | Not supported |
3535
| GatewayFrontendClientCertificateValidationInsecureFallback | Gateway ext | standard | No | No | Not supported |
36-
| GRPCRouteNamedRouteRule | GRPCRoute ext | standard | Yes | Yes | Supported |
36+
| GRPCRouteNamedRouteRule | GRPCRoute ext | standard | No | No | Not advertised pending conformance |
3737
| HTTPRouteDestinationPortMatching | HTTPRoute ext | experimental | Yes | Yes | Supported |
3838
| HTTPRouteBackendRequestHeaderModification | HTTPRoute ext | standard | Yes | Yes | Supported |
3939
| HTTPRouteQueryParamMatching | HTTPRoute ext | standard | Yes | Yes | Supported |
@@ -50,13 +50,13 @@ For conformance runs, `ALL_FEATURES=true` expands the suite to the repository-su
5050
| HTTPRouteRequestTimeout | HTTPRoute ext | standard | Yes | Yes | Supported |
5151
| HTTPRouteBackendTimeout | HTTPRoute ext | standard | Yes | Yes | Supported |
5252
| HTTPRouteParentRefPort | HTTPRoute ext | standard | Yes | Yes | Supported |
53-
| HTTPRouteBackendProtocolH2C | HTTPRoute ext | standard | Yes | Yes | Supported |
54-
| HTTPRouteBackendProtocolWebSocket | HTTPRoute ext | standard | Yes | Yes | Supported |
53+
| HTTPRouteBackendProtocolH2C | HTTPRoute ext | standard | No | No | Not advertised pending conformance |
54+
| HTTPRouteBackendProtocolWebSocket | HTTPRoute ext | standard | No | No | Not advertised pending conformance |
5555
| HTTPRouteNamedRouteRule | HTTPRoute ext | standard | Yes | Yes | Supported |
56-
| HTTPRouteCORS | HTTPRoute ext | standard | Yes | Yes | Supported |
57-
| HTTPRoute303RedirectStatusCode | HTTPRoute ext | standard | Yes | Yes | Supported |
58-
| HTTPRoute307RedirectStatusCode | HTTPRoute ext | standard | Yes | Yes | Supported |
59-
| HTTPRoute308RedirectStatusCode | HTTPRoute ext | standard | Yes | Yes | Supported |
56+
| HTTPRouteCORS | HTTPRoute ext | standard | No | No | Not advertised pending conformance |
57+
| HTTPRoute303RedirectStatusCode | HTTPRoute ext | standard | No | No | Not advertised pending conformance |
58+
| HTTPRoute307RedirectStatusCode | HTTPRoute ext | standard | No | No | Not advertised pending conformance |
59+
| HTTPRoute308RedirectStatusCode | HTTPRoute ext | standard | No | No | Not advertised pending conformance |
6060
| TLSRouteModeTerminate | TLSRoute ext | standard | No | Yes | Experimental runtime only |
6161
| TLSRouteModeMixed | TLSRoute ext | experimental | No | Yes | Experimental runtime only |
6262
| MeshClusterIPMatching | Mesh ext | standard | Yes | Yes | Supported |
@@ -68,13 +68,14 @@ For conformance runs, `ALL_FEATURES=true` expands the suite to the repository-su
6868
| MeshHTTPRouteBackendRequestHeaderModification | Mesh ext | standard | Yes | Yes | Supported |
6969
| MeshHTTPRouteQueryParamMatching | Mesh ext | standard | Yes | Yes | Supported |
7070
| MeshHTTPRouteNamedRouteRule | Mesh ext | standard | Yes | Yes | Supported |
71-
| BackendTLSPolicySANValidation | BackendTLSPolicy ext | standard | Yes | Yes | Supported |
71+
| BackendTLSPolicySANValidation | BackendTLSPolicy ext | standard | No | No | Not advertised pending conformance |
7272
| UDPRoute | UDPRoute | experimental | No | Yes | Experimental runtime only |
7373

7474
## Notes
7575

7676
- `ListenerSet`, `TLSRouteModeTerminate`, `TLSRouteModeMixed`, and `UDPRoute` require `enableExperimentalGateway=true` even though some of them are upstream standard-channel features. That runtime gate reflects Nantian Gateway's current implementation boundary, not just the upstream channel label.
7777
- `HTTPRouteDestinationPortMatching` is an upstream experimental-channel feature, but Nantian Gateway currently advertises it in the default runtime.
78+
- `BackendTLSPolicy`, `GRPCRoute`, and their listed extension features are not advertised until their upstream conformance probes pass.
7879
- The two upstream features that remain unsupported are:
7980
- `GatewayFrontendClientCertificateValidation`
8081
- `GatewayFrontendClientCertificateValidationInsecureFallback`

internal/gatewayapi/supported_features.go

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -34,28 +34,17 @@ var experimentalGatewayFeatures = []gatewayfeatures.FeatureName{
3434
// advertises through the in-repo conformance profile.
3535
func SupportedFeatureNameSet() sets.Set[gatewayfeatures.FeatureName] {
3636
return sets.New(
37-
gatewayfeatures.SupportBackendTLSPolicy,
38-
gatewayfeatures.SupportBackendTLSPolicySANValidation,
3937
SupportedBackendLBSessionPersistence,
40-
gatewayfeatures.SupportGRPCRoute,
41-
gatewayfeatures.SupportGRPCRouteNamedRouteRule,
4238
gatewayfeatures.SupportGateway,
4339
gatewayfeatures.SupportGatewayAddressEmpty,
4440
gatewayfeatures.SupportGatewayBackendClientCertificate,
4541
gatewayfeatures.SupportGatewayHTTPSListenerDetectMisdirectedRequests,
46-
gatewayfeatures.SupportGatewayHTTPListenerIsolation,
4742
gatewayfeatures.SupportGatewayInfrastructurePropagation,
4843
gatewayfeatures.SupportGatewayPort8080,
4944
gatewayfeatures.SupportGatewayStaticAddresses,
5045
gatewayfeatures.SupportHTTPRoute,
51-
gatewayfeatures.SupportHTTPRoute303RedirectStatusCode,
52-
gatewayfeatures.SupportHTTPRoute307RedirectStatusCode,
53-
gatewayfeatures.SupportHTTPRoute308RedirectStatusCode,
54-
gatewayfeatures.SupportHTTPRouteBackendProtocolH2C,
55-
gatewayfeatures.SupportHTTPRouteBackendProtocolWebSocket,
5646
gatewayfeatures.SupportHTTPRouteBackendRequestHeaderModification,
5747
gatewayfeatures.SupportHTTPRouteBackendTimeout,
58-
gatewayfeatures.SupportHTTPRouteCORS,
5948
gatewayfeatures.SupportHTTPRouteDestinationPortMatching,
6049
gatewayfeatures.SupportHTTPRouteHostRewrite,
6150
gatewayfeatures.SupportHTTPRouteMethodMatching,

internal/gatewayapi/supported_features_test.go

Lines changed: 33 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -19,27 +19,16 @@ func TestSupportedFeatureNamesAreSortedAndComplete(t *testing.T) {
1919

2020
want := sortedFeatureNames([]gatewayfeatures.FeatureName{
2121
SupportedBackendLBSessionPersistence,
22-
gatewayfeatures.SupportBackendTLSPolicy,
23-
gatewayfeatures.SupportBackendTLSPolicySANValidation,
24-
gatewayfeatures.SupportGRPCRoute,
25-
gatewayfeatures.SupportGRPCRouteNamedRouteRule,
2622
gatewayfeatures.SupportGateway,
2723
gatewayfeatures.SupportGatewayAddressEmpty,
2824
gatewayfeatures.SupportGatewayBackendClientCertificate,
29-
gatewayfeatures.SupportGatewayHTTPListenerIsolation,
3025
gatewayfeatures.SupportGatewayHTTPSListenerDetectMisdirectedRequests,
3126
gatewayfeatures.SupportGatewayInfrastructurePropagation,
3227
gatewayfeatures.SupportGatewayPort8080,
3328
gatewayfeatures.SupportGatewayStaticAddresses,
3429
gatewayfeatures.SupportHTTPRoute,
35-
gatewayfeatures.SupportHTTPRoute303RedirectStatusCode,
36-
gatewayfeatures.SupportHTTPRoute307RedirectStatusCode,
37-
gatewayfeatures.SupportHTTPRoute308RedirectStatusCode,
38-
gatewayfeatures.SupportHTTPRouteBackendProtocolH2C,
39-
gatewayfeatures.SupportHTTPRouteBackendProtocolWebSocket,
4030
gatewayfeatures.SupportHTTPRouteBackendRequestHeaderModification,
4131
gatewayfeatures.SupportHTTPRouteBackendTimeout,
42-
gatewayfeatures.SupportHTTPRouteCORS,
4332
gatewayfeatures.SupportHTTPRouteDestinationPortMatching,
4433
gatewayfeatures.SupportHTTPRouteHostRewrite,
4534
gatewayfeatures.SupportHTTPRouteMethodMatching,
@@ -98,7 +87,6 @@ func TestSupportedFeatureNamesForOptionsExcludesExperimentalGatewayFeaturesWhenD
9887
for _, name := range []gatewayfeatures.FeatureName{
9988
gatewayfeatures.SupportGateway,
10089
gatewayfeatures.SupportHTTPRoute,
101-
gatewayfeatures.SupportGRPCRoute,
10290
gatewayfeatures.SupportReferenceGrant,
10391
} {
10492
if !names[name] {
@@ -131,6 +119,39 @@ func TestSupportedFeatureNamesForOptionsExcludesUnsupportedFrontendClientCertifi
131119
}
132120
}
133121

122+
func TestSupportedFeatureNamesForOptionsExcludesNonConformantOptionalFeatures(t *testing.T) {
123+
for _, tc := range []struct {
124+
name string
125+
options FeatureOptions
126+
}{
127+
{name: "default runtime", options: FeatureOptions{EnableExperimentalGateway: false}},
128+
{name: "experimental runtime", options: FeatureOptions{EnableExperimentalGateway: true}},
129+
} {
130+
t.Run(tc.name, func(t *testing.T) {
131+
got := SupportedFeatureNamesForOptions(tc.options)
132+
names := featureNameSet(got)
133+
134+
for _, name := range []gatewayfeatures.FeatureName{
135+
gatewayfeatures.SupportBackendTLSPolicy,
136+
gatewayfeatures.SupportBackendTLSPolicySANValidation,
137+
gatewayfeatures.SupportGatewayHTTPListenerIsolation,
138+
gatewayfeatures.SupportGRPCRoute,
139+
gatewayfeatures.SupportGRPCRouteNamedRouteRule,
140+
gatewayfeatures.SupportHTTPRoute303RedirectStatusCode,
141+
gatewayfeatures.SupportHTTPRoute307RedirectStatusCode,
142+
gatewayfeatures.SupportHTTPRoute308RedirectStatusCode,
143+
gatewayfeatures.SupportHTTPRouteBackendProtocolH2C,
144+
gatewayfeatures.SupportHTTPRouteBackendProtocolWebSocket,
145+
gatewayfeatures.SupportHTTPRouteCORS,
146+
} {
147+
if names[name] {
148+
t.Fatalf("feature %s should not be advertised until it passes Gateway API conformance: %#v", name, got)
149+
}
150+
}
151+
})
152+
}
153+
}
154+
134155
func TestSupportedFeatureNamesForOptionsIncludesExperimentalGatewayFeaturesWhenEnabled(t *testing.T) {
135156
got := SupportedFeatureNamesForOptions(FeatureOptions{EnableExperimentalGateway: true})
136157
names := featureNameSet(got)
@@ -150,27 +171,16 @@ func TestSupportedFeatureNamesForOptionsIncludesExperimentalGatewayFeaturesWhenE
150171

151172
want := sortedFeatureNames([]gatewayfeatures.FeatureName{
152173
SupportedBackendLBSessionPersistence,
153-
gatewayfeatures.SupportBackendTLSPolicy,
154-
gatewayfeatures.SupportBackendTLSPolicySANValidation,
155-
gatewayfeatures.SupportGRPCRoute,
156-
gatewayfeatures.SupportGRPCRouteNamedRouteRule,
157174
gatewayfeatures.SupportGateway,
158175
gatewayfeatures.SupportGatewayAddressEmpty,
159176
gatewayfeatures.SupportGatewayBackendClientCertificate,
160-
gatewayfeatures.SupportGatewayHTTPListenerIsolation,
161177
gatewayfeatures.SupportGatewayHTTPSListenerDetectMisdirectedRequests,
162178
gatewayfeatures.SupportGatewayInfrastructurePropagation,
163179
gatewayfeatures.SupportGatewayPort8080,
164180
gatewayfeatures.SupportGatewayStaticAddresses,
165181
gatewayfeatures.SupportHTTPRoute,
166-
gatewayfeatures.SupportHTTPRoute303RedirectStatusCode,
167-
gatewayfeatures.SupportHTTPRoute307RedirectStatusCode,
168-
gatewayfeatures.SupportHTTPRoute308RedirectStatusCode,
169-
gatewayfeatures.SupportHTTPRouteBackendProtocolH2C,
170-
gatewayfeatures.SupportHTTPRouteBackendProtocolWebSocket,
171182
gatewayfeatures.SupportHTTPRouteBackendRequestHeaderModification,
172183
gatewayfeatures.SupportHTTPRouteBackendTimeout,
173-
gatewayfeatures.SupportHTTPRouteCORS,
174184
gatewayfeatures.SupportHTTPRouteDestinationPortMatching,
175185
gatewayfeatures.SupportHTTPRouteHostRewrite,
176186
gatewayfeatures.SupportHTTPRouteMethodMatching,

internal/grpcserver/snapshot_projection.go

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package grpcserver
22

33
import (
44
"log/slog"
5+
"strconv"
56

67
"github.com/nantian-gw/gateway/internal/ir"
78
controlv1 "github.com/nantian-gw/proto/gateway/control/v1"
@@ -173,13 +174,30 @@ func filterBackendRefs(routeNamespace string, refs []ir.BackendRef, survivingBac
173174
if namespace == "" {
174175
namespace = routeNamespace
175176
}
176-
if _, ok := survivingBackends[backendProjectionKey(namespace, ref.Name)]; ok {
177+
if backendRefSurvivesProjection(namespace, ref, survivingBackends) {
177178
out = append(out, ref)
178179
}
179180
}
180181
return out
181182
}
182183

184+
func backendRefSurvivesProjection(namespace string, ref ir.BackendRef, survivingBackends map[string]struct{}) bool {
185+
if backendRefMarkedInvalidForProjection(ref) {
186+
return true
187+
}
188+
if ref.Port != 0 {
189+
if _, ok := survivingBackends[backendProjectionKey(namespace, ref.Name+":"+strconv.FormatUint(uint64(ref.Port), 10))]; ok {
190+
return true
191+
}
192+
}
193+
_, ok := survivingBackends[backendProjectionKey(namespace, ref.Name)]
194+
return ok
195+
}
196+
197+
func backendRefMarkedInvalidForProjection(ref ir.BackendRef) bool {
198+
return ref.Metadata["nantian.dev/backend-ref-valid"] == "false"
199+
}
200+
183201
func backendRequiresUnsupportedHardFeature(backend ir.BackendCluster, supported map[string]struct{}) bool {
184202
if backend.AIService != nil && !supportsFeature(supported, featureBackendAIServiceV1) {
185203
return true

internal/grpcserver/snapshot_projection_test.go

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,111 @@ func TestProjectedSnapshotFullProfilePreservesAIServiceTokenPolicyWasmAndLabels(
135135
}
136136
}
137137

138+
func TestProjectedSnapshotPreservesBackendRefsMatchedByServiceNameAndPort(t *testing.T) {
139+
t.Parallel()
140+
141+
projected := buildProjectedProtoSnapshot(
142+
&ir.Snapshot{
143+
ID: "projection-port-backend",
144+
GeneratedAt: time.Unix(1_700_000_000, 0).UTC(),
145+
Listeners: []ir.Listener{{
146+
Name: "listener-http",
147+
Address: "0.0.0.0",
148+
Port: 80,
149+
Protocol: "HTTP",
150+
AttachedRoutes: []string{"default/http-port"},
151+
}},
152+
HTTPRoutes: []ir.HTTPRoute{{
153+
Name: "http-port",
154+
Namespace: "default",
155+
Rules: []ir.HTTPRule{{
156+
Name: "service-port",
157+
BackendRefs: []ir.BackendRef{{
158+
Name: "echo",
159+
Namespace: "default",
160+
Port: 8080,
161+
Weight: 1,
162+
}},
163+
}},
164+
}},
165+
Backends: []ir.BackendCluster{{
166+
Name: "echo:8080",
167+
Namespace: "default",
168+
Protocol: "HTTP",
169+
ConnectTimeout: 5 * time.Second,
170+
Endpoints: []ir.BackendEndpoint{{
171+
Address: "10.0.0.10",
172+
Port: 3000,
173+
Healthy: true,
174+
}},
175+
}},
176+
},
177+
effectiveProjectionProfile([]string{featureCoreV1}),
178+
slog.New(slog.NewTextHandler(io.Discard, nil)),
179+
)
180+
181+
listener := findProjectedListener(t, projected, "listener-http")
182+
if got, want := listener.GetAttachedRoutes(), []string{"default/http-port"}; !reflect.DeepEqual(got, want) {
183+
t.Fatalf("attached routes = %#v, want %#v", got, want)
184+
}
185+
186+
route := findProjectedHTTPRoute(t, projected, "http-port")
187+
if got := route.GetRules()[0].GetBackendRefs(); len(got) != 1 || got[0].GetName() != "echo" || got[0].GetPort() != 8080 {
188+
t.Fatalf("projected backend refs = %#v, want echo:8080 reference preserved", got)
189+
}
190+
}
191+
192+
func TestProjectedSnapshotPreservesInvalidBackendRefsWithoutBackendCluster(t *testing.T) {
193+
t.Parallel()
194+
195+
projected := buildProjectedProtoSnapshot(
196+
&ir.Snapshot{
197+
ID: "projection-invalid-backend",
198+
GeneratedAt: time.Unix(1_700_000_000, 0).UTC(),
199+
Listeners: []ir.Listener{{
200+
Name: "listener-http",
201+
Address: "0.0.0.0",
202+
Port: 80,
203+
Protocol: "HTTP",
204+
AttachedRoutes: []string{"default/http-invalid-backend"},
205+
}},
206+
HTTPRoutes: []ir.HTTPRoute{{
207+
Name: "http-invalid-backend",
208+
Namespace: "default",
209+
Rules: []ir.HTTPRule{{
210+
Name: "missing-service",
211+
BackendRefs: []ir.BackendRef{{
212+
Name: "missing",
213+
Namespace: "default",
214+
Port: 8080,
215+
Weight: 1,
216+
Metadata: map[string]string{
217+
"nantian.dev/backend-ref-valid": "false",
218+
"nantian.dev/backend-ref-reason": "BackendNotFound",
219+
},
220+
}},
221+
}},
222+
}},
223+
},
224+
effectiveProjectionProfile([]string{featureCoreV1}),
225+
slog.New(slog.NewTextHandler(io.Discard, nil)),
226+
)
227+
228+
listener := findProjectedListener(t, projected, "listener-http")
229+
if got, want := listener.GetAttachedRoutes(), []string{"default/http-invalid-backend"}; !reflect.DeepEqual(got, want) {
230+
t.Fatalf("attached routes = %#v, want %#v", got, want)
231+
}
232+
233+
route := findProjectedHTTPRoute(t, projected, "http-invalid-backend")
234+
refs := route.GetRules()[0].GetBackendRefs()
235+
if len(refs) != 1 {
236+
t.Fatalf("projected backend refs = %#v, want invalid ref preserved", refs)
237+
}
238+
if got := refs[0].GetMetadata()["nantian.dev/backend-ref-valid"]; got != "false" {
239+
t.Fatalf("projected backend ref metadata = %#v, want invalid marker preserved", refs[0].GetMetadata())
240+
}
241+
}
242+
138243
func projectionTestSnapshot() *ir.Snapshot {
139244
return &ir.Snapshot{
140245
ID: "projection-snapshot",

0 commit comments

Comments
 (0)