summaryrefslogtreecommitdiff
path: root/spec/lib/gitlab/database/background_migration/scheduler_spec.rb
blob: ba745acdf8a35a21f477b4395bac3e9592135eab (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Gitlab::Database::BackgroundMigration::Scheduler, '#perform' do
  let(:scheduler) { described_class.new }

  shared_examples_for 'it has no jobs to run' do
    it 'does not create and run a migration job' do
      test_wrapper = double('test wrapper')

      expect(test_wrapper).not_to receive(:perform)

      expect do
        scheduler.perform(migration_wrapper: test_wrapper)
      end.not_to change { Gitlab::Database::BackgroundMigration::BatchedJob.count }
    end
  end

  context 'when there are no active migrations' do
    let!(:migration) { create(:batched_background_migration, :finished) }

    it_behaves_like 'it has no jobs to run'
  end

  shared_examples_for 'it has completed the migration' do
    it 'marks the migration as finished' do
      relation = Gitlab::Database::BackgroundMigration::BatchedMigration.finished.where(id: first_migration.id)

      expect { scheduler.perform }.to change { relation.count }.by(1)
    end
  end

  context 'when there are active migrations' do
    let!(:first_migration) { create(:batched_background_migration, :active, batch_size: 2) }
    let!(:last_migration) { create(:batched_background_migration, :active) }

    let(:job_relation) do
      Gitlab::Database::BackgroundMigration::BatchedJob.where(batched_background_migration_id: first_migration.id)
    end

    context 'when the migration interval has not elapsed' do
      before do
        expect_next_found_instance_of(Gitlab::Database::BackgroundMigration::BatchedMigration) do |migration|
          expect(migration).to receive(:interval_elapsed?).and_return(false)
        end
      end

      it_behaves_like 'it has no jobs to run'
    end

    context 'when the interval has elapsed' do
      before do
        expect_next_found_instance_of(Gitlab::Database::BackgroundMigration::BatchedMigration) do |migration|
          expect(migration).to receive(:interval_elapsed?).and_return(true)
        end
      end

      context 'when the first migration has no previous jobs' do
        context 'when the migration has batches to process' do
          let!(:event1) { create(:event) }
          let!(:event2) { create(:event) }
          let!(:event3) { create(:event) }

          it 'runs the job for the first batch' do
            first_migration.update!(min_value: event1.id, max_value: event3.id)

            expect_next_instance_of(Gitlab::Database::BackgroundMigration::BatchedMigrationWrapper) do |wrapper|
              expect(wrapper).to receive(:perform).and_wrap_original do |_, job_record|
                expect(job_record).to eq(job_relation.first)
              end
            end

            expect { scheduler.perform }.to change { job_relation.count }.by(1)

            expect(job_relation.first).to have_attributes(
              min_value: event1.id,
              max_value: event2.id,
              batch_size: first_migration.batch_size,
              sub_batch_size: first_migration.sub_batch_size)
          end
        end

        context 'when the migration has no batches to process' do
          it_behaves_like 'it has no jobs to run'
          it_behaves_like 'it has completed the migration'
        end
      end

      context 'when the first migration has previous jobs' do
        let!(:event1) { create(:event) }
        let!(:event2) { create(:event) }
        let!(:event3) { create(:event) }

        let!(:previous_job) do
          create(:batched_background_migration_job,
            batched_migration: first_migration,
            min_value: event1.id,
            max_value: event2.id,
            batch_size: 2,
            sub_batch_size: 1)
        end

        context 'when the migration is ready to process another job' do
          it 'runs the migration job for the next batch' do
            first_migration.update!(min_value: event1.id, max_value: event3.id)

            expect_next_instance_of(Gitlab::Database::BackgroundMigration::BatchedMigrationWrapper) do |wrapper|
              expect(wrapper).to receive(:perform).and_wrap_original do |_, job_record|
                expect(job_record).to eq(job_relation.last)
              end
            end

            expect { scheduler.perform }.to change { job_relation.count }.by(1)

            expect(job_relation.last).to have_attributes(
              min_value: event3.id,
              max_value: event3.id,
              batch_size: first_migration.batch_size,
              sub_batch_size: first_migration.sub_batch_size)
          end
        end

        context 'when the migration has no batches remaining' do
          let!(:final_job) do
            create(:batched_background_migration_job,
              batched_migration: first_migration,
              min_value: event3.id,
              max_value: event3.id,
              batch_size: 2,
              sub_batch_size: 1)
          end

          it_behaves_like 'it has no jobs to run'
          it_behaves_like 'it has completed the migration'
        end
      end

      context 'when the bounds of the next batch exceed the migration maximum value' do
        let!(:events) { create_list(:event, 3) }
        let(:event1) { events[0] }
        let(:event2) { events[1] }

        context 'when the batch maximum exceeds the migration maximum' do
          it 'clamps the batch maximum to the migration maximum' do
            first_migration.update!(batch_size: 5, min_value: event1.id, max_value: event2.id)

            expect_next_instance_of(Gitlab::Database::BackgroundMigration::BatchedMigrationWrapper) do |wrapper|
              expect(wrapper).to receive(:perform)
            end

            expect { scheduler.perform }.to change { job_relation.count }.by(1)

            expect(job_relation.first).to have_attributes(
              min_value: event1.id,
              max_value: event2.id,
              batch_size: first_migration.batch_size,
              sub_batch_size: first_migration.sub_batch_size)
          end
        end

        context 'when the batch minimum exceeds the migration maximum' do
          let!(:previous_job) do
            create(:batched_background_migration_job,
              batched_migration: first_migration,
              min_value: event1.id,
              max_value: event2.id,
              batch_size: 5,
              sub_batch_size: 1)
          end

          before do
            first_migration.update!(batch_size: 5, min_value: 1, max_value: event2.id)
          end

          it_behaves_like 'it has no jobs to run'
          it_behaves_like 'it has completed the migration'
        end
      end
    end
  end
end