rsmp 0.1.21 → 0.1.32
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/.gitmodules +0 -4
- data/Gemfile.lock +13 -11
- data/config/supervisor.yaml +9 -15
- data/config/tlc.yaml +9 -8
- data/documentation/message_distribution.md +8 -8
- data/lib/rsmp.rb +5 -0
- data/lib/rsmp/archive.rb +7 -4
- data/lib/rsmp/cli.rb +133 -102
- data/lib/rsmp/collector.rb +42 -30
- data/lib/rsmp/component.rb +2 -0
- data/lib/rsmp/components.rb +1 -1
- data/lib/rsmp/convert/export/json_schema.rb +204 -0
- data/lib/rsmp/convert/import/yaml.rb +38 -0
- data/lib/rsmp/deep_merge.rb +11 -0
- data/lib/rsmp/inspect.rb +46 -0
- data/lib/rsmp/listener.rb +4 -14
- data/lib/rsmp/logger.rb +3 -1
- data/lib/rsmp/logging.rb +1 -1
- data/lib/rsmp/message.rb +22 -31
- data/lib/rsmp/node.rb +11 -1
- data/lib/rsmp/notifier.rb +7 -2
- data/lib/rsmp/proxy.rb +95 -39
- data/lib/rsmp/rsmp.rb +34 -23
- data/lib/rsmp/site.rb +30 -32
- data/lib/rsmp/site_proxy.rb +103 -22
- data/lib/rsmp/site_proxy_wait.rb +63 -38
- data/lib/rsmp/supervisor.rb +82 -47
- data/lib/rsmp/supervisor_proxy.rb +21 -13
- data/lib/rsmp/tlc.rb +61 -35
- data/lib/rsmp/version.rb +1 -1
- data/rsmp.gemspec +3 -14
- metadata +13 -113
- data/config/site.yaml +0 -40
data/lib/rsmp/logger.rb
CHANGED
@@ -13,6 +13,7 @@ module RSMP
|
|
13
13
|
'component'=>false,
|
14
14
|
'level'=>false,
|
15
15
|
'ip'=>false,
|
16
|
+
'port'=>false,
|
16
17
|
'index'=>false,
|
17
18
|
'timestamp'=>true,
|
18
19
|
'json'=>false,
|
@@ -137,8 +138,9 @@ module RSMP
|
|
137
138
|
parts = []
|
138
139
|
parts << item[:index].to_s.ljust(7) if @settings["index"] == true
|
139
140
|
parts << item[:author].to_s.ljust(13) if @settings["author"] == true
|
140
|
-
parts << item[:timestamp].
|
141
|
+
parts << Clock.to_s(item[:timestamp]).ljust(24) unless @settings["timestamp"] == false
|
141
142
|
parts << item[:ip].to_s.ljust(22) unless @settings["ip"] == false
|
143
|
+
parts << item[:port].to_s.ljust(8) unless @settings["port"] == false
|
142
144
|
parts << item[:site_id].to_s.ljust(13) unless @settings["site_id"] == false
|
143
145
|
parts << item[:component_id].to_s.ljust(18) unless @settings["component"] == false
|
144
146
|
|
data/lib/rsmp/logging.rb
CHANGED
@@ -15,7 +15,7 @@ module RSMP
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def log str, options={}
|
18
|
-
default = { str:str, level: :log, author: author }
|
18
|
+
default = { str:str, level: :log, author: author, ip: @ip, port: @port }
|
19
19
|
prepared = RSMP::Archive.prepare_item default.merge(options)
|
20
20
|
@archive.add prepared
|
21
21
|
@logger.log prepared
|
data/lib/rsmp/message.rb
CHANGED
@@ -1,32 +1,14 @@
|
|
1
|
+
require 'rsmp_schemer'
|
2
|
+
|
1
3
|
# rsmp messages
|
2
4
|
module RSMP
|
3
5
|
class Message
|
6
|
+
include Inspect
|
4
7
|
|
5
|
-
attr_reader :now, :attributes, :out
|
8
|
+
attr_reader :now, :attributes, :out
|
9
|
+
attr_reader :timestamp # this is an internal timestamp recording when we receive/send
|
6
10
|
attr_accessor :json, :direction
|
7
11
|
|
8
|
-
def self.load_schemas
|
9
|
-
# path to files in submodule folder
|
10
|
-
schema_path = File.join(File.dirname(__dir__),'rsmp_schema','schema')
|
11
|
-
@@schemas = {}
|
12
|
-
|
13
|
-
core_schema_path = File.join(schema_path,'core','rsmp.json')
|
14
|
-
@@schemas[nil] = JSONSchemer.schema( Pathname.new(core_schema_path) )
|
15
|
-
|
16
|
-
tlc_schema_path = File.join(schema_path,'tlc','sxl.json')
|
17
|
-
@@schemas['traffic_light_controller'] = JSONSchemer.schema( Pathname.new(tlc_schema_path) )
|
18
|
-
|
19
|
-
@@schemas
|
20
|
-
end
|
21
|
-
|
22
|
-
def self.get_schema sxl=nil
|
23
|
-
schema = @@schemas[sxl]
|
24
|
-
raise SchemaError.new("Unknown schema #{sxl}") unless schema
|
25
|
-
schema
|
26
|
-
end
|
27
|
-
|
28
|
-
@@schemas = load_schemas
|
29
|
-
|
30
12
|
def self.make_m_id
|
31
13
|
SecureRandom.uuid()
|
32
14
|
end
|
@@ -49,6 +31,8 @@ module RSMP
|
|
49
31
|
message = Version.new attributes
|
50
32
|
when "AggregatedStatus"
|
51
33
|
message = AggregatedStatus.new attributes
|
34
|
+
when "AggregatedStatusRequest"
|
35
|
+
message = AggregatedStatusRequest.new attributes
|
52
36
|
when "Watchdog"
|
53
37
|
message = Watchdog.new attributes
|
54
38
|
when "Alarm"
|
@@ -125,7 +109,9 @@ module RSMP
|
|
125
109
|
end
|
126
110
|
|
127
111
|
def initialize attributes = {}
|
128
|
-
@timestamp =
|
112
|
+
@timestamp = Time.now # this timestamp is for internal use, and does not the clock
|
113
|
+
# in the node, which can be set by an rsmp supervisor
|
114
|
+
|
129
115
|
@attributes = { "mType"=> "rSMsg" }.merge attributes
|
130
116
|
|
131
117
|
ensure_message_id
|
@@ -136,13 +122,10 @@ module RSMP
|
|
136
122
|
@attributes["mId"] ||= Message.make_m_id
|
137
123
|
end
|
138
124
|
|
139
|
-
def validate
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
error_string = errors.map do |item|
|
144
|
-
[item['data_pointer'],item['type'],item['details']].compact.join(' ')
|
145
|
-
end.join(", ")
|
125
|
+
def validate schemas
|
126
|
+
errors = RSMP::Schemer.validate attributes, schemas
|
127
|
+
if errors
|
128
|
+
error_string = errors.compact.join(', ').strip
|
146
129
|
raise SchemaError.new error_string
|
147
130
|
end
|
148
131
|
end
|
@@ -196,6 +179,14 @@ module RSMP
|
|
196
179
|
end
|
197
180
|
end
|
198
181
|
|
182
|
+
class AggregatedStatusRequest < Message
|
183
|
+
def initialize attributes = {}
|
184
|
+
super({
|
185
|
+
"type" => "AggregatedStatusRequest",
|
186
|
+
}.merge attributes)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
199
190
|
class Alarm < Message
|
200
191
|
def initialize attributes = {}
|
201
192
|
super({
|
data/lib/rsmp/node.rb
CHANGED
@@ -4,13 +4,23 @@ module RSMP
|
|
4
4
|
class Node
|
5
5
|
include Logging
|
6
6
|
include Wait
|
7
|
+
include Inspect
|
7
8
|
|
8
|
-
attr_reader :archive, :logger, :task, :deferred
|
9
|
+
attr_reader :archive, :logger, :task, :deferred, :error_condition, :clock
|
9
10
|
|
10
11
|
def initialize options
|
11
12
|
initialize_logging options
|
12
13
|
@task = options[:task]
|
13
14
|
@deferred = []
|
15
|
+
@clock = Clock.new
|
16
|
+
@error_condition = Async::Notification.new
|
17
|
+
end
|
18
|
+
|
19
|
+
def notify_error e, options={}
|
20
|
+
if options[:level] == :internal
|
21
|
+
log ["#{e.to_s} in task: #{Async::Task.current.to_s}",e.backtrace].flatten.join("\n"), level: :error
|
22
|
+
end
|
23
|
+
@error_condition.signal e
|
14
24
|
end
|
15
25
|
|
16
26
|
def defer item
|
data/lib/rsmp/notifier.rb
CHANGED
@@ -2,6 +2,11 @@
|
|
2
2
|
|
3
3
|
module RSMP
|
4
4
|
module Notifier
|
5
|
+
include Inspect
|
6
|
+
|
7
|
+
def inspect
|
8
|
+
"#<#{self.class.name}:#{self.object_id}, #{inspector(:@listeners)}>"
|
9
|
+
end
|
5
10
|
|
6
11
|
def initialize_distributor
|
7
12
|
@listeners = []
|
@@ -17,8 +22,8 @@ module RSMP
|
|
17
22
|
@listeners.delete listener
|
18
23
|
end
|
19
24
|
|
20
|
-
def notify
|
21
|
-
@listeners.each { |listener| listener.notify
|
25
|
+
def notify message
|
26
|
+
@listeners.each { |listener| listener.notify message }
|
22
27
|
end
|
23
28
|
end
|
24
29
|
end
|
data/lib/rsmp/proxy.rb
CHANGED
@@ -1,10 +1,15 @@
|
|
1
1
|
# Logging class for a connection to a remote site or supervisor.
|
2
2
|
|
3
|
+
require 'rubygems'
|
4
|
+
|
3
5
|
module RSMP
|
4
6
|
class Proxy
|
7
|
+
WRAPPING_DELIMITER = "\f"
|
8
|
+
|
5
9
|
include Logging
|
6
10
|
include Wait
|
7
11
|
include Notifier
|
12
|
+
include Inspect
|
8
13
|
|
9
14
|
attr_reader :state, :archive, :connection_info, :sxl, :task, :collector
|
10
15
|
|
@@ -14,15 +19,26 @@ module RSMP
|
|
14
19
|
@task = options[:task]
|
15
20
|
@socket = options[:socket]
|
16
21
|
@ip = options[:ip]
|
22
|
+
@port = options[:port]
|
17
23
|
@connection_info = options[:info]
|
18
24
|
@sxl = nil
|
25
|
+
@site_settings = nil # can't pick until we know the site id
|
19
26
|
initialize_distributor
|
20
27
|
|
21
|
-
prepare_collection
|
22
|
-
|
28
|
+
prepare_collection @settings['collect']
|
23
29
|
clear
|
24
30
|
end
|
25
31
|
|
32
|
+
def inspect
|
33
|
+
"#<#{self.class.name}:#{self.object_id}, #{inspector(
|
34
|
+
:@acknowledgements,:@settings,:@site_settings
|
35
|
+
)}>"
|
36
|
+
end
|
37
|
+
|
38
|
+
def clock
|
39
|
+
node.clock
|
40
|
+
end
|
41
|
+
|
26
42
|
def prepare_collection num
|
27
43
|
if num
|
28
44
|
@collector = RSMP::Collector.new self, num: num, ingoing: true, outgoing: true
|
@@ -31,13 +47,14 @@ module RSMP
|
|
31
47
|
end
|
32
48
|
|
33
49
|
def collect task, options, &block
|
34
|
-
|
35
|
-
|
50
|
+
collector = RSMP::Collector.new self, options
|
51
|
+
collector.collect task, &block
|
36
52
|
end
|
37
53
|
|
38
54
|
def run
|
39
55
|
start
|
40
56
|
@reader.wait if @reader
|
57
|
+
ensure
|
41
58
|
stop unless [:stopped, :stopping].include? @state
|
42
59
|
end
|
43
60
|
|
@@ -89,7 +106,7 @@ module RSMP
|
|
89
106
|
@reader = @task.async do |task|
|
90
107
|
task.annotate "reader"
|
91
108
|
@stream = Async::IO::Stream.new(@socket)
|
92
|
-
@protocol = Async::IO::Protocol::Line.new(@stream,
|
109
|
+
@protocol = Async::IO::Protocol::Line.new(@stream,WRAPPING_DELIMITER) # rsmp messages are json terminated with a form-feed
|
93
110
|
|
94
111
|
while json = @protocol.read_line
|
95
112
|
beginning = Time.now
|
@@ -117,31 +134,33 @@ module RSMP
|
|
117
134
|
log "Connection reset by peer", level: :warning
|
118
135
|
rescue Errno::EPIPE
|
119
136
|
log "Broken pipe", level: :warning
|
120
|
-
rescue SystemCallError => e # all ERRNO errors
|
121
|
-
log "Proxy exception: #{e.to_s}", level: :error
|
122
137
|
rescue StandardError => e
|
123
|
-
|
138
|
+
notify_error e, level: :internal
|
124
139
|
end
|
125
140
|
end
|
126
141
|
|
142
|
+
def notify_error e, options={}
|
143
|
+
node.notify_error e, options
|
144
|
+
end
|
145
|
+
|
127
146
|
def start_watchdog
|
128
|
-
log "Starting watchdog with interval #{@
|
147
|
+
log "Starting watchdog with interval #{@site_settings['intervals']['watchdog']} seconds", level: :debug
|
129
148
|
send_watchdog
|
130
149
|
@watchdog_started = true
|
131
150
|
end
|
132
151
|
|
133
152
|
def start_timer
|
134
153
|
name = "timer"
|
135
|
-
interval = @
|
154
|
+
interval = @site_settings['intervals']['timer'] || 1
|
136
155
|
log "Starting #{name} with interval #{interval} seconds", level: :debug
|
137
|
-
@latest_watchdog_received =
|
156
|
+
@latest_watchdog_received = Clock.now
|
138
157
|
|
139
158
|
@timer = @task.async do |task|
|
140
159
|
task.annotate "timer"
|
141
160
|
next_time = Time.now.to_f
|
142
161
|
loop do
|
143
162
|
begin
|
144
|
-
now =
|
163
|
+
now = Clock.now
|
145
164
|
timer(now)
|
146
165
|
rescue EOFError => e
|
147
166
|
log "Timer: Connection closed: #{e}", level: :warning
|
@@ -152,9 +171,7 @@ module RSMP
|
|
152
171
|
rescue Errno::EPIPE => e
|
153
172
|
log "Timer: Broken pipe", level: :warning
|
154
173
|
rescue StandardError => e
|
155
|
-
|
156
|
-
#rescue StandardError => e
|
157
|
-
# log ["Timer error: #{e}",e.backtrace].flatten.join("\n"), level: :error
|
174
|
+
notify_error e, level: :internal
|
158
175
|
end
|
159
176
|
ensure
|
160
177
|
next_time += interval
|
@@ -172,7 +189,7 @@ module RSMP
|
|
172
189
|
|
173
190
|
def watchdog_send_timer now
|
174
191
|
return unless @watchdog_started
|
175
|
-
return if @
|
192
|
+
return if @site_settings['intervals']['watchdog'] == :never
|
176
193
|
|
177
194
|
if @latest_watchdog_send_at == nil
|
178
195
|
send_watchdog now
|
@@ -180,21 +197,20 @@ module RSMP
|
|
180
197
|
# we add half the timer interval to pick the timer
|
181
198
|
# event closes to the wanted wathcdog interval
|
182
199
|
diff = now - @latest_watchdog_send_at
|
183
|
-
if (diff + 0.5*@
|
200
|
+
if (diff + 0.5*@site_settings['intervals']['timer']) >= (@site_settings['intervals']['watchdog'])
|
184
201
|
send_watchdog now
|
185
202
|
end
|
186
203
|
end
|
187
204
|
end
|
188
205
|
|
189
|
-
def send_watchdog now=
|
190
|
-
|
191
|
-
message = Watchdog.new( {"wTs" => RSMP.now_object_to_string(now)})
|
206
|
+
def send_watchdog now=Clock.now
|
207
|
+
message = Watchdog.new( {"wTs" => clock.to_s})
|
192
208
|
send_message message
|
193
209
|
@latest_watchdog_send_at = now
|
194
210
|
end
|
195
211
|
|
196
212
|
def check_ack_timeout now
|
197
|
-
timeout = @
|
213
|
+
timeout = @site_settings['timeouts']['acknowledgement']
|
198
214
|
# hash cannot be modify during iteration, so clone it
|
199
215
|
@awaiting_acknowledgement.clone.each_pair do |m_id, message|
|
200
216
|
latest = message.timestamp + timeout
|
@@ -206,7 +222,7 @@ module RSMP
|
|
206
222
|
end
|
207
223
|
|
208
224
|
def check_watchdog_timeout now
|
209
|
-
timeout = @
|
225
|
+
timeout = @site_settings['timeouts']['watchdog']
|
210
226
|
latest = @latest_watchdog_received + timeout
|
211
227
|
left = latest - now
|
212
228
|
if left < 0
|
@@ -224,19 +240,31 @@ module RSMP
|
|
224
240
|
super str, options.merge(ip: @ip, port: @port, site_id: @site_id)
|
225
241
|
end
|
226
242
|
|
227
|
-
def
|
243
|
+
def get_schemas
|
244
|
+
# normally we have an sxl, but during connection, it hasn't been established yet
|
245
|
+
# at these times we only validate against the core schema
|
246
|
+
# TODO
|
247
|
+
# what schema should we use to validate the intial Version and MessageAck messages?
|
248
|
+
schemas = { core: '3.1.5' }
|
249
|
+
schemas[sxl] = sxl_version if sxl
|
250
|
+
schemas
|
251
|
+
end
|
252
|
+
|
253
|
+
def send_message message, reason=nil, validate: true
|
228
254
|
raise IOError unless @protocol
|
229
|
-
message.generate_json
|
230
|
-
message.validate sxl
|
231
255
|
message.direction = :out
|
256
|
+
message.generate_json
|
257
|
+
message.validate get_schemas unless validate==false
|
232
258
|
expect_acknowledgement message
|
233
259
|
@protocol.write_lines message.json
|
234
|
-
notify message
|
260
|
+
notify message
|
235
261
|
log_send message, reason
|
236
262
|
rescue EOFError, IOError
|
237
263
|
buffer_message message
|
238
264
|
rescue SchemaError => e
|
239
|
-
|
265
|
+
str = "Could not send #{message.type} because schema validation failed: #{e.message}"
|
266
|
+
log str, message: message, level: :error
|
267
|
+
notify_error e.exception("#{str} #{message.json}")
|
240
268
|
end
|
241
269
|
|
242
270
|
def buffer_message message
|
@@ -261,27 +289,38 @@ module RSMP
|
|
261
289
|
def process_packet json
|
262
290
|
attributes = Message.parse_attributes json
|
263
291
|
message = Message.build attributes, json
|
264
|
-
message.validate
|
265
|
-
notify message
|
292
|
+
message.validate get_schemas
|
293
|
+
notify message
|
266
294
|
expect_version_message(message) unless @version_determined
|
267
295
|
process_message message
|
268
296
|
process_deferred
|
269
297
|
message
|
270
298
|
rescue InvalidPacket => e
|
271
|
-
|
299
|
+
str = "Received invalid package, must be valid JSON but got #{json.size} bytes: #{e.message}"
|
300
|
+
notify_error e.exception(str)
|
301
|
+
log str, level: :warning
|
272
302
|
nil
|
273
303
|
rescue MalformedMessage => e
|
274
|
-
|
304
|
+
str = "Received malformed message, #{e.message}"
|
305
|
+
notify_error e.exception(str)
|
306
|
+
log str, message: Malformed.new(attributes), level: :warning
|
275
307
|
# cannot send NotAcknowledged for a malformed message since we can't read it, just ignore it
|
276
308
|
nil
|
277
309
|
rescue SchemaError => e
|
278
|
-
|
310
|
+
str = "Received invalid #{message.type}, schema errors: #{e.message}"
|
311
|
+
log str, message: message, level: :warning
|
312
|
+
notify_error e.exception("#{str} #{message.json}")
|
313
|
+
dont_acknowledge message, str
|
279
314
|
message
|
280
315
|
rescue InvalidMessage => e
|
281
|
-
|
316
|
+
str = "Received", "invalid #{message.type}, #{e.message}"
|
317
|
+
notify_error e.exception("#{str} #{message.json}")
|
318
|
+
dont_acknowledge message, str
|
282
319
|
message
|
283
320
|
rescue FatalError => e
|
284
|
-
|
321
|
+
str = "Rejected #{message.type},"
|
322
|
+
notify_error e.exception("#{str} #{message.json}")
|
323
|
+
dont_acknowledge message, str, "#{e.message}"
|
285
324
|
stop
|
286
325
|
message
|
287
326
|
end
|
@@ -321,13 +360,20 @@ module RSMP
|
|
321
360
|
dont_acknowledge message, "Received", "extraneous Version message"
|
322
361
|
end
|
323
362
|
|
363
|
+
def rsmp_versions
|
364
|
+
return ['3.1.5'] if @site_settings["rsmp_versions"] == 'latest'
|
365
|
+
return ['3.1.1','3.1.2','3.1.3','3.1.4','3.1.5'] if @site_settings["rsmp_versions"] == 'all'
|
366
|
+
@site_settings["rsmp_versions"]
|
367
|
+
end
|
368
|
+
|
324
369
|
def check_rsmp_version message
|
370
|
+
versions = rsmp_versions
|
325
371
|
# find versions that both we and the client support
|
326
|
-
candidates = message.versions &
|
372
|
+
candidates = message.versions & versions
|
327
373
|
if candidates.any?
|
328
|
-
@rsmp_version = candidates.
|
374
|
+
@rsmp_version = candidates.sort_by { |v| Gem::Version.new(v) }.last # pick latest version
|
329
375
|
else
|
330
|
-
raise FatalError.new "RSMP versions [#{message.versions.join(',')}] requested, but only [#{
|
376
|
+
raise FatalError.new "RSMP versions [#{message.versions.join(',')}] requested, but only [#{versions.join(',')}] supported."
|
331
377
|
end
|
332
378
|
end
|
333
379
|
|
@@ -366,10 +412,20 @@ module RSMP
|
|
366
412
|
states.include?(@state)
|
367
413
|
end
|
368
414
|
@state
|
415
|
+
rescue Async::TimeoutError
|
416
|
+
raise RSMP::TimeoutError.new "Did not reach state #{state} within #{timeout}s"
|
369
417
|
end
|
370
418
|
|
371
419
|
def send_version site_id, rsmp_versions
|
372
|
-
|
420
|
+
if rsmp_versions=='latest'
|
421
|
+
versions = ['3.1.5']
|
422
|
+
elsif rsmp_versions=='all'
|
423
|
+
versions = ['3.1.1','3.1.2','3.1.3','3.1.4','3.1.5']
|
424
|
+
else
|
425
|
+
versions = [rsmp_versions].flatten
|
426
|
+
end
|
427
|
+
versions_array = versions.map {|v| {"vers" => v} }
|
428
|
+
|
373
429
|
site_id_array = [site_id].flatten.map {|id| {"sId" => id} }
|
374
430
|
|
375
431
|
version_response = Version.new({
|
@@ -455,7 +511,7 @@ module RSMP
|
|
455
511
|
|
456
512
|
def process_watchdog message
|
457
513
|
log "Received #{message.type}", message: message, level: :log
|
458
|
-
@latest_watchdog_received =
|
514
|
+
@latest_watchdog_received = Clock.now
|
459
515
|
acknowledge message
|
460
516
|
end
|
461
517
|
|