-
Notifications
You must be signed in to change notification settings - Fork 897
/
tenant.rb
377 lines (306 loc) · 11.1 KB
/
tenant.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
require 'ancestry'
class Tenant < ApplicationRecord
HARDCODED_LOGO = "custom_logo.png"
HARDCODED_LOGIN_LOGO = "custom_login_logo.png"
DEFAULT_URL = nil
include ActiveVmAggregationMixin
include CustomActionsMixin
include TenantQuotasMixin
include ExternalUrlMixin
acts_as_miq_taggable
default_value_for :name, "My Company"
default_value_for :description, "Tenant for My Company"
default_value_for :divisible, true
default_value_for :use_config_for_attributes, false
before_destroy :ensure_can_be_destroyed
has_ancestry(:orphan_strategy => :restrict)
has_many :providers
has_many :ext_management_systems
has_many :vm_or_templates
has_many :vms, :inverse_of => :tenant
has_many :miq_templates, :inverse_of => :tenant
has_many :service_template_catalogs
has_many :service_templates
has_many :tenant_quotas
has_many :miq_groups
has_many :users, :through => :miq_groups
has_many :ae_domains, :dependent => :destroy, :class_name => 'MiqAeDomain'
has_many :miq_requests, :dependent => :destroy
has_many :miq_request_tasks, :dependent => :destroy
has_many :services, :dependent => :destroy
has_many :shares
has_many :authentications, :dependent => :nullify
has_many :miq_product_features, :dependent => :destroy
has_many :service_template_tenants, :dependent => :destroy
has_many :service_templates, :through => :service_template_tenants
belongs_to :default_miq_group, :class_name => "MiqGroup", :dependent => :destroy
belongs_to :source, :polymorphic => true
validates :subdomain, :uniqueness_when_changed => true, :allow_nil => true
validates :domain, :uniqueness_when_changed => true, :allow_nil => true
validate :validate_only_one_root
validates :description, :presence => true
validates :name, :presence => true, :unless => :use_config_for_attributes?,
:uniqueness_when_changed => {:scope => :ancestry,
:conditions => -> { in_my_region },
:message => "should be unique per parent"}
validate :validate_default_tenant, :on => :update, :if => :saved_change_to_default_miq_group_id?
scope :all_tenants, -> { where(:divisible => true) }
scope :all_projects, -> { where(:divisible => false) }
virtual_column :parent_name, :type => :string
virtual_column :display_type, :type => :string
before_save :nil_blanks
after_save -> { MiqProductFeature.invalidate_caches }
after_create :create_tenant_group, :create_miq_product_features_for_tenant_nodes, :update_miq_product_features_for_tenant_nodes
def self.scope_by_tenant?
true
end
def self.with_current_tenant
current_tenant = User.current_user.current_tenant
where(:id => current_tenant.id)
end
def self.tenant_id_clause(user_or_group)
strategy = Rbac.accessible_tenant_ids_strategy(self)
tenant = user_or_group.try(:current_tenant)
return [] if tenant.root?
tenant_ids = tenant.accessible_tenant_ids(strategy)
return if tenant_ids.empty?
{table_name => {:id => tenant_ids}}
end
def all_subtenants
self.class.descendants_of(self).where(:divisible => true)
end
def all_subprojects
self.class.descendants_of(self).where(:divisible => false)
end
def regional_tenants
self.class.regional_tenants(self)
end
def nested_service_templates
ServiceTemplate.with_tenant(id)
end
def nested_providers
ExtManagementSystem.with_tenant(id)
end
def nested_ae_namespaces
MiqAeDomain.with_tenant(id)
end
def self.regional_tenants(tenant)
where(arel_table.grouping(arel_table.lower(arel_table[:name]).eq(tenant.name.downcase)))
end
def accessible_tenant_ids(strategy = nil)
(strategy ? regional_tenants.map(&strategy.to_sym).flatten : []) + regional_tenants.ids
end
def name
tenant_attribute(:name, :company)
end
def parent_name
parent.try(:name)
end
def display_type
project? ? "Project" : "Tenant"
end
def login_text
tenant_attribute(:login_text, :custom_login_text)
end
def get_quotas
tenant_quotas.each_with_object({}) do |q, h|
h[q.name.to_sym] = q.quota_hash
end.reverse_merge(TenantQuota.quota_definitions)
end
def set_quotas(quotas)
updated_keys = []
self.class.transaction do
quotas.each do |name, values|
next if values[:value].nil?
name = name.to_s
q = tenant_quotas.detect { |tq| tq.name == name } || tenant_quotas.build(:name => name)
q.update!(values)
updated_keys << name
end
# Delete any quotas that were not passed in
tenant_quotas.destroy_missing(updated_keys)
# unfortunately, an extra scope is created in destroy_missing, so we need to reload the records
tenant_quotas.reload
end
get_quotas
end
def used_quotas
tenant_quotas.each_with_object({}) do |q, h|
h[q.name.to_sym] = q.quota_hash.merge(:value => q.used)
end.reverse_merge(TenantQuota.quota_definitions)
end
# Amount of quotas allocated to the immediate child tenants
def allocated_quotas
tenant_quotas.each_with_object({}) do |q, h|
h[q.name.to_sym] = q.quota_hash.merge(:value => q.allocated)
end.reverse_merge(TenantQuota.quota_definitions)
end
# Amount of quotas available to be allocated to child tenants
def available_quotas
tenant_quotas.each_with_object({}) do |q, h|
h[q.name.to_sym] = q.quota_hash.merge(:value => q.available)
end.reverse_merge(TenantQuota.quota_definitions)
end
def combined_quotas
TenantQuota.quota_definitions.each_with_object({}) do |d, h|
scope_name, _ = d
q = tenant_quotas.send(scope_name).take || tenant_quotas.build(:name => scope_name, :value => 0)
h[q.name.to_sym] = q.quota_hash
h[q.name.to_sym][:allocated] = q.allocated
h[q.name.to_sym][:available] = q.available unless q.new_record?
h[q.name.to_sym][:used] = q.used
end.reverse_merge(TenantQuota.quota_definitions)
end
# @return [Boolean] Is this a default tenant?
def default?
root?
end
# @return [Boolean] Is this the root tenant?
def root?
!parent_id?
end
def tenant?
divisible?
end
def project?
!divisible?
end
def visible_domains
MiqAeDomain.where(:tenant_id => path_ids).joins(:tenant).order('tenants.ancestry DESC NULLS LAST, priority DESC')
end
def enabled_domains
visible_domains.where(:enabled => true)
end
def editable_domains
ae_domains.where(:source => MiqAeDomain::USER_SOURCE).order('priority DESC')
end
def sequenceable_domains
ae_domains.where.not(:source => MiqAeDomain::SYSTEM_SOURCE).order('priority DESC')
end
def any_editable_domains?
ae_domains.where(:source => MiqAeDomain::USER_SOURCE).count > 0
end
def reset_domain_priority_by_ordered_ids(ids)
uneditable_domains = visible_domains - editable_domains
uneditable_domains.delete_if { |domain| domain.name == MiqAeDatastore::MANAGEIQ_DOMAIN }
MiqAeDomain.reset_priority_by_ordered_ids(uneditable_domains.collect(&:id).reverse + ids)
end
# The default tenant is the tenant to be used when
# the url does not map to a known domain or subdomain
#
# At this time, urls are not used, so the root tenant is returned
# @return [Tenant] default tenant
def self.default_tenant
root_tenant
end
# the root tenant is also referred to as tenant0
# from this tenant, all tenants are positioned
#
# @return [Tenant] the root tenant
def self.root_tenant
@root_tenant ||= root_tenant_without_cache
end
def self.root_tenant_without_cache
in_my_region.roots.first
end
# NOTE: returns the root tenant
def self.seed
root_tenant || create!(:use_config_for_attributes => true) do |_|
_log.info("Creating root tenant")
end
end
# tenant
# tenant2
# project4 (!divisible)
# tenant3
# @return [Array(Array<Array(String, Numeric)>, Array<Array(String, Numeric)>) ] tenants and projects
# e.g.:
# [
# [["tenant", 1], ["tenant/tenant2", 2]], ["tenant/tenant3", 3]]
# [["tenant/tenant2/project4", 4]]
# ]
def self.tenant_and_project_names
all_tenants_and_projects = Tenant.in_my_region.select(:id, :ancestry, :divisible, :use_config_for_attributes, :name)
tenants_by_id = all_tenants_and_projects.index_by(&:id)
tenants_and_projects = Rbac.filtered(Tenant.in_my_region.select(:id, :ancestry, :divisible, :use_config_for_attributes, :name))
.to_a.sort_by { |t| [t.ancestry || "", t.name] }
tenants_and_projects.partition(&:divisible?).map do |tenants|
tenants.map do |t|
all_names = (t.ancestor_ids + [t.id]).map { |tid| tenants_by_id[tid] }.map(&:name)
[all_names.join("/"), t.id]
end.sort_by(&:first)
end
end
# Tenant
# Tenant A
# Tenant B
#
# @return [Array(JSON({name => String, id => Numeric, parent => Numeric}))] all subtenants of a tenant
# e.g.:
# [
# {name=>"Tenant A",id=>2,parent=>1},
# {name=>"Tenant B",id=>3,parent=>1}
# ]
def build_tenant_tree
data_tenant = []
all_subtenants.each do |subtenant|
next unless subtenant.parent_name == name
data_tenant.push(:name => subtenant.name, :id => subtenant.id, :parent => id)
if subtenant.all_subtenants.count > 0
data_tenant.concat(subtenant.build_tenant_tree)
end
end
data_tenant
end
def allowed?
Rbac::Filterer.filtered_object(self).present?
end
def create_miq_product_features_for_tenant_nodes
MiqProductFeature.seed_single_tenant_miq_product_features(self)
end
def update_miq_product_features_for_tenant_nodes
MiqProductFeature.invalidate_caches_queue
end
def destroy_with_subtree
subtree.sort_by(&:depth).reverse.each(&:destroy)
end
private
# when a root tenant has an attribute with a nil value,
# read the value from the configurations table instead
#
# @return the attribute value
def tenant_attribute(attr_name, setting_name)
if use_config_for_attributes?
ret = ::Settings.server[setting_name]
block_given? ? yield(ret) : ret
else
self[attr_name]
end
end
def nil_blanks
self.subdomain = nil unless subdomain.present?
self.domain = nil unless domain.present?
self.name = nil unless name.present?
end
# validates that there is only one tree
def validate_only_one_root
unless parent_id || parent
root = self.class.root_tenant_without_cache
errors.add(:parent, "required") if root && root != self
end
end
def create_tenant_group
update!(:default_miq_group => MiqGroup.create_tenant_group(self)) unless default_miq_group_id
self
end
def ensure_can_be_destroyed
errors.add(:base, _("A tenant with groups associated cannot be deleted.")) if miq_groups.non_tenant_groups.exists?
errors.add(:base, _("A tenant created by tenant mapping cannot be deleted.")) if source&.persisted?
throw :abort unless errors[:base].empty?
end
def validate_default_tenant
if default_miq_group.tenant_id != id || !default_miq_group.tenant_group?
errors.add(:default_miq_group, "default group must be a default group for this tenant")
end
end
end