foreman-tasks 5.0.0 → 5.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +0 -4
- data/.rubocop_todo.yml +0 -2
- data/README.md +8 -6
- data/app/assets/javascripts/foreman_tasks/trigger_form.js +7 -0
- data/app/controllers/foreman_tasks/api/tasks_controller.rb +4 -4
- data/app/controllers/foreman_tasks/tasks_controller.rb +5 -4
- data/app/graphql/mutations/recurring_logics/cancel.rb +27 -0
- data/app/graphql/types/recurring_logic.rb +3 -0
- data/app/helpers/foreman_tasks/foreman_tasks_helper.rb +4 -1
- data/app/lib/actions/base.rb +1 -0
- data/app/lib/actions/foreman/host/import_facts.rb +1 -1
- data/app/lib/actions/middleware/watch_delegated_proxy_sub_tasks.rb +20 -17
- data/app/lib/actions/proxy_action.rb +3 -13
- data/app/lib/actions/task_synchronization.rb +65 -0
- data/app/lib/actions/trigger_proxy_batch.rb +79 -0
- data/app/models/foreman_tasks/recurring_logic.rb +8 -0
- data/app/models/foreman_tasks/remote_task.rb +12 -21
- data/app/models/foreman_tasks/task/dynflow_task.rb +28 -24
- data/app/models/foreman_tasks/task.rb +1 -0
- data/app/models/foreman_tasks/triggering.rb +12 -4
- data/app/views/foreman_tasks/api/locks/show.json.rabl +4 -0
- data/app/views/foreman_tasks/api/tasks/show.json.rabl +1 -1
- data/app/views/foreman_tasks/layouts/react.html.erb +0 -1
- data/app/views/foreman_tasks/recurring_logics/index.html.erb +4 -2
- data/app/views/foreman_tasks/task_groups/recurring_logic_task_groups/_recurring_logic_task_group.html.erb +8 -0
- data/config/routes.rb +1 -1
- data/db/migrate/20210708123832_add_parent_task_id_to_remote_tasks.foreman_tasks.rb +5 -0
- data/db/migrate/20210720115251_add_purpose_to_recurring_logic.rb +6 -0
- data/db/migrate/20211123170430_tasks_settings_to_dsl_category.rb +5 -0
- data/extra/foreman-tasks-cleanup.sh +127 -0
- data/extra/foreman-tasks-export.sh +121 -0
- data/foreman-tasks.gemspec +2 -4
- data/lib/foreman_tasks/dynflow/configuration.rb +0 -5
- data/lib/foreman_tasks/engine.rb +58 -9
- data/lib/foreman_tasks/tasks/export_tasks.rake +112 -47
- data/lib/foreman_tasks/test_helpers.rb +1 -1
- data/lib/foreman_tasks/version.rb +1 -1
- data/locale/fr/LC_MESSAGES/foreman_tasks.mo +0 -0
- data/locale/ja/LC_MESSAGES/foreman_tasks.mo +0 -0
- data/locale/zh_CN/LC_MESSAGES/foreman_tasks.mo +0 -0
- data/package.json +7 -9
- data/test/controllers/api/tasks_controller_test.rb +30 -1
- data/test/controllers/tasks_controller_test.rb +19 -0
- data/test/factories/recurring_logic_factory.rb +7 -1
- data/test/factories/task_factory.rb +1 -1
- data/test/graphql/mutations/recurring_logics/cancel_mutation_test.rb +66 -0
- data/test/lib/actions/middleware/keep_current_taxonomies_test.rb +9 -1
- data/test/support/dummy_dynflow_action.rb +1 -1
- data/test/support/dummy_proxy_action.rb +6 -0
- data/test/unit/actions/proxy_action_test.rb +11 -11
- data/test/unit/actions/trigger_proxy_batch_test.rb +58 -0
- data/test/unit/remote_task_test.rb +0 -8
- data/test/unit/triggering_test.rb +22 -0
- data/test/unit/troubleshooting_help_generator_test.rb +0 -1
- data/test/unit/ui_notifications_test.rb +0 -1
- data/webpack/ForemanTasks/Components/TaskActions/TaskActionHelpers.js +12 -5
- data/webpack/ForemanTasks/Components/TaskActions/TaskActionHelpers.test.js +27 -5
- data/webpack/ForemanTasks/Components/TaskActions/index.js +1 -1
- data/webpack/ForemanTasks/Components/TaskDetails/Components/Locks.js +28 -14
- data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/__snapshots__/Locks.test.js.snap +50 -42
- data/webpack/ForemanTasks/Components/TaskDetails/TaskDetailsActions.js +1 -1
- data/webpack/ForemanTasks/Components/TasksTable/TasksBulkActions.js +1 -1
- data/webpack/ForemanTasks/Components/TasksTable/TasksTable.js +8 -0
- data/webpack/ForemanTasks/Components/TasksTable/TasksTableActions.js +6 -1
- data/webpack/ForemanTasks/Components/TasksTable/TasksTableHelpers.js +2 -1
- data/webpack/ForemanTasks/Components/TasksTable/TasksTablePage.js +22 -11
- data/webpack/ForemanTasks/Components/TasksTable/TasksTableReducer.js +17 -16
- data/webpack/ForemanTasks/Components/TasksTable/TasksTableSelectors.js +3 -0
- data/webpack/ForemanTasks/Components/TasksTable/__tests__/TasksTableHelpers.test.js +1 -1
- data/webpack/ForemanTasks/Components/TasksTable/__tests__/TasksTableReducer.test.js +3 -1
- data/webpack/ForemanTasks/Components/TasksTable/__tests__/__snapshots__/TasksTablePage.test.js.snap +12 -2
- data/webpack/ForemanTasks/Components/TasksTable/__tests__/__snapshots__/TasksTableReducer.test.js.snap +5 -0
- data/webpack/ForemanTasks/Components/TasksTable/formatters/__test__/__snapshots__/selectionHeaderCellFormatter.test.js.snap +1 -0
- data/webpack/ForemanTasks/Components/TasksTable/formatters/__test__/selectionHeaderCellFormatter.test.js +1 -1
- data/webpack/ForemanTasks/Components/TasksTable/formatters/selectionHeaderCellFormatter.js +1 -0
- data/webpack/ForemanTasks/Components/TasksTable/index.js +2 -0
- data/webpack/__mocks__/foremanReact/{redux/actions/toasts.js → components/ToastsList/index.js} +0 -0
- metadata +17 -10
- data/app/models/setting/foreman_tasks.rb +0 -29
- data/app/services/foreman_tasks/dashboard_table_filter.rb +0 -56
- data/lib/foreman_tasks/dynflow/persistence.rb +0 -46
- data/test/unit/dashboard_table_filter_test.rb +0 -77
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6f5ae3bb36274977790149d11f722477d6db0254b251ffa1dc79ec850a206183
|
4
|
+
data.tar.gz: bfd76d0fcc43a78e34c1831a392042e5770bd09276d95d126043f3a14db174b7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b2bde1cb649304ba13019b544c8db8f7fa72b2e0539d47b2db07d6e945364d08b4e5fa0b100b701aa7f080e8be308ad7a6af404eaee2865e7b13fd423f9a9300
|
7
|
+
data.tar.gz: 00a77aaf192bbfc12a70dc034aa75bfd728c18956b1b9377bbef3d7fce7491c68cfa520d9fd04de4789c721fcc2d2b2b605b63a9165601092841e44184219dfc
|
data/.rubocop.yml
CHANGED
data/.rubocop_todo.yml
CHANGED
@@ -11,7 +11,6 @@
|
|
11
11
|
# Include: **/*.gemspec
|
12
12
|
Gemspec/RequiredRubyVersion:
|
13
13
|
Exclude:
|
14
|
-
- 'foreman-tasks-core.gemspec'
|
15
14
|
- 'foreman-tasks.gemspec'
|
16
15
|
|
17
16
|
# Offense count: 1
|
@@ -37,7 +36,6 @@ Naming/MemoizedInstanceVariableName:
|
|
37
36
|
Exclude:
|
38
37
|
- 'app/controllers/foreman_tasks/recurring_logics_controller.rb'
|
39
38
|
- 'app/lib/actions/recurring_action.rb'
|
40
|
-
- 'lib/foreman_tasks_core/otp_manager.rb'
|
41
39
|
|
42
40
|
# Offense count: 11
|
43
41
|
# Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames.
|
data/README.md
CHANGED
@@ -6,7 +6,8 @@ happening/happened in your Foreman instance. A framework for asynchronous tasks
|
|
6
6
|
|
7
7
|
* Website: [TheForeman.org](http://theforeman.org)
|
8
8
|
* ServerFault tag: [Foreman](http://serverfault.com/questions/tagged/foreman)
|
9
|
-
* Issues: [
|
9
|
+
* Issues: [Foreman-tasks Redmine](http://projects.theforeman.org/projects/foreman-tasks)
|
10
|
+
* Manual: [Foreman-tasks Manual](https://www.theforeman.org/plugins/foreman_tasks/0.8/index.html)
|
10
11
|
* Wiki: [Foreman wiki](http://projects.theforeman.org/projects/foreman/wiki/About)
|
11
12
|
* Community and support: #theforeman for general support, #theforeman-dev for development chat in [Freenode](irc.freenode.net)
|
12
13
|
* Mailing lists:
|
@@ -25,6 +26,7 @@ happening/happened in your Foreman instance. A framework for asynchronous tasks
|
|
25
26
|
| >= 1.22 | ~> 0.15.0 |
|
26
27
|
| >= 2.0 | ~> 1.0.0 |
|
27
28
|
| >= 2.1 | ~> 2.0.0 |
|
29
|
+
| >= 2.6 | ~> 5.2.0 |
|
28
30
|
|
29
31
|
Installation
|
30
32
|
------------
|
@@ -154,9 +156,9 @@ rails root directory. See `-h` for more details and options
|
|
154
156
|
Tasks cleanup
|
155
157
|
-------------
|
156
158
|
|
157
|
-
Although
|
158
|
-
tasks can
|
159
|
-
|
159
|
+
Although the history of tasks has an auditing value, some kinds of
|
160
|
+
tasks can rapidly increase. Therefore, there is a mechanism for
|
161
|
+
cleaning up the tasks using a rake command. When running without
|
160
162
|
any arguments, the tasks are deleted based on the default parameters
|
161
163
|
defined in the code.
|
162
164
|
|
@@ -179,7 +181,7 @@ override the default configuration inside the configuration
|
|
179
181
|
```
|
180
182
|
:foreman-tasks:
|
181
183
|
:cleanup:
|
182
|
-
# the period after
|
184
|
+
# the period after which to delete all the tasks (by default, all tasks are not deleted after some period)
|
183
185
|
:after: 365d
|
184
186
|
# per action settings to override the default defined in the actions (cleanup_after method)
|
185
187
|
:actions:
|
@@ -194,7 +196,7 @@ to specify the search criteria for the cleanup manually:
|
|
194
196
|
* `TASK_SEARCH`: scoped search filter (example: 'label =
|
195
197
|
"Actions::Foreman::Host::ImportFacts"')
|
196
198
|
* `AFTER`: delete tasks created after `AFTER` period. Expected format
|
197
|
-
is a number followed by the time unit (`s`, `h`, `m`, `y`), such as
|
199
|
+
is a number followed by the time unit (`s`, `h`, `d`, `m`, `y`), such as
|
198
200
|
`10d` for 10 days (applicable only when the `TASK_SEARCH` option is
|
199
201
|
specified)
|
200
202
|
* `STATES`: comma separated list of task states to touch with the
|
@@ -8,6 +8,7 @@ function trigger_form_selector_binds(form_name, form_object_name) {
|
|
8
8
|
form.find('fieldset.trigger_mode_form#trigger_mode_' + type).hide();
|
9
9
|
});
|
10
10
|
form.find('fieldset.trigger_mode_form#trigger_mode_' + $(this).val()).show();
|
11
|
+
disable_hidden_start_field($(this).val(), form)
|
11
12
|
});
|
12
13
|
|
13
14
|
input_type_selector.on('change', function () {
|
@@ -39,3 +40,9 @@ function trigger_form_selector_binds(form_name, form_object_name) {
|
|
39
40
|
};
|
40
41
|
});
|
41
42
|
};
|
43
|
+
|
44
|
+
function disable_hidden_start_field(clicked, form) {
|
45
|
+
["future", "recurring"].forEach(function(type) {
|
46
|
+
form.find('.trigger_mode_form#trigger_mode_' + type + ' #triggering_start_at_raw').prop('disabled', type !== clicked);
|
47
|
+
});
|
48
|
+
};
|
@@ -192,7 +192,7 @@ module ForemanTasks
|
|
192
192
|
params[:order] = "#{ordering_params[:sort_by]} #{ordering_params[:sort_order]}"
|
193
193
|
end
|
194
194
|
params[:order] ||= 'started_at DESC'
|
195
|
-
@tasks =
|
195
|
+
@tasks = resource_scope_for_index.order(params[:order].to_s)
|
196
196
|
end
|
197
197
|
|
198
198
|
def search_options
|
@@ -260,7 +260,7 @@ module ForemanTasks
|
|
260
260
|
raise BadRequest,
|
261
261
|
_('Resource search_params requires resource_type and resource_id to be specified')
|
262
262
|
end
|
263
|
-
scope.joins(:
|
263
|
+
scope.joins(:links).where(foreman_tasks_links:
|
264
264
|
{ resource_type: search_params[:resource_type],
|
265
265
|
resource_id: search_params[:resource_id] })
|
266
266
|
when 'task'
|
@@ -320,7 +320,7 @@ module ForemanTasks
|
|
320
320
|
end
|
321
321
|
|
322
322
|
def find_task
|
323
|
-
@task = resource_scope.find(params[:id])
|
323
|
+
@task = resource_scope.with_duration.find(params[:id])
|
324
324
|
end
|
325
325
|
|
326
326
|
def resource_scope(_options = {})
|
@@ -330,7 +330,7 @@ module ForemanTasks
|
|
330
330
|
end
|
331
331
|
|
332
332
|
def resource_scope_for_index(*args)
|
333
|
-
super.
|
333
|
+
super.with_duration.distinct
|
334
334
|
end
|
335
335
|
|
336
336
|
def controller_permission
|
@@ -99,8 +99,8 @@ module ForemanTasks
|
|
99
99
|
private
|
100
100
|
|
101
101
|
def respond_with_tasks(scope)
|
102
|
-
@tasks = filter(scope, paginate: false)
|
103
|
-
csv_response(@tasks, [:id, :action, :state, :result, 'started_at.in_time_zone', 'ended_at.in_time_zone', :username], ['Id', 'Action', 'State', 'Result', 'Started At', 'Ended At', 'User'])
|
102
|
+
@tasks = filter(scope, paginate: false).with_duration
|
103
|
+
csv_response(@tasks, [:id, :action, :state, :result, 'started_at.in_time_zone', 'ended_at.in_time_zone', :duration, :username], ['Id', 'Action', 'State', 'Result', 'Started At', 'Ended At', 'Duration', 'User'])
|
104
104
|
end
|
105
105
|
|
106
106
|
def controller_permission
|
@@ -127,8 +127,9 @@ module ForemanTasks
|
|
127
127
|
end
|
128
128
|
|
129
129
|
def filter(scope, paginate: true)
|
130
|
-
|
131
|
-
|
130
|
+
search = current_taxonomy_search
|
131
|
+
search = [search, params[:search]].select(&:present?).join(' AND ')
|
132
|
+
scope = scope.search_for(search, order: params[:order])
|
132
133
|
scope = scope.paginate(page: params[:page], per_page: params[:per_page]) if paginate
|
133
134
|
scope.distinct
|
134
135
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Mutations
|
2
|
+
module RecurringLogics
|
3
|
+
class Cancel < BaseMutation
|
4
|
+
graphql_name 'CancelRecurringLogic'
|
5
|
+
description 'Cancels recurring logic and all its active tasks'
|
6
|
+
resource_class ::ForemanTasks::RecurringLogic
|
7
|
+
|
8
|
+
argument :id, ID, required: true
|
9
|
+
|
10
|
+
field :errors, [Types::AttributeError], null: false
|
11
|
+
field :recurring_logic, Types::RecurringLogic, null: true
|
12
|
+
|
13
|
+
def resolve(id:)
|
14
|
+
recurring_logic = load_object_by(id: id)
|
15
|
+
authorize!(recurring_logic, :edit)
|
16
|
+
task_errors = []
|
17
|
+
begin
|
18
|
+
recurring_logic.cancel
|
19
|
+
rescue => e
|
20
|
+
task_errors = [{ path: ['tasks'], message: "There has been an error when canceling one of the tasks: #{e}" }]
|
21
|
+
end
|
22
|
+
errors = recurring_logic.errors.any? ? map_errors_to_path(recurring_logic) : []
|
23
|
+
{ recurring_logic: recurring_logic, errors: (errors + task_errors) }
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -3,12 +3,15 @@ module Types
|
|
3
3
|
description 'A Recurring Logic'
|
4
4
|
model_class ::ForemanTasks::RecurringLogic
|
5
5
|
|
6
|
+
include ::Types::Concerns::MetaField
|
7
|
+
|
6
8
|
global_id_field :id
|
7
9
|
field :cron_line, String
|
8
10
|
field :end_time, GraphQL::Types::ISO8601DateTime
|
9
11
|
field :max_iteration, Integer
|
10
12
|
field :iteration, Integer
|
11
13
|
field :state, String
|
14
|
+
field :purpose, String
|
12
15
|
belongs_to :triggering, Types::Triggering
|
13
16
|
|
14
17
|
def self.graphql_definition
|
@@ -141,7 +141,10 @@ module ForemanTasks
|
|
141
141
|
weekly_fieldset(f, triggering),
|
142
142
|
time_picker_fieldset(f, triggering),
|
143
143
|
]
|
144
|
-
|
144
|
+
tags << text_f(f, :start_at_raw, :label => _('Start at'), :placeholder => 'YYYY-mm-dd HH:MM')
|
145
|
+
tags << text_f(f, :purpose,
|
146
|
+
:label => _('Purpose'),
|
147
|
+
:label_help => N_('A special label for tracking a recurring job. There can be only one active job with a given purpose at a time.'))
|
145
148
|
content_tag(:fieldset, nil, :id => 'trigger_mode_recurring', :class => "trigger_mode_form #{'hidden' unless triggering.recurring?}") do
|
146
149
|
tags.join.html_safe
|
147
150
|
end
|
data/app/lib/actions/base.rb
CHANGED
@@ -26,7 +26,7 @@ module Actions
|
|
26
26
|
def run
|
27
27
|
::User.as :admin do
|
28
28
|
host = ::Host.find(input[:host][:id])
|
29
|
-
state = host.import_facts(input[:facts], proxy)
|
29
|
+
state = ::HostFactImporter.new(host).import_facts(input[:facts], proxy)
|
30
30
|
output[:state] = state
|
31
31
|
end
|
32
32
|
rescue ::Foreman::Exception => e
|
@@ -20,33 +20,38 @@ module Actions
|
|
20
20
|
private
|
21
21
|
|
22
22
|
def set_clock
|
23
|
-
action.
|
24
|
-
POLL_INTERVAL,
|
25
|
-
CheckOnProxyActions
|
23
|
+
action.plan_event(CheckOnProxyActions, POLL_INTERVAL, optional: true)
|
26
24
|
end
|
27
25
|
|
28
26
|
def check_triggered
|
29
27
|
in_remote_task_batches(remote_tasks.triggered) do |batch|
|
30
28
|
batch.group_by(&:proxy_url).each do |(url, tasks)|
|
31
|
-
|
32
|
-
process_task_results tasks
|
29
|
+
results = poll_proxy_tasks(url, tasks)
|
30
|
+
process_task_results tasks, results
|
33
31
|
end
|
34
32
|
end
|
35
33
|
end
|
36
34
|
|
37
|
-
def process_task_results(tasks)
|
38
|
-
|
35
|
+
def process_task_results(tasks, results)
|
36
|
+
possibly_missing, present = tasks.partition { |task| !results.key?(task.remote_task_id) }
|
37
|
+
missing = possibly_missing.select do |task|
|
38
|
+
# Really missing are tasks which are missing and:
|
39
|
+
# don't have a remote parent
|
40
|
+
# had a remote parent but the proxy doesn't have the remote parent anymore
|
41
|
+
# has a remote parent, proxy has the remote parent but it is stopped or paused
|
42
|
+
task.parent_task_id.nil? ||
|
43
|
+
!results.key?(task.parent_task_id) ||
|
44
|
+
%(stopped paused).include?(results[task.parent_task_id]['state'])
|
45
|
+
end
|
39
46
|
notify ::Actions::ProxyAction::ProxyActionMissing.new, missing if missing.any?
|
40
47
|
|
41
|
-
stopped = present.select { |task| %w[stopped paused].include? task.
|
48
|
+
stopped = present.select { |task| %w[stopped paused].include? results.dig(task.remote_task_id, 'state') }
|
42
49
|
notify ::Actions::ProxyAction::ProxyActionStopped.new, stopped if stopped.any?
|
43
50
|
end
|
44
51
|
|
45
52
|
def notify(event, tasks)
|
46
53
|
tasks.each do |task|
|
47
|
-
action.
|
48
|
-
task.step_id,
|
49
|
-
event
|
54
|
+
action.plan_event(event, execution_plan_id: task.execution_plan_id, step_id: task.step_id)
|
50
55
|
end
|
51
56
|
end
|
52
57
|
|
@@ -56,15 +61,13 @@ module Actions
|
|
56
61
|
|
57
62
|
def poll_proxy_tasks(url, tasks)
|
58
63
|
proxy = ProxyAPI::ForemanDynflow::DynflowProxy.new(:url => url)
|
59
|
-
|
60
|
-
tasks.map
|
61
|
-
|
62
|
-
task
|
63
|
-
end
|
64
|
+
# Get statuses of tasks and their optional parent tasks
|
65
|
+
ids = (tasks.map(&:remote_task_id) + tasks.map(&:parent_task_id)).uniq
|
66
|
+
proxy.task_states(ids)
|
64
67
|
rescue => e
|
65
68
|
# We could not reach the remote task, we'll try again next time
|
66
69
|
action.action_logger.warn(_('Failed to check on tasks on proxy at %{url}: %{exception}') % { :url => url, :exception => e.message })
|
67
|
-
|
70
|
+
{}
|
68
71
|
end
|
69
72
|
|
70
73
|
def in_remote_task_batches(scope)
|
@@ -29,10 +29,10 @@ module Actions
|
|
29
29
|
default_connection_options.each do |key, value|
|
30
30
|
options[:connection_options][key] = value unless options[:connection_options].key?(key)
|
31
31
|
end
|
32
|
-
plan_self(options.merge(:proxy_url => proxy.url, :proxy_action_name => klass.to_s
|
32
|
+
plan_self(options.merge(:proxy_url => proxy.url, :proxy_action_name => klass.to_s))
|
33
33
|
# Just saving the RemoteTask is enough when using batch triggering
|
34
34
|
# It will be picked up by the ProxyBatchTriggering middleware
|
35
|
-
if input[:use_batch_triggering] &&
|
35
|
+
if input[:use_batch_triggering] && input.dig(:connection_options, :proxy_batch_triggering)
|
36
36
|
prepare_remote_task.save!
|
37
37
|
end
|
38
38
|
end
|
@@ -193,11 +193,6 @@ module Actions
|
|
193
193
|
:proxy_batch_triggering => Setting['foreman_tasks_proxy_batch_trigger'] || false }
|
194
194
|
end
|
195
195
|
|
196
|
-
def with_batch_triggering?(proxy_version)
|
197
|
-
((proxy_version[:major] == 1 && proxy_version[:minor] > 20) || proxy_version[:major] > 1) &&
|
198
|
-
input.fetch(:connection_options, {}).fetch(:proxy_batch_triggering, false)
|
199
|
-
end
|
200
|
-
|
201
196
|
def clean_remote_task(*_args)
|
202
197
|
remote_task.destroy! if remote_task
|
203
198
|
end
|
@@ -222,11 +217,6 @@ module Actions
|
|
222
217
|
.try(:fetch, 'output', {}) || {}
|
223
218
|
end
|
224
219
|
|
225
|
-
def proxy_version(proxy)
|
226
|
-
match = proxy.statuses[:version].version['version'].match(/(\d+)\.(\d+)\.(\d+)/)
|
227
|
-
{ :major => match[1].to_i, :minor => match[2].to_i, :patch => match[3].to_i }
|
228
|
-
end
|
229
|
-
|
230
220
|
def failed_proxy_tasks
|
231
221
|
metadata[:failed_proxy_tasks] ||= []
|
232
222
|
end
|
@@ -273,7 +263,7 @@ module Actions
|
|
273
263
|
end
|
274
264
|
|
275
265
|
def proxy_task_id
|
276
|
-
output[:proxy_task_id]
|
266
|
+
output[:proxy_task_id] || remote_task.try(:remote_task_id) || @execution_plan_id
|
277
267
|
end
|
278
268
|
end
|
279
269
|
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module Actions
|
2
|
+
# Examples:
|
3
|
+
|
4
|
+
# # Action A which emits an event when it successfully finishes.
|
5
|
+
# class A
|
6
|
+
# include ::Actions::ObservableAction
|
7
|
+
# # ... rest ...
|
8
|
+
# end
|
9
|
+
|
10
|
+
# # Action B which emits an event when it successfully finishes or fails.
|
11
|
+
# class B
|
12
|
+
# include ::Actions::ObservableAction
|
13
|
+
#
|
14
|
+
# execution_plan_hooks.use :emit_event_failure, :on => [:failure]
|
15
|
+
#
|
16
|
+
# def self.event_names
|
17
|
+
# super + [event_name_base + '_' + event_name_suffix(:failure)]
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# def emit_event_failure(plan)
|
21
|
+
# emit_event(plan, :failure)
|
22
|
+
# end
|
23
|
+
# # ... rest ...
|
24
|
+
# end
|
25
|
+
module TaskSynchronization
|
26
|
+
def self.included(base)
|
27
|
+
base.execution_plan_hooks.use :sync_execution_plan_to_task, on: ::Dynflow::ExecutionPlan.states
|
28
|
+
end
|
29
|
+
|
30
|
+
def sync_execution_plan_to_task(plan)
|
31
|
+
return unless root_action?
|
32
|
+
on_execution_plan_save(plan)
|
33
|
+
rescue => e
|
34
|
+
::Foreman::Logging.exception('Error on on_execution_plan_save event', e,
|
35
|
+
:logger => 'dynflow')
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def on_execution_plan_save(execution_plan)
|
41
|
+
# We can load the data unless the execution plan was properly planned and saved
|
42
|
+
# including its steps
|
43
|
+
case execution_plan.state
|
44
|
+
when :pending
|
45
|
+
task = ForemanTasks::Task::DynflowTask.new_for_execution_plan(execution_plan)
|
46
|
+
task.start_at ||= Time.zone.now
|
47
|
+
task.save!
|
48
|
+
when :scheduled
|
49
|
+
delayed_plan = world.persistence.load_delayed_plan(execution_plan.id)
|
50
|
+
raise ::Foreman::Exception, 'Plan is delayed but the delay record is missing' if delayed_plan.nil?
|
51
|
+
task = ::ForemanTasks::Task::DynflowTask.find_by!(:external_id => execution_plan.id)
|
52
|
+
task.update_from_dynflow(execution_plan, delayed_plan)
|
53
|
+
when :planning
|
54
|
+
task = ::ForemanTasks::Task::DynflowTask.where(:external_id => execution_plan.id).first
|
55
|
+
task.update_from_dynflow(execution_plan)
|
56
|
+
else
|
57
|
+
if (task = ::ForemanTasks::Task::DynflowTask.where(:external_id => execution_plan.id).first)
|
58
|
+
unless task.state.to_s == execution_plan.state.to_s
|
59
|
+
task.update_from_dynflow(execution_plan)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module Actions
|
2
|
+
# This action plans proxy tasks in batches.
|
3
|
+
# It needs to be manually notified about the next batch being available by sending a TriggerNextBatch event.
|
4
|
+
#
|
5
|
+
# The ProxyAction needs to be planned with `:use_batch_triggering => true` to activate the feature
|
6
|
+
class TriggerProxyBatch < Base
|
7
|
+
TriggerNextBatch = Algebrick.type do
|
8
|
+
fields! batches: Integer
|
9
|
+
end
|
10
|
+
TriggerLastBatch = Algebrick.atom
|
11
|
+
|
12
|
+
def run(event = nil)
|
13
|
+
case event
|
14
|
+
when nil
|
15
|
+
if output[:planned_count]
|
16
|
+
check_finish
|
17
|
+
else
|
18
|
+
init_counts and suspend
|
19
|
+
end
|
20
|
+
when TriggerNextBatch
|
21
|
+
trigger_remote_tasks_batches(event.batches) and suspend
|
22
|
+
when TriggerLastBatch
|
23
|
+
trigger_remote_tasks_batch and on_finish
|
24
|
+
when ::Dynflow::Action::Skip
|
25
|
+
# do nothing
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def trigger_remote_tasks_batches(amount = 1)
|
30
|
+
amount.times { trigger_remote_tasks_batch }
|
31
|
+
end
|
32
|
+
|
33
|
+
def trigger_remote_tasks_batch
|
34
|
+
# Find the tasks in batches, order them by proxy_url so we get all tasks
|
35
|
+
# to a certain proxy "close to each other"
|
36
|
+
batch = remote_tasks.pending.order(:proxy_url, :id).first(batch_size)
|
37
|
+
# Group the tasks by operation, in theory there should be only one operation
|
38
|
+
batch.group_by(&:operation).each do |operation, group|
|
39
|
+
ForemanTasks::RemoteTask.batch_trigger(operation, group)
|
40
|
+
output[:planned_count] += group.size
|
41
|
+
end
|
42
|
+
rescue => e
|
43
|
+
action_logger.warn "Could not trigger task on the smart proxy: #{e.message}"
|
44
|
+
batch.each { |remote_task| remote_task.update_from_batch_trigger({}) }
|
45
|
+
output[:failed_count] += batch.size
|
46
|
+
end
|
47
|
+
|
48
|
+
def init_counts
|
49
|
+
output[:planned_count] = 0
|
50
|
+
output[:failed_count] = 0
|
51
|
+
end
|
52
|
+
|
53
|
+
def check_finish
|
54
|
+
if output[:planned_count] + output[:failed_count] + batch_size >= input[:total_count]
|
55
|
+
trigger_remote_tasks_batch and on_finish
|
56
|
+
else
|
57
|
+
suspend
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def done?
|
62
|
+
output[:planned_count] + output[:failed_count] >= input[:total_count]
|
63
|
+
end
|
64
|
+
|
65
|
+
def remote_tasks
|
66
|
+
task.remote_sub_tasks
|
67
|
+
end
|
68
|
+
|
69
|
+
def on_finish
|
70
|
+
# nothing for now
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def batch_size
|
76
|
+
input[:batch_size] || Setting['foreman_tasks_proxy_batch_size']
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -17,6 +17,9 @@ module ForemanTasks
|
|
17
17
|
scoped_search :on => :iteration, :complete_value => false
|
18
18
|
scoped_search :on => :cron_line, :complete_value => true
|
19
19
|
scoped_search :on => :state, :complete_value => true
|
20
|
+
scoped_search :on => :purpose, :complete_value => true
|
21
|
+
|
22
|
+
validate :valid_purpose
|
20
23
|
|
21
24
|
before_create do
|
22
25
|
task_group.save
|
@@ -169,6 +172,7 @@ module ForemanTasks
|
|
169
172
|
::ForemanTasks::RecurringLogic.new_from_cronline(cronline).tap do |manager|
|
170
173
|
manager.end_time = triggering.end_time if triggering.end_time_limited.present?
|
171
174
|
manager.max_iteration = triggering.max_iteration if triggering.max_iteration.present?
|
175
|
+
manager.purpose = triggering.purpose if triggering.purpose.present?
|
172
176
|
manager.triggering = triggering
|
173
177
|
end
|
174
178
|
end
|
@@ -189,5 +193,9 @@ module ForemanTasks
|
|
189
193
|
end
|
190
194
|
hash.select { |key, _| allowed_keys.include? key }
|
191
195
|
end
|
196
|
+
|
197
|
+
def valid_purpose?
|
198
|
+
!(purpose.present? && self.class.where(:purpose => purpose, :state => %w[active disabled]).any?)
|
199
|
+
end
|
192
200
|
end
|
193
201
|
end
|
@@ -16,7 +16,7 @@ module ForemanTasks
|
|
16
16
|
# Triggers a task on the proxy "the old way"
|
17
17
|
def trigger(proxy_action_name, input)
|
18
18
|
response = begin
|
19
|
-
proxy.
|
19
|
+
proxy.launch_tasks('single', :action_class => proxy_action_name, :action_input => input)
|
20
20
|
rescue RestClient::Exception => e
|
21
21
|
logger.warn "Could not trigger task on the smart proxy: #{e.message}"
|
22
22
|
{}
|
@@ -31,37 +31,28 @@ module ForemanTasks
|
|
31
31
|
acc.merge(remote_task.execution_plan_id => { :action_input => remote_task.proxy_input,
|
32
32
|
:action_class => remote_task.proxy_action_name })
|
33
33
|
end
|
34
|
-
|
34
|
+
results = remote_tasks.first.proxy.launch_tasks(operation, input_hash)
|
35
|
+
remote_tasks.each do |remote_task|
|
36
|
+
remote_task.update_from_batch_trigger results.fetch(remote_task.execution_plan_id, {}),
|
37
|
+
results.fetch('parent', {})
|
38
|
+
end
|
35
39
|
end
|
36
40
|
remote_tasks
|
37
41
|
end
|
38
42
|
|
39
|
-
|
40
|
-
# if it fails
|
41
|
-
def self.safe_batch_trigger(operation, remote_tasks, input_hash)
|
42
|
-
results = remote_tasks.first.proxy.launch_tasks(operation, input_hash)
|
43
|
-
remote_tasks.each { |remote_task| remote_task.update_from_batch_trigger results[remote_task.execution_plan_id] }
|
44
|
-
rescue RestClient::NotFound
|
45
|
-
fallback_batch_trigger remote_tasks, input_hash
|
46
|
-
end
|
47
|
-
|
48
|
-
# Trigger the tasks one-by-one using the old API
|
49
|
-
def self.fallback_batch_trigger(remote_tasks, input_hash)
|
50
|
-
remote_tasks.each do |remote_task|
|
51
|
-
task_data = input_hash[remote_task.execution_plan_id]
|
52
|
-
remote_task.trigger(task_data[:action_class], task_data[:action_input])
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
def update_from_batch_trigger(data)
|
43
|
+
def update_from_batch_trigger(data, parent = {})
|
57
44
|
if data['result'] == 'success'
|
58
45
|
self.remote_task_id = data['task_id']
|
59
46
|
self.state = 'triggered'
|
47
|
+
elsif !parent.empty?
|
48
|
+
self.parent_task_id = parent['task_id']
|
49
|
+
self.state = 'parent-triggered'
|
60
50
|
else
|
61
51
|
# Tell the action the task on the smart proxy stopped
|
62
52
|
ForemanTasks.dynflow.world.event execution_plan_id,
|
63
53
|
step_id,
|
64
|
-
::Actions::ProxyAction::ProxyActionStopped.new
|
54
|
+
::Actions::ProxyAction::ProxyActionStopped.new,
|
55
|
+
optional: true
|
65
56
|
end
|
66
57
|
save!
|
67
58
|
end
|