diff --git a/lib/insights_cloud/async/connector_playbook_execution_reporter_task.rb b/lib/insights_cloud/async/connector_playbook_execution_reporter_task.rb index 029fd441..674a6919 100644 --- a/lib/insights_cloud/async/connector_playbook_execution_reporter_task.rb +++ b/lib/insights_cloud/async/connector_playbook_execution_reporter_task.rb @@ -43,7 +43,17 @@ def rescue_strategy_for_self end def done?(current_status = invocation_status) - job_invocation.finished? || current_status.map { |_host_id, task_status| task_status['report_done'] }.all? + ActiveModel::Type::Boolean.new.cast(current_status[:task_state][:task_done_reported]) + end + + def job_finished? + job_invocation.finished? + end + + def all_hosts_finished?(current_status) + current_status[:hosts_state].values.all? do |status| + ActiveModel::Type::Boolean.new.cast(status['report_done'] == true) + end end # noop, we don't want to do anything when the polling task starts @@ -95,7 +105,11 @@ def correlation_id end def host_status(host) - external_task&.dig('invocation_status', host) + external_task&.dig('invocation_status', :hosts_state, host) + end + + def task_done_state + ActiveModel::Type::Boolean.new.cast(external_task&.dig('invocation_status', :task_state, :task_done_reported)) end def sequence(host) @@ -111,13 +125,15 @@ def report_url end def invocation_status - Hash[job_invocation.targeting.hosts.map do |host| + hosts_state = Hash[job_invocation.targeting.hosts.map do |host| next unless host.insights&.uuid [ host.insights.uuid, task_status(job_invocation.sub_task_for_host(host), host.insights.uuid), ] end.compact] + + {task_state: {task_done_reported: task_done_state}, hosts_state: hosts_state} end def task_status(host_task, host_name) @@ -138,9 +154,9 @@ def report_job_progress(invocation_status) generator = InsightsCloud::Generators::PlaybookProgressGenerator.new(correlation_id) all_hosts_success = true - invocation_status.each do |host_name, status| + invocation_status[:hosts_state].each do |host_name, status| # skip host if the host already reported that it's finished - next if status['report_done'] + next if ActiveModel::Type::Boolean.new.cast(status['report_done']) unless status['state'] == 'unknown' sequence = status['sequence'] @@ -154,7 +170,11 @@ def report_job_progress(invocation_status) all_hosts_success &&= status['exit_status'] == 0 end end - generator.job_finished_message(all_hosts_success) if done?(invocation_status) + + if (job_finished? || all_hosts_finished?(invocation_status)) + generator.job_finished_message(all_hosts_success) + invocation_status[:task_state][:task_done_reported] = true + end send_report(generator.generate) end diff --git a/test/jobs/connector_playbook_execution_reporter_task_test.rb b/test/jobs/connector_playbook_execution_reporter_task_test.rb index f07c6ac5..32025827 100644 --- a/test/jobs/connector_playbook_execution_reporter_task_test.rb +++ b/test/jobs/connector_playbook_execution_reporter_task_test.rb @@ -29,7 +29,11 @@ def send_report(report) end test 'It reports finish playbook messages' do - TestConnectorPlaybookExecutionReporterTask.any_instance.stubs(:done?).returns(true) + host1_task = @job_invocation.sub_task_for_host(Host.where(name: 'host1').first) + host1_task.state = 'stopped' + host1_task.save! + + TestConnectorPlaybookExecutionReporterTask.any_instance.stubs(:job_finished?).returns(true) actual = ForemanTasks.sync_task(TestConnectorPlaybookExecutionReporterTask, @job_invocation) @@ -39,6 +43,9 @@ def send_report(report) assert_not_nil actual_report actual_jsonl = read_jsonl(actual_report) + assert_equal true, @job_invocation.finished? + assert_equal 'stopped', @job_invocation.sub_task_for_host(Host.where(name: 'host1').first)['state'] + assert_not_nil actual_report_finished = actual_jsonl.find { |l| l['type'] == 'playbook_run_completed' } assert_equal 'TEST_CORRELATION', actual_report_finished['correlation_id'] assert_equal 'success', actual_report_finished['status'] @@ -49,57 +56,117 @@ def send_report(report) end test 'It reports single progress message for done host' do - TestConnectorPlaybookExecutionReporterTask.any_instance.stubs(:done?).returns(false, true) + class ArrangeTestHost < InsightsCloud::Async::ConnectorPlaybookExecutionReporterTask + def send_report(report) + host1_task = @job_invocation.sub_task_for_host(Host.where(name: 'host1').first) + host1_task.state = 'stopped' + host1_task.save! + + output[:saved_reports] = (output[:saved_reports] || []) << report + end + end - actual = ForemanTasks.sync_task(TestConnectorPlaybookExecutionReporterTask, @job_invocation) + ArrangeTestHost.instance_variable_set(:@connector_feature_id, nil) + host1_task = @job_invocation.sub_task_for_host(Host.where(name: 'host1').first) + host1_task.state = 'running' + host1_task.save! - actual_report = actual.output[:saved_reports].first.to_s + ArrangeTestHost.any_instance.stubs(:job_finished?).returns(false, true) - assert_equal 1, actual.output[:saved_reports].size - assert_not_nil actual_report - actual_jsonl = read_jsonl(actual_report) + actual = ForemanTasks.sync_task(ArrangeTestHost, @job_invocation) + + actual_report1 = actual.output[:saved_reports].first.to_s + actual_report2 = actual.output[:saved_reports].second.to_s + + assert_equal 2, actual.output[:saved_reports].size + assert_not_nil actual_report1 + assert_not_nil actual_report2 + + actual_json1 = read_jsonl(actual_report1) + actual_json2 = read_jsonl(actual_report2) + + assert_equal 'stopped', @job_invocation.sub_task_for_host(Host.where(name: 'host1').first)['state'] - actual_host_updates = actual_jsonl - .select { |l| l['type'] == 'playbook_run_update' && l['host'] == @host1.insights.uuid } - assert_equal 1, actual_host_updates.size - assert_equal 0, actual_host_updates.first['sequence'] + assert_not_nil actual_report_updated = actual_json1.find { |l| l['type'] == 'playbook_run_update' && l['host'] == 'TEST_UUID1' } + assert_equal 'TEST_CORRELATION', actual_report_updated['correlation_id'] + assert_equal 'TEST_UUID1', actual_report_updated['host'] + assert_equal 0, actual_report_updated['sequence'] + + assert_not_nil actual_report_updated = actual_json2.find { |l| l['type'] == 'playbook_run_update' && l['host'] == 'TEST_UUID1' } + assert_equal 'TEST_CORRELATION', actual_report_updated['correlation_id'] + assert_equal 'TEST_UUID1', actual_report_updated['host'] + assert_equal 1, actual_report_updated['sequence'] + + assert_not_nil actual_host_finished = actual_json2.find { |l| l['type'] == 'playbook_run_finished' && l['host'] == 'TEST_UUID1' } + assert_equal 'TEST_CORRELATION', actual_host_finished['correlation_id'] + assert_equal 'TEST_UUID1', actual_host_finished['host'] + assert_equal 'success', actual_host_finished['status'] + + assert_not_nil actual_report_finished = actual_json2.find { |l| l['type'] == 'playbook_run_completed' } + assert_equal 'TEST_CORRELATION', actual_report_finished['correlation_id'] + assert_equal 'success', actual_report_finished['status'] end test 'It reports two progress messages for in progress host' do - TestConnectorPlaybookExecutionReporterTask.any_instance.stubs(:done?).returns(false, false, true) + class ArrangeTestHostTwo < InsightsCloud::Async::ConnectorPlaybookExecutionReporterTask + def send_report(report) + iteration_number = output[:iteration_number].to_i + + if iteration_number == 1 + host1_task = job_invocation.sub_task_for_host(Host.where(name: 'host1').first) + host1_task.state = 'stopped' + host1_task.save! + end + + output[:iteration_number] = iteration_number + 1 + output[:saved_reports] = (output[:saved_reports] || []) << report + end + end - host1_task = @job_invocation.template_invocations.joins(:host).where(hosts: {name: @host1.name}).first.run_host_job_task + ArrangeTestHostTwo.instance_variable_set(:@connector_feature_id, nil) + host1_task = @job_invocation.sub_task_for_host(Host.where(name: 'host1').first) host1_task.state = 'running' host1_task.save! - actual = ForemanTasks.sync_task(TestConnectorPlaybookExecutionReporterTask, @job_invocation) + ArrangeTestHostTwo.any_instance.stubs(:job_finished?).returns(false, false, true) - assert_equal 2, actual.output[:saved_reports].size + actual = ForemanTasks.sync_task(ArrangeTestHostTwo, @job_invocation) - first_report = actual.output[:saved_reports].first.to_s - actual_jsonl = read_jsonl(first_report) + actual_report1 = actual.output[:saved_reports].first.to_s + actual_report2 = actual.output[:saved_reports].second.to_s + actual_report3 = actual.output[:saved_reports].third.to_s - actual_host_updates = actual_jsonl - .select { |l| l['type'] == 'playbook_run_update' && l['host'] == @host1.insights.uuid } - assert_equal 1, actual_host_updates.size - assert_equal 0, actual_host_updates.first['sequence'] + assert_equal 3, actual.output[:saved_reports].size + assert_not_nil actual_report1 + assert_not_nil actual_report2 + assert_not_nil actual_report3 - actual_host_updates = actual_jsonl - .select { |l| l['type'] == 'playbook_run_update' && l['host'] == @host2.insights.uuid } - assert_equal 1, actual_host_updates.size - assert_equal 0, actual_host_updates.first['sequence'] + actual_json1 = read_jsonl(actual_report1) + actual_json2 = read_jsonl(actual_report2) + actual_json3 = read_jsonl(actual_report3) - second_report = actual.output[:saved_reports].last.to_s - actual_jsonl = read_jsonl(second_report) + assert_not_nil actual_report_updated = actual_json1.find { |l| l['type'] == 'playbook_run_update' && l['host'] == 'TEST_UUID1' } + assert_equal 'TEST_CORRELATION', actual_report_updated['correlation_id'] + assert_equal 'TEST_UUID1', actual_report_updated['host'] + assert_equal 0, actual_report_updated['sequence'] + assert_equal 6, actual_report_updated.size - actual_host_updates = actual_jsonl - .select { |l| l['type'] == 'playbook_run_update' && l['host'] == @host1.insights.uuid } - assert_equal 1, actual_host_updates.size - assert_equal 1, actual_host_updates.first['sequence'] + assert_not_nil actual_report_updated = actual_json2.find { |l| l['type'] == 'playbook_run_update' && l['host'] == 'TEST_UUID1' } + assert_equal 'TEST_CORRELATION', actual_report_updated['correlation_id'] + assert_equal 'TEST_UUID1', actual_report_updated['host'] + assert_equal 1, actual_report_updated['sequence'] + assert_equal 6, actual_report_updated.size - actual_host_updates = actual_jsonl - .select { |l| l['type'] == 'playbook_run_update' && l['host'] == @host2.insights.uuid } - assert_equal 0, actual_host_updates.size + assert_not_nil actual_host_finished = actual_json3.find { |l| l['type'] == 'playbook_run_finished' && l['host'] == 'TEST_UUID1' } + assert_equal 'TEST_CORRELATION', actual_host_finished['correlation_id'] + assert_equal 'TEST_UUID1', actual_host_finished['host'] + assert_equal 'success', actual_host_finished['status'] + assert_equal 7, actual_host_finished.size + + assert_not_nil actual_report_finished = actual_json3.find { |l| l['type'] == 'playbook_run_completed' } + assert_equal 'TEST_CORRELATION', actual_report_finished['correlation_id'] + assert_equal 'success', actual_report_finished['status'] + assert_equal 4, actual_report_finished.size end private @@ -164,10 +231,12 @@ def generate_job_invocation :value => '1' ) - @host1 = FactoryBot.create(:host, :with_insights_hits, name: 'host1') + @host1 = FactoryBot.create(:host, :with_insights_hits) + @host1.name = 'host1' # overriding name since there is an issue with Factorybot and setting the name correctly, same for 2nd host @host1.insights.uuid = 'TEST_UUID1' @host1.insights.save! - @host2 = FactoryBot.create(:host, :with_insights_hits, name: 'host2') + @host2 = FactoryBot.create(:host, :with_insights_hits) + @host2.name = 'host2' @host2.insights.uuid = 'TEST_UUID2' @host2.insights.save!