Skip to content

Commit

Permalink
Merge pull request #17002 from lpichler/group_chargeback_report_for_v…
Browse files Browse the repository at this point in the history
…m_report_by_tenant

Group chargeback report for VMs by tenant
  • Loading branch information
gtanzillo committed Feb 20, 2018
2 parents c2f58c6 + 71fea20 commit 0d23948
Show file tree
Hide file tree
Showing 8 changed files with 221 additions and 2 deletions.
5 changes: 5 additions & 0 deletions app/models/chargeback.rb
Expand Up @@ -46,6 +46,9 @@ def self.report_row_key(consumption)
"#{classification_id}_#{ts_key}"
elsif @options[:groupby_label].present?
"#{groupby_label_value(consumption, @options[:groupby_label])}_#{ts_key}"
elsif @options.group_by_tenant?
tenant = @options.tenant_for(consumption)
"#{tenant ? tenant.id : 'none'}_#{ts_key}"
else
default_key(consumption, ts_key)
end
Expand Down Expand Up @@ -75,6 +78,7 @@ def initialize(options, consumption)
self.interval_name = options.interval
self.chargeback_rates = ''
self.entity ||= consumption.resource
self.tenant_name = consumption.resource.try(:tenant).try(:name) if options.group_by_tenant?
end

def showback_category
Expand Down Expand Up @@ -164,6 +168,7 @@ def self.set_chargeback_report_options(rpt, group_by, header_for_tag, groupby_la
static_cols = group_by == "project" ? report_static_cols - ["image_name"] : report_static_cols
static_cols = group_by == "tag" ? [report_tag_field] : static_cols
static_cols = group_by == "label" ? [report_label_field] : static_cols
static_cols = group_by == "tenant" ? ['tenant_name'] : static_cols
rpt.cols += static_cols
rpt.col_order = static_cols + ["display_range"]
rpt.sortby = static_cols + ["start_date"]
Expand Down
2 changes: 1 addition & 1 deletion app/models/chargeback/consumption_history.rb
Expand Up @@ -22,7 +22,7 @@ def self.for_report(cb_class, options)

# we are building hash with grouped calculated values
# values are grouped by resource_id and timestamp (query_start_time...query_end_time)
records.group_by(&:resource_id).each do |_, metric_rollup_records|
options.group_with(records).each_value do |metric_rollup_records|
consumption = ConsumptionWithRollups.new(metric_rollup_records, query_start_time, query_end_time)
yield(consumption) unless consumption.consumed_hours_in_interval.zero?
end
Expand Down
7 changes: 7 additions & 0 deletions app/models/chargeback/consumption_with_rollups.rb
Expand Up @@ -41,6 +41,13 @@ def max(metric, sub_metric = nil)
values.present? ? values.max : 0
end

def sum_of_maxes_from_grouped_values(metric, sub_metric = nil)
return max(metric, sub_metric) if sub_metric
@grouped_values ||= {}
grouped_rollups = @rollups.group_by { |x| x.resource.id }
@grouped_values[metric] ||= grouped_rollups.map { |_, rollups| rollups.collect(&metric.to_sym).compact.max }.compact.sum
end

def avg(metric, sub_metric = nil)
metric_sum = values(metric, sub_metric).sum
metric_sum / consumed_hours_in_interval
Expand Down
1 change: 1 addition & 0 deletions app/models/chargeback/consumption_without_rollups.rb
Expand Up @@ -60,6 +60,7 @@ def current_value(metric, _sub_metric = nil)
end
alias avg current_value
alias max current_value
alias sum_of_maxes_from_grouped_values current_value
private :current_value
end
end
16 changes: 15 additions & 1 deletion app/models/chargeback/report_options.rb
Expand Up @@ -17,7 +17,8 @@ class Chargeback
:userid,
:ext_options,
:include_metrics, # enable charging allocated resources with C & U
:method_for_allocated_metrics
:method_for_allocated_metrics,
:group_by_tenant?
) do
def self.new_from_h(hash)
new(*hash.values_at(*members))
Expand All @@ -32,6 +33,7 @@ def method_for_allocated_metrics
raise "Invalid method for allocated calculations #{method}"
end

return :sum_of_maxes_from_grouped_values if method == :max && group_by_tenant?
method
end

Expand Down Expand Up @@ -115,12 +117,24 @@ def duration_of_report_step
end
end

def tenant_for(consumption)
consumption.resource.tenant
end

def group_with(records)
group_by_tenant? ? records.group_by { |x| x.resource.tenant.id } : records.group_by(&:resource_id)
end

