app/models/mixins/retirement_mixin.rb
module RetirementMixin
extend ActiveSupport::Concern
RETIREMENT_ERROR = 'error'.freeze
RETIREMENT_INITIALIZING = 'initializing'.freeze
RETIREMENT_RETIRED = 'retired'.freeze
RETIREMENT_RETIRING = 'retiring'.freeze
included do
scope :scheduled_to_retire, -> { where(arel_table[:retires_on].not_eq(nil).or(arel_table[:retired].not_eq(true))) }
end
module ClassMethods
def make_retire_request(*src_ids, requester, initiated_by: 'user')
klass = (base_class.name.demodulize + "RetireRequest").constantize
options = {:src_ids => src_ids.presence, :__initiated_by__ => initiated_by, :__request_type__ => klass.request_types.first}
set_retirement_requester(options[:src_ids], requester)
klass.make_request(nil, options, requester)
end
def retire(ids, options = {})
ids.each do |id|
object = find_by(:id => id)
object.retire(options) if object.respond_to?(:retire)
end
q_options = {:class_name => 'RetirementManager', :method_name => 'check'}
user = User.current_user
q_options.merge!(:user_id => user.id, :group_id => user.current_group.id, :tenant_id => user.current_tenant.id) if user
MiqQueue.put(q_options)
end
def set_retirement_requester(obj_ids, requester)
existing_objects = where(:id => obj_ids)
updated_count = existing_objects.update_all(:retirement_requester => requester.userid)
if updated_count != obj_ids.count
_log.info("Retirement requester for #{name} #{obj_ids - existing_objects.pluck(:id)} not set because objects not found")
else
_log.info("Retirement requester for #{name} #{obj_ids} being set to #{requester.userid}")
end
end
end
def retirement_warn=(days)
if retirement_warn != days
self.retirement_last_warn = nil # Reset so that a new warning can be sent out when the time is right
write_attribute(:retirement_warn, days)
self.retirement_requester = nil
end
end
def retirement_warned?
!retirement_last_warn.nil?
end
def retirement_warning_due?
retirement_warn && retires_on && retirement_warn.days.from_now >= retires_on
end
def retirement_due?
retires_on && (Time.zone.now >= retires_on)
end
def retires_on=(timestamp)
return if retires_on == timestamp
if timestamp.nil? || (timestamp > Time.zone.now)
self.retired = false
_log.warn("Resetting retirement state from #{retirement_state} for id:<#{id}>, name:<#{name}>") unless retirement_state.nil?
self.retirement_state = nil
end
self.retirement_last_warn = nil # Reset so that a new warning can be sent out when the time is right
self[:retires_on] = timestamp
self.retirement_requester = nil
end
def extend_retires_on(days, date = Time.zone.now)
raise _("Invalid Date specified: %{date}") % {:date => date} unless date.kind_of?(ActiveSupport::TimeWithZone)
_log.info("Extending Retirement Date on #{self.class.name} for id:<#{id}>, name:<#{name}> ")
new_retires_date = date.in_time_zone + days.to_i.days
_log.info("Original Date: #{date} Extend days: #{days} New Retirement Date: #{new_retires_date} for id:<#{id}>, name:<#{name}>")
self.retires_on = new_retires_date
save
end
def retire(options = {})
return unless options.keys.any? { |key| [:date, :warn].include?(key) }
message = "#{retirement_object_title}: [id:<#{id}>, name:<#{name}>]"
if options.key?(:date)
date = nil
date = options[:date].in_time_zone unless options[:date].nil?
self.retires_on = date
if date
message += " is scheduled to retire on: [#{retires_on.strftime("%x %R %Z")}]"
else
message += " is no longer scheduled to retire"
end
end
if options.key?(:warn)
message += " and" if options.key?(:date)
warn = options[:warn]
self.retirement_warn = warn
if warn
message += " has a value for retirement warning days of: [#{retirement_warn}]"
else
message += " has no value for retirement warning days"
end
end
save
raise_retire_audit_event(message)
end
def raise_retire_audit_event(message)
event_name = "#{retirement_event_prefix}_scheduled_to_retire"
_log.info(message.to_s)
raise_audit_event(event_name, message)
end
def retirement_check
return if retired? || retiring? || retirement_initialized?
requester = system_context_requester
if !retirement_warned? && retirement_warning_due?
begin
self.retirement_last_warn = Time.now.utc
save
raise_retirement_event(retire_warn_event_name, requester)
rescue => err
_log.log_backtrace(err)
end
end
if retirement_due?
if allow_retire_request_creation?
self.class.make_retire_request(id, requester, :initiated_by => 'system')
else
_log.warn("Attempt to create duplicate retirement request for id:<#{id}>, name:<#{name}> has been terminated")
end
end
end
def allow_retire_request_creation?
true
end
def retire_now(requester = nil)
if retired
return if retired_validated?
_log.info("#{retirement_object_title}: [id:<#{id}>, name:<#{name}>], Retires On: [#{retires_on.strftime("%x %R %Z")}], was previously retired, but currently #{retired_invalid_reason}")
elsif retiring?
_log.info("#{retirement_object_title}: [id:<#{id}>, name:<#{name}>] retirement in progress")
else
lock do
reload
if error_retiring? || retirement_state.blank?
update(:retirement_state => "initializing", :retirement_requester => requester)
event_name = "request_#{retirement_event_prefix}_retire"
_log.info("calling #{event_name}")
begin
raise_retirement_event(event_name, requester || User.current_user)
rescue => err
_log.log_backtrace(err)
end
else
_log.info("#{retirement_object_title}: retirement for [id:<#{id}>, name:<#{name}>] got updated while waiting to be unlocked and is now #{retirement_state}")
end
end
end
end
def finish_retirement
raise _("%{name} already retired") % {:name => name} if retired?
$log.info("Finishing Retirement for [#{name}]")
requester = retirement_requester
mark_retired
message = "#{self.class.base_model.name}: [id:<#{id}>, name:<#{name}>] with Retires On value: [#{retires_on.strftime("%x %R %Z")}], has been retired"
$log.info("Calling audit event for: #{message} ")
raise_audit_event(retired_event_name, message, requester)
$log.info("Called audit event for: #{message} ")
end
def mark_retired
self[:retires_on] = Time.zone.now
update(:retired => true, :retirement_state => "retired")
end
def start_retirement
return if retired? || retiring?
$log.info("Starting Retirement for [id:<#{id}>, name:<#{name}>]")
update(:retirement_state => "retiring")
end
def retired_validated?
true
end
def retired_invalid_reason
""
end
def retirement_base_model_name
@retirement_base_model_name ||= self.class.base_model.name
end
def retirement_initialized?
retirement_state == RETIREMENT_INITIALIZING
end
def retirement_object_title
@retirement_object_title ||= retirement_base_model_name
end
def retirement_event_prefix
@retirement_event_prefix ||= retirement_object_title.underscore
end
def retire_warn_event_name
@retire_warn_event_name ||= "#{retirement_event_prefix}_retire_warn".freeze
end
def retired_event_name
@retired_event_name ||= "#{retirement_event_prefix}_retired".freeze
end
def raise_retirement_event(event_name, requester = nil)
q_options = q_user_info(retire_queue_options, requester)
$log.info("Raising Retirement Event for [id:<#{id}>, name:<#{name}>] with queue options: #{q_options.inspect}")
MiqEvent.raise_evm_event(self, event_name, setup_event_hash(requester), q_options)
end
def raise_audit_event(event_name, message, requester = nil)
event_hash = {
:target_class => retirement_base_model_name,
:target_id => id.to_s,
:event => event_name,
:message => message
}
event_hash[:userid] = requester if requester.present?
AuditEvent.success(event_hash)
end
def retiring?
retirement_state == RETIREMENT_RETIRING
end
def error_retiring?
retirement_state == RETIREMENT_ERROR
end
private
def retire_queue_options
valid_zone? ? {:zone => my_zone} : {}
end
def valid_zone?
respond_to?(:my_zone) && my_zone.present?
end
def system_context_requester
if evm_owner.blank?
$log.info("System context defaulting to admin user because owner of id:<#{id}>, name:<#{name}> (#{self.class}) not set or owner no longer found in database.")
return User.super_admin
end
if evm_owner.current_group.nil?
$log.info("System context defaulting to admin user because owner of id:<#{id}>, name:<#{name}> (#{self.class}) was found but lacks a group.")
return User.super_admin
end
$log.info("Setting retirement requester of id:<#{id}>, name:<#{name}> to #{evm_owner_id}.")
evm_owner
end
def q_user_info(q_options, requester)
if requester && requester.kind_of?(String)
requester = User.find_by(:userid => requester)
end
if requester
q_options[:user_id] = requester.id
if requester.current_group.present? && requester.current_tenant.present?
q_options[:group_id] = requester.current_group.id
q_options[:tenant_id] = requester.current_tenant.id
end
end
q_options
end
def setup_event_hash(requester)
{}.tap do |event|
event[:userid] = requester
event[retirement_base_model_name.underscore.to_sym] = self
event[:host] = host if respond_to?(:host)
event[:type] ||= self.class.name
end
end
end