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
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));
|