Class: OpenC3::InterfaceModel

Inherits:
Model show all
Includes:
ConfigKeywordModel
Defined in:
lib/openc3/models/interface_model.rb

Direct Known Subclasses

RouterModel

Constant Summary collapse

INTERFACES_PRIMARY_KEY =
'openc3_interfaces'
ROUTERS_PRIMARY_KEY =
'openc3_routers'

Instance Attribute Summary collapse

Attributes inherited from Model

#name, #plugin, #scope, #updated_at

Class Method Summary collapse

Instance Method Summary collapse

Methods included from ConfigKeywordModel

#parse_port, #validate_secret

Methods inherited from Model

#check_disable_erb, #create, #destroy, #destroyed?, #diff, filter, find_all_by_plugin, from_json, get_all_models, get_model, set, store, store_queued, #update

Constructor Details

#initialize(name:, config_params: [], target_names: [], cmd_target_names: [], tlm_target_names: [], cmd_target_enabled: nil, tlm_target_enabled: nil, connect_on_startup: true, auto_reconnect: true, reconnect_delay: 5.0, disable_disconnect: false, options: [], secret_options: [], protocols: [], bridge_options: [], bridge_secret_options: [], bridge_protocols: [], log_stream: nil, updated_at: nil, plugin: nil, needs_dependencies: false, secrets: [], cmd: nil, work_dir: '/openc3/lib/openc3/microservices', ports: [], env: {}, container: nil, prefix: nil, shard: 0, db_shard: 0, bridge_name: nil, scope:) ⇒ InterfaceModel

Returns a new instance of InterfaceModel.



108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
# File 'lib/openc3/models/interface_model.rb', line 108

def initialize(
  name:,
  config_params: [],
  target_names: [],
  cmd_target_names: [],
  tlm_target_names: [],
  cmd_target_enabled: nil,
  tlm_target_enabled: nil,
  connect_on_startup: true,
  auto_reconnect: true,
  reconnect_delay: 5.0,
  disable_disconnect: false,
  options: [],
  secret_options: [],
  protocols: [],
  bridge_options: [],
  bridge_secret_options: [],
  bridge_protocols: [],
  log_stream: nil,
  updated_at: nil,
  plugin: nil,
  needs_dependencies: false,
  secrets: [],
  cmd: nil,
  work_dir: '/openc3/lib/openc3/microservices',
  ports: [],
  env: {},
  container: nil,
  prefix: nil,
  shard: 0,
  db_shard: 0,
  bridge_name: nil,
  scope:
)
  if self.class._get_type == 'INTERFACE'
    super("#{scope}__#{INTERFACES_PRIMARY_KEY}", name: name, updated_at: updated_at, plugin: plugin, scope: scope)
  else
    super("#{scope}__#{ROUTERS_PRIMARY_KEY}", name: name, updated_at: updated_at, plugin: plugin, scope: scope)
  end
  @config_params = config_params
  @target_names = target_names
  @cmd_target_names = cmd_target_names
  @tlm_target_names = tlm_target_names
  @cmd_target_enabled = cmd_target_enabled
  if @cmd_target_enabled.nil?
    @cmd_target_enabled = {}
    @cmd_target_names.each do |target_name|
      @cmd_target_enabled[target_name] = true
    end
    @cmd_target_enabled['UNKNOWN'] = true
  end
  @tlm_target_enabled = tlm_target_enabled
  if @tlm_target_enabled.nil?
    @tlm_target_enabled = {}
    @tlm_target_names.each do |target_name|
      @tlm_target_enabled[target_name] = true
    end
    @tlm_target_enabled['UNKNOWN'] = true
  end
  @connect_on_startup = connect_on_startup
  @auto_reconnect = auto_reconnect
  @reconnect_delay = reconnect_delay
  @disable_disconnect = disable_disconnect
  @options = options
  @secret_options = secret_options
  @bridge_options = bridge_options
  @bridge_secret_options = bridge_secret_options
  @protocols = protocols
  @bridge_protocols = bridge_protocols
  @log_stream = log_stream
  @needs_dependencies = needs_dependencies
  @cmd = cmd
  unless @cmd
    type = self.class._get_type
    microservice_name = "#{@scope}__#{type}__#{@name}"
    if config_params[0] and File.extname(config_params[0]) == '.py'
      work_dir.sub!('openc3/lib', 'openc3/python')
      # Use venv Python to ensure editable packages are found
      @cmd = [ENV['OPENC3_PYTHON_BIN'] || '/openc3/python/.venv/bin/python', "#{type.downcase}_microservice.py", microservice_name]
    else
      # If there are no config_params we assume ruby
      @cmd = ["ruby", "#{type.downcase}_microservice.rb", microservice_name]
    end
  end
  @work_dir = work_dir
  @ports = ports
  @env = env
  @container = container
  @prefix = prefix
  @shard = shard.to_i # to_i to handle nil
  @db_shard = db_shard.to_i # to_i to handle nil
  @bridge_name = bridge_name&.to_s&.upcase
  @secrets = secrets
