agent.rb 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455
  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, :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. validate :sources_are_owned
  26. validate :controllers_are_owned
  27. validate :control_targets_are_owned
  28. validate :scenarios_are_owned
  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
  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 :received_events, -> { order("events.id desc") }, :through => :sources, :class_name => "Event", :source => :events
  43. has_many :links_as_source, :dependent => :delete_all, :foreign_key => "source_id", :class_name => "Link", :inverse_of => :source
  44. has_many :links_as_receiver, :dependent => :delete_all, :foreign_key => "receiver_id", :class_name => "Link", :inverse_of => :receiver
  45. has_many :sources, :through => :links_as_receiver, :class_name => "Agent", :inverse_of => :receivers
  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) }
  54. scope :inactive, -> { where(disabled: 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]
  83. end
  84. # Implement me in your subclass to decide if your Agent is working.
  85. def working?
  86. raise "Implement me in your subclass"
  87. end
  88. def build_event(event)
  89. event = events.build(event) if event.is_a?(Hash)
  90. event.agent = self
  91. event.user = user
  92. event.expires_at ||= new_event_expiration_date
  93. event
  94. end
  95. def create_event(event)
  96. if can_create_events?
  97. event = build_event(event)
  98. event.save!
  99. event
  100. else
  101. error "This Agent cannot create events!"
  102. end
  103. end
  104. def credential(name)
  105. @credential_cache ||= {}
  106. if @credential_cache.has_key?(name)
  107. @credential_cache[name]
  108. else
  109. @credential_cache[name] = user.user_credentials.where(:credential_name => name).first.try(:credential_value)
  110. end
  111. end
  112. def reload
  113. @credential_cache = {}
  114. super
  115. end
  116. def new_event_expiration_date
  117. keep_events_for > 0 ? keep_events_for.seconds.from_now : nil
  118. end
  119. def update_event_expirations!
  120. if keep_events_for == 0
  121. events.update_all :expires_at => nil
  122. else
  123. events.update_all "expires_at = " + rdbms_date_add("created_at", "SECOND", keep_events_for.to_i)
  124. end
  125. end
  126. def trigger_web_request(params, method, format)
  127. if respond_to?(:receive_webhook)
  128. Rails.logger.warn "DEPRECATED: The .receive_webhook method is deprecated, please switch your Agent to use .receive_web_request."
  129. receive_webhook(params).tap do
  130. self.last_web_request_at = Time.now
  131. save!
  132. end
  133. else
  134. receive_web_request(params, method, format).tap do
  135. self.last_web_request_at = Time.now
  136. save!
  137. end
  138. end
  139. end
  140. def unavailable?
  141. disabled? || dependencies_missing?
  142. end
  143. def dependencies_missing?
  144. self.class.dependencies_missing?
  145. end
  146. def default_schedule
  147. self.class.default_schedule
  148. end
  149. def cannot_be_scheduled?
  150. self.class.cannot_be_scheduled?
  151. end
  152. def can_be_scheduled?
  153. !cannot_be_scheduled?
  154. end
  155. def cannot_receive_events?
  156. self.class.cannot_receive_events?
  157. end
  158. def can_receive_events?
  159. !cannot_receive_events?
  160. end
  161. def cannot_create_events?
  162. self.class.cannot_create_events?
  163. end
  164. def can_create_events?
  165. !cannot_create_events?
  166. end
  167. def can_control_other_agents?
  168. self.class.can_control_other_agents?
  169. end
  170. def can_dry_run?
  171. self.class.can_dry_run?
  172. end
  173. def log(message, options = {})
  174. AgentLog.log_for_agent(self, message, options)
  175. end
  176. def error(message, options = {})
  177. log(message, options.merge(:level => 4))
  178. end
  179. def delete_logs!
  180. logs.delete_all
  181. update_column :last_error_log_at, nil
  182. end
  183. def drop_pending_events
  184. false
  185. end
  186. def drop_pending_events=(bool)
  187. set_last_checked_event_id if bool
  188. end
  189. # Callbacks
  190. def set_default_schedule
  191. self.schedule = default_schedule unless schedule.present? || cannot_be_scheduled?
  192. end
  193. def unschedule_if_cannot_schedule
  194. self.schedule = nil if cannot_be_scheduled?
  195. end
  196. def set_last_checked_event_id
  197. if can_receive_events? && newest_event_id = Event.maximum(:id)
  198. self.last_checked_event_id = newest_event_id
  199. end
  200. end
  201. def possibly_update_event_expirations
  202. update_event_expirations! if keep_events_for_changed?
  203. end
  204. #Validation Methods
  205. private
  206. def sources_are_owned
  207. errors.add(:sources, "must be owned by you") unless sources.all? {|s| s.user_id == user_id }
  208. end
  209. def controllers_are_owned
  210. errors.add(:controllers, "must be owned by you") unless controllers.all? {|s| s.user_id == user_id }
  211. end
  212. def control_targets_are_owned
  213. errors.add(:control_targets, "must be owned by you") unless control_targets.all? {|s| s.user_id == user_id }
  214. end
  215. def scenarios_are_owned
  216. errors.add(:scenarios, "must be owned by you") unless scenarios.all? {|s| s.user_id == user_id }
  217. end
  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, :schedule, :controller_ids, :control_target_ids,
  241. :source_ids, :keep_events_for, :propagate_immediately)) { |clone|
  242. # Give it a unique name
  243. 2.upto(count) 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 gem_dependency_check
  284. @gem_dependencies_checked = true
  285. @gem_dependencies_met = yield
  286. end
  287. def dependencies_missing?
  288. @gem_dependencies_checked && !@gem_dependencies_met
  289. end
  290. # Find all Agents that have received Events since the last execution of this method. Update those Agents with
  291. # their new `last_checked_event_id` and queue each of the Agents to be called with #receive using `async_receive`.
  292. # This is called by bin/schedule.rb periodically.
  293. def receive!(options={})
  294. Agent.transaction do
  295. scope = Agent.
  296. select("agents.id AS receiver_agent_id, sources.id AS source_agent_id, events.id AS event_id").
  297. joins("JOIN links ON (links.receiver_id = agents.id)").
  298. joins("JOIN agents AS sources ON (links.source_id = sources.id)").
  299. joins("JOIN events ON (events.agent_id = sources.id AND events.id > links.event_id_at_creation)").
  300. where("NOT agents.disabled AND (agents.last_checked_event_id IS NULL OR events.id > agents.last_checked_event_id)")
  301. if options[:only_receivers].present?
  302. scope = scope.where("agents.id in (?)", options[:only_receivers])
  303. end
  304. sql = scope.to_sql()
  305. agents_to_events = {}
  306. Agent.connection.select_rows(sql).each do |receiver_agent_id, source_agent_id, event_id|
  307. agents_to_events[receiver_agent_id.to_i] ||= []
  308. agents_to_events[receiver_agent_id.to_i] << event_id
  309. end
  310. event_ids = agents_to_events.values.flatten.uniq.compact
  311. Agent.where(:id => agents_to_events.keys).each do |agent|
  312. agent.update_attribute :last_checked_event_id, event_ids.max
  313. Agent.async_receive(agent.id, agents_to_events[agent.id].uniq)
  314. end
  315. {
  316. :agent_count => agents_to_events.keys.length,
  317. :event_count => event_ids.length
  318. }
  319. end
  320. end
  321. # This method will enqueue an AgentReceiveJob job. It accepts Agent and Event ids instead of a literal ActiveRecord
  322. # models because it is preferable to serialize jobs with ids.
  323. def async_receive(agent_id, event_ids)
  324. AgentReceiveJob.perform_later(agent_id, event_ids)
  325. end
  326. # Given a schedule name, run `check` via `bulk_check` on all Agents with that schedule.
  327. # This is called by bin/schedule.rb for each schedule in `SCHEDULES`.
  328. def run_schedule(schedule)
  329. return if schedule == 'never'
  330. types = where(:schedule => schedule).group(:type).pluck(:type)
  331. types.each do |type|
  332. type.constantize.bulk_check(schedule)
  333. end
  334. end
  335. # Schedule `async_check`s for every Agent on the given schedule. This is normally called by `run_schedule` once
  336. # per type of agent, so you can override this to define custom bulk check behavior for your custom Agent type.
  337. def bulk_check(schedule)
  338. raise "Call #bulk_check on the appropriate subclass of Agent" if self == Agent
  339. where("agents.schedule = ? and disabled = false", schedule).pluck("agents.id").each do |agent_id|
  340. async_check(agent_id)
  341. end
  342. end
  343. # This method will enqueue an AgentCheckJob job. It accepts an Agent id instead of a literal Agent because it is
  344. # preferable to serialize job with ids, instead of with the full Agents.
  345. def async_check(agent_id)
  346. AgentCheckJob.perform_later(agent_id)
  347. end
  348. end
  349. end
  350. class AgentDrop
  351. def type
  352. @object.short_type
  353. end
  354. [
  355. :name,
  356. :type,
  357. :options,
  358. :memory,
  359. :sources,
  360. :receivers,
  361. :schedule,
  362. :controllers,
  363. :control_targets,
  364. :disabled,
  365. :keep_events_for,
  366. :propagate_immediately,
  367. ].each { |attr|
  368. define_method(attr) {
  369. @object.__send__(attr)
  370. } unless method_defined?(attr)
  371. }
  372. end