smart_proxy_dynflow 0.6.3 → 0.8.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6673ce798c24b310ce2b132ac1c07cddbdfbd49ac9b0dd8e0e8abbc0312b2a0c
4
- data.tar.gz: b966221317f31e9e32ba85a77d8f26c64ae238d35d99548da8e9647da5b092a5
3
+ metadata.gz: 0c9cd93e1cf950c5f7977d700a2d8b307faa98182af93063c55ced580617d054
4
+ data.tar.gz: 2de7767e855c5914d6db7678ef2c2302c414a20b04cb6dd4df7294263ee00f95
5
5
  SHA512:
6
- metadata.gz: e05968e12c48d7b997d60231cc541d4bb0ae13a3f2ba8a1b586af0a0a1232086cd09cceb5b49e10deb68bd8dfe65817a925b09a44f022126a7695316532bf89d
7
- data.tar.gz: ea263ed0aa2e2ca216c85385c7ec00e66cca6df6ca569cc6dda67f98fe1c5ef0d30eea3510b6227fdc69f61a9619b949265bd2f286a036a8d3f09520ed004326
6
+ metadata.gz: b03995ac1c1bf44c900926371d3b42c56f05dc8b994fb07f3c87613e788a990e9ec597d67bb24a97279ae4bc0557fca01dabfbaace26299553467f8173ff8f73
7
+ data.tar.gz: 0d4737d0298d940988c51ba62e04c45740530660ae76c492e706459965446e984f2ddeb319651a416864636dca407d4f281029bb75da9ad747da7f6b6d71ee42
@@ -3,15 +3,16 @@ module Proxy::Dynflow::Action
3
3
  include Dynflow::Action::WithSubPlans
4
4
  include Dynflow::Action::WithPollingSubPlans
5
5
 
6
- # { task_id => { :action_class => Klass, :input => input } }
6
+ # { execution_plan_uuid => { :action_class => Klass, :input => input } }
7
7
  def plan(launcher, input_hash)
8
- launcher.launch_children(self, input_hash)
9
- plan_self
8
+ plan_self :input_hash => input_hash,
9
+ :launcher => launcher.to_hash
10
10
  end
11
11
 
12
- def initiate
13
- ping suspended_action
14
- wait_for_sub_plans sub_plans
12
+ def create_sub_plans
13
+ Proxy::Dynflow::TaskLauncher::Abstract
14
+ .new_from_hash(world, input[:launcher])
15
+ .launch_children(self, input[:input_hash])
15
16
  end
16
17
 
17
18
  def rescue_strategy
@@ -1,7 +1,12 @@
1
1
  module Proxy::Dynflow::Action
2
2
  class BatchCallback < ::Dynflow::Action
3
3
  def plan(input_hash, results)
4
- plan_self :targets => input_hash, :results => results
4
+ # In input_hash there are complete inputs for all the actions for which this is reporting
5
+ # Trim it down to only the bare minimum we actually need
6
+ callbacks = input_hash.reduce({}) do |acc, (key, value)|
7
+ acc.merge(key => value['action_input']['callback'])
8
+ end
9
+ plan_self :targets => callbacks, :results => results
5
10
  end
6
11
 
7
12
  def run
@@ -2,13 +2,13 @@ require 'smart_proxy_dynflow/action/runner'
2
2
 
3
3
  module Proxy::Dynflow::Action
4
4
  class BatchRunner < ::Proxy::Dynflow::Action::Runner
5
- def plan(launcher, input)
6
- plan_self :targets => launcher.runner_input(input), :operation => launcher.operation
5
+ def plan(launcher, input, runner_id)
6
+ plan_self :targets => launcher.runner_input(input), :operation => launcher.operation, :runner_id => runner_id
7
7
  end
8
8
 
9
9
  def initiate_runner
10
10
  launcher = Proxy::Dynflow::TaskLauncherRegistry.fetch(input[:operation])
11
- launcher.runner_class.new(input[:targets], suspended_action: suspended_action)
11
+ launcher.runner_class.new(input[:targets], suspended_action: suspended_action, id: input[:runner_id])
12
12
  end
13
13
  end
14
14
  end
