openc3 5.17.1 → 5.19.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +7 -4
- data/bin/cstol_converter +14 -14
- data/bin/openc3cli +190 -8
- data/data/config/_interfaces.yaml +5 -5
- data/data/config/command_modifiers.yaml +59 -0
- data/data/config/interface_modifiers.yaml +19 -9
- data/data/config/item_modifiers.yaml +34 -26
- data/data/config/microservice.yaml +4 -1
- data/data/config/param_item_modifiers.yaml +17 -1
- data/data/config/parameter_modifiers.yaml +30 -13
- data/data/config/plugins.yaml +9 -5
- data/data/config/screen.yaml +9 -9
- data/data/config/table_manager.yaml +2 -2
- data/data/config/telemetry_modifiers.yaml +9 -4
- data/data/config/tool.yaml +4 -1
- data/data/config/widgets.yaml +44 -17
- data/ext/openc3/ext/config_parser/config_parser.c +1 -1
- data/ext/openc3/ext/packet/packet.c +7 -1
- data/ext/openc3/ext/platform/platform.c +3 -3
- data/ext/openc3/ext/structure/structure.c +56 -76
- data/lib/openc3/accessors/accessor.rb +1 -0
- data/lib/openc3/accessors/binary_accessor.rb +174 -15
- data/lib/openc3/accessors/form_accessor.rb +2 -2
- data/lib/openc3/accessors/http_accessor.rb +1 -1
- data/lib/openc3/accessors/json_accessor.rb +6 -4
- data/lib/openc3/accessors/template_accessor.rb +6 -9
- data/lib/openc3/accessors/xml_accessor.rb +1 -1
- data/lib/openc3/api/cmd_api.rb +72 -44
- data/lib/openc3/api/config_api.rb +10 -10
- data/lib/openc3/api/interface_api.rb +28 -21
- data/lib/openc3/api/limits_api.rb +30 -30
- data/lib/openc3/api/metrics_api.rb +3 -3
- data/lib/openc3/api/offline_access_api.rb +5 -5
- data/lib/openc3/api/router_api.rb +25 -19
- data/lib/openc3/api/settings_api.rb +10 -10
- data/lib/openc3/api/stash_api.rb +10 -10
- data/lib/openc3/api/target_api.rb +10 -10
- data/lib/openc3/api/tlm_api.rb +44 -44
- data/lib/openc3/config/config_parser.rb +1 -1
- data/lib/openc3/conversions/bit_reverse_conversion.rb +60 -0
- data/lib/openc3/conversions/ip_read_conversion.rb +59 -0
- data/lib/openc3/conversions/ip_write_conversion.rb +61 -0
- data/lib/openc3/conversions/object_read_conversion.rb +88 -0
- data/lib/openc3/conversions/object_write_conversion.rb +38 -0
- data/lib/openc3/conversions/segmented_polynomial_conversion.rb +7 -7
- data/lib/openc3/conversions.rb +6 -1
- data/lib/openc3/core_ext/array.rb +5 -5
- data/lib/openc3/core_ext/exception.rb +9 -2
- data/lib/openc3/core_ext/string.rb +2 -2
- data/lib/openc3/interfaces/http_server_interface.rb +1 -0
- data/lib/openc3/interfaces/interface.rb +1 -1
- data/lib/openc3/interfaces/linc_interface.rb +3 -3
- data/lib/openc3/io/json_api.rb +11 -6
- data/lib/openc3/io/json_drb.rb +19 -21
- data/lib/openc3/io/json_rpc.rb +15 -14
- data/lib/openc3/logs/buffered_packet_log_writer.rb +3 -3
- data/lib/openc3/logs/log_writer.rb +7 -8
- data/lib/openc3/logs/packet_log_writer.rb +7 -7
- data/lib/openc3/logs/text_log_writer.rb +4 -4
- data/lib/openc3/microservices/decom_microservice.rb +19 -4
- data/lib/openc3/microservices/interface_microservice.rb +41 -3
- data/lib/openc3/microservices/microservice.rb +11 -11
- data/lib/openc3/microservices/reaction_microservice.rb +2 -2
- data/lib/openc3/microservices/scope_cleanup_microservice.rb +1 -1
- data/lib/openc3/microservices/timeline_microservice.rb +70 -45
- data/lib/openc3/microservices/trigger_group_microservice.rb +3 -3
- data/lib/openc3/migrations/20240915000000_activity_uuid.rb +28 -0
- data/lib/openc3/models/activity_model.rb +124 -92
- data/lib/openc3/models/auth_model.rb +31 -2
- data/lib/openc3/models/cvt_model.rb +11 -5
- data/lib/openc3/models/gem_model.rb +8 -8
- data/lib/openc3/models/plugin_model.rb +3 -3
- data/lib/openc3/models/reducer_model.rb +2 -2
- data/lib/openc3/models/scope_model.rb +45 -14
- data/lib/openc3/models/sorted_model.rb +5 -5
- data/lib/openc3/models/target_model.rb +7 -4
- data/lib/openc3/models/tool_config_model.rb +1 -1
- data/lib/openc3/models/tool_model.rb +4 -4
- data/lib/openc3/models/widget_model.rb +11 -5
- data/lib/openc3/operators/microservice_operator.rb +2 -2
- data/lib/openc3/operators/operator.rb +14 -12
- data/lib/openc3/packets/command_validator.rb +48 -0
- data/lib/openc3/packets/commands.rb +6 -14
- data/lib/openc3/packets/packet.rb +49 -16
- data/lib/openc3/packets/packet_config.rb +47 -25
- data/lib/openc3/packets/packet_item.rb +5 -0
- data/lib/openc3/packets/parsers/packet_parser.rb +3 -3
- data/lib/openc3/packets/structure.rb +87 -15
- data/lib/openc3/packets/structure_item.rb +76 -53
- data/lib/openc3/packets/telemetry.rb +6 -27
- data/lib/openc3/script/api_shared.rb +7 -5
- data/lib/openc3/script/calendar.rb +2 -2
- data/lib/openc3/script/commands.rb +6 -4
- data/lib/openc3/script/extract.rb +5 -3
- data/lib/openc3/script/metadata.rb +2 -2
- data/lib/openc3/script/suite.rb +17 -17
- data/lib/openc3/script/web_socket_api.rb +11 -0
- data/lib/openc3/streams/serial_stream.rb +2 -3
- data/lib/openc3/streams/stream.rb +2 -2
- data/lib/openc3/tools/cmd_tlm_server/interface_thread.rb +10 -10
- data/lib/openc3/tools/table_manager/table_manager_core.rb +11 -11
- data/lib/openc3/tools/table_manager/table_parser.rb +2 -3
- data/lib/openc3/topics/command_decom_topic.rb +2 -1
- data/lib/openc3/topics/command_topic.rb +3 -3
- data/lib/openc3/topics/decom_interface_topic.rb +4 -3
- data/lib/openc3/topics/system_events_topic.rb +40 -0
- data/lib/openc3/topics/telemetry_decom_topic.rb +1 -1
- data/lib/openc3/utilities/authentication.rb +2 -1
- data/lib/openc3/utilities/authorization.rb +4 -3
- data/lib/openc3/utilities/cli_generator.rb +15 -8
- data/lib/openc3/utilities/cosmos_rails_formatter.rb +60 -0
- data/lib/openc3/utilities/crc.rb +6 -6
- data/lib/openc3/utilities/local_mode.rb +2 -1
- data/lib/openc3/utilities/logger.rb +44 -34
- data/lib/openc3/utilities/metric.rb +1 -2
- data/lib/openc3/utilities/quaternion.rb +18 -18
- data/lib/openc3/utilities/target_file.rb +4 -4
- data/lib/openc3/version.rb +6 -6
- data/lib/openc3/win32/win32_main.rb +2 -2
- data/templates/tool_angular/package.json +22 -22
- data/templates/tool_react/package.json +13 -13
- data/templates/tool_svelte/package.json +14 -14
- data/templates/tool_svelte/src/services/openc3-api.js +17 -17
- data/templates/tool_vue/package.json +13 -13
- data/templates/widget/package.json +11 -12
- data/templates/widget/src/Widget.vue +0 -1
- metadata +25 -2
@@ -20,13 +20,12 @@
|
|
20
20
|
# This file may also be used under the terms of a commercial license
|
21
21
|
# if purchased from OpenC3, Inc.
|
22
22
|
|
23
|
-
require 'thread'
|
24
23
|
require 'openc3/config/config_parser'
|
25
24
|
require 'openc3/topics/topic'
|
26
25
|
require 'openc3/utilities/bucket_utilities'
|
27
26
|
|
28
27
|
module OpenC3
|
29
|
-
# Creates a log. Can automatically cycle the log based on an
|
28
|
+
# Creates a log. Can automatically cycle the log based on an elapsed
|
30
29
|
# time period or when the log file reaches a predefined size.
|
31
30
|
class LogWriter
|
32
31
|
# @return [String] The filename of the packet log
|
@@ -275,10 +274,10 @@ module OpenC3
|
|
275
274
|
@last_time = nil
|
276
275
|
@previous_time_nsec_since_epoch = nil
|
277
276
|
Logger.debug "Log File Opened : #{@filename}"
|
278
|
-
rescue =>
|
279
|
-
Logger.error "Error starting new log file: #{
|
277
|
+
rescue => e
|
278
|
+
Logger.error "Error starting new log file: #{e.formatted}"
|
280
279
|
@logging_enabled = false
|
281
|
-
OpenC3.handle_critical_exception(
|
280
|
+
OpenC3.handle_critical_exception(e)
|
282
281
|
end
|
283
282
|
|
284
283
|
# @enforce_time_order requires the timestamps on each write to be greater than the previous
|
@@ -328,10 +327,10 @@ module OpenC3
|
|
328
327
|
@last_offsets.each do |redis_topic, last_offset|
|
329
328
|
@cleanup_offsets[-1][redis_topic] = last_offset
|
330
329
|
end
|
331
|
-
@cleanup_times << Time.now + CLEANUP_DELAY
|
330
|
+
@cleanup_times << (Time.now + CLEANUP_DELAY)
|
332
331
|
@last_offsets.clear
|
333
|
-
rescue Exception =>
|
334
|
-
Logger.error "Error closing #{@filename} : #{
|
332
|
+
rescue Exception => e
|
333
|
+
Logger.error "Error closing #{@filename} : #{e.formatted}"
|
335
334
|
end
|
336
335
|
|
337
336
|
@file = nil
|
@@ -26,7 +26,7 @@ require 'openc3/models/target_model'
|
|
26
26
|
require 'cbor'
|
27
27
|
|
28
28
|
module OpenC3
|
29
|
-
# Creates a packet log. Can automatically cycle the log based on an
|
29
|
+
# Creates a packet log. Can automatically cycle the log based on an elapsed
|
30
30
|
# time period or when the log file reaches a predefined size.
|
31
31
|
class PacketLogWriter < LogWriter
|
32
32
|
include PacketLogConstants
|
@@ -112,9 +112,9 @@ module OpenC3
|
|
112
112
|
ensure
|
113
113
|
@mutex.unlock if take_mutex
|
114
114
|
end
|
115
|
-
rescue =>
|
116
|
-
Logger.instance.error "Error writing #{@filename} : #{
|
117
|
-
OpenC3.handle_critical_exception(
|
115
|
+
rescue => e
|
116
|
+
Logger.instance.error "Error writing #{@filename} : #{e.formatted}"
|
117
|
+
OpenC3.handle_critical_exception(e)
|
118
118
|
end
|
119
119
|
|
120
120
|
# Starting a new file is a critical operation so the entire method is
|
@@ -133,10 +133,10 @@ module OpenC3
|
|
133
133
|
@next_target_index = 0
|
134
134
|
@target_dec_entries = []
|
135
135
|
@packet_dec_entries = []
|
136
|
-
rescue =>
|
137
|
-
Logger.error "Error starting new log file: #{
|
136
|
+
rescue => e
|
137
|
+
Logger.error "Error starting new log file: #{e.formatted}"
|
138
138
|
@logging_enabled = false
|
139
|
-
OpenC3.handle_critical_exception(
|
139
|
+
OpenC3.handle_critical_exception(e)
|
140
140
|
end
|
141
141
|
|
142
142
|
# Closing a log file isn't critical so we just log an error
|
@@ -24,7 +24,7 @@ require 'openc3/logs/log_writer'
|
|
24
24
|
require 'socket'
|
25
25
|
|
26
26
|
module OpenC3
|
27
|
-
# Creates a text log. Can automatically cycle the log based on an
|
27
|
+
# Creates a text log. Can automatically cycle the log based on an elapsed
|
28
28
|
# time period or when the log file reaches a predefined size.
|
29
29
|
class TextLogWriter < LogWriter
|
30
30
|
NEWLINE = "\n".freeze
|
@@ -48,9 +48,9 @@ module OpenC3
|
|
48
48
|
prepare_write(time_nsec_since_epoch, data.length, redis_topic, redis_offset)
|
49
49
|
write_entry(time_nsec_since_epoch, data) if @file
|
50
50
|
end
|
51
|
-
rescue =>
|
52
|
-
Logger.instance.error "Error writing #{@filename} : #{
|
53
|
-
OpenC3.handle_critical_exception(
|
51
|
+
rescue => e
|
52
|
+
Logger.instance.error "Error writing #{@filename} : #{e.formatted}"
|
53
|
+
OpenC3.handle_critical_exception(e)
|
54
54
|
end
|
55
55
|
|
56
56
|
def write_entry(time_nsec_since_epoch, data)
|
@@ -14,7 +14,7 @@
|
|
14
14
|
# GNU Affero General Public License for more details.
|
15
15
|
|
16
16
|
# Modified by OpenC3, Inc.
|
17
|
-
# All changes Copyright
|
17
|
+
# All changes Copyright 2024, OpenC3, Inc.
|
18
18
|
# All Rights Reserved
|
19
19
|
#
|
20
20
|
# This file may also be used under the terms of a commercial license
|
@@ -102,9 +102,22 @@ module OpenC3
|
|
102
102
|
packet.extra = extra
|
103
103
|
end
|
104
104
|
packet.buffer = msg_hash["buffer"]
|
105
|
-
|
106
|
-
|
105
|
+
# Processors are user code points which must be rescued
|
106
|
+
# so the TelemetryDecomTopic can write the packet
|
107
|
+
begin
|
108
|
+
packet.process # Run processors
|
109
|
+
rescue Exception => e
|
110
|
+
@error_count += 1
|
111
|
+
@metric.set(name: 'decom_error_total', value: @error_count, type: 'counter')
|
112
|
+
@error = e
|
113
|
+
@logger.error e.message
|
114
|
+
end
|
115
|
+
# Process all the limits and call the limits_change_callback (as necessary)
|
116
|
+
# check_limits also can call user code in the limits response
|
117
|
+
# but that is rescued separately in the limits_change_callback
|
118
|
+
packet.check_limits(System.limits_set)
|
107
119
|
|
120
|
+
# This is what updates the CVT
|
108
121
|
TelemetryDecomTopic.write_packet(packet, scope: @scope)
|
109
122
|
diff = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start # seconds as a float
|
110
123
|
@metric.set(name: 'decom_duration_seconds', value: diff, type: 'gauge', unit: 'seconds')
|
@@ -150,12 +163,14 @@ module OpenC3
|
|
150
163
|
|
151
164
|
if item.limits.response
|
152
165
|
begin
|
166
|
+
# TODO: The limits response is user code and should be run as a separate thread / process
|
167
|
+
# If this code blocks it will delay TelemetryDecomTopic.write_packet
|
153
168
|
item.limits.response.call(packet, item, old_limits_state)
|
154
169
|
rescue Exception => e
|
155
170
|
@error = e
|
156
171
|
@logger.error "#{packet.target_name} #{packet.packet_name} #{item.name} Limits Response Exception!"
|
157
172
|
@logger.error "Called with old_state = #{old_limits_state}, new_state = #{item.limits.state}"
|
158
|
-
@logger.error e.
|
173
|
+
@logger.error e.filtered
|
159
174
|
end
|
160
175
|
end
|
161
176
|
end
|
@@ -31,6 +31,7 @@ require 'openc3/topics/command_topic'
|
|
31
31
|
require 'openc3/topics/command_decom_topic'
|
32
32
|
require 'openc3/topics/interface_topic'
|
33
33
|
require 'openc3/topics/router_topic'
|
34
|
+
require 'openc3/interfaces/interface'
|
34
35
|
|
35
36
|
module OpenC3
|
36
37
|
class InterfaceCmdHandlerThread
|
@@ -191,22 +192,59 @@ module OpenC3
|
|
191
192
|
next e.message
|
192
193
|
end
|
193
194
|
|
195
|
+
command.extra ||= {}
|
196
|
+
command.extra['cmd_string'] = msg_hash['cmd_string']
|
197
|
+
command.extra['username'] = msg_hash['username']
|
194
198
|
if hazardous_check
|
195
199
|
hazardous, hazardous_description = System.commands.cmd_pkt_hazardous?(command)
|
196
200
|
# Return back the error, description, and the formatted command
|
197
201
|
# This allows the error handler to simply re-send the command
|
198
|
-
next "HazardousError\n#{hazardous_description}\n#{
|
202
|
+
next "HazardousError\n#{hazardous_description}\n#{msg_hash['cmd_string']}" if hazardous
|
199
203
|
end
|
200
204
|
|
205
|
+
validate = ConfigParser.handle_true_false(msg_hash['validate'])
|
201
206
|
begin
|
202
207
|
if @interface.connected?
|
208
|
+
result = true
|
209
|
+
reason = nil
|
210
|
+
if command.validator and validate
|
211
|
+
begin
|
212
|
+
result, reason = command.validator.pre_check(command)
|
213
|
+
rescue => e
|
214
|
+
result = false
|
215
|
+
reason = e.message
|
216
|
+
end
|
217
|
+
# Explicitly check for false to allow nil to represent unknown
|
218
|
+
if result == false
|
219
|
+
message = "pre_check returned false for #{msg_hash['cmd_string']} due to #{reason}"
|
220
|
+
raise WriteRejectError.new(message)
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
203
224
|
@count += 1
|
204
225
|
@metric.set(name: 'interface_cmd_total', value: @count, type: 'counter') if @metric
|
205
|
-
|
206
226
|
@interface.write(command)
|
207
|
-
|
227
|
+
|
228
|
+
if command.validator and validate
|
229
|
+
begin
|
230
|
+
result, reason = command.validator.post_check(command)
|
231
|
+
rescue => e
|
232
|
+
result = false
|
233
|
+
reason = e.message
|
234
|
+
end
|
235
|
+
command.extra['cmd_success'] = result
|
236
|
+
command.extra['cmd_reason'] = reason if reason
|
237
|
+
end
|
238
|
+
|
208
239
|
CommandDecomTopic.write_packet(command, scope: @scope)
|
240
|
+
CommandTopic.write_packet(command, scope: @scope)
|
209
241
|
InterfaceStatusModel.set(@interface.as_json(:allow_nan => true), queued: true, scope: @scope)
|
242
|
+
|
243
|
+
# Explicitly check for false to allow nil to represent unknown
|
244
|
+
if result == false
|
245
|
+
message = "post_check returned false for #{msg_hash['cmd_string']} due to #{reason}"
|
246
|
+
raise WriteRejectError.new(message)
|
247
|
+
end
|
210
248
|
next 'SUCCESS'
|
211
249
|
else
|
212
250
|
next "Interface not connected: #{@interface.name}"
|
@@ -14,7 +14,7 @@
|
|
14
14
|
# GNU Affero General Public License for more details.
|
15
15
|
|
16
16
|
# Modified by OpenC3, Inc.
|
17
|
-
# All changes Copyright
|
17
|
+
# All changes Copyright 2024, OpenC3, Inc.
|
18
18
|
# All Rights Reserved
|
19
19
|
#
|
20
20
|
# This file may also be used under the terms of a commercial license
|
@@ -54,13 +54,13 @@ module OpenC3
|
|
54
54
|
microservice.state = 'RUNNING'
|
55
55
|
microservice.run
|
56
56
|
microservice.state = 'FINISHED'
|
57
|
-
rescue Exception =>
|
58
|
-
if SystemExit ===
|
57
|
+
rescue Exception => e
|
58
|
+
if SystemExit === e or SignalException === e
|
59
59
|
microservice.state = 'KILLED'
|
60
60
|
else
|
61
|
-
microservice.error =
|
61
|
+
microservice.error = e
|
62
62
|
microservice.state = 'DIED_ERROR'
|
63
|
-
Logger.fatal("Microservice #{name} dying from exception\n#{
|
63
|
+
Logger.fatal("Microservice #{name} dying from exception\n#{e.formatted}")
|
64
64
|
end
|
65
65
|
ensure
|
66
66
|
MicroserviceStatusModel.set(microservice.as_json(:allow_nan => true), scope: microservice.scope)
|
@@ -171,7 +171,7 @@ module OpenC3
|
|
171
171
|
# Run ruby syntax so we can log those
|
172
172
|
syntax_check, _ = Open3.capture2e("ruby -c #{ruby_filename}")
|
173
173
|
if /Syntax OK/.match?(syntax_check)
|
174
|
-
@logger.
|
174
|
+
@logger.debug("Ruby microservice #{@name} file #{ruby_filename} passed syntax check\n", scope: @scope)
|
175
175
|
else
|
176
176
|
@logger.error("Ruby microservice #{@name} file #{ruby_filename} failed syntax check\n#{syntax_check}", scope: @scope)
|
177
177
|
end
|
@@ -188,9 +188,9 @@ module OpenC3
|
|
188
188
|
MicroserviceStatusModel.set(as_json(:allow_nan => true), scope: @scope) unless @cancel_thread
|
189
189
|
break if @microservice_status_sleeper.sleep(@microservice_status_period_seconds)
|
190
190
|
end
|
191
|
-
rescue Exception =>
|
192
|
-
@logger.error "#{@name} status thread died: #{
|
193
|
-
raise
|
191
|
+
rescue Exception => e
|
192
|
+
@logger.error "#{@name} status thread died: #{e.formatted}"
|
193
|
+
raise e
|
194
194
|
end
|
195
195
|
end
|
196
196
|
end
|
@@ -208,7 +208,7 @@ module OpenC3
|
|
208
208
|
MicroserviceStatusModel.set(as_json(:allow_nan => true), scope: @scope)
|
209
209
|
FileUtils.remove_entry(@temp_dir) if File.exist?(@temp_dir)
|
210
210
|
@metric.shutdown
|
211
|
-
@logger.
|
211
|
+
@logger.debug("Shutting down microservice complete: #{@name}")
|
212
212
|
@shutdown_complete = true
|
213
213
|
end
|
214
214
|
|
@@ -220,7 +220,7 @@ module OpenC3
|
|
220
220
|
end
|
221
221
|
|
222
222
|
# Returns if the command was handled
|
223
|
-
def microservice_cmd(topic, msg_id, msg_hash,
|
223
|
+
def microservice_cmd(topic, msg_id, msg_hash, _redis)
|
224
224
|
command = msg_hash['command']
|
225
225
|
case command
|
226
226
|
when 'ADD_TOPICS'
|
@@ -368,7 +368,7 @@ module OpenC3
|
|
368
368
|
end
|
369
369
|
|
370
370
|
# The reaction snooze manager starts a thread pool and keeps track of when a
|
371
|
-
# reaction is activated and to
|
371
|
+
# reaction is activated and to evaluate triggers when the snooze is complete.
|
372
372
|
class ReactionSnoozeManager
|
373
373
|
attr_reader :name, :scope, :share, :thread_pool
|
374
374
|
|
@@ -432,7 +432,7 @@ module OpenC3
|
|
432
432
|
|
433
433
|
def shutdown
|
434
434
|
@cancel_thread = true
|
435
|
-
@worker_count.times do |
|
435
|
+
@worker_count.times do |_i|
|
436
436
|
@share.queue_base.enqueue(kind: nil, data: nil)
|
437
437
|
end
|
438
438
|
end
|
@@ -22,7 +22,7 @@ require 'openc3/microservices/cleanup_microservice'
|
|
22
22
|
module OpenC3
|
23
23
|
class ScopeCleanupMicroservice < CleanupMicroservice
|
24
24
|
def run
|
25
|
-
scope = ScopeModel.get_model(name: @scope
|
25
|
+
scope = ScopeModel.get_model(name: @scope)
|
26
26
|
|
27
27
|
areas = [
|
28
28
|
["#{@scope}/text_logs", scope.text_log_retain_time],
|
@@ -24,6 +24,7 @@ require 'openc3/utilities/authentication'
|
|
24
24
|
require 'openc3/microservices/microservice'
|
25
25
|
require 'openc3/models/activity_model'
|
26
26
|
require 'openc3/models/timeline_model'
|
27
|
+
require 'openc3/models/tool_config_model'
|
27
28
|
require 'openc3/topics/timeline_topic'
|
28
29
|
|
29
30
|
require 'openc3/script'
|
@@ -82,45 +83,66 @@ module OpenC3
|
|
82
83
|
end
|
83
84
|
end
|
84
85
|
|
86
|
+
def get_exec_setting()
|
87
|
+
json = ToolConfigModel.load_config('calendar-settings', 'default', scope: @scope)
|
88
|
+
if json
|
89
|
+
settings = JSON.parse(json)
|
90
|
+
return settings['execEnabled']
|
91
|
+
else
|
92
|
+
# Default is execute
|
93
|
+
return true
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
85
97
|
def run_command(activity)
|
86
98
|
@logger.info "#{@timeline_name} run_command > #{activity.as_json(:allow_nan => true)}"
|
87
99
|
begin
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
100
|
+
if get_exec_setting()
|
101
|
+
username = activity.data['username']
|
102
|
+
token = get_token(username)
|
103
|
+
raise "No token available for username: #{username}" unless token
|
104
|
+
cmd_no_hazardous_check(activity.data['command'], scope: @scope, token: token)
|
105
|
+
activity.commit(status: 'completed', fulfillment: true)
|
106
|
+
else
|
107
|
+
activity.commit(status: 'disabled', message: 'Execution is disabled')
|
108
|
+
@logger.warn "#{@timeline_name} run_command disabled > #{activity.as_json(:allow_nan => true)}"
|
109
|
+
end
|
93
110
|
rescue StandardError => e
|
94
111
|
activity.commit(status: 'failed', message: e.message)
|
95
|
-
@logger.error "#{@timeline_name}
|
112
|
+
@logger.error "#{@timeline_name} run_command failed > #{activity.as_json(:allow_nan => true)}, #{e.formatted}"
|
96
113
|
end
|
97
114
|
end
|
98
115
|
|
99
116
|
def run_script(activity)
|
100
117
|
@logger.info "#{@timeline_name} run_script > #{activity.as_json(:allow_nan => true)}"
|
101
118
|
begin
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
119
|
+
if get_exec_setting()
|
120
|
+
username = activity.data['username']
|
121
|
+
token = get_token(username)
|
122
|
+
raise "No token available for username: #{username}" unless token
|
123
|
+
request = Net::HTTP::Post.new(
|
124
|
+
"/script-api/scripts/#{activity.data['script']}/run?scope=#{@scope}",
|
125
|
+
'Content-Type' => 'application/json',
|
126
|
+
'Authorization' => token
|
127
|
+
)
|
128
|
+
request.body = JSON.generate({
|
129
|
+
'scope' => @scope,
|
130
|
+
'environment' => activity.data['environment'],
|
131
|
+
'timeline' => @timeline_name,
|
132
|
+
'id' => activity.start
|
133
|
+
})
|
134
|
+
hostname = ENV['OPENC3_SCRIPT_HOSTNAME'] || 'openc3-cosmos-script-runner-api'
|
135
|
+
response = Net::HTTP.new(hostname, 2902).request(request)
|
136
|
+
raise "failed to call #{hostname}, for script: #{activity.data['script']}, response code: #{response.code}" if response.code != '200'
|
137
|
+
|
138
|
+
activity.commit(status: 'completed', message: "#{activity.data['script']} => #{response.body}", fulfillment: true)
|
139
|
+
else
|
140
|
+
activity.commit(status: 'disabled', message: 'Execution is disabled')
|
141
|
+
@logger.warn "#{@timeline_name} run_script disabled > #{activity.as_json(:allow_nan => true)}"
|
142
|
+
end
|
121
143
|
rescue StandardError => e
|
122
144
|
activity.commit(status: 'failed', message: e.message)
|
123
|
-
@logger.error "#{@timeline_name} run_script failed > #{activity.as_json(:allow_nan => true)
|
145
|
+
@logger.error "#{@timeline_name} run_script failed > #{activity.as_json(:allow_nan => true)}, #{e.message}"
|
124
146
|
end
|
125
147
|
end
|
126
148
|
|
@@ -212,7 +234,7 @@ module OpenC3
|
|
212
234
|
def run
|
213
235
|
@logger.info "#{@timeline_name} timeline manager running"
|
214
236
|
loop do
|
215
|
-
start = Time.now.
|
237
|
+
start = Time.now.to_f
|
216
238
|
@schedule.activities.each do |activity|
|
217
239
|
start_difference = activity.start - start
|
218
240
|
if start_difference <= 0 && @schedule.not_queued?(activity.start)
|
@@ -230,18 +252,18 @@ module OpenC3
|
|
230
252
|
sleep(1)
|
231
253
|
break if @cancel_thread
|
232
254
|
end
|
233
|
-
@logger.info "#{@timeline_name}
|
255
|
+
@logger.info "#{@timeline_name} timeline manager exiting"
|
234
256
|
end
|
235
257
|
|
236
258
|
# Add task to remove events older than 7 days
|
237
259
|
def add_expire_activity
|
238
|
-
now = Time.now.
|
239
|
-
@expire = now +
|
260
|
+
now = Time.now.to_f
|
261
|
+
@expire = now + 3540 # Needs to be less than 3600 which is the hour we store in memory
|
240
262
|
activity = ActivityModel.new(
|
241
263
|
name: @timeline_name,
|
242
264
|
scope: @scope,
|
243
265
|
start: 0,
|
244
|
-
stop: (now - 86_400 * 7),
|
266
|
+
stop: (now - (86_400 * 7)),
|
245
267
|
kind: 'expire',
|
246
268
|
data: {}
|
247
269
|
)
|
@@ -283,13 +305,13 @@ module OpenC3
|
|
283
305
|
super(name)
|
284
306
|
@timeline_name = name.split('__')[2]
|
285
307
|
@schedule = Schedule.new(@timeline_name)
|
286
|
-
@manager = TimelineManager.new(name: @timeline_name, logger: @logger, scope: scope, schedule: @schedule)
|
308
|
+
@manager = TimelineManager.new(name: @timeline_name, logger: @logger, scope: @scope, schedule: @schedule)
|
287
309
|
@manager_thread = nil
|
288
310
|
@read_topic = true
|
289
311
|
end
|
290
312
|
|
291
313
|
def run
|
292
|
-
@logger.info "#{@name}
|
314
|
+
@logger.info "#{@name} timeline running"
|
293
315
|
@manager_thread = Thread.new { @manager.run }
|
294
316
|
loop do
|
295
317
|
current_activities = ActivityModel.activities(name: @timeline_name, scope: @scope)
|
@@ -299,19 +321,19 @@ module OpenC3
|
|
299
321
|
block_for_updates()
|
300
322
|
break if @cancel_thread
|
301
323
|
end
|
302
|
-
@logger.info "#{@name}
|
324
|
+
@logger.info "#{@name} timeline exiting"
|
303
325
|
end
|
304
326
|
|
305
327
|
def topic_lookup_functions
|
306
328
|
{
|
307
329
|
'timeline' => {
|
308
|
-
'created' => :
|
330
|
+
'created' => :timeline_noop,
|
309
331
|
'refresh' => :schedule_refresh,
|
310
|
-
'updated' => :
|
311
|
-
'deleted' => :
|
332
|
+
'updated' => :timeline_noop,
|
333
|
+
'deleted' => :timeline_noop
|
312
334
|
},
|
313
335
|
'activity' => {
|
314
|
-
'event' => :
|
336
|
+
'event' => :timeline_noop,
|
315
337
|
'created' => :create_activity_from_event,
|
316
338
|
'updated' => :schedule_refresh,
|
317
339
|
'deleted' => :remove_activity_from_event
|
@@ -335,7 +357,7 @@ module OpenC3
|
|
335
357
|
end
|
336
358
|
end
|
337
359
|
|
338
|
-
def
|
360
|
+
def timeline_noop(data)
|
339
361
|
@logger.debug "#{@name} timeline web socket event: #{data}"
|
340
362
|
end
|
341
363
|
|
@@ -347,8 +369,8 @@ module OpenC3
|
|
347
369
|
# Add the activity to the schedule. We don't need to hold the job in memory
|
348
370
|
# if it is longer than an hour away. A refresh task will update that.
|
349
371
|
def create_activity_from_event(data)
|
350
|
-
diff = data['start'] - Time.now.
|
351
|
-
return
|
372
|
+
diff = data['start'] - Time.now.to_f
|
373
|
+
return if diff < 0 or diff > 3600
|
352
374
|
|
353
375
|
activity = ActivityModel.from_json(data, name: @timeline_name, scope: @scope)
|
354
376
|
@schedule.add_activity(activity)
|
@@ -357,17 +379,20 @@ module OpenC3
|
|
357
379
|
# Remove the activity from the schedule. We don't need to remove the activity
|
358
380
|
# if it is longer than an hour away. It will be removed from the data.
|
359
381
|
def remove_activity_from_event(data)
|
360
|
-
diff = data['start'] - Time.now.
|
361
|
-
return
|
382
|
+
diff = data['start'] - Time.now.to_f
|
383
|
+
return if diff < 0 or diff > 3600
|
362
384
|
|
363
385
|
activity = ActivityModel.from_json(data, name: @timeline_name, scope: @scope)
|
364
386
|
@schedule.remove_activity(activity)
|
365
387
|
end
|
366
388
|
|
367
389
|
def shutdown
|
368
|
-
@read_topic = false
|
369
390
|
@manager.shutdown
|
370
|
-
super
|
391
|
+
# super also sets @cancel_thread = true but we want to set it first
|
392
|
+
# so when we set @read_topic = false the run loop stops
|
393
|
+
@cancel_thread = true
|
394
|
+
@read_topic = false
|
395
|
+
super()
|
371
396
|
end
|
372
397
|
end
|
373
398
|
end
|
@@ -445,7 +445,7 @@ module OpenC3
|
|
445
445
|
visited["#{trigger.name}__P"] = Hash.new
|
446
446
|
end
|
447
447
|
if visited["#{head.name}__P"][trigger.name]
|
448
|
-
# Not sure if this is
|
448
|
+
# Not sure if this is possible as on create it validates that the dependents are already created
|
449
449
|
message = "loop detected from #{head.name} -> #{trigger.name} path: #{visited["#{head.name}__P"]}"
|
450
450
|
notify(name: trigger.name, severity: 'error', message: message)
|
451
451
|
return visited["#{trigger.name}__R"] = -1
|
@@ -494,7 +494,7 @@ module OpenC3
|
|
494
494
|
end
|
495
495
|
|
496
496
|
# The trigger manager starts a thread pool and subscribes
|
497
|
-
# to the
|
497
|
+
# to the telemetry decom topic. It adds the "packet" to the thread pool queue
|
498
498
|
# and the thread will evaluate the "trigger".
|
499
499
|
class TriggerGroupManager
|
500
500
|
attr_reader :name, :scope, :share, :group, :topics, :thread_pool
|
@@ -621,7 +621,7 @@ module OpenC3
|
|
621
621
|
loop do
|
622
622
|
triggers = TriggerModel.all(scope: @scope, group: @group)
|
623
623
|
@share.trigger_base.rebuild(triggers: triggers)
|
624
|
-
@manager.refresh() #
|
624
|
+
@manager.refresh() # Every time we do a full base update we refresh the manager
|
625
625
|
break if @cancel_thread
|
626
626
|
block_for_updates()
|
627
627
|
break if @cancel_thread
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'openc3/utilities/migration'
|
2
|
+
require 'openc3/models/scope_model'
|
3
|
+
require 'openc3/models/timeline_model'
|
4
|
+
|
5
|
+
module OpenC3
|
6
|
+
class ActivityUuid < Migration
|
7
|
+
def self.run
|
8
|
+
ScopeModel.names.each do |scope|
|
9
|
+
TimelineModel.names.each do |key|
|
10
|
+
name = key.split('__').last
|
11
|
+
json = Store.zrange("#{scope}#{ActivityModel::PRIMARY_KEY}__#{name}", 0, -1)
|
12
|
+
parsed = json.map { |value| JSON.parse(value, :allow_nan => true, :create_additions => true) }
|
13
|
+
parsed.each_with_index do |activity, index|
|
14
|
+
if activity['uuid'].nil?
|
15
|
+
activity['uuid'] = SecureRandom.uuid
|
16
|
+
Store.zrem("#{scope}#{ActivityModel::PRIMARY_KEY}__#{name}", json[index])
|
17
|
+
Store.zadd("#{scope}#{ActivityModel::PRIMARY_KEY}__#{name}", activity['start'], JSON.generate(activity))
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
unless ENV['OPENC3_NO_MIGRATE']
|
27
|
+
OpenC3::ActivityUuid.run
|
28
|
+
end
|