diff --git a/README.md b/README.md index 6979a298..18a8faf5 100644 --- a/README.md +++ b/README.md @@ -3,44 +3,24 @@ embedded-redis Redis embedded server for Java integration testing - -Maven dependency -============== - -Currently embedded-redis is available in clojars repository: -``` - - clojars.org - http://clojars.org/repo - -``` - -Dependency configuration: -``` - - redis.embedded - embedded-redis - 0.3 - -``` -More at https://clojars.org/redis.embedded/embedded-redis - -Usage example +Usage ============== Running RedisServer is as simple as: -``` +```java RedisServer redisServer = new RedisServer(6379); redisServer.start(); // do some work redisServer.stop(); ``` + You can also provide RedisServer with your own redis executable to run: -``` +```java RedisServer redisServer = new RedisServer("/path/to/your/redis", 6379); ``` + You can also use fluent API to create RedisServer: -``` +```java RedisServer redisServer = RedisServer.builder() .executable("/path/to/your/redis") .port(6379) @@ -48,8 +28,9 @@ RedisServer redisServer = RedisServer.builder() .configFile("/path/to/your/redis.conf") .build(); ``` + Or even create simple redis.conf file from scratch: -``` +```java RedisServer redisServer = RedisServer.builder() .executable("/path/to/your/redis") .port(6379) @@ -58,15 +39,23 @@ RedisServer redisServer = RedisServer.builder() .setting("appendonly no") .build(); ``` -A simple redis integration test would look like this: -``` + +Our Embedded Redis has support for HA Redis clusters with Sentinels and master-slave replication + +A simple redis integration test with Redis cluster setup similar to that from production would look like this: +```java public class SomeIntegrationTestThatRequiresRedis { - private RedisServer redisServer; + private RedisCluster cluster; @Before public void setup() throws Exception { - redisServer = new RedisServer(6379); // or new RedisServer("/path/to/your/redis", 6379); - redisServer.start(); + //creates a cluster with 3 sentinels, quorum size of 2 and 3 replication groups, each with one master and one slave + cluster = RedisCluster.builder().sentinelCount(3).quorumSize(2) + .replicationGroup("master1", 1) + .replicationGroup("master2", 1) + .replicationGroup("master3", 1) + .build(); + cluster.start(); } @Test @@ -76,7 +65,7 @@ public class SomeIntegrationTestThatRequiresRedis { @After public void tearDown() throws Exception { - redisServer.stop(); + cluster.stop(); } } ``` @@ -86,7 +75,8 @@ Redis version ============== When not provided with the desired redis executable, RedisServer runs os-dependent executable enclosed in jar. Currently is uses: -- Redis 2.6.14 in case of Linux/Unix +- Redis 2.8.19 in case of Linux/Unix +- Redis 2.8.19 in case of OSX - unofficial Win32/64 port from https://github.com/MSOpenTech/redis (branch 2.6) in case of Windows However, you should provide RedisServer with redis executable if you need specific version. diff --git a/src/main/java/redis/embedded/RedisClusterBuilder.java b/src/main/java/redis/embedded/RedisClusterBuilder.java index 7bda5ceb..acc8b5d5 100644 --- a/src/main/java/redis/embedded/RedisClusterBuilder.java +++ b/src/main/java/redis/embedded/RedisClusterBuilder.java @@ -32,6 +32,11 @@ public RedisClusterBuilder sentinelCount(int sentinelCount) { return this; } + public RedisClusterBuilder sentinelStartingPort(int startingPort) { + this.currentSentinelPort = startingPort; + return this; + } + public RedisClusterBuilder quorumSize(int quorumSize) { this.quorumSize = quorumSize; return this; diff --git a/src/main/java/redis/embedded/RedisSentinelBuilder.java b/src/main/java/redis/embedded/RedisSentinelBuilder.java index e1fe821c..41c73770 100644 --- a/src/main/java/redis/embedded/RedisSentinelBuilder.java +++ b/src/main/java/redis/embedded/RedisSentinelBuilder.java @@ -82,7 +82,7 @@ public RedisSentinelBuilder parallelSyncs(int parallelSyncs) { public RedisSentinelBuilder configFile(String redisConf) { if (redisConfigBuilder != null) { - throw new RuntimeException("Redis configuration is already partially build using setting(String) method!"); + throw new RedisBuildingException("Redis configuration is already partially build using setting(String) method!"); } this.sentinelConf = redisConf; return this; @@ -90,7 +90,7 @@ public RedisSentinelBuilder configFile(String redisConf) { public RedisSentinelBuilder setting(String configLine) { if (sentinelConf != null) { - throw new RuntimeException("Redis configuration is already set using redis conf file!"); + throw new RedisBuildingException("Redis configuration is already set using redis conf file!"); } if (redisConfigBuilder == null) { @@ -123,6 +123,7 @@ private void tryResolveConfAndExec() { public void reset() { this.redisConfigBuilder = null; + this.sentinelConf = null; } public void addDefaultReplicationGroup() { diff --git a/src/main/java/redis/embedded/RedisServerBuilder.java b/src/main/java/redis/embedded/RedisServerBuilder.java index da8b0cf6..39cb1ce5 100644 --- a/src/main/java/redis/embedded/RedisServerBuilder.java +++ b/src/main/java/redis/embedded/RedisServerBuilder.java @@ -53,7 +53,7 @@ public RedisServerBuilder slaveOf(InetSocketAddress slaveOf) { public RedisServerBuilder configFile(String redisConf) { if (redisConfigBuilder != null) { - throw new RuntimeException("Redis configuration is already partially build using setting(String) method!"); + throw new RedisBuildingException("Redis configuration is already partially build using setting(String) method!"); } this.redisConf = redisConf; return this; @@ -61,7 +61,7 @@ public RedisServerBuilder configFile(String redisConf) { public RedisServerBuilder setting(String configLine) { if (redisConf != null) { - throw new RuntimeException("Redis configuration is already set using redis conf file!"); + throw new RedisBuildingException("Redis configuration is already set using redis conf file!"); } if (redisConfigBuilder == null) { @@ -82,6 +82,7 @@ public RedisServer build() { public void reset() { this.redisConfigBuilder = null; this.slaveOf = null; + this.redisConf = null; } private void tryResolveConfAndExec() { diff --git a/src/main/java/redis/embedded/exceptions/RedisBuildingException.java b/src/main/java/redis/embedded/exceptions/RedisBuildingException.java index 03493d63..29fa76c6 100644 --- a/src/main/java/redis/embedded/exceptions/RedisBuildingException.java +++ b/src/main/java/redis/embedded/exceptions/RedisBuildingException.java @@ -7,4 +7,8 @@ public class RedisBuildingException extends RuntimeException { public RedisBuildingException(String message, Throwable cause) { super(message, cause); } + + public RedisBuildingException(String message) { + super(message); + } } diff --git a/src/test/java/redis/embedded/RedisClusterTest.java b/src/test/java/redis/embedded/RedisClusterTest.java index 994ecc3f..0217e420 100644 --- a/src/test/java/redis/embedded/RedisClusterTest.java +++ b/src/test/java/redis/embedded/RedisClusterTest.java @@ -1,11 +1,15 @@ package redis.embedded; +import com.google.common.collect.Sets; import org.junit.Before; import org.junit.Test; +import redis.clients.jedis.Jedis; +import redis.clients.jedis.JedisSentinelPool; import java.util.Arrays; import java.util.List; +import static org.junit.Assert.assertEquals; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -75,4 +79,130 @@ public void isActiveShouldCheckEntireClusterIfAllActive() throws Exception { sentinels.stream().forEach(s -> verify(s).isActive()); servers.stream().forEach(m -> verify(m).isActive()); } + + @Test + public void testSimpleOperationsAfterRunWithSingleMasterNoSlavesCluster() throws Exception { + //given + final RedisCluster cluster = RedisCluster.builder().sentinelCount(1).replicationGroup("ourmaster", 0).build(); + cluster.start(); + + //when + JedisSentinelPool pool = null; + Jedis jedis = null; + try { + pool = new JedisSentinelPool("ourmaster", Sets.newHashSet("localhost:26379")); + jedis = testPool(pool); + } finally { + if (jedis != null) + pool.returnResource(jedis); + cluster.stop(); + } + } + + @Test + public void testSimpleOperationsAfterRunWithSingleMasterAndOneSlave() throws Exception { + //given + final RedisCluster cluster = RedisCluster.builder().sentinelCount(1).replicationGroup("ourmaster", 1).build(); + cluster.start(); + + //when + JedisSentinelPool pool = null; + Jedis jedis = null; + try { + pool = new JedisSentinelPool("ourmaster", Sets.newHashSet("localhost:26379")); + jedis = testPool(pool); + } finally { + if (jedis != null) + pool.returnResource(jedis); + cluster.stop(); + } + } + + @Test + public void testSimpleOperationsAfterRunWithSingleMasterMultipleSlaves() throws Exception { + //given + final RedisCluster cluster = RedisCluster.builder().sentinelCount(1).replicationGroup("ourmaster", 2).build(); + cluster.start(); + + //when + JedisSentinelPool pool = null; + Jedis jedis = null; + try { + pool = new JedisSentinelPool("ourmaster", Sets.newHashSet("localhost:26379")); + jedis = testPool(pool); + } finally { + if (jedis != null) + pool.returnResource(jedis); + cluster.stop(); + } + } + + @Test + public void testSimpleOperationsAfterRunWithTwoSentinelsSingleMasterMultipleSlaves() throws Exception { + //given + final RedisCluster cluster = RedisCluster.builder().sentinelCount(2).replicationGroup("ourmaster", 2).build(); + cluster.start(); + + //when + JedisSentinelPool pool = null; + Jedis jedis = null; + try { + pool = new JedisSentinelPool("ourmaster", Sets.newHashSet("localhost:26379", "localhost:26380")); + jedis = testPool(pool); + } finally { + if (jedis != null) + pool.returnResource(jedis); + cluster.stop(); + } + } + + @Test + public void testSimpleOperationsAfterRunWithThreeSentinelsThreeMastersOneSlavePerMasterCluster() throws Exception { + //given + final String master1 = "master1"; + final String master2 = "master2"; + final String master3 = "master3"; + final RedisCluster cluster = RedisCluster.builder().sentinelCount(3).quorumSize(2) + .replicationGroup(master1, 1) + .replicationGroup(master2, 1) + .replicationGroup(master3, 1) + .build(); + cluster.start(); + + //when + JedisSentinelPool pool1 = null; + JedisSentinelPool pool2 = null; + JedisSentinelPool pool3 = null; + Jedis jedis1 = null; + Jedis jedis2 = null; + Jedis jedis3 = null; + try { + pool1 = new JedisSentinelPool(master1, Sets.newHashSet("localhost:26379", "localhost:26380", "localhost:26381")); + pool2 = new JedisSentinelPool(master2, Sets.newHashSet("localhost:26379", "localhost:26380", "localhost:26381")); + pool3 = new JedisSentinelPool(master3, Sets.newHashSet("localhost:26379", "localhost:26380", "localhost:26381")); + jedis1 = testPool(pool1); + jedis2 = testPool(pool2); + jedis3 = testPool(pool3); + } finally { + if (jedis1 != null) + pool1.returnResource(jedis1); + if (jedis2 != null) + pool2.returnResource(jedis2); + if (jedis3 != null) + pool3.returnResource(jedis3); + cluster.stop(); + } + } + + private Jedis testPool(JedisSentinelPool pool) { + Jedis jedis; + jedis = pool.getResource(); + jedis.mset("abc", "1", "def", "2"); + + //then + assertEquals("1", jedis.mget("abc").get(0)); + assertEquals("2", jedis.mget("def").get(0)); + assertEquals(null, jedis.mget("xyz").get(0)); + return jedis; + } } \ No newline at end of file diff --git a/src/test/java/redis/embedded/RedisSentinelTest.java b/src/test/java/redis/embedded/RedisSentinelTest.java index 913299c8..5dbc2204 100644 --- a/src/test/java/redis/embedded/RedisSentinelTest.java +++ b/src/test/java/redis/embedded/RedisSentinelTest.java @@ -1,9 +1,14 @@ package redis.embedded; +import com.google.common.collect.Sets; import org.junit.Test; +import redis.clients.jedis.Jedis; +import redis.clients.jedis.JedisSentinelPool; import java.util.concurrent.TimeUnit; +import static org.junit.Assert.assertEquals; + public class RedisSentinelTest { private RedisSentinel sentinel; private RedisServer server; @@ -19,4 +24,46 @@ public void testSimpleRun() throws Exception { sentinel.stop(); } + @Test + public void shouldAllowSubsequentRuns() throws Exception { + sentinel = RedisSentinel.builder().build(); + sentinel.start(); + sentinel.stop(); + + sentinel.start(); + sentinel.stop(); + + sentinel.start(); + sentinel.stop(); + } + + @Test + public void testSimpleOperationsAfterRun() throws Exception { + //given + server = new RedisServer(); + sentinel = RedisSentinel.builder().build(); + server.start(); + sentinel.start(); + TimeUnit.SECONDS.sleep(1); + + //when + JedisSentinelPool pool = null; + Jedis jedis = null; + try { + pool = new JedisSentinelPool("mymaster", Sets.newHashSet("localhost:26379")); + jedis = pool.getResource(); + jedis.mset("abc", "1", "def", "2"); + + //then + assertEquals("1", jedis.mget("abc").get(0)); + assertEquals("2", jedis.mget("def").get(0)); + assertEquals(null, jedis.mget("xyz").get(0)); + } finally { + if (jedis != null) + pool.returnResource(jedis); + sentinel.stop(); + server.stop(); + } + } + } \ No newline at end of file