diff --git a/apier/v1/dispatcher.go b/apier/v1/dispatcher.go index fdacc165e..1fcd7b892 100644 --- a/apier/v1/dispatcher.go +++ b/apier/v1/dispatcher.go @@ -274,8 +274,8 @@ func (dT *DispatcherThresholdSv1) GetThreshold(ctx *context.Context, args *utils return dT.dS.ThresholdSv1GetThreshold(ctx, args, th) } -func NewDispatcherTrendSv1(dps *dispatchers.DispatcherService) *DispatcherThresholdSv1 { - return &DispatcherThresholdSv1{dS: dps} +func NewDispatcherTrendSv1(dps *dispatchers.DispatcherService) *DispatcherTrendSv1 { + return &DispatcherTrendSv1{dS: dps} } type DispatcherTrendSv1 struct { diff --git a/data/conf/samples/dispatchers/trd_rnk2_mongo/cgrates.json b/data/conf/samples/dispatchers/trd_rnk2_mongo/cgrates.json new file mode 100644 index 000000000..35f5f2607 --- /dev/null +++ b/data/conf/samples/dispatchers/trd_rnk2_mongo/cgrates.json @@ -0,0 +1,48 @@ +{ + +"general": { + "node_id": "ALL2", + "log_level": 7 +}, + +"listen": { + "rpc_json": ":7012", + "rpc_gob": ":7013", + "http": ":7080" +}, + +"data_db": { + "db_type": "mongo", + "db_name": "12", + "db_port": 27017 +}, + +"stor_db": { + "db_type": "mongo", + "db_name": "cgrates", + "db_port": 27017, + "db_password": "" +}, + + +"trends": { + "enabled": true, + "stats_conns":["*internal"], + "store_interval": "-1" +}, + +"rankings": { + "enabled": true, + "stats_conns":["*internal"], + "store_interval": "-1" +}, + +"stats": { + "enabled": true +}, + +"apiers": { + "enabled": true +} + +} diff --git a/data/conf/samples/dispatchers/trd_rnk2_mysql/cgrates.json b/data/conf/samples/dispatchers/trd_rnk2_mysql/cgrates.json new file mode 100644 index 000000000..4ac6daeed --- /dev/null +++ b/data/conf/samples/dispatchers/trd_rnk2_mysql/cgrates.json @@ -0,0 +1,44 @@ +{ + +"general": { + "node_id": "ALL2", + "log_level": 7 +}, + + +"listen": { + "rpc_json": ":7012", + "rpc_gob": ":7013", + "http": ":7080" +}, + +"data_db": { + "db_type": "redis", + "db_port": 6379, + "db_name": "12" +}, + +"stor_db": { + "db_type":"*internal" +}, + +"trends": { + "enabled": true, + "stats_conns":["*internal"], + "store_interval": "-1" +}, + +"rankings": { + "enabled": true, + "stats_conns":["*internal"], + "store_interval": "-1" +}, + +"stats": { + "enabled": true +}, + +"apiers": { + "enabled": true +} +} diff --git a/data/conf/samples/dispatchers/trd_rnk_mongo/cgrates.json b/data/conf/samples/dispatchers/trd_rnk_mongo/cgrates.json new file mode 100644 index 000000000..ae3d65818 --- /dev/null +++ b/data/conf/samples/dispatchers/trd_rnk_mongo/cgrates.json @@ -0,0 +1,47 @@ +{ + +"general": { + "node_id": "ALL", + "log_level": 7 +}, + +"listen": { + "rpc_json": ":6012", + "rpc_gob": ":6013", + "http": ":6080" +}, + +"data_db": { + "db_type": "mongo", + "db_name": "11", + "db_port": 27017 +}, + +"stor_db": { + "db_type": "mongo", + "db_name": "cgrates", + "db_port": 27017, + "db_password": "" +}, + +"trends": { + "enabled": true, + "stats_conns":["*internal"], + "store_interval": "-1" +}, + +"rankings": { + "enabled": true, + "stats_conns":["*internal"], + "store_interval": "-1" +}, + +"stats": { + "enabled": true +}, + +"apiers": { + "enabled": true +} + +} diff --git a/data/conf/samples/dispatchers/trd_rnk_mysql/cgrates.json b/data/conf/samples/dispatchers/trd_rnk_mysql/cgrates.json new file mode 100644 index 000000000..482f5ef1f --- /dev/null +++ b/data/conf/samples/dispatchers/trd_rnk_mysql/cgrates.json @@ -0,0 +1,45 @@ +{ + +"general": { + "node_id": "ALL", + "log_level": 7 +}, + + +"listen": { + "rpc_json": ":6012", + "rpc_gob": ":6013", + "http": ":6080" +}, + +"data_db": { + "db_type": "redis", + "db_port": 6379, + "db_name": "11" +}, + +"stor_db": { + "db_password": "CGRateS.org" +}, + +"trends": { + "enabled": true, + "stats_conns":["*internal"], + "store_interval": "-1" +}, + +"rankings": { + "enabled": true, + "stats_conns":["*internal"], + "store_interval": "-1" +}, + +"stats": { + "enabled": true +}, + +"apiers": { + "enabled": true +} + +} diff --git a/data/conf/samples/ees/cgrates.json b/data/conf/samples/ees/cgrates.json index f61b30765..41695aaac 100644 --- a/data/conf/samples/ees/cgrates.json +++ b/data/conf/samples/ees/cgrates.json @@ -368,9 +368,9 @@ //"elsDiscoverNodesInterval":"10s", // "elsLogger":"elsJson", // "elsEnableDebugLogger":false, - // "elsCompressRequestBody":false, - // "elsCompressRequestBodyLevel":0, - // "elsRetryOnStatus":[], + // "elsCompressRequestBody":false, + // "elsCompressRequestBodyLevel":0, + // "elsRetryOnStatus":[], // "elsMaxRetries": 0, // "elsDisableRetry": false, @@ -416,12 +416,11 @@ //"elsApiKey":"aZmd2UQ==", // "elsLogger":"elsJson", // "elsEnableDebugLogger":false, - // "elsCompressRequestBody":false, - // "elsCompressRequestBodyLevel":0, - // "elsRetryOnStatus":[], + // "elsCompressRequestBody":false, + // "elsCompressRequestBodyLevel":0, + // "elsRetryOnStatus":[], // "elsMaxRetries": 0, // "elsDisableRetry": false, - //"elsIfPrimaryTerm": 0, //"elsIfSeqNo": 0, "elsOpType": "", @@ -467,9 +466,9 @@ // "elsDiscoverNodesOnStart":true, // "elsDiscoverNodesInterval":"10s", // "elsEnableDebugLogger":false, - // "elsCompressRequestBody":true, - // "elsCompressRequestBodyLevel":0, - // "elsRetryOnStatus":[], + // "elsCompressRequestBody":true, + // "elsCompressRequestBodyLevel":0, + // "elsRetryOnStatus":[], // "elsMaxRetries": 0, // "elsDisableRetry": false, diff --git a/data/tariffplans/dispatchers/Attributes.csv b/data/tariffplans/dispatchers/Attributes.csv index 89df28d63..33037e907 100644 --- a/data/tariffplans/dispatchers/Attributes.csv +++ b/data/tariffplans/dispatchers/Attributes.csv @@ -6,6 +6,8 @@ cgrates.org,ATTR_API_ATTR_FAKE_AUTH,*auth,*string:~*req.ApiKey:12345,,,*req.APIM cgrates.org,ATTR_API_ATTR_AUTH,*auth,*string:~*req.ApiKey:attr12345,,,*req.APIMethods,*constant,AttributeSv1.Ping&AttributeSv1.GetAttributeForEvent&AttributeSv1.ProcessEvent,false,20 cgrates.org,ATTR_API_CHRG_AUTH,*auth,*string:~*req.ApiKey:chrg12345,,,*req.APIMethods,*constant,ChargerSv1.Ping&ChargerSv1.GetChargersForEvent&ChargerSv1.ProcessEvent,false,20 cgrates.org,ATTR_API_THR_AUTH,*auth,*string:~*req.ApiKey:thr12345,,,*req.APIMethods,*constant,ThresholdSv1.Ping&ThresholdSv1.GetThresholdsForEvent&ThresholdSv1.ProcessEvent&ThresholdSv1.GetThreshold&ThresholdSv1.GetThresholdIDs,false,20 +cgrates.org,ATTR_API_TR_AUTH,*auth,*string:~*req.ApiKey:tr12345,,,*req.APIMethods,*constant,TrendSv1.Ping&TrendSv1.ScheduleQueries&TrendSv1.GetTrend&TrendSv1.GetScheduledTrends&TrendSv1.GetTrendSummary,false,20 +cgrates.org,ATTR_API_RN_AUTH,*auth,*string:~*req.ApiKey:rn12345,,,*req.APIMethods,*constant,RankingSv1.Ping&RankingSv1.GetRanking&RankingSv1.GetSchedule&RankingSv1.ScheduleQueries&RankingSv1.GetRankingSummary,false,20 cgrates.org,ATTR_API_SUP_AUTH,*auth,*string:~*req.ApiKey:sup12345,,,*req.APIMethods,*constant,RouteSv1.Ping&RouteSv1.GetRoutes&RouteSv1.GetRouteProfilesForEvent,false,20 cgrates.org,ATTR_API_STAT_AUTH,*auth,*string:~*req.ApiKey:stat12345,,,*req.APIMethods,*constant,StatSv1.Ping&StatSv1.GetStatQueuesForEvent&StatSv1.GetQueueStringMetrics&StatSv1.ProcessEvent&StatSv1.GetQueueIDs&StatSv1.GetQueueFloatMetrics,false,20 cgrates.org,ATTR_API_RES_AUTH,*auth,*string:~*req.ApiKey:res12345,,,*req.APIMethods,*constant,ResourceSv1.Ping&ResourceSv1.GetResourcesForEvent&ResourceSv1.AuthorizeResources&ResourceSv1.AllocateResources&ResourceSv1.ReleaseResources&ResourceSv1.GetResource,false,20 diff --git a/dispatchers/ranking_it_test.go b/dispatchers/ranking_it_test.go new file mode 100644 index 000000000..5efce4826 --- /dev/null +++ b/dispatchers/ranking_it_test.go @@ -0,0 +1,164 @@ +//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 dispatchers + +import ( + "testing" + "time" + + "github.com/cgrates/birpc/context" + "github.com/cgrates/cgrates/engine" + "github.com/cgrates/cgrates/utils" +) + +var sTestDspRn = []func(t *testing.T){ + testDspRnPingFailover, + testDspRnGetRankingFailover, + testDspRnPing, + testDspRnTestAuthKey, + testDspRnTestAuthKey2, +} + +func TestDspRankingS(t *testing.T) { + var config1, config2, config3 string + switch *utils.DBType { + case utils.MetaInternal: + t.SkipNow() + case utils.MetaMySQL: + config1 = "trd_rnk_mysql" + config2 = "trd_rnk2_mysql" + config3 = "dispatchers_mysql" + case utils.MetaMongo: + config1 = "trd_rnk_mongo" + config2 = "trd_rnk2_mongo" + config3 = "dispatchers_mongo" + case utils.MetaPostgres: + t.SkipNow() + default: + t.Fatal("Unknown Database type") + } + testDsp(t, sTestDspRn, "TestDspRankingS", config1, config2, config3, "tutrankings", "testtp", "dispatchers") +} + +func testDspRnPingFailover(t *testing.T) { + var reply string + if err := allEngine.RPC.Call(context.Background(), utils.RankingSv1Ping, new(utils.CGREvent), &reply); err != nil { + t.Error(err) + } else if reply != utils.Pong { + t.Errorf("Received: %s", reply) + } + ev := utils.CGREvent{ + Tenant: "cgrates.org", + + APIOpts: map[string]any{ + utils.OptsAPIKey: "rn12345", + }, + } + if err := dispEngine.RPC.Call(context.Background(), utils.RankingSv1Ping, &ev, &reply); err != nil { + t.Error(err) + } else if reply != utils.Pong { + t.Errorf("Received: %s", reply) + } + allEngine.stopEngine(t) + time.Sleep(200 * time.Millisecond) + if err := dispEngine.RPC.Call(context.Background(), utils.RankingSv1Ping, &ev, &reply); err != nil { + t.Error(err) + } else if reply != utils.Pong { + t.Errorf("Received: %s", reply) + } + allEngine2.stopEngine(t) + if err := dispEngine.RPC.Call(context.Background(), utils.RankingSv1Ping, &ev, &reply); err == nil { + t.Errorf("Expected error but received %v and reply %v\n", err, reply) + } + allEngine.startEngine(t) + allEngine2.startEngine(t) +} + +func testDspRnGetRankingFailover(t *testing.T) { + trSched := utils.ArgScheduleRankingQueries{RankingIDs: []string{"RANK2"}, TenantIDWithAPIOpts: utils.TenantIDWithAPIOpts{TenantID: &utils.TenantID{Tenant: "cgrates.org"}, APIOpts: map[string]any{utils.OptsAPIKey: "rn12345"}}} + var scheduled int + if err := dispEngine.RPC.Call(context.Background(), utils.RankingSv1ScheduleQueries, &trSched, + &scheduled); err != nil { + t.Error(err) + } else if scheduled != 1 { + t.Errorf("expected %d,received %d", 1, scheduled) + } + allEngine.stopEngine(t) + time.Sleep(200 * time.Millisecond) + if err := dispEngine.RPC.Call(context.Background(), utils.RankingSv1ScheduleQueries, &trSched, + &scheduled); err == nil || err.Error() != utils.ErrPartiallyExecuted.Error() { + t.Errorf("expected err %v,received %v", utils.ErrPartiallyExecuted, err) + } + allEngine.startEngine(t) +} + +func testDspRnPing(t *testing.T) { + var reply string + if err := allEngine.RPC.Call(context.Background(), utils.RankingSv1Ping, new(utils.CGREvent), &reply); err != nil { + t.Error(err) + } else if reply != utils.Pong { + t.Errorf("Received: %s", reply) + } + if err := dispEngine.RPC.Call(context.Background(), utils.RankingSv1Ping, &utils.CGREvent{ + Tenant: "cgrates.org", + + APIOpts: map[string]any{ + utils.OptsAPIKey: "rn12345", + }, + }, &reply); err != nil { + t.Error(err) + } else if reply != utils.Pong { + t.Errorf("Received: %s", reply) + } +} + +func testDspRnTestAuthKey(t *testing.T) { + var rn *engine.Ranking + args := &utils.TenantIDWithAPIOpts{ + TenantID: &utils.TenantID{ + Tenant: "cgrates.org", + ID: "RANK1", + }, + APIOpts: map[string]any{ + utils.OptsAPIKey: "12345", + }, + } + if err := dispEngine.RPC.Call(context.Background(), utils.RankingSv1GetRanking, + args, &rn); err == nil || err.Error() != utils.ErrUnauthorizedApi.Error() { + t.Error(err) + } + rnSched := utils.ArgScheduleRankingQueries{RankingIDs: []string{"RANK1"}, TenantIDWithAPIOpts: utils.TenantIDWithAPIOpts{TenantID: &utils.TenantID{Tenant: "cgrates.org"}, APIOpts: map[string]any{utils.OptsAPIKey: "12345"}}} + var scheduled int + if err := dispEngine.RPC.Call(context.Background(), utils.RankingSv1ScheduleQueries, + &rnSched, &scheduled); err == nil || err.Error() != utils.ErrUnauthorizedApi.Error() { + t.Error(err) + } +} + +func testDspRnTestAuthKey2(t *testing.T) { + var schedRankings []utils.ScheduledRanking + if err := dispEngine.RPC.Call(context.Background(), utils.RankingSv1GetSchedule, &utils.ArgScheduledRankings{TenantIDWithAPIOpts: utils.TenantIDWithAPIOpts{TenantID: &utils.TenantID{Tenant: "cgrates.org"}, APIOpts: map[string]any{utils.OptsAPIKey: "rn12345"}}, RankingIDPrefixes: []string{"RANK2"}}, &schedRankings); err != nil { + t.Error(err) + } else if len(schedRankings) != 1 { + t.Errorf("expected 1 schedTrends, got %d", len(schedRankings)) + } +} diff --git a/dispatchers/trends.go b/dispatchers/trends.go index 0cf6ef4b5..4623e2b78 100644 --- a/dispatchers/trends.go +++ b/dispatchers/trends.go @@ -32,7 +32,7 @@ func (dS *DispatcherService) TrendSv1Ping(ctx *context.Context, args *utils.CGRE } args.Tenant = utils.FirstNonEmpty(args.Tenant, dS.cfg.GeneralCfg().DefaultTenant) if len(dS.cfg.DispatcherSCfg().AttributeSConns) != 0 { - if err = dS.authorize(utils.ThresholdSv1Ping, args.Tenant, + if err = dS.authorize(utils.TrendSv1Ping, args.Tenant, utils.IfaceAsString(args.APIOpts[utils.OptsAPIKey]), args.Time); err != nil { return } diff --git a/dispatchers/trends_it_test.go b/dispatchers/trends_it_test.go new file mode 100644 index 000000000..5913a0ff6 --- /dev/null +++ b/dispatchers/trends_it_test.go @@ -0,0 +1,164 @@ +//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 dispatchers + +import ( + "testing" + "time" + + "github.com/cgrates/birpc/context" + "github.com/cgrates/cgrates/engine" + "github.com/cgrates/cgrates/utils" +) + +var sTestDspTr = []func(t *testing.T){ + testDspTrPingFailover, + testDspTrGetTrendFailover, + testDspTrPing, + testDspTrTestAuthKey, + testDspTrTestAuthKey2, +} + +func TestDspTrendS(t *testing.T) { + var config1, config2, config3 string + switch *utils.DBType { + case utils.MetaInternal: + t.SkipNow() + case utils.MetaMySQL: + config1 = "trd_rnk_mysql" + config2 = "trd_rnk2_mysql" + config3 = "dispatchers_mysql" + case utils.MetaMongo: + config1 = "trd_rnk_mongo" + config2 = "trd_rnk2_mongo" + config3 = "dispatchers_mongo" + case utils.MetaPostgres: + t.SkipNow() + default: + t.Fatal("Unknown Database type") + } + testDsp(t, sTestDspTr, "TestDspTrendS", config1, config2, config3, "tuttrends", "testtp", "dispatchers") +} + +func testDspTrPingFailover(t *testing.T) { + var reply string + if err := allEngine.RPC.Call(context.Background(), utils.TrendSv1Ping, new(utils.CGREvent), &reply); err != nil { + t.Error(err) + } else if reply != utils.Pong { + t.Errorf("Received: %s", reply) + } + ev := utils.CGREvent{ + Tenant: "cgrates.org", + + APIOpts: map[string]any{ + utils.OptsAPIKey: "tr12345", + }, + } + if err := dispEngine.RPC.Call(context.Background(), utils.TrendSv1Ping, &ev, &reply); err != nil { + t.Error(err) + } else if reply != utils.Pong { + t.Errorf("Received: %s", reply) + } + allEngine.stopEngine(t) + time.Sleep(200 * time.Millisecond) + if err := dispEngine.RPC.Call(context.Background(), utils.TrendSv1Ping, &ev, &reply); err != nil { + t.Error(err) + } else if reply != utils.Pong { + t.Errorf("Received: %s", reply) + } + allEngine2.stopEngine(t) + if err := dispEngine.RPC.Call(context.Background(), utils.TrendSv1Ping, &ev, &reply); err == nil { + t.Errorf("Expected error but received %v and reply %v\n", err, reply) + } + allEngine.startEngine(t) + allEngine2.startEngine(t) +} + +func testDspTrGetTrendFailover(t *testing.T) { + trSched := utils.ArgScheduleTrendQueries{TrendIDs: []string{"TREND_1"}, TenantIDWithAPIOpts: utils.TenantIDWithAPIOpts{TenantID: &utils.TenantID{Tenant: "cgrates.org"}, APIOpts: map[string]any{utils.OptsAPIKey: "tr12345"}}} + var scheduled int + + if err := dispEngine.RPC.Call(context.Background(), utils.TrendSv1ScheduleQueries, &trSched, + &scheduled); err != nil { + t.Error(err) + } else if scheduled != 1 { + t.Errorf("expected %d,received %d", 1, scheduled) + } + allEngine.stopEngine(t) + time.Sleep(200 * time.Millisecond) + if err := dispEngine.RPC.Call(context.Background(), utils.TrendSv1ScheduleQueries, &trSched, + &scheduled); err == nil || err.Error() != utils.ErrPartiallyExecuted.Error() { + t.Errorf("expected err %v,received %v", utils.ErrPartiallyExecuted, err) + } + allEngine.startEngine(t) +} + +func testDspTrPing(t *testing.T) { + var reply string + if err := allEngine.RPC.Call(context.Background(), utils.TrendSv1Ping, new(utils.CGREvent), &reply); err != nil { + t.Error(err) + } else if reply != utils.Pong { + t.Errorf("Received: %s", reply) + } + if err := dispEngine.RPC.Call(context.Background(), utils.TrendSv1Ping, &utils.CGREvent{ + Tenant: "cgrates.org", + + APIOpts: map[string]any{ + utils.OptsAPIKey: "tr12345", + }, + }, &reply); err != nil { + t.Error(err) + } else if reply != utils.Pong { + t.Errorf("Received: %s", reply) + } +} + +func testDspTrTestAuthKey(t *testing.T) { + var tr *engine.Trend + args := &utils.ArgGetTrend{ + TenantWithAPIOpts: utils.TenantWithAPIOpts{ + Tenant: "cgrates.org", + APIOpts: map[string]any{ + utils.OptsAPIKey: "12345", + }, + }, + ID: "TREND_1", + } + if err := dispEngine.RPC.Call(context.Background(), utils.TrendSv1GetTrend, + args, &tr); err == nil || err.Error() != utils.ErrUnauthorizedApi.Error() { + t.Error(err) + } + trSched := utils.ArgScheduleTrendQueries{TrendIDs: []string{"TREND_1", "TREND_2"}, TenantIDWithAPIOpts: utils.TenantIDWithAPIOpts{TenantID: &utils.TenantID{Tenant: "cgrates.org"}, APIOpts: map[string]any{utils.OptsAPIKey: "12345"}}} + var scheduled int + if err := dispEngine.RPC.Call(context.Background(), utils.TrendSv1ScheduleQueries, + &trSched, &scheduled); err == nil || err.Error() != utils.ErrUnauthorizedApi.Error() { + t.Error(err) + } +} +func testDspTrTestAuthKey2(t *testing.T) { + var schedTrends []utils.ScheduledTrend + if err := dispEngine.RPC.Call(context.Background(), utils.TrendSv1GetScheduledTrends, &utils.ArgScheduledTrends{TenantIDWithAPIOpts: utils.TenantIDWithAPIOpts{TenantID: &utils.TenantID{Tenant: "cgrates.org"}, APIOpts: map[string]any{utils.OptsAPIKey: "tr12345"}}, TrendIDPrefixes: []string{"TREND_1"}}, &schedTrends); err != nil { + t.Error(err) + } else if len(schedTrends) != 1 { + t.Errorf("expected 1 schedTrends, got %d", len(schedTrends)) + } +}