diff --git a/app/lib/actions/katello/repository/metadata_generate.rb b/app/lib/actions/katello/repository/metadata_generate.rb index 42b26152040..272dddf955c 100644 --- a/app/lib/actions/katello/repository/metadata_generate.rb +++ b/app/lib/actions/katello/repository/metadata_generate.rb @@ -1,8 +1,10 @@ module Actions module Katello module Repository - class MetadataGenerate < Actions::Base + class MetadataGenerate < Actions::EntryAction def plan(repository, options = {}) + action_subject(repository) + repository.check_ready_to_act! source_repository = options.fetch(:source_repository, nil) source_repository ||= repository.target_repository if repository.link? smart_proxy = options.fetch(:smart_proxy, SmartProxy.pulp_primary) @@ -15,6 +17,10 @@ def plan(repository, options = {}) :source_repository => source_repository, :matching_content => matching_content) end + + def resource_locks + :link + end end end end diff --git a/app/lib/actions/katello/repository/remove_content.rb b/app/lib/actions/katello/repository/remove_content.rb index a926c8e1cc7..bb70ebe5d67 100644 --- a/app/lib/actions/katello/repository/remove_content.rb +++ b/app/lib/actions/katello/repository/remove_content.rb @@ -5,6 +5,7 @@ class RemoveContent < Actions::EntryAction include Dynflow::Action::WithSubPlans def plan(repository, content_units, options = {}) + repository.check_ready_to_act! sync_capsule = options.fetch(:sync_capsule, true) if repository.redhat? fail _("Cannot remove content from a non-custom repository") diff --git a/app/lib/actions/katello/repository/sync.rb b/app/lib/actions/katello/repository/sync.rb index c5686b2af82..5fe16079a0e 100644 --- a/app/lib/actions/katello/repository/sync.rb +++ b/app/lib/actions/katello/repository/sync.rb @@ -20,6 +20,7 @@ class Sync < Actions::EntryAction # of Katello and we just need to finish the rest of the orchestration def plan(repo, options = {}) action_subject(repo) + repo.check_ready_to_act! validate_contents = options.fetch(:validate_contents, false) skip_metadata_check = options.fetch(:skip_metadata_check, false) || (validate_contents && (repo.yum? || repo.deb?)) diff --git a/app/lib/actions/katello/repository/upload_files.rb b/app/lib/actions/katello/repository/upload_files.rb index a9edf1309c9..0f988d742ed 100644 --- a/app/lib/actions/katello/repository/upload_files.rb +++ b/app/lib/actions/katello/repository/upload_files.rb @@ -8,6 +8,7 @@ module Repository class UploadFiles < Actions::EntryAction def plan(repository, files, content_type = nil, options = {}) action_subject(repository) + repository.check_ready_to_act! repository.clear_smart_proxy_sync_histories tmp_files = prepare_tmp_files(files) diff --git a/app/models/katello/content_view.rb b/app/models/katello/content_view.rb index fa2356bd917..25560cbfd89 100644 --- a/app/models/katello/content_view.rb +++ b/app/models/katello/content_view.rb @@ -629,6 +629,7 @@ def check_ready_to_publish!(importing: false, syncable: false) check_ready_to_import! else fail _("Import-only content views can not be published directly") if import_only? && !syncable + check_repositories_blocking_publish! check_composite_action_allowed!(organization.library) check_docker_repository_names!([organization.library]) check_orphaned_content_facets!(environments: self.environments) @@ -637,6 +638,16 @@ def check_ready_to_publish!(importing: false, syncable: false) true end + def check_repositories_blocking_publish! + blocking_tasks = repositories&.map { |repo| repo.blocking_task }&.compact + + if blocking_tasks&.any? + errored_tasks = blocking_tasks.uniq.map { |task| "- #{Setting['foreman_url']}/foreman_tasks/tasks/#{task&.id}" }.join("\n") + fail _("Pending tasks detected in repositories of this content view. Please wait for the tasks: " + + errored_tasks + " before publishing.") + end + end + def check_docker_repository_names!(environments) environments.each do |environment| repositories = [] @@ -881,6 +892,17 @@ def filtered? filters.present? end + def blocking_task + blocking_task_labels = [ + ::Actions::Katello::ContentView::Publish.name + ] + ForemanTasks::Task::DynflowTask.where(:label => blocking_task_labels) + .where.not(state: 'stopped') + .for_resource(self) + .order(:started_at) + .last + end + protected def remove_repository(repository) diff --git a/app/models/katello/repository.rb b/app/models/katello/repository.rb index 567006d65f6..4dccf0925a2 100644 --- a/app/models/katello/repository.rb +++ b/app/models/katello/repository.rb @@ -641,6 +641,33 @@ def latest_dynflow_sync for_resource(self).order(:started_at).last end + def blocking_task + blocking_task_labels = [ + ::Actions::Katello::Repository::Sync.name, + ::Actions::Katello::Repository::UploadFiles.name, + ::Actions::Katello::Repository::RemoveContent.name, + ::Actions::Katello::Repository::MetadataGenerate.name + ] + ForemanTasks::Task::DynflowTask.where(:label => blocking_task_labels) + .where.not(state: 'stopped') + .for_resource(self) + .order(:started_at) + .last + end + + def check_ready_to_act! + blocking_tasks = content_views&.map { |cv| cv.blocking_task }&.compact + + if blocking_tasks&.any? + errored_tasks = blocking_tasks + .uniq + .map { |task| "- #{Setting['foreman_url']}/foreman_tasks/tasks/#{task&.id}" } + .join("\n") + fail _("This repository has pending tasks in associated content views. Please wait for the tasks: " + errored_tasks + + " to complete before proceeding.") + end + end + # returns other instances of this repo with the same library # equivalent of repo def environmental_instances(view) diff --git a/test/actions/katello/content_view_test.rb b/test/actions/katello/content_view_test.rb index 0709cd7beed..42b9f18377f 100644 --- a/test/actions/katello/content_view_test.rb +++ b/test/actions/katello/content_view_test.rb @@ -1,5 +1,4 @@ require 'katello_test_helper' - module ::Actions::Katello::ContentView class TestBase < ActiveSupport::TestCase include Dynflow::Testing @@ -8,7 +7,7 @@ class TestBase < ActiveSupport::TestCase let(:action) { create_action action_class } let(:success_task) { ForemanTasks::Task::DynflowTask.create!(state: :success, result: "good") } - + let(:pending_task) { ForemanTasks::Task::DynflowTask.create!(state: :pending, result: "good", id: 123) } before(:all) do set_user end @@ -17,6 +16,7 @@ class TestBase < ActiveSupport::TestCase class PublishTest < TestBase let(:action_class) { ::Actions::Katello::ContentView::Publish } let(:content_view) { katello_content_views(:no_environment_view) } + let(:repository) { katello_repositories(:fedora_17_x86_64) } before do Dynflow::Testing::DummyPlannedAction.any_instance.stubs(:repository_mapping).returns({}) end @@ -36,6 +36,15 @@ class PublishTest < TestBase end end + it 'fails when planning if child repo is being acted upon' do + content_view.repositories = [repository] + repository.expects(:blocking_task).returns(pending_task) + action.stubs(:task).returns(success_task) + assert_raises(RuntimeError) do + plan_action(action, content_view) + end + end + it 'uses override_components properly' do action.stubs(:task).returns(success_task) action.expects(:include_other_components).with('mock', content_view).returns('mock') diff --git a/test/actions/katello/repository/metadata_generate_test.rb b/test/actions/katello/repository/metadata_generate_test.rb index cef033a6189..af84de633bf 100644 --- a/test/actions/katello/repository/metadata_generate_test.rb +++ b/test/actions/katello/repository/metadata_generate_test.rb @@ -8,6 +8,7 @@ module Actions let(:action_class) { ::Actions::Katello::Repository::MetadataGenerate } let(:pulp_metadata_generate_class) { ::Actions::Pulp3::Orchestration::Repository::GenerateMetadata } + let(:success_task) { ForemanTasks::Task::DynflowTask.create!(state: :success, result: "good") } let(:yum_repo) { katello_repositories(:fedora_17_x86_64) } let(:yum_repo2) { katello_repositories(:fedora_17_x86_64_dev) } let(:action_options) do @@ -20,6 +21,8 @@ module Actions it 'plans a yum metadata generate' do action = create_action(action_class) + action.stubs(:task).returns(success_task) + action.expects(:action_subject).with(yum_repo) plan_action(action, yum_repo) assert_action_planned_with(action, pulp_metadata_generate_class, yum_repo, SmartProxy.pulp_primary, @@ -31,6 +34,7 @@ module Actions Location.current = taxonomies(:location1) action = create_action(action_class) + action.stubs(:task).returns(success_task) plan_action(action, yum_repo) assert_action_planned_with(action, pulp_metadata_generate_class, yum_repo, SmartProxy.pulp_primary, @@ -41,6 +45,7 @@ module Actions it 'plans a yum refresh with source repo' do action = create_action(action_class) + action.stubs(:task).returns(success_task) plan_action(action, yum_repo, :source_repository => yum_repo2) yum_action_options = action_options.clone @@ -52,6 +57,7 @@ module Actions it 'plans a yum refresh with matching content true' do action = create_action(action_class) + action.stubs(:task).returns(success_task) plan_action(action, yum_repo, :matching_content => true) yum_action_options = action_options.clone @@ -62,6 +68,7 @@ module Actions it 'plans a yum refresh with matching content set to some deferred object' do action = create_action(action_class) + action.stubs(:task).returns(success_task) not_falsey = Object.new plan_action(action, yum_repo, :matching_content => not_falsey) diff --git a/test/actions/katello/repository_test.rb b/test/actions/katello/repository_test.rb index 93609284e24..5bd1a9de555 100644 --- a/test/actions/katello/repository_test.rb +++ b/test/actions/katello/repository_test.rb @@ -634,6 +634,8 @@ class SyncTest < TestBase let(:action_class) { ::Actions::Katello::Repository::Sync } let(:pulp3_action_class) { ::Actions::Pulp3::Orchestration::Repository::Sync } let(:pulp3_metadata_generate_action_class) { ::Actions::Pulp3::Orchestration::Repository::GenerateMetadata } + let(:content_view) { katello_content_views(:no_environment_view) } + let(:pending_task) { ForemanTasks::Task::DynflowTask.create!(state: :pending, result: "good", id: 123) } it 'skips applicability if non-yum and non-deb' do action = create_action action_class @@ -644,7 +646,7 @@ class SyncTest < TestBase refute_action_planed action, ::Actions::Katello::Applicability::Repository::Regenerate end - it 'plans verift checksum when validate_contents is passed' do + it 'plans verify checksum when validate_contents is passed' do action = create_action action_class action.stubs(:action_subject).with(repository) plan_action action, repository, :validate_contents => true, :skip_candlepin_check => true @@ -659,6 +661,16 @@ class SyncTest < TestBase assert_action_planned(action, ::Actions::Katello::Repository::ErrataMail) end + it 'fails when planning if parent CV is being published' do + action = create_action action_class + repository.content_views = [content_view] + content_view.expects(:blocking_task).returns(pending_task) + action.stubs(:action_subject).with(repository) + assert_raises(RuntimeError) do + plan_action(action, repository) + end + end + it 'plans pulp3 orchestration actions with file repo' do action = create_action pulp3_action_class action.stubs(:action_subject).with(repository_pulp3)