rsmp 0.2.1 → 0.3.1
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 +5 -5
- data/lib/rsmp/collector.rb +86 -41
- data/lib/rsmp/logger.rb +2 -1
- data/lib/rsmp/matcher.rb +195 -0
- data/lib/rsmp/proxy.rb +23 -9
- data/lib/rsmp/site.rb +3 -1
- data/lib/rsmp/site_proxy.rb +64 -62
- data/lib/rsmp/site_proxy_wait.rb +0 -171
- data/lib/rsmp/supervisor.rb +41 -10
- data/lib/rsmp/supervisor_proxy.rb +1 -1
- data/lib/rsmp/tlc.rb +1 -1
- data/lib/rsmp/version.rb +1 -1
- data/lib/rsmp.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a19b3e748ae4dff7c8f652f7015d8f0b798f78a3d8f07c545638724308f4c819
|
4
|
+
data.tar.gz: 8353a6b756fa36f45b0c9e8cf33d56cb96f69ef30ab158ebf34ea2cc8a863122
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 23fb75e6bad681f67be2f04038a6bebec458c5e6e5c29ee5d5333dc5583b490ba8e1841e5304a88a384ac4761edea45818848f2144a97e73a31dbe2ca89090ed
|
7
|
+
data.tar.gz: cc37d48a622961d41444631cb3543967ccbd926dfcf1314a45294c676270977272e842853a5e1f59a08f1706b26dd85a2494aaea1a12b5dced4917dbaaee9015
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
rsmp (0.
|
4
|
+
rsmp (0.3.1)
|
5
5
|
async (~> 1.29.1)
|
6
6
|
async-io (~> 1.32.1)
|
7
7
|
colorize (~> 0.8.1)
|
@@ -24,12 +24,12 @@ GEM
|
|
24
24
|
cucumber (>= 2.4, < 7.0)
|
25
25
|
rspec-expectations (~> 3.4)
|
26
26
|
thor (~> 1.0)
|
27
|
-
async (1.29.
|
27
|
+
async (1.29.2)
|
28
28
|
console (~> 1.10)
|
29
29
|
nio4r (~> 2.3)
|
30
30
|
timers (~> 4.1)
|
31
|
-
async-io (1.32.
|
32
|
-
async
|
31
|
+
async-io (1.32.2)
|
32
|
+
async
|
33
33
|
builder (3.2.4)
|
34
34
|
childprocess (4.1.0)
|
35
35
|
colorize (0.8.1)
|
@@ -88,7 +88,7 @@ GEM
|
|
88
88
|
mime-types-data (3.2021.0225)
|
89
89
|
minitest (5.14.4)
|
90
90
|
multi_test (0.1.2)
|
91
|
-
nio4r (2.5.
|
91
|
+
nio4r (2.5.8)
|
92
92
|
protobuf-cucumber (3.10.8)
|
93
93
|
activesupport (>= 3.2)
|
94
94
|
middleware
|
data/lib/rsmp/collector.rb
CHANGED
@@ -1,21 +1,20 @@
|
|
1
|
-
# Collects matching ingoing and/or outgoing messages and
|
2
|
-
# wakes up the client once the desired amount has been collected.
|
3
|
-
# Can listen for ingoing and/or outgoing messages.
|
4
|
-
|
5
1
|
module RSMP
|
6
|
-
class Collector < Listener
|
7
2
|
|
3
|
+
# Collects ingoing and/or outgoing messages.
|
4
|
+
# Can filter by message type and wakes up the client once the desired number of messages has been collected.
|
5
|
+
class Collector < Listener
|
8
6
|
attr_reader :condition, :messages, :done
|
9
7
|
|
10
8
|
def initialize proxy, options={}
|
11
9
|
super proxy, options
|
10
|
+
@options = options.clone
|
12
11
|
@ingoing = options[:ingoing] == nil ? true : options[:ingoing]
|
13
12
|
@outgoing = options[:outgoing] == nil ? false : options[:outgoing]
|
14
|
-
@messages = []
|
15
13
|
@condition = Async::Notification.new
|
16
|
-
@
|
17
|
-
@options
|
18
|
-
@
|
14
|
+
@title = options[:title] || [@options[:type]].flatten.join('/')
|
15
|
+
@options[:timeout] ||= 1
|
16
|
+
@options[:num] ||= 1
|
17
|
+
reset
|
19
18
|
end
|
20
19
|
|
21
20
|
def inspect
|
@@ -34,17 +33,9 @@ module RSMP
|
|
34
33
|
@condition.wait
|
35
34
|
end
|
36
35
|
|
37
|
-
def collect_for task, duration
|
38
|
-
siphon do
|
39
|
-
task.sleep duration
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
36
|
def collect task, options={}, &block
|
44
|
-
@
|
45
|
-
@options[:timeout] = options[:timeout] if options[:timeout]
|
37
|
+
@options.merge! options
|
46
38
|
@block = block
|
47
|
-
|
48
39
|
unless @done
|
49
40
|
listen do
|
50
41
|
task.with_timeout(@options[:timeout]) do
|
@@ -52,51 +43,105 @@ module RSMP
|
|
52
43
|
end
|
53
44
|
end
|
54
45
|
end
|
46
|
+
return @error if @error
|
47
|
+
self
|
48
|
+
rescue Async::TimeoutError
|
49
|
+
str = "Did not receive #{@title}"
|
50
|
+
str << " in response to #{options[:m_id]}" if options[:m_id]
|
51
|
+
str << " within #{@options[:timeout]}s"
|
52
|
+
raise RSMP::TimeoutError.new str
|
53
|
+
end
|
55
54
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
@messages
|
55
|
+
# Get the collected messages.
|
56
|
+
# If one message was requested, return it as a plain object instead of array
|
57
|
+
def result
|
58
|
+
return @messages.first if @options[:num] == 1
|
59
|
+
@messages.first @options[:num]
|
62
60
|
end
|
63
61
|
|
62
|
+
# Clear all query results
|
64
63
|
def reset
|
65
|
-
@
|
64
|
+
@messages = []
|
65
|
+
@error = nil
|
66
66
|
@done = false
|
67
67
|
end
|
68
68
|
|
69
|
+
# Check if we receive a NotAck related to initiating request, identified by @m_id.
|
70
|
+
def check_not_ack message
|
71
|
+
return unless @options[:m_id]
|
72
|
+
if message.is_a?(MessageNotAck)
|
73
|
+
if message.attribute('oMId') == @options[:m_id]
|
74
|
+
m_id_short = RSMP::Message.shorten_m_id @options[:m_id], 8
|
75
|
+
@error = RSMP::MessageRejected.new("#{@title} #{m_id_short} was rejected: #{message.attribute('rea')}")
|
76
|
+
complete
|
77
|
+
end
|
78
|
+
false
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# Handle message. and return true when we're done collecting
|
69
83
|
def notify message
|
70
84
|
raise ArgumentError unless message
|
85
|
+
raise RuntimeError.new("can't process message when already done") if @done
|
86
|
+
check_not_ack(message)
|
71
87
|
return true if @done
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
88
|
+
check_match message
|
89
|
+
complete if done?
|
90
|
+
@done
|
91
|
+
end
|
92
|
+
|
93
|
+
# Match message against our collection criteria
|
94
|
+
def check_match message
|
95
|
+
matched = match? message
|
96
|
+
if matched == true
|
97
|
+
keep message
|
98
|
+
elsif matched == false
|
99
|
+
forget message
|
81
100
|
end
|
82
101
|
end
|
83
102
|
|
84
|
-
|
85
|
-
|
103
|
+
# Have we collected the required number of messages?
|
104
|
+
def done?
|
105
|
+
@options[:num] && @messages.size >= @options[:num]
|
106
|
+
end
|
107
|
+
|
108
|
+
# Called when we're done collecting. Remove ourself as a listener,
|
109
|
+
# se we don't receive message notifications anymore
|
110
|
+
def complete
|
111
|
+
@done = true
|
112
|
+
@proxy.remove_listener self
|
113
|
+
@condition.signal
|
114
|
+
end
|
115
|
+
|
116
|
+
# Store a message in the result array
|
117
|
+
def keep message
|
118
|
+
@messages << message
|
119
|
+
end
|
120
|
+
|
121
|
+
# Remove a message from the result array
|
122
|
+
def forget message
|
123
|
+
@messages.delete message
|
124
|
+
end
|
86
125
|
|
126
|
+
# Check a message against our match criteria
|
127
|
+
# Return true if there's a match
|
128
|
+
def match? message
|
129
|
+
raise ArgumentError unless message
|
130
|
+
return if message.direction == :in && @ingoing == false
|
131
|
+
return if message.direction == :out && @outgoing == false
|
87
132
|
if @options[:type]
|
88
|
-
return
|
133
|
+
return if message == nil
|
89
134
|
if @options[:type].is_a? Array
|
90
|
-
return
|
135
|
+
return unless @options[:type].include? message.type
|
91
136
|
else
|
92
|
-
return
|
137
|
+
return unless message.type == @options[:type]
|
93
138
|
end
|
94
139
|
end
|
95
140
|
if @options[:component]
|
96
|
-
return
|
141
|
+
return if message.attributes['cId'] && message.attributes['cId'] != @options[:component]
|
97
142
|
end
|
98
143
|
if @block
|
99
|
-
return
|
144
|
+
return if @block.call(message) == false
|
100
145
|
end
|
101
146
|
true
|
102
147
|
end
|
data/lib/rsmp/logger.rb
CHANGED
@@ -96,7 +96,8 @@ module RSMP
|
|
96
96
|
'statistics' => 'light_black',
|
97
97
|
'not_acknowledged' => 'cyan',
|
98
98
|
'warning' => 'light_yellow',
|
99
|
-
'error' => 'red'
|
99
|
+
'error' => 'red',
|
100
|
+
'debug' => 'light_black'
|
100
101
|
}
|
101
102
|
colors.merge! @settings["color"] if @settings["color"].is_a?(Hash)
|
102
103
|
if colors[level.to_s]
|
data/lib/rsmp/matcher.rb
ADDED
@@ -0,0 +1,195 @@
|
|
1
|
+
module RSMP
|
2
|
+
|
3
|
+
# Base class for waiting for specific status or command responses, specified by
|
4
|
+
# a list of queries. Queries are defined as an array of hashes, e.g
|
5
|
+
# [
|
6
|
+
# {"cCI"=>"M0104", "cO"=>"setDate", "n"=>"securityCode", "v"=>"1111"},
|
7
|
+
# {"cCI"=>"M0104", "cO"=>"setDate", "n"=>"year", "v"=>"2020"},
|
8
|
+
# {"cCI"=>"M0104", "cO"=>"setDate", "n"=>"month", "v"=>/\d+/}
|
9
|
+
# ]
|
10
|
+
#
|
11
|
+
# Note that queries can contain regex patterns for values, like /\d+/ in the example above.
|
12
|
+
#
|
13
|
+
# When an input messages is received it typically contains several items, eg:
|
14
|
+
# [
|
15
|
+
# {"cCI"=>"M0104", "n"=>"month", "v"=>"9", "age"=>"recent"},
|
16
|
+
# {"cCI"=>"M0104", "n"=>"day", "v"=>"29", "age"=>"recent"},
|
17
|
+
# {"cCI"=>"M0104", "n"=>"hour", "v"=>"17", "age"=>"recent"}
|
18
|
+
# ]
|
19
|
+
#
|
20
|
+
# Each input item is matched against each of the queries.
|
21
|
+
# If a match is found, it's stored in the @results hash, with the query as the key,
|
22
|
+
# and a mesage and status as the key. In the example above, this query:
|
23
|
+
#
|
24
|
+
# {"cCI"=>"M0104", "cO"=>"setDate", "n"=>"month", "v"=>/\d+/}
|
25
|
+
#
|
26
|
+
# matches this input:
|
27
|
+
#
|
28
|
+
# {"cCI"=>"M0104", "n"=>"month", "v"=>"9", "age"=>"recent"}
|
29
|
+
#
|
30
|
+
# And the result is stored as:
|
31
|
+
# {
|
32
|
+
# {"cCI"=>"M0104", "cO"=>"setDate", "n"=>"month", "v"=>/\d+/} =>
|
33
|
+
# { <StatusResponse message>, {"cCI"=>"M0104", "cO"=>"setDate", "n"=>"month", "v"=>"9"} }
|
34
|
+
# }
|
35
|
+
#
|
36
|
+
#
|
37
|
+
class Matcher < Collector
|
38
|
+
attr_reader :queries
|
39
|
+
|
40
|
+
# Initialize with a list a wanted statuses
|
41
|
+
def initialize proxy, want, options={}
|
42
|
+
super proxy, options.merge( ingoing: true, outgoing: false)
|
43
|
+
@queries = {}
|
44
|
+
want.each do |query|
|
45
|
+
@queries[query] = nil
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Get the results, as a hash of queries => results
|
50
|
+
def result
|
51
|
+
@queries
|
52
|
+
end
|
53
|
+
|
54
|
+
# Get messages from results
|
55
|
+
def messages
|
56
|
+
@queries.map { |query,result| result[:message] }.uniq
|
57
|
+
end
|
58
|
+
|
59
|
+
# Get items from results
|
60
|
+
def items
|
61
|
+
@queries.map { |query,result| result[:item] }.uniq
|
62
|
+
end
|
63
|
+
|
64
|
+
# Are there queries left to match?
|
65
|
+
def done?
|
66
|
+
@queries.values.all? { |result| result != nil }
|
67
|
+
end
|
68
|
+
|
69
|
+
# Get a simplified hash of queries, with values set to either true or false,
|
70
|
+
# indicating which queries have been matched.
|
71
|
+
def status
|
72
|
+
@queries.transform_values{ |v| v != nil }
|
73
|
+
end
|
74
|
+
|
75
|
+
# Get a simply array of bools, showing which queries ahve been matched.
|
76
|
+
def summary
|
77
|
+
@queries.values.map { |v| v != nil }
|
78
|
+
end
|
79
|
+
|
80
|
+
# Mark a query as matched, by linking it to the matched item and message
|
81
|
+
def keep query, message, item
|
82
|
+
@queries[query] = { message:message, item:item }
|
83
|
+
end
|
84
|
+
|
85
|
+
# Mark a query as not matched
|
86
|
+
def forget query
|
87
|
+
@queries[query] = nil
|
88
|
+
end
|
89
|
+
|
90
|
+
# Check if a messages matches our criteria.
|
91
|
+
# We iterate through each of the status items or return values in the message
|
92
|
+
# Breaks as soon as where done matching all queries
|
93
|
+
def check_match message
|
94
|
+
return unless match?(message)
|
95
|
+
@queries.keys.each do |query| # look through queries
|
96
|
+
get_items(message).each do |item| # look through status items in message
|
97
|
+
break if check_item_match message, query, item
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# Check if an item matches, and mark query as matched/unmatched accordingly.
|
103
|
+
def check_item_match message, query, item
|
104
|
+
matched = match_item? query, item
|
105
|
+
if matched == true
|
106
|
+
keep query, message, item
|
107
|
+
true
|
108
|
+
elsif matched == false
|
109
|
+
forget query
|
110
|
+
true
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# Class for waiting for specific command responses
|
116
|
+
class CommandResponseMatcher < Matcher
|
117
|
+
def initialize proxy, want, options={}
|
118
|
+
super proxy, want, options.merge(
|
119
|
+
type: ['CommandResponse','MessageNotAck'],
|
120
|
+
title:'command response'
|
121
|
+
)
|
122
|
+
end
|
123
|
+
|
124
|
+
# Get items, in our case the return values
|
125
|
+
def get_items message
|
126
|
+
message.attributes['rvs']
|
127
|
+
end
|
128
|
+
|
129
|
+
# Match a return value item against a query
|
130
|
+
def match_item? query, item
|
131
|
+
return nil if query['cCI'] && query['cCI'] != item['cCI']
|
132
|
+
return nil if query['n'] && query['n'] != item['n']
|
133
|
+
if query['v'].is_a? Regexp
|
134
|
+
return false if query['v'] && item['v'] !~ query['v']
|
135
|
+
else
|
136
|
+
return false if query['v'] && item['v'] != query['v']
|
137
|
+
end
|
138
|
+
true
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
# Base class for waiting for status updates or responses
|
143
|
+
class StatusUpdateOrResponseMatcher < Matcher
|
144
|
+
def initialize proxy, want, options={}
|
145
|
+
super proxy, want, options.merge
|
146
|
+
end
|
147
|
+
|
148
|
+
# Get items, in our case status values
|
149
|
+
def get_items message
|
150
|
+
message.attributes['sS']
|
151
|
+
end
|
152
|
+
|
153
|
+
# Match a status value against a query
|
154
|
+
def match_item? query, item
|
155
|
+
return nil if query['sCI'] && query['sCI'] != item['sCI']
|
156
|
+
return nil if query['cO'] && query['cO'] != item['cO']
|
157
|
+
return nil if query['n'] && query['n'] != item['n']
|
158
|
+
return false if query['q'] && query['q'] != item['q']
|
159
|
+
if query['s'].is_a? Regexp
|
160
|
+
return false if query['s'] && item['s'] !~ query['s']
|
161
|
+
else
|
162
|
+
return false if query['s'] && item['s'] != query['s']
|
163
|
+
end
|
164
|
+
true
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
# Class for waiting for specific status responses
|
169
|
+
class StatusResponseMatcher < StatusUpdateOrResponseMatcher
|
170
|
+
def initialize proxy, want, options={}
|
171
|
+
super proxy, want, options.merge(
|
172
|
+
type: ['StatusResponse','MessageNotAck'],
|
173
|
+
title: 'status response'
|
174
|
+
)
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
# Class for waiting for specific status responses
|
179
|
+
class StatusUpdateMatcher < StatusUpdateOrResponseMatcher
|
180
|
+
def initialize proxy, want, options={}
|
181
|
+
super proxy, want, options.merge(
|
182
|
+
type: ['StatusUpdate','MessageNotAck'],
|
183
|
+
title:'status update'
|
184
|
+
)
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
# Class for waiting for an aggregated status response
|
189
|
+
class AggregatedStatusMatcher < Collector
|
190
|
+
def initialize proxy, options={}
|
191
|
+
required = { type: ['AggregatedStatus','MessageNotAck'], title: 'aggregated status' }
|
192
|
+
super proxy, options.merge(required)
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
data/lib/rsmp/proxy.rb
CHANGED
@@ -11,22 +11,32 @@ module RSMP
|
|
11
11
|
include Notifier
|
12
12
|
include Inspect
|
13
13
|
|
14
|
-
attr_reader :state, :archive, :connection_info, :sxl, :task, :collector
|
14
|
+
attr_reader :state, :archive, :connection_info, :sxl, :task, :collector, :ip, :port
|
15
15
|
|
16
16
|
def initialize options
|
17
17
|
initialize_logging options
|
18
|
+
setup options
|
19
|
+
initialize_distributor
|
20
|
+
prepare_collection @settings['collect']
|
21
|
+
clear
|
22
|
+
end
|
23
|
+
|
24
|
+
def revive options
|
25
|
+
setup options
|
26
|
+
end
|
27
|
+
|
28
|
+
def setup options
|
18
29
|
@settings = options[:settings]
|
19
30
|
@task = options[:task]
|
20
31
|
@socket = options[:socket]
|
32
|
+
@stream = options[:stream]
|
33
|
+
@protocol = options[:protocol]
|
21
34
|
@ip = options[:ip]
|
22
35
|
@port = options[:port]
|
23
36
|
@connection_info = options[:info]
|
24
37
|
@sxl = nil
|
25
38
|
@site_settings = nil # can't pick until we know the site id
|
26
|
-
|
27
|
-
|
28
|
-
prepare_collection @settings['collect']
|
29
|
-
clear
|
39
|
+
@state = :stopped
|
30
40
|
end
|
31
41
|
|
32
42
|
def inspect
|
@@ -49,6 +59,7 @@ module RSMP
|
|
49
59
|
def collect task, options, &block
|
50
60
|
collector = RSMP::Collector.new self, options
|
51
61
|
collector.collect task, &block
|
62
|
+
collector
|
52
63
|
end
|
53
64
|
|
54
65
|
def run
|
@@ -105,15 +116,19 @@ module RSMP
|
|
105
116
|
def start_reader
|
106
117
|
@reader = @task.async do |task|
|
107
118
|
task.annotate "reader"
|
108
|
-
@stream
|
109
|
-
@protocol
|
119
|
+
@stream ||= Async::IO::Stream.new(@socket)
|
120
|
+
@protocol ||= Async::IO::Protocol::Line.new(@stream,WRAPPING_DELIMITER) # rsmp messages are json terminated with a form-feed
|
110
121
|
|
111
122
|
while json = @protocol.read_line
|
112
123
|
beginning = Time.now
|
113
124
|
message = process_packet json
|
114
125
|
duration = Time.now - beginning
|
115
126
|
ms = (duration*1000).round(4)
|
116
|
-
|
127
|
+
if duration > 0
|
128
|
+
per_second = (1.0 / duration).round
|
129
|
+
else
|
130
|
+
per_second = Float::INFINITY
|
131
|
+
end
|
117
132
|
if message
|
118
133
|
type = message.type
|
119
134
|
m_id = Logger.shorten_message_id(message.m_id)
|
@@ -192,7 +207,6 @@ module RSMP
|
|
192
207
|
def watchdog_send_timer now
|
193
208
|
return unless @watchdog_started
|
194
209
|
return if @site_settings['intervals']['watchdog'] == :never
|
195
|
-
|
196
210
|
if @latest_watchdog_send_at == nil
|
197
211
|
send_watchdog now
|
198
212
|
else
|
data/lib/rsmp/site.rb
CHANGED
data/lib/rsmp/site_proxy.rb
CHANGED
@@ -3,7 +3,6 @@
|
|
3
3
|
module RSMP
|
4
4
|
class SiteProxy < Proxy
|
5
5
|
include Components
|
6
|
-
include SiteProxyWait
|
7
6
|
|
8
7
|
attr_reader :supervisor, :site_id
|
9
8
|
|
@@ -12,9 +11,16 @@ module RSMP
|
|
12
11
|
initialize_components
|
13
12
|
@supervisor = options[:supervisor]
|
14
13
|
@settings = @supervisor.supervisor_settings.clone
|
15
|
-
@site_id =
|
14
|
+
@site_id = options[:site_id]
|
16
15
|
end
|
17
16
|
|
17
|
+
def revive options
|
18
|
+
super options
|
19
|
+
@supervisor = options[:supervisor]
|
20
|
+
@settings = @supervisor.supervisor_settings.clone
|
21
|
+
end
|
22
|
+
|
23
|
+
|
18
24
|
def inspect
|
19
25
|
"#<#{self.class.name}:#{self.object_id}, #{inspector(
|
20
26
|
:@acknowledgements,:@settings,:@site_settings,:@components
|
@@ -36,7 +42,7 @@ module RSMP
|
|
36
42
|
|
37
43
|
def connection_complete
|
38
44
|
super
|
39
|
-
|
45
|
+
sanitized_sxl_version = RSMP::Schemer.sanitize_version(@site_sxl_version)
|
40
46
|
log "Connection to site #{@site_id} established, using core #{@rsmp_version}, #{@sxl} #{sanitized_sxl_version}", level: :info
|
41
47
|
end
|
42
48
|
|
@@ -79,11 +85,14 @@ module RSMP
|
|
79
85
|
acknowledge message
|
80
86
|
send_version @site_id, rsmp_versions
|
81
87
|
@version_determined = true
|
88
|
+
end
|
82
89
|
|
90
|
+
def validate_ready action
|
91
|
+
raise NotReady.new("Can't #{action} because connection is not ready. (Currently #{@state})") unless ready?
|
83
92
|
end
|
84
93
|
|
85
94
|
def request_aggregated_status component, options={}
|
86
|
-
|
95
|
+
validate_ready 'request aggregated status'
|
87
96
|
m_id = options[:m_id] || RSMP::Message.make_m_id
|
88
97
|
|
89
98
|
message = RSMP::AggregatedStatusRequest.new({
|
@@ -93,9 +102,8 @@ module RSMP
|
|
93
102
|
"mId" => m_id
|
94
103
|
})
|
95
104
|
if options[:collect]
|
96
|
-
result = nil
|
97
105
|
task = @task.async do |task|
|
98
|
-
|
106
|
+
collect_aggregated_status task, options[:collect].merge(m_id: m_id)
|
99
107
|
end
|
100
108
|
send_message message, validate: options[:validate]
|
101
109
|
return message, task.wait
|
@@ -163,11 +171,11 @@ module RSMP
|
|
163
171
|
end
|
164
172
|
|
165
173
|
def request_status component, status_list, options={}
|
166
|
-
|
174
|
+
validate_ready 'request status'
|
167
175
|
m_id = options[:m_id] || RSMP::Message.make_m_id
|
168
176
|
|
169
177
|
# additional items can be used when verifying the response,
|
170
|
-
# but must
|
178
|
+
# but must be removed from the request
|
171
179
|
request_list = status_list.map { |item| item.slice('sCI','n') }
|
172
180
|
|
173
181
|
message = RSMP::StatusRequest.new({
|
@@ -177,23 +185,8 @@ module RSMP
|
|
177
185
|
"sS" => request_list,
|
178
186
|
"mId" => m_id
|
179
187
|
})
|
180
|
-
|
181
|
-
|
182
|
-
task = @task.async do |task|
|
183
|
-
collect_options = options[:collect].merge status_list: status_list
|
184
|
-
collect_status_responses task, collect_options, m_id
|
185
|
-
end
|
186
|
-
send_message message, validate: options[:validate]
|
187
|
-
|
188
|
-
# task.wait return the result of the task. if the task raised an exception
|
189
|
-
# it will be reraised. but that mechanish does not work if multiple values
|
190
|
-
# are returned. so manually raise if first element is an exception
|
191
|
-
result = task.wait
|
192
|
-
raise result.first if result.first.is_a? Exception
|
193
|
-
return message, *result
|
194
|
-
else
|
195
|
-
send_message message, validate: options[:validate]
|
196
|
-
message
|
188
|
+
send_while_collecting message, options do |task|
|
189
|
+
collect_status_responses task, status_list, options[:collect].merge(m_id: m_id)
|
197
190
|
end
|
198
191
|
end
|
199
192
|
|
@@ -202,8 +195,15 @@ module RSMP
|
|
202
195
|
acknowledge message
|
203
196
|
end
|
204
197
|
|
198
|
+
def send_while_collecting message, options, &block
|
199
|
+
task = @task.async { |task| yield task } if options[:collect]
|
200
|
+
send_message message, validate: options[:validate]
|
201
|
+
return message, task.wait if task
|
202
|
+
message
|
203
|
+
end
|
204
|
+
|
205
205
|
def subscribe_to_status component, status_list, options={}
|
206
|
-
|
206
|
+
validate_ready 'subscribe to status'
|
207
207
|
m_id = options[:m_id] || RSMP::Message.make_m_id
|
208
208
|
|
209
209
|
# additional items can be used when verifying the response,
|
@@ -217,28 +217,13 @@ module RSMP
|
|
217
217
|
"sS" => subscribe_list,
|
218
218
|
'mId' => m_id
|
219
219
|
})
|
220
|
-
|
221
|
-
|
222
|
-
task = @task.async do |task|
|
223
|
-
collect_options = options[:collect].merge status_list: status_list
|
224
|
-
collect_status_updates task, collect_options, m_id
|
225
|
-
end
|
226
|
-
send_message message, validate: options[:validate]
|
227
|
-
|
228
|
-
# task.wait return the result of the task. if the task raised an exception
|
229
|
-
# it will be reraised. but that mechanish does not work if multiple values
|
230
|
-
# are returned. so manually raise if first element is an exception
|
231
|
-
result = task.wait
|
232
|
-
raise result.first if result.first.is_a? Exception
|
233
|
-
return message, *result
|
234
|
-
else
|
235
|
-
send_message message, validate: options[:validate]
|
236
|
-
message
|
220
|
+
send_while_collecting message, options do |task|
|
221
|
+
collect_status_updates task, status_list, options[:collect].merge(m_id: m_id)
|
237
222
|
end
|
238
223
|
end
|
239
224
|
|
240
225
|
def unsubscribe_to_status component, status_list, options={}
|
241
|
-
|
226
|
+
validate_ready 'unsubscribe to status'
|
242
227
|
message = RSMP::StatusUnsubscribe.new({
|
243
228
|
"ntsOId" => '',
|
244
229
|
"xNId" => '',
|
@@ -269,7 +254,7 @@ module RSMP
|
|
269
254
|
end
|
270
255
|
|
271
256
|
def send_command component, command_list, options={}
|
272
|
-
|
257
|
+
validate_ready 'send command'
|
273
258
|
m_id = options[:m_id] || RSMP::Message.make_m_id
|
274
259
|
message = RSMP::CommandRequest.new({
|
275
260
|
"ntsOId" => '',
|
@@ -278,23 +263,8 @@ module RSMP
|
|
278
263
|
"arg" => command_list,
|
279
264
|
"mId" => m_id
|
280
265
|
})
|
281
|
-
|
282
|
-
|
283
|
-
task = @task.async do |task|
|
284
|
-
collect_options = options[:collect].merge command_list: command_list
|
285
|
-
collect_command_responses task, collect_options, m_id
|
286
|
-
end
|
287
|
-
send_message message, validate: options[:validate]
|
288
|
-
|
289
|
-
# task.wait return the result of the task. if the task raised an exception
|
290
|
-
# it will be reraised. but that mechanish does not work if multiple values
|
291
|
-
# are returned. so manually raise if first element is an exception
|
292
|
-
result = task.wait
|
293
|
-
raise result.first if result.first.is_a? Exception
|
294
|
-
return message, *result
|
295
|
-
else
|
296
|
-
send_message message, validate: options[:validate]
|
297
|
-
message
|
266
|
+
send_while_collecting message, options do |task|
|
267
|
+
collect_command_responses task, command_list, options[:collect].merge(m_id: m_id)
|
298
268
|
end
|
299
269
|
end
|
300
270
|
|
@@ -370,5 +340,37 @@ module RSMP
|
|
370
340
|
@supervisor.notify_error e, options if @supervisor
|
371
341
|
end
|
372
342
|
|
343
|
+
def wait_for_alarm parent_task, options={}
|
344
|
+
matching_alarm = nil
|
345
|
+
message = collect(parent_task,options.merge(type: "Alarm", with_message: true, num: 1)) do |message|
|
346
|
+
# TODO check components
|
347
|
+
matching_alarm = nil
|
348
|
+
alarm = message
|
349
|
+
next if options[:aCId] && options[:aCId] != alarm.attribute("aCId")
|
350
|
+
next if options[:aSp] && options[:aSp] != alarm.attribute("aSp")
|
351
|
+
next if options[:aS] && options[:aS] != alarm.attribute("aS")
|
352
|
+
matching_alarm = alarm
|
353
|
+
break
|
354
|
+
end
|
355
|
+
if item
|
356
|
+
{ message: message, status: matching_alarm }
|
357
|
+
end
|
358
|
+
end
|
359
|
+
|
360
|
+
def collect_status_updates task, status_list, options
|
361
|
+
StatusUpdateMatcher.new(self, status_list, options).collect task
|
362
|
+
end
|
363
|
+
|
364
|
+
def collect_status_responses task, status_list, options
|
365
|
+
StatusResponseMatcher.new(self, status_list, options).collect task
|
366
|
+
end
|
367
|
+
|
368
|
+
def collect_command_responses task, command_list, options
|
369
|
+
CommandResponseMatcher.new(self, command_list, options).collect task
|
370
|
+
end
|
371
|
+
|
372
|
+
def collect_aggregated_status task, options
|
373
|
+
AggregatedStatusMatcher.new(self, options).collect task
|
374
|
+
end
|
373
375
|
end
|
374
376
|
end
|
data/lib/rsmp/site_proxy_wait.rb
CHANGED
@@ -1,171 +0,0 @@
|
|
1
|
-
# waiting for various types of messages and reponses from remote sites
|
2
|
-
module RSMP
|
3
|
-
module SiteProxyWait
|
4
|
-
|
5
|
-
def wait_for_alarm parent_task, options={}
|
6
|
-
matching_alarm = nil
|
7
|
-
message = collect(parent_task,options.merge(type: "Alarm", with_message: true, num: 1)) do |message|
|
8
|
-
# TODO check components
|
9
|
-
matching_alarm = nil
|
10
|
-
alarm = message
|
11
|
-
next if options[:aCId] && options[:aCId] != alarm.attribute("aCId")
|
12
|
-
next if options[:aSp] && options[:aSp] != alarm.attribute("aSp")
|
13
|
-
next if options[:aS] && options[:aS] != alarm.attribute("aS")
|
14
|
-
matching_alarm = alarm
|
15
|
-
break
|
16
|
-
end
|
17
|
-
if item
|
18
|
-
{ message: message, status: matching_alarm }
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
def collect_status_updates task, options, m_id
|
23
|
-
collect_status_updates_or_responses task, 'StatusUpdate', options, m_id
|
24
|
-
end
|
25
|
-
|
26
|
-
def collect_status_responses task, options, m_id
|
27
|
-
collect_status_updates_or_responses task, 'StatusResponse', options, m_id
|
28
|
-
end
|
29
|
-
|
30
|
-
def collect_command_responses parent_task, options, m_id
|
31
|
-
task.annotate "wait for command response"
|
32
|
-
want = options[:command_list].clone
|
33
|
-
result = {}
|
34
|
-
messages = []
|
35
|
-
collect(parent_task,options.merge({
|
36
|
-
type: ['CommandResponse','MessageNotAck'],
|
37
|
-
num: 1
|
38
|
-
})) do |message|
|
39
|
-
if message.is_a?(MessageNotAck)
|
40
|
-
if message.attribute('oMId') == m_id
|
41
|
-
# set result to an exception, but don't raise it.
|
42
|
-
# this will be returned by the task and stored as the task result
|
43
|
-
# when the parent task call wait() on the task, the exception
|
44
|
-
# will be raised in the parent task, and caught by rspec.
|
45
|
-
# rspec will then show the error and record the test as failed
|
46
|
-
m_id_short = RSMP::Message.shorten_m_id m_id, 8
|
47
|
-
result = RSMP::MessageRejected.new "Command request #{m_id_short} was rejected: #{message.attribute('rea')}"
|
48
|
-
next true # done, no more messages wanted
|
49
|
-
else
|
50
|
-
false
|
51
|
-
end
|
52
|
-
else
|
53
|
-
add = false
|
54
|
-
# look through querues
|
55
|
-
want.each_with_index do |query,i|
|
56
|
-
# look through items in message
|
57
|
-
message.attributes['rvs'].each do |input|
|
58
|
-
matching = command_match? query, input
|
59
|
-
if matching == true
|
60
|
-
result[query] = input
|
61
|
-
add = true
|
62
|
-
elsif matching == false
|
63
|
-
result.delete query
|
64
|
-
end
|
65
|
-
end
|
66
|
-
end
|
67
|
-
messages << message if add
|
68
|
-
result.size == want.size # any queries left to match?
|
69
|
-
end
|
70
|
-
end
|
71
|
-
return result, messages
|
72
|
-
rescue Async::TimeoutError
|
73
|
-
raise RSMP::TimeoutError.new "Did not receive correct command response to #{m_id} within #{options[:timeout]}s"
|
74
|
-
end
|
75
|
-
|
76
|
-
def collect_status_updates_or_responses task, type, options, m_id
|
77
|
-
want = options[:status_list].clone
|
78
|
-
result = {}
|
79
|
-
messages = []
|
80
|
-
# wait for a status update
|
81
|
-
collect(task,options.merge({
|
82
|
-
type: [type,'MessageNotAck'],
|
83
|
-
num: 1
|
84
|
-
})) do |message|
|
85
|
-
if message.is_a?(MessageNotAck)
|
86
|
-
if message.attribute('oMId') == m_id
|
87
|
-
# set result to an exception, but don't raise it.
|
88
|
-
# this will be returned by the task and stored as the task result
|
89
|
-
# when the parent task call wait() on the task, the exception
|
90
|
-
# will be raised in the parent task, and caught by rspec.
|
91
|
-
# rspec will then show the error and record the test as failed
|
92
|
-
m_id_short = RSMP::Message.shorten_m_id m_id, 8
|
93
|
-
result = RSMP::MessageRejected.new "Status request #{m_id_short} was rejected: #{message.attribute('rea')}"
|
94
|
-
next true # done, no more messages wanted
|
95
|
-
end
|
96
|
-
false
|
97
|
-
else
|
98
|
-
found = []
|
99
|
-
add = false
|
100
|
-
# look through querues
|
101
|
-
want.each_with_index do |query,i|
|
102
|
-
# look through status items in message
|
103
|
-
message.attributes['sS'].each do |input|
|
104
|
-
matching = status_match? query, input
|
105
|
-
if matching == true
|
106
|
-
result[query] = input
|
107
|
-
add = true
|
108
|
-
elsif matching == false
|
109
|
-
result.delete query
|
110
|
-
end
|
111
|
-
end
|
112
|
-
end
|
113
|
-
messages << message if add
|
114
|
-
result.size == want.size # any queries left to match?
|
115
|
-
end
|
116
|
-
end
|
117
|
-
return result, messages
|
118
|
-
rescue Async::TimeoutError
|
119
|
-
type_str = {'StatusUpdate'=>'update', 'StatusResponse'=>'response'}[type]
|
120
|
-
raise RSMP::TimeoutError.new "Did not received correct status #{type_str} in reply to #{m_id} within #{options[:timeout]}s"
|
121
|
-
end
|
122
|
-
|
123
|
-
def status_match? query, item
|
124
|
-
return nil if query['sCI'] && query['sCI'] != item['sCI']
|
125
|
-
return nil if query['n'] && query['n'] != item['n']
|
126
|
-
return false if query['q'] && query['q'] != item['q']
|
127
|
-
if query['s'].is_a? Regexp
|
128
|
-
return false if query['s'] && item['s'] !~ query['s']
|
129
|
-
else
|
130
|
-
return false if query['s'] && item['s'] != query['s']
|
131
|
-
end
|
132
|
-
true
|
133
|
-
end
|
134
|
-
|
135
|
-
def command_match? query, item
|
136
|
-
return nil if query['cCI'] && query['cCI'] != item['cCI']
|
137
|
-
return nil if query['n'] && query['n'] != item['n']
|
138
|
-
if query['v'].is_a? Regexp
|
139
|
-
return false if query['v'] && item['v'] !~ query['v']
|
140
|
-
else
|
141
|
-
return false if query['v'] && item['v'] != query['v']
|
142
|
-
end
|
143
|
-
true
|
144
|
-
end
|
145
|
-
|
146
|
-
def wait_for_aggregated_status parent_task, options, m_id
|
147
|
-
collect(parent_task,options.merge({
|
148
|
-
type: ['AggregatedStatus','MessageNotAck'],
|
149
|
-
num: 1
|
150
|
-
})) do |message|
|
151
|
-
if message.is_a?(MessageNotAck)
|
152
|
-
if message.attribute('oMId') == m_id
|
153
|
-
# set result to an exception, but don't raise it.
|
154
|
-
# this will be returned by the task and stored as the task result
|
155
|
-
# when the parent task call wait() on the task, the exception
|
156
|
-
# will be raised in the parent task, and caught by rspec.
|
157
|
-
# rspec will then show the error and record the test as failed
|
158
|
-
m_id_short = RSMP::Message.shorten_m_id m_id, 8
|
159
|
-
result = RSMP::MessageRejected.new "Aggregated status request #{m_id_short} was rejected: #{message.attribute('rea')}"
|
160
|
-
next true # done, no more messages wanted
|
161
|
-
else
|
162
|
-
false
|
163
|
-
end
|
164
|
-
else
|
165
|
-
true
|
166
|
-
end
|
167
|
-
end
|
168
|
-
end
|
169
|
-
|
170
|
-
end
|
171
|
-
end
|
data/lib/rsmp/supervisor.rb
CHANGED
@@ -136,6 +136,13 @@ module RSMP
|
|
136
136
|
end
|
137
137
|
end
|
138
138
|
|
139
|
+
def peek_version_message protocol
|
140
|
+
json = protocol.peek_line
|
141
|
+
attributes = Message.parse_attributes json
|
142
|
+
message = Message.build attributes, json
|
143
|
+
message.attribute('siteId').first['sId']
|
144
|
+
end
|
145
|
+
|
139
146
|
def connect socket, info
|
140
147
|
log "Site connected from #{format_ip_and_port(info)}",
|
141
148
|
ip: info[:ip],
|
@@ -144,25 +151,36 @@ module RSMP
|
|
144
151
|
timestamp: Clock.now
|
145
152
|
|
146
153
|
authorize_ip info[:ip]
|
147
|
-
check_max_sites
|
148
154
|
|
149
|
-
|
155
|
+
stream = Async::IO::Stream.new socket
|
156
|
+
protocol = Async::IO::Protocol::Line.new stream, Proxy::WRAPPING_DELIMITER
|
157
|
+
|
158
|
+
settings = {
|
150
159
|
supervisor: self,
|
151
160
|
ip: info[:ip],
|
152
161
|
port: info[:port],
|
153
162
|
task: @task,
|
154
163
|
settings: {'collect'=>@supervisor_settings['collect']},
|
155
164
|
socket: socket,
|
165
|
+
stream: stream,
|
166
|
+
protocol: protocol,
|
156
167
|
info: info,
|
157
168
|
logger: @logger,
|
158
169
|
archive: @archive
|
159
|
-
}
|
160
|
-
|
170
|
+
}
|
171
|
+
|
172
|
+
id = peek_version_message protocol
|
173
|
+
proxy = find_site id
|
174
|
+
if proxy
|
175
|
+
proxy.revive settings
|
176
|
+
else
|
177
|
+
check_max_sites
|
178
|
+
proxy = build_proxy settings.merge(site_id:id) # keep the id learned by peeking above
|
179
|
+
@proxies.push proxy
|
180
|
+
end
|
161
181
|
proxy.run # will run until the site disconnects
|
162
182
|
ensure
|
163
|
-
@proxies.delete proxy
|
164
183
|
site_ids_changed
|
165
|
-
|
166
184
|
stop if @supervisor_settings['one_shot']
|
167
185
|
end
|
168
186
|
|
@@ -188,7 +206,14 @@ module RSMP
|
|
188
206
|
return find_site(site_id) != nil
|
189
207
|
end
|
190
208
|
|
191
|
-
def
|
209
|
+
def find_site_from_ip_port ip, port
|
210
|
+
@proxies.each do |site|
|
211
|
+
return site if site.ip == ip && site.port == port
|
212
|
+
end
|
213
|
+
nil
|
214
|
+
end
|
215
|
+
|
216
|
+
def find_site site_id
|
192
217
|
@proxies.each do |site|
|
193
218
|
return site if site_id == :any || site.site_id == site_id
|
194
219
|
end
|
@@ -200,7 +225,12 @@ module RSMP
|
|
200
225
|
return site if site
|
201
226
|
wait_for(@site_id_condition,timeout) { find_site site_id }
|
202
227
|
rescue Async::TimeoutError
|
203
|
-
|
228
|
+
if site_id == :any
|
229
|
+
str = "No site connected"
|
230
|
+
else
|
231
|
+
str = "Site '#{site_id}' did not connect"
|
232
|
+
end
|
233
|
+
raise RSMP::TimeoutError.new "#{str} within #{timeout}s"
|
204
234
|
end
|
205
235
|
|
206
236
|
def wait_for_site_disconnect site_id, timeout
|
@@ -210,12 +240,13 @@ module RSMP
|
|
210
240
|
end
|
211
241
|
|
212
242
|
def check_site_id site_id
|
213
|
-
check_site_already_connected site_id
|
243
|
+
#check_site_already_connected site_id
|
214
244
|
return site_id_to_site_setting site_id
|
215
245
|
end
|
216
246
|
|
217
247
|
def check_site_already_connected site_id
|
218
|
-
|
248
|
+
site = find_site(site_id)
|
249
|
+
raise FatalError.new "Site '#{site_id}' already connected" if site != nil && site != self
|
219
250
|
end
|
220
251
|
|
221
252
|
def site_id_to_site_setting site_id
|
data/lib/rsmp/tlc.rb
CHANGED
@@ -798,7 +798,7 @@ module RSMP
|
|
798
798
|
super options
|
799
799
|
@sxl = 'traffic_light_controller'
|
800
800
|
@security_codes = options[:site_settings]['security_codes']
|
801
|
-
@interval = options[:site_settings]
|
801
|
+
@interval = options[:site_settings].dig('intervals','timer') || 1
|
802
802
|
unless @main
|
803
803
|
raise ConfigurationError.new "TLC must have a main component"
|
804
804
|
end
|
data/lib/rsmp/version.rb
CHANGED
data/lib/rsmp.rb
CHANGED
@@ -21,11 +21,11 @@ require 'rsmp/notifier'
|
|
21
21
|
|
22
22
|
require 'rsmp/listener'
|
23
23
|
require 'rsmp/collector'
|
24
|
+
require 'rsmp/matcher'
|
24
25
|
require 'rsmp/component'
|
25
26
|
require 'rsmp/site'
|
26
27
|
require 'rsmp/proxy'
|
27
28
|
require 'rsmp/supervisor_proxy'
|
28
|
-
require 'rsmp/site_proxy_wait'
|
29
29
|
require 'rsmp/site_proxy'
|
30
30
|
require 'rsmp/error'
|
31
31
|
require 'rsmp/message'
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rsmp
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Emil Tin
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-10-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: async
|
@@ -217,6 +217,7 @@ files:
|
|
217
217
|
- lib/rsmp/listener.rb
|
218
218
|
- lib/rsmp/logger.rb
|
219
219
|
- lib/rsmp/logging.rb
|
220
|
+
- lib/rsmp/matcher.rb
|
220
221
|
- lib/rsmp/message.rb
|
221
222
|
- lib/rsmp/node.rb
|
222
223
|
- lib/rsmp/notifier.rb
|