cf-message-bus 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,4 @@
1
+ require "cf_message_bus/version"
2
+
3
+ module CfMessageBus
4
+ end
@@ -0,0 +1,121 @@
1
+ require "eventmachine"
2
+ require "eventmachine/schedule_sync"
3
+ require "cf_message_bus/message_bus_factory"
4
+
5
+ module CfMessageBus
6
+ class Error < StandardError; end
7
+
8
+ class MessageBus
9
+ def initialize(config)
10
+ @logger = config[:logger]
11
+ @internal_bus = MessageBusFactory.message_bus(config[:uri])
12
+ @subscriptions = {}
13
+ @internal_bus.on_reconnect { start_internal_bus_recovery }
14
+ @recovery_callback = lambda {}
15
+ end
16
+
17
+ def subscribe(subject, opts = {}, &block)
18
+ @subscriptions[subject] = [opts, block]
19
+
20
+ subscribe_on_reactor(subject, opts) do |parsed_data, inbox|
21
+ EM.defer do
22
+ run_handler(block, parsed_data, inbox, subject, 'subscription')
23
+ end
24
+ end
25
+ end
26
+
27
+ def publish(subject, message = nil, &callback)
28
+ EM.schedule do
29
+ internal_bus.publish(subject, encode(message), &callback)
30
+ end
31
+ end
32
+
33
+ def recover(&block)
34
+ @recovery_callback = block
35
+ end
36
+
37
+ def request(subject, data = nil, options = {}, &block)
38
+ internal_bus.request(subject, encode(data), options) do |payload, inbox|
39
+ process_message(payload, inbox) do |parsed_data, inbox|
40
+ run_handler(block, parsed_data, inbox, subject, 'response')
41
+ end
42
+ end
43
+ end
44
+
45
+ def synchronous_request(subject, data = nil, opts = {})
46
+ result_count = opts[:result_count] || 1
47
+ timeout = opts[:timeout] || -1
48
+
49
+ return [] if result_count <= 0
50
+
51
+ response = EM.schedule_sync do |promise|
52
+ results = []
53
+
54
+ sid = request(subject, encode(data), max: result_count) do |data|
55
+ results << data
56
+ promise.deliver(results) if results.size == result_count
57
+ end
58
+
59
+ if timeout >= 0
60
+ internal_bus.timeout(sid, timeout, expected: result_count) do
61
+ promise.deliver(results)
62
+ end
63
+ end
64
+ end
65
+
66
+ response
67
+ end
68
+
69
+ def unsubscribe(subscription_id)
70
+ internal_bus.unsubscribe(subscription_id)
71
+ end
72
+
73
+ private
74
+
75
+ attr_reader :internal_bus
76
+
77
+ def run_handler(block, parsed_data, inbox, subject, type)
78
+ begin
79
+ block.yield(parsed_data, inbox)
80
+ rescue => e
81
+ @logger.error "exception processing #{type} for: '#{subject}' '#{parsed_data.inspect}' \n#{e.inspect}\n #{e.backtrace.join("\n")}"
82
+ end
83
+ end
84
+
85
+ def start_internal_bus_recovery
86
+ EM.defer do
87
+ @logger.info("Reconnected to internal_bus.")
88
+
89
+ @recovery_callback.call
90
+
91
+ @subscriptions.each do |subject, options|
92
+ @logger.info("Resubscribing to #{subject}")
93
+ subscribe(subject, options[0], &options[1])
94
+ end
95
+ end
96
+ end
97
+
98
+ def subscribe_on_reactor(subject, opts = {}, &blk)
99
+ EM.schedule do
100
+ internal_bus.subscribe(subject, opts) do |msg, inbox|
101
+ process_message(msg, inbox, &blk)
102
+ end
103
+ end
104
+ end
105
+
106
+ def process_message(msg, inbox, &block)
107
+ payload = JSON.parse(msg, symbolize_keys: true)
108
+ block.yield(payload, inbox)
109
+ rescue => e
110
+ @logger.error "exception parsing json: '#{msg}' '#{e.inspect}'"
111
+ block.yield({error: "JSON Parse Error: failed to parse", exception: e, message: msg}, inbox)
112
+ end
113
+
114
+ def encode(message)
115
+ unless message.nil? || message.is_a?(String)
116
+ message = JSON.dump(message)
117
+ end
118
+ message
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,9 @@
1
+ require 'nats/client'
2
+
3
+ module CfMessageBus
4
+ class MessageBusFactory
5
+ def self.message_bus(uri)
6
+ ::NATS.connect(uri: uri, max_reconnect_attempts: Float::INFINITY)
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,78 @@
1
+ module CfMessageBus
2
+ class MockMessageBus
3
+ def initialize(config = {})
4
+ @logger = config[:logger]
5
+ @subscriptions = Hash.new { |hash, key| hash[key] = [] }
6
+ @requests = {}
7
+ @published_messages = []
8
+ end
9
+
10
+ def subscribe(subject, opts = {}, &blk)
11
+ @subscriptions[subject] << blk
12
+ subject
13
+ end
14
+
15
+ def publish(subject, message = nil, &callback)
16
+ @subscriptions[subject].each do |subscription|
17
+ subscription.call(symbolize_keys(message))
18
+ end
19
+
20
+ @published_messages.push({subject: subject, message: message, callback: callback})
21
+
22
+ callback.call if callback
23
+ end
24
+
25
+ def request(subject, data=nil, options={}, &blk)
26
+ @requests[subject] = blk
27
+ publish(subject, data)
28
+ subject
29
+ end
30
+
31
+ def synchronous_request(subject, data=nil, options={})
32
+ end
33
+
34
+ def unsubscribe(subscription_id)
35
+ @subscriptions.delete(subscription_id)
36
+ @requests.delete(subscription_id)
37
+ end
38
+
39
+ def recover(&block)
40
+ @recovery = block
41
+ end
42
+
43
+ def respond_to_request(request_subject, data)
44
+ block = @requests.fetch(request_subject) { lambda { |data| nil } }
45
+ block.call(symbolize_keys(data))
46
+ end
47
+
48
+ def do_recovery
49
+ @recovery.call if @recovery
50
+ end
51
+
52
+ def published_messages
53
+ @published_messages
54
+ end
55
+
56
+ def clear_published_messages
57
+ @published_messages.clear
58
+ end
59
+
60
+ def has_published?(subject)
61
+ @published_messages.find { |message| message[:subject] == subject }
62
+ end
63
+
64
+ def has_published_with_message?(subject, message)
65
+ @published_messages.find { |publication| publication[:subject] == subject && publication[:message] == message }
66
+ end
67
+
68
+ private
69
+
70
+ def symbolize_keys(hash)
71
+ return hash unless hash.is_a?(Hash)
72
+ hash.inject({}) do |memo, (key, value)|
73
+ memo[key.to_sym] = symbolize_keys(value)
74
+ memo
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,3 @@
1
+ module CfMessageBus
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,27 @@
1
+ require "vcap/concurrency"
2
+
3
+ module EventMachine
4
+ # Runs a block on the reactor thread and blocks the current thread
5
+ # while waiting for the result. If the block raises an exception,
6
+ # it will be re-thrown in the calling thread.
7
+ #
8
+ # @param [Block] blk The block to be executed on the reactor thread.
9
+ #
10
+ # @return [Object] The result of calling blk.
11
+ def self.schedule_sync(&blk)
12
+ promise = VCAP::Concurrency::Promise.new
13
+ EM.schedule do
14
+ begin
15
+ if blk.arity > 0
16
+ blk.call(promise)
17
+ else
18
+ promise.deliver(blk.call)
19
+ end
20
+ rescue Exception => e
21
+ promise.fail(e)
22
+ end
23
+ end
24
+
25
+ promise.resolve
26
+ end
27
+ end
metadata ADDED
@@ -0,0 +1,164 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cf-message-bus
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - CloudFoundry Core Team
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-07-16 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: bundler
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '1.3'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '1.3'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rspec
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: eventmachine
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: 1.0.0
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: 1.0.0
62
+ - !ruby/object:Gem::Dependency
63
+ name: nats
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - '='
68
+ - !ruby/object:Gem::Version
69
+ version: 0.4.26
70
+ type: :runtime
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - '='
76
+ - !ruby/object:Gem::Version
77
+ version: 0.4.26
78
+ - !ruby/object:Gem::Dependency
79
+ name: vcap-concurrency
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ type: :runtime
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ - !ruby/object:Gem::Dependency
95
+ name: yajl-ruby
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ! '>='
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ type: :runtime
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ - !ruby/object:Gem::Dependency
111
+ name: vcap-concurrency
112
+ requirement: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ! '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ! '>='
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ description: Abstraction layer around NATS messaging bus
127
+ email:
128
+ - cfpi-dev@googlegroups.com
129
+ executables: []
130
+ extensions: []
131
+ extra_rdoc_files: []
132
+ files:
133
+ - lib/cf_message_bus/message_bus.rb
134
+ - lib/cf_message_bus/message_bus_factory.rb
135
+ - lib/cf_message_bus/mock_message_bus.rb
136
+ - lib/cf_message_bus/version.rb
137
+ - lib/cf_message_bus.rb
138
+ - lib/eventmachine/schedule_sync.rb
139
+ homepage:
140
+ licenses:
141
+ - Apache
142
+ post_install_message:
143
+ rdoc_options: []
144
+ require_paths:
145
+ - lib
146
+ required_ruby_version: !ruby/object:Gem::Requirement
147
+ none: false
148
+ requirements:
149
+ - - ! '>='
150
+ - !ruby/object:Gem::Version
151
+ version: '0'
152
+ required_rubygems_version: !ruby/object:Gem::Requirement
153
+ none: false
154
+ requirements:
155
+ - - ! '>='
156
+ - !ruby/object:Gem::Version
157
+ version: '0'
158
+ requirements: []
159
+ rubyforge_project:
160
+ rubygems_version: 1.8.23
161
+ signing_key:
162
+ specification_version: 3
163
+ summary: Cloud Foundry message bus
164
+ test_files: []