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,195 @@
|
|
|
1
|
+
# RSMP site
|
|
2
|
+
# The site initializes the connection to the supervisor.
|
|
3
|
+
# Connections to supervisors are handles via supervisor proxies.
|
|
4
|
+
|
|
5
|
+
module RSMP
|
|
6
|
+
class Site < Node
|
|
7
|
+
include Components
|
|
8
|
+
|
|
9
|
+
attr_reader :core_version, :site_settings, :logger, :proxies
|
|
10
|
+
|
|
11
|
+
def initialize(options = {})
|
|
12
|
+
super
|
|
13
|
+
initialize_components
|
|
14
|
+
handle_site_settings options
|
|
15
|
+
@proxies = []
|
|
16
|
+
@sleep_condition = Async::Notification.new
|
|
17
|
+
@proxies_condition = Async::Notification.new
|
|
18
|
+
build_proxies
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def sxl_version
|
|
22
|
+
@site_settings['sxl_version']
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def site_id
|
|
26
|
+
@site_settings['site_id']
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def default_site_settings
|
|
30
|
+
{
|
|
31
|
+
'site_id' => 'RN+SI0001',
|
|
32
|
+
'supervisors' => [
|
|
33
|
+
{ 'ip' => '127.0.0.1', 'port' => 12_111 }
|
|
34
|
+
],
|
|
35
|
+
'sxl' => 'tlc',
|
|
36
|
+
'sxl_version' => RSMP::Schema.latest_version(:tlc),
|
|
37
|
+
'intervals' => {
|
|
38
|
+
'timer' => 0.1,
|
|
39
|
+
'watchdog' => 1,
|
|
40
|
+
'reconnect' => 0.1
|
|
41
|
+
},
|
|
42
|
+
'timeouts' => {
|
|
43
|
+
'watchdog' => 2,
|
|
44
|
+
'acknowledgement' => 2
|
|
45
|
+
},
|
|
46
|
+
'send_after_connect' => true,
|
|
47
|
+
'components' => {
|
|
48
|
+
'main' => {
|
|
49
|
+
'C1' => {}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def handle_site_settings(options = {})
|
|
56
|
+
defaults = default_site_settings
|
|
57
|
+
defaults['components']['main'] = options[:site_settings]['components']['main'] if options.dig(
|
|
58
|
+
:site_settings, 'components', 'main'
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
@site_settings = defaults.deep_merge options[:site_settings]
|
|
62
|
+
|
|
63
|
+
check_sxl_version
|
|
64
|
+
check_core_versions
|
|
65
|
+
setup_components @site_settings['components']
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def check_sxl_version
|
|
69
|
+
sxl = @site_settings['sxl']
|
|
70
|
+
version = @site_settings['sxl_version'].to_s
|
|
71
|
+
RSMP::Schema.find_schema! sxl, version, lenient: true
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def check_core_versions
|
|
75
|
+
version = @site_settings['core_version']
|
|
76
|
+
return unless version
|
|
77
|
+
|
|
78
|
+
return if RSMP::Schema.core_versions.include? version
|
|
79
|
+
|
|
80
|
+
error_str = "Unknown core version: #{version}"
|
|
81
|
+
raise RSMP::ConfigurationError, error_str
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def site_type_name
|
|
85
|
+
'site'
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def log_site_starting
|
|
89
|
+
log "Starting #{site_type_name} #{@site_settings['site_id']}", level: :info, timestamp: @clock.now
|
|
90
|
+
sxl = "Using #{@site_settings['sxl']} sxl #{@site_settings['sxl_version']}"
|
|
91
|
+
version = @site_settings['core_version']
|
|
92
|
+
core = if version
|
|
93
|
+
"accepting only core version #{version}"
|
|
94
|
+
else
|
|
95
|
+
"accepting all core versions [#{RSMP::Schema.core_versions.join(', ')}]"
|
|
96
|
+
end
|
|
97
|
+
log "#{sxl}, #{core}", level: :info, timestamp: @clock.now
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def run
|
|
101
|
+
log_site_starting
|
|
102
|
+
@proxies.each do |proxy|
|
|
103
|
+
proxy.start
|
|
104
|
+
proxy.wait
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def build_proxies
|
|
109
|
+
@site_settings['supervisors'].each do |supervisor_settings|
|
|
110
|
+
@proxies << SupervisorProxy.new({
|
|
111
|
+
site: self,
|
|
112
|
+
task: @task,
|
|
113
|
+
settings: @site_settings,
|
|
114
|
+
ip: supervisor_settings['ip'],
|
|
115
|
+
port: supervisor_settings['port'],
|
|
116
|
+
logger: @logger,
|
|
117
|
+
archive: @archive,
|
|
118
|
+
collect: @collect
|
|
119
|
+
})
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def aggregated_status_changed(component, options = {})
|
|
124
|
+
@proxies.each do |proxy|
|
|
125
|
+
proxy.send_aggregated_status component, options if proxy.ready?
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def alarm_acknowledged(alarm_state)
|
|
130
|
+
send_alarm AlarmAcknowledged.new(alarm_state.to_hash)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def alarm_suspended_or_resumed(alarm_state)
|
|
134
|
+
send_alarm AlarmSuspended.new(alarm_state.to_hash)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def alarm_activated_or_deactivated(alarm_state)
|
|
138
|
+
send_alarm AlarmIssue.new(alarm_state.to_hash)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def send_alarm(alarm)
|
|
142
|
+
@proxies.each do |proxy|
|
|
143
|
+
proxy.send_message alarm if proxy.ready?
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def connect_to_supervisor(_task, supervisor_settings)
|
|
148
|
+
proxy = build_proxy({
|
|
149
|
+
site: self,
|
|
150
|
+
task: @task,
|
|
151
|
+
settings: @site_settings,
|
|
152
|
+
ip: supervisor_settings['ip'],
|
|
153
|
+
port: supervisor_settings['port'],
|
|
154
|
+
logger: @logger,
|
|
155
|
+
archive: @archive,
|
|
156
|
+
collect: @collect
|
|
157
|
+
})
|
|
158
|
+
@proxies << proxy
|
|
159
|
+
proxy.start
|
|
160
|
+
@proxies_condition.signal
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# stop
|
|
164
|
+
def stop
|
|
165
|
+
log "Stopping site #{@site_settings['site_id']}", level: :info
|
|
166
|
+
super
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def wait_for_supervisor(ip, timeout)
|
|
170
|
+
supervisor = find_supervisor ip
|
|
171
|
+
return supervisor if supervisor
|
|
172
|
+
|
|
173
|
+
wait_for_condition(@proxy_condition, timeout: timeout) { find_supervisor ip }
|
|
174
|
+
rescue Async::TimeoutError
|
|
175
|
+
raise RSMP::TimeoutError, "Supervisor '#{ip}' did not connect within #{timeout}s"
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def find_supervisor(ip)
|
|
179
|
+
@proxies.each do |supervisor|
|
|
180
|
+
return supervisor if ip == :any || supervisor.ip == ip
|
|
181
|
+
end
|
|
182
|
+
nil
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def build_component(id:, type:, settings:)
|
|
186
|
+
settings ||= {}
|
|
187
|
+
if type == 'main'
|
|
188
|
+
Component.new id: id, node: self, grouped: true,
|
|
189
|
+
ntsoid: settings['ntsOId'], xnid: settings['xNId']
|
|
190
|
+
else
|
|
191
|
+
Component.new id: id, node: self, grouped: false
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
module RSMP
|
|
2
|
+
class Supervisor < Node
|
|
3
|
+
module Modules
|
|
4
|
+
# Handles supervisor configuration and site settings
|
|
5
|
+
module Configuration
|
|
6
|
+
def handle_supervisor_settings(supervisor_settings)
|
|
7
|
+
defaults = {
|
|
8
|
+
'port' => 12_111,
|
|
9
|
+
'ips' => 'all',
|
|
10
|
+
'guest' => {
|
|
11
|
+
'sxl' => 'tlc',
|
|
12
|
+
'intervals' => {
|
|
13
|
+
'timer' => 1,
|
|
14
|
+
'watchdog' => 1
|
|
15
|
+
},
|
|
16
|
+
'timeouts' => {
|
|
17
|
+
'watchdog' => 2,
|
|
18
|
+
'acknowledgement' => 2
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
# merge options into defaults
|
|
24
|
+
@supervisor_settings = defaults.deep_merge(supervisor_settings)
|
|
25
|
+
@core_version = @supervisor_settings['guest']['core_version']
|
|
26
|
+
check_site_sxl_types
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def check_site_sxl_types
|
|
30
|
+
sites = @supervisor_settings['sites'].clone || {}
|
|
31
|
+
sites['guest'] = @supervisor_settings['guest']
|
|
32
|
+
sites.each do |site_id, settings|
|
|
33
|
+
raise RSMP::ConfigurationError, "Configuration for site '#{site_id}' is empty" unless settings
|
|
34
|
+
|
|
35
|
+
sxl = settings['sxl']
|
|
36
|
+
raise RSMP::ConfigurationError, "Configuration error for site '#{site_id}': No SXL specified" unless sxl
|
|
37
|
+
|
|
38
|
+
RSMP::Schema.find_schemas! sxl if sxl
|
|
39
|
+
rescue RSMP::Schema::UnknownSchemaError => e
|
|
40
|
+
raise RSMP::ConfigurationError, "Configuration error for site '#{site_id}': #{e}"
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def site_id_to_site_setting(site_id)
|
|
45
|
+
return {} unless @supervisor_settings['sites']
|
|
46
|
+
|
|
47
|
+
@supervisor_settings['sites'].each_pair do |id, settings|
|
|
48
|
+
return settings if id == 'guest' || id == site_id
|
|
49
|
+
end
|
|
50
|
+
raise HandshakeError, "site id #{site_id} unknown"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def ip_to_site_settings(ip)
|
|
54
|
+
@supervisor_settings['sites'][ip] || @supervisor_settings['sites']['guest']
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
module RSMP
|
|
2
|
+
class Supervisor < Node
|
|
3
|
+
module Modules
|
|
4
|
+
# Handles incoming connections from sites
|
|
5
|
+
module Connection
|
|
6
|
+
def handle_connection(socket)
|
|
7
|
+
remote_port = socket.remote_address.ip_port
|
|
8
|
+
remote_hostname = socket.remote_address.ip_address
|
|
9
|
+
remote_ip = socket.remote_address.ip_address
|
|
10
|
+
|
|
11
|
+
info = { ip: remote_ip, port: remote_port, hostname: remote_hostname, now: Clock.now }
|
|
12
|
+
if accept? socket, info
|
|
13
|
+
accept_connection socket, info
|
|
14
|
+
else
|
|
15
|
+
reject_connection socket, info
|
|
16
|
+
end
|
|
17
|
+
rescue ConnectionError, HandshakeError => e
|
|
18
|
+
log "Rejected connection from #{remote_ip}:#{remote_port}, #{e}", level: :warning
|
|
19
|
+
distribute_error e
|
|
20
|
+
rescue StandardError => e
|
|
21
|
+
log "Connection: #{e}", exception: e, level: :error
|
|
22
|
+
distribute_error e, level: :internal
|
|
23
|
+
ensure
|
|
24
|
+
close socket, info
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def accept?(_socket, _info)
|
|
28
|
+
true
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def format_ip_and_port(info)
|
|
32
|
+
if @logger.settings['hide_ip_and_port']
|
|
33
|
+
'********'
|
|
34
|
+
else
|
|
35
|
+
"#{info[:ip]}:#{info[:port]}"
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def authorize_ip(ip)
|
|
40
|
+
return if @supervisor_settings['ips'] == 'all'
|
|
41
|
+
return if @supervisor_settings['ips'].include? ip
|
|
42
|
+
|
|
43
|
+
raise ConnectionError, 'guest ip not allowed'
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def check_max_sites
|
|
47
|
+
max = @supervisor_settings['max_sites']
|
|
48
|
+
return unless max
|
|
49
|
+
return unless @proxies.size >= max
|
|
50
|
+
|
|
51
|
+
raise ConnectionError, "maximum of #{max} sites already connected"
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def peek_version_message(protocol)
|
|
55
|
+
json = protocol.peek_line
|
|
56
|
+
attributes = Message.parse_attributes json
|
|
57
|
+
Message.build attributes, json
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def build_proxy_settings(socket, info)
|
|
61
|
+
stream = IO::Stream::Buffered.new(socket)
|
|
62
|
+
protocol = RSMP::Protocol.new stream
|
|
63
|
+
|
|
64
|
+
{
|
|
65
|
+
supervisor: self,
|
|
66
|
+
ip: info[:ip],
|
|
67
|
+
port: info[:port],
|
|
68
|
+
task: @task,
|
|
69
|
+
collect: @collect,
|
|
70
|
+
socket: socket,
|
|
71
|
+
stream: stream,
|
|
72
|
+
protocol: protocol,
|
|
73
|
+
info: info,
|
|
74
|
+
logger: @logger,
|
|
75
|
+
archive: @archive
|
|
76
|
+
}
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def retrieve_site_id(protocol)
|
|
80
|
+
version_message = peek_version_message protocol
|
|
81
|
+
version_message.attribute('siteId').first['sId']
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def setup_proxy(proxy, settings, id)
|
|
85
|
+
if proxy
|
|
86
|
+
raise ConnectionError, "Site #{id} alredy connected from port #{proxy.port}" if proxy.connected?
|
|
87
|
+
|
|
88
|
+
proxy.revive settings
|
|
89
|
+
else
|
|
90
|
+
check_max_sites
|
|
91
|
+
proxy = build_proxy settings.merge(site_id: id)
|
|
92
|
+
@proxies.push proxy
|
|
93
|
+
end
|
|
94
|
+
proxy
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def validate_and_start_proxy(proxy, protocol)
|
|
98
|
+
proxy.setup_site_settings
|
|
99
|
+
proxy.check_core_version peek_version_message(protocol)
|
|
100
|
+
log "Validating using core version #{proxy.core_version}", level: :debug
|
|
101
|
+
proxy.start
|
|
102
|
+
proxy.wait
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def accept_connection(socket, info)
|
|
106
|
+
log "Site connected from #{format_ip_and_port(info)}",
|
|
107
|
+
ip: info[:ip],
|
|
108
|
+
port: info[:port],
|
|
109
|
+
level: :info,
|
|
110
|
+
timestamp: Clock.now
|
|
111
|
+
|
|
112
|
+
authorize_ip info[:ip]
|
|
113
|
+
|
|
114
|
+
settings = build_proxy_settings(socket, info)
|
|
115
|
+
id = retrieve_site_id(settings[:protocol])
|
|
116
|
+
proxy = setup_proxy(find_site(id), settings, id)
|
|
117
|
+
|
|
118
|
+
validate_and_start_proxy(proxy, settings[:protocol])
|
|
119
|
+
ensure
|
|
120
|
+
site_ids_changed
|
|
121
|
+
stop if @supervisor_settings['one_shot']
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def reject_connection(_socket, info)
|
|
125
|
+
log 'Site rejected', ip: info[:ip], level: :info
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def close(socket, info)
|
|
129
|
+
if info
|
|
130
|
+
log "Connection to #{format_ip_and_port(info)} closed", ip: info[:ip], level: :info, timestamp: Clock.now
|
|
131
|
+
else
|
|
132
|
+
log 'Connection closed', level: :info, timestamp: Clock.now
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
socket.close
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
module RSMP
|
|
2
|
+
class Supervisor < Node
|
|
3
|
+
module Modules
|
|
4
|
+
# Manages connected sites and site discovery
|
|
5
|
+
module Sites
|
|
6
|
+
def site_connected?(site_id)
|
|
7
|
+
!find_site(site_id).nil?
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def find_site_from_ip_port(ip, port)
|
|
11
|
+
@proxies.each do |site|
|
|
12
|
+
return site if site.ip == ip && site.port == port
|
|
13
|
+
end
|
|
14
|
+
nil
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def find_site(site_id)
|
|
18
|
+
@proxies.each do |site|
|
|
19
|
+
return site if site_id == :any || site.site_id == site_id
|
|
20
|
+
end
|
|
21
|
+
nil
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def wait_for_site(site_id, timeout:)
|
|
25
|
+
site = find_site site_id
|
|
26
|
+
return site if site
|
|
27
|
+
|
|
28
|
+
wait_for_condition(@site_id_condition, timeout: timeout) do
|
|
29
|
+
find_site site_id
|
|
30
|
+
end
|
|
31
|
+
rescue Async::TimeoutError
|
|
32
|
+
str = if site_id == :any
|
|
33
|
+
'No site connected'
|
|
34
|
+
else
|
|
35
|
+
"Site '#{site_id}' did not connect"
|
|
36
|
+
end
|
|
37
|
+
raise RSMP::TimeoutError, "#{str} within #{timeout}s"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def wait_for_site_disconnect(site_id, timeout:)
|
|
41
|
+
wait_for_condition(@site_id_condition, timeout: timeout) { true unless find_site site_id }
|
|
42
|
+
rescue Async::TimeoutError
|
|
43
|
+
raise RSMP::TimeoutError, "Site '#{site_id}' did not disconnect within #{timeout}s"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def check_site_id(site_id)
|
|
47
|
+
# check_site_already_connected site_id
|
|
48
|
+
site_id_to_site_setting site_id
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def check_site_already_connected(site_id)
|
|
52
|
+
site = find_site(site_id)
|
|
53
|
+
raise HandshakeError, "Site '#{site_id}' already connected" if !site.nil? && site != self
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def site_ids_changed
|
|
57
|
+
@site_id_condition.signal
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def aggregated_status_changed(site_proxy, component); end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# RSMP supervisor (server)
|
|
2
|
+
# The supervisor waits for sites to connect.
|
|
3
|
+
# Connections to sites are handles via site proxies.
|
|
4
|
+
|
|
5
|
+
module RSMP
|
|
6
|
+
class Supervisor < Node
|
|
7
|
+
include Modules::Configuration
|
|
8
|
+
include Modules::Connection
|
|
9
|
+
include Modules::Sites
|
|
10
|
+
|
|
11
|
+
attr_reader :core_version, :supervisor_settings, :proxies, :logger, :ready_condition
|
|
12
|
+
|
|
13
|
+
attr_accessor :site_id_condition
|
|
14
|
+
|
|
15
|
+
def initialize(options = {})
|
|
16
|
+
handle_supervisor_settings(options[:supervisor_settings] || {})
|
|
17
|
+
super
|
|
18
|
+
@proxies = []
|
|
19
|
+
@ready_condition = Async::Notification.new
|
|
20
|
+
@site_id_condition = Async::Notification.new
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def site_id
|
|
24
|
+
@supervisor_settings['site_id']
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# listen for connections
|
|
28
|
+
def run
|
|
29
|
+
log "Starting supervisor on port #{@supervisor_settings['port']}",
|
|
30
|
+
level: :info,
|
|
31
|
+
timestamp: @clock.now
|
|
32
|
+
|
|
33
|
+
@endpoint = IO::Endpoint.tcp('0.0.0.0', @supervisor_settings['port'])
|
|
34
|
+
@accept_task = Async::Task.current.async do |task|
|
|
35
|
+
task.annotate 'supervisor accept loop'
|
|
36
|
+
@endpoint.accept do |socket| # creates fibers
|
|
37
|
+
handle_connection(socket)
|
|
38
|
+
rescue StandardError => e
|
|
39
|
+
distribute_error e, level: :internal
|
|
40
|
+
end
|
|
41
|
+
rescue Async::Stop
|
|
42
|
+
# Expected during shutdown - no action needed
|
|
43
|
+
rescue StandardError => e
|
|
44
|
+
distribute_error e, level: :internal
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
@ready_condition.signal
|
|
48
|
+
@accept_task.wait
|
|
49
|
+
rescue StandardError => e
|
|
50
|
+
distribute_error e, level: :internal
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# stop
|
|
54
|
+
def stop
|
|
55
|
+
log "Stopping supervisor #{@supervisor_settings['site_id']}", level: :info
|
|
56
|
+
|
|
57
|
+
@accept_task&.stop
|
|
58
|
+
@accept_task = nil
|
|
59
|
+
|
|
60
|
+
@endpoint = nil
|
|
61
|
+
super
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def build_proxy(settings)
|
|
65
|
+
SiteProxy.new settings
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def self.build_id_from_ip_port(ip, port)
|
|
69
|
+
Digest::MD5.hexdigest("#{ip}:#{port}")[0..8]
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -13,7 +13,7 @@ module RSMP
|
|
|
13
13
|
# run() will be called inside the task to perform actual long-running work
|
|
14
14
|
def start
|
|
15
15
|
return if @task
|
|
16
|
-
|
|
16
|
+
|
|
17
17
|
# Use current task context if available, otherwise create new reactor
|
|
18
18
|
if Async::Task.current?
|
|
19
19
|
Async::Task.current.async do |task|
|
|
@@ -37,12 +37,12 @@ module RSMP
|
|
|
37
37
|
|
|
38
38
|
# initiate restart by raising a Restart exception
|
|
39
39
|
def restart
|
|
40
|
-
raise Restart
|
|
40
|
+
raise Restart, "restart initiated by #{self.class.name}:#{object_id}"
|
|
41
41
|
end
|
|
42
42
|
|
|
43
43
|
# get the status of our task, or nil of no task
|
|
44
44
|
def task_status
|
|
45
|
-
@task
|
|
45
|
+
@task&.status
|
|
46
46
|
end
|
|
47
47
|
|
|
48
48
|
# perform any long-running work
|
|
@@ -55,7 +55,7 @@ module RSMP
|
|
|
55
55
|
|
|
56
56
|
# wait for our task to complete
|
|
57
57
|
def wait
|
|
58
|
-
@task
|
|
58
|
+
@task&.wait
|
|
59
59
|
end
|
|
60
60
|
|
|
61
61
|
# stop our task
|
|
@@ -64,8 +64,7 @@ module RSMP
|
|
|
64
64
|
stop_task if @task
|
|
65
65
|
end
|
|
66
66
|
|
|
67
|
-
def stop_subtasks
|
|
68
|
-
end
|
|
67
|
+
def stop_subtasks; end
|
|
69
68
|
|
|
70
69
|
# stop our task and any subtask
|
|
71
70
|
def stop_task
|
|
@@ -75,22 +74,21 @@ module RSMP
|
|
|
75
74
|
|
|
76
75
|
# wait for an async condition to signal, then yield to block
|
|
77
76
|
# if block returns true we're done. otherwise, wait again
|
|
78
|
-
def wait_for_condition
|
|
79
|
-
unless task
|
|
80
|
-
|
|
81
|
-
end
|
|
77
|
+
def wait_for_condition(condition, timeout:, task: Async::Task.current, &block)
|
|
78
|
+
raise "Can't wait without a task" unless task
|
|
79
|
+
|
|
82
80
|
task.with_timeout(timeout) do
|
|
83
81
|
while task.running?
|
|
84
82
|
value = condition.wait
|
|
85
83
|
return value unless block
|
|
84
|
+
|
|
86
85
|
result = yield value
|
|
87
86
|
return result if result
|
|
88
87
|
end
|
|
89
|
-
raise
|
|
88
|
+
raise "Can't wait for condition because task #{task.object_id} #{task.annotation} is not running"
|
|
90
89
|
end
|
|
91
90
|
rescue Async::TimeoutError
|
|
92
|
-
raise RSMP::TimeoutError
|
|
91
|
+
raise RSMP::TimeoutError
|
|
93
92
|
end
|
|
94
|
-
|
|
95
93
|
end
|
|
96
|
-
end
|
|
94
|
+
end
|