diff options
author | Luke Kanies <luke@madstop.com> | 2007-10-06 17:18:30 -0500 |
---|---|---|
committer | Luke Kanies <luke@madstop.com> | 2007-10-06 17:18:30 -0500 |
commit | fc9c850414baff17dc97b0184f34e58b4bec5785 (patch) | |
tree | 5a39e0be8cbbdbef3aaf1b06d7d71d835a4e4d21 | |
parent | cdaad286b1fe5fc3c1ab363c890bb6a8a752c9b5 (diff) | |
download | puppet-fc9c850414baff17dc97b0184f34e58b4bec5785.tar.gz puppet-fc9c850414baff17dc97b0184f34e58b4bec5785.tar.xz puppet-fc9c850414baff17dc97b0184f34e58b4bec5785.zip |
Adding support for versions and freshness-checking
to the indirection layers. This should hopefully
enable the different application models we need in
our different executables.
-rw-r--r-- | lib/puppet/indirector.rb | 4 | ||||
-rw-r--r-- | lib/puppet/indirector/indirection.rb | 21 | ||||
-rw-r--r-- | lib/puppet/indirector/terminus.rb | 37 | ||||
-rwxr-xr-x | spec/unit/indirector/indirection.rb | 162 | ||||
-rwxr-xr-x | spec/unit/indirector/indirector.rb | 10 | ||||
-rwxr-xr-x | spec/unit/indirector/terminus.rb | 53 |
6 files changed, 234 insertions, 53 deletions
diff --git a/lib/puppet/indirector.rb b/lib/puppet/indirector.rb index 7ceaa43e2..a2eb41763 100644 --- a/lib/puppet/indirector.rb +++ b/lib/puppet/indirector.rb @@ -62,6 +62,10 @@ module Puppet::Indirector end module InstanceMethods + # Make it easy for the model to set versions, + # which are used for caching and such. + attr_accessor :version + # these become instance methods def save(*args) self.class.indirection.save(self, *args) diff --git a/lib/puppet/indirector/indirection.rb b/lib/puppet/indirector/indirection.rb index 3a6284877..cd71c4d66 100644 --- a/lib/puppet/indirector/indirection.rb +++ b/lib/puppet/indirector/indirection.rb @@ -90,12 +90,18 @@ class Puppet::Indirector::Indirection end end - def find(*args) - terminus.find(*args) + def find(key, *args) + if cache? and terminus.fresh?(key, cache.version(key)) + return cache.find(key, *args) + end + if result = terminus.find(key, *args) + result.version ||= Time.now.utc + cache.save(result, *args) if cache? + return result + end end def destroy(*args) - cache.destroy(*args) if cache? terminus.destroy(*args) end @@ -104,9 +110,12 @@ class Puppet::Indirector::Indirection end # these become instance methods - def save(*args) - cache.save(*args) if cache? - terminus.save(*args) + def save(instance, *args) + instance.version ||= Time.now.utc + dest = cache? ? cache : terminus + return if dest.fresh?(instance.name, instance.version) + cache.save(instance, *args) if cache? + terminus.save(instance, *args) end private diff --git a/lib/puppet/indirector/terminus.rb b/lib/puppet/indirector/terminus.rb index 3e0ea447c..5aeb4c6d7 100644 --- a/lib/puppet/indirector/terminus.rb +++ b/lib/puppet/indirector/terminus.rb @@ -95,25 +95,50 @@ class Puppet::Indirector::Terminus end end + # Is this instance fresh? Meaning, is the version we have equivalent or better + # than the version being asked about + def fresh?(key, vers) + raise Puppet::DevError.new("Cannot check update status when no 'version' method is defined") unless respond_to?(:version) + + if existing_version = version(key) + existing_version >= vers + else + false + end + end + + def indirection + self.class.indirection + end + def initialize if self.class.abstract_terminus? raise Puppet::DevError, "Cannot create instances of abstract terminus types" end end - def terminus_type - self.class.terminus_type + def model + self.class.model end def name self.class.name end - def model - self.class.model + def terminus_type + self.class.terminus_type end - def indirection - self.class.indirection + # Provide a default method for retrieving an instance's version. + # By default, just find the resource and get its version. Individual + # terminus types can override this method to provide custom definitions of + # 'versions'. + def version(name) + raise Puppet::DevError.new("Cannot retrieve an instance's version without a :find method") unless respond_to?(:find) + if instance = find(name) + instance.version + else + nil + end end end diff --git a/spec/unit/indirector/indirection.rb b/spec/unit/indirector/indirection.rb index 4311c88bf..4fcf5fc11 100755 --- a/spec/unit/indirector/indirection.rb +++ b/spec/unit/indirector/indirection.rb @@ -7,28 +7,62 @@ require 'puppet/indirector' describe Puppet::Indirector::Indirection do before do @indirection = Puppet::Indirector::Indirection.new(mock('model'), :test) - @terminus = mock 'terminus' + @terminus = stub 'terminus', :fresh? => false @indirection.stubs(:terminus).returns(@terminus) + @instance = stub 'instance', :version => nil, :version= => nil, :name => "whatever" + @name = :mything + end + + 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 end it "should handle lookups of a model instance by letting the appropriate terminus perform the lookup" do - @terminus.expects(:find).with(:mything).returns(:whev) - @indirection.find(:mything).should == :whev + @terminus.expects(:find).with(@name).returns(@instance) + @indirection.find(@name).should == @instance end it "should handle removing model instances from a terminus letting the appropriate terminus remove the instance" do - @terminus.expects(:destroy).with(:mything).returns(:whev) - @indirection.destroy(:mything).should == :whev + @terminus.expects(:destroy).with(@name).returns(@instance) + @indirection.destroy(@name).should == @instance end it "should handle searching for model instances by letting the appropriate terminus find the matching instances" do - @terminus.expects(:search).with(:mything).returns(:whev) - @indirection.search(:mything).should == :whev + @terminus.expects(:search).with(@name).returns(@instance) + @indirection.search(@name).should == @instance end it "should handle storing a model instance by letting the appropriate terminus store the instance" do - @terminus.expects(:save).with(:mything).returns(:whev) - @indirection.save(:mything).should == :whev + @terminus.expects(:save).with(@instance).returns(@instance) + @indirection.save(@instance).should == @instance + end + + 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 + + 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 + + # 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? + end + @terminus.stubs(:save) + @indirection.save(@instance) end after do @@ -189,33 +223,14 @@ describe Puppet::Indirector::Indirection, " when deciding whether to cache" do proc { @indirection.cache_class = :foo }.should raise_error(ArgumentError) end - it "should not use a cache if there no cache setting" do - @indirection.expects(:cache).never - @terminus.stubs(:save) - @indirection.save(:whev) - end - - it "should use a cache if a cache was configured" do - cache = mock 'cache' - cache.expects(:save).with(:whev) - - cache_class = mock 'cache class' - cache_class.expects(:new).returns(cache) - Puppet::Indirector::Terminus.stubs(:terminus_class).with(:mycache, :test).returns(cache_class) - - @indirection.cache_class = :mycache - @terminus.stubs(:save) - @indirection.save(:whev) - end - after do @indirection.delete Puppet::Indirector::Indirection.clear_cache end end -describe Puppet::Indirector::Indirection, " when using a cache" do - before do +module IndirectionCaching + def setup Puppet.settings.stubs(:value).with("test_terminus").returns("test_terminus") @terminus_class = mock 'terminus_class' @terminus = mock 'terminus' @@ -228,13 +243,14 @@ describe Puppet::Indirector::Indirection, " when using a cache" do @indirection.terminus_class = :test_terminus end - it "should copy all writing indirection calls to the cache terminus" do - @cache_class.expects(:new).returns(@cache) - @indirection.cache_class = :cache_terminus - @cache.expects(:save).with(:whev) - @terminus.stubs(:save) - @indirection.save(:whev) + def teardown + @indirection.delete + Puppet::Indirector::Indirection.clear_cache end +end + +describe Puppet::Indirector::Indirection, " when managing the cache terminus" do + include IndirectionCaching 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 @@ -257,9 +273,77 @@ describe Puppet::Indirector::Indirection, " when using a cache" do @indirection.clear_cache @indirection.cache.should equal(cache2) end +end - after do - @indirection.delete - Puppet::Indirector::Indirection.clear_cache +describe Puppet::Indirector::Indirection, " when saving and using a cache" do + include IndirectionCaching + + before do + @indirection.cache_class = :cache_terminus + @cache_class.expects(:new).returns(@cache) + @name = "testing" + @instance = stub 'instance', :version => 5, :name => @name + end + + it "should not update the cache or terminus if the new object is not different" do + @cache.expects(:fresh?).with(@name, 5).returns(true) + @indirection.save(@instance) + end + + it "should update the original and the cache if the cached object is different" do + @cache.expects(:fresh?).with(@name, 5).returns(false) + @terminus.expects(:save).with(@instance) + @cache.expects(:save).with(@instance) + @indirection.save(@instance) + end +end + +describe Puppet::Indirector::Indirection, " when finding and using a cache" do + include IndirectionCaching + + before do + @indirection.cache_class = :cache_terminus + @cache_class.expects(:new).returns(@cache) + end + + it "should return the cached object if the cache is up to date" do + cached = mock 'cached object' + + name = "myobject" + + @cache.expects(:version).with(name).returns(1) + @terminus.expects(:fresh?).with(name, 1).returns(true) + + @cache.expects(:find).with(name).returns(cached) + + @indirection.find(name).should equal(cached) + end + + it "should return the original object if the cache is not up to date" do + real = stub 'real object', :version => 1 + + name = "myobject" + + @cache.expects(:version).with(name).returns(1) + @cache.stubs(:save) + @terminus.expects(:fresh?).with(name, 1).returns(false) + + @terminus.expects(:find).with(name).returns(real) + + @indirection.find(name).should equal(real) + end + + it "should cache any newly returned objects" do + real = stub 'real object', :version => 1 + + name = "myobject" + + @cache.expects(:version).with(name).returns(1) + @terminus.expects(:fresh?).with(name, 1).returns(false) + + @terminus.expects(:find).with(name).returns(real) + @cache.expects(:save).with(real) + + @indirection.find(name).should equal(real) end end diff --git a/spec/unit/indirector/indirector.rb b/spec/unit/indirector/indirector.rb index 390907ca2..78c8c614a 100755 --- a/spec/unit/indirector/indirector.rb +++ b/spec/unit/indirector/indirector.rb @@ -64,6 +64,16 @@ describe Puppet::Indirector, " when redirecting a model" do @indirection = @thingie.send(:indirects, :test) end + it "should give the model the ability set a version" do + thing = @thingie.new + thing.should respond_to(:version=) + end + + it "should give the model the ability retrieve a version" do + thing = @thingie.new + thing.should respond_to(:version) + end + it "should give the model the ability to lookup a model instance by letting the indirection perform the lookup" do @indirection.expects(:find) @thingie.find diff --git a/spec/unit/indirector/terminus.rb b/spec/unit/indirector/terminus.rb index 44180cf4b..2488a1a67 100755 --- a/spec/unit/indirector/terminus.rb +++ b/spec/unit/indirector/terminus.rb @@ -1,3 +1,5 @@ +#!/usr/bin/env ruby + require File.dirname(__FILE__) + '/../../spec_helper' require 'puppet/defaults' require 'puppet/indirector' @@ -200,8 +202,8 @@ describe Puppet::Indirector::Terminus, " when creating terminus classes" do end end -describe Puppet::Indirector::Terminus, " when a terminus instance" do - before do +module TerminusInstanceTesting + def setup Puppet::Indirector::Terminus.stubs(:register_terminus_class) @indirection = stub 'indirection', :name => :myyaml, :register_terminus_type => nil Puppet::Indirector::Indirection.stubs(:instance).with(:my_stuff).returns(@indirection) @@ -218,6 +220,10 @@ describe Puppet::Indirector::Terminus, " when a terminus instance" do @terminus_class.name = :test @terminus = @terminus_class.new end +end + +describe Puppet::Indirector::Terminus, " when a terminus instance" do + include TerminusInstanceTesting it "should return the class's name as its name" do @terminus.name.should == :test @@ -236,3 +242,46 @@ describe Puppet::Indirector::Terminus, " when a terminus instance" do @terminus.model.should == :yay end end + +describe Puppet::Indirector::Terminus, " when managing indirected instances" do + include TerminusInstanceTesting + + it "should support comparing an instance's version with the terminus's version using just the instance's key" do + @terminus.should respond_to(:fresh?) + end + + it "should fail if the :version method has not been overridden and no :find method is available" do + proc { @terminus.version('yay') }.should raise_error(Puppet::DevError) + end + + it "should use a found instance's version by default" do + name = 'instance' + instance = stub name, :version => 2 + @terminus.expects(:find).with(name).returns(instance) + @terminus.version(name).should == 2 + end + + it "should return nil as the version if no instance can be found" do + name = 'instance' + @terminus.expects(:find).with(name).returns(nil) + @terminus.version(name).should be_nil + end + + it "should consider an instance fresh if its version is more recent than the version provided" do + name = "yay" + @terminus.expects(:version).with(name).returns(5) + @terminus.fresh?(name, 4).should be_true + end + + it "should consider an instance fresh if its version is equal to the version provided" do + name = "yay" + @terminus.expects(:version).with(name).returns(5) + @terminus.fresh?(name, 5).should be_true + end + + it "should consider an instance not fresh if the provided version is more recent than its version" do + name = "yay" + @terminus.expects(:version).with(name).returns(4) + @terminus.fresh?(name, 5).should be_false + end +end |