diff --git a/app/controllers/katello/api/v2/capsule_content_controller.rb b/app/controllers/katello/api/v2/capsule_content_controller.rb index 3eae4ae9ef0..106d4c272b7 100644 --- a/app/controllers/katello/api/v2/capsule_content_controller.rb +++ b/app/controllers/katello/api/v2/capsule_content_controller.rb @@ -112,6 +112,30 @@ def reclaim_space respond_for_async :resource => task end + api :POST, '/capsules/:id/content/verify_checksum', N_('Check for missing or corrupted artifacts, and attempt to redownload them.') + param :id, :number, :required => true, :desc => N_('Id of the smart proxy') + param :environment_id, Integer, :desc => N_('Id of the environment to limit verifying checksum on') + param :content_view_id, Integer, :desc => N_('Id of the content view to limit verifying checksum on') + param :repository_id, Integer, :desc => N_('Id of the repository to limit verifying checksum on') + def verify_checksum + find_capsule(false) + find_environment if params[:environment_id] + find_content_view if params[:content_view_id] + find_repository if params[:repository_id] + + repair_options = { + :environment_id => @environment.try(:id), + :content_view_id => @content_view.try(:id), + :repository_id => @repository.try(:id) + } + repair_options[:environment_ids] = @capsule.lifecycle_environments&.pluck(:id) unless (@environment || @content_view || @repository) + + task = async_task(::Actions::Katello::CapsuleContent::VerifyChecksum, + @capsule, + repair_options) + respond_for_async :resource => task + end + protected def respond_for_lifecycle_environments_index(environments) diff --git a/app/lib/actions/katello/capsule_content/verify_checksum.rb b/app/lib/actions/katello/capsule_content/verify_checksum.rb new file mode 100644 index 00000000000..9f430a19ec4 --- /dev/null +++ b/app/lib/actions/katello/capsule_content/verify_checksum.rb @@ -0,0 +1,75 @@ +module Actions + module Katello + module CapsuleContent + class VerifyChecksum < ::Actions::EntryAction + def humanized_name + _("Verify checksum for content on smart proxy") + end + + def plan(smart_proxy, options = {}) + input[:options] = options + action_subject(smart_proxy) + fail _("Action not allowed for the default smart proxy.") if smart_proxy.pulp_primary? + subjects = subjects(options) + repair_options = options.merge(subjects) + environment = repair_options[:environment] + content_view = repair_options[:content_view] + check_cv_capsule_environments!(smart_proxy, content_view, environment) + repository = repair_options[:repository] + repos = repos_to_repair(smart_proxy, environment, content_view, repository) + repos.in_groups_of(Setting[:foreman_proxy_content_batch_size], false) do |repo_batch| + concurrence do + repo_batch.each do |repo| + if smart_proxy.pulp3_support?(repo) + plan_action(Actions::Pulp3::CapsuleContent::VerifyChecksum, + repo, + smart_proxy) + end + end + end + end + end + + def repos_to_repair(smart_proxy, environment, content_view, repository) + smart_proxy_helper = ::Katello::SmartProxyHelper.new(smart_proxy) + smart_proxy_helper.lifecycle_environment_check(environment, repository) + if repository + [repository] + else + repositories = smart_proxy_helper.repositories_available_to_capsule(environment, content_view).by_rpm_count + repositories + end + end + + def check_cv_capsule_environments!(smart_proxy, content_view, environment) + cv_environments = content_view&.versions&.collect(&:environments)&.flatten + if cv_environments.present? + if environment.present? && !(cv_environments.pluck(:id).include? environment.id) + fail _("Content view '%{content_view}' is not attached to the environment.") % {content_view: content_view.name} + end + if (smart_proxy.lifecycle_environments.pluck(:id) & cv_environments.pluck(:id)).empty? + fail _("Content view '%{content_view}' is not attached to this capsule.") % {content_view: content_view.name} + end + end + end + + def subjects(options = {}) + environment_id = options.fetch(:environment_id, nil) + environment = ::Katello::KTEnvironment.find(environment_id) if environment_id + + repository_id = options.fetch(:repository_id, nil) + repository = ::Katello::Repository.find(repository_id) if repository_id + + content_view_id = options.fetch(:content_view_id, nil) + content_view = ::Katello::ContentView.find(content_view_id) if content_view_id + + {content_view: content_view, environment: environment, repository: repository} + end + + def rescue_strategy + Dynflow::Action::Rescue::Skip + end + end + end + end +end diff --git a/app/lib/actions/pulp3/capsule_content/verify_checksum.rb b/app/lib/actions/pulp3/capsule_content/verify_checksum.rb new file mode 100644 index 00000000000..a24fbf9ff91 --- /dev/null +++ b/app/lib/actions/pulp3/capsule_content/verify_checksum.rb @@ -0,0 +1,27 @@ +module Actions + module Pulp3 + module CapsuleContent + class VerifyChecksum < Pulp3::AbstractAsyncTask + def plan(repository, smart_proxy) + plan_self(:repository_id => repository.id, :smart_proxy_id => smart_proxy.id) + end + + def invoke_external_task + repo = ::Katello::Repository.find(input[:repository_id]) + output[:pulp_tasks] = repo.backend_service(smart_proxy).with_mirror_adapter.repair + end + + def repos_to_repair(smart_proxy, environment, content_view, repository) + smart_proxy_helper = ::Katello::SmartProxyHelper.new(smart_proxy) + smart_proxy_helper.lifecycle_environment_check(environment, repository) + if repository + [repository] + else + repositories = smart_proxy_helper.repositories_available_to_capsule(environment, content_view).by_rpm_count + repositories + end + end + end + end + end +end diff --git a/app/services/katello/pulp3/api/core.rb b/app/services/katello/pulp3/api/core.rb index 9153fb7c96a..6a9100c11b6 100644 --- a/app/services/katello/pulp3/api/core.rb +++ b/app/services/katello/pulp3/api/core.rb @@ -136,6 +136,10 @@ def api_client_class(client) client end + def repair_api + PulpcoreClient::RepairApi.new(core_api_client) + end + def uploads_api PulpcoreClient::UploadsApi.new(core_api_client) end @@ -230,6 +234,10 @@ def remotes_list_all(_smart_proxy, options = {}) end end + def repair + repair_api.post(PulpcoreClient::Repair.new(verify_checksums: true)) + end + def self.fetch_from_list page_size = Setting[:bulk_load_size] page_opts = { "offset" => 0, limit: page_size } diff --git a/app/services/katello/pulp3/repository_mirror.rb b/app/services/katello/pulp3/repository_mirror.rb index 5f01be34994..d2e3ce353aa 100644 --- a/app/services/katello/pulp3/repository_mirror.rb +++ b/app/services/katello/pulp3/repository_mirror.rb @@ -240,6 +240,12 @@ def create_distribution(path) distribution_data = api.distribution_class.new(distribution_options(path)) repo_service.distributions_api.create(distribution_data) end + + def repair + data = api.repair_class.new + fail "Could not lookup a version_href for repo #{repo_service.repo.id}" if version_href.nil? + api.repository_versions_api.repair(version_href, data) + end end end end diff --git a/config/routes/api/v2.rb b/config/routes/api/v2.rb index 187faa1122e..fd5b797cfe2 100644 --- a/config/routes/api/v2.rb +++ b/config/routes/api/v2.rb @@ -29,6 +29,7 @@ class ActionDispatch::Routing::Mapper get :sync, :action => :sync_status delete :sync, :action => :cancel_sync post :reclaim_space + post :verify_checksum post '/lifecycle_environments' => 'capsule_content#add_lifecycle_environment' delete '/lifecycle_environments/:environment_id' => 'capsule_content#remove_lifecycle_environment' end diff --git a/lib/katello/permission_creator.rb b/lib/katello/permission_creator.rb index ff2f58fd378..c6e670024b9 100644 --- a/lib/katello/permission_creator.rb +++ b/lib/katello/permission_creator.rb @@ -54,7 +54,7 @@ def capsule_content_permissions @plugin.permission :manage_capsule_content, { 'katello/api/v2/capsule_content' => [:add_lifecycle_environment, :remove_lifecycle_environment, - :update_counts, :sync, :reclaim_space, :cancel_sync], + :update_counts, :sync, :reclaim_space, :verify_checksum, :cancel_sync], 'katello/api/v2/capsules' => [:index, :show] }, :resource_type => 'SmartProxy'