rsmp 0.48.4 → 0.51.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 (87) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +6 -0
  3. data/Gemfile.lock +19 -20
  4. data/README.md +27 -3
  5. data/Rakefile +3 -1
  6. data/documentation/cli.md +79 -4
  7. data/documentation/collecting_message.md +90 -170
  8. data/documentation/message_distribution.md +17 -3
  9. data/documentation/tasks.md +65 -117
  10. data/lib/rsmp/cli.rb +93 -5
  11. data/lib/rsmp/collect/collector.rb +117 -134
  12. data/lib/rsmp/collect/distributor.rb +16 -3
  13. data/lib/rsmp/collect/queue.rb +14 -6
  14. data/lib/rsmp/collect/receiver.rb +1 -1
  15. data/lib/rsmp/collect/state_collector.rb +11 -2
  16. data/lib/rsmp/collection.rb +12 -0
  17. data/lib/rsmp/completion.rb +49 -0
  18. data/lib/rsmp/component/component_base.rb +3 -3
  19. data/lib/rsmp/convert/export/json_schema/index.rb +8 -3
  20. data/lib/rsmp/convert/import/yaml.rb +12 -8
  21. data/lib/rsmp/delivery.rb +18 -0
  22. data/lib/rsmp/event.rb +19 -0
  23. data/lib/rsmp/event_source.rb +36 -0
  24. data/lib/rsmp/exchange.rb +10 -0
  25. data/lib/rsmp/failure.rb +23 -0
  26. data/lib/rsmp/helpers/error.rb +14 -23
  27. data/lib/rsmp/message.rb +9 -7
  28. data/lib/rsmp/node/node.rb +3 -21
  29. data/lib/rsmp/node/site/connections.rb +4 -9
  30. data/lib/rsmp/node/site/site.rb +31 -16
  31. data/lib/rsmp/node/supervisor/modules/connection.rb +19 -5
  32. data/lib/rsmp/node/supervisor/modules/sites.rb +16 -5
  33. data/lib/rsmp/node/supervisor/supervisor.rb +13 -14
  34. data/lib/rsmp/node/task.rb +59 -53
  35. data/lib/rsmp/proxy/modules/acknowledgements.rb +22 -9
  36. data/lib/rsmp/proxy/modules/lifecycle.rb +134 -0
  37. data/lib/rsmp/proxy/modules/receive.rb +109 -66
  38. data/lib/rsmp/proxy/modules/send.rb +109 -51
  39. data/lib/rsmp/proxy/modules/state.rb +15 -5
  40. data/lib/rsmp/proxy/modules/tasks.rb +96 -70
  41. data/lib/rsmp/proxy/modules/versions.rb +8 -8
  42. data/lib/rsmp/proxy/modules/watchdogs.rb +15 -2
  43. data/lib/rsmp/proxy/proxy.rb +4 -66
  44. data/lib/rsmp/proxy/site/connection.rb +27 -22
  45. data/lib/rsmp/proxy/site/modules/aggregated_status.rb +18 -8
  46. data/lib/rsmp/proxy/site/modules/alarms.rb +45 -38
  47. data/lib/rsmp/proxy/site/modules/commands.rb +18 -8
  48. data/lib/rsmp/proxy/site/modules/status.rb +44 -19
  49. data/lib/rsmp/proxy/site/site_proxy.rb +12 -10
  50. data/lib/rsmp/proxy/supervisor/modules/aggregated_status.rb +1 -1
  51. data/lib/rsmp/proxy/supervisor/modules/alarms.rb +1 -1
  52. data/lib/rsmp/proxy/supervisor/modules/commands.rb +1 -1
  53. data/lib/rsmp/proxy/supervisor/modules/message_buffer.rb +8 -9
  54. data/lib/rsmp/proxy/supervisor/modules/status.rb +1 -1
  55. data/lib/rsmp/proxy/supervisor/modules/status_updates.rb +1 -1
  56. data/lib/rsmp/proxy/supervisor/supervisor_proxy.rb +34 -30
  57. data/lib/rsmp/result.rb +95 -0
  58. data/lib/rsmp/schema/validation.rb +5 -3
  59. data/lib/rsmp/sxl/interface.rb +8 -1
  60. data/lib/rsmp/sxl/processing/catalogue.rb +61 -0
  61. data/lib/rsmp/sxl/processing/conflict_checker.rb +30 -0
  62. data/lib/rsmp/sxl/processing/dependency_graph.rb +37 -0
  63. data/lib/rsmp/sxl/processing/document.rb +158 -0
  64. data/lib/rsmp/sxl/processing/error.rb +8 -0
  65. data/lib/rsmp/sxl/processing/manifest.rb +144 -0
  66. data/lib/rsmp/sxl/processing/natural_sort.rb +22 -0
  67. data/lib/rsmp/sxl/processing/resolver.rb +94 -0
  68. data/lib/rsmp/sxl/processing/version_requirement.rb +86 -0
  69. data/lib/rsmp/sxl/processing.rb +18 -0
  70. data/lib/rsmp/sxl/supervisor_interface.rb +8 -1
  71. data/lib/rsmp/termination.rb +10 -0
  72. data/lib/rsmp/tlc/modules/plans.rb +0 -2
  73. data/lib/rsmp/tlc/proxy/control.rb +50 -16
  74. data/lib/rsmp/tlc/proxy/detectors.rb +15 -4
  75. data/lib/rsmp/tlc/proxy/io.rb +46 -31
  76. data/lib/rsmp/tlc/proxy/plans.rb +36 -19
  77. data/lib/rsmp/tlc/proxy/plans_bang.rb +44 -0
  78. data/lib/rsmp/tlc/proxy/status.rb +47 -23
  79. data/lib/rsmp/tlc/proxy/system.rb +16 -4
  80. data/lib/rsmp/tlc/supervisor_interface.rb +7 -1
  81. data/lib/rsmp/tlc/traffic_controller_site.rb +6 -9
  82. data/lib/rsmp/validation.rb +22 -0
  83. data/lib/rsmp/version.rb +1 -1
  84. data/lib/rsmp.rb +19 -4
  85. data/rsmp.gemspec +1 -1
  86. metadata +25 -4
  87. data/lib/rsmp/collect/collector/status.rb +0 -34
