rsmp 0.1.19 → 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.
@@ -0,0 +1,24 @@
1
+ # Distributes messages to listeners
2
+
3
+ module RSMP
4
+ module Notifier
5
+
6
+ def initialize_distributor
7
+ @listeners = []
8
+ end
9
+
10
+ def add_listener listener
11
+ raise ArgumentError unless listener
12
+ @listeners << listener unless @listeners.include? listener
13
+ end
14
+
15
+ def remove_listener listener
16
+ raise ArgumentError unless listener
17
+ @listeners.delete listener
18
+ end
19
+
20
+ def notify item
21
+ @listeners.each { |listener| listener.notify item }
22
+ end
23
+ end
24
+ end
@@ -1,22 +1,40 @@
1
- # Base class for a connection to a remote site or supervisor.
1
+ # Logging class for a connection to a remote site or supervisor.
2
2
 
3
3
  module RSMP
4
- class Proxy < Base
4
+ class Proxy
5
+ include Logging
5
6
  include Wait
7
+ include Notifier
6
8
 
7
- attr_reader :state, :archive, :connection_info, :sxl, :task
9
+ attr_reader :state, :archive, :connection_info, :sxl, :task, :collector
8
10
 
9
11
  def initialize options
10
- super options
12
+ initialize_logging options
11
13
  @settings = options[:settings]
12
14
  @task = options[:task]
13
15
  @socket = options[:socket]
14
16
  @ip = options[:ip]
15
17
  @connection_info = options[:info]
16
18
  @sxl = nil
19
+ initialize_distributor
20
+
21
+ prepare_collection options[:settings]['collect']
22
+
17
23
  clear
18
24
  end
19
25
 
26
+ def prepare_collection num
27
+ if num
28
+ @collector = RSMP::Collector.new self, num: num, ingoing: true, outgoing: true
29
+ add_listener @collector
30
+ end
31
+ end
32
+
33
+ def collect task, options, &block
34
+ probe = RSMP::Collector.new self, options
35
+ probe.collect task, &block
36
+ end
37
+
20
38
  def run
21
39
  start
22
40
  @reader.wait if @reader
@@ -213,6 +231,7 @@ module RSMP
213
231
  message.direction = :out
214
232
  expect_acknowledgement message
215
233
  @protocol.write_lines message.json
234
+ notify message: message
216
235
  log_send message, reason
217
236
  rescue EOFError, IOError
218
237
  buffer_message message
@@ -243,6 +262,7 @@ module RSMP
243
262
  attributes = Message.parse_attributes json
244
263
  message = Message.build attributes, json
245
264
  message.validate sxl
265
+ notify message: message
246
266
  expect_version_message(message) unless @version_determined
247
267
  process_message message
248
268
  process_deferred
@@ -342,7 +362,7 @@ module RSMP
342
362
  def wait_for_state state, timeout
343
363
  states = [state].flatten
344
364
  return if states.include?(@state)
345
- wait_for(@state_condition,timeout) do |s|
365
+ wait_for(@state_condition,timeout) do
346
366
  states.include?(@state)
347
367
  end
348
368
  @state
@@ -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 = []
@@ -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
@@ -55,6 +56,11 @@ module RSMP
55
56
  end
56
57
  end
57
58
 
59
+ def process_command_response message
60
+ log "Received #{message.type}", message: message, level: :log
61
+ acknowledge message
62
+ end
63
+
58
64
  def process_deferred
59
65
  supervisor.process_deferred
60
66
  end
@@ -132,38 +138,33 @@ module RSMP
132
138
  @supervisor.site_ids_changed
133
139
  end
134
140
 
135
- def fetch_status parent_task, options
136
- wait_for_status_responses(parent_task,options) do |m_id|
137
- request_status options.merge(m_id: m_id)
138
- end
139
- end
141
+ def request_status component, status_list, options={}
142
+ raise NotReady unless ready?
143
+ m_id = options[:m_id] || RSMP::Message.make_m_id
140
144
 
141
- # Convert from a short ruby hash:
142
- # {:S0001=>[:signalgroupstatus, :cyclecounter, :basecyclecounter, :stage]}
143
- # to an rsmp-style list:
144
- # [{"sCI"=>"S0001", "n"=>"signalgroupstatus"}, {"sCI"=>"S0001", "n"=>"cyclecounter"}, {"sCI"=>"S0001", "n"=>"basecyclecounter"}, {"sCI"=>"S0001", "n"=>"stage"}]
145
- #
146
- # If the input is already an array, just return it
147
- def convert_status_list list
148
- return list.clone if list.is_a? Array
149
- list.map do |status_code_id,names|
150
- names.map do |name|
151
- { 'sCI' => status_code_id.to_s, 'n' => name.to_s }
152
- end
153
- end.flatten
154
- end
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') }
155
148
 
