summaryrefslogtreecommitdiff
path: root/lib/gitlab/git/storage/checker.rb
blob: de63cb4b40c5678b8a9b88c72ffdce8bcc4c3a5b (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
module Gitlab
  module Git
    module Storage
      class Checker
        include CircuitBreakerSettings

        attr_reader :storage_path, :storage, :hostname, :logger

        def self.check_all(logger = Rails.logger)
          threads = Gitlab.config.repositories.storages.keys.map do |storage_name|
            Thread.new do
              Thread.current[:result] = new(storage_name, logger).check_with_lease
            end
          end

          threads.map do |thread|
            thread.join
            thread[:result]
          end
        end

        def initialize(storage, logger = Rails.logger)
          @storage = storage
          config = Gitlab.config.repositories.storages[@storage]
          @storage_path = config['path']
          @logger = logger

          @hostname = Gitlab::Environment.hostname
        end

        def check_with_lease
          lease_key = "storage_check:#{cache_key}"
          lease = Gitlab::ExclusiveLease.new(lease_key, timeout: storage_timeout)
          result = { storage: storage, success: nil }

          if uuid = lease.try_obtain
            result[:success] = check

            Gitlab::ExclusiveLease.cancel(lease_key, uuid)
          else
            logger.warn("#{hostname}: #{storage}: Skipping check, previous check still running")
          end

          result
        end

        def check
          if Gitlab::Git::Storage::ForkedStorageCheck.storage_available?(storage_path, storage_timeout, access_retries)
            track_storage_accessible
            true
          else
            track_storage_inaccessible
            logger.error("#{hostname}: #{storage}: Not accessible.")
            false
          end
        end

        private

        def track_storage_inaccessible
          first_failure = current_failure_info.first_failure || Time.now
          last_failure = Time.now

          Gitlab::Git::Storage.redis.with do |redis|
            redis.pipelined do
              redis.hset(cache_key, :first_failure, first_failure.to_i)
              redis.hset(cache_key, :last_failure, last_failure.to_i)
              redis.hincrby(cache_key, :failure_count, 1)
              redis.expire(cache_key, failure_reset_time)
              maintain_known_keys(redis)
            end
          end
        end

        def track_storage_accessible
          Gitlab::Git::Storage.redis.with do |redis|
            redis.pipelined do
              redis.hset(cache_key, :first_failure, nil)
              redis.hset(cache_key, :last_failure, nil)
              redis.hset(cache_key, :failure_count, 0)
              maintain_known_keys(redis)
            end
          end
        end

        def maintain_known_keys(redis)
          expire_time = Time.now.to_i + failure_reset_time
          redis.zadd(Gitlab::Git::Storage::REDIS_KNOWN_KEYS, expire_time, cache_key)
          redis.zremrangebyscore(Gitlab::Git::Storage::REDIS_KNOWN_KEYS, '-inf', Time.now.to_i)
        end

        def current_failure_info
          FailureInfo.load(cache_key)
        end
      end
    end
  end
end