openc3 5.17.1 → 5.19.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (128) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +7 -4
  3. data/bin/cstol_converter +14 -14
  4. data/bin/openc3cli +190 -8
  5. data/data/config/_interfaces.yaml +5 -5
  6. data/data/config/command_modifiers.yaml +59 -0
  7. data/data/config/interface_modifiers.yaml +19 -9
  8. data/data/config/item_modifiers.yaml +34 -26
  9. data/data/config/microservice.yaml +4 -1
  10. data/data/config/param_item_modifiers.yaml +17 -1
  11. data/data/config/parameter_modifiers.yaml +30 -13
  12. data/data/config/plugins.yaml +9 -5
  13. data/data/config/screen.yaml +9 -9
  14. data/data/config/table_manager.yaml +2 -2
  15. data/data/config/telemetry_modifiers.yaml +9 -4
  16. data/data/config/tool.yaml +4 -1
  17. data/data/config/widgets.yaml +44 -17
  18. data/ext/openc3/ext/config_parser/config_parser.c +1 -1
  19. data/ext/openc3/ext/packet/packet.c +7 -1
  20. data/ext/openc3/ext/platform/platform.c +3 -3
  21. data/ext/openc3/ext/structure/structure.c +56 -76
  22. data/lib/openc3/accessors/accessor.rb +1 -0
  23. data/lib/openc3/accessors/binary_accessor.rb +174 -15
  24. data/lib/openc3/accessors/form_accessor.rb +2 -2
  25. data/lib/openc3/accessors/http_accessor.rb +1 -1
  26. data/lib/openc3/accessors/json_accessor.rb +6 -4
  27. data/lib/openc3/accessors/template_accessor.rb +6 -9
  28. data/lib/openc3/accessors/xml_accessor.rb +1 -1
  29. data/lib/openc3/api/cmd_api.rb +72 -44
  30. data/lib/openc3/api/config_api.rb +10 -10
  31. data/lib/openc3/api/interface_api.rb +28 -21
  32. data/lib/openc3/api/limits_api.rb +30 -30
  33. data/lib/openc3/api/metrics_api.rb +3 -3
  34. data/lib/openc3/api/offline_access_api.rb +5 -5
  35. data/lib/openc3/api/router_api.rb +25 -19
  36. data/lib/openc3/api/settings_api.rb +10 -10
  37. data/lib/openc3/api/stash_api.rb +10 -10
  38. data/lib/openc3/api/target_api.rb +10 -10
  39. data/lib/openc3/api/tlm_api.rb +44 -44
  40. data/lib/openc3/config/config_parser.rb +1 -1
  41. data/lib/openc3/conversions/bit_reverse_conversion.rb +60 -0
  42. data/lib/openc3/conversions/ip_read_conversion.rb +59 -0
  43. data/lib/openc3/conversions/ip_write_conversion.rb +61 -0
  44. data/lib/openc3/conversions/object_read_conversion.rb +88 -0
  45. data/lib/openc3/conversions/object_write_conversion.rb +38 -0
  46. data/lib/openc3/conversions/segmented_polynomial_conversion.rb +7 -7
  47. data/lib/openc3/conversions.rb +6 -1
  48. data/lib/openc3/core_ext/array.rb +5 -5
  49. data/lib/openc3/core_ext/exception.rb +9 -2
  50. data/lib/openc3/core_ext/string.rb +2 -2
  51. data/lib/openc3/interfaces/http_server_interface.rb +1 -0
  52. data/lib/openc3/interfaces/interface.rb +1 -1
  53. data/lib/openc3/interfaces/linc_interface.rb +3 -3
  54. data/lib/openc3/io/json_api.rb +11 -6
  55. data/lib/openc3/io/json_drb.rb +19 -21
  56. data/lib/openc3/io/json_rpc.rb +15 -14
  57. data/lib/openc3/logs/buffered_packet_log_writer.rb +3 -3
  58. data/lib/openc3/logs/log_writer.rb +7 -8
  59. data/lib/openc3/logs/packet_log_writer.rb +7 -7
  60. data/lib/openc3/logs/text_log_writer.rb +4 -4
  61. data/lib/openc3/microservices/decom_microservice.rb +19 -4
  62. data/lib/openc3/microservices/interface_microservice.rb +41 -3
  63. data/lib/openc3/microservices/microservice.rb +11 -11
  64. data/lib/openc3/microservices/reaction_microservice.rb +2 -2
  65. data/lib/openc3/microservices/scope_cleanup_microservice.rb +1 -1
  66. data/lib/openc3/microservices/timeline_microservice.rb +70 -45
  67. data/lib/openc3/microservices/trigger_group_microservice.rb +3 -3
  68. data/lib/openc3/migrations/20240915000000_activity_uuid.rb +28 -0
  69. data/lib/openc3/models/activity_model.rb +124 -92
  70. data/lib/openc3/models/auth_model.rb +31 -2
  71. data/lib/openc3/models/cvt_model.rb +11 -5
  72. data/lib/openc3/models/gem_model.rb +8 -8
  73. data/lib/openc3/models/plugin_model.rb +3 -3
  74. data/lib/openc3/models/reducer_model.rb +2 -2
  75. data/lib/openc3/models/scope_model.rb +45 -14
  76. data/lib/openc3/models/sorted_model.rb +5 -5
  77. data/lib/openc3/models/target_model.rb +7 -4
  78. data/lib/openc3/models/tool_config_model.rb +1 -1
  79. data/lib/openc3/models/tool_model.rb +4 -4
  80. data/lib/openc3/models/widget_model.rb +11 -5
  81. data/lib/openc3/operators/microservice_operator.rb +2 -2
  82. data/lib/openc3/operators/operator.rb +14 -12
  83. data/lib/openc3/packets/command_validator.rb +48 -0
  84. data/lib/openc3/packets/commands.rb +6 -14
  85. data/lib/openc3/packets/packet.rb +49 -16
  86. data/lib/openc3/packets/packet_config.rb +47 -25
  87. data/lib/openc3/packets/packet_item.rb +5 -0
  88. data/lib/openc3/packets/parsers/packet_parser.rb +3 -3
  89. data/lib/openc3/packets/structure.rb +87 -15
  90. data/lib/openc3/packets/structure_item.rb +76 -53
  91. data/lib/openc3/packets/telemetry.rb +6 -27
  92. data/lib/openc3/script/api_shared.rb +7 -5
  93. data/lib/openc3/script/calendar.rb +2 -2
  94. data/lib/openc3/script/commands.rb +6 -4
  95. data/lib/openc3/script/extract.rb +5 -3
  96. data/lib/openc3/script/metadata.rb +2 -2
  97. data/lib/openc3/script/suite.rb +17 -17
  98. data/lib/openc3/script/web_socket_api.rb +11 -0
  99. data/lib/openc3/streams/serial_stream.rb +2 -3
  100. data/lib/openc3/streams/stream.rb +2 -2
  101. data/lib/openc3/tools/cmd_tlm_server/interface_thread.rb +10 -10
  102. data/lib/openc3/tools/table_manager/table_manager_core.rb +11 -11
  103. data/lib/openc3/tools/table_manager/table_parser.rb +2 -3
  104. data/lib/openc3/topics/command_decom_topic.rb +2 -1
  105. data/lib/openc3/topics/command_topic.rb +3 -3
  106. data/lib/openc3/topics/decom_interface_topic.rb +4 -3
  107. data/lib/openc3/topics/system_events_topic.rb +40 -0
  108. data/lib/openc3/topics/telemetry_decom_topic.rb +1 -1
  109. data/lib/openc3/utilities/authentication.rb +2 -1
  110. data/lib/openc3/utilities/authorization.rb +4 -3
  111. data/lib/openc3/utilities/cli_generator.rb +15 -8
  112. data/lib/openc3/utilities/cosmos_rails_formatter.rb +60 -0
  113. data/lib/openc3/utilities/crc.rb +6 -6
  114. data/lib/openc3/utilities/local_mode.rb +2 -1
  115. data/lib/openc3/utilities/logger.rb +44 -34
  116. data/lib/openc3/utilities/metric.rb +1 -2
  117. data/lib/openc3/utilities/quaternion.rb +18 -18
  118. data/lib/openc3/utilities/target_file.rb +4 -4
  119. data/lib/openc3/version.rb +6 -6
  120. data/lib/openc3/win32/win32_main.rb +2 -2
  121. data/templates/tool_angular/package.json +22 -22
  122. data/templates/tool_react/package.json +13 -13
  123. data/templates/tool_svelte/package.json +14 -14
  124. data/templates/tool_svelte/src/services/openc3-api.js +17 -17
  125. data/templates/tool_vue/package.json +13 -13
  126. data/templates/widget/package.json +11 -12
  127. data/templates/widget/src/Widget.vue +0 -1
  128. 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 elasped
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 => err
279
- Logger.error "Error starting new log file: #{err.formatted}"
277
+ rescue => e
278
+ Logger.error "Error starting new log file: #{e.formatted}"
280
279
  @logging_enabled = false