end

Instance Attribute Details

#auto_reconnectObject

Returns the value of attribute auto_reconnect.



39
40
41
# File 'lib/openc3/models/interface_model.rb', line 39

def auto_reconnect
  @auto_reconnect
end

#bridge_nameObject

Returns the value of attribute bridge_name.



60
61
62
# File 'lib/openc3/models/interface_model.rb', line 60

def bridge_name
  @bridge_name
end

#bridge_optionsObject

Returns the value of attribute bridge_options.



44
45
46
# File 'lib/openc3/models/interface_model.rb', line 44

def bridge_options
  @bridge_options
end

#bridge_protocolsObject

Returns the value of attribute bridge_protocols.



47
48
49
# File 'lib/openc3/models/interface_model.rb', line 47

def bridge_protocols
  @bridge_protocols
end

#bridge_secret_optionsObject

Returns the value of attribute bridge_secret_options.



45
46
47
# File 'lib/openc3/models/interface_model.rb', line 45

def bridge_secret_options
  @bridge_secret_options
end

#cmdObject

Returns the value of attribute cmd.



52
53
54
# File 'lib/openc3/models/interface_model.rb', line 52

def cmd
  @cmd
end

#cmd_target_enabledObject

Returns the value of attribute cmd_target_enabled.



36
37
38
# File 'lib/openc3/models/interface_model.rb', line 36

def cmd_target_enabled
  @cmd_target_enabled
end

#cmd_target_namesObject

Returns the value of attribute cmd_target_names.



34
35
36
# File 'lib/openc3/models/interface_model.rb', line 34

def cmd_target_names
  @cmd_target_names
end

#config_paramsObject

Returns the value of attribute config_params.



32
33
34
# File 'lib/openc3/models/interface_model.rb', line 32

def config_params
  @config_params
end

#connect_on_startupObject

Returns the value of attribute connect_on_startup.



38
39
40
# File 'lib/openc3/models/interface_model.rb', line 38

def connect_on_startup
  @connect_on_startup
end

#containerObject

Returns the value of attribute container.



53
54
55
# File 'lib/openc3/models/interface_model.rb', line 53

def container
  @container
end

#db_shardObject

Returns the value of attribute db_shard.



59
60
61
# File 'lib/openc3/models/interface_model.rb', line 59

def db_shard
  @db_shard
end

#disable_disconnectObject

Returns the value of attribute disable_disconnect.



41
42
43
# File 'lib/openc3/models/interface_model.rb', line 41

def disable_disconnect
  @disable_disconnect
end

#envObject

Returns the value of attribute env.



54
55
56
# File 'lib/openc3/models/interface_model.rb', line 54

def env
  @env
end

#interfacesObject

Returns the value of attribute interfaces.



48
49
50
# File 'lib/openc3/models/interface_model.rb', line 48

def interfaces
  @interfaces
end

#log_streamObject

Returns the value of attribute log_stream.



49
50
51
# File 'lib/openc3/models/interface_model.rb', line 49

def log_stream
  @log_stream
end

#needs_dependenciesObject

Returns the value of attribute needs_dependencies.



50
51
52
# File 'lib/openc3/models/interface_model.rb', line 50

def needs_dependencies
  @needs_dependencies
end

#optionsObject

Returns the value of attribute options.



42
43
44
# File 'lib/openc3/models/interface_model.rb', line 42

def options
  @options
end

#portsObject

Returns the value of attribute ports.



56
57
58
# File 'lib/openc3/models/interface_model.rb', line 56

def ports
  @ports
end

#prefixObject

Returns the value of attribute prefix.



57
58
59
# File 'lib/openc3/models/interface_model.rb', line 57

def prefix
  @prefix
end

#protocolsObject

Returns the value of attribute protocols.



