foreman-tasks 0.15.0 → 0.15.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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') %>
|