rsmp 0.1.11 → 0.1.21
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/.gitignore +1 -0
- data/Gemfile.lock +50 -52
- data/README.md +7 -1
- data/config/site.yaml +2 -2
- data/config/tlc.yaml +44 -0
- data/documentation/classes_and_modules.md +65 -0
- data/documentation/message_distribution.md +23 -0
- data/lib/rsmp.rb +10 -5
- data/lib/rsmp/archive.rb +16 -18
- data/lib/rsmp/cli.rb +15 -5
- data/lib/rsmp/collector.rb +90 -0
- data/lib/rsmp/component.rb +12 -2
- data/lib/rsmp/{site_base.rb → components.rb} +8 -6
- data/lib/rsmp/error.rb +10 -1
- data/lib/rsmp/listener.rb +33 -0
- data/lib/rsmp/logger.rb +3 -3
- data/lib/rsmp/{base.rb → logging.rb} +2 -2
- data/lib/rsmp/message.rb +25 -2
- data/lib/rsmp/node.rb +37 -12
- data/lib/rsmp/notifier.rb +24 -0
- data/lib/rsmp/proxy.rb +65 -45
- data/lib/rsmp/site.rb +12 -5
- data/lib/rsmp/site_proxy.rb +91 -65
- data/lib/rsmp/site_proxy_wait.rb +181 -0
- data/lib/rsmp/supervisor.rb +7 -6
- data/lib/rsmp/supervisor_proxy.rb +84 -32
- data/lib/rsmp/tlc.rb +885 -0
- data/lib/rsmp/version.rb +1 -1
- data/lib/rsmp/wait.rb +7 -8
- data/rsmp.gemspec +10 -11
- metadata +40 -43
- data/lib/rsmp/probe.rb +0 -114
- data/lib/rsmp/probe_collection.rb +0 -28
- data/lib/rsmp/supervisor_base.rb +0 -10
data/lib/rsmp/site.rb
CHANGED
@@ -4,12 +4,12 @@
|
|
4
4
|
|
5
5
|
module RSMP
|
6
6
|
class Site < Node
|
7
|
-
include
|
7
|
+
include Components
|
8
8
|
|
9
9
|
attr_reader :rsmp_versions, :site_settings, :logger, :proxies
|
10
10
|
|
11
11
|
def initialize options={}
|
12
|
-
|
12
|
+
initialize_components
|
13
13
|
handle_site_settings options
|
14
14
|
super options
|
15
15
|
@proxies = []
|
@@ -27,6 +27,7 @@ module RSMP
|
|
27
27
|
{ 'ip' => '127.0.0.1', 'port' => 12111 }
|
28
28
|
],
|
29
29
|
'rsmp_versions' => ['3.1.1','3.1.2','3.1.3','3.1.4'],
|
30
|
+
'sxl' => 'traffic_light_controller',
|
30
31
|
'sxl_version' => '1.0.7',
|
31
32
|
'timer_interval' => 0.1,
|
32
33
|
'watchdog_interval' => 1,
|
@@ -39,6 +40,9 @@ module RSMP
|
|
39
40
|
'site_ready_timeout' => 1,
|
40
41
|
'reconnect_interval' => 0.1,
|
41
42
|
'send_after_connect' => true,
|
43
|
+
'components' => {
|
44
|
+
'C1' => {}
|
45
|
+
}
|
42
46
|
}
|
43
47
|
if options[:site_settings]
|
44
48
|
converted = options[:site_settings].map { |k,v| [k.to_s,v] }.to_h #convert symbol keys to string keys
|
@@ -66,7 +70,7 @@ module RSMP
|
|
66
70
|
end
|
67
71
|
end
|
68
72
|
|
69
|
-
def
|
73
|
+
def build_proxy settings
|
70
74
|
SupervisorProxy.new settings
|
71
75
|
end
|
72
76
|
|
@@ -77,7 +81,7 @@ module RSMP
|
|
77
81
|
end
|
78
82
|
|
79
83
|
def connect_to_supervisor task, supervisor_settings
|
80
|
-
proxy =
|
84
|
+
proxy = build_proxy({
|
81
85
|
site: self,
|
82
86
|
task: @task,
|
83
87
|
settings: @site_settings,
|
@@ -106,7 +110,9 @@ module RSMP
|
|
106
110
|
if @site_settings["reconnect_interval"] != :no
|
107
111
|
# sleep until waken by reconnect() or the reconnect interval passed
|
108
112
|
proxy.set_state :wait_for_reconnect
|
109
|
-
task.with_timeout(@site_settings["reconnect_interval"])
|
113
|
+
task.with_timeout(@site_settings["reconnect_interval"]) do
|
114
|
+
@sleep_condition.wait
|
115
|
+
end
|
110
116
|
else
|
111
117
|
proxy.set_state :cannot_connect
|
112
118
|
break
|
@@ -137,5 +143,6 @@ module RSMP
|
|
137
143
|
proxy.stop
|
138
144
|
end
|
139
145
|
end
|
146
|
+
|
140
147
|
end
|
141
148
|
end
|
data/lib/rsmp/site_proxy.rb
CHANGED
@@ -2,13 +2,14 @@
|
|
2
2
|
|
3
3
|
module RSMP
|
4
4
|
class SiteProxy < Proxy
|
5
|
-
include
|
5
|
+
include Components
|
6
|
+
include SiteProxyWait
|
6
7
|
|
7
8
|
attr_reader :supervisor, :site_id
|
8
9
|
|
9
10
|
def initialize options
|
10
11
|
super options
|
11
|
-
|
12
|
+
initialize_components
|
12
13
|
@supervisor = options[:supervisor]
|
13
14
|
@settings = @supervisor.supervisor_settings.clone
|
14
15
|
@site_id = nil
|
@@ -23,6 +24,11 @@ module RSMP
|
|
23
24
|
start_reader
|
24
25
|
end
|
25
26
|
|
27
|
+
def stop
|
28
|
+
log "Closing connection to site", level: :info
|
29
|
+
super
|
30
|
+
end
|
31
|
+
|
26
32
|
def connection_complete
|
27
33
|
super
|
28
34
|
log "Connection to site #{@site_id} established", level: :info
|
@@ -50,11 +56,20 @@ module RSMP
|
|
50
56
|
end
|
51
57
|
end
|
52
58
|
|
59
|
+
def process_command_response message
|
60
|
+
log "Received #{message.type}", message: message, level: :log
|
61
|
+
acknowledge message
|
62
|
+
end
|
63
|
+
|
64
|
+
def process_deferred
|
65
|
+
supervisor.process_deferred
|
66
|
+
end
|
67
|
+
|
53
68
|
def version_accepted message
|
54
69
|
log "Received Version message for site #{@site_id} using RSMP #{@rsmp_version}", message: message, level: :log
|
55
70
|
start_timer
|
56
71
|
acknowledge message
|
57
|
-
send_version @site_id, @
|
72
|
+
send_version @site_id, @settings['rsmp_versions']
|
58
73
|
@version_determined = true
|
59
74
|
|
60
75
|
if @settings['sites']
|
@@ -81,7 +96,7 @@ module RSMP
|
|
81
96
|
component = @components[c_id]
|
82
97
|
if component == nil
|
83
98
|
if @site_settings == nil || @site_settings['components'] == nil
|
84
|
-
component = build_component
|
99
|
+
component = build_component(id:c_id, type:nil)
|
85
100
|
@components[c_id] = component
|
86
101
|
log "Adding component #{c_id} to site #{@site_id}", level: :info
|
87
102
|
else
|
@@ -123,16 +138,33 @@ module RSMP
|
|
123
138
|
@supervisor.site_ids_changed
|
124
139
|
end
|
125
140
|
|
126
|
-
def request_status component, status_list,
|
127
|
-
raise NotReady unless
|
141
|
+
def request_status component, status_list, options={}
|
142
|
+
raise NotReady unless ready?
|
143
|
+
m_id = options[:m_id] || RSMP::Message.make_m_id
|
144
|
+
|
145
|
+
# additional items can be used when verifying the response,
|
146
|
+
# but must to remove from the request
|
147
|
+
request_list = status_list.map { |item| item.slice('sCI','n') }
|
148
|
+
|
128
149
|
message = RSMP::StatusRequest.new({
|
129
150
|
"ntsOId" => '',
|
130
151
|
"xNId" => '',
|
131
152
|
"cId" => component,
|
132
|
-
"sS" =>
|
153
|
+
"sS" => request_list,
|
154
|
+
"mId" => m_id
|
133
155
|
})
|
134
|
-
|
135
|
-
|
156
|
+
if options[:collect]
|
157
|
+
result = nil
|
158
|
+
task = @task.async do |task|
|
159
|
+
collect_options = options[:collect].merge status_list: status_list
|
160
|
+
collect_status_responses task, collect_options, m_id
|
161
|
+
end
|
162
|
+
send_message message
|
163
|
+
return message, task.wait
|
164
|
+
else
|
165
|
+
send_message message
|
166
|
+
message
|
167
|
+
end
|
136
168
|
end
|
137
169
|
|
138
170
|
def process_status_response message
|
@@ -140,36 +172,37 @@ module RSMP
|
|
140
172
|
acknowledge message
|
141
173
|
end
|
142
174
|
|
143
|
-
def
|
144
|
-
raise
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
if item[:message].type == 'MessageNotAck'
|
151
|
-
next item[:message].attribute('oMId') == options[:message].m_id
|
152
|
-
elsif item[:message].type == 'StatusResponse'
|
153
|
-
next item[:message].attribute('cId') == options[:message].attribute('cId')
|
154
|
-
end
|
155
|
-
end
|
156
|
-
item[:message] if item
|
157
|
-
end
|
175
|
+
def subscribe_to_status component, status_list, options={}
|
176
|
+
raise NotReady unless ready?
|
177
|
+
m_id = options[:m_id] || RSMP::Message.make_m_id
|
178
|
+
|
179
|
+
# additional items can be used when verifying the response,
|
180
|
+
# but must to remove from the subscribe message
|
181
|
+
subscribe_list = status_list.map { |item| item.slice('sCI','n','uRt') }
|
158
182
|
|
159
|
-
def subscribe_to_status component, status_list, timeout
|
160
|
-
raise NotReady unless @state == :ready
|
161
183
|
message = RSMP::StatusSubscribe.new({
|
162
184
|
"ntsOId" => '',
|
163
185
|
"xNId" => '',
|
164
186
|
"cId" => component,
|
165
|
-
"sS" =>
|
187
|
+
"sS" => subscribe_list,
|
188
|
+
'mId' => m_id
|
166
189
|
})
|
167
|
-
|
168
|
-
|
190
|
+
if options[:collect]
|
191
|
+
result = nil
|
192
|
+
task = @task.async do |task|
|
193
|
+
collect_options = options[:collect].merge status_list: status_list
|
194
|
+
collect_status_updates task, collect_options, m_id
|
195
|
+
end
|
196
|
+
send_message message
|
197
|
+
return message, task.wait
|
198
|
+
else
|
199
|
+
send_message message
|
200
|
+
message
|
201
|
+
end
|
169
202
|
end
|
170
203
|
|
171
204
|
def unsubscribe_to_status component, status_list
|
172
|
-
raise NotReady unless
|
205
|
+
raise NotReady unless ready?
|
173
206
|
message = RSMP::StatusUnsubscribe.new({
|
174
207
|
"ntsOId" => '',
|
175
208
|
"xNId" => '',
|
@@ -185,48 +218,42 @@ module RSMP
|
|
185
218
|
acknowledge message
|
186
219
|
end
|
187
220
|
|
188
|
-
def
|
189
|
-
|
190
|
-
item = @archive.capture(@task,options.merge(type: "StatusUpdate", with_message: true, num: 1)) do |item|
|
191
|
-
# TODO check components
|
192
|
-
found = false
|
193
|
-
sS = item[:message].attributes['sS']
|
194
|
-
sS.each do |status|
|
195
|
-
break if options[:sCI] && options[:sCI] != status['sCI']
|
196
|
-
break if options[:n] && options[:n] != status['n']
|
197
|
-
break if options[:q] && options[:q] != status['q']
|
198
|
-
break if options[:s] && options[:s] != status['s']
|
199
|
-
found = true
|
200
|
-
break
|
201
|
-
end
|
202
|
-
found
|
203
|
-
end
|
204
|
-
item[:message] if item
|
205
|
-
end
|
206
|
-
|
207
|
-
def send_command component, args
|
208
|
-
raise NotReady unless @state == :ready
|
209
|
-
message = RSMP::CommandRequest.new({
|
221
|
+
def send_alarm_acknowledgement component, alarm_code
|
222
|
+
message = RSMP::AlarmAcknowledged.new({
|
210
223
|
"ntsOId" => '',
|
211
224
|
"xNId" => '',
|
212
225
|
"cId" => component,
|
213
|
-
"
|
226
|
+
"aCId" => alarm_code,
|
227
|
+
"xACId" => '',
|
228
|
+
"xNACId" => '',
|
229
|
+
"aSp" => 'Acknowledge'
|
214
230
|
})
|
215
231
|
send_message message
|
216
232
|
message
|
217
233
|
end
|
218
234
|
|
219
|
-
def
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
235
|
+
def send_command component, command_list, options={}
|
236
|
+
raise NotReady unless ready?
|
237
|
+
m_id = options[:m_id] || RSMP::Message.make_m_id
|
238
|
+
message = RSMP::CommandRequest.new({
|
239
|
+
"ntsOId" => '',
|
240
|
+
"xNId" => '',
|
241
|
+
"cId" => component,
|
242
|
+
"arg" => command_list,
|
243
|
+
"mId" => m_id
|
244
|
+
})
|
245
|
+
if options[:collect]
|
246
|
+
result = nil
|
247
|
+
task = @task.async do |task|
|
248
|
+
collect_options = options[:collect].merge command_list: command_list
|
249
|
+
collect_command_responses task, collect_options, m_id
|
250
|
+
end
|
251
|
+
send_message message
|
252
|
+
return message, task.wait
|
253
|
+
else
|
254
|
+
send_message message
|
255
|
+
message
|
228
256
|
end
|
229
|
-
item[:message] if item
|
230
257
|
end
|
231
258
|
|
232
259
|
def set_watchdog_interval interval
|
@@ -261,6 +288,5 @@ module RSMP
|
|
261
288
|
site_ids_changed
|
262
289
|
end
|
263
290
|
|
264
|
-
|
265
291
|
end
|
266
|
-
end
|
292
|
+
end
|
@@ -0,0 +1,181 @@
|
|
1
|
+
# waiting for various types of messages and reponses from remote sites
|
2
|
+
module RSMP
|
3
|
+
module SiteProxyWait
|
4
|
+
|
5
|
+
def wait_for_status_updates parent_task, options={}, &send_block
|
6
|
+
send_while_collecting parent_task, send_block do |task, m_id|
|
7
|
+
collect_status_updates_or_responses task, 'StatusUpdate', options, m_id
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def wait_for_status_responses parent_task, options={}, &send_block
|
12
|
+
send_while_collecting parent_task, send_block do |task, m_id|
|
13
|
+
collect_status_updates_or_responses task, 'StatusResponse', options, m_id
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def wait_for_command_responses parent_task, options={}, &send_block
|
18
|
+
send_while_collecting parent_task, send_block do |task, m_id|
|
19
|
+
collect_command_responses task, options, m_id
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def wait_for_alarm options={}
|
24
|
+
matching_alarm = nil
|
25
|
+
item = collect(@task,options.merge(type: "Alarm", with_message: true, num: 1)) do |item|
|
26
|
+
# TODO check components
|
27
|
+
matching_alarm = nil
|
28
|
+
alarm = item[:message]
|
29
|
+
next if options[:aCId] && options[:aCId] != alarm.attribute("aCId")
|
30
|
+
next if options[:aSp] && options[:aSp] != alarm.attribute("aSp")
|
31
|
+
next if options[:aS] && options[:aS] != alarm.attribute("aS")
|
32
|
+
matching_alarm = alarm
|
33
|
+
break
|
34
|
+
end
|
35
|
+
if item
|
36
|
+
{ message: item[:message], status: matching_alarm }
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def collect_status_updates task, options, m_id
|
41
|
+
collect_status_updates_or_responses task, 'StatusUpdate', options, m_id
|
42
|
+
end
|
43
|
+
|
44
|
+
def collect_status_responses task, options, m_id
|
45
|
+
collect_status_updates_or_responses task, 'StatusResponse', options, m_id
|
46
|
+
end
|
47
|
+
|
48
|
+
def collect_command_responses parent_task, options, m_id
|
49
|
+
task.annotate "wait for command response"
|
50
|
+
want = options[:command_list].clone
|
51
|
+
result = {}
|
52
|
+
item = collect(parent_task,options.merge({
|
53
|
+
type: ['CommandResponse','MessageNotAck'],
|
54
|
+
num: 1
|
55
|
+
})) do |item|
|
56
|
+
message = item[:message]
|
57
|
+
if message.is_a?(MessageNotAck)
|
58
|
+
if message.attribute('oMId') == m_id
|
59
|
+
# set result to an exception, but don't raise it.
|
60
|
+
# this will be returned by the task and stored as the task result
|
61
|
+
# when the parent task call wait() on the task, the exception
|
62
|
+
# will be raised in the parent task, and caught by rspec.
|
63
|
+
# rspec will then show the error and record the test as failed
|
64
|
+
m_id_short = RSMP::Message.shorten_m_id m_id, 8
|
65
|
+
result = RSMP::MessageRejected.new "Command request #{m_id_short} was rejected: #{message.attribute('rea')}"
|
66
|
+
next true # done, no more messages wanted
|
67
|
+
else
|
68
|
+
false
|
69
|
+
end
|
70
|
+
else
|
71
|
+
found = []
|
72
|
+
# look through querues
|
73
|
+
want.each_with_index do |query,i|
|
74
|
+
# look through items in message
|
75
|
+
item[:message].attributes['rvs'].each do |input|
|
76
|
+
ok = command_match? query, input
|
77
|
+
if ok
|
78
|
+
result[query] = input
|
79
|
+
found << i # record which queries where matched succesfully
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
# remove queries that where matched
|
84
|
+
found.sort.reverse.each do |i|
|
85
|
+
want.delete_at i
|
86
|
+
end
|
87
|
+
want.empty? # any queries left to match?
|
88
|
+
end
|
89
|
+
end
|
90
|
+
result
|
91
|
+
rescue Async::TimeoutError
|
92
|
+
raise RSMP::TimeoutError.new "Did not receive command response to #{m_id} within #{options[:timeout]}s"
|
93
|
+
end
|
94
|
+
|
95
|
+
def collect_status_updates_or_responses task, type, options, m_id
|
96
|
+
want = options[:status_list]
|
97
|
+
result = {}
|
98
|
+
# wait for a status update
|
99
|
+
item = collect(task,options.merge({
|
100
|
+
type: [type,'MessageNotAck'],
|
101
|
+
num: 1
|
102
|
+
})) do |item|
|
103
|
+
message = item[:message]
|
104
|
+
if message.is_a?(MessageNotAck)
|
105
|
+
if message.attribute('oMId') == m_id
|
106
|
+
# set result to an exception, but don't raise it.
|
107
|
+
# this will be returned by the task and stored as the task result
|
108
|
+
# when the parent task call wait() on the task, the exception
|
109
|
+
# will be raised in the parent task, and caught by rspec.
|
110
|
+
# rspec will then show the error and record the test as failed
|
111
|
+
m_id_short = RSMP::Message.shorten_m_id m_id, 8
|
112
|
+
result = RSMP::MessageRejected.new "Status request #{m_id_short} was rejected: #{message.attribute('rea')}"
|
113
|
+
next true # done, no more messages wanted
|
114
|
+
end
|
115
|
+
false
|
116
|
+
else
|
117
|
+
found = []
|
118
|
+
# look through querues
|
119
|
+
want.each_with_index do |query,i|
|
120
|
+
# look through status items in message
|
121
|
+
item[:message].attributes['sS'].each do |input|
|
122
|
+
ok = status_match? query, input
|
123
|
+
if ok
|
124
|
+
result[query] = input
|
125
|
+
found << i # record which queries where matched succesfully
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
# remove queries that where matched
|
130
|
+
found.sort.reverse.each do |i|
|
131
|
+
want.delete_at i
|
132
|
+
end
|
133
|
+
want.empty? # any queries left to match?
|
134
|
+
end
|
135
|
+
end
|
136
|
+
result
|
137
|
+
rescue Async::TimeoutError
|
138
|
+
type_str = {'StatusUpdate'=>'update', 'StatusResponse'=>'response'}[type]
|
139
|
+
raise RSMP::TimeoutError.new "Did not received status #{type_str} in reply to #{m_id} within #{options[:timeout]}s"
|
140
|
+
end
|
141
|
+
|
142
|
+
def status_match? query, item
|
143
|
+
return false if query['sCI'] && query['sCI'] != item['sCI']
|
144
|
+
return false if query['n'] && query['n'] != item['n']
|
145
|
+
return false if query['q'] && query['q'] != item['q']
|
146
|
+
if query['s'].is_a? Regexp
|
147
|
+
return false if query['s'] && item['s'] !~ query['s']
|
148
|
+
else
|
149
|
+
return false if query['s'] && item['s'] != query['s']
|
150
|
+
end
|
151
|
+
true
|
152
|
+
end
|
153
|
+
|
154
|
+
def command_match? query, item
|
155
|
+
return false if query['cCI'] && query['cCI'] != item['cCI']
|
156
|
+
return false if query['n'] && query['n'] != item['n']
|
157
|
+
if query['v'].is_a? Regexp
|
158
|
+
return false if query['v'] && item['v'] !~ query['v']
|
159
|
+
else
|
160
|
+
return false if query['v'] && item['v'] != query['v']
|
161
|
+
end
|
162
|
+
true
|
163
|
+
end
|
164
|
+
|
165
|
+
def send_while_collecting parent_task, send_block, &collect_block
|
166
|
+
m_id = RSMP::Message.make_m_id # make message id so we can start waiting for it
|
167
|
+
|
168
|
+
# wait for command responses in an async task
|
169
|
+
task = parent_task.async do |task|
|
170
|
+
collect_block.call task, m_id
|
171
|
+
end
|
172
|
+
|
173
|
+
# call block, it should send command request using the given m_id
|
174
|
+
send_block.call m_id
|
175
|
+
|
176
|
+
# wait for the response and return it, raise exception if NotAck received, it it timed out
|
177
|
+
task.wait
|
178
|
+
end
|
179
|
+
|
180
|
+
end
|
181
|
+
end
|