foreman-tasks 0.13.0 → 0.13.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 0111beaab4e9971f1039c81e0ccf31ad5cbb79e4
4
- data.tar.gz: 0077b9efb0e93957f99a37fe4d08469b6f6b62d2
2
+ SHA256:
3
+ metadata.gz: 464f9df51dc38a0d63e3e573e462f9f57bc5d091f0bdc0090327229d6b8ecffb
4
+ data.tar.gz: 5d2f0b357ce12025975c5cd62d2d31637d5cbcff20349e3af89edb213e8c553a
5
5
  SHA512:
6
- metadata.gz: 0aff56c13e49c3cca9802a8ae63f0f4c024ba69a4b6bb07d9453e236de2571d284895906192c8b52cc2e8ed8300b083e3643d47bf8894fab8fbde55da5bc3364
7
- data.tar.gz: 55255ff6e02f84e834b2ea8927364985830f13a0b456c64af503af56b0a76f286e1a94e5ec2fef04b3ee4da48fe905836a242b9baae4efd90f54afe04ee03944
6
+ metadata.gz: df01ce6f12aec5edd07cfe1ba355d86ad130df011be72c559dc100f92e2dabcfae356aa636bc73454393acd52aa02d569fe3addefe6d4940aa2d9ec2289a0a6d
7
+ data.tar.gz: 2e6da699d36a27a4f5727475e998ee52e07861e0a2eecd8a879a9b17f6b27121a225edb34f66338939c11e9110bb3fd53f0edc090b113fedb983db5bdd8798f6
data/.rubocop_todo.yml CHANGED
@@ -1,74 +1,147 @@
1
1
  # This configuration was generated by
2
2
  # `rubocop --auto-gen-config`
3
- # on 2016-12-21 14:29:23 +0200 using RuboCop version 0.46.0.
3
+ # on 2018-04-02 18:15:37 +0200 using RuboCop version 0.54.0.
4
4
  # The point is for the user to remove these configuration records
5
5
  # one by one as the offenses are removed from the code base.
6
6
  # Note that changes in the inspected code, or installation of new
7
7
  # versions of RuboCop, may require this file to be generated again.
8
8
 
9
- Lint/EmptyWhen:
9
+ # Offense count: 3
10
+ # Cop supports --auto-correct.
11
+ # Configuration parameters: Include, TreatCommentsAsGroupSeparators.
12
+ # Include: **/*.gemspec
13
+ Gemspec/OrderedDependencies:
10
14
  Exclude:
11
- - 'app/lib/actions/proxy_action.rb'
15
+ - 'foreman-tasks.gemspec'
16
+
17
+ # Offense count: 1
18
+ # Cop supports --auto-correct.
19
+ # Configuration parameters: EnforcedStyle.
20
+ # SupportedStyles: final_newline, final_blank_line
21
+ Layout/TrailingBlankLines:
22
+ Exclude:
23
+ - 'locale/action_names.rb'
12
24
 
13
- Lint/ShadowingOuterLocalVariable:
25
+ # Offense count: 1
26
+ Lint/EmptyWhen:
14
27
  Exclude:
15
- - 'bin/dynflow-executor'
28
+ - 'app/lib/actions/proxy_action.rb'
16
29
 
30
+ # Offense count: 2
17
31
  Lint/UselessAssignment:
18
32
  Exclude:
19
33
  - 'lib/foreman_tasks/tasks/export_tasks.rake'
20
34
 
35
+ # Offense count: 32
21
36
  Metrics/AbcSize:
22
37
  Max: 41
23
38
 
24
- # Configuration parameters: CountComments.
39
+ # Offense count: 2
40
+ # Configuration parameters: CountComments, ExcludedMethods.
25
41
  Metrics/BlockLength:
26
- Max: 105
42
+ Max: 32
27
43
 
44
+ # Offense count: 13
28
45
  # Configuration parameters: CountComments.
29
46
  Metrics/ClassLength:
30
47
  Max: 230
31
48
 
49
+ # Offense count: 9
32
50
  Metrics/CyclomaticComplexity:
33
- Max: 10
51
+ Max: 9
34
52
 
53
+ # Offense count: 482
35
54
  # Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns.
36
55
  # URISchemes: http, https
37
56
  Metrics/LineLength:
38
- Max: 244
57
+ Max: 211
39
58
 
59
+ # Offense count: 54
40
60
  # Configuration parameters: CountComments.
41
61
  Metrics/MethodLength:
42
- Max: 37
62
+ Max: 29
43
63
 
64
+ # Offense count: 2
44
65
  # Configuration parameters: CountComments.
45
66
  Metrics/ModuleLength:
46
- Max: 170
67
+ Max: 167
47
68
 
69
+ # Offense count: 1
48
70
  # Configuration parameters: CountKeywordArgs.
