summaryrefslogtreecommitdiff
path: root/spec/lib/gitlab/issues/rebalancing/state_spec.rb
blob: bdd0dbd365dc046475a964e7ec5420fa6b1a1fd1 (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
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Gitlab::Issues::Rebalancing::State, :clean_gitlab_redis_shared_state do
  shared_examples 'issues rebalance caching' do
    describe '#track_new_running_rebalance' do
      it 'caches a project id to track caching in progress' do
        expect { rebalance_caching.track_new_running_rebalance }.to change { rebalance_caching.concurrent_running_rebalances_count }.from(0).to(1)
      end
    end

    describe '#set and get current_index' do
      it 'returns zero as current index when index not cached' do
        expect(rebalance_caching.get_current_index).to eq(0)
      end

      it 'returns cached current index' do
        expect { rebalance_caching.cache_current_index(123) }.to change { rebalance_caching.get_current_index }.from(0).to(123)
      end
    end

    describe '#set and get current_project' do
      it 'returns nil if there is no project_id cached' do
        expect(rebalance_caching.get_current_project_id).to be_nil
      end

      it 'returns cached current project_id' do
        expect { rebalance_caching.cache_current_project_id(456) }.to change { rebalance_caching.get_current_project_id }.from(nil).to('456')
      end
    end

    describe "#rebalance_in_progress?" do
      it 'return zero if no re-balances are running' do
        expect(rebalance_caching.concurrent_running_rebalances_count).to eq(0)
      end

      it 'return false if no re-balances are running' do
        expect(rebalance_caching.rebalance_in_progress?).to be false
      end

      it 'return true a re-balance for given project/namespace is running' do
        rebalance_caching.track_new_running_rebalance

        expect(rebalance_caching.rebalance_in_progress?).to be true
      end
    end

    context 'caching issue ids' do
      context 'with no issue ids cached' do
        it 'returns zero when there are no cached issue ids' do
          expect(rebalance_caching.issue_count).to eq(0)
        end

        it 'returns empty array when there are no cached issue ids' do
          expect(rebalance_caching.get_cached_issue_ids(0, 100)).to eq([])
        end
      end

      context 'with cached issue ids' do
        before do
          generate_and_cache_issues_ids(count: 3)
        end

        it 'returns count of cached issue ids' do
          expect(rebalance_caching.issue_count).to eq(3)
        end

        it 'returns array of issue ids' do
          expect(rebalance_caching.get_cached_issue_ids(0, 100)).to eq(%w(1 2 3))
        end

        it 'limits returned values' do
          expect(rebalance_caching.get_cached_issue_ids(0, 2)).to eq(%w(1 2))
        end

        context 'when caching duplicate issue_ids' do
          before do
            generate_and_cache_issues_ids(count: 3, position_offset: 3, position_direction: -1)
          end

          it 'does not cache duplicate issues' do
            expect(rebalance_caching.issue_count).to eq(3)
          end

          it 'returns cached issues with latest scores' do
            expect(rebalance_caching.get_cached_issue_ids(0, 100)).to eq(%w(3 2 1))
          end
        end
      end
    end

    context 'when setting expiration' do
      context 'when tracking new rebalance' do
        it 'returns as expired for non existent key' do
          ::Gitlab::Redis::SharedState.with do |redis|
            expect(redis.ttl(rebalance_caching.send(:concurrent_running_rebalances_key))).to be < 0
          end
        end

        it 'has expiration set' do
          rebalance_caching.track_new_running_rebalance

          ::Gitlab::Redis::SharedState.with do |redis|
            expect(redis.ttl(rebalance_caching.send(:concurrent_running_rebalances_key))).to be_between(0, described_class::REDIS_EXPIRY_TIME.ago.to_i)
          end
        end
      end

      context 'when setting current index' do
        it 'returns as expiring for non existent key' do
          ::Gitlab::Redis::SharedState.with do |redis|
            expect(redis.ttl(rebalance_caching.send(:current_index_key))).to be < 0
          end
        end

        it 'has expiration set' do
          rebalance_caching.cache_current_index(123)

          ::Gitlab::Redis::SharedState.with do |redis|
            expect(redis.ttl(rebalance_caching.send(:current_index_key))).to be_between(0, described_class::REDIS_EXPIRY_TIME.ago.to_i)
          end
        end
      end

      context 'when setting current project id' do
        it 'returns as expired for non existent key' do
          ::Gitlab::Redis::SharedState.with do |redis|
            expect(redis.ttl(rebalance_caching.send(:current_project_key))).to be < 0
          end
        end

        it 'has expiration set' do
          rebalance_caching.cache_current_project_id(456)

          ::Gitlab::Redis::SharedState.with do |redis|
            expect(redis.ttl(rebalance_caching.send(:current_project_key))).to be_between(0, described_class::REDIS_EXPIRY_TIME.ago.to_i)
          end
        end
      end

      context 'when setting cached issue ids' do
        it 'returns as expired for non existent key' do
          ::Gitlab::Redis::SharedState.with do |redis|
            expect(redis.ttl(rebalance_caching.send(:issue_ids_key))).to be < 0
          end
        end

        it 'has expiration set' do
          generate_and_cache_issues_ids(count: 3)

          ::Gitlab::Redis::SharedState.with do |redis|
            expect(redis.ttl(rebalance_caching.send(:issue_ids_key))).to be_between(0, described_class::REDIS_EXPIRY_TIME.ago.to_i)
          end
        end
      end
    end

    context 'cleanup cache' do
      before do
        generate_and_cache_issues_ids(count: 3)
        rebalance_caching.cache_current_index(123)
        rebalance_caching.cache_current_project_id(456)
        rebalance_caching.track_new_running_rebalance
      end

      it 'removes cache keys' do
        expect(check_existing_keys).to eq(4)

        rebalance_caching.cleanup_cache

        expect(check_existing_keys).to eq(0)
      end
    end
  end

  context 'rebalancing issues in namespace' do
    let_it_be(:group) { create(:group, :private) }
    let_it_be(:project) { create(:project, namespace: group) }

    subject(:rebalance_caching) { described_class.new(group, group.projects) }

    it { expect(rebalance_caching.send(:rebalanced_container_type)).to eq(described_class::NAMESPACE) }

    it_behaves_like 'issues rebalance caching'
  end

  context 'rebalancing issues in a project' do
    let_it_be(:project) { create(:project) }

    subject(:rebalance_caching) { described_class.new(project.namespace, Project.where(id: project)) }

    it { expect(rebalance_caching.send(:rebalanced_container_type)).to eq(described_class::PROJECT) }

    it_behaves_like 'issues rebalance caching'
  end

  # count - how many issue ids to generate, issue ids will start at 1
  # position_offset - if you'd want to offset generated relative_position for the issue ids,
  # relative_position is generated as = issue id * 10 + position_offset
  # position_direction - (1) for positive relative_positions, (-1) for negative relative_positions
  def generate_and_cache_issues_ids(count:, position_offset: 0, position_direction: 1)
    issues = []

    count.times do |idx|
      id = idx + 1
      issues << double(relative_position: position_direction * (id * 10 + position_offset), id: id)
    end

    rebalance_caching.cache_issue_ids(issues)
  end

  def check_existing_keys
    index = 0

    index += 1 if rebalance_caching.get_current_index > 0
    index += 1 if rebalance_caching.get_current_project_id.present?
    index += 1 if rebalance_caching.get_cached_issue_ids(0, 100).present?
    index += 1 if rebalance_caching.rebalance_in_progress?

    index
  end
end