Class: OpenC3::ReactionModel
- 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
-
#actions ⇒ Object
Returns the value of attribute actions.
-
#enabled ⇒ Object
readonly
Returns the value of attribute enabled.
-
#label ⇒ Object
Returns the value of attribute label.
-
#name ⇒ Object
readonly
Returns the value of attribute name.
-
#scope ⇒ Object
readonly
Returns the value of attribute scope.
-
#shard ⇒ Object
Returns the value of attribute shard.
-
#snooze ⇒ Object
Returns the value of attribute snooze.
-
#snoozed_until ⇒ Object
readonly
Returns the value of attribute snoozed_until.
-
#trigger_level ⇒ Object
Returns the value of attribute trigger_level.
-
#triggers ⇒ Object
Returns the value of attribute triggers.
-
#username ⇒ Object
Returns the value of attribute username.
Attributes inherited from Model
Class Method Summary collapse
-
.all(scope:) ⇒ Array<Hash>
All the Key, Values stored under the name key.
- .create_unique_name(scope:) ⇒ Object
-
.delete(name:, scope:) ⇒ Object
Check dependents before delete.
-
.from_json(json, name:, scope:) ⇒ ReactionModel
Model generated from the passed JSON.
-
.get(name:, scope:) ⇒ ReactionModel
Return the object with the name at.
-
.names(scope:) ⇒ Array<String>
All the uuids stored under the name key.
Instance Method Summary collapse
-
#as_json(*a) ⇒ Hash
Generated from the ReactionModel.
- #awaken ⇒ Object
-
#commit_trigger_dependents(models) ⇒ Object
Persist dependent changes to trigger models.
- #create ⇒ Object
- #create_microservice(topics:) ⇒ Object
- #deploy ⇒ Object
-
#initialize(name:, scope:, snooze:, actions:, triggers:, trigger_level:, enabled: true, snoozed_until: nil, username: nil, shard: 0, label: nil, updated_at: nil) ⇒ ReactionModel
constructor
A new instance of ReactionModel.
-
#notify(kind:) ⇒ Object
[] update the redis stream / reaction topic that something has changed.
- #notify_disable ⇒ Object
- #notify_enable ⇒ Object
- #notify_execute ⇒ Object
- #sleep ⇒ Object
- #undeploy ⇒ Object
- #update ⇒ Object
- #validate_actions(actions) ⇒ Object
- #validate_level(level) ⇒ Object
- #validate_snooze(snooze) ⇒ Object
- #validate_triggers(triggers) ⇒ Object
-
#validate_triggers_exist ⇒ Object
Validate that all triggers exist, but do not persist dependent changes yet.
Methods inherited from Model
#check_disable_erb, #destroy, #destroyed?, #diff, filter, find_all_by_plugin, get_all_models, get_model, handle_config, set, store, store_queued
Constructor Details
#initialize(name:, scope:, snooze:, actions:, triggers:, trigger_level:, enabled: true, snoozed_until: nil, username: nil, shard: 0, label: nil, updated_at: nil) ⇒ ReactionModel
Returns a new instance of ReactionModel.
84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 |
# File 'lib/openc3/models/reaction_model.rb', line 84 def initialize( name:, scope:, snooze:, actions:, triggers:, trigger_level:, enabled: true, snoozed_until: nil, username: nil, shard: 0, label: nil, updated_at: nil ) super("#{scope}#{PRIMARY_KEY}", name: name, scope: scope) @microservice_name = "#{scope}__OPENC3__REACTION" @enabled = enabled @snoozed_until = snoozed_until @trigger_level = validate_level(trigger_level) @snooze = validate_snooze(snooze) @actions = validate_actions(actions) @triggers = validate_triggers(triggers) @username = username @shard = shard.to_i # to_i to handle nil @label = label @updated_at = updated_at end |
Instance Attribute Details
#actions ⇒ Object
Returns the value of attribute actions.
81 82 83 |
# File 'lib/openc3/models/reaction_model.rb', line 81 def actions @actions end |
#enabled ⇒ Object (readonly)
Returns the value of attribute enabled.
81 82 83 |
# File 'lib/openc3/models/reaction_model.rb', line 81 def enabled @enabled end |
#label ⇒ Object
Returns the value of attribute label.
82 83 84 |
# File 'lib/openc3/models/reaction_model.rb', line 82 def label @label end |
#name ⇒ Object (readonly)
Returns the value of attribute name.
81 82 83 |
# File 'lib/openc3/models/reaction_model.rb', line 81 def name @name end |
#scope ⇒ Object (readonly)
Returns the value of attribute scope.
81 82 83 |
# File 'lib/openc3/models/reaction_model.rb', line 81 def scope @scope end |
#shard ⇒ Object
Returns the value of attribute shard.
82 83 84 |
# File 'lib/openc3/models/reaction_model.rb', line 82 def shard @shard end |
#snooze ⇒ Object
Returns the value of attribute snooze.
81 82 83 |
# File 'lib/openc3/models/reaction_model.rb', line 81 def snooze @snooze end |
#snoozed_until ⇒ Object (readonly)
Returns the value of attribute snoozed_until.
81 82 83 |
# File 'lib/openc3/models/reaction_model.rb', line 81 def snoozed_until @snoozed_until end |
#trigger_level ⇒ Object
Returns the value of attribute trigger_level.
81 82 83 |
# File 'lib/openc3/models/reaction_model.rb', line 81 def trigger_level @trigger_level end |
#triggers ⇒ Object
Returns the value of attribute triggers.
81 82 83 |
# File 'lib/openc3/models/reaction_model.rb', line 81 def triggers @triggers end |
#username ⇒ Object
Returns the value of attribute username.
82 83 84 |
# File 'lib/openc3/models/reaction_model.rb', line 82 def username @username end |
Class Method Details
.all(scope:) ⇒ Array<Hash>
Returns All the Key, Values stored under the name key.
54 55 56 |
# File 'lib/openc3/models/reaction_model.rb', line 54 def self.all(scope:) super("#{scope}#{PRIMARY_KEY}") end |
.create_unique_name(scope:) ⇒ Object
34 35 36 37 38 39 40 41 42 43 |
# File 'lib/openc3/models/reaction_model.rb', line 34 def self.create_unique_name(scope:) reaction_names = self.names(scope: scope) num = 1 # Users count with 1 unless reaction_names.empty? # Extract numeric suffixes and find the max to avoid lexicographic sort issues max_num = reaction_names.map { |name| name[5..-1].to_i }.max num = max_num + 1 end return "REACT#{num}" end |
.delete(name:, scope:) ⇒ Object
Check dependents before delete.
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
# File 'lib/openc3/models/reaction_model.rb', line 64 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.
310 311 312 313 314 |
# File 'lib/openc3/models/reaction_model.rb', line 310 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
46 47 48 49 50 51 |
# File 'lib/openc3/models/reaction_model.rb', line 46 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.
59 60 61 |
# File 'lib/openc3/models/reaction_model.rb', line 59 def self.names(scope:) super("#{scope}#{PRIMARY_KEY}") end |
Instance Method Details
#as_json(*a) ⇒ Hash
Returns generated from the ReactionModel.
292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 |
# File 'lib/openc3/models/reaction_model.rb', line 292 def as_json(*a) return { 'name' => @name, 'scope' => @scope, 'enabled' => @enabled, 'trigger_level' => @trigger_level, 'snooze' => @snooze, 'snoozed_until' => @snoozed_until, 'triggers' => @triggers, 'actions' => @actions, 'username' => @username, 'shard' => @shard, 'label' => @label, 'updated_at' => @updated_at } end |
#awaken ⇒ Object
284 285 286 287 288 289 |
# File 'lib/openc3/models/reaction_model.rb', line 284 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 |
#commit_trigger_dependents(models) ⇒ Object
Persist dependent changes to trigger models
209 210 211 |
# File 'lib/openc3/models/reaction_model.rb', line 209 def commit_trigger_dependents(models) models.each { |model| model.update() } end |
#create ⇒ Object
213 214 215 216 217 218 219 220 221 222 |
# File 'lib/openc3/models/reaction_model.rb', line 213 def create unless Store.hget(@primary_key, @name).nil? raise ReactionInputError.new "existing reaction found: #{@name}" end models = validate_triggers_exist() @updated_at = Time.now.to_nsec_from_epoch Store.hset(@primary_key, @name, JSON.generate(as_json, allow_nan: true)) commit_trigger_dependents(models) notify(kind: 'created') end |
#create_microservice(topics:) ⇒ Object
326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 |
# File 'lib/openc3/models/reaction_model.rb', line 326 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-enterprise/lib/openc3-enterprise/microservices', options: [], topics: topics, target_names: [], plugin: nil, shard: @shard, scope: @scope ) microservice.create end |
#deploy ⇒ Object
343 344 345 346 347 348 |
# File 'lib/openc3/models/reaction_model.rb', line 343 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.
317 318 319 320 321 322 323 324 |
# File 'lib/openc3/models/reaction_model.rb', line 317 def notify(kind:) notification = { 'kind' => kind, 'type' => 'reaction', 'data' => JSON.generate(as_json, allow_nan: true), } AutonomicTopic.write_notification(notification, scope: @scope) end |
#notify_disable ⇒ Object
259 260 261 262 263 264 265 266 267 |
# File 'lib/openc3/models/reaction_model.rb', line 259 def notify_disable @enabled = false # disabling clears the snooze so when it's enabled it can immediately run @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: 'disabled') # update() will also be called by the reaction_microservice end |
#notify_enable ⇒ Object
251 252 253 254 255 256 257 |
# File 'lib/openc3/models/reaction_model.rb', line 251 def notify_enable @enabled = true @updated_at = Time.now.to_nsec_from_epoch Store.hset(@primary_key, @name, JSON.generate(as_json, allow_nan: true)) notify(kind: 'enabled') # update() will also be called by the reaction_microservice end |
#notify_execute ⇒ Object
269 270 271 272 273 |
# File 'lib/openc3/models/reaction_model.rb', line 269 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 |
#sleep ⇒ Object
275 276 277 278 279 280 281 282 |
# File 'lib/openc3/models/reaction_model.rb', line 275 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 |
#undeploy ⇒ Object
350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 |
# File 'lib/openc3/models/reaction_model.rb', line 350 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 |
#update ⇒ Object
224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 |
# File 'lib/openc3/models/reaction_model.rb', line 224 def update old_reaction = ReactionModel.get(name: @name, scope: @scope) if old_reaction # Find triggers that are being removed (in old but not in new) old_trigger_keys = old_reaction.triggers.map { |t| "#{t['group']}:#{t['name']}" } new_trigger_keys = @triggers.map { |t| "#{t['group']}:#{t['name']}" } removed_trigger_keys = old_trigger_keys - new_trigger_keys # Remove this reaction from old triggers' dependents removed_trigger_keys.each do |trigger_key| group, name = trigger_key.split(':', 2) trigger_model = TriggerModel.get(name: name, group: group, scope: @scope) if trigger_model trigger_model.update_dependents(dependent: @name, remove: true) trigger_model.update() end end end models = validate_triggers_exist() @updated_at = Time.now.to_nsec_from_epoch Store.hset(@primary_key, @name, JSON.generate(as_json, allow_nan: true)) commit_trigger_dependents(models) # No notification as this is only called via reaction_controller which already notifies end |
#validate_actions(actions) ⇒ Object
166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 |
# File 'lib/openc3/models/reaction_model.rb', line 166 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
126 127 128 129 130 131 132 133 |
# File 'lib/openc3/models/reaction_model.rb', line 126 def validate_level(level) case level when 'EDGE', 'LEVEL' return level else raise ReactionInputError.new "invalid trigger level, must be EDGE or LEVEL: #{level}" end end |
#validate_snooze(snooze) ⇒ Object
135 136 137 138 139 |
# File 'lib/openc3/models/reaction_model.rb', line 135 def validate_snooze(snooze) Integer(snooze) rescue raise ReactionInputError.new "invalid snooze value: #{snooze}" end |
#validate_triggers(triggers) ⇒ Object
141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 |
# File 'lib/openc3/models/reaction_model.rb', line 141 def validate_triggers(triggers) unless triggers.is_a?(Array) raise ReactionInputError.new "invalid triggers, must be array of hashes: #{triggers}" end trigger_hash = {} 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 names are only unique within a group so both parts identify # the trigger. Keying on name alone rejects a reaction which references # the same trigger name in two different groups. trigger_key = "#{trigger['group']}__#{trigger['name']}" unless trigger_hash[trigger_key].nil? raise ReactionInputError.new "no duplicate triggers allowed: #{triggers}" else trigger_hash[trigger_key] = 1 end end return triggers end |
#validate_triggers_exist ⇒ Object
Validate that all triggers exist, but do not persist dependent changes yet. Returns the list of trigger models that need updating.
189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 |
# File 'lib/openc3/models/reaction_model.rb', line 189 def validate_triggers_exist if @triggers.empty? raise ReactionInputError.new "reaction must contain at least one valid trigger: #{@triggers}" end models_to_update = [] @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 unless model.dependents.include?(@name) model.update_dependents(dependent: @name) models_to_update << model end end models_to_update end |