foreman-tasks-core 0.3.1 → 0.4.0
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/lib/foreman_tasks_core.rb +12 -13
- data/lib/foreman_tasks_core/runner.rb +5 -6
- data/lib/foreman_tasks_core/runner/command_runner.rb +2 -41
- data/lib/foreman_tasks_core/runner/dispatcher.rb +2 -195
- data/lib/foreman_tasks_core/shareable_action.rb +3 -23
- data/lib/foreman_tasks_core/version.rb +1 -1
- metadata +23 -21
- data/lib/foreman_tasks_core/continuous_output.rb +0 -50
- data/lib/foreman_tasks_core/otp_manager.rb +0 -31
- data/lib/foreman_tasks_core/runner/action.rb +0 -76
- data/lib/foreman_tasks_core/runner/base.rb +0 -83
- data/lib/foreman_tasks_core/runner/parent.rb +0 -48
- data/lib/foreman_tasks_core/runner/update.rb +0 -28
- data/lib/foreman_tasks_core/settings_loader.rb +0 -53
- data/lib/foreman_tasks_core/task_launcher.rb +0 -8
- data/lib/foreman_tasks_core/task_launcher/abstract.rb +0 -42
- data/lib/foreman_tasks_core/task_launcher/batch.rb +0 -37
- data/lib/foreman_tasks_core/task_launcher/single.rb +0 -14
- data/lib/foreman_tasks_core/ticker.rb +0 -45
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f5c0486d95d64e7da989f4ab1c99111ea47102935d397f3d8995ddb3d6bd682b
|
4
|
+
data.tar.gz: ba8622004e32347d1603fb59698f0626470e39bc977a992241c933fb10579e50
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f5f287084e00895124f7da6fe936d0db2d3016311fb7a33333650cf9ddd09d2b11a1c5f097831d46945eb5a586e2ba29be9f1b39811fba9a50b2f3ad57d5030d
|
7
|
+
data.tar.gz: 968bdcd5f45add8ab258e79db98e5c0a01fd09d843a1f8f9f0f91cf10184421dcc75e0fd97964433b308556edecb05f93b69b83942910c06430d2637448164ae
|
data/lib/foreman_tasks_core.rb
CHANGED
@@ -1,26 +1,25 @@
|
|
1
1
|
# The goal of ForemanTasksCore is to collect parts of foreman-tasks
|
2
2
|
# that can be shared by the Foreman server and Foreman proxy
|
3
|
+
#
|
4
|
+
require 'smart_proxy_dynflow'
|
5
|
+
require 'smart_proxy_dynflow/task_launcher'
|
6
|
+
require 'smart_proxy_dynflow/settings_loader'
|
7
|
+
require 'smart_proxy_dynflow/otp_manager'
|
3
8
|
|
4
|
-
require 'foreman_tasks_core/
|
5
|
-
require 'foreman_tasks_core/otp_manager'
|
6
|
-
require 'foreman_tasks_core/ticker'
|
7
|
-
require 'foreman_tasks_core/task_launcher'
|
9
|
+
require 'foreman_tasks_core/runner'
|
8
10
|
|
9
11
|
module ForemanTasksCore
|
10
12
|
def self.dynflow_world
|
11
|
-
|
12
|
-
@dynflow_world
|
13
|
+
Proxy::Dynflow::Core.world
|
13
14
|
end
|
14
15
|
|
15
16
|
def self.dynflow_present?
|
16
|
-
|
17
|
+
true
|
17
18
|
end
|
18
19
|
|
19
|
-
def self.dynflow_setup(
|
20
|
-
@dynflow_world = dynflow_world
|
21
|
-
end
|
20
|
+
def self.dynflow_setup(_dynflow_world); end
|
22
21
|
|
23
|
-
|
24
|
-
|
25
|
-
|
22
|
+
TaskLauncher = Proxy::Dynflow::TaskLauncher
|
23
|
+
SettingsLoader = Proxy::Dynflow::SettingsLoader
|
24
|
+
OtpManager = Proxy::Dynflow::OtpManager
|
26
25
|
end
|
@@ -1,10 +1,9 @@
|
|
1
|
+
require 'smart_proxy_dynflow/runner'
|
2
|
+
|
1
3
|
module ForemanTasksCore
|
4
|
+
Runner = Proxy::Dynflow::Runner
|
5
|
+
|
2
6
|
module Runner
|
7
|
+
Action = Proxy::Dynflow::Action::Runner
|
3
8
|
end
|
4
9
|
end
|
5
|
-
|
6
|
-
require 'foreman_tasks_core/runner/update'
|
7
|
-
require 'foreman_tasks_core/runner/base'
|
8
|
-
require 'foreman_tasks_core/runner/dispatcher'
|
9
|
-
require 'foreman_tasks_core/runner/action'
|
10
|
-
require 'foreman_tasks_core/runner/parent'
|
@@ -1,41 +1,2 @@
|
|
1
|
-
require '
|
2
|
-
require '
|
3
|
-
|
4
|
-
module ForemanTasksCore
|
5
|
-
module Runner
|
6
|
-
class CommandRunner < Runner::Base
|
7
|
-
def initialize_command(*command)
|
8
|
-
@command_out, @command_in, @command_pid = PTY.spawn(*command)
|
9
|
-
end
|
10
|
-
|
11
|
-
def refresh
|
12
|
-
return if @command_out.nil?
|
13
|
-
ready_outputs, * = IO.select([@command_out], nil, nil, 0.1)
|
14
|
-
if ready_outputs
|
15
|
-
if @command_out.nread > 0
|
16
|
-
lines = @command_out.read_nonblock(@command_out.nread)
|
17
|
-
else
|
18
|
-
close_io
|
19
|
-
Process.wait(@command_pid)
|
20
|
-
publish_exit_status($CHILD_STATUS.exitstatus)
|
21
|
-
end
|
22
|
-
publish_data(lines, 'stdout') if lines && !lines.empty?
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
def close
|
27
|
-
close_io
|
28
|
-
end
|
29
|
-
|
30
|
-
private
|
31
|
-
|
32
|
-
def close_io
|
33
|
-
@command_out.close if @command_out && !@command_out.closed?
|
34
|
-
@command_out = nil
|
35
|
-
|
36
|
-
@command_in.close if @command_in && !@command_in.closed?
|
37
|
-
@command_in = nil
|
38
|
-
end
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|
1
|
+
require 'foreman_tasks_core/runner'
|
2
|
+
require 'smart_proxy_dynflow/runner/command_runner'
|
@@ -1,195 +1,2 @@
|
|
1
|
-
require 'foreman_tasks_core/
|
2
|
-
|
3
|
-
module ForemanTasksCore
|
4
|
-
module Runner
|
5
|
-
class Dispatcher
|
6
|
-
def self.instance
|
7
|
-
return @instance if @instance
|
8
|
-
@instance = new(ForemanTasksCore.dynflow_world.clock,
|
9
|
-
ForemanTasksCore.dynflow_world.logger)
|
10
|
-
end
|
11
|
-
|
12
|
-
class RunnerActor < ::Dynflow::Actor
|
13
|
-
def initialize(dispatcher, suspended_action, runner, clock, logger, _options = {})
|
14
|
-
@dispatcher = dispatcher
|
15
|
-
@clock = clock
|
16
|
-
@ticker = dispatcher.ticker
|
17
|
-
@logger = logger
|
18
|
-
@suspended_action = suspended_action
|
19
|
-
@runner = runner
|
20
|
-
@finishing = false
|
21
|
-
end
|
22
|
-
|
23
|
-
def on_envelope(*args)
|
24
|
-
super
|
25
|
-
rescue => e
|
26
|
-
handle_exception(e)
|
27
|
-
end
|
28
|
-
|
29
|
-
def start_runner
|
30
|
-
@logger.debug("start runner #{@runner.id}")
|
31
|
-
set_timeout if @runner.timeout_interval
|
32
|
-
@runner.start
|
33
|
-
refresh_runner
|
34
|
-
ensure
|
35
|
-
plan_next_refresh
|
36
|
-
end
|
37
|
-
|
38
|
-
def refresh_runner
|
39
|
-
@logger.debug("refresh runner #{@runner.id}")
|
40
|
-
updates = @runner.run_refresh
|
41
|
-
|
42
|
-
updates.each { |receiver, update| (receiver || @suspended_action) << update }
|
43
|
-
|
44
|
-
# This is a workaround when the runner does not accept the suspended action
|
45
|
-
main_key = updates.keys.any?(&:nil?) ? nil : @suspended_action
|
46
|
-
main_process = updates[main_key]
|
47
|
-
finish if main_process && main_process.exit_status
|
48
|
-
ensure
|
49
|
-
@refresh_planned = false
|
50
|
-
plan_next_refresh
|
51
|
-
end
|
52
|
-
|
53
|
-
def timeout_runner
|
54
|
-
@logger.debug("timeout runner #{@runner.id}")
|
55
|
-
@runner.timeout
|
56
|
-
rescue => e
|
57
|
-
handle_exception(e, false)
|
58
|
-
end
|
59
|
-
|
60
|
-
def kill
|
61
|
-
@logger.debug("kill runner #{@runner.id}")
|
62
|
-
@runner.kill
|
63
|
-
rescue => e
|
64
|
-
handle_exception(e, false)
|
65
|
-
end
|
66
|
-
|
67
|
-
def finish
|
68
|
-
@logger.debug("finish runner #{@runner.id}")
|
69
|
-
@finishing = true
|
70
|
-
@dispatcher.finish(@runner.id)
|
71
|
-
end
|
72
|
-
|
73
|
-
def start_termination(*args)
|
74
|
-
@logger.debug("terminate #{@runner.id}")
|
75
|
-
super
|
76
|
-
@runner.close
|
77
|
-
finish_termination
|
78
|
-
end
|
79
|
-
|
80
|
-
def external_event(_event)
|
81
|
-
refresh_runner
|
82
|
-
end
|
83
|
-
|
84
|
-
private
|
85
|
-
|
86
|
-
def set_timeout
|
87
|
-
timeout_time = Time.now.getlocal + @runner.timeout_interval
|
88
|
-
@logger.debug("setting timeout for #{@runner.id} to #{timeout_time}")
|
89
|
-
@clock.ping(reference, timeout_time, :timeout_runner)
|
90
|
-
end
|
91
|
-
|
92
|
-
def plan_next_refresh
|
93
|
-
if !@finishing && !@refresh_planned
|
94
|
-
@logger.debug("planning to refresh #{@runner.id}")
|
95
|
-
@ticker.tell([:add_event, reference, :refresh_runner])
|
96
|
-
@refresh_planned = true
|
97
|
-
end
|
98
|
-
end
|
99
|
-
|
100
|
-
def handle_exception(exception, fatal = true)
|
101
|
-
@dispatcher.handle_command_exception(@runner.id, exception, fatal)
|
102
|
-
end
|
103
|
-
end
|
104
|
-
|
105
|
-
attr_reader :ticker
|
106
|
-
def initialize(clock, logger)
|
107
|
-
@mutex = Mutex.new
|
108
|
-
@clock = clock
|
109
|
-
@logger = logger
|
110
|
-
@ticker = ::ForemanTasksCore::Ticker.spawn('dispatcher-ticker', @clock, @logger, refresh_interval)
|
111
|
-
@runner_actors = {}
|
112
|
-
@runner_suspended_actions = {}
|
113
|
-
end
|
114
|
-
|
115
|
-
def synchronize(&block)
|
116
|
-
@mutex.synchronize(&block)
|
117
|
-
end
|
118
|
-
|
119
|
-
def start(suspended_action, runner)
|
120
|
-
synchronize do
|
121
|
-
begin
|
122
|
-
raise "Actor with runner id #{runner.id} already exists" if @runner_actors[runner.id]
|
123
|
-
runner.logger = @logger
|
124
|
-
runner_actor = RunnerActor.spawn("runner-actor-#{runner.id}", self, suspended_action, runner, @clock, @logger)
|
125
|
-
@runner_actors[runner.id] = runner_actor
|
126
|
-
@runner_suspended_actions[runner.id] = suspended_action
|
127
|
-
runner_actor.tell(:start_runner)
|
128
|
-
return runner.id
|
129
|
-
rescue => exception
|
130
|
-
_handle_command_exception(runner.id, exception)
|
131
|
-
return nil
|
132
|
-
end
|
133
|
-
end
|
134
|
-
end
|
135
|
-
|
136
|
-
def kill(runner_id)
|
137
|
-
synchronize do
|
138
|
-
begin
|
139
|
-
runner_actor = @runner_actors[runner_id]
|
140
|
-
runner_actor.tell(:kill) if runner_actor
|
141
|
-
rescue => exception
|
142
|
-
_handle_command_exception(runner_id, exception, false)
|
143
|
-
end
|
144
|
-
end
|
145
|
-
end
|
146
|
-
|
147
|
-
def finish(runner_id)
|
148
|
-
synchronize do
|
149
|
-
begin
|
150
|
-
_finish(runner_id)
|
151
|
-
rescue => exception
|
152
|
-
_handle_command_exception(runner_id, exception, false)
|
153
|
-
end
|
154
|
-
end
|
155
|
-
end
|
156
|
-
|
157
|
-
def external_event(runner_id, external_event)
|
158
|
-
synchronize do
|
159
|
-
runner_actor = @runner_actors[runner_id]
|
160
|
-
runner_actor.tell([:external_event, external_event]) if runner_actor
|
161
|
-
end
|
162
|
-
end
|
163
|
-
|
164
|
-
def handle_command_exception(*args)
|
165
|
-
synchronize { _handle_command_exception(*args) }
|
166
|
-
end
|
167
|
-
|
168
|
-
def refresh_interval
|
169
|
-
1
|
170
|
-
end
|
171
|
-
|
172
|
-
private
|
173
|
-
|
174
|
-
def _finish(runner_id)
|
175
|
-
runner_actor = @runner_actors.delete(runner_id)
|
176
|
-
return unless runner_actor
|
177
|
-
@logger.debug("closing session for command [#{runner_id}]," \
|
178
|
-
"#{@runner_actors.size} actors left ")
|
179
|
-
runner_actor.tell([:start_termination, Concurrent::Promises.resolvable_future])
|
180
|
-
ensure
|
181
|
-
@runner_suspended_actions.delete(runner_id)
|
182
|
-
end
|
183
|
-
|
184
|
-
def _handle_command_exception(runner_id, exception, fatal = true)
|
185
|
-
@logger.error("error while dispatching request to runner #{runner_id}:"\
|
186
|
-
"#{exception.class} #{exception.message}:\n #{exception.backtrace.join("\n")}")
|
187
|
-
suspended_action = @runner_suspended_actions[runner_id]
|
188
|
-
if suspended_action
|
189
|
-
suspended_action << Runner::Update.encode_exception('Runner error', exception, fatal)
|
190
|
-
end
|
191
|
-
_finish(runner_id) if fatal
|
192
|
-
end
|
193
|
-
end
|
194
|
-
end
|
195
|
-
end
|
1
|
+
require 'foreman_tasks_core/runner'
|
2
|
+
require 'smart_proxy_dynflow/runner/dispatcher'
|
@@ -1,25 +1,5 @@
|
|
1
|
-
|
2
|
-
class ShareableAction < ::Dynflow::Action
|
3
|
-
def plan(input)
|
4
|
-
input = input.dup
|
5
|
-
callback = input.delete('callback')
|
6
|
-
if callback
|
7
|
-
input[:task_id] = callback['task_id']
|
8
|
-
else
|
9
|
-
input[:task_id] ||= SecureRandom.uuid
|
10
|
-
end
|
11
|
-
|
12
|
-
planned_action = plan_self(input)
|
13
|
-
# code only applicable, when run with SmartProxyDynflowCore in place
|
14
|
-
if on_proxy? && callback
|
15
|
-
plan_action(SmartProxyDynflowCore::Callback::Action, callback, planned_action.output)
|
16
|
-
end
|
17
|
-
end
|
1
|
+
require 'smart_proxy_dynflow/action/shareable'
|
18
2
|
|
19
|
-
|
20
|
-
|
21
|
-
def on_proxy?
|
22
|
-
defined?(SmartProxyDynflowCore::Callback)
|
23
|
-
end
|
24
|
-
end
|
3
|
+
module ForemanTasksCore
|
4
|
+
ShareableAction = Proxy::Dynflow::Action::Shareable
|
25
5
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: foreman-tasks-core
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ivan Nečas
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-06-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: dynflow
|
@@ -24,9 +24,23 @@ dependencies:
|
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: 1.2.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: smart_proxy_dynflow
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.5.0
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 0.5.0
|
27
41
|
description: 'Common code used both at Forman and Foreman proxy regarding tasks
|
28
42
|
|
29
|
-
'
|
43
|
+
'
|
30
44
|
email:
|
31
45
|
- inecas@redhat.com
|
32
46
|
executables: []
|
@@ -35,27 +49,16 @@ extra_rdoc_files: []
|
|
35
49
|
files:
|
36
50
|
- LICENSE
|
37
51
|
- lib/foreman_tasks_core.rb
|
38
|
-
- lib/foreman_tasks_core/continuous_output.rb
|
39
|
-
- lib/foreman_tasks_core/otp_manager.rb
|
40
52
|
- lib/foreman_tasks_core/runner.rb
|
41
|
-
- lib/foreman_tasks_core/runner/action.rb
|
42
|
-
- lib/foreman_tasks_core/runner/base.rb
|
43
53
|
- lib/foreman_tasks_core/runner/command_runner.rb
|
44
54
|
- lib/foreman_tasks_core/runner/dispatcher.rb
|
45
|
-
- lib/foreman_tasks_core/runner/parent.rb
|
46
|
-
- lib/foreman_tasks_core/runner/update.rb
|
47
|
-
- lib/foreman_tasks_core/settings_loader.rb
|
48
55
|
- lib/foreman_tasks_core/shareable_action.rb
|
49
|
-
- lib/foreman_tasks_core/task_launcher.rb
|
50
|
-
- lib/foreman_tasks_core/task_launcher/abstract.rb
|
51
|
-
- lib/foreman_tasks_core/task_launcher/batch.rb
|
52
|
-
- lib/foreman_tasks_core/task_launcher/single.rb
|
53
|
-
- lib/foreman_tasks_core/ticker.rb
|
54
56
|
- lib/foreman_tasks_core/version.rb
|
55
57
|
homepage: https://github.com/theforeman/foreman-tasks
|
56
|
-
licenses:
|
58
|
+
licenses:
|
59
|
+
- GPL-3.0
|
57
60
|
metadata: {}
|
58
|
-
post_install_message:
|
61
|
+
post_install_message:
|
59
62
|
rdoc_options: []
|
60
63
|
require_paths:
|
61
64
|
- lib
|
@@ -70,9 +73,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
70
73
|
- !ruby/object:Gem::Version
|
71
74
|
version: '0'
|
72
75
|
requirements: []
|
73
|
-
|
74
|
-
|
75
|
-
signing_key:
|
76
|
+
rubygems_version: 3.1.2
|
77
|
+
signing_key:
|
76
78
|
specification_version: 4
|
77
79
|
summary: Common code used both at Forman and Foreman proxy regarding tasks
|
78
80
|
test_files: []
|
@@ -1,50 +0,0 @@
|
|
1
|
-
module ForemanTasksCore
|
2
|
-
class ContinuousOutput
|
3
|
-
attr_accessor :raw_outputs
|
4
|
-
|
5
|
-
def initialize(raw_outputs = [])
|
6
|
-
@raw_outputs = []
|
7
|
-
raw_outputs.each { |raw_output| add_raw_output(raw_output) }
|
8
|
-
end
|
9
|
-
|
10
|
-
def add_raw_output(raw_output)
|
11
|
-
missing_args = %w[output_type output timestamp] - raw_output.keys
|
12
|
-
unless missing_args.empty?
|
13
|
-
raise ArgumentError, "Missing args for raw output: #{missing_args.inspect}"
|
14
|
-
end
|
15
|
-
@raw_outputs << raw_output
|
16
|
-
end
|
17
|
-
|
18
|
-
def empty?
|
19
|
-
@raw_outputs.empty?
|
20
|
-
end
|
21
|
-
|
22
|
-
def last_timestamp
|
23
|
-
return if @raw_outputs.empty?
|
24
|
-
@raw_outputs.last.fetch('timestamp')
|
25
|
-
end
|
26
|
-
|
27
|
-
def sort!
|
28
|
-
@raw_outputs.sort_by! { |record| record['timestamp'].to_f }
|
29
|
-
end
|
30
|
-
|
31
|
-
def humanize
|
32
|
-
sort!
|
33
|
-
raw_outputs.map { |output| output['output'] }.join("\n")
|
34
|
-
end
|
35
|
-
|
36
|
-
def add_exception(context, exception, timestamp = Time.now.getlocal)
|
37
|
-
add_output(context + ": #{exception.class} - #{exception.message}", 'debug', timestamp)
|
38
|
-
end
|
39
|
-
|
40
|
-
def add_output(*args)
|
41
|
-
add_raw_output(self.class.format_output(*args))
|
42
|
-
end
|
43
|
-
|
44
|
-
def self.format_output(message, type = 'debug', timestamp = Time.now.getlocal)
|
45
|
-
{ 'output_type' => type,
|
46
|
-
'output' => message,
|
47
|
-
'timestamp' => timestamp.to_f }
|
48
|
-
end
|
49
|
-
end
|
50
|
-
end
|
@@ -1,31 +0,0 @@
|
|
1
|
-
require 'base64'
|
2
|
-
require 'securerandom'
|
3
|
-
|
4
|
-
module ForemanTasksCore
|
5
|
-
class OtpManager
|
6
|
-
class << self
|
7
|
-
def generate_otp(username)
|
8
|
-
otp = SecureRandom.hex
|
9
|
-
passwords[username] = otp.to_s
|
10
|
-
end
|
11
|
-
|
12
|
-
def drop_otp(username, password)
|
13
|
-
passwords.delete(username) if passwords[username] == password
|
14
|
-
end
|
15
|
-
|
16
|
-
def passwords
|
17
|
-
@password ||= {}
|
18
|
-
end
|
19
|
-
|
20
|
-
def authenticate(hash)
|
21
|
-
plain = Base64.decode64(hash)
|
22
|
-
username, otp = plain.split(':', 2)
|
23
|
-
drop_otp(username, otp)
|
24
|
-
end
|
25
|
-
|
26
|
-
def tokenize(username, password)
|
27
|
-
Base64.strict_encode64("#{username}:#{password}")
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
31
|
-
end
|
@@ -1,76 +0,0 @@
|
|
1
|
-
require 'foreman_tasks_core/shareable_action'
|
2
|
-
module ForemanTasksCore
|
3
|
-
module Runner
|
4
|
-
class Action < ::ForemanTasksCore::ShareableAction
|
5
|
-
include ::Dynflow::Action::Cancellable
|
6
|
-
|
7
|
-
def run(event = nil)
|
8
|
-
case event
|
9
|
-
when nil
|
10
|
-
init_run
|
11
|
-
when Runner::Update
|
12
|
-
process_update(event)
|
13
|
-
when Runner::ExternalEvent
|
14
|
-
process_external_event(event)
|
15
|
-
when ::Dynflow::Action::Cancellable::Cancel
|
16
|
-
kill_run
|
17
|
-
else
|
18
|
-
raise "Unexpected event #{event.inspect}"
|
19
|
-
end
|
20
|
-
rescue => e
|
21
|
-
action_logger.error(e)
|
22
|
-
process_update(Runner::Update.encode_exception('Proxy error', e))
|
23
|
-
end
|
24
|
-
|
25
|
-
def finalize
|
26
|
-
# To mark the task as a whole as failed
|
27
|
-
error! 'Script execution failed' if on_proxy? && failed_run?
|
28
|
-
end
|
29
|
-
|
30
|
-
def rescue_strategy_for_self
|
31
|
-
::Dynflow::Action::Rescue::Fail
|
32
|
-
end
|
33
|
-
|
34
|
-
def initiate_runner
|
35
|
-
raise NotImplementedError
|
36
|
-
end
|
37
|
-
|
38
|
-
def init_run
|
39
|
-
output[:result] = []
|
40
|
-
output[:runner_id] = runner_dispatcher.start(suspended_action, initiate_runner)
|
41
|
-
suspend
|
42
|
-
end
|
43
|
-
|
44
|
-
def runner_dispatcher
|
45
|
-
Runner::Dispatcher.instance
|
46
|
-
end
|
47
|
-
|
48
|
-
def kill_run
|
49
|
-
runner_dispatcher.kill(output[:runner_id])
|
50
|
-
suspend
|
51
|
-
end
|
52
|
-
|
53
|
-
def finish_run(update)
|
54
|
-
output[:exit_status] = update.exit_status
|
55
|
-
end
|
56
|
-
|
57
|
-
def process_external_event(event)
|
58
|
-
runner_dispatcher.external_event(output[:runner_id], event)
|
59
|
-
suspend
|
60
|
-
end
|
61
|
-
|
62
|
-
def process_update(update)
|
63
|
-
output[:result].concat(update.continuous_output.raw_outputs)
|
64
|
-
if update.exit_status
|
65
|
-
finish_run(update)
|
66
|
-
else
|
67
|
-
suspend
|
68
|
-
end
|
69
|
-
end
|
70
|
-
|
71
|
-
def failed_run?
|
72
|
-
output[:exit_status] != 0
|
73
|
-
end
|
74
|
-
end
|
75
|
-
end
|
76
|
-
end
|
@@ -1,83 +0,0 @@
|
|
1
|
-
module ForemanTasksCore
|
2
|
-
module Runner
|
3
|
-
# Runner is an object that is able to initiate some action and
|
4
|
-
# provide update data on refresh call.
|
5
|
-
class Base
|
6
|
-
attr_reader :id
|
7
|
-
attr_writer :logger
|
8
|
-
|
9
|
-
def initialize(*_args, suspended_action: nil)
|
10
|
-
@suspended_action = suspended_action
|
11
|
-
@id = SecureRandom.uuid
|
12
|
-
initialize_continuous_outputs
|
13
|
-
end
|
14
|
-
|
15
|
-
def logger
|
16
|
-
@logger ||= Logger.new(STDERR)
|
17
|
-
end
|
18
|
-
|
19
|
-
def run_refresh
|
20
|
-
logger.debug('refreshing runner')
|
21
|
-
refresh
|
22
|
-
generate_updates
|
23
|
-
end
|
24
|
-
|
25
|
-
def start
|
26
|
-
raise NotImplementedError
|
27
|
-
end
|
28
|
-
|
29
|
-
def refresh
|
30
|
-
raise NotImplementedError
|
31
|
-
end
|
32
|
-
|
33
|
-
def kill
|
34
|
-
# Override when you can kill the runner in the middle
|
35
|
-
end
|
36
|
-
|
37
|
-
def close
|
38
|
-
# if cleanup is needed
|
39
|
-
end
|
40
|
-
|
41
|
-
def timeout
|
42
|
-
# Override when timeouts and regular kills should be handled differently
|
43
|
-
publish_data('Timeout for execution passed, trying to stop the job', 'debug')
|
44
|
-
kill
|
45
|
-
end
|
46
|
-
|
47
|
-
def timeout_interval
|
48
|
-
# A number of seconds after which the runner should receive a #timeout
|
49
|
-
# or nil for no timeout
|
50
|
-
end
|
51
|
-
|
52
|
-
def publish_data(data, type)
|
53
|
-
@continuous_output.add_output(data, type)
|
54
|
-
end
|
55
|
-
|
56
|
-
def publish_exception(context, exception, fatal = true)
|
57
|
-
logger.error("#{context} - #{exception.class} #{exception.message}:\n" + \
|
58
|
-
exception.backtrace.join("\n"))
|
59
|
-
dispatch_exception context, exception
|
60
|
-
publish_exit_status('EXCEPTION') if fatal
|
61
|
-
end
|
62
|
-
|
63
|
-
def publish_exit_status(status)
|
64
|
-
@exit_status = status
|
65
|
-
end
|
66
|
-
|
67
|
-
def dispatch_exception(context, exception)
|
68
|
-
@continuous_output.add_exception(context, exception)
|
69
|
-
end
|
70
|
-
|
71
|
-
def generate_updates
|
72
|
-
return {} if @continuous_output.empty? && @exit_status.nil?
|
73
|
-
new_data = @continuous_output
|
74
|
-
@continuous_output = ForemanTasksCore::ContinuousOutput.new
|
75
|
-
{ @suspended_action => Runner::Update.new(new_data, @exit_status) }
|
76
|
-
end
|
77
|
-
|
78
|
-
def initialize_continuous_outputs
|
79
|
-
@continuous_output = ::ForemanTasksCore::ContinuousOutput.new
|
80
|
-
end
|
81
|
-
end
|
82
|
-
end
|
83
|
-
end
|
@@ -1,48 +0,0 @@
|
|
1
|
-
module ForemanTasksCore
|
2
|
-
module Runner
|
3
|
-
class Parent < Base
|
4
|
-
# targets = { hostname => { :execution_plan_id => "...", :run_step_id => id,
|
5
|
-
# :input => { ... } }
|
6
|
-
def initialize(targets = {}, suspended_action: nil)
|
7
|
-
@targets = targets
|
8
|
-
super suspended_action: suspended_action
|
9
|
-
end
|
10
|
-
|
11
|
-
def generate_updates
|
12
|
-
@outputs.reduce({}) do |acc, (key, value)|
|
13
|
-
if value.empty? && @exit_status.nil?
|
14
|
-
acc
|
15
|
-
else
|
16
|
-
@outputs[key] = ForemanTasksCore::ContinuousOutput.new
|
17
|
-
key = host_action(key) unless key == @suspended_action
|
18
|
-
acc.merge(key => Runner::Update.new(value, @exit_status))
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
def initialize_continuous_outputs
|
24
|
-
@outputs = ([@suspended_action] + @targets.keys).reduce({}) do |acc, target|
|
25
|
-
acc.merge(target => ForemanTasksCore::ContinuousOutput.new)
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
def host_action(hostname)
|
30
|
-
options = @targets[hostname].slice('execution_plan_id', 'run_step_id')
|
31
|
-
.merge(:world => ForemanTasksCore.dynflow_world)
|
32
|
-
Dynflow::Action::Suspended.new OpenStruct.new(options)
|
33
|
-
end
|
34
|
-
|
35
|
-
def broadcast_data(data, type)
|
36
|
-
@outputs.each { |_k, output| output.add_output(data, type) }
|
37
|
-
end
|
38
|
-
|
39
|
-
def publish_data_for(hostname, data, type)
|
40
|
-
@outputs[hostname].add_output(data, type)
|
41
|
-
end
|
42
|
-
|
43
|
-
def dispatch_exception(context, exception)
|
44
|
-
@outputs.values.each { |output| output.add_exception(context, exception) }
|
45
|
-
end
|
46
|
-
end
|
47
|
-
end
|
48
|
-
end
|
@@ -1,28 +0,0 @@
|
|
1
|
-
require 'foreman_tasks_core/continuous_output'
|
2
|
-
|
3
|
-
module ForemanTasksCore
|
4
|
-
module Runner
|
5
|
-
# Runner::Update represents chunk of data produced by runner that
|
6
|
-
# can be consumed by other components, such as RunnerAction
|
7
|
-
class Update
|
8
|
-
attr_reader :continuous_output, :exit_status
|
9
|
-
def initialize(continuous_output, exit_status)
|
10
|
-
@continuous_output = continuous_output
|
11
|
-
@exit_status = exit_status
|
12
|
-
end
|
13
|
-
|
14
|
-
def self.encode_exception(context, exception, fatal = true)
|
15
|
-
continuous_output = ::ForemanTasksCore::ContinuousOutput.new
|
16
|
-
continuous_output.add_exception(context, exception)
|
17
|
-
new(continuous_output, fatal ? 'EXCEPTION' : nil)
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
class ExternalEvent
|
22
|
-
attr_reader :data
|
23
|
-
def initialize(data = {})
|
24
|
-
@data = data
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
@@ -1,53 +0,0 @@
|
|
1
|
-
module ForemanTasksCore
|
2
|
-
module SettingsLoader
|
3
|
-
def self.settings_registry
|
4
|
-
@settings_registry ||= {}
|
5
|
-
end
|
6
|
-
|
7
|
-
def self.name_to_settings
|
8
|
-
@name_to_settings ||= {}
|
9
|
-
end
|
10
|
-
|
11
|
-
def self.settings_keys
|
12
|
-
@settings_keys ||= []
|
13
|
-
end
|
14
|
-
|
15
|
-
def self.settings_registered?(name)
|
16
|
-
name_to_settings.key?(name)
|
17
|
-
end
|
18
|
-
|
19
|
-
def self.register_settings(names, object)
|
20
|
-
names = [names] unless names.is_a? Array
|
21
|
-
names.each do |name|
|
22
|
-
raise 'settings name has to be a symbol' unless name.is_a? Symbol
|
23
|
-
raise "settings #{name} already registered" if SettingsLoader.settings_registered?(name)
|
24
|
-
name_to_settings[name] = object
|
25
|
-
end
|
26
|
-
settings_registry[names] = object
|
27
|
-
end
|
28
|
-
|
29
|
-
def self.setup_settings(name, settings)
|
30
|
-
raise "Settings for #{name} were not registered" unless settings_registered?(name)
|
31
|
-
name_to_settings[name].initialize_settings(settings)
|
32
|
-
end
|
33
|
-
|
34
|
-
def register_settings(names, defaults = {})
|
35
|
-
SettingsLoader.register_settings(names, self)
|
36
|
-
@defaults = defaults
|
37
|
-
end
|
38
|
-
|
39
|
-
def initialize_settings(settings = {})
|
40
|
-
@settings = @defaults.merge(settings)
|
41
|
-
validate_settings!
|
42
|
-
end
|
43
|
-
|
44
|
-
def settings
|
45
|
-
raise "Settings for #{self} not initalized" unless @settings
|
46
|
-
@settings
|
47
|
-
end
|
48
|
-
|
49
|
-
def validate_settings!
|
50
|
-
raise 'Only symbols expected in keys' unless @settings.keys.all? { |key| key.is_a? Symbol }
|
51
|
-
end
|
52
|
-
end
|
53
|
-
end
|
@@ -1,42 +0,0 @@
|
|
1
|
-
module ForemanTasksCore
|
2
|
-
module TaskLauncher
|
3
|
-
class Abstract
|
4
|
-
attr_reader :callback, :options, :results, :world
|
5
|
-
def initialize(world, callback, options = {})
|
6
|
-
@world = world
|
7
|
-
@callback = callback
|
8
|
-
@options = options
|
9
|
-
@results = {}
|
10
|
-
end
|
11
|
-
|
12
|
-
def launch!(_input)
|
13
|
-
raise NotImplementedError
|
14
|
-
end
|
15
|
-
|
16
|
-
private
|
17
|
-
|
18
|
-
def format_result(result)
|
19
|
-
if result.triggered?
|
20
|
-
{ :result => 'success', :task_id => result.execution_plan_id }
|
21
|
-
else
|
22
|
-
plan = world.persistence.load_execution_plan(result.id)
|
23
|
-
{ :result => 'error', :errors => plan.errors }
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
def action_class(input)
|
28
|
-
::Dynflow::Utils.constantize(input['action_class'])
|
29
|
-
end
|
30
|
-
|
31
|
-
def with_callback(input)
|
32
|
-
input.merge(:callback_host => callback)
|
33
|
-
end
|
34
|
-
|
35
|
-
def trigger(parent, klass, *input)
|
36
|
-
world.trigger do
|
37
|
-
world.plan_with_options(caller_action: parent, action_class: klass, args: input)
|
38
|
-
end
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|
42
|
-
end
|
@@ -1,37 +0,0 @@
|
|
1
|
-
module ForemanTasksCore
|
2
|
-
module TaskLauncher
|
3
|
-
class ParentAction < ::Dynflow::Action
|
4
|
-
include Dynflow::Action::WithSubPlans
|
5
|
-
include Dynflow::Action::WithPollingSubPlans
|
6
|
-
|
7
|
-
# { task_id => { :action_class => Klass, :input => input } }
|
8
|
-
def plan(launcher, input_hash)
|
9
|
-
launcher.launch_children(self, input_hash)
|
10
|
-
plan_self
|
11
|
-
end
|
12
|
-
|
13
|
-
def initiate
|
14
|
-
ping suspended_action
|
15
|
-
wait_for_sub_plans sub_plans
|
16
|
-
end
|
17
|
-
|
18
|
-
def rescue_strategy
|
19
|
-
Dynflow::Action::Rescue::Fail
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
class Batch < Abstract
|
24
|
-
def launch!(input)
|
25
|
-
trigger(nil, ParentAction, self, input)
|
26
|
-
end
|
27
|
-
|
28
|
-
def launch_children(parent, input_hash)
|
29
|
-
input_hash.each do |task_id, input|
|
30
|
-
launcher = Single.new(world, callback, :parent => parent)
|
31
|
-
launcher.launch!(input)
|
32
|
-
results[task_id] = launcher.results
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|
@@ -1,14 +0,0 @@
|
|
1
|
-
module ForemanTasksCore
|
2
|
-
module TaskLauncher
|
3
|
-
class Single < Abstract
|
4
|
-
# { :action_class => "MyActionClass", :action_input => {} }
|
5
|
-
def launch!(input)
|
6
|
-
triggered = trigger(options[:parent],
|
7
|
-
action_class(input),
|
8
|
-
with_callback(input.fetch('action_input', {})))
|
9
|
-
@results = format_result(triggered)
|
10
|
-
triggered
|
11
|
-
end
|
12
|
-
end
|
13
|
-
end
|
14
|
-
end
|
@@ -1,45 +0,0 @@
|
|
1
|
-
module ForemanTasksCore
|
2
|
-
class Ticker < ::Dynflow::Actor
|
3
|
-
attr_reader :clock
|
4
|
-
|
5
|
-
def initialize(clock, logger, refresh_interval)
|
6
|
-
@clock = clock
|
7
|
-
@logger = logger
|
8
|
-
@events = []
|
9
|
-
@refresh_interval = refresh_interval
|
10
|
-
plan_next_tick
|
11
|
-
end
|
12
|
-
|
13
|
-
def tick
|
14
|
-
@logger.debug("Ticker ticking for #{@events.size} events")
|
15
|
-
@events.each do |(target, args)|
|
16
|
-
pass_event(target, args)
|
17
|
-
end
|
18
|
-
@events = []
|
19
|
-
ensure
|
20
|
-
@planned = false
|
21
|
-
plan_next_tick
|
22
|
-
end
|
23
|
-
|
24
|
-
def add_event(target, args)
|
25
|
-
@events << [target, args]
|
26
|
-
plan_next_tick
|
27
|
-
end
|
28
|
-
|
29
|
-
private
|
30
|
-
|
31
|
-
def pass_event(target, args)
|
32
|
-
target.tell(args)
|
33
|
-
rescue => e
|
34
|
-
@logger.error("Failed passing event to #{target} with #{args}")
|
35
|
-
@logger.error(e)
|
36
|
-
end
|
37
|
-
|
38
|
-
def plan_next_tick
|
39
|
-
if !@planned && !@events.empty?
|
40
|
-
@clock.ping(reference, Time.now.getlocal + @refresh_interval, :tick)
|
41
|
-
@planned = true
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|