rsmp 0.1.13 → 0.1.29

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.
@@ -0,0 +1,38 @@
1
+ # Import SXL from YAML format
2
+
3
+ require 'yaml'
4
+ require 'json'
5
+ require 'fileutils'
6
+
7
+ module RSMP
8
+ module Convert
9
+ module Import
10
+ module YAML
11
+
12
+ def self.read path
13
+ convert ::YAML.load_file(path)
14
+ end
15
+
16
+ def self.parse str
17
+ convert ::YAML.load(str)
18
+ end
19
+
20
+ def self.convert yaml
21
+ sxl = {
22
+ alarms: {},
23
+ statuses: {},
24
+ commands: {}
25
+ }
26
+
27
+ yaml['objects'].each_pair do |type,object|
28
+ object["alarms"].each { |id,item| sxl[:alarms][id] = item }
29
+ object["statuses"].each { |id,item| sxl[:statuses][id] = item }
30
+ object["commands"].each { |id,item| sxl[:commands][id] = item }
31
+ end
32
+ sxl
33
+ end
34
+ end
35
+
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,11 @@
1
+ class Hash
2
+ def deep_merge(other_hash)
3
+ self.merge(other_hash) do |key, old, fresh|
4
+ if old.is_a?(Hash) && fresh.is_a?(Hash)
5
+ old.deep_merge(fresh)
6
+ else
7
+ fresh
8
+ end
9
+ end
10
+ end
11
+ end
data/lib/rsmp/error.rb CHANGED
@@ -23,7 +23,7 @@ module RSMP
23
23
  class MissingWatchdog < Error
24
24
  end
25
25
 
26
- class MissingAcknowledgment < Error
26
+ class MessageRejected < Error
27
27
  end
28
28
 
29
29
  class MissingAttribute < InvalidMessage
@@ -0,0 +1,46 @@
1
+ # Costume inspect, to reduce noise
2
+ #
3
+ # Instance variables of classes starting with Async or RSMP are shown
4
+ # with only their class name and object id, which reduces output,
5
+ # especially for deep object structures.
6
+ # Additionally, a list of variables to shown in short format can be passed.
7
+ #
8
+ # The short form is generated by using to_s() insted of inspect()
9
+ #
10
+ # Array#to_s and Hash#to_s usually show items, but here we show just number
11
+ # of items, when the short form is requested.
12
+
13
+ module RSMP
14
+ module Inspect
15
+
16
+ def inspector *short_items
17
+ instance_variables.map do |var_name|
18
+ var = instance_variable_get(var_name)
19
+ class_name = var.class.name
20
+
21
+ short = short_items.include?(var_name) ||
22
+ class_name.start_with?('Async') ||
23
+ class_name.start_with?('RSMP')
24
+
25
+ if short
26
+ if var.is_a? Array
27
+ "#{var_name}: #<#{class_name}:#{class_name.object_id}, #{var.size} items>"
28
+ elsif var.is_a? Hash
29
+ "#{var_name}: #<#{class_name}:#{class_name.object_id}, #{var.size} items>"
30
+ else
31
+ "#{var_name}: #{var.to_s}"
32
+ end
33
+ else
34
+ "#{var_name}: #{var.inspect}"
35
+ end
36
+ end.join(', ')
37
+ end
38
+
39
+ # override this if you want additional variable to be shown in the short format,
40
+ # or ottherwise change the inspect format
41
+ def inspect
42
+ "#<#{self.class.name}:#{self.object_id}, #{inspector}>"
43
+ end
44
+
45
+ end
46
+ end
@@ -0,0 +1,23 @@
1
+ # Receives items from a Notifier, as long as it's
2
+ # installed as a listener.
3
+
4
+ module RSMP
5
+ class Listener
6
+ include Inspect
7
+
8
+ def initialize proxy, options={}
9
+ @proxy = proxy
10
+ end
11
+
12
+ def notify message
13
+ end
14
+
15
+ def listen &block
16
+ @proxy.add_listener self
17
+ yield
18
+ ensure
19
+ @proxy.remove_listener self
20
+ end
21
+
22
+ end
23
+ end
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,33 +1,18 @@
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
12
+ def self.make_m_id
13
+ SecureRandom.uuid()
26
14
  end
27
15
 