def classification_for(consumption)
tag = consumption.tag_names.find { |x| x.starts_with?(groupby_tag) } # 'department/*'
tag = tag.split('/').second unless tag.blank? # 'department/finance' -> 'finance'
tag_hash[tag]
end

def group_by_tenant?
self[:groupby] == 'tenant'
end

private

def tag_hash
Expand Down
1 change: 1 addition & 0 deletions app/models/chargeback_vm.rb
Expand Up @@ -6,6 +6,7 @@ class ChargebackVm < Chargeback
:vm_guid => :string,
:owner_name => :string,
:provider_name => :string,
:tenant_name => :string,
:provider_uid => :string,
:cpu_allocated_metric => :float,
:cpu_allocated_cost => :float,
Expand Down
190 changes: 190 additions & 0 deletions spec/models/chargeback_vm_spec.rb
Expand Up @@ -369,6 +369,196 @@
end
end

context 'monthly report, group by tenants' do
let(:options) do
{
:interval => "monthly",
:interval_size => 12,
:end_interval_offset => 1,
:tenant_id => tenant_1.id,
:method_for_allocated_metrics => :max,
:include_metrics => true,
:groupby => "tenant",
}
end

let(:monthly_used_rate) { hourly_rate * hours_in_month }
let(:monthly_allocated_rate) { count_hourly_rate * hours_in_month }

# My Company
# \___Tenant 2
# \___Tenant 3
# \__Tenant 4
# \__Tenant 5
#
let(:tenant_1) { Tenant.root_tenant }
let(:vm_1_1) { FactoryGirl.create(:vm_vmware, :tenant => tenant_1, :miq_group => nil) }
let(:vm_2_1) { FactoryGirl.create(:vm_vmware, :tenant => tenant_1, :miq_group => nil) }

let(:tenant_2) { FactoryGirl.create(:tenant, :name => 'Tenant 2', :parent => tenant_1) }
let(:vm_1_2) { FactoryGirl.create(:vm_vmware, :tenant => tenant_2, :miq_group => nil) }
let(:vm_2_2) { FactoryGirl.create(:vm_vmware, :tenant => tenant_2, :miq_group => nil) }

let(:tenant_3) { FactoryGirl.create(:tenant, :name => 'Tenant 3', :parent => tenant_1) }
let(:vm_1_3) { FactoryGirl.create(:vm_vmware, :tenant => tenant_3, :miq_group => nil) }
let(:vm_2_3) { FactoryGirl.create(:vm_vmware, :tenant => tenant_3, :miq_group => nil) }

let(:tenant_4) { FactoryGirl.create(:tenant, :name => 'Tenant 4', :divisible => false, :parent => tenant_3) }
let(:vm_1_4) { FactoryGirl.create(:vm_vmware, :tenant => tenant_4, :miq_group => nil) }
let(:vm_2_4) { FactoryGirl.create(:vm_vmware, :tenant => tenant_4, :miq_group => nil) }

let(:tenant_5) { FactoryGirl.create(:tenant, :name => 'Tenant 5', :divisible => false, :parent => tenant_3) }
let(:vm_1_5) { FactoryGirl.create(:vm_vmware, :tenant => tenant_5, :miq_group => nil) }
let(:vm_2_5) { FactoryGirl.create(:vm_vmware, :tenant => tenant_5, :miq_group => nil) }

subject { ChargebackVm.build_results_for_report_ChargebackVm(options).first }

let(:derived_vm_numvcpus_tenant_5) { 1 }
let(:cpu_usagemhz_rate_average_tenant_5) { 50 }

before(:each) do
add_metric_rollups_for([vm_1_1, vm_2_1], month_beginning...month_end, 8.hours, metric_rollup_params.merge!(:derived_vm_numvcpus => 1, :cpu_usagemhz_rate_average => 50))
add_metric_rollups_for([vm_1_2, vm_2_2], month_beginning...month_end, 8.hours, metric_rollup_params.merge!(:derived_vm_numvcpus => 1, :cpu_usagemhz_rate_average => 50))
add_metric_rollups_for([vm_1_3, vm_2_3], month_beginning...month_end, 8.hours, metric_rollup_params.merge!(:derived_vm_numvcpus => 1, :cpu_usagemhz_rate_average => 50))
add_metric_rollups_for([vm_1_4, vm_2_4], month_beginning...month_end, 8.hours, metric_rollup_params.merge!(:derived_vm_numvcpus => 1, :cpu_usagemhz_rate_average => 50))
add_metric_rollups_for([vm_1_5, vm_2_5], month_beginning...month_end, 8.hours, metric_rollup_params.merge!(:derived_vm_numvcpus => derived_vm_numvcpus_tenant_5, :cpu_usagemhz_rate_average => cpu_usagemhz_rate_average_tenant_5))
end

