foreman-tasks 0.10.0 → 0.10.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.
Files changed (33) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +0 -2
  3. data/app/assets/javascripts/{trigger_form.js → foreman_tasks/trigger_form.js} +0 -0
  4. data/app/controllers/foreman_tasks/recurring_logics_controller.rb +6 -1
  5. data/app/controllers/foreman_tasks/tasks_controller.rb +16 -2
  6. data/app/lib/actions/proxy_action.rb +7 -0
  7. data/app/models/foreman_tasks/task/dynflow_task.rb +19 -4
  8. data/app/models/foreman_tasks/task/summarizer.rb +3 -1
  9. data/app/views/common/_trigger_form.html.erb +1 -1
  10. data/app/views/foreman_tasks/recurring_logics/index.html.erb +1 -2
  11. data/app/views/foreman_tasks/tasks/dashboard/_latest_tasks_in_error_warning.html.erb +1 -1
  12. data/app/views/foreman_tasks/tasks/dashboard/_tasks_status.html.erb +2 -0
  13. data/app/views/foreman_tasks/tasks/index.html.erb +1 -2
  14. data/config/routes.rb +1 -0
  15. data/foreman-tasks.gemspec +1 -3
  16. data/lib/foreman_tasks.rb +2 -1
  17. data/lib/foreman_tasks/dynflow.rb +2 -108
  18. data/lib/foreman_tasks/dynflow/configuration.rb +7 -142
  19. data/lib/foreman_tasks/dynflow/persistence.rb +3 -2
  20. data/lib/foreman_tasks/engine.rb +2 -2
  21. data/lib/foreman_tasks/version.rb +1 -1
  22. data/test/controllers/recurring_logics_controller_test.rb +14 -0
  23. data/test/controllers/tasks_controller_test.rb +17 -0
  24. data/test/unit/otp_manager_test.rb +70 -0
  25. metadata +12 -43
  26. data/bin/dynflow-executor +0 -71
  27. data/bin/foreman-tasks +0 -5
  28. data/deploy/foreman-tasks.init +0 -231
  29. data/deploy/foreman-tasks.service +0 -16
  30. data/deploy/foreman-tasks.sysconfig +0 -26
  31. data/lib/foreman_tasks/dynflow/daemon.rb +0 -143
  32. data/lib/foreman_tasks/tasks/dynflow.rake +0 -7
  33. data/test/unit/daemon_test.rb +0 -86
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: acd7b0adebf587957c53c75ff415ae872d5b5995
4
- data.tar.gz: e682ae90a94614bae6d1cf7b1014e39bce5fa82e
3
+ metadata.gz: 7f1ef8e297d56025b9460e44ca7e96e753307d55
4
+ data.tar.gz: a26e87a7cd3abda09035c1b63747234a77c0562f
5
5
  SHA512:
6
- metadata.gz: bd0a1edea81a4b634fea1f4e81ab7b2778a11d68cb5cc2d8c35974c60db2d9cc58ac12b85904fb0943ba22a1540b19d513504aefac6f285f1703cba8f4ce3217
7
- data.tar.gz: 036bb166af540a7509a590fa8f5de2d056d93c8c77bf8311e92705d3e8936097365449b60b4f025fe6814df9ca8f6e5fc3e70d58fdc6a6ee74e9682a52351e13
6
+ metadata.gz: 0bf2da8785b5fb0504c110b4089b2a27fcb30173a6c060f0171295d77c4d3240192406822a38872edeb102beacfdd0f3fd462a03bcb5d2c9992de2a1b08b3fab
7
+ data.tar.gz: f95dcdf3437613f5c702df30248c6f962db176e88e2474edba21db0d97d362af4d554ccaecee5726fdef27686f2869eef2fb7853ce9fa26fb3458ade1f947a66
@@ -57,7 +57,6 @@ Metrics/PerceivedComplexity:
57
57
  Rails/Exit:
58
58
  Exclude:
59
59
  - 'lib/**/*.rake'
60
- - 'lib/foreman_tasks/dynflow/daemon.rb'
61
60
 
62
61
  Rails/HttpPositionalArguments:
63
62
  Enabled: false
