openc3 7.0.0 → 7.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/bin/openc3cli +105 -13
- data/bin/pipinstall +38 -6
- data/data/config/command_modifiers.yaml +1 -0
- data/data/config/item_modifiers.yaml +2 -1
- data/data/config/microservice.yaml +12 -1
- data/data/config/parameter_modifiers.yaml +49 -7
- data/data/config/table_parameter_modifiers.yaml +3 -1
- data/data/config/target.yaml +11 -0
- data/data/config/target_config.yaml +6 -2
- data/lib/openc3/accessors/template_accessor.rb +9 -0
- data/lib/openc3/api/cmd_api.rb +2 -1
- data/lib/openc3/api/metrics_api.rb +11 -1
- data/lib/openc3/api/tlm_api.rb +21 -6
- data/lib/openc3/core_ext/faraday.rb +1 -1
- data/lib/openc3/interfaces/interface.rb +1 -6
- data/lib/openc3/io/json_api.rb +1 -1
- data/lib/openc3/logs/log_writer.rb +3 -1
- data/lib/openc3/microservices/decom_common.rb +128 -0
- data/lib/openc3/microservices/decom_microservice.rb +27 -96
- data/lib/openc3/microservices/interface_decom_common.rb +28 -10
- data/lib/openc3/microservices/interface_microservice.rb +16 -9
- data/lib/openc3/microservices/log_microservice.rb +1 -1
- data/lib/openc3/microservices/microservice.rb +3 -2
- data/lib/openc3/microservices/queue_microservice.rb +1 -1
- data/lib/openc3/microservices/scope_cleanup_microservice.rb +60 -46
- data/lib/openc3/microservices/text_log_microservice.rb +1 -2
- data/lib/openc3/models/cvt_model.rb +24 -13
- data/lib/openc3/models/db_sharded_model.rb +110 -0
- data/lib/openc3/models/interface_model.rb +9 -0
- data/lib/openc3/models/interface_status_model.rb +33 -3
- data/lib/openc3/models/metric_model.rb +96 -37
- data/lib/openc3/models/microservice_model.rb +7 -0
- data/lib/openc3/models/microservice_status_model.rb +30 -3
- data/lib/openc3/models/plugin_model.rb +9 -1
- data/lib/openc3/models/python_package_model.rb +1 -1
- data/lib/openc3/models/reaction_model.rb +27 -9
- data/lib/openc3/models/reingest_job_model.rb +153 -0
- data/lib/openc3/models/scope_model.rb +3 -2
- data/lib/openc3/models/script_status_model.rb +4 -20
- data/lib/openc3/models/target_model.rb +113 -100
- data/lib/openc3/models/trigger_model.rb +24 -7
- data/lib/openc3/packets/packet_config.rb +4 -1
- data/lib/openc3/script/api_shared.rb +39 -2
- data/lib/openc3/script/calendar.rb +32 -10
- data/lib/openc3/script/extract.rb +46 -13
- data/lib/openc3/script/script.rb +2 -2
- data/lib/openc3/script/script_runner.rb +4 -4
- data/lib/openc3/script/telemetry.rb +3 -3
- data/lib/openc3/script/web_socket_api.rb +29 -22
- data/lib/openc3/system/system.rb +20 -3
- data/lib/openc3/topics/command_decom_topic.rb +4 -2
- data/lib/openc3/topics/command_topic.rb +8 -5
- data/lib/openc3/topics/decom_interface_topic.rb +31 -11
- data/lib/openc3/topics/interface_topic.rb +88 -27
- data/lib/openc3/topics/limits_event_topic.rb +62 -41
- data/lib/openc3/topics/router_topic.rb +61 -21
- data/lib/openc3/topics/system_events_topic.rb +18 -1
- data/lib/openc3/topics/telemetry_decom_topic.rb +2 -1
- data/lib/openc3/topics/telemetry_topic.rb +4 -2
- data/lib/openc3/topics/topic.rb +77 -5
- data/lib/openc3/utilities/aws_bucket.rb +2 -0
- data/lib/openc3/utilities/cli_generator.rb +3 -2
- data/lib/openc3/utilities/ctrf.rb +231 -0
- data/lib/openc3/utilities/metric.rb +15 -1
- data/lib/openc3/utilities/questdb_client.rb +177 -40
- data/lib/openc3/utilities/reingest_job.rb +377 -0
- data/lib/openc3/utilities/ruby_lex_utils.rb +2 -0
- data/lib/openc3/utilities/store_autoload.rb +78 -52
- data/lib/openc3/utilities/store_queued.rb +20 -12
- data/lib/openc3/version.rb +5 -5
- data/templates/plugin/plugin.gemspec +13 -1
- data/templates/tool_angular/package.json +2 -2
- data/templates/tool_react/package.json +1 -1
- data/templates/tool_svelte/package.json +1 -1
- data/templates/tool_vue/package.json +3 -4
- data/templates/tool_vue/src/router.js +2 -2
- data/templates/widget/package.json +2 -2
- metadata +8 -3
data/lib/openc3/topics/topic.rb
CHANGED
|
@@ -19,18 +19,90 @@ require 'openc3/utilities/store'
|
|
|
19
19
|
|
|
20
20
|
module OpenC3
|
|
21
21
|
class Topic
|
|
22
|
-
# Delegate all unknown class methods to
|
|
22
|
+
# Delegate all unknown class methods to EphemeralStore db_shard 0 (system-level topics)
|
|
23
23
|
def self.method_missing(message, *args, **kwargs, &block)
|
|
24
24
|
EphemeralStore.public_send(message, *args, **kwargs, &block)
|
|
25
25
|
end
|
|
26
26
|
|
|
27
|
-
def self.clear_topics(topics, maxlen = 0)
|
|
28
|
-
|
|
27
|
+
def self.clear_topics(topics, maxlen = 0, db_shard: 0)
|
|
28
|
+
store = EphemeralStore.instance(db_shard: db_shard)
|
|
29
|
+
topics.each { |topic| store.xtrim(topic, maxlen) }
|
|
29
30
|
end
|
|
30
31
|
|
|
31
|
-
def self.get_cnt(topic)
|
|
32
|
-
_, packet = EphemeralStore.get_newest_message(topic)
|
|
32
|
+
def self.get_cnt(topic, db_shard: 0)
|
|
33
|
+
_, packet = EphemeralStore.instance(db_shard: db_shard).get_newest_message(topic)
|
|
33
34
|
packet ? packet["received_count"].to_i : 0
|
|
34
35
|
end
|
|
36
|
+
|
|
37
|
+
# DB_Shard-aware topic methods for target-specific streams.
|
|
38
|
+
# These explicitly route to the correct EphemeralStore db_shard.
|
|
39
|
+
|
|
40
|
+
def self.write_topic(topic, msg_hash, id = '*', maxlen = nil, approximate = 'true', db_shard: 0)
|
|
41
|
+
EphemeralStore.instance(db_shard: db_shard).write_topic(topic, msg_hash, id, maxlen, approximate)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def self.read_topics(topics, offsets = nil, timeout_ms = 1000, count = nil, db_shard: 0, &block)
|
|
45
|
+
EphemeralStore.instance(db_shard: db_shard).read_topics(topics, offsets, timeout_ms, count, &block)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def self.get_newest_message(topic, db_shard: 0)
|
|
49
|
+
EphemeralStore.instance(db_shard: db_shard).get_newest_message(topic)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def self.get_oldest_message(topic, db_shard: 0)
|
|
53
|
+
EphemeralStore.instance(db_shard: db_shard).get_oldest_message(topic)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def self.get_last_offset(topic, db_shard: 0)
|
|
57
|
+
EphemeralStore.instance(db_shard: db_shard).get_last_offset(topic)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def self.update_topic_offsets(topics, db_shard: 0)
|
|
61
|
+
EphemeralStore.instance(db_shard: db_shard).update_topic_offsets(topics)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def self.trim_topic(topic, minid, approximate = true, limit: 0, db_shard: 0)
|
|
65
|
+
EphemeralStore.instance(db_shard: db_shard).trim_topic(topic, minid, approximate, limit: limit)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def self.del(topic, db_shard: 0)
|
|
69
|
+
EphemeralStore.instance(db_shard: db_shard).del(topic)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Group topics by db_shard. Each topic's target name is extracted and looked up.
|
|
73
|
+
# Topics matching target_pattern are db_sharded; others go to db_shard 0.
|
|
74
|
+
# @param topics [Array<String>] List of topic strings
|
|
75
|
+
# @param target_pattern [String] Substring to identify target-specific topics (e.g. 'CMD}TARGET__', '__TELEMETRY__')
|
|
76
|
+
# @param scope [String] Scope name for db_shard lookup
|
|
77
|
+
# @return [Hash] { db_shard => [topic, ...] }
|
|
78
|
+
def self.group_topics_by_db_shard(topics, target_pattern:, scope:)
|
|
79
|
+
groups = {}
|
|
80
|
+
topics.each do |topic|
|
|
81
|
+
if topic.include?(target_pattern)
|
|
82
|
+
target_name = topic.match(/__\{?([^}_]+)\}?__/)[1] rescue nil
|
|
83
|
+
# Handle CMD}TARGET__ pattern where target is after TARGET__
|
|
84
|
+
target_name = topic.split('TARGET__')[1] if target_pattern.include?('TARGET__') && target_name.nil?
|
|
85
|
+
db_shard = (Store.db_shard_for_target(target_name, scope: scope) || 0).to_i
|
|
86
|
+
else
|
|
87
|
+
db_shard = 0
|
|
88
|
+
end
|
|
89
|
+
groups[db_shard] ||= []
|
|
90
|
+
groups[db_shard] << topic
|
|
91
|
+
end
|
|
92
|
+
groups
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Check if all db_shard groups resolve to a single db_shard (fast path).
|
|
96
|
+
def self.all_same_db_shard?(db_shard_groups)
|
|
97
|
+
db_shard_groups.length <= 1
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Build the ACK topic from a command/router topic and write the ack.
|
|
101
|
+
def self.write_ack(topic, result, msg_id, db_shard: 0)
|
|
102
|
+
ack_topic = topic.split("__")
|
|
103
|
+
ack_topic[1] = 'ACK' + ack_topic[1]
|
|
104
|
+
ack_topic = ack_topic.join("__")
|
|
105
|
+
Topic.write_topic(ack_topic, { 'result' => result, 'id' => msg_id }, '*', 100, db_shard: db_shard)
|
|
106
|
+
end
|
|
35
107
|
end
|
|
36
108
|
end
|
|
@@ -177,6 +177,7 @@ module OpenC3
|
|
|
177
177
|
@client.put_bucket_policy(options)
|
|
178
178
|
rescue Aws::S3::Errors::NotImplemented, Aws::S3::Errors::ServiceError, Aws::S3::Errors::InternalError => e
|
|
179
179
|
Logger.warn("put_bucket_policy for #{config_bucket} not supported by S3 backend: #{e.message}")
|
|
180
|
+
Logger.warn("Policy applied:\n#{config_policy}")
|
|
180
181
|
end
|
|
181
182
|
|
|
182
183
|
begin
|
|
@@ -186,6 +187,7 @@ module OpenC3
|
|
|
186
187
|
@client.put_bucket_policy(options)
|
|
187
188
|
rescue Aws::S3::Errors::NotImplemented, Aws::S3::Errors::ServiceError, Aws::S3::Errors::InternalError => e
|
|
188
189
|
Logger.warn("put_bucket_policy for #{logs_bucket} not supported by S3 backend: #{e.message}")
|
|
190
|
+
Logger.warn("Policy applied:\n#{logs_policy}")
|
|
189
191
|
end
|
|
190
192
|
end
|
|
191
193
|
|
|
@@ -141,8 +141,9 @@ module OpenC3
|
|
|
141
141
|
abort("Usage: cli generate #{args[0]} <NAME> (--ruby or --python)")
|
|
142
142
|
end
|
|
143
143
|
|
|
144
|
-
# Create the local variables
|
|
145
|
-
|
|
144
|
+
# Create the local variables that are used in process_template below (see openc3/templates/plugin/plugin.gemspec as an example)
|
|
145
|
+
plugin_orig = args[1]
|
|
146
|
+
plugin = plugin_orig.downcase.gsub(/_+|-+/, '-')
|
|
146
147
|
plugin_name = "openc3-cosmos-#{plugin}"
|
|
147
148
|
if File.exist?(plugin_name)
|
|
148
149
|
abort("Plugin #{plugin_name} already exists!")
|
|
@@ -0,0 +1,231 @@
|
|
|
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 'openc3/version'
|
|
15
|
+
require 'date'
|
|
16
|
+
|
|
17
|
+
module OpenC3
|
|
18
|
+
# Utility class for converting COSMOS script reports to CTRF (Common Test Report Format)
|
|
19
|
+
# See https://ctrf.io/docs/category/specification
|
|
20
|
+
class Ctrf
|
|
21
|
+
# Convert a COSMOS plain text script report to CTRF JSON format
|
|
22
|
+
# @param report_content [String] Plain text script report
|
|
23
|
+
# @param version [String] Version string to include in CTRF output (defaults to OpenC3::VERSION)
|
|
24
|
+
# @return [Hash] CTRF formatted report as a Ruby hash
|
|
25
|
+
def self.convert_report(report_content, version: OpenC3::VERSION)
|
|
26
|
+
lines = report_content.split("\n")
|
|
27
|
+
tests = []
|
|
28
|
+
summary = {}
|
|
29
|
+
settings = {}
|
|
30
|
+
in_settings = false
|
|
31
|
+
last_result = nil
|
|
32
|
+
in_summary = false
|
|
33
|
+
|
|
34
|
+
lines.each do |line|
|
|
35
|
+
next if line.nil?
|
|
36
|
+
line_clean = line.strip
|
|
37
|
+
|
|
38
|
+
if line_clean == 'Settings:'
|
|
39
|
+
in_settings = true
|
|
40
|
+
next
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
if in_settings
|
|
44
|
+
if line_clean.include?('Manual')
|
|
45
|
+
parts = line.split('=')
|
|
46
|
+
settings[:manual] = parts[1].strip if parts[1]
|
|
47
|
+
next
|
|
48
|
+
elsif line_clean.include?('Pause on Error')
|
|
49
|
+
parts = line.split('=')
|
|
50
|
+
settings[:pauseOnError] = parts[1].strip if parts[1]
|
|
51
|
+
next
|
|
52
|
+
elsif line_clean.include?('Continue After Error')
|
|
53
|
+
parts = line.split('=')
|
|
54
|
+
settings[:continueAfterError] = parts[1].strip if parts[1]
|
|
55
|
+
next
|
|
56
|
+
elsif line_clean.include?('Abort After Error')
|
|
57
|
+
parts = line.split('=')
|
|
58
|
+
settings[:abortAfterError] = parts[1].strip if parts[1]
|
|
59
|
+
next
|
|
60
|
+
elsif line_clean.include?('Loop =')
|
|
61
|
+
parts = line.split('=')
|
|
62
|
+
settings[:loop] = parts[1].strip if parts[1]
|
|
63
|
+
next
|
|
64
|
+
elsif line_clean.include?('Break Loop On Error')
|
|
65
|
+
parts = line.split('=')
|
|
66
|
+
settings[:breakLoopOnError] = parts[1].strip if parts[1]
|
|
67
|
+
in_settings = false
|
|
68
|
+
next
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
if line_clean == 'Results:'
|
|
73
|
+
last_result = line_clean
|
|
74
|
+
next
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
if last_result
|
|
78
|
+
# The first line should always have a timestamp and what it is executing
|
|
79
|
+
# Format: "2026-04-02T19:45:41.228209Z: Executing MySuite:ExampleGroup:script_2"
|
|
80
|
+
if last_result == 'Results:' and line_clean.include?("Executing")
|
|
81
|
+
# Split on first ': ' to separate timestamp from message
|
|
82
|
+
timestamp_and_msg = line_clean.split(': ', 2)
|
|
83
|
+
if timestamp_and_msg.length >= 2
|
|
84
|
+
timestamp = timestamp_and_msg[0]
|
|
85
|
+
begin
|
|
86
|
+
summary[:startTime] = DateTime.parse(timestamp).to_time.to_f * 1000
|
|
87
|
+
rescue Date::Error
|
|
88
|
+
# Skip malformed timestamps
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
last_result = line_clean
|
|
92
|
+
next
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Format: "2026-04-02T19:45:44.041472Z: ExampleGroup:script_2:PASS"
|
|
96
|
+
# Check if line contains a test result (but not Executing or Completed)
|
|
97
|
+
if !line_clean.include?("Executing") && !line_clean.include?("Completed")
|
|
98
|
+
# Try to parse as a test result line
|
|
99
|
+
timestamp_and_msg = line_clean.split(': ', 2)
|
|
100
|
+
if timestamp_and_msg.length >= 2
|
|
101
|
+
result_string = timestamp_and_msg[1]
|
|
102
|
+
# Must have at least 2 colons for group:name:status format
|
|
103
|
+
result_parts = result_string.split(':')
|
|
104
|
+
if result_parts.length >= 3
|
|
105
|
+
# Get start time from last_result - could be Executing line or previous test result
|
|
106
|
+
start_time = nil
|
|
107
|
+
if last_result
|
|
108
|
+
last_timestamp_and_msg = last_result.split(': ', 2)
|
|
109
|
+
if last_timestamp_and_msg.length >= 2
|
|
110
|
+
begin
|
|
111
|
+
start_time = DateTime.parse(last_timestamp_and_msg[0]).to_time.to_f * 1000
|
|
112
|
+
rescue Date::Error
|
|
113
|
+
# Skip malformed timestamps
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Parse current line timestamp
|
|
119
|
+
timestamp = timestamp_and_msg[0]
|
|
120
|
+
begin
|
|
121
|
+
end_time = DateTime.parse(timestamp).to_time.to_f * 1000
|
|
122
|
+
rescue Date::Error
|
|
123
|
+
last_result = line_clean
|
|
124
|
+
next # Skip lines with malformed timestamps
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Parse the test result: ExampleGroup:script_2:PASS
|
|
128
|
+
suite_group = result_parts[0]
|
|
129
|
+
name = result_parts[1]
|
|
130
|
+
status = result_parts[2]
|
|
131
|
+
|
|
132
|
+
format_status = case status
|
|
133
|
+
when 'PASS'
|
|
134
|
+
'passed'
|
|
135
|
+
when 'SKIP'
|
|
136
|
+
'skipped'
|
|
137
|
+
when 'FAIL'
|
|
138
|
+
'failed'
|
|
139
|
+
else
|
|
140
|
+
'unknown'
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
tests << {
|
|
144
|
+
name: "#{suite_group}:#{name}",
|
|
145
|
+
status: format_status,
|
|
146
|
+
duration: start_time ? (end_time - start_time) : 0,
|
|
147
|
+
}
|
|
148
|
+
last_result = line_clean
|
|
149
|
+
next
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# Format: "2026-04-02T19:45:44.044982Z: Completed MySuite:ExampleGroup:script_2"
|
|
155
|
+
if line_clean.include?("Completed")
|
|
156
|
+
timestamp_and_msg = line_clean.split(': ', 2)
|
|
157
|
+
if timestamp_and_msg.length >= 2
|
|
158
|
+
timestamp = timestamp_and_msg[0]
|
|
159
|
+
begin
|
|
160
|
+
summary[:stopTime] = DateTime.parse(timestamp).to_time.to_f * 1000
|
|
161
|
+
rescue Date::Error
|
|
162
|
+
# Skip malformed timestamps
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
last_result = nil
|
|
166
|
+
next
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
if line_clean == '--- Test Summary ---'
|
|
171
|
+
in_summary = true
|
|
172
|
+
next
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
if in_summary
|
|
176
|
+
if line_clean.include?("Total Tests")
|
|
177
|
+
parts = line_clean.split(':')
|
|
178
|
+
summary[:total] = parts[1].to_i if parts[1]
|
|
179
|
+
end
|
|
180
|
+
if line_clean.include?("Pass:")
|
|
181
|
+
parts = line_clean.split(':')
|
|
182
|
+
summary[:passed] = parts[1].to_i if parts[1]
|
|
183
|
+
end
|
|
184
|
+
if line_clean.include?("Skip:")
|
|
185
|
+
parts = line_clean.split(':')
|
|
186
|
+
summary[:skipped] = parts[1].to_i if parts[1]
|
|
187
|
+
end
|
|
188
|
+
if line_clean.include?("Fail:")
|
|
189
|
+
parts = line_clean.split(':')
|
|
190
|
+
summary[:failed] = parts[1].to_i if parts[1]
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# Build CTRF report
|
|
196
|
+
# See https://ctrf.io/docs/specification/root
|
|
197
|
+
return {
|
|
198
|
+
reportFormat: "CTRF",
|
|
199
|
+
results: {
|
|
200
|
+
# See https://ctrf.io/docs/specification/tool
|
|
201
|
+
tool: {
|
|
202
|
+
name: "COSMOS Script Runner",
|
|
203
|
+
version: version,
|
|
204
|
+
},
|
|
205
|
+
# See https://ctrf.io/docs/specification/summary
|
|
206
|
+
summary: {
|
|
207
|
+
tests: summary[:total],
|
|
208
|
+
passed: summary[:passed],
|
|
209
|
+
failed: summary[:failed],
|
|
210
|
+
pending: 0,
|
|
211
|
+
skipped: summary[:skipped],
|
|
212
|
+
other: 0,
|
|
213
|
+
start: summary[:startTime],
|
|
214
|
+
stop: summary[:stopTime],
|
|
215
|
+
},
|
|
216
|
+
# See https://ctrf.io/docs/specification/tests
|
|
217
|
+
tests: tests,
|
|
218
|
+
# See https://ctrf.io/docs/specification/extra
|
|
219
|
+
extra: {
|
|
220
|
+
manual: settings[:manual],
|
|
221
|
+
pauseOnError: settings[:pauseOnError],
|
|
222
|
+
continueAfterError: settings[:continueAfterError],
|
|
223
|
+
abortAfterError: settings[:abortAfterError],
|
|
224
|
+
loop: settings[:loop],
|
|
225
|
+
breakLoopOnError: settings[:breakLoopOnError],
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
}
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
end
|
|
@@ -37,15 +37,28 @@ module OpenC3
|
|
|
37
37
|
|
|
38
38
|
attr_reader :microservice
|
|
39
39
|
attr_reader :scope
|
|
40
|
+
attr_reader :db_shard
|
|
40
41
|
attr_reader :data
|
|
41
42
|
attr_reader :mutex
|
|
42
43
|
|
|
43
|
-
def initialize(microservice:, scope:)
|
|
44
|
+
def initialize(microservice:, scope:, db_shard: nil)
|
|
44
45
|
@scope = scope
|
|
45
46
|
@microservice = microservice
|
|
46
47
|
@data = {}
|
|
47
48
|
@mutex = Mutex.new
|
|
48
49
|
|
|
50
|
+
if db_shard
|
|
51
|
+
@db_shard = db_shard
|
|
52
|
+
else
|
|
53
|
+
# Look up db_shard from MicroserviceModel
|
|
54
|
+
begin
|
|
55
|
+
json = Store.hget('openc3_microservices', microservice)
|
|
56
|
+
@db_shard = json ? JSON.parse(json)['db_shard'].to_i : 0
|
|
57
|
+
rescue
|
|
58
|
+
@db_shard = 0
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
49
62
|
# Always make sure there is a update thread
|
|
50
63
|
@@mutex.synchronize do
|
|
51
64
|
@@instances << self
|
|
@@ -90,6 +103,7 @@ module OpenC3
|
|
|
90
103
|
instance.mutex.synchronize do
|
|
91
104
|
json = {}
|
|
92
105
|
json['name'] = instance.microservice
|
|
106
|
+
json['db_shard'] = instance.db_shard
|
|
93
107
|
values = instance.data
|
|
94
108
|
json['values'] = values
|
|
95
109
|
MetricModel.set(json, scope: instance.scope) if values.length > 0
|