Class: ForemanTasks::Task::DynflowTask

Inherits:
ForemanTasks::Task show all
Includes:
Algebrick::TypeCheck
Defined in:
app/models/foreman_tasks/task/dynflow_task.rb

Constant Summary

Constants included from Search

Search::SUPPORTED_DURATION_FORMAT

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from ForemanTasks::Task

#action, #action_continuous_output, #add_missing_task_groups, authorized_resource_name, #build_notifications, #check_permissions_after_save, #delayed?, #execution_type, latest_tasks_by_resource_ids, #notification_recipients_ids, #owner, #paused?, #pending?, #recurring?, #scheduled?, #self_and_parents, #sub_tasks_counts, #to_label, #username

Methods included from Search

#search_by_duration, #search_by_generic_resource, #search_by_taxonomy

Class Method Details

.consistency_checkObject



219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
# File 'app/models/foreman_tasks/task/dynflow_task.rb', line 219

def self.consistency_check
  fixed_count = 0
  logger = Foreman::Logging.logger('foreman-tasks')

  # Dynflow execution plans which are not stopped at this point should
  # eventually get moving and their tasks should eventually get back in sync
  # as the execution plans run
  joins('LEFT JOIN dynflow_execution_plans ON foreman_tasks_tasks.external_id = dynflow_execution_plans.uuid::varchar')
    .where('foreman_tasks_tasks.state_updated_at < dynflow_execution_plans.ended_at')
    .find_each do |task|
    changes = task.update_from_dynflow(task.execution_plan.to_hash)
    unless changes.empty?
      fixed_count += 1
      logger.warn('Task %s updated at consistency check: %s' % [task.id, changes.inspect])
    end
  rescue => e
    task.update(:state => 'stopped', :result => 'error')
    Foreman::Logging.exception("Failed at consistency check for task #{task.id}", e, :logger => 'foreman-tasks')
  end
  # Wipe tasks left behind by now stopped tasks
  ForemanTasks::Lock.left_joins(:task).merge(where(:state => 'stopped')).destroy_all
  fixed_count
end

.model_nameObject



250
251
252
# File 'app/models/foreman_tasks/task/dynflow_task.rb', line 250

def self.model_name
  superclass.model_name
end

.new_for_execution_plan(execution_plan) ⇒ Object



243
244
245
246
247
248
# File 'app/models/foreman_tasks/task/dynflow_task.rb', line 243

def self.new_for_execution_plan(execution_plan)
  new(:external_id => execution_plan.id,
      :state => execution_plan.state.to_s,
      :result => execution_plan.result.to_s,
      :user_id => User.current.try(:id))
end

Instance Method Details

#abortObject



35
36
37
# File 'app/models/foreman_tasks/task/dynflow_task.rb', line 35

def abort
  execution_plan!.cancel(true).any?
end

#active_job?Boolean

Returns:

  • (Boolean)


165
166
167
168
169
# File 'app/models/foreman_tasks/task/dynflow_task.rb', line 165

def active_job?
  return false unless execution_plan.present? &&
                      execution_plan.root_plan_step.present?
  execution_plan_action.class == ::Dynflow::ActiveJob::QueueAdapters::JobWrapper
end

#active_job_action(klass, args) ⇒ Object

The class for ActiveJob jobs in Dynflow, JobWrapper is not expected to implement any humanized actions. Individual jobs are expected to implement humanized_* methods for foreman-tasks integration.



158
159
160
161
162
163
# File 'app/models/foreman_tasks/task/dynflow_task.rb', line 158

def active_job_action(klass, args)
  return if klass.blank?
  if (active_job_class = klass.safe_constantize)
    active_job_class.new(*args)
  end
end

#active_job_dataObject



171
172
173
174
175
176
177
178
179
180
# File 'app/models/foreman_tasks/task/dynflow_task.rb', line 171

def active_job_data
  args = if execution_plan.delay_record
           execution_plan.delay_record.args.first
         else
           execution_plan_action.input
         end
  return args['job_data'] if args.key?('job_data')
  # For dynflow <= 1.2.1
  { 'job_class' => args['job_class'], 'arguments' => args['job_arguments'] }
end

#cancelObject



31
32
33
# File 'app/models/foreman_tasks/task/dynflow_task.rb', line 31

