summaryrefslogtreecommitdiff
path: root/spec/lib/gitlab/git/storage/checker_spec.rb
blob: d74c3bcb04c36a69af0940c239f5eb203d2e07f6 (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
require 'spec_helper'

describe Gitlab::Git::Storage::Checker, :clean_gitlab_redis_shared_state do
  let(:storage_name) { 'default' }
  let(:hostname) { Gitlab::Environment.hostname }
  let(:cache_key) { "storage_accessible:#{storage_name}:#{hostname}" }

  subject(:checker) { described_class.new(storage_name) }

  def value_from_redis(name)
    Gitlab::Git::Storage.redis.with do |redis|
      redis.hmget(cache_key, name)
    end.first
  end

  def set_in_redis(name, value)
    Gitlab::Git::Storage.redis.with do |redis|
      redis.hmset(cache_key, name, value)
    end.first
  end

  describe '.check_all' do
    it 'calls a check for each storage' do
      fake_checker_default = double
      fake_checker_broken = double
      fake_logger = fake_logger

      expect(described_class).to receive(:new).with('default', fake_logger) { fake_checker_default }
      expect(described_class).to receive(:new).with('broken', fake_logger) { fake_checker_broken }
      expect(fake_checker_default).to receive(:check_with_lease)
      expect(fake_checker_broken).to receive(:check_with_lease)

      described_class.check_all(fake_logger)
    end

    context 'with broken storage', :broken_storage do
      it 'returns the results' do
        expected_result = [
          { storage: 'default', success: true },
          { storage: 'broken', success: false }
        ]

        expect(described_class.check_all).to eq(expected_result)
      end
    end
  end

  describe '#initialize' do
    it 'assigns the settings' do
      expect(checker.hostname).to eq(hostname)
      expect(checker.storage).to eq('default')
      expect(checker.storage_path).to eq(TestEnv.repos_path)
    end
  end

  describe '#check_with_lease' do
    it 'only allows one check at a time' do
      expect(checker).to receive(:check).once { sleep 1 }

      thread = Thread.new { checker.check_with_lease }
      checker.check_with_lease
      thread.join
    end

    it 'returns a result hash' do
      expect(checker.check_with_lease).to eq(storage: 'default', success: true)
    end
  end

  describe '#check' do
    it 'tracks that the storage was accessible' do
      set_in_redis(:failure_count, 10)
      set_in_redis(:last_failure, Time.now.to_f)

      checker.check

      expect(value_from_redis(:failure_count).to_i).to eq(0)
      expect(value_from_redis(:last_failure)).to be_empty
      expect(value_from_redis(:first_failure)).to be_empty
    end

    it 'calls the check with the correct arguments' do
      stub_application_setting(circuitbreaker_storage_timeout: 30,
                               circuitbreaker_access_retries: 3)

      expect(Gitlab::Git::Storage::ForkedStorageCheck)
        .to receive(:storage_available?).with(TestEnv.repos_path, 30, 3)
              .and_call_original

      checker.check
    end

    it 'returns `true`' do
      expect(checker.check).to eq(true)
    end

    it 'maintains known storage keys' do
      Timecop.freeze do
        # Insert an old key to expire
        old_entry = Time.now.to_i - 3.days.to_i
        Gitlab::Git::Storage.redis.with do |redis|
          redis.zadd(Gitlab::Git::Storage::REDIS_KNOWN_KEYS, old_entry, 'to_be_removed')
        end

        checker.check

        known_keys = Gitlab::Git::Storage.redis.with do |redis|
          redis.zrange(Gitlab::Git::Storage::REDIS_KNOWN_KEYS, 0, -1)
        end

        expect(known_keys).to contain_exactly(cache_key)
      end
    end

    context 'the storage is not available', :broken_storage do
      let(:storage_name) { 'broken' }

      it 'tracks that the storage was inaccessible' do
        Timecop.freeze do
          expect { checker.check }.to change { value_from_redis(:failure_count).to_i }.by(1)

          expect(value_from_redis(:last_failure)).not_to be_empty
          expect(value_from_redis(:first_failure)).not_to be_empty
        end
      end

      it 'returns `false`' do
        expect(checker.check).to eq(false)
      end
    end
  end
end