46
47
48
# File 'lib/openc3/models/interface_model.rb', line 46

def protocols
  @protocols
end

#reconnect_delayObject

Returns the value of attribute reconnect_delay.



40
41
42
# File 'lib/openc3/models/interface_model.rb', line 40

def reconnect_delay
  @reconnect_delay
end

#secret_optionsObject

Returns the value of attribute secret_options.



43
44
45
# File 'lib/openc3/models/interface_model.rb', line 43

def secret_options
  @secret_options
end

#secretsObject

Returns the value of attribute secrets.



51
52
53
# File 'lib/openc3/models/interface_model.rb', line 51

def secrets
  @secrets
end

#shardObject

Returns the value of attribute shard.



58
59
60
# File 'lib/openc3/models/interface_model.rb', line 58

def shard
  @shard
end

#target_namesObject

Redundant superset of cmd_target_names and tlm_target_names for backwards compat



33
34
35
# File 'lib/openc3/models/interface_model.rb', line 33

def target_names
  @target_names
end

#tlm_target_enabledObject

Returns the value of attribute tlm_target_enabled.



37
38
39
# File 'lib/openc3/models/interface_model.rb', line 37

def tlm_target_enabled
  @tlm_target_enabled
end

#tlm_target_namesObject

Returns the value of attribute tlm_target_names.



35
36
37
# File 'lib/openc3/models/interface_model.rb', line 35

def tlm_target_names
  @tlm_target_names
end

#work_dirObject

Returns the value of attribute work_dir.



55
56
57
# File 'lib/openc3/models/interface_model.rb', line 55

def work_dir
  @work_dir
end

Class Method Details

._get_keyObject

Helper method to return the correct primary key based on class name



96
97
98
99
100
101
102
103
104
105
106
# File 'lib/openc3/models/interface_model.rb', line 96

def self._get_key
  type = _get_type
  case type
  when 'INTERFACE'
    INTERFACES_PRIMARY_KEY
  when 'ROUTER'
    ROUTERS_PRIMARY_KEY
  else
    raise "Unknown type #{type} from class #{self.name}"
  end
end

._get_typeObject

Helper method to return the correct type based on class name



91
92
93
# File 'lib/openc3/models/interface_model.rb', line 91

def self._get_type
  self.name.to_s.split("Model")[0].upcase.split("::")[-1]
end

.all(scope:) ⇒ Object



72
73
74
# File 'lib/openc3/models/interface_model.rb', line 72

def self.all(scope:)
  super("#{scope}__#{_get_key}")
end

.get(name:, scope:) ⇒ Object

NOTE: The following three class methods are used by the ModelController and are reimplemented to enable various Model class methods to work



64
65
66
# File 'lib/openc3/models/interface_model.rb', line 64

def self.get(name:, scope:)
  super("#{scope}__#{_get_key}", name: name)
end

.handle_config(parser, keyword, parameters, plugin: nil, needs_dependencies: false, scope:) ⇒ Object

Called by the PluginModel to allow this class to validate it's top-level keyword: "INTERFACE" Interface/Router specific keywords are handled by the instance method "handle_config" NOTE: See RouterModel for the router method implementation



80
81
82
83
84
85
86
87
88
# File 'lib/openc3/models/interface_model.rb', line 80

def self.handle_config(parser, keyword, parameters, plugin: nil, needs_dependencies: false, scope:)
  case keyword
  when 'INTERFACE'
    parser.verify_num_parameters(2, nil, "INTERFACE <Name> <Filename> <Specific Parameters>")
    return self.new(name: parameters[0].upcase, config_params: parameters[1..-1], plugin: plugin, needs_dependencies: needs_dependencies, scope: scope)
  else
    raise ConfigParser::Error.new(parser, "Unknown keyword and parameters for Interface: #{keyword} #{parameters.join(" ")}")
  end
end

.names(scope:) ⇒ Object



68
69
70
# File 'lib/openc3/models/interface_model.rb', line 68

def self.names(scope:)
  super("#{scope}__#{_get_key}")
end

Instance Method Details

#as_json(*a) ⇒ Object



242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
# File 'lib/openc3/models/interface_model.rb', line 242

