diff --git a/lib/puppet/parser/interpreter.rb b/lib/puppet/parser/interpreter.rb index e37ef5efe..fa90838f0 100644 --- a/lib/puppet/parser/interpreter.rb +++ b/lib/puppet/parser/interpreter.rb @@ -1,145 +1,145 @@ require 'puppet' require 'timeout' require 'puppet/rails' require 'puppet/util/methodhelper' require 'puppet/parser/parser' require 'puppet/parser/configuration' require 'puppet/parser/scope' # The interpreter is a very simple entry-point class that # manages the existence of the parser (e.g., replacing it # when files are reparsed). You can feed it a node and # get the node's configuration back. class Puppet::Parser::Interpreter include Puppet::Util attr_accessor :usenodes attr_reader :parser include Puppet::Util::Errors # 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" end if hash.include?(:UseNodes) @usenodes = hash[: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? Puppet::Rails.init else raise Puppet::Error, "Rails is missing; cannot store configurations" end end @files = [] # Create our parser object parsefiles end def parsedate parsefiles() @parsedate end # evaluate our whole tree def compile(node) parsefiles() - return Puppet::Parser::Configuration.new(node, @parser).compile + return Puppet::Parser::Configuration.new(node, @parser, :ast_nodes => @usenodes).compile 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. 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 end # Mark when we parsed, so we can check freshness @parsedate = Time.now.to_i rescue => detail if Puppet[:trace] puts detail.backtrace end Puppet.err "Could not parse; using old configuration: %s" % detail end end end # $Id$ diff --git a/test/language/interpreter.rb b/test/language/interpreter.rb index ebbc3f87f..1adcb7bde 100755 --- a/test/language/interpreter.rb +++ b/test/language/interpreter.rb @@ -1,152 +1,152 @@ #!/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).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$