diff options
Diffstat (limited to 'spec/lib/gitlab/redis/multi_store_spec.rb')
-rw-r--r-- | spec/lib/gitlab/redis/multi_store_spec.rb | 544 |
1 files changed, 175 insertions, 369 deletions
diff --git a/spec/lib/gitlab/redis/multi_store_spec.rb b/spec/lib/gitlab/redis/multi_store_spec.rb index 8b73b5e03c0..207fe28e84e 100644 --- a/spec/lib/gitlab/redis/multi_store_spec.rb +++ b/spec/lib/gitlab/redis/multi_store_spec.rb @@ -127,19 +127,15 @@ RSpec.describe Gitlab::Redis::MultiStore do end before(:all) do - primary_store.multi do |multi| - multi.set(key1, value1) - multi.set(key2, value2) - multi.sadd(skey, value1) - multi.sadd(skey, value2) - end + primary_store.set(key1, value1) + primary_store.set(key2, value2) + primary_store.sadd?(skey, value1) + primary_store.sadd?(skey, value2) - secondary_store.multi do |multi| - multi.set(key1, value1) - multi.set(key2, value2) - multi.sadd(skey, value1) - multi.sadd(skey, value2) - end + secondary_store.set(key1, value1) + secondary_store.set(key2, value2) + secondary_store.sadd?(skey, value1) + secondary_store.sadd?(skey, value2) end RSpec.shared_examples_for 'reads correct value' do @@ -211,126 +207,86 @@ RSpec.describe Gitlab::Redis::MultiStore do end with_them do - describe "#{name}" do + describe name.to_s do before do allow(primary_store).to receive(name).and_call_original allow(secondary_store).to receive(name).and_call_original end - context 'with feature flag :use_primary_and_secondary_stores_for_test_store' do - before do - stub_feature_flags(use_primary_and_secondary_stores_for_test_store: true) - end - - context 'when reading from the primary is successful' do - it 'returns the correct value' do - expect(primary_store).to receive(name).with(*args).and_call_original - - subject - end - - it 'does not execute on the secondary store' do - expect(secondary_store).not_to receive(name) + context 'when reading from the primary is successful' do + it 'returns the correct value' do + expect(primary_store).to receive(name).with(*args).and_call_original - subject - end - - include_examples 'reads correct value' + subject end - context 'when reading from primary instance is raising an exception' do - before do - allow(primary_store).to receive(name).with(*args).and_raise(StandardError) - allow(Gitlab::ErrorTracking).to receive(:log_exception) - end + it 'does not execute on the secondary store' do + expect(secondary_store).not_to receive(name) - it 'logs the exception' do - expect(Gitlab::ErrorTracking).to receive(:log_exception).with(an_instance_of(StandardError), - hash_including(:multi_store_error_message, instance_name: instance_name, command_name: name)) + subject + end - subject - end + include_examples 'reads correct value' + end - include_examples 'fallback read from the secondary store' + context 'when reading from primary instance is raising an exception' do + before do + allow(primary_store).to receive(name).with(*args).and_raise(StandardError) + allow(Gitlab::ErrorTracking).to receive(:log_exception) end - context 'when reading from primary instance return no value' do - before do - allow(primary_store).to receive(name).and_return(nil) - end + it 'logs the exception' do + expect(Gitlab::ErrorTracking).to receive(:log_exception).with(an_instance_of(StandardError), + hash_including(:multi_store_error_message, instance_name: instance_name, command_name: name)) - include_examples 'fallback read from the secondary store' + subject end - context 'when the command is executed within pipelined block' do - subject do - multi_store.pipelined do |pipeline| - pipeline.send(name, *args) - end - end + include_examples 'fallback read from the secondary store' + end - it 'is executed only 1 time on primary and secondary instance' do - expect(primary_store).to receive(:pipelined).and_call_original - expect(secondary_store).to receive(:pipelined).and_call_original + context 'when reading from primary instance return no value' do + before do + allow(primary_store).to receive(name).and_return(nil) + end - 2.times do - expect_next_instance_of(Redis::PipelinedConnection) do |pipeline| - expect(pipeline).to receive(name).with(*args).once.and_call_original - end - end + include_examples 'fallback read from the secondary store' + end - subject + context 'when the command is executed within pipelined block' do + subject do + multi_store.pipelined do |pipeline| + pipeline.send(name, *args) end end - if params[:block] - subject do - multi_store.send(name, *args, &block) - end - - context 'when block is provided' do - it 'yields to the block' do - expect(primary_store).to receive(name).and_yield(value) + it 'is executed only 1 time on primary and secondary instance' do + expect(primary_store).to receive(:pipelined).and_call_original + expect(secondary_store).to receive(:pipelined).and_call_original - subject + 2.times do + expect_next_instance_of(Redis::PipelinedConnection) do |pipeline| + expect(pipeline).to receive(name).with(*args).once.and_call_original end - - include_examples 'reads correct value' end - end - end - context 'with feature flag :use_primary_and_secondary_stores_for_test_store' do - before do - stub_feature_flags(use_primary_and_secondary_stores_for_test_store: false) + subject end + end - context 'with feature flag :use_primary_store_as_default_for_test_store is disabled' do - before do - stub_feature_flags(use_primary_store_as_default_for_test_store: false) - end - - it_behaves_like 'secondary store' + if params[:block] + subject do + multi_store.send(name, *args, &block) end - context 'with feature flag :use_primary_store_as_default_for_test_store is enabled' do - before do - stub_feature_flags(use_primary_store_as_default_for_test_store: true) - end - - it 'execute on the primary instance' do - expect(primary_store).to receive(name).with(*args).and_call_original + context 'when block is provided' do + it 'yields to the block' do + expect(primary_store).to receive(name).and_yield(value) subject end include_examples 'reads correct value' - - it 'does not execute on the secondary store' do - expect(secondary_store).not_to receive(name) - - subject - end end end @@ -372,8 +328,9 @@ RSpec.describe Gitlab::Redis::MultiStore do let_it_be(:skey) { "redis:set:key" } let_it_be(:svalues1) { [value2, value1] } let_it_be(:svalues2) { [value1] } - let_it_be(:skey_value1) { [skey, value1] } - let_it_be(:skey_value2) { [skey, value2] } + let_it_be(:skey_value1) { [skey, [value1]] } + let_it_be(:skey_value2) { [skey, [value2]] } + let_it_be(:script) { %(redis.call("set", "#{key1}", "#{value1}")) } where(:case_name, :name, :args, :expected_value, :verification_name, :verification_args) do 'execute :set command' | :set | ref(:key1_value1) | ref(:value1) | :get | ref(:key1) @@ -383,25 +340,22 @@ RSpec.describe Gitlab::Redis::MultiStore do 'execute :srem command' | :srem | ref(:skey_value1) | [] | :smembers | ref(:skey) 'execute :del command' | :del | ref(:key2) | nil | :get | ref(:key2) 'execute :flushdb command' | :flushdb | nil | 0 | :dbsize | nil + 'execute :eval command' | :eval | ref(:script) | ref(:value1) | :get | ref(:key1) end before do primary_store.flushdb secondary_store.flushdb - primary_store.multi do |multi| - multi.set(key2, value1) - multi.sadd(skey, value1) - end + primary_store.set(key2, value1) + primary_store.sadd?(skey, value1) - secondary_store.multi do |multi| - multi.set(key2, value1) - multi.sadd(skey, value1) - end + secondary_store.set(key2, value1) + secondary_store.sadd?(skey, value1) end with_them do - describe "#{name}" do + describe name.to_s do let(:expected_args) { args || no_args } before do @@ -409,100 +363,58 @@ RSpec.describe Gitlab::Redis::MultiStore do allow(secondary_store).to receive(name).and_call_original end - context 'with feature flag :use_primary_and_secondary_stores_for_test_store' do - before do - stub_feature_flags(use_primary_and_secondary_stores_for_test_store: true) - end - - context 'when executing on primary instance is successful' do - it 'executes on both primary and secondary redis store', :aggregate_errors do - expect(primary_store).to receive(name).with(*expected_args).and_call_original - expect(secondary_store).to receive(name).with(*expected_args).and_call_original - - subject - end - - include_examples 'verify that store contains values', :primary_store - include_examples 'verify that store contains values', :secondary_store - end - - context 'when executing on the primary instance is raising an exception' do - before do - allow(primary_store).to receive(name).with(*expected_args).and_raise(StandardError) - allow(Gitlab::ErrorTracking).to receive(:log_exception) - end - - it 'logs the exception and execute on secondary instance', :aggregate_errors do - expect(Gitlab::ErrorTracking).to receive(:log_exception).with(an_instance_of(StandardError), - hash_including(:multi_store_error_message, command_name: name, instance_name: instance_name)) - expect(secondary_store).to receive(name).with(*expected_args).and_call_original - - subject - end + context 'when executing on primary instance is successful' do + it 'executes on both primary and secondary redis store', :aggregate_errors do + expect(primary_store).to receive(name).with(*expected_args).and_call_original + expect(secondary_store).to receive(name).with(*expected_args).and_call_original - include_examples 'verify that store contains values', :secondary_store + subject end - context 'when the command is executed within pipelined block' do - subject do - multi_store.pipelined do |pipeline| - pipeline.send(name, *args) - end - end - - it 'is executed only 1 time on each instance', :aggregate_errors do - expect(primary_store).to receive(:pipelined).and_call_original - expect_next_instance_of(Redis::PipelinedConnection) do |pipeline| - expect(pipeline).to receive(name).with(*expected_args).once.and_call_original - end - - expect(secondary_store).to receive(:pipelined).and_call_original - expect_next_instance_of(Redis::PipelinedConnection) do |pipeline| - expect(pipeline).to receive(name).with(*expected_args).once.and_call_original - end - - subject - end - - include_examples 'verify that store contains values', :primary_store - include_examples 'verify that store contains values', :secondary_store - end + include_examples 'verify that store contains values', :primary_store + include_examples 'verify that store contains values', :secondary_store end - context 'with feature flag :use_primary_and_secondary_stores_for_test_store is disabled' do + context 'when executing on the primary instance is raising an exception' do before do - stub_feature_flags(use_primary_and_secondary_stores_for_test_store: false) + allow(primary_store).to receive(name).with(*expected_args).and_raise(StandardError) + allow(Gitlab::ErrorTracking).to receive(:log_exception) end - context 'with feature flag :use_primary_store_as_default_for_test_store is disabled' do - before do - stub_feature_flags(use_primary_store_as_default_for_test_store: false) - end + it 'logs the exception and execute on secondary instance', :aggregate_errors do + expect(Gitlab::ErrorTracking).to receive(:log_exception).with(an_instance_of(StandardError), + hash_including(:multi_store_error_message, command_name: name, instance_name: instance_name)) + expect(secondary_store).to receive(name).with(*expected_args).and_call_original + + subject + end - it 'executes only on the secondary redis store', :aggregate_errors do - expect(secondary_store).to receive(name).with(*expected_args) - expect(primary_store).not_to receive(name).with(*expected_args) + include_examples 'verify that store contains values', :secondary_store + end - subject + context 'when the command is executed within pipelined block' do + subject do + multi_store.pipelined do |pipeline| + pipeline.send(name, *args) end - - include_examples 'verify that store contains values', :secondary_store end - context 'with feature flag :use_primary_store_as_default_for_test_store is enabled' do - before do - stub_feature_flags(use_primary_store_as_default_for_test_store: true) + it 'is executed only 1 time on each instance', :aggregate_errors do + expect(primary_store).to receive(:pipelined).and_call_original + expect_next_instance_of(Redis::PipelinedConnection) do |pipeline| + expect(pipeline).to receive(name).with(*expected_args).once.and_call_original end - it 'executes only on the primary_redis redis store', :aggregate_errors do - expect(primary_store).to receive(name).with(*expected_args) - expect(secondary_store).not_to receive(name).with(*expected_args) - - subject + expect(secondary_store).to receive(:pipelined).and_call_original + expect_next_instance_of(Redis::PipelinedConnection) do |pipeline| + expect(pipeline).to receive(name).with(*expected_args).once.and_call_original end - include_examples 'verify that store contains values', :primary_store + subject end + + include_examples 'verify that store contains values', :primary_store + include_examples 'verify that store contains values', :secondary_store end end end @@ -537,151 +449,109 @@ RSpec.describe Gitlab::Redis::MultiStore do end end - context 'with feature flag :use_primary_and_secondary_stores_for_test_store' do - before do - stub_feature_flags(use_primary_and_secondary_stores_for_test_store: true) - end - - context 'when executing on primary instance is successful' do - it 'executes on both primary and secondary redis store', :aggregate_errors do - expect(primary_store).to receive(name).and_call_original - expect(secondary_store).to receive(name).and_call_original - - subject - end + context 'when executing on primary instance is successful' do + it 'executes on both primary and secondary redis store', :aggregate_errors do + expect(primary_store).to receive(name).and_call_original + expect(secondary_store).to receive(name).and_call_original - include_examples 'verify that store contains values', :primary_store - include_examples 'verify that store contains values', :secondary_store + subject end - context 'when executing on the primary instance is raising an exception' do - before do - allow(primary_store).to receive(name).and_raise(StandardError) - allow(Gitlab::ErrorTracking).to receive(:log_exception) - end - - it 'logs the exception and execute on secondary instance', :aggregate_errors do - expect(Gitlab::ErrorTracking).to receive(:log_exception).with(an_instance_of(StandardError), - hash_including(:multi_store_error_message, command_name: name)) - expect(secondary_store).to receive(name).and_call_original - - subject - end + include_examples 'verify that store contains values', :primary_store + include_examples 'verify that store contains values', :secondary_store + end - include_examples 'verify that store contains values', :secondary_store + context 'when executing on the primary instance is raising an exception' do + before do + allow(primary_store).to receive(name).and_raise(StandardError) + allow(Gitlab::ErrorTracking).to receive(:log_exception) end - describe 'return values from a pipelined command' do - RSpec::Matchers.define :pipeline_diff_error_with_stacktrace do |message| - match do |object| - expect(object).to be_a(Gitlab::Redis::MultiStore::PipelinedDiffError) - expect(object.backtrace).not_to be_nil - expect(object.message).to eq(message) - end - end + it 'logs the exception and execute on secondary instance', :aggregate_errors do + expect(Gitlab::ErrorTracking).to receive(:log_exception).with(an_instance_of(StandardError), + hash_including(:multi_store_error_message, command_name: name)) + expect(secondary_store).to receive(name).and_call_original - subject do - multi_store.send(name) do |redis| - redis.get(key1) - end - end - - context 'when the value exists on both and are equal' do - before do - primary_store.set(key1, value1) - secondary_store.set(key1, value1) - end + subject + end - it 'returns the value' do - expect(Gitlab::ErrorTracking).not_to receive(:log_exception) + include_examples 'verify that store contains values', :secondary_store + end - expect(subject).to eq([value1]) - end + describe 'return values from a pipelined command' do + RSpec::Matchers.define :pipeline_diff_error_with_stacktrace do |message| + match do |object| + expect(object).to be_a(Gitlab::Redis::MultiStore::PipelinedDiffError) + expect(object.backtrace).not_to be_nil + expect(object.message).to eq(message) end + end - context 'when the value exists on both but differ' do - before do - primary_store.set(key1, value1) - secondary_store.set(key1, value2) - end - - it 'returns the value from the secondary store, logging an error' do - expect(Gitlab::ErrorTracking).to receive(:log_exception).with( - pipeline_diff_error_with_stacktrace( - 'Pipelined command executed on both stores successfully but results differ between them. ' \ - "Result from the primary: [#{value1.inspect}]. Result from the secondary: [#{value2.inspect}]." - ), - hash_including(command_name: name, instance_name: instance_name) - ).and_call_original - expect(counter).to receive(:increment).with(command: name, instance_name: instance_name) - - expect(subject).to eq([value2]) - end + subject do + multi_store.send(name) do |redis| + redis.get(key1) end + end - context 'when the value does not exist on the primary but it does on the secondary' do - before do - secondary_store.set(key1, value2) - end - - it 'returns the value from the secondary store, logging an error' do - expect(Gitlab::ErrorTracking).to receive(:log_exception).with( - pipeline_diff_error_with_stacktrace( - 'Pipelined command executed on both stores successfully but results differ between them. ' \ - "Result from the primary: [nil]. Result from the secondary: [#{value2.inspect}]." - ), - hash_including(command_name: name, instance_name: instance_name) - ) - expect(counter).to receive(:increment).with(command: name, instance_name: instance_name) - - expect(subject).to eq([value2]) - end + context 'when the value exists on both and are equal' do + before do + primary_store.set(key1, value1) + secondary_store.set(key1, value1) end - context 'when the value does not exist in either' do - it 'returns nil without logging an error' do - expect(Gitlab::ErrorTracking).not_to receive(:log_exception) - expect(counter).not_to receive(:increment) + it 'returns the value' do + expect(Gitlab::ErrorTracking).not_to receive(:log_exception) - expect(subject).to eq([nil]) - end + expect(subject).to eq([value1]) end end - end - context 'with feature flag :use_primary_and_secondary_stores_for_test_store is disabled' do - before do - stub_feature_flags(use_primary_and_secondary_stores_for_test_store: false) - end - - context 'with feature flag :use_primary_store_as_default_for_test_store is disabled' do + context 'when the value exists on both but differ' do before do - stub_feature_flags(use_primary_store_as_default_for_test_store: false) + primary_store.set(key1, value1) + secondary_store.set(key1, value2) end - it 'executes only on the secondary redis store', :aggregate_errors do - expect(secondary_store).to receive(name) - expect(primary_store).not_to receive(name) - - subject + it 'returns the value from the secondary store, logging an error' do + expect(Gitlab::ErrorTracking).to receive(:log_exception).with( + pipeline_diff_error_with_stacktrace( + 'Pipelined command executed on both stores successfully but results differ between them. ' \ + "Result from the primary: [#{value1.inspect}]. Result from the secondary: [#{value2.inspect}]." + ), + hash_including(command_name: name, instance_name: instance_name) + ).and_call_original + expect(counter).to receive(:increment).with(command: name, instance_name: instance_name) + + expect(subject).to eq([value2]) end - - include_examples 'verify that store contains values', :secondary_store end - context 'with feature flag :use_primary_store_as_default_for_test_store is enabled' do + context 'when the value does not exist on the primary but it does on the secondary' do before do - stub_feature_flags(use_primary_store_as_default_for_test_store: true) + secondary_store.set(key1, value2) end - it 'executes only on the primary_redis redis store', :aggregate_errors do - expect(primary_store).to receive(name) - expect(secondary_store).not_to receive(name) - - subject + it 'returns the value from the secondary store, logging an error' do + expect(Gitlab::ErrorTracking).to receive(:log_exception).with( + pipeline_diff_error_with_stacktrace( + 'Pipelined command executed on both stores successfully but results differ between them. ' \ + "Result from the primary: [nil]. Result from the secondary: [#{value2.inspect}]." + ), + hash_including(command_name: name, instance_name: instance_name) + ) + expect(counter).to receive(:increment).with(command: name, instance_name: instance_name) + + expect(subject).to eq([value2]) end + end - include_examples 'verify that store contains values', :primary_store + context 'when the value does not exist in either' do + it 'returns nil without logging an error' do + expect(Gitlab::ErrorTracking).not_to receive(:log_exception) + expect(counter).not_to receive(:increment) + + expect(subject).to eq([nil]) + end end end end @@ -825,40 +695,8 @@ RSpec.describe Gitlab::Redis::MultiStore do describe '#to_s' do subject { multi_store.to_s } - context 'with feature flag :use_primary_and_secondary_stores_for_test_store is enabled' do - before do - stub_feature_flags(use_primary_and_secondary_stores_for_test_store: true) - end - - it 'returns same value as primary_store' do - is_expected.to eq(primary_store.to_s) - end - end - - context 'with feature flag :use_primary_and_secondary_stores_for_test_store is disabled' do - before do - stub_feature_flags(use_primary_and_secondary_stores_for_test_store: false) - end - - context 'with feature flag :use_primary_store_as_default_for_test_store is enabled' do - before do - stub_feature_flags(use_primary_store_as_default_for_test_store: true) - end - - it 'returns same value as primary_store' do - is_expected.to eq(primary_store.to_s) - end - end - - context 'with feature flag :use_primary_store_as_default_for_test_store is disabled' do - before do - stub_feature_flags(use_primary_store_as_default_for_test_store: false) - end - - it 'returns same value as primary_store' do - is_expected.to eq(secondary_store.to_s) - end - end + it 'returns same value as primary_store' do + is_expected.to eq(primary_store.to_s) end end @@ -869,24 +707,8 @@ RSpec.describe Gitlab::Redis::MultiStore do end describe '#use_primary_and_secondary_stores?' do - context 'with feature flag :use_primary_and_secondary_stores_for_test_store is enabled' do - before do - stub_feature_flags(use_primary_and_secondary_stores_for_test_store: true) - end - - it 'multi store is disabled' do - expect(multi_store.use_primary_and_secondary_stores?).to be true - end - end - - context 'with feature flag :use_primary_and_secondary_stores_for_test_store is disabled' do - before do - stub_feature_flags(use_primary_and_secondary_stores_for_test_store: false) - end - - it 'multi store is disabled' do - expect(multi_store.use_primary_and_secondary_stores?).to be false - end + it 'multi store is enabled' do + expect(multi_store.use_primary_and_secondary_stores?).to be true end context 'with empty DB' do @@ -911,24 +733,8 @@ RSpec.describe Gitlab::Redis::MultiStore do end describe '#use_primary_store_as_default?' do - context 'with feature flag :use_primary_store_as_default_for_test_store is enabled' do - before do - stub_feature_flags(use_primary_store_as_default_for_test_store: true) - end - - it 'multi store is disabled' do - expect(multi_store.use_primary_store_as_default?).to be true - end - end - - context 'with feature flag :use_primary_store_as_default_for_test_store is disabled' do - before do - stub_feature_flags(use_primary_store_as_default_for_test_store: false) - end - - it 'multi store is disabled' do - expect(multi_store.use_primary_store_as_default?).to be false - end + it 'multi store is disabled' do + expect(multi_store.use_primary_store_as_default?).to be true end context 'with empty DB' do |