Project

General

Profile

« Previous | Next » 

Revision 5613fbe9

Added by Justin Sherrill almost 10 years ago

fixes #4989,6734,6735 - adapting sync management page to use dynflow

Note that some columns in the sync management table
have changed to reflect difficulty of fetching some data.

View differences:

app/assets/javascripts/katello/sync_management/sync_management.js
$("#sync_toggle_cont").find("img").remove();
});
$('.info-tipsy').tipsy({
html: true,
gravity: 's',
className: 'error-tipsy',
title: function() {
return $(this).find('.hidden-text').html();
}
});
});
KT.content_actions = (function(){
......
// Only stop when we reach 100% and the finish_time is done sometimes they are not both complete
if (!repo.is_running && (repo.raw_state !== 'waiting')) {
removeSyncing(repo.id);
KT.content.finishRepo(repo.id, repo.state, repo.duration, repo.raw_state, repo.error_details);
KT.content.updateRepo(repo.id, repo.start_time, repo.duration, repo.progress.progress, repo.display_size, repo.packages, repo.size);
KT.content.updateRepo(repo.id, repo.start_time, repo.duration, repo.progress.progress, repo.display_size, repo.packages, repo.size, repo.sync_id);
KT.content.finishRepo(repo.id, repo.state, repo.duration, repo.raw_state, repo.error_details, repo.sync_id);
KT.content.updateProduct(repo.product_id, false, false, true);
notices.checkNotices();
}
......
repo.duration,
repo.progress.progress,
repo.display_size,
repo.packages);
repo.packages,
repo.sync_id);
}
});
KT.content.reset_products(data);
......
element.find('.result-info').append(cancelButton);
}
},
updateRepo = function(repo_id, starttime, duration, progress, display_size, packages, size){
updateRepo = function(repo_id, starttime, duration, progress, display_size, packages, size, task_id){
var repo = $("#repo-" + repo_id);
update_item(repo, starttime, duration, progress, display_size, packages, size );
update_item(repo, starttime, duration, progress, display_size, packages, size, task_id);
},
finishRepo = function(repo_id, state, duration, raw_state, error_details){
finishRepo = function(repo_id, state, duration, raw_state, error_details, task_id){
var element = $("#repo-" + repo_id);
var messages = [];
state = '<a href="/foreman_tasks/tasks/' + task_id + '">' + state + '</a>';
element.find(".result .result-info").html(state);
fadeUpdate(element.find(".duration"), duration);
......
element.find('.result .info-tipsy ul').html(messages.join(''));
}
},
update_item = function(element, starttime, duration, progress, display_size, packages, size) {
update_item = function(element, starttime, duration, progress, display_size, packages, size, task_id) {
var pg = element.find(".progress"),
value = pg.find(".ui-progressbar-value");
......
fadeUpdate(element.find(".start_time"), starttime);
// clear duration during active sync
fadeUpdate(element.find(".duration"), '');
fadeUpdate(element.find(".size"), display_size + ' (' + packages + ')');
fadeUpdate(element.find(".size"), display_size);
element.find('.size').data('size', size);
element.find('.info-tipsy').attr('href', '/foreman_tasks/tasks/' + task_id);
progress = progress === 100 ? 99 : progress;
value.animate({'width': progress },{ queue:false,
duration:"slow", easing:"easeInSine" });
app/assets/stylesheets/katello/contents.scss
text-align: left;
bottom: 18px;
left: 30px;
display: block;
}
.progress {
display: inline-block;
app/controllers/katello/sync_management_controller.rb
include SyncManagementHelper::RepoMethods
respond_to :html, :json
# to avoid problems when generating Headpin documenation when
# building RPMs
if Katello.config.use_pulp
@@status_values = { PulpSyncStatus::Status::WAITING => _("Queued."),
PulpSyncStatus::Status::FINISHED => _("Sync complete."),
PulpSyncStatus::Status::ERROR => _("Error syncing!"),
PulpSyncStatus::Status::RUNNING => _("Running."),
PulpSyncStatus::Status::CANCELED => _("Canceled."),
PulpSyncStatus::Status::NOT_SYNCED => ""}
else
@@status_values = {}
end
@@status_values = { :stopped => _("Syncing Complete."),
:error => _("Sync Incomplete"),
:never_synced => _("Never Synced"),
:paused => _("Paused")}.with_indifferent_access
def section_id
'contents'
......
end
def sync
ids = sync_repos(params[:repoids]) || {}
render :json => ids.to_json
tasks = sync_repos(params[:repoids]) || []
render :json => tasks.as_json
end
def sync_status
collected = []
repos = Repository.where(:id => params[:repoids]).readable
repos.each do |repo|
begin
progress = format_sync_progress(repo.sync_status, repo)
collected.push(progress)
rescue ActiveRecord::RecordNotFound => e
notify.exception e # debugging and skip for now
next
end
end
render :json => collected.to_json
statuses = repos.map{ |repo| format_sync_progress(repo) }
render :json => statuses.flatten.to_json
end
def destroy
Repository.find(params[:id]).cancel_sync
repo = Repository.where(:id => params[:id]).syncable.first
repo.cancel_dynflow_sync if repo
render :text => ""
end
private
def format_sync_progress(sync_status, repo)
progress = sync_status.progress
error_details = progress.error_details
def format_sync_progress(repo)
task = latest_task(repo)
if task
{ :id => repo.id,
:product_id => repo.product.id,
:progress => {:progress => task.progress * 100},
:sync_id => task.id,
:state => format_state(task),
:raw_state => raw_state(task),
:start_time => format_date(task.started_at),
:finish_time => format_date(task.ended_at),
:duration => format_duration(task.ended_at, task.started_at),
:display_size => task.humanized[:output],
:size => task.humanized[:output],
:is_running => task.pending && task.state != 'paused',
:error_details => task.errors
}
else
empty_task(repo)
end
end
not_running_states = [PulpSyncStatus::Status::FINISHED,
PulpSyncStatus::Status::ERROR,
PulpSyncStatus::Status::WAITING,
PulpSyncStatus::Status::CANCELED,
PulpSyncStatus::Status::NOT_SYNCED]
def empty_task(repo)
state = 'never_synced'
{ :id => repo.id,
:product_id => repo.product.id,
:progress => calc_progress(sync_status, repo.content_type),
:sync_id => sync_status.uuid,
:state => format_state(sync_status.state),
:raw_state => sync_status.state,
:start_time => format_date(sync_status.start_time),
:finish_time => format_date(sync_status.finish_time),
:duration => format_duration(sync_status.finish_time, sync_status.start_time),
:packages => sync_status.progress.total_count,
:display_size => number_to_human_size(sync_status.progress.total_size),
:size => sync_status.progress.total_size,
:is_running => !not_running_states.include?(sync_status.state.to_sym),
:error_details => error_details ? error_details : "No errors."
:progress => {},
:state => format_state(OpenStruct.new(:state => state)),
:raw_state => state
}
end
def format_state(state)
@@status_values[state.to_sym]
def raw_state(task)
if task.result == 'error' || task.result == 'warning'
return 'error'
else
task.state
end
end
def format_state(task)
@@status_values[raw_state(task)] || task.state
end
def format_duration(finish, start)
......
retval
end
def latest_task(repo)
repo.latest_dynflow_sync
end
# loop through checkbox list of products and sync
def sync_repos(repos)
repos = [repos] if !repos.is_a? Array
def sync_repos(repo_ids)
collected = []
repos = Repository.where(:id => repos).syncable
repos = Repository.where(:id => repo_ids).syncable
repos.each do |repo|
begin
resp = repo.sync(:notify => true).first
collected.push(:id => repo.id, :product_id => repo.product.id, :state => resp[:state])
rescue RestClient::Conflict
if latest_task(repo).try(:state) != 'running'
ForemanTasks.async_task(::Actions::Katello::Repository::Sync, repo)
collected << format_sync_progress(repo)
else
notify.error N_("There is already an active sync process for the '%s' repository. Please try again later") %
repo.name
end
......
collected
end
# calculate the % complete of ongoing sync from pulp
def calc_progress(val, content_type)
if content_type == Repository::PUPPET_TYPE
completed = val.progress.total_count - val.progress.items_left
progress = if val.state =~ /error/i then -1
elsif val.progress.total_count == 0 then 0
else completed.to_f / val.progress.total_count.to_f * 100
end
else
completed = val.progress.total_size - val.progress.size_left
progress = if val.state =~ /error/i then -1
elsif val.progress.total_size == 0 then 0
else completed.to_f / val.progress.total_size.to_f * 100
end
end
{:count => val.progress.total_count,
:left => val.progress.items_left,
:progress => progress
}
end
def get_product_info(product)
product_size = 0
product.repos(product.organization.library).each do |repo|
status = repo.sync_status
@repo_status[repo.id] = format_sync_progress(status, repo)
product_size += status.progress.total_size
@repo_status[repo.id] = format_sync_progress(repo)
end
@product_size[product.id] = number_to_human_size(product_size)
end
end
end
app/lib/actions/elastic_search/repository/index_content.rb
input_format do
param :id, Integer
param :dependency, Hash
end
def run
app/lib/actions/katello/repository/node_metadata_generate.rb
module Repository
class NodeMetadataGenerate < Actions::Base
def plan(repo)
def plan(repo, dependency = nil)
return unless repo.environment
sequence do
unless repo.puppet? && repo.content_view.default?
plan_action(Pulp::Repository::DistributorPublish,
pulp_id: repo.pulp_id,
dependency: dependency,
distributor_type_id: Runcible::Models::NodesHttpDistributor.type_id)
end
concurrence do
app/lib/actions/katello/repository/sync.rb
input_format do
param :id, Integer
param :sync_result, Hash
end
def plan(repo)
sync_task = nil
action_subject(repo)
if repo.url.blank?
......
end
sequence do
plan_action(Pulp::Repository::Sync, pulp_id: repo.pulp_id)
sync_task = plan_action(Pulp::Repository::Sync, pulp_id: repo.pulp_id)
concurrence do
plan_action(Katello::Repository::NodeMetadataGenerate, repo)
plan_action(ElasticSearch::Repository::IndexContent, id: repo.id)
plan_action(Katello::Repository::NodeMetadataGenerate, repo, sync_task.output[:pulp_tasks])
plan_action(ElasticSearch::Repository::IndexContent, dependency: sync_task.output[:pulp_tasks], id: repo.id)
end
plan_action(ElasticSearch::Reindex, repo)
plan_action(Katello::Foreman::ContentUpdate, repo.environment, repo.content_view)
end
plan_self(:sync_result => sync_task.output)
end
def run
output[:sync_result] = input[:sync_result]
end
def humanized_name
app/lib/actions/pulp/abstract_async_task.rb
def cancel!
output[:pulp_tasks].each do |pulp_task|
task_resource.cancel(pulp_task['task_id'])
if pulp_task['spawned_tasks']
#the main task may have completed, so cancel spawned tasks too
pulp_task['spawned_tasks'].each{|spawned| task_resource.cancel(spawned['task_id'])}
end
end
self.external_task = poll_external_task
# We suspend the action and the polling will take care of finding
app/lib/actions/pulp/repository/sync.rb
def humanized_details
ret = []
if content_details && content_details[:state] != 'NOT_STARTED'
ret << _("Cancelled.") if cancelled?
if pending?
ret << _("Pending")
elsif content_started?
if items_total > 0
ret << (_("New packages: %s (%s)") % [count_summary, size_summary])
ret << (_("New packages: %s (%s).") % [count_summary, size_summary])
else
ret << _("No new packages")
#if there are no new packages, it could just mean that they have not started downloading yet
# so only tell the user no new packages if errata have been processed
if errata_details && errata_details['state'] != 'NOT_STARTED'
ret << _("No new packages.")
else
ret << _("Processing metadata.")
end
end
end
if metadata_details && metadata_details[:state] == 'IN_PROGRESS'
elsif metadata_in_progress?
ret << _("Processing metadata")
end
return ret.join("\n")
ret << metadata_error if metadata_error
if error_details.any?
ret << n_("Failed to download %s package.", "Failed to download %s packages.",
error_details.count) % error_details.count
end
ret.join("\n")
end
def count_summary
......
task_details && task_details[:content]
end
def error_details
content_details.nil? ? [] : content_details[:error_details]
end
def metadata_details
task_details && task_details[:metadata]
end
def errata_details
task_details && task_details[:errata]
end
def items_done
items_total - content_details[:items_left]
end
......
(content_details && content_details[:size_total]).to_i
end
def cancelled?
task_details.nil? ? false : task_details.values.map{|item| item['state']}.include?('CANCELLED')
end
def content_started?
content_details && content_details[:state] != 'NOT_STARTED'
end
def metadata_in_progress?
metadata_details && metadata_details[:state] == 'IN_PROGRESS'
end
def metadata_error
metadata_details && metadata_details[:error]
end
def pending?
metadata_details.nil? || metadata_details['state'] == 'NOT_RUNNING'
end
end
end
app/lib/katello/errors.rb
end
class PulpError < StandardError
# Return a CandlepinError with the displayMessage
# as the message set if
def self.from_task(task)
if task[:state] == 'error' || task[:state] == 'canceled'
if task[:state] == 'error'
message = if task[:exception]
Array(task[:exception]).join('; ')
elsif task[:error]
"#{task[:error][:code]}: #{task[:error][:description]}"
elsif task[:state] == 'canceled'
_("Task canceled")
else
_("Pulp task error")
end
app/models/katello/repository.rb
end
def cancel_dynflow_sync
if latest_dynflow_sync
plan = latest_dynflow_sync.execution_plan
plan.steps.each_pair do |number, step|
if step.cancellable? && step.is_a?(Dynflow::ExecutionPlan::Steps::RunStep)
::ForemanTasks.dynflow.world.event(plan.id, step.id, Dynflow::Action::Cancellable::Cancel)
end
end
end
end
def latest_dynflow_sync
ForemanTasks::Task::DynflowTask.for_action(::Actions::Katello::Repository::Sync).
for_resource(self).order(:started_at).last
end
def create_clone(options)
clone = build_clone(options)
clone.save!
app/views/katello/sync_management/_product.html.haml
= "(Orphaned)" if prod[:object].orphaned?
%td.max_width.start_time
%td.max_width.duration
%td.size= @product_size[pid]
%td
%td.result{:id =>'table_#{pid}'}
- if @show_org
%td= prod[:organization]
app/views/katello/sync_management/_products.html.haml
%th= _("Product")
%th.max_width= _("Start Time")
%th.max_width= _("Duration")
%th= _("Size (Packages)")
%th= _("Details")
%th= _("Result")
- if @show_org
%th= _("Organization")
app/views/katello/sync_management/_repo.html.haml
#{@repo_status[repo.id][:duration]}
%td.size{ "data-size" => @repo_status[repo.id][:size] }
#{@repo_status[repo.id][:display_size]}
(#{@repo_status[repo.id][:packages]})
%td.result
%a.info-tipsy.clickable.icon-warning-sign{ "class" => "#{"hidden" if !error_state?(@repo_status[repo.id])}", :href => notices_path }
%a.info-tipsy.clickable.icon-warning-sign{ "class" => "#{"hidden" if @repo_status[repo.id][:raw_state] != 'error'}",
:href => "/foreman_tasks/tasks/#{@repo_status[repo.id][:sync_id]}"}
%span.hidden-text.hidden
.la.error-tipsy
%ul
......
- @repo_status[repo.id][:error_details][:messages].each do |error|
%li #{error}
%span.result-info
#{@repo_status[repo.id][:state]}
%a{:href => "/foreman_tasks/tasks/#{@repo_status[repo.id][:sync_id]}"}
#{@repo_status[repo.id][:state]}
- if @show_org
%td
test/actions/katello/repository_test.rb
plan_action action, repository
assert_action_planed_with action, pulp_action_class, pulp_id: repository.pulp_id
assert_action_planed_with action, ::Actions::ElasticSearch::Repository::IndexContent, id: repository.id
assert_action_planed action, ::Actions::ElasticSearch::Repository::IndexContent
assert_action_planed_with action, ::Actions::ElasticSearch::Reindex, repository
end
......
let(:fixture_variant) { :success }
specify do
action.humanized_output.must_equal "New packages: 32 (76.7 KB)"
action.humanized_output.must_equal "New packages: 32 (76.7 KB)."
end
end
......
let(:fixture_variant) { :success_no_packages }
specify do
action.humanized_output.must_equal "No new packages"
action.humanized_output.must_equal "No new packages."
end
end
......
let(:fixture_variant) { :progress_packages }
specify do
action.humanized_output.must_equal "New packages: 20/32 (48 KB/76.7 KB)"
action.humanized_output.must_equal "New packages: 20/32 (48 KB/76.7 KB)."
end
specify do
test/controllers/sync_management_controller_test.rb
def test_sync_status
@request.env['HTTP_ACCEPT'] = 'application/json'
@request.env['CONTENT_TYPE'] = 'application/json'
Repository.any_instance.expects(:sync_status).returns({})
@controller.expects(:format_sync_progress).returns({})
get :sync_status, :repoids => [@repository.id]
......
end
def test_destroy
Repository.any_instance.expects(:cancel_sync)
Repository.any_instance.expects(:cancel_dynflow_sync)
delete :destroy, :id => @repository.id
assert_response :success

Also available in: Unified diff