geo.rb 4.51 KB
Newer Older
1 2
# frozen_string_literal: true

3 4
module Gitlab
  module Geo
5
    OauthApplicationUndefinedError = Class.new(StandardError)
6
    GeoNodeNotFoundError = Class.new(StandardError)
7 8
    InvalidDecryptionKeyError = Class.new(StandardError)
    InvalidSignatureTimeError = Class.new(StandardError)
9

10
    CACHE_KEYS = %i(
11 12 13 14
      primary_node
      secondary_nodes
      node_enabled
      oauth_application
15 16
    ).freeze

17 18
    API_SCOPE = 'geo_api'

19 20 21 22 23 24 25 26
    # TODO: Avoid having to maintain a list. Discussions related to possible
    # solutions can be found at
    # https://gitlab.com/gitlab-org/gitlab/-/issues/227693
    REPLICATOR_CLASSES = [
        ::Geo::PackageFileReplicator,
        ::Geo::TerraformStateReplicator
    ].freeze

27
    def self.current_node
28
      self.cache_value(:current_node, as: GeoNode) { GeoNode.current_node }
29 30 31
    end

    def self.primary_node
32
      self.cache_value(:primary_node, as: GeoNode) { GeoNode.primary_node }
33 34 35
    end

    def self.secondary_nodes
36
      self.cache_value(:secondary_nodes, as: GeoNode) { GeoNode.secondary_nodes }
37 38
    end

39
    def self.connected?
Nick Thomas's avatar
Nick Thomas committed
40
      GeoNode.connected? && GeoNode.table_exists?
41 42
    end

43
    def self.enabled?
44
      self.cache_value(:node_enabled) { GeoNode.exists? }
45
    end
46

47 48 49 50 51 52
    def self.primary?
      self.enabled? && self.current_node&.primary?
    end

    def self.secondary?
      self.enabled? && self.current_node&.secondary?
53 54
    end

55 56 57 58
    def self.current_node_misconfigured?
      self.enabled? && self.current_node.nil?
    end

59 60
    def self.current_node_enabled?
      # No caching of the enabled! If we cache it and an admin disables
61
      # this node, an active Geo::RepositorySyncWorker would keep going for up
62
      # to max run time after the node was disabled.
63
      Gitlab::Geo.current_node.reset.enabled?
64 65
    end

66 67 68 69
    def self.geo_database_configured?
      Rails.configuration.respond_to?(:geo_database)
    end

70 71 72 73
    def self.primary_node_configured?
      Gitlab::Geo.primary_node.present?
    end

74 75 76 77
    def self.secondary_with_primary?
      self.secondary? && self.primary_node_configured?
    end

78
    def self.license_allows?
79
      ::License.feature_available?(:geo)
80 81
    end

82
    def self.configure_cron_jobs!
83 84 85
      manager = CronManager.new
      manager.create_watcher!
      manager.execute
86 87
    end

88
    def self.oauth_authentication
89
      return false unless Gitlab::Geo.secondary?
90

91
      self.cache_value(:oauth_application) do
Douwe Maan's avatar
Douwe Maan committed
92
        Gitlab::Geo.current_node.oauth_application || raise(OauthApplicationUndefinedError)
93
      end
94
    end
95

96 97
    def self.l1_cache
      SafeRequestStore[:geo_l1_cache] ||=
98
        Gitlab::JsonCache.new(namespace: :geo, backend: ::Gitlab::ProcessMemoryCache.cache_backend)
99 100
    end

101 102
    def self.l2_cache
      SafeRequestStore[:geo_l2_cache] ||= Gitlab::JsonCache.new(namespace: :geo)
103
    end
104

105
    def self.cache_value(raw_key, as: nil, &block)
106 107 108
      # We need a short expire time as we can't manually expire on a secondary node
      l1_cache.fetch(raw_key, as: as, expires_in: 1.minute) do
        l2_cache.fetch(raw_key, as: as, expires_in: 2.minutes) { yield }
109
      end
110 111 112
    end

    def self.expire_cache!
113 114 115 116 117 118 119
      expire_cache_keys!(CACHE_KEYS)
    end

    def self.expire_cache_keys!(keys)
      keys.each do |key|
        l1_cache.expire(key)
        l2_cache.expire(key)
120 121 122
      end

      true
123
    end
124 125 126 127 128 129 130 131 132 133 134 135 136

    def self.generate_access_keys
      # Inspired by S3
      {
        access_key: generate_random_string(20),
        secret_access_key: generate_random_string(40)
      }
    end

    def self.generate_random_string(size)
      # urlsafe_base64 may return a string of size * 4/3
      SecureRandom.urlsafe_base64(size)[0, size]
    end
137 138

    def self.repository_verification_enabled?
139
      Feature.enabled?(:geo_repository_verification, default_enabled: true)
140
    end
141 142 143 144 145 146

    def self.allowed_ip?(ip)
      allowed_ips = ::Gitlab::CurrentSettings.current_application_settings.geo_node_allowed_ips

      Gitlab::CIDR.new(allowed_ips).match?(ip)
    end
147

148 149
    def self.interacting_with_primary_message(url)
      return unless url
150 151 152 153

      # This is formatted like this to fit into the console 'box', e.g.
      #
      # remote:
154 155
      # remote: This request to a Geo secondary node will be forwarded to the
      # remote: Geo primary node:
156 157 158 159
      # remote:
      # remote:   <url>
      # remote:
      template = <<~STR
160 161
        This request to a Geo secondary node will be forwarded to the
        Geo primary node:
162 163 164 165

          %{url}
      STR

166
      _(template) % { url: url }
167
    end
Mike Kozono's avatar
Mike Kozono committed
168

169
    def self.enabled_replicator_classes
170
      REPLICATOR_CLASSES.select(&:enabled?)
Mike Kozono's avatar
Mike Kozono committed
171
    end
172 173
  end
end