openc3 7.2.0 → 7.3.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 +108 -29
- data/bin/pipinstall +5 -4
- data/bin/pipuninstall +5 -1
- data/bin/uvinstall +149 -0
- data/data/config/interface_modifiers.yaml +15 -0
- data/data/config/item_modifiers.yaml +2 -1
- data/data/config/microservice.yaml +5 -1
- data/data/config/screen.yaml +2 -1
- data/data/config/target.yaml +38 -0
- data/data/config/tool.yaml +3 -1
- data/data/config/widgets.yaml +43 -3
- data/data/xtce_schemas/SpaceSystem_20180204.xsd +5918 -0
- data/data/xtce_schemas/xml.xsd +116 -0
- data/ext/openc3/ext/telemetry/telemetry.c +1 -1
- data/lib/openc3/accessors/binary_accessor.rb +4 -0
- data/lib/openc3/accessors/template_accessor.rb +2 -2
- data/lib/openc3/api/cmd_api.rb +5 -1
- data/lib/openc3/api/interface_api.rb +2 -2
- data/lib/openc3/api/limits_api.rb +89 -3
- data/lib/openc3/api/router_api.rb +3 -3
- data/lib/openc3/api/tlm_api.rb +1 -1
- data/lib/openc3/interfaces/http_client_interface.rb +1 -1
- data/lib/openc3/io/json_drb.rb +24 -8
- data/lib/openc3/io/json_rpc.rb +0 -2
- data/lib/openc3/logs/log_writer.rb +43 -7
- data/lib/openc3/logs/packet_log_writer.rb +1 -2
- data/lib/openc3/microservices/decom_common.rb +15 -4
- data/lib/openc3/microservices/decom_microservice.rb +6 -1
- data/lib/openc3/microservices/interface_microservice.rb +26 -12
- data/lib/openc3/microservices/queue_microservice.rb +4 -2
- data/lib/openc3/migrations/20260701000000_updated_at_to_int.rb +55 -0
- data/lib/openc3/migrations/20260731000000_system_health_json.rb +41 -0
- data/lib/openc3/models/cvt_model.rb +7 -7
- data/lib/openc3/models/db_sharded_model.rb +2 -2
- data/lib/openc3/models/gem_model.rb +1 -1
- data/lib/openc3/models/interface_model.rb +17 -11
- data/lib/openc3/models/interface_status_model.rb +2 -2
- data/lib/openc3/models/metric_model.rb +2 -2
- data/lib/openc3/models/microservice_model.rb +21 -0
- data/lib/openc3/models/microservice_status_model.rb +2 -2
- data/lib/openc3/models/model.rb +2 -6
- data/lib/openc3/models/plugin_model.rb +204 -49
- data/lib/openc3/models/python_package_model.rb +210 -12
- data/lib/openc3/models/reingest_job_model.rb +1 -1
- data/lib/openc3/models/scope_model.rb +34 -30
- data/lib/openc3/models/script_status_model.rb +73 -38
- data/lib/openc3/models/target_model.rb +193 -6
- data/lib/openc3/models/tool_model.rb +33 -20
- data/lib/openc3/operators/microservice_operator.rb +33 -4
- data/lib/openc3/operators/operator.rb +15 -5
- data/lib/openc3/packets/commands.rb +7 -7
- data/lib/openc3/packets/limits.rb +33 -2
- data/lib/openc3/packets/packet.rb +6 -4
- data/lib/openc3/packets/packet_config.rb +5 -1
- data/lib/openc3/packets/parsers/state_parser.rb +1 -1
- data/lib/openc3/packets/parsers/xtce_converter.rb +183 -103
- data/lib/openc3/packets/parsers/xtce_parser.rb +71 -23
- data/lib/openc3/packets/structure.rb +16 -12
- data/lib/openc3/packets/telemetry.rb +3 -3
- data/lib/openc3/script/commands.rb +1 -1
- data/lib/openc3/script/extract.rb +20 -25
- data/lib/openc3/script/limits.rb +1 -1
- data/lib/openc3/script/suite_runner.rb +43 -9
- data/lib/openc3/script/web_socket_api.rb +45 -5
- data/lib/openc3/system/target.rb +4 -1
- data/lib/openc3/tools/table_manager/table_config.rb +4 -1
- data/lib/openc3/top_level.rb +1 -1
- data/lib/openc3/topics/command_decom_topic.rb +16 -1
- data/lib/openc3/topics/limits_event_topic.rb +63 -4
- data/lib/openc3/topics/telemetry_decom_topic.rb +2 -2
- data/lib/openc3/utilities/aws_bucket.rb +10 -2
- data/lib/openc3/utilities/cli_generator.rb +5 -1
- data/lib/openc3/utilities/csv.rb +5 -5
- data/lib/openc3/utilities/env_helper.rb +13 -1
- data/lib/openc3/utilities/local_mode.rb +4 -1
- data/lib/openc3/utilities/pypi_url.rb +39 -0
- data/lib/openc3/utilities/reingest_job.rb +1 -1
- data/lib/openc3/utilities/ruby_lex_utils.rb +5 -1
- data/lib/openc3/utilities/running_script.rb +160 -100
- data/lib/openc3/utilities/script.rb +60 -13
- data/lib/openc3/utilities/store_autoload.rb +39 -1
- data/lib/openc3/utilities/store_queued.rb +5 -1
- data/lib/openc3/version.rb +5 -5
- data/lib/openc3.rb +6 -1
- data/templates/plugin/plugin.gemspec +1 -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 -3
- data/templates/widget/package.json +2 -2
- metadata +7 -3
- data/templates/tool_vue/.nycrc +0 -3
- data/templates/widget/.nycrc +0 -3
|
@@ -195,7 +195,7 @@ module OpenC3
|
|
|
195
195
|
msg_hash = critical_model.cmd_hash
|
|
196
196
|
release_critical = true
|
|
197
197
|
else
|
|
198
|
-
next "Critical command #{msg_hash['release_critical']} not found"
|
|
198
|
+
next "Critical command '#{msg_hash['release_critical']}' not found (interface command processing)"
|
|
199
199
|
end
|
|
200
200
|
end
|
|
201
201
|
if msg_hash.key?('target_control')
|
|
@@ -272,6 +272,11 @@ module OpenC3
|
|
|
272
272
|
else
|
|
273
273
|
next nil # Don't ack disabled targets
|
|
274
274
|
end
|
|
275
|
+
rescue RangeError => e
|
|
276
|
+
# Command parameter out of range is a user error, not a bug,
|
|
277
|
+
# so only log the message and not the full stack trace
|
|
278
|
+
@logger.error "#{@interface.name}: #{e.message}"
|
|
279
|
+
next e.message
|
|
275
280
|
rescue => e
|
|
276
281
|
@logger.error "#{@interface.name}: #{msg_hash}"
|
|
277
282
|
@logger.error "#{@interface.name}: #{e.formatted}"
|
|
@@ -281,6 +286,9 @@ module OpenC3
|
|
|
281
286
|
command.extra ||= {}
|
|
282
287
|
command.extra['cmd_string'] = msg_hash['cmd_string']
|
|
283
288
|
command.extra['username'] = msg_hash['username']
|
|
289
|
+
command.extra['interface_name'] = @interface.name
|
|
290
|
+
# Original author of a queued command (set when released from a queue)
|
|
291
|
+
command.extra['queue_username'] = msg_hash['queue_username'] if msg_hash['queue_username']
|
|
284
292
|
# Add approver info if this was a critical command that was approved
|
|
285
293
|
if critical_model
|
|
286
294
|
command.extra['approver'] = critical_model.approver
|
|
@@ -706,16 +714,19 @@ module OpenC3
|
|
|
706
714
|
# Try to do clean disconnect because we're going down
|
|
707
715
|
disconnect(false)
|
|
708
716
|
end
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
717
|
+
unless @cancel_thread
|
|
718
|
+
if @interface_or_router == 'INTERFACE'
|
|
719
|
+
InterfaceStatusModel.set(@interface.as_json(), queued: true, scope: @scope)
|
|
720
|
+
else
|
|
721
|
+
RouterStatusModel.set(@interface.as_json(), queued: true, scope: @scope)
|
|
722
|
+
end
|
|
713
723
|
end
|
|
714
724
|
@logger.info "#{@interface.name}: Stopped packet reading"
|
|
715
725
|
end
|
|
716
726
|
|
|
717
727
|
def handle_packet(packet)
|
|
718
|
-
|
|
728
|
+
# Skip status update if stop() has been called to avoid re-creating the status model
|
|
729
|
+
InterfaceStatusModel.set(@interface.as_json(), queued: true, scope: @scope) unless @cancel_thread
|
|
719
730
|
packet.received_time = Time.now.sys unless packet.received_time
|
|
720
731
|
|
|
721
732
|
if packet.stored
|
|
@@ -853,7 +864,8 @@ module OpenC3
|
|
|
853
864
|
|
|
854
865
|
# If the interface is set to auto_reconnect then delay so the thread
|
|
855
866
|
# can come back around and allow the interface a chance to reconnect.
|
|
856
|
-
if
|
|
867
|
+
# Skip reconnect if stop() has been called to avoid re-creating the status model
|
|
868
|
+
if allow_reconnect and @interface.auto_reconnect and @interface.state != 'DISCONNECTED' and !@cancel_thread
|
|
857
869
|
attempting()
|
|
858
870
|
if !@cancel_thread
|
|
859
871
|
# @logger.debug "reconnect delay: #{@interface.reconnect_delay}"
|
|
@@ -861,10 +873,12 @@ module OpenC3
|
|
|
861
873
|
end
|
|
862
874
|
else
|
|
863
875
|
@interface.state = 'DISCONNECTED'
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
876
|
+
unless @cancel_thread
|
|
877
|
+
if @interface_or_router == 'INTERFACE'
|
|
878
|
+
InterfaceStatusModel.set(@interface.as_json(), queued: true, scope: @scope)
|
|
879
|
+
else
|
|
880
|
+
RouterStatusModel.set(@interface.as_json(), queued: true, scope: @scope)
|
|
881
|
+
end
|
|
868
882
|
end
|
|
869
883
|
end
|
|
870
884
|
end
|
|
@@ -914,4 +928,4 @@ if __FILE__ == $0
|
|
|
914
928
|
OpenC3::InterfaceMicroservice.run
|
|
915
929
|
OpenC3::ThreadManager.instance.shutdown
|
|
916
930
|
OpenC3::ThreadManager.instance.join
|
|
917
|
-
end
|
|
931
|
+
end
|
|
@@ -76,12 +76,14 @@ module OpenC3
|
|
|
76
76
|
end
|
|
77
77
|
validate = command.key?('validate') ? command['validate'] : true
|
|
78
78
|
timeout = command['timeout']
|
|
79
|
-
|
|
79
|
+
# Pass queue_username so Command History is attributed to the original
|
|
80
|
+
# author rather than the queue microservice name
|
|
81
|
+
cmd(command['target_name'], command['cmd_name'], cmd_params, queue: false, validate: validate, timeout: timeout, queue_username: command['username'], scope: @scope)
|
|
80
82
|
elsif command['value']
|
|
81
83
|
# Legacy format: use single string parameter for backwards compatibility
|
|
82
84
|
validate = command.key?('validate') ? command['validate'] : true
|
|
83
85
|
timeout = command['timeout']
|
|
84
|
-
cmd(command['value'], queue: false, validate: validate, timeout: timeout, scope: @scope)
|
|
86
|
+
cmd(command['value'], queue: false, validate: validate, timeout: timeout, queue_username: command['username'], scope: @scope)
|
|
85
87
|
else
|
|
86
88
|
@logger.error "QueueProcessor: Invalid command format, missing required fields"
|
|
87
89
|
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# encoding: ascii-8bit
|
|
2
|
+
|
|
3
|
+
# Copyright 2026 OpenC3, Inc.
|
|
4
|
+
# All Rights Reserved.
|
|
5
|
+
#
|
|
6
|
+
# This program is distributed in the hope that it will be useful,
|
|
7
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
8
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
9
|
+
# See LICENSE.md for more details.
|
|
10
|
+
#
|
|
11
|
+
# This file may also be used under the terms of a commercial license
|
|
12
|
+
# if purchased from OpenC3, Inc.
|
|
13
|
+
|
|
14
|
+
require 'time'
|
|
15
|
+
require 'openc3/utilities/migration'
|
|
16
|
+
require 'openc3/core_ext/time'
|
|
17
|
+
require 'openc3/models/scope_model'
|
|
18
|
+
require 'openc3/models/script_status_model'
|
|
19
|
+
|
|
20
|
+
module OpenC3
|
|
21
|
+
# Earlier versions of the Python script runner wrote the ScriptStatusModel
|
|
22
|
+
# updated_at field as an ISO8601 string (e.g. "2026-06-30T12:34:56.789012Z")
|
|
23
|
+
# instead of integer nanoseconds since the epoch. This migration rewrites any
|
|
24
|
+
# such string values as integers so updated_at is consistently an int.
|
|
25
|
+
#
|
|
26
|
+
# NOTE: The stored JSON is edited in place rather than reloaded through the
|
|
27
|
+
# model and re-created, because ScriptStatusModel#create always sets
|
|
28
|
+
# updated_at to the current time, which would discard the original timestamp.
|
|
29
|
+
class UpdatedAtToInt < Migration
|
|
30
|
+
def self.run
|
|
31
|
+
ScopeModel.names.each do |scope|
|
|
32
|
+
[ScriptStatusModel::RUNNING_PRIMARY_KEY, ScriptStatusModel::COMPLETED_PRIMARY_KEY].each do |key|
|
|
33
|
+
primary_key = "#{key}__#{scope}"
|
|
34
|
+
Store.hgetall(primary_key).each do |name, json|
|
|
35
|
+
data = JSON.parse(json, allow_nan: true)
|
|
36
|
+
updated_at = data['updated_at']
|
|
37
|
+
next unless updated_at.is_a?(String)
|
|
38
|
+
|
|
39
|
+
begin
|
|
40
|
+
data['updated_at'] = Time.parse(updated_at).to_nsec_from_epoch
|
|
41
|
+
rescue ArgumentError
|
|
42
|
+
# Leave unparsable values untouched
|
|
43
|
+
next
|
|
44
|
+
end
|
|
45
|
+
Store.hset(primary_key, name, JSON.generate(data, allow_nan: true))
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
unless ENV['OPENC3_NO_MIGRATE']
|
|
54
|
+
OpenC3::UpdatedAtToInt.run
|
|
55
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
require 'json'
|
|
2
|
+
require 'openc3/utilities/local_mode'
|
|
3
|
+
require 'openc3/utilities/migration'
|
|
4
|
+
require 'openc3/models/scope_model'
|
|
5
|
+
require 'openc3/models/setting_model'
|
|
6
|
+
|
|
7
|
+
module OpenC3
|
|
8
|
+
# The system_health setting was originally stored as a Ruby Hash. Local mode
|
|
9
|
+
# writes settings to disk with File.write and reads them back as a String, so
|
|
10
|
+
# the Hash round tripped into a Ruby inspect String which nothing can parse.
|
|
11
|
+
# Store it as a JSON string like every other setting and repair existing data.
|
|
12
|
+
class SystemHealthJson < Migration
|
|
13
|
+
def self.run
|
|
14
|
+
setting = SettingModel.get(name: 'system_health')
|
|
15
|
+
data = repair(setting && setting['data'])
|
|
16
|
+
SettingModel.set({ name: 'system_health', data: JSON.generate(data) }, scope: 'DEFAULT')
|
|
17
|
+
# Repair the local mode file as well or sync_settings puts the bad data back
|
|
18
|
+
LocalMode.save_setting('DEFAULT', 'system_health', JSON.generate(data))
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# @return [Hash] the existing settings if they can be recovered, otherwise defaults
|
|
22
|
+
def self.repair(data)
|
|
23
|
+
return ScopeModel::SYSTEM_HEALTH_DEFAULTS if data.nil?
|
|
24
|
+
return data if Hash === data
|
|
25
|
+
return ScopeModel::SYSTEM_HEALTH_DEFAULTS unless String === data
|
|
26
|
+
begin
|
|
27
|
+
parsed = JSON.parse(data)
|
|
28
|
+
rescue JSON::ParserError
|
|
29
|
+
# Ruby inspect format, e.g. {"cpu" => {"redThreshold" => 90, ... => nil}}
|
|
30
|
+
# Ruby 3.3 and earlier had no spaces around the rocket. All the values are
|
|
31
|
+
# numbers, booleans, or nil so there's nothing to accidentally rewrite.
|
|
32
|
+
parsed = JSON.parse(data.gsub(/\s*=>\s*/, ': ').gsub(/\bnil\b/, 'null')) rescue nil
|
|
33
|
+
end
|
|
34
|
+
Hash === parsed ? parsed : ScopeModel::SYSTEM_HEALTH_DEFAULTS
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
unless ENV['OPENC3_NO_MIGRATE']
|
|
40
|
+
OpenC3::SystemHealthJson.run
|
|
41
|
+
end
|
|
@@ -26,8 +26,8 @@ module OpenC3
|
|
|
26
26
|
@@override_cache = {}
|
|
27
27
|
|
|
28
28
|
VALUE_TYPES = [:RAW, :CONVERTED, :FORMATTED]
|
|
29
|
-
def self.build_json_from_packet(packet)
|
|
30
|
-
packet.decom
|
|
29
|
+
def self.build_json_from_packet(packet, include_limits_states: true)
|
|
30
|
+
packet.decom(include_limits_states: include_limits_states)
|
|
31
31
|
end
|
|
32
32
|
|
|
33
33
|
# Get a Store instance routed to the correct db_shard for a target
|
|
@@ -86,7 +86,7 @@ module OpenC3
|
|
|
86
86
|
return hash if hash and (now - cache_time) < cache_timeout
|
|
87
87
|
end
|
|
88
88
|
packet = store_for_target(target_name, scope: scope).hget(key, packet_name)
|
|
89
|
-
raise "Packet '#{target_name} #{packet_name}'
|
|
89
|
+
raise "Packet '#{target_name} #{packet_name}' has no current values in CVT" unless packet
|
|
90
90
|
hash = JSON.parse(packet, allow_nan: true, create_additions: true)
|
|
91
91
|
@@packet_cache[tgt_pkt_key] = [now, hash]
|
|
92
92
|
hash
|
|
@@ -107,7 +107,7 @@ module OpenC3
|
|
|
107
107
|
hash["#{item_name}__C"] = value
|
|
108
108
|
hash[item_name] = value
|
|
109
109
|
else
|
|
110
|
-
raise "Unknown type '#{type}' for #{target_name} #{packet_name} #{item_name}"
|
|
110
|
+
raise "Unknown type '#{type}' for #{target_name} #{packet_name} #{item_name} (set_item)"
|
|
111
111
|
end
|
|
112
112
|
set(hash, target_name: target_name, packet_name: packet_name, queued: queued, scope: scope)
|
|
113
113
|
end
|
|
@@ -252,7 +252,7 @@ module OpenC3
|
|
|
252
252
|
when :FORMATTED, :WITH_UNITS
|
|
253
253
|
hash["#{item_name}__F"] = value.to_s # Always a String
|
|
254
254
|
else
|
|
255
|
-
raise "Unknown type '#{type}' for #{target_name} #{packet_name} #{item_name}"
|
|
255
|
+
raise "Unknown type '#{type}' for #{target_name} #{packet_name} #{item_name} (override)"
|
|
256
256
|
end
|
|
257
257
|
|
|
258
258
|
tgt_pkt_key = "#{scope}__tlm__#{target_name}__#{packet_name}"
|
|
@@ -277,7 +277,7 @@ module OpenC3
|
|
|
277
277
|
when :FORMATTED, :WITH_UNITS
|
|
278
278
|
hash.delete("#{item_name}__F")
|
|
279
279
|
else
|
|
280
|
-
raise "Unknown type '#{type}' for #{target_name} #{packet_name} #{item_name}"
|
|
280
|
+
raise "Unknown type '#{type}' for #{target_name} #{packet_name} #{item_name} (normalize)"
|
|
281
281
|
end
|
|
282
282
|
|
|
283
283
|
tgt_pkt_key = "#{scope}__tlm__#{target_name}__#{packet_name}"
|
|
@@ -327,7 +327,7 @@ module OpenC3
|
|
|
327
327
|
when :RAW
|
|
328
328
|
types = [item_name]
|
|
329
329
|
else
|
|
330
|
-
raise "Unknown type '#{type}' for #{target_name} #{packet_name} #{item_name}"
|
|
330
|
+
raise "Unknown type '#{type}' for #{target_name} #{packet_name} #{item_name} (get_item)"
|
|
331
331
|
end
|
|
332
332
|
|
|
333
333
|
tgt_pkt_key = "#{scope}__tlm__#{target_name}__#{packet_name}"
|
|
@@ -79,7 +79,7 @@ module OpenC3
|
|
|
79
79
|
end
|
|
80
80
|
|
|
81
81
|
# DB_Shard-aware create: writes to the store instance for the given db_shard.
|
|
82
|
-
def _db_sharded_create(db_shard, update: false, force: false, queued: false,
|
|
82
|
+
def _db_sharded_create(db_shard, update: false, force: false, queued: false, expire_seconds: nil)
|
|
83
83
|
db_shard_store = self.class.store.instance(db_shard: db_shard)
|
|
84
84
|
unless force
|
|
85
85
|
existing = db_shard_store.hget(@primary_key, @name)
|
|
@@ -89,7 +89,7 @@ module OpenC3
|
|
|
89
89
|
raise RuntimeError.new("#{@primary_key}:#{@name} doesn't exist at update") if update
|
|
90
90
|
end
|
|
91
91
|
end
|
|
92
|
-
@updated_at =
|
|
92
|
+
@updated_at = Time.now.utc.to_nsec_from_epoch
|
|
93
93
|
|
|
94
94
|
if queued
|
|
95
95
|
store = self.class.store_queued.instance(db_shard: db_shard)
|
|
@@ -47,7 +47,7 @@ module OpenC3
|
|
|
47
47
|
return path if File.exist?(path)
|
|
48
48
|
path = "#{ENV['GEM_HOME']}/cache/#{name}"
|
|
49
49
|
return path if File.exist?(path)
|
|
50
|
-
raise "Gem #{name} not found"
|
|
50
|
+
raise "Gem '#{name}' not found"
|
|
51
51
|
end
|
|
52
52
|
|
|
53
53
|
def self.put(gem_file_path, gem_install: true, scope:)
|
|
@@ -256,7 +256,7 @@ module OpenC3
|
|
|
256
256
|
|
|
257
257
|
def ensure_target_exists(target_name)
|
|
258
258
|
target = TargetModel.get(name: target_name, scope: @scope)
|
|
259
|
-
raise "Target #{target_name} does not exist" unless target
|
|
259
|
+
raise "Target '#{target_name}' does not exist (InterfaceModel)" unless target
|
|
260
260
|
target
|
|
261
261
|
end
|
|
262
262
|
|
|
@@ -444,9 +444,11 @@ module OpenC3
|
|
|
444
444
|
microservice
|
|
445
445
|
end
|
|
446
446
|
|
|
447
|
-
#
|
|
448
|
-
#
|
|
449
|
-
#
|
|
447
|
+
# Destroys the deployed MicroserviceModel which triggers the operator to kill
|
|
448
|
+
# the microservice. Also directly destroys the Interface/RouterStatusModel in
|
|
449
|
+
# an ensure block. The microservice stop() no longer recreates the status
|
|
450
|
+
# model once cancel_thread is set, so undeploy must clean it up itself to
|
|
451
|
+
# avoid leaving an orphaned status model behind.
|
|
450
452
|
def undeploy
|
|
451
453
|
type = self.class._get_type
|
|
452
454
|
name = "#{@scope}__#{type}__#{@name}"
|
|
@@ -455,15 +457,19 @@ module OpenC3
|
|
|
455
457
|
model.destroy
|
|
456
458
|
ConfigTopic.write({ kind: 'deleted', type: type.downcase, name: @name, plugin: @plugin }, scope: @scope)
|
|
457
459
|
end
|
|
458
|
-
|
|
459
|
-
if type == 'INTERFACE'
|
|
460
|
-
status_model = InterfaceStatusModel.get_model(name: @name, scope: @scope)
|
|
461
|
-
else
|
|
462
|
-
status_model = RouterStatusModel.get_model(name: @name, scope: @scope)
|
|
463
|
-
end
|
|
464
|
-
status_model.destroy if status_model
|
|
465
460
|
rescue Exception => error
|
|
466
461
|
Logger.error("Error undeploying interface/router model #{@name} in scope #{@scope} due to #{error}")
|
|
462
|
+
ensure
|
|
463
|
+
begin
|
|
464
|
+
if type == 'INTERFACE'
|
|
465
|
+
status_model = InterfaceStatusModel.get_model(name: @name, scope: @scope)
|
|
466
|
+
else
|
|
467
|
+
status_model = RouterStatusModel.get_model(name: @name, scope: @scope)
|
|
468
|
+
end
|
|
469
|
+
status_model.destroy if status_model
|
|
470
|
+
rescue Exception => error
|
|
471
|
+
Logger.error("Error destroying #{type&.downcase || 'unknown'} status model #{@name} in scope #{@scope} due to #{error}")
|
|
472
|
+
end
|
|
467
473
|
end
|
|
468
474
|
|
|
469
475
|
def unmap_target(target_name, cmd_only: false, tlm_only: false)
|
|
@@ -118,8 +118,8 @@ module OpenC3
|
|
|
118
118
|
@rxcnt = rxcnt
|
|
119
119
|
end
|
|
120
120
|
|
|
121
|
-
def create(update: false, force: false, queued: false
|
|
122
|
-
_db_sharded_create(self.class._db_shard_for_name(@name, scope: @scope, use_cache: true), update: update, force: force, queued: queued
|
|
121
|
+
def create(update: false, force: false, queued: false)
|
|
122
|
+
_db_sharded_create(self.class._db_shard_for_name(@name, scope: @scope, use_cache: true), update: update, force: force, queued: queued)
|
|
123
123
|
end
|
|
124
124
|
|
|
125
125
|
def destroy
|
|
@@ -77,8 +77,8 @@ module OpenC3
|
|
|
77
77
|
@db_shard = db_shard.to_i
|
|
78
78
|
end
|
|
79
79
|
|
|
80
|
-
def create(update: false, force: false, queued: false,
|
|
81
|
-
_db_sharded_create(@db_shard, update: update, force: force, queued: queued,
|
|
80
|
+
def create(update: false, force: false, queued: false, expire_seconds: nil)
|
|
81
|
+
_db_sharded_create(@db_shard, update: update, force: force, queued: queued, expire_seconds: expire_seconds)
|
|
82
82
|
end
|
|
83
83
|
|
|
84
84
|
def destroy
|
|
@@ -140,6 +140,27 @@ module OpenC3
|
|
|
140
140
|
@bucket = Bucket.getClient()
|
|
141
141
|
end
|
|
142
142
|
|
|
143
|
+
# Resolve the Python environment variables that will be used at runtime.
|
|
144
|
+
# This mirrors the logic in MicroserviceOperator#convert_microservice_to_process_definition.
|
|
145
|
+
def runtime_python_env
|
|
146
|
+
python_bin = ENV['OPENC3_PYTHON_BIN'] || '/openc3/python/.venv/bin/python'
|
|
147
|
+
result = { 'OPENC3_PYTHON_BIN' => python_bin }
|
|
148
|
+
if @needs_dependencies && @plugin
|
|
149
|
+
sanitized_name = "#{@scope}__#{@plugin}".tr('^a-zA-Z0-9_-', '_')
|
|
150
|
+
candidate = "/gems/plugin_venvs/#{sanitized_name}/.venv"
|
|
151
|
+
if File.directory?(candidate)
|
|
152
|
+
result['VIRTUAL_ENV'] = candidate
|
|
153
|
+
result['PYTHONUSERBASE'] = candidate
|
|
154
|
+
site_packages = Dir.glob("#{candidate}/lib/python*/site-packages").first
|
|
155
|
+
result['PYTHONPATH'] = site_packages
|
|
156
|
+
else
|
|
157
|
+
result['VIRTUAL_ENV'] = nil
|
|
158
|
+
result['PYTHONUSERBASE'] = '/gems/python_packages'
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
result
|
|
162
|
+
end
|
|
163
|
+
|
|
143
164
|
def as_json(*a)
|
|
144
165
|
{
|
|
145
166
|
'name' => @name,
|
|
@@ -76,8 +76,8 @@ module OpenC3
|
|
|
76
76
|
@custom = custom
|
|
77
77
|
end
|
|
78
78
|
|
|
79
|
-
def create(update: false, force: false, queued: false
|
|
80
|
-
_db_sharded_create(self.class._db_shard_for_name(@name, scope: @scope, use_cache: true), update: update, force: force, queued: queued
|
|
79
|
+
def create(update: false, force: false, queued: false)
|
|
80
|
+
_db_sharded_create(self.class._db_shard_for_name(@name, scope: @scope, use_cache: true), update: update, force: force, queued: queued)
|
|
81
81
|
end
|
|
82
82
|
|
|
83
83
|
def destroy
|
data/lib/openc3/models/model.rb
CHANGED
|
@@ -134,7 +134,7 @@ module OpenC3
|
|
|
134
134
|
|
|
135
135
|
# Update the Redis hash at primary_key and set the field "name"
|
|
136
136
|
# to the JSON generated via calling as_json
|
|
137
|
-
def create(update: false, force: false, queued: false
|
|
137
|
+
def create(update: false, force: false, queued: false)
|
|
138
138
|
unless force
|
|
139
139
|
existing = self.class.store.hget(@primary_key, @name)
|
|
140
140
|
if existing
|
|
@@ -143,11 +143,7 @@ module OpenC3
|
|
|
143
143
|
raise "#{@primary_key}:#{@name} doesn't exist at update" if update
|
|
144
144
|
end
|
|
145
145
|
end
|
|
146
|
-
|
|
147
|
-
@updated_at = Time.now.utc.iso8601
|
|
148
|
-
else
|
|
149
|
-
@updated_at = Time.now.utc.to_nsec_from_epoch
|
|
150
|
-
end
|
|
146
|
+
@updated_at = Time.now.utc.to_nsec_from_epoch
|
|
151
147
|
|
|
152
148
|
if queued
|
|
153
149
|
write_store = self.class.store_queued
|