1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
|
#!/usr/bin/env ruby
$:.unshift("../lib").unshift("../../lib") if __FILE__ =~ /\.rb$/
require 'mocha'
require 'puppettest'
require 'puppettest/resourcetesting'
require 'puppettest/parsertesting'
require 'puppettest/servertest'
require 'puppet/network/handler/node'
module NodeTesting
include PuppetTest
Node = Puppet::Network::Handler::Node
SimpleNode = Puppet::Node
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.last.chomp
result = {}
if name =~ /a/
result[:parameters] = {'one' => ARGV.last + '1', 'two' => ARGV.last + '2'}
end
if name =~ /p/
result['classes'] = [1,2,3].collect { |n| ARGV.last + n.to_s }
end
puts YAML.dump(result)
"
}
File.chmod(0755, mapper)
mapper
end
def mk_searcher(name)
searcher = Object.new
searcher.extend(Node.node_source(name))
searcher.meta_def(:newnode) do |name, *args|
SimpleNode.new(name, *args)
end
searcher
end
def mk_node_source
@node_info = {}
@node_source = Node.newnode_source(:testing, :fact_merge => true) do
def nodesearch(key)
if info = @node_info[key]
SimpleNode.new(info)
else
nil
end
end
end
Puppet[:node_source] = "testing"
cleanup { Node.rm_node_source(:testing) }
end
end
class TestNodeHandler < Test::Unit::TestCase
include NodeTesting
def setup
super
mk_node_source
end
# Make sure that the handler includes the appropriate
# node source.
def test_initialize
# First try it when passing in the node source
handler = nil
assert_nothing_raised("Could not specify a node source") do
handler = Node.new(:Source => :testing)
end
assert(handler.metaclass.included_modules.include?(@node_source), "Handler did not include node source")
# Now use the Puppet[:node_source]
Puppet[:node_source] = "testing"
assert_nothing_raised("Could not specify a node source") do
handler = Node.new()
end
assert(handler.metaclass.included_modules.include?(@node_source), "Handler did not include node source")
# And make sure we throw an exception when an invalid node source is used
assert_raise(ArgumentError, "Accepted an invalid node source") do
handler = Node.new(:Source => "invalid")
end
end
# Make sure we can find and we cache a fact handler.
def test_fact_handler
handler = Node.new
fhandler = nil
assert_nothing_raised("Could not retrieve the fact handler") do
fhandler = handler.send(:fact_handler)
end
assert_instance_of(Puppet::Network::Handler::Facts, fhandler, "Did not get a fact handler back")
# Now call it again, making sure we're caching the value.
fhandler2 = nil
assert_nothing_raised("Could not retrieve the fact handler") do
fhandler2 = handler.send(:fact_handler)
end
assert_instance_of(Puppet::Network::Handler::Facts, fhandler2, "Did not get a fact handler on the second run")
assert_equal(fhandler.object_id, fhandler2.object_id, "Did not cache fact handler")
end
# Make sure we can get node facts from the fact handler.
def test_node_facts
# Check the case where we find the node.
handler = Node.new
fhandler = handler.send(:fact_handler)
fhandler.expects(:get).with("present").returns("a" => "b")
result = nil
assert_nothing_raised("Could not get facts from fact handler") do
result = handler.send(:node_facts, "present")
end
assert_equal({"a" => "b"}, result, "Did not get correct facts back")
# Now try the case where the fact handler knows nothing about our host
fhandler.expects(:get).with('missing').returns(nil)
result = nil
assert_nothing_raised("Could not get facts from fact handler when host is missing") do
result = handler.send(:node_facts, "missing")
end
assert_equal({}, result, "Did not get empty hash when no facts are known")
end
# Test our simple shorthand
def test_newnode
SimpleNode.expects(:new).with("stuff")
handler = Node.new
handler.send(:newnode, "stuff")
end
# Make sure we can build up the correct node names to search for
def test_node_names
handler = Node.new
# Verify that the handler asks for the facts if we don't pass them in
handler.expects(:node_facts).with("testing").returns({})
handler.send(:node_names, "testing")
handler = Node.new
# Test it first with no parameters
assert_equal(%w{testing}, handler.send(:node_names, "testing"), "Node names did not default to an array including just the node name")
# Now test it with a fully qualified name
assert_equal(%w{testing.domain.com testing}, handler.send(:node_names, "testing.domain.com"),
"Fully qualified names did not get turned into multiple names, longest first")
# And try it with a short name + domain fact
assert_equal(%w{testing host.domain.com host}, handler.send(:node_names, "testing", "domain" => "domain.com", "hostname" => "host"),
"The domain fact was not used to build up an fqdn")
# And with an fqdn
assert_equal(%w{testing host.domain.com host}, handler.send(:node_names, "testing", "fqdn" => "host.domain.com"),
"The fqdn was not used")
# And make sure the fqdn beats the domain
assert_equal(%w{testing host.other.com host}, handler.send(:node_names, "testing", "domain" => "domain.com", "fqdn" => "host.other.com"),
"The domain was used in preference to the fqdn")
end
# Make sure we can retrieve a whole node by name.
def test_details_when_we_find_nodes
handler = Node.new
# Make sure we get the facts first
handler.expects(:node_facts).with("host").returns(:facts)
# Find the node names
handler.expects(:node_names).with("host", :facts).returns(%w{a b c})
# Iterate across them
handler.expects(:nodesearch).with("a").returns(nil)
handler.expects(:nodesearch).with("b").returns(nil)
# Create an example node to return
node = SimpleNode.new("host")
# Make sure its source is set
node.expects(:source=).with(handler.source)
# And that the names are retained
node.expects(:names=).with(%w{a b c})
# And make sure we actually get it back
handler.expects(:nodesearch).with("c").returns(node)
handler.expects(:fact_merge?).returns(true)
# Make sure we merge the facts with the node's parameters.
node.expects(:fact_merge).with(:facts)
# Now call the method
result = nil
assert_nothing_raised("could not call 'details'") do
result = handler.details("host")
end
assert_equal(node, result, "Did not get correct node back")
end
# But make sure we pass through to creating default nodes when appropriate.
def test_details_using_default_node
handler = Node.new
# Make sure we get the facts first
handler.expects(:node_facts).with("host").returns(:facts)
# Find the node names
handler.expects(:node_names).with("host", :facts).returns([])
# Create an example node to return
node = SimpleNode.new("host")
# Make sure its source is set
node.expects(:source=).with(handler.source)
# And make sure we actually get it back
handler.expects(:nodesearch).with("default").returns(node)
# This time, have it return false
handler.expects(:fact_merge?).returns(false)
# And because fact_merge was false, we don't merge them.
node.expects(:fact_merge).never
# Now call the method
result = nil
assert_nothing_raised("could not call 'details'") do
result = handler.details("host")
end
assert_equal(node, result, "Did not get correct node back")
end
# Make sure our handler behaves rationally when it comes to getting environment data.
def test_environment
# What happens when we can't find the node
handler = Node.new
handler.expects(:details).with("fake").returns(nil)
result = nil
assert_nothing_raised("Could not call 'Node.environment'") do
result = handler.environment("fake")
end
assert_nil(result, "Got an environment for a node we could not find")
# Now for nodes we can find
handler = Node.new
node = SimpleNode.new("fake")
handler.expects(:details).with("fake").returns(node)
node.expects(:environment).returns("dev")
result = nil
assert_nothing_raised("Could not call 'Node.environment'") do
result = handler.environment("fake")
end
assert_equal("dev", result, "Did not get environment back")
end
# Make sure our handler behaves rationally when it comes to getting parameter data.
def test_parameters
# What happens when we can't find the node
handler = Node.new
handler.expects(:details).with("fake").returns(nil)
result = nil
assert_nothing_raised("Could not call 'Node.parameters'") do
result = handler.parameters("fake")
end
assert_nil(result, "Got parameters for a node we could not find")
# Now for nodes we can find
handler = Node.new
node = SimpleNode.new("fake")
handler.expects(:details).with("fake").returns(node)
node.expects(:parameters).returns({"a" => "b"})
result = nil
assert_nothing_raised("Could not call 'Node.parameters'") do
result = handler.parameters("fake")
end
assert_equal({"a" => "b"}, result, "Did not get parameters back")
end
def test_classes
# What happens when we can't find the node
handler = Node.new
handler.expects(:details).with("fake").returns(nil)
result = nil
assert_nothing_raised("Could not call 'Node.classes'") do
result = handler.classes("fake")
end
assert_nil(result, "Got classes for a node we could not find")
# Now for nodes we can find
handler = Node.new
node = SimpleNode.new("fake")
handler.expects(:details).with("fake").returns(node)
node.expects(:classes).returns(%w{yay foo})
result = nil
assert_nothing_raised("Could not call 'Node.classes'") do
result = handler.classes("fake")
end
assert_equal(%w{yay foo}, result, "Did not get classes back")
end
# We reuse the filetimeout for the node caching timeout.
def test_node_caching
handler = Node.new
node = Object.new
node.metaclass.instance_eval do
attr_accessor :time, :name
end
node.time = Time.now
node.name = "yay"
# Make sure caching works normally
assert_nothing_raised("Could not cache node") do
handler.send(:cache, node)
end
assert_equal(node.object_id, handler.send(:cached?, "yay").object_id, "Did not get node back from the cache")
# And that it's returned if we ask for it, instead of creating a new node.
assert_equal(node.object_id, handler.details("yay").object_id, "Did not use cached node")
# Now set the node's time to be a long time ago
node.time = Time.now - 50000
assert(! handler.send(:cached?, "yay"), "Timed-out node was returned from cache")
end
end
# Test our configuration object.
class TestNodeSources < Test::Unit::TestCase
include NodeTesting
def test_node_sources
mod = nil
assert_nothing_raised("Could not add new search type") do
mod = Node.newnode_source(:testing) do
def nodesearch(name)
end
end
end
assert_equal(mod, Node.node_source(:testing), "Did not get node_source back")
cleanup do
Node.rm_node_source(:testing)
assert(! Node.const_defined?("Testing"), "Did not remove constant")
end
end
def test_external_node_source
source = Node.node_source(:external)
assert(source, "Could not find external node source")
mapper = mk_node_mapper
searcher = mk_searcher(:external)
assert(searcher.fact_merge?, "External node source does not merge facts")
# 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(searcher.nodesearch("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 = searcher.nodesearch("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 = searcher.nodesearch("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 = searcher.nodesearch("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 = searcher.nodesearch("honeydew")} # neither, thus nil
assert_nil(node)
end
# Make sure a nodesearch with arguments works
def test_nodesearch_external_arguments
mapper = mk_node_mapper
Puppet[:external_nodes] = "#{mapper} -s something -p somethingelse"
searcher = mk_searcher(:external)
node = nil
assert_nothing_raised do
node = searcher.nodesearch("apple")
end
assert_instance_of(SimpleNode, node, "did not create 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
searcher = mk_searcher(:external)
Puppet[:external_nodes] = mapper
node = nil
assert_nothing_raised do
node = searcher.nodesearch("apple")
end
assert_instance_of(SimpleNode, node, "did not create node")
end
# This can stay in the main test suite because it doesn't actually use ldapsearch,
# it just overrides the method so it behaves as though it were hitting ldap.
def test_ldap_nodesearch
source = Node.node_source(:ldap)
assert(source, "Could not find ldap node source")
searcher = mk_searcher(:ldap)
assert(searcher.fact_merge?, "LDAP node source does not merge facts")
nodetable = {}
# Override the ldapsearch definition, so we don't have to actually set it up.
searcher.meta_def(:ldapsearch) do |name|
nodetable[name]
end
# Make sure we get nothing for nonexistent hosts
node = nil
assert_nothing_raised do
node = searcher.nodesearch("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 = searcher.nodesearch("base")
end
assert_instance_of(SimpleNode, 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 = searcher.nodesearch("middle")
end
assert_instance_of(SimpleNode, 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 = searcher.nodesearch("top")
end
assert_instance_of(SimpleNode, 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
# Make sure we always get a node back from the 'none' nodesource.
def test_nodesource_none
source = Node.node_source(:none)
assert(source, "Could not find 'none' node source")
searcher = mk_searcher(:none)
assert(searcher.fact_merge?, "'none' node source does not merge facts")
# Run a couple of node names through it
node = nil
%w{192.168.0.1 0:0:0:3:a:f host host.domain.com}.each do |name|
assert_nothing_raised("Could not create an empty node with name '%s'" % name) do
node = searcher.nodesearch(name)
end
assert_instance_of(SimpleNode, node, "Did not get a simple node back for %s" % name)
assert_equal(name, node.name, "Name was not set correctly")
end
end
end
class LdapNodeTest < PuppetTest::TestCase
include NodeTesting
include PuppetTest::ServerTest
include PuppetTest::ParserTesting
include PuppetTest::ResourceTesting
AST = Puppet::Parser::AST
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 = Puppet::Node.new(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
searcher = Object.new
searcher.extend(Node.node_source(:ldap))
ldapconnect()
# 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 = searcher.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 = searcher.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 = searcher.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 NodeTesting
include PuppetTest::ServerTest
include PuppetTest::ParserTesting
include PuppetTest::ResourceTesting
AST = Puppet::Parser::AST
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
searcher = Object.new
searcher.extend(Node.node_source(:ldap))
hostname = "culain.madstop.com"
# look for our host
assert_nothing_raised {
parent, classes = searcher.nodesearch(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 = searcher.nodesearch(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 = searcher.nodesearch(hostname)
}
end
end
|