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.

Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/bin/openc3cli +108 -105
  3. data/data/config/interface_modifiers.yaml +22 -4
  4. data/data/config/item_modifiers.yaml +4 -2
  5. data/data/config/microservice.yaml +18 -0
  6. data/data/config/table_manager.yaml +2 -2
  7. data/data/config/tool.yaml +1 -1
  8. data/ext/openc3/ext/config_parser/config_parser.c +17 -2
  9. data/lib/openc3/api/api.rb +1 -0
  10. data/lib/openc3/api/interface_api.rb +12 -0
  11. data/lib/openc3/api/metrics_api.rb +97 -0
  12. data/lib/openc3/api/router_api.rb +14 -2
  13. data/lib/openc3/api/target_api.rb +24 -3
  14. data/lib/openc3/api/tlm_api.rb +5 -4
  15. data/lib/openc3/config/config_parser.rb +29 -4
  16. data/lib/openc3/core_ext/time.rb +6 -1
  17. data/lib/openc3/interfaces/interface.rb +27 -26
  18. data/lib/openc3/interfaces/mqtt_interface.rb +240 -0
  19. data/lib/openc3/interfaces/protocols/override_protocol.rb +2 -61
  20. data/lib/openc3/interfaces/protocols/protocol.rb +6 -1
  21. data/lib/openc3/interfaces/simulated_target_interface.rb +1 -3
  22. data/lib/openc3/interfaces/tcpip_server_interface.rb +0 -11
  23. data/lib/openc3/interfaces.rb +2 -3
  24. data/lib/openc3/logs/buffered_packet_log_reader.rb +2 -2
  25. data/lib/openc3/microservices/cleanup_microservice.rb +17 -1
  26. data/lib/openc3/microservices/decom_microservice.rb +12 -9
  27. data/lib/openc3/microservices/interface_microservice.rb +93 -9
  28. data/lib/openc3/microservices/log_microservice.rb +11 -5
  29. data/lib/openc3/microservices/microservice.rb +10 -9
  30. data/lib/openc3/microservices/periodic_microservice.rb +7 -0
  31. data/lib/openc3/microservices/reaction_microservice.rb +0 -33
  32. data/lib/openc3/microservices/reducer_microservice.rb +14 -10
  33. data/lib/openc3/microservices/text_log_microservice.rb +12 -3
  34. data/lib/openc3/microservices/timeline_microservice.rb +0 -6
  35. data/lib/openc3/microservices/trigger_group_microservice.rb +0 -20
  36. data/lib/openc3/models/cvt_model.rb +103 -47
  37. data/lib/openc3/models/interface_model.rb +23 -0
  38. data/lib/openc3/models/metric_model.rb +53 -6
  39. data/lib/openc3/models/microservice_model.rb +15 -1
  40. data/lib/openc3/models/model.rb +1 -1
  41. data/lib/openc3/models/plugin_model.rb +6 -1
  42. data/lib/openc3/models/secret_model.rb +53 -0
  43. data/lib/openc3/models/target_model.rb +2 -2
  44. data/lib/openc3/models/tool_model.rb +17 -8
  45. data/lib/openc3/operators/microservice_operator.rb +25 -0
  46. data/lib/openc3/operators/operator.rb +5 -1
  47. data/lib/openc3/packets/packet.rb +21 -7
  48. data/lib/openc3/packets/packet_item.rb +3 -2
  49. data/lib/openc3/script/api_shared.rb +18 -2
  50. data/lib/openc3/script/script.rb +8 -0
  51. data/lib/openc3/script/script_runner.rb +1 -2
  52. data/lib/openc3/script/storage.rb +2 -1
  53. data/lib/openc3/script/suite.rb +15 -11
  54. data/lib/openc3/system/system.rb +6 -3
  55. data/lib/openc3/topics/interface_topic.rb +17 -1
  56. data/lib/openc3/topics/router_topic.rb +17 -1
  57. data/lib/openc3/utilities/aws_bucket.rb +20 -3
  58. data/lib/openc3/utilities/bucket.rb +1 -1
  59. data/lib/openc3/utilities/bucket_file_cache.rb +1 -1
  60. data/lib/openc3/utilities/bucket_utilities.rb +1 -1
  61. data/lib/openc3/utilities/local_mode.rb +1 -0
  62. data/lib/openc3/utilities/metric.rb +77 -101
  63. data/lib/openc3/utilities/redis_secrets.rb +46 -0
  64. data/lib/openc3/utilities/s3_autoload.rb +19 -9
  65. data/lib/openc3/utilities/secrets.rb +63 -0
  66. data/lib/openc3/utilities/target_file.rb +3 -1
  67. data/lib/openc3/version.rb +5 -5
  68. data/templates/plugin-template/LICENSE.txt +7 -0
  69. data/templates/plugin-template/README.md +4 -3
  70. data/templates/plugin-template/plugin.gemspec +4 -4
  71. metadata +22 -3
  72. 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
- start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
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
- if @overrides["#{target_name}__#{packet_name}__#{item_name}__#{type}"]
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
- return result if result
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, packet_values|
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
- packet_values.each do |value|
112
- item_result[0] = hash[value]
113
- break if item_result[0] # We want the first value
114
- end
115
- # If we were able to find a value, try to get the limits state
116
- if item_result[0]
117
- if now - hash['RECEIVED_TIMESECONDS'] > stale_time
118
- item_result[1] = :STALE
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
- # The last key is simply the name (RAW) so we can append __L
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:, scope: $openc3_scope)
137
- if VALUE_TYPES.include?(type)
138
- @overrides["#{target_name}__#{packet_name}__#{item_name}__#{type}"] = value
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
- if type == :ALL
147
- VALUE_TYPES.each do |type|
148
- @overrides.delete("#{target_name}__#{packet_name}__#{item_name}__#{type}")
149
- end
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
- if VALUE_TYPES.include?(type)
152
- @overrides.delete("#{target_name}__#{packet_name}__#{item_name}__#{type}")
153
- else
154
- raise "Unknown type '#{type}' for #{target_name} #{packet_name} #{item_name}"
155
- end
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
- def self._parse_item(lookups, item)
162
- # parse item and update lookups with packet_name and target_name and keys
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 << ["#{target_name}__#{packet_name}", target_name, packet_name, keys]
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:, scope:, metric_name:, label_list:)
49
+ def initialize(name:, values: {}, scope:)
48
50
  super("#{scope}#{PRIMARY_KEY}", name: name, scope: scope)
49
- @metric_name = metric_name
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
- 'metric_name' => @metric_name,
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
@@ -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).each do |name, model_instance|
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