File: lock_tables_worker_spec.rb

package info (click to toggle)
gitlab 17.6.5-19
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 629,368 kB
  • sloc: ruby: 1,915,304; javascript: 557,307; sql: 60,639; xml: 6,509; sh: 4,567; makefile: 1,239; python: 406
file content (136 lines) | stat: -rw-r--r-- 5,010 bytes parent folder | download
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
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Database::LockTablesWorker, feature_category: :cell do
  using RSpec::Parameterized::TableSyntax

  let(:worker) { described_class.new }
  let(:exception_class) { described_class::TableShouldNotBeLocked }

  describe '#perform' do
    context 'when running with single database' do # this covers both single-db and single-db-ci-connection cases
      before do
        skip_if_database_exists(:ci)
      end

      it 'skips executing the job' do
        expect do
          worker.perform('ci', %w[ci_pipelines])
        end.to raise_error(exception_class, 'GitLab is not running in multiple database mode')
      end
    end

    context 'when running in decomposed database' do
      before do
        skip_if_shared_database(:ci)
      end

      context 'when the table is wrong' do
        context 'when trying to lock tables on an unknown database' do
          it 'raises an exception' do
            expect do
              worker.perform('foobar', %w[ci_pipelines])
            end.to raise_error(exception_class, /does not support locking writes on tables/)
          end
        end

        context 'when trying to lock tables on the database that does not support locking' do
          it 'raises an exception' do
            expect do
              worker.perform('geo', %w[ci_pipelines]) # ci tables should be locked only on main
            end.to raise_error(exception_class, /does not support locking writes on tables/)
          end
        end

        context 'when trying to lock tables on the wrong database' do
          it 'raises an exception' do
            expect do
              worker.perform('ci', %w[ci_pipelines]) # ci tables should be locked only on main
            end.to raise_error(exception_class, "table 'ci_pipelines' should not be locked on the database 'ci'")
          end
        end

        context 'when trying to lock shared tables on the database' do
          it 'raises an exception' do
            expect do
              worker.perform('main', %w[loose_foreign_keys_deleted_records])
            end.to raise_error(exception_class, /should not be locked on the database 'main'/)
          end
        end
      end

      context 'when the table is correct' do
        context 'when the table is not locked for writes' do
          where(:database_name, :tables) do
            :ci   | %w[users namespaces]
            :main | %w[ci_pipelines ci_builds]
          end

          with_them do
            it 'locks the tables on the corresponding database' do
              tables.each do |table_name|
                unlock_table(database_name, table_name)
                expect(lock_writes_manager(database_name, table_name).table_locked_for_writes?).to eq(false)
              end

              expected_log_results = tables.map do |table_name|
                { action: "locked", database: database_name, dry_run: false, table: table_name }
              end
              expect(worker).to receive(:log_extra_metadata_on_done).with(:performed_actions, expected_log_results)

              worker.perform(database_name, tables)
              tables.each do |table_name|
                expect(lock_writes_manager(database_name, table_name).table_locked_for_writes?).to eq(true)
              end
            end
          end

          context 'when the table is already locked for writes' do
            where(:database_name, :tables) do
              :ci   | %w[users namespaces]
              :main | %w[ci_pipelines ci_builds]
            end

            with_them do
              it 'skips locking the tables on the corresponding database' do
                tables.each do |table_name|
                  lock_table(database_name, table_name)
                end

                expected_log_results = tables.map do |table_name|
                  { action: 'skipped', database: database_name, dry_run: false, table: table_name }
                end
                expect(worker).to receive(:log_extra_metadata_on_done).with(:performed_actions, expected_log_results)

                worker.perform(database_name, tables)
                tables.each do |table_name|
                  expect(lock_writes_manager(database_name, table_name).table_locked_for_writes?).to eq(true)
                end
              end
            end
          end
        end
      end
    end
  end

  def lock_table(database_name, table_name)
    lock_writes_manager(database_name, table_name).lock_writes
  end

  def unlock_table(database_name, table_name)
    lock_writes_manager(database_name, table_name).unlock_writes
  end

  def lock_writes_manager(database_name, table_name)
    connection = Gitlab::Database.database_base_models_with_gitlab_shared[database_name].connection
    Gitlab::Database::LockWritesManager.new(
      table_name: table_name,
      connection: connection,
      database_name: database_name,
      with_retries: false,
      dry_run: false
    )
  end
end