rsmp 0.1.12 → 0.1.27
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/site.yaml +2 -2
- data/config/supervisor.yaml +12 -6
- data/config/tlc.yaml +52 -0
- 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 +131 -92
- data/lib/rsmp/collector.rb +102 -0
- data/lib/rsmp/component.rb +14 -2
- 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/error.rb +10 -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 +124 -63
- data/lib/rsmp/rsmp.rb +34 -23
- data/lib/rsmp/site.rb +27 -12
- data/lib/rsmp/site_proxy.rb +165 -73
- data/lib/rsmp/site_proxy_wait.rb +206 -0
- data/lib/rsmp/supervisor.rb +53 -23
- data/lib/rsmp/supervisor_proxy.rb +101 -41
- data/lib/rsmp/tlc.rb +907 -0
- data/lib/rsmp/version.rb +1 -1
- data/lib/rsmp/wait.rb +7 -8
- data/rsmp.gemspec +9 -21
- metadata +38 -141
- 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
@@ -23,7 +23,7 @@ module RSMP
|
|
23
23
|
class MissingWatchdog < Error
|
24
24
|
end
|
25
25
|
|
26
|
-
class
|
26
|
+
class MessageRejected < Error
|
27
27
|
end
|
28
28
|
|
29
29
|
class MissingAttribute < InvalidMessage
|
@@ -43,4 +43,13 @@ module RSMP
|
|
43
43
|
|
44
44
|
class UnknownComponent < Error
|
45
45
|
end
|
46
|
+
|
47
|
+
class UnknownCommand < Error
|
48
|
+
end
|
49
|
+
|
50
|
+
class UnknownStatus < Error
|
51
|
+
end
|
52
|
+
|
53
|
+
class ConfigurationError < Error
|
54
|
+
end
|
46
55
|
end
|
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
|
+
initialize_distributor
|
26
|
+
|
27
|
+
prepare_collection options[:settings]['collect']
|
28
|
+
|
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,13 +134,15 @@ 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
147
|
log "Starting watchdog with interval #{@settings["watchdog_interval"]} seconds", level: :debug
|
109
148
|
send_watchdog
|
@@ -114,27 +153,41 @@ module RSMP
|
|
114
153
|
name = "timer"
|
115
154
|
interval = @settings["timer_interval"] || 1
|
116
155
|
log "Starting #{name} with interval #{interval} seconds", level: :debug
|
117
|
-
@latest_watchdog_received =
|
156
|
+
@latest_watchdog_received = Clock.now
|
157
|
+
|
118
158
|
@timer = @task.async do |task|
|
119
159
|
task.annotate "timer"
|
160
|
+
next_time = Time.now.to_f
|
120
161
|
loop do
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
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
|
125
176
|
ensure
|
126
|
-
|
177
|
+
next_time += interval
|
178
|
+
duration = next_time - Time.now.to_f
|
179
|
+
task.sleep duration
|
127
180
|
end
|
128
181
|
end
|
129
182
|
end
|
130
183
|
|
131
184
|
def timer now
|
132
|
-
|
133
|
-
|
134
|
-
|
185
|
+
watchdog_send_timer now
|
186
|
+
check_ack_timeout now
|
187
|
+
check_watchdog_timeout now
|
135
188
|
end
|
136
189
|
|
137
|
-
def
|
190
|
+
def watchdog_send_timer now
|
138
191
|
return unless @watchdog_started
|
139
192
|
return if @settings["watchdog_interval"] == :never
|
140
193
|
|
@@ -148,13 +201,10 @@ module RSMP
|
|
148
201
|
send_watchdog now
|
149
202
|
end
|
150
203
|
end
|
151
|
-
rescue StandardError => e
|
152
|
-
log ["Watchdog error: #{e}",e.backtrace].flatten.join("\n"), level: :error
|
153
204
|
end
|
154
205
|
|
155
|
-
def send_watchdog now=
|
156
|
-
|
157
|
-
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})
|
158
208
|
send_message message
|
159
209
|
@latest_watchdog_send_at = now
|
160
210
|
end
|
@@ -167,24 +217,18 @@ module RSMP
|
|
167
217
|
if now > latest
|
168
218
|
log "No acknowledgements for #{message.type} #{message.m_id_short} within #{timeout} seconds", level: :error
|
169
219
|
stop
|
170
|
-
return true
|
171
220
|
end
|
172
221
|
end
|
173
|
-
false
|
174
222
|
end
|
175
223
|
|
176
224
|
def check_watchdog_timeout now
|
177
|
-
|
178
225
|
timeout = @settings["watchdog_timeout"]
|
179
226
|
latest = @latest_watchdog_received + timeout
|
180
227
|
left = latest - now
|
181
|
-
#log "Check watchdog, time:#{timeout}, last:#{@latest_watchdog_received}, now: #{now}, latest:#{latest}, left #{left}, fail:#{left<0}", level: :debug
|
182
228
|
if left < 0
|
183
229
|
log "No Watchdog within #{timeout} seconds", level: :error
|
184
230
|
stop
|
185
|
-
return true
|
186
231
|
end
|
187
|
-
false
|
188
232
|
end
|
189
233
|
|
190
234
|
def stop_tasks
|
@@ -196,23 +240,36 @@ module RSMP
|
|
196
240
|
super str, options.merge(ip: @ip, port: @port, site_id: @site_id)
|
197
241
|
end
|
198
242
|
|
199
|
-
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
|
200
254
|
raise IOError unless @protocol
|
201
|
-
message.generate_json
|
202
|
-
message.validate sxl
|
203
255
|
message.direction = :out
|
256
|
+
message.generate_json
|
257
|
+
message.validate get_schemas unless validate==false
|
204
258
|
expect_acknowledgement message
|
205
259
|
@protocol.write_lines message.json
|
260
|
+
notify message
|
206
261
|
log_send message, reason
|
207
262
|
rescue EOFError, IOError
|
208
263
|
buffer_message message
|
209
264
|
rescue SchemaError => e
|
210
|
-
|
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}")
|
211
268
|
end
|
212
269
|
|
213
270
|
def buffer_message message
|
214
271
|
# TODO
|
215
|
-
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
|
216
273
|
end
|
217
274
|
|
218
275
|
def log_send message, reason=nil
|
@@ -232,25 +289,38 @@ module RSMP
|
|
232
289
|
def process_packet json
|
233
290
|
attributes = Message.parse_attributes json
|
234
291
|
message = Message.build attributes, json
|
235
|
-
message.validate
|
292
|
+
message.validate get_schemas
|
293
|
+
notify message
|
236
294
|
expect_version_message(message) unless @version_determined
|
237
295
|
process_message message
|
296
|
+
process_deferred
|
238
297
|
message
|
239
298
|
rescue InvalidPacket => e
|
240
|
-
|
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
|
241
302
|
nil
|
242
303
|
rescue MalformedMessage => e
|
243
|
-
|
304
|
+
str = "Received malformed message, #{e.message}"
|
305
|
+
notify_error e.exception(str)
|
306
|
+
log str, message: Malformed.new(attributes), level: :warning
|
244
307
|
# cannot send NotAcknowledged for a malformed message since we can't read it, just ignore it
|
245
308
|
nil
|
246
309
|
rescue SchemaError => e
|
247
|
-
|
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
|
248
314
|
message
|
249
315
|
rescue InvalidMessage => e
|
250
|
-
|
316
|
+
str = "Received", "invalid #{message.type}, #{e.message}"
|
317
|
+
notify_error e.exception("#{str} #{message.json}")
|
318
|
+
dont_acknowledge message, str
|
251
319
|
message
|
252
320
|
rescue FatalError => e
|
253
|
-
|
321
|
+
str = "Rejected #{message.type},"
|
322
|
+
notify_error e.exception("#{str} #{message.json}")
|
323
|
+
dont_acknowledge message, str, "#{e.message}"
|
254
324
|
stop
|
255
325
|
message
|
256
326
|
end
|
@@ -294,7 +364,7 @@ module RSMP
|
|
294
364
|
# find versions that both we and the client support
|
295
365
|
candidates = message.versions & @settings["rsmp_versions"]
|
296
366
|
if candidates.any?
|
297
|
-
@rsmp_version = candidates.
|
367
|
+
@rsmp_version = candidates.sort_by { |v| Gem::Version.new(v) }.last # pick latest version
|
298
368
|
else
|
299
369
|
raise FatalError.new "RSMP versions [#{message.versions.join(',')}] requested, but only [#{@settings["rsmp_versions"].join(',')}] supported."
|
300
370
|
end
|
@@ -331,10 +401,12 @@ module RSMP
|
|
331
401
|
def wait_for_state state, timeout
|
332
402
|
states = [state].flatten
|
333
403
|
return if states.include?(@state)
|
334
|
-
|
404
|
+
wait_for(@state_condition,timeout) do
|
335
405
|
states.include?(@state)
|
336
406
|
end
|
337
407
|
@state
|
408
|
+
rescue Async::TimeoutError
|
409
|
+
raise RSMP::TimeoutError.new "Did not reach state #{state} within #{timeout}s"
|
338
410
|
end
|
339
411
|
|
340
412
|
def send_version site_id, rsmp_versions
|
@@ -424,7 +496,7 @@ module RSMP
|
|
424
496
|
|
425
497
|
def process_watchdog message
|
426
498
|
log "Received #{message.type}", message: message, level: :log
|
427
|
-
@latest_watchdog_received =
|
499
|
+
@latest_watchdog_received = Clock.now
|
428
500
|
acknowledge message
|
429
501
|
end
|
430
502
|
|
@@ -441,27 +513,16 @@ module RSMP
|
|
441
513
|
def version_acknowledged
|
442
514
|
end
|
443
515
|
|
444
|
-
def wait_for_acknowledgement original, timeout
|
516
|
+
def wait_for_acknowledgement original, timeout
|
445
517
|
raise ArgumentError unless original
|
446
|
-
|
447
|
-
message.is_a?(
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
def wait_for_not_acknowledged original, timeout
|
453
|
-
raise ArgumentError unless original
|
454
|
-
RSMP::Wait.wait_for(@task,@acknowledgement_condition,timeout) do |message|
|
455
|
-
message.is_a?(MessageNotAck) &&
|
456
|
-
message.attributes["oMId"] == original.m_id
|
457
|
-
end
|
458
|
-
end
|
459
|
-
|
460
|
-
def wait_for_acknowledgements timeout
|
461
|
-
return if @awaiting_acknowledgement.empty?
|
462
|
-
RSMP::Wait.wait_for(@task,@acknowledgement_condition,timeout) do |message|
|
463
|
-
@awaiting_acknowledgement.empty?
|
518
|
+
wait_for(@acknowledgement_condition,timeout) do |message|
|
519
|
+
if message.is_a?(MessageNotAck) && message.attributes["oMId"] == original.m_id
|
520
|
+
raise RSMP::MessageRejected.new(message.attributes['rea'])
|
521
|
+
end
|
522
|
+
message.is_a?(MessageAck) && message.attributes["oMId"] == original.m_id
|
464
523
|
end
|
524
|
+
rescue Async::TimeoutError
|
525
|
+
raise RSMP::TimeoutError.new("Acknowledgement for #{original.type} #{original.m_id} not received within #{timeout}s")
|
465
526
|
end
|
466
527
|
|
467
528
|
def node
|