On Apr 13, 2009, at 10:50 AM, Ethan Rowe wrote:

>
> Puppet::Util::Queue provides queue client mix-in behaviors that  
> enable easy queue client management for consumer classes.  Some  
> relevant behaviors include:
> * standard Puppet instance loader behavior for loading queue client  
> modules on-demand based on the client module specified by symbolic  
> name
> * singleton registry of known queue client types (based on symbol-to- 
> class mappings from the instance loading behavior)
> * simple interface for working with an actual queue client instance
>
> Puppet::Util::Queue::Stomp wraps the Stomp::Client class to provide  
> an initial queue client option supporting the Stomp messaging  
> protocol.  This defines the interface for all Puppet queue client  
> plugins going forward.
>
> Signed-off-by: Ethan Rowe <[email protected]>
> ---
> lib/puppet/util/queue.rb       |  102 +++++++++++++++++++++++++++++++ 
> +++++++++
> lib/puppet/util/queue/stomp.rb |   30 ++++++++++++
> spec/unit/util/queue.rb        |   95 +++++++++++++++++++++++++++++++ 
> ++++++
> spec/unit/util/queue/stomp.rb  |   62 ++++++++++++++++++++++++
> 4 files changed, 289 insertions(+), 0 deletions(-)
> create mode 100644 lib/puppet/util/queue.rb
> create mode 100644 lib/puppet/util/queue/stomp.rb
> create mode 100755 spec/unit/util/queue.rb
> create mode 100755 spec/unit/util/queue/stomp.rb
>
> diff --git a/lib/puppet/util/queue.rb b/lib/puppet/util/queue.rb
> new file mode 100644
> index 0000000..3724bab
> --- /dev/null
> +++ b/lib/puppet/util/queue.rb
> @@ -0,0 +1,102 @@
> +
> +require 'puppet/indirector'
> +require 'puppet/util/instance_loader'
> +
> +# Implements a message queue client type plugin registry for use by  
> the indirector facility.
> +# Client modules for speaking a particular protocol (e.g.  
> Stomp::Client for Stomp message
> +# brokers, Memcached for Starling and Sparrow, etc.) register  
> themselves with this module.
> +#
> +# Client classes are expected to live under the Puppet::Util::Queue  
> namespace and corresponding
> +# directory; the attempted use of a client via its typename (see  
> below) will cause Puppet::Util::Queue
> +# to attempt to load the corresponding plugin if it is not yet  
> loaded.  The client class registers itself
> +# with Puppet::Util::Queue and should use the same type name as the  
> autloader expects for the plugin file.
> +#   class Puppet::Util::Queue::SpecialMagicalClient <  
> Messaging::SpecialMagic
> +#       ...
> +#       Puppet::Util::Queue.register_queue_type_class(self)
> +#   end
> +#
> +# This module reduces the rightmost segment of the class name into  
> a pretty symbol that will
> +# serve as the queuing client's name.  Which means that the  
> "SpecialMagicalClient" above will
> +# be named <em>:special_magical_client</em> within the registry.
> +#
> +# Another class/module may mix-in this module, and may then make  
> use of the registered clients.
> +#   class Queue::Fue
> +#       # mix it in at the class object level rather than instance  
> level
> +#       extend ::Puppet::Util::Queue
> +#
> +#       # specify the default client type to use.
> +#       self.queue_type_default = :special_magical_type
> +#   end
> +#
> +# Queue::Fue instances can get a message queue client through the  
> registry through the mixed-in method
> +# +client+, which will return a class-wide singleton client  
> instance, determined by +client_class+.
> +#
> +# The client plugins are expected to implement an interface similar  
> to that of Stomp::Client:
> +# * <tt>new()</tt> should return a connected, ready-to-go client  
> instance.  Note that no arguments are passed in.
> +# * <tt>send_message(queue, message)</tt> should send the _message_  
> to the specified _queue_.
> +# * <tt>subscribe(queue)</tt> _block_ subscribes to _queue_ and  
> executes _block_ upon receiving a message.
> +# * _queue_ names are simple names independent of the message  
> broker or client library.  No "/queue/" prefixes like in  
> Stomp::Client.
> +module Puppet::Util::Queue
> +    extend Puppet::Util::InstanceLoader
> +    attr_accessor :queue_type_default

