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)
}