def as_json(*a)
  {
    'name' => @name,
    'config_params' => @config_params,
    'target_names' => @target_names,
    'cmd_target_names' => @cmd_target_names,
    'tlm_target_names' => @tlm_target_names,
    'cmd_target_enabled' => @cmd_target_enabled,
    'tlm_target_enabled' => @tlm_target_enabled,
    'connect_on_startup' => @connect_on_startup,
    'auto_reconnect' => @auto_reconnect,
    'reconnect_delay' => @reconnect_delay,
    'disable_disconnect' => @disable_disconnect,
    'options' => @options,
    'secret_options' => @secret_options,
    'protocols' => @protocols,
    'bridge_options' => @bridge_options,
    'bridge_secret_options' => @bridge_secret_options,
    'bridge_protocols' => @bridge_protocols,
    'log_stream' => @log_stream,
    'plugin' => @plugin,
    'needs_dependencies' => @needs_dependencies,
    'secrets' => @secrets.as_json(*a),
    'cmd' => @cmd,
    'work_dir' => @work_dir,
    'ports' => @ports,
    'env' => @env,
    'container' => @container,
    'prefix' => @prefix,
    'shard' => @shard,
    'db_shard' => @db_shard,
    'bridge_name' => @bridge_name,
    'updated_at' => @updated_at
  }
end

#bridge_relay_nameObject

Name of the shared bridge_microservice relay for a given bridge name.



547
548
549
# File 'lib/openc3/models/interface_model.rb', line 547

def bridge_relay_name
  "#{@scope}__BRIDGE__#{@bridge_name}"
end

#buildObject

Called by InterfaceMicroservice to instantiate the Interface defined by the model configuration. Must be called after get_model which calls from_json to instantiate the class and populate the attributes.



206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
# File 'lib/openc3/models/interface_model.rb', line 206

def build
  klass = OpenC3.require_class(@config_params[0])
  if @config_params.length > 1
    interface_or_router = klass.new(*@config_params[1..-1])
  else
    interface_or_router = klass.new
  end
  interface_or_router.secrets.setup(@secrets)
  interface_or_router.target_names = @target_names.dup
  interface_or_router.cmd_target_names = @cmd_target_names.dup
  interface_or_router.tlm_target_names = @tlm_target_names.dup
  interface_or_router.cmd_target_enabled = @cmd_target_enabled.dup
  interface_or_router.tlm_target_enabled = @tlm_target_enabled.dup
  interface_or_router.connect_on_startup = @connect_on_startup
  interface_or_router.auto_reconnect = @auto_reconnect
  interface_or_router.reconnect_delay = @reconnect_delay
  interface_or_router.disable_disconnect = @disable_disconnect
  @options.each do |option|
    interface_or_router.set_option(option[0], option[1..-1])
  end
  @secret_options.each do |option|
    secret_name = option[1]
    secret_value = interface_or_router.secrets.get(secret_name, scope: @scope)
    interface_or_router.set_option(option[0], [secret_value])
  end
  @protocols.each do |protocol|
    klass = OpenC3.require_class(protocol[1])
    interface_or_router.add_protocol(klass, protocol[2..-1], protocol[0].upcase.intern)
  end
  if @log_stream
    interface_or_router.stream_log_pair = StreamLogPair.new(interface_or_router.name, @log_stream)
    interface_or_router.start_raw_logging
  end
  interface_or_router
end

#check_bridge_relayObject

Verify the shared bridge_microservice relay for @bridge_name exists. Multiple bridged interfaces sharing a bridge name all route through the same relay. The relay discovers which streams to advertise by querying the HostInterfaceMicroserviceModels for its bridge (see bridge_microservice.py), so we deliberately do NOT push this interface's stream onto the relay as an OPTION: mutating (updating) the relay's MicroserviceModel would make the operator respawn the relay every time a bridged interface is added or removed. Leaving the model untouched lets the relay adapt live instead.



559
560
561
562
563
# File 'lib/openc3/models/interface_model.rb', line 559

def check_bridge_relay
  relay = MicroserviceModel.get_model(name: bridge_relay_name, scope: @scope)
  raise "Bridge #{@bridge_name} does not exist" unless relay
  relay
end

#create_host_interface_microserviceObject

Capture the real interface's spawn info for openc3-app. The host side does raw data transfer only, so protocols and target definitions are omitted; the connection options and secrets needed to open the device are included.



568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
# File 'lib/openc3/models/interface_model.rb', line 568