@@ -0,0 +1,16 @@
1
+ module Proxy::Dynflow::Action
2
+ module WithExternalPolling
3
+ Poll = Algebrick.atom
4
+
5
+ def run(event = nil)
6
+ if event.is_a?(Poll)
7
+ poll
8
+ suspend
9
+ else
10
+ super
11
+ end
12
+ end
13
+
14
+ def poll; end
15
+ end
16
+ end
@@ -2,6 +2,7 @@ module Proxy::Dynflow::Action
2
2
  class OutputCollector < ::Proxy::Dynflow::Action::Runner
3
3
  def init_run
4
4
  output[:result] = []
5
+ output[:runner_id] = input[:runner_id]
5
6
  suspend
6
7
  end
7
8
  end
@@ -1,8 +1,10 @@
1
1
  require 'smart_proxy_dynflow/action/shareable'
2
+ require 'smart_proxy_dynflow/action/external_polling'
2
3
  module Proxy::Dynflow
3
4
  module Action
4
5
  class Runner < Shareable
5
6
  include ::Dynflow::Action::Cancellable
7
+ include ::Proxy::Dynflow::Action::WithExternalPolling
6
8
 
7
9
  def run(event = nil)
8
10
  case event
@@ -14,6 +16,9 @@ module Proxy::Dynflow
14
16
  process_external_event(event)
15
17
  when ::Dynflow::Action::Cancellable::Cancel
16
18
  kill_run
19
+ when ::Proxy::Dynflow::Action::WithExternalPolling::Poll
20
+ poll
21
+ suspend
17
22
  else
18
23
  raise "Unexpected event #{event.inspect}"
19
24
  end
@@ -69,6 +74,10 @@ module Proxy::Dynflow
69
74
  end
70
75
  end
71
76
 
77
+ def poll
78
+ runner_dispatcher.refresh_output(output[:runner_id])
79
+ end
80
+
72
81
  def failed_run?
73
82
  output[:exit_status] != 0
74
83
  end
@@ -1,20 +1,8 @@
1
1
  module Proxy::Dynflow::Action
2
2
  class SingleRunnerBatch < Batch
3
3
  def plan(launcher, input_hash)
4
- launcher.launch_children(self, input_hash)
5
- sequence do
6
- results = plan_self
7
- plan_action BatchCallback, launcher.prepare_batch(input_hash), results.output[:results]
8
- end
9
- end
10
-
11
- def run(event = nil)
12
- super unless event == Dynflow::Action::Skip
13
- end
14
-
15
- def initiate
16
- ping suspended_action
17
- wait_for_sub_plans sub_plans
4
+ results = super
5
+ plan_action BatchCallback, input_hash, results.output[:results]
18
6
  end
19
7
 
20
8
  def check_for_errors!(optional = true)
@@ -1,3 +1,5 @@
1
+ require 'fileutils'
2
+
1
3
  module Proxy::Dynflow
2
4
  class Core
3
5
  attr_accessor :world, :accepted_cert_serial
@@ -29,6 +31,7 @@ module Proxy::Dynflow
29
31
  Log.instance.warn "Could not open DB for dynflow at '#{db_file}', " \
30
32
  "will keep data in memory. Restart will drop all dynflow data."
31
33
  else
34
+ FileUtils.mkdir_p(File.dirname(db_file))
32
35
  db_conn_string += "/#{db_file}"
33
36
  end
34
37
 
@@ -36,9 +36,8 @@ module Proxy
36
36
  def task_status(task_id)
37
37
  ep = world.persistence.load_execution_plan(task_id)
38
38
  actions = ep.actions.map do |action|
39
- hash = action.to_hash
40
- hash[:output][:result] = action.output_result if action.is_a?(Proxy::Dynflow::Action::Runner)
41
- hash
39
+ refresh_output(ep, action)
40
+ expand_output(action)
42
41
  end
43
42
  ep.to_hash.merge(:actions => actions)
44
43
  rescue KeyError => _e
@@ -58,6 +57,18 @@ module Proxy
58
57
  params['step_id'].to_i,
59
58
  ::Proxy::Dynflow::Runner::ExternalEvent.new(params))
