class Puppet::HTTP::DNS
Public Class Methods
# File lib/puppet/http/dns.rb 26 def initialize(resolver = Resolv::DNS.new) 27 @resolver = resolver 28 29 # Stores DNS records per service, along with their TTL 30 # and the time at which they were resolved, for cache 31 # eviction. 32 @record_cache = {} 33 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 42 def each_srv_record(domain, service_name = :puppet, &block) 43 if (domain.nil? or domain.empty?) 44 Puppet.debug "Domain not known; skipping SRV lookup" 45 return 46 end 47 48 Puppet.debug "Searching for SRV records for domain: #{domain}" 49 50 case service_name 51 when :puppet then service = '_x-puppet' 52 when :file then service = '_x-puppet-fileserver' 53 else service = "_x-puppet-#{service_name}" 54 end 55 record_name = "#{service}._tcp.#{domain}" 56 57 if @record_cache.has_key?(service_name) && !expired?(service_name) 58 records = @record_cache[service_name].records 59 Puppet.debug "Using cached record for #{record_name}" 60 else 61 records = @resolver.getresources(record_name, Resolv::DNS::Resource::IN::SRV) 62 if records.size > 0 63 @record_cache[service_name] = CacheEntry.new(records) 64 end 65 Puppet.debug "Found #{records.size} SRV records for: #{record_name}" 66 end 67 68 if records.size == 0 && service_name != :puppet 69 # Try the generic :puppet service if no SRV records were found 70 # for the specific service. 71 each_srv_record(domain, :puppet, &block) 72 else 73 each_priority(records) do |recs| 74 while next_rr = recs.delete(find_weighted_server(recs)) #rubocop:disable Lint/AssignmentInCondition 75 Puppet.debug "Yielding next server of #{next_rr.target}:#{next_rr.port}" 76 yield next_rr.target.to_s, next_rr.port 77 end 78 end 79 end 80 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 127 def expired?(service_name) 128 entry = @record_cache[service_name] 129 if entry 130 return Time.now > (entry.resolution_time + entry.ttl) 131 else 132 return true 133 end 134 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 87 def find_weighted_server(records) 88 return nil if records.nil? || records.empty? 89 return records.first if records.size == 1 90 91 # Calculate the sum of all weights in the list of resource records, 92 # This is used to then select hosts until the weight exceeds what 93 # random number we selected. For example, if we have weights of 1 8 and 3: 94 # 95 # |-|--------|---| 96 # ^ 97 # We generate a random number 5, and iterate through the records, adding 98 # the current record's weight to the accumulator until the weight of the 99 # current record plus previous records is greater than the random number. 100 total_weight = records.inject(0) { |sum,record| 101 sum + weight(record) 102 } 103 current_weight = 0 104 chosen_weight = 1 + Kernel.rand(total_weight) 105 106 records.each do |record| 107 current_weight += weight(record) 108 return record if current_weight >= chosen_weight 109 end 110 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 119 def ttl(service_name) 120 return @record_cache[service_name].ttl 121 end
# File lib/puppet/http/dns.rb 112 def weight(record) 113 record.weight == 0 ? 1 : record.weight * 10 114 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 147 def each_priority(records) 148 pri_hash = records.inject({}) do |groups, element| 149 groups[element.priority] ||= [] 150 groups[element.priority] << element 151 groups 152 end 153 154 pri_hash.keys.sort.each do |key| 155 yield pri_hash[key] 156 end 157 end