openc3 5.1.1 → 5.2.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 +48 -9
- data/data/config/interface_modifiers.yaml +14 -0
- data/data/config/parameter_modifiers.yaml +5 -3
- data/data/config/screen.yaml +12 -8
- data/data/config/target.yaml +33 -0
- data/ext/openc3/ext/config_parser/config_parser.c +66 -63
- data/ext/openc3/ext/packet/packet.c +1 -4
- data/lib/openc3/api/README.md +5 -0
- data/lib/openc3/api/api.rb +3 -1
- data/lib/openc3/api/cmd_api.rb +43 -112
- data/lib/openc3/api/interface_api.rb +3 -3
- data/lib/openc3/api/offline_access_api.rb +78 -0
- data/lib/openc3/api/settings_api.rb +3 -1
- data/lib/openc3/api/stash_api.rb +63 -0
- data/lib/openc3/api/target_api.rb +4 -5
- data/lib/openc3/config/config_parser.rb +47 -47
- data/lib/openc3/interfaces/interface.rb +11 -1
- data/lib/openc3/interfaces/protocols/burst_protocol.rb +30 -16
- data/lib/openc3/interfaces/protocols/fixed_protocol.rb +8 -2
- data/lib/openc3/interfaces/protocols/ignore_packet_protocol.rb +2 -2
- data/lib/openc3/interfaces/protocols/override_protocol.rb +2 -2
- data/lib/openc3/interfaces/tcpip_server_interface.rb +3 -1
- data/lib/openc3/io/json_api_object.rb +30 -9
- data/lib/openc3/io/json_drb.rb +6 -1
- data/lib/openc3/io/json_drb_object.rb +18 -9
- data/lib/openc3/io/json_rpc.rb +5 -3
- data/lib/openc3/logs/buffered_packet_log_writer.rb +1 -1
- data/lib/openc3/logs/log_writer.rb +8 -2
- data/lib/openc3/microservices/cleanup_microservice.rb +3 -3
- data/lib/openc3/microservices/decom_microservice.rb +8 -8
- data/lib/openc3/microservices/interface_microservice.rb +86 -71
- data/lib/openc3/microservices/log_microservice.rb +5 -3
- data/lib/openc3/microservices/microservice.rb +18 -14
- data/lib/openc3/microservices/multi_microservice.rb +62 -0
- data/lib/openc3/microservices/periodic_microservice.rb +58 -0
- data/lib/openc3/microservices/reaction_microservice.rb +61 -47
- data/lib/openc3/microservices/reducer_microservice.rb +64 -40
- data/lib/openc3/microservices/router_microservice.rb +4 -4
- data/lib/openc3/microservices/text_log_microservice.rb +2 -2
- data/lib/openc3/microservices/timeline_microservice.rb +44 -30
- data/lib/openc3/microservices/trigger_group_microservice.rb +39 -36
- data/lib/openc3/migrations/20221202214600_add_target_names.rb +30 -0
- data/lib/openc3/migrations/20221210174900_convert_to_multi.rb +65 -0
- data/lib/openc3/models/cvt_model.rb +1 -1
- data/lib/openc3/models/gem_model.rb +24 -20
- data/lib/openc3/models/interface_model.rb +69 -35
- data/lib/openc3/models/metadata_model.rb +1 -1
- data/lib/openc3/models/microservice_model.rb +7 -24
- data/lib/openc3/models/migration_model.rb +52 -0
- data/lib/openc3/models/model.rb +2 -7
- data/lib/openc3/models/note_model.rb +1 -1
- data/lib/openc3/models/offline_access_model.rb +55 -0
- data/lib/openc3/models/plugin_model.rb +12 -3
- data/lib/openc3/models/reaction_model.rb +6 -2
- data/lib/openc3/models/scope_model.rb +89 -13
- data/lib/openc3/models/settings_model.rb +1 -1
- data/lib/openc3/models/stash_model.rb +53 -0
- data/lib/openc3/models/target_model.rb +301 -130
- data/lib/openc3/models/tool_model.rb +1 -12
- data/lib/openc3/models/widget_model.rb +1 -6
- data/lib/openc3/operators/microservice_operator.rb +45 -6
- data/lib/openc3/operators/operator.rb +27 -5
- data/lib/openc3/packets/commands.rb +1 -25
- data/lib/openc3/packets/limits.rb +0 -75
- data/lib/openc3/packets/packet.rb +0 -28
- data/lib/openc3/packets/packet_item.rb +23 -0
- data/lib/openc3/packets/packet_item_limits.rb +2 -2
- data/lib/openc3/packets/parsers/state_parser.rb +10 -6
- data/lib/openc3/packets/telemetry.rb +1 -45
- data/lib/openc3/script/commands.rb +41 -71
- data/lib/openc3/script/extract.rb +15 -1
- data/lib/openc3/script/{calendar.rb → metadata.rb} +42 -17
- data/lib/openc3/script/script.rb +13 -5
- data/lib/openc3/script/storage.rb +3 -1
- data/lib/openc3/system/system.rb +19 -17
- data/lib/openc3/tools/cmd_tlm_server/interface_thread.rb +4 -4
- data/lib/openc3/top_level.rb +3 -3
- data/lib/openc3/topics/command_decom_topic.rb +2 -2
- data/lib/openc3/topics/command_topic.rb +7 -6
- data/lib/openc3/topics/interface_topic.rb +2 -2
- data/lib/openc3/topics/router_topic.rb +1 -1
- data/lib/openc3/topics/telemetry_topic.rb +2 -1
- data/lib/openc3/utilities/authentication.rb +35 -14
- data/lib/openc3/utilities/aws_bucket.rb +4 -3
- data/lib/openc3/utilities/bucket.rb +4 -2
- data/lib/openc3/utilities/bucket_file_cache.rb +3 -8
- data/lib/openc3/utilities/bucket_utilities.rb +77 -15
- data/lib/openc3/utilities/local_mode.rb +12 -9
- data/lib/openc3/utilities/logger.rb +17 -9
- data/lib/openc3/utilities/message_log.rb +6 -5
- data/lib/openc3/utilities/migration.rb +22 -0
- data/lib/openc3/utilities/store_autoload.rb +7 -5
- data/lib/openc3/utilities/target_file.rb +9 -7
- data/lib/openc3/version.rb +6 -6
- data/lib/openc3.rb +2 -1
- metadata +14 -3
|
@@ -33,17 +33,19 @@ require 'openc3/topics/router_topic'
|
|
|
33
33
|
|
|
34
34
|
module OpenC3
|
|
35
35
|
class InterfaceCmdHandlerThread
|
|
36
|
-
def initialize(interface, tlm, scope:)
|
|
36
|
+
def initialize(interface, tlm, logger: nil, scope:)
|
|
37
37
|
@interface = interface
|
|
38
38
|
@tlm = tlm
|
|
39
39
|
@scope = scope
|
|
40
|
+
@logger = logger
|
|
41
|
+
@logger = Logger unless @logger
|
|
40
42
|
end
|
|
41
43
|
|
|
42
44
|
def start
|
|
43
45
|
@thread = Thread.new do
|
|
44
46
|
run()
|
|
45
47
|
rescue Exception => err
|
|
46
|
-
|
|
48
|
+
@logger.error "#{@interface.name}: Command handler thread died: #{err.formatted}"
|
|
47
49
|
raise err
|
|
48
50
|
end
|
|
49
51
|
end
|
|
@@ -62,11 +64,11 @@ module OpenC3
|
|
|
62
64
|
# Check for a raw write to the interface
|
|
63
65
|
if topic =~ /CMD}INTERFACE/
|
|
64
66
|
if msg_hash['shutdown']
|
|
65
|
-
|
|
67
|
+
@logger.info "#{@interface.name}: Shutdown requested"
|
|
66
68
|
return
|
|
67
69
|
end
|
|
68
70
|
if msg_hash['connect']
|
|
69
|
-
|
|
71
|
+
@logger.info "#{@interface.name}: Connect requested"
|
|
70
72
|
params = []
|
|
71
73
|
if msg_hash['params']
|
|
72
74
|
params = JSON.parse(msg_hash['params'], :allow_nan => true, :create_additions => true)
|
|
@@ -75,28 +77,32 @@ module OpenC3
|
|
|
75
77
|
next 'SUCCESS'
|
|
76
78
|
end
|
|
77
79
|
if msg_hash['disconnect']
|
|
78
|
-
|
|
80
|
+
@logger.info "#{@interface.name}: Disconnect requested"
|
|
79
81
|
@tlm.disconnect(false)
|
|
80
82
|
next 'SUCCESS'
|
|
81
83
|
end
|
|
82
84
|
if msg_hash['raw']
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
85
|
+
if @interface.connected?
|
|
86
|
+
@logger.info "#{@interface.name}: Write raw"
|
|
87
|
+
# A raw interface write results in an UNKNOWN packet
|
|
88
|
+
command = System.commands.packet('UNKNOWN', 'UNKNOWN')
|
|
89
|
+
command.received_count += 1
|
|
90
|
+
command = command.clone
|
|
91
|
+
command.buffer = msg_hash['raw']
|
|
92
|
+
command.received_time = Time.now
|
|
93
|
+
CommandTopic.write_packet(command, scope: @scope)
|
|
94
|
+
@interface.write_raw(msg_hash['raw'])
|
|
95
|
+
next 'SUCCESS'
|
|
96
|
+
else
|
|
97
|
+
next "Interface not connected: #{@interface.name}"
|
|
98
|
+
end
|
|
93
99
|
end
|
|
94
100
|
if msg_hash.key?('log_raw')
|
|
95
101
|
if msg_hash['log_raw'] == 'true'
|
|
96
|
-
|
|
102
|
+
@logger.info "#{@interface.name}: Enable raw logging"
|
|
97
103
|
@interface.start_raw_logging
|
|
98
104
|
else
|
|
99
|
-
|
|
105
|
+
@logger.info "#{@interface.name}: Disable raw logging"
|
|
100
106
|
@interface.stop_raw_logging
|
|
101
107
|
end
|
|
102
108
|
next 'SUCCESS'
|
|
@@ -125,7 +131,7 @@ module OpenC3
|
|
|
125
131
|
if target_name
|
|
126
132
|
command = System.commands.identify(cmd_buffer, [target_name])
|
|
127
133
|
else
|
|
128
|
-
command = System.commands.identify(cmd_buffer, @
|
|
134
|
+
command = System.commands.identify(cmd_buffer, @cmd_target_names)
|
|
129
135
|
end
|
|
130
136
|
unless command
|
|
131
137
|
command = System.commands.packet('UNKNOWN', 'UNKNOWN')
|
|
@@ -138,8 +144,8 @@ module OpenC3
|
|
|
138
144
|
end
|
|
139
145
|
command.received_time = Time.now
|
|
140
146
|
rescue => e
|
|
141
|
-
|
|
142
|
-
|
|
147
|
+
@logger.error "#{@interface.name}: #{msg_hash}"
|
|
148
|
+
@logger.error "#{@interface.name}: #{e.formatted}"
|
|
143
149
|
next e.message
|
|
144
150
|
end
|
|
145
151
|
|
|
@@ -151,17 +157,21 @@ module OpenC3
|
|
|
151
157
|
end
|
|
152
158
|
|
|
153
159
|
begin
|
|
154
|
-
@interface.
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
160
|
+
if @interface.connected?
|
|
161
|
+
@interface.write(command)
|
|
162
|
+
CommandTopic.write_packet(command, scope: @scope)
|
|
163
|
+
CommandDecomTopic.write_packet(command, scope: @scope)
|
|
164
|
+
InterfaceStatusModel.set(@interface.as_json(:allow_nan => true), scope: @scope)
|
|
165
|
+
next 'SUCCESS'
|
|
166
|
+
else
|
|
167
|
+
next "Interface not connected: #{@interface.name}"
|
|
168
|
+
end
|
|
159
169
|
rescue => e
|
|
160
|
-
|
|
170
|
+
@logger.error "#{@interface.name}: #{e.formatted}"
|
|
161
171
|
next e.message
|
|
162
172
|
end
|
|
163
173
|
rescue => e
|
|
164
|
-
|
|
174
|
+
@logger.error "#{@interface.name}: #{e.formatted}"
|
|
165
175
|
next e.message
|
|
166
176
|
end
|
|
167
177
|
end
|
|
@@ -170,17 +180,19 @@ module OpenC3
|
|
|
170
180
|
end
|
|
171
181
|
|
|
172
182
|
class RouterTlmHandlerThread
|
|
173
|
-
def initialize(router, tlm, scope:)
|
|
183
|
+
def initialize(router, tlm, logger: nil, scope:)
|
|
174
184
|
@router = router
|
|
175
185
|
@tlm = tlm
|
|
176
186
|
@scope = scope
|
|
187
|
+
@logger = logger
|
|
188
|
+
@logger = Logger unless @logger
|
|
177
189
|
end
|
|
178
190
|
|
|
179
191
|
def start
|
|
180
192
|
@thread = Thread.new do
|
|
181
193
|
run()
|
|
182
194
|
rescue Exception => err
|
|
183
|
-
|
|
195
|
+
@logger.error "#{@router.name}: Telemetry handler thread died: #{err.formatted}"
|
|
184
196
|
raise err
|
|
185
197
|
end
|
|
186
198
|
end
|
|
@@ -198,11 +210,11 @@ module OpenC3
|
|
|
198
210
|
# Check for commands to the router itself
|
|
199
211
|
if /CMD}ROUTER/.match?(topic)
|
|
200
212
|
if msg_hash['shutdown']
|
|
201
|
-
|
|
213
|
+
@logger.info "#{@router.name}: Shutdown requested"
|
|
202
214
|
return
|
|
203
215
|
end
|
|
204
216
|
if msg_hash['connect']
|
|
205
|
-
|
|
217
|
+
@logger.info "#{@router.name}: Connect requested"
|
|
206
218
|
params = []
|
|
207
219
|
if msg_hash['params']
|
|
208
220
|
params = JSON.parse(msg_hash['params'], :allow_nan => true, :create_additions => true)
|
|
@@ -210,15 +222,15 @@ module OpenC3
|
|
|
210
222
|
@router = @tlm.attempting(*params)
|
|
211
223
|
end
|
|
212
224
|
if msg_hash['disconnect']
|
|
213
|
-
|
|
225
|
+
@logger.info "#{@router.name}: Disconnect requested"
|
|
214
226
|
@tlm.disconnect(false)
|
|
215
227
|
end
|
|
216
228
|
if msg_hash.key?('log_raw')
|
|
217
229
|
if msg_hash['log_raw'] == 'true'
|
|
218
|
-
|
|
230
|
+
@logger.info "#{@router.name}: Enable raw logging"
|
|
219
231
|
@router.start_raw_logging
|
|
220
232
|
else
|
|
221
|
-
|
|
233
|
+
@logger.info "#{@router.name}: Disable raw logging"
|
|
222
234
|
@router.stop_raw_logging
|
|
223
235
|
end
|
|
224
236
|
end
|
|
@@ -240,7 +252,7 @@ module OpenC3
|
|
|
240
252
|
RouterStatusModel.set(@router.as_json(:allow_nan => true), scope: @scope)
|
|
241
253
|
next 'SUCCESS'
|
|
242
254
|
rescue => e
|
|
243
|
-
|
|
255
|
+
@logger.error "#{@router.name}: #{e.formatted}"
|
|
244
256
|
next e.message
|
|
245
257
|
end
|
|
246
258
|
end
|
|
@@ -266,7 +278,8 @@ module OpenC3
|
|
|
266
278
|
@interface.target_names.each do |target_name|
|
|
267
279
|
target = System.targets[target_name]
|
|
268
280
|
target.interface = @interface
|
|
269
|
-
|
|
281
|
+
end
|
|
282
|
+
@interface.tlm_target_names.each do |target_name|
|
|
270
283
|
# Initialize the target's packet counters based on the Topic stream
|
|
271
284
|
# Prevents packet count resetting to 0 when interface restarts
|
|
272
285
|
System.telemetry.packets(target_name).each do |packet_name, packet|
|
|
@@ -296,9 +309,9 @@ module OpenC3
|
|
|
296
309
|
@connection_lost_messages = []
|
|
297
310
|
@mutex = Mutex.new
|
|
298
311
|
if @interface_or_router == 'INTERFACE'
|
|
299
|
-
@handler_thread = InterfaceCmdHandlerThread.new(@interface, self, scope: @scope)
|
|
312
|
+
@handler_thread = InterfaceCmdHandlerThread.new(@interface, self, logger: @logger, scope: @scope)
|
|
300
313
|
else
|
|
301
|
-
@handler_thread = RouterTlmHandlerThread.new(@interface, self, scope: @scope)
|
|
314
|
+
@handler_thread = RouterTlmHandlerThread.new(@interface, self, logger: @logger, scope: @scope)
|
|
302
315
|
end
|
|
303
316
|
@handler_thread.start
|
|
304
317
|
end
|
|
@@ -330,28 +343,28 @@ module OpenC3
|
|
|
330
343
|
@interface # Return the interface/router since we may have recreated it
|
|
331
344
|
# Need to rescue Exception so we cover LoadError
|
|
332
345
|
rescue Exception => error
|
|
333
|
-
|
|
346
|
+
@logger.error("Attempting connection failed with params #{params} due to #{error.message}")
|
|
347
|
+
if SignalException === error
|
|
348
|
+
@logger.info "#{@interface.name}: Closing from signal"
|
|
349
|
+
@cancel_thread = true
|
|
350
|
+
end
|
|
334
351
|
@interface # Return the original interface/router in case of error
|
|
335
352
|
end
|
|
336
353
|
|
|
337
354
|
def run
|
|
338
355
|
begin
|
|
339
356
|
if @interface.read_allowed?
|
|
340
|
-
|
|
357
|
+
@logger.info "#{@interface.name}: Starting packet reading"
|
|
341
358
|
else
|
|
342
|
-
|
|
359
|
+
@logger.info "#{@interface.name}: Starting connection maintenance"
|
|
343
360
|
end
|
|
344
361
|
while true
|
|
345
362
|
break if @cancel_thread
|
|
346
363
|
|
|
347
364
|
case @interface.state
|
|
348
365
|
when 'DISCONNECTED'
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
@interface_thread_sleeper.sleep(1)
|
|
352
|
-
rescue Exception => err
|
|
353
|
-
break if @cancel_thread
|
|
354
|
-
end
|
|
366
|
+
# Just wait to see if we should connect later
|
|
367
|
+
@interface_thread_sleeper.sleep(1)
|
|
355
368
|
when 'ATTEMPTING'
|
|
356
369
|
begin
|
|
357
370
|
@mutex.synchronize do
|
|
@@ -370,7 +383,7 @@ module OpenC3
|
|
|
370
383
|
handle_packet(packet)
|
|
371
384
|
@count += 1
|
|
372
385
|
else
|
|
373
|
-
|
|
386
|
+
@logger.info "#{@interface.name}: Internal disconnect requested (returned nil)"
|
|
374
387
|
handle_connection_lost()
|
|
375
388
|
break if @cancel_thread
|
|
376
389
|
end
|
|
@@ -385,8 +398,10 @@ module OpenC3
|
|
|
385
398
|
end
|
|
386
399
|
end
|
|
387
400
|
rescue Exception => error
|
|
388
|
-
|
|
389
|
-
|
|
401
|
+
unless SystemExit === error or SignalException === error
|
|
402
|
+
@logger.error "#{@interface.name}: Packet reading thread died: #{error.formatted}"
|
|
403
|
+
OpenC3.handle_fatal_exception(error)
|
|
404
|
+
end
|
|
390
405
|
# Try to do clean disconnect because we're going down
|
|
391
406
|
disconnect(false)
|
|
392
407
|
end
|
|
@@ -395,7 +410,7 @@ module OpenC3
|
|
|
395
410
|
else
|
|
396
411
|
RouterStatusModel.set(@interface.as_json(:allow_nan => true), scope: @scope)
|
|
397
412
|
end
|
|
398
|
-
|
|
413
|
+
@logger.info "#{@interface.name}: Stopped packet reading"
|
|
399
414
|
end
|
|
400
415
|
|
|
401
416
|
def handle_packet(packet)
|
|
@@ -404,7 +419,7 @@ module OpenC3
|
|
|
404
419
|
|
|
405
420
|
if packet.stored
|
|
406
421
|
# Stored telemetry does not update the current value table
|
|
407
|
-
identified_packet = System.telemetry.identify_and_define_packet(packet, @
|
|
422
|
+
identified_packet = System.telemetry.identify_and_define_packet(packet, @tlm_target_names)
|
|
408
423
|
else
|
|
409
424
|
# Identify and update packet
|
|
410
425
|
if packet.identified?
|
|
@@ -416,16 +431,16 @@ module OpenC3
|
|
|
416
431
|
rescue RuntimeError
|
|
417
432
|
# Packet identified but we don't know about it
|
|
418
433
|
# Clear packet_name and target_name and try to identify
|
|
419
|
-
|
|
434
|
+
@logger.warn "#{@interface.name}: Received unknown identified telemetry: #{packet.target_name} #{packet.packet_name}"
|
|
420
435
|
packet.target_name = nil
|
|
421
436
|
packet.packet_name = nil
|
|
422
437
|
identified_packet = System.telemetry.identify!(packet.buffer,
|
|
423
|
-
@
|
|
438
|
+
@tlm_target_names)
|
|
424
439
|
end
|
|
425
440
|
else
|
|
426
441
|
# Packet needs to be identified
|
|
427
442
|
identified_packet = System.telemetry.identify!(packet.buffer,
|
|
428
|
-
@
|
|
443
|
+
@tlm_target_names)
|
|
429
444
|
end
|
|
430
445
|
end
|
|
431
446
|
|
|
@@ -445,7 +460,7 @@ module OpenC3
|
|
|
445
460
|
num_bytes_to_print = [UNKNOWN_BYTES_TO_PRINT, packet.length].min
|
|
446
461
|
data = packet.buffer(false)[0..(num_bytes_to_print - 1)]
|
|
447
462
|
prefix = data.each_byte.map { | byte | sprintf("%02X", byte) }.join()
|
|
448
|
-
|
|
463
|
+
@logger.warn "#{@interface.name} #{packet.target_name} packet length: #{packet.length} starting with: #{prefix}"
|
|
449
464
|
end
|
|
450
465
|
|
|
451
466
|
# Write to stream
|
|
@@ -455,10 +470,10 @@ module OpenC3
|
|
|
455
470
|
|
|
456
471
|
def handle_connection_failed(connect_error)
|
|
457
472
|
@error = connect_error
|
|
458
|
-
|
|
473
|
+
@logger.error "#{@interface.name}: Connection Failed: #{connect_error.formatted(false, false)}"
|
|
459
474
|
case connect_error
|
|
460
|
-
when
|
|
461
|
-
|
|
475
|
+
when SignalException
|
|
476
|
+
@logger.info "#{@interface.name}: Closing from signal"
|
|
462
477
|
@cancel_thread = true
|
|
463
478
|
when Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::ETIMEDOUT, Errno::ENOTSOCK, Errno::EHOSTUNREACH, IOError
|
|
464
479
|
# Do not write an exception file for these extremely common cases
|
|
@@ -466,7 +481,7 @@ module OpenC3
|
|
|
466
481
|
if RuntimeError === connect_error and (connect_error.message =~ /canceled/ or connect_error.message =~ /timeout/)
|
|
467
482
|
# Do not write an exception file for these extremely common cases
|
|
468
483
|
else
|
|
469
|
-
|
|
484
|
+
@logger.error "#{@interface.name}: #{connect_error.formatted}"
|
|
470
485
|
unless @connection_failed_messages.include?(connect_error.message)
|
|
471
486
|
OpenC3.write_exception_file(connect_error)
|
|
472
487
|
@connection_failed_messages << connect_error.message
|
|
@@ -479,28 +494,28 @@ module OpenC3
|
|
|
479
494
|
def handle_connection_lost(err = nil, reconnect: true)
|
|
480
495
|
if err
|
|
481
496
|
@error = err
|
|
482
|
-
|
|
497
|
+
@logger.info "#{@interface.name}: Connection Lost: #{err.formatted(false, false)}"
|
|
483
498
|
case err
|
|
484
|
-
when
|
|
485
|
-
|
|
499
|
+
when SignalException
|
|
500
|
+
@logger.info "#{@interface.name}: Closing from signal"
|
|
486
501
|
@cancel_thread = true
|
|
487
502
|
when Errno::ECONNABORTED, Errno::ECONNRESET, Errno::ETIMEDOUT, Errno::EBADF, Errno::ENOTSOCK, IOError
|
|
488
503
|
# Do not write an exception file for these extremely common cases
|
|
489
504
|
else
|
|
490
|
-
|
|
505
|
+
@logger.error "#{@interface.name}: #{err.formatted}"
|
|
491
506
|
unless @connection_lost_messages.include?(err.message)
|
|
492
507
|
OpenC3.write_exception_file(err)
|
|
493
508
|
@connection_lost_messages << err.message
|
|
494
509
|
end
|
|
495
510
|
end
|
|
496
511
|
else
|
|
497
|
-
|
|
512
|
+
@logger.info "#{@interface.name}: Connection Lost"
|
|
498
513
|
end
|
|
499
514
|
disconnect(reconnect) # Ensure we do a clean disconnect
|
|
500
515
|
end
|
|
501
516
|
|
|
502
517
|
def connect
|
|
503
|
-
|
|
518
|
+
@logger.info "#{@interface.name}: Connecting ..."
|
|
504
519
|
@interface.connect
|
|
505
520
|
@interface.state = 'CONNECTED'
|
|
506
521
|
if @interface_or_router == 'INTERFACE'
|
|
@@ -508,7 +523,7 @@ module OpenC3
|
|
|
508
523
|
else
|
|
509
524
|
RouterStatusModel.set(@interface.as_json(:allow_nan => true), scope: @scope)
|
|
510
525
|
end
|
|
511
|
-
|
|
526
|
+
@logger.info "#{@interface.name}: Connection Success"
|
|
512
527
|
end
|
|
513
528
|
|
|
514
529
|
def disconnect(allow_reconnect = true)
|
|
@@ -521,7 +536,7 @@ module OpenC3
|
|
|
521
536
|
begin
|
|
522
537
|
@interface.disconnect if @interface.connected?
|
|
523
538
|
rescue => e
|
|
524
|
-
|
|
539
|
+
@logger.error "Disconnect: #{@interface.name}: #{e.formatted}"
|
|
525
540
|
end
|
|
526
541
|
end
|
|
527
542
|
|
|
@@ -530,7 +545,7 @@ module OpenC3
|
|
|
530
545
|
if allow_reconnect and @interface.auto_reconnect and @interface.state != 'DISCONNECTED'
|
|
531
546
|
attempting()
|
|
532
547
|
if !@cancel_thread
|
|
533
|
-
#
|
|
548
|
+
# @logger.debug "reconnect delay: #{@interface.reconnect_delay}"
|
|
534
549
|
@interface_thread_sleeper.sleep(@interface.reconnect_delay)
|
|
535
550
|
end
|
|
536
551
|
else
|
|
@@ -545,7 +560,7 @@ module OpenC3
|
|
|
545
560
|
|
|
546
561
|
# Disconnect from the interface and stop the thread
|
|
547
562
|
def stop
|
|
548
|
-
|
|
563
|
+
@logger.info "#{@interface.name}: stop requested"
|
|
549
564
|
@mutex.synchronize do
|
|
550
565
|
# Need to make sure that @cancel_thread is set and the interface disconnected within
|
|
551
566
|
# mutex to ensure that connect() is not called when we want to stop()
|
|
@@ -563,7 +578,7 @@ module OpenC3
|
|
|
563
578
|
end
|
|
564
579
|
|
|
565
580
|
def shutdown(sig = nil)
|
|
566
|
-
|
|
581
|
+
@logger.info "#{@interface.name}: shutdown requested"
|
|
567
582
|
stop()
|
|
568
583
|
super()
|
|
569
584
|
end
|
|
@@ -27,6 +27,8 @@ require 'openc3/config/config_parser'
|
|
|
27
27
|
|
|
28
28
|
module OpenC3
|
|
29
29
|
class LogMicroservice < Microservice
|
|
30
|
+
DEFAULT_BUFFER_DEPTH = 60 # 1 minutes at 1Hz
|
|
31
|
+
|
|
30
32
|
def initialize(name)
|
|
31
33
|
super(name)
|
|
32
34
|
@config['options'].each do |option|
|
|
@@ -42,7 +44,7 @@ module OpenC3
|
|
|
42
44
|
when 'BUFFER_DEPTH' # Buffer depth to write in time order
|
|
43
45
|
@buffer_depth = option[1].to_i
|
|
44
46
|
else
|
|
45
|
-
|
|
47
|
+
@logger.error("Unknown option passed to microservice #{@name}: #{option}")
|
|
46
48
|
end
|
|
47
49
|
end
|
|
48
50
|
|
|
@@ -52,7 +54,7 @@ module OpenC3
|
|
|
52
54
|
@cycle_time = 600 unless @cycle_time # 10 minutes
|
|
53
55
|
@cycle_size = 50_000_000 unless @cycle_size # ~50 MB
|
|
54
56
|
|
|
55
|
-
@buffer_depth =
|
|
57
|
+
@buffer_depth = DEFAULT_BUFFER_DEPTH unless @buffer_depth
|
|
56
58
|
end
|
|
57
59
|
|
|
58
60
|
def run
|
|
@@ -107,7 +109,7 @@ module OpenC3
|
|
|
107
109
|
@metric.add_sample(name: "log_duration_seconds", value: diff, labels: metric_labels)
|
|
108
110
|
rescue => err
|
|
109
111
|
@error = err
|
|
110
|
-
|
|
112
|
+
@logger.error("#{@name} error: #{err.formatted}")
|
|
111
113
|
end
|
|
112
114
|
|
|
113
115
|
def shutdown
|
|
@@ -42,21 +42,23 @@ module OpenC3
|
|
|
42
42
|
attr_accessor :error
|
|
43
43
|
attr_accessor :custom
|
|
44
44
|
attr_accessor :scope
|
|
45
|
+
attr_accessor :logger
|
|
45
46
|
|
|
46
|
-
def self.run
|
|
47
|
-
|
|
47
|
+
def self.run(name = nil)
|
|
48
|
+
name = ENV['OPENC3_MICROSERVICE_NAME'] unless name
|
|
49
|
+
microservice = self.new(name)
|
|
48
50
|
begin
|
|
49
51
|
MicroserviceStatusModel.set(microservice.as_json(:allow_nan => true), scope: microservice.scope)
|
|
50
52
|
microservice.state = 'RUNNING'
|
|
51
53
|
microservice.run
|
|
52
54
|
microservice.state = 'FINISHED'
|
|
53
55
|
rescue Exception => err
|
|
54
|
-
if
|
|
56
|
+
if SystemExit === err or SignalException === err
|
|
55
57
|
microservice.state = 'KILLED'
|
|
56
58
|
else
|
|
57
59
|
microservice.error = err
|
|
58
60
|
microservice.state = 'DIED_ERROR'
|
|
59
|
-
Logger.fatal("Microservice #{
|
|
61
|
+
Logger.fatal("Microservice #{name} dying from exception\n#{err.formatted}")
|
|
60
62
|
end
|
|
61
63
|
ensure
|
|
62
64
|
MicroserviceStatusModel.set(microservice.as_json(:allow_nan => true), scope: microservice.scope)
|
|
@@ -84,11 +86,13 @@ module OpenC3
|
|
|
84
86
|
|
|
85
87
|
@scope = split_name[0]
|
|
86
88
|
$openc3_scope = @scope
|
|
87
|
-
Logger.scope = @scope
|
|
88
89
|
@cancel_thread = false
|
|
89
90
|
@metric = Metric.new(microservice: @name, scope: @scope)
|
|
91
|
+
Logger.scope = @scope
|
|
90
92
|
Logger.microservice_name = @name
|
|
91
|
-
|
|
93
|
+
@logger = Logger.new
|
|
94
|
+
@logger.scope = @scope
|
|
95
|
+
@logger.microservice_name = @name
|
|
92
96
|
|
|
93
97
|
OpenC3.setup_open_telemetry(@name, false)
|
|
94
98
|
|
|
@@ -104,7 +108,7 @@ module OpenC3
|
|
|
104
108
|
@config = {}
|
|
105
109
|
@plugin = nil
|
|
106
110
|
end
|
|
107
|
-
|
|
111
|
+
@logger.info("Microservice initialized with config:\n#{@config}")
|
|
108
112
|
@topics ||= []
|
|
109
113
|
|
|
110
114
|
# Get configuration for any targets
|
|
@@ -122,9 +126,9 @@ module OpenC3
|
|
|
122
126
|
@custom = nil
|
|
123
127
|
@state = 'INITIALIZED'
|
|
124
128
|
metric_name = "metric_output_duration_seconds"
|
|
129
|
+
@work_dir = @config["work_dir"]
|
|
125
130
|
|
|
126
131
|
if is_plugin
|
|
127
|
-
@work_dir = @config["work_dir"]
|
|
128
132
|
cmd_array = @config["cmd"]
|
|
129
133
|
|
|
130
134
|
# Get Microservice files from bucket storage
|
|
@@ -160,12 +164,12 @@ module OpenC3
|
|
|
160
164
|
# Run ruby syntax so we can log those
|
|
161
165
|
syntax_check, _ = Open3.capture2e("ruby -c #{ruby_filename}")
|
|
162
166
|
if /Syntax OK/.match?(syntax_check)
|
|
163
|
-
|
|
167
|
+
@logger.info("Ruby microservice #{@name} file #{ruby_filename} passed syntax check\n", scope: @scope)
|
|
164
168
|
else
|
|
165
|
-
|
|
169
|
+
@logger.error("Ruby microservice #{@name} file #{ruby_filename} failed syntax check\n#{syntax_check}", scope: @scope)
|
|
166
170
|
end
|
|
167
171
|
else
|
|
168
|
-
|
|
172
|
+
@logger.error("Ruby microservice #{@name} file #{ruby_filename} does not exist", scope: @scope)
|
|
169
173
|
end
|
|
170
174
|
end
|
|
171
175
|
end
|
|
@@ -182,7 +186,7 @@ module OpenC3
|
|
|
182
186
|
break if @microservice_sleeper.sleep(@microservice_status_period_seconds)
|
|
183
187
|
end
|
|
184
188
|
rescue Exception => err
|
|
185
|
-
|
|
189
|
+
@logger.error "#{@name} status thread died: #{err.formatted}"
|
|
186
190
|
raise err
|
|
187
191
|
end
|
|
188
192
|
end
|
|
@@ -194,13 +198,13 @@ module OpenC3
|
|
|
194
198
|
end
|
|
195
199
|
|
|
196
200
|
def shutdown
|
|
197
|
-
|
|
201
|
+
@logger.info("Shutting down microservice: #{@name}")
|
|
198
202
|
@cancel_thread = true
|
|
199
203
|
@microservice_sleeper.cancel if @microservice_sleeper
|
|
200
204
|
MicroserviceStatusModel.set(as_json(:allow_nan => true), scope: @scope)
|
|
201
205
|
FileUtils.remove_entry(@temp_dir) if File.exist?(@temp_dir)
|
|
202
206
|
@metric.destroy
|
|
203
|
-
|
|
207
|
+
@logger.info("Shutting down microservice complete: #{@name}")
|
|
204
208
|
end
|
|
205
209
|
end
|
|
206
210
|
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# encoding: ascii-8bit
|
|
2
|
+
|
|
3
|
+
# Copyright 2022 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/microservices/microservice'
|
|
20
|
+
require 'openc3/topics/topic'
|
|
21
|
+
|
|
22
|
+
module OpenC3
|
|
23
|
+
class MultiMicroservice < Microservice
|
|
24
|
+
def run
|
|
25
|
+
@threads = []
|
|
26
|
+
ARGV.each do |microservice_name|
|
|
27
|
+
microservice_model = MicroserviceModel.get_model(name: microservice_name, scope: @scope)
|
|
28
|
+
@threads << Thread.new do
|
|
29
|
+
cmd_line = microservice_model.cmd.join(' ')
|
|
30
|
+
split_cmd_line = cmd_line.split(' ')
|
|
31
|
+
filename = nil
|
|
32
|
+
split_cmd_line.each do |item|
|
|
33
|
+
if File.extname(item) == '.rb'
|
|
34
|
+
filename = item
|
|
35
|
+
break
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
raise "Could not determine class filename from '#{cmd_line}'" unless filename
|
|
39
|
+
OpenC3.set_working_dir(@work_dir) do
|
|
40
|
+
require_relative filename
|
|
41
|
+
end
|
|
42
|
+
klass = filename.filename_to_class_name.to_class
|
|
43
|
+
klass.run(microservice_model.name)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
@threads.each do |thread|
|
|
47
|
+
thread.join
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def shutdown
|
|
52
|
+
super()
|
|
53
|
+
if @threads
|
|
54
|
+
@threads.each do |thread|
|
|
55
|
+
thread.join
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
OpenC3::MultiMicroservice.run if __FILE__ == $0
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# encoding: ascii-8bit
|
|
2
|
+
|
|
3
|
+
# Copyright 2022 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/microservices/microservice'
|
|
20
|
+
require 'openc3/models/offline_access_model'
|
|
21
|
+
|
|
22
|
+
module OpenC3
|
|
23
|
+
class PeriodicMicroservice < Microservice
|
|
24
|
+
STARTUP_DELAY_SECONDS = 2 * 60 # Two Minutes
|
|
25
|
+
SLEEP_PERIOD_SECONDS = 24 * 60 * 60 # Run once per day
|
|
26
|
+
|
|
27
|
+
def run
|
|
28
|
+
@run_sleeper = Sleeper.new
|
|
29
|
+
return if @run_sleeper.sleep(STARTUP_DELAY_SECONDS)
|
|
30
|
+
while true
|
|
31
|
+
models = OfflineAccessModel.get_all_models(scope: @scope)
|
|
32
|
+
models.each do |name, model|
|
|
33
|
+
if model.offline_access_token
|
|
34
|
+
auth = OpenC3KeycloakAuthentication.new(ENV['OPENC3_KEYCLOAK_URL'])
|
|
35
|
+
valid_token = auth.get_token_from_refresh_token(model.offline_access_token)
|
|
36
|
+
if valid_token
|
|
37
|
+
@logger.info("Refreshed offline access token for #{name}")
|
|
38
|
+
model.offline_access_token = auth.refresh_token
|
|
39
|
+
else
|
|
40
|
+
@logger.error("Unable to refresh offline access token for #{name}")
|
|
41
|
+
model.offline_access_token = nil
|
|
42
|
+
end
|
|
43
|
+
model.update
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
break if @cancel_thread
|
|
47
|
+
break if @run_sleeper.sleep(SLEEP_PERIOD_SECONDS)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def shutdown
|
|
52
|
+
@run_sleeper.cancel if @run_sleeper
|
|
53
|
+
super()
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
OpenC3::PeriodicMicroservice.run if __FILE__ == $0
|