60
59
  end
60
+
61
+ def refresh_output(execution_plan, action)
62
+ if action.is_a?(Proxy::Dynflow::Action::WithExternalPolling) && %i[running suspended].include?(action.run_step&.state)
63
+ world.event(execution_plan.id, action.run_step_id, Proxy::Dynflow::Action::WithExternalPolling::Poll)
64
+ end
65
+ end
66
+
67
+ def expand_output(action)
68
+ hash = action.to_hash
69
+ hash[:output][:result] = action.output_result if action.is_a?(Proxy::Dynflow::Action::Runner)
70
+ hash
71
+ end
61
72
  end
62
73
  end
63
74
  end
@@ -0,0 +1,115 @@
1
+ module Proxy
2
+ module Dynflow
3
+ # A buffer around an IO object providing buffering and convenience methods
4
+ # for non-blocking reads and writes.
5
+ #
6
+ # @note Using a single IOBuffer with a single IO for both reads and writes might not be a good idea. If you need to use a single IO for both reads and writes, wrap it in two separate IOBuffers.
7
+ #
8
+ # @attr_accessor [IO] io The IO which the buffer wraps
9
+ # @attr_reader [String] buffer The buffer where the data read from the underlying IO is buffered
10
+ class IOBuffer
11
+ attr_accessor :io
12
+ attr_reader :buffer
13
+
14
+ # @param [IO] io The IO object to be buffered
15
+ def initialize(io)
16
+ @buffer = ''
17
+ @io = io
18
+ end
19
+
20
+ # Sets a callback to be executed each time data is read from the
21
+ # underlying IO.
22
+ #
23
+ # @note Note that if the callback is provided, the buffer will store the return value of the callback instead of the raw data.
24
+ #
25
+ # @yieldparam [String] data read from the underlying IO
26
+ # @yieldreturn [String] data to be buffered
27
+ # @return [void]
28
+ def on_data(&block)
29
+ @callback = block
30
+ end
31
+
32
+ # Exposes the underlying IO so that the buffer itself can be used in IO.select calls.
33
+ #
34
+ # @return [IO] the underlying IO
35
+ def to_io
36
+ @io
37
+ end
38
+
39
+ # Exposes the contents of the buffer as a String
40
+ #
41
+ # @return [String] the buffered data
42
+ def to_s
43
+ @buffer
44
+ end
45
+
46
+ # Checks whether the buffer is empty
47
+ #
48
+ # @return [true, false] whether the buffer is empty
49
+ def empty?
50
+ @buffer.empty?
51
+ end
52
+
53
+ # Checks whether the underlying IO is empty
54
+ #
55
+ # @return [true, false] whether the underlying IO is empty
56
+ def closed?
57
+ @io.closed?
58
+ end
59
+
60
+ # Closes the underlying IO. Does nothing if the IO is already closed.
61
+ #
62
+ # @return [void]
63
+ def close
64
+ @io.close unless @io.closed?
65
+ end
66
+
67
+ # Reads all the data that is currently waiting in the IO and stores it. If
68
+ # EOFError is encountered during the read, the underlying IO is closed.
69
+ #
70
+ # @return [void]
71
+ def read_available!
72
+ data = ''
73
+ loop { data += @io.read_nonblock(4096) }
74
+ rescue IO::WaitReadable # rubocop:disable Lint/HandleExceptions
75
+ rescue EOFError
76
+ close
77
+ ensure
78
+ @buffer += with_callback(data) unless data.empty?
79
+ end
80
+
81
+ # Writes all the data into the IO that can be written without blocking. It
82
+ # is a no-op if there are no data to be written. If an EOFError is
83
+ # encountered during the write, the underlying IO is closed.
84
+ #
85
+ # @return [void]
86
+ def write_available!
87
+ until @buffer.empty?
88
+ n = @io.write_nonblock(@buffer)
89
+ @buffer = @buffer[n..-1]
90
+ end
91
+ rescue IO::WaitWritable # rubocop:disable Lint/HandleExceptions
92
+ rescue EOFError
93
+ close
94
+ end
95
+
96
+ # Adds data to the buffer. If the buffer is used for writing, then this
97
+ # should be the preferred method of queueing the data to be written.
98
+ #
99
+ # @return [void]
100
+ def add_data(data)
101
+ @buffer += data
102
+ end
103
+
104
+ private
105
+
106
+ def with_callback(data)
107
+ if @callback
108
+ @callback.call(data)
109
+ else
110
+ data
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
@@ -14,6 +14,8 @@ module Proxy::Dynflow
14
14
  :execution_plan_cleaner_age => 60 * 30
