rsmp 0.1.11 → 0.1.21

Sign up to get free protection for your applications and to get access to all the features.
@@ -4,12 +4,12 @@
4
4
 
5
5
  module RSMP
6
6
  class Site < Node
7
- include SiteBase
7
+ include Components
8
8
 
9
9
  attr_reader :rsmp_versions, :site_settings, :logger, :proxies
10
10
 
11
11
  def initialize options={}
12
- initialize_site
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 build_connector settings
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 = build_connector({
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"]) { @sleep_condition.wait }
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
@@ -2,13 +2,14 @@
2
2
 
3
3
  module RSMP
4
4
  class SiteProxy < Proxy
5
- include SiteBase
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
- initialize_site
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, @rsmp_version
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 c_id
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, timeout=nil
127
- raise NotReady unless @state == :ready
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" => status_list
153
+ "sS" => request_list,
154
+ "mId" => m_id
133
155
  })
134
- send_message message
135
- return message, wait_for_status_response(message: message, timeout: timeout)
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 wait_for_status_response options
144
- raise ArgumentError unless options[:message]
145
- item = @archive.capture(@task, options.merge(
146
- type: ['StatusResponse','MessageNotAck'],
147
- with_message: true,
148
- num: 1
149
- )) do |item|
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" => status_list
187
+ "sS" => subscribe_list,
188
+ 'mId' => m_id
166
189
  })
167
- send_message message
168
- return message, wait_for_status_update(component: component, timeout: timeout)
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 @state == :ready
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 wait_for_status_update options={}
189
- raise ArgumentError unless options[:component]
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
- "arg" => args
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 process_command_response message
220
- log "Received #{message.type}", message: message, level: :log
221
- acknowledge message
222
- end
223
-
224
- def wait_for_command_response options
225
- raise ArgumentError unless options[:component]
226
- item = @archive.capture(@task,options.merge(num: 1, type: "CommandResponse", with_message: true)) do |item|
227
- # check component
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