def cancel
  execution_plan!.cancel.any?
end

#cancellable?Boolean

Returns:

  • (Boolean)


27
28
29
# File 'app/models/foreman_tasks/task/dynflow_task.rb', line 27

def cancellable?
  execution_plan.try(:cancellable?)
end

#cancellable_action?(action) ⇒ Boolean

Returns:

  • (Boolean)


43
44
45
# File 'app/models/foreman_tasks/task/dynflow_task.rb', line 43

def cancellable_action?(action)
  action.is_a?(::Dynflow::Action::Cancellable)
end

#cli_exampleObject



136
137
138
# File 'app/models/foreman_tasks/task/dynflow_task.rb', line 136

def cli_example
  main_action.cli_example if main_action.respond_to?(:cli_example)
end

#delayed_planObject



72
73
74
# File 'app/models/foreman_tasks/task/dynflow_task.rb', line 72

def delayed_plan
  ForemanTasks.dynflow.world.persistence.load_delayed_plan(external_id) if state == :scheduled
end

#execution_plan(silence_exception = true) ⇒ Object



52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# File 'app/models/foreman_tasks/task/dynflow_task.rb', line 52

def execution_plan(silence_exception = true)
  return @execution_plan if defined?(@execution_plan)
  execution_plan = ForemanTasks.dynflow.world.persistence.load_execution_plan(external_id)
  # don't use invalid execution plans for our purposes
  if execution_plan.respond_to?(:valid?) && !execution_plan.valid?
    raise execution_plan.exception
  else
    @execution_plan = execution_plan
  end
  @execution_plan
rescue => e
  Foreman::Logging.exception("Could not load execution plan #{external_id} for task #{id}", e, :logger => 'foreman-tasks')
  raise e unless silence_exception
  nil
end

#execution_plan!Object



76
77
78
# File 'app/models/foreman_tasks/task/dynflow_task.rb', line 76

def execution_plan!
  execution_plan(false)
end

#execution_plan_actionObject



182
183
184
# File 'app/models/foreman_tasks/task/dynflow_task.rb', line 182

def execution_plan_action
  execution_plan.root_plan_step.action(execution_plan)
end

#execution_scheduled?Boolean

Returns:

  • (Boolean)


186
187
188
189
190
# File 'app/models/foreman_tasks/task/dynflow_task.rb', line 186

def execution_scheduled?
  main_action.nil? || main_action.respond_to?(:execution_plan) &&
    main_action.execution_plan.state == :scheduled ||
    execution_plan.state == :scheduled
end

#failed_stepsObject



94
95
96
# File 'app/models/foreman_tasks/task/dynflow_task.rb', line 94

def failed_steps
  execution_plan.try(:steps_in_state, :skipped, :skipping, :error) || []
end

#find_humanize_method_kind(method) ⇒ Object



212
213
214
215
216
217
# File 'app/models/foreman_tasks/task/dynflow_task.rb', line 212

def find_humanize_method_kind(method)
  return method if /humanized_.*/ =~ method
  if [:name, :input, :output, :error].include?(method)
    "humanized_#{method}".to_sym
  end
end

#frozenObject



68
69
70
# File 'app/models/foreman_tasks/task/dynflow_task.rb', line 68

def frozen
  delayed_plan.try(:frozen)
end

#get_humanized(method) ⇒ Object



192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
# File 'app/models/foreman_tasks/task/dynflow_task.rb', line 192

def get_humanized(method)
  @humanized_cache ||= {}
  method = find_humanize_method_kind(method)
  Match! method, :humanized_name, :humanized_input, :humanized_output, :humanized_errors
  if method != :humanized_name && execution_scheduled?
    return
  elsif method == :humanized_name && main_action.nil?
    return N_(label)
  end
  @humanized_cache[method] ||= begin
                                 if main_action.respond_to? method
                                   begin
                                     main_action.send method
                                   rescue Exception => error # rubocop:disable Lint/RescueException
                                     "#{error.message} (#{error.class})\n#{error.backtrace.join "\n"}"
                                   end
                                 end
                               end
end

#haltObject



254
255
256
# File 'app/models/foreman_tasks/task/dynflow_task.rb', line 254

def halt
  ::ForemanTasks.dynflow.world.halt(external_id)
