app/models/chargeback_vm.rb
class ChargebackVm < Chargeback
set_columns_hash(
:vm_id => :integer,
:vm_name => :string,
:vm_uid => :string,
:vm_guid => :string,
:owner_name => :string,
:provider_name => :string,
:tenant_name => :string,
:provider_uid => :string,
:cpu_allocated_metric => :float,
:cpu_allocated_cost => :float,
:cpu_used_cost => :float,
:cpu_used_metric => :float,
:cpu_cost => :float,
:disk_io_used_cost => :float,
:disk_io_used_metric => :float,
:fixed_compute_1_cost => :float,
:fixed_compute_2_cost => :float,
:fixed_storage_1_cost => :float,
:fixed_storage_2_cost => :float,
:memory_allocated_cost => :float,
:memory_allocated_metric => :float,
:memory_used_cost => :float,
:memory_used_metric => :float,
:memory_cost => :float,
:net_io_used_cost => :float,
:net_io_used_metric => :float,
:storage_allocated_cost => :float,
:storage_allocated_metric => :float,
:storage_used_cost => :float,
:storage_used_metric => :float,
:storage_cost => :float,
:total_cost => :float
)
DEFAULT_STORAGE_METRICS = %w[
storage_allocated_unclassified_metric
storage_allocated_unclassified_cost
storage_allocated_metric
storage_allocated_cost
].freeze
cache_with_timeout(:current_volume_types) do
volume_types = CloudVolume.volume_types
volume_types.push(nil) if volume_types.present?
volume_types
end
def self.attribute_names
loaded_attribute_names = super
loaded_storage_allocated_attributes = loaded_attribute_names.select { |x| x.starts_with?('storage_allocated_') }
loaded_sub_metric_fields = loaded_storage_allocated_attributes - DEFAULT_STORAGE_METRICS
non_existing_sub_metric_fields = loaded_sub_metric_fields - dynamic_columns_for(:float).keys - dynamic_rate_columns.keys
loaded_attribute_names - non_existing_sub_metric_fields
end
# example:
# dynamic_columns_for(:group => [:total])
# returns:
# { 'storage_allocated_volume_type1_metric' => {:group => [:total]},
# 'storage_allocated_volume_type1_cost' => {:group => [:total]},
# }
def self.dynamic_columns_for(column_type)
current_volume_types.each_with_object({}) do |volume_type, result|
%i[metric cost rate].collect do |type|
result["storage_allocated_#{volume_type || 'unclassified'}_#{type}"] = column_type
end
end
end
def self.refresh_dynamic_metric_columns
set_columns_hash(dynamic_columns_for(:float))
super
end
def self.build_results_for_report_ChargebackVm(options)
# Options: a hash transformable to Chargeback::ReportOptions
# Get the most up to date types from the DB
current_volume_types(true)
@report_user = User.find_by(:userid => options[:userid])
@vm_owners = @vms = nil
build_results_for_report_chargeback(options)
end
def self.where_clause(records, options, region)
scope = records.where(:resource_type => "VmOrTemplate")
if options[:tag] && (@report_user.nil? || !@report_user.self_service?)
scope_with_current_tags = scope.where(:resource => Vm.find_tagged_with(:any => @options[:tag], :ns => '*'))
scope.for_tag_names(Array(options[:tag]).flatten.map { |t| t.split("/")[2..-1] }).or(scope_with_current_tags)
else
scope.where(:resource => vms(region))
end
end
def self.extra_resources_without_rollups(region)
# support VM types for which we do not collect metrics yet (also when we are including metrics in calculations)
#
# NOTE since this scope is used later on as part of other queries this has to be an
# ActiveRecord::Relation not an array. The SupportsFeatureMixin.supporting method
# checks instance-level supports which returns an array thus cannot be used here.
scope = @options.include_metrics? ? Vm.where.not(:type => Vm.types_supporting(:capture)) : vms(region)
scope = scope.eager_load(:hardware, :taggings, :tags, :host, :ems_cluster, :storage, :ext_management_system,
:tenant)
if @options[:tag] && (@report_user.nil? || !@report_user.self_service?)
scope.find_tagged_with(:any => @options[:tag], :ns => '*')
else
scope.where(:id => vms(region))
end
end
def self.report_static_cols
%w[vm_name]
end
def self.sub_metric_columns
dynamic_columns_for(:grouping => [:total])
end
def self.report_col_options
{
"cpu_allocated_cost" => {:grouping => [:total]},
"cpu_allocated_metric" => {:grouping => [:total]},
"cpu_cost" => {:grouping => [:total]},
"cpu_used_cost" => {:grouping => [:total]},
"cpu_used_metric" => {:grouping => [:total]},
"disk_io_used_cost" => {:grouping => [:total]},
"disk_io_used_metric" => {:grouping => [:total]},
"fixed_compute_metric" => {:grouping => [:total]},
"fixed_compute_1_cost" => {:grouping => [:total]},
"fixed_compute_2_cost" => {:grouping => [:total]},
"fixed_cost" => {:grouping => [:total]},
"fixed_storage_1_cost" => {:grouping => [:total]},
"fixed_storage_2_cost" => {:grouping => [:total]},
"memory_allocated_cost" => {:grouping => [:total]},
"memory_allocated_metric" => {:grouping => [:total]},
"memory_cost" => {:grouping => [:total]},
"memory_used_cost" => {:grouping => [:total]},
"memory_used_metric" => {:grouping => [:total]},
"net_io_used_cost" => {:grouping => [:total]},
"net_io_used_metric" => {:grouping => [:total]},
"storage_allocated_cost" => {:grouping => [:total]},
"storage_allocated_metric" => {:grouping => [:total]},
"storage_cost" => {:grouping => [:total]},
"storage_used_cost" => {:grouping => [:total]},
"storage_used_metric" => {:grouping => [:total]},
"total_cost" => {:grouping => [:total]}
}.merge(sub_metric_columns)
end
def self.vm_owner(consumption, region)
@vm_owners ||= vms(region).each_with_object({}) { |vm, res| res[vm.id] = vm.evm_owner_name }
@vm_owners[consumption.resource_id] ||= consumption.resource.try(:evm_owner_name)
end
def self.vms(region)
@vms ||= {}
@vms[region] ||=
begin
# Find Vms by user or by tag
if @options[:entity_id]
Vm.where(:id => @options[:entity_id])
elsif @options[:owner]
user = User.lookup_by_userid(@options[:owner])
if user.nil?
_log.error("Unable to find user '#{@options[:owner]}'. Calculating chargeback costs aborted.")
raise MiqException::Error, _("Unable to find user '%{name}'") % {:name => @options[:owner]}
end
user.vms
elsif @options[:tag]
vms = Vm.find_tagged_with(:all => @options[:tag], :ns => '*')
vms &= @report_user.accessible_vms if @report_user && @report_user.self_service?
vms
elsif @options[:tenant_id]
tenant = Tenant.find_by(:id => @options[:tenant_id])
if tenant.nil?
error_message = "Unable to find tenant '#{@options[:tenant_id]}'"
_log.info("#{error_message}. Calculating chargeback costs skipped for #{@options[:tenant_id]} in region #{region}.")
return Vm.none
end
tenant_name = tenant.name
# NOTE: tenant.name has settings applied.
# i.e.: Tenant.where(:id => tenant.id, :name => tenant.name).exists? could be false
if tenant.region_number != region
tenant = Tenant.in_region(region).find_by(:name => tenant.name)
end
if tenant.nil?
error_message = "Unable to find tenant '#{tenant_name}' (based on tenant id '#{@options[:tenant_id]}' from default region) in region #{region}"
_log.info("#{error_message}. Calculating chargeback costs skipped for #{@options[:tenant_id]} in region #{region}.")
Vm.none
else
Vm.where(:tenant_id => tenant.subtree.select(:id))
end
elsif @options[:service_id]
service = Service.find(@options[:service_id])
if service.nil?
_log.error("Unable to find service '#{@options[:service_id]}'. Calculating chargeback costs aborted.")
raise MiqException::Error, "Unable to find service '#{@options[:service_id]}'"
end
service.vms
else
raise _('Unable to find strategy for VM selection')
end
end
end
def self.display_name(number = 1)
n_('Chargeback for VMs', 'Chargebacks for VMs', number)
end
private
def init_extra_fields(consumption, region)
self.vm_id = consumption.resource_id
self.vm_name = consumption.resource_name
self.vm_uid = consumption.resource.try(:ems_ref)
self.vm_guid = consumption.resource.try(:guid)
self.owner_name = self.class.vm_owner(consumption, region)
self.provider_name = consumption.parent_ems.try(:name)
self.provider_uid = consumption.parent_ems.try(:guid)
end
end # class Chargeback