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.
Files changed (97) hide show
  1. checksums.yaml +4 -4
  2. data/bin/openc3cli +48 -9
  3. data/data/config/interface_modifiers.yaml +14 -0
  4. data/data/config/parameter_modifiers.yaml +5 -3
  5. data/data/config/screen.yaml +12 -8
  6. data/data/config/target.yaml +33 -0
  7. data/ext/openc3/ext/config_parser/config_parser.c +66 -63
  8. data/ext/openc3/ext/packet/packet.c +1 -4
  9. data/lib/openc3/api/README.md +5 -0
  10. data/lib/openc3/api/api.rb +3 -1
  11. data/lib/openc3/api/cmd_api.rb +43 -112
  12. data/lib/openc3/api/interface_api.rb +3 -3
  13. data/lib/openc3/api/offline_access_api.rb +78 -0
  14. data/lib/openc3/api/settings_api.rb +3 -1
  15. data/lib/openc3/api/stash_api.rb +63 -0
  16. data/lib/openc3/api/target_api.rb +4 -5
  17. data/lib/openc3/config/config_parser.rb +47 -47
  18. data/lib/openc3/interfaces/interface.rb +11 -1
  19. data/lib/openc3/interfaces/protocols/burst_protocol.rb +30 -16
  20. data/lib/openc3/interfaces/protocols/fixed_protocol.rb +8 -2
  21. data/lib/openc3/interfaces/protocols/ignore_packet_protocol.rb +2 -2
  22. data/lib/openc3/interfaces/protocols/override_protocol.rb +2 -2
  23. data/lib/openc3/interfaces/tcpip_server_interface.rb +3 -1
  24. data/lib/openc3/io/json_api_object.rb +30 -9
  25. data/lib/openc3/io/json_drb.rb +6 -1
  26. data/lib/openc3/io/json_drb_object.rb +18 -9
  27. data/lib/openc3/io/json_rpc.rb +5 -3
  28. data/lib/openc3/logs/buffered_packet_log_writer.rb +1 -1
  29. data/lib/openc3/logs/log_writer.rb +8 -2
  30. data/lib/openc3/microservices/cleanup_microservice.rb +3 -3
  31. data/lib/openc3/microservices/decom_microservice.rb +8 -8
  32. data/lib/openc3/microservices/interface_microservice.rb +86 -71
  33. data/lib/openc3/microservices/log_microservice.rb +5 -3
  34. data/lib/openc3/microservices/microservice.rb +18 -14
  35. data/lib/openc3/microservices/multi_microservice.rb +62 -0
  36. data/lib/openc3/microservices/periodic_microservice.rb +58 -0
  37. data/lib/openc3/microservices/reaction_microservice.rb +61 -47
  38. data/lib/openc3/microservices/reducer_microservice.rb +64 -40
  39. data/lib/openc3/microservices/router_microservice.rb +4 -4
  40. data/lib/openc3/microservices/text_log_microservice.rb +2 -2
  41. data/lib/openc3/microservices/timeline_microservice.rb +44 -30
  42. data/lib/openc3/microservices/trigger_group_microservice.rb +39 -36
  43. data/lib/openc3/migrations/20221202214600_add_target_names.rb +30 -0
  44. data/lib/openc3/migrations/20221210174900_convert_to_multi.rb +65 -0
  45. data/lib/openc3/models/cvt_model.rb +1 -1
  46. data/lib/openc3/models/gem_model.rb +24 -20
  47. data/lib/openc3/models/interface_model.rb +69 -35
  48. data/lib/openc3/models/metadata_model.rb +1 -1
  49. data/lib/openc3/models/microservice_model.rb +7 -24
  50. data/lib/openc3/models/migration_model.rb +52 -0
  51. data/lib/openc3/models/model.rb +2 -7
  52. data/lib/openc3/models/note_model.rb +1 -1
  53. data/lib/openc3/models/offline_access_model.rb +55 -0
  54. data/lib/openc3/models/plugin_model.rb +12 -3
  55. data/lib/openc3/models/reaction_model.rb +6 -2
  56. data/lib/openc3/models/scope_model.rb +89 -13
  57. data/lib/openc3/models/settings_model.rb +1 -1
  58. data/lib/openc3/models/stash_model.rb +53 -0
  59. data/lib/openc3/models/target_model.rb +301 -130
  60. data/lib/openc3/models/tool_model.rb +1 -12
  61. data/lib/openc3/models/widget_model.rb +1 -6
  62. data/lib/openc3/operators/microservice_operator.rb +45 -6
  63. data/lib/openc3/operators/operator.rb +27 -5
  64. data/lib/openc3/packets/commands.rb +1 -25
  65. data/lib/openc3/packets/limits.rb +0 -75
  66. data/lib/openc3/packets/packet.rb +0 -28
  67. data/lib/openc3/packets/packet_item.rb +23 -0
  68. data/lib/openc3/packets/packet_item_limits.rb +2 -2
  69. data/lib/openc3/packets/parsers/state_parser.rb +10 -6
  70. data/lib/openc3/packets/telemetry.rb +1 -45
  71. data/lib/openc3/script/commands.rb +41 -71
  72. data/lib/openc3/script/extract.rb +15 -1
  73. data/lib/openc3/script/{calendar.rb → metadata.rb} +42 -17
  74. data/lib/openc3/script/script.rb +13 -5
  75. data/lib/openc3/script/storage.rb +3 -1
  76. data/lib/openc3/system/system.rb +19 -17
  77. data/lib/openc3/tools/cmd_tlm_server/interface_thread.rb +4 -4
  78. data/lib/openc3/top_level.rb +3 -3
  79. data/lib/openc3/topics/command_decom_topic.rb +2 -2
  80. data/lib/openc3/topics/command_topic.rb +7 -6
  81. data/lib/openc3/topics/interface_topic.rb +2 -2
  82. data/lib/openc3/topics/router_topic.rb +1 -1
  83. data/lib/openc3/topics/telemetry_topic.rb +2 -1
  84. data/lib/openc3/utilities/authentication.rb +35 -14
  85. data/lib/openc3/utilities/aws_bucket.rb +4 -3
  86. data/lib/openc3/utilities/bucket.rb +4 -2
  87. data/lib/openc3/utilities/bucket_file_cache.rb +3 -8
  88. data/lib/openc3/utilities/bucket_utilities.rb +77 -15
  89. data/lib/openc3/utilities/local_mode.rb +12 -9
  90. data/lib/openc3/utilities/logger.rb +17 -9
  91. data/lib/openc3/utilities/message_log.rb +6 -5
  92. data/lib/openc3/utilities/migration.rb +22 -0
  93. data/lib/openc3/utilities/store_autoload.rb +7 -5
  94. data/lib/openc3/utilities/target_file.rb +9 -7
  95. data/lib/openc3/version.rb +6 -6
  96. data/lib/openc3.rb +2 -1
  97. 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
