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..933da4535b3 --- /dev/null +++ b/app/services/katello/ui_notifications/hosts/lifecycle_expire_soon.rb @@ -0,0 +1,58 @@ +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| + count = hosts_with_index(release).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.hosts_with_index(release) + /RHEL(?\d+)/ =~ release + ::Host::Managed.joins(:operatingsystem, :fact_values, :fact_names) + .where(fact_names: {name: "distribution::name"}) + .where("fact_values.value like ?", "Red Hat Enterprise Linux%") + .where(operatingsystem: {major: major}) + end + + def self.blueprint + @blueprint ||= NotificationBlueprint.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/models/concerns/host_managed_extensions_test.rb b/test/models/concerns/host_managed_extensions_test.rb index 3ec02db1b5e..af97e16a326 100644 --- a/test/models/concerns/host_managed_extensions_test.rb +++ b/test/models/concerns/host_managed_extensions_test.rb @@ -459,30 +459,6 @@ def test_rhel_eos_schedule_index_non_rhel host.expects(:facts).returns({'distribution::name' => 'CentOS Stream'}) assert_nil host.rhel_eos_schedule_index end - - def test_full_support_end_dates - host.expects(:rhel_eos_schedule_index).returns('RHEL9') - expected_date = ::Katello::RhelLifecycleStatus.full_support_end_dates['RHEL9'] - assert_equal expected_date, host.full_support_end_date - end - - def test_maintenance_support_end_dates - host.expects(:rhel_eos_schedule_index).returns('RHEL9') - expected_date = ::Katello::RhelLifecycleStatus.maintenance_support_end_dates['RHEL9'] - assert_equal expected_date, host.maintenance_support_end_date - end - - def test_extended_support_end_dates - host.expects(:rhel_eos_schedule_index).returns('RHEL9') - expected_date = ::Katello::RhelLifecycleStatus.extended_support_end_dates['RHEL9'] - assert_equal expected_date, host.extended_support_end_date - end - - def test_end_of_support_dates - host.expects(:rhel_eos_schedule_index).returns('RHEL9') - expected_date = ::Katello::RhelLifecycleStatus.eos_date(eos_schedule_index: 'RHEL9') - assert_equal expected_date, host.end_of_support_date - end end class HostManagedExtensionsKickstartTest < ActiveSupport::TestCase 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..af226f50de8 --- /dev/null +++ b/test/services/katello/ui_notifications/hosts/lifecycle_expire_soon_test.rb @@ -0,0 +1,91 @@ +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 + def setup + blueprint = FactoryBot.create(:notification_blueprint, :name => 'host_lifecycle_expire_soon') + @subject = Katello::UINotifications::Hosts::LifecycleExpireSoon + @subject.stubs(:blueprint).returns(blueprint) + 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.expects(:hosts_with_index).with("RHEL6").returns([mock('rhel6')]) + @subject.expects(:hosts_with_index).with("RHEL7").returns([mock('rhel7')]) + @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.expects(:hosts_with_index).with("RHEL7").returns([mock('rhel7')]) + @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.expects(:hosts_with_index).with("RHEL7").returns([mock('rhel7')]) + @subject.expects(:hosts_with_index).with("RHEL8").returns([mock('rhel8')]) + @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.expects(:hosts_with_index).with("RHEL8").returns([mock('rhel8')]) + @subject.expects(:hosts_with_index).with("RHEL9").returns([mock('rhel9')]) + @subject.deliver! + assert_equal 2, NotificationBlueprint.find_by(name: 'host_lifecycle_expire_soon').notifications.count + end + end + end + end +end