rsmp 0.48.4 → 0.51.1
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/CHANGELOG.md +14 -0
- data/Gemfile.lock +19 -20
- data/README.md +27 -3
- data/Rakefile +3 -1
- data/documentation/cli.md +79 -4
- data/documentation/collecting_message.md +90 -170
- data/documentation/configuration.md +8 -0
- data/documentation/message_distribution.md +17 -3
- data/documentation/tasks.md +65 -117
- data/lib/rsmp/cli.rb +93 -5
- data/lib/rsmp/collect/collector.rb +117 -134
- data/lib/rsmp/collect/distributor.rb +16 -3
- data/lib/rsmp/collect/queue.rb +14 -6
- data/lib/rsmp/collect/receiver.rb +1 -1
- data/lib/rsmp/collect/state_collector.rb +11 -2
- data/lib/rsmp/collection.rb +12 -0
- data/lib/rsmp/completion.rb +49 -0
- data/lib/rsmp/component/component_base.rb +3 -3
- data/lib/rsmp/convert/export/json_schema/index.rb +8 -4
- data/lib/rsmp/convert/import/yaml.rb +12 -8
- data/lib/rsmp/delivery.rb +18 -0
- data/lib/rsmp/event.rb +19 -0
- data/lib/rsmp/event_source.rb +36 -0
- data/lib/rsmp/exchange.rb +10 -0
- data/lib/rsmp/failure.rb +23 -0
- data/lib/rsmp/helpers/error.rb +14 -23
- data/lib/rsmp/message.rb +10 -9
- data/lib/rsmp/node/node.rb +3 -21
- data/lib/rsmp/node/site/connections.rb +4 -9
- data/lib/rsmp/node/site/site.rb +33 -17
- data/lib/rsmp/node/supervisor/modules/configuration.rb +24 -11
- data/lib/rsmp/node/supervisor/modules/connection.rb +19 -5
- data/lib/rsmp/node/supervisor/modules/sites.rb +16 -5
- data/lib/rsmp/node/supervisor/supervisor.rb +13 -14
- data/lib/rsmp/node/task.rb +59 -53
- data/lib/rsmp/proxy/modules/acknowledgements.rb +22 -9
- data/lib/rsmp/proxy/modules/lifecycle.rb +134 -0
- data/lib/rsmp/proxy/modules/receive.rb +109 -66
- data/lib/rsmp/proxy/modules/send.rb +109 -51
- data/lib/rsmp/proxy/modules/state.rb +15 -5
- data/lib/rsmp/proxy/modules/tasks.rb +96 -70
- data/lib/rsmp/proxy/modules/versions.rb +69 -19
- data/lib/rsmp/proxy/modules/watchdogs.rb +15 -2
- data/lib/rsmp/proxy/proxy.rb +7 -69
- data/lib/rsmp/proxy/site/connection.rb +27 -22
- data/lib/rsmp/proxy/site/modules/aggregated_status.rb +18 -8
- data/lib/rsmp/proxy/site/modules/alarms.rb +45 -38
- data/lib/rsmp/proxy/site/modules/commands.rb +18 -8
- data/lib/rsmp/proxy/site/modules/status.rb +44 -19
- data/lib/rsmp/proxy/site/site_proxy.rb +12 -10
- data/lib/rsmp/proxy/site/sxl_selection.rb +7 -2
- data/lib/rsmp/proxy/supervisor/modules/aggregated_status.rb +1 -1
- data/lib/rsmp/proxy/supervisor/modules/alarms.rb +1 -1
- data/lib/rsmp/proxy/supervisor/modules/commands.rb +1 -1
- data/lib/rsmp/proxy/supervisor/modules/message_buffer.rb +8 -9
- data/lib/rsmp/proxy/supervisor/modules/status.rb +1 -1
- data/lib/rsmp/proxy/supervisor/modules/status_updates.rb +1 -1
- data/lib/rsmp/proxy/supervisor/supervisor_proxy.rb +36 -31
- data/lib/rsmp/result.rb +95 -0
- data/lib/rsmp/schema/validation.rb +5 -3
- data/lib/rsmp/schema/version.rb +27 -0
- data/lib/rsmp/schema.rb +1 -0
- data/lib/rsmp/sxl/interface.rb +8 -1
- data/lib/rsmp/sxl/processing/catalogue.rb +61 -0
- data/lib/rsmp/sxl/processing/conflict_checker.rb +30 -0
- data/lib/rsmp/sxl/processing/dependency_graph.rb +37 -0
- data/lib/rsmp/sxl/processing/document.rb +158 -0
- data/lib/rsmp/sxl/processing/error.rb +8 -0
- data/lib/rsmp/sxl/processing/manifest.rb +144 -0
- data/lib/rsmp/sxl/processing/natural_sort.rb +22 -0
- data/lib/rsmp/sxl/processing/resolver.rb +94 -0
- data/lib/rsmp/sxl/processing/version_requirement.rb +86 -0
- data/lib/rsmp/sxl/processing.rb +18 -0
- data/lib/rsmp/sxl/supervisor_interface.rb +8 -1
- data/lib/rsmp/termination.rb +10 -0
- data/lib/rsmp/tlc/modules/plans.rb +0 -2
- data/lib/rsmp/tlc/modules/system.rb +2 -2
- data/lib/rsmp/tlc/proxy/control.rb +50 -16
- data/lib/rsmp/tlc/proxy/detectors.rb +15 -4
- data/lib/rsmp/tlc/proxy/io.rb +46 -31
- data/lib/rsmp/tlc/proxy/plans.rb +36 -19
- data/lib/rsmp/tlc/proxy/plans_bang.rb +44 -0
- data/lib/rsmp/tlc/proxy/status.rb +47 -23
- data/lib/rsmp/tlc/proxy/system.rb +16 -4
- data/lib/rsmp/tlc/supervisor_interface.rb +7 -1
- data/lib/rsmp/tlc/traffic_controller_site.rb +6 -9
- data/lib/rsmp/validation.rb +22 -0
- data/lib/rsmp/version.rb +1 -1
- data/lib/rsmp.rb +19 -4
- data/rsmp.gemspec +1 -1
- metadata +26 -4
- data/lib/rsmp/collect/collector/status.rb +0 -34
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
require 'date'
|
|
2
|
+
require 'psych'
|
|
3
|
+
require 'time'
|
|
4
|
+
|
|
5
|
+
module RSMP
|
|
6
|
+
module SXL
|
|
7
|
+
module Processing
|
|
8
|
+
# Creates and verifies deterministic SXL manifests.
|
|
9
|
+
module Manifest
|
|
10
|
+
TOP_LEVEL_KEYS = %w[meta sxls].freeze
|
|
11
|
+
META_KEYS = %w[created_at created_by format].freeze
|
|
12
|
+
|
|
13
|
+
def self.create(documents, format:, now: Time.now)
|
|
14
|
+
validate_documents!(documents, format)
|
|
15
|
+
{
|
|
16
|
+
'meta' => {
|
|
17
|
+
'created_at' => now.utc.iso8601,
|
|
18
|
+
'created_by' => "rsmp v#{RSMP::VERSION}",
|
|
19
|
+
'format' => format.to_s
|
|
20
|
+
},
|
|
21
|
+
'sxls' => NaturalSort.sort(documents.map(&:name)).to_h do |name|
|
|
22
|
+
document = documents.find { |candidate| candidate.name == name }
|
|
23
|
+
[name, document.version_string]
|
|
24
|
+
end
|
|
25
|
+
}
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def self.dump(manifest)
|
|
29
|
+
Psych.safe_dump(manifest, permitted_classes: [], aliases: false, line_width: -1)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def self.load(path)
|
|
33
|
+
raise Error, "Manifest #{path} not found" unless File.exist?(path)
|
|
34
|
+
raise Error, "Manifest #{path} is not a file" unless File.file?(path)
|
|
35
|
+
|
|
36
|
+
parse(File.read(path, encoding: 'UTF-8'), path: path)
|
|
37
|
+
rescue EncodingError => e
|
|
38
|
+
raise Error, "Cannot read manifest #{path}: #{e.message}"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def self.parse(source, path: '(manifest)')
|
|
42
|
+
Psych.safe_load(source, permitted_classes: [Date, Time], permitted_symbols: [], aliases: false)
|
|
43
|
+
rescue Psych::Exception => e
|
|
44
|
+
raise Error, "Cannot read manifest #{path}: #{e.message}"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def self.verify!(manifest, catalogue)
|
|
48
|
+
meta, sxls = validate_shape!(manifest)
|
|
49
|
+
documents = load_documents!(sxls, catalogue)
|
|
50
|
+
validate_dependency_closure!(documents, sxls)
|
|
51
|
+
validate_documents!(documents, meta['format'])
|
|
52
|
+
documents
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def self.validate_documents!(documents, format)
|
|
56
|
+
format_version = VersionRequirement.exact_version(format, label: 'manifest format')
|
|
57
|
+
documents.each do |document|
|
|
58
|
+
next unless document.minimum_core_version && document.minimum_core_version > format_version
|
|
59
|
+
|
|
60
|
+
raise Error,
|
|
61
|
+
"SXL #{document.name} #{document.version_string} requires Core " \
|
|
62
|
+
"#{document.minimum_core_version}, newer than manifest format #{format}"
|
|
63
|
+
end
|
|
64
|
+
cycle = DependencyGraph.cycle(documents)
|
|
65
|
+
raise Error, "Cyclic dependency #{cycle.join(' -> ')}" if cycle
|
|
66
|
+
|
|
67
|
+
ConflictChecker.check!(documents)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def self.validate_shape!(manifest)
|
|
71
|
+
raise Error, 'Invalid manifest; expected a mapping' unless manifest.is_a?(Hash)
|
|
72
|
+
|
|
73
|
+
reject_unknown_keys!(manifest, TOP_LEVEL_KEYS, 'manifest')
|
|
74
|
+
|
|
75
|
+
meta = manifest['meta']
|
|
76
|
+
sxls = manifest['sxls']
|
|
77
|
+
raise Error, 'Invalid manifest meta; expected a mapping' unless meta.is_a?(Hash)
|
|
78
|
+
raise Error, 'Invalid manifest sxls; expected a non-empty mapping' unless sxls.is_a?(Hash) && !sxls.empty?
|
|
79
|
+
|
|
80
|
+
reject_unknown_keys!(meta, META_KEYS, 'manifest meta')
|
|
81
|
+
validate_metadata!(meta)
|
|
82
|
+
validate_sxls!(sxls)
|
|
83
|
+
[meta, sxls]
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def self.validate_metadata!(meta)
|
|
87
|
+
missing = META_KEYS.reject { |key| meta.key?(key) }
|
|
88
|
+
raise Error, "Manifest meta is missing #{missing.join(', ')}" unless missing.empty?
|
|
89
|
+
|
|
90
|
+
Time.iso8601(meta['created_at'].to_s)
|
|
91
|
+
unless meta['created_by'].is_a?(String) && !meta['created_by'].empty?
|
|
92
|
+
raise Error, 'Manifest created_by must be a non-empty string'
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
VersionRequirement.exact_version(meta['format'], label: 'manifest format')
|
|
96
|
+
rescue ArgumentError
|
|
97
|
+
raise Error, 'Manifest created_at must be an ISO 8601 timestamp'
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def self.validate_sxls!(sxls)
|
|
101
|
+
sxls.each_pair do |name, version|
|
|
102
|
+
Document.validate_name!(name)
|
|
103
|
+
VersionRequirement.exact_version(version, label: "version for #{name}")
|
|
104
|
+
end
|
|
105
|
+
expected_order = NaturalSort.sort(sxls.keys)
|
|
106
|
+
return if sxls.keys == expected_order
|
|
107
|
+
|
|
108
|
+
raise Error, 'Manifest SXLs are not in natural name order'
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def self.load_documents!(sxls, catalogue)
|
|
112
|
+
sxls.map do |name, version|
|
|
113
|
+
document = catalogue.fetch(name, version)
|
|
114
|
+
raise Error, "SXL #{name} #{version} is not available from the supplied sources" unless document
|
|
115
|
+
|
|
116
|
+
document
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def self.validate_dependency_closure!(documents, sxls)
|
|
121
|
+
documents.each do |document|
|
|
122
|
+
document.dependencies.each_pair do |name, requirement|
|
|
123
|
+
version = sxls[name]
|
|
124
|
+
raise Error, "Manifest is missing dependency #{name} required by #{document.name}" unless version
|
|
125
|
+
next if requirement.satisfied_by?(version)
|
|
126
|
+
|
|
127
|
+
raise Error,
|
|
128
|
+
"Manifest dependency #{name} #{version} does not satisfy #{requirement.source} " \
|
|
129
|
+
"required by #{document.name}"
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def self.reject_unknown_keys!(hash, expected, label)
|
|
135
|
+
unknown = hash.keys - expected
|
|
136
|
+
raise Error, "Unknown #{label} key(s): #{unknown.join(', ')}" unless unknown.empty?
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
private_class_method :validate_shape!, :validate_metadata!, :validate_sxls!, :load_documents!,
|
|
140
|
+
:validate_dependency_closure!, :reject_unknown_keys!
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module RSMP
|
|
2
|
+
module SXL
|
|
3
|
+
module Processing
|
|
4
|
+
# Natural sorting for identifiers containing decimal digit sequences.
|
|
5
|
+
module NaturalSort
|
|
6
|
+
def self.key(value)
|
|
7
|
+
value.scan(/\d+|\D+/).map do |part|
|
|
8
|
+
if part.match?(/\A\d+\z/)
|
|
9
|
+
[0, part.to_i, part.length]
|
|
10
|
+
else
|
|
11
|
+
[1, part]
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.sort(values)
|
|
17
|
+
values.sort_by { |value| key(value) }
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
module RSMP
|
|
2
|
+
module SXL
|
|
3
|
+
module Processing
|
|
4
|
+
# Resolves root requirements to a deterministic set of exact SXL versions.
|
|
5
|
+
class Resolver
|
|
6
|
+
def initialize(catalogue)
|
|
7
|
+
@catalogue = catalogue
|
|
8
|
+
@diagnostics = []
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def resolve(root_requirements)
|
|
12
|
+
constraints = Hash.new { |hash, key| hash[key] = [] }
|
|
13
|
+
root_requirements.each do |name, requirement|
|
|
14
|
+
Document.validate_name!(name)
|
|
15
|
+
constraints[name] << requirement
|
|
16
|
+
end
|
|
17
|
+
raise Error, 'At least one root SXL is required' if constraints.empty?
|
|
18
|
+
|
|
19
|
+
solution = search({}, constraints)
|
|
20
|
+
return sorted(solution.values) if solution
|
|
21
|
+
|
|
22
|
+
detail = @diagnostics.uniq.first(3).join('; ')
|
|
23
|
+
raise Error, "Cannot resolve SXL dependencies#{": #{detail}" unless detail.empty?}"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def search(selected, constraints)
|
|
29
|
+
return nil unless selected_versions_valid?(selected, constraints)
|
|
30
|
+
|
|
31
|
+
unresolved = NaturalSort.sort(constraints.keys.reject { |name| selected.key?(name) })
|
|
32
|
+
return selected if unresolved.empty?
|
|
33
|
+
|
|
34
|
+
name = unresolved.first
|
|
35
|
+
candidates = @catalogue.candidates(name, constraints[name])
|
|
36
|
+
record_missing_candidate(name, constraints[name]) if candidates.empty?
|
|
37
|
+
|
|
38
|
+
candidates.each do |document|
|
|
39
|
+
solution = search_candidate(name, document, selected, constraints)
|
|
40
|
+
return solution if solution
|
|
41
|
+
end
|
|
42
|
+
nil
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def search_candidate(name, document, selected, constraints)
|
|
46
|
+
next_selected = selected.merge(name => document)
|
|
47
|
+
next_constraints = copy_constraints(constraints)
|
|
48
|
+
document.dependencies.each_pair do |dependency_name, requirement|
|
|
49
|
+
next_constraints[dependency_name] << requirement
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
cycle = DependencyGraph.cycle(next_selected.values)
|
|
53
|
+
if cycle
|
|
54
|
+
@diagnostics << "cyclic dependency #{cycle.join(' -> ')}"
|
|
55
|
+
return nil
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
search(next_selected, next_constraints)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def selected_versions_valid?(selected, constraints)
|
|
62
|
+
selected.each_pair do |name, document|
|
|
63
|
+
next if constraints[name].all? { |requirement| requirement.satisfied_by?(document.version) }
|
|
64
|
+
|
|
65
|
+
requirements = constraints[name].map(&:source).join(', ')
|
|
66
|
+
@diagnostics << "#{name} #{document.version_string} does not satisfy #{requirements}"
|
|
67
|
+
return false
|
|
68
|
+
end
|
|
69
|
+
true
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def record_missing_candidate(name, requirements)
|
|
73
|
+
available = @catalogue.versions(name)
|
|
74
|
+
if available.empty?
|
|
75
|
+
@diagnostics << "no versions available for #{name}"
|
|
76
|
+
else
|
|
77
|
+
expected = requirements.map(&:source).join(', ')
|
|
78
|
+
@diagnostics << "no version of #{name} satisfies #{expected} (available: #{available.join(', ')})"
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def copy_constraints(constraints)
|
|
83
|
+
constraints.each_with_object(Hash.new { |hash, key| hash[key] = [] }) do |(name, requirements), copy|
|
|
84
|
+
copy[name] = requirements.dup
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def sorted(documents)
|
|
89
|
+
documents.sort_by { |document| NaturalSort.key(document.name) }
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
require 'rubygems'
|
|
2
|
+
|
|
3
|
+
module RSMP
|
|
4
|
+
module SXL
|
|
5
|
+
module Processing
|
|
6
|
+
# Parses the version requirement syntax defined by RSMP Core.
|
|
7
|
+
class VersionRequirement
|
|
8
|
+
EXACT_VERSION = /\A(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)\z/
|
|
9
|
+
PARTIAL_VERSION = /\A(0|[1-9]\d*)\.(0|[1-9]\d*)(?:\.(0|[1-9]\d*))?\z/
|
|
10
|
+
CLAUSE = /\A(~|>=|<=|>|<)?\s*(\d+(?:\.\d+){1,2})\z/
|
|
11
|
+
|
|
12
|
+
attr_reader :source
|
|
13
|
+
|
|
14
|
+
def self.exact_version(value, label: 'version')
|
|
15
|
+
string = value.to_s
|
|
16
|
+
unless EXACT_VERSION.match?(string)
|
|
17
|
+
raise Error, "Invalid #{label} #{value.inspect}; expected MAJOR.MINOR.PATCH"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
Gem::Version.new(string)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def initialize(source)
|
|
24
|
+
@source = source.to_s
|
|
25
|
+
@predicates = parse(@source)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def satisfied_by?(version)
|
|
29
|
+
version = self.class.exact_version(version) unless version.is_a?(Gem::Version)
|
|
30
|
+
@predicates.all? { |predicate| predicate.call(version) }
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def parse(source)
|
|
36
|
+
raise Error, 'Version requirement cannot be empty' if source.strip.empty?
|
|
37
|
+
|
|
38
|
+
source.split(/\s+and\s+/).map { |clause| parse_clause(clause.strip) }
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def parse_clause(clause)
|
|
42
|
+
match = CLAUSE.match(clause)
|
|
43
|
+
raise Error, "Invalid version requirement #{source.inspect}" unless match
|
|
44
|
+
|
|
45
|
+
operator = match[1]
|
|
46
|
+
version_string = match[2]
|
|
47
|
+
return compatible_predicate(version_string) if operator == '~'
|
|
48
|
+
|
|
49
|
+
version = self.class.exact_version(version_string, label: 'requirement version')
|
|
50
|
+
comparison_predicate(operator || '=', version)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def compatible_predicate(version_string)
|
|
54
|
+
match = PARTIAL_VERSION.match(version_string)
|
|
55
|
+
raise Error, "Invalid compatibility requirement #{source.inspect}" unless match
|
|
56
|
+
|
|
57
|
+
major, minor, patch = match.captures.map { |part| part&.to_i }
|
|
58
|
+
return major_zero_predicate(major, minor, patch) if major.zero?
|
|
59
|
+
|
|
60
|
+
lower = Gem::Version.new("#{major}.#{minor}.#{patch || 0}")
|
|
61
|
+
upper = Gem::Version.new("#{major + 1}.0.0")
|
|
62
|
+
->(version) { version >= lower && version < upper }
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def major_zero_predicate(major, minor, patch)
|
|
66
|
+
unless patch
|
|
67
|
+
raise Error, "Major-zero compatibility requirement #{source.inspect} must include an exact patch version"
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
exact = Gem::Version.new("#{major}.#{minor}.#{patch}")
|
|
71
|
+
->(version) { version == exact }
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def comparison_predicate(operator, expected)
|
|
75
|
+
case operator
|
|
76
|
+
when '=' then ->(version) { version == expected }
|
|
77
|
+
when '>' then ->(version) { version > expected }
|
|
78
|
+
when '>=' then ->(version) { version >= expected }
|
|
79
|
+
when '<' then ->(version) { version < expected }
|
|
80
|
+
when '<=' then ->(version) { version <= expected }
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
require_relative '../version'
|
|
2
|
+
require_relative 'processing/error'
|
|
3
|
+
require_relative 'processing/natural_sort'
|
|
4
|
+
require_relative 'processing/version_requirement'
|
|
5
|
+
require_relative 'processing/document'
|
|
6
|
+
require_relative 'processing/catalogue'
|
|
7
|
+
require_relative 'processing/dependency_graph'
|
|
8
|
+
require_relative 'processing/resolver'
|
|
9
|
+
require_relative 'processing/conflict_checker'
|
|
10
|
+
require_relative 'processing/manifest'
|
|
11
|
+
|
|
12
|
+
module RSMP
|
|
13
|
+
module SXL
|
|
14
|
+
# Dependency resolution, conflict checks, and manifests for SXL definitions.
|
|
15
|
+
module Processing
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -8,12 +8,19 @@ module RSMP
|
|
|
8
8
|
|
|
9
9
|
def_delegators :proxy,
|
|
10
10
|
:request_status,
|
|
11
|
+
:request_status!,
|
|
11
12
|
:request_status_and_collect,
|
|
13
|
+
:request_status_and_collect!,
|
|
12
14
|
:subscribe_to_status,
|
|
15
|
+
:subscribe_to_status!,
|
|
13
16
|
:subscribe_to_status_and_collect,
|
|
17
|
+
:subscribe_to_status_and_collect!,
|
|
14
18
|
:unsubscribe_to_status,
|
|
19
|
+
:unsubscribe_to_status!,
|
|
15
20
|
:send_command,
|
|
16
|
-
:
|
|
21
|
+
:send_command!,
|
|
22
|
+
:send_command_and_collect,
|
|
23
|
+
:send_command_and_collect!
|
|
17
24
|
|
|
18
25
|
def process_message(_message); end
|
|
19
26
|
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
module RSMP
|
|
2
|
+
# An explicit reason for a long-running service task to finish normally.
|
|
3
|
+
Termination = Data.define(:reason, :source) do
|
|
4
|
+
def initialize(reason:, source:)
|
|
5
|
+
raise ArgumentError, 'termination reason must be a Symbol' unless reason.is_a?(Symbol)
|
|
6
|
+
|
|
7
|
+
super
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
end
|
|
@@ -64,7 +64,7 @@ module RSMP
|
|
|
64
64
|
|
|
65
65
|
# S0091 - Operator logged in/out OP-panel
|
|
66
66
|
def handle_s0091(_status_code, status_name = nil, options = {})
|
|
67
|
-
if RSMP::Proxy.version_meets_requirement?(options[:sxl_version], '>=1.1')
|
|
67
|
+
if RSMP::Proxy.version_meets_requirement?(options[:sxl_version], '>=1.1.0')
|
|
68
68
|
case status_name
|
|
69
69
|
when 'user'
|
|
70
70
|
TrafficControllerSite.make_status 0
|
|
@@ -81,7 +81,7 @@ module RSMP
|
|
|
81
81
|
|
|
82
82
|
# S0092 - Operator logged in/out web-interface
|
|
83
83
|
def handle_s0092(_status_code, status_name = nil, options = {})
|
|
84
|
-
if RSMP::Proxy.version_meets_requirement?(options[:sxl_version], '>=1.1')
|
|
84
|
+
if RSMP::Proxy.version_meets_requirement?(options[:sxl_version], '>=1.1.0')
|
|
85
85
|
case status_name
|
|
86
86
|
when 'user'
|
|
87
87
|
TrafficControllerSite.make_status 0
|
|
@@ -6,20 +6,29 @@ module RSMP
|
|
|
6
6
|
module Control
|
|
7
7
|
# M0001 - Set functional position (NormalControl, YellowFlash, Dark).
|
|
8
8
|
def set_functional_position(status, within:, timeout_minutes: 0)
|
|
9
|
-
validate_ready 'set functional position'
|
|
9
|
+
readiness = validate_ready 'set functional position'
|
|
10
|
+
return readiness if readiness.failure?
|
|
11
|
+
|
|
10
12
|
raise 'TLC main component not found' unless main
|
|
11
13
|
|
|
12
14
|
command_list = functional_position_command_list(status, timeout_minutes)
|
|
13
15
|
confirm_status = functional_position_confirm_status(status)
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
send_command_and_collect(command_list, within: within).and_then do |exchange|
|
|
17
|
+
next Result.success(exchange) if confirm_status.empty?
|
|
18
|
+
|
|
19
|
+
wait_for_status("functional position #{status}", confirm_status, timeout: within).map { exchange }
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def set_functional_position!(...)
|
|
24
|
+
set_functional_position(...).value!
|
|
18
25
|
end
|
|
19
26
|
|
|
20
27
|
# M0005 - Set or clear an emergency route.
|
|
21
28
|
def set_emergency_route(route:, active:, within:)
|
|
22
|
-
validate_ready 'set emergency route'
|
|
29
|
+
readiness = validate_ready 'set emergency route'
|
|
30
|
+
return readiness if readiness.failure?
|
|
31
|
+
|
|
23
32
|
raise 'TLC main component not found' unless main
|
|
24
33
|
|
|
25
34
|
security_code = security_code_for(2)
|
|
@@ -41,12 +50,18 @@ module RSMP
|
|
|
41
50
|
'v' => command_value('M0005', 'emergencyroute', route)
|
|
42
51
|
}]
|
|
43
52
|
|
|
44
|
-
send_command_and_collect(command_list, within: within)
|
|
53
|
+
send_command_and_collect(command_list, within: within)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def set_emergency_route!(...)
|
|
57
|
+
set_emergency_route(...).value!
|
|
45
58
|
end
|
|
46
59
|
|
|
47
60
|
# M0007 - Enable or disable fixed-time control.
|
|
48
61
|
def set_fixed_time(status, within:)
|
|
49
|
-
validate_ready 'set fixed time'
|
|
62
|
+
readiness = validate_ready 'set fixed time'
|
|
63
|
+
return readiness if readiness.failure?
|
|
64
|
+
|
|
50
65
|
raise 'TLC main component not found' unless main
|
|
51
66
|
|
|
52
67
|
security_code = security_code_for(2)
|
|
@@ -64,13 +79,20 @@ module RSMP
|
|
|
64
79
|
}]
|
|
65
80
|
|
|
66
81
|
confirm_status = [{ 'sCI' => 'S0009', 'n' => 'status', 's' => [boolean_value(status)] }]
|
|
67
|
-
send_command_and_collect(command_list, within: within).
|
|
68
|
-
|
|
82
|
+
send_command_and_collect(command_list, within: within).and_then do |exchange|
|
|
83
|
+
wait_for_status("fixed time #{status}", confirm_status, timeout: within).map { exchange }
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def set_fixed_time!(...)
|
|
88
|
+
set_fixed_time(...).value!
|
|
69
89
|
end
|
|
70
90
|
|
|
71
91
|
# M0003 - Set traffic situation (activate a specific situation number).
|
|
72
92
|
def set_traffic_situation(situation, within:)
|
|
73
|
-
validate_ready 'set traffic situation'
|
|
93
|
+
readiness = validate_ready 'set traffic situation'
|
|
94
|
+
return readiness if readiness.failure?
|
|
95
|
+
|
|
74
96
|
raise 'TLC main component not found' unless main
|
|
75
97
|
|
|
76
98
|
security_code = security_code_for(2)
|
|
@@ -93,13 +115,20 @@ module RSMP
|
|
|
93
115
|
}]
|
|
94
116
|
|
|
95
117
|
confirm_status = [{ 'sCI' => 'S0015', 'n' => 'status', 's' => integer_value(situation) }]
|
|
96
|
-
send_command_and_collect(command_list, within: within).
|
|
97
|
-
|
|
118
|
+
send_command_and_collect(command_list, within: within).and_then do |exchange|
|
|
119
|
+
wait_for_status("traffic situation #{situation}", confirm_status, timeout: within).map { exchange }
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def set_traffic_situation!(...)
|
|
124
|
+
set_traffic_situation(...).value!
|
|
98
125
|
end
|
|
99
126
|
|
|
100
127
|
# M0003 - Clear the active traffic situation.
|
|
101
128
|
def unset_traffic_situation(within:)
|
|
102
|
-
validate_ready 'unset traffic situation'
|
|
129
|
+
readiness = validate_ready 'unset traffic situation'
|
|
130
|
+
return readiness if readiness.failure?
|
|
131
|
+
|
|
103
132
|
raise 'TLC main component not found' unless main
|
|
104
133
|
|
|
105
134
|
security_code = security_code_for(2)
|
|
@@ -122,8 +151,13 @@ module RSMP
|
|
|
122
151
|
}]
|
|
123
152
|
|
|
124
153
|
confirm_status = [{ 'sCI' => 'S0015', 'n' => 'status', 's' => 1 }]
|
|
125
|
-
send_command_and_collect(command_list, within: within).
|
|
126
|
-
|
|
154
|
+
send_command_and_collect(command_list, within: within).and_then do |exchange|
|
|
155
|
+
wait_for_status('traffic situation unset', confirm_status, timeout: within).map { exchange }
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def unset_traffic_situation!(...)
|
|
160
|
+
unset_traffic_situation(...).value!
|
|
127
161
|
end
|
|
128
162
|
|
|
129
163
|
private
|
|
@@ -7,7 +7,8 @@ module RSMP
|
|
|
7
7
|
# M0008 - Force detector logic to a given mode and status.
|
|
8
8
|
# component_id must refer to the detector logic component, not main.
|
|
9
9
|
def force_detector_logic(component_id, status:, mode:, within:)
|
|
10
|
-
validate_ready 'force detector logic'
|
|
10
|
+
readiness = validate_ready 'force detector logic'
|
|
11
|
+
return readiness if readiness.failure?
|
|
11
12
|
|
|
12
13
|
security_code = security_code_for(2)
|
|
13
14
|
|
|
@@ -27,12 +28,18 @@ module RSMP
|
|
|
27
28
|
'n' => 'mode',
|
|
28
29
|
'v' => command_value('M0008', 'mode', mode)
|
|
29
30
|
}]
|
|
30
|
-
send_command_and_collect(command_list, component: component_id, within: within)
|
|
31
|
+
send_command_and_collect(command_list, component: component_id, within: within)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def force_detector_logic!(...)
|
|
35
|
+
force_detector_logic(...).value!
|
|
31
36
|
end
|
|
32
37
|
|
|
33
38
|
# M0021 - Set the trigger level for traffic counting.
|
|
34
39
|
def set_trigger_level(status, within:)
|
|
35
|
-
validate_ready 'set trigger level'
|
|
40
|
+
readiness = validate_ready 'set trigger level'
|
|
41
|
+
return readiness if readiness.failure?
|
|
42
|
+
|
|
36
43
|
raise 'TLC main component not found' unless main
|
|
37
44
|
|
|
38
45
|
security_code = security_code_for(2)
|
|
@@ -48,7 +55,11 @@ module RSMP
|
|
|
48
55
|
'n' => 'securityCode',
|
|
49
56
|
'v' => security_code.to_s
|
|
50
57
|
}]
|
|
51
|
-
send_command_and_collect(command_list, within: within)
|
|
58
|
+
send_command_and_collect(command_list, within: within)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def set_trigger_level!(...)
|
|
62
|
+
set_trigger_level(...).value!
|
|
52
63
|
end
|
|
53
64
|
end
|
|
54
65
|
end
|