Skip to content

Commit d83f49c

Browse files
author
Alexander Zubakov
committed
Tests to expose UpdateChannelState problem
1 parent 64e87a6 commit d83f49c

File tree

1 file changed

+292
-0
lines changed

1 file changed

+292
-0
lines changed
Lines changed: 292 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,292 @@
1+
#if SUPPORT_LOAD_BALANCING
2+
3+
using System.Net;
4+
using Grpc.Core;
5+
using Grpc.Net.Client.Balancer;
6+
using Grpc.Net.Client.Balancer.Internal;
7+
using Grpc.Tests.Shared;
8+
using Microsoft.Extensions.Logging;
9+
using Microsoft.Extensions.Logging.Abstractions;
10+
using Xunit;
11+
12+
namespace Grpc.Net.Client.Tests.Infrastructure.Balancer;
13+
14+
public class SubchannelsLoadBalancerTests
15+
{
16+
[Fact]
17+
public void UpdateChannelState_Should_Update_channel_state_with_different_attributes_only()
18+
{
19+
/* Arrange */
20+
21+
const string host1 = "127.0.0.1";
22+
const string host2 = "127.0.0.2";
23+
const int port = 80;
24+
25+
const string attributeKey = "key1";
26+
27+
var controller = new CustomChannelControlHelper();
28+
var balancer = new CustomBalancer(controller, NullLoggerFactory.Instance);
29+
30+
// create 2 addresses with some attributes
31+
var address1 = new BalancerAddress(host1, port);
32+
address1.Attributes.TryAdd(attributeKey, 20); // <-- difference
33+
34+
var address2 = new BalancerAddress(host2, port);
35+
address2.Attributes.TryAdd(attributeKey, 80); // <-- difference
36+
37+
var state1 = new ChannelState(
38+
status: new Status(),
39+
addresses: [address1, address2],
40+
loadBalancingConfig: null,
41+
attributes: new BalancerAttributes());
42+
43+
// create 2 addresses with the same hosts and ports as previous but with other attribute values
44+
var address3 = new BalancerAddress(host1, port);
45+
address3.Attributes.TryAdd(attributeKey, 40); // <-- difference
46+
47+
var address4 = new BalancerAddress(host2, port);
48+
address4.Attributes.TryAdd(attributeKey, 60); // <-- difference
49+
50+
var state2 = new ChannelState(
51+
status: new Status(),
52+
addresses: [address3, address4],
53+
loadBalancingConfig: null,
54+
attributes: new BalancerAttributes());
55+
56+
/* Act */
57+
58+
// first update with `address1` and `address2`
59+
balancer.UpdateChannelState(state1);
60+
61+
// remember count of `IChannelControlHelper.UpdateState()` calls
62+
var updateStateCallsCount1 = controller.UpdateStateCallsCount;
63+
64+
// second update with `address3` and `address4`
65+
// which differs from `address1` and `address2` _only_ in attributes values
66+
balancer.UpdateChannelState(state2);
67+
68+
// get count of `IChannelControlHelper.UpdateState()` calls after second update
69+
var updateStateCallsCount2 = controller.UpdateStateCallsCount;
70+
71+
Assert.True(
72+
updateStateCallsCount2 > updateStateCallsCount1,
73+
"`IChannelControlHelper.UpdateState()` was not called from `SubchannelsLoadBalancer.UpdateChannelState()`");
74+
}
75+
76+
[Fact]
77+
public void UpdateChannelState_Should_Update_channel_state_with_shorter_address_list()
78+
{
79+
/* Arrange */
80+
81+
const string host1 = "127.0.0.1";
82+
const string host2 = "127.0.0.2";
83+
const int port = 80;
84+
85+
const string attributeKey = "key1";
86+
87+
var controller = new CustomChannelControlHelper();
88+
var balancer = new CustomBalancer(controller, NullLoggerFactory.Instance);
89+
90+
// create 2 addresses with some attributes
91+
var address1 = new BalancerAddress(host1, port);
92+
address1.Attributes.TryAdd(attributeKey, 20); // <-- difference
93+
94+
var address2 = new BalancerAddress(host2, port);
95+
address2.Attributes.TryAdd(attributeKey, 80); // <-- difference
96+
97+
var state1 = new ChannelState(
98+
status: new Status(),
99+
addresses: [address1, address2],
100+
loadBalancingConfig: null,
101+
attributes: new BalancerAttributes());
102+
103+
// create 1 address with the same host and port as one of the previous addresses but with other attribute value
104+
var address3 = new BalancerAddress(host1, port);
105+
address3.Attributes.TryAdd(attributeKey, 40); // <-- difference
106+
107+
var state2 = new ChannelState(
108+
status: new Status(),
109+
addresses: [address3],
110+
loadBalancingConfig: null,
111+
attributes: new BalancerAttributes());
112+
113+
/* Act */
114+
115+
// first update with `address1` and `address2`
116+
balancer.UpdateChannelState(state1);
117+
118+
// remember count of `IChannelControlHelper.UpdateState()` calls
119+
var updateStateCallsCount1 = controller.UpdateStateCallsCount;
120+
121+
// second update with `address3` and `address4`
122+
// which differs from `address1` and `address2` _only_ in attributes values
123+
balancer.UpdateChannelState(state2);
124+
125+
// get count of `IChannelControlHelper.UpdateState()` calls after second update
126+
var updateStateCallsCount2 = controller.UpdateStateCallsCount;
127+
128+
Assert.True(
129+
updateStateCallsCount2 > updateStateCallsCount1,
130+
"`IChannelControlHelper.UpdateState()` was not called from `SubchannelsLoadBalancer.UpdateChannelState()`");
131+
}
132+
133+
[Fact]
134+
public void UpdateChannelState_Should_Update_channel_state_with_longer_address_list()
135+
{
136+
/* Arrange */
137+
138+
const string host1 = "127.0.0.1";
139+
const string host2 = "127.0.0.2";
140+
const int port = 80;
141+
142+
const string attributeKey = "key1";
143+
const string attributeKey2 = "key2";
144+
145+
var controller = new CustomChannelControlHelper();
146+
var balancer = new CustomBalancer(controller, NullLoggerFactory.Instance);
147+
148+
// create 2 addresses with some attributes
149+
var address1 = new BalancerAddress(host1, port);
150+
address1.Attributes.TryAdd(attributeKey, 20); // <-- difference
151+
152+
var address2 = new BalancerAddress(host2, port);
153+
address2.Attributes.TryAdd(attributeKey, 80); // <-- difference
154+
155+
var state1 = new ChannelState(
156+
status: new Status(),
157+
addresses: [address1, address2],
158+
loadBalancingConfig: null,
159+
attributes: new BalancerAttributes());
160+
161+
// create 2 addresses with the same hosts and ports as previous but with other attribute values
162+
var address3 = new BalancerAddress(host1, port);
163+
address3.Attributes.TryAdd(attributeKey, 40); // <-- difference
164+
165+
var address4 = new BalancerAddress(host2, port);
166+
address4.Attributes.TryAdd(attributeKey, 60); // <-- difference
167+
168+
// create one extra address with another fake attribute
169+
var address5 = new BalancerAddress(host2, port);
170+
address5.Attributes.TryAdd(attributeKey, 60);
171+
address5.Attributes.TryAdd(attributeKey2, "Fake"); // <-- difference
172+
173+
var state2 = new ChannelState(
174+
status: new Status(),
175+
addresses: [address3, address4, address5],
176+
loadBalancingConfig: null,
177+
attributes: new BalancerAttributes());
178+
179+
/* Act */
180+
181+
// first update with `address1` and `address2`
182+
balancer.UpdateChannelState(state1);
183+
184+
// remember count of `IChannelControlHelper.UpdateState()` calls
185+
var updateStateCallsCount1 = controller.UpdateStateCallsCount;
186+
187+
// second update with `address3` and `address4`
188+
// which differs from `address1` and `address2` _only_ in attributes values
189+
balancer.UpdateChannelState(state2);
190+
191+
// get count of `IChannelControlHelper.UpdateState()` calls after second update
192+
var updateStateCallsCount2 = controller.UpdateStateCallsCount;
193+
194+
Assert.True(
195+
updateStateCallsCount2 > updateStateCallsCount1,
196+
"`IChannelControlHelper.UpdateState()` was not called from `SubchannelsLoadBalancer.UpdateChannelState()`");
197+
}
198+
}
199+
200+
/* Helper chasses */
201+
file class CustomBalancer(
202+
IChannelControlHelper controller,
203+
ILoggerFactory loggerFactory)
204+
: SubchannelsLoadBalancer(controller, loggerFactory)
205+
{
206+
protected override SubchannelPicker CreatePicker(IReadOnlyList<Subchannel> readySubchannels)
207+
{
208+
return new CustomPicker(readySubchannels);
209+
}
210+
}
211+
212+
file class CustomChannelControlHelper : IChannelControlHelper
213+
{
214+
public int UpdateStateCallsCount { get; private set; }
215+
216+
public Subchannel CreateSubchannel(SubchannelOptions options)
217+
{
218+
var subchannelTransportFactory = new CustomSubchannelTransportFactory();
219+
220+
var manager = new ConnectionManager(
221+
new CustomResolver(),
222+
true,
223+
NullLoggerFactory.Instance,
224+
new CustomBackoffPolicyFactory(),
225+
subchannelTransportFactory,
226+
[]);
227+
228+
return ((IChannelControlHelper)manager).CreateSubchannel(options);
229+
}
230+
231+
public void UpdateState(BalancerState state)
232+
{
233+
UpdateStateCallsCount++;
234+
}
235+
236+
public void RefreshResolver() { }
237+
}
238+
239+
file class CustomResolver() : PollingResolver(NullLoggerFactory.Instance)
240+
{
241+
protected override Task ResolveAsync(CancellationToken cancellationToken)
242+
{
243+
return Task.CompletedTask;
244+
}
245+
}
246+
247+
file class CustomBackoffPolicyFactory : IBackoffPolicyFactory
248+
{
249+
public IBackoffPolicy Create()
250+
{
251+
return new CustomBackoffPolicy();
252+
}
253+
}
254+
255+
file class CustomBackoffPolicy : IBackoffPolicy
256+
{
257+
public TimeSpan NextBackoff()
258+
{
259+
return TimeSpan.Zero;
260+
}
261+
}
262+
263+
file class CustomSubchannelTransportFactory: ISubchannelTransportFactory
264+
{
265+
public ISubchannelTransport Create(Subchannel subchannel)
266+
{
267+
return new CustomSubchannelTransport();
268+
}
269+
}
270+
271+
file class CustomSubchannelTransport : ISubchannelTransport
272+
{
273+
public void Dispose() { }
274+
275+
public DnsEndPoint? CurrentEndPoint { get; }
276+
public TimeSpan? ConnectTimeout { get; }
277+
public TransportStatus TransportStatus { get; }
278+
279+
public ValueTask<Stream> GetStreamAsync(DnsEndPoint endPoint, CancellationToken cancellationToken)
280+
{
281+
return ValueTask.FromResult<Stream>(new MemoryStream());
282+
}
283+
284+
public ValueTask<ConnectResult> TryConnectAsync(ConnectContext context, int attempt)
285+
{
286+
return ValueTask.FromResult(ConnectResult.Success);
287+
}
288+
289+
public void Disconnect() { }
290+
}
291+
292+
#endif

0 commit comments

Comments
 (0)