Revision 5613fbe9
Added by Justin Sherrill almost 10 years ago
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
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.