-
Notifications
You must be signed in to change notification settings - Fork 38
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add an option to enable load balancing between replicas (#535)
* in progress shuffle clients * first draft load balancing, need tests * WIP logic for client shuffling - unsure how to incorporate priority * WIP * shuffle all clients together, fix logic for order of clients used * WIP * WIP store shuffle order for combined list * WIP shuffle logic * WIP new design * clean up logic/leftover code * move tests, check if dynamic clients are available in getclients * remove unused code * fix syntax issues, extend test * fix logic to increment client index * add clarifying comment * remove tests for now * WIP tests * add some tests, will add more * add to last test * remove unused usings * add extra verify statement to check client isnt used * edit logic to treat passed in clients as highest priority * PR comment revisions * check for more than one client in load balancing logic * set clients equal to new copied list before finding next available client * remove convert list to clients
- Loading branch information
1 parent
2745270
commit e74a679
Showing
6 changed files
with
220 additions
and
10 deletions.
There are no files selected for viewing
This file contains 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 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 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 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 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 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)); | ||
} | ||
} | ||
} |