Skip to content

Commit 48386bd

Browse files
committed
Refs #38856 - simplify auto-publish to always use events
* All composite publishes now go through event system (not just :running case) * Event handler performs coordination and chaining * Pass calling_task_id through event metadata for sibling exclusion
1 parent e891fc2 commit 48386bd

File tree

4 files changed

+80
-145
lines changed

4 files changed

+80
-145
lines changed

app/models/katello/content_view_version.rb

Lines changed: 44 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -374,16 +374,12 @@ def auto_publish_composites!(component_task_id)
374374
when :scheduled
375375
Rails.logger.info("Composite CV #{composite_cv.name} publish already scheduled, skipping duplicate")
376376
next
377-
when :running
378-
# A composite publish is currently running - we need to schedule another one
379-
# after it finishes to include this new component version
380-
Rails.logger.info("Composite CV #{composite_cv.name} publish running, scheduling event for retry")
381-
schedule_auto_publish_event(composite_cv, description)
377+
when :running, nil
378+
# Either composite is running or no composite activity detected
379+
# Schedule event to trigger composite publish with proper coordination
380+
Rails.logger.info("Composite CV #{composite_cv.name} scheduling auto-publish event")
381+
schedule_auto_publish_event(composite_cv, description, component_task_id)
382382
next
383-
when nil
384-
# No composite publish running or scheduled - trigger one now
385-
sibling_task_ids = find_sibling_component_publish_tasks(composite_cv, component_task_id)
386-
trigger_composite_publish(composite_cv, sibling_task_ids, description)
387383
end
388384
end
389385
end
@@ -411,9 +407,9 @@ def composite_publish_status(composite_cv)
411407
end
412408

413409
# Schedule an event to retry composite publish after current one finishes
414-
def schedule_auto_publish_event(composite_cv, description)
410+
def schedule_auto_publish_event(composite_cv, description, component_task_id)
415411
::Katello::EventQueue.push_event(::Katello::Events::AutoPublishCompositeView::EVENT_TYPE, composite_cv.id) do |attrs|
416-
attrs[:metadata] = { description: description, version_id: self.id }
412+
attrs[:metadata] = { description: description, version_id: self.id, calling_task_id: component_task_id }
417413
end
418414
end
419415

@@ -429,65 +425,55 @@ def scheduled_task_for_composite?(task, composite_cv)
429425
false
430426
end
431427

432-
# Trigger a composite publish, either immediately or chained to sibling tasks
433-
def trigger_composite_publish(composite_cv, sibling_task_ids, description)
434-
if sibling_task_ids.any?
435-
trigger_chained_composite_publish(composite_cv, sibling_task_ids, description)
436-
else
437-
trigger_immediate_composite_publish(composite_cv, description)
438-
end
428+
# Trigger a composite publish with coordination for sibling tasks.
429+
# Checks for running component CV publishes and chains if necessary.
430+
def self.trigger_composite_publish_with_coordination(composite_cv, description, triggered_by_version_id, calling_task_id: nil)
431+
# Find currently running component CV publish tasks
432+
component_cv_ids = composite_cv.components.pluck(:content_view_id)
433+
running_tasks = ForemanTasks::Task::DynflowTask
434+
.for_action(::Actions::Katello::ContentView::Publish)
435+
.where(state: ['planning', 'planned', 'running'])
436+
.select do |task|
437+
task_input = task.input
438+
task_input && component_cv_ids.include?(task_input.dig('content_view', 'id'))
439+
end
440+
441+
sibling_task_ids = running_tasks.map(&:external_id)
442+
# Exclude current task if provided (when called from auto_publish_composites!)
443+
sibling_task_ids.reject! { |id| id == calling_task_id } if calling_task_id
444+
445+
trigger_publish_with_sibling_tasks(composite_cv, sibling_task_ids, description, triggered_by_version_id)
439446
rescue ForemanTasks::Lock::LockConflict => e
440447
Rails.logger.info("Composite CV #{composite_cv.name} publish lock conflict: #{e.class} - #{e.message}")
441448
::Katello::UINotifications::ContentView::AutoPublishFailure.deliver!(composite_cv)
449+
raise
442450
rescue StandardError => e
443451
Rails.logger.error("Failed to auto-publish composite CV #{composite_cv.name}: #{e.class} - #{e.message}")
444452
Rails.logger.debug(e.backtrace.join("\n")) if e.backtrace
445453
::Katello::UINotifications::ContentView::AutoPublishFailure.deliver!(composite_cv)
446454
raise
447455
end
448456

