Skip to content

Commit

Permalink
Merge pull request #17795 from lpichler/add_cumulative_chargeback_cal…
Browse files Browse the repository at this point in the history
…culation

Add Cumulative Chargeback rates
  • Loading branch information
gtanzillo authored Aug 6, 2018
2 parents 382ef25 + ccee53b commit ae59274
Show file tree
Hide file tree
Showing 4 changed files with 205 additions and 14 deletions.
6 changes: 4 additions & 2 deletions app/models/chargeback.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def self.build_results_for_report_chargeback(options)
@options = options = ReportOptions.new_from_h(options)

data = {}
rates = RatesCache.new
rates = RatesCache.new(options)

MiqRegion.all.each do |region|
ConsumptionHistory.for_report(self, options, region.region) do |consumption|
Expand Down Expand Up @@ -158,7 +158,9 @@ def calculate_costs(consumption, rates)
rates.each do |rate|
rate.rate_details_relevant_to(relevant_fields, self.class.attribute_names).each do |r|
r.charge(consumption, @options).each do |field, value|
self[field] = self[field].kind_of?(Numeric) ? (self[field] || 0) + value : value
next if @options.skip_field_accumulation?(field, self[field])

(self[field] = self[field].kind_of?(Numeric) ? (self[field] || 0) + value : value)
end
end
end
Expand Down
6 changes: 5 additions & 1 deletion app/models/chargeback/rates_cache.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
class Chargeback
class RatesCache
def initialize(options = nil)
@options = options
end

def get(consumption)
# we need to select ChargebackRates for groups of MetricRollups records
# and rates are selected by first MetricRollup record
Expand Down Expand Up @@ -30,7 +34,7 @@ def rates(consumption)

metric_rollup_record_tags = consumption.tag_names

unique_rates_by_tagged_resources(rates, metric_rollup_record_tags)
@options.cumulative_rate_calculation? ? rates.sort_by(&:description) : unique_rates_by_tagged_resources(rates, metric_rollup_record_tags)
end

def unique_rates_by_tagged_resources(rates, metric_rollup_record_tags)
Expand Down
16 changes: 15 additions & 1 deletion app/models/chargeback/report_options.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,26 @@ class Chargeback
:ext_options,
:include_metrics, # enable charging allocated resources with C & U
:method_for_allocated_metrics,
:group_by_tenant?
:group_by_tenant?,
:cumulative_rate_calculation,
) do
def self.new_from_h(hash)
new(*hash.values_at(*members))
end

# skip metric value field because we don't want
# to accumulate metric values(only costs)
def skip_field_accumulation?(field, value)
return false if cumulative_rate_calculation? == false
return false unless field.ends_with?("_metric") && value

true
end

def cumulative_rate_calculation?
!!self[:cumulative_rate_calculation]
end

ALLOCATED_METHODS_WHITELIST = %i(max avg current_value).freeze

def method_for_allocated_metrics
Expand Down
191 changes: 181 additions & 10 deletions spec/models/chargeback_vm_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,11 @@

let(:metric_rollup_params) do
{
:tag_names => "environment/prod",
:parent_host_id => @host1.id,
:parent_ems_cluster_id => @ems_cluster.id,
:parent_ems_id => ems.id,
:parent_storage_id => @storage.id,
:tag_names => "environment/prod",
:parent_host_id => @host1.id,
:parent_ems_cluster_id => @ems_cluster.id,
:parent_ems_id => ems.id,
:parent_storage_id => @storage.id,
}
end

Expand Down Expand Up @@ -805,7 +805,7 @@ def result_row_for_vm(vm)

before do
ChargebackRate.set_assignments(:compute, [rate_assignment_options])
@rate = Chargeback::RatesCache.new.get(consumption).first
@rate = Chargeback::RatesCache.new(Chargeback::ReportOptions.new_from_h(base_options)).get(consumption).first
@assigned_rate = ChargebackRate.get_assignments("Compute").first
end

Expand All @@ -832,7 +832,7 @@ def result_row_for_vm(vm)

let(:rate_assignment_options) { {:cb_rate => storage_chargeback_rate, :tag => [classification, "storage"]} }

subject { Chargeback::RatesCache.new.get(consumption).first }
subject { Chargeback::RatesCache.new(Chargeback::ReportOptions.new_from_h(base_options)).get(consumption).first }

before do
ChargebackRate.set_assignments(:storage, [rate_assignment_options])
Expand Down Expand Up @@ -909,10 +909,182 @@ def result_row_for_vm(vm)
end
end

context 'cumulative chargeback' do
let(:options) do
base_options[:tag] = nil
base_options[:entity_id] = vm.id
base_options[:cumulative_rate_calculation] = true
base_options[:interval] = 'monthly'
base_options
end

let(:vm) { FactoryGirl.create(:vm_vmware, :evm_owner => admin, :name => "vm_1", :created_on => month_beginning) }

let(:parent_classification_1) { FactoryGirl.create(:classification, :name => 'department') }
let(:classification_1_1) { FactoryGirl.create(:classification, :name => 'financial', :parent_id => parent_classification_1.id) }

