Significantly reworking both external_nodes and ldapnodes support (see changelog).

git-svn-id: https://reductivelabs.com/svn/puppet/trunk@2587 980ebf18-57e1-0310-9a29-db15c13687c0
This commit is contained in:
luke 2007-06-15 02:40:20 +00:00
Родитель fc9a798282
Коммит f84ac7d270
4 изменённых файлов: 538 добавлений и 463 удалений

Просмотреть файл

@ -1,4 +1,24 @@
Reworked the idatbase schema used to store configurations with the
COMPATIBILITY ALERT:
Significantly reworked external node support, in a way that's NOT
backward-compatible:
Only ONE node source can be used -- you can use LDAP, code, or
an external node program, but not more than one.
LDAP node support has two changes: First, the "ldapattrs" attribute is
now used for setting the attributes to retrieve from the server (in
addition to required attriutes), and second, all retrieved attributes
are set as variables in the top scope. This means you can set attributes
on your LDAP nodes and they will automatically appear as variables
in your configurations.
External node support has been completely rewritten. These programs must
now generate a YAML dump of a hash, with "classes" and "parameters" keys.
The classes should be an array, and the parameters should be a hash. The
external node program has no support for parent nodes -- the script must
handle that on its own.
Reworked the database schema used to store configurations with the
storeconfigs option.
Replaced the obsolete RRD ruby library with the maintained

Просмотреть файл

