rsmp 0.29.0 → 0.32.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,13 +1,13 @@
1
1
  module RSMP
2
2
  # Base class for waiting for specific status or command responses, specified by
3
- # a list of queries. Queries are defined as an array of hashes, e.g
3
+ # a list of matchers. Matchers are defined as an array of hashes, e.g
4
4
  # [
5
5
  # {"cCI"=>"M0104", "cO"=>"setDate", "n"=>"securityCode", "v"=>"1111"},
6
6
  # {"cCI"=>"M0104", "cO"=>"setDate", "n"=>"year", "v"=>"2020"},
7
7
  # {"cCI"=>"M0104", "cO"=>"setDate", "n"=>"month", "v"=>/\d+/}
8
8
  # ]
9
9
  #
10
- # Note that queries can contain regex patterns for values, like /\d+/ in the example above.
10
+ # Note that matchers can contain regex patterns for values, like /\d+/ in the example above.
11
11
  #
12
12
  # When an input messages is received it typically contains several items, eg:
13
13
  # [
@@ -16,9 +16,9 @@ module RSMP
16
16
  # {"cCI"=>"M0104", "n"=>"hour", "v"=>"17", "age"=>"recent"}
17
17
  # ]
18
18
  #
19
- # Each input item is matched against each of the queries.
20
- # If a match is found, it's stored in the @results hash, with the query as the key,
21
- # and a mesage and status as the key. In the example above, this query:
19
+ # Each input item is matched against each of the matchers.
20
+ # If a match is found, it's stored in the @results hash, with the matcher as the key,
21
+ # and a mesage and status as the key. In the example above, this matcher:
22
22
  #
23
23
  # {"cCI"=>"M0104", "cO"=>"setDate", "n"=>"month", "v"=>/\d+/}
24
24
  #
@@ -32,101 +32,101 @@ module RSMP
32
32
  # { <StatusResponse message>, {"cCI"=>"M0104", "cO"=>"setDate", "n"=>"month", "v"=>"9"} }
33
33
  # }
34
34
  class StateCollector < Collector
35
- attr_reader :queries
35
+ attr_reader :matchers
36
36
 
37
37
  # Initialize with a list of wanted statuses
38
38
  def initialize proxy, want, options={}
39
39
  raise ArgumentError.new("num option cannot be used") if options[:num]
40
- super proxy, options.merge( ingoing: true, outgoing: false)
41
- @queries = want.map { |item| build_query item }
40
+ super proxy, options
41
+ @matchers = want.map { |item| build_matcher item }
42
42
  end
43
43
 
44
- # Build a query object.
45
- # Sub-classes should override to use their own query classes.
46
- def build_query want
47
- Query.new want
44
+ # Build a matcher object.
45
+ # Sub-classes should override to use their own matcher classes.
46
+ def build_matcher want
47
+ Matcher.new want
48
48
  end
49
49
 
50
50
  # Get a results
51
- def query_result want
52
- query = @queries.find { |q| q.want == want}
53
- raise unless query
54
- query.got
51
+ def matcher_result want
52
+ matcher = @matchers.find { |q| q.want == want}
53
+ raise unless matcher
54
+ matcher.got
55
55
  end
56
56
 
57
- # Get an array of the last item received for each query
57
+ # Get an array of the last item received for each matcher
58
58
  def reached
59
- @queries.map { |query| query.got }.compact
59
+ @matchers.map { |matcher| matcher.got }.compact
60
60
  end
61
61
 
62
62
  # Get messages from results
63
63
  def messages
64
- @queries.map { |query| query.message }.uniq.compact
64
+ @matchers.map { |matcher| matcher.message }.uniq.compact
65
65
  end
66
66
 
67
- # Return progress as completes queries vs. total number of queries
67
+ # Return progress as completes matchers vs. total number of matchers
68
68
  def progress
69
- need = @queries.size
70
- reached = @queries.count { |query| query.done? }
69
+ need = @matchers.size
70
+ reached = @matchers.count { |matcher| matcher.done? }
71
71
  { need: need, reached: reached }
72
72
  end
73
73
 
74
- # Are there queries left to type_match?
74
+ # Are there matchers left to type_match?
75
75
  def done?
76
- @queries.all? { |query| query.done? }
76
+ @matchers.all? { |matcher| matcher.done? }
77
77
  end
