Skip to content

Commit 4e16336

Browse files
authored
util:create a test for MultiChildLoadBalancer and Endpoint (grpc#10771)
* Add a test for MultiChildLB doing some basic checking and using multiple addresses for an eag * Add tests for Endpoint
1 parent 4b2e5ed commit 4e16336

File tree

2 files changed

+402
-0
lines changed

2 files changed

+402
-0
lines changed
Lines changed: 387 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,387 @@
1+
/*
2+
* Copyright 2023 The gRPC Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.grpc.util;
18+
19+
import static com.google.common.truth.Truth.assertThat;
20+
import static io.grpc.ConnectivityState.CONNECTING;
21+
import static io.grpc.ConnectivityState.READY;
22+
import static io.grpc.ConnectivityState.SHUTDOWN;
23+
import static org.junit.Assert.assertEquals;
24+
import static org.junit.Assert.assertNotEquals;
25+
import static org.junit.Assert.assertTrue;
26+
import static org.junit.Assert.fail;
27+
import static org.mockito.AdditionalAnswers.delegatesTo;
28+
import static org.mockito.ArgumentMatchers.any;
29+
import static org.mockito.ArgumentMatchers.eq;
30+
import static org.mockito.Mockito.inOrder;
31+
import static org.mockito.Mockito.mock;
32+
import static org.mockito.Mockito.never;
33+
import static org.mockito.Mockito.times;
34+
import static org.mockito.Mockito.verify;
35+
import static org.mockito.Mockito.verifyNoMoreInteractions;
36+
37+
import com.google.common.collect.Lists;
38+
import io.grpc.Attributes;
39+
import io.grpc.ConnectivityState;
40+
import io.grpc.ConnectivityStateInfo;
41+
import io.grpc.EquivalentAddressGroup;
42+
import io.grpc.LoadBalancer;
43+
import io.grpc.Status;
44+
import io.grpc.util.AbstractTestHelper.FakeSocketAddress;
45+
import io.grpc.util.MultiChildLoadBalancer.ChildLbState;
46+
import io.grpc.util.MultiChildLoadBalancer.Endpoint;
47+
import java.net.SocketAddress;
48+
import java.util.ArrayList;
49+
import java.util.Arrays;
50+
import java.util.Collections;
51+
import java.util.HashMap;
52+
import java.util.List;
53+
import java.util.Map;
54+
import java.util.concurrent.ConcurrentHashMap;
55+
import java.util.stream.Collectors;
56+
import org.junit.Before;
57+
import org.junit.Rule;
58+
import org.junit.Test;
59+
import org.junit.runner.RunWith;
60+
import org.junit.runners.JUnit4;
61+
import org.mockito.ArgumentCaptor;
62+
import org.mockito.Captor;
63+
import org.mockito.InOrder;
64+
import org.mockito.junit.MockitoJUnit;
65+
import org.mockito.junit.MockitoRule;
66+
67+
@RunWith(JUnit4.class)
68+
public class MultiChildLoadBalancerTest {
69+
private static final Attributes.Key<String> FOO = Attributes.Key.create("foo");
70+
71+
@Rule
72+
public final MockitoRule mocks = MockitoJUnit.rule();
73+
74+
private final List<EquivalentAddressGroup> servers = Lists.newArrayList();
75+
private final Map<List<EquivalentAddressGroup>, LoadBalancer.Subchannel> subchannels =
76+
new ConcurrentHashMap<>();
77+
private final Attributes affinity = Attributes.newBuilder().set(FOO, "bar").build();
78+
@Captor
79+
private ArgumentCaptor<LoadBalancer.SubchannelPicker> pickerCaptor;
80+
@Captor
81+
private ArgumentCaptor<ConnectivityState> stateCaptor;
82+
@Captor
83+
private ArgumentCaptor<LoadBalancer.CreateSubchannelArgs> createArgsCaptor;
84+
private TestHelper testHelperInst = new TestHelper();
85+
private LoadBalancer.Helper mockHelper =
86+
mock(LoadBalancer.Helper.class, delegatesTo(testHelperInst));
87+
private TestLb loadBalancer;
88+
89+
90+
@Before
91+
public void setUp() {
92+
for (int i = 0; i < 3; i++) {
93+
SocketAddress addr = new FakeSocketAddress("server" + i);
94+
EquivalentAddressGroup eag = new EquivalentAddressGroup(addr);
95+
servers.add(eag);
96+
}
97+
98+
loadBalancer = new TestLb(mockHelper);
99+
}
100+
101+
@Test
102+
public void pickAfterResolved() throws Exception {
103+
Status addressesAcceptanceStatus = loadBalancer.acceptResolvedAddresses(
104+
LoadBalancer.ResolvedAddresses.newBuilder().setAddresses(servers).build());
105+
assertThat(addressesAcceptanceStatus.isOk()).isTrue();
106+
final LoadBalancer.Subchannel readySubchannel = subchannels.values().iterator().next();
107+
deliverSubchannelState(readySubchannel, ConnectivityStateInfo.forNonError(READY));
108+
109+
verify(mockHelper, times(3)).createSubchannel(createArgsCaptor.capture());
110+
List<List<EquivalentAddressGroup>> capturedAddrs = new ArrayList<>();
111+
for (LoadBalancer.CreateSubchannelArgs arg : createArgsCaptor.getAllValues()) {
112+
capturedAddrs.add(arg.getAddresses());
113+
}
114+
115+
assertThat(capturedAddrs).containsAtLeastElementsIn(subchannels.keySet());
116+
for (LoadBalancer.Subchannel subchannel : subchannels.values()) {
117+
verify(subchannel).requestConnection();
118+
verify(subchannel, never()).shutdown();
119+
}
120+
121+
verify(mockHelper, times(2))
122+
.updateBalancingState(stateCaptor.capture(), pickerCaptor.capture());
123+
124+
assertEquals(CONNECTING, stateCaptor.getAllValues().get(0));
125+
assertEquals(READY, stateCaptor.getAllValues().get(1));
126+
TestLb.TestSubchannelPicker subchannelPicker =
127+
(TestLb.TestSubchannelPicker) pickerCaptor.getValue();
128+
assertThat(subchannelPicker.getReadySubchannels()).containsExactly(readySubchannel);
129+
130+
verifyNoMoreInteractions(mockHelper);
131+
}
132+
133+
@Test
134+
public void pickAfterResolvedUpdatedHosts() throws Exception {
135+
Attributes.Key<String> key = Attributes.Key.create("check-that-it-is-propagated");
136+
FakeSocketAddress removedAddr = new FakeSocketAddress("removed");
137+
EquivalentAddressGroup removedEag = new EquivalentAddressGroup(removedAddr);
138+
FakeSocketAddress oldAddr = new FakeSocketAddress("old");
139+
EquivalentAddressGroup oldEag1 = new EquivalentAddressGroup(oldAddr);
140+
EquivalentAddressGroup oldEag2 = new EquivalentAddressGroup(
141+
oldAddr, Attributes.newBuilder().set(key, "oldattr").build());
142+
FakeSocketAddress newAddr = new FakeSocketAddress("new");
143+
EquivalentAddressGroup newEag = new EquivalentAddressGroup(
144+
newAddr, Attributes.newBuilder().set(key, "newattr").build());
145+
146+
List<EquivalentAddressGroup> currentServers = Lists.newArrayList(removedEag, oldEag1);
147+
148+
InOrder inOrder = inOrder(mockHelper);
149+
150+
Status addressesAcceptanceStatus = loadBalancer.acceptResolvedAddresses(
151+
LoadBalancer.ResolvedAddresses.newBuilder().setAddresses(currentServers).build());
152+
assertThat(addressesAcceptanceStatus.isOk()).isTrue();
153+
LoadBalancer.Subchannel removedSubchannel = getSubchannel(removedEag);
154+
LoadBalancer.Subchannel oldSubchannel = getSubchannel(oldEag1);
155+
LoadBalancer.SubchannelStateListener removedListener =
156+
testHelperInst.getSubchannelStateListeners()
157+
.get(testHelperInst.getRealForMockSubChannel(removedSubchannel));
158+
159+
inOrder.verify(mockHelper).updateBalancingState(eq(CONNECTING), pickerCaptor.capture());
160+
161+
deliverSubchannelState(removedSubchannel, ConnectivityStateInfo.forNonError(READY));
162+
deliverSubchannelState(oldSubchannel, ConnectivityStateInfo.forNonError(READY));
163+
164+
inOrder.verify(mockHelper, times(2)).updateBalancingState(eq(READY), pickerCaptor.capture());
165+
LoadBalancer.SubchannelPicker picker = pickerCaptor.getValue();
166+
assertThat(getList(picker)).containsExactly(removedSubchannel, oldSubchannel);
167+
168+
verify(removedSubchannel, times(1)).requestConnection();
169+
verify(oldSubchannel, times(1)).requestConnection();
170+
171+
assertThat(getChildEags(loadBalancer)).containsExactly(removedEag, oldEag1);
172+
173+
// This time with Attributes
174+
List<EquivalentAddressGroup> latestServers = Lists.newArrayList(oldEag2, newEag);
175+
176+
addressesAcceptanceStatus = loadBalancer.acceptResolvedAddresses(
177+
LoadBalancer.ResolvedAddresses.newBuilder().setAddresses(latestServers).build());
178+
assertThat(addressesAcceptanceStatus.isOk()).isTrue();
179+
180+
LoadBalancer.Subchannel newSubchannel = getSubchannel(newEag);
181+
182+
verify(newSubchannel, times(1)).requestConnection();
183+
verify(oldSubchannel, times(1)).updateAddresses(Arrays.asList(oldEag2));
184+
verify(removedSubchannel, times(1)).shutdown();
185+
186+
removedListener.onSubchannelState(ConnectivityStateInfo.forNonError(SHUTDOWN));
187+
deliverSubchannelState(newSubchannel, ConnectivityStateInfo.forNonError(READY));
188+
189+
assertThat(getChildEags(loadBalancer)).containsExactly(oldEag2, newEag);
190+
191+
verify(mockHelper, times(3)).createSubchannel(any(LoadBalancer.CreateSubchannelArgs.class));
192+
inOrder.verify(mockHelper, times(2)).updateBalancingState(eq(READY), pickerCaptor.capture());
193+
194+
verifyNoMoreInteractions(mockHelper);
195+
}
196+
197+
@Test
198+
public void pickFromMultiAddressEags() throws Exception {
199+
List<SocketAddress> addressList1 = new ArrayList<>();
200+
List<SocketAddress> addressList2 = new ArrayList<>();
201+
for (int i = 0; i < 3; i++) {
202+
if (i % 2 == 0) {
203+
addressList1.add(new FakeSocketAddress("multi_" + i));
204+
} else {
205+
addressList2.add(new FakeSocketAddress("multi_" + i));
206+
}
207+
}
208+
209+
EquivalentAddressGroup eag1 = new EquivalentAddressGroup(addressList1, Attributes.EMPTY);
210+
EquivalentAddressGroup eag2 = new EquivalentAddressGroup(addressList2, Attributes.EMPTY);
211+
212+
List<EquivalentAddressGroup> multiGroups = Arrays.asList(eag1, eag2);
213+
214+
Status addressesAcceptanceStatus = loadBalancer.acceptResolvedAddresses(
215+
LoadBalancer.ResolvedAddresses.newBuilder().setAddresses(multiGroups).build());
216+
217+
assertTrue(addressesAcceptanceStatus.isOk());
218+
LoadBalancer.Subchannel evens = subchannels.get(Collections.singletonList(eag1));
219+
deliverSubchannelState(evens, ConnectivityStateInfo.forNonError(READY));
220+
verify(mockHelper).updateBalancingState(eq(READY), pickerCaptor.capture());
221+
assertThat(pickerCaptor.getValue()).isInstanceOf(TestLb.TestSubchannelPicker.class);
222+
assertThat(((TestLb.TestSubchannelPicker)pickerCaptor.getValue()).childPickerMap).hasSize(2);
223+
}
224+
225+
@Test
226+
public void testEndpoint_toString() {
227+
try {
228+
new Endpoint(null);
229+
fail("No exception thrown for null");
230+
} catch (NullPointerException e) {
231+
assertThat(e.getMessage()).contains("eag");
232+
}
233+
234+
// Simple eag
235+
EquivalentAddressGroup eagSimple = servers.get(0);
236+
Endpoint simple = new Endpoint(eagSimple);
237+
assertEquals(addressesOnlyString(eagSimple), simple.toString());
238+
239+
// Multiple address eag
240+
EquivalentAddressGroup eagMulti = createEag("addr1", "addr2");
241+
Endpoint multi = new Endpoint(eagMulti);
242+
assertEquals(addressesOnlyString(eagMulti), multi.toString());
243+
}
244+
245+
@Test
246+
public void testEndpoint_equals() {
247+
assertEquals(
248+
createEndpoint(Attributes.EMPTY, "addr1"),
249+
createEndpoint(Attributes.EMPTY, "addr1"));
250+
251+
assertEquals(
252+
createEndpoint(Attributes.EMPTY, "addr1", "addr2"),
253+
createEndpoint(Attributes.EMPTY, "addr2", "addr1"));
254+
255+
assertEquals(
256+
createEndpoint(Attributes.EMPTY, "addr1", "addr2"),
257+
createEndpoint(affinity, "addr2", "addr1"));
258+
259+
assertEquals(
260+
createEndpoint(Attributes.EMPTY, "addr1", "addr2").hashCode(),
261+
createEndpoint(affinity, "addr2", "addr1").hashCode());
262+
263+
}
264+
265+
@Test
266+
public void testEndpoint_notEquals() {
267+
assertNotEquals(
268+
createEndpoint(Attributes.EMPTY, "addr1", "addr2"),
269+
createEndpoint(Attributes.EMPTY, "addr1", "addr3"));
270+
271+
assertNotEquals(
272+
createEndpoint(Attributes.EMPTY, "addr1"),
273+
createEndpoint(Attributes.EMPTY, "addr1", "addr2"));
274+
275+
assertNotEquals(
276+
createEndpoint(Attributes.EMPTY, "addr1", "addr2"),
277+
createEndpoint(Attributes.EMPTY, "addr1"));
278+
}
279+
280+
private String addressesOnlyString(EquivalentAddressGroup eag) {
281+
if (eag == null) {
282+
return null;
283+
}
284+
285+
String withoutAttrs = eag.toString().replaceAll("\\/\\{\\}","");
286+
return "[" + withoutAttrs.replaceAll("[\\[\\]]", "") + "]";
287+
}
288+
289+
@SuppressWarnings("MixedMutabilityReturnType")
290+
private List<LoadBalancer.Subchannel> getList(LoadBalancer.SubchannelPicker picker) {
291+
if (picker instanceof LoadBalancer.FixedResultPicker) {
292+
LoadBalancer.Subchannel subchannel = picker.pickSubchannel(null).getSubchannel();
293+
return subchannel != null ? Collections.singletonList(subchannel) : Collections.emptyList();
294+
}
295+
if (picker instanceof TestLb.TestSubchannelPicker) {
296+
List<LoadBalancer.Subchannel> subchannelsInPicker = new ArrayList<>();
297+
for (LoadBalancer.SubchannelPicker childPicker :
298+
((TestLb.TestSubchannelPicker)picker).childPickerMap.values()) {
299+
subchannelsInPicker.add(childPicker.pickSubchannel(null).getSubchannel());
300+
}
301+
return subchannelsInPicker;
302+
}
303+
return Collections.emptyList();
304+
}
305+
306+
private EquivalentAddressGroup createEag(String... names) {
307+
List<SocketAddress> addresses = buildAddressList(names);
308+
return new EquivalentAddressGroup(addresses, Attributes.EMPTY);
309+
}
310+
311+
private static List<SocketAddress> buildAddressList(String... names) {
312+
List<SocketAddress> addresses = new ArrayList<>();
313+
for (String name : names) {
314+
addresses.add(new FakeSocketAddress(name));
315+
}
316+
return addresses;
317+
}
318+
319+
private Endpoint createEndpoint(Attributes attr, String... names) {
320+
EquivalentAddressGroup eag = new EquivalentAddressGroup(buildAddressList(names), attr);
321+
return new Endpoint(eag);
322+
}
323+
324+
private LoadBalancer.Subchannel getSubchannel(EquivalentAddressGroup removedEag) {
325+
return subchannels.get(Collections.singletonList(removedEag));
326+
}
327+
328+
private static List<Object> getChildEags(MultiChildLoadBalancer loadBalancer) {
329+
return loadBalancer.getChildLbStates().stream()
330+
.map(ChildLbState::getEag)
331+
.collect(Collectors.toList());
332+
}
333+
334+
private void deliverSubchannelState(LoadBalancer.Subchannel subchannel,
335+
ConnectivityStateInfo newState) {
336+
testHelperInst.deliverSubchannelState(subchannel, newState);
337+
}
338+
339+
private class TestLb extends MultiChildLoadBalancer {
340+
341+
protected TestLb(Helper mockHelper) {
342+
super(mockHelper);
343+
}
344+
345+
@Override
346+
protected SubchannelPicker getSubchannelPicker(Map<Object, SubchannelPicker> childPickers) {
347+
return new TestSubchannelPicker(childPickers);
348+
}
349+
350+
private class TestSubchannelPicker extends SubchannelPicker {
351+
Map<Object, SubchannelPicker> childPickerMap;
352+
Map<Object, ConnectivityState> childStates = new HashMap<>();
353+
354+
TestSubchannelPicker(Map<Object, SubchannelPicker> childPickers) {
355+
childPickerMap = childPickers;
356+
for (Object key : childPickerMap.keySet()) {
357+
childStates.put(key, getChildLbState(key).getCurrentState());
358+
}
359+
}
360+
361+
List<Subchannel> getReadySubchannels() {
362+
List<Subchannel> readySubchannels = new ArrayList<>();
363+
for ( Map.Entry<Object, ConnectivityState> cur : childStates.entrySet()) {
364+
if (cur.getValue() == READY) {
365+
Subchannel s = subchannels.get(Arrays.asList(getChildLbState(cur.getKey()).getEag()));
366+
readySubchannels.add(s);
367+
}
368+
}
369+
return readySubchannels;
370+
}
371+
372+
@Override
373+
public PickResult pickSubchannel(PickSubchannelArgs args) {
374+
return childPickerMap.values().iterator().next().pickSubchannel(args); // Always use the 1st
375+
}
376+
}
377+
}
378+
379+
private class TestHelper extends AbstractTestHelper {
380+
381+
@Override
382+
public Map<List<EquivalentAddressGroup>, LoadBalancer.Subchannel> getSubchannelMap() {
383+
return subchannels;
384+
}
385+
}
386+
387+
}

0 commit comments

Comments
 (0)