156
- def request_status options
157
- raise NotReady unless ready?
158
149
  message = RSMP::StatusRequest.new({
159
150
  "ntsOId" => '',
160
151
  "xNId" => '',
161
- "cId" => options[:component],
162
- "sS" => convert_status_list(options[:status_list]),
163
- "mId" => options[:m_id]
152
+ "cId" => component,
153
+ "sS" => request_list,
154
+ "mId" => m_id
164
155
  })
165
- send_message message
166
- message
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
167
168
  end
168
169
 
169
170
  def process_status_response message
@@ -173,15 +174,31 @@ module RSMP
173
174
 
174
175
  def subscribe_to_status component, status_list, options={}
175
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') }
182
+
176
183
  message = RSMP::StatusSubscribe.new({
177
184
  "ntsOId" => '',
178
185
  "xNId" => '',
179
186
  "cId" => component,
180
- "sS" => convert_status_list(status_list),
181
- 'mId'=>options[:m_id]
187
+ "sS" => subscribe_list,
188
+ 'mId' => m_id
182
189
  })
183
- send_message message
184
- return message
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
185
202
  end
186
203
 
187
204
  def unsubscribe_to_status component, status_list
@@ -190,7 +207,7 @@ module RSMP
190
207
  "ntsOId" => '',
191
208
  "xNId" => '',
192
209
  "cId" => component,
193
- "sS" => convert_status_list(status_list)
210
+ "sS" => status_list
194
211
  })
195
212
  send_message message
196
213
  message
@@ -201,36 +218,6 @@ module RSMP
201
218
  acknowledge message
202
219
  end
203
220
 
204
- def status_match? query, item
205
- return false if query[:sCI] && query[:sCI] != item['sCI']
206
- return false if query[:n] && query[:n] != item['n']
207
- return false if query[:q] && query[:q] != item['q']
208
- if query[:s].is_a? Regexp
209
- return false if query[:s] && item['s'] !~ query[:s]
210
- else
211
- return false if query[:s] && item['s'] != query[:s]
212
- end
213
- true
214
- end
215
-
216
- def wait_for_alarm options={}
217
- raise ArgumentError.new("component argument is missing") unless options[:component]
218
- matching_alarm = nil
219
- item = @archive.capture(@task,options.merge(type: "Alarm", with_message: true, num: 1)) do |item|
220
- # TODO check components
221
- matching_alarm = nil
222
- alarm = item[:message]
223
- next if options[:aCId] && options[:aCId] != alarm.attribute("aCId")
224
- next if options[:aSp] && options[:aSp] != alarm.attribute("aSp")
225
- next if options[:aS] && options[:aS] != alarm.attribute("aS")
226
- matching_alarm = alarm
227
- break
228
- end
229
- if item
230
- { message: item[:message], status: matching_alarm }
231
- end
232
- end
233
-
234
221
  def send_alarm_acknowledgement component, alarm_code
235
222
  message = RSMP::AlarmAcknowledged.new({
236
223
  "ntsOId" => '',
@@ -245,33 +232,28 @@ module RSMP
245
232
  message
246
233
  end
247
234
 
248
- def wait_for_alarm_acknowledgement_response options
249
- raise ArgumentError.new("component argument is missing") unless options[:component]
250
- item = @archive.capture(@task,options.merge(
251
- num: 1,
252
- type: ['AlarmAcknowledgedResponse','MessageNotAck'],
253
- with_message: true
254
- )) do |item|
255
- if item[:message].type == 'MessageNotAck'
256
- next item[:message].attribute('oMId') == options[:message].m_id
257
- elsif item[:message].type == 'AlarmAcknowledgedResponse'
258
- next item[:message].attribute('cId') == options[:message].attribute('cId')
259
- end
260
- end
261
- item[:message] if item
262
- end
263
-
264
- def send_command component, args, options={}
235
+ def send_command component, command_list, options={}
265
236
  raise NotReady unless ready?
237
+ m_id = options[:m_id] || RSMP::Message.make_m_id
266
238
  message = RSMP::CommandRequest.new({
267
239
  "ntsOId" => '',
268
240
  "xNId" => '',
269
241
  "cId" => component,
270
- "arg" => args,
271
- "mId" => options[:m_id]
242
+ "arg" => command_list,
243
+ "mId" => m_id
272
244
  })
273
- send_message message
274
- message
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
256
+ end
275
257
  end
276
258
 
277
259
  def set_watchdog_interval interval
@@ -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