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
data/config/routes.rb
CHANGED
@@ -23,6 +23,9 @@ Foreman::Application.routes.draw do
|
|
23
23
|
end
|
24
24
|
end
|
25
25
|
|
26
|
+
match '/ex_tasks' => 'react#index', :via => [:get]
|
27
|
+
match '/ex_tasks/:id' => 'react#index', :via => [:get]
|
28
|
+
|
26
29
|
namespace :api do
|
27
30
|
resources :recurring_logics, :only => [:index, :show, :update] do
|
28
31
|
member do
|
data/foreman-tasks.gemspec
CHANGED
@@ -29,7 +29,7 @@ same resource. It also optionally provides Dynflow infrastructure for using it f
|
|
29
29
|
s.extra_rdoc_files = Dir['README*', 'LICENSE']
|
30
30
|
|
31
31
|
s.add_dependency "foreman-tasks-core"
|
32
|
-
s.add_dependency "dynflow", '>= 1.2.
|
32
|
+
s.add_dependency "dynflow", '>= 1.2.2'
|
33
33
|
s.add_dependency "sinatra" # for Dynflow web console
|
34
34
|
s.add_dependency "parse-cron", '~> 0.1.4'
|
35
35
|
s.add_dependency "get_process_mem" # for memory polling
|
data/lib/foreman_tasks/engine.rb
CHANGED
@@ -57,6 +57,7 @@ module ForemanTasks
|
|
57
57
|
|
58
58
|
security_block :foreman_tasks do |_map|
|
59
59
|
permission :view_foreman_tasks, { :'foreman_tasks/tasks' => [:auto_complete_search, :sub_tasks, :index, :show],
|
60
|
+
:'foreman_tasks/react' => [:index],
|
60
61
|
:'foreman_tasks/api/tasks' => [:bulk_search, :show, :index, :summary] }, :resource_type => ForemanTasks::Task.name
|
61
62
|
permission :edit_foreman_tasks, { :'foreman_tasks/tasks' => [:resume, :unlock, :force_unlock, :cancel_step, :cancel, :abort],
|
62
63
|
:'foreman_tasks/api/tasks' => [:bulk_resume] }, :resource_type => ForemanTasks::Task.name
|
data/lib/foreman_tasks.rb
CHANGED
@@ -12,7 +12,11 @@ module ForemanTasks
|
|
12
12
|
extend Algebrick::Matching
|
13
13
|
|
14
14
|
def self.dynflow
|
15
|
-
@dynflow ||=
|
15
|
+
@dynflow ||= begin
|
16
|
+
world = ForemanTasks::Dynflow.new(nil, ForemanTasks::Dynflow::Configuration.new)
|
17
|
+
ForemanTasksCore.dynflow_setup(world) if defined?(ForemanTasksCore)
|
18
|
+
world
|
19
|
+
end
|
16
20
|
end
|
17
21
|
|
18
22
|
def self.trigger(action, *args, &block)
|
data/package.json
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
{
|
2
|
+
"name": "foreman-tasks",
|
3
|
+
"version": "1.0.0",
|
4
|
+
"description": "Foreman Tasks =============",
|
5
|
+
"main": "index.js",
|
6
|
+
"scripts": {
|
7
|
+
"lint": "./node_modules/.bin/eslint -c .eslintrc webpack/",
|
8
|
+
"test": "node node_modules/.bin/jest --no-cache",
|
9
|
+
"test:watch": "node node_modules/.bin/jest --watchAll",
|
10
|
+
"test:current": "node node_modules/.bin/jest --watch",
|
11
|
+
"coveralls": "cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js",
|
12
|
+
"storybook": "start-storybook -p 6006",
|
13
|
+
"create-react-component": "yo react-domain"
|
14
|
+
},
|
15
|
+
"repository": {
|
16
|
+
"type": "git",
|
17
|
+
"url": "git+https://github.com/theforeman/foreman-tasks.git"
|
18
|
+
},
|
19
|
+
"bugs": {
|
20
|
+
"url": "http://projects.theforeman.org/projects/foreman-tasks/issues"
|
21
|
+
},
|
22
|
+
"devDependencies": {
|
23
|
+
"@storybook/addon-actions": "~3.4.11",
|
24
|
+
"@storybook/addon-knobs": "~3.4.11",
|
25
|
+
"@storybook/react": "~3.4.11",
|
26
|
+
"babel-cli": "^6.10.1",
|
27
|
+
"babel-core": "^6.26.3",
|
28
|
+
"babel-eslint": "^8.2.3",
|
29
|
+
"babel-jest": "^23.6.0",
|
30
|
+
"babel-loader": "^7.1.1",
|
31
|
+
"babel-plugin-dynamic-import-node": "^2.0.0",
|
32
|
+
"babel-plugin-lodash": "^3.3.4",
|
33
|
+
"babel-plugin-module-resolver": "^3.2.0",
|
34
|
+
"babel-plugin-syntax-dynamic-import": "^6.18.0",
|
35
|
+
"babel-plugin-transform-class-properties": "^6.24.1",
|
36
|
+
"babel-plugin-transform-object-assign": "^6.8.0",
|
37
|
+
"babel-plugin-transform-object-rest-spread": "^6.8.0",
|
38
|
+
"babel-preset-env": "^1.7.0",
|
39
|
+
"babel-preset-react": "^6.5.0",
|
40
|
+
"coveralls": "^3.0.0",
|
41
|
+
"enzyme": "^3.4.0",
|
42
|
+
"enzyme-adapter-react-16": "^1.4.0",
|
43
|
+
"enzyme-to-json": "^3.2.1",
|
44
|
+
"eslint": "^4.10.0",
|
45
|
+
"eslint-import-resolver-babel-module": "^4.0.0",
|
46
|
+
"eslint-plugin-patternfly-react": "0.2.0",
|
47
|
+
"jest-cli": "^23.6.0",
|
48
|
+
"jest-prop-type-error": "^1.1.0",
|
49
|
+
"patternfly": "^3.59.1",
|
50
|
+
"prettier": "^1.13.5",
|
51
|
+
"raf": "^3.4.0",
|
52
|
+
"react-redux-test-utils": "^0.1.1",
|
53
|
+
"react-remarkable": "^1.1.3",
|
54
|
+
"stylelint": "^9.3.0",
|
55
|
+
"stylelint-config-standard": "^18.0.0"
|
56
|
+
},
|
57
|
+
"dependencies": {
|
58
|
+
"babel-polyfill": "^6.26.0",
|
59
|
+
"classnames": "^2.2.5",
|
60
|
+
"patternfly-react": "^2.29.0",
|
61
|
+
"prop-types": "^15.6.0",
|
62
|
+
"react": "^16.8.1",
|
63
|
+
"react-dom": "^16.8.1",
|
64
|
+
"react-redux": "^5.0.6",
|
65
|
+
"react-router": "^4.3.1",
|
66
|
+
"react-router-bootstrap": "^0.24.4",
|
67
|
+
"react-router-dom": "^4.3.1",
|
68
|
+
"redux": "^3.6.0",
|
69
|
+
"redux-thunk": "^2.3.0",
|
70
|
+
"reselect": "^3.0.1",
|
71
|
+
"seamless-immutable": "^7.1.2",
|
72
|
+
"uuid": "^3.3.2"
|
73
|
+
},
|
74
|
+
"jest": {
|
75
|
+
"automock": true,
|
76
|
+
"verbose": true,
|
77
|
+
"testMatch": [
|
78
|
+
"**/*.test.js"
|
79
|
+
],
|
80
|
+
"testURL": "http://localhost/",
|
81
|
+
"collectCoverage": true,
|
82
|
+
"collectCoverageFrom": [
|
83
|
+
"webpack/**/*.js",
|
84
|
+
"!webpack/index.js",
|
85
|
+
"!webpack/test_setup.js",
|
86
|
+
"!webpack/**/bundle*",
|
87
|
+
"!webpack/stories/**",
|
88
|
+
"!webpack/**/*stories.js"
|
89
|
+
],
|
90
|
+
"coverageReporters": [
|
91
|
+
"lcov"
|
92
|
+
],
|
93
|
+
"unmockedModulePathPatterns": [
|
94
|
+
"webpack/",
|
95
|
+
"react",
|
96
|
+
"node_modules/"
|
97
|
+
],
|
98
|
+
"moduleNameMapper": {
|
99
|
+
"^.+\\.(png|gif|css|scss)$": "identity-obj-proxy"
|
100
|
+
},
|
101
|
+
"globals": {
|
102
|
+
"__testing__": true
|
103
|
+
},
|
104
|
+
"transform": {
|
105
|
+
"^.+\\.js$": "babel-jest"
|
106
|
+
},
|
107
|
+
"moduleDirectories": [
|
108
|
+
"node_modules",
|
109
|
+
"webpack"
|
110
|
+
],
|
111
|
+
"setupFiles": [
|
112
|
+
"raf/polyfill",
|
113
|
+
"jest-prop-type-error",
|
114
|
+
"./webpack/test_setup.js"
|
115
|
+
]
|
116
|
+
}
|
117
|
+
}
|
@@ -65,6 +65,9 @@ module ForemanTasks
|
|
65
65
|
describe 'POST /tasks/callback' do
|
66
66
|
it 'passes the data to the corresponding action' do
|
67
67
|
Support::DummyProxyAction.reset
|
68
|
+
ForemanTasks::RemoteTask.any_instance
|
69
|
+
.expects(:proxy)
|
70
|
+
.returns(Support::DummyProxyAction.proxy)
|
68
71
|
|
69
72
|
triggered = ForemanTasks.trigger(Support::DummyProxyAction,
|
70
73
|
Support::DummyProxyAction.proxy,
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'foreman_tasks_core_test_helper'
|
2
|
+
require 'foreman_tasks/test_helpers'
|
3
|
+
require 'foreman_tasks_core/runner'
|
4
|
+
|
5
|
+
module ForemanTasksCore
|
6
|
+
module Runner
|
7
|
+
describe Dispatcher::RunnerActor do
|
8
|
+
include ForemanTasks::TestHelpers::WithInThreadExecutor
|
9
|
+
|
10
|
+
let(:dispatcher) { Dispatcher.instance }
|
11
|
+
let(:suspended_action) { mock }
|
12
|
+
let(:runner) { mock.tap { |r| r.stubs(:id) } }
|
13
|
+
let(:clock) { ForemanTasks.dynflow.world.clock }
|
14
|
+
let(:logger) { mock.tap { |l| l.stubs(:debug) } }
|
15
|
+
let(:actor) do
|
16
|
+
Dispatcher::RunnerActor.new dispatcher, suspended_action, runner, clock, logger
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'delivers all updates to actions' do
|
20
|
+
targets = (0..2).map { mock }.each_with_index { |mock, index| mock.expects(:<<).with(index) }
|
21
|
+
updates = targets.each_with_index.reduce({}) { |acc, (cur, index)| acc.merge(cur => index) }
|
22
|
+
runner.expects(:run_refresh).returns(updates)
|
23
|
+
actor.expects(:plan_next_refresh)
|
24
|
+
actor.refresh_runner
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'plans next refresh' do
|
28
|
+
runner.expects(:run_refresh).returns({})
|
29
|
+
actor.expects(:plan_next_refresh)
|
30
|
+
actor.refresh_runner
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'does not plan next resfresh if done' do
|
34
|
+
update = Update.new(nil, 0)
|
35
|
+
suspended_action.expects(:<<).with(update)
|
36
|
+
runner.expects(:run_refresh).returns(suspended_action => update)
|
37
|
+
dispatcher.expects(:finish)
|
38
|
+
dispatcher.ticker.expects(:tell).never
|
39
|
+
actor.refresh_runner
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
require 'foreman_tasks_core_test_helper'
|
2
|
+
require 'foreman_tasks/test_helpers'
|
3
|
+
require 'foreman_tasks_core/runner'
|
4
|
+
require 'ostruct'
|
5
|
+
|
6
|
+
module ForemanTasksCore
|
7
|
+
module Runner
|
8
|
+
class RunnerTest < ActiveSupport::TestCase
|
9
|
+
include ForemanTasks::TestHelpers::WithInThreadExecutor
|
10
|
+
|
11
|
+
describe Base do
|
12
|
+
let(:suspended_action) { Class.new }
|
13
|
+
let(:runner) { Base.new suspended_action: suspended_action }
|
14
|
+
|
15
|
+
describe '#generate_updates' do
|
16
|
+
it 'returns empty hash when there are no outputs' do
|
17
|
+
runner.generate_updates.must_be :empty?
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'returns a hash with outputs' do
|
21
|
+
message = 'a message'
|
22
|
+
type = 'stdout'
|
23
|
+
runner.publish_data(message, type)
|
24
|
+
updates = runner.generate_updates
|
25
|
+
updates.keys.must_equal [suspended_action]
|
26
|
+
update = updates.values.first
|
27
|
+
update.exit_status.must_be :nil?
|
28
|
+
update.continuous_output.raw_outputs.count.must_equal 1
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'works in compatibility mode' do
|
32
|
+
runner = Base.new
|
33
|
+
message = 'a message'
|
34
|
+
type = 'stdout'
|
35
|
+
runner.publish_data(message, type)
|
36
|
+
updates = runner.generate_updates
|
37
|
+
updates.keys.must_equal [nil]
|
38
|
+
update = updates.values.first
|
39
|
+
update.exit_status.must_be :nil?
|
40
|
+
update.continuous_output.raw_outputs.count.must_equal 1
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe Parent do
|
46
|
+
let(:suspended_action) { ::Dynflow::Action::Suspended.allocate }
|
47
|
+
let(:runner) { Parent.new targets, suspended_action: suspended_action }
|
48
|
+
let(:targets) do
|
49
|
+
{ 'foo' => { 'execution_plan_id' => '123', 'run_step_id' => 2 },
|
50
|
+
'bar' => { 'execution_plan_id' => '456', 'run_step_id' => 2 } }
|
51
|
+
end
|
52
|
+
|
53
|
+
describe '#initialize_continuous_outputs' do
|
54
|
+
it 'initializes outputs for targets and parent' do
|
55
|
+
outputs = runner.initialize_continuous_outputs
|
56
|
+
outputs.keys.count.must_equal 3
|
57
|
+
outputs.values.each { |output| output.must_be_instance_of ContinuousOutput }
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
describe '#generate_updates' do
|
62
|
+
it 'returns only updates for hosts with pending outputs' do
|
63
|
+
runner.generate_updates.must_equal({})
|
64
|
+
runner.publish_data_for('foo', 'something', 'something')
|
65
|
+
updates = runner.generate_updates
|
66
|
+
updates.keys.count.must_equal 1
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'works in compatibility mode' do
|
70
|
+
runner = Parent.new targets
|
71
|
+
runner.generate_updates.must_equal({})
|
72
|
+
runner.broadcast_data('something', 'stdout')
|
73
|
+
updates = runner.generate_updates
|
74
|
+
updates.keys.count.must_equal 3
|
75
|
+
# One of the keys is nil in compatibility mode
|
76
|
+
updates.keys.compact.count.must_equal 2
|
77
|
+
updates.keys.compact.each do |key|
|
78
|
+
key.must_be_instance_of ::Dynflow::Action::Suspended
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'works without compatibility mode' do
|
83
|
+
runner.broadcast_data('something', 'stdout')
|
84
|
+
updates = runner.generate_updates
|
85
|
+
updates.keys.count.must_equal 3
|
86
|
+
updates.keys.each do |key|
|
87
|
+
key.must_be_instance_of ::Dynflow::Action::Suspended
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
describe '#publish_data_for' do
|
93
|
+
it 'publishes data for a single host' do
|
94
|
+
runner.publish_data_for('foo', 'message', 'stdout')
|
95
|
+
runner.generate_updates.keys.count.must_equal 1
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
describe '#broadcast_data' do
|
100
|
+
it 'publishes data for all hosts' do
|
101
|
+
runner.broadcast_data('message', 'stdout')
|
102
|
+
runner.generate_updates.keys.count.must_equal 3
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
describe '#publish_exception' do
|
107
|
+
let(:exception) do
|
108
|
+
exception = RuntimeError.new
|
109
|
+
exception.stubs(:backtrace).returns([])
|
110
|
+
exception
|
111
|
+
end
|
112
|
+
|
113
|
+
before { runner.logger.stubs(:error) }
|
114
|
+
|
115
|
+
it 'broadcasts the exception to all targets' do
|
116
|
+
runner.expects(:publish_exit_status).never
|
117
|
+
runner.publish_exception('general failure', exception, false)
|
118
|
+
runner.generate_updates.keys.count.must_equal 3
|
119
|
+
end
|
120
|
+
|
121
|
+
it 'publishes exit status if fatal' do
|
122
|
+
runner.expects(:publish_exit_status)
|
123
|
+
runner.publish_exception('general failure', exception, true)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'foreman_tasks_core_test_helper'
|
2
|
+
require 'foreman_tasks/test_helpers'
|
3
|
+
|
4
|
+
module ForemanTasksCore
|
5
|
+
module TaskLauncher
|
6
|
+
class TaskLauncherTest < ActiveSupport::TestCase
|
7
|
+
include ForemanTasks::TestHelpers::WithInThreadExecutor
|
8
|
+
|
9
|
+
describe ForemanTasksCore::TaskLauncher do
|
10
|
+
let(:launcher) { launcher_class.new ForemanTasks.dynflow.world, {} }
|
11
|
+
let(:launcher_input) { { 'action_class' => Support::DummyDynflowAction.to_s, 'action_input' => input } }
|
12
|
+
let(:input) { { :do => :something } }
|
13
|
+
let(:expected_result) { input.merge(:callback_host => {}) }
|
14
|
+
|
15
|
+
describe ForemanTasksCore::TaskLauncher::Single do
|
16
|
+
let(:launcher_class) { Single }
|
17
|
+
|
18
|
+
it 'triggers an action' do
|
19
|
+
Support::DummyDynflowAction.any_instance.expects(:plan).with do |arg|
|
20
|
+
arg.must_equal(expected_result)
|
21
|
+
end
|
22
|
+
launcher.launch!(launcher_input)
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'provides results' do
|
26
|
+
plan = launcher.launch!(launcher_input).finished.value!
|
27
|
+
launcher.results[:result].must_equal 'success'
|
28
|
+
plan.result.must_equal :success
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe ForemanTasksCore::TaskLauncher::Batch do
|
33
|
+
let(:launcher_class) { Batch }
|
34
|
+
|
35
|
+
it 'triggers the actions' do
|
36
|
+
Support::DummyDynflowAction.any_instance.expects(:plan).with { |arg| arg == expected_result }.twice
|
37
|
+
parent = launcher.launch!('foo' => launcher_input, 'bar' => launcher_input)
|
38
|
+
plan = parent.finished.value!
|
39
|
+
plan.result.must_equal :success
|
40
|
+
plan.sub_plans.count.must_equal 2
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'provides results' do
|
44
|
+
launcher.launch!('foo' => launcher_input, 'bar' => launcher_input)
|
45
|
+
launcher.results.keys.must_equal %w[foo bar]
|
46
|
+
launcher.results.values.each do |result|
|
47
|
+
plan = ForemanTasks.dynflow.world.persistence.load_execution_plan(result[:task_id])
|
48
|
+
result[:result].must_equal 'success'
|
49
|
+
plan.result.must_equal :success
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -2,6 +2,14 @@ require 'securerandom'
|
|
2
2
|
|
3
3
|
module Support
|
4
4
|
class DummyProxyAction < Actions::ProxyAction
|
5
|
+
class DummyProxyVersion
|
6
|
+
attr_reader :version
|
7
|
+
|
8
|
+
def initialize(version)
|
9
|
+
@version = { 'version' => version }
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
5
13
|
class DummyProxy
|
6
14
|
attr_reader :log, :task_triggered, :uuid
|
7
15
|
|
@@ -14,7 +22,7 @@ module Support
|
|
14
22
|
def trigger_task(*args)
|
15
23
|
@log[:trigger_task] << args
|
16
24
|
@task_triggered.fulfill(true)
|
17
|
-
{ 'task_id' => @uuid }
|
25
|
+
{ 'task_id' => @uuid, 'result' => 'success' }
|
18
26
|
end
|
19
27
|
|
20
28
|
def cancel_task(*args)
|
@@ -24,6 +32,10 @@ module Support
|
|
24
32
|
def url
|
25
33
|
'proxy.example.com'
|
26
34
|
end
|
35
|
+
|
36
|
+
def statuses
|
37
|
+
{ version: DummyProxyVersion.new('1.21.0') }
|
38
|
+
end
|
27
39
|
end
|
28
40
|
|
29
41
|
class ProxySelector < ::ForemanTasks::ProxySelector
|
@@ -32,6 +44,10 @@ module Support
|
|
32
44
|
end
|
33
45
|
end
|
34
46
|
|
47
|
+
def proxy_operation_name
|
48
|
+
'support'
|
49
|
+
end
|
50
|
+
|
35
51
|
def proxy
|
36
52
|
self.class.proxy
|
37
53
|
end
|
@@ -8,14 +8,18 @@ module ForemanTasks
|
|
8
8
|
let(:secrets) do
|
9
9
|
{ 'logins' => { 'admin' => 'changeme', 'root' => 'toor' } }
|
10
10
|
end
|
11
|
+
let(:batch_triggering) { false }
|
11
12
|
|
12
13
|
before do
|
14
|
+
Support::DummyProxyAction.any_instance.stubs(:with_batch_triggering?).returns(batch_triggering)
|
13
15
|
Support::DummyProxyAction.reset
|
16
|
+
RemoteTask.any_instance.stubs(:proxy).returns(Support::DummyProxyAction.proxy)
|
14
17
|
@action = create_and_plan_action(Support::DummyProxyAction,
|
15
18
|
Support::DummyProxyAction.proxy,
|
16
19
|
'Proxy::DummyAction',
|
17
20
|
'foo' => 'bar',
|
18
|
-
'secrets' => secrets
|
21
|
+
'secrets' => secrets,
|
22
|
+
'use_batch_triggering' => batch_triggering)
|
19
23
|
@action = run_action(@action)
|
20
24
|
end
|
21
25
|
|
@@ -26,12 +30,26 @@ module ForemanTasks
|
|
26
30
|
{ 'foo' => 'bar',
|
27
31
|
'secrets' => secrets,
|
28
32
|
'connection_options' =>
|
29
|
-
|
33
|
+
{ 'retry_interval' => 15, 'retry_count' => 4,
|
34
|
+
'proxy_batch_triggering' => batch_triggering },
|
35
|
+
'use_batch_triggering' => batch_triggering,
|
30
36
|
'proxy_url' => 'proxy.example.com',
|
31
37
|
'proxy_action_name' => 'Proxy::DummyAction',
|
38
|
+
"proxy_version" => { "major" => 1, "minor" => 21, "patch" => 0 },
|
32
39
|
'callback' => { 'task_id' => Support::DummyProxyAction.proxy.uuid, 'step_id' => @action.run_step_id } }]
|
33
40
|
proxy_call.must_equal(expected_call)
|
34
41
|
end
|
42
|
+
|
43
|
+
describe 'with batch triggering' do
|
44
|
+
let(:batch_triggering) { true }
|
45
|
+
it 'create remote tasks for batch triggering' do
|
46
|
+
task = RemoteTask.first
|
47
|
+
task.state.must_equal 'new'
|
48
|
+
task.execution_plan_id.must_equal @action.execution_plan_id
|
49
|
+
task.operation.must_equal 'support'
|
50
|
+
task.remote_task_id.must_be :nil?
|
51
|
+
end
|
52
|
+
end
|
35
53
|
end
|
36
54
|
|
37
55
|
describe 'resumed run' do
|
@@ -55,7 +55,7 @@ module ForemanTasks
|
|
55
55
|
|
56
56
|
specify 'it triggers the repeat when the task goes into planned state' do
|
57
57
|
delay_options = recurring_logic.generate_delay_options
|
58
|
-
task = ForemanTasks.delay HookedAction, delay_options, args
|
58
|
+
task = ForemanTasks.delay HookedAction, delay_options, *args
|
59
59
|
recurring_logic.tasks.count.must_equal 1
|
60
60
|
|
61
61
|
# Perform planning of the delayed plan
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'foreman_tasks_test_helper'
|
2
|
+
|
3
|
+
module ForemanTasks
|
4
|
+
class RemoteTaskTest < ActiveSupport::TestCase
|
5
|
+
describe 'batch triggering' do
|
6
|
+
let(:remote_tasks) do
|
7
|
+
(1..5).map do |i|
|
8
|
+
task = RemoteTask.new :execution_plan_id => i, :step_id => 1, :proxy_url => "something"
|
9
|
+
task.expects(:proxy_input).returns({})
|
10
|
+
task.expects(:proxy_action_name).returns('MyProxyAction')
|
11
|
+
task.save!
|
12
|
+
task
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'triggers in batches' do
|
17
|
+
results = remote_tasks.reduce({}) do |acc, cur|
|
18
|
+
acc.merge(cur.execution_plan_id.to_s => { 'task_id' => cur.id + 5, 'result' => 'success' })
|
19
|
+
end
|
20
|
+
|
21
|
+
fake_proxy = mock
|
22
|
+
fake_proxy.expects(:launch_tasks).returns(results)
|
23
|
+
remote_tasks.first.expects(:proxy).returns(fake_proxy)
|
24
|
+
RemoteTask.batch_trigger('a_operation', remote_tasks)
|
25
|
+
remote_tasks.each do |remote_task|
|
26
|
+
remote_task.reload
|
27
|
+
remote_task.state.must_equal 'triggered'
|
28
|
+
remote_task.remote_task_id.must_equal((remote_task.id + 5).to_s)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'fallbacks to old way when batch trigger gets 404' do
|
33
|
+
fake_proxy = mock
|
34
|
+
fake_proxy.expects(:launch_tasks).raises(RestClient::NotFound.new)
|
35
|
+
remote_tasks.first.expects(:proxy).returns(fake_proxy)
|
36
|
+
remote_tasks.each { |task| task.expects(:trigger) }
|
37
|
+
RemoteTask.batch_trigger('a_operation', remote_tasks)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
data/test/unit/task_test.rb
CHANGED
@@ -84,9 +84,11 @@ class TasksTest < ActiveSupport::TestCase
|
|
84
84
|
end
|
85
85
|
|
86
86
|
describe 'task without valid execution plan' do
|
87
|
+
let(:missing_task_uuid) { '11111111-2222-3333-4444-555555555555' }
|
88
|
+
|
87
89
|
let(:task) do
|
88
90
|
task = FactoryBot.create(:dynflow_task).tap do |task|
|
89
|
-
task.external_id =
|
91
|
+
task.external_id = missing_task_uuid
|
90
92
|
task.save
|
91
93
|
end
|
92
94
|
ForemanTasks::Task.find(task.id)
|
@@ -0,0 +1,27 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import { BrowserRouter } from 'react-router-dom';
|
3
|
+
import { LinkContainer } from 'react-router-bootstrap';
|
4
|
+
import { ButtonGroup, Button } from 'patternfly-react';
|
5
|
+
|
6
|
+
import routes from './Routes/ForemanTasksRoutes';
|
7
|
+
import ForemanTasksRouter from './Routes/ForemanTasksRouter';
|
8
|
+
|
9
|
+
const ForemanTasks = () => (
|
10
|
+
<BrowserRouter>
|
11
|
+
<div>
|
12
|
+
<div style={{ paddingTop: '10px', paddingBottom: '10px' }}>
|
13
|
+
<ButtonGroup bsSize="large">
|
14
|
+
<LinkContainer to={routes.indexTasks.path}>
|
15
|
+
<Button bsStyle="link">index-tasks-page</Button>
|
16
|
+
</LinkContainer>
|
17
|
+
<LinkContainer to={routes.showTask.path.replace(':id', 'some-id')}>
|
18
|
+
<Button bsStyle="link">show-task-page</Button>
|
19
|
+
</LinkContainer>
|
20
|
+
</ButtonGroup>
|
21
|
+
</div>
|
22
|
+
<ForemanTasksRouter />
|
23
|
+
</div>
|
24
|
+
</BrowserRouter>
|
25
|
+
);
|
26
|
+
|
27
|
+
export default ForemanTasks;
|
@@ -0,0 +1,10 @@
|
|
1
|
+
import { testComponentSnapshotsWithFixtures } from 'react-redux-test-utils';
|
2
|
+
|
3
|
+
import ForemanTasks from './ForemanTasks';
|
4
|
+
|
5
|
+
const fixtures = {
|
6
|
+
'render without Props': {},
|
7
|
+
};
|
8
|
+
|
9
|
+
describe('ForemanTasks', () =>
|
10
|
+
testComponentSnapshotsWithFixtures(ForemanTasks, fixtures));
|
@@ -0,0 +1,14 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import { Switch, Route } from 'react-router-dom';
|
3
|
+
|
4
|
+
import routes from './ForemanTasksRoutes';
|
5
|
+
|
6
|
+
const ForemanTasksRouter = () => (
|
7
|
+
<Switch>
|
8
|
+
{Object.entries(routes).map(([key, props]) => (
|
9
|
+
<Route key={key} {...props} />
|
10
|
+
))}
|
11
|
+
</Switch>
|
12
|
+
);
|
13
|
+
|
14
|
+
export default ForemanTasksRouter;
|
@@ -0,0 +1,22 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import { testComponentSnapshotsWithFixtures } from 'react-redux-test-utils';
|
3
|
+
|
4
|
+
import ForemanTasksRouter from './ForemanTasksRouter';
|
5
|
+
|
6
|
+
jest.mock('./ForemanTasksRoutes', () => ({
|
7
|
+
someRoute: {
|
8
|
+
path: '/some-route',
|
9
|
+
render: props => <span {...props}>some-route</span>,
|
10
|
+
},
|
11
|
+
someOtherRoute: {
|
12
|
+
path: '/some-other-route',
|
13
|
+
render: props => <span {...props}>some-other-route</span>,
|
14
|
+
},
|
15
|
+
}));
|
16
|
+
|
17
|
+
const fixtures = {
|
18
|
+
'render without Props': {},
|
19
|
+
};
|
20
|
+
|
21
|
+
describe('ForemanTasksRouter', () =>
|
22
|
+
testComponentSnapshotsWithFixtures(ForemanTasksRouter, fixtures));
|