Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'spec/tasks/gitlab')
-rw-r--r--spec/tasks/gitlab/background_migrations_rake_spec.rb2
-rw-r--r--spec/tasks/gitlab/backup_rake_spec.rb2
-rw-r--r--spec/tasks/gitlab/db/decomposition/rollback/bump_ci_sequences_rake_spec.rb103
-rw-r--r--spec/tasks/gitlab/db/lock_writes_rake_spec.rb177
-rw-r--r--spec/tasks/gitlab/db/validate_config_rake_spec.rb41
-rw-r--r--spec/tasks/gitlab/db_rake_spec.rb17
-rw-r--r--spec/tasks/gitlab/pages_rake_spec.rb80
7 files changed, 329 insertions, 93 deletions
diff --git a/spec/tasks/gitlab/background_migrations_rake_spec.rb b/spec/tasks/gitlab/background_migrations_rake_spec.rb
index 36623e86f27..bbd33f71e60 100644
--- a/spec/tasks/gitlab/background_migrations_rake_spec.rb
+++ b/spec/tasks/gitlab/background_migrations_rake_spec.rb
@@ -112,7 +112,7 @@ RSpec.describe 'gitlab:background_migrations namespace rake tasks' do
let(:main_database_name) { Gitlab::Database::MAIN_DATABASE_NAME }
let(:model) { Gitlab::Database.database_base_models[main_database_name] }
let(:connection) { double(:connection) }
- let(:base_models) { { 'main' => model } }
+ let(:base_models) { { 'main' => model }.with_indifferent_access }
around do |example|
Gitlab::Database::SharedModel.using_connection(model.connection) do
diff --git a/spec/tasks/gitlab/backup_rake_spec.rb b/spec/tasks/gitlab/backup_rake_spec.rb
index 52a0a9a7385..4a3b81a072f 100644
--- a/spec/tasks/gitlab/backup_rake_spec.rb
+++ b/spec/tasks/gitlab/backup_rake_spec.rb
@@ -465,7 +465,7 @@ RSpec.describe 'gitlab:app namespace rake task', :delete do
stub_env('GITLAB_BACKUP_MAX_STORAGE_CONCURRENCY', 2)
expect(::Backup::Repositories).to receive(:new)
- .with(anything, strategy: anything, storages: [])
+ .with(anything, strategy: anything, storages: [], paths: [])
.and_call_original
expect(::Backup::GitalyBackup).to receive(:new).with(anything, max_parallelism: 5, storage_parallelism: 2, incremental: false).and_call_original
diff --git a/spec/tasks/gitlab/db/decomposition/rollback/bump_ci_sequences_rake_spec.rb b/spec/tasks/gitlab/db/decomposition/rollback/bump_ci_sequences_rake_spec.rb
new file mode 100644
index 00000000000..29b80176ef8
--- /dev/null
+++ b/spec/tasks/gitlab/db/decomposition/rollback/bump_ci_sequences_rake_spec.rb
@@ -0,0 +1,103 @@
+# frozen_string_literal: true
+
+require 'rake_helper'
+
+RSpec.describe 'gitlab:db:decomposition:rollback:bump_ci_sequences', :silence_stdout do
+ before :all do
+ Rake.application.rake_require 'tasks/gitlab/db/decomposition/rollback/bump_ci_sequences'
+
+ # empty task as env is already loaded
+ Rake::Task.define_task :environment
+ end
+
+ let(:expected_error_message) do
+ <<-EOS.strip_heredoc
+ Please specify a positive integer `increase_by` value
+ Example: rake gitlab:db:decomposition:rollback:bump_ci_sequences[100000]
+ EOS
+ end
+
+ let(:main_sequence_name) { 'issues_id_seq' }
+ let(:ci_sequence_name) { 'ci_build_needs_id_seq' }
+
+ # This is just to make sure that all of the sequences start with `is_called=True`
+ # which means that the next call to nextval() is going to increment the sequence.
+ # To give predictable test results.
+ before do
+ ApplicationRecord.connection.select_value("select nextval($1)", nil, [ci_sequence_name])
+ end
+
+ context 'when passing wrong argument' do
+ it 'will print an error message and exit when passing no argument' do
+ expect do
+ run_rake_task('gitlab:db:decomposition:rollback:bump_ci_sequences')
+ end.to raise_error(SystemExit) { |error| expect(error.status).to eq(1) }
+ .and output(expected_error_message).to_stdout
+ end
+
+ it 'will print an error message and exit when passing a non positive integer value' do
+ expect do
+ run_rake_task('gitlab:db:decomposition:rollback:bump_ci_sequences', '-5')
+ end.to raise_error(SystemExit) { |error| expect(error.status).to eq(1) }
+ .and output(expected_error_message).to_stdout
+ end
+ end
+
+ context 'when bumping the ci sequences' do
+ it 'changes ci sequences by the passed argument `increase_by` value on the main database' do
+ expect do
+ run_rake_task('gitlab:db:decomposition:rollback:bump_ci_sequences', '15')
+ end.to change {
+ last_value_of_sequence(ApplicationRecord.connection, ci_sequence_name)
+ }.by(16) # the +1 is because the sequence has is_called = true
+ end
+
+ it 'will still increase the value of sequences that have is_called = False' do
+ # see `is_called`: https://www.postgresql.org/docs/12/functions-sequence.html
+ # choosing a new arbitrary value for the sequence
+ new_value = last_value_of_sequence(ApplicationRecord.connection, ci_sequence_name) + 1000
+ ApplicationRecord.connection.select_value("select setval($1, $2, false)", nil, [ci_sequence_name, new_value])
+ expect do
+ run_rake_task('gitlab:db:decomposition:rollback:bump_ci_sequences', '15')
+ end.to change {
+ last_value_of_sequence(ApplicationRecord.connection, ci_sequence_name)
+ }.by(15)
+ end
+
+ it 'resets the INCREMENT value of the sequences back to 1 for the following calls to nextval()' do
+ run_rake_task('gitlab:db:decomposition:rollback:bump_ci_sequences', '15')
+ value_1 = ApplicationRecord.connection.select_value("select nextval($1)", nil, [ci_sequence_name])
+ value_2 = ApplicationRecord.connection.select_value("select nextval($1)", nil, [ci_sequence_name])
+ expect(value_2 - value_1).to eq(1)
+ end
+
+ it 'does not change the sequences on the gitlab_main tables' do
+ expect do
+ run_rake_task('gitlab:db:decomposition:rollback:bump_ci_sequences', '10')
+ end.to change {
+ last_value_of_sequence(ApplicationRecord.connection, main_sequence_name)
+ }.by(0)
+ .and change {
+ last_value_of_sequence(ApplicationRecord.connection, ci_sequence_name)
+ }.by(11) # the +1 is because the sequence has is_called = true
+ end
+ end
+
+ context 'when multiple databases' do
+ before do
+ skip_if_multiple_databases_not_setup
+ end
+
+ it 'does not change ci sequences on the ci database' do
+ expect do
+ run_rake_task('gitlab:db:decomposition:rollback:bump_ci_sequences', '10')
+ end.to change {
+ last_value_of_sequence(Ci::ApplicationRecord.connection, ci_sequence_name)
+ }.by(0)
+ end
+ end
+end
+
+def last_value_of_sequence(connection, sequence_name)
+ connection.select_value("select last_value from #{sequence_name}")
+end
diff --git a/spec/tasks/gitlab/db/lock_writes_rake_spec.rb b/spec/tasks/gitlab/db/lock_writes_rake_spec.rb
new file mode 100644
index 00000000000..034c520887e
--- /dev/null
+++ b/spec/tasks/gitlab/db/lock_writes_rake_spec.rb
@@ -0,0 +1,177 @@
+# frozen_string_literal: true
+
+require 'rake_helper'
+
+RSpec.describe 'gitlab:db:lock_writes', :silence_stdout, :reestablished_active_record_base do
+ before :all do
+ Rake.application.rake_require 'active_record/railties/databases'
+ Rake.application.rake_require 'tasks/seed_fu'
+ Rake.application.rake_require 'tasks/gitlab/db/validate_config'
+ Rake.application.rake_require 'tasks/gitlab/db/lock_writes'
+
+ # empty task as env is already loaded
+ Rake::Task.define_task :environment
+ end
+
+ let!(:project) { create(:project) }
+ let!(:ci_build) { create(:ci_build) }
+ let(:main_connection) { ApplicationRecord.connection }
+ let(:ci_connection) { Ci::ApplicationRecord.connection }
+
+ context 'single database' do
+ before do
+ skip_if_multiple_databases_are_setup
+ end
+
+ context 'when locking writes' do
+ it 'does not add any triggers to the main schema tables' do
+ expect do
+ run_rake_task('gitlab:db:lock_writes')
+ end.to change {
+ number_of_triggers(main_connection)
+ }.by(0)
+ end
+
+ it 'will be still able to modify tables that belong to the main two schemas' do
+ run_rake_task('gitlab:db:lock_writes')
+ expect do
+ Project.last.touch
+ Ci::Build.last.touch
+ end.not_to raise_error
+ end
+ end
+ end
+
+ context 'multiple databases' do
+ before do
+ skip_if_multiple_databases_not_setup
+ end
+
+ context 'when locking writes' do
+ it 'adds 3 triggers to the ci schema tables on the main database' do
+ expect do
+ run_rake_task('gitlab:db:lock_writes')
+ end.to change {
+ number_of_triggers_on(main_connection, Ci::Build.table_name)
+ }.by(3) # Triggers to block INSERT / UPDATE / DELETE
+ # Triggers on TRUNCATE are not added to the information_schema.triggers
+ # See https://www.postgresql.org/message-id/16934.1568989957%40sss.pgh.pa.us
+ end
+
+ it 'adds 3 triggers to the main schema tables on the ci database' do
+ expect do
+ run_rake_task('gitlab:db:lock_writes')
+ end.to change {
+ number_of_triggers_on(ci_connection, Project.table_name)
+ }.by(3) # Triggers to block INSERT / UPDATE / DELETE
+ # Triggers on TRUNCATE are not added to the information_schema.triggers
+ # See https://www.postgresql.org/message-id/16934.1568989957%40sss.pgh.pa.us
+ end
+
+ it 'still allows writes on the tables with the correct connections' do
+ Project.update_all(updated_at: Time.now)
+ Ci::Build.update_all(updated_at: Time.now)
+ end
+
+ it 'still allows writing to gitlab_shared schema on any connection' do
+ connections = [main_connection, ci_connection]
+ connections.each do |connection|
+ Gitlab::Database::SharedModel.using_connection(connection) do
+ LooseForeignKeys::DeletedRecord.create!(
+ fully_qualified_table_name: "public.projects",
+ primary_key_value: 1,
+ cleanup_attempts: 0
+ )
+ end
+ end
+ end
+
+ it 'prevents writes on the main tables on the ci database' do
+ run_rake_task('gitlab:db:lock_writes')
+ expect do
+ ci_connection.execute("delete from projects")
+ end.to raise_error(ActiveRecord::StatementInvalid, /Table: "projects" is write protected/)
+ end
+
+ it 'prevents writes on the ci tables on the main database' do
+ run_rake_task('gitlab:db:lock_writes')
+ expect do
+ main_connection.execute("delete from ci_builds")
+ end.to raise_error(ActiveRecord::StatementInvalid, /Table: "ci_builds" is write protected/)
+ end
+
+ it 'prevents truncating a ci table on the main database' do
+ run_rake_task('gitlab:db:lock_writes')
+ expect do
+ main_connection.execute("truncate ci_build_needs")
+ end.to raise_error(ActiveRecord::StatementInvalid, /Table: "ci_build_needs" is write protected/)
+ end
+
+ it 'retries again if it receives a statement_timeout a few number of times' do
+ error_message = "PG::QueryCanceled: ERROR: canceling statement due to statement timeout"
+ call_count = 0
+ allow(main_connection).to receive(:execute) do |statement|
+ if statement.include?("CREATE TRIGGER")
+ call_count += 1
+ raise(ActiveRecord::QueryCanceled, error_message) if call_count.even?
+ end
+ end
+ run_rake_task('gitlab:db:lock_writes')
+ end
+
+ it 'raises the exception if it happened many times' do
+ error_message = "PG::QueryCanceled: ERROR: canceling statement due to statement timeout"
+ allow(main_connection).to receive(:execute) do |statement|
+ if statement.include?("CREATE TRIGGER")
+ raise(ActiveRecord::QueryCanceled, error_message)
+ end
+ end
+
+ expect do
+ run_rake_task('gitlab:db:lock_writes')
+ end.to raise_error(ActiveRecord::QueryCanceled)
+ end
+ end
+
+ context 'when unlocking writes' do
+ before do
+ run_rake_task('gitlab:db:lock_writes')
+ end
+
+ it 'removes the write protection triggers from the gitlab_main tables on the ci database' do
+ expect do
+ run_rake_task('gitlab:db:unlock_writes')
+ end.to change {
+ number_of_triggers_on(ci_connection, Project.table_name)
+ }.by(-3) # Triggers to block INSERT / UPDATE / DELETE
+ # Triggers on TRUNCATE are not added to the information_schema.triggers
+ # See https://www.postgresql.org/message-id/16934.1568989957%40sss.pgh.pa.us
+
+ expect do
+ ci_connection.execute("delete from projects")
+ end.not_to raise_error
+ end
+
+ it 'removes the write protection triggers from the gitlab_ci tables on the main database' do
+ expect do
+ run_rake_task('gitlab:db:unlock_writes')
+ end.to change {
+ number_of_triggers_on(main_connection, Ci::Build.table_name)
+ }.by(-3)
+
+ expect do
+ main_connection.execute("delete from ci_builds")
+ end.not_to raise_error
+ end
+ end
+ end
+
+ def number_of_triggers(connection)
+ connection.select_value("SELECT count(*) FROM information_schema.triggers")
+ end
+
+ def number_of_triggers_on(connection, table_name)
+ connection
+ .select_value("SELECT count(*) FROM information_schema.triggers WHERE event_object_table=$1", nil, [table_name])
+ end
+end
diff --git a/spec/tasks/gitlab/db/validate_config_rake_spec.rb b/spec/tasks/gitlab/db/validate_config_rake_spec.rb
index 0b2c844a91f..03d7504e8b1 100644
--- a/spec/tasks/gitlab/db/validate_config_rake_spec.rb
+++ b/spec/tasks/gitlab/db/validate_config_rake_spec.rb
@@ -3,6 +3,10 @@
require 'rake_helper'
RSpec.describe 'gitlab:db:validate_config', :silence_stdout do
+ # We don't need to delete this data since it only modifies `ar_internal_metadata`
+ # which would not be cleaned either by `DbCleaner`
+ self.use_transactional_tests = false
+
before :all do
Rake.application.rake_require 'active_record/railties/databases'
Rake.application.rake_require 'tasks/seed_fu'
@@ -111,6 +115,26 @@ RSpec.describe 'gitlab:db:validate_config', :silence_stdout do
end
it_behaves_like 'validates successfully'
+
+ context 'when config is pointing to incorrect server' do
+ let(:test_config) do
+ {
+ main: main_database_config.merge(port: 11235)
+ }
+ end
+
+ it_behaves_like 'validates successfully'
+ end
+
+ context 'when config is pointing to non-existent database' do
+ let(:test_config) do
+ {
+ main: main_database_config.merge(database: 'non_existent_database')
+ }
+ end
+
+ it_behaves_like 'validates successfully'
+ end
end
context 'when main: uses database_tasks=false' do
@@ -181,6 +205,23 @@ RSpec.describe 'gitlab:db:validate_config', :silence_stdout do
it_behaves_like 'raises an error', /The 'ci' since it is using 'database_tasks: false' should share database with 'main:'/
end
end
+
+ context 'one of the databases is in read-only mode' do
+ let(:test_config) do
+ {
+ main: main_database_config
+ }
+ end
+
+ let(:exception) { ActiveRecord::StatementInvalid.new("READONLY") }
+
+ before do
+ allow(exception).to receive(:cause).and_return(PG::ReadOnlySqlTransaction.new("cannot execute INSERT in a read-only transaction"))
+ allow(ActiveRecord::InternalMetadata).to receive(:upsert).at_least(:once).and_raise(exception)
+ end
+
+ it_behaves_like 'validates successfully'
+ end
end
%w[db:migrate db:schema:load db:schema:dump].each do |task|
diff --git a/spec/tasks/gitlab/db_rake_spec.rb b/spec/tasks/gitlab/db_rake_spec.rb
index e340d568269..d8199c09ca1 100644
--- a/spec/tasks/gitlab/db_rake_spec.rb
+++ b/spec/tasks/gitlab/db_rake_spec.rb
@@ -822,18 +822,20 @@ RSpec.describe 'gitlab:db namespace rake task', :silence_stdout do
let(:connection_pool) { instance_double(ActiveRecord::ConnectionAdapters::ConnectionPool ) }
let(:connection) { instance_double(ActiveRecord::ConnectionAdapters::PostgreSQLAdapter) }
let(:configurations) { double(ActiveRecord::DatabaseConfigurations) }
- let(:configuration) { instance_double(ActiveRecord::DatabaseConfigurations::HashConfig) }
+ let(:configuration) { instance_double(ActiveRecord::DatabaseConfigurations::HashConfig, env_name: 'test', name: 'main') }
let(:config_hash) { { username: 'foo' } }
- it 'migrate as nonsuperuser check with default username' do
+ before do
allow(Rake::Task['db:drop']).to receive(:invoke)
allow(Rake::Task['db:create']).to receive(:invoke)
allow(ActiveRecord::Base).to receive(:configurations).and_return(configurations)
allow(configurations).to receive(:configs_for).and_return([configuration])
allow(configuration).to receive(:configuration_hash).and_return(config_hash)
allow(ActiveRecord::Base).to receive(:establish_connection).and_return(connection_pool)
+ end
- expect(config_hash).to receive(:merge).with({ username: 'gitlab' })
+ it 'migrate as nonsuperuser check with default username' do
+ expect(config_hash).to receive(:merge).with({ username: 'gitlab' }).and_call_original
expect(Gitlab::Database).to receive(:check_for_non_superuser)
expect(Rake::Task['db:migrate']).to receive(:invoke)
@@ -841,14 +843,7 @@ RSpec.describe 'gitlab:db namespace rake task', :silence_stdout do
end
it 'migrate as nonsuperuser check with specified username' do
- allow(Rake::Task['db:drop']).to receive(:invoke)
- allow(Rake::Task['db:create']).to receive(:invoke)
- allow(ActiveRecord::Base).to receive(:configurations).and_return(configurations)
- allow(configurations).to receive(:configs_for).and_return([configuration])
- allow(configuration).to receive(:configuration_hash).and_return(config_hash)
- allow(ActiveRecord::Base).to receive(:establish_connection).and_return(connection_pool)
-
- expect(config_hash).to receive(:merge).with({ username: 'foo' })
+ expect(config_hash).to receive(:merge).with({ username: 'foo' }).and_call_original
expect(Gitlab::Database).to receive(:check_for_non_superuser)
expect(Rake::Task['db:migrate']).to receive(:invoke)
diff --git a/spec/tasks/gitlab/pages_rake_spec.rb b/spec/tasks/gitlab/pages_rake_spec.rb
index d4bfcafa7b4..9e3d5c3ccf0 100644
--- a/spec/tasks/gitlab/pages_rake_spec.rb
+++ b/spec/tasks/gitlab/pages_rake_spec.rb
@@ -7,86 +7,6 @@ RSpec.describe 'gitlab:pages', :silence_stdout do
Rake.application.rake_require 'tasks/gitlab/pages'
end
- describe 'migrate_legacy_storage task' do
- subject { run_rake_task('gitlab:pages:migrate_legacy_storage') }
-
- it 'calls migration service' do
- expect_next_instance_of(::Pages::MigrateFromLegacyStorageService, anything,
- ignore_invalid_entries: false,
- mark_projects_as_not_deployed: false) do |service|
- expect(service).to receive(:execute_with_threads).with(threads: 3, batch_size: 10).and_call_original
- end
-
- subject
- end
-
- it 'uses PAGES_MIGRATION_THREADS environment variable' do
- stub_env('PAGES_MIGRATION_THREADS', '5')
-
- expect_next_instance_of(::Pages::MigrateFromLegacyStorageService, anything,
- ignore_invalid_entries: false,
- mark_projects_as_not_deployed: false) do |service|
- expect(service).to receive(:execute_with_threads).with(threads: 5, batch_size: 10).and_call_original
- end
-
- subject
- end
-
- it 'uses PAGES_MIGRATION_BATCH_SIZE environment variable' do
- stub_env('PAGES_MIGRATION_BATCH_SIZE', '100')
-
- expect_next_instance_of(::Pages::MigrateFromLegacyStorageService, anything,
- ignore_invalid_entries: false,
- mark_projects_as_not_deployed: false) do |service|
- expect(service).to receive(:execute_with_threads).with(threads: 3, batch_size: 100).and_call_original
- end
-
- subject
- end
-
- it 'uses PAGES_MIGRATION_IGNORE_INVALID_ENTRIES environment variable' do
- stub_env('PAGES_MIGRATION_IGNORE_INVALID_ENTRIES', 'true')
-
- expect_next_instance_of(::Pages::MigrateFromLegacyStorageService, anything,
- ignore_invalid_entries: true,
- mark_projects_as_not_deployed: false) do |service|
- expect(service).to receive(:execute_with_threads).with(threads: 3, batch_size: 10).and_call_original
- end
-
- subject
- end
-
- it 'uses PAGES_MIGRATION_MARK_PROJECTS_AS_NOT_DEPLOYED environment variable' do
- stub_env('PAGES_MIGRATION_MARK_PROJECTS_AS_NOT_DEPLOYED', 'true')
-
- expect_next_instance_of(::Pages::MigrateFromLegacyStorageService, anything,
- ignore_invalid_entries: false,
- mark_projects_as_not_deployed: true) do |service|
- expect(service).to receive(:execute_with_threads).with(threads: 3, batch_size: 10).and_call_original
- end
-
- subject
- end
- end
-
- describe 'clean_migrated_zip_storage task' do
- it 'removes only migrated deployments' do
- regular_deployment = create(:pages_deployment)
- migrated_deployment = create(:pages_deployment, :migrated)
-
- regular_deployment.project.update_pages_deployment!(regular_deployment)
- migrated_deployment.project.update_pages_deployment!(migrated_deployment)
-
- expect(PagesDeployment.all).to contain_exactly(regular_deployment, migrated_deployment)
-
- run_rake_task('gitlab:pages:clean_migrated_zip_storage')
-
- expect(PagesDeployment.all).to contain_exactly(regular_deployment)
- expect(PagesDeployment.find_by_id(regular_deployment.id)).not_to be_nil
- expect(PagesDeployment.find_by_id(migrated_deployment.id)).to be_nil
- end
- end
-
describe 'gitlab:pages:deployments:migrate_to_object_storage' do
subject { run_rake_task('gitlab:pages:deployments:migrate_to_object_storage') }