rsmp 0.1.13 → 0.1.29
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.gitmodules +0 -4
- data/Gemfile.lock +52 -52
- data/README.md +7 -1
- data/config/supervisor.yaml +9 -15
- data/config/tlc.yaml +26 -28
- data/documentation/classes_and_modules.md +65 -0
- data/documentation/message_distribution.md +23 -0
- data/lib/rsmp.rb +14 -5
- data/lib/rsmp/archive.rb +23 -22
- data/lib/rsmp/cli.rb +133 -102
- data/lib/rsmp/collector.rb +102 -0
- data/lib/rsmp/component.rb +13 -4
- data/lib/rsmp/{site_base.rb → components.rb} +8 -6
- data/lib/rsmp/convert/export/json_schema.rb +204 -0
- data/lib/rsmp/convert/import/yaml.rb +38 -0
- data/lib/rsmp/deep_merge.rb +11 -0
- data/lib/rsmp/error.rb +1 -1
- data/lib/rsmp/inspect.rb +46 -0
- data/lib/rsmp/listener.rb +23 -0
- data/lib/rsmp/logger.rb +6 -4
- data/lib/rsmp/{base.rb → logging.rb} +3 -3
- data/lib/rsmp/message.rb +46 -32
- data/lib/rsmp/node.rb +47 -12
- data/lib/rsmp/notifier.rb +29 -0
- data/lib/rsmp/proxy.rb +143 -71
- data/lib/rsmp/rsmp.rb +34 -23
- data/lib/rsmp/site.rb +35 -42
- data/lib/rsmp/site_proxy.rb +182 -78
- data/lib/rsmp/site_proxy_wait.rb +206 -0
- data/lib/rsmp/supervisor.rb +85 -49
- data/lib/rsmp/supervisor_proxy.rb +80 -34
- data/lib/rsmp/tlc.rb +761 -86
- data/lib/rsmp/version.rb +1 -1
- data/lib/rsmp/wait.rb +7 -8
- data/rsmp.gemspec +9 -21
- metadata +37 -142
- data/config/site.yaml +0 -40
- data/lib/rsmp/probe.rb +0 -114
- data/lib/rsmp/probe_collection.rb +0 -28
- data/lib/rsmp/supervisor_base.rb +0 -10
@@ -0,0 +1,38 @@
|
|
1
|
+
# Import SXL from YAML format
|
2
|
+
|
3
|
+
require 'yaml'
|
4
|
+
require 'json'
|
5
|
+
require 'fileutils'
|
6
|
+
|
7
|
+
module RSMP
|
8
|
+
module Convert
|
9
|
+
module Import
|
10
|
+
module YAML
|
11
|
+
|
12
|
+
def self.read path
|
13
|
+
convert ::YAML.load_file(path)
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.parse str
|
17
|
+
convert ::YAML.load(str)
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.convert yaml
|
21
|
+
sxl = {
|
22
|
+
alarms: {},
|
23
|
+
statuses: {},
|
24
|
+
commands: {}
|
25
|
+
}
|
26
|
+
|
27
|
+
yaml['objects'].each_pair do |type,object|
|
28
|
+
object["alarms"].each { |id,item| sxl[:alarms][id] = item }
|
29
|
+
object["statuses"].each { |id,item| sxl[:statuses][id] = item }
|
30
|
+
object["commands"].each { |id,item| sxl[:commands][id] = item }
|
31
|
+
end
|
32
|
+
sxl
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
data/lib/rsmp/error.rb
CHANGED
data/lib/rsmp/inspect.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
# Costume inspect, to reduce noise
|
2
|
+
#
|
3
|
+
# Instance variables of classes starting with Async or RSMP are shown
|
4
|
+
# with only their class name and object id, which reduces output,
|
5
|
+
# especially for deep object structures.
|
6
|
+
# Additionally, a list of variables to shown in short format can be passed.
|
7
|
+
#
|
8
|
+
# The short form is generated by using to_s() insted of inspect()
|
9
|
+
#
|
10
|
+
# Array#to_s and Hash#to_s usually show items, but here we show just number
|
11
|
+
# of items, when the short form is requested.
|
12
|
+
|
13
|
+
module RSMP
|
14
|
+
module Inspect
|
15
|
+
|
16
|
+
def inspector *short_items
|
17
|
+
instance_variables.map do |var_name|
|
18
|
+
var = instance_variable_get(var_name)
|
19
|
+
class_name = var.class.name
|
20
|
+
|
21
|
+
short = short_items.include?(var_name) ||
|
22
|
+
class_name.start_with?('Async') ||
|
23
|
+
class_name.start_with?('RSMP')
|
24
|
+
|
25
|
+
if short
|
26
|
+
if var.is_a? Array
|
27
|
+
"#{var_name}: #<#{class_name}:#{class_name.object_id}, #{var.size} items>"
|
28
|
+
elsif var.is_a? Hash
|
29
|
+
"#{var_name}: #<#{class_name}:#{class_name.object_id}, #{var.size} items>"
|
30
|
+
else
|
31
|
+
"#{var_name}: #{var.to_s}"
|
32
|
+
end
|
33
|
+
else
|
34
|
+
"#{var_name}: #{var.inspect}"
|
35
|
+
end
|
36
|
+
end.join(', ')
|
37
|
+
end
|
38
|
+
|
39
|
+
# override this if you want additional variable to be shown in the short format,
|
40
|
+
# or ottherwise change the inspect format
|
41
|
+
def inspect
|
42
|
+
"#<#{self.class.name}:#{self.object_id}, #{inspector}>"
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# Receives items from a Notifier, as long as it's
|
2
|
+
# installed as a listener.
|
3
|
+
|
4
|
+
module RSMP
|
5
|
+
class Listener
|
6
|
+
include Inspect
|
7
|
+
|
8
|
+
def initialize proxy, options={}
|
9
|
+
@proxy = proxy
|
10
|
+
end
|
11
|
+
|
12
|
+
def notify message
|
13
|
+
end
|
14
|
+
|
15
|
+
def listen &block
|
16
|
+
@proxy.add_listener self
|
17
|
+
yield
|
18
|
+
ensure
|
19
|
+
@proxy.remove_listener self
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
data/lib/rsmp/logger.rb
CHANGED
@@ -13,6 +13,7 @@ module RSMP
|
|
13
13
|
'component'=>false,
|
14
14
|
'level'=>false,
|
15
15
|
'ip'=>false,
|
16
|
+
'port'=>false,
|
16
17
|
'index'=>false,
|
17
18
|
'timestamp'=>true,
|
18
19
|
'json'=>false,
|
@@ -82,7 +83,6 @@ module RSMP
|
|
82
83
|
end
|
83
84
|
|
84
85
|
def colorize level, str
|
85
|
-
#p String.color_samples
|
86
86
|
if @settings["color"] == false || @settings["color"] == nil
|
87
87
|
str
|
88
88
|
elsif @settings["color"] == true
|
@@ -125,8 +125,9 @@ module RSMP
|
|
125
125
|
end
|
126
126
|
end
|
127
127
|
|
128
|
-
def dump archive, force:false
|
129
|
-
|
128
|
+
def dump archive, force:false, num:nil
|
129
|
+
num ||= archive.items.size
|
130
|
+
log = archive.items.last(num).map do |item|
|
130
131
|
str = build_output item
|
131
132
|
str = colorize item[:level], str
|
132
133
|
end
|
@@ -137,8 +138,9 @@ module RSMP
|
|
137
138
|
parts = []
|
138
139
|
parts << item[:index].to_s.ljust(7) if @settings["index"] == true
|
139
140
|
parts << item[:author].to_s.ljust(13) if @settings["author"] == true
|
140
|
-
parts << item[:timestamp].
|
141
|
+
parts << Clock.to_s(item[:timestamp]).ljust(24) unless @settings["timestamp"] == false
|
141
142
|
parts << item[:ip].to_s.ljust(22) unless @settings["ip"] == false
|
143
|
+
parts << item[:port].to_s.ljust(8) unless @settings["port"] == false
|
142
144
|
parts << item[:site_id].to_s.ljust(13) unless @settings["site_id"] == false
|
143
145
|
parts << item[:component_id].to_s.ljust(18) unless @settings["component"] == false
|
144
146
|
|
@@ -3,10 +3,10 @@
|
|
3
3
|
#
|
4
4
|
|
5
5
|
module RSMP
|
6
|
-
|
6
|
+
module Logging
|
7
7
|
attr_reader :archive, :logger
|
8
8
|
|
9
|
-
def
|
9
|
+
def initialize_logging options
|
10
10
|
@archive = options[:archive] || RSMP::Archive.new
|
11
11
|
@logger = options[:logger] || RSMP::Logger.new(options[:log_settings])
|
12
12
|
end
|
@@ -15,7 +15,7 @@ module RSMP
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def log str, options={}
|
18
|
-
default = { str:str, level: :log, author: author }
|
18
|
+
default = { str:str, level: :log, author: author, ip: @ip, port: @port }
|
19
19
|
prepared = RSMP::Archive.prepare_item default.merge(options)
|
20
20
|
@archive.add prepared
|
21
21
|
@logger.log prepared
|
data/lib/rsmp/message.rb
CHANGED
@@ -1,33 +1,18 @@
|
|
1
|
+
require 'rsmp_schemer'
|
2
|
+
|
1
3
|
# rsmp messages
|
2
4
|
module RSMP
|
3
5
|
class Message
|
6
|
+
include Inspect
|
4
7
|
|
5
|
-
attr_reader :now, :attributes, :out
|
8
|
+
attr_reader :now, :attributes, :out
|
9
|
+
attr_reader :timestamp # this is an internal timestamp recording when we receive/send
|
6
10
|
attr_accessor :json, :direction
|
7
11
|
|
8
|
-
def self.
|
9
|
-
|
10
|
-
schema_path = File.join(File.dirname(__dir__),'rsmp_schema','schema')
|
11
|
-
@@schemas = {}
|
12
|
-
|
13
|
-
core_schema_path = File.join(schema_path,'core','rsmp.json')
|
14
|
-
@@schemas[nil] = JSONSchemer.schema( Pathname.new(core_schema_path) )
|
15
|
-
|
16
|
-
tlc_schema_path = File.join(schema_path,'tlc','sxl.json')
|
17
|
-
@@schemas['traffic_light_controller'] = JSONSchemer.schema( Pathname.new(tlc_schema_path) )
|
18
|
-
|
19
|
-
@@schemas
|
20
|
-
end
|
21
|
-
|
22
|
-
def self.get_schema sxl=nil
|
23
|
-
schema = @@schemas[sxl]
|
24
|
-
raise SchemaError.new("Unknown schema #{sxl}") unless schema
|
25
|
-
schema
|
12
|
+
def self.make_m_id
|
13
|
+
SecureRandom.uuid()
|
26
14
|
end
|
27
15
|
|
28
|
-
@@schemas = load_schemas
|
29
|
-
|
30
|
-
|
31
16
|
def self.parse_attributes json
|
32
17
|
raise ArgumentError unless json
|
33
18
|
JSON.parse json
|
@@ -46,6 +31,8 @@ module RSMP
|
|
46
31
|
message = Version.new attributes
|
47
32
|
when "AggregatedStatus"
|
48
33
|
message = AggregatedStatus.new attributes
|
34
|
+
when "AggregatedStatusRequest"
|
35
|
+
message = AggregatedStatusRequest.new attributes
|
49
36
|
when "Watchdog"
|
50
37
|
message = Watchdog.new attributes
|
51
38
|
when "Alarm"
|
@@ -80,8 +67,12 @@ module RSMP
|
|
80
67
|
@attributes["mId"]
|
81
68
|
end
|
82
69
|
|
70
|
+
def self.shorten_m_id m_id, length=4
|
71
|
+
m_id[0..length-1]
|
72
|
+
end
|
73
|
+
|
83
74
|
def m_id_short
|
84
|
-
@attributes["mId"]
|
75
|
+
Message.shorten_m_id @attributes["mId"]
|
85
76
|
end
|
86
77
|
|
87
78
|
def attribute key
|
@@ -118,7 +109,9 @@ module RSMP
|
|
118
109
|
end
|
119
110
|
|
120
111
|
def initialize attributes = {}
|
121
|
-
@timestamp =
|
112
|
+
@timestamp = Time.now # this timestamp is for internal use, and does not the clock
|
113
|
+
# in the node, which can be set by an rsmp supervisor
|
114
|
+
|
122
115
|
@attributes = { "mType"=> "rSMsg" }.merge attributes
|
123
116
|
|
124
117
|
ensure_message_id
|
@@ -126,16 +119,13 @@ module RSMP
|
|
126
119
|
|
127
120
|
def ensure_message_id
|
128
121
|
# if message id is empty, generate a new one
|
129
|
-
@attributes["mId"] ||=
|
122
|
+
@attributes["mId"] ||= Message.make_m_id
|
130
123
|
end
|
131
124
|
|
132
|
-
def validate
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
error_string = errors.map do |item|
|
137
|
-
[item['data_pointer'],item['type'],item['details']].compact.join(' ')
|
138
|
-
end.join(", ")
|
125
|
+
def validate schemas
|
126
|
+
errors = RSMP::Schemer.validate attributes, schemas
|
127
|
+
if errors
|
128
|
+
error_string = errors.compact.join(', ').strip
|
139
129
|
raise SchemaError.new error_string
|
140
130
|
end
|
141
131
|
end
|
@@ -189,6 +179,14 @@ module RSMP
|
|
189
179
|
end
|
190
180
|
end
|
191
181
|
|
182
|
+
class AggregatedStatusRequest < Message
|
183
|
+
def initialize attributes = {}
|
184
|
+
super({
|
185
|
+
"type" => "AggregatedStatusRequest",
|
186
|
+
}.merge attributes)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
192
190
|
class Alarm < Message
|
193
191
|
def initialize attributes = {}
|
194
192
|
super({
|
@@ -197,6 +195,22 @@ module RSMP
|
|
197
195
|
end
|
198
196
|
end
|
199
197
|
|
198
|
+
class AlarmRequest < Message
|
199
|
+
def initialize attributes = {}
|
200
|
+
super({
|
201
|
+
"type" => "Alarm",
|
202
|
+
}.merge attributes)
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
class AlarmAcknowledged < Message
|
207
|
+
def initialize attributes = {}
|
208
|
+
super({
|
209
|
+
"type" => "Alarm",
|
210
|
+
}.merge attributes)
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
200
214
|
class Watchdog < Message
|
201
215
|
def initialize attributes = {}
|
202
216
|
super({
|
data/lib/rsmp/node.rb
CHANGED
@@ -1,23 +1,58 @@
|
|
1
|
-
#
|
2
|
-
#
|
3
|
-
# Handles a single connection to a supervisor.
|
4
|
-
# We connect to the supervisor.
|
1
|
+
# Base class for sites and supervisors
|
5
2
|
|
6
3
|
module RSMP
|
7
|
-
class Node
|
8
|
-
|
4
|
+
class Node
|
5
|
+
include Logging
|
6
|
+
include Wait
|
7
|
+
include Inspect
|
8
|
+
|
9
|
+
attr_reader :archive, :logger, :task, :deferred, :error_condition, :clock
|
9
10
|
|
10
11
|
def initialize options
|
11
|
-
|
12
|
+
initialize_logging options
|
13
|
+
@task = options[:task]
|
14
|
+
@deferred = []
|
15
|
+
@clock = Clock.new
|
16
|
+
@error_condition = Async::Notification.new
|
17
|
+
end
|
18
|
+
|
19
|
+
def notify_error e, options={}
|
20
|
+
if options[:level] == :internal
|
21
|
+
log ["#{e.to_s} in task: #{Async::Task.current.to_s}",e.backtrace].flatten.join("\n"), level: :error
|
22
|
+
end
|
23
|
+
@error_condition.signal e
|
24
|
+
end
|
25
|
+
|
26
|
+
def defer item
|
27
|
+
@deferred << item
|
28
|
+
end
|
29
|
+
|
30
|
+
def process_deferred
|
31
|
+
cloned = @deferred.clone # clone in case do_deferred restarts the current task
|
32
|
+
@deferred.clear
|
33
|
+
cloned.each do |item|
|
34
|
+
do_deferred item
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def do_deferred item
|
39
|
+
end
|
40
|
+
|
41
|
+
def do_start task
|
42
|
+
task.annotate self.class.to_s
|
43
|
+
@task = task
|
44
|
+
start_action
|
45
|
+
idle
|
12
46
|
end
|
13
47
|
|
14
48
|
def start
|
15
49
|
starting
|
16
|
-
|
17
|
-
task
|
18
|
-
|
19
|
-
|
20
|
-
|
50
|
+
if @task
|
51
|
+
do_start @task
|
52
|
+
else
|
53
|
+
Async do |task|
|
54
|
+
do_start task
|
55
|
+
end
|
21
56
|
end
|
22
57
|
rescue Errno::EADDRINUSE => e
|
23
58
|
log "Cannot start: #{e.to_s}", level: :error
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# Distributes messages to listeners
|
2
|
+
|
3
|
+
module RSMP
|
4
|
+
module Notifier
|
5
|
+
include Inspect
|
6
|
+
|
7
|
+
def inspect
|
8
|
+
"#<#{self.class.name}:#{self.object_id}, #{inspector(:@listeners)}>"
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize_distributor
|
12
|
+
@listeners = []
|
13
|
+
end
|
14
|
+
|
15
|
+
def add_listener listener
|
16
|
+
raise ArgumentError unless listener
|
17
|
+
@listeners << listener unless @listeners.include? listener
|
18
|
+
end
|
19
|
+
|
20
|
+
def remove_listener listener
|
21
|
+
raise ArgumentError unless listener
|
22
|
+
@listeners.delete listener
|
23
|
+
end
|
24
|
+
|
25
|
+
def notify message
|
26
|
+
@listeners.each { |listener| listener.notify message }
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/rsmp/proxy.rb
CHANGED
@@ -1,24 +1,61 @@
|
|
1
|
-
#
|
1
|
+
# Logging class for a connection to a remote site or supervisor.
|
2
|
+
|
3
|
+
require 'rubygems'
|
2
4
|
|
3
5
|
module RSMP
|
4
|
-
class Proxy
|
5
|
-
|
6
|
+
class Proxy
|
7
|
+
WRAPPING_DELIMITER = "\f"
|
8
|
+
|
9
|
+
include Logging
|
10
|
+
include Wait
|
11
|
+
include Notifier
|
12
|
+
include Inspect
|
13
|
+
|
14
|
+
attr_reader :state, :archive, :connection_info, :sxl, :task, :collector
|
6
15
|
|
7
16
|
def initialize options
|
8
|
-
|
17
|
+
initialize_logging options
|
9
18
|
@settings = options[:settings]
|
10
19
|
@task = options[:task]
|
11
20
|
@socket = options[:socket]
|
12
21
|
@ip = options[:ip]
|
22
|
+
@port = options[:port]
|
13
23
|
@connection_info = options[:info]
|
14
24
|
@sxl = nil
|
25
|
+
@site_settings = nil # can't pick until we know the site id
|
26
|
+
initialize_distributor
|
27
|
+
|
28
|
+
prepare_collection @settings['collect']
|
15
29
|
clear
|
16
30
|
end
|
17
31
|
|
32
|
+
def inspect
|
33
|
+
"#<#{self.class.name}:#{self.object_id}, #{inspector(
|
34
|
+
:@acknowledgements,:@settings,:@site_settings
|
35
|
+
)}>"
|
36
|
+
end
|
37
|
+
|
38
|
+
def clock
|
39
|
+
node.clock
|
40
|
+
end
|
41
|
+
|
42
|
+
def prepare_collection num
|
43
|
+
if num
|
44
|
+
@collector = RSMP::Collector.new self, num: num, ingoing: true, outgoing: true
|
45
|
+
add_listener @collector
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def collect task, options, &block
|
50
|
+
collector = RSMP::Collector.new self, options
|
51
|
+
collector.collect task, &block
|
52
|
+
end
|
53
|
+
|
18
54
|
def run
|
19
55
|
start
|
20
56
|
@reader.wait if @reader
|
21
|
-
|
57
|
+
ensure
|
58
|
+
stop unless [:stopped, :stopping].include? @state
|
22
59
|
end
|
23
60
|
|
24
61
|
def ready?
|
@@ -69,7 +106,7 @@ module RSMP
|
|
69
106
|
@reader = @task.async do |task|
|
70
107
|
task.annotate "reader"
|
71
108
|
@stream = Async::IO::Stream.new(@socket)
|
72
|
-
@protocol = Async::IO::Protocol::Line.new(@stream,
|
109
|
+
@protocol = Async::IO::Protocol::Line.new(@stream,WRAPPING_DELIMITER) # rsmp messages are json terminated with a form-feed
|
73
110
|
|
74
111
|
while json = @protocol.read_line
|
75
112
|
beginning = Time.now
|
@@ -97,33 +134,45 @@ module RSMP
|
|
97
134
|
log "Connection reset by peer", level: :warning
|
98
135
|
rescue Errno::EPIPE
|
99
136
|
log "Broken pipe", level: :warning
|
100
|
-
rescue SystemCallError => e # all ERRNO errors
|
101
|
-
log "Proxy exception: #{e.to_s}", level: :error
|
102
137
|
rescue StandardError => e
|
103
|
-
|
138
|
+
notify_error e, level: :internal
|
104
139
|
end
|
105
140
|
end
|
106
141
|
|
142
|
+
def notify_error e, options={}
|
143
|
+
node.notify_error e, options
|
144
|
+
end
|
145
|
+
|
107
146
|
def start_watchdog
|
108
|
-
log "Starting watchdog with interval #{@
|
147
|
+
log "Starting watchdog with interval #{@site_settings['intervals']['watchdog']} seconds", level: :debug
|
109
148
|
send_watchdog
|
110
149
|
@watchdog_started = true
|
111
150
|
end
|
112
151
|
|
113
152
|
def start_timer
|
114
153
|
name = "timer"
|
115
|
-
interval = @
|
154
|
+
interval = @site_settings['intervals']['timer'] || 1
|
116
155
|
log "Starting #{name} with interval #{interval} seconds", level: :debug
|
117
|
-
@latest_watchdog_received =
|
156
|
+
@latest_watchdog_received = Clock.now
|
118
157
|
|
119
158
|
@timer = @task.async do |task|
|
120
159
|
task.annotate "timer"
|
121
160
|
next_time = Time.now.to_f
|
122
161
|
loop do
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
162
|
+
begin
|
163
|
+
now = Clock.now
|
164
|
+
timer(now)
|
165
|
+
rescue EOFError => e
|
166
|
+
log "Timer: Connection closed: #{e}", level: :warning
|
167
|
+
rescue IOError => e
|
168
|
+
log "Timer: IOError", level: :warning
|
169
|
+
rescue Errno::ECONNRESET
|
170
|
+
log "Timer: Connection reset by peer", level: :warning
|
171
|
+
rescue Errno::EPIPE => e
|
172
|
+
log "Timer: Broken pipe", level: :warning
|
173
|
+
rescue StandardError => e
|
174
|
+
notify_error e, level: :internal
|
175
|
+
end
|
127
176
|
ensure
|
128
177
|
next_time += interval
|
129
178
|
duration = next_time - Time.now.to_f
|
@@ -133,14 +182,14 @@ module RSMP
|
|
133
182
|
end
|
134
183
|
|
135
184
|
def timer now
|
136
|
-
|
137
|
-
|
138
|
-
|
185
|
+
watchdog_send_timer now
|
186
|
+
check_ack_timeout now
|
187
|
+
check_watchdog_timeout now
|
139
188
|
end
|
140
189
|
|
141
|
-
def
|
190
|
+
def watchdog_send_timer now
|
142
191
|
return unless @watchdog_started
|
143
|
-
return if @
|
192
|
+
return if @site_settings['intervals']['watchdog'] == :never
|
144
193
|
|
145
194
|
if @latest_watchdog_send_at == nil
|
146
195
|
send_watchdog now
|
@@ -148,47 +197,38 @@ module RSMP
|
|
148
197
|
# we add half the timer interval to pick the timer
|
149
198
|
# event closes to the wanted wathcdog interval
|
150
199
|
diff = now - @latest_watchdog_send_at
|
151
|
-
if (diff + 0.5*@
|
200
|
+
if (diff + 0.5*@site_settings['intervals']['timer']) >= (@site_settings['intervals']['watchdog'])
|
152
201
|
send_watchdog now
|
153
202
|
end
|
154
203
|
end
|
155
|
-
rescue StandardError => e
|
156
|
-
log ["Watchdog error: #{e}",e.backtrace].flatten.join("\n"), level: :error
|
157
204
|
end
|
158
205
|
|
159
|
-
def send_watchdog now=
|
160
|
-
|
161
|
-
message = Watchdog.new( {"wTs" => RSMP.now_object_to_string(now)})
|
206
|
+
def send_watchdog now=Clock.now
|
207
|
+
message = Watchdog.new( {"wTs" => clock.to_s})
|
162
208
|
send_message message
|
163
209
|
@latest_watchdog_send_at = now
|
164
210
|
end
|
165
211
|
|
166
212
|
def check_ack_timeout now
|
167
|
-
timeout = @
|
213
|
+
timeout = @site_settings['timeouts']['acknowledgement']
|
168
214
|
# hash cannot be modify during iteration, so clone it
|
169
215
|
@awaiting_acknowledgement.clone.each_pair do |m_id, message|
|
170
216
|
latest = message.timestamp + timeout
|
171
217
|
if now > latest
|
172
218
|
log "No acknowledgements for #{message.type} #{message.m_id_short} within #{timeout} seconds", level: :error
|
173
219
|
stop
|
174
|
-
return true
|
175
220
|
end
|
176
221
|
end
|
177
|
-
false
|
178
222
|
end
|
179
223
|
|
180
224
|
def check_watchdog_timeout now
|
181
|
-
|
182
|
-
timeout = @settings["watchdog_timeout"]
|
225
|
+
timeout = @site_settings['timeouts']['watchdog']
|
183
226
|
latest = @latest_watchdog_received + timeout
|
184
227
|
left = latest - now
|
185
|
-
#log "Check watchdog, time:#{timeout}, last:#{@latest_watchdog_received}, now: #{now}, latest:#{latest}, left #{left}, fail:#{left<0}", level: :debug
|
186
228
|
if left < 0
|
187
229
|
log "No Watchdog within #{timeout} seconds", level: :error
|
188
230
|
stop
|
189
|
-
return true
|
190
231
|
end
|
191
|
-
false
|
192
232
|
end
|
193
233
|
|
194
234
|
def stop_tasks
|
@@ -200,23 +240,36 @@ module RSMP
|
|
200
240
|
super str, options.merge(ip: @ip, port: @port, site_id: @site_id)
|
201
241
|
end
|
202
242
|
|
203
|
-
def
|
243
|
+
def get_schemas
|
244
|
+
# normally we have an sxl, but during connection, it hasn't been established yet
|
245
|
+
# at these times we only validate against the core schema
|
246
|
+
# TODO
|
247
|
+
# what schema should we use to validate the intial Version and MessageAck messages?
|
248
|
+
schemas = { core: '3.1.5' }
|
249
|
+
schemas[sxl] = sxl_version if sxl
|
250
|
+
schemas
|
251
|
+
end
|
252
|
+
|
253
|
+
def send_message message, reason=nil, validate: true
|
204
254
|
raise IOError unless @protocol
|
205
|
-
message.generate_json
|
206
|
-
message.validate sxl
|
207
255
|
message.direction = :out
|
256
|
+
message.generate_json
|
257
|
+
message.validate get_schemas unless validate==false
|
208
258
|
expect_acknowledgement message
|
209
259
|
@protocol.write_lines message.json
|
260
|
+
notify message
|
210
261
|
log_send message, reason
|
211
262
|
rescue EOFError, IOError
|
212
263
|
buffer_message message
|
213
264
|
rescue SchemaError => e
|
214
|
-
|
265
|
+
str = "Could not send #{message.type} because schema validation failed: #{e.message}"
|
266
|
+
log str, message: message, level: :error
|
267
|
+
notify_error e.exception("#{str} #{message.json}")
|
215
268
|
end
|
216
269
|
|
217
270
|
def buffer_message message
|
218
271
|
# TODO
|
219
|
-
log "Cannot send #{message.type} because the connection is closed.", message: message, level: :error
|
272
|
+
#log "Cannot send #{message.type} because the connection is closed.", message: message, level: :error
|
220
273
|
end
|
221
274
|
|
222
275
|
def log_send message, reason=nil
|
@@ -236,25 +289,38 @@ module RSMP
|
|
236
289
|
def process_packet json
|
237
290
|
attributes = Message.parse_attributes json
|
238
291
|
message = Message.build attributes, json
|
239
|
-
message.validate
|
292
|
+
message.validate get_schemas
|
293
|
+
notify message
|
240
294
|
expect_version_message(message) unless @version_determined
|
241
295
|
process_message message
|
296
|
+
process_deferred
|
242
297
|
message
|
243
298
|
rescue InvalidPacket => e
|
244
|
-
|
299
|
+
str = "Received invalid package, must be valid JSON but got #{json.size} bytes: #{e.message}"
|
300
|
+
notify_error e.exception(str)
|
301
|
+
log str, level: :warning
|
245
302
|
nil
|
246
303
|
rescue MalformedMessage => e
|
247
|
-
|
304
|
+
str = "Received malformed message, #{e.message}"
|
305
|
+
notify_error e.exception(str)
|
306
|
+
log str, message: Malformed.new(attributes), level: :warning
|
248
307
|
# cannot send NotAcknowledged for a malformed message since we can't read it, just ignore it
|
249
308
|
nil
|
250
309
|
rescue SchemaError => e
|
251
|
-
|
310
|
+
str = "Received invalid #{message.type}, schema errors: #{e.message}"
|
311
|
+
log str, message: message, level: :warning
|
312
|
+
notify_error e.exception("#{str} #{message.json}")
|
313
|
+
dont_acknowledge message, str
|
252
314
|
message
|
253
315
|
rescue InvalidMessage => e
|
254
|
-
|
316
|
+
str = "Received", "invalid #{message.type}, #{e.message}"
|
317
|
+
notify_error e.exception("#{str} #{message.json}")
|
318
|
+
dont_acknowledge message, str
|
255
319
|
message
|
256
320
|
rescue FatalError => e
|
257
|
-
|
321
|
+
str = "Rejected #{message.type},"
|
322
|
+
notify_error e.exception("#{str} #{message.json}")
|
323
|
+
dont_acknowledge message, str, "#{e.message}"
|
258
324
|
stop
|
259
325
|
message
|
260
326
|
end
|
@@ -294,13 +360,20 @@ module RSMP
|
|
294
360
|
dont_acknowledge message, "Received", "extraneous Version message"
|
295
361
|
end
|
296
362
|
|
363
|
+
def rsmp_versions
|
364
|
+
return ['3.1.5'] if @site_settings["rsmp_versions"] == 'latest'
|
365
|
+
return ['3.1.1','3.1.2','3.1.3','3.1.4','3.1.5'] if @site_settings["rsmp_versions"] == 'all'
|
366
|
+
@site_settings["rsmp_versions"]
|
367
|
+
end
|
368
|
+
|
297
369
|
def check_rsmp_version message
|
370
|
+
versions = rsmp_versions
|
298
371
|
# find versions that both we and the client support
|
299
|
-
candidates = message.versions &
|
372
|
+
candidates = message.versions & versions
|
300
373
|
if candidates.any?
|
301
|
-
@rsmp_version = candidates.
|
374
|
+
@rsmp_version = candidates.sort_by { |v| Gem::Version.new(v) }.last # pick latest version
|
302
375
|
else
|
303
|
-
raise FatalError.new "RSMP versions [#{message.versions.join(',')}] requested, but only [#{
|
376
|
+
raise FatalError.new "RSMP versions [#{message.versions.join(',')}] requested, but only [#{versions.join(',')}] supported."
|
304
377
|
end
|
305
378
|
end
|
306
379
|
|
@@ -335,14 +408,24 @@ module RSMP
|
|
335
408
|
def wait_for_state state, timeout
|
336
409
|
states = [state].flatten
|
337
410
|
return if states.include?(@state)
|
338
|
-
|
411
|
+
wait_for(@state_condition,timeout) do
|
339
412
|
states.include?(@state)
|
340
413
|
end
|
341
414
|
@state
|
415
|
+
rescue Async::TimeoutError
|
416
|
+
raise RSMP::TimeoutError.new "Did not reach state #{state} within #{timeout}s"
|
342
417
|
end
|
343
418
|
|
344
419
|
def send_version site_id, rsmp_versions
|
345
|
-
|
420
|
+
if rsmp_versions=='latest'
|
421
|
+
versions = ['3.1.5']
|
422
|
+
elsif rsmp_versions=='all'
|
423
|
+
versions = ['3.1.1','3.1.2','3.1.3','3.1.4','3.1.5']
|
424
|
+
else
|
425
|
+
versions = [rsmp_versions].flatten
|
426
|
+
end
|
427
|
+
versions_array = versions.map {|v| {"vers" => v} }
|
428
|
+
|
346
429
|
site_id_array = [site_id].flatten.map {|id| {"sId" => id} }
|
347
430
|
|
348
431
|
version_response = Version.new({
|
@@ -428,7 +511,7 @@ module RSMP
|
|
428
511
|
|
429
512
|
def process_watchdog message
|
430
513
|
log "Received #{message.type}", message: message, level: :log
|
431
|
-
@latest_watchdog_received =
|
514
|
+
@latest_watchdog_received = Clock.now
|
432
515
|
acknowledge message
|
433
516
|
end
|
434
517
|
|
@@ -445,27 +528,16 @@ module RSMP
|
|
445
528
|
def version_acknowledged
|
446
529
|
end
|
447
530
|
|
448
|
-
def wait_for_acknowledgement original, timeout
|
449
|
-
raise ArgumentError unless original
|
450
|
-
RSMP::Wait.wait_for(@task,@acknowledgement_condition,timeout) do |message|
|
451
|
-
message.is_a?(MessageAck) &&
|
452
|
-
message.attributes["oMId"] == original.m_id
|
453
|
-
end
|
454
|
-
end
|
455
|
-
|
456
|
-
def wait_for_not_acknowledged original, timeout
|
531
|
+
def wait_for_acknowledgement original, timeout
|
457
532
|
raise ArgumentError unless original
|
458
|
-
|
459
|
-
message.is_a?(MessageNotAck) &&
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
def wait_for_acknowledgements timeout
|
465
|
-
return if @awaiting_acknowledgement.empty?
|
466
|
-
RSMP::Wait.wait_for(@task,@acknowledgement_condition,timeout) do |message|
|
467
|
-
@awaiting_acknowledgement.empty?
|
533
|
+
wait_for(@acknowledgement_condition,timeout) do |message|
|
534
|
+
if message.is_a?(MessageNotAck) && message.attributes["oMId"] == original.m_id
|
535
|
+
raise RSMP::MessageRejected.new(message.attributes['rea'])
|
536
|
+
end
|
537
|
+
message.is_a?(MessageAck) && message.attributes["oMId"] == original.m_id
|
468
538
|
end
|
539
|
+
rescue Async::TimeoutError
|
540
|
+
raise RSMP::TimeoutError.new("Acknowledgement for #{original.type} #{original.m_id} not received within #{timeout}s")
|
469
541
|
end
|
470
542
|
|
471
543
|
def node
|