agent.rb 14 KB

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