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 +8 -8
- data/README.md +62 -1
- data/app/controllers/foreman_tasks/tasks_controller.rb +1 -1
- data/app/lib/actions/foreman/host/import_facts.rb +5 -0
- data/app/models/foreman_tasks/task.rb +1 -0
- data/app/models/foreman_tasks/task/dynflow_task.rb +3 -2
- data/app/views/foreman_tasks/tasks/index.html.erb +6 -3
- data/app/views/foreman_tasks/tasks/show.html.erb +36 -35
- data/config/foreman-tasks.yaml.example +26 -0
- data/lib/foreman_tasks.rb +1 -0
- data/lib/foreman_tasks/cleaner.rb +152 -0
- data/lib/foreman_tasks/dynflow/configuration.rb +11 -9
- data/lib/foreman_tasks/dynflow/daemon.rb +4 -1
- data/lib/foreman_tasks/dynflow/persistence.rb +1 -3
- data/lib/foreman_tasks/engine.rb +5 -2
- data/lib/foreman_tasks/tasks/cleanup.rake +67 -0
- data/lib/foreman_tasks/version.rb +1 -1
- data/test/factories/task_factory.rb +9 -2
- data/test/unit/cleaner_test.rb +74 -0
- data/test/unit/task_test.rb +1 -1
- metadata +7 -2
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
MDE1OGU2NTc0M2VkNTdhYjUwODE4YTM1YTNiMTVmYWQwNTUyNjIxZQ==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
YmU0NzM5ZjNhZTMxODdjZjVkYTFkYTkwYWY5YjhiZDljMjUxNGZkZA==
|
7
7
|
SHA512:
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
Yzc2NTgwODNhODY2MTM5MWFjNmU3NzBkODZlZmNhYjZjZTJlZTIxMzVlZjll
|
10
|
+
MDQ1OTk0ZmE4MTk5ZDRiMmUyMzViNzQxYTdjZDViNmMyNTcyZjQwNmIyODI0
|
11
|
+
MzRiZjM4ZjA0YWJlNTRkZGY1ZjVjODNiNWJjMmZkYjAxYWU5OWM=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
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
|
-
|
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
|
|
@@ -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
|
-
|
94
|
+
logger.warn("Task %s updated at consistency check: %s" % [task.id, changes.inspect])
|
94
95
|
end
|
95
96
|
rescue => e
|
96
|
-
|
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
|
-
|
19
|
-
if(
|
21
|
+
currentTwoPaneTask = $(this).find("a");
|
22
|
+
if(currentTwoPaneTask.length){
|
20
23
|
e.preventDefault();
|
21
|
-
two_pane_open(
|
24
|
+
two_pane_open(currentTwoPaneTask);
|
22
25
|
}
|
23
26
|
});
|
24
27
|
|
@@ -1,44 +1,45 @@
|
|
1
1
|
<script>
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
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
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
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
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
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
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
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
@@ -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
|
-
|
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
|
-
|
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
|
data/lib/foreman_tasks/engine.rb
CHANGED
@@ -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.
|
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
|
@@ -22,11 +22,18 @@ FactoryGirl.define do
|
|
22
22
|
result "success"
|
23
23
|
parent_task_id nil
|
24
24
|
|
25
|
-
|
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.
|
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
|
data/test/unit/task_test.rb
CHANGED
@@ -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.
|
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-
|
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
|