From d833457eb018e70eabab0223743f7aa022a4a295 Mon Sep 17 00:00:00 2001 From: Lucy Fu Date: Thu, 14 Sep 2023 21:31:30 +0000 Subject: [PATCH] Fixes #36735 - Add a notication on RHEL lifecycle expiry --- ...ost_lifecycle_expire_soon_notifications.rb | 11 +++ .../concerns/host_managed_extensions.rb | 16 ---- app/models/katello/rhel_lifecycle_status.rb | 10 +- .../hosts/lifecycle_expire_soon.rb | 51 ++++++++++ .../109-katello-notification-blueprints.rb | 6 ++ lib/katello/scheduled_jobs.rb | 2 +- .../hosts/lifecycle_expire_soon_test.rb | 93 +++++++++++++++++++ 7 files changed, 171 insertions(+), 18 deletions(-) create mode 100644 app/jobs/create_host_lifecycle_expire_soon_notifications.rb create mode 100644 app/services/katello/ui_notifications/hosts/lifecycle_expire_soon.rb create mode 100644 test/services/katello/ui_notifications/hosts/lifecycle_expire_soon_test.rb diff --git a/app/jobs/create_host_lifecycle_expire_soon_notifications.rb b/app/jobs/create_host_lifecycle_expire_soon_notifications.rb new file mode 100644 index 00000000000..fceb08b0fdc --- /dev/null +++ b/app/jobs/create_host_lifecycle_expire_soon_notifications.rb @@ -0,0 +1,11 @@ +class CreateHostLifecycleExpireSoonNotifications < ApplicationJob + def perform + Katello::UINotifications::Hosts::LifecycleExpireSoon.deliver! + ensure + self.class.set(:wait => 1.week).perform_later + end + + def humanized_name + _('Host lifecycle support expiration notification') + end +end diff --git a/app/models/katello/concerns/host_managed_extensions.rb b/app/models/katello/concerns/host_managed_extensions.rb index bc32a67aaba..9e760cd2fed 100644 --- a/app/models/katello/concerns/host_managed_extensions.rb +++ b/app/models/katello/concerns/host_managed_extensions.rb @@ -493,22 +493,6 @@ def rhel_lifecycle_status_label @rhel_lifecycle_status_label ||= get_status(::Katello::RhelLifecycleStatus).to_label end - def full_support_end_date - ::Katello::RhelLifecycleStatus.full_support_end_dates[rhel_eos_schedule_index] - end - - def maintenance_support_end_date - ::Katello::RhelLifecycleStatus.maintenance_support_end_dates[rhel_eos_schedule_index] - end - - def extended_support_end_date - ::Katello::RhelLifecycleStatus.extended_support_end_dates[rhel_eos_schedule_index] - end - - def end_of_support_date - ::Katello::RhelLifecycleStatus.eos_date(eos_schedule_index: rhel_eos_schedule_index) - end - def traces_status @traces_status ||= get_status(::Katello::TraceStatus).status end diff --git a/app/models/katello/rhel_lifecycle_status.rb b/app/models/katello/rhel_lifecycle_status.rb index 554f4f35b20..0630f0d34cd 100644 --- a/app/models/katello/rhel_lifecycle_status.rb +++ b/app/models/katello/rhel_lifecycle_status.rb @@ -69,7 +69,15 @@ def self.status_map end def self.approaching_end_of_category(eos_schedule_index:) - RHEL_EOS_SCHEDULE[eos_schedule_index].select { |_k, v| (Time.now.utc..Time.now.utc + EOS_WARNING_THRESHOLD).cover?(v) } + lifecycles_expire_soon[eos_schedule_index] + end + + def self.lifecycles_expire_soon + expiring = RHEL_EOS_SCHEDULE.collect do |index, schedules| + expire_soon = schedules.except("full_support").select { |_k, v| (Time.now.utc..Time.now.utc + EOS_WARNING_THRESHOLD).cover?(v) } + {index => expire_soon} if expire_soon.present? + end + expiring.compact.reduce(:update) || {} end def self.to_status(rhel_eos_schedule_index: nil) diff --git a/app/services/katello/ui_notifications/hosts/lifecycle_expire_soon.rb b/app/services/katello/ui_notifications/hosts/lifecycle_expire_soon.rb new file mode 100644 index 00000000000..3acf7a2c0f7 --- /dev/null +++ b/app/services/katello/ui_notifications/hosts/lifecycle_expire_soon.rb @@ -0,0 +1,51 @@ +module Katello + module UINotifications + module Hosts + class LifecycleExpireSoon + def self.deliver! + ::Katello::RhelLifecycleStatus.lifecycles_expire_soon.each do |release, schedule| + schedule.each do |lifecycle, end_date| + lifecycle_status = lifecycle == "maintenance_support" ? "approaching_end_of_maintenance" : "approaching_end_of_support" + count = ::Host::Managed.search_for("rhel_lifecycle_status = #{lifecycle_status}").count + next if count == 0 + + message = message(count: count, release: release, lifecycle: lifecycle, end_date: end_date) + if (notification = existing_notification(release)) + /[^:]+: (?\d+) hosts/ =~ notification.message + next if number_of_hosts == count.to_s + notification.update(message: message) + else + ::Notification.create!( + :initiator => User.anonymous_admin, + :audience => Notification::AUDIENCE_GLOBAL, + :message => message, + :expired_at => end_date.strftime('%Y-%m-%d'), + :notification_blueprint => blueprint + ) + end + end + end + end + + def self.existing_notification(release) + blueprint.notifications.where("message like ?", "#{release}%").first + end + + def self.message(options) + ::UINotifications::StringParser.new( + blueprint.message, + :number_of_hosts => options[:count], + :release => options[:release], + :lifecycle => options[:lifecycle].gsub(/_/, " "), + :end_date => options[:end_date].strftime('%Y-%m-%d'), + :audience => Notification::AUDIENCE_GLOBAL + ) + end + + def self.blueprint + @blueprint ||= NotificationBlueprint.unscoped.find_by(name: 'host_lifecycle_expire_soon') + end + end + end + end +end diff --git a/db/seeds.d/109-katello-notification-blueprints.rb b/db/seeds.d/109-katello-notification-blueprints.rb index b687db7cc7e..1d897ac1ba5 100644 --- a/db/seeds.d/109-katello-notification-blueprints.rb +++ b/db/seeds.d/109-katello-notification-blueprints.rb @@ -1,4 +1,10 @@ blueprints = [ + { + group: N_('Hosts'), + name: 'host_lifecycle_expire_soon', + message: N_('%{release}: %{number_of_hosts} hosts are approaching end of %{lifecycle} on %{end_date}. Please upgrade them before support expires. Check Report Host - Statuses for detail.'), + level: 'warning' + }, { group: N_('Proxies'), name: 'pulp_low_disk_space', diff --git a/lib/katello/scheduled_jobs.rb b/lib/katello/scheduled_jobs.rb index 8bb1bf78d62..f76819e8d8b 100644 --- a/lib/katello/scheduled_jobs.rb +++ b/lib/katello/scheduled_jobs.rb @@ -1,6 +1,6 @@ # First, we check if there's a job already enqueued for any notifications ::Foreman::Application.dynflow.config.on_init do |world| - [CreateExpiredManifestNotifications, CreatePulpDiskSpaceNotifications, SendExpireSoonNotifications].each do |job_class| + [CreateExpiredManifestNotifications, CreateHostLifecycleExpireSoonNotifications, CreatePulpDiskSpaceNotifications, SendExpireSoonNotifications].each do |job_class| job_class.spawn_if_missing(world) end end diff --git a/test/services/katello/ui_notifications/hosts/lifecycle_expire_soon_test.rb b/test/services/katello/ui_notifications/hosts/lifecycle_expire_soon_test.rb new file mode 100644 index 00000000000..01c348a34ad --- /dev/null +++ b/test/services/katello/ui_notifications/hosts/lifecycle_expire_soon_test.rb @@ -0,0 +1,93 @@ +require 'katello_test_helper' + +# 'RHEL9' => { +# 'maintenance_support' => end_of_day('2032-05-31'), +# 'extended_support' => end_of_day('2035-05-31') +# }, +# 'RHEL8' => { +# 'maintenance_support' => end_of_day('2029-05-31'), +# 'extended_support' => end_of_day('2032-05-31') +# }, +# 'RHEL7' => { +# 'maintenance_support' => end_of_day('2024-06-30'), +# 'extended_support' => end_of_day('2028-06-30') +# }, +# 'RHEL6' => { +# 'maintenance_support' => end_of_day('2020-11-30'), +# 'extended_support' => end_of_day('2024-06-30') +# }, + +module Katello + module UINotifications + module Hosts + class LifecycleExpireSoonTest < ::ActiveSupport::TestCase + FactoryBot.create(:notification_blueprint, :name => 'host_lifecycle_expire_soon') + let(:subject) { Katello::UINotifications::Hosts::LifecycleExpireSoon } + let(:arch) { Architecture.find_by(name: "x86_64") } + let(:os6) { FactoryBot.create(:operatingsystem, :major => "6", :name => "RedHat") } + let(:os7) { FactoryBot.create(:operatingsystem, :major => "7", :name => "RedHat") } + let(:os8) { FactoryBot.create(:operatingsystem, :major => "8", :name => "RedHat") } + let(:os9) { FactoryBot.create(:operatingsystem, :major => "9", :name => "RedHat") } + + def setup + FactoryBot.create(:host, :architecture => arch, :operatingsystem => os6) + FactoryBot.create(:host, :architecture => arch, :operatingsystem => os7) + FactoryBot.create(:host, :architecture => arch, :operatingsystem => os8) + FactoryBot.create(:host, :architecture => arch, :operatingsystem => os9) + end + + def teardown + NotificationBlueprint.find_by(name: 'host_lifecycle_expire_soon').notifications.destroy_all + end + + def test_with_year_2024_1_1 + Time.stubs(:now).returns(Time.utc(2024, 1, 1)) + subject.deliver! + assert_equal 2, NotificationBlueprint.find_by(name: 'host_lifecycle_expire_soon').notifications.count + end + + def test_with_year_2025_1_1 + Time.stubs(:now).returns(Time.utc(2025, 1, 1)) + subject.deliver! + assert_equal 0, NotificationBlueprint.find_by(name: 'host_lifecycle_expire_soon').notifications.count + end + + def test_with_year_2026_6_1 + Time.stubs(:now).returns(Time.utc(2026, 6, 1)) + subject.deliver! + assert_equal 0, NotificationBlueprint.find_by(name: 'host_lifecycle_expire_soon').notifications.count + end + + def test_with_year_2027_7_1 + Time.stubs(:now).returns(Time.utc(2027, 7, 1)) + subject.deliver! + assert_equal 1, NotificationBlueprint.find_by(name: 'host_lifecycle_expire_soon').notifications.count + end + + def test_with_year_2028_6_1 + Time.stubs(:now).returns(Time.utc(2028, 6, 1)) + subject.deliver! + assert_equal 2, NotificationBlueprint.find_by(name: 'host_lifecycle_expire_soon').notifications.count + end + + def test_with_year_2029_6_1 + Time.stubs(:now).returns(Time.utc(2029, 6, 1)) + subject.deliver! + assert_equal 0, NotificationBlueprint.find_by(name: 'host_lifecycle_expire_soon').notifications.count + end + + def test_with_year_2030_6_1 + Time.stubs(:now).returns(Time.utc(2030, 6, 1)) + subject.deliver! + assert_equal 0, NotificationBlueprint.find_by(name: 'host_lifecycle_expire_soon').notifications.count + end + + def test_with_year_2031_6_1 + Time.stubs(:now).returns(Time.utc(2031, 6, 1)) + subject.deliver! + assert_equal 2, NotificationBlueprint.find_by(name: 'host_lifecycle_expire_soon').notifications.count + end + end + end + end +end