I just tried loading puppetqd, and I don't think this setting does  
what you want.  The setting was actually set on  
Puppet::Indirector::Queue, but it's an instance variable, which means  
it's not passed down to subclasses, thus  
Puppet::Indirector::Queue::Catalog, which actually needs the value,  
couldn't get it.

This makes more sense as a constant, with a hard-coded default.  Or,  
really, it makes the most sense as an actual setting, with a default  
value but that is easily tunable.  See below for more.

>
> +    instance_load :queue_clients, 'puppet/util/queue'
> +
> +    # Adds a new class/queue-type pair to the registry.  The _type_  
> argument is optional; if not provided,
> +    # _type_ defaults to a lowercased, underscored symbol  
> programmatically derived from the rightmost
> +    # namespace of <em>klass.name</em>.
> +    #
> +    #   # register with default name +:you+
> +    #   register_queue_type(Foo::You)
> +    #
> +    #   # register with explicit queue type name +:myself+
> +    #   register_queue_type(Foo::Me, :myself)
> +    #
> +    # If the type is already registered, an exception is thrown.   
> No checking is performed of _klass_,
> +    # however; a given class could be registered any number of  
> times, as long as the _type_ differs with
> +    # each registration.
> +    def self.register_queue_type(klass, type = nil)
> +        type ||= queue_type_from_class(klass)
> +        raise Puppet::Error, "Queue type %s is already registered"  
> % type.to_s if instance_hash(:queue_clients).include?(type)
> +        instance_hash(:queue_clients)[type] = klass
> +    end
> +
> +    # Given a queue type symbol, returns the associated +Class+  
> object.  If the queue type is unknown
> +    # (meaning it hasn't been registered with this module), an  
> exception is thrown.
> +    def self.queue_type_to_class(type)
> +        c = loaded_instance :queue_clients, type
> +        raise Puppet::Error, "Queue type %s is unknown." % type  
> unless c
> +        c
> +    end
> +
> +    # Given a class object _klass_, returns the programmatic  
> default queue type name symbol for _klass_.
> +    # The algorithm is as shown in earlier examples; the last  
> namespace segment of _klass.name_ is taken
> +    # and converted from mixed case to underscore-separated  
> lowercase, and interned.
> +    #   queue_type_from_class(Foo) -> :foo
> +    #   queue_type_from_class(Foo::Too) -> :too
> +    #   queue_type_from_class(Foo::ForYouTwo) -> :for_you_too
> +    #
> +    # The implicit assumption here, consistent with Puppet's  
> approach to plugins in general,
> +    # is that all your client modules live in the same namespace,  
> such that reduction to
> +    # a flat namespace of symbols is reasonably safe.
> +    def self.queue_type_from_class(klass)
> +        # convert last segment of classname from studly caps to  
> lower case with underscores, and symbolize
> +        klass.name.split('::').pop.sub(/^[A-Z]/) {|c|  
> c.downcase}.gsub(/[A-Z]/) {|c| '_' + c.downcase }.intern
> +    end
> +
> +    # The class object for the client to be used, determined by  
> queue configuration
> +    # settings and known queue client types.
> +    # Looked to the <tt>:queue_client</tt> configuration entry in  
> the running application for
> +    # the default queue type to use, and fails over to  
> +queue_type_default+ if the configuration
> +    # information is not present.
> +    def client_class
> +          
> Puppet::Util::Queue.queue_type_to_class(Puppet[:queue_client] ||  
> queue_type_default)

AFAICT, this always fails in the current installation, because there's  
no queue_type_default set in the Catalog Queue class and there's  
no :queue_client setting anywhere.

This should all be switched to just use something like  
Puppet[:queue_type], IMO.

>
> +        #  
> Puppet::Util::Queue.queue_type_to_class(Puppet[:queue_client]  
> || :stomp)

And, of course, this can be removed. :)

