This is an automated email from the ASF dual-hosted git repository.
exceptionfactory pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nifi.git
The following commit(s) were added to refs/heads/main by this push:
new 34a6d8a0a1 NIFI-8128 Added Sentinel Password to Redis Service
34a6d8a0a1 is described below
commit 34a6d8a0a14cdc0cb4e0e779ee63dd11942386e2
Author: EndzeitBegins <[email protected]>
AuthorDate: Fri Jul 14 18:07:43 2023 +0200
NIFI-8128 Added Sentinel Password to Redis Service
- Replaced embedded-redis and with Testcontainers for Redis integration
tests
This closes #7481
Signed-off-by: David Handermann <[email protected]>
---
.../nifi-redis-extensions/pom.xml | 17 +-
.../org/apache/nifi/redis/util/RedisUtils.java | 26 ++-
.../ITRedisDistributedMapCacheClientService.java | 228 +++++++++++++++++----
.../nifi/redis/state/ITRedisStateProvider.java | 42 ++--
.../nifi/redis/testcontainers/RedisContainer.java | 113 ++++++++++
.../testcontainers/RedisReplicaContainer.java | 60 ++++++
.../testcontainers/RedisSentinelContainer.java | 113 ++++++++++
7 files changed, 516 insertions(+), 83 deletions(-)
diff --git a/nifi-nar-bundles/nifi-redis-bundle/nifi-redis-extensions/pom.xml
b/nifi-nar-bundles/nifi-redis-bundle/nifi-redis-extensions/pom.xml
index 80baf8d1d2..82a3b96607 100644
--- a/nifi-nar-bundles/nifi-redis-bundle/nifi-redis-extensions/pom.xml
+++ b/nifi-nar-bundles/nifi-redis-bundle/nifi-redis-extensions/pom.xml
@@ -82,6 +82,11 @@
<artifactId>nifi-record-path</artifactId>
<version>2.0.0-SNAPSHOT</version>
</dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-ssl-context-service-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
<!-- Test dependencies -->
<dependency>
<groupId>org.apache.nifi</groupId>
@@ -96,15 +101,15 @@
<scope>test</scope>
</dependency>
<dependency>
- <groupId>com.github.kstyrc</groupId>
- <artifactId>embedded-redis</artifactId>
- <version>0.6</version>
+ <groupId>org.testcontainers</groupId>
+ <artifactId>testcontainers</artifactId>
+ <version>${testcontainers.version}</version>
<scope>test</scope>
</dependency>
<dependency>
- <groupId>org.apache.nifi</groupId>
- <artifactId>nifi-ssl-context-service-api</artifactId>
- <scope>compile</scope>
+ <groupId>org.testcontainers</groupId>
+ <artifactId>junit-jupiter</artifactId>
+ <scope>test</scope>
</dependency>
</dependencies>
</project>
diff --git
a/nifi-nar-bundles/nifi-redis-bundle/nifi-redis-extensions/src/main/java/org/apache/nifi/redis/util/RedisUtils.java
b/nifi-nar-bundles/nifi-redis-bundle/nifi-redis-extensions/src/main/java/org/apache/nifi/redis/util/RedisUtils.java
index af9b61e7a0..30e6f7b009 100644
---
a/nifi-nar-bundles/nifi-redis-bundle/nifi-redis-extensions/src/main/java/org/apache/nifi/redis/util/RedisUtils.java
+++
b/nifi-nar-bundles/nifi-redis-bundle/nifi-redis-extensions/src/main/java/org/apache/nifi/redis/util/RedisUtils.java
@@ -35,6 +35,7 @@ import
org.springframework.data.redis.connection.RedisSentinelConfiguration;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import
org.springframework.data.redis.connection.jedis.JedisClientConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
+import org.springframework.lang.Nullable;
import redis.clients.jedis.JedisPoolConfig;
import javax.net.ssl.SSLContext;
@@ -134,7 +135,16 @@ public class RedisUtils {
public static final PropertyDescriptor PASSWORD = new
PropertyDescriptor.Builder()
.name("Password")
.displayName("Password")
- .description("The password used to authenticate to the Redis
server. See the requirepass property in redis.conf.")
+ .description("The password used to authenticate to the Redis
server. See the 'requirepass' property in redis.conf.")
+ .addValidator(StandardValidators.NON_BLANK_VALIDATOR)
+
.expressionLanguageSupported(ExpressionLanguageScope.VARIABLE_REGISTRY)
+ .sensitive(true)
+ .build();
+
+ public static final PropertyDescriptor SENTINEL_PASSWORD = new
PropertyDescriptor.Builder()
+ .name("Sentinel Password")
+ .displayName("Sentinel Password")
+ .description("The password used to authenticate to the Redis
Sentinel server. See the 'requirepass' and 'sentinel sentinel-pass' properties
in sentinel.conf.")
.addValidator(StandardValidators.NON_BLANK_VALIDATOR)
.expressionLanguageSupported(ExpressionLanguageScope.VARIABLE_REGISTRY)
.sensitive(true)
@@ -275,6 +285,7 @@ public class RedisUtils {
props.add(RedisUtils.CLUSTER_MAX_REDIRECTS);
props.add(RedisUtils.SENTINEL_MASTER);
props.add(RedisUtils.PASSWORD);
+ props.add(RedisUtils.SENTINEL_PASSWORD);
props.add(RedisUtils.SSL_CONTEXT_SERVICE);
props.add(RedisUtils.POOL_MAX_TOTAL);
props.add(RedisUtils.POOL_MAX_IDLE);
@@ -297,6 +308,7 @@ public class RedisUtils {
final String connectionString =
context.getProperty(RedisUtils.CONNECTION_STRING).evaluateAttributeExpressions().getValue();
final Integer dbIndex =
context.getProperty(RedisUtils.DATABASE).evaluateAttributeExpressions().asInteger();
final String password =
context.getProperty(RedisUtils.PASSWORD).evaluateAttributeExpressions().getValue();
+ final String sentinelPassword =
context.getProperty(RedisUtils.SENTINEL_PASSWORD).evaluateAttributeExpressions().getValue();
final Integer timeout =
context.getProperty(RedisUtils.COMMUNICATION_TIMEOUT).asTimePeriod(TimeUnit.MILLISECONDS).intValue();
final JedisPoolConfig poolConfig = createJedisPoolConfig(context);
@@ -323,7 +335,7 @@ public class RedisUtils {
final String host = hostAndPortSplit[0].trim();
final Integer port = Integer.parseInt(hostAndPortSplit[1].trim());
final RedisStandaloneConfiguration redisStandaloneConfiguration =
new RedisStandaloneConfiguration(host, port);
- enrichRedisConfiguration(redisStandaloneConfiguration, dbIndex,
password);
+ enrichRedisConfiguration(redisStandaloneConfiguration, dbIndex,
password, sentinelPassword);
connectionFactory = new
JedisConnectionFactory(redisStandaloneConfiguration, jedisClientConfiguration);
@@ -331,7 +343,7 @@ public class RedisUtils {
final String[] sentinels = connectionString.split("[,]");
final String sentinelMaster =
context.getProperty(RedisUtils.SENTINEL_MASTER).evaluateAttributeExpressions().getValue();
final RedisSentinelConfiguration sentinelConfiguration = new
RedisSentinelConfiguration(sentinelMaster, new
HashSet<>(getTrimmedValues(sentinels)));
- enrichRedisConfiguration(sentinelConfiguration, dbIndex, password);
+ enrichRedisConfiguration(sentinelConfiguration, dbIndex, password,
sentinelPassword);
logger.info("Connecting to Redis in sentinel mode...");
logger.info("Redis master = " + sentinelMaster);
@@ -347,7 +359,7 @@ public class RedisUtils {
final Integer maxRedirects =
context.getProperty(RedisUtils.CLUSTER_MAX_REDIRECTS).asInteger();
final RedisClusterConfiguration clusterConfiguration = new
RedisClusterConfiguration(getTrimmedValues(clusterNodes));
- enrichRedisConfiguration(clusterConfiguration, dbIndex, password);
+ enrichRedisConfiguration(clusterConfiguration, dbIndex, password,
sentinelPassword);
clusterConfiguration.setMaxRedirects(maxRedirects);
logger.info("Connecting to Redis in clustered mode...");
@@ -373,13 +385,17 @@ public class RedisUtils {
private static void enrichRedisConfiguration(final RedisConfiguration
redisConfiguration,
final Integer dbIndex,
- final String password) {
+ @Nullable final String
password,
+ @Nullable final String
sentinelPassword) {
if (redisConfiguration instanceof
RedisConfiguration.WithDatabaseIndex) {
((RedisConfiguration.WithDatabaseIndex)
redisConfiguration).setDatabase(dbIndex);
}
if (redisConfiguration instanceof RedisConfiguration.WithPassword) {
((RedisConfiguration.WithPassword)
redisConfiguration).setPassword(RedisPassword.of(password));
}
+ if (redisConfiguration instanceof
RedisConfiguration.SentinelConfiguration) {
+ ((RedisConfiguration.SentinelConfiguration)
redisConfiguration).setSentinelPassword(RedisPassword.of(sentinelPassword));
+ }
}
private static JedisPoolConfig createJedisPoolConfig(final PropertyContext
context) {
diff --git
a/nifi-nar-bundles/nifi-redis-bundle/nifi-redis-extensions/src/test/java/org/apache/nifi/redis/service/ITRedisDistributedMapCacheClientService.java
b/nifi-nar-bundles/nifi-redis-bundle/nifi-redis-extensions/src/test/java/org/apache/nifi/redis/service/ITRedisDistributedMapCacheClientService.java
index aec018954e..f14187f6ed 100644
---
a/nifi-nar-bundles/nifi-redis-bundle/nifi-redis-extensions/src/test/java/org/apache/nifi/redis/service/ITRedisDistributedMapCacheClientService.java
+++
b/nifi-nar-bundles/nifi-redis-bundle/nifi-redis-extensions/src/test/java/org/apache/nifi/redis/service/ITRedisDistributedMapCacheClientService.java
@@ -30,14 +30,19 @@ import org.apache.nifi.processor.ProcessSession;
import org.apache.nifi.processor.Relationship;
import org.apache.nifi.processor.exception.ProcessException;
import org.apache.nifi.processor.util.StandardValidators;
+import org.apache.nifi.redis.RedisConnectionPool;
+import org.apache.nifi.redis.testcontainers.RedisContainer;
+import org.apache.nifi.redis.testcontainers.RedisReplicaContainer;
+import org.apache.nifi.redis.testcontainers.RedisSentinelContainer;
import org.apache.nifi.redis.util.RedisUtils;
import org.apache.nifi.reporting.InitializationException;
import org.apache.nifi.util.TestRunner;
import org.apache.nifi.util.TestRunners;
import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
-import redis.embedded.RedisServer;
+import org.junit.jupiter.api.io.TempDir;
+import org.springframework.lang.NonNull;
+import org.springframework.lang.Nullable;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@@ -46,6 +51,8 @@ import java.net.InetSocketAddress;
import java.net.StandardSocketOptions;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
@@ -53,8 +60,10 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.function.Consumer;
import static org.apache.nifi.redis.util.RedisUtils.REDIS_CONNECTION_POOL;
+import static org.apache.nifi.redis.util.RedisUtils.REDIS_MODE_SENTINEL;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -64,22 +73,156 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
*/
public class ITRedisDistributedMapCacheClientService {
- private TestRedisProcessor proc;
- private TestRunner testRunner;
- private RedisServer redisServer;
+ public static final String CONTAINER_IMAGE_TAG = "redis:7.0.12-alpine";
+
+ private static final String masterName = "redisLeader";
+ @TempDir
+
+ private Path testDirectory;
+
+ private final TestRedisProcessor proc = new TestRedisProcessor();
+ private final TestRunner testRunner = TestRunners.newTestRunner(proc);
+
+ private final List<RedisContainer> redisContainers = new ArrayList<>();
private RedisConnectionPoolService redisConnectionPool;
- private RedisDistributedMapCacheClientService redisMapCacheClientService;
- private int redisPort;
- @BeforeEach
- public void setup() throws IOException {
- this.redisPort = getAvailablePort();
+ @Test
+ public void testStandaloneRedis() throws InitializationException,
IOException {
+ int redisPort = setupStandaloneRedis(null).port;
+ setUpRedisConnectionPool(portsToConnectionString(redisPort), pool -> {
+ // uncomment this to test using a different database index than
the default 0
+ // testRunner.setProperty(pool, RedisUtils.DATABASE, "1");
+ });
+ setupRedisMapCacheClientService();
+
+ executeProcessor();
+ }
+
+ @Test
+ public void testStandaloneRedisWithAuthentication() throws
InitializationException, IOException {
+ final String redisPassword = "foobared";
+ final int redisPort = setupStandaloneRedis(redisPassword).port;
+ setUpRedisConnectionPool(portsToConnectionString(redisPort), pool -> {
+ testRunner.setProperty(redisConnectionPool, RedisUtils.PASSWORD,
redisPassword);
+ });
+ setupRedisMapCacheClientService();
+
+ executeProcessor();
+ }
+
+ @Test
+ public void testSentinelRedis() throws InitializationException,
IOException {
+ RedisContainer redisMasterContainer = setupStandaloneRedis(null);
+ String masterHost = "127.0.0.1";
+ int masterPort = redisMasterContainer.port;
+ setUpRedisReplica(masterHost, masterPort, null);
+ setUpRedisReplica(masterHost, masterPort, null);
+
+ int sentinelAPort = setUpSentinel(masterHost, masterPort, null, 2,
null).port;
+ int sentinelBPort = setUpSentinel(masterHost, masterPort, null, 2,
null).port;
+ int sentinelCPort = setUpSentinel(masterHost, masterPort, null, 2,
null).port;
+
+ setUpRedisConnectionPool(portsToConnectionString(sentinelAPort,
sentinelBPort, sentinelCPort), pool -> {
+ testRunner.setProperty(redisConnectionPool, RedisUtils.REDIS_MODE,
REDIS_MODE_SENTINEL);
+ testRunner.setProperty(redisConnectionPool,
RedisUtils.SENTINEL_MASTER, masterName);
+ });
+
+ setupRedisMapCacheClientService();
+
+ executeProcessor();
+ }
- this.redisServer = new RedisServer(redisPort);
- redisServer.start();
+ @Test
+ public void testSentinelRedisWithAuthentication() throws
InitializationException, IOException {
+ String redisPassword = "t0p_53cr35";
+ String sentinelPassword = "otherPassword";
+
+ RedisContainer redisMasterContainer =
setupStandaloneRedis(redisPassword);
+ String masterHost = "127.0.0.1";
+ int masterPort = redisMasterContainer.port;
+ setUpRedisReplica(masterHost, masterPort, redisPassword);
+ setUpRedisReplica(masterHost, masterPort, redisPassword);
+
+ int sentinelAPort = setUpSentinel(masterHost, masterPort,
redisPassword, 2, sentinelPassword).port;
+ int sentinelBPort = setUpSentinel(masterHost, masterPort,
redisPassword, 2, sentinelPassword).port;
+ int sentinelCPort = setUpSentinel(masterHost, masterPort,
redisPassword, 2, sentinelPassword).port;
+
+ setUpRedisConnectionPool(portsToConnectionString(sentinelAPort,
sentinelBPort, sentinelCPort), pool -> {
+ testRunner.setProperty(redisConnectionPool, RedisUtils.REDIS_MODE,
REDIS_MODE_SENTINEL);
+ testRunner.setProperty(redisConnectionPool,
RedisUtils.SENTINEL_MASTER, masterName);
+
+ testRunner.setProperty(redisConnectionPool, RedisUtils.PASSWORD,
redisPassword);
+ testRunner.setProperty(redisConnectionPool,
RedisUtils.SENTINEL_PASSWORD, sentinelPassword);
+ });
+ setupRedisMapCacheClientService();
+
+ executeProcessor();
+ }
+
+ @AfterEach
+ public void teardown() {
+ if (redisConnectionPool != null) {
+ redisConnectionPool.onDisabled();
+ }
- proc = new TestRedisProcessor();
- testRunner = TestRunners.newTestRunner(proc);
+ redisContainers.forEach(RedisContainer::stop);
+ }
+
+ private RedisContainer setupStandaloneRedis(final @Nullable String
redisPassword) throws IOException {
+ int redisPort = getAvailablePort();
+
+ RedisContainer redisContainer = new
RedisContainer(CONTAINER_IMAGE_TAG);
+ redisContainer.mountConfigurationFrom(testDirectory);
+ redisContainer.setPort(redisPort);
+ redisContainer.addPortBinding(redisPort, redisPort);
+ redisContainer.setPassword(redisPassword);
+
+ redisContainers.add(redisContainer);
+ redisContainer.start();
+
+ return redisContainer;
+ }
+
+ private RedisReplicaContainer setUpRedisReplica(final @NonNull String
masterHost,
+ final int masterPort,
+ final @Nullable String
redisPassword) throws IOException {
+ int replicaPort = getAvailablePort();
+
+ RedisReplicaContainer redisReplicaContainer = new
RedisReplicaContainer(CONTAINER_IMAGE_TAG);
+ redisReplicaContainer.mountConfigurationFrom(testDirectory);
+ redisReplicaContainer.setPort(replicaPort);
+ redisReplicaContainer.addPortBinding(replicaPort, replicaPort);
+ redisReplicaContainer.setReplicaOf(masterHost, masterPort);
+ redisReplicaContainer.setPassword(redisPassword);
+
+ redisContainers.add(redisReplicaContainer);
+ redisReplicaContainer.start();
+
+ return redisReplicaContainer;
+ }
+
+ private RedisSentinelContainer setUpSentinel(final @NonNull String
masterHost,
+ final int masterPort,
+ final @Nullable String
redisPassword,
+ final int quorumSize,
+ final @Nullable String
sentinelPassword) throws IOException {
+ int sentinelPort = getAvailablePort();
+
+ RedisSentinelContainer redisSentinelContainer = new
RedisSentinelContainer(CONTAINER_IMAGE_TAG);
+ redisSentinelContainer.mountConfigurationFrom(testDirectory);
+ redisSentinelContainer.setPort(sentinelPort);
+ redisSentinelContainer.addPortBinding(sentinelPort, sentinelPort);
+ redisSentinelContainer.setMasterHost(masterHost);
+ redisSentinelContainer.setMasterPort(masterPort);
+ redisSentinelContainer.setMasterName(masterName);
+ redisSentinelContainer.setQuorumSize(quorumSize);
+ redisSentinelContainer.setPassword(redisPassword);
+ redisSentinelContainer.setSentinelPassword(sentinelPassword);
+
+ redisContainers.add(redisSentinelContainer);
+ redisSentinelContainer.start();
+
+ return redisSentinelContainer;
}
private int getAvailablePort() throws IOException {
@@ -90,41 +233,37 @@ public class ITRedisDistributedMapCacheClientService {
}
}
- @AfterEach
- public void teardown() throws IOException {
- if (redisServer != null) {
- redisServer.stop();
- }
- }
-
- @Test
- public void testStandaloneRedis() throws InitializationException {
- try {
- // create, configure, and enable the RedisConnectionPool service
- redisConnectionPool = new RedisConnectionPoolService();
- testRunner.addControllerService("redis-connection-pool",
redisConnectionPool);
- testRunner.setProperty(redisConnectionPool,
RedisUtils.CONNECTION_STRING, "localhost:" + redisPort);
+ private String portsToConnectionString(int... ports) {
+ StringBuilder stringBuilder = new StringBuilder();
- // uncomment this to test using a different database index than
the default 0
- //testRunner.setProperty(redisConnectionPool, RedisUtils.DATABASE,
"1");
+ for (int i = 0; i < ports.length; i++) {
+ if (i > 0) {
+ stringBuilder.append(',');
+ }
+ stringBuilder.append("localhost");
+ stringBuilder.append(':');
+ stringBuilder.append(ports[i]);
+ }
- // uncomment this to test using a password to authenticate to redis
- //testRunner.setProperty(redisConnectionPool, RedisUtils.PASSWORD,
"foobared");
+ return stringBuilder.toString();
+ }
- testRunner.enableControllerService(redisConnectionPool);
+ public void setUpRedisConnectionPool(String connectionString,
Consumer<RedisConnectionPool> initializer) throws InitializationException {
+ // create, configure, and enable the RedisConnectionPool service
+ redisConnectionPool = new RedisConnectionPoolService();
+ testRunner.addControllerService("redis-connection-pool",
redisConnectionPool);
+ testRunner.setProperty(redisConnectionPool,
RedisUtils.CONNECTION_STRING, connectionString);
- setupRedisMapCacheClientService();
- executeProcessor();
- } finally {
- if (redisConnectionPool != null) {
- redisConnectionPool.onDisabled();
- }
+ if (initializer != null) {
+ initializer.accept(redisConnectionPool);
}
+
+ testRunner.enableControllerService(redisConnectionPool);
}
private void setupRedisMapCacheClientService() throws
InitializationException {
// create, configure, and enable the RedisDistributedMapCacheClient
service
- redisMapCacheClientService = new
RedisDistributedMapCacheClientService();
+ RedisDistributedMapCacheClientService redisMapCacheClientService = new
RedisDistributedMapCacheClientService();
testRunner.addControllerService("redis-map-cache-client",
redisMapCacheClientService);
testRunner.setProperty(redisMapCacheClientService,
REDIS_CONNECTION_POOL, "redis-connection-pool");
testRunner.enableControllerService(redisMapCacheClientService);
@@ -218,12 +357,12 @@ public class ITRedisDistributedMapCacheClientService {
assertEquals(value, cacheClient.get(keyThatDoesntExist,
stringSerializer, stringDeserializer));
// verify atomic fetch returns the correct entry
- final AtomicCacheEntry<String,String,byte[]> entry =
cacheClient.fetch(key, stringSerializer, stringDeserializer);
+ final AtomicCacheEntry<String, String, byte[]> entry =
cacheClient.fetch(key, stringSerializer, stringDeserializer);
assertEquals(key, entry.getKey());
assertEquals(value, entry.getValue());
assertTrue(Arrays.equals(value.getBytes(StandardCharsets.UTF_8),
entry.getRevision().orElse(null)));
- final AtomicCacheEntry<String,String,byte[]> notLatestEntry =
new AtomicCacheEntry<>(entry.getKey(), entry.getValue(), "not
previous".getBytes(StandardCharsets.UTF_8));
+ final AtomicCacheEntry<String, String, byte[]> notLatestEntry
= new AtomicCacheEntry<>(entry.getKey(), entry.getValue(), "not
previous".getBytes(StandardCharsets.UTF_8));
// verify atomic replace does not replace when previous value
is not equal
assertFalse(cacheClient.replace(notLatestEntry,
stringSerializer, stringSerializer));
@@ -237,12 +376,12 @@ public class ITRedisDistributedMapCacheClientService {
// verify atomic replace does replace no value previous existed
final String replaceKeyDoesntExist = key +
"_REPLACE_DOES_NOT_EXIST";
- final AtomicCacheEntry<String,String,byte[]> entryDoesNotExist
= new AtomicCacheEntry<>(replaceKeyDoesntExist, replacementValue, null);
+ final AtomicCacheEntry<String, String, byte[]>
entryDoesNotExist = new AtomicCacheEntry<>(replaceKeyDoesntExist,
replacementValue, null);
assertTrue(cacheClient.replace(entryDoesNotExist,
stringSerializer, stringSerializer));
assertEquals(replacementValue,
cacheClient.get(replaceKeyDoesntExist, stringSerializer, stringDeserializer));
final int numToDelete = 2000;
- for (int i=0; i < numToDelete; i++) {
+ for (int i = 0; i < numToDelete; i++) {
cacheClient.put(key + "-" + i, value, stringSerializer,
stringSerializer);
}
@@ -294,3 +433,4 @@ public class ITRedisDistributedMapCacheClientService {
}
}
}
+
diff --git
a/nifi-nar-bundles/nifi-redis-bundle/nifi-redis-extensions/src/test/java/org/apache/nifi/redis/state/ITRedisStateProvider.java
b/nifi-nar-bundles/nifi-redis-bundle/nifi-redis-extensions/src/test/java/org/apache/nifi/redis/state/ITRedisStateProvider.java
index 599e5ec6ab..5315439f1f 100644
---
a/nifi-nar-bundles/nifi-redis-bundle/nifi-redis-extensions/src/test/java/org/apache/nifi/redis/state/ITRedisStateProvider.java
+++
b/nifi-nar-bundles/nifi-redis-bundle/nifi-redis-extensions/src/test/java/org/apache/nifi/redis/state/ITRedisStateProvider.java
@@ -23,18 +23,18 @@ import org.apache.nifi.components.state.StateMap;
import org.apache.nifi.components.state.StateProvider;
import org.apache.nifi.components.state.StateProviderInitializationContext;
import org.apache.nifi.logging.ComponentLog;
+import org.apache.nifi.redis.service.ITRedisDistributedMapCacheClientService;
+import org.apache.nifi.redis.testcontainers.RedisContainer;
import org.apache.nifi.redis.util.RedisUtils;
import org.apache.nifi.util.MockComponentLog;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
-import redis.embedded.RedisServer;
+import org.testcontainers.junit.jupiter.Container;
+import org.testcontainers.junit.jupiter.Testcontainers;
import javax.net.ssl.SSLContext;
import java.io.IOException;
-import java.net.InetSocketAddress;
-import java.net.StandardSocketOptions;
-import java.nio.channels.SocketChannel;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
@@ -51,22 +51,21 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
* NOTE: These test cases should be kept in-sync with
AbstractTestStateProvider which is in the framework
* and couldn't be extended here.
*/
+@Testcontainers
public class ITRedisStateProvider {
protected final String componentId =
"111111111-1111-1111-1111-111111111111";
- private RedisServer redisServer;
+ @Container
+ public RedisContainer redisContainer = new
RedisContainer(ITRedisDistributedMapCacheClientService.CONTAINER_IMAGE_TAG)
+ .withExposedPorts(6379);
+
private RedisStateProvider provider;
@BeforeEach
- public void setup() throws Exception {
- final int redisPort = getAvailablePort();
-
- this.redisServer = new RedisServer(redisPort);
- redisServer.start();
-
+ public void setup() {
final Map<PropertyDescriptor, String> properties = new HashMap<>();
- properties.put(RedisUtils.CONNECTION_STRING, "localhost:" + redisPort);
+ properties.put(RedisUtils.CONNECTION_STRING, redisContainer.getHost()
+ ":" + redisContainer.getFirstMappedPort());
this.provider = createProvider(properties);
}
@@ -80,10 +79,6 @@ public class ITRedisStateProvider {
provider.disable();
provider.shutdown();
}
-
- if (redisServer != null) {
- redisServer.stop();
- }
}
public StateProvider getProvider() {
@@ -173,7 +168,7 @@ public class ITRedisStateProvider {
assertEquals(1, map.size());
assertEquals("value", map.get(key));
- provider.setState(Collections.<String, String> emptyMap(),
componentId);
+ provider.setState(Collections.<String, String>emptyMap(), componentId);
final StateMap stateMap = provider.getState(componentId);
map = stateMap.toMap();
@@ -284,8 +279,8 @@ public class ITRedisStateProvider {
}
@Override
- public Map<String,String> getAllProperties() {
- final Map<String,String> propValueMap = new LinkedHashMap<>();
+ public Map<String, String> getAllProperties() {
+ final Map<String, String> propValueMap = new LinkedHashMap<>();
for (final Map.Entry<PropertyDescriptor, String> entry :
properties.entrySet()) {
propValueMap.put(entry.getKey().getName(),
entry.getValue());
}
@@ -321,13 +316,4 @@ public class ITRedisStateProvider {
provider.enable();
return provider;
}
-
- private int getAvailablePort() throws IOException {
- try (SocketChannel socket = SocketChannel.open()) {
- socket.setOption(StandardSocketOptions.SO_REUSEADDR, true);
- socket.bind(new InetSocketAddress("localhost", 0));
- return socket.socket().getLocalPort();
- }
- }
-
}
diff --git
a/nifi-nar-bundles/nifi-redis-bundle/nifi-redis-extensions/src/test/java/org/apache/nifi/redis/testcontainers/RedisContainer.java
b/nifi-nar-bundles/nifi-redis-bundle/nifi-redis-extensions/src/test/java/org/apache/nifi/redis/testcontainers/RedisContainer.java
new file mode 100644
index 0000000000..24f5b68a98
--- /dev/null
+++
b/nifi-nar-bundles/nifi-redis-bundle/nifi-redis-extensions/src/test/java/org/apache/nifi/redis/testcontainers/RedisContainer.java
@@ -0,0 +1,113 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.redis.testcontainers;
+
+import org.springframework.lang.NonNull;
+import org.springframework.lang.Nullable;
+import org.testcontainers.containers.BindMode;
+import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.utility.DockerImageName;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+public class RedisContainer extends GenericContainer<RedisContainer> {
+
+ public static final int REDIS_PORT = 6379;
+
+ public RedisContainer(@NonNull DockerImageName dockerImageName) {
+ super(dockerImageName);
+ }
+
+ public RedisContainer(@NonNull String fullImageName) {
+ this(DockerImageName.parse(fullImageName));
+ }
+
+ public int port = REDIS_PORT;
+
+ @Nullable
+ protected String password = null;
+ @Nullable
+ protected Path configurationMountDirectory = null;
+
+ protected final List<String> configurationOptions = new ArrayList<>();
+
+ public void setPassword(final @Nullable String password) {
+ this.password = password;
+ }
+
+ public void setPort(final int port) {
+ this.port = port;
+ }
+
+ public void mountConfigurationFrom(final Path mountDirectory) {
+ this.configurationMountDirectory = mountDirectory;
+ }
+
+ public void addConfigurationOption(final String configurationOption) {
+ this.configurationOptions.add(configurationOption);
+ }
+
+ protected void adjustConfiguration() {
+ addConfigurationOption("port " + port);
+
+ if (password != null) {
+ addConfigurationOption("requirepass " + password);
+ }
+ }
+
+ /**
+ * Sets up a static binding between a port on the host and one in the
container.
+ * In order for auto-discovery mechanisms of Redis to work, 1-to-1 mapped
ports are useful.
+ */
+ public void addPortBinding(int hostPort, int containerPort) {
+ addFixedExposedPort(hostPort, containerPort);
+ }
+
+ @Override
+ protected void configure() {
+ adjustConfiguration();
+
+ Path configurationFilePath = writeConfigurationFile().toAbsolutePath();
+ String hostPath = configurationFilePath.toString();
+ String containerPath = "/usr/local/etc/redis/redis.conf";
+ addFileSystemBind(hostPath, containerPath, BindMode.READ_WRITE);
+
+ setCommand(containerPath);
+ }
+
+ protected Path writeConfigurationFile() {
+ try {
+ Path mountDirectory = this.configurationMountDirectory;
+ if (mountDirectory == null) {
+ mountDirectory =
Files.createTempDirectory("redis-container-configuration");
+ }
+
+ Path configFile = mountDirectory.resolve("redis-" +
UUID.randomUUID() + ".conf");
+ Files.write(configFile, configurationOptions,
StandardCharsets.UTF_8);
+
+ return configFile;
+ } catch (IOException ioException) {
+ throw new IllegalStateException("Cannot start container because
configuration could not be written", ioException);
+ }
+ }
+}
diff --git
a/nifi-nar-bundles/nifi-redis-bundle/nifi-redis-extensions/src/test/java/org/apache/nifi/redis/testcontainers/RedisReplicaContainer.java
b/nifi-nar-bundles/nifi-redis-bundle/nifi-redis-extensions/src/test/java/org/apache/nifi/redis/testcontainers/RedisReplicaContainer.java
new file mode 100644
index 0000000000..815fe33f85
--- /dev/null
+++
b/nifi-nar-bundles/nifi-redis-bundle/nifi-redis-extensions/src/test/java/org/apache/nifi/redis/testcontainers/RedisReplicaContainer.java
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.redis.testcontainers;
+
+import org.springframework.lang.NonNull;
+import org.testcontainers.utility.DockerImageName;
+
+public class RedisReplicaContainer extends RedisContainer {
+
+ public RedisReplicaContainer(@NonNull DockerImageName dockerImageName) {
+ super(dockerImageName);
+ }
+
+ public RedisReplicaContainer(@NonNull String fullImageName) {
+ this(DockerImageName.parse(fullImageName));
+ }
+
+ @NonNull
+ protected String masterHost = "localhost";
+ protected int masterPort = REDIS_PORT;
+
+ public void setMasterHost(@NonNull String masterHost) {
+ this.masterHost = masterHost;
+ }
+
+ public void setMasterPort(int masterPort) {
+ this.masterPort = masterPort;
+ }
+
+ public void setReplicaOf(@NonNull String masterHost, int masterPort) {
+ setMasterHost(masterHost);
+ setMasterPort(masterPort);
+ }
+
+ @Override
+ protected void adjustConfiguration() {
+ addConfigurationOption("port " + port);
+
+ if (password != null) {
+ addConfigurationOption("requirepass " + password);
+ addConfigurationOption("masterauth " + password);
+ }
+
+ addConfigurationOption("replicaof " + masterHost + " " + masterPort);
+ }
+}
diff --git
a/nifi-nar-bundles/nifi-redis-bundle/nifi-redis-extensions/src/test/java/org/apache/nifi/redis/testcontainers/RedisSentinelContainer.java
b/nifi-nar-bundles/nifi-redis-bundle/nifi-redis-extensions/src/test/java/org/apache/nifi/redis/testcontainers/RedisSentinelContainer.java
new file mode 100644
index 0000000000..06b802dc3b
--- /dev/null
+++
b/nifi-nar-bundles/nifi-redis-bundle/nifi-redis-extensions/src/test/java/org/apache/nifi/redis/testcontainers/RedisSentinelContainer.java
@@ -0,0 +1,113 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.redis.testcontainers;
+
+import org.springframework.lang.NonNull;
+import org.springframework.lang.Nullable;
+import org.testcontainers.utility.DockerImageName;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+public class RedisSentinelContainer extends RedisContainer {
+
+ public static final int REDIS_SENTINEL_PORT = 26379;
+
+ public RedisSentinelContainer(final @NonNull DockerImageName
dockerImageName) {
+ super(dockerImageName);
+
+ setPort(REDIS_SENTINEL_PORT);
+ }
+
+ public RedisSentinelContainer(final @NonNull String fullImageName) {
+ this(DockerImageName.parse(fullImageName));
+ }
+
+ @NonNull
+ protected String masterHost = "localhost";
+ protected int masterPort = REDIS_PORT;
+ @NonNull
+ protected String masterName = "mymaster";
+ @Nullable
+ protected String sentinelPassword = null;
+ private long downAfterMilliseconds = 60000L;
+ private long failoverTimeout = 180000L;
+ private int parallelSyncs = 1;
+ private int quorumSize = 1;
+
+ public void setMasterHost(final @NonNull String masterHost) {
+ this.masterHost = masterHost;
+ }
+
+ public void setMasterPort(final int masterPort) {
+ this.masterPort = masterPort;
+ }
+
+ public void setMasterName(final @NonNull String masterName) {
+ this.masterName = masterName;
+ }
+
+ public void setSentinelPassword(final @Nullable String sentinelPassword) {
+ this.sentinelPassword = sentinelPassword;
+ }
+
+ public void setQuorumSize(final int quorumSize) {
+ this.quorumSize = quorumSize;
+ }
+
+ public void setDownAfterMilliseconds(final long downAfterMilliseconds) {
+ this.downAfterMilliseconds = downAfterMilliseconds;
+ }
+
+ public void setFailoverTimeout(final long failoverTimeout) {
+ this.failoverTimeout = failoverTimeout;
+ }
+
+ public void setParallelSyncs(final int parallelSyncs) {
+ this.parallelSyncs = parallelSyncs;
+ }
+
+
+ @Override
+ protected void adjustConfiguration() {
+ addConfigurationOption("port " + port);
+
+ addConfigurationOption(String.format("sentinel monitor %s %s %d %d",
masterName, masterHost, masterPort, quorumSize));
+ addConfigurationOption(String.format("sentinel down-after-milliseconds
%s %d", masterName, downAfterMilliseconds));
+ addConfigurationOption(String.format("sentinel failover-timeout %s
%d", masterName, failoverTimeout));
+ addConfigurationOption(String.format("sentinel parallel-syncs %s %d",
masterName, parallelSyncs));
+
+ if (password != null) {
+ addConfigurationOption("sentinel auth-pass " + masterName + " " +
password);
+ }
+ if (sentinelPassword != null) {
+ addConfigurationOption("requirepass " + sentinelPassword);
+ addConfigurationOption("sentinel sentinel-pass " +
sentinelPassword);
+ }
+ }
+
+ @Override
+ protected void configure() {
+ super.configure();
+
+
+ List<String> commandParts = new
ArrayList<>(Arrays.asList(getCommandParts()));
+ commandParts.add("--sentinel");
+ setCommandParts(commandParts.toArray(new String[0]));
+ }
+}