package triggerManager import ( "context" "encoding/json" "fmt" "math" "net/http" "strings" "time" httptransport "github.com/go-openapi/runtime/client" "github.com/go-openapi/runtime/middleware" "github.com/go-openapi/strfmt" "github.com/prometheus/client_golang/prometheus" "github.com/remeh/sizedwaitgroup" "gitlab.com/cyclops-utilities/datamodels" eventsClient "github.com/Cyclops-Labs/cyclops-4-hpc.git/services/eventsengine/client" eventsUsage "github.com/Cyclops-Labs/cyclops-4-hpc.git/services/eventsengine/client/usage_management" eeModels "github.com/Cyclops-Labs/cyclops-4-hpc.git/services/eventsengine/models" "github.com/Cyclops-Labs/cyclops-4-hpc.git/services/udr/models" "github.com/Cyclops-Labs/cyclops-4-hpc.git/services/udr/restapi/operations/trigger_management" "github.com/Cyclops-Labs/cyclops-4-hpc.git/services/udr/server/dbManager" "github.com/Cyclops-Labs/cyclops-4-hpc.git/services/udr/server/statusManager" l "gitlab.com/cyclops-utilities/logging" ) // TriggerManager is the struct defined to group and contain all the methods // that interact with the trigger subsystem. // Parameters: // - db: a DbParameter reference to be able to use the DBManager methods. // - monit: a StatusManager reference to be able to use the status subsystem methods. // - eventsConfig: a EventsEngine client config reference to interact with EventsEngine type TriggerManager struct { pipe chan interface{} db *dbManager.DbParameter monit *statusManager.StatusManager eventsConfig eventsClient.Config } type CompactionKey struct { Account string ID string Name string Metadata string Metric string Unit string } const ( scaler = 1e7 ) // New is the function to create the struct TriggerManager. // Parameters: // - DbParameter: reference pointing to the DbParameter that allows the interaction // with the DBManager methods. // - StatusParameter: reference poining to the StatusManager that allows the // interaction with the TriggerManager methods. // - eventsClient.Config: a reference to the config needed to start the client // interface to be able to interact with the EventsEngine service. // Returns: // - TriggerManager: struct to interact with triggerManager subsystem functionalities. func New(db *dbManager.DbParameter, monit *statusManager.StatusManager, c eventsClient.Config, ch chan interface{}) *TriggerManager { l.Trace.Printf("[TriggerManager] Generating new TriggerManager.\n") monit.InitEndpoint("trigger") return &TriggerManager{ pipe: ch, db: db, monit: monit, eventsConfig: c, } } func (m *TriggerManager) getClient(param *http.Request) *eventsClient.EventEngineManagementAPI { config := m.eventsConfig if len(param.Header.Get("Authorization")) > 0 { token := strings.Fields(param.Header.Get("Authorization"))[1] config.AuthInfo = httptransport.BearerToken(token) } r := eventsClient.New(config) return r } func (m *TriggerManager) getNiceFloat(i float64) (o float64) { return float64(math.Round(i*scaler) / scaler) } // ExecCompactation (Swagger func) is the function behind the (GET) endpoint // /trigger/compact // Its job is to generate a compactation of the information in the database // by account, metrics and elements. // It also extracts the usage from the EventsEngine and consolidates it with // the UDRs already present in the system. func (m *TriggerManager) ExecCompactation(ctx context.Context, params trigger_management.ExecCompactationParams) middleware.Responder { l.Trace.Printf("[TriggerManager] Compact endpoint invoked.\n") callTime := time.Now() m.monit.APIHit("trigger", callTime) var from, to strfmt.DateTime var accumErr []string var udrCount int64 var interval float64 queue := make(chan string, 1) now := time.Now() records := make(map[string][]*models.Usage) if params.From != nil && params.To != nil { l.Trace.Printf("[TriggerManager] Preriod for compactation provided by params: [ %v ] to [ %v ].\n", params.From, params.To) from = *params.From to = *params.To } else { if params.FastMode != nil && *params.FastMode { switch now.Minute() { case 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15: f := time.Date(now.Year(), now.Month(), now.Day(), now.Hour(), 45, 0, 0, time.UTC) to = strfmt.DateTime(time.Date(now.Year(), now.Month(), now.Day(), now.Hour(), 0, 0, 0, time.UTC)) from = strfmt.DateTime(f.Add(time.Minute * -60)) l.Trace.Printf("[TriggerManager] Preriod for compactation: HH(-1):45-HH:00 in day(-1?).\n") case 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30: from = strfmt.DateTime(time.Date(now.Year(), now.Month(), now.Day(), now.Hour(), 0, 0, 0, time.UTC)) to = strfmt.DateTime(time.Date(now.Year(), now.Month(), now.Day(), now.Hour(), 15, 0, 0, time.UTC)) l.Trace.Printf("[TriggerManager] Preriod for compactation: HH:00-HH:15.\n") case 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45: from = strfmt.DateTime(time.Date(now.Year(), now.Month(), now.Day(), now.Hour(), 15, 0, 0, time.UTC)) to = strfmt.DateTime(time.Date(now.Year(), now.Month(), now.Day(), now.Hour(), 30, 0, 0, time.UTC)) l.Trace.Printf("[TriggerManager] Preriod for compactation: HH:15-HH:30.\n") case 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 0: from = strfmt.DateTime(time.Date(now.Year(), now.Month(), now.Day(), now.Hour(), 30, 0, 0, time.UTC)) to = strfmt.DateTime(time.Date(now.Year(), now.Month(), now.Day(), now.Hour(), 45, 0, 0, time.UTC)) l.Trace.Printf("[TriggerManager] Preriod for compactation: HH:30-HH:45.\n") } } else { switch now.Hour() { case 0, 1, 2, 3, 4, 5, 6, 7: from = strfmt.DateTime(time.Date(now.Year(), now.Month(), now.Day()-1, 16, 0, 0, 0, time.UTC)) to = strfmt.DateTime(time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.UTC)) l.Trace.Printf("[TriggerManager] Preriod for compactation: 16:00-24:00 in day -1.\n") case 8, 9, 10, 11, 12, 13, 14, 15: from = strfmt.DateTime(time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.UTC)) to = strfmt.DateTime(time.Date(now.Year(), now.Month(), now.Day(), 8, 0, 0, 0, time.UTC)) l.Trace.Printf("[TriggerManager] Preriod for compactation: 00:00-08:00.\n") case 16, 17, 18, 19, 20, 21, 22, 23: from = strfmt.DateTime(time.Date(now.Year(), now.Month(), now.Day(), 8, 0, 0, 0, time.UTC)) to = strfmt.DateTime(time.Date(now.Year(), now.Month(), now.Day(), 16, 0, 0, 0, time.UTC)) l.Trace.Printf("[TriggerManager] Preriod for compactation: 08:00-16:00.\n") } } } interval = float64(((time.Time)(to)).Sub((time.Time)(from)).Seconds()) // First we clean all the records so we always have a clean UDR generation templateRecord := models.UDRRecord{ TimeTo: to, TimeFrom: from, } l.Info.Printf("[TriggerManager] Clean up of previous UDRRecords started for period [ %v ] - [ %v ].\n", from, to) if e := m.db.Db.Where(&templateRecord).Delete(&models.UDRRecord{}).Error; e != nil { l.Warning.Printf("[TriggerManager] The clean up of previous UDRecords failed. Error: %v\n", e) } templateReport := models.UReport{ TimeFrom: from, TimeTo: to, } l.Info.Printf("[TriggerManager] Clean up of previous UReports started for period [ %v ] - [ %v ].\n", from, to) if e := m.db.Db.Where(&templateReport).Delete(&models.UReport{}).Error; e != nil { l.Warning.Printf("[TriggerManager] The clean up of previous UReports failed. Error: %v\n", e) } // Handling all the error in the same place go func() { for t := range queue { accumErr = append(accumErr, t) } }() // First we get the metric and accounts in the system. metrics, _ := m.db.GetMetrics() accounts := m.db.GetAccounts() // Then, we get all the records by metrics for _, metric := range metrics { records[metric.Metric] = m.db.GetRecords(metric.Metric, from, to) } swg := sizedwaitgroup.New(8) // Goroutines start swg.Add() go func() { defer swg.Done() // And finally, we compact the records by account // Using the metadata as matching-key for i := range accounts { swg.Add() go func(acc string) { account := acc defer swg.Done() for _, record := range records { accum := make(map[CompactionKey]float64) counter := make(map[CompactionKey]float64) metas := make(map[CompactionKey]datamodels.JSONdb) // In the first part we calculate the usage by metadata // And create a map of metadatas/matching keys for id := range record { if record[id].Account == account { key := CompactionKey{ Account: record[id].Account, ID: record[id].ResourceID, Name: record[id].ResourceName, Metadata: fmt.Sprintf("%v", record[id].Metadata), Metric: record[id].ResourceType, Unit: record[id].Unit, } accum[key] += record[id].Usage metas[key] = record[id].Metadata counter[key]++ } } // In the second part we add the compacted records in the system for key := range accum { use := datamodels.JSONdb{"used": accum[key] * interval / counter[key]} unit := key.Unit + "(*period)" if v, exists := metas[key]["UDRMode"].(string); exists { switch v { case "avg": use = datamodels.JSONdb{"used": accum[key] / counter[key]} unit = key.Unit case "sum": use = datamodels.JSONdb{"used": accum[key]} unit = key.Unit default: use = datamodels.JSONdb{"used": accum[key] * interval / counter[key]} unit = key.Unit + "(*period)" } } report := models.UDRRecord{ AccountID: key.Account, UsageBreakup: use, Metadata: metas[key], TimeTo: to, TimeFrom: from, ResourceID: key.ID, ResourceName: key.Name, ResourceType: key.Metric, Unit: unit, } if e := m.db.AddRecord(report); e != nil { err := "Acc: " + account + "-" + report.ResourceID + ". Error: " + e.Error() queue <- err l.Trace.Printf("[TriggerManager] There was a problem adding the record in the system. Error: %v.\n", e) } else { l.Trace.Printf("[TriggerManager] Compacted record for account [ %v ] added to the system.\n", account) } } } r := models.UReport{ AccountID: account, TimeFrom: from, TimeTo: to, } if e := m.db.AddReport(r); e != nil { err := "Acc(r): " + account + ". Error: " + e.Error() queue <- err l.Trace.Printf("[TriggerManager] There was a problem adding the Report in the system. Error: %v.\n", e) } else { udrCount++ l.Trace.Printf("[TriggerManager] Compacted report for account [ %v ] added to the system.\n", account) } }(accounts[i]) } }() // EE import and compactation client := m.getClient(params.HTTPRequest) fu := ((time.Time)(from)).Unix() tu := ((time.Time)(to)).Unix() eeParams := eventsUsage.NewGetSystemUsageParams().WithFrom(&fu).WithTo(&tu) r, e := client.UsageManagement.GetSystemUsage(ctx, eeParams) if e != nil { err := "EE(g)-Error: " + e.Error() queue <- err l.Warning.Printf("[TriggerManager] There was a problem while importing data from EventsEngine. Error: %v", e) } else { for i := range r.Payload { swg.Add() go func(ac *eeModels.Usage) { defer swg.Done() acc := *ac accum := make(map[CompactionKey]datamodels.JSONdb) metas := make(map[CompactionKey]datamodels.JSONdb) // In the first part we calculate the usage by metadata // And create a map of metadatas/matching keys for id := range acc.Usage { key := CompactionKey{ Account: acc.AccountID, ID: acc.Usage[id].ResourceID, Name: acc.Usage[id].ResourceName, Metadata: fmt.Sprintf("%v", acc.Usage[id].MetaData), Metric: acc.Usage[id].ResourceType, Unit: acc.Usage[id].Unit, } accum[key] = make(datamodels.JSONdb) metas[key] = acc.Usage[id].MetaData for i := range acc.Usage[id].UsageBreakup { if _, exists := accum[key][i]; !exists { accum[key][i] = int64(0) } addition, _ := acc.Usage[id].UsageBreakup[i].(json.Number).Int64() accum[key][i] = accum[key][i].(int64) + addition } } // In the second part we add the compacted records in the system for key := range accum { report := models.UDRRecord{ AccountID: key.Account, UsageBreakup: accum[key], Metadata: metas[key], TimeTo: to, TimeFrom: from, ResourceID: key.ID, ResourceName: key.Name, ResourceType: key.Metric, Unit: key.Unit, } if e := m.db.AddRecord(report); e != nil { err := "Acc: " + report.AccountID + "-" + report.ResourceID + ". Error: " + e.Error() queue <- err l.Trace.Printf("[TriggerManager] There was a problem adding the record in the system. Error: %v.\n", e) } else { l.Trace.Printf("[TriggerManager] Compacted record for account [ %v ] added to the system.\n", report.AccountID) } } r := models.UReport{ AccountID: acc.AccountID, TimeFrom: from, TimeTo: to, } if e := m.db.AddReport(r); e != nil { err := "Acc(r): " + r.AccountID + ". Error: " + e.Error() queue <- err l.Trace.Printf("[TriggerManager] There was a problem adding the Report in the system. Error: %v.\n", e) } else { udrCount++ l.Trace.Printf("[TriggerManager] Compacted report for account [ %v ] added to the system.\n", r.AccountID) } }(r.Payload[i]) } } swg.Wait() // Kafka report accs := m.db.GetUDRAccounts() for _, acc := range accs { usages, e := m.db.GetUsage(acc, "", from, to) if e == nil { l.Trace.Printf("[TriggerManager] There's [ %v ] compacted usages associated with the account [ %v ].\n", len(usages), acc) for _, usage := range usages { m.pipe <- *usage } } else { err := "Usage(g)-Acc: " + acc + ". Error: " + e.Error() queue <- err l.Warning.Printf("[TriggerManager] There was a problem while retrieving the compacted usages to be sent through Kafka. Error: %v", e) } } close(queue) m.db.Metrics["time"].With(prometheus.Labels{"type": "UDRs generation time"}).Set(float64(time.Now().UnixNano()-now.UnixNano()) / float64(time.Millisecond)) m.db.Metrics["count"].With(prometheus.Labels{"type": "UDRs generated"}).Set(float64(udrCount)) m.db.Metrics["count"].With(prometheus.Labels{"type": "Errors during UDR generation"}).Set(float64(len(accumErr))) if len(accumErr) > 0 { s := "This are the accumulative errors: " + strings.Join(accumErr, ",\n") errorReturn := models.ErrorResponse{ ErrorString: &s, } m.db.Metrics["api"].With(prometheus.Labels{"code": "500", "method": "GET", "route": "/trigger/compact"}).Inc() m.monit.APIHitDone("trigger", callTime) return trigger_management.NewExecCompactationInternalServerError().WithPayload(&errorReturn) } m.db.Metrics["api"].With(prometheus.Labels{"code": "200", "method": "GET", "route": "/trigger/compact"}).Inc() m.monit.APIHitDone("trigger", callTime) return trigger_management.NewExecCompactationOK() }