foreman-tasks 0.10.0 → 0.10.1

Sign up to get free protection for your applications and to get access to all the features.
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