Class: OpenC3::ReactionModel

Inherits:
Model show all
Defined in:
lib/openc3/models/reaction_model.rb

Constant Summary collapse

PRIMARY_KEY =
'__openc3__reaction'.freeze
SCRIPT_REACTION =
'script'.freeze
COMMAND_REACTION =
'command'.freeze
NOTIFY_REACTION =
'notify'.freeze
ACTION_TYPES =
[SCRIPT_REACTION, COMMAND_REACTION, NOTIFY_REACTION]

Instance Attribute Summary collapse

Attributes inherited from Model

#plugin, #updated_at

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Model

#check_disable_erb, #destroy, #destroyed?, filter, find_all_by_plugin, get_all_models, get_model, handle_config, set, store, store_queued

Constructor Details

#initialize(name:, scope:, snooze:, actions:, triggers:, triggerLevel:, enabled: true, snoozed_until: nil, username: nil, updated_at: nil) ⇒ ReactionModel

Returns a new instance of ReactionModel.



87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/openc3/models/reaction_model.rb', line 87

def initialize(
  name:,
  scope:,
  snooze:,
  actions:,
  triggers:,
  triggerLevel:,
  enabled: true,
  snoozed_until: nil,
  username: nil,
  updated_at: nil
)
  super("#{scope}#{PRIMARY_KEY}", name: name, scope: scope)
  @microservice_name = "#{scope}__OPENC3__REACTION"
  @enabled = enabled
  @snoozed_until = snoozed_until
  @triggerLevel = validate_level(triggerLevel)
  @snooze = validate_snooze(snooze)
  @actions = validate_actions(actions)
  @triggers = validate_triggers(triggers)
  @username = username
  @updated_at = updated_at
end

Instance Attribute Details

#actionsObject

Returns the value of attribute actions.



84
85
86
# File 'lib/openc3/models/reaction_model.rb', line 84

def actions
  @actions
end

#enabledObject (readonly)

Returns the value of attribute enabled.



84
85
86
# File 'lib/openc3/models/reaction_model.rb', line 84

def enabled
  @enabled
end

#nameObject (readonly)

Returns the value of attribute name.



84
85
86
# File 'lib/openc3/models/reaction_model.rb', line 84

def name
  @name
end

#scopeObject (readonly)

Returns the value of attribute scope.



84
85
86
# File 'lib/openc3/models/reaction_model.rb', line 84

def scope
  @scope
end

#snoozeObject

Returns the value of attribute snooze.



84
85
86
# File 'lib/openc3/models/reaction_model.rb', line 84

def snooze
  @snooze
end

#snoozed_untilObject (readonly)

Returns the value of attribute snoozed_until.



84
85
86
# File 'lib/openc3/models/reaction_model.rb', line 84

def snoozed_until
  @snoozed_until
end

#triggerLevelObject

Returns the value of attribute triggerLevel.



84
85
86
# File 'lib/openc3/models/reaction_model.rb', line 84

def triggerLevel
  @triggerLevel
end

#triggersObject

Returns the value of attribute triggers.



84
85
86
# File 'lib/openc3/models/reaction_model.rb', line 84

def triggers
  @triggers
end

#usernameObject

Returns the value of attribute username.



85
86
87
# File 'lib/openc3/models/reaction_model.rb', line 85

def username
  @username
end

Class Method Details

.all(scope:) ⇒ Array<Hash>

Returns All the Key, Values stored under the name key.

Returns:

  • (Array<Hash>)

    All the Key, Values stored under the name key



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

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

.create_unique_name(scope:) ⇒ Object



39
40
41
42
43
44
45
46
# File 'lib/openc3/models/reaction_model.rb', line 39

def self.create_unique_name(scope:)
  reaction_names = self.names(scope: scope) # comes back sorted
  num = 1 # Users count with 1
  if reaction_names[-1]
    num = reaction_names[-1][5..-1].to_i + 1
  end
  return "REACT#{num}"
end

.delete(name:, scope:) ⇒ Object

Check dependents before delete.



67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/openc3/models/reaction_model.rb', line 67

def self.delete(name:, scope:)
  model = self.get(name: name, scope: scope)
  if model.nil?
    raise ReactionInputError.new "reaction '#{name}' does not exist"
  end
  model.triggers.each do | trigger |
    trigger_model = TriggerModel.get(name: trigger['name'], group: trigger['group'], scope: scope)
    trigger_model.update_dependents(dependent: name, remove: true)
    trigger_model.update()
  end
  Store.hdel("#{scope}#{PRIMARY_KEY}", name)
  # No notification as this is only called via reaction_controller which already notifies

  # undeploy only actually runs if no reactions are left
  model.undeploy()
end