49
71
  Metrics/ParameterLists:
50
72
  Max: 6
51
73
 
74
+ # Offense count: 4
52
75
  Metrics/PerceivedComplexity:
53
- Max: 10
76
+ Max: 9
77
+
78
+ # Offense count: 3
79
+ Naming/MemoizedInstanceVariableName:
80
+ Exclude:
81
+ - 'app/controllers/foreman_tasks/recurring_logics_controller.rb'
82
+ - 'app/lib/actions/recurring_action.rb'
83
+ - 'lib/foreman_tasks_core/otp_manager.rb'
84
+
85
+ # Offense count: 1
86
+ # Configuration parameters: NamePrefix, NamePrefixBlacklist, NameWhitelist, MethodDefinitionMacros.
87
+ # NamePrefix: is_, has_, have_
88
+ # NamePrefixBlacklist: is_, has_, have_
89
+ # NameWhitelist: is_a?
90
+ # MethodDefinitionMacros: define_method, define_singleton_method
91
+ Naming/PredicateName:
92
+ Exclude:
93
+ - 'spec/**/*'
94
+ - 'app/models/foreman_tasks/task/status_explicator.rb'
95
+
96
+ # Offense count: 12
97
+ # Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames.
98
+ # AllowedNames: io, id, to
99
+ Naming/UncommunicativeMethodParamName:
100
+ Exclude:
101
+ - 'app/helpers/foreman_tasks/foreman_tasks_helper.rb'
102
+ - 'app/models/foreman_tasks/recurring_logic.rb'
54
103
 
104
+ # Offense count: 3
105
+ # Cop supports --auto-correct.
106
+ Rails/ActiveRecordAliases:
107
+ Exclude:
108
+ - 'app/models/foreman_tasks/task/dynflow_task.rb'
109
+ - 'test/factories/task_factory.rb'
110
+
111
+ # Offense count: 6
55
112
  # Configuration parameters: Include.
56
- # Include: app/**/*.rb, config/**/*.rb, lib/**/*.rb
57
- Rails/Exit:
113
+ # Include: db/migrate/*.rb
114
+ Rails/CreateTableWithTimestamps:
115
+ Exclude:
116
+ - 'db/migrate/20131205204140_create_foreman_tasks.rb'
117
+ - 'db/migrate/20131209122644_create_foreman_tasks_locks.rb'
118
+ - 'db/migrate/20150907124936_create_recurring_logic.rb'
119
+ - 'db/migrate/20150907131503_create_task_groups.rb'
120
+ - 'db/migrate/20151112152108_create_triggerings.rb'
121
+
122
+ # Offense count: 1
123
+ # Cop supports --auto-correct.
124
+ # Configuration parameters: EnforcedStyle.
125
+ # SupportedStyles: numeric, symbolic
126
+ Rails/HttpStatus:
58
127
  Exclude:
59
- - 'lib/**/*.rake'
128
+ - 'app/controllers/foreman_tasks/concerns/hosts_controller_extension.rb'
60
129
 
130
+ # Offense count: 1
61
131
  # Configuration parameters: Include.
62
132
  # Include: app/**/*.rb, config/**/*.rb, db/**/*.rb, lib/**/*.rb
63
133
  Rails/Output:
64
134
  Exclude:
65
135
  - 'lib/foreman_tasks/cleaner.rb'
66
136
 
137
+ # Offense count: 5
67
138
  Rails/OutputSafety:
68
139
  Exclude:
69
140
  - 'app/helpers/foreman_tasks/foreman_tasks_helper.rb'
70
141
 
71
- # Configuration parameters: EnforcedStyle, SupportedStyles.
142
+ # Offense count: 11
143
+ # Cop supports --auto-correct.
144
+ # Configuration parameters: AutoCorrect, EnforcedStyle.
72
145
  # SupportedStyles: nested, compact
73
146
  Style/ClassAndModuleChildren:
74
147
  Exclude:
@@ -83,51 +156,37 @@ Style/ClassAndModuleChildren:
83
156
  - 'lib/foreman_tasks/dynflow/persistence.rb'
84
157
  - 'test/controllers/api/recurring_logics_controller_test.rb'
85
158
  - 'test/controllers/api/tasks_controller_test.rb'
86
- - 'test/unit/actions/action_with_sub_plans_test.rb'
87
159
 
160
+ # Offense count: 2
88
161
  Style/DoubleNegation:
89
162
  Exclude:
90
163
  - 'app/models/foreman_tasks/lock.rb'
91
164
  - 'app/models/foreman_tasks/recurring_logic.rb'
92
165
 
93
- # Configuration parameters: ExpectMatchingDefinition, Regex, IgnoreExecutableScripts.
94
- Naming/FileName:
166
+ # Offense count: 2
167
+ # Cop supports --auto-correct.
168
+ Style/Encoding:
95
169
  Exclude:
96
- - 'db/seeds.d/20-foreman_tasks_permissions.rb'
97
- - 'db/seeds.d/60-dynflow_proxy_feature.rb'
98
- - 'db/seeds.d/61-foreman_tasks_bookmarks.rb'
99
- - 'lib/foreman-tasks.rb'
170
+ - 'foreman-tasks-core.gemspec'
171
+ - 'foreman-tasks.gemspec'
100
172
 
101
- # Configuration parameters: EnforcedStyle, SupportedStyles.
102
- # SupportedStyles: format, sprintf, percent
103
- Style/FormatString:
173
+ # Offense count: 6
174
+ # Cop supports --auto-correct.
175
+ Style/ExpandPathArguments:
104
176
  Exclude:
105
- - 'app/models/foreman_tasks/task/dynflow_task.rb'
106
- - 'lib/foreman_tasks/tasks/export_tasks.rake'
177
+ - 'foreman-tasks-core.gemspec'
178
+ - 'foreman-tasks.gemspec'
179
+ - 'lib/foreman_tasks/engine.rb'
180
+ - 'script/rails'
107
181
 
182
+ # Offense count: 32
108
183
  # Configuration parameters: MinBodyLength.
109
184
  Style/GuardClause:
110
185
  Enabled: false
111
186
 
112
- # Configuration parameters: NamePrefix, NamePrefixBlacklist, NameWhitelist.
113
- # NamePrefix: is_, has_, have_
114
- # NamePrefixBlacklist: is_, has_, have_
115
- # NameWhitelist: is_a?
116
- Naming/PredicateName:
117
- Exclude:
118
- - 'spec/**/*'
119
- - 'app/models/foreman_tasks/task/status_explicator.rb'
120
-
187
+ # Offense count: 1
121
188
  # Cop supports --auto-correct.
122
- # Configuration parameters: EnforcedStyle, SupportedStyles, AllowInnerSlashes.
123
- # SupportedStyles: slashes, percent_r, mixed
124
- Style/RegexpLiteral:
189
+ # Configuration parameters: AllowMultipleReturnValues.
190
+ Style/RedundantReturn:
125
191
  Exclude:
126
- - 'lib/foreman_tasks/dynflow/console_authorizer.rb'
127
-
128
- # Configuration parameters: Methods.
129
- # Methods: {"reduce"=>["acc", "elem"]}, {"inject"=>["acc", "elem"]}
130
- Style/SingleLineBlockParams:
131
- Exclude:
132
- - 'app/models/foreman_tasks/concerns/action_subject.rb'
133
- - 'app/models/foreman_tasks/lock.rb'
192
+ - 'app/models/foreman_tasks/concerns/action_triggering.rb'
data/README.md CHANGED
@@ -24,7 +24,7 @@ Please see the Foreman manual for appropriate instructions:
24
24
 
25
25
  Set up the repo as explained in the link above, then run
26
26
 
27
- # yum install ruby193-rubygem-foreman-tasks
27
+ # yum install tfm-rubygem-foreman-tasks
28
28
 
29
29
  ### Bundle (gem)
30
30
 
@@ -131,7 +131,7 @@ The executor process needs to be executed before the web server. You
131
131
  can run it by:
132
132
 
133
133
  ```
134
- foreman-rake foreman_tasks:dynflow:executor
134
+ foreman-rake dynflow:executor
135
135
  ```
136
136
 
137
137
  Also, there is a possibility to run the executor in daemonized mode
@@ -1,13 +1,7 @@
1
1
  module ForemanTasks
2
2
  module Concerns
3
3
  module HostsControllerExtension