>
> +    end
> +
> +    # Returns (instantiating as necessary) the singleton queue  
> client instance, according to the
> +    # client_class.  No arguments go to the client class  
> constructor, meaning its up to the client class
> +    # to know how to determine its queue message source (presumably  
> through Puppet configuration data).
> +    def client
> +        @client ||= client_class.new
> +    end
> +end
> diff --git a/lib/puppet/util/queue/stomp.rb b/lib/puppet/util/queue/ 
> stomp.rb
> new file mode 100644
> index 0000000..6f845c3
> --- /dev/null
> +++ b/lib/puppet/util/queue/stomp.rb
> @@ -0,0 +1,30 @@
> +require 'puppet/util/queue'
> +require 'stomp'
> +
> +# Implements the Ruby Stomp client as a queue type within the  
> Puppet::Indirector::Queue::Client
> +# registry, for use with the <tt>:queue</tt> indirection terminus  
> type.
> +#
> +# Looks to <tt>Puppet[:queue_source]</tt> for the sole argument to  
> the underlying Stomp::Client constructor;
> +# consequently, for this client to work, <tt>Puppet[:queue_source]</ 
> tt> must use the Stomp::Client URL-like
> +# syntax for identifying the Stomp message broker: <em>login:[email protected] 
> </em>
> +class Puppet::Util::Queue::Stomp
> +    attr_accessor :stomp_client
> +
> +    def initialize
> +        self.stomp_client =  
> Stomp::Client.new( Puppet[:queue_source] )

Is that setting defined anywhere?  I don't see it.

>
> +    end
> +
> +    def send_message(target, msg)
> +        stomp_client.send(stompify_target(target), msg)
> +    end
> +
> +    def subscribe(target)
> +        stomp_client.subscribe(stompify_target(target)) {| 
> stomp_message| yield(stomp_message.body)}
> +    end
> +
> +    def stompify_target(target)
> +        '/queue/' + target.to_s
> +    end
> +
> +    Puppet::Util::Queue.register_queue_type(self, :stomp)
> +end
> diff --git a/spec/unit/util/queue.rb b/spec/unit/util/queue.rb
> new file mode 100755
> index 0000000..525e623
> --- /dev/null
> +++ b/spec/unit/util/queue.rb
> @@ -0,0 +1,95 @@
> +#!/usr/bin/env ruby
> +
> +require File.dirname(__FILE__) + '/../../spec_helper'
> +require 'puppet/util/queue'
> +require 'spec/mocks'
> +
> +def make_test_client_class(n)
> +    c = Class.new do
> +        class <<self
> +            attr_accessor :name
> +            def to_s
> +                name
> +            end
> +        end
> +    end
> +    c.name = n
> +    c
> +end
> +
> +mod = Puppet::Util::Queue
> +client_classes = { :default =>  
> make_test_client_class('Bogus::Default'), :setup =>  
> make_test_client_class('Bogus::Setup') }
> +mod.register_queue_type(client_classes[:default], :default)
> +mod.register_queue_type(client_classes[:setup], :setup)
> +
> +describe Puppet::Util::Queue do
> +    before :each do
> +        @class = Class.new do
> +            extend mod
> +            self.queue_type_default = :default
> +        end
> +    end
> +
> +    context 'when determining a type name from a class' do
> +        it 'should handle a simple one-word class name' do
> +             
> mod.queue_type_from_class(make_test_client_class('Foo')).should  
> == :foo
> +        end
> +
> +        it 'should handle a simple two-word class name' do
> +             
> mod.queue_type_from_class(make_test_client_class('FooBar')).should  
> == :foo_bar
> +        end
> +
> +        it 'should handle a two-part class name with one  
> terminating word' do
> +             
> mod.queue_type_from_class(make_test_client_class('Foo::Bar')).should  
> == :bar
> +        end
> +
> +        it 'should handle a two-part class name with two  
> terminating words' do
> +             
> mod 
> .queue_type_from_class(make_test_client_class('Foo::BarBah')).should  
> == :bar_bah
> +        end
> +    end
> +
> +    context 'when registering a queue client class' do
> +        c = make_test_client_class('Foo::Bogus')
> +        it 'uses the proper default name logic when type is  
> unspecified' do
> +            mod.register_queue_type(c)
> +            mod.queue_type_to_class(:bogus).should == c
> +        end
> +
> +        it 'uses an explicit type name when provided' do
> +            mod.register_queue_type(c, :aardvark)
> +            mod.queue_type_to_class(:aardvark).should == c
> +        end
> +
> +        it 'throws an exception when type names conflict' do
> +             
> mod.register_queue_type( make_test_client_class('Conflict') )
> +            lambda  
> { mod.register_queue_type( c, :conflict) }.should raise_error
> +        end
> +
> +        it 'handle multiple, non-conflicting registrations' do
> +            a = make_test_client_class('TestA')
> +            b = make_test_client_class('TestB')
> +            mod.register_queue_type(a)
> +            mod.register_queue_type(b)
> +            mod.queue_type_to_class(:test_a).should == a
> +            mod.queue_type_to_class(:test_b).should == b
> +        end
> +
> +        it 'throws an exception when type name is unknown' do
> +            lambda { mod.queue_type_to_class(:nope) }.should  
> raise_error
> +        end
> +    end
> +
> +    context 'when determining client type' do
> +        it 'returns client class based on queue_type_default' do
> +            Puppet.settings.stubs(:value).returns(nil)
> +            @class.client_class.should == client_classes[:default]
> +            @class.client.class.should == client_classes[:default]
> +        end
> +
> +        it 'prefers settings variable for client class when  
> specified' do
> +             
> Puppet.settings.stubs(:value).with(:queue_client).returns(:setup)
> +            @class.client_class.should == client_classes[:setup]
> +            @class.client.class.should == client_classes[:setup]
> +        end
> +    end