def create_host_interface_microservice
  host = HostInterfaceMicroserviceModel.new(
    name: @name,
    bridge_name: @bridge_name,
    stream: @name,
    config_params: @config_params,
    work_dir: @work_dir,
    env: @env,
    options: @bridge_options,
    secret_options: @bridge_secret_options,
    protocols: @bridge_protocols,
    secrets: @secrets,
    container: @container,
    needs_dependencies: @needs_dependencies,
    plugin: @plugin,
    scope: @scope
  )
  host.create(force: true)
  host
end

#deploy(gem_path, variables, validate_only: false) ⇒ Object

Creates a MicroserviceModel to deploy the Interface/Router



469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
# File 'lib/openc3/models/interface_model.rb', line 469

def deploy(gem_path, variables, validate_only: false)
  type = self.class._get_type
  microservice_name = "#{@scope}__#{type}__#{@name}"
  # A bridged interface runs on the host (openc3-app) rather than in Docker.
  # COSMOS runs a bridge_interface tunnel + a shared bridge_microservice relay
  # and hands the real interface's spawn info to openc3-app. See deploy_bridge.
  return deploy_bridge(gem_path, variables, microservice_name, type, validate_only: validate_only) if @bridge_name

  microservice = MicroserviceModel.new(
    name: microservice_name,
    work_dir: @work_dir,
    cmd: @cmd,
    env: @env,
    ports: @ports,
    container: @container,
    target_names: @target_names,
    plugin: @plugin,
    needs_dependencies: @needs_dependencies,
    secrets: @secrets,
    prefix: @prefix,
    shard: @shard,
    db_shard: @db_shard,
    scope: @scope
  )
  unless validate_only
    @target_names.each { |target_name| ensure_target_exists(target_name) }
    microservice.create
    microservice.deploy(gem_path, variables)
    ConfigTopic.write({ kind: 'created', type: type.downcase, name: @name, plugin: @plugin }, scope: @scope)
    Logger.info "Configured #{type.downcase} microservice #{microservice_name}"
  end
  microservice
end

#deploy_bridge(gem_path, variables, microservice_name, type, validate_only: false) ⇒ Object

Deploy a bridged interface. This splits the interface across the Docker side of COSMOS (bridge_interface tunnel + protocols/target processing) and the host side (openc3-app spawns the real interface for raw data transfer):

1. A COSMOS interface_microservice that builds a bridge_interface tunnel
 (InterfaceModel#build substitutes BridgeInterface when bridge_name is
 set) and connects to the named bridge_microservice relay.
2. A shared bridge_microservice relay (one per bridge_name) that this
 interface's stream (ALPN stream/<name>) is added to.
3. A HostInterfaceMicroserviceModel holding the real interface's spawn info for
 openc3-app to run the interface on the host.


513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
# File 'lib/openc3/models/interface_model.rb', line 513

def deploy_bridge(gem_path, variables, microservice_name, type, validate_only: false)
  # The COSMOS side runs the Python interface_microservice which builds a
  # BridgeInterface (see InterfaceModel#build). It keeps this interface's
  # protocols and target mapping; the raw device I/O is tunneled to the host.
  cmd = [BridgeModel.bridge_python_bin, "#{type.downcase}_microservice.py", microservice_name]
  microservice = MicroserviceModel.new(
    name: microservice_name,
    work_dir: BridgeModel::BRIDGE_WORK_DIR,
    cmd: cmd,
    env: @env,
    ports: @ports,
    container: @container,
    target_names: @target_names,
    plugin: @plugin,
    needs_dependencies: @needs_dependencies,
    secrets: @secrets,
    prefix: @prefix,
    shard: @shard,
    db_shard: @db_shard,
    scope: @scope
  )
  unless validate_only
    @target_names.each { |target_name| ensure_target_exists(target_name) }
    check_bridge_relay()
    microservice.create
    microservice.deploy(gem_path, variables)
    create_host_interface_microservice()
    ConfigTopic.write({ kind: 'created', type: type.downcase, name: @name, plugin: @plugin }, scope: @scope)
    Logger.info "Configured bridged #{type.downcase} microservice #{microservice_name} (bridge #{@bridge_name})"
  end
  microservice
end

#ensure_target_exists(target_name) ⇒ Object



278
279
280
281
282
# File 'lib/openc3/models/interface_model.rb', line 278

def ensure_target_exists(target_name)
  target = TargetModel.get(name: target_name, scope: @scope)
  raise "Target '#{target_name}' does not exist (InterfaceModel)" unless target
  target
end

#handle_config(parser, keyword, parameters) ⇒ Object