it 'reports each tenants' do
expect(subject.map(&:tenant_name)).to match_array([tenant_1, tenant_2, tenant_3, tenant_4, tenant_5].map(&:name))
end

def subject_row_for_tenant(tenant)
subject.detect { |x| x.tenant_name == tenant.name }
end

let(:hourly_usage) { 30 * 3.0 / 720 } # count of metric rollups / hours in month

it 'calculates allocated,used metric with using max,avg method with vcpus=1.0 and 50% usage' do
# sum of maxes from each VM:
# (max from first tenant_1's VM + max from second tenant_1's VM) * monthly_allocated_rate
expect(subject_row_for_tenant(tenant_1).cpu_allocated_metric).to eq(1 + 1)
expect(subject_row_for_tenant(tenant_1).cpu_allocated_cost).to eq((1 + 1) * monthly_allocated_rate)

expect(subject_row_for_tenant(tenant_2).cpu_allocated_metric).to eq(1 + 1)
expect(subject_row_for_tenant(tenant_2).cpu_allocated_cost).to eq((1 + 1) * monthly_allocated_rate)

expect(subject_row_for_tenant(tenant_3).cpu_allocated_metric).to eq(1 + 1)
expect(subject_row_for_tenant(tenant_3).cpu_allocated_cost).to eq((1 + 1) * monthly_allocated_rate)

expect(subject_row_for_tenant(tenant_4).cpu_allocated_metric).to eq(1 + 1)
expect(subject_row_for_tenant(tenant_4).cpu_allocated_cost).to eq((1 + 1) * monthly_allocated_rate)

expect(subject_row_for_tenant(tenant_5).cpu_allocated_metric).to eq(1 + 1)
expect(subject_row_for_tenant(tenant_5).cpu_allocated_cost).to eq((1 + 1) * monthly_allocated_rate)

# each tenant has 2 VMs and each VM has 50 of cpu usage:
# 5 tenants(tenant_1 has 4 tenants and plus tenant_1 ) * 2 VMs * 50% of usage
expect(subject_row_for_tenant(tenant_1).cpu_used_metric).to eq(2 * 50 * hourly_usage)
# and cost - there is multiplication by monthly_used_rate
expect(subject_row_for_tenant(tenant_1).cpu_used_cost).to eq(2 * 50 * hourly_usage * monthly_used_rate)

expect(subject_row_for_tenant(tenant_2).cpu_used_metric).to eq(2 * 50 * hourly_usage)
expect(subject_row_for_tenant(tenant_2).cpu_used_cost).to eq(2 * 50 * hourly_usage * monthly_used_rate)

expect(subject_row_for_tenant(tenant_3).cpu_used_metric).to eq(2 * 50 * hourly_usage)
expect(subject_row_for_tenant(tenant_3).cpu_used_cost).to eq(2 * 50 * hourly_usage * monthly_used_rate)

expect(subject_row_for_tenant(tenant_4).cpu_used_metric).to eq(2 * 50 * hourly_usage)
expect(subject_row_for_tenant(tenant_4).cpu_used_cost).to eq(2 * 50 * hourly_usage * monthly_used_rate)

expect(subject_row_for_tenant(tenant_5).cpu_used_metric).to eq(2 * 50 * hourly_usage)
expect(subject_row_for_tenant(tenant_5).cpu_used_cost).to eq(2 * 50 * hourly_usage * monthly_used_rate)
end

context 'vcpu=5 for VMs of tenant_5' do
let(:derived_vm_numvcpus_tenant_5) { 5 }
let(:cpu_usagemhz_rate_average_tenant_5) { 75 }

it 'calculates allocated,used metric with using max,avg method with vcpus=1.0 and 50% usage' do
expect(subject_row_for_tenant(tenant_1).cpu_allocated_metric).to eq(1 + 1)
expect(subject_row_for_tenant(tenant_1).cpu_allocated_cost).to eq((1 + 1) * monthly_allocated_rate)

expect(subject_row_for_tenant(tenant_2).cpu_allocated_metric).to eq(1 + 1)
expect(subject_row_for_tenant(tenant_2).cpu_allocated_cost).to eq((1 + 1) * monthly_allocated_rate)

expect(subject_row_for_tenant(tenant_3).cpu_allocated_metric).to eq(1 + 1)
expect(subject_row_for_tenant(tenant_3).cpu_allocated_cost).to eq((1 + 1) * monthly_allocated_rate)

expect(subject_row_for_tenant(tenant_4).cpu_allocated_metric).to eq(1 + 1)
expect(subject_row_for_tenant(tenant_4).cpu_allocated_cost).to eq((1 + 1) * monthly_allocated_rate)