@@ -84,7 +83,6 @@ Style/ClassAndModuleChildren:
84
83
  - 'app/models/setting/foreman_tasks.rb'
85
84
  - 'lib/foreman_tasks/dynflow/configuration.rb'
86
85
  - 'lib/foreman_tasks/dynflow/console_authorizer.rb'
87
- - 'lib/foreman_tasks/dynflow/daemon.rb'
88
86
  - 'lib/foreman_tasks/dynflow/persistence.rb'
89
87
  - 'test/controllers/api/recurring_logics_controller_test.rb'
90
88
  - 'test/controllers/api/tasks_controller_test.rb'
@@ -17,6 +17,10 @@ module ForemanTasks
17
17
  'foreman_tasks_recurring_logics'
18
18
  end
19
19
 
20
+ def resource_class
21
+ ::ForemanTasks::RecurringLogic
22
+ end
23
+
20
24
  private
21
25
 
22
26
  def find_recurring_logic
@@ -24,7 +28,8 @@ module ForemanTasks
24
28
  end
25
29
 
26
30
  def filter(scope)
27
- scope.search_for(params[:search]).paginate(:page => params[:page])
31
+ scope.search_for(params[:search])
32
+ .paginate(:page => params[:page], :per_page => params[:per_page])
28
33
  end
29
34
  end
30
35
  end
@@ -36,6 +36,16 @@ module ForemanTasks
36
36
  redirect_to :back
37
37
  end
38
38
 
39
+ def abort
40
+ task = find_dynflow_task
41
+ if task.abort
42
+ flash[:notice] = _('Trying to abort the task')
43
+ else
44
+ flash[:warning] = _('The task cannot be aborted at the moment.')
45
+ end
46
+ redirect_to :back
47
+ end
48
+
39
49
  def resume
40
50
  task = find_dynflow_task
41
51
  if task.resumable?
@@ -72,6 +82,10 @@ module ForemanTasks
72
82
  'foreman_tasks_tasks'
73
83
  end
74
84
 
85
+ def resource_class
86
+ ForemanTasks::Task
87
+ end
88
+
75
89
  private
76
90
 
77
91
  def restrict_dangerous_actions
@@ -86,7 +100,7 @@ module ForemanTasks
86
100
  case params[:action]
87
101
  when 'sub_tasks'
88
102
  :view
89
- when 'resume', 'unlock', 'force_unlock', 'cancel_step', 'cancel'
103
+ when 'resume', 'unlock', 'force_unlock', 'cancel_step', 'cancel', 'abort'
90
104
  :edit
91
105
  else
92
106
  super
@@ -103,7 +117,7 @@ module ForemanTasks
103
117
 
104
118
  def filter(scope)
105
119
  scope.search_for(params[:search], :order => params[:order])
106
- .paginate(:page => params[:page]).select('DISTINCT foreman_tasks_tasks.*')
120
+ .paginate(:page => params[:page], :per_page => params[:per_page]).distinct
107
121
  end
108
122
  end
109
123
  end
@@ -31,6 +31,8 @@ module Actions
31
31
  # do nothing
32
32
  when ::Dynflow::Action::Cancellable::Cancel
33
33
  cancel_proxy_task
34
+ when ::Dynflow::Action::Cancellable::Abort
35
+ abort_proxy_task
34
36
  when CallbackData
35
37
  on_data(event.data)
36
38
  when ::Dynflow::Action::Timeouts::Timeout
@@ -78,6 +80,11 @@ module Actions
78
80
  end
79
81
  end
80
82
 
83
+ def abort_proxy_task
84
+ proxy.cancel_task(output[:proxy_task_id])
85
+ error! ForemanTasks::Task::TaskCancelledException.new(_('Task aborted: the task might be still running on the proxy'))
86
+ end
87
+
81
88
  def on_resume
82
89
  # TODO: add logic to load the data from the external action
83
90
  suspend
@@ -9,8 +9,8 @@ module ForemanTasks
9
9
  self.external_id = data[:id]
10
10
  self.started_at = utc_zone.parse(data[:started_at]) unless data[:started_at].nil?