let(:parent_classification_2) { FactoryGirl.create(:classification, :name => 'enviroment') }
let(:classification_2_1) { FactoryGirl.create(:classification, :name => 'test', :parent_id => parent_classification_2.id) }

let(:hourly_rate_2) { 0.05 }
let(:count_hourly_rate_2) { 10.00 }

let(:hourly_variable_tier_rate_2) { {:variable_rate => hourly_rate_2.to_s} }
let(:count_hourly_variable_tier_rate_2) { {:variable_rate => count_hourly_rate_2.to_s} }

let(:fixed_hourly_variable_tier_rate_2) { {:fixed_rate => count_hourly_rate_2.to_s} }

let(:detail_params_2) do
{
:chargeback_rate_detail_cpu_used => {:tiers => [hourly_variable_tier_rate_2]},
:chargeback_rate_detail_cpu_allocated => {:tiers => [count_hourly_variable_tier_rate_2]},
:chargeback_rate_detail_memory_allocated => {:tiers => [hourly_variable_tier_rate_2]},
:chargeback_rate_detail_memory_used => {:tiers => [hourly_variable_tier_rate_2]},
:chargeback_rate_detail_disk_io_used => {:tiers => [hourly_variable_tier_rate_2]},
:chargeback_rate_detail_net_io_used => {:tiers => [hourly_variable_tier_rate_2]},
:chargeback_rate_detail_storage_used => {:tiers => [count_hourly_variable_tier_rate_2]},
:chargeback_rate_detail_storage_allocated => {:tiers => [count_hourly_variable_tier_rate_2]},
:chargeback_rate_detail_fixed_compute_cost => {:tiers => [fixed_hourly_variable_tier_rate_2]},
:chargeback_rate_detail_metering_used => {:tiers => [count_hourly_variable_tier_rate_2]}
}
end

let(:chargeback_rate_1) { FactoryGirl.create(:chargeback_rate, :rate_type => "Compute", :detail_params => detail_params) }
let(:chargeback_rate_2) { FactoryGirl.create(:chargeback_rate, :rate_type => "Compute", :detail_params => detail_params_2) }

let(:rates) do
[
{:cb_rate => chargeback_rate_1, :tag => [classification_1_1, "vm"]},
{:cb_rate => chargeback_rate_2, :tag => [classification_2_1, "vm"]}
]
end

before do
# fix fixed computes cost tier - we are not using variable part
detail_params[:chargeback_rate_detail_fixed_compute_cost][:tiers] = [{:fixed_rate => count_hourly_rate.to_s }]

vm.tag_with([classification_1_1.tag.name, classification_2_1.tag.name], :ns => '*')

metric_rollup_params[:tag_names] = rates.map { |rate| rate[:tag].first.tag.send(:name_path) }.join('|')
add_metric_rollups_for(vm, month_beginning...month_end, 12.hours, metric_rollup_params)

ChargebackRate.set_assignments(:compute, rates)
end

subject { ChargebackVm.build_results_for_report_ChargebackVm(options).first.first }

it 'calculates accumulations' do
skip('this feature needs to be added to new chargeback') if Settings.new_chargeback

descriptions = [chargeback_rate_1.description, chargeback_rate_2.description].sort
expect(subject.chargeback_rates).to eq(descriptions.join(", "))

# fixed
expect(subject.fixed_compute_metric).to eq(vm.metric_rollups.count)

fixed_cost1 = hours_in_month * count_hourly_rate
fixed_cost2 = hours_in_month * count_hourly_rate_2
expect(subject.fixed_compute_1_cost).to eq(fixed_cost1 + fixed_cost2)

# cpu
expect(subject.cpu_allocated_metric).to eq(cpu_count)

cpu_cost_rate1 = cpu_count * count_hourly_rate * hours_in_month
cpu_cost_rate2 = cpu_count * count_hourly_rate_2 * hours_in_month
expect(subject.cpu_allocated_cost).to eq(cpu_cost_rate1 + cpu_cost_rate2)

used_metric = used_average_for(:cpu_usagemhz_rate_average, hours_in_month, vm)

expect(subject.cpu_used_metric).to eq(used_metric)

cpu_cost_rate1 = used_metric * hourly_rate * hours_in_month
cpu_cost_rate2 = used_metric * hourly_rate_2 * hours_in_month
expect(subject.cpu_used_cost).to eq(cpu_cost_rate1 + cpu_cost_rate2)

expect(subject.cpu_cost).to eq(subject.cpu_allocated_cost + subject.cpu_used_cost)

# memory
expect(subject.memory_allocated_metric).to eq(memory_available)

memory_cost_rate1 = memory_available * hourly_rate * hours_in_month
memory_cost_rate2 = memory_available * hourly_rate_2 * hours_in_month
expect(subject.memory_allocated_cost).to eq(memory_cost_rate1 + memory_cost_rate2)

used_metric = used_average_for(:derived_memory_used, hours_in_month, vm)
expect(subject.memory_used_metric).to eq(used_metric)

memory_cost_rate1 = used_metric * hourly_rate * hours_in_month
memory_cost_rate2 = used_metric * hourly_rate_2 * hours_in_month

