rsmp 0.37.0 → 0.38.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.
Files changed (106) hide show
  1. checksums.yaml +4 -4
  2. data/.devcontainer/devcontainer.json +22 -0
  3. data/.github/workflows/rubocop.yaml +17 -0
  4. data/.gitignore +5 -6
  5. data/.rubocop.yml +80 -0
  6. data/Gemfile +13 -1
  7. data/Gemfile.lock +34 -1
  8. data/Rakefile +3 -3
  9. data/lib/rsmp/cli.rb +147 -124
  10. data/lib/rsmp/collect/ack_collector.rb +8 -7
  11. data/lib/rsmp/collect/aggregated_status_collector.rb +4 -4
  12. data/lib/rsmp/collect/alarm_collector.rb +31 -23
  13. data/lib/rsmp/collect/alarm_matcher.rb +3 -3
  14. data/lib/rsmp/collect/collector/logging.rb +17 -0
  15. data/lib/rsmp/collect/collector/reporting.rb +44 -0
  16. data/lib/rsmp/collect/collector/status.rb +34 -0
  17. data/lib/rsmp/collect/collector.rb +69 -150
  18. data/lib/rsmp/collect/command_matcher.rb +19 -6
  19. data/lib/rsmp/collect/command_response_collector.rb +7 -7
  20. data/lib/rsmp/collect/distributor.rb +14 -11
  21. data/lib/rsmp/collect/filter.rb +31 -15
  22. data/lib/rsmp/collect/matcher.rb +7 -11
  23. data/lib/rsmp/collect/queue.rb +4 -4
  24. data/lib/rsmp/collect/receiver.rb +10 -12
  25. data/lib/rsmp/collect/state_collector.rb +116 -77
  26. data/lib/rsmp/collect/status_collector.rb +6 -6
  27. data/lib/rsmp/collect/status_matcher.rb +17 -7
  28. data/lib/rsmp/{alarm_state.rb → component/alarm_state.rb} +76 -37
  29. data/lib/rsmp/{component.rb → component/component.rb} +15 -15
  30. data/lib/rsmp/component/component_base.rb +89 -0
  31. data/lib/rsmp/component/component_proxy.rb +75 -0
  32. data/lib/rsmp/component/components.rb +63 -0
  33. data/lib/rsmp/convert/export/json_schema.rb +116 -110
  34. data/lib/rsmp/convert/import/yaml.rb +21 -18
  35. data/lib/rsmp/{rsmp.rb → helpers/clock.rb} +5 -6
  36. data/lib/rsmp/{deep_merge.rb → helpers/deep_merge.rb} +2 -1
  37. data/lib/rsmp/helpers/error.rb +71 -0
  38. data/lib/rsmp/{inspect.rb → helpers/inspect.rb} +6 -10
  39. data/lib/rsmp/log/archive.rb +98 -0
  40. data/lib/rsmp/log/colorization.rb +41 -0
  41. data/lib/rsmp/log/filtering.rb +54 -0
  42. data/lib/rsmp/log/logger.rb +206 -0
  43. data/lib/rsmp/{logging.rb → log/logging.rb} +5 -7
  44. data/lib/rsmp/message.rb +159 -148
  45. data/lib/rsmp/{node.rb → node/node.rb} +19 -17
  46. data/lib/rsmp/{protocol.rb → node/protocol.rb} +5 -3
  47. data/lib/rsmp/node/site/site.rb +195 -0
  48. data/lib/rsmp/node/supervisor/modules/configuration.rb +59 -0
  49. data/lib/rsmp/node/supervisor/modules/connection.rb +140 -0
  50. data/lib/rsmp/node/supervisor/modules/sites.rb +64 -0
  51. data/lib/rsmp/node/supervisor/supervisor.rb +72 -0
  52. data/lib/rsmp/{task.rb → node/task.rb} +12 -14
  53. data/lib/rsmp/proxy/modules/acknowledgements.rb +144 -0
  54. data/lib/rsmp/proxy/modules/receive.rb +119 -0
  55. data/lib/rsmp/proxy/modules/send.rb +76 -0
  56. data/lib/rsmp/proxy/modules/state.rb +25 -0
  57. data/lib/rsmp/proxy/modules/tasks.rb +105 -0
  58. data/lib/rsmp/proxy/modules/versions.rb +69 -0
  59. data/lib/rsmp/proxy/modules/watchdogs.rb +66 -0
  60. data/lib/rsmp/proxy/proxy.rb +199 -0
  61. data/lib/rsmp/proxy/site/modules/aggregated_status.rb +52 -0
  62. data/lib/rsmp/proxy/site/modules/alarms.rb +27 -0
  63. data/lib/rsmp/proxy/site/modules/commands.rb +31 -0
  64. data/lib/rsmp/proxy/site/modules/status.rb +110 -0
  65. data/lib/rsmp/proxy/site/site_proxy.rb +205 -0
  66. data/lib/rsmp/proxy/supervisor/modules/aggregated_status.rb +47 -0
  67. data/lib/rsmp/proxy/supervisor/modules/alarms.rb +73 -0
  68. data/lib/rsmp/proxy/supervisor/modules/commands.rb +53 -0
  69. data/lib/rsmp/proxy/supervisor/modules/status.rb +204 -0
  70. data/lib/rsmp/proxy/supervisor/supervisor_proxy.rb +178 -0
  71. data/lib/rsmp/tlc/detector_logic.rb +18 -34
  72. data/lib/rsmp/tlc/input_states.rb +126 -0
  73. data/lib/rsmp/tlc/modules/detector_logics.rb +50 -0
  74. data/lib/rsmp/tlc/modules/display.rb +78 -0
  75. data/lib/rsmp/tlc/modules/helpers.rb +41 -0
  76. data/lib/rsmp/tlc/modules/inputs.rb +173 -0
  77. data/lib/rsmp/tlc/modules/modes.rb +253 -0
  78. data/lib/rsmp/tlc/modules/outputs.rb +30 -0
  79. data/lib/rsmp/tlc/modules/plans.rb +218 -0
  80. data/lib/rsmp/tlc/modules/signal_groups.rb +109 -0
  81. data/lib/rsmp/tlc/modules/startup_sequence.rb +22 -0
  82. data/lib/rsmp/tlc/modules/system.rb +140 -0
  83. data/lib/rsmp/tlc/modules/traffic_data.rb +49 -0
  84. data/lib/rsmp/tlc/signal_group.rb +37 -41
  85. data/lib/rsmp/tlc/signal_plan.rb +14 -11
  86. data/lib/rsmp/tlc/signal_priority.rb +39 -35
  87. data/lib/rsmp/tlc/startup_sequence.rb +59 -0
  88. data/lib/rsmp/tlc/traffic_controller.rb +38 -1010
  89. data/lib/rsmp/tlc/traffic_controller_site.rb +58 -57
  90. data/lib/rsmp/version.rb +1 -1
  91. data/lib/rsmp.rb +82 -48
  92. data/rsmp.gemspec +24 -31
  93. metadata +79 -139
  94. data/lib/rsmp/archive.rb +0 -76
  95. data/lib/rsmp/collect/message_matchers.rb +0 -0
  96. data/lib/rsmp/component_base.rb +0 -87
  97. data/lib/rsmp/component_proxy.rb +0 -57
  98. data/lib/rsmp/components.rb +0 -65
  99. data/lib/rsmp/error.rb +0 -71
  100. data/lib/rsmp/logger.rb +0 -216
  101. data/lib/rsmp/proxy.rb +0 -693
  102. data/lib/rsmp/site.rb +0 -188
  103. data/lib/rsmp/site_proxy.rb +0 -389
  104. data/lib/rsmp/supervisor.rb +0 -302
  105. data/lib/rsmp/supervisor_proxy.rb +0 -510
  106. data/lib/rsmp/tlc/inputs.rb +0 -134
