agent.rb 14KB

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