cf-message-bus 0.0.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,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: []