rsmp 0.1.13 → 0.1.29
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/.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
|