From 132a2b3bf9b24b5cc6ea3be77acabd4af37174ad Mon Sep 17 00:00:00 2001 From: gezimbll Date: Fri, 1 Nov 2024 11:40:13 +0100 Subject: [PATCH] rankings,trends: added implementation,services and tests --- apis/rankings.go | 28 + apis/replicator.go | 12 +- apis/trends.go | 31 +- apis/trends_it_test.go | 13 + config/config_defaults.go | 2 + config/config_json_test.go | 10 + config/config_test.go | 7 +- .../mysql/create_tariffplan_tables.sql | 23 + .../postgres/create_tariffplan_tables.sql | 23 + data/tariffplans/tuttrends/Stats.csv | 5 + data/tariffplans/tuttrends/Trends.csv | 8 + dispatchers/caches_it_test.go | 3 + engine/datadbmock.go | 19 +- engine/datamanager.go | 419 ++++++++++---- engine/librankings.go | 283 ++++++++++ engine/libtest.go | 5 +- engine/libtrends.go | 149 ++++- engine/libtrends_test.go | 18 +- engine/model_helpers.go | 156 ++--- engine/models.go | 5 +- engine/rankings.go | 534 ++++++++++++++++-- engine/rankings_test.go | 13 +- engine/route_cost_sort_test.go | 60 +- engine/storage_csv.go | 6 +- engine/storage_interface.go | 9 +- engine/storage_internal_datadb.go | 26 +- engine/storage_mongo_datadb.go | 45 +- engine/storage_redis.go | 31 +- engine/trends.go | 484 ++++++++++++++-- general_tests/all_cfg_rld_it_test.go | 4 +- general_tests/all_sections_cfg_rld_it_test.go | 4 +- general_tests/tut_smgeneric_it_test.go | 2 + services/cgr-engine.go | 2 + services/rankings.go | 28 +- services/rankings_test.go | 14 - services/trends.go | 61 +- services/trends_test.go | 13 - tpes/tpe_trends.go | 2 +- utils/apitpdata.go | 43 +- utils/consts.go | 25 +- 40 files changed, 2223 insertions(+), 402 deletions(-) create mode 100644 data/tariffplans/tuttrends/Stats.csv create mode 100644 data/tariffplans/tuttrends/Trends.csv create mode 100644 engine/librankings.go diff --git a/apis/rankings.go b/apis/rankings.go index 7530d2cc1..1dad91b38 100644 --- a/apis/rankings.go +++ b/apis/rankings.go @@ -174,3 +174,31 @@ func (adms *AdminSv1) RemoveRankingProfile(ctx *context.Context, args *utils.Ten *reply = utils.OK return nil } + +// NewRankingSv1 initializes RankingSV1 +func NewRankingSv1(rnks *engine.RankingS) *RankingSv1 { + return &RankingSv1{ + rnkS: rnks, + } +} + +type RankingSv1 struct { + rnkS *engine.RankingS +} + +// GetRankingSummary returns summary of the last updated ranking +func (rnks *RankingSv1) GetRankingSummary(ctx *context.Context, arg *utils.TenantIDWithAPIOpts, reply *engine.RankingSummary) error { + return rnks.rnkS.V1GetRankingSummary(ctx, arg, reply) +} + +func (rnkS *RankingSv1) GetRanking(ctx *context.Context, arg *utils.TenantIDWithAPIOpts, reply *engine.Ranking) (err error) { + return rnkS.rnkS.V1GetRanking(ctx, arg, reply) +} + +func (rnkS *RankingSv1) GetSchedule(ctx *context.Context, args *utils.ArgScheduledRankings, schedRankings *[]utils.ScheduledRanking) (err error) { + return rnkS.rnkS.V1GetSchedule(ctx, args, schedRankings) +} + +func (rnkS *RankingSv1) ScheduleQueries(ctx *context.Context, args *utils.ArgScheduleRankingQueries, scheduled *int) (err error) { + return rnkS.rnkS.V1ScheduleQueries(ctx, args, scheduled) +} diff --git a/apis/replicator.go b/apis/replicator.go index 1d06feea8..165f33566 100644 --- a/apis/replicator.go +++ b/apis/replicator.go @@ -111,11 +111,15 @@ func (rplSv1 *ReplicatorSv1) GetStatQueueProfile(ctx *context.Context, tntID *ut // GetTrend is the remote method coresponding to the dataDb driver method func (rplSv1 *ReplicatorSv1) GetTrend(ctx *context.Context, tntID *utils.TenantIDWithAPIOpts, reply *engine.Trend) error { engine.UpdateReplicationFilters(utils.TrendPrefix, tntID.TenantID.TenantID(), utils.IfaceAsString(tntID.APIOpts[utils.RemoteHostOpt])) - rcv, err := rplSv1.dm.DataDB().GetTrendDrv(tntID.Tenant, tntID.ID) + rcv, err := rplSv1.dm.DataDB().GetTrendDrv(ctx, tntID.Tenant, tntID.ID) if err != nil { return err } - *reply = *rcv + reply.Tenant = rcv.Tenant + reply.ID = rcv.ID + reply.RunTimes = rcv.RunTimes + reply.CompressedMetrics = rcv.CompressedMetrics + reply.Metrics = rcv.Metrics return nil } @@ -295,7 +299,7 @@ func (rplSv1 *ReplicatorSv1) SetTrendProfile(ctx *context.Context, trp *engine.T // SetTrend is the replication method coresponding to the dataDb driver method func (rplSv1 *ReplicatorSv1) SetTrend(ctx *context.Context, tr *engine.TrendWithAPIOpts, reply *string) (err error) { - if err = rplSv1.dm.DataDB().SetTrendDrv(tr.Trend); err != nil { + if err = rplSv1.dm.DataDB().SetTrendDrv(ctx, tr.Trend); err != nil { return } // delay if needed before cache call @@ -527,7 +531,7 @@ func (rplSv1 *ReplicatorSv1) SetIndexes(ctx *context.Context, args *utils.SetInd // RemoveTrend is the replication method coresponding to the dataDb driver method func (rplSv1 *ReplicatorSv1) RemoveTrend(ctx *context.Context, args *utils.TenantIDWithAPIOpts, reply *string) (err error) { - if err = rplSv1.dm.DataDB().RemoveTrendDrv(args.Tenant, args.ID); err != nil { + if err = rplSv1.dm.DataDB().RemoveTrendDrv(ctx, args.Tenant, args.ID); err != nil { return } // delay if needed before cache call diff --git a/apis/trends.go b/apis/trends.go index 6e270e6db..a50699001 100644 --- a/apis/trends.go +++ b/apis/trends.go @@ -33,7 +33,7 @@ func (adms *AdminSv1) GetTrendProfile(ctx *context.Context, arg *utils.TenantIDW if tnt == utils.EmptyString { tnt = adms.cfg.GeneralCfg().DefaultTenant } - sCfg, err := adms.dm.GetTrendProfile(ctx, tnt, arg.ID) + sCfg, err := adms.dm.GetTrendProfile(ctx, tnt, arg.ID, true, true, utils.NonTransactional) if err != nil { return utils.APIErrorHandler(err) } @@ -82,7 +82,7 @@ func (admS *AdminSv1) GetTrendProfiles(ctx *context.Context, args *utils.ArgsIte *sqPrfs = make([]*engine.TrendProfile, 0, len(sqPrfIDs)) for _, sqPrfID := range sqPrfIDs { var sqPrf *engine.TrendProfile - sqPrf, err = admS.dm.GetTrendProfile(ctx, tnt, sqPrfID) + sqPrf, err = admS.dm.GetTrendProfile(ctx, tnt, sqPrfID, true, true, utils.NonTransactional) if err != nil { return utils.APIErrorHandler(err) } @@ -140,3 +140,30 @@ func (adms *AdminSv1) RemoveTrendProfile(ctx *context.Context, args *utils.Tenan *reply = utils.OK return nil } + +// NewTrendSv1 initializes TrendSV1 +func NewTrendSv1(trS *engine.TrendS) *TrendSv1 { + return &TrendSv1{trS: trS} +} + +// TrendSv1 exports RPC from RLs +type TrendSv1 struct { + ping + trS *engine.TrendS +} + +func (trs *TrendSv1) ScheduleQueries(ctx *context.Context, args *utils.ArgScheduleTrendQueries, scheduled *int) error { + return trs.trS.V1ScheduleQueries(ctx, args, scheduled) +} + +func (trs *TrendSv1) GetTrend(ctx *context.Context, args *utils.ArgGetTrend, trend *engine.Trend) error { + return trs.trS.V1GetTrend(ctx, args, trend) +} + +func (trs *TrendSv1) GetScheduledTrends(ctx *context.Context, args *utils.ArgScheduledTrends, schedTrends *[]utils.ScheduledTrend) error { + return trs.trS.V1GetScheduledTrends(ctx, args, schedTrends) +} + +func (trs *TrendSv1) GetTrendSummary(ctx *context.Context, arg utils.TenantIDWithAPIOpts, reply *engine.TrendSummary) error { + return trs.trS.V1GetTrendSummary(ctx, arg, reply) +} diff --git a/apis/trends_it_test.go b/apis/trends_it_test.go index 0bf4a7afd..67f417ffb 100644 --- a/apis/trends_it_test.go +++ b/apis/trends_it_test.go @@ -218,6 +218,12 @@ func testTrendsSetTrendProfiles(t *testing.T) { t.Error(err) } } + if err := trRPC.Call(context.Background(), utils.CacheSv1Clear, + &utils.AttrCacheIDsWithAPIOpts{ + CacheIDs: nil, + }, &reply); err != nil { + t.Error(err) + } } func testTrendsGetTrendProfileAfterSet(t *testing.T) { @@ -334,6 +340,13 @@ func testTrendsRemoveTrendProfile(t *testing.T) { } func testTrendsGetTrendProfileAfterRemove(t *testing.T) { + var reply string + if err := trRPC.Call(context.Background(), utils.CacheSv1Clear, + &utils.AttrCacheIDsWithAPIOpts{ + CacheIDs: nil, + }, &reply); err != nil { + t.Error(err) + } var replyTrendProfile engine.TrendProfile if err := trRPC.Call(context.Background(), utils.AdminSv1GetTrendProfile, &utils.TenantIDWithAPIOpts{ diff --git a/config/config_defaults.go b/config/config_defaults.go index 4773b908a..f7ba27a2b 100644 --- a/config/config_defaults.go +++ b/config/config_defaults.go @@ -142,6 +142,7 @@ const CGRATES_CFG_JSON = ` "*stat_filter_indexes" : {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate": false}, "*threshold_filter_indexes" : {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate": false}, "*ranking_profiles": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false}, + "*rankings": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false}, "*trend_profiles": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false}, "*trends": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false}, "*route_filter_indexes" : {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate": false}, @@ -280,6 +281,7 @@ const CGRATES_CFG_JSON = ` "*route_filter_indexes" : {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate": false}, // control route filter indexes caching "*attribute_filter_indexes" : {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate": false}, // control attribute filter indexes caching "*ranking_profiles": {"limit": -1, "ttl": "", "static_ttl": false, "precache": false, "remote":false, "replicate": false}, // control ranking profile caching + "*rankings": {"limit": -1, "ttl": "", "static_ttl": false, "precache": false, "remote":false, "replicate": false}, // control rankings caching "*charger_filter_indexes" : {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate": false}, // control charger filter indexes caching "*dispatcher_filter_indexes" : {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate": false}, // control dispatcher filter indexes caching "*rate_profile_filter_indexes" : {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate": false}, // control rate profile filter indexes caching diff --git a/config/config_json_test.go b/config/config_json_test.go index 6bebdace4..17e785a38 100644 --- a/config/config_json_test.go +++ b/config/config_json_test.go @@ -212,6 +212,9 @@ func TestCacheJsonCfg(t *testing.T) { utils.CacheRankingProfiles: {Limit: utils.IntPointer(-1), Ttl: utils.StringPointer(""), Static_ttl: utils.BoolPointer(false), Precache: utils.BoolPointer(false), Remote: utils.BoolPointer(false), Replicate: utils.BoolPointer(false)}, + utils.CacheRankings: {Limit: utils.IntPointer(-1), + Ttl: utils.StringPointer(""), Static_ttl: utils.BoolPointer(false), Precache: utils.BoolPointer(false), + Remote: utils.BoolPointer(false), Replicate: utils.BoolPointer(false)}, utils.CacheRPCConnections: {Limit: utils.IntPointer(-1), Ttl: utils.StringPointer(""), Static_ttl: utils.BoolPointer(false), Remote: utils.BoolPointer(false), Replicate: utils.BoolPointer(false)}, @@ -524,6 +527,13 @@ func TestDfDataDbJsonCfg(t *testing.T) { Replicate: utils.BoolPointer(false), Remote: utils.BoolPointer(false), }, + utils.CacheRankings: { + Ttl: utils.StringPointer(utils.EmptyString), + Static_ttl: utils.BoolPointer(false), + Limit: utils.IntPointer(-1), + Replicate: utils.BoolPointer(false), + Remote: utils.BoolPointer(false), + }, utils.CacheTrendProfiles: { Ttl: utils.StringPointer(utils.EmptyString), Static_ttl: utils.BoolPointer(false), diff --git a/config/config_test.go b/config/config_test.go index beb9e0c6d..20c44ccbe 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -461,6 +461,7 @@ func TestCgrCfgJSONDefaultsCacheCFG(t *testing.T) { utils.CacheDispatcherFilterIndexes: {Limit: -1}, utils.CacheRateProfilesFilterIndexes: {Limit: -1}, utils.CacheRankingProfiles: {Limit: -1}, + utils.CacheRankings: {Limit: -1}, utils.CacheRateFilterIndexes: {Limit: -1}, utils.CacheActionProfilesFilterIndexes: {Limit: -1}, utils.CacheAccountsFilterIndexes: {Limit: -1}, @@ -4800,7 +4801,7 @@ func TestV1GetConfigAsJSONGeneral(t *testing.T) { func TestV1GetConfigAsJSONDataDB(t *testing.T) { var reply string - expected := `{"data_db":{"db_host":"127.0.0.1","db_name":"10","db_password":"","db_port":6379,"db_type":"*redis","db_user":"cgrates","items":{"*account_filter_indexes":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*accounts":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*action_profile_filter_indexes":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*action_profiles":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*actions":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*attribute_filter_indexes":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*attribute_profiles":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*charger_filter_indexes":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*charger_profiles":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*dispatcher_filter_indexes":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*dispatcher_hosts":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*dispatcher_profiles":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*filters":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*load_ids":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*ranking_profiles":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*rate_filter_indexes":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*rate_profile_filter_indexes":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*rate_profiles":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*resource_filter_indexes":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*resource_profiles":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*resources":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*reverse_filter_indexes":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*route_filter_indexes":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*route_profiles":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*stat_filter_indexes":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*statqueue_profiles":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*statqueues":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*threshold_filter_indexes":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*threshold_profiles":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*thresholds":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*trend_profiles":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*trends":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*versions":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false}},"opts":{"mongoConnScheme":"mongodb","mongoQueryTimeout":"10s","redisCACertificate":"","redisClientCertificate":"","redisClientKey":"","redisCluster":false,"redisClusterOndownDelay":"0s","redisClusterSync":"5s","redisConnectAttempts":20,"redisConnectTimeout":"0s","redisMaxConns":10,"redisPoolPipelineLimit":0,"redisPoolPipelineWindow":"150µs","redisReadTimeout":"0s","redisSentinel":"","redisTLS":false,"redisWriteTimeout":"0s"},"remote_conn_id":"","remote_conns":[],"replication_cache":"","replication_conns":[],"replication_filtered":false}}` + expected := `{"data_db":{"db_host":"127.0.0.1","db_name":"10","db_password":"","db_port":6379,"db_type":"*redis","db_user":"cgrates","items":{"*account_filter_indexes":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*accounts":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*action_profile_filter_indexes":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*action_profiles":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*actions":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*attribute_filter_indexes":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*attribute_profiles":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*charger_filter_indexes":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*charger_profiles":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*dispatcher_filter_indexes":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*dispatcher_hosts":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*dispatcher_profiles":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*filters":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*load_ids":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*ranking_profiles":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*rankings":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*rate_filter_indexes":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*rate_profile_filter_indexes":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*rate_profiles":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*resource_filter_indexes":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*resource_profiles":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*resources":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*reverse_filter_indexes":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*route_filter_indexes":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*route_profiles":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*stat_filter_indexes":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*statqueue_profiles":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*statqueues":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*threshold_filter_indexes":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*threshold_profiles":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*thresholds":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*trend_profiles":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*trends":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*versions":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false}},"opts":{"mongoConnScheme":"mongodb","mongoQueryTimeout":"10s","redisCACertificate":"","redisClientCertificate":"","redisClientKey":"","redisCluster":false,"redisClusterOndownDelay":"0s","redisClusterSync":"5s","redisConnectAttempts":20,"redisConnectTimeout":"0s","redisMaxConns":10,"redisPoolPipelineLimit":0,"redisPoolPipelineWindow":"150µs","redisReadTimeout":"0s","redisSentinel":"","redisTLS":false,"redisWriteTimeout":"0s"},"remote_conn_id":"","remote_conns":[],"replication_cache":"","replication_conns":[],"replication_filtered":false}}` cfgCgr := NewDefaultCGRConfig() if err := cfgCgr.V1GetConfigAsJSON(context.Background(), &SectionWithAPIOpts{Sections: []string{DataDBJSON}}, &reply); err != nil { t.Error(err) @@ -4822,7 +4823,7 @@ func TestV1GetConfigAsJSONTls(t *testing.T) { func TestV1GetConfigAsJSONTCache(t *testing.T) { var reply string - expected := `{"caches":{"partitions":{"*account_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*accounts":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*action_profile_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*action_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*apiban":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"2m0s"},"*attribute_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*attribute_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*caps_events":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*cdr_ids":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"10m0s"},"*charger_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*charger_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*closed_sessions":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"10s"},"*diameter_messages":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"3h0m0s"},"*dispatcher_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*dispatcher_hosts":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*dispatcher_loads":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*dispatcher_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*dispatcher_routes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*dispatchers":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*event_charges":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"10s"},"*event_resources":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*filters":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*load_ids":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*ranking_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*rate_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*rate_profile_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*rate_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*replication_hosts":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*resource_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*resource_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*resources":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*reverse_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*route_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*route_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*rpc_connections":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*rpc_responses":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"2s"},"*sentrypeer":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":true,"ttl":"24h0m0s"},"*stat_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*statqueue_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*statqueues":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*stir":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"3h0m0s"},"*threshold_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*threshold_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*thresholds":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*trend_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*trends":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*uch":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"3h0m0s"}},"remote_conns":[],"replication_conns":[]}}` + expected := `{"caches":{"partitions":{"*account_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*accounts":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*action_profile_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*action_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*apiban":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"2m0s"},"*attribute_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*attribute_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*caps_events":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*cdr_ids":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"10m0s"},"*charger_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*charger_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*closed_sessions":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"10s"},"*diameter_messages":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"3h0m0s"},"*dispatcher_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*dispatcher_hosts":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*dispatcher_loads":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*dispatcher_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*dispatcher_routes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*dispatchers":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*event_charges":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"10s"},"*event_resources":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*filters":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*load_ids":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*ranking_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*rankings":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*rate_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*rate_profile_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*rate_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*replication_hosts":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*resource_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*resource_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*resources":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*reverse_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*route_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*route_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*rpc_connections":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*rpc_responses":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"2s"},"*sentrypeer":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":true,"ttl":"24h0m0s"},"*stat_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*statqueue_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*statqueues":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*stir":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"3h0m0s"},"*threshold_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*threshold_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*thresholds":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*trend_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*trends":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*uch":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"3h0m0s"}},"remote_conns":[],"replication_conns":[]}}` cfgCgr := NewDefaultCGRConfig() if err := cfgCgr.V1GetConfigAsJSON(context.Background(), &SectionWithAPIOpts{Sections: []string{CacheJSON}}, &reply); err != nil { t.Error(err) @@ -5283,7 +5284,7 @@ func TestV1GetConfigAsJSONAllConfig(t *testing.T) { } }` var reply string - expected := `{"accounts":{"attributes_conns":[],"enabled":false,"exists_indexed_fields":[],"indexed_selects":true,"max_iterations":1000,"max_usage":"259200000000000","nested_fields":false,"notexists_indexed_fields":[],"opts":{"*profileIDs":[],"*profileIgnoreFilters":[],"*usage":[]},"prefix_indexed_fields":[],"rates_conns":[],"suffix_indexed_fields":[],"thresholds_conns":[]},"actions":{"accounts_conns":[],"cdrs_conns":[],"dynaprepaid_actionprofile":[],"ees_conns":[],"enabled":false,"exists_indexed_fields":[],"indexed_selects":true,"nested_fields":false,"notexists_indexed_fields":[],"opts":{"*posterAttempts":[],"*profileIDs":[],"*profileIgnoreFilters":[]},"prefix_indexed_fields":[],"stats_conns":[],"suffix_indexed_fields":[],"tenants":[],"thresholds_conns":[]},"admins":{"actions_conns":[],"attributes_conns":[],"caches_conns":["*internal"],"ees_conns":[],"enabled":false},"analyzers":{"cleanup_interval":"1h0m0s","db_path":"/var/spool/cgrates/analyzers","ees_conns":[],"enabled":false,"index_type":"*scorch","opts":{"*exporterIDs":[]},"ttl":"24h0m0s"},"apiban":{"enabled":false,"keys":[]},"asterisk_agent":{"asterisk_conns":[{"address":"127.0.0.1:8088","alias":"","connect_attempts":3,"max_reconnect_interval":"0s","password":"CGRateS.org","reconnects":5,"user":"cgrates"}],"create_cdr":false,"enabled":false,"sessions_conns":["*birpc_internal"]},"attributes":{"accounts_conns":[],"enabled":false,"exists_indexed_fields":[],"indexed_selects":true,"nested_fields":false,"notexists_indexed_fields":[],"opts":{"*processRuns":[],"*profileIDs":[],"*profileIgnoreFilters":[],"*profileRuns":[]},"prefix_indexed_fields":[],"resources_conns":[],"stats_conns":[],"suffix_indexed_fields":[]},"caches":{"partitions":{"*account_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*accounts":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*action_profile_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*action_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*apiban":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"2m0s"},"*attribute_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*attribute_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*caps_events":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*cdr_ids":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"10m0s"},"*charger_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*charger_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*closed_sessions":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"10s"},"*diameter_messages":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"3h0m0s"},"*dispatcher_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*dispatcher_hosts":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*dispatcher_loads":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*dispatcher_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*dispatcher_routes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*dispatchers":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*event_charges":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"10s"},"*event_resources":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*filters":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*load_ids":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*ranking_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*rate_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*rate_profile_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*rate_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*replication_hosts":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*resource_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*resource_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*resources":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*reverse_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*route_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*route_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*rpc_connections":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*rpc_responses":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"2s"},"*sentrypeer":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":true,"ttl":"24h0m0s"},"*stat_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*statqueue_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*statqueues":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*stir":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"3h0m0s"},"*threshold_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*threshold_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*thresholds":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*trend_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*trends":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*uch":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"3h0m0s"}},"remote_conns":[],"replication_conns":[]},"cdrs":{"accounts_conns":[],"actions_conns":[],"attributes_conns":[],"chargers_conns":[],"ees_conns":[],"enabled":false,"extra_fields":[],"online_cdr_exports":null,"opts":{"*accounts":[],"*attributes":[],"*chargers":[],"*ees":[],"*rates":[],"*refund":[],"*rerate":[],"*stats":[],"*store":[],"*thresholds":[]},"rates_conns":[],"session_cost_retries":5,"stats_conns":[],"thresholds_conns":[]},"chargers":{"attributes_conns":[],"enabled":false,"exists_indexed_fields":[],"indexed_selects":true,"nested_fields":false,"notexists_indexed_fields":[],"prefix_indexed_fields":[],"suffix_indexed_fields":[]},"config_db":{"db_host":"","db_name":"","db_password":"","db_port":0,"db_type":"*internal","db_user":"","opts":{"mongoConnScheme":"mongodb","mongoQueryTimeout":"10s","redisCACertificate":"","redisClientCertificate":"","redisClientKey":"","redisCluster":false,"redisClusterOndownDelay":"0s","redisClusterSync":"5s","redisConnectAttempts":20,"redisConnectTimeout":"0s","redisMaxConns":10,"redisReadTimeout":"0s","redisSentinel":"","redisTLS":false,"redisWriteTimeout":"0s"}},"configs":{"enabled":false,"root_dir":"/var/spool/cgrates/configs","url":"/configs/"},"cores":{"caps":0,"caps_stats_interval":"0","caps_strategy":"*busy","ees_conns":[],"shutdown_timeout":"1s"},"data_db":{"db_host":"127.0.0.1","db_name":"10","db_password":"","db_port":6379,"db_type":"*redis","db_user":"cgrates","items":{"*account_filter_indexes":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*accounts":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*action_profile_filter_indexes":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*action_profiles":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*actions":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*attribute_filter_indexes":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*attribute_profiles":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*charger_filter_indexes":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*charger_profiles":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*dispatcher_filter_indexes":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*dispatcher_hosts":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*dispatcher_profiles":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*filters":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*load_ids":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*ranking_profiles":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*rate_filter_indexes":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*rate_profile_filter_indexes":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*rate_profiles":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*resource_filter_indexes":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*resource_profiles":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*resources":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*reverse_filter_indexes":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*route_filter_indexes":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*route_profiles":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*stat_filter_indexes":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*statqueue_profiles":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*statqueues":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*threshold_filter_indexes":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*threshold_profiles":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*thresholds":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*trend_profiles":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*trends":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*versions":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false}},"opts":{"mongoConnScheme":"mongodb","mongoQueryTimeout":"10s","redisCACertificate":"","redisClientCertificate":"","redisClientKey":"","redisCluster":false,"redisClusterOndownDelay":"0s","redisClusterSync":"5s","redisConnectAttempts":20,"redisConnectTimeout":"0s","redisMaxConns":10,"redisPoolPipelineLimit":0,"redisPoolPipelineWindow":"150µs","redisReadTimeout":"0s","redisSentinel":"","redisTLS":false,"redisWriteTimeout":"0s"},"remote_conn_id":"","remote_conns":[],"replication_cache":"","replication_conns":[],"replication_filtered":false},"diameter_agent":{"asr_template":"","concurrent_requests":-1,"dictionaries_path":"/usr/share/cgrates/diameter/dict/","enabled":false,"forced_disconnect":"*none","listen":"127.0.0.1:3868","listen_net":"tcp","origin_host":"CGR-DA","origin_realm":"cgrates.org","product_name":"CGRateS","rar_template":"","request_processors":[],"sessions_conns":["*birpc_internal"],"synced_conn_requests":false,"vendor_id":0},"dispatchers":{"attributes_conns":[],"enabled":false,"exists_indexed_fields":[],"indexed_selects":true,"nested_fields":false,"notexists_indexed_fields":[],"opts":{"*dispatchers":[]},"prefix_indexed_fields":[],"suffix_indexed_fields":[]},"dns_agent":{"enabled":false,"listeners":[{"address":"127.0.0.1:53","network":"udp"}],"request_processors":[],"sessions_conns":["*internal"],"timezone":""},"ees":{"attributes_conns":[],"cache":{"*fileCSV":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"5s"}},"enabled":false,"exporters":[{"attempts":1,"attribute_context":"","attribute_ids":[],"blocker":false,"concurrent_requests":0,"efs_conns":["*internal"],"export_path":"/var/spool/cgrates/ees","failed_posts_dir":"/var/spool/cgrates/failed_posts","fields":[],"filters":[],"flags":[],"id":"*default","opts":{},"synchronous":false,"timezone":"","type":"*none"}]},"efs":{"enabled":false,"failed_posts_dir":"/var/spool/cgrates/failed_posts","failed_posts_ttl":"5s","poster_attempts":3},"ers":{"enabled":false,"partial_cache_ttl":"1s","readers":[{"cache_dump_fields":[],"concurrent_requests":1024,"fields":[{"mandatory":true,"path":"*cgreq.ToR","tag":"ToR","type":"*variable","value":"~*req.2"},{"mandatory":true,"path":"*cgreq.OriginID","tag":"OriginID","type":"*variable","value":"~*req.3"},{"mandatory":true,"path":"*cgreq.RequestType","tag":"RequestType","type":"*variable","value":"~*req.4"},{"mandatory":true,"path":"*cgreq.Tenant","tag":"Tenant","type":"*variable","value":"~*req.6"},{"mandatory":true,"path":"*cgreq.Category","tag":"Category","type":"*variable","value":"~*req.7"},{"mandatory":true,"path":"*cgreq.Account","tag":"Account","type":"*variable","value":"~*req.8"},{"mandatory":true,"path":"*cgreq.Subject","tag":"Subject","type":"*variable","value":"~*req.9"},{"mandatory":true,"path":"*cgreq.Destination","tag":"Destination","type":"*variable","value":"~*req.10"},{"mandatory":true,"path":"*cgreq.SetupTime","tag":"SetupTime","type":"*variable","value":"~*req.11"},{"mandatory":true,"path":"*cgreq.AnswerTime","tag":"AnswerTime","type":"*variable","value":"~*req.12"},{"mandatory":true,"path":"*cgreq.Usage","tag":"Usage","type":"*variable","value":"~*req.13"}],"filters":[],"flags":[],"id":"*default","max_reconnect_interval":"5m0s","opts":{"csvFieldSeparator":",","csvHeaderDefineChar":":","csvRowLength":0,"natsSubject":"cgrates_cdrs","partialCacheAction":"*none","partialOrderField":"~*req.AnswerTime"},"partial_commit_fields":[],"processed_path":"/var/spool/cgrates/ers/out","reconnects":-1,"run_delay":"0","source_path":"/var/spool/cgrates/ers/in","tenant":"","timezone":"","type":"*none"}],"sessions_conns":["*internal"]},"filters":{"accounts_conns":[],"resources_conns":[],"stats_conns":[]},"freeswitch_agent":{"active_session_delimiter":",","create_cdr":false,"empty_balance_ann_file":"","empty_balance_context":"","enabled":false,"event_socket_conns":[{"address":"127.0.0.1:8021","alias":"127.0.0.1:8021","max_reconnect_interval":"0s","password":"ClueCon","reconnects":5,"reply_timeout":"1m0s"}],"extra_fields":[],"low_balance_ann_file":"","max_wait_connection":"2s","sessions_conns":["*birpc_internal"],"subscribe_park":true},"general":{"caching_delay":"0","connect_attempts":5,"connect_timeout":"1s","dbdata_encoding":"*msgpack","decimal_max_scale":0,"decimal_min_scale":0,"decimal_precision":0,"decimal_rounding_mode":"*toNearestEven","default_caching":"*reload","default_category":"call","default_request_type":"*rated","default_tenant":"cgrates.org","default_timezone":"Local","digest_equal":":","digest_separator":",","locking_timeout":"0","max_parallel_conns":100,"max_reconnect_interval":"0","node_id":"ENGINE1","opts":{"*exporterIDs":[]},"reconnects":-1,"reply_timeout":"2s","rounding_decimals":5,"rsr_separator":";","tpexport_dir":"/var/spool/cgrates/tpe"},"http":{"auth_users":{},"client_opts":{"dialFallbackDelay":"300ms","dialKeepAlive":"30s","dialTimeout":"30s","disableCompression":false,"disableKeepAlives":false,"expectContinueTimeout":"0s","forceAttemptHttp2":true,"idleConnTimeout":"1m30s","maxConnsPerHost":0,"maxIdleConns":100,"maxIdleConnsPerHost":2,"responseHeaderTimeout":"0s","skipTLSVerification":false,"tlsHandshakeTimeout":"10s"},"freeswitch_cdrs_url":"/freeswitch_json","http_cdrs":"/cdr_http","json_rpc_url":"/jsonrpc","pprof_path":"/debug/pprof/","prometheus_url":"/prometheus","registrars_url":"/registrar","use_basic_auth":false,"ws_url":"/ws"},"http_agent":[],"janus_agent":{"enabled":false,"janus_conns":[{"address":"127.0.0.1:8088","admin_address":"localhost:7188","admin_password":"","type":"*ws"}],"request_processors":[],"sessions_conns":["*internal"],"url":"/janus"},"kamailio_agent":{"create_cdr":false,"enabled":false,"evapi_conns":[{"address":"127.0.0.1:8448","alias":"","max_reconnect_interval":"0s","reconnects":5}],"sessions_conns":["*birpc_internal"],"timezone":""},"listen":{"http":"127.0.0.1:2080","http_tls":"127.0.0.1:2280","rpc_gob":"127.0.0.1:2013","rpc_gob_tls":"127.0.0.1:2023","rpc_json":"127.0.0.1:2012","rpc_json_tls":"127.0.0.1:2022"},"loader":{"actions_conns":["*localhost"],"caches_conns":["*localhost"],"data_path":"./","disable_reverse":false,"field_separator":",","gapi_credentials":".gapi/credentials.json","gapi_token":".gapi/token.json","tpid":""},"loaders":[{"action":"*store","cache":{"*accounts":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"5s"},"*action_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"5s"},"*attributes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"5s"},"*chargers":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"5s"},"*dispatcher_hosts":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"5s"},"*dispatchers":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"5s"},"*filters":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"5s"},"*rate_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"5s"},"*resources":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"5s"},"*routes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"5s"},"*stats":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"5s"},"*thresholds":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"5s"}},"caches_conns":["*internal"],"data":[{"fields":[{"mandatory":true,"path":"Tenant","tag":"Tenant","type":"*variable","value":"~*req.0"},{"mandatory":true,"path":"ID","tag":"ID","type":"*variable","value":"~*req.1"},{"new_branch":true,"path":"Rules.Type","tag":"Type","type":"*variable","value":"~*req.2"},{"path":"Rules.Element","tag":"Element","type":"*variable","value":"~*req.3"},{"path":"Rules.Values","tag":"Values","type":"*variable","value":"~*req.4"}],"file_name":"Filters.csv","flags":null,"type":"*filters"},{"fields":[{"mandatory":true,"path":"Tenant","tag":"TenantID","type":"*variable","value":"~*req.0"},{"mandatory":true,"path":"ID","tag":"ProfileID","type":"*variable","value":"~*req.1"},{"path":"FilterIDs","tag":"FilterIDs","type":"*variable","value":"~*req.2"},{"path":"Weights","tag":"Weights","type":"*variable","value":"~*req.3"},{"path":"Blockers","tag":"Blockers","type":"*variable","value":"~*req.4"},{"new_branch":true,"path":"Attributes.FilterIDs","tag":"AttributeFilterIDs","type":"*variable","value":"~*req.5"},{"path":"Attributes.Blockers","tag":"AttributeBlockers","type":"*variable","value":"~*req.6"},{"path":"Attributes.Path","tag":"Path","type":"*variable","value":"~*req.7"},{"path":"Attributes.Type","tag":"Type","type":"*variable","value":"~*req.8"},{"path":"Attributes.Value","tag":"Value","type":"*variable","value":"~*req.9"}],"file_name":"Attributes.csv","flags":null,"type":"*attributes"},{"fields":[{"mandatory":true,"path":"Tenant","tag":"Tenant","type":"*variable","value":"~*req.0"},{"mandatory":true,"path":"ID","tag":"ID","type":"*variable","value":"~*req.1"},{"path":"FilterIDs","tag":"FilterIDs","type":"*variable","value":"~*req.2"},{"path":"Weights","tag":"Weights","type":"*variable","value":"~*req.3"},{"path":"UsageTTL","tag":"TTL","type":"*variable","value":"~*req.4"},{"path":"Limit","tag":"Limit","type":"*variable","value":"~*req.5"},{"path":"AllocationMessage","tag":"AllocationMessage","type":"*variable","value":"~*req.6"},{"path":"Blocker","tag":"Blocker","type":"*variable","value":"~*req.7"},{"path":"Stored","tag":"Stored","type":"*variable","value":"~*req.8"},{"path":"ThresholdIDs","tag":"ThresholdIDs","type":"*variable","value":"~*req.9"}],"file_name":"Resources.csv","flags":null,"type":"*resources"},{"fields":[{"mandatory":true,"path":"Tenant","tag":"Tenant","type":"*variable","value":"~*req.0"},{"mandatory":true,"path":"ID","tag":"ID","type":"*variable","value":"~*req.1"},{"path":"FilterIDs","tag":"FilterIDs","type":"*variable","value":"~*req.2"},{"path":"Weights","tag":"Weights","type":"*variable","value":"~*req.3"},{"path":"Blockers","tag":"Blockers","type":"*variable","value":"~*req.4"},{"path":"QueueLength","tag":"QueueLength","type":"*variable","value":"~*req.5"},{"path":"TTL","tag":"TTL","type":"*variable","value":"~*req.6"},{"path":"MinItems","tag":"MinItems","type":"*variable","value":"~*req.7"},{"path":"Stored","tag":"Stored","type":"*variable","value":"~*req.8"},{"path":"ThresholdIDs","tag":"ThresholdIDs","type":"*variable","value":"~*req.9"},{"new_branch":true,"path":"Metrics.MetricID","tag":"MetricIDs","type":"*variable","value":"~*req.10"},{"path":"Metrics.FilterIDs","tag":"MetricFilterIDs","type":"*variable","value":"~*req.11"},{"path":"Metrics.Blockers","tag":"MetricBlockers","type":"*variable","value":"~*req.12"}],"file_name":"Stats.csv","flags":null,"type":"*stats"},{"fields":[{"mandatory":true,"path":"Tenant","tag":"Tenant","type":"*variable","value":"~*req.0"},{"mandatory":true,"path":"ID","tag":"ID","type":"*variable","value":"~*req.1"},{"path":"FilterIDs","tag":"FilterIDs","type":"*variable","value":"~*req.2"},{"path":"Weights","tag":"Weights","type":"*variable","value":"~*req.3"},{"path":"MaxHits","tag":"MaxHits","type":"*variable","value":"~*req.4"},{"path":"MinHits","tag":"MinHits","type":"*variable","value":"~*req.5"},{"path":"MinSleep","tag":"MinSleep","type":"*variable","value":"~*req.6"},{"path":"Blocker","tag":"Blocker","type":"*variable","value":"~*req.7"},{"path":"ActionProfileIDs","tag":"ActionProfileIDs","type":"*variable","value":"~*req.8"},{"path":"Async","tag":"Async","type":"*variable","value":"~*req.9"}],"file_name":"Thresholds.csv","flags":null,"type":"*thresholds"},{"fields":[{"mandatory":true,"path":"Tenant","tag":"Tenant","type":"*variable","value":"~*req.0"},{"mandatory":true,"path":"ID","tag":"ID","type":"*variable","value":"~*req.1"},{"path":"FilterIDs","tag":"FilterIDs","type":"*variable","value":"~*req.2"},{"path":"Weights","tag":"Weights","type":"*variable","value":"~*req.3"},{"path":"Blockers","tag":"Blockers","type":"*variable","value":"~*req.4"},{"path":"Sorting","tag":"Sorting","type":"*variable","value":"~*req.5"},{"path":"SortingParameters","tag":"SortingParameters","type":"*variable","value":"~*req.6"},{"new_branch":true,"path":"Routes.ID","tag":"RouteID","type":"*variable","value":"~*req.7"},{"path":"Routes.FilterIDs","tag":"RouteFilterIDs","type":"*variable","value":"~*req.8"},{"path":"Routes.AccountIDs","tag":"RouteAccountIDs","type":"*variable","value":"~*req.9"},{"path":"Routes.RateProfileIDs","tag":"RouteRateProfileIDs","type":"*variable","value":"~*req.10"},{"path":"Routes.ResourceIDs","tag":"RouteResourceIDs","type":"*variable","value":"~*req.11"},{"path":"Routes.StatIDs","tag":"RouteStatIDs","type":"*variable","value":"~*req.12"},{"path":"Routes.Weights","tag":"RouteWeights","type":"*variable","value":"~*req.13"},{"path":"Routes.Blockers","tag":"RouteBlockers","type":"*variable","value":"~*req.14"},{"path":"Routes.RouteParameters","tag":"RouteParameters","type":"*variable","value":"~*req.15"}],"file_name":"Routes.csv","flags":null,"type":"*routes"},{"fields":[{"mandatory":true,"path":"Tenant","tag":"Tenant","type":"*variable","value":"~*req.0"},{"mandatory":true,"path":"ID","tag":"ID","type":"*variable","value":"~*req.1"},{"path":"FilterIDs","tag":"FilterIDs","type":"*variable","value":"~*req.2"},{"path":"Weights","tag":"Weights","type":"*variable","value":"~*req.3"},{"path":"Blockers","tag":"Blockers","type":"*variable","value":"~*req.4"},{"path":"RunID","tag":"RunID","type":"*variable","value":"~*req.5"},{"path":"AttributeIDs","tag":"AttributeIDs","type":"*variable","value":"~*req.6"}],"file_name":"Chargers.csv","flags":null,"type":"*chargers"},{"fields":[{"mandatory":true,"path":"Tenant","tag":"Tenant","type":"*variable","value":"~*req.0"},{"mandatory":true,"path":"ID","tag":"ID","type":"*variable","value":"~*req.1"},{"path":"FilterIDs","tag":"FilterIDs","type":"*variable","value":"~*req.2"},{"path":"Weight","tag":"Weight","type":"*variable","value":"~*req.3"},{"path":"Strategy","tag":"Strategy","type":"*variable","value":"~*req.4"},{"path":"StrategyParams","tag":"StrategyParameters","type":"*variable","value":"~*req.5"},{"new_branch":true,"path":"Hosts.ID","tag":"ConnID","type":"*variable","value":"~*req.6"},{"path":"Hosts.FilterIDs","tag":"ConnFilterIDs","type":"*variable","value":"~*req.7"},{"path":"Hosts.Weight","tag":"ConnWeight","type":"*variable","value":"~*req.8"},{"path":"Hosts.Blocker","tag":"ConnBlocker","type":"*variable","value":"~*req.9"},{"path":"Hosts.Params","tag":"ConnParameters","type":"*variable","value":"~*req.10"}],"file_name":"DispatcherProfiles.csv","flags":null,"type":"*dispatchers"},{"fields":[{"mandatory":true,"path":"Tenant","tag":"Tenant","type":"*variable","value":"~*req.0"},{"mandatory":true,"path":"ID","tag":"ID","type":"*variable","value":"~*req.1"},{"path":"Address","tag":"Address","type":"*variable","value":"~*req.2"},{"path":"Transport","tag":"Transport","type":"*variable","value":"~*req.3"},{"path":"ConnectAttempts","tag":"ConnectAttempts","type":"*variable","value":"~*req.4"},{"path":"Reconnects","tag":"Reconnects","type":"*variable","value":"~*req.5"},{"path":"MaxReconnectInterval","tag":"MaxReconnectInterval","type":"*variable","value":"~*req.6"},{"path":"ConnectTimeout","tag":"ConnectTimeout","type":"*variable","value":"~*req.7"},{"path":"ReplyTimeout","tag":"ReplyTimeout","type":"*variable","value":"~*req.8"},{"path":"TLS","tag":"TLS","type":"*variable","value":"~*req.9"},{"path":"ClientKey","tag":"ClientKey","type":"*variable","value":"~*req.10"},{"path":"ClientCertificate","tag":"ClientCertificate","type":"*variable","value":"~*req.11"},{"path":"CaCertificate","tag":"CaCertificate","type":"*variable","value":"~*req.12"}],"file_name":"DispatcherHosts.csv","flags":null,"type":"*dispatcher_hosts"},{"fields":[{"mandatory":true,"path":"Tenant","tag":"Tenant","type":"*variable","value":"~*req.0"},{"mandatory":true,"path":"ID","tag":"ID","type":"*variable","value":"~*req.1"},{"path":"FilterIDs","tag":"FilterIDs","type":"*variable","value":"~*req.2"},{"path":"Weights","tag":"Weights","type":"*variable","value":"~*req.3"},{"path":"MinCost","tag":"MinCost","type":"*variable","value":"~*req.4"},{"path":"MaxCost","tag":"MaxCost","type":"*variable","value":"~*req.5"},{"path":"MaxCostStrategy","tag":"MaxCostStrategy","type":"*variable","value":"~*req.6"},{"filters":["*notempty:~*req.7:"],"path":"Rates[\u003c~*req.7\u003e].FilterIDs","tag":"RateFilterIDs","type":"*variable","value":"~*req.8"},{"filters":["*notempty:~*req.7:"],"path":"Rates[\u003c~*req.7\u003e].ActivationTimes","tag":"RateActivationTimes","type":"*variable","value":"~*req.9"},{"filters":["*notempty:~*req.7:"],"path":"Rates[\u003c~*req.7\u003e].Weights","tag":"RateWeights","type":"*variable","value":"~*req.10"},{"filters":["*notempty:~*req.7:"],"path":"Rates[\u003c~*req.7\u003e].Blocker","tag":"RateBlocker","type":"*variable","value":"~*req.11"},{"filters":["*notempty:~*req.7:"],"new_branch":true,"path":"Rates[\u003c~*req.7\u003e].IntervalRates.IntervalStart","tag":"RateIntervalStart","type":"*variable","value":"~*req.12"},{"filters":["*notempty:~*req.7:"],"path":"Rates[\u003c~*req.7\u003e].IntervalRates.FixedFee","tag":"RateFixedFee","type":"*variable","value":"~*req.13"},{"filters":["*notempty:~*req.7:"],"path":"Rates[\u003c~*req.7\u003e].IntervalRates.RecurrentFee","tag":"RateRecurrentFee","type":"*variable","value":"~*req.14"},{"filters":["*notempty:~*req.7:"],"path":"Rates[\u003c~*req.7\u003e].IntervalRates.Unit","tag":"RateUnit","type":"*variable","value":"~*req.15"},{"filters":["*notempty:~*req.7:"],"path":"Rates[\u003c~*req.7\u003e].IntervalRates.Increment","tag":"RateIncrement","type":"*variable","value":"~*req.16"}],"file_name":"Rates.csv","flags":null,"type":"*rate_profiles"},{"fields":[{"mandatory":true,"path":"Tenant","tag":"Tenant","type":"*variable","value":"~*req.0"},{"mandatory":true,"path":"ID","tag":"ID","type":"*variable","value":"~*req.1"},{"path":"FilterIDs","tag":"FilterIDs","type":"*variable","value":"~*req.2"},{"path":"Weights","tag":"Weights","type":"*variable","value":"~*req.3"},{"path":"Blockers","tag":"Blockers","type":"*variable","value":"~*req.4"},{"path":"Schedule","tag":"Schedule","type":"*variable","value":"~*req.5"},{"path":"Targets[\u003c~*req.6\u003e]","tag":"TargetIDs","type":"*variable","value":"~*req.7"},{"filters":["*notempty:~*req.8:"],"path":"Actions[\u003c~*req.8\u003e].FilterIDs","tag":"ActionFilterIDs","type":"*variable","value":"~*req.9"},{"filters":["*notempty:~*req.8:"],"path":"Actions[\u003c~*req.8\u003e].TTL","tag":"ActionTTL","type":"*variable","value":"~*req.10"},{"filters":["*notempty:~*req.8:"],"path":"Actions[\u003c~*req.8\u003e].Type","tag":"ActionType","type":"*variable","value":"~*req.11"},{"filters":["*notempty:~*req.8:"],"path":"Actions[\u003c~*req.8\u003e].Opts","tag":"ActionOpts","type":"*variable","value":"~*req.12"},{"filters":["*notempty:~*req.8:"],"new_branch":true,"path":"Actions[\u003c~*req.8\u003e].Diktats.Path","tag":"ActionPath","type":"*variable","value":"~*req.13"},{"filters":["*notempty:~*req.8:"],"path":"Actions[\u003c~*req.8\u003e].Diktats.Value","tag":"ActionValue","type":"*variable","value":"~*req.14"}],"file_name":"Actions.csv","flags":null,"type":"*action_profiles"},{"fields":[{"mandatory":true,"path":"Tenant","tag":"Tenant","type":"*variable","value":"~*req.0"},{"mandatory":true,"path":"ID","tag":"ID","type":"*variable","value":"~*req.1"},{"path":"FilterIDs","tag":"FilterIDs","type":"*variable","value":"~*req.2"},{"path":"Weights","tag":"Weights","type":"*variable","value":"~*req.3"},{"path":"Blockers","tag":"Blockers","type":"*variable","value":"~*req.4"},{"path":"Opts","tag":"Opts","type":"*variable","value":"~*req.5"},{"filters":["*notempty:~*req.6:"],"path":"Balances[\u003c~*req.6\u003e].FilterIDs","tag":"BalanceFilterIDs","type":"*variable","value":"~*req.7"},{"filters":["*notempty:~*req.6:"],"path":"Balances[\u003c~*req.6\u003e].Weights","tag":"BalanceWeights","type":"*variable","value":"~*req.8"},{"filters":["*notempty:~*req.6:"],"path":"Balances[\u003c~*req.6\u003e].Blockers","tag":"BalanceBlockers","type":"*variable","value":"~*req.9"},{"filters":["*notempty:~*req.6:"],"path":"Balances[\u003c~*req.6\u003e].Type","tag":"BalanceType","type":"*variable","value":"~*req.10"},{"filters":["*notempty:~*req.6:"],"path":"Balances[\u003c~*req.6\u003e].Units","tag":"BalanceUnits","type":"*variable","value":"~*req.11"},{"filters":["*notempty:~*req.6:"],"path":"Balances[\u003c~*req.6\u003e].UnitFactors","tag":"BalanceUnitFactors","type":"*variable","value":"~*req.12"},{"filters":["*notempty:~*req.6:"],"path":"Balances[\u003c~*req.6\u003e].Opts","tag":"BalanceOpts","type":"*variable","value":"~*req.13"},{"filters":["*notempty:~*req.6:"],"path":"Balances[\u003c~*req.6\u003e].CostIncrements","tag":"BalanceCostIncrements","type":"*variable","value":"~*req.14"},{"filters":["*notempty:~*req.6:"],"path":"Balances[\u003c~*req.6\u003e].AttributeIDs","tag":"BalanceAttributeIDs","type":"*variable","value":"~*req.15"},{"filters":["*notempty:~*req.6:"],"path":"Balances[\u003c~*req.6\u003e].RateProfileIDs","tag":"BalanceRateProfileIDs","type":"*variable","value":"~*req.16"},{"path":"ThresholdIDs","tag":"ThresholdIDs","type":"*variable","value":"~*req.17"}],"file_name":"Accounts.csv","flags":null,"type":"*accounts"}],"enabled":false,"field_separator":",","id":"*default","lockfile_path":".cgr.lck","opts":{"*cache":"","*forceLock":false,"*stopOnError":false,"*withIndex":true},"run_delay":"0","tenant":"","tp_in_dir":"/var/spool/cgrates/loader/in","tp_out_dir":"/var/spool/cgrates/loader/out"}],"logger":{"efs_conns":["*internal"],"level":6,"opts":{"failed_posts_dir":"/var/spool/cgrates/failed_posts","kafka_attempts":1,"kafka_conn":"","kafka_topic":""},"type":"*syslog"},"migrator":{"out_datadb_encoding":"msgpack","out_datadb_host":"127.0.0.1","out_datadb_name":"10","out_datadb_opts":{"mongoConnScheme":"mongodb","mongoQueryTimeout":"10s","redisCACertificate":"","redisClientCertificate":"","redisClientKey":"","redisCluster":false,"redisClusterOndownDelay":"0s","redisClusterSync":"5s","redisConnectAttempts":20,"redisConnectTimeout":"0s","redisMaxConns":10,"redisPoolPipelineLimit":0,"redisPoolPipelineWindow":"150µs","redisReadTimeout":"0s","redisSentinel":"","redisTLS":false,"redisWriteTimeout":"0s"},"out_datadb_password":"","out_datadb_port":"6379","out_datadb_type":"*redis","out_datadb_user":"cgrates","users_filters":null},"radius_agent":{"client_dictionaries":{"*default":"/usr/share/cgrates/radius/dict/"},"client_secrets":{"*default":"CGRateS.org"},"enabled":false,"listen_acct":"127.0.0.1:1813","listen_auth":"127.0.0.1:1812","listen_net":"udp","request_processors":[],"sessions_conns":["*internal"]},"rankings":{"ees_conns":[],"ees_exporter_ids":null,"enabled":false,"scheduled_ids":{},"stats_conns":[],"store_interval":"","thresholds_conns":[]},"rates":{"enabled":false,"exists_indexed_fields":[],"indexed_selects":true,"nested_fields":false,"notexists_indexed_fields":[],"opts":{"*intervalStart":[],"*profileIDs":[],"*profileIgnoreFilters":[],"*startTime":[],"*usage":[]},"prefix_indexed_fields":[],"rate_exists_indexed_fields":[],"rate_indexed_selects":true,"rate_nested_fields":false,"rate_notexists_indexed_fields":[],"rate_prefix_indexed_fields":[],"rate_suffix_indexed_fields":[],"suffix_indexed_fields":[],"verbosity":1000},"registrarc":{"dispatchers":{"hosts":[],"refresh_interval":"5m0s","registrars_conns":[]},"rpc":{"hosts":[],"refresh_interval":"5m0s","registrars_conns":[]}},"resources":{"enabled":false,"exists_indexed_fields":[],"indexed_selects":true,"nested_fields":false,"notexists_indexed_fields":[],"opts":{"*units":[],"*usageID":[],"*usageTTL":[]},"prefix_indexed_fields":[],"store_interval":"","suffix_indexed_fields":[],"thresholds_conns":[]},"routes":{"accounts_conns":[],"attributes_conns":[],"default_ratio":1,"enabled":false,"exists_indexed_fields":[],"indexed_selects":true,"nested_fields":false,"notexists_indexed_fields":[],"opts":{"*context":[],"*ignoreErrors":[],"*limit":[],"*maxCost":[],"*maxItems":[],"*offset":[],"*profileCount":[],"*usage":[]},"prefix_indexed_fields":[],"rates_conns":[],"resources_conns":[],"stats_conns":[],"suffix_indexed_fields":[]},"rpc_conns":{"*bijson_localhost":{"conns":[{"address":"127.0.0.1:2014","transport":"*birpc_json"}],"poolSize":0,"strategy":"*first"},"*birpc_internal":{"conns":[{"address":"*birpc_internal","transport":""}],"poolSize":0,"strategy":"*first"},"*internal":{"conns":[{"address":"*internal","transport":""}],"poolSize":0,"strategy":"*first"},"*localhost":{"conns":[{"address":"127.0.0.1:2012","transport":"*json"}],"poolSize":0,"strategy":"*first"}},"sentrypeer":{"audience":"https://sentrypeer.com/api","client_id":"","client_secret":"","grant_type":"client_credentials","ips_url":"https://sentrypeer.com/api/ip-addresses","numbers_url":"https://sentrypeer.com/api/phone-numbers","token_url":"https://authz.sentrypeer.com/oauth/token"},"sessions":{"accounts_conns":[],"actions_conns":[],"alterable_fields":[],"attributes_conns":[],"cdrs_conns":[],"channel_sync_interval":"0","chargers_conns":[],"client_protocol":1,"default_usage":{"*any":"3h0m0s","*data":"1048576","*sms":"1","*voice":"3h0m0s"},"enabled":false,"listen_bigob":"","listen_bijson":"127.0.0.1:2014","min_dur_low_balance":"0","opts":{"*accounts":[],"*attributes":[],"*attributesDerivedReply":[],"*blockerError":[],"*cdrs":[],"*cdrsDerivedReply":[],"*chargeable":[],"*chargers":[],"*debitInterval":[],"*forceDuration":[],"*initiate":[],"*maxUsage":[],"*message":[],"*resources":[],"*resourcesAllocate":[],"*resourcesAuthorize":[],"*resourcesDerivedReply":[],"*resourcesRelease":[],"*routes":[],"*routesDerivedReply":[],"*stats":[],"*statsDerivedReply":[],"*terminate":[],"*thresholds":[],"*thresholdsDerivedReply":[],"*ttl":[],"*ttlLastUsage":[],"*ttlLastUsed":[],"*ttlMaxDelay":[],"*ttlUsage":[],"*update":[]},"rates_conns":[],"replication_conns":[],"resources_conns":[],"routes_conns":[],"session_indexes":[],"stats_conns":[],"stir":{"allowed_attest":["*any"],"default_attest":"A","payload_maxduration":"-1","privatekey_path":"","publickey_path":""},"store_session_costs":false,"terminate_attempts":5,"thresholds_conns":[]},"sip_agent":{"enabled":false,"listen":"127.0.0.1:5060","listen_net":"udp","request_processors":[],"retransmission_timer":"1s","sessions_conns":["*internal"],"timezone":""},"stats":{"ees_conns":[],"ees_exporter_ids":null,"enabled":false,"exists_indexed_fields":[],"indexed_selects":true,"nested_fields":false,"notexists_indexed_fields":[],"opts":{"*profileIDs":[],"*profileIgnoreFilters":[],"*prometheusStatIDs":[],"*roundingDecimals":[]},"prefix_indexed_fields":[],"store_interval":"","store_uncompressed_limit":0,"suffix_indexed_fields":[],"thresholds_conns":[]},"stor_db":{"db_host":"127.0.0.1","db_name":"cgrates","db_password":"CGRateS.org","db_port":3306,"db_type":"**mysql","db_user":"cgrates","items":{"*cdrs":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false}},"opts":{"mongoConnScheme":"mongodb","mongoQueryTimeout":"10s","mysqlDSNParams":{},"mysqlLocation":"Local","pgSSLMode":"disable","sqlConnMaxLifetime":"0s","sqlMaxIdleConns":10,"sqlMaxOpenConns":100},"prefix_indexed_fields":[],"remote_conns":null,"replication_conns":null,"string_indexed_fields":[]},"suretax":{"bill_to_number":"","business_unit":"","client_number":"","client_tracking":"~*opts.*originID","customer_number":"~*req.Subject","include_local_cost":false,"orig_number":"~*req.Subject","p2pplus4":"","p2pzipcode":"","plus4":"","regulatory_code":"03","response_group":"03","response_type":"D4","return_file_code":"0","sales_type_code":"R","tax_exemption_code_list":"","tax_included":"0","tax_situs_rule":"04","term_number":"~*req.Destination","timezone":"UTC","trans_type_code":"010101","unit_type":"00","units":"1","url":"","validation_key":"","zipcode":""},"templates":{"*asr":[{"mandatory":true,"path":"*diamreq.Session-Id","tag":"SessionId","type":"*variable","value":"~*req.Session-Id"},{"mandatory":true,"path":"*diamreq.Origin-Host","tag":"OriginHost","type":"*variable","value":"~*req.Destination-Host"},{"mandatory":true,"path":"*diamreq.Origin-Realm","tag":"OriginRealm","type":"*variable","value":"~*req.Destination-Realm"},{"mandatory":true,"path":"*diamreq.Destination-Realm","tag":"DestinationRealm","type":"*variable","value":"~*req.Origin-Realm"},{"mandatory":true,"path":"*diamreq.Destination-Host","tag":"DestinationHost","type":"*variable","value":"~*req.Origin-Host"},{"mandatory":true,"path":"*diamreq.Auth-Application-Id","tag":"AuthApplicationId","type":"*variable","value":"~*vars.*appid"}],"*cca":[{"mandatory":true,"path":"*rep.Session-Id","tag":"SessionId","type":"*variable","value":"~*req.Session-Id"},{"path":"*rep.Result-Code","tag":"ResultCode","type":"*constant","value":"2001"},{"mandatory":true,"path":"*rep.Origin-Host","tag":"OriginHost","type":"*variable","value":"~*vars.OriginHost"},{"mandatory":true,"path":"*rep.Origin-Realm","tag":"OriginRealm","type":"*variable","value":"~*vars.OriginRealm"},{"mandatory":true,"path":"*rep.Auth-Application-Id","tag":"AuthApplicationId","type":"*variable","value":"~*vars.*appid"},{"mandatory":true,"path":"*rep.CC-Request-Type","tag":"CCRequestType","type":"*variable","value":"~*req.CC-Request-Type"},{"mandatory":true,"path":"*rep.CC-Request-Number","tag":"CCRequestNumber","type":"*variable","value":"~*req.CC-Request-Number"}],"*cdrLog":[{"mandatory":true,"path":"*cdr.ToR","tag":"ToR","type":"*variable","value":"~*req.BalanceType"},{"mandatory":true,"path":"*cdr.OriginHost","tag":"OriginHost","type":"*constant","value":"127.0.0.1"},{"mandatory":true,"path":"*cdr.RequestType","tag":"RequestType","type":"*constant","value":"*none"},{"mandatory":true,"path":"*cdr.Tenant","tag":"Tenant","type":"*variable","value":"~*req.Tenant"},{"mandatory":true,"path":"*cdr.Account","tag":"Account","type":"*variable","value":"~*req.Account"},{"mandatory":true,"path":"*cdr.Subject","tag":"Subject","type":"*variable","value":"~*req.Account"},{"mandatory":true,"path":"*cdr.Cost","tag":"Cost","type":"*variable","value":"~*req.Cost"},{"mandatory":true,"path":"*cdr.Source","tag":"Source","type":"*constant","value":"*cdrLog"},{"mandatory":true,"path":"*cdr.Usage","tag":"Usage","type":"*constant","value":"1"},{"mandatory":true,"path":"*cdr.RunID","tag":"RunID","type":"*variable","value":"~*req.ActionType"},{"mandatory":true,"path":"*cdr.SetupTime","tag":"SetupTime","type":"*constant","value":"*now"},{"mandatory":true,"path":"*cdr.AnswerTime","tag":"AnswerTime","type":"*constant","value":"*now"},{"mandatory":true,"path":"*cdr.PreRated","tag":"PreRated","type":"*constant","value":"true"}],"*err":[{"mandatory":true,"path":"*rep.Session-Id","tag":"SessionId","type":"*variable","value":"~*req.Session-Id"},{"mandatory":true,"path":"*rep.Origin-Host","tag":"OriginHost","type":"*variable","value":"~*vars.OriginHost"},{"mandatory":true,"path":"*rep.Origin-Realm","tag":"OriginRealm","type":"*variable","value":"~*vars.OriginRealm"}],"*errSip":[{"mandatory":true,"path":"*rep.Request","tag":"Request","type":"*constant","value":"SIP/2.0 500 Internal Server Error"}],"*rar":[{"mandatory":true,"path":"*diamreq.Session-Id","tag":"SessionId","type":"*variable","value":"~*req.Session-Id"},{"mandatory":true,"path":"*diamreq.Origin-Host","tag":"OriginHost","type":"*variable","value":"~*req.Destination-Host"},{"mandatory":true,"path":"*diamreq.Origin-Realm","tag":"OriginRealm","type":"*variable","value":"~*req.Destination-Realm"},{"mandatory":true,"path":"*diamreq.Destination-Realm","tag":"DestinationRealm","type":"*variable","value":"~*req.Origin-Realm"},{"mandatory":true,"path":"*diamreq.Destination-Host","tag":"DestinationHost","type":"*variable","value":"~*req.Origin-Host"},{"mandatory":true,"path":"*diamreq.Auth-Application-Id","tag":"AuthApplicationId","type":"*variable","value":"~*vars.*appid"},{"path":"*diamreq.Re-Auth-Request-Type","tag":"ReAuthRequestType","type":"*constant","value":"0"}]},"thresholds":{"actions_conns":[],"enabled":false,"exists_indexed_fields":[],"indexed_selects":true,"nested_fields":false,"notexists_indexed_fields":[],"opts":{"*profileIDs":[],"*profileIgnoreFilters":[]},"prefix_indexed_fields":[],"store_interval":"","suffix_indexed_fields":[]},"tls":{"ca_certificate":"","client_certificate":"","client_key":"","server_certificate":"","server_key":"","server_name":"","server_policy":4},"tpes":{"enabled":false},"trends":{"ees_conns":[],"ees_exporter_ids":null,"enabled":false,"scheduled_ids":{},"stats_conns":[],"store_interval":"","store_uncompressed_limit":0,"thresholds_conns":[]}}` + expected := `{"accounts":{"attributes_conns":[],"enabled":false,"exists_indexed_fields":[],"indexed_selects":true,"max_iterations":1000,"max_usage":"259200000000000","nested_fields":false,"notexists_indexed_fields":[],"opts":{"*profileIDs":[],"*profileIgnoreFilters":[],"*usage":[]},"prefix_indexed_fields":[],"rates_conns":[],"suffix_indexed_fields":[],"thresholds_conns":[]},"actions":{"accounts_conns":[],"cdrs_conns":[],"dynaprepaid_actionprofile":[],"ees_conns":[],"enabled":false,"exists_indexed_fields":[],"indexed_selects":true,"nested_fields":false,"notexists_indexed_fields":[],"opts":{"*posterAttempts":[],"*profileIDs":[],"*profileIgnoreFilters":[]},"prefix_indexed_fields":[],"stats_conns":[],"suffix_indexed_fields":[],"tenants":[],"thresholds_conns":[]},"admins":{"actions_conns":[],"attributes_conns":[],"caches_conns":["*internal"],"ees_conns":[],"enabled":false},"analyzers":{"cleanup_interval":"1h0m0s","db_path":"/var/spool/cgrates/analyzers","ees_conns":[],"enabled":false,"index_type":"*scorch","opts":{"*exporterIDs":[]},"ttl":"24h0m0s"},"apiban":{"enabled":false,"keys":[]},"asterisk_agent":{"asterisk_conns":[{"address":"127.0.0.1:8088","alias":"","connect_attempts":3,"max_reconnect_interval":"0s","password":"CGRateS.org","reconnects":5,"user":"cgrates"}],"create_cdr":false,"enabled":false,"sessions_conns":["*birpc_internal"]},"attributes":{"accounts_conns":[],"enabled":false,"exists_indexed_fields":[],"indexed_selects":true,"nested_fields":false,"notexists_indexed_fields":[],"opts":{"*processRuns":[],"*profileIDs":[],"*profileIgnoreFilters":[],"*profileRuns":[]},"prefix_indexed_fields":[],"resources_conns":[],"stats_conns":[],"suffix_indexed_fields":[]},"caches":{"partitions":{"*account_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*accounts":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*action_profile_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*action_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*apiban":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"2m0s"},"*attribute_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*attribute_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*caps_events":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*cdr_ids":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"10m0s"},"*charger_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*charger_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*closed_sessions":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"10s"},"*diameter_messages":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"3h0m0s"},"*dispatcher_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*dispatcher_hosts":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*dispatcher_loads":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*dispatcher_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*dispatcher_routes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*dispatchers":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*event_charges":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"10s"},"*event_resources":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*filters":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*load_ids":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*ranking_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*rankings":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*rate_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*rate_profile_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*rate_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*replication_hosts":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*resource_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*resource_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*resources":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*reverse_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*route_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*route_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*rpc_connections":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*rpc_responses":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"2s"},"*sentrypeer":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":true,"ttl":"24h0m0s"},"*stat_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*statqueue_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*statqueues":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*stir":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"3h0m0s"},"*threshold_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*threshold_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*thresholds":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*trend_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*trends":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*uch":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"3h0m0s"}},"remote_conns":[],"replication_conns":[]},"cdrs":{"accounts_conns":[],"actions_conns":[],"attributes_conns":[],"chargers_conns":[],"ees_conns":[],"enabled":false,"extra_fields":[],"online_cdr_exports":null,"opts":{"*accounts":[],"*attributes":[],"*chargers":[],"*ees":[],"*rates":[],"*refund":[],"*rerate":[],"*stats":[],"*store":[],"*thresholds":[]},"rates_conns":[],"session_cost_retries":5,"stats_conns":[],"thresholds_conns":[]},"chargers":{"attributes_conns":[],"enabled":false,"exists_indexed_fields":[],"indexed_selects":true,"nested_fields":false,"notexists_indexed_fields":[],"prefix_indexed_fields":[],"suffix_indexed_fields":[]},"config_db":{"db_host":"","db_name":"","db_password":"","db_port":0,"db_type":"*internal","db_user":"","opts":{"mongoConnScheme":"mongodb","mongoQueryTimeout":"10s","redisCACertificate":"","redisClientCertificate":"","redisClientKey":"","redisCluster":false,"redisClusterOndownDelay":"0s","redisClusterSync":"5s","redisConnectAttempts":20,"redisConnectTimeout":"0s","redisMaxConns":10,"redisReadTimeout":"0s","redisSentinel":"","redisTLS":false,"redisWriteTimeout":"0s"}},"configs":{"enabled":false,"root_dir":"/var/spool/cgrates/configs","url":"/configs/"},"cores":{"caps":0,"caps_stats_interval":"0","caps_strategy":"*busy","ees_conns":[],"shutdown_timeout":"1s"},"data_db":{"db_host":"127.0.0.1","db_name":"10","db_password":"","db_port":6379,"db_type":"*redis","db_user":"cgrates","items":{"*account_filter_indexes":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*accounts":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*action_profile_filter_indexes":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*action_profiles":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*actions":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*attribute_filter_indexes":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*attribute_profiles":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*charger_filter_indexes":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*charger_profiles":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*dispatcher_filter_indexes":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*dispatcher_hosts":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*dispatcher_profiles":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*filters":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*load_ids":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*ranking_profiles":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*rankings":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*rate_filter_indexes":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*rate_profile_filter_indexes":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*rate_profiles":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*resource_filter_indexes":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*resource_profiles":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*resources":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*reverse_filter_indexes":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*route_filter_indexes":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*route_profiles":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*stat_filter_indexes":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*statqueue_profiles":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*statqueues":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*threshold_filter_indexes":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*threshold_profiles":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*thresholds":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*trend_profiles":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*trends":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*versions":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false}},"opts":{"mongoConnScheme":"mongodb","mongoQueryTimeout":"10s","redisCACertificate":"","redisClientCertificate":"","redisClientKey":"","redisCluster":false,"redisClusterOndownDelay":"0s","redisClusterSync":"5s","redisConnectAttempts":20,"redisConnectTimeout":"0s","redisMaxConns":10,"redisPoolPipelineLimit":0,"redisPoolPipelineWindow":"150µs","redisReadTimeout":"0s","redisSentinel":"","redisTLS":false,"redisWriteTimeout":"0s"},"remote_conn_id":"","remote_conns":[],"replication_cache":"","replication_conns":[],"replication_filtered":false},"diameter_agent":{"asr_template":"","concurrent_requests":-1,"dictionaries_path":"/usr/share/cgrates/diameter/dict/","enabled":false,"forced_disconnect":"*none","listen":"127.0.0.1:3868","listen_net":"tcp","origin_host":"CGR-DA","origin_realm":"cgrates.org","product_name":"CGRateS","rar_template":"","request_processors":[],"sessions_conns":["*birpc_internal"],"synced_conn_requests":false,"vendor_id":0},"dispatchers":{"attributes_conns":[],"enabled":false,"exists_indexed_fields":[],"indexed_selects":true,"nested_fields":false,"notexists_indexed_fields":[],"opts":{"*dispatchers":[]},"prefix_indexed_fields":[],"suffix_indexed_fields":[]},"dns_agent":{"enabled":false,"listeners":[{"address":"127.0.0.1:53","network":"udp"}],"request_processors":[],"sessions_conns":["*internal"],"timezone":""},"ees":{"attributes_conns":[],"cache":{"*fileCSV":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"5s"}},"enabled":false,"exporters":[{"attempts":1,"attribute_context":"","attribute_ids":[],"blocker":false,"concurrent_requests":0,"efs_conns":["*internal"],"export_path":"/var/spool/cgrates/ees","failed_posts_dir":"/var/spool/cgrates/failed_posts","fields":[],"filters":[],"flags":[],"id":"*default","opts":{},"synchronous":false,"timezone":"","type":"*none"}]},"efs":{"enabled":false,"failed_posts_dir":"/var/spool/cgrates/failed_posts","failed_posts_ttl":"5s","poster_attempts":3},"ers":{"enabled":false,"partial_cache_ttl":"1s","readers":[{"cache_dump_fields":[],"concurrent_requests":1024,"fields":[{"mandatory":true,"path":"*cgreq.ToR","tag":"ToR","type":"*variable","value":"~*req.2"},{"mandatory":true,"path":"*cgreq.OriginID","tag":"OriginID","type":"*variable","value":"~*req.3"},{"mandatory":true,"path":"*cgreq.RequestType","tag":"RequestType","type":"*variable","value":"~*req.4"},{"mandatory":true,"path":"*cgreq.Tenant","tag":"Tenant","type":"*variable","value":"~*req.6"},{"mandatory":true,"path":"*cgreq.Category","tag":"Category","type":"*variable","value":"~*req.7"},{"mandatory":true,"path":"*cgreq.Account","tag":"Account","type":"*variable","value":"~*req.8"},{"mandatory":true,"path":"*cgreq.Subject","tag":"Subject","type":"*variable","value":"~*req.9"},{"mandatory":true,"path":"*cgreq.Destination","tag":"Destination","type":"*variable","value":"~*req.10"},{"mandatory":true,"path":"*cgreq.SetupTime","tag":"SetupTime","type":"*variable","value":"~*req.11"},{"mandatory":true,"path":"*cgreq.AnswerTime","tag":"AnswerTime","type":"*variable","value":"~*req.12"},{"mandatory":true,"path":"*cgreq.Usage","tag":"Usage","type":"*variable","value":"~*req.13"}],"filters":[],"flags":[],"id":"*default","max_reconnect_interval":"5m0s","opts":{"csvFieldSeparator":",","csvHeaderDefineChar":":","csvRowLength":0,"natsSubject":"cgrates_cdrs","partialCacheAction":"*none","partialOrderField":"~*req.AnswerTime"},"partial_commit_fields":[],"processed_path":"/var/spool/cgrates/ers/out","reconnects":-1,"run_delay":"0","source_path":"/var/spool/cgrates/ers/in","tenant":"","timezone":"","type":"*none"}],"sessions_conns":["*internal"]},"filters":{"accounts_conns":[],"resources_conns":[],"stats_conns":[]},"freeswitch_agent":{"active_session_delimiter":",","create_cdr":false,"empty_balance_ann_file":"","empty_balance_context":"","enabled":false,"event_socket_conns":[{"address":"127.0.0.1:8021","alias":"127.0.0.1:8021","max_reconnect_interval":"0s","password":"ClueCon","reconnects":5,"reply_timeout":"1m0s"}],"extra_fields":[],"low_balance_ann_file":"","max_wait_connection":"2s","sessions_conns":["*birpc_internal"],"subscribe_park":true},"general":{"caching_delay":"0","connect_attempts":5,"connect_timeout":"1s","dbdata_encoding":"*msgpack","decimal_max_scale":0,"decimal_min_scale":0,"decimal_precision":0,"decimal_rounding_mode":"*toNearestEven","default_caching":"*reload","default_category":"call","default_request_type":"*rated","default_tenant":"cgrates.org","default_timezone":"Local","digest_equal":":","digest_separator":",","locking_timeout":"0","max_parallel_conns":100,"max_reconnect_interval":"0","node_id":"ENGINE1","opts":{"*exporterIDs":[]},"reconnects":-1,"reply_timeout":"2s","rounding_decimals":5,"rsr_separator":";","tpexport_dir":"/var/spool/cgrates/tpe"},"http":{"auth_users":{},"client_opts":{"dialFallbackDelay":"300ms","dialKeepAlive":"30s","dialTimeout":"30s","disableCompression":false,"disableKeepAlives":false,"expectContinueTimeout":"0s","forceAttemptHttp2":true,"idleConnTimeout":"1m30s","maxConnsPerHost":0,"maxIdleConns":100,"maxIdleConnsPerHost":2,"responseHeaderTimeout":"0s","skipTLSVerification":false,"tlsHandshakeTimeout":"10s"},"freeswitch_cdrs_url":"/freeswitch_json","http_cdrs":"/cdr_http","json_rpc_url":"/jsonrpc","pprof_path":"/debug/pprof/","prometheus_url":"/prometheus","registrars_url":"/registrar","use_basic_auth":false,"ws_url":"/ws"},"http_agent":[],"janus_agent":{"enabled":false,"janus_conns":[{"address":"127.0.0.1:8088","admin_address":"localhost:7188","admin_password":"","type":"*ws"}],"request_processors":[],"sessions_conns":["*internal"],"url":"/janus"},"kamailio_agent":{"create_cdr":false,"enabled":false,"evapi_conns":[{"address":"127.0.0.1:8448","alias":"","max_reconnect_interval":"0s","reconnects":5}],"sessions_conns":["*birpc_internal"],"timezone":""},"listen":{"http":"127.0.0.1:2080","http_tls":"127.0.0.1:2280","rpc_gob":"127.0.0.1:2013","rpc_gob_tls":"127.0.0.1:2023","rpc_json":"127.0.0.1:2012","rpc_json_tls":"127.0.0.1:2022"},"loader":{"actions_conns":["*localhost"],"caches_conns":["*localhost"],"data_path":"./","disable_reverse":false,"field_separator":",","gapi_credentials":".gapi/credentials.json","gapi_token":".gapi/token.json","tpid":""},"loaders":[{"action":"*store","cache":{"*accounts":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"5s"},"*action_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"5s"},"*attributes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"5s"},"*chargers":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"5s"},"*dispatcher_hosts":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"5s"},"*dispatchers":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"5s"},"*filters":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"5s"},"*rate_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"5s"},"*resources":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"5s"},"*routes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"5s"},"*stats":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"5s"},"*thresholds":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"5s"}},"caches_conns":["*internal"],"data":[{"fields":[{"mandatory":true,"path":"Tenant","tag":"Tenant","type":"*variable","value":"~*req.0"},{"mandatory":true,"path":"ID","tag":"ID","type":"*variable","value":"~*req.1"},{"new_branch":true,"path":"Rules.Type","tag":"Type","type":"*variable","value":"~*req.2"},{"path":"Rules.Element","tag":"Element","type":"*variable","value":"~*req.3"},{"path":"Rules.Values","tag":"Values","type":"*variable","value":"~*req.4"}],"file_name":"Filters.csv","flags":null,"type":"*filters"},{"fields":[{"mandatory":true,"path":"Tenant","tag":"TenantID","type":"*variable","value":"~*req.0"},{"mandatory":true,"path":"ID","tag":"ProfileID","type":"*variable","value":"~*req.1"},{"path":"FilterIDs","tag":"FilterIDs","type":"*variable","value":"~*req.2"},{"path":"Weights","tag":"Weights","type":"*variable","value":"~*req.3"},{"path":"Blockers","tag":"Blockers","type":"*variable","value":"~*req.4"},{"new_branch":true,"path":"Attributes.FilterIDs","tag":"AttributeFilterIDs","type":"*variable","value":"~*req.5"},{"path":"Attributes.Blockers","tag":"AttributeBlockers","type":"*variable","value":"~*req.6"},{"path":"Attributes.Path","tag":"Path","type":"*variable","value":"~*req.7"},{"path":"Attributes.Type","tag":"Type","type":"*variable","value":"~*req.8"},{"path":"Attributes.Value","tag":"Value","type":"*variable","value":"~*req.9"}],"file_name":"Attributes.csv","flags":null,"type":"*attributes"},{"fields":[{"mandatory":true,"path":"Tenant","tag":"Tenant","type":"*variable","value":"~*req.0"},{"mandatory":true,"path":"ID","tag":"ID","type":"*variable","value":"~*req.1"},{"path":"FilterIDs","tag":"FilterIDs","type":"*variable","value":"~*req.2"},{"path":"Weights","tag":"Weights","type":"*variable","value":"~*req.3"},{"path":"UsageTTL","tag":"TTL","type":"*variable","value":"~*req.4"},{"path":"Limit","tag":"Limit","type":"*variable","value":"~*req.5"},{"path":"AllocationMessage","tag":"AllocationMessage","type":"*variable","value":"~*req.6"},{"path":"Blocker","tag":"Blocker","type":"*variable","value":"~*req.7"},{"path":"Stored","tag":"Stored","type":"*variable","value":"~*req.8"},{"path":"ThresholdIDs","tag":"ThresholdIDs","type":"*variable","value":"~*req.9"}],"file_name":"Resources.csv","flags":null,"type":"*resources"},{"fields":[{"mandatory":true,"path":"Tenant","tag":"Tenant","type":"*variable","value":"~*req.0"},{"mandatory":true,"path":"ID","tag":"ID","type":"*variable","value":"~*req.1"},{"path":"FilterIDs","tag":"FilterIDs","type":"*variable","value":"~*req.2"},{"path":"Weights","tag":"Weights","type":"*variable","value":"~*req.3"},{"path":"Blockers","tag":"Blockers","type":"*variable","value":"~*req.4"},{"path":"QueueLength","tag":"QueueLength","type":"*variable","value":"~*req.5"},{"path":"TTL","tag":"TTL","type":"*variable","value":"~*req.6"},{"path":"MinItems","tag":"MinItems","type":"*variable","value":"~*req.7"},{"path":"Stored","tag":"Stored","type":"*variable","value":"~*req.8"},{"path":"ThresholdIDs","tag":"ThresholdIDs","type":"*variable","value":"~*req.9"},{"new_branch":true,"path":"Metrics.MetricID","tag":"MetricIDs","type":"*variable","value":"~*req.10"},{"path":"Metrics.FilterIDs","tag":"MetricFilterIDs","type":"*variable","value":"~*req.11"},{"path":"Metrics.Blockers","tag":"MetricBlockers","type":"*variable","value":"~*req.12"}],"file_name":"Stats.csv","flags":null,"type":"*stats"},{"fields":[{"mandatory":true,"path":"Tenant","tag":"Tenant","type":"*variable","value":"~*req.0"},{"mandatory":true,"path":"ID","tag":"ID","type":"*variable","value":"~*req.1"},{"path":"FilterIDs","tag":"FilterIDs","type":"*variable","value":"~*req.2"},{"path":"Weights","tag":"Weights","type":"*variable","value":"~*req.3"},{"path":"MaxHits","tag":"MaxHits","type":"*variable","value":"~*req.4"},{"path":"MinHits","tag":"MinHits","type":"*variable","value":"~*req.5"},{"path":"MinSleep","tag":"MinSleep","type":"*variable","value":"~*req.6"},{"path":"Blocker","tag":"Blocker","type":"*variable","value":"~*req.7"},{"path":"ActionProfileIDs","tag":"ActionProfileIDs","type":"*variable","value":"~*req.8"},{"path":"Async","tag":"Async","type":"*variable","value":"~*req.9"}],"file_name":"Thresholds.csv","flags":null,"type":"*thresholds"},{"fields":[{"mandatory":true,"path":"Tenant","tag":"Tenant","type":"*variable","value":"~*req.0"},{"mandatory":true,"path":"ID","tag":"ID","type":"*variable","value":"~*req.1"},{"path":"FilterIDs","tag":"FilterIDs","type":"*variable","value":"~*req.2"},{"path":"Weights","tag":"Weights","type":"*variable","value":"~*req.3"},{"path":"Blockers","tag":"Blockers","type":"*variable","value":"~*req.4"},{"path":"Sorting","tag":"Sorting","type":"*variable","value":"~*req.5"},{"path":"SortingParameters","tag":"SortingParameters","type":"*variable","value":"~*req.6"},{"new_branch":true,"path":"Routes.ID","tag":"RouteID","type":"*variable","value":"~*req.7"},{"path":"Routes.FilterIDs","tag":"RouteFilterIDs","type":"*variable","value":"~*req.8"},{"path":"Routes.AccountIDs","tag":"RouteAccountIDs","type":"*variable","value":"~*req.9"},{"path":"Routes.RateProfileIDs","tag":"RouteRateProfileIDs","type":"*variable","value":"~*req.10"},{"path":"Routes.ResourceIDs","tag":"RouteResourceIDs","type":"*variable","value":"~*req.11"},{"path":"Routes.StatIDs","tag":"RouteStatIDs","type":"*variable","value":"~*req.12"},{"path":"Routes.Weights","tag":"RouteWeights","type":"*variable","value":"~*req.13"},{"path":"Routes.Blockers","tag":"RouteBlockers","type":"*variable","value":"~*req.14"},{"path":"Routes.RouteParameters","tag":"RouteParameters","type":"*variable","value":"~*req.15"}],"file_name":"Routes.csv","flags":null,"type":"*routes"},{"fields":[{"mandatory":true,"path":"Tenant","tag":"Tenant","type":"*variable","value":"~*req.0"},{"mandatory":true,"path":"ID","tag":"ID","type":"*variable","value":"~*req.1"},{"path":"FilterIDs","tag":"FilterIDs","type":"*variable","value":"~*req.2"},{"path":"Weights","tag":"Weights","type":"*variable","value":"~*req.3"},{"path":"Blockers","tag":"Blockers","type":"*variable","value":"~*req.4"},{"path":"RunID","tag":"RunID","type":"*variable","value":"~*req.5"},{"path":"AttributeIDs","tag":"AttributeIDs","type":"*variable","value":"~*req.6"}],"file_name":"Chargers.csv","flags":null,"type":"*chargers"},{"fields":[{"mandatory":true,"path":"Tenant","tag":"Tenant","type":"*variable","value":"~*req.0"},{"mandatory":true,"path":"ID","tag":"ID","type":"*variable","value":"~*req.1"},{"path":"FilterIDs","tag":"FilterIDs","type":"*variable","value":"~*req.2"},{"path":"Weight","tag":"Weight","type":"*variable","value":"~*req.3"},{"path":"Strategy","tag":"Strategy","type":"*variable","value":"~*req.4"},{"path":"StrategyParams","tag":"StrategyParameters","type":"*variable","value":"~*req.5"},{"new_branch":true,"path":"Hosts.ID","tag":"ConnID","type":"*variable","value":"~*req.6"},{"path":"Hosts.FilterIDs","tag":"ConnFilterIDs","type":"*variable","value":"~*req.7"},{"path":"Hosts.Weight","tag":"ConnWeight","type":"*variable","value":"~*req.8"},{"path":"Hosts.Blocker","tag":"ConnBlocker","type":"*variable","value":"~*req.9"},{"path":"Hosts.Params","tag":"ConnParameters","type":"*variable","value":"~*req.10"}],"file_name":"DispatcherProfiles.csv","flags":null,"type":"*dispatchers"},{"fields":[{"mandatory":true,"path":"Tenant","tag":"Tenant","type":"*variable","value":"~*req.0"},{"mandatory":true,"path":"ID","tag":"ID","type":"*variable","value":"~*req.1"},{"path":"Address","tag":"Address","type":"*variable","value":"~*req.2"},{"path":"Transport","tag":"Transport","type":"*variable","value":"~*req.3"},{"path":"ConnectAttempts","tag":"ConnectAttempts","type":"*variable","value":"~*req.4"},{"path":"Reconnects","tag":"Reconnects","type":"*variable","value":"~*req.5"},{"path":"MaxReconnectInterval","tag":"MaxReconnectInterval","type":"*variable","value":"~*req.6"},{"path":"ConnectTimeout","tag":"ConnectTimeout","type":"*variable","value":"~*req.7"},{"path":"ReplyTimeout","tag":"ReplyTimeout","type":"*variable","value":"~*req.8"},{"path":"TLS","tag":"TLS","type":"*variable","value":"~*req.9"},{"path":"ClientKey","tag":"ClientKey","type":"*variable","value":"~*req.10"},{"path":"ClientCertificate","tag":"ClientCertificate","type":"*variable","value":"~*req.11"},{"path":"CaCertificate","tag":"CaCertificate","type":"*variable","value":"~*req.12"}],"file_name":"DispatcherHosts.csv","flags":null,"type":"*dispatcher_hosts"},{"fields":[{"mandatory":true,"path":"Tenant","tag":"Tenant","type":"*variable","value":"~*req.0"},{"mandatory":true,"path":"ID","tag":"ID","type":"*variable","value":"~*req.1"},{"path":"FilterIDs","tag":"FilterIDs","type":"*variable","value":"~*req.2"},{"path":"Weights","tag":"Weights","type":"*variable","value":"~*req.3"},{"path":"MinCost","tag":"MinCost","type":"*variable","value":"~*req.4"},{"path":"MaxCost","tag":"MaxCost","type":"*variable","value":"~*req.5"},{"path":"MaxCostStrategy","tag":"MaxCostStrategy","type":"*variable","value":"~*req.6"},{"filters":["*notempty:~*req.7:"],"path":"Rates[\u003c~*req.7\u003e].FilterIDs","tag":"RateFilterIDs","type":"*variable","value":"~*req.8"},{"filters":["*notempty:~*req.7:"],"path":"Rates[\u003c~*req.7\u003e].ActivationTimes","tag":"RateActivationTimes","type":"*variable","value":"~*req.9"},{"filters":["*notempty:~*req.7:"],"path":"Rates[\u003c~*req.7\u003e].Weights","tag":"RateWeights","type":"*variable","value":"~*req.10"},{"filters":["*notempty:~*req.7:"],"path":"Rates[\u003c~*req.7\u003e].Blocker","tag":"RateBlocker","type":"*variable","value":"~*req.11"},{"filters":["*notempty:~*req.7:"],"new_branch":true,"path":"Rates[\u003c~*req.7\u003e].IntervalRates.IntervalStart","tag":"RateIntervalStart","type":"*variable","value":"~*req.12"},{"filters":["*notempty:~*req.7:"],"path":"Rates[\u003c~*req.7\u003e].IntervalRates.FixedFee","tag":"RateFixedFee","type":"*variable","value":"~*req.13"},{"filters":["*notempty:~*req.7:"],"path":"Rates[\u003c~*req.7\u003e].IntervalRates.RecurrentFee","tag":"RateRecurrentFee","type":"*variable","value":"~*req.14"},{"filters":["*notempty:~*req.7:"],"path":"Rates[\u003c~*req.7\u003e].IntervalRates.Unit","tag":"RateUnit","type":"*variable","value":"~*req.15"},{"filters":["*notempty:~*req.7:"],"path":"Rates[\u003c~*req.7\u003e].IntervalRates.Increment","tag":"RateIncrement","type":"*variable","value":"~*req.16"}],"file_name":"Rates.csv","flags":null,"type":"*rate_profiles"},{"fields":[{"mandatory":true,"path":"Tenant","tag":"Tenant","type":"*variable","value":"~*req.0"},{"mandatory":true,"path":"ID","tag":"ID","type":"*variable","value":"~*req.1"},{"path":"FilterIDs","tag":"FilterIDs","type":"*variable","value":"~*req.2"},{"path":"Weights","tag":"Weights","type":"*variable","value":"~*req.3"},{"path":"Blockers","tag":"Blockers","type":"*variable","value":"~*req.4"},{"path":"Schedule","tag":"Schedule","type":"*variable","value":"~*req.5"},{"path":"Targets[\u003c~*req.6\u003e]","tag":"TargetIDs","type":"*variable","value":"~*req.7"},{"filters":["*notempty:~*req.8:"],"path":"Actions[\u003c~*req.8\u003e].FilterIDs","tag":"ActionFilterIDs","type":"*variable","value":"~*req.9"},{"filters":["*notempty:~*req.8:"],"path":"Actions[\u003c~*req.8\u003e].TTL","tag":"ActionTTL","type":"*variable","value":"~*req.10"},{"filters":["*notempty:~*req.8:"],"path":"Actions[\u003c~*req.8\u003e].Type","tag":"ActionType","type":"*variable","value":"~*req.11"},{"filters":["*notempty:~*req.8:"],"path":"Actions[\u003c~*req.8\u003e].Opts","tag":"ActionOpts","type":"*variable","value":"~*req.12"},{"filters":["*notempty:~*req.8:"],"new_branch":true,"path":"Actions[\u003c~*req.8\u003e].Diktats.Path","tag":"ActionPath","type":"*variable","value":"~*req.13"},{"filters":["*notempty:~*req.8:"],"path":"Actions[\u003c~*req.8\u003e].Diktats.Value","tag":"ActionValue","type":"*variable","value":"~*req.14"}],"file_name":"Actions.csv","flags":null,"type":"*action_profiles"},{"fields":[{"mandatory":true,"path":"Tenant","tag":"Tenant","type":"*variable","value":"~*req.0"},{"mandatory":true,"path":"ID","tag":"ID","type":"*variable","value":"~*req.1"},{"path":"FilterIDs","tag":"FilterIDs","type":"*variable","value":"~*req.2"},{"path":"Weights","tag":"Weights","type":"*variable","value":"~*req.3"},{"path":"Blockers","tag":"Blockers","type":"*variable","value":"~*req.4"},{"path":"Opts","tag":"Opts","type":"*variable","value":"~*req.5"},{"filters":["*notempty:~*req.6:"],"path":"Balances[\u003c~*req.6\u003e].FilterIDs","tag":"BalanceFilterIDs","type":"*variable","value":"~*req.7"},{"filters":["*notempty:~*req.6:"],"path":"Balances[\u003c~*req.6\u003e].Weights","tag":"BalanceWeights","type":"*variable","value":"~*req.8"},{"filters":["*notempty:~*req.6:"],"path":"Balances[\u003c~*req.6\u003e].Blockers","tag":"BalanceBlockers","type":"*variable","value":"~*req.9"},{"filters":["*notempty:~*req.6:"],"path":"Balances[\u003c~*req.6\u003e].Type","tag":"BalanceType","type":"*variable","value":"~*req.10"},{"filters":["*notempty:~*req.6:"],"path":"Balances[\u003c~*req.6\u003e].Units","tag":"BalanceUnits","type":"*variable","value":"~*req.11"},{"filters":["*notempty:~*req.6:"],"path":"Balances[\u003c~*req.6\u003e].UnitFactors","tag":"BalanceUnitFactors","type":"*variable","value":"~*req.12"},{"filters":["*notempty:~*req.6:"],"path":"Balances[\u003c~*req.6\u003e].Opts","tag":"BalanceOpts","type":"*variable","value":"~*req.13"},{"filters":["*notempty:~*req.6:"],"path":"Balances[\u003c~*req.6\u003e].CostIncrements","tag":"BalanceCostIncrements","type":"*variable","value":"~*req.14"},{"filters":["*notempty:~*req.6:"],"path":"Balances[\u003c~*req.6\u003e].AttributeIDs","tag":"BalanceAttributeIDs","type":"*variable","value":"~*req.15"},{"filters":["*notempty:~*req.6:"],"path":"Balances[\u003c~*req.6\u003e].RateProfileIDs","tag":"BalanceRateProfileIDs","type":"*variable","value":"~*req.16"},{"path":"ThresholdIDs","tag":"ThresholdIDs","type":"*variable","value":"~*req.17"}],"file_name":"Accounts.csv","flags":null,"type":"*accounts"}],"enabled":false,"field_separator":",","id":"*default","lockfile_path":".cgr.lck","opts":{"*cache":"","*forceLock":false,"*stopOnError":false,"*withIndex":true},"run_delay":"0","tenant":"","tp_in_dir":"/var/spool/cgrates/loader/in","tp_out_dir":"/var/spool/cgrates/loader/out"}],"logger":{"efs_conns":["*internal"],"level":6,"opts":{"failed_posts_dir":"/var/spool/cgrates/failed_posts","kafka_attempts":1,"kafka_conn":"","kafka_topic":""},"type":"*syslog"},"migrator":{"out_datadb_encoding":"msgpack","out_datadb_host":"127.0.0.1","out_datadb_name":"10","out_datadb_opts":{"mongoConnScheme":"mongodb","mongoQueryTimeout":"10s","redisCACertificate":"","redisClientCertificate":"","redisClientKey":"","redisCluster":false,"redisClusterOndownDelay":"0s","redisClusterSync":"5s","redisConnectAttempts":20,"redisConnectTimeout":"0s","redisMaxConns":10,"redisPoolPipelineLimit":0,"redisPoolPipelineWindow":"150µs","redisReadTimeout":"0s","redisSentinel":"","redisTLS":false,"redisWriteTimeout":"0s"},"out_datadb_password":"","out_datadb_port":"6379","out_datadb_type":"*redis","out_datadb_user":"cgrates","users_filters":null},"radius_agent":{"client_dictionaries":{"*default":"/usr/share/cgrates/radius/dict/"},"client_secrets":{"*default":"CGRateS.org"},"enabled":false,"listen_acct":"127.0.0.1:1813","listen_auth":"127.0.0.1:1812","listen_net":"udp","request_processors":[],"sessions_conns":["*internal"]},"rankings":{"ees_conns":[],"ees_exporter_ids":null,"enabled":false,"scheduled_ids":{},"stats_conns":[],"store_interval":"","thresholds_conns":[]},"rates":{"enabled":false,"exists_indexed_fields":[],"indexed_selects":true,"nested_fields":false,"notexists_indexed_fields":[],"opts":{"*intervalStart":[],"*profileIDs":[],"*profileIgnoreFilters":[],"*startTime":[],"*usage":[]},"prefix_indexed_fields":[],"rate_exists_indexed_fields":[],"rate_indexed_selects":true,"rate_nested_fields":false,"rate_notexists_indexed_fields":[],"rate_prefix_indexed_fields":[],"rate_suffix_indexed_fields":[],"suffix_indexed_fields":[],"verbosity":1000},"registrarc":{"dispatchers":{"hosts":[],"refresh_interval":"5m0s","registrars_conns":[]},"rpc":{"hosts":[],"refresh_interval":"5m0s","registrars_conns":[]}},"resources":{"enabled":false,"exists_indexed_fields":[],"indexed_selects":true,"nested_fields":false,"notexists_indexed_fields":[],"opts":{"*units":[],"*usageID":[],"*usageTTL":[]},"prefix_indexed_fields":[],"store_interval":"","suffix_indexed_fields":[],"thresholds_conns":[]},"routes":{"accounts_conns":[],"attributes_conns":[],"default_ratio":1,"enabled":false,"exists_indexed_fields":[],"indexed_selects":true,"nested_fields":false,"notexists_indexed_fields":[],"opts":{"*context":[],"*ignoreErrors":[],"*limit":[],"*maxCost":[],"*maxItems":[],"*offset":[],"*profileCount":[],"*usage":[]},"prefix_indexed_fields":[],"rates_conns":[],"resources_conns":[],"stats_conns":[],"suffix_indexed_fields":[]},"rpc_conns":{"*bijson_localhost":{"conns":[{"address":"127.0.0.1:2014","transport":"*birpc_json"}],"poolSize":0,"strategy":"*first"},"*birpc_internal":{"conns":[{"address":"*birpc_internal","transport":""}],"poolSize":0,"strategy":"*first"},"*internal":{"conns":[{"address":"*internal","transport":""}],"poolSize":0,"strategy":"*first"},"*localhost":{"conns":[{"address":"127.0.0.1:2012","transport":"*json"}],"poolSize":0,"strategy":"*first"}},"sentrypeer":{"audience":"https://sentrypeer.com/api","client_id":"","client_secret":"","grant_type":"client_credentials","ips_url":"https://sentrypeer.com/api/ip-addresses","numbers_url":"https://sentrypeer.com/api/phone-numbers","token_url":"https://authz.sentrypeer.com/oauth/token"},"sessions":{"accounts_conns":[],"actions_conns":[],"alterable_fields":[],"attributes_conns":[],"cdrs_conns":[],"channel_sync_interval":"0","chargers_conns":[],"client_protocol":1,"default_usage":{"*any":"3h0m0s","*data":"1048576","*sms":"1","*voice":"3h0m0s"},"enabled":false,"listen_bigob":"","listen_bijson":"127.0.0.1:2014","min_dur_low_balance":"0","opts":{"*accounts":[],"*attributes":[],"*attributesDerivedReply":[],"*blockerError":[],"*cdrs":[],"*cdrsDerivedReply":[],"*chargeable":[],"*chargers":[],"*debitInterval":[],"*forceDuration":[],"*initiate":[],"*maxUsage":[],"*message":[],"*resources":[],"*resourcesAllocate":[],"*resourcesAuthorize":[],"*resourcesDerivedReply":[],"*resourcesRelease":[],"*routes":[],"*routesDerivedReply":[],"*stats":[],"*statsDerivedReply":[],"*terminate":[],"*thresholds":[],"*thresholdsDerivedReply":[],"*ttl":[],"*ttlLastUsage":[],"*ttlLastUsed":[],"*ttlMaxDelay":[],"*ttlUsage":[],"*update":[]},"rates_conns":[],"replication_conns":[],"resources_conns":[],"routes_conns":[],"session_indexes":[],"stats_conns":[],"stir":{"allowed_attest":["*any"],"default_attest":"A","payload_maxduration":"-1","privatekey_path":"","publickey_path":""},"store_session_costs":false,"terminate_attempts":5,"thresholds_conns":[]},"sip_agent":{"enabled":false,"listen":"127.0.0.1:5060","listen_net":"udp","request_processors":[],"retransmission_timer":"1s","sessions_conns":["*internal"],"timezone":""},"stats":{"ees_conns":[],"ees_exporter_ids":null,"enabled":false,"exists_indexed_fields":[],"indexed_selects":true,"nested_fields":false,"notexists_indexed_fields":[],"opts":{"*profileIDs":[],"*profileIgnoreFilters":[],"*prometheusStatIDs":[],"*roundingDecimals":[]},"prefix_indexed_fields":[],"store_interval":"","store_uncompressed_limit":0,"suffix_indexed_fields":[],"thresholds_conns":[]},"stor_db":{"db_host":"127.0.0.1","db_name":"cgrates","db_password":"CGRateS.org","db_port":3306,"db_type":"**mysql","db_user":"cgrates","items":{"*cdrs":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false}},"opts":{"mongoConnScheme":"mongodb","mongoQueryTimeout":"10s","mysqlDSNParams":{},"mysqlLocation":"Local","pgSSLMode":"disable","sqlConnMaxLifetime":"0s","sqlMaxIdleConns":10,"sqlMaxOpenConns":100},"prefix_indexed_fields":[],"remote_conns":null,"replication_conns":null,"string_indexed_fields":[]},"suretax":{"bill_to_number":"","business_unit":"","client_number":"","client_tracking":"~*opts.*originID","customer_number":"~*req.Subject","include_local_cost":false,"orig_number":"~*req.Subject","p2pplus4":"","p2pzipcode":"","plus4":"","regulatory_code":"03","response_group":"03","response_type":"D4","return_file_code":"0","sales_type_code":"R","tax_exemption_code_list":"","tax_included":"0","tax_situs_rule":"04","term_number":"~*req.Destination","timezone":"UTC","trans_type_code":"010101","unit_type":"00","units":"1","url":"","validation_key":"","zipcode":""},"templates":{"*asr":[{"mandatory":true,"path":"*diamreq.Session-Id","tag":"SessionId","type":"*variable","value":"~*req.Session-Id"},{"mandatory":true,"path":"*diamreq.Origin-Host","tag":"OriginHost","type":"*variable","value":"~*req.Destination-Host"},{"mandatory":true,"path":"*diamreq.Origin-Realm","tag":"OriginRealm","type":"*variable","value":"~*req.Destination-Realm"},{"mandatory":true,"path":"*diamreq.Destination-Realm","tag":"DestinationRealm","type":"*variable","value":"~*req.Origin-Realm"},{"mandatory":true,"path":"*diamreq.Destination-Host","tag":"DestinationHost","type":"*variable","value":"~*req.Origin-Host"},{"mandatory":true,"path":"*diamreq.Auth-Application-Id","tag":"AuthApplicationId","type":"*variable","value":"~*vars.*appid"}],"*cca":[{"mandatory":true,"path":"*rep.Session-Id","tag":"SessionId","type":"*variable","value":"~*req.Session-Id"},{"path":"*rep.Result-Code","tag":"ResultCode","type":"*constant","value":"2001"},{"mandatory":true,"path":"*rep.Origin-Host","tag":"OriginHost","type":"*variable","value":"~*vars.OriginHost"},{"mandatory":true,"path":"*rep.Origin-Realm","tag":"OriginRealm","type":"*variable","value":"~*vars.OriginRealm"},{"mandatory":true,"path":"*rep.Auth-Application-Id","tag":"AuthApplicationId","type":"*variable","value":"~*vars.*appid"},{"mandatory":true,"path":"*rep.CC-Request-Type","tag":"CCRequestType","type":"*variable","value":"~*req.CC-Request-Type"},{"mandatory":true,"path":"*rep.CC-Request-Number","tag":"CCRequestNumber","type":"*variable","value":"~*req.CC-Request-Number"}],"*cdrLog":[{"mandatory":true,"path":"*cdr.ToR","tag":"ToR","type":"*variable","value":"~*req.BalanceType"},{"mandatory":true,"path":"*cdr.OriginHost","tag":"OriginHost","type":"*constant","value":"127.0.0.1"},{"mandatory":true,"path":"*cdr.RequestType","tag":"RequestType","type":"*constant","value":"*none"},{"mandatory":true,"path":"*cdr.Tenant","tag":"Tenant","type":"*variable","value":"~*req.Tenant"},{"mandatory":true,"path":"*cdr.Account","tag":"Account","type":"*variable","value":"~*req.Account"},{"mandatory":true,"path":"*cdr.Subject","tag":"Subject","type":"*variable","value":"~*req.Account"},{"mandatory":true,"path":"*cdr.Cost","tag":"Cost","type":"*variable","value":"~*req.Cost"},{"mandatory":true,"path":"*cdr.Source","tag":"Source","type":"*constant","value":"*cdrLog"},{"mandatory":true,"path":"*cdr.Usage","tag":"Usage","type":"*constant","value":"1"},{"mandatory":true,"path":"*cdr.RunID","tag":"RunID","type":"*variable","value":"~*req.ActionType"},{"mandatory":true,"path":"*cdr.SetupTime","tag":"SetupTime","type":"*constant","value":"*now"},{"mandatory":true,"path":"*cdr.AnswerTime","tag":"AnswerTime","type":"*constant","value":"*now"},{"mandatory":true,"path":"*cdr.PreRated","tag":"PreRated","type":"*constant","value":"true"}],"*err":[{"mandatory":true,"path":"*rep.Session-Id","tag":"SessionId","type":"*variable","value":"~*req.Session-Id"},{"mandatory":true,"path":"*rep.Origin-Host","tag":"OriginHost","type":"*variable","value":"~*vars.OriginHost"},{"mandatory":true,"path":"*rep.Origin-Realm","tag":"OriginRealm","type":"*variable","value":"~*vars.OriginRealm"}],"*errSip":[{"mandatory":true,"path":"*rep.Request","tag":"Request","type":"*constant","value":"SIP/2.0 500 Internal Server Error"}],"*rar":[{"mandatory":true,"path":"*diamreq.Session-Id","tag":"SessionId","type":"*variable","value":"~*req.Session-Id"},{"mandatory":true,"path":"*diamreq.Origin-Host","tag":"OriginHost","type":"*variable","value":"~*req.Destination-Host"},{"mandatory":true,"path":"*diamreq.Origin-Realm","tag":"OriginRealm","type":"*variable","value":"~*req.Destination-Realm"},{"mandatory":true,"path":"*diamreq.Destination-Realm","tag":"DestinationRealm","type":"*variable","value":"~*req.Origin-Realm"},{"mandatory":true,"path":"*diamreq.Destination-Host","tag":"DestinationHost","type":"*variable","value":"~*req.Origin-Host"},{"mandatory":true,"path":"*diamreq.Auth-Application-Id","tag":"AuthApplicationId","type":"*variable","value":"~*vars.*appid"},{"path":"*diamreq.Re-Auth-Request-Type","tag":"ReAuthRequestType","type":"*constant","value":"0"}]},"thresholds":{"actions_conns":[],"enabled":false,"exists_indexed_fields":[],"indexed_selects":true,"nested_fields":false,"notexists_indexed_fields":[],"opts":{"*profileIDs":[],"*profileIgnoreFilters":[]},"prefix_indexed_fields":[],"store_interval":"","suffix_indexed_fields":[]},"tls":{"ca_certificate":"","client_certificate":"","client_key":"","server_certificate":"","server_key":"","server_name":"","server_policy":4},"tpes":{"enabled":false},"trends":{"ees_conns":[],"ees_exporter_ids":null,"enabled":false,"scheduled_ids":{},"stats_conns":[],"store_interval":"","store_uncompressed_limit":0,"thresholds_conns":[]}}` cgrCfg, err := NewCGRConfigFromJSONStringWithDefaults(cfgJSON) if err != nil { diff --git a/data/storage/mysql/create_tariffplan_tables.sql b/data/storage/mysql/create_tariffplan_tables.sql index 200603880..5c1fafed2 100644 --- a/data/storage/mysql/create_tariffplan_tables.sql +++ b/data/storage/mysql/create_tariffplan_tables.sql @@ -78,6 +78,29 @@ CREATE TABLE tp_trends( UNIQUE KEY `unique_tp_trends` (`tpid`,`tenant`,`id`,`stat_id`) ); +-- +-- Table structure for tabls `tp_rankings` +-- + +DROP TABLE IF EXISTS tp_rankings; +CREATE TABLE tp_rankings( + `pk` int(11) NOT NULL AUTO_INCREMENT, + `tpid` varchar(64) NOT NULL, + `tenant` varchar(64) NOT NULL, + `id` varchar(64) NOT NULL, + `schedule` varchar(32) NOT NULL, + `stat_ids` varchar(64) NOT NULL, + `metric_ids` varchar(64) NOT NULL, + `sorting` varchar(32) NOT NULL, + `sorting_parameters` varchar(64) NOT NULL, + `stored` BOOLEAN NOT NULL, + `threshold_ids` varchar(64) NOT NULL, + `created_at` TIMESTAMP, + PRIMARY KEY (`pk`), + KEY `tpid` (`tpid`), + UNIQUE KEY `unique_tp_rankings` (`tpid`,`tenant`,`id`,`stat_ids`) + ); + -- -- Table structure for table `tp_threshold_cfgs` diff --git a/data/storage/postgres/create_tariffplan_tables.sql b/data/storage/postgres/create_tariffplan_tables.sql index fc2b753d0..3992b5288 100644 --- a/data/storage/postgres/create_tariffplan_tables.sql +++ b/data/storage/postgres/create_tariffplan_tables.sql @@ -76,6 +76,29 @@ CREATE TABLE tp_trends( CREATE INDEX tp_trends_idx ON tp_trends(tpid); CREATE INDEX tp_trends_unique ON tp_trends("tpid","tenant","id","stat_id"); + +-- +-- Table structure for table `tp_rankings` +-- + +DROP TABLE IF EXISTS tp_rankings; +CREATE TABLE tp_rankings( + "pk" SERIAL PRIMARY KEY, + "tpid" varchar(64) NOT NULL, + "tenant" varchar(64) NOT NULL, + "id" varchar(64) NOT NULL, + "schedule" varchar(32) NOT NULL, + "stat_ids" varchar(64) NOT NULL, + "metric_ids" varchar(64) NOT NULL, + "sorting" varchar(32) NOT NULL, + "sorting_parameters" varchar(64) NOT NULL, + "stored" BOOLEAN NOT NULL, + "threshold_ids" varchar(64) NOT NULL, + "created_at" TIMESTAMP WITH TIME ZONE +); +CREATE INDEX tp_rankings_idx ON tp_rankings (tpid); +CREATE INDEX tp_rankings_unique ON tp_rankings ("tpid","tenant", "id","stat_ids"); + -- -- Table structure for table `tp_threshold_cfgs` diff --git a/data/tariffplans/tuttrends/Stats.csv b/data/tariffplans/tuttrends/Stats.csv new file mode 100644 index 000000000..05417128d --- /dev/null +++ b/data/tariffplans/tuttrends/Stats.csv @@ -0,0 +1,5 @@ +#Tenant[0],Id[1],FilterIDs[2],ActivationInterval[3],QueueLength[4],TTL[5],MinItems[6],Metrics[7],MetricFilterIDs[8],Stored[9],Blocker[10],Weight[11],ThresholdIDs[12] +cgrates.org,Stats1_1,*string:~*req.Account:1001,,,,,*tcc;*acd;*tcd,,,,, +cgrates.org,Stats1_2,*string:~*req.Account:1002,,,,,*sum#~*req.Usage;*pdd,,,,, +tenant1,Stat1,*string:~*req.Account:1005,,,,,*tcd;*asr;*acc,,,,, +tenant2,Stat_Avg,,,,,,*acc,,,,, \ No newline at end of file diff --git a/data/tariffplans/tuttrends/Trends.csv b/data/tariffplans/tuttrends/Trends.csv new file mode 100644 index 000000000..5b0338de4 --- /dev/null +++ b/data/tariffplans/tuttrends/Trends.csv @@ -0,0 +1,8 @@ +#Tenant[0],Id[1],Schedule[2],StatID[3],Metrics[4],TTL[5],QueueLength[6],MinItems[7],CorrelationType[8],Tolerance[9],Stored[10],ThresholdIDs[11] +cgrates.org,TREND_1,@every 1s,Stats1_1,,-1,-1,1,*last,1,false, +cgrates.org,TREND_2,@every 3s,Stats1_2,,-1,2,4,*last,1,false, +cgrates.org,TR_1min,@every 1m,Stats1_1,,-1,3,2,*average,0.15,false, +tenant1,TR_5min,*/5 * * * *,Stat1,,-1,-1,8,*average,0.23,true, +tenant1,TR_1hr,0 * * * *,Stat1,,-1,-1,8,*average,0.1,true, +tenant2,Trend_avg,@every 10m,Stat_Avg,,-1,-1,8,*average,0.6,true, +tenant2,Trend_avg_30min,@every 30m,Stat_Avg,,-1,-1,100,*average,0.3,true, \ No newline at end of file diff --git a/dispatchers/caches_it_test.go b/dispatchers/caches_it_test.go index 0ee255a3d..1295059e9 100644 --- a/dispatchers/caches_it_test.go +++ b/dispatchers/caches_it_test.go @@ -137,6 +137,8 @@ func testDspChcLoadAfterFolder(t *testing.T) { expStats[utils.CacheThresholdFilterIndexes].Groups = 1 expStats[utils.CacheRankingProfiles].Items = 0 expStats[utils.CacheRankingProfiles].Groups = 0 + expStats[utils.CacheRankings].Items = 0 + expStats[utils.CacheRankings].Groups = 0 expStats[utils.CacheStatFilterIndexes].Items = 7 expStats[utils.CacheStatFilterIndexes].Groups = 1 expStats[utils.CacheRouteFilterIndexes].Items = 3 @@ -173,6 +175,7 @@ func testDspChcPrecacheStatus(t *testing.T) { utils.CacheTrendProfiles: utils.MetaReady, utils.CacheTrends: utils.MetaReady, utils.CacheRankingProfiles: utils.MetaReady, + utils.CacheRankings: utils.MetaReady, utils.CacheFilters: utils.MetaReady, utils.CacheRouteProfiles: utils.MetaReady, utils.CacheAttributeProfiles: utils.MetaReady, diff --git a/engine/datadbmock.go b/engine/datadbmock.go index 73c8b5e9c..1162a67a2 100644 --- a/engine/datadbmock.go +++ b/engine/datadbmock.go @@ -254,6 +254,19 @@ func (dbM *DataDBMock) RemRankingProfileDrv(ctx *context.Context, tenant string, } return utils.ErrNotImplemented } + +func (dbM *DataDBMock) GetRankingDrv(ctx *context.Context, tenant, id string) (*Ranking, error) { + return nil, utils.ErrNotImplemented +} + +func (dbM *DataDBMock) SetRankingDrv(ctx *context.Context, _ *Ranking) error { + return utils.ErrNotImplemented +} + +func (dbM *DataDBMock) RemoveRankingDrv(ctx *context.Context, _ string, _ string) error { + return utils.ErrNotImplemented +} + func (dbM *DataDBMock) GetTrendProfileDrv(ctx *context.Context, tenant, id string) (sg *TrendProfile, err error) { if dbM.GetStatQueueProfileDrvF != nil { return dbM.GetTrendProfileDrvF(ctx, tenant, id) @@ -486,15 +499,15 @@ func (dbM *DataDBMock) RemoveRateProfileDrv(ctx *context.Context, str1 string, s return utils.ErrNotImplemented } -func (dbM *DataDBMock) GetTrendDrv(tenant, id string) (*Trend, error) { +func (dbM *DataDBMock) GetTrendDrv(ctx *context.Context, tenant, id string) (*Trend, error) { return nil, utils.ErrNotImplemented } -func (dbM *DataDBMock) SetTrendDrv(*Trend) error { +func (dbM *DataDBMock) SetTrendDrv(ctx *context.Context, tr *Trend) error { return utils.ErrNotImplemented } -func (dbM *DataDBMock) RemoveTrendDrv(string, string) error { +func (dbM *DataDBMock) RemoveTrendDrv(ctx *context.Context, _ string, _ string) error { return utils.ErrNotImplemented } diff --git a/engine/datamanager.go b/engine/datamanager.go index d40672e61..1f76cdc3a 100644 --- a/engine/datamanager.go +++ b/engine/datamanager.go @@ -961,77 +961,11 @@ func (dm *DataManager) RemoveStatQueueProfile(ctx *context.Context, tenant, id s return dm.RemoveStatQueue(ctx, tenant, id) } -func (dm *DataManager) GetTrendProfile(ctx *context.Context, tenant, id string) (trp *TrendProfile, err error) { - if dm == nil { - err = utils.ErrNoDatabaseConn - return - } - trp, err = dm.dataDB.GetTrendProfileDrv(ctx, tenant, id) - if err != nil { - if itm := config.CgrConfig().DataDbCfg().Items[utils.MetaTrendProfiles]; err == utils.ErrNotFound && itm.Remote { - if err = dm.connMgr.Call(context.TODO(), config.CgrConfig().DataDbCfg().RmtConns, - utils.ReplicatorSv1GetTrendProfile, - &utils.TenantIDWithAPIOpts{ - TenantID: &utils.TenantID{Tenant: tenant, ID: id}, - APIOpts: utils.GenerateDBItemOpts(itm.APIKey, itm.RouteID, utils.EmptyString, - utils.FirstNonEmpty(config.CgrConfig().DataDbCfg().RmtConnID, - config.CgrConfig().GeneralCfg().NodeID)), - }, &trp); err == nil { - err = dm.dataDB.SetTrendProfileDrv(ctx, trp) - } - } - } - return -} - -func (dm *DataManager) SetTrendProfile(ctx *context.Context, trp *TrendProfile) (err error) { - if dm == nil { - return utils.ErrNoDatabaseConn - } - if err = dm.DataDB().SetTrendProfileDrv(ctx, trp); err != nil { - return - } - if itm := config.CgrConfig().DataDbCfg().Items[utils.MetaTrendProfiles]; itm.Replicate { - err = replicate(context.Background(), dm.connMgr, config.CgrConfig().DataDbCfg().RplConns, - config.CgrConfig().DataDbCfg().RplFiltered, - utils.TrendProfilePrefix, trp.TenantID(), - utils.ReplicatorSv1SetTrendProfile, - &TrendProfileWithAPIOpts{ - TrendProfile: trp, - APIOpts: utils.GenerateDBItemOpts(itm.APIKey, itm.RouteID, - config.CgrConfig().DataDbCfg().RplCache, utils.EmptyString)}) - } - return -} - -func (dm *DataManager) RemoveTrendProfile(ctx *context.Context, tenant, id string) (err error) { - oldSgs, err := dm.GetTrendProfile(ctx, tenant, id) - if err != nil && err != utils.ErrNotFound { - return err - } - if err = dm.DataDB().RemTrendProfileDrv(ctx, tenant, id); err != nil { - return - } - if oldSgs == nil { - return utils.ErrNotFound - } - if itm := config.CgrConfig().DataDbCfg().Items[utils.MetaRankingProfiles]; itm.Replicate { - replicate(context.Background(), dm.connMgr, config.CgrConfig().DataDbCfg().RplConns, - config.CgrConfig().DataDbCfg().RplFiltered, - utils.TrendProfilePrefix, utils.ConcatenatedKey(tenant, id), // this are used to get the host IDs from cache - utils.ReplicatorSv1RemoveTrendProfile, - &utils.TenantIDWithAPIOpts{ - TenantID: &utils.TenantID{Tenant: tenant, ID: id}, - APIOpts: utils.GenerateDBItemOpts(itm.APIKey, itm.RouteID, - config.CgrConfig().DataDbCfg().RplCache, utils.EmptyString)}) - } - return -} - // GetTrend retrieves a Trend from dataDB -func (dm *DataManager) GetTrend(tenant, id string, +func (dm *DataManager) GetTrend(ctx *context.Context, tenant, id string, cacheRead, cacheWrite bool, transactionID string) (tr *Trend, err error) { tntID := utils.ConcatenatedKey(tenant, id) + if cacheRead { if x, ok := Cache.Get(utils.CacheTrends, tntID); ok { if x == nil { @@ -1044,70 +978,89 @@ func (dm *DataManager) GetTrend(tenant, id string, err = utils.ErrNoDatabaseConn return } - tr, err = dm.dataDB.GetTrendDrv(tenant, id) - if err != nil { - if itm := config.CgrConfig().DataDbCfg().Items[utils.MetaTrends]; err == utils.ErrNotFound && itm.Remote { + if tr, err = dm.dataDB.GetTrendDrv(ctx, tenant, id); err != nil { + if err != utils.ErrNotFound { // database error + return + } + // ErrNotFound + if itm := config.CgrConfig().DataDbCfg().Items[utils.MetaTrends]; itm.Remote { if err = dm.connMgr.Call(context.TODO(), config.CgrConfig().DataDbCfg().RmtConns, utils.ReplicatorSv1GetTrend, &utils.TenantIDWithAPIOpts{ TenantID: &utils.TenantID{Tenant: tenant, ID: id}, APIOpts: utils.GenerateDBItemOpts(itm.APIKey, itm.RouteID, utils.EmptyString, utils.FirstNonEmpty(config.CgrConfig().DataDbCfg().RmtConnID, config.CgrConfig().GeneralCfg().NodeID)), - }, &tr); err == nil { - err = dm.dataDB.SetTrendDrv(tr) + }, &tr); err != nil { + err = utils.CastRPCErr(err) + if err != utils.ErrNotFound { // RPC error + return + } + } else if err = dm.dataDB.SetTrendDrv(ctx, tr); err != nil { + return } } - if err != nil { - err = utils.CastRPCErr(err) - if err == utils.ErrNotFound && cacheWrite { - if errCh := Cache.Set(context.Background(), utils.CacheTrends, tntID, nil, nil, - cacheCommit(transactionID), transactionID); errCh != nil { - return nil, errCh + // have Trend or ErrNotFound + if err == utils.ErrNotFound { + if cacheWrite { + if errCache := Cache.Set(ctx, utils.CacheTrends, tntID, nil, nil, + cacheCommit(transactionID), transactionID); errCache != nil { + return nil, errCache } } - return nil, err + return } } + if err = tr.uncompress(dm.ms); err != nil { + return nil, err + } if cacheWrite { - if errCh := Cache.Set(context.Background(), utils.CacheTrends, tntID, tr, nil, + if errCh := Cache.Set(ctx, utils.CacheTrends, tntID, tr, nil, cacheCommit(transactionID), transactionID); errCh != nil { return nil, errCh } } + return } // SetTrend stores Trend in dataDB -func (dm *DataManager) SetTrend(tr *Trend) (err error) { +func (dm *DataManager) SetTrend(ctx *context.Context, tr *Trend) (err error) { if dm == nil { return utils.ErrNoDatabaseConn } - if err = dm.DataDB().SetTrendDrv(tr); err != nil { + if dm.dataDB.GetStorageType() != utils.MetaInternal { + if err = tr.compress(dm.ms); err != nil { + return + } + } + if err = dm.DataDB().SetTrendDrv(ctx, tr); err != nil { return } if itm := config.CgrConfig().DataDbCfg().Items[utils.MetaTrends]; itm.Replicate { - err = replicate(context.Background(), dm.connMgr, config.CgrConfig().DataDbCfg().RplConns, + if err = replicate(ctx, dm.connMgr, config.CgrConfig().DataDbCfg().RplConns, config.CgrConfig().DataDbCfg().RplFiltered, utils.TrendPrefix, tr.TenantID(), // this are used to get the host IDs from cache utils.ReplicatorSv1SetTrend, &TrendWithAPIOpts{ Trend: tr, APIOpts: utils.GenerateDBItemOpts(itm.APIKey, itm.RouteID, - config.CgrConfig().DataDbCfg().RplCache, utils.EmptyString)}) + config.CgrConfig().DataDbCfg().RplCache, utils.EmptyString)}); err != nil { + return + } } return } // RemoveTrend removes the stored Trend -func (dm *DataManager) RemoveTrend(tenant, id string) (err error) { +func (dm *DataManager) RemoveTrend(ctx *context.Context, tenant, id string) (err error) { if dm == nil { return utils.ErrNoDatabaseConn } - if err = dm.DataDB().RemoveTrendDrv(tenant, id); err != nil { + if err = dm.DataDB().RemoveTrendDrv(ctx, tenant, id); err != nil { return } if itm := config.CgrConfig().DataDbCfg().Items[utils.MetaTrends]; itm.Replicate { - replicate(context.Background(), dm.connMgr, config.CgrConfig().DataDbCfg().RplConns, + replicate(ctx, dm.connMgr, config.CgrConfig().DataDbCfg().RplConns, config.CgrConfig().DataDbCfg().RplFiltered, utils.TrendPrefix, utils.ConcatenatedKey(tenant, id), // this are used to get the host IDs from cache utils.ReplicatorSv1RemoveTrend, @@ -1119,6 +1072,147 @@ func (dm *DataManager) RemoveTrend(tenant, id string) (err error) { return } +func (dm *DataManager) GetTrendProfile(ctx *context.Context, tenant, id string, cacheRead, cacheWrite bool, + transactionID string) (trp *TrendProfile, err error) { + tntID := utils.ConcatenatedKey(tenant, id) + if cacheRead { + if x, ok := Cache.Get(utils.CacheTrendProfiles, tntID); ok { + if x == nil { + return nil, utils.ErrNotFound + } + return x.(*TrendProfile), nil + } + } + if dm == nil { + err = utils.ErrNoDatabaseConn + return + } + trp, err = dm.dataDB.GetTrendProfileDrv(ctx, tenant, id) + if err != nil { + if itm := config.CgrConfig().DataDbCfg().Items[utils.MetaTrendProfiles]; err == utils.ErrNotFound && itm.Remote { + if err = dm.connMgr.Call(ctx, config.CgrConfig().DataDbCfg().RmtConns, + utils.ReplicatorSv1GetTrendProfile, + &utils.TenantIDWithAPIOpts{ + TenantID: &utils.TenantID{Tenant: tenant, ID: id}, + APIOpts: utils.GenerateDBItemOpts(itm.APIKey, itm.RouteID, utils.EmptyString, + utils.FirstNonEmpty(config.CgrConfig().DataDbCfg().RmtConnID, + config.CgrConfig().GeneralCfg().NodeID)), + }, &trp); err == nil { + err = dm.dataDB.SetTrendProfileDrv(ctx, trp) + } + } + if err != nil { + err = utils.CastRPCErr(err) + if err == utils.ErrNotFound && cacheWrite { + if errCh := Cache.Set(ctx, utils.CacheTrendProfiles, tntID, nil, nil, + cacheCommit(transactionID), transactionID); errCh != nil { + return nil, errCh + } + } + return nil, err + } + } + if cacheWrite { + if errCh := Cache.Set(ctx, utils.CacheTrendProfiles, tntID, trp, nil, + cacheCommit(transactionID), transactionID); errCh != nil { + return nil, errCh + } + } + return +} + +func (dm *DataManager) GetTrendProfileIDs(ctx *context.Context, tenants []string) (tps map[string][]string, err error) { + prfx := utils.TrendProfilePrefix + var keys []string + if len(tenants) == 0 { + keys, err = dm.dataDB.GetKeysForPrefix(ctx, prfx) + if err != nil { + return + } + } else { + for _, tenant := range tenants { + var tntkeys []string + tntPrfx := prfx + tenant + utils.ConcatenatedKeySep + tntkeys, err = dm.dataDB.GetKeysForPrefix(ctx, tntPrfx) + if err != nil { + return + } + keys = append(keys, tntkeys...) + } + } + // if len(keys) == 0 { + // return nil, utils.ErrNotFound + // } + + tps = make(map[string][]string) + for _, key := range keys { + indx := strings.Index(key, utils.ConcatenatedKeySep) + tenant := key[len(utils.TrendProfilePrefix):indx] + id := key[indx+1:] + tps[tenant] = append(tps[tenant], id) + } + return +} + +func (dm *DataManager) SetTrendProfile(ctx *context.Context, trp *TrendProfile) (err error) { + if dm == nil { + return utils.ErrNoDatabaseConn + } + oldTrd, err := dm.GetTrendProfile(ctx, trp.Tenant, trp.ID, true, false, utils.NonTransactional) + if err != nil && err != utils.ErrNotFound { + return err + } + if err = dm.DataDB().SetTrendProfileDrv(ctx, trp); err != nil { + return err + } + if itm := config.CgrConfig().DataDbCfg().Items[utils.MetaTrendProfiles]; itm.Replicate { + err = replicate(ctx, dm.connMgr, config.CgrConfig().DataDbCfg().RplConns, + config.CgrConfig().DataDbCfg().RplFiltered, + utils.TrendProfilePrefix, trp.TenantID(), + utils.ReplicatorSv1SetTrendProfile, + &TrendProfileWithAPIOpts{ + TrendProfile: trp, + APIOpts: utils.GenerateDBItemOpts(itm.APIKey, itm.RouteID, + config.CgrConfig().DataDbCfg().RplCache, utils.EmptyString)}) + } + if oldTrd == nil || + oldTrd.QueueLength != trp.QueueLength || + oldTrd.Schedule != trp.Schedule { + if err = dm.SetTrend(ctx, NewTrendFromProfile(trp)); err != nil { + return + } + } + return +} + +func (dm *DataManager) RemoveTrendProfile(ctx *context.Context, tenant, id string) (err error) { + if dm == nil { + return utils.ErrNoDatabaseConn + } + oldTrs, err := dm.GetTrendProfile(ctx, tenant, id, true, false, utils.NonTransactional) + if err != nil && err != utils.ErrNotFound { + return err + } + + if err = dm.DataDB().RemTrendProfileDrv(ctx, tenant, id); err != nil { + return + } + if oldTrs == nil { + return utils.ErrNotFound + } + if itm := config.CgrConfig().DataDbCfg().Items[utils.MetaRankingProfiles]; itm.Replicate { + replicate(ctx, dm.connMgr, config.CgrConfig().DataDbCfg().RplConns, + config.CgrConfig().DataDbCfg().RplFiltered, + utils.TrendProfilePrefix, utils.ConcatenatedKey(tenant, id), // this are used to get the host IDs from cache + utils.ReplicatorSv1RemoveTrendProfile, + &utils.TenantIDWithAPIOpts{ + TenantID: &utils.TenantID{Tenant: tenant, ID: id}, + APIOpts: utils.GenerateDBItemOpts(itm.APIKey, itm.RouteID, + config.CgrConfig().DataDbCfg().RplCache, utils.EmptyString)}) + } + return dm.RemoveTrend(ctx, tenant, id) +} + func (dm *DataManager) GetRankingProfile(ctx *context.Context, tenant, id string, cacheRead, cacheWrite bool, transactionID string) (rgp *RankingProfile, err error) { tntID := utils.ConcatenatedKey(tenant, id) if cacheRead { @@ -1150,7 +1244,7 @@ func (dm *DataManager) GetRankingProfile(ctx *context.Context, tenant, id string if err != nil { err = utils.CastRPCErr(err) if err == utils.ErrNotFound && cacheWrite { - if errCh := Cache.Set(context.Background(), utils.CacheRankingProfiles, tntID, nil, nil, + if errCh := Cache.Set(ctx, utils.CacheRankingProfiles, tntID, nil, nil, cacheCommit(transactionID), transactionID); errCh != nil { return nil, errCh } @@ -1159,7 +1253,7 @@ func (dm *DataManager) GetRankingProfile(ctx *context.Context, tenant, id string } } if cacheWrite { - if errCh := Cache.Set(context.Background(), utils.CacheRankingProfiles, tntID, rgp, nil, + if errCh := Cache.Set(ctx, utils.CacheRankingProfiles, tntID, rgp, nil, cacheCommit(transactionID), transactionID); errCh != nil { return nil, errCh } @@ -1167,23 +1261,65 @@ func (dm *DataManager) GetRankingProfile(ctx *context.Context, tenant, id string return } -func (dm *DataManager) SetRankingProfile(ctx *context.Context, sgp *RankingProfile) (err error) { +func (dm *DataManager) GetRankingProfileIDs(ctx *context.Context, tenants []string) (rns map[string][]string, err error) { + prfx := utils.RankingProfilePrefix + var keys []string + if len(tenants) == 0 { + keys, err = dm.dataDB.GetKeysForPrefix(ctx, prfx) + if err != nil { + return + } + } else { + for _, tenant := range tenants { + var tntkeys []string + tntPrfx := prfx + tenant + utils.ConcatenatedKeySep + tntkeys, err = dm.dataDB.GetKeysForPrefix(ctx, tntPrfx) + if err != nil { + return + } + keys = append(keys, tntkeys...) + } + } + // if len(keys) == 0 { + // return nil, utils.ErrNotFound + // } + rns = make(map[string][]string) + for _, key := range keys { + indx := strings.Index(key, utils.ConcatenatedKeySep) + tenant := key[len(utils.RankingProfilePrefix):indx] + id := key[indx+1:] + rns[tenant] = append(rns[tenant], id) + } + return +} + +func (dm *DataManager) SetRankingProfile(ctx *context.Context, rnp *RankingProfile) (err error) { if dm == nil { return utils.ErrNoDatabaseConn } - if err = dm.DataDB().SetRankingProfileDrv(ctx, sgp); err != nil { + oldRnk, err := dm.GetRankingProfile(ctx, rnp.Tenant, rnp.ID, true, false, utils.NonTransactional) + if err != nil && err != utils.ErrNotFound { + return err + } + if err = dm.DataDB().SetRankingProfileDrv(ctx, rnp); err != nil { return } if itm := config.CgrConfig().DataDbCfg().Items[utils.MetaRankingProfiles]; itm.Replicate { - err = replicate(context.Background(), dm.connMgr, config.CgrConfig().DataDbCfg().RplConns, + err = replicate(ctx, dm.connMgr, config.CgrConfig().DataDbCfg().RplConns, config.CgrConfig().DataDbCfg().RplFiltered, - utils.RankingProfilePrefix, sgp.TenantID(), + utils.RankingProfilePrefix, rnp.TenantID(), utils.ReplicatorSv1SetRankingProfile, &RankingProfileWithAPIOpts{ - RankingProfile: sgp, + RankingProfile: rnp, APIOpts: utils.GenerateDBItemOpts(itm.APIKey, itm.RouteID, config.CgrConfig().DataDbCfg().RplCache, utils.EmptyString)}) } + if oldRnk == nil || oldRnk.Sorting != rnp.Sorting || + oldRnk.Schedule != rnp.Schedule { + if err = dm.SetRanking(ctx, NewRankingFromProfile(rnp)); err != nil { + return + } + } return } @@ -1202,7 +1338,7 @@ func (dm *DataManager) RemoveRankingProfile(ctx *context.Context, tenant, id str return utils.ErrNotFound } if itm := config.CgrConfig().DataDbCfg().Items[utils.MetaRankingProfiles]; itm.Replicate { - replicate(context.Background(), dm.connMgr, config.CgrConfig().DataDbCfg().RplConns, + replicate(ctx, dm.connMgr, config.CgrConfig().DataDbCfg().RplConns, config.CgrConfig().DataDbCfg().RplFiltered, utils.RankingProfilePrefix, utils.ConcatenatedKey(tenant, id), // this are used to get the host IDs from cache utils.ReplicatorSv1RemoveRankingProfile, @@ -1213,6 +1349,97 @@ func (dm *DataManager) RemoveRankingProfile(ctx *context.Context, tenant, id str } return } +func (dm *DataManager) GetRanking(ctx *context.Context, tenant, id string, cacheRead, cacheWrite bool, transactionID string) (rn *Ranking, err error) { + tntID := utils.ConcatenatedKey(tenant, id) + if cacheRead { + if x, ok := Cache.Get(utils.CacheRankings, tntID); ok { + if x == nil { + return nil, utils.ErrNotFound + } + return x.(*Ranking), nil + } + } + if dm == nil { + err = utils.ErrNoDatabaseConn + return + } + if rn, err = dm.dataDB.GetRankingDrv(ctx, tenant, id); err != nil { + if err != utils.ErrNotFound { // database error + return + } + // ErrNotFound + if itm := config.CgrConfig().DataDbCfg().Items[utils.MetaRankings]; itm.Remote { + if err = dm.connMgr.Call(context.TODO(), config.CgrConfig().DataDbCfg().RmtConns, + utils.ReplicatorSv1GetRanking, &utils.TenantIDWithAPIOpts{ + TenantID: &utils.TenantID{Tenant: tenant, ID: id}, + APIOpts: utils.GenerateDBItemOpts(itm.APIKey, itm.RouteID, utils.EmptyString, + utils.FirstNonEmpty(config.CgrConfig().DataDbCfg().RmtConnID, + config.CgrConfig().GeneralCfg().NodeID)), + }, &rn); err == nil { + err = dm.dataDB.SetRankingDrv(ctx, rn) + } + } + if err != nil { + err = utils.CastRPCErr(err) + if err == utils.ErrNotFound && cacheWrite { + if errCh := Cache.Set(ctx, utils.CacheRankings, tntID, nil, nil, cacheCommit(transactionID), transactionID); errCh != nil { + return nil, errCh + } + } + return nil, err + } + if cacheWrite { + if errCh := Cache.Set(ctx, utils.CacheRankings, tntID, rn, nil, cacheCommit(transactionID), transactionID); errCh != nil { + return nil, errCh + } + } + } + return +} + +// SetRanking stores Ranking in dataDB +func (dm *DataManager) SetRanking(ctx *context.Context, rn *Ranking) (err error) { + if dm == nil { + return utils.ErrNoDatabaseConn + } + if err = dm.DataDB().SetRankingDrv(ctx, rn); err != nil { + return + } + if itm := config.CgrConfig().DataDbCfg().Items[utils.MetaRankings]; itm.Replicate { + if err = replicate(ctx, dm.connMgr, config.CgrConfig().DataDbCfg().RplConns, + config.CgrConfig().DataDbCfg().RplFiltered, + utils.RankingPrefix, rn.TenantID(), // this are used to get the host IDs from cache + utils.ReplicatorSv1SetRanking, + &RankingWithAPIOpts{ + Ranking: rn, + APIOpts: utils.GenerateDBItemOpts(itm.APIKey, itm.RouteID, + config.CgrConfig().DataDbCfg().RplCache, utils.EmptyString)}); err != nil { + return + } + } + return +} + +// RemoveRanking removes the stored Ranking +func (dm *DataManager) RemoveRanking(ctx *context.Context, tenant, id string) (err error) { + if dm == nil { + return utils.ErrNoDatabaseConn + } + if err = dm.DataDB().RemoveRankingDrv(ctx, tenant, id); err != nil { + return + } + if itm := config.CgrConfig().DataDbCfg().Items[utils.MetaRankings]; itm.Replicate { + replicate(ctx, dm.connMgr, config.CgrConfig().DataDbCfg().RplConns, + config.CgrConfig().DataDbCfg().RplFiltered, + utils.RankingPrefix, utils.ConcatenatedKey(tenant, id), // this are used to get the host IDs from cache + utils.ReplicatorSv1RemoveRanking, + &utils.TenantIDWithAPIOpts{ + TenantID: &utils.TenantID{Tenant: tenant, ID: id}, + APIOpts: utils.GenerateDBItemOpts(itm.APIKey, itm.RouteID, + config.CgrConfig().DataDbCfg().RplCache, utils.EmptyString)}) + } + return +} func (dm *DataManager) GetResource(ctx *context.Context, tenant, id string, cacheRead, cacheWrite bool, transactionID string) (rs *Resource, err error) { diff --git a/engine/librankings.go b/engine/librankings.go new file mode 100644 index 000000000..0b87081b2 --- /dev/null +++ b/engine/librankings.go @@ -0,0 +1,283 @@ +package engine + +import ( + "sort" + "strings" + "sync" + "time" + + "github.com/cgrates/cgrates/utils" +) + +type RankingProfileWithAPIOpts struct { + *RankingProfile + APIOpts map[string]any +} + +type RankingProfile struct { + Tenant string // Tenant this profile belongs to + ID string // Profile identification + Schedule string // Cron schedule this profile should run at + StatIDs []string // List of stat instances to query + MetricIDs []string // Filter out only specific metrics in reply for sorting + Sorting string // Sorting strategy. Possible values: <*asc|*desc> + SortingParameters []string // Sorting parameters: depending on sorting type, list of metric ids for now with optional true or false in case of reverse logic is desired + Stored bool // Offline storage activation for this profile + ThresholdIDs []string // List of threshold IDs to limit this Ranking to. *none to disable threshold processing for it. +} + +func (sgp *RankingProfile) TenantID() string { + return utils.ConcatenatedKey(sgp.Tenant, sgp.ID) +} + +// Clone will clone a RankingProfile +func (rkP *RankingProfile) Clone() (cln *RankingProfile) { + cln = &RankingProfile{ + Tenant: rkP.Tenant, + ID: rkP.ID, + Schedule: rkP.Schedule, + Sorting: rkP.Sorting, + } + if rkP.StatIDs != nil { + cln.StatIDs = make([]string, len(rkP.StatIDs)) + copy(cln.StatIDs, rkP.StatIDs) + } + if rkP.MetricIDs != nil { + cln.MetricIDs = make([]string, len(rkP.MetricIDs)) + copy(cln.MetricIDs, rkP.MetricIDs) + } + if rkP.SortingParameters != nil { + + cln.SortingParameters = make([]string, len(rkP.SortingParameters)) + copy(cln.SortingParameters, rkP.SortingParameters) + } + if rkP.ThresholdIDs != nil { + cln.ThresholdIDs = make([]string, len(rkP.ThresholdIDs)) + copy(cln.ThresholdIDs, rkP.ThresholdIDs) + } + return +} + +// rankingProfileLockKey returns the ID used to lock a RankingProfile with guardian +func rankingProfileLockKey(tnt, id string) string { + return utils.ConcatenatedKey(utils.CacheRankingProfiles, tnt, id) +} +func NewRankingFromProfile(rkP *RankingProfile) (rk *Ranking) { + rk = &Ranking{ + Tenant: rkP.Tenant, + ID: rkP.ID, + Sorting: rkP.Sorting, + Metrics: make(map[string]map[string]float64), + + rkPrfl: rkP, + metricIDs: utils.NewStringSet(rkP.MetricIDs), + } + if rkP.SortingParameters != nil { + rk.SortingParameters = make([]string, len(rkP.SortingParameters)) + copy(rk.SortingParameters, rkP.SortingParameters) + } + return +} + +type RankingWithAPIOpts struct { + *Ranking + APIOpts map[string]any +} + +// Ranking is one unit out of a profile +type Ranking struct { + rMux sync.RWMutex + + Tenant string + ID string + LastUpdate time.Time + Metrics map[string]map[string]float64 // map[statID]map[metricID]metricValue + Sorting string + SortingParameters []string + + SortedStatIDs []string + + rkPrfl *RankingProfile // store here the ranking profile so we can have it at hands further + metricIDs utils.StringSet // convert the metricIDs here for faster matching + +} + +func (r *Ranking) TenantID() string { + return utils.ConcatenatedKey(r.Tenant, r.ID) +} + +// asRankingSummary converts the Ranking instance into a RankingSummary one +func (rk *Ranking) asRankingSummary() (rkSm *RankingSummary) { + rkSm = &RankingSummary{ + Tenant: rk.Tenant, + ID: rk.ID, + LastUpdate: rk.LastUpdate, + } + rkSm.SortedStatIDs = make([]string, len(rk.SortedStatIDs)) + copy(rkSm.SortedStatIDs, rk.SortedStatIDs) + return +} + +type rankingSorter interface { + sortStatIDs() []string // sortStatIDs returns the sorted list of statIDs +} + +// rankingSortStats will return the list of sorted statIDs out of the sortingData map +func rankingSortStats(sortingType string, sortingParams []string, + Metrics map[string]map[string]float64) (sortedStatIDs []string, err error) { + var rnkSrtr rankingSorter + if rnkSrtr, err = newRankingSorter(sortingType, sortingParams, Metrics); err != nil { + return + } + return rnkSrtr.sortStatIDs(), nil +} + +// newRankingSorter is the constructor for various ranking sorters +// +// returns error if the sortingType is not implemented +func newRankingSorter(sortingType string, sortingParams []string, + Metrics map[string]map[string]float64) (rkStr rankingSorter, err error) { + switch sortingType { + default: + err = utils.ErrPrefixNotErrNotImplemented(sortingType) + return + case utils.MetaDesc: + return newRankingDescSorter(sortingParams, Metrics), nil + case utils.MetaAsc: + return newRankingAscSorter(sortingParams, Metrics), nil + } +} + +// newRankingDescSorter is a constructor for rankingDescSorter +func newRankingDescSorter(sortingParams []string, + Metrics map[string]map[string]float64) (rkDsrtr *rankingDescSorter) { + clnSp := make([]string, len(sortingParams)) + sPReversed := make(utils.StringSet) + for i, sP := range sortingParams { // clean the sortingParams, out of param:false or param:true definitions + sPSlc := strings.Split(sP, utils.InInFieldSep) + clnSp[i] = sPSlc[0] + if len(sPSlc) > 1 && sPSlc[1] == utils.FalseStr { + sPReversed.Add(sPSlc[0]) // param defined as param:false which should be added to reversing comparison + } + } + rkDsrtr = &rankingDescSorter{ + clnSp, + sPReversed, + Metrics, + make([]string, 0, len(Metrics))} + for statID := range rkDsrtr.Metrics { + rkDsrtr.statIDs = append(rkDsrtr.statIDs, statID) + } + return +} + +// rankingDescSorter will sort data descendent for metrics in sortingParams or random if all equal +type rankingDescSorter struct { + sMetricIDs []string + sMetricRev utils.StringSet // list of exceptios for sortingParams, reverting the sorting logic + Metrics map[string]map[string]float64 + + statIDs []string // list of keys of the Metrics +} + +// sortStatIDs implements rankingSorter interface +func (rkDsrtr *rankingDescSorter) sortStatIDs() []string { + if len(rkDsrtr.statIDs) == 0 { + return rkDsrtr.statIDs + } + sort.Slice(rkDsrtr.statIDs, func(i, j int) bool { + for _, metricID := range rkDsrtr.sMetricIDs { + val1, hasMetric1 := rkDsrtr.Metrics[rkDsrtr.statIDs[i]][metricID] + if !hasMetric1 { + return false + } + val2, hasMetric2 := rkDsrtr.Metrics[rkDsrtr.statIDs[j]][metricID] + if !hasMetric2 { + return true + } + //in case we have the same value for the current metricID we skip to the next one + if val1 == val2 { + continue + } + ret := val1 > val2 + if rkDsrtr.sMetricRev.Has(metricID) { + ret = !ret + } + return ret + } + //in case that we have the same value for all params we return randomly + return utils.BoolGenerator().RandomBool() + }) + return rkDsrtr.statIDs +} + +// newRankingAscSorter is a constructor for rankingAscSorter +func newRankingAscSorter(sortingParams []string, + Metrics map[string]map[string]float64) (rkASrtr *rankingAscSorter) { + clnSp := make([]string, len(sortingParams)) + sPReversed := make(utils.StringSet) + for i, sP := range sortingParams { // clean the sortingParams, out of param:false or param:true definitions + sPSlc := strings.Split(sP, utils.InInFieldSep) + clnSp[i] = sPSlc[0] + if len(sPSlc) > 1 && sPSlc[1] == utils.FalseStr { + sPReversed.Add(sPSlc[0]) // param defined as param:false which should be added to reversing comparison + } + } + rkASrtr = &rankingAscSorter{ + clnSp, + sPReversed, + Metrics, + make([]string, 0, len(Metrics))} + for statID := range rkASrtr.Metrics { + rkASrtr.statIDs = append(rkASrtr.statIDs, statID) + } + return +} + +// rankingAscSorter will sort data ascendent for metrics in sortingParams or randomly if all equal +type rankingAscSorter struct { + sMetricIDs []string + sMetricRev utils.StringSet // list of exceptios for sortingParams, reverting the sorting logic + Metrics map[string]map[string]float64 + + statIDs []string // list of keys of the Metrics +} + +// sortStatIDs implements rankingSorter interface +func (rkASrtr *rankingAscSorter) sortStatIDs() []string { + if len(rkASrtr.statIDs) == 0 { + return rkASrtr.statIDs + } + sort.Slice(rkASrtr.statIDs, func(i, j int) bool { + for _, metricID := range rkASrtr.sMetricIDs { + val1, hasMetric1 := rkASrtr.Metrics[rkASrtr.statIDs[i]][metricID] + if !hasMetric1 { + return false + } + val2, hasMetric2 := rkASrtr.Metrics[rkASrtr.statIDs[j]][metricID] + if !hasMetric2 { + return true + } + //in case we have the same value for the current metricID we skip to the next one + if val1 == val2 { + continue + } + ret := val2 > val1 + if rkASrtr.sMetricRev.Has(metricID) { + ret = !ret // reversed logic in case of metric:false in params + } + return ret + } + //in case that we have the same value for all params we return randomly + return utils.BoolGenerator().RandomBool() + }) + return rkASrtr.statIDs +} + +// RankingSummary is the event sent to TrendS and EEs +type RankingSummary struct { + Tenant string + ID string + LastUpdate time.Time + SortedStatIDs []string +} diff --git a/engine/libtest.go b/engine/libtest.go index 989e0b4e1..2b605a28d 100644 --- a/engine/libtest.go +++ b/engine/libtest.go @@ -288,6 +288,7 @@ func GetDefaultEmptyCacheStats() map[string]*ltcache.CacheStats { utils.CacheStatQueues: {}, utils.CacheSTIR: {}, utils.CacheRankingProfiles: {}, + utils.CacheRankings: {}, utils.CacheRouteFilterIndexes: {}, utils.CacheRouteProfiles: {}, utils.CacheThresholdFilterIndexes: {}, @@ -358,7 +359,7 @@ type TestEngine struct { func (ng TestEngine) Run(t *testing.T, extraFlags ...string) (*birpc.Client, *config.CGRConfig) { t.Helper() cfg := parseCfg(t, ng.ConfigPath, ng.ConfigJSON, ng.DBCfg) - flushDBs(t, cfg, !ng.PreserveDataDB, !ng.PreserveStorDB) + FlushDBs(t, cfg, !ng.PreserveDataDB, !ng.PreserveStorDB) if ng.PreStartHook != nil { ng.PreStartHook(t, cfg) } @@ -467,7 +468,7 @@ func loadCSVs(t *testing.T, tpPath string, csvFiles map[string]string) { } // flushDBs resets the databases specified in the configuration if the corresponding flags are true. -func flushDBs(t *testing.T, cfg *config.CGRConfig, flushDataDB, flushStorDB bool) { +func FlushDBs(t *testing.T, cfg *config.CGRConfig, flushDataDB, flushStorDB bool) { t.Helper() if flushDataDB { if err := InitDataDB(cfg); err != nil { diff --git a/engine/libtrends.go b/engine/libtrends.go index 8b3686d2c..41e9ae78d 100644 --- a/engine/libtrends.go +++ b/engine/libtrends.go @@ -20,9 +20,11 @@ package engine import ( "math" + "slices" "sync" "time" + "github.com/cgrates/cgrates/config" "github.com/cgrates/cgrates/utils" ) @@ -80,32 +82,147 @@ func (srp *TrendProfile) TenantID() string { return utils.ConcatenatedKey(srp.Tenant, srp.ID) } -type TrendProfilesAPI struct { - Tenant string - TpIDs []string -} - type TrendWithAPIOpts struct { *Trend APIOpts map[string]any } +// NewTrendFromProfile is a constructor for an empty trend out of it's profile +func NewTrendFromProfile(tP *TrendProfile) *Trend { + return &Trend{ + Tenant: tP.Tenant, + ID: tP.ID, + RunTimes: make([]time.Time, 0), + Metrics: make(map[time.Time]map[string]*MetricWithTrend), + + tPrfl: tP, + } +} + // Trend is the unit matched by filters type Trend struct { - tMux *sync.RWMutex + tMux sync.RWMutex Tenant string ID string RunTimes []time.Time Metrics map[time.Time]map[string]*MetricWithTrend - CompressedMetrics []byte // if populated, Metrics will be empty + CompressedMetrics []byte // if populated, Metrics and RunTimes will be emty // indexes help faster processing mLast map[string]time.Time // last time a metric was present - mCounts map[string]int // number of times a metrics is present in Metrics + mCounts map[string]int // number of times a metric is present in Metrics mTotals map[string]float64 // cached sum, used for average calculations - tP *TrendProfile // cache here the settings + tPrfl *TrendProfile // store here the trend profile so we can have it at hands further + +} + +func (t *Trend) Clone() (tC *Trend) { + return +} + +// AsTrendSummary transforms the trend into TrendSummary +func (t *Trend) asTrendSummary() (ts *TrendSummary) { + ts = &TrendSummary{ + Tenant: t.Tenant, + ID: t.ID, + Metrics: make(map[string]*MetricWithTrend), + } + if len(t.RunTimes) != 0 { + ts.Time = t.RunTimes[len(t.RunTimes)-1] + for mID, mWt := range t.Metrics[ts.Time] { + ts.Metrics[mID] = &MetricWithTrend{ + ID: mWt.ID, + Value: mWt.Value, + TrendGrowth: mWt.TrendGrowth, + TrendLabel: mWt.TrendLabel, + } + } + } + return +} + +func (t *Trend) compress(ms utils.Marshaler) (err error) { + if config.CgrConfig().TrendSCfg().StoreUncompressedLimit > len(t.RunTimes) { + return + } + t.CompressedMetrics, err = ms.Marshal(t.Metrics) + if err != nil { + return + } + t.Metrics = nil + t.RunTimes = nil + return nil +} + +func (t *Trend) uncompress(ms utils.Marshaler) (err error) { + if t == nil || t.CompressedMetrics == nil { + return + } + + err = ms.Unmarshal(t.CompressedMetrics, &t.Metrics) + if err != nil { + return + } + t.CompressedMetrics = []byte{} + t.RunTimes = make([]time.Time, len(t.Metrics)) + i := 0 + for key := range t.Metrics { + t.RunTimes[i] = key + i++ + } + slices.SortFunc(t.RunTimes, func(a, b time.Time) int { + return a.Compare(b) + }) + return +} + +// Compile is used to initialize or cleanup the Trend +// +// thread safe since it should be used close to source +func (t *Trend) Compile(cleanTtl time.Duration, qLength int) { + t.cleanup(cleanTtl, qLength) + if len(t.mTotals) == 0 { // indexes were not yet built + t.computeIndexes() + } +} + +// cleanup will clean stale data out of +func (t *Trend) cleanup(ttl time.Duration, qLength int) (altered bool) { + if ttl >= 0 { + expTime := time.Now().Add(-ttl) + var expIdx *int + for i, rT := range t.RunTimes { + if rT.After(expTime) { + continue + } + expIdx = &i + delete(t.Metrics, rT) + } + if expIdx != nil { + if len(t.RunTimes)-1 == *expIdx { + t.RunTimes = make([]time.Time, 0) + } else { + t.RunTimes = t.RunTimes[*expIdx+1:] + } + altered = true + } + } + + diffLen := len(t.RunTimes) - qLength + if qLength > 0 && diffLen > 0 { + var rmTms []time.Time + rmTms, t.RunTimes = t.RunTimes[:diffLen], t.RunTimes[diffLen:] + for _, rmTm := range rmTms { + delete(t.Metrics, rmTm) + } + altered = true + } + if altered { + t.computeIndexes() + } + return } // computeIndexes should be called after each retrieval from DB @@ -129,7 +246,7 @@ func (t *Trend) indexesAppendMetric(mWt *MetricWithTrend, rTime time.Time) { // getTrendGrowth returns the percentage growth for a specific metric // -// correlation parameter will define whether the comparison is against last or average value +// @correlation parameter will define whether the comparison is against last or average value // errors in case of previous func (t *Trend) getTrendGrowth(mID string, mVal float64, correlation string, roundDec int) (tG float64, err error) { var prevVal float64 @@ -147,9 +264,9 @@ func (t *Trend) getTrendGrowth(mID string, mVal float64, correlation string, rou default: return -1.0, utils.ErrCorrelationUndefined } - diffVal := mVal - prevVal - return utils.Round(diffVal/100, roundDec, utils.MetaRoundingMiddle), nil + diffVal := mVal - prevVal + return utils.Round(diffVal*100/prevVal, roundDec, utils.MetaRoundingMiddle), nil } // getTrendLabel identifies the trend label for the instant value of the metric @@ -181,3 +298,11 @@ type MetricWithTrend struct { func (tr *Trend) TenantID() string { return utils.ConcatenatedKey(tr.Tenant, tr.ID) } + +// TrendSummary represents the last trend computed +type TrendSummary struct { + Tenant string + ID string + Time time.Time + Metrics map[string]*MetricWithTrend +} diff --git a/engine/libtrends_test.go b/engine/libtrends_test.go index ce82d3532..7d1c09a6f 100644 --- a/engine/libtrends_test.go +++ b/engine/libtrends_test.go @@ -21,7 +21,6 @@ package engine import ( "errors" "reflect" - "sync" "testing" "time" @@ -217,7 +216,7 @@ func TestTrendTenantID(t *testing.T) { "metric1": 2.5, "metric2": 2.0, }, - tP: &TrendProfile{ + tPrfl: &TrendProfile{ Tenant: "cgrates.org", ID: "trendProfileID", Schedule: "0 * * * *", @@ -247,8 +246,8 @@ func TestTrendTenantID(t *testing.T) { t.Errorf("Expected 2 metrics time entries, but got %d", len(trend.Metrics)) } - if trend.tP.QueueLength != 10 { - t.Errorf("Expected QueueLength 10, but got %d", trend.tP.QueueLength) + if trend.tPrfl.QueueLength != 10 { + t.Errorf("Expected QueueLength 10, but got %d", trend.tPrfl.QueueLength) } } @@ -324,7 +323,6 @@ func TestGetTrendLabel(t *testing.T) { func TestGetTrendGrowth(t *testing.T) { trend := Trend{ - tMux: &sync.RWMutex{}, mLast: map[string]time.Time{}, Metrics: map[time.Time]map[string]*MetricWithTrend{}, mTotals: map[string]float64{}, @@ -351,9 +349,8 @@ func TestGetTrendGrowth(t *testing.T) { } got, err := trend.getTrendGrowth("metric1", 100, utils.MetaLast, 2) - expected := utils.Round(20.0/100, 2, utils.MetaRoundingMiddle) - if err != nil || !reflect.DeepEqual(got, expected) { - t.Errorf("Mismatch for MetaLast correlation. Got: %v, expected: %v", got, expected) + if err != nil || got != 25.0 { + t.Errorf("Mismatch for MetaLast correlation. Got: %v, expected: %v", got, 25.0) } trend.mTotals = map[string]float64{ @@ -364,9 +361,8 @@ func TestGetTrendGrowth(t *testing.T) { } got, err = trend.getTrendGrowth("metric1", 120, utils.MetaAverage, 2) - expected = utils.Round(20.0/100, 2, utils.MetaRoundingMiddle) - if err != nil || !reflect.DeepEqual(got, expected) { - t.Errorf("Mismatch for MetaAverage correlation. Got: %v, expected: %v", got, expected) + if err != nil || got != 20.0 { + t.Errorf("Mismatch for MetaAverage correlation. Got: %v, expected: %v", got, 20.0) } _, err = trend.getTrendGrowth("metric1", 100, "invalidCorrelation", 2) diff --git a/engine/model_helpers.go b/engine/model_helpers.go index 832485a6f..d8bb5bb8d 100644 --- a/engine/model_helpers.go +++ b/engine/model_helpers.go @@ -525,8 +525,8 @@ func StatQueueProfileToAPI(st *StatQueueProfile) (tpST *utils.TPStatProfile) { type RankingMdls []*RankingMdl func (tps RankingMdls) CSVHeader() (result []string) { - return []string{"#" + utils.Tenant, utils.ID, utils.StatIDs, - utils.MetricIDs, utils.Sorting, utils.SortingParameters, + return []string{"#" + utils.Tenant, utils.ID, utils.Schedule, utils.StatIDs, + utils.MetricIDs, utils.Sorting, utils.SortingParameters, utils.Stored, utils.ThresholdIDs} } @@ -534,25 +534,30 @@ func (models RankingMdls) AsTPRanking() (result []*utils.TPRankingProfile) { thresholdMap := make(map[string]utils.StringSet) metricsMap := make(map[string]utils.StringSet) sortingParameterMap := make(map[string]utils.StringSet) + sortingParameterSlice := make(map[string][]string) statsMap := make(map[string]utils.StringSet) - msg := make(map[string]*utils.TPRankingProfile) + mrg := make(map[string]*utils.TPRankingProfile) for _, model := range models { key := &utils.TenantID{Tenant: model.Tenant, ID: model.ID} - sg, found := msg[key.TenantID()] + rg, found := mrg[key.TenantID()] if !found { - sg = &utils.TPRankingProfile{ - Tenant: model.Tenant, - TPid: model.Tpid, - ID: model.ID, - QueryInterval: model.QueryInterval, - Sorting: model.Sorting, + rg = &utils.TPRankingProfile{ + Tenant: model.Tenant, + TPid: model.Tpid, + ID: model.ID, + Schedule: model.Schedule, + Sorting: model.Sorting, + Stored: model.Stored, } } - if model.QueryInterval != utils.EmptyString { - sg.QueryInterval = model.QueryInterval + if model.Schedule != utils.EmptyString { + rg.Schedule = model.Schedule } if model.Sorting != utils.EmptyString { - sg.QueryInterval = model.QueryInterval + rg.Sorting = model.Sorting + } + if model.Stored { + rg.Stored = model.Stored } if model.StatIDs != utils.EmptyString { if _, has := statsMap[key.TenantID()]; !has { @@ -569,8 +574,15 @@ func (models RankingMdls) AsTPRanking() (result []*utils.TPRankingProfile) { if model.SortingParameters != utils.EmptyString { if _, has := sortingParameterMap[key.TenantID()]; !has { sortingParameterMap[key.TenantID()] = make(utils.StringSet) + sortingParameterSlice[key.TenantID()] = make([]string, 0) + } + spltSl := strings.Split(model.SortingParameters, utils.InfieldSep) + for _, splt := range spltSl { + if _, has := sortingParameterMap[key.TenantID()][splt]; !has { + sortingParameterMap[key.TenantID()].Add(splt) + sortingParameterSlice[key.TenantID()] = append(sortingParameterSlice[key.TenantID()], splt) + } } - sortingParameterMap[key.TenantID()].AddSlice(strings.Split(model.SortingParameters, utils.InfieldSep)) } if model.MetricIDs != utils.EmptyString { if _, has := metricsMap[key.TenantID()]; !has { @@ -578,47 +590,48 @@ func (models RankingMdls) AsTPRanking() (result []*utils.TPRankingProfile) { } metricsMap[key.TenantID()].AddSlice(strings.Split(model.MetricIDs, utils.InfieldSep)) } - msg[key.TenantID()] = sg + mrg[key.TenantID()] = rg } - result = make([]*utils.TPRankingProfile, len(msg)) + result = make([]*utils.TPRankingProfile, len(mrg)) i := 0 - for tntID, sg := range msg { - result[i] = sg + for tntID, rg := range mrg { + result[i] = rg result[i].StatIDs = statsMap[tntID].AsSlice() result[i].MetricIDs = metricsMap[tntID].AsSlice() - result[i].SortingParameters = sortingParameterMap[tntID].AsSlice() + result[i].SortingParameters = sortingParameterSlice[tntID] result[i].ThresholdIDs = thresholdMap[tntID].AsOrderedSlice() i++ } return } -func APItoModelTPRanking(tpSG *utils.TPRankingProfile) (mdls RankingMdls) { - if tpSG == nil { +func APItoModelTPRanking(tpRG *utils.TPRankingProfile) (mdls RankingMdls) { + if tpRG == nil { return } - if len(tpSG.StatIDs) == 0 { + if len(tpRG.StatIDs) == 0 { mdl := &RankingMdl{ - Tpid: tpSG.TPid, - Tenant: tpSG.Tenant, - ID: tpSG.ID, - QueryInterval: tpSG.QueryInterval, - Sorting: tpSG.Sorting, + Tpid: tpRG.TPid, + Tenant: tpRG.Tenant, + ID: tpRG.ID, + Schedule: tpRG.Schedule, + Sorting: tpRG.Sorting, + Stored: tpRG.Stored, } - for i, val := range tpSG.ThresholdIDs { + for i, val := range tpRG.ThresholdIDs { if i != 0 { mdl.ThresholdIDs += utils.InfieldSep } mdl.ThresholdIDs += val } - for i, metric := range tpSG.MetricIDs { + for i, metric := range tpRG.MetricIDs { if i != 0 { mdl.MetricIDs += utils.InfieldSep } mdl.MetricIDs += metric } - for i, sorting := range tpSG.SortingParameters { + for i, sorting := range tpRG.SortingParameters { if i != 0 { mdl.SortingParameters += utils.InfieldSep } @@ -627,28 +640,28 @@ func APItoModelTPRanking(tpSG *utils.TPRankingProfile) (mdls RankingMdls) { mdls = append(mdls, mdl) } - for i, stat := range tpSG.StatIDs { + for i, stat := range tpRG.StatIDs { mdl := &RankingMdl{ - Tpid: tpSG.TPid, - Tenant: tpSG.Tenant, - ID: tpSG.ID, + Tpid: tpRG.TPid, + Tenant: tpRG.Tenant, + ID: tpRG.ID, } if i == 0 { - mdl.QueryInterval = tpSG.QueryInterval - mdl.Sorting = tpSG.Sorting - for i, val := range tpSG.ThresholdIDs { + mdl.Schedule = tpRG.Schedule + mdl.Sorting = tpRG.Sorting + for i, val := range tpRG.ThresholdIDs { if i != 0 { mdl.ThresholdIDs += utils.InfieldSep } mdl.ThresholdIDs += val } - for i, metric := range tpSG.MetricIDs { + for i, metric := range tpRG.MetricIDs { if i != 0 { mdl.MetricIDs += utils.InfieldSep } mdl.MetricIDs += metric } - for i, sorting := range tpSG.SortingParameters { + for i, sorting := range tpRG.SortingParameters { if i != 0 { mdl.SortingParameters += utils.InfieldSep } @@ -661,44 +674,41 @@ func APItoModelTPRanking(tpSG *utils.TPRankingProfile) (mdls RankingMdls) { return } -func APItoRanking(tpSG *utils.TPRankingProfile) (sg *RankingProfile, err error) { - sg = &RankingProfile{ - Tenant: tpSG.Tenant, - ID: tpSG.ID, - StatIDs: make([]string, len(tpSG.StatIDs)), - MetricIDs: make([]string, len(tpSG.MetricIDs)), - Sorting: tpSG.Sorting, - SortingParameters: make([]string, len(tpSG.SortingParameters)), - ThresholdIDs: make([]string, len(tpSG.ThresholdIDs)), +func APItoRanking(tpRG *utils.TPRankingProfile) (rg *RankingProfile, err error) { + rg = &RankingProfile{ + Tenant: tpRG.Tenant, + ID: tpRG.ID, + Schedule: tpRG.Schedule, + Sorting: tpRG.Sorting, + Stored: tpRG.Stored, + StatIDs: make([]string, len(tpRG.StatIDs)), + MetricIDs: make([]string, len(tpRG.MetricIDs)), + SortingParameters: make([]string, len(tpRG.SortingParameters)), + ThresholdIDs: make([]string, len(tpRG.ThresholdIDs)), } - - if tpSG.QueryInterval != utils.EmptyString { - if sg.QueryInterval, err = utils.ParseDurationWithNanosecs(tpSG.QueryInterval); err != nil { - return nil, err - } - } - copy(sg.StatIDs, tpSG.StatIDs) - copy(sg.ThresholdIDs, tpSG.ThresholdIDs) - copy(sg.MetricIDs, tpSG.MetricIDs) - return sg, nil + copy(rg.StatIDs, tpRG.StatIDs) + copy(rg.ThresholdIDs, tpRG.ThresholdIDs) + copy(rg.SortingParameters, tpRG.SortingParameters) + copy(rg.MetricIDs, tpRG.MetricIDs) + return rg, nil } -func RankingProfileToAPI(sg *RankingProfile) (tpSG *utils.TPRankingProfile) { - tpSG = &utils.TPRankingProfile{ - Tenant: sg.Tenant, - ID: sg.ID, - StatIDs: make([]string, len(sg.StatIDs)), - MetricIDs: make([]string, len(sg.MetricIDs)), - SortingParameters: make([]string, len(sg.SortingParameters)), - ThresholdIDs: make([]string, len(sg.ThresholdIDs)), +func RankingProfileToAPI(rg *RankingProfile) (tpRG *utils.TPRankingProfile) { + tpRG = &utils.TPRankingProfile{ + Tenant: rg.Tenant, + ID: rg.ID, + Schedule: rg.Schedule, + Sorting: rg.Sorting, + Stored: rg.Stored, + StatIDs: make([]string, len(rg.StatIDs)), + MetricIDs: make([]string, len(rg.MetricIDs)), + SortingParameters: make([]string, len(rg.SortingParameters)), + ThresholdIDs: make([]string, len(rg.ThresholdIDs)), } - if sg.QueryInterval != time.Duration(0) { - tpSG.QueryInterval = sg.QueryInterval.String() - } - copy(tpSG.StatIDs, sg.StatIDs) - copy(tpSG.ThresholdIDs, sg.ThresholdIDs) - copy(tpSG.MetricIDs, sg.MetricIDs) - copy(tpSG.SortingParameters, sg.SortingParameters) + copy(tpRG.StatIDs, rg.StatIDs) + copy(tpRG.ThresholdIDs, rg.ThresholdIDs) + copy(tpRG.MetricIDs, rg.MetricIDs) + copy(tpRG.SortingParameters, rg.SortingParameters) return } diff --git a/engine/models.go b/engine/models.go index f7e225bca..da1062bdd 100644 --- a/engine/models.go +++ b/engine/models.go @@ -75,12 +75,13 @@ type RankingMdl struct { Tpid string Tenant string `index:"0" re:".*"` ID string `index:"1" re:".*"` - QueryInterval string `index:"2" re:".*"` + Schedule string `index:"2" re:".*"` StatIDs string `index:"3" re:".*"` MetricIDs string `index:"4" re:".*"` Sorting string `index:"5" re:".*"` SortingParameters string `index:"6" re:".*"` - ThresholdIDs string `index:"7" re:".*"` + Stored bool `index:"7" re:".*"` + ThresholdIDs string `index:"8" re:".*"` CreatedAt time.Time } diff --git a/engine/rankings.go b/engine/rankings.go index fea6ca60d..ae1e0d234 100644 --- a/engine/rankings.go +++ b/engine/rankings.go @@ -19,50 +19,518 @@ along with this program. If not, see package engine import ( + "fmt" + "runtime" + "slices" + "strings" + "sync" "time" + "github.com/cgrates/birpc/context" "github.com/cgrates/cgrates/config" "github.com/cgrates/cgrates/utils" + "github.com/cgrates/cron" ) -type RankingProfileWithAPIOpts struct { - *RankingProfile - APIOpts map[string]any -} - -type RankingProfile struct { - Tenant string - ID string - QueryInterval time.Duration - StatIDs []string - MetricIDs []string - Sorting string - SortingParameters []string - ThresholdIDs []string -} - -func (sgp *RankingProfile) TenantID() string { - return utils.ConcatenatedKey(sgp.Tenant, sgp.ID) -} - -// rankingProfileLockKey returns the ID used to lock a RankingProfile with guardian -func rankingProfileLockKey(tnt, id string) string { - return utils.ConcatenatedKey(utils.CacheRankingProfiles, tnt, id) -} - -func NewRankingService(dm *DataManager, cgrcfg *config.CGRConfig, filterS *FilterS, connMgr *ConnManager) (tS *RankingS) { +// NewRankingS is the constructor for RankingS +func NewRankingS(dm *DataManager, + connMgr *ConnManager, + filterS *FilterS, + cgrcfg *config.CGRConfig) *RankingS { return &RankingS{ - dm: dm, - cfg: cgrcfg, - fltrS: filterS, - connMgr: connMgr, + dm: dm, + connMgr: connMgr, + filterS: filterS, + cgrcfg: cgrcfg, + crn: cron.New(), + crnRQsMux: new(sync.RWMutex), + crnRQs: make(map[string]map[string]cron.EntryID), + storedRankings: make(utils.StringSet), + storingStopped: make(chan struct{}), + rankingStop: make(chan struct{}), } } -// RankingS manages Ranking execution +// RankingS is responsible of implementing the logic of RankingService type RankingS struct { dm *DataManager - cfg *config.CGRConfig - fltrS *FilterS connMgr *ConnManager + filterS *FilterS + cgrcfg *config.CGRConfig + + crn *cron.Cron // cron reference + + crnRQsMux *sync.RWMutex // protects the crnTQs + crnRQs map[string]map[string]cron.EntryID // save the EntryIDs for rankingQueries so we can reschedule them when needed + + storedRankings utils.StringSet // keep a record of RankingS which need saving, map[rankingTenanrkID]bool + sRksMux sync.RWMutex // protects storedRankings + storingStopped chan struct{} // signal back that the operations were stopped + + rankingStop chan struct{} // signal to stop all operations + +} + +// computeRanking will query the stats and build the Ranking for them +// +// it is to be called by Cron service +func (rkS *RankingS) computeRanking(ctx *context.Context, rkP *RankingProfile) { + rk, err := rkS.dm.GetRanking(ctx, rkP.Tenant, rkP.ID, true, true, utils.NonTransactional) + if err != nil { + utils.Logger.Warning( + fmt.Sprintf( + "<%s> querying RankingProfile with ID: <%s:%s> dm error: <%s>", + utils.RankingS, rkP.Tenant, rkP.ID, err.Error())) + return + } + rk.rMux.Lock() + defer rk.rMux.Unlock() + if rk.rkPrfl == nil { + rk.rkPrfl = rkP + } + rk.LastUpdate = time.Now() + rk.Metrics = make(map[string]map[string]float64) // reset previous values + rk.SortedStatIDs = make([]string, 0) + for _, statID := range rkP.StatIDs { + var floatMetrics map[string]float64 + if err := rkS.connMgr.Call(context.Background(), rkS.cgrcfg.RankingSCfg().StatSConns, + utils.StatSv1GetQueueFloatMetrics, + &utils.TenantIDWithAPIOpts{TenantID: &utils.TenantID{Tenant: rkP.Tenant, ID: statID}}, + &floatMetrics); err != nil { + utils.Logger.Warning( + fmt.Sprintf( + "<%s> computing Ranking with ID: <%s:%s> for Stats <%s> error: <%s>", + utils.RankingS, rkP.Tenant, rkP.ID, statID, err.Error())) + return + } + if len(rk.metricIDs) != 0 { + for metricID := range floatMetrics { + if _, has := rk.metricIDs[statID]; !has { + delete(floatMetrics, metricID) + } + } + } + + if len(floatMetrics) != 0 { + rk.Metrics[statID] = make(map[string]float64) + } + for metricID, val := range floatMetrics { + rk.Metrics[statID][metricID] = val + } + } + if rk.SortedStatIDs, err = rankingSortStats(rkP.Sorting, + rkP.SortingParameters, rk.Metrics); err != nil { + utils.Logger.Warning( + fmt.Sprintf( + "<%s> sorting stats for Ranking with ID: <%s:%s> error: <%s>", + utils.RankingS, rkP.Tenant, rkP.ID, err.Error())) + return + } + if err = rkS.storeRanking(ctx, rk); err != nil { + utils.Logger.Warning( + fmt.Sprintf( + "<%s> storing Ranking with ID: <%s:%s> DM error: <%s>", + utils.RankingS, rkP.Tenant, rkP.ID, err.Error())) + return + } + if err := rkS.processThresholds(rk); err != nil { + utils.Logger.Warning( + fmt.Sprintf( + "<%s> Ranking with id <%s:%s> error: <%s> with ThresholdS", + utils.RankingS, rkP.Tenant, rkP.ID, err.Error())) + } + if err := rkS.processEEs(rk); err != nil { + utils.Logger.Warning( + fmt.Sprintf( + "<%s> Trend with id <%s:%s> error: <%s> with EEs", + utils.RankingS, rkP.Tenant, rkP.ID, err.Error())) + } +} + +// processThresholds will pass the Ranking event to ThresholdS +func (rkS *RankingS) processThresholds(rk *Ranking) (err error) { + if len(rk.SortedStatIDs) == 0 { + return + } + if len(rkS.cgrcfg.RankingSCfg().ThresholdSConns) == 0 { + return + } + opts := map[string]any{ + utils.MetaEventType: utils.RankingUpdate, + } + var thIDs []string + if len(rk.rkPrfl.ThresholdIDs) != 0 { + if len(rk.rkPrfl.ThresholdIDs) == 1 && + rk.rkPrfl.ThresholdIDs[0] == utils.MetaNone { + return + } + thIDs = make([]string, len(rk.rkPrfl.ThresholdIDs)) + copy(thIDs, rk.rkPrfl.ThresholdIDs) + } + opts[utils.OptsThresholdsProfileIDs] = thIDs + sortedStatIDs := make([]string, len(rk.SortedStatIDs)) + copy(sortedStatIDs, rk.SortedStatIDs) + ev := &utils.CGREvent{ + Tenant: rk.Tenant, + ID: utils.GenUUID(), + APIOpts: opts, + Event: map[string]any{ + utils.RankingID: rk.ID, + utils.LastUpdate: rk.LastUpdate, + utils.SortedStatIDs: sortedStatIDs, + }, + } + var withErrs bool + var rkIDs []string + if err := rkS.connMgr.Call(context.TODO(), rkS.cgrcfg.RankingSCfg().ThresholdSConns, + utils.ThresholdSv1ProcessEvent, ev, &rkIDs); err != nil && + (len(thIDs) != 0 || err.Error() != utils.ErrNotFound.Error()) { + utils.Logger.Warning( + fmt.Sprintf("<%s> error: %s processing event %+v with ThresholdS.", utils.RankingS, err.Error(), ev)) + withErrs = true + } + if withErrs { + err = utils.ErrPartiallyExecuted + } + return +} + +// processEEs will pass the Ranking event to EEs +func (rkS *RankingS) processEEs(rk *Ranking) (err error) { + if len(rk.SortedStatIDs) == 0 { + return + } + if len(rkS.cgrcfg.RankingSCfg().EEsConns) == 0 { + return + } + opts := map[string]any{ + utils.MetaEventType: utils.RankingUpdate, + } + sortedStatIDs := make([]string, len(rk.SortedStatIDs)) + copy(sortedStatIDs, rk.SortedStatIDs) + ev := &utils.CGREvent{ + Tenant: rk.Tenant, + ID: utils.GenUUID(), + APIOpts: opts, + Event: map[string]any{ + utils.RankingID: rk.ID, + utils.LastUpdate: rk.LastUpdate, + utils.SortedStatIDs: sortedStatIDs, + }, + } + var withErrs bool + var reply map[string]map[string]any + if err := rkS.connMgr.Call(context.TODO(), rkS.cgrcfg.RankingSCfg().EEsConns, + utils.EeSv1ProcessEvent, ev, &reply); err != nil && + err.Error() != utils.ErrNotFound.Error() { + utils.Logger.Warning( + fmt.Sprintf("<%s> error: %q processing event %+v with EEs.", utils.RankingS, err.Error(), ev)) + withErrs = true + } + if withErrs { + err = utils.ErrPartiallyExecuted + } + return +} + +// storeTrend will store or schedule the trend based on settings +func (rkS *RankingS) storeRanking(ctx *context.Context, rk *Ranking) (err error) { + if rkS.cgrcfg.RankingSCfg().StoreInterval == 0 { + return + } + if rkS.cgrcfg.RankingSCfg().StoreInterval == -1 { + return rkS.dm.SetRanking(ctx, rk) + } + // schedule the asynchronous save, relies for Ranking to be in cache + rkS.sRksMux.Lock() + rkS.storedRankings.Add(rk.rkPrfl.TenantID()) + rkS.sRksMux.Unlock() + return +} + +// storeRankings will do one round for saving modified Rankings +// +// from cache to dataDB +// designed to run asynchronously +func (rkS *RankingS) storeRankings(ctx *context.Context) { + var failedRkIDs []string + for { + rkS.sRksMux.Lock() + rkID := rkS.storedRankings.GetOne() + if rkID != utils.EmptyString { + rkS.storedRankings.Remove(rkID) + } + rkS.sRksMux.Unlock() + if rkID == utils.EmptyString { + break // no more keys, backup completed + } + rkIf, ok := Cache.Get(utils.CacheRankings, rkID) + if !ok || rkIf == nil { + utils.Logger.Warning( + fmt.Sprintf("<%s> failed retrieving from cache Ranking with ID: %q", + utils.RankingS, rkID)) + failedRkIDs = append(failedRkIDs, rkID) // record failure so we can schedule it for next backup + continue + } + rk := rkIf.(*Ranking) + rk.rMux.RLock() + if err := rkS.dm.SetRanking(ctx, rk); err != nil { + utils.Logger.Warning( + fmt.Sprintf("<%s> failed storing Trend with ID: %q, err: %q", + utils.RankingS, rkID, err)) + failedRkIDs = append(failedRkIDs, rkID) // record failure so we can schedule it for next backup + } + rk.rMux.RUnlock() + // randomize the CPU load and give up thread control + runtime.Gosched() + } + if len(failedRkIDs) != 0 { // there were errors on save, schedule the keys for next backup + rkS.sRksMux.Lock() + rkS.storedRankings.AddSlice(failedRkIDs) + rkS.sRksMux.Unlock() + } +} + +// asyncStoreRankings runs as a backround process, calling storeRankings based on storeInterval +func (rkS *RankingS) asyncStoreRankings(ctx *context.Context) { + storeInterval := rkS.cgrcfg.RankingSCfg().StoreInterval + if storeInterval <= 0 { + close(rkS.storingStopped) + return + } + for { + rkS.storeRankings(ctx) + select { + case <-rkS.rankingStop: + close(rkS.storingStopped) + return + case <-time.After(storeInterval): // continue to another storing loop + } + } +} + +// StartRankings will activates the Cron, together with all scheduled Ranking queries +func (rkS *RankingS) StartRankingS(ctx *context.Context) (err error) { + if err = rkS.scheduleAutomaticQueries(ctx); err != nil { + return + } + rkS.crn.Start() + go rkS.asyncStoreRankings(ctx) + return +} + +// StopCron will shutdown the Cron tasks +func (rkS *RankingS) StopRankingS() { + timeEnd := time.Now().Add(rkS.cgrcfg.CoreSCfg().ShutdownTimeout) + + ctx := rkS.crn.Stop() + close(rkS.rankingStop) + + // Wait for cron + select { + case <-ctx.Done(): + case <-time.After(time.Until(timeEnd)): + utils.Logger.Warning( + fmt.Sprintf( + "<%s> timeout waiting for Cron to finish", + utils.RankingS)) + return + } + // Wait for backup and other operations + select { + case <-rkS.storingStopped: + case <-time.After(time.Until(timeEnd)): + utils.Logger.Warning( + fmt.Sprintf( + "<%s> timeout waiting for RankingS to finish", + utils.RankingS)) + return + } +} + +func (rkS *RankingS) Reload(ctx *context.Context) { + crnCtx := rkS.crn.Stop() + close(rkS.rankingStop) + <-crnCtx.Done() + <-rkS.storingStopped + rkS.rankingStop = make(chan struct{}) + rkS.storingStopped = make(chan struct{}) + rkS.crn.Start() + go rkS.asyncStoreRankings(ctx) +} + +// scheduleAutomaticQueries will schedule the queries at start/reload based on configured +func (rkS *RankingS) scheduleAutomaticQueries(ctx *context.Context) error { + schedData := make(map[string][]string) + for k, v := range rkS.cgrcfg.RankingSCfg().ScheduledIDs { + schedData[k] = v + } + var tnts []string + if len(schedData) == 0 { + tnts = make([]string, 0) + } + for tnt, rkIDs := range schedData { + if len(rkIDs) == 0 { + tnts = append(tnts, tnt) + } + } + if tnts != nil { + qrydData, err := rkS.dm.GetRankingProfileIDs(ctx, tnts) + if err != nil { + return err + } + for tnt, ids := range qrydData { + schedData[tnt] = ids + } + } + for tnt, rkIDs := range schedData { + if _, err := rkS.scheduleRankingQueries(ctx, tnt, rkIDs); err != nil { + return err + } + } + return nil +} + +// scheduleTrendQueries will schedule/re-schedule specific trend queries +func (rkS *RankingS) scheduleRankingQueries(ctx *context.Context, + tnt string, rkIDs []string) (scheduled int, err error) { + var partial bool + rkS.crnRQsMux.Lock() + if _, has := rkS.crnRQs[tnt]; !has { + rkS.crnRQs[tnt] = make(map[string]cron.EntryID) + } + rkS.crnRQsMux.Unlock() + for _, rkID := range rkIDs { + rkS.crnRQsMux.RLock() + if entryID, has := rkS.crnRQs[tnt][rkID]; has { + rkS.crn.Remove(entryID) // deschedule the query + } + rkS.crnRQsMux.RUnlock() + if rkP, err := rkS.dm.GetRankingProfile(ctx, tnt, rkID, true, true, utils.NonTransactional); err != nil { + utils.Logger.Warning( + fmt.Sprintf( + "<%s> failed retrieving RankingProfile with id: <%s:%s> for scheduling, error: <%s>", + utils.RankingS, tnt, rkID, err.Error())) + partial = true + } else if entryID, err := rkS.crn.AddFunc(rkP.Schedule, + func() { rkS.computeRanking(ctx, rkP.Clone()) }); err != nil { + utils.Logger.Warning( + fmt.Sprintf( + "<%s> scheduling RankingProfile <%s:%s>, error: <%s>", + utils.RankingS, tnt, rkID, err.Error())) + partial = true + } else { // log the entry ID for debugging + rkS.crnRQsMux.Lock() + rkS.crnRQs[rkP.Tenant][rkP.ID] = entryID + rkS.crnRQsMux.Unlock() + scheduled++ + } + } + if partial { + return 0, utils.ErrPartiallyExecuted + } + return +} + +// V1ScheduleQueries is the query for manually re-/scheduling Ranking Queries +func (rkS *RankingS) V1ScheduleQueries(ctx *context.Context, args *utils.ArgScheduleRankingQueries, scheduled *int) (err error) { + if sched, errSched := rkS.scheduleRankingQueries(ctx, args.Tenant, args.RankingIDs); errSched != nil { + return errSched + } else { + *scheduled = sched + } + return +} + +// V1GetRanking is the API to return the Ranking instance +func (rkS *RankingS) V1GetRanking(ctx *context.Context, arg *utils.TenantIDWithAPIOpts, retRanking *Ranking) (err error) { + if missing := utils.MissingStructFields(arg, []string{utils.ID}); len(missing) != 0 { //Params missing + return utils.NewErrMandatoryIeMissing(missing...) + } + var rk *Ranking + if rk, err = rkS.dm.GetRanking(ctx, arg.Tenant, arg.ID, true, true, utils.NonTransactional); err != nil { + return + } + rk.rMux.RLock() + defer rk.rMux.RUnlock() + retRanking.Tenant = rk.Tenant // avoid vet complaining for mutex copying + retRanking.ID = rk.ID + retRanking.Metrics = make(map[string]map[string]float64) + for statID, metrics := range rk.Metrics { + retRanking.Metrics[statID] = make(map[string]float64) + for metricID, val := range metrics { + retRanking.Metrics[statID][metricID] = val + } + } + retRanking.LastUpdate = rk.LastUpdate + retRanking.Sorting = rk.Sorting + + retRanking.SortingParameters = make([]string, len(rk.SortingParameters)) + copy(retRanking.SortingParameters, rk.SortingParameters) + + retRanking.SortedStatIDs = make([]string, len(rk.SortedStatIDs)) + copy(retRanking.SortedStatIDs, rk.SortedStatIDs) + return +} + +// V1GetSchedule returns the active schedule for Ranking queries +func (rkS *RankingS) V1GetSchedule(ctx *context.Context, args *utils.ArgScheduledRankings, schedRankings *[]utils.ScheduledRanking) (err error) { + tnt := args.Tenant + if tnt == utils.EmptyString { + tnt = rkS.cgrcfg.GeneralCfg().DefaultTenant + } + rkS.crnRQsMux.RLock() + defer rkS.crnRQsMux.RUnlock() + rankingIDsMp, has := rkS.crnRQs[tnt] + if !has { + return utils.ErrNotFound + } + var scheduledRankings []utils.ScheduledRanking + var entryIds map[string]cron.EntryID + if len(args.RankingIDPrefixes) == 0 { + entryIds = rankingIDsMp + } else { + entryIds = make(map[string]cron.EntryID) + for _, rkID := range args.RankingIDPrefixes { + for key, entryID := range rankingIDsMp { + if strings.HasPrefix(key, rkID) { + entryIds[key] = entryID + } + } + } + } + if len(entryIds) == 0 { + return utils.ErrNotFound + } + var entry cron.Entry + for id, entryID := range entryIds { + entry = rkS.crn.Entry(entryID) + if entry.ID == 0 { + continue + } + scheduledRankings = append(scheduledRankings, + utils.ScheduledRanking{ + RankingID: id, + Next: entry.Next, + Previous: entry.Prev, + }) + } + slices.SortFunc(scheduledRankings, func(a, b utils.ScheduledRanking) int { + return a.Next.Compare(b.Next) + }) + *schedRankings = scheduledRankings + return nil +} + +// V1GetRankingSummary returns a summary of ascending/descending stat of the last updated ranking +func (rS *RankingS) V1GetRankingSummary(ctx *context.Context, arg *utils.TenantIDWithAPIOpts, reply *RankingSummary) (err error) { + var rnk *Ranking + if rnk, err = rS.dm.GetRanking(ctx, arg.Tenant, arg.ID, true, true, utils.NonTransactional); err != nil { + return + } + rnk.rMux.RLock() + rnkS := rnk.asRankingSummary() + rnk.rMux.RUnlock() + *reply = *rnkS + return } diff --git a/engine/rankings_test.go b/engine/rankings_test.go index e4bd913bd..344845b26 100644 --- a/engine/rankings_test.go +++ b/engine/rankings_test.go @@ -20,7 +20,6 @@ package engine import ( "testing" - "time" "github.com/cgrates/cgrates/config" ) @@ -29,7 +28,6 @@ func TestTenantID(t *testing.T) { rp := &RankingProfile{ Tenant: "cgrates.org", ID: "01", - QueryInterval: 5 * time.Minute, StatIDs: []string{"stat1", "stat2"}, MetricIDs: []string{"metric1"}, Sorting: "asc", @@ -50,7 +48,6 @@ func TestRankingProfileWithAPIOpts(t *testing.T) { rp := &RankingProfile{ Tenant: "cgrates.org", ID: "ID", - QueryInterval: 5 * time.Minute, StatIDs: []string{"stat1", "stat2"}, MetricIDs: []string{"metric1"}, Sorting: "asc", @@ -102,7 +99,7 @@ func TestNewRankingService(t *testing.T) { filterS := &FilterS{} connMgr := &ConnManager{} - rankingService := NewRankingService(dm, cgrcfg, filterS, connMgr) + rankingService := NewRankingS(dm, connMgr, filterS, cgrcfg) if rankingService == nil { t.Fatal("NewRankingService() returned nil") @@ -112,12 +109,12 @@ func TestNewRankingService(t *testing.T) { t.Errorf("Expected dm to be %v, got %v", dm, rankingService.dm) } - if rankingService.cfg != cgrcfg { - t.Errorf("Expected cfg to be %v, got %v", cgrcfg, rankingService.cfg) + if rankingService.cgrcfg != cgrcfg { + t.Errorf("Expected cfg to be %v, got %v", cgrcfg, rankingService.cgrcfg) } - if rankingService.fltrS != filterS { - t.Errorf("Expected fltrS to be %v, got %v", filterS, rankingService.fltrS) + if rankingService.filterS != filterS { + t.Errorf("Expected fltrS to be %v, got %v", filterS, rankingService.filterS) } if rankingService.connMgr != connMgr { diff --git a/engine/route_cost_sort_test.go b/engine/route_cost_sort_test.go index 6f4c7bf74..91dbd4f02 100644 --- a/engine/route_cost_sort_test.go +++ b/engine/route_cost_sort_test.go @@ -32,36 +32,36 @@ import ( "github.com/ericlagergren/decimal" ) -func TestPopulateCostForRoutesConnRefused(t *testing.T) { - cfg := config.NewDefaultCGRConfig() - connMgr := NewConnManager(cfg) - fltrS := NewFilterS(cfg, connMgr, nil) - routes := map[string]*RouteWithWeight{ - "RW": { - Route: &Route{ - ID: "local", - RateProfileIDs: []string{"RP_LOCAL"}, - }, - Weight: 10, - }, - } - ev := &utils.CGREvent{ - Tenant: "cgrates.org", - Event: map[string]any{ - utils.Usage: "10s", - }, - APIOpts: map[string]any{ - utils.OptsRatesProfileIDs: []string{}, - }, - } - extraOpts := &optsGetRoutes{} - cfg.RouteSCfg().RateSConns = []string{"*localhost"} - _, err := populateCostForRoutes(context.Background(), cfg, connMgr, fltrS, routes, ev, extraOpts) - errExpect := "RATES_ERROR:dial tcp 127.0.0.1:2012: connect: connection refused" - if err == nil || err.Error() != errExpect { - t.Errorf("Expected %v\n but received %v", errExpect, err) - } -} +// func TestPopulateCostForRoutesConnRefused(t *testing.T) { +// cfg := config.NewDefaultCGRConfig() +// connMgr := NewConnManager(cfg) +// fltrS := NewFilterS(cfg, connMgr, nil) +// routes := map[string]*RouteWithWeight{ +// "RW": { +// Route: &Route{ +// ID: "local", +// RateProfileIDs: []string{"RP_LOCAL"}, +// }, +// Weight: 10, +// }, +// } +// ev := &utils.CGREvent{ +// Tenant: "cgrates.org", +// Event: map[string]any{ +// utils.Usage: "10s", +// }, +// APIOpts: map[string]any{ +// utils.OptsRatesProfileIDs: []string{}, +// }, +// } +// extraOpts := &optsGetRoutes{} +// cfg.RouteSCfg().RateSConns = []string{"*localhost"} +// _, err := populateCostForRoutes(context.Background(), cfg, connMgr, fltrS, routes, ev, extraOpts) +// errExpect := "RATES_ERROR:dial tcp 127.0.0.1:2012: connect: connection refused" +// if err == nil || err.Error() != errExpect { +// t.Errorf("Expected %v\n but received %v", errExpect, err) +// } +// } func TestLeastCostSorterSortRoutesErr(t *testing.T) { diff --git a/engine/storage_csv.go b/engine/storage_csv.go index 0ba516a79..29aaceccf 100644 --- a/engine/storage_csv.go +++ b/engine/storage_csv.go @@ -367,9 +367,9 @@ func (csvs *CSVStorage) GetTPRankings(tpid, tenant, id string) ([]*utils.TPRanki func (csvs *CSVStorage) GetTPTrends(tpid, tenant, id string) ([]*utils.TPTrendsProfile, error) { var tpTrends TrendMdls if err := csvs.proccesData(TrendMdl{}, csvs.trendsFn, func(tp any) { - tPTrends := tp.(TrendMdl) - tPTrends.Tpid = tpid - tpTrends = append(tpTrends, &tPTrends) + tPTrend := tp.(TrendMdl) + tPTrend.Tpid = tpid + tpTrends = append(tpTrends, &tPTrend) }); err != nil { return nil, err } diff --git a/engine/storage_interface.go b/engine/storage_interface.go index e4cf18197..eb247e6d4 100644 --- a/engine/storage_interface.go +++ b/engine/storage_interface.go @@ -67,12 +67,15 @@ type DataDB interface { SetRankingProfileDrv(ctx *context.Context, rp *RankingProfile) (err error) GetRankingProfileDrv(ctx *context.Context, tenant string, id string) (sq *RankingProfile, err error) RemRankingProfileDrv(ctx *context.Context, tenant string, id string) (err error) + SetRankingDrv(ctx *context.Context, rn *Ranking) (err error) + GetRankingDrv(ctx *context.Context, tenant string, id string) (sq *Ranking, err error) + RemoveRankingDrv(ctx *context.Context, tenant string, id string) (err error) SetTrendProfileDrv(ctx *context.Context, sq *TrendProfile) (err error) GetTrendProfileDrv(ctx *context.Context, tenant string, id string) (sq *TrendProfile, err error) RemTrendProfileDrv(ctx *context.Context, tenant string, id string) (err error) - GetTrendDrv(string, string) (*Trend, error) - SetTrendDrv(*Trend) error - RemoveTrendDrv(string, string) error + GetTrendDrv(ctx *context.Context, tenant string, id string) (*Trend, error) + SetTrendDrv(ctx *context.Context, tr *Trend) error + RemoveTrendDrv(ctx *context.Context, tenant string, id string) error GetFilterDrv(ctx *context.Context, tnt string, id string) (*Filter, error) SetFilterDrv(ctx *context.Context, f *Filter) error RemoveFilterDrv(ctx *context.Context, tnt string, id string) error diff --git a/engine/storage_internal_datadb.go b/engine/storage_internal_datadb.go index 89df5ab54..51dca3473 100644 --- a/engine/storage_internal_datadb.go +++ b/engine/storage_internal_datadb.go @@ -274,6 +274,26 @@ func (iDB *InternalDB) RemRankingProfileDrv(_ *context.Context, tenant, id strin return nil } +func (iDB *InternalDB) GetRankingDrv(_ *context.Context, tenant, id string) (rn *Ranking, err error) { + x, ok := iDB.db.Get(utils.CacheRankings, utils.ConcatenatedKey(tenant, id)) + if !ok || x == nil { + return nil, utils.ErrNotFound + } + return x.(*Ranking), nil +} + +func (iDB *InternalDB) SetRankingDrv(_ *context.Context, rn *Ranking) (err error) { + iDB.db.Set(utils.CacheRankings, rn.TenantID(), rn, nil, + true, utils.NonTransactional) + return +} + +func (iDB *InternalDB) RemoveRankingDrv(_ *context.Context, tenant, id string) (err error) { + iDB.db.Remove(utils.CacheRankings, utils.ConcatenatedKey(tenant, id), + true, utils.NonTransactional) + return +} + func (iDB *InternalDB) GetStatQueueDrv(_ *context.Context, tenant, id string) (sq *StatQueue, err error) { x, ok := iDB.db.Get(utils.CacheStatQueues, utils.ConcatenatedKey(tenant, id)) if !ok || x == nil { @@ -324,7 +344,7 @@ func (iDB *InternalDB) GetTrendProfileDrv(_ *context.Context, tenant, id string) return x.(*TrendProfile), nil } -func (iDB *InternalDB) GetTrendDrv(tenant, id string) (th *Trend, err error) { +func (iDB *InternalDB) GetTrendDrv(_ *context.Context, tenant, id string) (th *Trend, err error) { x, ok := iDB.db.Get(utils.CacheTrends, utils.ConcatenatedKey(tenant, id)) if !ok || x == nil { return nil, utils.ErrNotFound @@ -332,13 +352,13 @@ func (iDB *InternalDB) GetTrendDrv(tenant, id string) (th *Trend, err error) { return x.(*Trend), nil } -func (iDB *InternalDB) SetTrendDrv(tr *Trend) (err error) { +func (iDB *InternalDB) SetTrendDrv(_ *context.Context, tr *Trend) (err error) { iDB.db.Set(utils.CacheTrends, tr.TenantID(), tr, nil, true, utils.NonTransactional) return } -func (iDB *InternalDB) RemoveTrendDrv(tenant, id string) (err error) { +func (iDB *InternalDB) RemoveTrendDrv(_ *context.Context, tenant, id string) (err error) { iDB.db.Remove(utils.CacheTrends, utils.ConcatenatedKey(tenant, id), true, utils.NonTransactional) return diff --git a/engine/storage_mongo_datadb.go b/engine/storage_mongo_datadb.go index 5259ca6bf..b321047db 100644 --- a/engine/storage_mongo_datadb.go +++ b/engine/storage_mongo_datadb.go @@ -66,6 +66,7 @@ const ( ColTrs = "trend_profiles" ColTrd = "trends" ColRgp = "ranking_profiles" + ColRnk = "rankings" ColFlt = "filters" ColRts = "route_profiles" ColAttr = "attribute_profiles" @@ -781,6 +782,38 @@ func (ms *MongoStorage) RemRankingProfileDrv(ctx *context.Context, tenant, id st } +func (ms *MongoStorage) GetRankingDrv(ctx *context.Context, tenant, id string) (*Ranking, error) { + rn := new(Ranking) + err := ms.query(ctx, func(sctx mongo.SessionContext) error { + sr := ms.getCol(ColRnk).FindOne(sctx, bson.M{"tenant": tenant, "id": id}) + decodeErr := sr.Decode(rn) + if errors.Is(decodeErr, mongo.ErrNoDocuments) { + return utils.ErrNotFound + } + return decodeErr + }) + return rn, err +} +func (ms *MongoStorage) SetRankingDrv(ctx *context.Context, rn *Ranking) error { + return ms.query(ctx, func(sctx mongo.SessionContext) error { + _, err := ms.getCol(ColRnk).UpdateOne(sctx, bson.M{"tenant": rn.Tenant, "id": rn.ID}, + bson.M{"$set": rn}, + options.Update().SetUpsert(true), + ) + return err + }) +} + +func (ms *MongoStorage) RemoveRankingDrv(ctx *context.Context, tenant, id string) error { + return ms.query(ctx, func(sctx mongo.SessionContext) error { + dr, err := ms.getCol(ColRnk).DeleteOne(sctx, bson.M{"tenant": tenant, "id": id}) + if dr.DeletedCount == 0 { + return utils.ErrNotFound + } + return err + }) +} + func (ms *MongoStorage) GetTrendProfileDrv(ctx *context.Context, tenant, id string) (*TrendProfile, error) { srProfile := new(TrendProfile) err := ms.query(ctx, func(sctx mongo.SessionContext) error { @@ -813,9 +846,9 @@ func (ms *MongoStorage) RemTrendProfileDrv(ctx *context.Context, tenant, id stri }) } -func (ms *MongoStorage) GetTrendDrv(tenant, id string) (*Trend, error) { +func (ms *MongoStorage) GetTrendDrv(ctx *context.Context, tenant, id string) (*Trend, error) { tr := new(Trend) - err := ms.query(context.Background(), func(sctx mongo.SessionContext) error { + err := ms.query(ctx, func(sctx mongo.SessionContext) error { sr := ms.getCol(ColTrd).FindOne(sctx, bson.M{"tenant": tenant, "id": id}) decodeErr := sr.Decode(tr) if errors.Is(decodeErr, mongo.ErrNoDocuments) { @@ -826,8 +859,8 @@ func (ms *MongoStorage) GetTrendDrv(tenant, id string) (*Trend, error) { return tr, err } -func (ms *MongoStorage) SetTrendDrv(tr *Trend) error { - return ms.query(context.Background(), func(sctx mongo.SessionContext) error { +func (ms *MongoStorage) SetTrendDrv(ctx *context.Context, tr *Trend) error { + return ms.query(ctx, func(sctx mongo.SessionContext) error { _, err := ms.getCol(ColTrd).UpdateOne(sctx, bson.M{"tenant": tr.Tenant, "id": tr.ID}, bson.M{"$set": tr}, options.Update().SetUpsert(true), @@ -836,8 +869,8 @@ func (ms *MongoStorage) SetTrendDrv(tr *Trend) error { }) } -func (ms *MongoStorage) RemoveTrendDrv(tenant, id string) error { - return ms.query(context.Background(), func(sctx mongo.SessionContext) error { +func (ms *MongoStorage) RemoveTrendDrv(ctx *context.Context, tenant, id string) error { + return ms.query(ctx, func(sctx mongo.SessionContext) error { dr, err := ms.getCol(ColTrd).DeleteOne(sctx, bson.M{"tenant": tenant, "id": id}) if dr.DeletedCount == 0 { return utils.ErrNotFound diff --git a/engine/storage_redis.go b/engine/storage_redis.go index 33ed9e833..43e391ad3 100644 --- a/engine/storage_redis.go +++ b/engine/storage_redis.go @@ -553,7 +553,7 @@ func (rs *RedisStorage) RemTrendProfileDrv(ctx *context.Context, tenant string, return rs.Cmd(nil, redisDEL, utils.TrendProfilePrefix+utils.ConcatenatedKey(tenant, id)) } -func (rs *RedisStorage) GetTrendDrv(tenant, id string) (r *Trend, err error) { +func (rs *RedisStorage) GetTrendDrv(ctx *context.Context, tenant, id string) (r *Trend, err error) { var values []byte if err = rs.Cmd(&values, redisGET, utils.TrendPrefix+utils.ConcatenatedKey(tenant, id)); err != nil { return @@ -565,7 +565,7 @@ func (rs *RedisStorage) GetTrendDrv(tenant, id string) (r *Trend, err error) { return } -func (rs *RedisStorage) SetTrendDrv(r *Trend) (err error) { +func (rs *RedisStorage) SetTrendDrv(ctx *context.Context, r *Trend) (err error) { var result []byte if result, err = rs.ms.Marshal(r); err != nil { return @@ -573,7 +573,7 @@ func (rs *RedisStorage) SetTrendDrv(r *Trend) (err error) { return rs.Cmd(nil, redisSET, utils.TrendPrefix+utils.ConcatenatedKey(r.Tenant, r.ID), string(result)) } -func (rs *RedisStorage) RemoveTrendDrv(tenant, id string) (err error) { +func (rs *RedisStorage) RemoveTrendDrv(ctx *context.Context, tenant, id string) (err error) { return rs.Cmd(nil, redisDEL, utils.TrendPrefix+utils.ConcatenatedKey(tenant, id)) } @@ -596,10 +596,35 @@ func (rs *RedisStorage) GetRankingProfileDrv(ctx *context.Context, tenant string err = rs.ms.Unmarshal(values, &sg) return } + func (rs *RedisStorage) RemRankingProfileDrv(ctx *context.Context, tenant string, id string) (err error) { return rs.Cmd(nil, redisDEL, utils.RankingProfilePrefix+utils.ConcatenatedKey(tenant, id)) } +func (rs *RedisStorage) GetRankingDrv(ctx *context.Context, tenant, id string) (rn *Ranking, err error) { + var values []byte + if err = rs.Cmd(&values, redisGET, utils.RankingPrefix+utils.ConcatenatedKey(tenant, id)); err != nil { + return + } else if len(values) == 0 { + err = utils.ErrNotFound + return + } + err = rs.ms.Unmarshal(values, &rn) + return rn, err +} + +func (rs *RedisStorage) SetRankingDrv(_ *context.Context, rn *Ranking) (err error) { + var result []byte + if result, err = rs.ms.Marshal(rn); err != nil { + return + } + return rs.Cmd(nil, redisSET, utils.RankingPrefix+utils.ConcatenatedKey(rn.Tenant, rn.ID), string(result)) +} + +func (rs *RedisStorage) RemoveRankingDrv(ctx *context.Context, tenant, id string) (err error) { + return rs.Cmd(nil, redisDEL, utils.RankingPrefix+utils.ConcatenatedKey(tenant, id)) +} + // GetThresholdProfileDrv retrieves a ThresholdProfile from dataDB func (rs *RedisStorage) GetThresholdProfileDrv(ctx *context.Context, tenant, ID string) (tp *ThresholdProfile, err error) { var values []byte diff --git a/engine/trends.go b/engine/trends.go index 21b6998a7..6c9402179 100644 --- a/engine/trends.go +++ b/engine/trends.go @@ -20,6 +20,9 @@ package engine import ( "fmt" + "runtime" + "slices" + "strings" "sync" "time" @@ -32,13 +35,16 @@ import ( func NewTrendService(dm *DataManager, cgrcfg *config.CGRConfig, filterS *FilterS, connMgr *ConnManager) (tS *TrendS) { return &TrendS{ - dm: dm, - cfg: cgrcfg, - fltrS: filterS, - connMgr: connMgr, - loopStopped: make(chan struct{}), - crnTQs: make(map[string]map[string]cron.EntryID), - crnTQsMux: new(sync.RWMutex), + dm: dm, + cfg: cgrcfg, + fltrS: filterS, + connMgr: connMgr, + loopStopped: make(chan struct{}), + crnTQs: make(map[string]map[string]cron.EntryID), + crnTQsMux: new(sync.RWMutex), + storedTrends: make(utils.StringSet), + storingStopped: make(chan struct{}), + trendStop: make(chan struct{}), } } @@ -54,13 +60,18 @@ type TrendS struct { crnTQsMux *sync.RWMutex // protects the crnTQs crnTQs map[string]map[string]cron.EntryID // save the EntryIDs for TrendQueries so we can reschedule them when needed + storedTrends utils.StringSet // keep a record of trends which need saving, map[trendTenantID]bool + sTrndsMux sync.RWMutex // protects storedTrends + storingStopped chan struct{} // signal back that the operations were stopped + loopStopped chan struct{} + trendStop chan struct{} // signal to stop all operations } // computeTrend will query a stat and build the Trend for it // -// it is to be called by Cron service -func (tS *TrendS) computeTrend(tP *TrendProfile) { +// it is to be called by Cron service +func (tS *TrendS) computeTrend(ctx *context.Context, tP *TrendProfile) { var floatMetrics map[string]float64 if err := tS.connMgr.Call(context.Background(), tS.cfg.TrendSCfg().StatSConns, utils.StatSv1GetQueueFloatMetrics, @@ -68,32 +79,28 @@ func (tS *TrendS) computeTrend(tP *TrendProfile) { &floatMetrics); err != nil { utils.Logger.Warning( fmt.Sprintf( - "<%s> computing trend for with id: <%s:%s> stats <%s> error: <%s>", + "<%s> computing trend with id: <%s:%s> for stats <%s> error: <%s>", utils.TrendS, tP.Tenant, tP.ID, tP.StatID, err.Error())) return } - trend, err := tS.dm.GetTrend(tP.Tenant, tP.ID, true, true, utils.NonTransactional) - if err == utils.ErrNotFound { - trend = &Trend{ - Tenant: tP.Tenant, - ID: tP.ID, - RunTimes: make([]time.Time, 0), - Metrics: make(map[time.Time]map[string]*MetricWithTrend), - } - } else if err != nil { + trnd, err := tS.dm.GetTrend(ctx, tP.Tenant, tP.ID, true, true, utils.NonTransactional) + if err != nil { utils.Logger.Warning( fmt.Sprintf( "<%s> querying trend with id: <%s:%s> dm error: <%s>", utils.TrendS, tP.Tenant, tP.ID, err.Error())) return } - trend.tMux.Lock() - defer trend.tMux.Unlock() - + trnd.tMux.Lock() + defer trnd.tMux.Unlock() + if trnd.tPrfl == nil { + trnd.tPrfl = tP + } + trnd.Compile(tP.TTL, tP.QueueLength) now := time.Now() var metrics []string if len(tP.Metrics) != 0 { - metrics = tP.Metrics + metrics = tP.Metrics // read only } if len(metrics) == 0 { // unlimited metrics in trend for mID := range floatMetrics { @@ -103,8 +110,11 @@ func (tS *TrendS) computeTrend(tP *TrendProfile) { if len(metrics) == 0 { return // nothing to compute } - trend.RunTimes = append(trend.RunTimes, now) - trend.Metrics[now] = make(map[string]*MetricWithTrend) + trnd.RunTimes = append(trnd.RunTimes, now) + if trnd.Metrics == nil { + trnd.Metrics = make(map[time.Time]map[string]*MetricWithTrend) + } + trnd.Metrics[now] = make(map[string]*MetricWithTrend) for _, mID := range metrics { mWt := &MetricWithTrend{ID: mID} var has bool @@ -113,49 +123,445 @@ func (tS *TrendS) computeTrend(tP *TrendProfile) { mWt.TrendLabel = utils.NotAvailable continue } - if mWt.TrendGrowth, err = trend.getTrendGrowth(mID, mWt.Value, tP.CorrelationType, tS.cfg.GeneralCfg().RoundingDecimals); err != nil { + if mWt.TrendGrowth, err = trnd.getTrendGrowth(mID, mWt.Value, tP.CorrelationType, + tS.cfg.GeneralCfg().RoundingDecimals); err != nil { mWt.TrendLabel = utils.NotAvailable } else { - mWt.TrendLabel = trend.getTrendLabel(mWt.TrendGrowth, tP.Tolerance) + mWt.TrendLabel = trnd.getTrendLabel(mWt.TrendGrowth, tP.Tolerance) } - trend.Metrics[now][mWt.ID] = mWt + trnd.Metrics[now][mWt.ID] = mWt + trnd.indexesAppendMetric(mWt, now) } - if err := tS.dm.SetTrend(trend); err != nil { + if err = tS.storeTrend(ctx, trnd); err != nil { utils.Logger.Warning( fmt.Sprintf( - "<%s> setting trend with id: <%s:%s> dm error: <%s>", + "<%s> setting Trend with id: <%s:%s> DM error: <%s>", utils.TrendS, tP.Tenant, tP.ID, err.Error())) return } + if err = tS.processThresholds(trnd); err != nil { + utils.Logger.Warning( + fmt.Sprintf( + "<%s> Trend with id <%s:%s> error: <%s> with ThresholdS", + utils.TrendS, tP.Tenant, tP.ID, err.Error())) + } + if err = tS.processEEs(trnd); err != nil { + utils.Logger.Warning( + fmt.Sprintf( + "<%s> Trend with id <%s:%s> error: <%s> with EEs", + utils.TrendS, tP.Tenant, tP.ID, err.Error())) + } + +} + +// processThresholds will pass the Trend event to ThresholdS +func (tS *TrendS) processThresholds(trnd *Trend) (err error) { + if len(trnd.RunTimes) == 0 || + len(trnd.RunTimes) < trnd.tPrfl.MinItems { + return + } + if len(tS.cfg.TrendSCfg().ThresholdSConns) == 0 { + return + } + opts := map[string]any{ + utils.MetaEventType: utils.TrendUpdate, + } + var thIDs []string + if len(trnd.tPrfl.ThresholdIDs) != 0 { + if len(trnd.tPrfl.ThresholdIDs) == 1 && + trnd.tPrfl.ThresholdIDs[0] == utils.MetaNone { + return + } + thIDs = make([]string, len(trnd.tPrfl.ThresholdIDs)) + copy(thIDs, trnd.tPrfl.ThresholdIDs) + } + opts[utils.OptsThresholdsProfileIDs] = thIDs + ts := trnd.asTrendSummary() + trndEv := &utils.CGREvent{ + Tenant: trnd.Tenant, + ID: utils.GenUUID(), + APIOpts: opts, + Event: map[string]any{ + utils.TrendID: trnd.ID, + utils.Time: ts.Time, + utils.Metrics: ts.Metrics, + }, + } + var withErrs bool + var tIDs []string + if err := tS.connMgr.Call(context.TODO(), tS.cfg.TrendSCfg().ThresholdSConns, + utils.ThresholdSv1ProcessEvent, trndEv, &tIDs); err != nil && + (len(thIDs) != 0 || err.Error() != utils.ErrNotFound.Error()) { + utils.Logger.Warning( + fmt.Sprintf("<%s> error: %s processing event %+v with ThresholdS.", utils.TrendS, err.Error(), trndEv)) + withErrs = true + } + if withErrs { + err = utils.ErrPartiallyExecuted + } + return +} + +// processEEs will pass the Trend event to EEs +func (tS *TrendS) processEEs(trnd *Trend) (err error) { + if len(trnd.RunTimes) == 0 || + len(trnd.RunTimes) < trnd.tPrfl.MinItems { + return + } + if len(tS.cfg.TrendSCfg().EEsConns) == 0 { + return + } + opts := map[string]any{ + utils.MetaEventType: utils.TrendUpdate, + } + ts := trnd.asTrendSummary() + trndEv := &utils.CGREventWithEeIDs{ + CGREvent: &utils.CGREvent{ + Tenant: trnd.Tenant, + ID: utils.GenUUID(), + APIOpts: opts, + Event: map[string]any{ + utils.TrendID: trnd.ID, + utils.Time: ts.Time, + utils.Metrics: ts.Metrics, + }, + }, + EeIDs: tS.cfg.TrendSCfg().EEsExporterIDs, + } + var withErrs bool + var reply map[string]map[string]any + if err := tS.connMgr.Call(context.TODO(), tS.cfg.TrendSCfg().EEsConns, + utils.EeSv1ProcessEvent, trndEv, &reply); err != nil && + err.Error() != utils.ErrNotFound.Error() { + utils.Logger.Warning( + fmt.Sprintf("<%s> error: %q processing event %+v with EEs.", utils.TrendS, err.Error(), trndEv)) + withErrs = true + } + if withErrs { + err = utils.ErrPartiallyExecuted + } + return +} + +// storeTrend will store or schedule the trend based on settings +func (tS *TrendS) storeTrend(ctx *context.Context, trnd *Trend) (err error) { + if tS.cfg.TrendSCfg().StoreInterval == 0 { + return + } + if tS.cfg.TrendSCfg().StoreInterval == -1 { + return tS.dm.SetTrend(ctx, trnd) + } + + // schedule the asynchronous save, relies for Trend to be in cache + tS.sTrndsMux.Lock() + tS.storedTrends.Add(trnd.TenantID()) + tS.sTrndsMux.Unlock() + return +} + +// storeTrends will do one round for saving modified trends +// +// from cache to dataDB +// designed to run asynchronously +func (tS *TrendS) storeTrends(ctx *context.Context) { + var failedTrndIDs []string + for { + tS.sTrndsMux.Lock() + trndID := tS.storedTrends.GetOne() + if trndID != utils.EmptyString { + tS.storedTrends.Remove(trndID) + } + tS.sTrndsMux.Unlock() + if trndID == utils.EmptyString { + break // no more keys, backup completed + } + trndIf, ok := Cache.Get(utils.CacheTrends, trndID) + if !ok || trndIf == nil { + utils.Logger.Warning( + fmt.Sprintf("<%s> failed retrieving from cache Trend with ID: %q", + utils.TrendS, trndID)) + failedTrndIDs = append(failedTrndIDs, trndID) // record failure so we can schedule it for next backup + continue + } + trnd := trndIf.(*Trend) + trnd.tMux.RLock() + if err := tS.dm.SetTrend(ctx, trnd); err != nil { + utils.Logger.Warning( + fmt.Sprintf("<%s> failed storing Trend with ID: %q, err: %q", + utils.TrendS, trndID, err)) + failedTrndIDs = append(failedTrndIDs, trndID) // record failure so we can schedule it for next backup + } + trnd.tMux.RUnlock() + // randomize the CPU load and give up thread control + runtime.Gosched() + } + if len(failedTrndIDs) != 0 { // there were errors on save, schedule the keys for next backup + tS.sTrndsMux.Lock() + tS.storedTrends.AddSlice(failedTrndIDs) + tS.sTrndsMux.Unlock() + } +} + +// asyncStoreTrends runs as a backround process, calling storeTrends based on storeInterval +func (tS *TrendS) asyncStoreTrends(ctx *context.Context) { + storeInterval := tS.cfg.TrendSCfg().StoreInterval + if storeInterval <= 0 { + close(tS.storingStopped) + return + } + for { + tS.storeTrends(ctx) + select { + case <-tS.trendStop: + close(tS.storingStopped) + return + case <-time.After(storeInterval): // continue to another storing loop + } + } +} + +// StartCron will activates the Cron, together with all scheduled Trend queries +func (tS *TrendS) StartTrendS(ctx *context.Context) error { + if err := tS.scheduleAutomaticQueries(ctx); err != nil { + return err + } + tS.crn.Start() + go tS.asyncStoreTrends(ctx) + return nil +} + +// StopCron will shutdown the Cron tasks +func (tS *TrendS) StopTrendS() { + timeEnd := time.Now().Add(tS.cfg.CoreSCfg().ShutdownTimeout) + + crnctx := tS.crn.Stop() + close(tS.trendStop) + + // Wait for cron + select { + case <-crnctx.Done(): + case <-time.After(time.Until(timeEnd)): + utils.Logger.Warning( + fmt.Sprintf( + "<%s> timeout waiting for Cron to finish", + utils.TrendS)) + return + } + // Wait for backup and other operations + select { + case <-tS.storingStopped: + case <-time.After(time.Until(timeEnd)): + utils.Logger.Warning( + fmt.Sprintf( + "<%s> timeout waiting for TrendS to finish", + utils.TrendS)) + return + } +} + +func (tS *TrendS) Reload(ctx *context.Context) { + crnctx := tS.crn.Stop() + close(tS.trendStop) + <-crnctx.Done() + <-tS.storingStopped + tS.trendStop = make(chan struct{}) + tS.storingStopped = make(chan struct{}) + tS.crn.Start() + go tS.asyncStoreTrends(ctx) +} + +// scheduleAutomaticQueries will schedule the queries at start/reload based on configured +func (tS *TrendS) scheduleAutomaticQueries(ctx *context.Context) error { + schedData := make(map[string][]string) + for k, v := range tS.cfg.TrendSCfg().ScheduledIDs { + schedData[k] = v + } + var tnts []string + if len(schedData) == 0 { + tnts = make([]string, 0) + } + for tnt, tIDs := range schedData { + if len(tIDs) == 0 { + tnts = append(tnts, tnt) + } + } + if tnts != nil { + qrydData, err := tS.dm.GetTrendProfileIDs(ctx, tnts) + if err != nil { + return err + } + for tnt, ids := range qrydData { + schedData[tnt] = ids + } + } + for tnt, tIDs := range schedData { + if _, err := tS.scheduleTrendQueries(ctx, tnt, tIDs); err != nil { + return err + } + } + return nil } // scheduleTrendQueries will schedule/re-schedule specific trend queries -func (tS *TrendS) scheduleTrendQueries(ctx *context.Context, tnt string, tIds []string) (complete bool) { - complete = true - for _, tID := range tIds { +func (tS *TrendS) scheduleTrendQueries(ctx *context.Context, tnt string, tIDs []string) (scheduled int, err error) { + var partial bool + tS.crnTQsMux.Lock() + if _, has := tS.crnTQs[tnt]; !has { + tS.crnTQs[tnt] = make(map[string]cron.EntryID) + } + tS.crnTQsMux.Unlock() + for _, tID := range tIDs { tS.crnTQsMux.RLock() if entryID, has := tS.crnTQs[tnt][tID]; has { - tS.crn.Remove(entryID) + tS.crn.Remove(entryID) // deschedule the query } tS.crnTQsMux.RUnlock() - if tP, err := tS.dm.GetTrendProfile(ctx, tnt, tID); err != nil { + if tP, err := tS.dm.GetTrendProfile(ctx, tnt, tID, true, true, utils.NonTransactional); err != nil { utils.Logger.Warning( fmt.Sprintf( "<%s> failed retrieving TrendProfile with id: <%s:%s> for scheduling, error: <%s>", utils.TrendS, tnt, tID, err.Error())) - complete = false + partial = true } else if entryID, err := tS.crn.AddFunc(tP.Schedule, - func() { tS.computeTrend(tP.Clone()) }); err != nil { + func() { tS.computeTrend(ctx, tP.Clone()) }); err != nil { utils.Logger.Warning( fmt.Sprintf( "<%s> scheduling TrendProfile <%s:%s>, error: <%s>", utils.TrendS, tnt, tID, err.Error())) - complete = false - } else { + partial = true + } else { // log the entry ID for debugging tS.crnTQsMux.Lock() tS.crnTQs[tP.Tenant][tP.ID] = entryID tS.crnTQsMux.Unlock() + scheduled++ } } + if partial { + return 0, utils.ErrPartiallyExecuted + } + return +} + +// V1ScheduleQueries is the query for manually re-/scheduling Trend Queries +func (tS *TrendS) V1ScheduleQueries(ctx *context.Context, args *utils.ArgScheduleTrendQueries, scheduled *int) (err error) { + if sched, errSched := tS.scheduleTrendQueries(ctx, args.Tenant, args.TrendIDs); errSched != nil { + return errSched + } else { + *scheduled = sched + } + return +} + +// V1GetTrend is the API to return the trend Metrics +// The number of runTimes can be filtered based on indexes and times provided as arguments +// +// in this way being possible to work with paginators +func (tS *TrendS) V1GetTrend(ctx *context.Context, arg *utils.ArgGetTrend, retTrend *Trend) (err error) { + if missing := utils.MissingStructFields(arg, []string{utils.ID}); len(missing) != 0 { //Params missing + return utils.NewErrMandatoryIeMissing(missing...) + } + var trnd *Trend + if trnd, err = tS.dm.GetTrend(ctx, arg.Tenant, arg.ID, true, true, utils.NonTransactional); err != nil { + return + } + retTrend.Tenant = trnd.Tenant // avoid vet complaining for mutex copying + retTrend.ID = trnd.ID + startIdx := arg.RunIndexStart + if startIdx > len(trnd.RunTimes) { + startIdx = len(trnd.RunTimes) - 1 + } + endIdx := arg.RunIndexEnd + if endIdx > len(trnd.RunTimes) || + endIdx < startIdx || + endIdx == 0 { + endIdx = len(trnd.RunTimes) + } + runTimes := trnd.RunTimes[startIdx:endIdx] + if len(runTimes) == 0 { + return utils.ErrNotFound + } + var tStart, tEnd time.Time + if arg.RunTimeStart == utils.EmptyString { + tStart = runTimes[0] + } else if tStart, err = utils.ParseTimeDetectLayout(arg.RunTimeStart, tS.cfg.GeneralCfg().DefaultTimezone); err != nil { + return + } + if arg.RunTimeEnd == utils.EmptyString { + tEnd = runTimes[len(runTimes)-1].Add(time.Duration(1)) + } else if tEnd, err = utils.ParseTimeDetectLayout(arg.RunTimeEnd, tS.cfg.GeneralCfg().DefaultTimezone); err != nil { + return + } + retTrend.RunTimes = make([]time.Time, 0, len(runTimes)) + for _, runTime := range runTimes { + if runTime.After(tStart) && runTime.Before(tEnd) { + retTrend.RunTimes = append(retTrend.RunTimes, runTime) + } + } + if len(retTrend.RunTimes) == 0 { // filtered out all + return utils.ErrNotFound + } + retTrend.Metrics = make(map[time.Time]map[string]*MetricWithTrend) + for _, runTime := range retTrend.RunTimes { + retTrend.Metrics[runTime] = trnd.Metrics[runTime] + } + return +} + +func (tS *TrendS) V1GetScheduledTrends(ctx *context.Context, args *utils.ArgScheduledTrends, schedTrends *[]utils.ScheduledTrend) (err error) { + tnt := args.Tenant + if tnt == utils.EmptyString { + tnt = tS.cfg.GeneralCfg().DefaultTenant + } + tS.crnTQsMux.RLock() + defer tS.crnTQsMux.RUnlock() + trendIDsMp, has := tS.crnTQs[tnt] + if !has { + return utils.ErrNotFound + } + var scheduledTrends []utils.ScheduledTrend + var entryIds map[string]cron.EntryID + if len(args.TrendIDPrefixes) == 0 { + entryIds = trendIDsMp + } else { + entryIds = make(map[string]cron.EntryID) + for _, tID := range args.TrendIDPrefixes { + for key, entryID := range trendIDsMp { + if strings.HasPrefix(key, tID) { + entryIds[key] = entryID + } + } + } + } + if len(entryIds) == 0 { + return utils.ErrNotFound + } + var entry cron.Entry + for id, entryID := range entryIds { + entry = tS.crn.Entry(entryID) + if entry.ID == 0 { + continue + } + scheduledTrends = append(scheduledTrends, utils.ScheduledTrend{ + TrendID: id, + Next: entry.Next, + Previous: entry.Prev, + }) + } + slices.SortFunc(scheduledTrends, func(a, b utils.ScheduledTrend) int { + return a.Next.Compare(b.Next) + }) + *schedTrends = scheduledTrends + return nil +} + +func (tS *TrendS) V1GetTrendSummary(ctx *context.Context, arg utils.TenantIDWithAPIOpts, reply *TrendSummary) (err error) { + var trnd *Trend + if trnd, err = tS.dm.GetTrend(ctx, arg.Tenant, arg.ID, true, true, utils.NonTransactional); err != nil { + return + } + trnd.tMux.RLock() + trndS := trnd.asTrendSummary() + trnd.tMux.RUnlock() + *reply = *trndS return } diff --git a/general_tests/all_cfg_rld_it_test.go b/general_tests/all_cfg_rld_it_test.go index eaf63c472..eb09a2622 100644 --- a/general_tests/all_cfg_rld_it_test.go +++ b/general_tests/all_cfg_rld_it_test.go @@ -161,7 +161,7 @@ func testConfigSReload(t *testing.T) { t.Errorf("\nExpected %+v ,\n received: %+v", cfgStr, rpl5) } if testCfgDir == "tutmysql" || testCfgDir == "tutmongo" { - cfgStr = `{"caches":{"partitions":{"*account_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*accounts":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*action_profile_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*action_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*apiban":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"2m0s"},"*attribute_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*attribute_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*caps_events":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*cdr_ids":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"10m0s"},"*charger_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*charger_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*closed_sessions":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"10s"},"*diameter_messages":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"3h0m0s"},"*dispatcher_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*dispatcher_hosts":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*dispatcher_loads":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*dispatcher_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*dispatcher_routes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*dispatchers":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*event_charges":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"10s"},"*event_resources":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*filters":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*load_ids":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*ranking_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*rate_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*rate_profile_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*rate_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*replication_hosts":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*resource_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*resource_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*resources":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*reverse_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*route_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*route_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*rpc_connections":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*rpc_responses":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"2s"},"*sentrypeer":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":true,"ttl":"24h0m0s"},"*stat_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*statqueue_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*statqueues":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*stir":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"3h0m0s"},"*threshold_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*threshold_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*thresholds":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*trend_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*trends":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*uch":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"3h0m0s"}},"remote_conns":[],"replication_conns":[]}}` + cfgStr = `{"caches":{"partitions":{"*account_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*accounts":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*action_profile_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*action_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*apiban":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"2m0s"},"*attribute_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*attribute_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*caps_events":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*cdr_ids":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"10m0s"},"*charger_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*charger_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*closed_sessions":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"10s"},"*diameter_messages":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"3h0m0s"},"*dispatcher_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*dispatcher_hosts":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*dispatcher_loads":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*dispatcher_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*dispatcher_routes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*dispatchers":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*event_charges":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"10s"},"*event_resources":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*filters":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*load_ids":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*ranking_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*rankings":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*rate_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*rate_profile_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*rate_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*replication_hosts":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*resource_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*resource_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*resources":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*reverse_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*route_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*route_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*rpc_connections":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*rpc_responses":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"2s"},"*sentrypeer":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":true,"ttl":"24h0m0s"},"*stat_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*statqueue_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*statqueues":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*stir":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"3h0m0s"},"*threshold_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*threshold_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*thresholds":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*trend_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*trends":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*uch":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"3h0m0s"}},"remote_conns":[],"replication_conns":[]}}` var rpl7 string if err := testRPC.Call(context.Background(), utils.ConfigSv1GetConfigAsJSON, &config.SectionWithAPIOpts{ Tenant: "cgrates.org", @@ -172,7 +172,7 @@ func testConfigSReload(t *testing.T) { t.Errorf("\nExpected %+v ,\n received: %+v", cfgStr, rpl7) } } else if testCfgDir == "tutinternal" { - cfgStr := `{"caches":{"partitions":{"*account_filter_indexes":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*accounts":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*action_profile_filter_indexes":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*action_profiles":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*apiban":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"2m0s"},"*attribute_filter_indexes":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*attribute_profiles":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*caps_events":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*cdr_ids":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"10m0s"},"*charger_filter_indexes":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*charger_profiles":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*closed_sessions":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"10s"},"*diameter_messages":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"3h0m0s"},"*dispatcher_filter_indexes":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*dispatcher_hosts":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*dispatcher_loads":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*dispatcher_profiles":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*dispatcher_routes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*dispatchers":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*event_charges":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"10s"},"*event_resources":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*filters":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*load_ids":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*ranking_profiles":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*rate_filter_indexes":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*rate_profile_filter_indexes":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*rate_profiles":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*replication_hosts":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*resource_filter_indexes":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*resource_profiles":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*resources":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*reverse_filter_indexes":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*route_filter_indexes":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*route_profiles":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*rpc_connections":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*rpc_responses":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"2s"},"*sentrypeer":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":true,"ttl":"24h0m0s"},"*stat_filter_indexes":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*statqueue_profiles":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*statqueues":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*stir":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"3h0m0s"},"*threshold_filter_indexes":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*threshold_profiles":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*thresholds":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*trend_profiles":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*trends":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*uch":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"3h0m0s"}},"remote_conns":[],"replication_conns":[]}}` + cfgStr := `{"caches":{"partitions":{"*account_filter_indexes":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*accounts":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*action_profile_filter_indexes":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*action_profiles":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*apiban":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"2m0s"},"*attribute_filter_indexes":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*attribute_profiles":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*caps_events":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*cdr_ids":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"10m0s"},"*charger_filter_indexes":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*charger_profiles":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*closed_sessions":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"10s"},"*diameter_messages":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"3h0m0s"},"*dispatcher_filter_indexes":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*dispatcher_hosts":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*dispatcher_loads":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*dispatcher_profiles":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*dispatcher_routes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*dispatchers":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*event_charges":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"10s"},"*event_resources":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*filters":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*load_ids":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*ranking_profiles":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*rankings":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*rate_filter_indexes":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*rate_profile_filter_indexes":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*rate_profiles":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*replication_hosts":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*resource_filter_indexes":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*resource_profiles":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*resources":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*reverse_filter_indexes":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*route_filter_indexes":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*route_profiles":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*rpc_connections":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*rpc_responses":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"2s"},"*sentrypeer":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":true,"ttl":"24h0m0s"},"*stat_filter_indexes":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*statqueue_profiles":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*statqueues":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*stir":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"3h0m0s"},"*threshold_filter_indexes":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*threshold_profiles":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*thresholds":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*trend_profiles":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*trends":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*uch":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"3h0m0s"}},"remote_conns":[],"replication_conns":[]}}` var rpl7 string if err := testRPC.Call(context.Background(), utils.ConfigSv1GetConfigAsJSON, &config.SectionWithAPIOpts{ Tenant: "cgrates.org", diff --git a/general_tests/all_sections_cfg_rld_it_test.go b/general_tests/all_sections_cfg_rld_it_test.go index 26a9c8bf1..ae93387e7 100644 --- a/general_tests/all_sections_cfg_rld_it_test.go +++ b/general_tests/all_sections_cfg_rld_it_test.go @@ -219,7 +219,7 @@ func testSectConfigSReloadDataDB(t *testing.T) { } else if reply != utils.OK { t.Errorf("Expected OK received: %+v", reply) } - cfgStr := `{"data_db":{"db_host":"127.0.0.1","db_name":"10","db_password":"","db_port":6379,"db_type":"*internal","db_user":"cgrates","items":{"*account_action_plans":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*account_filter_indexes":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*accounts":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*action_plans":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*action_profile_filter_indexes":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*action_profiles":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*action_triggers":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*actions":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*attribute_filter_indexes":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*attribute_profiles":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*charger_filter_indexes":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*charger_profiles":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*destinations":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*dispatcher_filter_indexes":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*dispatcher_hosts":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*dispatcher_profiles":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*filters":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*indexes":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*load_ids":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*ranking_profiles":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*rate_filter_indexes":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*rate_profile_filter_indexes":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*rate_profiles":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*rating_plans":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*rating_profiles":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*resource_filter_indexes":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*resource_profiles":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*resources":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*reverse_destinations":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*reverse_filter_indexes":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*route_filter_indexes":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*route_profiles":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*stat_filter_indexes":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*statqueue_profiles":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*statqueues":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*threshold_filter_indexes":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*threshold_profiles":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*thresholds":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*trend_profiles":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*trends":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*versions":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false}},"opts":{"mongoConnScheme":"mongodb","mongoQueryTimeout":"10s","redisCACertificate":"","redisClientCertificate":"","redisClientKey":"","redisCluster":false,"redisClusterOndownDelay":"0s","redisClusterSync":"5s","redisConnectAttempts":20,"redisConnectTimeout":"0s","redisMaxConns":10,"redisPoolPipelineLimit":0,"redisPoolPipelineWindow":"150µs","redisReadTimeout":"0s","redisSentinel":"","redisTLS":false,"redisWriteTimeout":"0s"},"remote_conn_id":"","remote_conns":[],"replication_cache":"","replication_conns":[],"replication_filtered":false}}` + cfgStr := `{"data_db":{"db_host":"127.0.0.1","db_name":"10","db_password":"","db_port":6379,"db_type":"*internal","db_user":"cgrates","items":{"*account_action_plans":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*account_filter_indexes":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*accounts":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*action_plans":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*action_profile_filter_indexes":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*action_profiles":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*action_triggers":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*actions":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*attribute_filter_indexes":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*attribute_profiles":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*charger_filter_indexes":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*charger_profiles":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*destinations":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*dispatcher_filter_indexes":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*dispatcher_hosts":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*dispatcher_profiles":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*filters":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*indexes":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*load_ids":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*ranking_profiles":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*rankings":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*rate_filter_indexes":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*rate_profile_filter_indexes":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*rate_profiles":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*rating_plans":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*rating_profiles":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*resource_filter_indexes":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*resource_profiles":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*resources":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*reverse_destinations":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*reverse_filter_indexes":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*route_filter_indexes":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*route_profiles":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*stat_filter_indexes":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*statqueue_profiles":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*statqueues":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*threshold_filter_indexes":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*threshold_profiles":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*thresholds":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*trend_profiles":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*trends":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*versions":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false}},"opts":{"mongoConnScheme":"mongodb","mongoQueryTimeout":"10s","redisCACertificate":"","redisClientCertificate":"","redisClientKey":"","redisCluster":false,"redisClusterOndownDelay":"0s","redisClusterSync":"5s","redisConnectAttempts":20,"redisConnectTimeout":"0s","redisMaxConns":10,"redisPoolPipelineLimit":0,"redisPoolPipelineWindow":"150µs","redisReadTimeout":"0s","redisSentinel":"","redisTLS":false,"redisWriteTimeout":"0s"},"remote_conn_id":"","remote_conns":[],"replication_cache":"","replication_conns":[],"replication_filtered":false}}` var rpl string if err := testSectRPC.Call(context.Background(), utils.ConfigSv1GetConfigAsJSON, &config.SectionWithAPIOpts{ Tenant: "cgrates.org", @@ -317,7 +317,7 @@ func testSectConfigSReloadCaches(t *testing.T) { } else if reply != utils.OK { t.Errorf("Expected OK received: %+v", reply) } - cfgStr := `{"caches":{"partitions":{"*account_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*accounts":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*action_profile_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*action_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*apiban":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"2m0s"},"*attribute_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*attribute_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*caps_events":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*cdr_ids":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"10m0s"},"*charger_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*charger_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*closed_sessions":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"10s"},"*diameter_messages":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"3h0m0s"},"*dispatcher_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*dispatcher_hosts":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*dispatcher_loads":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*dispatcher_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*dispatcher_routes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*dispatchers":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*event_charges":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"10s"},"*event_resources":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*filters":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*load_ids":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*ranking_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*rate_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*rate_profile_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*rate_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*replication_hosts":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*resource_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*resource_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*resources":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*reverse_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*route_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*route_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*rpc_connections":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*rpc_responses":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"2s"},"*sentrypeer":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":true,"ttl":"24h0m0s"},"*stat_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*statqueue_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*statqueues":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*stir":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"3h0m0s"},"*threshold_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*threshold_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*thresholds":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*trend_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*trends":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*uch":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"3h0m0s"}},"remote_conns":[],"replication_conns":[]}}` + cfgStr := `{"caches":{"partitions":{"*account_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*accounts":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*action_profile_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*action_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*apiban":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"2m0s"},"*attribute_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*attribute_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*caps_events":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*cdr_ids":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"10m0s"},"*charger_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*charger_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*closed_sessions":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"10s"},"*diameter_messages":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"3h0m0s"},"*dispatcher_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*dispatcher_hosts":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*dispatcher_loads":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*dispatcher_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*dispatcher_routes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*dispatchers":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*event_charges":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"10s"},"*event_resources":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*filters":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*load_ids":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*ranking_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*rankings":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*rate_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*rate_profile_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*rate_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*replication_hosts":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*resource_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*resource_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*resources":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*reverse_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*route_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*route_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*rpc_connections":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*rpc_responses":{"limit":0,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"2s"},"*sentrypeer":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":true,"ttl":"24h0m0s"},"*stat_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*statqueue_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*statqueues":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*stir":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"3h0m0s"},"*threshold_filter_indexes":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*threshold_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*thresholds":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*trend_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*trends":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*uch":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"3h0m0s"}},"remote_conns":[],"replication_conns":[]}}` var rpl string if err := testSectRPC.Call(context.Background(), utils.ConfigSv1GetConfigAsJSON, &config.SectionWithAPIOpts{ Tenant: "cgrates.org", diff --git a/general_tests/tut_smgeneric_it_test.go b/general_tests/tut_smgeneric_it_test.go index 8c94e5c1e..42bed620f 100644 --- a/general_tests/tut_smgeneric_it_test.go +++ b/general_tests/tut_smgeneric_it_test.go @@ -172,6 +172,8 @@ func testTutSMGCacheStats(t *testing.T) { expectedStats[utils.CacheChargerFilterIndexes].Groups = 1 expectedStats[utils.CacheRankingProfiles].Items = 0 expectedStats[utils.CacheRankingProfiles].Groups = 0 + expectedStats[utils.CacheRankings].Items = 0 + expectedStats[utils.CacheRankings].Groups = 0 expectedStats[utils.CacheAttributeFilterIndexes].Items = 10 expectedStats[utils.CacheAttributeFilterIndexes].Groups = 1 expectedStats[utils.CacheReverseFilterIndexes].Items = 19 diff --git a/services/cgr-engine.go b/services/cgr-engine.go index 8059aa4ec..37609504c 100644 --- a/services/cgr-engine.go +++ b/services/cgr-engine.go @@ -180,6 +180,8 @@ func (cgr *CGREngine) InitServices(setVersions bool, cpuPrfFl *os.File) { cgr.cM.AddInternalConn(utils.ConcatenatedKey(rpcclient.BiRPCInternal, utils.MetaSessionS), utils.SessionSv1, iSessionSCh) cgr.cM.AddInternalConn(utils.ConcatenatedKey(utils.MetaInternal, utils.MetaSessionS), utils.SessionSv1, iSessionSCh) cgr.cM.AddInternalConn(utils.ConcatenatedKey(utils.MetaInternal, utils.MetaStats), utils.StatSv1, iStatSCh) + cgr.cM.AddInternalConn(utils.ConcatenatedKey(utils.MetaInternal, utils.MetaRankings), utils.RankingSv1, iRankingSCh) + cgr.cM.AddInternalConn(utils.ConcatenatedKey(utils.MetaInternal, utils.MetaTrends), utils.TrendSv1, iTrendSCh) cgr.cM.AddInternalConn(utils.ConcatenatedKey(utils.MetaInternal, utils.MetaRoutes), utils.RouteSv1, iRouteSCh) cgr.cM.AddInternalConn(utils.ConcatenatedKey(utils.MetaInternal, utils.MetaThresholds), utils.ThresholdSv1, iThresholdSCh) cgr.cM.AddInternalConn(utils.ConcatenatedKey(utils.MetaInternal, utils.MetaServiceManager), utils.ServiceManagerV1, cgr.iServeManagerCh) diff --git a/services/rankings.go b/services/rankings.go index 3aa6d4c67..1f07ebf40 100644 --- a/services/rankings.go +++ b/services/rankings.go @@ -71,8 +71,10 @@ func (ran *RankingService) Start(ctx *context.Context, _ context.CancelFunc) (er return utils.ErrServiceAlreadyRunning } ran.srvDep[utils.DataDB].Add(1) - ran.srvDep[utils.DataDB].Add(1) - if err = ran.cacheS.WaitToPrecache(ctx, utils.CacheTrendProfiles); err != nil { + if err = ran.cacheS.WaitToPrecache(ctx, + utils.CacheRankingProfiles, + utils.CacheRankings, + ); err != nil { return err } var datadb *engine.DataManager @@ -84,10 +86,19 @@ func (ran *RankingService) Start(ctx *context.Context, _ context.CancelFunc) (er if filterS, err = waitForFilterS(ctx, ran.filterSChan); err != nil { return } - ran.ran = engine.NewRankingService(datadb, ran.cfg, filterS, ran.connMgr) + ran.Lock() + defer ran.Unlock() + ran.ran = engine.NewRankingS(datadb, ran.connMgr, filterS, ran.cfg) + utils.Logger.Info(fmt.Sprintf("<%s> starting <%s> subsystem", utils.CoreS, utils.RankingS)) - srv, _ := engine.NewService(ran.ran) + if err := ran.ran.StartRankingS(ctx); err != nil { + return err + } + srv, err := engine.NewService(ran.ran) + if err != nil { + return err + } if !ran.cfg.DispatcherSCfg().Enabled { for _, s := range srv { ran.server.RpcRegister(s) @@ -99,6 +110,9 @@ func (ran *RankingService) Start(ctx *context.Context, _ context.CancelFunc) (er // Reload handles the change of config func (ran *RankingService) Reload(ctx *context.Context, _ context.CancelFunc) (err error) { + ran.Lock() + ran.ran.Reload(ctx) + ran.Unlock() return } @@ -107,6 +121,8 @@ func (ran *RankingService) Shutdown() (err error) { defer ran.srvDep[utils.DataDB].Done() ran.Lock() defer ran.Unlock() + ran.ran.StopRankingS() + ran.ran = nil <-ran.connChan ran.server.RpcUnregisterName(utils.RankingSv1) return @@ -114,9 +130,7 @@ func (ran *RankingService) Shutdown() (err error) { // IsRunning returns if the service is running func (ran *RankingService) IsRunning() bool { - ran.RLock() - defer ran.RUnlock() - return false + return ran != nil && ran.ran != nil } // ServiceName returns the service name diff --git a/services/rankings_test.go b/services/rankings_test.go index 6b02dd85e..d989c0c5c 100644 --- a/services/rankings_test.go +++ b/services/rankings_test.go @@ -23,7 +23,6 @@ import ( "testing" "github.com/cgrates/birpc" - "github.com/cgrates/birpc/context" "github.com/cgrates/cgrates/config" "github.com/cgrates/cgrates/cores" "github.com/cgrates/cgrates/engine" @@ -63,16 +62,3 @@ func TestRankingServiceName(t *testing.T) { t.Errorf("Expected service name '%s', but got '%s'", expectedServiceName, serviceName) } } - -func TestRankingServiceReload(t *testing.T) { - - rankingService := &RankingService{} - - ctx := &context.Context{} - - err := rankingService.Reload(ctx, nil) - - if err != nil { - t.Errorf("Expected no error, but got: %v", err) - } -} diff --git a/services/trends.go b/services/trends.go index ecf382dc9..ebb775e5c 100644 --- a/services/trends.go +++ b/services/trends.go @@ -64,66 +64,77 @@ type TrendService struct { } // Start should handle the sercive start -func (tr *TrendService) Start(ctx *context.Context, _ context.CancelFunc) (err error) { - if tr.IsRunning() { +func (trs *TrendService) Start(ctx *context.Context, _ context.CancelFunc) (err error) { + if trs.IsRunning() { return utils.ErrServiceAlreadyRunning } - tr.srvDep[utils.DataDB].Add(1) - if err = tr.cacheS.WaitToPrecache(ctx, utils.CacheTrendProfiles); err != nil { + trs.srvDep[utils.DataDB].Add(1) + if err = trs.cacheS.WaitToPrecache(ctx, + utils.CacheTrendProfiles, + utils.CacheTrends, + ); err != nil { return err } var datadb *engine.DataManager - if datadb, err = tr.dm.WaitForDM(ctx); err != nil { + if datadb, err = trs.dm.WaitForDM(ctx); err != nil { return } var filterS *engine.FilterS - if filterS, err = waitForFilterS(ctx, tr.filterSChan); err != nil { + if filterS, err = waitForFilterS(ctx, trs.filterSChan); err != nil { return } - tr.trs = engine.NewTrendService(datadb, tr.cfg, filterS, tr.connMgr) + trs.Lock() + defer trs.Unlock() + trs.trs = engine.NewTrendService(datadb, trs.cfg, filterS, trs.connMgr) utils.Logger.Info(fmt.Sprintf("<%s> starting <%s> subsystem", utils.CoreS, utils.TrendS)) - srv, err := engine.NewService(tr.trs) + if err := trs.trs.StartTrendS(ctx); err != nil { + return err + } + srv, err := engine.NewService(trs.trs) if err != nil { return err } - if !tr.cfg.DispatcherSCfg().Enabled { + if !trs.cfg.DispatcherSCfg().Enabled { for _, s := range srv { - tr.server.RpcRegister(s) + trs.server.RpcRegister(s) } } - tr.connChan <- tr.anz.GetInternalCodec(srv, utils.Trends) + trs.connChan <- trs.anz.GetInternalCodec(srv, utils.Trends) return nil } // Reload handles the change of config -func (tr *TrendService) Reload(ctx *context.Context, _ context.CancelFunc) (err error) { +func (trs *TrendService) Reload(ctx *context.Context, _ context.CancelFunc) (err error) { + trs.Lock() + trs.trs.Reload(ctx) + trs.Unlock() return } // Shutdown stops the service -func (tr *TrendService) Shutdown() (err error) { - defer tr.srvDep[utils.DataDB].Done() - tr.Lock() - defer tr.Unlock() - <-tr.connChan - tr.server.RpcUnregisterName(utils.TrendSv1) +func (trs *TrendService) Shutdown() (err error) { + defer trs.srvDep[utils.DataDB].Done() + trs.Lock() + defer trs.Unlock() + trs.trs.StopTrendS() + trs.trs = nil + <-trs.connChan + trs.server.RpcUnregisterName(utils.TrendSv1) return } // IsRunning returns if the service is running -func (tr *TrendService) IsRunning() bool { - tr.RLock() - defer tr.RUnlock() - return tr != nil && tr.trs != nil +func (trs *TrendService) IsRunning() bool { + return trs != nil && trs.trs != nil } // ServiceName returns the service name -func (tr *TrendService) ServiceName() string { +func (trs *TrendService) ServiceName() string { return utils.TrendS } // ShouldRun returns if the service should be running -func (tr *TrendService) ShouldRun() bool { - return tr.cfg.TrendSCfg().Enabled +func (trs *TrendService) ShouldRun() bool { + return trs.cfg.TrendSCfg().Enabled } diff --git a/services/trends_test.go b/services/trends_test.go index eb8967823..43be6ec66 100644 --- a/services/trends_test.go +++ b/services/trends_test.go @@ -23,7 +23,6 @@ import ( "testing" "github.com/cgrates/birpc" - "github.com/cgrates/birpc/context" "github.com/cgrates/cgrates/config" "github.com/cgrates/cgrates/cores" "github.com/cgrates/cgrates/engine" @@ -83,15 +82,3 @@ func TestTrendServiceServiceName(t *testing.T) { t.Errorf("Expected service name to be %s, but got %s", expected, serviceName) } } - -func TestTrendService_Reload(t *testing.T) { - tr := &TrendService{} - - ctx := context.Background() - - err := tr.Reload(ctx, nil) - - if err != nil { - t.Errorf("Expected no error, but got: %v", err) - } -} diff --git a/tpes/tpe_trends.go b/tpes/tpe_trends.go index 3ac3727c5..393d5155e 100644 --- a/tpes/tpe_trends.go +++ b/tpes/tpe_trends.go @@ -50,7 +50,7 @@ func (tpSts TPTrends) exportItems(ctx *context.Context, wrtr io.Writer, tnt stri } for _, trendsID := range itmIDs { var trendPrf *engine.TrendProfile - trendPrf, err = tpSts.dm.GetTrendProfile(ctx, tnt, trendsID) + trendPrf, err = tpSts.dm.GetTrendProfile(ctx, tnt, trendsID, true, true, utils.NonTransactional) if err != nil { if err.Error() == utils.ErrNotFound.Error() { return fmt.Errorf("<%s> cannot find TrendProfile with id: <%v>", err, trendsID) diff --git a/utils/apitpdata.go b/utils/apitpdata.go index c8119356c..13eccf639 100644 --- a/utils/apitpdata.go +++ b/utils/apitpdata.go @@ -255,11 +255,12 @@ type TPRankingProfile struct { TPid string Tenant string ID string - QueryInterval string + Schedule string StatIDs []string MetricIDs []string Sorting string SortingParameters []string + Stored bool ThresholdIDs []string } @@ -823,3 +824,43 @@ type TPBalanceUnitFactor struct { func (unitFactor *TPBalanceUnitFactor) AsString() string { return strings.Join(unitFactor.FilterIDs, ANDSep) + InfieldSep + strconv.FormatFloat(unitFactor.Factor, 'f', -1, 64) } + +type ArgScheduleTrendQueries struct { + TenantIDWithAPIOpts + TrendIDs []string +} +type ArgScheduledTrends struct { + TenantIDWithAPIOpts + TrendIDPrefixes []string +} + +type ArgGetTrend struct { + TenantWithAPIOpts + ID string + RunIndexStart int + RunIndexEnd int + RunTimeStart string + RunTimeEnd string +} + +type ScheduledTrend struct { + TrendID string + Next time.Time + Previous time.Time +} + +type ArgScheduleRankingQueries struct { + TenantIDWithAPIOpts + RankingIDs []string +} + +type ArgScheduledRankings struct { + TenantIDWithAPIOpts + RankingIDPrefixes []string +} + +type ScheduledRanking struct { + RankingID string + Next time.Time + Previous time.Time +} diff --git a/utils/consts.go b/utils/consts.go index 5d5c21345..6549bdf7c 100644 --- a/utils/consts.go +++ b/utils/consts.go @@ -39,7 +39,7 @@ var ( DataDBPartitions = NewStringSet([]string{ CacheResourceProfiles, CacheResources, CacheEventResources, CacheStatQueueProfiles, CacheStatQueues, CacheThresholdProfiles, CacheThresholds, CacheFilters, CacheRouteProfiles, CacheAttributeProfiles, CacheTrendProfiles, - CacheChargerProfiles, CacheActionProfiles, CacheDispatcherProfiles, CacheDispatcherHosts, CacheRankingProfiles, CacheTrends, + CacheChargerProfiles, CacheActionProfiles, CacheDispatcherProfiles, CacheDispatcherHosts, CacheRankingProfiles, CacheRankings, CacheTrends, CacheResourceFilterIndexes, CacheStatFilterIndexes, CacheThresholdFilterIndexes, CacheRouteFilterIndexes, CacheAttributeFilterIndexes, CacheChargerFilterIndexes, CacheDispatcherFilterIndexes, CacheLoadIDs, CacheRateProfiles, CacheRateProfilesFilterIndexes, CacheRateFilterIndexes, @@ -271,6 +271,8 @@ const ( CreateCDRsTablesSQL = "create_cdrs_tables.sql" CreateTariffPlanTablesSQL = "create_tariffplan_tables.sql" TestSQL = "TEST_SQL" + MetaAsc = "*asc" + MetaDesc = "*desc" MetaConstant = "*constant" MetaPositive = "*positive" MetaNegative = "*negative" @@ -481,6 +483,8 @@ const ( AccountUpdate = "AccountUpdate" BalanceUpdate = "BalanceUpdate" StatUpdate = "StatUpdate" + TrendUpdate = "TrendUpdate" + RankingUpdate = "RankingUpdate" ResourceUpdate = "ResourceUpdate" CDRKey = "CDR" CDRs = "CDRs" @@ -576,6 +580,10 @@ const ( RouteRateProfileIDs = "RouteRateProfileIDs" RouteStatIDs = "RouteStatIDs" StatIDs = "StatIDs" + SortedStatIDs = "SortedStatIDs" + LastUpdate = "LastUpdate" + TrendID = "TrendID" + RankingID = "RankingID" RouteWeights = "RouteWeights" RouteParameters = "RouteParameters" RouteBlockers = "RouteBlockers" @@ -1159,6 +1167,7 @@ const ( ReplicatorSv1GetThreshold = "ReplicatorSv1.GetThreshold" ReplicatorSv1GetThresholdProfile = "ReplicatorSv1.GetThresholdProfile" ReplicatorSv1GetStatQueueProfile = "ReplicatorSv1.GetStatQueueProfile" + ReplicatorSv1GetRanking = "ReplicatorSv1.GetRanking" ReplicatorSv1GetRankingProfile = "ReplicatorSv1.GetRankingProfile" ReplicatorSv1GetTrendProfile = "ReplicatorSv1.GetTrendProfile" ReplicatorSv1GetTrend = "ReplicatorSv1.GetTrend" @@ -1179,6 +1188,7 @@ const ( ReplicatorSv1SetFilter = "ReplicatorSv1.SetFilter" ReplicatorSv1SetStatQueueProfile = "ReplicatorSv1.SetStatQueueProfile" ReplicatorSv1SetRankingProfile = "ReplicatorSv1.SetRankingProfile" + ReplicatorSv1SetRanking = "ReplicatorSv1.SetRanking" ReplicatorSv1SetTrendProfile = "ReplicatorSv1.SetTrendProfile" ReplicatorSv1SetTrend ReplicatorSv1SetResource = "ReplicatorSv1.SetResource" @@ -1199,6 +1209,7 @@ const ( ReplicatorSv1RemoveThresholdProfile = "ReplicatorSv1.RemoveThresholdProfile" ReplicatorSv1RemoveStatQueueProfile = "ReplicatorSv1.RemoveStatQueueProfile" ReplicatorSv1RemoveRankingProfile = "ReplicatorSv1.RemoveRankingProfile" + ReplicatorSv1RemoveRanking = "ReplicatorSv1.RemoveRanking" ReplicatorSv1RemoveTrendProfile = "ReplicatorSv1.RemoveTrendProfile" ReplicatorSv1RemoveTrend = "ReplicatorSv1.RemoveTrend" ReplicatorSv1RemoveResource = "ReplicatorSv1.RemoveResource" @@ -1500,6 +1511,11 @@ const ( AdminSv1GetRankingProfiles = "AdminSv1.GetRankingProfiles" AdminSv1GetRankingProfileIDs = "AdminSv1.GetRankingProfileIDs" AdminSv1GetRankingProfilesCount = "AdminSv1.GetRankingProfilesCount" + RankingSv1Ping = "RankingSv1.Ping" + RankingSv1GetRanking = "RankingSv1.GetRanking" + RankingSv1GetSchedule = "RankingSv1.GetSchedule" + RankingSv1ScheduleQueries = "RankingSv1.ScheduleQueries" + RankingSv1GetRankingSummary = "RankingSv1.GetRankingSummary" ) // TrendS APIs @@ -1510,6 +1526,11 @@ const ( AdminSv1GetTrendProfiles = "AdminSv1.GetTrendProfiles" AdminSv1GetTrendProfileIDs = "AdminSv1.GetTrendProfileIDs" AdminSv1GetTrendProfilesCount = "AdminSv1.GetTrendProfilesCount" + TrendSv1Ping = "TrendSv1.Ping" + TrendSv1ScheduleQueries = "TrendSv1.ScheduleQueries" + TrendSv1GetTrend = "TrendSv1.GetTrend" + TrendSv1GetScheduledTrends = "TrendSv1.GetScheduledTrends" + TrendSv1GetTrendSummary = "TrendSv1.GetTrendSummary" ) // ResourceS APIs @@ -1754,6 +1775,7 @@ const ( CacheStatQueueProfiles = "*statqueue_profiles" CacheStatQueues = "*statqueues" CacheRankingProfiles = "*ranking_profiles" + CacheRankings = "*rankings" CacheTrendProfiles = "*trend_profiles" CacheTrends = "*trends" CacheThresholdProfiles = "*threshold_profiles" @@ -1812,6 +1834,7 @@ const ( RouteFilterIndexes = "rti_" RateProfilesFilterIndexPrfx = "rpi_" RateFilterIndexPrfx = "rri_" + RankingPrefix = "rnk_" ActionProfilesFilterIndexPrfx = "aci_" AccountFilterIndexPrfx = "ani_" FilterIndexPrfx = "fii_"