4
- extend ActiveSupport::Concern
5
-
6
- included do
7
- alias_method_chain :facts, :dynflow
8
- end
9
-
10
- def facts_with_dynflow
4
+ def facts
11
5
  task = ForemanTasks.async_task(::Actions::Foreman::Host::ImportFacts,
12
6
  detect_host_type,
13
7
  params[:name],
@@ -17,7 +17,12 @@ module Actions
17
17
 
18
18
  def fill_planning_errors_to_continuous_output(continuous_output)
19
19
  execution_plan.errors.map do |e|
20
- continuous_output.add_exception(_('Failed to initialize'), e, task.started_at)
20
+ case e.exception
21
+ when ::Actions::ProxyAction::ProxyActionMissing
22
+ continuous_output.add_output(e.message, 'debug', task.started_at)
23
+ else
24
+ continuous_output.add_exception(_('Failed to initialize'), e.exception, task.started_at)
25
+ end
21
26
  end
22
27
  end
23
28
  end
@@ -0,0 +1,73 @@
1
+ module Actions
2
+ module Middleware
3
+ class WatchDelegatedProxySubTasks < ::Dynflow::Middleware
4
+ class CheckOnProxyActions; end
5
+ # Poll the proxy every 10 minutes
6
+ POLL_INTERVAL = 600
7
+ BATCH_SIZE = 1000
8
+
9
+ def run(event = nil)
10
+ if event.nil?
11
+ set_clock
12
+ elsif event == CheckOnProxyActions
13
+ check_triggered
14
+ set_clock
15
+ action.send(:suspend)
16
+ end
17
+ pass event
18
+ end
19
+
20
+ private
21
+
22
+ def set_clock
23
+ action.world.clock.ping action.send(:suspended_action),
24
+ POLL_INTERVAL,
25
+ CheckOnProxyActions
26
+ end
27
+
28
+ def check_triggered
29
+ # Sort by proxy_url so there are less requests per proxy
30
+ source = remote_tasks.triggered.order(:proxy_url, :id)
31
+ source.find_in_batches(:batch_size => BATCH_SIZE) do |batch|
32
+ tasks = batch.group_by(&:proxy_url)
33
+ .map { |(url, tasks)| poll_proxy_tasks(url, tasks) }
34
+ .flatten
35
+ process_task_results tasks
36
+ end
37
+ end
38
+
39
+ def process_task_results(tasks)
40
+ missing, present = tasks.partition { |task| task.result.nil? }
41
+ notify ::Actions::ProxyAction::ProxyActionMissing.new, missing if missing.any?
42
+
43
+ stopped = present.select { |task| %w[stopped paused].include? task.result['state'] }
44
+ notify ::Actions::ProxyAction::ProxyActionStopped.new, stopped if stopped.any?
45
+ end
46
+
47
+ def notify(event, tasks)
48
+ tasks.each do |task|
49
+ action.world.event task.execution_plan_id,
50
+ task.step_id,
51
+ event
52
+ end
53
+ end
54
+
55
+ def remote_tasks
56
+ action.task.remote_sub_tasks
57
+ end
58
+
59
+ def poll_proxy_tasks(url, tasks)
60
+ proxy = ProxyAPI::ForemanDynflow::DynflowProxy.new(:url => url)
61
+ results = proxy.task_states(tasks.map(&:remote_task_id))
62
+ tasks.map do |task|
63
+ task.result = results[task.remote_task_id]
64
+ task
65
+ end
66
+ rescue => e
67
+ # We could not reach the remote task, we assume it's gone
68
+ action.action_logger.warn(_('Failed to check on tasks on proxy at %{url}: %{exception}') % { :url => url, :exception => e.message })
69
+ tasks
70
+ end
71
+ end
72
+ end
73
+ end
@@ -1,10 +1,12 @@
1
1
  module Actions
2
2
  class ProxyAction < Base
3
3
  include ::Dynflow::Action::Cancellable
4
- include ::Dynflow::Action::Timeouts
5
4
 
6
5
  middleware.use ::Actions::Middleware::HideSecrets
7
6
 
7
+ execution_plan_hooks.use :clean_remote_task, :on => :stopped
8
+ execution_plan_hooks.use :wipe_secrets!, :on => :stopped
9
+
8
10
  class CallbackData
9
11
  attr_reader :data
10
12
 
@@ -13,6 +15,14 @@ module Actions
13
15
  end
14
16
  end
15
17
 
18
+ class ProxyActionMissing < RuntimeError
19
+ def backtrace
20
+ []
21
+ end
22
+ end
23
+
24
+ class ProxyActionStopped; end
25
+
16
26
  def plan(proxy, klass, options)
17
27
  options[:connection_options] ||= {}
18
28
  default_connection_options.each { |key, value| options[:connection_options][key] ||= value }
@@ -23,7 +33,7 @@ module Actions
23
33
  with_connection_error_handling(event) do |event|
24
34
  case event
25
35
  when nil
26
- if output[:proxy_task_id]
36
+ if remote_task
27
37
  on_resume
28
38
  else
29
39
  trigger_proxy_task
@@ -37,38 +47,41 @@ module Actions
37
47
  abort_proxy_task
38
48
  when CallbackData
39
49
  on_data(event.data)
40
- when ::Dynflow::Action::Timeouts::Timeout
41
- check_task_status
50
+ when ProxyActionMissing
51
+ on_proxy_action_missing
52
+ when ProxyActionStopped
53
+ on_proxy_action_stopped
42
54
  else
43
55
  raise "Unexpected event #{event.inspect}"
44
56
  end
45
57
  end
46
58
  end
47
59
 
60
+ def remote_task
61
+ @remote_task ||= ForemanTasks::RemoteTask.find_by(:execution_plan_id => execution_plan_id, :step_id => run_step_id)
62
+ end
63
+
48
64
  def trigger_proxy_task
49
65
  suspend do |_suspended_action|
50
- set_timeout! unless timeout_set?
51
66
  response = proxy.trigger_task(proxy_action_name,
52
67
  input.merge(:callback => { :task_id => task.id,
53
68
  :step_id => run_step_id }))
69
+ ::ForemanTasks::RemoteTask.new(:remote_task_id => response['task_id'], :execution_plan_id => execution_plan_id,
70
+ :state => 'triggered', :proxy_url => input[:proxy_url], :step_id => run_step_id).save!
54
71
  output[:proxy_task_id] = response['task_id']
55
72
  end
56
73
  end
57
74
 
58
75
  def check_task_status
59
- if output[:proxy_task_id]
60
- response = proxy.status_of_task(output[:proxy_task_id])
61
- if %w[stopped paused].include? response['state']
62
- if response['result'] == 'error'
63
- raise ::Foreman::Exception, _('The smart proxy task %s failed.') % output[:proxy_task_id]
64
- else
65
- on_data(response['actions'].find { |block_action| block_action['class'] == proxy_action_name }['output'])
66
- end
76
+ response = proxy.status_of_task(output[:proxy_task_id])
77
+ if %w[stopped paused].include? response['state']
78
+ if response['result'] == 'error'
79
+ raise ::Foreman::Exception, _('The smart proxy task %s failed.') % output[:proxy_task_id]
67
80
  else
68
- suspend
81
+ on_data(response['actions'].find { |block_action| block_action['class'] == proxy_action_name }['output'])
69
82
  end
70
83
  else
71
- process_timeout
84
+ suspend
72
85
  end
73
86
  end
74
87
 
@@ -95,12 +108,21 @@ module Actions
95
108
  # @override to put custom logic on event handling
96
109
  def on_data(data)
97
110
  output[:proxy_output] = data
98
- wipe_secrets!
99
111
  end
100
112
 
101
- def wipe_secrets!
113
+ # Removes the :secrets key from the action's input and output and saves the action
114
+ def wipe_secrets!(_execution_plan)
102
115
  input.delete(:secrets)
103
116
  output.delete(:secrets)
117
+ world.persistence.save_action(execution_plan_id, self)
118
+ end
119
+
120
+ def on_proxy_action_missing
121
+ error! ProxyActionMissing.new(_('Proxy task gone missing from the smart proxy'))
122
+ end
123
+
124
+ def on_proxy_action_stopped
125
+ check_task_status
104
126
  end
105
127
 
106
128
  # @override String name of an action to be triggered on server
@@ -113,8 +135,8 @@ module Actions
113
135
  end
114
136
 
115
137
  def proxy_output(live = false)
116
- if output.key?(:proxy_output)
117
- output.fetch(:proxy_output) || {}
138
+ if output.key?(:proxy_output) || state == :error
139
+ output.fetch(:proxy_output, {})
118
140
  elsif live && output[:proxy_task_id]
119
141
  proxy_data = proxy.status_of_task(output[:proxy_task_id])['actions'].detect { |action| action['class'] == proxy_action_name }
120
142
  proxy_data.fetch('output', {})
@@ -146,22 +168,15 @@ module Actions
146
168
  output[:metadata] = thing
147
169
  end
148
170
 
149
- def timeout_set?
150
- !metadata[:timeout].nil?
151
- end
152
-
153
- def set_timeout!
154
- time = Time.zone.now + input[:connection_options][:timeout]
155
- schedule_timeout(time)
156
- metadata[:timeout] = time.to_s
157
- end
158
-
159
171
  def default_connection_options
160
172
  # Fails if the plan is not finished within 60 seconds from the first task trigger attempt on the smart proxy
161
173
  # If the triggering fails, it retries 3 more times with 15 second delays
162
174
  { :retry_interval => Setting['foreman_tasks_proxy_action_retry_interval'] || 15,
163
- :retry_count => Setting['foreman_tasks_proxy_action_retry_count'] || 4,
164
- :timeout => Setting['foreman_tasks_proxy_action_start_timeout'] || 60 }
175
+ :retry_count => Setting['foreman_tasks_proxy_action_retry_count'] || 4 }
176
+ end
177
+
178
+ def clean_remote_task(*_args)
179
+ remote_task.destroy! if remote_task
165
180
  end
