|
19 | 19 | import static com.google.common.truth.Truth.assertThat; |
20 | 20 | import static com.google.common.truth.Truth.assertWithMessage; |
21 | 21 | import static io.grpc.StatusMatcher.statusHasCode; |
| 22 | +import static io.grpc.xds.XdsEndpointResource.GRPC_EXPERIMENTAL_XDS_DUALSTACK_ENDPOINTS; |
22 | 23 | import static org.mockito.ArgumentMatchers.any; |
23 | 24 | import static org.mockito.ArgumentMatchers.argThat; |
24 | 25 | import static org.mockito.ArgumentMatchers.eq; |
|
47 | 48 | import com.google.protobuf.UInt32Value; |
48 | 49 | import com.google.protobuf.util.Durations; |
49 | 50 | import io.envoyproxy.envoy.config.cluster.v3.OutlierDetection; |
| 51 | +import io.envoyproxy.envoy.config.core.v3.Address; |
| 52 | +import io.envoyproxy.envoy.config.core.v3.SocketAddress; |
| 53 | +import io.envoyproxy.envoy.config.endpoint.v3.Endpoint; |
50 | 54 | import io.envoyproxy.envoy.config.route.v3.FilterConfig; |
51 | 55 | import io.envoyproxy.envoy.config.route.v3.WeightedCluster; |
52 | 56 | import io.envoyproxy.envoy.extensions.filters.http.router.v3.Router; |
@@ -4068,6 +4072,215 @@ public void edsResourceUpdated() { |
4068 | 4072 | verifySubscribedResourcesMetadataSizes(0, 0, 0, 1); |
4069 | 4073 | } |
4070 | 4074 |
|
| 4075 | + @Test |
| 4076 | + public void edsDuplicateEndpointAcrossLocalities_retainsLastGoodResource() { |
| 4077 | + DiscoveryRpcCall call = startResourceWatcher(XdsEndpointResource.getInstance(), EDS_RESOURCE, |
| 4078 | + edsResourceWatcher); |
| 4079 | + call.sendResponse(EDS, testClusterLoadAssignment, VERSION_1, "0000"); |
| 4080 | + call.verifyRequest(EDS, EDS_RESOURCE, VERSION_1, "0000", NODE); |
| 4081 | + verify(edsResourceWatcher).onResourceChanged(edsUpdateCaptor.capture()); |
| 4082 | + EdsUpdate lastGoodUpdate = edsUpdateCaptor.getValue().getValue(); |
| 4083 | + |
| 4084 | + Any invalidAssignment = Any.pack(mf.buildClusterLoadAssignment(EDS_RESOURCE, |
| 4085 | + ImmutableList.of( |
| 4086 | + mf.buildLocalityLbEndpoints("region1", "zone1", "subzone1", |
| 4087 | + mf.buildLbEndpoint("172.44.2.2", 8000, "healthy", 1, "host1"), 1, 0), |
| 4088 | + mf.buildLocalityLbEndpoints("region2", "zone2", "subzone2", |
| 4089 | + mf.buildLbEndpoint("172.44.2.2", 8000, "unhealthy", 2, "host2"), 1, 0)), |
| 4090 | + ImmutableList.of())); |
| 4091 | + List<String> errors = ImmutableList.of("EDS response ClusterLoadAssignment '" + EDS_RESOURCE |
| 4092 | + + "' validation error: ClusterLoadAssignment has duplicate endpoint address: " |
| 4093 | + + "/172.44.2.2:8000"); |
| 4094 | + call.sendResponse(EDS, invalidAssignment, VERSION_2, "0001"); |
| 4095 | + call.verifyRequestNack(EDS, EDS_RESOURCE, VERSION_1, "0001", NODE, errors); |
| 4096 | + verifyResourceMetadataNacked(EDS, EDS_RESOURCE, testClusterLoadAssignment, VERSION_1, |
| 4097 | + TIME_INCREMENT, VERSION_2, TIME_INCREMENT * 2, errors, true); |
| 4098 | + ArgumentCaptor<Status> statusCaptor = ArgumentCaptor.forClass(Status.class); |
| 4099 | + verify(edsResourceWatcher).onAmbientError(statusCaptor.capture()); |
| 4100 | + assertThat(statusCaptor.getValue().getCode()).isEqualTo(Code.UNAVAILABLE); |
| 4101 | + assertThat(statusCaptor.getValue().getDescription()).contains(errors.get(0)); |
| 4102 | + verifyNoMoreInteractions(edsResourceWatcher); |
| 4103 | + |
| 4104 | + // A new watcher still receives the last accepted resource. |
| 4105 | + @SuppressWarnings("unchecked") |
| 4106 | + ResourceWatcher<EdsUpdate> watcher = mock(ResourceWatcher.class); |
| 4107 | + xdsClient.watchXdsResource(XdsEndpointResource.getInstance(), EDS_RESOURCE, watcher); |
| 4108 | + verify(watcher).onResourceChanged(StatusOr.fromValue(lastGoodUpdate)); |
| 4109 | + verify(watcher).onAmbientError(statusCaptor.getValue()); |
| 4110 | + |
| 4111 | + call.sendResponse(EDS, invalidAssignment, VERSION_3, "0002"); |
| 4112 | + call.verifyRequestNack(EDS, EDS_RESOURCE, VERSION_1, "0002", NODE, errors); |
| 4113 | + verifyResourceMetadataNacked(EDS, EDS_RESOURCE, testClusterLoadAssignment, VERSION_1, |
| 4114 | + TIME_INCREMENT, VERSION_3, TIME_INCREMENT * 3, errors, true); |
| 4115 | + verify(edsResourceWatcher, times(2)).onAmbientError(any()); |
| 4116 | + verify(watcher, times(2)).onAmbientError(any()); |
| 4117 | + verifyNoMoreInteractions(edsResourceWatcher, watcher); |
| 4118 | + |
| 4119 | + Any validAssignment = Any.pack(mf.buildClusterLoadAssignment(EDS_RESOURCE, |
| 4120 | + ImmutableList.of(mf.buildLocalityLbEndpoints("region2", "zone2", "subzone2", |
| 4121 | + mf.buildLbEndpoint("172.44.2.2", 8001, "healthy", 1, "host2"), 1, 0)), |
| 4122 | + ImmutableList.of())); |
| 4123 | + call.sendResponse(EDS, validAssignment, "45", "0003"); |
| 4124 | + call.verifyRequest(EDS, EDS_RESOURCE, "45", "0003", NODE); |
| 4125 | + verifyResourceMetadataAcked(EDS, EDS_RESOURCE, validAssignment, "45", TIME_INCREMENT * 4); |
| 4126 | + verify(edsResourceWatcher, times(2)).onResourceChanged(edsUpdateCaptor.capture()); |
| 4127 | + assertThat(edsUpdateCaptor.getValue().getValue()).isNotEqualTo(lastGoodUpdate); |
| 4128 | + verify(watcher).onResourceChanged(edsUpdateCaptor.getValue()); |
| 4129 | + } |
| 4130 | + |
| 4131 | + @Test |
| 4132 | + public void edsDuplicateEndpointOnColdStart() { |
| 4133 | + DiscoveryRpcCall call = startResourceWatcher(XdsEndpointResource.getInstance(), EDS_RESOURCE, |
| 4134 | + edsResourceWatcher); |
| 4135 | + Any invalidAssignment = Any.pack(mf.buildClusterLoadAssignment(EDS_RESOURCE, |
| 4136 | + ImmutableList.of( |
| 4137 | + mf.buildLocalityLbEndpoints("region1", "zone1", "subzone1", |
| 4138 | + mf.buildLbEndpoint("172.44.2.2", 8000, "healthy", 1, "host1"), 1, 0), |
| 4139 | + mf.buildLocalityLbEndpoints("region2", "zone2", "subzone2", |
| 4140 | + mf.buildLbEndpoint("172.44.2.2", 8000, "healthy", 1, "host2"), 1, 0)), |
| 4141 | + ImmutableList.of())); |
| 4142 | + call.sendResponse(EDS, invalidAssignment, VERSION_1, "0000"); |
| 4143 | + call.verifyRequestNack(EDS, EDS_RESOURCE, "", "0000", NODE, |
| 4144 | + ImmutableList.of("EDS response ClusterLoadAssignment '" + EDS_RESOURCE |
| 4145 | + + "' validation error: ClusterLoadAssignment has duplicate endpoint address:")); |
| 4146 | + verify(edsResourceWatcher).onResourceChanged(edsUpdateCaptor.capture()); |
| 4147 | + assertThat(edsUpdateCaptor.getValue().hasValue()).isFalse(); |
| 4148 | + |
| 4149 | + call.sendResponse(EDS, testClusterLoadAssignment, VERSION_2, "0001"); |
| 4150 | + call.verifyRequest(EDS, EDS_RESOURCE, VERSION_2, "0001", NODE); |
| 4151 | + verify(edsResourceWatcher, times(2)).onResourceChanged(edsUpdateCaptor.capture()); |
| 4152 | + assertThat(edsUpdateCaptor.getValue().hasValue()).isTrue(); |
| 4153 | + validateGoldenClusterLoadAssignment(edsUpdateCaptor.getValue().getValue()); |
| 4154 | + } |
| 4155 | + |
| 4156 | + @Test |
| 4157 | + public void edsDuplicateEndpointWithinLocality() { |
| 4158 | + verifyEdsDuplicateEndpoint(ImmutableList.of( |
| 4159 | + mf.buildLocalityLbEndpoints("region1", "zone1", "subzone1", |
| 4160 | + ImmutableList.of( |
| 4161 | + mf.buildLbEndpoint("172.44.2.2", 8000, "healthy", 1, "host1"), |
| 4162 | + mf.buildLbEndpoint("172.44.2.2", 8000, "healthy", 1, "host2")), 1, 0))); |
| 4163 | + } |
| 4164 | + |
| 4165 | + @Test |
| 4166 | + public void edsDuplicateEndpointAcrossPriorities() { |
| 4167 | + verifyEdsDuplicateEndpoint(ImmutableList.of( |
| 4168 | + mf.buildLocalityLbEndpoints("region1", "zone1", "subzone1", |
| 4169 | + mf.buildLbEndpoint("172.44.2.2", 8000, "healthy", 1, "host1"), 1, 0), |
| 4170 | + mf.buildLocalityLbEndpoints("region2", "zone2", "subzone2", |
| 4171 | + mf.buildLbEndpoint("172.44.2.2", 8000, "healthy", 1, "host2"), 1, 1))); |
| 4172 | + } |
| 4173 | + |
| 4174 | + @Test |
| 4175 | + public void edsDuplicateEndpointInOverwrittenLocality() { |
| 4176 | + // The second entry overwrites the first in the locality-keyed output map. |
| 4177 | + verifyEdsDuplicateEndpoint(ImmutableList.of( |
| 4178 | + mf.buildLocalityLbEndpoints("region1", "zone1", "subzone1", |
| 4179 | + mf.buildLbEndpoint("172.44.2.2", 8000, "healthy", 1, "host1"), 1, 0), |
| 4180 | + mf.buildLocalityLbEndpoints("region1", "zone1", "subzone1", |
| 4181 | + mf.buildLbEndpoint("172.44.2.3", 8000, "healthy", 1, "host2"), 1, 1), |
| 4182 | + mf.buildLocalityLbEndpoints("region2", "zone2", "subzone2", |
| 4183 | + mf.buildLbEndpoint("172.44.2.2", 8000, "healthy", 1, "host3"), 1, 0))); |
| 4184 | + } |
| 4185 | + |
| 4186 | + @Test |
| 4187 | + public void edsDuplicateEndpointWithEquivalentIpv6Addresses() { |
| 4188 | + verifyEdsDuplicateEndpoint(ImmutableList.of( |
| 4189 | + mf.buildLocalityLbEndpoints("region1", "zone1", "subzone1", |
| 4190 | + mf.buildLbEndpoint("2001:db8::1", 8000, "healthy", 1, "host1"), 1, 0), |
| 4191 | + mf.buildLocalityLbEndpoints("region2", "zone2", "subzone2", |
| 4192 | + mf.buildLbEndpoint("2001:0db8:0:0:0:0:0:1", 8000, "healthy", 1, "host2"), 1, 0))); |
| 4193 | + } |
| 4194 | + |
| 4195 | + @Test |
| 4196 | + public void edsDuplicateEndpointInAdditionalAddresses() { |
| 4197 | + String originalDualStackProp = |
| 4198 | + System.setProperty(GRPC_EXPERIMENTAL_XDS_DUALSTACK_ENDPOINTS, "false"); |
| 4199 | + try { |
| 4200 | + io.envoyproxy.envoy.config.endpoint.v3.LbEndpoint endpoint = |
| 4201 | + (io.envoyproxy.envoy.config.endpoint.v3.LbEndpoint) |
| 4202 | + mf.buildLbEndpoint("172.44.2.2", 8000, "healthy", 1, "host1"); |
| 4203 | + Message dualStackEndpoint = endpoint.toBuilder() |
| 4204 | + .setEndpoint(endpoint.getEndpoint().toBuilder() |
| 4205 | + .addAdditionalAddresses(Endpoint.AdditionalAddress.newBuilder() |
| 4206 | + .setAddress(Address.newBuilder().setSocketAddress(SocketAddress.newBuilder() |
| 4207 | + .setAddress("2001:db8::1").setPortValue(8000))))) |
| 4208 | + .build(); |
| 4209 | + Any assignment = Any.pack(mf.buildClusterLoadAssignment(EDS_RESOURCE, |
| 4210 | + ImmutableList.of( |
| 4211 | + mf.buildLocalityLbEndpoints("region1", "zone1", "subzone1", |
| 4212 | + dualStackEndpoint, 1, 0), |
| 4213 | + mf.buildLocalityLbEndpoints("region2", "zone2", "subzone2", |
| 4214 | + mf.buildLbEndpoint("2001:db8::1", 8000, "healthy", 1, "host2"), 1, 0)), |
| 4215 | + ImmutableList.of())); |
| 4216 | + DiscoveryRpcCall call = startResourceWatcher(XdsEndpointResource.getInstance(), EDS_RESOURCE, |
| 4217 | + edsResourceWatcher); |
| 4218 | + call.sendResponse(EDS, assignment, VERSION_1, "0000"); |
| 4219 | + call.verifyRequest(EDS, EDS_RESOURCE, VERSION_1, "0000", NODE); |
| 4220 | + |
| 4221 | + System.setProperty(GRPC_EXPERIMENTAL_XDS_DUALSTACK_ENDPOINTS, "true"); |
| 4222 | + call.sendResponse(EDS, assignment, VERSION_2, "0001"); |
| 4223 | + List<String> errors = ImmutableList.of("EDS response ClusterLoadAssignment '" + EDS_RESOURCE |
| 4224 | + + "' validation error: ClusterLoadAssignment has duplicate endpoint address:"); |
| 4225 | + call.verifyRequestNack(EDS, EDS_RESOURCE, VERSION_1, "0001", NODE, errors); |
| 4226 | + verifyResourceMetadataNacked(EDS, EDS_RESOURCE, assignment, VERSION_1, TIME_INCREMENT, |
| 4227 | + VERSION_2, TIME_INCREMENT * 2, errors, true); |
| 4228 | + verify(edsResourceWatcher).onResourceChanged(edsUpdateCaptor.capture()); |
| 4229 | + assertThat(edsUpdateCaptor.getValue().hasValue()).isTrue(); |
| 4230 | + } finally { |
| 4231 | + if (originalDualStackProp == null) { |
| 4232 | + System.clearProperty(GRPC_EXPERIMENTAL_XDS_DUALSTACK_ENDPOINTS); |
| 4233 | + } else { |
| 4234 | + System.setProperty(GRPC_EXPERIMENTAL_XDS_DUALSTACK_ENDPOINTS, originalDualStackProp); |
| 4235 | + } |
| 4236 | + } |
| 4237 | + } |
| 4238 | + |
| 4239 | + private void verifyEdsDuplicateEndpoint(List<Message> localities) { |
| 4240 | + DiscoveryRpcCall call = startResourceWatcher(XdsEndpointResource.getInstance(), EDS_RESOURCE, |
| 4241 | + edsResourceWatcher); |
| 4242 | + Any assignment = Any.pack(mf.buildClusterLoadAssignment(EDS_RESOURCE, localities, |
| 4243 | + ImmutableList.of())); |
| 4244 | + call.sendResponse(EDS, assignment, VERSION_1, "0000"); |
| 4245 | + call.verifyRequestNack(EDS, EDS_RESOURCE, "", "0000", NODE, |
| 4246 | + ImmutableList.of("EDS response ClusterLoadAssignment '" + EDS_RESOURCE |
| 4247 | + + "' validation error: ClusterLoadAssignment has duplicate endpoint address:")); |
| 4248 | + verify(edsResourceWatcher).onResourceChanged(edsUpdateCaptor.capture()); |
| 4249 | + assertThat(edsUpdateCaptor.getValue().hasValue()).isFalse(); |
| 4250 | + } |
| 4251 | + |
| 4252 | + @Test |
| 4253 | + public void edsEndpointsWithSameIpAndDifferentPorts() { |
| 4254 | + DiscoveryRpcCall call = startResourceWatcher(XdsEndpointResource.getInstance(), EDS_RESOURCE, |
| 4255 | + edsResourceWatcher); |
| 4256 | + Any assignment = Any.pack(mf.buildClusterLoadAssignment(EDS_RESOURCE, |
| 4257 | + ImmutableList.of( |
| 4258 | + mf.buildLocalityLbEndpoints("region1", "zone1", "subzone1", |
| 4259 | + mf.buildLbEndpoint("172.44.2.2", 8000, "healthy", 1, "host1"), 1, 0), |
| 4260 | + mf.buildLocalityLbEndpoints("region2", "zone2", "subzone2", |
| 4261 | + mf.buildLbEndpoint("172.44.2.2", 8001, "healthy", 1, "host2"), 1, 0)), |
| 4262 | + ImmutableList.of())); |
| 4263 | + call.sendResponse(EDS, assignment, VERSION_1, "0000"); |
| 4264 | + call.verifyRequest(EDS, EDS_RESOURCE, VERSION_1, "0000", NODE); |
| 4265 | + verify(edsResourceWatcher).onResourceChanged(edsUpdateCaptor.capture()); |
| 4266 | + assertThat(edsUpdateCaptor.getValue().getValue().localityLbEndpointsMap).hasSize(2); |
| 4267 | + } |
| 4268 | + |
| 4269 | + @Test |
| 4270 | + public void edsDuplicateEndpointInZeroWeightLocalityIgnored() { |
| 4271 | + DiscoveryRpcCall call = startResourceWatcher(XdsEndpointResource.getInstance(), EDS_RESOURCE, |
| 4272 | + edsResourceWatcher); |
| 4273 | + Any assignment = Any.pack(mf.buildClusterLoadAssignment(EDS_RESOURCE, |
| 4274 | + ImmutableList.of(lbEndpointHealthy, |
| 4275 | + mf.buildLocalityLbEndpoints("region2", "zone2", "subzone2", |
| 4276 | + mf.buildLbEndpoint("192.168.0.1", 8080, "healthy", 1, "host2"), 0, 0)), |
| 4277 | + ImmutableList.of())); |
| 4278 | + call.sendResponse(EDS, assignment, VERSION_1, "0000"); |
| 4279 | + call.verifyRequest(EDS, EDS_RESOURCE, VERSION_1, "0000", NODE); |
| 4280 | + verify(edsResourceWatcher).onResourceChanged(edsUpdateCaptor.capture()); |
| 4281 | + assertThat(edsUpdateCaptor.getValue().getValue().localityLbEndpointsMap).hasSize(1); |
| 4282 | + } |
| 4283 | + |
4071 | 4284 | @Test |
4072 | 4285 | public void edsDuplicateLocalityInTheSamePriority() { |
4073 | 4286 | DiscoveryRpcCall call = startResourceWatcher(XdsEndpointResource.getInstance(), EDS_RESOURCE, |
|
0 commit comments