Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import java.net.URL;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;
import java.util.regex.Matcher;
Expand Down Expand Up @@ -156,7 +157,8 @@ List<AppConfigurationReplicaClient> buildClients(ConfigStore configStore) {
LOGGER.debug("Connecting to " + endpoint + " using Connecting String.");
ConfigurationClientBuilder builder = createBuilderInstance().connectionString(connectionString);

clients.add(modifyAndBuildClient(builder, endpoint, configStore.getEndpoint(), connectionStrings.size() - 1));
clients.add(
modifyAndBuildClient(builder, endpoint, configStore.getEndpoint(), connectionStrings.size() - 1));
}
} else {
DefaultAzureCredential defautAzureCredential = new DefaultAzureCredentialBuilder().build();
Expand All @@ -171,6 +173,11 @@ List<AppConfigurationReplicaClient> buildClients(ConfigStore configStore) {
clients.add(modifyAndBuildClient(builder, endpoint, configStore.getEndpoint(), endpoints.size() - 1));
}
}

if (configStore.isLoadBalancingEnabled()) {
Collections.shuffle(clients);
}

return clients;
}

Expand All @@ -196,7 +203,8 @@ AppConfigurationReplicaClient buildClient(String failoverEndpoint, ConfigStore c
}
}

private AppConfigurationReplicaClient modifyAndBuildClient(ConfigurationClientBuilder builder, String endpoint, String originEndpoint,
private AppConfigurationReplicaClient modifyAndBuildClient(ConfigurationClientBuilder builder, String endpoint,
String originEndpoint,
Integer replicaCount) {
TracingInfo tracingInfo = new TracingInfo(isKeyVaultConfigured, replicaCount,
Configuration.getGlobalConfiguration());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ String getMainEndpoint() {
AppConfigurationReplicaClient getNextActiveClient(boolean useLastActive) {
if (useLastActive) {
List<AppConfigurationReplicaClient> clients = getAvailableClients();
for (AppConfigurationReplicaClient client: clients) {
for (AppConfigurationReplicaClient client : clients) {
if (client.getEndpoint().equals(lastActiveClient)) {
return client;
}
Expand All @@ -106,7 +106,7 @@ AppConfigurationReplicaClient getNextActiveClient(boolean useLastActive) {
if (activeClients.isEmpty()) {
lastActiveClient = "";
return null;
}
}

if (!configStore.isLoadBalancingEnabled()) {
if (!activeClients.isEmpty()) {
Expand All @@ -115,7 +115,8 @@ AppConfigurationReplicaClient getNextActiveClient(boolean useLastActive) {
return null;
}

// Remove the current client from the list. The list will be rebuilt and rotated on the next refresh cycle by findActiveClients().
// Remove the current client from the list. The list will be rebuilt and rotated on the next refresh cycle by
// findActiveClients().
AppConfigurationReplicaClient nextClient = activeClients.remove(0);
lastActiveClient = nextClient.getEndpoint();
return nextClient;
Expand All @@ -125,26 +126,18 @@ AppConfigurationReplicaClient getNextActiveClient(boolean useLastActive) {
* Finds the currently active clients for a given origin endpoint.
*/
void findActiveClients() {
activeClients = getAvailableClients();

if (!configStore.isLoadBalancingEnabled() || lastActiveClient.isEmpty()) {
return;
}

for (int i = 0; i < activeClients.size(); i++) {
AppConfigurationReplicaClient client = activeClients.get(i);
if (client.getEndpoint().equals(lastActiveClient)) {
int swapPoint = (i + 1) % activeClients.size();
List<AppConfigurationReplicaClient> rotatedClients = new ArrayList<>();

// Add elements from swapPoint to end
rotatedClients.addAll(activeClients.subList(swapPoint, activeClients.size()));

// Add elements from beginning to swapPoint
rotatedClients.addAll(activeClients.subList(0, swapPoint));

activeClients = rotatedClients;
return;
if (activeClients.size() == 0 && configStore.isLoadBalancingEnabled()) {
// If load balancing is enabled and there are no currently active clients, attempt to find any available
// clients
// Otherwise we continue from where we left off with the active clients list and rotate through.
activeClients = getAvailableClients();
} else if (!configStore.isLoadBalancingEnabled()) {
// If load balancing is not enabled, we want to ensure we are always using the most preferred available
// client. This means
// we check for available clients on each refresh and update the active clients list accordingly.
List<AppConfigurationReplicaClient> availableClients = getAvailableClients();
if (availableClients.size() > 0) {
activeClients = availableClients;
}
}
}
Expand Down Expand Up @@ -177,7 +170,7 @@ public List<AppConfigurationReplicaClient> getAvailableClients() {
}
}
} else if (configStore.isLoadBalancingEnabled()) {
for (AppConfigurationReplicaClient client: clients) {
for (AppConfigurationReplicaClient client : clients) {
if (client.getBackoffEndTime().isBefore(Instant.now())) {
availableClients.add(client);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -298,4 +298,76 @@ public void buildClientConnectionStringInvalid3Test() {
exception.getMessage());
}

@Test
public void buildClientsWithLoadBalancingEnabledTest() {
// Test that load balancing shuffles the order of clients
configStore = new ConfigStore();
List<String> endpoints = new ArrayList<>();

// Add multiple endpoints to shuffle
endpoints.add(TEST_ENDPOINT);
endpoints.add(TEST_ENDPOINT_GEO);
endpoints.add("https://third.test.config.io");
endpoints.add("https://fourth.test.config.io");

configStore.setEndpoints(endpoints);
configStore.setLoadBalancingEnabled(true);
configStore.validateAndInit();

clientBuilder = new AppConfigurationReplicaClientsBuilder(clientFactoryMock, null, false, false);
AppConfigurationReplicaClientsBuilder spy = Mockito.spy(clientBuilder);

ConfigurationClientBuilder builder = new ConfigurationClientBuilder();
when(builderMock.endpoint(Mockito.anyString())).thenReturn(builder);
when(builderMock.addPolicy(Mockito.any())).thenReturn(builderMock);
when(clientFactoryMock.build()).thenReturn(builderMock);

// Build clients multiple times and verify that order changes (due to shuffle)
List<AppConfigurationReplicaClient> clients1 = spy.buildClients(configStore);
List<AppConfigurationReplicaClient> clients2 = spy.buildClients(configStore);

assertEquals(4, clients1.size());
assertEquals(4, clients2.size());

// The order should potentially differ due to random shuffling
// We can't guarantee they're different, but we can verify the same endpoints exist
List<String> endpoints1 = clients1.stream().map(AppConfigurationReplicaClient::getEndpoint).toList();
List<String> endpoints2 = clients2.stream().map(AppConfigurationReplicaClient::getEndpoint).toList();

// All endpoints should be present in both lists (just potentially in different order)
assertTrue(endpoints1.containsAll(endpoints));
assertTrue(endpoints2.containsAll(endpoints));
}

@Test
public void buildClientsWithLoadBalancingDisabledTest() {
// Test that without load balancing, order is preserved
configStore = new ConfigStore();
List<String> endpoints = new ArrayList<>();

endpoints.add(TEST_ENDPOINT);
endpoints.add(TEST_ENDPOINT_GEO);
endpoints.add("https://third.test.config.io");

configStore.setEndpoints(endpoints);
configStore.setLoadBalancingEnabled(false);
configStore.validateAndInit();

clientBuilder = new AppConfigurationReplicaClientsBuilder(clientFactoryMock, null, false, false);
AppConfigurationReplicaClientsBuilder spy = Mockito.spy(clientBuilder);

ConfigurationClientBuilder builder = new ConfigurationClientBuilder();
when(builderMock.endpoint(Mockito.anyString())).thenReturn(builder);
when(builderMock.addPolicy(Mockito.any())).thenReturn(builderMock);
when(clientFactoryMock.build()).thenReturn(builderMock);

List<AppConfigurationReplicaClient> clients = spy.buildClients(configStore);

assertEquals(3, clients.size());
// When load balancing is disabled, order should match input order
assertEquals(TEST_ENDPOINT, clients.get(0).getEndpoint());
assertEquals(TEST_ENDPOINT_GEO, clients.get(1).getEndpoint());
assertEquals("https://third.test.config.io", clients.get(2).getEndpoint());
}

}