rsmp 0.1.12 → 0.1.27

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
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
@@ -43,4 +43,13 @@ module RSMP
43
43
 
44
44
  class UnknownComponent < Error
45
45
  end
46
+
47
+ class UnknownCommand < Error
48
+ end
49
+
50
+ class UnknownStatus < Error
51
+ end
52
+
53
+ class ConfigurationError < Error
54
+ end
46
55
  end
@@ -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
+ initialize_distributor
26
+
27
+ prepare_collection options[:settings]['collect']
28
+
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,13 +134,15 @@ 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
147
  log "Starting watchdog with interval #{@settings["watchdog_interval"]} seconds", level: :debug
109
148
  send_watchdog
@@ -114,27 +153,41 @@ module RSMP
114
153
  name = "timer"
115
154
  interval = @settings["timer_interval"] || 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
157
+
118
158
  @timer = @task.async do |task|
119
159
  task.annotate "timer"
160
+ next_time = Time.now.to_f
120
161
  loop do
121
- now = RSMP.now_object
122
- break if timer(now) == false
123
- rescue StandardError => e
124
- 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
125
176
  ensure
126
- task.sleep interval
177
+ next_time += interval
178
+ duration = next_time - Time.now.to_f
179
+ task.sleep duration
127
180
  end
128
181
  end
129
182
  end
130
183
 
131
184
  def timer now
132
- check_watchdog_send_time now
133
- return false if check_ack_timeout now
134
- return false if check_watchdog_timeout now
185
+ watchdog_send_timer now
186
+ check_ack_timeout now
187
+ check_watchdog_timeout now
135
188
  end
136
189
 
137
- def check_watchdog_send_time now
190
+ def watchdog_send_timer now
138
191
  return unless @watchdog_started
139
192
  return if @settings["watchdog_interval"] == :never
140
193
 
@@ -148,13 +201,10 @@ module RSMP
148
201
  send_watchdog now
149
202
  end
150
203
  end
151
- rescue StandardError => e
152
- log ["Watchdog error: #{e}",e.backtrace].flatten.join("\n"), level: :error
153
204
  end
154
205
 
155
- def send_watchdog now=nil
156
- now = RSMP.now_object unless nil
157
- 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})
158
208
  send_message message
159
209
  @latest_watchdog_send_at = now
160
210
  end
@@ -167,24 +217,18 @@ module RSMP
167
217
  if now > latest
168
218
  log "No acknowledgements for #{message.type} #{message.m_id_short} within #{timeout} seconds", level: :error
169
219
  stop
170
- return true
171
220
  end
172
221
  end
173
- false
174
222
  end
175
223
 
176
224
  def check_watchdog_timeout now
177
-
178
225
  timeout = @settings["watchdog_timeout"]
179
226
  latest = @latest_watchdog_received + timeout
180
227
  left = latest - now
181
- #log "Check watchdog, time:#{timeout}, last:#{@latest_watchdog_received}, now: #{now}, latest:#{latest}, left #{left}, fail:#{left<0}", level: :debug
182
228
  if left < 0
183
229
  log "No Watchdog within #{timeout} seconds", level: :error
184
230
  stop
185
- return true
186
231
  end
187
- false
188
232
  end
189
233
 
190
234
  def stop_tasks
@@ -196,23 +240,36 @@ module RSMP
196
240
  super str, options.merge(ip: @ip, port: @port, site_id: @site_id)
197
241
  end
198
242
 
199
- 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
200
254
  raise IOError unless @protocol
201
- message.generate_json
202
- message.validate sxl
203
255
  message.direction = :out
256
+ message.generate_json
257
+ message.validate get_schemas unless validate==false
204
258
  expect_acknowledgement message
205
259
  @protocol.write_lines message.json
260
+ notify message
206
261
  log_send message, reason
207
262
  rescue EOFError, IOError
208
263
  buffer_message message
209
264
  rescue SchemaError => e
210
- 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}")
211
268
  end
212
269
 
213
270
  def buffer_message message
214
271
  # TODO
215
- 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
216
273
  end
217
274
 
218
275
  def log_send message, reason=nil
