rsmp 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.
- checksums.yaml +7 -0
- data/.gitignore +15 -0
- data/.gitmodules +3 -0
- data/.rspec +1 -0
- data/CHANGELOG.md +4 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +101 -0
- data/LICENSE +7 -0
- data/README.md +204 -0
- data/Rakefile +6 -0
- data/bin/console +51 -0
- data/bin/setup +8 -0
- data/config/site.yaml +41 -0
- data/config/supervisor.yaml +33 -0
- data/exe/rsmp +4 -0
- data/lib/rsmp/alarm.rb +15 -0
- data/lib/rsmp/archive.rb +75 -0
- data/lib/rsmp/base.rb +26 -0
- data/lib/rsmp/cli.rb +52 -0
- data/lib/rsmp/component.rb +65 -0
- data/lib/rsmp/error.rb +44 -0
- data/lib/rsmp/logger.rb +153 -0
- data/lib/rsmp/message.rb +313 -0
- data/lib/rsmp/node.rb +53 -0
- data/lib/rsmp/probe.rb +104 -0
- data/lib/rsmp/probe_collection.rb +28 -0
- data/lib/rsmp/proxy.rb +508 -0
- data/lib/rsmp/rsmp.rb +31 -0
- data/lib/rsmp/site.rb +165 -0
- data/lib/rsmp/site_base.rb +26 -0
- data/lib/rsmp/site_proxy.rb +220 -0
- data/lib/rsmp/supervisor.rb +220 -0
- data/lib/rsmp/supervisor_base.rb +10 -0
- data/lib/rsmp/supervisor_proxy.rb +292 -0
- data/lib/rsmp/version.rb +3 -0
- data/lib/rsmp/wait.rb +17 -0
- data/lib/rsmp.rb +29 -0
- data/rsmp.gemspec +43 -0
- metadata +266 -0
data/lib/rsmp/probe.rb
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
# A probe checks incoming messages and store matches
|
2
|
+
# Once it has collected what it needs, it triggers a condition variable
|
3
|
+
# and the client wakes up.
|
4
|
+
|
5
|
+
module RSMP
|
6
|
+
class Probe
|
7
|
+
attr_reader :condition, :items, :done
|
8
|
+
|
9
|
+
# block should send a message and return message just sent
|
10
|
+
def self.collect_response proxy, options={}, &block
|
11
|
+
from = proxy.archive.current_index
|
12
|
+
sent = yield
|
13
|
+
raise RuntimeError unless sent && sent[:message].is_a?(RSMP::Message)
|
14
|
+
item = proxy.archive.capture(options.merge(from: from+1, num: 1, with_message: true)) do |item|
|
15
|
+
["CommandResponse","StatusResponse","MessageNotAck"].include?(item[:message].type)
|
16
|
+
end
|
17
|
+
if item
|
18
|
+
item[:message]
|
19
|
+
else
|
20
|
+
nil
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
def initialize archive
|
26
|
+
raise ArgumentError.new("Archive expected") unless archive.is_a? Archive
|
27
|
+
@archive = archive
|
28
|
+
@items = []
|
29
|
+
@condition = Async::Notification.new
|
30
|
+
end
|
31
|
+
|
32
|
+
def capture task, options={}, &block
|
33
|
+
@options = options
|
34
|
+
@block = block
|
35
|
+
@num = options[:num]
|
36
|
+
|
37
|
+
if options[:earliest]
|
38
|
+
from = find_timestamp_index options[:earliest]
|
39
|
+
backscan from
|
40
|
+
elsif options[:from]
|
41
|
+
backscan options[:from]
|
42
|
+
end
|
43
|
+
|
44
|
+
# if backscan didn't find enough items, then
|
45
|
+
# insert ourself as probe and sleep until enough items are captured
|
46
|
+
if @items.size < @num
|
47
|
+
begin
|
48
|
+
@archive.probes.add self
|
49
|
+
task.with_timeout(options[:timeout]) do
|
50
|
+
@condition.wait
|
51
|
+
end
|
52
|
+
ensure
|
53
|
+
@archive.probes.remove self
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
if @num == 1
|
58
|
+
@items.first # if one item was requested, return item instead of array
|
59
|
+
else
|
60
|
+
@items[0..@num-1] # return array, but ensure we never return more than requested
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def find_timestamp_index earliest
|
65
|
+
(0..@archive.items.size).bsearch do |i| # use binary search to find item index
|
66
|
+
@archive.items[i][:timestamp] >= earliest
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def backscan from
|
71
|
+
from.upto(@archive.items.size-1) do |i|
|
72
|
+
return if process @archive.items[i]
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def reset
|
77
|
+
@items.clear
|
78
|
+
@done = false
|
79
|
+
end
|
80
|
+
|
81
|
+
def process item
|
82
|
+
raise ArgumentError unless item
|
83
|
+
return true if @done
|
84
|
+
if matches? item
|
85
|
+
@items << item
|
86
|
+
if @num && @items.size >= @num
|
87
|
+
@done = true
|
88
|
+
@condition.signal
|
89
|
+
return true
|
90
|
+
end
|
91
|
+
end
|
92
|
+
false
|
93
|
+
end
|
94
|
+
|
95
|
+
def matches? item
|
96
|
+
raise ArgumentError unless item
|
97
|
+
return false if @options[:type] && (item[:message] == nil || (item[:message].type != @options[:type]))
|
98
|
+
return if @options[:level] && item[:level] != @options[:level]
|
99
|
+
return false if @options[:with_message] && !(item[:direction] && item[:message])
|
100
|
+
return false if @block && @block.call(item) == false
|
101
|
+
true
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# Collection of probes
|
2
|
+
|
3
|
+
module RSMP
|
4
|
+
class ProbeCollection
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@probes = []
|
8
|
+
end
|
9
|
+
|
10
|
+
def add probe
|
11
|
+
raise ArgumentError unless probe
|
12
|
+
@probes << probe
|
13
|
+
end
|
14
|
+
|
15
|
+
def remove probe
|
16
|
+
raise ArgumentError unless probe
|
17
|
+
@probes.delete probe
|
18
|
+
end
|
19
|
+
|
20
|
+
def process item
|
21
|
+
@probes.each { |probe| probe.process item }
|
22
|
+
end
|
23
|
+
|
24
|
+
def clear
|
25
|
+
@probes.each { |probe| probe.clear }
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/lib/rsmp/proxy.rb
ADDED
@@ -0,0 +1,508 @@
|
|
1
|
+
# Base class for a connection to a remote site or supervisor.
|
2
|
+
|
3
|
+
module RSMP
|
4
|
+
class Proxy < Base
|
5
|
+
attr_reader :site_ids, :state, :archive, :connection_info
|
6
|
+
|
7
|
+
def initialize options
|
8
|
+
super options
|
9
|
+
@settings = options[:settings]
|
10
|
+
@task = options[:task]
|
11
|
+
@socket = options[:socket]
|
12
|
+
@ip = options[:ip]
|
13
|
+
@connection_info = options[:info]
|
14
|
+
clear
|
15
|
+
end
|
16
|
+
|
17
|
+
def site_id
|
18
|
+
@site_ids.first #rsmp connection can represent multiple site ids. pick the first
|
19
|
+
end
|
20
|
+
|
21
|
+
def run
|
22
|
+
start
|
23
|
+
@reader.wait if @reader
|
24
|
+
stop
|
25
|
+
end
|
26
|
+
|
27
|
+
def ready?
|
28
|
+
@state == :ready
|
29
|
+
end
|
30
|
+
|
31
|
+
def start
|
32
|
+
set_state :starting
|
33
|
+
end
|
34
|
+
|
35
|
+
def stop
|
36
|
+
return if @state == :stopped
|
37
|
+
set_state :stopping
|
38
|
+
stop_tasks
|
39
|
+
ensure
|
40
|
+
close_socket
|
41
|
+
clear
|
42
|
+
set_state :stopped
|
43
|
+
end
|
44
|
+
|
45
|
+
def clear
|
46
|
+
@site_ids = []
|
47
|
+
@awaiting_acknowledgement = {}
|
48
|
+
@latest_watchdog_received = nil
|
49
|
+
@watchdog_started = false
|
50
|
+
@version_determined = false
|
51
|
+
@ingoing_acknowledged = {}
|
52
|
+
@outgoing_acknowledged = {}
|
53
|
+
@latest_watchdog_send_at = nil
|
54
|
+
|
55
|
+
@state_condition = Async::Notification.new
|
56
|
+
@acknowledgements = {}
|
57
|
+
@acknowledgement_condition = Async::Notification.new
|
58
|
+
end
|
59
|
+
|
60
|
+
def close_socket
|
61
|
+
if @stream
|
62
|
+
@stream.close
|
63
|
+
@stream = nil
|
64
|
+
end
|
65
|
+
|
66
|
+
if @socket
|
67
|
+
@socket.close
|
68
|
+
@socket = nil
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def start_reader
|
73
|
+
@reader = @task.async do |task|
|
74
|
+
task.annotate "reader"
|
75
|
+
@stream = Async::IO::Stream.new(@socket)
|
76
|
+
@protocol = Async::IO::Protocol::Line.new(@stream,"\f") # rsmp messages are json terminated with a form-feed
|
77
|
+
|
78
|
+
while packet = @protocol.read_line
|
79
|
+
beginning = Time.now
|
80
|
+
message = process_packet packet
|
81
|
+
duration = Time.now - beginning
|
82
|
+
ms = (duration*1000).round(4)
|
83
|
+
per_second = (1.0 / duration).round
|
84
|
+
if message
|
85
|
+
type = message.type
|
86
|
+
m_id = Logger.shorten_message_id(message.m_id)
|
87
|
+
else
|
88
|
+
type = 'Unknown'
|
89
|
+
m_id = nil
|
90
|
+
end
|
91
|
+
str = [type,m_id,"processed in #{ms}ms, #{per_second}req/s"].compact.join(' ')
|
92
|
+
log str, level: :statistics
|
93
|
+
end
|
94
|
+
rescue Async::Wrapper::Cancelled
|
95
|
+
# ignore
|
96
|
+
rescue EOFError
|
97
|
+
log "Connection closed", level: :warning
|
98
|
+
rescue IOError => e
|
99
|
+
log "IOError: #{e}", level: :warning
|
100
|
+
rescue Errno::ECONNRESET
|
101
|
+
log "Connection reset by peer", level: :warning
|
102
|
+
rescue Errno::EPIPE
|
103
|
+
log "Broken pipe", level: :warning
|
104
|
+
rescue SystemCallError => e # all ERRNO errors
|
105
|
+
log "Proxy exception: #{e.to_s}", level: :error
|
106
|
+
rescue StandardError => e
|
107
|
+
log ["Proxy exception: #{e.inspect}",e.backtrace].flatten.join("\n"), level: :error
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def start_watchdog
|
112
|
+
log "Starting watchdog with interval #{@settings["watchdog_interval"]} seconds", level: :debug
|
113
|
+
send_watchdog
|
114
|
+
@watchdog_started = true
|
115
|
+
end
|
116
|
+
|
117
|
+
def start_timer
|
118
|
+
name = "timer"
|
119
|
+
interval = @settings["timer_interval"] || 1
|
120
|
+
log "Starting #{name} with interval #{interval} seconds", level: :debug
|
121
|
+
@latest_watchdog_received = RSMP.now_object
|
122
|
+
@timer = @task.async do |task|
|
123
|
+
task.annotate "timer"
|
124
|
+
loop do
|
125
|
+
now = RSMP.now_object
|
126
|
+
break if timer(now) == false
|
127
|
+
rescue StandardError => e
|
128
|
+
log ["#{name} exception: #{e}",e.backtrace].flatten.join("\n"), level: :error
|
129
|
+
ensure
|
130
|
+
task.sleep interval
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def timer now
|
136
|
+
check_watchdog_send_time now
|
137
|
+
return false if check_ack_timeout now
|
138
|
+
return false if check_watchdog_timeout now
|
139
|
+
end
|
140
|
+
|
141
|
+
def check_watchdog_send_time now
|
142
|
+
return unless @watchdog_started
|
143
|
+
return if @settings["watchdog_interval"] == :never
|
144
|
+
|
145
|
+
if @latest_watchdog_send_at == nil
|
146
|
+
send_watchdog now
|
147
|
+
else
|
148
|
+
# we add half the timer interval to pick the timer
|
149
|
+
# event closes to the wanted wathcdog interval
|
150
|
+
diff = now - @latest_watchdog_send_at
|
151
|
+
if (diff + 0.5*@settings["timer_interval"]) >= (@settings["watchdog_interval"])
|
152
|
+
send_watchdog now
|
153
|
+
end
|
154
|
+
end
|
155
|
+
rescue StandardError => e
|
156
|
+
log ["Watchdog error: #{e}",e.backtrace].flatten.join("\n"), level: :error
|
157
|
+
end
|
158
|
+
|
159
|
+
def send_watchdog now=nil
|
160
|
+
now = RSMP.now_object unless nil
|
161
|
+
message = Watchdog.new( {"wTs" => RSMP.now_object_to_string(now)})
|
162
|
+
send_message message
|
163
|
+
@latest_watchdog_send_at = now
|
164
|
+
end
|
165
|
+
|
166
|
+
def check_ack_timeout now
|
167
|
+
timeout = @settings["acknowledgement_timeout"]
|
168
|
+
# hash cannot be modify during iteration, so clone it
|
169
|
+
@awaiting_acknowledgement.clone.each_pair do |m_id, message|
|
170
|
+
latest = message.timestamp + timeout
|
171
|
+
if now > latest
|
172
|
+
log "No acknowledgements for #{message.type} within #{timeout} seconds", level: :error
|
173
|
+
stop
|
174
|
+
return true
|
175
|
+
end
|
176
|
+
end
|
177
|
+
false
|
178
|
+
end
|
179
|
+
|
180
|
+
def check_watchdog_timeout now
|
181
|
+
timeout = @settings["watchdog_timeout"]
|
182
|
+
latest = @latest_watchdog_received + timeout
|
183
|
+
if now > latest
|
184
|
+
log "No Watchdog within #{timeout} seconds, received at #{@latest_watchdog_received}, now is #{now}, diff #{now-latest}", level: :error
|
185
|
+
stop
|
186
|
+
return true
|
187
|
+
end
|
188
|
+
false
|
189
|
+
end
|
190
|
+
|
191
|
+
def stop_tasks
|
192
|
+
@timer.stop if @timer
|
193
|
+
@reader.stop if @reader
|
194
|
+
end
|
195
|
+
|
196
|
+
def log str, options={}
|
197
|
+
super str, options.merge(ip: @ip, port: @port, site_id: site_id)
|
198
|
+
end
|
199
|
+
|
200
|
+
def send_message message, reason=nil
|
201
|
+
raise IOError unless @protocol
|
202
|
+
message.validate
|
203
|
+
message.generate_json
|
204
|
+
message.direction = :out
|
205
|
+
expect_acknowledgement message
|
206
|
+
@protocol.write_lines message.out
|
207
|
+
log_send message, reason
|
208
|
+
rescue EOFError, IOError
|
209
|
+
buffer_message message
|
210
|
+
rescue SchemaError => e
|
211
|
+
log "Error sending #{message.type}, schema validation failed: #{e.message}", message: message, level: :error
|
212
|
+
end
|
213
|
+
|
214
|
+
def buffer_message message
|
215
|
+
# TODO
|
216
|
+
log "Cannot send #{message.type} because the connection is closed.", message: message, level: :error
|
217
|
+
end
|
218
|
+
|
219
|
+
def log_send message, reason=nil
|
220
|
+
if reason
|
221
|
+
str = "Sent #{message.type} #{reason}"
|
222
|
+
else
|
223
|
+
str = "Sent #{message.type}"
|
224
|
+
end
|
225
|
+
|
226
|
+
if message.type == "MessageNotAck"
|
227
|
+
log str, message: message, level: :warning
|
228
|
+
else
|
229
|
+
log str, message: message, level: :log
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
def process_packet packet
|
234
|
+
attributes = Message.parse_attributes packet
|
235
|
+
message = Message.build attributes, packet
|
236
|
+
message.validate
|
237
|
+
expect_version_message(message) unless @version_determined
|
238
|
+
process_message message
|
239
|
+
message
|
240
|
+
rescue InvalidPacket => e
|
241
|
+
warning "Received invalid package, must be valid JSON but got #{packet.size} bytes: #{e.message}"
|
242
|
+
nil
|
243
|
+
rescue MalformedMessage => e
|
244
|
+
warning "Received malformed message, #{e.message}", Malformed.new(attributes)
|
245
|
+
# cannot send NotAcknowledged for a malformed message since we can't read it, just ignore it
|
246
|
+
nil
|
247
|
+
rescue SchemaError => e
|
248
|
+
dont_acknowledge message, "Received", "invalid #{message.type}, schema errors: #{e.message}"
|
249
|
+
message
|
250
|
+
rescue InvalidMessage => e
|
251
|
+
dont_acknowledge message, "Received", "invalid #{message.type}, #{e.message}"
|
252
|
+
message
|
253
|
+
rescue FatalError => e
|
254
|
+
dont_acknowledge message, "Rejected #{message.type},", "#{e.message}"
|
255
|
+
stop
|
256
|
+
message
|
257
|
+
end
|
258
|
+
|
259
|
+
def process_message message
|
260
|
+
case message
|
261
|
+
when MessageAck
|
262
|
+
process_ack message
|
263
|
+
when MessageNotAck
|
264
|
+
process_not_ack message
|
265
|
+
when Version
|
266
|
+
process_version message
|
267
|
+
when Watchdog
|
268
|
+
process_watchdog message
|
269
|
+
else
|
270
|
+
dont_acknowledge message, "Received", "unknown message (#{message.type})"
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
def will_not_handle message
|
275
|
+
reason = "since we're a #{self.class.name.downcase}" unless reason
|
276
|
+
log "Ignoring #{message.type}, #{reason}", message: message, level: :warning
|
277
|
+
dont_acknowledge message, nil, reason
|
278
|
+
end
|
279
|
+
|
280
|
+
def expect_acknowledgement message
|
281
|
+
unless message.is_a?(MessageAck) || message.is_a?(MessageNotAck)
|
282
|
+
@awaiting_acknowledgement[message.m_id] = message
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
def dont_expect_acknowledgement message
|
287
|
+
@awaiting_acknowledgement.delete message.attribute("oMId")
|
288
|
+
end
|
289
|
+
|
290
|
+
def extraneous_version message
|
291
|
+
dont_acknowledge message, "Received", "extraneous Version message"
|
292
|
+
end
|
293
|
+
|
294
|
+
def check_rsmp_version message
|
295
|
+
# find versions that both we and the client support
|
296
|
+
candidates = message.versions & @settings["rsmp_versions"]
|
297
|
+
if candidates.any?
|
298
|
+
# pick latest version
|
299
|
+
version = candidates.sort.last
|
300
|
+
return version
|
301
|
+
else
|
302
|
+
raise FatalError.new "RSMP versions [#{message.versions.join(',')}] requested, but only [#{@settings["rsmp_versions"].join(',')}] supported."
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
def process_version message
|
307
|
+
return extraneous_version message if @version_determined
|
308
|
+
check_site_ids message
|
309
|
+
site_ids_changed
|
310
|
+
rsmp_version = check_rsmp_version message
|
311
|
+
set_state :version_determined
|
312
|
+
check_sxl_version
|
313
|
+
version_accepted message, rsmp_version
|
314
|
+
end
|
315
|
+
|
316
|
+
def site_ids_changed
|
317
|
+
end
|
318
|
+
|
319
|
+
def check_sxl_version
|
320
|
+
end
|
321
|
+
|
322
|
+
def acknowledge original
|
323
|
+
raise InvalidArgument unless original
|
324
|
+
ack = MessageAck.build_from(original)
|
325
|
+
ack.original = original.clone
|
326
|
+
send_message ack, "for #{ack.original.type} #{original.m_id_short}"
|
327
|
+
check_ingoing_acknowledged original
|
328
|
+
end
|
329
|
+
|
330
|
+
def dont_acknowledge original, prefix=nil, reason=nil
|
331
|
+
raise InvalidArgument unless original
|
332
|
+
str = [prefix,reason].join(' ')
|
333
|
+
log str, message: original, level: :warning if reason
|
334
|
+
message = MessageNotAck.new({
|
335
|
+
"oMId" => original.m_id,
|
336
|
+
"rea" => reason || "Unknown reason"
|
337
|
+
})
|
338
|
+
message.original = original.clone
|
339
|
+
send_message message, "for #{original.type} #{original.m_id_short}"
|
340
|
+
end
|
341
|
+
|
342
|
+
def set_state state
|
343
|
+
@state = state
|
344
|
+
@state_condition.signal @state
|
345
|
+
end
|
346
|
+
|
347
|
+
def wait_for_state state, timeout
|
348
|
+
states = [state].flatten
|
349
|
+
return if states.include?(@state)
|
350
|
+
RSMP::Wait.wait_for(@task,@state_condition,timeout) do |s|
|
351
|
+
states.include?(@state)
|
352
|
+
end
|
353
|
+
@state
|
354
|
+
end
|
355
|
+
|
356
|
+
def send_version rsmp_versions
|
357
|
+
versions_hash = [rsmp_versions].flatten.map {|v| {"vers" => v} }
|
358
|
+
version_response = Version.new({
|
359
|
+
"RSMP"=>versions_hash,
|
360
|
+
"siteId"=>[{"sId"=>@settings["site_id"]}],
|
361
|
+
"SXL"=>"1.1"
|
362
|
+
})
|
363
|
+
send_message version_response
|
364
|
+
end
|
365
|
+
|
366
|
+
def find_original_for_message message
|
367
|
+
@awaiting_acknowledgement[ message.attribute("oMId") ]
|
368
|
+
end
|
369
|
+
|
370
|
+
# TODO this might be better handled by a proper event machine using e.g. the EventMachine gem
|
371
|
+
def check_outgoing_acknowledged message
|
372
|
+
unless @outgoing_acknowledged[message.type]
|
373
|
+
@outgoing_acknowledged[message.type] = true
|
374
|
+
acknowledged_first_outgoing message
|
375
|
+
end
|
376
|
+
end
|
377
|
+
|
378
|
+
def check_ingoing_acknowledged message
|
379
|
+
unless @ingoing_acknowledged[message.type]
|
380
|
+
@ingoing_acknowledged[message.type] = true
|
381
|
+
acknowledged_first_ingoing message
|
382
|
+
end
|
383
|
+
end
|
384
|
+
|
385
|
+
def acknowledged_first_outgoing message
|
386
|
+
end
|
387
|
+
|
388
|
+
def acknowledged_first_ingoing message
|
389
|
+
end
|
390
|
+
|
391
|
+
def process_ack message
|
392
|
+
original = find_original_for_message message
|
393
|
+
if original
|
394
|
+
dont_expect_acknowledgement message
|
395
|
+
message.original = original
|
396
|
+
log_acknowledgement_for_original message, original
|
397
|
+
|
398
|
+
if original.type == "Version"
|
399
|
+
version_acknowledged
|
400
|
+
end
|
401
|
+
|
402
|
+
check_outgoing_acknowledged original
|
403
|
+
|
404
|
+
@acknowledgements[ original.m_id ] = message
|
405
|
+
@acknowledgement_condition.signal message
|
406
|
+
else
|
407
|
+
log_acknowledgement_for_unknown message
|
408
|
+
end
|
409
|
+
end
|
410
|
+
|
411
|
+
def process_not_ack message
|
412
|
+
original = find_original_for_message message
|
413
|
+
if original
|
414
|
+
dont_expect_acknowledgement message
|
415
|
+
message.original = original
|
416
|
+
log_acknowledgement_for_original message, original
|
417
|
+
@acknowledgements[ original.m_id ] = message
|
418
|
+
@acknowledgement_condition.signal message
|
419
|
+
else
|
420
|
+
log_acknowledgement_for_unknown message
|
421
|
+
end
|
422
|
+
end
|
423
|
+
|
424
|
+
def log_acknowledgement_for_original message, original
|
425
|
+
str = "Received #{message.type} for #{original.type} #{message.attribute("oMId")[0..3]}"
|
426
|
+
if message.type == 'MessageNotAck'
|
427
|
+
reason = message.attributes["rea"]
|
428
|
+
str = "#{str}: #{reason}" if reason
|
429
|
+
log str, message: message, level: :warning
|
430
|
+
else
|
431
|
+
log str, message: message, level: :log
|
432
|
+
end
|
433
|
+
end
|
434
|
+
|
435
|
+
def log_acknowledgement_for_unknown message
|
436
|
+
log "Received #{message.type} for unknown message #{message.attribute("oMId")[0..3]}", message: message, level: :warning
|
437
|
+
end
|
438
|
+
|
439
|
+
def process_watchdog message
|
440
|
+
log "Received #{message.type}", message: message, level: :log
|
441
|
+
@latest_watchdog_received = RSMP.now_object
|
442
|
+
acknowledge message
|
443
|
+
end
|
444
|
+
|
445
|
+
def expect_version_message message
|
446
|
+
unless message.is_a?(Version) || message.is_a?(MessageAck) || message.is_a?(MessageNotAck)
|
447
|
+
raise FatalError.new "Version must be received first"
|
448
|
+
end
|
449
|
+
end
|
450
|
+
|
451
|
+
def connection_complete
|
452
|
+
set_state :ready
|
453
|
+
end
|
454
|
+
|
455
|
+
def check_site_ids message
|
456
|
+
message.attribute("siteId").map { |item| item["sId"] }.each do |site_id|
|
457
|
+
check_site_id site_id
|
458
|
+
@site_ids << site_id
|
459
|
+
end
|
460
|
+
end
|
461
|
+
|
462
|
+
def check_site_id site_id
|
463
|
+
end
|
464
|
+
|
465
|
+
def site_id_accetable? site_id
|
466
|
+
true
|
467
|
+
end
|
468
|
+
|
469
|
+
def add_site_id site_id
|
470
|
+
@site_ids << site_id
|
471
|
+
end
|
472
|
+
|
473
|
+
def version_acknowledged
|
474
|
+
end
|
475
|
+
|
476
|
+
def wait_for_acknowledgement original, timeout, options={}
|
477
|
+
raise ArgumentError unless original
|
478
|
+
RSMP::Wait.wait_for(@task,@acknowledgement_condition,timeout) do |message|
|
479
|
+
message.is_a?(MessageAck) &&
|
480
|
+
message.attributes["oMId"] == original.m_id
|
481
|
+
end
|
482
|
+
end
|
483
|
+
|
484
|
+
def wait_for_not_acknowledged original, timeout
|
485
|
+
raise ArgumentError unless original
|
486
|
+
RSMP::Wait.wait_for(@task,@acknowledgement_condition,timeout) do |message|
|
487
|
+
message.is_a?(MessageNotAck) &&
|
488
|
+
message.attributes["oMId"] == original.m_id
|
489
|
+
end
|
490
|
+
end
|
491
|
+
|
492
|
+
def wait_for_acknowledgements timeout
|
493
|
+
return if @awaiting_acknowledgement.empty?
|
494
|
+
RSMP::Wait.wait_for(@task,@acknowledgement_condition,timeout) do |message|
|
495
|
+
@awaiting_acknowledgement.empty?
|
496
|
+
end
|
497
|
+
end
|
498
|
+
|
499
|
+
def node
|
500
|
+
raise 'Must be overridden'
|
501
|
+
end
|
502
|
+
|
503
|
+
def author
|
504
|
+
node.site_id
|
505
|
+
end
|
506
|
+
|
507
|
+
end
|
508
|
+
end
|
data/lib/rsmp/rsmp.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# RSMP module
|
2
|
+
|
3
|
+
module RSMP
|
4
|
+
WRAPPING_DELIMITER = "\f"
|
5
|
+
|
6
|
+
def self.now_object
|
7
|
+
# date using UTC time zone
|
8
|
+
Time.now.utc
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.now_object_to_string now
|
12
|
+
# date in the format required by rsmp, using UTC time zone
|
13
|
+
# example: 2015-06-08T12:01:39.654Z
|
14
|
+
time ||= now.utc
|
15
|
+
time.strftime("%FT%T.%3NZ")
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.now_string time=nil
|
19
|
+
time ||= Time.now
|
20
|
+
now_object_to_string time
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.parse_time time_str
|
24
|
+
Time.parse time_str
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.log_prefix ip
|
28
|
+
"#{now_string} #{ip.ljust(20)}"
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|