166
181
 
167
182
  private
@@ -173,7 +188,7 @@ module Actions
173
188
  def with_connection_error_handling(event = nil)
174
189
  yield event
175
190
  rescue ::RestClient::Exception, Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::ETIMEDOUT => e
176
- if event.class == CallbackData || event == ::Dynflow::Action::Timeouts::Timeout
191
+ if event.class == CallbackData
177
192
  raise e
178
193
  else
179
194
  handle_connection_exception(e, event)
@@ -34,6 +34,11 @@ module ProxyAPI
34
34
  def tasks_count(state)
35
35
  MultiJson.load(Task.new(@args).send(:get, "count?state=#{state}"))['count'].to_i
36
36
  end
37
+
38
+ def task_states(ids)
39
+ payload = MultiJson.dump(:task_ids => ids)
40
+ MultiJson.load(Task.new(@args).send(:post, payload, 'status'))
41
+ end
37
42
  end
38
43
  end
39
44
  end
@@ -0,0 +1,12 @@
1
+ module ForemanTasks
2
+ class RemoteTask < ApplicationRecord
3
+ attr_accessor :result
4
+
5
+ belongs_to :task, :class_name => 'ForemanTasks::Task',
6
+ :primary_key => :external_id,
7
+ :foreign_key => :execution_plan_id,
8
+ :inverse_of => :remote_tasks
9
+
10
+ scope :triggered, -> { where(:state => 'triggered') }
11
+ end
12
+ end
@@ -20,6 +20,10 @@ module ForemanTasks
20
20
  has_many :sub_tasks, :class_name => 'ForemanTasks::Task', :foreign_key => :parent_task_id, :dependent => :nullify