@@ -1,103 +1,129 @@
1
1
  module RSMP
2
2
  class Proxy
3
3
  module Modules
4
- # Reader and timer task management
5
- # Handles async tasks for reading from socket and running periodic timers
4
+ # Reader and timer tasks are siblings in one supervised session barrier.
6
5
  module Tasks
7
- # run an async task that reads from @socket
6
+ def session_active?(id = @session_id)
7
+ id == @session_id && !@session_closed
8
+ end
9
+
10
+ def session_tasks
11
+ @session_tasks ||= Async::Barrier.new(parent: @task)
12
+ end
13
+
8
14
  def start_reader
9
- @reader = @task.async do |task|
10
- task.annotate 'reader'
11
- run_reader
15
+ id = @session_id
16
+ @reader = session_tasks.async do |task|
17
+ task.annotate "reader session #{id}"
18
+ run_reader(id)
12
19
  end
13
20
  end
14
21
 
15
- def run_reader
22
+ def run_reader(id)
16
23
  @stream ||= IO::Stream::Buffered.new(@socket)
17
- @protocol ||= RSMP::Protocol.new(@stream) # rsmp messages are json terminated with a form-feed
18
- loop do
19
- read_line
24
+ @protocol ||= RSMP::Protocol.new(@stream)
25
+ while session_active?(id)
26
+ line = read_protocol_line
27
+ return line if line.failure?
28
+
29
+ process_received_line(line.value)
20
30
  end
