Skip to content

Commit a0970ee

Browse files
committed
fix: do not return 500 for all requests when part of BackendRefs are invalid (envoyproxy#7488)
* do not return 500 for all requests when part of BackendRefs are invalid Signed-off-by: Huabing Zhao <[email protected]> Signed-off-by: Huabing (Robin) Zhao <[email protected]> (cherry picked from commit 2899416) Signed-off-by: Huabing Zhao <[email protected]>
1 parent 0b82736 commit a0970ee

13 files changed

+680
-53
lines changed

internal/gatewayapi/route.go

Lines changed: 42 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,7 @@ func (t *Translator) processHTTPRouteRules(httpRoute *HTTPRouteContext, parentRe
233233
BackendRef: &rule.BackendRefs[i].BackendRef,
234234
Filters: rule.BackendRefs[i].Filters,
235235
}
236+
// ds will never be nil here because processDestination returns an empty DestinationSetting for invalid backendRefs.
236237
ds, unstructuredRef, err := t.processDestination(settingName, backendRefCtx, parentRef, httpRoute, resources)
237238
if err != nil {
238239
// Gateway API conformance: When backendRef Service exists but has no endpoints,
@@ -250,13 +251,12 @@ func (t *Translator) processHTTPRouteRules(httpRoute *HTTPRouteContext, parentRe
250251
))
251252
processDestinationError = err
252253
}
253-
continue
254254
}
255255
if unstructuredRef != nil {
256256
backendCustomRefs = append(backendCustomRefs, unstructuredRef)
257257
}
258-
// ds can be nil if the backendRef weight is 0
259-
if ds == nil {
258+
// skip backendRefs with weight 0 as they do not affect the traffic distribution
259+
if ds.Weight != nil && *ds.Weight == 0 {
260260
continue
261261
}
262262
allDs = append(allDs, ds)
@@ -275,9 +275,9 @@ func (t *Translator) processHTTPRouteRules(httpRoute *HTTPRouteContext, parentRe
275275
Metadata: routeRuleMetadata,
276276
}
277277
switch {
278-
// return 500 if any destination setting is invalid
278+
// return 500 if no valid destination settings exist
279279
// the error is already added to the error list when processing the destination
280-
case processDestinationError != nil:
280+
case processDestinationError != nil && destination.ToBackendWeights().Valid == 0:
281281
routesWithDirectResponse := sets.New[string]()
282282
for _, irRoute := range ruleRoutes {
283283
// If the route already has a direct response or redirect configured, then it was from a filter so skip
@@ -297,9 +297,9 @@ func (t *Translator) processHTTPRouteRules(httpRoute *HTTPRouteContext, parentRe
297297
"error", processDestinationError,
298298
)
299299
}
300-
// return 503 if endpoints does not exist
300+
// return 503 if no ready endpoints exist
301301
// the error is already added to the error list when processing the destination
302-
case failedNoReadyEndpoints && len(allDs) == 0:
302+
case failedNoReadyEndpoints && destination.ToBackendWeights().Valid == 0:
303303
routesWithDirectResponse := sets.New[string]()
304304
for _, irRoute := range ruleRoutes {
305305
// If the route already has a direct response or redirect configured, then it was from a filter so skip
@@ -334,8 +334,8 @@ func (t *Translator) processHTTPRouteRules(httpRoute *HTTPRouteContext, parentRe
334334
t.Logger.Info("setting 500 direct response in routes due to all valid destinations having 0 weight",
335335
"routes", sets.List(routesWithDirectResponse))
336336
}
337-
// A route can only have one destination if this destination is a dynamic resolver, because the behavior of
338-
// multiple destinations with one being a dynamic resolver just doesn't make sense.
337+
// A route can only have one destination if this destination is a dynamic resolver, because the behavior of
338+
// multiple destinations with one being a dynamic resolver just doesn't make sense.
339339
case hasDynamicResolver && len(rule.BackendRefs) > 1:
340340
routesWithDirectResponse := sets.New[string]()
341341
for _, irRoute := range ruleRoutes {
@@ -388,10 +388,6 @@ func (t *Translator) processHTTPRouteRules(httpRoute *HTTPRouteContext, parentRe
388388
}
389389
}
390390

391-
// TODO handle:
392-
// - sum of weights for valid backend refs is 0
393-
// - etc.
394-
395391
irRoutes = append(irRoutes, ruleRoutes...)
396392
}
397393
if errorCollector.Empty() {
@@ -826,6 +822,7 @@ func (t *Translator) processGRPCRouteRules(grpcRoute *GRPCRouteContext, parentRe
826822
BackendRef: &rule.BackendRefs[i].BackendRef,
827823
Filters: rule.BackendRefs[i].Filters,
828824
}
825+
// ds will never be nil here because processDestination returns an empty DestinationSetting for invalid backendRefs.
829826
ds, _, err := t.processDestination(settingName, backendRefCtx, parentRef, grpcRoute, resources)
830827
if err != nil {
831828
// Gateway API conformance: When backendRef Service exists but has no endpoints,
@@ -843,10 +840,10 @@ func (t *Translator) processGRPCRouteRules(grpcRoute *GRPCRouteContext, parentRe
843840
))
844841
processDestinationError = err
845842
}
846-
continue
847843
}
848844

849-
if ds == nil {
845+
// skip backendRefs with weight 0 as they do not affect the traffic distribution
846+
if ds.Weight != nil && *ds.Weight == 0 {
850847
continue
851848
}
852849
allDs = append(allDs, ds)
@@ -862,7 +859,7 @@ func (t *Translator) processGRPCRouteRules(grpcRoute *GRPCRouteContext, parentRe
862859
switch {
863860
// return 500 if any destination setting is invalid
864861
// the error is already added to the error list when processing the destination
865-
case processDestinationError != nil:
862+
case processDestinationError != nil && destination.ToBackendWeights().Valid == 0:
866863
routesWithDirectResponse := sets.New[string]()
867864
for _, irRoute := range ruleRoutes {
868865
// If the route already has a direct response or redirect configured, then it was from a filter so skip
@@ -883,7 +880,7 @@ func (t *Translator) processGRPCRouteRules(grpcRoute *GRPCRouteContext, parentRe
883880
}
884881
// return 503 if endpoints does not exist
885882
// the error is already added to the error list when processing the destination
886-
case failedNoReadyEndpoints && len(allDs) == 0:
883+
case failedNoReadyEndpoints && destination.ToBackendWeights().Valid == 0:
887884
routesWithDirectResponse := sets.New[string]()
888885
for _, irRoute := range ruleRoutes {
889886
// If the route already has a direct response or redirect configured, then it was from a filter so skip
@@ -945,10 +942,6 @@ func (t *Translator) processGRPCRouteRules(grpcRoute *GRPCRouteContext, parentRe
945942
}
946943
}
947944

948-
// TODO handle:
949-
// - sum of weights for valid backend refs is 0
950-
// - etc.
951-
952945
irRoutes = append(irRoutes, ruleRoutes...)
953946
}
954947

@@ -1195,12 +1188,14 @@ func (t *Translator) processTLSRouteParentRefs(tlsRoute *TLSRouteContext, resour
11951188
for i := range rule.BackendRefs {
11961189
settingName := irDestinationSettingName(destName, i)
11971190
backendRefCtx := DirectBackendRef{BackendRef: &rule.BackendRefs[i]}
1191+
// ds will never be nil here because processDestination returns an empty DestinationSetting for invalid backendRefs.
11981192
ds, _, err := t.processDestination(settingName, backendRefCtx, parentRef, tlsRoute, resources)
11991193
if err != nil {
12001194
resolveErrs.Add(err)
12011195
continue
12021196
}
1203-
if ds != nil {
1197+
// skip backendRefs with weight 0 as they do not affect the traffic distribution
1198+
if ds.Weight != nil && *ds.Weight > 0 {
12041199
destSettings = append(destSettings, ds)
12051200
}
12061201
}
@@ -1351,14 +1346,15 @@ func (t *Translator) processUDPRouteParentRefs(udpRoute *UDPRouteContext, resour
13511346
for i := range udpRoute.Spec.Rules[0].BackendRefs {
13521347
settingName := irDestinationSettingName(destName, i)
13531348
backendRefCtx := DirectBackendRef{BackendRef: &udpRoute.Spec.Rules[0].BackendRefs[i]}
1349+
// ds will never be nil here because processDestination returns an empty DestinationSetting for invalid backendRefs.
13541350
ds, _, err := t.processDestination(settingName, backendRefCtx, parentRef, udpRoute, resources)
13551351
if err != nil {
13561352
resolveErrs.Add(err)
13571353
continue
13581354
}
13591355

1360-
// Skip nil destination settings
1361-
if ds != nil {
1356+
// skip backendRefs with weight 0 as they do not affect the traffic distribution
1357+
if ds.Weight != nil && *ds.Weight > 0 {
13621358
destSettings = append(destSettings, ds)
13631359
}
13641360
}
@@ -1507,8 +1503,8 @@ func (t *Translator) processTCPRouteParentRefs(tcpRoute *TCPRouteContext, resour
15071503
resolveErrs.Add(err)
15081504
continue
15091505
}
1510-
// Skip nil destination settings
1511-
if ds != nil {
1506+
// skip backendRefs with weight 0 as they do not affect the traffic distribution
1507+
if ds.Weight != nil && *ds.Weight > 0 {
15121508
destSettings = append(destSettings, ds)
15131509
}
15141510
}
@@ -1613,27 +1609,33 @@ func (t *Translator) processTCPRouteParentRefs(tcpRoute *TCPRouteContext, resour
16131609
func (t *Translator) processDestination(name string, backendRefContext BackendRefContext,
16141610
parentRef *RouteParentContext, route RouteContext, resources *resource.Resources,
16151611
) (ds *ir.DestinationSetting, unstructuredRef *ir.UnstructuredRef, err status.Error) {
1616-
routeType := route.GetRouteType()
1617-
weight := uint32(1)
1618-
backendRef := backendRefContext.GetBackendRef()
1619-
if backendRef.Weight != nil {
1620-
weight = uint32(*backendRef.Weight)
1612+
var (
1613+
routeType = route.GetRouteType()
1614+
weight = (uint32(ptr.Deref(backendRefContext.GetBackendRef().Weight, int32(1))))
1615+
backendRef = backendRefContext.GetBackendRef()
1616+
)
1617+
1618+
// Create an empty DS without endpoints
1619+
// This represents an invalid DS.
1620+
emptyDS := &ir.DestinationSetting{
1621+
Name: name,
1622+
Weight: &weight,
16211623
}
16221624

16231625
backendNamespace := NamespaceDerefOr(backendRef.Namespace, route.GetNamespace())
16241626
if !t.isCustomBackendResource(backendRef.Group, KindDerefOr(backendRef.Kind, resource.KindService)) {
16251627
err = t.validateBackendRef(backendRefContext, route, resources, backendNamespace, routeType)
16261628
{
1627-
// return with empty endpoint means the backend is invalid and an error to fail the associated route.
1629+
// Empty DS means the backend is invalid and an error to fail the associated route.
16281630
if err != nil {
1629-
return nil, nil, err
1631+
return emptyDS, nil, err
16301632
}
16311633
}
16321634
}
16331635

16341636
// Skip processing backends with 0 weight
16351637
if weight == 0 {
1636-
return nil, nil, nil
1638+
return emptyDS, nil, nil
16371639
}
16381640

16391641
var envoyProxy *egv1a1.EnvoyProxy
@@ -1660,19 +1662,19 @@ func (t *Translator) processDestination(name string, backendRefContext BackendRe
16601662
envoyProxy,
16611663
)
16621664
if tlsErr != nil {
1663-
return nil, nil, status.NewRouteStatusError(tlsErr, status.RouteReasonInvalidBackendTLS)
1665+
return emptyDS, nil, status.NewRouteStatusError(tlsErr, status.RouteReasonInvalidBackendTLS)
16641666
}
16651667

16661668
switch KindDerefOr(backendRef.Kind, resource.KindService) {
16671669
case resource.KindServiceImport:
16681670
ds, err = t.processServiceImportDestinationSetting(name, backendRef.BackendObjectReference, backendNamespace, protocol, resources, envoyProxy)
16691671
if err != nil {
1670-
return nil, nil, err
1672+
return emptyDS, nil, err
16711673
}
16721674
case resource.KindService:
16731675
ds, err = t.processServiceDestinationSetting(name, backendRef.BackendObjectReference, backendNamespace, protocol, resources, envoyProxy)
16741676
if err != nil {
1675-
return nil, nil, err
1677+
return emptyDS, nil, err
16761678
}
16771679
svc := resources.GetService(backendNamespace, string(backendRef.Name))
16781680
ds.IPFamily = getServiceIPFamily(svc)
@@ -1687,7 +1689,7 @@ func (t *Translator) processDestination(name string, backendRefContext BackendRe
16871689

16881690
// Check if the custom backend resource was found
16891691
if unstructuredRef == nil {
1690-
return nil, nil, status.NewRouteStatusError(
1692+
return emptyDS, nil, status.NewRouteStatusError(
16911693
fmt.Errorf("custom backend %s %s/%s not found",
16921694
KindDerefOr(backendRef.Kind, resource.KindService),
16931695
backendNamespace,
@@ -1709,11 +1711,11 @@ func (t *Translator) processDestination(name string, backendRefContext BackendRe
17091711
var filtersErr error
17101712
ds.Filters, filtersErr = t.processDestinationFilters(routeType, backendRefContext, parentRef, route, resources)
17111713
if filtersErr != nil {
1712-
return nil, nil, status.NewRouteStatusError(filtersErr, status.RouteReasonInvalidBackendFilters)
1714+
return emptyDS, nil, status.NewRouteStatusError(filtersErr, status.RouteReasonInvalidBackendFilters)
17131715
}
17141716

17151717
if err := validateDestinationSettings(ds, t.IsEnvoyServiceRouting(envoyProxy), backendRef.Kind); err != nil {
1716-
return nil, nil, err
1718+
return emptyDS, nil, err
17171719
}
17181720

17191721
ds.Weight = &weight
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
gateways:
2+
- apiVersion: gateway.networking.k8s.io/v1
3+
kind: Gateway
4+
metadata:
5+
namespace: envoy-gateway
6+
name: gateway-1
7+
spec:
8+
gatewayClassName: envoy-gateway-class
9+
listeners:
10+
- name: http
11+
protocol: HTTP
12+
port: 80
13+
allowedRoutes:
14+
namespaces:
15+
from: All
16+
grpcRoutes:
17+
- apiVersion: gateway.networking.k8s.io/v1alpha2
18+
kind: GRPCRoute
19+
metadata:
20+
namespace: default
21+
name: grpcroute-1
22+
spec:
23+
parentRefs:
24+
- namespace: envoy-gateway
25+
name: gateway-1
26+
sectionName: http
27+
rules:
28+
- matches:
29+
- method:
30+
method: ExampleExact
31+
type: Exact
32+
backendRefs:
33+
- name: service-1
34+
port: 8080
35+
- name: service-not-exist
36+
port: 8080
37+
services:
38+
- apiVersion: v1
39+
kind: Service
40+
metadata:
41+
name: service-1
42+
spec:
43+
clusterIP: 7.7.7.7
44+
ports:
45+
- port: 8080

0 commit comments

Comments
 (0)