15
15
  plugin :dynflow, Proxy::Dynflow::VERSION
16
16
 
17
+ capability(proc { self.available_operations })
18
+
17
19
  after_activation do
18
20
  require 'smart_proxy_dynflow/settings_loader'
19
21
  require 'smart_proxy_dynflow/otp_manager'
@@ -25,5 +27,9 @@ module Proxy::Dynflow
25
27
 
26
28
  Proxy::Dynflow::Core.ensure_initialized
27
29
  end
30
+
31
+ def self.available_operations
32
+ TaskLauncherRegistry.operations
33
+ end
28
34
  end
29
35
  end
@@ -0,0 +1,168 @@
1
+ require 'smart_proxy_dynflow/io_buffer'
2
+
3
+ module Proxy
4
+ module Dynflow
5
+ # An abstraction for managing local processes.
6
+ #
7
+ # It can be used to:
8
+ # - spawn a local process
9
+ # - track its lifecycle
10
+ # - communicate with it through its standard input, output and error
11
+ # - step through the execution one event at a time or start the child process and wait until it finishes
12
+ #
13
+ # @example Run date command and collect its output
14
+ # pm = ProcessManager.new('date')
15
+ # pm.run!
16
+ # pm.status #=> 0
17
+ # pm.stdout.to_s.chomp #=> "Thu Feb 3 04:27:42 PM CET 2022"
18
+ #
19
+ # @example Run a shell loop, outputting all the lines it generates
20
+ # pm = ProcessManager.new(['/bin/sh', '-c', 'for i in 1 2 3; do echo $i; sleep 1; done'])
21
+ # pm.on_stdout { |data| puts data; '' }
22
+ # pm.run!
23
+ # #=> 1
24
+ # #=> 2
25
+ # #=> 3
26
+ #
27
+ # @example Run bc (calculator) interactively and count down from 10 to 0
28
+ # pm = ProcessManager.new('bc')
29
+ # pm.on_stdout do |data|
30
+ # if data.match?(/^\d+/)
31
+ # n = data.to_i
32
+ # if n.zero?
33
+ # pm.stdin.to_io.close
34
+ # else
35
+ # pm.stdin.add_data("#{n} - 1\n")
36
+ # end
37
+ # end
38
+ # data
39
+ # end
40
+ # pm.stdin.add_data("10\n")
41
+ # pm.run!
42
+ # pm.stdout.to_s.lines #=. ["10\n", "9\n", "8\n", "7\n", "6\n", "5\n", "4\n", "3\n", "2\n", "1\n", "0\n"]
43
+ #
44
+ # @attr_reader [Proxy::Dynflow::IOBuffer] stdin IOBuffer buffering writes to child process' standard input
45
+ # @attr_reader [Proxy::Dynflow::IOBuffer] stdout IOBuffer buffering reads from child process' standard output
46
+ # @attr_reader [Proxy::Dynflow::IOBuffer] stderr IOBuffer buffering reads from child process' standard error
47
+ # @attr_reader [nil, Integer] pid Process id of the child process, nil if the process was not started yet, -1 if the process could not be started
48
+ # @attr_reader [nil, Integer] status Exit status of the child process. nil if the child process has not finished yet, 255 if the process could not be started
49
+ class ProcessManager
50
+ attr_reader :stdin, :stdout, :stderr, :pid, :status
51
+
52
+ # @param [String, [String], [Hash, String]] command A command to run in one of the forms accepted by Kernel.spawn
53
+ def initialize(command)
54
+ @command = command
55
+ @stdin = IOBuffer.new(nil)
56
+ @stdout = IOBuffer.new(nil)
57
+ @stderr = IOBuffer.new(nil)
58
+ end
59
+
60
+ # Starts the process manager and runs it until it finishes
61
+ #
62
+ # @return [ProcessManager] the process manager itself to allow method chaining
63
+ def run!
64
+ start! unless started?
65
+ process until done?
66
+ self
67
+ end
68
+
69
+ # Starts the child process. It creates 3 pipes for communicating with the
70
+ # child process and the forks it. The process manager is considered done
71
+ # if the child process cannot be started.
72
+ #
73
+ # @return [void]
74
+ def start!
75
+ in_read, in_write = IO.pipe
76
+ out_read, out_write = IO.pipe
77
+ err_read, err_write = IO.pipe
78
+
79
+ @stdin.io = in_write
80
+ @stdout.io = out_read
81
+ @stderr.io = err_read
82
+
83
+ @pid = spawn(*@command, :in => in_read, :out => out_write, :err => err_write)
84
+ [in_read, out_write, err_write].each(&:close)
85
+ rescue Errno::ENOENT => e
86
+ [in_read, in_write, out_read, out_write, err_read, err_write].each(&:close)
87
+ @pid = -1
88
+ @status = 255
89
+ @stderr.add_data(e.message)
90
+ end
91
+
92
+ # Determines whether the process manager already forked off its child process
93
+ #
94
+ # @return [true, false] whether the process manager already forked off its child process
95
+ def started?
96
+ !pid.nil?
97
+ end
98
+
99
+ # Determines whether the child process of the process manager already finished
100
+ #
101
+ # @return [true, false] whether the child process of the process manager already finished
102
+ def done?
103
+ started? && !status.nil?
104
+ end
105
+
106
+ # Runs a single iteration of the manager's processing loop. It waits until either:
107
+ # - data is available in pipes connected to the child process' standard output or error
108
+ # - there is pending data to be written and the pipe connected to the child process' standard input is writable
109
+ # - a timeout is reached
110
+ #
111
+ # After the wait, all pending data is read and written.
112
+ #
113
+ # If all the pipes connected to the child process are closed, it marks the
114
+ # execution as complete and performs cleanup.
115
+ #
116
+ # @param timeout [nil, Numeric] controls how long this call should wait for data to become available. Waits indefinitely if nil.
117
+ # @return [void]
118
+ def process(timeout: nil)
119
+ raise 'Cannot process until the manager is started' unless started?
120
+ writers = [@stdin].reject { |buf| buf.empty? || buf.closed? }
121
+ readers = [@stdout, @stderr].reject(&:closed?)
122
+
123
+ if readers.empty? && writers.empty?
124
+ finish
125
+ return
126
+ end
127
+
128
+ ready_readers, ready_writers = IO.select(readers, writers, nil, timeout)
129
+ (ready_readers || []).each(&:read_available!)
130
+ (ready_writers || []).each(&:write_available!)
131
+ end
132
+
133
+ # Sets block to be executed each time data is read from child process' standard output
134
+ #
135
+ # @return [void]
136
+ def on_stdout(&block)
137
+ @stdout.on_data(&block)
138
+ end
139
+
140
+ # Sets block to be executed each time data is read from child process' standard error
141
+ #
142
+ # @return [void]
143
+ def on_stderr(&block)
144
+ @stderr.on_data(&block)
145
+ end
146
+
147
+ # Makes the process manager close all the pipes it may have opened to communicate with the child process
148
+ #
149
+ # @return [void]
150
+ def close
151
+ [@stdin, @stdout, @stderr].each(&:close)
152
+ end
153
+
154
+ private
155
+
156
+ # Makes the process manager finish its run, closing opened FDs and reaping the child process
157
+ #
158
+ # @return [void]
159
+ def finish
160
+ close
161
+ unless @pid == -1
162
+ _pid, status = Process.wait2(@pid)
163
+ @status = status.exitstatus
164
+ end
165
+ end
166
+ end
167
+ end
168
+ end
@@ -6,9 +6,9 @@ module Proxy::Dynflow
6
6
  attr_reader :id