28
- @@schemas = load_schemas
29
-
30
-
31
16
  def self.parse_attributes json
32
17
  raise ArgumentError unless json
33
18
  JSON.parse json
@@ -46,6 +31,8 @@ module RSMP
46
31
  message = Version.new attributes
47
32
  when "AggregatedStatus"
48
33
  message = AggregatedStatus.new attributes
34
+ when "AggregatedStatusRequest"
35
+ message = AggregatedStatusRequest.new attributes
49
36
  when "Watchdog"
50
37
  message = Watchdog.new attributes
51
38
  when "Alarm"
@@ -80,8 +67,12 @@ module RSMP
80
67
  @attributes["mId"]
81
68
  end
82
69
 
70
+ def self.shorten_m_id m_id, length=4
71
+ m_id[0..length-1]
72
+ end
73
+
83
74
  def m_id_short
84
- @attributes["mId"][0..3]
75
+ Message.shorten_m_id @attributes["mId"]
85
76
  end
86
77
 
87
78
  def attribute key
@@ -118,7 +109,9 @@ module RSMP
118
109
  end
119
110
 
120
111
  def initialize attributes = {}
121
- @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
+
122
115
  @attributes = { "mType"=> "rSMsg" }.merge attributes
123
116
 
124
117
  ensure_message_id
@@ -126,16 +119,13 @@ module RSMP
126
119
 
127
120
  def ensure_message_id
128
121
  # if message id is empty, generate a new one
129
- @attributes["mId"] ||= SecureRandom.uuid()
122
+ @attributes["mId"] ||= Message.make_m_id
130
123
  end
131
124
 
132
- def validate sxl=nil
133
- schema = Message.get_schema(sxl)
134
- unless schema.valid? attributes
135
- errors = schema.validate attributes
136
- error_string = errors.map do |item|
137
- [item['data_pointer'],item['type'],item['details']].compact.join(' ')
138
- end.join(", ")
125
+ def validate schemas
126
+ errors = RSMP::Schemer.validate attributes, schemas
127
+ if errors
128
+ error_string = errors.compact.join(', ').strip
139
129
  raise SchemaError.new error_string
140
130
  end
141
131
  end
@@ -189,6 +179,14 @@ module RSMP
189
179
  end
190
180
  end
191
181
 
182
+ class AggregatedStatusRequest < Message
183
+ def initialize attributes = {}
184
+ super({
185
+ "type" => "AggregatedStatusRequest",
186
+ }.merge attributes)
187
+ end
188
+ end
189
+
192
190
  class Alarm < Message
193
191
  def initialize attributes = {}
