diff options
Diffstat (limited to 'spec/lib/gitlab/database/background_migration/batched_job_spec.rb')
-rw-r--r-- | spec/lib/gitlab/database/background_migration/batched_job_spec.rb | 112 |
1 files changed, 103 insertions, 9 deletions
diff --git a/spec/lib/gitlab/database/background_migration/batched_job_spec.rb b/spec/lib/gitlab/database/background_migration/batched_job_spec.rb index 7338ea657b9..8c663ff9f8a 100644 --- a/spec/lib/gitlab/database/background_migration/batched_job_spec.rb +++ b/spec/lib/gitlab/database/background_migration/batched_job_spec.rb @@ -5,6 +5,10 @@ require 'spec_helper' RSpec.describe Gitlab::Database::BackgroundMigration::BatchedJob, type: :model do it_behaves_like 'having unique enum values' + it { is_expected.to be_a Gitlab::Database::SharedModel } + + it { expect(described_class::TIMEOUT_EXCEPTIONS).to match_array [ActiveRecord::StatementTimeout, ActiveRecord::ConnectionTimeoutError, ActiveRecord::AdapterTimeout, ActiveRecord::LockWaitTimeout] } + describe 'associations' do it { is_expected.to belong_to(:batched_migration).with_foreign_key(:batched_background_migration_id) } it { is_expected.to have_many(:batched_job_transition_logs).with_foreign_key(:batched_background_migration_job_id) } @@ -13,6 +17,8 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedJob, type: :model d describe 'state machine' do let_it_be(:job) { create(:batched_background_migration_job, :failed) } + it { expect(described_class.state_machine.states.map(&:name)).to eql(%i(pending running failed succeeded)) } + context 'when a job is running' do it 'logs the transition' do expect(Gitlab::AppLogger).to receive(:info).with( { batched_job_id: job.id, message: 'BatchedJob transition', new_state: :running, previous_state: :failed } ) @@ -45,6 +51,51 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedJob, type: :model d end end + context 'when a job fails the number of max times' do + let(:max_times) { described_class::MAX_ATTEMPTS } + let!(:job) { create(:batched_background_migration_job, :running, batch_size: 10, min_value: 6, max_value: 15, attempts: max_times) } + + context 'when job can be split' do + let(:exception) { ActiveRecord::StatementTimeout.new('Timeout!') } + + before do + allow_next_instance_of(Gitlab::BackgroundMigration::BatchingStrategies::PrimaryKeyBatchingStrategy) do |batch_class| + allow(batch_class).to receive(:next_batch).and_return([6, 10]) + end + end + + it 'splits the job into two retriable jobs' do + expect { job.failure!(error: exception) }.to change { job.batched_migration.batched_jobs.retriable.count }.from(0).to(2) + end + end + + context 'when the job cannot be split' do + let(:exception) { ActiveRecord::StatementTimeout.new('Timeout!') } + let(:max_times) { described_class::MAX_ATTEMPTS } + let!(:job) { create(:batched_background_migration_job, :running, batch_size: 50, sub_batch_size: 20, min_value: 6, max_value: 15, attempts: max_times) } + let(:error_message) { 'Job cannot be split further' } + let(:split_and_retry_exception) { Gitlab::Database::BackgroundMigration::SplitAndRetryError.new(error_message) } + + before do + allow(job).to receive(:split_and_retry!).and_raise(split_and_retry_exception) + end + + it 'does not split the job' do + expect { job.failure!(error: exception) }.not_to change { job.batched_migration.batched_jobs.retriable.count } + end + + it 'keeps the same job attributes' do + expect { job.failure!(error: exception) }.not_to change { job } + end + + it 'logs the error' do + expect(Gitlab::AppLogger).to receive(:error).with( { message: error_message, batched_job_id: job.id } ) + + job.failure!(error: exception) + end + end + end + context 'when a job fails' do let(:job) { create(:batched_background_migration_job, :running) } @@ -145,6 +196,49 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedJob, type: :model d end end + describe '#can_split?' do + subject { job.can_split?(exception) } + + context 'when the number of attempts is greater than the limit and the batch_size is greater than the sub_batch_size' do + let(:job) { create(:batched_background_migration_job, :failed, batch_size: 4, sub_batch_size: 2, attempts: described_class::MAX_ATTEMPTS + 1) } + + context 'when is a timeout exception' do + let(:exception) { ActiveRecord::StatementTimeout.new } + + it { expect(subject).to be_truthy } + end + + context 'when is not a timeout exception' do + let(:exception) { RuntimeError.new } + + it { expect(subject).to be_falsey } + end + end + + context 'when the number of attempts is lower than the limit and the batch_size is greater than the sub_batch_size' do + let(:job) { create(:batched_background_migration_job, :failed, batch_size: 4, sub_batch_size: 2, attempts: described_class::MAX_ATTEMPTS - 1) } + + context 'when is a timeout exception' do + let(:exception) { ActiveRecord::StatementTimeout.new } + + it { expect(subject).to be_falsey } + end + + context 'when is not a timeout exception' do + let(:exception) { RuntimeError.new } + + it { expect(subject).to be_falsey } + end + end + + context 'when the batch_size is lower than the sub_batch_size' do + let(:job) { create(:batched_background_migration_job, :failed, batch_size: 2, sub_batch_size: 4) } + let(:exception) { ActiveRecord::StatementTimeout.new } + + it { expect(subject).to be_falsey } + end + end + describe '#time_efficiency' do subject { job.time_efficiency } @@ -197,15 +291,17 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedJob, type: :model d end describe '#split_and_retry!' do - let!(:job) { create(:batched_background_migration_job, :failed, batch_size: 10, min_value: 6, max_value: 15, attempts: 3) } + let_it_be(:migration) { create(:batched_background_migration, table_name: :events) } + let_it_be(:job) { create(:batched_background_migration_job, :failed, batched_migration: migration, batch_size: 10, min_value: 6, max_value: 15, attempts: 3) } + let_it_be(:project) { create(:project) } - context 'when job can be split' do - before do - allow_next_instance_of(Gitlab::BackgroundMigration::BatchingStrategies::PrimaryKeyBatchingStrategy) do |batch_class| - allow(batch_class).to receive(:next_batch).with(anything, anything, batch_min_value: 6, batch_size: 5).and_return([6, 10]) - end + before_all do + (6..16).each do |id| + create(:event, id: id, project: project) end + end + context 'when job can be split' do it 'sets the correct attributes' do expect { job.split_and_retry! }.to change { described_class.count }.by(1) @@ -261,9 +357,7 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedJob, type: :model d context 'when computed midpoint is larger than the max value of the batch' do before do - allow_next_instance_of(Gitlab::BackgroundMigration::BatchingStrategies::PrimaryKeyBatchingStrategy) do |batch_class| - allow(batch_class).to receive(:next_batch).with(anything, anything, batch_min_value: 6, batch_size: 5).and_return([6, 16]) - end + Event.where(id: 6..12).delete_all end it 'lowers the batch size and resets the number of attempts' do |