foreman-tasks 0.7.1 → 0.7.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- NDU0Mzk4MWU2NDIzMmY3ZDVhMTEyZDdhN2RiNDcyNGVjZGVjMGQ1Mg==
4
+ MDE1OGU2NTc0M2VkNTdhYjUwODE4YTM1YTNiMTVmYWQwNTUyNjIxZQ==
5
5
  data.tar.gz: !binary |-
6
- MTcyNGQ0ZDdhN2JkZWY0NzNiM2Q1MzBkNWUzZDU5Y2I0MjMxNjliZQ==
6
+ YmU0NzM5ZjNhZTMxODdjZjVkYTFkYTkwYWY5YjhiZDljMjUxNGZkZA==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- Yjg3Yjk4OTJlMmY5MDk0OWUyZWI0MDRmOTIyYjFmMDY2NDE2ZjU0YTNmODgz
10
- ODI1YWY1NTBjNDk3NTgwOWFmZjgyZDhiMzcxNzVlNDg1ZTM3ODNjZjNhOWEw
11
- NDcxZWY4MDEyNTJkZDk0M2RkNDQ0YTNlOWMxZDNkOTIzZDdkN2M=
9
+ Yzc2NTgwODNhODY2MTM5MWFjNmU3NzBkODZlZmNhYjZjZTJlZTIxMzVlZjll
10
+ MDQ1OTk0ZmE4MTk5ZDRiMmUyMzViNzQxYTdjZDViNmMyNTcyZjQwNmIyODI0
11
+ MzRiZjM4ZjA0YWJlNTRkZGY1ZjVjODNiNWJjMmZkYjAxYWU5OWM=
12
12
  data.tar.gz: !binary |-
13
- OGQzOWFmNTgyZjJlMTIyNDRiNmY4Nzk4OTdlYTEyZmMzNmFlMmY4MzE3YmM5
14
- M2YxMmVlOWRiMDZiODI1ZjZiMWQ4ZDhkMDMzNDIwZGY1MGY4NzEyODRhNmFj
15
- ZTNhOWZiY2YwYjE2MDFlMTE2YjBjNjhkYTY3NjYxYTE0MmRjZjQ=
13
+ Y2VjMzU0Y2UxNjg0YjJlNGY1NDMwMWVjMzQ3NGMwNGVmMzI1ZjhkMmM2YWRl
14
+ NTU1NGJmZDVhMWFiODkyYjQzMzJlMmExYTI3YmJmNjY4MGU1Yzg2ZGIwNGY1
15
+ Njc1NzE3YTRmZmYxOTVjN2IyMGVhYmFiMDkyNmY1ZjM1ZTcyNmM=
data/README.md CHANGED
@@ -93,6 +93,7 @@ it would be:
93
93
  ```ruby
94
94
  initializer "your_engine.require_dynflow", :before => "foreman_tasks.initialize_dynflow" do |app|
95
95
  ForemanTasks.dynflow.require!
96
+ ForemanTasks.dynflow.config.eager_load_paths << File.join(YourEngine::Engine.root, 'app/lib/actions')
96
97
  end
97
98
  ```
98
99
 
@@ -128,13 +129,73 @@ The executor process needs to be executed before the web server. You
128
129
  can run it by:
129
130
 
130
131
  ```
131
- RAILS_ENV=production bundle exec rake foreman_tasks:dynflow:executor
132
+ foreman-rake foreman_tasks:dynflow:executor
132
133
  ```
133
134
 
134
135
  Also, there is a possibility to run the executor in daemonized mode
135
136
  using the `dynflow-executor`. It expects to be executed from Foreman
136
137
  rails root directory. See `-h` for more details and options
137
138
 
