openc3 5.2.0 → 5.4.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of openc3 might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/bin/openc3cli +108 -105
- data/data/config/interface_modifiers.yaml +22 -4
- data/data/config/item_modifiers.yaml +4 -2
- data/data/config/microservice.yaml +18 -0
- data/data/config/table_manager.yaml +2 -2
- data/data/config/tool.yaml +1 -1
- data/ext/openc3/ext/config_parser/config_parser.c +17 -2
- data/lib/openc3/api/api.rb +1 -0
- data/lib/openc3/api/interface_api.rb +12 -0
- data/lib/openc3/api/metrics_api.rb +97 -0
- data/lib/openc3/api/router_api.rb +14 -2
- data/lib/openc3/api/target_api.rb +24 -3
- data/lib/openc3/api/tlm_api.rb +5 -4
- data/lib/openc3/config/config_parser.rb +29 -4
- data/lib/openc3/core_ext/time.rb +6 -1
- data/lib/openc3/interfaces/interface.rb +27 -26
- data/lib/openc3/interfaces/mqtt_interface.rb +240 -0
- data/lib/openc3/interfaces/protocols/override_protocol.rb +2 -61
- data/lib/openc3/interfaces/protocols/protocol.rb +6 -1
- data/lib/openc3/interfaces/simulated_target_interface.rb +1 -3
- data/lib/openc3/interfaces/tcpip_server_interface.rb +0 -11
- data/lib/openc3/interfaces.rb +2 -3
- data/lib/openc3/logs/buffered_packet_log_reader.rb +2 -2
- data/lib/openc3/microservices/cleanup_microservice.rb +17 -1
- data/lib/openc3/microservices/decom_microservice.rb +12 -9
- data/lib/openc3/microservices/interface_microservice.rb +93 -9
- data/lib/openc3/microservices/log_microservice.rb +11 -5
- data/lib/openc3/microservices/microservice.rb +10 -9
- data/lib/openc3/microservices/periodic_microservice.rb +7 -0
- data/lib/openc3/microservices/reaction_microservice.rb +0 -33
- data/lib/openc3/microservices/reducer_microservice.rb +14 -10
- data/lib/openc3/microservices/text_log_microservice.rb +12 -3
- data/lib/openc3/microservices/timeline_microservice.rb +0 -6
- data/lib/openc3/microservices/trigger_group_microservice.rb +0 -20
- data/lib/openc3/models/cvt_model.rb +103 -47
- data/lib/openc3/models/interface_model.rb +23 -0
- data/lib/openc3/models/metric_model.rb +53 -6
- data/lib/openc3/models/microservice_model.rb +15 -1
- data/lib/openc3/models/model.rb +1 -1
- data/lib/openc3/models/plugin_model.rb +6 -1
- data/lib/openc3/models/secret_model.rb +53 -0
- data/lib/openc3/models/target_model.rb +2 -2
- data/lib/openc3/models/tool_model.rb +17 -8
- data/lib/openc3/operators/microservice_operator.rb +25 -0
- data/lib/openc3/operators/operator.rb +5 -1
- data/lib/openc3/packets/packet.rb +21 -7
- data/lib/openc3/packets/packet_item.rb +3 -2
- data/lib/openc3/script/api_shared.rb +18 -2
- data/lib/openc3/script/script.rb +8 -0
- data/lib/openc3/script/script_runner.rb +1 -2
- data/lib/openc3/script/storage.rb +2 -1
- data/lib/openc3/script/suite.rb +15 -11
- data/lib/openc3/system/system.rb +6 -3
- data/lib/openc3/topics/interface_topic.rb +17 -1
- data/lib/openc3/topics/router_topic.rb +17 -1
- data/lib/openc3/utilities/aws_bucket.rb +20 -3
- data/lib/openc3/utilities/bucket.rb +1 -1
- data/lib/openc3/utilities/bucket_file_cache.rb +1 -1
- data/lib/openc3/utilities/bucket_utilities.rb +1 -1
- data/lib/openc3/utilities/local_mode.rb +1 -0
- data/lib/openc3/utilities/metric.rb +77 -101
- data/lib/openc3/utilities/redis_secrets.rb +46 -0
- data/lib/openc3/utilities/s3_autoload.rb +19 -9
- data/lib/openc3/utilities/secrets.rb +63 -0
- data/lib/openc3/utilities/target_file.rb +3 -1
- data/lib/openc3/version.rb +5 -5
- data/templates/plugin-template/LICENSE.txt +7 -0
- data/templates/plugin-template/README.md +4 -3
- data/templates/plugin-template/plugin.gemspec +4 -4
- metadata +22 -3
- data/data/config/_interfaces.yaml.err +0 -1017
@@ -41,6 +41,10 @@ module OpenC3
|
|
41
41
|
# These settings limit the log file to 10 minutes or 50MB of data, whichever comes first
|
42
42
|
@cycle_time = 600 unless @cycle_time # 10 minutes
|
43
43
|
@cycle_size = 50_000_000 unless @cycle_size # ~50 MB
|
44
|
+
|
45
|
+
@error_count = 0
|
46
|
+
@metric.set(name: 'text_log_total', value: @count, type: 'counter')
|
47
|
+
@metric.set(name: 'text_error_total', value: @error_count, type: 'counter')
|
44
48
|
end
|
45
49
|
|
46
50
|
def run
|
@@ -52,6 +56,8 @@ module OpenC3
|
|
52
56
|
break if @cancel_thread
|
53
57
|
|
54
58
|
log_data(topic, msg_id, msg_hash, redis)
|
59
|
+
@count += 1
|
60
|
+
@metric.set(name: 'text_log_total', value: @count, type: 'counter')
|
55
61
|
end
|
56
62
|
end
|
57
63
|
end
|
@@ -68,17 +74,20 @@ module OpenC3
|
|
68
74
|
end
|
69
75
|
|
70
76
|
def log_data(topic, msg_id, msg_hash, redis)
|
71
|
-
|
77
|
+
msgid_seconds_from_epoch = msg_id.split('-')[0].to_i / 1000.0
|
78
|
+
delta = Time.now.to_f - msgid_seconds_from_epoch
|
79
|
+
@metric.set(name: 'text_log_topic_delta_seconds', value: delta, type: 'gauge', unit: 'seconds', help: 'Delta time between data written to stream and text log start')
|
80
|
+
|
72
81
|
keys = msg_hash.keys
|
73
82
|
keys.delete("time")
|
74
83
|
entry = keys.reduce("") { |data, key| data + "#{key}: #{msg_hash[key]}\t" }
|
75
84
|
@tlws[topic].write(msg_hash["time"].to_i, entry, topic, msg_id)
|
76
85
|
@count += 1
|
77
|
-
diff = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start # seconds as a float
|
78
|
-
@metric.add_sample(name: "log_duration_seconds", value: diff, labels: {})
|
79
86
|
rescue => err
|
80
87
|
@error = err
|
81
88
|
@logger.error("#{@name} error: #{err.formatted}")
|
89
|
+
@error_count += 1
|
90
|
+
@metric.set(name: 'text_log_error_total', value: @error_count, type: 'counter')
|
82
91
|
end
|
83
92
|
|
84
93
|
def shutdown
|
@@ -278,8 +278,6 @@ module OpenC3
|
|
278
278
|
# manager. Timeline will then wait for an update on the timeline
|
279
279
|
# stream this will trigger an update again to the schedule.
|
280
280
|
class TimelineMicroservice < Microservice
|
281
|
-
TIMELINE_METRIC_NAME = 'timeline_activities_duration_seconds'.freeze
|
282
|
-
|
283
281
|
def initialize(name)
|
284
282
|
super(name)
|
285
283
|
@timeline_name = name.split('__')[2]
|
@@ -293,12 +291,8 @@ module OpenC3
|
|
293
291
|
@logger.info "#{@name} timeine running"
|
294
292
|
@manager_thread = Thread.new { @manager.run }
|
295
293
|
loop do
|
296
|
-
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
297
294
|
current_activities = ActivityModel.activities(name: @timeline_name, scope: @scope)
|
298
295
|
@schedule.update(current_activities)
|
299
|
-
diff = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start # seconds as a float
|
300
|
-
metric_labels = { 'timeline' => @timeline_name, 'thread' => 'microservice' }
|
301
|
-
@metric.add_sample(name: TIMELINE_METRIC_NAME, value: diff, labels: metric_labels)
|
302
296
|
break if @cancel_thread
|
303
297
|
|
304
298
|
block_for_updates()
|
@@ -244,8 +244,6 @@ module OpenC3
|
|
244
244
|
# these workers will evaluate the triggers in the kit and
|
245
245
|
# evaluate triggers for that packet.
|
246
246
|
class TriggerGroupWorker
|
247
|
-
TRIGGER_METRIC_NAME = 'trigger_eval_duration_seconds'.freeze
|
248
|
-
|
249
247
|
TYPE = 'type'.freeze
|
250
248
|
ITEM_RAW = 'raw'.freeze
|
251
249
|
ITEM_TARGET = 'target'.freeze
|
@@ -266,8 +264,6 @@ module OpenC3
|
|
266
264
|
@queue = queue
|
267
265
|
@share = share
|
268
266
|
@ident = ident
|
269
|
-
@metric = Metric.new(microservice: @name, scope: @scope)
|
270
|
-
@metric_output_time = 0
|
271
267
|
end
|
272
268
|
|
273
269
|
def run
|
@@ -277,11 +273,6 @@ module OpenC3
|
|
277
273
|
break if topic.nil?
|
278
274
|
begin
|
279
275
|
evaluate_wrapper(topic: topic)
|
280
|
-
current_time = Time.now.to_i
|
281
|
-
if @metric_output_time < current_time
|
282
|
-
@metric.output
|
283
|
-
@metric_output_time = current_time + 120
|
284
|
-
end
|
285
276
|
rescue StandardError => e
|
286
277
|
@logger.error "TriggerGroupWorker-#{@ident} failed to evaluate data packet from topic: #{topic}\n#{e.formatted}"
|
287
278
|
end
|
@@ -289,13 +280,8 @@ module OpenC3
|
|
289
280
|
@logger.info "TriggerGroupWorker-#{@ident} exiting"
|
290
281
|
end
|
291
282
|
|
292
|
-
# time how long each packet takes to eval and produce a metric to public
|
293
283
|
def evaluate_wrapper(topic:)
|
294
|
-
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
295
284
|
evaluate_data_packet(topic: topic, triggers: @share.trigger_base.triggers)
|
296
|
-
diff = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start # seconds as a float
|
297
|
-
metric_labels = { 'trigger_group' => @group, 'thread' => "worker-#{@ident}" }
|
298
|
-
@metric.add_sample(name: TRIGGER_METRIC_NAME, value: diff, labels: metric_labels)
|
299
285
|
end
|
300
286
|
|
301
287
|
# Each packet will be evaluated to all triggers and use the result to send
|
@@ -546,8 +532,6 @@ module OpenC3
|
|
546
532
|
# manager. Timeline will then wait for an update on the timeline
|
547
533
|
# stream this will trigger an update again to the schedule.
|
548
534
|
class TriggerGroupMicroservice < Microservice
|
549
|
-
TRIGGER_METRIC_NAME = 'update_triggers_duration_seconds'.freeze
|
550
|
-
|
551
535
|
attr_reader :name, :scope, :share, :group, :manager, :manager_thread
|
552
536
|
|
553
537
|
def initialize(*args)
|
@@ -563,12 +547,8 @@ module OpenC3
|
|
563
547
|
@logger.info "TriggerGroupMicroservice running"
|
564
548
|
@manager_thread = Thread.new { @manager.run }
|
565
549
|
loop do
|
566
|
-
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
567
550
|
triggers = TriggerModel.all(scope: @scope, group: @group)
|
568
551
|
@share.trigger_base.update(triggers: triggers)
|
569
|
-
diff = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start # seconds as a float
|
570
|
-
metric_labels = { 'trigger_group' => @group, 'thread' => 'microservice' }
|
571
|
-
@metric.add_sample(name: TRIGGER_METRIC_NAME, value: diff, labels: metric_labels)
|
572
552
|
break if @cancel_thread
|
573
553
|
|
574
554
|
block_for_updates()
|
@@ -25,30 +25,29 @@ require 'openc3/utilities/store'
|
|
25
25
|
module OpenC3
|
26
26
|
class CvtModel
|
27
27
|
VALUE_TYPES = [:RAW, :CONVERTED, :FORMATTED, :WITH_UNITS]
|
28
|
-
# Stores telemetry item overrides which are returned on every request to get_item
|
29
|
-
@overrides = {}
|
30
|
-
|
31
28
|
def self.build_json_from_packet(packet)
|
32
29
|
packet.decom
|
33
30
|
end
|
34
31
|
|
35
32
|
# Delete the current value table for a target
|
36
|
-
def self.del(target_name:, packet_name:, scope:)
|
33
|
+
def self.del(target_name:, packet_name:, scope: $openc3_scope)
|
37
34
|
Store.hdel("#{scope}__tlm__#{target_name}", packet_name)
|
38
35
|
end
|
39
36
|
|
40
37
|
# Set the current value table for a target, packet
|
41
|
-
def self.set(hash, target_name:, packet_name:, scope:)
|
38
|
+
def self.set(hash, target_name:, packet_name:, scope: $openc3_scope)
|
42
39
|
Store.hset("#{scope}__tlm__#{target_name}", packet_name, JSON.generate(hash.as_json(:allow_nan => true)))
|
43
40
|
end
|
44
41
|
|
45
42
|
# Set an item in the current value table
|
46
|
-
def self.set_item(target_name, packet_name, item_name, value, type:, scope:)
|
43
|
+
def self.set_item(target_name, packet_name, item_name, value, type:, scope: $openc3_scope)
|
47
44
|
case type
|
48
45
|
when :WITH_UNITS
|
49
46
|
field = "#{item_name}__U"
|
47
|
+
value = value.to_s # WITH_UNITS should always be a string
|
50
48
|
when :FORMATTED
|
51
49
|
field = "#{item_name}__F"
|
50
|
+
value = value.to_s # FORMATTED should always be a string
|
52
51
|
when :CONVERTED
|
53
52
|
field = "#{item_name}__C"
|
54
53
|
when :RAW
|
@@ -62,27 +61,37 @@ module OpenC3
|
|
62
61
|
end
|
63
62
|
|
64
63
|
# Get an item from the current value table
|
65
|
-
def self.get_item(target_name, packet_name, item_name, type:, scope:)
|
66
|
-
|
67
|
-
return @overrides["#{target_name}__#{packet_name}__#{item_name}__#{type}"]
|
68
|
-
end
|
69
|
-
|
64
|
+
def self.get_item(target_name, packet_name, item_name, type:, scope: $openc3_scope)
|
65
|
+
override_key = item_name
|
70
66
|
types = []
|
71
67
|
case type
|
72
68
|
when :WITH_UNITS
|
73
69
|
types = ["#{item_name}__U", "#{item_name}__F", "#{item_name}__C", item_name]
|
70
|
+
override_key = "#{item_name}__U"
|
74
71
|
when :FORMATTED
|
75
72
|
types = ["#{item_name}__F", "#{item_name}__C", item_name]
|
73
|
+
override_key = "#{item_name}__F"
|
76
74
|
when :CONVERTED
|
77
75
|
types = ["#{item_name}__C", item_name]
|
76
|
+
override_key = "#{item_name}__C"
|
78
77
|
when :RAW
|
79
78
|
types = [item_name]
|
80
79
|
else
|
81
80
|
raise "Unknown type '#{type}' for #{target_name} #{packet_name} #{item_name}"
|
82
81
|
end
|
82
|
+
overrides = Store.hget("#{scope}__override__#{target_name}", packet_name)
|
83
|
+
if overrides
|
84
|
+
result = JSON.parse(overrides, :allow_nan => true, :create_additions => true)[override_key]
|
85
|
+
return result if result
|
86
|
+
end
|
83
87
|
hash = JSON.parse(Store.hget("#{scope}__tlm__#{target_name}", packet_name), :allow_nan => true, :create_additions => true)
|
84
88
|
hash.values_at(*types).each do |result|
|
85
|
-
|
89
|
+
if result
|
90
|
+
if type == :FORMATTED or type == :WITH_UNITS
|
91
|
+
return result.to_s
|
92
|
+
end
|
93
|
+
return result
|
94
|
+
end
|
86
95
|
end
|
87
96
|
return nil
|
88
97
|
end
|
@@ -97,10 +106,11 @@ module OpenC3
|
|
97
106
|
results = []
|
98
107
|
lookups = []
|
99
108
|
packet_lookup = {}
|
109
|
+
overrides = {}
|
100
110
|
# First generate a lookup hash of all the items represented so we can query the CVT
|
101
|
-
items.each { |item| _parse_item(lookups, item) }
|
111
|
+
items.each { |item| _parse_item(lookups, overrides, item, scope: scope) }
|
102
112
|
|
103
|
-
lookups.each do |target_packet_key, target_name, packet_name,
|
113
|
+
lookups.each do |target_packet_key, target_name, packet_name, value_keys|
|
104
114
|
unless packet_lookup[target_packet_key]
|
105
115
|
packet = Store.hget("#{scope}__tlm__#{target_name}", packet_name)
|
106
116
|
raise "Packet '#{target_name} #{packet_name}' does not exist" unless packet
|
@@ -108,23 +118,26 @@ module OpenC3
|
|
108
118
|
end
|
109
119
|
hash = packet_lookup[target_packet_key]
|
110
120
|
item_result = []
|
111
|
-
|
112
|
-
item_result[0] =
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
121
|
+
if value_keys.is_a?(Hash) # Set in _parse_item to indicate override
|
122
|
+
item_result[0] = value_keys['value']
|
123
|
+
else
|
124
|
+
value_keys.each do |key|
|
125
|
+
item_result[0] = hash[key]
|
126
|
+
break if item_result[0] # We want the first value
|
127
|
+
end
|
128
|
+
# If we were able to find a value, try to get the limits state
|
129
|
+
if item_result[0]
|
130
|
+
if now - hash['RECEIVED_TIMESECONDS'] > stale_time
|
131
|
+
item_result[1] = :STALE
|
132
|
+
else
|
133
|
+
# The last key is simply the name (RAW) so we can append __L
|
134
|
+
# If there is no limits then it returns nil which is acceptable
|
135
|
+
item_result[1] = hash["#{value_keys[-1]}__L"]
|
136
|
+
item_result[1] = item_result[1].intern if item_result[1] # Convert to symbol
|
137
|
+
end
|
119
138
|
else
|
120
|
-
#
|
121
|
-
# If there is no limits then it returns nil which is acceptable
|
122
|
-
item_result[1] = hash["#{packet_values[-1]}__L"]
|
123
|
-
item_result[1] = item_result[1].intern if item_result[1] # Convert to symbol
|
139
|
+
raise "Item '#{target_name} #{packet_name} #{value_keys[-1]}' does not exist" unless hash.key?(value_keys[-1])
|
124
140
|
end
|
125
|
-
else
|
126
|
-
raise "Item '#{target_name} #{packet_name} #{packet_values[-1]}' does not exist" unless hash.key?(packet_values[-1])
|
127
|
-
item_result[1] = nil
|
128
141
|
end
|
129
142
|
results << item_result
|
130
143
|
end
|
@@ -133,35 +146,64 @@ module OpenC3
|
|
133
146
|
|
134
147
|
# Override a current value table item such that it always returns the same value
|
135
148
|
# for the given type
|
136
|
-
def self.override(target_name, packet_name, item_name, value, type
|
137
|
-
|
138
|
-
|
149
|
+
def self.override(target_name, packet_name, item_name, value, type: :ALL, scope: $openc3_scope)
|
150
|
+
hash = Store.hget("#{scope}__override__#{target_name}", packet_name)
|
151
|
+
hash = JSON.parse(hash, :allow_nan => true, :create_additions => true) if hash
|
152
|
+
hash ||= {} # In case the above didn't create anything
|
153
|
+
case type
|
154
|
+
when :ALL
|
155
|
+
hash[item_name] = value
|
156
|
+
hash["#{item_name}__C"] = value
|
157
|
+
hash["#{item_name}__F"] = value.to_s
|
158
|
+
hash["#{item_name}__U"] = value.to_s
|
159
|
+
when :RAW
|
160
|
+
hash[item_name] = value
|
161
|
+
when :CONVERTED
|
162
|
+
hash["#{item_name}__C"] = value
|
163
|
+
when :FORMATTED
|
164
|
+
hash["#{item_name}__F"] = value.to_s # Always a String
|
165
|
+
when :WITH_UNITS
|
166
|
+
hash["#{item_name}__U"] = value.to_s # Always a String
|
139
167
|
else
|
140
168
|
raise "Unknown type '#{type}' for #{target_name} #{packet_name} #{item_name}"
|
141
169
|
end
|
170
|
+
Store.hset("#{scope}__override__#{target_name}", packet_name, JSON.generate(hash.as_json(:allow_nan => true)))
|
142
171
|
end
|
143
172
|
|
144
173
|
# Normalize a current value table item such that it returns the actual value
|
145
174
|
def self.normalize(target_name, packet_name, item_name, type: :ALL, scope: $openc3_scope)
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
175
|
+
hash = Store.hget("#{scope}__override__#{target_name}", packet_name)
|
176
|
+
hash = JSON.parse(hash, :allow_nan => true, :create_additions => true) if hash
|
177
|
+
hash ||= {} # In case the above didn't create anything
|
178
|
+
case type
|
179
|
+
when :ALL
|
180
|
+
hash.delete(item_name)
|
181
|
+
hash.delete("#{item_name}__C")
|
182
|
+
hash.delete("#{item_name}__F")
|
183
|
+
hash.delete("#{item_name}__U")
|
184
|
+
when :RAW
|
185
|
+
hash.delete(item_name)
|
186
|
+
when :CONVERTED
|
187
|
+
hash.delete("#{item_name}__C")
|
188
|
+
when :FORMATTED
|
189
|
+
hash.delete("#{item_name}__F")
|
190
|
+
when :WITH_UNITS
|
191
|
+
hash.delete("#{item_name}__U")
|
150
192
|
else
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
193
|
+
raise "Unknown type '#{type}' for #{target_name} #{packet_name} #{item_name}"
|
194
|
+
end
|
195
|
+
if hash.empty?
|
196
|
+
Store.hdel("#{scope}__override__#{target_name}", packet_name)
|
197
|
+
else
|
198
|
+
Store.hset("#{scope}__override__#{target_name}", packet_name, JSON.generate(hash.as_json(:allow_nan => true)))
|
156
199
|
end
|
157
200
|
end
|
158
201
|
|
159
202
|
# PRIVATE METHODS
|
160
203
|
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
# return an ordered array of hash with keys
|
204
|
+
# parse item and update lookups with packet_name and target_name and keys
|
205
|
+
# return an ordered array of hash with keys
|
206
|
+
def self._parse_item(lookups, overrides, item, scope:)
|
165
207
|
target_name, packet_name, item_name, value_type = item.split('__')
|
166
208
|
raise ArgumentError, "items must be formatted as TGT__PKT__ITEM__TYPE" if target_name.nil? || packet_name.nil? || item_name.nil? || value_type.nil?
|
167
209
|
|
@@ -177,9 +219,23 @@ module OpenC3
|
|
177
219
|
when 'WITH_UNITS'
|
178
220
|
keys = ["#{item_name}__U", "#{item_name}__F", "#{item_name}__C", item_name]
|
179
221
|
else
|
180
|
-
raise "Unknown value type #{value_type}"
|
222
|
+
raise "Unknown value type '#{value_type}'"
|
223
|
+
end
|
224
|
+
tgt_pkt_key = "#{target_name}__#{packet_name}"
|
225
|
+
# Check the overrides cache for this target / packet
|
226
|
+
unless overrides[tgt_pkt_key]
|
227
|
+
override_data = Store.hget("#{scope}__override__#{target_name}", packet_name)
|
228
|
+
if override_data
|
229
|
+
overrides[tgt_pkt_key] = JSON.parse(override_data, :allow_nan => true, :create_additions => true)
|
230
|
+
else
|
231
|
+
overrides[tgt_pkt_key] = {}
|
232
|
+
end
|
233
|
+
end
|
234
|
+
if overrides[tgt_pkt_key][keys[0]]
|
235
|
+
# Set the result as a Hash to distingish it from the key array and from an overridden Array value
|
236
|
+
keys = {'value' => overrides[tgt_pkt_key][keys[0]]}
|
181
237
|
end
|
182
|
-
lookups << [
|
238
|
+
lookups << [tgt_pkt_key, target_name, packet_name, keys]
|
183
239
|
end
|
184
240
|
end
|
185
241
|
end
|
@@ -38,11 +38,13 @@ module OpenC3
|
|
38
38
|
attr_accessor :reconnect_delay
|
39
39
|
attr_accessor :disable_disconnect
|
40
40
|
attr_accessor :options
|
41
|
+
attr_accessor :secret_options
|
41
42
|
attr_accessor :protocols
|
42
43
|
attr_accessor :interfaces
|
43
44
|
attr_accessor :log
|
44
45
|
attr_accessor :log_raw
|
45
46
|
attr_accessor :needs_dependencies
|
47
|
+
attr_accessor :secrets
|
46
48
|
|
47
49
|
# NOTE: The following three class methods are used by the ModelController
|
48
50
|
# and are reimplemented to enable various Model class methods to work
|
@@ -101,12 +103,14 @@ module OpenC3
|
|
101
103
|
reconnect_delay: 5.0,
|
102
104
|
disable_disconnect: false,
|
103
105
|
options: [],
|
106
|
+
secret_options: [],
|
104
107
|
protocols: [],
|
105
108
|
log: true,
|
106
109
|
log_raw: false,
|
107
110
|
updated_at: nil,
|
108
111
|
plugin: nil,
|
109
112
|
needs_dependencies: false,
|
113
|
+
secrets: [],
|
110
114
|
scope:
|
111
115
|
)
|
112
116
|
if self.class._get_type == 'INTERFACE'
|
@@ -123,10 +127,12 @@ module OpenC3
|
|
123
127
|
@reconnect_delay = reconnect_delay
|
124
128
|
@disable_disconnect = disable_disconnect
|
125
129
|
@options = options
|
130
|
+
@secret_options = secret_options
|
126
131
|
@protocols = protocols
|
127
132
|
@log = log
|
128
133
|
@log_raw = log_raw
|
129
134
|
@needs_dependencies = needs_dependencies
|
135
|
+
@secrets = secrets
|
130
136
|
end
|
131
137
|
|
132
138
|
# Called by InterfaceMicroservice to instantiate the Interface defined
|
@@ -139,6 +145,7 @@ module OpenC3
|
|
139
145
|
else
|
140
146
|
interface_or_router = klass.new
|
141
147
|
end
|
148
|
+
interface_or_router.secrets.setup(@secrets)
|
142
149
|
interface_or_router.target_names = @target_names.dup
|
143
150
|
interface_or_router.cmd_target_names = @cmd_target_names.dup
|
144
151
|
interface_or_router.tlm_target_names = @tlm_target_names.dup
|
@@ -149,6 +156,11 @@ module OpenC3
|
|
149
156
|
@options.each do |option|
|
150
157
|
interface_or_router.set_option(option[0], option[1..-1])
|
151
158
|
end
|
159
|
+
@secret_options.each do |option|
|
160
|
+
secret_name = option[1]
|
161
|
+
secret_value = interface_or_router.secrets.get(secret_name, scope: @scope)
|
162
|
+
interface_or_router.set_option(option[0], [secret_value])
|
163
|
+
end
|
152
164
|
@protocols.each do |protocol|
|
153
165
|
klass = OpenC3.require_class(protocol[1])
|
154
166
|
interface_or_router.add_protocol(klass, protocol[2..-1], protocol[0].upcase.intern)
|
@@ -168,11 +180,13 @@ module OpenC3
|
|
168
180
|
'reconnect_delay' => @reconnect_delay,
|
169
181
|
'disable_disconnect' => @disable_disconnect,
|
170
182
|
'options' => @options,
|
183
|
+
'secret_options' => @secret_options,
|
171
184
|
'protocols' => @protocols,
|
172
185
|
'log' => @log,
|
173
186
|
'log_raw' => @log_raw,
|
174
187
|
'plugin' => @plugin,
|
175
188
|
'needs_dependencies' => @needs_dependencies,
|
189
|
+
'secrets' => @secrets.as_json(*a),
|
176
190
|
'updated_at' => @updated_at
|
177
191
|
}
|
178
192
|
end
|
@@ -242,6 +256,14 @@ module OpenC3
|
|
242
256
|
parser.verify_num_parameters(0, 0, "#{keyword}")
|
243
257
|
@log_raw = true
|
244
258
|
|
259
|
+
when 'SECRET'
|
260
|
+
parser.verify_num_parameters(3, 4, "#{keyword} <Secret Type: ENV or FILE> <Secret Name> <Environment Variable Name or File Path> <Option Name (Optional)>")
|
261
|
+
@secrets << parameters[0..2]
|
262
|
+
if parameters[3]
|
263
|
+
# Option Name, Secret Name
|
264
|
+
@secret_options << [parameters[3], parameters[1]]
|
265
|
+
end
|
266
|
+
|
245
267
|
else
|
246
268
|
raise ConfigParser::Error.new(parser, "Unknown keyword and parameters for Interface/Router: #{keyword} #{parameters.join(" ")}")
|
247
269
|
|
@@ -261,6 +283,7 @@ module OpenC3
|
|
261
283
|
target_names: @target_names,
|
262
284
|
plugin: @plugin,
|
263
285
|
needs_dependencies: @needs_dependencies,
|
286
|
+
secrets: @secrets,
|
264
287
|
scope: @scope
|
265
288
|
)
|
266
289
|
unless validate_only
|
@@ -17,7 +17,7 @@
|
|
17
17
|
# All changes Copyright 2022, OpenC3, Inc.
|
18
18
|
# All Rights Reserved
|
19
19
|
#
|
20
|
-
# This file may also be used under the terms of a commercial license
|
20
|
+
# This file may also be used under the terms of a commercial license
|
21
21
|
# if purchased from OpenC3, Inc.
|
22
22
|
|
23
23
|
require 'openc3/models/model'
|
@@ -26,6 +26,8 @@ module OpenC3
|
|
26
26
|
class MetricModel < EphemeralModel
|
27
27
|
PRIMARY_KEY = '__openc3__metric'.freeze
|
28
28
|
|
29
|
+
attr_accessor :values
|
30
|
+
|
29
31
|
# NOTE: The following three class methods are used by the ModelController
|
30
32
|
# and are reimplemented to enable various Model class methods to work
|
31
33
|
def self.get(name:, scope:)
|
@@ -44,19 +46,64 @@ module OpenC3
|
|
44
46
|
EphemeralStore.hdel("#{scope}#{PRIMARY_KEY}", name)
|
45
47
|
end
|
46
48
|
|
47
|
-
def initialize(name:,
|
49
|
+
def initialize(name:, values: {}, scope:)
|
48
50
|
super("#{scope}#{PRIMARY_KEY}", name: name, scope: scope)
|
49
|
-
@
|
50
|
-
@label_list = label_list
|
51
|
+
@values = values
|
51
52
|
end
|
52
53
|
|
53
54
|
def as_json(*a)
|
54
55
|
{
|
55
56
|
'name' => @name,
|
56
57
|
'updated_at' => @updated_at,
|
57
|
-
'
|
58
|
-
'label_list' => @label_list
|
58
|
+
'values' => @values.as_json(*a)
|
59
59
|
}
|
60
60
|
end
|
61
|
+
|
62
|
+
def self.redis_extract_p50_and_p99_seconds(value)
|
63
|
+
if value
|
64
|
+
split_value = value.to_s.split(',')
|
65
|
+
p50 = split_value[0].split('=')[-1].to_f / 1_000_000
|
66
|
+
p99 = split_value[-1].split('=')[-1].to_f / 1_000_000
|
67
|
+
return p50, p99
|
68
|
+
else
|
69
|
+
return 0.0, 0.0
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.redis_metrics
|
74
|
+
result = {}
|
75
|
+
|
76
|
+
metrics = OpenC3::Store.info("all")
|
77
|
+
result['redis_connected_clients_total'] = metrics['connected_clients']
|
78
|
+
result['redis_used_memory_rss_total'] = metrics['used_memory_rss']
|
79
|
+
result['redis_commands_processed_total'] = metrics['total_commands_processed']
|
80
|
+
result['redis_iops'] = metrics['instantaneous_ops_per_sec']
|
81
|
+
result['redis_instantaneous_input_kbps'] = metrics['instantaneous_input_kbps']
|
82
|
+
result['redis_instantaneous_output_kbps'] = metrics['instantaneous_output_kbps']
|
83
|
+
result['redis_hget_p50_seconds'], result['redis_hget_p99_seconds'] = redis_extract_p50_and_p99_seconds(metrics['latency_percentiles_usec_hget'])
|
84
|
+
result['redis_hgetall_p50_seconds'], result['redis_hgetall_p99_seconds'] = redis_extract_p50_and_p99_seconds(metrics['latency_percentiles_usec_hgetall'])
|
85
|
+
result['redis_hset_p50_seconds'], result['redis_hset_p99_seconds'] = redis_extract_p50_and_p99_seconds(metrics['latency_percentiles_usec_hset'])
|
86
|
+
result['redis_xadd_p50_seconds'], result['redis_xadd_p99_seconds'] = redis_extract_p50_and_p99_seconds(metrics['latency_percentiles_usec_xadd'])
|
87
|
+
result['redis_xread_p50_seconds'], result['redis_xread_p99_seconds'] = redis_extract_p50_and_p99_seconds(metrics['latency_percentiles_usec_xread'])
|
88
|
+
result['redis_xrevrange_p50_seconds'], result['redis_xrevrange_p99_seconds'] = redis_extract_p50_and_p99_seconds(metrics['latency_percentiles_usec_xrevrange'])
|
89
|
+
result['redis_xtrim_p50_seconds'], result['redis_xtrim_p99_seconds'] = redis_extract_p50_and_p99_seconds(metrics['latency_percentiles_usec_xtrim'])
|
90
|
+
|
91
|
+
metrics = OpenC3::EphemeralStore.info("all")
|
92
|
+
result['redis_ephemeral_connected_clients_total'] = metrics['connected_clients']
|
93
|
+
result['redis_ephemeral_used_memory_rss_total'] = metrics['used_memory_rss']
|
94
|
+
result['redis_ephemeral_commands_processed_total'] = metrics['total_commands_processed']
|
95
|
+
result['redis_ephemeral_iops'] = metrics['instantaneous_ops_per_sec']
|
96
|
+
result['redis_ephemeral_instantaneous_input_kbps'] = metrics['instantaneous_input_kbps']
|
97
|
+
result['redis_ephemeral_instantaneous_output_kbps'] = metrics['instantaneous_output_kbps']
|
98
|
+
result['redis_ephemeral_hget_p50_seconds'], result['redis_ephemeral_hget_p99_seconds'] = redis_extract_p50_and_p99_seconds(metrics['latency_percentiles_usec_hget'])
|
99
|
+
result['redis_ephemeral_hgetall_p50_seconds'], result['redis_ephemeral_hgetall_p99_seconds'] = redis_extract_p50_and_p99_seconds(metrics['latency_percentiles_usec_hgetall'])
|
100
|
+
result['redis_ephemeral_hset_p50_seconds'], result['redis_ephemeral_hset_p99_seconds'] = redis_extract_p50_and_p99_seconds(metrics['latency_percentiles_usec_hset'])
|
101
|
+
result['redis_ephemeral_xadd_p50_seconds'], result['redis_ephemeral_xadd_p99_seconds'] = redis_extract_p50_and_p99_seconds(metrics['latency_percentiles_usec_xadd'])
|
102
|
+
result['redis_ephemeral_xread_p50_seconds'], result['redis_ephemeral_xread_p99_seconds'] = redis_extract_p50_and_p99_seconds(metrics['latency_percentiles_usec_xread'])
|
103
|
+
result['redis_ephemeral_xrevrange_p50_seconds'], result['redis_ephemeral_xrevrange_p99_seconds'] = redis_extract_p50_and_p99_seconds(metrics['latency_percentiles_usec_xrevrange'])
|
104
|
+
result['redis_ephemeral_xtrim_p50_seconds'], result['redis_ephemeral_xtrim_p99_seconds'] = redis_extract_p50_and_p99_seconds(metrics['latency_percentiles_usec_xtrim'])
|
105
|
+
|
106
|
+
return result
|
107
|
+
end
|
61
108
|
end
|
62
109
|
end
|
@@ -22,6 +22,7 @@
|
|
22
22
|
|
23
23
|
require 'openc3/top_level'
|
24
24
|
require 'openc3/models/model'
|
25
|
+
require 'openc3/models/metric_model'
|
25
26
|
require 'openc3/utilities/bucket'
|
26
27
|
|
27
28
|
module OpenC3
|
@@ -39,6 +40,7 @@ module OpenC3
|
|
39
40
|
attr_accessor :work_dir
|
40
41
|
attr_accessor :ports
|
41
42
|
attr_accessor :parent
|
43
|
+
attr_accessor :secrets
|
42
44
|
|
43
45
|
# NOTE: The following three class methods are used by the ModelController
|
44
46
|
# and are reimplemented to enable various Model class methods to work
|
@@ -96,6 +98,7 @@ module OpenC3
|
|
96
98
|
updated_at: nil,
|
97
99
|
plugin: nil,
|
98
100
|
needs_dependencies: false,
|
101
|
+
secrets: [],
|
99
102
|
scope:
|
100
103
|
)
|
101
104
|
parts = name.split("__")
|
@@ -118,6 +121,7 @@ module OpenC3
|
|
118
121
|
@parent = parent
|
119
122
|
@container = container
|
120
123
|
@needs_dependencies = needs_dependencies
|
124
|
+
@secrets = secrets
|
121
125
|
@bucket = Bucket.getClient()
|
122
126
|
end
|
123
127
|
|
@@ -137,6 +141,7 @@ module OpenC3
|
|
137
141
|
'updated_at' => @updated_at,
|
138
142
|
'plugin' => @plugin,
|
139
143
|
'needs_dependencies' => @needs_dependencies,
|
144
|
+
'secrets' => @secrets.as_json(*a)
|
140
145
|
}
|
141
146
|
end
|
142
147
|
|
@@ -182,6 +187,9 @@ module OpenC3
|
|
182
187
|
when 'CONTAINER'
|
183
188
|
parser.verify_num_parameters(1, 1, "#{keyword} <Container Image Name>")
|
184
189
|
@container = parameters[0]
|
190
|
+
when 'SECRET'
|
191
|
+
parser.verify_num_parameters(3, 3, "#{keyword} <Secret Type: ENV or FILE> <Secret Name> <Environment Variable Name or File Path>")
|
192
|
+
@secrets << parameters.dup
|
185
193
|
else
|
186
194
|
raise ConfigParser::Error.new(parser, "Unknown keyword and parameters for Microservice: #{keyword} #{parameters.join(" ")}")
|
187
195
|
end
|
@@ -202,7 +210,7 @@ module OpenC3
|
|
202
210
|
# Load microservice files
|
203
211
|
data = File.read(filename, mode: "rb")
|
204
212
|
OpenC3.set_working_dir(File.dirname(filename)) do
|
205
|
-
data = ERB.new(data, trim_mode: "-").result(binding.set_variables(variables)) if data.is_printable?
|
213
|
+
data = ERB.new(data, trim_mode: "-").result(binding.set_variables(variables)) if data.is_printable? and File.basename(filename)[0] != '_'
|
206
214
|
end
|
207
215
|
unless validate_only
|
208
216
|
@bucket.put_object(bucket: ENV['OPENC3_CONFIG_BUCKET'], key: key, body: data)
|
@@ -220,5 +228,11 @@ module OpenC3
|
|
220
228
|
rescue Exception => error
|
221
229
|
Logger.error("Error undeploying microservice model #{@name} in scope #{@scope} due to #{error}")
|
222
230
|
end
|
231
|
+
|
232
|
+
def cleanup
|
233
|
+
# Cleanup metrics
|
234
|
+
metric_model = MetricModel.new(name: @name, scope: @scope)
|
235
|
+
metric_model.destroy
|
236
|
+
end
|
223
237
|
end
|
224
238
|
end
|
data/lib/openc3/models/model.rb
CHANGED
@@ -154,7 +154,7 @@ module OpenC3
|
|
154
154
|
end
|
155
155
|
end
|
156
156
|
@updated_at = Time.now.to_nsec_from_epoch
|
157
|
-
self.class.store.hset(@primary_key, @name, JSON.generate(self.as_json(:allow_nan => true)))
|
157
|
+
self.class.store.hset(@primary_key, @name, JSON.generate(self.as_json(:allow_nan => true), :allow_nan => true))
|
158
158
|
end
|
159
159
|
|
160
160
|
# Alias for create(update: true)
|
@@ -269,7 +269,8 @@ module OpenC3
|
|
269
269
|
# Undeploy all models associated with this plugin
|
270
270
|
def undeploy
|
271
271
|
microservice_count = 0
|
272
|
-
MicroserviceModel.find_all_by_plugin(plugin: @name, scope: @scope)
|
272
|
+
microservices = MicroserviceModel.find_all_by_plugin(plugin: @name, scope: @scope)
|
273
|
+
microservices.each do |name, model_instance|
|
273
274
|
model_instance.destroy
|
274
275
|
microservice_count += 1
|
275
276
|
end
|
@@ -282,6 +283,10 @@ module OpenC3
|
|
282
283
|
model_instance.destroy
|
283
284
|
end
|
284
285
|
end
|
286
|
+
# Cleanup Redis stuff that might have been left by microservices
|
287
|
+
microservices.each do |name, model_instance|
|
288
|
+
model_instance.cleanup
|
289
|
+
end
|
285
290
|
end
|
286
291
|
|
287
292
|
# Reinstall
|