rsmp 0.48.4 → 0.51.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +14 -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/configuration.md +8 -0
  9. data/documentation/message_distribution.md +17 -3
  10. data/documentation/tasks.md +65 -117
  11. data/lib/rsmp/cli.rb +93 -5
  12. data/lib/rsmp/collect/collector.rb +117 -134
  13. data/lib/rsmp/collect/distributor.rb +16 -3
  14. data/lib/rsmp/collect/queue.rb +14 -6
  15. data/lib/rsmp/collect/receiver.rb +1 -1
  16. data/lib/rsmp/collect/state_collector.rb +11 -2
  17. data/lib/rsmp/collection.rb +12 -0
  18. data/lib/rsmp/completion.rb +49 -0
  19. data/lib/rsmp/component/component_base.rb +3 -3
  20. data/lib/rsmp/convert/export/json_schema/index.rb +8 -4
  21. data/lib/rsmp/convert/import/yaml.rb +12 -8
  22. data/lib/rsmp/delivery.rb +18 -0
  23. data/lib/rsmp/event.rb +19 -0
  24. data/lib/rsmp/event_source.rb +36 -0
  25. data/lib/rsmp/exchange.rb +10 -0
  26. data/lib/rsmp/failure.rb +23 -0
  27. data/lib/rsmp/helpers/error.rb +14 -23
  28. data/lib/rsmp/message.rb +10 -9
  29. data/lib/rsmp/node/node.rb +3 -21
  30. data/lib/rsmp/node/site/connections.rb +4 -9
  31. data/lib/rsmp/node/site/site.rb +33 -17
  32. data/lib/rsmp/node/supervisor/modules/configuration.rb +24 -11
  33. data/lib/rsmp/node/supervisor/modules/connection.rb +19 -5
  34. data/lib/rsmp/node/supervisor/modules/sites.rb +16 -5
  35. data/lib/rsmp/node/supervisor/supervisor.rb +13 -14
  36. data/lib/rsmp/node/task.rb +59 -53
  37. data/lib/rsmp/proxy/modules/acknowledgements.rb +22 -9
  38. data/lib/rsmp/proxy/modules/lifecycle.rb +134 -0
  39. data/lib/rsmp/proxy/modules/receive.rb +109 -66
  40. data/lib/rsmp/proxy/modules/send.rb +109 -51
  41. data/lib/rsmp/proxy/modules/state.rb +15 -5
  42. data/lib/rsmp/proxy/modules/tasks.rb +96 -70
  43. data/lib/rsmp/proxy/modules/versions.rb +69 -19
  44. data/lib/rsmp/proxy/modules/watchdogs.rb +15 -2
  45. data/lib/rsmp/proxy/proxy.rb +7 -69
  46. data/lib/rsmp/proxy/site/connection.rb +27 -22
  47. data/lib/rsmp/proxy/site/modules/aggregated_status.rb +18 -8
  48. data/lib/rsmp/proxy/site/modules/alarms.rb +45 -38
  49. data/lib/rsmp/proxy/site/modules/commands.rb +18 -8
  50. data/lib/rsmp/proxy/site/modules/status.rb +44 -19
  51. data/lib/rsmp/proxy/site/site_proxy.rb +12 -10
  52. data/lib/rsmp/proxy/site/sxl_selection.rb +7 -2
  53. data/lib/rsmp/proxy/supervisor/modules/aggregated_status.rb +1 -1
  54. data/lib/rsmp/proxy/supervisor/modules/alarms.rb +1 -1
  55. data/lib/rsmp/proxy/supervisor/modules/commands.rb +1 -1
  56. data/lib/rsmp/proxy/supervisor/modules/message_buffer.rb +8 -9
  57. data/lib/rsmp/proxy/supervisor/modules/status.rb +1 -1
  58. data/lib/rsmp/proxy/supervisor/modules/status_updates.rb +1 -1
  59. data/lib/rsmp/proxy/supervisor/supervisor_proxy.rb +36 -31
  60. data/lib/rsmp/result.rb +95 -0
  61. data/lib/rsmp/schema/validation.rb +5 -3
  62. data/lib/rsmp/schema/version.rb +27 -0
  63. data/lib/rsmp/schema.rb +1 -0
  64. data/lib/rsmp/sxl/interface.rb +8 -1
  65. data/lib/rsmp/sxl/processing/catalogue.rb +61 -0
  66. data/lib/rsmp/sxl/processing/conflict_checker.rb +30 -0
  67. data/lib/rsmp/sxl/processing/dependency_graph.rb +37 -0
  68. data/lib/rsmp/sxl/processing/document.rb +158 -0
  69. data/lib/rsmp/sxl/processing/error.rb +8 -0
  70. data/lib/rsmp/sxl/processing/manifest.rb +144 -0
  71. data/lib/rsmp/sxl/processing/natural_sort.rb +22 -0
  72. data/lib/rsmp/sxl/processing/resolver.rb +94 -0
  73. data/lib/rsmp/sxl/processing/version_requirement.rb +86 -0
  74. data/lib/rsmp/sxl/processing.rb +18 -0
  75. data/lib/rsmp/sxl/supervisor_interface.rb +8 -1
  76. data/lib/rsmp/termination.rb +10 -0
  77. data/lib/rsmp/tlc/modules/plans.rb +0 -2
  78. data/lib/rsmp/tlc/modules/system.rb +2 -2
  79. data/lib/rsmp/tlc/proxy/control.rb +50 -16
  80. data/lib/rsmp/tlc/proxy/detectors.rb +15 -4
  81. data/lib/rsmp/tlc/proxy/io.rb +46 -31
  82. data/lib/rsmp/tlc/proxy/plans.rb +36 -19
  83. data/lib/rsmp/tlc/proxy/plans_bang.rb +44 -0
  84. data/lib/rsmp/tlc/proxy/status.rb +47 -23
  85. data/lib/rsmp/tlc/proxy/system.rb +16 -4
  86. data/lib/rsmp/tlc/supervisor_interface.rb +7 -1
  87. data/lib/rsmp/tlc/traffic_controller_site.rb +6 -9
  88. data/lib/rsmp/validation.rb +22 -0
  89. data/lib/rsmp/version.rb +1 -1
  90. data/lib/rsmp.rb +19 -4
  91. data/rsmp.gemspec +1 -1
  92. metadata +26 -4
  93. data/lib/rsmp/collect/collector/status.rb +0 -34