78
78
 
79
- # Get a simplified hash of queries, with values set to either true or false,
80
- # indicating which queries have been matched.
81
- def query_status
82
- @queries.map { |query| [query.want, query.done?] }.to_h
79
+ # Get a simplified hash of matchers, with values set to either true or false,
80
+ # indicating which matchers have been matched.
81
+ def matcher_status
82
+ @matchers.map { |matcher| [matcher.want, matcher.done?] }.to_h
83
83
  end
84
84
 
85
- # Get a simply array of bools, showing which queries have been matched.
85
+ # Get a simply array of bools, showing which matchers have been matched.
86
86
  def summary
87
- @queries.map { |query| query.done? }
87
+ @matchers.map { |matcher| matcher.done? }
88
88
  end
89
89
 
90
90
  # Check if a messages matches our criteria.
91
- # Match each query against each item in the message
91
+ # Match each matcher against each item in the message
92
92
  def perform_match message
93
93
  return false if super(message) == false
94
94
  return unless collecting?
95
- @queries.each do |query| # look through queries
95
+ @matchers.each do |matcher| # look through matchers
96
96
  get_items(message).each do |item| # look through items in message
97
- matched = query.perform_match(item,message,@block)
97
+ matched = matcher.perform_match(item,message,@block)
98
98
  return unless collecting?
99
99
  if matched != nil
100
- #type = {true=>'match',false=>'mismatch'}[matched]
101
- #@notifier.log "#{@title.capitalize} #{message.m_id_short} collect #{type} #{query.want}, item #{item}", level: :debug
100
+ type = {true=>'match',false=>'mismatch'}[matched]
101
+ @distributor.log "#{@title.capitalize} #{message.m_id_short} collect #{type} #{matcher.want}, item #{item}", level: :debug
102
102
  if matched == true
103
- query.keep message, item
103
+ matcher.keep message, item
104
104
  elsif matched == false
105
- query.forget
105
+ matcher.forget
106
106
  end
107
107
  end
108
108
  end
109
109
  end
110
110
  end
111
111
 
112
- # don't collect anything. Query will collect them instead
112
+ # don't collect anything. Matcher will collect them instead
113
113
  def keep message
114
114
  end
115
115
 
116
116
  def describe
117
- @queries.map {|q| q.want.to_s }
117
+ @matchers.map {|q| q.want.to_s }
118
118
  end
119
119
 
120
120
  # return a string that describes the attributes that we're looking for
121
- def describe_query
122
- "#{super} matching #{query_want_hash.to_s}"
121
+ def describe_matcher
122
+ "#{super} matching #{matcher_want_hash.to_s}"
123
123
  end
124
124
 
125
- # return a hash that describe the status of all queries
125
+ # return a hash that describe the status of all matchers
126
126
  def progress_hash
127
127
  h = {}
128
- @queries.each do |query|
129
- want = query.want
128
+ @matchers.each do |matcher|
129
+ want = matcher.want
130
130
  if want['cCI']
131
131
  cCI = want['cCI']
132
132
  h[cCI] ||= {}
@@ -140,8 +140,8 @@ module RSMP
140
140
  h[sCI] ||= {}
141
141
  n = want['n']
142
142
  s = want['s']
143
- if query.got && query.got['s']
144
- h[sCI][n] = { {s=>query.got['s']} => query.done? }
143
+ if matcher.got && matcher.got['s']
144
+ h[sCI][n] = { {s=>matcher.got['s']} => matcher.done? }
145
145
  else
146
146
  h[sCI][n] = { s=>nil }
147
147
  end
@@ -152,15 +152,15 @@ module RSMP
152
152
 
153
153
  # return a string that describe how many many messages have been collected
154
154
  def describe_progress
155
- num_queries = @queries.size
156
- num_matched = @queries.count { |query| query.done? }
157
- ".. Matched #{num_matched}/#{num_queries} with #{progress_hash.to_s}"
155
+ num_matchers = @matchers.size
156
+ num_matched = @matchers.count { |matcher| matcher.done? }
157
+ ".. Matched #{num_matched}/#{num_matchers} with #{progress_hash.to_s}"
158
158
  end
159
159
 
160
- def query_want_hash
160
+ def matcher_want_hash
161
161
  h = {}
