openc3 6.2.1 → 6.4.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/Gemfile +2 -3
- data/bin/openc3cli +13 -14
- data/data/config/interface_modifiers.yaml +1 -1
- data/data/config/microservice.yaml +1 -1
- data/data/config/plugins.yaml +1 -1
- data/data/config/tool.yaml +1 -1
- data/data/config/widgets.yaml +3 -0
- data/ext/openc3/ext/burst_protocol/burst_protocol.c +5 -1
- data/lib/openc3/api/api.rb +7 -1
- data/lib/openc3/api/cmd_api.rb +5 -8
- data/lib/openc3/api/interface_api.rb +8 -4
- data/lib/openc3/api/tlm_api.rb +23 -8
- data/lib/openc3/interfaces/file_interface.rb +36 -7
- data/lib/openc3/interfaces/protocols/burst_protocol.rb +3 -3
- data/lib/openc3/interfaces/protocols/preidentified_protocol.rb +20 -26
- data/lib/openc3/interfaces.rb +4 -3
- data/lib/openc3/io/json_api_object.rb +1 -1
- data/lib/openc3/io/json_drb_object.rb +1 -1
- data/lib/openc3/logs/log_writer.rb +11 -8
- data/lib/openc3/logs/packet_log_reader.rb +2 -2
- data/lib/openc3/logs/packet_log_writer.rb +1 -1
- data/lib/openc3/microservices/interface_decom_common.rb +4 -1
- data/lib/openc3/microservices/interface_microservice.rb +69 -6
- data/lib/openc3/microservices/microservice.rb +3 -1
- data/lib/openc3/microservices/multi_microservice.rb +1 -1
- data/lib/openc3/migrations/20250402000000_periodic_only_default.rb +24 -0
- data/lib/openc3/models/model.rb +6 -2
- data/lib/openc3/models/offline_access_model.rb +7 -6
- data/lib/openc3/models/scope_model.rb +5 -2
- data/lib/openc3/models/script_status_model.rb +242 -0
- data/lib/openc3/models/target_model.rb +150 -15
- data/lib/openc3/packets/commands.rb +10 -3
- data/lib/openc3/script/api_shared.rb +4 -0
- data/lib/openc3/script/commands.rb +1 -1
- data/lib/openc3/script/script.rb +14 -0
- data/lib/openc3/script/script_runner.rb +22 -7
- data/lib/openc3/utilities/authentication.rb +6 -6
- data/lib/openc3/utilities/cosmos_rails_formatter.rb +1 -1
- data/lib/openc3/utilities/local_mode.rb +5 -2
- data/lib/openc3/utilities/message_log.rb +2 -0
- data/lib/openc3/utilities/metric.rb +1 -1
- data/lib/openc3/utilities/ruby_lex_utils.rb +114 -279
- data/lib/openc3/utilities/target_file.rb +6 -2
- data/lib/openc3/version.rb +6 -6
- 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/eslint.config.mjs +17 -41
- data/templates/tool_vue/package.json +3 -3
- data/templates/widget/package.json +2 -2
- metadata +21 -23
- data/ext/mkrf_conf.rb +0 -52
@@ -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 2025, OpenC3, Inc.
|
18
18
|
# All Rights Reserved
|
19
19
|
#
|
20
20
|
# This file may also be used under the terms of a commercial license
|
@@ -309,7 +309,7 @@ module OpenC3
|
|
309
309
|
if cbor
|
310
310
|
extra = CBOR.decode(extra_encoded)
|
311
311
|
else
|
312
|
-
extra = JSON.parse(
|
312
|
+
extra = JSON.parse(extra_encoded, allow_nan: true, create_additions: true)
|
313
313
|
end
|
314
314
|
end
|
315
315
|
data = entry[next_offset..-1]
|
@@ -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 2025, OpenC3, Inc.
|
18
18
|
# All Rights Reserved
|
19
19
|
#
|
20
20
|
# This file may also be used under the terms of a commercial license
|
@@ -15,6 +15,9 @@
|
|
15
15
|
#
|
16
16
|
# This file may also be used under the terms of a commercial license
|
17
17
|
# if purchased from OpenC3, Inc.
|
18
|
+
#
|
19
|
+
# A portion of this file was funded by Blue Origin Enterprises, L.P.
|
20
|
+
# See https://github.com/OpenC3/cosmos/pull/1963
|
18
21
|
|
19
22
|
require 'openc3/topics/telemetry_topic'
|
20
23
|
require 'openc3/system/system'
|
@@ -33,8 +36,8 @@ module OpenC3
|
|
33
36
|
packet.write(name.to_s, value, type)
|
34
37
|
end
|
35
38
|
end
|
36
|
-
packet.received_count += 1
|
37
39
|
packet.received_time = Time.now.sys
|
40
|
+
packet.received_count = TargetModel.increment_telemetry_count(packet.target_name, packet.packet_name, 1, scope: @scope)
|
38
41
|
TelemetryTopic.write_packet(packet, scope: @scope)
|
39
42
|
# If the inject_tlm parameters are bad we rescue so
|
40
43
|
# interface_microservice and decom_microservice can continue
|
@@ -19,6 +19,9 @@
|
|
19
19
|
#
|
20
20
|
# This file may also be used under the terms of a commercial license
|
21
21
|
# if purchased from OpenC3, Inc.
|
22
|
+
#
|
23
|
+
# A portion of this file was funded by Blue Origin Enterprises, L.P.
|
24
|
+
# See https://github.com/OpenC3/cosmos/pull/1963
|
22
25
|
|
23
26
|
require 'openc3/microservices/microservice'
|
24
27
|
require 'openc3/microservices/interface_decom_common'
|
@@ -37,7 +40,7 @@ require 'openc3/interfaces/interface'
|
|
37
40
|
begin
|
38
41
|
require 'openc3-enterprise/models/critical_cmd_model'
|
39
42
|
rescue LoadError
|
40
|
-
#
|
43
|
+
# LoadError expected in COSMOS Core
|
41
44
|
end
|
42
45
|
|
43
46
|
module OpenC3
|
@@ -128,7 +131,7 @@ module OpenC3
|
|
128
131
|
@logger.info "#{@interface.name}: Write raw"
|
129
132
|
# A raw interface write results in an UNKNOWN packet
|
130
133
|
command = System.commands.packet('UNKNOWN', 'UNKNOWN')
|
131
|
-
command.received_count
|
134
|
+
command.received_count = TargetModel.increment_command_count('UNKNOWN', 'UNKNOWN', 1, scope: @scope)
|
132
135
|
command = command.clone
|
133
136
|
command.buffer = msg_hash['raw']
|
134
137
|
command.received_time = Time.now
|
@@ -215,14 +218,15 @@ module OpenC3
|
|
215
218
|
command = System.commands.identify(cmd_buffer, @interface.cmd_target_names)
|
216
219
|
end
|
217
220
|
unless command
|
218
|
-
command = System.commands.packet('UNKNOWN', 'UNKNOWN')
|
219
|
-
command.received_count += 1
|
220
|
-
command = command.clone
|
221
|
+
command = System.commands.packet('UNKNOWN', 'UNKNOWN').clone
|
221
222
|
command.buffer = cmd_buffer
|
222
223
|
end
|
223
224
|
else
|
224
225
|
raise "Invalid command received:\n #{msg_hash}"
|
225
226
|
end
|
227
|
+
orig_command = System.commands.packet(command.target_name, command.packet_name)
|
228
|
+
orig_command.received_count = TargetModel.increment_command_count(command.target_name, command.packet_name, 1, scope: @scope)
|
229
|
+
command.received_count = orig_command.received_count
|
226
230
|
command.received_time = Time.now
|
227
231
|
rescue => e
|
228
232
|
@logger.error "#{@interface.name}: #{msg_hash}"
|
@@ -504,6 +508,9 @@ module OpenC3
|
|
504
508
|
end
|
505
509
|
|
506
510
|
@queued = false
|
511
|
+
@sync_packet_count_data = {}
|
512
|
+
@sync_packet_count_time = nil
|
513
|
+
@sync_packet_count_delay_seconds = 1.0 # Sync packet counts every second
|
507
514
|
@interface.options.each do |option_name, option_values|
|
508
515
|
if option_name.upcase == 'OPTIMIZE_THROUGHPUT'
|
509
516
|
@queued = true
|
@@ -511,6 +518,9 @@ module OpenC3
|
|
511
518
|
EphemeralStoreQueued.instance.set_update_interval(update_interval)
|
512
519
|
StoreQueued.instance.set_update_interval(update_interval)
|
513
520
|
end
|
521
|
+
if option_name.upcase == 'SYNC_PACKET_COUNT_DELAY_SECONDS'
|
522
|
+
@sync_packet_count_delay_seconds = option_values[0].to_f
|
523
|
+
end
|
514
524
|
end
|
515
525
|
|
516
526
|
@interface_thread_sleeper = Sleeper.new
|
@@ -678,7 +688,7 @@ module OpenC3
|
|
678
688
|
end
|
679
689
|
|
680
690
|
# Write to stream
|
681
|
-
packet
|
691
|
+
sync_tlm_packet_counts(packet)
|
682
692
|
TelemetryTopic.write_packet(packet, queued: @queued, scope: @scope)
|
683
693
|
end
|
684
694
|
|
@@ -812,6 +822,59 @@ module OpenC3
|
|
812
822
|
def graceful_kill
|
813
823
|
# Just to avoid warning
|
814
824
|
end
|
825
|
+
|
826
|
+
def sync_tlm_packet_counts(packet)
|
827
|
+
if @sync_packet_count_delay_seconds <= 0
|
828
|
+
# Perfect but slow method
|
829
|
+
packet.received_count = TargetModel.increment_telemetry_count(packet.target_name, packet.packet_name, 1, scope: @scope)
|
830
|
+
else
|
831
|
+
# Eventually consistent method
|
832
|
+
# Only sync every period (default 1 second) to avoid hammering Redis
|
833
|
+
# This is a trade off between speed and accuracy
|
834
|
+
# The packet count is eventually consistent
|
835
|
+
@sync_packet_count_data[packet.target_name] ||= {}
|
836
|
+
@sync_packet_count_data[packet.target_name][packet.packet_name] ||= 0
|
837
|
+
@sync_packet_count_data[packet.target_name][packet.packet_name] += 1
|
838
|
+
|
839
|
+
# Ensures counters change between syncs
|
840
|
+
packet.received_count += 1
|
841
|
+
|
842
|
+
# Check if we need to sync the packet counts
|
843
|
+
if @sync_packet_count_time.nil? or (Time.now - @sync_packet_count_time) > @sync_packet_count_delay_seconds
|
844
|
+
@sync_packet_count_time = Time.now
|
845
|
+
|
846
|
+
inc_count = 0
|
847
|
+
# Use pipeline to make this one transaction
|
848
|
+
result = Store.redis_pool.pipelined do
|
849
|
+
# Increment global counters for packets received
|
850
|
+
@sync_packet_count_data.each do |target_name, packet_data|
|
851
|
+
packet_data.each do |packet_name, count|
|
852
|
+
TargetModel.increment_telemetry_count(target_name, packet_name, count, scope: @scope)
|
853
|
+
inc_count += 1
|
854
|
+
end
|
855
|
+
end
|
856
|
+
@sync_packet_count_data = {}
|
857
|
+
|
858
|
+
# Get all the packet counts with the global counters
|
859
|
+
@interface.tlm_target_names.each do |target_name|
|
860
|
+
TargetModel.get_all_telemetry_counts(target_name, scope: @scope)
|
861
|
+
end
|
862
|
+
TargetModel.get_all_telemetry_counts('UNKNOWN', scope: @scope)
|
863
|
+
end
|
864
|
+
@interface.tlm_target_names.each do |target_name|
|
865
|
+
result[inc_count].each do |packet_name, count|
|
866
|
+
update_packet = System.telemetry.packet(target_name, packet_name)
|
867
|
+
update_packet.received_count = count.to_i
|
868
|
+
end
|
869
|
+
inc_count += 1
|
870
|
+
end
|
871
|
+
result[inc_count].each do |packet_name, count|
|
872
|
+
update_packet = System.telemetry.packet('UNKNOWN', packet_name)
|
873
|
+
update_packet.received_count = count.to_i
|
874
|
+
end
|
875
|
+
end
|
876
|
+
end
|
877
|
+
end
|
815
878
|
end
|
816
879
|
end
|
817
880
|
|
@@ -64,6 +64,7 @@ module OpenC3
|
|
64
64
|
microservice.state = 'DIED_ERROR'
|
65
65
|
Logger.fatal("Microservice #{name} dying from exception\n#{e.formatted}")
|
66
66
|
end
|
67
|
+
microservice.shutdown # Dying in crash so should try to shutdown
|
67
68
|
ensure
|
68
69
|
MicroserviceStatusModel.set(microservice.as_json(:allow_nan => true), scope: microservice.scope)
|
69
70
|
end
|
@@ -207,9 +208,10 @@ module OpenC3
|
|
207
208
|
shutdown()
|
208
209
|
end
|
209
210
|
|
210
|
-
def shutdown
|
211
|
+
def shutdown(state = 'STOPPED')
|
211
212
|
return if @shutdown_complete
|
212
213
|
@logger.info("Shutting down microservice: #{@name}")
|
214
|
+
@state = state
|
213
215
|
@cancel_thread = true
|
214
216
|
@microservice_status_sleeper.cancel if @microservice_status_sleeper
|
215
217
|
MicroserviceStatusModel.set(as_json(:allow_nan => true), scope: @scope)
|
@@ -25,7 +25,7 @@ module OpenC3
|
|
25
25
|
def run
|
26
26
|
ARGV.each do |microservice_name|
|
27
27
|
microservice_model = MicroserviceModel.get_model(name: microservice_name, scope: @scope)
|
28
|
-
if microservice_model.enabled
|
28
|
+
if microservice_model and microservice_model.enabled
|
29
29
|
thread = Thread.new do
|
30
30
|
cmd_line = microservice_model.cmd.join(' ')
|
31
31
|
split_cmd_line = cmd_line.split(' ')
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'openc3/utilities/migration'
|
2
|
+
require 'openc3/models/scope_model'
|
3
|
+
require 'openc3/models/microservice_model'
|
4
|
+
|
5
|
+
module OpenC3
|
6
|
+
class PeriodicOnlyDefault < Migration
|
7
|
+
def self.run
|
8
|
+
ScopeModel.get_all_models(scope: nil).each do |scope, scope_model|
|
9
|
+
next if scope == 'DEFAULT'
|
10
|
+
model = MicroserviceModel.get_model(name: "#{scope}__SCOPEMULTI__#{scope}", scope: scope)
|
11
|
+
if model
|
12
|
+
model.cmd.delete("#{scope}__PERIODIC__#{scope}")
|
13
|
+
model.update
|
14
|
+
end
|
15
|
+
model = MicroserviceModel.get_model(name: "#{scope}__PERIODIC__#{scope}", scope: scope)
|
16
|
+
model.destroy if model
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
unless ENV['OPENC3_NO_MIGRATE']
|
23
|
+
OpenC3::PeriodicOnlyDefault.run
|
24
|
+
end
|
data/lib/openc3/models/model.rb
CHANGED
@@ -139,7 +139,7 @@ module OpenC3
|
|
139
139
|
|
140
140
|
# Update the Redis hash at primary_key and set the field "name"
|
141
141
|
# to the JSON generated via calling as_json
|
142
|
-
def create(update: false, force: false, queued: false)
|
142
|
+
def create(update: false, force: false, queued: false, isoformat: false)
|
143
143
|
unless force
|
144
144
|
existing = self.class.store.hget(@primary_key, @name)
|
145
145
|
if existing
|
@@ -148,7 +148,11 @@ module OpenC3
|
|
148
148
|
raise "#{@primary_key}:#{@name} doesn't exist at update" if update
|
149
149
|
end
|
150
150
|
end
|
151
|
-
|
151
|
+
if isoformat
|
152
|
+
@updated_at = Time.now.utc.iso8601
|
153
|
+
else
|
154
|
+
@updated_at = Time.now.utc.to_nsec_from_epoch
|
155
|
+
end
|
152
156
|
|
153
157
|
if queued
|
154
158
|
write_store = self.class.store_queued
|
@@ -19,28 +19,29 @@
|
|
19
19
|
require 'openc3/models/model'
|
20
20
|
|
21
21
|
module OpenC3
|
22
|
+
# Note: This model is locked to the DEFAULT scope
|
22
23
|
class OfflineAccessModel < Model
|
23
|
-
PRIMARY_KEY = '
|
24
|
+
PRIMARY_KEY = 'DEFAULT__openc3__offline_access'
|
24
25
|
|
25
26
|
attr_accessor :offline_access_token
|
26
27
|
|
27
28
|
# NOTE: The following three class methods are used by the ModelController
|
28
29
|
# and are reimplemented to enable various Model class methods to work
|
29
30
|
def self.get(name:, scope:)
|
30
|
-
super(
|
31
|
+
super(PRIMARY_KEY, name: name)
|
31
32
|
end
|
32
33
|
|
33
34
|
def self.names(scope:)
|
34
|
-
super(
|
35
|
+
super(PRIMARY_KEY)
|
35
36
|
end
|
36
37
|
|
37
38
|
def self.all(scope:)
|
38
|
-
super(
|
39
|
+
super(PRIMARY_KEY)
|
39
40
|
end
|
40
41
|
# END NOTE
|
41
42
|
|
42
|
-
def initialize(name:, offline_access_token: nil, updated_at: nil, scope:)
|
43
|
-
super(
|
43
|
+
def initialize(name:, offline_access_token: nil, updated_at: nil, scope: 'DEFAULT')
|
44
|
+
super(PRIMARY_KEY, name: name, updated_at: updated_at, scope: 'DEFAULT')
|
44
45
|
@offline_access_token = offline_access_token
|
45
46
|
end
|
46
47
|
|
@@ -350,8 +350,11 @@ module OpenC3
|
|
350
350
|
# UNKNOWN PacketLog Microservice
|
351
351
|
deploy_unknown_packetlog_microservice(gem_path, variables, @parent)
|
352
352
|
|
353
|
-
#
|
354
|
-
|
353
|
+
# Only DEFAULT scope
|
354
|
+
if @scope == 'DEFAULT'
|
355
|
+
# Periodic Microservice
|
356
|
+
deploy_periodic_microservice(gem_path, variables, @parent)
|
357
|
+
end
|
355
358
|
|
356
359
|
# Scope Cleanup Microservice
|
357
360
|
deploy_scopecleanup_microservice(gem_path, variables, @parent)
|
@@ -0,0 +1,242 @@
|
|
1
|
+
# encoding: ascii-8bit
|
2
|
+
|
3
|
+
# Copyright 2025 OpenC3, Inc.
|
4
|
+
# All Rights Reserved.
|
5
|
+
#
|
6
|
+
# This program is free software; you can modify and/or redistribute it
|
7
|
+
# under the terms of the GNU Affero General Public License
|
8
|
+
# as published by the Free Software Foundation; version 3 with
|
9
|
+
# attribution addendums as found in the LICENSE.txt
|
10
|
+
#
|
11
|
+
# This program is distributed in the hope that it will be useful,
|
12
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
+
# GNU Affero General Public License for more details.
|
15
|
+
#
|
16
|
+
# This file may also be used under the terms of a commercial license
|
17
|
+
# if purchased from OpenC3, Inc.
|
18
|
+
|
19
|
+
require 'openc3/models/model'
|
20
|
+
|
21
|
+
module OpenC3
|
22
|
+
class ScriptStatusModel < Model
|
23
|
+
# Note: ScriptRunner only has permissions for keys that start with running-script
|
24
|
+
RUNNING_PRIMARY_KEY = 'running-script'
|
25
|
+
COMPLETED_PRIMARY_KEY = 'running-script-completed'
|
26
|
+
|
27
|
+
def id
|
28
|
+
return @name
|
29
|
+
end
|
30
|
+
attr_reader :state # spawning, init, running, paused, waiting, breakpoint, error, crashed, stopped, completed, completed_errors, killed
|
31
|
+
attr_accessor :shard
|
32
|
+
attr_accessor :filename
|
33
|
+
attr_accessor :current_filename
|
34
|
+
attr_accessor :line_no
|
35
|
+
attr_accessor :start_line_no
|
36
|
+
attr_accessor :end_line_no
|
37
|
+
attr_accessor :username
|
38
|
+
attr_accessor :user_full_name
|
39
|
+
attr_accessor :start_time
|
40
|
+
attr_accessor :end_time
|
41
|
+
attr_accessor :disconnect
|
42
|
+
attr_accessor :environment
|
43
|
+
attr_accessor :suite_runner
|
44
|
+
attr_accessor :errors
|
45
|
+
attr_accessor :pid
|
46
|
+
attr_accessor :log
|
47
|
+
attr_accessor :report
|
48
|
+
|
49
|
+
# NOTE: The following three class methods are used by the ModelController
|
50
|
+
# and are reimplemented to enable various Model class methods to work
|
51
|
+
def self.get(name:, scope:, type: "auto")
|
52
|
+
if type == "auto" or type == "running"
|
53
|
+
# Check for running first
|
54
|
+
running = super("#{RUNNING_PRIMARY_KEY}__#{scope}", name: name)
|
55
|
+
return running if running
|
56
|
+
end
|
57
|
+
return super("#{COMPLETED_PRIMARY_KEY}__#{scope}", name: name)
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.names(scope:, type: "running")
|
61
|
+
if type == "running"
|
62
|
+
return super("#{RUNNING_PRIMARY_KEY}__#{scope}")
|
63
|
+
else
|
64
|
+
return super("#{COMPLETED_PRIMARY_KEY}__#{scope}")
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.all(scope:, offset: 0, limit: 10, type: "running")
|
69
|
+
if type == "running"
|
70
|
+
keys = self.store.zrevrange("#{RUNNING_PRIMARY_KEY}__#{scope}__LIST", offset.to_i, offset.to_i + limit.to_i - 1)
|
71
|
+
return [] if keys.empty?
|
72
|
+
result = self.store.redis_pool.pipelined do
|
73
|
+
keys.each do |key|
|
74
|
+
self.store.hget("#{RUNNING_PRIMARY_KEY}__#{scope}", key)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
result = result.map do |r|
|
78
|
+
if r.nil?
|
79
|
+
nil
|
80
|
+
else
|
81
|
+
JSON.parse(r, :allow_nan => true, :create_additions => true)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
return result
|
85
|
+
else
|
86
|
+
keys = self.store.zrevrange("#{COMPLETED_PRIMARY_KEY}__#{scope}__LIST", offset.to_i, offset.to_i + limit.to_i - 1)
|
87
|
+
return [] if keys.empty?
|
88
|
+
result = self.store.redis_pool.pipelined do
|
89
|
+
keys.each do |key|
|
90
|
+
self.store.hget("#{COMPLETED_PRIMARY_KEY}__#{scope}", key)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
result = result.map do |r|
|
94
|
+
if r.nil?
|
95
|
+
nil
|
96
|
+
else
|
97
|
+
JSON.parse(r, :allow_nan => true, :create_additions => true)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
return result
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def self.count(scope:, type: "running")
|
105
|
+
if type == "running"
|
106
|
+
return self.store.zcount("#{RUNNING_PRIMARY_KEY}__#{scope}__LIST", 0, Float::INFINITY)
|
107
|
+
else
|
108
|
+
return self.store.zcount("#{COMPLETED_PRIMARY_KEY}__#{scope}__LIST", 0, Float::INFINITY)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def initialize(
|
113
|
+
name:, # id
|
114
|
+
state:, # spawning, init, running, paused, waiting, error, breakpoint, crashed, stopped, completed, completed_errors, killed
|
115
|
+
shard: 0, # Future enhancement of script runner shards
|
116
|
+
filename:, # The initial filename
|
117
|
+
current_filename: nil, # The current filename
|
118
|
+
line_no: 0, # The current line number
|
119
|
+
start_line_no: 1, # The line number to start the script at
|
120
|
+
end_line_no: nil, # The line number to end the script at
|
121
|
+
username:, # The username of the person who started the script
|
122
|
+
user_full_name:, # The full name of the person who started the script
|
123
|
+
start_time:, # The time the script started ISO format
|
124
|
+
end_time: nil, # The time the script ended ISO format
|
125
|
+
disconnect: false,
|
126
|
+
environment: nil,
|
127
|
+
suite_runner: nil,
|
128
|
+
errors: nil,
|
129
|
+
pid: nil,
|
130
|
+
log: nil,
|
131
|
+
report: nil,
|
132
|
+
updated_at: nil,
|
133
|
+
scope:
|
134
|
+
)
|
135
|
+
@state = state
|
136
|
+
if is_complete?()
|
137
|
+
super("#{COMPLETED_PRIMARY_KEY}__#{scope}", name: name, updated_at: updated_at, plugin: nil, scope: scope)
|
138
|
+
else
|
139
|
+
super("#{RUNNING_PRIMARY_KEY}__#{scope}", name: name, updated_at: updated_at, plugin: nil, scope: scope)
|
140
|
+
end
|
141
|
+
@shard = shard.to_i
|
142
|
+
@filename = filename
|
143
|
+
@current_filename = current_filename
|
144
|
+
@line_no = line_no
|
145
|
+
@start_line_no = start_line_no
|
146
|
+
@end_line_no = end_line_no
|
147
|
+
@username = username
|
148
|
+
@user_full_name = user_full_name
|
149
|
+
@start_time = start_time
|
150
|
+
@end_time = end_time
|
151
|
+
@disconnect = disconnect
|
152
|
+
@environment = environment
|
153
|
+
@suite_runner = suite_runner
|
154
|
+
@errors = errors
|
155
|
+
@pid = pid
|
156
|
+
@log = log
|
157
|
+
@report = report
|
158
|
+
end
|
159
|
+
|
160
|
+
def is_complete?
|
161
|
+
return (@state == 'completed' or @state == 'completed_errors' or @state == 'stopped' or @state == 'crashed' or @state == 'killed')
|
162
|
+
end
|
163
|
+
|
164
|
+
def state=(new_state)
|
165
|
+
# If the state is already a flavor of complete, leave it alone (first wins)
|
166
|
+
if not is_complete?()
|
167
|
+
@state = new_state
|
168
|
+
# If setting to complete, check for errors
|
169
|
+
# and set the state to complete_errors if they exist
|
170
|
+
if @state == 'completed' and @errors
|
171
|
+
@state = 'completed_errors'
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
# Update the Redis hash at primary_key and set the field "name"
|
177
|
+
# to the JSON generated via calling as_json
|
178
|
+
def create(update: false, force: false, queued: false, isoformat: true)
|
179
|
+
@updated_at = Time.now.utc.to_nsec_from_epoch
|
180
|
+
|
181
|
+
if queued
|
182
|
+
write_store = self.class.store_queued
|
183
|
+
else
|
184
|
+
write_store = self.class.store
|
185
|
+
end
|
186
|
+
write_store.hset(@primary_key, @name, JSON.generate(self.as_json(:allow_nan => true), :allow_nan => true))
|
187
|
+
|
188
|
+
# Also add to ordered set on create
|
189
|
+
write_store.zadd(@primary_key + "__LIST", @name.to_i, @name) if not update
|
190
|
+
end
|
191
|
+
|
192
|
+
def update(force: false, queued: false)
|
193
|
+
# Magically handle the change from running to completed
|
194
|
+
if is_complete?() and @primary_key == "#{RUNNING_PRIMARY_KEY}__#{@scope}"
|
195
|
+
# Destroy the running key
|
196
|
+
destroy()
|
197
|
+
@destroyed = false
|
198
|
+
|
199
|
+
# Move to completed
|
200
|
+
@primary_key = "#{COMPLETED_PRIMARY_KEY}__#{@scope}"
|
201
|
+
create(update: false, force: force, queued: queued, isoformat: true)
|
202
|
+
else
|
203
|
+
create(update: true, force: force, queued: queued, isoformat: true)
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
# Delete the model from the Store
|
208
|
+
def destroy
|
209
|
+
@destroyed = true
|
210
|
+
undeploy()
|
211
|
+
self.class.store.hdel(@primary_key, @name)
|
212
|
+
# Also remove from ordered set
|
213
|
+
self.class.store.zremrangebyscore(@primary_key + "__LIST", @name.to_i, @name.to_i)
|
214
|
+
end
|
215
|
+
|
216
|
+
def as_json(*a)
|
217
|
+
{
|
218
|
+
'name' => @name,
|
219
|
+
'state' => @state,
|
220
|
+
'shard' => @shard,
|
221
|
+
'filename' => @filename,
|
222
|
+
'current_filename' => @current_filename,
|
223
|
+
'line_no' => @line_no,
|
224
|
+
'start_line_no' => @start_line_no,
|
225
|
+
'end_line_no' => @end_line_no,
|
226
|
+
'username' => @username,
|
227
|
+
'user_full_name' => @user_full_name,
|
228
|
+
'start_time' => @start_time,
|
229
|
+
'end_time' => @end_time,
|
230
|
+
'disconnect' => @disconnect,
|
231
|
+
'environment' => @environment,
|
232
|
+
'suite_runner' => @suite_runner,
|
233
|
+
'errors' => @errors,
|
234
|
+
'pid' => @pid,
|
235
|
+
'log' => @log,
|
236
|
+
'report' => @report,
|
237
|
+
'updated_at' => @updated_at,
|
238
|
+
'scope' => @scope
|
239
|
+
}
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|