.from_json(json, name:, scope:) ⇒ ReactionModel

Returns Model generated from the passed JSON.

Returns:



271
272
273
274
275
# File 'lib/openc3/models/reaction_model.rb', line 271

def self.from_json(json, name:, scope:)
  json = JSON.parse(json, :allow_nan => true, :create_additions => true) if String === json
  raise "json data is nil" if json.nil?
  self.new(**json.transform_keys(&:to_sym), name: name, scope: scope)
end

.get(name:, scope:) ⇒ ReactionModel

Return the object with the name at

Returns:



49
50
51
52
53
54
# File 'lib/openc3/models/reaction_model.rb', line 49

def self.get(name:, scope:)
  json = super("#{scope}#{PRIMARY_KEY}", name: name)
  unless json.nil?
    self.from_json(json, name: name, scope: scope)
  end
end

.names(scope:) ⇒ Array<String>

Returns All the uuids stored under the name key.

Returns:

  • (Array<String>)

    All the uuids stored under the name key



62
63
64
# File 'lib/openc3/models/reaction_model.rb', line 62

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

Instance Method Details

#as_json(*a) ⇒ Hash

Returns generated from the ReactionModel.

Returns:

  • (Hash)

    generated from the ReactionModel



255
256
257
258
259
260
261
262
263
264
265
266
267
268
# File 'lib/openc3/models/reaction_model.rb', line 255

def as_json(*a)
  return {
    'name' => @name,
    'scope' => @scope,
    'enabled' => @enabled,
    'triggerLevel' => @triggerLevel,
    'snooze' => @snooze,
    'snoozed_until' => @snoozed_until,
    'triggers' => @triggers,
    'actions' => @actions,
    'username' => @username,
    'updated_at' => @updated_at
  }
end

#awakenObject



247
248
249
250
251
252
# File 'lib/openc3/models/reaction_model.rb', line 247

def awaken
  @snoozed_until = nil
  @updated_at = Time.now.to_nsec_from_epoch
  Store.hset(@primary_key, @name, JSON.generate(as_json(:allow_nan => true)))
  notify(kind: 'awakened')
end

#createObject



201
202
203
204
205
206
207
208
209
# File 'lib/openc3/models/reaction_model.rb', line 201

def create
  unless Store.hget(@primary_key, @name).nil?
    raise ReactionInputError.new "existing reaction found: #{@name}"
  end
  verify_triggers()
  @updated_at = Time.now.to_nsec_from_epoch
  Store.hset(@primary_key, @name, JSON.generate(as_json(:allow_nan => true)))
  notify(kind: 'created')
end

#create_microservice(topics:) ⇒ Object



287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
# File 'lib/openc3/models/reaction_model.rb', line 287

def create_microservice(topics:)
  # reaction Microservice
  microservice = MicroserviceModel.new(
    name: @microservice_name,
    folder_name: nil,
    cmd: ['ruby', 'reaction_microservice.rb', @microservice_name],
    work_dir: '/openc3/lib/openc3/microservices',
    options: [],
    topics: topics,
    target_names: [],
    plugin: nil,
    scope: @scope
  )
  microservice.create
end

#deployObject



303
304
305
306
307
308
# File 'lib/openc3/models/reaction_model.rb', line 303

def deploy
  topics = ["#{@scope}__openc3_autonomic"]
  if MicroserviceModel.get_model(name: @microservice_name, scope: @scope).nil?
    create_microservice(topics: topics)
  end
end

#notify(kind:) ⇒ Object

Returns [] update the redis stream / reaction topic that something has changed.

Returns:

  • update the redis stream / reaction topic that something has changed



278
279
280
281
282
283
284
285
# File 'lib/openc3/models/reaction_model.rb', line 278

def notify(kind:)
  notification = {
    'kind' => kind,
    'type' => 'reaction',
    'data' => JSON.generate(as_json(:allow_nan => true)),
  }
  AutonomicTopic.write_notification(notification, scope: @scope)
end

#notify_disableObject



224
225
226
227
228
229
230
# File 'lib/openc3/models/reaction_model.rb', line 224

def notify_disable
  @enabled = false
  # disabling clears the snooze so when it's enabled it can immediately run
  @snoozed_until = nil
  notify(kind: 'disabled')
  # update() will be called by the reaction_microservice
end

#notify_enableObject



218
219
220
221
222
# File 'lib/openc3/models/reaction_model.rb', line 218

def notify_enable
  @enabled = true
  notify(kind: 'enabled')
  # update() will be called by the reaction_microservice
end

#notify_executeObject



232
233
234
235
236
# File 'lib/openc3/models/reaction_model.rb', line 232

def notify_execute
  # Set updated_at because the event is all we get ... no update later
  @updated_at = Time.now.to_nsec_from_epoch
  notify(kind: 'executed')
