diff options
-rw-r--r-- | lib/puppet/indirector/indirection.rb | 96 | ||||
-rwxr-xr-x | spec/unit/indirector/indirection.rb | 927 |
2 files changed, 562 insertions, 461 deletions
diff --git a/lib/puppet/indirector/indirection.rb b/lib/puppet/indirector/indirection.rb index d47433c60..91cc42b17 100644 --- a/lib/puppet/indirector/indirection.rb +++ b/lib/puppet/indirector/indirection.rb @@ -1,4 +1,5 @@ require 'puppet/util/docs' +require 'puppet/indirector/envelope' # The class that connects functional classes with their different collection # back-ends. Each indirection has a set of associated terminus classes, @@ -28,8 +29,7 @@ class Puppet::Indirector::Indirection # Find an indirected model by name. This is provided so that Terminus classes # can specifically hook up with the indirections they are associated with. def self.model(name) - match = @@indirections.find { |i| i.name == name } - return nil unless match + return nil unless match = @@indirections.find { |i| i.name == name } match.model end @@ -65,6 +65,25 @@ class Puppet::Indirector::Indirection @@indirections.delete(self) if @@indirections.include?(self) end + # Set the time-to-live for instances created through this indirection. + def ttl=(value) + raise ArgumentError, "Indirection TTL must be an integer" unless value.is_a?(Fixnum) + @ttl = value + end + + # Default to the runinterval for the ttl. + def ttl + unless defined?(@ttl) + @ttl = Puppet[:runinterval].to_i + end + @ttl + end + + # Calculate the expiration date for a returned instance. + def expiration + Time.now + ttl + end + # Generate the full doc string. def doc text = "" @@ -106,6 +125,12 @@ class Puppet::Indirector::Indirection end end + # Set the options that can be passed on to the terminus instances. + attr_reader :option_struct + def options=(options) + @option_struct = Struct.new(*options) + end + # Return the singleton terminus for this indirection. def terminus(terminus_name = nil) # Get the name of the terminus. @@ -161,29 +186,41 @@ class Puppet::Indirector::Indirection check_authorization(:find, terminus_name, ([key] + args)) # See if our instance is in the cache and up to date. - if cache? and cache.has_most_recent?(key, terminus(terminus_name).version(key)) - Puppet.debug "Using cached %s %s" % [self.name, key] - return cache.find(key, *args) + if cache? and cached = cache.find(key, *args) + if cached.expired? + Puppet.info "Cached %s %s expired at %s; not using" % [self.name, key, cached.expiration] + else + Puppet.debug "Using cached %s %s" % [self.name, key] + return cached + end end # Otherwise, return the result from the terminus, caching if appropriate. if result = terminus(terminus_name).find(key, *args) - result.version ||= Time.now.utc + # Include the envelope module, so we can set the expiration. + result.extend(Puppet::Indirector::Envelope) + result.expiration ||= self.expiration if cache? Puppet.info "Caching %s %s" % [self.name, key] cache.save(result, *args) end - terminus(terminus_name).post_find(result) if terminus(terminus_name).respond_to?(:post_find) - return result end + + return nil end - def destroy(*args) - check_authorization(:destroy, terminus_class, args) + def destroy(key, *args) + check_authorization(:destroy, terminus_class, ([key] + args)) + + terminus.destroy(key, *args) + + if cache? and cached = cache.find(key, *args) + cache.destroy(key, *args) + end - terminus.destroy(*args) + nil end def search(*args) @@ -191,21 +228,22 @@ class Puppet::Indirector::Indirection result = terminus.search(*args) - terminus().post_search(result) if terminus().respond_to?(:post_search) - result end # these become instance methods def save(instance, *args) - check_authorization(:save, terminus_class, ([instance] + args)) + if respond_to?(:select_terminus) + terminus_name = select_terminus(instance.name, *args) + else + terminus_name = terminus_class + end - instance.version ||= Time.now.utc - dest = cache? ? cache : terminus - return if dest.has_most_recent?(instance.name, instance.version) - Puppet.info "Caching %s %s" % [self.name, instance.name] if cache? + check_authorization(:save, terminus_name, ([instance] + args)) + + # If caching is enabled, save our document there, do cache.save(instance, *args) if cache? - terminus.save(instance, *args) + terminus(terminus_class).save(instance, *args) end def version(*args) @@ -226,6 +264,20 @@ class Puppet::Indirector::Indirection end end + # Handle a given indirected call. + def prepare_call(method, arguments) + raise ArgumentError, "Options must be a hash" unless arguments.is_a?(Hash) + + # Set any terminus options. + options = option_struct ? set_options(option_struct, arguments) : nil + + tclass = choose_terminus(options) + + check_authorization(method, tclass, options) + + return terminus(tclass), options + end + # Create a new terminus instance. def make_terminus(terminus_class) # Load our terminus class. @@ -234,4 +286,10 @@ class Puppet::Indirector::Indirection end return klass.new end + + # Create a struct instance with all of the appropriate options set + # from the provided hash. + def set_options(struct, arguments) + struct.new(struct.members.inject([]) { |array, param| arguments[param.to_sym]; array } ) + end end diff --git a/spec/unit/indirector/indirection.rb b/spec/unit/indirector/indirection.rb index fe456b61e..5338a4858 100755 --- a/spec/unit/indirector/indirection.rb +++ b/spec/unit/indirector/indirection.rb @@ -2,579 +2,622 @@ require File.dirname(__FILE__) + '/../../spec_helper' -require 'puppet/indirector' +require 'puppet/indirector/indirection' +describe Puppet::Indirector::Indirection do + describe "when initializing" do + # (LAK) I've no idea how to test this, really. + it "should store a reference to itself before it consumes its options" do + proc { @indirection = Puppet::Indirector::Indirection.new(Object.new, :testingness, :not_valid_option) }.should raise_error + Puppet::Indirector::Indirection.instance(:testingness).should be_instance_of(Puppet::Indirector::Indirection) + Puppet::Indirector::Indirection.instance(:testingness).delete + end + it "should keep a reference to the indirecting model" do + model = mock 'model' + @indirection = Puppet::Indirector::Indirection.new(model, :myind) + @indirection.model.should equal(model) + end -describe Puppet::Indirector::Indirection, " when initializing" do - # (LAK) I've no idea how to test this, really. - it "should store a reference to itself before it consumes its options" do - proc { @indirection = Puppet::Indirector::Indirection.new(Object.new, :testingness, :not_valid_option) }.should raise_error - Puppet::Indirector::Indirection.instance(:testingness).should be_instance_of(Puppet::Indirector::Indirection) - Puppet::Indirector::Indirection.instance(:testingness).delete - end + it "should set the name" do + @indirection = Puppet::Indirector::Indirection.new(mock('model'), :myind) + @indirection.name.should == :myind + end - it "should keep a reference to the indirecting model" do - model = mock 'model' - @indirection = Puppet::Indirector::Indirection.new(model, :myind) - @indirection.model.should equal(model) - end + it "should require indirections to have unique names" do + @indirection = Puppet::Indirector::Indirection.new(mock('model'), :test) + proc { Puppet::Indirector::Indirection.new(:test) }.should raise_error(ArgumentError) + end - it "should set the name" do - @indirection = Puppet::Indirector::Indirection.new(mock('model'), :myind) - @indirection.name.should == :myind - end + it "should extend itself with any specified module" do + mod = Module.new + @indirection = Puppet::Indirector::Indirection.new(mock('model'), :test, :extend => mod) + @indirection.metaclass.included_modules.should include(mod) + end - it "should require indirections to have unique names" do - @indirection = Puppet::Indirector::Indirection.new(mock('model'), :test) - proc { Puppet::Indirector::Indirection.new(:test) }.should raise_error(ArgumentError) - end + it "should allow specification of supported options" do + lambda { @indirection = Puppet::Indirector::Indirection.new(mock('model'), :test, :options => [:one, :two]) }.should_not raise_error + end - it "should extend itself with any specified module" do - mod = Module.new - @indirection = Puppet::Indirector::Indirection.new(mock('model'), :test, :extend => mod) - @indirection.metaclass.included_modules.should include(mod) - end + it "should define a new Struct class to support the specified options" do + struct = mock 'struct' + Struct.expects(:new).with(:one, :two) + @indirection = Puppet::Indirector::Indirection.new(mock('model'), :test, :options => [:one, :two]) + end - after do - @indirection.delete if defined? @indirection + after do + @indirection.delete if defined? @indirection + end end -end -describe Puppet::Indirector::Indirection do - before :each do - @indirection = Puppet::Indirector::Indirection.new(mock('model'), :test) - @terminus = stub 'terminus', :has_most_recent? => false - @indirection.stubs(:terminus).returns(@terminus) - @indirection.stubs(:terminus_class).returns(:whatever) - @instance = stub 'instance', :version => nil, :version= => nil, :name => "whatever" - @name = :mything - end - - describe Puppet::Indirector::Indirection, " when looking for a model instance" do + describe "when an instance" do + before :each do + @terminus_class = mock 'terminus_class' + @terminus = mock 'terminus' + @terminus_class.stubs(:new).returns(@terminus) + @cache = mock 'cache' + @cache_class = mock 'cache_class' + Puppet::Indirector::Terminus.stubs(:terminus_class).with(:test, :cache_terminus).returns(@cache_class) + Puppet::Indirector::Terminus.stubs(:terminus_class).with(:test, :test_terminus).returns(@terminus_class) - it "should let the appropriate terminus perform the lookup" do - @terminus.expects(:find).with(@name).returns(@instance) - @indirection.find(@name).should == @instance - end + @indirection = Puppet::Indirector::Indirection.new(mock('model'), :test) + @indirection.terminus_class = :test_terminus - it "should not attempt to set a timestamp if the terminus cannot find the instance" do - @terminus.expects(:find).with(@name).returns(nil) - proc { @indirection.find(@name) }.should_not raise_error + @instance = stub 'instance', :expiration => nil, :expiration= => nil, :name => "whatever" + @name = :mything end - it "should pass the instance to the :post_find hook if there is one" do - class << @terminus - def post_find - end - end - @terminus.expects(:post_find).with(@instance) - @terminus.expects(:find).with(@name).returns(@instance) - @indirection.find(@name) + it "should allow setting the ttl" do + @indirection.ttl = 300 + @indirection.ttl.should == 300 end - end - - describe Puppet::Indirector::Indirection, " when removing a model instance" do - it "should let the appropriate terminus remove the instance" do - @terminus.expects(:destroy).with(@name).returns(@instance) - @indirection.destroy(@name).should == @instance + it "should default to the :runinterval setting, converted to an integer, for its ttl" do + Puppet.settings.expects(:value).returns "1800" + @indirection.ttl.should == 1800 end - end - - describe Puppet::Indirector::Indirection, " when searching for multiple model instances" do - it "should let the appropriate terminus find the matching instances" do - @terminus.expects(:search).with(@name).returns(@instance) - @indirection.search(@name).should == @instance + it "should calculate the current expiration by adding the TTL to the current time" do + @indirection.stubs(:ttl).returns(100) + now = Time.now + Time.stubs(:now).returns now + @indirection.expiration.should == (Time.now + 100) end + + describe "and looking for a model instance" do + it "should let the appropriate terminus perform the lookup" do + @terminus.expects(:find).with(@name).returns(@instance) + @indirection.find(@name).should == @instance + end - it "should pass the instances to the :post_search hook if there is one" do - class << @terminus - def post_search - end + it "should return nil if nothing is returned by the terminus" do + @terminus.expects(:find).with(@name).returns(nil) + @indirection.find(@name).should be_nil end - @terminus.expects(:post_search).with(@instance) - @terminus.expects(:search).with(@name).returns(@instance) - @indirection.search(@name) - end - end - describe Puppet::Indirector::Indirection, " when storing a model instance" do + it "should extend any found instance with the Envelope module" do + @terminus.stubs(:find).returns(@instance) - it "should let the appropriate terminus store the instance" do - @terminus.expects(:save).with(@instance).returns(@instance) - @indirection.save(@instance).should == @instance - end - end - - describe Puppet::Indirector::Indirection, " when handling instance versions" do + @instance.expects(:extend).with(Puppet::Indirector::Envelope) + @indirection.find(@name) + end - it "should let the appropriate terminus perform the lookup" do - @terminus.expects(:version).with(@name).returns(5) - @indirection.version(@name).should == 5 - end + it "should set the expiration date on any instances without one set" do + # Otherwise, our stub method doesn't get used, so the tests fail. + @instance.stubs(:extend) + @terminus.stubs(:find).returns(@instance) - it "should add versions to found instances that do not already have them" do - @terminus.expects(:find).with(@name).returns(@instance) - time = mock 'time' - time.expects(:utc).returns(:mystamp) - Time.expects(:now).returns(time) - @instance.expects(:version=).with(:mystamp) - @indirection.find(@name) - end + @indirection.expects(:expiration).returns :yay - it "should add versions to saved instances that do not already have them" do - time = mock 'time' - time.expects(:utc).returns(:mystamp) - Time.expects(:now).returns(time) - @instance.expects(:version=).with(:mystamp) - @terminus.stubs(:save) - @indirection.save(@instance) - end + @instance.expects(:expiration).returns(nil) + @instance.expects(:expiration=).with(:yay) - # We've already tested this, basically, but... - it "should use the current time in UTC for versions" do - @instance.expects(:version=).with do |time| - time.utc? + @indirection.find(@name) end - @terminus.stubs(:save) - @indirection.save(@instance) - end - end + it "should not override an already-set expiration date on returned instances" do + # Otherwise, our stub method doesn't get used, so the tests fail. + @instance.stubs(:extend) + @terminus.stubs(:find).returns(@instance) - describe Puppet::Indirector::Indirection, " when an authorization hook is present" do + @indirection.expects(:expiration).never - before do - # So the :respond_to? turns out right. - class << @terminus - def authorized? - end + @instance.expects(:expiration).returns(:yay) + @instance.expects(:expiration=).never + + @indirection.find(@name) end - end - it "should not check authorization if a node name is not provided" do - @terminus.expects(:authorized?).never - @terminus.stubs(:find) - @indirection.find("/my/key") - end + describe "when caching is enabled" do + before do + @indirection.cache_class = :cache_terminus + @cache_class.expects(:new).returns(@cache) - it "should fail while finding instances if authorization returns false" do - @terminus.expects(:authorized?).with(:find, "/my/key", :node => "mynode").returns(false) - @terminus.stubs(:find) - proc { @indirection.find("/my/key", :node => "mynode") }.should raise_error(ArgumentError) - end + @instance.stubs(:expired?).returns false + end - it "should continue finding instances if authorization returns true" do - @terminus.expects(:authorized?).with(:find, "/my/key", :node => "mynode").returns(true) - @terminus.stubs(:find) - @indirection.find("/my/key", :node => "mynode") - end + it "should first look in the cache for an instance" do + @terminus.expects(:find).never + @cache.expects(:find).with(@name).returns @instance - it "should fail while saving instances if authorization returns false" do - @terminus.expects(:authorized?).with(:save, :myinstance, :node => "mynode").returns(false) - @terminus.stubs(:save) - proc { @indirection.save(:myinstance, :node => "mynode") }.should raise_error(ArgumentError) - end + @indirection.find(@name) + end - it "should continue saving instances if authorization returns true" do - instance = stub 'instance', :version => 1.0, :name => "eh" - @terminus.expects(:authorized?).with(:save, instance, :node => "mynode").returns(true) - @terminus.stubs(:save) - @indirection.save(instance, :node => "mynode") - end + it "should return the cached object if it is not expired" do + @instance.stubs(:expired?).returns false - it "should fail while destroying instances if authorization returns false" do - @terminus.expects(:authorized?).with(:destroy, "/my/key", :node => "mynode").returns(false) - @terminus.stubs(:destroy) - proc { @indirection.destroy("/my/key", :node => "mynode") }.should raise_error(ArgumentError) - end + @cache.stubs(:find).returns @instance + @indirection.find(@name).should equal(@instance) + end - it "should continue destroying instances if authorization returns true" do - instance = stub 'instance', :version => 1.0, :name => "eh" - @terminus.expects(:authorized?).with(:destroy, instance, :node => "mynode").returns(true) - @terminus.stubs(:destroy) - @indirection.destroy(instance, :node => "mynode") - end + it "should send a debug log if it is using the cached object" do + Puppet.expects(:debug) + @cache.stubs(:find).returns @instance - it "should fail while searching for instances if authorization returns false" do - @terminus.expects(:authorized?).with(:search, "/my/key", :node => "mynode").returns(false) - @terminus.stubs(:search) - proc { @indirection.search("/my/key", :node => "mynode") }.should raise_error(ArgumentError) - end + @indirection.find(@name) + end - it "should continue searching for instances if authorization returns true" do - @terminus.expects(:authorized?).with(:search, "/my/key", :node => "mynode").returns(true) - @terminus.stubs(:search) - @indirection.search("/my/key", :node => "mynode") - end - end + it "should not return the cached object if it is expired" do + @instance.stubs(:expired?).returns true - after :each do - @indirection.delete - Puppet::Indirector::Indirection.clear_cache - end -end + @cache.stubs(:find).returns @instance + @terminus.stubs(:find).returns nil + @indirection.find(@name).should be_nil + end + it "should send an info log if it is using the cached object" do + Puppet.expects(:info) + @instance.stubs(:expired?).returns true -describe Puppet::Indirector::Indirection, " when managing indirection instances" do - it "should allow an indirection to be retrieved by name" do - @indirection = Puppet::Indirector::Indirection.new(mock('model'), :test) - Puppet::Indirector::Indirection.instance(:test).should equal(@indirection) - end - - it "should return nil when the named indirection has not been created" do - Puppet::Indirector::Indirection.instance(:test).should be_nil - end + @cache.stubs(:find).returns @instance + @terminus.stubs(:find).returns nil + @indirection.find(@name) + end - it "should allow an indirection's model to be retrieved by name" do - mock_model = mock('model') - @indirection = Puppet::Indirector::Indirection.new(mock_model, :test) - Puppet::Indirector::Indirection.model(:test).should equal(mock_model) - end - - it "should return nil when no model matches the requested name" do - Puppet::Indirector::Indirection.model(:test).should be_nil - end + it "should cache any objects not retrieved from the cache" do + @cache.expects(:find).with(@name).returns nil - after do - @indirection.delete if defined? @indirection - end -end + @terminus.expects(:find).with(@name).returns(@instance) + @cache.expects(:save).with(@instance) -describe Puppet::Indirector::Indirection, " when choosing the terminus class" do - before do - @indirection = Puppet::Indirector::Indirection.new(mock('model'), :test) - @terminus = mock 'terminus' - @terminus_class = stub 'terminus class', :new => @terminus - Puppet::Indirector::Terminus.stubs(:terminus_class).with(:test, :default).returns(@terminus_class) - end + @indirection.find(@name) + end - it "should choose the default terminus class if one is specified and no specific terminus class is provided" do - @indirection.terminus_class = :default - @indirection.terminus_class.should equal(:default) - end + it "should send an info log that the object is being cached" do + @cache.stubs(:find).returns nil - it "should use the provided Puppet setting if told to do so" do - Puppet::Indirector::Terminus.stubs(:terminus_class).with(:test, :my_terminus).returns(mock("terminus_class2")) - Puppet.settings.expects(:value).with(:my_setting).returns("my_terminus") - @indirection.terminus_setting = :my_setting - @indirection.terminus_class.should equal(:my_terminus) - end + @terminus.stubs(:find).returns(@instance) + @cache.stubs(:save) - it "should fail if the provided terminus class is not valid" do - Puppet::Indirector::Terminus.stubs(:terminus_class).with(:test, :nosuchclass).returns(nil) - proc { @indirection.terminus_class = :nosuchclass }.should raise_error(ArgumentError) - end - - it "should fail if no terminus class is picked" do - proc { @indirection.terminus_class }.should raise_error(Puppet::DevError) - end + Puppet.expects(:info) - after do - @indirection.delete if defined? @indirection - end -end + @indirection.find(@name) + end + end + end -describe Puppet::Indirector::Indirection, " when specifying the terminus class to use" do - before do - @indirection = Puppet::Indirector::Indirection.new(mock('model'), :test) - @terminus = mock 'terminus' - @terminus_class = stub 'terminus class', :new => @terminus - end + describe "and storing a model instance" do + it "should let the appropriate terminus store the instance" do + @terminus.expects(:save).with(@instance).returns(@instance) + @indirection.save(@instance).should == @instance + end - it "should allow specification of a terminus type" do - @indirection.should respond_to(:terminus_class=) - end + it "should check authorization" do + @indirection.expects(:check_authorization).with(:save, :test_terminus, [@instance]) + @terminus.stubs(:save) - it "should fail to redirect if no terminus type has been specified" do - proc { @indirection.find("blah") }.should raise_error(Puppet::DevError) - end + @indirection.save(@instance) + end - it "should fail when the terminus class name is an empty string" do - proc { @indirection.terminus_class = "" }.should raise_error(ArgumentError) - end + describe "when caching is enabled" do + before do + @indirection.cache_class = :cache_terminus + @cache_class.expects(:new).returns(@cache) - it "should fail when the terminus class name is nil" do - proc { @indirection.terminus_class = nil }.should raise_error(ArgumentError) - end + @instance.stubs(:expired?).returns false + end - it "should fail when the specified terminus class cannot be found" do - Puppet::Indirector::Terminus.expects(:terminus_class).with(:test, :foo).returns(nil) - proc { @indirection.terminus_class = :foo }.should raise_error(ArgumentError) - end + it "should save the object to the cache" do + @cache.expects(:save).with(@instance) + @terminus.stubs(:save) + @indirection.save(@instance) + end + end + end + + describe "and removing a model instance" do + it "should delegate the instance removal to the appropriate terminus" do + @terminus.expects(:destroy).with(@name) + @indirection.destroy(@name) + end - it "should select the specified terminus class if a terminus class name is provided" do - Puppet::Indirector::Terminus.expects(:terminus_class).with(:test, :foo).returns(@terminus_class) - @indirection.terminus(:foo).should equal(@terminus) - end + it "should return nil" do + @terminus.stubs(:destroy) + @indirection.destroy(@name).should be_nil + end - it "should use the configured terminus class if no terminus name is specified" do - Puppet::Indirector::Terminus.stubs(:terminus_class).with(:test, :foo).returns(@terminus_class) - @indirection.terminus_class = :foo - @indirection.terminus().should equal(@terminus) - end + describe "when caching is enabled" do + before do + @indirection.cache_class = :cache_terminus + @cache_class.expects(:new).returns(@cache) - after do - @indirection.delete if defined? @indirection - end -end + @instance.stubs(:expired?).returns false + end + + it "should destroy any found object in the cache" do + cached = mock 'cache' + @cache.expects(:find).with(@name).returns cached + @cache.expects(:destroy).with(@name) + @terminus.stubs(:destroy) -describe Puppet::Indirector::Indirection, " when a select_terminus hook is available" do - before do - @indirection = Puppet::Indirector::Indirection.new(mock('model'), :test) + @indirection.destroy(@name) + end + end + end - # And provide a select_terminus hook - @indirection.meta_def(:select_terminus) do |uri| - :other + describe "and searching for multiple model instances" do + it "should let the appropriate terminus find the matching instances" do + @terminus.expects(:search).with(@name).returns(@instance) + @indirection.search(@name).should == @instance + end end - @terminus = mock 'terminus' - @terminus_class = stub 'terminus class', :new => @terminus + describe "and an authorization hook is present" do + before do + # So the :respond_to? turns out correctly. + class << @terminus + def authorized? + end + end + end - @other_terminus = mock 'other_terminus' - @other_terminus_class = stub 'other_terminus_class', :new => @other_terminus + it "should not check authorization if a node name is not provided" do + @terminus.expects(:authorized?).never + @terminus.stubs(:find) + @indirection.find("/my/key") + end - @cache_terminus = mock 'cache_terminus' - @cache_terminus_class = stub 'cache_terminus_class', :new => @cache_terminus + it "should fail while finding instances if authorization returns false" do + @terminus.expects(:authorized?).with(:find, "/my/key", :node => "mynode").returns(false) + @terminus.stubs(:find) + proc { @indirection.find("/my/key", :node => "mynode") }.should raise_error(ArgumentError) + end - Puppet::Indirector::Terminus.stubs(:terminus_class).with(:test, :foo).returns(@terminus_class) - Puppet::Indirector::Terminus.stubs(:terminus_class).with(:test, :other).returns(@other_terminus_class) - Puppet::Indirector::Terminus.stubs(:terminus_class).with(:test, :cache).returns(@cache_terminus_class) + it "should continue finding instances if authorization returns true" do + @terminus.expects(:authorized?).with(:find, "/my/key", :node => "mynode").returns(true) + @terminus.stubs(:find) + @indirection.find("/my/key", :node => "mynode") + end - # Set it to a default type. - @indirection.terminus_class = :foo + it "should fail while saving instances if authorization returns false" do + @terminus.expects(:authorized?).with(:save, @instance, :node => "mynode").returns(false) + @terminus.stubs(:save) + proc { @indirection.save(@instance, :node => "mynode") }.should raise_error(ArgumentError) + end - @uri = "full://url/path" - @result = stub 'result', :version => 1.0 - end + it "should continue saving instances if authorization returns true" do + @terminus.expects(:authorized?).with(:save, @instance, :node => "mynode").returns(true) + @terminus.stubs(:save) + @indirection.save(@instance, :node => "mynode") + end - it "should use the terminus name provided by passing the key to the :select_terminus hook when finding instances" do - # Set up the expectation - @other_terminus.expects(:find).with(@uri).returns(@result) + it "should fail while destroying instances if authorization returns false" do + @terminus.expects(:authorized?).with(:destroy, "/my/key", :node => "mynode").returns(false) + @terminus.stubs(:destroy) + proc { @indirection.destroy("/my/key", :node => "mynode") }.should raise_error(ArgumentError) + end + + it "should continue destroying instances if authorization returns true" do + @terminus.expects(:authorized?).with(:destroy, @instance, :node => "mynode").returns(true) + @terminus.stubs(:destroy) + @indirection.destroy(@instance, :node => "mynode") + end + + it "should fail while searching for instances if authorization returns false" do + @terminus.expects(:authorized?).with(:search, "/my/key", :node => "mynode").returns(false) + @terminus.stubs(:search) + proc { @indirection.search("/my/key", :node => "mynode") }.should raise_error(ArgumentError) + end + + it "should continue searching for instances if authorization returns true" do + @terminus.expects(:authorized?).with(:search, "/my/key", :node => "mynode").returns(true) + @terminus.stubs(:search) + @indirection.search("/my/key", :node => "mynode") + end + end - @indirection.find(@uri) + after :each do + @indirection.delete + Puppet::Indirector::Indirection.clear_cache + end end - it "should use the terminus name provided by passing the key to the :select_terminus hook when testing if a cached instance is up to date" do - @indirection.cache_class = :cache - @other_terminus.expects(:version).with(@uri).returns(2.0) + describe "when managing indirection instances" do + it "should allow an indirection to be retrieved by name" do + @indirection = Puppet::Indirector::Indirection.new(mock('model'), :test) + Puppet::Indirector::Indirection.instance(:test).should equal(@indirection) + end + + it "should return nil when the named indirection has not been created" do + Puppet::Indirector::Indirection.instance(:test).should be_nil + end - @cache_terminus.expects(:has_most_recent?).with(@uri, 2.0).returns(true) - @cache_terminus.expects(:find).returns(:whatever) + it "should allow an indirection's model to be retrieved by name" do + mock_model = mock('model') + @indirection = Puppet::Indirector::Indirection.new(mock_model, :test) + Puppet::Indirector::Indirection.model(:test).should equal(mock_model) + end + + it "should return nil when no model matches the requested name" do + Puppet::Indirector::Indirection.model(:test).should be_nil + end - @indirection.find(@uri).should == :whatever + after do + @indirection.delete if defined? @indirection + end end - it "should pass all arguments to the :select_terminus hook" do - @indirection.expects(:select_terminus).with(@uri, :node => "johnny").returns(:other) - @other_terminus.stubs(:find) + describe "when routing to the correct the terminus class" do + before do + @indirection = Puppet::Indirector::Indirection.new(mock('model'), :test) + @terminus = mock 'terminus' + @terminus_class = stub 'terminus class', :new => @terminus + Puppet::Indirector::Terminus.stubs(:terminus_class).with(:test, :default).returns(@terminus_class) + end + + it "should have a method for choosing the appropriate terminus class" - @indirection.find(@uri, :node => "johnny") - end + it "should fail if no terminus class can be picked" do + proc { @indirection.terminus_class }.should raise_error(Puppet::DevError) + end - it "should pass the original key to the terminus rather than a modified key" do - # This is the same test as before - @other_terminus.expects(:find).with(@uri).returns(@result) - @indirection.find(@uri) - end + it "should use the select_terminus hook if one is available" - after do - @indirection.delete if defined? @indirection - end -end + it "should choose the default terminus class if one is specified" do + @indirection.terminus_class = :default + @indirection.terminus_class.should equal(:default) + end -describe Puppet::Indirector::Indirection, " when managing terminus instances" do - before do - @indirection = Puppet::Indirector::Indirection.new(mock('model'), :test) - @terminus = mock 'terminus' - @terminus_class = mock 'terminus class' - Puppet::Indirector::Terminus.stubs(:terminus_class).with(:test, :foo).returns(@terminus_class) - end + it "should use the provided Puppet setting if told to do so" do + Puppet::Indirector::Terminus.stubs(:terminus_class).with(:test, :my_terminus).returns(mock("terminus_class2")) + Puppet.settings.expects(:value).with(:my_setting).returns("my_terminus") + @indirection.terminus_setting = :my_setting + @indirection.terminus_class.should equal(:my_terminus) + end - it "should create an instance of the chosen terminus class" do - @terminus_class.stubs(:new).returns(@terminus) - @indirection.terminus(:foo).should equal(@terminus) - end + it "should fail if the provided terminus class is not valid" do + Puppet::Indirector::Terminus.stubs(:terminus_class).with(:test, :nosuchclass).returns(nil) + proc { @indirection.terminus_class = :nosuchclass }.should raise_error(ArgumentError) + end - it "should allow the clearance of cached terminus instances" do - terminus1 = mock 'terminus1' - terminus2 = mock 'terminus2' - @terminus_class.stubs(:new).returns(terminus1, terminus2, ArgumentError) - @indirection.terminus(:foo).should equal(terminus1) - @indirection.class.clear_cache - @indirection.terminus(:foo).should equal(terminus2) + after do + @indirection.delete if defined? @indirection + end end - # Make sure it caches the terminus. - it "should return the same terminus instance each time for a given name" do - @terminus_class.stubs(:new).returns(@terminus) - @indirection.terminus(:foo).should equal(@terminus) - @indirection.terminus(:foo).should equal(@terminus) - end + describe "when specifying the terminus class to use" do + before do + @indirection = Puppet::Indirector::Indirection.new(mock('model'), :test) + @terminus = mock 'terminus' + @terminus_class = stub 'terminus class', :new => @terminus + end - it "should not create a terminus instance until one is actually needed" do - Puppet::Indirector.expects(:terminus).never - indirection = Puppet::Indirector::Indirection.new(mock('model'), :lazytest) - end + it "should allow specification of a terminus type" do + @indirection.should respond_to(:terminus_class=) + end - after do - @indirection.delete - Puppet::Indirector::Indirection.clear_cache - end -end + it "should fail to redirect if no terminus type has been specified" do + proc { @indirection.find("blah") }.should raise_error(Puppet::DevError) + end -describe Puppet::Indirector::Indirection, " when deciding whether to cache" do - before do - @indirection = Puppet::Indirector::Indirection.new(mock('model'), :test) - @terminus = mock 'terminus' - @terminus_class = mock 'terminus class' - @terminus_class.stubs(:new).returns(@terminus) - Puppet::Indirector::Terminus.stubs(:terminus_class).with(:test, :foo).returns(@terminus_class) - @indirection.terminus_class = :foo - end + it "should fail when the terminus class name is an empty string" do + proc { @indirection.terminus_class = "" }.should raise_error(ArgumentError) + end - it "should provide a method for setting the cache terminus class" do - @indirection.should respond_to(:cache_class=) - end + it "should fail when the terminus class name is nil" do + proc { @indirection.terminus_class = nil }.should raise_error(ArgumentError) + end - it "should fail to cache if no cache type has been specified" do - proc { @indirection.cache }.should raise_error(Puppet::DevError) - end + it "should fail when the specified terminus class cannot be found" do + Puppet::Indirector::Terminus.expects(:terminus_class).with(:test, :foo).returns(nil) + proc { @indirection.terminus_class = :foo }.should raise_error(ArgumentError) + end - it "should fail to set the cache class when the cache class name is an empty string" do - proc { @indirection.cache_class = "" }.should raise_error(ArgumentError) - end + it "should select the specified terminus class if a terminus class name is provided" do + Puppet::Indirector::Terminus.expects(:terminus_class).with(:test, :foo).returns(@terminus_class) + @indirection.terminus(:foo).should equal(@terminus) + end - it "should fail to set the cache class when the cache class name is nil" do - proc { @indirection.cache_class = nil }.should raise_error(ArgumentError) - end + it "should use the configured terminus class if no terminus name is specified" do + Puppet::Indirector::Terminus.stubs(:terminus_class).with(:test, :foo).returns(@terminus_class) + @indirection.terminus_class = :foo + @indirection.terminus().should equal(@terminus) + end - it "should fail to set the cache class when the specified cache class cannot be found" do - Puppet::Indirector::Terminus.expects(:terminus_class).with(:test, :foo).returns(nil) - proc { @indirection.cache_class = :foo }.should raise_error(ArgumentError) + after do + @indirection.delete if defined? @indirection + end end - after do - @indirection.delete - Puppet::Indirector::Indirection.clear_cache - end -end + describe "when a select_terminus hook is available" do + before do + @indirection = Puppet::Indirector::Indirection.new(mock('model'), :test) -describe Puppet::Indirector::Indirection do - before :each do - Puppet.settings.stubs(:value).with("test_terminus").returns("test_terminus") - @terminus_class = mock 'terminus_class' - @terminus = mock 'terminus' - @terminus_class.stubs(:new).returns(@terminus) - @cache = mock 'cache' - @cache_class = mock 'cache_class' - Puppet::Indirector::Terminus.stubs(:terminus_class).with(:test, :cache_terminus).returns(@cache_class) - Puppet::Indirector::Terminus.stubs(:terminus_class).with(:test, :test_terminus).returns(@terminus_class) - @indirection = Puppet::Indirector::Indirection.new(mock('model'), :test) - @indirection.terminus_class = :test_terminus - end + # And provide a select_terminus hook + @indirection.meta_def(:select_terminus) do |uri| + :other + end - describe Puppet::Indirector::Indirection, " when managing the cache terminus" do + @terminus = mock 'terminus' + @terminus_class = stub 'terminus class', :new => @terminus - it "should not create a cache terminus at initialization" do - # This is weird, because all of the code is in the setup. If we got - # new() called on the cache class, we'd get an exception here. - end + @other_terminus = mock 'other_terminus' + @other_terminus_class = stub 'other_terminus_class', :new => @other_terminus - it "should reuse the cache terminus" do - @cache_class.expects(:new).returns(@cache) - Puppet.settings.stubs(:value).with("test_cache").returns("cache_terminus") - @indirection.cache_class = :cache_terminus - @indirection.cache.should equal(@cache) - @indirection.cache.should equal(@cache) + @cache_terminus = mock 'cache_terminus' + @cache_terminus_class = stub 'cache_terminus_class', :new => @cache_terminus + + Puppet::Indirector::Terminus.stubs(:terminus_class).with(:test, :foo).returns(@terminus_class) + Puppet::Indirector::Terminus.stubs(:terminus_class).with(:test, :other).returns(@other_terminus_class) + Puppet::Indirector::Terminus.stubs(:terminus_class).with(:test, :cache).returns(@cache_terminus_class) + + # Set it to a default type. + @indirection.terminus_class = :foo + + @uri = "full://url/path" + @result = stub 'result', :expiration => 1, :expired? => false end - it "should remove the cache terminus when all other terminus instances are cleared" do - cache2 = mock 'cache2' - @cache_class.stubs(:new).returns(@cache, cache2) - @indirection.cache_class = :cache_terminus - @indirection.cache.should equal(@cache) - @indirection.clear_cache - @indirection.cache.should equal(cache2) + it "should use the terminus name provided by passing the key to the :select_terminus hook when finding instances" do + # Set up the expectation + @other_terminus.expects(:find).with(@uri).returns(@result) + + @indirection.find(@uri) end - end - describe Puppet::Indirector::Indirection, " when saving and using a cache" do + it "should pass all arguments to the :select_terminus hook" do + @indirection.expects(:select_terminus).with(@uri, :node => "johnny").returns(:other) + @other_terminus.stubs(:find) - before do - @indirection.cache_class = :cache_terminus - @cache_class.expects(:new).returns(@cache) - @name = "testing" - @instance = stub 'instance', :version => 5, :name => @name + @indirection.find(@uri, :node => "johnny") end - it "should not update the cache or terminus if the new object is not different" do - @cache.expects(:has_most_recent?).with(@name, 5).returns(true) - @indirection.save(@instance) + it "should pass the original key to the terminus rather than a modified key" do + # This is the same test as before + @other_terminus.expects(:find).with(@uri).returns(@result) + @indirection.find(@uri) end - it "should update the original and the cache if the cached object is different" do - @cache.expects(:has_most_recent?).with(@name, 5).returns(false) - @terminus.expects(:save).with(@instance) - @cache.expects(:save).with(@instance) - @indirection.save(@instance) + after do + @indirection.delete if defined? @indirection end end - - describe Puppet::Indirector::Indirection, " when finding and using a cache" do + describe "when managing terminus instances" do before do - @indirection.cache_class = :cache_terminus - @cache_class.expects(:new).returns(@cache) + @indirection = Puppet::Indirector::Indirection.new(mock('model'), :test) + @terminus = mock 'terminus' + @terminus_class = mock 'terminus class' + Puppet::Indirector::Terminus.stubs(:terminus_class).with(:test, :foo).returns(@terminus_class) end - it "should return the cached object if the cache is up to date" do - cached = mock 'cached object' + it "should create an instance of the chosen terminus class" do + @terminus_class.stubs(:new).returns(@terminus) + @indirection.terminus(:foo).should equal(@terminus) + end - name = "myobject" + it "should allow the clearance of cached terminus instances" do + terminus1 = mock 'terminus1' + terminus2 = mock 'terminus2' + @terminus_class.stubs(:new).returns(terminus1, terminus2, ArgumentError) + @indirection.terminus(:foo).should equal(terminus1) + @indirection.class.clear_cache + @indirection.terminus(:foo).should equal(terminus2) + end - @terminus.expects(:version).with(name).returns(1) - @cache.expects(:has_most_recent?).with(name, 1).returns(true) + # Make sure it caches the terminus. + it "should return the same terminus instance each time for a given name" do + @terminus_class.stubs(:new).returns(@terminus) + @indirection.terminus(:foo).should equal(@terminus) + @indirection.terminus(:foo).should equal(@terminus) + end - @cache.expects(:find).with(name).returns(cached) + it "should not create a terminus instance until one is actually needed" do + Puppet::Indirector.expects(:terminus).never + indirection = Puppet::Indirector::Indirection.new(mock('model'), :lazytest) + end - @indirection.find(name).should equal(cached) + after do + @indirection.delete + Puppet::Indirector::Indirection.clear_cache end + end - it "should return the original object if the cache is not up to date" do - real = stub 'real object', :version => 1 + describe "when deciding whether to cache" do + before do + @indirection = Puppet::Indirector::Indirection.new(mock('model'), :test) + @terminus = mock 'terminus' + @terminus_class = mock 'terminus class' + @terminus_class.stubs(:new).returns(@terminus) + Puppet::Indirector::Terminus.stubs(:terminus_class).with(:test, :foo).returns(@terminus_class) + @indirection.terminus_class = :foo + end - name = "myobject" + it "should provide a method for setting the cache terminus class" do + @indirection.should respond_to(:cache_class=) + end - @cache.stubs(:save) - @cache.expects(:has_most_recent?).with(name, 1).returns(false) - @terminus.expects(:version).with(name).returns(1) + it "should fail to cache if no cache type has been specified" do + proc { @indirection.cache }.should raise_error(Puppet::DevError) + end - @terminus.expects(:find).with(name).returns(real) + it "should fail to set the cache class when the cache class name is an empty string" do + proc { @indirection.cache_class = "" }.should raise_error(ArgumentError) + end - @indirection.find(name).should equal(real) + it "should fail to set the cache class when the cache class name is nil" do + proc { @indirection.cache_class = nil }.should raise_error(ArgumentError) end - it "should cache any newly returned objects" do - real = stub 'real object', :version => 1 + it "should fail to set the cache class when the specified cache class cannot be found" do + Puppet::Indirector::Terminus.expects(:terminus_class).with(:test, :foo).returns(nil) + proc { @indirection.cache_class = :foo }.should raise_error(ArgumentError) + end - name = "myobject" + after do + @indirection.delete + Puppet::Indirector::Indirection.clear_cache + end + end + + describe "when using a cache" do + before :each do + Puppet.settings.stubs(:value).with("test_terminus").returns("test_terminus") + @terminus_class = mock 'terminus_class' + @terminus = mock 'terminus' + @terminus_class.stubs(:new).returns(@terminus) + @cache = mock 'cache' + @cache_class = mock 'cache_class' + Puppet::Indirector::Terminus.stubs(:terminus_class).with(:test, :cache_terminus).returns(@cache_class) + Puppet::Indirector::Terminus.stubs(:terminus_class).with(:test, :test_terminus).returns(@terminus_class) + @indirection = Puppet::Indirector::Indirection.new(mock('model'), :test) + @indirection.terminus_class = :test_terminus + end - @terminus.expects(:version).with(name).returns(1) - @cache.expects(:has_most_recent?).with(name, 1).returns(false) + describe "and managing the cache terminus" do + it "should not create a cache terminus at initialization" do + # This is weird, because all of the code is in the setup. If we got + # new() called on the cache class, we'd get an exception here. + end - @terminus.expects(:find).with(name).returns(real) - @cache.expects(:save).with(real) + it "should reuse the cache terminus" do + @cache_class.expects(:new).returns(@cache) + Puppet.settings.stubs(:value).with("test_cache").returns("cache_terminus") + @indirection.cache_class = :cache_terminus + @indirection.cache.should equal(@cache) + @indirection.cache.should equal(@cache) + end - @indirection.find(name).should equal(real) + it "should remove the cache terminus when all other terminus instances are cleared" do + cache2 = mock 'cache2' + @cache_class.stubs(:new).returns(@cache, cache2) + @indirection.cache_class = :cache_terminus + @indirection.cache.should equal(@cache) + @indirection.clear_cache + @indirection.cache.should equal(cache2) + end + end + + describe "and saving" do + end + + describe "and finding" do + end + + after :each do + @indirection.delete + Puppet::Indirector::Indirection.clear_cache end - end - - after :each do - @indirection.delete - Puppet::Indirector::Indirection.clear_cache end end |