139
+ Tasks cleanup
140
+ -------------
141
+
142
+ Although, the history of tasks has an auditing value, some kinds of
143
+ tasks can grow up in number quite soon. Therefore there is a mechanism
144
+ how to clean the tasks, using a rake command. When running without
145
+ any arguments, the tasks are deleted based on the default parameters
146
+ defined in the code.
147
+
148
+ ```
149
+ foreman-rake foreman_tasks:cleanup
150
+ ```
151
+
152
+ To see what tasks would be deleted, without actually deleting the records, you can run
153
+
154
+ ```
155
+ foreman-rake foreman_tasks:cleanup NOOP=true
156
+ ```
157
+
158
+ By default, only the actions explicitly defined with expiration time
159
+ in the code, will get cleaned. One can configure new actions, or
160
+ override the default configuration inside the configuration
161
+ `config/settings.plugins.d/foreman_tasks.yaml`, such as:
162
+
163
+
164
+ ```
165
+ :foreman-tasks:
166
+ :cleanup:
167
+ # the period after witch to delete all the tasks (by default all tasks are not being deleted after some period)
168
+ :after: 365d
169
+ # per action settings to override the default defined in the actions (cleanup_after method)
170
+ :actions:
171
+ - :name: Actions::Foreman::Host::ImportFacts
172
+ :after: 10d
173
+
174
+ ```
175
+
176
+ The `foreman_tasks:cleanup` script also accepts additional parameters
177
+ to specify the search criteria for the cleanup manually:
178
+
179
+ * `FILTER`: scoped search filter (example: 'label = "Actions::Foreman::Host::ImportFacts"')
180
+ * `AFTER`: delete tasks created after `AFTER` period. Expected format
181
+ is a number followed by the time unit (`s`, `h`, `m`, `y`), such as
182
+ `10d` for 10 days (applicable only when the `FILTER` option is specified)
183
+ * `STATES`: comma separated list of task states to touch with the
184
+ cleanup, by default only stopped tasks are affected
185
+ (applicable only when the `FILTER` option is specified)
186
+ * `NOOP`: set to "true" if the task should not actuall perform the
187
+ deletion, only report the actions the script would perform
188
+ * `VERBOSE`: set to "true" for more verbose output
189
+ * `BATCH_SIZE`: the size of batches the tasks get processed in (1000 by default)
190
+
191
+ To see the current configuration (what actions get cleaned
192
+ automatically and what is their `after` period), this script can be
193
+ used:
194
+
195
+ ```
196
+ foreman-rake foreman_tasks:cleanup:config
197
+ ```
198
+
138
199
  Issues
139
200
  ------
140
201
 
@@ -93,7 +93,7 @@ module ForemanTasks
93
93
 
94
94
  def filter(scope)
95
95
  scope.search_for(params[:search], :order => params[:order]).
96
- paginate(:page => params[:page])
96
+ paginate(:page => params[:page]).select('DISTINCT foreman_tasks_tasks.*')
97
97
  end
98
98
 
99
99
  end
@@ -35,6 +35,11 @@ module Actions
35
35
  input[:host] && input[:host][:name]
36
36
  end
37
37
 
38
+ # default value for cleaning up the tasks, it can be overriden by settings
39
+ def self.cleanup_after
40
+ '30d'
41
+ end
42
+
38
43
  end
39
44
  end
40
45
  end
@@ -23,6 +23,7 @@ module ForemanTasks
23
23
  scoped_search :on => :state, :complete_value => true
24
24
  scoped_search :on => :result, :complete_value => true
25
25
  scoped_search :on => :started_at, :complete_value => false
26
+ scoped_search :on => :parent_task_id, :complete_value => true
26
27
  scoped_search :in => :locks, :on => :resource_type, :complete_value => true, :rename => "resource_type", :ext_method => :search_by_generic_resource
27
28
  scoped_search :in => :locks, :on => :resource_id, :complete_value => false, :rename => "resource_id", :ext_method => :search_by_generic_resource
28
29
  scoped_search :in => :owners, :on => :id, :complete_value => true, :rename => "owner.id", :ext_method => :search_by_owner
@@ -85,15 +85,16 @@ module ForemanTasks
85
85
 
86
86
  def self.consistency_check
87
87
  fixed_count = 0
88
+ logger = Foreman::Logging.logger('foreman-tasks')
88
89
  self.running.each do |task|
89
90
  begin
90
91
  changes = task.update_from_dynflow(task.execution_plan.to_hash)
91
92
  unless changes.empty?
92
93
  fixed_count += 1
93
- Rails.logger.warn("Task %s updated at consistency check: %s" % [task.id, changes.inspect])
94
+ logger.warn("Task %s updated at consistency check: %s" % [task.id, changes.inspect])
94
95
  end
95
96
  rescue => e
96
- Rails.logger.warn("Failed at consistency check for task %s: %s\n %s" % [task.id, e.message, e.backtrace.join("\n")])
97
+ Foreman::Logging.exception("Failed at consistency check for task #{task.id}", e, :logger => logger)
97
98
  end
98
99
  end
99
100
  return fixed_count
@@ -14,11 +14,14 @@
14
14
  </style>
15
15
 
16
16
  <script>
