Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 69 additions & 29 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,42 +12,73 @@ jobs:
strategy:
fail-fast: false
matrix:
ruby: ['2.4', '2.5', '2.6', '2.7', '3.0', jruby-head, truffleruby-head]
redis: ['4']
search: [['opensearch-ruby:2.1.0', 'opensearchproject/opensearch:2.2.1']]
include:
# Redis 3
- ruby: '2.7'
redis: '3'
search: ['opensearch-ruby:2.1.0', 'opensearchproject/opensearch:2.2.1']
# Opensearch 1.0
- ruby: '2.7'
redis: '4'
search: ['opensearch-ruby:1.0.1', 'opensearchproject/opensearch:1.0.1']
# Elasticsearch 7.13
- ruby: '2.7'
redis: '4'
search: ['elasticsearch:7.13.3', 'elasticsearch:7.13.4']
# Redis 5
- ruby: '2.7'
ruby: ['2.3', '2.4', '2.5', '2.6', '2.7', '3.0', '3.1', '3.2', '3.3', '3.4', head, jruby-head, truffleruby-head]
redis: ['4', '5']
redis_cluster: [false, true]
search: [
['opensearch-ruby:2', 'opensearchproject/opensearch:2'],
['opensearch-ruby:3', 'opensearchproject/opensearch:3'],
['elasticsearch:8', 'elasticsearch:8.18.2'],
['elasticsearch:9', 'elasticsearch:9.0.2']
]
exclude:
# redis 5.x requires ruby >= 2.5
- ruby: '2.3'
redis: '5'
- ruby: '2.4'
redis: '5'
search: ['opensearch-ruby:2.1.0', 'opensearchproject/opensearch:2.2.1']
# Ruby 2.3 & Elasticsearch 7.5
# redis-clustering first release is 5.x
- redis: '4'
redis_cluster: true
# redis-clustering 5.x requires ruby >= 2.7
- ruby: '2.3'
redis: '4'
search: ['elasticsearch:7.5.0', 'elasticsearch:7.13.4']
redis_cluster: true
- ruby: '2.4'
redis_cluster: true
- ruby: '2.5'
redis_cluster: true
- ruby: '2.6'
redis_cluster: true
# our usage of redis-cluster-client suffers from https://bugs.ruby-lang.org/issues/18991 in ruby <= 3.0
- ruby: '2.7'
redis_cluster: true
- ruby: '3.0'
redis_cluster: true
# opensearch-ruby 2.x requires ruby >= 2.4
- ruby: '2.3'
search: ['opensearch-ruby:2', 'opensearchproject/opensearch:2']
- ruby: '2.3'
search: ['opensearch-ruby:3', 'opensearchproject/opensearch:3']
# opensearch-ruby 3.x requires ruby >= 2.5
- ruby: '2.3'
search: ['opensearch-ruby:3', 'opensearchproject/opensearch:3']
- ruby: '2.4'
search: ['opensearch-ruby:3', 'opensearchproject/opensearch:3']
# elasticsearch 8.x requires ruby >= 2.5
- ruby: '2.3'
search: ['elasticsearch:8', 'elasticsearch:8.18.2']
- ruby: '2.4'
search: ['elasticsearch:8', 'elasticsearch:8.18.2']
# elasticsearch 9.x requires ruby >= 2.6
- ruby: '2.3'
search: ['elasticsearch:9', 'elasticsearch:9.0.2']
- ruby: '2.4'
search: ['elasticsearch:9', 'elasticsearch:9.0.2']
- ruby: '2.5'
search: ['elasticsearch:9', 'elasticsearch:9.0.2']
services:
redis:
image: redis
ports:
- 6379:6379
search:
image: ${{ matrix.search[1] }}
ports:
- 9200:9200
env:
discovery.type: single-node
# Disable security for OpenSearch
plugins.security.disabled: ${{ contains(matrix.search[1], 'opensearch') && 'true' || '' }}
# OpenSearch 2.12.0 removed the default admin password
OPENSEARCH_INITIAL_ADMIN_PASSWORD: M7$thunder9K # this doesn't need to be a secret
# Disable security for Elasticsearch 8.x and 9.x
xpack.security.enabled: ${{ contains(matrix.search[1], 'elasticsearch') && 'false' || '' }}
options: >-
--health-cmd="curl http://localhost:9200/_cluster/health"
--health-interval=3s
Expand All @@ -56,6 +87,7 @@ jobs:

env:
REDIS_VERSION: ${{ matrix.redis }}
REDIS_CLUSTER: ${{ matrix.redis_cluster && 'true' || '' }}
SEARCH_GEM: ${{ matrix.search[0] }}

