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.
307 308 309 310 311 |
# File 'lib/openc3/models/reaction_model.rb', line 307 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.
289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 |
# File 'lib/openc3/models/reaction_model.rb', line 289 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
281 282 283 284 285 286 |
# File 'lib/openc3/models/reaction_model.rb', line 281 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
206 207 208 |
# File 'lib/openc3/models/reaction_model.rb', line 206 def commit_trigger_dependents(models) models.each { |model| model.update() } end |
#create ⇒ Object
210 211 212 213 214 215 216 217 218 219 |
# File 'lib/openc3/models/reaction_model.rb', line 210 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
323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 |
# File 'lib/openc3/models/reaction_model.rb', line 323 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
340 341 342 343 344 345 |
# File 'lib/openc3/models/reaction_model.rb', line 340 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.
314 315 316 317 318 319 320 321 |
# File 'lib/openc3/models/reaction_model.rb', line 314 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
256 257 258 259 260 261 262 263 264 |
# File 'lib/openc3/models/reaction_model.rb', line 256 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
248 249 250 251 252 253 254 |
# File 'lib/openc3/models/reaction_model.rb', line 248 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
266 267 268 269 270 |
# File 'lib/openc3/models/reaction_model.rb', line 266 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
272 273 274 275 276 277 278 279 |
# File 'lib/openc3/models/reaction_model.rb', line 272 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
347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 |
# File 'lib/openc3/models/reaction_model.rb', line 347 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
221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 |
# File 'lib/openc3/models/reaction_model.rb', line 221 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
163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 |
# File 'lib/openc3/models/reaction_model.rb', line 163 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 |
# 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_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 |
#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.
186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 |
# File 'lib/openc3/models/reaction_model.rb', line 186 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 |