diff --git a/.env.test b/.env.test index eb0df36..1276992 100644 --- a/.env.test +++ b/.env.test @@ -11,6 +11,9 @@ INFLUX_BUCKET=my-bucket INFLUX_MEASUREMENT=my_power_splitter INFLUX_INTERVAL=5 +##### Redis +REDIS_URL=redis://localhost:6379/1 + # Sensor mapping: Map to Measurement/Field in InfluxDB INFLUX_SENSOR_GRID_IMPORT_POWER=SENEC:grid_power_plus INFLUX_SENSOR_HOUSE_POWER=SENEC:house_power diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index 19b00b4..12d78ce 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -18,6 +18,11 @@ jobs: test: runs-on: ubuntu-latest + services: + redis: + image: redis:7-alpine + ports: ['6379:6379'] + env: CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }} CI: true diff --git a/Gemfile b/Gemfile index 9ee4bab..fe2c87d 100644 --- a/Gemfile +++ b/Gemfile @@ -15,6 +15,9 @@ gem 'base64' # A toolkit of support libraries and Ruby core extensions extracted from the Rails framework. (https://rubyonrails.org) gem 'activesupport' +# A Ruby client library for Redis (https://github.com/redis/redis-rb) +gem 'redis' + group :development do # Guard gem for RSpec (https://github.com/guard/guard-rspec) gem 'guard-rspec', require: false diff --git a/Gemfile.lock b/Gemfile.lock index 93cffd5..6da15df 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -25,7 +25,7 @@ GEM rexml csv (3.3.0) diff-lcs (1.5.1) - docile (1.4.0) + docile (1.4.1) dotenv (3.1.2) drb (2.2.1) ffi (1.17.0) @@ -44,7 +44,7 @@ GEM guard (~> 2.1) guard-compat (~> 1.1) rspec (>= 2.99.0, < 4.0) - hashdiff (1.1.0) + hashdiff (1.1.1) i18n (1.14.5) concurrent-ruby (~> 1.0) influxdb-client (3.1.0) @@ -69,14 +69,18 @@ GEM coderay (~> 1.1) method_source (~> 1.0) public_suffix (6.0.1) - racc (1.8.0) + racc (1.8.1) rainbow (3.1.1) rake (13.2.1) rb-fsevent (0.11.2) rb-inotify (0.11.1) ffi (~> 1.0) + redis (5.2.0) + redis-client (>= 0.22.0) + redis-client (0.22.2) + connection_pool regexp_parser (2.9.2) - rexml (3.3.2) + rexml (3.3.4) strscan rspec (3.13.0) rspec-core (~> 3.13.0) @@ -91,7 +95,7 @@ GEM diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) rspec-support (3.13.1) - rubocop (1.65.0) + rubocop (1.65.1) json (~> 2.3) language_server-protocol (>= 3.17.0) parallel (~> 1.10) @@ -102,14 +106,14 @@ GEM rubocop-ast (>= 1.31.1, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 2.4.0, < 3.0) - rubocop-ast (1.31.3) + rubocop-ast (1.32.0) parser (>= 3.3.1.0) rubocop-performance (1.21.1) rubocop (>= 1.48.1, < 2.0) rubocop-ast (>= 1.31.1, < 2.0) rubocop-rake (0.6.0) rubocop (~> 1.0) - rubocop-rspec (3.0.3) + rubocop-rspec (3.0.4) rubocop (~> 1.61) rubocop-thread_safety (0.5.1) rubocop (>= 0.90.0) @@ -144,6 +148,7 @@ DEPENDENCIES guard-rspec influxdb-client rake + redis rspec rubocop rubocop-performance @@ -155,4 +160,4 @@ DEPENDENCIES webmock BUNDLED WITH - 2.5.16 + 2.5.17 diff --git a/lib/config.rb b/lib/config.rb index 3487787..0bcb27f 100644 --- a/lib/config.rb +++ b/lib/config.rb @@ -12,6 +12,7 @@ class Config # rubocop:disable Metrics/ClassLength :influx_bucket, :influx_measurement, :influx_interval, + :redis_url, :time_zone def initialize(env, logger: NullLogger.new) @@ -30,6 +31,7 @@ def initialize(env, logger: NullLogger.new) logger.info "Accessing InfluxDB at #{influx_url}, bucket #{influx_bucket}" @time_zone = env.fetch('TZ', 'Europe/Berlin') + @redis_url = env.fetch('REDIS_URL', nil) init_sensors(env) validate_sensors! diff --git a/lib/loop.rb b/lib/loop.rb index 5ad39a7..1759e7c 100644 --- a/lib/loop.rb +++ b/lib/loop.rb @@ -1,6 +1,7 @@ require 'influx_push' require 'influx_pull' require 'processor' +require 'redis_cache' class Loop def initialize(config:) @@ -53,6 +54,8 @@ def process_historical_data day += 1.day end + RedisCache.new(config:).flush + config.logger.info '--- Processing historical data successfully finished' end diff --git a/lib/redis_cache.rb b/lib/redis_cache.rb new file mode 100644 index 0000000..51c35a2 --- /dev/null +++ b/lib/redis_cache.rb @@ -0,0 +1,30 @@ +require 'redis' + +class RedisCache + def initialize(config:) + @config = config + end + attr_reader :config + + def flush + unless redis + config.logger.warn 'REDIS_URL not set, skipping Redis cache flush' + return + end + + result = redis.flushall + if result == 'OK' + config.logger.info 'Redis cache flushed' + else + config.logger.error "Flushing Redis cache failed: #{result}" + end + rescue StandardError => e + config.logger.error "Flushing Redis cache failed: #{e.message}" + end + + def redis + return unless config.redis_url + + @redis ||= Redis.new(url: config.redis_url) + end +end diff --git a/spec/lib/redis_cache_spec.rb b/spec/lib/redis_cache_spec.rb new file mode 100644 index 0000000..acca5cc --- /dev/null +++ b/spec/lib/redis_cache_spec.rb @@ -0,0 +1,64 @@ +require 'redis_cache' + +describe RedisCache do + subject(:redis_cache) { described_class.new(config:) } + + let(:config) { Config.new(ENV.to_h, logger:) } + let(:logger) { MemoryLogger.new } + + describe '#flush' do + subject(:flush) { redis_cache.flush } + + context 'when Redis is available' do + it 'writes info message into log' do + flush + + expect(logger.info_messages).to include('Redis cache flushed') + end + end + + context 'when Redis is not available' do + before do + allow(config).to receive(:redis_url).and_return( + 'redis://localhost:1234', + ) + end + + it 'writes error message into log' do + flush + + expect(logger.error_messages).to include( + 'Flushing Redis cache failed: Connection refused - connect(2) for 127.0.0.1:1234 (redis://localhost:1234)', + ) + end + end + + context 'when Redis cannot flush' do + before do + allow(Redis).to receive(:new).and_return( + instance_double(Redis, flushall: 'ERROR'), + ) + end + + it 'writes error message into log' do + flush + + expect(logger.error_messages).to include( + 'Flushing Redis cache failed: ERROR', + ) + end + end + + context 'when REDIS_URL missing' do + before { allow(config).to receive(:redis_url).and_return(nil) } + + it 'writes warn message into log' do + flush + + expect(logger.warn_messages).to include( + 'REDIS_URL not set, skipping Redis cache flush', + ) + end + end + end +end