7
7
  attr_writer :logger
8
8
 
9
- def initialize(*_args, suspended_action: nil)
9
+ def initialize(*_args, suspended_action: nil, id: nil)
10
10
  @suspended_action = suspended_action
11
- @id = SecureRandom.uuid
11
+ @id = id || SecureRandom.uuid
12
12
  initialize_continuous_outputs
13
13
  end
14
14
 
@@ -93,6 +93,12 @@ module Proxy::Dynflow
93
93
  def initialize_continuous_outputs
94
94
  @continuous_output = ::Proxy::Dynflow::ContinuousOutput.new
95
95
  end
96
+
97
+ def run_refresh_output
98
+ logger.debug('refreshing runner on demand')
99
+ refresh
100
+ generate_updates
101
+ end
96
102
  end
97
103
  end
98
104
  end
@@ -1,5 +1,18 @@
1
1
  module Proxy::Dynflow
2
2
  module Runner
3
+ # This module expects to be included into a Runner action, where it can be
4
+ # used to simplify handling of long-running processes. However it tracks the
5
+ # running process as a group of instance variables, which has served us
6
+ # reasonably well in the past, but can be rather error prone.
7
+ #
8
+ # A better alternative to this is
9
+ # {::Proxy::Dynflow::Runner::ProcessManagerCommand}. It tracks the whole
10
+ # execution of a process under a single instance variable and uses a more
11
+ # robust {::Proxy::Dynflow::ProcessManager} under the hood. It also
12
+ # maintains the same interface and can be used as a drop-in replacement.
13
+ #
14
+ # This module is now soft-deprecated and
15
+ # {::Proxy::Dynflow::Runner::ProcessManagerCommand} should be used instead.
3
16
  module Command
