From fd2fb9e8573e9957d021f660a7913bfc8a7c15ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20G=C3=BCnther?= Date: Tue, 31 Oct 2023 09:39:15 +0100 Subject: [PATCH] Add redis support --- Gemfile | 1 + Gemfile.lock | 6 +++ config/constants/settings/definition.rb | 10 ++++- .../configuration/README.md | 9 +++- lib_static/open_project/configuration.rb | 43 ++++++++++++++----- spec/lib/open_project/configuration_spec.rb | 12 ++++++ 6 files changed, 67 insertions(+), 14 deletions(-) diff --git a/Gemfile b/Gemfile index 7d82c3b1ce6..d56fa856202 100644 --- a/Gemfile +++ b/Gemfile @@ -165,6 +165,7 @@ group :production do # we use dalli as standard memcache client # requires memcached 1.4+ gem 'dalli', '~> 3.2.0' + gem 'redis', '~> 5.0.8' end gem 'i18n-js', '~> 4.2.3' diff --git a/Gemfile.lock b/Gemfile.lock index 0be0c78238e..1b8fefb5610 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -375,6 +375,7 @@ GEM compare-xml (0.66) nokogiri (~> 1.8) concurrent-ruby (1.2.2) + connection_pool (2.4.1) cookiejar (0.3.3) crack (0.4.5) rexml @@ -814,6 +815,10 @@ GEM psych (>= 4.0.0) recaptcha (5.16.0) redcarpet (3.6.0) + redis (5.0.8) + redis-client (>= 0.17.0) + redis-client (0.18.0) + connection_pool regexp_parser (2.8.2) reline (0.3.9) io-console (~> 0.5) @@ -1151,6 +1156,7 @@ DEPENDENCIES rails-controller-testing (~> 1.0.2) rails-i18n (~> 7.0.0) rdoc (>= 2.4.2) + redis (~> 5.0.8) request_store (~> 1.5.0) responders (~> 3.0) retriable (~> 3.1.1) diff --git a/config/constants/settings/definition.rb b/config/constants/settings/definition.rb index 1137a08825e..b06c3294130 100644 --- a/config/constants/settings/definition.rb +++ b/config/constants/settings/definition.rb @@ -209,7 +209,13 @@ module Settings description: 'The memcache server host and IP', format: :string, default: nil, - writable: false + writable: false, + }, + cache_redis_url: { + description: 'URL to the redis cache server', + format: :string, + default: nil, + writable: false, }, cache_namespace: { format: :string, @@ -703,7 +709,7 @@ module Settings format: :symbol, default: :file_store, writable: false, - allowed: %i[file_store memcache] + allowed: %i[file_store memcache redis] }, rails_relative_url_root: { description: 'Set a URL prefix / base path to run OpenProject under, e.g., host.tld/openproject', diff --git a/docs/installation-and-operations/configuration/README.md b/docs/installation-and-operations/configuration/README.md index 74bef4e73cf..1781faf39ae 100644 --- a/docs/installation-and-operations/configuration/README.md +++ b/docs/installation-and-operations/configuration/README.md @@ -644,8 +644,13 @@ OPENPROJECT_SECURITY__BADGE__DISPLAYED="false" ### Cache configuration options -* `rails_cache_store`: `memcache` for [memcached](https://www.memcached.org/) or `memory_store` (default: `file_store`) -* `cache_memcache_server`: The memcache server host and IP (default: `nil`) +* `rails_cache_store`: `memcache` for [memcached](https://www.memcached.org/), `redis` for [Redis cache](https://redis.io/), or `memory_store` (default: `file_store`) +* When using `memcached`, the following configuration option is relevant: + * `cache_memcache_server`: The memcache server host and IP (default: `nil`) + +* When using `redis`, the following configuration option is relevant: + * `cache_redis_url`: The URL of the Redis host (e.g., `redis://host:6379`) + * `cache_expires_in`: Expiration time for memcache entries (default: `nil`, no expiry) * `cache_namespace`: Namespace for cache keys, useful when multiple applications use a single memcache server (default: `nil`) diff --git a/lib_static/open_project/configuration.rb b/lib_static/open_project/configuration.rb index 10f091e8222..a146f28666c 100644 --- a/lib_static/open_project/configuration.rb +++ b/lib_static/open_project/configuration.rb @@ -51,16 +51,18 @@ module OpenProject # Also use :mem_cache_store for when :dalli_store is configured cache_store = self['rails_cache_store'].try(:to_sym) - case cache_store - when :memcache, :dalli_store - cache_config = [:mem_cache_store] - cache_config << self['cache_memcache_server'] if self['cache_memcache_server'] - # default to :file_store - when NilClass, :file_store - cache_config = [:file_store, Rails.root.join('tmp/cache')] - else - cache_config = [cache_store] - end + cache_config = + case cache_store + when :redis + redis_cache_configuration + when :memcache, :dalli_store + memcache_configuration + # default to :file_store + when NilClass, :file_store + [:file_store, Rails.root.join('tmp/cache')] + else + [cache_store] + end parameters = cache_store_parameters cache_config << parameters unless parameters.empty? @@ -85,6 +87,27 @@ module OpenProject private + def memcache_configuration + cache_config = [:mem_cache_store] + cache_config << self['cache_memcache_server'] if self['cache_memcache_server'] + cache_config + end + + def redis_cache_configuration + url = self['cache_redis_url'] + raise ArgumentError, "CACHE_SERVER is set to redis, but CACHE_REDIS_URL is not set." if url.blank? + + [ + :redis_cache_store, + { + url:, + error_handler: ->(method:, exception:) { + OpenProject.logger.error("Error in redis cache store #{method}: #{exception.message}", exception:) + } + } + ] + end + def method_missing(name, *args, &) setting_name = name.to_s.sub(/\?$/, '') diff --git a/spec/lib/open_project/configuration_spec.rb b/spec/lib/open_project/configuration_spec.rb index a49726cf5f4..6c5174f658d 100644 --- a/spec/lib/open_project/configuration_spec.rb +++ b/spec/lib/open_project/configuration_spec.rb @@ -74,6 +74,18 @@ RSpec.describe OpenProject::Configuration, :settings_reset do expect(subject.first).to eq(:file_store) end end + + context 'setting rails cache to redis', with_config: { 'rails_cache_store' => 'redis' } do + context 'when setting the URL', with_config: { 'cache_redis_url' => 'redis://localhost:1234' } do + it 'sets the cache to :redis_cache_store' do + expect(subject.first).to eq(:redis_cache_store) + end + end + + it 'raises an error trying to set redis without an URL' do + expect { subject }.to raise_error(ArgumentError, /CACHE_REDIS_URL is not set/) + end + end end end