21
- rescue Restart
22
- log 'Closing connection', level: :warning
23
- raise
24
- rescue EOFError, Async::Stop
25
- log 'Connection closed', level: :warning
26
- rescue IOError => e
27
- log "IOError: #{e}", level: :debug
28
- rescue Errno::ECONNRESET
29
- log 'Connection reset by peer', level: :warning
30
- rescue Errno::EPIPE
31
- log 'Broken pipe', level: :warning
32
- rescue StandardError => e
33
- distribute_error e, level: :internal
31
+ Result.success(:closed)
34
32
  end
35
33
 
36
- def read_line
34
+ def read_protocol_line
37
35
  json = @protocol.read_line
36
+ raise EOFError, 'Connection closed by peer' unless json
37
+
38
+ Result.success(json)
39
+ rescue EOFError => e
40
+ connection_read_failure(:peer_closed, 'Connection closed by peer', e)
41
+ rescue IOError, Errno::ECONNRESET, Errno::EPIPE => e
42
+ connection_read_failure(:transport_failure, e.message, e)
43
+ end
44
+
45
+ def connection_read_failure(reason, text, error)
46
+ log(text, level: :warning)
47
+ Result.failure(
48
+ :disconnected,
49
+ message: text,
50
+ source: reason == :peer_closed ? :peer : :transport,
51
+ context: { reason: reason, session_id: @session_id },
52
+ cause: error
53
+ )
54
+ end
55
+
56
+ # Wait for the reader while observing every sibling. A failed timer task
57
+ # raises here with its original exception and backtrace.
58
+ def wait_for_session
59
+ reader = @reader
60
+ observed = session_tasks.wait do |finished|
61
+ result = finished.wait
62
+ break result if finished.equal?(reader)
63
+ break result if result.is_a?(Result::Failure)
64
+ next if finished.cancelled? || !session_active?
65
+
66
+ raise "#{finished.annotation} ended while its connection session was active"
67
+ end
68
+ observed.is_a?(Result::Success) || observed.is_a?(Result::Failure) ? observed : Result.success(:closed)
69
+ ensure
70
+ @session_tasks.cancel if @session_tasks && !@session_tasks.empty?
71
+ @session_tasks = nil
72
+ end
73
+
74
+ def process_received_line(json)
38
75
  beginning = Time.now
39
- message = process_packet json
76
+ result = process_packet(json)
77
+ log_processing_statistics(result, beginning)
78
+ result
79
+ end
80
+
81
+ def log_processing_statistics(result, beginning)
82
+ message = result.success? ? result.value : result.failure.context[:message]
40
83
  duration = Time.now - beginning
41
- ms = (duration * 1000).round(4)
42
- per_second = if duration.positive?
43
- (1.0 / duration).round
44
- else
45
- Float::INFINITY
46
- end
47
- if message
48
- type = message.type
49
- m_id = Logger.shorten_message_id(message.m_id)
50
- else
51
- type = 'Unknown'
52
- m_id = nil
53
- end
54
- str = [type, m_id, "processed in #{ms}ms, #{per_second}req/s"].compact.join(' ')
55
- log str, level: :statistics
84
+ type = message.respond_to?(:type) ? message.type : 'Unknown'
85
+ m_id = message.respond_to?(:m_id) ? Logger.shorten_message_id(message.m_id) : nil
86
+ log([type, m_id, processing_speed(duration)].compact.join(' '), level: :statistics)
87
+ end
88
+
89
+ def processing_speed(duration)
90
+ milliseconds = (duration * 1000).round(4)
91
+ per_second = duration.positive? ? (1.0 / duration).round : Float::INFINITY
92
+ "processed in #{milliseconds}ms, #{per_second}req/s"
56
93
  end
57
94
 
58
95
  def start_timer
59
- return if @timer
96
+ return if @timer&.running?
60
97
 
61
- name = 'timer'
98
+ id = @session_id
62
99
  interval = @site_settings['intervals']['timer'] || 1
