foreman-tasks 0.15.0 → 0.15.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.babelrc +24 -0
- data/.eslintrc +27 -0
- data/.gitignore +3 -0
- data/.prettierrc +4 -0
- data/.rubocop.yml +1 -1
- data/.storybook/addons.js +2 -0
- data/.storybook/config.js +7 -0
- data/.storybook/webpack.config.js +66 -0
- data/.stylelintrc +5 -0
- data/.travis.yml +5 -0
- data/.yo-rc.json +5 -0
- data/README.md +1 -0
- data/app/controllers/foreman_tasks/api/tasks_controller.rb +28 -10
- data/app/controllers/foreman_tasks/react_controller.rb +17 -0
- data/app/lib/actions/middleware/proxy_batch_triggering.rb +36 -0
- data/app/lib/actions/middleware/watch_delegated_proxy_sub_tasks.rb +12 -7
- data/app/lib/actions/proxy_action.rb +52 -16
- data/app/lib/proxy_api/foreman_dynflow/dynflow_proxy.rb +12 -0
- data/app/models/foreman_tasks/remote_task.rb +74 -0
- data/app/models/foreman_tasks/task/dynflow_task.rb +14 -7
- data/app/models/setting/foreman_tasks.rb +3 -1
- data/app/views/foreman_tasks/layouts/react.html.erb +12 -0
- data/app/views/foreman_tasks/tasks/show.html.erb +1 -1
- data/config/routes.rb +3 -0
- data/db/migrate/20181019135324_add_remote_task_operation.rb +5 -0
- data/foreman-tasks.gemspec +1 -1
- data/lib/foreman_tasks/engine.rb +1 -0
- data/lib/foreman_tasks/version.rb +1 -1
- data/lib/foreman_tasks.rb +5 -1
- data/package.json +117 -0
- data/script/travis_run_js_tests.sh +7 -0
- data/test/controllers/api/tasks_controller_test.rb +3 -0
- data/test/core/unit/dispatcher_test.rb +43 -0
- data/test/core/unit/runner_test.rb +129 -0
- data/test/core/unit/task_launcher_test.rb +56 -0
- data/test/foreman_tasks_core_test_helper.rb +4 -0
- data/test/support/dummy_proxy_action.rb +17 -1
- data/test/unit/actions/proxy_action_test.rb +20 -2
- data/test/unit/actions/recurring_action_test.rb +1 -1
- data/test/unit/remote_task_test.rb +41 -0
- data/test/unit/task_test.rb +3 -1
- data/webpack/ForemanTasks/ForemanTasks.js +27 -0
- data/webpack/ForemanTasks/ForemanTasks.test.js +10 -0
- data/webpack/ForemanTasks/Routes/ForemanTasksRouter.js +14 -0
- data/webpack/ForemanTasks/Routes/ForemanTasksRouter.test.js +22 -0
- data/webpack/ForemanTasks/Routes/ForemanTasksRoutes.js +17 -0
- data/webpack/ForemanTasks/Routes/ForemanTasksRoutes.test.js +16 -0
- data/webpack/ForemanTasks/Routes/IndexTasks/IndexTasks.js +10 -0
- data/webpack/ForemanTasks/Routes/IndexTasks/__tests__/IndexTasks.test.js +10 -0
- data/webpack/ForemanTasks/Routes/IndexTasks/__tests__/__snapshots__/IndexTasks.test.js.snap +12 -0
- data/webpack/ForemanTasks/Routes/IndexTasks/index.js +1 -0
- data/webpack/ForemanTasks/Routes/IndexTasks/indexTasks.scss +0 -0
- data/webpack/ForemanTasks/Routes/ShowTask/ShowTask.js +10 -0
- data/webpack/ForemanTasks/Routes/ShowTask/__tests__/ShowTask.test.js +10 -0
- data/webpack/ForemanTasks/Routes/ShowTask/__tests__/__snapshots__/ShowTask.test.js.snap +12 -0
- data/webpack/ForemanTasks/Routes/ShowTask/index.js +1 -0
- data/webpack/ForemanTasks/Routes/ShowTask/showTask.scss +0 -0
- data/webpack/ForemanTasks/Routes/__snapshots__/ForemanTasksRouter.test.js.snap +16 -0
- data/webpack/ForemanTasks/Routes/__snapshots__/ForemanTasksRoutes.test.js.snap +21 -0
- data/webpack/ForemanTasks/__snapshots__/ForemanTasks.test.js.snap +60 -0
- data/webpack/ForemanTasks/components/Hello/Hello.stories.js +5 -0
- data/webpack/ForemanTasks/components/Hello/__tests__/Hello.test.js +11 -0
- data/webpack/ForemanTasks/components/Hello/__tests__/__snapshots__/Hello.test.js.snap +7 -0
- data/webpack/ForemanTasks/components/Hello/index.js +5 -0
- data/webpack/ForemanTasks/index.js +1 -0
- data/webpack/index.js +11 -0
- data/webpack/stories/index.js +12 -0
- data/webpack/test_setup.js +6 -0
- metadata +56 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d9be0418b3db898832481c46ba3f3eae300e2562d7fa240d71cb96876ee825de
|
4
|
+
data.tar.gz: 61db2a1487d48b7312478c3ad65adffa2230c2fbe9f5a452d200cc4b56428962
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d303c00a15e97cbf2c5fe443559a5bd2260298e8b54e52e552040c93fb8885caba6130f7957ce097bfbddafee4a4b3b1bd1a8d41786471b88cd306876496f2f9
|
7
|
+
data.tar.gz: f4a036829b034d94577d0affce68fdb899bbcdeaf0bef74f8b2358d0cb165ac9e42b7afc8111060210d6eb90bc5289d3af5d49ca6fa88b4f05bd7464a5c94480
|
data/.babelrc
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
{
|
2
|
+
"presets": [
|
3
|
+
"env",
|
4
|
+
"react"
|
5
|
+
],
|
6
|
+
"plugins": [
|
7
|
+
["module-resolver", {
|
8
|
+
"alias": {
|
9
|
+
"root": ["./"],
|
10
|
+
"foremanReact": "../foreman/webpack/assets/javascripts/react_app"
|
11
|
+
}
|
12
|
+
}],
|
13
|
+
"transform-class-properties",
|
14
|
+
"transform-object-rest-spread",
|
15
|
+
"transform-object-assign",
|
16
|
+
"lodash",
|
17
|
+
"syntax-dynamic-import"
|
18
|
+
],
|
19
|
+
"env": {
|
20
|
+
"test": {
|
21
|
+
"plugins": ["dynamic-import-node"]
|
22
|
+
}
|
23
|
+
}
|
24
|
+
}
|
data/.eslintrc
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
{
|
2
|
+
"plugins": ["patternfly-react"],
|
3
|
+
"extends": ["plugin:patternfly-react/recommended"],
|
4
|
+
"rules": {
|
5
|
+
"prettier/prettier": ["error", {
|
6
|
+
"singleQuote": true,
|
7
|
+
"trailingComma": "es5"
|
8
|
+
}]
|
9
|
+
},
|
10
|
+
"settings": {
|
11
|
+
"import/resolver": {
|
12
|
+
"babel-module": {}
|
13
|
+
}
|
14
|
+
},
|
15
|
+
"globals": {
|
16
|
+
"document": false,
|
17
|
+
"escape": false,
|
18
|
+
"navigator": false,
|
19
|
+
"unescape": false,
|
20
|
+
"window": false,
|
21
|
+
"$": true,
|
22
|
+
"_": true,
|
23
|
+
"__": true,
|
24
|
+
"n__": true,
|
25
|
+
"d3": true
|
26
|
+
}
|
27
|
+
}
|
data/.gitignore
CHANGED
data/.prettierrc
ADDED
data/.rubocop.yml
CHANGED
@@ -0,0 +1,66 @@
|
|
1
|
+
let path = require('path');
|
2
|
+
|
3
|
+
// Use storybook's default configuration with our customizations
|
4
|
+
module.exports = (baseConfig, env, defaultConfig) => {
|
5
|
+
|
6
|
+
// overwrite storybook's default import rules
|
7
|
+
defaultConfig.module.rules = [
|
8
|
+
{
|
9
|
+
test: /\.js$/,
|
10
|
+
exclude: /node_modules/,
|
11
|
+
loader: 'babel-loader',
|
12
|
+
options: {
|
13
|
+
presets: [
|
14
|
+
path.join(__dirname, '..', 'node_modules/babel-preset-react'),
|
15
|
+
path.join(__dirname, '..', 'node_modules/babel-preset-env')
|
16
|
+
],
|
17
|
+
plugins: [
|
18
|
+
path.join(__dirname, '..', 'node_modules/babel-plugin-transform-class-properties'),
|
19
|
+
path.join(__dirname, '..', 'node_modules/babel-plugin-transform-object-rest-spread'),
|
20
|
+
path.join(__dirname, '..', 'node_modules/babel-plugin-transform-object-assign'),
|
21
|
+
path.join(__dirname, '..', 'node_modules/babel-plugin-syntax-dynamic-import')
|
22
|
+
]
|
23
|
+
}
|
24
|
+
},
|
25
|
+
{
|
26
|
+
test: /(\.png|\.gif)$/,
|
27
|
+
loader: 'url-loader?limit=32767'
|
28
|
+
},
|
29
|
+
{
|
30
|
+
test: /\.css$/,
|
31
|
+
loaders: ['style-loader', 'css-loader']
|
32
|
+
},
|
33
|
+
{
|
34
|
+
test: /\.scss$/,
|
35
|
+
loaders: ['style-loader', 'css-loader', {
|
36
|
+
loader: 'sass-loader',
|
37
|
+
options: {
|
38
|
+
includePaths: [
|
39
|
+
// teach webpack to resolve patternfly dependencies
|
40
|
+
path.resolve(__dirname, '..', 'node_modules', 'patternfly', 'dist', 'sass'),
|
41
|
+
path.resolve(__dirname, '..', 'node_modules', 'bootstrap-sass', 'assets', 'stylesheets'),
|
42
|
+
path.resolve(__dirname, '..', 'node_modules', 'font-awesome-sass', 'assets', 'stylesheets')
|
43
|
+
]
|
44
|
+
}
|
45
|
+
}]
|
46
|
+
},
|
47
|
+
{
|
48
|
+
test: /\.md$/,
|
49
|
+
loaders: ['raw-loader']
|
50
|
+
},
|
51
|
+
{
|
52
|
+
test: /(\.ttf|\.woff|\.woff2|\.eot|\.svg|\.jpg)$/,
|
53
|
+
loaders: ['url-loader']
|
54
|
+
},
|
55
|
+
]
|
56
|
+
|
57
|
+
defaultConfig.resolve = {
|
58
|
+
modules: [
|
59
|
+
path.join(__dirname, '..', 'webpack'),
|
60
|
+
path.join(__dirname, '..', 'node_modules'),
|
61
|
+
'node_modules/',
|
62
|
+
],
|
63
|
+
};
|
64
|
+
|
65
|
+
return defaultConfig;
|
66
|
+
};
|
data/.stylelintrc
ADDED
data/.travis.yml
ADDED
data/.yo-rc.json
ADDED
data/README.md
CHANGED
@@ -149,23 +149,41 @@ module ForemanTasks
|
|
149
149
|
}
|
150
150
|
end
|
151
151
|
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
152
|
+
def_param_group :callback_target do
|
153
|
+
param :callback, Hash do
|
154
|
+
param :task_id, :identifier, :desc => N_('UUID of the task')
|
155
|
+
param :step_id, String, :desc => N_('The ID of the step inside the execution plan to send the event to')
|
156
|
+
end
|
156
157
|
end
|
157
|
-
|
158
|
+
|
159
|
+
def_param_group :callback do
|
160
|
+
param_group :callback_target
|
161
|
+
param :data, Hash, :desc => N_('Data to be sent to the action')
|
162
|
+
end
|
163
|
+
|
164
|
+
api :POST, '/tasks/callback', N_('Send data to the task from external executor (such as smart_proxy_dynflow)')
|
165
|
+
param_group :callback
|
166
|
+
param :callbacks, Array, :of => param_group(:callback)
|
158
167
|
def callback
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
168
|
+
callbacks = params.key?(:callback) ? Array(params) : params[:callbacks]
|
169
|
+
ids = callbacks.map { |payload| payload[:callback][:task_id] }
|
170
|
+
external_map = Hash[*ForemanTasks::Task.where(:id => ids).pluck(:id, :external_id).flatten]
|
171
|
+
callbacks.each do |payload|
|
172
|
+
# We need to call .to_unsafe_h to unwrap the hash from ActionController::Parameters
|
173
|
+
callback = payload[:callback]
|
174
|
+
process_callback(external_map[callback[:task_id]], callback[:step_id].to_i, payload[:data].to_unsafe_h, :request_id => ::Logging.mdc['request'])
|
175
|
+
end
|
164
176
|
render :json => { :message => 'processing' }.to_json
|
165
177
|
end
|
166
178
|
|
167
179
|
private
|
168
180
|
|
181
|
+
def process_callback(execution_plan_uuid, step_id, data, meta)
|
182
|
+
ForemanTasks.dynflow.world.event(execution_plan_uuid,
|
183
|
+
step_id,
|
184
|
+
::Actions::ProxyAction::CallbackData.new(data, meta))
|
185
|
+
end
|
186
|
+
|
169
187
|
def search_tasks(search_params)
|
170
188
|
scope = resource_scope_for_index.select('DISTINCT foreman_tasks_tasks.*')
|
171
189
|
scope = ordering_scope(scope, search_params)
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module ForemanTasks
|
2
|
+
class ReactController < ::ApplicationController
|
3
|
+
def index
|
4
|
+
render 'foreman_tasks/layouts/react', :layout => false
|
5
|
+
end
|
6
|
+
|
7
|
+
private
|
8
|
+
|
9
|
+
def controller_permission
|
10
|
+
:foreman_tasks
|
11
|
+
end
|
12
|
+
|
13
|
+
def action_permission
|
14
|
+
:view
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Actions
|
2
|
+
module Middleware
|
3
|
+
class ProxyBatchTriggering < ::Dynflow::Middleware
|
4
|
+
# If the event could result into sub tasks being planned, check if there are any RemoteTasks
|
5
|
+
# to trigger after the event is processed
|
6
|
+
#
|
7
|
+
# The ProxyAction needs to be planned with `:use_batch_triggering => true` to activate the feature
|
8
|
+
def run(event = nil)
|
9
|
+
pass event
|
10
|
+
ensure
|
11
|
+
trigger_remote_tasks if event.nil? || event.is_a?(Dynflow::Action::WithBulkSubPlans::PlanNextBatch)
|
12
|
+
end
|
13
|
+
|
14
|
+
def trigger_remote_tasks
|
15
|
+
# Find the tasks in batches, order them by proxy_url so we get all tasks
|
16
|
+
# to a certain proxy "close to each other"
|
17
|
+
remote_tasks.pending.order(:proxy_url, :id).find_in_batches(:batch_size => batch_size) do |batch|
|
18
|
+
# Group the tasks by operation, in theory there should be only one operation
|
19
|
+
batch.group_by(&:operation).each do |operation, group|
|
20
|
+
ForemanTasks::RemoteTask.batch_trigger(operation, group)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def remote_tasks
|
26
|
+
action.task.remote_sub_tasks
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def batch_size
|
32
|
+
Setting['foreman_tasks_proxy_batch_size']
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -26,13 +26,11 @@ module Actions
|
|
26
26
|
end
|
27
27
|
|
28
28
|
def check_triggered
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
.flatten
|
35
|
-
process_task_results tasks
|
29
|
+
in_remote_task_batches(remote_tasks.triggered) do |batch|
|
30
|
+
batch.group_by(&:proxy_url).each do |(url, tasks)|
|
31
|
+
tasks = poll_proxy_tasks(url, tasks).flatten
|
32
|
+
process_task_results tasks
|
33
|
+
end
|
36
34
|
end
|
37
35
|
end
|
38
36
|
|
@@ -68,6 +66,13 @@ module Actions
|
|
68
66
|
action.action_logger.warn(_('Failed to check on tasks on proxy at %{url}: %{exception}') % { :url => url, :exception => e.message })
|
69
67
|
[]
|
70
68
|
end
|
69
|
+
|
70
|
+
def in_remote_task_batches(scope)
|
71
|
+
# Sort by proxy_url so there are less requests per proxy
|
72
|
+
scope.order(:proxy_url, :id).find_in_batches(:batch_size => BATCH_SIZE) do |batch|
|
73
|
+
yield batch
|
74
|
+
end
|
75
|
+
end
|
71
76
|
end
|
72
77
|
end
|
73
78
|
end
|
@@ -26,8 +26,15 @@ module Actions
|
|
26
26
|
|
27
27
|
def plan(proxy, klass, options)
|
28
28
|
options[:connection_options] ||= {}
|
29
|
-
default_connection_options.each
|
30
|
-
|
29
|
+
default_connection_options.each do |key, value|
|
30
|
+
options[:connection_options][key] = value unless options[:connection_options].key?(key)
|
31
|
+
end
|
32
|
+
plan_self(options.merge(:proxy_url => proxy.url, :proxy_action_name => klass.to_s, :proxy_version => proxy_version(proxy)))
|
33
|
+
# Just saving the RemoteTask is enough when using batch triggering
|
34
|
+
# It will be picked up by the ProxyBatchTriggering middleware
|
35
|
+
if input[:use_batch_triggering] && with_batch_triggering?(input[:proxy_version])
|
36
|
+
prepare_remote_task.save!
|
37
|
+
end
|
31
38
|
end
|
32
39
|
|
33
40
|
def run(event = nil)
|
@@ -64,20 +71,22 @@ module Actions
|
|
64
71
|
|
65
72
|
def trigger_proxy_task
|
66
73
|
suspend do |_suspended_action|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
::ForemanTasks::RemoteTask.new(:remote_task_id => response['task_id'], :execution_plan_id => execution_plan_id,
|
71
|
-
:state => 'triggered', :proxy_url => input[:proxy_url], :step_id => run_step_id).save!
|
72
|
-
output[:proxy_task_id] = response['task_id']
|
74
|
+
remote_task = prepare_remote_task
|
75
|
+
remote_task.trigger(proxy_action_name, proxy_input)
|
76
|
+
output[:proxy_task_id] = remote_task.remote_task_id
|
73
77
|
end
|
74
78
|
end
|
75
79
|
|
80
|
+
def proxy_input(task_id = task.id)
|
81
|
+
input.merge(:callback => { :task_id => task_id,
|
82
|
+
:step_id => run_step_id })
|
83
|
+
end
|
84
|
+
|
76
85
|
def check_task_status
|
77
|
-
response = proxy.status_of_task(
|
86
|
+
response = proxy.status_of_task(proxy_task_id)
|
78
87
|
if %w[stopped paused].include? response['state']
|
79
88
|
if response['result'] == 'error'
|
80
|
-
raise ::Foreman::Exception, _('The smart proxy task %s failed.') %
|
89
|
+
raise ::Foreman::Exception, _('The smart proxy task %s failed.') % proxy_task_id
|
81
90
|
else
|
82
91
|
on_data(response['actions'].find { |block_action| block_action['class'] == proxy_action_name }['output'])
|
83
92
|
end
|
@@ -90,14 +99,14 @@ module Actions
|
|
90
99
|
if output[:cancel_sent]
|
91
100
|
error! ForemanTasks::Task::TaskCancelledException.new(_('Cancel enforced: the task might be still running on the proxy'))
|
92
101
|
else
|
93
|
-
proxy.cancel_task(
|
102
|
+
proxy.cancel_task(proxy_task_id)
|
94
103
|
output[:cancel_sent] = true
|
95
104
|
suspend
|
96
105
|
end
|
97
106
|
end
|
98
107
|
|
99
108
|
def abort_proxy_task
|
100
|
-
proxy.cancel_task(
|
109
|
+
proxy.cancel_task(proxy_task_id)
|
101
110
|
error! ForemanTasks::Task::TaskCancelledException.new(_('Task aborted: the task might be still running on the proxy'))
|
102
111
|
end
|
103
112
|
|
@@ -132,6 +141,11 @@ module Actions
|
|
132
141
|
input[:proxy_action_name]
|
133
142
|
end
|
134
143
|
|
144
|
+
# @override String name of a operation to be triggered on server
|
145
|
+
def proxy_operation_name
|
146
|
+
input[:proxy_operation_name]
|
147
|
+
end
|
148
|
+
|
135
149
|
def proxy
|
136
150
|
ProxyAPI::ForemanDynflow::DynflowProxy.new(:url => input[:proxy_url])
|
137
151
|
end
|
@@ -139,8 +153,8 @@ module Actions
|
|
139
153
|
def proxy_output(live = false)
|
140
154
|
if output.key?(:proxy_output) || state == :error
|
141
155
|
output.fetch(:proxy_output, {})
|
142
|
-
elsif live &&
|
143
|
-
proxy_data = proxy.status_of_task(
|
156
|
+
elsif live && proxy_task_id
|
157
|
+
proxy_data = proxy.status_of_task(proxy_task_id)['actions'].detect { |action| action['class'] == proxy_action_name }
|
144
158
|
proxy_data.fetch('output', {})
|
145
159
|
else
|
146
160
|
{}
|
@@ -174,7 +188,13 @@ module Actions
|
|
174
188
|
# Fails if the plan is not finished within 60 seconds from the first task trigger attempt on the smart proxy
|
175
189
|
# If the triggering fails, it retries 3 more times with 15 second delays
|
176
190
|
{ :retry_interval => Setting['foreman_tasks_proxy_action_retry_interval'] || 15,
|
177
|
-
:retry_count => Setting['foreman_tasks_proxy_action_retry_count'] || 4
|
191
|
+
:retry_count => Setting['foreman_tasks_proxy_action_retry_count'] || 4,
|
192
|
+
:proxy_batch_triggering => Setting['foreman_tasks_proxy_batch_trigger'] || false }
|
193
|
+
end
|
194
|
+
|
195
|
+
def with_batch_triggering?(proxy_version)
|
196
|
+
(proxy_version[:major] == 1 && proxy_version[:minor] > 20) || proxy_version[:major] > 1 &&
|
197
|
+
input.fetch(:connection_options, {}).fetch(:proxy_batch_triggering, false)
|
178
198
|
end
|
179
199
|
|
180
200
|
def clean_remote_task(*_args)
|
@@ -183,6 +203,11 @@ module Actions
|
|
183
203
|
|
184
204
|
private
|
185
205
|
|
206
|
+
def proxy_version(proxy)
|
207
|
+
match = proxy.statuses[:version].version['version'].match(/(\d+)\.(\d+)\.(\d+)/)
|
208
|
+
{ :major => match[1].to_i, :minor => match[2].to_i, :patch => match[3].to_i }
|
209
|
+
end
|
210
|
+
|
186
211
|
def failed_proxy_tasks
|
187
212
|
metadata[:failed_proxy_tasks] ||= []
|
188
213
|
end
|
@@ -198,7 +223,7 @@ module Actions
|
|
198
223
|
end
|
199
224
|
|
200
225
|
def format_exception(exception)
|
201
|
-
{ :proxy_task_id =>
|
226
|
+
{ :proxy_task_id => proxy_task_id,
|
202
227
|
:exception_class => exception.class.name,
|
203
228
|
:exception_message => exception.message,
|
204
229
|
:timestamp => Time.now.to_f }
|
@@ -218,5 +243,16 @@ module Actions
|
|
218
243
|
raise exception
|
219
244
|
end
|
220
245
|
end
|
246
|
+
|
247
|
+
def prepare_remote_task
|
248
|
+
::ForemanTasks::RemoteTask.new(:execution_plan_id => execution_plan_id,
|
249
|
+
:proxy_url => input[:proxy_url],
|
250
|
+
:step_id => run_step_id,
|
251
|
+
:operation => proxy_operation_name)
|
252
|
+
end
|
253
|
+
|
254
|
+
def proxy_task_id
|
255
|
+
output[:proxy_task_id] ||= remote_task.try(:remote_task_id)
|
256
|
+
end
|
221
257
|
end
|
222
258
|
end
|
@@ -39,6 +39,18 @@ module ProxyAPI
|
|
39
39
|
payload = MultiJson.dump(:task_ids => ids)
|
40
40
|
MultiJson.load(Task.new(@args).send(:post, payload, 'status'))
|
41
41
|
end
|
42
|
+
|
43
|
+
def operations
|
44
|
+
MultiJson.load(Task.new(@args).send(:get, 'operations'))
|
45
|
+
end
|
46
|
+
|
47
|
+
def launch_tasks(operation, input, options = {})
|
48
|
+
data = { :input => input,
|
49
|
+
:operation => operation,
|
50
|
+
:options => options }
|
51
|
+
payload = MultiJson.dump(data)
|
52
|
+
MultiJson.load(Task.new(@args).send(:post, payload, 'launch'))
|
53
|
+
end
|
42
54
|
end
|
43
55
|
end
|
44
56
|
end
|
@@ -8,5 +8,79 @@ module ForemanTasks
|
|
8
8
|
:inverse_of => :remote_tasks
|
9
9
|
|
10
10
|
scope :triggered, -> { where(:state => 'triggered') }
|
11
|
+
scope :pending, -> { where(:state => 'new') }
|
12
|
+
|
13
|
+
delegate :proxy_action_name, :to => :action
|
14
|
+
|
15
|
+
# Triggers a task on the proxy "the old way"
|
16
|
+
def trigger(proxy_action_name, input)
|
17
|
+
response = begin
|
18
|
+
proxy.trigger_task(proxy_action_name, input).merge('result' => 'success')
|
19
|
+
rescue RestClient::Exception => e
|
20
|
+
logger.warn "Could not trigger task on the smart proxy: #{e.message}"
|
21
|
+
{}
|
22
|
+
end
|
23
|
+
update_from_batch_trigger(response)
|
24
|
+
save!
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.batch_trigger(operation, remote_tasks)
|
28
|
+
remote_tasks.group_by(&:proxy_url).values.map do |group|
|
29
|
+
input_hash = group.reduce({}) do |acc, remote_task|
|
30
|
+
acc.merge(remote_task.execution_plan_id => { :action_input => remote_task.proxy_input,
|
31
|
+
:action_class => remote_task.proxy_action_name })
|
32
|
+
end
|
33
|
+
safe_batch_trigger(operation, group, input_hash)
|
34
|
+
end
|
35
|
+
remote_tasks
|
36
|
+
end
|
37
|
+
|
38
|
+
# Attempt to trigger the tasks using the new API and fall back to the old one
|
39
|
+
# if it fails
|
40
|
+
def self.safe_batch_trigger(operation, remote_tasks, input_hash)
|
41
|
+
results = remote_tasks.first.proxy.launch_tasks(operation, input_hash)
|
42
|
+
remote_tasks.each { |remote_task| remote_task.update_from_batch_trigger results[remote_task.execution_plan_id] }
|
43
|
+
rescue RestClient::NotFound
|
44
|
+
fallback_batch_trigger remote_tasks, input_hash
|
45
|
+
end
|
46
|
+
|
47
|
+
# Trigger the tasks one-by-one using the old API
|
48
|
+
def self.fallback_batch_trigger(remote_tasks, input_hash)
|
49
|
+
remote_tasks.each do |remote_task|
|
50
|
+
task_data = input_hash[remote_task.execution_plan_id]
|
51
|
+
remote_task.trigger(task_data[:action_class], task_data[:action_input])
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def update_from_batch_trigger(data)
|
56
|
+
if data['result'] == 'success'
|
57
|
+
self.remote_task_id = data['task_id']
|
58
|
+
self.state = 'triggered'
|
59
|
+
else
|
60
|
+
# Tell the action the task on the smart proxy stopped
|
61
|
+
ForemanTasks.dynflow.world.event execution_plan_id,
|
62
|
+
step_id,
|
63
|
+
::Actions::ProxyAction::ProxyActionStopped.new
|
64
|
+
end
|
65
|
+
save!
|
66
|
+
end
|
67
|
+
|
68
|
+
def proxy_input
|
69
|
+
action.proxy_input(task.id)
|
70
|
+
end
|
71
|
+
|
72
|
+
def proxy
|
73
|
+
@proxy ||= ::ProxyAPI::ForemanDynflow::DynflowProxy.new(:url => proxy_url)
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def action
|
79
|
+
@action ||= ForemanTasks.dynflow.world.persistence.load_action(step)
|
80
|
+
end
|
81
|
+
|
82
|
+
def step
|
83
|
+
@step ||= task.execution_plan.steps[step_id]
|
84
|
+
end
|
11
85
|
end
|
12
86
|
end
|
@@ -83,7 +83,7 @@ module ForemanTasks
|
|
83
83
|
end
|
84
84
|
|
85
85
|
def input
|
86
|
-
return
|
86
|
+
return active_job_data['arguments'] if active_job?
|
87
87
|
main_action.task_input if main_action.respond_to?(:task_input)
|
88
88
|
end
|
89
89
|
|
@@ -113,12 +113,8 @@ module ForemanTasks
|
|
113
113
|
def main_action
|
114
114
|
return @main_action if defined?(@main_action)
|
115
115
|
if active_job?
|
116
|
-
|
117
|
-
|
118
|
-
else
|
119
|
-
execution_plan_action.input
|
120
|
-
end
|
121
|
-
@main_action = active_job_action(args['job_class'], args['job_arguments'])
|
116
|
+
job_data = active_job_data
|
117
|
+
@main_action = active_job_action(job_data['job_class'], job_data['arguments'])
|
122
118
|
else
|
123
119
|
@main_action = execution_plan && execution_plan.root_plan_step.try(:action, execution_plan)
|
124
120
|
end
|
@@ -140,6 +136,17 @@ module ForemanTasks
|
|
140
136
|
execution_plan_action.class == ::Dynflow::ActiveJob::QueueAdapters::JobWrapper
|
141
137
|
end
|
142
138
|
|
139
|
+
def active_job_data
|
140
|
+
args = if execution_plan.delay_record
|
141
|
+
execution_plan.delay_record.args.first
|
142
|
+
else
|
143
|
+
execution_plan_action.input
|
144
|
+
end
|
145
|
+
return args['job_data'] if args.key?('job_data')
|
146
|
+
# For dynflow <= 1.2.1
|
147
|
+
{ 'job_class' => args['job_class'], 'arguments' => args['job_arguments'] }
|
148
|
+
end
|
149
|
+
|
143
150
|
def execution_plan_action
|
144
151
|
execution_plan.root_plan_step.action(execution_plan)
|
145
152
|
end
|
@@ -10,7 +10,9 @@ class Setting::ForemanTasks < Setting
|
|
10
10
|
set('dynflow_enable_console', N_('Enable the dynflow console (/foreman_tasks/dynflow) for debugging'), true),
|
11
11
|
set('dynflow_console_require_auth', N_('Require user to be authenticated as user with admin rights when accessing dynflow console'), true),
|
12
12
|
set('foreman_tasks_proxy_action_retry_count', N_('Number of attempts to start a task on the smart proxy before failing'), 4),
|
13
|
-
set('foreman_tasks_proxy_action_retry_interval', N_('Time in seconds between retries'), 15)
|
13
|
+
set('foreman_tasks_proxy_action_retry_interval', N_('Time in seconds between retries'), 15),
|
14
|
+
set('foreman_tasks_proxy_batch_trigger', N_('Allow triggering tasks on the smart proxy in batches'), true),
|
15
|
+
set('foreman_tasks_proxy_batch_size', N_('Number of tasks which should be sent to the smart proxy in one request, if foreman_tasks_proxy_batch_trigger is enabled'), 1000)
|
14
16
|
].each { |s| create! s.update(:category => 'Setting::ForemanTasks') }
|
15
17
|
end
|
16
18
|
|
@@ -0,0 +1,12 @@
|
|
1
|
+
<% content_for(:javascripts) do %>
|
2
|
+
<%= webpacked_plugins_js_for :'foreman-tasks' %>
|
3
|
+
<% end %>
|
4
|
+
|
5
|
+
<% content_for(:content) do %>
|
6
|
+
<%= notifications %>
|
7
|
+
<div id="organization-id" data-id="<%= Organization.current.id if Organization.current %>" ></div>
|
8
|
+
<div id="user-id" data-id="<%= User.current.id if User.current %>" ></div>
|
9
|
+
<div id="foremanTasksReactRoot"></div>
|
10
|
+
<% end %>
|
11
|
+
<%= render file: "layouts/base" %>
|
12
|
+
<%= mount_react_component('ForemanTasks', '#foremanTasksReactRoot') %>
|