From 8a2257d554cd74db89fe9f813f69a11f2876598f Mon Sep 17 00:00:00 2001 From: porosnicuadrian Date: Thu, 31 Mar 2022 17:59:00 +0300 Subject: [PATCH] Routing for lcr and qos --- data/tariffplans/tutroutes/Routes.csv | 9 + data/tariffplans/tutroutes/Stats.csv | 7 + general_tests/route_stats_it_test.go | 374 ++++++++++++++++++++++++++ 3 files changed, 390 insertions(+) create mode 100644 general_tests/route_stats_it_test.go diff --git a/data/tariffplans/tutroutes/Routes.csv b/data/tariffplans/tutroutes/Routes.csv index 12ffff908..7a412b6d4 100644 --- a/data/tariffplans/tutroutes/Routes.csv +++ b/data/tariffplans/tutroutes/Routes.csv @@ -26,3 +26,12 @@ cgrates.org,ROUTE_HC1,Fltr_tcc,,*hc,,route1,*gte:~*resources.RES_GRP2.Available: cgrates.org,ROUTE_HC1,,,,,route2,*gte:~*resources.RES_GRP1.TotalUsage:9,,RP_VENDOR1,RES_GRP1,,;20,,, cgrates.org,ROUTE_HC1,,,,,route3,,,RP_VENDOR1,RES_GRP2,,;10,,, + +cgrates.org,ROUTE_QOS_STATS,*prefix:~*req.Destination:+3342,,*qos,*asr;*acd,route1,,,,,STATS_TOP1,;20,,, +cgrates.org,ROUTE_QOS_STATS,,,,,route2,,,,,STATS_TOP2,;10,,, + +cgrates.org,ROUTE_LCR,*prefix:~*req.Destination:+2273,,*lc,,route1,*gte:~*stats.STATS_TOP3.*acd:1m;*lte:~*stats.STATS_TOP2.*asr:50,,RP_VENDOR1,,,;20,,, +cgrates.org,ROUTE_LCR,,,,,route2,*eq:~*stats.STATS_TOP3.*asr:100,,RP_STANDARD,,,;15,,, +cgrates.org,ROUTE_LCR,,,,,route3,*gt:~*stats.STATS_TOP1.*acd:30s,,RP_VENDOR2,,,;10,,, + + diff --git a/data/tariffplans/tutroutes/Stats.csv b/data/tariffplans/tutroutes/Stats.csv index 3fe5de26e..338b8c09d 100644 --- a/data/tariffplans/tutroutes/Stats.csv +++ b/data/tariffplans/tutroutes/Stats.csv @@ -3,3 +3,10 @@ cgrates.org,STATS_VENDOR_1,*string:~*req.Category:vendor1,,100,-1,,*acd;*tcd;*ac cgrates.org,STATS_VENDOR_2,*string:~*req.Category:vendor2,,100,-1,,*acd;*tcd;*acc;*tcc;*sum#1;*distinct#~*req.Usage,,,,,*none cgrates.org,STATS_TCC1,,,100,-1,,*tcc,,,,,*none cgrates.org,STATS_TCC2,Fltr_tcc,,100,-1,,*tcc,,,,,*none + +cgrates.org,STATS_TOP1,*string:~*req.Account:1010,,100,-1,,*asr;*acd,,,,,*none +cgrates.org,STATS_TOP2,*string:~*req.Destination:1021,,100,-1,,*asr;*acd,,,,,*none +cgrates.org,STATS_TOP3,*string:~*req.Category:call,,100,-1,,*asr;*acd,,,,,*none + + + diff --git a/general_tests/route_stats_it_test.go b/general_tests/route_stats_it_test.go new file mode 100644 index 000000000..616c22876 --- /dev/null +++ b/general_tests/route_stats_it_test.go @@ -0,0 +1,374 @@ +//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 ( + "path" + "reflect" + "sort" + "testing" + + "github.com/cgrates/birpc" + "github.com/cgrates/birpc/context" + "github.com/cgrates/cgrates/config" + "github.com/cgrates/cgrates/engine" + "github.com/cgrates/cgrates/loaders" + "github.com/cgrates/cgrates/utils" +) + +var ( + RtStatsSv1CfgPath string + RtStatsSv1Cfg *config.CGRConfig + RtStatsSv1BiRpc *birpc.Client + RtStatsSv1ConfDIR string // + + sTestsRtStatsSV1 = []func(t *testing.T){ + testV1RtStatsLoadConfig, + testV1RtStatsInitDataDb, + testV1RtStatsStartEngine, + testV1RtStatsRpcConn, + testV1RtStatsFromFolder, + testV1RtStatsProcessStatsValid, + testV1RtStatsProcessStatsNotAnswered, + testV1RtStatsGetMetrics, + testV1RtStatsGetRoutesQOSStrategy, + testV1RtStatsGetRoutesLowestCostStrategy, + testV1RtStatsStopEngine, + } +) + +// Test start here +func TestRtStatsCaseV1IT(t *testing.T) { + switch *dbType { + case utils.MetaInternal: + RtStatsSv1ConfDIR = "routes_cases_internal" + case utils.MetaMySQL: + RtStatsSv1ConfDIR = "routes_cases_mysql" + case utils.MetaMongo: + RtStatsSv1ConfDIR = "routes_cases_mongo" + case utils.MetaPostgres: + t.SkipNow() + default: + t.Fatal("Unknown Database type") + } + for _, stest := range sTestsRtStatsSV1 { + t.Run(RtStatsSv1ConfDIR, stest) + } +} + +func testV1RtStatsLoadConfig(t *testing.T) { + var err error + RtStatsSv1CfgPath = path.Join(*dataDir, "conf", "samples", RtStatsSv1ConfDIR) + if RtStatsSv1Cfg, err = config.NewCGRConfigFromPath(context.Background(), RtStatsSv1CfgPath); err != nil { + t.Error(err) + } +} + +func testV1RtStatsInitDataDb(t *testing.T) { + if err := engine.InitDataDB(RtStatsSv1Cfg); err != nil { + t.Fatal(err) + } +} + +func testV1RtStatsStartEngine(t *testing.T) { + if _, err := engine.StopStartEngine(RtStatsSv1CfgPath, *waitRater); err != nil { + t.Fatal(err) + } +} + +func testV1RtStatsRpcConn(t *testing.T) { + var err error + RtStatsSv1BiRpc, err = newBiRPCClient(RtStatsSv1Cfg.ListenCfg()) // We connect over JSON so we can also troubleshoot if needed + if err != nil { + t.Fatal("Could not connect to rater: ", err.Error()) + } +} + +func testV1RtStatsFromFolder(t *testing.T) { + caching := utils.MetaReload + if RtStatsSv1Cfg.DataDbCfg().Type == utils.Internal { + caching = utils.MetaNone + } + var reply string + if err := RtStatsSv1BiRpc.Call(context.Background(), utils.LoaderSv1Run, + &loaders.ArgsProcessFolder{ + // StopOnError: true, + APIOpts: map[string]interface{}{utils.MetaCache: caching}, + }, &reply); err != nil { + t.Error(err) + } else if reply != utils.OK { + t.Error("Unexpected reply returned:", reply) + } +} + +func testV1RtStatsProcessStatsValid(t *testing.T) { + var reply []string + expected := []string{"STATS_TCC1", "STATS_TOP1", "STATS_TOP2", "STATS_TOP3"} + ev1 := &utils.CGREvent{ + Tenant: "cgrates.org", + ID: "event1", + Event: map[string]interface{}{ + utils.AccountField: "1010", + utils.Destination: "1021", + utils.Category: "call", + utils.Usage: "1m20s", + utils.AnswerTime: "2022-04-01T05:00:00Z", + utils.Cost: 1.8, + }, + APIOpts: map[string]interface{}{ + utils.MetaUsage: "1m20s", + }, + } + if err := RtStatsSv1BiRpc.Call(context.Background(), utils.StatSv1ProcessEvent, ev1, &reply); err != nil { + t.Error(err) + } else { + sort.Strings(expected) + sort.Strings(reply) + if !reflect.DeepEqual(reply, expected) { + t.Errorf("Expecting: %+v, received: %+v", expected, reply) + } + } +} + +func testV1RtStatsProcessStatsNotAnswered(t *testing.T) { + // not answered means that our event does not have AnsweredTime + var reply []string + expected := []string{"STATS_TCC1", "STATS_TOP1", "STATS_TOP2"} + ev1 := &utils.CGREvent{ + Tenant: "cgrates.org", + ID: "event1", + Event: map[string]interface{}{ + utils.AccountField: "1010", + utils.Destination: "1021", + utils.Usage: "26s", + utils.Cost: 1.8, + }, + APIOpts: map[string]interface{}{ + utils.MetaUsage: "1m20s", + }, + } + // we will process this two times + // 1 + if err := RtStatsSv1BiRpc.Call(context.Background(), utils.StatSv1ProcessEvent, ev1, &reply); err != nil { + t.Error(err) + } else { + sort.Strings(expected) + sort.Strings(reply) + if !reflect.DeepEqual(reply, expected) { + t.Errorf("Expecting: %+v, received: %+v", expected, reply) + } + } + // 2 + if err := RtStatsSv1BiRpc.Call(context.Background(), utils.StatSv1ProcessEvent, ev1, &reply); err != nil { + t.Error(err) + } else { + sort.Strings(expected) + sort.Strings(reply) + if !reflect.DeepEqual(reply, expected) { + t.Errorf("Expecting: %+v, received: %+v", expected, reply) + } + } + + // process again some stats two times + ev1 = &utils.CGREvent{ + Tenant: "cgrates.org", + ID: "event1", + Event: map[string]interface{}{ + utils.AccountField: "1010", + utils.Category: "call", + utils.Usage: "50s", + utils.AnswerTime: "2022-04-01T05:00:00Z", + utils.Cost: 1.8, + }, + APIOpts: map[string]interface{}{ + utils.MetaUsage: "1m20s", + }, + } + expected = []string{"STATS_TCC1", "STATS_TOP1", "STATS_TOP3"} + if err := RtStatsSv1BiRpc.Call(context.Background(), utils.StatSv1ProcessEvent, ev1, &reply); err != nil { + t.Error(err) + } else { + sort.Strings(expected) + sort.Strings(reply) + if !reflect.DeepEqual(reply, expected) { + t.Errorf("Expecting: %+v, received: %+v", expected, reply) + } + } + if err := RtStatsSv1BiRpc.Call(context.Background(), utils.StatSv1ProcessEvent, ev1, &reply); err != nil { + t.Error(err) + } else { + sort.Strings(expected) + sort.Strings(reply) + if !reflect.DeepEqual(reply, expected) { + t.Errorf("Expecting: %+v, received: %+v", expected, reply) + } + } +} + +func testV1RtStatsGetMetrics(t *testing.T) { + expMetrics := map[string]float64{ + utils.MetaACD: 46400000000, + utils.MetaASR: 60, + } + rplyFloatMetrics := make(map[string]float64) + if err := RtStatsSv1BiRpc.Call(context.Background(), utils.StatSv1GetQueueFloatMetrics, + &utils.TenantIDWithAPIOpts{ + TenantID: &utils.TenantID{ + Tenant: "cgrates.org", + ID: "STATS_TOP1", + }, + }, &rplyFloatMetrics); err != nil { + t.Error(err) + } else if !reflect.DeepEqual(rplyFloatMetrics, expMetrics) { + t.Errorf("Expected %v, received %v", utils.ToJSON(expMetrics), utils.ToJSON(rplyFloatMetrics)) + } + + expMetrics = map[string]float64{ + utils.MetaACD: 44000000000, + utils.MetaASR: 33.33333333333333, + } + if err := RtStatsSv1BiRpc.Call(context.Background(), utils.StatSv1GetQueueFloatMetrics, + &utils.TenantIDWithAPIOpts{ + TenantID: &utils.TenantID{ + Tenant: "cgrates.org", + ID: "STATS_TOP2", + }, + }, &rplyFloatMetrics); err != nil { + t.Error(err) + } else if !reflect.DeepEqual(rplyFloatMetrics, expMetrics) { + t.Errorf("Expected %v, received %v", utils.ToJSON(expMetrics), utils.ToJSON(rplyFloatMetrics)) + } + + expMetrics = map[string]float64{ + utils.MetaACD: 60000000000, + utils.MetaASR: 100, + } + if err := RtStatsSv1BiRpc.Call(context.Background(), utils.StatSv1GetQueueFloatMetrics, + &utils.TenantIDWithAPIOpts{ + TenantID: &utils.TenantID{ + Tenant: "cgrates.org", + ID: "STATS_TOP3", + }, + }, &rplyFloatMetrics); err != nil { + t.Error(err) + } else if !reflect.DeepEqual(rplyFloatMetrics, expMetrics) { + t.Errorf("Expected %v, received %v", utils.ToJSON(expMetrics), utils.ToJSON(rplyFloatMetrics)) + } +} + +func testV1RtStatsGetRoutesQOSStrategy(t *testing.T) { + ev := &utils.CGREvent{ + ID: "LC_SORT", + Tenant: "cgrates.org", + Event: map[string]interface{}{ + utils.AccountField: "10015", + utils.Destination: "+33426654", + }, + } + expSrtdRoutes := &engine.SortedRoutesList{ + { + ProfileID: "ROUTE_QOS_STATS", + Sorting: "*qos", + Routes: []*engine.SortedRoute{ + { + RouteID: "route1", + SortingData: map[string]interface{}{ + utils.MetaACD: 46400000000., + utils.MetaASR: 60., + utils.Weight: 20., + }, + }, + { + RouteID: "route2", + SortingData: map[string]interface{}{ + utils.MetaACD: 44000000000., + utils.MetaASR: 33.33333333333333, + utils.Weight: 10., + }, + }, + }, + }, + } + var reply *engine.SortedRoutesList + if err := RtStatsSv1BiRpc.Call(context.Background(), utils.RouteSv1GetRoutes, + ev, &reply); err != nil { + t.Error(err) + } else if !reflect.DeepEqual(reply, expSrtdRoutes) { + t.Errorf("Expecting: %+v \n, received: %+v", utils.ToJSON(expSrtdRoutes), utils.ToJSON(reply)) + } +} + +func testV1RtStatsGetRoutesLowestCostStrategy(t *testing.T) { + ev := &utils.CGREvent{ + ID: "LC_SORT", + Tenant: "cgrates.org", + Event: map[string]interface{}{ + utils.AccountField: "10015", + utils.Destination: "+2273676400", + }, + } + expSrtdRoutes := &engine.SortedRoutesList{ + { + ProfileID: "ROUTE_LCR", + Sorting: "*lc", + Routes: []*engine.SortedRoute{ + { + RouteID: "route3", + SortingData: map[string]interface{}{ + utils.Cost: 0.05, + utils.RatingPlanID: "RP_VENDOR2", + utils.Weight: 10., + }, + }, + { + RouteID: "route1", + SortingData: map[string]interface{}{ + utils.Cost: 0.1, + utils.RatingPlanID: "RP_VENDOR1", + utils.Weight: 20., + }, + }, + { + RouteID: "route2", + SortingData: map[string]interface{}{ + utils.Cost: 0.6, + utils.RatingPlanID: "RP_STANDARD", + utils.Weight: 15., + }, + }, + }, + }, + } + var reply *engine.SortedRoutesList + if err := RtStatsSv1BiRpc.Call(context.Background(), utils.RouteSv1GetRoutes, + ev, &reply); err != nil { + t.Error(err) + } else if !reflect.DeepEqual(reply, expSrtdRoutes) { + t.Errorf("Expecting: %+v \n, received: %+v", utils.ToJSON(expSrtdRoutes), utils.ToJSON(reply)) + } +} + +func testV1RtStatsStopEngine(t *testing.T) { + if err := engine.KillEngine(100); err != nil { + t.Error(err) + } +}