63
- log "Starting #{name} with interval #{interval} seconds", level: :debug
100
+ log "Starting timer with interval #{interval} seconds", level: :debug
64
101
  @latest_watchdog_received = Clock.now
65
- @timer = @task.async do |task|
66
- task.annotate 'timer'
67
- run_timer task, interval
102
+ @timer = session_tasks.async do |task|
103
+ task.annotate "timer session #{id}"
104
+ run_timer(task, interval, id)
68
105
  end
69
106
  end
70
107
 
71
- def run_timer(task, interval)
108
+ def run_timer(task, interval, id)
72
109
  next_time = Time.now.to_f
73
- loop do
74
- begin
75
- now = Clock.now
76
- timer(now)
77
- rescue RSMP::Schema::Error => e
78
- log "Timer: Schema error: #{e}", level: :warning
79
- rescue EOFError => e
80
- log "Timer: Connection closed: #{e}", level: :warning
81
- rescue IOError
82
- log 'Timer: IOError', level: :warning
83
- rescue Errno::ECONNRESET
84
- log 'Timer: Connection reset by peer', level: :warning
85
- rescue Errno::EPIPE
86
- log 'Timer: Broken pipe', level: :warning
87
- rescue StandardError => e
88
- distribute_error e, level: :internal
89
- end
90
- ensure
110
+ while session_active?(id)
111
+ result = timer(Clock.now)
112
+ return result if result.failure?
113
+
91
114
  next_time += interval
92
- duration = next_time - Time.now.to_f
93
- task.sleep duration
115
+ task.sleep([next_time - Time.now.to_f, 0].max) if session_active?(id)
94
116
  end
117
+ Result.success(:closed)
95
118
  end
96
119
 
97
120
  def timer(now)
98
- watchdog_send_timer now
99
- check_ack_timeout now
100
- check_watchdog_timeout now
121
+ watchdog_send_timer(now)
122
+ acknowledgement = check_ack_timeout(now)
123
+ return acknowledgement if acknowledgement.failure?
124
+
125
+ check_watchdog_timeout(now)
126
+ Result.success
101
127
  end
102
128
  end
103
129
  end
@@ -66,13 +66,13 @@ module RSMP
66
66
 
67
67
  def send_version_response(site_id, core_versions)
68
68
  if core_3_3?
69
- send_message Version.new({
70
- 'step' => 'Response',
71
- 'RSMP' => [{ 'vers' => core_version }],
72
- 'supervisorId' => site_id,
73
- 'SXLS' => version_response_sxls,
74
- 'receiveAlarms' => @site_settings['receive_alarms'] != false
75
- }), validate: false
69
+ send_generated_message Version.new({
70
+ 'step' => 'Response',
71
+ 'RSMP' => [{ 'vers' => core_version }],
72
+ 'supervisorId' => site_id,
73
+ 'SXLS' => version_response_sxls,
74
+ 'receiveAlarms' => @site_settings['receive_alarms'] != false
75
+ }), validate: false
76
76
  else
77
77
  send_version_message(site_id, core_versions, step: nil)
78
78
  end
@@ -81,7 +81,7 @@ module RSMP
81
81
  def send_version_message(site_id, core_versions, step:)
82
82
  attributes = version_message_attributes(site_id, core_versions)
83
83
  attributes.merge!(version_request_attributes) if step == 'Request'
84
- send_message Version.new(attributes), validate: false
84
+ send_generated_message Version.new(attributes), validate: false
85
85
  end
86
86
 
87
87
  def version_message_attributes(site_id, core_versions)
@@ -40,7 +40,7 @@ module RSMP
40
40
 
41
41
  def send_watchdog(now = Clock.now)
42
42
  message = RSMP::Watchdog.new({ 'wTs' => clock.to_s })
43
- send_message message
43
+ send_generated_message message
44
44
  @latest_watchdog_send_at = now
45
45
  end
46
46
 