281
- OpenC3.handle_critical_exception(err)
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 => err
334
- Logger.error "Error closing #{@filename} : #{err.formatted}"
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 elasped
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 => err
116
- Logger.instance.error "Error writing #{@filename} : #{err.formatted}"
117
- OpenC3.handle_critical_exception(err)
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 => err
137
- Logger.error "Error starting new log file: #{err.formatted}"
136
+ rescue => e
137
+ Logger.error "Error starting new log file: #{e.formatted}"
138
138
  @logging_enabled = false
139
- OpenC3.handle_critical_exception(err)
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 elasped
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 => err
52
- Logger.instance.error "Error writing #{@filename} : #{err.formatted}"
53
- OpenC3.handle_critical_exception(err)
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 2022, OpenC3, Inc.
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
- packet.process # Run processors
106
- packet.check_limits(System.limits_set) # Process all the limits and call the limits_change_callback (as necessary)
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.formatted
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#{System.commands.format(command)}" if hazardous
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
- CommandTopic.write_packet(command, scope: @scope)
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 2023, OpenC3, Inc.
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 => err
58
- if SystemExit === err or SignalException === err
57
+ rescue Exception => e
58
+ if SystemExit === e or SignalException === e
59
59
  microservice.state = 'KILLED'
60
60
  else
