Skip to content

Commit

Permalink
Lettuce EntraID integration tests (#3133)
Browse files Browse the repository at this point in the history
* EntraId integration test - integrate with cae infra
 - Read test endpoint's configuration from endpoint's json
 - Invoke only EntraId related test:
  mvn  integration-test -Pentraid-it

* Load EntraIdTest
 from environment variables directly

remove dotenv dependency

* Replace whoami check with get/set to not depend on username

* Remove deprecated dnsResolver

* use mset to test default connection, and ping for individual node connections

* Add EntraId managed identity integration test
ggivo authored Jan 21, 2025
1 parent 5911303 commit ee7c46c
Showing 7 changed files with 584 additions and 120 deletions.
38 changes: 32 additions & 6 deletions pom.xml
Original file line number Diff line number Diff line change
@@ -189,12 +189,6 @@
<version>0.1.1-beta1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.github.cdimascio</groupId>
<artifactId>dotenv-java</artifactId>
<version>2.2.0</version>
<scope>test</scope>
</dependency>
<!-- Start of core dependencies -->

<dependency>
@@ -1044,6 +1038,38 @@
<profile>
<id>ci</id>
</profile>
<profile>
<id>entraid-it</id>
<build>
<plugins>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skipTests>true</skipTests>
</configuration>
</plugin>
<plugin>
<artifactId>maven-failsafe-plugin</artifactId>
<configuration>
<groups>entraid</groups>
<skipITs>false</skipITs>
<includes>
<include>**/*IntegrationTests</include>
</includes>
</configuration>
<executions>
<execution>
<phase>integration-test</phase>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>

<profile>

5 changes: 5 additions & 0 deletions src/test/java/io/lettuce/TestTags.java
Original file line number Diff line number Diff line change
@@ -29,4 +29,9 @@ public class TestTags {
*/
public static final String API_GENERATOR = "api_generator";

/**
* Tag for EntraId integration tests (require a running environment with configured microsoft EntraId authentication)
*/
public static final String ENTRA_ID = "entraid";

}
140 changes: 140 additions & 0 deletions src/test/java/io/lettuce/authx/EntraIdClusterIntegrationTests.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
package io.lettuce.authx;

import io.lettuce.core.ClientOptions;
import io.lettuce.core.RedisURI;
import io.lettuce.core.SocketOptions;
import io.lettuce.core.TimeoutOptions;
import io.lettuce.core.api.StatefulRedisConnection;
import io.lettuce.core.cluster.ClusterClientOptions;
import io.lettuce.core.cluster.ClusterTopologyRefreshOptions;
import io.lettuce.core.cluster.RedisClusterClient;
import io.lettuce.core.cluster.api.StatefulRedisClusterConnection;
import io.lettuce.core.cluster.api.sync.RedisAdvancedClusterCommands;
import io.lettuce.core.resource.ClientResources;
import io.lettuce.core.resource.DnsResolver;
import io.lettuce.test.env.Endpoints;
import io.lettuce.test.env.Endpoints.Endpoint;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import redis.clients.authentication.core.TokenAuthConfig;
import redis.clients.authentication.entraid.EntraIDTokenAuthConfigBuilder;

import java.time.Duration;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ExecutionException;

import static io.lettuce.TestTags.ENTRA_ID;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;
import static org.junit.jupiter.api.Assumptions.assumeTrue;

