rsmp 0.1.21 → 0.1.27
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 +12 -6
- data/config/tlc.yaml +8 -0
- data/documentation/message_distribution.md +8 -8
- data/lib/rsmp.rb +4 -0
- data/lib/rsmp/archive.rb +7 -4
- data/lib/rsmp/cli.rb +131 -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/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 +69 -28
- data/lib/rsmp/rsmp.rb +34 -23
- data/lib/rsmp/site.rb +16 -8
- data/lib/rsmp/site_proxy.rb +86 -20
- data/lib/rsmp/site_proxy_wait.rb +63 -38
- data/lib/rsmp/supervisor.rb +47 -18
- data/lib/rsmp/supervisor_proxy.rb +18 -10
- data/lib/rsmp/tlc.rb +55 -33
- data/lib/rsmp/version.rb +1 -1
- data/rsmp.gemspec +3 -14
- metadata +12 -112
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,6 +19,7 @@ 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
|
19
25
|
initialize_distributor
|
@@ -23,6 +29,16 @@ module RSMP
|
|
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,13 +134,15 @@ 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
147
|
log "Starting watchdog with interval #{@settings["watchdog_interval"]} seconds", level: :debug
|
129
148
|
send_watchdog
|
@@ -134,14 +153,14 @@ module RSMP
|
|
134
153
|
name = "timer"
|
135
154
|
interval = @settings["timer_interval"] || 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
|
@@ -186,9 +203,8 @@ module RSMP
|
|
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
|
@@ -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
|
@@ -325,7 +364,7 @@ module RSMP
|
|
325
364
|
# find versions that both we and the client support
|
326
365
|
candidates = message.versions & @settings["rsmp_versions"]
|
327
366
|
if candidates.any?
|
328
|
-
@rsmp_version = candidates.
|
367
|
+
@rsmp_version = candidates.sort_by { |v| Gem::Version.new(v) }.last # pick latest version
|
329
368
|
else
|
330
369
|
raise FatalError.new "RSMP versions [#{message.versions.join(',')}] requested, but only [#{@settings["rsmp_versions"].join(',')}] supported."
|
331
370
|
end
|
@@ -366,6 +405,8 @@ module RSMP
|
|
366
405
|
states.include?(@state)
|
367
406
|
end
|
368
407
|
@state
|
408
|
+
rescue Async::TimeoutError
|
409
|
+
raise RSMP::TimeoutError.new "Did not reach state #{state} within #{timeout}s"
|
369
410
|
end
|
370
411
|
|
371
412
|
def send_version site_id, rsmp_versions
|
@@ -455,7 +496,7 @@ module RSMP
|
|
455
496
|
|
456
497
|
def process_watchdog message
|
457
498
|
log "Received #{message.type}", message: message, level: :log
|
458
|
-
@latest_watchdog_received =
|
499
|
+
@latest_watchdog_received = Clock.now
|
459
500
|
acknowledge message
|
460
501
|
end
|
461
502
|
|
data/lib/rsmp/rsmp.rb
CHANGED
@@ -1,31 +1,42 @@
|
|
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
|
3
|
+
# Note that using to_s on a my_clock.to_s will not produce an RSMP formatted timestamp,
|
4
|
+
# you need to use Clock.to_s my_clock
|
5
|
+
|
6
|
+
require 'time'
|
2
7
|
|
3
8
|
module RSMP
|
4
|
-
WRAPPING_DELIMITER = "\f"
|
5
9
|
|
6
|
-
|
7
|
-
|
8
|
-
Time.now.utc
|
9
|
-
end
|
10
|
+
class Clock
|
11
|
+
attr_reader :adjustment
|
10
12
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
time ||= now.utc
|
15
|
-
time.strftime("%FT%T.%3NZ")
|
16
|
-
end
|
13
|
+
def initialize
|
14
|
+
@adjustment = 0
|
15
|
+
end
|
17
16
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
end
|
17
|
+
def set target
|
18
|
+
@adjustment = target - Time.now
|
19
|
+
end
|
22
20
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
def self.log_prefix ip
|
28
|
-
"#{now_string} #{ip.ljust(20)}"
|
29
|
-
end
|
21
|
+
def reset
|
22
|
+
@adjustment = 0
|
23
|
+
end
|
30
24
|
|
25
|
+
def now
|
26
|
+
Time.now.utc + @adjustment
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_s
|
30
|
+
Clock.to_s now
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.now
|
34
|
+
Time.now.utc
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.to_s time=nil
|
38
|
+
(time || now).strftime("%FT%T.%3NZ")
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
31
42
|
end
|