Here's a new version of it. - I factored out the begin/rescue into it's own method - Added 2 new configuration variables use_srv_records, and srv_record
use_srv_records defaults to true unless 'server' is explicitly set. srv_record defaults to _puppet._tcp.$domain (from facter) If there are no SRV records for srv_record (or if they all fail), it falls back to the 'server' variable. I also updated some tests. Looking forward to comments, again. :) -Andrew On Dec 15, 10:50 pm, "Andrew J. Forgue" <[email protected]> wrote: > This patch gives Puppet the ability to find a puppet master via SRV > records in DNS. First Puppet will try to resolve the server parameter > in puppet.conf (or supplied on command line) to an SRV record before > treating it as a regular host. This patch basically adds client-side > load-balancing. > > * Adds 2 new configuration variables: > > use_srv_records: Will attempt to lookup SRV records for hostname > found in srv_record (default: true) > srv_record: The hostname that will be queried for SRV records, > (default: _puppet._tcp.$domain) > > * Changes 'server' config variable to set use_srv_records to false > if the server is manually configured. > > Signed-off-by: Andrew J. Forgue <[email protected]> > --- > lib/puppet/defaults.rb | 11 +++- > lib/puppet/indirector/rest.rb | 64 ++++++++++++++++--- > lib/puppet/network/resolver.rb | 59 ++++++++++++++++++ > lib/puppet/type/file/content.rb | 28 ++++++++- > lib/puppet/type/file/source.rb | 4 + > .../indirector/bucket_file/rest_spec.rb | 1 + > .../indirector/certificate_request/rest_spec.rb | 1 + > spec/unit/network/resolver_spec.rb | 64 > ++++++++++++++++++++ > spec/unit/type/file/content_spec.rb | 8 ++- > 9 files changed, 224 insertions(+), 16 deletions(-) > create mode 100644 lib/puppet/network/resolver.rb > create mode 100755 spec/unit/network/resolver_spec.rb > > diff --git a/lib/puppet/defaults.rb b/lib/puppet/defaults.rb > index 4521a59..05e2d9f 100644 > --- a/lib/puppet/defaults.rb > +++ b/lib/puppet/defaults.rb > @@ -497,7 +497,16 @@ module Puppet > :mode => 0640, > :desc => "The log file for puppet agent. This is generally not used." > }, > - :server => ["puppet", "The server to which server puppet agent should > connect"], > + :server => { > + :default => "puppet", > + :desc => "The server to which server puppet agent should connect", > + :call_on_define => false, > + :hook => proc do > + Puppet.settings[:use_srv_records] = false > + end > + }, > + :use_srv_records => [true, "Whether the server will search for SRV > records in DNS for the current domain"], > + :srv_record => [ "_puppet._tcp.#{domain}", "The default SRV record which > will be queried to find a server"], > :ignoreschedules => [false, > "Boolean; whether puppet agent should ignore schedules. This is useful > for initial puppet agent runs."], > diff --git a/lib/puppet/indirector/rest.rb b/lib/puppet/indirector/rest.rb > index eb41ff3..a99d367 100644 > --- a/lib/puppet/indirector/rest.rb > +++ b/lib/puppet/indirector/rest.rb > @@ -4,6 +4,7 @@ require 'uri' > require 'puppet/network/http_pool' > require 'puppet/network/http/api/v1' > require 'puppet/network/http/compression' > +require 'puppet/network/resolver' > > # Access objects via REST > class Puppet::Indirector::REST < Puppet::Indirector::Terminus > @@ -19,19 +20,46 @@ class Puppet::Indirector::REST < > Puppet::Indirector::Terminus > �...@server_setting = setting > end > > - def self.server > - Puppet.settings[server_setting || :server] > - end > - > # Specify the setting that we should use to get the port. > def self.use_port_setting(setting) > �...@port_setting = setting > end > > + def self.server > + Puppet.settings[server_setting || :server] > + end > + > def self.port > Puppet.settings[port_setting || :masterport].to_i > end > > + def resolve(request) > + if !Puppet.settings[:use_srv_records] or request.server or > self.class.server_setting or self.class.port_setting > + request.server ||= self.class.server > + request.port ||= self.class.port > + yield request > + return > + end > + > + # Finally, find a working SRV record, if none ... > + Puppet::Network::Resolver.by_srv(Puppet.settings[:srv_record]) do > |srv_server, srv_port| > + begin > + request.server = srv_server > + request.port = srv_port > + yield request > + return > + rescue SystemCallError => e > + Puppet.warning "Error connecting to #{srv_server}:#{srv_port}: > #{e.message}" > + end > + end > + > + # ... Fall back onto the default server. > + Puppet.debug "No more servers left, falling back to #{self.class.server}" > + request.server = self.class.server > + request.port = self.class.port > + yield request > + end > + > # Figure out the content type, turn that into a format, and use the format > # to extract the body of the response. > def deserialize(response, multiple = false) > @@ -68,26 +96,42 @@ class Puppet::Indirector::REST < > Puppet::Indirector::Terminus > end > > def find(request) > - return nil unless result = > deserialize(network(request).get(indirection2uri(request), headers)) > + result = nil > + > + resolve(request) do |request| > + result = deserialize(network(request).get(indirection2uri(request), > headers)) > + end > + > + return nil unless result > result.name = request.key if result.respond_to?(:name=) > + > result > end > > def search(request) > - unless result = > deserialize(network(request).get(indirection2uri(request), headers), true) > - return [] > + result = nil > + > + resolve(request) do |request| > + result = deserialize(network(request).get(indirection2uri(request), > headers), true) > end > - result > + > + result || [] > end > > def destroy(request) > raise ArgumentError, "DELETE does not accept options" unless > request.options.empty? > - deserialize network(request).delete(indirection2uri(request), headers) > + > + resolve(request) do |request| > + return deserialize network(request).delete(indirection2uri(request), > headers) > + end > end > > def save(request) > raise ArgumentError, "PUT does not accept options" unless > request.options.empty? > - deserialize network(request).put(indirection2uri(request), > request.instance.render, headers.merge({ "Content-Type" => > request.instance.mime })) > + > + resolve(request) do |request| > + return deserialize network(request).put(indirection2uri(request), > request.instance.render, headers.merge({ "Content-Type" => > request.instance.mime })) > + end > end > > private > diff --git a/lib/puppet/network/resolver.rb b/lib/puppet/network/resolver.rb > new file mode 100644 > index 0000000..168f295 > --- /dev/null > +++ b/lib/puppet/network/resolver.rb > @@ -0,0 +1,59 @@ > +require 'resolv' > +module Puppet::Network; end > + > +module Puppet::Network::Resolver > + # Iterate through the list of servers that service this hostname > + # and yield each server/port since SRV records have ports in them > + # It will override whatever masterport setting is already set. > + def self.by_srv(srv) > + Puppet.debug "Searching for SRV records for #{srv}" > + > + resolver = Resolv::DNS.new > + records = resolver.getresources(srv, Resolv::DNS::Resource::IN::SRV) > + > + Puppet.debug "Found #{records.size} SRV records." > + > + each_priority(records) do |priority, records| > + while next_rr = records.delete(find_weighted_server(records)) > + Puppet.debug "Yielding next server of > #{next_rr.target.to_s}:#{next_rr.port}" > + yield next_rr.target.to_s, next_rr.port > + end > + end > + end > + > + private > + > + def self.each_priority(records) > + pri_hash = records.inject({}) do |groups, element| > + groups[element.priority] ||= [] > + groups[element.priority] << element > + groups > + end > + > + pri_hash.keys.sort.each { |key| yield key, pri_hash[key] } > + end > + > + def self.find_weighted_server(records) > + return nil if records.nil? || records.empty? > + return records.first if records.size == 1 > + > + # Calculate the sum of all weights in the list of resource records, > + # This is used to then select hosts until the weight exceeds what > + # random number we selected. For example, if we have weights of 1 8 and > 3: > + # > + # |-|---|--------| > + # ^ > + # We generate a random number 5, and iterate through the records, adding > + # the current record's weight to the accumulator until the weight of the > + # current record plus previous records is greater than the random number. > + > + total_weight = records.inject(0) { |sum,record| sum + record.weight } > + current_weight = 0 > + chosen_weight = 1 + rand(total_weight) > + > + records.each do |record| > + current_weight += record.weight > + return record if current_weight >= chosen_weight > + end > + end > +end > diff --git a/lib/puppet/type/file/content.rb b/lib/puppet/type/file/content.rb > index b8f30a9..834d440 100755 > --- a/lib/puppet/type/file/content.rb > +++ b/lib/puppet/type/file/content.rb > @@ -189,10 +189,34 @@ module Puppet > end > end > > - def chunk_file_from_source(source_or_content) > + > + def get_from_source(source_or_content) > request = Puppet::Indirector::Request.new(:file_content, :find, > source_or_content.full_path.sub(/^\//,'')) > + if source_or_content.server? or !Puppet.settings[:use_srv_records] > + connection = > Puppet::Network::HttpPool.http_instance(source_or_content.server, > source_or_content.port) > + yield connection.get(indirection2uri(request), > add_accept_encoding({"Accept" => "raw"})) > + return > + else > + Puppet::Network::Resolver.by_srv(Puppet.settings[:srv_record]) do > |server, port| > + begin > + connection = Puppet::Network::HttpPool.http_instance(server, > port) > + yield connection.get(indirection2uri(request), > add_accept_encoding({"Accept" => "raw"})) > + return > + rescue SystemCallError => e > + Puppet.warning "Error connecting to #{server}:#{port}: > #{e.message}" > + end > + end > + end > + > + # Fallback to hostname configured in :server > + Puppet.debug "No more servers left, falling back to > #{source_or_content.server}" > connection = > Puppet::Network::HttpPool.http_instance(source_or_content.server, > source_or_content.port) > - connection.request_get(indirection2uri(request), > ... > > read more » -- You received this message because you are subscribed to the Google Groups "Puppet Developers" group. To post to this group, send email to [email protected]. To unsubscribe from this group, send email to [email protected]. For more options, visit this group at http://groups.google.com/group/puppet-dev?hl=en.