162
- @queries.each do |query|
163
- item = query.want
162
+ @matchers.each do |matcher|
163
+ item = matcher.want
164
164
  if item['cCI']
165
165
  cCI = item['cCI']
166
166
  h[cCI] ||= {}
@@ -181,11 +181,11 @@ module RSMP
181
181
  end
182
182
 
183
183
  # return a hash that describe the end result
184
- def query_got_hash
184
+ def matcher_got_hash
185
185
  h = {}
186
- @queries.each do |query|
187
- want = query.want
188
- got = query.got
186
+ @matchers.each do |matcher|
187
+ want = matcher.want
188
+ got = matcher.got
189
189
  if want['cCI']
190
190
  cCI = want['cCI']
191
191
  h[cCI] ||= {}
@@ -207,7 +207,7 @@ module RSMP
207
207
 
208
208
  # log when we end collecting
209
209
  def log_complete
210
- @notifier.log "#{identifier}: Completed with #{query_got_hash.to_s}", level: :collect
210
+ @distributor.log "#{identifier}: Completed with #{matcher_got_hash.to_s}", level: :collect
211
211
  end
212
212
  end
213
213
  end
@@ -2,15 +2,18 @@ module RSMP
2
2
  # Base class for waiting for status updates or responses
3
3
  class StatusCollector < StateCollector
4
4
  def initialize proxy, want, options={}
5
- super proxy, want, options.merge(title: 'status response')
5
+ type = []
6
+ type << 'StatusUpdate' unless options[:updates] == false
7
+ type << 'StatusResponse' unless options[:reponses] == false
6
8
 
7
- @options[:type] ||= []
8
- @options[:type] << 'StatusUpdate' unless options[:updates] == false
9
- @options[:type] << 'StatusResponse' unless options[:reponses] == false
9
+ super proxy, want, options.merge(
10
+ title: 'status response',
11
+ filter: RSMP::Filter.new(ingoing: true, outgoing: false, type: type)
12
+ )
10
13
  end
11
14
 
12
- def build_query want
13
- RSMP::StatusQuery.new want
15
+ def build_matcher want
16
+ RSMP::StatusMatcher.new want
14
17
  end
15
18
 
16
19
  # Get items, in our case status values
@@ -1,7 +1,7 @@
1
1
  module RSMP
2
2
  # Match a specific status response or update
3
- class StatusQuery < Query
4
- # Match a status value against a query
3
+ class StatusMatcher < Matcher
4
+ # Match a status value against a matcher
5
5
  def match? item
6
6
  return nil if @want['sCI'] && @want['sCI'] != item['sCI']
7
7
  return nil if @want['cO'] && @want['cO'] != item['cO']
@@ -56,5 +56,9 @@ module RSMP
56
56
  log "Deactivating alarm #{alarm_code}", level: :info
57
57
  @node.alarm_activated_or_deactivated alarm
58
58
  end
59
+
60
+ def status_updates_sent
61
+ end
62
+
59
63
  end
60
64
  end
data/lib/rsmp/node.rb CHANGED
@@ -36,7 +36,7 @@ module RSMP
36
36
  @ignore_errors = was
37
37
  end
38
38
 
39
- def notify_error e, options={}
39
+ def distribute_error e, options={}
40
40
  return if @ignore_errors.find { |klass| e.is_a? klass }
41
41
  if options[:level] == :internal
42
42
  log ["#{e.to_s} in task: #{Async::Task.current.to_s}",e.backtrace].flatten.join("\n"), level: :error
data/lib/rsmp/proxy.rb CHANGED
@@ -9,7 +9,7 @@ module RSMP
9
9
  WRAPPING_DELIMITER = "\f"
10
10
 
11
11
  include Logging
12
- include Notifier
12
+ include Distributor
13
13
  include Inspect
14
14
  include Task
15
15
 
@@ -47,7 +47,7 @@ module RSMP
47
47
  close_socket
48
48
  stop_reader
49
49
  set_state :disconnected
50
- notify_error DisconnectError.new("Connection was closed")
50
+ distribute_error DisconnectError.new("Connection was closed")
51
51
 
52
52
  # stop timer
53
53
  # as we're running inside the timer, code after stop_timer() will not be called,