449-
# Trigger a chained composite publish that waits for sibling tasks
450-
def trigger_chained_composite_publish(composite_cv, sibling_task_ids, description)
451-
ForemanTasks.dynflow.world.chain(
452-
sibling_task_ids,
453-
::Actions::Katello::ContentView::Publish,
454-
composite_cv,
455-
description,
456-
triggered_by_id: self.id
457-
)
458-
end
459-
460-
# Trigger an immediate composite publish
461-
def trigger_immediate_composite_publish(composite_cv, description)
462-
ForemanTasks.async_task(
463-
::Actions::Katello::ContentView::Publish,
464-
composite_cv,
465-
description,
466-
triggered_by_id: self.id
467-
)
457+
# Trigger a composite publish, chaining to sibling tasks if any exist
458+
def self.trigger_publish_with_sibling_tasks(composite_cv, sibling_task_ids, description, triggered_by_version_id)
459+
if sibling_task_ids.any?
460+
ForemanTasks.dynflow.world.chain(
461+
sibling_task_ids,
462+
::Actions::Katello::ContentView::Publish,
463+
composite_cv,
464+
description,
465+
triggered_by_id: triggered_by_version_id
466+
)
467+
else
468+
ForemanTasks.async_task(
469+
::Actions::Katello::ContentView::Publish,
470+
composite_cv,
471+
description,
472+
triggered_by_id: triggered_by_version_id
473+
)
474+
end
468475
end
469476

470-
# Find sibling component publish tasks that should be waited for
471-
def find_sibling_component_publish_tasks(composite_cv, current_task_id)
472-
# Get all component CV IDs for this composite
473-
component_cv_ids = composite_cv.components.pluck(:content_view_id)
474-
475-
# Find all currently running publish tasks for these component CVs
476-
running_tasks = ForemanTasks::Task::DynflowTask
477-
.for_action(::Actions::Katello::ContentView::Publish)
478-
.where(state: ['planning', 'planned', 'running'])
479-
.select do |task|
480-
# Check if task is publishing one of the component CVs
481-
task_input = task.input
482-
task_input && component_cv_ids.include?(task_input.dig('content_view', 'id'))
483-
end
484-
485-
task_ids = running_tasks.map(&:external_id)
486-
487-
# Exclude the current task - if we're running this code, the current task
488-
# has already reached its Finalize step and doesn't need to be waited for
489-
task_ids.reject { |id| id == current_task_id }
490-
end
491477

492478
# Find active (planning/planned/running) composite publish tasks (does NOT check scheduled tasks)
493479
def find_active_composite_publish_tasks(composite_cv)

app/models/katello/events/auto_publish_composite_view.rb

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,14 @@ def run
2525
return unless composite_view
2626

2727
begin
28-
ForemanTasks.async_task(::Actions::Katello::ContentView::Publish,
29-
composite_view,
30-
metadata[:description],
31-
triggered_by: metadata[:version_id])
28+
# Use the same coordination logic as auto_publish_composites! to check for
29+
# running component tasks and chain if necessary
30+
::Katello::ContentViewVersion.trigger_composite_publish_with_coordination(
31+
composite_view,
32+
metadata[:description],
33+
metadata[:version_id],
34+
calling_task_id: metadata[:calling_task_id]
35+
)
3236
rescue => e
3337
self.retry = true if e.is_a?(ForemanTasks::Lock::LockConflict)
3438
deliver_failure_notification