11
11
  self.ended_at = utc_zone.parse(data[:ended_at]) unless data[:ended_at].nil?
12
+ self.result = map_result(data).to_s
12
13
  self.state = data[:state].to_s
13
- self.result = map_result(data[:result])
14
14
  self.start_at = utc_zone.parse(data[:start_at]) if data[:start_at]
15
15
  self.start_before = utc_zone.parse(data[:start_before]) if data[:start_before]
16
16
  self.parent_task_id ||= begin
@@ -32,6 +32,10 @@ module ForemanTasks
32
32
  execution_plan!.cancel.any?
33
33
  end
34
34
 
35
+ def abort
36
+ execution_plan!.cancel(true).any?
37
+ end
38
+
35
39
  def resumable?
36
40
  execution_plan.try(:state) == :paused
37
41
  end
@@ -148,9 +152,20 @@ module ForemanTasks
148
152
 
149
153
  private
150
154
 
151
- def map_result(result)
152
- result = :cancelled if result == :error && cancelled?
153
- result.to_s
155
+ def map_result(data)
156
+ if state_result_transitioned?(%w[planned pending], %w[stopped error], data) ||
157
+ (data[:result] == :error && cancelled?)
158
+ :cancelled
159
+ else
160
+ data[:result]
161
+ end
162
+ end
163
+
164
+ def state_result_transitioned?(from, to, data)
165
+ oldstate, oldresult = from
166
+ newstate, newresult = to
167
+ state == oldstate && data[:state].to_s == newstate &&
168
+ result == oldresult && data[:result].to_s == newresult
154
169
  end
155
170
 
156
171
  def cancelled?
@@ -1,7 +1,9 @@
1
1
  module ForemanTasks
2
2
  class Task::Summarizer
3
3
  def summarize_by_status(since = nil)
4
- result = ::ForemanTasks::Task.select('count(state) AS count, state, result').group(:state, :result).order(:state)
4
+ result = ::ForemanTasks::Task.where("result <> 'success'")
5
+ .select('count(state) AS count, state, result, max(started_at) AS started_at')
6
+ .group(:state, :result).order(:state)
5
7
  result = result.where('started_at > ?', since) if since
6
8
  result
7
9
  end
@@ -1,7 +1,7 @@
1
1
  <div class="form-group ">
2
2
  <label class="col-md-2 control-label"><%= _('Schedule') %></label>
3
3
  <div class="col-md-6">
4
- <% javascript 'trigger_form' %>
4
+ <% javascript 'foreman_tasks/trigger_form' %>
5
5
  <% stylesheet 'foreman_tasks/trigger_form' %>
6
6
 
7
7
  <%= javascript_tag do %>
@@ -30,5 +30,4 @@
30
30
  <% end %>
31
31
  </table>
32
32
 
33
- <%= page_entries_info @recurring_logics %>
34
- <%= will_paginate @recurring_logics %>
33
+ <%= will_paginate_with_info @recurring_logics %>
@@ -11,7 +11,7 @@
11
11
  <td class="ellipsis"><%= link_to task.humanized[:action], defined?(main_app) ? main_app.foreman_tasks_task_path(task.id) : foreman_tasks_task_path(task.id) %></td>
12
12
  <td><%= task.state %></td>
13
13
  <td><%= task.result %></td>
14
- <td><%= _('%s ago') % time_ago_in_words(task.started_at) %></td>
14
+ <td><%= task.started_at ? (_('%s ago') % time_ago_in_words(task.started_at)) : _('N/A') %></td>
15
15
  </tr>
16
16
  <% end %>
17
17
  </table>
@@ -4,12 +4,14 @@
4
4
  <th><%= _("State") %></th>
5
5
  <th><%= _("Result") %></th>
6
6
  <th><%= _("No. of Tasks") %></th>
7
+ <th><%= _("Last start time") %></th>
7
8
  </tr>
8
9
  <% ForemanTasks::Task::Summarizer.new.summarize_by_status.each do |result| %>
9
10
  <tr class="<%= ForemanTasks::Task::StatusExplicator.new.is_erroneous(result) ? 'text-danger' : '' %>">
10
11
  <td><%= result.state %></td>