194
192
  super({
@@ -197,6 +195,22 @@ module RSMP
197
195
  end
198
196
  end
199
197
 
198
+ class AlarmRequest < Message
199
+ def initialize attributes = {}
200
+ super({
201
+ "type" => "Alarm",
202
+ }.merge attributes)
203
+ end
204
+ end
205
+
206
+ class AlarmAcknowledged < Message
207
+ def initialize attributes = {}
208
+ super({
209
+ "type" => "Alarm",
210
+ }.merge attributes)
211
+ end
212
+ end
213
+
200
214
  class Watchdog < Message
201
215
  def initialize attributes = {}
202
216
  super({
data/lib/rsmp/node.rb CHANGED
@@ -1,23 +1,58 @@
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
8
- attr_reader :archive, :logger, :task
4
+ class Node
5
+ include Logging
6
+ include Wait
7
+ include Inspect
8
+
9
+ attr_reader :archive, :logger, :task, :deferred, :error_condition, :clock
9
10
 
10
11
  def initialize options
11
- super options
12
+ initialize_logging options
13
+ @task = options[:task]
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
24
+ end
25
+
26
+ def defer item
27
+ @deferred << item
28
+ end
29
+
30
+ def process_deferred
31
+ cloned = @deferred.clone # clone in case do_deferred restarts the current task
32
+ @deferred.clear
33
+ cloned.each do |item|
34
+ do_deferred item
35
+ end
36
+ end
37
+
38
+ def do_deferred item
39
+ end
40
+
41
+ def do_start task
42
+ task.annotate self.class.to_s
43
+ @task = task
44
+ start_action
45
+ idle
12
46
  end
13
47
 
14
48
  def start
15
49
  starting
16
- Async do |task|
17
- task.annotate self.class
18
- @task = task
19
- start_action
20
- idle
50
+ if @task
51
+ do_start @task
52
+ else
53
+ Async do |task|
54
+ do_start task
55
+ end
21
56
  end
22
57
  rescue Errno::EADDRINUSE => e
23
58
  log "Cannot start: #{e.to_s}", level: :error
@@ -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,24 +1,61 @@
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
5
- attr_reader :state, :archive, :connection_info, :sxl
6
+ class Proxy
7
+ WRAPPING_DELIMITER = "\f"
8
+
9
+ include Logging
10
+ include Wait
11
+ include Notifier
12
+ include Inspect
13
+
14
+ attr_reader :state, :archive, :connection_info, :sxl, :task, :collector
6
15
 
7
16
  def initialize options
8
- super options
17
+ initialize_logging options
9
18
  @settings = options[:settings]
10
19
  @task = options[:task]
11
20
  @socket = options[:socket]
12
21
  @ip = options[:ip]
22
+ @port = options[:port]
13
23
  @connection_info = options[:info]
14
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']
15
29
  clear
16
30
  end
17
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
+
18
54
  def run
19
55
  start
20
56
  @reader.wait if @reader
21
- stop
57
+ ensure
58
+ stop unless [:stopped, :stopping].include? @state
22
59
  end
23
60
 
24
61
  def ready?
@@ -69,7 +106,7 @@ module RSMP
69
106
  @reader = @task.async do |task|
70
107
  task.annotate "reader"
71
108
  @stream = Async::IO::Stream.new(@socket)
72
- @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
73
110
 
74
111
  while json = @protocol.read_line
75
112
  beginning = Time.now
@@ -97,33 +134,45 @@ module RSMP
97
134
  log "Connection reset by peer", level: :warning
98
135
  rescue Errno::EPIPE
99
136
  log "Broken pipe", level: :warning
100
- rescue SystemCallError => e # all ERRNO errors
101
- log "Proxy exception: #{e.to_s}", level: :error
102
137
  rescue StandardError => e
103
- log ["Proxy exception: #{e.inspect}",e.backtrace].flatten.join("\n"), level: :error
138
+ notify_error e, level: :internal
104
139
  end
105
140
  end
106
141
 
142
+ def notify_error e, options={}
143
+ node.notify_error e, options
144
+ end
145
+
107
146
  def start_watchdog
108
- log "Starting watchdog with interval #{@settings["watchdog_interval"]} seconds", level: :debug
147
+ log "Starting watchdog with interval #{@site_settings['intervals']['watchdog']} seconds", level: :debug
109
148
  send_watchdog
110
149
  @watchdog_started = true
111
150
  end
112
151
 
113
152
  def start_timer
114
153
  name = "timer"
115
- interval = @settings["timer_interval"] || 1
154
+ interval = @site_settings['intervals']['timer'] || 1
116
155
  log "Starting #{name} with interval #{interval} seconds", level: :debug
117
- @latest_watchdog_received = RSMP.now_object
156
+ @latest_watchdog_received = Clock.now
118
157
 
119
158
  @timer = @task.async do |task|
120
159
  task.annotate "timer"
121
160
  next_time = Time.now.to_f
122
161
  loop do
123
- now = RSMP.now_object
124
- break if timer(now) == false
125
- rescue StandardError => e
126
- log ["#{name} exception: #{e}",e.backtrace].flatten.join("\n"), level: :error
162
+ begin
163
+ now = Clock.now
164
+ timer(now)
165
+ rescue EOFError => e
166
+ log "Timer: Connection closed: #{e}", level: :warning
167
+ rescue IOError => e
168
+ log "Timer: IOError", level: :warning
169
+ rescue Errno::ECONNRESET
170
+ log "Timer: Connection reset by peer", level: :warning
171
+ rescue Errno::EPIPE => e
172
+ log "Timer: Broken pipe", level: :warning
173
+ rescue StandardError => e
174
+ notify_error e, level: :internal
175
+ end
127
176
  ensure
128
177
  next_time += interval
129
178
  duration = next_time - Time.now.to_f
@@ -133,14 +182,14 @@ module RSMP
133
182
  end
134
183
 
135
184
  def timer now
136
- check_watchdog_send_time now
137
- return false if check_ack_timeout now
138
- return false if check_watchdog_timeout now
185
+ watchdog_send_timer now
186
+ check_ack_timeout now
187
+ check_watchdog_timeout now
139
188
  end
140
189
 
141
- def check_watchdog_send_time now
190
+ def watchdog_send_timer now
142
191
  return unless @watchdog_started
143
- return if @settings["watchdog_interval"] == :never
192
+ return if @site_settings['intervals']['watchdog'] == :never
144
193
 
145
194
  if @latest_watchdog_send_at == nil
146
195
  send_watchdog now
@@ -148,47 +197,38 @@ module RSMP
148
197
  # we add half the timer interval to pick the timer
149
198
  # event closes to the wanted wathcdog interval
150
199
  diff = now - @latest_watchdog_send_at
151
- if (diff + 0.5*@settings["timer_interval"]) >= (@settings["watchdog_interval"])
200
+ if (diff + 0.5*@site_settings['intervals']['timer']) >= (@site_settings['intervals']['watchdog'])
152
201
  send_watchdog now
153
202
  end
154
203
  end
155
- rescue StandardError => e
156
- log ["Watchdog error: #{e}",e.backtrace].flatten.join("\n"), level: :error
157
204
  end
158
205
 
159
- def send_watchdog now=nil
160
- now = RSMP.now_object unless nil
161
- 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})
162
208
  send_message message
163
209
  @latest_watchdog_send_at = now
164
210
  end
165
211
 
166
212
  def check_ack_timeout now
167
- timeout = @settings["acknowledgement_timeout"]
213
+ timeout = @site_settings['timeouts']['acknowledgement']
168
214
  # hash cannot be modify during iteration, so clone it
169
215
  @awaiting_acknowledgement.clone.each_pair do |m_id, message|
170
216
  latest = message.timestamp + timeout
171
217
  if now > latest
172
218
  log "No acknowledgements for #{message.type} #{message.m_id_short} within #{timeout} seconds", level: :error
173
219
  stop
174
- return true
175
220
  end
176
221
  end
177
- false
178
222
  end
179
223
 
180
224
  def check_watchdog_timeout now
181
-
182
- timeout = @settings["watchdog_timeout"]
225
+ timeout = @site_settings['timeouts']['watchdog']
183
226
  latest = @latest_watchdog_received + timeout
184
227
  left = latest - now
185
- #log "Check watchdog, time:#{timeout}, last:#{@latest_watchdog_received}, now: #{now}, latest:#{latest}, left #{left}, fail:#{left<0}", level: :debug
186
228
  if left < 0
187
229
  log "No Watchdog within #{timeout} seconds", level: :error
188
230
  stop
189
- return true
190
231
  end
191
- false
192
232
  end
193
233
 
194
234
  def stop_tasks
@@ -200,23 +240,36 @@ module RSMP
200
240
  super str, options.merge(ip: @ip, port: @port, site_id: @site_id)
201
241
  end
202
242
 
203
- 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
204
254
  raise IOError unless @protocol
205
- message.generate_json
206
- message.validate sxl
207
255
  message.direction = :out
256
+ message.generate_json
257
+ message.validate get_schemas unless validate==false
208
258
  expect_acknowledgement message
209
259
  @protocol.write_lines message.json
260
+ notify message
210
261
  log_send message, reason
211
262
  rescue EOFError, IOError
212
263
  buffer_message message
213
264
  rescue SchemaError => e
214
- 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}")
215
268
  end
216
269
 
217
270
  def buffer_message message
218
271
  # TODO
219
- log "Cannot send #{message.type} because the connection is closed.", message: message, level: :error
272
+ #log "Cannot send #{message.type} because the connection is closed.", message: message, level: :error
220
273
  end
221
274
 
222
275
  def log_send message, reason=nil
@@ -236,25 +289,38 @@ module RSMP
236
289
  def process_packet json
237
290
  attributes = Message.parse_attributes json
238
291
  message = Message.build attributes, json
239
- message.validate sxl
292
+ message.validate get_schemas
293
+ notify message
240
294
  expect_version_message(message) unless @version_determined
241
295
  process_message message
296
+ process_deferred
242
297
  message
243
298
  rescue InvalidPacket => e
244
- warning "Received invalid package, must be valid JSON but got #{json.size} bytes: #{e.message}"
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
245
302
  nil
246
303
  rescue MalformedMessage => e
247
- warning "Received malformed message, #{e.message}", Malformed.new(attributes)
304
+ str = "Received malformed message, #{e.message}"
305
+ notify_error e.exception(str)
306
+ log str, message: Malformed.new(attributes), level: :warning
248
307
  # cannot send NotAcknowledged for a malformed message since we can't read it, just ignore it
249
308
  nil
250
309
  rescue SchemaError => e
251
- 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
252
314
  message
253
315
  rescue InvalidMessage => e
254
- 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
255
319
  message
256
320
  rescue FatalError => e
257
- 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}"
258
324
  stop