4
17
  def initialize_command(*command)
5
18
  @command_out, @command_in, @command_pid = PTY.spawn(*command)
@@ -4,6 +4,7 @@ require 'smart_proxy_dynflow/runner/command'
4
4
 
5
5
  module Proxy::Dynflow
6
6
  module Runner
7
+ # This class is now soft-deprecated, see {::Proxy::Dynflow::Runner::Command}
7
8
  class CommandRunner < Base
8
9
  include Command
9
10
  end
@@ -43,6 +43,11 @@ module Proxy::Dynflow
43
43
  plan_next_refresh
44
44
  end
45
45
 
46
+ def refresh_output
47
+ @logger.debug("refresh output #{@runner.id}")
48
+ dispatch_updates(@runner.run_refresh_output)
49
+ end
50
+
46
51
  def dispatch_updates(updates)
47
52
  updates.each { |receiver, update| (receiver || @suspended_action) << update }
48
53
 
@@ -157,6 +162,12 @@ module Proxy::Dynflow
157
162
  end
158
163
  end
159
164
 
165
+ def refresh_output(runner_id)
166
+ synchronize do
167
+ @runner_actors[runner_id]&.tell([:refresh_output])
168
+ end
169
+ end
170
+
160
171
  def handle_command_exception(*args)
161
172
  synchronize { _handle_command_exception(*args) }
162
173
  end
@@ -3,10 +3,10 @@ module Proxy::Dynflow
3
3
  class Parent < Base
4
4
  # targets = { identifier => { :execution_plan_id => "...", :run_step_id => id,
5
5
  # :input => { ... } }
6
- def initialize(targets = {}, suspended_action: nil)
6
+ def initialize(targets = {}, suspended_action: nil, id: nil)
7
7
  @targets = targets
8
8
  @exit_statuses = {}
9
- super suspended_action: suspended_action
9
+ super suspended_action: suspended_action, id: id
10
10
  end
11
11
 
12
12
  def generate_updates
