openc3 7.0.0 → 7.1.0
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/bin/openc3cli +105 -13
- data/bin/pipinstall +38 -6
- data/data/config/command_modifiers.yaml +1 -0
- data/data/config/item_modifiers.yaml +2 -1
- data/data/config/microservice.yaml +12 -1
- data/data/config/parameter_modifiers.yaml +49 -7
- data/data/config/table_parameter_modifiers.yaml +3 -1
- data/data/config/target.yaml +11 -0
- data/data/config/target_config.yaml +6 -2
- data/lib/openc3/accessors/template_accessor.rb +9 -0
- data/lib/openc3/api/cmd_api.rb +2 -1
- data/lib/openc3/api/metrics_api.rb +11 -1
- data/lib/openc3/api/tlm_api.rb +21 -6
- data/lib/openc3/core_ext/faraday.rb +1 -1
- data/lib/openc3/interfaces/interface.rb +1 -6
- data/lib/openc3/io/json_api.rb +1 -1
- data/lib/openc3/logs/log_writer.rb +3 -1
- data/lib/openc3/microservices/decom_common.rb +128 -0
- data/lib/openc3/microservices/decom_microservice.rb +27 -96
- data/lib/openc3/microservices/interface_decom_common.rb +28 -10
- data/lib/openc3/microservices/interface_microservice.rb +16 -9
- data/lib/openc3/microservices/log_microservice.rb +1 -1
- data/lib/openc3/microservices/microservice.rb +3 -2
- data/lib/openc3/microservices/queue_microservice.rb +1 -1
- data/lib/openc3/microservices/scope_cleanup_microservice.rb +60 -46
- data/lib/openc3/microservices/text_log_microservice.rb +1 -2
- data/lib/openc3/models/cvt_model.rb +24 -13
- data/lib/openc3/models/db_sharded_model.rb +110 -0
- data/lib/openc3/models/interface_model.rb +9 -0
- data/lib/openc3/models/interface_status_model.rb +33 -3
- data/lib/openc3/models/metric_model.rb +96 -37
- data/lib/openc3/models/microservice_model.rb +7 -0
- data/lib/openc3/models/microservice_status_model.rb +30 -3
- data/lib/openc3/models/plugin_model.rb +9 -1
- data/lib/openc3/models/python_package_model.rb +1 -1
- data/lib/openc3/models/reaction_model.rb +27 -9
- data/lib/openc3/models/reingest_job_model.rb +153 -0
- data/lib/openc3/models/scope_model.rb +3 -2
- data/lib/openc3/models/script_status_model.rb +4 -20
- data/lib/openc3/models/target_model.rb +113 -100
- data/lib/openc3/models/trigger_model.rb +24 -7
- data/lib/openc3/packets/packet_config.rb +4 -1
- data/lib/openc3/script/api_shared.rb +39 -2
- data/lib/openc3/script/calendar.rb +32 -10
- data/lib/openc3/script/extract.rb +46 -13
- data/lib/openc3/script/script.rb +2 -2
- data/lib/openc3/script/script_runner.rb +4 -4
- data/lib/openc3/script/telemetry.rb +3 -3
- data/lib/openc3/script/web_socket_api.rb +29 -22
- data/lib/openc3/system/system.rb +20 -3
- data/lib/openc3/topics/command_decom_topic.rb +4 -2
- data/lib/openc3/topics/command_topic.rb +8 -5
- data/lib/openc3/topics/decom_interface_topic.rb +31 -11
- data/lib/openc3/topics/interface_topic.rb +88 -27
- data/lib/openc3/topics/limits_event_topic.rb +62 -41
- data/lib/openc3/topics/router_topic.rb +61 -21
- data/lib/openc3/topics/system_events_topic.rb +18 -1
- data/lib/openc3/topics/telemetry_decom_topic.rb +2 -1
- data/lib/openc3/topics/telemetry_topic.rb +4 -2
- data/lib/openc3/topics/topic.rb +77 -5
- data/lib/openc3/utilities/aws_bucket.rb +2 -0
- data/lib/openc3/utilities/cli_generator.rb +3 -2
- data/lib/openc3/utilities/ctrf.rb +231 -0
- data/lib/openc3/utilities/metric.rb +15 -1
- data/lib/openc3/utilities/questdb_client.rb +177 -40
- data/lib/openc3/utilities/reingest_job.rb +377 -0
- data/lib/openc3/utilities/ruby_lex_utils.rb +2 -0
- data/lib/openc3/utilities/store_autoload.rb +78 -52
- data/lib/openc3/utilities/store_queued.rb +20 -12
- data/lib/openc3/version.rb +5 -5
- data/templates/plugin/plugin.gemspec +13 -1
- data/templates/tool_angular/package.json +2 -2
- data/templates/tool_react/package.json +1 -1
- data/templates/tool_svelte/package.json +1 -1
- data/templates/tool_vue/package.json +3 -4
- data/templates/tool_vue/src/router.js +2 -2
- data/templates/widget/package.json +2 -2
- metadata +8 -3
|
@@ -288,6 +288,7 @@ module OpenC3
|
|
|
288
288
|
start_time = Time.now.sys
|
|
289
289
|
success, value = _openc3_script_wait_implementation_comparison(target_name, packet_name, item_name, type, comparison_to_eval, timeout, polling_rate, scope: scope, token: token, &block)
|
|
290
290
|
value = "'#{value}'" if value.is_a? String # Show user the check against a quoted string
|
|
291
|
+
value = 'nil' if value.nil? # Show user nil value as 'nil'
|
|
291
292
|
time_diff = Time.now.sys - start_time
|
|
292
293
|
check_str = "CHECK: #{_upcase(target_name, packet_name, item_name)}"
|
|
293
294
|
if comparison_to_eval
|
|
@@ -531,7 +532,7 @@ module OpenC3
|
|
|
531
532
|
if comparison_to_eval
|
|
532
533
|
_check_eval(target_name, packet_name, item_name, comparison_to_eval, value)
|
|
533
534
|
else
|
|
534
|
-
puts "CHECK: #{_upcase(target_name, packet_name, item_name)} == #{value}"
|
|
535
|
+
puts "CHECK: #{_upcase(target_name, packet_name, item_name)} == #{value.nil? ? 'nil' : value.inspect}"
|
|
535
536
|
end
|
|
536
537
|
end
|
|
537
538
|
|
|
@@ -632,6 +633,7 @@ module OpenC3
|
|
|
632
633
|
start_time = Time.now.sys
|
|
633
634
|
success, value = _openc3_script_wait_implementation_comparison(target_name, packet_name, item_name, value_type, comparison_to_eval, timeout, polling_rate, scope: scope, token: token)
|
|
634
635
|
value = "'#{value}'" if value.is_a? String # Show user the check against a quoted string
|
|
636
|
+
value = 'nil' if value.nil? # Show user nil value as 'nil'
|
|
635
637
|
time_diff = Time.now.sys - start_time
|
|
636
638
|
wait_str = "WAIT: #{_upcase(target_name, packet_name, item_name)} #{comparison_to_eval}"
|
|
637
639
|
value_str = "with value == #{value} after waiting #{time_diff} seconds"
|
|
@@ -863,8 +865,20 @@ module OpenC3
|
|
|
863
865
|
# Show user the check against a quoted string
|
|
864
866
|
# Note: We have to preserve the original 'value' variable because we're going to eval against it
|
|
865
867
|
value_str = value.is_a?(String) ? "'#{value}'" : value
|
|
868
|
+
value_str = 'nil' if value.nil? # Show user nil value as 'nil'
|
|
866
869
|
with_value = "with value == #{value_str}"
|
|
867
|
-
|
|
870
|
+
|
|
871
|
+
eval_is_valid = _check_eval_validity(value, comparison_to_eval)
|
|
872
|
+
unless eval_is_valid
|
|
873
|
+
message = "Invalid comparison for types"
|
|
874
|
+
if $disconnect
|
|
875
|
+
puts "ERROR: #{message}"
|
|
876
|
+
else
|
|
877
|
+
raise CheckError, message
|
|
878
|
+
end
|
|
879
|
+
end
|
|
880
|
+
|
|
881
|
+
if eval_is_valid && eval(string)
|
|
868
882
|
puts "#{check_str} success #{with_value}"
|
|
869
883
|
else
|
|
870
884
|
message = "#{check_str} failed #{with_value}"
|
|
@@ -883,5 +897,28 @@ module OpenC3
|
|
|
883
897
|
raise e
|
|
884
898
|
end
|
|
885
899
|
end
|
|
900
|
+
|
|
901
|
+
def _check_eval_validity(value, comparison)
|
|
902
|
+
return true if comparison.nil? || comparison.empty?
|
|
903
|
+
|
|
904
|
+
begin
|
|
905
|
+
operator, operand = extract_operator_and_operand_from_comparison(comparison)
|
|
906
|
+
rescue RuntimeError => e
|
|
907
|
+
if e.message.include?("Unable to parse operand")
|
|
908
|
+
# If we can't parse the operand, let the eval happen anyway
|
|
909
|
+
# It will raise an appropriate error (like NameError for undefined constants)
|
|
910
|
+
return true
|
|
911
|
+
end
|
|
912
|
+
raise # Re-raise invalid operator errors
|
|
913
|
+
rescue JSON::ParserError
|
|
914
|
+
return true
|
|
915
|
+
end
|
|
916
|
+
|
|
917
|
+
if [">=", "<=", ">", "<"].include?(operator)
|
|
918
|
+
return false if value.nil? || operand.nil? || value.is_a?(Array) || operand.is_a?(Array)
|
|
919
|
+
end
|
|
920
|
+
|
|
921
|
+
return true
|
|
922
|
+
end
|
|
886
923
|
end
|
|
887
924
|
end
|
|
@@ -61,20 +61,28 @@ module OpenC3
|
|
|
61
61
|
# @param data [Hash, optional] Additional data to associate with the activity. Defaults to {}. Any activity can provide "username", "notes", and "customTitle". "command", "script", and "reserve" keys are reserves for the corresponding activity kind, with "environment" also available for script activities.
|
|
62
62
|
# @param scope [String, optional] The scope of the activity. Defaults to OPENC3_SCOPE, must correspond to the timeline.
|
|
63
63
|
def create_timeline_activity(name, kind:, start:, stop:, data: {}, scope: $openc3_scope)
|
|
64
|
-
|
|
65
|
-
kinds = %w(command script reserve)
|
|
66
|
-
unless kinds.include?(kind)
|
|
67
|
-
raise "Unknown kind: #{kind}. Must be one of #{kinds.join(', ')}."
|
|
68
|
-
end
|
|
69
|
-
post_data = {}
|
|
70
|
-
post_data['start'] = start.to_datetime.iso8601
|
|
71
|
-
post_data['stop'] = stop.to_datetime.iso8601
|
|
72
|
-
post_data['kind'] = kind
|
|
73
|
-
post_data['data'] = data
|
|
64
|
+
post_data = _build_activity_data(kind, start, stop, data)
|
|
74
65
|
response = $api_server.request('post', "/openc3-api/timeline/#{name}/activities", data: post_data, json: true, scope: scope)
|
|
75
66
|
return _cal_handle_response(response, 'Failed to create timeline activity')
|
|
76
67
|
end
|
|
77
68
|
|
|
69
|
+
# Updates an existing activity on the specified timeline.
|
|
70
|
+
#
|
|
71
|
+
# @param name [String] The name of the timeline.
|
|
72
|
+
# @param id [Integer] The start time / score of the activity (Unix seconds).
|
|
73
|
+
# @param kind [String] The kind of activity. Must be one of "COMMAND", "SCRIPT", or "RESERVE".
|
|
74
|
+
# @param start [DateTime] The new start time of the activity.
|
|
75
|
+
# @param stop [DateTime] The new stop time of the activity.
|
|
76
|
+
# @param data [Hash, optional] Additional data to associate with the activity. Defaults to {}. Any activity can provide "username", "notes", and "customTitle". "command", "script", and "reserve" keys are reserved for the corresponding activity kind, with "environment" also available for script activities.
|
|
77
|
+
# @param uuid [String] The UUID of the activity.
|
|
78
|
+
# @param scope [String, optional] The scope of the activity. Defaults to OPENC3_SCOPE.
|
|
79
|
+
def update_timeline_activity(name, id:, kind:, start:, stop:, uuid:, data: {}, scope: $openc3_scope)
|
|
80
|
+
post_data = _build_activity_data(kind, start, stop, data)
|
|
81
|
+
url = "/openc3-api/timeline/#{name}/activity/#{id}/#{uuid}"
|
|
82
|
+
response = $api_server.request('put', url, data: post_data, json: true, scope: scope)
|
|
83
|
+
return _cal_handle_response(response, 'Failed to update timeline activity')
|
|
84
|
+
end
|
|
85
|
+
|
|
78
86
|
def get_timeline_activity(name, start, uuid, scope: $openc3_scope)
|
|
79
87
|
response = $api_server.request('get', "/openc3-api/timeline/#{name}/activity/#{start}/#{uuid}", scope: scope)
|
|
80
88
|
return _cal_handle_response(response, 'Failed to get timeline activity')
|
|
@@ -97,6 +105,20 @@ module OpenC3
|
|
|
97
105
|
return _cal_handle_response(response, 'Failed to delete timeline activity')
|
|
98
106
|
end
|
|
99
107
|
|
|
108
|
+
def _build_activity_data(kind, start, stop, data)
|
|
109
|
+
kind = kind.to_s.downcase()
|
|
110
|
+
kinds = %w(command script reserve)
|
|
111
|
+
unless kinds.include?(kind)
|
|
112
|
+
raise "Unknown kind: #{kind}. Must be one of #{kinds.join(', ')}."
|
|
113
|
+
end
|
|
114
|
+
{
|
|
115
|
+
'start' => start.to_datetime.iso8601,
|
|
116
|
+
'stop' => stop.to_datetime.iso8601,
|
|
117
|
+
'kind' => kind,
|
|
118
|
+
'data' => data,
|
|
119
|
+
}
|
|
120
|
+
end
|
|
121
|
+
|
|
100
122
|
# Helper method to handle the response
|
|
101
123
|
def _cal_handle_response(response, error_message)
|
|
102
124
|
return nil if response.nil?
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
# This file may also be used under the terms of a commercial license
|
|
16
16
|
# if purchased from OpenC3, Inc.
|
|
17
17
|
|
|
18
|
+
require 'json'
|
|
18
19
|
require 'openc3/utilities/store'
|
|
19
20
|
|
|
20
21
|
module OpenC3
|
|
@@ -154,22 +155,54 @@ module OpenC3
|
|
|
154
155
|
end
|
|
155
156
|
|
|
156
157
|
def extract_fields_from_check_text(text)
|
|
157
|
-
|
|
158
|
-
raise "ERROR: Check improperly specified: #{text}" if
|
|
158
|
+
target_name, packet_name, item_name, comparison = text.split(nil, 4) # Ruby: second split arg is max number of resultant elements
|
|
159
|
+
raise "ERROR: Check improperly specified: #{text}" if item_name.nil?
|
|
159
160
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
raise "ERROR:
|
|
161
|
+
# comparison is either nil, the comparison string, or an empty string.
|
|
162
|
+
# We need it to not be an empty string.
|
|
163
|
+
comparison = nil if comparison&.length == 0
|
|
164
|
+
|
|
165
|
+
operator, _ = comparison&.split(nil, 2)
|
|
166
|
+
raise "ERROR: Use '==' instead of '=' in #{text}" if operator == "="
|
|
167
|
+
|
|
168
|
+
return [target_name, packet_name, item_name, comparison]
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# Splits `check()` comparison expressions, e.g. "== 'foo bar'" becomes ["==", "foo bar"]
|
|
172
|
+
def extract_operator_and_operand_from_comparison(comparison)
|
|
173
|
+
valid_operators = ["==", "!=", ">=", "<=", ">", "<", "in"]
|
|
166
174
|
|
|
167
|
-
|
|
168
|
-
index = split_string.rindex(item_name)
|
|
169
|
-
comparison_to_eval = split_string[(index + 1)..(split_string.length - 1)].join(" ")
|
|
170
|
-
raise "ERROR: Use '==' instead of '=': #{text}" if split_string[3] == '='
|
|
175
|
+
operator, operand = comparison.split(nil, 2) # Ruby: second split arg is max number of resultant elements
|
|
171
176
|
|
|
172
|
-
|
|
177
|
+
if operand.nil?
|
|
178
|
+
# Don't allow operator without operand
|
|
179
|
+
raise "ERROR: Invalid comparison, must specify an operand: #{comparison}" if !operator.nil?
|
|
180
|
+
return [nil, nil]
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
raise "ERROR: Invalid operator: '#{operator}'" unless valid_operators.include?(operator)
|
|
184
|
+
|
|
185
|
+
# Handle string operand: remove surrounding double/single quotes
|
|
186
|
+
if operand.match?(/^(['"])(.*)\1$/m) # Starts with single or double quote, and ends with matching quote
|
|
187
|
+
operand = operand[1..-2]
|
|
188
|
+
return [operator, operand]
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# Handle other operand types
|
|
192
|
+
if operand == "nil"
|
|
193
|
+
operand = nil
|
|
194
|
+
elsif operand == "false"
|
|
195
|
+
operand = false
|
|
196
|
+
elsif operand == "true"
|
|
197
|
+
operand = true
|
|
198
|
+
else
|
|
199
|
+
begin
|
|
200
|
+
operand = JSON.parse(operand)
|
|
201
|
+
rescue JSON::ParserError
|
|
202
|
+
raise "ERROR: Unable to parse operand: #{operand}"
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
return [operator, operand]
|
|
173
206
|
end
|
|
174
207
|
end
|
|
175
208
|
end
|
data/lib/openc3/script/script.rb
CHANGED
|
@@ -44,11 +44,11 @@ require 'openc3/utilities/authentication'
|
|
|
44
44
|
$api_server = nil
|
|
45
45
|
$script_runner_api_server = nil
|
|
46
46
|
$disconnect = false
|
|
47
|
-
$openc3_scope = ENV
|
|
47
|
+
$openc3_scope = ENV.fetch('OPENC3_SCOPE', 'DEFAULT')
|
|
48
48
|
$openc3_in_cluster = false
|
|
49
49
|
|
|
50
50
|
saved_verbose = $VERBOSE
|
|
51
|
-
$VERBOSE =
|
|
51
|
+
$VERBOSE = nil
|
|
52
52
|
|
|
53
53
|
module OpenC3
|
|
54
54
|
module Script
|
|
@@ -37,9 +37,9 @@ module OpenC3
|
|
|
37
37
|
|
|
38
38
|
def script_syntax_check(script, scope: $openc3_scope)
|
|
39
39
|
endpoint = "/script-api/scripts/temp.rb/syntax"
|
|
40
|
-
# Explicitly set the headers to plain
|
|
40
|
+
# Explicitly set the headers to text/plain so the request.body is set correctly
|
|
41
41
|
headers = {
|
|
42
|
-
'Content-Type': 'plain
|
|
42
|
+
'Content-Type': 'text/plain',
|
|
43
43
|
}
|
|
44
44
|
response = $script_runner_api_server.request('post', endpoint, headers: headers, data: script, scope: scope)
|
|
45
45
|
if response.nil? || response.status != 200
|
|
@@ -129,9 +129,9 @@ module OpenC3
|
|
|
129
129
|
|
|
130
130
|
def script_instrumented(script, scope: $openc3_scope)
|
|
131
131
|
endpoint = "/script-api/scripts/temp.rb/instrumented"
|
|
132
|
-
# Explicitly set the headers to plain
|
|
132
|
+
# Explicitly set the headers to text/plain so the request.body is set correctly
|
|
133
133
|
headers = {
|
|
134
|
-
'Content-Type': 'plain
|
|
134
|
+
'Content-Type': 'text/plain',
|
|
135
135
|
}
|
|
136
136
|
response = $script_runner_api_server.request('post', endpoint, headers: headers, data: script, scope: scope)
|
|
137
137
|
if response.nil? || response.status != 200
|
|
@@ -40,9 +40,9 @@ module OpenC3
|
|
|
40
40
|
# inject_tlm, set_tlm, override_tlm, and normalize_tlm are implemented here simply to add a puts
|
|
41
41
|
# these methods modify the telemetry so the user should be notified in the Script Runner log messages
|
|
42
42
|
|
|
43
|
-
def inject_tlm(target_name, packet_name, item_hash = nil, type: :CONVERTED, scope: $openc3_scope, token: $openc3_token)
|
|
44
|
-
puts "inject_tlm(\"#{target_name}\", \"#{packet_name}\", #{item_hash}, type: #{type})"
|
|
45
|
-
$api_server.method_missing(:inject_tlm, target_name, packet_name, item_hash, type: type, scope: scope, token: token)
|
|
43
|
+
def inject_tlm(target_name, packet_name, item_hash = nil, type: :CONVERTED, stored: false, scope: $openc3_scope, token: $openc3_token)
|
|
44
|
+
puts "inject_tlm(\"#{target_name}\", \"#{packet_name}\", #{item_hash}, type: #{type}, stored: #{stored})"
|
|
45
|
+
$api_server.method_missing(:inject_tlm, target_name, packet_name, item_hash, type: type, stored: stored, scope: scope, token: token)
|
|
46
46
|
end
|
|
47
47
|
|
|
48
48
|
def set_tlm(*args, type: :ALL, scope: $openc3_scope, token: $openc3_token)
|
|
@@ -50,34 +50,41 @@ module OpenC3
|
|
|
50
50
|
start_time = Time.now
|
|
51
51
|
while true
|
|
52
52
|
message = read_message()
|
|
53
|
-
|
|
53
|
+
# Empty string is a normal end-of-stream signal when ActionCable / anycable-go
|
|
54
|
+
# closes the WS. Treat it the same as nil so consumer `while (resp = api.read)`
|
|
55
|
+
# loops exit cleanly instead of hitting JSON::ParserError on JSON.parse("").
|
|
56
|
+
return nil if message.nil? || message.empty?
|
|
57
|
+
|
|
58
|
+
begin
|
|
54
59
|
json_hash = JSON.parse(message, allow_nan: true, create_additions: true)
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
if
|
|
64
|
-
raise "
|
|
65
|
-
end
|
|
66
|
-
if timeout
|
|
67
|
-
end_time = Time.now
|
|
68
|
-
if (end_time - start_time) > timeout
|
|
69
|
-
raise Timeout::Error, "No Data Timeout"
|
|
70
|
-
end
|
|
60
|
+
rescue JSON::ParserError
|
|
61
|
+
# Defense-in-depth: treat malformed frames as end-of-stream rather than crashing.
|
|
62
|
+
return nil
|
|
63
|
+
end
|
|
64
|
+
if ignore_protocol_messages
|
|
65
|
+
type = json_hash['type']
|
|
66
|
+
if type # ping, welcome, confirm_subscription, reject_subscription, disconnect
|
|
67
|
+
if type == 'disconnect'
|
|
68
|
+
if json_hash['reason'] == 'unauthorized'
|
|
69
|
+
raise "Unauthorized"
|
|
71
70
|
end
|
|
72
|
-
|
|
73
|
-
|
|
71
|
+
end
|
|
72
|
+
if type == 'reject_subscription'
|
|
73
|
+
raise "Subscription Rejected"
|
|
74
|
+
end
|
|
75
|
+
if timeout
|
|
76
|
+
end_time = Time.now
|
|
77
|
+
if (end_time - start_time) > timeout
|
|
78
|
+
raise Timeout::Error, "No Data Timeout"
|
|
74
79
|
end
|
|
75
|
-
next
|
|
76
80
|
end
|
|
81
|
+
if defined? RunningScript and RunningScript.instance
|
|
82
|
+
raise StopScript if RunningScript.instance.stop?
|
|
83
|
+
end
|
|
84
|
+
next
|
|
77
85
|
end
|
|
78
|
-
return json_hash['message']
|
|
79
86
|
end
|
|
80
|
-
return message
|
|
87
|
+
return json_hash['message']
|
|
81
88
|
end
|
|
82
89
|
end
|
|
83
90
|
|
data/lib/openc3/system/system.rb
CHANGED
|
@@ -77,7 +77,8 @@ module OpenC3
|
|
|
77
77
|
end
|
|
78
78
|
end
|
|
79
79
|
|
|
80
|
-
|
|
80
|
+
# target_version can also be the actual hash used in the target_archives folder
|
|
81
|
+
def self.setup_targets(target_names, base_dir, target_version: 'current', scope:)
|
|
81
82
|
# Nothing to do if there are no targets
|
|
82
83
|
return if target_names.nil? or target_names.length == 0
|
|
83
84
|
if @@instance.nil?
|
|
@@ -85,10 +86,15 @@ module OpenC3
|
|
|
85
86
|
FileUtils.mkdir_p(targets_path)
|
|
86
87
|
bucket = Bucket.getClient()
|
|
87
88
|
target_names.each do |target_name|
|
|
89
|
+
# Remove any prior extraction so re-running setup_targets (e.g. after
|
|
90
|
+
# reset_instance! during reingest) starts from a clean slate. Without
|
|
91
|
+
# this, Zip::File#extract fails on files left behind by a previous run.
|
|
92
|
+
FileUtils.rm_rf("#{targets_path}/#{target_name}")
|
|
93
|
+
|
|
88
94
|
# Retrieve bucket/targets/target_name/<TARGET>_current.zip
|
|
89
|
-
zip_path = "#{targets_path}/#{target_name}
|
|
95
|
+
zip_path = "#{targets_path}/#{target_name}_#{target_version}.zip"
|
|
90
96
|
FileUtils.mkdir_p(File.dirname(zip_path))
|
|
91
|
-
bucket_key = "#{scope}/target_archives/#{target_name}/#{target_name}
|
|
97
|
+
bucket_key = "#{scope}/target_archives/#{target_name}/#{target_name}_#{target_version}.zip"
|
|
92
98
|
Logger.info("Retrieving #{bucket_key} from targets bucket")
|
|
93
99
|
bucket.get_object(bucket: ENV['OPENC3_CONFIG_BUCKET'], key: bucket_key, path: zip_path)
|
|
94
100
|
Zip::File.open(zip_path) do |zip_file|
|
|
@@ -116,6 +122,17 @@ module OpenC3
|
|
|
116
122
|
end
|
|
117
123
|
end
|
|
118
124
|
|
|
125
|
+
# Clears the System singleton so the next call to setup_targets or
|
|
126
|
+
# instance rebuilds it. Intended for admin flows (e.g. reingest) that
|
|
127
|
+
# need to load a specific target_version distinct from whatever is
|
|
128
|
+
# currently loaded. Callers must hold an external lock if they need to
|
|
129
|
+
# protect other threads from observing a nil @@instance briefly.
|
|
130
|
+
def self.reset_instance!
|
|
131
|
+
@@instance_mutex.synchronize do
|
|
132
|
+
@@instance = nil
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
119
136
|
# Get the singleton instance of System
|
|
120
137
|
#
|
|
121
138
|
# @param target_names [Array of target_names]
|
|
@@ -42,11 +42,13 @@ module OpenC3
|
|
|
42
42
|
end
|
|
43
43
|
msg_hash['json_data'] = JSON.generate(json_hash.as_json, allow_nan: true)
|
|
44
44
|
msg_hash['extra'] = JSON.generate(packet.extra.as_json, allow_nan: true) if packet.extra
|
|
45
|
-
|
|
45
|
+
db_shard = Store.db_shard_for_target(packet.target_name, scope: scope)
|
|
46
|
+
EphemeralStoreQueued.instance(db_shard: db_shard).write_topic(topic, msg_hash)
|
|
46
47
|
end
|
|
47
48
|
|
|
48
49
|
def self.get_cmd_item(target_name, packet_name, param_name, type: :FORMATTED, scope: $openc3_scope)
|
|
49
|
-
|
|
50
|
+
db_shard = Store.db_shard_for_target(target_name, scope: scope)
|
|
51
|
+
msg_id, msg_hash = Topic.get_newest_message("#{scope}__DECOMCMD__{#{target_name}}__#{packet_name}", db_shard: db_shard)
|
|
50
52
|
if msg_id
|
|
51
53
|
if param_name == 'RECEIVED_COUNT'
|
|
52
54
|
msg_hash['received_count'].to_i
|
|
@@ -33,7 +33,8 @@ module OpenC3
|
|
|
33
33
|
received_count: packet.received_count,
|
|
34
34
|
stored: packet.stored.to_s,
|
|
35
35
|
buffer: packet.buffer(false) }
|
|
36
|
-
|
|
36
|
+
db_shard = Store.db_shard_for_target(packet.target_name, scope: scope)
|
|
37
|
+
EphemeralStoreQueued.instance(db_shard: db_shard).write_topic(topic, msg_hash)
|
|
37
38
|
end
|
|
38
39
|
|
|
39
40
|
# @param command [Hash] Command hash structure read to be written to a topic
|
|
@@ -45,20 +46,22 @@ module OpenC3
|
|
|
45
46
|
command['cmd_params'] = JSON.generate(command['cmd_params'].as_json, allow_nan: true)
|
|
46
47
|
OpenC3.inject_context(command)
|
|
47
48
|
|
|
49
|
+
db_shard = Store.db_shard_for_target(command['target_name'], scope: scope)
|
|
50
|
+
|
|
48
51
|
# Fire-and-forget mode: skip ACK waiting when timeout <= 0
|
|
49
52
|
if timeout <= 0
|
|
50
|
-
Topic.write_topic("{#{scope}__CMD}TARGET__#{command['target_name']}", command, '*', 100)
|
|
53
|
+
Topic.write_topic("{#{scope}__CMD}TARGET__#{command['target_name']}", command, '*', 100, db_shard: db_shard)
|
|
51
54
|
command["cmd_params"] = cmd_params # Restore the original cmd_params Hash
|
|
52
55
|
return command
|
|
53
56
|
end
|
|
54
57
|
|
|
55
58
|
ack_topic = "{#{scope}__ACKCMD}TARGET__#{command['target_name']}"
|
|
56
|
-
Topic.update_topic_offsets([ack_topic])
|
|
57
|
-
cmd_id = Topic.write_topic("{#{scope}__CMD}TARGET__#{command['target_name']}", command, '*', 100)
|
|
59
|
+
Topic.update_topic_offsets([ack_topic], db_shard: db_shard)
|
|
60
|
+
cmd_id = Topic.write_topic("{#{scope}__CMD}TARGET__#{command['target_name']}", command, '*', 100, db_shard: db_shard)
|
|
58
61
|
command["cmd_params"] = cmd_params # Restore the original cmd_params Hash
|
|
59
62
|
time = Time.now
|
|
60
63
|
while (Time.now - time) < timeout
|
|
61
|
-
Topic.read_topics([ack_topic]) do |_topic, _msg_id, msg_hash, _redis|
|
|
64
|
+
Topic.read_topics([ack_topic], db_shard: db_shard) do |_topic, _msg_id, msg_hash, _redis|
|
|
62
65
|
if msg_hash["id"] == cmd_id
|
|
63
66
|
if msg_hash["result"] == "SUCCESS"
|
|
64
67
|
return command
|
|
@@ -26,18 +26,19 @@ module OpenC3
|
|
|
26
26
|
# DecomMicroservice is listening to the DECOMINTERFACE topic and is responsible
|
|
27
27
|
# for actually building the command. This was deliberate to allow this to work
|
|
28
28
|
# with or without an interface.
|
|
29
|
+
db_shard = Store.db_shard_for_target(target_name, scope: scope)
|
|
29
30
|
ack_topic = "{#{scope}__ACKCMD}TARGET__#{target_name}"
|
|
30
|
-
Topic.update_topic_offsets([ack_topic])
|
|
31
|
+
Topic.update_topic_offsets([ack_topic], db_shard: db_shard)
|
|
31
32
|
decom_id = Topic.write_topic("#{scope}__DECOMINTERFACE__{#{target_name}}",
|
|
32
|
-
{ 'build_cmd' => JSON.generate(data, allow_nan: true) }, '*', 100)
|
|
33
|
+
{ 'build_cmd' => JSON.generate(data, allow_nan: true) }, '*', 100, db_shard: db_shard)
|
|
33
34
|
time = Time.now
|
|
34
35
|
while (Time.now - time) < timeout
|
|
35
|
-
Topic.read_topics([ack_topic]) do |_topic, _msg_id, msg_hash, _redis|
|
|
36
|
+
Topic.read_topics([ack_topic], db_shard: db_shard) do |_topic, _msg_id, msg_hash, _redis|
|
|
36
37
|
if msg_hash["id"] == decom_id
|
|
37
38
|
if msg_hash["result"] == "SUCCESS"
|
|
38
39
|
return msg_hash
|
|
39
40
|
else
|
|
40
|
-
raise msg_hash["
|
|
41
|
+
raise msg_hash["result"]
|
|
41
42
|
end
|
|
42
43
|
end
|
|
43
44
|
end
|
|
@@ -45,14 +46,32 @@ module OpenC3
|
|
|
45
46
|
raise "Timeout of #{timeout}s waiting for cmd ack. Does target '#{target_name}' exist?"
|
|
46
47
|
end
|
|
47
48
|
|
|
48
|
-
def self.inject_tlm(target_name, packet_name, item_hash = nil, type: :CONVERTED, scope:)
|
|
49
|
+
def self.inject_tlm(target_name, packet_name, item_hash = nil, type: :CONVERTED, stored: false, timeout: 5, scope:)
|
|
49
50
|
data = {}
|
|
50
51
|
data['target_name'] = target_name.to_s.upcase
|
|
51
52
|
data['packet_name'] = packet_name.to_s.upcase
|
|
52
53
|
data['item_hash'] = item_hash
|
|
53
54
|
data['type'] = type
|
|
54
|
-
|
|
55
|
-
|
|
55
|
+
|
|
56
|
+
db_shard = Store.db_shard_for_target(target_name, scope: scope)
|
|
57
|
+
data['stored'] = stored
|
|
58
|
+
ack_topic = "{#{scope}__ACKCMD}TARGET__#{target_name}"
|
|
59
|
+
Topic.update_topic_offsets([ack_topic], db_shard: db_shard)
|
|
60
|
+
decom_id = Topic.write_topic("#{scope}__DECOMINTERFACE__{#{target_name}}",
|
|
61
|
+
{ 'inject_tlm' => JSON.generate(data, allow_nan: true) }, '*', 100, db_shard: db_shard)
|
|
62
|
+
time = Time.now
|
|
63
|
+
while (Time.now - time) < timeout
|
|
64
|
+
Topic.read_topics([ack_topic], db_shard: db_shard) do |_topic, _msg_id, msg_hash, _redis|
|
|
65
|
+
if msg_hash["id"] == decom_id
|
|
66
|
+
if msg_hash["result"] == "SUCCESS"
|
|
67
|
+
return
|
|
68
|
+
else
|
|
69
|
+
raise msg_hash["result"]
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
raise "Timeout of #{timeout}s waiting for cmd ack. Does target '#{target_name}' exist?"
|
|
56
75
|
end
|
|
57
76
|
|
|
58
77
|
def self.get_tlm_buffer(target_name, packet_name, timeout: 5, scope:)
|
|
@@ -61,13 +80,14 @@ module OpenC3
|
|
|
61
80
|
data['packet_name'] = packet_name.to_s.upcase
|
|
62
81
|
# DecomMicroservice is listening to the DECOMINTERFACE topic and has
|
|
63
82
|
# the most recent decommed packets including subpackets
|
|
83
|
+
db_shard = Store.db_shard_for_target(target_name, scope: scope)
|
|
64
84
|
ack_topic = "{#{scope}__ACKCMD}TARGET__#{target_name}"
|
|
65
|
-
Topic.update_topic_offsets([ack_topic])
|
|
85
|
+
Topic.update_topic_offsets([ack_topic], db_shard: db_shard)
|
|
66
86
|
decom_id = Topic.write_topic("#{scope}__DECOMINTERFACE__{#{target_name}}",
|
|
67
|
-
{ 'get_tlm_buffer' => JSON.generate(data, allow_nan: true) }, '*', 100)
|
|
87
|
+
{ 'get_tlm_buffer' => JSON.generate(data, allow_nan: true) }, '*', 100, db_shard: db_shard)
|
|
68
88
|
time = Time.now
|
|
69
89
|
while (Time.now - time) < timeout
|
|
70
|
-
Topic.read_topics([ack_topic]) do |_topic, _msg_id, msg_hash, _redis|
|
|
90
|
+
Topic.read_topics([ack_topic], db_shard: db_shard) do |_topic, _msg_id, msg_hash, _redis|
|
|
71
91
|
if msg_hash["id"] == decom_id
|
|
72
92
|
if msg_hash["result"] == "SUCCESS"
|
|
73
93
|
msg_hash["stored"] = ConfigParser.handle_true_false(msg_hash["stored"])
|
|
@@ -77,7 +97,7 @@ module OpenC3
|
|
|
77
97
|
end
|
|
78
98
|
return msg_hash
|
|
79
99
|
else
|
|
80
|
-
raise msg_hash["
|
|
100
|
+
raise msg_hash["result"]
|
|
81
101
|
end
|
|
82
102
|
end
|
|
83
103
|
end
|