agent.rb 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492
  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/huginn/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.merge(inbound_event: current_event))
  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. attr_accessor :current_event
  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. def is_positive_integer?(value)
  239. Integer(value) >= 0
  240. rescue
  241. false
  242. end
  243. # Class Methods
  244. class << self
  245. def build_clone(original)
  246. new(original.slice(:type, :options, :service_id, :schedule, :controller_ids, :control_target_ids,
  247. :source_ids, :receiver_ids, :keep_events_for, :propagate_immediately, :scenario_ids)) { |clone|
  248. # Give it a unique name
  249. 2.step do |i|
  250. name = '%s (%d)' % [original.name, i]
  251. unless exists?(name: name)
  252. clone.name = name
  253. break
  254. end
  255. end
  256. }
  257. end
  258. def cannot_be_scheduled!
  259. @cannot_be_scheduled = true
  260. end
  261. def cannot_be_scheduled?
  262. !!@cannot_be_scheduled
  263. end
  264. def default_schedule(schedule = nil)
  265. @default_schedule = schedule unless schedule.nil?
  266. @default_schedule
  267. end
  268. def cannot_create_events!
  269. @cannot_create_events = true
  270. end
  271. def cannot_create_events?
  272. !!@cannot_create_events
  273. end
  274. def cannot_receive_events!
  275. @cannot_receive_events = true
  276. end
  277. def cannot_receive_events?
  278. !!@cannot_receive_events
  279. end
  280. def can_control_other_agents?
  281. include? AgentControllerConcern
  282. end
  283. def can_dry_run!
  284. @can_dry_run = true
  285. end
  286. def can_dry_run?
  287. !!@can_dry_run
  288. end
  289. def no_bulk_receive!
  290. @no_bulk_receive = true
  291. end
  292. def no_bulk_receive?
  293. !!@no_bulk_receive
  294. end
  295. def gem_dependency_check
  296. @gem_dependencies_checked = true
  297. @gem_dependencies_met = yield
  298. end
  299. def dependencies_missing?
  300. @gem_dependencies_checked && !@gem_dependencies_met
  301. end
  302. # Find all Agents that have received Events since the last execution of this method. Update those Agents with
  303. # their new `last_checked_event_id` and queue each of the Agents to be called with #receive using `async_receive`.
  304. # This is called by bin/schedule.rb periodically.
  305. def receive!(options={})
  306. Agent.transaction do
  307. scope = Agent.
  308. select("agents.id AS receiver_agent_id, sources.type AS source_agent_type, agents.type AS receiver_agent_type, events.id AS event_id").
  309. joins("JOIN links ON (links.receiver_id = agents.id)").
  310. joins("JOIN agents AS sources ON (links.source_id = sources.id)").
  311. joins("JOIN events ON (events.agent_id = sources.id AND events.id > links.event_id_at_creation)").
  312. where("NOT agents.disabled AND NOT agents.deactivated AND (agents.last_checked_event_id IS NULL OR events.id > agents.last_checked_event_id)")
  313. if options[:only_receivers].present?
  314. scope = scope.where("agents.id in (?)", options[:only_receivers])
  315. end
  316. sql = scope.to_sql
  317. agents_to_events = {}
  318. Agent.connection.select_rows(sql).each do |receiver_agent_id, source_agent_type, receiver_agent_type, event_id|
  319. begin
  320. Object.const_get(source_agent_type)
  321. Object.const_get(receiver_agent_type)
  322. rescue NameError
  323. next
  324. end
  325. agents_to_events[receiver_agent_id.to_i] ||= []
  326. agents_to_events[receiver_agent_id.to_i] << event_id
  327. end
  328. Agent.where(:id => agents_to_events.keys).each do |agent|
  329. event_ids = agents_to_events[agent.id].uniq
  330. agent.update_attribute :last_checked_event_id, event_ids.max
  331. if agent.no_bulk_receive?
  332. event_ids.each { |event_id| Agent.async_receive(agent.id, [event_id]) }
  333. else
  334. Agent.async_receive(agent.id, event_ids)
  335. end
  336. end
  337. {
  338. :agent_count => agents_to_events.keys.length,
  339. :event_count => agents_to_events.values.flatten.uniq.compact.length
  340. }
  341. end
  342. end
  343. # This method will enqueue an AgentReceiveJob job. It accepts Agent and Event ids instead of a literal ActiveRecord
  344. # models because it is preferable to serialize jobs with ids.
  345. def async_receive(agent_id, event_ids)
  346. AgentReceiveJob.perform_later(agent_id, event_ids)
  347. end
  348. # Given a schedule name, run `check` via `bulk_check` on all Agents with that schedule.
  349. # This is called by bin/schedule.rb for each schedule in `SCHEDULES`.
  350. def run_schedule(schedule)
  351. return if schedule == 'never'
  352. types = where(:schedule => schedule).group(:type).pluck(:type)
  353. types.each do |type|
  354. next unless valid_type?(type)
  355. type.constantize.bulk_check(schedule)
  356. end
  357. end
  358. # Schedule `async_check`s for every Agent on the given schedule. This is normally called by `run_schedule` once
  359. # per type of agent, so you can override this to define custom bulk check behavior for your custom Agent type.
  360. def bulk_check(schedule)
  361. raise "Call #bulk_check on the appropriate subclass of Agent" if self == Agent
  362. where("NOT disabled AND NOT deactivated AND schedule = ?", schedule).pluck("agents.id").each do |agent_id|
  363. async_check(agent_id)
  364. end
  365. end
  366. # This method will enqueue an AgentCheckJob job. It accepts an Agent id instead of a literal Agent because it is
  367. # preferable to serialize job with ids, instead of with the full Agents.
  368. def async_check(agent_id)
  369. AgentCheckJob.perform_later(agent_id)
  370. end
  371. end
  372. end
  373. class AgentDrop
  374. def type
  375. @object.short_type
  376. end
  377. METHODS = %i[
  378. id
  379. name
  380. type
  381. options
  382. memory
  383. sources
  384. receivers
  385. schedule
  386. controllers
  387. control_targets
  388. disabled
  389. keep_events_for
  390. propagate_immediately
  391. ]
  392. METHODS.each { |attr|
  393. define_method(attr) {
  394. @object.__send__(attr)
  395. } unless method_defined?(attr)
  396. }
  397. def working
  398. @object.working?
  399. end
  400. def url
  401. Rails.application.routes.url_helpers.agent_url(@object, Rails.application.config.action_mailer.default_url_options)
  402. end
  403. end