├── .gitignore ├── .travis.yml ├── README.md ├── pom.xml ├── release.sh └── src ├── main ├── java │ └── redis │ │ └── embedded │ │ ├── AbstractRedisInstance.java │ │ ├── PortProvider.java │ │ ├── Redis.java │ │ ├── RedisCluster.java │ │ ├── RedisClusterBuilder.java │ │ ├── RedisExecProvider.java │ │ ├── RedisSentinel.java │ │ ├── RedisSentinelBuilder.java │ │ ├── RedisServer.java │ │ ├── RedisServerBuilder.java │ │ ├── exceptions │ │ ├── EmbeddedRedisException.java │ │ ├── OsDetectionException.java │ │ └── RedisBuildingException.java │ │ ├── ports │ │ ├── EphemeralPortProvider.java │ │ ├── PredefinedPortProvider.java │ │ └── SequencePortProvider.java │ │ └── util │ │ ├── Architecture.java │ │ ├── JarUtil.java │ │ ├── JedisUtil.java │ │ ├── OS.java │ │ ├── OSDetector.java │ │ └── OsArchitecture.java └── resources │ ├── redis-server-2.8.19 │ ├── redis-server-2.8.19-32 │ ├── redis-server-2.8.19.app │ └── redis-server-2.8.19.exe └── test ├── java └── redis │ └── embedded │ ├── RedisClusterTest.java │ ├── RedisSentinelTest.java │ ├── RedisServerClusterTest.java │ ├── RedisServerTest.java │ ├── SpringDataConnectivityTest.java │ └── ports │ ├── EphemeralPortProviderTest.java │ ├── PredefinedPortProviderTest.java │ └── SequencePortProviderTest.java └── resources ├── redis-2.x-sentinel-startup-output.txt ├── redis-2.x-standalone-startup-output.txt ├── redis-3.x-sentinel-startup-output.txt ├── redis-3.x-standalone-startup-output.txt ├── redis-4.x-sentinel-startup-output.txt └── redis-4.x-standalone-startup-output.txt /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | .project 3 | .classpath 4 | .settings 5 | .idea 6 | *.iml 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | 3 | dist: trusty 4 | 5 | jdk: 6 | - oraclejdk8 7 | 8 | install: 9 | true 10 | 11 | before_script: 12 | - pip install --user codecov 13 | 14 | script: 15 | - mvn -U -B -V test --fail-at-end -Dsource.skip=true -Dmaven.javadoc.skip=true 16 | 17 | after_success: 18 | - mvn clean test jacoco:report 19 | - codecov --build "$TRAVIS_JOB_NUMBER-jdk8" 20 | 21 | notifications: 22 | email: 23 | on_failure: change -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | embedded-redis 2 | ============== 3 | 4 | Redis embedded server for Java integration testing 5 | 6 | Fork Notes 7 | ============== 8 | This repository clones from [kstyrc](https://github.com/kstyrc/embedded-redis) original repository. 9 | The aim is to release some long waiting fixes. 10 | 11 | 12 | **Source Website:** *[github.com/ozimov/embedded-redis](http://github.com/ozimov/embedded-redis/)*
13 | 14 | **Latest Release:** *0.7.3*
15 | **Latest Artifact:** *it.ozimov:embedded-redis*
16 | **Continuous Integration:**
17 | [![Maven Central](https://maven-badges.herokuapp.com/maven-central/it.ozimov/embedded-redis/badge.svg)](https://maven-badges.herokuapp.com/maven-central/it.ozimov/embedded-redis) 18 |
19 | [![Build Status](https://travis-ci.org/ozimov/embedded-redis.svg?branch=master)](https://travis-ci.org/ozimov/embedded-redis) 20 | [![codecov.io](https://codecov.io/github/ozimov/embedded-redis/coverage.svg?branch=master)](https://codecov.io/github/ozimov/embedded-redis?branch=master) 21 | [![Codacy Badge](https://api.codacy.com/project/badge/grade/7a4364b93df6473fb18a597e900edceb)](https://www.codacy.com/app/roberto-trunfio/embedded-redis) 22 | 23 | ![codecov.io](https://codecov.io/github/ozimov/embedded-redis/branch.svg?branch=master) 24 | 25 | Maven dependency 26 | ============== 27 | 28 | Maven Central: 29 | ```xml 30 | 31 | it.ozimov 32 | embedded-redis 33 | 0.7.3 34 | 35 | ``` 36 | 37 | Usage 38 | ============== 39 | 40 | Running RedisServer is as simple as: 41 | ```java 42 | RedisServer redisServer = new RedisServer(6379); 43 | redisServer.start(); 44 | // do some work 45 | redisServer.stop(); 46 | ``` 47 | 48 | You can also provide RedisServer with your own executable: 49 | ```java 50 | // 1) given explicit file (os-independence broken!) 51 | RedisServer redisServer = new RedisServer("/path/to/your/redis", 6379); 52 | 53 | // 2) given os-independent matrix 54 | RedisExecProvider customProvider = RedisExecProvider.defaultProvider() 55 | .override(OS.UNIX, "/path/to/unix/redis") 56 | .override(OS.WINDOWS, Architecture.x86, "/path/to/windows/redis") 57 | .override(OS.Windows, Architecture.x86_64, "/path/to/windows/redis") 58 | .override(OS.MAC_OS_X, Architecture.x86, "/path/to/macosx/redis") 59 | .override(OS.MAC_OS_X, Architecture.x86_64, "/path/to/macosx/redis") 60 | 61 | RedisServer redisServer = new RedisServer(customProvider, 6379); 62 | ``` 63 | 64 | You can also use fluent API to create RedisServer: 65 | ```java 66 | RedisServer redisServer = RedisServer.builder() 67 | .redisExecProvider(customRedisProvider) 68 | .port(6379) 69 | .slaveOf("locahost", 6378) 70 | .configFile("/path/to/your/redis.conf") 71 | .build(); 72 | ``` 73 | 74 | Or even create simple redis.conf file from scratch: 75 | ```java 76 | RedisServer redisServer = RedisServer.builder() 77 | .redisExecProvider(customRedisProvider) 78 | .port(6379) 79 | .setting("bind 127.0.0.1") // good for local development on Windows to prevent security popups 80 | .slaveOf("locahost", 6378) 81 | .setting("daemonize no") 82 | .setting("appendonly no") 83 | .setting("maxmemory 128M") 84 | .build(); 85 | ``` 86 | 87 | ## Setting up a cluster 88 | 89 | Our Embedded Redis has support for HA Redis clusters with Sentinels and master-slave replication 90 | 91 | #### Using ephemeral ports 92 | A simple redis integration test with Redis cluster on ephemeral ports, with setup similar to that from production would look like this: 93 | ```java 94 | public class SomeIntegrationTestThatRequiresRedis { 95 | private RedisCluster cluster; 96 | private Set jedisSentinelHosts; 97 | 98 | @Before 99 | public void setup() throws Exception { 100 | //creates a cluster with 3 sentinels, quorum size of 2 and 3 replication groups, each with one master and one slave 101 | cluster = RedisCluster.builder().ephemeral().sentinelCount(3).quorumSize(2) 102 | .replicationGroup("master1", 1) 103 | .replicationGroup("master2", 1) 104 | .replicationGroup("master3", 1) 105 | .build(); 106 | cluster.start(); 107 | 108 | //retrieve ports on which sentinels have been started, using a simple Jedis utility class 109 | jedisSentinelHosts = JedisUtil.sentinelHosts(cluster); 110 | } 111 | 112 | @Test 113 | public void test() throws Exception { 114 | // testing code that requires redis running 115 | JedisSentinelPool pool = new JedisSentinelPool("master1", jedisSentinelHosts); 116 | } 117 | 118 | @After 119 | public void tearDown() throws Exception { 120 | cluster.stop(); 121 | } 122 | } 123 | ``` 124 | 125 | #### Retrieving ports 126 | The above example starts Redis cluster on ephemeral ports, which you can later get with ```cluster.ports()```, 127 | which will return a list of all ports of the cluster. You can also get ports of sentinels with ```cluster.sentinelPorts()``` 128 | or servers with ```cluster.serverPorts()```. ```JedisUtil``` class contains utility methods for use with Jedis client. 129 | 130 | #### Using predefined ports 131 | You can also start Redis cluster on predefined ports and even mix both approaches: 132 | ```java 133 | public class SomeIntegrationTestThatRequiresRedis { 134 | private RedisCluster cluster; 135 | 136 | @Before 137 | public void setup() throws Exception { 138 | final List sentinels = Arrays.asList(26739, 26912); 139 | final List group1 = Arrays.asList(6667, 6668); 140 | final List group2 = Arrays.asList(6387, 6379); 141 | //creates a cluster with 3 sentinels, quorum size of 2 and 3 replication groups, each with one master and one slave 142 | cluster = RedisCluster.builder().sentinelPorts(sentinels).quorumSize(2) 143 | .serverPorts(group1).replicationGroup("master1", 1) 144 | .serverPorts(group2).replicationGroup("master2", 1) 145 | .ephemeralServers().replicationGroup("master3", 1) 146 | .build(); 147 | cluster.start(); 148 | } 149 | //(...) 150 | ``` 151 | The above will create and start a cluster with sentinels on ports ```26739, 26912```, first replication group on ```6667, 6668```, 152 | second replication group on ```6387, 6379``` and third replication group on ephemeral ports. 153 | 154 | Redis version 155 | ============== 156 | 157 | When not provided with the desired redis executable, RedisServer runs os-dependent executable enclosed in jar. Currently is uses: 158 | - Redis 2.8.19 in case of Linux/Unix 159 | - Redis 2.8.19 in case of OSX 160 | - Redis 2.8.19 in case of Windows: https://github.com/MSOpenTech/redis/releases/tag/win-2.8.19 161 | 162 | However, you should provide RedisServer with redis executable if you need specific version. 163 | 164 | 165 | License 166 | ============== 167 | Licensed under the Apache License, Version 2.0 168 | 169 | 170 | Contributors 171 | ============== 172 | * Krzysztof Styrc ([@kstyrc](http://github.com/kstyrc)) 173 | * Piotr Turek ([@turu](http://github.com/turu)) 174 | * anthonyu ([@anthonyu](http://github.com/anthonyu)) 175 | * Artem Orobets ([@enisher](http://github.com/enisher)) 176 | * Sean Simonsen ([@SeanSimonsen](http://github.com/SeanSimonsen)) 177 | * Rob Winch ([@rwinch](http://github.com/rwinch)) 178 | 179 | 180 | Changelog 181 | ============== 182 | 183 | ### 0.6 184 | * Support JDK 6 + 185 | 186 | ### 0.5 187 | * OS detection fix 188 | * redis binary per OS/arch pair 189 | * Updated to 2.8.19 binary for Windows 190 | 191 | ### 0.4 192 | * Updated for Java 8 193 | * Added Sentinel support 194 | * Ability to create arbitrary clusters on arbitrary (ephemeral) ports 195 | * Updated to latest guava 196 | * Throw an exception if redis has not been started 197 | * Redis errorStream logged to System.out 198 | 199 | ### 0.3 200 | * Fluent API for RedisServer creation 201 | 202 | ### 0.2 203 | * Initial decent release 204 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | it.ozimov 5 | embedded-redis 6 | jar 7 | 0.7.4-SNAPSHOT 8 | embedded-redis 9 | Redis embedded server for Java integration testing. 10 | Project forked from https://github.com/kstyrc/embedded-redis 11 | https://github.com/ozimov/embedded-redis 12 | 13 | 14 | 15 | The Apache Software License, Version 2.0 16 | http://www.apache.org/licenses/LICENSE-2.0.txt 17 | repo 18 | 19 | 20 | 21 | 22 | https://github.com/ozimov/embedded-redis 23 | scm:git:https://github.com/ozimov/embedded-redis.git 24 | scm:git:https://github.com/ozimov/embedded-redis.git 25 | embedded-redis-0.7.2 26 | 27 | 28 | 29 | 30 | Krzysztof Styrc 31 | kstyrc@gmail.com 32 | 33 | 34 | 35 | 36 | UTF-8 37 | 38 | 39 | 40 | 41 | com.google.guava 42 | guava 43 | 21.0 44 | 45 | 46 | 47 | commons-io 48 | commons-io 49 | 2.5 50 | 51 | 52 | 53 | org.slf4j 54 | slf4j-simple 55 | 1.7.21 56 | 57 | 58 | 59 | commons-logging 60 | commons-logging 61 | 1.2 62 | 63 | 64 | 65 | 66 | redis.clients 67 | jedis 68 | 2.8.2 69 | 70 | test 71 | 72 | 73 | org.mockito 74 | mockito-all 75 | 1.10.19 76 | test 77 | 78 | 79 | 80 | junit 81 | junit 82 | 4.12 83 | test 84 | 85 | 86 | 87 | org.springframework.data 88 | spring-data-redis 89 | 1.8.0.RELEASE 90 | test 91 | 92 | 93 | 94 | 95 | 96 | 3.0.4 97 | 98 | 99 | 100 | 101 | ossrh 102 | https://oss.sonatype.org/content/repositories/snapshots 103 | 104 | 105 | ossrh 106 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 107 | 108 | 109 | 110 | 111 | 112 | ${project.basedir}/src/main/resources 113 | 114 | 115 | ${project.build.directory}/generated-resources 116 | 117 | 118 | 119 | 120 | 121 | org.apache.maven.plugins 122 | maven-surefire-plugin 123 | 2.19.1 124 | 125 | 126 | org.jacoco 127 | jacoco-maven-plugin 128 | 0.7.9 129 | 130 | 131 | default-prepare-agent 132 | 133 | prepare-agent 134 | 135 | 136 | 137 | report 138 | test 139 | 140 | report 141 | 142 | 143 | 144 | 145 | 146 | 147 | **/config/** 148 | **/model/** 149 | **/item/** 150 | 151 | 152 | 153 | 154 | 155 | 156 | org.codehaus.mojo 157 | versions-maven-plugin 158 | 2.3 159 | 160 | false 161 | 162 | 163 | 164 | org.apache.maven.plugins 165 | maven-scm-plugin 166 | 1.9.5 167 | 168 | false 169 | ${project.version} 170 | 171 | 172 | 173 | org.apache.maven.plugins 174 | maven-compiler-plugin 175 | 176 | 8 177 | 8 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | disable-java8-doclint 186 | 187 | [1.8,) 188 | 189 | 190 | 191 | 192 | org.apache.maven.plugins 193 | maven-javadoc-plugin 194 | 2.10.4 195 | 196 | -Xdoclint:none 197 | 198 | 199 | 200 | 201 | 202 | 203 | release 204 | 205 | 206 | 207 | org.apache.maven.plugins 208 | maven-source-plugin 209 | 3.0.1 210 | 211 | 212 | attach-sources 213 | 214 | jar-no-fork 215 | 216 | 217 | 218 | 219 | 220 | org.apache.maven.plugins 221 | maven-javadoc-plugin 222 | 2.10.3 223 | 224 | 225 | attach-javadocs 226 | 227 | jar 228 | 229 | 230 | 231 | 232 | 233 | org.apache.maven.plugins 234 | maven-gpg-plugin 235 | 1.6 236 | 237 | 238 | sign-artifacts 239 | verify 240 | 241 | sign 242 | 243 | 244 | 245 | 246 | 247 | org.sonatype.plugins 248 | nexus-staging-maven-plugin 249 | 1.6.7 250 | true 251 | 252 | ossrh 253 | https://oss.sonatype.org/ 254 | true 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | org.codehaus.mojo 266 | versions-maven-plugin 267 | 2.3 268 | 269 | 270 | 271 | dependency-updates-report 272 | plugin-updates-report 273 | property-updates-report 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | spring-releases 286 | https://repo.spring.io/libs-release 287 | 288 | 289 | 290 | 291 | 292 | spring-releases 293 | https://repo.spring.io/libs-release 294 | 295 | 296 | 297 | 298 | -------------------------------------------------------------------------------- /release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Deploy maven artifact in current directory into Maven central repository 3 | # using maven-release-plugin goals 4 | 5 | 6 | 7 | ########################################################### 8 | ########################################################### 9 | cat << "RELEASE" 10 | ██████╗ ███████╗██╗ ███████╗ █████╗ ███████╗███████╗ 11 | ██╔══██╗██╔════╝██║ ██╔════╝██╔══██╗██╔════╝██╔════╝ 12 | ██████╔╝█████╗ ██║ █████╗ ███████║███████╗█████╗ 13 | ██╔══██╗██╔══╝ ██║ ██╔══╝ ██╔══██║╚════██║██╔══╝ 14 | ██║ ██║███████╗███████╗███████╗██║ ██║███████║███████╗ 15 | ╚═╝ ╚═╝╚══════╝╚══════╝╚══════╝╚═╝ ╚═╝╚══════╝╚══════╝ 16 | RELEASE 17 | 18 | set -e 19 | 20 | mvn --settings ~/.m2/settings.xml scm:check-local-modification 21 | 22 | # release 23 | echo "\n----------------------------------------" 24 | echo "When at prompt, type the release version (e.g. from 1.0-SNAPSHOT to 1.0)\n" 25 | mvn --settings ~/.m2/settings.xml versions:set 26 | git commit -am "[Deploy phase] Preparing release" 27 | mvn --settings ~/.m2/settings.xml clean deploy -DskipTests -P release 28 | mvn --settings ~/.m2/settings.xml scm:tag 29 | echo 'Release deployed' 30 | 31 | 32 | ########################################################### 33 | ########################################################### 34 | # next development version 35 | cat << "NEXT_ITERATION" 36 | ███╗ ██╗███████╗██╗ ██╗████████╗ 37 | ████╗ ██║██╔════╝╚██╗██╔╝╚══██╔══╝ 38 | ██╔██╗ ██║█████╗ ╚███╔╝ ██║ 39 | ██║╚██╗██║██╔══╝ ██╔██╗ ██║ 40 | ██║ ╚████║███████╗██╔╝ ██╗ ██║ 41 | ╚═╝ ╚═══╝╚══════╝╚═╝ ╚═╝ ╚═╝ 42 | 43 | ██╗████████╗███████╗██████╗ █████╗ ████████╗██╗ ██████╗ ███╗ ██╗ 44 | ██║╚══██╔══╝██╔════╝██╔══██╗██╔══██╗╚══██╔══╝██║██╔═══██╗████╗ ██║ 45 | ██║ ██║ █████╗ ██████╔╝███████║ ██║ ██║██║ ██║██╔██╗ ██║ 46 | ██║ ██║ ██╔══╝ ██╔══██╗██╔══██║ ██║ ██║██║ ██║██║╚██╗██║ 47 | ██║ ██║ ███████╗██║ ██║██║ ██║ ██║ ██║╚██████╔╝██║ ╚████║ 48 | ╚═╝ ╚═╝ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝ 49 | NEXT_ITERATION 50 | 51 | echo "When at prompt, type the SNAPSHOT version (e.g. from 1.0 to 2.0-SNAPSHOT)\n" 52 | mvn --settings ~/.m2/settings.xml versions:set 53 | git commit -am "[Deploy phase] Preparing for next iteration" 54 | 55 | # updating origin 56 | git push --force --follow-tags 57 | 58 | echo 'Next iteration prepared' 59 | -------------------------------------------------------------------------------- /src/main/java/redis/embedded/AbstractRedisInstance.java: -------------------------------------------------------------------------------- 1 | package redis.embedded; 2 | 3 | import org.apache.commons.io.IOUtils; 4 | import org.apache.commons.logging.Log; 5 | import org.apache.commons.logging.LogFactory; 6 | import redis.embedded.exceptions.EmbeddedRedisException; 7 | 8 | import java.io.*; 9 | import java.util.Arrays; 10 | import java.util.Collections; 11 | import java.util.List; 12 | import java.util.concurrent.ExecutorService; 13 | import java.util.concurrent.Executors; 14 | 15 | abstract class AbstractRedisInstance implements Redis { 16 | 17 | private static Log log = LogFactory.getLog(AbstractRedisInstance.class); 18 | 19 | protected List args = Collections.emptyList(); 20 | private volatile boolean active = false; 21 | private Process redisProcess; 22 | private final int port; 23 | 24 | private ExecutorService executor; 25 | 26 | protected AbstractRedisInstance(int port) { 27 | this.port = port; 28 | } 29 | 30 | public boolean isActive() { 31 | return active; 32 | } 33 | 34 | public synchronized void start() throws EmbeddedRedisException { 35 | if (active) { 36 | throw new EmbeddedRedisException("This redis server instance is already running..."); 37 | } 38 | try { 39 | redisProcess = createRedisProcessBuilder().start(); 40 | installExitHook(); 41 | logErrors(); 42 | awaitRedisServerReady(); 43 | active = true; 44 | } catch (IOException e) { 45 | throw new EmbeddedRedisException("Failed to start Redis instance", e); 46 | } 47 | } 48 | 49 | private void installExitHook() { 50 | Runtime.getRuntime().addShutdownHook(new Thread(this::stop, "RedisInstanceCleaner")); 51 | } 52 | 53 | private void logErrors() { 54 | final InputStream errorStream = redisProcess.getErrorStream(); 55 | BufferedReader reader = new BufferedReader(new InputStreamReader(errorStream)); 56 | Runnable printReaderTask = new PrintReaderRunnable(reader); 57 | executor = Executors.newSingleThreadExecutor(); 58 | executor.submit(printReaderTask); 59 | } 60 | 61 | private void awaitRedisServerReady() throws IOException { 62 | BufferedReader reader = new BufferedReader(new InputStreamReader(redisProcess.getInputStream())); 63 | try { 64 | StringBuffer outputStringBuffer = new StringBuffer(); 65 | String outputLine; 66 | do { 67 | outputLine = reader.readLine(); 68 | if (outputLine == null) { 69 | //Something goes wrong. Stream is ended before server was activated. 70 | throw new RuntimeException("Can't start redis server. Check logs for details. Redis process log: " + outputStringBuffer.toString()); 71 | } else { 72 | outputStringBuffer.append("\n"); 73 | outputStringBuffer.append(outputLine); 74 | } 75 | } while (!outputLine.matches(redisReadyPattern())); 76 | } finally { 77 | IOUtils.closeQuietly(reader); 78 | } 79 | } 80 | 81 | protected abstract String redisReadyPattern(); 82 | 83 | private ProcessBuilder createRedisProcessBuilder() { 84 | File executable = new File(args.get(0)); 85 | ProcessBuilder pb = new ProcessBuilder(args); 86 | pb.directory(executable.getParentFile()); 87 | return pb; 88 | } 89 | 90 | public synchronized void stop() throws EmbeddedRedisException { 91 | if (active) { 92 | log.info("Stopping redis server..."); 93 | 94 | if (executor != null && !executor.isShutdown()) { 95 | executor.shutdown(); 96 | } 97 | redisProcess.destroy(); 98 | tryWaitFor(); 99 | log.info("Redis exited"); 100 | active = false; 101 | } 102 | } 103 | 104 | private void tryWaitFor() { 105 | try { 106 | redisProcess.waitFor(); 107 | } catch (InterruptedException e) { 108 | throw new EmbeddedRedisException("Failed to stop redis instance", e); 109 | } 110 | } 111 | 112 | public List ports() { 113 | return Arrays.asList(port); 114 | } 115 | 116 | private static class PrintReaderRunnable implements Runnable { 117 | private final BufferedReader reader; 118 | 119 | private PrintReaderRunnable(BufferedReader reader) { 120 | this.reader = reader; 121 | } 122 | 123 | public void run() { 124 | try { 125 | readLines(); 126 | } finally { 127 | IOUtils.closeQuietly(reader); 128 | } 129 | } 130 | 131 | public void readLines() { 132 | try { 133 | String line; 134 | while ((line = reader.readLine()) != null) { 135 | System.out.println(line); 136 | } 137 | } catch (IOException e) { 138 | e.printStackTrace(); 139 | } 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/main/java/redis/embedded/PortProvider.java: -------------------------------------------------------------------------------- 1 | package redis.embedded; 2 | 3 | public interface PortProvider { 4 | int next(); 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/redis/embedded/Redis.java: -------------------------------------------------------------------------------- 1 | package redis.embedded; 2 | 3 | import redis.embedded.exceptions.EmbeddedRedisException; 4 | 5 | import java.util.List; 6 | 7 | public interface Redis { 8 | boolean isActive(); 9 | 10 | void start() throws EmbeddedRedisException; 11 | 12 | void stop() throws EmbeddedRedisException; 13 | 14 | List ports(); 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/redis/embedded/RedisCluster.java: -------------------------------------------------------------------------------- 1 | package redis.embedded; 2 | 3 | import com.google.common.collect.Lists; 4 | import redis.embedded.exceptions.EmbeddedRedisException; 5 | 6 | import java.util.ArrayList; 7 | import java.util.LinkedList; 8 | import java.util.List; 9 | 10 | public class RedisCluster implements Redis { 11 | private final List sentinels = new LinkedList(); 12 | private final List servers = new LinkedList(); 13 | 14 | RedisCluster(List sentinels, List servers) { 15 | this.servers.addAll(servers); 16 | this.sentinels.addAll(sentinels); 17 | } 18 | 19 | @Override 20 | public boolean isActive() { 21 | for(Redis redis : sentinels) { 22 | if(!redis.isActive()) { 23 | return false; 24 | } 25 | } 26 | for(Redis redis : servers) { 27 | if(!redis.isActive()) { 28 | return false; 29 | } 30 | } 31 | return true; 32 | } 33 | 34 | @Override 35 | public void start() throws EmbeddedRedisException { 36 | for(Redis redis : sentinels) { 37 | redis.start(); 38 | } 39 | for(Redis redis : servers) { 40 | redis.start(); 41 | } 42 | } 43 | 44 | @Override 45 | public void stop() throws EmbeddedRedisException { 46 | for(Redis redis : sentinels) { 47 | redis.stop(); 48 | } 49 | for(Redis redis : servers) { 50 | redis.stop(); 51 | } 52 | } 53 | 54 | @Override 55 | public List ports() { 56 | List ports = new ArrayList(); 57 | ports.addAll(sentinelPorts()); 58 | ports.addAll(serverPorts()); 59 | return ports; 60 | } 61 | 62 | public List sentinels() { 63 | return Lists.newLinkedList(sentinels); 64 | } 65 | 66 | public List sentinelPorts() { 67 | List ports = new ArrayList(); 68 | for(Redis redis : sentinels) { 69 | ports.addAll(redis.ports()); 70 | } 71 | return ports; 72 | } 73 | 74 | public List servers() { 75 | return Lists.newLinkedList(servers); 76 | } 77 | 78 | public List serverPorts() { 79 | List ports = new ArrayList(); 80 | for(Redis redis : servers) { 81 | ports.addAll(redis.ports()); 82 | } 83 | return ports; 84 | } 85 | 86 | public static RedisClusterBuilder builder() { 87 | return new RedisClusterBuilder(); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/main/java/redis/embedded/RedisClusterBuilder.java: -------------------------------------------------------------------------------- 1 | package redis.embedded; 2 | 3 | import redis.embedded.ports.EphemeralPortProvider; 4 | import redis.embedded.ports.PredefinedPortProvider; 5 | import redis.embedded.ports.SequencePortProvider; 6 | 7 | import java.util.ArrayList; 8 | import java.util.Collection; 9 | import java.util.LinkedList; 10 | import java.util.List; 11 | 12 | public class RedisClusterBuilder { 13 | private RedisSentinelBuilder sentinelBuilder = new RedisSentinelBuilder(); 14 | private RedisServerBuilder serverBuilder = new RedisServerBuilder(); 15 | private int sentinelCount = 1; 16 | private int quorumSize = 1; 17 | private PortProvider sentinelPortProvider = new SequencePortProvider(26379); 18 | private PortProvider replicationGroupPortProvider = new SequencePortProvider(6379); 19 | private final List groups = new LinkedList(); 20 | 21 | public RedisClusterBuilder withSentinelBuilder(RedisSentinelBuilder sentinelBuilder) { 22 | this.sentinelBuilder = sentinelBuilder; 23 | return this; 24 | } 25 | 26 | public RedisClusterBuilder withServerBuilder(RedisServerBuilder serverBuilder) { 27 | this.serverBuilder = serverBuilder; 28 | return this; 29 | } 30 | 31 | public RedisClusterBuilder sentinelPorts(Collection ports) { 32 | this.sentinelPortProvider = new PredefinedPortProvider(ports); 33 | this.sentinelCount = ports.size(); 34 | return this; 35 | } 36 | 37 | public RedisClusterBuilder serverPorts(Collection ports) { 38 | this.replicationGroupPortProvider = new PredefinedPortProvider(ports); 39 | return this; 40 | } 41 | 42 | public RedisClusterBuilder ephemeralSentinels() { 43 | this.sentinelPortProvider = new EphemeralPortProvider(); 44 | return this; 45 | } 46 | 47 | public RedisClusterBuilder ephemeralServers() { 48 | this.replicationGroupPortProvider = new EphemeralPortProvider(); 49 | return this; 50 | } 51 | 52 | 53 | public RedisClusterBuilder ephemeral() { 54 | ephemeralSentinels(); 55 | ephemeralServers(); 56 | return this; 57 | } 58 | 59 | public RedisClusterBuilder sentinelCount(int sentinelCount) { 60 | this.sentinelCount = sentinelCount; 61 | return this; 62 | } 63 | 64 | public RedisClusterBuilder sentinelStartingPort(int startingPort) { 65 | this.sentinelPortProvider = new SequencePortProvider(startingPort); 66 | return this; 67 | } 68 | 69 | public RedisClusterBuilder quorumSize(int quorumSize) { 70 | this.quorumSize = quorumSize; 71 | return this; 72 | } 73 | 74 | public RedisClusterBuilder replicationGroup(String masterName, int slaveCount) { 75 | this.groups.add(new ReplicationGroup(masterName, slaveCount, this.replicationGroupPortProvider)); 76 | return this; 77 | } 78 | 79 | public RedisCluster build() { 80 | final List sentinels = buildSentinels(); 81 | final List servers = buildServers(); 82 | return new RedisCluster(sentinels, servers); 83 | } 84 | 85 | private List buildServers() { 86 | List servers = new ArrayList(); 87 | for(ReplicationGroup g : groups) { 88 | servers.add(buildMaster(g)); 89 | buildSlaves(servers, g); 90 | } 91 | return servers; 92 | } 93 | 94 | private void buildSlaves(List servers, ReplicationGroup g) { 95 | for (Integer slavePort : g.slavePorts) { 96 | serverBuilder.reset(); 97 | serverBuilder.port(slavePort); 98 | serverBuilder.slaveOf("localhost", g.masterPort); 99 | final RedisServer slave = serverBuilder.build(); 100 | servers.add(slave); 101 | } 102 | } 103 | 104 | private Redis buildMaster(ReplicationGroup g) { 105 | serverBuilder.reset(); 106 | return serverBuilder.port(g.masterPort).build(); 107 | } 108 | 109 | private List buildSentinels() { 110 | int toBuild = this.sentinelCount; 111 | final List sentinels = new LinkedList(); 112 | while (toBuild-- > 0) { 113 | sentinels.add(buildSentinel()); 114 | } 115 | return sentinels; 116 | } 117 | 118 | private Redis buildSentinel() { 119 | sentinelBuilder.reset(); 120 | sentinelBuilder.port(nextSentinelPort()); 121 | for(ReplicationGroup g : groups) { 122 | sentinelBuilder.masterName(g.masterName); 123 | sentinelBuilder.masterPort(g.masterPort); 124 | sentinelBuilder.quorumSize(quorumSize); 125 | sentinelBuilder.addDefaultReplicationGroup(); 126 | } 127 | return sentinelBuilder.build(); 128 | } 129 | 130 | private int nextSentinelPort() { 131 | return sentinelPortProvider.next(); 132 | } 133 | 134 | private static class ReplicationGroup { 135 | private final String masterName; 136 | private final int masterPort; 137 | private final List slavePorts = new LinkedList(); 138 | 139 | private ReplicationGroup(String masterName, int slaveCount, PortProvider portProvider) { 140 | this.masterName = masterName; 141 | masterPort = portProvider.next(); 142 | while (slaveCount-- > 0) { 143 | slavePorts.add(portProvider.next()); 144 | } 145 | } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/main/java/redis/embedded/RedisExecProvider.java: -------------------------------------------------------------------------------- 1 | package redis.embedded; 2 | 3 | import com.google.common.base.Preconditions; 4 | import com.google.common.collect.Maps; 5 | import redis.embedded.util.Architecture; 6 | import redis.embedded.util.JarUtil; 7 | import redis.embedded.util.OS; 8 | import redis.embedded.util.OsArchitecture; 9 | 10 | import java.io.File; 11 | import java.io.IOException; 12 | import java.util.Map; 13 | 14 | public class RedisExecProvider { 15 | 16 | private final Map executables = Maps.newHashMap(); 17 | 18 | public static RedisExecProvider defaultProvider() { 19 | return new RedisExecProvider(); 20 | } 21 | 22 | private RedisExecProvider() { 23 | initExecutables(); 24 | } 25 | 26 | private void initExecutables() { 27 | executables.put(OsArchitecture.WINDOWS_x86, "redis-server-2.8.19.exe"); 28 | executables.put(OsArchitecture.WINDOWS_x86_64, "redis-server-2.8.19.exe"); 29 | 30 | executables.put(OsArchitecture.UNIX_x86, "redis-server-2.8.19-32"); 31 | executables.put(OsArchitecture.UNIX_x86_64, "redis-server-2.8.19"); 32 | 33 | executables.put(OsArchitecture.MAC_OS_X_x86, "redis-server-2.8.19.app"); 34 | executables.put(OsArchitecture.MAC_OS_X_x86_64, "redis-server-2.8.19.app"); 35 | } 36 | 37 | public RedisExecProvider override(OS os, String executable) { 38 | Preconditions.checkNotNull(executable); 39 | for (Architecture arch : Architecture.values()) { 40 | override(os, arch, executable); 41 | } 42 | return this; 43 | } 44 | 45 | public RedisExecProvider override(OS os, Architecture arch, String executable) { 46 | Preconditions.checkNotNull(executable); 47 | executables.put(new OsArchitecture(os, arch), executable); 48 | return this; 49 | } 50 | 51 | public File get() throws IOException { 52 | OsArchitecture osArch = OsArchitecture.detect(); 53 | String executablePath = executables.get(osArch); 54 | return fileExists(executablePath) ? 55 | new File(executablePath) : 56 | JarUtil.extractExecutableFromJar(executablePath); 57 | 58 | } 59 | 60 | private boolean fileExists(String executablePath) { 61 | return new File(executablePath).exists(); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/redis/embedded/RedisSentinel.java: -------------------------------------------------------------------------------- 1 | package redis.embedded; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | public class RedisSentinel extends AbstractRedisInstance { 7 | private static final String REDIS_READY_PATTERN = ".*Sentinel (runid|ID) is.*"; 8 | 9 | public RedisSentinel(List args, int port) { 10 | super(port); 11 | this.args = new ArrayList(args); 12 | } 13 | 14 | public static RedisSentinelBuilder builder() { return new RedisSentinelBuilder(); } 15 | 16 | @Override 17 | protected String redisReadyPattern() { 18 | return REDIS_READY_PATTERN; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/redis/embedded/RedisSentinelBuilder.java: -------------------------------------------------------------------------------- 1 | package redis.embedded; 2 | 3 | import com.google.common.base.Preconditions; 4 | import com.google.common.io.Files; 5 | import redis.embedded.exceptions.RedisBuildingException; 6 | 7 | import java.io.File; 8 | import java.io.IOException; 9 | import java.nio.charset.Charset; 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | 13 | public class RedisSentinelBuilder { 14 | private static final String LINE_SEPARATOR = System.getProperty("line.separator"); 15 | private static final String CONF_FILENAME = "embedded-redis-sentinel"; 16 | private static final String MASTER_MONITOR_LINE = "sentinel monitor %s 127.0.0.1 %d %d"; 17 | private static final String DOWN_AFTER_LINE = "sentinel down-after-milliseconds %s %d"; 18 | private static final String FAILOVER_LINE = "sentinel failover-timeout %s %d"; 19 | private static final String PARALLEL_SYNCS_LINE = "sentinel parallel-syncs %s %d"; 20 | private static final String PORT_LINE = "port %d"; 21 | 22 | private File executable; 23 | private RedisExecProvider redisExecProvider = RedisExecProvider.defaultProvider(); 24 | private String bind="127.0.0.1"; 25 | private Integer port = 26379; 26 | private int masterPort = 6379; 27 | private String masterName = "mymaster"; 28 | private long downAfterMilliseconds = 60000L; 29 | private long failoverTimeout = 180000L; 30 | private int parallelSyncs = 1; 31 | private int quorumSize = 1; 32 | private String sentinelConf; 33 | 34 | private StringBuilder redisConfigBuilder; 35 | 36 | public RedisSentinelBuilder redisExecProvider(RedisExecProvider redisExecProvider) { 37 | this.redisExecProvider = redisExecProvider; 38 | return this; 39 | } 40 | 41 | public RedisSentinelBuilder bind(String bind) { 42 | this.bind = bind; 43 | return this; 44 | } 45 | 46 | public RedisSentinelBuilder port(Integer port) { 47 | this.port = port; 48 | return this; 49 | } 50 | 51 | public RedisSentinelBuilder masterPort(Integer masterPort) { 52 | this.masterPort = masterPort; 53 | return this; 54 | } 55 | 56 | public RedisSentinelBuilder masterName(String masterName) { 57 | this.masterName = masterName; 58 | return this; 59 | } 60 | 61 | public RedisSentinelBuilder quorumSize(int quorumSize) { 62 | this.quorumSize = quorumSize; 63 | return this; 64 | } 65 | 66 | public RedisSentinelBuilder downAfterMilliseconds(Long downAfterMilliseconds) { 67 | this.downAfterMilliseconds = downAfterMilliseconds; 68 | return this; 69 | } 70 | 71 | public RedisSentinelBuilder failoverTimeout(Long failoverTimeout) { 72 | this.failoverTimeout = failoverTimeout; 73 | return this; 74 | } 75 | 76 | public RedisSentinelBuilder parallelSyncs(int parallelSyncs) { 77 | this.parallelSyncs = parallelSyncs; 78 | return this; 79 | } 80 | 81 | public RedisSentinelBuilder configFile(String redisConf) { 82 | if (redisConfigBuilder != null) { 83 | throw new RedisBuildingException("Redis configuration is already partially build using setting(String) method!"); 84 | } 85 | this.sentinelConf = redisConf; 86 | return this; 87 | } 88 | 89 | public RedisSentinelBuilder setting(String configLine) { 90 | if (sentinelConf != null) { 91 | throw new RedisBuildingException("Redis configuration is already set using redis conf file!"); 92 | } 93 | 94 | if (redisConfigBuilder == null) { 95 | redisConfigBuilder = new StringBuilder(); 96 | } 97 | 98 | redisConfigBuilder.append(configLine); 99 | redisConfigBuilder.append(LINE_SEPARATOR); 100 | return this; 101 | } 102 | 103 | public RedisSentinel build() { 104 | tryResolveConfAndExec(); 105 | List args = buildCommandArgs(); 106 | return new RedisSentinel(args, port); 107 | } 108 | 109 | private void tryResolveConfAndExec() { 110 | try { 111 | if (sentinelConf == null) { 112 | resolveSentinelConf(); 113 | } 114 | executable = redisExecProvider.get(); 115 | } catch (Exception e) { 116 | throw new RedisBuildingException("Could not build sentinel instance", e); 117 | } 118 | } 119 | 120 | public void reset() { 121 | this.redisConfigBuilder = null; 122 | this.sentinelConf = null; 123 | } 124 | 125 | public void addDefaultReplicationGroup() { 126 | setting(String.format(MASTER_MONITOR_LINE, masterName, masterPort, quorumSize)); 127 | setting(String.format(DOWN_AFTER_LINE, masterName, downAfterMilliseconds)); 128 | setting(String.format(FAILOVER_LINE, masterName, failoverTimeout)); 129 | setting(String.format(PARALLEL_SYNCS_LINE, masterName, parallelSyncs)); 130 | } 131 | 132 | private void resolveSentinelConf() throws IOException { 133 | if (redisConfigBuilder == null) { 134 | addDefaultReplicationGroup(); 135 | } 136 | setting("bind "+bind); 137 | setting(String.format(PORT_LINE, port)); 138 | final String configString = redisConfigBuilder.toString(); 139 | 140 | File redisConfigFile = File.createTempFile(resolveConfigName(), ".conf"); 141 | redisConfigFile.deleteOnExit(); 142 | Files.write(configString, redisConfigFile, Charset.forName("UTF-8")); 143 | sentinelConf = redisConfigFile.getAbsolutePath(); 144 | } 145 | 146 | private String resolveConfigName() { 147 | return CONF_FILENAME + "_" + port; 148 | } 149 | 150 | private List buildCommandArgs() { 151 | Preconditions.checkNotNull(sentinelConf); 152 | 153 | List args = new ArrayList(); 154 | args.add(executable.getAbsolutePath()); 155 | args.add(sentinelConf); 156 | args.add("--sentinel"); 157 | 158 | if (port != null) { 159 | args.add("--port"); 160 | args.add(Integer.toString(port)); 161 | } 162 | 163 | return args; 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /src/main/java/redis/embedded/RedisServer.java: -------------------------------------------------------------------------------- 1 | package redis.embedded; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.util.ArrayList; 6 | import java.util.Arrays; 7 | import java.util.List; 8 | 9 | public class RedisServer extends AbstractRedisInstance { 10 | private static final String REDIS_READY_PATTERN = ".*(R|r)eady to accept connections.*"; 11 | private static final int DEFAULT_REDIS_PORT = 6379; 12 | 13 | public RedisServer() { 14 | this(DEFAULT_REDIS_PORT); 15 | } 16 | 17 | public RedisServer(int port) { 18 | super(port); 19 | this.args = builder().port(port).build().args; 20 | } 21 | 22 | public RedisServer(File executable, int port) { 23 | super(port); 24 | this.args = Arrays.asList( 25 | executable.getAbsolutePath(), 26 | "--port", Integer.toString(port) 27 | ); 28 | } 29 | 30 | public RedisServer(RedisExecProvider redisExecProvider, int port) throws IOException { 31 | super(port); 32 | this.args = Arrays.asList( 33 | redisExecProvider.get().getAbsolutePath(), 34 | "--port", Integer.toString(port) 35 | ); 36 | } 37 | 38 | RedisServer(List args, int port) { 39 | super(port); 40 | this.args = new ArrayList(args); 41 | } 42 | 43 | public static RedisServerBuilder builder() { 44 | return new RedisServerBuilder(); 45 | } 46 | 47 | @Override 48 | protected String redisReadyPattern() { 49 | return REDIS_READY_PATTERN; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/redis/embedded/RedisServerBuilder.java: -------------------------------------------------------------------------------- 1 | package redis.embedded; 2 | 3 | import com.google.common.base.Strings; 4 | import com.google.common.io.Files; 5 | import redis.embedded.exceptions.RedisBuildingException; 6 | 7 | import java.io.File; 8 | import java.io.IOException; 9 | import java.net.InetSocketAddress; 10 | import java.nio.charset.Charset; 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | 14 | public class RedisServerBuilder { 15 | private static final String LINE_SEPARATOR = System.getProperty("line.separator"); 16 | private static final String CONF_FILENAME = "embedded-redis-server"; 17 | 18 | private File executable; 19 | private RedisExecProvider redisExecProvider = RedisExecProvider.defaultProvider(); 20 | private String bind="127.0.0.1"; 21 | private int port = 6379; 22 | private InetSocketAddress slaveOf; 23 | private String redisConf; 24 | 25 | private StringBuilder redisConfigBuilder; 26 | 27 | public RedisServerBuilder redisExecProvider(RedisExecProvider redisExecProvider) { 28 | this.redisExecProvider = redisExecProvider; 29 | return this; 30 | } 31 | 32 | public RedisServerBuilder bind(String bind) { 33 | this.bind = bind; 34 | return this; 35 | } 36 | 37 | public RedisServerBuilder port(int port) { 38 | this.port = port; 39 | return this; 40 | } 41 | 42 | public RedisServerBuilder slaveOf(String hostname, int port) { 43 | this.slaveOf = new InetSocketAddress(hostname, port); 44 | return this; 45 | } 46 | 47 | public RedisServerBuilder slaveOf(InetSocketAddress slaveOf) { 48 | this.slaveOf = slaveOf; 49 | return this; 50 | } 51 | 52 | public RedisServerBuilder configFile(String redisConf) { 53 | if (redisConfigBuilder != null) { 54 | throw new RedisBuildingException("Redis configuration is already partially build using setting(String) method!"); 55 | } 56 | this.redisConf = redisConf; 57 | return this; 58 | } 59 | 60 | public RedisServerBuilder setting(String configLine) { 61 | if (redisConf != null) { 62 | throw new RedisBuildingException("Redis configuration is already set using redis conf file!"); 63 | } 64 | 65 | if (redisConfigBuilder == null) { 66 | redisConfigBuilder = new StringBuilder(); 67 | } 68 | 69 | redisConfigBuilder.append(configLine); 70 | redisConfigBuilder.append(LINE_SEPARATOR); 71 | return this; 72 | } 73 | 74 | public RedisServer build() { 75 | setting("bind "+bind); 76 | tryResolveConfAndExec(); 77 | List args = buildCommandArgs(); 78 | return new RedisServer(args, port); 79 | } 80 | 81 | public void reset() { 82 | this.executable = null; 83 | this.redisConfigBuilder = null; 84 | this.slaveOf = null; 85 | this.redisConf = null; 86 | } 87 | 88 | private void tryResolveConfAndExec() { 89 | try { 90 | resolveConfAndExec(); 91 | } catch (IOException e) { 92 | throw new RedisBuildingException("Could not build server instance", e); 93 | } 94 | } 95 | 96 | private void resolveConfAndExec() throws IOException { 97 | if (redisConf == null && redisConfigBuilder != null) { 98 | File redisConfigFile = File.createTempFile(resolveConfigName(), ".conf"); 99 | redisConfigFile.deleteOnExit(); 100 | Files.write(redisConfigBuilder.toString(), redisConfigFile, Charset.forName("UTF-8")); 101 | redisConf = redisConfigFile.getAbsolutePath(); 102 | } 103 | 104 | try { 105 | executable = redisExecProvider.get(); 106 | } catch (Exception e) { 107 | throw new RedisBuildingException("Failed to resolve executable", e); 108 | } 109 | } 110 | 111 | private String resolveConfigName() { 112 | return CONF_FILENAME + "_" + port; 113 | } 114 | 115 | private List buildCommandArgs() { 116 | List args = new ArrayList(); 117 | args.add(executable.getAbsolutePath()); 118 | 119 | if (!Strings.isNullOrEmpty(redisConf)) { 120 | args.add(redisConf); 121 | } 122 | 123 | args.add("--port"); 124 | args.add(Integer.toString(port)); 125 | 126 | if (slaveOf != null) { 127 | args.add("--slaveof"); 128 | args.add(slaveOf.getHostName()); 129 | args.add(Integer.toString(slaveOf.getPort())); 130 | } 131 | 132 | return args; 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/main/java/redis/embedded/exceptions/EmbeddedRedisException.java: -------------------------------------------------------------------------------- 1 | package redis.embedded.exceptions; 2 | 3 | public class EmbeddedRedisException extends RuntimeException { 4 | public EmbeddedRedisException(String message, Throwable cause) { 5 | super(message, cause); 6 | } 7 | 8 | public EmbeddedRedisException(String message) { 9 | super(message); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/redis/embedded/exceptions/OsDetectionException.java: -------------------------------------------------------------------------------- 1 | package redis.embedded.exceptions; 2 | 3 | public class OsDetectionException extends RuntimeException { 4 | public OsDetectionException(String message) { 5 | super(message); 6 | } 7 | 8 | public OsDetectionException(Throwable cause) { 9 | super(cause); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/redis/embedded/exceptions/RedisBuildingException.java: -------------------------------------------------------------------------------- 1 | package redis.embedded.exceptions; 2 | 3 | public class RedisBuildingException extends RuntimeException { 4 | public RedisBuildingException(String message, Throwable cause) { 5 | super(message, cause); 6 | } 7 | 8 | public RedisBuildingException(String message) { 9 | super(message); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/redis/embedded/ports/EphemeralPortProvider.java: -------------------------------------------------------------------------------- 1 | package redis.embedded.ports; 2 | 3 | import redis.embedded.PortProvider; 4 | import redis.embedded.exceptions.RedisBuildingException; 5 | 6 | import java.io.IOException; 7 | import java.net.ServerSocket; 8 | 9 | public class EphemeralPortProvider implements PortProvider { 10 | @Override 11 | public int next() { 12 | try { 13 | final ServerSocket socket = new ServerSocket(0); 14 | socket.setReuseAddress(false); 15 | int port = socket.getLocalPort(); 16 | socket.close(); 17 | return port; 18 | } catch (IOException e) { 19 | //should not ever happen 20 | throw new RedisBuildingException("Could not provide ephemeral port", e); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/redis/embedded/ports/PredefinedPortProvider.java: -------------------------------------------------------------------------------- 1 | package redis.embedded.ports; 2 | 3 | import redis.embedded.PortProvider; 4 | import redis.embedded.exceptions.RedisBuildingException; 5 | 6 | import java.util.Collection; 7 | import java.util.Iterator; 8 | import java.util.LinkedList; 9 | import java.util.List; 10 | 11 | public class PredefinedPortProvider implements PortProvider { 12 | private final List ports = new LinkedList(); 13 | private final Iterator current; 14 | 15 | public PredefinedPortProvider(Collection ports) { 16 | this.ports.addAll(ports); 17 | this.current = this.ports.iterator(); 18 | } 19 | 20 | @Override 21 | public synchronized int next() { 22 | if (!current.hasNext()) { 23 | throw new RedisBuildingException("Run out of Redis ports!"); 24 | } 25 | return current.next(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/redis/embedded/ports/SequencePortProvider.java: -------------------------------------------------------------------------------- 1 | package redis.embedded.ports; 2 | 3 | import redis.embedded.PortProvider; 4 | 5 | import java.util.concurrent.atomic.AtomicInteger; 6 | 7 | public class SequencePortProvider implements PortProvider { 8 | private AtomicInteger currentPort = new AtomicInteger(26379); 9 | 10 | public SequencePortProvider() { 11 | } 12 | 13 | public SequencePortProvider(int currentPort) { 14 | this.currentPort.set(currentPort); 15 | } 16 | 17 | public void setCurrentPort(int port) { 18 | currentPort.set(port); 19 | } 20 | 21 | @Override 22 | public int next() { 23 | return currentPort.getAndIncrement(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/redis/embedded/util/Architecture.java: -------------------------------------------------------------------------------- 1 | package redis.embedded.util; 2 | 3 | public enum Architecture { 4 | x86, 5 | x86_64 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/redis/embedded/util/JarUtil.java: -------------------------------------------------------------------------------- 1 | package redis.embedded.util; 2 | 3 | import com.google.common.io.Files; 4 | import com.google.common.io.Resources; 5 | import org.apache.commons.io.FileUtils; 6 | 7 | import java.io.File; 8 | import java.io.IOException; 9 | 10 | public class JarUtil { 11 | 12 | public static File extractExecutableFromJar(String executable) throws IOException { 13 | File tmpDir = Files.createTempDir(); 14 | tmpDir.deleteOnExit(); 15 | 16 | File command = new File(tmpDir, executable); 17 | FileUtils.copyURLToFile(Resources.getResource(executable), command); 18 | command.deleteOnExit(); 19 | command.setExecutable(true); 20 | 21 | return command; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/redis/embedded/util/JedisUtil.java: -------------------------------------------------------------------------------- 1 | package redis.embedded.util; 2 | 3 | import redis.embedded.Redis; 4 | import redis.embedded.RedisCluster; 5 | 6 | import java.util.HashSet; 7 | import java.util.List; 8 | import java.util.Set; 9 | 10 | public class JedisUtil { 11 | public static Set jedisHosts(Redis redis) { 12 | final List ports = redis.ports(); 13 | return portsToJedisHosts(ports); 14 | } 15 | 16 | public static Set sentinelHosts(RedisCluster cluster) { 17 | final List ports = cluster.sentinelPorts(); 18 | return portsToJedisHosts(ports); 19 | } 20 | 21 | public static Set portsToJedisHosts(List ports) { 22 | Set hosts = new HashSet(); 23 | for(Integer p : ports) { 24 | hosts.add("localhost:" + p); 25 | } 26 | return hosts; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/redis/embedded/util/OS.java: -------------------------------------------------------------------------------- 1 | package redis.embedded.util; 2 | 3 | public enum OS { 4 | WINDOWS, 5 | UNIX, 6 | MAC_OS_X 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/redis/embedded/util/OSDetector.java: -------------------------------------------------------------------------------- 1 | package redis.embedded.util; 2 | 3 | import redis.embedded.exceptions.OsDetectionException; 4 | 5 | import java.io.BufferedReader; 6 | import java.io.InputStreamReader; 7 | 8 | public class OSDetector { 9 | 10 | public static OS getOS() { 11 | String osName = System.getProperty("os.name").toLowerCase(); 12 | 13 | if (osName.contains("win")) { 14 | return OS.WINDOWS; 15 | } else if (osName.contains("nix") || osName.contains("nux") || osName.contains("aix")) { 16 | return OS.UNIX; 17 | } else if ("Mac OS X".equalsIgnoreCase(osName)) { 18 | return OS.MAC_OS_X; 19 | } else { 20 | throw new OsDetectionException("Unrecognized OS: " + osName); 21 | } 22 | } 23 | 24 | public static Architecture getArchitecture() { 25 | OS os = getOS(); 26 | switch (os) { 27 | case WINDOWS: 28 | return getWindowsArchitecture(); 29 | case UNIX: 30 | return getUnixArchitecture(); 31 | case MAC_OS_X: 32 | return getMacOSXArchitecture(); 33 | default: 34 | throw new OsDetectionException("Unrecognized OS: " + os); 35 | } 36 | } 37 | 38 | private static Architecture getWindowsArchitecture() { 39 | String arch = System.getenv("PROCESSOR_ARCHITECTURE"); 40 | String wow64Arch = System.getenv("PROCESSOR_ARCHITEW6432"); 41 | 42 | if (arch.endsWith("64") || wow64Arch != null && wow64Arch.endsWith("64")) { 43 | return Architecture.x86_64; 44 | } else { 45 | return Architecture.x86; 46 | } 47 | } 48 | 49 | private static Architecture getUnixArchitecture() { 50 | BufferedReader input = null; 51 | try { 52 | String line; 53 | Process proc = Runtime.getRuntime().exec("uname -m"); 54 | input = new BufferedReader(new InputStreamReader(proc.getInputStream())); 55 | while ((line = input.readLine()) != null) { 56 | if (line.length() > 0) { 57 | if (line.contains("64")) { 58 | return Architecture.x86_64; 59 | } 60 | } 61 | } 62 | } catch (Exception e) { 63 | throw new OsDetectionException(e); 64 | } finally { 65 | try { 66 | if (input != null) { 67 | input.close(); 68 | } 69 | } catch (Exception ignored) { 70 | // ignore 71 | } 72 | } 73 | 74 | return Architecture.x86; 75 | } 76 | 77 | private static Architecture getMacOSXArchitecture() { 78 | BufferedReader input = null; 79 | try { 80 | String line; 81 | Process proc = Runtime.getRuntime().exec("sysctl hw"); 82 | input = new BufferedReader(new InputStreamReader(proc.getInputStream())); 83 | while ((line = input.readLine()) != null) { 84 | if (line.length() > 0) { 85 | if ((line.contains("cpu64bit_capable")) && (line.trim().endsWith("1"))) { 86 | return Architecture.x86_64; 87 | } 88 | } 89 | } 90 | } catch (Exception e) { 91 | throw new OsDetectionException(e); 92 | } finally { 93 | try { 94 | if (input != null) { 95 | input.close(); 96 | } 97 | } catch (Exception ignored) { 98 | // ignore 99 | } 100 | } 101 | 102 | return Architecture.x86; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/main/java/redis/embedded/util/OsArchitecture.java: -------------------------------------------------------------------------------- 1 | package redis.embedded.util; 2 | 3 | import com.google.common.base.Preconditions; 4 | 5 | public class OsArchitecture { 6 | 7 | public static final OsArchitecture WINDOWS_x86 = new OsArchitecture(OS.WINDOWS, Architecture.x86); 8 | public static final OsArchitecture WINDOWS_x86_64 = new OsArchitecture(OS.WINDOWS, Architecture.x86_64); 9 | 10 | public static final OsArchitecture UNIX_x86 = new OsArchitecture(OS.UNIX, Architecture.x86); 11 | public static final OsArchitecture UNIX_x86_64 = new OsArchitecture(OS.UNIX, Architecture.x86_64); 12 | 13 | public static final OsArchitecture MAC_OS_X_x86 = new OsArchitecture(OS.MAC_OS_X, Architecture.x86); 14 | public static final OsArchitecture MAC_OS_X_x86_64 = new OsArchitecture(OS.MAC_OS_X, Architecture.x86_64); 15 | 16 | private final OS os; 17 | private final Architecture arch; 18 | 19 | public static OsArchitecture detect() { 20 | OS os = OSDetector.getOS(); 21 | Architecture arch = OSDetector.getArchitecture(); 22 | return new OsArchitecture(os, arch); 23 | } 24 | 25 | public OsArchitecture(OS os, Architecture arch) { 26 | Preconditions.checkNotNull(os); 27 | Preconditions.checkNotNull(arch); 28 | 29 | this.os = os; 30 | this.arch = arch; 31 | } 32 | 33 | public OS os() { 34 | return os; 35 | } 36 | 37 | public Architecture arch() { 38 | return arch; 39 | } 40 | 41 | @Override 42 | public boolean equals(Object o) { 43 | if (this == o) return true; 44 | if (o == null || getClass() != o.getClass()) return false; 45 | 46 | OsArchitecture that = (OsArchitecture) o; 47 | 48 | return arch == that.arch && os == that.os; 49 | 50 | } 51 | 52 | @Override 53 | public int hashCode() { 54 | int result = os.hashCode(); 55 | result = 31 * result + arch.hashCode(); 56 | return result; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/resources/redis-server-2.8.19: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ozimov/embedded-redis/659f4ba43db47782bcd8fd2a52ea4a91c8c7d33c/src/main/resources/redis-server-2.8.19 -------------------------------------------------------------------------------- /src/main/resources/redis-server-2.8.19-32: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ozimov/embedded-redis/659f4ba43db47782bcd8fd2a52ea4a91c8c7d33c/src/main/resources/redis-server-2.8.19-32 -------------------------------------------------------------------------------- /src/main/resources/redis-server-2.8.19.app: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ozimov/embedded-redis/659f4ba43db47782bcd8fd2a52ea4a91c8c7d33c/src/main/resources/redis-server-2.8.19.app -------------------------------------------------------------------------------- /src/main/resources/redis-server-2.8.19.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ozimov/embedded-redis/659f4ba43db47782bcd8fd2a52ea4a91c8c7d33c/src/main/resources/redis-server-2.8.19.exe -------------------------------------------------------------------------------- /src/test/java/redis/embedded/RedisClusterTest.java: -------------------------------------------------------------------------------- 1 | package redis.embedded; 2 | 3 | import com.google.common.collect.Sets; 4 | import org.junit.Before; 5 | import org.junit.Test; 6 | import redis.clients.jedis.Jedis; 7 | import redis.clients.jedis.JedisSentinelPool; 8 | import redis.embedded.util.JedisUtil; 9 | 10 | import java.util.Arrays; 11 | import java.util.List; 12 | import java.util.Set; 13 | 14 | import static org.junit.Assert.assertEquals; 15 | import static org.mockito.BDDMockito.given; 16 | import static org.mockito.Mockito.mock; 17 | import static org.mockito.Mockito.verify; 18 | 19 | public class RedisClusterTest { 20 | private Redis sentinel1; 21 | private Redis sentinel2; 22 | private Redis master1; 23 | private Redis master2; 24 | 25 | private RedisCluster instance; 26 | 27 | @Before 28 | public void setUp() throws Exception { 29 | sentinel1 = mock(Redis.class); 30 | sentinel2 = mock(Redis.class); 31 | master1 = mock(Redis.class); 32 | master2 = mock(Redis.class); 33 | } 34 | 35 | 36 | @Test 37 | public void stopShouldStopEntireCluster() throws Exception { 38 | //given 39 | final List sentinels = Arrays.asList(sentinel1, sentinel2); 40 | final List servers = Arrays.asList(master1, master2); 41 | instance = new RedisCluster(sentinels, servers); 42 | 43 | //when 44 | instance.stop(); 45 | 46 | //then 47 | for(Redis s : sentinels) { 48 | verify(s).stop(); 49 | } 50 | for(Redis s : servers) { 51 | verify(s).stop(); 52 | } 53 | } 54 | 55 | @Test 56 | public void startShouldStartEntireCluster() throws Exception { 57 | //given 58 | final List sentinels = Arrays.asList(sentinel1, sentinel2); 59 | final List servers = Arrays.asList(master1, master2); 60 | instance = new RedisCluster(sentinels, servers); 61 | 62 | //when 63 | instance.start(); 64 | 65 | //then 66 | for(Redis s : sentinels) { 67 | verify(s).start(); 68 | } 69 | for(Redis s : servers) { 70 | verify(s).start(); 71 | } 72 | } 73 | 74 | @Test 75 | public void isActiveShouldCheckEntireClusterIfAllActive() throws Exception { 76 | //given 77 | given(sentinel1.isActive()).willReturn(true); 78 | given(sentinel2.isActive()).willReturn(true); 79 | given(master1.isActive()).willReturn(true); 80 | given(master2.isActive()).willReturn(true); 81 | final List sentinels = Arrays.asList(sentinel1, sentinel2); 82 | final List servers = Arrays.asList(master1, master2); 83 | instance = new RedisCluster(sentinels, servers); 84 | 85 | //when 86 | instance.isActive(); 87 | 88 | //then 89 | for(Redis s : sentinels) { 90 | verify(s).isActive(); 91 | } 92 | for(Redis s : servers) { 93 | verify(s).isActive(); 94 | } 95 | } 96 | 97 | @Test 98 | public void testSimpleOperationsAfterRunWithSingleMasterNoSlavesCluster() throws Exception { 99 | //given 100 | final RedisCluster cluster = RedisCluster.builder().sentinelCount(1).replicationGroup("ourmaster", 0).build(); 101 | cluster.start(); 102 | 103 | //when 104 | JedisSentinelPool pool = null; 105 | Jedis jedis = null; 106 | try { 107 | pool = new JedisSentinelPool("ourmaster", Sets.newHashSet("localhost:26379")); 108 | jedis = testPool(pool); 109 | } finally { 110 | if (jedis != null) 111 | pool.returnResource(jedis); 112 | cluster.stop(); 113 | } 114 | } 115 | 116 | @Test 117 | public void testSimpleOperationsAfterRunWithSingleMasterAndOneSlave() throws Exception { 118 | //given 119 | final RedisCluster cluster = RedisCluster.builder().sentinelCount(1).replicationGroup("ourmaster", 1).build(); 120 | cluster.start(); 121 | 122 | //when 123 | JedisSentinelPool pool = null; 124 | Jedis jedis = null; 125 | try { 126 | pool = new JedisSentinelPool("ourmaster", Sets.newHashSet("localhost:26379")); 127 | jedis = testPool(pool); 128 | } finally { 129 | if (jedis != null) 130 | pool.returnResource(jedis); 131 | cluster.stop(); 132 | } 133 | } 134 | 135 | @Test 136 | public void testSimpleOperationsAfterRunWithSingleMasterMultipleSlaves() throws Exception { 137 | //given 138 | final RedisCluster cluster = RedisCluster.builder().sentinelCount(1).replicationGroup("ourmaster", 2).build(); 139 | cluster.start(); 140 | 141 | //when 142 | JedisSentinelPool pool = null; 143 | Jedis jedis = null; 144 | try { 145 | pool = new JedisSentinelPool("ourmaster", Sets.newHashSet("localhost:26379")); 146 | jedis = testPool(pool); 147 | } finally { 148 | if (jedis != null) 149 | pool.returnResource(jedis); 150 | cluster.stop(); 151 | } 152 | } 153 | 154 | @Test 155 | public void testSimpleOperationsAfterRunWithTwoSentinelsSingleMasterMultipleSlaves() throws Exception { 156 | //given 157 | final RedisCluster cluster = RedisCluster.builder().sentinelCount(2).replicationGroup("ourmaster", 2).build(); 158 | cluster.start(); 159 | 160 | //when 161 | JedisSentinelPool pool = null; 162 | Jedis jedis = null; 163 | try { 164 | pool = new JedisSentinelPool("ourmaster", Sets.newHashSet("localhost:26379", "localhost:26380")); 165 | jedis = testPool(pool); 166 | } finally { 167 | if (jedis != null) 168 | pool.returnResource(jedis); 169 | cluster.stop(); 170 | } 171 | } 172 | 173 | @Test 174 | public void testSimpleOperationsAfterRunWithTwoPredefinedSentinelsSingleMasterMultipleSlaves() throws Exception { 175 | //given 176 | List sentinelPorts = Arrays.asList(26381, 26382); 177 | final RedisCluster cluster = RedisCluster.builder().sentinelPorts(sentinelPorts).replicationGroup("ourmaster", 2).build(); 178 | cluster.start(); 179 | final Set sentinelHosts = JedisUtil.portsToJedisHosts(sentinelPorts); 180 | 181 | //when 182 | JedisSentinelPool pool = null; 183 | Jedis jedis = null; 184 | try { 185 | pool = new JedisSentinelPool("ourmaster", sentinelHosts); 186 | jedis = testPool(pool); 187 | } finally { 188 | if (jedis != null) 189 | pool.returnResource(jedis); 190 | cluster.stop(); 191 | } 192 | } 193 | 194 | @Test 195 | public void testSimpleOperationsAfterRunWithThreeSentinelsThreeMastersOneSlavePerMasterCluster() throws Exception { 196 | //given 197 | final String master1 = "master1"; 198 | final String master2 = "master2"; 199 | final String master3 = "master3"; 200 | final RedisCluster cluster = RedisCluster.builder().sentinelCount(3).quorumSize(2) 201 | .replicationGroup(master1, 1) 202 | .replicationGroup(master2, 1) 203 | .replicationGroup(master3, 1) 204 | .build(); 205 | cluster.start(); 206 | 207 | //when 208 | JedisSentinelPool pool1 = null; 209 | JedisSentinelPool pool2 = null; 210 | JedisSentinelPool pool3 = null; 211 | Jedis jedis1 = null; 212 | Jedis jedis2 = null; 213 | Jedis jedis3 = null; 214 | try { 215 | pool1 = new JedisSentinelPool(master1, Sets.newHashSet("localhost:26379", "localhost:26380", "localhost:26381")); 216 | pool2 = new JedisSentinelPool(master2, Sets.newHashSet("localhost:26379", "localhost:26380", "localhost:26381")); 217 | pool3 = new JedisSentinelPool(master3, Sets.newHashSet("localhost:26379", "localhost:26380", "localhost:26381")); 218 | jedis1 = testPool(pool1); 219 | jedis2 = testPool(pool2); 220 | jedis3 = testPool(pool3); 221 | } finally { 222 | if (jedis1 != null) 223 | pool1.returnResource(jedis1); 224 | if (jedis2 != null) 225 | pool2.returnResource(jedis2); 226 | if (jedis3 != null) 227 | pool3.returnResource(jedis3); 228 | cluster.stop(); 229 | } 230 | } 231 | 232 | @Test 233 | public void testSimpleOperationsAfterRunWithThreeSentinelsThreeMastersOneSlavePerMasterEphemeralCluster() throws Exception { 234 | //given 235 | final String master1 = "master1"; 236 | final String master2 = "master2"; 237 | final String master3 = "master3"; 238 | final RedisCluster cluster = RedisCluster.builder().ephemeral().sentinelCount(3).quorumSize(2) 239 | .replicationGroup(master1, 1) 240 | .replicationGroup(master2, 1) 241 | .replicationGroup(master3, 1) 242 | .build(); 243 | cluster.start(); 244 | final Set sentinelHosts = JedisUtil.sentinelHosts(cluster); 245 | 246 | //when 247 | JedisSentinelPool pool1 = null; 248 | JedisSentinelPool pool2 = null; 249 | JedisSentinelPool pool3 = null; 250 | Jedis jedis1 = null; 251 | Jedis jedis2 = null; 252 | Jedis jedis3 = null; 253 | try { 254 | pool1 = new JedisSentinelPool(master1, sentinelHosts); 255 | pool2 = new JedisSentinelPool(master2, sentinelHosts); 256 | pool3 = new JedisSentinelPool(master3, sentinelHosts); 257 | jedis1 = testPool(pool1); 258 | jedis2 = testPool(pool2); 259 | jedis3 = testPool(pool3); 260 | } finally { 261 | if (jedis1 != null) 262 | pool1.returnResource(jedis1); 263 | if (jedis2 != null) 264 | pool2.returnResource(jedis2); 265 | if (jedis3 != null) 266 | pool3.returnResource(jedis3); 267 | cluster.stop(); 268 | } 269 | } 270 | 271 | private Jedis testPool(JedisSentinelPool pool) { 272 | Jedis jedis; 273 | jedis = pool.getResource(); 274 | jedis.mset("abc", "1", "def", "2"); 275 | 276 | //then 277 | assertEquals("1", jedis.mget("abc").get(0)); 278 | assertEquals("2", jedis.mget("def").get(0)); 279 | assertEquals(null, jedis.mget("xyz").get(0)); 280 | return jedis; 281 | } 282 | } -------------------------------------------------------------------------------- /src/test/java/redis/embedded/RedisSentinelTest.java: -------------------------------------------------------------------------------- 1 | package redis.embedded; 2 | 3 | import com.google.common.collect.Sets; 4 | import org.junit.Test; 5 | import redis.clients.jedis.Jedis; 6 | import redis.clients.jedis.JedisSentinelPool; 7 | 8 | import java.io.BufferedReader; 9 | import java.io.IOException; 10 | import java.io.InputStreamReader; 11 | import java.util.concurrent.TimeUnit; 12 | 13 | import static org.junit.Assert.*; 14 | 15 | public class RedisSentinelTest { 16 | private RedisSentinel sentinel; 17 | private RedisServer server; 18 | 19 | @Test(timeout = 3000L) 20 | public void testSimpleRun() throws Exception { 21 | server = new RedisServer(); 22 | sentinel = RedisSentinel.builder().build(); 23 | sentinel.start(); 24 | server.start(); 25 | TimeUnit.SECONDS.sleep(1); 26 | server.stop(); 27 | sentinel.stop(); 28 | } 29 | 30 | @Test 31 | public void shouldAllowSubsequentRuns() throws Exception { 32 | sentinel = RedisSentinel.builder().build(); 33 | sentinel.start(); 34 | sentinel.stop(); 35 | 36 | sentinel.start(); 37 | sentinel.stop(); 38 | 39 | sentinel.start(); 40 | sentinel.stop(); 41 | } 42 | 43 | @Test 44 | public void testSimpleOperationsAfterRun() throws Exception { 45 | //given 46 | server = new RedisServer(); 47 | sentinel = RedisSentinel.builder().build(); 48 | server.start(); 49 | sentinel.start(); 50 | TimeUnit.SECONDS.sleep(1); 51 | 52 | //when 53 | JedisSentinelPool pool = null; 54 | Jedis jedis = null; 55 | try { 56 | pool = new JedisSentinelPool("mymaster", Sets.newHashSet("localhost:26379")); 57 | jedis = pool.getResource(); 58 | jedis.mset("abc", "1", "def", "2"); 59 | 60 | //then 61 | assertEquals("1", jedis.mget("abc").get(0)); 62 | assertEquals("2", jedis.mget("def").get(0)); 63 | assertNull(jedis.mget("xyz").get(0)); 64 | } finally { 65 | if (jedis != null) 66 | pool.returnResource(jedis); 67 | sentinel.stop(); 68 | server.stop(); 69 | } 70 | } 71 | 72 | @Test 73 | public void testAwaitRedisSentinelReady() throws Exception { 74 | String readyPattern = RedisSentinel.builder().build().redisReadyPattern(); 75 | 76 | assertReadyPattern(new BufferedReader( 77 | new InputStreamReader(getClass() 78 | .getClassLoader() 79 | .getResourceAsStream("redis-2.x-sentinel-startup-output.txt"))), 80 | readyPattern); 81 | 82 | assertReadyPattern(new BufferedReader( 83 | new InputStreamReader(getClass() 84 | .getClassLoader() 85 | .getResourceAsStream("redis-3.x-sentinel-startup-output.txt"))), 86 | readyPattern); 87 | 88 | assertReadyPattern(new BufferedReader( 89 | new InputStreamReader(getClass() 90 | .getClassLoader() 91 | .getResourceAsStream("redis-4.x-sentinel-startup-output.txt"))), 92 | readyPattern); 93 | } 94 | 95 | private void assertReadyPattern(BufferedReader reader, String readyPattern) throws IOException { 96 | String outputLine; 97 | do { 98 | outputLine = reader.readLine(); 99 | assertNotNull(outputLine); 100 | } while (!outputLine.matches(readyPattern)); 101 | } 102 | } -------------------------------------------------------------------------------- /src/test/java/redis/embedded/RedisServerClusterTest.java: -------------------------------------------------------------------------------- 1 | package redis.embedded; 2 | 3 | import org.junit.After; 4 | import org.junit.Before; 5 | import org.junit.Test; 6 | import redis.clients.jedis.Jedis; 7 | import redis.clients.jedis.JedisPool; 8 | 9 | import static org.junit.Assert.assertEquals; 10 | 11 | public class RedisServerClusterTest { 12 | 13 | private RedisServer redisServer1; 14 | private RedisServer redisServer2; 15 | 16 | @Before 17 | public void setUp() throws Exception { 18 | redisServer1 = RedisServer.builder() 19 | .port(6300) 20 | .build(); 21 | 22 | redisServer2 = RedisServer.builder() 23 | .port(6301) 24 | .slaveOf("localhost", 6300) 25 | .build(); 26 | 27 | redisServer1.start(); 28 | redisServer2.start(); 29 | } 30 | 31 | @Test 32 | public void testSimpleOperationsAfterRun() throws Exception { 33 | JedisPool pool = null; 34 | Jedis jedis = null; 35 | try { 36 | pool = new JedisPool("localhost", 6300); 37 | jedis = pool.getResource(); 38 | jedis.mset("abc", "1", "def", "2"); 39 | 40 | assertEquals("1", jedis.mget("abc").get(0)); 41 | assertEquals("2", jedis.mget("def").get(0)); 42 | assertEquals(null, jedis.mget("xyz").get(0)); 43 | } finally { 44 | if (jedis != null) 45 | pool.returnResource(jedis); 46 | } 47 | } 48 | 49 | 50 | @After 51 | public void tearDown() throws Exception { 52 | redisServer1.stop(); 53 | redisServer2.stop(); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/test/java/redis/embedded/RedisServerTest.java: -------------------------------------------------------------------------------- 1 | package redis.embedded; 2 | 3 | import com.google.common.io.Resources; 4 | import org.junit.Test; 5 | import redis.clients.jedis.Jedis; 6 | import redis.clients.jedis.JedisPool; 7 | import redis.embedded.exceptions.RedisBuildingException; 8 | import redis.embedded.util.Architecture; 9 | import redis.embedded.util.OS; 10 | 11 | import java.io.BufferedReader; 12 | import java.io.IOException; 13 | import java.io.InputStreamReader; 14 | 15 | import static org.junit.Assert.*; 16 | 17 | public class RedisServerTest { 18 | 19 | private RedisServer redisServer; 20 | 21 | @Test(timeout = 1500L) 22 | public void testSimpleRun() throws Exception { 23 | redisServer = new RedisServer(6379); 24 | redisServer.start(); 25 | Thread.sleep(1000L); 26 | redisServer.stop(); 27 | } 28 | 29 | @Test(expected = RuntimeException.class) 30 | public void shouldNotAllowMultipleRunsWithoutStop() throws Exception { 31 | try { 32 | redisServer = new RedisServer(6379); 33 | redisServer.start(); 34 | redisServer.start(); 35 | } finally { 36 | redisServer.stop(); 37 | } 38 | } 39 | 40 | @Test 41 | public void shouldAllowSubsequentRuns() throws Exception { 42 | redisServer = new RedisServer(6379); 43 | redisServer.start(); 44 | redisServer.stop(); 45 | 46 | redisServer.start(); 47 | redisServer.stop(); 48 | 49 | redisServer.start(); 50 | redisServer.stop(); 51 | } 52 | 53 | @Test 54 | public void testSimpleOperationsAfterRun() throws Exception { 55 | redisServer = new RedisServer(6379); 56 | redisServer.start(); 57 | 58 | JedisPool pool = null; 59 | Jedis jedis = null; 60 | try { 61 | pool = new JedisPool("localhost", 6379); 62 | jedis = pool.getResource(); 63 | jedis.mset("abc", "1", "def", "2"); 64 | 65 | assertEquals("1", jedis.mget("abc").get(0)); 66 | assertEquals("2", jedis.mget("def").get(0)); 67 | assertNull(jedis.mget("xyz").get(0)); 68 | } finally { 69 | if (jedis != null) 70 | pool.returnResource(jedis); 71 | redisServer.stop(); 72 | } 73 | } 74 | 75 | @Test 76 | public void shouldIndicateInactiveBeforeStart() throws Exception { 77 | redisServer = new RedisServer(6379); 78 | assertFalse(redisServer.isActive()); 79 | } 80 | 81 | @Test 82 | public void shouldIndicateActiveAfterStart() throws Exception { 83 | redisServer = new RedisServer(6379); 84 | redisServer.start(); 85 | assertTrue(redisServer.isActive()); 86 | redisServer.stop(); 87 | } 88 | 89 | @Test 90 | public void shouldIndicateInactiveAfterStop() throws Exception { 91 | redisServer = new RedisServer(6379); 92 | redisServer.start(); 93 | redisServer.stop(); 94 | assertFalse(redisServer.isActive()); 95 | } 96 | 97 | @Test 98 | public void shouldOverrideDefaultExecutable() throws Exception { 99 | RedisExecProvider customProvider = RedisExecProvider.defaultProvider() 100 | .override(OS.UNIX, Architecture.x86, Resources.getResource("redis-server-2.8.19-32").getFile()) 101 | .override(OS.UNIX, Architecture.x86_64, Resources.getResource("redis-server-2.8.19").getFile()) 102 | .override(OS.WINDOWS, Architecture.x86, Resources.getResource("redis-server-2.8.19.exe").getFile()) 103 | .override(OS.WINDOWS, Architecture.x86_64, Resources.getResource("redis-server-2.8.19.exe").getFile()) 104 | .override(OS.MAC_OS_X, Resources.getResource("redis-server-2.8.19").getFile()); 105 | 106 | redisServer = new RedisServerBuilder() 107 | .redisExecProvider(customProvider) 108 | .build(); 109 | } 110 | 111 | @Test(expected = RedisBuildingException.class) 112 | public void shouldFailWhenBadExecutableGiven() throws Exception { 113 | RedisExecProvider buggyProvider = RedisExecProvider.defaultProvider() 114 | .override(OS.UNIX, "some") 115 | .override(OS.WINDOWS, Architecture.x86, "some") 116 | .override(OS.WINDOWS, Architecture.x86_64, "some") 117 | .override(OS.MAC_OS_X, "some"); 118 | 119 | redisServer = new RedisServerBuilder() 120 | .redisExecProvider(buggyProvider) 121 | .build(); 122 | } 123 | 124 | @Test 125 | public void testAwaitRedisServerReady() throws Exception { 126 | String readyPattern = RedisServer.builder().build().redisReadyPattern(); 127 | 128 | assertReadyPattern(new BufferedReader( 129 | new InputStreamReader(getClass() 130 | .getClassLoader() 131 | .getResourceAsStream("redis-2.x-standalone-startup-output.txt"))), 132 | readyPattern); 133 | 134 | assertReadyPattern(new BufferedReader( 135 | new InputStreamReader(getClass() 136 | .getClassLoader() 137 | .getResourceAsStream("redis-3.x-standalone-startup-output.txt"))), 138 | readyPattern); 139 | 140 | assertReadyPattern(new BufferedReader( 141 | new InputStreamReader(getClass() 142 | .getClassLoader() 143 | .getResourceAsStream("redis-4.x-standalone-startup-output.txt"))), 144 | readyPattern); 145 | } 146 | 147 | private void assertReadyPattern(BufferedReader reader, String readyPattern) throws IOException { 148 | String outputLine; 149 | do { 150 | outputLine = reader.readLine(); 151 | assertNotNull(outputLine); 152 | } while (!outputLine.matches(readyPattern)); 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/test/java/redis/embedded/SpringDataConnectivityTest.java: -------------------------------------------------------------------------------- 1 | package redis.embedded; 2 | 3 | import org.junit.After; 4 | import org.junit.Before; 5 | import org.junit.Test; 6 | import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; 7 | import org.springframework.data.redis.core.RedisTemplate; 8 | import org.springframework.data.redis.core.StringRedisTemplate; 9 | import redis.clients.jedis.JedisShardInfo; 10 | 11 | import static org.junit.Assert.assertEquals; 12 | 13 | public class SpringDataConnectivityTest { 14 | 15 | private RedisServer redisServer; 16 | private RedisTemplate template; 17 | private JedisConnectionFactory connectionFactory; 18 | 19 | @Before 20 | public void setUp() throws Exception { 21 | redisServer = new RedisServer(6379); 22 | redisServer.start(); 23 | 24 | JedisShardInfo shardInfo = new JedisShardInfo("localhost", 6379); 25 | connectionFactory = new JedisConnectionFactory(); 26 | connectionFactory.setShardInfo(shardInfo); 27 | 28 | template = new StringRedisTemplate(); 29 | template.setConnectionFactory(connectionFactory); 30 | template.afterPropertiesSet(); 31 | } 32 | 33 | @Test 34 | public void shouldBeAbleToUseSpringData() throws Exception { 35 | // given 36 | template.opsForValue().set("foo", "bar"); 37 | 38 | // when 39 | String result = template.opsForValue().get("foo"); 40 | 41 | // then 42 | assertEquals("bar", result); 43 | } 44 | 45 | @After 46 | public void tearDown() throws Exception { 47 | redisServer.stop(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/test/java/redis/embedded/ports/EphemeralPortProviderTest.java: -------------------------------------------------------------------------------- 1 | package redis.embedded.ports; 2 | 3 | import org.junit.Test; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | import static org.junit.Assert.assertEquals; 9 | 10 | public class EphemeralPortProviderTest { 11 | 12 | @Test 13 | public void nextShouldGiveNextFreeEphemeralPort() throws Exception { 14 | //given 15 | final int portCount = 20; 16 | final EphemeralPortProvider provider = new EphemeralPortProvider(); 17 | 18 | //when 19 | final List ports = new ArrayList(); 20 | for(int i = 0;i < portCount; i++) { 21 | ports.add(provider.next()); 22 | } 23 | 24 | //then 25 | System.out.println(ports); 26 | assertEquals(20, ports.size()); 27 | } 28 | } -------------------------------------------------------------------------------- /src/test/java/redis/embedded/ports/PredefinedPortProviderTest.java: -------------------------------------------------------------------------------- 1 | package redis.embedded.ports; 2 | 3 | import org.junit.Test; 4 | import redis.embedded.exceptions.RedisBuildingException; 5 | 6 | import java.util.ArrayList; 7 | import java.util.Arrays; 8 | import java.util.Collection; 9 | import java.util.List; 10 | 11 | import static org.junit.Assert.assertEquals; 12 | 13 | public class PredefinedPortProviderTest { 14 | 15 | @Test 16 | public void nextShouldGiveNextPortFromAssignedList() throws Exception { 17 | //given 18 | Collection ports = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); 19 | final PredefinedPortProvider provider = new PredefinedPortProvider(ports); 20 | 21 | //when 22 | final List returnedPorts = new ArrayList(); 23 | for(int i = 0;i < ports.size(); i++) { 24 | returnedPorts.add(provider.next()); 25 | } 26 | 27 | //then 28 | assertEquals(ports, returnedPorts); 29 | } 30 | 31 | @Test(expected = RedisBuildingException.class) 32 | public void nextShouldThrowExceptionWhenRunOutsOfPorts() throws Exception { 33 | //given 34 | Collection ports = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); 35 | final PredefinedPortProvider provider = new PredefinedPortProvider(ports); 36 | 37 | //when 38 | for(int i = 0;i < ports.size(); i++) { 39 | provider.next(); 40 | } 41 | 42 | //then exception should be thrown... 43 | provider.next(); 44 | } 45 | } -------------------------------------------------------------------------------- /src/test/java/redis/embedded/ports/SequencePortProviderTest.java: -------------------------------------------------------------------------------- 1 | package redis.embedded.ports; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.assertEquals; 6 | 7 | public class SequencePortProviderTest { 8 | 9 | @Test 10 | public void nextShouldIncrementPorts() throws Exception { 11 | //given 12 | final int startPort = 10; 13 | final int portCount = 101; 14 | final SequencePortProvider provider = new SequencePortProvider(startPort); 15 | 16 | //when 17 | int max = 0; 18 | for(int i = 0;i max) { 21 | max = port; 22 | } 23 | } 24 | 25 | //then 26 | assertEquals(portCount + startPort - 1, max); 27 | } 28 | } -------------------------------------------------------------------------------- /src/test/resources/redis-2.x-sentinel-startup-output.txt: -------------------------------------------------------------------------------- 1 | _._ 2 | _.-``__ ''-._ 3 | _.-`` `. `_. ''-._ Redis 2.8.19 (00000000/0) 64 bit 4 | .-`` .-```. ```\/ _.,_ ''-._ 5 | ( ' , .-` | `, ) Running in sentinel mode 6 | |`-._`-...-` __...-.``-._|'` _.-'| Port: 26379 7 | | `-._ `._ / _.-' | PID: 5532 8 | `-._ `-._ `-./ _.-' _.-' 9 | |`-._`-._ `-.__.-' _.-'_.-'| 10 | | `-._`-._ _.-'_.-' | http://redis.io 11 | `-._ `-._`-.__.-'_.-' _.-' 12 | |`-._`-._ `-.__.-' _.-'_.-'| 13 | | `-._`-._ _.-'_.-' | 14 | `-._ `-._`-.__.-'_.-' _.-' 15 | `-._ `-.__.-' _.-' 16 | `-._ _.-' 17 | `-.__.-' 18 | 19 | [5532] 18 May 18:23:19.755 # Sentinel runid is 19036a5984e785f22fbf267af283649226736049 -------------------------------------------------------------------------------- /src/test/resources/redis-2.x-standalone-startup-output.txt: -------------------------------------------------------------------------------- 1 | _._ 2 | _.-``__ ''-._ 3 | _.-`` `. `_. ''-._ Redis 2.8.19 (00000000/0) 64 bit 4 | .-`` .-```. ```\/ _.,_ ''-._ 5 | ( ' , .-` | `, ) Running in stand alone mode 6 | |`-._`-...-` __...-.``-._|'` _.-'| Port: 6379 7 | | `-._ `._ / _.-' | PID: 6212 8 | `-._ `-._ `-./ _.-' _.-' 9 | |`-._`-._ `-.__.-' _.-'_.-'| 10 | | `-._`-._ _.-'_.-' | http://redis.io 11 | `-._ `-._`-.__.-'_.-' _.-' 12 | |`-._`-._ `-.__.-' _.-'_.-'| 13 | | `-._`-._ _.-'_.-' | 14 | `-._ `-._`-.__.-'_.-' _.-' 15 | `-._ `-.__.-' _.-' 16 | `-._ _.-' 17 | `-.__.-' 18 | 19 | [6212] 18 May 18:23:17.244 # Server started, Redis version 2.8.19 20 | [6212] 18 May 18:23:17.244 * The server is now ready to accept connections on port 6379 -------------------------------------------------------------------------------- /src/test/resources/redis-3.x-sentinel-startup-output.txt: -------------------------------------------------------------------------------- 1 | _._ 2 | _.-``__ ''-._ 3 | _.-`` `. `_. ''-._ Redis 3.2.100 (00000000/0) 64 bit 4 | .-`` .-```. ```\/ _.,_ ''-._ 5 | ( ' , .-` | `, ) Running in sentinel mode 6 | |`-._`-...-` __...-.``-._|'` _.-'| Port: 6379 7 | | `-._ `._ / _.-' | PID: 8332 8 | `-._ `-._ `-./ _.-' _.-' 9 | |`-._`-._ `-.__.-' _.-'_.-'| 10 | | `-._`-._ _.-'_.-' | http://redis.io 11 | `-._ `-._`-.__.-'_.-' _.-' 12 | |`-._`-._ `-.__.-' _.-'_.-'| 13 | | `-._`-._ _.-'_.-' | 14 | `-._ `-._`-.__.-'_.-' _.-' 15 | `-._ `-.__.-' _.-' 16 | `-._ _.-' 17 | `-.__.-' 18 | 19 | [8332] 18 May 12:24:04.651 # Sentinel ID is 4bb1b2d80c74deb14e116664261c47b5a9c4b185 -------------------------------------------------------------------------------- /src/test/resources/redis-3.x-standalone-startup-output.txt: -------------------------------------------------------------------------------- 1 | _._ 2 | _.-``__ ''-._ 3 | _.-`` `. `_. ''-._ Redis 3.2.100 (00000000/0) 64 bit 4 | .-`` .-```. ```\/ _.,_ ''-._ 5 | ( ' , .-` | `, ) Running in standalone mode 6 | |`-._`-...-` __...-.``-._|'` _.-'| Port: 6379 7 | | `-._ `._ / _.-' | PID: 1592 8 | `-._ `-._ `-./ _.-' _.-' 9 | |`-._`-._ `-.__.-' _.-'_.-'| 10 | | `-._`-._ _.-'_.-' | http://redis.io 11 | `-._ `-._`-.__.-'_.-' _.-' 12 | |`-._`-._ `-.__.-' _.-'_.-'| 13 | | `-._`-._ _.-'_.-' | 14 | `-._ `-._`-.__.-'_.-' _.-' 15 | `-._ `-.__.-' _.-' 16 | `-._ _.-' 17 | `-.__.-' 18 | 19 | [1592] 18 May 12:22:56.535 # Server started, Redis version 3.2.100 20 | [1592] 18 May 12:22:56.535 * The server is now ready to accept connections on port 6379 -------------------------------------------------------------------------------- /src/test/resources/redis-4.x-sentinel-startup-output.txt: -------------------------------------------------------------------------------- 1 | 2091:X 18 May 18:04:58.961 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo 2 | 2091:X 18 May 18:04:58.962 # Redis version=4.0.9, bits=64, commit=00000000, modified=0, pid=2091, just started 3 | 2091:X 18 May 18:04:58.962 # Configuration loaded 4 | 2091:X 18 May 18:04:58.963 # You requested maxclients of 10000 requiring at least 10032 max file descriptors. 5 | 2091:X 18 May 18:04:58.963 # Server can't set maximum open files to 10032 because of OS error: Operation not permitted. 6 | 2091:X 18 May 18:04:58.963 # Current maximum open files is 4096. maxclients has been reduced to 4064 to compensate for low ulimit. If you need higher maxclients increase 'ulimit -n'. 7 | _._ 8 | _.-``__ ''-._ 9 | _.-`` `. `_. ''-._ Redis 4.0.9 (00000000/0) 64 bit 10 | .-`` .-```. ```\/ _.,_ ''-._ 11 | ( ' , .-` | `, ) Running in sentinel mode 12 | |`-._`-...-` __...-.``-._|'` _.-'| Port: 6379 13 | | `-._ `._ / _.-' | PID: 2091 14 | `-._ `-._ `-./ _.-' _.-' 15 | |`-._`-._ `-.__.-' _.-'_.-'| 16 | | `-._`-._ _.-'_.-' | http://redis.io 17 | `-._ `-._`-.__.-'_.-' _.-' 18 | |`-._`-._ `-.__.-' _.-'_.-'| 19 | | `-._`-._ _.-'_.-' | 20 | `-._ `-._`-.__.-'_.-' _.-' 21 | `-._ `-.__.-' _.-' 22 | `-._ _.-' 23 | `-.__.-' 24 | 25 | 2091:X 18 May 18:04:58.964 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128. 26 | 2091:X 18 May 18:04:58.970 # Sentinel ID is 70592a23712d81e2ebc460624a95a0a0fd13a737 -------------------------------------------------------------------------------- /src/test/resources/redis-4.x-standalone-startup-output.txt: -------------------------------------------------------------------------------- 1 | 1780:C 18 May 18:03:03.442 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo 2 | 1780:C 18 May 18:03:03.442 # Redis version=4.0.9, bits=64, commit=00000000, modified=0, pid=1780, just started 3 | 1780:C 18 May 18:03:03.442 # Configuration loaded 4 | 1780:M 18 May 18:03:03.443 # You requested maxclients of 10000 requiring at least 10032 max file descriptors. 5 | 1780:M 18 May 18:03:03.443 # Server can't set maximum open files to 10032 because of OS error: Operation not permitted. 6 | 1780:M 18 May 18:03:03.443 # Current maximum open files is 4096. maxclients has been reduced to 4064 to compensate for low ulimit. If you need higher maxclients increase 'ulimit -n'. 7 | _._ 8 | _.-``__ ''-._ 9 | _.-`` `. `_. ''-._ Redis 4.0.9 (00000000/0) 64 bit 10 | .-`` .-```. ```\/ _.,_ ''-._ 11 | ( ' , .-` | `, ) Running in standalone mode 12 | |`-._`-...-` __...-.``-._|'` _.-'| Port: 6379 13 | | `-._ `._ / _.-' | PID: 1780 14 | `-._ `-._ `-./ _.-' _.-' 15 | |`-._`-._ `-.__.-' _.-'_.-'| 16 | | `-._`-._ _.-'_.-' | http://redis.io 17 | `-._ `-._`-.__.-'_.-' _.-' 18 | |`-._`-._ `-.__.-' _.-'_.-'| 19 | | `-._`-._ _.-'_.-' | 20 | `-._ `-._`-.__.-'_.-' _.-' 21 | `-._ `-.__.-' _.-' 22 | `-._ _.-' 23 | `-.__.-' 24 | 25 | 1780:M 18 May 18:03:03.444 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128. 26 | 1780:M 18 May 18:03:03.445 # Server initialized 27 | 1780:M 18 May 18:03:03.445 # WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect. 28 | 1780:M 18 May 18:03:03.445 # WARNING you have Transparent Huge Pages (THP) support enabled in your kernel. This will create latency and memory usage issues with Redis. To fix this issue run the command 'echo never > /sys/kernel/mm/transparent_hugepage/enabled' as root, and add it to your /etc/rc.local in order to retain the setting after a reboot. Redis must be restarted after THP is disabled. 29 | 1780:M 18 May 18:03:03.445 * Ready to accept connections --------------------------------------------------------------------------------