@@ -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
- :collector, :ip, :port, :node, :core_version, :sxl_interfaces,
22
- :site_settings
22
+ :collector, :ip, :port, :node, :core_version, :core_version_string, :sxl_interfaces,
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
@@ -252,8 +190,8 @@ module RSMP
252
190
  end
253
191
 
254
192
  # Use Gem class to check version requirement
255
- # Requirement must be a string like '1.1', '>=1.0.3' or '<2.1.4',
256
- # or list of strings, like ['<=1.4','<1.5']
193
+ # Requirement must be a string like '1.1.0', '>=1.0.3' or '<2.1.4',
194
+ # or list of strings, like ['<=1.4.0','<1.5.0']
257
195
  def self.version_meets_requirement?(version, requirement)
258
196
  Modules::Versions.version_meets_requirement?(version, requirement)
259
197
  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)
@@ -3,9 +3,11 @@ module RSMP
3
3
  module Modules
4
4
  # Handles status requests, responses, subscriptions and updates
5
5
  module Status
6
- # Build and send a StatusRequest. Returns { sent: message }.
6
+ # Build and send a StatusRequest. Returns Result<StatusRequest>.
7
7
  def request_status(status_list, component: nil, m_id: nil, validate: true)
8
- validate_ready 'request status'
8
+ readiness = validate_ready 'request status'
9
+ return readiness if readiness.failure?
10
+
9
11
  component ||= main.c_id
10
12
  m_id ||= RSMP::Message.make_m_id
11
13
 
@@ -21,14 +23,18 @@ module RSMP
21
23
  'mId' => m_id
22
24
  })
23
25
  apply_nts_message_attributes message
24
- send_message message, validate: validate
25
- { sent: message }
26
+ send_message(message, validate: validate).map(&:message)
27
+ end
28
+
29
+ def request_status!(...)
30
+ request_status(...).value!
26
31
  end
27
32
 
28
- # Build, send a StatusRequest and collect the StatusResponse. Returns the collector.
29
- # Call .ok! on the result to raise on NotAck or timeout.
33
+ # Build, send a StatusRequest and collect the StatusResponse. Returns Result<Exchange>.
30
34
  def request_status_and_collect(status_list, within:, component: nil, m_id: nil, validate: true)
31
- validate_ready 'request status'
35
+ readiness = validate_ready 'request status'
36
+ return readiness if readiness.failure?
37
+
32
38
  component ||= main.c_id
33
39
  m_id ||= RSMP::Message.make_m_id
34
40
 
@@ -45,7 +51,11 @@ module RSMP
45
51
  })
