diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-02-20 16:49:51 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-02-20 16:49:51 +0300 |
commit | 71786ddc8e28fbd3cb3fcc4b3ff15e5962a1c82e (patch) | |
tree | 6a2d93ef3fb2d353bb7739e4b57e6541f51cdd71 /spec/lib/backup/database_spec.rb | |
parent | a7253423e3403b8c08f8a161e5937e1488f5f407 (diff) |
Add latest changes from gitlab-org/gitlab@15-9-stable-eev15.9.0-rc42
Diffstat (limited to 'spec/lib/backup/database_spec.rb')
-rw-r--r-- | spec/lib/backup/database_spec.rb | 219 |
1 files changed, 191 insertions, 28 deletions
diff --git a/spec/lib/backup/database_spec.rb b/spec/lib/backup/database_spec.rb index ed5b34b7f8c..c70d47e4940 100644 --- a/spec/lib/backup/database_spec.rb +++ b/spec/lib/backup/database_spec.rb @@ -2,11 +2,23 @@ require 'spec_helper' -RSpec.describe Backup::Database do +RSpec.configure do |rspec| + rspec.expect_with :rspec do |c| + c.max_formatted_output_length = nil + end +end + +RSpec.describe Backup::Database, feature_category: :backup_restore do let(:progress) { StringIO.new } let(:output) { progress.string } + let(:one_db_configured?) { Gitlab::Database.database_base_models.one? } + let(:database_models_for_backup) { Gitlab::Database.database_base_models_with_gitlab_shared } + let(:timeout_service) do + instance_double(Gitlab::Database::TransactionTimeoutSettings, restore_timeouts: nil, disable_timeouts: nil) + end before(:all) do + Rake::Task.define_task(:environment) Rake.application.rake_require 'active_record/railties/databases' Rake.application.rake_require 'tasks/gitlab/backup' Rake.application.rake_require 'tasks/gitlab/shell' @@ -14,14 +26,136 @@ RSpec.describe Backup::Database do Rake.application.rake_require 'tasks/cache' end + describe '#dump', :delete do + let(:backup_id) { 'some_id' } + let(:force) { true } + + subject { described_class.new(progress, force: force) } + + before do + database_models_for_backup.each do |database_name, base_model| + base_model.connection.rollback_transaction unless base_model.connection.open_transactions.zero? + allow(base_model.connection).to receive(:execute).and_call_original + end + end + + it 'creates gzipped database dumps' do + Dir.mktmpdir do |dir| + subject.dump(dir, backup_id) + + database_models_for_backup.each_key do |database_name| + filename = database_name == 'main' ? 'database.sql.gz' : "#{database_name}_database.sql.gz" + expect(File.exist?(File.join(dir, filename))).to eq(true) + end + end + end + + it 'uses snapshots' do + Dir.mktmpdir do |dir| + base_model = Gitlab::Database.database_base_models['main'] + expect(base_model.connection).to receive(:begin_transaction).with( + isolation: :repeatable_read + ).and_call_original + expect(base_model.connection).to receive(:execute).with( + "SELECT pg_export_snapshot() as snapshot_id;" + ).and_call_original + expect(base_model.connection).to receive(:rollback_transaction).and_call_original + + subject.dump(dir, backup_id) + end + end + + it 'disables transaction time out' do + number_of_databases = Gitlab::Database.database_base_models_with_gitlab_shared.count + expect(Gitlab::Database::TransactionTimeoutSettings) + .to receive(:new).exactly(2 * number_of_databases).times.and_return(timeout_service) + expect(timeout_service).to receive(:disable_timeouts).exactly(number_of_databases).times + expect(timeout_service).to receive(:restore_timeouts).exactly(number_of_databases).times + + Dir.mktmpdir do |dir| + subject.dump(dir, backup_id) + end + end + + describe 'pg_dump arguments' do + let(:snapshot_id) { 'fake_id' } + let(:pg_args) do + [ + '--clean', + '--if-exists', + "--snapshot=#{snapshot_id}" + ] + end + + let(:dumper) { double } + let(:destination_dir) { 'tmp' } + + before do + allow(Backup::Dump::Postgres).to receive(:new).and_return(dumper) + allow(dumper).to receive(:dump).with(any_args).and_return(true) + + database_models_for_backup.each do |database_name, base_model| + allow(base_model.connection).to receive(:execute).with( + "SELECT pg_export_snapshot() as snapshot_id;" + ).and_return(['snapshot_id' => snapshot_id]) + end + end + + it 'calls Backup::Dump::Postgres with correct pg_dump arguments' do + expect(dumper).to receive(:dump).with(anything, anything, pg_args) + + subject.dump(destination_dir, backup_id) + end + + context 'when a PostgreSQL schema is used' do + let(:schema) { 'gitlab' } + let(:additional_args) do + pg_args + ['-n', schema] + Gitlab::Database::EXTRA_SCHEMAS.flat_map do |schema| + ['-n', schema.to_s] + end + end + + before do + allow(Gitlab.config.backup).to receive(:pg_schema).and_return(schema) + end + + it 'calls Backup::Dump::Postgres with correct pg_dump arguments' do + expect(dumper).to receive(:dump).with(anything, anything, additional_args) + + subject.dump(destination_dir, backup_id) + end + end + end + + context 'when a StandardError (or descendant) is raised' do + before do + allow(FileUtils).to receive(:mkdir_p).and_raise(StandardError) + end + + it 'restores timeouts' do + Dir.mktmpdir do |dir| + number_of_databases = Gitlab::Database.database_base_models_with_gitlab_shared.count + expect(Gitlab::Database::TransactionTimeoutSettings) + .to receive(:new).exactly(number_of_databases).times.and_return(timeout_service) + expect(timeout_service).to receive(:restore_timeouts).exactly(number_of_databases).times + + expect { subject.dump(dir, backup_id) }.to raise_error StandardError + end + end + end + end + describe '#restore' do let(:cmd) { %W[#{Gem.ruby} -e $stdout.puts(1)] } - let(:data) { Rails.root.join("spec/fixtures/pages_empty.tar.gz").to_s } + let(:backup_dir) { Rails.root.join("spec/fixtures/") } let(:force) { true } + let(:rake_task) { instance_double(Rake::Task, invoke: true) } - subject { described_class.new(Gitlab::Database::MAIN_DATABASE_NAME.to_sym, progress, force: force) } + subject { described_class.new(progress, force: force) } before do + allow(Rake::Task).to receive(:[]).with(any_args).and_return(rake_task) + allow(subject).to receive(:pg_restore_cmd).and_return(cmd) end @@ -30,9 +164,14 @@ RSpec.describe Backup::Database do it 'warns the user and waits' do expect(subject).to receive(:sleep) - expect(Rake::Task['gitlab:db:drop_tables']).to receive(:invoke) - subject.restore(data) + if one_db_configured? + expect(Rake::Task['gitlab:db:drop_tables']).to receive(:invoke) + else + expect(Rake::Task['gitlab:db:drop_tables:main']).to receive(:invoke) + end + + subject.restore(backup_dir) expect(output).to include('Removing all tables. Press `Ctrl-C` within 5 seconds to abort') end @@ -43,12 +182,14 @@ RSpec.describe Backup::Database do end context 'with an empty .gz file' do - let(:data) { Rails.root.join("spec/fixtures/pages_empty.tar.gz").to_s } - it 'returns successfully' do - expect(Rake::Task['gitlab:db:drop_tables']).to receive(:invoke) + if one_db_configured? + expect(Rake::Task['gitlab:db:drop_tables']).to receive(:invoke) + else + expect(Rake::Task['gitlab:db:drop_tables:main']).to receive(:invoke) + end - subject.restore(data) + subject.restore(backup_dir) expect(output).to include("Restoring PostgreSQL database") expect(output).to include("[DONE]") @@ -57,12 +198,18 @@ RSpec.describe Backup::Database do end context 'with a corrupted .gz file' do - let(:data) { Rails.root.join("spec/fixtures/big-image.png").to_s } + before do + allow(subject).to receive(:file_name).and_return("#{backup_dir}big-image.png") + end it 'raises a backup error' do - expect(Rake::Task['gitlab:db:drop_tables']).to receive(:invoke) + if one_db_configured? + expect(Rake::Task['gitlab:db:drop_tables']).to receive(:invoke) + else + expect(Rake::Task['gitlab:db:drop_tables:main']).to receive(:invoke) + end - expect { subject.restore(data) }.to raise_error(Backup::Error) + expect { subject.restore(backup_dir) }.to raise_error(Backup::Error) end end @@ -72,9 +219,13 @@ RSpec.describe Backup::Database do let(:cmd) { %W[#{Gem.ruby} -e $stderr.write("#{noise}#{visible_error}")] } it 'filters out noise from errors and has a post restore warning' do - expect(Rake::Task['gitlab:db:drop_tables']).to receive(:invoke) + if one_db_configured? + expect(Rake::Task['gitlab:db:drop_tables']).to receive(:invoke) + else + expect(Rake::Task['gitlab:db:drop_tables:main']).to receive(:invoke) + end - subject.restore(data) + subject.restore(backup_dir) expect(output).to include("ERRORS") expect(output).not_to include(noise) @@ -95,9 +246,13 @@ RSpec.describe Backup::Database do end it 'overrides default config values' do - expect(Rake::Task['gitlab:db:drop_tables']).to receive(:invoke) + if one_db_configured? + expect(Rake::Task['gitlab:db:drop_tables']).to receive(:invoke) + else + expect(Rake::Task['gitlab:db:drop_tables:main']).to receive(:invoke) + end - subject.restore(data) + subject.restore(backup_dir) expect(output).to include(%("PGHOST"=>"test.example.com")) expect(output).to include(%("PGPASSWORD"=>"donotchange")) @@ -107,22 +262,30 @@ RSpec.describe Backup::Database do end context 'when the source file is missing' do - let(:main_database) { described_class.new(Gitlab::Database::MAIN_DATABASE_NAME.to_sym, progress, force: force) } - let(:ci_database) { described_class.new(Gitlab::Database::CI_DATABASE_NAME.to_sym, progress, force: force) } - let(:missing_file) { Rails.root.join("spec/fixtures/missing_file.tar.gz").to_s } + context 'for main database' do + before do + allow(File).to receive(:exist?).and_call_original + allow(File).to receive(:exist?).with("#{backup_dir}database.sql.gz").and_return(false) + allow(File).to receive(:exist?).with("#{backup_dir}ci_database.sql.gz").and_return(false) + end - it 'main database raises an error about missing source file' do - expect(Rake::Task['gitlab:db:drop_tables']).not_to receive(:invoke) + it 'raises an error about missing source file' do + if one_db_configured? + expect(Rake::Task['gitlab:db:drop_tables']).not_to receive(:invoke) + else + expect(Rake::Task['gitlab:db:drop_tables:main']).not_to receive(:invoke) + end - expect do - main_database.restore(missing_file) - end.to raise_error(Backup::Error, /Source database file does not exist/) + expect do + subject.restore('db') + end.to raise_error(Backup::Error, /Source database file does not exist/) + end end - it 'ci database tolerates missing source file' do - expect(Rake::Task['gitlab:db:drop_tables']).not_to receive(:invoke) - skip_if_multiple_databases_not_setup - expect { ci_database.restore(missing_file) }.not_to raise_error + context 'for ci database' do + it 'ci database tolerates missing source file' do + expect { subject.restore(backup_dir) }.not_to raise_error + end end end end |