@@ -52,7 +52,20 @@ module RSMP
52
52
 
53
53
  str = "No Watchdog received within #{timeout} seconds"
54
54
  log str, level: :warning
55
- distribute MissingWatchdog.new(str)
55
+ failure = Failure.new(
56
+ code: :missing_watchdog,
57
+ message: str,
58
+ source: :peer,
59
+ context: { session_id: session_id }
60
+ )
61
+ distribute_event(
62
+ Event.new(
63
+ type: :missing_watchdog,
64
+ source: self,
65
+ session_id: session_id,
66
+ failure: failure
67
+ )
68
+ )
56
69
  end
57
70
 
58
71
  def process_watchdog(message)
@@ -16,10 +16,11 @@ module RSMP
16
16
  include Modules::Receive
17
17
  include Modules::Versions
18
18
  include Modules::Tasks
19
+ include Modules::Lifecycle
19
20
 
20
21
  attr_reader :state, :archive, :connection_info, :sxls, :accepted_sxls, :rejected_sxls,
21
22
  :collector, :ip, :port, :node, :core_version, :sxl_interfaces,
22
- :site_settings
23
+ :site_settings, :session_id
23
24
 
24
25
  def initialize(options)
25
26
  @node = options[:node]
@@ -31,6 +32,8 @@ module RSMP
31
32
  clear
32
33
  @state = :disconnected
33
34
  @state_condition = Async::Notification.new
35
+ @session_id = 0
36
+ @session_closed = true
34
37
  end
35
38
 
36
39
  def inspect
@@ -41,67 +44,6 @@ module RSMP
41
44
  node.now
42
45
  end
43
46
 
44
- # Connection lifecycle methods
45
-
46
- def disconnect; end
47
-
48
- # wait for the reader task to complete,
49
- # which is not expected to happen before the connection is closed
50
- def wait_for_reader
51
- @reader&.wait
52
- end
53
-
54
- # close connection, but keep our main task running so we can reconnect
55
- def close
56
- log 'Closing connection', level: :warning
57
- close_stream
58
- close_socket
59
- stop_reader
60
- self.state = :disconnected
61
- distribute_error DisconnectError.new('Connection was closed')
62
-
63
- # stop timer
64
- # as we're running inside the timer, code after stop_timer() will not be called,
65
- # unless it's in the ensure block
66
- stop_timer
67
- end
68
-
69
- def stop_subtasks
70
- stop_timer
71
- stop_reader
72
- clear
73
- super
74
- end
75
-
76
- def stop_timer
77
- @timer&.stop
78
- ensure
79
- @timer = nil
80
- end
81
-
82
- def stop_reader
83
- @reader&.stop
84
- ensure
85
- @reader = nil
86
- end
87
-
88
- def close_stream
89
- @stream&.close
90
- ensure
91
- @stream = nil
92
- end
93
-
94
- def close_socket
95
- @socket&.close
96
- ensure
97
- @socket = nil
98
- end
99
-
100
- def stop_task
101
- close
102
- super
103
- end
104
-
105
47
  # State management methods
106
48
 
107
49
  def ready?
@@ -174,10 +116,6 @@ module RSMP
174
116
  @node.clock
175
117
  end
176
118
 
177
- def receive_error(error, options = {})
178
- @node.receive_error error, options
179
- end
180
-
181
119
  def log(str, options = {})
182
120
  super(str, options.merge(ip: @ip, port: @port, site_id: @site_id))
183
121
  end
@@ -12,50 +12,46 @@ module RSMP
12
12
  end
13
13
 
14
14
  def run_accepted_connection
15
+ begin_session
15
16
  self.state = :connected
16
17
  start_reader
17
- wait_for_reader # run until disconnected
18
- rescue RSMP::ConnectionError => e
19
- log e, level: :error
20
- rescue StandardError => e
21
- distribute_error e, level: :internal
18
+ ended = wait_for_reader
19
+ close_from_result(ended)
22
20
  ensure