259
325
  message
260
326
  end
@@ -294,13 +360,20 @@ module RSMP
294
360
  dont_acknowledge message, "Received", "extraneous Version message"
295
361
  end
296
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
+
297
369
  def check_rsmp_version message
370
+ versions = rsmp_versions
298
371
  # find versions that both we and the client support
299
- candidates = message.versions & @settings["rsmp_versions"]
372
+ candidates = message.versions & versions
300
373
  if candidates.any?
301
- @rsmp_version = candidates.sort.last # pick latest version
374
+ @rsmp_version = candidates.sort_by { |v| Gem::Version.new(v) }.last # pick latest version
302
375
  else
303
- 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."
304
377
  end
305
378
  end
306
379
 
@@ -335,14 +408,24 @@ module RSMP
335
408
  def wait_for_state state, timeout
336
409
  states = [state].flatten
337
410
  return if states.include?(@state)
338
- RSMP::Wait.wait_for(@task,@state_condition,timeout) do |s|
411
+ wait_for(@state_condition,timeout) do
339
412
  states.include?(@state)
340
413
  end
341
414
  @state
415
+ rescue Async::TimeoutError
416
+ raise RSMP::TimeoutError.new "Did not reach state #{state} within #{timeout}s"
342
417
  end
343
418
 
344
419
  def send_version site_id, rsmp_versions
