rsmp 0.1.19 → 0.1.31

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.
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,
@@ -82,7 +83,6 @@ module RSMP
82
83
  end
83
84
 
84
85
  def colorize level, str
85
- #p String.color_samples
86
86
  if @settings["color"] == false || @settings["color"] == nil
87
87
  str
88
88
  elsif @settings["color"] == true
@@ -125,8 +125,9 @@ module RSMP
125
125
  end
126
126
  end
127
127
 
128
- def dump archive, force:false
129
- log = archive.items.map do |item|
128
+ def dump archive, force:false, num:nil
129
+ num ||= archive.items.size
130
+ log = archive.items.last(num).map do |item|
130
131
  str = build_output item
131
132
  str = colorize item[:level], str
132
133
  end
@@ -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].to_s.ljust(24) unless @settings["timestamp"] == false
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
 
@@ -3,10 +3,10 @@
3
3
  #
4
4
 
5
5
  module RSMP
6
- class Base
6
+ module Logging
7
7
  attr_reader :archive, :logger
8
8
 
9
- def initialize options
9
+ def initialize_logging options
10
10
  @archive = options[:archive] || RSMP::Archive.new
11
11
  @logger = options[:logger] || RSMP::Logger.new(options[:log_settings])
12
12
  end
@@ -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, :timestamp
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 = RSMP.now_object
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 sxl=nil
140
- schema = Message.get_schema(sxl)
141
- unless schema.valid? attributes
142
- errors = schema.validate attributes
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
@@ -1,18 +1,26 @@
1
- # RSMP site
2
- #
3
- # Handles a single connection to a supervisor.
4
- # We connect to the supervisor.
1
+ # Base class for sites and supervisors
5
2
 
6
3
  module RSMP
7
- class Node < Base
4
+ class Node
5
+ include Logging
8
6
  include Wait
7
+ include Inspect
9
8
 
10
- attr_reader :archive, :logger, :task, :deferred
9
+ attr_reader :archive, :logger, :task, :deferred, :error_condition, :clock
11
10
 
12
11
  def initialize options
13
- super options
12
+ initialize_logging options
14
13
  @task = options[:task]
15
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
16
24
  end
17
25
 
18
26
  def defer item
@@ -0,0 +1,29 @@
1
+ # Distributes messages to listeners
2
+
3
+ module RSMP
4
+ module Notifier
5
+ include Inspect
6
+
7
+ def inspect
8
+ "#<#{self.class.name}:#{self.object_id}, #{inspector(:@listeners)}>"
9
+ end
10
+
11
+ def initialize_distributor
12
+ @listeners = []
13
+ end
14
+
15
+ def add_listener listener
16
+ raise ArgumentError unless listener
17
+ @listeners << listener unless @listeners.include? listener
18
+ end
19
+
20
+ def remove_listener listener
21
+ raise ArgumentError unless listener
22
+ @listeners.delete listener
23
+ end
24
+
25
+ def notify message
26
+ @listeners.each { |listener| listener.notify message }
27
+ end
28
+ end
29
+ end
data/lib/rsmp/proxy.rb CHANGED
@@ -1,25 +1,60 @@
1
- # Base class for a connection to a remote site or supervisor.
1
+ # Logging class for a connection to a remote site or supervisor.
2
+
3
+ require 'rubygems'
2
4
 
3
5
  module RSMP
4
- class Proxy < Base
6
+ class Proxy
7
+ WRAPPING_DELIMITER = "\f"
8
+
9
+ include Logging
5
10
  include Wait
11
+ include Notifier
12
+ include Inspect
6
13
 
7
- attr_reader :state, :archive, :connection_info, :sxl, :task
14
+ attr_reader :state, :archive, :connection_info, :sxl, :task, :collector
8
15
 
9
16
  def initialize options
10
- super options
17
+ initialize_logging options
11
18
  @settings = options[:settings]
12
19
  @task = options[:task]
13
20
  @socket = options[:socket]
14
21
  @ip = options[:ip]
22
+ @port = options[:port]
15
23
  @connection_info = options[:info]
16
24
  @sxl = nil
25
+ @site_settings = nil # can't pick until we know the site id
26
+ initialize_distributor
27
+
28
+ prepare_collection @settings['collect']
17
29
  clear
18
30
  end
19
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
+
42
+ def prepare_collection num
43
+ if num
44
+ @collector = RSMP::Collector.new self, num: num, ingoing: true, outgoing: true
45
+ add_listener @collector
46
+ end
47
+ end
48
+
49
+ def collect task, options, &block
50
+ collector = RSMP::Collector.new self, options
51
+ collector.collect task, &block
52
+ end
53
+
20
54
  def run
21
55
  start
22
56
  @reader.wait if @reader
57
+ ensure
23
58
  stop unless [:stopped, :stopping].include? @state
