summaryrefslogtreecommitdiff
path: root/lib/gitlab/database/load_balancing/resolver.rb
blob: 3e3998cae92ae4d3970a3b511a2ea26c2e72b3b1 (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
# frozen_string_literal: true

require 'net/dns'
require 'resolv'

module Gitlab
  module Database
    module LoadBalancing
      class Resolver
        FAR_FUTURE_TTL = 100.years.from_now

        UnresolvableNameserverError = Class.new(StandardError)

        Response = Class.new do
          attr_reader :address, :ttl

          def initialize(address:, ttl:)
            raise ArgumentError unless ttl.present? && address.present?

            @address = address
            @ttl = ttl
          end
        end

        def initialize(nameserver)
          @nameserver = nameserver
        end

        def resolve
          address = ip_address || ip_address_from_hosts_file ||
            ip_address_from_dns

          unless address
            raise UnresolvableNameserverError,
              "could not resolve #{@nameserver}"
          end

          address
        end

        private

        def ip_address
          # IP addresses are valid forever
          Response.new(address: IPAddr.new(@nameserver), ttl: FAR_FUTURE_TTL)
        rescue IPAddr::InvalidAddressError
        end

        def ip_address_from_hosts_file
          ip = Resolv::Hosts.new.getaddress(@nameserver)
          Response.new(address: IPAddr.new(ip), ttl: FAR_FUTURE_TTL)
        rescue Resolv::ResolvError
        end

        def ip_address_from_dns
          answer = Net::DNS::Resolver.start(@nameserver, Net::DNS::A).answer
          return if answer.empty?

          raw_response = answer.first

          # Defaults to 30 seconds if there is no TTL present
          ttl_in_seconds = raw_response.ttl.presence || 30

          Response.new(address: answer.first.address, ttl: ttl_in_seconds.seconds.from_now)
        rescue Net::DNS::Resolver::NoResponseError
          raise UnresolvableNameserverError, "no response from DNS server(s)"
        end
      end
    end
  end
end