From eb4d862d82812bf3231abc7807b33dfeb471a94f Mon Sep 17 00:00:00 2001 From: Alec Embke Date: Sun, 19 Dec 2021 16:09:28 -0800 Subject: [PATCH] 4.3.1 (#20) * fix: fix sentinel-auth feature, switch to circleci, update docs --- .circleci/Dockerfile.sentinel | 29 +++++++ .circleci/config.yml | 85 +++++++++++++++++++ .circleci/install_redis_cli.sh | 54 ++++++++++++ .circleci/sentinel-compose.yml | 25 ++++++ .gitignore | 3 +- .travis.yml | 44 ---------- CHANGELOG.md | 7 ++ Cargo.toml | 2 +- README.md | 32 +------ src/multiplexer/sentinel.rs | 31 +++++-- tests/README.md | 55 +++++++++++- tests/environ | 6 ++ tests/integration/acl/mod.rs | 46 ++++++++++ tests/integration/centralized.rs | 5 ++ tests/integration/utils.rs | 35 ++++++-- tests/run.sh | 3 - tests/run_all.sh | 22 ----- tests/run_sentinel.sh | 4 - tests/run_with_chaos_monkey.sh | 25 +++--- tests/runners/all-features.sh | 18 ++++ tests/runners/default-features.sh | 13 +++ tests/runners/everything.sh | 6 ++ tests/runners/no-features.sh | 13 +++ tests/runners/sentinel-features.sh | 13 +++ .../scripts/docker-install-redis-sentinel.sh | 11 ++- tests/scripts/full_install.sh | 22 +++++ tests/scripts/install_redis_centralized.sh | 40 +++++++-- tests/scripts/install_redis_clustered.sh | 14 +-- tests/scripts/stop_all_redis.sh | 22 +++++ tests/sentinel-docker-compose.yml | 14 +++ 30 files changed, 554 insertions(+), 145 deletions(-) create mode 100644 .circleci/Dockerfile.sentinel create mode 100644 .circleci/config.yml create mode 100755 .circleci/install_redis_cli.sh create mode 100644 .circleci/sentinel-compose.yml delete mode 100644 .travis.yml create mode 100644 tests/environ delete mode 100755 tests/run.sh delete mode 100755 tests/run_all.sh delete mode 100755 tests/run_sentinel.sh create mode 100755 tests/runners/all-features.sh create mode 100755 tests/runners/default-features.sh create mode 100755 tests/runners/everything.sh create mode 100755 tests/runners/no-features.sh create mode 100755 tests/runners/sentinel-features.sh create mode 100755 tests/scripts/full_install.sh create mode 100755 tests/scripts/stop_all_redis.sh diff --git a/.circleci/Dockerfile.sentinel b/.circleci/Dockerfile.sentinel new file mode 100644 index 00000000..a56ebfd6 --- /dev/null +++ b/.circleci/Dockerfile.sentinel @@ -0,0 +1,29 @@ +# Docker image to build and run the sentinel tests + +# This is required because CircleCI will run the sentinel nodes on a remote docker instance, +# and these containers cannot be accessed directly over the host container's network. Instead +# we build an image on the fly and run it in the same network space as the sentinel containers. + +# note: the top level target directory must be removed prior to running this + +FROM cimg/rust:1.57.0 +USER circleci + +ARG REDIS_VERSION +ARG REDIS_USERNAME +ARG REDIS_PASSWORD +ARG REDIS_SENTINEL_PASSWORD + +# try to make the new container look like the host +WORKDIR /home/circleci/project +COPY --chown=1001:1001 . /home/circleci/project + +RUN sudo apt-get update && sudo apt-get install -y libssl-dev dnsutils +RUN rm -rf ~/.cargo/git/* && rm -rf ~/.cargo/registry/cache/* +RUN echo "REDIS_VERSION=$REDIS_VERSION" +RUN .circleci/install_redis_cli.sh + +# For debugging +RUN cargo --version && rustc --version + +CMD tests/runners/sentinel-features.sh \ No newline at end of file diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 00000000..3b276e5f --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,85 @@ +version: 2 +jobs: + test-default: + docker: + - image: cimg/rust:1.57.0 + steps: + - checkout + - run: + name: Install OpenSSL + command: sudo apt-get update && sudo apt-get install -y libssl-dev + - run: + name: Install redis centralized + command: tests/scripts/install_redis_centralized.sh + - run: + name: Install redis cluster + command: tests/scripts/install_redis_clustered.sh + - run: + name: Clear the cargo git cache + command: rm -rf ~/.cargo/git/* && rm -rf ~/.cargo/registry/cache/* + - run: + name: Run tests with default features + command: cargo clean && eval `ssh-agent` && ssh-add ~/.ssh/id_rsa && tests/runners/default-features.sh + test-no-features: + docker: + - image: cimg/rust:1.57.0 + steps: + - checkout + - run: + name: Install OpenSSL + command: sudo apt-get update && sudo apt-get install -y libssl-dev + - run: + name: Install redis centralized + command: tests/scripts/install_redis_centralized.sh + - run: + name: Install redis cluster + command: tests/scripts/install_redis_clustered.sh + - run: + name: Clear the cargo git cache + command: rm -rf ~/.cargo/git/* && rm -rf ~/.cargo/registry/cache/* + - run: + name: Run tests with no features + command: cargo clean && eval `ssh-agent` && ssh-add ~/.ssh/id_rsa && tests/runners/no-features.sh + test-all-features: + docker: + - image: cimg/rust:1.57.0 + steps: + - checkout + - run: + name: Install redis centralized + command: tests/scripts/install_redis_centralized.sh + - run: + name: Install redis cluster + command: tests/scripts/install_redis_clustered.sh + - run: + name: Clear the cargo git cache + command: rm -rf ~/.cargo/git/* && rm -rf ~/.cargo/registry/cache/* + - run: + name: Run tests with all features + command: cargo clean && eval `ssh-agent` && ssh-add ~/.ssh/id_rsa && tests/runners/all-features.sh + test-sentinel: + docker: + - image: cimg/rust:1.57.0 + steps: + - checkout + - run: + name: Install Docker Compose + environment: + COMPOSE_VERSION: '1.29.2' + command: | + curl -L "https://github.com/docker/compose/releases/download/${COMPOSE_VERSION}/docker-compose-$(uname -s)-$(uname -m)" -o ~/docker-compose + chmod +x ~/docker-compose + sudo mv ~/docker-compose /usr/local/bin/docker-compose + - setup_remote_docker + - run: + name: Install and run sentinel tests + command: docker-compose -f tests/sentinel-docker-compose.yml -f .circleci/sentinel-compose.yml run sentinel-tests + +workflows: + version: 2 + build: + jobs: + - test-default + - test-all-features + - test-no-features + - test-sentinel \ No newline at end of file diff --git a/.circleci/install_redis_cli.sh b/.circleci/install_redis_cli.sh new file mode 100755 index 00000000..c4dd6fb1 --- /dev/null +++ b/.circleci/install_redis_cli.sh @@ -0,0 +1,54 @@ +#!/bin/bash + +function check_root_dir { + if [ ! -d "./tests/tmp" ]; then + echo "Must be in application root for redis installation scripts to work." + exit 1 + fi +} + +declare -a arr=("REDIS_VERSION") + +for env in "${arr[@]}" +do + if [ -z "$env" ]; then + echo "$env must be set. Run `source tests/environ` if needed." + exit 1 + fi +done + +ROOT=$PWD +[[ -z "${JOBS}" ]] && PARALLEL_JOBS='2' || PARALLEL_JOBS="${JOBS}" + +# Returns 0 if not installed, 1 otherwise. +function check_redis { + if [ -d "$ROOT/tests/tmp/redis_$REDIS_VERSION/redis-$REDIS_VERSION" ]; then + echo "Skipping redis install." + return 1 + else + echo "Redis install not found." + return 0 + fi +} + +function install_redis { + echo "Installing redis..." + pushd $ROOT > /dev/null + rm -rf tests/tmp/redis_cluster_$REDIS_VERSION + cd tests/tmp + curl -O http://download.redis.io/releases/redis-$REDIS_VERSION.tar.gz + mkdir redis_$REDIS_VERSION + tar xf redis-$REDIS_VERSION.tar.gz -C redis_$REDIS_VERSION + rm redis-$REDIS_VERSION.tar.gz + cd redis_$REDIS_VERSION/redis-$REDIS_VERSION + make -j"${PARALLEL_JOBS}" + popd > /dev/null +} + +check_root_dir +check_redis +if [[ "$?" -eq 0 ]]; then + install_redis +fi + +echo "Finished installing centralized redis server." \ No newline at end of file diff --git a/.circleci/sentinel-compose.yml b/.circleci/sentinel-compose.yml new file mode 100644 index 00000000..f43b4cbb --- /dev/null +++ b/.circleci/sentinel-compose.yml @@ -0,0 +1,25 @@ +version: '2' + +services: + sentinel-tests: + depends_on: + - redis-main + - redis-replica + - redis-sentinel-3 + container_name: "sentinel-tests" + build: + context: .. + dockerfile: .circleci/Dockerfile.sentinel + args: + REDIS_VERSION: "${REDIS_VERSION}" + REDIS_USERNAME: "${REDIS_USERNAME}" + REDIS_PASSWORD: "${REDIS_PASSWORD}" + REDIS_SENTINEL_PASSWORD: "${REDIS_SENTINEL_PASSWORD}" + networks: + - app-tier + environment: + CIRCLECI_TESTS: "1" + REDIS_VERSION: "${REDIS_VERSION}" + REDIS_USERNAME: "${REDIS_USERNAME}" + REDIS_PASSWORD: "${REDIS_PASSWORD}" + REDIS_SENTINEL_PASSWORD: "${REDIS_SENTINEL_PASSWORD}" \ No newline at end of file diff --git a/.gitignore b/.gitignore index 94ce805a..772c0d42 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ Cargo.lock tests/tmp/* !tests/tmp/.gitkeep dump.rdb -appendonly.aof \ No newline at end of file +appendonly.aof +test_users.acl \ No newline at end of file diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 038fe69b..00000000 --- a/.travis.yml +++ /dev/null @@ -1,44 +0,0 @@ -language: rust -services: - - docker -rust: - - stable - - beta - - nightly -addons: - apt: - packages: - - binutils-dev - - build-essential - - expect - - pkg-config - - zlib1g-dev - - libssh2-1-dev - - libssl-dev -cache: - apt: true - directories: - - tests/tmp - - target/debug/deps - - target/debug/build - - target/release/deps -notifications: - email: - on_success: never - on_failure: never -branches: - only: - - main - - ci -env: - - REDIS_VERSION=6.2.2 -before_script: - - ./tests/scripts/install_redis_centralized.sh - - ./tests/scripts/install_redis_clustered.sh - - ./tests/scripts/docker-install-redis-sentinel.sh -jobs: - allow_failures: - - rust: nightly - fast_finish: true -script: - - ./tests/run_all.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index 945798e8..5af59217 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## 4.3.1 + +* Fix authentication bug with `sentinel-auth` tests +* Update tests and CI config for `sentinel-auth` feature +* Add more testing scripts, update docs +* Switch to CircleCI + ## 4.3.0 * Add `sentinel-auth` feature diff --git a/Cargo.toml b/Cargo.toml index e3418894..90e857f2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "fred" -version = "4.3.0" +version = "4.3.1" authors = ["Alec Embke "] edition = "2018" description = "An async Redis client for Rust built on Futures and Tokio." diff --git a/README.md b/README.md index 66bf8449..b221c5f2 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ Fred [![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://opensource.org/licenses/MIT) [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) -[![Build Status](https://travis-ci.com/aembke/fred.rs.svg?branch=main)](https://travis-ci.com/aembke/fred.rs) +[![CircleCI](https://circleci.com/gh/aembke/fred.rs/tree/main.svg?style=svg)](https://circleci.com/gh/aembke/fred.rs/tree/main) [![Crates.io](https://img.shields.io/crates/v/fred.svg)](https://crates.io/crates/fred) [![API docs](https://docs.rs/fred/badge.svg)](https://docs.rs/fred) @@ -151,35 +151,7 @@ Errors that trigger this can be seen with the [on_error](https://docs.rs/fred/*/ ## Tests -To run the unit and integration tests: - -``` -cargo test -- --test-threads=1 -``` - -OR - -``` -# run the tests with default features -./tests/run.sh -``` - -OR - -``` -# run the tests 3 times, once with default features, once with no features, and once with all features (except chaos monkey) -./tests/run_all.sh -``` - -Note: a local Redis server must be running on port 6379, and a clustered deployment must be running on ports 30001 - 30006 for the integration tests to pass. - -Scripts are included to download and run centralized and clustered redis servers at any Redis version. These scripts will not make any modifications to your system outside the `tests/tmp` folder. - -``` -export REDIS_VERSION=6.2.2 -./tests/scripts/install_redis_centralized.sh -./tests/scripts/install_redis_clustered.sh -``` +See the [testing documentation](./tests/README.md) for more information. **Beware: the tests will periodically run `flushall`.** diff --git a/src/multiplexer/sentinel.rs b/src/multiplexer/sentinel.rs index 017538f6..d36d4fd2 100644 --- a/src/multiplexer/sentinel.rs +++ b/src/multiplexer/sentinel.rs @@ -72,17 +72,27 @@ fn read_sentinel_auth(inner: &Arc) -> Result<(Option, Ok((guard.username.clone(), guard.password.clone())) } +fn read_redis_auth(inner: &Arc) -> (Option, Option) { + let guard = inner.config.read(); + (guard.username.clone(), guard.password.clone()) +} + // TODO clean this up in the next major release by breaking up the connection functions #[cfg(feature = "enable-tls")] pub async fn create_authenticated_connection_tls( addr: &SocketAddr, domain: &str, inner: &Arc, + is_sentinel: bool, ) -> Result { let server = format!("{}:{}", addr.ip().to_string(), addr.port()); let codec = RedisCodec::new(inner, server); let client_name = inner.client_name(); - let (username, password) = read_sentinel_auth(inner)?; + let (username, password) = if is_sentinel { + read_sentinel_auth(inner)? + } else { + read_redis_auth(inner) + }; let socket = TcpStream::connect(addr).await?; let tls_stream = tls::create_tls_connector(&inner.config)?; @@ -97,18 +107,24 @@ pub(crate) async fn create_authenticated_connection_tls( addr: &SocketAddr, _domain: &str, inner: &Arc, + is_sentinel: bool, ) -> Result { - create_authenticated_connection(addr, inner).await + create_authenticated_connection(addr, inner, is_sentinel).await } pub async fn create_authenticated_connection( addr: &SocketAddr, inner: &Arc, + is_sentinel: bool, ) -> Result { let server = format!("{}:{}", addr.ip().to_string(), addr.port()); let codec = RedisCodec::new(inner, server); let client_name = inner.client_name(); - let (username, password) = read_sentinel_auth(inner)?; + let (username, password) = if is_sentinel { + read_sentinel_auth(inner)? + } else { + read_redis_auth(inner) + }; let socket = TcpStream::connect(addr).await?; let framed = authenticate(Framed::new(socket, codec), &client_name, username, password).await?; @@ -121,16 +137,17 @@ async fn connect_to_server( host: &str, addr: &SocketAddr, timeout: u64, + is_sentinel: bool, ) -> Result { let uses_tls = inner.config.read().uses_tls(); let transport = if uses_tls { - let transport_ft = create_authenticated_connection_tls(addr, host, inner); + let transport_ft = create_authenticated_connection_tls(addr, host, inner, is_sentinel); let transport = stry!(client_utils::apply_timeout(transport_ft, timeout).await); RedisTransport::Tls(transport) } else { - let transport_ft = create_authenticated_connection(addr, inner); + let transport_ft = create_authenticated_connection(addr, inner, is_sentinel); let transport = stry!(client_utils::apply_timeout(transport_ft, timeout).await); RedisTransport::Tcp(transport) @@ -173,7 +190,7 @@ pub async fn connect_to_sentinel( ) -> Result { let sentinel_addr = inner.resolver.resolve(host.to_owned(), port).await?; _debug!(inner, "Connecting to sentinel {}", sentinel_addr); - connect_to_server(inner, host, &sentinel_addr, timeout).await + connect_to_server(inner, host, &sentinel_addr, timeout, true).await } async fn discover_primary_node( @@ -210,7 +227,7 @@ async fn connect_and_check_primary_role( addr: &SocketAddr, ) -> Result { let request = RedisCommand::new(RedisCommandKind::Role, vec![], None); - let transport = stry!(connect_to_server(inner, host, addr, DEFAULT_CONNECTION_TIMEOUT_MS).await); + let transport = stry!(connect_to_server(inner, host, addr, DEFAULT_CONNECTION_TIMEOUT_MS, false).await); _debug!(inner, "Checking role for redis server at {}:{}", host, addr.port()); let (frame, transport) = stry!(connection::transport_request_response(transport, &request).await); diff --git a/tests/README.md b/tests/README.md index f03edccd..504fda9b 100644 --- a/tests/README.md +++ b/tests/README.md @@ -8,12 +8,51 @@ By default, most tests run 4 times based on the following configuration paramete ## Installation -Redis installation scripts exist to install Redis at any version. Use the env variable `REDIS_VERSION` to configure this. These scripts will only modify the contents of the [tests/tmp](../tests/tmp) folder. +The [environ](environ) file contains any environment variables that might be needed. **This should be loaded before installing or running any tests, unless otherwise set manually.** + +In order to run the installation scripts the following must be installed: + +* Bash (all the scripts assume `bash`) +* `build-essential` (or equivalent) +* OpenSSL (`libssl-dev`) for testing TLS features without vendored OpenSSL dependencies +* `docker` +* `docker-compose` (this may come with `docker` depending on the version you use) +* `expect` + +### Fresh Installation + +A script is included to start a fresh install, but **it will shut down any local redis processes first, including those from docker.** + +``` +source tests/environ +./tests/scripts/full_install.sh +``` + +### Custom Installation + +Redis installation scripts exist to install Redis at any version. Use the env variable `REDIS_VERSION` to configure this, either setting manually **after** sourcing the [environ](environ), or by changing this file. These scripts will only modify the contents of the [tests/tmp](../tests/tmp) folder. * [Install Centralized](scripts/install_redis_centralized.sh) will download, install, and start a centralized server on port 6379. * [Install Clustered](scripts/install_redis_clustered.sh) will download, install, and start a clustered deployment on ports 30001-30006. +* [Install Sentinel](scripts/docker-install-redis-sentinel.sh) will download, install, and start a sentinel deployment with docker-compose. + +The tests assume that redis servers are running on the above ports. The installation scripts will modify ACL rules, so installing redis via other means may not work with the tests as they're currently written unless users manually set up the test users as well. -The tests assume that redis servers are running on the above ports. +## Running Tests + +Once the required local Redis servers are installed users can run tests with the [runner](runners) scripts. + +* [all-features](runners/all-features.sh) will run tests with all features (except chaos monkey and sentinel tests). +* [default-features](runners/default-features.sh) will run tests with default features (except sentinel tests). +* [no-features](runners/no-features.sh) will run the tests without any of the feature flags. +* [sentinel-features](runners/sentinel-features.sh) will run the centralized tests against a sentinel deployment. This is the only test runner that requires the sentinel deployment via docker-compose. +* [everything](runners/everything.sh) will run all of the above scripts. + +These scripts will pass through any extra argv so callers can filter tests as needed. + +See the [CI configuration](../.circleci/config.yml) for more information. + +Note: the [stop redis script](scripts/stop_all_redis.sh) can stop all local Redis servers, including those started via docker. ## Adding Tests @@ -25,7 +64,7 @@ Note: When writing tests that operate on multiple keys be sure to use a [hash_ta 2. Create a new async function in the appropriate file. This function should take a `RedisClient` and `RedisConfig` as arguments and should return a `Result<(), RedisError>`. The client will already be connected when this function runs. 3. This new function should **not** be marked as a `#[test]` or `#[tokio::test]` 4. Call the test from the appropriate [integration/cluster.rs](integration/cluster.rs) or [integration/centralized.rs](integration/centralized.rs) files, or both. Create a wrapping `mod` block with the same name as the test's folder if necessary. -5. Use `centralized_test!` or `cluster_test!` to generate tests in the appropriate module. +5. Use `centralized_test!` or `cluster_test!` to generate tests in the appropriate module. Centralized tests will be automatically converted to sentinel tests if using the sentinel testing features. Tests that use this pattern will run 4 times to check the functionality against a clustered and centralized redis servers using both pipelined and non-pipelined clients. @@ -37,9 +76,17 @@ To run the tests with the chaos monkey process use [run_with_chaos_monkey.sh](./ ## Notes -* Since we're mutating shared state in external redis servers with these tests it's necessary to run the tests with `--test-threads=1`. The [run.sh](run.sh) script will do this automatically. +* Since we're mutating shared state in external redis servers with these tests it's necessary to run the tests with `--test-threads=1`. The test runner scripts will do this automatically. * **The tests will periodically call `flushall` before each test iteration.** +If you're not running any other docker containers locally, and you feel like your docker containers aren't updating, try a fresh install... + +``` +# THIS WILL DESTROY ANY LOCAL DOCKER CONTAINER STATE +docker container kill $(docker ps -q) +docker container rm $(docker ps -aq) +``` + ## Contributing The following modules still need better test coverage: diff --git a/tests/environ b/tests/environ new file mode 100644 index 00000000..d4cbb635 --- /dev/null +++ b/tests/environ @@ -0,0 +1,6 @@ + +export RUST_BACKTRACE=full \ + REDIS_VERSION=6.2.2 \ + REDIS_USERNAME=foo \ + REDIS_PASSWORD=bar \ + REDIS_SENTINEL_PASSWORD=baz \ No newline at end of file diff --git a/tests/integration/acl/mod.rs b/tests/integration/acl/mod.rs index e69de29b..d2e5ebbe 100644 --- a/tests/integration/acl/mod.rs +++ b/tests/integration/acl/mod.rs @@ -0,0 +1,46 @@ +use fred::client::RedisClient; +use fred::error::RedisError; +use fred::types::RedisConfig; +use super::utils::read_env_var; + +// the docker image we use for sentinel tests doesn't allow for configuring users, just passwords, +// so for the tests here we just use an empty username so it uses the `default` user +#[cfg(feature = "sentinel-tests")] +fn read_redis_username() -> Option { + None +} + +#[cfg(not(feature = "sentinel-tests"))] +fn read_redis_username() -> Option { + read_env_var("REDIS_USERNAME") +} + +fn check_env_creds() -> (Option, Option) { + (read_redis_username(), read_env_var("REDIS_PASSWORD")) +} + +// note: currently this only works in CI against the centralized server +pub async fn should_auth_as_test_user(client: RedisClient, _: RedisConfig) -> Result<(), RedisError> { + let (username, password) = check_env_creds(); + if let Some(password) = password { + let _ = client.auth(username, password).await?; + let _: () = client.get("foo").await?; + } + + Ok(()) +} + +// note: currently this only works in CI against the centralized server +pub async fn should_auth_as_test_user_via_config(_: RedisClient, mut config: RedisConfig) -> Result<(), RedisError> { + let (username, password) = check_env_creds(); + if let Some(password) = password { + config.username = username; + config.password = Some(password); + let client = RedisClient::new(config); + let _ = client.connect(None); + let _ = client.wait_for_connect().await?; + let _: () = client.get("foo").await?; + } + + Ok(()) +} \ No newline at end of file diff --git a/tests/integration/centralized.rs b/tests/integration/centralized.rs index c19c3ad4..2349322d 100644 --- a/tests/integration/centralized.rs +++ b/tests/integration/centralized.rs @@ -215,3 +215,8 @@ pub mod geo { centralized_test!(geo, should_georadiusbymember_values); centralized_test!(geo, should_geosearch_values); } + +pub mod acl { + centralized_test!(acl, should_auth_as_test_user); + centralized_test!(acl, should_auth_as_test_user_via_config); +} \ No newline at end of file diff --git a/tests/integration/utils.rs b/tests/integration/utils.rs index 9dc2ff66..ab204437 100644 --- a/tests/integration/utils.rs +++ b/tests/integration/utils.rs @@ -13,6 +13,10 @@ const RECONNECT_DELAY: u32 = 500; #[cfg(not(feature = "chaos-monkey"))] const RECONNECT_DELAY: u32 = 1000; +pub fn read_env_var(name: &str) -> Option { + env::var_os(name).and_then(|s| s.into_string().ok()) +} + fn read_fail_fast_env() -> bool { match env::var_os("FRED_FAIL_FAST") { Some(s) => match s.into_string() { @@ -26,6 +30,25 @@ fn read_fail_fast_env() -> bool { } } +#[cfg(feature = "sentinel-auth")] +fn read_redis_password() -> String { + read_env_var("REDIS_PASSWORD").expect("Failed to read REDIS_PASSWORD env") +} + +#[cfg(feature = "sentinel-auth")] +fn read_sentinel_password() -> String { + read_env_var("REDIS_SENTINEL_PASSWORD").expect("Failed to read REDIS_SENTINEL_PASSWORD env") +} + +#[cfg(feature = "sentinel-tests")] +fn read_sentinel_hostname() -> String { + if read_env_var("CIRCLECI_TESTS").is_some() { + "redis-sentinel-1".to_owned() + }else{ + "127.0.0.1".to_owned() + } +} + #[cfg(all(feature = "sentinel-tests", not(feature = "chaos-monkey")))] pub async fn run_sentinel(func: F, pipeline: bool) where @@ -39,17 +62,17 @@ where fail_fast: read_fail_fast_env(), server: ServerConfig::Sentinel { hosts: vec![ - ("127.0.0.1".into(), 26379), - ("127.0.0.1".into(), 26380), - ("127.0.0.1".into(), 26381), + (read_sentinel_hostname(), 26379), + (read_sentinel_hostname(), 26380), + (read_sentinel_hostname(), 26381), ], service_name: "redis-sentinel-main".into(), - #[cfg(feature = "sentinel-auth")] + // TODO fix this so sentinel-tests can run without sentinel-auth username: None, - #[cfg(feature = "sentinel-auth")] - password: None, + password: Some(read_sentinel_password()), }, pipeline, + password: Some(read_redis_password()), ..Default::default() }; let client = RedisClient::new(config.clone()); diff --git a/tests/run.sh b/tests/run.sh deleted file mode 100755 index 3e39d6c1..00000000 --- a/tests/run.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash - -cargo test --release --lib --tests -- --test-threads=1 -- "$@" \ No newline at end of file diff --git a/tests/run_all.sh b/tests/run_all.sh deleted file mode 100755 index 79ddf5c9..00000000 --- a/tests/run_all.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/bash - -export RUST_BACKTRACE=full - -echo "Testing with default features..." -cargo test --release --lib --tests -- --test-threads=1 - -echo "Testing with no features..." -cargo test --release --lib --tests --no-default-features -- --test-threads=1 - -echo "Testing with all features..." -# cant use all-features here or it'll run chaos monkey and then the tests will take forever -cargo test --release --lib --tests --features \ - "index-map network-logs pool-prefer-active enable-tls vendored-tls - custom-reconnect-errors ignore-auth-error blocking-encoding full-tracing - reconnect-on-auth-error monitor metrics sentinel-client" \ - -- --test-threads=1 - -echo "Testing with sentinel interface..." -cargo test --release --features "sentinel-tests" --lib --tests -- --test-threads=1 -echo "Testing with sentinel interface and sentinel auth..." -cargo test --release --features "sentinel-tests sentinel-auth" --lib --tests -- --test-threads=1 \ No newline at end of file diff --git a/tests/run_sentinel.sh b/tests/run_sentinel.sh deleted file mode 100755 index 8abee01f..00000000 --- a/tests/run_sentinel.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash - -cargo test --release --features "sentinel-tests" --lib --tests -- --test-threads=1 -- "$@" -cargo test --release --features "sentinel-tests sentinel-auth" --lib --tests -- --test-threads=1 -- "$@" \ No newline at end of file diff --git a/tests/run_with_chaos_monkey.sh b/tests/run_with_chaos_monkey.sh index a43adc7e..1d33d9d7 100755 --- a/tests/run_with_chaos_monkey.sh +++ b/tests/run_with_chaos_monkey.sh @@ -3,19 +3,24 @@ ROOT=$PWD . $ROOT/tests/chaos_monkey/util.sh -if [ -z "$REDIS_VERSION" ]; then - echo "REDIS_VERSION must be set!" +declare -a arr=("REDIS_VERSION" "REDIS_USERNAME" "REDIS_PASSWORD" "REDIS_SENTINEL_PASSWORD") + +for env in "${arr[@]}" +do + if [ -z "$env" ]; then + echo "$env must be set. Run `source tests/environ` if needed." exit 1 -fi + fi +done check_root_dir check_redis ROOT="$ROOT" \ - FRED_FAIL_FAST=false \ - REDIS_ROOT_DIR="$ROOT/tests/tmp/redis_$REDIS_VERSION/redis-$REDIS_VERSION" \ - REDIS_CLI_PATH="$REDIS_ROOT_DIR/src/redis-cli" \ - REDIS_SERVER_PATH="$REDIS_ROOT_DIR/src/redis-server" \ - CREATE_CLUSTER_PATH="$REDIS_ROOT_DIR/utils/create-cluster/create-cluster" \ - cargo test --release --features "chaos-monkey custom-reconnect-errors network-logs" \ - --lib --tests -- --test-threads=1 -- "$@" \ No newline at end of file + FRED_FAIL_FAST=false \ + REDIS_ROOT_DIR="$ROOT/tests/tmp/redis_$REDIS_VERSION/redis-$REDIS_VERSION" \ + REDIS_CLI_PATH="$REDIS_ROOT_DIR/src/redis-cli" \ + REDIS_SERVER_PATH="$REDIS_ROOT_DIR/src/redis-server" \ + CREATE_CLUSTER_PATH="$REDIS_ROOT_DIR/utils/create-cluster/create-cluster" \ + cargo test --release --features "chaos-monkey custom-reconnect-errors network-logs" \ + --lib --tests -- --test-threads=1 -- "$@" \ No newline at end of file diff --git a/tests/runners/all-features.sh b/tests/runners/all-features.sh new file mode 100755 index 00000000..724f821b --- /dev/null +++ b/tests/runners/all-features.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +declare -a arr=("REDIS_VERSION" "REDIS_USERNAME" "REDIS_PASSWORD" "REDIS_SENTINEL_PASSWORD") + +for env in "${arr[@]}" +do + if [ -z "$env" ]; then + echo "$env must be set. Run `source tests/environ` if needed." + exit 1 + fi +done + +# cant use all-features here or it'll run chaos monkey and then the tests will take forever +cargo test --release --lib --tests --features \ + "index-map network-logs pool-prefer-active enable-tls vendored-tls + custom-reconnect-errors ignore-auth-error blocking-encoding full-tracing + reconnect-on-auth-error monitor metrics sentinel-client" \ + -- --test-threads=1 "$@" \ No newline at end of file diff --git a/tests/runners/default-features.sh b/tests/runners/default-features.sh new file mode 100755 index 00000000..d7c00725 --- /dev/null +++ b/tests/runners/default-features.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +declare -a arr=("REDIS_VERSION" "REDIS_USERNAME" "REDIS_PASSWORD" "REDIS_SENTINEL_PASSWORD") + +for env in "${arr[@]}" +do + if [ -z "$env" ]; then + echo "$env must be set. Run `source tests/environ` if needed." + exit 1 + fi +done + +cargo test --release --lib --tests -- --test-threads=1 "$@" \ No newline at end of file diff --git a/tests/runners/everything.sh b/tests/runners/everything.sh new file mode 100755 index 00000000..dbabc571 --- /dev/null +++ b/tests/runners/everything.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +./tests/runners/default-features.sh +./tests/runners/no-features.sh +./tests/runners/all-features.sh +./tests/runners/sentinel-features.sh \ No newline at end of file diff --git a/tests/runners/no-features.sh b/tests/runners/no-features.sh new file mode 100755 index 00000000..21cb11ec --- /dev/null +++ b/tests/runners/no-features.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +declare -a arr=("REDIS_VERSION" "REDIS_USERNAME" "REDIS_PASSWORD" "REDIS_SENTINEL_PASSWORD") + +for env in "${arr[@]}" +do + if [ -z "$env" ]; then + echo "$env must be set. Run `source tests/environ` if needed." + exit 1 + fi +done + +cargo test --release --lib --tests --no-default-features -- --test-threads=1 "$@" \ No newline at end of file diff --git a/tests/runners/sentinel-features.sh b/tests/runners/sentinel-features.sh new file mode 100755 index 00000000..2b1744c3 --- /dev/null +++ b/tests/runners/sentinel-features.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +declare -a arr=("REDIS_VERSION" "REDIS_USERNAME" "REDIS_PASSWORD" "REDIS_SENTINEL_PASSWORD") + +for env in "${arr[@]}" +do + if [ -z "$env" ]; then + echo "$env must be set. Run `source tests/environ` if needed." + exit 1 + fi +done + +cargo test --release --features "network-logs sentinel-tests sentinel-auth" --lib --tests -- --test-threads=1 "$@" \ No newline at end of file diff --git a/tests/scripts/docker-install-redis-sentinel.sh b/tests/scripts/docker-install-redis-sentinel.sh index f024e86f..c0ca2888 100755 --- a/tests/scripts/docker-install-redis-sentinel.sh +++ b/tests/scripts/docker-install-redis-sentinel.sh @@ -5,10 +5,15 @@ if [ ! -d "./tests/tmp" ]; then exit 1 fi -if [ -z "$REDIS_VERSION" ]; then - echo "REDIS_VERSION must be set!" +declare -a arr=("REDIS_VERSION" "REDIS_USERNAME" "REDIS_PASSWORD" "REDIS_SENTINEL_PASSWORD") + +for env in "${arr[@]}" +do + if [ -z "$env" ]; then + echo "$env must be set. Run `source tests/environ` if needed." exit 1 -fi + fi +done echo "Note: this requires docker, docker-compose, and redis >=6.2 to work reliably." docker-compose -f ./tests/sentinel-docker-compose.yml up -d diff --git a/tests/scripts/full_install.sh b/tests/scripts/full_install.sh new file mode 100755 index 00000000..66499d4a --- /dev/null +++ b/tests/scripts/full_install.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +declare -a arr=("REDIS_VERSION" "REDIS_USERNAME" "REDIS_PASSWORD" "REDIS_SENTINEL_PASSWORD") + +for env in "${arr[@]}" +do + if [ -z "$env" ]; then + echo "$env must be set. Run `source tests/environ` if needed." + exit 1 + fi +done + +if [ ! -d "./tests/tmp" ]; then + echo "Must be in application root for installation script to work." + exit 1 +fi + +./tests/scripts/stop_all_redis.sh +rm -rf ./tests/tmp/redis* +./tests/scripts/install_redis_centralized.sh +./tests/scripts/install_redis_clustered.sh +./tests/scripts/docker-install-redis-sentinel.sh \ No newline at end of file diff --git a/tests/scripts/install_redis_centralized.sh b/tests/scripts/install_redis_centralized.sh index 2229d86d..193bc87f 100755 --- a/tests/scripts/install_redis_centralized.sh +++ b/tests/scripts/install_redis_centralized.sh @@ -7,10 +7,15 @@ function check_root_dir { fi } -if [ -z "$REDIS_VERSION" ]; then - echo "REDIS_VERSION must be set!" +declare -a arr=("REDIS_VERSION" "REDIS_USERNAME" "REDIS_PASSWORD" "REDIS_SENTINEL_PASSWORD") + +for env in "${arr[@]}" +do + if [ -z "$env" ]; then + echo "$env must be set. Run `source tests/environ` if needed." exit 1 -fi + fi +done ROOT=$PWD [[ -z "${JOBS}" ]] && PARALLEL_JOBS='2' || PARALLEL_JOBS="${JOBS}" @@ -37,6 +42,25 @@ function install_redis { rm redis-$REDIS_VERSION.tar.gz cd redis_$REDIS_VERSION/redis-$REDIS_VERSION make -j"${PARALLEL_JOBS}" + mv redis.conf redis.conf.bk + popd > /dev/null +} + +function configure_acl { + if [ -z "$REDIS_USERNAME" ]; then + echo "Skipping ACL setup due to missing REDIS_USERNAME..." + return + fi + if [ -z "$REDIS_PASSWORD" ]; then + echo "Skipping ACL setup due to missing REDIS_PASSWORD..." + return + fi + + echo "Configuring ACL rules..." + pushd $ROOT > /dev/null + cd tests/tmp/redis_$REDIS_VERSION/redis-$REDIS_VERSION + echo "user $REDIS_USERNAME on allkeys allcommands allchannels >$REDIS_PASSWORD" > ./test_users.acl + echo "aclfile `pwd`/test_users.acl" > ./redis_centralized.conf popd > /dev/null } @@ -49,8 +73,13 @@ function start_server { kill -9 `cat ./redis_server.pid` fi - echo "Starting server..." - nohup ./src/redis-server > ./centralized_server.log 2>&1 & + if [ -f "./redis_centralized.conf" ]; then + echo "Starting server with config file..." + nohup ./src/redis-server ./redis_centralized.conf > ./centralized_server.log 2>&1 & + else + echo "Starting server without config file..." + nohup ./src/redis-server > ./centralized_server.log 2>&1 & + fi echo $! > ./redis_server.pid echo "Redis server PID is `cat redis_server.pid`" popd > /dev/null @@ -61,6 +90,7 @@ check_redis if [[ "$?" -eq 0 ]]; then install_redis fi +configure_acl start_server echo "Finished installing centralized redis server." \ No newline at end of file diff --git a/tests/scripts/install_redis_clustered.sh b/tests/scripts/install_redis_clustered.sh index 68af2aef..1390d51f 100755 --- a/tests/scripts/install_redis_clustered.sh +++ b/tests/scripts/install_redis_clustered.sh @@ -7,10 +7,15 @@ function check_root_dir { fi } -if [ -z "$REDIS_VERSION" ]; then - echo "REDIS_VERSION must be set!" +declare -a arr=("REDIS_VERSION" "REDIS_USERNAME" "REDIS_PASSWORD" "REDIS_SENTINEL_PASSWORD") + +for env in "${arr[@]}" +do + if [ -z "$env" ]; then + echo "$env must be set. Run `source tests/environ` if needed." exit 1 -fi + fi +done ROOT=$PWD [[ -z "${JOBS}" ]] && PARALLEL_JOBS='2' || PARALLEL_JOBS="${JOBS}" @@ -47,8 +52,7 @@ function start_cluster { ./create-cluster stop ./create-cluster clean ./create-cluster start - cp $ROOT/tests/scripts/start_cluster.exp ./ - ./start_cluster.exp + ./create-cluster create -f popd > /dev/null } diff --git a/tests/scripts/stop_all_redis.sh b/tests/scripts/stop_all_redis.sh new file mode 100755 index 00000000..a5ca89d9 --- /dev/null +++ b/tests/scripts/stop_all_redis.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +if [ ! -d "./tests/tmp" ]; then + echo "Must be in application root for redis installation scripts to work." + exit 1 +fi + +declare -a arr=("REDIS_VERSION" "REDIS_USERNAME" "REDIS_PASSWORD" "REDIS_SENTINEL_PASSWORD") + +for env in "${arr[@]}" +do + if [ -z "$env" ]; then + echo "$env must be set. Run `source tests/environ` if needed." + exit 1 + fi +done + +echo "Stopping redis processes..." +pgrep redis | sudo xargs kill -9 + +echo "Stopping sentinel redis in docker..." +docker-compose -f ./tests/sentinel-docker-compose.yml stop \ No newline at end of file diff --git a/tests/sentinel-docker-compose.yml b/tests/sentinel-docker-compose.yml index 01404bf3..18f4a108 100644 --- a/tests/sentinel-docker-compose.yml +++ b/tests/sentinel-docker-compose.yml @@ -6,18 +6,23 @@ networks: services: redis-main: + container_name: "redis-main" image: 'bitnami/redis:${REDIS_VERSION}' environment: - ALLOW_EMPTY_PASSWORD=yes + - 'REDIS_PASSWORD=${REDIS_PASSWORD}' ports: - "6379" networks: - app-tier redis-replica: + container_name: "redis-replica" image: 'bitnami/redis:${REDIS_VERSION}' depends_on: - redis-main environment: + - 'REDIS_PASSWORD=${REDIS_PASSWORD}' + - 'REDIS_MASTER_PASSWORD=${REDIS_PASSWORD}' - ALLOW_EMPTY_PASSWORD=yes - REDIS_REPLICATION_MODE=slave - REDIS_MASTER_HOST=redis-main @@ -26,6 +31,7 @@ services: networks: - app-tier redis-sentinel-1: + container_name: "redis-sentinel-1" image: 'bitnami/redis-sentinel:${REDIS_VERSION}' depends_on: - redis-main @@ -35,11 +41,14 @@ services: - ALLOW_EMPTY_PASSWORD=yes - REDIS_MASTER_HOST=redis-main - REDIS_MASTER_SET=redis-sentinel-main + - 'REDIS_SENTINEL_PASSWORD=${REDIS_SENTINEL_PASSWORD}' + - 'REDIS_MASTER_PASSWORD=${REDIS_PASSWORD}' ports: - '26379:26379' networks: - app-tier redis-sentinel-2: + container_name: "redis-sentinel-2" image: 'bitnami/redis-sentinel:${REDIS_VERSION}' depends_on: - redis-main @@ -50,11 +59,14 @@ services: - ALLOW_EMPTY_PASSWORD=yes - REDIS_MASTER_HOST=redis-main - REDIS_MASTER_SET=redis-sentinel-main + - 'REDIS_SENTINEL_PASSWORD=${REDIS_SENTINEL_PASSWORD}' + - 'REDIS_MASTER_PASSWORD=${REDIS_PASSWORD}' ports: - '26380:26380' networks: - app-tier redis-sentinel-3: + container_name: "redis-sentinel-3" image: 'bitnami/redis-sentinel:${REDIS_VERSION}' depends_on: - redis-main @@ -65,6 +77,8 @@ services: - ALLOW_EMPTY_PASSWORD=yes - REDIS_MASTER_HOST=redis-main - REDIS_MASTER_SET=redis-sentinel-main + - 'REDIS_SENTINEL_PASSWORD=${REDIS_SENTINEL_PASSWORD}' + - 'REDIS_MASTER_PASSWORD=${REDIS_PASSWORD}' ports: - '26381:26381' networks: