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