- Logger.error "#{@interface.name}: Command handler thread died: #{err.formatted}"
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
- Logger.info "#{@interface.name}: Shutdown requested"
67
+ @logger.info "#{@interface.name}: Shutdown requested"
66
68
  return
67
69
  end
68
70
  if msg_hash['connect']
69
- Logger.info "#{@interface.name}: Connect requested"
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
- Logger.info "#{@interface.name}: Disconnect requested"
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
- Logger.info "#{@interface.name}: Write raw"
84
- # A raw interface write results in an UNKNOWN packet
85
- command = System.commands.packet('UNKNOWN', 'UNKNOWN')
86
- command.received_count += 1
87
- command = command.clone
88
- command.buffer = msg_hash['raw']
89
- command.received_time = Time.now
90
- CommandTopic.write_packet(command, scope: @scope)
91
- @interface.write_raw(msg_hash['raw'])
92
- next 'SUCCESS'
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
- Logger.info "#{@interface.name}: Enable raw logging"
102
+ @logger.info "#{@interface.name}: Enable raw logging"
97
103
  @interface.start_raw_logging
98
104
  else
99
- Logger.info "#{@interface.name}: Disable raw logging"
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, @target_names)
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
- Logger.error "#{@interface.name}: #{msg_hash}"
142
- Logger.error "#{@interface.name}: #{e.formatted}"
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.write(command)
155
- CommandTopic.write_packet(command, scope: @scope)
156
- CommandDecomTopic.write_packet(command, scope: @scope)
157
- InterfaceStatusModel.set(@interface.as_json(:allow_nan => true), scope: @scope)
158
- next 'SUCCESS'
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
- Logger.error "#{@interface.name}: #{e.formatted}"
170
+ @logger.error "#{@interface.name}: #{e.formatted}"
161
171
  next e.message
162
172
  end
163
173
  rescue => e