end

#humanizedObject



129
130
131
132
133
134
# File 'app/models/foreman_tasks/task/dynflow_task.rb', line 129

def humanized
  { action: get_humanized(:humanized_name),
    input:  get_humanized(:humanized_input),
    output: get_humanized(:humanized_output),
    errors: get_humanized(:humanized_errors) }
end

#inputObject



85
86
87
88
# File 'app/models/foreman_tasks/task/dynflow_task.rb', line 85

def input
  return active_job_data['arguments'] if active_job?
  main_action.task_input if main_action.respond_to?(:task_input)
end

#input_output_failed_stepsObject



102
103
104
105
106
107
108
109
110
111
112
113
# File 'app/models/foreman_tasks/task/dynflow_task.rb', line 102

def input_output_failed_steps
  failed_steps.map do |f|
    f_action = f.action(execution_plan)
    {
      error: ({ exception_class: f.error.exception_class, message: f.error.message, backtrace: f.error.backtrace } if f.error),
      action_class: f.action_class.name,
      state: f.state,
      input: f_action.input.pretty_inspect,
      output: f_action.output.pretty_inspect,
    }
  end
end

#input_output_running_stepsObject



115
116
117
118
119
120
121
122
123
124
125
126
127
# File 'app/models/foreman_tasks/task/dynflow_task.rb', line 115

def input_output_running_steps
  running_steps.map do |f|
    f_action = f.action(execution_plan)
    {
      id: f_action.id,
      action_class: f.action_class.name,
      state: f.state,
      input: f_action.input.pretty_inspect,
      output: f_action.output.pretty_inspect,
      cancellable: cancellable_action?(f_action),
    }
  end
end

#labelObject



80
81
82
83
# File 'app/models/foreman_tasks/task/dynflow_task.rb', line 80

def label
  return main_action.class.name if main_action.present?
  self[:label]
end

#main_actionObject



140
141
142
143
144
145
146
147
148
149
150
151
152
153
# File 'app/models/foreman_tasks/task/dynflow_task.rb', line 140

def main_action
  return @main_action if defined?(@main_action)

  @main_action = execution_plan && execution_plan.root_plan_step.try(:action, execution_plan)
  if active_job?
    job_data = active_job_data
    begin
      @main_action = active_job_action(job_data['job_class'], job_data['arguments'])
    rescue => e
      Foreman::Logging.exception("Failed to load ActiveJob for task #{id}", e, :logger => 'foreman-tasks')
    end
  end
  @main_action
end

#outputObject



90
91
92
# File 'app/models/foreman_tasks/task/dynflow_task.rb', line 90

def output
  main_action.task_output if main_action.respond_to?(:task_output)
end

#progressObject



47
48
49
50
# File 'app/models/foreman_tasks/task/dynflow_task.rb', line 47

def progress
  progress_raw = execution_plan.try(:progress) || 0
  progress_raw.round(2)
end

#resumable?Boolean

Returns:

  • (Boolean)


39
40
41
# File 'app/models/foreman_tasks/task/dynflow_task.rb', line 39

def resumable?
  execution_plan.try(:state) == :paused
end

#running_stepsObject



98
99
100
# File 'app/models/foreman_tasks/task/dynflow_task.rb', line 98

def running_steps
  execution_plan.try(:steps_in_state, :running, :suspended) || []
end

#update_from_dynflow(plan, delayed_plan = nil) ⇒ Object



8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# File 'app/models/foreman_tasks/task/dynflow_task.rb', line 8

def update_from_dynflow(plan, delayed_plan = nil)
  self.external_id    = plan.id
  self.result         = map_result(plan).to_s
  self.state          = plan.state.to_s
  self.started_at     = plan.started_at unless plan.started_at.nil?
  self.ended_at       = plan.ended_at unless plan.ended_at.nil?
  self.start_at       = delayed_plan.start_at if delayed_plan
  self.start_before   = delayed_plan.start_before if delayed_plan
  self.parent_task_id ||= begin
                            if main_action.try(:caller_execution_plan_id)
                              DynflowTask.where(:external_id => main_action.caller_execution_plan_id).first!.id
                            end
                          end
  self[:label]        ||= label
  changes = self.changes
  save!
  changes
end