@@ -188,7 +188,7 @@ module RSMP
188
188
  rescue Errno::EPIPE
189
189
  log "Broken pipe", level: :warning
190
190
  rescue StandardError => e
191
- notify_error e, level: :internal
191
+ distribute_error e, level: :internal
192
192
  end
193
193
 
194
194
  def read_line
@@ -213,8 +213,8 @@ module RSMP
213
213
  log str, level: :statistics
214
214
  end
215
215
 
216
- def notify_error e, options={}
217
- @node.notify_error e, options
216
+ def receive_error e, options={}
217
+ @node.receive_error e, options
218
218
  end
219
219
 
220
220
  def start_watchdog
@@ -265,7 +265,7 @@ module RSMP
265
265
  rescue Errno::EPIPE => e
266
266
  log "Timer: Broken pipe", level: :warning
267
267
  rescue StandardError => e
268
- notify_error e, level: :internal
268
+ distribute_error e, level: :internal
269
269
  end
270
270
  ensure
271
271
  next_time += interval
@@ -312,7 +312,7 @@ module RSMP
312
312
  begin
313
313
  close
314
314
  ensure
315
- notify_error MissingAcknowledgment.new(str)
315
+ distribute_error MissingAcknowledgment.new(str)
316
316
  end
317
317
  end
318
318
  end
@@ -325,7 +325,7 @@ module RSMP
325
325
  if left < 0
326
326
  str = "No Watchdog received within #{timeout} seconds"
327
327
  log str, level: :warning
328
- notify MissingWatchdog.new(str)
328
+ distribute MissingWatchdog.new(str)
329
329
  end
330
330
  end
331
331
 
@@ -340,22 +340,22 @@ module RSMP
340
340
  schemas
341
341
  end
342
342
 
343
- def send_message message, reason=nil, validate: true
344
- raise NotReady unless connected?
343
+ def send_message message, reason=nil, validate: true, force: false
344
+ raise NotReady unless connected? unless force
345
345
  raise IOError unless @protocol
346
346
  message.direction = :out
347
347
  message.generate_json
348
348
  message.validate get_schemas unless validate==false
349
349
  @protocol.write_lines message.json
350
350
  expect_acknowledgement message
351
- notify message
351
+ distribute message
352
352
  log_send message, reason
353
353
  rescue EOFError, IOError
354
354
  buffer_message message
355
355
  rescue SchemaError, RSMP::Schema::Error => e
356
356
  str = "Could not send #{message.type} because schema validation failed: #{e.message}"
357
357
  log str, message: message, level: :error
358
- notify_error e.exception("#{str} #{message.json}")
358
+ distribute_error e.exception("#{str} #{message.json}")
359
359
  end
360
360
 
361
361
  def buffer_message message
@@ -398,39 +398,39 @@ module RSMP
398
398
  message = Message.build attributes, json
399
399
  message.validate(get_schemas) if should_validate_ingoing_message?(message)
400
400
  verify_sequence message
401
- deferred_notify do
402
- notify message
401
+ with_deferred_distribution do
402
+ distribute message
403
403
  process_message message
404
404
  end
405
405
  process_deferred
406
406
  message
407
407
  rescue InvalidPacket => e
408
408
  str = "Received invalid package, must be valid JSON but got #{json.size} bytes: #{e.message}"
409
- notify_error e.exception(str)
409
+ distribute_error e.exception(str)
410
410
  log str, level: :warning
411
411
  nil
412
412
  rescue MalformedMessage => e
413
413
  str = "Received malformed message, #{e.message}"
414
- notify_error e.exception(str)
414
+ distribute_error e.exception(str)
415
415
  log str, message: Malformed.new(attributes), level: :warning
416
416
  # cannot send NotAcknowledged for a malformed message since we can't read it, just ignore it
417
417
  nil
418
418
  rescue SchemaError, RSMP::Schema::Error => e
419
419
  reason = "schema errors: #{e.message}"
420
420
  str = "Received invalid #{message.type}"
421
- notify_error e.exception(str), message: message
421
+ distribute_error e.exception(str), message: message
422
422
  dont_acknowledge message, str, reason
423
423
  message
424
424
  rescue InvalidMessage => e
425
425
  reason = "#{e.message}"
426
426
  str = "Received invalid #{message.type},"