61
- microservice.error = err
61
+ microservice.error = e
62
62
  microservice.state = 'DIED_ERROR'
63
- Logger.fatal("Microservice #{name} dying from exception\n#{err.formatted}")
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.info("Ruby microservice #{@name} file #{ruby_filename} passed syntax check\n", scope: @scope)
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 => err
192
- @logger.error "#{@name} status thread died: #{err.formatted}"
193
- raise err
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.info("Shutting down microservice complete: #{@name}")
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, redis)
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 evalute triggers when the snooze is complete.
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 |i|
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, scope: @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
- username = activity.data['username']
89
- token = get_token(username)
90
- raise "No token available for username: #{username}" unless token
91
- cmd_no_hazardous_check(activity.data['command'], scope: @scope, token: token)
92
- activity.commit(status: 'completed', fulfillment: true)
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} run_cmd failed > #{activity.as_json(:allow_nan => true)}, #{e.formatted}"
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
- username = activity.data['username']
103
- token = get_token(username)
104
- raise "No token available for username: #{username}" unless token
105
- request = Net::HTTP::Post.new(
106
- "/script-api/scripts/#{activity.data['script']}/run?scope=#{@scope}",
107
- 'Content-Type' => 'application/json',
108
- 'Authorization' => token
109
- )
110
- request.body = JSON.generate({
111
- 'scope' => @scope,
112
- 'environment' => activity.data['environment'],
113
- 'timeline' => @timeline_name,
114
- 'id' => activity.start
115
- })
116
- hostname = ENV['OPENC3_SCRIPT_HOSTNAME'] || 'openc3-cosmos-script-runner-api'
117
- response = Net::HTTP.new(hostname, 2902).request(request)
118
- raise "failed to call #{hostname}, for script: #{activity.data['script']}, response code: #{response.code}" if response.code != '200'
119
-
120
- activity.commit(status: 'completed', message: "#{activity.data['script']} => #{response.body}", fulfillment: true)
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).to_s}, #{e.message}"
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.to_i
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} timeine manager exiting"
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.to_i
239
- @expire = now + 3_000
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} timeine running"
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} timeine exitting"
324
+ @logger.info "#{@name} timeline exiting"
303
325
  end
304
326
 
305
327
  def topic_lookup_functions
306
328
  {
307
329
  'timeline' => {
308
- 'created' => :timeline_nop,
330
+ 'created' => :timeline_noop,
309
331
  'refresh' => :schedule_refresh,
310
- 'updated' => :timeline_nop,
311
- 'deleted' => :timeline_nop
332
+ 'updated' => :timeline_noop,
333
+ 'deleted' => :timeline_noop
312
334
  },
313
335
  'activity' => {
314
- 'event' => :timeline_nop,
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 timeline_nop(data)
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.to_i
351
- return unless (2..3600).include? diff
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.to_i
361
- return unless (2..3600).include? diff
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 posible as on create it validates that the dependents are already created
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 telemtry decom topic. It adds the "packet" to the thread pool queue
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() # Everytime we do a full base update we refesh the manager
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