305 lines
11 KiB
Ruby
305 lines
11 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require 'spec_helper'
|
|
|
|
RSpec.describe Gitlab::Database::TablesLocker, :suppress_gitlab_schemas_validate_connection, :silence_stdout,
|
|
feature_category: :pods do
|
|
let(:default_lock_writes_manager) do
|
|
instance_double(Gitlab::Database::LockWritesManager, lock_writes: nil, unlock_writes: nil)
|
|
end
|
|
|
|
before do
|
|
allow(Gitlab::Database::LockWritesManager).to receive(:new).with(any_args).and_return(default_lock_writes_manager)
|
|
# Limiting the scope of the tests to a subset of the database tables
|
|
allow(Gitlab::Database::GitlabSchema).to receive(:tables_to_schema).and_return({
|
|
'application_setttings' => :gitlab_main_clusterwide,
|
|
'projects' => :gitlab_main,
|
|
'security_findings' => :gitlab_main,
|
|
'ci_builds' => :gitlab_ci,
|
|
'ci_jobs' => :gitlab_ci,
|
|
'loose_foreign_keys_deleted_records' => :gitlab_shared,
|
|
'ar_internal_metadata' => :gitlab_internal
|
|
})
|
|
end
|
|
|
|
before(:all) do
|
|
create_partition_sql = <<~SQL
|
|
CREATE TABLE IF NOT EXISTS #{Gitlab::Database::DYNAMIC_PARTITIONS_SCHEMA}.security_findings_test_partition
|
|
PARTITION OF security_findings
|
|
FOR VALUES IN (0)
|
|
SQL
|
|
|
|
ApplicationRecord.connection.execute(create_partition_sql)
|
|
Ci::ApplicationRecord.connection.execute(create_partition_sql)
|
|
|
|
create_detached_partition_sql = <<~SQL
|
|
CREATE TABLE IF NOT EXISTS #{Gitlab::Database::DYNAMIC_PARTITIONS_SCHEMA}._test_gitlab_main_part_202201 (
|
|
id bigserial primary key not null
|
|
)
|
|
SQL
|
|
|
|
ApplicationRecord.connection.execute(create_detached_partition_sql)
|
|
Ci::ApplicationRecord.connection.execute(create_detached_partition_sql)
|
|
|
|
Gitlab::Database::SharedModel.using_connection(ApplicationRecord.connection) do
|
|
Postgresql::DetachedPartition.create!(
|
|
table_name: '_test_gitlab_main_part_20220101',
|
|
drop_after: Time.current
|
|
)
|
|
end
|
|
Gitlab::Database::SharedModel.using_connection(Ci::ApplicationRecord.connection) do
|
|
Postgresql::DetachedPartition.create!(
|
|
table_name: '_test_gitlab_main_part_20220101',
|
|
drop_after: Time.current
|
|
)
|
|
end
|
|
end
|
|
|
|
shared_examples "lock tables" do |gitlab_schema, database_name|
|
|
let(:connection) { Gitlab::Database.database_base_models[database_name].connection }
|
|
let(:tables_to_lock) do
|
|
Gitlab::Database::GitlabSchema
|
|
.tables_to_schema.filter_map { |table_name, schema| table_name if schema == gitlab_schema }
|
|
end
|
|
|
|
it "locks table in schema #{gitlab_schema} and database #{database_name}" do
|
|
expect(tables_to_lock).not_to be_empty
|
|
|
|
tables_to_lock.each do |table_name|
|
|
lock_writes_manager = instance_double(Gitlab::Database::LockWritesManager, lock_writes: nil)
|
|
|
|
expect(Gitlab::Database::LockWritesManager).to receive(:new).with(
|
|
table_name: table_name,
|
|
connection: connection,
|
|
database_name: database_name,
|
|
with_retries: true,
|
|
logger: anything,
|
|
dry_run: anything
|
|
).once.and_return(lock_writes_manager)
|
|
expect(lock_writes_manager).to receive(:lock_writes).once
|
|
end
|
|
|
|
subject
|
|
end
|
|
end
|
|
|
|
shared_examples "unlock tables" do |gitlab_schema, database_name|
|
|
let(:connection) { Gitlab::Database.database_base_models[database_name].connection }
|
|
|
|
let(:tables_to_unlock) do
|
|
Gitlab::Database::GitlabSchema
|
|
.tables_to_schema.filter_map { |table_name, schema| table_name if schema == gitlab_schema }
|
|
end
|
|
|
|
it "unlocks table in schema #{gitlab_schema} and database #{database_name}" do
|
|
expect(tables_to_unlock).not_to be_empty
|
|
|
|
tables_to_unlock.each do |table_name|
|
|
lock_writes_manager = instance_double(Gitlab::Database::LockWritesManager, unlock_writes: nil)
|
|
|
|
expect(Gitlab::Database::LockWritesManager).to receive(:new).with(
|
|
table_name: table_name,
|
|
connection: anything,
|
|
database_name: database_name,
|
|
with_retries: true,
|
|
logger: anything,
|
|
dry_run: anything
|
|
).once.and_return(lock_writes_manager)
|
|
expect(lock_writes_manager).to receive(:unlock_writes)
|
|
end
|
|
|
|
subject
|
|
end
|
|
end
|
|
|
|
shared_examples "lock partitions" do |partition_identifier, database_name|
|
|
let(:connection) { Gitlab::Database.database_base_models[database_name].connection }
|
|
|
|
it 'locks the partition' do
|
|
lock_writes_manager = instance_double(Gitlab::Database::LockWritesManager, lock_writes: nil)
|
|
|
|
expect(Gitlab::Database::LockWritesManager).to receive(:new).with(
|
|
table_name: partition_identifier,
|
|
connection: connection,
|
|
database_name: database_name,
|
|
with_retries: true,
|
|
logger: anything,
|
|
dry_run: anything
|
|
).once.and_return(lock_writes_manager)
|
|
expect(lock_writes_manager).to receive(:lock_writes)
|
|
|
|
subject
|
|
end
|
|
end
|
|
|
|
shared_examples "unlock partitions" do |partition_identifier, database_name|
|
|
let(:connection) { Gitlab::Database.database_base_models[database_name].connection }
|
|
|
|
it 'unlocks the partition' do
|
|
lock_writes_manager = instance_double(Gitlab::Database::LockWritesManager, unlock_writes: nil)
|
|
|
|
expect(Gitlab::Database::LockWritesManager).to receive(:new).with(
|
|
table_name: partition_identifier,
|
|
connection: connection,
|
|
database_name: database_name,
|
|
with_retries: true,
|
|
logger: anything,
|
|
dry_run: anything
|
|
).once.and_return(lock_writes_manager)
|
|
expect(lock_writes_manager).to receive(:unlock_writes)
|
|
|
|
subject
|
|
end
|
|
end
|
|
|
|
context 'when running on single database' do
|
|
before do
|
|
skip_if_multiple_databases_are_setup(:ci)
|
|
end
|
|
|
|
describe '#lock_writes' do
|
|
subject { described_class.new.lock_writes }
|
|
|
|
it 'does not lock any table' do
|
|
expect(Gitlab::Database::LockWritesManager).to receive(:new)
|
|
.with(any_args).and_return(default_lock_writes_manager)
|
|
expect(default_lock_writes_manager).not_to receive(:lock_writes)
|
|
|
|
subject
|
|
end
|
|
|
|
it_behaves_like 'unlock tables', :gitlab_main, 'main'
|
|
it_behaves_like 'unlock tables', :gitlab_ci, 'main'
|
|
it_behaves_like 'unlock tables', :gitlab_main_clusterwide, 'main'
|
|
it_behaves_like 'unlock tables', :gitlab_shared, 'main'
|
|
it_behaves_like 'unlock tables', :gitlab_internal, 'main'
|
|
end
|
|
|
|
describe '#unlock_writes' do
|
|
subject { described_class.new.lock_writes }
|
|
|
|
it 'does call Gitlab::Database::LockWritesManager.unlock_writes' do
|
|
expect(Gitlab::Database::LockWritesManager).to receive(:new)
|
|
.with(any_args).and_return(default_lock_writes_manager)
|
|
expect(default_lock_writes_manager).to receive(:unlock_writes)
|
|
expect(default_lock_writes_manager).not_to receive(:lock_writes)
|
|
|
|
subject
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'when running on multiple databases' do
|
|
before do
|
|
skip_if_multiple_databases_not_setup(:ci)
|
|
end
|
|
|
|
describe '#lock_writes' do
|
|
subject { described_class.new.lock_writes }
|
|
|
|
it_behaves_like 'lock tables', :gitlab_ci, 'main'
|
|
it_behaves_like 'lock tables', :gitlab_main, 'ci'
|
|
it_behaves_like 'lock tables', :gitlab_main_clusterwide, 'ci'
|
|
|
|
it_behaves_like 'unlock tables', :gitlab_main_clusterwide, 'main'
|
|
it_behaves_like 'unlock tables', :gitlab_main, 'main'
|
|
it_behaves_like 'unlock tables', :gitlab_ci, 'ci'
|
|
it_behaves_like 'unlock tables', :gitlab_shared, 'main'
|
|
it_behaves_like 'unlock tables', :gitlab_shared, 'ci'
|
|
it_behaves_like 'unlock tables', :gitlab_internal, 'main'
|
|
it_behaves_like 'unlock tables', :gitlab_internal, 'ci'
|
|
|
|
gitlab_main_partition = "#{Gitlab::Database::DYNAMIC_PARTITIONS_SCHEMA}.security_findings_test_partition"
|
|
it_behaves_like 'unlock partitions', gitlab_main_partition, 'main'
|
|
it_behaves_like 'lock partitions', gitlab_main_partition, 'ci'
|
|
|
|
gitlab_main_detached_partition = "#{Gitlab::Database::DYNAMIC_PARTITIONS_SCHEMA}._test_gitlab_main_part_20220101"
|
|
it_behaves_like 'unlock partitions', gitlab_main_detached_partition, 'main'
|
|
it_behaves_like 'lock partitions', gitlab_main_detached_partition, 'ci'
|
|
end
|
|
|
|
describe '#unlock_writes' do
|
|
subject { described_class.new.unlock_writes }
|
|
|
|
it_behaves_like "unlock tables", :gitlab_ci, 'main'
|
|
it_behaves_like "unlock tables", :gitlab_main, 'ci'
|
|
it_behaves_like "unlock tables", :gitlab_main, 'main'
|
|
it_behaves_like "unlock tables", :gitlab_ci, 'ci'
|
|
it_behaves_like "unlock tables", :gitlab_shared, 'main'
|
|
it_behaves_like "unlock tables", :gitlab_shared, 'ci'
|
|
it_behaves_like "unlock tables", :gitlab_internal, 'main'
|
|
it_behaves_like "unlock tables", :gitlab_internal, 'ci'
|
|
|
|
gitlab_main_partition = "#{Gitlab::Database::DYNAMIC_PARTITIONS_SCHEMA}.security_findings_test_partition"
|
|
it_behaves_like 'unlock partitions', gitlab_main_partition, 'main'
|
|
it_behaves_like 'unlock partitions', gitlab_main_partition, 'ci'
|
|
|
|
gitlab_main_detached_partition = "#{Gitlab::Database::DYNAMIC_PARTITIONS_SCHEMA}._test_gitlab_main_part_20220101"
|
|
it_behaves_like 'unlock partitions', gitlab_main_detached_partition, 'main'
|
|
it_behaves_like 'unlock partitions', gitlab_main_detached_partition, 'ci'
|
|
end
|
|
|
|
context 'when running in dry_run mode' do
|
|
subject { described_class.new(dry_run: true).lock_writes }
|
|
|
|
it 'passes dry_run flag to LockWritesManager' do
|
|
expect(Gitlab::Database::LockWritesManager).to receive(:new).with(
|
|
table_name: 'security_findings',
|
|
connection: anything,
|
|
database_name: 'ci',
|
|
with_retries: true,
|
|
logger: anything,
|
|
dry_run: true
|
|
).and_return(default_lock_writes_manager)
|
|
expect(default_lock_writes_manager).to receive(:lock_writes)
|
|
|
|
subject
|
|
end
|
|
end
|
|
|
|
context 'when running on multiple shared databases' do
|
|
subject { described_class.new.lock_writes }
|
|
|
|
before do
|
|
allow(::Gitlab::Database).to receive(:db_config_share_with).and_return(nil)
|
|
ci_db_config = Ci::ApplicationRecord.connection_db_config
|
|
allow(::Gitlab::Database).to receive(:db_config_share_with).with(ci_db_config).and_return('main')
|
|
end
|
|
|
|
it 'does not lock any tables if the ci database is shared with main database' do
|
|
expect(Gitlab::Database::LockWritesManager).to receive(:new)
|
|
.with(any_args).and_return(default_lock_writes_manager)
|
|
expect(default_lock_writes_manager).not_to receive(:lock_writes)
|
|
|
|
subject
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'when geo database is configured' do
|
|
let(:geo_table) do
|
|
Gitlab::Database::GitlabSchema
|
|
.tables_to_schema.filter_map { |table_name, schema| table_name if schema == :gitlab_geo }
|
|
.first
|
|
end
|
|
|
|
subject { described_class.new.unlock_writes }
|
|
|
|
before do
|
|
skip "Geo database is not configured" unless Gitlab::Database.has_config?(:geo)
|
|
end
|
|
|
|
it 'does not lock table in geo database' do
|
|
expect(Gitlab::Database::LockWritesManager).not_to receive(:new).with(
|
|
table_name: geo_table,
|
|
connection: anything,
|
|
database_name: 'geo',
|
|
with_retries: true,
|
|
logger: anything,
|
|
dry_run: anything
|
|
)
|
|
|
|
subject
|
|
end
|
|
end
|
|
end
|