diff --git a/.rubocop.yml b/.rubocop.yml index d08c168..342509a 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -14,6 +14,10 @@ Layout/ArgumentAlignment: Gemspec/RequiredRubyVersion: Enabled: false +Metrics/ClassLength: + Exclude: + - 'app/models/foreman_resource_quota/resource_quota.rb' + Metrics/MethodLength: Enabled: false diff --git a/app/controllers/foreman_resource_quota/api/v2/resource_quotas_controller.rb b/app/controllers/foreman_resource_quota/api/v2/resource_quotas_controller.rb index 84bf925..5f11432 100644 --- a/app/controllers/foreman_resource_quota/api/v2/resource_quotas_controller.rb +++ b/app/controllers/foreman_resource_quota/api/v2/resource_quotas_controller.rb @@ -14,7 +14,7 @@ class ResourceQuotasController < ::Api::V2::BaseController end before_action :find_resource, only: %i[show update destroy] - before_action :custom_find_resource, only: %i[utilization hosts users usergroups] + before_action :custom_find_resource, only: %i[utilization missing_hosts hosts users usergroups] api :GET, '/resource_quotas', N_('List all resource quotas') param_group :search_and_pagination, ::Api::V2::BaseController @@ -35,6 +35,14 @@ def utilization process_response @resource_quota end + api :GET, '/resource_quotas/:id/missing_hosts', + N_('Show resources could not be determined when calculating utilization') + param :id, :identifier, required: true + def missing_hosts + @resource_quota.determine_utilization + process_response @resource_quota + end + api :GET, '/resource_quotas/:id/hosts', N_('Show hosts of a resource quota') param :id, :identifier, required: true def hosts diff --git a/app/helpers/foreman_resource_quota/resource_quota_helper.rb b/app/helpers/foreman_resource_quota/resource_quota_helper.rb index 59d1bc7..683ef40 100644 --- a/app/helpers/foreman_resource_quota/resource_quota_helper.rb +++ b/app/helpers/foreman_resource_quota/resource_quota_helper.rb @@ -74,12 +74,8 @@ def utilization_from_resource_origins(resources, hosts, custom_resource_origins: # { : [] } # for example: # { - # "host_a": { - # [ :cpu_cores, :disk_gb ] - # }, - # "host_b": { - # [ :cpu_cores, :disk_gb ] - # }, + # "host_a": [ :cpu_cores, :disk_gb ], + # "host_b": [ :cpu_cores, :disk_gb ], # Parameters: # - hosts: Array of host objects. # - resources: Array of resources (as symbol, e.g. [:cpu_cores, :disk_gb]). diff --git a/app/models/concerns/foreman_resource_quota/host_managed_extensions.rb b/app/models/concerns/foreman_resource_quota/host_managed_extensions.rb index 3a5b28b..763232b 100644 --- a/app/models/concerns/foreman_resource_quota/host_managed_extensions.rb +++ b/app/models/concerns/foreman_resource_quota/host_managed_extensions.rb @@ -10,6 +10,8 @@ module HostManagedExtensions validate :check_resource_quota_capacity belongs_to :resource_quota, class_name: '::ForemanResourceQuota::ResourceQuota' + has_one :resource_quota_missing_resources, class_name: '::ForemanResourceQuota::ResourceQuotaMissingHost', + inverse_of: :missing_host, foreign_key: :missing_host_id, dependent: :destroy scoped_search relation: :resource_quota, on: :name, complete_value: true, rename: :resource_quota end diff --git a/app/models/foreman_resource_quota/resource_quota.rb b/app/models/foreman_resource_quota/resource_quota.rb index e6842d7..79b0c24 100644 --- a/app/models/foreman_resource_quota/resource_quota.rb +++ b/app/models/foreman_resource_quota/resource_quota.rb @@ -3,6 +3,7 @@ module ForemanResourceQuota class ResourceQuota < ApplicationRecord include ResourceQuotaHelper + include Exceptions include Authorizable include Parameterizable::ByIdName extend FriendlyId @@ -12,20 +13,19 @@ class ResourceQuota < ApplicationRecord self.table_name = 'resource_quotas' has_many :resource_quotas_users, class_name: 'ResourceQuotaUser', inverse_of: :resource_quota, dependent: :destroy - has_many :users, class_name: '::User', through: :resource_quotas_users has_many :resource_quotas_usergroups, class_name: 'ResourceQuotaUsergroup', inverse_of: :resource_quota, dependent: :destroy - has_many :usergroups, class_name: '::Usergroup', through: :resource_quotas_usergroups + has_many :resource_quotas_missing_hosts, class_name: 'ResourceQuotaMissingHost', inverse_of: :resource_quota, + dependent: :destroy has_many :hosts, class_name: '::Host::Managed', dependent: :nullify + has_many :users, class_name: '::User', through: :resource_quotas_users + has_many :usergroups, class_name: '::Usergroup', through: :resource_quotas_usergroups validates :name, presence: true, uniqueness: true scoped_search on: :name, complete_value: true scoped_search on: :id, complete_enabled: false, only_explicit: true, validator: ScopedSearch::Validators::INTEGER - attribute :utilization, :jsonb, default: {} - attribute :missing_hosts, :jsonb, default: {} - def number_of_hosts hosts.size end @@ -38,13 +38,68 @@ def number_of_usergroups usergroups.size end + def number_of_missing_hosts + missing_hosts.size + end + + # Returns a Hash with host name as key and a list of missing resources as value + # { : [] } + # for example: + # { + # "host_a": [ :cpu_cores, :disk_gb ], + # "host_b": [ :memory_mb ], + # } + def missing_hosts + # Initialize default value as an empty array + missing_hosts_list = Hash.new { |hash, key| hash[key] = [] } + resource_quotas_missing_hosts.each do |missing_host_rel| + host_name = missing_host_rel.missing_host.name + missing_hosts_list[host_name] << :cpu_cores if missing_host_rel.no_cpu_cores + missing_hosts_list[host_name] << :memory_mb if missing_host_rel.no_memory_mb + missing_hosts_list[host_name] << :disk_gb if missing_host_rel.no_disk_gb + end + missing_hosts_list + end + + # Set the hosts that are listed in resource_quotas_missing_hosts + # Parameters: + # - val: Hash of host names and list of missing resources + # { : [] } + # for example: + # { + # "host_a": [ :cpu_cores, :disk_gb ], + # "host_b": [ :memory_mb ], + # } + def missing_hosts=(val) + # Delete all entries and write new ones + resource_quotas_missing_hosts.delete_all + val.each do |host_name, missing_resources| + add_missing_host(host_name, missing_resources) + end + end + + def utilization + { + cpu_cores: utilization_cpu_cores, + memory_mb: utilization_memory_mb, + disk_gb: utilization_disk_gb, + } + end + + def utilization=(val) + update_single_utilization(:cpu_cores, val) + update_single_utilization(:memory_mb, val) + update_single_utilization(:disk_gb, val) + end + def determine_utilization(additional_hosts = []) quota_hosts = (hosts | (additional_hosts)) - self.utilization, self.missing_hosts = call_utilization_helper(quota_hosts) - - print_warning(missing_hosts, quota_hosts) unless missing_hosts.empty? + quota_utilization, missing_hosts_resources = call_utilization_helper(quota_hosts) + update(utilization: quota_utilization) + update(missing_hosts: missing_hosts_resources) + Rails.logger.warn create_hosts_resources_warning(missing_hosts_resources) unless missing_hosts.empty? rescue StandardError => e - print_error(e) # print error log here and forward error + Rails.logger.error("An error occured while determining resources for quota '#{name}': #{e}") raise e end @@ -67,17 +122,31 @@ def call_utilization_helper(quota_hosts) utilization_from_resource_origins(active_resources, quota_hosts) end - def print_warning(missing_hosts, hosts) - warn_text = "Could not determines resources for #{missing_hosts.size} hosts:" - missing_hosts.each do |host_id, missing_resources| - missing_host = hosts.find { |obj| obj.id == host_id } - warn_text << " '#{missing_host.name}': '#{missing_resources}'\n" unless missing_host.nil? + def create_hosts_resources_warning(missing_hosts_resources) + warn_text = +"Could not determines resources for #{missing_hosts_resources.size} hosts:" + missing_hosts_resources.each do |host_name, missing_resources| + warn_text << " '#{host_name}': '#{missing_resources}'\n" unless missing_resources.empty? end - Rails.logger.warn warn_text end - def print_error(err) - Rails.logger.error("An error occured while determining resources for quota '#{name}': #{err}") + def update_single_utilization(attribute, val) + return unless val.key?(attribute.to_sym) || val.key?(attribute.to_s) + update("utilization_#{attribute}": val[attribute.to_sym] || val[attribute.to_s]) + end + + def add_missing_host(host_name, missing_resources) + return if missing_resources.empty? + + host = Host::Managed.find_by(name: host_name) + raise HostNotFoundException if host.nil? + + resource_quotas_missing_hosts << ResourceQuotaMissingHost.new( + missing_host: host, + resource_quota: self, + no_cpu_cores: missing_resources.include?(:cpu_cores), + no_memory_mb: missing_resources.include?(:memory_mb), + no_disk_gb: missing_resources.include?(:disk_gb) + ) end end end diff --git a/app/models/foreman_resource_quota/resource_quota_missing_host.rb b/app/models/foreman_resource_quota/resource_quota_missing_host.rb new file mode 100644 index 0000000..00786b8 --- /dev/null +++ b/app/models/foreman_resource_quota/resource_quota_missing_host.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +module ForemanResourceQuota + class ResourceQuotaMissingHost < ApplicationRecord + self.table_name = 'resource_quotas_missing_hosts' + + belongs_to :resource_quota, inverse_of: :resource_quotas_missing_hosts + belongs_to :missing_host, class_name: '::Host::Managed', inverse_of: :resource_quota_missing_resources + end +end diff --git a/app/views/foreman_resource_quota/api/v2/resource_quotas/base.json.rabl b/app/views/foreman_resource_quota/api/v2/resource_quotas/base.json.rabl index 6b98f79..ba8ef85 100644 --- a/app/views/foreman_resource_quota/api/v2/resource_quotas/base.json.rabl +++ b/app/views/foreman_resource_quota/api/v2/resource_quotas/base.json.rabl @@ -3,4 +3,4 @@ object @resource_quota attributes :name, :id, :description, :cpu_cores, :memory_mb, :disk_gb, :number_of_hosts, :number_of_users, - :number_of_usergroups + :number_of_usergroups, :number_of_missing_hosts, :utilization diff --git a/app/views/foreman_resource_quota/api/v2/resource_quotas/missing_hosts.json.rabl b/app/views/foreman_resource_quota/api/v2/resource_quotas/missing_hosts.json.rabl new file mode 100644 index 0000000..d9ea1ec --- /dev/null +++ b/app/views/foreman_resource_quota/api/v2/resource_quotas/missing_hosts.json.rabl @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +object @resource_quota + +extends 'api/v2/resource_quotas/main' + +attributes :missing_hosts diff --git a/config/routes.rb b/config/routes.rb index 7c832ed..3c70bb2 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -31,6 +31,7 @@ end constraints(id: %r{[^/]+}) do get 'utilization' + get 'missing_hosts' get 'hosts' get 'users' get 'usergroups' diff --git a/db/migrate/20230306120001_create_resource_quotas.rb b/db/migrate/20230306120001_create_resource_quotas.rb index 371c4b2..2f710a9 100644 --- a/db/migrate/20230306120001_create_resource_quotas.rb +++ b/db/migrate/20230306120001_create_resource_quotas.rb @@ -9,6 +9,9 @@ def change t.integer :cpu_cores, default: nil t.integer :memory_mb, default: nil t.integer :disk_gb, default: nil + t.integer :utilization_cpu_cores, default: nil + t.integer :utilization_memory_mb, default: nil + t.integer :utilization_disk_gb, default: nil t.timestamps end @@ -24,6 +27,16 @@ def change t.belongs_to :user t.timestamps end + + create_table :resource_quotas_missing_hosts do |t| + t.references :resource_quota, null: false, foreign_key: { to_table: :resource_quotas } + t.references :missing_host, null: false, unique: true, foreign_key: { to_table: :hosts } + t.boolean :no_cpu_cores, default: false + t.boolean :no_memory_mb, default: false + t.boolean :no_disk_gb, default: false + t.timestamps + end + add_reference :hosts, :resource_quota, foreign_key: { to_table: :resource_quotas } add_column :users, :resource_quota_is_optional, :boolean, default: false end diff --git a/lib/foreman_resource_quota/exceptions.rb b/lib/foreman_resource_quota/exceptions.rb index a18d3d6..aab17c6 100644 --- a/lib/foreman_resource_quota/exceptions.rb +++ b/lib/foreman_resource_quota/exceptions.rb @@ -7,5 +7,6 @@ class HostResourceQuotaEmptyException < ResourceQuotaException; end class ResourceLimitException < ResourceQuotaException; end class HostResourcesException < ResourceQuotaException; end class ResourceQuotaUtilizationException < ResourceQuotaException; end + class HostNotFoundException < ResourceQuotaException; end end end diff --git a/lib/foreman_resource_quota/register.rb b/lib/foreman_resource_quota/register.rb index 345f8ae..9cae950 100644 --- a/lib/foreman_resource_quota/register.rb +++ b/lib/foreman_resource_quota/register.rb @@ -11,9 +11,10 @@ security_block :foreman_resource_quota do permission 'view_foreman_resource_quota/resource_quotas', { 'foreman_resource_quota/resource_quotas': %i[index welcome auto_complete_search], - 'foreman_resource_quota/api/v2/resource_quotas': %i[index show utilization hosts users usergroups + 'foreman_resource_quota/api/v2/resource_quotas': %i[index show utilization missing_hosts hosts users usergroups auto_complete_search], - 'foreman_resource_quota/api/v2/resource_quotas/:resource_quota_id/': %i[utilization hosts users usergroups] }, + 'foreman_resource_quota/api/v2/resource_quotas/:resource_quota_id/': %i[utilization missing_hosts hosts users + usergroups] }, resource_type: 'ForemanResourceQuota::ResourceQuota' permission 'create_foreman_resource_quota/resource_quotas', { 'foreman_resource_quota/resource_quotas': %i[new create], diff --git a/test/controllers/api/v2/resource_quotas_test.rb b/test/controllers/api/v2/resource_quotas_test.rb index 555117e..a690bdb 100644 --- a/test/controllers/api/v2/resource_quotas_test.rb +++ b/test/controllers/api/v2/resource_quotas_test.rb @@ -13,12 +13,6 @@ def setup as_admin { @quota.save! } end - def add_quota - quota = FactoryBot.create :resource_quota - as_admin { @quota.save! } - quota - end - test 'should get index with quotas' do get :index, session: set_session_user assert_response :success @@ -126,6 +120,30 @@ def add_quota assert_response :not_found assert_equal nof_quota_before, ResourceQuota.all.size end + + test 'should show utilization' do + exp_utilization = { cpu_cores: 10, memory_mb: 20 } + stub_quota_utilization(exp_utilization, {}) + get :utilization, params: { resource_quota_id: @quota.id }, session: set_session_user + assert_response :success + show_response = ActiveSupport::JSON.decode(@response.body) + assert_not show_response.empty? + assert_equal @quota.id, show_response['id'] + assert_equal exp_utilization, show_response['utilization'].transform_keys(&:to_sym) + end + + test 'should show missing_hosts' do + exp_missing_hosts = { 'some_host' => %i[cpu_cores memory_mb] } + stub_quota_utilization({}, exp_missing_hosts) + get :missing_hosts, params: { resource_quota_id: @quota.id }, session: set_session_user + assert_response :success + show_response = ActiveSupport::JSON.decode(@response.body) + assert_not show_response.empty? + assert_equal @quota.id, show_response['id'] + # JSON.decode makes everything strings -> convert 'some_host' value to symbols: + assert_equal(exp_missing_hosts, + show_response['missing_hosts'].transform_values { |value| value.map(&:to_sym) }) + end end end end diff --git a/test/models/concerns/host_managed_extension_test.rb b/test/models/concerns/host_managed_extension_test.rb index a678893..4f42e58 100644 --- a/test/models/concerns/host_managed_extension_test.rb +++ b/test/models/concerns/host_managed_extension_test.rb @@ -47,20 +47,9 @@ def setup User.current.resource_quota_is_optional = false end - def stub_quota_utilization(return_utilization, return_missing_hosts) - ResourceQuota.any_instance.stubs(:call_utilization_helper) - .returns([return_utilization, return_missing_hosts]) - end - - def stub_host_utilization(return_utilization, return_missing_hosts) - Host::Managed.any_instance.stubs(:call_utilization_helper) - .returns([return_utilization, return_missing_hosts]) - end - test 'should fail at determine utilization' do stub_quota_utilization({}, { 'my.missing.host': [:cpu_cores] }) # fail on quota utilization stub_host_utilization({ cpu_cores: 5 }, {}) # pass host utilization - host = FactoryBot.create(:host, :with_resource_quota) host.resource_quota.update!(cpu_cores: 10) @@ -168,9 +157,10 @@ def stub_host_utilization(return_utilization, return_missing_hosts) assert host.save # TODO: Test must be adapted, when host resources are added to resource quota - # assert_equal nil, host.resource_quota.utilization[:cpu_cores] # assert_equal 10 * 1024, host.resource_quota.utilization[:memory_mb] - # assert_equal nil, host.resource_quota.utilization[:disk_gb] + assert_nil host.resource_quota.utilization[:cpu_cores] + assert_equal 0, host.resource_quota.utilization[:memory_mb] + assert_nil host.resource_quota.utilization[:disk_gb] end test 'should validate multi limit capacity (host only)' do @@ -187,6 +177,9 @@ def stub_host_utilization(return_utilization, return_missing_hosts) # assert_equal 5, host.resource_quota.utilization[:cpu_cores] # assert_equal 10 * 1024, host.resource_quota.utilization[:memory_mb] # assert_equal 0, host.resource_quota.utilization[:disk_gb] + assert_equal 0, host.resource_quota.utilization[:cpu_cores] + assert_equal 0, host.resource_quota.utilization[:memory_mb] + assert_equal 0, host.resource_quota.utilization[:disk_gb] end test 'should validate multi limit capacity (with quota utilization)' do @@ -203,6 +196,9 @@ def stub_host_utilization(return_utilization, return_missing_hosts) # assert_equal 7, host.resource_quota.utilization[:cpu_cores] # assert_equal 9 * 1024, host.resource_quota.utilization[:memory_mb] # assert_equal 30, host.resource_quota.utilization[:disk_gb] + assert_equal 5, host.resource_quota.utilization[:cpu_cores] + assert_equal 5 * 1024, host.resource_quota.utilization[:memory_mb] + assert_equal 10, host.resource_quota.utilization[:disk_gb] end end end diff --git a/test/models/resource_quota_test.rb b/test/models/resource_quota_test.rb index 7fece1e..d99532c 100644 --- a/test/models/resource_quota_test.rb +++ b/test/models/resource_quota_test.rb @@ -28,6 +28,24 @@ def setup assert_equal @quota.id, @user.resource_quotas[0].id end + test 'users relation delete user' do + @quota.users << @user + as_admin { @quota.save! } + assert_equal @user.id, @quota.users[0].id + assert_equal @quota.id, @user.resource_quotas[0].id + as_admin { @user.destroy! } + assert_empty @quota.reload.users + end + + test 'users relation delete quota' do + @user.resource_quotas << @quota + as_admin { @user.save! } + assert_equal @quota.users[0].id, @user.id + assert_equal @user.resource_quotas[0].id, @quota.id + as_admin { @quota.destroy! } + assert_empty @user.reload.resource_quotas + end + test 'usergroups relation' do @quota.usergroups << @usergroup as_admin { @quota.save! } @@ -35,6 +53,15 @@ def setup assert_equal @quota.id, @usergroup.resource_quotas[0].id end + test 'usergroup delete' do + @quota.usergroups << @usergroup + as_admin { @quota.save! } + assert_equal @usergroup.id, @quota.usergroups[0].id + assert_equal @quota.id, @usergroup.resource_quotas[0].id + as_admin { @usergroup.destroy! } + assert_empty @quota.reload.usergroups + end + test 'number of hosts' do second_host = FactoryBot.create :host third_host = FactoryBot.create :host @@ -56,6 +83,42 @@ def setup assert_equal 3, @quota.number_of_usergroups end + test 'utilization is set (cpu_cores)' do + @quota.utilization_cpu_cores = 13 + assert_equal 13, @quota.utilization[:cpu_cores] + end + + test 'utilization is set (memory_mb)' do + @quota.utilization_memory_mb = 14 + assert_equal 14, @quota.utilization[:memory_mb] + end + + test 'utilization is set (disk_gb)' do + @quota.utilization_disk_gb = 15 + assert_equal 15, @quota.utilization[:disk_gb] + end + + test 'utilization is set (all parameters)' do + exp_utilization = { cpu_cores: 3, memory_mb: 4, disk_gb: 5 } + @quota.utilization_cpu_cores = exp_utilization[:cpu_cores] + @quota.utilization_memory_mb = exp_utilization[:memory_mb] + @quota.utilization_disk_gb = exp_utilization[:disk_gb] + assert_equal exp_utilization, @quota.utilization + end + + test 'utilization_ is set by utilization' do + exp_utilization = { cpu_cores: 6, memory_mb: 7, disk_gb: 8 } + @quota.utilization = exp_utilization + assert_equal exp_utilization, @quota.utilization + end + + test 'utilization sets attributes' do + second_usergroup = FactoryBot.create :usergroup + third_usergroup = FactoryBot.create :usergroup + @quota.usergroups << [@usergroup, second_usergroup, third_usergroup] + assert_equal 3, @quota.number_of_usergroups + end + test 'determine utilization' do exp_utilization = { cpu_cores: 1, memory_mb: 1, disk_gb: 2 } exp_missing_hosts = {} @@ -66,9 +129,99 @@ def setup @quota.stub(:call_utilization_helper, [exp_utilization, exp_missing_hosts]) do @quota.determine_utilization end - assert_equal exp_utilization.transform_keys(&:to_s), @quota.utilization + assert_equal exp_utilization, @quota.utilization assert_equal exp_missing_hosts, @quota.missing_hosts end + + test 'determine utilization stores missing hosts' do + host_a = FactoryBot.create :host + host_b = FactoryBot.create :host + exp_utilization = { cpu_cores: 1, memory_mb: 1, disk_gb: 2 } + exp_missing_hosts = { host_a.name => [:cpu_cores], host_b.name => [:memory_mb] } + @quota.hosts << [host_a, host_b] + @quota.update(cpu_cores: 10, memory_mb: 10, disk_gb: 10) + as_admin { @quota.save! } + + @quota.stub(:call_utilization_helper, [exp_utilization, exp_missing_hosts]) do + @quota.determine_utilization + end + assert_equal exp_utilization, @quota.utilization + assert_equal exp_missing_hosts, @quota.missing_hosts + end + + test 'utilization uses quota utilization_ fields' do + exp_utilization = { cpu_cores: 1, memory_mb: 1, disk_gb: 2 } + @quota.utilization_cpu_cores = exp_utilization[:cpu_cores] + @quota.utilization_memory_mb = exp_utilization[:memory_mb] + @quota.utilization_disk_gb = exp_utilization[:disk_gb] + + assert_equal exp_utilization, @quota.utilization + end + + test 'missing_hosts are constructed' do + host_a = FactoryBot.create :host + host_b = FactoryBot.create :host + exp_utilization = { cpu_cores: 1, memory_mb: 1, disk_gb: 2 } + exp_missing_hosts = { host_a.name => [:cpu_cores], host_b.name => [:memory_mb] } + @quota.hosts << [host_a, host_b] + @quota.update(cpu_cores: 10, memory_mb: 10, disk_gb: 10) + as_admin { @quota.save! } + + @quota.stub(:call_utilization_helper, [exp_utilization, exp_missing_hosts]) do + @quota.determine_utilization + end + @quota.reload + assert_equal exp_missing_hosts, @quota.missing_hosts + assert_equal 2, @quota.resource_quotas_missing_hosts.size + assert_equal host_a.id, @quota.resource_quotas_missing_hosts.find_by(missing_host_id: host_a.id).missing_host_id + assert_equal host_b.id, @quota.resource_quotas_missing_hosts.find_by(missing_host_id: host_b.id).missing_host_id + assert_equal host_a.resource_quota_missing_resources.resource_quota.id, @quota.id + end + + test 'missing_hosts are destroyed on host destroy' do + host_a = FactoryBot.create :host + host_b = FactoryBot.create :host + exp_utilization = { cpu_cores: 1, memory_mb: 1, disk_gb: 2 } + exp_missing_hosts = { host_a.name => [:cpu_cores], host_b.name => [:memory_mb] } + @quota.hosts << [host_a, host_b] + @quota.update(cpu_cores: 10, memory_mb: 10, disk_gb: 10) + as_admin { @quota.save! } + + @quota.stub(:call_utilization_helper, [exp_utilization, exp_missing_hosts]) do + @quota.determine_utilization + end + assert_equal 2, @quota.resource_quotas_missing_hosts.size + host_a.destroy! + @quota.reload + assert_equal 1, @quota.resource_quotas_missing_hosts.size + assert_equal host_b.id, @quota.resource_quotas_missing_hosts[0].missing_host.id + host_b.destroy! + @quota.reload + assert_equal 0, @quota.resource_quotas_missing_hosts.size + end + + test 'missing_hosts are destroyed on re-computing utilization' do + host_a = FactoryBot.create :host + host_b = FactoryBot.create :host + exp_utilization = { cpu_cores: 1, memory_mb: 1, disk_gb: 2 } + exp_missing_hosts_two = { host_a.name => [:cpu_cores], host_b.name => [:memory_mb] } + exp_missing_hosts_one = { host_b.name => [:memory_mb] } + @quota.hosts << [host_a, host_b] + @quota.update(cpu_cores: 10, memory_mb: 10, disk_gb: 10) + as_admin { @quota.save! } + + @quota.stub(:call_utilization_helper, [exp_utilization, exp_missing_hosts_two]) do + @quota.determine_utilization + end + assert_equal 2, @quota.resource_quotas_missing_hosts.size + @quota.stub(:call_utilization_helper, [exp_utilization, exp_missing_hosts_one]) do + @quota.determine_utilization + end + @quota.reload + assert_equal 1, @quota.resource_quotas_missing_hosts.size + assert_equal host_b.id, @quota.resource_quotas_missing_hosts + .find_by(missing_host_id: host_b.id).missing_host_id + end end end end diff --git a/test/test_plugin_helper.rb b/test/test_plugin_helper.rb index 00d9da7..83a97a6 100644 --- a/test/test_plugin_helper.rb +++ b/test/test_plugin_helper.rb @@ -29,3 +29,21 @@ def assert_quota_equal(expexted_list, quota) end end end + +def stub_quota_utilization(return_utilization, return_missing_hosts) + ForemanResourceQuota::ResourceQuota.any_instance.stubs(:call_utilization_helper) + .returns([return_utilization, return_missing_hosts]) + ForemanResourceQuota::ResourceQuota.any_instance.stubs(:missing_hosts) + .returns(return_missing_hosts) + ForemanResourceQuota::ResourceQuota.any_instance.stubs(:missing_hosts=) + .returns + ForemanResourceQuota::ResourceQuota.any_instance.stubs(:utilization) + .returns(return_utilization) + ForemanResourceQuota::ResourceQuota.any_instance.stubs(:utilization=) + .returns +end + +def stub_host_utilization(return_utilization, return_missing_hosts) + Host::Managed.any_instance.stubs(:call_utilization_helper) + .returns([return_utilization, return_missing_hosts]) +end