Handles Interface/Router specific configuration keywords



300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
# File 'lib/openc3/models/interface_model.rb', line 300

def handle_config(parser, keyword, parameters)
  type = self.class._get_type

  case keyword
  when 'MAP_TARGET'
    usage = "#{keyword} <Target Name> <ENABLED/DISABLED>"
    parser.verify_num_parameters(1, 2, usage)
    target_name = parameters[0].upcase
    enabled = handle_enabled(parser)

    if target_name != 'UNKNOWN'
      @target_names << target_name unless @target_names.include?(target_name)
      @cmd_target_names << target_name unless @cmd_target_names.include?(target_name)
      @tlm_target_names << target_name unless @tlm_target_names.include?(target_name)
    end
    @cmd_target_enabled[target_name] = enabled
    @tlm_target_enabled[target_name] = enabled

  when 'MAP_CMD_TARGET'
    parser.verify_num_parameters(1, 2, "#{keyword} <Target Name> <ENABLED/DISABLED>")
    target_name = parameters[0].upcase
    enabled = handle_enabled(parser)

    if target_name != 'UNKNOWN'
      @target_names << target_name unless @target_names.include?(target_name)
      @cmd_target_names << target_name unless @cmd_target_names.include?(target_name)
    end
    @cmd_target_enabled[target_name] = enabled

  when 'MAP_TLM_TARGET'
    parser.verify_num_parameters(1, 2, "#{keyword} <Target Name> <ENABLED/DISABLED>")
    target_name = parameters[0].upcase
    enabled = handle_enabled(parser)

    if target_name != 'UNKNOWN'
      @target_names << target_name unless @target_names.include?(target_name)
      @tlm_target_names << target_name unless @tlm_target_names.include?(target_name)
    end
    @tlm_target_enabled[target_name] = enabled

  when 'DONT_CONNECT'
    parser.verify_num_parameters(0, 0, "#{keyword}")
    @connect_on_startup = false

  when 'DONT_RECONNECT'
    parser.verify_num_parameters(0, 0, "#{keyword}")
    @auto_reconnect = false

  when 'RECONNECT_DELAY'
    parser.verify_num_parameters(1, 1, "#{keyword} <Delay in Seconds>")
    @reconnect_delay = Float(parameters[0])

  when 'DISABLE_DISCONNECT'
    parser.verify_num_parameters(0, 0, "#{keyword}")
    @disable_disconnect = true

  when 'OPTION'
    parser.verify_num_parameters(2, nil, "#{keyword} <Option Name> <Option Value 1> <Option Value 2 (optional)> <etc>")
    @options << parameters.dup

  when 'BRIDGE_OPTION'
    if type == 'INTERFACE'
      parser.verify_num_parameters(2, nil, "#{keyword} <Option Name> <Option Value 1> <Option Value 2 (optional)> <etc>")
      @bridge_options << parameters.dup
    else
      raise ConfigParser::Error.new(parser, "BRIDGE_OPTION not supported for Router: #{keyword} #{parameters.join(" ")}")
    end

  when 'PROTOCOL'
    usage = "#{keyword} <READ WRITE READ_WRITE> <protocol filename or classname> <Protocol specific parameters>"
    parser.verify_num_parameters(2, nil, usage)
    unless %w(READ WRITE READ_WRITE).include? parameters[0].upcase
      raise parser.error("Invalid protocol type: #{parameters[0]}", usage)
    end

    @protocols << parameters.dup

  when 'BRIDGE_PROTOCOL'
    if type == 'INTERFACE'
      usage = "#{keyword} <READ WRITE READ_WRITE> <protocol filename or classname> <Protocol specific parameters>"
      parser.verify_num_parameters(2, nil, usage)
      unless %w(READ WRITE READ_WRITE).include? parameters[0].upcase
        raise parser.error("Invalid protocol type: #{parameters[0]}", usage)
      end

      @bridge_protocols << parameters.dup
    else
      raise ConfigParser::Error.new(parser, "BRIDGE_PROTOCOL not supported for Router: #{keyword} #{parameters.join(" ")}")
    end

  when 'DONT_LOG'
    Logger.warn "DONT_LOG is deprecated and does nothing."

  when 'LOG_STREAM'
    parser.verify_num_parameters(0, nil, "#{keyword} <Log Stream Class File (optional)> <Log Stream Parameters (optional)>")
    @log_stream = parameters.dup # Even if it is empty we copy it to set it as not nil

  when 'SECRET'
    parser.verify_num_parameters(3, 5, "#{keyword} <Secret Type: ENV or FILE> <Secret Name> <Environment Variable Name or File Path> <Option Name (Optional)> <Secret Store Name (Optional)>")
    validate_secret(parser, keyword, parameters)
    @secrets << parameters[0..2]
    if ConfigParser.handle_nil(parameters[3])
      # Option Name, Secret Name
      @secret_options << [parameters[3], parameters[1]]
    end
    @secrets[-1] << ConfigParser.handle_nil(parameters[4])

  when 'BRIDGE_SECRET'
    if type == 'INTERFACE'
      parser.verify_num_parameters(3, 5, "#{keyword} <Secret Type: ENV or FILE> <Secret Name> <Environment Variable Name or File Path> <Option Name (Optional)> <Secret Store Name (Optional)>")
      validate_secret(parser, keyword, parameters)
      @secrets << parameters[0..2]
      if ConfigParser.handle_nil(parameters[3])
        # Option Name, Secret Name
        @bridge_secret_options << [parameters[3], parameters[1]]
      end
      @secrets[-1] << ConfigParser.handle_nil(parameters[4])
    else
      raise ConfigParser::Error.new(parser, "BRIDGE_SECRET not supported for Router: #{keyword} #{parameters.join(" ")}")
    end

  when 'ENV'
    parser.verify_num_parameters(2, 2, "#{keyword} <Key> <Value>")
    @env[parameters[0]] = parameters[1]

  when 'PORT'
    parse_port(parser, keyword, parameters)

  when 'WORK_DIR'
    parser.verify_num_parameters(1, 1, "#{keyword} <Dir>")
    @work_dir = parameters[0]

  when 'CMD'
    parser.verify_num_parameters(1, nil, "#{keyword} <Args>")
    @cmd = parameters.dup

  when 'CONTAINER'
    parser.verify_num_parameters(1, 1, "#{keyword} <Container Image Name>")
    @container = parameters[0]

  when 'ROUTE_PREFIX'
    parser.verify_num_parameters(1, 1, "#{keyword} <Route Prefix>")
    @prefix = parameters[0]

  when 'SHARD'
    parser.verify_num_parameters(1, 1, "#{keyword} <Shard Number Starting from 0>")
    @shard = Integer(parameters[0])

  when 'DB_SHARD'
    parser.verify_num_parameters(1, 1, "#{keyword} <DB_Shard Number Starting from 0>")
    @db_shard = Integer(parameters[0])

  when 'BRIDGE'
    if type == 'INTERFACE'
      parser.verify_num_parameters(1, 1, "#{keyword} <Bridge Name>")
      @bridge_name = parameters[0].to_s.upcase
    else
      raise ConfigParser::Error.new(parser, "BRIDGE not supported for Router: #{keyword} #{parameters.join(" ")}")
    end

  else
    raise ConfigParser::Error.new(parser, "Unknown keyword and parameters for Interface/Router: #{keyword} #{parameters.join(" ")}")

  end

  return nil
