3 require File.dirname(__FILE__) + '/../../spec_helper'
5 describe Puppet::Node::Catalog, " when compiling" do
6 it "should accept tags" do
7 config = Puppet::Node::Catalog.new("mynode")
9 config.tags.should == %w{one}
12 it "should accept multiple tags at once" do
13 config = Puppet::Node::Catalog.new("mynode")
14 config.tag("one", "two")
15 config.tags.should == %w{one two}
18 it "should convert all tags to strings" do
19 config = Puppet::Node::Catalog.new("mynode")
20 config.tag("one", :two)
21 config.tags.should == %w{one two}
24 it "should tag with both the qualified name and the split name" do
25 config = Puppet::Node::Catalog.new("mynode")
26 config.tag("one::two")
27 config.tags.include?("one").should be_true
28 config.tags.include?("one::two").should be_true
31 it "should accept classes" do
32 config = Puppet::Node::Catalog.new("mynode")
33 config.add_class("one")
34 config.classes.should == %w{one}
35 config.add_class("two", "three")
36 config.classes.should == %w{one two three}
39 it "should tag itself with passed class names" do
40 config = Puppet::Node::Catalog.new("mynode")
41 config.add_class("one")
42 config.tags.should == %w{one}
46 describe Puppet::Node::Catalog, " when extracting" do
47 it "should return extraction result as the method result" do
48 config = Puppet::Node::Catalog.new("mynode")
49 config.expects(:extraction_format).returns(:whatever)
50 config.expects(:extract_to_whatever).returns(:result)
51 config.extract.should == :result
55 describe Puppet::Node::Catalog, " when extracting transobjects" do
58 @parser = Puppet::Parser::Parser.new :Code => ""
59 @node = Puppet::Node.new("mynode")
60 @compile = Puppet::Parser::Compile.new(@node, @parser)
62 # XXX This is ridiculous.
63 @compile.send(:evaluate_main)
64 @scope = @compile.topscope
67 def mkresource(type, name)
68 Puppet::Parser::Resource.new(:type => type, :title => name, :source => @source, :scope => @scope)
71 it "should always create a TransBucket for the 'main' class" do
72 config = Puppet::Node::Catalog.new("mynode")
75 @source = mock 'source'
77 main = mkresource("class", :main)
78 config.add_vertex!(main)
80 bucket = mock 'bucket'
81 bucket.expects(:classes=).with(config.classes)
82 main.stubs(:builtin?).returns(false)
83 main.expects(:to_transbucket).returns(bucket)
85 config.extract_to_transportable.should equal(bucket)
88 # This isn't really a spec-style test, but I don't know how better to do it.
89 it "should transform the resource graph into a tree of TransBuckets and TransObjects" do
90 config = Puppet::Node::Catalog.new("mynode")
93 @source = mock 'source'
95 defined = mkresource("class", :main)
96 builtin = mkresource("file", "/yay")
98 config.add_edge!(defined, builtin)
101 bucket.expects(:classes=).with(config.classes)
102 defined.stubs(:builtin?).returns(false)
103 defined.expects(:to_transbucket).returns(bucket)
104 builtin.expects(:to_transobject).returns(:builtin)
106 config.extract_to_transportable.should == [:builtin]
109 # Now try it with a more complicated graph -- a three tier graph, each tier
110 it "should transform arbitrarily deep graphs into isomorphic trees" do
111 config = Puppet::Node::Catalog.new("mynode")
114 @scope.stubs(:tags).returns([])
115 @source = mock 'source'
118 top = mkresource "class", :main
120 topbucket.expects(:classes=).with([])
121 top.expects(:to_trans).returns(topbucket)
122 topres = mkresource "file", "/top"
123 topres.expects(:to_trans).returns(:topres)
124 config.add_edge! top, topres
126 middle = mkresource "class", "middle"
127 middle.expects(:to_trans).returns([])
128 config.add_edge! top, middle
129 midres = mkresource "file", "/mid"
130 midres.expects(:to_trans).returns(:midres)
131 config.add_edge! middle, midres
133 bottom = mkresource "class", "bottom"
134 bottom.expects(:to_trans).returns([])
135 config.add_edge! middle, bottom
136 botres = mkresource "file", "/bot"
137 botres.expects(:to_trans).returns(:botres)
138 config.add_edge! bottom, botres
140 toparray = config.extract_to_transportable
142 # This is annoying; it should look like:
143 # [[[:botres], :midres], :topres]
144 # but we can't guarantee sort order.
145 toparray.include?(:topres).should be_true
147 midarray = toparray.find { |t| t.is_a?(Array) }
148 midarray.include?(:midres).should be_true
149 botarray = midarray.find { |t| t.is_a?(Array) }
150 botarray.include?(:botres).should be_true
154 describe Puppet::Node::Catalog, " when converting to a transobject catalog" do
156 attr_accessor :name, :virtual, :builtin
157 def initialize(name, options = {})
159 options.each { |p,v| send(p.to_s + "=", v) }
179 Puppet::TransObject.new(name, builtin? ? "file" : "class")
184 @original = Puppet::Node::Catalog.new("mynode")
185 @original.tag(*%w{one two three})
186 @original.add_class *%w{four five six}
188 @top = TestResource.new 'top'
189 @topobject = TestResource.new 'topobject', :builtin => true
190 @virtual = TestResource.new 'virtual', :virtual => true
191 @virtualobject = TestResource.new 'virtualobject', :builtin => true, :virtual => true
192 @middle = TestResource.new 'middle'
193 @middleobject = TestResource.new 'middleobject', :builtin => true
194 @bottom = TestResource.new 'bottom'
195 @bottomobject = TestResource.new 'bottomobject', :builtin => true
197 @resources = [@top, @topobject, @middle, @middleobject, @bottom, @bottomobject]
199 @original.add_edge!(@top, @topobject)
200 @original.add_edge!(@top, @virtual)
201 @original.add_edge!(@virtual, @virtualobject)
202 @original.add_edge!(@top, @middle)
203 @original.add_edge!(@middle, @middleobject)
204 @original.add_edge!(@middle, @bottom)
205 @original.add_edge!(@bottom, @bottomobject)
207 @config = @original.to_transportable
210 it "should add all resources as TransObjects" do
211 @resources.each { |resource| @config.resource(resource.ref).should be_instance_of(Puppet::TransObject) }
214 it "should not extract defined virtual resources" do
215 @config.vertices.find { |v| v.name == "virtual" }.should be_nil
218 it "should not extract builtin virtual resources" do
219 @config.vertices.find { |v| v.name == "virtualobject" }.should be_nil
222 it "should copy the tag list to the new catalog" do
223 @config.tags.sort.should == @original.tags.sort
226 it "should copy the class list to the new catalog" do
227 @config.classes.should == @original.classes
230 it "should duplicate the original edges" do
231 @original.edges.each do |edge|
232 next if edge.source.virtual? or edge.target.virtual?
233 source = @config.resource(edge.source.ref)
234 target = @config.resource(edge.target.ref)
236 source.should_not be_nil
237 target.should_not be_nil
238 @config.edge?(source, target).should be_true
242 it "should set itself as the catalog for each converted resource" do
243 @config.vertices.each { |v| v.catalog.object_id.should equal(@config.object_id) }
247 describe Puppet::Node::Catalog, " when converting to a RAL catalog" do
249 @original = Puppet::Node::Catalog.new("mynode")
250 @original.tag(*%w{one two three})
251 @original.add_class *%w{four five six}
253 @top = Puppet::TransObject.new 'top', "class"
254 @topobject = Puppet::TransObject.new '/topobject', "file"
255 @middle = Puppet::TransObject.new 'middle', "class"
256 @middleobject = Puppet::TransObject.new '/middleobject', "file"
257 @bottom = Puppet::TransObject.new 'bottom', "class"
258 @bottomobject = Puppet::TransObject.new '/bottomobject', "file"
260 @resources = [@top, @topobject, @middle, @middleobject, @bottom, @bottomobject]
262 @original.add_resource(*@resources)
264 @original.add_edge!(@top, @topobject)
265 @original.add_edge!(@top, @middle)
266 @original.add_edge!(@middle, @middleobject)
267 @original.add_edge!(@middle, @bottom)
268 @original.add_edge!(@bottom, @bottomobject)
270 @config = @original.to_ral
273 it "should add all resources as RAL instances" do
274 @resources.each { |resource| @config.resource(resource.ref).should be_instance_of(Puppet::Type) }
277 it "should copy the tag list to the new catalog" do
278 @config.tags.sort.should == @original.tags.sort
281 it "should copy the class list to the new catalog" do
282 @config.classes.should == @original.classes
285 it "should duplicate the original edges" do
286 @original.edges.each do |edge|
287 @config.edge?(@config.resource(edge.source.ref), @config.resource(edge.target.ref)).should be_true
291 it "should set itself as the catalog for each converted resource" do
292 @config.vertices.each { |v| v.catalog.object_id.should equal(@config.object_id) }
296 it "should not lose track of resources whose names vary" do
297 changer = Puppet::TransObject.new 'changer', 'test'
299 config = Puppet::Node::Catalog.new('test')
300 config.add_resource(changer)
301 config.add_resource(@top)
303 config.add_edge!(@top, changer)
305 resource = stub 'resource', :name => "changer2", :title => "changer2", :ref => "Test[changer2]", :catalog= => nil, :remove => nil
307 changer.expects(:to_type).returns(resource)
311 Puppet::Type.allclear
313 proc { @config = config.to_ral }.should_not raise_error
314 @config.resource("Test[changer2]").should equal(resource)
318 # Remove all resource instances.
323 describe Puppet::Node::Catalog, " when functioning as a resource container" do
325 @config = Puppet::Node::Catalog.new("host")
326 @one = stub 'resource1', :ref => "Me[one]", :catalog= => nil
327 @two = stub 'resource2', :ref => "Me[two]", :catalog= => nil
328 @dupe = stub 'resource3', :ref => "Me[one]", :catalog= => nil
331 it "should provide a method to add one or more resources" do
332 @config.add_resource @one, @two
333 @config.resource(@one.ref).should equal(@one)
334 @config.resource(@two.ref).should equal(@two)
337 it "should set itself as the resource's catalog if it is not a relationship graph" do
338 @one.expects(:catalog=).with(@config)
339 @config.add_resource @one
342 it "should not set itself as the resource's catalog if it is a relationship graph" do
343 @one.expects(:catalog=).never
344 @config.is_relationship_graph = true
345 @config.add_resource @one
348 it "should make all vertices available by resource reference" do
349 @config.add_resource(@one)
350 @config.resource(@one.ref).should equal(@one)
351 @config.vertices.find { |r| r.ref == @one.ref }.should equal(@one)
354 it "should canonize how resources are referred to during retrieval when both type and title are provided" do
355 @config.add_resource(@one)
357 @config.resource("me", "one").should equal(@one)
360 it "should canonize how resources are referred to during retrieval when just the title is provided" do
361 @config.add_resource(@one)
363 @config.resource("me[one]", nil).should equal(@one)
366 it "should not allow two resources with the same resource reference" do
367 @config.add_resource(@one)
368 proc { @config.add_resource(@dupe) }.should raise_error(ArgumentError)
371 it "should not store objects that do not respond to :ref" do
372 proc { @config.add_resource("thing") }.should raise_error(ArgumentError)
375 it "should remove all resources when asked" do
376 @config.add_resource @one
377 @config.add_resource @two
383 it "should support a mechanism for finishing resources" do
386 @config.add_resource @one
387 @config.add_resource @two
392 it "should optionally support an initialization block and should finalize after such blocks" do
395 config = Puppet::Node::Catalog.new("host") do |conf|
396 conf.add_resource @one
397 conf.add_resource @two
401 it "should inform the resource that it is the resource's catalog" do
402 @one.expects(:catalog=).with(@config)
403 @config.add_resource @one
406 it "should be able to find resources by reference" do
407 @config.add_resource @one
408 @config.resource(@one.ref).should equal(@one)
411 it "should be able to find resources by reference or by type/title tuple" do
412 @config.add_resource @one
413 @config.resource("me", "one").should equal(@one)
416 it "should have a mechanism for removing resources" do
417 @config.add_resource @one
419 @config.remove_resource(@one)
420 @config.resource(@one.ref).should be_nil
421 @config.vertex?(@one).should be_false
424 it "should have a method for creating aliases for resources" do
425 @config.add_resource @one
426 @config.alias(@one, "other")
427 @config.resource("me", "other").should equal(@one)
430 # This test is the same as the previous, but the behaviour should be explicit.
431 it "should alias using the class name from the resource reference, not the resource class name" do
432 @config.add_resource @one
433 @config.alias(@one, "other")
434 @config.resource("me", "other").should equal(@one)
437 it "should fail to add an alias if the aliased name already exists" do
438 @config.add_resource @one
439 proc { @config.alias @two, "one" }.should raise_error(ArgumentError)
442 it "should remove resource aliases when the target resource is removed" do
443 @config.add_resource @one
444 @config.alias(@one, "other")
446 @config.remove_resource(@one)
447 @config.resource("me", "other").should be_nil
451 module ApplyingCatalogs
453 @config = Puppet::Node::Catalog.new("host")
455 @config.retrieval_duration = Time.now
456 @transaction = mock 'transaction'
457 Puppet::Transaction.stubs(:new).returns(@transaction)
458 @transaction.stubs(:evaluate)
459 @transaction.stubs(:cleanup)
460 @transaction.stubs(:addtimes)
464 describe Puppet::Node::Catalog, " when applying" do
465 include ApplyingCatalogs
467 it "should create and evaluate a transaction" do
468 @transaction.expects(:evaluate)
472 it "should provide the catalog time to the transaction" do
473 @transaction.expects(:addtimes).with do |arg|
474 arg[:config_retrieval].should be_instance_of(Time)
480 it "should clean up the transaction" do
481 @transaction.expects :cleanup
485 it "should return the transaction" do
486 @config.apply.should equal(@transaction)
489 it "should yield the transaction if a block is provided" do
490 @config.apply do |trans|
491 trans.should equal(@transaction)
495 it "should default to not being a host catalog" do
496 @config.host_config.should be_nil
499 it "should pass supplied tags on to the transaction" do
500 @transaction.expects(:tags=).with(%w{one two})
501 @config.apply(:tags => %w{one two})
504 it "should set ignoreschedules on the transaction if specified in apply()" do
505 @transaction.expects(:ignoreschedules=).with(true)
506 @config.apply(:ignoreschedules => true)
510 describe Puppet::Node::Catalog, " when applying host catalogs" do
511 include ApplyingCatalogs
513 # super() doesn't work in the setup method for some reason
515 @config.host_config = true
518 it "should send a report if reporting is enabled" do
519 Puppet[:report] = true
520 @transaction.expects :send_report
521 @transaction.stubs :any_failed? => false
525 it "should send a report if report summaries are enabled" do
526 Puppet[:summarize] = true
527 @transaction.expects :send_report
528 @transaction.stubs :any_failed? => false
532 it "should initialize the state database before applying a catalog" do
533 Puppet::Util::Storage.expects(:load)
535 # Short-circuit the apply, so we know we're loading before the transaction
536 Puppet::Transaction.expects(:new).raises ArgumentError
537 proc { @config.apply }.should raise_error(ArgumentError)
540 it "should sync the state database after applying" do
541 Puppet::Util::Storage.expects(:store)
542 @transaction.stubs :any_failed? => false
546 after { Puppet.settings.clear }
549 describe Puppet::Node::Catalog, " when applying non-host catalogs" do
550 include ApplyingCatalogs
553 @config.host_config = false
556 it "should never send reports" do
557 Puppet[:report] = true
558 Puppet[:summarize] = true
559 @transaction.expects(:send_report).never
563 it "should never modify the state database" do
564 Puppet::Util::Storage.expects(:load).never
565 Puppet::Util::Storage.expects(:store).never
569 after { Puppet.settings.clear }
572 describe Puppet::Node::Catalog, " when creating a relationship graph" do
574 @config = Puppet::Node::Catalog.new("host")
575 @compone = Puppet::Type::Component.create :name => "one"
576 @comptwo = Puppet::Type::Component.create :name => "two", :require => ["class", "one"]
577 @file = Puppet::Type.type(:file)
578 @one = @file.create :path => "/one"
579 @two = @file.create :path => "/two"
580 @config.add_edge! @compone, @one
581 @config.add_edge! @comptwo, @two
583 @three = @file.create :path => "/three"
584 @four = @file.create :path => "/four", :require => ["file", "/three"]
585 @five = @file.create :path => "/five"
586 @config.add_resource @compone, @comptwo, @one, @two, @three, @four, @five
587 @relationships = @config.relationship_graph
590 it "should fail when trying to create a relationship graph for a relationship graph" do
591 proc { @relationships.relationship_graph }.should raise_error(Puppet::DevError)
594 it "should be able to create a relationship graph" do
595 @relationships.should be_instance_of(Puppet::Node::Catalog)
598 it "should copy its host_config setting to the relationship graph" do
599 config = Puppet::Node::Catalog.new
600 config.host_config = true
601 config.relationship_graph.host_config.should be_true
604 it "should not have any components" do
605 @relationships.vertices.find { |r| r.instance_of?(Puppet::Type::Component) }.should be_nil
608 it "should have all non-component resources from the catalog" do
609 # The failures print out too much info, so i just do a class comparison
610 @relationships.vertex?(@five).should be_true
613 it "should have all resource relationships set as edges" do
614 @relationships.edge?(@three, @four).should be_true
617 it "should copy component relationships to all contained resources" do
618 @relationships.edge?(@one, @two).should be_true
621 it "should get removed when the catalog is cleaned up" do
622 @relationships.expects(:clear).with(false)
624 @config.instance_variable_get("@relationship_graph").should be_nil
627 it "should create a new relationship graph after clearing the old one" do
628 @relationships.expects(:clear).with(false)
630 @config.relationship_graph.should be_instance_of(Puppet::Node::Catalog)
633 it "should look up resources in the relationship graph if not found in the main catalog" do
634 five = stub 'five', :ref => "File[five]", :catalog= => nil
635 @relationships.add_resource five
636 @config.resource(five.ref).should equal(five)
639 it "should provide a method to create additional resources that also registers the resource" do
640 args = {:name => "/yay", :ensure => :file}
641 resource = stub 'file', :ref => "File[/yay]", :catalog= => @config
642 Puppet::Type.type(:file).expects(:create).with(args).returns(resource)
643 @config.create_resource :file, args
644 @config.resource("File[/yay]").should equal(resource)
647 it "should provide a mechanism for creating implicit resources" do
648 args = {:name => "/yay", :ensure => :file}
649 resource = stub 'file', :ref => "File[/yay]", :catalog= => @config
650 Puppet::Type.type(:file).expects(:create).with(args).returns(resource)
651 resource.expects(:implicit=).with(true)
652 @config.create_implicit_resource :file, args
653 @config.resource("File[/yay]").should equal(resource)
656 it "should add implicit resources to the relationship graph if there is one" do
657 args = {:name => "/yay", :ensure => :file}
658 resource = stub 'file', :ref => "File[/yay]", :catalog= => @config
659 resource.expects(:implicit=).with(true)
660 Puppet::Type.type(:file).expects(:create).with(args).returns(resource)
662 relgraph = @config.relationship_graph
664 @config.create_implicit_resource :file, args
665 relgraph.resource("File[/yay]").should equal(resource)
668 it "should remove resources created mid-transaction" do
669 args = {:name => "/yay", :ensure => :file}
670 resource = stub 'file', :ref => "File[/yay]", :catalog= => @config
671 @transaction = mock 'transaction'
672 Puppet::Transaction.stubs(:new).returns(@transaction)
673 @transaction.stubs(:evaluate)
674 @transaction.stubs(:cleanup)
675 @transaction.stubs(:addtimes)
676 Puppet::Type.type(:file).expects(:create).with(args).returns(resource)
677 resource.expects :remove
678 @config.apply do |trans|
679 @config.create_resource :file, args
680 @config.resource("File[/yay]").should equal(resource)
682 @config.resource("File[/yay]").should be_nil
685 it "should remove resources from the relationship graph if it exists" do
686 @config.remove_resource(@one)
687 @config.relationship_graph.vertex?(@one).should be_false
691 Puppet::Type.allclear
695 describe Puppet::Node::Catalog, " when writing dot files" do
697 @config = Puppet::Node::Catalog.new("host")
699 @file = File.join(Puppet[:graphdir], @name.to_s + ".dot")
701 it "should only write when it is a host catalog" do
702 File.expects(:open).with(@file).never
703 @config.host_config = false
704 Puppet[:graph] = true
705 @config.write_graph(@name)
708 it "should only write when graphing is enabled" do
709 File.expects(:open).with(@file).never
710 @config.host_config = true
711 Puppet[:graph] = false
712 @config.write_graph(@name)
715 it "should write a dot file based on the passed name" do
716 File.expects(:open).with(@file, "w").yields(stub("file", :puts => nil))
717 @config.expects(:to_dot).with("name" => @name.to_s.capitalize)
718 @config.host_config = true
719 Puppet[:graph] = true
720 @config.write_graph(@name)
724 Puppet.settings.clear
728 describe Puppet::Node::Catalog, " when indirecting" do
730 @indirection = mock 'indirection'
732 Puppet::Indirector::Indirection.clear_cache
735 it "should redirect to the indirection for retrieval" do
736 Puppet::Node::Catalog.stubs(:indirection).returns(@indirection)
737 @indirection.expects(:find).with(:myconfig)
738 Puppet::Node::Catalog.find(:myconfig)
741 it "should default to the 'compiler' terminus" do
742 Puppet::Node::Catalog.indirection.terminus_class.should == :compiler
747 Puppet::Indirector::Indirection.clear_cache
751 describe Puppet::Node::Catalog, " when converting to yaml" do
753 @catalog = Puppet::Node::Catalog.new("me")
754 @catalog.add_edge!("one", "two")
757 it "should be able to be dumped to yaml" do
758 YAML.dump(@catalog).should be_instance_of(String)
762 describe Puppet::Node::Catalog, " when converting from yaml" do
764 @catalog = Puppet::Node::Catalog.new("me")
765 @catalog.add_edge!("one", "two")
767 text = YAML.dump(@catalog)
768 @newcatalog = YAML.load(text)
771 it "should get converted back to a catalog" do
772 @newcatalog.should be_instance_of(Puppet::Node::Catalog)
775 it "should have all vertices" do
776 @newcatalog.vertex?("one").should be_true
777 @newcatalog.vertex?("two").should be_true
780 it "should have all edges" do
781 @newcatalog.edge?("one", "two").should be_true