Created
March 8, 2017 11:52
-
-
Save iNecas/96b69a1bf55256ae5241b9ba055df77a to your computer and use it in GitHub Desktop.
sat61_tasks_cleaner.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Usage: | |
# 1. edit the TASK_SEARCH and AFTER values | |
# 2. run `foreman-rake console < sat61_tasks_cleaner.rb` | |
TASK_SEARCH='' | |
AFTER='30d' | |
module ForemanTasks | |
# Represents the cleanup mechanism for tasks | |
class Cleaner | |
def self.run(options) | |
if options.key?(:filter) | |
new(options).delete | |
else | |
[:after, :states].each do |invalid_option| | |
if options.key?(invalid_option) | |
raise "The option #{invalid_option} is not valid unless the filter specified" | |
end | |
end | |
if cleanup_settings[:after] | |
new(options.merge(:filter => '', :after => cleanup_settings[:after])).delete | |
end | |
actions_with_default_cleanup.each do |action_class, period| | |
new(options.merge(:filter => "label = #{action_class.name}", :after => period)).delete | |
end | |
end | |
end | |
def self.actions_with_default_cleanup | |
actions_with_periods = {} | |
if cleanup_settings[:actions] | |
cleanup_settings[:actions].each do |action| | |
begin | |
action_class = action[:name].constantize | |
actions_with_periods[action_class] = action[:after] | |
rescue => e | |
Foreman::Logging.exception("Error handling #{action} cleanup settings", e) | |
end | |
end | |
end | |
(ForemanTasks.dynflow.world.action_classes - actions_with_periods.keys).each do |action_class| | |
if action_class.respond_to?(:cleanup_after) | |
actions_with_periods[action_class] = action_class.cleanup_after | |
end | |
end | |
actions_with_periods | |
end | |
def self.cleanup_settings | |
return @cleanup_settings if @cleanup_settings | |
@cleanup_settings = SETTINGS[:'foreman-tasks'] && SETTINGS[:'foreman-tasks'][:cleanup] || {} | |
end | |
attr_reader :filter, :after, :states, :verbose, :batch_size, :noop, :full_filter | |
# @param filter [String] scoped search matching the tasks to be deleted | |
# @param after [String|nil] delete the tasks after they are older | |
# than the value: the number in string is expected | |
# to be followed by time unit specification one of s,h,d,y for | |
# seconds ago. If not specified, no implicit filtering on the date. | |
def initialize(options = {}) | |
default_options = { :after => '0s', | |
:verbose => false, | |
:batch_size => 1000, | |
:noop => false, | |
:states => ['stopped'] } | |
options = default_options.merge(options) | |
@filter = options[:filter] | |
@after = parse_time_interval(options[:after]) | |
@states = options[:states] | |
@verbose = options[:verbose] | |
@batch_size = options[:batch_size] | |
@noop = options[:noop] | |
raise ArgumentError, 'filter not speficied' if @filter.nil? | |
@full_filter = prepare_filter | |
end | |
# Delete the filtered tasks, including the dynflow execution plans | |
def delete | |
if noop | |
say "[noop] deleting all tasks matching filter #{full_filter}" | |
say "[noop] #{ForemanTasks::Task.search_for(full_filter).size} tasks would be deleted" | |
else | |
start_tracking_progress | |
while (chunk = ForemanTasks::Task.search_for(full_filter).limit(batch_size)).any? | |
delete_tasks(chunk) | |
delete_dynflow_plans(chunk) | |
report_progress(chunk) | |
end | |
end | |
end | |
def tasks | |
ForemanTasks::Task.search_for(full_filter).select('DISTINCT foreman_tasks_tasks.id, foreman_tasks_tasks.type, foreman_tasks_tasks.external_id') | |
end | |
def delete_tasks(chunk) | |
ForemanTasks::Task.where(:id => chunk.map(&:id)).delete_all | |
end | |
def delete_dynflow_plans(chunk) | |
dynflow_ids = chunk.find_all { |task| task.is_a? Task::DynflowTask }.map(&:external_id) | |
ForemanTasks.dynflow.world.persistence.delete_execution_plans({ 'uuid' => dynflow_ids }, batch_size) | |
end | |
def prepare_filter | |
filter_parts = [filter] | |
filter_parts << %(started_at < "#{after.ago.to_s(:db)}") if after > 0 | |
filter_parts << states.map { |s| "state = #{s}" }.join(' OR ') if states.any? | |
filter_parts.select(&:present?).join(' AND ') | |
end | |
def start_tracking_progress | |
if verbose | |
@current = 0 | |
@total = tasks.size | |
say "#{@current}/#{@total}", false | |
end | |
end | |
def report_progress(chunk) | |
if verbose | |
@current += chunk.size | |
say "#{@current}/#{@total}", false | |
end | |
end | |
def say(message, log = true) | |
puts message | |
Foreman::Logging.logger('foreman-tasks').info(message) if log | |
end | |
def parse_time_interval(string) | |
matched_string = string.delete(' ').match(/\A(\d+)(\w)\Z/) | |
unless matched_string | |
raise ArgumentError, "String #{string} isn't an expected specification of time in format of \"{number}{time_unit}\"" | |
end | |
number = matched_string[1].to_i | |
value = case matched_string[2] | |
when 's' | |
number.seconds | |
when 'h' | |
number.hours | |
when 'd' | |
number.days | |
when 'y' | |
number.years | |
else | |
raise ArgumentError, "Unexpected time unit in #{string}, expected one of [s,h,d,y]" | |
end | |
value | |
end | |
end | |
end | |
ForemanTasks::Cleaner.run(:filter => TASK_SEARCH, :verbose => true, :after => AFTER) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment