summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-xbin/puppetd25
-rwxr-xr-xbin/puppetmasterd13
-rw-r--r--lib/puppet.rb12
-rw-r--r--lib/puppet/client.rb141
-rw-r--r--lib/puppet/parser/interpreter.rb26
-rw-r--r--lib/puppet/server/master.rb22
-rwxr-xr-xlib/puppet/type/pfile/checksum.rb1
-rw-r--r--test/server/master.rb33
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