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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +14 -0
- data/Gemfile.lock +19 -20
- data/README.md +27 -3
- data/Rakefile +3 -1
- data/documentation/cli.md +79 -4
- data/documentation/collecting_message.md +90 -170
- data/documentation/configuration.md +8 -0
- data/documentation/message_distribution.md +17 -3
- data/documentation/tasks.md +65 -117
- data/lib/rsmp/cli.rb +93 -5
- data/lib/rsmp/collect/collector.rb +117 -134
- data/lib/rsmp/collect/distributor.rb +16 -3
- data/lib/rsmp/collect/queue.rb +14 -6
- data/lib/rsmp/collect/receiver.rb +1 -1
- data/lib/rsmp/collect/state_collector.rb +11 -2
- data/lib/rsmp/collection.rb +12 -0
- data/lib/rsmp/completion.rb +49 -0
- data/lib/rsmp/component/component_base.rb +3 -3
- data/lib/rsmp/convert/export/json_schema/index.rb +8 -4
- data/lib/rsmp/convert/import/yaml.rb +12 -8
- data/lib/rsmp/delivery.rb +18 -0
- data/lib/rsmp/event.rb +19 -0
- data/lib/rsmp/event_source.rb +36 -0
- data/lib/rsmp/exchange.rb +10 -0
- data/lib/rsmp/failure.rb +23 -0
- data/lib/rsmp/helpers/error.rb +14 -23
- data/lib/rsmp/message.rb +10 -9
- data/lib/rsmp/node/node.rb +3 -21
- data/lib/rsmp/node/site/connections.rb +4 -9
- data/lib/rsmp/node/site/site.rb +33 -17
- data/lib/rsmp/node/supervisor/modules/configuration.rb +24 -11
- data/lib/rsmp/node/supervisor/modules/connection.rb +19 -5
- data/lib/rsmp/node/supervisor/modules/sites.rb +16 -5
- data/lib/rsmp/node/supervisor/supervisor.rb +13 -14
- data/lib/rsmp/node/task.rb +59 -53
- data/lib/rsmp/proxy/modules/acknowledgements.rb +22 -9
- data/lib/rsmp/proxy/modules/lifecycle.rb +134 -0
- data/lib/rsmp/proxy/modules/receive.rb +109 -66
- data/lib/rsmp/proxy/modules/send.rb +109 -51
- data/lib/rsmp/proxy/modules/state.rb +15 -5
- data/lib/rsmp/proxy/modules/tasks.rb +96 -70
- data/lib/rsmp/proxy/modules/versions.rb +69 -19
- data/lib/rsmp/proxy/modules/watchdogs.rb +15 -2
- data/lib/rsmp/proxy/proxy.rb +7 -69
- data/lib/rsmp/proxy/site/connection.rb +27 -22
- data/lib/rsmp/proxy/site/modules/aggregated_status.rb +18 -8
- data/lib/rsmp/proxy/site/modules/alarms.rb +45 -38
- data/lib/rsmp/proxy/site/modules/commands.rb +18 -8
- data/lib/rsmp/proxy/site/modules/status.rb +44 -19
- data/lib/rsmp/proxy/site/site_proxy.rb +12 -10
- data/lib/rsmp/proxy/site/sxl_selection.rb +7 -2
- data/lib/rsmp/proxy/supervisor/modules/aggregated_status.rb +1 -1
- data/lib/rsmp/proxy/supervisor/modules/alarms.rb +1 -1
- data/lib/rsmp/proxy/supervisor/modules/commands.rb +1 -1
- data/lib/rsmp/proxy/supervisor/modules/message_buffer.rb +8 -9
- data/lib/rsmp/proxy/supervisor/modules/status.rb +1 -1
- data/lib/rsmp/proxy/supervisor/modules/status_updates.rb +1 -1
- data/lib/rsmp/proxy/supervisor/supervisor_proxy.rb +36 -31
- data/lib/rsmp/result.rb +95 -0
- data/lib/rsmp/schema/validation.rb +5 -3
- data/lib/rsmp/schema/version.rb +27 -0
- data/lib/rsmp/schema.rb +1 -0
- data/lib/rsmp/sxl/interface.rb +8 -1
- data/lib/rsmp/sxl/processing/catalogue.rb +61 -0
- data/lib/rsmp/sxl/processing/conflict_checker.rb +30 -0
- data/lib/rsmp/sxl/processing/dependency_graph.rb +37 -0
- data/lib/rsmp/sxl/processing/document.rb +158 -0
- data/lib/rsmp/sxl/processing/error.rb +8 -0
- data/lib/rsmp/sxl/processing/manifest.rb +144 -0
- data/lib/rsmp/sxl/processing/natural_sort.rb +22 -0
- data/lib/rsmp/sxl/processing/resolver.rb +94 -0
- data/lib/rsmp/sxl/processing/version_requirement.rb +86 -0
- data/lib/rsmp/sxl/processing.rb +18 -0
- data/lib/rsmp/sxl/supervisor_interface.rb +8 -1
- data/lib/rsmp/termination.rb +10 -0
- data/lib/rsmp/tlc/modules/plans.rb +0 -2
- data/lib/rsmp/tlc/modules/system.rb +2 -2
- data/lib/rsmp/tlc/proxy/control.rb +50 -16
- data/lib/rsmp/tlc/proxy/detectors.rb +15 -4
- data/lib/rsmp/tlc/proxy/io.rb +46 -31
- data/lib/rsmp/tlc/proxy/plans.rb +36 -19
- data/lib/rsmp/tlc/proxy/plans_bang.rb +44 -0
- data/lib/rsmp/tlc/proxy/status.rb +47 -23
- data/lib/rsmp/tlc/proxy/system.rb +16 -4
- data/lib/rsmp/tlc/supervisor_interface.rb +7 -1
- data/lib/rsmp/tlc/traffic_controller_site.rb +6 -9
- data/lib/rsmp/validation.rb +22 -0
- data/lib/rsmp/version.rb +1 -1
- data/lib/rsmp.rb +19 -4
- data/rsmp.gemspec +1 -1
- metadata +26 -4
- data/lib/rsmp/collect/collector/status.rb +0 -34
|
@@ -1,79 +1,137 @@
|
|
|
1
1
|
module RSMP
|
|
2
2
|
class Proxy
|
|
3
3
|
module Modules
|
|
4
|
-
# Message sending functionality
|
|
5
|
-
#
|
|
4
|
+
# Message sending functionality. Expected operational failures are returned
|
|
5
|
+
# as Result::Failure; unexpected implementation errors are not rescued.
|
|
6
6
|
module Send
|
|
7
|
-
def handle_send_schema_error(message, error)
|
|
8
|
-
schemas_string = error.schemas.map { |schema| "#{schema.first}: #{schema.last}" }.join(', ')
|
|
9
|
-
str = "Could not send #{message.type} because schema validation failed (#{schemas_string}): #{error.message}"
|
|
10
|
-
log str, message: message, level: :error
|
|
11
|
-
distribute_error error.exception("#{str} #{message.json}")
|
|
12
|
-
end
|
|
13
|
-
|
|
14
7
|
def send_message(message, reason = nil, validate: true, force: false, buffer: true)
|
|
15
|
-
|
|
16
|
-
write_message(message, validate: validate)
|
|
17
|
-
expect_acknowledgement message
|
|
18
|
-
distribute message
|
|
19
|
-
log_send message, reason
|
|
20
|
-
end
|
|
21
|
-
rescue NotReady, IOError => e
|
|
22
|
-
raise e unless buffer
|
|
8
|
+
return buffer_or_fail(message, buffer: buffer) unless force || connected?
|
|
23
9
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
handle_send_schema_error(message, e)
|
|
27
|
-
end
|
|
10
|
+
written = write_message(message, validate: validate)
|
|
11
|
+
return recover_write_failure(message, written, buffer: buffer) if written.failure?
|
|
28
12
|
|
|
29
|
-
|
|
30
|
-
|
|
13
|
+
expect_acknowledgement(message)
|
|
14
|
+
distribute(message)
|
|
15
|
+
log_send(message, reason)
|
|
16
|
+
Result.success(Delivery.new(message: message, state: :sent))
|
|
17
|
+
end
|
|
31
18
|
|
|
32
|
-
|
|
33
|
-
|
|
19
|
+
def send_message!(...)
|
|
20
|
+
send_message(...).value!
|
|
21
|
+
end
|
|
34
22
|
|
|
35
|
-
|
|
23
|
+
# Messages constructed by RSMP itself should never fail local schema
|
|
24
|
+
# validation. Surface that as an implementation defect while preserving
|
|
25
|
+
# connection and transport failures as ordinary Result values.
|
|
26
|
+
def send_generated_message(...)
|
|
27
|
+
result = send_message(...)
|
|
28
|
+
result.value! if result.failure&.source == :local
|
|
29
|
+
result
|
|
36
30
|
end
|
|
37
31
|
|
|
38
32
|
def write_message(message, validate:)
|
|
39
|
-
|
|
33
|
+
return disconnected_send_result(message) unless @protocol
|
|
34
|
+
|
|
35
|
+
prepared = prepare_message(message, validate: validate)
|
|
36
|
+
return prepared if prepared.failure?
|
|
40
37
|
|
|
38
|
+
write_protocol(message)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def prepare_message(message, validate:)
|
|
41
42
|
message.direction = :out
|
|
42
43
|
message.encode_for(schemas) unless validate == false
|
|
43
44
|
message.generate_json
|
|
44
|
-
|
|
45
|
-
|
|
45
|
+
unless validate == false
|
|
46
|
+
validation = message.validate(schemas)
|
|
47
|
+
return invalid_outbound_result(message, validation.message, validation: validation) if validation.invalid?
|
|
48
|
+
end
|
|
49
|
+
Result.success(message)
|
|
50
|
+
rescue RSMP::Schema::UnknownMessageCodeError => e
|
|
51
|
+
invalid_outbound_result(message, e.message, cause: e)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def write_protocol(message)
|
|
55
|
+
@protocol.write_lines(message.json)
|
|
56
|
+
Result.success(message)
|
|
57
|
+
rescue IOError, SystemCallError => e
|
|
58
|
+
Result.failure(
|
|
59
|
+
:disconnected,
|
|
60
|
+
message: "Could not send #{message.type}: #{e.message}",
|
|
61
|
+
source: :transport,
|
|
62
|
+
context: { message: message, session_id: session_id },
|
|
63
|
+
cause: e
|
|
64
|
+
)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def recover_write_failure(message, result, buffer:)
|
|
68
|
+
return result unless result.failure.code == :disconnected
|
|
69
|
+
|
|
70
|
+
buffered = buffer_message(message) if buffer
|
|
71
|
+
return Result.success(Delivery.new(message: buffered, state: :buffered)) if buffered
|
|
72
|
+
|
|
73
|
+
result
|
|
46
74
|
end
|
|
47
75
|
|
|
48
|
-
def
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
76
|
+
def buffer_or_fail(message, buffer:)
|
|
77
|
+
buffered = buffer_message(message) if buffer
|
|
78
|
+
return Result.success(Delivery.new(message: buffered, state: :buffered)) if buffered
|
|
79
|
+
|
|
80
|
+
Result.failure(
|
|
81
|
+
:not_ready,
|
|
82
|
+
message: "Cannot send #{message.type}: connection is #{@state}",
|
|
83
|
+
source: :connection,
|
|
84
|
+
context: { message: message, state: @state, session_id: session_id }
|
|
85
|
+
)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Base proxies do not buffer. SupervisorProxy's MessageBuffer override
|
|
89
|
+
# returns a cloned queued message when its explicit policy accepts it.
|
|
90
|
+
def buffer_message(message)
|
|
91
|
+
log "Discarded #{message.type}; connection is #{@state}", message: message, level: :warning
|
|
92
|
+
nil
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def disconnected_send_result(message)
|
|
96
|
+
Result.failure(
|
|
97
|
+
:disconnected,
|
|
98
|
+
message: "Cannot send #{message.type}: connection transport is closed",
|
|
99
|
+
source: :transport,
|
|
100
|
+
context: { message: message, session_id: session_id }
|
|
101
|
+
)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def invalid_outbound_result(message, detail, validation: nil, cause: nil)
|
|
105
|
+
text = "Could not send #{message.type}: #{detail}"
|
|
106
|
+
log(text, message: message, level: :error)
|
|
107
|
+
Result.failure(
|
|
108
|
+
:invalid_outbound_message,
|
|
109
|
+
message: text,
|
|
110
|
+
source: :local,
|
|
111
|
+
context: { message: message, validation: validation }.compact,
|
|
112
|
+
cause: cause
|
|
113
|
+
)
|
|
52
114
|
end
|
|
53
115
|
|
|
54
116
|
def log_send(message, reason = nil)
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
"Sent #{message.type}"
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
if message.type == 'MessageNotAck'
|
|
62
|
-
log str, message: message, level: :warning
|
|
63
|
-
else
|
|
64
|
-
log str, message: message, level: :log
|
|
65
|
-
end
|
|
117
|
+
text = reason ? "Sent #{message.type} #{reason}" : "Sent #{message.type}"
|
|
118
|
+
level = message.type == 'MessageNotAck' ? :warning : :log
|
|
119
|
+
log(text, message: message, level: level)
|
|
66
120
|
end
|
|
67
121
|
|
|
68
122
|
def send_message_and_collect(message, collector, validate: true)
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
123
|
+
collector.start
|
|
124
|
+
delivery = send_message(message, validate: validate, buffer: false)
|
|
125
|
+
collector.fail(delivery.failure) if delivery.failure?
|
|
126
|
+
collector.wait.map do |collection|
|
|
127
|
+
Exchange.new(request: message, collection: collection)
|
|
73
128
|
end
|
|
74
|
-
|
|
75
|
-
collector
|
|
76
|
-
|
|
129
|
+
ensure
|
|
130
|
+
collector.stop
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def send_message_and_collect!(...)
|
|
134
|
+
send_message_and_collect(...).value!
|
|
77
135
|
end
|
|
78
136
|
|
|
79
137
|
def apply_nts_message_attributes(message)
|
|
@@ -6,14 +6,24 @@ module RSMP
|
|
|
6
6
|
module State
|
|
7
7
|
def wait_for_state(state, timeout:)
|
|
8
8
|
states = [state].flatten
|
|
9
|
-
return
|
|
9
|
+
return Result.success(@state) if states.include?(@state)
|
|
10
10
|
|
|
11
|
-
wait_for_condition(@state_condition, timeout: timeout) do
|
|
11
|
+
result = wait_for_condition(@state_condition, timeout: timeout) do
|
|
12
12
|
states.include?(@state)
|
|
13
13
|
end
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
return Result.success(@state) if result.success?
|
|
15
|
+
|
|
16
|
+
Result.failure(
|
|
17
|
+
:timeout,
|
|
18
|
+
message: "Did not reach state #{state} within #{timeout}s",
|
|
19
|
+
source: :timeout,
|
|
20
|
+
context: { expected: states, actual: @state },
|
|
21
|
+
cause: result.failure.cause
|
|
22
|
+
)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def wait_for_state!(...)
|
|
26
|
+
wait_for_state(...).value!
|
|
17
27
|
end
|
|
18
28
|
|
|
19
29
|
def handshake_complete
|
|
@@ -1,103 +1,129 @@
|
|
|
1
1
|
module RSMP
|
|
2
2
|
class Proxy
|
|
3
3
|
module Modules
|
|
4
|
-
# Reader and timer
|
|
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
|
-
|
|
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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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)
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
98
|
+
id = @session_id
|
|
62
99
|
interval = @site_settings['intervals']['timer'] || 1
|
|
63
|
-
log "Starting
|
|
100
|
+
log "Starting timer with interval #{interval} seconds", level: :debug
|
|
64
101
|
@latest_watchdog_received = Clock.now
|
|
65
|
-
@timer =
|
|
66
|
-
task.annotate
|
|
67
|
-
run_timer
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
|
|
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
|
|
99
|
-
check_ack_timeout
|
|
100
|
-
|
|
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
|
|
@@ -8,13 +8,23 @@ module RSMP
|
|
|
8
8
|
version = @site_settings['core_version']
|
|
9
9
|
if version == 'latest'
|
|
10
10
|
[RSMP::Schema.latest_core_version]
|
|
11
|
-
elsif version
|
|
12
|
-
[version]
|
|
13
|
-
else
|
|
11
|
+
elsif version.nil? || version == 'all'
|
|
14
12
|
RSMP::Schema.core_versions
|
|
13
|
+
else
|
|
14
|
+
[RSMP::Schema.normalize_core_version(version)].compact
|
|
15
15
|
end
|
|
16
16
|
end
|
|
17
17
|
|
|
18
|
+
# Version strings to advertise before a Core version has been selected.
|
|
19
|
+
# Include known legacy spellings so peers using exact string matching
|
|
20
|
+
# can find a common pre-3.3 version.
|
|
21
|
+
def advertised_core_versions
|
|
22
|
+
versions = core_versions.flat_map { |version| wire_core_version_aliases(version) }
|
|
23
|
+
configured = @site_settings['core_version']
|
|
24
|
+
versions.unshift configured if RSMP::Schema.normalize_core_version(configured)
|
|
25
|
+
versions.uniq
|
|
26
|
+
end
|
|
27
|
+
|
|
18
28
|
def core_3_3?
|
|
19
29
|
core_version && version_meets_requirement?(core_version, '>=3.3.0')
|
|
20
30
|
end
|
|
@@ -29,8 +39,9 @@ module RSMP
|
|
|
29
39
|
|
|
30
40
|
def sxl_request_items
|
|
31
41
|
configured_sxls.map do |sxl|
|
|
32
|
-
|
|
33
|
-
|
|
42
|
+
version = RSMP::Schema.sanitize_version(sxl['version'].to_s)
|
|
43
|
+
item = { 'name' => sxl['name'], 'version' => version }
|
|
44
|
+
prefix = RSMP::Schema.sxl_prefix(sxl['name'], version)
|
|
34
45
|
item['prefix'] = prefix if prefix
|
|
35
46
|
item
|
|
36
47
|
end
|
|
@@ -38,10 +49,14 @@ module RSMP
|
|
|
38
49
|
|
|
39
50
|
def check_core_version(message)
|
|
40
51
|
versions = core_versions
|
|
41
|
-
|
|
42
|
-
|
|
52
|
+
candidates = message.versions.filter_map do |wire_version|
|
|
53
|
+
normalized = RSMP::Schema.normalize_core_version(wire_version)
|
|
54
|
+
[wire_version, normalized] if normalized && versions.include?(normalized)
|
|
55
|
+
end
|
|
43
56
|
if candidates.any?
|
|
44
|
-
@core_version = candidates.max_by
|
|
57
|
+
@core_version_string, @core_version = candidates.max_by do |_wire_version, normalized|
|
|
58
|
+
Gem::Version.new(normalized)
|
|
59
|
+
end
|
|
45
60
|
else
|
|
46
61
|
reason = "RSMP versions [#{message.versions.join(', ')}] requested, " \
|
|
47
62
|
"but only [#{versions.join(', ')}] supported."
|
|
@@ -66,22 +81,33 @@ module RSMP
|
|
|
66
81
|
|
|
67
82
|
def send_version_response(site_id, core_versions)
|
|
68
83
|
if core_3_3?
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
84
|
+
send_generated_message Version.new({
|
|
85
|
+
'step' => 'Response',
|
|
86
|
+
'RSMP' => [{ 'vers' => core_version }],
|
|
87
|
+
'supervisorId' => site_id,
|
|
88
|
+
'SXLS' => version_response_sxls,
|
|
89
|
+
'receiveAlarms' => @site_settings['receive_alarms'] != false
|
|
90
|
+
}), validate: false
|
|
76
91
|
else
|
|
77
|
-
|
|
92
|
+
send_legacy_version_response(site_id, core_versions)
|
|
78
93
|
end
|
|
79
94
|
end
|
|
80
95
|
|
|
96
|
+
def send_legacy_version_response(site_id, core_versions)
|
|
97
|
+
response_versions = normalized_core_versions(core_versions).map do |version|
|
|
98
|
+
normalized = RSMP::Schema.normalize_core_version(version)
|
|
99
|
+
normalized == core_version ? core_version_string : version
|
|
100
|
+
end.uniq
|
|
101
|
+
attributes = version_message_attributes(site_id, response_versions)
|
|
102
|
+
primary = accepted_sxls.first
|
|
103
|
+
attributes['SXL'] = primary['version'].to_s if primary
|
|
104
|
+
send_message Version.new(attributes), validate: false
|
|
105
|
+
end
|
|
106
|
+
|
|
81
107
|
def send_version_message(site_id, core_versions, step:)
|
|
82
108
|
attributes = version_message_attributes(site_id, core_versions)
|
|
83
109
|
attributes.merge!(version_request_attributes) if step == 'Request'
|
|
84
|
-
|
|
110
|
+
send_generated_message Version.new(attributes), validate: false
|
|
85
111
|
end
|
|
86
112
|
|
|
87
113
|
def version_message_attributes(site_id, core_versions)
|
|
@@ -109,6 +135,15 @@ module RSMP
|
|
|
109
135
|
end
|
|
110
136
|
end
|
|
111
137
|
|
|
138
|
+
def wire_core_version_aliases(version)
|
|
139
|
+
normalized = RSMP::Schema.normalize_core_version(version)
|
|
140
|
+
return [version] unless normalized
|
|
141
|
+
return [normalized] if Gem::Version.new(normalized) >= Gem::Version.new('3.3.0')
|
|
142
|
+
return [normalized] unless normalized.end_with?('.0')
|
|
143
|
+
|
|
144
|
+
[normalized, normalized.delete_suffix('.0')]
|
|
145
|
+
end
|
|
146
|
+
|
|
112
147
|
def site_id_items(site_id)
|
|
113
148
|
[site_id].flatten.map { |id| { 'sId' => id } }
|
|
114
149
|
end
|
|
@@ -124,13 +159,28 @@ module RSMP
|
|
|
124
159
|
accepted_sxls + rejected_sxls
|
|
125
160
|
end
|
|
126
161
|
|
|
162
|
+
def validate_sxl_response!(sxls)
|
|
163
|
+
invalid = sxls.find { |accepted| !valid_sxl_response?(accepted) }
|
|
164
|
+
return unless invalid
|
|
165
|
+
|
|
166
|
+
raise HandshakeError,
|
|
167
|
+
"Invalid SXL version #{invalid['name']} #{invalid['version']}; " \
|
|
168
|
+
'Core 3.3 requires an exact MAJOR.MINOR.PATCH match'
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def valid_sxl_response?(accepted)
|
|
172
|
+
requested = sxl_request_items.find { |item| item['name'] == accepted['name'] }
|
|
173
|
+
RSMP::Schema.strict_version?(accepted['version']) && requested &&
|
|
174
|
+
requested['version'] == accepted['version']
|
|
175
|
+
end
|
|
176
|
+
|
|
127
177
|
def version_acknowledged; end
|
|
128
178
|
|
|
129
179
|
def component_list_acknowledged; end
|
|
130
180
|
|
|
131
181
|
# Use Gem class to check version requirement
|
|
132
|
-
# Requirement must be a string like '1.1', '>=1.0.3' or '<2.1.4',
|
|
133
|
-
# or list of strings, like ['<=1.4','<1.5']
|
|
182
|
+
# Requirement must be a string like '1.1.0', '>=1.0.3' or '<2.1.4',
|
|
183
|
+
# or list of strings, like ['<=1.4.0','<1.5.0']
|
|
134
184
|
def self.version_meets_requirement?(version, requirement)
|
|
135
185
|
Gem::Requirement.new(requirement).satisfied_by?(Gem::Version.new(version))
|
|
136
186
|
end
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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)
|