21
+ close(reason: :internal_failure) if $ERROR_INFO
23
22
  close
24
23
  end
25
24
 
26
25
  def run_outbound_connection
27
26
  loop do
28
27
  setup_site_settings
29
- connect
28
+ connected = connect
29
+ unless connected.success?
30
+ publish_connection_attempt_failure(connected.failure)
31
+ break unless reconnect_delay?
32
+
33
+ next
34
+ end
30
35
  start_reader
31
- wait_for_reader
32
- break unless reconnect_delay?
33
- rescue Restart
34
- @logger.mute @ip, @port
35
- raise
36
- rescue RSMP::ConnectionError => e
37
- log e, level: :error
38
- break unless reconnect_delay?
39
- rescue StandardError => e
40
- distribute_error e, level: :internal
36
+ close_from_result(wait_for_reader)
41
37
  break unless reconnect_delay?
42
38
  ensure
39
+ close(reason: :internal_failure) if $ERROR_INFO
43
40
  close
44
- stop_subtasks
45
41
  end
46
42
  end
47
43
 
48
44
  def connect
49
45
  log "Connecting to site #{@site_id} at #{@ip}:#{@port}", level: :info
46
+ begin_session
50
47
  self.state = :connecting
51
- open_socket
48
+ opened = open_socket
49
+ return opened if opened.failure?
50
+
52
51
  self.state = :connected
53
52
  @logger.unmute @ip, @port
54
53
  log "Connected to site #{@site_id} at #{@ip}:#{@port}", level: :info
55
- rescue SystemCallError => e
56
- raise ConnectionError, "Could not connect to site #{@site_id} at #{@ip}:#{@port}: Errno #{e.errno} #{e}"
57
- rescue StandardError => e
58
- raise ConnectionError, "Error while connecting to site #{@site_id} at #{@ip}:#{@port}: #{e}"
54
+ Result.success(self)
59
55
  end
60
56
 
61
57
  def open_socket
@@ -64,6 +60,15 @@ module RSMP
64
60
  task.with_timeout(timeout) { @socket = endpoint.connect }
65
61
  @stream = IO::Stream::Buffered.new(@socket)
66
62
  @protocol = RSMP::Protocol.new(@stream)
63
+ Result.success(self)
64
+ rescue SystemCallError, SocketError, IOError, Async::TimeoutError => e
65
+ Result.failure(
66
+ :connection_failed,
67
+ message: "Could not connect to site #{@site_id} at #{@ip}:#{@port}: #{e.message}",
68
+ source: :transport,
69
+ context: { ip: @ip, port: @port, session_id: @session_id },
70
+ cause: e
71
+ )
67
72
  end
68
73
 
69
74
  def reconnect_delay?
@@ -3,23 +3,29 @@ module RSMP
3
3
  module Modules
4
4
  # Handles aggregated status requests and responses
5
5
  module AggregatedStatus
6
- # Build and send an AggregatedStatusRequest. Returns { sent: message }.
6
+ # Build and send an AggregatedStatusRequest. Returns Result<AggregatedStatusRequest>.
7
7
  def request_aggregated_status(component, m_id: nil, validate: true)
8
- validate_ready 'request aggregated status'
8
+ readiness = validate_ready 'request aggregated status'
9
+ return readiness if readiness.failure?
10
+
9
11
  m_id ||= RSMP::Message.make_m_id
10
12
  message = RSMP::AggregatedStatusRequest.new({
11
13
  'cId' => component,
12
14
  'mId' => m_id
13
15
  })
14
16
  apply_nts_message_attributes message
15
- send_message message, validate: validate
16
- { sent: message }
17
+ send_message(message, validate: validate).map(&:message)
18
+ end
19
+
20
+ def request_aggregated_status!(...)
21
+ request_aggregated_status(...).value!
17
22
  end
18
23
 