24
59
  end
25
60
 
@@ -71,7 +106,7 @@ module RSMP
71
106
  @reader = @task.async do |task|
72
107
  task.annotate "reader"
73
108
  @stream = Async::IO::Stream.new(@socket)
74
- @protocol = Async::IO::Protocol::Line.new(@stream,RSMP::WRAPPING_DELIMITER) # rsmp messages are json terminated with a form-feed
109
+ @protocol = Async::IO::Protocol::Line.new(@stream,WRAPPING_DELIMITER) # rsmp messages are json terminated with a form-feed
75
110
 
76
111
  while json = @protocol.read_line
77
112
  beginning = Time.now
@@ -99,31 +134,33 @@ module RSMP
99
134
  log "Connection reset by peer", level: :warning
100
135
  rescue Errno::EPIPE
101
136
  log "Broken pipe", level: :warning
102
- rescue SystemCallError => e # all ERRNO errors
103
- log "Proxy exception: #{e.to_s}", level: :error
104
137
  rescue StandardError => e
105
- log ["Proxy exception: #{e.inspect}",e.backtrace].flatten.join("\n"), level: :error
138
+ notify_error e, level: :internal
106
139
  end
107
140
  end
108
141
 
142
+ def notify_error e, options={}
143
+ node.notify_error e, options
144
+ end
145
+
109
146
  def start_watchdog
110
- log "Starting watchdog with interval #{@settings["watchdog_interval"]} seconds", level: :debug
147
+ log "Starting watchdog with interval #{@site_settings['intervals']['watchdog']} seconds", level: :debug
111
148
  send_watchdog
112
149
  @watchdog_started = true
113
150
  end
114
151
 
115
152
  def start_timer
116
153
  name = "timer"
117
- interval = @settings["timer_interval"] || 1
154
+ interval = @site_settings['intervals']['timer'] || 1
118
155
  log "Starting #{name} with interval #{interval} seconds", level: :debug
119
- @latest_watchdog_received = RSMP.now_object
156
+ @latest_watchdog_received = Clock.now
120
157
 
121
158
  @timer = @task.async do |task|
122
159
  task.annotate "timer"
123
160
  next_time = Time.now.to_f
124
161
  loop do
125
162
  begin
126
- now = RSMP.now_object
163
+ now = Clock.now
127
164
  timer(now)
128
165
  rescue EOFError => e
129
166
  log "Timer: Connection closed: #{e}", level: :warning
@@ -134,9 +171,7 @@ module RSMP
134
171
  rescue Errno::EPIPE => e
135
172
  log "Timer: Broken pipe", level: :warning
136
173
  rescue StandardError => e
137
- log "Error: #{e}", level: :debug
138
- #rescue StandardError => e
139
- # log ["Timer error: #{e}",e.backtrace].flatten.join("\n"), level: :error
174
+ notify_error e, level: :internal
140
175
  end
141
176
  ensure
142
177
  next_time += interval
@@ -154,7 +189,7 @@ module RSMP
154
189
 
155
190
  def watchdog_send_timer now
156
191
  return unless @watchdog_started
157
- return if @settings["watchdog_interval"] == :never
192
+ return if @site_settings['intervals']['watchdog'] == :never
158
193
 
159
194
  if @latest_watchdog_send_at == nil
160
195
  send_watchdog now
@@ -162,21 +197,20 @@ module RSMP
162
197
  # we add half the timer interval to pick the timer
163
198
  # event closes to the wanted wathcdog interval
164
199
  diff = now - @latest_watchdog_send_at
165
- if (diff + 0.5*@settings["timer_interval"]) >= (@settings["watchdog_interval"])
200
+ if (diff + 0.5*@site_settings['intervals']['timer']) >= (@site_settings['intervals']['watchdog'])
166
201
  send_watchdog now
167
202
  end
168
203
  end
169
204
  end
170
205
 
171
- def send_watchdog now=nil
172
- now = RSMP.now_object unless nil
173
- 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})
174
208
  send_message message
175
209
  @latest_watchdog_send_at = now
176
210
  end
177
211
 
178
212
  def check_ack_timeout now
179
- timeout = @settings["acknowledgement_timeout"]
213
+ timeout = @site_settings['timeouts']['acknowledgement']
180
214
  # hash cannot be modify during iteration, so clone it
181
215
  @awaiting_acknowledgement.clone.each_pair do |m_id, message|
182
216
  latest = message.timestamp + timeout
@@ -188,7 +222,7 @@ module RSMP
188
222
  end
189
223
 
190
224
  def check_watchdog_timeout now
191
- timeout = @settings["watchdog_timeout"]
225
+ timeout = @site_settings['timeouts']['watchdog']
192
226
  latest = @latest_watchdog_received + timeout
193
227
  left = latest - now
194
228
  if left < 0
