foreman_resource_quota 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/app/controllers/foreman_resource_quota/api/v2/resource_quotas_controller.rb +0 -1
- data/app/helpers/foreman_resource_quota/resource_quota_helper.rb +31 -3
- data/app/models/concerns/foreman_resource_quota/host_managed_extensions.rb +77 -34
- data/app/models/foreman_resource_quota/host_resources.rb +43 -0
- data/app/models/foreman_resource_quota/resource_quota.rb +73 -59
- data/app/models/foreman_resource_quota/resource_quota_host.rb +10 -0
- data/app/services/foreman_resource_quota/resource_origin.rb +4 -4
- data/app/services/foreman_resource_quota/resource_origins/compute_resource_origin.rb +6 -6
- data/config/initializers/inflections.rb +2 -0
- data/db/migrate/20240611141744_remove_utilization_from_resource_quotas.rb +9 -0
- data/db/migrate/20240611141939_drop_missing_hosts.rb +7 -0
- data/db/migrate/20240611142813_create_hosts_resources.rb +21 -0
- data/db/migrate/20240618163434_remove_resource_quota_from_hosts.rb +7 -0
- data/lib/foreman_resource_quota/async/refresh_resource_quota_utilization.rb +25 -0
- data/lib/foreman_resource_quota/engine.rb +43 -9
- data/lib/foreman_resource_quota/register.rb +17 -17
- data/lib/foreman_resource_quota/version.rb +1 -1
- data/package.json +2 -2
- data/webpack/components/ResourceQuotaForm/components/Properties/index.js +6 -4
- data/webpack/components/ResourceQuotaForm/components/Resource/UtilizationProgress.js +1 -1
- metadata +45 -5
- data/app/models/foreman_resource_quota/resource_quota_missing_host.rb +0 -10
- /data/{lib → app/lib}/foreman_resource_quota/exceptions.rb +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e5ae7058a06490ad3f9c4b8a299834a4c5cf013cabfd0a6123bf9ba4d26c3c47
|
4
|
+
data.tar.gz: ddc85109fc571d9d723181765dcb81b8302acfb721556fcd1fd9739af234baf9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 88f0f162d137fe245f38733a9f361624d81f36d889d114877fe70654b67d0de8f31daf3916aa029da9d9cf5f30ebc19b8152f565f24709e565fe74edd6ed98a1
|
7
|
+
data.tar.gz: 1bef610395fd2a5901623c9370d70339b8b12f53bd7d7f96aefe80d70607daa73c1b731b88ca2a73578219ee2cf59786fa8d3147ff0b092e9cc7e6209faa9230
|
@@ -64,7 +64,6 @@ module ForemanResourceQuota
|
|
64
64
|
def_param_group :resource_quota do
|
65
65
|
param :resource_quota, Hash, required: true, action_aware: true do
|
66
66
|
param :name, String, required: true
|
67
|
-
# param :operatingsystem_ids, Array, :desc => N_("Operating system IDs")
|
68
67
|
end
|
69
68
|
end
|
70
69
|
|
@@ -52,20 +52,29 @@ module ForemanResourceQuota
|
|
52
52
|
format(format_text, unit_applied_value, symbol)
|
53
53
|
end
|
54
54
|
|
55
|
+
# Use different resource origins to determine host resource utilization.
|
56
|
+
# - iterates all given hosts and tries do determine their resources utilization
|
57
|
+
# Returns:
|
58
|
+
# [ <hosts_resources>, <missing_hosts_resources> ]
|
59
|
+
# for example:
|
60
|
+
# [
|
61
|
+
# { "host_a": { cpu_cores: 20, memory_mb: 8196 }, "host_b": { cpu_cores: 15, memory_mb: nil } },
|
62
|
+
# { "host_c": [ :memory_mb ] },
|
63
|
+
# ]
|
55
64
|
def utilization_from_resource_origins(resources, hosts, custom_resource_origins: nil)
|
56
|
-
|
65
|
+
hosts_resources = create_hosts_resources_hash(hosts, resources)
|
57
66
|
missing_hosts_resources = create_missing_hosts_resources_hash(hosts, resources)
|
58
67
|
hosts_hash = hosts.index_by(&:name)
|
59
68
|
resource_classes = custom_resource_origins || default_resource_origin_classes
|
60
69
|
resource_classes.each do |origin_class|
|
61
70
|
origin_class.new.collect_resources!(
|
62
|
-
|
71
|
+
hosts_resources,
|
63
72
|
missing_hosts_resources,
|
64
73
|
hosts_hash
|
65
74
|
)
|
66
75
|
end
|
67
76
|
|
68
|
-
[
|
77
|
+
[hosts_resources, missing_hosts_resources]
|
69
78
|
end
|
70
79
|
|
71
80
|
private
|
@@ -89,6 +98,25 @@ module ForemanResourceQuota
|
|
89
98
|
hosts.map(&:name).index_with { resources_to_determine.clone }
|
90
99
|
end
|
91
100
|
|
101
|
+
# Create a Hash that maps resources and a value to host names.
|
102
|
+
# { <host name>: {<hash of resource values>} }
|
103
|
+
# for example:
|
104
|
+
# {
|
105
|
+
# "host_a": { cpu_cores: nil, disk_gb: nil },
|
106
|
+
# "host_b": { cpu_cores: nil, disk_gb: nil },
|
107
|
+
# }
|
108
|
+
# Parameters:
|
109
|
+
# - hosts: Array of host objects.
|
110
|
+
# - resources: Array of resources (as symbol, e.g. [:cpu_cores, :disk_gb]).
|
111
|
+
# Returns: Hash with host names as keys and resource-hashs as values.
|
112
|
+
def create_hosts_resources_hash(hosts, resources)
|
113
|
+
return {} if hosts.empty? || resources.empty?
|
114
|
+
|
115
|
+
# Create a hash template with resources mapped to nil
|
116
|
+
resources_to_determine = resources.index_with { |_resource| nil }
|
117
|
+
hosts.map(&:name).index_with { resources_to_determine.dup }
|
118
|
+
end
|
119
|
+
|
92
120
|
# Default classes that are used to determine host resources. Determines
|
93
121
|
# resources in the order of this list.
|
94
122
|
def default_resource_origin_classes
|
@@ -7,16 +7,23 @@ module ForemanResourceQuota
|
|
7
7
|
include ForemanResourceQuota::Exceptions
|
8
8
|
|
9
9
|
included do
|
10
|
-
validate :
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
10
|
+
validate :verify_resource_quota
|
11
|
+
|
12
|
+
has_one :host_resources, class_name: '::ForemanResourceQuota::HostResources',
|
13
|
+
inverse_of: :host, foreign_key: :host_id, dependent: :destroy
|
14
|
+
has_one :resource_quota_host, class_name: '::ForemanResourceQuota::ResourceQuotaHost',
|
15
|
+
inverse_of: :host, foreign_key: :host_id, dependent: :destroy
|
16
|
+
has_one :resource_quota, class_name: '::ForemanResourceQuota::ResourceQuota',
|
17
|
+
through: :resource_quota_host
|
15
18
|
scoped_search relation: :resource_quota, on: :name, complete_value: true, rename: :resource_quota
|
19
|
+
|
20
|
+
# A host shall always have a .host_resources attribute
|
21
|
+
before_validation :build_host_resources, unless: -> { host_resources.present? }
|
22
|
+
after_save :save_host_resources, if: -> { host_resources.changed? }
|
16
23
|
end
|
17
24
|
|
18
|
-
def
|
19
|
-
handle_quota_check
|
25
|
+
def verify_resource_quota
|
26
|
+
handle_quota_check(resource_quota)
|
20
27
|
true
|
21
28
|
rescue ResourceQuotaException => e
|
22
29
|
handle_error('resource_quota_id',
|
@@ -32,13 +39,27 @@ module ForemanResourceQuota
|
|
32
39
|
format('An unknown error occured while checking the resource quota capacity: %s', e))
|
33
40
|
end
|
34
41
|
|
42
|
+
def resource_quota_id
|
43
|
+
resource_quota&.id
|
44
|
+
end
|
45
|
+
|
46
|
+
def resource_quota_id=(val)
|
47
|
+
if val.blank?
|
48
|
+
resource_quota_host&.destroy
|
49
|
+
else
|
50
|
+
quota = ForemanResourceQuota::ResourceQuota.find_by(id: val)
|
51
|
+
raise ActiveRecord::RecordNotFound, "ResourceQuota with ID \"#{val}\" not found" unless quota
|
52
|
+
self.resource_quota = quota
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
35
56
|
private
|
36
57
|
|
37
|
-
def handle_quota_check
|
38
|
-
return if early_return?
|
39
|
-
quota_utilization = determine_quota_utilization
|
40
|
-
|
41
|
-
|
58
|
+
def handle_quota_check(quota)
|
59
|
+
return if early_return?(quota)
|
60
|
+
quota_utilization = determine_quota_utilization(quota)
|
61
|
+
current_host_resources = determine_host_resources(quota.active_resources)
|
62
|
+
check_resource_quota_limits(quota, quota_utilization, current_host_resources)
|
42
63
|
end
|
43
64
|
|
44
65
|
def handle_error(error_module, error_message, log_message)
|
@@ -47,60 +68,72 @@ module ForemanResourceQuota
|
|
47
68
|
false
|
48
69
|
end
|
49
70
|
|
50
|
-
def determine_quota_utilization
|
51
|
-
|
52
|
-
missing_hosts = resource_quota.missing_hosts
|
71
|
+
def determine_quota_utilization(quota)
|
72
|
+
missing_hosts = quota.missing_hosts(exclude: [name])
|
53
73
|
unless missing_hosts.empty?
|
54
74
|
raise ResourceQuotaUtilizationException,
|
55
|
-
"Resource Quota '#{
|
75
|
+
"Resource Quota '#{quota.name}' cannot determine resources for #{missing_hosts.size} hosts."
|
56
76
|
end
|
57
|
-
|
77
|
+
quota.utilization(exclude: [name])
|
58
78
|
end
|
59
79
|
|
60
|
-
def determine_host_resources
|
61
|
-
|
62
|
-
|
80
|
+
def determine_host_resources(active_resources)
|
81
|
+
new_host_resources, missing_hosts = call_utilization_helper(active_resources, [self])
|
82
|
+
if missing_hosts.key?(name) || missing_hosts.key?(name.to_sym)
|
63
83
|
raise HostResourcesException,
|
64
|
-
"Cannot determine host resources for #{name}"
|
84
|
+
"Cannot determine host resources for #{name}: #{missing_hosts[name]}"
|
65
85
|
end
|
66
|
-
host_resources
|
86
|
+
host_resources.resources = new_host_resources
|
87
|
+
host_resources.resources
|
67
88
|
end
|
68
89
|
|
69
|
-
def
|
90
|
+
def check_resource_quota_limits(quota, quota_utilization, current_host_resources)
|
70
91
|
quota_utilization.each do |resource_type, resource_utilization|
|
71
92
|
next if resource_utilization.nil?
|
72
93
|
|
73
|
-
max_quota =
|
74
|
-
all_hosts_utilization = resource_utilization +
|
94
|
+
max_quota = quota[resource_type]
|
95
|
+
all_hosts_utilization = resource_utilization + current_host_resources[resource_type.to_sym]
|
75
96
|
next if all_hosts_utilization <= max_quota
|
76
97
|
|
77
|
-
raise ResourceLimitException, formulate_limit_error(resource_utilization,
|
98
|
+
raise ResourceLimitException, formulate_limit_error(quota.name, resource_utilization,
|
78
99
|
all_hosts_utilization, max_quota, resource_type)
|
79
100
|
end
|
80
101
|
end
|
81
102
|
|
82
|
-
def formulate_limit_error(resource_utilization, all_hosts_utilization, max_quota, resource_type)
|
83
|
-
if resource_utilization
|
103
|
+
def formulate_limit_error(quota_name, resource_utilization, all_hosts_utilization, max_quota, resource_type)
|
104
|
+
if resource_utilization <= max_quota
|
84
105
|
N_(format("Host exceeds %s limit of '%s'-quota by %s (max. %s)",
|
85
106
|
natural_resource_name_by_type(resource_type),
|
86
|
-
|
107
|
+
quota_name,
|
87
108
|
resource_value_to_string(all_hosts_utilization - max_quota, resource_type),
|
88
109
|
resource_value_to_string(max_quota, resource_type)))
|
89
110
|
else
|
90
111
|
N_(format("%s limit of '%s'-quota is already exceeded by %s without adding the new host (max. %s)",
|
91
112
|
natural_resource_name_by_type(resource_type),
|
92
|
-
|
113
|
+
quota_name,
|
93
114
|
resource_value_to_string(resource_utilization - max_quota, resource_type),
|
94
115
|
resource_value_to_string(max_quota, resource_type)))
|
95
116
|
end
|
96
117
|
end
|
97
118
|
|
98
|
-
def
|
99
|
-
|
119
|
+
def formulate_resource_inconsistency_error(quota_name, resource_type, quota_utilization_value, resource_value)
|
120
|
+
N_("Resource Quota '#{quota_name}' inconsistency detected while destroying host '#{name}':\n" \
|
121
|
+
"Resource Quota #{resource_type} current utilization: #{quota_utilization_value}.\n" \
|
122
|
+
"Host resource value: #{resource_value}.\n" \
|
123
|
+
'Skipping.')
|
124
|
+
end
|
125
|
+
|
126
|
+
def formulate_quota_inconsistency_error(quota_name)
|
127
|
+
N_("An error occured adapting the resource quota utilization of '#{quota_name}' " \
|
128
|
+
"while processing host '#{name}'. The resource quota utilization values might be inconsistent.")
|
129
|
+
end
|
130
|
+
|
131
|
+
def early_return?(quota)
|
132
|
+
if quota.nil?
|
100
133
|
return true if quota_assigment_optional?
|
101
134
|
raise HostResourceQuotaEmptyException, 'must be given.'
|
102
135
|
end
|
103
|
-
return true if
|
136
|
+
return true if quota.active_resources.empty?
|
104
137
|
return true if Setting[:resource_quota_global_no_action] # quota is assigned, but not supposed to be checked
|
105
138
|
false
|
106
139
|
end
|
@@ -109,9 +142,19 @@ module ForemanResourceQuota
|
|
109
142
|
owner.resource_quota_is_optional || Setting[:resource_quota_optional_assignment]
|
110
143
|
end
|
111
144
|
|
145
|
+
def save_host_resources
|
146
|
+
host_resources.save
|
147
|
+
end
|
148
|
+
|
112
149
|
# Wrap into a function for easier testing
|
113
150
|
def call_utilization_helper(resources, hosts)
|
114
|
-
utilization_from_resource_origins(resources, hosts)
|
151
|
+
all_host_resources, missing_hosts = utilization_from_resource_origins(resources, hosts)
|
152
|
+
unless all_host_resources.key?(name)
|
153
|
+
raise HostResourcesException,
|
154
|
+
"Host #{name} was not included when determining host resources."
|
155
|
+
end
|
156
|
+
current_host_resources = all_host_resources[name]
|
157
|
+
[current_host_resources, missing_hosts]
|
115
158
|
end
|
116
159
|
end
|
117
160
|
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ForemanResourceQuota
|
4
|
+
class HostResources < ApplicationRecord
|
5
|
+
self.table_name = 'hosts_resources'
|
6
|
+
|
7
|
+
belongs_to :host, class_name: '::Host::Managed'
|
8
|
+
validates :host, { presence: true, uniqueness: true }
|
9
|
+
|
10
|
+
def resources
|
11
|
+
{
|
12
|
+
cpu_cores: cpu_cores,
|
13
|
+
memory_mb: memory_mb,
|
14
|
+
disk_gb: disk_gb,
|
15
|
+
}
|
16
|
+
end
|
17
|
+
|
18
|
+
def resources=(val)
|
19
|
+
allowed_attributes = val.slice(:cpu_cores, :memory_mb, :disk_gb)
|
20
|
+
assign_attributes(allowed_attributes) # Set multiple attributes at once (given a hash)
|
21
|
+
end
|
22
|
+
|
23
|
+
# Returns an array of unknown host resources (returns an empty array if all are known)
|
24
|
+
# For example, completely unknown host resources returns:
|
25
|
+
# [
|
26
|
+
# :cpu_cores,
|
27
|
+
# :memory_mb,
|
28
|
+
# :disk_gb,
|
29
|
+
# ]
|
30
|
+
# Consider only the resource_quota's active resources by default.
|
31
|
+
def missing_resources(only_active_resources: true)
|
32
|
+
empty_resources = []
|
33
|
+
resources_to_check = %i[cpu_cores memory_mb disk_gb]
|
34
|
+
resources_to_check = host.resource_quota.active_resources if only_active_resources && host.resource_quota.present?
|
35
|
+
|
36
|
+
resources_to_check.each do |single_resource|
|
37
|
+
empty_resources << single_resource if send(single_resource).nil?
|
38
|
+
end
|
39
|
+
|
40
|
+
empty_resources
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -12,12 +12,12 @@ module ForemanResourceQuota
|
|
12
12
|
|
13
13
|
self.table_name = 'resource_quotas'
|
14
14
|
|
15
|
+
has_many :resource_quotas_hosts, class_name: 'ResourceQuotaHost', inverse_of: :resource_quota, dependent: :destroy
|
15
16
|
has_many :resource_quotas_users, class_name: 'ResourceQuotaUser', inverse_of: :resource_quota, dependent: :destroy
|
16
17
|
has_many :resource_quotas_usergroups, class_name: 'ResourceQuotaUsergroup', inverse_of: :resource_quota,
|
17
18
|
dependent: :destroy
|
18
|
-
has_many :
|
19
|
-
|
20
|
-
has_many :hosts, class_name: '::Host::Managed', dependent: :nullify
|
19
|
+
has_many :hosts, -> { distinct }, class_name: '::Host::Managed', through: :resource_quotas_hosts
|
20
|
+
has_many :hosts_resources, class_name: 'HostResources', through: :hosts
|
21
21
|
has_many :users, class_name: '::User', through: :resource_quotas_users
|
22
22
|
has_many :usergroups, class_name: '::Usergroup', through: :resource_quotas_usergroups
|
23
23
|
|
@@ -26,8 +26,12 @@ module ForemanResourceQuota
|
|
26
26
|
scoped_search on: :name, complete_value: true
|
27
27
|
scoped_search on: :id, complete_enabled: false, only_explicit: true, validator: ScopedSearch::Validators::INTEGER
|
28
28
|
|
29
|
+
def self.permission_name
|
30
|
+
'resource_quotas'
|
31
|
+
end
|
32
|
+
|
29
33
|
def number_of_hosts
|
30
|
-
|
34
|
+
hosts_resources.size
|
31
35
|
end
|
32
36
|
|
33
37
|
def number_of_users
|
@@ -49,54 +53,84 @@ module ForemanResourceQuota
|
|
49
53
|
# "host_a": [ :cpu_cores, :disk_gb ],
|
50
54
|
# "host_b": [ :memory_mb ],
|
51
55
|
# }
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
56
|
+
# Parameters:
|
57
|
+
# - exclude: an Array of host names to exclude from the utilization
|
58
|
+
def missing_hosts(exclude: [])
|
59
|
+
missing_hosts = {}
|
60
|
+
active_resources.each do |single_resource|
|
61
|
+
hosts_resources.where(single_resource => nil).includes([:host]).find_each do |host_resources_item|
|
62
|
+
host_name = host_resources_item.host.name
|
63
|
+
next if exclude.include?(host_name)
|
64
|
+
missing_hosts[host_name] ||= []
|
65
|
+
missing_hosts[host_name] << single_resource
|
66
|
+
end
|
60
67
|
end
|
61
|
-
|
68
|
+
missing_hosts
|
62
69
|
end
|
63
70
|
|
64
|
-
#
|
71
|
+
# Returns a Hash with the quota resources and their utilization as key-value pair
|
72
|
+
# It returns always all resources, even if they are not used (nil in that case).
|
73
|
+
# For example:
|
74
|
+
# {
|
75
|
+
# cpu_cores: 10,
|
76
|
+
# memory_mb: nil,
|
77
|
+
# disk_gb: 20,
|
78
|
+
# }
|
65
79
|
# Parameters:
|
66
|
-
# -
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
80
|
+
# - exclude: an Array of host names to exclude from the utilization
|
81
|
+
def utilization(exclude: [])
|
82
|
+
current_utilization = {
|
83
|
+
cpu_cores: nil,
|
84
|
+
memory_mb: nil,
|
85
|
+
disk_gb: nil,
|
86
|
+
}
|
87
|
+
|
88
|
+
active_resources.each do |resource|
|
89
|
+
current_utilization[resource] = 0
|
90
|
+
end
|
91
|
+
|
92
|
+
hosts_resources.each do |host_resources_item|
|
93
|
+
next if exclude.include?(host_resources_item.host.name)
|
94
|
+
|
95
|
+
active_resources.each do |resource|
|
96
|
+
current_utilization[resource] += host_resources_item.send(resource).to_i
|
97
|
+
end
|
78
98
|
end
|
99
|
+
|
100
|
+
current_utilization
|
79
101
|
end
|
80
102
|
|
81
|
-
def
|
82
|
-
{
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
103
|
+
def hosts_resources_as_hash
|
104
|
+
resources_hash = hosts.map(&:name).index_with { {} }
|
105
|
+
hosts_resources.each do |host_resources_item|
|
106
|
+
active_resources do |resource_name|
|
107
|
+
resources_hash[host_resources_item.host.name][resource_name] = host_resources_item.send(resource_name)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
resources_hash
|
87
111
|
end
|
88
112
|
|
89
|
-
def
|
90
|
-
|
91
|
-
|
92
|
-
|
113
|
+
def update_hosts_resources(hosts_resources_hash)
|
114
|
+
# Only update hosts that are associated with this quota
|
115
|
+
update_hosts = hosts.where(name: hosts_resources_hash.keys)
|
116
|
+
update_hosts_ids = update_hosts.pluck(:name, :id).to_h
|
117
|
+
hosts_resources_hash.each do |host_name, resources|
|
118
|
+
# Update the host_resources without loading the whole host object
|
119
|
+
host_resources_item = hosts_resources.find_by(host_id: update_hosts_ids[host_name])
|
120
|
+
if host_resources_item
|
121
|
+
host_resources_item.resources = resources
|
122
|
+
host_resources_item.save
|
123
|
+
else
|
124
|
+
Rails.logger.warn "HostResources not found for host_name: #{host_name}"
|
125
|
+
end
|
126
|
+
end
|
93
127
|
end
|
94
128
|
|
95
129
|
def determine_utilization(additional_hosts = [])
|
96
130
|
quota_hosts = (hosts | (additional_hosts))
|
97
|
-
|
98
|
-
|
99
|
-
|
131
|
+
all_host_resources, missing_hosts_resources = call_utilization_helper(quota_hosts)
|
132
|
+
update_hosts_resources(all_host_resources)
|
133
|
+
|
100
134
|
Rails.logger.warn create_hosts_resources_warning(missing_hosts_resources) unless missing_hosts.empty?
|
101
135
|
rescue StandardError => e
|
102
136
|
Rails.logger.error("An error occured while determining resources for quota '#{name}': #{e}")
|
@@ -128,25 +162,5 @@ module ForemanResourceQuota
|
|
128
162
|
warn_text << " '#{host_name}': '#{missing_resources}'\n" unless missing_resources.empty?
|
129
163
|
end
|
130
164
|
end
|
131
|
-
|
132
|
-
def update_single_utilization(attribute, val)
|
133
|
-
return unless val.key?(attribute.to_sym) || val.key?(attribute.to_s)
|
134
|
-
update("utilization_#{attribute}": val[attribute.to_sym] || val[attribute.to_s])
|
135
|
-
end
|
136
|
-
|
137
|
-
def add_missing_host(host_name, missing_resources)
|
138
|
-
return if missing_resources.empty?
|
139
|
-
|
140
|
-
host = Host::Managed.find_by(name: host_name)
|
141
|
-
raise HostNotFoundException if host.nil?
|
142
|
-
|
143
|
-
resource_quotas_missing_hosts << ResourceQuotaMissingHost.new(
|
144
|
-
missing_host: host,
|
145
|
-
resource_quota: self,
|
146
|
-
no_cpu_cores: missing_resources.include?(:cpu_cores),
|
147
|
-
no_memory_mb: missing_resources.include?(:memory_mb),
|
148
|
-
no_disk_gb: missing_resources.include?(:disk_gb)
|
149
|
-
)
|
150
|
-
end
|
151
165
|
end
|
152
166
|
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ForemanResourceQuota
|
4
|
+
class ResourceQuotaHost < ApplicationRecord
|
5
|
+
self.table_name = 'resource_quotas_hosts'
|
6
|
+
|
7
|
+
belongs_to :resource_quota, class_name: 'ResourceQuota'
|
8
|
+
belongs_to :host, class_name: '::Host::Managed'
|
9
|
+
end
|
10
|
+
end
|
@@ -8,12 +8,12 @@ module ForemanResourceQuota
|
|
8
8
|
disk_gb: :extract_disk_gb,
|
9
9
|
}.freeze
|
10
10
|
|
11
|
-
def collect_resources!(
|
11
|
+
def collect_resources!(hosts_resources, missing_hosts_resources, host_objects)
|
12
12
|
return if missing_hosts_resources.empty?
|
13
13
|
|
14
14
|
relevant_hosts = load_hosts_eagerly(missing_hosts_resources, host_objects, host_attribute_eager_name)
|
15
15
|
host_values = collect_attribute_from_hosts(relevant_hosts, host_attribute_name)
|
16
|
-
|
16
|
+
process_resources_and_delete_missing_hosts!(hosts_resources, missing_hosts_resources, host_values)
|
17
17
|
end
|
18
18
|
|
19
19
|
def host_attribute_eager_name
|
@@ -65,12 +65,12 @@ module ForemanResourceQuota
|
|
65
65
|
host_values
|
66
66
|
end
|
67
67
|
|
68
|
-
def
|
68
|
+
def process_resources_and_delete_missing_hosts!(hosts_resources, missing_hosts_resources, host_values)
|
69
69
|
host_values.each do |host_name, attribute_content|
|
70
70
|
missing_hosts_resources[host_name].reverse_each do |resource_name|
|
71
71
|
resource_value = process_resource(resource_name, attribute_content)
|
72
72
|
next unless resource_value
|
73
|
-
|
73
|
+
hosts_resources[host_name][resource_name] = resource_value
|
74
74
|
missing_hosts_resources[host_name].delete(resource_name)
|
75
75
|
end
|
76
76
|
missing_hosts_resources.delete(host_name) if missing_hosts_resources[host_name].empty?
|
@@ -20,7 +20,7 @@ module ForemanResourceQuota
|
|
20
20
|
nil
|
21
21
|
end
|
22
22
|
|
23
|
-
def collect_resources!(
|
23
|
+
def collect_resources!(hosts_resources, missing_hosts_resources, _host_objects)
|
24
24
|
compute_resource_to_hosts = group_hosts_by_compute_resource(missing_hosts_resources.keys)
|
25
25
|
|
26
26
|
compute_resource_to_hosts.each do |compute_resource_id, hosts|
|
@@ -32,7 +32,7 @@ module ForemanResourceQuota
|
|
32
32
|
hosts.each do |host|
|
33
33
|
vm = host_vms.find { |obj| obj.send(vm_id_attr) == host.uuid }
|
34
34
|
next unless vm
|
35
|
-
process_host_vm!(
|
35
|
+
process_host_vm!(hosts_resources, missing_hosts_resources, host.name, vm)
|
36
36
|
end
|
37
37
|
end
|
38
38
|
end
|
@@ -63,16 +63,16 @@ module ForemanResourceQuota
|
|
63
63
|
|
64
64
|
# Processes a host's virtual machines and updates resource allocation.
|
65
65
|
# Parameters:
|
66
|
-
# -
|
66
|
+
# - hosts_resources: Hash containing successfully determined resources per host.
|
67
67
|
# - missing_hosts_resources: Hash containing missing resources per host.
|
68
|
-
# -
|
68
|
+
# - host: Host object
|
69
69
|
# - vm: Compute resource VM object of given host.
|
70
70
|
# Returns: None.
|
71
|
-
def process_host_vm!(
|
71
|
+
def process_host_vm!(hosts_resources, missing_hosts_resources, host_name, host_vm)
|
72
72
|
missing_hosts_resources[host_name].reverse_each do |resource_name|
|
73
73
|
resource_value = process_resource(resource_name, host_vm)
|
74
74
|
next unless resource_value
|
75
|
-
|
75
|
+
hosts_resources[host_name][resource_name] = resource_value
|
76
76
|
missing_hosts_resources[host_name].delete(resource_name)
|
77
77
|
end
|
78
78
|
missing_hosts_resources.delete(host_name) if missing_hosts_resources[host_name].empty?
|
@@ -0,0 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class RemoveUtilizationFromResourceQuotas < ActiveRecord::Migration[6.1]
|
4
|
+
def change
|
5
|
+
remove_column :resource_quotas, :utilization_cpu_cores, :integer
|
6
|
+
remove_column :resource_quotas, :utilization_memory_mb, :integer
|
7
|
+
remove_column :resource_quotas, :utilization_disk_gb, :integer
|
8
|
+
end
|
9
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class CreateHostsResources < ActiveRecord::Migration[6.1]
|
4
|
+
def change
|
5
|
+
create_table :hosts_resources do |t|
|
6
|
+
t.belongs_to :host, index: { unique: true }, foreign_key: true, null: false
|
7
|
+
t.integer :cpu_cores, default: nil
|
8
|
+
t.integer :memory_mb, default: nil
|
9
|
+
t.integer :disk_gb, default: nil
|
10
|
+
|
11
|
+
t.timestamps
|
12
|
+
end
|
13
|
+
|
14
|
+
create_table :resource_quotas_hosts do |t|
|
15
|
+
t.belongs_to :host, index: { unique: true }, foreign_key: true, null: false
|
16
|
+
t.belongs_to :resource_quota, foreign_key: true, null: false
|
17
|
+
|
18
|
+
t.timestamps
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ForemanResourceQuota
|
4
|
+
module Async
|
5
|
+
class RefreshResourceQuotaUtilization < ::Actions::EntryAction
|
6
|
+
include ::Actions::RecurringAction
|
7
|
+
|
8
|
+
def run
|
9
|
+
ResourceQuota.all.each do |quota|
|
10
|
+
quota.determine_utilization
|
11
|
+
rescue e
|
12
|
+
logger.error N_(format("An error occured determining the utilization of '%s'-quota: %s", quota.name, e))
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def logger
|
17
|
+
action_logger
|
18
|
+
end
|
19
|
+
|
20
|
+
def rescue_strategy_for_self
|
21
|
+
Dynflow::Action::Rescue::Fail
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -1,16 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'foreman_tasks'
|
4
|
+
|
3
5
|
module ForemanResourceQuota
|
4
6
|
class Engine < ::Rails::Engine
|
5
7
|
engine_name 'foreman_resource_quota'
|
6
8
|
|
7
|
-
config.autoload_paths += Dir["#{config.root}/app/models/"]
|
8
|
-
config.autoload_paths += Dir["#{config.root}/app/controllers/"]
|
9
|
-
config.autoload_paths += Dir["#{config.root}/app/views/"]
|
10
|
-
config.autoload_paths += Dir["#{config.root}/app/services/foreman_resource_quota/"]
|
11
|
-
config.autoload_paths += Dir["#{config.root}/app/helpers/foreman_resource_quota/"]
|
12
|
-
config.autoload_paths += Dir["#{config.root}/lib/"]
|
13
|
-
|
14
9
|
# Add db migrations
|
15
10
|
initializer 'foreman_resource_quota.load_app_instance_data' do |app|
|
16
11
|
ForemanResourceQuota::Engine.paths['db/migrate'].existent.each do |path|
|
@@ -34,8 +29,10 @@ module ForemanResourceQuota
|
|
34
29
|
end
|
35
30
|
|
36
31
|
# Plugin extensions
|
37
|
-
initializer 'foreman_resource_quota.register_plugin', before: :finisher_hook do |
|
38
|
-
|
32
|
+
initializer 'foreman_resource_quota.register_plugin', before: :finisher_hook do |app|
|
33
|
+
app.reloader.to_prepare do
|
34
|
+
require 'foreman_resource_quota/register'
|
35
|
+
end
|
39
36
|
end
|
40
37
|
|
41
38
|
# Include concerns in this config.to_prepare block
|
@@ -47,10 +44,47 @@ module ForemanResourceQuota
|
|
47
44
|
Rails.logger.warn "ForemanResourceQuota: skipping engine hook (#{e})"
|
48
45
|
end
|
49
46
|
|
47
|
+
# Register ForemanTasks-based recurring logic/scheduled tasks
|
48
|
+
initializer 'foreman_resource_quota.register_scheduled_tasks', before: :finisher_hook do |_app|
|
49
|
+
action_paths = [ForemanResourceQuota::Engine.root.join('lib/foreman_resource_quota/async')]
|
50
|
+
::ForemanTasks.dynflow.config.eager_load_paths.concat(action_paths)
|
51
|
+
|
52
|
+
# Skip object creation if the admin user is not present
|
53
|
+
# skip database manipulations while tables do not exist, like in migrations
|
54
|
+
if ActiveRecord::Base.connection.data_source_exists?(::ForemanTasks::Task.table_name) &&
|
55
|
+
User.unscoped.find_by_login(User::ANONYMOUS_ADMIN).present?
|
56
|
+
# Register the scheduled tasks
|
57
|
+
::ForemanTasks.dynflow.config.on_init(false) do |_world|
|
58
|
+
ForemanResourceQuota::Engine.register_scheduled_task(
|
59
|
+
ForemanResourceQuota::Async::RefreshResourceQuotaUtilization,
|
60
|
+
'0 1 * * *'
|
61
|
+
)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
rescue ActiveRecord::NoDatabaseError => e
|
65
|
+
Rails.logger.warn "ForemanResourceQuota: skipping ForemanTasks registration hook (#{e})"
|
66
|
+
end
|
67
|
+
|
50
68
|
initializer 'foreman_resource_quota.register_gettext', after: :load_config_initializers do |_app|
|
51
69
|
locale_dir = File.join(File.expand_path('../..', __dir__), 'locale')
|
52
70
|
locale_domain = 'foreman_resource_quota'
|
53
71
|
Foreman::Gettext::Support.add_text_domain locale_domain, locale_dir
|
54
72
|
end
|
73
|
+
|
74
|
+
# Helper to register ForemanTasks
|
75
|
+
def self.register_scheduled_task(task_class, cronline)
|
76
|
+
return if ::ForemanTasks::RecurringLogic.joins(:tasks)
|
77
|
+
.merge(::ForemanTasks::Task.where(label: task_class.name))
|
78
|
+
.exists?
|
79
|
+
::ForemanTasks::RecurringLogic.transaction(isolation: :serializable) do
|
80
|
+
User.as_anonymous_admin do
|
81
|
+
recurring_logic = ::ForemanTasks::RecurringLogic.new_from_cronline(cronline)
|
82
|
+
recurring_logic.save!
|
83
|
+
recurring_logic.start(task_class)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
rescue ActiveRecord::TransactionIsolationError => e
|
87
|
+
Rails.logger.warn "ForemanResourceQuota: skipping RecurringLogic registration hook (#{e})"
|
88
|
+
end
|
55
89
|
end
|
56
90
|
end
|
@@ -2,29 +2,29 @@
|
|
2
2
|
|
3
3
|
# rubocop: disable Metrics/BlockLength
|
4
4
|
Foreman::Plugin.register :foreman_resource_quota do
|
5
|
-
requires_foreman '>= 3.
|
5
|
+
requires_foreman '>= 3.13'
|
6
6
|
# Apipie
|
7
7
|
apipie_documented_controllers ["#{ForemanResourceQuota::Engine.root}" \
|
8
8
|
'/app/controllers/foreman_resource_quot/api/v2/*.rb']
|
9
9
|
|
10
10
|
# Add permissions
|
11
11
|
security_block :foreman_resource_quota do
|
12
|
-
permission
|
12
|
+
permission :view_resource_quotas,
|
13
13
|
{ 'foreman_resource_quota/resource_quotas': %i[index welcome auto_complete_search],
|
14
14
|
'foreman_resource_quota/api/v2/resource_quotas': %i[index show utilization missing_hosts hosts users usergroups
|
15
15
|
auto_complete_search],
|
16
16
|
'foreman_resource_quota/api/v2/resource_quotas/:resource_quota_id/': %i[utilization missing_hosts hosts users
|
17
17
|
usergroups] },
|
18
18
|
resource_type: 'ForemanResourceQuota::ResourceQuota'
|
19
|
-
permission
|
19
|
+
permission :create_resource_quotas,
|
20
20
|
{ 'foreman_resource_quota/resource_quotas': %i[new create],
|
21
21
|
'foreman_resource_quota/api/v2/resource_quotas': %i[create] },
|
22
22
|
resource_type: 'ForemanResourceQuota::ResourceQuota'
|
23
|
-
permission
|
23
|
+
permission :edit_resource_quotas,
|
24
24
|
{ 'foreman_resource_quota/resource_quotas': %i[edit update],
|
25
25
|
'foreman_resource_quota/api/v2/resource_quotas': %i[update] },
|
26
26
|
resource_type: 'ForemanResourceQuota::ResourceQuota'
|
27
|
-
permission
|
27
|
+
permission :destroy_resource_quotas,
|
28
28
|
{ 'foreman_resource_quota/resource_quotas': %i[destroy],
|
29
29
|
'foreman_resource_quota/api/v2/resource_quotas': %i[destroy] },
|
30
30
|
resource_type: 'ForemanResourceQuota::ResourceQuota'
|
@@ -33,18 +33,18 @@ Foreman::Plugin.register :foreman_resource_quota do
|
|
33
33
|
end
|
34
34
|
|
35
35
|
# Add a permissions to default roles (Viewer and Manager)
|
36
|
-
role 'Resource Quota Manager', [
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
role 'Resource Quota User', [
|
45
|
-
|
46
|
-
|
47
|
-
|
36
|
+
role 'Resource Quota Manager', %i[view_resource_quotas
|
37
|
+
create_resource_quotas
|
38
|
+
edit_resource_quotas
|
39
|
+
destroy_resource_quotas
|
40
|
+
view_hosts
|
41
|
+
edit_hosts
|
42
|
+
view_users
|
43
|
+
edit_users]
|
44
|
+
role 'Resource Quota User', %i[view_resource_quotas
|
45
|
+
view_hosts
|
46
|
+
view_users
|
47
|
+
view_usergroups]
|
48
48
|
add_all_permissions_to_default_roles
|
49
49
|
|
50
50
|
# add controller parameter extension
|
data/package.json
CHANGED
@@ -25,7 +25,7 @@
|
|
25
25
|
},
|
26
26
|
"dependencies": {},
|
27
27
|
"devDependencies": {
|
28
|
-
"@babel/core": "^7.
|
28
|
+
"@babel/core": "^7.26.0",
|
29
29
|
"@sheerun/mutationobserver-shim": "^0.3.3",
|
30
30
|
"@testing-library/react": "^10.4.9",
|
31
31
|
"@theforeman/builder": ">=12.0.1",
|
@@ -37,7 +37,7 @@
|
|
37
37
|
"eslint": "^6.7.2",
|
38
38
|
"prettier": "^1.19.1",
|
39
39
|
"react-redux-test-utils": "^0.2.0",
|
40
|
-
"stylelint": "^16.
|
40
|
+
"stylelint": "^16.10.0",
|
41
41
|
"stylelint-config-standard": "^36.0.0"
|
42
42
|
}
|
43
43
|
}
|
@@ -16,10 +16,12 @@ import {
|
|
16
16
|
Tooltip,
|
17
17
|
} from '@patternfly/react-core';
|
18
18
|
|
19
|
-
import
|
20
|
-
|
21
|
-
|
22
|
-
|
19
|
+
import {
|
20
|
+
UserIcon,
|
21
|
+
UsersIcon,
|
22
|
+
ClusterIcon,
|
23
|
+
SyncAltIcon,
|
24
|
+
} from '@patternfly/react-icons';
|
23
25
|
|
24
26
|
import { translate as __ } from 'foremanReact/common/I18n';
|
25
27
|
import { dispatchAPICallbackToast } from '../../../../api_helper';
|
@@ -7,7 +7,7 @@ import {
|
|
7
7
|
ProgressMeasureLocation,
|
8
8
|
Tooltip,
|
9
9
|
} from '@patternfly/react-core';
|
10
|
-
import SyncAltIcon from '@patternfly/react-icons
|
10
|
+
import SyncAltIcon from '@patternfly/react-icons';
|
11
11
|
|
12
12
|
import { translate as __ } from 'foremanReact/common/I18n';
|
13
13
|
|
metadata
CHANGED
@@ -1,15 +1,49 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: foreman_resource_quota
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Bastian Schmidt
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
12
|
-
dependencies:
|
11
|
+
date: 2024-11-27 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: foreman-tasks
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '10.0'
|
20
|
+
- - "<"
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '11'
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '10.0'
|
30
|
+
- - "<"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '11'
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: theforeman-rubocop
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: 0.1.0
|
40
|
+
type: :development
|
41
|
+
prerelease: false
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - "~>"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: 0.1.0
|
13
47
|
description: Foreman Plug-in to manage resource usage among users.
|
14
48
|
email:
|
15
49
|
- schmidt@atix.de
|
@@ -26,11 +60,13 @@ files:
|
|
26
60
|
- app/controllers/foreman_resource_quota/resource_quotas_controller.rb
|
27
61
|
- app/helpers/foreman_resource_quota/hosts_helper.rb
|
28
62
|
- app/helpers/foreman_resource_quota/resource_quota_helper.rb
|
63
|
+
- app/lib/foreman_resource_quota/exceptions.rb
|
29
64
|
- app/models/concerns/foreman_resource_quota/host_managed_extensions.rb
|
30
65
|
- app/models/concerns/foreman_resource_quota/user_extensions.rb
|
31
66
|
- app/models/concerns/foreman_resource_quota/usergroup_extensions.rb
|
67
|
+
- app/models/foreman_resource_quota/host_resources.rb
|
32
68
|
- app/models/foreman_resource_quota/resource_quota.rb
|
33
|
-
- app/models/foreman_resource_quota/
|
69
|
+
- app/models/foreman_resource_quota/resource_quota_host.rb
|
34
70
|
- app/models/foreman_resource_quota/resource_quota_user.rb
|
35
71
|
- app/models/foreman_resource_quota/resource_quota_usergroup.rb
|
36
72
|
- app/services/foreman_resource_quota/resource_origin.rb
|
@@ -62,9 +98,13 @@ files:
|
|
62
98
|
- config/initializers/inflections.rb
|
63
99
|
- config/routes.rb
|
64
100
|
- db/migrate/20230306120001_create_resource_quotas.rb
|
101
|
+
- db/migrate/20240611141744_remove_utilization_from_resource_quotas.rb
|
102
|
+
- db/migrate/20240611141939_drop_missing_hosts.rb
|
103
|
+
- db/migrate/20240611142813_create_hosts_resources.rb
|
104
|
+
- db/migrate/20240618163434_remove_resource_quota_from_hosts.rb
|
65
105
|
- lib/foreman_resource_quota.rb
|
106
|
+
- lib/foreman_resource_quota/async/refresh_resource_quota_utilization.rb
|
66
107
|
- lib/foreman_resource_quota/engine.rb
|
67
|
-
- lib/foreman_resource_quota/exceptions.rb
|
68
108
|
- lib/foreman_resource_quota/register.rb
|
69
109
|
- lib/foreman_resource_quota/version.rb
|
70
110
|
- lib/tasks/foreman_resource_quota_tasks.rake
|
@@ -1,10 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module ForemanResourceQuota
|
4
|
-
class ResourceQuotaMissingHost < ApplicationRecord
|
5
|
-
self.table_name = 'resource_quotas_missing_hosts'
|
6
|
-
|
7
|
-
belongs_to :resource_quota, inverse_of: :resource_quotas_missing_hosts
|
8
|
-
belongs_to :missing_host, class_name: '::Host::Managed', inverse_of: :resource_quota_missing_resources
|
9
|
-
end
|
10
|
-
end
|
File without changes
|