Seems like the tests mix between assertions with and without  
'should'.   We've never really had a policy on that, but consistency  
would at least be good, I think.

>
> +end
> diff --git a/spec/unit/util/queue/stomp.rb b/spec/unit/util/queue/ 
> stomp.rb
> new file mode 100755
> index 0000000..c4d8b76
> --- /dev/null
> +++ b/spec/unit/util/queue/stomp.rb
> @@ -0,0 +1,62 @@
> +#!/usr/bin/env ruby
> +
> +require File.dirname(__FILE__) + '/../../../spec_helper'
> +require 'puppet/util/queue'
> +
> +describe Puppet::Util::Queue do
> +    it 'should load :stomp client appropriately' do
> +        Puppet.settings.stubs(:value).returns 'faux_queue_source'
> +        Puppet::Util::Queue.queue_type_to_class(:stomp).name.should  
> == 'Puppet::Util::Queue::Stomp'
> +    end
> +end

As mentioned above, this behaviour doesn't really help, because it's  
an instance variable on an abstract class.

>
> +describe 'Puppet::Util::Queue::Stomp' do
> +    before :all do
> +        class Stomp::Client
> +            include Mocha::Standalone
> +            attr_accessor :queue_source
> +
> +            def send(q, m)
> +                'To %s: %s' % [q, m]
> +            end
> +
> +            def subscribe(q)
> +                yield(stub(:body => 'subscribe: %s' % q))
> +            end
> +
> +            def initialize(s)
> +                self.queue_source = s
> +            end
> +        end
> +    end
> +
> +    before :each do
> +        Puppet.settings.stubs(:value).returns 'faux_queue_source'
> +    end
> +
> +    it 'should make send function like core Ruby instead of stomp  
> client send method' do
> +        o = Puppet::Util::Queue::Stomp.new
> +        o.expects(:pants).with('foo').once
> +        o.send(:pants, 'foo')
> +    end
> +
> +    it 'should be registered with Puppet::Util::Queue as :stomp  
> type' do
> +        Puppet::Util::Queue.queue_type_to_class(:stomp).should ==  
> Puppet::Util::Queue::Stomp
> +    end
> +
> +    it 'should initialize using Puppet[:queue_source] for  
> configuration' do
> +        o = Puppet::Util::Queue::Stomp.new
> +        o.stomp_client.queue_source.should == 'faux_queue_source'
> +    end
> +
> +    it 'should transform the simple queue name to "/queue/ 
> <queue_name>"' do
> +         
> Puppet::Util::Queue::Stomp.new.stompify_target('blah').should == '/ 
> queue/blah'
> +    end
> +
> +    it 'should transform the queue name properly and pass along to  
> superclass for send and subscribe' do
> +        o = Puppet::Util::Queue::Stomp.new
> +        o.send_message('fooqueue', 'Smite!').should == 'To /queue/ 
> fooqueue: Smite!'
> +        o.subscribe('moodew') {|obj| obj}.should == 'subscribe: / 
> queue/moodew'
> +    end
> +end
> +
> -- 
> 1.5.5.1
>
>
> >


-- 
Nonreciprocal Laws of Expectations:
     Negative expectations yield negative results. Positive expectations
     yield negative results.
---------------------------------------------------------------------
Luke Kanies | http://reductivelabs.com | http://madstop.com


--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups 
"Puppet Developers" group.
To post to this group, send email to [email protected]
To unsubscribe from this group, send email to 
[email protected]
For more options, visit this group at 
http://groups.google.com/group/puppet-dev?hl=en
-~----------~----~----~----~------~----~------~--~---

Reply via email to