├── .github
└── workflows
│ └── test.yml
├── .gitignore
├── .travis.yml
├── README.md
├── pom.xml
└── src
├── main
├── docker
│ ├── .gitignore
│ ├── Dockerfile
│ └── build-server-binaries.sh
├── 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-7.0.15-darwin-amd64
│ ├── redis-server-7.0.15-darwin-arm64
│ ├── redis-server-7.0.15-linux-386
│ ├── redis-server-7.0.15-linux-amd64
│ └── redis-server-7.0.15-linux-arm64
└── test
├── bash
├── cleanup-test-certs.sh
├── gen-test-certs-if-needed.sh
└── gen-test-certs.sh
├── java
└── redis
│ └── embedded
│ ├── RedisClusterTest.java
│ ├── RedisSentinelTest.java
│ ├── RedisServerClusterTest.java
│ ├── RedisServerTest.java
│ ├── RedisTlsTest.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
└── redis-6.x-standalone-startup-output.txt
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: Service CI
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | build:
7 | strategy:
8 | matrix:
9 | os:
10 | - ubuntu-latest
11 | - macos-latest
12 | runs-on: ${{ matrix.os }}
13 |
14 | steps:
15 | - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
16 | - name: Set up JDK 11
17 | uses: actions/setup-java@9704b39bf258b59bc04b50fa2dd55e9ed76b47a8 # v4.1.0
18 | with:
19 | java-version: 11
20 | distribution: 'temurin'
21 | cache: 'maven'
22 | - name: Build with Maven
23 | run: mvn -e -B test
24 |
--------------------------------------------------------------------------------
/.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 is a fork of https://github.com/ozimov/embedded-redis, which is in turn a fork of https://github.com/kstyrc/embedded-redis. We've updated the embedded Redis binaries to version 7.0.15 so we can write tests that use recent Redis features without imposing dependencies that are not well-encapsulated by a single Maven/Gradle build.
9 |
10 | Maven dependency
11 | ==============
12 |
13 | Maven Central:
14 | ```xml
15 |
16 | org.signal
17 | embedded-redis
18 | 0.9.0
19 |
20 | ```
21 |
22 | Usage
23 | ==============
24 |
25 | Running RedisServer is as simple as:
26 | ```java
27 | RedisServer redisServer = new RedisServer(6379);
28 | redisServer.start();
29 | // do some work
30 | redisServer.stop();
31 | ```
32 |
33 | You can also provide RedisServer with your own executable:
34 | ```java
35 | // 1) given explicit file (os-independence broken!)
36 | RedisServer redisServer = new RedisServer("/path/to/your/redis", 6379);
37 |
38 | // 2) given os-independent matrix
39 | RedisExecProvider customProvider = RedisExecProvider.defaultProvider()
40 | .override(OS.UNIX, "/path/to/unix/redis")
41 | .override(OS.UNIX, Architecture.x86_64, "/path/to/unix/redis.x86_64")
42 | .override(OS.UNIX, Architecture.arm64, "/path/to/unix/redis.arm64")
43 | .override(OS.UNIX, Architecture.x86, "/path/to/unix/redis.i386")
44 | .override(OS.MAC_OS_X, Architecture.x86_64, "/path/to/macosx/redis-x86_64")
45 | .override(OS.MAC_OS_X, Architecture.arm64, "/path/to/macosx/redis.arm64")
46 |
47 | RedisServer redisServer = new RedisServer(customProvider, 6379);
48 | ```
49 |
50 | You can also use fluent API to create RedisServer:
51 | ```java
52 | RedisServer redisServer = RedisServer.builder()
53 | .redisExecProvider(customRedisProvider)
54 | .port(6379)
55 | .slaveOf("locahost", 6378)
56 | .configFile("/path/to/your/redis.conf")
57 | .build();
58 | ```
59 |
60 | Or even create simple redis.conf file from scratch:
61 | ```java
62 | RedisServer redisServer = RedisServer.builder()
63 | .redisExecProvider(customRedisProvider)
64 | .port(6379)
65 | .setting("bind 127.0.0.1") // good for local development on Windows to prevent security popups
66 | .slaveOf("locahost", 6378)
67 | .setting("daemonize no")
68 | .setting("appendonly no")
69 | .setting("maxmemory 128M")
70 | .build();
71 | ```
72 |
73 | ## Setting up a cluster
74 |
75 | Our Embedded Redis has support for HA Redis clusters with Sentinels and master-slave replication
76 |
77 | #### Using ephemeral ports
78 | A simple redis integration test with Redis cluster on ephemeral ports, with setup similar to that from production would look like this:
79 | ```java
80 | public class SomeIntegrationTestThatRequiresRedis {
81 | private RedisCluster cluster;
82 | private Set jedisSentinelHosts;
83 |
84 | @Before
85 | public void setup() throws Exception {
86 | //creates a cluster with 3 sentinels, quorum size of 2 and 3 replication groups, each with one master and one slave
87 | cluster = RedisCluster.builder().ephemeral().sentinelCount(3).quorumSize(2)
88 | .replicationGroup("master1", 1)
89 | .replicationGroup("master2", 1)
90 | .replicationGroup("master3", 1)
91 | .build();
92 | cluster.start();
93 |
94 | //retrieve ports on which sentinels have been started, using a simple Jedis utility class
95 | jedisSentinelHosts = JedisUtil.sentinelHosts(cluster);
96 | }
97 |
98 | @Test
99 | public void test() throws Exception {
100 | // testing code that requires redis running
101 | JedisSentinelPool pool = new JedisSentinelPool("master1", jedisSentinelHosts);
102 | }
103 |
104 | @After
105 | public void tearDown() throws Exception {
106 | cluster.stop();
107 | }
108 | }
109 | ```
110 |
111 | #### Retrieving ports
112 | The above example starts Redis cluster on ephemeral ports, which you can later get with ```cluster.ports()```,
113 | which will return a list of all ports of the cluster. You can also get ports of sentinels with ```cluster.sentinelPorts()```
114 | or servers with ```cluster.serverPorts()```. ```JedisUtil``` class contains utility methods for use with Jedis client.
115 |
116 | #### Using predefined ports
117 | You can also start Redis cluster on predefined ports and even mix both approaches:
118 | ```java
119 | public class SomeIntegrationTestThatRequiresRedis {
120 | private RedisCluster cluster;
121 |
122 | @Before
123 | public void setup() throws Exception {
124 | final List sentinels = Arrays.asList(26739, 26912);
125 | final List group1 = Arrays.asList(6667, 6668);
126 | final List group2 = Arrays.asList(6387, 6379);
127 | //creates a cluster with 3 sentinels, quorum size of 2 and 3 replication groups, each with one master and one slave
128 | cluster = RedisCluster.builder().sentinelPorts(sentinels).quorumSize(2)
129 | .serverPorts(group1).replicationGroup("master1", 1)
130 | .serverPorts(group2).replicationGroup("master2", 1)
131 | .ephemeralServers().replicationGroup("master3", 1)
132 | .build();
133 | cluster.start();
134 | }
135 | //(...)
136 | ```
137 | The above will create and start a cluster with sentinels on ports ```26739, 26912```, first replication group on ```6667, 6668```,
138 | second replication group on ```6387, 6379``` and third replication group on ephemeral ports.
139 |
140 | Redis version
141 | ==============
142 |
143 | By default, RedisServer runs an OS-specific executable enclosed in in the `embedded-redis` jar. The jar includes:
144 |
145 | - Redis 7.0.15 for Linux/Unix (i386, x86_64 and arm64)
146 | - Redis 7.0.15 for macOS (x86_64 and arm64e AKA Apple Silicon)
147 |
148 | The enclosed binaries are built from source from the [`7.0.15` tag](https://github.com/redis/redis/releases/tag/7.0.15) in the official Redis repository. The Linux and Darwin/macOS binaries are statically-linked amd64 and x86 executables built using the [build-server-binaries.sh](src/main/docker/build-server-binaries.sh) script included in this repository at `/src/main/docker`. Windows binaries are not included because Windows is not officially supported by Redis.
149 |
150 | Note: the `build-server-binaries.sh` script attempts to build all of the above noted OS and architectures, which means that it expects the local Docker daemon to support all of them. Docker Desktop on macOS and Windows supports multi-arch builds out of the box; Docker on Linux may require [additional configuration](https://docs.docker.com/buildx/working-with-buildx/).
151 |
152 | Callers may provide a path to a specific `redis-server` executable if needed.
153 |
154 |
155 | License
156 | ==============
157 | Licensed under the Apache License, Version 2.0
158 |
159 | The included Redis binaries are covered by [Redis’s license](https://github.com/redis/redis/blob/4930d19e70c391750479951022e207e19111eb55/COPYING):
160 |
161 | > Copyright (c) 2006-2020, Salvatore Sanfilippo
162 | > All rights reserved.
163 | >
164 | > Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
165 | >
166 | > * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
167 | > * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
168 | > * Neither the name of Redis nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
169 | >
170 | > THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
171 |
172 |
173 | Contributors
174 | ==============
175 | * Krzysztof Styrc ([@kstyrc](https://github.com/kstyrc))
176 | * Piotr Turek ([@turu](https://github.com/turu))
177 | * anthonyu ([@anthonyu](https://github.com/anthonyu))
178 | * Artem Orobets ([@enisher](https://github.com/enisher))
179 | * Sean Simonsen ([@SeanSimonsen](https://github.com/SeanSimonsen))
180 | * Rob Winch ([@rwinch](https://github.com/rwinch))
181 | * Jon Chambers ([@jchambers](https://github.com/jchambers))
182 | * Chris Eager ([@eager](https://github.com/eager))
183 |
184 | Changelog
185 | ==============
186 |
187 | ### 0.9.0
188 | * Updated to Redis 7.0.15
189 | * Updated Guava to 33
190 | * Updated JUnit to 4.13.2
191 |
192 | ### 0.8.3
193 | * Updated to Redis 6.2.7
194 | * Statically link Linux binaries with OpenSSL instead of LibreSSL to avoid `openssl.cnf` incompatibilities
195 |
196 | ### 0.8.2
197 | * Updated to Redis 6.2.6
198 | * Added native support for Apple Silicon (darwin/arm64) and Linux aarch64
199 | * Compiled Redis servers with TLS support
200 |
201 | ### 0.8.1
202 | * Include statically-linked Redis binaries
203 | * Update still more dependencies
204 |
205 | ### 0.8
206 | * Updated to Redis 6.0.5
207 | * Dropped support for Windows
208 | * Updated to Guava 29
209 |
210 | ### 0.7
211 | * Updated dependencies
212 | * Fixed an incorrect maximum memory setting
213 | * Add support for more Redis versions
214 | * Bind to 127.0.0.1 by default
215 | * Clean up gracefully at JVM exit
216 |
217 | ### 0.6
218 | * Support JDK 6 +
219 |
220 | ### 0.5
221 | * OS detection fix
222 | * redis binary per OS/arch pair
223 | * Updated to 2.8.19 binary for Windows
224 |
225 | ### 0.4
226 | * Updated for Java 8
227 | * Added Sentinel support
228 | * Ability to create arbitrary clusters on arbitrary (ephemeral) ports
229 | * Updated to latest guava
230 | * Throw an exception if redis has not been started
231 | * Redis errorStream logged to System.out
232 |
233 | ### 0.3
234 | * Fluent API for RedisServer creation
235 |
236 | ### 0.2
237 | * Initial decent release
238 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 | org.signal
5 | embedded-redis
6 | jar
7 | 0.9.2-SNAPSHOT
8 | embedded-redis
9 | Redis embedded server for Java integration testing.
10 | https://github.com/signalapp/embedded-redis
11 |
12 |
13 |
14 | The Apache Software License, Version 2.0
15 | http://www.apache.org/licenses/LICENSE-2.0.txt
16 | repo
17 |
18 |
19 |
20 |
21 | https://github.com/signalapp/embedded-redis
22 | scm:git:https://github.com/signalapp/embedded-redis.git
23 | scm:git:git@github.com:signalapp/embedded-redis.git
24 | 0.9.1
25 |
26 |
27 |
28 |
29 | Jon Chambers
30 | jon@signal.org
31 |
32 |
33 |
34 |
35 | UTF-8
36 |
37 |
38 |
39 |
40 | com.google.guava
41 | guava
42 | 33.0.0-jre
43 |
44 |
45 |
46 | commons-io
47 | commons-io
48 | 2.17.0
49 |
50 |
51 |
52 |
53 | redis.clients
54 | jedis
55 | 2.10.2
56 | test
57 |
58 |
59 | org.mockito
60 | mockito-core
61 | 3.3.3
62 | test
63 |
64 |
65 |
66 | junit
67 | junit
68 | 4.13.2
69 | test
70 |
71 |
72 |
73 |
74 |
75 | ossrh
76 | https://oss.sonatype.org/service/local/staging/deploy/maven2/
77 |
78 |
79 |
80 |
81 |
82 | ${project.basedir}/src/main/resources
83 |
84 |
85 | ${project.build.directory}/generated-resources
86 |
87 |
88 |
89 |
90 | org.apache.maven.plugins
91 | maven-surefire-plugin
92 | 3.2.5
93 |
94 |
95 |
96 | org.apache.maven.plugins
97 | maven-compiler-plugin
98 | 3.13.0
99 |
100 | 8
101 | 8
102 |
103 |
104 |
105 |
106 | org.apache.maven.plugins
107 | maven-source-plugin
108 | 3.3.0
109 |
110 |
111 | attach-sources
112 |
113 | jar
114 |
115 |
116 |
117 |
118 |
119 | org.apache.maven.plugins
120 | maven-javadoc-plugin
121 | 3.6.3
122 |
123 | 8
124 |
125 |
126 |
127 | attach-javadocs
128 |
129 | jar
130 |
131 |
132 |
133 |
134 |
135 | org.apache.maven.plugins
136 | maven-gpg-plugin
137 | 3.2.2
138 |
139 |
140 | sign-artifacts
141 | verify
142 |
143 | sign
144 |
145 |
146 |
147 |
148 |
149 | org.sonatype.plugins
150 | nexus-staging-maven-plugin
151 | 1.6.13
152 | true
153 |
154 | ossrh
155 | https://oss.sonatype.org/
156 | true
157 |
158 |
159 |
160 | org.apache.maven.plugins
161 | maven-resources-plugin
162 | 3.3.1
163 |
164 |
165 | copy-test-certs
166 | process-test-resources
167 |
168 | testResources
169 |
170 |
171 |
172 |
173 | ${project.basedir}/src/test/bash/tests/tls
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 | org.codehaus.mojo
182 | exec-maven-plugin
183 | 3.2.0
184 |
185 |
186 | generate-test-certificates
187 | generate-test-resources
188 |
189 | exec
190 |
191 |
192 | ./gen-test-certs-if-needed.sh
193 | ${project.basedir}/src/test/bash
194 |
195 | ${project.build.testOutputDirectory}
196 |
197 |
198 |
199 |
200 | cleanup-test-certificates
201 | process-test-resources
202 |
203 | exec
204 |
205 |
206 | ./cleanup-test-certs.sh
207 | ${project.basedir}/src/test/bash
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
--------------------------------------------------------------------------------
/src/main/docker/.gitignore:
--------------------------------------------------------------------------------
1 | redis-server-*
2 | redis-*
3 |
--------------------------------------------------------------------------------
/src/main/docker/Dockerfile:
--------------------------------------------------------------------------------
1 | ARG ALPINE_VERSION=3.19.1
2 | FROM alpine:${ALPINE_VERSION}
3 | RUN apk add --no-cache gcc musl-dev openssl-dev openssl-libs-static make pkgconfig linux-headers
4 |
5 | WORKDIR /build
6 |
7 | ARG REDIS_VERSION
8 | ENV REDIS_VERSION=${REDIS_VERSION}
9 | COPY redis-${REDIS_VERSION}.tar.gz /redis-${REDIS_VERSION}.tar.gz
10 | RUN ls -l /
11 |
12 | ARG ARCH
13 | RUN tar zxf /redis-${REDIS_VERSION}.tar.gz && \
14 | cd redis-${REDIS_VERSION} && \
15 | make BUILD_TLS='yes' CC='gcc -static' LDFLAGS='-s' MALLOC='libc' && \
16 | mv src/redis-server /build/redis-server-${REDIS_VERSION}-linux-${ARCH}
17 |
18 | CMD [ "/bin/sh" ]
19 |
--------------------------------------------------------------------------------
/src/main/docker/build-server-binaries.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -e
4 |
5 | REDIS_VERSION=7.0.15
6 | REDIS_TARBALL="redis-${REDIS_VERSION}.tar.gz"
7 | REDIS_URL="https://download.redis.io/releases/${REDIS_TARBALL}"
8 |
9 | echo $ARCH
10 |
11 | function copy_openssl_and_remove_dylibs() {
12 | # To make macOS builds more portable, we want to statically link OpenSSL,
13 | # which is not straightforward. To force static compilation, we copy
14 | # the openssl libraries and remove dylibs, forcing static linking
15 | OPENSSL_HOME="${1}"
16 | ARCH=$2
17 | OPENSSL_HOME_COPY="${3}/${ARCH}"
18 |
19 | echo "*** Copying openssl libraries for static linking"
20 | cp -RL "${OPENSSL_HOME}" "${OPENSSL_HOME_COPY}"
21 | rm -f "${OPENSSL_HOME_COPY}"/lib/*.dylib
22 | }
23 |
24 | if [ "$(dirname ${0})" != "." ]; then
25 | echo "This script must be run from $(dirname ${0}). \`cd\` there and run again"
26 | exit 1
27 | fi
28 |
29 | if ! [ -f "${REDIS_TARBALL}" ]; then
30 | curl -o "${REDIS_TARBALL}" "${REDIS_URL}"
31 | fi
32 |
33 | all_linux=0
34 | if command -pv docker buildx 2>/dev/null; then
35 | for arch in amd64 arm64 386; do
36 | builder_name="embedded-redis-builder-$RANDOM"
37 |
38 | docker buildx create \
39 | --name "$builder_name" \
40 | --platform linux/amd64,linux/arm64,linux/386
41 |
42 | docker buildx use "$builder_name"
43 |
44 | echo "*** Building redis version ${REDIS_VERSION} for linux-${arch}"
45 |
46 | set +e
47 | docker buildx build \
48 | "--platform=linux/${arch}" \
49 | --build-arg "REDIS_VERSION=${REDIS_VERSION}" \
50 | --build-arg "ARCH=${arch}" \
51 | -t "redis-server-builder-${arch}" \
52 | --load \
53 | .
54 |
55 | if [[ $? -ne 0 ]]; then
56 | echo "*** ERROR: could not build for linux-${arch}"
57 | continue
58 | fi
59 | set -e
60 |
61 | docker buildx rm "$builder_name"
62 |
63 | docker run -it --rm \
64 | "--platform=linux/${arch}" \
65 | -v "$(pwd)/":/mnt \
66 | --user "$(id -u):$(id -g)" \
67 | "redis-server-builder-${arch}" \
68 | sh -c "cp /build/redis-server-${REDIS_VERSION}-linux-${arch} /mnt"
69 |
70 | ((all_linux+=1))
71 |
72 | done
73 | else
74 | echo "*** WARNING: No docker command found or docker does not support buildx. Cannot build for linux."
75 | fi
76 |
77 | if [[ "${all_linux}" -lt 3 ]]; then
78 | echo "*** WARNING: was not able to build for all linux arches; see above for errors"
79 | fi
80 |
81 | # To build for macOS, you must be running this script from a Mac. The script requires that openssl@1.1
82 | # be installed via Homebrew.
83 | #
84 | # To build Redis binaries for both arm64e and x86_64, you'll need to run this script from an arm64e
85 | # Mac with _two_ parallel installations of Homebrew (see
86 | # https://stackoverflow.com/questions/64951024/how-can-i-run-two-isolated-installations-of-homebrew),
87 | # and install openssl@1.1 with each.
88 | if [[ "$(uname -s)" == "Darwin" ]]; then
89 |
90 | tar zxf "${REDIS_TARBALL}"
91 | cd "redis-${REDIS_VERSION}"
92 |
93 | # temporary directory for openssl libraries for static linking.
94 | # assumes standard Homebrew openssl install:
95 | # - arm64e at /opt/homebrew/opt/openssl@1.1
96 | # - x86_64 at /usr/local/opt/openssl@1.1
97 | OPENSSL_TEMP=$(mktemp -d /tmp/embedded-redis-darwin-openssl.XXXXX)
98 |
99 | # build for arm64 on apple silicon
100 | if arch -arm64e true 2>/dev/null; then
101 | if [ -d /opt/homebrew/opt/openssl@1.1 ]; then
102 | copy_openssl_and_remove_dylibs /opt/homebrew/opt/openssl@1.1 arm64e "${OPENSSL_TEMP}"
103 | echo "*** Building redis version ${REDIS_VERSION} for darwin-arm64e (apple silicon)"
104 | make distclean
105 | arch -arm64e make -j3 BUILD_TLS=yes OPENSSL_PREFIX="$OPENSSL_TEMP/arm64e"
106 | mv src/redis-server "../redis-server-${REDIS_VERSION}-darwin-arm64"
107 | else
108 | echo "*** WARNING: openssl@1.1 not found for darwin-arm64e; skipping build"
109 | fi
110 | else
111 | echo "*** WARNING: could not build for darwin-arm64e; you probably want to do this on an apple silicon device"
112 | fi
113 |
114 | # build for x86_64 if we're on apple silicon or a recent macos on x86_64
115 | if arch -x86_64 true 2>/dev/null; then
116 | if [ -d /usr/local/opt/openssl@1.1 ]; then
117 | copy_openssl_and_remove_dylibs /usr/local/opt/openssl@1.1 x86_64 "${OPENSSL_TEMP}"
118 | echo "*** Building redis version ${REDIS_VERSION} for darwin-x86_64"
119 | make distclean
120 | arch -x86_64 make -j3 BUILD_TLS=yes OPENSSL_PREFIX="$OPENSSL_TEMP/x86_64"
121 | # x86_64 and amd64 are effectively synonymous; we use amd64 here to match the naming scheme used by Docker builds
122 | mv src/redis-server "../redis-server-${REDIS_VERSION}-darwin-amd64"
123 | else
124 | echo "*** WARNING: openssl@1.1 not found for darwin-x86_64; skipping build"
125 | fi
126 | else
127 | echo "*** WARNING: you are on a version of macos that lacks /usr/bin/arch, you probably do not want this"
128 | exit 1
129 | fi
130 | cd ..
131 |
132 | else
133 | echo "*** WARNING: Cannot build for macos/darwin on a $(uname -s) host"
134 | fi
135 |
136 | ls -l redis-server-*
137 |
138 | echo "*** Moving built binaries to ../resources; you need to handle the rest yourself"
139 | mv redis-server-* ../resources/
140 |
--------------------------------------------------------------------------------
/src/main/java/redis/embedded/AbstractRedisInstance.java:
--------------------------------------------------------------------------------
1 | package redis.embedded;
2 |
3 | import org.apache.commons.io.IOUtils;
4 | import redis.embedded.exceptions.EmbeddedRedisException;
5 |
6 | import java.io.*;
7 | import java.util.Arrays;
8 | import java.util.Collections;
9 | import java.util.List;
10 | import java.util.concurrent.ExecutorService;
11 | import java.util.concurrent.Executors;
12 |
13 | abstract class AbstractRedisInstance implements Redis {
14 |
15 | protected List args = Collections.emptyList();
16 | private volatile boolean active = false;
17 | private Process redisProcess;
18 | private final int port;
19 | private final int tlsPort;
20 |
21 | private ExecutorService executor;
22 |
23 | protected AbstractRedisInstance(int port, int tlsPort) {
24 | this.port = port;
25 | this.tlsPort = tlsPort;
26 | }
27 |
28 | protected AbstractRedisInstance(int port) {
29 | this(port, 0);
30 | }
31 |
32 | public boolean isActive() {
33 | return active;
34 | }
35 |
36 | public synchronized void start() throws EmbeddedRedisException {
37 | if (active) {
38 | throw new EmbeddedRedisException("This redis server instance is already running...");
39 | }
40 | try {
41 | redisProcess = createRedisProcessBuilder().start();
42 | installExitHook();
43 | logErrors();
44 | awaitRedisServerReady();
45 | active = true;
46 | } catch (IOException e) {
47 | throw new EmbeddedRedisException("Failed to start Redis instance", e);
48 | }
49 | }
50 |
51 | private void installExitHook() {
52 | Runtime.getRuntime().addShutdownHook(new Thread(this::stop, "RedisInstanceCleaner"));
53 | }
54 |
55 | private void logErrors() {
56 | final InputStream errorStream = redisProcess.getErrorStream();
57 | BufferedReader reader = new BufferedReader(new InputStreamReader(errorStream));
58 | Runnable printReaderTask = new PrintReaderRunnable(reader);
59 | executor = Executors.newSingleThreadExecutor();
60 | executor.submit(printReaderTask);
61 | }
62 |
63 | private void awaitRedisServerReady() throws IOException {
64 | BufferedReader reader = new BufferedReader(new InputStreamReader(redisProcess.getInputStream()));
65 | try {
66 | StringBuilder outputStringBuilder = new StringBuilder();
67 | String outputLine;
68 | do {
69 | outputLine = reader.readLine();
70 | if (outputLine == null) {
71 | // Something is wrong. Stream ended before server was activated.
72 | throw new EmbeddedRedisException("Redis server failed to become ready. Check logs for details. Redis process log: " + outputStringBuilder.toString());
73 | } else {
74 | outputStringBuilder.append("\n");
75 | outputStringBuilder.append(outputLine);
76 | }
77 | } while (!outputLine.matches(redisReadyPattern()));
78 | } finally {
79 | IOUtils.closeQuietly(reader, null);
80 | }
81 | }
82 |
83 | protected abstract String redisReadyPattern();
84 |
85 | private ProcessBuilder createRedisProcessBuilder() {
86 | File executable = new File(args.get(0));
87 | ProcessBuilder pb = new ProcessBuilder(args);
88 | pb.directory(executable.getParentFile());
89 | return pb;
90 | }
91 |
92 | public synchronized void stop() throws EmbeddedRedisException {
93 | if (active) {
94 | if (executor != null && !executor.isShutdown()) {
95 | executor.shutdown();
96 | }
97 | redisProcess.destroy();
98 | tryWaitFor();
99 | active = false;
100 | }
101 | }
102 |
103 | private void tryWaitFor() {
104 | try {
105 | redisProcess.waitFor();
106 | } catch (InterruptedException e) {
107 | throw new EmbeddedRedisException("Failed to stop redis instance", e);
108 | }
109 | }
110 |
111 | public List ports() {
112 | return port > 0 ? Collections.singletonList(port) : Collections.emptyList();
113 | }
114 |
115 | public List tlsPorts() {
116 | return tlsPort > 0 ? Collections.singletonList(tlsPort) : Collections.emptyList();
117 | }
118 |
119 | private static class PrintReaderRunnable implements Runnable {
120 | private final BufferedReader reader;
121 |
122 | private PrintReaderRunnable(BufferedReader reader) {
123 | this.reader = reader;
124 | }
125 |
126 | public void run() {
127 | try {
128 | readLines();
129 | } finally {
130 | IOUtils.closeQuietly(reader, null);
131 | }
132 | }
133 |
134 | public void readLines() {
135 | try {
136 | String line;
137 | while ((line = reader.readLine()) != null) {
138 | System.out.println(line);
139 | }
140 | } catch (IOException e) {
141 | e.printStackTrace();
142 | }
143 | }
144 | }
145 | }
146 |
--------------------------------------------------------------------------------
/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 | List tlsPorts();
17 | }
18 |
--------------------------------------------------------------------------------
/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 | @Override
63 | public List tlsPorts() {
64 | List ports = new ArrayList();
65 | ports.addAll(sentinelTlsPorts());
66 | ports.addAll(serverTlsPorts());
67 | return ports;
68 | }
69 |
70 | public List sentinels() {
71 | return Lists.newLinkedList(sentinels);
72 | }
73 |
74 | public List sentinelPorts() {
75 | List ports = new ArrayList();
76 | for(Redis redis : sentinels) {
77 | ports.addAll(redis.ports());
78 | }
79 | return ports;
80 | }
81 |
82 | public List sentinelTlsPorts() {
83 | List ports = new ArrayList();
84 | for(Redis redis : sentinels) {
85 | ports.addAll(redis.tlsPorts());
86 | }
87 | return ports;
88 | }
89 |
90 | public List servers() {
91 | return Lists.newLinkedList(servers);
92 | }
93 |
94 | public List serverPorts() {
95 | List ports = new ArrayList();
96 | for(Redis redis : servers) {
97 | ports.addAll(redis.ports());
98 | }
99 | return ports;
100 | }
101 |
102 | public List serverTlsPorts() {
103 | List ports = new ArrayList();
104 | for(Redis redis : servers) {
105 | ports.addAll(redis.tlsPorts());
106 | }
107 | return ports;
108 | }
109 |
110 | public static RedisClusterBuilder builder() {
111 | return new RedisClusterBuilder();
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/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 final String redisVersion = "7.0.15";
19 |
20 | public static RedisExecProvider defaultProvider() {
21 | return new RedisExecProvider();
22 | }
23 |
24 | private RedisExecProvider() {
25 | initExecutables();
26 | }
27 |
28 | private void initExecutables() {
29 | executables.put(OsArchitecture.UNIX_x86, "redis-server-" + redisVersion + "-linux-386");
30 | executables.put(OsArchitecture.UNIX_x86_64, "redis-server-" + redisVersion + "-linux-amd64");
31 | executables.put(OsArchitecture.UNIX_arm64, "redis-server-" + redisVersion + "-linux-arm64");
32 |
33 | executables.put(OsArchitecture.MAC_OS_X_x86_64, "redis-server-" + redisVersion + "-darwin-amd64");
34 | executables.put(OsArchitecture.MAC_OS_X_arm64, "redis-server-" + redisVersion + "-darwin-arm64");
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 |
54 | if (!executables.containsKey(osArch)) {
55 | throw new IllegalArgumentException("No Redis executable found for " + osArch);
56 | }
57 |
58 | String executablePath = executables.get(osArch);
59 |
60 | return fileExists(executablePath) ?
61 | new File(executablePath) :
62 | JarUtil.extractExecutableFromJar(executablePath);
63 |
64 | }
65 |
66 | private boolean fileExists(String executablePath) {
67 | return new File(executablePath).exists();
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/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.nio.charset.StandardCharsets;
11 | import java.util.ArrayList;
12 | import java.util.List;
13 |
14 | public class RedisSentinelBuilder {
15 | private static final String LINE_SEPARATOR = System.getProperty("line.separator");
16 | private static final String CONF_FILENAME = "embedded-redis-sentinel";
17 | private static final String MASTER_MONITOR_LINE = "sentinel monitor %s 127.0.0.1 %d %d";
18 | private static final String DOWN_AFTER_LINE = "sentinel down-after-milliseconds %s %d";
19 | private static final String FAILOVER_LINE = "sentinel failover-timeout %s %d";
20 | private static final String PARALLEL_SYNCS_LINE = "sentinel parallel-syncs %s %d";
21 | private static final String PORT_LINE = "port %d";
22 |
23 | private File executable;
24 | private RedisExecProvider redisExecProvider = RedisExecProvider.defaultProvider();
25 | private String bind="127.0.0.1";
26 | private Integer port = 26379;
27 | private int masterPort = 6379;
28 | private String masterName = "mymaster";
29 | private long downAfterMilliseconds = 60000L;
30 | private long failoverTimeout = 180000L;
31 | private int parallelSyncs = 1;
32 | private int quorumSize = 1;
33 | private String sentinelConf;
34 |
35 | private StringBuilder redisConfigBuilder;
36 |
37 | public RedisSentinelBuilder redisExecProvider(RedisExecProvider redisExecProvider) {
38 | this.redisExecProvider = redisExecProvider;
39 | return this;
40 | }
41 |
42 | public RedisSentinelBuilder bind(String bind) {
43 | this.bind = bind;
44 | return this;
45 | }
46 |
47 | public RedisSentinelBuilder port(Integer port) {
48 | this.port = port;
49 | return this;
50 | }
51 |
52 | public RedisSentinelBuilder masterPort(Integer masterPort) {
53 | this.masterPort = masterPort;
54 | return this;
55 | }
56 |
57 | public RedisSentinelBuilder masterName(String masterName) {
58 | this.masterName = masterName;
59 | return this;
60 | }
61 |
62 | public RedisSentinelBuilder quorumSize(int quorumSize) {
63 | this.quorumSize = quorumSize;
64 | return this;
65 | }
66 |
67 | public RedisSentinelBuilder downAfterMilliseconds(Long downAfterMilliseconds) {
68 | this.downAfterMilliseconds = downAfterMilliseconds;
69 | return this;
70 | }
71 |
72 | public RedisSentinelBuilder failoverTimeout(Long failoverTimeout) {
73 | this.failoverTimeout = failoverTimeout;
74 | return this;
75 | }
76 |
77 | public RedisSentinelBuilder parallelSyncs(int parallelSyncs) {
78 | this.parallelSyncs = parallelSyncs;
79 | return this;
80 | }
81 |
82 | public RedisSentinelBuilder configFile(String redisConf) {
83 | if (redisConfigBuilder != null) {
84 | throw new RedisBuildingException("Redis configuration is already partially build using setting(String) method!");
85 | }
86 | this.sentinelConf = redisConf;
87 | return this;
88 | }
89 |
90 | public RedisSentinelBuilder setting(String configLine) {
91 | if (sentinelConf != null) {
92 | throw new RedisBuildingException("Redis configuration is already set using redis conf file!");
93 | }
94 |
95 | if (redisConfigBuilder == null) {
96 | redisConfigBuilder = new StringBuilder();
97 | }
98 |
99 | redisConfigBuilder.append(configLine);
100 | redisConfigBuilder.append(LINE_SEPARATOR);
101 | return this;
102 | }
103 |
104 | public RedisSentinel build() {
105 | tryResolveConfAndExec();
106 | List args = buildCommandArgs();
107 | return new RedisSentinel(args, port);
108 | }
109 |
110 | private void tryResolveConfAndExec() {
111 | try {
112 | if (sentinelConf == null) {
113 | resolveSentinelConf();
114 | }
115 | executable = redisExecProvider.get();
116 | } catch (Exception e) {
117 | throw new RedisBuildingException("Could not build sentinel instance", e);
118 | }
119 | }
120 |
121 | public void reset() {
122 | this.redisConfigBuilder = null;
123 | this.sentinelConf = null;
124 | }
125 |
126 | public void addDefaultReplicationGroup() {
127 | setting(String.format(MASTER_MONITOR_LINE, masterName, masterPort, quorumSize));
128 | setting(String.format(DOWN_AFTER_LINE, masterName, downAfterMilliseconds));
129 | setting(String.format(FAILOVER_LINE, masterName, failoverTimeout));
130 | setting(String.format(PARALLEL_SYNCS_LINE, masterName, parallelSyncs));
131 | }
132 |
133 | private void resolveSentinelConf() throws IOException {
134 | if (redisConfigBuilder == null) {
135 | addDefaultReplicationGroup();
136 | }
137 | setting("bind "+bind);
138 | setting(String.format(PORT_LINE, port));
139 | final String configString = redisConfigBuilder.toString();
140 |
141 | File redisConfigFile = File.createTempFile(resolveConfigName(), ".conf");
142 | redisConfigFile.deleteOnExit();
143 | Files.asCharSink(redisConfigFile, StandardCharsets.UTF_8).write(configString);
144 | sentinelConf = redisConfigFile.getAbsolutePath();
145 | }
146 |
147 | private String resolveConfigName() {
148 | return CONF_FILENAME + "_" + port;
149 | }
150 |
151 | private List buildCommandArgs() {
152 | Preconditions.checkNotNull(sentinelConf);
153 |
154 | List args = new ArrayList();
155 | args.add(executable.getAbsolutePath());
156 | args.add(sentinelConf);
157 | args.add("--sentinel");
158 |
159 | if (port != null) {
160 | args.add("--port");
161 | args.add(Integer.toString(port));
162 | }
163 |
164 | return args;
165 | }
166 | }
167 |
--------------------------------------------------------------------------------
/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(int port, int tlsPort) {
23 | super(port, tlsPort);
24 | this.args = builder().port(port).tlsPort(tlsPort).build().args;
25 | }
26 |
27 | public RedisServer(File executable, int port) {
28 | super(port);
29 | this.args = Arrays.asList(
30 | executable.getAbsolutePath(),
31 | "--port", Integer.toString(port)
32 | );
33 | }
34 |
35 | public RedisServer(RedisExecProvider redisExecProvider, int port) throws IOException {
36 | super(port);
37 | this.args = Arrays.asList(
38 | redisExecProvider.get().getAbsolutePath(),
39 | "--port", Integer.toString(port)
40 | );
41 | }
42 |
43 | RedisServer(List args, int port, int tlsPort) {
44 | super(port, tlsPort);
45 | this.args = new ArrayList<>(args);
46 | }
47 |
48 | public static RedisServerBuilder builder() {
49 | return new RedisServerBuilder();
50 | }
51 |
52 | @Override
53 | protected String redisReadyPattern() {
54 | return REDIS_READY_PATTERN;
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/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.nio.charset.StandardCharsets;
12 | import java.util.ArrayList;
13 | import java.util.List;
14 |
15 | public class RedisServerBuilder {
16 | private static final String LINE_SEPARATOR = System.getProperty("line.separator");
17 | private static final String CONF_FILENAME = "embedded-redis-server";
18 |
19 | private File executable;
20 | private RedisExecProvider redisExecProvider = RedisExecProvider.defaultProvider();
21 | private String bind="127.0.0.1";
22 | private int port = 6379;
23 | private int tlsPort = 0;
24 | private InetSocketAddress slaveOf;
25 | private String redisConf;
26 |
27 | private StringBuilder redisConfigBuilder;
28 |
29 | public RedisServerBuilder redisExecProvider(RedisExecProvider redisExecProvider) {
30 | this.redisExecProvider = redisExecProvider;
31 | return this;
32 | }
33 |
34 | public RedisServerBuilder bind(String bind) {
35 | this.bind = bind;
36 | return this;
37 | }
38 |
39 | public RedisServerBuilder port(int port) {
40 | this.port = port;
41 | return this;
42 | }
43 |
44 | public RedisServerBuilder tlsPort(int tlsPort) {
45 | this.tlsPort = tlsPort;
46 | return this;
47 | }
48 |
49 | public RedisServerBuilder slaveOf(String hostname, int port) {
50 | this.slaveOf = new InetSocketAddress(hostname, port);
51 | return this;
52 | }
53 |
54 | public RedisServerBuilder slaveOf(InetSocketAddress slaveOf) {
55 | this.slaveOf = slaveOf;
56 | return this;
57 | }
58 |
59 | public RedisServerBuilder configFile(String redisConf) {
60 | if (redisConfigBuilder != null) {
61 | throw new RedisBuildingException("Redis configuration is already partially build using setting(String) method!");
62 | }
63 | this.redisConf = redisConf;
64 | return this;
65 | }
66 |
67 | public RedisServerBuilder setting(String configLine) {
68 | if (redisConf != null) {
69 | throw new RedisBuildingException("Redis configuration is already set using redis conf file!");
70 | }
71 |
72 | if (redisConfigBuilder == null) {
73 | redisConfigBuilder = new StringBuilder();
74 | }
75 |
76 | redisConfigBuilder.append(configLine);
77 | redisConfigBuilder.append(LINE_SEPARATOR);
78 | return this;
79 | }
80 |
81 | public RedisServer build() {
82 | setting("bind "+bind);
83 | tryResolveConfAndExec();
84 | List args = buildCommandArgs();
85 | return new RedisServer(args, port, tlsPort);
86 | }
87 |
88 | public void reset() {
89 | this.executable = null;
90 | this.redisConfigBuilder = null;
91 | this.slaveOf = null;
92 | this.redisConf = null;
93 | }
94 |
95 | private void tryResolveConfAndExec() {
96 | try {
97 | resolveConfAndExec();
98 | } catch (IOException e) {
99 | throw new RedisBuildingException("Could not build server instance", e);
100 | }
101 | }
102 |
103 | private void resolveConfAndExec() throws IOException {
104 | if (redisConf == null && redisConfigBuilder != null) {
105 | File redisConfigFile = File.createTempFile(resolveConfigName(), ".conf");
106 | redisConfigFile.deleteOnExit();
107 | Files.asCharSink(redisConfigFile, StandardCharsets.UTF_8).write(redisConfigBuilder.toString());
108 | redisConf = redisConfigFile.getAbsolutePath();
109 | }
110 |
111 | try {
112 | executable = redisExecProvider.get();
113 | } catch (Exception e) {
114 | throw new RedisBuildingException("Failed to resolve executable", e);
115 | }
116 | }
117 |
118 | private String resolveConfigName() {
119 | return CONF_FILENAME + "_" + port;
120 | }
121 |
122 | private List buildCommandArgs() {
123 | List args = new ArrayList();
124 | args.add(executable.getAbsolutePath());
125 |
126 | if (!Strings.isNullOrEmpty(redisConf)) {
127 | args.add(redisConf);
128 | }
129 |
130 | args.add("--port");
131 | args.add(Integer.toString(port));
132 |
133 | if (tlsPort > 0) {
134 | args.add("--tls-port");
135 | args.add(Integer.toString(tlsPort));
136 | }
137 |
138 | if (slaveOf != null) {
139 | args.add("--slaveof");
140 | args.add(slaveOf.getHostName());
141 | args.add(Integer.toString(slaveOf.getPort()));
142 | }
143 |
144 | return args;
145 | }
146 | }
147 |
--------------------------------------------------------------------------------
/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 | arm64
7 | }
8 |
--------------------------------------------------------------------------------
/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 command = extractFileFromJar(executable);
14 | command.setExecutable(true);
15 |
16 | return command;
17 | }
18 |
19 | public static File extractFileFromJar(String path) throws IOException {
20 | File tmpDir = Files.createTempDir();
21 | tmpDir.deleteOnExit();
22 |
23 | File file = new File(tmpDir, path);
24 | FileUtils.copyURLToFile(Resources.getResource(path), file);
25 | file.deleteOnExit();
26 |
27 | return file;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/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 | try {
51 | Process proc = Runtime.getRuntime().exec("uname -m");
52 | try (BufferedReader input = new BufferedReader(new InputStreamReader(proc.getInputStream()))) {
53 | String machine = input.readLine();
54 | switch (machine) {
55 | case "aarch64":
56 | return Architecture.arm64;
57 | case "x86_64":
58 | return Architecture.x86_64;
59 | default:
60 | throw new OsDetectionException("unsupported architecture: " + machine);
61 | }
62 | }
63 | } catch (Exception e) {
64 | if (e instanceof OsDetectionException) {
65 | throw (OsDetectionException)e;
66 | }
67 | throw new OsDetectionException(e);
68 | }
69 | }
70 |
71 | private static Architecture getMacOSXArchitecture() {
72 | try {
73 | Process proc = Runtime.getRuntime().exec("uname -m");
74 | try (BufferedReader input = new BufferedReader(new InputStreamReader(proc.getInputStream()))) {
75 | String machine = input.readLine();
76 | switch (machine) {
77 | case "arm64":
78 | return Architecture.arm64;
79 | case "x86_64":
80 | return Architecture.x86_64;
81 | default:
82 | throw new OsDetectionException("unsupported architecture: " + machine);
83 | }
84 | }
85 | } catch (Exception e) {
86 | if (e instanceof OsDetectionException) {
87 | throw (OsDetectionException)e;
88 | }
89 | throw new OsDetectionException(e);
90 | }
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/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 UNIX_x86 = new OsArchitecture(OS.UNIX, Architecture.x86);
8 | public static final OsArchitecture UNIX_x86_64 = new OsArchitecture(OS.UNIX, Architecture.x86_64);
9 | public static final OsArchitecture UNIX_arm64 = new OsArchitecture(OS.UNIX, Architecture.arm64);
10 |
11 | public static final OsArchitecture MAC_OS_X_x86_64 = new OsArchitecture(OS.MAC_OS_X, Architecture.x86_64);
12 | public static final OsArchitecture MAC_OS_X_arm64 = new OsArchitecture(OS.MAC_OS_X, Architecture.arm64);
13 |
14 | private final OS os;
15 | private final Architecture arch;
16 |
17 | public static OsArchitecture detect() {
18 | OS os = OSDetector.getOS();
19 | Architecture arch = OSDetector.getArchitecture();
20 | return new OsArchitecture(os, arch);
21 | }
22 |
23 | public OsArchitecture(OS os, Architecture arch) {
24 | Preconditions.checkNotNull(os);
25 | Preconditions.checkNotNull(arch);
26 |
27 | this.os = os;
28 | this.arch = arch;
29 | }
30 |
31 | public OS os() {
32 | return os;
33 | }
34 |
35 | public Architecture arch() {
36 | return arch;
37 | }
38 |
39 | @Override
40 | public boolean equals(Object o) {
41 | if (this == o) return true;
42 | if (o == null || getClass() != o.getClass()) return false;
43 |
44 | OsArchitecture that = (OsArchitecture) o;
45 |
46 | return arch == that.arch && os == that.os;
47 |
48 | }
49 |
50 | @Override
51 | public int hashCode() {
52 | int result = os.hashCode();
53 | result = 31 * result + arch.hashCode();
54 | return result;
55 | }
56 |
57 | @Override
58 | public String toString() {
59 | return String.format("%s (%s)", os.name(), arch.name());
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/main/resources/redis-server-7.0.15-darwin-amd64:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/signalapp/embedded-redis/52cac449aa5c4e6155ed9705329169c75171919e/src/main/resources/redis-server-7.0.15-darwin-amd64
--------------------------------------------------------------------------------
/src/main/resources/redis-server-7.0.15-darwin-arm64:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/signalapp/embedded-redis/52cac449aa5c4e6155ed9705329169c75171919e/src/main/resources/redis-server-7.0.15-darwin-arm64
--------------------------------------------------------------------------------
/src/main/resources/redis-server-7.0.15-linux-386:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/signalapp/embedded-redis/52cac449aa5c4e6155ed9705329169c75171919e/src/main/resources/redis-server-7.0.15-linux-386
--------------------------------------------------------------------------------
/src/main/resources/redis-server-7.0.15-linux-amd64:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/signalapp/embedded-redis/52cac449aa5c4e6155ed9705329169c75171919e/src/main/resources/redis-server-7.0.15-linux-amd64
--------------------------------------------------------------------------------
/src/main/resources/redis-server-7.0.15-linux-arm64:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/signalapp/embedded-redis/52cac449aa5c4e6155ed9705329169c75171919e/src/main/resources/redis-server-7.0.15-linux-arm64
--------------------------------------------------------------------------------
/src/test/bash/cleanup-test-certs.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | rm -rf ./tests
4 |
--------------------------------------------------------------------------------
/src/test/bash/gen-test-certs-if-needed.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | OUTPUT_DIR="$1"
4 |
5 | if [ ! -f "$OUTPUT_DIR/redis.key" -o ! -f "$OUTPUT_DIR/redis.crt" -o ! -f "$OUTPUT_DIR/ca.crt" ]; then
6 | ./gen-test-certs.sh
7 | fi
8 |
--------------------------------------------------------------------------------
/src/test/bash/gen-test-certs.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # From https://github.com/redis/redis/blob/4930d19e70c391750479951022e207e19111eb55/redis/util/gen-test-certs.sh, under the license:
4 | #
5 | # Copyright (c) 2006-2020, Salvatore Sanfilippo
6 | # All rights reserved.
7 | #
8 | # Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
9 | #
10 | # * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
11 | # * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
12 | # * Neither the name of Redis nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
13 | #
14 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
15 |
16 |
17 |
18 | # Generate some test certificates which are used by the regression test suite:
19 | #
20 | # tests/tls/ca.{crt,key} Self signed CA certificate.
21 | # tests/tls/redis.{crt,key} A certificate with no key usage/policy restrictions.
22 | # tests/tls/client.{crt,key} A certificate restricted for SSL client usage.
23 | # tests/tls/server.{crt,key} A certificate restricted fro SSL server usage.
24 | # tests/tls/redis.dh DH Params file.
25 |
26 | generate_cert() {
27 | local name=$1
28 | local cn="$2"
29 | local opts="$3"
30 |
31 | local keyfile=tests/tls/${name}.key
32 | local certfile=tests/tls/${name}.crt
33 |
34 | [ -f $keyfile ] || openssl genrsa -out $keyfile 2048
35 | openssl req \
36 | -new -sha256 \
37 | -subj "/O=Redis Test/CN=$cn" \
38 | -key $keyfile | \
39 | openssl x509 \
40 | -req -sha256 \
41 | -CA tests/tls/ca.crt \
42 | -CAkey tests/tls/ca.key \
43 | -CAserial tests/tls/ca.txt \
44 | -CAcreateserial \
45 | -days 365 \
46 | $opts \
47 | -out $certfile
48 | }
49 |
50 | mkdir -p tests/tls
51 | [ -f tests/tls/ca.key ] || openssl genrsa -out tests/tls/ca.key 4096
52 | openssl req \
53 | -x509 -new -nodes -sha256 \
54 | -key tests/tls/ca.key \
55 | -days 3650 \
56 | -subj '/O=Redis Test/CN=Certificate Authority' \
57 | -out tests/tls/ca.crt
58 |
59 | cat > tests/tls/openssl.cnf <<_END_
60 | [ server_cert ]
61 | keyUsage = digitalSignature, keyEncipherment
62 | nsCertType = server
63 |
64 | [ client_cert ]
65 | keyUsage = digitalSignature, keyEncipherment
66 | nsCertType = client
67 | _END_
68 |
69 | generate_cert server "Server-only" "-extfile tests/tls/openssl.cnf -extensions server_cert"
70 | generate_cert client "Client-only" "-extfile tests/tls/openssl.cnf -extensions client_cert"
71 | generate_cert redis "Generic-cert"
72 |
73 | [ -f tests/tls/redis.dh ] || openssl dhparam -out tests/tls/redis.dh 2048
74 |
--------------------------------------------------------------------------------
/src/test/java/redis/embedded/RedisClusterTest.java:
--------------------------------------------------------------------------------
1 | package redis.embedded;
2 |
3 | import com.google.common.collect.Sets;
4 | import org.apache.commons.io.IOUtils;
5 | import org.junit.Before;
6 | import org.junit.Test;
7 | import redis.clients.jedis.Jedis;
8 | import redis.clients.jedis.JedisSentinelPool;
9 | import redis.embedded.util.JedisUtil;
10 |
11 | import java.io.Closeable;
12 | import java.net.Inet4Address;
13 | import java.util.Arrays;
14 | import java.util.List;
15 | import java.util.Set;
16 |
17 | import static org.junit.Assert.assertEquals;
18 | import static org.mockito.BDDMockito.given;
19 | import static org.mockito.Mockito.mock;
20 | import static org.mockito.Mockito.verify;
21 |
22 | public class RedisClusterTest {
23 | private final RedisSentinelBuilder sentinelBuilder = RedisSentinel.builder();
24 | private String bindAddress;
25 |
26 | private Redis sentinel1;
27 | private Redis sentinel2;
28 | private Redis master1;
29 | private Redis master2;
30 |
31 | private RedisCluster instance;
32 |
33 | @Before
34 | public void setUp() throws Exception {
35 | sentinel1 = mock(Redis.class);
36 | sentinel2 = mock(Redis.class);
37 | master1 = mock(Redis.class);
38 | master2 = mock(Redis.class);
39 |
40 | // Jedis translates ”localhost” to getLocalHost().getHostAddress() (see Jedis HostAndPort#getLocalHostQuietly),
41 | // which can vary from 127.0.0.1 (most notably, Debian/Ubuntu return 127.0.1.1)
42 | if (bindAddress == null) {
43 | bindAddress = Inet4Address.getLocalHost().getHostAddress();
44 | sentinelBuilder.bind(bindAddress);
45 | }
46 | }
47 |
48 |
49 | @Test
50 | public void stopShouldStopEntireCluster() throws Exception {
51 | //given
52 | final List sentinels = Arrays.asList(sentinel1, sentinel2);
53 | final List servers = Arrays.asList(master1, master2);
54 | instance = new RedisCluster(sentinels, servers);
55 |
56 | //when
57 | instance.stop();
58 |
59 | //then
60 | for(Redis s : sentinels) {
61 | verify(s).stop();
62 | }
63 | for(Redis s : servers) {
64 | verify(s).stop();
65 | }
66 | }
67 |
68 | @Test
69 | public void startShouldStartEntireCluster() throws Exception {
70 | //given
71 | final List sentinels = Arrays.asList(sentinel1, sentinel2);
72 | final List servers = Arrays.asList(master1, master2);
73 | instance = new RedisCluster(sentinels, servers);
74 |
75 | //when
76 | instance.start();
77 |
78 | //then
79 | for(Redis s : sentinels) {
80 | verify(s).start();
81 | }
82 | for(Redis s : servers) {
83 | verify(s).start();
84 | }
85 | }
86 |
87 | @Test
88 | public void isActiveShouldCheckEntireClusterIfAllActive() throws Exception {
89 | //given
90 | given(sentinel1.isActive()).willReturn(true);
91 | given(sentinel2.isActive()).willReturn(true);
92 | given(master1.isActive()).willReturn(true);
93 | given(master2.isActive()).willReturn(true);
94 | final List sentinels = Arrays.asList(sentinel1, sentinel2);
95 | final List servers = Arrays.asList(master1, master2);
96 | instance = new RedisCluster(sentinels, servers);
97 |
98 | //when
99 | instance.isActive();
100 |
101 | //then
102 | for(Redis s : sentinels) {
103 | verify(s).isActive();
104 | }
105 | for(Redis s : servers) {
106 | verify(s).isActive();
107 | }
108 | }
109 |
110 | @Test
111 | public void testSimpleOperationsAfterRunWithSingleMasterNoSlavesCluster() throws Exception {
112 | //given
113 | final RedisCluster cluster = RedisCluster.builder().withSentinelBuilder(sentinelBuilder).sentinelCount(1).replicationGroup("ourmaster", 0).build();
114 | cluster.start();
115 |
116 | //when
117 | JedisSentinelPool pool = null;
118 | Jedis jedis = null;
119 | try {
120 | pool = new JedisSentinelPool("ourmaster", Sets.newHashSet("localhost:26379"));
121 | jedis = testPool(pool);
122 | } finally {
123 | closeQuietly(jedis, pool);
124 | cluster.stop();
125 | }
126 | }
127 |
128 | @Test
129 | public void testSimpleOperationsAfterRunWithSingleMasterAndOneSlave() throws Exception {
130 | //given
131 | final RedisCluster cluster = RedisCluster.builder().withSentinelBuilder(sentinelBuilder).sentinelCount(1).replicationGroup("ourmaster", 1).build();
132 | cluster.start();
133 |
134 | //when
135 | JedisSentinelPool pool = null;
136 | Jedis jedis = null;
137 | try {
138 | pool = new JedisSentinelPool("ourmaster", Sets.newHashSet("localhost:26379"));
139 | jedis = testPool(pool);
140 | } finally {
141 | closeQuietly(jedis, pool);
142 | cluster.stop();
143 | }
144 | }
145 |
146 | @Test
147 | public void testSimpleOperationsAfterRunWithSingleMasterMultipleSlaves() throws Exception {
148 | //given
149 | final RedisCluster cluster = RedisCluster.builder().withSentinelBuilder(sentinelBuilder).sentinelCount(1).replicationGroup("ourmaster", 2).build();
150 | cluster.start();
151 |
152 | //when
153 | JedisSentinelPool pool = null;
154 | Jedis jedis = null;
155 | try {
156 | pool = new JedisSentinelPool("ourmaster", Sets.newHashSet("localhost:26379"));
157 | jedis = testPool(pool);
158 | } finally {
159 | closeQuietly(jedis, pool);
160 | cluster.stop();
161 | }
162 | }
163 |
164 | @Test
165 | public void testSimpleOperationsAfterRunWithTwoSentinelsSingleMasterMultipleSlaves() throws Exception {
166 | //given
167 | final RedisCluster cluster = RedisCluster.builder().withSentinelBuilder(sentinelBuilder).sentinelCount(2).replicationGroup("ourmaster", 2).build();
168 | cluster.start();
169 |
170 | //when
171 | JedisSentinelPool pool = null;
172 | Jedis jedis = null;
173 | try {
174 | pool = new JedisSentinelPool("ourmaster", Sets.newHashSet("localhost:26379", "localhost:26380"));
175 | jedis = testPool(pool);
176 | } finally {
177 | closeQuietly(jedis, pool);
178 | cluster.stop();
179 | }
180 | }
181 |
182 | @Test
183 | public void testSimpleOperationsAfterRunWithTwoPredefinedSentinelsSingleMasterMultipleSlaves() throws Exception {
184 | //given
185 | List sentinelPorts = Arrays.asList(26381, 26382);
186 | final RedisCluster cluster = RedisCluster.builder().withSentinelBuilder(sentinelBuilder).sentinelPorts(sentinelPorts).replicationGroup("ourmaster", 2).build();
187 | cluster.start();
188 | final Set sentinelHosts = JedisUtil.portsToJedisHosts(sentinelPorts);
189 |
190 | //when
191 | JedisSentinelPool pool = null;
192 | Jedis jedis = null;
193 | try {
194 | pool = new JedisSentinelPool("ourmaster", sentinelHosts);
195 | jedis = testPool(pool);
196 | } finally {
197 | closeQuietly(jedis, pool);
198 | cluster.stop();
199 | }
200 | }
201 |
202 | @Test
203 | public void testSimpleOperationsAfterRunWithThreeSentinelsThreeMastersOneSlavePerMasterCluster() throws Exception {
204 | //given
205 | final String master1 = "master1";
206 | final String master2 = "master2";
207 | final String master3 = "master3";
208 | final RedisCluster cluster = RedisCluster.builder().withSentinelBuilder(sentinelBuilder).sentinelCount(3).quorumSize(2)
209 | .replicationGroup(master1, 1)
210 | .replicationGroup(master2, 1)
211 | .replicationGroup(master3, 1)
212 | .build();
213 | cluster.start();
214 |
215 | //when
216 | JedisSentinelPool pool1 = null;
217 | JedisSentinelPool pool2 = null;
218 | JedisSentinelPool pool3 = null;
219 | Jedis jedis1 = null;
220 | Jedis jedis2 = null;
221 | Jedis jedis3 = null;
222 | try {
223 | pool1 = new JedisSentinelPool(master1, Sets.newHashSet("localhost:26379", "localhost:26380", "localhost:26381"));
224 | pool2 = new JedisSentinelPool(master2, Sets.newHashSet("localhost:26379", "localhost:26380", "localhost:26381"));
225 | pool3 = new JedisSentinelPool(master3, Sets.newHashSet("localhost:26379", "localhost:26380", "localhost:26381"));
226 | jedis1 = testPool(pool1);
227 | jedis2 = testPool(pool2);
228 | jedis3 = testPool(pool3);
229 | } finally {
230 | closeQuietly(jedis1, pool1, jedis2, pool2, jedis3, pool3);
231 | cluster.stop();
232 | }
233 | }
234 |
235 | @Test
236 | public void testSimpleOperationsAfterRunWithThreeSentinelsThreeMastersOneSlavePerMasterEphemeralCluster() throws Exception {
237 | //given
238 | final String master1 = "master1";
239 | final String master2 = "master2";
240 | final String master3 = "master3";
241 | final RedisCluster cluster = RedisCluster.builder().withSentinelBuilder(sentinelBuilder).ephemeral().sentinelCount(3).quorumSize(2)
242 | .replicationGroup(master1, 1)
243 | .replicationGroup(master2, 1)
244 | .replicationGroup(master3, 1)
245 | .build();
246 | cluster.start();
247 | final Set sentinelHosts = JedisUtil.sentinelHosts(cluster);
248 |
249 | //when
250 | JedisSentinelPool pool1 = null;
251 | JedisSentinelPool pool2 = null;
252 | JedisSentinelPool pool3 = null;
253 | Jedis jedis1 = null;
254 | Jedis jedis2 = null;
255 | Jedis jedis3 = null;
256 | try {
257 | pool1 = new JedisSentinelPool(master1, sentinelHosts);
258 | pool2 = new JedisSentinelPool(master2, sentinelHosts);
259 | pool3 = new JedisSentinelPool(master3, sentinelHosts);
260 | jedis1 = testPool(pool1);
261 | jedis2 = testPool(pool2);
262 | jedis3 = testPool(pool3);
263 | } finally {
264 | closeQuietly(jedis1, pool1, jedis2, pool2, jedis3, pool3);
265 | cluster.stop();
266 | }
267 | }
268 |
269 | private Jedis testPool(JedisSentinelPool pool) {
270 | Jedis jedis;
271 | jedis = pool.getResource();
272 | jedis.mset("abc", "1", "def", "2");
273 |
274 | //then
275 | assertEquals("1", jedis.mget("abc").get(0));
276 | assertEquals("2", jedis.mget("def").get(0));
277 | assertEquals(null, jedis.mget("xyz").get(0));
278 | return jedis;
279 | }
280 |
281 | private void closeQuietly(Closeable... closeables) {
282 | for (Closeable closeable : closeables) {
283 | IOUtils.closeQuietly(closeable, null);
284 | }
285 | }
286 |
287 | }
288 |
--------------------------------------------------------------------------------
/src/test/java/redis/embedded/RedisSentinelTest.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 |
9 | import java.io.BufferedReader;
10 | import java.io.IOException;
11 | import java.io.InputStreamReader;
12 | import java.net.Inet4Address;
13 | import java.util.concurrent.TimeUnit;
14 |
15 | import static org.junit.Assert.*;
16 |
17 | public class RedisSentinelTest {
18 | private String bindAddress;
19 |
20 | private RedisSentinel sentinel;
21 | private RedisServer server;
22 |
23 | @Before
24 | public void setup() throws Exception {
25 | // Jedis translates ”localhost” to getLocalHost().getHostAddress() (see Jedis HostAndPort#getLocalHostQuietly),
26 | // which can vary from 127.0.0.1 (most notably, Debian/Ubuntu return 127.0.1.1)
27 | if (bindAddress == null) {
28 | bindAddress = Inet4Address.getLocalHost().getHostAddress();
29 | }
30 | }
31 |
32 | @Test(timeout = 3000L)
33 | public void testSimpleRun() throws Exception {
34 | server = new RedisServer();
35 | sentinel = RedisSentinel.builder().bind(bindAddress).build();
36 | sentinel.start();
37 | server.start();
38 | TimeUnit.SECONDS.sleep(1);
39 | server.stop();
40 | sentinel.stop();
41 | }
42 |
43 | @Test
44 | public void shouldAllowSubsequentRuns() throws Exception {
45 | sentinel = RedisSentinel.builder().bind(bindAddress).build();
46 | sentinel.start();
47 | sentinel.stop();
48 |
49 | sentinel.start();
50 | sentinel.stop();
51 |
52 | sentinel.start();
53 | sentinel.stop();
54 | }
55 |
56 | @Test
57 | public void testSimpleOperationsAfterRun() throws Exception {
58 | //given
59 | server = new RedisServer();
60 | sentinel = RedisSentinel.builder().bind(bindAddress).build();
61 | server.start();
62 | sentinel.start();
63 | TimeUnit.SECONDS.sleep(1);
64 |
65 | //when
66 | JedisSentinelPool pool = null;
67 | Jedis jedis = null;
68 | try {
69 | pool = new JedisSentinelPool("mymaster", Sets.newHashSet("localhost:26379"));
70 | jedis = pool.getResource();
71 | jedis.mset("abc", "1", "def", "2");
72 |
73 | //then
74 | assertEquals("1", jedis.mget("abc").get(0));
75 | assertEquals("2", jedis.mget("def").get(0));
76 | assertNull(jedis.mget("xyz").get(0));
77 | } finally {
78 | if (jedis != null)
79 | pool.returnResource(jedis);
80 | sentinel.stop();
81 | server.stop();
82 | }
83 | }
84 |
85 | @Test
86 | public void testAwaitRedisSentinelReady() throws Exception {
87 | String readyPattern = RedisSentinel.builder().build().redisReadyPattern();
88 |
89 | assertReadyPattern(new BufferedReader(
90 | new InputStreamReader(getClass()
91 | .getClassLoader()
92 | .getResourceAsStream("redis-2.x-sentinel-startup-output.txt"))),
93 | readyPattern);
94 |
95 | assertReadyPattern(new BufferedReader(
96 | new InputStreamReader(getClass()
97 | .getClassLoader()
98 | .getResourceAsStream("redis-3.x-sentinel-startup-output.txt"))),
99 | readyPattern);
100 |
101 | assertReadyPattern(new BufferedReader(
102 | new InputStreamReader(getClass()
103 | .getClassLoader()
104 | .getResourceAsStream("redis-4.x-sentinel-startup-output.txt"))),
105 | readyPattern);
106 | }
107 |
108 | private void assertReadyPattern(BufferedReader reader, String readyPattern) throws IOException {
109 | String outputLine;
110 | do {
111 | outputLine = reader.readLine();
112 | assertNotNull(outputLine);
113 | } while (!outputLine.matches(readyPattern));
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/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-" + RedisExecProvider.redisVersion + "-linux-386").getFile())
101 | .override(OS.UNIX, Architecture.x86_64, Resources.getResource("redis-server-" + RedisExecProvider.redisVersion + "-linux-amd64").getFile())
102 | .override(OS.UNIX, Architecture.arm64, Resources.getResource("redis-server-" + RedisExecProvider.redisVersion + "-linux-arm64").getFile())
103 | .override(OS.MAC_OS_X, Architecture.x86_64, Resources.getResource("redis-server-" + RedisExecProvider.redisVersion + "-darwin-amd64").getFile())
104 | .override(OS.MAC_OS_X, Architecture.arm64, Resources.getResource("redis-server-" + RedisExecProvider.redisVersion + "-darwin-arm64").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.MAC_OS_X, "some");
116 |
117 | redisServer = new RedisServerBuilder()
118 | .redisExecProvider(buggyProvider)
119 | .build();
120 | }
121 |
122 | @Test
123 | public void testAwaitRedisServerReady() throws Exception {
124 | String readyPattern = RedisServer.builder().build().redisReadyPattern();
125 |
126 | assertReadyPattern(new BufferedReader(
127 | new InputStreamReader(getClass()
128 | .getClassLoader()
129 | .getResourceAsStream("redis-2.x-standalone-startup-output.txt"))),
130 | readyPattern);
131 |
132 | assertReadyPattern(new BufferedReader(
133 | new InputStreamReader(getClass()
134 | .getClassLoader()
135 | .getResourceAsStream("redis-3.x-standalone-startup-output.txt"))),
136 | readyPattern);
137 |
138 | assertReadyPattern(new BufferedReader(
139 | new InputStreamReader(getClass()
140 | .getClassLoader()
141 | .getResourceAsStream("redis-4.x-standalone-startup-output.txt"))),
142 | readyPattern);
143 |
144 | assertReadyPattern(new BufferedReader(
145 | new InputStreamReader(getClass()
146 | .getClassLoader()
147 | .getResourceAsStream("redis-6.x-standalone-startup-output.txt"))),
148 | readyPattern);
149 | }
150 |
151 | private void assertReadyPattern(BufferedReader reader, String readyPattern) throws IOException {
152 | String outputLine;
153 | do {
154 | outputLine = reader.readLine();
155 | assertNotNull(outputLine);
156 | } while (!outputLine.matches(readyPattern));
157 | }
158 | }
159 |
--------------------------------------------------------------------------------
/src/test/java/redis/embedded/RedisTlsTest.java:
--------------------------------------------------------------------------------
1 | package redis.embedded;
2 |
3 | import org.junit.Before;
4 | import org.junit.BeforeClass;
5 | import org.junit.Test;
6 | import redis.clients.jedis.Jedis;
7 | import redis.clients.jedis.JedisPool;
8 | import redis.embedded.util.JarUtil;
9 |
10 | import java.io.File;
11 | import java.io.FileInputStream;
12 | import java.io.FileOutputStream;
13 | import java.io.IOException;
14 | import java.security.KeyStore;
15 | import java.security.cert.Certificate;
16 | import java.security.cert.CertificateFactory;
17 |
18 | import static org.junit.Assert.assertEquals;
19 | import static org.junit.Assert.assertNull;
20 |
21 | public class RedisTlsTest {
22 |
23 | private RedisServer redisServer;
24 |
25 | private static File certFile;
26 | private static File keyFile;
27 | private static File caCertFile;
28 |
29 | @BeforeClass
30 | public static void setupClass() throws Exception {
31 | certFile = getFile("redis.crt");
32 | keyFile = getFile("redis.key");
33 | caCertFile = getFile("ca.crt");
34 |
35 | File trustStore = File.createTempFile("embedded-redis-test-truststore", ".jks");
36 | trustStore.deleteOnExit();
37 |
38 | Certificate cert;
39 | try (FileInputStream certInput = new FileInputStream(certFile)) {
40 | cert = CertificateFactory.getInstance("X.509").generateCertificate(certInput);
41 | }
42 |
43 | KeyStore keyStore = KeyStore.getInstance("jks");
44 | keyStore.load(null, null);
45 |
46 | keyStore.setCertificateEntry("testCaCert", cert);
47 | try (FileOutputStream out = new FileOutputStream(trustStore)) {
48 | keyStore.store(out, new char[0]);
49 | }
50 |
51 | System.setProperty("javax.net.ssl.trustStore", trustStore.getAbsolutePath());
52 | System.setProperty("javax.net.ssl.trustStoreType", "jks");
53 | }
54 |
55 | @Before
56 | public void setup() throws Exception {
57 |
58 | redisServer = RedisServer.builder()
59 | .port(0) // disable non-tls
60 | .tlsPort(6380)
61 | .setting("tls-cert-file " + certFile.getAbsolutePath())
62 | .setting("tls-key-file " + keyFile.getAbsolutePath())
63 | .setting("tls-ca-cert-file " + caCertFile.getAbsolutePath())
64 | .setting("tls-auth-clients no") // disable mTLS, for simplicity
65 | .build();
66 | }
67 |
68 |
69 | @Test(timeout = 1500L)
70 | public void testSimpleRun() throws Exception {
71 | redisServer.start();
72 | Thread.sleep(1000L);
73 | redisServer.stop();
74 | }
75 |
76 | @Test
77 | public void testSimpleOperationsAfterRun() {
78 | redisServer.start();
79 |
80 | try (JedisPool pool = new JedisPool("localhost", redisServer.tlsPorts().get(0), true);
81 | Jedis jedis = pool.getResource()) {
82 | jedis.mset("abc", "1", "def", "2");
83 |
84 | assertEquals("1", jedis.mget("abc").get(0));
85 | assertEquals("2", jedis.mget("def").get(0));
86 | assertNull(jedis.mget("xyz").get(0));
87 | } finally {
88 | redisServer.stop();
89 | }
90 | }
91 |
92 | private static File getFile(String path) throws IOException {
93 | return fileExists(path) ?
94 | new File(path) :
95 | JarUtil.extractFileFromJar(path);
96 | }
97 |
98 | private static boolean fileExists(String path) {
99 | return new File(path).exists();
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/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
--------------------------------------------------------------------------------
/src/test/resources/redis-6.x-standalone-startup-output.txt:
--------------------------------------------------------------------------------
1 | 124304:C 26 Jun 2020 12:23:30.115 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
2 | 124304:C 26 Jun 2020 12:23:30.115 # Redis version=6.0.5, bits=64, commit=00000000, modified=0, pid=124304, just started
3 | 124304:C 26 Jun 2020 12:23:30.115 # Warning: no config file specified, using the default config. In order to specify a config file use ./src/main/resources/redis-server-6.0.5 /path/to/redis.conf
4 | 124304:M 26 Jun 2020 12:23:30.116 * Increased maximum number of open files to 10032 (it was originally set to 1024).
5 | _._
6 | _.-``__ ''-._
7 | _.-`` `. `_. ''-._ Redis 6.0.5 (00000000/0) 64 bit
8 | .-`` .-```. ```\/ _.,_ ''-._
9 | ( ' , .-` | `, ) Running in standalone mode
10 | |`-._`-...-` __...-.``-._|'` _.-'| Port: 6379
11 | | `-._ `._ / _.-' | PID: 124304
12 | `-._ `-._ `-./ _.-' _.-'
13 | |`-._`-._ `-.__.-' _.-'_.-'|
14 | | `-._`-._ _.-'_.-' | http://redis.io
15 | `-._ `-._`-.__.-'_.-' _.-'
16 | |`-._`-._ `-.__.-' _.-'_.-'|
17 | | `-._`-._ _.-'_.-' |
18 | `-._ `-._`-.__.-'_.-' _.-'
19 | `-._ `-.__.-' _.-'
20 | `-._ _.-'
21 | `-.__.-'
22 |
23 | 124304:M 26 Jun 2020 12:23:30.116 # Server initialized
24 | 124304:M 26 Jun 2020 12:23:30.116 # 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.
25 | 124304:M 26 Jun 2020 12:23:30.116 # 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.
26 | 124304:M 26 Jun 2020 12:23:30.116 * Ready to accept connections
27 |
--------------------------------------------------------------------------------