Cyclops 4 HPC is the purpose built stack to support large HPC centers with resource accounting and billing of cluster as well as cloud resources.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
cyclops-4-hpc/services/cdr/server/dbManager/dbManager.go

1086 lines
27 KiB

package dbManager
import (
"encoding/json"
"errors"
"fmt"
"math"
"reflect"
"strconv"
"strings"
"time"
"github.com/go-openapi/strfmt"
"github.com/prometheus/client_golang/prometheus"
datamodels "gitlab.com/cyclops-utilities/datamodels"
"github.com/Cyclops-Labs/cyclops-4-hpc.git/services/cdr/models"
"github.com/Cyclops-Labs/cyclops-4-hpc.git/services/cdr/server/cacheManager"
cusModels "github.com/Cyclops-Labs/cyclops-4-hpc.git/services/customerdb/models"
pmModels "github.com/Cyclops-Labs/cyclops-4-hpc.git/services/plan-manager/models"
udrModels "github.com/Cyclops-Labs/cyclops-4-hpc.git/services/udr/models"
l "gitlab.com/cyclops-utilities/logging"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
const (
scaler = 1e9
statusDuplicated = iota
statusFail
statusMissing
statusOK
)
var (
totalTime float64
totalCount int64
)
// DbParameter is the struct defined to group and contain all the methods
// that interact with the database.
// On it there is the following parameters:
// - Cache: CacheManager pointer for the cache mechanism.
// - connStr: strings with the connection information to the database.
// - Db: a gorm.DB pointer to the db to invoke all the db methods.
type DbParameter struct {
Cache *cacheManager.CacheManager
connStr string
Db *gorm.DB
Metrics map[string]*prometheus.GaugeVec
Pipe chan interface{}
}
// New is the function to create the struct DbParameter.
// Parameters:
// - dbConn: strings with the connection information to the database.
// - tables: array of interfaces that will contains the models to migrate
// to the database on initialization.
// Returns:
// - DbParameter: struct to interact with dbManager functionalities.
func New(dbConn string, tables ...interface{}) *DbParameter {
l.Trace.Printf("[DB] Gerenating new DBParameter.\n")
var (
dp DbParameter
err error
)
dp.connStr = dbConn
dp.Db, err = gorm.Open(postgres.Open(dbConn), &gorm.Config{})
if err != nil {
l.Error.Printf("[DB] Error opening connection. Error: %v\n", err)
}
l.Trace.Printf("[DB] Migrating tables.\n")
//Database migration, it handles everything
dp.Db.AutoMigrate(tables...)
//l.Trace.Printf("[DB] Generating hypertables.\n")
// Hypertables creation for timescaledb in case of needed
//dp.Db.Exec("SELECT create_hypertable('" + dp.Db.NewScope(&models.TABLE).TableName() + "', 'TIMESCALE-ROW-INDEX');")
return &dp
}
// AddRecord job is to save in the system the provided CDR report to be easy to
// retrieve later with the usage endpoints.
// Parameters:
// - cdr: the CDR report model to be added to the system.
// Returns:
// - e: error in case of duplication or failure in the task.
func (d *DbParameter) AddRecord(cdr models.CReport) (e error) {
l.Trace.Printf("[DB] Attempting to add a new cost report to the system.\n")
if r := d.Db.Where(&cdr).First(&models.CReport{}).Error; errors.Is(r, gorm.ErrRecordNotFound) {
e = d.Db.Create(&cdr).Error
if e != nil {
l.Trace.Printf("[DB] Something went wrong when adding a new usage report to the system. Error: %v\n", e)
} else {
d.Metrics["count"].With(prometheus.Labels{"type": "CDR Reports added"}).Inc()
l.Trace.Printf("[DB] New cost report added to the system.\n")
}
} else {
l.Warning.Printf("[DB] Cost report already in the system, skipping creation...\n")
}
l.Trace.Printf("[DB] Attempting to add CDR Records from reported account [ %v ] to the system.\n", cdr.AccountID)
for i := range cdr.Usage {
var cdrrecord, cdrUpdate models.CDRRecord
cdrrecord.AccountID = cdr.AccountID
cdrrecord.Metadata = cdr.Usage[i].Metadata
cdrrecord.ResourceID = cdr.Usage[i].ResourceID
cdrrecord.ResourceName = cdr.Usage[i].ResourceName
cdrrecord.ResourceType = cdr.Usage[i].ResourceType
cdrrecord.TimeFrom = cdr.TimeFrom
cdrrecord.TimeTo = cdr.TimeTo
cdrrecord.Unit = cdr.Usage[i].Unit
if r := d.Db.Where(&cdrrecord).First(&cdrUpdate).Error; errors.Is(r, gorm.ErrRecordNotFound) {
cdrrecord.Cost = cdr.Usage[i].Cost
cdrrecord.UsageBreakup = cdr.Usage[i].UsageBreakup
if e = d.Db.Create(&cdrrecord).Error; e != nil {
l.Warning.Printf("[DB] Something went wrong when adding a new CDR Record to the system. Error: %v\n", e)
} else {
d.Metrics["count"].With(prometheus.Labels{"type": "CDR Records added"}).Inc()
l.Trace.Printf("[DB] New CDR Record added in the system.\n")
}
} else {
l.Trace.Printf("[DB] CDR record already in the system, updating...\n")
cdrrecord.Cost = cdr.Usage[i].Cost
cdrrecord.UsageBreakup = cdr.Usage[i].UsageBreakup
if e = d.Db.Model(&cdrUpdate).Updates(&cdrrecord).Error; e == nil {
d.Metrics["count"].With(prometheus.Labels{"type": "CDR Records updated"}).Inc()
l.Trace.Printf("[DB] CDR Record updated in the system.\n")
} else {
l.Trace.Printf("[DB] Something went wrong when updating the existing CDR Record to the system. Error: %v\n", e)
}
}
}
return
}
// GetReport job is to retrieve the compacted usage records from the system for
// the provided account in the requested time-window with the posibility of
// filter by metric.
// Parameters:
// - id: string containing the id of the account requested.
// - metric: a string with the metric to filter the records.
// - from: a datatime reference for the initial border of the time-window.
// - to: a datatime reference for the final border of the time-window.
// Returns:
// - a slice of references with the usage contained in the system within the
// requested time-window.
func (d *DbParameter) GetReport(id string, metric string, from strfmt.DateTime, to strfmt.DateTime) []*models.CDRReport {
l.Trace.Printf("[DB] Attempting to retrieve the compacted usage records.\n")
var r []*models.CDRReport
var u models.CDRRecord
window := d.getWindow(from, to)
if id != "" {
u.AccountID = id
}
if metric != "" {
u.ResourceType = metric
}
if e := d.Db.Where(window).Where(&u).Find(&models.CDRRecord{}).Scan(&r).Error; e != nil {
l.Warning.Printf("[DB] Something went wrong while retrieving the compacted usage records from the system. Error: %v\n", e)
} else {
l.Debug.Printf("[DB] [ %v ] compacted usage records retrieved from the system.\n", len(r))
}
return r
}
// GetUsage job is to retrieve the usage report of the system or the specified
// account within the requested time-window with the posibility to filter by
// metric.
// Parameters:
// - id: string containing the id of the account requested.
// - metric: a string with the metric to filter the records.
// - from: a datatime reference for the initial border of the time-window.
// - to: a datatime reference for the final border of the time-window.
// Returns:
// - a slice of references with the usage report contained in the system within
// the requested time-window.
// - error in case of duplication or failure in the task.
func (d *DbParameter) GetUsage(id, metric string, from, to strfmt.DateTime) ([]*models.CReport, error) {
l.Trace.Printf("[DB] Attempting to retrieve the usage report from the system.\n")
var r []*models.CReport
var r0 models.CReport
var e error
window := d.getWindow(from, to)
if id != "" {
r0.AccountID = id
}
e = d.Db.Where(window).Where(&r0).Find(&r).Error
if e == nil {
for idx := range r {
r[idx].Usage = d.GetReport(r[idx].AccountID, metric, r[idx].TimeFrom, r[idx].TimeTo)
}
l.Debug.Printf("[DB] [ %v ] Usage reports retrieved from the system.\n", len(r))
} else {
l.Warning.Printf("[DB] Something went wrong while retrieving the usage reports from the system. Error: %v\n", e)
}
return r, e
}
// GetUsages job is to retrieve the usage report of the system or the specified
// account accounts within the requested time-window with the posibility to
// filter by metric.
// Parameters:
// - ids: string containing the list of ids of the accounts requested.
// - metric: a string with the metric to filter the records.
// - from: a datatime reference for the initial border of the time-window.
// - to: a datatime reference for the final border of the time-window.
// Returns:
// - a slice of references with the usage report contained in the system within
// the requested time-window.
// - error in case of duplication or failure in the task.
func (d *DbParameter) GetUsages(ids, metric string, from, to strfmt.DateTime) ([]*models.CReport, error) {
l.Trace.Printf("[DB] Attempting to retrieve the usage report from the system.\n")
var total, r []*models.CReport
var r0 models.CReport
var e error
window := d.getWindow(from, to)
list := strings.Split(ids, ",")
for _, acc := range list {
r0.AccountID = acc
if e = d.Db.Where(window).Where(&r0).Find(&r).Error; e == nil {
for idx := range r {
r[idx].Usage = d.GetReport(r[idx].AccountID, metric, r[idx].TimeFrom, r[idx].TimeTo)
}
l.Debug.Printf("[DB] [ %v ] Usage reports retrieved from the system.\n", len(r))
} else {
l.Warning.Printf("[DB] Something went wrong while retrieving the usage reports from the system. Error: %v\n", e)
}
total = append(total, r...)
}
return total, e
}
// GetUsageSummary job is to retrieve the usage report of the system of the specified
// account within the requested time-window and process it into an easy to use
// estructure for the UI.
// Parameters:
// - id: string containing the id of the account requested.
// - from: a datatime reference for the initial border of the time-window.
// - to: a datatime reference for the final border of the time-window.
// Returns:
// - a references with the usage report contained in the system within
// the requested time-window.
// - error in case of duplication or failure in the task.
func (d *DbParameter) GetUsageSummary(id string, from, to strfmt.DateTime) (*models.UISummary, error) {
l.Trace.Printf("[DB] Attempting to retrieve the usage report from the system.\n")
var usages []*models.CDRReport
var report models.UISummary
interval := float64(28800) //UDR are created every 8h and the usage is provided in GB*s...
rounder := float64(((time.Time)(to)).Sub((time.Time)(from)).Seconds()) / interval
report.AccountID = id
report.TimeFrom = from
report.TimeTo = to
// Get the reseller data
r, err := d.Cache.Get(id, "reseller", "")
if err != nil {
l.Warning.Printf("[DB] Something went wrong while retrieving the reseller info for id [ %v ]. Error: %v\n", id, err)
return nil, err
}
reseller := r.(cusModels.Reseller)
// Get all the usages linked to the reseller
for _, customer := range reseller.Customers {
for _, product := range customer.Products {
u := d.GetReport(product.ProductID, "", from, to)
usages = append(usages, u...)
}
}
// Scan the usages and make the numbers
resourcesMap := make(datamodels.JSONdb)
for i := range usages {
masterKey := strings.ToTitle(usages[i].ResourceType)
if _, exists := resourcesMap[masterKey]; !exists {
resourcesMap[masterKey] = make(datamodels.JSONdb)
}
// First the total
key := "TotalCount"
if _, exists := resourcesMap[masterKey].(datamodels.JSONdb)[key]; !exists {
resourcesMap[masterKey].(datamodels.JSONdb)[key] = float64(0)
}
resourcesMap[masterKey].(datamodels.JSONdb)[key] = resourcesMap[masterKey].(datamodels.JSONdb)[key].(float64) + float64(1/rounder)
// Second the regions
if region, exists := usages[i].Metadata["region"]; exists {
key := "Regions"
if _, exists := resourcesMap[masterKey].(datamodels.JSONdb)[key]; !exists {
resourcesMap[masterKey].(datamodels.JSONdb)[key] = make(datamodels.JSONdb)
}
regionKey := region.(string)
if _, exists := resourcesMap[masterKey].(datamodels.JSONdb)[key].(datamodels.JSONdb)[regionKey]; !exists {
resourcesMap[masterKey].(datamodels.JSONdb)[key].(datamodels.JSONdb)[regionKey] = float64(0)
}
resourcesMap[masterKey].(datamodels.JSONdb)[key].(datamodels.JSONdb)[regionKey] = resourcesMap[masterKey].(datamodels.JSONdb)[key].(datamodels.JSONdb)[regionKey].(float64) + float64(1/rounder)
}
// Third the flavors in case of
if flavor, exists := usages[i].Metadata["flavorname"]; exists {
key := "Flavors"
if _, exists := resourcesMap[masterKey].(datamodels.JSONdb)[key]; !exists {
resourcesMap[masterKey].(datamodels.JSONdb)[key] = make(datamodels.JSONdb)
}
flavorKey := flavor.(string)
if _, exists := resourcesMap[masterKey].(datamodels.JSONdb)[key].(datamodels.JSONdb)[flavorKey]; !exists {
resourcesMap[masterKey].(datamodels.JSONdb)[key].(datamodels.JSONdb)[flavorKey] = float64(0)
}
resourcesMap[masterKey].(datamodels.JSONdb)[key].(datamodels.JSONdb)[flavorKey] = resourcesMap[masterKey].(datamodels.JSONdb)[key].(datamodels.JSONdb)[flavorKey].(float64) + float64(1/rounder)
}
// Fourth the sizes in case of
if size, exists := usages[i].Metadata["size"]; exists {
sizeF, err := strconv.ParseFloat(size.(string), 64)
if err != nil {
l.Warning.Printf("[DB] Failed to turn the size [ %v ] into a float64 value. Error: %v\n", size, err)
} else {
key := "TotalAmount"
if _, exists := resourcesMap[key]; !exists {
resourcesMap[masterKey].(datamodels.JSONdb)[key] = float64(0)
}
resourcesMap[masterKey].(datamodels.JSONdb)[key] = resourcesMap[masterKey].(datamodels.JSONdb)[key].(float64) + float64(sizeF/rounder)
}
}
// UDR usages
if size, exists := usages[i].UsageBreakup["used"]; exists {
var sizeF float64
key := "TotalAmount"
// size to float64
if k := reflect.ValueOf(size); k.Kind() == reflect.Float64 {
sizeF = size.(float64) / interval
} else {
v, err := size.(json.Number).Float64()
if err != nil {
l.Warning.Printf("[DB] Failed to turn the size [ %v ] into a float64 value. Error: %v\n", size, err)
} else {
sizeF = float64(v) / interval
}
}
if _, exists := resourcesMap[masterKey].(datamodels.JSONdb)[key]; !exists {
resourcesMap[masterKey].(datamodels.JSONdb)[key] = float64(0)
}
resourcesMap[masterKey].(datamodels.JSONdb)[key] = resourcesMap[masterKey].(datamodels.JSONdb)[key].(float64) + float64(sizeF/rounder)
}
}
// Reshape of the JSON to easy showing in the front..
for key, value := range resourcesMap {
metricData := value.(datamodels.JSONdb)
if _, exists := metricData["Regions"]; exists {
var regions []datamodels.JSONdb
for k, v := range metricData["Regions"].(datamodels.JSONdb) {
region := make(datamodels.JSONdb)
region["name"] = k
region["amount"] = v
regions = append(regions, region)
}
resourcesMap[key].(datamodels.JSONdb)["Regions"] = regions
}
if _, exists := metricData["Flavors"]; exists {
var regions []datamodels.JSONdb
for k, v := range metricData["Flavors"].(datamodels.JSONdb) {
region := make(datamodels.JSONdb)
region["name"] = k
region["amount"] = v
regions = append(regions, region)
}
resourcesMap[key].(datamodels.JSONdb)["Flavors"] = regions
}
}
// Create the summarey report
report.UsageBreakup = resourcesMap
return &report, nil
}
// getWindow job is to select the timeframe for the usage retrievals according
// to the data providad from<window, window<to, or from<window<to.
// Parameters:
// - from: a datatime reference for the initial border of the time-window.
// - to: a datatime reference for the final border of the time-window.
// Returns:
// - s: a string with the needed window correctly formated to be used with GORM.
func (d *DbParameter) getWindow(from, to strfmt.DateTime) (s string) {
l.Trace.Printf("[DB] Attempting to get a window for a db query.\n")
if !((time.Time)(from)).IsZero() {
if ((time.Time)(to)).IsZero() {
s = fmt.Sprintf("%v >= '%v'", d.Db.NamingStrategy.ColumnName("", "TimeFrom"), from)
} else {
s = fmt.Sprintf("%v >= '%v' AND %v <= '%v'", d.Db.NamingStrategy.ColumnName("", "TimeFrom"), from, d.Db.NamingStrategy.ColumnName("", "TimeTo"), to)
}
} else {
if !((time.Time)(to)).IsZero() {
s = fmt.Sprintf("%v <= '%v'", d.Db.NamingStrategy.ColumnName("", "TimeTo"), to)
}
}
return s
}
// ProcessUDR job is to process the UDR Reports and turn then into CDR Reports
// and store them in the system.
// Parameters:
// - report: UDR Report model reference to be processed.
// - token: an optional keycloak token in case it's provided.
// Returns:
// - e: error in case of duplication or failure in the task.
func (d *DbParameter) ProcessUDR(report udrModels.UReport, token string) (e error) {
l.Trace.Printf("[DB] Attempting to process and transform the UDR report into a CDR report and save it to the system.\n")
// First we clean all the records so we always have a clean UDR generation
templateRecord := models.CDRRecord{
AccountID: report.AccountID,
TimeFrom: report.TimeFrom,
TimeTo: report.TimeTo,
}
l.Info.Printf("[DB] Clean up of previous CDRRecords started for period [ %v ] - [ %v ].\n", report.TimeFrom, report.TimeTo)
if e := d.Db.Where(&templateRecord).Delete(&models.CDRRecord{}).Error; e != nil {
l.Warning.Printf("[DB] The clean up of previous CDRecords failed. Error: %v\n", e)
}
templateReport := models.CReport{
AccountID: report.AccountID,
TimeFrom: report.TimeFrom,
TimeTo: report.TimeTo,
}
l.Info.Printf("[DB] Clean up of previous CReports started for period [ %v ] - [ %v ].\n", report.TimeFrom, report.TimeTo)
if e := d.Db.Where(&templateReport).Delete(&models.CReport{}).Error; e != nil {
l.Warning.Printf("[DB] The clean up of previous CReports failed. Error: %v\n", e)
}
// First we get the metric and accounts in the system.
var planID string
var cdr models.CReport
var usages []*models.CDRReport
now := time.Now().UnixNano()
x, e := d.Cache.Get(report.AccountID, "product", token)
if e != nil {
l.Warning.Printf("[DB] Something went wrong while retrieving the product info for id [ %v ]. Error: %v\n", report.AccountID, e)
return
}
product := x.(cusModels.Product)
if product.PlanID != "" {
planID = product.PlanID
} else if product.Type != "" {
planID = product.Type
} else {
c, e := d.Cache.Get(product.CustomerID, "customer", token)
if e != nil {
l.Warning.Printf("[DB] Something went wrong while retrieving the customer info for id [ %v ]. Error: %v\n", product.CustomerID, e)
return e
}
customer := c.(cusModels.Customer)
if customer.PlanID != "" {
planID = customer.PlanID
} else {
r, e := d.Cache.Get(customer.ResellerID, "reseller", token)
if e != nil {
l.Warning.Printf("[DB] Something went wrong while retrieving the reseller info for id [ %v ]. Error: %v\n", customer.ResellerID, e)
return e
}
reseller := r.(cusModels.Reseller)
planID = reseller.PlanID
}
}
PlanDefault:
p, e := d.Cache.Get(planID, "plan", token)
if e != nil {
if planID == "DEFAULT" {
l.Warning.Printf("[DB] Something went wrong while retrieving the default plan id [ %v ]. Error: %v\n", planID, e)
return
}
l.Warning.Printf("[DB] Something went wrong while retrieving the plan id [ %v ]. Re-trying with default plan. Error: %v\n", planID, e)
planID = "DEFAULT"
goto PlanDefault
}
plan := p.(pmModels.Plan)
savedPlan := p.(pmModels.Plan)
// In case the plan is not valid we return to the deault plan (id 0) which is valid ad vitam
if (time.Now()).After((time.Time)(*plan.OfferedEndDate)) || (time.Now()).Before((time.Time)(*plan.OfferedStartDate)) {
l.Warning.Printf("[DB] The plan [ %v ] is only valid between [ %v ] and [ %v ]. Falling back to default plan.\n", plan.ID, *plan.OfferedStartDate, *plan.OfferedEndDate)
planID = "DEFAULT"
goto PlanDefault
}
s, e := d.Cache.Get("ALL", "sku", token)
if e != nil {
l.Warning.Printf("[DB] Something went wrong while retrieving the skus list. Error: %v\n", e)
return
}
skus := make(map[string]string)
for _, k := range s.([]*pmModels.Sku) {
skus[*k.Name] = k.ID
}
// having the customer and the plan...
// iter the report
for _, u := range report.Usage {
costBreakup := make(datamodels.JSONdb)
var costSkuTotal []datamodels.JSONdb
var cycles []*pmModels.Cycle
var bundle pmModels.SkuBundle
var skuDiscount, skuPrice, usage, multiplier, amount, quantity float64
// get cycle of resourceType
c, e := d.Cache.Get(u.ResourceType, "cycle", token)
if e != nil {
l.Warning.Printf("[DB] Something went wrong while retrieving the states for the life cycle linked to the ResourceType [ %v ]. Error: %v\n", u.ResourceType, e)
return e
}
cycles = c.([]*pmModels.Cycle)
// if server, get the skubundle associated
if _, exist := cycles[0].SkuList[u.ResourceType]; !exist {
if id, exists := u.Metadata["flavorid"]; exists {
b, e := d.Cache.Get(id, "bundle", token)
if e != nil {
l.Warning.Printf("[DB] Something went wrong while retrieving the skuBundle id [ %v ]. Error: %v\n", u.Metadata["flavorid"], e)
return e
}
bundle = b.(pmModels.SkuBundle)
// Usecase: flavor is a generic one but then the image is a windows base one, so the license is not included by default
if id, exists := u.Metadata["imagename"]; exists && strings.Contains(strings.ToLower(id.(string)), "windows") {
_, license := bundle.SkuPrices["license"]
vcpu, exists := bundle.SkuPrices["vcpu"]
if !license && exists {
var previous pmModels.SkuBundle
previous.ID = bundle.ID
previous.Name = bundle.Name
previous.SkuPrices = make(datamodels.JSONdb)
for k, v := range bundle.SkuPrices {
previous.SkuPrices[k] = v
}
previous.SkuPrices["license"] = vcpu
bundle = previous
}
}
} else {
l.Warning.Printf("[DB] Something is wrong. Error: The flavorid is supposed to exist in the metadata.\n")
continue
}
}
// METADA OVERRIDERS
if override, exists := u.Metadata["PlanOverride"]; exists && override.(bool) {
overridePlan, e := d.Cache.Get("DEFAULT", "plan", token)
if e != nil {
l.Warning.Printf("[DB] Something went wrong while retrieving the Overriding plan. Error: %v\n", e)
} else {
plan = overridePlan.(pmModels.Plan)
}
} else {
plan = savedPlan
}
// loop cycle
for i := range cycles {
// if usageBreakup(cycle.state) !exists, continue
if stateUse, exists := u.UsageBreakup[*(cycles[i].State)]; !exists {
continue
// else, get the usage value
} else {
// usage to float64
if k := reflect.ValueOf(stateUse); k.Kind() == reflect.Float64 {
usage = stateUse.(float64)
} else {
v, _ := stateUse.(json.Number).Int64()
usage = float64(v)
}
}
// and loop actual.cycle.skus
for sku, value := range cycles[i].SkuList {
// get the skuDiscount and skuPrice associated
if id, exists := skus[sku]; exists {
for j := range plan.SkuPrices {
if *plan.SkuPrices[j].SkuID == id {
skuDiscount = float64(plan.SkuPrices[j].Discount)
skuPrice = float64(*plan.SkuPrices[j].UnitPrice)
break
}
}
} else {
l.Warning.Printf("[DB] Something is wrong. Error: [ %v ] is supposed to exist in the system as a sku.\n", u.ResourceType)
continue
}
// multiplier to float64
if k := reflect.ValueOf(value); k.Kind() == reflect.Float64 {
multiplier = value.(float64)
} else {
v, _ := value.(json.Number).Int64()
multiplier = float64(v)
}
// if resType not server then use actual.cycle.skus as multiplier for usage
if *cycles[i].ResourceType == sku {
// Size of buckets to be computed
if sz, exists := u.Metadata["size"]; exists {
size, e := strconv.ParseInt(sz.(string), 10, 0)
if e != nil {
l.Warning.Printf("[DB] Something is wrong with the size [ %v ] of the metadata. Error: %v.\n", sz, u.ResourceType)
amount = usage * multiplier
} else {
amount = usage * multiplier * float64(size)
}
} else {
amount = usage * multiplier
}
// else, resType is server
} else {
// check if in bundle for amounts, get bundle value times the usage and use actual.cycle.skus as an extra multiplier
if q, exists := bundle.SkuPrices[sku]; !exists {
continue
} else {
// quantity to float64
if k := reflect.ValueOf(q); k.Kind() == reflect.Float64 {
quantity = q.(float64)
} else {
v, _ := q.(json.Number).Int64()
quantity = float64(v)
}
amount = usage * multiplier * quantity
}
}
// create the cost elemt
costSku := make(datamodels.JSONdb)
costSku["sku"] = sku
costSku["sku-state"] = *cycles[i].State
// get cost
costSku["sku-cost"] = amount * skuPrice
// get discount as % of cost
costSku["sku-discount"] = costSku["sku-cost"].(float64) * skuDiscount
// get the net cost as cost-discount
costSku["sku-net"] = costSku["sku-cost"].(float64) - costSku["sku-discount"].(float64)
// Add the cost to the collection
costSkuTotal = append(costSkuTotal, costSku)
}
}
// place the costbreakup in place
costBreakup["costBreakup"] = costSkuTotal
if costBreakup["costBreakup"] == nil {
l.Warning.Printf("[DB] Something went wrong in the cost breakup generation. Skipping this element...\n")
continue
}
// Let's create the total cost
costBreakup["totalFromSku"] = float64(0)
for _, c := range costBreakup["costBreakup"].([]datamodels.JSONdb) {
costBreakup["totalFromSku"] = costBreakup["totalFromSku"].(float64) + c["sku-net"].(float64)
}
costBreakup["appliedDiscount"] = costBreakup["totalFromSku"].(float64) * float64(plan.Discount)
costBreakup["netTotal"] = costBreakup["totalFromSku"].(float64) - costBreakup["appliedDiscount"].(float64)
var use models.CDRReport
use.Cost = costBreakup
use.Metadata = u.Metadata
use.ResourceID = u.ResourceID
use.ResourceName = u.ResourceName
use.ResourceType = u.ResourceType
use.Unit = u.Unit
use.UsageBreakup = u.UsageBreakup
usages = append(usages, &use)
}
cdr.AccountID = report.AccountID
cdr.TimeFrom = report.TimeFrom
cdr.TimeTo = report.TimeTo
cdr.Usage = usages
if e = d.AddRecord(cdr); e != nil {
l.Warning.Printf("[DB] Something went wrong while saving the CDR record in the system. Error: %v\n", e)
return
}
l.Trace.Printf("[DB] UDR processed and transformed into CDR and saved in the system successfully .\n")
d.Pipe <- cdr
l.Trace.Printf("[DB] CDR transmited to the Credit Manager successfully .\n")
totalTime += float64(time.Now().UnixNano() - now)
totalCount++
d.Metrics["count"].With(prometheus.Labels{"type": "Total UDRs transformed to CDRs"}).Inc()
d.Metrics["time"].With(prometheus.Labels{"type": "CDRs average generation time"}).Set(totalTime / float64(totalCount) / float64(time.Millisecond))
return
}
// getNiceFloat job is to turn the ugly float64 provided into a "nice looking"
// one according to the scale set, so we move from a floating coma number into
// a fixed coma number.
// Parameters:
// - i: the ugly floating coma number.
// Returns:
// - o: the "nice looking" fixed coma float.
func (d *DbParameter) getNiceFloat(i float64) (o float64) {
min := float64(float64(1) / float64(scaler))
if diff := math.Abs(i - min); diff < min {
o = float64(0)
} else {
o = float64(math.Round(i*scaler) / scaler)
}
return
}