rsmp 0.1.19 → 0.1.31

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