diff --git a/apier/v1/apier_it_test.go b/apier/v1/apier_it_test.go index 96127e029..366c04651 100644 --- a/apier/v1/apier_it_test.go +++ b/apier/v1/apier_it_test.go @@ -1701,7 +1701,7 @@ func testApierResetDataAfterLoadFromFolder(t *testing.T) { expStats[utils.CacheRouteProfiles].Items = 2 expStats[utils.CacheThresholdProfiles].Items = 1 expStats[utils.CacheThresholds].Items = 1 - expStats[utils.CacheLoadIDs].Items = 34 + expStats[utils.CacheLoadIDs].Items = 35 expStats[utils.CacheTimings].Items = 12 expStats[utils.CacheThresholdFilterIndexes].Items = 5 expStats[utils.CacheThresholdFilterIndexes].Groups = 1 @@ -1716,6 +1716,8 @@ func testApierResetDataAfterLoadFromFolder(t *testing.T) { expStats[utils.CacheReverseFilterIndexes].Items = 10 expStats[utils.CacheReverseFilterIndexes].Groups = 7 expStats[utils.CacheRankingProfiles].Items = 1 + expStats[utils.CacheTrendProfiles].Items = 1 + expStats[utils.CacheTrends].Items = 1 if err := rater.Call(context.Background(), utils.CacheSv1GetCacheStats, new(utils.AttrCacheIDsWithAPIOpts), &rcvStats); err != nil { t.Error(err) diff --git a/apier/v1/caches_it_test.go b/apier/v1/caches_it_test.go index 83ed535e4..7cb15e6d6 100644 --- a/apier/v1/caches_it_test.go +++ b/apier/v1/caches_it_test.go @@ -163,7 +163,7 @@ func testCacheSAfterLoadFromFolder(t *testing.T) { expStats[utils.CacheRouteProfiles].Items = 2 expStats[utils.CacheThresholdProfiles].Items = 1 expStats[utils.CacheThresholds].Items = 1 - expStats[utils.CacheLoadIDs].Items = 34 + expStats[utils.CacheLoadIDs].Items = 35 expStats[utils.CacheTimings].Items = 12 expStats[utils.CacheThresholdFilterIndexes].Items = 5 expStats[utils.CacheThresholdFilterIndexes].Groups = 1 @@ -178,6 +178,8 @@ func testCacheSAfterLoadFromFolder(t *testing.T) { expStats[utils.CacheReverseFilterIndexes].Items = 10 expStats[utils.CacheReverseFilterIndexes].Groups = 7 expStats[utils.CacheRankingProfiles].Items = 1 + expStats[utils.CacheTrendProfiles].Items = 1 + expStats[utils.CacheTrends].Items = 1 if err := chcRPC.Call(context.Background(), utils.CacheSv1GetCacheStats, &utils.AttrCacheIDsWithAPIOpts{}, &rcvStats); err != nil { t.Error(err) @@ -232,7 +234,7 @@ func testCacheSReload(t *testing.T) { expStats[utils.CacheRouteProfiles].Items = 2 expStats[utils.CacheThresholdProfiles].Items = 1 expStats[utils.CacheThresholds].Items = 1 - expStats[utils.CacheLoadIDs].Items = 34 + expStats[utils.CacheLoadIDs].Items = 35 expStats[utils.CacheTimings].Items = 12 expStats[utils.CacheThresholdFilterIndexes].Items = 5 expStats[utils.CacheThresholdFilterIndexes].Groups = 1 @@ -247,6 +249,8 @@ func testCacheSReload(t *testing.T) { expStats[utils.CacheReverseFilterIndexes].Items = 10 expStats[utils.CacheReverseFilterIndexes].Groups = 7 expStats[utils.CacheRankingProfiles].Items = 1 + expStats[utils.CacheTrends].Items = 1 + expStats[utils.CacheTrendProfiles].Items = 1 if err := chcRPC.Call(context.Background(), utils.CacheSv1GetCacheStats, &utils.AttrCacheIDsWithAPIOpts{}, &rcvStats); err != nil { t.Error(err) diff --git a/apier/v1/precache_it_test.go b/apier/v1/precache_it_test.go index bd6bbcf35..b04010482 100644 --- a/apier/v1/precache_it_test.go +++ b/apier/v1/precache_it_test.go @@ -228,6 +228,7 @@ func testPrecacheGetCacheStatsAfterRestart(t *testing.T) { utils.MetaAPIBan: {}, utils.CacheReplicationHosts: {}, utils.CacheRadiusPackets: {}, + utils.CacheRankings: {}, utils.CacheRankingProfiles: {}, utils.CacheTrends: {}, utils.CacheTrendProfiles: {}, diff --git a/apier/v1/rankings.go b/apier/v1/rankings.go index cb1814731..28edfd331 100644 --- a/apier/v1/rankings.go +++ b/apier/v1/rankings.go @@ -142,3 +142,15 @@ type RankingSv1 struct { 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/apier/v1/replicator.go b/apier/v1/replicator.go index 12ee4907f..3d1fa4300 100644 --- a/apier/v1/replicator.go +++ b/apier/v1/replicator.go @@ -152,6 +152,7 @@ func (rplSv1 *ReplicatorSv1) GetRanking(ctx *context.Context, tntID *utils.Tenan reply.ID = rcv.ID reply.Tenant = rcv.Tenant reply.Sorting = rcv.Sorting + reply.LastUpdate = rcv.LastUpdate reply.Metrics = rcv.Metrics reply.SortedStatIDs = rcv.SortedStatIDs reply.SortingParameters = rcv.SortingParameters diff --git a/cmd/cgr-engine/cgr-engine.go b/cmd/cgr-engine/cgr-engine.go index 80c760bea..9321c70bb 100644 --- a/cmd/cgr-engine/cgr-engine.go +++ b/cmd/cgr-engine/cgr-engine.go @@ -588,7 +588,7 @@ func main() { internalStatSChan, connManager, anz, srvDep) trS := services.NewTrendService(cfg, dmService, cacheS, filterSChan, server, internalTrendSChan, connManager, anz, srvDep) - sgS := services.NewRankingService(cfg, dmService, cacheS, filterSChan, server, + rnS := services.NewRankingService(cfg, dmService, cacheS, filterSChan, server, internalRankingSChan, connManager, anz, srvDep) reS := services.NewResourceService(cfg, dmService, cacheS, filterSChan, server, internalResourceSChan, connManager, anz, srvDep) @@ -615,7 +615,7 @@ func main() { ldrs := services.NewLoaderService(cfg, dmService, filterSChan, server, internalLoaderSChan, connManager, anz, srvDep) - srvManager.AddServices(gvService, attrS, chrS, tS, stS, trS, sgS, reS, routeS, schS, rals, + srvManager.AddServices(gvService, attrS, chrS, tS, stS, trS, rnS, reS, routeS, schS, rals, apiSv1, apiSv2, cdrS, smg, coreS, services.NewDNSAgent(cfg, filterSChan, shdChan, connManager, srvDep), services.NewFreeswitchAgent(cfg, shdChan, connManager, srvDep), @@ -660,6 +660,7 @@ func main() { engine.IntRPC.AddInternalRPCClient(utils.SessionSv1, internalSessionSChan) engine.IntRPC.AddInternalRPCClient(utils.StatSv1, internalStatSChan) engine.IntRPC.AddInternalRPCClient(utils.TrendSv1, internalTrendSChan) + engine.IntRPC.AddInternalRPCClient(utils.RankingSv1, internalRankingSChan) engine.IntRPC.AddInternalRPCClient(utils.RouteSv1, internalRouteSChan) engine.IntRPC.AddInternalRPCClient(utils.ThresholdSv1, internalThresholdSChan) engine.IntRPC.AddInternalRPCClient(utils.ServiceManagerV1, internalServeManagerChan) diff --git a/config/config_defaults.go b/config/config_defaults.go index 91064bc07..ecabef89e 100644 --- a/config/config_defaults.go +++ b/config/config_defaults.go @@ -114,6 +114,7 @@ const CGRATES_CFG_JSON = ` "*resource_profiles": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false}, "*resources": {"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}, "*statqueue_profiles": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false}, @@ -297,6 +298,7 @@ const CGRATES_CFG_JSON = ` "*trend_profiles": {"limit": -1, "ttl": "", "static_ttl": false, "precache": false, "remote":false, "replicate": false}, // control trend profiles caching "*trends": {"limit": -1, "ttl": "", "static_ttl": false, "precache": false, "remote":false, "replicate": false}, // control trends caching "*ranking_profiles": {"limit": -1, "ttl": "", "static_ttl": false, "precache": false, "remote":false, "replicate": false}, // ranking profiles + "*rankings": {"limit": -1, "ttl": "", "static_ttl": false, "precache": false, "remote":false, "replicate": false}, // controle rankings caching "*statqueue_profiles": {"limit": -1, "ttl": "", "static_ttl": false, "precache": false, "remote":false, "replicate": false}, // statqueue profiles "*statqueues": {"limit": -1, "ttl": "", "static_ttl": false, "precache": false, "remote":false, "replicate": false}, // statqueues with metrics "*threshold_profiles": {"limit": -1, "ttl": "", "static_ttl": false, "precache": false, "remote":false, "replicate": false}, // control threshold profiles caching diff --git a/config/config_json_test.go b/config/config_json_test.go index 394574126..4c910f775 100644 --- a/config/config_json_test.go +++ b/config/config_json_test.go @@ -138,6 +138,9 @@ func TestCacheJsonCfg(t *testing.T) { utils.CacheTrends: {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.CacheStatQueues: {Limit: utils.IntPointer(-1), Ttl: utils.StringPointer(""), Static_ttl: utils.BoolPointer(false), Precache: utils.BoolPointer(false), Remote: utils.BoolPointer(false), Replicate: utils.BoolPointer(false)}, @@ -437,6 +440,13 @@ func TestDfDataDbJsonCfg(t *testing.T) { Ttl: utils.StringPointer(utils.EmptyString), Static_ttl: utils.BoolPointer(false), }, + utils.MetaRankings: { + Replicate: utils.BoolPointer(false), + Remote: utils.BoolPointer(false), + Limit: utils.IntPointer(-1), + Ttl: utils.StringPointer(utils.EmptyString), + Static_ttl: utils.BoolPointer(false), + }, utils.MetaThresholds: { Replicate: utils.BoolPointer(false), Remote: utils.BoolPointer(false), diff --git a/config/config_test.go b/config/config_test.go index 2d37282d7..fab177b3f 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -560,6 +560,8 @@ func TestCgrCfgJSONDefaultsCacheCFG(t *testing.T) { TTL: 0, Remote: false, StaticTTL: false, Precache: false}, utils.CacheTrends: {Limit: -1, TTL: 0, Remote: false, StaticTTL: false, Precache: false}, + utils.CacheRankings: {Limit: -1, + TTL: 0, Remote: false, StaticTTL: false, Precache: false}, utils.CacheThresholdProfiles: {Limit: -1, TTL: 0, Remote: false, StaticTTL: false, Precache: false}, utils.CacheThresholds: {Limit: -1, @@ -4631,7 +4633,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_action_plans":{"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_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},"*load_ids":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*ranking_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},"*sessions_backup":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*shared_groups":{"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},"*timings":{"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_action_plans":{"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_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},"*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},"*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},"*sessions_backup":{"limit":-1,"remote":false,"replicate":false,"static_ttl":false},"*shared_groups":{"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},"*timings":{"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{Section: DATADB_JSN}, &reply); err != nil { t.Error(err) @@ -4664,7 +4666,7 @@ func TestV1GetConfigAsJSONTls(t *testing.T) { func TestV1GetConfigAsJSONTCache(t *testing.T) { var reply string - expected := `{"caches":{"partitions":{"*account_action_plans":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*action_plans":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*action_triggers":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*actions":{"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"},"*destinations":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*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},"*radius_packets":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"3h0m0s"},"*ranking_profiles":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*rating_plans":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*rating_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_destinations":{"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"},"*shared_groups":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*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},"*timings":{"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_action_plans":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*action_plans":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*action_triggers":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*actions":{"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"},"*destinations":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*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},"*radius_packets":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false,"ttl":"3h0m0s"},"*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},"*rating_plans":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*rating_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_destinations":{"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"},"*shared_groups":{"limit":-1,"precache":false,"remote":false,"replicate":false,"static_ttl":false},"*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},"*timings":{"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{Section: CACHE_JSN}, &reply); err != nil { t.Error(err) diff --git a/data/conf/samples/ranking_internal/cgrates.json b/data/conf/samples/ranking_internal/cgrates.json new file mode 100644 index 000000000..7c33a283e --- /dev/null +++ b/data/conf/samples/ranking_internal/cgrates.json @@ -0,0 +1,26 @@ +{ + "general": { + "log_level": 7, + }, + "data_db": { + "db_type": "*internal", + }, + "stor_db": { + "db_type": "*internal", + }, + "rankings": { + "enabled": true, + "store_interval": "-1", + "scheduled_ids": {}, + "stats_conns": [ + "*localhost" + ] + }, + "stats": { + "enabled": true, + "store_interval": "-1", + }, + "apiers": { + "enabled": true, + } +} diff --git a/data/conf/samples/ranking_mongo/cgrates.json b/data/conf/samples/ranking_mongo/cgrates.json new file mode 100644 index 000000000..6faf7ed31 --- /dev/null +++ b/data/conf/samples/ranking_mongo/cgrates.json @@ -0,0 +1,31 @@ +{ + "general": { + "log_level": 7, + }, + "data_db": { + "db_type": "mongo", + "db_name": "10", + "db_port": 27017, + }, + "stor_db": { + "db_type": "mongo", + "db_name": "cgrates", + "db_port": 27017, + "db_password": "", + }, + "rankings": { + "enabled": true, + "store_interval": "-1", + "scheduled_ids": {}, + "stats_conns": [ + "*localhost" + ] + }, + "stats": { + "enabled": true, + "store_interval": "-1", + }, + "apiers": { + "enabled": true, + } +} \ No newline at end of file diff --git a/data/conf/samples/ranking_mysql/cgrates.json b/data/conf/samples/ranking_mysql/cgrates.json new file mode 100644 index 000000000..0d9ccda20 --- /dev/null +++ b/data/conf/samples/ranking_mysql/cgrates.json @@ -0,0 +1,28 @@ +{ + "general": { + "log_level": 7, + }, + "data_db": { + "db_type": "redis", + "db_port": 6379, + "db_name": "10", + }, + "stor_db": { + "db_password": "CGRateS.org", + }, + "rankings": { + "enabled": true, + "store_interval": "-1", + "scheduled_ids": {}, + "stats_conns": [ + "*localhost" + ] + }, + "stats": { + "enabled": true, + "store_interval": "-1", + }, + "apiers": { + "enabled": true, + } +} \ No newline at end of file diff --git a/data/tariffplans/tutrankings/Rankings.csv b/data/tariffplans/tutrankings/Rankings.csv new file mode 100644 index 000000000..1d20c55a2 --- /dev/null +++ b/data/tariffplans/tutrankings/Rankings.csv @@ -0,0 +1,3 @@ +#Tenant[0],Id[1],Schedule[2],StatIDs[3],MetricIDs[4],Sorting[5],SortingParameters[6],Stored[7],ThresholdIDs[8] +cgrates.org,RANK1,@every 1s,Stats1;Stats2;Stats3;Stats4,,*asc,*acc;*pdd:false;*acd,, +cgrates.org,RANK2,@every 1s,Stats3;Stats4;Stats1;Stats2,,*desc,*acc;*pdd:false;*acd,, \ No newline at end of file diff --git a/data/tariffplans/tutrankings/Stats.csv b/data/tariffplans/tutrankings/Stats.csv new file mode 100644 index 000000000..269047b89 --- /dev/null +++ b/data/tariffplans/tutrankings/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,*string:~*req.Account:1001,,,,,*acc;*acd;*pdd,,,,, +cgrates.org,Stats2,*string:~*req.Account:1002,,,,,*acc;*acd;*pdd,,,,, +cgrates.org,Stats3,*string:~*req.Account:1003,,,,,*acc;*acd;*pdd,,,,, +cgrates.org,Stats4,*string:~*req.Account:1004,,,,,*acc;*acd;*pdd,,,,, \ No newline at end of file diff --git a/dispatchers/caches_it_test.go b/dispatchers/caches_it_test.go index cb2cb7c91..a9e107019 100644 --- a/dispatchers/caches_it_test.go +++ b/dispatchers/caches_it_test.go @@ -141,7 +141,7 @@ func testDspChcLoadAfterFolder(t *testing.T) { expStats[utils.CacheRouteProfiles].Items = 3 expStats[utils.CacheThresholdProfiles].Items = 2 expStats[utils.CacheThresholds].Items = 2 - expStats[utils.CacheLoadIDs].Items = 34 + expStats[utils.CacheLoadIDs].Items = 35 expStats[utils.CacheTimings].Items = 10 expStats[utils.CacheThresholdFilterIndexes].Items = 2 expStats[utils.CacheThresholdFilterIndexes].Groups = 1 @@ -218,6 +218,7 @@ func testDspChcPrecacheStatus(t *testing.T) { utils.CacheRadiusPackets: utils.MetaReady, utils.CacheReplicationHosts: utils.MetaReady, utils.CacheRankingProfiles: utils.MetaReady, + utils.CacheRankings: utils.MetaReady, utils.CacheTrendProfiles: utils.MetaReady, utils.CacheTrends: utils.MetaReady, } diff --git a/engine/datamanager.go b/engine/datamanager.go index 1cc0a9a94..fd7bb9fe2 100644 --- a/engine/datamanager.go +++ b/engine/datamanager.go @@ -60,6 +60,7 @@ var ( utils.StatQueueProfilePrefix: {}, utils.ThresholdPrefix: {}, utils.ThresholdProfilePrefix: {}, + utils.RankingPrefix: {}, utils.RankingsProfilePrefix: {}, utils.FilterPrefix: {}, utils.RouteProfilePrefix: {}, @@ -193,6 +194,9 @@ func (dm *DataManager) CacheDataFromDB(prfx string, ids []string, mustBeCached b case utils.RankingsProfilePrefix: tntID := utils.NewTenantID(dataID) _, err = dm.GetRankingProfile(tntID.Tenant, tntID.ID, false, true, utils.NonTransactional) + case utils.RankingPrefix: + tntID := utils.NewTenantID(dataID) + _, err = dm.GetRanking(tntID.Tenant, tntID.ID, false, true, utils.NonTransactional) case utils.TimingsPrefix: _, err = dm.GetTiming(dataID, true, utils.NonTransactional) case utils.ThresholdProfilePrefix: @@ -205,6 +209,12 @@ func (dm *DataManager) CacheDataFromDB(prfx string, ids []string, mustBeCached b lkID := guardian.Guardian.GuardIDs("", config.CgrConfig().GeneralCfg().LockingTimeout, thresholdLockKey(tntID.Tenant, tntID.ID)) _, err = dm.GetThreshold(tntID.Tenant, tntID.ID, false, true, utils.NonTransactional) guardian.Guardian.UnguardIDs(lkID) + case utils.TrendsProfilePrefix: + tntID := utils.NewTenantID(dataID) + _, err = dm.GetTrendProfile(tntID.Tenant, tntID.ID, false, true, utils.NonTransactional) + case utils.TrendPrefix: + tntID := utils.NewTenantID(dataID) + _, err = dm.GetTrend(tntID.Tenant, tntID.ID, false, true, utils.NonTransactional) case utils.FilterPrefix: tntID := utils.NewTenantID(dataID) _, err = dm.GetFilter(tntID.Tenant, tntID.ID, false, true, utils.NonTransactional) @@ -1608,9 +1618,9 @@ func (dm *DataManager) GetRankingProfileIDs(tenants []string) (rns map[string][] keys = append(keys, tntkeys...) } } - if len(keys) == 0 { - return nil, utils.ErrNotFound - } + // if len(keys) == 0 { + // return nil, utils.ErrNotFound + // } rns = make(map[string][]string) for _, key := range keys { indx := strings.Index(key, utils.ConcatenatedKeySep) @@ -1621,23 +1631,33 @@ func (dm *DataManager) GetRankingProfileIDs(tenants []string) (rns map[string][] return } -func (dm *DataManager) SetRankingProfile(sgp *RankingProfile) (err error) { +func (dm *DataManager) SetRankingProfile(rnp *RankingProfile) (err error) { if dm == nil { return utils.ErrNoDatabaseConn } - if err = dm.DataDB().SetRankingProfileDrv(sgp); err != nil { + oldRnk, err := dm.GetRankingProfile(rnp.Tenant, rnp.ID, true, false, utils.NonTransactional) + if err != nil && err != utils.ErrNotFound { + return err + } + if err = dm.DataDB().SetRankingProfileDrv(rnp); err != nil { return } if itm := config.CgrConfig().DataDbCfg().Items[utils.MetaRankingProfiles]; itm.Replicate { err = replicate(dm.connMgr, config.CgrConfig().DataDbCfg().RplConns, config.CgrConfig().DataDbCfg().RplFiltered, - utils.RankingsProfilePrefix, sgp.TenantID(), + utils.RankingsProfilePrefix, 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(NewRankingFromProfile(rnp)); err != nil { + return + } + } return } @@ -1723,7 +1743,7 @@ func (dm *DataManager) SetRanking(rn *Ranking) (err error) { if err = dm.DataDB().SetRankingDrv(rn); err != nil { return } - if itm := config.CgrConfig().DataDbCfg().Items[utils.MetaTrends]; itm.Replicate { + if itm := config.CgrConfig().DataDbCfg().Items[utils.MetaRankings]; itm.Replicate { if err = replicate(dm.connMgr, config.CgrConfig().DataDbCfg().RplConns, config.CgrConfig().DataDbCfg().RplFiltered, utils.RankingPrefix, rn.TenantID(), // this are used to get the host IDs from cache diff --git a/engine/librankings.go b/engine/librankings.go index 11f713a12..7123d1b23 100644 --- a/engine/librankings.go +++ b/engine/librankings.go @@ -58,15 +58,20 @@ func (rkP *RankingProfile) Clone() (cln *RankingProfile) { 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 @@ -84,6 +89,7 @@ func NewRankingFromProfile(rkP *RankingProfile) (rk *Ranking) { metricIDs: utils.NewStringSet(rkP.MetricIDs), } if rkP.SortingParameters != nil { + rk.SortingParameters = make([]string, len(rkP.SortingParameters)) copy(rk.SortingParameters, rkP.SortingParameters) } return @@ -123,6 +129,7 @@ func (rk *Ranking) asRankingSummary() (rkSm *RankingSummary) { ID: rk.ID, LastUpdate: rk.LastUpdate, } + rkSm.SortedStatIDs = make([]string, len(rk.SortedStatIDs)) copy(rkSm.SortedStatIDs, rk.SortedStatIDs) return } diff --git a/engine/libtest.go b/engine/libtest.go index 08d4c7576..31d07842a 100644 --- a/engine/libtest.go +++ b/engine/libtest.go @@ -281,6 +281,7 @@ func GetDefaultEmptyCacheStats() map[string]*ltcache.CacheStats { utils.CacheStatFilterIndexes: {}, utils.CacheStatQueueProfiles: {}, utils.CacheStatQueues: {}, + utils.CacheRankings: {}, utils.CacheRankingProfiles: {}, utils.CacheSTIR: {}, utils.CacheRouteFilterIndexes: {}, @@ -351,7 +352,7 @@ type TestEngine struct { func (ng TestEngine) Run(t testing.TB, 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) } @@ -525,7 +526,7 @@ func LoadCSVs(t testing.TB, client *birpc.Client, tpPath string, csvFiles map[st } // flushDBs resets the databases specified in the configuration if the corresponding flags are true. -func flushDBs(t testing.TB, cfg *config.CGRConfig, flushDataDB, flushStorDB bool) { +func FlushDBs(t testing.TB, cfg *config.CGRConfig, flushDataDB, flushStorDB bool) { t.Helper() if flushDataDB { if err := InitDataDb(cfg); err != nil { diff --git a/engine/libtest_test.go b/engine/libtest_test.go index 5afce315b..e702de0a9 100644 --- a/engine/libtest_test.go +++ b/engine/libtest_test.go @@ -61,6 +61,7 @@ func TestGetDefaultEmptyCacheStats(t *testing.T) { utils.CacheStatFilterIndexes, utils.CacheStatQueueProfiles, utils.CacheStatQueues, + utils.CacheRankings, utils.CacheRankingProfiles, utils.CacheSTIR, utils.CacheRouteFilterIndexes, diff --git a/engine/model_helpers.go b/engine/model_helpers.go index 9a5aba7f0..0ff535b1a 100644 --- a/engine/model_helpers.go +++ b/engine/model_helpers.go @@ -1472,6 +1472,7 @@ func (models RankingsMdls) 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) mrg := make(map[string]*utils.TPRankingProfile) for _, model := range models { @@ -1511,9 +1512,17 @@ func (models RankingsMdls) 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 { metricsMap[key.TenantID()] = make(utils.StringSet) @@ -1528,7 +1537,7 @@ func (models RankingsMdls) AsTPRanking() (result []*utils.TPRankingProfile) { 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++ } diff --git a/engine/rankings.go b/engine/rankings.go index 9a8f89a00..6a89273ca 100644 --- a/engine/rankings.go +++ b/engine/rankings.go @@ -118,7 +118,6 @@ func (rkS *RankingS) computeRanking(rkP *RankingProfile) { rk.Metrics[statID][metricID] = val } } - if rk.SortedStatIDs, err = rankingSortStats(rkP.Sorting, rkP.SortingParameters, rk.Metrics); err != nil { utils.Logger.Warning( @@ -169,6 +168,8 @@ func (rkS *RankingS) processThresholds(rk *Ranking) (err error) { 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(), @@ -176,7 +177,7 @@ func (rkS *RankingS) processThresholds(rk *Ranking) (err error) { Event: map[string]any{ utils.RankingID: rk.ID, utils.LastUpdate: rk.LastUpdate, - utils.SortedStatIDs: copy([]string{}, rk.SortedStatIDs), + utils.SortedStatIDs: sortedStatIDs, }, } var withErrs bool @@ -205,6 +206,8 @@ func (rkS *RankingS) processEEs(rk *Ranking) (err error) { 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(), @@ -212,7 +215,7 @@ func (rkS *RankingS) processEEs(rk *Ranking) (err error) { Event: map[string]any{ utils.RankingID: rk.ID, utils.LastUpdate: rk.LastUpdate, - utils.SortedStatIDs: copy([]string{}, rk.SortedStatIDs), + utils.SortedStatIDs: sortedStatIDs, }, } var withErrs bool @@ -372,7 +375,7 @@ func (rkS *RankingS) scheduleAutomaticQueries() error { } } if tnts != nil { - qrydData, err := rkS.dm.GetTrendProfileIDs(tnts) + qrydData, err := rkS.dm.GetRankingProfileIDs(tnts) if err != nil { return err } @@ -409,7 +412,7 @@ func (rkS *RankingS) scheduleRankingQueries(_ *context.Context, "<%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(utils.EmptyString, + } else if entryID, err := rkS.crn.AddFunc(rkP.Schedule, func() { rkS.computeRanking(rkP.Clone()) }); err != nil { utils.Logger.Warning( fmt.Sprintf( @@ -420,8 +423,8 @@ func (rkS *RankingS) scheduleRankingQueries(_ *context.Context, rkS.crnTQsMux.Lock() rkS.crnTQs[rkP.Tenant][rkP.ID] = entryID rkS.crnTQsMux.Unlock() + scheduled++ } - scheduled += 1 } if partial { return 0, utils.ErrPartiallyExecuted @@ -459,8 +462,13 @@ func (rkS *RankingS) V1GetRanking(ctx *context.Context, arg *utils.TenantIDWithA 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 } @@ -473,18 +481,18 @@ func (rkS *RankingS) V1GetSchedule(ctx *context.Context, args *utils.ArgSchedule } rkS.crnTQsMux.RLock() defer rkS.crnTQsMux.RUnlock() - trendIDsMp, has := rkS.crnTQs[tnt] + rankingIDsMp, has := rkS.crnTQs[tnt] if !has { return utils.ErrNotFound } var scheduledRankings []utils.ScheduledRanking var entryIds map[string]cron.EntryID if len(args.RankingIDPrefixes) == 0 { - entryIds = trendIDsMp + entryIds = rankingIDsMp } else { entryIds = make(map[string]cron.EntryID) for _, rkID := range args.RankingIDPrefixes { - for key, entryID := range trendIDsMp { + for key, entryID := range rankingIDsMp { if strings.HasPrefix(key, rkID) { entryIds[key] = entryID } diff --git a/engine/tpreader.go b/engine/tpreader.go index 70f77cd26..ae5d8ad5f 100644 --- a/engine/tpreader.go +++ b/engine/tpreader.go @@ -1633,9 +1633,9 @@ func (tpr *TpReader) WriteToDatabase(verbose, disableReverse bool) (err error) { if verbose { log.Print("RankingProfiles:") } - for _, tpSG := range tpr.rgProfiles { + for _, tpRN := range tpr.rgProfiles { var sg *RankingProfile - if sg, err = APItoRanking(tpSG); err != nil { + if sg, err = APItoRanking(tpRN); err != nil { return } if err = tpr.dm.SetRankingProfile(sg); err != nil { diff --git a/general_tests/ranking_schedule_it_test.go b/general_tests/ranking_schedule_it_test.go new file mode 100644 index 000000000..79b171540 --- /dev/null +++ b/general_tests/ranking_schedule_it_test.go @@ -0,0 +1,225 @@ +//go:build integration +// +build integration + +/* +Real-time Online/Offline Charging System (OCS) for Telecom & ISP environments +Copyright (C) ITsysCOM GmbH + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see +*/ + +package general_tests + +import ( + "fmt" + "path" + "slices" + "testing" + "time" + + "github.com/cgrates/birpc/context" + "github.com/cgrates/cgrates/config" + "github.com/cgrates/cgrates/engine" + "github.com/cgrates/cgrates/utils" +) + +func TestRankingSchedule(t *testing.T) { + var rnkConfigDir string + switch *utils.DBType { + case utils.MetaInternal, utils.MetaPostgres: + t.SkipNow() + case utils.MetaMySQL: + rnkConfigDir = "ranking_mysql" + case utils.MetaMongo: + rnkConfigDir = "ranking_mongo" + default: + t.Fatal("Unkwown database type") + } + + ng := engine.TestEngine{ + ConfigPath: path.Join(*utils.DataDir, "conf", "samples", rnkConfigDir), + PreserveStorDB: true, + PreserveDataDB: true, + PreStartHook: func(t *testing.T, c *config.CGRConfig) { + engine.FlushDBs(t, c, true, true) + engine.LoadCSVsWithCGRLoader(t, c.ConfigPath, path.Join(*utils.DataDir, "tariffplans", "tutrankings"), nil, nil, "-caches_address=") + }, + } + client, _ := ng.Run(t) + + t.Run("ProcessStats", func(t *testing.T) { + var reply []string + for i := 1; i <= 4; i++ { + if err := client.Call(context.Background(), utils.StatSv1ProcessEvent, &utils.CGREvent{ + Tenant: "cgrates.org", + ID: fmt.Sprintf("event%d", i), + Event: map[string]any{ + utils.AccountField: fmt.Sprintf("100%d", i), + utils.AnswerTime: time.Date(2024, 8, 22, 14, 25, 0, 0, time.UTC), + utils.Usage: time.Duration(1800+60) / time.Duration(i) * time.Second, + utils.Cost: 20.0 + float64((i*7)%10)/2, + utils.PDD: time.Duration(10+i*2) * time.Second, + }}, &reply); err != nil { + t.Error(err) + } + } + }) + time.Sleep(1 * time.Second) + + t.Run("GetRankings", func(t *testing.T) { + var rnk engine.Ranking + sortedStatIds := []string{"Stats3", "Stats2", "Stats1", "Stats4"} + if err := client.Call(context.Background(), utils.RankingSv1GetRanking, &utils.TenantIDWithAPIOpts{TenantID: &utils.TenantID{Tenant: "cgrates.org", ID: "RANK1"}}, &rnk); err != nil { + t.Error(err) + } else if len(rnk.SortedStatIDs) == 0 { + t.Error("no ranked statIDs") + } else if !slices.Equal(sortedStatIds, rnk.SortedStatIDs) { + t.Errorf("expected sorted statIDs %v, got %v", sortedStatIds, rnk.SortedStatIDs) + } + + sortedStatIds = []string{"Stats4", "Stats1", "Stats2", "Stats3"} + if err := client.Call(context.Background(), utils.RankingSv1GetRanking, &utils.TenantIDWithAPIOpts{TenantID: &utils.TenantID{Tenant: "cgrates.org", ID: "RANK2"}}, &rnk); err != nil { + t.Error(err) + } else if len(rnk.SortedStatIDs) == 0 { + t.Error("no ranked statIDs") + } else if !slices.Equal(sortedStatIds, rnk.SortedStatIDs) { + t.Errorf("expected sorted statIDs %v, got %v", sortedStatIds, rnk.SortedStatIDs) + } + }) + + t.Run("SetRankingProfile", func(t *testing.T) { + rankingProfile := &engine.RankingProfileWithAPIOpts{ + RankingProfile: &engine.RankingProfile{ + Tenant: "cgrates.org", + ID: "RANK3", + Schedule: "@every 1s", + StatIDs: []string{"Stats2", "Stats3", "Stats1"}, + Sorting: "*desc", + SortingParameters: []string{"*acd", "*pdd:false", "*acc"}, + }, + } + var result string + if err := client.Call(context.Background(), utils.APIerSv1SetRankingProfile, rankingProfile, &result); err != nil { + t.Error(err) + } else if result != utils.OK { + t.Error("Unexpected reply returned", result) + } + }) + + t.Run("ScheduleManualRankings", func(t *testing.T) { + var scheduled int + if err := client.Call(context.Background(), utils.RankingSv1ScheduleQueries, &utils.ArgScheduleRankingQueries{TenantIDWithAPIOpts: utils.TenantIDWithAPIOpts{TenantID: &utils.TenantID{Tenant: "cgrates.org"}}, RankingIDs: []string{"RANK3"}}, &scheduled); err != nil { + t.Error(err) + } else if scheduled != 1 { + t.Errorf("Expected 1 scheduled rankings, got %d", scheduled) + } + var schedRankings []utils.ScheduledRanking + if err := client.Call(context.Background(), utils.RankingSv1GetSchedule, &utils.ArgScheduledRankings{RankingIDPrefixes: []string{"RANK3"}, TenantIDWithAPIOpts: utils.TenantIDWithAPIOpts{TenantID: &utils.TenantID{Tenant: "cgrates.org"}}}, &schedRankings); err != nil { + t.Error(err) + } else if len(schedRankings) == 0 { + t.Errorf("Expected scheduled rankings") + } else { + for _, schedRanking := range schedRankings { + if schedRanking.Next.IsZero() { + t.Errorf("Expected to have a scheduled time got %v", schedRanking.Next) + } + } + } + }) + time.Sleep(time.Second) + t.Run("GetRankings", func(t *testing.T) { + sortedStatIds := []string{"Stats1", "Stats2", "Stats3"} + var rnk engine.Ranking + if err := client.Call(context.Background(), utils.RankingSv1GetRanking, &utils.TenantIDWithAPIOpts{TenantID: &utils.TenantID{Tenant: "cgrates.org", ID: "RANK3"}}, &rnk); err != nil { + t.Error(err) + } else if len(rnk.SortedStatIDs) == 0 { + t.Error("no ranked statIDs") + } else if !slices.Equal(sortedStatIds, rnk.SortedStatIDs) { + t.Errorf("expected sorted statIDs %v, got %v", sortedStatIds, rnk.SortedStatIDs) + } + }) + + t.Run("RankingSetConfig", func(t *testing.T) { + var reply string + // store interval is set to 0 + if err := client.Call(context.Background(), utils.ConfigSv1SetConfig, &config.SetConfigArgs{ + Tenant: "cgrates.org", + Config: map[string]any{ + "rankings": map[string]any{ + "enabled": true, + "store_interval": "0", + "stats_conns": []string{"*localhost"}, + }, + }, + }, &reply); err != nil { + t.Error(err) + } else if reply != utils.OK { + t.Errorf("Expected OK received: %s", reply) + } + }) + t.Run("SetRankingProfile", func(t *testing.T) { + rankingProfile := &engine.RankingProfileWithAPIOpts{ + RankingProfile: &engine.RankingProfile{ + Tenant: "cgrates.org", + ID: "RANK4", + Schedule: "@every 1s", + StatIDs: []string{"Stats2", "Stats3", "Stats1"}, + Sorting: "*desc", + SortingParameters: []string{"*pdd:false", "*acd"}, + }, + } + var result string + if err := client.Call(context.Background(), utils.APIerSv1SetRankingProfile, rankingProfile, &result); err != nil { + t.Error(err) + } else if result != utils.OK { + t.Error("Unexpected reply returned", result) + } + }) + t.Run("ScheduleManualRankings", func(t *testing.T) { + var scheduled int + if err := client.Call(context.Background(), utils.RankingSv1ScheduleQueries, &utils.ArgScheduleRankingQueries{TenantIDWithAPIOpts: utils.TenantIDWithAPIOpts{TenantID: &utils.TenantID{Tenant: "cgrates.org"}}, RankingIDs: []string{"RANK4"}}, &scheduled); err != nil { + t.Error(err) + } else if scheduled != 1 { + t.Errorf("Expected 1 scheduled rankings, got %d", scheduled) + } + var schedRankings []utils.ScheduledRanking + if err := client.Call(context.Background(), utils.RankingSv1GetSchedule, &utils.ArgScheduledRankings{RankingIDPrefixes: []string{"RANK4"}, TenantIDWithAPIOpts: utils.TenantIDWithAPIOpts{TenantID: &utils.TenantID{Tenant: "cgrates.org"}}}, &schedRankings); err != nil { + t.Error(err) + } else if len(schedRankings) == 0 { + t.Errorf("Expected scheduled rankings") + } else { + for _, schedRanking := range schedRankings { + if schedRanking.Next.IsZero() { + t.Errorf("Expected to have a scheduled time got %v", schedRanking.Next) + } + } + } + }) + time.Sleep(time.Second) + t.Run("GetRankings", func(t *testing.T) { + var reply string + if err := client.Call(context.Background(), utils.CacheSv1Clear, &utils.AttrCacheIDsWithAPIOpts{}, &reply); err != nil { + t.Error(err) + } else if reply != utils.OK { + t.Error("Calling CacheSv1.ReloadCache got reply: ", reply) + } + + var rnk engine.Ranking + if err := client.Call(context.Background(), utils.RankingSv1GetRanking, &utils.TenantIDWithAPIOpts{TenantID: &utils.TenantID{Tenant: "cgrates.org", ID: "RANK4"}}, &rnk); err != nil { + t.Error(err) + } else if len(rnk.SortedStatIDs) != 0 || !rnk.LastUpdate.IsZero() { + t.Error("expected updated ranking not set in db") + } + }) +} diff --git a/general_tests/tut_smgeneric_it_test.go b/general_tests/tut_smgeneric_it_test.go index d08d453c3..d179abefe 100644 --- a/general_tests/tut_smgeneric_it_test.go +++ b/general_tests/tut_smgeneric_it_test.go @@ -150,7 +150,7 @@ func testTutSMGCacheStats(t *testing.T) { expectedStats[utils.CacheAttributeProfiles].Items = 3 expectedStats[utils.MetaDefault].Items = 1 expectedStats[utils.CacheActionTriggers].Items = 1 - expectedStats[utils.CacheLoadIDs].Items = 34 + expectedStats[utils.CacheLoadIDs].Items = 35 expectedStats[utils.CacheChargerProfiles].Items = 1 expectedStats[utils.CacheRPCConnections].Items = 2 expectedStats[utils.CacheTimings].Items = 14 @@ -168,6 +168,8 @@ func testTutSMGCacheStats(t *testing.T) { expectedStats[utils.CacheAttributeFilterIndexes].Groups = 3 expectedStats[utils.CacheReverseFilterIndexes].Items = 15 expectedStats[utils.CacheReverseFilterIndexes].Groups = 13 + expectedStats[utils.CacheTrendProfiles].Items = 1 + expectedStats[utils.CacheTrends].Items = 1 if err := tutSMGRpc.Call(context.Background(), utils.CacheSv1GetCacheStats, new(utils.AttrCacheIDsWithAPIOpts), &rcvStats); err != nil { t.Error("Got error on CacheSv1.GetCacheStats: ", err.Error()) } else if !reflect.DeepEqual(expectedStats, rcvStats) { diff --git a/services/datadb_it_test.go b/services/datadb_it_test.go index cc73a36ec..50d9db1de 100644 --- a/services/datadb_it_test.go +++ b/services/datadb_it_test.go @@ -122,6 +122,7 @@ func TestDataDBReload(t *testing.T) { utils.MetaStatQueues: {Limit: -1}, utils.MetaResources: {Limit: -1}, utils.MetaStatQueueProfiles: {Limit: -1}, + utils.MetaRankings: {Limit: -1}, utils.MetaRankingProfiles: {Limit: -1}, utils.MetaTrends: {Limit: -1}, utils.MetaTrendProfiles: {Limit: -1}, diff --git a/services/rankings.go b/services/rankings.go index 35df72f28..19a87f6fa 100644 --- a/services/rankings.go +++ b/services/rankings.go @@ -71,14 +71,12 @@ func (rk *RankingService) Start() error { } rk.srvDep[utils.DataDB].Add(1) <-rk.cacheS.GetPrecacheChannel(utils.CacheRankingProfiles) - <-rk.cacheS.GetPrecacheChannel(utils.CacheRankingFilterIndexes) - + <-rk.cacheS.GetPrecacheChannel(utils.CacheRankings) filterS := <-rk.filterSChan rk.filterSChan <- filterS dbchan := rk.dm.GetDMChan() datadb := <-dbchan dbchan <- datadb - utils.Logger.Info(fmt.Sprintf("<%s> starting <%s> subsystem", utils.CoreS, utils.RankingS)) diff --git a/utils/apitpdata.go b/utils/apitpdata.go index f192ebbbe..9f89e8dc9 100644 --- a/utils/apitpdata.go +++ b/utils/apitpdata.go @@ -1384,6 +1384,7 @@ func NewAttrReloadCacheWithOpts() *AttrReloadCacheWithAPIOpts { ResourceIDs: []string{MetaAny}, StatsQueueIDs: []string{MetaAny}, StatsQueueProfileIDs: []string{MetaAny}, + RankingIDs: []string{MetaAny}, RankingProfileIDs: []string{MetaAny}, TrendIDs: []string{MetaAny}, TrendProfileIDs: []string{MetaAny}, @@ -1426,6 +1427,7 @@ func NewAttrReloadCacheWithOptsFromMap(arg map[string][]string, tnt string, opts ResourceIDs: arg[CacheResources], StatsQueueIDs: arg[CacheStatQueues], StatsQueueProfileIDs: arg[CacheStatQueueProfiles], + RankingIDs: arg[CacheRankings], RankingProfileIDs: arg[CacheRankingProfiles], ThresholdIDs: arg[CacheThresholds], ThresholdProfileIDs: arg[CacheThresholdProfiles], @@ -1466,6 +1468,7 @@ type AttrReloadCacheWithAPIOpts struct { ResourceIDs []string `json:",omitempty"` StatsQueueIDs []string `json:",omitempty"` StatsQueueProfileIDs []string `json:",omitempty"` + RankingIDs []string `json:",omitempty"` RankingProfileIDs []string `json:",omitempty"` TrendIDs []string `json:",omitempty"` TrendProfileIDs []string `json:",omitempty"` @@ -1506,6 +1509,7 @@ func (a *AttrReloadCacheWithAPIOpts) Map() map[string][]string { CacheStatQueueProfiles: a.StatsQueueProfileIDs, CacheThresholds: a.ThresholdIDs, CacheThresholdProfiles: a.ThresholdProfileIDs, + CacheRankings: a.RankingIDs, CacheRankingProfiles: a.RankingProfileIDs, CacheTrends: a.TrendIDs, CacheTrendProfiles: a.TrendProfileIDs, diff --git a/utils/apitpdata_test.go b/utils/apitpdata_test.go index 1751ccbd0..3f329d290 100644 --- a/utils/apitpdata_test.go +++ b/utils/apitpdata_test.go @@ -999,6 +999,7 @@ func TestNewAttrReloadCacheWithOpts(t *testing.T) { ChargerFilterIndexIDs: []string{MetaAny}, DispatcherFilterIndexIDs: []string{MetaAny}, FilterIndexIDs: []string{MetaAny}, + RankingIDs: []string{MetaAny}, RankingProfileIDs: []string{MetaAny}, } eMap := NewAttrReloadCacheWithOpts() diff --git a/utils/consts.go b/utils/consts.go index 89bbdb968..52420c0b5 100644 --- a/utils/consts.go +++ b/utils/consts.go @@ -38,7 +38,7 @@ var ( DataDBPartitions = NewStringSet([]string{CacheDestinations, CacheReverseDestinations, CacheRatingPlans, CacheRatingProfiles, CacheDispatcherProfiles, CacheDispatcherHosts, CacheChargerProfiles, CacheActions, CacheActionTriggers, CacheSharedGroups, CacheTimings, - CacheResourceProfiles, CacheResources, CacheEventResources, CacheStatQueueProfiles, CacheRankingProfiles, CacheStatQueues, + CacheResourceProfiles, CacheResources, CacheEventResources, CacheStatQueueProfiles, CacheRankingProfiles, CacheRankings, CacheStatQueues, CacheThresholdProfiles, CacheThresholds, CacheFilters, CacheRouteProfiles, CacheAttributeProfiles, CacheTrendProfiles, CacheTrends, CacheResourceFilterIndexes, CacheStatFilterIndexes, CacheThresholdFilterIndexes, CacheRouteFilterIndexes, CacheAttributeFilterIndexes, CacheChargerFilterIndexes, CacheDispatcherFilterIndexes, CacheLoadIDs, @@ -70,6 +70,7 @@ var ( CacheStatQueueProfiles: StatQueueProfilePrefix, CacheStatQueues: StatQueuePrefix, CacheRankingProfiles: RankingsProfilePrefix, + CacheRankings: RankingPrefix, CacheTrendProfiles: TrendsProfilePrefix, CacheTrends: TrendPrefix, CacheThresholdProfiles: ThresholdProfilePrefix, @@ -1729,6 +1730,9 @@ const ( APIerSv1GetRankingProfile = "APIerSv1.GetRankingProfile" APIerSv1GetRankingProfileIDs = "APIerSv1.GetRankingProfileIDs" RankingSv1Ping = "RankingSv1.Ping" + RankingSv1GetRanking = "RankingSv1.GetRanking" + RankingSv1GetSchedule = "RankingSv1.GetSchedule" + RankingSv1ScheduleQueries = "RankingSv1.ScheduleQueries" ) // ResourceS APIs