bunnicula 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ The MIT LICENSE
2
+
3
+ Copyright (c) 2010 Nathan Stults
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,30 @@
1
+ == Overview
2
+
3
+ == Motivation
4
+
5
+ == Approach
6
+
7
+ == Install
8
+
9
+ Install the gem with:
10
+
11
+ gem install bunnicula
12
+ or
13
+ sudo gem install bunnicula
14
+
15
+
16
+ == An Example
17
+
18
+ == Limitations
19
+ * Cubicle cannot currently cause child documents to be emitted in the map reduce. This is a pretty big limitation, and will be resolved shortly.
20
+ * Documentation is non-existent. This is being worked on (head that one before?)
21
+ * Test coverage is OK, but the tests could be better organized
22
+ * Code needs to be modularized a bit, main classes are pretty hairy at the moment
23
+
24
+
25
+ == Bugs/Issues
26
+ Please report them {on github}[http://github.com/plasticlizard/bunnicula/issues].
27
+
28
+ == Links
29
+
30
+ == Todo
data/Rakefile ADDED
@@ -0,0 +1,45 @@
1
+ require File.dirname(__FILE__) + '/config/boot'
2
+
3
+ require 'rake'
4
+ require 'daemon_kit/tasks'
5
+ require 'rake/testtask'
6
+
7
+ Dir[File.join(File.dirname(__FILE__), 'tasks/*.rake')].each { |rake| load rake }
8
+
9
+ Rake::TestTask.new do |t|
10
+ t.libs << 'libs' << 'test'
11
+ t.pattern = 'test/**/*_test.rb'
12
+ t.verbose = false
13
+ end
14
+
15
+ begin
16
+ require 'jeweler'
17
+ require File.dirname(__FILE__) + "/lib/bunnicula/version"
18
+
19
+ Jeweler::Tasks.new do |s|
20
+ s.name = "bunnicula"
21
+ s.version = Bunnicula::VERSION
22
+ s.summary = "A very simple relay for moving messages from a local broker to a remote broker"
23
+ s.description = "A very simple relay for moving messages from a local broker to a remote broker"
24
+ s.email = "hereiam@sonic.net"
25
+ s.homepage = "http://github.com/PlasticLizard/bunnicula"
26
+ s.authors = ["Nathan Stults"]
27
+ s.has_rdoc = false #=>Should be true, someday
28
+ s.extra_rdoc_files = ["README.rdoc", "LICENSE.txt"]
29
+ s.files = FileList["[A-Z]*", "{bin,lib,config,libexec,test}/**/*"]
30
+
31
+ s.add_dependency('daemon-kit', '0.1.7.12')
32
+ s.add_dependency('bunny', '0.6.0')
33
+ s.add_dependency('amqp', '0.6.7')
34
+
35
+ s.add_development_dependency('shoulda', '2.11.1')
36
+ end
37
+
38
+ Jeweler::GemcutterTasks.new
39
+
40
+ rescue LoadError => ex
41
+ puts "Jeweler not available. Install it for jeweler-related tasks with: sudo gem install jeweler"
42
+
43
+ end
44
+
45
+ task :default => :test
data/Relayfile ADDED
@@ -0,0 +1,9 @@
1
+ #Bunnicula.bite {
2
+ # victim "localhost"
3
+ #
4
+ # transfusion_to("remote-host") {
5
+ # relay "source_exchange" do
6
+ # to "target_exchange"
7
+ # end
8
+ # }
9
+ #}
data/bin/bunnicula ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Stub executable for bunnicula
4
+
5
+ require File.dirname(__FILE__) + '/../config/environment'
6
+
7
+ DaemonKit::Application.exec( DAEMON_ROOT + '/libexec/bunnicula-daemon.rb' )
data/bunnicula.gemspec ADDED
@@ -0,0 +1,84 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{bunnicula}
8
+ s.version = "0.1.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Nathan Stults"]
12
+ s.date = %q{2010-07-09}
13
+ s.default_executable = %q{bunnicula}
14
+ s.description = %q{A very simple relay for moving messages from a local broker to a remote broker}
15
+ s.email = %q{hereiam@sonic.net}
16
+ s.executables = ["bunnicula"]
17
+ s.extra_rdoc_files = [
18
+ "LICENSE.txt"
19
+ ]
20
+ s.files = [
21
+ "LICENSE.txt",
22
+ "README",
23
+ "Rakefile",
24
+ "Relayfile",
25
+ "bin/bunnicula",
26
+ "bunnicula.gemspec",
27
+ "config/arguments.rb",
28
+ "config/boot.rb",
29
+ "config/environment.rb",
30
+ "config/environments/development.rb",
31
+ "config/environments/production.rb",
32
+ "config/environments/test.rb",
33
+ "config/post-daemonize/readme",
34
+ "config/pre-daemonize/bunnicula.rb",
35
+ "config/pre-daemonize/readme",
36
+ "lib/bunnicula.rb",
37
+ "lib/bunnicula/amqp.rb",
38
+ "lib/bunnicula/bunny_farm.rb",
39
+ "lib/bunnicula/dsl_base.rb",
40
+ "lib/bunnicula/exchange.rb",
41
+ "lib/bunnicula/rabbit.rb",
42
+ "lib/bunnicula/relay.rb",
43
+ "lib/bunnicula/support.rb",
44
+ "lib/bunnicula/vampire_rabbit.rb",
45
+ "lib/bunnicula/version.rb",
46
+ "libexec/bunnicula-daemon.rb",
47
+ "test/config/Relayfile.rb",
48
+ "test/lib/bunnicula_test.rb",
49
+ "test/test_helper.rb"
50
+ ]
51
+ s.homepage = %q{http://github.com/PlasticLizard/bunnicula}
52
+ s.rdoc_options = ["--charset=UTF-8"]
53
+ s.require_paths = ["lib"]
54
+ s.rubygems_version = %q{1.3.7}
55
+ s.summary = %q{A very simple relay for moving messages from a local broker to a remote broker}
56
+ s.test_files = [
57
+ "test/config/Relayfile.rb",
58
+ "test/lib/bunnicula_test.rb",
59
+ "test/test_helper.rb"
60
+ ]
61
+
62
+ if s.respond_to? :specification_version then
63
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
64
+ s.specification_version = 3
65
+
66
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
67
+ s.add_runtime_dependency(%q<daemon-kit>, ["= 0.1.7.12"])
68
+ s.add_runtime_dependency(%q<bunny>, ["= 0.6.0"])
69
+ s.add_runtime_dependency(%q<amqp>, ["= 0.6.7"])
70
+ s.add_development_dependency(%q<shoulda>, ["= 2.11.1"])
71
+ else
72
+ s.add_dependency(%q<daemon-kit>, ["= 0.1.7.12"])
73
+ s.add_dependency(%q<bunny>, ["= 0.6.0"])
74
+ s.add_dependency(%q<amqp>, ["= 0.6.7"])
75
+ s.add_dependency(%q<shoulda>, ["= 2.11.1"])
76
+ end
77
+ else
78
+ s.add_dependency(%q<daemon-kit>, ["= 0.1.7.12"])
79
+ s.add_dependency(%q<bunny>, ["= 0.6.0"])
80
+ s.add_dependency(%q<amqp>, ["= 0.6.7"])
81
+ s.add_dependency(%q<shoulda>, ["= 2.11.1"])
82
+ end
83
+ end
84
+
@@ -0,0 +1,70 @@
1
+ # Argument handling for your daemon is configured here.
2
+ #
3
+ # You have access to two variables when this file is
4
+ # parsed. The first is +opts+, which is the object yielded from
5
+ # +OptionParser.new+, the second is +@options+ which is a standard
6
+ # Ruby hash that is later accessible through
7
+ # DaemonKit.arguments.options and can be used in your daemon process.
8
+
9
+ # Here is an example:
10
+ # opts.on('-f', '--foo FOO', 'Set foo') do |foo|
11
+ # @options[:foo] = foo
12
+ # end
13
+
14
+
15
+ opts.on('-c PATH','--config_path PATH') do |config|
16
+ @options[:configuration_file] = config
17
+ end
18
+
19
+ opts.on('-s SOURCE','--source SOURCE') do |source_uri|
20
+ @options[:source_uri] = source_uri
21
+ end
22
+
23
+ opts.on('-t TARGET','--target TARGET') do |target_uri|
24
+ @options[:targets] ||= []
25
+ @options[:targets] << {:target_uri=>target_uri}
26
+ end
27
+
28
+ opts.on('-r RELAY', '--relay RELAY') do |relay|
29
+ raise "You specified a RELAY parameter, but did not first specify a TARGET parameter. Please indicate the target AMQP server in the form of -t TARGET or --target TARGET" unless @options[:targets]
30
+ @options[:targets][-1][:relays] ||= []
31
+ @options[:targets][-1][:relays] << {:from=>relay}
32
+ end
33
+
34
+ opts.on('-y TO', '--to TO') do |to|
35
+ puts to
36
+ raise "You specified a RELAY_TO parameter, but did not first specify a RELAY parameter. Please indicate the target AMQP server in the form of -r RELAY or --relay RELAY" unless @options[:targets] && @options[:targets][-1][:relays]
37
+ relay = @options[:targets][-1][:relays][-1]
38
+ relay[:to] = to
39
+ end
40
+
41
+ opts.on('-x TYPE', '--type TYPE') do |type|
42
+ raise "You specified a TYPE parameter, but did not first specify a RELAY parameter. Please indicate the target AMQP server in the form of -r RELAY or --relay RELAY" unless @options[:targets] && @options[:targets][-1][:relays]
43
+ relay = @options[:targets][-1][:relays][-1]
44
+ if relay[:to]
45
+ relay[:to_type] = type.downcase.to_sym
46
+ else
47
+ relay[:from_type] = type.downcase.to_sym
48
+ end
49
+ end
50
+
51
+ opts.on('-d','--durable') do |durable|
52
+ raise "You specified a DURABLE parameter, but did not first specify a RELAY parameter. Please indicate the target AMQP server in the form of -r RELAY or --relay RELAY" unless @options[:targets] && @options[:targets][-1][:relays]
53
+ relay = @options[:targets][-1][:relays][-1]
54
+ if relay[:to]
55
+ relay[:to_durable] = durable
56
+ else
57
+ relay[:from_durable] = durable
58
+ end
59
+ end
60
+
61
+ opts.on('-a','--ack') do |ack|
62
+ raise "You specified a ACK parameter, but did not first specify a RELAY parameter. Please indicate the target AMQP server in the form of -r RELAY or --relay RELAY" unless @options[:targets] && @options[:targets][-1][:relays]
63
+ relay = @options[:targets][-1][:relays][-1]
64
+ if relay[:to]
65
+ relay[:to_ack] = ack
66
+ else
67
+ relay[:from_ack] = ack
68
+ end
69
+ end
70
+
data/config/boot.rb ADDED
@@ -0,0 +1,59 @@
1
+ # Don't change this file!
2
+ # Configure your daemon in config/environment.rb
3
+
4
+ DAEMON_ROOT = "#{File.expand_path(File.dirname(__FILE__))}/.." unless defined?( DAEMON_ROOT )
5
+
6
+ module DaemonKit
7
+ class << self
8
+ def boot!
9
+ unless booted?
10
+ pick_boot.run
11
+ end
12
+ end
13
+
14
+ def booted?
15
+ defined? DaemonKit::Initializer
16
+ end
17
+
18
+ def pick_boot
19
+ (vendor_kit? ? VendorBoot : GemBoot).new
20
+ end
21
+
22
+ def vendor_kit?
23
+ File.exists?( "#{DAEMON_ROOT}/vendor/daemon_kit" )
24
+ end
25
+ end
26
+
27
+ class Boot
28
+ def run
29
+ load_initializer
30
+ DaemonKit::Initializer.run
31
+ end
32
+ end
33
+
34
+ class VendorBoot < Boot
35
+ def load_initializer
36
+ require "#{DAEMON_ROOT}/vendor/daemon_kit/lib/daemon_kit/initializer"
37
+ end
38
+ end
39
+
40
+ class GemBoot < Boot
41
+ def load_initializer
42
+ begin
43
+ gem 'daemon-kit'
44
+ require 'daemon_kit/initializer'
45
+ rescue Gem::LoadError => e
46
+ msg = <<EOF
47
+ You are missing the daemon-kit gem. Please install the following gems:
48
+
49
+ * Stable - sudo gem install daemon-kit
50
+
51
+ EOF
52
+ $stderr.puts msg
53
+ exit 1
54
+ end
55
+ end
56
+ end
57
+ end
58
+
59
+ DaemonKit.boot!
@@ -0,0 +1,33 @@
1
+ # Be sure to restart your daemon when you modify this file
2
+
3
+ # Uncomment below to force your daemon into production mode
4
+ ENV['DAEMON_ENV'] ||= 'production'
5
+
6
+ # Boot up
7
+ require "rubygems"
8
+
9
+ begin
10
+ require 'amqp'
11
+ require 'mq'
12
+ rescue LoadError
13
+ $stderr.puts "Missing amqp gem. Please run 'gem install amqp'."
14
+ exit 1
15
+ end
16
+
17
+ require File.join(File.dirname(__FILE__), 'boot')
18
+
19
+ DaemonKit::Initializer.run do |config|
20
+
21
+ # The name of the daemon as reported by process monitoring tools
22
+ config.daemon_name = 'bunnicula'
23
+
24
+ # Force the daemon to be killed after X seconds from asking it to
25
+ # config.force_kill_wait = 30
26
+
27
+ # Log backraces when a thread/daemon dies (Recommended)
28
+ # config.backtraces = true
29
+
30
+ # Configure the safety net (see DaemonKit::Safety)
31
+ # config.safety_net.handler = :mail # (or :hoptoad )
32
+ # config.safety_net.mail.host = 'localhost'
33
+ end
@@ -0,0 +1,2 @@
1
+ # This is the same context as the environment.rb file, it is only
2
+ # loaded afterwards and only in the development environment
@@ -0,0 +1,5 @@
1
+ # This is the same context as the environment.rb file, it is only
2
+ # loaded afterwards and only in the production environment
3
+
4
+ # Change the production log level to debug
5
+ #config.log_level = :debug
@@ -0,0 +1,2 @@
1
+ # This is the same context as the environment.rb file, it is only
2
+ # loaded afterwards and only in the test environment
@@ -0,0 +1,5 @@
1
+ # You can place files in here to be loaded after the code is daemonized.
2
+ #
3
+ # All the files placed here will just be required into the running
4
+ # process. This is the correct place to open any IO objects, establish
5
+ # database connections, etc.
@@ -0,0 +1,2 @@
1
+
2
+
@@ -0,0 +1,12 @@
1
+ # You can place files in here to be loaded before the code is daemonized.
2
+ #
3
+ # DaemonKit looks for a file named '<config.daemon_name>.rb' and loads
4
+ # that file first, and inside a DaemonKit::Initializer block. The
5
+ # remaning files then simply required into the running process.
6
+ #
7
+ # These files are mostly useful for operations that should fail blatantly
8
+ # before daemonizing, like loading gems.
9
+ #
10
+ # Be careful not to open any form of IO in here and expecting it to be
11
+ # open inside the running daemon since all IO instances are closed when
12
+ # daemonizing (including STDIN, STDOUT & STDERR).
data/lib/bunnicula.rb ADDED
@@ -0,0 +1,61 @@
1
+ require "rubygems"
2
+ require "bunny"
3
+
4
+ dir = File.dirname(__FILE__)
5
+ ["support",
6
+ "amqp",
7
+ "bunny_farm",
8
+ "dsl_base",
9
+ "exchange",
10
+ "relay",
11
+ "rabbit",
12
+ "vampire_rabbit",].each {|lib|require File.join(dir,'bunnicula',lib)}
13
+
14
+
15
+ module Bunnicula
16
+ class << self
17
+
18
+
19
+ def suck(amq)
20
+ vampire_rabbits.each {|vampire|vampire.suck(amq)}
21
+ end
22
+
23
+ def vampire_rabbits
24
+ @@vampire_rabbits ||= []
25
+ end
26
+
27
+ def bite(&block)
28
+ instance_eval(&block)
29
+ end
30
+ alias configure bite
31
+
32
+ def victim(host=nil,&block)
33
+ return (@@victim ||= Bunnicula::Rabbit.new) unless host || block_given?
34
+ @@victim = Bunnicula::Rabbit.new(host)
35
+ if block_given?
36
+ if block.arity > 0
37
+ block.call(@@victim)
38
+ else
39
+ @@victim.instance_eval(&block)
40
+ end
41
+ end
42
+ end
43
+ alias source victim
44
+
45
+ def transfusion_to(host=nil,&block)
46
+ rabbit = Bunnicula::VampireRabbit.new(host)
47
+ if block_given?
48
+ if (block.arity > 0)
49
+ block.call(rabbit)
50
+ else
51
+ rabbit.instance_eval(&block)
52
+ end
53
+ end
54
+ vampire_rabbits << rabbit
55
+ rabbit
56
+ end
57
+ alias target transfusion_to
58
+
59
+ end
60
+
61
+ end
@@ -0,0 +1,38 @@
1
+
2
+ module Bunnicula
3
+ # Thin wrapper around the amqp gem, specifically designed to ease
4
+ # configuration of a AMQP consumer daemon and provide some added
5
+ # simplicity
6
+ class AMQP
7
+
8
+ @@instance = nil
9
+
10
+ class << self
11
+
12
+ def instance
13
+ @instance ||= new
14
+ end
15
+
16
+ private :new
17
+
18
+ def run(config=nil,&block)
19
+ @instance = new(config) if config
20
+ instance.run(&block)
21
+ end
22
+ end
23
+
24
+ def initialize( config = {} )
25
+ @config = config || DaemonKit::Config.load('amqp').to_h( true )
26
+ end
27
+
28
+ def run(&block)
29
+ # Ensure graceful shutdown of the connection to the broker
30
+ DaemonKit.trap('INT') { ::AMQP.stop { ::EM.stop } }
31
+ DaemonKit.trap('TERM') { ::AMQP.stop { ::EM.stop } }
32
+
33
+ # Start our event loop and AMQP client
34
+ DaemonKit.logger.debug("AMQP.start(#{@config.inspect})")
35
+ ::AMQP.start(@config, &block)
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,21 @@
1
+ module Bunnicula
2
+ class BunnyFarm
3
+
4
+ def self.bunnies
5
+ @@bunnies ||= []
6
+ end
7
+
8
+ def self.breed(connection_options={})
9
+ bunny = Bunny.new(connection_options)
10
+ bunnies << bunny
11
+ bunny.start
12
+ bunny
13
+ end
14
+
15
+
16
+
17
+ def self.stop
18
+ bunnies.each {|bunny|bunny.stop}
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,25 @@
1
+ module Bunnicula
2
+ module DslBase
3
+ def dsl_attr(*args)
4
+ options = args.extract_options!
5
+ [args].flatten.each {|attr|create_property_for(attr,options)}
6
+ end
7
+
8
+ private
9
+
10
+ def create_property_for(attr,options)
11
+ class_eval <<-end_eval
12
+ @@dsl_attr_defaults ||= {}
13
+ @@dsl_attr_defaults['#{attr}'] = #{options[:default].inspect}
14
+
15
+ def #{attr}(val = nil)
16
+ return @#{attr} || @@dsl_attr_defaults['#{attr}'] unless val
17
+ @#{attr} = val
18
+ end
19
+ end_eval
20
+ if (alias_list = options[:alias])
21
+ [alias_list].flatten.each {|attr_alias| alias_method(attr_alias, attr.to_sym)}
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,13 @@
1
+ class Bunnicula::Exchange
2
+
3
+ attr_reader :name, :type, :durable, :ack, :routing_key
4
+
5
+ def initialize(exchange_name=nil,options={})
6
+ @name = exchange_name
7
+ @type = options.delete(:type) || options.delete(:exchange_type)
8
+ @durable = options.delete(:durable)
9
+ @ack = options.delete(:ack)
10
+ @routing_key = options.delete(:routing_key)
11
+ end
12
+
13
+ end
@@ -0,0 +1,37 @@
1
+ require 'uri'
2
+
3
+ module Bunnicula
4
+ class Rabbit
5
+ extend Bunnicula::DslBase
6
+ #DSL
7
+ dsl_attr :host, :default=>"localhost"
8
+ dsl_attr :username, :password, :default=>"guest"
9
+ dsl_attr :vhost, :default=>"/"
10
+ dsl_attr :port, :default=>5672
11
+
12
+
13
+ def initialize(constr=nil)
14
+ return unless constr
15
+ if (constr =~ /amqp:\/\//)
16
+ uri = URI.parse(constr)
17
+ host uri.host
18
+ port uri.port || 5672
19
+ username uri.user || "guest"
20
+ password uri.password || "guest"
21
+ vhost uri.path.strip.length < 1 ? "/" : uri.path
22
+ else
23
+ host(constr)
24
+ end
25
+ end
26
+
27
+ def to_h
28
+ {
29
+ :host=>host,
30
+ :port=>port,
31
+ :username=>username,
32
+ :password=>password,
33
+ :vhost=>vhost
34
+ }
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,87 @@
1
+ class Bunnicula::Relay
2
+ extend Bunnicula::DslBase
3
+
4
+ dsl_attr :filter, :alias=>:where
5
+ attr_reader :source_exchange, :target_exchange
6
+
7
+ def from(*args)
8
+ return @source_exchange if args.length == 0
9
+ options = args.extract_options!
10
+ exchange_name = args.pop
11
+ @source_exchange = Bunnicula::Exchange.new(exchange_name,options)
12
+ end
13
+
14
+ def to(*args)
15
+ return @target_exchange if args.length == 0
16
+ options = args.extract_options!
17
+ exchange_name = args.pop
18
+ @target_exchange = Bunnicula::Exchange.new(exchange_name,options)
19
+ end
20
+
21
+ #Operational
22
+ def queue_name
23
+ "bunnicula.#{from.name}"
24
+ end
25
+
26
+ def start(amq,bunny)
27
+ DaemonKit.logger.info "Starting relay from:#{from.name} to:#{to.name || from.name}"
28
+ @channel = amq
29
+ @bunny = bunny
30
+
31
+ prepare_destination_exchange
32
+ bind_to_source_exchange
33
+ subscribe_to_queue
34
+
35
+ DaemonKit.logger.info "Relay started"
36
+
37
+ end
38
+
39
+ private
40
+
41
+ def prepare_destination_exchange
42
+ exchange_options = {:type=>to.type || from.type || :direct,
43
+ :durable=>to.durable || from.durable || true}
44
+ @destination = @bunny.exchange(to.name || from.name, exchange_options)
45
+ end
46
+
47
+ def bind_to_source_exchange
48
+ DaemonKit.logger.info "Binding queue #{queue_name} to the source exchange #{from.name}"
49
+ @queue = @channel.queue(queue_name,:durable=>from.durable.to_b)
50
+ @exchange = @channel.instance_eval("#{from.type || :direct}('#{from.name}', :durable=>#{from.durable.to_b})")
51
+ binding_options = from.type == :topic ? {:key=>from.routing_key || "#"} : {}
52
+ @queue.bind(@exchange,binding_options)
53
+ end
54
+
55
+ def subscribe_to_queue
56
+ @queue.subscribe(:ack=>from.ack) do |header,message|
57
+ routing_key = header.properties[:routing_key]
58
+ DaemonKit.logger.debug "Received message on #{from.name}. Routing Key:#{routing_key}"
59
+ if relay?(header,message)
60
+ DaemonKit.logger.debug "Trasmitting #{routing_key} to #{to.name || from.name}"
61
+ persistent = to.durable || from.durable || true
62
+ begin
63
+ @destination.publish(message, :key=>routing_key, :persistent=>persistent)
64
+ header.ack
65
+ DaemonKit.logger.debug "#{routing_key} transmitted and acknowledged"
66
+ rescue RuntimeError=>ex
67
+ Debug.error "#{routing_key} could not be delivered due to an error"
68
+ DaemonKit.logger.exception ex
69
+ end
70
+ else
71
+ DaemonKit.logger.debug "#{routing_key} did not satisfy the configured filter (#{filter.inspect})"
72
+ end
73
+ end
74
+ end
75
+
76
+ def relay?(header,message)
77
+ return true unless filter
78
+ return header.properties[:routing_key] =~ filter if filter.is_a?(Regexp)
79
+ if (filter.is_a?(Proc))
80
+ return filter.call if proc.arity <= 0
81
+ return filter.call(header) if proc.arity == 1
82
+ return filter.call(header,message)
83
+ end
84
+ #default behavior
85
+ header.properties[:routing_key] == filter.to_s
86
+ end
87
+ end
@@ -0,0 +1,11 @@
1
+ class Object
2
+ def to_b
3
+ !!(self)
4
+ end
5
+ end
6
+
7
+ class Array
8
+ def extract_options!
9
+ last.is_a?(::Hash) ? pop : {}
10
+ end
11
+ end
@@ -0,0 +1,36 @@
1
+ class Bunnicula::VampireRabbit < Bunnicula::Rabbit
2
+
3
+ attr_reader :relays
4
+
5
+ def initialize(host=nil)
6
+ super(host)
7
+ @relays = []
8
+ end
9
+
10
+ def relay(*args,&block)
11
+ options = args.extract_options!
12
+ args = [""] if args.length == 0 && block_given?
13
+ args.each do |from_exchange_name|
14
+ relay = Bunnicula::Relay.new
15
+ relay.from(from_exchange_name,options.dup)
16
+ if block_given?
17
+ if (block.arity > 0)
18
+ block.call(relay)
19
+ else
20
+ relay.instance_eval(&block)
21
+ end
22
+ end
23
+ @relays << relay
24
+ end
25
+ end
26
+
27
+ def suck(amq)
28
+ DaemonKit.logger.info "Setting up relays to #{host}:#{port}, vhost=#{vhost} as #{username}"
29
+ @bunny = Bunnicula::BunnyFarm.breed(:host=>host, :port=>port, :vhost=>vhost, :user=>username, :pass=>password)
30
+ @relays.each do |relay|
31
+ relay.start(amq,@bunny)
32
+ end
33
+ DaemonKit.logger.info "Let the good times roll, #{host}"
34
+ end
35
+
36
+ end
@@ -0,0 +1,3 @@
1
+ module Bunnicula
2
+ VERSION = '0.1.0'
3
+ end
@@ -0,0 +1,63 @@
1
+ # Generated amqp daemon
2
+
3
+ # Do your post daemonization configuration here
4
+ # At minimum you need just the first line (without the block), or a lot
5
+ # of strange things might start happening...
6
+ DaemonKit::Application.running! do |config|
7
+ # Trap signals with blocks or procs
8
+ # config.trap( 'INT' ) do
9
+ # # do something clever
10
+ # end
11
+ config.trap( 'TERM', Proc.new { Bunnicula::BunnyFarm.stop } )
12
+ end
13
+
14
+ if( config = DaemonKit.arguments.options[:configuration_file])
15
+ load config if File.exist?(config)
16
+ end
17
+
18
+ if (source_uri = DaemonKit.arguments.options[:source_uri])
19
+ Bunnicula.victim(source_uri)
20
+ end
21
+
22
+ if (DaemonKit.arguments.options[:targets])
23
+ targets = DaemonKit.arguments.options[:targets]
24
+ targets.each do |target_config|
25
+ Bunnicula.transfusion_to(target_config[:target_uri]) do |vamp|
26
+ target_config[:relays].each do |relay_config|
27
+ vamp.relay do |r|
28
+ from_options = {}
29
+ from_options[:durable] = relay_config[:from_durable] if relay_config[:from_durable]
30
+ from_options[:ack] = relay_config[:from_ack] if relay_config[:from_ack]
31
+ from_options[:type] = relay_config[:from_type] if relay_config[:from_type]
32
+ r.from relay_config[:from], from_options
33
+ if (to_exchange = relay_config[:to])
34
+ to_options = {}
35
+ to_options[:durable] = relay_config[:to_durable] if relay_config[:to_durable]
36
+ to_options[:ack] = relay_config[:to_ack] if relay_config[:to_ack]
37
+ to_options[:type] = relay_config[:to_type] if relay_config[:to_type]
38
+ r.to to_exchange, to_options
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+
46
+ # Run an event-loop for processing
47
+ raise "Bunnicula requires a victim. Please specify a source rabbitmq instance via a configuration file or commandline argument" unless Bunnicula.victim
48
+ Bunnicula::AMQP.run(Bunnicula.victim.to_h) do
49
+ # Inside this block we're running inside the reactor setup by the
50
+ # amqp gem. Any code in the examples (from the gem) would work just
51
+ # fine here.
52
+
53
+ # Uncomment this for connection keep-alive
54
+ # AMQP.conn.connection_status do |status|
55
+ # DaemonKit.logger.debug("AMQP connection status changed: #{status}")
56
+ # if status == :disconnected
57
+ # AMQP.conn.reconnect(true)
58
+ # end
59
+ # end
60
+
61
+ amq = ::MQ.new
62
+ Bunnicula.suck(amq)
63
+ end
@@ -0,0 +1,29 @@
1
+ Bunnicula.bite {
2
+ victim "amqp://a:b@a-host:12345/tada"
3
+
4
+ transfusion_to "example.com" do
5
+ relay "an exchange name"
6
+ end
7
+
8
+ transfusion_to {
9
+ host "target_server_1"
10
+ port 12345
11
+ username "a"
12
+ password "b"
13
+ vhost "tada"
14
+
15
+ relay do
16
+ from "test_source_exchange", :type=>:topic, :durable=>true, :ack=>true
17
+ to "test_destination_exchange", :durable=>true
18
+ end
19
+
20
+ relay "another_exchange",
21
+ "and_another_exchange",
22
+ "and_even_another",
23
+ :type=>:fanout,
24
+ :durable=>false,
25
+ :ack=>true
26
+ }
27
+ }
28
+
29
+
@@ -0,0 +1,72 @@
1
+ require "test_helper"
2
+
3
+ class BunniculaTest < Test::Unit::TestCase
4
+ context "Excuting a representative relay file" do
5
+ setup do
6
+ require "config/Relayfile"
7
+ end
8
+
9
+ should "configure the victim" do
10
+ victim = Bunnicula.victim
11
+ assert_equal "a-host", victim.host
12
+ assert_equal 12345, victim.port
13
+ assert_equal "a", victim.username
14
+ assert_equal "b", victim.password
15
+ assert_equal "/tada", victim.vhost
16
+ end
17
+ should "configure two vampire rabbits" do
18
+ assert_equal 2, Bunnicula.vampire_rabbits.length
19
+ end
20
+ should "use default values for target rabbit connection when no overrides present" do
21
+ vamp = Bunnicula.vampire_rabbits[0]
22
+ assert_equal "example.com", vamp.host
23
+ assert_equal 5672, vamp.port
24
+ assert_equal "guest", vamp.username
25
+ assert_equal "guest", vamp.password
26
+ assert_equal "/", vamp.vhost
27
+ end
28
+ should "accept overrides to default connection values" do
29
+ vamp = Bunnicula.vampire_rabbits[1]
30
+ assert_equal "target_server_1", vamp.host
31
+ assert_equal 12345, vamp.port
32
+ assert_equal "a", vamp.username
33
+ assert_equal "b", vamp.password
34
+ assert_equal "tada", vamp.vhost
35
+ end
36
+ should "configure the correct number of relays per vampire rabbit" do
37
+ assert_equal 1, Bunnicula.vampire_rabbits[0].relays.length
38
+ assert_equal 4, Bunnicula.vampire_rabbits[1].relays.length
39
+ end
40
+ should "construct a default relay with matching from/to exchanges when provided with just a name" do
41
+ relay = Bunnicula.vampire_rabbits[0].relays[0]
42
+ assert_equal "an exchange name", relay.source_exchange.name
43
+ assert_nil relay.source_exchange.type
44
+ assert_nil relay.source_exchange.durable
45
+ assert_nil relay.source_exchange.ack
46
+ assert_nil relay.target_exchange
47
+ end
48
+ should "construct source and destination relays via a block" do
49
+ relay = Bunnicula.vampire_rabbits[1].relays[0]
50
+ assert_equal "test_source_exchange", relay.from.name
51
+ assert_equal :topic, relay.from.type
52
+ assert_equal true, relay.from.durable
53
+ assert_equal true, relay.from.ack
54
+ assert_equal "test_destination_exchange", relay.to.name
55
+ assert_equal true, relay.to.durable
56
+ assert_nil relay.to.ack
57
+ assert_nil relay.to.type
58
+ end
59
+ should "create a set of relays with common options when created via a list of names" do
60
+ relays = Bunnicula.vampire_rabbits[1].relays[1..3]
61
+ assert_equal "another_exchange",relays[0].from.name
62
+ assert_equal "and_another_exchange",relays[1].from.name
63
+ assert_equal "and_even_another",relays[2].from.name
64
+ relays.each do |relay|
65
+ assert_equal :fanout, relay.from.type
66
+ assert_equal false, relay.from.durable.to_b
67
+ assert_equal true, relay.from.ack
68
+ assert_nil relay.to
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,8 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+
3
+ dir = File.dirname(__FILE__)
4
+ require File.join(dir,"..","lib","bunnicula")
5
+ require "rubygems"
6
+ require "shoulda"
7
+ require "logger"
8
+
metadata ADDED
@@ -0,0 +1,161 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bunnicula
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - Nathan Stults
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-07-09 00:00:00 -07:00
19
+ default_executable: bunnicula
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: daemon-kit
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - "="
28
+ - !ruby/object:Gem::Version
29
+ hash: 67
30
+ segments:
31
+ - 0
32
+ - 1
33
+ - 7
34
+ - 12
35
+ version: 0.1.7.12
36
+ type: :runtime
37
+ version_requirements: *id001
38
+ - !ruby/object:Gem::Dependency
39
+ name: bunny
40
+ prerelease: false
41
+ requirement: &id002 !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - "="
45
+ - !ruby/object:Gem::Version
46
+ hash: 7
47
+ segments:
48
+ - 0
49
+ - 6
50
+ - 0
51
+ version: 0.6.0
52
+ type: :runtime
53
+ version_requirements: *id002
54
+ - !ruby/object:Gem::Dependency
55
+ name: amqp
56
+ prerelease: false
57
+ requirement: &id003 !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - "="
61
+ - !ruby/object:Gem::Version
62
+ hash: 9
63
+ segments:
64
+ - 0
65
+ - 6
66
+ - 7
67
+ version: 0.6.7
68
+ type: :runtime
69
+ version_requirements: *id003
70
+ - !ruby/object:Gem::Dependency
71
+ name: shoulda
72
+ prerelease: false
73
+ requirement: &id004 !ruby/object:Gem::Requirement
74
+ none: false
75
+ requirements:
76
+ - - "="
77
+ - !ruby/object:Gem::Version
78
+ hash: 33
79
+ segments:
80
+ - 2
81
+ - 11
82
+ - 1
83
+ version: 2.11.1
84
+ type: :development
85
+ version_requirements: *id004
86
+ description: A very simple relay for moving messages from a local broker to a remote broker
87
+ email: hereiam@sonic.net
88
+ executables:
89
+ - bunnicula
90
+ extensions: []
91
+
92
+ extra_rdoc_files:
93
+ - LICENSE.txt
94
+ files:
95
+ - LICENSE.txt
96
+ - README
97
+ - Rakefile
98
+ - Relayfile
99
+ - bin/bunnicula
100
+ - bunnicula.gemspec
101
+ - config/arguments.rb
102
+ - config/boot.rb
103
+ - config/environment.rb
104
+ - config/environments/development.rb
105
+ - config/environments/production.rb
106
+ - config/environments/test.rb
107
+ - config/post-daemonize/readme
108
+ - config/pre-daemonize/bunnicula.rb
109
+ - config/pre-daemonize/readme
110
+ - lib/bunnicula.rb
111
+ - lib/bunnicula/amqp.rb
112
+ - lib/bunnicula/bunny_farm.rb
113
+ - lib/bunnicula/dsl_base.rb
114
+ - lib/bunnicula/exchange.rb
115
+ - lib/bunnicula/rabbit.rb
116
+ - lib/bunnicula/relay.rb
117
+ - lib/bunnicula/support.rb
118
+ - lib/bunnicula/vampire_rabbit.rb
119
+ - lib/bunnicula/version.rb
120
+ - libexec/bunnicula-daemon.rb
121
+ - test/config/Relayfile.rb
122
+ - test/lib/bunnicula_test.rb
123
+ - test/test_helper.rb
124
+ has_rdoc: true
125
+ homepage: http://github.com/PlasticLizard/bunnicula
126
+ licenses: []
127
+
128
+ post_install_message:
129
+ rdoc_options:
130
+ - --charset=UTF-8
131
+ require_paths:
132
+ - lib
133
+ required_ruby_version: !ruby/object:Gem::Requirement
134
+ none: false
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ hash: 3
139
+ segments:
140
+ - 0
141
+ version: "0"
142
+ required_rubygems_version: !ruby/object:Gem::Requirement
143
+ none: false
144
+ requirements:
145
+ - - ">="
146
+ - !ruby/object:Gem::Version
147
+ hash: 3
148
+ segments:
149
+ - 0
150
+ version: "0"
151
+ requirements: []
152
+
153
+ rubyforge_project:
154
+ rubygems_version: 1.3.7
155
+ signing_key:
156
+ specification_version: 3
157
+ summary: A very simple relay for moving messages from a local broker to a remote broker
158
+ test_files:
159
+ - test/config/Relayfile.rb
160
+ - test/lib/bunnicula_test.rb
161
+ - test/test_helper.rb