diff --git a/lib/puppet/server/master.rb b/lib/puppet/server/master.rb index 198a6f682..1822e779e 100644 --- a/lib/puppet/server/master.rb +++ b/lib/puppet/server/master.rb @@ -1,193 +1,204 @@ require 'openssl' require 'puppet' require 'puppet/parser/interpreter' require 'puppet/sslcertificates' require 'xmlrpc/server' require 'yaml' module Puppet class Server class MasterError < Puppet::Error; end class Master < Handler include Puppet::Util attr_accessor :ast, :local attr_reader :ca @interface = XMLRPC::Service::Interface.new("puppetmaster") { |iface| iface.add_method("string getconfig(string)") iface.add_method("int freshness()") } # FIXME At some point, this should be autodocumenting. def addfacts(facts) # Add our server version to the fact list facts["serverversion"] = Puppet.version.to_s # And then add the server name and IP {"servername" => "hostname", "serverip" => "ipaddress" }.each do |var, fact| if obj = Facter[fact] facts[var] = obj.value else Puppet.warning "Could not retrieve fact %s" % fact end end end + # Manipulate the client name as appropriate. + def clientname(name, ip, facts) + # Always use the hostname from Facter. + client = facts["hostname"] + clientip = facts["ipaddress"] + if Puppet[:node_name] == 'cert' + if name + client = name + end + if ip + clientip = ip + end + end + + return client, clientip + 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 = {}) args = {} # Allow specification of a code snippet or of a file if code = hash[:Code] args[:Code] = code else args[:Manifest] = hash[:Manifest] || Puppet[:manifest] end if hash[:Local] @local = hash[:Local] else @local = false end args[:Local] = @local if hash.include?(:CA) and hash[:CA] @ca = Puppet::SSLCertificates::CA.new() else @ca = nil end Puppet.debug("Creating interpreter") if hash.include?(:UseNodes) args[:UseNodes] = hash[:UseNodes] elsif @local args[:UseNodes] = false end # This is only used by the cfengine module, or if --loadclasses was # specified in +puppet+. if hash.include?(:Classes) args[:Classes] = hash[:Classes] end @interpreter = Puppet::Parser::Interpreter.new(args) end def getconfig(facts, format = "marshal", client = nil, clientip = nil) if @local # we don't need to do anything, since we should already # have raw objects Puppet.debug "Our client is local" else Puppet.debug "Our client is remote" # XXX this should definitely be done in the protocol, somehow case format when "marshal": Puppet.warning "You should upgrade your client. 'Marshal' will not be supported much longer." begin facts = Marshal::load(CGI.unescape(facts)) rescue => detail raise XMLRPC::FaultException.new( 1, "Could not rebuild facts" ) end when "yaml": begin facts = YAML.load(CGI.unescape(facts)) rescue => detail raise XMLRPC::FaultException.new( 1, "Could not rebuild facts" ) end else raise XMLRPC::FaultException.new( 1, "Unavailable config format %s" % format ) end end - # Always use the hostname from Facter. - if Puppet[:node_name] == 'facter' - client = facts["hostname"] - clientip = facts["ipaddress"] - else - facts['hostname'] = client - end + client, clientip = clientname(client, clientip, facts) # Add any server-side facts to our server. addfacts(facts) retobjects = nil # This is hackish, but there's no "silence" option for benchmarks # right now if @local #begin retobjects = @interpreter.run(client, facts) #rescue Puppet::Error => detail # Puppet.err detail # raise XMLRPC::FaultException.new( # 1, detail.to_s # ) #rescue => detail # Puppet.err detail.to_s # return "" #end else benchmark(:notice, "Compiled configuration for %s" % client) do begin retobjects = @interpreter.run(client, facts) rescue Puppet::Error => detail Puppet.err detail raise XMLRPC::FaultException.new( 1, detail.to_s ) rescue => detail Puppet.err detail.to_s return "" end end end if @local return retobjects else str = nil case format when "marshal": str = Marshal::dump(retobjects) when "yaml": str = YAML.dump(retobjects) else raise XMLRPC::FaultException.new( 1, "Unavailable config format %s" % format ) end return CGI.escape(str) end end def local? if defined? @local and @local return true else return false end end end end end diff --git a/test/server/master.rb b/test/server/master.rb index 2d1b9ec2c..b04fc92fd 100644 --- a/test/server/master.rb +++ b/test/server/master.rb @@ -1,248 +1,301 @@ require 'puppet' require 'puppet/server' require 'puppet/client' require 'puppettest' class TestMaster < Test::Unit::TestCase include PuppetTest::ServerTest def teardown super #print "\n\n\n\n" if Puppet[:debug] end # run through all of the existing test files and make sure everything # works def test_files count = 0 textfiles { |file| Puppet.debug("parsing %s" % file) client = nil master = nil # create our master assert_nothing_raised() { # this is the default server setup master = Puppet::Server::Master.new( :Manifest => file, :UseNodes => false, :Local => true ) } # and our client assert_nothing_raised() { client = Puppet::Client::MasterClient.new( :Master => master ) } # pull our configuration a few times assert_nothing_raised() { client.getconfig stopservices Puppet::Type.allclear } assert_nothing_raised() { client.getconfig stopservices Puppet::Type.allclear } assert_nothing_raised() { client.getconfig stopservices Puppet::Type.allclear } # only test three files; that's plenty if count > 3 break end count += 1 } end def test_defaultmanifest textfiles { |file| Puppet[:manifest] = file client = nil master = nil assert_nothing_raised() { # this is the default server setup master = Puppet::Server::Master.new( :Manifest => file, :UseNodes => false, :Local => true ) } assert_nothing_raised() { client = Puppet::Client::MasterClient.new( :Master => master ) } # pull our configuration assert_nothing_raised() { client.getconfig stopservices Puppet::Type.allclear } break } end def test_filereread # Start with a normal setting Puppet[:filetimeout] = 15 manifest = mktestmanifest() file2 = @createdfile + "2" @@tmpfiles << file2 client = master = nil assert_nothing_raised() { # this is the default server setup master = Puppet::Server::Master.new( :Manifest => manifest, :UseNodes => false, :Local => true ) } assert_nothing_raised() { client = Puppet::Client::MasterClient.new( :Master => master ) } # The client doesn't have a config, so it can't be up to date assert(! client.fresh?, "Client is incorrectly up to date") + Puppet.config.use(:puppet) 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) 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") # Then eliminate it Puppet[:filetimeout] = 0 # 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 def test_addfacts master = nil file = mktestmanifest() # create our master assert_nothing_raised() { # this is the default server setup master = Puppet::Server::Master.new( :Manifest => file, :UseNodes => false, :Local => true ) } facts = {} assert_nothing_raised { master.addfacts(facts) } %w{serverversion servername serverip}.each do |fact| assert(facts.include?(fact), "Fact %s was not set" % fact) end end # Make sure we're using the hostname as configured with :node_name def test_hostname_in_getconfig master = nil file = tempfile() #@createdfile = File.join(tmpdir(), self.class.to_s + "manifesttesting" + # "_" + @method_name) file_cert = tempfile() file_fact = tempfile() certname = "y4yn3ss" - factname = Facter["hostname"].value + factname = Facter.value("hostname") File.open(file, "w") { |f| f.puts %{ node #{certname} { file { "#{file_cert}": ensure => file, mode => 755 } } node #{factname} { file { "#{file_fact}": ensure => file, mode => 755 } } } } # create our master assert_nothing_raised() { # this is the default server setup master = Puppet::Server::Master.new( :Manifest => file, :UseNodes => true, :Local => true ) } result = nil # Use the hostname from facter Puppet[:node_name] = 'facter' assert_nothing_raised { result = master.getconfig({"hostname" => factname}, "yaml", certname, "127.0.0.1") } result = result.flatten assert(result.find { |obj| obj.name == file_fact }, "Could not find correct file") assert(!result.find { |obj| obj.name == file_cert }, "Found incorrect file") # Use the hostname from the cert Puppet[:node_name] = 'cert' assert_nothing_raised { result = master.getconfig({"hostname" => factname}, "yaml", certname, "127.0.0.1") } result = result.flatten assert(!result.find { |obj| obj.name == file_fact }, "Could not find correct file") assert(result.find { |obj| obj.name == file_cert }, "Found incorrect file") end + # Make sure we're correctly doing clientname manipulations. + # Testing to make sure we always get a hostname and IP address. + def test_clientname + master = nil + file = tempfile() + + File.open(file, "w") { |f| + f.puts %{ + node yay { file { "/something": ensure => file, mode => 755 } } +} + } + # create our master + assert_nothing_raised() { + # this is the default server setup + master = Puppet::Server::Master.new( + :Manifest => file, + :UseNodes => true, + :Local => true + ) + } + + Puppet[:node_name] = "cert" + # First act like we're local + fakename = nil + fakeip = nil + + name = ip = nil + facts = Facter.to_hash + assert_nothing_raised do + name, ip = master.clientname(fakename, fakeip, facts) + end + + assert(facts["hostname"], "Removed hostname fact") + assert(facts["ipaddress"], "Removed ipaddress fact") + + assert_equal(facts["hostname"], name) + assert_equal(facts["ipaddress"], ip) + + # Now set them to something real, and make sure we get them back + fakename = "yayness" + fakeip = "192.168.0.1" + facts = Facter.to_hash + assert_nothing_raised do + name, ip = master.clientname(fakename, fakeip, facts) + end + + assert(facts["hostname"], "Removed hostname fact") + assert(facts["ipaddress"], "Removed ipaddress fact") + + assert_equal(fakename, name) + assert_equal(fakeip, ip) + end end # $Id$