data/lib/rsmp/proxy.rb DELETED
@@ -1,693 +0,0 @@
1
- # A connection to a remote site or supervisor.
2
- # Uses the Task module to handle asyncronous work, but adds
3
- # the concept of a connection that can be connected or disconnected.
4
-
5
- require 'rubygems'
6
-
7
- module RSMP
8
- class Proxy
9
- WRAPPING_DELIMITER = "\f"
10
-
11
- include Logging
12
- include Distributor
13
- include Inspect
14
- include Task
15
-
16
- attr_reader :state, :archive, :connection_info, :sxl, :collector, :ip, :port, :node, :core_version
17
- def initialize options
18
- @node = options[:node]
19
- initialize_logging options
20
- initialize_distributor
21
- initialize_task
22
- setup options
23
- clear
24
- @state = :disconnected
25
- @state_condition = Async::Notification.new
26
- end
27
-
28
-
29
- def now
30
- node.now
31
- end
32
-
33
- def disconnect
34
- end
35
-
36
-
37
- # wait for the reader task to complete,
38
- # which is not expected to happen before the connection is closed
39
- def wait_for_reader
40
- @reader.wait if @reader
41
- end
42
-
43
- # close connection, but keep our main task running so we can reconnect
44
- def close
45
- log "Closing connection", level: :warning
46
- close_stream
47
- close_socket
48
- stop_reader
49
- set_state :disconnected
50
- distribute_error DisconnectError.new("Connection was closed")
51
-
52
- # stop timer
53
- # as we're running inside the timer, code after stop_timer() will not be called,
54
- # unless it's in the ensure block
55
- stop_timer
56
- end
57
-
58
- def stop_subtasks
59
- stop_timer
60
- stop_reader
61
- clear
62
- super
63
- end
64
-
65
- def stop_timer
66
- @timer.stop if @timer
67
- ensure
68
- @timer = nil
69
- end
70
-
71
- def stop_reader
72
- @reader.stop if @reader
73
- ensure
74
- @reader = nil
75
- end
76
-
77
- def close_stream
78
- @stream.close if @stream
79
- ensure
80
- @stream = nil
81
- end
82
-
83
- def close_socket
84
- @socket.close if @socket
85
- ensure
86
- @socket = nil
87
- end
88
-
89
- def stop_task
90
- close
91
- super
92
- end
93
-
94
- # change our state
95
- def set_state state
96
- return if state == @state
97
- @state = state
98
- state_changed
99
- end
100
-
101
- # the state changed
102
- # override to to things like notifications
103
- def state_changed
104
- @state_condition.signal @state
105
- end
106
-
107
- # revive after a reconnect
108
- def revive options
109
- setup options
110
- end
111
-
112
- def setup options
113
- @settings = options[:settings]
114
- @socket = options[:socket]
115
- @stream = options[:stream]
116
- @protocol = options[:protocol]
117
- @ip = options[:ip]
118
- @port = options[:port]
119
- @connection_info = options[:info]
120
- @sxl = nil
121
- @site_settings = nil # can't pick until we know the site id
122
- if options[:collect]
123
- @collector = RSMP::Collector.new self, options[:collect]
124
- @collector.start
125
- end
126
- end
127
-
128
- def inspect
129
- "#<#{self.class.name}:#{self.object_id}, #{inspector(
130
- :@acknowledgements,:@settings,:@site_settings
131
- )}>"
132
- end
133
-
134
- def clock
135
- @node.clock
136
- end
137
-
138
- def ready?
139
- @state == :ready
140
- end
141
-
142
- def connected?
143
- @state == :connected || @state == :ready
144
- end
145
-
146
- def disconnected?
147
- @state == :disconnected
148
- end
149
-
150
- def clear
151
- @awaiting_acknowledgement = {}
152
- @latest_watchdog_received = nil
153
- @watchdog_started = false
154
- @version_determined = false
155
- @ingoing_acknowledged = {}
156
- @outgoing_acknowledged = {}
157
- @latest_watchdog_send_at = nil
158
-
159
- @acknowledgements = {}
160
- @acknowledgement_condition = Async::Notification.new
161
- end
162
-
163
- # run an async task that reads from @socket
164
- def start_reader
165
- @reader = @task.async do |task|
166
- task.annotate "reader"
167
- run_reader
168
- end
169
- end
170
-
171
- def run_reader
172
- @stream ||= IO::Stream::Buffered.new(@socket)
173
- @protocol ||= RSMP::Protocol.new(@stream) # rsmp messages are json terminated with a form-feed
174
- loop do
175
- read_line
176
- end
177
- rescue Restart
178
- log "Closing connection", level: :warning
179
- raise
180
- rescue EOFError, Async::Stop
181
- log "Connection closed", level: :warning
182
- rescue IOError => e
183
- log "IOError: #{e}", level: :warning
184
- rescue Errno::ECONNRESET
185
- log "Connection reset by peer", level: :warning
186
- rescue Errno::EPIPE
187
- log "Broken pipe", level: :warning
188
- rescue StandardError => e
189
- distribute_error e, level: :internal
190
- end
191
-
192
- def read_line
193
- json = @protocol.read_line
194
- beginning = Time.now
195
- message = process_packet json
196
- duration = Time.now - beginning
197
- ms = (duration*1000).round(4)
198
- if duration > 0
199
- per_second = (1.0 / duration).round
200
- else
201
- per_second = Float::INFINITY
202
- end
203
- if message
204
- type = message.type
205
- m_id = Logger.shorten_message_id(message.m_id)
206
- else
207
- type = 'Unknown'
208
- m_id = nil
209
- end
210
- str = [type,m_id,"processed in #{ms}ms, #{per_second}req/s"].compact.join(' ')
211
- log str, level: :statistics
212
- end
213
-
214
- def receive_error e, options={}
215
- @node.receive_error e, options
216
- end
217
-
218
- def start_watchdog
219
- log "Starting watchdog with interval #{@site_settings['intervals']['watchdog']} seconds", level: :debug
220
- @watchdog_started = true
221
- end
222
-
223
- def stop_watchdog
224
- log "Stopping watchdog", level: :debug
225
- @watchdog_started = false
226
- end
227
-
228
- def with_watchdog_disabled
229
- was = @watchdog_started
230
- stop_watchdog if was
231
- yield
232
- ensure
233
- start_watchdog if was
234
- end
235
-
236
-
237
- def start_timer
238
- return if @timer
239
- name = "timer"
240
- interval = @site_settings['intervals']['timer'] || 1
241
- log "Starting #{name} with interval #{interval} seconds", level: :debug
242
- @latest_watchdog_received = Clock.now
243
- @timer = @task.async do |task|
244
- task.annotate "timer"
245
- run_timer task, interval
246
- end
247
- end
248
-
249
- def run_timer task, interval
250
- next_time = Time.now.to_f
251
- loop do
252
- begin
253
- now = Clock.now
254
- timer(now)
255
- rescue RSMP::Schema::Error => e
256
- log "Timer: Schema error: #{e}", level: :warning
257
- rescue EOFError => e
258
- log "Timer: Connection closed: #{e}", level: :warning
259
- rescue IOError => e
260
- log "Timer: IOError", level: :warning
261
- rescue Errno::ECONNRESET
262
- log "Timer: Connection reset by peer", level: :warning
263
- rescue Errno::EPIPE => e
264
- log "Timer: Broken pipe", level: :warning
265
- rescue StandardError => e
266
- distribute_error e, level: :internal
267
- end
268
- ensure
269
- next_time += interval
270
- duration = next_time - Time.now.to_f
271
- task.sleep duration
272
- end
273
- end
274
-
275
- def timer now
276
- watchdog_send_timer now
277
- check_ack_timeout now
278
- check_watchdog_timeout now
279
- end
280
-
281
- def watchdog_send_timer now
282
- return unless @watchdog_started
283
- return if @site_settings['intervals']['watchdog'] == :never
284
- if @latest_watchdog_send_at == nil
285
- send_watchdog now
286
- else
287
- # we add half the timer interval to pick the timer
288
- # event closes to the wanted wathcdog interval
289
- diff = now - @latest_watchdog_send_at
290
- if (diff + 0.5*@site_settings['intervals']['timer']) >= (@site_settings['intervals']['watchdog'])
291
- send_watchdog now
292
- end
293
- end
294
- end
295
-
296
- def send_watchdog now=Clock.now
297
- message = Watchdog.new( {"wTs" => clock.to_s})
298
- send_message message
299
- @latest_watchdog_send_at = now
300
- end
301
-
302
- def check_ack_timeout now
303
- timeout = @site_settings['timeouts']['acknowledgement']
304
- # hash cannot be modify during iteration, so clone it
305
- @awaiting_acknowledgement.clone.each_pair do |m_id, message|
306
- latest = message.timestamp + timeout
307
- if now > latest
308
- str = "No acknowledgements for #{message.type} #{message.m_id_short} within #{timeout} seconds"
309
- log str, level: :error
310
- begin
311
- close
312
- ensure
313
- distribute_error MissingAcknowledgment.new(str)
314
- end
315
- end
316
- end
317
- end
318
-
319
- def check_watchdog_timeout now
320
- timeout = @site_settings['timeouts']['watchdog']
321
- latest = @latest_watchdog_received + timeout
322
- left = latest - now
323
- if left < 0
324
- str = "No Watchdog received within #{timeout} seconds"
325
- log str, level: :warning
326
- distribute MissingWatchdog.new(str)
327
- end
328
- end
329
-
330
- def log str, options={}
331
- super str, options.merge(ip: @ip, port: @port, site_id: @site_id)
332
- end
333
-
334
- def get_schemas
335
- schemas = { core: RSMP::Schema.latest_core_version } # use latest core
336
- schemas[:core] = core_version if core_version
337
- schemas[sxl] = RSMP::Schema.sanitize_version(sxl_version.to_s) if sxl && sxl_version
338
- schemas
339
- end
340
-
341
- def send_message message, reason=nil, validate: true, force: false
342
- raise NotReady unless connected? unless force
343
- raise IOError unless @protocol
344
- message.direction = :out
345
- message.generate_json
346
- message.validate get_schemas unless validate==false
347
- @protocol.write_lines message.json
348
- expect_acknowledgement message
349
- distribute message
350
- log_send message, reason
351
- rescue EOFError, IOError
352
- buffer_message message
353
- rescue SchemaError, RSMP::Schema::Error => e
354
- schemas_string = e.schemas.map {|schema| "#{schema.first}: #{schema.last}"}.join(", ")
355
- str = "Could not send #{message.type} because schema validation failed (#{schemas_string}): #{e.message}"
356
- log str, message: message, level: :error
357
- distribute_error e.exception("#{str} #{message.json}")
358
- end
359
-
360
- def buffer_message message
361
- # TODO
362
- #log "Cannot send #{message.type} because the connection is closed.", message: message, level: :error
363
- end
364
-
365
- def log_send message, reason=nil
366
- if reason
367
- str = "Sent #{message.type} #{reason}"
368
- else
369
- str = "Sent #{message.type}"
370
- end
371
-
372
- if message.type == "MessageNotAck"
373
- log str, message: message, level: :warning
374
- else
375
- log str, message: message, level: :log
376
- end
377
- end
378
-
379
- def should_validate_ingoing_message? message
380
- return true unless @site_settings
381
- skip = @site_settings.dig('skip_validation')
382
- return true unless skip
383
- klass = message.class.name.split('::').last
384
- !skip.include?(klass)
385
- end
386
-
387
- def process_deferred
388
- @node.process_deferred
389
- end
390
-
391
- def verify_sequence message
392
- expect_version_message(message) unless @version_determined
393
- end
394
-
395
- def process_packet json
396
- attributes = Message.parse_attributes json
397
- message = Message.build attributes, json
398
- message.validate(get_schemas) if should_validate_ingoing_message?(message)
399
- verify_sequence message
400
- with_deferred_distribution do
401
- distribute message
402
- process_message message
403
- end
404
- process_deferred
405
- message
406
- rescue InvalidPacket => e
407
- str = "Received invalid package, must be valid JSON but got #{json.size} bytes: #{e.message}"
408
- distribute_error e.exception(str)
409
- log str, level: :warning
410
- nil
411
- rescue MalformedMessage => e
412
- str = "Received malformed message, #{e.message}"
413
- distribute_error e.exception(str)
414
- log str, message: Malformed.new(attributes), level: :warning
415
- # cannot send NotAcknowledged for a malformed message since we can't read it, just ignore it
416
- nil
417
- rescue SchemaError, RSMP::Schema::Error => e
418
- schemas_string = e.schemas.map {|schema| "#{schema.first}: #{schema.last}"}.join(", ")
419
- reason = "schema errors (#{schemas_string}): #{e.message}"
420
- str = "Received invalid #{message.type}"
421
- distribute_error e.exception(str), message: message
422
- dont_acknowledge message, str, reason
423
- message
424
- rescue InvalidMessage => e
425
- reason = "#{e.message}"
426
- str = "Received invalid #{message.type},"
427
- distribute_error e.exception("#{str} #{message.json}"), message: message
428
- dont_acknowledge message, str, reason
429
- message
430
- rescue FatalError => e
431
- reason = e.message
432
- str = "Rejected #{message.type},"
433
- distribute_error e.exception(str), message: message
434
- dont_acknowledge message, str, reason
435
- close
436
- message
437
- ensure
438
- @node.clear_deferred
439
- end
440
-
441
- def process_message message
442
- case message
443
- when MessageAck
444
- process_ack message
445
- when MessageNotAck
446
- process_not_ack message
447
- when Version
448
- process_version message
449
- when Watchdog
450
- process_watchdog message
451
- else
452
- dont_acknowledge message, "Received", "unknown message (#{message.type})"
453
- end
454
- end
455
-
456
- def will_not_handle message
457
- reason = "since we're a #{self.class.name.downcase}" unless reason
458
- log "Ignoring #{message.type}, #{reason}", message: message, level: :warning
459
- dont_acknowledge message, nil, reason
460
- end
461
-
462
- def expect_acknowledgement message
463
- unless message.is_a?(MessageAck) || message.is_a?(MessageNotAck)
464
- @awaiting_acknowledgement[message.m_id] = message
465
- end
466
- end
467
-
468
- def dont_expect_acknowledgement message
469
- @awaiting_acknowledgement.delete message.attribute("oMId")
470
- end
471
-
472
- def extraneous_version message
473
- dont_acknowledge message, "Received", "extraneous Version message"
474
- end
475
-
476
- def core_versions
477
- version = @site_settings["core_version"]
478
- if version == 'latest'
479
- [RSMP::Schema.latest_core_version]
480
- elsif version
481
- [version]
482
- else
483
- RSMP::Schema.core_versions
484
- end
485
- end
486
-
487
- def check_core_version message
488
- versions = core_versions
489
- # find versions that both we and the client support
490
- candidates = message.versions & versions
491
- if candidates.any?
492
- @core_version = candidates.sort_by { |v| Gem::Version.new(v) }.last # pick latest version
493
- else
494
- reason = "RSMP versions [#{message.versions.join(', ')}] requested, but only [#{versions.join(', ')}] supported."
495
- dont_acknowledge message, "Version message rejected", reason, force: true
496
- raise HandshakeError.new reason
497
- end
498
- end
499
-
500
- def process_version message
501
- end
502
-
503
- def acknowledge original
504
- raise InvalidArgument unless original
505
- ack = MessageAck.build_from(original)
506
- ack.original = original.clone
507
- send_message ack, "for #{ack.original.type} #{original.m_id_short}"
508
- check_ingoing_acknowledged original
509
- end
510
-
511
- def dont_acknowledge original, prefix=nil, reason=nil, force: true
512
- raise InvalidArgument unless original
513
- str = [prefix,reason].join(' ')
514
- log str, message: original, level: :warning if reason
515
- message = MessageNotAck.new({
516
- "oMId" => original.m_id,
517
- "rea" => reason || "Unknown reason"
518
- })
519
- message.original = original.clone
520
- send_message message, "for #{original.type} #{original.m_id_short}", force: force
521
- end
522
-
523
- def wait_for_state state, timeout:
524
- states = [state].flatten
525
- return if states.include?(@state)
526
- wait_for_condition(@state_condition,timeout: timeout) do
527
- states.include?(@state)
528
- end
529
- @state
530
- rescue RSMP::TimeoutError
531
- raise RSMP::TimeoutError.new "Did not reach state #{state} within #{timeout}s"
532
- end
533
-
534
- def send_version site_id, core_versions
535
- if core_versions=='latest'
536
- versions = [RSMP::Schema.latest_core_version]
537
- elsif core_versions=='all'
538
- versions = RSMP::Schema.core_versions
539
- else
540
- versions = [core_versions].flatten
541
- end
542
- versions_array = versions.map {|v| {"vers" => v} }
543
-
544
- site_id_array = [site_id].flatten.map {|id| {"sId" => id} }
545
-
546
- version_response = Version.new({
547
- "RSMP"=>versions_array,
548
- "siteId"=>site_id_array,
549
- "SXL"=>sxl_version.to_s
550
- })
551
- send_message version_response
552
- end
553
-
554
- def find_original_for_message message
555
- @awaiting_acknowledgement[ message.attribute("oMId") ]
556
- end
557
-
558
- # TODO this might be better handled by a proper event machine using e.g. the EventMachine gem
559
- def check_outgoing_acknowledged message
560
- unless @outgoing_acknowledged[message.type]
561
- @outgoing_acknowledged[message.type] = true
562
- acknowledged_first_outgoing message
563
- end
564
- end
565
-
566
- def check_ingoing_acknowledged message
567
- unless @ingoing_acknowledged[message.type]
568
- @ingoing_acknowledged[message.type] = true
569
- acknowledged_first_ingoing message
570
- end
571
- end
572
-
573
- def acknowledged_first_outgoing message
574
- end
575
-
576
- def acknowledged_first_ingoing message
577
- end
578
-
579
- def process_ack message
580
- original = find_original_for_message message
581
- if original
582
- dont_expect_acknowledgement message
583
- message.original = original
584
- log_acknowledgement_for_original message, original
585
-
586
- case original.type
587
- when "Version"
588
- version_acknowledged
589
- when "StatusSubscribe"
590
- status_subscribe_acknowledged original
591
- end
592
-
593
- check_outgoing_acknowledged original
594
-
595
- @acknowledgements[ original.m_id ] = message
596
- @acknowledgement_condition.signal message
597
- else
598
- log_acknowledgement_for_unknown message
599
- end
600
- end
601
-
602
- def process_not_ack message
603
- original = find_original_for_message message
604
- if original
605
- dont_expect_acknowledgement message
606
- message.original = original
607
- log_acknowledgement_for_original message, original
608
- @acknowledgements[ original.m_id ] = message
609
- @acknowledgement_condition.signal message
610
- else
611
- log_acknowledgement_for_unknown message
612
- end
613
- end
614
-
615
- def log_acknowledgement_for_original message, original
616
- str = "Received #{message.type} for #{original.type} #{message.attribute("oMId")[0..3]}"
617
- if message.type == 'MessageNotAck'
618
- reason = message.attributes["rea"]
619
- str = "#{str}: #{reason}" if reason
620
- log str, message: message, level: :warning
621
- else
622
- log str, message: message, level: :log
623
- end
624
- end
625
-
626
- def log_acknowledgement_for_unknown message
627
- log "Received #{message.type} for unknown message #{message.attribute("oMId")[0..3]}", message: message, level: :warning
628
- end
629
-
630
- def process_watchdog message
631
- log "Received #{message.type}", message: message, level: :log
632
- @latest_watchdog_received = Clock.now
633
- acknowledge message
634
- end
635
-
636
- def expect_version_message message
637
- unless message.is_a?(Version) || message.is_a?(MessageAck) || message.is_a?(MessageNotAck)
638
- raise HandshakeError.new "Version must be received first"
639
- end
640
- end
641
-
642
- def handshake_complete
643
- set_state :ready
644
- end
645
-
646
- def version_acknowledged
647
- end
648
-
649
- def author
650
- @node.site_id
651
- end
652
-
653
- def send_and_optionally_collect message, options, &block
654
- collect_options = options[:collect] || options[:collect!]
655
- if collect_options
656
- task = @task.async do |task|
657
- task.annotate 'send_and_optionally_collect'
658
- collector = yield collect_options # call block to create collector
659
- collector.collect
660
- collector.ok! if options[:collect!] # raise any errors if the bang version was specified
661
- collector
662
- end
663
-
664
- send_message message, validate: options[:validate]
665
- { sent: message, collector: task.wait }
666
- else
667
- send_message message, validate: options[:validate]
668
- return { sent: message }
669
- end
670
- end
671
-
672
- def set_nts_message_attributes message
673
- message.attributes['ntsOId'] = (main && main.ntsOId) ? main.ntsOId : ''
674
- message.attributes['xNId'] = (main && main.xNId) ? main.xNId : ''
675
- end
676
-
677
- # Use Gem class to check version requirement
678
- # Requirement must be a string like '1.1', '>=1.0.3' or '<2.1.4',
679
- # or list of strings, like ['<=1.4','<1.5']
680
- def self.version_meets_requirement? version, requirement
681
- Gem::Requirement.new(requirement).satisfied_by?(Gem::Version.new(version))
682
- end
683
-
684
- def status_subscribe_acknowledged original
685
- component = find_component original.attribute('cId')
686
- return unless component
687
- short = Message.shorten_m_id original.m_id
688
- subscribe_list = original.attributes['sS']
689
- log "StatusSubscribe #{short} acknowledged, allowing repeated status values for #{subscribe_list}", level: :info
690
- component.allow_repeat_updates subscribe_list
691
- end
692
- end
693
- end