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.
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].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
 
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, :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
@@ -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 item
21
- @listeners.each { |listener| listener.notify item }
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
- probe = RSMP::Collector.new self, options
35
- probe.collect task, &block
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,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
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
- log ["Proxy exception: #{e.inspect}",e.backtrace].flatten.join("\n"), level: :error
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 = RSMP.now_object
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 = RSMP.now_object
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
- log "Error: #{e}", level: :debug
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=nil
190
- now = RSMP.now_object unless nil
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 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
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: 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
- 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}")
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 sxl
265
- notify message: 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
- 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
272
302
  nil
273
303
  rescue MalformedMessage => e
274
- 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
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
- 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
279
314
  message
280
315
  rescue InvalidMessage => e
281
- 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
282
319
  message
283
320
  rescue FatalError => e
284
- 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}"
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.sort.last # pick latest version
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 = RSMP.now_object
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
- # RSMP module
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
- def self.now_object
7
- # date using UTC time zone
8
- Time.now.utc
9
- end
10
+ class Clock
11
+ attr_reader :adjustment
10
12
 
11
- def self.now_object_to_string now
12
- # date in the format required by rsmp, using UTC time zone
13
- # example: 2015-06-08T12:01:39.654Z
14
- time ||= now.utc
15
- time.strftime("%FT%T.%3NZ")
16
- end
13
+ def initialize
14
+ @adjustment = 0
15
+ end
17
16
 
18
- def self.now_string time=nil
19
- time ||= Time.now
20
- now_object_to_string time
21
- end
17
+ def set target
18
+ @adjustment = target - Time.now
19
+ end
22
20
 
23
- def self.parse_time time_str
24
- Time.parse time_str
25
- end
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