21
21
  has_many :locks, :dependent => :destroy
22
22
 
23
+ has_many :remote_sub_tasks, :class_name => 'ForemanTasks::RemoteTask', :through => :sub_tasks, :source => :remote_tasks
24
+
25
+ has_many :remote_tasks, :class_name => 'ForemanTasks::RemoteTask', :primary_key => :external_id, :foreign_key => :execution_plan_id, :dependent => :destroy
26
+
23
27
  has_many :task_group_members, :dependent => :destroy
24
28
  has_many :task_groups, :through => :task_group_members
25
29
  if Rails::VERSION::MAJOR < 4
@@ -210,7 +214,8 @@ module ForemanTasks
210
214
  end
211
215
 
212
216
  def action
213
- super || to_label
217
+ return to_label if super.blank?
218
+ super
214
219
  end
215
220
 
216
221
  def to_label
@@ -15,11 +15,11 @@ module ForemanTasks
15
15
  self.start_at = string_to_time(utc_zone, data[:start_at]) if data[:start_at]
16
16
  self.start_before = string_to_time(utc_zone, data[:start_before]) if data[:start_before]
17
17
  self.parent_task_id ||= begin
18
- if main_action.caller_execution_plan_id
18
+ if main_action.present? && main_action.caller_execution_plan_id
19
19
  DynflowTask.where(:external_id => main_action.caller_execution_plan_id).first!.id
20
20
  end
21
21
  end
22
- self.label ||= main_action && main_action.class.name
22
+ self[:label] ||= label
23
23
  changes = self.changes
24
24
  save!
25
25
  changes
@@ -69,12 +69,19 @@ module ForemanTasks
69
69
  execution_plan(false)
70
70
  end
71
71
 
72
+ def label
73
+ return execution_plan_action.input['job_class'] if active_job?
74
+ return main_action.class.name if main_action.present?
75
+ self[:label]
76
+ end
77
+
72
78
  def input
73
- main_action.respond_to?(:task_input) && main_action.task_input
79
+ return execution_plan_action.input['job_arguments'] if active_job?
80
+ main_action.task_input if main_action.respond_to?(:task_input)
74
81
  end
75
82
 
76
83
  def output
77
- main_action.respond_to?(:task_output) && main_action.task_output
84
+ main_action.task_output if main_action.respond_to?(:task_output)
78
85
  end
79
86
 
80
87
  def failed_steps
@@ -98,17 +105,47 @@ module ForemanTasks
98
105
 
99
106
  def main_action
100
107
  return @main_action if defined?(@main_action)
101
- @main_action = execution_plan && execution_plan.root_plan_step.try(:action, execution_plan)
108
+ if active_job?
109
+ action = execution_plan_action
110
+ active_job_action(action.input['job_class'], action.input['job_arguments'])
111
+ else
112
+ @main_action = execution_plan && execution_plan.root_plan_step.try(:action, execution_plan)
113
+ end
114
+ end
115
+
116
+ # The class for ActiveJob jobs in Dynflow, JobWrapper is not expected to
117
+ # implement any humanized actions. Individual jobs are expected to implement
118
+ # humanized_* methods for foreman-tasks integration.
119
+ def active_job_action(klass, args)
120
+ return if klass.blank?
121
+ klass.constantize.new(*args)
122
+ end
123
+
124
+ def active_job?
125
+ return false unless execution_plan.present? &&
126
+ execution_plan.root_plan_step.present?
127
+ execution_plan_action.class == ::Dynflow::ActiveJob::QueueAdapters::JobWrapper
128
+ end
129
+
130
+ def execution_plan_action
131
+ execution_plan.root_plan_step.action(execution_plan)
132
+ end
133
+
134
+ def execution_scheduled?
135
+ main_action.nil? || main_action.respond_to?(:execution_plan) &&
136
+ main_action.execution_plan.state == :scheduled ||
137
+ execution_plan.state == :scheduled
102
138
  end