427
- notify_error e.exception("#{str} #{message.json}"), message: message
427
+ distribute_error e.exception("#{str} #{message.json}"), message: message
428
428
  dont_acknowledge message, str, reason
429
429
  message
430
430
  rescue FatalError => e
431
431
  reason = e.message
432
432
  str = "Rejected #{message.type},"
433
- notify_error e.exception(str), message: message
433
+ distribute_error e.exception(str), message: message
434
434
  dont_acknowledge message, str, reason
435
435
  close
436
436
  message
@@ -486,7 +486,9 @@ module RSMP
486
486
  if candidates.any?
487
487
  @core_version = candidates.sort_by { |v| Gem::Version.new(v) }.last # pick latest version
488
488
  else
489
- raise HandshakeError.new "RSMP versions [#{message.versions.join(',')}] requested, but only [#{versions.join(',')}] supported."
489
+ reason = "RSMP versions [#{message.versions.join(',')}] requested, but only [#{versions.join(',')}] supported."
490
+ dont_acknowledge message, "Version message rejected", reason, force: true
491
+ raise HandshakeError.new reason
490
492
  end
491
493
  end
492
494
 
@@ -501,7 +503,7 @@ module RSMP
501
503
  check_ingoing_acknowledged original
502
504
  end
503
505
 
504
- def dont_acknowledge original, prefix=nil, reason=nil
506
+ def dont_acknowledge original, prefix=nil, reason=nil, force: true
505
507
  raise InvalidArgument unless original
506
508
  str = [prefix,reason].join(' ')
507
509
  log str, message: original, level: :warning if reason
@@ -510,7 +512,7 @@ module RSMP
510
512
  "rea" => reason || "Unknown reason"
511
513
  })
512
514
  message.original = original.clone
513
- send_message message, "for #{original.type} #{original.m_id_short}"
515
+ send_message message, "for #{original.type} #{original.m_id_short}", force: force
514
516
  end
515
517
 
516
518
  def wait_for_state state, timeout:
data/lib/rsmp/site.rb CHANGED
@@ -58,6 +58,7 @@ module RSMP
58
58
 
59
59
  @site_settings = defaults.deep_merge options[:site_settings]
60
60
  check_sxl_version
61
+ check_core_versions
61
62
  setup_components @site_settings['components']
62
63
  end
63
64
 
@@ -67,10 +68,49 @@ module RSMP
67
68
  RSMP::Schema::find_schema! sxl, version, lenient: true
68
69
  end
69
70
 
71
+ def check_core_versions
72
+ return if @site_settings['core_versions'] == 'all'
73
+ requested = [@site_settings['core_versions']].flatten
74
+ invalid = requested - RSMP::Schema::core_versions
75
+ if invalid.any?
76
+ if invalid.size == 1
77
+ error_str = "Unknown core version: #{invalid.first}"
78
+ else
79
+ error_str = "Unknown core versions: [#{invalid.join(' ')}]"
80
+ end
81
+
82
+ raise RSMP::ConfigurationError.new(error_str)
83
+ end
84
+ end
85
+
86
+ def site_type_name
87
+ "site"
88
+ end
89
+
90
+ def log_site_starting
91
+ log "Starting #{site_type_name} #{@site_settings["site_id"]}", level: :info, timestamp: @clock.now
92
+
93
+ sxl = "Using #{@site_settings["sxl"]} sxl #{@site_settings["sxl_version"]}"
94
+
95
+ versions = @site_settings["core_versions"]
96
+ if versions.is_a?(Array) && versions.size == 1
97
+ versions = versions.first
98
+ end
99
+ if versions == 'all'
100
+ core = "accepting all core versions [#{RSMP::Schema.core_versions.join(', ')}]"
101
+ else
102
+ if versions.is_a?(String)
103
+ core = "accepting only core version #{versions}"
104
+ else
105
+ core = "accepting core versions [#{versions.join(', ')}]"
106
+ end
107
+ end
108
+
109
+ log "#{sxl}, #{core}", level: :info, timestamp: @clock.now
110
+ end
111
+
70
112
  def run
71
- log "Starting site #{@site_settings["site_id"]}",
72
- level: :info,
73
- timestamp: @clock.now
113
+ log_site_starting
74
114
  @proxies.each { |proxy| proxy.start }
