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.
- checksums.yaml +4 -4
- data/.devcontainer/devcontainer.json +22 -0
- data/.github/workflows/rubocop.yaml +17 -0
- data/.gitignore +5 -6
- data/.rubocop.yml +80 -0
- data/Gemfile +13 -1
- data/Gemfile.lock +34 -1
- data/Rakefile +3 -3
- data/lib/rsmp/cli.rb +147 -124
- data/lib/rsmp/collect/ack_collector.rb +8 -7
- data/lib/rsmp/collect/aggregated_status_collector.rb +4 -4
- data/lib/rsmp/collect/alarm_collector.rb +31 -23
- data/lib/rsmp/collect/alarm_matcher.rb +3 -3
- data/lib/rsmp/collect/collector/logging.rb +17 -0
- data/lib/rsmp/collect/collector/reporting.rb +44 -0
- data/lib/rsmp/collect/collector/status.rb +34 -0
- data/lib/rsmp/collect/collector.rb +69 -150
- data/lib/rsmp/collect/command_matcher.rb +19 -6
- data/lib/rsmp/collect/command_response_collector.rb +7 -7
- data/lib/rsmp/collect/distributor.rb +14 -11
- data/lib/rsmp/collect/filter.rb +31 -15
- data/lib/rsmp/collect/matcher.rb +7 -11
- data/lib/rsmp/collect/queue.rb +4 -4
- data/lib/rsmp/collect/receiver.rb +10 -12
- data/lib/rsmp/collect/state_collector.rb +116 -77
- data/lib/rsmp/collect/status_collector.rb +6 -6
- data/lib/rsmp/collect/status_matcher.rb +17 -7
- data/lib/rsmp/{alarm_state.rb → component/alarm_state.rb} +76 -37
- data/lib/rsmp/{component.rb → component/component.rb} +15 -15
- data/lib/rsmp/component/component_base.rb +89 -0
- data/lib/rsmp/component/component_proxy.rb +75 -0
- data/lib/rsmp/component/components.rb +63 -0
- data/lib/rsmp/convert/export/json_schema.rb +116 -110
- data/lib/rsmp/convert/import/yaml.rb +21 -18
- data/lib/rsmp/{rsmp.rb → helpers/clock.rb} +5 -6
- data/lib/rsmp/{deep_merge.rb → helpers/deep_merge.rb} +2 -1
- data/lib/rsmp/helpers/error.rb +71 -0
- data/lib/rsmp/{inspect.rb → helpers/inspect.rb} +6 -10
- data/lib/rsmp/log/archive.rb +98 -0
- data/lib/rsmp/log/colorization.rb +41 -0
- data/lib/rsmp/log/filtering.rb +54 -0
- data/lib/rsmp/log/logger.rb +206 -0
- data/lib/rsmp/{logging.rb → log/logging.rb} +5 -7
- data/lib/rsmp/message.rb +159 -148
- data/lib/rsmp/{node.rb → node/node.rb} +19 -17
- data/lib/rsmp/{protocol.rb → node/protocol.rb} +5 -3
- data/lib/rsmp/node/site/site.rb +195 -0
- data/lib/rsmp/node/supervisor/modules/configuration.rb +59 -0
- data/lib/rsmp/node/supervisor/modules/connection.rb +140 -0
- data/lib/rsmp/node/supervisor/modules/sites.rb +64 -0
- data/lib/rsmp/node/supervisor/supervisor.rb +72 -0
- data/lib/rsmp/{task.rb → node/task.rb} +12 -14
- data/lib/rsmp/proxy/modules/acknowledgements.rb +144 -0
- data/lib/rsmp/proxy/modules/receive.rb +119 -0
- data/lib/rsmp/proxy/modules/send.rb +76 -0
- data/lib/rsmp/proxy/modules/state.rb +25 -0
- data/lib/rsmp/proxy/modules/tasks.rb +105 -0
- data/lib/rsmp/proxy/modules/versions.rb +69 -0
- data/lib/rsmp/proxy/modules/watchdogs.rb +66 -0
- data/lib/rsmp/proxy/proxy.rb +199 -0
- data/lib/rsmp/proxy/site/modules/aggregated_status.rb +52 -0
- data/lib/rsmp/proxy/site/modules/alarms.rb +27 -0
- data/lib/rsmp/proxy/site/modules/commands.rb +31 -0
- data/lib/rsmp/proxy/site/modules/status.rb +110 -0
- data/lib/rsmp/proxy/site/site_proxy.rb +205 -0
- data/lib/rsmp/proxy/supervisor/modules/aggregated_status.rb +47 -0
- data/lib/rsmp/proxy/supervisor/modules/alarms.rb +73 -0
- data/lib/rsmp/proxy/supervisor/modules/commands.rb +53 -0
- data/lib/rsmp/proxy/supervisor/modules/status.rb +204 -0
- data/lib/rsmp/proxy/supervisor/supervisor_proxy.rb +178 -0
- data/lib/rsmp/tlc/detector_logic.rb +18 -34
- data/lib/rsmp/tlc/input_states.rb +126 -0
- data/lib/rsmp/tlc/modules/detector_logics.rb +50 -0
- data/lib/rsmp/tlc/modules/display.rb +78 -0
- data/lib/rsmp/tlc/modules/helpers.rb +41 -0
- data/lib/rsmp/tlc/modules/inputs.rb +173 -0
- data/lib/rsmp/tlc/modules/modes.rb +253 -0
- data/lib/rsmp/tlc/modules/outputs.rb +30 -0
- data/lib/rsmp/tlc/modules/plans.rb +218 -0
- data/lib/rsmp/tlc/modules/signal_groups.rb +109 -0
- data/lib/rsmp/tlc/modules/startup_sequence.rb +22 -0
- data/lib/rsmp/tlc/modules/system.rb +140 -0
- data/lib/rsmp/tlc/modules/traffic_data.rb +49 -0
- data/lib/rsmp/tlc/signal_group.rb +37 -41
- data/lib/rsmp/tlc/signal_plan.rb +14 -11
- data/lib/rsmp/tlc/signal_priority.rb +39 -35
- data/lib/rsmp/tlc/startup_sequence.rb +59 -0
- data/lib/rsmp/tlc/traffic_controller.rb +38 -1010
- data/lib/rsmp/tlc/traffic_controller_site.rb +58 -57
- data/lib/rsmp/version.rb +1 -1
- data/lib/rsmp.rb +82 -48
- data/rsmp.gemspec +24 -31
- metadata +79 -139
- data/lib/rsmp/archive.rb +0 -76
- data/lib/rsmp/collect/message_matchers.rb +0 -0
- data/lib/rsmp/component_base.rb +0 -87
- data/lib/rsmp/component_proxy.rb +0 -57
- data/lib/rsmp/components.rb +0 -65
- data/lib/rsmp/error.rb +0 -71
- data/lib/rsmp/logger.rb +0 -216
- data/lib/rsmp/proxy.rb +0 -693
- data/lib/rsmp/site.rb +0 -188
- data/lib/rsmp/site_proxy.rb +0 -389
- data/lib/rsmp/supervisor.rb +0 -302
- data/lib/rsmp/supervisor_proxy.rb +0 -510
- data/lib/rsmp/tlc/inputs.rb +0 -134
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
module RSMP
|
|
2
|
+
class Proxy
|
|
3
|
+
module Modules
|
|
4
|
+
# Message acknowledgement handling
|
|
5
|
+
# Manages sending/receiving acks and nacks, and tracking acknowledged messages
|
|
6
|
+
module Acknowledgements
|
|
7
|
+
def acknowledge(original)
|
|
8
|
+
raise InvalidArgument unless original
|
|
9
|
+
|
|
10
|
+
ack = MessageAck.build_from(original)
|
|
11
|
+
ack.original = original.clone
|
|
12
|
+
send_message ack, "for #{ack.original.type} #{original.m_id_short}"
|
|
13
|
+
check_ingoing_acknowledged original
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def dont_acknowledge(original, prefix = nil, reason = nil, force: true)
|
|
17
|
+
raise InvalidArgument unless original
|
|
18
|
+
|
|
19
|
+
str = [prefix, reason].join(' ')
|
|
20
|
+
log str, message: original, level: :warning if reason
|
|
21
|
+
message = MessageNotAck.new({
|
|
22
|
+
'oMId' => original.m_id,
|
|
23
|
+
'rea' => reason || 'Unknown reason'
|
|
24
|
+
})
|
|
25
|
+
message.original = original.clone
|
|
26
|
+
send_message message, "for #{original.type} #{original.m_id_short}", force: force
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def expect_acknowledgement(message)
|
|
30
|
+
return if message.is_a?(MessageAck) || message.is_a?(MessageNotAck)
|
|
31
|
+
|
|
32
|
+
@awaiting_acknowledgement[message.m_id] = message
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def dont_expect_acknowledgement(message)
|
|
36
|
+
@awaiting_acknowledgement.delete message.attribute('oMId')
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def check_ack_timeout(now)
|
|
40
|
+
timeout = @site_settings['timeouts']['acknowledgement']
|
|
41
|
+
# hash cannot be modify during iteration, so clone it
|
|
42
|
+
@awaiting_acknowledgement.clone.each_pair do |_m_id, message|
|
|
43
|
+
latest = message.timestamp + timeout
|
|
44
|
+
next unless now > latest
|
|
45
|
+
|
|
46
|
+
str = "No acknowledgements for #{message.type} #{message.m_id_short} within #{timeout} seconds"
|
|
47
|
+
log str, level: :error
|
|
48
|
+
begin
|
|
49
|
+
close
|
|
50
|
+
ensure
|
|
51
|
+
distribute_error MissingAcknowledgment.new(str)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def find_original_for_message(message)
|
|
57
|
+
@awaiting_acknowledgement[message.attribute('oMId')]
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# TODO: this might be better handled by a proper event machine using e.g. the EventMachine gem
|
|
61
|
+
def check_outgoing_acknowledged(message)
|
|
62
|
+
return if @outgoing_acknowledged[message.type]
|
|
63
|
+
|
|
64
|
+
@outgoing_acknowledged[message.type] = true
|
|
65
|
+
acknowledged_first_outgoing message
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def check_ingoing_acknowledged(message)
|
|
69
|
+
return if @ingoing_acknowledged[message.type]
|
|
70
|
+
|
|
71
|
+
@ingoing_acknowledged[message.type] = true
|
|
72
|
+
acknowledged_first_ingoing message
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def acknowledged_first_outgoing(message); end
|
|
76
|
+
|
|
77
|
+
def acknowledged_first_ingoing(message); end
|
|
78
|
+
|
|
79
|
+
def process_ack(message)
|
|
80
|
+
original = find_original_for_message message
|
|
81
|
+
if original
|
|
82
|
+
dont_expect_acknowledgement message
|
|
83
|
+
message.original = original
|
|
84
|
+
log_acknowledgement_for_original message, original
|
|
85
|
+
|
|
86
|
+
case original.type
|
|
87
|
+
when 'Version'
|
|
88
|
+
version_acknowledged
|
|
89
|
+
when 'StatusSubscribe'
|
|
90
|
+
status_subscribe_acknowledged original
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
check_outgoing_acknowledged original
|
|
94
|
+
|
|
95
|
+
@acknowledgements[original.m_id] = message
|
|
96
|
+
@acknowledgement_condition.signal message
|
|
97
|
+
else
|
|
98
|
+
log_acknowledgement_for_unknown message
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def process_not_ack(message)
|
|
103
|
+
original = find_original_for_message message
|
|
104
|
+
if original
|
|
105
|
+
dont_expect_acknowledgement message
|
|
106
|
+
message.original = original
|
|
107
|
+
log_acknowledgement_for_original message, original
|
|
108
|
+
@acknowledgements[original.m_id] = message
|
|
109
|
+
@acknowledgement_condition.signal message
|
|
110
|
+
else
|
|
111
|
+
log_acknowledgement_for_unknown message
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def log_acknowledgement_for_original(message, original)
|
|
116
|
+
str = "Received #{message.type} for #{original.type} #{message.attribute('oMId')[0..3]}"
|
|
117
|
+
if message.type == 'MessageNotAck'
|
|
118
|
+
reason = message.attributes['rea']
|
|
119
|
+
str = "#{str}: #{reason}" if reason
|
|
120
|
+
log str, message: message, level: :warning
|
|
121
|
+
else
|
|
122
|
+
log str, message: message, level: :log
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def log_acknowledgement_for_unknown(message)
|
|
127
|
+
log "Received #{message.type} for unknown message #{message.attribute('oMId')[0..3]}", message: message,
|
|
128
|
+
level: :warning
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def status_subscribe_acknowledged(original)
|
|
132
|
+
component = find_component original.attribute('cId')
|
|
133
|
+
return unless component
|
|
134
|
+
|
|
135
|
+
short = Message.shorten_m_id original.m_id
|
|
136
|
+
subscribe_list = original.attributes['sS']
|
|
137
|
+
log "StatusSubscribe #{short} acknowledged, allowing repeated status values for #{subscribe_list}",
|
|
138
|
+
level: :info
|
|
139
|
+
component.allow_repeat_updates subscribe_list
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
module RSMP
|
|
2
|
+
class Proxy
|
|
3
|
+
module Modules
|
|
4
|
+
# Message processing functionality
|
|
5
|
+
# Handles receiving and processing incoming messages
|
|
6
|
+
module Receive
|
|
7
|
+
def should_validate_ingoing_message?(message)
|
|
8
|
+
return true unless @site_settings
|
|
9
|
+
|
|
10
|
+
skip = @site_settings['skip_validation']
|
|
11
|
+
return true unless skip
|
|
12
|
+
|
|
13
|
+
klass = message.class.name.split('::').last
|
|
14
|
+
!skip.include?(klass)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def process_deferred
|
|
18
|
+
@node.process_deferred
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def verify_sequence(message)
|
|
22
|
+
expect_version_message(message) unless @version_determined
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def handle_invalid_packet(json, error)
|
|
26
|
+
str = "Received invalid package, must be valid JSON but got #{json.size} bytes: #{error.message}"
|
|
27
|
+
distribute_error error.exception(str)
|
|
28
|
+
log str, level: :warning
|
|
29
|
+
nil
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def handle_malformed_message(attributes, error)
|
|
33
|
+
str = "Received malformed message, #{error.message}"
|
|
34
|
+
distribute_error error.exception(str)
|
|
35
|
+
log str, message: Malformed.new(attributes), level: :warning
|
|
36
|
+
nil
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def handle_schema_error(message, error)
|
|
40
|
+
schemas_string = error.schemas.map { |schema| "#{schema.first}: #{schema.last}" }.join(', ')
|
|
41
|
+
reason = "schema errors (#{schemas_string}): #{error.message}"
|
|
42
|
+
str = "Received invalid #{message.type}"
|
|
43
|
+
distribute_error error.exception(str), message: message
|
|
44
|
+
dont_acknowledge message, str, reason
|
|
45
|
+
message
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def handle_invalid_message(message, error)
|
|
49
|
+
reason = error.message.to_s
|
|
50
|
+
str = "Received invalid #{message.type},"
|
|
51
|
+
distribute_error error.exception("#{str} #{message.json}"), message: message
|
|
52
|
+
dont_acknowledge message, str, reason
|
|
53
|
+
message
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def handle_fatal_error(message, error)
|
|
57
|
+
reason = error.message
|
|
58
|
+
str = "Rejected #{message.type},"
|
|
59
|
+
distribute_error error.exception(str), message: message
|
|
60
|
+
dont_acknowledge message, str, reason
|
|
61
|
+
close
|
|
62
|
+
message
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def process_packet(json)
|
|
66
|
+
attributes = Message.parse_attributes json
|
|
67
|
+
message = Message.build attributes, json
|
|
68
|
+
message.validate(schemas) if should_validate_ingoing_message?(message)
|
|
69
|
+
verify_sequence message
|
|
70
|
+
with_deferred_distribution do
|
|
71
|
+
distribute message
|
|
72
|
+
process_message message
|
|
73
|
+
end
|
|
74
|
+
process_deferred
|
|
75
|
+
message
|
|
76
|
+
rescue InvalidPacket => e
|
|
77
|
+
handle_invalid_packet(json, e)
|
|
78
|
+
rescue MalformedMessage => e
|
|
79
|
+
handle_malformed_message(attributes, e)
|
|
80
|
+
rescue SchemaError, RSMP::Schema::Error => e
|
|
81
|
+
handle_schema_error(message, e)
|
|
82
|
+
rescue InvalidMessage => e
|
|
83
|
+
handle_invalid_message(message, e)
|
|
84
|
+
rescue FatalError => e
|
|
85
|
+
handle_fatal_error(message, e)
|
|
86
|
+
ensure
|
|
87
|
+
@node.clear_deferred
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def process_message(message)
|
|
91
|
+
case message
|
|
92
|
+
when MessageAck
|
|
93
|
+
process_ack message
|
|
94
|
+
when MessageNotAck
|
|
95
|
+
process_not_ack message
|
|
96
|
+
when Version
|
|
97
|
+
process_version message
|
|
98
|
+
when RSMP::Watchdog
|
|
99
|
+
process_watchdog message
|
|
100
|
+
else
|
|
101
|
+
dont_acknowledge message, 'Received', "unknown message (#{message.type})"
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def will_not_handle(message)
|
|
106
|
+
reason ||= "since we're a #{self.class.name.downcase}"
|
|
107
|
+
log "Ignoring #{message.type}, #{reason}", message: message, level: :warning
|
|
108
|
+
dont_acknowledge message, nil, reason
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def expect_version_message(message)
|
|
112
|
+
return if message.is_a?(Version) || message.is_a?(MessageAck) || message.is_a?(MessageNotAck)
|
|
113
|
+
|
|
114
|
+
raise HandshakeError, 'Version must be received first'
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
module RSMP
|
|
2
|
+
class Proxy
|
|
3
|
+
module Modules
|
|
4
|
+
# Message sending functionality
|
|
5
|
+
# Handles sending messages, validation, and buffering
|
|
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
|
+
def send_message(message, reason = nil, validate: true, force: false)
|
|
15
|
+
raise NotReady if !force && !connected?
|
|
16
|
+
raise IOError unless @protocol
|
|
17
|
+
|
|
18
|
+
message.direction = :out
|
|
19
|
+
message.generate_json
|
|
20
|
+
message.validate schemas unless validate == false
|
|
21
|
+
@protocol.write_lines message.json
|
|
22
|
+
expect_acknowledgement message
|
|
23
|
+
distribute message
|
|
24
|
+
log_send message, reason
|
|
25
|
+
rescue IOError
|
|
26
|
+
buffer_message message
|
|
27
|
+
rescue SchemaError, RSMP::Schema::Error => e
|
|
28
|
+
handle_send_schema_error(message, e)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def buffer_message(message)
|
|
32
|
+
# TODO
|
|
33
|
+
# log "Cannot send #{message.type} because the connection is closed.", message: message, level: :error
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def log_send(message, reason = nil)
|
|
37
|
+
str = if reason
|
|
38
|
+
"Sent #{message.type} #{reason}"
|
|
39
|
+
else
|
|
40
|
+
"Sent #{message.type}"
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
if message.type == 'MessageNotAck'
|
|
44
|
+
log str, message: message, level: :warning
|
|
45
|
+
else
|
|
46
|
+
log str, message: message, level: :log
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def send_and_optionally_collect(message, options)
|
|
51
|
+
collect_options = options[:collect] || options[:collect!]
|
|
52
|
+
if collect_options
|
|
53
|
+
task = @task.async do |task|
|
|
54
|
+
task.annotate 'send_and_optionally_collect'
|
|
55
|
+
collector = yield collect_options # call block to create collector
|
|
56
|
+
collector.collect
|
|
57
|
+
collector.ok! if options[:collect!] # raise any errors if the bang version was specified
|
|
58
|
+
collector
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
send_message message, validate: options[:validate]
|
|
62
|
+
{ sent: message, collector: task.wait }
|
|
63
|
+
else
|
|
64
|
+
send_message message, validate: options[:validate]
|
|
65
|
+
{ sent: message }
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def apply_nts_message_attributes(message)
|
|
70
|
+
message.attributes['ntsOId'] = main && main.ntsoid ? main.ntsoid : ''
|
|
71
|
+
message.attributes['xNId'] = main && main.xnid ? main.xnid : ''
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module RSMP
|
|
2
|
+
class Proxy
|
|
3
|
+
module Modules
|
|
4
|
+
# State management helpers
|
|
5
|
+
# Utility methods for waiting on state changes
|
|
6
|
+
module State
|
|
7
|
+
def wait_for_state(state, timeout:)
|
|
8
|
+
states = [state].flatten
|
|
9
|
+
return true if states.include?(@state)
|
|
10
|
+
|
|
11
|
+
wait_for_condition(@state_condition, timeout: timeout) do
|
|
12
|
+
states.include?(@state)
|
|
13
|
+
end
|
|
14
|
+
true
|
|
15
|
+
rescue RSMP::TimeoutError
|
|
16
|
+
raise RSMP::TimeoutError, "Did not reach state #{state} within #{timeout}s"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def handshake_complete
|
|
20
|
+
self.state = :ready
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
module RSMP
|
|
2
|
+
class Proxy
|
|
3
|
+
module Modules
|
|
4
|
+
# Reader and timer task management
|
|
5
|
+
# Handles async tasks for reading from socket and running periodic timers
|
|
6
|
+
module Tasks
|
|
7
|
+
# run an async task that reads from @socket
|
|
8
|
+
def start_reader
|
|
9
|
+
@reader = @task.async do |task|
|
|
10
|
+
task.annotate 'reader'
|
|
11
|
+
run_reader
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def run_reader
|
|
16
|
+
@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
|
|
20
|
+
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: :warning
|
|
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
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def read_line
|
|
37
|
+
json = @protocol.read_line
|
|
38
|
+
beginning = Time.now
|
|
39
|
+
message = process_packet json
|
|
40
|
+
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
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def start_timer
|
|
59
|
+
return if @timer
|
|
60
|
+
|
|
61
|
+
name = 'timer'
|
|
62
|
+
interval = @site_settings['intervals']['timer'] || 1
|
|
63
|
+
log "Starting #{name} with interval #{interval} seconds", level: :debug
|
|
64
|
+
@latest_watchdog_received = Clock.now
|
|
65
|
+
@timer = @task.async do |task|
|
|
66
|
+
task.annotate 'timer'
|
|
67
|
+
run_timer task, interval
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def run_timer(task, interval)
|
|
72
|
+
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
|
|
91
|
+
next_time += interval
|
|
92
|
+
duration = next_time - Time.now.to_f
|
|
93
|
+
task.sleep duration
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def timer(now)
|
|
98
|
+
watchdog_send_timer now
|
|
99
|
+
check_ack_timeout now
|
|
100
|
+
check_watchdog_timeout now
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
module RSMP
|
|
2
|
+
class Proxy
|
|
3
|
+
module Modules
|
|
4
|
+
# Version negotiation and handling
|
|
5
|
+
# Manages RSMP version handshake between sites and supervisors
|
|
6
|
+
module Versions
|
|
7
|
+
def core_versions
|
|
8
|
+
version = @site_settings['core_version']
|
|
9
|
+
if version == 'latest'
|
|
10
|
+
[RSMP::Schema.latest_core_version]
|
|
11
|
+
elsif version
|
|
12
|
+
[version]
|
|
13
|
+
else
|
|
14
|
+
RSMP::Schema.core_versions
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def check_core_version(message)
|
|
19
|
+
versions = core_versions
|
|
20
|
+
# find versions that both we and the client support
|
|
21
|
+
candidates = message.versions & versions
|
|
22
|
+
if candidates.any?
|
|
23
|
+
@core_version = candidates.max_by { |v| Gem::Version.new(v) } # pick latest version
|
|
24
|
+
else
|
|
25
|
+
reason = "RSMP versions [#{message.versions.join(', ')}] requested, " \
|
|
26
|
+
"but only [#{versions.join(', ')}] supported."
|
|
27
|
+
dont_acknowledge message, 'Version message rejected', reason, force: true
|
|
28
|
+
raise HandshakeError, reason
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def process_version(message); end
|
|
33
|
+
|
|
34
|
+
def extraneous_version(message)
|
|
35
|
+
dont_acknowledge message, 'Received', 'extraneous Version message'
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def send_version(site_id, core_versions)
|
|
39
|
+
versions = if core_versions == 'latest'
|
|
40
|
+
[RSMP::Schema.latest_core_version]
|
|
41
|
+
elsif core_versions == 'all'
|
|
42
|
+
RSMP::Schema.core_versions
|
|
43
|
+
else
|
|
44
|
+
[core_versions].flatten
|
|
45
|
+
end
|
|
46
|
+
versions_array = versions.map { |v| { 'vers' => v } }
|
|
47
|
+
|
|
48
|
+
site_id_array = [site_id].flatten.map { |id| { 'sId' => id } }
|
|
49
|
+
|
|
50
|
+
version_response = Version.new({
|
|
51
|
+
'RSMP' => versions_array,
|
|
52
|
+
'siteId' => site_id_array,
|
|
53
|
+
'SXL' => sxl_version.to_s
|
|
54
|
+
})
|
|
55
|
+
send_message version_response
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def version_acknowledged; end
|
|
59
|
+
|
|
60
|
+
# Use Gem class to check version requirement
|
|
61
|
+
# Requirement must be a string like '1.1', '>=1.0.3' or '<2.1.4',
|
|
62
|
+
# or list of strings, like ['<=1.4','<1.5']
|
|
63
|
+
def self.version_meets_requirement?(version, requirement)
|
|
64
|
+
Gem::Requirement.new(requirement).satisfied_by?(Gem::Version.new(version))
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
module RSMP
|
|
2
|
+
class Proxy
|
|
3
|
+
module Modules
|
|
4
|
+
# Watchdog functionality for monitoring connection health
|
|
5
|
+
# Handles sending and receiving watchdog messages
|
|
6
|
+
module Watchdogs
|
|
7
|
+
def start_watchdog
|
|
8
|
+
log "Starting watchdog with interval #{@site_settings['intervals']['watchdog']} seconds", level: :debug
|
|
9
|
+
@watchdog_started = true
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def stop_watchdog
|
|
13
|
+
log 'Stopping watchdog', level: :debug
|
|
14
|
+
@watchdog_started = false
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def with_watchdog_disabled
|
|
18
|
+
was = @watchdog_started
|
|
19
|
+
stop_watchdog if was
|
|
20
|
+
yield
|
|
21
|
+
ensure
|
|
22
|
+
start_watchdog if was
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def watchdog_send_timer(now)
|
|
26
|
+
return unless @watchdog_started
|
|
27
|
+
return if @site_settings['intervals']['watchdog'] == :never
|
|
28
|
+
|
|
29
|
+
if @latest_watchdog_send_at.nil?
|
|
30
|
+
send_watchdog now
|
|
31
|
+
else
|
|
32
|
+
# we add half the timer interval to pick the timer
|
|
33
|
+
# event closes to the wanted wathcdog interval
|
|
34
|
+
diff = now - @latest_watchdog_send_at
|
|
35
|
+
if (diff + (0.5 * @site_settings['intervals']['timer'])) >= @site_settings['intervals']['watchdog']
|
|
36
|
+
send_watchdog now
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def send_watchdog(now = Clock.now)
|
|
42
|
+
message = RSMP::Watchdog.new({ 'wTs' => clock.to_s })
|
|
43
|
+
send_message message
|
|
44
|
+
@latest_watchdog_send_at = now
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def check_watchdog_timeout(now)
|
|
48
|
+
timeout = @site_settings['timeouts']['watchdog']
|
|
49
|
+
latest = @latest_watchdog_received + timeout
|
|
50
|
+
left = latest - now
|
|
51
|
+
return unless left.negative?
|
|
52
|
+
|
|
53
|
+
str = "No Watchdog received within #{timeout} seconds"
|
|
54
|
+
log str, level: :warning
|
|
55
|
+
distribute MissingWatchdog.new(str)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def process_watchdog(message)
|
|
59
|
+
log "Received #{message.type}", message: message, level: :log
|
|
60
|
+
@latest_watchdog_received = Clock.now
|
|
61
|
+
acknowledge message
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|