rsmp 0.8.4 → 0.9.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/rspec.yaml +21 -0
- data/.ruby-version +1 -1
- data/Gemfile.lock +51 -67
- data/README.md +2 -12
- data/bin/console +1 -1
- data/cucumber.yml +1 -0
- data/documentation/classes_and_modules.md +4 -4
- data/documentation/collecting_message.md +2 -2
- data/documentation/tasks.md +149 -0
- data/lib/rsmp/archive.rb +3 -3
- data/lib/rsmp/cli.rb +32 -4
- data/lib/rsmp/collect/aggregated_status_collector.rb +1 -1
- data/lib/rsmp/collect/collector.rb +13 -6
- data/lib/rsmp/collect/command_response_collector.rb +1 -1
- data/lib/rsmp/collect/state_collector.rb +1 -1
- data/lib/rsmp/collect/status_collector.rb +2 -1
- data/lib/rsmp/components.rb +3 -3
- data/lib/rsmp/convert/export/json_schema.rb +4 -4
- data/lib/rsmp/convert/import/yaml.rb +1 -1
- data/lib/rsmp/deep_merge.rb +1 -0
- data/lib/rsmp/error.rb +0 -3
- data/lib/rsmp/inspect.rb +1 -1
- data/lib/rsmp/logger.rb +5 -5
- data/lib/rsmp/logging.rb +1 -1
- data/lib/rsmp/message.rb +1 -1
- data/lib/rsmp/node.rb +10 -45
- data/lib/rsmp/proxy.rb +184 -134
- data/lib/rsmp/rsmp.rb +1 -1
- data/lib/rsmp/site.rb +23 -60
- data/lib/rsmp/site_proxy.rb +33 -37
- data/lib/rsmp/supervisor.rb +25 -21
- data/lib/rsmp/supervisor_proxy.rb +58 -29
- data/lib/rsmp/task.rb +84 -0
- data/lib/rsmp/tlc/signal_group.rb +5 -3
- data/lib/rsmp/tlc/signal_plan.rb +2 -2
- data/lib/rsmp/tlc/traffic_controller.rb +97 -29
- data/lib/rsmp/tlc/traffic_controller_site.rb +43 -36
- data/lib/rsmp/version.rb +1 -1
- data/lib/rsmp.rb +1 -1
- data/rsmp.gemspec +7 -7
- metadata +21 -20
- data/lib/rsmp/site_proxy_wait.rb +0 -0
- data/lib/rsmp/wait.rb +0 -16
- data/test.rb +0 -27
data/lib/rsmp/proxy.rb
CHANGED
@@ -1,32 +1,104 @@
|
|
1
|
-
#
|
1
|
+
# A connection to a remote site or supervisor.
|
2
|
+
# Uses the Task module to handle asyncronous work, but adds
|
3
|
+
# the concept of a connection that can be connected or disconnected.
|
2
4
|
|
3
5
|
require 'rubygems'
|
4
6
|
|
5
|
-
module RSMP
|
7
|
+
module RSMP
|
6
8
|
class Proxy
|
7
9
|
WRAPPING_DELIMITER = "\f"
|
8
10
|
|
9
11
|
include Logging
|
10
|
-
include Wait
|
11
12
|
include Notifier
|
12
13
|
include Inspect
|
14
|
+
include Task
|
13
15
|
|
14
|
-
attr_reader :state, :archive, :connection_info, :sxl, :
|
16
|
+
attr_reader :state, :archive, :connection_info, :sxl, :collector, :ip, :port
|
15
17
|
|
16
18
|
def initialize options
|
17
19
|
initialize_logging options
|
18
20
|
initialize_distributor
|
21
|
+
initialize_task
|
19
22
|
setup options
|
20
23
|
clear
|
24
|
+
@state = :disconnected
|
21
25
|
end
|
22
26
|
|
27
|
+
def disconnect
|
28
|
+
end
|
29
|
+
|
30
|
+
# wait for the reader task to complete,
|
31
|
+
# which is not expected to happen before the connection is closed
|
32
|
+
def wait_for_reader
|
33
|
+
@reader.wait if @reader
|
34
|
+
end
|
35
|
+
|
36
|
+
# close connection, but keep our main task running so we can reconnect
|
37
|
+
def close
|
38
|
+
log "Closing connection", level: :warning
|
39
|
+
close_stream
|
40
|
+
close_socket
|
41
|
+
set_state :disconnected
|
42
|
+
notify_error DisconnectError.new("Connection was closed")
|
43
|
+
stop_timer
|
44
|
+
end
|
45
|
+
|
46
|
+
def stop_subtasks
|
47
|
+
stop_timer
|
48
|
+
stop_reader
|
49
|
+
clear
|
50
|
+
super
|
51
|
+
end
|
52
|
+
|
53
|
+
def stop_timer
|
54
|
+
return unless @timer
|
55
|
+
@timer.stop
|
56
|
+
@timer = nil
|
57
|
+
end
|
58
|
+
|
59
|
+
def stop_reader
|
60
|
+
return unless @reader
|
61
|
+
@reader.stop
|
62
|
+
@reader = nil
|
63
|
+
end
|
64
|
+
|
65
|
+
def close_stream
|
66
|
+
return unless @stream
|
67
|
+
@stream.close
|
68
|
+
@stream = nil
|
69
|
+
end
|
70
|
+
|
71
|
+
def close_socket
|
72
|
+
return unless @socket
|
73
|
+
@socket.close
|
74
|
+
@socket = nil
|
75
|
+
end
|
76
|
+
|
77
|
+
def stop_task
|
78
|
+
close
|
79
|
+
super
|
80
|
+
end
|
81
|
+
|
82
|
+
# change our state
|
83
|
+
def set_state state
|
84
|
+
return if state == @state
|
85
|
+
@state = state
|
86
|
+
state_changed
|
87
|
+
end
|
88
|
+
|
89
|
+
# the state changed
|
90
|
+
# override to to things like notifications
|
91
|
+
def state_changed
|
92
|
+
@state_condition.signal @state
|
93
|
+
end
|
94
|
+
|
95
|
+
# revive after a reconnect
|
23
96
|
def revive options
|
24
97
|
setup options
|
25
98
|
end
|
26
99
|
|
27
100
|
def setup options
|
28
101
|
@settings = options[:settings]
|
29
|
-
@task = options[:task]
|
30
102
|
@socket = options[:socket]
|
31
103
|
@stream = options[:stream]
|
32
104
|
@protocol = options[:protocol]
|
@@ -35,7 +107,6 @@ module RSMP
|
|
35
107
|
@connection_info = options[:info]
|
36
108
|
@sxl = nil
|
37
109
|
@site_settings = nil # can't pick until we know the site id
|
38
|
-
@state = :stopped
|
39
110
|
if options[:collect]
|
40
111
|
@collector = RSMP::Collector.new self, options[:collect]
|
41
112
|
@collector.start
|
@@ -52,35 +123,16 @@ module RSMP
|
|
52
123
|
node.clock
|
53
124
|
end
|
54
125
|
|
55
|
-
def run
|
56
|
-
start
|
57
|
-
@reader.wait if @reader
|
58
|
-
ensure
|
59
|
-
stop unless [:stopped, :stopping].include? @state
|
60
|
-
end
|
61
|
-
|
62
126
|
def ready?
|
63
127
|
@state == :ready
|
64
128
|
end
|
65
129
|
|
66
130
|
def connected?
|
67
|
-
@state == :
|
131
|
+
@state == :connected || @state == :ready
|
68
132
|
end
|
69
133
|
|
70
|
-
|
71
|
-
|
72
|
-
set_state :starting
|
73
|
-
end
|
74
|
-
|
75
|
-
def stop
|
76
|
-
return if @state == :stopped
|
77
|
-
set_state :stopping
|
78
|
-
stop_tasks
|
79
|
-
notify_error DisconnectError.new("Connection was closed")
|
80
|
-
ensure
|
81
|
-
close_socket
|
82
|
-
clear
|
83
|
-
set_state :stopped
|
134
|
+
def disconnected?
|
135
|
+
@state == :disconnected
|
84
136
|
end
|
85
137
|
|
86
138
|
def clear
|
@@ -97,56 +149,57 @@ module RSMP
|
|
97
149
|
@acknowledgement_condition = Async::Notification.new
|
98
150
|
end
|
99
151
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
if @socket
|
107
|
-
@socket.close
|
108
|
-
@socket = nil
|
152
|
+
# run an async task that reads from @socket
|
153
|
+
def start_reader
|
154
|
+
@reader = @task.async do |task|
|
155
|
+
task.annotate "reader"
|
156
|
+
run_reader
|
109
157
|
end
|
110
158
|
end
|
111
159
|
|
112
|
-
def
|
113
|
-
@
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
160
|
+
def run_reader
|
161
|
+
@stream ||= Async::IO::Stream.new(@socket)
|
162
|
+
@protocol ||= Async::IO::Protocol::Line.new(@stream,WRAPPING_DELIMITER) # rsmp messages are json terminated with a form-feed
|
163
|
+
loop do
|
164
|
+
read_line
|
165
|
+
end
|
166
|
+
rescue Restart
|
167
|
+
log "Closing connection", level: :warning
|
168
|
+
raise
|
169
|
+
rescue Async::Wrapper::Cancelled
|
170
|
+
# ignore exceptions raised when a wait is aborted because a task is stopped
|
171
|
+
rescue EOFError, Async::Stop
|
172
|
+
log "Connection closed", level: :warning
|
173
|
+
rescue IOError => e
|
174
|
+
log "IOError: #{e}", level: :warning
|
175
|
+
rescue Errno::ECONNRESET
|
176
|
+
log "Connection reset by peer", level: :warning
|
177
|
+
rescue Errno::EPIPE
|
178
|
+
log "Broken pipe", level: :warning
|
179
|
+
rescue StandardError => e
|
180
|
+
notify_error e, level: :internal
|
181
|
+
end
|
182
|
+
|
183
|
+
def read_line
|
184
|
+
json = @protocol.read_line
|
185
|
+
beginning = Time.now
|
186
|
+
message = process_packet json
|
187
|
+
duration = Time.now - beginning
|
188
|
+
ms = (duration*1000).round(4)
|
189
|
+
if duration > 0
|
190
|
+
per_second = (1.0 / duration).round
|
191
|
+
else
|
192
|
+
per_second = Float::INFINITY
|
193
|
+
end
|
194
|
+
if message
|
195
|
+
type = message.type
|
196
|
+
m_id = Logger.shorten_message_id(message.m_id)
|
197
|
+
else
|
198
|
+
type = 'Unknown'
|
199
|
+
m_id = nil
|
149
200
|
end
|
201
|
+
str = [type,m_id,"processed in #{ms}ms, #{per_second}req/s"].compact.join(' ')
|
202
|
+
log str, level: :statistics
|
150
203
|
end
|
151
204
|
|
152
205
|
def notify_error e, options={}
|
@@ -160,36 +213,40 @@ module RSMP
|
|
160
213
|
end
|
161
214
|
|
162
215
|
def start_timer
|
216
|
+
return if @timer
|
163
217
|
name = "timer"
|
164
218
|
interval = @site_settings['intervals']['timer'] || 1
|
165
219
|
log "Starting #{name} with interval #{interval} seconds", level: :debug
|
166
220
|
@latest_watchdog_received = Clock.now
|
167
|
-
|
168
221
|
@timer = @task.async do |task|
|
169
222
|
task.annotate "timer"
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
223
|
+
run_timer task, interval
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
def run_timer task, interval
|
228
|
+
next_time = Time.now.to_f
|
229
|
+
loop do
|
230
|
+
begin
|
231
|
+
now = Clock.now
|
232
|
+
timer(now)
|
233
|
+
rescue RSMP::Schemer::Error => e
|
234
|
+
log "Timer: Schema error: #{e}", level: :warning
|
235
|
+
rescue EOFError => e
|
236
|
+
log "Timer: Connection closed: #{e}", level: :warning
|
237
|
+
rescue IOError => e
|
238
|
+
log "Timer: IOError", level: :warning
|
239
|
+
rescue Errno::ECONNRESET
|
240
|
+
log "Timer: Connection reset by peer", level: :warning
|
241
|
+
rescue Errno::EPIPE => e
|
242
|
+
log "Timer: Broken pipe", level: :warning
|
243
|
+
rescue StandardError => e
|
244
|
+
notify_error e, level: :internal
|
192
245
|
end
|
246
|
+
ensure
|
247
|
+
next_time += interval
|
248
|
+
duration = next_time - Time.now.to_f
|
249
|
+
task.sleep duration
|
193
250
|
end
|
194
251
|
end
|
195
252
|
|
@@ -200,7 +257,7 @@ module RSMP
|
|
200
257
|
end
|
201
258
|
|
202
259
|
def watchdog_send_timer now
|
203
|
-
return unless @watchdog_started
|
260
|
+
return unless @watchdog_started
|
204
261
|
return if @site_settings['intervals']['watchdog'] == :never
|
205
262
|
if @latest_watchdog_send_at == nil
|
206
263
|
send_watchdog now
|
@@ -226,9 +283,13 @@ module RSMP
|
|
226
283
|
@awaiting_acknowledgement.clone.each_pair do |m_id, message|
|
227
284
|
latest = message.timestamp + timeout
|
228
285
|
if now > latest
|
229
|
-
|
230
|
-
|
231
|
-
|
286
|
+
str = "No acknowledgements for #{message.type} #{message.m_id_short} within #{timeout} seconds"
|
287
|
+
log str, level: :error
|
288
|
+
begin
|
289
|
+
close
|
290
|
+
ensure
|
291
|
+
notify_error MissingAcknowledgment.new(str)
|
292
|
+
end
|
232
293
|
end
|
233
294
|
end
|
234
295
|
end
|
@@ -238,16 +299,16 @@ module RSMP
|
|
238
299
|
latest = @latest_watchdog_received + timeout
|
239
300
|
left = latest - now
|
240
301
|
if left < 0
|
241
|
-
|
242
|
-
|
302
|
+
str = "No Watchdog within #{timeout} seconds"
|
303
|
+
log str, level: :error
|
304
|
+
begin
|
305
|
+
close # this will stop the current task (ourself)
|
306
|
+
ensure
|
307
|
+
notify_error MissingWatchdog.new(str) # but ensure block will still be reached
|
308
|
+
end
|
243
309
|
end
|
244
310
|
end
|
245
311
|
|
246
|
-
def stop_tasks
|
247
|
-
@timer.stop if @timer
|
248
|
-
@reader.stop if @reader
|
249
|
-
end
|
250
|
-
|
251
312
|
def log str, options={}
|
252
313
|
super str, options.merge(ip: @ip, port: @port, site_id: @site_id)
|
253
314
|
end
|
@@ -355,7 +416,7 @@ module RSMP
|
|
355
416
|
str = "Rejected #{message.type},"
|
356
417
|
notify_error e.exception(str), message: message
|
357
418
|
dont_acknowledge message, str, reason
|
358
|
-
|
419
|
+
close
|
359
420
|
message
|
360
421
|
ensure
|
361
422
|
node.clear_deferred
|
@@ -436,19 +497,14 @@ module RSMP
|
|
436
497
|
send_message message, "for #{original.type} #{original.m_id_short}"
|
437
498
|
end
|
438
499
|
|
439
|
-
def
|
440
|
-
@state = state
|
441
|
-
@state_condition.signal @state
|
442
|
-
end
|
443
|
-
|
444
|
-
def wait_for_state state, timeout
|
500
|
+
def wait_for_state state, timeout:
|
445
501
|
states = [state].flatten
|
446
502
|
return if states.include?(@state)
|
447
|
-
|
503
|
+
wait_for_condition(@state_condition,timeout: timeout) do
|
448
504
|
states.include?(@state)
|
449
505
|
end
|
450
506
|
@state
|
451
|
-
rescue
|
507
|
+
rescue RSMP::TimeoutError
|
452
508
|
raise RSMP::TimeoutError.new "Did not reach state #{state} within #{timeout}s"
|
453
509
|
end
|
454
510
|
|
@@ -557,10 +613,10 @@ module RSMP
|
|
557
613
|
end
|
558
614
|
end
|
559
615
|
|
560
|
-
def
|
616
|
+
def handshake_complete
|
561
617
|
set_state :ready
|
562
618
|
end
|
563
|
-
|
619
|
+
|
564
620
|
def version_acknowledged
|
565
621
|
end
|
566
622
|
|
@@ -572,23 +628,17 @@ module RSMP
|
|
572
628
|
node.site_id
|
573
629
|
end
|
574
630
|
|
575
|
-
def
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
collector.complete if message.attribute('oMId') == m_id
|
631
|
+
def send_and_optionally_collect message, options, &block
|
632
|
+
collect_options = options[:collect] || options[:collect!]
|
633
|
+
if collect_options
|
634
|
+
task = @task.async do |task|
|
635
|
+
task.annotate 'send_and_optionally_collect'
|
636
|
+
collector = yield collect_options # call block to create collector
|
637
|
+
collector.collect
|
638
|
+
collector.ok! if options[:collect!] # raise any errors if the bang version was specified
|
639
|
+
collector
|
585
640
|
end
|
586
|
-
end
|
587
|
-
end
|
588
641
|
|
589
|
-
def send_and_optionally_collect message, options, &block
|
590
|
-
if options[:collect]
|
591
|
-
task = @task.async { |task| yield task }
|
592
642
|
send_message message, validate: options[:validate]
|
593
643
|
{ sent: message, collector: task.wait }
|
594
644
|
else
|
data/lib/rsmp/rsmp.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
# Get the current time in UTC, with optional adjustment
|
2
|
-
# Convertion to string uses the RSMP format 2015-06-08T12:01:39.654Z
|
2
|
+
# Convertion to string uses the RSMP format 2015-06-08T12:01:39.654Z
|
3
3
|
# Note that using to_s on a my_clock.to_s will not produce an RSMP formatted timestamp,
|
4
4
|
# you need to use Clock.to_s my_clock
|
5
5
|
|
data/lib/rsmp/site.rb
CHANGED
@@ -15,6 +15,8 @@ module RSMP
|
|
15
15
|
@proxies = []
|
16
16
|
@sleep_condition = Async::Notification.new
|
17
17
|
@proxies_condition = Async::Notification.new
|
18
|
+
|
19
|
+
build_proxies
|
18
20
|
end
|
19
21
|
|
20
22
|
def site_id
|
@@ -62,25 +64,29 @@ module RSMP
|
|
62
64
|
RSMP::Schemer::find_schema! sxl, version, lenient: true
|
63
65
|
end
|
64
66
|
|
65
|
-
def
|
66
|
-
@
|
67
|
+
def run
|
68
|
+
log "Starting site #{@site_settings["site_id"]}",
|
69
|
+
level: :info,
|
70
|
+
timestamp: @clock.now
|
71
|
+
@proxies.each { |proxy| proxy.start }
|
72
|
+
@proxies.each { |proxy| proxy.wait }
|
67
73
|
end
|
68
74
|
|
69
|
-
def
|
75
|
+
def build_proxies
|
70
76
|
@site_settings["supervisors"].each do |supervisor_settings|
|
71
|
-
@
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
+
@proxies << SupervisorProxy.new({
|
78
|
+
site: self,
|
79
|
+
task: @task,
|
80
|
+
settings: @site_settings,
|
81
|
+
ip: supervisor_settings['ip'],
|
82
|
+
port: supervisor_settings['port'],
|
83
|
+
logger: @logger,
|
84
|
+
archive: @archive,
|
85
|
+
collect: @collect
|
86
|
+
})
|
77
87
|
end
|
78
88
|
end
|
79
89
|
|
80
|
-
def build_proxy settings
|
81
|
-
SupervisorProxy.new settings
|
82
|
-
end
|
83
|
-
|
84
90
|
def aggregated_status_changed component, options={}
|
85
91
|
@proxies.each do |proxy|
|
86
92
|
proxy.send_aggregated_status component, options if proxy.ready?
|
@@ -91,7 +97,7 @@ module RSMP
|
|
91
97
|
proxy = build_proxy({
|
92
98
|
site: self,
|
93
99
|
task: @task,
|
94
|
-
settings: @site_settings,
|
100
|
+
settings: @site_settings,
|
95
101
|
ip: supervisor_settings['ip'],
|
96
102
|
port: supervisor_settings['port'],
|
97
103
|
logger: @logger,
|
@@ -99,63 +105,20 @@ module RSMP
|
|
99
105
|
collect: @collect
|
100
106
|
})
|
101
107
|
@proxies << proxy
|
108
|
+
proxy.start
|
102
109
|
@proxies_condition.signal
|
103
|
-
run_site_proxy task, proxy
|
104
|
-
ensure
|
105
|
-
@proxies.delete proxy
|
106
|
-
@proxies_condition.signal
|
107
|
-
end
|
108
|
-
|
109
|
-
def run_site_proxy task, proxy
|
110
|
-
loop do
|
111
|
-
proxy.run # run until disconnected
|
112
|
-
rescue IOError => e
|
113
|
-
log "Stream error: #{e}", level: :warning
|
114
|
-
rescue StandardError => e
|
115
|
-
notify_error e, level: :internal
|
116
|
-
ensure
|
117
|
-
begin
|
118
|
-
if @site_settings['intervals']['watchdog'] != :no
|
119
|
-
# sleep until waken by reconnect() or the reconnect interval passed
|
120
|
-
proxy.set_state :wait_for_reconnect
|
121
|
-
task.with_timeout(@site_settings['intervals']['watchdog']) do
|
122
|
-
@sleep_condition.wait
|
123
|
-
end
|
124
|
-
else
|
125
|
-
proxy.set_state :cannot_connect
|
126
|
-
break
|
127
|
-
end
|
128
|
-
rescue Async::TimeoutError
|
129
|
-
# ignore
|
130
|
-
end
|
131
|
-
end
|
132
110
|
end
|
133
111
|
|
112
|
+
# stop
|
134
113
|
def stop
|
135
114
|
log "Stopping site #{@site_settings["site_id"]}", level: :info
|
136
|
-
@proxies.each do |proxy|
|
137
|
-
proxy.stop
|
138
|
-
end
|
139
|
-
@proxies.clear
|
140
115
|
super
|
141
116
|
end
|
142
|
-
|
143
|
-
def starting
|
144
|
-
log "Starting site #{@site_settings["site_id"]}",
|
145
|
-
level: :info,
|
146
|
-
timestamp: @clock.now
|
147
|
-
end
|
148
|
-
|
149
|
-
def alarm
|
150
|
-
@proxies.each do |proxy|
|
151
|
-
proxy.stop
|
152
|
-
end
|
153
|
-
end
|
154
117
|
|
155
118
|
def wait_for_supervisor ip, timeout
|
156
119
|
supervisor = find_supervisor ip
|
157
120
|
return supervisor if supervisor
|
158
|
-
|
121
|
+
wait_for_condition(@proxy_condition,timeout:timeout) { find_supervisor ip }
|
159
122
|
rescue Async::TimeoutError
|
160
123
|
raise RSMP::TimeoutError.new "Supervisor '#{ip}' did not connect within #{timeout}s"
|
161
124
|
end
|