agent.rb 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469
  1. require 'utils'
  2. # Agent is the core class in Huginn, representing a configurable, schedulable, reactive system with memory that can
  3. # be sub-classed for many different purposes. Agents can emit Events, as well as receive them and react in many different ways.
  4. # The basic Agent API is detailed on the Huginn wiki: https://github.com/cantino/huginn/wiki/Creating-a-new-agent
  5. class Agent < ActiveRecord::Base
  6. include AssignableTypes
  7. include MarkdownClassAttributes
  8. include JSONSerializedField
  9. include RDBMSFunctions
  10. include WorkingHelpers
  11. include LiquidInterpolatable
  12. include HasGuid
  13. include LiquidDroppable
  14. include DryRunnable
  15. include SortableEvents
  16. markdown_class_attributes :description, :event_description
  17. load_types_in "Agents"
  18. SCHEDULES = %w[every_1m every_2m every_5m every_10m every_30m every_1h every_2h every_5h every_12h every_1d every_2d every_7d] +
  19. %w[midnight 1am 2am 3am 4am 5am 6am 7am 8am 9am 10am 11am noon 1pm 2pm 3pm 4pm 5pm 6pm 7pm 8pm 9pm 10pm 11pm never]
  20. EVENT_RETENTION_SCHEDULES = [["Forever", 0], ['1 hour', 1.hour], ['6 hours', 6.hours], ["1 day", 1.day], *([2, 3, 4, 5, 7, 14, 21, 30, 45, 90, 180, 365].map {|n| ["#{n} days", n.days] })]
  21. json_serialize :options, :memory
  22. validates_presence_of :name, :user
  23. validates_inclusion_of :keep_events_for, :in => EVENT_RETENTION_SCHEDULES.map(&:last)
  24. validates :sources, owned_by: :user_id
  25. validates :receivers, owned_by: :user_id
  26. validates :controllers, owned_by: :user_id
  27. validates :control_targets, owned_by: :user_id
  28. validates :scenarios, owned_by: :user_id
  29. validate :validate_schedule
  30. validate :validate_options
  31. after_initialize :set_default_schedule
  32. before_validation :set_default_schedule
  33. before_validation :unschedule_if_cannot_schedule
  34. before_save :unschedule_if_cannot_schedule
  35. before_create :set_last_checked_event_id
  36. after_save :possibly_update_event_expirations
  37. belongs_to :user, :inverse_of => :agents
  38. belongs_to :service, :inverse_of => :agents, optional: true
  39. has_many :events, -> { order("events.id desc") }, :dependent => :delete_all, :inverse_of => :agent
  40. has_one :most_recent_event, -> { order("events.id desc") }, :inverse_of => :agent, :class_name => "Event"
  41. has_many :logs, -> { order("agent_logs.id desc") }, :dependent => :delete_all, :inverse_of => :agent, :class_name => "AgentLog"
  42. has_many :links_as_source, :dependent => :delete_all, :foreign_key => "source_id", :class_name => "Link", :inverse_of => :source
  43. has_many :links_as_receiver, :dependent => :delete_all, :foreign_key => "receiver_id", :class_name => "Link", :inverse_of => :receiver
  44. has_many :sources, :through => :links_as_receiver, :class_name => "Agent", :inverse_of => :receivers
  45. has_many :received_events, -> { order("events.id desc") }, :through => :sources, :class_name => "Event", :source => :events
  46. has_many :receivers, :through => :links_as_source, :class_name => "Agent", :inverse_of => :sources
  47. has_many :control_links_as_controller, dependent: :delete_all, foreign_key: 'controller_id', class_name: 'ControlLink', inverse_of: :controller
  48. has_many :control_links_as_control_target, dependent: :delete_all, foreign_key: 'control_target_id', class_name: 'ControlLink', inverse_of: :control_target
  49. has_many :controllers, through: :control_links_as_control_target, class_name: "Agent", inverse_of: :control_targets
  50. has_many :control_targets, through: :control_links_as_controller, class_name: "Agent", inverse_of: :controllers
  51. has_many :scenario_memberships, :dependent => :destroy, :inverse_of => :agent
  52. has_many :scenarios, :through => :scenario_memberships, :inverse_of => :agents
  53. scope :active, -> { where(disabled: false, deactivated: false) }
  54. scope :inactive, -> { where(['disabled = ? OR deactivated = ?', true, true]) }
  55. scope :of_type, lambda { |type|
  56. type = case type
  57. when Agent
  58. type.class.to_s
  59. else
  60. type.to_s
  61. end
  62. where(:type => type)
  63. }
  64. def short_type
  65. type.demodulize
  66. end
  67. def check
  68. # Implement me in your subclass of Agent.
  69. end
  70. def default_options
  71. # Implement me in your subclass of Agent.
  72. {}
  73. end
  74. def receive(events)
  75. # Implement me in your subclass of Agent.
  76. end
  77. def is_form_configurable?
  78. false
  79. end
  80. def receive_web_request(params, method, format)
  81. # Implement me in your subclass of Agent.
  82. ["not implemented", 404, "text/plain", {}] # last two elements in response array are optional
  83. end
  84. # alternate method signature for receive_web_request
  85. # def receive_web_request(request=ActionDispatch::Request.new( ... ))
  86. # end
  87. # Implement me in your subclass to decide if your Agent is working.
  88. def working?
  89. raise "Implement me in your subclass"
  90. end
  91. def build_event(event)
  92. event = events.build(event) if event.is_a?(Hash)
  93. event.agent = self
  94. event.user = user
  95. event.expires_at ||= new_event_expiration_date
  96. event
  97. end
  98. def create_event(event)
  99. if can_create_events?
  100. event = build_event(event)
  101. event.save!
  102. event
  103. else
  104. error "This Agent cannot create events!"
  105. end
  106. end
  107. def credential(name)
  108. @credential_cache ||= {}
  109. if @credential_cache.has_key?(name)
  110. @credential_cache[name]
  111. else
  112. @credential_cache[name] = user.user_credentials.where(:credential_name => name).first.try(:credential_value)
  113. end
  114. end
  115. def reload
  116. @credential_cache = {}
  117. super
  118. end
  119. def new_event_expiration_date
  120. keep_events_for > 0 ? keep_events_for.seconds.from_now : nil
  121. end
  122. def update_event_expirations!
  123. if keep_events_for == 0
  124. events.update_all :expires_at => nil
  125. else
  126. events.update_all "expires_at = " + rdbms_date_add("created_at", "SECOND", keep_events_for.to_i)
  127. end
  128. end
  129. def trigger_web_request(request)
  130. params = request.params.except(:action, :controller, :agent_id, :user_id, :format)
  131. if respond_to?(:receive_webhook)
  132. Rails.logger.warn "DEPRECATED: The .receive_webhook method is deprecated, please switch your Agent to use .receive_web_request."
  133. receive_webhook(params).tap do
  134. self.last_web_request_at = Time.now
  135. save!
  136. end
  137. else
  138. if method(:receive_web_request).arity == 1
  139. handled_request = receive_web_request(request)
  140. else
  141. handled_request = receive_web_request(params, request.method_symbol.to_s, request.format.to_s)
  142. end
  143. handled_request.tap do
  144. self.last_web_request_at = Time.now
  145. save!
  146. end
  147. end
  148. end
  149. def unavailable?
  150. disabled? || dependencies_missing?
  151. end
  152. def dependencies_missing?
  153. self.class.dependencies_missing?
  154. end
  155. def default_schedule
  156. self.class.default_schedule
  157. end
  158. def cannot_be_scheduled?
  159. self.class.cannot_be_scheduled?
  160. end
  161. def can_be_scheduled?
  162. !cannot_be_scheduled?
  163. end
  164. def cannot_receive_events?
  165. self.class.cannot_receive_events?
  166. end
  167. def can_receive_events?
  168. !cannot_receive_events?
  169. end
  170. def cannot_create_events?
  171. self.class.cannot_create_events?
  172. end
  173. def can_create_events?
  174. !cannot_create_events?
  175. end
  176. def can_control_other_agents?
  177. self.class.can_control_other_agents?
  178. end
  179. def can_dry_run?
  180. self.class.can_dry_run?
  181. end
  182. def no_bulk_receive?
  183. self.class.no_bulk_receive?
  184. end
  185. def log(message, options = {})
  186. AgentLog.log_for_agent(self, message, options)
  187. end
  188. def error(message, options = {})
  189. log(message, options.merge(:level => 4))
  190. end
  191. def delete_logs!
  192. logs.delete_all
  193. update_column :last_error_log_at, nil
  194. end
  195. def drop_pending_events
  196. false
  197. end
  198. def drop_pending_events=(bool)
  199. set_last_checked_event_id if bool
  200. end
  201. # Callbacks
  202. def set_default_schedule
  203. self.schedule = default_schedule unless schedule.present? || cannot_be_scheduled?
  204. end
  205. def unschedule_if_cannot_schedule
  206. self.schedule = nil if cannot_be_scheduled?
  207. end
  208. def set_last_checked_event_id
  209. if can_receive_events? && newest_event_id = Event.maximum(:id)
  210. self.last_checked_event_id = newest_event_id
  211. end
  212. end
  213. def possibly_update_event_expirations
  214. update_event_expirations! if saved_change_to_keep_events_for?
  215. end
  216. #Validation Methods
  217. private
  218. def validate_schedule
  219. unless cannot_be_scheduled?
  220. errors.add(:schedule, "is not a valid schedule") unless SCHEDULES.include?(schedule.to_s)
  221. end
  222. end
  223. def validate_options
  224. # Implement me in your subclass to test for valid options.
  225. end
  226. # Utility Methods
  227. def boolify(option_value)
  228. case option_value
  229. when true, 'true'
  230. true
  231. when false, 'false'
  232. false
  233. else
  234. nil
  235. end
  236. end
  237. # Class Methods
  238. class << self
  239. def build_clone(original)
  240. new(original.slice(:type, :options, :service_id, :schedule, :controller_ids, :control_target_ids,
  241. :source_ids, :receiver_ids, :keep_events_for, :propagate_immediately, :scenario_ids)) { |clone|
  242. # Give it a unique name
  243. 2.step do |i|
  244. name = '%s (%d)' % [original.name, i]
  245. unless exists?(name: name)
  246. clone.name = name
  247. break
  248. end
  249. end
  250. }
  251. end
  252. def cannot_be_scheduled!
  253. @cannot_be_scheduled = true
  254. end
  255. def cannot_be_scheduled?
  256. !!@cannot_be_scheduled
  257. end
  258. def default_schedule(schedule = nil)
  259. @default_schedule = schedule unless schedule.nil?
  260. @default_schedule
  261. end
  262. def cannot_create_events!
  263. @cannot_create_events = true
  264. end
  265. def cannot_create_events?
  266. !!@cannot_create_events
  267. end
  268. def cannot_receive_events!
  269. @cannot_receive_events = true
  270. end
  271. def cannot_receive_events?
  272. !!@cannot_receive_events
  273. end
  274. def can_control_other_agents?
  275. include? AgentControllerConcern
  276. end
  277. def can_dry_run!
  278. @can_dry_run = true
  279. end
  280. def can_dry_run?
  281. !!@can_dry_run
  282. end
  283. def no_bulk_receive!
  284. @no_bulk_receive = true
  285. end
  286. def no_bulk_receive?
  287. !!@no_bulk_receive
  288. end
  289. def gem_dependency_check
  290. @gem_dependencies_checked = true
  291. @gem_dependencies_met = yield
  292. end
  293. def dependencies_missing?
  294. @gem_dependencies_checked && !@gem_dependencies_met
  295. end
  296. # Find all Agents that have received Events since the last execution of this method. Update those Agents with
  297. # their new `last_checked_event_id` and queue each of the Agents to be called with #receive using `async_receive`.
  298. # This is called by bin/schedule.rb periodically.
  299. def receive!(options={})
  300. Agent.transaction do
  301. scope = Agent.
  302. select("agents.id AS receiver_agent_id, sources.type AS source_agent_type, agents.type AS receiver_agent_type, events.id AS event_id").
  303. joins("JOIN links ON (links.receiver_id = agents.id)").
  304. joins("JOIN agents AS sources ON (links.source_id = sources.id)").
  305. joins("JOIN events ON (events.agent_id = sources.id AND events.id > links.event_id_at_creation)").
  306. where("NOT agents.disabled AND NOT agents.deactivated AND (agents.last_checked_event_id IS NULL OR events.id > agents.last_checked_event_id)")
  307. if options[:only_receivers].present?
  308. scope = scope.where("agents.id in (?)", options[:only_receivers])
  309. end
  310. sql = scope.to_sql
  311. agents_to_events = {}
  312. Agent.connection.select_rows(sql).each do |receiver_agent_id, source_agent_type, receiver_agent_type, event_id|
  313. next unless const_defined?(source_agent_type) && const_defined?(receiver_agent_type)
  314. agents_to_events[receiver_agent_id.to_i] ||= []
  315. agents_to_events[receiver_agent_id.to_i] << event_id
  316. end
  317. Agent.where(:id => agents_to_events.keys).each do |agent|
  318. event_ids = agents_to_events[agent.id].uniq
  319. agent.update_attribute :last_checked_event_id, event_ids.max
  320. if agent.no_bulk_receive?
  321. event_ids.each { |event_id| Agent.async_receive(agent.id, [event_id]) }
  322. else
  323. Agent.async_receive(agent.id, event_ids)
  324. end
  325. end
  326. {
  327. :agent_count => agents_to_events.keys.length,
  328. :event_count => agents_to_events.values.flatten.uniq.compact.length
  329. }
  330. end
  331. end
  332. # This method will enqueue an AgentReceiveJob job. It accepts Agent and Event ids instead of a literal ActiveRecord
  333. # models because it is preferable to serialize jobs with ids.
  334. def async_receive(agent_id, event_ids)
  335. AgentReceiveJob.perform_later(agent_id, event_ids)
  336. end
  337. # Given a schedule name, run `check` via `bulk_check` on all Agents with that schedule.
  338. # This is called by bin/schedule.rb for each schedule in `SCHEDULES`.
  339. def run_schedule(schedule)
  340. return if schedule == 'never'
  341. types = where(:schedule => schedule).group(:type).pluck(:type)
  342. types.each do |type|
  343. next unless valid_type?(type)
  344. type.constantize.bulk_check(schedule)
  345. end
  346. end
  347. # Schedule `async_check`s for every Agent on the given schedule. This is normally called by `run_schedule` once
  348. # per type of agent, so you can override this to define custom bulk check behavior for your custom Agent type.
  349. def bulk_check(schedule)
  350. raise "Call #bulk_check on the appropriate subclass of Agent" if self == Agent
  351. where("NOT disabled AND NOT deactivated AND schedule = ?", schedule).pluck("agents.id").each do |agent_id|
  352. async_check(agent_id)
  353. end
  354. end
  355. # This method will enqueue an AgentCheckJob job. It accepts an Agent id instead of a literal Agent because it is
  356. # preferable to serialize job with ids, instead of with the full Agents.
  357. def async_check(agent_id)
  358. AgentCheckJob.perform_later(agent_id)
  359. end
  360. end
  361. end
  362. class AgentDrop
  363. def type
  364. @object.short_type
  365. end
  366. METHODS = %i[
  367. id
  368. name
  369. type
  370. options
  371. memory
  372. sources
  373. receivers
  374. schedule
  375. controllers
  376. control_targets
  377. disabled
  378. keep_events_for
  379. propagate_immediately
  380. ]
  381. METHODS.each { |attr|
  382. define_method(attr) {
  383. @object.__send__(attr)
  384. } unless method_defined?(attr)
  385. }
  386. end