end

#sleepObject



238
239
240
241
242
243
244
245
# File 'lib/openc3/models/reaction_model.rb', line 238

def sleep
  if @snooze > 0
    @snoozed_until = Time.now.to_i + @snooze
    @updated_at = Time.now.to_nsec_from_epoch
    Store.hset(@primary_key, @name, JSON.generate(as_json(:allow_nan => true)))
    notify(kind: 'snoozed')
  end
end

#undeployObject



310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
# File 'lib/openc3/models/reaction_model.rb', line 310

def undeploy
  return unless ReactionModel.names(scope: @scope).empty?

  model = MicroserviceModel.get_model(name: @microservice_name, scope: @scope)
  if model
    # Let the frontend know that the microservice is shutting down
    # Custom event which matches the 'deployed' event in ReactionMicroservice
    notification = {
      'kind' => 'undeployed',
      'type' => 'reaction',
      # name and updated_at fields are required for Event formatting
      'data' => JSON.generate({
        'name' => @microservice_name,
        'updated_at' => Time.now.to_nsec_from_epoch,
      }),
    }
    AutonomicTopic.write_notification(notification, scope: @scope)
    model.destroy
  end
end

#updateObject



211
212
213
214
215
216
# File 'lib/openc3/models/reaction_model.rb', line 211

def update
  verify_triggers()
  @updated_at = Time.now.to_nsec_from_epoch
  Store.hset(@primary_key, @name, JSON.generate(as_json(:allow_nan => true)))
  # No notification as this is only called via reaction_controller which already notifies
end

#validate_actions(actions) ⇒ Object



162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
# File 'lib/openc3/models/reaction_model.rb', line 162

def validate_actions(actions)
  unless actions.is_a?(Array)
    raise ReactionInputError.new "invalid actions, must be array of hashes: #{actions}"
  end
  actions.each do | action |
    unless action.is_a?(Hash)
      raise ReactionInputError.new "invalid action, must be a hash: #{action}"
    end
    action_type = action['type']
    if action_type.nil?
      raise ReactionInputError.new "invalid action, must contain 'type': #{action}"
    elsif action['value'].nil?
      raise ReactionInputError.new "invalid action, must contain 'value': #{action}"
    end
    unless ACTION_TYPES.include?(action_type)
      raise ReactionInputError.new "invalid action type '#{action_type}', must be one of #{ACTION_TYPES}"
    end
  end
  return actions
end

#validate_level(level) ⇒ Object



125
126
127
128
129
130
131
132
# File 'lib/openc3/models/reaction_model.rb', line 125

def validate_level(level)
  case level
  when 'EDGE', 'LEVEL'
    return level
  else
    raise ReactionInputError.new "invalid triggerLevel, must be EDGE or LEVEL: #{level}"
  end
end

#validate_snooze(snooze) ⇒ Object



134
135
136
137
138
# File 'lib/openc3/models/reaction_model.rb', line 134

def validate_snooze(snooze)
  Integer(snooze)
rescue
  raise ReactionInputError.new "invalid snooze value: #{snooze}"
end

#validate_triggers(triggers) ⇒ Object



140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
# File 'lib/openc3/models/reaction_model.rb', line 140

def validate_triggers(triggers)
  unless triggers.is_a?(Array)
    raise ReactionInputError.new "invalid triggers, must be array of hashes: #{triggers}"
  end
  trigger_hash = Hash.new()
  triggers.each do | trigger |
    unless trigger.is_a?(Hash)
      raise ReactionInputError.new "invalid trigger, must be hash: #{trigger}"
    end
    if trigger['name'].nil? || trigger['group'].nil?
      raise ReactionInputError.new "invalid trigger, must contain 'name' and 'group' keys: #{trigger}"
    end
    trigger_name = trigger['name']
    unless trigger_hash[trigger_name].nil?
      raise ReactionInputError.new "no duplicate triggers allowed: #{triggers}"
    else
      trigger_hash[trigger_name] = 1
    end
  end
  return triggers
end

#verify_triggersObject



183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
# File 'lib/openc3/models/reaction_model.rb', line 183

def verify_triggers
  trigger_models = []
  @triggers.each do | trigger |
    model = TriggerModel.get(name: trigger['name'], group: trigger['group'], scope: @scope)
    if model.nil?
      raise ReactionInputError.new "failed to find trigger: #{trigger}"
    end
    trigger_models << model
  end
  if trigger_models.empty?
    raise ReactionInputError.new "reaction must contain at least one valid trigger: #{@triggers}"
  end
  trigger_models.each do | trigger_model |
    trigger_model.update_dependents(dependent: @name)
    trigger_model.update()
  end
end