@ -539,14 +539,14 @@ module Puppet
should be case-sensitive. Case insensitivity is
handled by downcasing all values before comparison."],
:external_nodes => ["none",
"An external command that can produce node information. The
first line of output must be either the parent node or blank,
and if there is a second line of output it should be a list of
whitespace-separated classes to include on that node. This command
makes it straightforward to store your node mapping information
in other data sources like databases.
For unknown nodes, the commands should exit with an exit code of 1."])
"An external command that can produce node information. The output
must be a YAML dump of a hash, and that hash must have one or both of
``classes`` and ``parameters``, where ``classes`` is an array and
``parameters`` is a hash. For unknown nodes, the commands should
exit with a non-zero exit code.
This command makes it straightforward to store your node mapping
information in other data sources like databases."])
setdefaults(:ldap,
:ldapnodes => [false,
@ -565,9 +565,14 @@ module Puppet
"The LDAP port. Only used if ``ldapnodes`` is enabled."],
:ldapstring => ["(&(objectclass=puppetClient)(cn=%s))",
"The search string used to find an LDAP node."],
:ldapattrs => ["puppetclass",
:ldapclassattrs => ["puppetclass",
"The LDAP attributes to use to define Puppet classes. Values
should be comma-separated."],
:ldapattrs => ["all",
"The LDAP attributes to include when querying LDAP for nodes. All
returned attributes are set as variables in the top-level scope.
Multiple values should be comma-separated. The value 'all' returns
all attributes."],
:ldapparentattr => ["parentnode",
"The attribute to use to define the parent node."],
:ldapuser => ["",

Просмотреть файл

@ -5,10 +5,54 @@
require 'puppet'
require 'timeout'
require 'puppet/rails'
require 'puppet/util/methodhelper'
require 'puppet/parser/parser'
require 'puppet/parser/scope'
class Puppet::Parser::Interpreter
class NodeDef
include Puppet::Util::MethodHelper
attr_accessor :name, :classes, :parameters, :source
def evaluate(options)
begin
parameters.each do |param, value|
# Don't try to override facts with these parameters
options[:scope].setvar(param, value) unless options[:scope].lookupvar(param, false) != :undefined
end
# Also, set the 'nodename', since it might not be obvious how the node was looked up
options[:scope].setvar("nodename", @name) unless options[:scope].lookupvar(@nodename, false) != :undefined
rescue => detail
raise Puppet::ParseError, "Could not set parameters for %s: %s" % [name, detail]
end
# Then evaluate the classes.
begin
options[:scope].function_include(classes)
rescue => detail
raise Puppet::ParseError, "Could not evaluate classes for %s: %s" % [name, detail]
end
end
def initialize(args)
set_options(args)
raise Puppet::DevError, "NodeDefs require names" unless self.name
if self.classes.is_a?(String)
@classes = [@classes]
else
@classes ||= []
end
@parameters ||= {}
end
def safeevaluate(args)
evaluate(args)
end
end
include Puppet::Util
attr_accessor :usenodes
@ -156,7 +200,8 @@ class Puppet::Parser::Interpreter
klass.safeevaluate :scope => scope, :nosubscope => true
end
# Next evaluate the node
# Next evaluate the node. We pass the facts so they can be used
# when building the list of names for which to search.
evalnode(client, scope, facts)
# If we were passed any classes, evaluate those.
@ -258,51 +303,6 @@ class Puppet::Parser::Interpreter
return nil
end
# Create a new node, just from a list of names, classes, and an optional parent.
def gennode(name, hash, source = nil)
facts = hash[:facts]
classes = hash[:classes]
parent = hash[:parentnode]
arghash = {
:name => name,
:interp => self,
:classname => name
}
if (classes.is_a?(Array) and classes.empty?) or classes.nil?
arghash[:code] = AST::ASTArray.new(:children => [])
else
classes = [classes] unless classes.is_a?(Array)
classcode = @parser.ast(AST::ASTArray, :children => classes.collect do |klass|
@parser.ast(AST::FlatString, :value => klass)
end)
# Now generate a function call.
code = @parser.ast(AST::Function,
:name => "include",
:arguments => classcode,
:ftype => :statement
)
arghash[:code] = code
end
if parent
arghash[:parentclass] = parent
end
# Create the node
if source
arghash[:file] = source
else
arghash[:file] = nil
end
arghash[:line] = nil
node = @parser.ast(AST::Node, arghash)
return node
end
# create our interpreter
def initialize(hash)
if @code = hash[:Code]
@ -317,33 +317,15 @@ class Puppet::Parser::Interpreter
@usenodes = true
end
# By default, we only search for parsed nodes.
@nodesources = []
if Puppet[:ldapnodes]
# Nodes in the file override nodes in ldap.
@nodesources << :ldap
end
if hash[:NodeSources]
unless hash[:NodeSources].is_a?(Array)
hash[:NodeSources] = [hash[:NodeSources]]
end
hash[:NodeSources].each do |src|
if respond_to? "nodesearch_#{src.to_s}"
@nodesources << src.to_s.intern
else
Puppet.warning "Node source '#{src}' not supported"
end
end
end
unless @nodesources.include?(:code)
@nodesources << :code
end
unless Puppet[:external_nodes] == "none"
@nodesources << :external
@nodesource = :ldap
elsif Puppet[:external_nodes] != "none"
@nodesource = :external
else
# By default, we only search for parsed nodes.
@nodesource = :code
end
@setup = false
@ -389,26 +371,31 @@ class Puppet::Parser::Interpreter
@definetable = {}
end
# Find the ldap node and extra the info, returning just
# the critical data.
# Find the ldap node, return the class list and parent node specially,
# and everything else in a parameter hash.
def ldapsearch(node)
unless defined? @ldap and @ldap
setup_ldap()
unless @ldap
Puppet.info "Skipping ldap source; no ldap connection"
return nil, []
return nil
end
end
filter = Puppet[:ldapstring]
attrs = Puppet[:ldapattrs].split("\s*,\s*")
sattrs = attrs.dup
classattrs = Puppet[:ldapclassattrs].split("\s*,\s*")
if Puppet[:ldapattrs] == "all"
# A nil value here causes all attributes to be returned.
search_attrs = nil
else
search_attrs = classattrs + Puppet[:ldapattrs].split("\s*,\s*")
end
pattr = nil
if pattr = Puppet[:ldapparentattr]
if pattr == ""
pattr = nil
else
sattrs << pattr
search_attrs << pattr unless search_attrs.nil?
end
end
@ -418,12 +405,14 @@ class Puppet::Parser::Interpreter
parent = nil
classes = []
parameters = nil
found = false
count = 0
begin
# We're always doing a sub here; oh well.
@ldap.search(Puppet[:ldapbase], 2, filter, sattrs) do |entry|
@ldap.search(Puppet[:ldapbase], 2, filter, search_attrs) do |entry|
found = true
if pattr
if values = entry.vals(pattr)
@ -438,11 +427,20 @@ class Puppet::Parser::Interpreter
end
end
attrs.each { |attr|
classattrs.each { |attr|
if values = entry.vals(attr)
values.each do |v| classes << v end
end
}
parameters = entry.to_hash.inject({}) do |hash, ary|
if ary[1].length == 1
hash[ary[0]] = ary[1].shift
else
hash[ary[0]] = ary[1]
end
hash
end
end
rescue => detail
if count == 0
@ -461,7 +459,11 @@ class Puppet::Parser::Interpreter
classes = nil
end
return parent, classes
if parent or classes or parameters
return parent, classes, parameters
else
return nil
end
end
# Split an fq name into a namespace and name
@ -590,21 +592,20 @@ class Puppet::Parser::Interpreter
# Search for our node in the various locations.
def nodesearch(*nodes)
nodes = nodes.collect { |n| n.to_s.downcase }
# At this point, stop at the first source that defines
# the node
@nodesources.each do |source|
method = "nodesearch_%s" % source
if self.respond_to? method
# Do an inverse sort on the length, so the longest match always
# wins
nodes.sort { |a,b| b.length <=> a.length }.each do |node|
node = node.to_s if node.is_a?(Symbol)
if obj = self.send(method, node)
nsource = obj.file || source
Puppet.info "Found %s in %s" % [node, nsource]
return obj
end
method = "nodesearch_%s" % @nodesource
# Do an inverse sort on the length, so the longest match always
# wins
nodes.sort { |a,b| b.length <=> a.length }.each do |node|
node = node.to_s if node.is_a?(Symbol)
if obj = self.send(method, node)
if obj.is_a?(AST::Node)
nsource = obj.file
else
nsource = obj.source
end
Puppet.info "Found %s in %s" % [node, nsource]
return obj
end
end
@ -640,41 +641,51 @@ class Puppet::Parser::Interpreter
return nil
end
if output =~ /\A\s+\Z/ # all whitespace
if output =~ /\A\s*\Z/ # all whitespace
Puppet.debug "Empty response for %s from external node source" % name
return nil
end
lines = output.split("\n")
args = {}
parent = lines[0].gsub(/\s+/, '')
args[:parentnode] = parent unless parent == ""
if lines[1]
classes = lines[1].sub(/^\s+/,'').sub(/\s+$/,'').split(/\s+/)
args[:classes] = classes unless classes.empty?
begin
result = YAML.load(output).inject({}) { |hash, data| hash[symbolize(data[0])] = data[1]; hash }
rescue => detail
raise Puppet::Error, "Could not load external node results for %s: %s" % [name, detail]
end
if args.empty?
Puppet.warning "Somehow got a node with no information"
return nil
node_args = {:source => "external node source", :name => name}
set = false
[:parameters, :classes].each do |param|
if value = result[param]
node_args[param] = value
set = true
end
end
if set
return NodeDef.new(node_args)
else
return gennode(name, args, Puppet[:external_nodes])
return nil
end
end
# Look for our node in ldap.
def nodesearch_ldap(node)
parent, classes = ldapsearch(node)
if parent or classes
args = {}
args[:classes] = classes if classes
args[:parentnode] = parent if parent
return gennode(node, args, "ldap")
else
unless ary = ldapsearch(node)
return nil
end
parent, classes, parameters = ary
while parent
parent, tmpclasses, tmpparams = ldapsearch(parent)
classes += tmpclasses if tmpclasses
tmpparams.each do |param, value|
# Specifically test for whether it's set, so false values are handled
# correctly.
parameters[param] = value unless parameters.include?(param)
end
end
return NodeDef.new(:name => node, :classes => classes, :source => "ldap", :parameters => parameters)
end
def parsedate
@ -687,15 +698,13 @@ class Puppet::Parser::Interpreter
# We have to leave this for after initialization because there
# seems to be a problem keeping ldap open after a fork.
unless @setup
@nodesources.each { |source|
method = "setup_%s" % source.to_s
if respond_to? method
exceptwrap :type => Puppet::Error,
:message => "Could not set up node source %s" % source do
self.send(method)
end
method = "setup_%s" % @nodesource.to_s
if respond_to? method
exceptwrap :type => Puppet::Error,
:message => "Could not set up node source %s" % @nodesource do
self.send(method)
end
}
end
end
parsefiles()

Просмотреть файл

@ -16,13 +16,14 @@ require 'puppettest/servertest'
require 'puppettest/railstesting'
require 'timeout'
class TestInterpreter < Test::Unit::TestCase
class TestInterpreter < PuppetTest::TestCase
include PuppetTest
include PuppetTest::ServerTest
include PuppetTest::ParserTesting
include PuppetTest::ResourceTesting
include PuppetTest::RailsTesting
AST = Puppet::Parser::AST
NodeDef = Puppet::Parser::Interpreter::NodeDef
# create a simple manifest that uses nodes to create a file
def mknodemanifest(node, file)
@ -76,199 +77,6 @@ class TestInterpreter < Test::Unit::TestCase
assert(config != newconfig, "Configs are somehow the same")
end
if Puppet.features.rails?
def test_hoststorage
assert_nothing_raised {
Puppet[:storeconfigs] = true
}
file = tempfile()
File.open(file, "w") { |f|
f.puts "file { \"/etc\": owner => root }"
}
interp = nil
assert_nothing_raised {
interp = Puppet::Parser::Interpreter.new(
:Manifest => file,
:UseNodes => false,
:ForkSave => false
)
}
facts = {}
Facter.each { |fact, val| facts[fact] = val }
objects = nil
assert_nothing_raised {
objects = interp.run(facts["hostname"], facts)
}
obj = Puppet::Rails::Host.find_by_name(facts["hostname"])
assert(obj, "Could not find host object")
end
else
$stderr.puts "No ActiveRecord -- skipping collection tests"
end
if Facter["domain"].value == "madstop.com"
# Only test ldap stuff on luke's network, since that's the only place we
# have data for.
if Puppet.features.ldap?
def ldapconnect
@ldap = LDAP::Conn.new("ldap", 389)
@ldap.set_option( LDAP::LDAP_OPT_PROTOCOL_VERSION, 3 )
@ldap.simple_bind("", "")
return @ldap
end
def ldaphost(node)
parent = nil
classes = nil
@ldap.search( "ou=hosts, dc=madstop, dc=com", 2,
"(&(objectclass=puppetclient)(cn=%s))" % node
) do |entry|
parent = entry.vals("parentnode").shift
classes = entry.vals("puppetclass") || []
end
return parent, classes
end
def test_ldapsearch
Puppet[:ldapbase] = "ou=hosts, dc=madstop, dc=com"
Puppet[:ldapnodes] = true
ldapconnect()
interp = mkinterp :NodeSources => [:ldap, :code]
# Make sure we get nil and nil back when we search for something missing
parent, classes = nil
assert_nothing_raised do
parent, classes = interp.ldapsearch("nosuchhost")
end
assert_nil(parent, "Got a parent for a non-existent host")
assert_nil(classes, "Got classes for a non-existent host")
# Make sure we can find 'culain' in ldap
assert_nothing_raised do
parent, classes = interp.ldapsearch("culain")
end
realparent, realclasses = ldaphost("culain")
assert_equal(realparent, parent)
assert_equal(realclasses, classes)
end
def test_ldapnodes
Puppet[:ldapbase] = "ou=hosts, dc=madstop, dc=com"
Puppet[:ldapnodes] = true
ldapconnect()
interp = mkinterp :NodeSources => [:ldap, :code]
# culain uses basenode, so create that
basenode = interp.newnode([:basenode])[0]
# Make sure we get nothing for nonexistent hosts
none = nil
assert_nothing_raised do
none = interp.nodesearch_ldap("nosuchhost")
end
assert_nil(none, "Got a node for a non-existent host")
# Make sure we can find 'culain' in ldap
culain = nil
assert_nothing_raised do
culain = interp.nodesearch_ldap("culain")
end
assert(culain, "Did not find culain in ldap")
assert_nothing_raised do
assert_equal(basenode.classname.to_s, culain.parentobj.classname.to_s,
"Did not get parent class")
end
end
if Puppet::Util::SUIDManager.uid == 0 and Facter["hostname"].value == "culain"
def test_ldapreconnect
Puppet[:ldapbase] = "ou=hosts, dc=madstop, dc=com"
Puppet[:ldapnodes] = true
interp = nil
assert_nothing_raised {
interp = Puppet::Parser::Interpreter.new(
:Manifest => mktestmanifest()
)
}
hostname = "culain.madstop.com"
# look for our host
assert_nothing_raised {
parent, classes = interp.nodesearch_ldap(hostname)
}
# Now restart ldap
system("/etc/init.d/slapd restart 2>/dev/null >/dev/null")
sleep(1)
# and look again
assert_nothing_raised {
parent, classes = interp.nodesearch_ldap(hostname)
}
# Now stop ldap
system("/etc/init.d/slapd stop 2>/dev/null >/dev/null")
cleanup do
system("/etc/init.d/slapd start 2>/dev/null >/dev/null")
end
# And make sure we actually fail here
assert_raise(Puppet::Error) {
parent, classes = interp.nodesearch_ldap(hostname)
}
end
else
$stderr.puts "Run as root for ldap reconnect tests"
end
end
else
$stderr.puts "Not in madstop.com; skipping ldap tests"
end
# Test that node info and default node info in different sources isn't
# bad.
def test_multiple_nodesources
# Create another node source
Puppet::Parser::Interpreter.send(:define_method, :nodesearch_multi) do |*names|
if names[0] == "default"
gennode("default", {:facts => {}})
else
nil
end
end
interp = mkinterp :NodeSources => [:multi, :code]
interp.newnode(["node"])
obj = nil
assert_nothing_raised do
obj = interp.nodesearch("node")
end
assert(obj, "Did not find node")
assert_equal("node", obj.classname)
end
# Make sure searchnode behaves as we expect.
def test_nodesearch
# We use two sources here to catch a weird bug where the default
@ -410,54 +218,6 @@ class TestInterpreter < Test::Unit::TestCase
end
end
# Make sure we're correctly generating a node definition.
def test_gennode
interp = mkinterp
interp.newnode "base"
interp.newclass "yaytest"
# Go through the different iterations:
[
[nil, "yaytest"],
[nil, ["yaytest"]],
[nil, nil],
[nil, []],
["base", nil],
["base", []],
["base", "yaytest"],
["base", ["yaytest"]]
].each do |parent, classes|
node = nil
assert_nothing_raised {
node = interp.gennode("nodeA", :classes => classes,
:parentnode => parent)
}
assert_instance_of(Puppet::Parser::AST::Node, node)
assert_equal("nodeA", node.name)
scope = mkscope :interp => interp
assert_nothing_raised do
node.evaluate :scope => scope
end
# If there's a parent, make sure it got evaluated
if parent
assert(scope.classlist.include?("base"),
"Did not evaluate parent node")
end
# If there are classes make sure they got evaluated
if classes == ["yaytest"] or classes == "yaytest"
assert(scope.classlist.include?("yaytest"),
"Did not evaluate class")
end
end
end
def test_fqfind
interp = mkinterp
@ -955,8 +715,349 @@ class TestInterpreter < Test::Unit::TestCase
assert(found.include?("/tmp/klass1"), "Did not evaluate klass1")
assert(found.include?("/tmp/klass2"), "Did not evaluate klass2")
end
def mk_node_mapper
# First, make sure our nodesearch command works as we expect
# Make a nodemapper
mapper = tempfile()
ruby = %x{which ruby}.chomp
File.open(mapper, "w") { |f|
f.puts "#!#{ruby}
require 'yaml'
name = ARGV[0].chomp
result = {}
if name =~ /a/
result[:parameters] = {'one' => ARGV[0] + '1', 'two' => ARGV[0] + '2'}
end
if name =~ /p/
result['classes'] = [1,2,3].collect { |n| ARGV[0] + n.to_s }
end
puts YAML.dump(result)
"
}
File.chmod(0755, mapper)
mapper
end
def test_nodesearch_external
interp = mkinterp
mapper = mk_node_mapper
# Make sure it gives the right response
assert_equal({'classes' => %w{apple1 apple2 apple3}, :parameters => {"one" => "apple1", "two" => "apple2"}},
YAML.load(%x{#{mapper} apple}))
# First make sure we get nil back by default
assert_nothing_raised {
assert_nil(interp.nodesearch_external("apple"),
"Interp#nodesearch_external defaulted to a non-nil response")
}
assert_nothing_raised { Puppet[:external_nodes] = mapper }
node = nil
# Both 'a' and 'p', so we get classes and parameters
assert_nothing_raised { node = interp.nodesearch_external("apple") }
assert_equal("apple", node.name, "node name was not set correctly for apple")
assert_equal(%w{apple1 apple2 apple3}, node.classes, "node classes were not set correctly for apple")
assert_equal( {"one" => "apple1", "two" => "apple2"}, node.parameters, "node parameters were not set correctly for apple")
# A 'p' but no 'a', so we only get classes
assert_nothing_raised { node = interp.nodesearch_external("plum") }
assert_equal("plum", node.name, "node name was not set correctly for plum")
assert_equal(%w{plum1 plum2 plum3}, node.classes, "node classes were not set correctly for plum")
assert_equal({}, node.parameters, "node parameters were not set correctly for plum")
# An 'a' but no 'p', so we only get parameters.
assert_nothing_raised { node = interp.nodesearch_external("guava")} # no p's, thus no classes
assert_equal("guava", node.name, "node name was not set correctly for guava")
assert_equal([], node.classes, "node classes were not set correctly for guava")
assert_equal({"one" => "guava1", "two" => "guava2"}, node.parameters, "node parameters were not set correctly for guava")
assert_nothing_raised { node = interp.nodesearch_external("honeydew")} # neither, thus nil
assert_nil(node)
end
# A wrapper test, to make sure we're correctly calling the external search method.
def test_nodesearch_external_functional
mapper = mk_node_mapper
Puppet[:external_nodes] = mapper
interp = mkinterp
node = nil
assert_nothing_raised do
node = interp.nodesearch("apple")
end
assert_instance_of(NodeDef, node, "did not create node")
end
def test_check_resource_collections
interp = mkinterp
scope = mkscope :interp => interp
coll = Puppet::Parser::Collector.new(scope, "file", nil, nil, :virtual)
coll.resources = ["File[/tmp/virtual1]", "File[/tmp/virtual2]"]
scope.newcollection(coll)
assert_raise(Puppet::ParseError, "Did not fail on remaining resource colls") do
interp.check_resource_collections(scope)
end
end
def test_nodedef
interp = mkinterp
interp.newclass("base")
interp.newclass("sub", :parent => "base")
interp.newclass("other")
node = nil
assert_nothing_raised("Could not create a node definition") do
node = NodeDef.new :name => "yay", :classes => "sub", :parameters => {"one" => "two", "three" => "four"}
end
scope = mkscope :interp => interp
assert_nothing_raised("Could not evaluate the node definition") do
node.evaluate(:scope => scope)
end
assert_equal("two", scope.lookupvar("one"), "NodeDef did not set variable")
assert_equal("four", scope.lookupvar("three"), "NodeDef did not set variable")
assert(scope.classlist.include?("sub"), "NodeDef did not evaluate class")
assert(scope.classlist.include?("base"), "NodeDef did not evaluate base class")
# Now try a node def with multiple classes
assert_nothing_raised("Could not create a node definition") do
node = NodeDef.new :name => "yay", :classes => %w{sub other base}, :parameters => {"one" => "two", "three" => "four"}
end
scope = mkscope :interp => interp
assert_nothing_raised("Could not evaluate the node definition") do
node.evaluate(:scope => scope)
end
assert_equal("two", scope.lookupvar("one"), "NodeDef did not set variable")
assert_equal("four", scope.lookupvar("three"), "NodeDef did not set variable")
assert(scope.classlist.include?("sub"), "NodeDef did not evaluate class")
assert(scope.classlist.include?("other"), "NodeDef did not evaluate other class")
# And a node def with no params
assert_nothing_raised("Could not create a node definition with no params") do
node = NodeDef.new :name => "yay", :classes => %w{sub other base}
end
scope = mkscope :interp => interp
assert_nothing_raised("Could not evaluate the node definition") do
node.evaluate(:scope => scope)
end
assert(scope.classlist.include?("sub"), "NodeDef did not evaluate class")
assert(scope.classlist.include?("other"), "NodeDef did not evaluate other class")
end
def test_ldapnodes
interp = mkinterp
nodetable = {}
# Override the ldapsearch definition, so we don't have to actually set it up.
interp.meta_def(:ldapsearch) do |name|
nodetable[name]
end
# Make sure we get nothing for nonexistent hosts
node = nil
assert_nothing_raised do
node = interp.nodesearch_ldap("nosuchhost")
end
assert_nil(node, "Got a node for a non-existent host")
# Now add a base node with some classes and parameters
nodetable["base"] = [nil, %w{one two}, {"base" => "true"}]
assert_nothing_raised do
node = interp.nodesearch_ldap("base")
end
assert_instance_of(NodeDef, node, "Did not get node from ldap nodesearch")
assert_equal("base", node.name, "node name was not set")
assert_equal(%w{one two}, node.classes, "node classes were not set")
assert_equal({"base" => "true"}, node.parameters, "node parameters were not set")
# Now use a different with this as the base
nodetable["middle"] = ["base", %w{three}, {"center" => "boo"}]
assert_nothing_raised do
node = interp.nodesearch_ldap("middle")
end
assert_instance_of(NodeDef, node, "Did not get node from ldap nodesearch")
assert_equal("middle", node.name, "node name was not set")
assert_equal(%w{one two three}.sort, node.classes.sort, "node classes were not set correctly with a parent node")
assert_equal({"base" => "true", "center" => "boo"}, node.parameters, "node parameters were not set correctly with a parent node")
# And one further, to make sure we fully recurse
nodetable["top"] = ["middle", %w{four five}, {"master" => "far"}]
assert_nothing_raised do
node = interp.nodesearch_ldap("top")
end
assert_instance_of(NodeDef, node, "Did not get node from ldap nodesearch")
assert_equal("top", node.name, "node name was not set")
assert_equal(%w{one two three four five}.sort, node.classes.sort, "node classes were not set correctly with the top node")
assert_equal({"base" => "true", "center" => "boo", "master" => "far"}, node.parameters, "node parameters were not set correctly with the top node")
end
end
class LdapNodeTest < PuppetTest::TestCase
include PuppetTest
include PuppetTest::ServerTest
include PuppetTest::ParserTesting
include PuppetTest::ResourceTesting
include PuppetTest::RailsTesting
AST = Puppet::Parser::AST
NodeDef = Puppet::Parser::Interpreter::NodeDef
confine "LDAP is not available" => Puppet.features.ldap?
confine "No LDAP test data for networks other than Luke's" => Facter.value(:domain) == "madstop.com"
def ldapconnect
@ldap = LDAP::Conn.new("ldap", 389)
@ldap.set_option( LDAP::LDAP_OPT_PROTOCOL_VERSION, 3 )
@ldap.simple_bind("", "")
return @ldap
end
def ldaphost(name)
node = NodeDef.new(:name => name)
parent = nil
found = false
@ldap.search( "ou=hosts, dc=madstop, dc=com", 2,
"(&(objectclass=puppetclient)(cn=%s))" % name
) do |entry|
node.classes = entry.vals("puppetclass") || []
node.parameters = entry.to_hash.inject({}) do |hash, ary|
if ary[1].length == 1
hash[ary[0]] = ary[1].shift
else
hash[ary[0]] = ary[1]
end
hash
end
parent = node.parameters["parentnode"]
found = true
end
raise "Could not find node %s" % name unless found
return node, parent
end
def test_ldapsearch
Puppet[:ldapbase] = "ou=hosts, dc=madstop, dc=com"
Puppet[:ldapnodes] = true
ldapconnect()
interp = mkinterp :NodeSources => [:ldap, :code]
# Make sure we get nil and nil back when we search for something missing
parent, classes, parameters = nil
assert_nothing_raised do
parent, classes, parameters = interp.ldapsearch("nosuchhost")
end
assert_nil(parent, "Got a parent for a non-existent host")
assert_nil(classes, "Got classes for a non-existent host")
# Make sure we can find 'culain' in ldap
assert_nothing_raised do
parent, classes, parameters = interp.ldapsearch("culain")
end
node, realparent = ldaphost("culain")
assert_equal(realparent, parent, "did not get correct parent node from ldap")
assert_equal(node.classes, classes, "did not get correct ldap classes from ldap")
assert_equal(node.parameters, parameters, "did not get correct ldap parameters from ldap")
# Now compare when we specify the attributes to get.
Puppet[:ldapattrs] = "cn"
assert_nothing_raised do
parent, classes, parameters = interp.ldapsearch("culain")
end
assert_equal(realparent, parent, "did not get correct parent node from ldap")
assert_equal(node.classes, classes, "did not get correct ldap classes from ldap")
list = %w{cn puppetclass parentnode dn}
should = node.parameters.inject({}) { |h, a| h[a[0]] = a[1] if list.include?(a[0]); h }
assert_equal(should, parameters, "did not get correct ldap parameters from ldap")
end
end
class LdapReconnectTests < PuppetTest::TestCase
include PuppetTest
include PuppetTest::ServerTest
include PuppetTest::ParserTesting
include PuppetTest::ResourceTesting
include PuppetTest::RailsTesting
AST = Puppet::Parser::AST
NodeDef = Puppet::Parser::Interpreter::NodeDef
confine "Not running on culain as root" => (Puppet::Util::SUIDManager.uid == 0 and Facter.value("hostname") == "culain")
def test_ldapreconnect
Puppet[:ldapbase] = "ou=hosts, dc=madstop, dc=com"
Puppet[:ldapnodes] = true
interp = nil
assert_nothing_raised {
interp = Puppet::Parser::Interpreter.new(
:Manifest => mktestmanifest()
)
}
hostname = "culain.madstop.com"
# look for our host
assert_nothing_raised {
parent, classes = interp.nodesearch_ldap(hostname)
}
# Now restart ldap
system("/etc/init.d/slapd restart 2>/dev/null >/dev/null")
sleep(1)
# and look again
assert_nothing_raised {
parent, classes = interp.nodesearch_ldap(hostname)
}
# Now stop ldap
system("/etc/init.d/slapd stop 2>/dev/null >/dev/null")
cleanup do
system("/etc/init.d/slapd start 2>/dev/null >/dev/null")
end
# And make sure we actually fail here
assert_raise(Puppet::Error) {
parent, classes = interp.nodesearch_ldap(hostname)
}
end
end
class InterpreterRailsTests < PuppetTest::TestCase
include PuppetTest
include PuppetTest::ServerTest
include PuppetTest::ParserTesting
include PuppetTest::ResourceTesting
include PuppetTest::RailsTesting
AST = Puppet::Parser::AST
NodeDef = Puppet::Parser::Interpreter::NodeDef
confine "No rails support" => Puppet.features.rails?
if Puppet.features.rails?
# We need to make sure finished objects are stored in the db.
def test_finish_before_store
railsinit
@ -993,96 +1094,36 @@ class TestInterpreter < Test::Unit::TestCase
pvalue = param.param_values.find_by_value("root")
assert_equal("root", pvalue[:value])
end
end
def mk_node_mapper
# First, make sure our nodesearch command works as we expect
# Make a nodemapper
mapper = tempfile()
ruby = %x{which ruby}.chomp
File.open(mapper, "w") { |f|
f.puts "#!#{ruby}
name = ARGV[0]
if name =~ /a/
puts ARGV[0].gsub('a', 'b')
else
puts ''
end
if name =~ /p/
puts [1,2,3].collect { |n| ARGV[0] + n.to_s }.join(' ')
else
puts ''
end
"
}
File.chmod(0755, mapper)
mapper
end
def test_nodesearch_external
interp = mkinterp
# Make a fake gennode method
class << interp
def gennode(name, args, source)
args[:name] = name
return args
end
end
mapper = mk_node_mapper
# Make sure it gives the right response
assert_equal("bpple\napple1 apple2 apple3\n",
%x{#{mapper} apple})
# First make sure we get nil back by default
def test_hoststorage
assert_nothing_raised {
assert_nil(interp.nodesearch_external("apple"),
"Interp#nodesearch_external defaulted to a non-nil response")
Puppet[:storeconfigs] = true
}
assert_nothing_raised { Puppet[:external_nodes] = mapper }
node = nil
assert_nothing_raised { node = interp.nodesearch_external("apple") }
assert_equal({:name => "apple", :classes => %w{apple1 apple2 apple3}, :parentnode => "bpple"},
node)
assert_nothing_raised { node = interp.nodesearch_external("plum")} # no a's, thus no parent
assert_equal({:name => "plum", :classes => %w{plum1 plum2 plum3}},
node)
assert_nothing_raised { node = interp.nodesearch_external("guava")} # no p's, thus no classes
assert_equal({:name => "guava", :parentnode => "gubvb"},
node)
assert_nothing_raised { node = interp.nodesearch_external("honeydew")} # neither, thus nil
assert_nil(node)
end
def test_nodesearch_external_functional
mapper = mk_node_mapper
Puppet[:external_nodes] = mapper
interp = mkinterp
node = nil
assert_nothing_raised do
node = interp.nodesearch("apple")
end
assert_instance_of(Puppet::Parser::AST::Node, node, "did not create node")
end
def test_check_resource_collections
interp = mkinterp
scope = mkscope :interp => interp
coll = Puppet::Parser::Collector.new(scope, "file", nil, nil, :virtual)
coll.resources = ["File[/tmp/virtual1]", "File[/tmp/virtual2]"]
scope.newcollection(coll)
file = tempfile()
File.open(file, "w") { |f|
f.puts "file { \"/etc\": owner => root }"
}
assert_raise(Puppet::ParseError, "Did not fail on remaining resource colls") do
interp.check_resource_collections(scope)
end
interp = nil
assert_nothing_raised {
interp = Puppet::Parser::Interpreter.new(
:Manifest => file,
:UseNodes => false,
:ForkSave => false
)
}
facts = {}
Facter.each { |fact, val| facts[fact] = val }
objects = nil
assert_nothing_raised {
objects = interp.run(facts["hostname"], facts)
}
obj = Puppet::Rails::Host.find_by_name(facts["hostname"])
assert(obj, "Could not find host object")
end
end