bunnicula 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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