@Tag(ENTRA_ID)
public class EntraIdClusterIntegrationTests {

private static final EntraIdTestContext testCtx = EntraIdTestContext.DEFAULT;

private static TokenBasedRedisCredentialsProvider credentialsProvider;

private static RedisClusterClient clusterClient;

private static ClientResources resources;

private static Endpoint cluster;

@BeforeAll
public static void setup() {
cluster = Endpoints.DEFAULT.getEndpoint("cluster-entraid-acl");
if (cluster != null) {
Assumptions.assumeTrue(testCtx.getClientId() != null && testCtx.getClientSecret() != null,
"Skipping EntraID tests. Azure AD credentials not provided!");

// Configure timeout options to assure fast test failover
ClusterClientOptions clientOptions = ClusterClientOptions.builder()
.socketOptions(SocketOptions.builder().connectTimeout(Duration.ofSeconds(1)).build())
.timeoutOptions(TimeoutOptions.enabled(Duration.ofSeconds(1)))
// enable re-authentication
.reauthenticateBehavior(ClientOptions.ReauthenticateBehavior.ON_NEW_CREDENTIALS).build();

TokenAuthConfig tokenAuthConfig = EntraIDTokenAuthConfigBuilder.builder().clientId(testCtx.getClientId())
.secret(testCtx.getClientSecret()).authority(testCtx.getAuthority()).scopes(testCtx.getRedisScopes())
.expirationRefreshRatio(0.0000001F).build();

credentialsProvider = TokenBasedRedisCredentialsProvider.create(tokenAuthConfig);

resources = ClientResources.builder()
// .dnsResolver(DnsResolver.jvmDefault())
.build();

List<RedisURI> seedURI = new ArrayList<>();
for (String addr : cluster.getRawEndpoints().get(0).getAddr()) {
seedURI.add(RedisURI.builder().withAuthentication(credentialsProvider).withHost(addr)
.withPort(cluster.getRawEndpoints().get(0).getPort()).build());
}

clusterClient = RedisClusterClient.create(resources, seedURI);
clusterClient.setOptions(clientOptions);
}
}

@AfterAll
public static void cleanup() {
if (credentialsProvider != null) {
credentialsProvider.close();
}
if (resources != null) {
resources.shutdown();
}
}

// T.1.1
// Verify authentication using Azure AD with service principals using Redis Cluster Client
@Test
public void clusterWithSecret_azureServicePrincipalIntegrationTest() throws ExecutionException, InterruptedException {
assumeTrue(cluster != null, "Skipping EntraID tests. Redis host with enabled EntraId not provided!");

try (StatefulRedisClusterConnection<String, String> defaultConnection = clusterClient.connect()) {
RedisAdvancedClusterCommands<String, String> sync = defaultConnection.sync();
String keyPrefix = UUID.randomUUID().toString();
Map<String, String> mset = prepareMset(keyPrefix);

assertThat(sync.mset(mset)).isEqualTo("OK");

for (String mykey : mset.keySet()) {
assertThat(defaultConnection.sync().get(mykey)).isEqualTo("value-" + mykey);
assertThat(defaultConnection.async().get(mykey).get()).isEqualTo("value-" + mykey);
assertThat(defaultConnection.reactive().get(mykey).block()).isEqualTo("value-" + mykey);
}
assertThat(sync.del(mset.keySet().toArray(new String[0]))).isEqualTo(mset.keySet().size());

// Test connections to each node
defaultConnection.getPartitions().forEach((partition) -> {
StatefulRedisConnection<?, ?> nodeConnection = defaultConnection.getConnection(partition.getNodeId());
assertThat(nodeConnection.sync().ping()).isEqualTo("PONG");
});

defaultConnection.getPartitions().forEach((partition) -> {
StatefulRedisConnection<?, ?> nodeConnection = defaultConnection.getConnection(partition.getUri().getHost(),
partition.getUri().getPort());
assertThat(nodeConnection.sync().ping()).isEqualTo("PONG");
});
}
}

Map<String, String> prepareMset(String keyPrefix) {
Map<String, String> mset = new HashMap<>();
for (char c = 'a'; c <= 'z'; c++) {
String keySuffix = new String(new char[] { c, c, c }); // Generates "aaa", "bbb", etc.
String key = String.format("%s-{%s}", keyPrefix, keySuffix);
mset.put(key, "value-" + key);
}
return mset;
}

}
97 changes: 45 additions & 52 deletions src/test/java/io/lettuce/authx/EntraIdIntegrationTests.java
Original file line number Diff line number Diff line change
@@ -9,67 +9,69 @@
import io.lettuce.core.TransactionResult;
import io.lettuce.core.api.StatefulRedisConnection;
import io.lettuce.core.api.async.RedisAsyncCommands;
import io.lettuce.core.api.sync.RedisCommands;
import io.lettuce.core.cluster.ClusterClientOptions;
import io.lettuce.core.cluster.RedisClusterClient;
import io.lettuce.core.cluster.api.StatefulRedisClusterConnection;
import io.lettuce.core.pubsub.StatefulRedisPubSubConnection;
import io.lettuce.core.support.PubSubTestListener;
import io.lettuce.test.Wait;
import io.lettuce.test.env.Endpoints;
import io.lettuce.test.env.Endpoints.Endpoint;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import redis.clients.authentication.core.TokenAuthConfig;
import redis.clients.authentication.entraid.EntraIDTokenAuthConfigBuilder;

