rsmp 0.1.21 → 0.1.27

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,
@@ -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