Skip to content

Commit 78455da

Browse files
authored
envoy/eds: respond to EDS request for cluster with no endpoints (openservicemesh#4085)
It's possible for a cluster created via CDS to have no endpoints, such as an apex service referenced in a traffic split resource that is not backed by any pods. Respond to the EDS request for such a cluster with no endpoints so that Envoy does not keep requesting for it. Signed-off-by: Shashank Ram <[email protected]>
1 parent 98d8904 commit 78455da

File tree

3 files changed

+88
-46
lines changed

3 files changed

+88
-46
lines changed

pkg/envoy/eds/cluster_load_assignment.go

+15-9
Original file line numberDiff line numberDiff line change
@@ -29,26 +29,32 @@ func newClusterLoadAssignment(svc service.MeshService, serviceEndpoints []endpoi
2929
},
3030
}
3131

32-
lenIPs := len(serviceEndpoints)
33-
if lenIPs == 0 {
34-
lenIPs = 1
32+
// If there are no service endpoints corresponding to this service, we
33+
// return a ClusterLoadAssignment without any endpoints.
34+
// Envoy will correctly handle this response.
35+
// This can happen if we create a cluster via CDS corresponding to a traffic split
36+
// apex service that has no endpoints.
37+
if len(serviceEndpoints) == 0 {
38+
return cla
3539
}
36-
weight := uint32(100 / lenIPs)
40+
41+
// Equal weight is assigned to a cluster with multiple endpoints in the same locality
42+
lbWeightPerEndpoint := 100 / len(serviceEndpoints)
3743

3844
for _, meshEndpoint := range serviceEndpoints {
39-
log.Trace().Msgf("Adding Endpoint: Cluster=%s, Services=%s, Endpoint=%s, Weight=%d", svc, svc, meshEndpoint, weight)
40-
lbEpt := xds_endpoint.LbEndpoint{
45+
log.Trace().Msgf("Adding Endpoint: cluster=%s, endpoint=%s, weight=%d", svc, meshEndpoint, lbWeightPerEndpoint)
46+
lbEpt := &xds_endpoint.LbEndpoint{
4147
HostIdentifier: &xds_endpoint.LbEndpoint_Endpoint{
4248
Endpoint: &xds_endpoint.Endpoint{
4349
Address: envoy.GetAddress(meshEndpoint.IP.String(), uint32(meshEndpoint.Port)),
4450
},
4551
},
4652
LoadBalancingWeight: &wrappers.UInt32Value{
47-
Value: weight,
53+
Value: uint32(lbWeightPerEndpoint),
4854
},
4955
}
50-
cla.Endpoints[0].LbEndpoints = append(cla.Endpoints[0].LbEndpoints, &lbEpt)
56+
cla.Endpoints[0].LbEndpoints = append(cla.Endpoints[0].LbEndpoints, lbEpt)
5157
}
52-
log.Trace().Msgf("Constructed ClusterLoadAssignment: %v", cla)
58+
5359
return cla
5460
}

pkg/envoy/eds/cluster_load_assignment_test.go

+73-32
Original file line numberDiff line numberDiff line change
@@ -4,48 +4,89 @@ import (
44
"net"
55
"testing"
66

7+
xds_core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
8+
xds_endpoint "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3"
9+
"github.com/golang/protobuf/ptypes/wrappers"
10+
"github.com/google/go-cmp/cmp"
711
tassert "github.com/stretchr/testify/assert"
12+
"google.golang.org/protobuf/testing/protocmp"
813

914
"github.com/openservicemesh/osm/pkg/endpoint"
15+
"github.com/openservicemesh/osm/pkg/envoy"
1016
"github.com/openservicemesh/osm/pkg/service"
1117
)
1218

1319
func TestNewClusterLoadAssignment(t *testing.T) {
14-
assert := tassert.New(t)
15-
16-
namespacedServices := []service.MeshService{
17-
{Namespace: "ns1", Name: "bookstore-1", TargetPort: 80},
18-
{Namespace: "ns2", Name: "bookstore-2", TargetPort: 90},
19-
}
20-
21-
allServiceEndpoints := map[service.MeshService][]endpoint.Endpoint{
22-
namespacedServices[0]: {
23-
{IP: net.IP("0.0.0.0")},
20+
testCases := []struct {
21+
name string
22+
svc service.MeshService
23+
endpoints []endpoint.Endpoint
24+
expected *xds_endpoint.ClusterLoadAssignment
25+
}{
26+
{
27+
name: "multiple endpoints per cluster within the same locality",
28+
svc: service.MeshService{Namespace: "ns1", Name: "bookstore-1", TargetPort: 80},
29+
endpoints: []endpoint.Endpoint{
30+
{IP: net.ParseIP("1.1.1.1"), Port: 80},
31+
{IP: net.ParseIP("2.2.2.2"), Port: 80},
32+
},
33+
expected: &xds_endpoint.ClusterLoadAssignment{
34+
ClusterName: "ns1/bookstore-1|80",
35+
Endpoints: []*xds_endpoint.LocalityLbEndpoints{
36+
{
37+
Locality: &xds_core.Locality{
38+
Zone: zone,
39+
},
40+
LbEndpoints: []*xds_endpoint.LbEndpoint{
41+
{
42+
HostIdentifier: &xds_endpoint.LbEndpoint_Endpoint{
43+
Endpoint: &xds_endpoint.Endpoint{
44+
Address: envoy.GetAddress("1.1.1.1", 80),
45+
},
46+
},
47+
LoadBalancingWeight: &wrappers.UInt32Value{
48+
Value: 50,
49+
},
50+
},
51+
{
52+
HostIdentifier: &xds_endpoint.LbEndpoint_Endpoint{
53+
Endpoint: &xds_endpoint.Endpoint{
54+
Address: envoy.GetAddress("2.2.2.2", 80),
55+
},
56+
},
57+
LoadBalancingWeight: &wrappers.UInt32Value{
58+
Value: 50,
59+
},
60+
},
61+
},
62+
},
63+
},
64+
},
2465
},
25-
namespacedServices[1]: {
26-
{IP: net.IP("0.0.0.1")},
27-
{IP: net.IP("0.0.0.2")},
66+
{
67+
name: "no endpoints for cluster",
68+
svc: service.MeshService{Namespace: "ns1", Name: "bookstore-1", TargetPort: 80},
69+
endpoints: nil,
70+
expected: &xds_endpoint.ClusterLoadAssignment{
71+
ClusterName: "ns1/bookstore-1|80",
72+
Endpoints: []*xds_endpoint.LocalityLbEndpoints{
73+
{
74+
Locality: &xds_core.Locality{
75+
Zone: zone,
76+
},
77+
LbEndpoints: []*xds_endpoint.LbEndpoint{},
78+
},
79+
},
80+
},
2881
},
2982
}
3083

31-
cla := newClusterLoadAssignment(namespacedServices[0], allServiceEndpoints[namespacedServices[0]])
32-
assert.NotNil(cla)
33-
assert.Equal(cla.ClusterName, "ns1/bookstore-1|80")
34-
assert.Len(cla.Endpoints, 1)
35-
assert.Len(cla.Endpoints[0].LbEndpoints, 1)
36-
assert.Equal(cla.Endpoints[0].LbEndpoints[0].GetLoadBalancingWeight().Value, uint32(100))
84+
for _, tc := range testCases {
85+
t.Run(tc.name, func(t *testing.T) {
86+
assert := tassert.New(t)
3787

38-
cla2 := newClusterLoadAssignment(namespacedServices[1], allServiceEndpoints[namespacedServices[1]])
39-
assert.NotNil(cla2)
40-
assert.Equal(cla2.ClusterName, "ns2/bookstore-2|90")
41-
assert.Len(cla2.Endpoints, 1)
42-
assert.Len(cla2.Endpoints[0].LbEndpoints, 2)
43-
assert.Equal(cla2.Endpoints[0].LbEndpoints[0].GetLoadBalancingWeight().Value, uint32(50))
44-
assert.Equal(cla2.Endpoints[0].LbEndpoints[1].GetLoadBalancingWeight().Value, uint32(50))
45-
46-
cla3 := newClusterLoadAssignment(namespacedServices[0], []endpoint.Endpoint{})
47-
assert.NotNil(cla3)
48-
assert.Equal(cla3.ClusterName, "ns1/bookstore-1|80")
49-
assert.Len(cla3.Endpoints, 1)
50-
assert.Len(cla3.Endpoints[0].LbEndpoints, 0)
88+
actual := newClusterLoadAssignment(tc.svc, tc.endpoints)
89+
assert.True(cmp.Equal(tc.expected, actual, protocmp.Transform()), cmp.Diff(tc.expected, actual, protocmp.Transform()))
90+
})
91+
}
5192
}

pkg/envoy/eds/response.go

-5
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,6 @@ func fulfillEDSRequest(meshCatalog catalog.MeshCataloger, proxy *envoy.Proxy, re
5050
continue
5151
}
5252
endpoints := meshCatalog.ListAllowedUpstreamEndpointsForService(proxyIdentity, meshSvc)
53-
if len(endpoints) == 0 {
54-
log.Error().Str(errcode.Kind, errcode.GetErrCodeWithMetric(errcode.ErrEndpointsNotFound)).
55-
Msgf("Endpoints not found for upstream cluster %s for proxy identity %s, skipping cluster in EDS response", cluster, proxyIdentity)
56-
continue
57-
}
5853
log.Trace().Msgf("Endpoints for upstream cluster %s for downstream proxy identity %s: %v", cluster, proxyIdentity, endpoints)
5954
loadAssignment := newClusterLoadAssignment(meshSvc, endpoints)
6055
rdsResources = append(rdsResources, loadAssignment)

0 commit comments

Comments
 (0)