17
+
18
+ var currentTwoPaneTask;
19
+
17
20
  $(document).on('click', ".table-two-pane td.two-pane-link", function(e) {
18
- var item = $(this).find("a");
19
- if(item.length){
21
+ currentTwoPaneTask = $(this).find("a");
22
+ if(currentTwoPaneTask.length){
20
23
  e.preventDefault();
21
- two_pane_open(item);
24
+ two_pane_open(currentTwoPaneTask);
22
25
  }
23
26
  });
24
27
 
@@ -1,44 +1,45 @@
1
1
  <script>
2
- var taskProgressReloader = {
3
- timeoutId: null,
4
- reload: function () {
5
- $.ajax({
6
- url: "",
7
- context: document.body,
8
- success: function (s, x) {
9
- $(this).html(s);
10
- taskProgressReloader.start();
2
+ if (typeof taskProgressReloader === 'undefined') {
3
+ var taskProgressReloader = {
4
+ timeoutId: null,
5
+ reload: function () {
6
+ // we need different reload mechanism for two-pane and non-two-pane
7
+ if (typeof currentTwoPaneTask !== 'undefined') {
8
+ taskProgressReloader.timeoutId = null;
9
+ two_pane_open(currentTwoPaneTask);
10
+ } else {
11
+ document.location.reload();
11
12
  }
12
- });
13
- },
13
+ },
14
14
 
15
- start: function () {
16
- if (!this.timeoutId) {
17
- this.timeoutId = setTimeout(this.reload, 5000);
18
- }
19
- var button = $('.reload-button');
20
- button.html('<span class="glyphicon glyphicon-refresh spin"></span> <%= _('Stop auto-reloading') %>');
21
- button.show();
22
- },
15
+ start: function () {
16
+ if (!taskProgressReloader.timeoutId) {
17
+ taskProgressReloader.timeoutId = setTimeout(this.reload, 5000);
18
+ }
19
+ var button = $('.reload-button');
20
+ button.html('<span class="glyphicon glyphicon-refresh spin"></span> <%= _('Stop auto-reloading') %>');
21
+ button.show();
22
+ },
23
23
 
24
- stop: function () {
25
- if (this.timeoutId) {
26
- clearTimeout(this.timeoutId);
27
- }
28
- this.timeoutId = null;
29
- var button = $('.reload-button');
30
- button.html('<span class="glyphicon glyphicon-refresh"></span> <%= _('Start auto-reloading') %>');
31
- button.show();
32
- },
24
+ stop: function () {
25
+ if (taskProgressReloader.timeoutId) {
26
+ clearTimeout(taskProgressReloader.timeoutId);
27
+ }
28
+ taskProgressReloader.timeoutId = null;
29
+ var button = $('.reload-button');
30
+ button.html('<span class="glyphicon glyphicon-refresh"></span> <%= _('Start auto-reloading') %>');
31
+ button.show();
32
+ },
33
33
 
34
- toggle: function () {
35
- if (this.timeoutId) {
36
- this.stop();
37
- } else {
38
- this.start();
34
+ toggle: function () {
35
+ if (taskProgressReloader.timeoutId) {
36
+ this.stop();
37
+ } else {
38
+ this.start();
39
+ }
39
40
  }
40
- }
41
- };
41
+ };
42
+ }
42
43
 
43
44
  $(document).ready(function () {
44
45
  $('.modal-submit').click(function(e){
@@ -0,0 +1,26 @@
1
+ :foreman-tasks:
2
+ #
3
+ # Logging configuration can be changed by uncommenting the loggers
4
+ # section and the logger configuration desired.
5
+ #
6
+ # :loggers:
7
+ # :dynflow:
8
+ # :enabled: true
9
+ # :action:
10
+ # :enabled: true
11
+
12
+
13
+ # Cleaning configuration: how long should the actions be kept before deleted
14
+ # by `rake foreman_tasks:clean` task
15
+ #
16
+ # :cleanup:
17
+ #
18
+ # the period after which to delete all the tasks (by default all tasks are not being deleted after some period)
19
+ #
20
+ # :after: 365d
21
+ #
22
+ # per action settings to override the default defined in the actions (self.cleanup_after method)
23
+ #
24
+ # :actions:
25
+ # - :name: Actions::Foreman::Host::ImportFacts
26
+ # :after: 10d
data/lib/foreman_tasks.rb CHANGED
@@ -4,6 +4,7 @@ require 'foreman_tasks/engine'
4
4
  require 'foreman_tasks/dynflow'
5
5
  require 'foreman_tasks/triggers'
6
6
  require 'foreman_tasks/authorizer_ext'
7
+ require 'foreman_tasks/cleaner'
7
8
 
8
9
  module ForemanTasks
9
10
  extend Algebrick::TypeCheck
@@ -0,0 +1,152 @@
1
+ module ForemanTasks
2
+ # Represents the cleanup mechanism for tasks
3
+ class Cleaner
4
+
5
+ def self.run(options)
6
+ if options.key?(:filter)
7
+ self.new(options).delete
8
+ else
9
+ [:after, :states].each do |invalid_option|
10
+ if options.key?(invalid_option)
11
+ raise "The option #{invalid_option} is not valid unless the filter specified"
12
+ end
13
+ end
14
+ if cleanup_settings[:after]
15
+ self.new(options.merge(:filter => "", :after => cleanup_settings[:after])).delete
16
+ end
17
+ actions_with_default_cleanup.each do |action_class, period|
18
+ self.new(options.merge(:filter => "label = #{action_class.name}", :after => period)).delete
19
+ end
20
+ end
21
+ end
22
+
23
+ def self.actions_with_default_cleanup
24
+ actions_with_periods = {}
25
+ if cleanup_settings[:actions]
26
+ cleanup_settings[:actions].each do |action|
27
+ begin
28
+ action_class = action[:name].constantize
29
+ actions_with_periods[action_class] = action[:after]
30
+ rescue => e
31
+ Foreman::Logging.exception("Error handling #{action} cleanup settings", e)
32
+ end
33
+ end
34
+ end
35
+ (ForemanTasks.dynflow.world.action_classes - actions_with_periods.keys).each do |action_class|
36
+ if action_class.respond_to?(:cleanup_after)
37
+ actions_with_periods[action_class] = action_class.cleanup_after
38
+ end
39
+ end
40
+ return actions_with_periods
41
+ end
42
+
43
+ def self.cleanup_settings
44
+ return @cleanup_settings if @cleanup_settings
45
+ @cleanup_settings = SETTINGS[:'foreman-tasks'] && SETTINGS[:'foreman-tasks'][:cleanup] || {}
46
+ end
47
+
48
+ attr_reader :filter, :after , :states, :verbose, :batch_size, :noop, :full_filter
49
+
50
+ # @param filter [String] scoped search matching the tasks to be deleted
51
+ # @param after [String|nil] delete the tasks after they are older
52
+ # than the value: the number in string is expected
53
+ # to be followed by time unit specification one of s,h,d,y for
54
+ # seconds ago. If not specified, no implicit filtering on the date.
55
+ def initialize(options = {})
56
+ default_options = { :after => '0s',
57
+ :verbose => false,
58
+ :batch_size => 1000,
59
+ :noop => false,
60
+ :states => ["stopped"] }
61
+ options = default_options.merge(options)
62
+
63
+ @filter = options[:filter]
64
+ @after = parse_time_interval(options[:after])
65
+ @states = options[:states]
66
+ @verbose = options[:verbose]
67
+ @batch_size = options[:batch_size]
68
+ @noop = options[:noop]
69
+
70
+ raise ArgumentError, 'filter not speficied' if @filter.nil?
71
+
72
+ @full_filter = prepare_filter
73
+ end
74
+
75
+ # Delete the filtered tasks, including the dynflow execution plans
76
+ def delete
77
+ if noop
78
+ say "[noop] deleting all tasks matching filter #{full_filter}"
79
+ say "[noop] #{ForemanTasks::Task.search_for(full_filter).size} tasks would be deleted"
80
+ else
81
+ start_tracking_progress
82
+ while (chunk = ForemanTasks::Task.search_for(full_filter).limit(batch_size)).any?
83
+ delete_tasks(chunk)
84
+ delete_dynflow_plans(chunk)
85
+ report_progress(chunk)
86
+ end
87
+ end
88
+ end
89
+
90
+ def tasks
91
+ ForemanTasks::Task.search_for(full_filter).select('DISTINCT foreman_tasks_tasks.id, foreman_tasks_tasks.type, foreman_tasks_tasks.external_id')
92
+ end
93
+
94
+ def delete_tasks(chunk)
95
+ ForemanTasks::Task.where(:id => chunk.map(&:id)).delete_all
96
+ end
97
+
98
+ def delete_dynflow_plans(chunk)
99
+ dynflow_ids = chunk.find_all { |task| task.is_a? Task::DynflowTask }.map(&:external_id)
100
+ ForemanTasks.dynflow.world.persistence.delete_execution_plans({ 'uuid' => dynflow_ids }, batch_size)
101
+ end
102
+
103
+ def prepare_filter
104
+ filter_parts = [filter]
105
+ filter_parts << %{started_at < "#{after.ago.to_s(:db)}"} if after > 0
106
+ filter_parts << states.map { |s| "state = #{s}" }.join(" OR ") if states.any?
107
+ filter_parts.select(&:present?).join(' AND ')
108
+ end
109
+
110
+ def start_tracking_progress
111
+ if verbose
112
+ @current, @total = 0, tasks.size
113
+ say "#{@current}/#{@total}", false
114
+ end
115
+ end
116
+
117
+ def report_progress(chunk)
118
+ if verbose
119
+ @current += chunk.size
120
+ say "#{@current}/#{@total}", false
121
+ end
122
+ end
123
+
124
+ def say(message, log = true)
125
+ puts message
126
+ if log
127
+ Foreman::Logging.logger('foreman-tasks').info(message)
128
+ end
129
+ end
130
+
131
+ def parse_time_interval(string)
132
+ matched_string = string.gsub(' ','').match(/\A(\d+)(\w)\Z/)
133
+ unless matched_string
134
+ raise ArgumentError, "String #{string} isn't an expected specification of time in format of \"{number}{time_unit}\""
135
+ end
136
+ number = matched_string[1].to_i
137
+ value = case matched_string[2]
138
+ when 's'
139
+ number.seconds
140
+ when 'h'
141
+ number.hours
142
+ when 'd'
143
+ number.days
144
+ when 'y'
145
+ number.years
146
+ else
147
+ raise ArgumentError, "Unexpected time unit in #{string}, expected one of [s,h,d,y]"
148
+ end
149
+ return value
150
+ end
151
+ end
152
+ end
@@ -1,13 +1,6 @@
1
1
  module ForemanTasks
2
2
  class Dynflow::Configuration
3
3
 
4
- # for logging action related info (such as exceptions raised in side
5
- # the actions' methods
6
- attr_accessor :action_logger
7
-
8
- # for logging dynflow related info about the progress of the execution etc.
9
- attr_accessor :dynflow_logger
10
-
11
4
  # the number of threads in the pool handling the execution
12
5
  attr_accessor :pool_size
13
6
 
@@ -35,8 +28,6 @@ module ForemanTasks
35
28
  attr_accessor :disable_active_record_actions
36
29
 
37
30
  def initialize
38
- self.action_logger = Rails.logger
39
- self.dynflow_logger = Rails.logger
40
31
  self.pool_size = 5
41
32
  self.db_pool_size = pool_size + 5
42
33
  self.remote = Rails.env.production?
@@ -48,6 +39,17 @@ module ForemanTasks
48
39
  @on_init = []
49
40
  end
50
41
 
42
+ # for logging action related info (such as exceptions raised in side
43
+ # the actions' methods
44
+ def action_logger
45
+ Foreman::Logging.logger('foreman-tasks/action')
46
+ end
47
+
48
+ # for logging dynflow related info about the progress of the execution etc.
49
+ def dynflow_logger
50
+ Foreman::Logging.logger('foreman-tasks/dynflow')
51
+ end
52
+
51
53
  def on_init(&block)
52
54
  @on_init << block
53
55
  end
@@ -24,6 +24,7 @@ module ForemanTasks
24
24
  default_options = { foreman_root: Dir.pwd,
25
25
  process_name: 'dynflow_executor',
26
26
  pid_dir: "#{Rails.root}/tmp/pids",
27
+ log_dir: File.join(Rails.root, 'log'),
27
28
  wait_attempts: 300,
28
29
  wait_sleep: 1 }
29
30
  options = default_options.merge(options)
@@ -42,15 +43,17 @@ module ForemanTasks
42
43
 
43
44
  Daemons.run_proc(options[:process_name],
44
45
  :dir => options[:pid_dir],
46
+ :log_dir => options[:log_dir],
45
47
  :dir_mode => :normal,
46
48
  :monitor => true,
47
49
  :log_output => true,
48
50
  :ARGV => [command]) do |*args|
49
51
  begin
52
+ ::Logging.reopen
50
53
  run(options[:foreman_root])
51
54
  rescue => e
52
55
  STDERR.puts e.message
53
- Rails.logger.fatal e
56
+ Foreman::Logging.exception("Failed running foreman-tasks daemon", e)
54
57
  exit 1
55
58
  end
56
59
  end
@@ -13,9 +13,7 @@ module ForemanTasks
13
13
  begin
14
14
  on_execution_plan_save(execution_plan_id, value)
15
15
  rescue => e
16
- ForemanTasks.dynflow.world.logger.error('Error on on_execution_plan_save event')
17
- ForemanTasks.dynflow.world.logger.error(e.message)
18
- ForemanTasks.dynflow.world.logger.error(e.backtrace.join("\n"))
16
+ Foreman::Logging.exception("Error on on_execution_plan_save event", e, :logger => Foreman::Logging.logger('foreman-tasks/dynflow'))
19
17
  end
20
18
  end
21
19
  ensure
@@ -8,7 +8,7 @@ module ForemanTasks
8
8
 
9
9
  initializer 'foreman_tasks.register_plugin', :after => :finisher_hook do |app|
10
10
  Foreman::Plugin.register :"foreman-tasks" do
11
- requires_foreman '>= 1.6'
11
+ requires_foreman '>= 1.9.0'
12
12
  divider :top_menu, :parent => :monitor_menu, :after => :audits
13
13
  menu :top_menu, :tasks,
14
14
  :url_hash => { :controller => 'foreman_tasks/tasks', :action => :index },
@@ -22,6 +22,9 @@ module ForemanTasks
22
22
  :'foreman_tasks/api/tasks' => [:bulk_resume]}, :resource_type => ForemanTasks::Task.name
23
23
  end
24
24
 
25
+ logger :dynflow, :enabled => true
26
+ logger :action, :enabled => true
27
+
25
28
  role "Tasks Manager", [:view_foreman_tasks, :edit_foreman_tasks]
26
29
  role "Tasks Reader", [:view_foreman_tasks]
27
30
 
@@ -91,7 +94,7 @@ module ForemanTasks
91
94
 
92
95
 
93
96
  rake_tasks do
94
- %w[dynflow.rake test.rake export_tasks.rake].each do |rake_file|
97
+ %w[dynflow.rake test.rake export_tasks.rake cleanup.rake].each do |rake_file|
95
98
  full_path = File.expand_path("../tasks/#{rake_file}", __FILE__)
96
99
  load full_path if File.exists?(full_path)
97
100
  end
@@ -0,0 +1,67 @@
1
+ namespace :foreman_tasks do
2
+ namespace :cleanup do
3
+ desc <<DESC
4
+ Clean tasks based on filter and age. ENV variables:
5
+
6
+ * FILTER : scoped search filter (example: 'label = "Actions::Foreman::Host::ImportFacts"')
7
+ * AFTER : delete tasks created after *AFTER* period. Expected format is a number followed by the time unit (s,h,m,y), such as '10d' for 10 days
8
+ * STATES : comma separated list of task states to touch with the cleanup, by default only stopped tasks are covered
9
+ * NOOP : set to "true" if the task should not actuall perform the deletion
10
+ * VERBOSE : set to "true" for more verbose output
11
+ * BATCH_SIZE : the size of batches the tasks get processed in (1000 by default)
12
+
13
+ If none of FILTER, BEFORE, STATES is specified, the tasks will be cleaned based
14
+ configuration in settings
15
+ DESC
16
+ task :run => 'environment' do
17
+ options = {}
18
+
19
+ if ENV['FILTER']
20
+ options[:filter] = ENV['FILTER']
21
+ end
22
+
23
+ if ENV['AFTER']
24
+ options[:after] = ENV['AFTER']
25
+ end
26
+
27
+ if ENV['STATES']
28
+ options[:states] = ENV['STATES'].to_s.split(',')
29
+ end
30
+
31
+ if ENV['NOOP']
32
+ options[:noop] = true
33
+ end
34
+
35
+ if ENV['VERBOSE']
36
+ options[:verbose] = true
37
+ end
38
+
39
+ if ENV['BATCH_SIZE']
40
+ options[:batch_size] = ENV['BATCH_SIZE'].to_i
41
+ end
42
+
43
+ ForemanTasks::Cleaner.run(options)
44
+ end
45
+
46
+ desc 'Show the current configuration for auto-cleanup'
47
+ task :config => 'environment' do
48
+ if ForemanTasks::Cleaner.cleanup_settings[:after]
49
+ puts _('The tasks will be deleted after %{after}') % ForemanTasks::Cleaner.cleanup_settings[:after]
50
+ else
51
+ puts _('Global period for cleaning up tasks is not set')
52
+ end
53
+
54
+ if ForemanTasks::Cleaner.actions_with_default_cleanup.empty?
55
+ puts _('No actions are configured to be cleaned automatically')
56
+ else
57
+ puts _('The following actions are configured to be deleted automatically after some time:')
58
+ printf("%-50s %s\n", _('name'), _('delete after'))
59
+ ForemanTasks::Cleaner.actions_with_default_cleanup.each do |action, after|
60
+ printf("%-50s %s\n", action.name, after)
61
+ end
62
+ end
63
+ end
64
+ end
65
+
66
+ task :cleanup => 'cleanup:run'
67
+ end
@@ -1,3 +1,3 @@
1
1
  module ForemanTasks
2
- VERSION = "0.7.1"
2
+ VERSION = "0.7.2"
3
3
  end
@@ -22,11 +22,18 @@ FactoryGirl.define do
22
22
  result "success"
23
23
  parent_task_id nil
24
24
 
25
- after(:build) do |task|
25
+ transient do
26
+ sync_with_dynflow false
27
+ end
28
+
29
+ after(:build) do |task, evaluator|
26
30
  execution_plan = ForemanTasks.dynflow.world.plan(Support::DummyDynflowAction)
27
31
  # remove the task created automatically by the persistence
28
32
  ForemanTasks::Task.where(:external_id => execution_plan.id).delete_all
29
- task.update_from_dynflow(execution_plan.to_hash)
33
+ task.update_attributes!(:external_id => execution_plan.id)
34
+ if evaluator.sync_with_dynflow
35
+ task.update_from_dynflow(execution_plan.to_hash)
36
+ end
30
37
  end
31
38
 
32
39
  trait :user_create_task do
@@ -0,0 +1,74 @@
1
+ require 'foreman_tasks_test_helper'
2
+
3
+ class TasksTest < ActiveSupport::TestCase
4
+ describe ForemanTasks::Cleaner do
5
+ it 'is able to delete tasks (including the dynflow plans) based on filter' do
6
+ cleaner = ForemanTasks::Cleaner.new(:filter => 'label = "Actions::User::Create"', :after => '10d')
7
+
8
+ tasks_to_delete = [FactoryGirl.create(:dynflow_task, :user_create_task),
9
+ FactoryGirl.create(:dynflow_task, :user_create_task)]
10
+ tasks_to_keep = [FactoryGirl.create(:dynflow_task, :user_create_task) do |task|
11
+ task.started_at = task.ended_at = Time.now
12
+ task.save
13
+ end,
14
+ FactoryGirl.create(:dynflow_task, :product_create_task)]
15
+ cleaner.delete
16
+ ForemanTasks::Task.where(id: tasks_to_delete).must_be_empty
17
+ ForemanTasks::Task.where(id: tasks_to_keep).must_equal tasks_to_keep
18
+
19
+ ForemanTasks.dynflow.world.persistence.
20
+ find_execution_plans(filters: {'uuid' => tasks_to_delete.map(&:external_id)}).size.must_equal 0
21
+
22
+ ForemanTasks.dynflow.world.persistence.
23
+ find_execution_plans(filters: {'uuid' => tasks_to_keep.map(&:external_id)}).size.must_equal tasks_to_keep.size
24
+ end
25
+
26
+ it 'deletes all tasks matching the filter when the time limit is not specified' do
27
+ cleaner = ForemanTasks::Cleaner.new(:filter => 'label = "Actions::User::Create"')
28
+ tasks_to_delete = [FactoryGirl.create(:dynflow_task, :user_create_task),
29
+ FactoryGirl.create(:dynflow_task, :user_create_task) do |task|
30
+ task.started_at = task.ended_at = Time.now
31
+ task.save
32
+ end]
33
+
34
+ tasks_to_keep = [FactoryGirl.create(:dynflow_task, :product_create_task)]
35
+ cleaner.delete
36
+ ForemanTasks::Task.where(id: tasks_to_delete).must_be_empty
37
+ ForemanTasks::Task.where(id: tasks_to_keep).must_equal tasks_to_keep
38
+ end
39
+
40
+ it 'supports passing empty filter (just delete all)' do
41
+ cleaner = ForemanTasks::Cleaner.new(:filter => '', :after => '10d')
42
+ tasks_to_delete = [FactoryGirl.create(:dynflow_task, :user_create_task),
43
+ FactoryGirl.create(:dynflow_task, :product_create_task)]
44
+
45
+ tasks_to_keep = [FactoryGirl.create(:dynflow_task, :user_create_task) do |task|
46
+ task.started_at = task.ended_at = Time.now
47
+ task.save
48
+ end]
49
+ cleaner.delete
50
+ ForemanTasks::Task.where(id: tasks_to_delete).must_be_empty
51
+ ForemanTasks::Task.where(id: tasks_to_keep).must_equal tasks_to_keep
52
+ end
53
+
54
+ class ActionWithCleanup < Actions::Base
55
+ def self.cleanup_after
56
+ '15d'
57
+ end
58
+ end
59
+
60
+ describe "default behaviour" do
61
+ it "searches for the actions that have the cleanup_after defined" do
62
+ ForemanTasks::Cleaner.stubs(:cleanup_settings => {})
63
+ ForemanTasks::Cleaner.actions_with_default_cleanup[ActionWithCleanup].must_equal '15d'
64
+ end
65
+
66
+ it "searches for the actions that have the cleanup_after defined" do
67
+ ForemanTasks::Cleaner.stubs(:cleanup_settings =>
68
+ { :actions => [{:name => ActionWithCleanup.name, :after => '5d'}]})
69
+ ForemanTasks::Cleaner.actions_with_default_cleanup[ActionWithCleanup].must_equal '5d'
70
+ end
71
+
72
+ end
73
+ end
74
+ end
@@ -37,7 +37,7 @@ class TasksTest < ActiveSupport::TestCase
37
37
 
38
38
  describe 'consistency check' do
39
39
 
40
- let(:consistent_task) { FactoryGirl.create(:dynflow_task) }
40
+ let(:consistent_task) { FactoryGirl.create(:dynflow_task, :sync_with_dynflow => true) }
41
41
  let(:inconsistent_task) { FactoryGirl.create(:dynflow_task, :inconsistent_dynflow_task) }
42
42
 
43
43
  it 'ensures the tasks marked as running are really running in Dynflow' do
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: foreman-tasks
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.1
4
+ version: 0.7.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ivan Nečas
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-07-03 00:00:00.000000000 Z
11
+ date: 2015-07-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dynflow
@@ -126,6 +126,7 @@ files:
126
126
  - app/views/foreman_tasks/tasks/show.html.erb
127
127
  - bin/dynflow-executor
128
128
  - bin/foreman-tasks
129
+ - config/foreman-tasks.yaml.example
129
130
  - config/routes.rb
130
131
  - db/migrate/20131205204140_create_foreman_tasks.rb
131
132
  - db/migrate/20131209122644_create_foreman_tasks_locks.rb
@@ -138,6 +139,7 @@ files:
138
139
  - lib/foreman-tasks.rb
139
140
  - lib/foreman_tasks.rb
140
141
  - lib/foreman_tasks/authorizer_ext.rb
142
+ - lib/foreman_tasks/cleaner.rb
141
143
  - lib/foreman_tasks/dynflow.rb
142
144
  - lib/foreman_tasks/dynflow/configuration.rb
143
145
  - lib/foreman_tasks/dynflow/console_authorizer.rb
@@ -145,6 +147,7 @@ files:
145
147
  - lib/foreman_tasks/dynflow/persistence.rb
146
148
  - lib/foreman_tasks/engine.rb
147
149
  - lib/foreman_tasks/task_error.rb
150
+ - lib/foreman_tasks/tasks/cleanup.rake
148
151
  - lib/foreman_tasks/tasks/dynflow.rake
149
152
  - lib/foreman_tasks/tasks/export_tasks.rake
150
153
  - lib/foreman_tasks/triggers.rb
@@ -156,6 +159,7 @@ files:
156
159
  - test/helpers/foreman_tasks/tasks_helper_test.rb
157
160
  - test/support/dummy_dynflow_action.rb
158
161
  - test/unit/actions/action_with_sub_plans_test.rb
162
+ - test/unit/cleaner_test.rb
159
163
  - test/unit/dynflow_console_authorizer_test.rb
160
164
  - test/unit/task_test.rb
161
165
  homepage: https://github.com/theforeman/foreman-tasks
@@ -187,6 +191,7 @@ test_files:
187
191
  - test/foreman_tasks_test_helper.rb
188
192
  - test/controllers/api/tasks_controller_test.rb
189
193
  - test/factories/task_factory.rb
194
+ - test/unit/cleaner_test.rb
190
195
  - test/unit/dynflow_console_authorizer_test.rb
191
196
  - test/unit/actions/action_with_sub_plans_test.rb
192
197
  - test/unit/task_test.rb