diff options
-rwxr-xr-x | bin/puppetd | 25 | ||||
-rwxr-xr-x | bin/puppetmasterd | 13 | ||||
-rw-r--r-- | lib/puppet.rb | 12 | ||||
-rw-r--r-- | lib/puppet/client.rb | 141 | ||||
-rw-r--r-- | lib/puppet/parser/interpreter.rb | 26 | ||||
-rw-r--r-- | lib/puppet/server/master.rb | 22 | ||||
-rwxr-xr-x | lib/puppet/type/pfile/checksum.rb | 1 | ||||
-rw-r--r-- | test/server/master.rb | 33 |
8 files changed, 220 insertions, 53 deletions
diff --git a/bin/puppetd b/bin/puppetd index 1b772213b..9ba6f89f1 100755 --- a/bin/puppetd +++ b/bin/puppetd @@ -10,7 +10,8 @@ # # puppetd [-h|--help] [-V|--version] [-d|--debug] [-v|--verbose] # [--ssldir <cert directory>] [-l|--logdest <syslog|<file>|console>] -# [--fqdn <host name>] [-p|--port <port>] [-s|--server <server>] +# [--fqdn <host name>] [-p|--port <port>] [-o|--onetime] +# [-s|--server <server>] # [-w|--waitforcert <seconds>] [-c|--confdir <configuration directory>] # [--vardir <var directory>] [--centrallogging] # @@ -56,6 +57,9 @@ # port:: # The port to which to connect on the remote server. Currently defaults to 8139. # +# onetime:: +# Run the configuration once, rather than as a long-running daemon. +# # server:: # The remote server from whom to receive the local configuration. Currently # must also be the certificate authority. Currently defaults to 'puppet'. @@ -111,6 +115,7 @@ result = GetoptLong.new( [ "--help", "-h", GetoptLong::NO_ARGUMENT ], [ "--logdest", "-l", GetoptLong::REQUIRED_ARGUMENT ], [ "--noop", "-n", GetoptLong::NO_ARGUMENT ], + [ "--onetime", "-o", GetoptLong::NO_ARGUMENT ], [ "--port", "-p", GetoptLong::REQUIRED_ARGUMENT ], [ "--server", "-s", GetoptLong::REQUIRED_ARGUMENT ], [ "--ssldir", GetoptLong::REQUIRED_ARGUMENT ], @@ -126,6 +131,8 @@ args = {} waitforcert = false +onetime = false + centrallogs = false begin result.each { |opt,arg| @@ -158,6 +165,8 @@ begin fqdn = arg when "--server" server = arg + when "--onetime" + onetime = true when "--port" args[:Port] = arg when "--logdest" @@ -239,16 +248,8 @@ trap(:INT) { exit(1) } -# and then retrieve and apply our configuration -begin - client.getconfig - client.apply -rescue => detail - Puppet.err detail.to_s - if Puppet[:debug] - puts detail.backtrace - end - exit(13) -end +client.run(onetime) + +#Puppet.join # $Id$ diff --git a/bin/puppetmasterd b/bin/puppetmasterd index 592051e3d..685ff6485 100755 --- a/bin/puppetmasterd +++ b/bin/puppetmasterd @@ -61,6 +61,9 @@ # configurations. Defaults to /etc/puppet/manifests/site.pp. # # noca:: +# Do not function as a file bucket. +# +# noca:: # Do not function as a certificate authority. # # nonodes:: @@ -116,6 +119,7 @@ result = GetoptLong.new( [ "--logdest", "-l", GetoptLong::REQUIRED_ARGUMENT ], [ "--manifest", "-m", GetoptLong::REQUIRED_ARGUMENT ], [ "--noca", GetoptLong::NO_ARGUMENT ], + [ "--nobucket", GetoptLong::NO_ARGUMENT ], [ "--nonodes", GetoptLong::NO_ARGUMENT ], [ "--parseonly", GetoptLong::NO_ARGUMENT ], [ "--port", "-p", GetoptLong::REQUIRED_ARGUMENT ], @@ -139,12 +143,15 @@ haveca = true master = {} ca = {} fs = {} +bucket = {} args = {} #user = Puppet[:user] #group = Puppet[:group] user = nil group = nil +havebucket = true + parseonly = false begin @@ -177,6 +184,8 @@ begin master[:File] = arg when "--noca" haveca = false + when "--nobucket" + havebucket = false when "--nonodes" master[:UseNodes] = false when "--parseonly" @@ -271,6 +280,10 @@ if haveca handlers[:CA] = ca end +#if havebucket +# handlers[:FileBucket] = bucket +#end + unless fs.include?(:Config) if File.exists?(Puppet[:fileserverconfig]) fs[:Config] = Puppet[:fileserverconfig] diff --git a/lib/puppet.rb b/lib/puppet.rb index 7609f6f98..5dacb15bf 100644 --- a/lib/puppet.rb +++ b/lib/puppet.rb @@ -207,6 +207,18 @@ PUPPETVERSION = '0.11.2' return retval end + def self.join + return unless defined? @threads + @threads.each { |th| th.join } + end + + def self.newthread + @threads ||= [] + @threads << Thread.new { + yield + } + end + def self.setdefault(param,value) if value.is_a?(Array) if value[0].is_a?(Symbol) diff --git a/lib/puppet/client.rb b/lib/puppet/client.rb index adc6b09ba..9023a2529 100644 --- a/lib/puppet/client.rb +++ b/lib/puppet/client.rb @@ -276,6 +276,26 @@ module Puppet return transaction end + # Cache the config + def cache(text) + Puppet.info "Caching configuration at %s" % self.cachefile + confdir = File.dirname(Puppet[:localconfig]) + unless FileTest.exists?(confdir) + Puppet.recmkdir(confdir, 0770) + end + File.open(self.cachefile + ".tmp", "w", 0660) { |f| + f.print text + } + File.rename(self.cachefile + ".tmp", self.cachefile) + end + + def cachefile + unless defined? @cachefile + @cachefile = Puppet[:localconfig] + ".yaml" + end + @cachefile + end + # Initialize and load storage def dostorage begin @@ -293,9 +313,27 @@ module Puppet end end + # Check whether our configuration is up to date + def fresh? + unless defined? @configstamp + return false + end + + # We're willing to give a 2 second drift + if @driver.freshness - @configstamp < 1 + return true + else + return false + end + end + # Retrieve the config from a remote server. If this fails, then # use the cached copy. def getconfig + if self.fresh? + Puppet.info "Config is up to date" + return + end Puppet.debug("getting config") dostorage() @@ -309,50 +347,47 @@ module Puppet objects = nil if @local + # If we're local, we don't have to do any of the conversion + # stuff. objects = @driver.getconfig(facts, "yaml") + @configstamp = Time.now.to_i if objects == "" raise Puppet::Error, "Could not retrieve configuration" end else + textobjects = "" + textfacts = CGI.escape(YAML.dump(facts)) # error handling for this is done in the network client - textobjects = @driver.getconfig(textfacts, "yaml") - - unless textobjects == "" - begin - textobjects = CGI.unescape(textobjects) - rescue => detail - raise Puppet::Error, "Could not CGI.unescape configuration" - end + begin + textobjects = @driver.getconfig(textfacts, "yaml") + rescue => detail + Puppet.err "Could not retrieve configuration: %s" % detail end - cachefile = Puppet[:localconfig] + ".yaml" - if @cache + fromcache = false + if textobjects == "" + textobjects = self.retrievecache if textobjects == "" - if FileTest.exists?(cachefile) - textobjects = File.read(cachefile) - else - raise Puppet::Error.new( - "Cannot connect to server and there is no cached configuration" - ) - end - else - # We store the config so that if we can't connect - # next time, we can just run against the most - # recently acquired copy. - Puppet.info "Caching configuration at %s" % cachefile - confdir = File.dirname(Puppet[:localconfig]) - unless FileTest.exists?(confdir) - Puppet.recmkdir(confdir, 0770) - end - File.open(cachefile, "w", 0660) { |f| - f.print textobjects - } + raise Puppet::Error.new( + "Cannot connect to server and there is no cached configuration" + ) end - elsif textobjects == "" - raise Puppet::Error, "Could not retrieve configuration" + Puppet.notice "Could not get config; using cached copy" + fromcache = true + end + + begin + textobjects = CGI.unescape(textobjects) + @configstamp = Time.now.to_i + rescue => detail + raise Puppet::Error, "Could not CGI.unescape configuration" + end + + if @cache and ! fromcache + self.cache(textobjects) end begin @@ -372,14 +407,14 @@ module Puppet if classes = objects.classes self.setclasses(classes) else - Puppet.info "No classes" + Puppet.info "No classes to store" end # Clear all existing objects, so we can recreate our stack. - @objects = nil if defined? @objects Puppet::Type.allclear end + @objects = nil # Now convert the objects to real Puppet objects @objects = objects.to_type @@ -395,6 +430,46 @@ module Puppet return @objects end + # Retrieve the cached config + def retrievecache + if FileTest.exists?(self.cachefile) + return File.read(self.cachefile) + else + return "" + end + end + + # The code that actually runs the configuration. For now, just + # ignore the onetime thing. + def run(onetime = false) + #if onetime + begin + self.getconfig + self.apply + rescue => detail + Puppet.err detail.to_s + if Puppet[:debug] + puts detail.backtrace + end + exit(13) + end + return + #end + +# Puppet.newthread do +# begin +# self.getconfig +# self.apply +# rescue => detail +# Puppet.err detail.to_s +# if Puppet[:debug] +# puts detail.backtrace +# end +# exit(13) +# end +# end + end + def setclasses(ary) begin File.open(Puppet[:classfile], "w") { |f| diff --git a/lib/puppet/parser/interpreter.rb b/lib/puppet/parser/interpreter.rb index b1cf4207c..942c417a2 100644 --- a/lib/puppet/parser/interpreter.rb +++ b/lib/puppet/parser/interpreter.rb @@ -10,7 +10,7 @@ require 'puppet/parser/scope' module Puppet module Parser class Interpreter - attr_accessor :ast + attr_accessor :ast, :filetimeout # just shorten the constant path a bit, using what amounts to an alias AST = Puppet::Parser::AST @@ -21,6 +21,9 @@ module Puppet end @file = hash[:Manifest] + @filetimeout = hash[:ParseCheck] || 15 + + @lastchecked = 0 if hash.include?(:UseNodes) @usenodes = hash[:UseNodes] @@ -38,6 +41,11 @@ module Puppet evaluate end + def parsedate + parsefiles() + @parsedate + end + # evaluate our whole tree def run(client, facts) parsefiles() @@ -124,14 +132,20 @@ module Puppet def parsefiles if defined? @parser - unless @parser.reparse? - return false + # Only check the files every 15 seconds or so, not on + # every single connection + if (Time.now - @lastchecked).to_i >= @filetimeout.to_i + unless @parser.reparse? + @lastchecked = Time.now + return false + end + else + return end end unless FileTest.exists?(@file) if @ast - Puppet.warning "Manifest %s has disappeared" % @file return else raise Puppet::Error, "Manifest %s must exist" % @file @@ -146,6 +160,10 @@ module Puppet @parser.file = @file @ast = @parser.parse + # Mark when we parsed, so we can check freshness + @parsedate = Time.now.to_i + @lastchecked = Time.now + # Reevaluate the config. This is what actually replaces the # existing scope. evaluate diff --git a/lib/puppet/server/master.rb b/lib/puppet/server/master.rb index 8d4ddbfde..1b9883ead 100644 --- a/lib/puppet/server/master.rb +++ b/lib/puppet/server/master.rb @@ -14,8 +14,26 @@ class Server @interface = XMLRPC::Service::Interface.new("puppetmaster") { |iface| iface.add_method("string getconfig(string)") + iface.add_method("int freshness()") } + def filetimeout + @interpreter.filetimeout + end + + def filetimeout=(int) + @interpreter.filetimeout = int + end + + # Tell a client whether there's a fresh config for it + def freshness(client = nil, clientip = nil) + if defined? @interpreter + return @interpreter.parsedate + else + return 0 + end + end + def initialize(hash = {}) # FIXME this should all be s/:File/:Manifest/g or something @@ -35,9 +53,11 @@ class Server @ca = nil end + @parsecheck = hash[:FileTimeout] || 15 + Puppet.debug("Creating interpreter") - args = {:Manifest => @file} + args = {:Manifest => @file, :ParseCheck => @parsecheck} if hash.include?(:UseNodes) args[:UseNodes] = hash[:UseNodes] diff --git a/lib/puppet/type/pfile/checksum.rb b/lib/puppet/type/pfile/checksum.rb index 2f71a1a30..61d8fea80 100755 --- a/lib/puppet/type/pfile/checksum.rb +++ b/lib/puppet/type/pfile/checksum.rb @@ -134,7 +134,6 @@ module Puppet # out of sync. We don't want to generate an event the first # time we get a sum. if ! defined? @should or @should == [:nosum] - self.info "@should is %s" % @should.inspect @should = [@is] # FIXME we should support an updatechecksums-like mechanism self.updatesum diff --git a/test/server/master.rb b/test/server/master.rb index a300a18a4..4ece12a87 100644 --- a/test/server/master.rb +++ b/test/server/master.rb @@ -114,7 +114,7 @@ class TestMaster < Test::Unit::TestCase :File => manifest, :UseNodes => false, :Local => true, - :FileTimeout => 0.5 + :FileTimeout => 15 ) } assert_nothing_raised() { @@ -123,23 +123,52 @@ class TestMaster < Test::Unit::TestCase ) } + # The client doesn't have a config, so it can't be up to date + assert(! client.fresh?, "Client is incorrectly up to date") + assert_nothing_raised { client.getconfig client.apply } + # Now it should be up to date + assert(client.fresh?, "Client is not up to date") + + # Cache this value for later + parse1 = master.freshness + + # Verify the config got applied assert(FileTest.exists?(@createdfile), "Created file %s does not exist" % @createdfile) - sleep 1 Puppet::Type.allclear + sleep 1.5 + # Create a new manifest File.open(manifest, "w") { |f| f.puts "file { \"%s\": ensure => file }\n" % file2 } + + # Verify that the master doesn't immediately reparse the file; we + # want to wait through the timeout + assert_equal(parse1, master.freshness, "Master did not wait through timeout") + assert(client.fresh?, "Client is not up to date") + + assert_nothing_raised("Could not resent the file timeout") { + master.filetimeout = 0 + } + assert_equal(0, master.filetimeout) + + # Now make sure the master does reparse + #Puppet.notice "%s vs %s" % [parse1, master.freshness] + assert(parse1 != master.freshness, "Master did not reparse file") + assert(! client.fresh?, "Client is incorrectly up to date") + + # Retrieve and apply the new config assert_nothing_raised { client.getconfig client.apply } + assert(client.fresh?, "Client is not up to date") assert(FileTest.exists?(file2), "Second file %s does not exist" % file2) end |