75
115
  @proxies.each { |proxy| proxy.wait }
76
116
  end
@@ -24,7 +24,7 @@ module RSMP
24
24
  rescue RSMP::ConnectionError => e
25
25
  log e, level: :error
26
26
  rescue StandardError => e
27
- notify_error e, level: :internal
27
+ distribute_error e, level: :internal
28
28
  ensure
29
29
  close
30
30
  end
@@ -77,7 +77,7 @@ module RSMP
77
77
  rescue RSMP::RepeatedAlarmError, RSMP::RepeatedStatusError, RSMP::TimestampError => e
78
78
  str = "Rejected #{message.type} message,"
79
79
  dont_acknowledge message, str, "#{e}"
80
- notify_error e.exception("#{str}#{e.message} #{message.json}")
80
+ distribute_error e.exception("#{str}#{e.message} #{message.json}")
81
81
  end
82
82
 
83
83
  def process_command_response message
@@ -367,8 +367,8 @@ module RSMP
367
367
  end
368
368
  end
369
369
 
370
- def notify_error e, options={}
371
- @supervisor.notify_error e, options if @supervisor
370
+ def receive_error e, options={}
371
+ @supervisor.receive_error e, options if @supervisor
372
372
  distribute_error e, options
373
373
  end
374
374
 
@@ -69,11 +69,11 @@ module RSMP
69
69
  tasks = @endpoint.accept do |socket| # creates async tasks
70
70
  handle_connection(socket)
71
71
  rescue StandardError => e
72
- notify_error e, level: :internal
72
+ distribute_error e, level: :internal
73
73
  end
74
74
  tasks.each { |task| task.wait }
75
75
  rescue StandardError => e
76
- notify_error e, level: :internal
76
+ distribute_error e, level: :internal
77
77
  end
78
78
 
79
79
  # stop
@@ -94,12 +94,12 @@ module RSMP
94
94
  else
95
95
  reject_connection socket, info
96
96
  end
97
- rescue ConnectionError => e
97
+ rescue ConnectionError, HandshakeError => e
98
98
  log "Rejected connection from #{remote_ip}:#{remote_port}, #{e.to_s}", level: :warning
99
- notify_error e
99
+ distribute_error e
100
100
  rescue StandardError => e
101
101
  log "Connection: #{e.to_s}", exception: e, level: :error
102
- notify_error e, level: :internal
102
+ distribute_error e, level: :internal
103
103
  ensure
104
104
  close socket, info
105
105
  end
@@ -34,7 +34,7 @@ module RSMP
34
34
  log e, level: :error
35
35
  break if reconnect_delay == false
36
36
  rescue StandardError => e
37
- notify_error e, level: :internal
37
+ distribute_error e, level: :internal
38
38
  break if reconnect_delay == false
39
39
  ensure
40
40
  close
@@ -474,6 +474,7 @@ module RSMP
474
474
  set_nts_message_attributes update
475
475
  send_message update
476
476
  store_last_sent_status update
477
+ component.status_updates_sent
477
478
  end
478
479
  end
479
480
 
@@ -10,19 +10,35 @@ class RSMP::TLC::SignalPriority
10
10
  set_state 'received'
11
11
  end
12
12
 
13
+ def prune?
14
+ @state == 'stale' || @state == 'completed'
15
+ end
16
+
17
+ def cancel
18
+ if @state == 'activated'
19
+ set_state 'completed'
20
+ end
21
+ end
22
+
13
23
  def set_state state
14
24
  @state = state
15
25
  @updated = node.clock.now
16
- node.signal_priority_changed self, @state
26
+ @node.signal_priority_changed self, @state
17
27
  end
18
28
 
19
29
  def timer
20
30
  @age = @node.clock.now - @updated
21
31
  case @state
22
32
  when 'received'
23
- set_state 'activated' if @age >= 0.5
33
+ if @age >= 0.5
34
+ @node.log "Priority request #{@id} activated.", level: :info
35
+ set_state 'activated'
36
+ end
24
37
  when 'activated'
25
- set_state 'completed' if @age >= 0.5
38
+ if @age >= 1
39
+ @node.log "Priority request #{@id} became stale.", level: :info
40
+ set_state 'stale'
41
+ end
26
42
  end
27
43
  end
28
44
  end