46
52
  apply_nts_message_attributes message
47
53
  collector = StatusCollector.new(self, list.to_a, timeout: within, m_id: m_id)
48
- send_message_and_collect(message, collector, validate: validate)[:collector]
54
+ send_message_and_collect(message, collector, validate: validate)
55
+ end
56
+
57
+ def request_status_and_collect!(...)
58
+ request_status_and_collect(...).value!
49
59
  end
50
60
 
51
61
  def process_status_response(message)
@@ -71,9 +81,11 @@ module RSMP
71
81
  end
72
82
  end
73
83
 
74
- # Build and send a StatusSubscribe. Returns { sent: message }.
84
+ # Build and send a StatusSubscribe. Returns Result<StatusSubscribe>.
75
85
  def subscribe_to_status(status_list, component: nil, m_id: nil, validate: true)
76
- validate_ready 'subscribe to status'
86
+ readiness = validate_ready 'subscribe to status'
87
+ return readiness if readiness.failure?
88
+
77
89
  component ||= main.c_id
78
90
  m_id ||= RSMP::Message.make_m_id
79
91
 
@@ -89,14 +101,18 @@ module RSMP
89
101
  'mId' => m_id
90
102
  })
91
103
  apply_nts_message_attributes message
92
- send_message message, validate: validate
93
- { sent: message }
104
+ send_message(message, validate: validate).map(&:message)
94
105
  end
95
106
 
96
- # Build, send a StatusSubscribe and collect the first matching status update. Returns the collector.
97
- # Call .ok! on the result to raise on NotAck or timeout.
107
+ def subscribe_to_status!(...)
108
+ subscribe_to_status(...).value!
109
+ end
110
+
111
+ # Build, send a StatusSubscribe and collect the first matching status update. Returns Result<Exchange>.
98
112
  def subscribe_to_status_and_collect(status_list, within:, component: nil, m_id: nil, validate: true)
99
- validate_ready 'subscribe to status'
113
+ readiness = validate_ready 'subscribe to status'
114
+ return readiness if readiness.failure?
115
+
100
116
  component ||= main.c_id
101
117
  m_id ||= RSMP::Message.make_m_id
102
118
 
@@ -113,7 +129,11 @@ module RSMP
113
129
  })
114
130
  apply_nts_message_attributes message
115
131
  collector = StatusCollector.new(self, list.to_a, timeout: within, m_id: m_id)
116
- send_message_and_collect(message, collector, validate: validate)[:collector]
132
+ send_message_and_collect(message, collector, validate: validate)
133
+ end
134
+
135
+ def subscribe_to_status_and_collect!(...)
136
+ subscribe_to_status_and_collect(...).value!
117
137
  end
118
138
 
119
139
  def remove_subscription_item(component_id, code, name)
@@ -131,15 +151,20 @@ module RSMP
131
151
  remove_subscription_item(component, item['sCI'], item['n'])
132
152
  end
133
153
 
134
- return unless ready? # if the connection is don't we skip sending
154
+ # Local subscription state must be cleaned up even when the peer has
155
+ # already disconnected. In that case there is nothing left to send.
156
+ return Result.success(nil) unless ready?
135
157
 
136
158
  message = RSMP::StatusUnsubscribe.new({
137
159
  'cId' => component,
138
160
  'sS' => status_list
139
161
  })
140
162
  apply_nts_message_attributes message
141
- send_message message, validate: validate
142
- message
163
+ send_message(message, validate: validate).map(&:message)
164
+ end
165
+
166
+ def unsubscribe_to_status!(...)
167
+ unsubscribe_to_status(...).value!
143
168
  end
144
169
 
145
170
  # unsubscribes to all statuses (with all attributes) defined in the used SXL
@@ -56,10 +56,6 @@ module RSMP
56
56
  else
57
57
  super
58
58
  end
59
- rescue RSMP::RepeatedAlarmError, RSMP::RepeatedStatusError, RSMP::TimestampError => e
60
- str = "Rejected #{message.type} message,"
61
- dont_acknowledge message, str, e.to_s
62
- distribute_error e.exception("#{str}#{e.message} #{message.json}")
63
59
  end
64
60
 
65
61
  def handled_by_parent?(message)
@@ -115,7 +111,18 @@ module RSMP
115
111
  end
116
112
 
117
113
  def validate_ready(action)