11
12
  <td><%= result.result %></td>
12
13
  <td><%= link_to result.count, main_app.foreman_tasks_tasks_path(:search => "state=#{result.state}&result=#{result.result}") %></td>
14
+ <td><%= result.started_at ? (_('%s ago') % time_ago_in_words(result.started_at)) : _('N/A') %></td>
13
15
  </tr>
14
16
  <% end %>
15
17
  </table>
@@ -38,5 +38,4 @@ $(document).on('click', ".table-two-pane td.two-pane-link", function(e) {
38
38
  </tr>
39
39
  <% end %>
40
40
  </table>
41
- <%= page_entries_info @tasks %>
42
- <%= will_paginate @tasks %>
41
+ <%= will_paginate_with_info @tasks %>
@@ -12,6 +12,7 @@ Foreman::Application.routes.draw do
12
12
  end
13
13
  member do
14
14
  get :sub_tasks
15
+ post :abort
15
16
  post :cancel
16
17
  post :resume
17
18
  post :unlock
@@ -29,10 +29,8 @@ same resource. It also optionally provides Dynflow infrastructure for using it f
29
29
  s.extra_rdoc_files = Dir['README*', 'LICENSE']
30
30
 
31
31
  s.add_dependency "foreman-tasks-core"
32
- s.add_dependency "dynflow", '~> 0.8.24'
33
- s.add_dependency "sequel" # for Dynflow process persistence
32
+ s.add_dependency "dynflow", '~> 0.8.26'
34
33
  s.add_dependency "sinatra" # for Dynflow web console
35
- s.add_dependency "daemons" # for running remote executor
36
34
  s.add_dependency "parse-cron", '~> 0.1.4'
37
35
  s.add_dependency "get_process_mem" # for memory polling
38
36
  end
@@ -2,6 +2,7 @@ require 'foreman_tasks/version'
2
2
  require 'foreman_tasks/task_error'
3
3
  require 'foreman_tasks/engine'
4
4
  require 'foreman_tasks/dynflow'
5
+ require 'foreman_tasks/dynflow/configuration'
5
6
  require 'foreman_tasks/triggers'
6
7
  require 'foreman_tasks/authorizer_ext'
7
8
  require 'foreman_tasks/cleaner'
@@ -11,7 +12,7 @@ module ForemanTasks
11
12
  extend Algebrick::Matching
12
13
 
13
14
  def self.dynflow
14
- @dynflow ||= ForemanTasks::Dynflow.new
15
+ @dynflow ||= ForemanTasks::Dynflow.new(nil, ForemanTasks::Dynflow::Configuration.new)
15
16
  end
16
17
 
17
18
  def self.trigger(action, *args, &block)
@@ -3,92 +3,9 @@ require 'dynflow'
3
3
 
4
4
  module ForemanTasks
5
5
  # Class for configuring and preparing the Dynflow runtime environment.
6
- class Dynflow
7
- require 'foreman_tasks/dynflow/configuration'
8
- require 'foreman_tasks/dynflow/persistence'
9
- require 'foreman_tasks/dynflow/daemon'
6
+ class Dynflow < ::Dynflow::Rails
10
7
  require 'foreman_tasks/dynflow/console_authorizer'
11
8
 
12
- def initialize(world_class = nil)
13
- @required = false
14
- @world_class = world_class
15
- end
16
-
17
- def config
18
- @config ||= ForemanTasks::Dynflow::Configuration.new
19
- end
20
-
21
- # call this method if your engine uses Dynflow
22
- def require!
23
- @required = true
24
- end
25
-
26
- def required?
27
- @required
28
- end
29
-
30
- def initialized?
31
- !@world.nil?
32
- end
33
-
34
- def initialize!
35
- return unless @required
36
- return @world if @world
37
-
38
- if config.lazy_initialization && defined?(PhusionPassenger)
39
- config.dynflow_logger.warn('ForemanTasks: lazy loading with PhusionPassenger might lead to unexpected results')
40
- end
41
- init_world.tap do |world|
42
- @world = world
43
-
44
- unless config.remote?
45
- # don't try to do any rescuing until the tables are properly migrated
46
- if !Foreman.in_rake?('db:migrate') && (begin
47
- ForemanTasks::Task.table_exists?
48
- rescue
49
- (false)
50
- end)
51
- config.run_on_init_hooks(world)
52
- # leave this just for long-running executors
53
- unless config.rake_task_with_executor?
54
- world.auto_execute
55
- ForemanTasks::Task::DynflowTask.consistency_check
56
- end
57
- end
58
- end
59
- end
60
- end
61
-
62
- # Mark that the process is executor. This prevents the remote setting from
63
- # applying. Needs to be set up before the world is being initialized
64
- def executor!
65
- @executor = true
66
- end
67
-
68
- def executor?
69
- @executor
70
- end
71
-
72
- def reinitialize!
73
- @world = nil
74
- initialize!
75
- end
76
-
77
- def world
78
- return @world if @world
79
-
80
- initialize! if config.lazy_initialization
81
- unless @world
82
- raise 'The Dynflow world was not initialized yet. '\
83
- 'If your plugin uses it, make sure to call ForemanTasks.dynflow.require! '\
84
- 'in some initializer'
85
- end
86
-
87
- @world
88
- end
89
-
90
- attr_writer :world
91
-
92
9
  def web_console
93
10
  ::Dynflow::Web.setup do
94
11
  before do
@@ -101,31 +18,8 @@ module ForemanTasks
101
18
  set(:custom_navigation) do
102
19
  { _('Back to tasks') => "/#{ForemanTasks::TasksController.controller_path}" }
103
20
  end
104
- set(:world) { ForemanTasks.dynflow.world }
21
+ set(:world) { Rails.application.dynflow.world }
105
22
  end
106
23
  end
107
-
108
- def eager_load_actions!
109
- config.eager_load_paths.each do |load_path|
110
- Dir.glob("#{load_path}/**/*.rb").sort.each do |file|
111
- unless loaded_paths.include?(file)
112
- require_dependency file
113
- loaded_paths << file
114
- end
115
- end
116
- end
117
- @world.reload! if @world
118
- end
119
-
120
- def loaded_paths
121
- @loaded_paths ||= Set.new
122
- end
123
-
124
- private
125
-
126
- def init_world
127
- return config.initialize_world(@world_class) if @world_class
128
- config.initialize_world
129
- end
130
24
  end
131
25
  end
@@ -1,146 +1,11 @@
1
- module ForemanTasks
2
- class Dynflow::Configuration
3
- # the number of threads in the pool handling the execution
4
- attr_accessor :pool_size
5
-
6
- # the size of db connection pool
7
- attr_accessor :db_pool_size
8
-
9
- # set true if the executor runs externally (by default true in procution, othewise false)
10
- attr_accessor :remote
11
- alias remote? remote
12
-
13
- # what transaction adapater should be used, by default, it uses the ActiveRecord
14
- # based adapter, expecting ActiveRecord is used as ORM in the application
15
- attr_accessor :transaction_adapter
16
-
17
- attr_accessor :eager_load_paths
18
-
19
- attr_accessor :lazy_initialization
20
-
21
- # what rake tasks should run their own executor, not depending on the external one
22
- attr_accessor :rake_tasks_with_executor
23
-
24
- # if true, the ForemanTasks::Concerns::ActionTriggering will make
25
- # no effect. Useful for testing, where we mignt not want to execute
26
- # the orchestration tied to the models.
27
- attr_accessor :disable_active_record_actions
28
-
29
- def initialize
30
- self.pool_size = 5
31
- self.db_pool_size = pool_size + 5
32
- self.remote = Rails.env.production?
33
- self.transaction_adapter = ::Dynflow::TransactionAdapters::ActiveRecord.new
34
- self.eager_load_paths = []
35
- self.lazy_initialization = !Rails.env.production?
36
- self.rake_tasks_with_executor = %w[db:migrate db:seed]
37
-
38
- @on_init = []
39
- end
1
+ # Require foreman's lib directory which contains foreman/dynflow/configuration
2
+ lib_foreman = File.expand_path('lib/foreman', Rails.root)
3
+ require lib_foreman if Dir.exist?(lib_foreman)
4
+ require 'foreman_tasks/dynflow/persistence'
40
5
 
41
- # for logging action related info (such as exceptions raised in side
42
- # the actions' methods
43
- def action_logger
44
- Foreman::Logging.logger('foreman-tasks/action')
45
- end
46
-
47
- # for logging dynflow related info about the progress of the execution etc.
48
- def dynflow_logger
49
- Foreman::Logging.logger('foreman-tasks/dynflow')
50
- end
51
-
52
- def on_init(&block)
53
- @on_init << block
54
- end
55
-
56
- def run_on_init_hooks(world)
57
- @on_init.each { |init| init.call(world) }
58
- end
59
-
60
- def initialize_world(world_class = ::Dynflow::World)
61
- world_class.new(world_config)
62
- end
63
-
64
- # No matter what config.remote says, when the process is marked as executor,
65
- # it can't be remote
66
- def remote?
67
- !ForemanTasks.dynflow.executor? &&
68
- !rake_task_with_executor? &&
69
- @remote
70
- end
71
-
72
- def rake_task_with_executor?
73
- return false unless defined?(Rake)
74
-
75
- Rake.application.top_level_tasks.any? do |rake_task|
76
- rake_tasks_with_executor.include?(rake_task)
77
- end
78
- end
79
-
80
- def increase_db_pool_size?
81
- ForemanTasks.dynflow.required? && !remote? && !Rails.env.test?
82
- end
83
-
84
- # To avoid pottential timeouts on db connection pool, make sure
85
- # we have the pool bigger than the thread pool
86
- def increase_db_pool_size
87
- if increase_db_pool_size?
88
- ActiveRecord::Base.connection_pool.disconnect!
89
-
90
- config = ActiveRecord::Base.configurations[Rails.env]
91
- config['pool'] = db_pool_size if config['pool'].to_i < db_pool_size
92
- ActiveRecord::Base.establish_connection(config)
93
- end
94
- end
95
-
96
- # generates the options hash consumable by the Dynflow's world
97
- def world_config
98
- ::Dynflow::Config.new.tap do |config|
99
- config.auto_rescue = true
100
- config.logger_adapter = ::Dynflow::LoggerAdapters::Delegator.new(action_logger, dynflow_logger)
101
- config.pool_size = 5
102
- config.persistence_adapter = initialize_persistence
103
- config.transaction_adapter = transaction_adapter
104
- config.executor = ->(world, _) { initialize_executor(world) }
105
- config.connector = ->(world, _) { initialize_connector(world) }
106
-
107
- # we can't do any operation until the ForemanTasks.dynflow.world is set
108
- config.auto_execute = false
109
- end
110
- end
111
-
112
- protected
113
-
114
- def default_sequel_adapter_options
115
- db_config = ActiveRecord::Base.configurations[Rails.env].dup
116
- db_config['adapter'] = 'postgres' if db_config['adapter'] == 'postgresql'
117
- db_config['max_connections'] = db_pool_size if increase_db_pool_size?
118
-
119
- if db_config['adapter'] == 'sqlite3'
120
- db_config['adapter'] = 'sqlite'
121
- database = db_config['database']
122
- unless database == ':memory:'
123
- # We need to create separate database for sqlite
124
- # to avoid lock conflicts on the database
125
- db_config['database'] = "#{File.dirname(database)}/dynflow-#{File.basename(database)}"
126
- end
127
- end
128
- db_config
129
- end
130
-
131
- def initialize_executor(world)
132
- if remote?
133
- false
134
- else
135
- ::Dynflow::Executors::Parallel.new(world, pool_size)
136
- end
137
- end
138
-
139
- def initialize_connector(world)
140
- ::Dynflow::Connectors::Database.new(world)
141
- end
142
-
143
- # Sequel adapter based on Rails app database.yml configuration
6
+ module ForemanTasks
7
+ # Import all Dynflow configuration from Foreman, and add our own for Tasks
8
+ class Dynflow::Configuration < ::Foreman::Dynflow::Configuration
144
9
  def initialize_persistence
145
10
  ForemanTasks::Dynflow::Persistence.new(default_sequel_adapter_options)
146
11
  end