steps:
Expand All @@ -64,6 +96,14 @@ jobs:
with:
ruby-version: ${{ matrix.ruby }}
bundler-cache: true
- name: Start Redis (single instance)
if: ${{ !matrix.redis_cluster }}
run: |
docker run -d --name redis -p 6379:6379 redis
- name: Start Redis Cluster
if: ${{ matrix.redis_cluster }}
run: |
docker compose -f docker-compose.redis-cluster.yml up -d --wait
- name: start MySQL
run: sudo /etc/init.d/mysql start
- run: bundle exec rspec --format doc
Expand All @@ -81,7 +121,7 @@ jobs:
- uses: actions/checkout@v3
- uses: ruby/setup-ruby@v1
with:
ruby-version: '2.7'
ruby-version: '3.4'
bundler-cache: true
- run: bundle exec rubocop

Expand All @@ -91,7 +131,7 @@ jobs:
- uses: actions/checkout@v3
- uses: ruby/setup-ruby@v1
with:
ruby-version: '2.7'
ruby-version: '3.4'
bundler-cache: true
- run: bin/yardoc --fail-on-warning

Expand All @@ -102,7 +142,7 @@ jobs:
- uses: actions/checkout@v3
- uses: ruby/setup-ruby@v1
with:
ruby-version: '2.7'
ruby-version: '3.4'
bundler-cache: true
- run: bin/check-version

Expand Down
19 changes: 13 additions & 6 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,21 @@ if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.6')
gem 'simplecov-cobertura', '~> 2.1'
end

if ENV['REDIS_VERSION']
gem 'redis', "~> #{ENV['REDIS_VERSION']}"
if (redis_version = ENV.fetch('REDIS_VERSION', nil))
gem 'redis', "~> #{redis_version}"
end

if ENV['SEARCH_GEM']
name, version = ENV['SEARCH_GEM'].split(':')
name = 'opensearch-ruby' if name == 'opensearch'
if redis_version
if ENV.fetch('REDIS_CLUSTER', nil) == 'true'
gem 'redis-clustering', "~> #{redis_version}"
end
elsif Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.7')
gem 'redis-clustering' # rubocop:disable Bundler/DuplicatedGem
end

if (search_gem = ENV.fetch('SEARCH_GEM', nil))
name, version = search_gem.split(':')
gem name, "~> #{version}"
else
gem 'opensearch-ruby', '~> 2.1'
gem 'opensearch-ruby'
end
72 changes: 72 additions & 0 deletions docker-compose.redis-cluster.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
services:
redis-cluster-node-0:
image: docker.io/bitnami/redis-cluster:latest
volumes:
- redis-cluster_data-0:/bitnami/redis/data
environment:
- "ALLOW_EMPTY_PASSWORD=yes"
- "REDIS_NODES=redis-cluster-node-0 redis-cluster-node-1 redis-cluster-node-2 redis-cluster-node-3 redis-cluster-node-4 redis-cluster-node-5"

redis-cluster-node-1:
image: docker.io/bitnami/redis-cluster:latest
volumes:
- redis-cluster_data-1:/bitnami/redis/data
environment:
- "ALLOW_EMPTY_PASSWORD=yes"
- "REDIS_NODES=redis-cluster-node-0 redis-cluster-node-1 redis-cluster-node-2 redis-cluster-node-3 redis-cluster-node-4 redis-cluster-node-5"

redis-cluster-node-2:
image: docker.io/bitnami/redis-cluster:latest
volumes:
- redis-cluster_data-2:/bitnami/redis/data
environment:
- "ALLOW_EMPTY_PASSWORD=yes"
- "REDIS_NODES=redis-cluster-node-0 redis-cluster-node-1 redis-cluster-node-2 redis-cluster-node-3 redis-cluster-node-4 redis-cluster-node-5"

redis-cluster-node-3:
image: docker.io/bitnami/redis-cluster:latest
volumes:
- redis-cluster_data-3:/bitnami/redis/data
environment:
- "ALLOW_EMPTY_PASSWORD=yes"
- "REDIS_NODES=redis-cluster-node-0 redis-cluster-node-1 redis-cluster-node-2 redis-cluster-node-3 redis-cluster-node-4 redis-cluster-node-5"

redis-cluster-node-4:
image: docker.io/bitnami/redis-cluster:latest
volumes:
- redis-cluster_data-4:/bitnami/redis/data
environment:
- "ALLOW_EMPTY_PASSWORD=yes"
- "REDIS_NODES=redis-cluster-node-0 redis-cluster-node-1 redis-cluster-node-2 redis-cluster-node-3 redis-cluster-node-4 redis-cluster-node-5"

redis-cluster-node-5:
image: docker.io/bitnami/redis-cluster:latest
volumes:
- redis-cluster_data-5:/bitnami/redis/data
depends_on:
- redis-cluster-node-0
- redis-cluster-node-1
- redis-cluster-node-2
- redis-cluster-node-3
- redis-cluster-node-4
environment:
- "ALLOW_EMPTY_PASSWORD=yes"
- "REDIS_CLUSTER_REPLICAS=1"
- "REDIS_NODES=redis-cluster-node-0 redis-cluster-node-1 redis-cluster-node-2 redis-cluster-node-3 redis-cluster-node-4 redis-cluster-node-5"
- "REDIS_CLUSTER_CREATOR=yes"
ports:
- "6379:6379"