103
139
 
104
140
  def get_humanized(method)
105
141
  @humanized_cache ||= {}
106
- if [:name, :input, :output, :error].include?(method)
107
- method = "humanized_#{method}".to_sym
108
- end
142
+ method = find_humanize_method_kind(method)
109
143
  Match! method, :humanized_name, :humanized_input, :humanized_output, :humanized_errors
110
- return N_('N/A') if method != :humanized_name && (main_action.nil? || main_action.execution_plan.state == :scheduled)
111
- return N_(label) if method == :humanized_name && main_action.nil?
144
+ if method != :humanized_name && execution_scheduled?
145
+ return N_('N/A')
146
+ elsif method == :humanized_name && main_action.nil?
147
+ return N_(label)
148
+ end
112
149
  @humanized_cache[method] ||= begin
113
150
  if main_action.respond_to? method
114
151
  begin
@@ -120,6 +157,13 @@ module ForemanTasks
120
157
  end
121
158
  end
122
159
 
160
+ def find_humanize_method_kind(method)
161
+ return method if /humanized_.*/ =~ method
162
+ if [:name, :input, :output, :error].include?(method)
163
+ "humanized_#{method}".to_sym
164
+ end
165
+ end
166
+
123
167
  def self.consistency_check
124
168
  fixed_count = 0
125
169
  logger = Foreman::Logging.logger('foreman-tasks')
@@ -9,8 +9,7 @@ class Setting::ForemanTasks < Setting
9
9
  set('dynflow_enable_console', N_('Enable the dynflow console (/foreman_tasks/dynflow) for debugging'), true),
10
10
  set('dynflow_console_require_auth', N_('Require user to be authenticated as user with admin rights when accessing dynflow console'), true),
11
11
  set('foreman_tasks_proxy_action_retry_count', N_('Number of attempts to start a task on the smart proxy before failing'), 4),
12
- set('foreman_tasks_proxy_action_retry_interval', N_('Time in seconds between retries'), 15),
13
- set('foreman_tasks_proxy_action_start_timeout', N_('Time in second during which a task has to be started on the proxy'), 60)
12
+ set('foreman_tasks_proxy_action_retry_interval', N_('Time in seconds between retries'), 15)
14
13
  ].each { |s| create! s.update(:category => 'Setting::ForemanTasks') }
15
14
  end
16
15
 
@@ -0,0 +1,16 @@
1
+ class AddRemoteTasks < ActiveRecord::Migration[5.0]
2
+ def change
3
+ create_table :foreman_tasks_remote_tasks do |t|
4
+ t.string :execution_plan_id, :null => false
5
+ t.integer :step_id, :null => false
6
+ t.string :state, :null => false, :default => 'new'
7
+
8
+ t.string :proxy_url, :null => false
9
+ t.string :remote_task_id
10
+
11
+ t.index [:execution_plan_id, :step_id], :name => 'index_foreman_tasks_plan_id_and_step_id'
12
+
13
+ t.datetime :created_at
14
+ end
15
+ end
16
+ end
@@ -100,6 +100,7 @@ module ForemanTasks
100
100
  with_batches(source, name) do |chunk|
101
101
  delete_tasks chunk
102
102
  delete_dynflow_plans chunk
103
+ delete_remote_tasks(chunk)
103
104
  end
104
105
  end
105
106
  delete_orphaned_dynflow_tasks
@@ -115,6 +116,10 @@ module ForemanTasks
115
116
  tasks.delete_all
116
117
  end
117
118
 
119
+ def delete_remote_tasks(chunk)
120
+ ForemanTasks::RemoteTask.where(:execution_plan_id => chunk.map(&:external_id)).delete_all
121
+ end
122
+
118
123
  def tasks_to_csv(dataset, backup_dir, file_name)
119
124
  with_backup_file(backup_dir, file_name) do |csv, appending|
120
125
  csv << ForemanTasks::Task.attribute_names.to_csv unless appending
@@ -133,7 +133,7 @@ module ForemanTasks
133
133
  # to enable async Foreman operations using Dynflow
134
134
  if ENV['FOREMAN_TASKS_MONKEYS'] == 'true'
135
135
  config.to_prepare do
136
- ::Api::V2::HostsController.send :include, ForemanTasks::Concerns::HostsControllerExtension
136
+ ::Api::V2::HostsController.send :prepend, ForemanTasks::Concerns::HostsControllerExtension
137
137
  ::Host::Base.send :include, ForemanTasks::Concerns::HostActionSubject
138
138
  end
139
139
  end
@@ -1,3 +1,3 @@
1
1
  module ForemanTasks
2
- VERSION = '0.13.0'.freeze
2
+ VERSION = '0.13.1'.freeze
3
3
  end