expect(subject_row_for_tenant(tenant_5).cpu_allocated_metric).to eq(5 + 5)
expect(subject_row_for_tenant(tenant_5).cpu_allocated_cost).to eq((5 + 5) * monthly_allocated_rate)

# each tenant has 2 VMs and each VM has 50 of cpu usage:
# 5 tenants(tenant_1 has 4 tenants and plus tenant_1 ) * 2 VMs * 50% of usage
# but tenant_5 has 2 VMs and each VM has 75 of cpu usage
expect(subject_row_for_tenant(tenant_1).cpu_used_metric).to eq(hourly_usage * 2 * 50)
# and cost - there is multiplication by monthly_used_rate
expect(subject_row_for_tenant(tenant_1).cpu_used_cost).to eq(hourly_usage * 2 * 50 * monthly_used_rate)

expect(subject_row_for_tenant(tenant_2).cpu_used_metric).to eq(hourly_usage * 2 * 50)
expect(subject_row_for_tenant(tenant_2).cpu_used_cost).to eq(hourly_usage * 2 * 50 * monthly_used_rate)

expect(subject_row_for_tenant(tenant_3).cpu_used_metric).to eq(hourly_usage * 2 * 50)
expect(subject_row_for_tenant(tenant_3).cpu_used_cost).to eq(hourly_usage * 2 * 50 * monthly_used_rate)

expect(subject_row_for_tenant(tenant_4).cpu_used_metric).to eq(hourly_usage * 2 * 50)
expect(subject_row_for_tenant(tenant_4).cpu_used_cost).to eq(hourly_usage * 2 * 50 * monthly_used_rate)

expect(subject_row_for_tenant(tenant_5).cpu_used_metric).to eq(hourly_usage * 2 * 75)
expect(subject_row_for_tenant(tenant_5).cpu_used_cost).to eq(hourly_usage * 2 * 75 * monthly_used_rate)
end

context 'test against group by vm report' do
let(:options_group_vm) do
{
:interval => "monthly",
:interval_size => 12,
:end_interval_offset => 1,
:tenant_id => tenant_1.id,
:method_for_allocated_metrics => :max,
:include_metrics => true,
:groupby => "vm"
}
end

def result_row_for_vm(vm)
result_group_by_vm.detect { |x| x.vm_name == vm.name }
end

let(:result_group_by_vm) { ChargebackVm.build_results_for_report_ChargebackVm(options_group_vm).first }

it 'calculates used metric and cost same as report for each vm' do
# Tenant 1 VMs
all_vms_cpu_metric = [vm_1_1, vm_2_1].map { |vm| result_row_for_vm(vm).cpu_used_metric }.sum
all_vms_cpu_cost = [vm_1_1, vm_2_1].map { |vm| result_row_for_vm(vm).cpu_used_cost }.sum

# Tenant 1
expect(subject_row_for_tenant(tenant_1).cpu_used_metric).to eq(all_vms_cpu_metric)
expect(subject_row_for_tenant(tenant_1).cpu_used_cost).to eq(all_vms_cpu_cost)

# Tenant 5 Vms
result_vm15 = result_row_for_vm(vm_1_5)
result_vm25 = result_row_for_vm(vm_2_5)

expect(subject_row_for_tenant(tenant_5).cpu_used_metric).to eq(result_vm15.cpu_used_metric + result_vm25.cpu_used_metric)
expect(subject_row_for_tenant(tenant_5).cpu_used_cost).to eq(result_vm15.cpu_used_cost + result_vm25.cpu_used_cost)
end

it 'calculated allocted metric and cost with using max(max is not summed up - it is taken maximum)' do
# Tenant 1 VMs
all_vms_cpu_metric = [vm_1_1, vm_2_1].map { |vm| result_row_for_vm(vm).cpu_allocated_metric }.sum
all_vms_cpu_cost = [vm_1_1, vm_2_1].map { |vm| result_row_for_vm(vm).cpu_allocated_cost }.sum

expect(subject_row_for_tenant(tenant_1).cpu_allocated_metric).to eq(all_vms_cpu_metric)
expect(subject_row_for_tenant(tenant_1).cpu_allocated_cost).to eq(all_vms_cpu_cost)
end
end
end
end

context "Monthly" do
let(:options) { base_options.merge(:interval => 'monthly') }
before do
Expand Down
1 change: 1 addition & 0 deletions spec/models/metering_vm_spec.rb
Expand Up @@ -126,6 +126,7 @@
storage_used_metric
metering_used_metric
existence_hours_metric
tenant_name
)
end

Expand Down

0 comments on commit 0d23948

Please sign in to comment.