@@ -232,25 +289,38 @@ module RSMP
232
289
  def process_packet json
233
290
  attributes = Message.parse_attributes json
234
291
  message = Message.build attributes, json
235
- message.validate sxl
292
+ message.validate get_schemas
293
+ notify message
236
294
  expect_version_message(message) unless @version_determined
237
295
  process_message message
296
+ process_deferred
238
297
  message
239
298
  rescue InvalidPacket => e
240
- 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
241
302
  nil
242
303
  rescue MalformedMessage => e
243
- 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
244
307
  # cannot send NotAcknowledged for a malformed message since we can't read it, just ignore it
245
308
  nil
246
309
  rescue SchemaError => e
247
- 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
248
314
  message
249
315
  rescue InvalidMessage => e
250
- 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
251
319
  message
252
320
  rescue FatalError => e
253
- 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}"
254
324
  stop
255
325
  message
256
326
  end
@@ -294,7 +364,7 @@ module RSMP
294
364
  # find versions that both we and the client support
295
365
  candidates = message.versions & @settings["rsmp_versions"]
296
366
  if candidates.any?
297
- @rsmp_version = candidates.sort.last # pick latest version
367
+ @rsmp_version = candidates.sort_by { |v| Gem::Version.new(v) }.last # pick latest version
298
368
  else
299
369
  raise FatalError.new "RSMP versions [#{message.versions.join(',')}] requested, but only [#{@settings["rsmp_versions"].join(',')}] supported."
300
370
  end
@@ -331,10 +401,12 @@ module RSMP
331
401
  def wait_for_state state, timeout
332
402
  states = [state].flatten
333
403
  return if states.include?(@state)
334
- RSMP::Wait.wait_for(@task,@state_condition,timeout) do |s|
404
+ wait_for(@state_condition,timeout) do
335
405
  states.include?(@state)
336
406
  end
337
407
  @state
408
+ rescue Async::TimeoutError
409
+ raise RSMP::TimeoutError.new "Did not reach state #{state} within #{timeout}s"
338
410
  end
339
411
 
340
412
  def send_version site_id, rsmp_versions
@@ -424,7 +496,7 @@ module RSMP
424
496
 
425
497
  def process_watchdog message
426
498
  log "Received #{message.type}", message: message, level: :log
427
- @latest_watchdog_received = RSMP.now_object
499
+ @latest_watchdog_received = Clock.now
428
500
  acknowledge message
429
501
  end
430
502
 
@@ -441,27 +513,16 @@ module RSMP
441
513
  def version_acknowledged
442
514
  end
443
515
 
444
- def wait_for_acknowledgement original, timeout, options={}
516
+ def wait_for_acknowledgement original, timeout
445
517
  raise ArgumentError unless original
446
- RSMP::Wait.wait_for(@task,@acknowledgement_condition,timeout) do |message|
447
- message.is_a?(MessageAck) &&
448
- message.attributes["oMId"] == original.m_id
449
- end
450
- end
451
-
452
- def wait_for_not_acknowledged original, timeout
453
- raise ArgumentError unless original
454
- RSMP::Wait.wait_for(@task,@acknowledgement_condition,timeout) do |message|
455
- message.is_a?(MessageNotAck) &&
456
- message.attributes["oMId"] == original.m_id
457
- end
458
- end
459
-
460
- def wait_for_acknowledgements timeout
461
- return if @awaiting_acknowledgement.empty?
462
- RSMP::Wait.wait_for(@task,@acknowledgement_condition,timeout) do |message|
463
- @awaiting_acknowledgement.empty?
518
+ wait_for(@acknowledgement_condition,timeout) do |message|
519
+ if message.is_a?(MessageNotAck) && message.attributes["oMId"] == original.m_id
520
+ raise RSMP::MessageRejected.new(message.attributes['rea'])
521
+ end
522
+ message.is_a?(MessageAck) && message.attributes["oMId"] == original.m_id
464
523
  end
524
+ rescue Async::TimeoutError
525
+ raise RSMP::TimeoutError.new("Acknowledgement for #{original.type} #{original.m_id} not received within #{timeout}s")
465
526
  end
466
527
 
467
528
  def node