buster 0.1.1

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.
@@ -0,0 +1,6 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ *.swp
6
+ vendor/bundle
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in buster.gemspec
4
+ gemspec
5
+
6
+ group :development do
7
+ gem 'rspec'
8
+ end
@@ -0,0 +1,36 @@
1
+ Buster
2
+ ---
3
+
4
+ A Ruby Service Bus
5
+
6
+ License
7
+ ---
8
+
9
+ Copyright (c) 2012, Jason Staten, PeerIntel
10
+
11
+ All rights reserved.
12
+
13
+ Redistribution and use in source and binary forms, with or without
14
+ modification, are permitted provided that the following conditions are met:
15
+
16
+ - Redistributions of source code must retain the above copyright notice, this
17
+ list of conditions and the following disclaimer.
18
+
19
+ - Redistributions in binary form must reproduce the above copyright notice,
20
+ this list of conditions and the following disclaimer in the documentation
21
+ and/or other materials provided with the distribution.
22
+
23
+ - Neither the name of PeerIntel nor the names of its contributors may be used
24
+ to endorse or promote products derived from this software without specific
25
+ prior written permission.
26
+
27
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
28
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
29
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
30
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
31
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
32
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
33
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
34
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
35
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
36
+ THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,4 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "buster/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "buster"
7
+ s.version = Buster::VERSION
8
+ s.authors = ["Jason Staten"]
9
+ s.email = ["jstaten07@gmail.com"]
10
+ s.homepage = ""
11
+ s.summary = %q{Service Bus}
12
+ s.description = %q{Service Bus}
13
+
14
+ s.rubyforge_project = "buster"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ s.add_runtime_dependency "ffi-rzmq"
22
+ s.add_runtime_dependency "msgpack"
23
+ end
@@ -0,0 +1,40 @@
1
+ %w{host sender worker poller command_handler router context version}.each { |r| require "buster/#{r}" }
2
+
3
+ require 'ffi-rzmq'
4
+ require 'msgpack'
5
+
6
+ module Buster
7
+ class << self
8
+ attr_accessor :local_endpoint
9
+
10
+ def routes
11
+ @routes ||= {}
12
+ end
13
+
14
+ def start
15
+ raise "Bus is already started" if @host
16
+ @host = Buster::Host.new(routes, context, self.local_endpoint)
17
+ @host.start
18
+ end
19
+
20
+ def stop
21
+ raise "Bus is not running" unless @host
22
+ @host.stop
23
+ @host = nil
24
+ end
25
+
26
+ def fire(name, props = {})
27
+ sender.fire(name, props)
28
+ end
29
+
30
+ private
31
+
32
+ def sender
33
+ @sender ||= Buster::Sender.new(context)
34
+ end
35
+
36
+ def context
37
+ @context ||= Buster::Context.new(ZMQ::Context.create)
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,11 @@
1
+ module Buster
2
+ module CommandHandler
3
+ attr_accessor :reply_action
4
+
5
+ def reply(message_name, props = {})
6
+ raise 'No reply_action available' unless self.reply_action
7
+
8
+ reply_action.call(message_name, props)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,25 @@
1
+ module Buster
2
+ class Context
3
+
4
+ attr_accessor :context
5
+
6
+ def initialize(context)
7
+ @context = context
8
+ @sockets = []
9
+ end
10
+
11
+ def socket(type)
12
+ socket = @context.socket(type)
13
+ @sockets << socket
14
+ socket
15
+ end
16
+
17
+ def terminate
18
+ @sockets.reverse.each do |s|
19
+ s.setsockopt(ZMQ::LINGER, 0)
20
+ s.close
21
+ end
22
+ @context.terminate
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,36 @@
1
+ module Buster
2
+ class Host
3
+
4
+ def initialize(routes, context, local_endpoint = nil)
5
+ @local_endpoint = local_endpoint ||"inproc://local_endpoint"
6
+ @context = context
7
+ @router = Buster::Router.new(@context, routes)
8
+ @poller = Buster::Poller.new
9
+ end
10
+
11
+ def start
12
+ frontend = @context.socket(ZMQ::ROUTER)
13
+ frontend.bind(@local_endpoint)
14
+
15
+ backend = @context.socket(ZMQ::DEALER)
16
+ backend.bind("inproc://workers")
17
+
18
+ @router.connect_routes backend, @poller
19
+
20
+ Buster::Worker.start_worker @context
21
+
22
+ @poller.pipe frontend, backend
23
+ @poller.pipe backend, frontend
24
+
25
+ poll_thread = Thread.new { @poller.start }
26
+ poll_thread.join
27
+
28
+ @context.terminate
29
+ end
30
+
31
+ def stop
32
+ @poller.stop
33
+ end
34
+
35
+ end
36
+ end
@@ -0,0 +1,40 @@
1
+ module Buster
2
+ class Poller
3
+ def initialize
4
+ @poller = ZMQ::Poller.new
5
+ end
6
+
7
+ def start
8
+ @run = true
9
+ while @run
10
+ @poller.poll(500)
11
+ @poller.readables.each do |socket|
12
+ action = @readable_actions[socket]
13
+ action.call(socket) if action
14
+ end
15
+ end
16
+ end
17
+
18
+ def stop
19
+ @run = false
20
+ end
21
+
22
+ def register(socket, &block)
23
+ @poller.register(socket, ZMQ::POLLIN)
24
+ readable_actions[socket] = block
25
+ end
26
+
27
+ def pipe(incoming, outgoing)
28
+ register(incoming) do |s|
29
+ s.recv_strings(msgs = [])
30
+ outgoing.send_strings(msgs, ZMQ::NOBLOCK)
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ def readable_actions
37
+ @readable_actions ||= {}
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,34 @@
1
+ module Buster
2
+ class Router
3
+
4
+ def initialize(context, routes = {})
5
+ @context = context
6
+ @routes = routes
7
+ end
8
+
9
+ def connect_routes(reply_socket, poller)
10
+ local = @context.socket(ZMQ::DEALER)
11
+ local.bind("inproc://routes")
12
+
13
+ sockets = @routes.map do |pattern, uri|
14
+ remote = @context.socket(ZMQ::DEALER)
15
+ result = remote.connect(uri)
16
+
17
+ poller.pipe remote, reply_socket
18
+ [pattern, remote]
19
+ end
20
+
21
+ poller.register(local) do |s|
22
+ s.recv_strings(msgs = [])
23
+ message_name = msgs[0]
24
+ #TODO: More robust routing
25
+ remote = sockets.detect([nil,nil]){|x| x[0].match message_name}[1]
26
+ if remote.nil?
27
+ puts "No remote matching '#{message_name}'"
28
+ return
29
+ end
30
+ remote.send_strings(msgs)
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,20 @@
1
+ module Buster
2
+ class Sender
3
+ def initialize(context)
4
+ @context = context
5
+ end
6
+
7
+ def fire(name, props = {})
8
+ bin = MessagePack.pack(props)
9
+ sender.send_strings([name.to_s, bin])
10
+ end
11
+
12
+ private
13
+
14
+ def sender
15
+ s = @context.socket(ZMQ::DEALER)
16
+ s.connect("inproc://routes")
17
+ s
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,3 @@
1
+ module Buster
2
+ VERSION = "0.1.1"
3
+ end
@@ -0,0 +1,49 @@
1
+ module Buster
2
+ class Worker
3
+ def initialize(context)
4
+ @context = context
5
+ end
6
+
7
+ def run
8
+ worker = @context.socket(ZMQ::DEALER)
9
+ worker.connect("inproc://workers")
10
+ while true
11
+ worker.recv_strings(msgs = [])
12
+ return if msgs.length < 2
13
+
14
+ body = MessagePack.unpack(msgs.pop)
15
+ message_name = msgs.pop
16
+ reply_id = msgs.pop
17
+
18
+ handler = find_handler message_name
19
+ if handler.nil?
20
+ puts "No handler found for #{message_name}"
21
+ return
22
+ end
23
+
24
+ handler.reply_action = lambda do |name,props|
25
+ worker.send_strings([reply_id, name.to_s, MessagePack.pack(props)])
26
+ end
27
+
28
+ handler.execute body
29
+ end
30
+ worker.close
31
+ end
32
+
33
+
34
+ def self.start_worker(context)
35
+ worker = Worker.new(context)
36
+ t = Thread.new { worker.run }
37
+ t.run
38
+ end
39
+
40
+ private
41
+
42
+ def find_handler(name)
43
+ camelized = name.split(/[^a-z0-9]/i).map{|w| w.capitalize}.join
44
+ const_name = "#{camelized}Handler"
45
+ Object.const_get(const_name).new if Object.const_defined?(const_name)
46
+ end
47
+
48
+ end
49
+ end
@@ -0,0 +1,82 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'integration' do
4
+ before :all do
5
+ start_endpoint :mars
6
+ start_endpoint :pluto
7
+ Buster.local_endpoint = 'ipc://earth.ipc'
8
+ Buster.routes[/mars/] = 'ipc://mars.ipc'
9
+ Buster.routes[/pluto/] = 'ipc://pluto.ipc'
10
+ Buster.routes[/station/] = 'ipc://station.ipc'
11
+ Thread.new { Buster.start }
12
+ sleep 1 #bus startup delay
13
+ end
14
+
15
+ after :all do
16
+ Buster.stop
17
+ end
18
+
19
+ let(:mutex) { Mutex.new }
20
+ let(:baton) { ConditionVariable.new }
21
+
22
+ describe 'when sending a hello_mars' do
23
+ before do
24
+ Handler 'HelloEarth' do |props|
25
+ mutex.synchronize do
26
+ @response = props['name']
27
+ baton.signal
28
+ end
29
+ end
30
+ end
31
+
32
+ it 'should respond in the mars uppercase style' do
33
+ name = 'Major Tom'
34
+ Buster.fire :hello_mars, :name => name
35
+ mutex.synchronize do
36
+ baton.wait(mutex, 1)
37
+ @response.should == name.upcase
38
+ end
39
+ end
40
+ end
41
+
42
+ describe 'when sending a hello_pluto' do
43
+ before do
44
+ Handler 'HelloEarth' do |props|
45
+ mutex.synchronize do
46
+ @response = props['name']
47
+ baton.signal
48
+ end
49
+ end
50
+ end
51
+
52
+ it 'should respond in the pluto reversed style' do
53
+ name = 'Ground Control'
54
+ Buster.fire :hello_pluto, :name => name
55
+ mutex.synchronize do
56
+ baton.wait(mutex, 1)
57
+ @response.should == name.reverse
58
+ end
59
+ end
60
+ end
61
+
62
+ describe 'when sending to a powered down space station' do
63
+ before do
64
+ Handler 'TenFour' do |props|
65
+ mutex.synchronize do
66
+ @ten_foured = true
67
+ baton.signal
68
+ end
69
+ end
70
+ end
71
+
72
+ it 'should receive response when the station powers up' do
73
+ Buster.fire :adjust_station_temperature
74
+ sleep 1 #Oh no! Station isn't started!
75
+ start_endpoint :station
76
+ mutex.synchronize do
77
+ baton.wait(mutex, 1)
78
+ @ten_foured.should == true
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,51 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+
4
+ $:.unshift File.expand_path('../../lib', __FILE__)
5
+ require 'buster'
6
+
7
+ Bundler.require :development
8
+
9
+ Dir[File.join(File.dirname(__FILE__), 'support', '*.rb')].each { |d| require d }
10
+
11
+ Thread.abort_on_exception = true
12
+
13
+ $endpoint_pids = []
14
+
15
+ RSpec.configure do |config|
16
+ config.mock_with :rspec
17
+
18
+ config.after(:all) do
19
+ $endpoint_pids.each { |p| Process.kill("TERM", p) }
20
+ end
21
+ end
22
+
23
+ def Handler(name, &block)
24
+ const_name = "#{name}Handler"
25
+ if Object.const_defined?(const_name)
26
+ klass = Object.const_get(const_name)
27
+ else
28
+ klass = Class.new do
29
+ include Buster::CommandHandler
30
+
31
+ def execute(props)
32
+ @@execute_action.call(props,self)
33
+ end
34
+
35
+ def self.execute_action=(action)
36
+ @@execute_action = action
37
+ end
38
+ end
39
+ Object.const_set(const_name, klass)
40
+ end
41
+
42
+ klass.execute_action = block
43
+
44
+ klass
45
+ end
46
+
47
+ def start_endpoint(name)
48
+ pid = Process.spawn("ruby #{File.join(File.dirname(__FILE__),'test_endpoints', "#{name}.rb")}")
49
+ $endpoint_pids << pid
50
+
51
+ end
@@ -0,0 +1,14 @@
1
+ require File.expand_path('../../spec_helper.rb', __FILE__)
2
+
3
+ class HelloMarsHandler
4
+ include Buster::CommandHandler
5
+
6
+ def execute(props)
7
+ reply :hello_earth, 'name' => props['name'].upcase
8
+ end
9
+ end
10
+
11
+ trap("TERM") { Buster.stop }
12
+
13
+ Buster.local_endpoint = 'ipc://mars.ipc'
14
+ Buster.start
@@ -0,0 +1,14 @@
1
+ require File.expand_path('../../spec_helper.rb', __FILE__)
2
+
3
+ class HelloPlutoHandler
4
+ include Buster::CommandHandler
5
+
6
+ def execute(props)
7
+ reply :hello_earth, 'name' => props['name'].reverse
8
+ end
9
+ end
10
+
11
+ trap("TERM") { Buster.stop }
12
+
13
+ Buster.local_endpoint = 'ipc://pluto.ipc'
14
+ Buster.start
@@ -0,0 +1,14 @@
1
+ require File.expand_path('../../spec_helper.rb', __FILE__)
2
+
3
+ class AdjustStationTemperatureHandler
4
+ include Buster::CommandHandler
5
+
6
+ def execute(props)
7
+ reply :ten_four
8
+ end
9
+ end
10
+
11
+ trap("TERM") { Buster.stop }
12
+
13
+ Buster.local_endpoint = 'ipc://station.ipc'
14
+ Buster.start
metadata ADDED
@@ -0,0 +1,97 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: buster
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Jason Staten
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-01-13 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: ffi-rzmq
16
+ requirement: &11559360 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *11559360
25
+ - !ruby/object:Gem::Dependency
26
+ name: msgpack
27
+ requirement: &11558760 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *11558760
36
+ description: Service Bus
37
+ email:
38
+ - jstaten07@gmail.com
39
+ executables: []
40
+ extensions: []
41
+ extra_rdoc_files: []
42
+ files:
43
+ - .gitignore
44
+ - Gemfile
45
+ - README.md
46
+ - Rakefile
47
+ - buster.gemspec
48
+ - lib/buster.rb
49
+ - lib/buster/command_handler.rb
50
+ - lib/buster/context.rb
51
+ - lib/buster/host.rb
52
+ - lib/buster/poller.rb
53
+ - lib/buster/router.rb
54
+ - lib/buster/sender.rb
55
+ - lib/buster/version.rb
56
+ - lib/buster/worker.rb
57
+ - spec/integration_spec.rb
58
+ - spec/spec_helper.rb
59
+ - spec/test_endpoints/mars.rb
60
+ - spec/test_endpoints/pluto.rb
61
+ - spec/test_endpoints/station.rb
62
+ homepage: ''
63
+ licenses: []
64
+ post_install_message:
65
+ rdoc_options: []
66
+ require_paths:
67
+ - lib
68
+ required_ruby_version: !ruby/object:Gem::Requirement
69
+ none: false
70
+ requirements:
71
+ - - ! '>='
72
+ - !ruby/object:Gem::Version
73
+ version: '0'
74
+ segments:
75
+ - 0
76
+ hash: 1543500698659680995
77
+ required_rubygems_version: !ruby/object:Gem::Requirement
78
+ none: false
79
+ requirements:
80
+ - - ! '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ segments:
84
+ - 0
85
+ hash: 1543500698659680995
86
+ requirements: []
87
+ rubyforge_project: buster
88
+ rubygems_version: 1.8.13
89
+ signing_key:
90
+ specification_version: 3
91
+ summary: Service Bus
92
+ test_files:
93
+ - spec/integration_spec.rb
94
+ - spec/spec_helper.rb
95
+ - spec/test_endpoints/mars.rb
96
+ - spec/test_endpoints/pluto.rb
97
+ - spec/test_endpoints/station.rb