summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Rakefile2
-rw-r--r--lib/puppet/network/handler/configuration.rb18
-rw-r--r--lib/puppet/network/handler/master.rb4
-rw-r--r--lib/puppet/parser/configuration.rb11
-rw-r--r--lib/puppet/parser/interpreter.rb140
-rw-r--r--lib/puppet/parser/parser_support.rb14
-rw-r--r--lib/puppet/transportable.rb13
-rw-r--r--spec/spec.opts5
-rwxr-xr-xspec/unit/parser/interpreter.rb76
-rwxr-xr-xtest/language/configuration.rb26
-rwxr-xr-xtest/language/functions.rb9
-rwxr-xr-xtest/language/interpreter.rb152
-rw-r--r--test/lib/puppettest/parsertesting.rb3
13 files changed, 186 insertions, 287 deletions
diff --git a/Rakefile b/Rakefile
index d560b85b9..6b0f6a827 100644
--- a/Rakefile
+++ b/Rakefile
@@ -137,5 +137,3 @@ task :dailyclean do
File.unlink(file)
end
end
-
-# $Id$
diff --git a/lib/puppet/network/handler/configuration.rb b/lib/puppet/network/handler/configuration.rb
index 7e91d74d6..a1b22207e 100644
--- a/lib/puppet/network/handler/configuration.rb
+++ b/lib/puppet/network/handler/configuration.rb
@@ -57,14 +57,18 @@ class Puppet::Network::Handler
# Return the configuration version.
def version(client = nil, clientip = nil)
- v = interpreter.parsedate
- # If we can find the node, then store the fact that the node
- # has checked in.
- if client and node = node_handler.details(client)
- update_node_check(node)
+ if client
+ if node = node_handler.details(client)
+ update_node_check(node)
+ return interpreter.configuration_version(node)
+ else
+ raise Puppet::Error, "Could not find node '%s'" % client
+ end
+ else
+ # Just return something that will always result in a recompile, because
+ # this is local.
+ return 0
end
-
- return v
end
private
diff --git a/lib/puppet/network/handler/master.rb b/lib/puppet/network/handler/master.rb
index acc6c4cda..0cab94f69 100644
--- a/lib/puppet/network/handler/master.rb
+++ b/lib/puppet/network/handler/master.rb
@@ -32,8 +32,8 @@ class Puppet::Network::Handler
# Allow specification of a code snippet or of a file
if code = hash[:Code]
args[:Code] = code
- else
- args[:Manifest] = hash[:Manifest] || Puppet[:manifest]
+ elsif man = hash[:Manifest]
+ args[:Manifest] = man
end
if hash[:Local]
diff --git a/lib/puppet/parser/configuration.rb b/lib/puppet/parser/configuration.rb
index 44fb8c476..148f4dcd1 100644
--- a/lib/puppet/parser/configuration.rb
+++ b/lib/puppet/parser/configuration.rb
@@ -347,15 +347,8 @@ class Puppet::Parser::Configuration
# Retrive the bucket for the top-level scope and set the appropriate metadata.
result = buckets[topscope]
- case topscope.type
- when "": result.type = "main"
- when nil: devfail "A Scope with no type"
- else
- result.type = topscope.type
- end
- if topscope.name
- result.name = topscope.name
- end
+
+ result.copy_type_and_name(topscope)
unless classlist.empty?
result.classes = classlist
diff --git a/lib/puppet/parser/interpreter.rb b/lib/puppet/parser/interpreter.rb
index fa90838f0..0398115de 100644
--- a/lib/puppet/parser/interpreter.rb
+++ b/lib/puppet/parser/interpreter.rb
@@ -14,31 +14,32 @@ class Puppet::Parser::Interpreter
include Puppet::Util
attr_accessor :usenodes
- attr_reader :parser
+ attr_accessor :code, :file
include Puppet::Util::Errors
+ # Determine the configuration version for a given node's environment.
+ def configuration_version(node)
+ parser(node.environment).version
+ end
+
+ # evaluate our whole tree
+ def compile(node)
+ return Puppet::Parser::Configuration.new(node, parser(node.environment), :ast_nodes => usenodes?).compile
+ end
+
# create our interpreter
- def initialize(hash)
- if @code = hash[:Code]
- @file = nil # to avoid warnings
- elsif ! @file = hash[:Manifest]
- devfail "You must provide code or a manifest"
+ def initialize(options = {})
+ if @code = options[:Code]
+ elsif @file = options[:Manifest]
end
- if hash.include?(:UseNodes)
- @usenodes = hash[:UseNodes]
+ if options.include?(:UseNodes)
+ @usenodes = options[:UseNodes]
else
@usenodes = true
end
- # By default, we only search for parsed nodes.
- @nodesource = :code
-
- @setup = false
-
- @local = hash[:Local] || false
-
# The class won't always be defined during testing.
if Puppet[:storeconfigs]
if Puppet.features.rails?
@@ -48,98 +49,47 @@ class Puppet::Parser::Interpreter
end
end
- @files = []
-
- # Create our parser object
- parsefiles
+ @parsers = {}
end
- def parsedate
- parsefiles()
- @parsedate
- end
-
- # evaluate our whole tree
- def compile(node)
- parsefiles()
-
- return Puppet::Parser::Configuration.new(node, @parser, :ast_nodes => @usenodes).compile
+ # Should we parse ast nodes?
+ def usenodes?
+ defined?(@usenodes) and @usenodes
end
private
- # Check whether any of our files have changed.
- def checkfiles
- if @files.find { |f| f.changed? }
- @parsedate = Time.now.to_i
- end
- end
-
- # Parse the files, generating our parse tree. This automatically
- # reparses only if files are updated, so it's safe to call multiple
- # times.
- def parsefiles
- # First check whether there are updates to any non-puppet files
- # like templates. If we need to reparse, this will get quashed,
- # but it needs to be done first in case there's no reparse
- # but there are other file changes.
- checkfiles()
-
- # Check if the parser should reparse.
- if @file
- if defined? @parser
- if stamp = @parser.reparse?
- Puppet.notice "Reloading files"
- else
- return false
- end
- end
-
- unless FileTest.exists?(@file)
- # If we've already parsed, then we're ok.
- if findclass("", "")
- return
- else
- raise Puppet::Error, "Manifest %s must exist" % @file
- end
- end
- end
-
- # Create a new parser, just to keep things fresh. Don't replace our
- # current parser until we know weverything works.
- newparser = Puppet::Parser::Parser.new()
- if @code
- newparser.string = @code
- else
- newparser.file = @file
- end
-
- # Parsing stores all classes and defines and such in their
- # various tables, so we don't worry about the return.
+ # Create a new parser object and pre-parse the configuration.
+ def create_parser(environment)
begin
- if @local
- newparser.parse
- else
- benchmark(:info, "Parsed manifest") do
- newparser.parse
- end
- end
- # We've gotten this far, so it's ok to swap the parsers.
- oldparser = @parser
- @parser = newparser
- if oldparser
- oldparser.clear
+ parser = Puppet::Parser::Parser.new(environment)
+ if self.code
+ parser.code = self.code
+ elsif self.file
+ parser.file = self.file
end
-
- # Mark when we parsed, so we can check freshness
- @parsedate = Time.now.to_i
+ parser.parse
+ return parser
rescue => detail
if Puppet[:trace]
puts detail.backtrace
end
- Puppet.err "Could not parse; using old configuration: %s" % detail
+ Puppet.err "Could not parse for environment %s: %s" % [environment, detail]
+ return nil
end
end
-end
-# $Id$
+ # Return the parser for a specific environment.
+ def parser(environment)
+ if ! @parsers[environment] or @parsers[environment].reparse?
+ if tmp = create_parser(environment)
+ @parsers[environment].clear if @parsers[environment]
+ @parsers[environment] = tmp
+ end
+ unless @parsers[environment]
+ raise Puppet::Error, "Could not parse any configurations"
+ end
+ end
+ @parsers[environment]
+ end
+end
diff --git a/lib/puppet/parser/parser_support.rb b/lib/puppet/parser/parser_support.rb
index 967508e56..dfc91ba12 100644
--- a/lib/puppet/parser/parser_support.rb
+++ b/lib/puppet/parser/parser_support.rb
@@ -15,7 +15,7 @@ class Puppet::Parser::Parser
AST = Puppet::Parser::AST
- attr_reader :file, :interp
+ attr_reader :file, :version
attr_accessor :files
@@ -202,11 +202,9 @@ class Puppet::Parser::Parser
}
end
- def initialize(astset = nil)
+ def initialize(environment)
+ @environment = environment
initvars()
- if astset
- @astset = astset
- end
end
# Initialize or reset all of our variables.
@@ -427,6 +425,7 @@ class Puppet::Parser::Parser
# Store the results as the top-level class.
newclass("", :code => main)
end
+ @version = Time.now.to_i
return @astset
ensure
@lexer.clear
@@ -446,7 +445,8 @@ class Puppet::Parser::Parser
end
# Add a new file to be checked when we're checking to see if we should be
- # reparsed.
+ # reparsed. This is basically only used by the TemplateWrapper to let the
+ # parser know about templates that should be parsed.
def watch_file(*files)
files.each do |file|
unless file.is_a? Puppet::Util::LoadedFile
@@ -456,5 +456,3 @@ class Puppet::Parser::Parser
end
end
end
-
-# $Id$
diff --git a/lib/puppet/transportable.rb b/lib/puppet/transportable.rb
index aa7eb92f7..acd69fb0c 100644
--- a/lib/puppet/transportable.rb
+++ b/lib/puppet/transportable.rb
@@ -100,6 +100,19 @@ module Puppet
end
}
+ # Copy a scope's type and name.
+ def copy_type_and_name(scope)
+ case scope.type
+ when "": self.type = "main"
+ when nil: devfail "A Scope with no type"
+ else
+ self.type = scope.type
+ end
+ if scope.name
+ self.name = scope.name
+ end
+ end
+
# Remove all collectable objects from our tree, since the client
# should not see them.
def collectstrip!
diff --git a/spec/spec.opts b/spec/spec.opts
new file mode 100644
index 000000000..2cac5f260
--- /dev/null
+++ b/spec/spec.opts
@@ -0,0 +1,5 @@
+--colour
+--format
+s
+--loadby
+mtime \ No newline at end of file
diff --git a/spec/unit/parser/interpreter.rb b/spec/unit/parser/interpreter.rb
index 0e32b8c5b..7328e2651 100755
--- a/spec/unit/parser/interpreter.rb
+++ b/spec/unit/parser/interpreter.rb
@@ -2,6 +2,40 @@
require File.dirname(__FILE__) + '/../../spec_helper'
+describe Puppet::Parser::Interpreter, " when initializing" do
+ it "should default to neither code nor file" do
+ interp = Puppet::Parser::Interpreter.new
+ interp.code.should be_nil
+ interp.file.should be_nil
+ end
+
+ it "should set the code to parse" do
+ interp = Puppet::Parser::Interpreter.new :Code => :code
+ interp.code.should equal(:code)
+ end
+
+ it "should set the file to parse" do
+ interp = Puppet::Parser::Interpreter.new :Manifest => :file
+ interp.file.should equal(:file)
+ end
+
+ it "should set code and ignore manifest when both are present" do
+ interp = Puppet::Parser::Interpreter.new :Code => :code, :Manifest => :file
+ interp.code.should equal(:code)
+ interp.file.should be_nil
+ end
+
+ it "should default to usenodes" do
+ interp = Puppet::Parser::Interpreter.new
+ interp.usenodes?.should be_true
+ end
+
+ it "should allow disabling of usenodes" do
+ interp = Puppet::Parser::Interpreter.new :UseNodes => false
+ interp.usenodes?.should be_false
+ end
+end
+
describe Puppet::Parser::Interpreter, " when creating parser instances" do
before do
@interp = Puppet::Parser::Interpreter.new
@@ -92,3 +126,45 @@ describe Puppet::Parser::Interpreter, " when managing parser instances" do
@interp.send(:parser, :second_env).should equal(other_parser)
end
end
+
+describe Puppet::Parser::Interpreter, " when compiling configurations" do
+ before do
+ @interp = Puppet::Parser::Interpreter.new
+ end
+
+ it "should create a configuration with the node, parser, and whether to use ast nodes" do
+ node = mock('node')
+ node.expects(:environment).returns(:myenv)
+ compile = mock 'compile'
+ compile.expects(:compile).returns(:config)
+ parser = mock 'parser'
+ @interp.expects(:parser).with(:myenv).returns(parser)
+ @interp.expects(:usenodes?).returns(true)
+ Puppet::Parser::Configuration.expects(:new).with(node, parser, :ast_nodes => true).returns(compile)
+ @interp.compile(node)
+
+ # Now try it when usenodes is true
+ @interp = Puppet::Parser::Interpreter.new :UseNodes => false
+ node.expects(:environment).returns(:myenv)
+ compile.expects(:compile).returns(:config)
+ @interp.expects(:parser).with(:myenv).returns(parser)
+ @interp.expects(:usenodes?).returns(false)
+ Puppet::Parser::Configuration.expects(:new).with(node, parser, :ast_nodes => false).returns(compile)
+ @interp.compile(node).should equal(:config)
+ end
+end
+
+describe Puppet::Parser::Interpreter, " when returning configuration version" do
+ before do
+ @interp = Puppet::Parser::Interpreter.new
+ end
+
+ it "should ask the appropriate parser for the configuration version" do
+ node = mock 'node'
+ node.expects(:environment).returns(:myenv)
+ parser = mock 'parser'
+ parser.expects(:version).returns(:myvers)
+ @interp.expects(:parser).with(:myenv).returns(parser)
+ @interp.configuration_version(node).should equal(:myvers)
+ end
+end
diff --git a/test/language/configuration.rb b/test/language/configuration.rb
index a17b5a7ae..409d4ca1d 100755
--- a/test/language/configuration.rb
+++ b/test/language/configuration.rb
@@ -424,17 +424,22 @@ class TestConfiguration < Test::Unit::TestCase
Puppet.features.expects(:rails?).returns(true)
Puppet::Rails.expects(:connect)
- args = {:name => "yay"}
- config.expects(:store_to_active_record).with(args)
- config.send(:store, args)
+ node = mock 'node'
+ resource_table = mock 'resources'
+ resource_table.expects(:values).returns(:resources)
+ config.instance_variable_set("@node", node)
+ config.instance_variable_set("@resource_table", resource_table)
+ config.expects(:store_to_active_record).with(node, :resources)
+ config.send(:store)
end
def test_store_to_active_record
config = mkconfig
- args = {:name => "yay"}
+ node = mock 'node'
+ node.expects(:name).returns("myname")
Puppet::Rails::Host.stubs(:transaction).yields
- Puppet::Rails::Host.expects(:store).with(args)
- config.send(:store_to_active_record, args)
+ Puppet::Rails::Host.expects(:store).with(node, :resources)
+ config.send(:store_to_active_record, node, :resources)
end
# Make sure that 'finish' gets called on all of our resources.
@@ -473,8 +478,10 @@ class TestConfiguration < Test::Unit::TestCase
# Get rid of the topscope
scopes.vertices.each { |v| scopes.remove_vertex!(v) }
+ bucket = []
scope = mock("scope")
- scope.expects(:to_trans).returns([])
+ bucket.expects(:copy_type_and_name).with(scope)
+ scope.expects(:to_trans).returns(bucket)
scopes.add_vertex! scope
# The topscope is the key to picking out the top of the graph.
@@ -510,7 +517,10 @@ class TestConfiguration < Test::Unit::TestCase
# Create our scopes.
top = mock("top")
- top.expects(:to_trans).returns(fakebucket.new("top"))
+ topbucket = fakebucket.new "top"
+ topbucket.expects(:copy_type_and_name).with(top)
+ top.stubs(:copy_type_and_name)
+ top.expects(:to_trans).returns(topbucket)
# The topscope is the key to picking out the top of the graph.
config.instance_variable_set("@topscope", top)
middle = mock("middle")
diff --git a/test/language/functions.rb b/test/language/functions.rb
index 42d8d7585..9314df179 100755
--- a/test/language/functions.rb
+++ b/test/language/functions.rb
@@ -208,8 +208,11 @@ class TestLangFunctions < Test::Unit::TestCase
:UseNodes => false
)
node = mknode
+ node.stubs(:environment).returns("yay")
- parsedate = interp.parsedate()
+ Puppet[:environment] = "yay"
+
+ version = interp.configuration_version(node)
objects = nil
assert_nothing_raised {
@@ -233,9 +236,9 @@ class TestLangFunctions < Test::Unit::TestCase
assert_nothing_raised {
objects = interp.compile(node)
}
- newdate = interp.parsedate()
+ newversion = interp.configuration_version(node)
- assert(parsedate != newdate, "Parse date did not change")
+ assert(version != newversion, "Parse date did not change")
end
def test_template_defined_vars
diff --git a/test/language/interpreter.rb b/test/language/interpreter.rb
deleted file mode 100755
index 1adcb7bde..000000000
--- a/test/language/interpreter.rb
+++ /dev/null
@@ -1,152 +0,0 @@
-#!/usr/bin/env ruby
-
-$:.unshift("../lib").unshift("../../lib") if __FILE__ =~ /\.rb$/
-
-require 'facter'
-
-require 'puppet'
-require 'puppet/parser/interpreter'
-require 'puppet/parser/parser'
-require 'puppet/network/client'
-require 'puppettest'
-require 'puppettest/resourcetesting'
-require 'puppettest/parsertesting'
-require 'puppettest/servertest'
-require 'timeout'
-
-class TestInterpreter < PuppetTest::TestCase
- include PuppetTest
- include PuppetTest::ServerTest
- include PuppetTest::ParserTesting
- include PuppetTest::ResourceTesting
- AST = Puppet::Parser::AST
-
- # create a simple manifest that uses nodes to create a file
- def mknodemanifest(node, file)
- createdfile = tempfile()
-
- File.open(file, "w") { |f|
- f.puts "node %s { file { \"%s\": ensure => file, mode => 755 } }\n" %
- [node, createdfile]
- }
-
- return [file, createdfile]
- end
-
- def test_reloadfiles
- node = mknode(Facter["hostname"].value)
-
- file = tempfile()
-
- # Create a first version
- createdfile = mknodemanifest(node.name, file)
-
- interp = nil
- assert_nothing_raised {
- interp = Puppet::Parser::Interpreter.new(:Manifest => file)
- }
-
- config = nil
- assert_nothing_raised {
- config = interp.compile(node)
- }
- Puppet[:filetimeout] = -5
-
- # Now create a new file
- createdfile = mknodemanifest(node.name, file)
-
- newconfig = nil
- assert_nothing_raised {
- newconfig = interp.compile(node)
- }
-
- assert(config != newconfig, "Configs are somehow the same")
- end
-
- def test_parsedate
- Puppet[:filetimeout] = 0
- main = tempfile()
- sub = tempfile()
- mainfile = tempfile()
- subfile = tempfile()
- count = 0
- updatemain = proc do
- count += 1
- File.open(main, "w") { |f|
- f.puts "import '#{sub}'
- file { \"#{mainfile}\": content => #{count} }
- "
- }
- end
- updatesub = proc do
- count += 1
- File.open(sub, "w") { |f|
- f.puts "file { \"#{subfile}\": content => #{count} }
- "
- }
- end
-
- updatemain.call
- updatesub.call
-
- interp = Puppet::Parser::Interpreter.new(
- :Manifest => main,
- :Local => true
- )
-
- date = interp.parsedate
-
- # Now update the site file and make sure we catch it
- sleep 1
- updatemain.call
- newdate = interp.parsedate
- assert(date != newdate, "Parsedate was not updated")
- date = newdate
-
- # And then the subfile
- sleep 1
- updatesub.call
- newdate = interp.parsedate
- assert(date != newdate, "Parsedate was not updated")
- end
-
- # Make sure our whole chain works.
- def test_compile
- interp = mkinterp
- interp.expects(:parsefiles)
- parser = interp.instance_variable_get("@parser")
-
- node = mock('node')
- config = mock('config')
- config.expects(:compile).returns(:config)
- Puppet::Parser::Configuration.expects(:new).with(node, parser, :ast_nodes => interp.usenodes).returns(config)
- assert_equal(:config, interp.compile(node), "Did not return the results of config.compile")
- end
-
- # Make sure that reparsing is atomic -- failures don't cause a broken state, and we aren't subject
- # to race conditions if someone contacts us while we're reparsing.
- def test_atomic_reparsing
- Puppet[:filetimeout] = -10
- file = tempfile
- File.open(file, "w") { |f| f.puts %{file { '/tmp': ensure => directory }} }
- interp = mkinterp :Manifest => file, :UseNodes => false
-
- assert_nothing_raised("Could not compile the first time") do
- interp.compile(mknode("yay"))
- end
-
- oldparser = interp.send(:instance_variable_get, "@parser")
-
- # Now add a syntax failure
- File.open(file, "w") { |f| f.puts %{file { /tmp: ensure => directory }} }
- assert_nothing_raised("Could not compile the first time") do
- interp.compile(mknode("yay"))
- end
-
- # And make sure the old parser is still there
- newparser = interp.send(:instance_variable_get, "@parser")
- assert_equal(oldparser.object_id, newparser.object_id, "Failed parser still replaced existing parser")
- end
-end
-
-# $Id$
diff --git a/test/lib/puppettest/parsertesting.rb b/test/lib/puppettest/parsertesting.rb
index 3e2930728..326c25756 100644
--- a/test/lib/puppettest/parsertesting.rb
+++ b/test/lib/puppettest/parsertesting.rb
@@ -49,9 +49,10 @@ module PuppetTest::ParserTesting
end
def mknode(name = nil)
+ require 'puppet/node'
name ||= "nodename"
Puppet::Network::Handler.handler(:node)
- Puppet::Network::Handler::Node::SimpleNode.new(name)
+ Puppet::Node.new(name)
end
def mkinterp(args = {})