diff options
-rw-r--r-- | Rakefile | 2 | ||||
-rw-r--r-- | lib/puppet/network/handler/configuration.rb | 18 | ||||
-rw-r--r-- | lib/puppet/network/handler/master.rb | 4 | ||||
-rw-r--r-- | lib/puppet/parser/configuration.rb | 11 | ||||
-rw-r--r-- | lib/puppet/parser/interpreter.rb | 140 | ||||
-rw-r--r-- | lib/puppet/parser/parser_support.rb | 14 | ||||
-rw-r--r-- | lib/puppet/transportable.rb | 13 | ||||
-rw-r--r-- | spec/spec.opts | 5 | ||||
-rwxr-xr-x | spec/unit/parser/interpreter.rb | 76 | ||||
-rwxr-xr-x | test/language/configuration.rb | 26 | ||||
-rwxr-xr-x | test/language/functions.rb | 9 | ||||
-rwxr-xr-x | test/language/interpreter.rb | 152 | ||||
-rw-r--r-- | test/lib/puppettest/parsertesting.rb | 3 |
13 files changed, 186 insertions, 287 deletions
@@ -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 = {}) |