rsmp 0.1.21 → 0.1.32
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
|