345
- 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
+
346
429
  site_id_array = [site_id].flatten.map {|id| {"sId" => id} }
347
430
 
348
431
  version_response = Version.new({
@@ -428,7 +511,7 @@ module RSMP
428
511
 
429
512
  def process_watchdog message
430
513
  log "Received #{message.type}", message: message, level: :log
431
- @latest_watchdog_received = RSMP.now_object
514
+ @latest_watchdog_received = Clock.now
432
515
  acknowledge message
433
516
  end
434
517
 
@@ -445,27 +528,16 @@ module RSMP
445
528
  def version_acknowledged
446
529
  end
447
530
 
448
- def wait_for_acknowledgement original, timeout, options={}
449
- raise ArgumentError unless original
450
- RSMP::Wait.wait_for(@task,@acknowledgement_condition,timeout) do |message|
451
- message.is_a?(MessageAck) &&
452
- message.attributes["oMId"] == original.m_id
453
- end
454
- end
455
-
456
- def wait_for_not_acknowledged original, timeout
531
+ def wait_for_acknowledgement original, timeout
457
532
  raise ArgumentError unless original
458
- RSMP::Wait.wait_for(@task,@acknowledgement_condition,timeout) do |message|
459
- message.is_a?(MessageNotAck) &&
460
- message.attributes["oMId"] == original.m_id
461
- end
462
- end
463
-
464
- def wait_for_acknowledgements timeout
465
- return if @awaiting_acknowledgement.empty?
466
- RSMP::Wait.wait_for(@task,@acknowledgement_condition,timeout) do |message|
467
- @awaiting_acknowledgement.empty?
533
+ wait_for(@acknowledgement_condition,timeout) do |message|
534
+ if message.is_a?(MessageNotAck) && message.attributes["oMId"] == original.m_id
535
+ raise RSMP::MessageRejected.new(message.attributes['rea'])
536
+ end
537
+ message.is_a?(MessageAck) && message.attributes["oMId"] == original.m_id
468
538
  end
539
+ rescue Async::TimeoutError
540
+ raise RSMP::TimeoutError.new("Acknowledgement for #{original.type} #{original.m_id} not received within #{timeout}s")
469
541
  end
470
542
 
471
543
  def node