expect(subject.memory_used_cost).to eq(memory_cost_rate1 + memory_cost_rate2)
expect(subject.memory_cost).to eq(subject.memory_allocated_cost + subject.memory_used_cost)

used_metric = used_average_for(:disk_usage_rate_average, hours_in_month, vm)
expect(subject.disk_io_used_metric).to eq(used_metric)

# disk io
disk_io_cost_rate1 = used_metric * hourly_rate * hours_in_month
disk_io_cost_rate2 = used_metric * hourly_rate_2 * hours_in_month
expect(subject.disk_io_used_cost).to eq(disk_io_cost_rate1 + disk_io_cost_rate2)

used_metric = used_average_for(:net_usage_rate_average, hours_in_month, vm)
expect(subject.net_io_used_metric).to eq(used_metric)

# net io
net_io_cost_rate1 = used_metric * hourly_rate * hours_in_month
net_io_cost_rate2 = used_metric * hourly_rate_2 * hours_in_month
expect(subject.net_io_used_cost).to eq(net_io_cost_rate1 + net_io_cost_rate2)

expect(subject.storage_allocated_metric).to eq(vm_allocated_disk_storage.gigabytes)

# storage
storage_cost_rate1 = vm_allocated_disk_storage * count_hourly_rate * hours_in_month
storage_cost_rate2 = vm_allocated_disk_storage * count_hourly_rate_2 * hours_in_month
expect(subject.storage_allocated_cost).to eq(storage_cost_rate1 + storage_cost_rate2)

used_metric = used_average_for(:derived_vm_used_disk_storage, hours_in_month, vm)
expect(subject.storage_used_metric).to eq(used_metric)
storage_cost_rate1 = used_metric / 1.gigabytes * count_hourly_rate * hours_in_month
storage_cost_rate2 = used_metric / 1.gigabytes * count_hourly_rate_2 * hours_in_month

expect(subject.storage_used_cost).to be_within(0.01).of(storage_cost_rate1 + storage_cost_rate2)
expect(subject.storage_cost).to eq(subject.storage_allocated_cost + subject.storage_used_cost)
end

context 'with fixed part for second chargeback rates' do
let(:fixed_rate) { 100 }
let(:hourly_variable_tier_rate_2) { {:variable_rate => hourly_rate_2.to_s, :fixed_rate => fixed_rate.to_s} }

it 'calculates accumulations' do
skip('this feature needs to be added to new chargeback') if Settings.new_chargeback

# memory
expect(subject.memory_allocated_metric).to eq(memory_available)

memory_cost_rate1 = memory_available * hourly_rate * hours_in_month
memory_cost_rate2 = fixed_rate * hours_in_month + memory_available * hourly_rate_2 * hours_in_month

expect(subject.memory_allocated_cost).to eq(memory_cost_rate1 + memory_cost_rate2)

used_metric = used_average_for(:derived_memory_used, hours_in_month, vm)
expect(subject.memory_used_metric).to eq(used_metric)

memory_cost_rate1 = used_metric * hourly_rate * hours_in_month
memory_cost_rate2 = fixed_rate * hours_in_month + used_metric * hourly_rate_2 * hours_in_month

expect(subject.memory_used_cost).to eq(memory_cost_rate1 + memory_cost_rate2)
expect(subject.memory_cost).to eq(subject.memory_allocated_cost + subject.memory_used_cost)

used_metric = used_average_for(:disk_usage_rate_average, hours_in_month, vm)
expect(subject.disk_io_used_metric).to eq(used_metric)
end
end
end

context 'more rates have been selected' do
let(:storage_chargeback_rate_1) { FactoryGirl.create(:chargeback_rate, :rate_type => "Storage") }
let(:storage_chargeback_rate_2) { FactoryGirl.create(:chargeback_rate, :rate_type => "Storage") }
let(:chargeback_vm) { Chargeback::RatesCache.new }
let(:chargeback_vm) { Chargeback::RatesCache.new(Chargeback::ReportOptions.new_from_h(base_options)) }

let(:parent_classification) { FactoryGirl.create(:classification) }
let(:classification_1) { FactoryGirl.create(:classification, :parent_id => parent_classification.id) }
Expand Down Expand Up @@ -941,9 +1113,8 @@ def result_row_for_vm(vm)
metric_rollup.update_attributes!(:tag_names => rate_assignment[:tag].first.tag.send(:name_path))
@vm.tag_with(["/managed/#{metric_rollup.tag_names}"], :ns => '*')
@vm.reload

consumption = Chargeback::ConsumptionWithRollups.new(pluck_rollup([metric_rollup]), nil, nil)
uniq_rates = Chargeback::RatesCache.new.get(consumption)
uniq_rates = Chargeback::RatesCache.new(Chargeback::ReportOptions.new_from_h(base_options)).get(consumption)
consumption.instance_variable_set(:@tag_names, nil)
consumption.instance_variable_set(:@hash_features_affecting_rate, nil)
expect([rate_assignment[:cb_rate]]).to match_array(uniq_rates)
Expand Down

0 comments on commit ae59274

Please sign in to comment.