# frozen_string_literal: true require 'spec_helper' RSpec.describe Gitlab::Database::LoadBalancing::Configuration, :request_store do let(:configuration_hash) { {} } let(:db_config) { ActiveRecord::DatabaseConfigurations::HashConfig.new('test', 'ci', configuration_hash) } let(:model) { double(:model, connection_db_config: db_config) } describe '.for_model' do context 'when load balancing is not configured' do it 'uses the default settings' do config = described_class.for_model(model) expect(config.hosts).to eq([]) expect(config.max_replication_difference).to eq(8.megabytes) expect(config.max_replication_lag_time).to eq(60.0) expect(config.replica_check_interval).to eq(60.0) expect(config.service_discovery).to eq( nameserver: 'localhost', port: 8600, record: nil, record_type: 'A', interval: 60, disconnect_timeout: 120, use_tcp: false ) expect(config.pool_size).to eq(Gitlab::Database.default_pool_size) end end context 'when load balancing is configured' do let(:configuration_hash) do { pool: 4, load_balancing: { max_replication_difference: 1, max_replication_lag_time: 2, replica_check_interval: 3, hosts: %w[foo bar], discover: { 'record' => 'foo.example.com' } } } end it 'uses the custom configuration settings' do config = described_class.for_model(model) expect(config.hosts).to eq(%w[foo bar]) expect(config.max_replication_difference).to eq(1) expect(config.max_replication_lag_time).to eq(2.0) expect(config.replica_check_interval).to eq(3.0) expect(config.service_discovery).to eq( nameserver: 'localhost', port: 8600, record: 'foo.example.com', record_type: 'A', interval: 60, disconnect_timeout: 120, use_tcp: false ) expect(config.pool_size).to eq(4) end end context 'when the load balancing configuration uses strings as the keys' do let(:configuration_hash) do { pool: 4, load_balancing: { 'max_replication_difference' => 1, 'max_replication_lag_time' => 2, 'replica_check_interval' => 3, 'hosts' => %w[foo bar], 'discover' => { 'record' => 'foo.example.com' } } } end it 'uses the custom configuration settings' do config = described_class.for_model(model) expect(config.hosts).to eq(%w[foo bar]) expect(config.max_replication_difference).to eq(1) expect(config.max_replication_lag_time).to eq(2.0) expect(config.replica_check_interval).to eq(3.0) expect(config.service_discovery).to eq( nameserver: 'localhost', port: 8600, record: 'foo.example.com', record_type: 'A', interval: 60, disconnect_timeout: 120, use_tcp: false ) expect(config.pool_size).to eq(4) end end it 'calls reuse_primary_connection!' do expect_next_instance_of(described_class) do |subject| expect(subject).to receive(:reuse_primary_connection!).and_call_original end described_class.for_model(model) end end describe '#load_balancing_enabled?' do it 'returns false when running inside a Rake task' do config = described_class.new(ActiveRecord::Base, %w[foo bar]) allow(Gitlab::Runtime).to receive(:rake?).and_return(true) expect(config.load_balancing_enabled?).to eq(false) end it 'returns true when hosts are configured' do config = described_class.new(ActiveRecord::Base, %w[foo bar]) expect(config.load_balancing_enabled?).to eq(true) end it 'returns true when a service discovery record is configured' do config = described_class.new(ActiveRecord::Base) config.service_discovery[:record] = 'foo' expect(config.load_balancing_enabled?).to eq(true) end it 'returns false when no hosts are configured and service discovery is disabled' do config = described_class.new(ActiveRecord::Base) expect(config.load_balancing_enabled?).to eq(false) end end describe '#service_discovery_enabled?' do it 'returns false when running inside a Rake task' do allow(Gitlab::Runtime).to receive(:rake?).and_return(true) config = described_class.new(ActiveRecord::Base) config.service_discovery[:record] = 'foo' expect(config.service_discovery_enabled?).to eq(false) end it 'returns true when a record is configured' do config = described_class.new(ActiveRecord::Base) config.service_discovery[:record] = 'foo' expect(config.service_discovery_enabled?).to eq(true) end it 'returns false when no record is configured' do config = described_class.new(ActiveRecord::Base) expect(config.service_discovery_enabled?).to eq(false) end end describe '#pool_size' do context 'when a custom pool size is used' do let(:configuration_hash) { { pool: 4 } } it 'always reads the value from the model configuration' do config = described_class.new(model) expect(config.pool_size).to eq(4) # We can't modify `configuration_hash` as it's only used to populate the # internal hash used by ActiveRecord; instead of it being used as-is. allow(model.connection_db_config) .to receive(:configuration_hash) .and_return({ pool: 42 }) expect(config.pool_size).to eq(42) end end context 'when the pool size is nil' do let(:configuration_hash) { {} } it 'returns the default pool size' do config = described_class.new(model) expect(config.pool_size).to eq(Gitlab::Database.default_pool_size) end end end describe '#db_config_name' do let(:config) { described_class.new(model) } subject { config.db_config_name } it 'returns connection name as symbol' do is_expected.to eq(:ci) end end describe '#replica_db_config' do let(:model) { double(:model, connection_db_config: db_config, connection_specification_name: 'Ci::ApplicationRecord') } let(:config) { described_class.for_model(model) } it 'returns exactly db_config' do expect(config.replica_db_config).to eq(db_config) end context 'when GITLAB_LOAD_BALANCING_REUSE_PRIMARY_ci=main' do it 'does not change replica_db_config' do stub_env('GITLAB_LOAD_BALANCING_REUSE_PRIMARY_ci', 'main') expect(config.replica_db_config).to eq(db_config) end end end describe 'reuse_primary_connection!' do let(:model) { double(:model, connection_db_config: db_config, connection_specification_name: 'Ci::ApplicationRecord') } let(:config) { described_class.for_model(model) } context 'when GITLAB_LOAD_BALANCING_REUSE_PRIMARY_* not configured' do it 'the primary connection uses default specification' do stub_env('GITLAB_LOAD_BALANCING_REUSE_PRIMARY_ci', nil) expect(config.primary_connection_specification_name).to eq('Ci::ApplicationRecord') end end context 'when GITLAB_LOAD_BALANCING_REUSE_PRIMARY_ci=main' do before do stub_env('GITLAB_LOAD_BALANCING_REUSE_PRIMARY_ci', 'main') end it 'the primary connection uses main connection' do expect(config.primary_connection_specification_name).to eq('ActiveRecord::Base') end context 'when force_no_sharing_primary_model feature flag is enabled' do before do stub_feature_flags(force_no_sharing_primary_model: true) end it 'the primary connection uses ci connection' do expect(config.primary_connection_specification_name).to eq('Ci::ApplicationRecord') end end end context 'when GITLAB_LOAD_BALANCING_REUSE_PRIMARY_ci=unknown' do it 'raises exception' do stub_env('GITLAB_LOAD_BALANCING_REUSE_PRIMARY_ci', 'unknown') expect { config.reuse_primary_connection! }.to raise_error /Invalid value for/ end end end end