end

#handle_enabled(parser) ⇒ Object



284
285
286
287
288
289
290
291
292
293
294
295
296
297
# File 'lib/openc3/models/interface_model.rb', line 284

def handle_enabled(parser)
  if parser.parameters[1].nil?
    return true
  else
    enabled = parser.parameters[1].to_s.upcase
    if enabled == 'ENABLED'
      return true
    elsif enabled == 'DISABLED'
      return false
    else
      raise parser.error("MAP_TARGET enabled state must be ENABLED or DISABLED.", usage)
    end
  end
end

#map_target(target_name, cmd_only: false, tlm_only: false, unmap_old: true, cmd_enabled: true, tlm_enabled: true) ⇒ Object



663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
# File 'lib/openc3/models/interface_model.rb', line 663

def map_target(target_name, cmd_only: false, tlm_only: false, unmap_old: true, cmd_enabled: true, tlm_enabled: true)
  if cmd_only and tlm_only
    cmd_only = false
    tlm_only = false
  end
  target_name = target_name.to_s.upcase
  ensure_target_exists(target_name)

  if unmap_old
    # Remove from old interface
    all_interfaces = InterfaceModel.all(scope: @scope)
    old_interface = nil
    all_interfaces.each do |old_interface_name, old_interface_details|
      if old_interface_details['target_names'].include?(target_name)
        old_interface = InterfaceModel.from_json(old_interface_details, scope: @scope)
        old_interface.unmap_target(target_name, cmd_only: cmd_only, tlm_only: tlm_only) if old_interface
      end
    end
  end

  # Add to this interface
  @target_names << target_name unless @target_names.include?(target_name)
  @cmd_target_names << target_name unless @cmd_target_names.include?(target_name) or tlm_only
  @tlm_target_names << target_name unless @tlm_target_names.include?(target_name) or cmd_only
  @cmd_target_enabled[target_name] = cmd_enabled if not cmd_enabled.nil? and @cmd_target_names.include?(target_name)
  @tlm_target_enabled[target_name] = tlm_enabled if not tlm_enabled.nil? and @tlm_target_names.include?(target_name)
  update()

  # Respawn the microservice
  type = self.class._get_type
  microservice_name = "#{@scope}__#{type}__#{@name}"
  microservice = MicroserviceModel.get_model(name: microservice_name, scope: @scope)
  microservice.target_names << target_name unless microservice.target_names.include?(target_name)
  microservice.update
