From 4885f9355965fc8f0352462954d5f691ec5f053e Mon Sep 17 00:00:00 2001 From: Bastian Schmidt Date: Wed, 20 Nov 2024 16:00:17 +0100 Subject: [PATCH] Add RecurringLogic to update HostResources Add task RefreshResourceQuotaUtilization which calls determine_utilization on every ResourceQuota. The command iterates all hosts of a ResourceQuota and sets the host.host_resources which is used for the ResourceQuota.utilization. This guarantees that Foreman does not miss changes to HostResources, when they are not performed via the Foreman UI. Moreover, it increases performance since Foreman Resource Quota does not re-compute a ResourceQuota's utilization on host deployment and, instead, takes the last-computed sum of HostResources. However, this comes with the downside that HostResources might be missed when edited out-of Foreman UI and new hosts are deployed before the task is executed. --- .rubocop.yml | 4 ++ .../refresh_resource_quota_utilization.rb | 25 ++++++++++++ lib/foreman_resource_quota/engine.rb | 40 +++++++++++++++++++ 3 files changed, 69 insertions(+) create mode 100644 lib/foreman_resource_quota/async/refresh_resource_quota_utilization.rb diff --git a/.rubocop.yml b/.rubocop.yml index f19eac4..e81de53 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -43,3 +43,7 @@ Rails/SkipsModelValidations: Style/FormatStringToken: Enabled: false + +Rails/DynamicFindBy: + Exclude: + - "lib/foreman_resource_quota/engine.rb" diff --git a/lib/foreman_resource_quota/async/refresh_resource_quota_utilization.rb b/lib/foreman_resource_quota/async/refresh_resource_quota_utilization.rb new file mode 100644 index 0000000..3720ba6 --- /dev/null +++ b/lib/foreman_resource_quota/async/refresh_resource_quota_utilization.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module ForemanResourceQuota + module Async + class RefreshResourceQuotaUtilization < ::Actions::EntryAction + include ::Actions::RecurringAction + + def run + ResourceQuota.all.each do |quota| + quota.determine_utilization + rescue e + logger.error N_(format("An error occured determining the utilization of '%s'-quota: %s", quota.name, e)) + end + end + + def logger + action_logger + end + + def rescue_strategy_for_self + Dynflow::Action::Rescue::Fail + end + end + end +end diff --git a/lib/foreman_resource_quota/engine.rb b/lib/foreman_resource_quota/engine.rb index 0e96649..f73de8a 100644 --- a/lib/foreman_resource_quota/engine.rb +++ b/lib/foreman_resource_quota/engine.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require 'foreman_tasks' + module ForemanResourceQuota class Engine < ::Rails::Engine engine_name 'foreman_resource_quota' @@ -42,10 +44,48 @@ class Engine < ::Rails::Engine Rails.logger.warn "ForemanResourceQuota: skipping engine hook (#{e})" end + # Register ForemanTasks-based recurring logic/scheduled tasks + initializer 'foreman_resource_quota.register_scheduled_tasks', before: :finisher_hook do |_app| + action_paths = [ForemanResourceQuota::Engine.root.join('lib/foreman_resource_quota/async')] + ::ForemanTasks.dynflow.config.eager_load_paths.concat(action_paths) + + # Skip object creation if the admin user is not present + # skip database manipulations while tables do not exist, like in migrations + if ActiveRecord::Base.connection.data_source_exists?(ForemanTasks::Task.table_name) && + User.unscoped.find_by_login(User::ANONYMOUS_ADMIN).present? + # Register the scheduled tasks + ::ForemanTasks.dynflow.config.on_init(false) do |_world| + ForemanResourceQuota::Engine.register_scheduled_task( + ForemanResourceQuota::Async::RefreshResourceQuotaUtilization, + '0 1 * * *' + ) + end + end + rescue ActiveRecord::NoDatabaseError => e + Rails.logger.warn "ForemanResourceQuota: skipping ForemanTasks registration hook (#{e})" + end + initializer 'foreman_resource_quota.register_gettext', after: :load_config_initializers do |_app| locale_dir = File.join(File.expand_path('../..', __dir__), 'locale') locale_domain = 'foreman_resource_quota' Foreman::Gettext::Support.add_text_domain locale_domain, locale_dir end + + # Helper to register ForemanTasks + def self.register_scheduled_task(task_class, cronline) + unless ::ForemanTasks::RecurringLogic.joins(:tasks) + .merge(::ForemanTasks::Task.where(label: task_class.name)) + .exists? + ::ForemanTasks::RecurringLogic.transaction(isolation: :serializable) do + User.as_anonymous_admin do + recurring_logic = ::ForemanTasks::RecurringLogic.new_from_cronline(cronline) + recurring_logic.save! + recurring_logic.start(task_class) + end + rescue ActiveRecord::TransactionIsolationError => e + Rails.logger.warn "ForemanResourceQuota: skipping RecurringLogic registration hook (#{e})" + end + end + end end end