19
- # Build, send an AggregatedStatusRequest and collect the response. Returns the collector.
20
- # Call .ok! on the result to raise on NotAck or timeout.
24
+ # Build, send an AggregatedStatusRequest and collect the response. Returns Result<Exchange>.
21
25
  def request_aggregated_status_and_collect(component, within:, m_id: nil, validate: true)
22
- validate_ready 'request aggregated status'
26
+ readiness = validate_ready 'request aggregated status'
27
+ return readiness if readiness.failure?
28
+
23
29
  m_id ||= RSMP::Message.make_m_id
24
30
  message = RSMP::AggregatedStatusRequest.new({
25
31
  'cId' => component,
@@ -27,7 +33,11 @@ module RSMP
27
33
  })
28
34
  apply_nts_message_attributes message
29
35
  collector = AggregatedStatusCollector.new(self, timeout: within, m_id: m_id, num: 1)
30
- send_message_and_collect(message, collector, validate: validate)[:collector]
36
+ send_message_and_collect(message, collector, validate: validate)
37
+ end
38
+
39
+ def request_aggregated_status_and_collect!(...)
40
+ request_aggregated_status_and_collect(...).value!
31
41
  end
32
42
 
33
43
  def validate_aggregated_status(message, status_elements)
@@ -18,67 +18,74 @@ module RSMP
18
18
  'cId' => component,
19
19
  'aCId' => alarm_code
20
20
  })
21
- send_message message, validate: options[:validate]
22
- message
21
+ send_message(message, validate: options[:validate]).map(&:message)
22
+ end
23
+
24
+ def send_alarm_acknowledgement!(...)
25
+ send_alarm_acknowledgement(...).value!
23
26
  end
24
27
 
25
28
  # Send an AlarmSuspend message and optionally collect the confirming response.
26
- # When collect: true, returns [message, response]; when collect: false, returns message.
27
- def suspend_alarm(task, c_id:, a_c_id:, collect: false)
29
+ # Returns Result<Exchange> when collecting and Result<AlarmSuspend> otherwise.
30
+ def suspend_alarm(c_id:, a_c_id:, collect: false)
28
31
  message = RSMP::AlarmSuspend.new(
29
32
  'mId' => RSMP::Message.make_m_id,
30
33
  'cId' => c_id,
31
34
  'aCId' => a_c_id
32
35
  )
33
36
  if collect
34
- collect_task = task.async do
35
- RSMP::AlarmCollector.new(self,
36
- m_id: message.m_id,
37
- num: 1,
38
- matcher: {
39
- 'cId' => c_id,
40
- 'aCI' => a_c_id,
41
- 'aSp' => 'Suspend',
42
- 'sS' => /^Suspended/i
43
- },
44
- timeout: node.supervisor_settings.dig('default', 'timeouts', 'alarm')).collect!
45
- end
46
- send_message message
47
- [message, collect_task.wait.first]
37
+ collector = RSMP::AlarmCollector.new(
38
+ self,
39
+ m_id: message.m_id,
40
+ num: 1,
41
+ matcher: {
42
+ 'cId' => c_id,
43
+ 'aCId' => a_c_id,
44
+ 'aSp' => 'Suspend',
45
+ 'sS' => /^Suspended/i
46
+ },
47
+ timeout: node.supervisor_settings.dig('default', 'timeouts', 'alarm')
48
+ )
49
+ send_message_and_collect(message, collector)
48
50
  else
49
- send_message message
50
- message
51
+ send_message(message).map(&:message)
51
52
  end
52
53
  end
53
54
 
55
+ def suspend_alarm!(...)
56
+ suspend_alarm(...).value!
57
+ end
58
+
54
59
  # Send an AlarmResume message and optionally collect the confirming response.