@@ -206,18 +240,31 @@ module RSMP
206
240
  super str, options.merge(ip: @ip, port: @port, site_id: @site_id)
207
241
  end
208
242
 
209
- def send_message message, reason=nil
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
210
254
  raise IOError unless @protocol
211
- message.generate_json
212
- message.validate sxl
213
255
  message.direction = :out
256
+ message.generate_json
257
+ message.validate get_schemas unless validate==false
214
258
  expect_acknowledgement message
215
259
  @protocol.write_lines message.json
260
+ notify message
216
261
  log_send message, reason
217
262
  rescue EOFError, IOError
218
263
  buffer_message message
219
264
  rescue SchemaError => e
220
- log "Error sending #{message.type}, schema validation failed: #{e.message}", message: message, level: :error
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}")
221
268
  end
222
269
 
223
270
  def buffer_message message
@@ -242,26 +289,38 @@ module RSMP
242
289
  def process_packet json
243
290
  attributes = Message.parse_attributes json
244
291
  message = Message.build attributes, json
245
- message.validate sxl
292
+ message.validate get_schemas
293
+ notify message
246
294
  expect_version_message(message) unless @version_determined
247
295
  process_message message
248
296
  process_deferred
249
297
  message
250
298
  rescue InvalidPacket => e
251
- log "Received invalid package, must be valid JSON but got #{json.size} bytes: #{e.message}", level: :warning
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
252
302
  nil
253
303
  rescue MalformedMessage => e
254
- log "Received malformed message, #{e.message}", message: Malformed.new(attributes), level: :warning
304
+ str = "Received malformed message, #{e.message}"
305
+ notify_error e.exception(str)
306
+ log str, message: Malformed.new(attributes), level: :warning
255
307
  # cannot send NotAcknowledged for a malformed message since we can't read it, just ignore it
256
308
  nil
257
309
  rescue SchemaError => e
258
- dont_acknowledge message, "Received", "invalid #{message.type}, schema errors: #{e.message}"
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
259
314
  message
260
315
  rescue InvalidMessage => e
261
- dont_acknowledge message, "Received", "invalid #{message.type}, #{e.message}"
316
+ str = "Received", "invalid #{message.type}, #{e.message}"
317
+ notify_error e.exception("#{str} #{message.json}")
318
+ dont_acknowledge message, str
262
319
  message
263
320
  rescue FatalError => e
264
- dont_acknowledge message, "Rejected #{message.type},", "#{e.message}"
321
+ str = "Rejected #{message.type},"
322
+ notify_error e.exception("#{str} #{message.json}")
323
+ dont_acknowledge message, str, "#{e.message}"
265
324
  stop
266
325
  message
267
326
  end
@@ -301,13 +360,20 @@ module RSMP
301
360
  dont_acknowledge message, "Received", "extraneous Version message"
302
361
  end
303
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
+
304
369
  def check_rsmp_version message
370
+ versions = rsmp_versions
305
371
  # find versions that both we and the client support
306
- candidates = message.versions & @settings["rsmp_versions"]
372
+ candidates = message.versions & versions
307
373
  if candidates.any?
308
- @rsmp_version = candidates.sort.last # pick latest version
374
+ @rsmp_version = candidates.sort_by { |v| Gem::Version.new(v) }.last # pick latest version
309
375
  else
310
- raise FatalError.new "RSMP versions [#{message.versions.join(',')}] requested, but only [#{@settings["rsmp_versions"].join(',')}] supported."
376
+ raise FatalError.new "RSMP versions [#{message.versions.join(',')}] requested, but only [#{versions.join(',')}] supported."
311
377
  end
312
378
  end
313
379
 
@@ -342,14 +408,24 @@ module RSMP
342
408
  def wait_for_state state, timeout
343
409
  states = [state].flatten
344
410
  return if states.include?(@state)
345
- wait_for(@state_condition,timeout) do |s|
411
+ wait_for(@state_condition,timeout) do
346
412
  states.include?(@state)
347
413
  end
348
414
  @state
415
+ rescue Async::TimeoutError
416
+ raise RSMP::TimeoutError.new "Did not reach state #{state} within #{timeout}s"
349
417
  end
350
418
 
351
419
  def send_version site_id, rsmp_versions
352
- versions_array = [rsmp_versions].flatten.map {|v| {"vers" => v} }
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
+
353
429
  site_id_array = [site_id].flatten.map {|id| {"sId" => id} }
354
430
 
355
431
  version_response = Version.new({
@@ -435,7 +511,7 @@ module RSMP
435
511
 
436
512
  def process_watchdog message
437
513
  log "Received #{message.type}", message: message, level: :log
438
- @latest_watchdog_received = RSMP.now_object
514
+ @latest_watchdog_received = Clock.now
439
515
  acknowledge message
440
516
  end
441
517