164
- Logger.error "#{@interface.name}: #{e.formatted}"
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
- Logger.error "#{@router.name}: Telemetry handler thread died: #{err.formatted}"
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
- Logger.info "#{@router.name}: Shutdown requested"
213
+ @logger.info "#{@router.name}: Shutdown requested"
202
214
  return
203
215
  end
204
216
  if msg_hash['connect']
205
- Logger.info "#{@router.name}: Connect requested"
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
- Logger.info "#{@router.name}: Disconnect requested"
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
- Logger.info "#{@router.name}: Enable raw logging"
230
+ @logger.info "#{@router.name}: Enable raw logging"
219
231
  @router.start_raw_logging
220
232
  else
221
- Logger.info "#{@router.name}: Disable raw logging"
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
- Logger.error "#{@router.name}: #{e.formatted}"
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
- Logger.error("Attempting connection failed with params #{params} due to #{error.message}")
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
- Logger.info "#{@interface.name}: Starting packet reading"
357
+ @logger.info "#{@interface.name}: Starting packet reading"
341
358
  else
342
- Logger.info "#{@interface.name}: Starting connection maintenance"
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
- begin
350
- # Just wait to see if we should connect later
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
- Logger.info "#{@interface.name}: Internal disconnect requested (returned nil)"
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
- Logger.error "#{@interface.name}: Packet reading thread died: #{error.formatted}"
389
- OpenC3.handle_fatal_exception(error)
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
- Logger.info "#{@interface.name}: Stopped packet reading"
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, @target_names)
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
- Logger.warn "#{@interface.name}: Received unknown identified telemetry: #{packet.target_name} #{packet.packet_name}"
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
- @target_names)
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
- @target_names)
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
- Logger.warn "#{@interface.name} #{packet.target_name} packet length: #{packet.length} starting with: #{prefix}"
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
- Logger.error "#{@interface.name}: Connection Failed: #{connect_error.formatted(false, false)}"
473
+ @logger.error "#{@interface.name}: Connection Failed: #{connect_error.formatted(false, false)}"
459
474
  case connect_error
460
- when Interrupt
461
- Logger.info "#{@interface.name}: Closing from signal"
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
- Logger.error "#{@interface.name}: #{connect_error.formatted}"
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
- Logger.info "#{@interface.name}: Connection Lost: #{err.formatted(false, false)}"
497
+ @logger.info "#{@interface.name}: Connection Lost: #{err.formatted(false, false)}"
483
498
  case err
484
- when Interrupt
485
- Logger.info "#{@interface.name}: Closing from signal"
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
- Logger.error "#{@interface.name}: #{err.formatted}"
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
- Logger.info "#{@interface.name}: Connection Lost"
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
- Logger.info "#{@interface.name}: Connecting ..."
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
- Logger.info "#{@interface.name}: Connection Success"
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
- Logger.error "Disconnect: #{@interface.name}: #{e.formatted}"
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
- # Logger.debug "reconnect delay: #{@interface.reconnect_delay}"
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
- Logger.info "#{@interface.name}: stop requested"
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
- Logger.info "#{@interface.name}: shutdown requested"
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
- Logger.error("Unknown option passed to microservice #{@name}: #{option}")
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 = 10 unless @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
- Logger.error("#{@name} error: #{err.formatted}")
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
- microservice = self.new(ENV['OPENC3_MICROSERVICE_NAME'])
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 err.class == SystemExit or err.class == Interrupt
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 #{ENV['OPENC3_MICROSERVICE_NAME']} dying from exception\n#{err.formatted}")
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
- Logger.tag = @name + "__openc3.log"
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
- Logger.info("Microservice initialized with config:\n#{@config}")
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
- Logger.info("Ruby microservice #{@name} file #{ruby_filename} passed syntax check\n", scope: @scope)
167
+ @logger.info("Ruby microservice #{@name} file #{ruby_filename} passed syntax check\n", scope: @scope)
164
168
  else
165
- Logger.error("Ruby microservice #{@name} file #{ruby_filename} failed syntax check\n#{syntax_check}", scope: @scope)
169
+ @logger.error("Ruby microservice #{@name} file #{ruby_filename} failed syntax check\n#{syntax_check}", scope: @scope)
166
170
  end
167
171
  else
168
- Logger.error("Ruby microservice #{@name} file #{ruby_filename} does not exist", scope: @scope)
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
- Logger.error "#{@name} status thread died: #{err.formatted}"
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
- Logger.info("Shutting down microservice: #{@name}")
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
- Logger.info("Shutting down microservice complete: #{@name}")
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