volumes:
redis-cluster_data-0:
driver: local
redis-cluster_data-1:
driver: local
redis-cluster_data-2:
driver: local
redis-cluster_data-3:
driver: local
redis-cluster_data-4:
driver: local
redis-cluster_data-5:
driver: local
2 changes: 1 addition & 1 deletion faulty.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ Gem::Specification.new do |spec|
# Other non-essential development dependencies go in the Gemfile.
spec.add_development_dependency 'connection_pool', '~> 2.0'
spec.add_development_dependency 'json'
spec.add_development_dependency 'redis', '>= 3.0'
spec.add_development_dependency 'redis', '>= 4.0'
spec.add_development_dependency 'rspec', '~> 3.8'
spec.add_development_dependency 'timecop', '>= 0.9'
end
15 changes: 14 additions & 1 deletion lib/faulty/patch/elasticsearch.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,12 @@ module ServerError; end
::OpenSearch
else
require 'elasticsearch'
::Elasticsearch
if Gem.loaded_specs['elastic-transport']
require 'elastic-transport'
::Elastic
else
::Elasticsearch
end
end

# We will freeze this after adding the dynamic error classes
Expand Down Expand Up @@ -100,6 +105,14 @@ class Client
end
end
end
elsif Gem.loaded_specs['elastic-transport']
module Elastic
module Transport
class Client
prepend(Faulty::Patch::Elasticsearch)
end
end
end
else
module Elasticsearch
module Transport
Expand Down
5 changes: 5 additions & 0 deletions lib/faulty/patch/redis.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
# frozen_string_literal: true

require 'redis'
begin
require 'redis-clustering'
rescue LoadError
nil
end

class Faulty
module Patch
Expand Down
41 changes: 30 additions & 11 deletions lib/faulty/storage/redis.rb
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ def key(*parts)
end

def ckey(circuit_name, *parts)
key('circuit', circuit_name, *parts)
key('circuit', hashtag(circuit_name), *parts)
end

# @return [String] The key for circuit options
Expand Down Expand Up @@ -323,7 +323,7 @@ def opened_at_key(circuit_name)

# Get the current key to add circuit names to
def list_key
key('list', current_list_block)
key('list', hashtag(current_list_block))
end

# Get all active circuit list keys
Expand All @@ -348,10 +348,14 @@ def all_list_keys
num_blocks = (options.circuit_ttl.to_f / options.list_granularity).floor + 1
start_block = current_list_block - num_blocks + 1
num_blocks.times.map do |i|
key('list', start_block + i)
key('list', hashtag(start_block + i))
end
end

def hashtag(part)
"{#{part}}"
end

# Get the block number for the current list set
#
# @return [Integer] The current block number
Expand All @@ -372,11 +376,11 @@ def current_list_block
# inside the block
def watch_exec(key, old, &block)
redis do |r|
r.watch(key) do
if old.include?(r.get(key))
r.multi(&block)
r.watch(key) do |c|
if old.include?(c.get(key))
c.multi(&block)
else
r.unwatch
c.unwatch
nil
end
end
Expand Down Expand Up @@ -424,11 +428,17 @@ def check_client_options!
warn "Faulty error while checking client options: #{e.message}"
end

def check_redis_options!
def check_redis_options! # rubocop:disable Metrics/MethodLength
gte5 = ::Redis::VERSION.to_f >= 5
method = gte5 ? :config : :options
ropts = redis do |r|
r.instance_variable_get(:@client).public_send(method)
if r.instance_of?(::Redis)
method = gte5 ? :config : :options
r._client.public_send(method)
elsif r.instance_of?(::Redis::Cluster)
r._client.config
else
raise TypeError, "Unsupported Redis client type: #{r.class}"
end
end

bad_timeouts = {}
Expand All @@ -445,7 +455,16 @@ def check_redis_options!
MSG
end

gt1_retry = gte5 ? ropts.retry_connecting?(1, nil) : ropts[:reconnect_attempts] > 1
gt1_retry = redis do |r|
if r.instance_of?(::Redis)
gte5 ? ropts.retry_connecting?(1, nil) : ropts[:reconnect_attempts] > 1
elsif r.instance_of?(::Redis::Cluster)
ra = ropts.client_config[:reconnect_attempts]
(ra.is_a?(Array) && ra.length > 1) || ra > 1
else
raise TypeError, "Unsupported Redis client type: #{r.class}"
end
end
if gt1_retry
warn <<~MSG
Faulty recommends setting Redis reconnect_attempts to <= 1 to
Expand Down
Loading