rsmp 0.29.0 → 0.32.0
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.
- checksums.yaml +4 -4
- data/Gemfile.lock +7 -7
- data/documentation/collecting_message.md +25 -10
- data/documentation/message_distribution.md +8 -8
- data/lib/rsmp/cli.rb +43 -19
- data/lib/rsmp/collect/ack_collector.rb +7 -5
- data/lib/rsmp/collect/aggregated_status_collector.rb +4 -2
- data/lib/rsmp/collect/alarm_collector.rb +10 -10
- data/lib/rsmp/collect/{alarm_query.rb → alarm_matcher.rb} +2 -2
- data/lib/rsmp/collect/collector.rb +69 -66
- data/lib/rsmp/collect/{command_query.rb → command_matcher.rb} +2 -2
- data/lib/rsmp/collect/command_response_collector.rb +3 -3
- data/lib/rsmp/collect/distributor.rb +65 -0
- data/lib/rsmp/collect/filter.rb +10 -5
- data/lib/rsmp/collect/{query.rb → matcher.rb} +1 -1
- data/lib/rsmp/collect/queue.rb +39 -0
- data/lib/rsmp/collect/receiver.rb +40 -0
- data/lib/rsmp/collect/state_collector.rb +57 -57
- data/lib/rsmp/collect/status_collector.rb +9 -6
- data/lib/rsmp/collect/{status_query.rb → status_matcher.rb} +2 -2
- data/lib/rsmp/component.rb +4 -0
- data/lib/rsmp/node.rb +1 -1
- data/lib/rsmp/proxy.rb +24 -22
- data/lib/rsmp/site.rb +43 -3
- data/lib/rsmp/site_proxy.rb +4 -4
- data/lib/rsmp/supervisor.rb +5 -5
- data/lib/rsmp/supervisor_proxy.rb +2 -1
- data/lib/rsmp/tlc/signal_priority.rb +19 -3
- data/lib/rsmp/tlc/traffic_controller.rb +29 -15
- data/lib/rsmp/tlc/traffic_controller_site.rb +4 -1
- data/lib/rsmp/version.rb +1 -1
- data/lib/rsmp.rb +7 -6
- metadata +9 -8
- data/lib/rsmp/collect/listener.rb +0 -23
- data/lib/rsmp/collect/notifier.rb +0 -65
@@ -1,40 +1,53 @@
|
|
1
1
|
module RSMP
|
2
2
|
|
3
|
-
# Collects messages from a
|
3
|
+
# Collects messages from a distributor.
|
4
4
|
# Can filter by message type, componet and direction.
|
5
5
|
# Wakes up the once the desired number of messages has been collected.
|
6
|
-
class Collector
|
7
|
-
|
6
|
+
class Collector
|
7
|
+
include Receiver
|
8
|
+
attr_reader :condition, :messages, :status, :error, :task, :m_id
|
8
9
|
|
9
|
-
def initialize
|
10
|
-
|
10
|
+
def initialize distributor, options={}
|
11
|
+
initialize_receiver distributor, filter: options[:filter]
|
11
12
|
@options = {
|
12
13
|
cancel: {
|
13
14
|
schema_error: true,
|
14
15
|
disconnect: false,
|
15
16
|
}
|
16
17
|
}.deep_merge options
|
17
|
-
@
|
18
|
-
@
|
18
|
+
@timeout = options[:timeout]
|
19
|
+
@num = options[:num]
|
20
|
+
@m_id = options[:m_id]
|
19
21
|
@condition = Async::Notification.new
|
20
|
-
|
21
|
-
|
22
|
-
|
22
|
+
make_title options[:title]
|
23
|
+
|
24
|
+
if task
|
25
|
+
@task = task
|
23
26
|
else
|
24
|
-
# if
|
27
|
+
# if distributor is a Proxy, or some other object that implements task(),
|
25
28
|
# then try to get the task that way
|
26
|
-
if
|
27
|
-
@task =
|
29
|
+
if distributor.respond_to? 'task'
|
30
|
+
@task = distributor.task
|
28
31
|
end
|
29
32
|
end
|
30
33
|
reset
|
31
34
|
end
|
32
35
|
|
36
|
+
def make_title title
|
37
|
+
if title
|
38
|
+
@title = title
|
39
|
+
elsif @filter
|
40
|
+
@title = [@filter.type].flatten.join('/')
|
41
|
+
else
|
42
|
+
@title = ""
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
33
46
|
def use_task task
|
34
47
|
@task = task
|
35
48
|
end
|
36
49
|
|
37
|
-
# Clear all
|
50
|
+
# Clear all matcher results
|
38
51
|
def reset
|
39
52
|
@messages = []
|
40
53
|
@error = nil
|
@@ -95,7 +108,7 @@ module RSMP
|
|
95
108
|
wait
|
96
109
|
@status
|
97
110
|
ensure
|
98
|
-
@
|
111
|
+
@distributor.remove_receiver self if @distributor
|
99
112
|
end
|
100
113
|
|
101
114
|
# Collect message
|
@@ -110,8 +123,8 @@ module RSMP
|
|
110
123
|
# the desired messages have been collected, or timeout is reached.
|
111
124
|
def wait
|
112
125
|
if collecting?
|
113
|
-
if @
|
114
|
-
@task.with_timeout(@
|
126
|
+
if @timeout
|
127
|
+
@task.with_timeout(@timeout) { @condition.wait }
|
115
128
|
else
|
116
129
|
@condition.wait
|
117
130
|
end
|
@@ -136,39 +149,41 @@ module RSMP
|
|
136
149
|
def start &block
|
137
150
|
raise RuntimeError.new("Can't start collectimng unless ready (currently #{@status})") unless ready?
|
138
151
|
@block = block
|
139
|
-
raise ArgumentError.new("Num, timeout or block must be provided") unless @
|
152
|
+
raise ArgumentError.new("Num, timeout or block must be provided") unless @num || @timeout || @block
|
140
153
|
reset
|
141
154
|
@status = :collecting
|
142
155
|
log_start
|
143
|
-
@
|
156
|
+
@distributor.add_receiver self if @distributor
|
144
157
|
end
|
145
158
|
|
146
159
|
# Build a string describing how how progress reached before timeout
|
147
160
|
def describe_progress
|
148
161
|
str = "#{identifier}: #{@title.capitalize} collection "
|
149
|
-
str << "in response to #{@
|
150
|
-
str << "didn't complete within #{@
|
151
|
-
str << "reached #{@messages.size}/#{@
|
162
|
+
str << "in response to #{@m_id} " if @m_id
|
163
|
+
str << "didn't complete within #{@timeout}s, "
|
164
|
+
str << "reached #{@messages.size}/#{@num}"
|
152
165
|
str
|
153
166
|
end
|
154
167
|
|
155
168
|
# Check if we receive a NotAck related to initiating request, identified by @m_id.
|
156
169
|
def reject_not_ack message
|
157
|
-
return unless @
|
170
|
+
return unless @m_id
|
158
171
|
if message.is_a?(MessageNotAck)
|
159
|
-
if message.attribute('oMId') == @
|
160
|
-
m_id_short = RSMP::Message.shorten_m_id @
|
172
|
+
if message.attribute('oMId') == @m_id
|
173
|
+
m_id_short = RSMP::Message.shorten_m_id @m_id, 8
|
161
174
|
cancel RSMP::MessageRejected.new("#{@title} #{m_id_short} was rejected with '#{message.attribute('rea')}'")
|
162
|
-
@
|
175
|
+
@distributor.log "#{identifier}: cancelled due to a NotAck", level: :debug
|
163
176
|
true
|
164
177
|
end
|
165
178
|
end
|
166
179
|
end
|
167
180
|
|
168
181
|
# Handle message. and return true when we're done collecting
|
169
|
-
def
|
182
|
+
def receive message
|
170
183
|
raise ArgumentError unless message
|
171
|
-
|
184
|
+
unless ready? || collecting?
|
185
|
+
raise RuntimeError.new("can't process message when status is :#{@status}, title: #{@title}, desc: #{describe}")
|
186
|
+
end
|
172
187
|
if perform_match message
|
173
188
|
if done?
|
174
189
|
complete
|
@@ -185,8 +200,8 @@ module RSMP
|
|
185
200
|
# Match message against our collection criteria
|
186
201
|
def perform_match message
|
187
202
|
return false if reject_not_ack(message)
|
188
|
-
return false unless
|
189
|
-
#@
|
203
|
+
return false unless acceptable?(message)
|
204
|
+
#@distributor.log "#{identifier}: Looking at #{message.type} #{message.m_id_short}", level: :collect
|
190
205
|
if @block
|
191
206
|
status = [@block.call(message)].flatten
|
192
207
|
return unless collecting?
|
@@ -198,10 +213,10 @@ module RSMP
|
|
198
213
|
|
199
214
|
# Have we collected the required number of messages?
|
200
215
|
def done?
|
201
|
-
@
|
216
|
+
@num && @messages.size >= @num
|
202
217
|
end
|
203
218
|
|
204
|
-
# Called when we're done collecting. Remove ourself as a
|
219
|
+
# Called when we're done collecting. Remove ourself as a receiver,
|
205
220
|
# se we don't receive message notifications anymore
|
206
221
|
def complete
|
207
222
|
@status = :ok
|
@@ -214,39 +229,39 @@ module RSMP
|
|
214
229
|
log_incomplete
|
215
230
|
end
|
216
231
|
|
217
|
-
# Remove ourself as a
|
232
|
+
# Remove ourself as a receiver, so we don't receive message notifications anymore,
|
218
233
|
# and wake up the async condition
|
219
234
|
def do_stop
|
220
|
-
@
|
235
|
+
@distributor.remove_receiver self
|
221
236
|
@condition.signal
|
222
237
|
end
|
223
238
|
|
224
239
|
# An error occured upstream.
|
225
240
|
# Check if we should cancel.
|
226
|
-
def
|
241
|
+
def receive_error error, options={}
|
227
242
|
case error
|
228
243
|
when RSMP::SchemaError
|
229
|
-
|
244
|
+
receive_schema_error error, options
|
230
245
|
when RSMP::DisconnectError
|
231
|
-
|
246
|
+
receive_disconnect error, options
|
232
247
|
end
|
233
248
|
end
|
234
249
|
|
235
250
|
# Cancel if we received e schema error for a message type we're collecting
|
236
|
-
def
|
251
|
+
def receive_schema_error error, options
|
237
252
|
return unless @options.dig(:cancel,:schema_error)
|
238
253
|
message = options[:message]
|
239
254
|
return unless message
|
240
255
|
klass = message.class.name.split('::').last
|
241
|
-
return unless @
|
242
|
-
@
|
256
|
+
return unless @filter&.type == nil || [@filter&.type].flatten.include?(klass)
|
257
|
+
@distributor.log "#{identifier}: cancelled due to schema error in #{klass} #{message.m_id_short}", level: :debug
|
243
258
|
cancel error
|
244
259
|
end
|
245
260
|
|
246
261
|
# Cancel if we received e notificaiton about a disconnect
|
247
|
-
def
|
262
|
+
def receive_disconnect error, options
|
248
263
|
return unless @options.dig(:cancel,:disconnect)
|
249
|
-
@
|
264
|
+
@distributor.log "#{identifier}: cancelled due to a connection error: #{error.to_s}", level: :debug
|
250
265
|
cancel error
|
251
266
|
end
|
252
267
|
|
@@ -264,39 +279,27 @@ module RSMP
|
|
264
279
|
|
265
280
|
# Check a message against our match criteria
|
266
281
|
# Return true if there's a match, false if not
|
267
|
-
def
|
268
|
-
|
269
|
-
return false if message.direction == :out && @outgoing == false
|
270
|
-
if @options[:type]
|
271
|
-
if @options[:type].is_a? Array
|
272
|
-
return false unless @options[:type].include? message.type
|
273
|
-
else
|
274
|
-
return false unless message.type == @options[:type]
|
275
|
-
end
|
276
|
-
end
|
277
|
-
if @options[:component]
|
278
|
-
return false if message.attributes['cId'] && message.attributes['cId'] != @options[:component]
|
279
|
-
end
|
280
|
-
true
|
282
|
+
def acceptable? message
|
283
|
+
@filter == nil || @filter.accept?(message)
|
281
284
|
end
|
282
285
|
|
283
286
|
# return a string describing the types of messages we're collecting
|
284
287
|
def describe_types
|
285
|
-
[@
|
288
|
+
[@filter&.type].flatten.join('/')
|
286
289
|
end
|
287
290
|
|
288
291
|
# return a string that describes whe number of messages, and type of message we're collecting
|
289
292
|
def describe_num_and_type
|
290
|
-
if @
|
291
|
-
"#{@
|
293
|
+
if @num && @num > 1
|
294
|
+
"#{@num} #{describe_types}s"
|
292
295
|
else
|
293
296
|
describe_types
|
294
297
|
end
|
295
298
|
end
|
296
299
|
|
297
300
|
# return a string that describes the attributes that we're looking for
|
298
|
-
def
|
299
|
-
h = {component: @
|
301
|
+
def describe_matcher
|
302
|
+
h = {component: @filter&.component}.compact
|
300
303
|
if h.empty?
|
301
304
|
describe_num_and_type
|
302
305
|
else
|
@@ -306,8 +309,8 @@ module RSMP
|
|
306
309
|
|
307
310
|
# return a string that describe how many many messages have been collected
|
308
311
|
def describe_progress
|
309
|
-
if @
|
310
|
-
"#{@messages.size} of #{@
|
312
|
+
if @num
|
313
|
+
"#{@messages.size} of #{@num} message#{'s' if @messages.size!=1} collected"
|
311
314
|
else
|
312
315
|
"#{@messages.size} message#{'s' if @messages.size!=1} collected"
|
313
316
|
end
|
@@ -315,17 +318,17 @@ module RSMP
|
|
315
318
|
|
316
319
|
# log when we start collecting
|
317
320
|
def log_start
|
318
|
-
@
|
321
|
+
@distributor.log "#{identifier}: Waiting for #{describe_matcher}".strip, level: :collect
|
319
322
|
end
|
320
323
|
|
321
324
|
# log current progress
|
322
325
|
def log_incomplete
|
323
|
-
@
|
326
|
+
@distributor.log "#{identifier}: #{describe_progress}", level: :collect
|
324
327
|
end
|
325
328
|
|
326
329
|
# log when we end collecting
|
327
330
|
def log_complete
|
328
|
-
@
|
331
|
+
@distributor.log "#{identifier}: Done", level: :collect
|
329
332
|
end
|
330
333
|
|
331
334
|
# get a short id in hex format, identifying ourself
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module RSMP
|
2
2
|
# Match a specific command responses
|
3
|
-
class
|
4
|
-
# Match a return value item against a
|
3
|
+
class CommandMatcher < Matcher
|
4
|
+
# Match a return value item against a matcher
|
5
5
|
def match? item
|
6
6
|
return nil if @want['cCI'] && @want['cCI'] != item['cCI']
|
7
7
|
return nil if @want['n'] && @want['n'] != item['n']
|
@@ -3,13 +3,13 @@ module RSMP
|
|
3
3
|
class CommandResponseCollector < StateCollector
|
4
4
|
def initialize proxy, want, options={}
|
5
5
|
super proxy, want, options.merge(
|
6
|
-
type: 'CommandResponse',
|
6
|
+
filter: RSMP::Filter.new(ingoing: true, outgoing: false, type: 'CommandResponse'),
|
7
7
|
title:'command response'
|
8
8
|
)
|
9
9
|
end
|
10
10
|
|
11
|
-
def
|
12
|
-
|
11
|
+
def build_matcher want
|
12
|
+
CommandMatcher.new want
|
13
13
|
end
|
14
14
|
|
15
15
|
# Get items, in our case the return values
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# Distributes messages to receivers
|
2
|
+
|
3
|
+
module RSMP
|
4
|
+
module Distributor
|
5
|
+
attr_reader :receivers
|
6
|
+
|
7
|
+
include Inspect
|
8
|
+
|
9
|
+
def inspect
|
10
|
+
"#<#{self.class.name}:#{self.object_id}, #{inspector(:@receivers)}>"
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize_distributor
|
14
|
+
@receivers = []
|
15
|
+
@defer_distribution = false
|
16
|
+
@deferred_messages = []
|
17
|
+
end
|
18
|
+
|
19
|
+
def clear_deferred_distribution &block
|
20
|
+
@deferred_messages = []
|
21
|
+
end
|
22
|
+
|
23
|
+
def with_deferred_distribution &block
|
24
|
+
was, @defer_distribution = @defer_distribution, true
|
25
|
+
yield
|
26
|
+
distribute_queued
|
27
|
+
ensure
|
28
|
+
@defer_distribution = was
|
29
|
+
@deferred_messages = []
|
30
|
+
end
|
31
|
+
|
32
|
+
def distribute_queued
|
33
|
+
@deferred_messages.each { |message| distribute_immediately message }
|
34
|
+
ensure
|
35
|
+
@deferred_messages = []
|
36
|
+
end
|
37
|
+
|
38
|
+
def add_receiver receiver
|
39
|
+
raise ArgumentError unless receiver
|
40
|
+
@receivers << receiver unless @receivers.include? receiver
|
41
|
+
end
|
42
|
+
|
43
|
+
def remove_receiver receiver
|
44
|
+
raise ArgumentError unless receiver
|
45
|
+
@receivers.delete receiver
|
46
|
+
end
|
47
|
+
|
48
|
+
def distribute message
|
49
|
+
raise ArgumentError unless message
|
50
|
+
if @defer_distribution
|
51
|
+
@deferred_messages << message
|
52
|
+
else
|
53
|
+
distribute_immediately message
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def distribute_immediately message
|
58
|
+
@receivers.each { |receiver| receiver.receive message }
|
59
|
+
end
|
60
|
+
|
61
|
+
def distribute_error error, options={}
|
62
|
+
@receivers.each { |receiver| receiver.receive_error error, options }
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
data/lib/rsmp/collect/filter.rb
CHANGED
@@ -3,10 +3,13 @@ module RSMP
|
|
3
3
|
# Filter messages based on type, direction and component id.
|
4
4
|
# Used by Collectors.
|
5
5
|
class Filter
|
6
|
-
|
6
|
+
|
7
|
+
attr_reader :ingoing, :outgoing, :type, :component
|
8
|
+
|
9
|
+
def initialize ingoing:true, outgoing:true, type:nil, component:nil
|
7
10
|
@ingoing = ingoing
|
8
11
|
@outgoing = outgoing
|
9
|
-
@type = type
|
12
|
+
@type = type ? [type].flatten : nil
|
10
13
|
@component = component
|
11
14
|
end
|
12
15
|
|
@@ -16,10 +19,8 @@ module RSMP
|
|
16
19
|
return false if message.direction == :in && @ingoing == false
|
17
20
|
return false if message.direction == :out && @outgoing == false
|
18
21
|
if @type
|
19
|
-
|
22
|
+
unless message.is_a?(MessageNotAck)
|
20
23
|
return false unless @type.include? message.type
|
21
|
-
else
|
22
|
-
return false unless message.type == @type
|
23
24
|
end
|
24
25
|
end
|
25
26
|
if @component
|
@@ -27,5 +28,9 @@ module RSMP
|
|
27
28
|
end
|
28
29
|
true
|
29
30
|
end
|
31
|
+
|
32
|
+
def reject? message
|
33
|
+
!accept?(message)
|
34
|
+
end
|
30
35
|
end
|
31
36
|
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# Receives items from a Distributor and keeps them in a queue.
|
2
|
+
# The client can wait for mesages and will get them one by one.
|
3
|
+
|
4
|
+
module RSMP
|
5
|
+
class Queue
|
6
|
+
include Receiver
|
7
|
+
|
8
|
+
attr_reader :messages
|
9
|
+
|
10
|
+
def initialize distributor, filter: nil, task:
|
11
|
+
initialize_receiver distributor, filter: filter
|
12
|
+
@condition = Async::Notification.new
|
13
|
+
@task = task
|
14
|
+
clear
|
15
|
+
end
|
16
|
+
|
17
|
+
def clear
|
18
|
+
@messages = []
|
19
|
+
end
|
20
|
+
|
21
|
+
def wait_for_message timeout: nil
|
22
|
+
if @messages.empty?
|
23
|
+
if timeout
|
24
|
+
@task.with_timeout(timeout) { @condition.wait }
|
25
|
+
else
|
26
|
+
@condition.wait
|
27
|
+
end
|
28
|
+
end
|
29
|
+
@messages.shift
|
30
|
+
rescue Async::TimeoutError
|
31
|
+
raise RSMP::TimeoutError
|
32
|
+
end
|
33
|
+
|
34
|
+
def handle_message message
|
35
|
+
@messages << message
|
36
|
+
@condition.signal
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# Receives items from a Distributor, as long as it's
|
2
|
+
# installed as a receiver.
|
3
|
+
# Optionally can filter mesage using a Filter.
|
4
|
+
|
5
|
+
module RSMP
|
6
|
+
module Receiver
|
7
|
+
include Inspect
|
8
|
+
|
9
|
+
def initialize_receiver distributor, filter: nil
|
10
|
+
@distributor = distributor
|
11
|
+
@filter = filter
|
12
|
+
end
|
13
|
+
|
14
|
+
def start_receiving
|
15
|
+
@distributor.add_receiver(self)
|
16
|
+
end
|
17
|
+
|
18
|
+
def stop_receiving
|
19
|
+
@distributor.remove_receiver(self)
|
20
|
+
end
|
21
|
+
|
22
|
+
def receive message
|
23
|
+
handle_message(message) if accept_message?(message)
|
24
|
+
end
|
25
|
+
|
26
|
+
def receive_error error, options={}
|
27
|
+
end
|
28
|
+
|
29
|
+
def accept_message? message
|
30
|
+
@filter == nil || @filter.accept?(message)
|
31
|
+
end
|
32
|
+
|
33
|
+
def reject_message? message
|
34
|
+
!accept_message?(message)
|
35
|
+
end
|
36
|
+
|
37
|
+
def handle_message message
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|