Skip to content

Commit

Permalink
Merge pull request #44 from adamruzicka/recurring
Browse files Browse the repository at this point in the history
Refs #10755 - Recurring actions, job invocation task groups
  • Loading branch information
iNecas committed Dec 2, 2015
2 parents 67ea5b6 + 2fa2ed3 commit 03807a0
Show file tree
Hide file tree
Showing 23 changed files with 200 additions and 123 deletions.
5 changes: 0 additions & 5 deletions app/assets/javascripts/template_invocation.js
Expand Up @@ -58,11 +58,6 @@ function job_invocation_form_binds() {
$('#job_template_' + $(this).val()).show();
});

$('input.trigger_mode_selector').on('click', function () {
$("#trigger_mode_future").hide();
$('#trigger_mode_' + $(this).val()).show();
});

$('select#job_invocation_job_name').on('change', refresh_execution_form);

$('button#refresh_execution_form').on('click', refresh_execution_form);
Expand Down
21 changes: 11 additions & 10 deletions app/controllers/job_invocations_controller.rb
@@ -1,9 +1,13 @@
class JobInvocationsController < ApplicationController
include Foreman::Controller::AutoCompleteSearch

before_filter :find_or_create_triggering, :only => [:create, :refresh]

def new
@composer = JobInvocationComposer.new.compose_from_params(
:host_ids => params[:host_ids],
:job_invocation => {
},
:targeting => {
:targeting_type => Targeting::STATIC_TYPE,
:bookmark_id => params[:bookmark_id]
Expand All @@ -24,16 +28,9 @@ def rerun

def create
@composer = JobInvocationComposer.new.compose_from_params(params)
action = ::Actions::RemoteExecution::RunHostsJob
if @composer.save
job_invocation = @composer.job_invocation
if job_invocation.trigger_mode == :future
ForemanTasks.delay action,
job_invocation.delay_options,
job_invocation
else
ForemanTasks.async_task(action, job_invocation)
end
@composer.triggering.trigger(::Actions::RemoteExecution::RunHostsJob, job_invocation)
redirect_to job_invocation_path(job_invocation)
else
render :action => 'new'
Expand All @@ -42,13 +39,13 @@ def create

def show
@job_invocation = resource_base.find(params[:id])
@auto_refresh = @job_invocation.last_task.try(:pending?)
@auto_refresh = @job_invocation.task.try(:pending?)
hosts_base = @job_invocation.targeting.hosts.authorized(:view_hosts, Host)
@hosts = hosts_base.search_for(params[:search], :order => params[:order] || 'name ASC').paginate(:page => params[:page])
end

def index
@job_invocations = resource_base.search_for(params[:search]).paginate(:page => params[:page]).with_last_task.order(params[:order] || 'id DESC')
@job_invocations = resource_base.search_for(params[:search]).paginate(:page => params[:page]).with_last_task.order(params[:order] || '"job_invocations"."id" DESC')
end

# refreshes the form
Expand All @@ -69,6 +66,10 @@ def preview_hosts

private

def find_or_create_triggering
@triggering ||= ::ForemanTasks::Triggering.new_from_params(params[:triggering])
end

def action_permission
case params[:action]
when 'rerun'
Expand Down
30 changes: 16 additions & 14 deletions app/helpers/remote_execution_helper.rb
Expand Up @@ -10,7 +10,7 @@ def template_input_types_options
def job_invocation_chart(invocation)
options = { :class => 'statistics-pie small', :expandable => true, :border => 0, :show_title => true }

if (bulk_task = invocation.last_task)
if (bulk_task = invocation.task)
failed_tasks = bulk_task.sub_tasks.select { |sub_task| task_failed? sub_task }
cancelled_tasks, failed_tasks = failed_tasks.partition { |task| task_cancelled? task }
success = bulk_task.output['success_count'] || 0
Expand All @@ -30,15 +30,15 @@ def job_invocation_chart(invocation)
end

def job_invocation_status(invocation)
if invocation.last_task.blank?
if invocation.task.blank?
_('Job not started yet 0%')
elsif invocation.last_task.state == 'scheduled'
_('Job set to execute at %s') % invocation.last_task.start_at
elsif invocation.last_task.state == 'stopped' && invocation.last_task.result == 'error'
invocation.last_task.execution_plan.errors.map(&:message).join("\n")
elsif invocation.task.state == 'scheduled'
_('Job set to execute at %s') % invocation.task.start_at
elsif invocation.task.state == 'stopped' && invocation.task.result == 'error'
invocation.task.execution_plan.errors.map(&:message).join("\n")
else
label = invocation.last_task.pending ? _('Running') : _('Finished')
label + ' ' + (invocation.last_task.progress * 100).to_i.to_s + '%'
label = invocation.task.pending ? _('Running') : _('Finished')
label + ' ' + (invocation.task.progress * 100).to_i.to_s + '%'
end
end

Expand Down Expand Up @@ -92,15 +92,16 @@ def remote_execution_provider_for(task)

# rubocop:disable Metrics/AbcSize
def job_invocation_task_buttons(task)
job_invocation = task.task_groups.find { |group| group.class == JobInvocationTaskGroup }.job_invocation
buttons = []
buttons << link_to(_('Refresh'), {}, :class => 'btn btn-default', :title => _('Refresh this page'))
if authorized_for(hash_for_new_job_invocation_path)
buttons << link_to(_("Rerun"), rerun_job_invocation_path(:id => task.locks.where(:resource_type => 'JobInvocation').first.resource),
buttons << link_to(_("Rerun"), rerun_job_invocation_path(:id => job_invocation.id),
:class => "btn btn-default",
:title => _('Rerun the job'))
end
if authorized_for(hash_for_new_job_invocation_path)
buttons << link_to(_("Rerun failed"), rerun_job_invocation_path(:id => task.locks.where(:resource_type => 'JobInvocation').first.resource, :failed_only => 1),
buttons << link_to(_("Rerun failed"), rerun_job_invocation_path(:id => job_invocation.id, :failed_only => 1),
:class => "btn btn-default",
:disabled => !task.sub_tasks.any? { |sub_task| task_failed?(sub_task) },
:title => _('Rerun on failed hosts'))
Expand Down Expand Up @@ -139,18 +140,18 @@ def template_invocation_task_buttons(task)
end

def link_to_invocation_task_if_authorized(invocation)
if invocation.last_task.present? && invocation.last_task.state != 'scheduled'
if invocation.task.present? && invocation.task.state != 'scheduled'
link_to_if_authorized job_invocation_status(invocation),
hash_for_foreman_tasks_task_path(invocation.last_task).merge(:auth_object => invocation.last_task, :permission => :view_foreman_tasks)
hash_for_foreman_tasks_task_path(invocation.task).merge(:auth_object => invocation.task, :permission => :view_foreman_tasks)
else
job_invocation_status(invocation)
end
end

def invocation_count(invocation, options = {})
options = { :unknown_string => 'N/A' }.merge(options)
if invocation.last_task.nil? || invocation.last_task.state != 'scheduled'
(invocation.last_task.try(:output) || {}).fetch(options[:output_key], options[:unknown_string])
if invocation.task.nil? || invocation.task.state != 'scheduled'
(invocation.task.try(:output) || {}).fetch(options[:output_key], options[:unknown_string])
else
options[:unknown_string]
end
Expand Down Expand Up @@ -185,4 +186,5 @@ def time_ago(time)
{ :'data-original-title' => time.try(:in_time_zone), :rel => 'twipsy' }
end
end

end
8 changes: 6 additions & 2 deletions app/lib/actions/middleware/bind_job_invocation.rb
Expand Up @@ -4,7 +4,11 @@ module Middleware
class BindJobInvocation < ::Dynflow::Middleware

def delay(*args)
_schedule_options, job_invocation = args
schedule_options, job_invocation = args
if !job_invocation.task_id.nil? && job_invocation.task_id != task.id
job_invocation = job_invocation.deep_clone
args = [schedule_options, job_invocation]
end
pass(*args).tap { bind(job_invocation) }
end

Expand All @@ -20,7 +24,7 @@ def task
end

def bind(job_invocation)
job_invocation.update_attribute :last_task_id, task.id if job_invocation.last_task_id != task.id
job_invocation.update_attribute :task_id, task.id if job_invocation.task_id != task.id
end

end
Expand Down
9 changes: 6 additions & 3 deletions app/lib/actions/remote_execution/run_hosts_job.rb
Expand Up @@ -3,14 +3,17 @@ module RemoteExecution
class RunHostsJob < Actions::ActionWithSubPlans

middleware.use Actions::Middleware::BindJobInvocation
middleware.use Actions::Middleware::RecurringLogic

def delay(delay_options, job_invocation)
def delay(delay_options, job_invocation, locked = false)
task.add_missing_task_groups(job_invocation.task_group)
job_invocation.targeting.resolve_hosts! if job_invocation.targeting.static?
action_subject(job_invocation)
super(delay_options, job_invocation, true)
super delay_options, job_invocation, locked
end

def plan(job_invocation, locked = false, connection_options = {})
job_invocation.task_group.save! if job_invocation.task_group.try(:new_record?)
task.add_missing_task_groups(job_invocation.task_group) if job_invocation.task_group
action_subject(job_invocation) unless locked
job_invocation.targeting.resolve_hosts! if job_invocation.targeting.dynamic? || !locked
input.update(:job_name => job_invocation.job_name)
Expand Down
Expand Up @@ -3,7 +3,7 @@ module ForemanTasksTaskExtensions
extend ActiveSupport::Concern

included do
has_many :job_invocations, :dependent => :nullify, :foreign_key => 'last_task_id'
has_many :job_invocations, :dependent => :nullify, :foreign_key => 'task_id'
end
end
end
@@ -0,0 +1,9 @@
module ForemanRemoteExecution
module ForemanTasksTriggeringExtensions
extend ActiveSupport::Concern

included do
has_one :job_invocation, :dependent => :nullify, :foreign_key => 'triggering_id'
end
end
end
56 changes: 16 additions & 40 deletions app/models/job_invocation.rb
Expand Up @@ -15,27 +15,39 @@ class JobInvocation < ActiveRecord::Base

scoped_search :on => :job_name

scoped_search :in => :recurring_logic, :on => 'id', :rename => 'recurring_logic.id', :auto_complete => true

delegate :bookmark, :resolved?, :to => :targeting, :allow_nil => true

include ForemanTasks::Concerns::ActionSubject

belongs_to :last_task, :class_name => 'ForemanTasks::Task'
has_many :sub_tasks, :through => :last_task
belongs_to :task, :class_name => 'ForemanTasks::Task'
has_many :sub_tasks, :through => :task

belongs_to :task_group, :class_name => 'JobInvocationTaskGroup'

has_many :tasks, :through => :task_group

scoped_search :on => [:job_name], :complete_value => true

scoped_search :in => :last_task, :on => :started_at, :rename => 'started_at', :complete_value => true
scoped_search :in => :last_task, :on => :ended_at, :rename => 'ended_at', :complete_value => true

belongs_to :triggering, :class_name => 'ForemanTasks::Triggering'
has_one :recurring_logic, :through => :triggering, :class_name => 'ForemanTasks::RecurringLogic'

scope :with_last_task, -> { joins('LEFT JOIN "foreman_tasks_tasks" ON "foreman_tasks_tasks"."id" = "job_invocations"."last_task_id"') }

attr_accessor :start_before
attr_writer :start_at

def self.allowed_trigger_modes
%w(immediate future)
def deep_clone
JobInvocationComposer.new.compose_from_invocation(self).job_invocation.tap do |invocation|
invocation.task_group = JobInvocationTaskGroup.new.tap(&:save!)
invocation.triggering = self.triggering
invocation.template_invocations = self.template_invocations.map(&:deep_clone)
invocation.save!
end
end

def to_action_input
Expand Down Expand Up @@ -70,42 +82,6 @@ def total_hosts_count
end
end

def delay_options
{
:start_at => start_at_parsed,
:start_before => start_before_parsed
}
end

def trigger_mode
@trigger_mode || :immediate
end

def trigger_mode=(value)
return trigger_mode if @trigger_mode || value.nil?
if JobInvocation.allowed_trigger_modes.include?(value)
@trigger_mode = value.to_sym
else
raise ::Foreman::Exception, _("Job Invocation trigger mode must be one of [#{JobInvocation.allowed_trigger_modes.join(', ')}]")
end
end

def start_at_parsed
@start_at.present? && Time.strptime(@start_at, time_format)
end

def start_at
@start_at ||= Time.now.strftime(time_format)
end

def start_before_parsed
@start_before.present? && Time.strptime(@start_before, time_format) || nil
end

def time_format
'%Y-%m-%d %H:%M'
end

def template_invocation_for_host(host)
providers = available_providers(host)
providers.each do |provider|
Expand Down
18 changes: 12 additions & 6 deletions app/models/job_invocation_composer.rb
@@ -1,5 +1,5 @@
class JobInvocationComposer
attr_accessor :params, :job_invocation, :host_ids, :search_query
attr_accessor :params, :job_invocation, :host_ids, :search_query, :triggering
attr_reader :job_template_ids
delegate :job_name, :targeting, :to => :job_invocation

Expand All @@ -13,12 +13,13 @@ def compose_from_params(params)
@host_ids = validate_host_ids(params[:host_ids])
@search_query = targeting_base[:search_query]

@triggering = ::ForemanTasks::Triggering.new_from_params triggering_base

job_invocation.job_name = validate_job_name(job_invocation_base[:job_name])
job_invocation.job_name ||= available_job_names.first if job_invocation.new_record?
job_invocation.targeting = build_targeting
job_invocation.trigger_mode = job_invocation_base[:trigger_mode]
job_invocation.start_at = job_invocation_base[:start_at]
job_invocation.start_before = job_invocation_base[:start_before]
job_invocation.task_group = JobInvocationTaskGroup.new
job_invocation.triggering = @triggering

@job_template_ids = validate_job_template_ids(job_templates_base.keys.compact)
self
Expand All @@ -29,6 +30,7 @@ def compose_from_invocation(invocation)

job_invocation.job_name = validate_job_name(invocation.job_name)
job_invocation.targeting = invocation.targeting.dup
@triggering = job_invocation.triggering = ::ForemanTasks::Triggering.new_from_params(@params)
@search_query = targeting.search_query unless targeting.bookmark_id.present?

@job_template_ids = invocation.template_invocations.map(&:template_id)
Expand All @@ -37,11 +39,11 @@ def compose_from_invocation(invocation)
end

def valid?
targeting.valid? & job_invocation.valid? & !template_invocations.map(&:valid?).include?(false)
triggering.valid? & targeting.valid? & job_invocation.valid? & !template_invocations.map(&:valid?).include?(false)
end

def save
valid? && job_invocation.save
valid? && triggering.save && job_invocation.save
end

def available_templates
Expand Down Expand Up @@ -160,6 +162,10 @@ def job_invocation_base
@params.fetch(:job_invocation, {})
end

def triggering_base
@params.fetch(:triggering, {})
end

def input_values_base
@params.fetch(:input_values, [])
end
Expand Down
18 changes: 18 additions & 0 deletions app/models/job_invocation_task_group.rb
@@ -0,0 +1,18 @@
class JobInvocationTaskGroup < ::ForemanTasks::TaskGroup

has_one :job_invocation, :foreign_key => :task_group_id

alias_method :resource, :job_invocation

def resource_name
N_('Job Invocation')
end

def self.search_query_for(thing)
case thing
when ::ForemanTasks::RecurringLogic
"recurring_logic.id = #{thing.id}"
end
end

end
13 changes: 13 additions & 0 deletions app/models/targeting.rb
Expand Up @@ -20,6 +20,19 @@ class Targeting < ActiveRecord::Base

before_create :assign_search_query, :if => :static?

def clone
if static?
self.dup
else
Targeting.new(
:user => self.user,
:bookmark_id => self.bookmark.try(:id),
:targeting_type => self.targeting_type,
:search_query => self.search_query
)
end.tap(&:save)
end

def resolve_hosts!
raise ::Foreman::Exception, _('Cannot resolve hosts without a user') if user.nil?
raise ::Foreman::Exception, _('Cannot resolve hosts without a bookmark or search query') if bookmark.nil? && search_query.blank?
Expand Down

0 comments on commit 03807a0

Please sign in to comment.