test/models/content_view_version_auto_publish_test.rb

Lines changed: 18 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -39,45 +39,37 @@ def setup
3939
:latest => true)
4040
end
4141

42-
def test_auto_publish_with_no_sibling_tasks_triggers_immediately
42+
def test_auto_publish_schedules_event_when_no_composite_activity
4343
task_id = SecureRandom.uuid
4444

45-
# Stub to return no scheduled, no running composite, no sibling tasks
45+
# Stub to return no scheduled, no running composite
4646
ForemanTasks::Task::DynflowTask.stubs(:for_action)
4747
.returns(stub(where: stub(any?: false))) # Scheduled check: no scheduled tasks
4848
.then.returns(stub(where: stub(select: []))) # Running composite check: none
49-
.then.returns(stub(where: stub(select: []))) # Sibling check: none
5049

51-
ForemanTasks.expects(:async_task).with(
52-
::Actions::Katello::ContentView::Publish,
53-
@composite_cv,
54-
anything,
55-
triggered_by_id: @component1_version.id
56-
).returns(stub(id: SecureRandom.uuid))
50+
::Katello::EventQueue.expects(:push_event).with(
51+
::Katello::Events::AutoPublishCompositeView::EVENT_TYPE,
52+
@composite_cv.id
53+
)
5754

5855
@component1_version.auto_publish_composites!(task_id)
5956
end
6057

61-
def test_auto_publish_with_sibling_tasks_uses_chaining
62-
task_id1 = SecureRandom.uuid
63-
task_id2 = SecureRandom.uuid
64-
65-
sibling_task = stub(external_id: task_id2, input: { 'content_view' => { 'id' => @component_cv2.id } })
58+
def test_auto_publish_schedules_event_when_composite_running
59+
task_id = SecureRandom.uuid
60+
running_task = stub(input: { 'content_view' => { 'id' => @composite_cv.id } })
6661

62+
# Stub to return no scheduled but a running composite
6763
ForemanTasks::Task::DynflowTask.stubs(:for_action)
6864
.returns(stub(where: stub(any?: false))) # Scheduled check: no scheduled tasks
69-
.then.returns(stub(where: stub(select: []))) # Running composite check: none
70-
.then.returns(stub(where: stub(select: [sibling_task]))) # Sibling check: found sibling
71-
72-
ForemanTasks.dynflow.world.expects(:chain).with(
73-
[task_id2],
74-
::Actions::Katello::ContentView::Publish,
75-
@composite_cv,
76-
anything,
77-
triggered_by_id: @component1_version.id
78-
).returns(stub(id: SecureRandom.uuid))
79-
80-
@component1_version.auto_publish_composites!(task_id1)
65+
.then.returns(stub(where: stub(select: [running_task]))) # Running composite check: found running
66+
67+
::Katello::EventQueue.expects(:push_event).with(
68+
::Katello::Events::AutoPublishCompositeView::EVENT_TYPE,
69+
@composite_cv.id
70+
)
71+
72+
@component1_version.auto_publish_composites!(task_id)
8173
end
8274

8375
def test_auto_publish_skips_when_composite_already_scheduled
@@ -129,56 +121,5 @@ def test_auto_publish_schedules_event_when_composite_running
129121
@component1_version.auto_publish_composites!(task_id)
130122
end
131123