@@ -18,6 +18,33 @@ module ForemanTasks
18
18
  assert_response :success
19
19
  assert_template 'api/tasks/show'
20
20
  end
21
+
22
+ test_attributes :pid => 'a2a81ca2-63c4-47f5-9314-5852f5e2617f'
23
+ it 'search for non-existent task' do
24
+ get :show, params: { :id => 'abc123' }
25
+ assert_response :missing
26
+ assert_includes @response.body, 'Resource task not found by id'
27
+ end
28
+ end
29
+
30
+ describe 'GET /api/tasks/summary' do
31
+ class DummyTestSummaryAction < Support::DummyDynflowAction
32
+ # a dummy test action that do nothing
33
+ def run
34
+ # a dummy run method
35
+ end
36
+ end
37
+
38
+ test_attributes :pid => 'bdcab413-a25d-4fe1-9db4-b50b5c31ebce'
39
+ it 'get tasks summary' do
40
+ ForemanTasks.trigger(DummyTestSummaryAction)
41
+ get :summary
42
+ assert_response :success
43
+ response = JSON.parse(@response.body)
44
+ assert_kind_of Array, response
45
+ refute response.empty?
46
+ assert_kind_of Hash, response[0]
47
+ end
21
48
  end
22
49
 
23
50
  describe 'POST /tasks/callback' do
@@ -26,7 +26,7 @@ module ForemanTasks
26
26
  { 'foo' => 'bar',
27
27
  'secrets' => secrets,
28
28
  'connection_options' =>
29
- { 'retry_interval' => 15, 'retry_count' => 4, 'timeout' => 60 },
29
+ { 'retry_interval' => 15, 'retry_count' => 4 },
30
30
  'proxy_url' => 'proxy.example.com',
31
31
  'proxy_action_name' => 'Proxy::DummyAction',
32
32
  'callback' => { 'task_id' => Support::DummyProxyAction.proxy.uuid, 'step_id' => @action.run_step_id } }]
@@ -107,6 +107,13 @@ module ForemanTasks
107
107
  it 'wipes secrets' do
108
108
  @action.input[:secrets].must_equal secrets
109
109
  action = run_action(@action, ::Actions::ProxyAction::CallbackData.new('result' => 'success'))
110
+
111
+ # #wipe_secrets! gets called as a hook, hooks are not triggered when using action testing helpers
112
+ persistence = mock
113
+ persistence.stubs(:save_action)
114
+ action.world.stubs(:persistence).returns(persistence)
115
+ action.wipe_secrets!(nil)
116
+
110
117
  refute action.input.key?(:secrets)
111
118
  end
112
119
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: foreman-tasks
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.13.0
4
+ version: 0.13.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ivan Nečas
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-04-02 00:00:00.000000000 Z
11
+ date: 2018-05-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: foreman-tasks-core
@@ -143,6 +143,7 @@ files:
143
143
  - app/lib/actions/middleware/keep_current_user.rb
144
144
  - app/lib/actions/middleware/rails_executor_wrap.rb
145
145
  - app/lib/actions/middleware/recurring_logic.rb
146
+ - app/lib/actions/middleware/watch_delegated_proxy_sub_tasks.rb
146
147
  - app/lib/actions/proxy_action.rb
147
148
  - app/lib/actions/recurring_action.rb
148
149
  - app/lib/actions/serializers/active_record_serializer.rb
@@ -152,6 +153,7 @@ files:
152
153
  - app/models/foreman_tasks/concerns/host_action_subject.rb
153
154
  - app/models/foreman_tasks/lock.rb
154
155
  - app/models/foreman_tasks/recurring_logic.rb
156
+ - app/models/foreman_tasks/remote_task.rb
155
157
  - app/models/foreman_tasks/task.rb
156
158
  - app/models/foreman_tasks/task/dynflow_task.rb
157
159
  - app/models/foreman_tasks/task/status_explicator.rb
@@ -203,6 +205,7 @@ files:
203
205
  - db/migrate/20160924213030_change_tasks_widget_names.rb
204
206
  - db/migrate/20161003091412_add_missing_indexes.rb
205
207
  - db/migrate/20171026082635_add_task_action.foreman_tasks.rb
208
+ - db/migrate/20180207150921_add_remote_tasks.foreman_tasks.rb
206
209
  - db/migrate/20180216092715_use_uuid.rb
207
210
  - db/seeds.d/20-foreman_tasks_permissions.rb
208
211
  - db/seeds.d/60-dynflow_proxy_feature.rb
@@ -283,7 +286,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
283
286
  version: '0'
284
287
  requirements: []
285
288
  rubyforge_project:
286
- rubygems_version: 2.6.8
289
+ rubygems_version: 2.7.3
287
290
  signing_key:
288
291
  specification_version: 4
289
292
  summary: Foreman plugin for showing tasks information for resoruces and users