app/models/kuroko2/job_instance.rb



class Kuroko2::JobInstance < Kuroko2::ApplicationRecord
  include Kuroko2::TableNameCustomizable

  belongs_to :job_definition

  attr_accessor :log_message

  has_many :logs, dependent: :delete_all do
    def info(message)
      add('INFO', message)
    end

    def warn(message)
      add('WARN', message)
    end

    def error(message)
      add('ERROR', message)
    end

    private
    def add(level, message)
      self.create(level: level, message: message)
    end
  end
  has_many :tokens, dependent: :restrict_with_exception
  has_many :executions, dependent: :restrict_with_exception
  has_one :memory_consumption_log, dependent: :destroy

  before_create :copy_script
  after_create :notify_launch
  after_create :generate_token

  scope :working, -> { where(finished_at: nil, canceled_at: nil) }
  scope :finished, -> { where.not(finished_at: nil) }

  def error?
    working? && error_at?
  end

  def working?
    !finished_at? && !canceled_at?
  end

  def cancelable?
    tokens.first.try(:cancelable?)
  end

  def cancel(by:)
    self.tokens.destroy(*self.tokens)
    self.executions.destroy(*self.executions)
    self.touch(:canceled_at)

    message = "This job was canceled by #{by}."
    self.logs.warn(message)
    Kuroko2.logger.warn(message)

    Kuroko2::Workflow::Notifier.notify(:cancellation, self) if job_definition.hipchat_notify_finished?
  end

  # Log given value if it is greater than stored one.
  # This logging is not so important that we can ignore race condition,
  # so we use `#update` and `#create_association` without bang here.
  # @param [Intger] value
  def log_memory_consumption(value)
    if memory_consumption_log
      max = [value, memory_consumption_log.value].max
      memory_consumption_log.update(value: max)
    else
      create_memory_consumption_log(value: value)
    end
  end

  def execution_minutes
    (((error_at || canceled_at || finished_at || Time.current) - created_at).to_f / 60).round(2)
  end

  def status
    if finished_at?
      'success'
    elsif canceled_at?
      'canceled'
    elsif error_at?
      'error'
    else
      'working'
    end
  end

  private

  def copy_script
    self.script = job_definition.try(:script) if self.script.blank?
  end

  def notify_launch
    if log_message
      Kuroko2.logger.info(log_message)
      self.logs.info(log_message)
      Kuroko2::Workflow::Notifier.notify(:launch, self)
    end
  end

  def generate_token
    unless self.job_definition
      raise 'No parent association is found'
    end

    if job_definition.proceed_multi_instance?
      self.tokens << Kuroko2::Token.new do |token|
        definition = self.job_definition

        token.job_definition         = definition
        token.job_definition_version = definition.version
        token.script                 = self.script
        token.context                = {
          meta: {
            launched_time:       Time.current,
            job_definition_id:   definition.id,
            job_definition_name: definition.name,
            job_instance_id:     id,
          }
        }
      end
    else
      self.touch(:canceled_at)

      message = 'This job was canceled because there is already a working or erred job instance.'
      self.logs.warn(message)
      Kuroko2.logger.warn(message)

      Kuroko2::Workflow::Notifier.notify(:cancellation, self) if job_definition.notify_cancellation
    end
  end
end