import java.time.Duration;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

import static io.lettuce.TestTags.ENTRA_ID;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;
import static org.junit.jupiter.api.Assumptions.assumeTrue;

@Tag(ENTRA_ID)
public class EntraIdIntegrationTests {

private static final EntraIdTestContext testCtx = EntraIdTestContext.DEFAULT;;

private static ClusterClientOptions clientOptions;
private static final EntraIdTestContext testCtx = EntraIdTestContext.DEFAULT;

private static TokenBasedRedisCredentialsProvider credentialsProvider;

private static RedisClient client;

private static RedisClusterClient clusterClient;
private static Endpoint standalone;

@BeforeAll
public static void setup() {
Assumptions.assumeTrue(testCtx.host() != null && !testCtx.host().isEmpty(),
"Skipping EntraID tests. Redis host with enabled EntraId not provided!");

// Configure timeout options to assure fast test failover
clientOptions = ClusterClientOptions.builder()
.socketOptions(SocketOptions.builder().connectTimeout(Duration.ofSeconds(1)).build())
.timeoutOptions(TimeoutOptions.enabled(Duration.ofSeconds(1)))
.reauthenticateBehavior(ClientOptions.ReauthenticateBehavior.ON_NEW_CREDENTIALS).build();

TokenAuthConfig tokenAuthConfig = EntraIDTokenAuthConfigBuilder.builder().clientId(testCtx.getClientId())
.secret(testCtx.getClientSecret()).authority(testCtx.getAuthority()).scopes(testCtx.getRedisScopes())
.expirationRefreshRatio(0.0000001F).build();

credentialsProvider = TokenBasedRedisCredentialsProvider.create(tokenAuthConfig);

RedisURI uri = RedisURI.builder().withHost(testCtx.host()).withPort(testCtx.port())
.withAuthentication(credentialsProvider).build();
standalone = Endpoints.DEFAULT.getEndpoint("standalone-entraid-acl");
if (standalone != null) {
Assumptions.assumeTrue(testCtx.getClientId() != null && testCtx.getClientSecret() != null,
"Skipping EntraID tests. Azure AD credentials not provided!");
// Configure timeout options to assure fast test failover
ClusterClientOptions clientOptions = ClusterClientOptions.builder()
.socketOptions(SocketOptions.builder().connectTimeout(Duration.ofSeconds(1)).build())
.timeoutOptions(TimeoutOptions.enabled(Duration.ofSeconds(1)))
// enable re-authentication
.reauthenticateBehavior(ClientOptions.ReauthenticateBehavior.ON_NEW_CREDENTIALS).build();

TokenAuthConfig tokenAuthConfig = EntraIDTokenAuthConfigBuilder.builder().clientId(testCtx.getClientId())
.secret(testCtx.getClientSecret()).authority(testCtx.getAuthority()).scopes(testCtx.getRedisScopes())
.expirationRefreshRatio(0.0000001F).build();

credentialsProvider = TokenBasedRedisCredentialsProvider.create(tokenAuthConfig);

RedisURI uri = RedisURI.create((standalone.getEndpoints().get(0)));
uri.setCredentialsProvider(credentialsProvider);
client = RedisClient.create(uri);
client.setOptions(clientOptions);

client = RedisClient.create(uri);
client.setOptions(clientOptions);

RedisURI clusterUri = RedisURI.builder().withHost(testCtx.clusterHost().get(0)).withPort(testCtx.clusterPort())
.withAuthentication(credentialsProvider).build();
clusterClient = RedisClusterClient.create(clusterUri);
clusterClient.setOptions(clientOptions);
}
}

@AfterAll
@@ -83,35 +85,24 @@ public static void cleanup() {
// Verify authentication using Azure AD with service principals using Redis Standalone client
@Test
public void standaloneWithSecret_azureServicePrincipalIntegrationTest() throws ExecutionException, InterruptedException {
try (StatefulRedisConnection<String, String> connection = client.connect()) {
assertThat(connection.sync().aclWhoami()).isEqualTo(testCtx.getSpOID());
assertThat(connection.async().aclWhoami().get()).isEqualTo(testCtx.getSpOID());
assertThat(connection.reactive().aclWhoami().block()).isEqualTo(testCtx.getSpOID());
}
}

// T.1.1
// Verify authentication using Azure AD with service principals using Redis Cluster Client
@Test
public void clusterWithSecret_azureServicePrincipalIntegrationTest() throws ExecutionException, InterruptedException {

try (StatefulRedisClusterConnection<String, String> connection = clusterClient.connect()) {
assertThat(connection.sync().aclWhoami()).isEqualTo(testCtx.getSpOID());
assertThat(connection.async().aclWhoami().get()).isEqualTo(testCtx.getSpOID());
assertThat(connection.reactive().aclWhoami().block()).isEqualTo(testCtx.getSpOID());
assumeTrue(standalone != null, "Skipping EntraID tests. Redis host with enabled EntraId not provided!");

connection.getPartitions().forEach((partition) -> {
try (StatefulRedisConnection<?, ?> nodeConnection = connection.getConnection(partition.getNodeId())) {
assertThat(nodeConnection.sync().aclWhoami()).isEqualTo(testCtx.getSpOID());
}
});
try (StatefulRedisConnection<String, String> connection = client.connect()) {
RedisCommands<String, String> sync = connection.sync();
String key = UUID.randomUUID().toString();
sync.set(key, "value");
assertThat(connection.sync().get(key)).isEqualTo("value");
assertThat(connection.async().get(key).get()).isEqualTo("value");
assertThat(connection.reactive().get(key).block()).isEqualTo("value");
sync.del(key);
}
}

// T.2.2
// Test that the Redis client is not blocked/interrupted during token renewal.
@Test
public void renewalDuringOperationsTest() throws InterruptedException {
assumeTrue(standalone != null, "Skipping EntraID tests. Redis host with enabled EntraId not provided!");

// Counter to track the number of command cycles
AtomicInteger commandCycleCount = new AtomicInteger(0);
@@ -162,6 +153,8 @@ public void renewalDuringOperationsTest() throws InterruptedException {
// Test basic Pub/Sub functionality is not blocked/interrupted during token renewal.
@Test
public void renewalDuringPubSubOperationsTest() throws InterruptedException {
assumeTrue(standalone != null, "Skipping EntraID tests. Redis host with enabled EntraId not provided!");

try (StatefulRedisPubSubConnection<String, String> connectionPubSub = client.connectPubSub();
StatefulRedisPubSubConnection<String, String> connectionPubSub1 = client.connectPubSub()) {

@@ -183,7 +176,7 @@ public void renewalDuringPubSubOperationsTest() throws InterruptedException {
latch.countDown();
});

assertThat(latch.await(1, TimeUnit.SECONDS)).isTrue(); // Wait for at least 10 token renewals
assertThat(latch.await(2, TimeUnit.SECONDS)).isTrue(); // Wait for at least 10 token renewals
pubsubThread.join(); // Wait for the pub/sub thread to finish

// Verify that all messages were received
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package io.lettuce.authx;

import io.lettuce.core.ClientOptions;
import io.lettuce.core.RedisClient;
import io.lettuce.core.RedisURI;
import io.lettuce.core.api.StatefulRedisConnection;
import io.lettuce.core.api.sync.RedisCommands;
import io.lettuce.core.cluster.ClusterClientOptions;
import io.lettuce.test.env.Endpoints;
import io.lettuce.test.env.Endpoints.Endpoint;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import redis.clients.authentication.core.TokenAuthConfig;
import redis.clients.authentication.entraid.EntraIDTokenAuthConfigBuilder;
import redis.clients.authentication.entraid.ManagedIdentityInfo;

import java.util.Collections;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ExecutionException;

import static io.lettuce.TestTags.ENTRA_ID;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assumptions.assumeTrue;

@Tag(ENTRA_ID)
public class EntraIdManagedIdentityIntegrationTests {

private static final EntraIdTestContext testCtx = EntraIdTestContext.DEFAULT;

private static RedisClient client;

private static Endpoint standalone;

private static Set<String> managedIdentityAudience = Collections.singleton("https://redis.azure.com");

@BeforeAll
public static void setup() {
standalone = Endpoints.DEFAULT.getEndpoint("standalone-entraid-acl");
assumeTrue(standalone != null, "Skipping test because no Redis endpoint is configured!");
}

@Test
public void withUserAssignedId_azureManagedIdentityIntegrationTest() throws ExecutionException, InterruptedException {

// enable re-authentication
ClusterClientOptions clientOptions = ClusterClientOptions.builder()
.reauthenticateBehavior(ClientOptions.ReauthenticateBehavior.ON_NEW_CREDENTIALS).build();

TokenAuthConfig tokenAuthConfig = EntraIDTokenAuthConfigBuilder.builder()
.userAssignedManagedIdentity(ManagedIdentityInfo.UserManagedIdentityType.OBJECT_ID,
testCtx.getUserAssignedManagedIdentity())
.scopes(managedIdentityAudience).build();

try (TokenBasedRedisCredentialsProvider credentialsProvider = TokenBasedRedisCredentialsProvider
.create(tokenAuthConfig)) {

RedisURI uri = RedisURI.create((standalone.getEndpoints().get(0)));
uri.setCredentialsProvider(credentialsProvider);
client = RedisClient.create(uri);
client.setOptions(clientOptions);

try (StatefulRedisConnection<String, String> connection = client.connect()) {
RedisCommands<String, String> sync = connection.sync();
String key = UUID.randomUUID().toString();
sync.set(key, "value");
assertThat(connection.sync().get(key)).isEqualTo("value");
assertThat(connection.async().get(key).get()).isEqualTo("value");
assertThat(connection.reactive().get(key).block()).isEqualTo("value");
sync.del(key);
}
}
}

@Test
public void withSystemAssignedId_azureManagedIdentityIntegrationTest() throws ExecutionException, InterruptedException {
// enable re-authentication
ClusterClientOptions clientOptions = ClusterClientOptions.builder()
.reauthenticateBehavior(ClientOptions.ReauthenticateBehavior.ON_NEW_CREDENTIALS).build();

TokenAuthConfig tokenAuthConfig = EntraIDTokenAuthConfigBuilder.builder().systemAssignedManagedIdentity()
.scopes(managedIdentityAudience).build();

try (TokenBasedRedisCredentialsProvider credentialsProvider = TokenBasedRedisCredentialsProvider
.create(tokenAuthConfig)) {

RedisURI uri = RedisURI.create((standalone.getEndpoints().get(0)));
uri.setCredentialsProvider(credentialsProvider);
client = RedisClient.create(uri);
client.setOptions(clientOptions);

try (StatefulRedisConnection<String, String> connection = client.connect()) {
RedisCommands<String, String> sync = connection.sync();
String key = UUID.randomUUID().toString();
sync.set(key, "value");
assertThat(connection.sync().get(key)).isEqualTo("value");
assertThat(connection.async().get(key).get()).isEqualTo("value");
assertThat(connection.reactive().get(key).block()).isEqualTo("value");
sync.del(key);
}
}
}

}
84 changes: 22 additions & 62 deletions src/test/java/io/lettuce/authx/EntraIdTestContext.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
package io.lettuce.authx;

import io.github.cdimascio.dotenv.Dotenv;

import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class EntraIdTestContext {
@@ -13,89 +10,44 @@ public class EntraIdTestContext {

private static final String AZURE_CLIENT_SECRET = "AZURE_CLIENT_SECRET";

private static final String AZURE_SP_OID = "AZURE_SP_OID";

private static final String AZURE_AUTHORITY = "AZURE_AUTHORITY";

private static final String AZURE_REDIS_SCOPES = "AZURE_REDIS_SCOPES";

private static final String REDIS_AZURE_HOST = "REDIS_AZURE_HOST";

private static final String REDIS_AZURE_PORT = "REDIS_AZURE_PORT";

private static final String REDIS_AZURE_CLUSTER_HOST = "REDIS_AZURE_CLUSTER_HOST";

private static final String REDIS_AZURE_CLUSTER_PORT = "REDIS_AZURE_CLUSTER_PORT";

private static final String REDIS_AZURE_DB = "REDIS_AZURE_DB";
private static final String AZURE_USER_ASSIGNED_MANAGED_ID = "AZURE_USER_ASSIGNED_MANAGED_ID";

private final String clientId;

private final String authority;

private final String clientSecret;

private final String spOID;

private final Set<String> redisScopes;
private Set<String> redisScopes;

private final String redisHost;

private final int redisPort;

private final List<String> redisClusterHost;

private final int redisClusterPort;

private static Dotenv dotenv;
static {
dotenv = Dotenv.configure().directory("src/test/resources").filename(".env.entraid").load();
}
private String userAssignedManagedIdentity;

public static final EntraIdTestContext DEFAULT = new EntraIdTestContext();

private EntraIdTestContext() {
// Using Dotenv directly here
clientId = dotenv.get(AZURE_CLIENT_ID, "<client-id>");
clientSecret = dotenv.get(AZURE_CLIENT_SECRET, "<client-secret>");
spOID = dotenv.get(AZURE_SP_OID, "<service-provider-oid>");
authority = dotenv.get(AZURE_AUTHORITY, "https://login.microsoftonline.com/your-tenant-id");
redisHost = dotenv.get(REDIS_AZURE_HOST);
redisPort = Integer.parseInt(dotenv.get(REDIS_AZURE_PORT, "6379"));
redisClusterHost = Arrays.asList(dotenv.get(REDIS_AZURE_CLUSTER_HOST, "").split(","));
redisClusterPort = Integer.parseInt(dotenv.get(REDIS_AZURE_CLUSTER_PORT, "6379"));
String redisScopesEnv = dotenv.get(AZURE_REDIS_SCOPES, "https://redis.azure.com/.default");
if (redisScopesEnv != null && !redisScopesEnv.isEmpty()) {
this.redisScopes = new HashSet<>(Arrays.asList(redisScopesEnv.split(";")));
} else {
this.redisScopes = new HashSet<>();
}
}

public String host() {
return redisHost;
}

public int port() {
return redisPort;
}

public List<String> clusterHost() {
return redisClusterHost;
clientId = System.getenv(AZURE_CLIENT_ID);
authority = System.getenv(AZURE_AUTHORITY);
clientSecret = System.getenv(AZURE_CLIENT_SECRET);
this.userAssignedManagedIdentity = System.getenv(AZURE_USER_ASSIGNED_MANAGED_ID);
}

public int clusterPort() {
return redisClusterPort;
public EntraIdTestContext(String clientId, String authority, String clientSecret, Set<String> redisScopes,
String userAssignedManagedIdentity) {
this.clientId = clientId;
this.authority = authority;
this.clientSecret = clientSecret;
this.redisScopes = redisScopes;
this.userAssignedManagedIdentity = userAssignedManagedIdentity;
}

public String getClientId() {
return clientId;
}

public String getSpOID() {
return spOID;
}

public String getAuthority() {
return authority;
}
@@ -105,7 +57,15 @@ public String getClientSecret() {
}

public Set<String> getRedisScopes() {
if (redisScopes == null) {
String redisScopesEnv = System.getenv(AZURE_REDIS_SCOPES);
this.redisScopes = new HashSet<>(Arrays.asList(redisScopesEnv.split(";")));
}
return redisScopes;
}

public String getUserAssignedManagedIdentity() {
return userAssignedManagedIdentity;
}

}
235 changes: 235 additions & 0 deletions src/test/java/io/lettuce/test/env/Endpoints.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
package io.lettuce.test.env;

import com.fasterxml.jackson.annotation.JsonProperty;

import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;

public class Endpoints {

private static final Logger log = LoggerFactory.getLogger(Endpoints.class);

private Map<String, Endpoint> endpoints;

public static final Endpoints DEFAULT;

static {
String filePath = System.getenv("REDIS_ENDPOINTS_CONFIG_PATH");
if (filePath == null || filePath.isEmpty()) {
log.info("REDIS_ENDPOINTS_CONFIG_PATH environment variable is not set. No Endpoints configuration will be loaded.");
DEFAULT = new Endpoints(Collections.emptyMap());
} else {
DEFAULT = fromFile(filePath);
}
}

private Endpoints(Map<String, Endpoint> endpoints) {
this.endpoints = endpoints;
}

/**
* Factory method to create an Endpoints instance from a file.
*
* @param filePath Path to the JSON file.
* @return Populated Endpoints instance.
*/
public static Endpoints fromFile(String filePath) {
try {
ObjectMapper objectMapper = new ObjectMapper();
File file = new File(filePath);

HashMap<String, Endpoint> endpoints = objectMapper.readValue(file,
objectMapper.getTypeFactory().constructMapType(HashMap.class, String.class, Endpoint.class));
return new Endpoints(endpoints);

} catch (IOException e) {
throw new RuntimeException("Failed to load Endpoints from file: " + filePath, e);
}
}

/**
* Get an endpoint by name.
*
* @param name the name of the endpoint.
* @return the corresponding Endpoint or {@code null} if not found.
*/
public Endpoint getEndpoint(String name) {
return endpoints != null ? endpoints.get(name) : null;
}

public Map<String, Endpoint> getEndpoints() {
return endpoints;
}

public void setEndpoints(Map<String, Endpoint> endpoints) {
this.endpoints = endpoints;
}

// Inner classes for Endpoint and RawEndpoint
public static class Endpoint {

@JsonProperty("bdb_id")
private int bdbId;

private String username;

private String password;

private boolean tls;

@JsonProperty("raw_endpoints")
private List<RawEndpoint> rawEndpoints;

private List<String> endpoints;

// Getters and Setters
public int getBdbId() {
return bdbId;
}

public void setBdbId(int bdbId) {
this.bdbId = bdbId;
}

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
}

public boolean isTls() {
return tls;
}

public void setTls(boolean tls) {
this.tls = tls;
}

public List<RawEndpoint> getRawEndpoints() {
return rawEndpoints;
}

public void setRawEndpoints(List<RawEndpoint> rawEndpoints) {
this.rawEndpoints = rawEndpoints;
}

public List<String> getEndpoints() {
return endpoints;
}

public void setEndpoints(List<String> endpoints) {
this.endpoints = endpoints;
}

}

public static class RawEndpoint {

private List<String> addr;

@JsonProperty("addr_type")
private String addrType;

@JsonProperty("dns_name")
private String dnsName;

@JsonProperty("oss_cluster_api_preferred_endpoint_type")
private String preferredEndpointType;

@JsonProperty("oss_cluster_api_preferred_ip_type")
private String preferredIpType;

private int port;

@JsonProperty("proxy_policy")
private String proxyPolicy;

private String uid;

// Getters and Setters
public List<String> getAddr() {
return addr;
}

public void setAddr(List<String> addr) {
this.addr = addr;
}

public String getAddrType() {
return addrType;
}

public void setAddrType(String addrType) {
this.addrType = addrType;
}

public String getDnsName() {
return dnsName;
}

public void setDnsName(String dnsName) {
this.dnsName = dnsName;
}

public String getPreferredEndpointType() {
return preferredEndpointType;
}

public void setPreferredEndpointType(String preferredEndpointType) {
this.preferredEndpointType = preferredEndpointType;
}

public String getPreferredIpType() {
return preferredIpType;
}

public void setPreferredIpType(String preferredIpType) {
this.preferredIpType = preferredIpType;
}

public int getPort() {
return port;
}

public void setPort(int port) {
this.port = port;
}

public String getProxyPolicy() {
return proxyPolicy;
}

public void setProxyPolicy(String proxyPolicy) {
this.proxyPolicy = proxyPolicy;
}

public String getUid() {
return uid;
}

public void setUid(String uid) {
this.uid = uid;
}

}

}

0 comments on commit ee7c46c

Please sign in to comment.