class Puppet::HTTP::DNS
Public Class Methods
# File lib/puppet/http/dns.rb 27 def initialize(resolver = Resolv::DNS.new) 28 @resolver = resolver 29 30 # Stores DNS records per service, along with their TTL 31 # and the time at which they were resolved, for cache 32 # eviction. 33 @record_cache = {} 34 end
Public Instance Methods
Iterate through the list of records for this service and yield each server and port pair. Records are only fetched via DNS query the first time and cached for the duration of their service's TTL thereafter. @param [String] domain the domain to search for @param [Symbol] service_name the key of the service we are querying @yields [String, Integer] server and port of selected record
# File lib/puppet/http/dns.rb 43 def each_srv_record(domain, service_name = :puppet, &block) 44 if (domain.nil? or domain.empty?) 45 Puppet.debug "Domain not known; skipping SRV lookup" 46 return 47 end 48 49 Puppet.debug "Searching for SRV records for domain: #{domain}" 50 51 case service_name 52 when :puppet then service = '_x-puppet' 53 when :file then service = '_x-puppet-fileserver' 54 else service = "_x-puppet-#{service_name}" 55 end 56 record_name = "#{service}._tcp.#{domain}" 57 58 if @record_cache.has_key?(service_name) && !expired?(service_name) 59 records = @record_cache[service_name].records 60 Puppet.debug "Using cached record for #{record_name}" 61 else 62 records = @resolver.getresources(record_name, Resolv::DNS::Resource::IN::SRV) 63 if records.size > 0 64 @record_cache[service_name] = CacheEntry.new(records) 65 end 66 Puppet.debug "Found #{records.size} SRV records for: #{record_name}" 67 end 68 69 if records.size == 0 && service_name != :puppet 70 # Try the generic :puppet service if no SRV records were found 71 # for the specific service. 72 each_srv_record(domain, :puppet, &block) 73 else 74 each_priority(records) do |recs| 75 while next_rr = recs.delete(find_weighted_server(recs)) #rubocop:disable Lint/AssignmentInCondition 76 Puppet.debug "Yielding next server of #{next_rr.target}:#{next_rr.port}" 77 yield next_rr.target.to_s, next_rr.port 78 end 79 end 80 end 81 end
Checks if the cached entry for the given service has expired. @param [String] service_name the name of the service to check @return [Boolean] true if the entry has expired, false otherwise.
Always returns true if the record had no TTL.
# File lib/puppet/http/dns.rb 128 def expired?(service_name) 129 entry = @record_cache[service_name] 130 if entry 131 return Time.now > (entry.resolution_time + entry.ttl) 132 else 133 return true 134 end 135 end
Given a list of records of the same priority, chooses a random one from among them, favoring those with higher weights. @param [[Resolv::DNS::Resource::IN::SRV]] records a list of records
of the same priority
@return [Resolv::DNS::Resource::IN:SRV] the chosen record
# File lib/puppet/http/dns.rb 88 def find_weighted_server(records) 89 return nil if records.nil? || records.empty? 90 return records.first if records.size == 1 91 92 # Calculate the sum of all weights in the list of resource records, 93 # This is used to then select hosts until the weight exceeds what 94 # random number we selected. For example, if we have weights of 1 8 and 3: 95 # 96 # |-|--------|---| 97 # ^ 98 # We generate a random number 5, and iterate through the records, adding 99 # the current record's weight to the accumulator until the weight of the 100 # current record plus previous records is greater than the random number. 101 total_weight = records.inject(0) { |sum,record| 102 sum + weight(record) 103 } 104 current_weight = 0 105 chosen_weight = 1 + Kernel.rand(total_weight) 106 107 records.each do |record| 108 current_weight += weight(record) 109 return record if current_weight >= chosen_weight 110 end 111 end
Returns TTL for the cached records for this service. @param [String] service_name the service whose TTL we want @return [Integer] the TTL for this service, in seconds
# File lib/puppet/http/dns.rb 120 def ttl(service_name) 121 return @record_cache[service_name].ttl 122 end
# File lib/puppet/http/dns.rb 113 def weight(record) 114 record.weight == 0 ? 1 : record.weight * 10 115 end
Private Instance Methods
Groups the records by their priority and yields the groups in order of highest to lowest priority (lowest to highest numbers), one at a time. { 1 => [records], 2 => [records], etc. }
@param [[Resolv::DNS::Resource::IN::SRV]] records the list of
records for a given service
@yields [[Resolv::DNS::Resource::IN::SRV]] a group of records of
the same priority
# File lib/puppet/http/dns.rb 148 def each_priority(records) 149 pri_hash = records.inject({}) do |groups, element| 150 groups[element.priority] ||= [] 151 groups[element.priority] << element 152 groups 153 end 154 155 pri_hash.keys.sort.each do |key| 156 yield pri_hash[key] 157 end 158 end