package dbManager import ( "errors" "fmt" "strings" "time" "github.com/go-openapi/strfmt" "github.com/prometheus/client_golang/prometheus" "github.com/Cyclops-Labs/cyclops-4-hpc.git/services/udr/models" "github.com/Cyclops-Labs/cyclops-4-hpc.git/services/udr/server/cacheManager" l "gitlab.com/cyclops-utilities/logging" "gorm.io/driver/postgres" "gorm.io/gorm" ) const ( statusDuplicated = iota statusFail statusMissing statusOK ) // 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 } // 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) panic(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.NamingStrategy.TableName("Usage") + "', 'timedate');") return &dp } // AddMetric tries to insert a given new metric in the system, checking and // reporting if it's already exists in the system. // Parameters: // - metric: string with the new metric to add to the system. // Returns: // - error in case of duplication or failure in the task. func (d *DbParameter) AddMetric(metric string) error { l.Trace.Printf("[DB] Attempting to add a new metric [ %v ] to the system.\n", metric) m := models.Metric{Metric: metric} if e := d.Db.Where(&m).First(&models.Metric{}).Error; errors.Is(e, gorm.ErrRecordNotFound) { e := d.Db.Create(&m).Error if e != nil { l.Warning.Printf("[DB] There were an problem adding the metric [ %v ] to the system. Error: %v\n", metric, e) } else { d.Metrics["count"].With(prometheus.Labels{"type": "Usage metrics added"}).Inc() l.Debug.Printf("[DB] Metric [ %v ] added to the system.\n", metric) } return e } l.Debug.Printf("[DB] The metric [ %v ] already exists in the system.\n", metric) return nil } // AddRecord job is to add a compacted record for a 8h interval in the system. // Parameters: // - report: a UDRRecord containing the Usage received and compacted. // Returns: // - error in case of duplication or failure in the task. func (d *DbParameter) AddRecord(report models.UDRRecord) (e error) { l.Trace.Printf("[DB] Attempting to add a new compacted UDR Record in the system.\n") var reportUpdate models.UDRRecord reportCheck := models.UDRRecord{ AccountID: report.AccountID, Metadata: report.Metadata, TimeTo: report.TimeTo, TimeFrom: report.TimeFrom, ResourceID: report.ResourceID, ResourceName: report.ResourceName, ResourceType: report.ResourceType, } e = d.Db.Where(&reportCheck).First(&reportUpdate).Error if errors.Is(e, gorm.ErrRecordNotFound) { e = d.Db.Create(&report).Error if e == nil { d.Metrics["count"].With(prometheus.Labels{"type": "UDR Records added"}).Inc() l.Trace.Printf("[DB] New UDR Record added in the system.\n") return } l.Trace.Printf("[DB] Something went wrong when adding a new UDR Record to the system. Error: %v\n", e) return } l.Trace.Printf("[DB] The UDR Record [ %v ] is already in the system, attempt to update it...\n", reportUpdate) e = d.Db.Model(&reportUpdate).Updates(&report).Error if e == nil { d.Metrics["count"].With(prometheus.Labels{"type": "UDR Records updated"}).Inc() l.Trace.Printf("[DB] UDR Record updated in the system.\n") return } l.Trace.Printf("[DB] Something went wrong when updating the existing UDR Record to the system. Error: %v\n", e) return } // AddReport job is to add a new report of compacted usage to the system. // Parameters: // - report: a Report containing the data to be added to the system. // Returns: // - error in case of duplication or failure in the task. func (d *DbParameter) AddReport(report models.UReport) (e error) { l.Trace.Printf("[DB] Attempting to add a new usage report to the system.\n") if e = d.Db.Where(&report).First(&models.UReport{}).Error; errors.Is(e, gorm.ErrRecordNotFound) { e = d.Db.Create(&report).Error if e == nil { d.Metrics["count"].With(prometheus.Labels{"type": "UDR Reports added"}).Inc() l.Trace.Printf("[DB] New usage report added in the system.\n") } else { l.Trace.Printf("[DB] Something went wrong when adding a new usage report to the system. Error: %v\n", e) } return e } return nil } // GetAccounts job is to retrieve a list of the accounts in the system. // Returns: // - Slice of strings containing the accounts in the system with usage data. func (d *DbParameter) GetAccounts() []string { l.Trace.Printf("[DB] Attempting to retrieve the account list in the system.\n") type scann struct { Account string } var accounts []string var acc []scann if e := d.Db.Debug().Raw("SELECT DISTINCT " + d.Db.NamingStrategy.ColumnName("", "Account") + " FROM " + d.Db.NamingStrategy.TableName("Usage")).Scan(&acc).Error; e == nil { for i := range acc { accounts = append(accounts, acc[i].Account) } l.Trace.Printf("[DB] [ %v ] accounts were retrieved from the system.\n", len(accounts)) } else { l.Warning.Printf("[DB] Something went wrong while retrieving the accounts in the system. Error: %v\n", e) } return accounts } // GetUDRAccounts job is to retrieve a list of the accounts in the system that // have a UDR Report ready to be sent. // Returns: // - Slice of strings containing the accounts in the system with UDR Reports. func (d *DbParameter) GetUDRAccounts() []string { l.Trace.Printf("[DB] Attempting to retrieve the account list in the system.\n") type scann struct { AccountID string } var accounts []string var acc []scann if e := d.Db.Debug().Raw("SELECT DISTINCT " + d.Db.NamingStrategy.ColumnName("", "AccountID") + " FROM " + d.Db.NamingStrategy.TableName("UDRRecord")).Scan(&acc).Error; e == nil { for i := range acc { accounts = append(accounts, acc[i].AccountID) } l.Trace.Printf("[DB] [ %v ] accounts were retrieved from the system.\n", len(accounts)) } else { l.Warning.Printf("[DB] Something went wrong while retrieving the accounts in the system. Error: %v\n", e) } return accounts } // GetInterval job is to provide a proper query string to use in GORM to indicate // a range of time. // Parameters: // - field: a string with the db field to use for the time-window. // - 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: the ready for query string with the data provided. func (d *DbParameter) GetInterval(field string, from strfmt.DateTime, to strfmt.DateTime) (s string) { l.Trace.Printf("[DB] Attempting to get an interval for a db query.\n") s = fmt.Sprintf("%v >= '%v' AND %v <= '%v'", field, from, field, to) return } // GetMetrics job is to retrieve the list of metrics registered in the system. // Returns: // - a slice of references with the metrics in the system. // - error in case of duplication or failure in the task. func (d *DbParameter) GetMetrics() ([]*models.Metric, error) { l.Trace.Printf("[DB] Attempting to retrieve the metrics in the system.\n") var m []*models.Metric var e error if e = d.Db.Debug().Find(&m).Error; e == nil { l.Debug.Printf("[DB] [%v] metrics retrieved from the system.\n", len(m)) } else { l.Warning.Printf("[DB] Something went wrong while retrieving metrics from the system. Error: %v\n", e) } return m, e } // GetRecords job is to retrieve the non-compacted usage records from the system // in the requested time-window with the posibility of filter by metric. // Parameters: // - 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) GetRecords(metric string, from strfmt.DateTime, to strfmt.DateTime) []*models.Usage { l.Trace.Printf("[DB] Attempting to retrieve the non-compacted usage records.\n") var u []*models.Usage interval := d.GetInterval("timedate", from, to) if e := d.Db.Where(interval).Where(&models.Usage{ResourceType: metric}).Find(&u).Error; e != nil { l.Warning.Printf("[DB] Something went wrong while retrieving the non-compacted usage records from the system. Error: %v\n", e) } else { l.Debug.Printf("[DB] [ %v ] non-compacted usage records retrieved from the system.\n", len(u)) } return u } // 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, metric string, from, to strfmt.DateTime) []*models.UDRReport { l.Trace.Printf("[DB] Attempting to retrieve the compacted usage records.\n") var r []*models.UDRReport var u models.UDRRecord 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.UDRRecord{}).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.UReport, error) { l.Trace.Printf("[DB] Attempting to retrieve the usage report from the system.\n") var r []*models.UReport var r0 models.UReport 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.UReport, error) { l.Trace.Printf("[DB] Attempting to retrieve the usage report from the system.\n") var total, r []*models.UReport var r0 models.UReport 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 } // getWindow job is to select the timeframe for the usage retrievals according // to the data providad from= '%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 }