@@ -0,0 +1,44 @@
1
+ require 'smart_proxy_dynflow/process_manager'
2
+
3
+ module Proxy::Dynflow
4
+ module Runner
5
+ # A convenience module which should be included into a Runner action. It
6
+ # leverages {::Proxy::Dynflow::ProcessManager} to reliably keep track of
7
+ # an external process and collect its output.
8
+ #
9
+ # The only expectation from the Runner action is to call
10
+ # {#initialize_command} somewhere and pass the command to be run to it.
11
+ module ProcessManagerCommand
12
+ def initialize_command(*command)
13
+ @process_manager = ProcessManager.new(command)
14
+ set_process_manager_callbacks(@process_manager)
15
+ @process_manager.start!
16
+ if @process_manager.done? && @process_manager.status == 255
17
+ exception = RuntimeError.new(@process_manager.stderr.to_s)
18
+ exception.set_backtrace Thread.current.backtrace
19
+ publish_exception("Error running command '#{command.join(' ')}'", exception)
20
+ end
21
+ end
22
+
23
+ def set_process_manager_callbacks(pm)
24
+ pm.on_stdout do |data|
25
+ publish_data(data, 'stdout')
26
+ ''
27
+ end
28
+ pm.on_stderr do |data|
29
+ publish_data(data, 'stderr')
30
+ ''
31
+ end
32
+ end
33
+
34
+ def refresh
35
+ @process_manager.process(timeout: 0.1) unless @process_manager.done?
36
+ publish_exit_status(@process_manager.status) if @process_manager.done?
37
+ end
38
+
39
+ def close
40
+ @process_manager&.close
41
+ end
42
+ end
43
+ end
44
+ end
@@ -15,6 +15,14 @@ module Proxy::Dynflow
15
15
 
16
16
  def self.input_format; end
17
17
 
18
+ def to_hash
19
+ { :class => self.class.to_s, :callback => callback, :options => options }
20
+ end
21
+
22
+ def self.new_from_hash(world, hash)
23
+ ::Dynflow::Utils.constantize(hash[:class]).new(world, hash[:callback], hash[:options])
24
+ end
25
+
18
26
  private
19
27
 
20
28
  def format_result(result)
@@ -34,9 +42,9 @@ module Proxy::Dynflow
34
42
  input.merge(:callback_host => callback)
35
43
  end
36
44
 
37
- def trigger(parent, klass, *input)
45
+ def trigger(parent, klass, *input, id: nil)
38
46
  world.trigger do
39
- world.plan_with_options(caller_action: parent, action_class: klass, args: input)
47
+ world.plan_with_options(caller_action: parent, action_class: klass, args: input, id: id)
40
48
  end
41
49
  end
42
50
  end
@@ -2,36 +2,37 @@ module Proxy::Dynflow
2
2
  module TaskLauncher
3
3
  class Batch < Abstract
4
4
  def launch!(input)
5
- trigger(nil, Proxy::Dynflow::Action::Batch, self, input)
5
+ plan = trigger(nil, action_class, self, input)
6
+ results[:parent] = format_result(plan)
6
7
  end
7
8
 
8
9
  def launch_children(parent, input_hash)
9
- input_hash.each do |task_id, input|
10
+ input_hash.map do |task_id, input|
10
11
  launcher = child_launcher(parent)
11
- launcher.launch!(transform_input(input))
12
+ triggered = launcher.launch!(transform_input(input), id: task_id)
12
13
  results[task_id] = launcher.results
14
+ triggered
13
15
  end
14
16
  end
15
17
 
16
18
  def prepare_batch(input_hash)
17
- success_tasks = input_hash.select do |task_id, _input|
18
- results[task_id][:result] == 'success'
19
- end
20
- success_tasks.reduce({}) do |acc, (key, value)|
21
- acc.merge(results[key][:task_id] => value['action_input']['callback'])
22
- end
19
+ input_hash
23
20
  end
24
21
 
25
- private
26
-
27
22
  def child_launcher(parent)
28
23
  Single.new(world, callback, :parent => parent)
29
24
  end
30
25
 
26
+ private
27
+
31
28
  # Identity by default
32
29
  def transform_input(input)
33
30
  input
34
31
  end
32
+
33
+ def action_class
34
+ Proxy::Dynflow::Action::Batch
35
+ end
35
36
  end
36
37
  end
37
38
  end
@@ -3,17 +3,22 @@ require 'smart_proxy_dynflow/runner'
3
3
  module Proxy::Dynflow
4
4
  module TaskLauncher
5
5
  class AbstractGroup < Batch
6
+ def initialize(*args)
7
+ super
8
+ @runner_id = SecureRandom.uuid
9
+ end
10
+
6
11
  def self.runner_class
7
12
  raise NotImplementedError