55
- # When collect: true, returns [message, response]; when collect: false, returns message.
56
- def resume_alarm(task, c_id:, a_c_id:, collect: false)
60
+ # Returns Result<Exchange> when collecting and Result<AlarmResume> otherwise.
61
+ def resume_alarm(c_id:, a_c_id:, collect: false)
57
62
  message = RSMP::AlarmResume.new(
58
63
  'mId' => RSMP::Message.make_m_id,
59
64
  'cId' => c_id,
60
65
  'aCId' => a_c_id
61
66
  )
62
67
  if collect
63
- collect_task = task.async do
64
- RSMP::AlarmCollector.new(self,
65
- m_id: message.m_id,
66
- num: 1,
67
- matcher: {
68
- 'cId' => c_id,
69
- 'aCI' => a_c_id,
70
- 'aSp' => 'Suspend',
71
- 'sS' => /^notSuspended/i
72
- },
73
- timeout: node.supervisor_settings.dig('default', 'timeouts', 'alarm')).collect!
74
- end
75
- send_message message
76
- [message, collect_task.wait.first]
68
+ collector = RSMP::AlarmCollector.new(
69
+ self,
70
+ m_id: message.m_id,
71
+ num: 1,
72
+ matcher: {
73
+ 'cId' => c_id,
74
+ 'aCId' => a_c_id,
75
+ 'aSp' => 'Suspend',
76
+ 'sS' => /^notSuspended/i
77
+ },
78
+ timeout: node.supervisor_settings.dig('default', 'timeouts', 'alarm')
79
+ )
80
+ send_message_and_collect(message, collector)
77
81
  else
78
- send_message message
79
- message
82
+ send_message(message).map(&:message)
80
83
  end
81
84
  end
85
+
86
+ def resume_alarm!(...)
87
+ resume_alarm(...).value!
88
+ end
82
89
  end
83
90
  end
84
91
  end
@@ -3,9 +3,11 @@ module RSMP
3
3
  module Modules
4
4
  # Handles command requests and responses
5
5
  module Commands
6
- # Build and send a CommandRequest. Returns the CommandRequest message.
6
+ # Build and send a CommandRequest. Returns Result<CommandRequest>.
7
7
  def send_command(command_list, component: nil, validate: true)
8
- validate_ready 'send command'
8
+ readiness = validate_ready 'send command'
9
+ return readiness if readiness.failure?
10
+
9
11
  component ||= main.c_id
10
12
  m_id = RSMP::Message.make_m_id
11
13
  message = RSMP::CommandRequest.new({
@@ -14,14 +16,18 @@ module RSMP
14
16
  'mId' => m_id
15
17
  })
16
18
  apply_nts_message_attributes message
17
- send_message message, validate: validate
18
- message
19
+ send_message(message, validate: validate).map(&:message)
20
+ end
21
+
22
+ def send_command!(...)
23
+ send_command(...).value!
19
24
  end
20
25
 
21
- # Build, send a CommandRequest and collect the CommandResponse. Returns the collector.
22
- # Raises on NotAck or timeout if ok! is called on the result.
26
+ # Build, send a CommandRequest and collect its responses. Returns Result<Exchange>.
23
27
  def send_command_and_collect(command_list, within:, component: nil, validate: true)
24
- validate_ready 'send command'
28
+ readiness = validate_ready 'send command'
29
+ return readiness if readiness.failure?
30
+
25
31
  component ||= main.c_id
26
32
  m_id = RSMP::Message.make_m_id
27
33
  collector_list = command_collector_list(command_list)
@@ -32,7 +38,11 @@ module RSMP
32
38
  })
33
39
  apply_nts_message_attributes message
34
40
  collector = CommandResponseCollector.new(self, collector_list, timeout: within, initiator: message)
35
- send_message_and_collect(message, collector, validate: validate)[:collector]
41
+ send_message_and_collect(message, collector, validate: validate)
42
+ end
43
+
44
+ def send_command_and_collect!(...)
45
+ send_command_and_collect(...).value!
36
46
  end
37
47
 
38
48
  def command_collector_list(command_list)