end

#undeployObject

Destroys the deployed MicroserviceModel which triggers the operator to kill the microservice. Also directly destroys the Interface/RouterStatusModel in an ensure block. The microservice stop() no longer recreates the status model once cancel_thread is set, so undeploy must clean it up itself to avoid leaving an orphaned status model behind.



594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
# File 'lib/openc3/models/interface_model.rb', line 594

def undeploy
  type = self.class._get_type
  name = "#{@scope}__#{type}__#{@name}"
  model = MicroserviceModel.get_model(name: name, scope: @scope)
  if model
    model.destroy
    ConfigTopic.write({ kind: 'deleted', type: type.downcase, name: @name, plugin: @plugin }, scope: @scope)
  end
rescue Exception => error
  Logger.error("Error undeploying interface/router model #{@name} in scope #{@scope} due to #{error}")
ensure
  begin
    if type == 'INTERFACE'
      status_model = InterfaceStatusModel.get_model(name: @name, scope: @scope)
    else
      status_model = RouterStatusModel.get_model(name: @name, scope: @scope)
    end
    status_model.destroy if status_model
  rescue Exception => error
    Logger.error("Error destroying #{type&.downcase || 'unknown'} status model #{@name} in scope #{@scope} due to #{error}")
  ensure
    undeploy_bridge if @bridge_name
  end
end

#undeploy_bridgeObject

Clean up the host-side pieces of a bridged interface by destroying its HostInterfaceMicroserviceModel. The shared bridge_microservice relay is left untouched: it discovers its streams by querying the HostInterfaceMicroserviceModels, so removing this one makes the relay drop the stream's ALPN on its own. (Mutating the relay's MicroserviceModel here would respawn it.) The relay itself is destroyed with its BridgeModel, not per-interface.



625
626
627
628
629
630
631
632
# File 'lib/openc3/models/interface_model.rb', line 625

def undeploy_bridge
  begin
    host = HostInterfaceMicroserviceModel.get_model(name: @name, scope: @scope)
    host.destroy if host
  rescue Exception => error
    Logger.error("Error destroying HostInterfaceMicroservice model #{@name} in scope #{@scope} due to #{error}")
  end
end

#unmap_target(target_name, cmd_only: false, tlm_only: false) ⇒ Object



634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
# File 'lib/openc3/models/interface_model.rb', line 634

def unmap_target(target_name, cmd_only: false, tlm_only: false)
  if cmd_only and tlm_only
    cmd_only = false
    tlm_only = false
  end
  target_name = target_name.to_s.upcase

  # Remove from this interface
  if cmd_only
    @cmd_target_names.delete(target_name)
    @target_names.delete(target_name) unless @tlm_target_names.include?(target_name)
  elsif tlm_only
    @tlm_target_names.delete(target_name)
    @target_names.delete(target_name) unless @cmd_target_names.include?(target_name)
  else
    @cmd_target_names.delete(target_name)
    @tlm_target_names.delete(target_name)
    @target_names.delete(target_name)
  end
  update()

  # Respawn the microservice
  type = self.class._get_type
  microservice_name = "#{@scope}__#{type}__#{@name}"
  microservice = MicroserviceModel.get_model(name: microservice_name, scope: @scope)
  microservice.target_names.delete(target_name) unless @target_names.include?(target_name)
  microservice.update
end