8
13
  end
9
14
 
10
- def launch!(input)
11
- trigger(nil, Action::SingleRunnerBatch, self, input)
15
+ def action_class
16
+ Action::SingleRunnerBatch
12
17
  end
13
18
 
14
19
  def launch_children(parent, input_hash)
15
20
  super(parent, input_hash)
16
- trigger(parent, Action::BatchRunner, self, input_hash)
21
+ trigger(parent, Action::BatchRunner, self, input_hash, @runner_id)
17
22
  end
18
23
 
19
24
  def operation
@@ -36,7 +41,8 @@ module Proxy::Dynflow
36
41
  end
37
42
 
38
43
  def transform_input(input)
39
- wipe_callback(input)
44
+ tmp = wipe_callback(input)
45
+ input.merge('action_input' => tmp['action_input'].merge(:runner_id => @runner_id))
40
46
  end
41
47
 
42
48
  def wipe_callback(input)
@@ -5,10 +5,11 @@ module Proxy::Dynflow
5
5
  { :action_class => "MyActionClass", :action_input => {} }
6
6
  end
7
7
 
8
- def launch!(input)
8
+ def launch!(input, id: nil)
9
9
  triggered = trigger(options[:parent],
10
10
  action_class(input),
11
- with_callback(input.fetch('action_input', {})))
11
+ with_callback(input.fetch('action_input', {})),
12
+ id: id)
12
13
  @results = format_result(triggered)
13
14
  triggered
14
15
  end
@@ -1,5 +1,5 @@
1
1
  module Proxy
2
2
  module Dynflow
3
- VERSION = '0.6.3'.freeze
3
+ VERSION = '0.8.1'.freeze
4
4
  end
5
5
  end
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  :enabled: true
3
- :database: /var/lib/foreman-proxy/dynflow/dynflow.sqlite
3
+ :database:
4
4
 
5
5
  # Require a valid cert to access Dynflow console
6
6
  # :console_auth: true
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: smart_proxy_dynflow
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.3
4
+ version: 0.8.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ivan Nečas
@@ -151,6 +151,7 @@ files:
151
151
  - lib/smart_proxy_dynflow/action/batch.rb
152
152
  - lib/smart_proxy_dynflow/action/batch_callback.rb
153
153
  - lib/smart_proxy_dynflow/action/batch_runner.rb
154
+ - lib/smart_proxy_dynflow/action/external_polling.rb
154
155
  - lib/smart_proxy_dynflow/action/output_collector.rb
155
156
  - lib/smart_proxy_dynflow/action/runner.rb
156
157
  - lib/smart_proxy_dynflow/action/shareable.rb
@@ -161,10 +162,12 @@ files:
161
162
  - lib/smart_proxy_dynflow/core.rb
162
163
  - lib/smart_proxy_dynflow/helpers.rb
163
164
  - lib/smart_proxy_dynflow/http_config.ru
165
+ - lib/smart_proxy_dynflow/io_buffer.rb
164
166
  - lib/smart_proxy_dynflow/log.rb
165
167
  - lib/smart_proxy_dynflow/middleware/keep_current_request_id.rb
166
168
  - lib/smart_proxy_dynflow/otp_manager.rb
167
169
  - lib/smart_proxy_dynflow/plugin.rb
170
+ - lib/smart_proxy_dynflow/process_manager.rb
168
171
  - lib/smart_proxy_dynflow/proxy_adapter.rb
169
172
  - lib/smart_proxy_dynflow/runner.rb
170
173
  - lib/smart_proxy_dynflow/runner/base.rb
@@ -172,6 +175,7 @@ files:
172
175
  - lib/smart_proxy_dynflow/runner/command_runner.rb
173
176
  - lib/smart_proxy_dynflow/runner/dispatcher.rb
174
177
  - lib/smart_proxy_dynflow/runner/parent.rb
178
+ - lib/smart_proxy_dynflow/runner/process_manager_command.rb
175
179
  - lib/smart_proxy_dynflow/runner/update.rb
176
180
  - lib/smart_proxy_dynflow/settings.rb
177
181
  - lib/smart_proxy_dynflow/settings_loader.rb