132-
def test_auto_publish_handles_lock_conflict_gracefully
133-
task_id = SecureRandom.uuid
134-
135-
ForemanTasks::Task::DynflowTask.stubs(:for_action)
136-
.returns(stub(where: stub(any?: false))) # Scheduled check: none
137-
.then.returns(stub(where: stub(select: []))) # Running composite check: none
138-
.then.returns(stub(where: stub(select: []))) # Sibling check: none
139-
140-
lock = stub('required_lock')
141-
conflicting_task = stub(id: 123)
142-
conflicting_lock = stub(task: conflicting_task)
143-
144-
ForemanTasks.expects(:async_task).raises(ForemanTasks::Lock::LockConflict.new(lock, [conflicting_lock]))
145-
::Katello::UINotifications::ContentView::AutoPublishFailure.expects(:deliver!).with(@composite_cv)
146-
147-
assert_nothing_raised do
148-
@component1_version.auto_publish_composites!(task_id)
149-
end
150-
end
151-
152-
def test_find_sibling_component_publish_tasks_finds_running_tasks
153-
task_id1 = SecureRandom.uuid
154-
task_id2 = SecureRandom.uuid
155-
156-
# Create mock running tasks
157-
task1 = stub(external_id: task_id1, input: { 'content_view' => { 'id' => @component_cv1.id } })
158-
task2 = stub(external_id: task_id2, input: { 'content_view' => { 'id' => @component_cv2.id } })
159-
160-
ForemanTasks::Task::DynflowTask.stubs(:for_action).returns(stub(where: stub(select: [task1, task2])))
161-
162-
current_task_id = SecureRandom.uuid
163-
result = @component1_version.send(:find_sibling_component_publish_tasks, @composite_cv, current_task_id)
164-
165-
# Should include both sibling tasks but exclude current task
166-
assert_equal 2, result.length
167-
assert_includes result, task_id1
168-
assert_includes result, task_id2
169-
assert_not_includes result, current_task_id
170-
end
171-
172-
def test_find_sibling_tasks_excludes_non_component_tasks
173-
task_id = SecureRandom.uuid
174-
175-
# The select block will filter out tasks for CVs that aren't components
176-
ForemanTasks::Task::DynflowTask.stubs(:for_action).returns(stub(where: stub(select: [])))
177-
178-
result = @component1_version.send(:find_sibling_component_publish_tasks, @composite_cv, task_id)
179-
180-
# Should exclude current task and other CV's task
181-
assert_empty result
182-
end
183124
end
184125
end

test/models/events/auto_publish_composite_view_test.rb

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,14 @@ class AutoPublishCompositeViewTest < ActiveSupport::TestCase
77
let(:component_version) { katello_content_view_versions(:library_view_version_1) }
88

99
def test_run_with_publish
10-
metadata = { description: "Auto Publish - Test", version_id: component_version.id }
10+
calling_task_id = SecureRandom.uuid
11+
metadata = { description: "Auto Publish - Test", version_id: component_version.id, calling_task_id: calling_task_id }
1112

12-
ForemanTasks.expects(:async_task).with(
13-
::Actions::Katello::ContentView::Publish,
13+
::Katello::ContentViewVersion.expects(:trigger_composite_publish_with_coordination).with(
1414
composite_view,
1515
metadata[:description],
16-
triggered_by: metadata[:version_id]
16+
metadata[:version_id],
17+
calling_task_id: calling_task_id
1718
)
1819

1920
event = AutoPublishCompositeView.new(composite_view.id) do |instance|
@@ -31,9 +32,12 @@ def test_run_with_error
3132
end
3233

3334
def test_run_with_lock_error
34-
metadata = { description: "Auto Publish - Test", version_id: component_version.id }
35+
calling_task_id = SecureRandom.uuid
36+
metadata = { description: "Auto Publish - Test", version_id: component_version.id, calling_task_id: calling_task_id }
3537

36-
ForemanTasks.expects(:async_task).raises(ForemanTasks::Lock::LockConflict.new(mock, []))
38+
::Katello::ContentViewVersion.expects(:trigger_composite_publish_with_coordination).raises(
39+
ForemanTasks::Lock::LockConflict.new(mock, [])
40+
)
3741

3842
instance = AutoPublishCompositeView.new(composite_view.id) do |event|
3943
event.metadata = metadata

0 commit comments

Comments
 (0)