From 31322d2b3cbb5980afaee807a6f8b229bdedf6ae Mon Sep 17 00:00:00 2001 From: Jared Petersen Date: Wed, 14 Jul 2021 21:00:39 -0700 Subject: [PATCH 01/10] updated confluent hub archive to use the new naming convention, fixes #21 --- pom.xml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/pom.xml b/pom.xml index 5b8affc..ee66cdf 100644 --- a/pom.xml +++ b/pom.xml @@ -8,8 +8,8 @@ 1.2.0 jar - kafka-connect-redis - Kafka Connect source and sink connector for Redis + Kafka Redis Connector (Sink and Source) + Kafka sink and source connector for Redis https://github.com/jaredpetersen/kafka-connect-redis @@ -218,7 +218,8 @@ kafka-connect - Kafka Connect Redis + redis-connector + Redis Connector (Sink and Source) ${project.url}/blob/main/README.md docs/logos/jaredpetersen-logo.png jaredpetersen @@ -252,7 +253,7 @@ jar-with-dependencies - ${project.name}-${project.version} + ${project.artifactId}-${project.version} false From 0eda37edc0cec087d45cf88093d3350ce182a445 Mon Sep 17 00:00:00 2001 From: Jared Petersen Date: Wed, 14 Jul 2021 21:38:01 -0700 Subject: [PATCH 02/10] added changelog to confluent hub archive, fixes #20 --- pom.xml | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index ee66cdf..01dfa1e 100644 --- a/pom.xml +++ b/pom.xml @@ -220,7 +220,7 @@ redis-connector Redis Connector (Sink and Source) - ${project.url}/blob/main/README.md + ${project.url} docs/logos/jaredpetersen-logo.png jaredpetersen user @@ -229,6 +229,17 @@ Open Source Community Support provided through community involvement. ${project.issueManagement.url} + + + ${project.basedir} + doc + + README.md + LICENSE.md + CHANGELOG.md + + + true source From 6a259b8deefb86057960bcd5dbf337689f2e0172 Mon Sep 17 00:00:00 2001 From: Jared Petersen Date: Wed, 14 Jul 2021 21:43:04 -0700 Subject: [PATCH 03/10] upgraded lettuce --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 01dfa1e..2cdae36 100644 --- a/pom.xml +++ b/pom.xml @@ -69,7 +69,7 @@ io.lettuce lettuce-core - 6.0.2.RELEASE + 6.1.4.RELEASE From 85ee641f4d21aaee060afb2acea147de87834548 Mon Sep 17 00:00:00 2001 From: Jared Petersen Date: Thu, 15 Jul 2021 00:43:44 -0700 Subject: [PATCH 04/10] rewrite sink to stop using project reactor --- pom.xml | 1 + .../kafkaconnectredis/sink/RedisSinkTask.java | 39 +- .../sink/writer/RecordConverter.java | 211 ++++--- .../kafkaconnectredis/sink/writer/Writer.java | 222 +++---- .../sink/RedisSinkTaskIT.java | 45 +- .../sink/writer/WriterIT.java | 557 ++++++------------ .../source/RedisSourceTaskIT.java | 6 +- .../subscriber/RedisChannelSubscriberIT.java | 6 +- .../RedisClusterChannelSubscriberIT.java | 6 +- .../RedisClusterPatternSubscriberIT.java | 6 +- .../subscriber/RedisPatternSubscriberIT.java | 6 +- .../sink/writer/RecordConverterTest.java | 54 +- 12 files changed, 473 insertions(+), 686 deletions(-) diff --git a/pom.xml b/pom.xml index 2cdae36..6445c17 100644 --- a/pom.xml +++ b/pom.xml @@ -72,6 +72,7 @@ 6.1.4.RELEASE + io.projectreactor reactor-core diff --git a/src/main/java/io/github/jaredpetersen/kafkaconnectredis/sink/RedisSinkTask.java b/src/main/java/io/github/jaredpetersen/kafkaconnectredis/sink/RedisSinkTask.java index 52c17e1..bb5c923 100644 --- a/src/main/java/io/github/jaredpetersen/kafkaconnectredis/sink/RedisSinkTask.java +++ b/src/main/java/io/github/jaredpetersen/kafkaconnectredis/sink/RedisSinkTask.java @@ -3,15 +3,16 @@ import io.github.jaredpetersen.kafkaconnectredis.sink.config.RedisSinkConfig; import io.github.jaredpetersen.kafkaconnectredis.sink.writer.RecordConverter; import io.github.jaredpetersen.kafkaconnectredis.sink.writer.Writer; +import io.github.jaredpetersen.kafkaconnectredis.sink.writer.record.RedisCommand; import io.github.jaredpetersen.kafkaconnectredis.util.VersionUtil; import io.lettuce.core.RedisClient; import io.lettuce.core.api.StatefulRedisConnection; -import io.lettuce.core.api.reactive.RedisReactiveCommands; +import io.lettuce.core.api.sync.RedisCommands; 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.reactive.RedisClusterReactiveCommands; +import io.lettuce.core.cluster.api.sync.RedisClusterCommands; import java.util.Collection; import java.util.Map; import org.apache.kafka.common.config.ConfigException; @@ -20,7 +21,6 @@ import org.apache.kafka.connect.sink.SinkTask; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import reactor.core.publisher.Flux; /** * Kafka Connect Task for Kafka Connect Redis Sink. @@ -67,14 +67,14 @@ public void start(final Map props) { this.redisClusterConnection = this.redisClusterClient.connect(); - final RedisClusterReactiveCommands redisClusterCommands = this.redisClusterConnection.reactive(); + final RedisClusterCommands redisClusterCommands = this.redisClusterConnection.sync(); this.writer = new Writer(redisClusterCommands); } else { this.redisStandaloneClient = RedisClient.create(config.getRedisUri()); this.redisStandaloneConnection = this.redisStandaloneClient.connect(); - final RedisReactiveCommands redisStandaloneCommands = this.redisStandaloneConnection.reactive(); + final RedisCommands redisStandaloneCommands = this.redisStandaloneConnection.sync(); this.writer = new Writer(redisStandaloneCommands); } } @@ -88,14 +88,27 @@ public void put(final Collection records) { LOG.info("writing {} record(s) to redis", records.size()); LOG.debug("records: {}", records); - Flux - .fromIterable(records) - .flatMapSequential(RECORD_CONVERTER::convert) - .onErrorMap(error -> new ConnectException("failed to convert record", error)) - .flatMapSequential(redisCommand -> this.writer.write(redisCommand)) - .onErrorMap(error -> new ConnectException("failed to write record", error)) - .then() - .block(); + for (SinkRecord record : records) { + put(record); + } + } + + private void put(SinkRecord record) { + final RedisCommand redisCommand; + + try { + redisCommand = RECORD_CONVERTER.convert(record); + } + catch (Exception exception) { + throw new ConnectException("failed to convert record", exception); + } + + try { + writer.write(redisCommand); + } + catch (Exception exception) { + throw new ConnectException("failed to write record", exception); + } } @Override diff --git a/src/main/java/io/github/jaredpetersen/kafkaconnectredis/sink/writer/RecordConverter.java b/src/main/java/io/github/jaredpetersen/kafkaconnectredis/sink/writer/RecordConverter.java index ca17e95..36961bb 100644 --- a/src/main/java/io/github/jaredpetersen/kafkaconnectredis/sink/writer/RecordConverter.java +++ b/src/main/java/io/github/jaredpetersen/kafkaconnectredis/sink/writer/RecordConverter.java @@ -8,13 +8,13 @@ import io.github.jaredpetersen.kafkaconnectredis.sink.writer.record.RedisPexpireCommand; import io.github.jaredpetersen.kafkaconnectredis.sink.writer.record.RedisSaddCommand; import io.github.jaredpetersen.kafkaconnectredis.sink.writer.record.RedisSetCommand; +import java.util.ArrayList; +import java.util.List; import org.apache.kafka.connect.data.Struct; import org.apache.kafka.connect.errors.ConnectException; import org.apache.kafka.connect.sink.SinkRecord; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; public class RecordConverter { private static final Logger LOG = LoggerFactory.getLogger(RecordConverter.class); @@ -25,158 +25,147 @@ public class RecordConverter { * @param sinkRecord Record to convert. * @return Redis command. */ - public Mono convert(SinkRecord sinkRecord) { + public RedisCommand convert(SinkRecord sinkRecord) { LOG.debug("converting record {}", sinkRecord); final Struct recordValue = (Struct) sinkRecord.value(); final String recordValueSchemaName = recordValue.schema().name(); - final Mono redisCommandMono; + final RedisCommand redisCommand; switch (recordValueSchemaName) { case "io.github.jaredpetersen.kafkaconnectredis.RedisSetCommand": - redisCommandMono = convertSet(recordValue); + redisCommand = convertSet(recordValue); break; case "io.github.jaredpetersen.kafkaconnectredis.RedisExpireCommand": - redisCommandMono = convertExpire(recordValue); + redisCommand = convertExpire(recordValue); break; case "io.github.jaredpetersen.kafkaconnectredis.RedisExpireatCommand": - redisCommandMono = convertExpireat(recordValue); + redisCommand = convertExpireat(recordValue); break; case "io.github.jaredpetersen.kafkaconnectredis.RedisPexpireCommand": - redisCommandMono = convertPexpire(recordValue); + redisCommand = convertPexpire(recordValue); break; case "io.github.jaredpetersen.kafkaconnectredis.RedisSaddCommand": - redisCommandMono = convertSadd(recordValue); + redisCommand = convertSadd(recordValue); break; case "io.github.jaredpetersen.kafkaconnectredis.RedisGeoaddCommand": - redisCommandMono = convertGeoadd(recordValue); + redisCommand = convertGeoadd(recordValue); break; case "io.github.jaredpetersen.kafkaconnectredis.RedisArbitraryCommand": - redisCommandMono = convertArbitrary(recordValue); + redisCommand = convertArbitrary(recordValue); break; default: - redisCommandMono = Mono.error(new ConnectException("unsupported command schema " + recordValueSchemaName)); + throw new ConnectException("unsupported command schema " + recordValueSchemaName); } - return redisCommandMono; + return redisCommand; } - private Mono convertSet(Struct value) { - return Mono.fromCallable(() -> { - final Struct expirationStruct = value.getStruct("expiration"); - final RedisSetCommand.Payload.Expiration expiration = (expirationStruct == null) - ? null - : RedisSetCommand.Payload.Expiration.builder() - .type(RedisSetCommand.Payload.Expiration.Type - .valueOf(expirationStruct.getString("type"))) - .time(expirationStruct.getInt64("time")) - .build(); - - final String conditionString = value.getString("condition"); - final RedisSetCommand.Payload.Condition condition = (conditionString == null) - ? null - : RedisSetCommand.Payload.Condition.valueOf(conditionString.toUpperCase()); - - final RedisSetCommand.Payload payload = RedisSetCommand.Payload.builder() - .key(value.getString("key")) - .value(value.getString("value")) - .expiration(expiration) - .condition(condition) - .build(); + private RedisCommand convertSet(Struct value) { + final Struct expirationStruct = value.getStruct("expiration"); + final RedisSetCommand.Payload.Expiration expiration = (expirationStruct == null) + ? null + : RedisSetCommand.Payload.Expiration.builder() + .type(RedisSetCommand.Payload.Expiration.Type + .valueOf(expirationStruct.getString("type"))) + .time(expirationStruct.getInt64("time")) + .build(); + + final String conditionString = value.getString("condition"); + final RedisSetCommand.Payload.Condition condition = (conditionString == null) + ? null + : RedisSetCommand.Payload.Condition.valueOf(conditionString.toUpperCase()); + + final RedisSetCommand.Payload payload = RedisSetCommand.Payload.builder() + .key(value.getString("key")) + .value(value.getString("value")) + .expiration(expiration) + .condition(condition) + .build(); + + return RedisSetCommand.builder() + .payload(payload) + .build(); + } - return RedisSetCommand.builder() - .payload(payload) - .build(); - }); + private RedisCommand convertExpire(Struct value) { + final RedisExpireCommand.Payload payload = RedisExpireCommand.Payload.builder() + .key(value.getString("key")) + .seconds(value.getInt64("seconds")) + .build(); + + return RedisExpireCommand.builder() + .payload(payload) + .build(); } - private Mono convertExpire(Struct value) { - return Mono.fromCallable(() -> { - final RedisExpireCommand.Payload payload = RedisExpireCommand.Payload.builder() - .key(value.getString("key")) - .seconds(value.getInt64("seconds")) - .build(); + private RedisCommand convertExpireat(Struct value) { + final RedisExpireatCommand.Payload payload = RedisExpireatCommand.Payload.builder() + .key(value.getString("key")) + .timestamp(value.getInt64("timestamp")) + .build(); - return RedisExpireCommand.builder() - .payload(payload) - .build(); - }); + return RedisExpireatCommand.builder() + .payload(payload) + .build(); } - private Mono convertExpireat(Struct value) { - return Mono.fromCallable(() -> { - final RedisExpireatCommand.Payload payload = RedisExpireatCommand.Payload.builder() - .key(value.getString("key")) - .timestamp(value.getInt64("timestamp")) - .build(); + private RedisCommand convertPexpire(Struct value) { + final RedisPexpireCommand.Payload payload = RedisPexpireCommand.Payload.builder() + .key(value.getString("key")) + .milliseconds(value.getInt64("milliseconds")) + .build(); - return RedisExpireatCommand.builder() - .payload(payload) - .build(); - }); + return RedisPexpireCommand.builder() + .payload(payload) + .build(); } - private Mono convertPexpire(Struct value) { - return Mono.fromCallable(() -> { - final RedisPexpireCommand.Payload payload = RedisPexpireCommand.Payload.builder() - .key(value.getString("key")) - .milliseconds(value.getInt64("milliseconds")) - .build(); + private RedisCommand convertSadd(Struct value) { + final RedisSaddCommand.Payload payload = RedisSaddCommand.Payload.builder() + .key(value.getString("key")) + .values(value.getArray("values")) + .build(); - return RedisPexpireCommand.builder() - .payload(payload) - .build(); - }); + return RedisSaddCommand.builder() + .payload(payload) + .build(); } - private Mono convertSadd(Struct value) { - return Mono.fromCallable(() -> { - final RedisSaddCommand.Payload payload = RedisSaddCommand.Payload.builder() - .key(value.getString("key")) - .values(value.getArray("values")) - .build(); + private RedisCommand convertGeoadd(Struct value) { + final List geoLocations = new ArrayList<>(); + + for (Object rawGeoLocation : value.getArray("values")) { + final Struct rawGeolocationStruct = (Struct) rawGeoLocation; - return RedisSaddCommand.builder() - .payload(payload) + final RedisGeoaddCommand.Payload.GeoLocation geoLocation = RedisGeoaddCommand.Payload.GeoLocation.builder() + .latitude(rawGeolocationStruct.getFloat64("latitude")) + .longitude(rawGeolocationStruct.getFloat64("longitude")) + .member(rawGeolocationStruct.getString("member")) .build(); - }); - } - private Mono convertGeoadd(Struct value) { - return Flux - .fromIterable(value.getArray("values")) - .flatMap(rawGeolocation -> Mono.fromCallable(() -> { - final Struct rawGeolocationStruct = (Struct) rawGeolocation; - return RedisGeoaddCommand.Payload.GeoLocation.builder() - .latitude(rawGeolocationStruct.getFloat64("latitude")) - .longitude(rawGeolocationStruct.getFloat64("longitude")) - .member(rawGeolocationStruct.getString("member")) - .build(); - })) - .collectList() - .flatMap(geolocations -> Mono.fromCallable(() -> { - final RedisGeoaddCommand.Payload payload = RedisGeoaddCommand.Payload.builder() - .key(value.getString("key")) - .values(geolocations) - .build(); - - return RedisGeoaddCommand.builder() - .payload(payload) - .build(); - })); + geoLocations.add(geoLocation); + } + + final RedisGeoaddCommand.Payload payload = RedisGeoaddCommand.Payload.builder() + .key(value.getString("key")) + .values(geoLocations) + .build(); + + return RedisGeoaddCommand.builder() + .payload(payload) + .build(); } - private Mono convertArbitrary(Struct value) { - return Mono.fromCallable(() -> { - final RedisArbitraryCommand.Payload payload = RedisArbitraryCommand.Payload.builder() - .command(value.getString("command")) - .arguments(value.getArray("arguments")) - .build(); + private RedisCommand convertArbitrary(Struct value) { + final RedisArbitraryCommand.Payload payload = RedisArbitraryCommand.Payload.builder() + .command(value.getString("command")) + .arguments(value.getArray("arguments")) + .build(); - return RedisArbitraryCommand.builder() - .payload(payload) - .build(); - }); + return RedisArbitraryCommand.builder() + .payload(payload) + .build(); } } diff --git a/src/main/java/io/github/jaredpetersen/kafkaconnectredis/sink/writer/Writer.java b/src/main/java/io/github/jaredpetersen/kafkaconnectredis/sink/writer/Writer.java index 4aa2f9e..21a0a51 100644 --- a/src/main/java/io/github/jaredpetersen/kafkaconnectredis/sink/writer/Writer.java +++ b/src/main/java/io/github/jaredpetersen/kafkaconnectredis/sink/writer/Writer.java @@ -9,24 +9,23 @@ import io.github.jaredpetersen.kafkaconnectredis.sink.writer.record.RedisSaddCommand; import io.github.jaredpetersen.kafkaconnectredis.sink.writer.record.RedisSetCommand; import io.lettuce.core.SetArgs; -import io.lettuce.core.api.reactive.RedisReactiveCommands; -import io.lettuce.core.cluster.api.reactive.RedisClusterReactiveCommands; +import io.lettuce.core.api.sync.RedisCommands; +import io.lettuce.core.cluster.api.sync.RedisClusterCommands; import io.lettuce.core.codec.StringCodec; import io.lettuce.core.output.CommandOutput; import io.lettuce.core.output.VoidOutput; import io.lettuce.core.protocol.CommandArgs; import io.lettuce.core.protocol.ProtocolKeyword; import java.nio.charset.StandardCharsets; -import java.util.Arrays; +import java.util.ArrayList; +import java.util.List; import org.apache.kafka.connect.errors.ConnectException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; public class Writer { - private final RedisReactiveCommands redisStandaloneCommands; - private final RedisClusterReactiveCommands redisClusterCommands; + private final RedisCommands redisStandaloneCommands; + private final RedisClusterCommands redisClusterCommands; private final boolean clusterEnabled; private static final Logger LOG = LoggerFactory.getLogger(Writer.class); @@ -34,9 +33,9 @@ public class Writer { /** * Set up writer to interact with standalone Redis. * - * @param redisStandaloneCommands Standalone Redis to write to. + * @param redisStandaloneCommands Standalone Redis to write to */ - public Writer(RedisReactiveCommands redisStandaloneCommands) { + public Writer(RedisCommands redisStandaloneCommands) { this.redisStandaloneCommands = redisStandaloneCommands; this.redisClusterCommands = null; this.clusterEnabled = false; @@ -45,9 +44,9 @@ public Writer(RedisReactiveCommands redisStandaloneCommands) { /** * Set up writer to interact with Redis cluster. * - * @param redisClusterCommands Redis cluster to write to. + * @param redisClusterCommands Redis cluster to write to */ - public Writer(RedisClusterReactiveCommands redisClusterCommands) { + public Writer(RedisClusterCommands redisClusterCommands) { this.redisStandaloneCommands = null; this.redisClusterCommands = redisClusterCommands; this.clusterEnabled = true; @@ -56,145 +55,145 @@ public Writer(RedisClusterReactiveCommands redisClusterCommands) /** * Apply write-type command to Redis. * - * @param redisCommand Command to apply. - * @return Mono used to indicate the write has completed. + * @param redisCommand Command to apply */ - public Mono write(RedisCommand redisCommand) { + public void write(RedisCommand redisCommand) { LOG.debug("writing {}", redisCommand); - final Mono response; - switch (redisCommand.getCommand()) { case SET: - response = set((RedisSetCommand) redisCommand); + set((RedisSetCommand) redisCommand); break; case EXPIRE: - response = expire((RedisExpireCommand) redisCommand); + expire((RedisExpireCommand) redisCommand); break; case EXPIREAT: - response = expireat((RedisExpireatCommand) redisCommand); + expireat((RedisExpireatCommand) redisCommand); break; case PEXPIRE: - response = pexpire((RedisPexpireCommand) redisCommand); + pexpire((RedisPexpireCommand) redisCommand); break; case SADD: - response = sadd((RedisSaddCommand) redisCommand); + sadd((RedisSaddCommand) redisCommand); break; case GEOADD: - response = geoadd((RedisGeoaddCommand) redisCommand); + geoadd((RedisGeoaddCommand) redisCommand); break; case ARBITRARY: - response = arbitrary((RedisArbitraryCommand) redisCommand); + arbitrary((RedisArbitraryCommand) redisCommand); break; default: - response = Mono.error(new ConnectException("redis command " + redisCommand + " is not supported")); + throw new ConnectException("redis command " + redisCommand + " is not supported"); } - - return response; } - private Mono set(RedisSetCommand setCommand) { + private void set(RedisSetCommand setCommand) { final RedisSetCommand.Payload payload = setCommand.getPayload(); - final Mono setArgsMono = Mono - .fromCallable(() -> { - final SetArgs setArgs = new SetArgs(); - - if (payload.getExpiration() != null) { - final RedisSetCommand.Payload.Expiration expiration = payload.getExpiration(); - - if (expiration.getType() == RedisSetCommand.Payload.Expiration.Type.EX) { - setArgs.ex(expiration.getTime()); - } - else if (expiration.getType() == RedisSetCommand.Payload.Expiration.Type.PX) { - setArgs.px(expiration.getTime()); - } - else if (expiration.getType() == RedisSetCommand.Payload.Expiration.Type.KEEPTTL) { - setArgs.keepttl(); - } - } - - if (payload.getCondition() != null) { - if (payload.getCondition() == RedisSetCommand.Payload.Condition.NX) { - setArgs.nx(); - } - else if (payload.getCondition() == RedisSetCommand.Payload.Condition.XX) { - setArgs.xx(); - } - } - - return setArgs; - }); - final Mono setResult = setArgsMono - .flatMap(setArgs -> - (this.clusterEnabled) - ? this.redisClusterCommands.set(payload.getKey(), payload.getValue(), setArgs) - : this.redisStandaloneCommands.set(payload.getKey(), payload.getValue(), setArgs)); - - return setResult.then(); + final SetArgs setArgs = new SetArgs(); + + if (payload.getExpiration() != null) { + final RedisSetCommand.Payload.Expiration expiration = payload.getExpiration(); + + if (expiration.getType() == RedisSetCommand.Payload.Expiration.Type.EX) { + setArgs.ex(expiration.getTime()); + } + else if (expiration.getType() == RedisSetCommand.Payload.Expiration.Type.PX) { + setArgs.px(expiration.getTime()); + } + else if (expiration.getType() == RedisSetCommand.Payload.Expiration.Type.KEEPTTL) { + setArgs.keepttl(); + } + } + + if (payload.getCondition() != null) { + if (payload.getCondition() == RedisSetCommand.Payload.Condition.NX) { + setArgs.nx(); + } + else if (payload.getCondition() == RedisSetCommand.Payload.Condition.XX) { + setArgs.xx(); + } + } + + if (clusterEnabled) { + redisClusterCommands.set(payload.getKey(), payload.getValue(), setArgs); + } + else { + redisStandaloneCommands.set(payload.getKey(), payload.getValue(), setArgs); + } } - private Mono expire(RedisExpireCommand expireCommand) { + private void expire(RedisExpireCommand expireCommand) { final RedisExpireCommand.Payload payload = expireCommand.getPayload(); - final Mono expirationResult = (this.clusterEnabled) - ? this.redisClusterCommands.expire(payload.getKey(), payload.getSeconds()) - : this.redisStandaloneCommands.expire(payload.getKey(), payload.getSeconds()); - return expirationResult.then(); + if (clusterEnabled) { + redisClusterCommands.expire(payload.getKey(), payload.getSeconds()); + } + else { + redisStandaloneCommands.expire(payload.getKey(), payload.getSeconds()); + } } - private Mono expireat(RedisExpireatCommand expireAtCommand) { + private void expireat(RedisExpireatCommand expireAtCommand) { final RedisExpireatCommand.Payload payload = expireAtCommand.getPayload(); - final Mono expirationResult = (this.clusterEnabled) - ? this.redisClusterCommands.expireat(payload.getKey(), payload.getTimestamp()) - : this.redisStandaloneCommands.expireat(payload.getKey(), payload.getTimestamp()); - return expirationResult.then(); + if (clusterEnabled) { + redisClusterCommands.expireat(payload.getKey(), payload.getTimestamp()); + } + else { + redisStandaloneCommands.expireat(payload.getKey(), payload.getTimestamp()); + } } - private Mono pexpire(RedisPexpireCommand pexpireCommand) { + private void pexpire(RedisPexpireCommand pexpireCommand) { final RedisPexpireCommand.Payload payload = pexpireCommand.getPayload(); - final Mono expirationResult = (this.clusterEnabled) - ? this.redisClusterCommands.pexpire(payload.getKey(), payload.getMilliseconds()) - : this.redisStandaloneCommands.pexpire(payload.getKey(), payload.getMilliseconds()); - return expirationResult.then(); + if (clusterEnabled) { + redisClusterCommands.pexpire(payload.getKey(), payload.getMilliseconds()); + } + else { + redisStandaloneCommands.pexpire(payload.getKey(), payload.getMilliseconds()); + } } - private Mono sadd(RedisSaddCommand saddCommand) { + private void sadd(RedisSaddCommand saddCommand) { final RedisSaddCommand.Payload payload = saddCommand.getPayload(); final String[] members = payload.getValues().toArray(new String[0]); - final Mono saddResult = (this.clusterEnabled) - ? this.redisClusterCommands.sadd(payload.getKey(), members) - : this.redisStandaloneCommands.sadd(payload.getKey(), members); - return saddResult.then(); + if (clusterEnabled) { + redisClusterCommands.sadd(payload.getKey(), members); + } + else { + redisStandaloneCommands.sadd(payload.getKey(), members); + } } - private Mono geoadd(RedisGeoaddCommand geoaddCommand) { + private void geoadd(RedisGeoaddCommand geoaddCommand) { final RedisGeoaddCommand.Payload payload = geoaddCommand.getPayload(); - final Flux geoLocationFlux = Flux - .fromIterable(payload.getValues()) - .flatMapIterable(geoLocation -> { - if (geoLocation.getMember() == null) { - throw new ConnectException("geoadd command does not contain member"); - } - return Arrays.asList(geoLocation.getLongitude(), geoLocation.getLatitude(), geoLocation.getMember()); - }); - final Mono geoaddResult = geoLocationFlux - .collectList() - .flatMap(geoLocationList -> { - final String key = payload.getKey(); - final Object[] longitudeLatitudeMembers = geoLocationList.toArray(new Object[0]); - return (this.clusterEnabled) - ? this.redisClusterCommands.geoadd(key, longitudeLatitudeMembers) - : this.redisStandaloneCommands.geoadd(key, longitudeLatitudeMembers); - }); - - return geoaddResult.then(); + final String key = payload.getKey(); + final List geoLocationList = new ArrayList<>(); + + for (RedisGeoaddCommand.Payload.GeoLocation geoLocation : payload.getValues()) { + if (geoLocation.getMember() == null) { + throw new ConnectException("geoadd command does not contain member"); + } + + geoLocationList.add(geoLocation.getLongitude()); + geoLocationList.add(geoLocation.getLatitude()); + geoLocationList.add(geoLocation.getMember()); + } + + final Object[] longitudeLatitudeMembers = geoLocationList.toArray(new Object[0]); + + if (clusterEnabled) { + redisClusterCommands.geoadd(key, longitudeLatitudeMembers); + } + else { + redisStandaloneCommands.geoadd(key, longitudeLatitudeMembers); + } } - private Mono arbitrary(RedisArbitraryCommand arbitraryCommand) { - // Set up arbitrary command + private void arbitrary(RedisArbitraryCommand arbitraryCommand) { + // Generate arbitrary command final ProtocolKeyword protocolKeyword = new ProtocolKeyword() { public final byte[] bytes = name().getBytes(StandardCharsets.US_ASCII); @@ -208,12 +207,19 @@ public String name() { return arbitraryCommand.getPayload().getCommand(); } }; + + // Ignore output except for errors final CommandOutput commandOutput = new VoidOutput<>(); + + // Provide arguments to the command final CommandArgs commandArgs = new CommandArgs<>(StringCodec.UTF8) .addValues(arbitraryCommand.getPayload().getArguments()); - return (this.clusterEnabled) - ? this.redisClusterCommands.dispatch(protocolKeyword, commandOutput, commandArgs).then() - : this.redisStandaloneCommands.dispatch(protocolKeyword, commandOutput, commandArgs).then(); + if (clusterEnabled) { + redisClusterCommands.dispatch(protocolKeyword, commandOutput, commandArgs); + } + else { + redisStandaloneCommands.dispatch(protocolKeyword, commandOutput, commandArgs); + } } } diff --git a/src/test/integration/java/io/github/jaredpetersen/kafkaconnectredis/sink/RedisSinkTaskIT.java b/src/test/integration/java/io/github/jaredpetersen/kafkaconnectredis/sink/RedisSinkTaskIT.java index 9d23c4c..c83d3b1 100644 --- a/src/test/integration/java/io/github/jaredpetersen/kafkaconnectredis/sink/RedisSinkTaskIT.java +++ b/src/test/integration/java/io/github/jaredpetersen/kafkaconnectredis/sink/RedisSinkTaskIT.java @@ -4,10 +4,10 @@ import io.github.jaredpetersen.kafkaconnectredis.util.VersionUtil; import io.lettuce.core.RedisClient; import io.lettuce.core.api.StatefulRedisConnection; -import io.lettuce.core.api.reactive.RedisReactiveCommands; +import io.lettuce.core.api.sync.RedisCommands; import io.lettuce.core.cluster.RedisClusterClient; import io.lettuce.core.cluster.api.StatefulRedisClusterConnection; -import io.lettuce.core.cluster.api.reactive.RedisClusterReactiveCommands; +import io.lettuce.core.cluster.api.sync.RedisClusterCommands; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; @@ -24,7 +24,6 @@ import org.junit.jupiter.api.Test; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; -import reactor.test.StepVerifier; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -40,12 +39,12 @@ public class RedisSinkTaskIT { private static String REDIS_STANDALONE_URI; private static RedisClient REDIS_STANDALONE_CLIENT; private static StatefulRedisConnection REDIS_STANDALONE_CONNECTION; - private static RedisReactiveCommands REDIS_STANDALONE_COMMANDS; + private static RedisCommands REDIS_STANDALONE_COMMANDS; private static String REDIS_CLUSTER_URI; private static RedisClusterClient REDIS_CLUSTER_CLIENT; private static StatefulRedisClusterConnection REDIS_CLUSTER_CONNECTION; - private static RedisClusterReactiveCommands REDIS_CLUSTER_COMMANDS; + private static RedisClusterCommands REDIS_CLUSTER_COMMANDS; private static final Schema REDIS_SET_COMMAND_SCHEMA = SchemaBuilder.struct() .name("io.github.jaredpetersen.kafkaconnectredis.RedisSetCommand") @@ -60,26 +59,26 @@ public class RedisSinkTaskIT { .build(); @BeforeAll - static void setupAll() { + static void beforeAll() { REDIS_STANDALONE_URI = REDIS_STANDALONE.getUri(); REDIS_STANDALONE_CLIENT = RedisClient.create(REDIS_STANDALONE_URI); REDIS_STANDALONE_CONNECTION = REDIS_STANDALONE_CLIENT.connect(); - REDIS_STANDALONE_COMMANDS = REDIS_STANDALONE_CONNECTION.reactive(); + REDIS_STANDALONE_COMMANDS = REDIS_STANDALONE_CONNECTION.sync(); REDIS_CLUSTER_URI = REDIS_CLUSTER.getUri(); REDIS_CLUSTER_CLIENT = RedisClusterClient.create(REDIS_CLUSTER_URI); REDIS_CLUSTER_CONNECTION = REDIS_CLUSTER_CLIENT.connect(); - REDIS_CLUSTER_COMMANDS = REDIS_CLUSTER_CONNECTION.reactive(); + REDIS_CLUSTER_COMMANDS = REDIS_CLUSTER_CONNECTION.sync(); } @AfterEach - public void cleanupEach() { - REDIS_STANDALONE_COMMANDS.flushall().block(); - REDIS_CLUSTER_COMMANDS.flushall().block(); + public void afterEach() { + REDIS_STANDALONE_COMMANDS.flushall(); + REDIS_CLUSTER_COMMANDS.flushall(); } @AfterAll - static void cleanupAll() { + static void afterAll() { REDIS_STANDALONE_CONNECTION.close(); REDIS_STANDALONE_CLIENT.shutdown(); @@ -113,17 +112,14 @@ public void putRecordsAppliesCommandsToStandalone() { final long offset = 0L; final SinkRecord sinkRecord = new SinkRecord(topic, partition, keySchema, key, valueSchema, value, offset); - final List sinkRecords = Arrays.asList(sinkRecord); + final List sinkRecords = Collections.singletonList(sinkRecord); // Configure task and write records final RedisSinkTask sinkTask = new RedisSinkTask(); sinkTask.start(config); sinkTask.put(sinkRecords); - StepVerifier - .create(REDIS_STANDALONE_COMMANDS.get("{user.1}.username")) - .expectNext("jetpackmelon22") - .verifyComplete(); + assertEquals("jetpackmelon22", REDIS_STANDALONE_COMMANDS.get("{user.1}.username")); } @Test @@ -152,10 +148,7 @@ public void putRecordsAppliesCommandsToCluster() { sinkTask.start(config); sinkTask.put(sinkRecords); - StepVerifier - .create(REDIS_CLUSTER_COMMANDS.get("{user.1}.username")) - .expectNext("jetpackmelon22") - .verifyComplete(); + assertEquals("jetpackmelon22", REDIS_CLUSTER_COMMANDS.get("{user.1}.username")); } @Test @@ -173,10 +166,7 @@ public void putEmptyRecordsDoesNothingToStandalone() { sinkTask.start(config); sinkTask.put(sinkRecords); - StepVerifier - .create(REDIS_STANDALONE_COMMANDS.dbsize()) - .expectNext(0L) - .verifyComplete(); + assertEquals(0L, REDIS_STANDALONE_COMMANDS.dbsize()); } @Test @@ -194,10 +184,7 @@ public void putEmptyRecordsDoesNothingToCluster() { sinkTask.start(config); sinkTask.put(sinkRecords); - StepVerifier - .create(REDIS_CLUSTER_COMMANDS.dbsize()) - .expectNext(0L) - .verifyComplete(); + assertEquals(0L, REDIS_CLUSTER_COMMANDS.dbsize()); } @Test diff --git a/src/test/integration/java/io/github/jaredpetersen/kafkaconnectredis/sink/writer/WriterIT.java b/src/test/integration/java/io/github/jaredpetersen/kafkaconnectredis/sink/writer/WriterIT.java index e0225b3..d30a474 100644 --- a/src/test/integration/java/io/github/jaredpetersen/kafkaconnectredis/sink/writer/WriterIT.java +++ b/src/test/integration/java/io/github/jaredpetersen/kafkaconnectredis/sink/writer/WriterIT.java @@ -8,15 +8,19 @@ import io.github.jaredpetersen.kafkaconnectredis.sink.writer.record.RedisSaddCommand; import io.github.jaredpetersen.kafkaconnectredis.sink.writer.record.RedisSetCommand; import io.github.jaredpetersen.kafkaconnectredis.testutil.RedisContainer; +import io.lettuce.core.GeoCoordinates; import io.lettuce.core.RedisClient; import io.lettuce.core.SetArgs; import io.lettuce.core.api.StatefulRedisConnection; -import io.lettuce.core.api.reactive.RedisReactiveCommands; +import io.lettuce.core.api.sync.RedisCommands; import io.lettuce.core.cluster.RedisClusterClient; import io.lettuce.core.cluster.api.StatefulRedisClusterConnection; -import io.lettuce.core.cluster.api.reactive.RedisClusterReactiveCommands; +import io.lettuce.core.cluster.api.sync.RedisClusterCommands; import java.time.Instant; +import java.util.ArrayList; import java.util.Arrays; +import java.util.HashSet; +import java.util.List; import org.apache.kafka.connect.errors.ConnectException; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; @@ -24,8 +28,10 @@ import org.junit.jupiter.api.Test; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; -import reactor.core.publisher.Mono; -import reactor.test.StepVerifier; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; @Testcontainers public class WriterIT { @@ -37,31 +43,31 @@ public class WriterIT { private static RedisClient REDIS_STANDALONE_CLIENT; private static StatefulRedisConnection REDIS_STANDALONE_CONNECTION; - private static RedisReactiveCommands REDIS_STANDALONE_COMMANDS; + private static RedisCommands REDIS_STANDALONE_COMMANDS; private static RedisClusterClient REDIS_CLUSTER_CLIENT; private static StatefulRedisClusterConnection REDIS_CLUSTER_CONNECTION; - private static RedisClusterReactiveCommands REDIS_CLUSTER_COMMANDS; + private static RedisClusterCommands REDIS_CLUSTER_COMMANDS; @BeforeAll - static void setupAll() { + static void beforeAll() { REDIS_STANDALONE_CLIENT = RedisClient.create(REDIS_STANDALONE.getUri()); REDIS_STANDALONE_CONNECTION = REDIS_STANDALONE_CLIENT.connect(); - REDIS_STANDALONE_COMMANDS = REDIS_STANDALONE_CONNECTION.reactive(); + REDIS_STANDALONE_COMMANDS = REDIS_STANDALONE_CONNECTION.sync(); REDIS_CLUSTER_CLIENT = RedisClusterClient.create(REDIS_CLUSTER.getUri()); REDIS_CLUSTER_CONNECTION = REDIS_CLUSTER_CLIENT.connect(); - REDIS_CLUSTER_COMMANDS = REDIS_CLUSTER_CONNECTION.reactive(); + REDIS_CLUSTER_COMMANDS = REDIS_CLUSTER_CONNECTION.sync(); } @AfterEach - public void cleanupEach() { - REDIS_STANDALONE_COMMANDS.flushall().block(); - REDIS_CLUSTER_COMMANDS.flushall().block(); + public void afterEach() { + REDIS_STANDALONE_COMMANDS.flushall(); + REDIS_CLUSTER_COMMANDS.flushall(); } @AfterAll - static void cleanupAll() { + static void afterAll() { REDIS_STANDALONE_CONNECTION.close(); REDIS_STANDALONE_CLIENT.shutdown(); @@ -69,6 +75,32 @@ static void cleanupAll() { REDIS_CLUSTER_CLIENT.shutdown(); } + /** + * Check if the provided TTL is around the expected value. Allows a drift of 2 seconds, i.e. actual TTL can be at + * most 2 seconds less than the expected TTL. + * + * @param ttl TTL (seconds) + * @param expectedTtl Expected TTL that the ttl should not exceed + * @return True if TTL is near the expected TTL, given a drift of 2 seconds + */ + static boolean isValidTtl(long ttl, long expectedTtl) { + long allowableDrift = 2L; + return (ttl <= expectedTtl) && (ttl >= (expectedTtl - allowableDrift)); + } + + /** + * Check if the provided PTTL is around the expected value. Allows a drift of 2,000 milliseconds, i.e. actual PTTL + * can be at most 2,000 milliseconds less than the expected PTTL. + * + * @param pttl PTTL (milliseconds) + * @param expectedPttl Expected TTL that the ttl should not exceed + * @return True if PTTL is near the expected PTTL, given a drift of 2,000 milliseconds + */ + static boolean isValidPttl(long pttl, long expectedPttl) { + long allowableDrift = 2_000L; + return (pttl <= expectedPttl) && (pttl >= (expectedPttl - allowableDrift)); + } + @Test public void writeSetCommandAppliesCommandToStandalone() { final RedisSetCommand redisCommand = RedisSetCommand.builder() @@ -79,16 +111,9 @@ public void writeSetCommandAppliesCommandToStandalone() { .build(); final Writer writer = new Writer(REDIS_STANDALONE_COMMANDS); - final Mono write = writer.write(redisCommand); - - StepVerifier - .create(write) - .verifyComplete(); + writer.write(redisCommand); - StepVerifier - .create(REDIS_STANDALONE_COMMANDS.get(redisCommand.getPayload().getKey())) - .expectNext("jetpackmelon22") - .verifyComplete(); + assertEquals("jetpackmelon22", REDIS_STANDALONE_COMMANDS.get(redisCommand.getPayload().getKey())); } @Test @@ -101,16 +126,9 @@ public void writeSetCommandAppliesCommandToCluster() { .build(); final Writer writer = new Writer(REDIS_CLUSTER_COMMANDS); - final Mono write = writer.write(redisCommand); + writer.write(redisCommand); - StepVerifier - .create(write) - .verifyComplete(); - - StepVerifier - .create(REDIS_CLUSTER_COMMANDS.get(redisCommand.getPayload().getKey())) - .expectNext("jetpackmelon22") - .verifyComplete(); + assertEquals("jetpackmelon22", REDIS_CLUSTER_COMMANDS.get(redisCommand.getPayload().getKey())); } @Test @@ -121,28 +139,19 @@ public void writeSetWithExpireSecondsCommandAppliesCommandToStandalone() { .value("jetpackmelon22") .expiration(RedisSetCommand.Payload.Expiration.builder() .type(RedisSetCommand.Payload.Expiration.Type.EX) - .time(2100L) + .time(21_000L) .build()) .condition(RedisSetCommand.Payload.Condition.NX) .build()) .build(); final Writer writer = new Writer(REDIS_STANDALONE_COMMANDS); - final Mono write = writer.write(redisCommand); - - StepVerifier - .create(write) - .verifyComplete(); - - StepVerifier - .create(REDIS_STANDALONE_COMMANDS.get(redisCommand.getPayload().getKey())) - .expectNext("jetpackmelon22") - .verifyComplete(); + writer.write(redisCommand); - StepVerifier - .create(REDIS_STANDALONE_COMMANDS.ttl(redisCommand.getPayload().getKey())) - .expectNextMatches(ttl -> ttl <= redisCommand.getPayload().getExpiration().getTime()) - .verifyComplete(); + assertEquals("jetpackmelon22", REDIS_STANDALONE_COMMANDS.get(redisCommand.getPayload().getKey())); + assertTrue(isValidTtl( + REDIS_STANDALONE_COMMANDS.ttl(redisCommand.getPayload().getKey()), + redisCommand.getPayload().getExpiration().getTime())); } @Test @@ -153,28 +162,19 @@ public void writeSetWithExpireSecondsCommandAppliesCommandToCluster() { .value("jetpackmelon22") .expiration(RedisSetCommand.Payload.Expiration.builder() .type(RedisSetCommand.Payload.Expiration.Type.EX) - .time(2100L) + .time(21_000L) .build()) .condition(RedisSetCommand.Payload.Condition.NX) .build()) .build(); final Writer writer = new Writer(REDIS_CLUSTER_COMMANDS); - final Mono write = writer.write(redisCommand); - - StepVerifier - .create(write) - .verifyComplete(); - - StepVerifier - .create(REDIS_CLUSTER_COMMANDS.get(redisCommand.getPayload().getKey())) - .expectNext("jetpackmelon22") - .verifyComplete(); + writer.write(redisCommand); - StepVerifier - .create(REDIS_CLUSTER_COMMANDS.ttl(redisCommand.getPayload().getKey())) - .expectNextMatches(ttl -> ttl <= redisCommand.getPayload().getExpiration().getTime()) - .verifyComplete(); + assertEquals("jetpackmelon22", REDIS_CLUSTER_COMMANDS.get(redisCommand.getPayload().getKey())); + assertTrue(isValidTtl( + REDIS_CLUSTER_COMMANDS.ttl(redisCommand.getPayload().getKey()), + redisCommand.getPayload().getExpiration().getTime())); } @Test @@ -185,28 +185,19 @@ public void writeSetWithExpireMillisecondsCommandAppliesCommandToStandalone() { .value("jetpackmelon22") .expiration(RedisSetCommand.Payload.Expiration.builder() .type(RedisSetCommand.Payload.Expiration.Type.PX) - .time(2100L) + .time(21_000L) .build()) .condition(RedisSetCommand.Payload.Condition.NX) .build()) .build(); final Writer writer = new Writer(REDIS_STANDALONE_COMMANDS); - final Mono write = writer.write(redisCommand); + writer.write(redisCommand); - StepVerifier - .create(write) - .verifyComplete(); - - StepVerifier - .create(REDIS_STANDALONE_COMMANDS.get(redisCommand.getPayload().getKey())) - .expectNext("jetpackmelon22") - .verifyComplete(); - - StepVerifier - .create(REDIS_STANDALONE_COMMANDS.pttl(redisCommand.getPayload().getKey())) - .expectNextMatches(pttl -> pttl <= redisCommand.getPayload().getExpiration().getTime()) - .verifyComplete(); + assertEquals("jetpackmelon22", REDIS_STANDALONE_COMMANDS.get(redisCommand.getPayload().getKey())); + assertTrue(isValidPttl( + REDIS_STANDALONE_COMMANDS.pttl(redisCommand.getPayload().getKey()), + redisCommand.getPayload().getExpiration().getTime())); } @Test @@ -217,35 +208,27 @@ public void writeSetWithExpireMillisecondsCommandAppliesCommandToCluster() { .value("jetpackmelon22") .expiration(RedisSetCommand.Payload.Expiration.builder() .type(RedisSetCommand.Payload.Expiration.Type.PX) - .time(2100L) + .time(21_000L) .build()) .condition(RedisSetCommand.Payload.Condition.NX) .build()) .build(); final Writer writer = new Writer(REDIS_CLUSTER_COMMANDS); - final Mono write = writer.write(redisCommand); - - StepVerifier - .create(write) - .verifyComplete(); + writer.write(redisCommand); - StepVerifier - .create(REDIS_CLUSTER_COMMANDS.get(redisCommand.getPayload().getKey())) - .expectNext("jetpackmelon22") - .verifyComplete(); - - StepVerifier - .create(REDIS_CLUSTER_COMMANDS.pttl(redisCommand.getPayload().getKey())) - .expectNextMatches(pttl -> pttl <= redisCommand.getPayload().getExpiration().getTime()) - .verifyComplete(); + assertEquals("jetpackmelon22", REDIS_CLUSTER_COMMANDS.get(redisCommand.getPayload().getKey())); + assertTrue(isValidPttl( + REDIS_CLUSTER_COMMANDS.pttl(redisCommand.getPayload().getKey()), + redisCommand.getPayload().getExpiration().getTime())); } @Test public void writeSetWithExpireKeepTtlCommandAppliesCommandToStandalone() { final String key = "{user.1}.username"; + final long originalPttl = 21_000L; - final Mono result = REDIS_STANDALONE_COMMANDS.set(key, "artistjanitor90", new SetArgs().px(2100)); + REDIS_STANDALONE_COMMANDS.set(key, "artistjanitor90", new SetArgs().px(originalPttl)); final RedisSetCommand redisCommand = RedisSetCommand.builder() .payload(RedisSetCommand.Payload.builder() @@ -258,33 +241,18 @@ public void writeSetWithExpireKeepTtlCommandAppliesCommandToStandalone() { .build(); final Writer writer = new Writer(REDIS_STANDALONE_COMMANDS); - final Mono write = writer.write(redisCommand); - - StepVerifier - .create(result) - .expectNext("OK") - .verifyComplete(); - - StepVerifier - .create(write) - .verifyComplete(); - - StepVerifier - .create(REDIS_STANDALONE_COMMANDS.get(redisCommand.getPayload().getKey())) - .expectNext("jetpackmelon22") - .verifyComplete(); - - StepVerifier - .create(REDIS_STANDALONE_COMMANDS.pttl(redisCommand.getPayload().getKey())) - .expectNextMatches(pttl -> pttl <= 2100) - .verifyComplete(); + writer.write(redisCommand); + + assertEquals("jetpackmelon22", REDIS_STANDALONE_COMMANDS.get(redisCommand.getPayload().getKey())); + assertTrue(isValidPttl(REDIS_STANDALONE_COMMANDS.pttl(redisCommand.getPayload().getKey()), originalPttl)); } @Test public void writeSetWithExpireKeepTtlCommandAppliesCommandToCluster() { final String key = "{user.1}.username"; + final long originalPttl = 21_000L; - final Mono result = REDIS_CLUSTER_COMMANDS.set(key, "artistjanitor90", new SetArgs().px(2100)); + REDIS_CLUSTER_COMMANDS.set(key, "artistjanitor90", new SetArgs().px(originalPttl)); final RedisSetCommand redisCommand = RedisSetCommand.builder() .payload(RedisSetCommand.Payload.builder() @@ -297,33 +265,17 @@ public void writeSetWithExpireKeepTtlCommandAppliesCommandToCluster() { .build(); final Writer writer = new Writer(REDIS_CLUSTER_COMMANDS); - final Mono write = writer.write(redisCommand); - - StepVerifier - .create(result) - .expectNext("OK") - .verifyComplete(); - - StepVerifier - .create(write) - .verifyComplete(); - - StepVerifier - .create(REDIS_CLUSTER_COMMANDS.get(redisCommand.getPayload().getKey())) - .expectNext("jetpackmelon22") - .verifyComplete(); - - StepVerifier - .create(REDIS_CLUSTER_COMMANDS.pttl(redisCommand.getPayload().getKey())) - .expectNextMatches(pttl -> pttl <= 2100) - .verifyComplete(); + writer.write(redisCommand); + + assertEquals("jetpackmelon22", REDIS_CLUSTER_COMMANDS.get(redisCommand.getPayload().getKey())); + assertTrue(isValidPttl(REDIS_CLUSTER_COMMANDS.pttl(redisCommand.getPayload().getKey()), originalPttl)); } @Test public void writeSetWithNxConditionCommandAppliesCommandToStandalone() { final String key = "{user.1}.username"; - final Mono result = REDIS_STANDALONE_COMMANDS.set(key, "artistjanitor90"); + REDIS_STANDALONE_COMMANDS.set(key, "artistjanitor90"); final RedisSetCommand redisCommand = RedisSetCommand.builder() .payload(RedisSetCommand.Payload.builder() @@ -331,40 +283,24 @@ public void writeSetWithNxConditionCommandAppliesCommandToStandalone() { .value("jetpackmelon22") .expiration(RedisSetCommand.Payload.Expiration.builder() .type(RedisSetCommand.Payload.Expiration.Type.EX) - .time(2100L) + .time(21_000L) .build()) .condition(RedisSetCommand.Payload.Condition.NX) .build()) .build(); final Writer writer = new Writer(REDIS_STANDALONE_COMMANDS); - final Mono write = writer.write(redisCommand); - - StepVerifier - .create(result) - .expectNext("OK") - .verifyComplete(); - - StepVerifier - .create(write) - .verifyComplete(); - - StepVerifier - .create(REDIS_STANDALONE_COMMANDS.get(redisCommand.getPayload().getKey())) - .expectNext("artistjanitor90") - .verifyComplete(); - - StepVerifier - .create(REDIS_STANDALONE_COMMANDS.ttl(redisCommand.getPayload().getKey())) - .expectNext(-1L) - .verifyComplete(); + writer.write(redisCommand); + + assertEquals("artistjanitor90", REDIS_STANDALONE_COMMANDS.get(redisCommand.getPayload().getKey())); + assertEquals(-1L, REDIS_STANDALONE_COMMANDS.ttl(redisCommand.getPayload().getKey())); } @Test public void writeSetWithNxConditionCommandAppliesCommandToCluster() { final String key = "{user.1}.username"; - final Mono initialSetResult = REDIS_CLUSTER_COMMANDS.set(key, "artistjanitor90"); + REDIS_CLUSTER_COMMANDS.set(key, "artistjanitor90"); final RedisSetCommand redisCommand = RedisSetCommand.builder() .payload(RedisSetCommand.Payload.builder() @@ -372,40 +308,24 @@ public void writeSetWithNxConditionCommandAppliesCommandToCluster() { .value("jetpackmelon22") .expiration(RedisSetCommand.Payload.Expiration.builder() .type(RedisSetCommand.Payload.Expiration.Type.EX) - .time(2100L) + .time(21_000L) .build()) .condition(RedisSetCommand.Payload.Condition.NX) .build()) .build(); final Writer writer = new Writer(REDIS_CLUSTER_COMMANDS); - final Mono write = writer.write(redisCommand); - - StepVerifier - .create(initialSetResult) - .expectNext("OK") - .verifyComplete(); - - StepVerifier - .create(write) - .verifyComplete(); - - StepVerifier - .create(REDIS_CLUSTER_COMMANDS.get(redisCommand.getPayload().getKey())) - .expectNext("artistjanitor90") - .verifyComplete(); - - StepVerifier - .create(REDIS_CLUSTER_COMMANDS.ttl(redisCommand.getPayload().getKey())) - .expectNext(-1L) - .verifyComplete(); + writer.write(redisCommand); + + assertEquals("artistjanitor90", REDIS_CLUSTER_COMMANDS.get(redisCommand.getPayload().getKey())); + assertEquals(-1L, REDIS_CLUSTER_COMMANDS.ttl(redisCommand.getPayload().getKey())); } @Test public void writeSetWithXxConditionCommandAppliesCommandToStandalone() { final String key = "{user.1}.username"; - final Mono result = REDIS_STANDALONE_COMMANDS.set(key, "artistjanitor90"); + REDIS_STANDALONE_COMMANDS.set(key, "artistjanitor90"); final RedisSetCommand redisCommand = RedisSetCommand.builder() .payload(RedisSetCommand.Payload.builder() @@ -413,40 +333,26 @@ public void writeSetWithXxConditionCommandAppliesCommandToStandalone() { .value("jetpackmelon22") .expiration(RedisSetCommand.Payload.Expiration.builder() .type(RedisSetCommand.Payload.Expiration.Type.EX) - .time(2100L) + .time(21_000L) .build()) .condition(RedisSetCommand.Payload.Condition.XX) .build()) .build(); final Writer writer = new Writer(REDIS_STANDALONE_COMMANDS); - final Mono write = writer.write(redisCommand); - - StepVerifier - .create(result) - .expectNext("OK") - .verifyComplete(); - - StepVerifier - .create(write) - .verifyComplete(); - - StepVerifier - .create(REDIS_STANDALONE_COMMANDS.get(redisCommand.getPayload().getKey())) - .expectNext("jetpackmelon22") - .verifyComplete(); - - StepVerifier - .create(REDIS_STANDALONE_COMMANDS.ttl(redisCommand.getPayload().getKey())) - .expectNextMatches(ttl -> ttl <= redisCommand.getPayload().getExpiration().getTime()) - .verifyComplete(); + writer.write(redisCommand); + + assertEquals("jetpackmelon22", REDIS_STANDALONE_COMMANDS.get(redisCommand.getPayload().getKey())); + assertTrue(isValidTtl( + REDIS_STANDALONE_COMMANDS.ttl(redisCommand.getPayload().getKey()), + redisCommand.getPayload().getExpiration().getTime())); } @Test public void writeSetWithXxConditionCommandAppliesCommandToCluster() { final String key = "{user.1}.username"; - final Mono result = REDIS_CLUSTER_COMMANDS.set(key, "artistjanitor90"); + REDIS_CLUSTER_COMMANDS.set(key, "artistjanitor90"); final RedisSetCommand redisCommand = RedisSetCommand.builder() .payload(RedisSetCommand.Payload.builder() @@ -454,33 +360,19 @@ public void writeSetWithXxConditionCommandAppliesCommandToCluster() { .value("jetpackmelon22") .expiration(RedisSetCommand.Payload.Expiration.builder() .type(RedisSetCommand.Payload.Expiration.Type.EX) - .time(2100L) + .time(21_000L) .build()) .condition(RedisSetCommand.Payload.Condition.XX) .build()) .build(); final Writer writer = new Writer(REDIS_CLUSTER_COMMANDS); - final Mono write = writer.write(redisCommand); - - StepVerifier - .create(result) - .expectNext("OK") - .verifyComplete(); - - StepVerifier - .create(write) - .verifyComplete(); - - StepVerifier - .create(REDIS_CLUSTER_COMMANDS.get(redisCommand.getPayload().getKey())) - .expectNext("jetpackmelon22") - .verifyComplete(); - - StepVerifier - .create(REDIS_CLUSTER_COMMANDS.ttl(redisCommand.getPayload().getKey())) - .expectNextMatches(ttl -> ttl <= redisCommand.getPayload().getExpiration().getTime()) - .verifyComplete(); + writer.write(redisCommand); + + assertEquals("jetpackmelon22", REDIS_CLUSTER_COMMANDS.get(redisCommand.getPayload().getKey())); + assertTrue(isValidTtl( + REDIS_CLUSTER_COMMANDS.ttl(redisCommand.getPayload().getKey()), + redisCommand.getPayload().getExpiration().getTime())); } @Test @@ -492,19 +384,14 @@ public void writeExpireCommandAppliesCommandToStandalone() { .build()) .build(); - REDIS_STANDALONE_COMMANDS.set(redisCommand.getPayload().getKey(), "$2.29").block(); + REDIS_STANDALONE_COMMANDS.set(redisCommand.getPayload().getKey(), "$2.29"); final Writer writer = new Writer(REDIS_STANDALONE_COMMANDS); - final Mono write = writer.write(redisCommand); + writer.write(redisCommand); - StepVerifier - .create(write) - .verifyComplete(); - - StepVerifier - .create(REDIS_STANDALONE_COMMANDS.ttl(redisCommand.getPayload().getKey())) - .expectNextMatches(ttl -> ttl <= redisCommand.getPayload().getSeconds()) - .verifyComplete(); + assertTrue(isValidTtl( + REDIS_STANDALONE_COMMANDS.ttl(redisCommand.getPayload().getKey()), + redisCommand.getPayload().getSeconds())); } @Test @@ -516,19 +403,14 @@ public void writeExpireCommandAppliesCommandToCluster() { .build()) .build(); - REDIS_CLUSTER_COMMANDS.set(redisCommand.getPayload().getKey(), "$2.29").block(); + REDIS_CLUSTER_COMMANDS.set(redisCommand.getPayload().getKey(), "$2.29"); final Writer writer = new Writer(REDIS_CLUSTER_COMMANDS); - final Mono write = writer.write(redisCommand); - - StepVerifier - .create(write) - .verifyComplete(); + writer.write(redisCommand); - StepVerifier - .create(REDIS_CLUSTER_COMMANDS.ttl(redisCommand.getPayload().getKey())) - .expectNextMatches(ttl -> ttl <= redisCommand.getPayload().getSeconds()) - .verifyComplete(); + assertTrue(isValidTtl( + REDIS_CLUSTER_COMMANDS.ttl(redisCommand.getPayload().getKey()), + redisCommand.getPayload().getSeconds())); } @Test @@ -542,19 +424,14 @@ public void writeExpireatCommandAppliesCommandToStandalone() { .build()) .build(); - REDIS_STANDALONE_COMMANDS.set(redisCommand.getPayload().getKey(), "$2.29").block(); + REDIS_STANDALONE_COMMANDS.set(redisCommand.getPayload().getKey(), "$2.29"); final Writer writer = new Writer(REDIS_STANDALONE_COMMANDS); - final Mono write = writer.write(redisCommand); - - StepVerifier - .create(write) - .verifyComplete(); + writer.write(redisCommand); - StepVerifier - .create(REDIS_STANDALONE_COMMANDS.ttl(redisCommand.getPayload().getKey())) - .expectNextMatches(ttl -> ttl <= secondsIntoFuture) - .verifyComplete(); + assertTrue(isValidTtl( + REDIS_STANDALONE_COMMANDS.ttl(redisCommand.getPayload().getKey()), + secondsIntoFuture)); } @Test @@ -568,19 +445,14 @@ public void writeExpireatCommandAppliesCommandToCluster() { .build()) .build(); - REDIS_CLUSTER_COMMANDS.set(redisCommand.getPayload().getKey(), "$2.29").block(); + REDIS_CLUSTER_COMMANDS.set(redisCommand.getPayload().getKey(), "$2.29"); final Writer writer = new Writer(REDIS_CLUSTER_COMMANDS); - final Mono write = writer.write(redisCommand); + writer.write(redisCommand); - StepVerifier - .create(write) - .verifyComplete(); - - StepVerifier - .create(REDIS_CLUSTER_COMMANDS.ttl(redisCommand.getPayload().getKey())) - .expectNextMatches(ttl -> ttl <= secondsIntoFuture) - .verifyComplete(); + assertTrue(isValidTtl( + REDIS_CLUSTER_COMMANDS.ttl(redisCommand.getPayload().getKey()), + secondsIntoFuture)); } @Test @@ -592,19 +464,14 @@ public void writePexpireCommandAppliesCommandToStandalone() { .build()) .build(); - REDIS_STANDALONE_COMMANDS.set(redisCommand.getPayload().getKey(), "$2.29").block(); + REDIS_STANDALONE_COMMANDS.set(redisCommand.getPayload().getKey(), "$2.29"); final Writer writer = new Writer(REDIS_STANDALONE_COMMANDS); - final Mono write = writer.write(redisCommand); - - StepVerifier - .create(write) - .verifyComplete(); + writer.write(redisCommand); - StepVerifier - .create(REDIS_STANDALONE_COMMANDS.pttl(redisCommand.getPayload().getKey())) - .expectNextMatches(pttl -> pttl <= redisCommand.getPayload().getMilliseconds()) - .verifyComplete(); + assertTrue(isValidPttl( + REDIS_STANDALONE_COMMANDS.pttl(redisCommand.getPayload().getKey()), + redisCommand.getPayload().getMilliseconds())); } @Test @@ -616,19 +483,14 @@ public void writePexpireCommandAppliesCommandToCluster() { .build()) .build(); - REDIS_CLUSTER_COMMANDS.set(redisCommand.getPayload().getKey(), "$2.29").block(); + REDIS_CLUSTER_COMMANDS.set(redisCommand.getPayload().getKey(), "$2.29"); final Writer writer = new Writer(REDIS_CLUSTER_COMMANDS); - final Mono write = writer.write(redisCommand); - - StepVerifier - .create(write) - .verifyComplete(); + writer.write(redisCommand); - StepVerifier - .create(REDIS_CLUSTER_COMMANDS.pttl(redisCommand.getPayload().getKey())) - .expectNextMatches(pttl -> pttl <= redisCommand.getPayload().getMilliseconds()) - .verifyComplete(); + assertTrue(isValidPttl( + REDIS_CLUSTER_COMMANDS.pttl(redisCommand.getPayload().getKey()), + redisCommand.getPayload().getMilliseconds())); } @Test @@ -641,16 +503,10 @@ public void writeSaddCommandAppliesCommandToStandalone() { .build(); final Writer writer = new Writer(REDIS_STANDALONE_COMMANDS); - final Mono write = writer.write(redisCommand); + writer.write(redisCommand); - StepVerifier - .create(write) - .verifyComplete(); - - StepVerifier - .create(REDIS_STANDALONE_COMMANDS.smembers(redisCommand.getPayload().getKey())) - .thenConsumeWhile(member -> redisCommand.getPayload().getValues().contains(member)) - .verifyComplete(); + assertEquals(new HashSet<>(redisCommand.getPayload().getValues()), + REDIS_STANDALONE_COMMANDS.smembers(redisCommand.getPayload().getKey())); } @Test @@ -663,16 +519,10 @@ public void writeSaddCommandAppliesCommandToCluster() { .build(); final Writer writer = new Writer(REDIS_CLUSTER_COMMANDS); - final Mono write = writer.write(redisCommand); - - StepVerifier - .create(write) - .verifyComplete(); + writer.write(redisCommand); - StepVerifier - .create(REDIS_CLUSTER_COMMANDS.smembers(redisCommand.getPayload().getKey())) - .thenConsumeWhile(member -> redisCommand.getPayload().getValues().contains(member)) - .verifyComplete(); + assertEquals(new HashSet<>(redisCommand.getPayload().getValues()), + REDIS_CLUSTER_COMMANDS.smembers(redisCommand.getPayload().getKey())); } @Test @@ -695,29 +545,20 @@ public void writeGeoaddCommandAppliesCommandToStandalone() { .build()) .build(); + // Beware, Redis does not store exact coordinates + final List expectedGeoCoordinates = new ArrayList<>(); + expectedGeoCoordinates.add(GeoCoordinates.create(13.36138933897018433d, 38.11555639549629859d)); + expectedGeoCoordinates.add(GeoCoordinates.create(15.087267458438873d, 37.50266842333162d)); + final Writer writer = new Writer(REDIS_STANDALONE_COMMANDS); - final Mono write = writer.write(redisCommand); + writer.write(redisCommand); - StepVerifier - .create(write) - .verifyComplete(); + final List geoCoordinates = REDIS_STANDALONE_COMMANDS.geopos( + redisCommand.getPayload().getKey(), + redisCommand.getPayload().getValues().get(0).getMember(), + redisCommand.getPayload().getValues().get(1).getMember()); - // Beware, Redis does not store exact coordinates - - StepVerifier - .create(REDIS_STANDALONE_COMMANDS.geopos( - redisCommand.getPayload().getKey(), - redisCommand.getPayload().getValues().get(0).getMember(), - redisCommand.getPayload().getValues().get(1).getMember())) - .thenConsumeWhile(value -> { - final double actualLongitude = value.getValue().getX().doubleValue(); - final double actualLatitude = value.getValue().getY().doubleValue(); - - // Beware, Redis does not store exact coordinates - return (actualLongitude == 13.36138933897018433d && actualLatitude == 38.11555639549629859d) - || (actualLongitude == 15.087267458438873 && actualLatitude == 37.50266842333162); - }) - .verifyComplete(); + assertEquals(expectedGeoCoordinates, geoCoordinates); } @Test @@ -740,27 +581,20 @@ public void writeGeoaddCommandAppliesCommandToCluster() { .build()) .build(); + // Beware, Redis does not store exact coordinates + final List expectedGeoCoordinates = new ArrayList<>(); + expectedGeoCoordinates.add(GeoCoordinates.create(13.36138933897018433d, 38.11555639549629859d)); + expectedGeoCoordinates.add(GeoCoordinates.create(15.087267458438873d, 37.50266842333162d)); + final Writer writer = new Writer(REDIS_CLUSTER_COMMANDS); - final Mono write = writer.write(redisCommand); - - StepVerifier - .create(write) - .verifyComplete(); - - StepVerifier - .create(REDIS_CLUSTER_COMMANDS.geopos( - redisCommand.getPayload().getKey(), - redisCommand.getPayload().getValues().get(0).getMember(), - redisCommand.getPayload().getValues().get(1).getMember())) - .thenConsumeWhile(value -> { - final double actualLongitude = value.getValue().getX().doubleValue(); - final double actualLatitude = value.getValue().getY().doubleValue(); - - // Beware, Redis does not store exact coordinates - return (actualLongitude == 13.36138933897018433d && actualLatitude == 38.11555639549629859d) - || (actualLongitude == 15.087267458438873 && actualLatitude == 37.50266842333162); - }) - .verifyComplete(); + writer.write(redisCommand); + + final List geoCoordinates = REDIS_CLUSTER_COMMANDS.geopos( + redisCommand.getPayload().getKey(), + redisCommand.getPayload().getValues().get(0).getMember(), + redisCommand.getPayload().getValues().get(1).getMember()); + + assertEquals(expectedGeoCoordinates, geoCoordinates); } @Test @@ -783,14 +617,9 @@ public void writeGeoaddCommandRetunsErrorForMissingMember() { .build(); final Writer writer = new Writer(REDIS_STANDALONE_COMMANDS); - final Mono write = writer.write(redisCommand); - - StepVerifier - .create(write) - .expectErrorMatches(exception -> - exception instanceof ConnectException - && exception.getMessage().equals("geoadd command does not contain member")) - .verify(); + final ConnectException exception = assertThrows(ConnectException.class, () -> writer.write(redisCommand)); + + assertEquals("geoadd command does not contain member", exception.getMessage()); } @Test @@ -803,21 +632,10 @@ public void writeArbitraryCommandAppliesCommandToStandalone() { .build(); final Writer writer = new Writer(REDIS_STANDALONE_COMMANDS); - final Mono write = writer.write(redisCommand); - - StepVerifier - .create(write) - .verifyComplete(); - - StepVerifier - .create(REDIS_STANDALONE_COMMANDS.get("arbitrarykey")) - .expectNext("arbitraryvalue") - .verifyComplete(); + writer.write(redisCommand); - StepVerifier - .create(REDIS_STANDALONE_COMMANDS.ttl("arbitrarykey")) - .expectNextMatches(ttl -> ttl <= 25L) - .verifyComplete(); + assertEquals("arbitraryvalue", REDIS_STANDALONE_COMMANDS.get("arbitrarykey")); + assertTrue(REDIS_STANDALONE_COMMANDS.ttl("arbitrarykey") <= 25L); } @Test @@ -830,20 +648,9 @@ public void writeArbitraryCommandAppliesCommandToCluster() { .build(); final Writer writer = new Writer(REDIS_CLUSTER_COMMANDS); - final Mono write = writer.write(redisCommand); - - StepVerifier - .create(write) - .verifyComplete(); - - StepVerifier - .create(REDIS_CLUSTER_COMMANDS.get("arbitrarykey")) - .expectNext("arbitraryvalue") - .verifyComplete(); + writer.write(redisCommand); - StepVerifier - .create(REDIS_CLUSTER_COMMANDS.ttl("arbitrarykey")) - .expectNextMatches(ttl -> ttl <= 25L) - .verifyComplete(); + assertEquals("arbitraryvalue", REDIS_CLUSTER_COMMANDS.get("arbitrarykey")); + assertTrue(REDIS_CLUSTER_COMMANDS.ttl("arbitrarykey") <= 25L); } } diff --git a/src/test/integration/java/io/github/jaredpetersen/kafkaconnectredis/source/RedisSourceTaskIT.java b/src/test/integration/java/io/github/jaredpetersen/kafkaconnectredis/source/RedisSourceTaskIT.java index 57b7201..17f65f3 100644 --- a/src/test/integration/java/io/github/jaredpetersen/kafkaconnectredis/source/RedisSourceTaskIT.java +++ b/src/test/integration/java/io/github/jaredpetersen/kafkaconnectredis/source/RedisSourceTaskIT.java @@ -44,7 +44,7 @@ public class RedisSourceTaskIT { private static StatefulRedisClusterPubSubConnection REDIS_CLUSTER_SUB_CONNECTION; @BeforeAll - static void setupAll() { + static void beforeAll() { REDIS_STANDALONE_URI = REDIS_STANDALONE.getUri(); REDIS_STANDALONE_CLIENT = RedisClient.create(REDIS_STANDALONE_URI); @@ -66,13 +66,13 @@ static void setupAll() { } @AfterEach - public void cleanupEach() { + public void afterEach() { REDIS_STANDALONE_PUB_COMMANDS.flushall().block(); REDIS_CLUSTER_PUB_COMMANDS.flushall().block(); } @AfterAll - static void cleanupAll() { + static void afterAll() { REDIS_STANDALONE_SUB_CONNECTION.close(); REDIS_STANDALONE_CLIENT.shutdown(); diff --git a/src/test/integration/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/subscriber/RedisChannelSubscriberIT.java b/src/test/integration/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/subscriber/RedisChannelSubscriberIT.java index 86838ed..1ec344b 100644 --- a/src/test/integration/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/subscriber/RedisChannelSubscriberIT.java +++ b/src/test/integration/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/subscriber/RedisChannelSubscriberIT.java @@ -29,7 +29,7 @@ public class RedisChannelSubscriberIT { private static StatefulRedisPubSubConnection REDIS_STANDALONE_SUB_CONNECTION; @BeforeAll - static void setupAll() { + static void beforeAll() { REDIS_STANDALONE_CLIENT = RedisClient.create(REDIS_STANDALONE.getUri()); REDIS_STANDALONE_PUB_CONNECTION = REDIS_STANDALONE_CLIENT.connectPubSub(); @@ -39,12 +39,12 @@ static void setupAll() { } @AfterEach - public void cleanupEach() { + public void afterEach() { REDIS_STANDALONE_PUB_COMMANDS.flushall().block(); } @AfterAll - static void cleanupAll() { + static void afterAll() { REDIS_STANDALONE_SUB_CONNECTION.close(); REDIS_STANDALONE_CLIENT.shutdown(); } diff --git a/src/test/integration/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/subscriber/RedisClusterChannelSubscriberIT.java b/src/test/integration/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/subscriber/RedisClusterChannelSubscriberIT.java index 9e5e03e..413a12d 100644 --- a/src/test/integration/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/subscriber/RedisClusterChannelSubscriberIT.java +++ b/src/test/integration/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/subscriber/RedisClusterChannelSubscriberIT.java @@ -29,7 +29,7 @@ public class RedisClusterChannelSubscriberIT { private static StatefulRedisClusterPubSubConnection REDIS_CLUSTER_SUB_CONNECTION; @BeforeAll - static void setupAll() { + static void beforeAll() { REDIS_CLUSTER_CLIENT = RedisClusterClient.create(REDIS_CLUSTER.getUri()); REDIS_CLUSTER_PUB_CONNECTION = REDIS_CLUSTER_CLIENT.connectPubSub(); @@ -40,12 +40,12 @@ static void setupAll() { } @AfterEach - public void cleanupEach() { + public void afterEach() { REDIS_CLUSTER_PUB_COMMANDS.flushall().block(); } @AfterAll - static void cleanupAll() { + static void afterAll() { REDIS_CLUSTER_SUB_CONNECTION.close(); REDIS_CLUSTER_CLIENT.shutdown(); } diff --git a/src/test/integration/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/subscriber/RedisClusterPatternSubscriberIT.java b/src/test/integration/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/subscriber/RedisClusterPatternSubscriberIT.java index c318936..ceec12e 100644 --- a/src/test/integration/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/subscriber/RedisClusterPatternSubscriberIT.java +++ b/src/test/integration/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/subscriber/RedisClusterPatternSubscriberIT.java @@ -29,7 +29,7 @@ public class RedisClusterPatternSubscriberIT { private static StatefulRedisClusterPubSubConnection REDIS_CLUSTER_SUB_CONNECTION; @BeforeAll - static void setupAll() { + static void beforeAll() { REDIS_CLUSTER_CLIENT = RedisClusterClient.create(REDIS_CLUSTER.getUri()); REDIS_CLUSTER_PUB_CONNECTION = REDIS_CLUSTER_CLIENT.connectPubSub(); @@ -40,12 +40,12 @@ static void setupAll() { } @AfterEach - public void cleanupEach() { + public void afterEach() { REDIS_CLUSTER_PUB_COMMANDS.flushall().block(); } @AfterAll - static void cleanupAll() { + static void afterAll() { REDIS_CLUSTER_SUB_CONNECTION.close(); REDIS_CLUSTER_CLIENT.shutdown(); } diff --git a/src/test/integration/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/subscriber/RedisPatternSubscriberIT.java b/src/test/integration/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/subscriber/RedisPatternSubscriberIT.java index 231239d..ed7faf7 100644 --- a/src/test/integration/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/subscriber/RedisPatternSubscriberIT.java +++ b/src/test/integration/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/subscriber/RedisPatternSubscriberIT.java @@ -29,7 +29,7 @@ public class RedisPatternSubscriberIT { private static StatefulRedisPubSubConnection REDIS_STANDALONE_SUB_CONNECTION; @BeforeAll - static void setupAll() { + static void beforeAll() { REDIS_STANDALONE_CLIENT = RedisClient.create(REDIS_STANDALONE.getUri()); REDIS_STANDALONE_PUB_CONNECTION = REDIS_STANDALONE_CLIENT.connectPubSub(); @@ -39,12 +39,12 @@ static void setupAll() { } @AfterEach - public void cleanupEach() { + public void afterEach() { REDIS_STANDALONE_PUB_COMMANDS.flushall().block(); } @AfterAll - static void cleanupAll() { + static void afterAll() { REDIS_STANDALONE_SUB_CONNECTION.close(); REDIS_STANDALONE_CLIENT.shutdown(); } diff --git a/src/test/unit/java/io/github/jaredpetersen/kafkaconnectredis/sink/writer/RecordConverterTest.java b/src/test/unit/java/io/github/jaredpetersen/kafkaconnectredis/sink/writer/RecordConverterTest.java index 845bc1a..a8ed04f 100644 --- a/src/test/unit/java/io/github/jaredpetersen/kafkaconnectredis/sink/writer/RecordConverterTest.java +++ b/src/test/unit/java/io/github/jaredpetersen/kafkaconnectredis/sink/writer/RecordConverterTest.java @@ -14,8 +14,8 @@ import org.apache.kafka.connect.data.Struct; import org.apache.kafka.connect.sink.SinkRecord; import org.junit.jupiter.api.Test; -import reactor.core.publisher.Mono; -import reactor.test.StepVerifier; + +import static org.junit.jupiter.api.Assertions.assertEquals; public class RecordConverterTest { private static final Schema REDIS_SET_COMMAND_SCHEMA = SchemaBuilder.struct() @@ -95,11 +95,9 @@ public void convertTransformsPartialSinkRecordToRedisSetCommand() { .build(); final RecordConverter recordConverter = new RecordConverter(); - final Mono redisCommandMono = recordConverter.convert(sinkRecord); - - StepVerifier.create(redisCommandMono) - .expectNext(expectedRedisCommand) - .verifyComplete(); + final RedisCommand redisCommand = recordConverter.convert(sinkRecord); + + assertEquals(expectedRedisCommand, redisCommand); } @Test @@ -132,11 +130,9 @@ public void convertTransformsSinkRecordToRedisSetCommand() { .build(); final RecordConverter recordConverter = new RecordConverter(); - final Mono redisCommandMono = recordConverter.convert(sinkRecord); + final RedisCommand redisCommand = recordConverter.convert(sinkRecord); - StepVerifier.create(redisCommandMono) - .expectNext(expectedRedisCommand) - .verifyComplete(); + assertEquals(expectedRedisCommand, redisCommand); } @Test @@ -160,11 +156,9 @@ public void convertTransformsSinkRecordToRedisExpireCommand() { .build(); final RecordConverter recordConverter = new RecordConverter(); - final Mono redisCommandMono = recordConverter.convert(sinkRecord); + final RedisCommand redisCommand = recordConverter.convert(sinkRecord); - StepVerifier.create(redisCommandMono) - .expectNext(expectedRedisCommand) - .verifyComplete(); + assertEquals(expectedRedisCommand, redisCommand); } @Test @@ -188,11 +182,9 @@ public void convertTransformsSinkRecordToRedisExpireatCommand() { .build(); final RecordConverter recordConverter = new RecordConverter(); - final Mono redisCommandMono = recordConverter.convert(sinkRecord); + final RedisCommand redisCommand = recordConverter.convert(sinkRecord); - StepVerifier.create(redisCommandMono) - .expectNext(expectedRedisCommand) - .verifyComplete(); + assertEquals(expectedRedisCommand, redisCommand); } @Test @@ -216,11 +208,9 @@ public void convertTransformsSinkRecordToRedisPexpireCommand() { .build(); final RecordConverter recordConverter = new RecordConverter(); - final Mono redisCommandMono = recordConverter.convert(sinkRecord); + final RedisCommand redisCommand = recordConverter.convert(sinkRecord); - StepVerifier.create(redisCommandMono) - .expectNext(expectedRedisCommand) - .verifyComplete(); + assertEquals(expectedRedisCommand, redisCommand); } @Test @@ -244,11 +234,9 @@ public void convertTransformsSinkRecordToRedisSaddCommand() { .build(); final RecordConverter recordConverter = new RecordConverter(); - final Mono redisCommandMono = recordConverter.convert(sinkRecord); + final RedisCommand redisCommand = recordConverter.convert(sinkRecord); - StepVerifier.create(redisCommandMono) - .expectNext(expectedRedisCommand) - .verifyComplete(); + assertEquals(expectedRedisCommand, redisCommand); } @Test @@ -291,11 +279,9 @@ public void convertTransformsSinkRecordToRedisGeoaddCommand() { .build(); final RecordConverter recordConverter = new RecordConverter(); - final Mono redisCommandMono = recordConverter.convert(sinkRecord); + final RedisCommand redisCommand = recordConverter.convert(sinkRecord); - StepVerifier.create(redisCommandMono) - .expectNext(expectedRedisCommand) - .verifyComplete(); + assertEquals(expectedRedisCommand, redisCommand); } @Test @@ -319,10 +305,8 @@ public void convertTransformsSinkRecordToRedisArbitraryCommand() { .build(); final RecordConverter recordConverter = new RecordConverter(); - final Mono redisCommandMono = recordConverter.convert(sinkRecord); + final RedisCommand redisCommand = recordConverter.convert(sinkRecord); - StepVerifier.create(redisCommandMono) - .expectNext(expectedRedisCommand) - .verifyComplete(); + assertEquals(expectedRedisCommand, redisCommand); } } From 9ab93654032a05d2d8d5f57db36fd19337a1112b Mon Sep 17 00:00:00 2001 From: Jared Petersen Date: Thu, 15 Jul 2021 01:10:36 -0700 Subject: [PATCH 05/10] left a note for myself later --- .../source/listener/RedisListener.java | 1 + .../source/listener/OldRedisListenerTest.java | 223 ++++++++++++++++++ 2 files changed, 224 insertions(+) create mode 100644 src/test/unit/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/OldRedisListenerTest.java diff --git a/src/main/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/RedisListener.java b/src/main/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/RedisListener.java index 5cc63ed..5259426 100644 --- a/src/main/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/RedisListener.java +++ b/src/main/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/RedisListener.java @@ -10,6 +10,7 @@ import reactor.core.Disposable; import reactor.core.scheduler.Schedulers; +// TODO check if we can use lombok annotation here for logging public class RedisListener { private final Queue queue = new ConcurrentLinkedQueue<>(); private final RedisSubscriber redisSubscriber; diff --git a/src/test/unit/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/OldRedisListenerTest.java b/src/test/unit/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/OldRedisListenerTest.java new file mode 100644 index 0000000..8eab0b3 --- /dev/null +++ b/src/test/unit/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/OldRedisListenerTest.java @@ -0,0 +1,223 @@ +package io.github.jaredpetersen.kafkaconnectredis.source.listener; + +import io.github.jaredpetersen.kafkaconnectredis.source.listener.subscriber.RedisSubscriber; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import org.junit.jupiter.api.Test; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class RedisListenerTest { + @Test + public void startSubscribesAndListens() throws InterruptedException { + // Generate an unbounded flux redis messages on demand + // Use an external index to track the progress for verification purposes + final AtomicInteger redisSubscriptionMessageIndex = new AtomicInteger(); + final Flux redisSubscriptionMessageFlux = Flux.generate( + () -> 0, + (state, sink) -> { + redisSubscriptionMessageIndex.set(state); + + final RedisMessage redisMessage = RedisMessage.builder() + .channel("election") + .message("vote-" + state) + .build(); + + sink.next(redisMessage); + + return ++state; + } + ); + + final RedisSubscriber mockRedisSubscriber = mock(RedisSubscriber.class); + when(mockRedisSubscriber.subscribe()).thenReturn(Mono.empty()); + when(mockRedisSubscriber.observe()).thenReturn(redisSubscriptionMessageFlux); + + final RedisListener redisListener = new RedisListener(mockRedisSubscriber); + + redisListener.start(); + + Thread.sleep(1000L); + + assertTrue(redisSubscriptionMessageIndex.get() > 0); + } + + @Test + public void stopUnsubscribesAndStopsListening() throws InterruptedException { + // Generate an unbounded flux redis messages on demand + // Use an external index to track the progress for verification purposes + final AtomicInteger redisSubscriptionMessageIndex = new AtomicInteger(); + final Flux redisSubscriptionMessageFlux = Flux.generate( + () -> 0, + (state, sink) -> { + redisSubscriptionMessageIndex.set(state); + + final RedisMessage redisMessage = RedisMessage.builder() + .channel("election") + .message("vote-" + state) + .build(); + + sink.next(redisMessage); + + return ++state; + } + ); + + final RedisSubscriber mockRedisSubscriber = mock(RedisSubscriber.class); + when(mockRedisSubscriber.subscribe()).thenReturn(Mono.empty()); + when(mockRedisSubscriber.observe()).thenReturn(redisSubscriptionMessageFlux); + when(mockRedisSubscriber.unsubscribe()).thenReturn(Mono.empty()); + + final RedisListener redisListener = new RedisListener(mockRedisSubscriber); + + redisListener.start(); + Thread.sleep(1000L); + redisListener.stop(); + + final int redisSubscriptionMessageIndexAfterStop = redisSubscriptionMessageIndex.get(); + + Thread.sleep(1000L); + + assertEquals(redisSubscriptionMessageIndexAfterStop, redisSubscriptionMessageIndex.get()); + } + + @Test + public void pollRetrievesEmptyListOfSubscribedMessages() throws InterruptedException { + final RedisSubscriber mockRedisSubscriber = mock(RedisSubscriber.class); + when(mockRedisSubscriber.subscribe()).thenReturn(Mono.empty()); + when(mockRedisSubscriber.observe()).thenReturn(Flux.empty()); + when(mockRedisSubscriber.unsubscribe()).thenReturn(Mono.empty()); + + final RedisListener redisListener = new RedisListener(mockRedisSubscriber); + + final List redisMessages = redisListener.poll(); + + assertEquals(0, redisMessages.size()); + } + + @Test + public void pollRetrievesSubscribedMessages() throws InterruptedException { + // Generate an unbounded flux redis messages on demand + // Use an external index to track the progress for verification purposes + final AtomicInteger redisSubscriptionMessageIndex = new AtomicInteger(); + final Flux redisSubscriptionMessageFlux = Flux.generate( + () -> 0, + (state, sink) -> { + redisSubscriptionMessageIndex.set(state); + + final RedisMessage redisMessage = RedisMessage.builder() + .channel("election") + .message("vote-" + state) + .build(); + + sink.next(redisMessage); + + return ++state; + } + ); + + final RedisSubscriber mockRedisSubscriber = mock(RedisSubscriber.class); + when(mockRedisSubscriber.subscribe()).thenReturn(Mono.empty()); + when(mockRedisSubscriber.observe()).thenReturn(redisSubscriptionMessageFlux); + when(mockRedisSubscriber.unsubscribe()).thenReturn(Mono.empty()); + + final RedisListener redisListener = new RedisListener(mockRedisSubscriber); + + redisListener.start(); + + // Give the listener time to observe some messages + Thread.sleep(2000L); + + final List redisMessages = redisListener.poll(); + + assertTrue(redisMessages.size() > 0); + assertTrue(redisMessages.size() <= 100_000); + assertEquals( + RedisMessage.builder() + .channel("election") + .message("vote-0") + .build(), + redisMessages.get(0)); + assertEquals( + RedisMessage.builder() + .channel("election") + .message("vote-" + (redisMessages.size() - 1)) + .build(), + redisMessages.get(redisMessages.size() - 1)); + } + + @Test + public void pollMultipleTimesRetrievesSubscribedMessagesWithoutDataLoss() throws InterruptedException { + // Generate an unbounded flux redis messages on demand + // Use an external index to track the progress for verification purposes + final AtomicInteger redisSubscriptionMessageIndex = new AtomicInteger(); + final Flux redisSubscriptionMessageFlux = Flux.generate( + () -> 0, + (state, sink) -> { + redisSubscriptionMessageIndex.set(state); + + final RedisMessage redisMessage = RedisMessage.builder() + .channel("election") + .message("vote-" + state) + .build(); + + sink.next(redisMessage); + + return ++state; + } + ); + + final RedisSubscriber mockRedisSubscriber = mock(RedisSubscriber.class); + when(mockRedisSubscriber.subscribe()).thenReturn(Mono.empty()); + when(mockRedisSubscriber.observe()).thenReturn(redisSubscriptionMessageFlux); + when(mockRedisSubscriber.unsubscribe()).thenReturn(Mono.empty()); + + final RedisListener redisListener = new RedisListener(mockRedisSubscriber); + + redisListener.start(); + + // Give the listener time to observe some messages + Thread.sleep(1000L); + + final List redisMessagesRoundA = redisListener.poll(); + + assertTrue(redisMessagesRoundA.size() > 0); + assertTrue(redisMessagesRoundA.size() <= 100_000); + assertEquals( + RedisMessage.builder() + .channel("election") + .message("vote-0") + .build(), + redisMessagesRoundA.get(0)); + assertEquals( + RedisMessage.builder() + .channel("election") + .message("vote-" + (redisMessagesRoundA.size() - 1)) + .build(), + redisMessagesRoundA.get(redisMessagesRoundA.size() - 1)); + + // Poll again, confirming that we're getting the next batch of data + Thread.sleep(1000L); + final List redisMessagesRoundB = redisListener.poll(); + + assertTrue(redisMessagesRoundB.size() > 0); + assertTrue(redisMessagesRoundB.size() <= 100_000); + assertEquals( + RedisMessage.builder() + .channel("election") + .message("vote-" + redisMessagesRoundA.size()) + .build(), + redisMessagesRoundB.get(0)); + assertEquals( + RedisMessage.builder() + .channel("election") + .message("vote-" + (redisMessagesRoundA.size() + redisMessagesRoundB.size() - 1)) + .build(), + redisMessagesRoundB.get(redisMessagesRoundB.size() - 1)); + } +} From 14bd881e486e89f93445519df7d14eaf7f89134e Mon Sep 17 00:00:00 2001 From: Jared Petersen Date: Fri, 16 Jul 2021 23:01:05 -0700 Subject: [PATCH 06/10] rewrite source to stop using project reactor --- pom.xml | 17 +- .../kafkaconnectredis/sink/RedisSinkTask.java | 7 +- .../kafkaconnectredis/sink/writer/Writer.java | 4 +- .../source/RedisSourceTask.java | 79 ++++--- .../source/listener/RecordConverter.java | 11 +- .../source/listener/RedisListener.java | 77 ------ .../source/listener/RedisMessage.java | 2 +- .../subscriber/RedisChannelSubscriber.java | 49 ++-- .../RedisClusterChannelSubscriber.java | 58 ++--- .../subscriber/RedisClusterListener.java | 42 ++++ .../RedisClusterPatternSubscriber.java | 59 ++--- .../listener/subscriber/RedisListener.java | 51 ++++ .../subscriber/RedisPatternSubscriber.java | 50 ++-- .../subscriber/RedisStandaloneListener.java | 31 +++ .../listener/subscriber/RedisSubscriber.java | 32 ++- .../sink/RedisSinkConnectorIT.java | 18 +- .../sink/RedisSinkTaskIT.java | 20 +- .../sink/writer/WriterIT.java | 50 ++-- .../source/RedisSourceConnectorIT.java | 18 +- .../source/RedisSourceTaskIT.java | 111 ++++----- .../subscriber/RedisChannelSubscriberIT.java | 174 ++++++-------- .../RedisClusterChannelSubscriberIT.java | 174 ++++++-------- .../RedisClusterPatternSubscriberIT.java | 178 +++++++------- .../subscriber/RedisPatternSubscriberIT.java | 186 +++++++-------- .../kafkaconnectredis/util/VersionUtilIT.java | 6 +- .../sink/config/RedisSinkConfigTest.java | 8 +- .../sink/writer/RecordConverterTest.java | 18 +- .../source/config/RedisSourceConfigTest.java | 10 +- .../source/listener/OldRedisListenerTest.java | 223 ------------------ .../source/listener/RecordConverterTest.java | 34 +-- .../source/listener/RedisListenerTest.java | 223 ------------------ .../subscriber/RedisClusterListenerTest.java | 114 +++++++++ .../RedisStandaloneListenerTest.java | 106 +++++++++ 33 files changed, 944 insertions(+), 1296 deletions(-) delete mode 100644 src/main/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/RedisListener.java create mode 100644 src/main/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/subscriber/RedisClusterListener.java create mode 100644 src/main/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/subscriber/RedisListener.java create mode 100644 src/main/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/subscriber/RedisStandaloneListener.java delete mode 100644 src/test/unit/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/OldRedisListenerTest.java delete mode 100644 src/test/unit/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/RedisListenerTest.java create mode 100644 src/test/unit/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/subscriber/RedisClusterListenerTest.java create mode 100644 src/test/unit/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/subscriber/RedisStandaloneListenerTest.java diff --git a/pom.xml b/pom.xml index 6445c17..93ea94a 100644 --- a/pom.xml +++ b/pom.xml @@ -72,13 +72,6 @@ 6.1.4.RELEASE - - - io.projectreactor - reactor-core - 3.4.2 - - org.projectlombok lombok @@ -88,8 +81,8 @@ org.slf4j - slf4j-nop - 1.7.30 + slf4j-simple + 1.7.31 test @@ -120,12 +113,6 @@ test - - io.projectreactor - reactor-test - 3.4.2 - test - diff --git a/src/main/java/io/github/jaredpetersen/kafkaconnectredis/sink/RedisSinkTask.java b/src/main/java/io/github/jaredpetersen/kafkaconnectredis/sink/RedisSinkTask.java index bb5c923..a5f7aeb 100644 --- a/src/main/java/io/github/jaredpetersen/kafkaconnectredis/sink/RedisSinkTask.java +++ b/src/main/java/io/github/jaredpetersen/kafkaconnectredis/sink/RedisSinkTask.java @@ -26,6 +26,9 @@ * Kafka Connect Task for Kafka Connect Redis Sink. */ public class RedisSinkTask extends SinkTask { + private static final RecordConverter RECORD_CONVERTER = new RecordConverter(); + private static final Logger LOG = LoggerFactory.getLogger(RedisSinkTask.class); + private RedisClient redisStandaloneClient; private StatefulRedisConnection redisStandaloneConnection; @@ -34,10 +37,6 @@ public class RedisSinkTask extends SinkTask { private Writer writer; - private static final RecordConverter RECORD_CONVERTER = new RecordConverter(); - - private static final Logger LOG = LoggerFactory.getLogger(RedisSinkTask.class); - @Override public String version() { return VersionUtil.getVersion(); diff --git a/src/main/java/io/github/jaredpetersen/kafkaconnectredis/sink/writer/Writer.java b/src/main/java/io/github/jaredpetersen/kafkaconnectredis/sink/writer/Writer.java index 21a0a51..cb29fa7 100644 --- a/src/main/java/io/github/jaredpetersen/kafkaconnectredis/sink/writer/Writer.java +++ b/src/main/java/io/github/jaredpetersen/kafkaconnectredis/sink/writer/Writer.java @@ -24,12 +24,12 @@ import org.slf4j.LoggerFactory; public class Writer { + private static final Logger LOG = LoggerFactory.getLogger(Writer.class); + private final RedisCommands redisStandaloneCommands; private final RedisClusterCommands redisClusterCommands; private final boolean clusterEnabled; - private static final Logger LOG = LoggerFactory.getLogger(Writer.class); - /** * Set up writer to interact with standalone Redis. * diff --git a/src/main/java/io/github/jaredpetersen/kafkaconnectredis/source/RedisSourceTask.java b/src/main/java/io/github/jaredpetersen/kafkaconnectredis/source/RedisSourceTask.java index e29d76a..56bd8bf 100644 --- a/src/main/java/io/github/jaredpetersen/kafkaconnectredis/source/RedisSourceTask.java +++ b/src/main/java/io/github/jaredpetersen/kafkaconnectredis/source/RedisSourceTask.java @@ -2,7 +2,7 @@ import io.github.jaredpetersen.kafkaconnectredis.source.config.RedisSourceConfig; import io.github.jaredpetersen.kafkaconnectredis.source.listener.RecordConverter; -import io.github.jaredpetersen.kafkaconnectredis.source.listener.RedisListener; +import io.github.jaredpetersen.kafkaconnectredis.source.listener.RedisMessage; import io.github.jaredpetersen.kafkaconnectredis.source.listener.subscriber.RedisChannelSubscriber; import io.github.jaredpetersen.kafkaconnectredis.source.listener.subscriber.RedisClusterChannelSubscriber; import io.github.jaredpetersen.kafkaconnectredis.source.listener.subscriber.RedisClusterPatternSubscriber; @@ -15,6 +15,7 @@ import io.lettuce.core.cluster.RedisClusterClient; import io.lettuce.core.cluster.pubsub.StatefulRedisClusterPubSubConnection; import io.lettuce.core.pubsub.StatefulRedisPubSubConnection; +import java.util.ArrayList; import java.util.List; import java.util.Map; import org.apache.kafka.common.config.ConfigException; @@ -23,23 +24,23 @@ import org.apache.kafka.connect.source.SourceTask; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import reactor.core.publisher.Flux; /** * Kafka Connect Task for Kafka Connect Redis Sink. */ public class RedisSourceTask extends SourceTask { + private static final long MAX_POLL_SIZE = 10_000L; + private static final Logger LOG = LoggerFactory.getLogger(RedisSourceTask.class); + private RedisClient redisStandaloneClient; private StatefulRedisPubSubConnection redisStandalonePubSubConnection; private RedisClusterClient redisClusterClient; private StatefulRedisClusterPubSubConnection redisClusterPubSubConnection; - private RedisListener redisListener; + private RedisSubscriber redisSubscriber; private RecordConverter recordConverter; - private static final Logger LOG = LoggerFactory.getLogger(RedisSourceTask.class); - @Override public String version() { return VersionUtil.getVersion(); @@ -58,19 +59,17 @@ public void start(Map props) { } // Set up the subscriber for Redis - final RedisSubscriber redisSubscriber; - if (config.isRedisClusterEnabled()) { - this.redisClusterClient = RedisClusterClient.create(config.getRedisUri()); - this.redisClusterClient.setOptions(ClusterClientOptions.builder() + redisClusterClient = RedisClusterClient.create(config.getRedisUri()); + redisClusterClient.setOptions(ClusterClientOptions.builder() .topologyRefreshOptions(ClusterTopologyRefreshOptions.builder() .enableAllAdaptiveRefreshTriggers() .enablePeriodicRefresh() .build()) .build()); - this.redisClusterPubSubConnection = this.redisClusterClient.connectPubSub(); - this.redisClusterPubSubConnection.setNodeMessagePropagation(true); + redisClusterPubSubConnection = redisClusterClient.connectPubSub(); + redisClusterPubSubConnection.setNodeMessagePropagation(true); redisSubscriber = (config.isRedisChannelPatternEnabled()) ? new RedisClusterPatternSubscriber( @@ -81,8 +80,8 @@ public void start(Map props) { config.getRedisChannels()); } else { - this.redisStandaloneClient = RedisClient.create(config.getRedisUri()); - this.redisStandalonePubSubConnection = this.redisStandaloneClient.connectPubSub(); + redisStandaloneClient = RedisClient.create(config.getRedisUri()); + redisStandalonePubSubConnection = redisStandaloneClient.connectPubSub(); redisSubscriber = (config.isRedisChannelPatternEnabled()) ? new RedisPatternSubscriber( @@ -93,19 +92,37 @@ public void start(Map props) { config.getRedisChannels()); } - this.redisListener = new RedisListener(redisSubscriber); - this.redisListener.start(); - - this.recordConverter = new RecordConverter(config.getTopic()); + recordConverter = new RecordConverter(config.getTopic()); } @Override public List poll() { - final List sourceRecords = Flux - .fromIterable(this.redisListener.poll()) - .flatMapSequential(this.recordConverter::convert) - .collectList() - .block(); + final List sourceRecords = new ArrayList<>(); + + while (true) { + final RedisMessage redisMessage = redisSubscriber.poll(); + + // No more events left, stop iterating + if (redisMessage == null) { + break; + } + + final SourceRecord sourceRecord; + + try { + sourceRecord = recordConverter.convert(redisMessage); + } + catch (Exception exception) { + throw new ConnectException("failed to convert redis message", exception); + } + + sourceRecords.add(sourceRecord); + + // Subscription events may come in faster than we can iterate over them here so return early once we hit the max + if (sourceRecords.size() >= MAX_POLL_SIZE) { + break; + } + } if (sourceRecords.size() > 1) { LOG.info("writing {} record(s) to kafka", sourceRecords.size()); @@ -116,22 +133,20 @@ public List poll() { @Override public void stop() { - this.redisListener.stop(); - // Close out Redis standalone - if (this.redisStandalonePubSubConnection != null) { - this.redisStandalonePubSubConnection.close(); + if (redisStandalonePubSubConnection != null) { + redisStandalonePubSubConnection.close(); } - if (this.redisStandaloneClient != null) { - this.redisStandaloneClient.shutdown(); + if (redisStandaloneClient != null) { + redisStandaloneClient.shutdown(); } // Close out Redis cluster - if (this.redisClusterPubSubConnection != null) { - this.redisClusterPubSubConnection.close(); + if (redisClusterPubSubConnection != null) { + redisClusterPubSubConnection.close(); } - if (this.redisClusterClient != null) { - this.redisClusterClient.shutdown(); + if (redisClusterClient != null) { + redisClusterClient.shutdown(); } } } diff --git a/src/main/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/RecordConverter.java b/src/main/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/RecordConverter.java index 0e95b82..ca0908f 100644 --- a/src/main/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/RecordConverter.java +++ b/src/main/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/RecordConverter.java @@ -7,11 +7,8 @@ import org.apache.kafka.connect.data.SchemaBuilder; import org.apache.kafka.connect.data.Struct; import org.apache.kafka.connect.source.SourceRecord; -import reactor.core.publisher.Mono; public class RecordConverter { - private final String topic; - private static final Schema KEY_SCHEMA = SchemaBuilder.struct() .name("io.github.jaredpetersen.kafkaconnectredis.RedisSubscriptionEventKey") .field("channel", Schema.STRING_SCHEMA) @@ -20,6 +17,8 @@ public class RecordConverter { .name("io.github.jaredpetersen.kafkaconnectredis.RedisSubscriptionEventValue") .field("message", Schema.STRING_SCHEMA); + private final String topic; + /** * Set up converter with preset topic. * @@ -35,7 +34,7 @@ public RecordConverter(String topic) { * @param redisMessage Redis subscription event to be converted. * @return Converted source record. */ - public Mono convert(RedisMessage redisMessage) { + public SourceRecord convert(RedisMessage redisMessage) { // Source partition and offset are not useful in our case because the Redis subscription model does not allow us // to pick up where we left off if we stop subscribing for a while final Map sourcePartition = new HashMap<>(); @@ -47,7 +46,7 @@ public Mono convert(RedisMessage redisMessage) { final Struct value = new Struct(VALUE_SCHEMA) .put("message", redisMessage.getMessage()); - final SourceRecord sourceRecord = new SourceRecord( + return new SourceRecord( sourcePartition, sourceOffset, this.topic, @@ -58,7 +57,5 @@ public Mono convert(RedisMessage redisMessage) { value, Instant.now().getEpochSecond() ); - - return Mono.just(sourceRecord); } } diff --git a/src/main/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/RedisListener.java b/src/main/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/RedisListener.java deleted file mode 100644 index 5259426..0000000 --- a/src/main/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/RedisListener.java +++ /dev/null @@ -1,77 +0,0 @@ -package io.github.jaredpetersen.kafkaconnectredis.source.listener; - -import io.github.jaredpetersen.kafkaconnectredis.source.listener.subscriber.RedisSubscriber; -import java.util.ArrayList; -import java.util.List; -import java.util.Queue; -import java.util.concurrent.ConcurrentLinkedQueue; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import reactor.core.Disposable; -import reactor.core.scheduler.Schedulers; - -// TODO check if we can use lombok annotation here for logging -public class RedisListener { - private final Queue queue = new ConcurrentLinkedQueue<>(); - private final RedisSubscriber redisSubscriber; - - private Disposable listener; - - private static final long MAX_POLL_SIZE = 100_000L; - - private static final Logger LOG = LoggerFactory.getLogger(RedisListener.class); - - public RedisListener(RedisSubscriber redisSubscriber) { - this.redisSubscriber = redisSubscriber; - } - - /** - * Subscribe and start listening asynchronously. - */ - public void start() { - this.redisSubscriber - .subscribe() - .doOnSuccess(empty -> LOG.info("subscribed to channel(s)")) - .block(); - this.listener = this.redisSubscriber.observe() - .doOnNext(redisSubscriptionMessage -> LOG.debug("observed " + redisSubscriptionMessage)) - .doOnNext(this.queue::add) - .subscribeOn(Schedulers.boundedElastic()) - .subscribe(); - } - - /** - * Unsubscribe and stop listening. - */ - public void stop() { - this.redisSubscriber.unsubscribe().block(); - this.listener.dispose(); - } - - /** - * Retrieve messages from Redis Pub/Sub. - * - * @return List of subscription messages. - */ - public List poll() { - final List redisMessages = new ArrayList<>(); - - while (true) { - final RedisMessage redisMessage = this.queue.poll(); - - // No more events left, stop iterating - if (redisMessage == null) { - break; - } - - redisMessages.add(redisMessage); - - // Subscription events may come in faster than we can iterate over them here so return early once we hit the max - if (redisMessages.size() >= MAX_POLL_SIZE) { - break; - } - } - - return redisMessages; - } -} diff --git a/src/main/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/RedisMessage.java b/src/main/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/RedisMessage.java index c4a35c3..75db1e4 100644 --- a/src/main/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/RedisMessage.java +++ b/src/main/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/RedisMessage.java @@ -6,7 +6,7 @@ @Value @Builder(builderClassName = "Builder") public class RedisMessage { - String channel; String pattern; + String channel; String message; } diff --git a/src/main/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/subscriber/RedisChannelSubscriber.java b/src/main/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/subscriber/RedisChannelSubscriber.java index 6b5bcc4..f843dc2 100644 --- a/src/main/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/subscriber/RedisChannelSubscriber.java +++ b/src/main/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/subscriber/RedisChannelSubscriber.java @@ -1,40 +1,25 @@ package io.github.jaredpetersen.kafkaconnectredis.source.listener.subscriber; -import io.github.jaredpetersen.kafkaconnectredis.source.listener.RedisMessage; import io.lettuce.core.pubsub.StatefulRedisPubSubConnection; import java.util.List; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -public class RedisChannelSubscriber implements RedisSubscriber { - private final StatefulRedisPubSubConnection redisPubSubConnection; - private final List channels; +import java.util.concurrent.ConcurrentLinkedQueue; +/** + * Redis pub/sub subscriber that listens to channels and caches the retrieved messages for later retrieval. + */ +public class RedisChannelSubscriber extends RedisSubscriber { + /** + * Create a subscriber that listens to channels. + * + * @param redisPubSubConnection Pub/sub connection used to facilitate the subscription + * @param channels Patterns to subscribe and listen to + */ public RedisChannelSubscriber( - StatefulRedisPubSubConnection redisPubSubConnection, - List channels) { - this.redisPubSubConnection = redisPubSubConnection; - this.channels = channels; - } - - @Override - public Mono subscribe() { - return redisPubSubConnection.reactive() - .subscribe(channels.toArray(new String[0])); - } - - @Override - public Mono unsubscribe() { - return redisPubSubConnection.reactive() - .unsubscribe(channels.toArray(new String[0])); - } - - @Override - public Flux observe() { - return redisPubSubConnection.reactive().observeChannels() - .map(channelMessage -> RedisMessage.builder() - .channel(channelMessage.getChannel()) - .message(channelMessage.getMessage()) - .build()); + StatefulRedisPubSubConnection redisPubSubConnection, + List channels + ) { + super(new ConcurrentLinkedQueue<>()); + redisPubSubConnection.addListener(new RedisStandaloneListener(this.messageQueue)); + redisPubSubConnection.sync().subscribe(channels.toArray(new String[0])); } } diff --git a/src/main/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/subscriber/RedisClusterChannelSubscriber.java b/src/main/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/subscriber/RedisClusterChannelSubscriber.java index ca24cee..a05a7a0 100644 --- a/src/main/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/subscriber/RedisClusterChannelSubscriber.java +++ b/src/main/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/subscriber/RedisClusterChannelSubscriber.java @@ -1,48 +1,26 @@ package io.github.jaredpetersen.kafkaconnectredis.source.listener.subscriber; -import io.github.jaredpetersen.kafkaconnectredis.source.listener.RedisMessage; import io.lettuce.core.cluster.pubsub.StatefulRedisClusterPubSubConnection; import java.util.List; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -public class RedisClusterChannelSubscriber implements RedisSubscriber { - private final StatefulRedisClusterPubSubConnection redisClusterPubSubConnection; - private final List channels; +import java.util.concurrent.ConcurrentLinkedQueue; +/** + * Redis cluster-aware pub/sub subscriber that listens to channels and caches the retrieved messages for later + * retrieval. + */ +public class RedisClusterChannelSubscriber extends RedisSubscriber { + /** + * Create a cluster-aware subscriber that listens to channels. + * + * @param redisClusterPubSubConnection Cluster pub/sub connection used to facilitate the subscription + * @param channels Channels to subscribe and listen to + */ public RedisClusterChannelSubscriber( - StatefulRedisClusterPubSubConnection redisClusterPubSubConnection, - List channels) { - this.redisClusterPubSubConnection = redisClusterPubSubConnection; - this.channels = channels; - } - - @Override - public Mono subscribe() { - return redisClusterPubSubConnection.reactive() - .upstream() - .commands() - .subscribe(channels.toArray(new String[0])) - .flux() - .then(); - } - - @Override - public Mono unsubscribe() { - return redisClusterPubSubConnection.reactive() - .upstream() - .commands() - .unsubscribe(channels.toArray(new String[0])) - .flux() - .then(); - } - - @Override - public Flux observe() { - return redisClusterPubSubConnection.reactive().observeChannels() - .map(channelMessage -> RedisMessage.builder() - .channel(channelMessage.getChannel()) - .message(channelMessage.getMessage()) - .build()); + StatefulRedisClusterPubSubConnection redisClusterPubSubConnection, + List channels + ) { + super(new ConcurrentLinkedQueue<>()); + redisClusterPubSubConnection.addListener(new RedisClusterListener(this.messageQueue)); + redisClusterPubSubConnection.sync().upstream().commands().subscribe(channels.toArray(new String[0])); } } diff --git a/src/main/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/subscriber/RedisClusterListener.java b/src/main/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/subscriber/RedisClusterListener.java new file mode 100644 index 0000000..782b5c7 --- /dev/null +++ b/src/main/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/subscriber/RedisClusterListener.java @@ -0,0 +1,42 @@ +package io.github.jaredpetersen.kafkaconnectredis.source.listener.subscriber; + +import io.github.jaredpetersen.kafkaconnectredis.source.listener.RedisMessage; +import io.lettuce.core.cluster.models.partitions.RedisClusterNode; +import io.lettuce.core.cluster.pubsub.RedisClusterPubSubListener; +import java.util.concurrent.ConcurrentLinkedQueue; + +class RedisClusterListener extends RedisListener implements RedisClusterPubSubListener { + public RedisClusterListener(ConcurrentLinkedQueue messageQueue) { + super(messageQueue); + } + + @Override + public void message(RedisClusterNode node, String channel, String message) { + message(channel, message); + } + + @Override + public void message(RedisClusterNode node, String pattern, String channel, String message) { + message(pattern, channel, message); + } + + @Override + public void subscribed(RedisClusterNode node, String channel, long count) { + subscribed(channel); + } + + @Override + public void psubscribed(RedisClusterNode node, String pattern, long count) { + psubscribed(pattern); + } + + @Override + public void unsubscribed(RedisClusterNode node, String channel, long count) { + unsubscribed(channel); + } + + @Override + public void punsubscribed(RedisClusterNode node, String pattern, long count) { + punsubscribed(pattern); + } +} diff --git a/src/main/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/subscriber/RedisClusterPatternSubscriber.java b/src/main/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/subscriber/RedisClusterPatternSubscriber.java index b0591fc..fef9435 100644 --- a/src/main/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/subscriber/RedisClusterPatternSubscriber.java +++ b/src/main/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/subscriber/RedisClusterPatternSubscriber.java @@ -1,49 +1,26 @@ package io.github.jaredpetersen.kafkaconnectredis.source.listener.subscriber; -import io.github.jaredpetersen.kafkaconnectredis.source.listener.RedisMessage; import io.lettuce.core.cluster.pubsub.StatefulRedisClusterPubSubConnection; import java.util.List; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -public class RedisClusterPatternSubscriber implements RedisSubscriber { - private final StatefulRedisClusterPubSubConnection redisClusterPubSubConnection; - private final List patterns; +import java.util.concurrent.ConcurrentLinkedQueue; +/** + * Redis cluster-aware pub/sub subscriber that listens to patterns and caches the retrieved messages for later + * retrieval. + */ +public class RedisClusterPatternSubscriber extends RedisSubscriber { + /** + * Create a cluster-aware subscriber that listens to patterns. + * + * @param redisClusterPubSubConnection Cluster pub/sub connection used to facilitate the subscription + * @param patterns Patterns to subscribe and listen to + */ public RedisClusterPatternSubscriber( - StatefulRedisClusterPubSubConnection redisClusterPubSubConnection, - List patterns) { - this.redisClusterPubSubConnection = redisClusterPubSubConnection; - this.patterns = patterns; - } - - @Override - public Mono subscribe() { - return redisClusterPubSubConnection.reactive() - .upstream() - .commands() - .psubscribe(patterns.toArray(new String[0])) - .flux() - .then(); - } - - @Override - public Mono unsubscribe() { - return redisClusterPubSubConnection.reactive() - .upstream() - .commands() - .punsubscribe(patterns.toArray(new String[0])) - .flux() - .then(); - } - - @Override - public Flux observe() { - return redisClusterPubSubConnection.reactive().observePatterns() - .map(channelMessage -> RedisMessage.builder() - .channel(channelMessage.getChannel()) - .pattern(channelMessage.getPattern()) - .message(channelMessage.getMessage()) - .build()); + StatefulRedisClusterPubSubConnection redisClusterPubSubConnection, + List patterns + ) { + super(new ConcurrentLinkedQueue<>()); + redisClusterPubSubConnection.addListener(new RedisClusterListener(this.messageQueue)); + redisClusterPubSubConnection.sync().upstream().commands().psubscribe(patterns.toArray(new String[0])); } } diff --git a/src/main/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/subscriber/RedisListener.java b/src/main/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/subscriber/RedisListener.java new file mode 100644 index 0000000..48f596e --- /dev/null +++ b/src/main/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/subscriber/RedisListener.java @@ -0,0 +1,51 @@ +package io.github.jaredpetersen.kafkaconnectredis.source.listener.subscriber; + +import io.github.jaredpetersen.kafkaconnectredis.source.listener.RedisMessage; +import java.util.concurrent.ConcurrentLinkedQueue; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +abstract class RedisListener { + private static final Logger LOG = LoggerFactory.getLogger(RedisListener.class); + + private final ConcurrentLinkedQueue messageQueue; + + public RedisListener(ConcurrentLinkedQueue messageQueue) { + this.messageQueue = messageQueue; + } + + public void message(String channel, String message) { + final RedisMessage redisMessage = RedisMessage.builder() + .channel(channel) + .message(message) + .build(); + + messageQueue.add(redisMessage); + } + + public void message(String pattern, String channel, String message) { + final RedisMessage redisMessage = RedisMessage.builder() + .pattern(pattern) + .channel(channel) + .message(message) + .build(); + + messageQueue.add(redisMessage); + } + + public void subscribed(String channel) { + LOG.info("subscribed to channel {}", channel); + } + + public void psubscribed(String pattern) { + LOG.info("psubscribed to pattern {}", pattern); + } + + public void unsubscribed(String channel) { + LOG.info("unsubscribed from channel {}", channel); + } + + public void punsubscribed(String pattern) { + LOG.info("unsubscribed from pattern {}", pattern); + } +} diff --git a/src/main/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/subscriber/RedisPatternSubscriber.java b/src/main/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/subscriber/RedisPatternSubscriber.java index 33622bf..a180521 100644 --- a/src/main/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/subscriber/RedisPatternSubscriber.java +++ b/src/main/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/subscriber/RedisPatternSubscriber.java @@ -1,41 +1,25 @@ package io.github.jaredpetersen.kafkaconnectredis.source.listener.subscriber; -import io.github.jaredpetersen.kafkaconnectredis.source.listener.RedisMessage; import io.lettuce.core.pubsub.StatefulRedisPubSubConnection; import java.util.List; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -public class RedisPatternSubscriber implements RedisSubscriber { - private final StatefulRedisPubSubConnection redisPubSubConnection; - private final List patterns; +import java.util.concurrent.ConcurrentLinkedQueue; +/** + * Redis pub/sub subscriber that listens to patterns and caches the retrieved messages for later retrieval. + */ +public class RedisPatternSubscriber extends RedisSubscriber { + /** + * Create a subscriber that listens to patterns. + * + * @param redisPubSubConnection Pub/sub connection used to facilitate the subscription + * @param patterns Patterns to subscribe and listen to + */ public RedisPatternSubscriber( - StatefulRedisPubSubConnection redisPubSubConnection, - List patterns) { - this.redisPubSubConnection = redisPubSubConnection; - this.patterns = patterns; - } - - @Override - public Mono subscribe() { - return redisPubSubConnection.reactive() - .psubscribe(patterns.toArray(new String[0])); - } - - @Override - public Mono unsubscribe() { - return redisPubSubConnection.reactive() - .punsubscribe(patterns.toArray(new String[0])); - } - - @Override - public Flux observe() { - return redisPubSubConnection.reactive().observePatterns() - .map(channelMessage -> RedisMessage.builder() - .channel(channelMessage.getChannel()) - .pattern(channelMessage.getPattern()) - .message(channelMessage.getMessage()) - .build()); + StatefulRedisPubSubConnection redisPubSubConnection, + List patterns + ) { + super(new ConcurrentLinkedQueue<>()); + redisPubSubConnection.addListener(new RedisStandaloneListener(this.messageQueue)); + redisPubSubConnection.sync().psubscribe(patterns.toArray(new String[0])); } } diff --git a/src/main/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/subscriber/RedisStandaloneListener.java b/src/main/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/subscriber/RedisStandaloneListener.java new file mode 100644 index 0000000..a876b33 --- /dev/null +++ b/src/main/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/subscriber/RedisStandaloneListener.java @@ -0,0 +1,31 @@ +package io.github.jaredpetersen.kafkaconnectredis.source.listener.subscriber; + +import io.github.jaredpetersen.kafkaconnectredis.source.listener.RedisMessage; +import io.lettuce.core.pubsub.RedisPubSubListener; +import java.util.concurrent.ConcurrentLinkedQueue; + +class RedisStandaloneListener extends RedisListener implements RedisPubSubListener { + public RedisStandaloneListener(ConcurrentLinkedQueue messageQueue) { + super(messageQueue); + } + + @Override + public void subscribed(String channel, long count) { + subscribed(channel); + } + + @Override + public void psubscribed(String pattern, long count) { + psubscribed(pattern); + } + + @Override + public void unsubscribed(String channel, long count) { + unsubscribed(channel); + } + + @Override + public void punsubscribed(String pattern, long count) { + punsubscribed(pattern); + } +} diff --git a/src/main/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/subscriber/RedisSubscriber.java b/src/main/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/subscriber/RedisSubscriber.java index d324c17..de3b446 100644 --- a/src/main/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/subscriber/RedisSubscriber.java +++ b/src/main/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/subscriber/RedisSubscriber.java @@ -1,28 +1,24 @@ package io.github.jaredpetersen.kafkaconnectredis.source.listener.subscriber; import io.github.jaredpetersen.kafkaconnectredis.source.listener.RedisMessage; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; +import java.util.concurrent.ConcurrentLinkedQueue; -public interface RedisSubscriber { - /** - * Subscribe to Redis Pub/Sub channels. - * - * @return Mono to indicate subscription is complete. - */ - Mono subscribe(); +/** + * Abstract Redis subscriber to help facilitate internal caching of emitted messages. + */ +public abstract class RedisSubscriber { + final ConcurrentLinkedQueue messageQueue; - /** - * Unsubscribe from Redis Pub/Sub channels. - * - * @return Mono to indicate unsubscription is complete. - */ - Mono unsubscribe(); + public RedisSubscriber(ConcurrentLinkedQueue messageQueue) { + this.messageQueue = messageQueue; + } /** - * Listen to subscribed Redis Pub/Sub channels and emit messages reactively.f + * Retrieve a single cached message that was emitted to the subscriber and remove it from the cache. * - * @return Flux of emitted Redis subscription messages. + * @return Cached message or null if nothing is available */ - Flux observe(); + public RedisMessage poll() { + return messageQueue.poll(); + } } diff --git a/src/test/integration/java/io/github/jaredpetersen/kafkaconnectredis/sink/RedisSinkConnectorIT.java b/src/test/integration/java/io/github/jaredpetersen/kafkaconnectredis/sink/RedisSinkConnectorIT.java index eb3461a..3fa3815 100644 --- a/src/test/integration/java/io/github/jaredpetersen/kafkaconnectredis/sink/RedisSinkConnectorIT.java +++ b/src/test/integration/java/io/github/jaredpetersen/kafkaconnectredis/sink/RedisSinkConnectorIT.java @@ -1,7 +1,6 @@ package io.github.jaredpetersen.kafkaconnectredis.sink; import io.github.jaredpetersen.kafkaconnectredis.sink.config.RedisSinkConfig; -import io.github.jaredpetersen.kafkaconnectredis.source.RedisSourceConnector; import io.github.jaredpetersen.kafkaconnectredis.util.VersionUtil; import java.util.HashMap; import java.util.List; @@ -14,21 +13,21 @@ public class RedisSinkConnectorIT { @Test - public void versionReturnsVersion() { + void versionReturnsVersion() { final RedisSinkConnector sinkConnector = new RedisSinkConnector(); assertEquals(VersionUtil.getVersion(), sinkConnector.version()); } @Test - public void taskClassReturnsTaskClass() { + void taskClassReturnsTaskClass() { final RedisSinkConnector sinkConnector = new RedisSinkConnector(); assertEquals(RedisSinkTask.class, sinkConnector.taskClass()); } @Test - public void taskConfigsReturnsTaskConfigs() { + void taskConfigsReturnsTaskConfigs() { final RedisSinkConnector sinkConnector = new RedisSinkConnector(); final Map connectorConfig = new HashMap<>(); @@ -46,24 +45,23 @@ public void taskConfigsReturnsTaskConfigs() { } @Test - public void startThrowsConnectExceptionForInvalidConfig() { - final RedisSourceConnector sourceConnector = new RedisSourceConnector(); + void startThrowsConnectExceptionForInvalidConfig() { + final RedisSinkConnector sinkConnector = new RedisSinkConnector(); final Map connectorConfig = new HashMap<>(); - connectorConfig.put("redis.uri", "redis://localhost:6379"); - final ConnectException thrown = assertThrows(ConnectException.class, () -> sourceConnector.start(connectorConfig)); + final ConnectException thrown = assertThrows(ConnectException.class, () -> sinkConnector.start(connectorConfig)); assertEquals("connector configuration error", thrown.getMessage()); } @Test - public void stopDoesNothing() { + void stopDoesNothing() { final RedisSinkConnector sinkConnector = new RedisSinkConnector(); sinkConnector.stop(); } @Test - public void configReturnsConfigDefinition() { + void configReturnsConfigDefinition() { final RedisSinkConnector sinkConnector = new RedisSinkConnector(); assertEquals(RedisSinkConfig.CONFIG_DEF, sinkConnector.config()); diff --git a/src/test/integration/java/io/github/jaredpetersen/kafkaconnectredis/sink/RedisSinkTaskIT.java b/src/test/integration/java/io/github/jaredpetersen/kafkaconnectredis/sink/RedisSinkTaskIT.java index c83d3b1..e55718b 100644 --- a/src/test/integration/java/io/github/jaredpetersen/kafkaconnectredis/sink/RedisSinkTaskIT.java +++ b/src/test/integration/java/io/github/jaredpetersen/kafkaconnectredis/sink/RedisSinkTaskIT.java @@ -29,7 +29,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows; @Testcontainers -public class RedisSinkTaskIT { +class RedisSinkTaskIT { @Container private static final RedisContainer REDIS_STANDALONE = new RedisContainer(); @@ -72,7 +72,7 @@ static void beforeAll() { } @AfterEach - public void afterEach() { + void afterEach() { REDIS_STANDALONE_COMMANDS.flushall(); REDIS_CLUSTER_COMMANDS.flushall(); } @@ -87,14 +87,14 @@ static void afterAll() { } @Test - public void versionReturnsVersion() { + void versionReturnsVersion() { final RedisSinkTask sinkTask = new RedisSinkTask(); assertEquals(VersionUtil.getVersion(), sinkTask.version()); } @Test - public void putRecordsAppliesCommandsToStandalone() { + void putRecordsAppliesCommandsToStandalone() { // Set up task config final Map config = new HashMap<>(); config.put("redis.uri", REDIS_STANDALONE_URI); @@ -123,7 +123,7 @@ public void putRecordsAppliesCommandsToStandalone() { } @Test - public void putRecordsAppliesCommandsToCluster() { + void putRecordsAppliesCommandsToCluster() { // Set up task config final Map config = new HashMap<>(); config.put("redis.uri", REDIS_CLUSTER_URI); @@ -152,7 +152,7 @@ public void putRecordsAppliesCommandsToCluster() { } @Test - public void putEmptyRecordsDoesNothingToStandalone() { + void putEmptyRecordsDoesNothingToStandalone() { // Set up task config final Map config = new HashMap<>(); config.put("redis.uri", REDIS_STANDALONE_URI); @@ -170,7 +170,7 @@ public void putEmptyRecordsDoesNothingToStandalone() { } @Test - public void putEmptyRecordsDoesNothingToCluster() { + void putEmptyRecordsDoesNothingToCluster() { // Set up task config final Map config = new HashMap<>(); config.put("redis.uri", REDIS_CLUSTER_URI); @@ -188,7 +188,7 @@ public void putEmptyRecordsDoesNothingToCluster() { } @Test - public void startThrowsConnectExceptionForInvalidConfig() { + void startThrowsConnectExceptionForInvalidConfig() { final RedisSinkTask sinkTask = new RedisSinkTask(); final Map connectorConfig = new HashMap<>(); @@ -199,7 +199,7 @@ public void startThrowsConnectExceptionForInvalidConfig() { } @Test - public void stopClosesStandalone() { + void stopClosesStandalone() { // Set up task config final Map config = new HashMap<>(); config.put("redis.uri", REDIS_STANDALONE_URI); @@ -214,7 +214,7 @@ public void stopClosesStandalone() { } @Test - public void stopClosesCluster() { + void stopClosesCluster() { // Set up task config final Map config = new HashMap<>(); config.put("redis.uri", REDIS_CLUSTER_URI); diff --git a/src/test/integration/java/io/github/jaredpetersen/kafkaconnectredis/sink/writer/WriterIT.java b/src/test/integration/java/io/github/jaredpetersen/kafkaconnectredis/sink/writer/WriterIT.java index d30a474..2daf1d0 100644 --- a/src/test/integration/java/io/github/jaredpetersen/kafkaconnectredis/sink/writer/WriterIT.java +++ b/src/test/integration/java/io/github/jaredpetersen/kafkaconnectredis/sink/writer/WriterIT.java @@ -102,7 +102,7 @@ static boolean isValidPttl(long pttl, long expectedPttl) { } @Test - public void writeSetCommandAppliesCommandToStandalone() { + void writeSetCommandAppliesCommandToStandalone() { final RedisSetCommand redisCommand = RedisSetCommand.builder() .payload(RedisSetCommand.Payload.builder() .key("{user.1}.username") @@ -117,7 +117,7 @@ public void writeSetCommandAppliesCommandToStandalone() { } @Test - public void writeSetCommandAppliesCommandToCluster() { + void writeSetCommandAppliesCommandToCluster() { final RedisSetCommand redisCommand = RedisSetCommand.builder() .payload(RedisSetCommand.Payload.builder() .key("{user.1}.username") @@ -132,7 +132,7 @@ public void writeSetCommandAppliesCommandToCluster() { } @Test - public void writeSetWithExpireSecondsCommandAppliesCommandToStandalone() { + void writeSetWithExpireSecondsCommandAppliesCommandToStandalone() { final RedisSetCommand redisCommand = RedisSetCommand.builder() .payload(RedisSetCommand.Payload.builder() .key("{user.1}.username") @@ -155,7 +155,7 @@ public void writeSetWithExpireSecondsCommandAppliesCommandToStandalone() { } @Test - public void writeSetWithExpireSecondsCommandAppliesCommandToCluster() { + void writeSetWithExpireSecondsCommandAppliesCommandToCluster() { final RedisSetCommand redisCommand = RedisSetCommand.builder() .payload(RedisSetCommand.Payload.builder() .key("{user.1}.username") @@ -178,7 +178,7 @@ public void writeSetWithExpireSecondsCommandAppliesCommandToCluster() { } @Test - public void writeSetWithExpireMillisecondsCommandAppliesCommandToStandalone() { + void writeSetWithExpireMillisecondsCommandAppliesCommandToStandalone() { final RedisSetCommand redisCommand = RedisSetCommand.builder() .payload(RedisSetCommand.Payload.builder() .key("{user.1}.username") @@ -201,7 +201,7 @@ public void writeSetWithExpireMillisecondsCommandAppliesCommandToStandalone() { } @Test - public void writeSetWithExpireMillisecondsCommandAppliesCommandToCluster() { + void writeSetWithExpireMillisecondsCommandAppliesCommandToCluster() { final RedisSetCommand redisCommand = RedisSetCommand.builder() .payload(RedisSetCommand.Payload.builder() .key("{user.1}.username") @@ -224,7 +224,7 @@ public void writeSetWithExpireMillisecondsCommandAppliesCommandToCluster() { } @Test - public void writeSetWithExpireKeepTtlCommandAppliesCommandToStandalone() { + void writeSetWithExpireKeepTtlCommandAppliesCommandToStandalone() { final String key = "{user.1}.username"; final long originalPttl = 21_000L; @@ -248,7 +248,7 @@ public void writeSetWithExpireKeepTtlCommandAppliesCommandToStandalone() { } @Test - public void writeSetWithExpireKeepTtlCommandAppliesCommandToCluster() { + void writeSetWithExpireKeepTtlCommandAppliesCommandToCluster() { final String key = "{user.1}.username"; final long originalPttl = 21_000L; @@ -272,7 +272,7 @@ public void writeSetWithExpireKeepTtlCommandAppliesCommandToCluster() { } @Test - public void writeSetWithNxConditionCommandAppliesCommandToStandalone() { + void writeSetWithNxConditionCommandAppliesCommandToStandalone() { final String key = "{user.1}.username"; REDIS_STANDALONE_COMMANDS.set(key, "artistjanitor90"); @@ -297,7 +297,7 @@ public void writeSetWithNxConditionCommandAppliesCommandToStandalone() { } @Test - public void writeSetWithNxConditionCommandAppliesCommandToCluster() { + void writeSetWithNxConditionCommandAppliesCommandToCluster() { final String key = "{user.1}.username"; REDIS_CLUSTER_COMMANDS.set(key, "artistjanitor90"); @@ -322,7 +322,7 @@ public void writeSetWithNxConditionCommandAppliesCommandToCluster() { } @Test - public void writeSetWithXxConditionCommandAppliesCommandToStandalone() { + void writeSetWithXxConditionCommandAppliesCommandToStandalone() { final String key = "{user.1}.username"; REDIS_STANDALONE_COMMANDS.set(key, "artistjanitor90"); @@ -349,7 +349,7 @@ public void writeSetWithXxConditionCommandAppliesCommandToStandalone() { } @Test - public void writeSetWithXxConditionCommandAppliesCommandToCluster() { + void writeSetWithXxConditionCommandAppliesCommandToCluster() { final String key = "{user.1}.username"; REDIS_CLUSTER_COMMANDS.set(key, "artistjanitor90"); @@ -376,7 +376,7 @@ public void writeSetWithXxConditionCommandAppliesCommandToCluster() { } @Test - public void writeExpireCommandAppliesCommandToStandalone() { + void writeExpireCommandAppliesCommandToStandalone() { final RedisExpireCommand redisCommand = RedisExpireCommand.builder() .payload(RedisExpireCommand.Payload.builder() .key("product.milk") @@ -395,7 +395,7 @@ public void writeExpireCommandAppliesCommandToStandalone() { } @Test - public void writeExpireCommandAppliesCommandToCluster() { + void writeExpireCommandAppliesCommandToCluster() { final RedisExpireCommand redisCommand = RedisExpireCommand.builder() .payload(RedisExpireCommand.Payload.builder() .key("product.milk") @@ -414,7 +414,7 @@ public void writeExpireCommandAppliesCommandToCluster() { } @Test - public void writeExpireatCommandAppliesCommandToStandalone() { + void writeExpireatCommandAppliesCommandToStandalone() { final long secondsIntoFuture = 50L; final long timestamp = Instant.now().plusSeconds(secondsIntoFuture).getEpochSecond(); final RedisExpireatCommand redisCommand = RedisExpireatCommand.builder() @@ -435,7 +435,7 @@ public void writeExpireatCommandAppliesCommandToStandalone() { } @Test - public void writeExpireatCommandAppliesCommandToCluster() { + void writeExpireatCommandAppliesCommandToCluster() { final long secondsIntoFuture = 50L; final long timestamp = Instant.now().plusSeconds(secondsIntoFuture).getEpochSecond(); final RedisExpireatCommand redisCommand = RedisExpireatCommand.builder() @@ -456,7 +456,7 @@ public void writeExpireatCommandAppliesCommandToCluster() { } @Test - public void writePexpireCommandAppliesCommandToStandalone() { + void writePexpireCommandAppliesCommandToStandalone() { final RedisPexpireCommand redisCommand = RedisPexpireCommand.builder() .payload(RedisPexpireCommand.Payload.builder() .key("product.milk") @@ -475,7 +475,7 @@ public void writePexpireCommandAppliesCommandToStandalone() { } @Test - public void writePexpireCommandAppliesCommandToCluster() { + void writePexpireCommandAppliesCommandToCluster() { final RedisPexpireCommand redisCommand = RedisPexpireCommand.builder() .payload(RedisPexpireCommand.Payload.builder() .key("product.milk") @@ -494,7 +494,7 @@ public void writePexpireCommandAppliesCommandToCluster() { } @Test - public void writeSaddCommandAppliesCommandToStandalone() { + void writeSaddCommandAppliesCommandToStandalone() { final RedisSaddCommand redisCommand = RedisSaddCommand.builder() .payload(RedisSaddCommand.Payload.builder() .key("boats") @@ -510,7 +510,7 @@ public void writeSaddCommandAppliesCommandToStandalone() { } @Test - public void writeSaddCommandAppliesCommandToCluster() { + void writeSaddCommandAppliesCommandToCluster() { final RedisSaddCommand redisCommand = RedisSaddCommand.builder() .payload(RedisSaddCommand.Payload.builder() .key("boats") @@ -526,7 +526,7 @@ public void writeSaddCommandAppliesCommandToCluster() { } @Test - public void writeGeoaddCommandAppliesCommandToStandalone() { + void writeGeoaddCommandAppliesCommandToStandalone() { final RedisGeoaddCommand redisCommand = RedisGeoaddCommand.builder() .payload(RedisGeoaddCommand.Payload.builder() .key("Sicily") @@ -562,7 +562,7 @@ public void writeGeoaddCommandAppliesCommandToStandalone() { } @Test - public void writeGeoaddCommandAppliesCommandToCluster() { + void writeGeoaddCommandAppliesCommandToCluster() { final RedisGeoaddCommand redisCommand = RedisGeoaddCommand.builder() .payload(RedisGeoaddCommand.Payload.builder() .key("Sicily") @@ -598,7 +598,7 @@ public void writeGeoaddCommandAppliesCommandToCluster() { } @Test - public void writeGeoaddCommandRetunsErrorForMissingMember() { + void writeGeoaddCommandRetunsErrorForMissingMember() { final RedisGeoaddCommand redisCommand = RedisGeoaddCommand.builder() .payload(RedisGeoaddCommand.Payload.builder() .key("Sicily") @@ -623,7 +623,7 @@ public void writeGeoaddCommandRetunsErrorForMissingMember() { } @Test - public void writeArbitraryCommandAppliesCommandToStandalone() { + void writeArbitraryCommandAppliesCommandToStandalone() { final RedisArbitraryCommand redisCommand = RedisArbitraryCommand.builder() .payload(RedisArbitraryCommand.Payload.builder() .command("SET") @@ -639,7 +639,7 @@ public void writeArbitraryCommandAppliesCommandToStandalone() { } @Test - public void writeArbitraryCommandAppliesCommandToCluster() { + void writeArbitraryCommandAppliesCommandToCluster() { final RedisArbitraryCommand redisCommand = RedisArbitraryCommand.builder() .payload(RedisArbitraryCommand.Payload.builder() .command("SET") diff --git a/src/test/integration/java/io/github/jaredpetersen/kafkaconnectredis/source/RedisSourceConnectorIT.java b/src/test/integration/java/io/github/jaredpetersen/kafkaconnectredis/source/RedisSourceConnectorIT.java index 802f30e..749208f 100644 --- a/src/test/integration/java/io/github/jaredpetersen/kafkaconnectredis/source/RedisSourceConnectorIT.java +++ b/src/test/integration/java/io/github/jaredpetersen/kafkaconnectredis/source/RedisSourceConnectorIT.java @@ -11,23 +11,23 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; -public class RedisSourceConnectorIT { +class RedisSourceConnectorIT { @Test - public void versionReturnsVersion() { + void versionReturnsVersion() { final RedisSourceConnector sourceConnector = new RedisSourceConnector(); assertEquals(VersionUtil.getVersion(), sourceConnector.version()); } @Test - public void taskClassReturnsTaskClass() { + void taskClassReturnsTaskClass() { final RedisSourceConnector sourceConnector = new RedisSourceConnector(); assertEquals(RedisSourceTask.class, sourceConnector.taskClass()); } @Test - public void taskConfigsReturnsTaskConfigs() { + void taskConfigsReturnsTaskConfigs() { final RedisSourceConnector sourceConnector = new RedisSourceConnector(); final Map connectorConfig = new HashMap<>(); @@ -46,7 +46,7 @@ public void taskConfigsReturnsTaskConfigs() { } @Test - public void taskConfigsReturnsPartitionedTaskConfigs() { + void taskConfigsReturnsPartitionedTaskConfigs() { final RedisSourceConnector sourceConnector = new RedisSourceConnector(); final Map connectorConfig = new HashMap<>(); @@ -80,7 +80,7 @@ public void taskConfigsReturnsPartitionedTaskConfigs() { } @Test - public void taskConfigsReturnsPartitionedTaskConfigsWithExcessiveMaxTask() { + void taskConfigsReturnsPartitionedTaskConfigsWithExcessiveMaxTask() { final RedisSourceConnector sourceConnector = new RedisSourceConnector(); final Map connectorConfig = new HashMap<>(); @@ -122,7 +122,7 @@ public void taskConfigsReturnsPartitionedTaskConfigsWithExcessiveMaxTask() { } @Test - public void startThrowsConnectExceptionForInvalidConfig() { + void startThrowsConnectExceptionForInvalidConfig() { final RedisSourceConnector sourceConnector = new RedisSourceConnector(); final Map connectorConfig = new HashMap<>(); @@ -133,13 +133,13 @@ public void startThrowsConnectExceptionForInvalidConfig() { } @Test - public void stopDoesNothing() { + void stopDoesNothing() { final RedisSourceConnector sourceConnector = new RedisSourceConnector(); sourceConnector.stop(); } @Test - public void configReturnsConfigDefinition() { + void configReturnsConfigDefinition() { final RedisSourceConnector sourceConnector = new RedisSourceConnector(); assertEquals(RedisSourceConfig.CONFIG_DEF, sourceConnector.config()); diff --git a/src/test/integration/java/io/github/jaredpetersen/kafkaconnectredis/source/RedisSourceTaskIT.java b/src/test/integration/java/io/github/jaredpetersen/kafkaconnectredis/source/RedisSourceTaskIT.java index 17f65f3..100624a 100644 --- a/src/test/integration/java/io/github/jaredpetersen/kafkaconnectredis/source/RedisSourceTaskIT.java +++ b/src/test/integration/java/io/github/jaredpetersen/kafkaconnectredis/source/RedisSourceTaskIT.java @@ -3,14 +3,15 @@ import io.github.jaredpetersen.kafkaconnectredis.testutil.RedisContainer; import io.github.jaredpetersen.kafkaconnectredis.util.VersionUtil; import io.lettuce.core.RedisClient; -import io.lettuce.core.api.reactive.RedisReactiveCommands; +import io.lettuce.core.api.sync.RedisCommands; import io.lettuce.core.cluster.RedisClusterClient; -import io.lettuce.core.cluster.api.reactive.RedisClusterReactiveCommands; +import io.lettuce.core.cluster.api.sync.RedisClusterCommands; import io.lettuce.core.cluster.pubsub.StatefulRedisClusterPubSubConnection; import io.lettuce.core.pubsub.StatefulRedisPubSubConnection; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.UUID; import org.apache.kafka.connect.errors.ConnectException; import org.apache.kafka.connect.source.SourceRecord; import org.junit.jupiter.api.AfterAll; @@ -19,14 +20,12 @@ import org.junit.jupiter.api.Test; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; -import reactor.core.publisher.Mono; -import reactor.test.StepVerifier; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; @Testcontainers -public class RedisSourceTaskIT { +class RedisSourceTaskIT { @Container private static final RedisContainer REDIS_STANDALONE = new RedisContainer(); @@ -35,12 +34,12 @@ public class RedisSourceTaskIT { private static String REDIS_STANDALONE_URI; private static RedisClient REDIS_STANDALONE_CLIENT; - private static RedisReactiveCommands REDIS_STANDALONE_PUB_COMMANDS; + private static RedisCommands REDIS_STANDALONE_PUB_COMMANDS; private static StatefulRedisPubSubConnection REDIS_STANDALONE_SUB_CONNECTION; private static String REDIS_CLUSTER_URI; private static RedisClusterClient REDIS_CLUSTER_CLIENT; - private static RedisClusterReactiveCommands REDIS_CLUSTER_PUB_COMMANDS; + private static RedisClusterCommands REDIS_CLUSTER_PUB_COMMANDS; private static StatefulRedisClusterPubSubConnection REDIS_CLUSTER_SUB_CONNECTION; @BeforeAll @@ -50,7 +49,7 @@ static void beforeAll() { final StatefulRedisPubSubConnection redisStandalonePubConnection = REDIS_STANDALONE_CLIENT .connectPubSub(); - REDIS_STANDALONE_PUB_COMMANDS = redisStandalonePubConnection.reactive(); + REDIS_STANDALONE_PUB_COMMANDS = redisStandalonePubConnection.sync(); REDIS_STANDALONE_SUB_CONNECTION = REDIS_STANDALONE_CLIENT.connectPubSub(); @@ -59,16 +58,16 @@ static void beforeAll() { final StatefulRedisClusterPubSubConnection redisClusterPubConnection = REDIS_CLUSTER_CLIENT .connectPubSub(); - REDIS_CLUSTER_PUB_COMMANDS = redisClusterPubConnection.reactive(); + REDIS_CLUSTER_PUB_COMMANDS = redisClusterPubConnection.sync(); REDIS_CLUSTER_SUB_CONNECTION = REDIS_CLUSTER_CLIENT.connectPubSub(); REDIS_CLUSTER_SUB_CONNECTION.setNodeMessagePropagation(true); } @AfterEach - public void afterEach() { - REDIS_STANDALONE_PUB_COMMANDS.flushall().block(); - REDIS_CLUSTER_PUB_COMMANDS.flushall().block(); + void afterEach() { + REDIS_STANDALONE_PUB_COMMANDS.flushall(); + REDIS_CLUSTER_PUB_COMMANDS.flushall(); } @AfterAll @@ -81,14 +80,14 @@ static void afterAll() { } @Test - public void versionReturnsVersion() { + void versionReturnsVersion() { final RedisSourceTask sourceTask = new RedisSourceTask(); assertEquals(VersionUtil.getVersion(), sourceTask.version()); } @Test - public void pollRetrievesChannelRecordsFromStandalone() throws InterruptedException { + void pollRetrievesChannelRecordsFromStandalone() throws InterruptedException { final RedisSourceTask sourceTask = new RedisSourceTask(); final Map config = new HashMap<>(); @@ -102,14 +101,9 @@ public void pollRetrievesChannelRecordsFromStandalone() throws InterruptedExcept Thread.sleep(1000L); - final Mono publish = REDIS_STANDALONE_PUB_COMMANDS.publish("boats", "fishing") - .then(REDIS_STANDALONE_PUB_COMMANDS.publish("boats", "sport")) - .then(REDIS_STANDALONE_PUB_COMMANDS.publish("boats", "speed")) - .then(); - - StepVerifier - .create(publish) - .verifyComplete(); + REDIS_STANDALONE_PUB_COMMANDS.publish("boats", "fishing"); + REDIS_STANDALONE_PUB_COMMANDS.publish("boats", "sport"); + REDIS_STANDALONE_PUB_COMMANDS.publish("boats", "speed"); Thread.sleep(1000L); @@ -119,7 +113,7 @@ public void pollRetrievesChannelRecordsFromStandalone() throws InterruptedExcept } @Test - public void pollRetrievesPatternRecordsFromStandalone() throws InterruptedException { + void pollRetrievesPatternRecordsFromStandalone() throws InterruptedException { final RedisSourceTask sourceTask = new RedisSourceTask(); final Map config = new HashMap<>(); @@ -133,14 +127,9 @@ public void pollRetrievesPatternRecordsFromStandalone() throws InterruptedExcept Thread.sleep(1000L); - final Mono publish = REDIS_STANDALONE_PUB_COMMANDS.publish("boats", "fishing") - .then(REDIS_STANDALONE_PUB_COMMANDS.publish("boats", "sport")) - .then(REDIS_STANDALONE_PUB_COMMANDS.publish("boats", "speed")) - .then(); - - StepVerifier - .create(publish) - .verifyComplete(); + REDIS_STANDALONE_PUB_COMMANDS.publish("boats", "fishing"); + REDIS_STANDALONE_PUB_COMMANDS.publish("boats", "sport"); + REDIS_STANDALONE_PUB_COMMANDS.publish("boats", "speed"); Thread.sleep(1000L); @@ -150,7 +139,7 @@ public void pollRetrievesPatternRecordsFromStandalone() throws InterruptedExcept } @Test - public void pollRetrievesChannelRecordsFromCluster() throws InterruptedException { + void pollRetrievesChannelRecordsFromCluster() throws InterruptedException { final RedisSourceTask sourceTask = new RedisSourceTask(); final Map config = new HashMap<>(); @@ -164,14 +153,9 @@ public void pollRetrievesChannelRecordsFromCluster() throws InterruptedException Thread.sleep(1000L); - final Mono publish = REDIS_CLUSTER_PUB_COMMANDS.publish("boats", "fishing") - .then(REDIS_CLUSTER_PUB_COMMANDS.publish("boats", "sport")) - .then(REDIS_CLUSTER_PUB_COMMANDS.publish("boats", "speed")) - .then(); - - StepVerifier - .create(publish) - .verifyComplete(); + REDIS_CLUSTER_PUB_COMMANDS.publish("boats", "fishing"); + REDIS_CLUSTER_PUB_COMMANDS.publish("boats", "sport"); + REDIS_CLUSTER_PUB_COMMANDS.publish("boats", "speed"); Thread.sleep(1000L); @@ -181,7 +165,7 @@ public void pollRetrievesChannelRecordsFromCluster() throws InterruptedException } @Test - public void pollRetrievesPatternRecordsFromCluster() throws InterruptedException { + void pollRetrievesPatternRecordsFromCluster() throws InterruptedException { final RedisSourceTask sourceTask = new RedisSourceTask(); final Map config = new HashMap<>(); @@ -195,14 +179,9 @@ public void pollRetrievesPatternRecordsFromCluster() throws InterruptedException Thread.sleep(1000L); - final Mono publish = REDIS_CLUSTER_PUB_COMMANDS.publish("boats", "fishing") - .then(REDIS_CLUSTER_PUB_COMMANDS.publish("boats", "sport")) - .then(REDIS_CLUSTER_PUB_COMMANDS.publish("boats", "speed")) - .then(); - - StepVerifier - .create(publish) - .verifyComplete(); + REDIS_CLUSTER_PUB_COMMANDS.publish("boats", "fishing"); + REDIS_CLUSTER_PUB_COMMANDS.publish("boats", "sport"); + REDIS_CLUSTER_PUB_COMMANDS.publish("boats", "speed"); Thread.sleep(1000L); @@ -212,7 +191,33 @@ public void pollRetrievesPatternRecordsFromCluster() throws InterruptedException } @Test - public void pollEmptyReturnsEmptyList() { + void pollStopsWhenHittingInternalMax() throws InterruptedException { + final RedisSourceTask sourceTask = new RedisSourceTask(); + + final Map config = new HashMap<>(); + config.put("topic", "mytopic"); + config.put("redis.uri", REDIS_STANDALONE_URI); + config.put("redis.cluster.enabled", "false"); + config.put("redis.channels", "robots"); + config.put("redis.channels.pattern.enabled", "false"); + + sourceTask.start(config); + + Thread.sleep(1000L); + + for (int i = 0; i < 11_000; i++) { + REDIS_STANDALONE_PUB_COMMANDS.publish("robots", UUID.randomUUID().toString()); + } + + Thread.sleep(1000L); + + final List sourceRecords = sourceTask.poll(); + + assertEquals(10_000, sourceRecords.size()); + } + + @Test + void pollEmptyReturnsEmptyList() { final RedisSourceTask sourceTask = new RedisSourceTask(); final Map config = new HashMap<>(); @@ -230,7 +235,7 @@ public void pollEmptyReturnsEmptyList() { } @Test - public void startThrowsConnectExceptionForInvalidConfig() { + void startThrowsConnectExceptionForInvalidConfig() { final RedisSourceTask sourceTask = new RedisSourceTask(); final Map taskConfig = new HashMap<>(); @@ -241,7 +246,7 @@ public void startThrowsConnectExceptionForInvalidConfig() { } @Test - public void stopStopsStandalone() { + void stopStopsStandalone() { final RedisSourceTask sourceTask = new RedisSourceTask(); final Map config = new HashMap<>(); @@ -256,7 +261,7 @@ public void stopStopsStandalone() { } @Test - public void stopStopsCluster() { + void stopStopsCluster() { final RedisSourceTask sourceTask = new RedisSourceTask(); final Map config = new HashMap<>(); diff --git a/src/test/integration/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/subscriber/RedisChannelSubscriberIT.java b/src/test/integration/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/subscriber/RedisChannelSubscriberIT.java index 1ec344b..b2e18d4 100644 --- a/src/test/integration/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/subscriber/RedisChannelSubscriberIT.java +++ b/src/test/integration/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/subscriber/RedisChannelSubscriberIT.java @@ -3,160 +3,132 @@ import io.github.jaredpetersen.kafkaconnectredis.source.listener.RedisMessage; import io.github.jaredpetersen.kafkaconnectredis.testutil.RedisContainer; import io.lettuce.core.RedisClient; -import io.lettuce.core.api.reactive.RedisReactiveCommands; +import io.lettuce.core.api.sync.RedisCommands; import io.lettuce.core.pubsub.StatefulRedisPubSubConnection; -import java.time.Duration; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; +import java.util.stream.IntStream; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; -import reactor.test.StepVerifier; + +import static org.junit.jupiter.api.Assertions.assertEquals; @Testcontainers -public class RedisChannelSubscriberIT { +class RedisChannelSubscriberIT { @Container private static final RedisContainer REDIS_STANDALONE = new RedisContainer(); private static RedisClient REDIS_STANDALONE_CLIENT; private static StatefulRedisPubSubConnection REDIS_STANDALONE_PUB_CONNECTION; - private static RedisReactiveCommands REDIS_STANDALONE_PUB_COMMANDS; + private static RedisCommands REDIS_STANDALONE_PUB_COMMANDS; private static StatefulRedisPubSubConnection REDIS_STANDALONE_SUB_CONNECTION; @BeforeAll static void beforeAll() { REDIS_STANDALONE_CLIENT = RedisClient.create(REDIS_STANDALONE.getUri()); + } + @BeforeEach + void beforeEach() { REDIS_STANDALONE_PUB_CONNECTION = REDIS_STANDALONE_CLIENT.connectPubSub(); - REDIS_STANDALONE_PUB_COMMANDS = REDIS_STANDALONE_PUB_CONNECTION.reactive(); + REDIS_STANDALONE_PUB_COMMANDS = REDIS_STANDALONE_PUB_CONNECTION.sync(); REDIS_STANDALONE_SUB_CONNECTION = REDIS_STANDALONE_CLIENT.connectPubSub(); } @AfterEach - public void afterEach() { - REDIS_STANDALONE_PUB_COMMANDS.flushall().block(); + void afterEach() { + REDIS_STANDALONE_PUB_COMMANDS.flushall(); + REDIS_STANDALONE_PUB_CONNECTION.close(); + REDIS_STANDALONE_SUB_CONNECTION.close(); } @AfterAll static void afterAll() { - REDIS_STANDALONE_SUB_CONNECTION.close(); REDIS_STANDALONE_CLIENT.shutdown(); } - @Test - public void subscribeSubscribesToChannels() { - final List channels = Arrays.asList("podcasts", "podcasters"); - final RedisSubscriber redisSubscriber = new RedisChannelSubscriber(REDIS_STANDALONE_SUB_CONNECTION, channels); + /** + * Poll the RedisSubscriber until there aren't any messages left to retrieve. + * + * @param redisSubscriber RedisSubscriber to poll + * @return Redis messages retrieved by polling + */ + static List pollUntilEmpty(RedisSubscriber redisSubscriber) { + final List retrievedMessages = new ArrayList<>(); - StepVerifier - .create(REDIS_STANDALONE_PUB_CONNECTION.reactive().pubsubNumsub(channels.toArray(new String[0]))) - .expectNextMatches(channelMap -> - channelMap.get(channels.get(0)) == 0L && channelMap.get(channels.get(1)) == 0L) - .verifyComplete(); - - StepVerifier - .create(redisSubscriber.subscribe()) - .verifyComplete(); - - StepVerifier - .create(REDIS_STANDALONE_PUB_CONNECTION.reactive().pubsubNumsub(channels.toArray(new String[0]))) - .expectNextMatches(channelMap -> - channelMap.get(channels.get(0)) == 1L && channelMap.get(channels.get(1)) == 1L) - .verifyComplete(); - } + while (true) { + final RedisMessage message = redisSubscriber.poll(); - @Test - public void unsubscribeUnsubscribesFromChannels() { - final List channels = Arrays.asList("podcasts", "podcasters"); - final RedisSubscriber redisSubscriber = new RedisChannelSubscriber(REDIS_STANDALONE_SUB_CONNECTION, channels); + if (message == null) { + break; + } - StepVerifier - .create(redisSubscriber.subscribe()) - .verifyComplete(); - - StepVerifier - .create(REDIS_STANDALONE_PUB_CONNECTION.reactive().pubsubNumsub(channels.toArray(new String[0]))) - .expectNextMatches(channelMap -> - channelMap.get(channels.get(0)) == 1L && channelMap.get(channels.get(1)) == 1L) - .verifyComplete(); - - StepVerifier - .create(redisSubscriber.unsubscribe()) - .verifyComplete(); - - StepVerifier - .create(REDIS_STANDALONE_PUB_CONNECTION.reactive().pubsubNumsub(channels.toArray(new String[0]))) - .expectNextMatches(channelMap -> - channelMap.get(channels.get(0)) == 0 && channelMap.get(channels.get(1)) == 0) - .verifyComplete(); + retrievedMessages.add(message); + } + + return retrievedMessages; } @Test - public void observeRetrievesPubSubMessages() { + void pollRetrievesCachedMessagesFromSingleChannelPubSub() throws InterruptedException { final String channel = "podcasts"; final RedisSubscriber redisSubscriber = new RedisChannelSubscriber( REDIS_STANDALONE_SUB_CONNECTION, - Arrays.asList(channel)); - - final Mono publish = Flux - .range(1, 5) - .flatMapSequential(id -> REDIS_STANDALONE_PUB_COMMANDS.publish("podcasts", "podcast-" + id)) - .then(); - - StepVerifier - .create(redisSubscriber.subscribe()) - .verifyComplete(); - - final StepVerifier observeVerifier = StepVerifier - .create(redisSubscriber.observe()) - .expectNext(RedisMessage.builder().channel(channel).message("podcast-1").build()) - .expectNext(RedisMessage.builder().channel(channel).message("podcast-2").build()) - .expectNext(RedisMessage.builder().channel(channel).message("podcast-3").build()) - .expectNext(RedisMessage.builder().channel(channel).message("podcast-4").build()) - .expectNext(RedisMessage.builder().channel(channel).message("podcast-5").build()) - .expectNoEvent(Duration.ofSeconds(2L)) - .thenCancel() - .verifyLater(); - - StepVerifier - .create(publish) - .verifyComplete(); - - observeVerifier.verify(); + Collections.singletonList(channel)); + + final List expectedRedisMessages = IntStream.range(0, 5) + .mapToObj(i -> RedisMessage.builder() + .channel(channel) + .message(UUID.randomUUID().toString()) + .build()) + .collect(Collectors.toList()); + + for (RedisMessage redisMessage : expectedRedisMessages) { + REDIS_STANDALONE_PUB_COMMANDS.publish(redisMessage.getChannel(), redisMessage.getMessage()); + } + + // Subscription is async and we want to make sure all of the messages are available + Thread.sleep(2000); + + final List retrievedMessages = pollUntilEmpty(redisSubscriber); + + assertEquals(expectedRedisMessages, retrievedMessages); } @Test - public void observeRetrievesPubSubMessagesFromMultipleChannels() { + void pollRetrievesCachedMessagesFromMultipleChannelPubSub() throws InterruptedException { final List channels = Arrays.asList("podcasts", "podcasters"); final RedisSubscriber redisSubscriber = new RedisChannelSubscriber(REDIS_STANDALONE_SUB_CONNECTION, channels); - final Mono publish = Flux - .range(1, 5) - .flatMapSequential(id -> REDIS_STANDALONE_PUB_COMMANDS.publish("podcasts", "podcast-" + id) - .then(REDIS_STANDALONE_PUB_COMMANDS.publish("podcasters", "podcaster-" + id))) - .then(); + final List expectedRedisMessages = IntStream.range(0, 5) + .mapToObj(i -> { + final String channel = (i % 2 == 0) ? "podcasts" : "podcasters"; + return RedisMessage.builder() + .channel(channel) + .message(UUID.randomUUID().toString()) + .build(); + }) + .collect(Collectors.toList()); - StepVerifier - .create(redisSubscriber.subscribe()) - .verifyComplete(); + for (RedisMessage redisMessage : expectedRedisMessages) { + REDIS_STANDALONE_PUB_COMMANDS.publish(redisMessage.getChannel(), redisMessage.getMessage()); + } - final StepVerifier observeVerifier = StepVerifier - .create(redisSubscriber.observe()) - .expectNextCount(10) - .expectNoEvent(Duration.ofSeconds(2L)) - .thenCancel() - .verifyLater(); + // Subscription is async and we want to make sure all of the messages are available + Thread.sleep(2000); - StepVerifier - .create(publish) - .verifyComplete(); + final List retrievedMessages = pollUntilEmpty(redisSubscriber); - observeVerifier.verify(); + assertEquals(expectedRedisMessages, retrievedMessages); } } diff --git a/src/test/integration/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/subscriber/RedisClusterChannelSubscriberIT.java b/src/test/integration/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/subscriber/RedisClusterChannelSubscriberIT.java index 413a12d..a26ddec 100644 --- a/src/test/integration/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/subscriber/RedisClusterChannelSubscriberIT.java +++ b/src/test/integration/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/subscriber/RedisClusterChannelSubscriberIT.java @@ -3,161 +3,133 @@ import io.github.jaredpetersen.kafkaconnectredis.source.listener.RedisMessage; import io.github.jaredpetersen.kafkaconnectredis.testutil.RedisContainer; import io.lettuce.core.cluster.RedisClusterClient; -import io.lettuce.core.cluster.api.reactive.RedisClusterReactiveCommands; +import io.lettuce.core.cluster.api.sync.RedisClusterCommands; import io.lettuce.core.cluster.pubsub.StatefulRedisClusterPubSubConnection; -import java.time.Duration; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; +import java.util.stream.IntStream; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; -import reactor.test.StepVerifier; + +import static org.junit.jupiter.api.Assertions.assertEquals; @Testcontainers -public class RedisClusterChannelSubscriberIT { +class RedisClusterChannelSubscriberIT { @Container private static final RedisContainer REDIS_CLUSTER = new RedisContainer().withClusterMode(); private static RedisClusterClient REDIS_CLUSTER_CLIENT; private static StatefulRedisClusterPubSubConnection REDIS_CLUSTER_PUB_CONNECTION; - private static RedisClusterReactiveCommands REDIS_CLUSTER_PUB_COMMANDS; + private static RedisClusterCommands REDIS_CLUSTER_PUB_COMMANDS; private static StatefulRedisClusterPubSubConnection REDIS_CLUSTER_SUB_CONNECTION; @BeforeAll static void beforeAll() { REDIS_CLUSTER_CLIENT = RedisClusterClient.create(REDIS_CLUSTER.getUri()); + } + @BeforeEach + void beforeEach() { REDIS_CLUSTER_PUB_CONNECTION = REDIS_CLUSTER_CLIENT.connectPubSub(); - REDIS_CLUSTER_PUB_COMMANDS = REDIS_CLUSTER_PUB_CONNECTION.reactive(); + REDIS_CLUSTER_PUB_COMMANDS = REDIS_CLUSTER_PUB_CONNECTION.sync(); REDIS_CLUSTER_SUB_CONNECTION = REDIS_CLUSTER_CLIENT.connectPubSub(); REDIS_CLUSTER_SUB_CONNECTION.setNodeMessagePropagation(true); } @AfterEach - public void afterEach() { - REDIS_CLUSTER_PUB_COMMANDS.flushall().block(); + void afterEach() { + REDIS_CLUSTER_PUB_COMMANDS.flushall(); + REDIS_CLUSTER_PUB_CONNECTION.close(); + REDIS_CLUSTER_SUB_CONNECTION.close(); } @AfterAll static void afterAll() { - REDIS_CLUSTER_SUB_CONNECTION.close(); REDIS_CLUSTER_CLIENT.shutdown(); } - @Test - public void subscribeSubscribesToChannels() { - final List channels = Arrays.asList("podcasts", "podcasters"); - final RedisSubscriber redisSubscriber = new RedisClusterChannelSubscriber(REDIS_CLUSTER_SUB_CONNECTION, channels); + /** + * Poll the RedisSubscriber until there aren't any messages left to retrieve. + * + * @param redisSubscriber RedisSubscriber to poll + * @return Redis messages retrieved by polling + */ + static List pollUntilEmpty(RedisSubscriber redisSubscriber) { + final List retrievedMessages = new ArrayList<>(); - StepVerifier - .create(REDIS_CLUSTER_PUB_CONNECTION.reactive().pubsubNumsub(channels.toArray(new String[0]))) - .expectNextMatches(channelMap -> - channelMap.get(channels.get(0)) == 0L && channelMap.get(channels.get(1)) == 0L) - .verifyComplete(); - - StepVerifier - .create(redisSubscriber.subscribe()) - .verifyComplete(); - - StepVerifier - .create(REDIS_CLUSTER_PUB_CONNECTION.reactive().pubsubNumsub(channels.toArray(new String[0]))) - .expectNextMatches(channelMap -> - channelMap.get(channels.get(0)) == 1L && channelMap.get(channels.get(1)) == 1L) - .verifyComplete(); - } + while (true) { + final RedisMessage message = redisSubscriber.poll(); - @Test - public void unsubscribeUnsubscribesFromChannels() { - final List channels = Arrays.asList("podcasts", "podcasters"); - final RedisSubscriber redisSubscriber = new RedisClusterChannelSubscriber(REDIS_CLUSTER_SUB_CONNECTION, channels); + if (message == null) { + break; + } - StepVerifier - .create(redisSubscriber.subscribe()) - .verifyComplete(); - - StepVerifier - .create(REDIS_CLUSTER_PUB_CONNECTION.reactive().pubsubNumsub(channels.toArray(new String[0]))) - .expectNextMatches(channelMap -> - channelMap.get(channels.get(0)) == 1L && channelMap.get(channels.get(1)) == 1L) - .verifyComplete(); - - StepVerifier - .create(redisSubscriber.unsubscribe()) - .verifyComplete(); - - StepVerifier - .create(REDIS_CLUSTER_PUB_CONNECTION.reactive().pubsubNumsub(channels.toArray(new String[0]))) - .expectNextMatches(channelMap -> - channelMap.get(channels.get(0)) == 0 && channelMap.get(channels.get(1)) == 0) - .verifyComplete(); + retrievedMessages.add(message); + } + + return retrievedMessages; } @Test - public void observeRetrievesPubSubMessages() { + void pollRetrievesCachedMessagesFromSingleChannelPubSub() throws InterruptedException { final String channel = "podcasts"; final RedisSubscriber redisSubscriber = new RedisClusterChannelSubscriber( REDIS_CLUSTER_SUB_CONNECTION, - Arrays.asList(channel)); - - final Mono publish = Flux - .range(1, 5) - .flatMapSequential(id -> REDIS_CLUSTER_PUB_COMMANDS.publish("podcasts", "podcast-" + id)) - .then(); - - StepVerifier - .create(redisSubscriber.subscribe()) - .verifyComplete(); - - final StepVerifier observeVerifier = StepVerifier - .create(redisSubscriber.observe()) - .expectNext(RedisMessage.builder().channel(channel).message("podcast-1").build()) - .expectNext(RedisMessage.builder().channel(channel).message("podcast-2").build()) - .expectNext(RedisMessage.builder().channel(channel).message("podcast-3").build()) - .expectNext(RedisMessage.builder().channel(channel).message("podcast-4").build()) - .expectNext(RedisMessage.builder().channel(channel).message("podcast-5").build()) - .expectNoEvent(Duration.ofSeconds(2L)) - .thenCancel() - .verifyLater(); - - StepVerifier - .create(publish) - .verifyComplete(); - - observeVerifier.verify(); + Collections.singletonList(channel)); + + final List expectedRedisMessages = IntStream.range(0, 5) + .mapToObj(i -> RedisMessage.builder() + .channel("podcasts") + .message(UUID.randomUUID().toString()) + .build()) + .collect(Collectors.toList()); + + for (RedisMessage redisMessage : expectedRedisMessages) { + REDIS_CLUSTER_PUB_COMMANDS.publish(redisMessage.getChannel(), redisMessage.getMessage()); + } + + // Subscription is async and we want to make sure all of the messages are available + Thread.sleep(2000); + + final List retrievedMessages = pollUntilEmpty(redisSubscriber); + + assertEquals(expectedRedisMessages, retrievedMessages); } @Test - public void observeRetrievesPubSubMessagesFromMultipleChannels() { + void pollRetrievesCachedMessagesFromMultipleChannelPubSub() throws InterruptedException { final List channels = Arrays.asList("podcasts", "podcasters"); final RedisSubscriber redisSubscriber = new RedisClusterChannelSubscriber(REDIS_CLUSTER_SUB_CONNECTION, channels); - final Mono publish = Flux - .range(1, 5) - .flatMapSequential(id -> REDIS_CLUSTER_PUB_COMMANDS.publish("podcasts", "podcast-" + id) - .then(REDIS_CLUSTER_PUB_COMMANDS.publish("podcasters", "podcaster-" + id))) - .then(); + final List expectedRedisMessages = IntStream.range(0, 5) + .mapToObj(i -> { + final String channel = (i % 2 == 0) ? "podcasts" : "podcasters"; + return RedisMessage.builder() + .channel(channel) + .message(UUID.randomUUID().toString()) + .build(); + }) + .collect(Collectors.toList()); - StepVerifier - .create(redisSubscriber.subscribe()) - .verifyComplete(); + for (RedisMessage redisMessage : expectedRedisMessages) { + REDIS_CLUSTER_PUB_COMMANDS.publish(redisMessage.getChannel(), redisMessage.getMessage()); + } - final StepVerifier observeVerifier = StepVerifier - .create(redisSubscriber.observe()) - .expectNextCount(10) - .expectNoEvent(Duration.ofSeconds(2L)) - .thenCancel() - .verifyLater(); + // Subscription is async and we want to make sure all of the messages are available + Thread.sleep(2000); - StepVerifier - .create(publish) - .verifyComplete(); + final List retrievedMessages = pollUntilEmpty(redisSubscriber); - observeVerifier.verify(); + assertEquals(expectedRedisMessages, retrievedMessages); } } diff --git a/src/test/integration/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/subscriber/RedisClusterPatternSubscriberIT.java b/src/test/integration/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/subscriber/RedisClusterPatternSubscriberIT.java index ceec12e..f5b746e 100644 --- a/src/test/integration/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/subscriber/RedisClusterPatternSubscriberIT.java +++ b/src/test/integration/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/subscriber/RedisClusterPatternSubscriberIT.java @@ -3,157 +3,137 @@ import io.github.jaredpetersen.kafkaconnectredis.source.listener.RedisMessage; import io.github.jaredpetersen.kafkaconnectredis.testutil.RedisContainer; import io.lettuce.core.cluster.RedisClusterClient; -import io.lettuce.core.cluster.api.reactive.RedisClusterReactiveCommands; +import io.lettuce.core.cluster.api.sync.RedisClusterCommands; import io.lettuce.core.cluster.pubsub.StatefulRedisClusterPubSubConnection; -import java.time.Duration; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; +import java.util.stream.IntStream; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; -import reactor.test.StepVerifier; + +import static org.junit.jupiter.api.Assertions.assertEquals; @Testcontainers -public class RedisClusterPatternSubscriberIT { +class RedisClusterPatternSubscriberIT { @Container private static final RedisContainer REDIS_CLUSTER = new RedisContainer().withClusterMode(); private static RedisClusterClient REDIS_CLUSTER_CLIENT; private static StatefulRedisClusterPubSubConnection REDIS_CLUSTER_PUB_CONNECTION; - private static RedisClusterReactiveCommands REDIS_CLUSTER_PUB_COMMANDS; + private static RedisClusterCommands REDIS_CLUSTER_PUB_COMMANDS; private static StatefulRedisClusterPubSubConnection REDIS_CLUSTER_SUB_CONNECTION; @BeforeAll static void beforeAll() { REDIS_CLUSTER_CLIENT = RedisClusterClient.create(REDIS_CLUSTER.getUri()); + } + @BeforeEach + void beforeEach() { REDIS_CLUSTER_PUB_CONNECTION = REDIS_CLUSTER_CLIENT.connectPubSub(); - REDIS_CLUSTER_PUB_COMMANDS = REDIS_CLUSTER_PUB_CONNECTION.reactive(); + REDIS_CLUSTER_PUB_COMMANDS = REDIS_CLUSTER_PUB_CONNECTION.sync(); REDIS_CLUSTER_SUB_CONNECTION = REDIS_CLUSTER_CLIENT.connectPubSub(); REDIS_CLUSTER_SUB_CONNECTION.setNodeMessagePropagation(true); } @AfterEach - public void afterEach() { - REDIS_CLUSTER_PUB_COMMANDS.flushall().block(); + void afterEach() { + REDIS_CLUSTER_PUB_COMMANDS.flushall(); + REDIS_CLUSTER_PUB_CONNECTION.close(); + REDIS_CLUSTER_SUB_CONNECTION.close(); } @AfterAll static void afterAll() { - REDIS_CLUSTER_SUB_CONNECTION.close(); REDIS_CLUSTER_CLIENT.shutdown(); } - @Test - public void subscribeSubscribesToPattern() { - final List patterns = Arrays.asList("*casts", "*casters"); - final RedisSubscriber redisSubscriber = new RedisClusterPatternSubscriber(REDIS_CLUSTER_SUB_CONNECTION, patterns); + /** + * Poll the RedisSubscriber until there aren't any messages left to retrieve. + * + * @param redisSubscriber RedisSubscriber to poll + * @return Redis messages retrieved by polling + */ + static List pollUntilEmpty(RedisSubscriber redisSubscriber) { + final List retrievedMessages = new ArrayList<>(); + + while (true) { + final RedisMessage message = redisSubscriber.poll(); - StepVerifier - .create(REDIS_CLUSTER_PUB_CONNECTION.reactive().pubsubNumpat()) - .expectNext(0L) - .verifyComplete(); + if (message == null) { + break; + } - StepVerifier - .create(redisSubscriber.subscribe()) - .verifyComplete(); + retrievedMessages.add(message); + } - StepVerifier - .create(REDIS_CLUSTER_PUB_CONNECTION.reactive().pubsubNumpat()) - .expectNext(2L) - .verifyComplete(); + return retrievedMessages; } @Test - public void unsubscribeUnsubscribesFromPatterns() { - final List patterns = Arrays.asList("*casts", "*casters"); - final RedisSubscriber redisSubscriber = new RedisClusterPatternSubscriber(REDIS_CLUSTER_SUB_CONNECTION, patterns); + void pollRetrievesCachedMessagesFromSinglePatternPubSub() throws InterruptedException { + final String pattern = "*casts"; + final RedisSubscriber redisSubscriber = new RedisClusterPatternSubscriber( + REDIS_CLUSTER_SUB_CONNECTION, + Collections.singletonList(pattern)); - StepVerifier - .create(redisSubscriber.subscribe()) - .verifyComplete(); + final List expectedRedisMessages = IntStream.range(0, 5) + .mapToObj(i -> RedisMessage.builder() + .pattern(pattern) + .channel("podcasts") + .message(UUID.randomUUID().toString()) + .build()) + .collect(Collectors.toList()); - StepVerifier - .create(REDIS_CLUSTER_PUB_CONNECTION.reactive().pubsubNumpat()) - .expectNext(2L) - .verifyComplete(); + for (RedisMessage redisMessage : expectedRedisMessages) { + REDIS_CLUSTER_PUB_COMMANDS.publish(redisMessage.getChannel(), redisMessage.getMessage()); + } - StepVerifier - .create(redisSubscriber.unsubscribe()) - .verifyComplete(); + // Subscription is async and we want to make sure all of the messages are available + Thread.sleep(2000); - StepVerifier - .create(REDIS_CLUSTER_PUB_CONNECTION.reactive().pubsubNumpat()) - .expectNext(0L) - .verifyComplete(); - } + final List retrievedMessages = pollUntilEmpty(redisSubscriber); - @Test - public void observeRetrievesPubSubMessages() { - final String pattern = "*casts"; - final RedisSubscriber redisSubscriber = new RedisClusterPatternSubscriber( - REDIS_CLUSTER_SUB_CONNECTION, - Arrays.asList(pattern)); - - final Mono publish = Flux - .range(1, 5) - .flatMapSequential(id -> REDIS_CLUSTER_PUB_COMMANDS.publish("podcasts", "podcast-" + id)) - .then(); - - StepVerifier - .create(redisSubscriber.subscribe()) - .verifyComplete(); - - final StepVerifier observeVerifier = StepVerifier - .create(redisSubscriber.observe()) - .expectNext(RedisMessage.builder().channel("podcasts").pattern(pattern).message("podcast-1").build()) - .expectNext(RedisMessage.builder().channel("podcasts").pattern(pattern).message("podcast-2").build()) - .expectNext(RedisMessage.builder().channel("podcasts").pattern(pattern).message("podcast-3").build()) - .expectNext(RedisMessage.builder().channel("podcasts").pattern(pattern).message("podcast-4").build()) - .expectNext(RedisMessage.builder().channel("podcasts").pattern(pattern).message("podcast-5").build()) - .expectNoEvent(Duration.ofSeconds(2L)) - .thenCancel() - .verifyLater(); - - StepVerifier - .create(publish) - .verifyComplete(); - - observeVerifier.verify(); + assertEquals(expectedRedisMessages, retrievedMessages); } @Test - public void observeRetrievesPubSubMessagesFromMultiplePattern() { + void pollRetrievesCachedMessagesFromMultiplePatternPubSub() throws InterruptedException { final List patterns = Arrays.asList("*casts", "*casters"); final RedisSubscriber redisSubscriber = new RedisClusterPatternSubscriber(REDIS_CLUSTER_SUB_CONNECTION, patterns); - final Mono publish = Flux - .range(1, 5) - .flatMapSequential(id -> REDIS_CLUSTER_PUB_COMMANDS.publish("podcasts", "podcast-" + id) - .then(REDIS_CLUSTER_PUB_COMMANDS.publish("podcasters", "podcaster-" + id))) - .then(); - - StepVerifier - .create(redisSubscriber.subscribe()) - .verifyComplete(); - - final StepVerifier observeVerifier = StepVerifier - .create(redisSubscriber.observe()) - .expectNextCount(10) - .expectNoEvent(Duration.ofSeconds(2L)) - .thenCancel() - .verifyLater(); - - StepVerifier - .create(publish) - .verifyComplete(); - - observeVerifier.verify(); + final List expectedRedisMessages = IntStream.range(0, 5) + .mapToObj(i -> { + final boolean isEven = (i % 2 == 0); + final String pattern = (isEven) ? "*casts" : "*casters"; + final String channel = (isEven) ? "podcasts" : "podcasters"; + return RedisMessage.builder() + .pattern(pattern) + .channel(channel) + .message(UUID.randomUUID().toString()) + .build(); + }) + .collect(Collectors.toList()); + + for (RedisMessage redisMessage : expectedRedisMessages) { + REDIS_CLUSTER_PUB_COMMANDS.publish(redisMessage.getChannel(), redisMessage.getMessage()); + } + + // Subscription is async and we want to make sure all of the messages are available + Thread.sleep(2000); + + final List retrievedMessages = pollUntilEmpty(redisSubscriber); + + assertEquals(expectedRedisMessages, retrievedMessages); } } diff --git a/src/test/integration/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/subscriber/RedisPatternSubscriberIT.java b/src/test/integration/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/subscriber/RedisPatternSubscriberIT.java index ed7faf7..3b74caf 100644 --- a/src/test/integration/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/subscriber/RedisPatternSubscriberIT.java +++ b/src/test/integration/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/subscriber/RedisPatternSubscriberIT.java @@ -3,156 +3,136 @@ import io.github.jaredpetersen.kafkaconnectredis.source.listener.RedisMessage; import io.github.jaredpetersen.kafkaconnectredis.testutil.RedisContainer; import io.lettuce.core.RedisClient; -import io.lettuce.core.api.reactive.RedisReactiveCommands; +import io.lettuce.core.api.sync.RedisCommands; import io.lettuce.core.pubsub.StatefulRedisPubSubConnection; -import java.time.Duration; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; +import java.util.stream.IntStream; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; -import reactor.test.StepVerifier; + +import static org.junit.jupiter.api.Assertions.assertEquals; @Testcontainers -public class RedisPatternSubscriberIT { +class RedisPatternSubscriberIT { @Container private static final RedisContainer REDIS_STANDALONE = new RedisContainer(); private static RedisClient REDIS_STANDALONE_CLIENT; private static StatefulRedisPubSubConnection REDIS_STANDALONE_PUB_CONNECTION; - private static RedisReactiveCommands REDIS_STANDALONE_PUB_COMMANDS; + private static RedisCommands REDIS_STANDALONE_PUB_COMMANDS; private static StatefulRedisPubSubConnection REDIS_STANDALONE_SUB_CONNECTION; @BeforeAll static void beforeAll() { REDIS_STANDALONE_CLIENT = RedisClient.create(REDIS_STANDALONE.getUri()); + } + @BeforeEach + void beforeEach() { REDIS_STANDALONE_PUB_CONNECTION = REDIS_STANDALONE_CLIENT.connectPubSub(); - REDIS_STANDALONE_PUB_COMMANDS = REDIS_STANDALONE_PUB_CONNECTION.reactive(); + REDIS_STANDALONE_PUB_COMMANDS = REDIS_STANDALONE_PUB_CONNECTION.sync(); REDIS_STANDALONE_SUB_CONNECTION = REDIS_STANDALONE_CLIENT.connectPubSub(); } @AfterEach - public void afterEach() { - REDIS_STANDALONE_PUB_COMMANDS.flushall().block(); + void afterEach() { + REDIS_STANDALONE_PUB_COMMANDS.flushall(); + REDIS_STANDALONE_PUB_CONNECTION.close(); + REDIS_STANDALONE_SUB_CONNECTION.close(); } @AfterAll static void afterAll() { - REDIS_STANDALONE_SUB_CONNECTION.close(); REDIS_STANDALONE_CLIENT.shutdown(); } - @Test - public void subscribeSubscribesToPattern() { - final List channels = Arrays.asList("podcasts", "podcasters"); - final RedisSubscriber redisSubscriber = new RedisPatternSubscriber(REDIS_STANDALONE_SUB_CONNECTION, channels); - - StepVerifier - .create(REDIS_STANDALONE_PUB_CONNECTION.reactive().pubsubNumpat()) - .expectNext(0L) - .verifyComplete(); - - StepVerifier - .create(redisSubscriber.subscribe()) - .verifyComplete(); - - StepVerifier - .create(REDIS_STANDALONE_PUB_CONNECTION.reactive().pubsubNumpat()) - .expectNext(2L) - .verifyComplete(); - } + /** + * Poll the RedisSubscriber until there aren't any messages left to retrieve. + * + * @param redisSubscriber RedisSubscriber to poll + * @return Redis messages retrieved by polling + */ + static List pollUntilEmpty(RedisSubscriber redisSubscriber) { + final List retrievedMessages = new ArrayList<>(); - @Test - public void unsubscribeUnsubscribesFromPatterns() { - final List channels = Arrays.asList("podcasts", "podcasters"); - final RedisSubscriber redisSubscriber = new RedisPatternSubscriber(REDIS_STANDALONE_SUB_CONNECTION, channels); - - StepVerifier - .create(redisSubscriber.subscribe()) - .verifyComplete(); - - StepVerifier - .create(REDIS_STANDALONE_PUB_CONNECTION.reactive().pubsubNumpat()) - .expectNext(2L) - .verifyComplete(); - - StepVerifier - .create(redisSubscriber.unsubscribe()) - .verifyComplete(); - - StepVerifier - .create(REDIS_STANDALONE_PUB_CONNECTION.reactive().pubsubNumpat()) - .expectNext(0L) - .verifyComplete(); + while (true) { + final RedisMessage message = redisSubscriber.poll(); + + if (message == null) { + break; + } + + retrievedMessages.add(message); + } + + return retrievedMessages; } @Test - public void observeRetrievesPubSubMessages() { + void pollRetrievesCachedMessagesFromSinglePatternPubSub() throws InterruptedException { final String pattern = "*casts"; final RedisSubscriber redisSubscriber = new RedisPatternSubscriber( REDIS_STANDALONE_SUB_CONNECTION, - Arrays.asList(pattern)); - - final Mono publish = Flux - .range(1, 5) - .flatMapSequential(id -> REDIS_STANDALONE_PUB_COMMANDS.publish("podcasts", "podcast-" + id)) - .then(); - - StepVerifier - .create(redisSubscriber.subscribe()) - .verifyComplete(); - - final StepVerifier observeVerifier = StepVerifier - .create(redisSubscriber.observe()) - .expectNext(RedisMessage.builder().channel("podcasts").pattern(pattern).message("podcast-1").build()) - .expectNext(RedisMessage.builder().channel("podcasts").pattern(pattern).message("podcast-2").build()) - .expectNext(RedisMessage.builder().channel("podcasts").pattern(pattern).message("podcast-3").build()) - .expectNext(RedisMessage.builder().channel("podcasts").pattern(pattern).message("podcast-4").build()) - .expectNext(RedisMessage.builder().channel("podcasts").pattern(pattern).message("podcast-5").build()) - .expectNoEvent(Duration.ofSeconds(2L)) - .thenCancel() - .verifyLater(); - - StepVerifier - .create(publish) - .verifyComplete(); - - observeVerifier.verify(); - } + Collections.singletonList(pattern)); - @Test - public void observeRetrievesPubSubMessagesFromMultiplePattern() { - final List patterns = Arrays.asList("*casts", "*casters"); - final RedisSubscriber redisSubscriber = new RedisPatternSubscriber(REDIS_STANDALONE_SUB_CONNECTION, patterns); + final List expectedRedisMessages = IntStream.range(0, 5) + .mapToObj(i -> RedisMessage.builder() + .pattern(pattern) + .channel("podcasts") + .message(UUID.randomUUID().toString()) + .build()) + .collect(Collectors.toList()); - final Mono publish = Flux - .range(1, 5) - .flatMapSequential(id -> REDIS_STANDALONE_PUB_COMMANDS.publish("podcasts", "podcast-" + id) - .then(REDIS_STANDALONE_PUB_COMMANDS.publish("podcasters", "podcaster-" + id))) - .then(); + for (RedisMessage redisMessage : expectedRedisMessages) { + REDIS_STANDALONE_PUB_COMMANDS.publish(redisMessage.getChannel(), redisMessage.getMessage()); + } - StepVerifier - .create(redisSubscriber.subscribe()) - .verifyComplete(); + // Subscription is async and we want to make sure all of the messages are available + Thread.sleep(2000); - final StepVerifier observeVerifier = StepVerifier - .create(redisSubscriber.observe()) - .expectNextCount(10) - .expectNoEvent(Duration.ofSeconds(2L)) - .thenCancel() - .verifyLater(); + final List retrievedMessages = pollUntilEmpty(redisSubscriber); - StepVerifier - .create(publish) - .verifyComplete(); + assertEquals(expectedRedisMessages, retrievedMessages); + } + + @Test + void pollRetrievesCachedMessagesFromMultiplePatternPubSub() throws InterruptedException { + final List patterns = Arrays.asList("*casts", "*casters"); + final RedisSubscriber redisSubscriber = new RedisPatternSubscriber(REDIS_STANDALONE_SUB_CONNECTION, patterns); - observeVerifier.verify(); + final List expectedRedisMessages = IntStream.range(0, 5) + .mapToObj(i -> { + final boolean isEven = (i % 2 == 0); + final String pattern = (isEven) ? "*casts" : "*casters"; + final String channel = (isEven) ? "podcasts" : "podcasters"; + return RedisMessage.builder() + .pattern(pattern) + .channel(channel) + .message(UUID.randomUUID().toString()) + .build(); + }) + .collect(Collectors.toList()); + + for (RedisMessage redisMessage : expectedRedisMessages) { + REDIS_STANDALONE_PUB_COMMANDS.publish(redisMessage.getChannel(), redisMessage.getMessage()); + } + + // Subscription is async and we want to make sure all of the messages are available + Thread.sleep(2000); + + final List retrievedMessages = pollUntilEmpty(redisSubscriber); + + assertEquals(expectedRedisMessages, retrievedMessages); } } diff --git a/src/test/integration/java/io/github/jaredpetersen/kafkaconnectredis/util/VersionUtilIT.java b/src/test/integration/java/io/github/jaredpetersen/kafkaconnectredis/util/VersionUtilIT.java index 5a5e76e..6bb82b8 100644 --- a/src/test/integration/java/io/github/jaredpetersen/kafkaconnectredis/util/VersionUtilIT.java +++ b/src/test/integration/java/io/github/jaredpetersen/kafkaconnectredis/util/VersionUtilIT.java @@ -4,14 +4,14 @@ import static org.junit.jupiter.api.Assertions.assertTrue; -public class VersionUtilIT { +class VersionUtilIT { @Test - public void constructorDoesNothing() { + void constructorDoesNothing() { new VersionUtil(); } @Test - public void getVersionReturnsVersion() { + void getVersionReturnsVersion() { final String version = VersionUtil.getVersion(); assertTrue(version.matches("[0-9]+\\.[0-9]+\\.[0-9]+")); } diff --git a/src/test/unit/java/io/github/jaredpetersen/kafkaconnectredis/sink/config/RedisSinkConfigTest.java b/src/test/unit/java/io/github/jaredpetersen/kafkaconnectredis/sink/config/RedisSinkConfigTest.java index 38d0dc3..66f1927 100644 --- a/src/test/unit/java/io/github/jaredpetersen/kafkaconnectredis/sink/config/RedisSinkConfigTest.java +++ b/src/test/unit/java/io/github/jaredpetersen/kafkaconnectredis/sink/config/RedisSinkConfigTest.java @@ -6,9 +6,9 @@ import static org.junit.jupiter.api.Assertions.assertEquals; -public class RedisSinkConfigTest { +class RedisSinkConfigTest { @Test - public void isRedisClusterEnabledReturnsDefaultStatus() { + void isRedisClusterEnabledReturnsDefaultStatus() { final Map originalConfig = new HashMap<>(); originalConfig.put("redis.uri", "redis://localhost:6379"); @@ -18,7 +18,7 @@ public void isRedisClusterEnabledReturnsDefaultStatus() { } @Test - public void getRedisUriReturnsUri() { + void getRedisUriReturnsUri() { final Map originalConfig = new HashMap<>(); originalConfig.put("redis.uri", "redis://localhost:6379"); @@ -28,7 +28,7 @@ public void getRedisUriReturnsUri() { } @Test - public void isRedisClusterEnabledReturnsStatus() { + void isRedisClusterEnabledReturnsStatus() { final Map originalConfig = new HashMap<>(); originalConfig.put("redis.uri", "redis://localhost:6379"); originalConfig.put("redis.cluster.enabled", true); diff --git a/src/test/unit/java/io/github/jaredpetersen/kafkaconnectredis/sink/writer/RecordConverterTest.java b/src/test/unit/java/io/github/jaredpetersen/kafkaconnectredis/sink/writer/RecordConverterTest.java index a8ed04f..4eb0b37 100644 --- a/src/test/unit/java/io/github/jaredpetersen/kafkaconnectredis/sink/writer/RecordConverterTest.java +++ b/src/test/unit/java/io/github/jaredpetersen/kafkaconnectredis/sink/writer/RecordConverterTest.java @@ -17,7 +17,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; -public class RecordConverterTest { +class RecordConverterTest { private static final Schema REDIS_SET_COMMAND_SCHEMA = SchemaBuilder.struct() .name("io.github.jaredpetersen.kafkaconnectredis.RedisSetCommand") .field("key", SchemaBuilder.STRING_SCHEMA) @@ -75,7 +75,7 @@ public class RecordConverterTest { .build(); @Test - public void convertTransformsPartialSinkRecordToRedisSetCommand() { + void convertTransformsPartialSinkRecordToRedisSetCommand() { final String topic = "rediscommands"; final int partition = 0; final Schema keySchema = null; @@ -101,7 +101,7 @@ public void convertTransformsPartialSinkRecordToRedisSetCommand() { } @Test - public void convertTransformsSinkRecordToRedisSetCommand() { + void convertTransformsSinkRecordToRedisSetCommand() { final String topic = "rediscommands"; final int partition = 0; final Schema keySchema = null; @@ -136,7 +136,7 @@ public void convertTransformsSinkRecordToRedisSetCommand() { } @Test - public void convertTransformsSinkRecordToRedisExpireCommand() { + void convertTransformsSinkRecordToRedisExpireCommand() { final String topic = "rediscommands"; final int partition = 0; final Schema keySchema = null; @@ -162,7 +162,7 @@ public void convertTransformsSinkRecordToRedisExpireCommand() { } @Test - public void convertTransformsSinkRecordToRedisExpireatCommand() { + void convertTransformsSinkRecordToRedisExpireatCommand() { final String topic = "rediscommands"; final int partition = 0; final Schema keySchema = null; @@ -188,7 +188,7 @@ public void convertTransformsSinkRecordToRedisExpireatCommand() { } @Test - public void convertTransformsSinkRecordToRedisPexpireCommand() { + void convertTransformsSinkRecordToRedisPexpireCommand() { final String topic = "rediscommands"; final int partition = 0; final Schema keySchema = null; @@ -214,7 +214,7 @@ public void convertTransformsSinkRecordToRedisPexpireCommand() { } @Test - public void convertTransformsSinkRecordToRedisSaddCommand() { + void convertTransformsSinkRecordToRedisSaddCommand() { final String topic = "rediscommands"; final int partition = 0; final Schema keySchema = null; @@ -240,7 +240,7 @@ public void convertTransformsSinkRecordToRedisSaddCommand() { } @Test - public void convertTransformsSinkRecordToRedisGeoaddCommand() { + void convertTransformsSinkRecordToRedisGeoaddCommand() { final String topic = "rediscommands"; final int partition = 0; final Schema keySchema = null; @@ -285,7 +285,7 @@ public void convertTransformsSinkRecordToRedisGeoaddCommand() { } @Test - public void convertTransformsSinkRecordToRedisArbitraryCommand() { + void convertTransformsSinkRecordToRedisArbitraryCommand() { final String topic = "rediscommands"; final int partition = 0; final Schema keySchema = null; diff --git a/src/test/unit/java/io/github/jaredpetersen/kafkaconnectredis/source/config/RedisSourceConfigTest.java b/src/test/unit/java/io/github/jaredpetersen/kafkaconnectredis/source/config/RedisSourceConfigTest.java index 5803829..e95e249 100644 --- a/src/test/unit/java/io/github/jaredpetersen/kafkaconnectredis/source/config/RedisSourceConfigTest.java +++ b/src/test/unit/java/io/github/jaredpetersen/kafkaconnectredis/source/config/RedisSourceConfigTest.java @@ -10,9 +10,9 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; -public class RedisSourceConfigTest { +class RedisSourceConfigTest { @Test - public void getTopicReturnsTopic() { + void getTopicReturnsTopic() { final Map originalConfig = new HashMap<>(); originalConfig.put("topic", "mytopic"); originalConfig.put("redis.uri", "redis://localhost:6379"); @@ -25,7 +25,7 @@ public void getTopicReturnsTopic() { } @Test - public void getRedisUriReturnsUri() { + void getRedisUriReturnsUri() { final Map originalConfig = new HashMap<>(); originalConfig.put("topic", "mytopic"); originalConfig.put("redis.uri", "redis://localhost:6379"); @@ -38,7 +38,7 @@ public void getRedisUriReturnsUri() { } @Test - public void isRedisClusterEnabledReturnsDefaultStatus() { + void isRedisClusterEnabledReturnsDefaultStatus() { final Map originalConfig = new HashMap<>(); originalConfig.put("topic", "mytopic"); originalConfig.put("redis.uri", "redis://localhost:6379"); @@ -66,7 +66,7 @@ public void isRedisClusterEnabledReturnsStatus(boolean status) { } @Test - public void getRedisChannelsReturnsChannels() { + void getRedisChannelsReturnsChannels() { final Map originalConfig = new HashMap<>(); originalConfig.put("topic", "mytopic"); originalConfig.put("redis.uri", "redis://localhost:6379"); diff --git a/src/test/unit/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/OldRedisListenerTest.java b/src/test/unit/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/OldRedisListenerTest.java deleted file mode 100644 index 8eab0b3..0000000 --- a/src/test/unit/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/OldRedisListenerTest.java +++ /dev/null @@ -1,223 +0,0 @@ -package io.github.jaredpetersen.kafkaconnectredis.source.listener; - -import io.github.jaredpetersen.kafkaconnectredis.source.listener.subscriber.RedisSubscriber; -import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; -import org.junit.jupiter.api.Test; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -public class RedisListenerTest { - @Test - public void startSubscribesAndListens() throws InterruptedException { - // Generate an unbounded flux redis messages on demand - // Use an external index to track the progress for verification purposes - final AtomicInteger redisSubscriptionMessageIndex = new AtomicInteger(); - final Flux redisSubscriptionMessageFlux = Flux.generate( - () -> 0, - (state, sink) -> { - redisSubscriptionMessageIndex.set(state); - - final RedisMessage redisMessage = RedisMessage.builder() - .channel("election") - .message("vote-" + state) - .build(); - - sink.next(redisMessage); - - return ++state; - } - ); - - final RedisSubscriber mockRedisSubscriber = mock(RedisSubscriber.class); - when(mockRedisSubscriber.subscribe()).thenReturn(Mono.empty()); - when(mockRedisSubscriber.observe()).thenReturn(redisSubscriptionMessageFlux); - - final RedisListener redisListener = new RedisListener(mockRedisSubscriber); - - redisListener.start(); - - Thread.sleep(1000L); - - assertTrue(redisSubscriptionMessageIndex.get() > 0); - } - - @Test - public void stopUnsubscribesAndStopsListening() throws InterruptedException { - // Generate an unbounded flux redis messages on demand - // Use an external index to track the progress for verification purposes - final AtomicInteger redisSubscriptionMessageIndex = new AtomicInteger(); - final Flux redisSubscriptionMessageFlux = Flux.generate( - () -> 0, - (state, sink) -> { - redisSubscriptionMessageIndex.set(state); - - final RedisMessage redisMessage = RedisMessage.builder() - .channel("election") - .message("vote-" + state) - .build(); - - sink.next(redisMessage); - - return ++state; - } - ); - - final RedisSubscriber mockRedisSubscriber = mock(RedisSubscriber.class); - when(mockRedisSubscriber.subscribe()).thenReturn(Mono.empty()); - when(mockRedisSubscriber.observe()).thenReturn(redisSubscriptionMessageFlux); - when(mockRedisSubscriber.unsubscribe()).thenReturn(Mono.empty()); - - final RedisListener redisListener = new RedisListener(mockRedisSubscriber); - - redisListener.start(); - Thread.sleep(1000L); - redisListener.stop(); - - final int redisSubscriptionMessageIndexAfterStop = redisSubscriptionMessageIndex.get(); - - Thread.sleep(1000L); - - assertEquals(redisSubscriptionMessageIndexAfterStop, redisSubscriptionMessageIndex.get()); - } - - @Test - public void pollRetrievesEmptyListOfSubscribedMessages() throws InterruptedException { - final RedisSubscriber mockRedisSubscriber = mock(RedisSubscriber.class); - when(mockRedisSubscriber.subscribe()).thenReturn(Mono.empty()); - when(mockRedisSubscriber.observe()).thenReturn(Flux.empty()); - when(mockRedisSubscriber.unsubscribe()).thenReturn(Mono.empty()); - - final RedisListener redisListener = new RedisListener(mockRedisSubscriber); - - final List redisMessages = redisListener.poll(); - - assertEquals(0, redisMessages.size()); - } - - @Test - public void pollRetrievesSubscribedMessages() throws InterruptedException { - // Generate an unbounded flux redis messages on demand - // Use an external index to track the progress for verification purposes - final AtomicInteger redisSubscriptionMessageIndex = new AtomicInteger(); - final Flux redisSubscriptionMessageFlux = Flux.generate( - () -> 0, - (state, sink) -> { - redisSubscriptionMessageIndex.set(state); - - final RedisMessage redisMessage = RedisMessage.builder() - .channel("election") - .message("vote-" + state) - .build(); - - sink.next(redisMessage); - - return ++state; - } - ); - - final RedisSubscriber mockRedisSubscriber = mock(RedisSubscriber.class); - when(mockRedisSubscriber.subscribe()).thenReturn(Mono.empty()); - when(mockRedisSubscriber.observe()).thenReturn(redisSubscriptionMessageFlux); - when(mockRedisSubscriber.unsubscribe()).thenReturn(Mono.empty()); - - final RedisListener redisListener = new RedisListener(mockRedisSubscriber); - - redisListener.start(); - - // Give the listener time to observe some messages - Thread.sleep(2000L); - - final List redisMessages = redisListener.poll(); - - assertTrue(redisMessages.size() > 0); - assertTrue(redisMessages.size() <= 100_000); - assertEquals( - RedisMessage.builder() - .channel("election") - .message("vote-0") - .build(), - redisMessages.get(0)); - assertEquals( - RedisMessage.builder() - .channel("election") - .message("vote-" + (redisMessages.size() - 1)) - .build(), - redisMessages.get(redisMessages.size() - 1)); - } - - @Test - public void pollMultipleTimesRetrievesSubscribedMessagesWithoutDataLoss() throws InterruptedException { - // Generate an unbounded flux redis messages on demand - // Use an external index to track the progress for verification purposes - final AtomicInteger redisSubscriptionMessageIndex = new AtomicInteger(); - final Flux redisSubscriptionMessageFlux = Flux.generate( - () -> 0, - (state, sink) -> { - redisSubscriptionMessageIndex.set(state); - - final RedisMessage redisMessage = RedisMessage.builder() - .channel("election") - .message("vote-" + state) - .build(); - - sink.next(redisMessage); - - return ++state; - } - ); - - final RedisSubscriber mockRedisSubscriber = mock(RedisSubscriber.class); - when(mockRedisSubscriber.subscribe()).thenReturn(Mono.empty()); - when(mockRedisSubscriber.observe()).thenReturn(redisSubscriptionMessageFlux); - when(mockRedisSubscriber.unsubscribe()).thenReturn(Mono.empty()); - - final RedisListener redisListener = new RedisListener(mockRedisSubscriber); - - redisListener.start(); - - // Give the listener time to observe some messages - Thread.sleep(1000L); - - final List redisMessagesRoundA = redisListener.poll(); - - assertTrue(redisMessagesRoundA.size() > 0); - assertTrue(redisMessagesRoundA.size() <= 100_000); - assertEquals( - RedisMessage.builder() - .channel("election") - .message("vote-0") - .build(), - redisMessagesRoundA.get(0)); - assertEquals( - RedisMessage.builder() - .channel("election") - .message("vote-" + (redisMessagesRoundA.size() - 1)) - .build(), - redisMessagesRoundA.get(redisMessagesRoundA.size() - 1)); - - // Poll again, confirming that we're getting the next batch of data - Thread.sleep(1000L); - final List redisMessagesRoundB = redisListener.poll(); - - assertTrue(redisMessagesRoundB.size() > 0); - assertTrue(redisMessagesRoundB.size() <= 100_000); - assertEquals( - RedisMessage.builder() - .channel("election") - .message("vote-" + redisMessagesRoundA.size()) - .build(), - redisMessagesRoundB.get(0)); - assertEquals( - RedisMessage.builder() - .channel("election") - .message("vote-" + (redisMessagesRoundA.size() + redisMessagesRoundB.size() - 1)) - .build(), - redisMessagesRoundB.get(redisMessagesRoundB.size() - 1)); - } -} diff --git a/src/test/unit/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/RecordConverterTest.java b/src/test/unit/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/RecordConverterTest.java index da244cc..048ae1e 100644 --- a/src/test/unit/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/RecordConverterTest.java +++ b/src/test/unit/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/RecordConverterTest.java @@ -3,12 +3,16 @@ import java.time.Instant; import org.apache.kafka.connect.data.Schema; import org.apache.kafka.connect.data.Struct; +import org.apache.kafka.connect.source.SourceRecord; import org.junit.jupiter.api.Test; -import reactor.test.StepVerifier; -public class RecordConverterTest { +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class RecordConverterTest { @Test - public void convertTransformsRedisMessageToSourceRecord() { + void convertTransformsRedisMessageToSourceRecord() { final RedisMessage redisMessage = RedisMessage.builder() .channel("mychannel") .pattern("mypattern") @@ -17,19 +21,17 @@ public void convertTransformsRedisMessageToSourceRecord() { final String topic = "mytopic"; final RecordConverter recordConverter = new RecordConverter(topic); + final SourceRecord sourceRecord = recordConverter.convert(redisMessage); - StepVerifier - .create(recordConverter.convert(redisMessage)) - .expectNextMatches(sourceRecord -> - sourceRecord.sourcePartition().size() == 0 - && sourceRecord.sourceOffset().size() == 0 - && sourceRecord.topic().equals(topic) - && sourceRecord.kafkaPartition() == null - && sourceRecord.keySchema().type() == Schema.Type.STRUCT - && ((Struct) sourceRecord.key()).getString("channel").equals("mychannel") - && ((Struct) sourceRecord.key()).getString("pattern").equals("mypattern") - && sourceRecord.valueSchema().type() == Schema.Type.STRUCT - && ((Struct) sourceRecord.value()).getString("message").equals("some message") - && sourceRecord.timestamp() <= Instant.now().getEpochSecond()); + assertTrue(sourceRecord.sourcePartition().isEmpty()); + assertTrue(sourceRecord.sourceOffset().isEmpty()); + assertEquals(topic, sourceRecord.topic()); + assertNull(sourceRecord.kafkaPartition()); + assertEquals(Schema.Type.STRUCT, sourceRecord.keySchema().type()); + assertEquals(redisMessage.getChannel(), ((Struct) sourceRecord.key()).getString("channel")); + assertEquals(redisMessage.getPattern(), ((Struct) sourceRecord.key()).getString("pattern")); + assertEquals(Schema.Type.STRUCT, sourceRecord.valueSchema().type()); + assertEquals(redisMessage.getMessage(), ((Struct) sourceRecord.value()).getString("message")); + assertTrue(sourceRecord.timestamp() <= Instant.now().getEpochSecond()); } } diff --git a/src/test/unit/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/RedisListenerTest.java b/src/test/unit/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/RedisListenerTest.java deleted file mode 100644 index 8eab0b3..0000000 --- a/src/test/unit/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/RedisListenerTest.java +++ /dev/null @@ -1,223 +0,0 @@ -package io.github.jaredpetersen.kafkaconnectredis.source.listener; - -import io.github.jaredpetersen.kafkaconnectredis.source.listener.subscriber.RedisSubscriber; -import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; -import org.junit.jupiter.api.Test; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -public class RedisListenerTest { - @Test - public void startSubscribesAndListens() throws InterruptedException { - // Generate an unbounded flux redis messages on demand - // Use an external index to track the progress for verification purposes - final AtomicInteger redisSubscriptionMessageIndex = new AtomicInteger(); - final Flux redisSubscriptionMessageFlux = Flux.generate( - () -> 0, - (state, sink) -> { - redisSubscriptionMessageIndex.set(state); - - final RedisMessage redisMessage = RedisMessage.builder() - .channel("election") - .message("vote-" + state) - .build(); - - sink.next(redisMessage); - - return ++state; - } - ); - - final RedisSubscriber mockRedisSubscriber = mock(RedisSubscriber.class); - when(mockRedisSubscriber.subscribe()).thenReturn(Mono.empty()); - when(mockRedisSubscriber.observe()).thenReturn(redisSubscriptionMessageFlux); - - final RedisListener redisListener = new RedisListener(mockRedisSubscriber); - - redisListener.start(); - - Thread.sleep(1000L); - - assertTrue(redisSubscriptionMessageIndex.get() > 0); - } - - @Test - public void stopUnsubscribesAndStopsListening() throws InterruptedException { - // Generate an unbounded flux redis messages on demand - // Use an external index to track the progress for verification purposes - final AtomicInteger redisSubscriptionMessageIndex = new AtomicInteger(); - final Flux redisSubscriptionMessageFlux = Flux.generate( - () -> 0, - (state, sink) -> { - redisSubscriptionMessageIndex.set(state); - - final RedisMessage redisMessage = RedisMessage.builder() - .channel("election") - .message("vote-" + state) - .build(); - - sink.next(redisMessage); - - return ++state; - } - ); - - final RedisSubscriber mockRedisSubscriber = mock(RedisSubscriber.class); - when(mockRedisSubscriber.subscribe()).thenReturn(Mono.empty()); - when(mockRedisSubscriber.observe()).thenReturn(redisSubscriptionMessageFlux); - when(mockRedisSubscriber.unsubscribe()).thenReturn(Mono.empty()); - - final RedisListener redisListener = new RedisListener(mockRedisSubscriber); - - redisListener.start(); - Thread.sleep(1000L); - redisListener.stop(); - - final int redisSubscriptionMessageIndexAfterStop = redisSubscriptionMessageIndex.get(); - - Thread.sleep(1000L); - - assertEquals(redisSubscriptionMessageIndexAfterStop, redisSubscriptionMessageIndex.get()); - } - - @Test - public void pollRetrievesEmptyListOfSubscribedMessages() throws InterruptedException { - final RedisSubscriber mockRedisSubscriber = mock(RedisSubscriber.class); - when(mockRedisSubscriber.subscribe()).thenReturn(Mono.empty()); - when(mockRedisSubscriber.observe()).thenReturn(Flux.empty()); - when(mockRedisSubscriber.unsubscribe()).thenReturn(Mono.empty()); - - final RedisListener redisListener = new RedisListener(mockRedisSubscriber); - - final List redisMessages = redisListener.poll(); - - assertEquals(0, redisMessages.size()); - } - - @Test - public void pollRetrievesSubscribedMessages() throws InterruptedException { - // Generate an unbounded flux redis messages on demand - // Use an external index to track the progress for verification purposes - final AtomicInteger redisSubscriptionMessageIndex = new AtomicInteger(); - final Flux redisSubscriptionMessageFlux = Flux.generate( - () -> 0, - (state, sink) -> { - redisSubscriptionMessageIndex.set(state); - - final RedisMessage redisMessage = RedisMessage.builder() - .channel("election") - .message("vote-" + state) - .build(); - - sink.next(redisMessage); - - return ++state; - } - ); - - final RedisSubscriber mockRedisSubscriber = mock(RedisSubscriber.class); - when(mockRedisSubscriber.subscribe()).thenReturn(Mono.empty()); - when(mockRedisSubscriber.observe()).thenReturn(redisSubscriptionMessageFlux); - when(mockRedisSubscriber.unsubscribe()).thenReturn(Mono.empty()); - - final RedisListener redisListener = new RedisListener(mockRedisSubscriber); - - redisListener.start(); - - // Give the listener time to observe some messages - Thread.sleep(2000L); - - final List redisMessages = redisListener.poll(); - - assertTrue(redisMessages.size() > 0); - assertTrue(redisMessages.size() <= 100_000); - assertEquals( - RedisMessage.builder() - .channel("election") - .message("vote-0") - .build(), - redisMessages.get(0)); - assertEquals( - RedisMessage.builder() - .channel("election") - .message("vote-" + (redisMessages.size() - 1)) - .build(), - redisMessages.get(redisMessages.size() - 1)); - } - - @Test - public void pollMultipleTimesRetrievesSubscribedMessagesWithoutDataLoss() throws InterruptedException { - // Generate an unbounded flux redis messages on demand - // Use an external index to track the progress for verification purposes - final AtomicInteger redisSubscriptionMessageIndex = new AtomicInteger(); - final Flux redisSubscriptionMessageFlux = Flux.generate( - () -> 0, - (state, sink) -> { - redisSubscriptionMessageIndex.set(state); - - final RedisMessage redisMessage = RedisMessage.builder() - .channel("election") - .message("vote-" + state) - .build(); - - sink.next(redisMessage); - - return ++state; - } - ); - - final RedisSubscriber mockRedisSubscriber = mock(RedisSubscriber.class); - when(mockRedisSubscriber.subscribe()).thenReturn(Mono.empty()); - when(mockRedisSubscriber.observe()).thenReturn(redisSubscriptionMessageFlux); - when(mockRedisSubscriber.unsubscribe()).thenReturn(Mono.empty()); - - final RedisListener redisListener = new RedisListener(mockRedisSubscriber); - - redisListener.start(); - - // Give the listener time to observe some messages - Thread.sleep(1000L); - - final List redisMessagesRoundA = redisListener.poll(); - - assertTrue(redisMessagesRoundA.size() > 0); - assertTrue(redisMessagesRoundA.size() <= 100_000); - assertEquals( - RedisMessage.builder() - .channel("election") - .message("vote-0") - .build(), - redisMessagesRoundA.get(0)); - assertEquals( - RedisMessage.builder() - .channel("election") - .message("vote-" + (redisMessagesRoundA.size() - 1)) - .build(), - redisMessagesRoundA.get(redisMessagesRoundA.size() - 1)); - - // Poll again, confirming that we're getting the next batch of data - Thread.sleep(1000L); - final List redisMessagesRoundB = redisListener.poll(); - - assertTrue(redisMessagesRoundB.size() > 0); - assertTrue(redisMessagesRoundB.size() <= 100_000); - assertEquals( - RedisMessage.builder() - .channel("election") - .message("vote-" + redisMessagesRoundA.size()) - .build(), - redisMessagesRoundB.get(0)); - assertEquals( - RedisMessage.builder() - .channel("election") - .message("vote-" + (redisMessagesRoundA.size() + redisMessagesRoundB.size() - 1)) - .build(), - redisMessagesRoundB.get(redisMessagesRoundB.size() - 1)); - } -} diff --git a/src/test/unit/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/subscriber/RedisClusterListenerTest.java b/src/test/unit/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/subscriber/RedisClusterListenerTest.java new file mode 100644 index 0000000..02e9336 --- /dev/null +++ b/src/test/unit/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/subscriber/RedisClusterListenerTest.java @@ -0,0 +1,114 @@ +package io.github.jaredpetersen.kafkaconnectredis.source.listener.subscriber; + +import io.github.jaredpetersen.kafkaconnectredis.source.listener.RedisMessage; +import io.lettuce.core.cluster.models.partitions.RedisClusterNode; +import java.util.Random; +import java.util.UUID; +import java.util.concurrent.ConcurrentLinkedQueue; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class RedisClusterListenerTest { + private static final Random random = new Random(); + + @Test + void messageAddsChannelMessageToQueue() { + final ConcurrentLinkedQueue queue = new ConcurrentLinkedQueue<>(); + final RedisClusterListener redisClusterListener = new RedisClusterListener(queue); + + final String channel = "books"; + final String message = "the best book ever"; + + redisClusterListener.message( + RedisClusterNode.of(UUID.randomUUID().toString()), + channel, + message); + + final RedisMessage expectedRedisMessage = RedisMessage.builder() + .channel(channel) + .message(message) + .build(); + + assertEquals(1, queue.size()); + assertEquals(expectedRedisMessage, queue.poll()); + } + + @Test + void messageAddsPatternMessageToQueue() { + final ConcurrentLinkedQueue queue = new ConcurrentLinkedQueue<>(); + final RedisClusterListener redisClusterListener = new RedisClusterListener(queue); + + final String pattern = "b*"; + final String channel = "books"; + final String message = "the best book ever"; + + redisClusterListener.message( + RedisClusterNode.of(UUID.randomUUID().toString()), + pattern, + channel, + message); + + final RedisMessage expectedRedisMessage = RedisMessage.builder() + .pattern(pattern) + .channel(channel) + .message(message) + .build(); + + assertEquals(1, queue.size()); + assertEquals(expectedRedisMessage, queue.poll()); + } + + @Test + void subscribedDoesNothing() { + final ConcurrentLinkedQueue queue = new ConcurrentLinkedQueue<>(); + final RedisClusterListener redisClusterListener = new RedisClusterListener(queue); + + redisClusterListener.subscribed( + RedisClusterNode.of(UUID.randomUUID().toString()), + "books", + random.nextInt()); + + assertTrue(queue.isEmpty()); + } + + @Test + void psubscribedDoesNothing() { + final ConcurrentLinkedQueue queue = new ConcurrentLinkedQueue<>(); + final RedisClusterListener redisClusterListener = new RedisClusterListener(queue); + + redisClusterListener.psubscribed( + RedisClusterNode.of(UUID.randomUUID().toString()), + "b*", + random.nextInt()); + + assertTrue(queue.isEmpty()); + } + + @Test + void unsubscribedDoesNothing() { + final ConcurrentLinkedQueue queue = new ConcurrentLinkedQueue<>(); + final RedisClusterListener redisClusterListener = new RedisClusterListener(queue); + + redisClusterListener.unsubscribed( + RedisClusterNode.of(UUID.randomUUID().toString()), + "books", + random.nextInt()); + + assertTrue(queue.isEmpty()); + } + + @Test + void punsubscribedDoesNothing() { + final ConcurrentLinkedQueue queue = new ConcurrentLinkedQueue<>(); + final RedisClusterListener redisClusterListener = new RedisClusterListener(queue); + + redisClusterListener.punsubscribed( + RedisClusterNode.of(UUID.randomUUID().toString()), + "b*", + random.nextInt()); + + assertTrue(queue.isEmpty()); + } +} diff --git a/src/test/unit/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/subscriber/RedisStandaloneListenerTest.java b/src/test/unit/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/subscriber/RedisStandaloneListenerTest.java new file mode 100644 index 0000000..f71ad94 --- /dev/null +++ b/src/test/unit/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/subscriber/RedisStandaloneListenerTest.java @@ -0,0 +1,106 @@ +package io.github.jaredpetersen.kafkaconnectredis.source.listener.subscriber; + +import io.github.jaredpetersen.kafkaconnectredis.source.listener.RedisMessage; +import java.util.Random; +import java.util.concurrent.ConcurrentLinkedQueue; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class RedisStandaloneListenerTest { + private static final Random random = new Random(); + + @Test + void messageAddsChannelMessageToQueue() { + final ConcurrentLinkedQueue queue = new ConcurrentLinkedQueue<>(); + final RedisStandaloneListener redisStandaloneListener = new RedisStandaloneListener(queue); + + final String channel = "books"; + final String message = "the best book ever"; + + redisStandaloneListener.message( + channel, + message); + + final RedisMessage expectedRedisMessage = RedisMessage.builder() + .channel(channel) + .message(message) + .build(); + + assertEquals(1, queue.size()); + assertEquals(expectedRedisMessage, queue.poll()); + } + + @Test + void messageAddsPatternMessageToQueue() { + final ConcurrentLinkedQueue queue = new ConcurrentLinkedQueue<>(); + final RedisStandaloneListener redisStandaloneListener = new RedisStandaloneListener(queue); + + final String pattern = "b*"; + final String channel = "books"; + final String message = "the best book ever"; + + redisStandaloneListener.message( + pattern, + channel, + message); + + final RedisMessage expectedRedisMessage = RedisMessage.builder() + .pattern(pattern) + .channel(channel) + .message(message) + .build(); + + assertEquals(1, queue.size()); + assertEquals(expectedRedisMessage, queue.poll()); + } + + @Test + void subscribedDoesNothing() { + final ConcurrentLinkedQueue queue = new ConcurrentLinkedQueue<>(); + final RedisStandaloneListener redisStandaloneListener = new RedisStandaloneListener(queue); + + redisStandaloneListener.subscribed( + "books", + random.nextInt()); + + assertTrue(queue.isEmpty()); + } + + @Test + void psubscribedDoesNothing() { + final ConcurrentLinkedQueue queue = new ConcurrentLinkedQueue<>(); + final RedisStandaloneListener redisStandaloneListener = new RedisStandaloneListener(queue); + + redisStandaloneListener.psubscribed( + "b*", + random.nextInt()); + + assertTrue(queue.isEmpty()); + } + + @Test + void unsubscribedDoesNothing() { + final ConcurrentLinkedQueue queue = new ConcurrentLinkedQueue<>(); + final RedisStandaloneListener redisStandaloneListener = new RedisStandaloneListener(queue); + + redisStandaloneListener.unsubscribed( + "books", + random.nextInt()); + + assertTrue(queue.isEmpty()); + } + + @Test + void punsubscribedDoesNothing() { + final ConcurrentLinkedQueue queue = new ConcurrentLinkedQueue<>(); + final RedisStandaloneListener redisStandaloneListener = new RedisStandaloneListener(queue); + + redisStandaloneListener.punsubscribed( + "b*", + random.nextInt()); + + assertTrue(queue.isEmpty()); + } +} From 9294d944aaa9a34b4451b66f018637015054b3d7 Mon Sep 17 00:00:00 2001 From: Jared Petersen Date: Fri, 16 Jul 2021 23:13:24 -0700 Subject: [PATCH 07/10] update version -- patch --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 93ea94a..33a593e 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ io.github.jaredpetersen kafka-connect-redis - 1.2.0 + 1.2.1 jar Kafka Redis Connector (Sink and Source) From 27307430330a7ae025de97a12172e8297ea397fa Mon Sep 17 00:00:00 2001 From: Jared Petersen Date: Fri, 16 Jul 2021 23:32:11 -0700 Subject: [PATCH 08/10] updated changelog --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a19cf23..2e0818e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.2.1] - 2021-07-17 +### Changed +- Modified Confluent archive to follow new standards +- Updated Lettuce dependency +- Stopped using reactive Lettuce + ## [1.2.0] - 2021-02-13 ### Added - Handle Redis cluster topology changes on the fly From fe73d83b29adbab3063bb9c6ae5689a25077ecf0 Mon Sep 17 00:00:00 2001 From: Jared Petersen Date: Sat, 17 Jul 2021 01:54:19 -0700 Subject: [PATCH 09/10] updated more dependencies, lombok tweaks --- CHANGELOG.md | 2 +- lombok.config | 2 ++ pom.xml | 12 ++++++------ .../kafkaconnectredis/sink/RedisSinkTask.java | 5 ++--- .../sink/writer/RecordConverter.java | 6 ++---- .../kafkaconnectredis/sink/writer/Writer.java | 6 ++---- .../sink/writer/record/RedisArbitraryCommand.java | 4 ++-- .../sink/writer/record/RedisExpireCommand.java | 4 ++-- .../sink/writer/record/RedisExpireatCommand.java | 4 ++-- .../sink/writer/record/RedisGeoaddCommand.java | 6 +++--- .../sink/writer/record/RedisPexpireCommand.java | 4 ++-- .../sink/writer/record/RedisSaddCommand.java | 4 ++-- .../sink/writer/record/RedisSetCommand.java | 6 +++--- .../kafkaconnectredis/source/RedisSourceTask.java | 5 ++--- .../source/listener/RedisMessage.java | 2 +- .../source/listener/subscriber/RedisListener.java | 6 ++---- .../kafkaconnectredis/util/VersionUtil.java | 7 +++---- 17 files changed, 39 insertions(+), 46 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e0818e..e181bb4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,8 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [1.2.1] - 2021-07-17 ### Changed - Modified Confluent archive to follow new standards -- Updated Lettuce dependency - Stopped using reactive Lettuce +- Upgraded various dependencies ## [1.2.0] - 2021-02-13 ### Added diff --git a/lombok.config b/lombok.config index df71bb6..2992a18 100644 --- a/lombok.config +++ b/lombok.config @@ -1,2 +1,4 @@ config.stopBubbling = true lombok.addLombokGeneratedAnnotation = true +lombok.builder.className = Builder +lombok.log.fieldname = LOG \ No newline at end of file diff --git a/pom.xml b/pom.xml index 33a593e..0bb92ea 100644 --- a/pom.xml +++ b/pom.xml @@ -62,7 +62,7 @@ org.apache.kafka connect-api - 2.7.0 + 2.8.0 provided @@ -75,7 +75,7 @@ org.projectlombok lombok - 1.18.18 + 1.18.20 provided @@ -89,27 +89,27 @@ org.junit.jupiter junit-jupiter - 5.7.1 + 5.7.2 test org.mockito mockito-core - 3.7.7 + 3.11.2 test org.testcontainers testcontainers - 1.15.2 + 1.15.3 test org.testcontainers junit-jupiter - 1.15.2 + 1.15.3 test diff --git a/src/main/java/io/github/jaredpetersen/kafkaconnectredis/sink/RedisSinkTask.java b/src/main/java/io/github/jaredpetersen/kafkaconnectredis/sink/RedisSinkTask.java index a5f7aeb..9fa48c1 100644 --- a/src/main/java/io/github/jaredpetersen/kafkaconnectredis/sink/RedisSinkTask.java +++ b/src/main/java/io/github/jaredpetersen/kafkaconnectredis/sink/RedisSinkTask.java @@ -15,19 +15,18 @@ import io.lettuce.core.cluster.api.sync.RedisClusterCommands; import java.util.Collection; import java.util.Map; +import lombok.extern.slf4j.Slf4j; import org.apache.kafka.common.config.ConfigException; import org.apache.kafka.connect.errors.ConnectException; import org.apache.kafka.connect.sink.SinkRecord; import org.apache.kafka.connect.sink.SinkTask; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * Kafka Connect Task for Kafka Connect Redis Sink. */ +@Slf4j public class RedisSinkTask extends SinkTask { private static final RecordConverter RECORD_CONVERTER = new RecordConverter(); - private static final Logger LOG = LoggerFactory.getLogger(RedisSinkTask.class); private RedisClient redisStandaloneClient; private StatefulRedisConnection redisStandaloneConnection; diff --git a/src/main/java/io/github/jaredpetersen/kafkaconnectredis/sink/writer/RecordConverter.java b/src/main/java/io/github/jaredpetersen/kafkaconnectredis/sink/writer/RecordConverter.java index 36961bb..8f848cc 100644 --- a/src/main/java/io/github/jaredpetersen/kafkaconnectredis/sink/writer/RecordConverter.java +++ b/src/main/java/io/github/jaredpetersen/kafkaconnectredis/sink/writer/RecordConverter.java @@ -10,15 +10,13 @@ import io.github.jaredpetersen.kafkaconnectredis.sink.writer.record.RedisSetCommand; import java.util.ArrayList; import java.util.List; +import lombok.extern.slf4j.Slf4j; import org.apache.kafka.connect.data.Struct; import org.apache.kafka.connect.errors.ConnectException; import org.apache.kafka.connect.sink.SinkRecord; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +@Slf4j public class RecordConverter { - private static final Logger LOG = LoggerFactory.getLogger(RecordConverter.class); - /** * Convert sink record to Redis command. * diff --git a/src/main/java/io/github/jaredpetersen/kafkaconnectredis/sink/writer/Writer.java b/src/main/java/io/github/jaredpetersen/kafkaconnectredis/sink/writer/Writer.java index cb29fa7..f46012b 100644 --- a/src/main/java/io/github/jaredpetersen/kafkaconnectredis/sink/writer/Writer.java +++ b/src/main/java/io/github/jaredpetersen/kafkaconnectredis/sink/writer/Writer.java @@ -19,13 +19,11 @@ import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; +import lombok.extern.slf4j.Slf4j; import org.apache.kafka.connect.errors.ConnectException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +@Slf4j public class Writer { - private static final Logger LOG = LoggerFactory.getLogger(Writer.class); - private final RedisCommands redisStandaloneCommands; private final RedisClusterCommands redisClusterCommands; private final boolean clusterEnabled; diff --git a/src/main/java/io/github/jaredpetersen/kafkaconnectredis/sink/writer/record/RedisArbitraryCommand.java b/src/main/java/io/github/jaredpetersen/kafkaconnectredis/sink/writer/record/RedisArbitraryCommand.java index 302ffa8..5fefdb5 100644 --- a/src/main/java/io/github/jaredpetersen/kafkaconnectredis/sink/writer/record/RedisArbitraryCommand.java +++ b/src/main/java/io/github/jaredpetersen/kafkaconnectredis/sink/writer/record/RedisArbitraryCommand.java @@ -5,13 +5,13 @@ import lombok.Value; @Value -@Builder(builderClassName = "Builder") +@Builder public class RedisArbitraryCommand implements RedisCommand { Command command = Command.ARBITRARY; Payload payload; @Value - @lombok.Builder(builderClassName = "Builder") + @lombok.Builder public static class Payload { String command; List arguments; diff --git a/src/main/java/io/github/jaredpetersen/kafkaconnectredis/sink/writer/record/RedisExpireCommand.java b/src/main/java/io/github/jaredpetersen/kafkaconnectredis/sink/writer/record/RedisExpireCommand.java index 358a938..5fb10e3 100644 --- a/src/main/java/io/github/jaredpetersen/kafkaconnectredis/sink/writer/record/RedisExpireCommand.java +++ b/src/main/java/io/github/jaredpetersen/kafkaconnectredis/sink/writer/record/RedisExpireCommand.java @@ -4,13 +4,13 @@ import lombok.Value; @Value -@Builder(builderClassName = "Builder") +@Builder public class RedisExpireCommand implements RedisCommand { Command command = Command.EXPIRE; Payload payload; @Value - @lombok.Builder(builderClassName = "Builder") + @lombok.Builder public static class Payload { String key; long seconds; diff --git a/src/main/java/io/github/jaredpetersen/kafkaconnectredis/sink/writer/record/RedisExpireatCommand.java b/src/main/java/io/github/jaredpetersen/kafkaconnectredis/sink/writer/record/RedisExpireatCommand.java index 240485e..97aaaad 100644 --- a/src/main/java/io/github/jaredpetersen/kafkaconnectredis/sink/writer/record/RedisExpireatCommand.java +++ b/src/main/java/io/github/jaredpetersen/kafkaconnectredis/sink/writer/record/RedisExpireatCommand.java @@ -4,13 +4,13 @@ import lombok.Value; @Value -@Builder(builderClassName = "Builder") +@Builder public class RedisExpireatCommand implements RedisCommand { Command command = Command.EXPIREAT; Payload payload; @Value - @lombok.Builder(builderClassName = "Builder") + @lombok.Builder public static class Payload { String key; long timestamp; diff --git a/src/main/java/io/github/jaredpetersen/kafkaconnectredis/sink/writer/record/RedisGeoaddCommand.java b/src/main/java/io/github/jaredpetersen/kafkaconnectredis/sink/writer/record/RedisGeoaddCommand.java index 7289529..9b33a31 100644 --- a/src/main/java/io/github/jaredpetersen/kafkaconnectredis/sink/writer/record/RedisGeoaddCommand.java +++ b/src/main/java/io/github/jaredpetersen/kafkaconnectredis/sink/writer/record/RedisGeoaddCommand.java @@ -5,19 +5,19 @@ import lombok.Value; @Value -@Builder(builderClassName = "Builder") +@Builder public class RedisGeoaddCommand implements RedisCommand { Command command = Command.GEOADD; Payload payload; @Value - @lombok.Builder(builderClassName = "Builder") + @lombok.Builder public static class Payload { String key; List values; @Value - @lombok.Builder(builderClassName = "Builder") + @lombok.Builder public static class GeoLocation { double latitude; double longitude; diff --git a/src/main/java/io/github/jaredpetersen/kafkaconnectredis/sink/writer/record/RedisPexpireCommand.java b/src/main/java/io/github/jaredpetersen/kafkaconnectredis/sink/writer/record/RedisPexpireCommand.java index 3c71b53..a439659 100644 --- a/src/main/java/io/github/jaredpetersen/kafkaconnectredis/sink/writer/record/RedisPexpireCommand.java +++ b/src/main/java/io/github/jaredpetersen/kafkaconnectredis/sink/writer/record/RedisPexpireCommand.java @@ -4,13 +4,13 @@ import lombok.Value; @Value -@Builder(builderClassName = "Builder") +@Builder public class RedisPexpireCommand implements RedisCommand { Command command = Command.PEXPIRE; Payload payload; @Value - @lombok.Builder(builderClassName = "Builder") + @lombok.Builder public static class Payload { String key; long milliseconds; diff --git a/src/main/java/io/github/jaredpetersen/kafkaconnectredis/sink/writer/record/RedisSaddCommand.java b/src/main/java/io/github/jaredpetersen/kafkaconnectredis/sink/writer/record/RedisSaddCommand.java index 0a8976b..139e9b9 100644 --- a/src/main/java/io/github/jaredpetersen/kafkaconnectredis/sink/writer/record/RedisSaddCommand.java +++ b/src/main/java/io/github/jaredpetersen/kafkaconnectredis/sink/writer/record/RedisSaddCommand.java @@ -5,13 +5,13 @@ import lombok.Value; @Value -@Builder(builderClassName = "Builder") +@Builder public class RedisSaddCommand implements RedisCommand { Command command = Command.SADD; Payload payload; @Value - @lombok.Builder(builderClassName = "Builder") + @lombok.Builder public static class Payload { String key; List values; diff --git a/src/main/java/io/github/jaredpetersen/kafkaconnectredis/sink/writer/record/RedisSetCommand.java b/src/main/java/io/github/jaredpetersen/kafkaconnectredis/sink/writer/record/RedisSetCommand.java index 887dd34..9cfbfbd 100644 --- a/src/main/java/io/github/jaredpetersen/kafkaconnectredis/sink/writer/record/RedisSetCommand.java +++ b/src/main/java/io/github/jaredpetersen/kafkaconnectredis/sink/writer/record/RedisSetCommand.java @@ -4,13 +4,13 @@ import lombok.Value; @Value -@Builder(builderClassName = "Builder") +@Builder public class RedisSetCommand implements RedisCommand { Command command = Command.SET; Payload payload; @Value - @lombok.Builder(builderClassName = "Builder") + @lombok.Builder public static class Payload { public enum Condition { NX, XX } @@ -20,7 +20,7 @@ public enum Condition { NX, XX } Condition condition; @Value - @lombok.Builder(builderClassName = "Builder") + @lombok.Builder public static class Expiration { public enum Type { EX, PX, KEEPTTL } diff --git a/src/main/java/io/github/jaredpetersen/kafkaconnectredis/source/RedisSourceTask.java b/src/main/java/io/github/jaredpetersen/kafkaconnectredis/source/RedisSourceTask.java index 56bd8bf..0a0069b 100644 --- a/src/main/java/io/github/jaredpetersen/kafkaconnectredis/source/RedisSourceTask.java +++ b/src/main/java/io/github/jaredpetersen/kafkaconnectredis/source/RedisSourceTask.java @@ -18,19 +18,18 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; +import lombok.extern.slf4j.Slf4j; import org.apache.kafka.common.config.ConfigException; import org.apache.kafka.connect.errors.ConnectException; import org.apache.kafka.connect.source.SourceRecord; import org.apache.kafka.connect.source.SourceTask; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * Kafka Connect Task for Kafka Connect Redis Sink. */ +@Slf4j public class RedisSourceTask extends SourceTask { private static final long MAX_POLL_SIZE = 10_000L; - private static final Logger LOG = LoggerFactory.getLogger(RedisSourceTask.class); private RedisClient redisStandaloneClient; private StatefulRedisPubSubConnection redisStandalonePubSubConnection; diff --git a/src/main/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/RedisMessage.java b/src/main/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/RedisMessage.java index 75db1e4..091a444 100644 --- a/src/main/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/RedisMessage.java +++ b/src/main/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/RedisMessage.java @@ -4,7 +4,7 @@ import lombok.Value; @Value -@Builder(builderClassName = "Builder") +@Builder public class RedisMessage { String pattern; String channel; diff --git a/src/main/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/subscriber/RedisListener.java b/src/main/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/subscriber/RedisListener.java index 48f596e..f8ba566 100644 --- a/src/main/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/subscriber/RedisListener.java +++ b/src/main/java/io/github/jaredpetersen/kafkaconnectredis/source/listener/subscriber/RedisListener.java @@ -2,12 +2,10 @@ import io.github.jaredpetersen.kafkaconnectredis.source.listener.RedisMessage; import java.util.concurrent.ConcurrentLinkedQueue; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import lombok.extern.slf4j.Slf4j; +@Slf4j abstract class RedisListener { - private static final Logger LOG = LoggerFactory.getLogger(RedisListener.class); - private final ConcurrentLinkedQueue messageQueue; public RedisListener(ConcurrentLinkedQueue messageQueue) { diff --git a/src/main/java/io/github/jaredpetersen/kafkaconnectredis/util/VersionUtil.java b/src/main/java/io/github/jaredpetersen/kafkaconnectredis/util/VersionUtil.java index b86f0c5..a970f82 100644 --- a/src/main/java/io/github/jaredpetersen/kafkaconnectredis/util/VersionUtil.java +++ b/src/main/java/io/github/jaredpetersen/kafkaconnectredis/util/VersionUtil.java @@ -2,14 +2,13 @@ import java.io.IOException; import java.util.Properties; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import lombok.extern.slf4j.Slf4j; /** * Provide version information. */ +@Slf4j public class VersionUtil { - private static final Logger LOGGER = LoggerFactory.getLogger(VersionUtil.class); private static final Properties PROPERTIES = new Properties(); static { @@ -17,7 +16,7 @@ public class VersionUtil { PROPERTIES.load(VersionUtil.class.getClassLoader().getResourceAsStream("kafka-connect-redis.properties")); } catch (IOException exception) { - LOGGER.error("failed to load properties", exception); + LOG.error("failed to load properties", exception); } } From 9365aa5290d302b6a1a97c32179e6f5e4fb699d8 Mon Sep 17 00:00:00 2001 From: Jared Petersen Date: Sat, 17 Jul 2021 02:10:44 -0700 Subject: [PATCH 10/10] added missing trailing newline to lombok config --- lombok.config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lombok.config b/lombok.config index 2992a18..e150afd 100644 --- a/lombok.config +++ b/lombok.config @@ -1,4 +1,4 @@ config.stopBubbling = true lombok.addLombokGeneratedAnnotation = true lombok.builder.className = Builder -lombok.log.fieldname = LOG \ No newline at end of file +lombok.log.fieldname = LOG