118
- raise NotReady, "Can't #{action} because connection is not ready. (Currently #{@state})" unless ready?
114
+ return Result.success(self) if ready?
115
+
116
+ Result.failure(
117
+ :not_ready,
118
+ message: "Can't #{action} because connection is not ready. (Currently #{@state})",
119
+ source: :connection,
120
+ context: { action: action, state: @state, session_id: session_id }
121
+ )
122
+ end
123
+
124
+ def validate_ready!(action)
125
+ validate_ready(action).value!
119
126
  end
120
127
 
121
128
  def version_acknowledged; end
@@ -204,11 +211,6 @@ module RSMP
204
211
  end
205
212
  end
206
213
 
207
- def receive_error(error, options = {})
208
- @supervisor&.receive_error error, options
209
- distribute_error error, options
210
- end
211
-
212
214
  def build_component(id:, type:, settings: {})
213
215
  settings ||= {}
214
216
  if type == 'main'
@@ -34,8 +34,13 @@ module RSMP
34
34
  configured = configured_sxls.find { |item| item['name'] == requested['name'] }
35
35
  return rejected_sxl(requested, 1, 'SXL not supported') unless configured
36
36
 
37
- if configured['version'].to_s == requested['version'].to_s
38
- RSMP::Schema.find_schema! requested['name'], requested['version'], lenient: true
37
+ unless RSMP::Schema.strict_version?(requested['version'])
38
+ return rejected_sxl(requested, 2, 'Core 3.3 requires SXL versions to use MAJOR.MINOR.PATCH')
39
+ end
40
+
41
+ configured_version = RSMP::Schema.sanitize_version(configured['version'].to_s)
42
+ if configured_version == requested['version']
43
+ RSMP::Schema.find_schema! requested['name'], requested['version']
39
44
  requested.slice('name', 'version', 'prefix')
40
45
  else
41
46
  rejected_sxl(requested, 2, "Supervisor only supports #{configured['version']}")
@@ -29,7 +29,7 @@ module RSMP
29
29
  })
30
30
 
31
31
  apply_nts_message_attributes message
32
- send_message message
32
+ send_generated_message message
33
33
  end
34
34
 
35
35
  def process_aggregated_status_request(message)
@@ -8,7 +8,7 @@ module RSMP
8
8
  component.alarms.each_pair do |_alarm_code, alarm_state|
9
9
  if alarm_state.active
10
10
  alarm = AlarmIssue.new(alarm_state.to_hash.merge('aSp' => 'Issue'))
11
- send_message alarm
11
+ send_generated_message alarm
12
12
  end
13
13
  end
14
14
  end
@@ -107,7 +107,7 @@ module RSMP
107
107
  })
108
108
  apply_nts_message_attributes response
109
109
  acknowledge message
110
- send_message response
110
+ send_generated_message response
111
111
  end
112
112
  end
113
113
  end
@@ -88,7 +88,7 @@ module RSMP
88
88
  end
89
89
  end
90
90
 
91
- def buffer_message(message, error = nil)
91
+ def buffer_message(message)
92
92
  prepared = prepare_message_for_buffer message, core_version: @core_version
93
93
  if prepared
94
94
  enqueue_buffered_message prepared
@@ -97,8 +97,6 @@ module RSMP
97
97
  else
98
98
  super
99
99
  end
100
- rescue NotReady, IOError
101
- raise error if error
102
100
  end
103
101
 
104
102
  def enqueue_buffered_message(message)
@@ -118,19 +116,20 @@ module RSMP
118
116
  @message_buffer = []
119
117
  log "Sending #{queued.size} buffered message(s)", level: :info
120
118
  queued.each_with_index do |message, index|
121
- break unless flush_buffered_message(message, queued, index)
119
+ break unless flush_buffered_message?(message, queued, index)
122
120
  end
123
121
  end
124
122
 
125
- def flush_buffered_message(message, queued, index)
123
+ def flush_buffered_message?(message, queued, index)
126
124
  prepared = prepare_message_for_buffer message, core_version: @core_version, for_send: true
127
125
  return true unless prepared
128
126
 
129
- send_message prepared, 'from buffer', buffer: false
130
- true
131
- rescue NotReady, IOError
127
+ result = send_message(prepared, 'from buffer', buffer: false)
128
+ return true if result.success?
129
+
132
130
  @message_buffer = queued[index..] + @message_buffer
133
- log "Stopped sending buffered messages; #{message_buffer.size} message(s) remain queued", level: :warning
131
+ log "Stopped sending buffered messages (#{result.failure.code}); " \
132
+ "#{message_buffer.size} message(s) remain queued", level: :warning
134
133
  false
135
134
  end
136
135
  end