From 8af781fb75de389022c0838646cd6df602bebe85 Mon Sep 17 00:00:00 2001 From: arberkatellari Date: Mon, 20 Oct 2025 18:19:24 +0200 Subject: [PATCH] Add tests for multiple DB conns --- config/configsanity.go | 2 +- .../cgrates.json | 28 +- .../samples/multiple_dbs_mongo/cgrates.json | 154 ++++ .../samples/offline_internal/cgrates.json | 6 +- .../offline_internal_limit/cgrates.json | 6 +- .../samples/offline_internal_ms/cgrates.json | 6 +- .../offline_internal_ms_limit/cgrates.json | 6 +- engine/libtest.go | 29 + general_tests/multiple_dbs_it_test.go | 715 ++++++++++++++++++ general_tests/offline_internal_it_test.go | 3 - 10 files changed, 936 insertions(+), 19 deletions(-) rename data/conf/samples/{multiple_datadbs => multiple_dbs}/cgrates.json (79%) create mode 100644 data/conf/samples/multiple_dbs_mongo/cgrates.json create mode 100644 general_tests/multiple_dbs_it_test.go diff --git a/config/configsanity.go b/config/configsanity.go index b7550161c..08faa8b16 100644 --- a/config/configsanity.go +++ b/config/configsanity.go @@ -1029,7 +1029,7 @@ func (cfg *CGRConfig) checkConfigSanity() error { for _, dbcfg := range cfg.dbCfg.DBConns { if dbcfg.Type == utils.MetaInternal { if hasOneInternalDB { - return fmt.Errorf("<%s> There can only be 1 internal DataDB", utils.DB) + return fmt.Errorf("<%s> There can only be 1 internal DB", utils.DB) } if (cfg.dbCfg.Opts.InternalDBDumpInterval != 0 || cfg.dbCfg.Opts.InternalDBRewriteInterval != 0) && diff --git a/data/conf/samples/multiple_datadbs/cgrates.json b/data/conf/samples/multiple_dbs/cgrates.json similarity index 79% rename from data/conf/samples/multiple_datadbs/cgrates.json rename to data/conf/samples/multiple_dbs/cgrates.json index 9539fbba7..b471edae6 100644 --- a/data/conf/samples/multiple_datadbs/cgrates.json +++ b/data/conf/samples/multiple_dbs/cgrates.json @@ -40,9 +40,13 @@ } }, "items":{ - "*charger_profiles": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false, "dbConn": "intrnl"}, - "*charger_filter_indexes" : {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate": false, "dbConn": "redis2"}, + "*charger_profiles": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false, "dbConn": "redis2"}, + "*charger_filter_indexes" : {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate": false, "dbConn": "intrnl"}, "*cdrs": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false, "dbConn": "StorDB"} + }, + "opts": { + "internalDBDumpPath": "/tmp/internal_db/db", + "internalDBRewriteInterval": "0s" } }, @@ -54,9 +58,9 @@ "attributes": { "enabled": true, - "stats_conns": ["*localhost"], - "resources_conns": ["*localhost"], - "accounts_conns": ["*localhost"] + "stats_conns": ["*internal"], + "resources_conns": ["*internal"], + "accounts_conns": ["*internal"] }, "chargers": { @@ -124,7 +128,7 @@ "actions": { "enabled": true, - "accounts_conns": ["*localhost"] + "accounts_conns": ["*internal"] }, @@ -141,6 +145,16 @@ "tpes": { "enabled": true -} +}, + +"loaders": [ + { + "enabled":true, + "id": "*default", + "tp_in_dir": "/usr/share/cgrates/tariffplans/testit", + "tp_out_dir": "", + "lockfile_path": ".cgr.lck" + } +] } diff --git a/data/conf/samples/multiple_dbs_mongo/cgrates.json b/data/conf/samples/multiple_dbs_mongo/cgrates.json new file mode 100644 index 000000000..ec3bb12d5 --- /dev/null +++ b/data/conf/samples/multiple_dbs_mongo/cgrates.json @@ -0,0 +1,154 @@ +{ + + +"general": { + "reply_timeout": "50s" +}, + +"logger": { + "level": 7 +}, + +"listen": { + "rpc_json": ":2012", + "rpc_gob": ":2013", + "http": ":2080" +}, + +"db": { + "db_conns": { + "*default": { // using redis a default to see which files are dumped in internal + "db_type": "*redis", + "db_port": 6379, + "db_name": "10" + }, + "internalDBConn": { + "db_type": "*internal" + }, + "mongoDBConn": { + "db_type": "mongo", + "db_host": "127.0.0.1", + "db_port": 27017, + "db_name": "cgrates", + "db_user": "cgrates" + } + }, + "items":{ + "*charger_profiles": {"dbConn": "mongoDBConn"}, + "*charger_filter_indexes" : {"dbConn": "internalDBConn"}, + "*cdrs": {"dbConn": "mongoDBConn"} + }, + "opts": { + "internalDBDumpPath": "/tmp/internal_db/db", + "internalDBRewriteInterval": "0s" + } +}, + +"cdrs": { + "enabled": true, + "chargers_conns":["*internal"] +}, + + +"attributes": { + "enabled": true, + "stats_conns": ["*localhost"], + "resources_conns": ["*localhost"], + "accounts_conns": ["*localhost"] +}, + +"chargers": { + "enabled": true, + "attributes_conns": ["*internal"] +}, + + +"resources": { + "enabled": true, + "store_interval": "1s", + "thresholds_conns": ["*internal"] +}, + + +"stats": { + "enabled": true, + "store_interval": "1s", + "thresholds_conns": ["*internal"] +}, + + +"thresholds": { + "enabled": true, + "store_interval": "1s" +}, + + +"routes": { + "enabled": true, + "prefix_indexed_fields":["*req.Destination"], + "stats_conns": ["*internal"], + "resources_conns": ["*internal"], + "rates_conns": ["*internal"] +}, + + +"sessions": { + "enabled": true, + "routes_conns": ["*internal"], + "resources_conns": ["*internal"], + "attributes_conns": ["*internal"], + "rates_conns": ["*internal"], + "cdrs_conns": ["*internal"], + "chargers_conns": ["*internal"] +}, + + +"migrator":{ + + "users_filters":["Account"] +}, + + +"admins": { + "enabled": true, + "scheduler_conns": ["*internal"] +}, + + +"rates": { + "enabled": true +}, + + +"actions": { + "enabled": true, + "accounts_conns": ["*localhost"] +}, + + +"accounts": { + "enabled": true +}, + + +"filters": { + "stats_conns": ["*internal"], + "resources_conns": ["*internal"], + "accounts_conns": ["*internal"] +}, + +"tpes": { + "enabled": true +}, + +"loaders": [ + { + "enabled":true, + "id": "*default", + "tp_in_dir": "/usr/share/cgrates/tariffplans/testit", + "tp_out_dir": "", + "lockfile_path": ".cgr.lck" + } +] + +} diff --git a/data/conf/samples/offline_internal/cgrates.json b/data/conf/samples/offline_internal/cgrates.json index 46697cabc..c54613489 100644 --- a/data/conf/samples/offline_internal/cgrates.json +++ b/data/conf/samples/offline_internal/cgrates.json @@ -18,14 +18,16 @@ }, "opts": { "internalDBStartTimeout": "1m", - "internalDBDumpInterval": "-1" + "internalDBDumpInterval": "-1", + "internalDBRewriteInterval": "0s" } }, "config_db": { "db_type": "*internal", "opts": { "internalDBStartTimeout": "1m", - "internalDBDumpInterval": "-1" + "internalDBDumpInterval": "-1", + "internalDBRewriteInterval": "0s" } }, "cdrs": { diff --git a/data/conf/samples/offline_internal_limit/cgrates.json b/data/conf/samples/offline_internal_limit/cgrates.json index 5b6b1e988..1db975a1f 100644 --- a/data/conf/samples/offline_internal_limit/cgrates.json +++ b/data/conf/samples/offline_internal_limit/cgrates.json @@ -18,7 +18,8 @@ "opts":{ "internalDBStartTimeout": "1m", "internalDBDumpInterval": "-1", - "internalDBFileSizeLimit": "4KB" + "internalDBFileSizeLimit": "4KB", + "internalDBRewriteInterval": "0s" } }, "config_db": { @@ -26,7 +27,8 @@ "opts": { "internalDBStartTimeout": "1m", "internalDBDumpInterval": "-1", - "internalDBFileSizeLimit": "4KB" + "internalDBFileSizeLimit": "4KB", + "internalDBRewriteInterval": "0s" } }, "cdrs": { diff --git a/data/conf/samples/offline_internal_ms/cgrates.json b/data/conf/samples/offline_internal_ms/cgrates.json index 98a9762df..ef879dd98 100644 --- a/data/conf/samples/offline_internal_ms/cgrates.json +++ b/data/conf/samples/offline_internal_ms/cgrates.json @@ -17,14 +17,16 @@ }, "opts":{ "internalDBStartTimeout": "1m", - "internalDBDumpInterval": "500ms" + "internalDBDumpInterval": "500ms", + "internalDBRewriteInterval": "0s" } }, "config_db": { "db_type": "*internal", "opts": { "internalDBStartTimeout": "1m", - "internalDBDumpInterval": "500ms" + "internalDBDumpInterval": "500ms", + "internalDBRewriteInterval": "0s" } }, "cdrs": { diff --git a/data/conf/samples/offline_internal_ms_limit/cgrates.json b/data/conf/samples/offline_internal_ms_limit/cgrates.json index 2ef2547db..a2c3ba59e 100644 --- a/data/conf/samples/offline_internal_ms_limit/cgrates.json +++ b/data/conf/samples/offline_internal_ms_limit/cgrates.json @@ -18,7 +18,8 @@ "opts":{ "internalDBStartTimeout": "1m", "internalDBDumpInterval": "500ms", - "internalDBFileSizeLimit": "4KB" + "internalDBFileSizeLimit": "4KB", + "internalDBRewriteInterval": "0s" } }, "config_db": { @@ -26,7 +27,8 @@ "opts": { "internalDBStartTimeout": "1m", "internalDBDumpInterval": "500ms", - "internalDBFileSizeLimit": "4KB" + "internalDBFileSizeLimit": "4KB", + "internalDBRewriteInterval": "0s" } }, "cdrs": { diff --git a/engine/libtest.go b/engine/libtest.go index 7628eccfc..cd69d38f4 100644 --- a/engine/libtest.go +++ b/engine/libtest.go @@ -125,6 +125,35 @@ func StartEngine(cfgPath string, waitEngine int) (*exec.Cmd, error) { return engine, nil } +// Starts the engine from a string JSON config +func StartEngineFromString(cfgJSON string, waitEngine int, t testing.TB) (*exec.Cmd, error) { + cfgPath := t.TempDir() + // A JSON configuration string has been passed to the object. + // It can be standalone or used to overwrite sections from an + // existing configuration file. In case it's the latter, ensure + // the file is processed towards the end. + filePath := filepath.Join(cfgPath, "zzz_dynamic_cgrates.json") + if err := os.WriteFile(filePath, []byte(cfgJSON), 0644); err != nil { + return nil, err + } + var err error + cfg, err := config.NewCGRConfigFromPath(context.TODO(), cfgPath) + if err != nil { + return nil, fmt.Errorf("could not init config from path %s: %v", cfgPath, err) + } + binPath, err := exec.LookPath("cgr-engine") + if err != nil { + return nil, err + } + flags := []string{"-config_path", cfg.ConfigPath} + engine := exec.Command(binPath, flags...) + if err := engine.Start(); err != nil { + return nil, fmt.Errorf("cgr-engine command failed: %v", err) + } + time.Sleep(time.Duration(waitEngine) * time.Millisecond) // wait for rater to register all subsystems + return engine, nil +} + // StartEngineWithContext return reference towards the command started so we can stop it if necessary func StartEngineWithContext(ctx context.Context, cfgPath string, waitEngine int) (engine *exec.Cmd, err error) { engine = exec.CommandContext(ctx, "cgr-engine", "-config_path", cfgPath) diff --git a/general_tests/multiple_dbs_it_test.go b/general_tests/multiple_dbs_it_test.go new file mode 100644 index 000000000..b313eafc8 --- /dev/null +++ b/general_tests/multiple_dbs_it_test.go @@ -0,0 +1,715 @@ +//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 Affero 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 Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see +*/ +package general_tests + +import ( + "io/fs" + "os" + "path/filepath" + "reflect" + "sort" + "strings" + "testing" + "time" + + "github.com/cgrates/birpc/context" + "github.com/cgrates/cgrates/apis" + "github.com/cgrates/cgrates/engine" + "github.com/cgrates/cgrates/loaders" + "github.com/cgrates/cgrates/utils" +) + +func TestMultipleDBs(t *testing.T) { + if err := os.MkdirAll("/tmp/internal_db/db", 0755); err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + if err := os.RemoveAll("/tmp/internal_db"); err != nil { + t.Error(err) + } + }) + ng := engine.TestEngine{ + ConfigPath: filepath.Join(*utils.DataDir, "conf", "samples", "multiple_dbs"), + GracefulShutdown: true, + Encoding: *utils.Encoding, + } + client, cfg := ng.Run(t) + t.Run("LoadTariffPlans", func(t *testing.T) { + var reply string + if err := client.Call(context.Background(), utils.LoaderSv1Run, + &loaders.ArgsProcessFolder{ + APIOpts: map[string]any{ + utils.MetaCache: utils.MetaNone, + }, + }, &reply); err != nil { + t.Error(err) + } else if reply != utils.OK { + t.Error("Unexpected reply returned:", reply) + } + time.Sleep(100 * time.Millisecond) + }) + + t.Run("CheckChargers", func(t *testing.T) { // stored in redis2 + var chrgrs []*utils.ChargerProfile + if err := client.Call(context.Background(), utils.AdminSv1GetChargerProfiles, + &utils.ArgsItemIDs{ + Tenant: "cgrates.org", + }, &chrgrs); err != nil { + t.Errorf("AdminSv1GetChargerProfiles failed unexpectedly: %v", err) + } + if len(chrgrs) != 3 { + t.Fatalf("AdminSv1GetChargerProfiles len(chrgrs)=%v, want 3", len(chrgrs)) + } + sort.Slice(chrgrs, func(i, j int) bool { + return chrgrs[i].ID > chrgrs[j].ID + }) + exp := []*utils.ChargerProfile{ + { + ID: "SupplierCharges", + Tenant: "cgrates.org", + Weights: utils.DynamicWeights{ + { + Weight: 10, + }, + }, + RunID: "SupplierCharges", + AttributeIDs: []string{"ATTR_SUPPLIER1"}, + }, + { + ID: "Raw", + Tenant: "cgrates.org", + Weights: utils.DynamicWeights{ + { + Weight: 20, + }, + }, + RunID: "raw", + AttributeIDs: []string{"*constant:*req.RequestType:*none"}, + }, + { + ID: "CustomerCharges", + Tenant: "cgrates.org", + Weights: utils.DynamicWeights{ + { + Weight: 20, + }, + }, + RunID: "CustomerCharges", + AttributeIDs: []string{"*none"}, + }, + } + if !reflect.DeepEqual(exp, chrgrs) { + t.Errorf("Expected <%+v>,\n received <%+v>", exp, chrgrs) + } + }) + + t.Run("CheckChargerFilterIndexes", func(t *testing.T) { // stored in internal + var replyIdx []string + expectedIDx := []string{"*none:*any:*any:CustomerCharges", "*none:*any:*any:Raw", + "*none:*any:*any:SupplierCharges"} + if err := client.Call(context.Background(), utils.AdminSv1GetFilterIndexes, + &apis.AttrGetFilterIndexes{ + Tenant: utils.CGRateSorg, + ItemType: utils.MetaChargers, + }, + &replyIdx); err != nil { + t.Error(err) + } else { + sort.Strings(replyIdx) + sort.Strings(expectedIDx) + if !reflect.DeepEqual(expectedIDx, replyIdx) { + t.Errorf("Expected %+v, \nreceived %+v", utils.ToJSON(expectedIDx), utils.ToJSON(replyIdx)) + } + } + }) + + t.Run("CheckAccounts", func(t *testing.T) { // stored in *default (redis in this case) + var acnts []*utils.Account + if err := client.Call(context.Background(), utils.AdminSv1GetAccounts, + &utils.ArgsItemIDs{ + Tenant: "cgrates.org", + }, &acnts); err != nil { + t.Errorf("AdminSv2GetAccounts failed unexpectedly: %v", err) + } + if len(acnts) != 2 { + t.Fatalf("AdminSv2GetAccounts len(acnts)=%v, want 2", len(acnts)) + } + sort.Slice(acnts, func(i, j int) bool { + return acnts[i].ID > acnts[j].ID + }) + exp := []*utils.Account{ + { + Tenant: "cgrates.org", + ID: "ACC_PRF_1", + Opts: map[string]any{}, + Balances: map[string]*utils.Balance{ + "MonetaryBalance": { + ID: "MonetaryBalance", + Weights: utils.DynamicWeights{ + { + Weight: 10, + }, + }, + Type: "*monetary", + Opts: map[string]any{}, + Units: utils.NewDecimal(14, 0), + UnitFactors: []*utils.UnitFactor{ + { + FilterIDs: []string{"fltr1", "fltr2"}, + Factor: utils.NewDecimal(100, 0), + }, + { + FilterIDs: []string{"fltr3"}, + Factor: utils.NewDecimal(200, 0), + }, + }, + CostIncrements: []*utils.CostIncrement{ + { + FilterIDs: []string{"fltr1", "fltr2"}, + Increment: utils.NewDecimal(13, 1), + FixedFee: utils.NewDecimal(23, 1), + RecurrentFee: utils.NewDecimal(33, 1), + }, + }, + AttributeIDs: []string{"attr1", "attr2"}, + }, + }, + Weights: utils.DynamicWeights{ + { + Weight: 20, + }, + }, + ThresholdIDs: []string{utils.MetaNone}, + }, + { + Tenant: "cgrates.org", + ID: "1001", + Weights: utils.DynamicWeights{ + { + Weight: 20, + }, + }, + Opts: map[string]any{}, + Balances: map[string]*utils.Balance{ + "MonetaryBalance": { + ID: "MonetaryBalance", + Weights: utils.DynamicWeights{ + { + Weight: 10, + }, + }, + Type: "*monetary", + Opts: map[string]any{}, + Units: utils.NewDecimal(14, 0), + UnitFactors: []*utils.UnitFactor{ + { + FilterIDs: []string{"fltr1", "fltr2"}, + Factor: utils.NewDecimal(100, 0), + }, + { + FilterIDs: []string{"fltr3"}, + Factor: utils.NewDecimal(200, 0), + }, + }, + CostIncrements: []*utils.CostIncrement{ + { + FilterIDs: []string{"fltr1", "fltr2"}, + Increment: utils.NewDecimal(13, 1), + FixedFee: utils.NewDecimal(23, 1), + RecurrentFee: utils.NewDecimal(33, 1), + }, + }, + AttributeIDs: []string{"attr1", "attr2"}, + }, + "VoiceBalance": { + ID: "VoiceBalance", + Weights: utils.DynamicWeights{ + { + Weight: 10, + }, + { + Weight: 10, + }, + }, + Blockers: utils.DynamicBlockers{ + { + FilterIDs: []string{"*string:~*req.Destination:1002"}, + Blocker: true, + }, + { + Blocker: false, + }, + }, + Type: "*voice", + Opts: map[string]any{}, + Units: utils.NewDecimalFromUsageIgnoreErr("1h"), + }, + }, + ThresholdIDs: []string{utils.MetaNone}, + }, + } + if !reflect.DeepEqual(exp, acnts) { + t.Errorf("Expected <%+v>,\n received <%+v>", exp, acnts) + } + }) + + t.Run("CheckCdrs", func(t *testing.T) { // stored in mysql + var cdrs []*utils.CDR + if err := client.Call(context.Background(), utils.AdminSv1GetCDRs, &utils.CDRFilters{Tenant: "cgrates.org"}, &cdrs); err == nil || err.Error() != "retrieving CDRs failed: NOT_FOUND" { + t.Errorf("Expecting error <%v>, received: <%v>", "retrieving CDRs failed: NOT_FOUND", err) + } + ev := &utils.CGREvent{ + Tenant: "cgrates.org", + ID: "TestEv1", + Event: map[string]any{ + utils.ToR: utils.MetaVoice, + utils.OriginID: "TestEv1", + utils.RequestType: utils.MetaPrepaid, + utils.AccountField: "1001", + utils.Subject: "1001", + utils.Destination: "1002", + utils.Usage: time.Minute, + }, + APIOpts: map[string]any{ + utils.MetaRates: true, + utils.MetaAccounts: false, + }, + } + var rply string + client.Call(context.Background(), utils.CDRsV1ProcessEvent, ev, &rply) + if err := client.Call(context.Background(), utils.AdminSv1GetCDRs, &utils.CDRFilters{Tenant: "cgrates.org"}, &cdrs); err != nil { + t.Error(err) + } + if len(cdrs) != 1 { + t.Errorf("unexpected number of cdrs found: %v", len(cdrs)) + } + exp := &utils.CDR{ + Tenant: utils.CGRateSorg, + Opts: map[string]any{ + utils.MetaCDRID: cdrs[0].Opts[utils.MetaCDRID], + utils.MetaRates: true, + utils.MetaAccounts: false, + }, + Event: map[string]any{ + utils.AccountField: "1001", + utils.Destination: "1002", + utils.OriginID: "TestEv1", + utils.RequestType: utils.MetaPrepaid, + utils.Subject: "1001", + utils.ToR: utils.MetaVoice, + utils.Usage: 6e+10, + }, + CreatedAt: cdrs[0].CreatedAt, + UpdatedAt: cdrs[0].UpdatedAt, + } + if !reflect.DeepEqual(exp, cdrs[0]) { + t.Errorf("Expecting <%#v>, \nreceived <%#v>", exp, cdrs[0]) + } + }) + t.Run("EngineShutdown", func(t *testing.T) { + if err := engine.KillEngine(100); err != nil { + t.Error(err) + } + }) + + t.Run("CountDBFiles", func(t *testing.T) { + var dirs, files int + if err := filepath.WalkDir(cfg.DbCfg().Opts.InternalDBDumpPath, func(root string, info fs.DirEntry, err error) error { + if err != nil { + return err + } + if info.IsDir() { + dirs++ + } else { + if !strings.HasPrefix(root, "/tmp/internal_db/db/*charger_filter_indexes") && + !strings.HasPrefix(root, "/tmp/internal_db/db/*versions") { + t.Fatalf("got unexpected folder <%s>", root) + } + files++ + } + return nil + }); err != nil { + t.Error(err) + } else if dirs != 37 { + t.Errorf("expected <%d> directories, received <%d>", 37, dirs) + } else if files != 2 { + t.Errorf("expected <%d> files, received <%d>", 2, files) + } + }) +} + +func TestMultipleDBsMongo(t *testing.T) { + if err := os.MkdirAll("/tmp/internal_db/db", 0755); err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + if err := os.RemoveAll("/tmp/internal_db"); err != nil { + t.Error(err) + } + }) + ng := engine.TestEngine{ + ConfigPath: filepath.Join(*utils.DataDir, "conf", "samples", "multiple_dbs_mongo"), + GracefulShutdown: true, + Encoding: *utils.Encoding, + } + client, cfg := ng.Run(t) + t.Run("LoadTariffPlans", func(t *testing.T) { + var reply string + if err := client.Call(context.Background(), utils.LoaderSv1Run, + &loaders.ArgsProcessFolder{ + APIOpts: map[string]any{ + utils.MetaCache: utils.MetaNone, + }, + }, &reply); err != nil { + t.Error(err) + } else if reply != utils.OK { + t.Error("Unexpected reply returned:", reply) + } + time.Sleep(100 * time.Millisecond) + }) + + t.Run("CheckChargers", func(t *testing.T) { // stored in mongo + var chrgrs []*utils.ChargerProfile + if err := client.Call(context.Background(), utils.AdminSv1GetChargerProfiles, + &utils.ArgsItemIDs{ + Tenant: "cgrates.org", + }, &chrgrs); err != nil { + t.Errorf("AdminSv1GetChargerProfiles failed unexpectedly: %v", err) + } + if len(chrgrs) != 3 { + t.Fatalf("AdminSv1GetChargerProfiles len(chrgrs)=%v, want 3", len(chrgrs)) + } + sort.Slice(chrgrs, func(i, j int) bool { + return chrgrs[i].ID > chrgrs[j].ID + }) + exp := []*utils.ChargerProfile{ + { + ID: "SupplierCharges", + Tenant: "cgrates.org", + Weights: utils.DynamicWeights{ + { + Weight: 10, + }, + }, + RunID: "SupplierCharges", + AttributeIDs: []string{"ATTR_SUPPLIER1"}, + }, + { + ID: "Raw", + Tenant: "cgrates.org", + Weights: utils.DynamicWeights{ + { + Weight: 20, + }, + }, + RunID: "raw", + AttributeIDs: []string{"*constant:*req.RequestType:*none"}, + }, + { + ID: "CustomerCharges", + Tenant: "cgrates.org", + Weights: utils.DynamicWeights{ + { + Weight: 20, + }, + }, + RunID: "CustomerCharges", + AttributeIDs: []string{"*none"}, + }, + } + if !reflect.DeepEqual(exp, chrgrs) { + t.Errorf("Expected <%+v>,\n received <%+v>", exp, chrgrs) + } + }) + + t.Run("CheckChargerFilterIndexes", func(t *testing.T) { // stored in internal + var replyIdx []string + expectedIDx := []string{"*none:*any:*any:CustomerCharges", "*none:*any:*any:Raw", + "*none:*any:*any:SupplierCharges"} + if err := client.Call(context.Background(), utils.AdminSv1GetFilterIndexes, + &apis.AttrGetFilterIndexes{ + Tenant: utils.CGRateSorg, + ItemType: utils.MetaChargers, + }, + &replyIdx); err != nil { + t.Error(err) + } else { + sort.Strings(replyIdx) + sort.Strings(expectedIDx) + if !reflect.DeepEqual(expectedIDx, replyIdx) { + t.Errorf("Expected %+v, \nreceived %+v", utils.ToJSON(expectedIDx), utils.ToJSON(replyIdx)) + } + } + }) + + t.Run("CheckAccounts", func(t *testing.T) { // stored in *default (redis in this case) + var acnts []*utils.Account + if err := client.Call(context.Background(), utils.AdminSv1GetAccounts, + &utils.ArgsItemIDs{ + Tenant: "cgrates.org", + }, &acnts); err != nil { + t.Errorf("AdminSv2GetAccounts failed unexpectedly: %v", err) + } + if len(acnts) != 2 { + t.Fatalf("AdminSv2GetAccounts len(acnts)=%v, want 2", len(acnts)) + } + sort.Slice(acnts, func(i, j int) bool { + return acnts[i].ID > acnts[j].ID + }) + exp := []*utils.Account{ + { + Tenant: "cgrates.org", + ID: "ACC_PRF_1", + Opts: map[string]any{}, + Balances: map[string]*utils.Balance{ + "MonetaryBalance": { + ID: "MonetaryBalance", + Weights: utils.DynamicWeights{ + { + Weight: 10, + }, + }, + Type: "*monetary", + Opts: map[string]any{}, + Units: utils.NewDecimal(14, 0), + UnitFactors: []*utils.UnitFactor{ + { + FilterIDs: []string{"fltr1", "fltr2"}, + Factor: utils.NewDecimal(100, 0), + }, + { + FilterIDs: []string{"fltr3"}, + Factor: utils.NewDecimal(200, 0), + }, + }, + CostIncrements: []*utils.CostIncrement{ + { + FilterIDs: []string{"fltr1", "fltr2"}, + Increment: utils.NewDecimal(13, 1), + FixedFee: utils.NewDecimal(23, 1), + RecurrentFee: utils.NewDecimal(33, 1), + }, + }, + AttributeIDs: []string{"attr1", "attr2"}, + }, + }, + Weights: utils.DynamicWeights{ + { + Weight: 20, + }, + }, + ThresholdIDs: []string{utils.MetaNone}, + }, + { + Tenant: "cgrates.org", + ID: "1001", + Weights: utils.DynamicWeights{ + { + Weight: 20, + }, + }, + Opts: map[string]any{}, + Balances: map[string]*utils.Balance{ + "MonetaryBalance": { + ID: "MonetaryBalance", + Weights: utils.DynamicWeights{ + { + Weight: 10, + }, + }, + Type: "*monetary", + Opts: map[string]any{}, + Units: utils.NewDecimal(14, 0), + UnitFactors: []*utils.UnitFactor{ + { + FilterIDs: []string{"fltr1", "fltr2"}, + Factor: utils.NewDecimal(100, 0), + }, + { + FilterIDs: []string{"fltr3"}, + Factor: utils.NewDecimal(200, 0), + }, + }, + CostIncrements: []*utils.CostIncrement{ + { + FilterIDs: []string{"fltr1", "fltr2"}, + Increment: utils.NewDecimal(13, 1), + FixedFee: utils.NewDecimal(23, 1), + RecurrentFee: utils.NewDecimal(33, 1), + }, + }, + AttributeIDs: []string{"attr1", "attr2"}, + }, + "VoiceBalance": { + ID: "VoiceBalance", + Weights: utils.DynamicWeights{ + { + Weight: 10, + }, + { + Weight: 10, + }, + }, + Blockers: utils.DynamicBlockers{ + { + FilterIDs: []string{"*string:~*req.Destination:1002"}, + Blocker: true, + }, + { + Blocker: false, + }, + }, + Type: "*voice", + Opts: map[string]any{}, + Units: utils.NewDecimalFromUsageIgnoreErr("1h"), + }, + }, + ThresholdIDs: []string{utils.MetaNone}, + }, + } + if !reflect.DeepEqual(exp, acnts) { + t.Errorf("Expected <%+v>,\n received <%+v>", exp, acnts) + } + }) + + t.Run("CheckCdrs", func(t *testing.T) { // stored in mongo + var cdrs []*utils.CDR + if err := client.Call(context.Background(), utils.AdminSv1GetCDRs, &utils.CDRFilters{Tenant: "cgrates.org"}, &cdrs); err == nil || err.Error() != "retrieving CDRs failed: NOT_FOUND" { + t.Errorf("Expecting error <%v>, received: <%v>", "retrieving CDRs failed: NOT_FOUND", err) + } + ev := &utils.CGREvent{ + Tenant: "cgrates.org", + ID: "TestEv1", + Event: map[string]any{ + utils.ToR: utils.MetaVoice, + utils.OriginID: "TestEv1", + utils.RequestType: utils.MetaPrepaid, + utils.AccountField: "1001", + utils.Subject: "1001", + utils.Destination: "1002", + utils.Usage: time.Minute, + }, + APIOpts: map[string]any{ + utils.MetaRates: true, + utils.MetaAccounts: false, + }, + } + var rply string + client.Call(context.Background(), utils.CDRsV1ProcessEvent, ev, &rply) + if err := client.Call(context.Background(), utils.AdminSv1GetCDRs, &utils.CDRFilters{Tenant: "cgrates.org"}, &cdrs); err != nil { + t.Error(err) + } + if len(cdrs) != 1 { + t.Errorf("unexpected number of cdrs found: %v", len(cdrs)) + } + exp := &utils.CDR{ + Tenant: utils.CGRateSorg, + Opts: map[string]any{ + utils.MetaCDRID: cdrs[0].Opts[utils.MetaCDRID], + utils.MetaRates: true, + utils.MetaAccounts: false, + }, + Event: map[string]any{ + utils.AccountField: "1001", + utils.Destination: "1002", + utils.OriginID: "TestEv1", + utils.RequestType: utils.MetaPrepaid, + utils.Subject: "1001", + utils.ToR: utils.MetaVoice, + utils.Usage: 6e+10, + }, + CreatedAt: cdrs[0].CreatedAt, + UpdatedAt: cdrs[0].UpdatedAt, + } + if !reflect.DeepEqual(exp, cdrs[0]) { + t.Errorf("Expecting <%#v>, \nreceived <%#v>", exp, cdrs[0]) + } + }) + t.Run("EngineShutdown", func(t *testing.T) { + if err := engine.KillEngine(100); err != nil { + t.Error(err) + } + }) + + t.Run("CountDBFiles", func(t *testing.T) { + var dirs, files int + if err := filepath.WalkDir(cfg.DbCfg().Opts.InternalDBDumpPath, func(root string, info fs.DirEntry, err error) error { + if err != nil { + return err + } + if info.IsDir() { + dirs++ + } else { + if !strings.HasPrefix(root, "/tmp/internal_db/db/*charger_filter_indexes") && + !strings.HasPrefix(root, "/tmp/internal_db/db/*versions") { + t.Fatalf("got unexpected folder <%s>", root) + } + files++ + } + return nil + }); err != nil { + t.Error(err) + } else if dirs != 37 { + t.Errorf("expected <%d> directories, received <%d>", 37, dirs) + } else if files != 2 { + t.Errorf("expected <%d> files, received <%d>", 2, files) + } + }) +} + +func TestMultipleDBsInternalFail(t *testing.T) { + if err := os.MkdirAll("/tmp/internal_db/db", 0755); err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + if err := os.RemoveAll("/tmp/internal_db"); err != nil { + t.Error(err) + } + }) + cfgJSON := `{ +"logger": { + "level": 7 +}, + +"db": { + "db_conns": { + "intrnl": { + "db_type": "*internal" + }, + }, + "items":{ + "*charger_profiles": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false, "dbConn": "intrnl"}, + }, + "opts": { + "internalDBDumpPath": "/tmp/internal_db/db", + "internalDBRewriteInterval": "0s" + } +}, + +} +` + expErr := `/001: There can only be 1 internal DB` + if _, err := engine.StartEngineFromString(cfgJSON, 200, t); err == nil || + !strings.Contains(err.Error(), expErr) { + t.Errorf("expected error <%v>, received <%v>", expErr, err) + } + +} diff --git a/general_tests/offline_internal_it_test.go b/general_tests/offline_internal_it_test.go index 15c64b3f3..84a1407e7 100644 --- a/general_tests/offline_internal_it_test.go +++ b/general_tests/offline_internal_it_test.go @@ -62,9 +62,6 @@ func TestOfflineInternal(t *testing.T) { // run with sudo if err := os.MkdirAll(dfltCfg.DbCfg().Opts.InternalDBDumpPath, 0755); err != nil { t.Fatal(err) } - // if err := os.MkdirAll(dfltCfg.StorDbCfg().Opts.InternalDBDumpPath, 0755); err != nil { - // t.Fatal(err) - // } if err := os.MkdirAll(dfltCfg.ConfigDBCfg().Opts.InternalDBDumpPath, 0755); err != nil { t.Fatal(err) }