-
Notifications
You must be signed in to change notification settings - Fork 36
Add an option to enable load balancing between replicas #535
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
30 commits
Select commit
Hold shift + click to select a range
f028234
in progress shuffle clients
amerjusupovic d1e1c14
first draft load balancing, need tests
amerjusupovic dfe4a08
WIP logic for client shuffling - unsure how to incorporate priority
amerjusupovic 34c4782
WIP
amerjusupovic 9de1487
shuffle all clients together, fix logic for order of clients used
amerjusupovic c83250c
WIP
amerjusupovic 7ce975d
WIP store shuffle order for combined list
amerjusupovic 7f03daa
WIP shuffle logic
amerjusupovic 1c2c907
WIP new design
amerjusupovic 73cb6f6
clean up logic/leftover code
amerjusupovic b731453
move tests, check if dynamic clients are available in getclients
amerjusupovic 38e038a
remove unused code
amerjusupovic 83e1f6a
fix syntax issues, extend test
amerjusupovic a4cec35
Merge branch 'preview' of https://github.com/Azure/AppConfiguration-D…
amerjusupovic 6cbc9ca
fix logic to increment client index
amerjusupovic 3233438
add clarifying comment
amerjusupovic 7c10cbd
remove tests for now
amerjusupovic 1980ac7
WIP tests
amerjusupovic 6c2eda1
add some tests, will add more
amerjusupovic d4f98b4
add to last test
amerjusupovic 9112adf
remove unused usings
amerjusupovic 9efe589
add extra verify statement to check client isnt used
amerjusupovic e06e58a
edit logic to treat passed in clients as highest priority
amerjusupovic 5ba6c54
Merge branch 'preview' into ajusupovic/load-balancing
amerjusupovic 0ae5317
Merge branch 'preview' of https://github.com/Azure/AppConfiguration-D…
amerjusupovic 6cdbf58
Merge branch 'ajusupovic/load-balancing' of https://github.com/Azure/…
amerjusupovic 062cf01
PR comment revisions
amerjusupovic d36e479
check for more than one client in load balancing logic
amerjusupovic a75356a
set clients equal to new copied list before finding next available cl…
amerjusupovic 1d76867
remove convert list to clients
amerjusupovic File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
152 changes: 152 additions & 0 deletions
152
tests/Tests.AzureAppConfiguration/LoadBalancingTests.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
// Copyright (c) Microsoft Corporation. | ||
// Licensed under the MIT license. | ||
// | ||
using Azure; | ||
using Azure.Core.Testing; | ||
using Azure.Data.AppConfiguration; | ||
using Microsoft.Extensions.Configuration; | ||
using Microsoft.Extensions.Configuration.AzureAppConfiguration; | ||
using Moq; | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Threading; | ||
using Xunit; | ||
|
||
namespace Tests.AzureAppConfiguration | ||
{ | ||
public class LoadBalancingTests | ||
{ | ||
readonly ConfigurationSetting kv = ConfigurationModelFactory.ConfigurationSetting(key: "TestKey1", label: "label", value: "TestValue1", | ||
eTag: new ETag("0a76e3d7-7ec1-4e37-883c-9ea6d0d89e63"), | ||
contentType: "text"); | ||
|
||
TimeSpan CacheExpirationTime = TimeSpan.FromSeconds(1); | ||
|
||
[Fact] | ||
public void LoadBalancingTests_UsesAllEndpoints() | ||
{ | ||
IConfigurationRefresher refresher = null; | ||
var mockResponse = new MockResponse(200); | ||
|
||
var mockClient1 = new Mock<ConfigurationClient>(MockBehavior.Strict); | ||
mockClient1.Setup(c => c.GetConfigurationSettingsAsync(It.IsAny<SettingSelector>(), It.IsAny<CancellationToken>())) | ||
.Returns(new MockAsyncPageable(Enumerable.Empty<ConfigurationSetting>().ToList())); | ||
mockClient1.Setup(c => c.GetConfigurationSettingAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<CancellationToken>())) | ||
.ReturnsAsync(Response.FromValue<ConfigurationSetting>(kv, mockResponse)); | ||
mockClient1.Setup(c => c.GetConfigurationSettingAsync(It.IsAny<ConfigurationSetting>(), It.IsAny<bool>(), It.IsAny<CancellationToken>())) | ||
.ReturnsAsync(Response.FromValue(kv, mockResponse)); | ||
mockClient1.Setup(c => c.Equals(mockClient1)).Returns(true); | ||
|
||
var mockClient2 = new Mock<ConfigurationClient>(MockBehavior.Strict); | ||
mockClient2.Setup(c => c.GetConfigurationSettingsAsync(It.IsAny<SettingSelector>(), It.IsAny<CancellationToken>())) | ||
.Returns(new MockAsyncPageable(Enumerable.Empty<ConfigurationSetting>().ToList())); | ||
mockClient2.Setup(c => c.GetConfigurationSettingAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<CancellationToken>())) | ||
.ReturnsAsync(Response.FromValue<ConfigurationSetting>(kv, mockResponse)); | ||
mockClient2.Setup(c => c.GetConfigurationSettingAsync(It.IsAny<ConfigurationSetting>(), It.IsAny<bool>(), It.IsAny<CancellationToken>())) | ||
.ReturnsAsync(Response.FromValue(kv, mockResponse)); | ||
mockClient2.Setup(c => c.Equals(mockClient2)).Returns(true); | ||
|
||
ConfigurationClientWrapper cw1 = new ConfigurationClientWrapper(TestHelpers.PrimaryConfigStoreEndpoint, mockClient1.Object); | ||
ConfigurationClientWrapper cw2 = new ConfigurationClientWrapper(TestHelpers.SecondaryConfigStoreEndpoint, mockClient2.Object); | ||
|
||
var clientList = new List<ConfigurationClientWrapper>() { cw1, cw2 }; | ||
var configClientManager = new ConfigurationClientManager(clientList); | ||
|
||
var config = new ConfigurationBuilder() | ||
.AddAzureAppConfiguration(options => | ||
{ | ||
options.ClientManager = configClientManager; | ||
options.ConfigureRefresh(refreshOptions => | ||
{ | ||
refreshOptions.Register("TestKey1", "label") | ||
.SetCacheExpiration(CacheExpirationTime); | ||
}); | ||
options.ReplicaDiscoveryEnabled = false; | ||
options.LoadBalancingEnabled = true; | ||
|
||
refresher = options.GetRefresher(); | ||
}).Build(); | ||
|
||
// Ensure client 1 was used for startup | ||
mockClient1.Verify(mc => mc.GetConfigurationSettingsAsync(It.IsAny<SettingSelector>(), It.IsAny<CancellationToken>()), Times.Exactly(1)); | ||
|
||
Thread.Sleep(CacheExpirationTime); | ||
refresher.RefreshAsync().Wait(); | ||
|
||
// Ensure client 2 was used for refresh | ||
mockClient1.Verify(mc => mc.GetConfigurationSettingAsync(It.IsAny<ConfigurationSetting>(), It.IsAny<bool>(), It.IsAny<CancellationToken>()), Times.Exactly(0)); | ||
|
||
mockClient2.Verify(mc => mc.GetConfigurationSettingAsync(It.IsAny<ConfigurationSetting>(), It.IsAny<bool>(), It.IsAny<CancellationToken>()), Times.Exactly(1)); | ||
|
||
Thread.Sleep(CacheExpirationTime); | ||
refresher.RefreshAsync().Wait(); | ||
|
||
// Ensure client 1 was now used for refresh | ||
mockClient1.Verify(mc => mc.GetConfigurationSettingAsync(It.IsAny<ConfigurationSetting>(), It.IsAny<bool>(), It.IsAny<CancellationToken>()), Times.Exactly(1)); | ||
} | ||
|
||
[Fact] | ||
public void LoadBalancingTests_UsesClientAfterBackoffEnds() | ||
{ | ||
IConfigurationRefresher refresher = null; | ||
var mockResponse = new MockResponse(200); | ||
|
||
var mockClient1 = new Mock<ConfigurationClient>(MockBehavior.Strict); | ||
mockClient1.Setup(c => c.GetConfigurationSettingsAsync(It.IsAny<SettingSelector>(), It.IsAny<CancellationToken>())) | ||
.Throws(new RequestFailedException(503, "Request failed.")); | ||
mockClient1.Setup(c => c.GetConfigurationSettingAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<CancellationToken>())) | ||
.ReturnsAsync(Response.FromValue<ConfigurationSetting>(kv, mockResponse)); | ||
mockClient1.Setup(c => c.GetConfigurationSettingAsync(It.IsAny<ConfigurationSetting>(), It.IsAny<bool>(), It.IsAny<CancellationToken>())) | ||
.ReturnsAsync(Response.FromValue(kv, mockResponse)); | ||
mockClient1.Setup(c => c.Equals(mockClient1)).Returns(true); | ||
|
||
var mockClient2 = new Mock<ConfigurationClient>(MockBehavior.Strict); | ||
mockClient2.Setup(c => c.GetConfigurationSettingsAsync(It.IsAny<SettingSelector>(), It.IsAny<CancellationToken>())) | ||
.Returns(new MockAsyncPageable(Enumerable.Empty<ConfigurationSetting>().ToList())); | ||
mockClient2.Setup(c => c.GetConfigurationSettingAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<CancellationToken>())) | ||
.ReturnsAsync(Response.FromValue<ConfigurationSetting>(kv, mockResponse)); | ||
mockClient2.Setup(c => c.GetConfigurationSettingAsync(It.IsAny<ConfigurationSetting>(), It.IsAny<bool>(), It.IsAny<CancellationToken>())) | ||
.ReturnsAsync(Response.FromValue(kv, mockResponse)); | ||
mockClient2.Setup(c => c.Equals(mockClient2)).Returns(true); | ||
|
||
ConfigurationClientWrapper cw1 = new ConfigurationClientWrapper(TestHelpers.PrimaryConfigStoreEndpoint, mockClient1.Object); | ||
ConfigurationClientWrapper cw2 = new ConfigurationClientWrapper(TestHelpers.SecondaryConfigStoreEndpoint, mockClient2.Object); | ||
|
||
var clientList = new List<ConfigurationClientWrapper>() { cw1, cw2 }; | ||
var configClientManager = new ConfigurationClientManager(clientList); | ||
|
||
var config = new ConfigurationBuilder() | ||
.AddAzureAppConfiguration(options => | ||
{ | ||
options.MinBackoffDuration = TimeSpan.FromSeconds(2); | ||
options.ClientManager = configClientManager; | ||
options.ConfigureRefresh(refreshOptions => | ||
{ | ||
refreshOptions.Register("TestKey1", "label") | ||
.SetCacheExpiration(CacheExpirationTime); | ||
}); | ||
options.ReplicaDiscoveryEnabled = false; | ||
options.LoadBalancingEnabled = true; | ||
|
||
refresher = options.GetRefresher(); | ||
}).Build(); | ||
|
||
// Ensure client 2 was used for startup | ||
mockClient2.Verify(mc => mc.GetConfigurationSettingsAsync(It.IsAny<SettingSelector>(), It.IsAny<CancellationToken>()), Times.Exactly(1)); | ||
|
||
Thread.Sleep(TimeSpan.FromSeconds(2)); | ||
refresher.RefreshAsync().Wait(); | ||
|
||
// Ensure client 1 has recovered and is used for refresh | ||
mockClient2.Verify(mc => mc.GetConfigurationSettingAsync(It.IsAny<ConfigurationSetting>(), It.IsAny<bool>(), It.IsAny<CancellationToken>()), Times.Exactly(0)); | ||
|
||
mockClient1.Verify(mc => mc.GetConfigurationSettingAsync(It.IsAny<ConfigurationSetting>(), It.IsAny<bool>(), It.IsAny<CancellationToken>()), Times.Exactly(1)); | ||
|
||
Thread.Sleep(CacheExpirationTime); | ||
refresher.RefreshAsync().Wait(); | ||
|
||
mockClient2.Verify(mc => mc.GetConfigurationSettingAsync(It.IsAny<ConfigurationSetting>(), It.IsAny<bool>(), It.IsAny<CancellationToken>()), Times.Exactly(1)); | ||
} | ||
} | ||
} |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.