diff --git a/data/conf/samples/ers_reload/disabled/cgrates.json b/data/conf/samples/ers_reload/disabled/cgrates.json new file mode 100644 index 000000000..a2368be8c --- /dev/null +++ b/data/conf/samples/ers_reload/disabled/cgrates.json @@ -0,0 +1,83 @@ +{ +// CGRateS Configuration file +// +// Used for SessionSv1 integration tests + + +"general": { + "log_level": 7, +}, + + +"listen": { + "rpc_json": ":2012", + "rpc_gob": ":2013", + "http": ":2080", +}, + + +"stor_db": { + "db_password": "CGRateS.org", +}, + + +"rals": { + "enabled": true, +}, + + +"scheduler": { + "enabled": true, +}, + + +"chargers": { + "enabled": true, + "attributes_conns": [ + {"address": "*internal"} + ], +}, + + +"resources": { + "enabled": true, +}, + + +"attributes": { + "enabled": true, +}, + + +"cdrs": { + "enabled": true, + "chargers_conns": [ + {"address": "127.0.0.1:2012", "transport": "*json"} + ], + "rals_conns": [ + {"address": "*internal"} + ], +}, + + +"sessions": { + "enabled": true, + "chargers_conns": [ + {"address": "127.0.0.1:2012", "transport": "*json"} + ], + "rals_conns": [ + {"address": "*internal"} + ], + "cdrs_conns": [ + {"address": "*internal"} + ], + "resources_conns": [ + {"address": "*internal"} + ], + "attributes_conns": [ + {"address": "*internal"} + ], +}, + + +} diff --git a/data/conf/samples/ers_reload/first_reload/cgrates.json b/data/conf/samples/ers_reload/first_reload/cgrates.json new file mode 100644 index 000000000..6aa44f4b5 --- /dev/null +++ b/data/conf/samples/ers_reload/first_reload/cgrates.json @@ -0,0 +1,127 @@ +{ +// CGRateS Configuration file +// +// Used for SessionSv1 integration tests + + +"general": { + "log_level": 7, +}, + + +"listen": { + "rpc_json": ":2012", + "rpc_gob": ":2013", + "http": ":2080", +}, + + +"stor_db": { + "db_password": "CGRateS.org", +}, + + +"rals": { + "enabled": true, +}, + + +"scheduler": { + "enabled": true, +}, + + +"chargers": { + "enabled": true, + "attributes_conns": [ + {"address": "*internal"} + ], +}, + + +"resources": { + "enabled": true, +}, + + +"attributes": { + "enabled": true, +}, + + +"cdrs": { + "enabled": true, + "chargers_conns": [ + {"address": "127.0.0.1:2012", "transport": "*json"} + ], + "rals_conns": [ + {"address": "*internal"} + ], +}, + + +"sessions": { + "enabled": true, + "chargers_conns": [ + {"address": "127.0.0.1:2012", "transport": "*json"} + ], + "rals_conns": [ + {"address": "*internal"} + ], + "cdrs_conns": [ + {"address": "*internal"} + ], + "resources_conns": [ + {"address": "*internal"} + ], + "attributes_conns": [ + {"address": "*internal"} + ], +}, + + +"ers": { + "enabled": true, + "sessions_conns": [ // connections to SessionS: <*internal|127.0.0.1:2012> + {"address": "127.0.0.1:2012", "transport": "*json"} + ], + "readers": [ + { + "id": "file_reader1", + "run_delay": -1, + "type": "*file_csv", + "flags": ["*cdrs","*log"], + "source_path": "/tmp/ers/in", + "processed_path": "/tmp/ers/out", + }, + { + "id": "file_reader2", + "run_delay": -1, + "field_separator": ";", + "type": "*file_csv", + "source_path": "/tmp/ers2/in", + "flags": ["*cdrs","*log"], + "processed_path": "/tmp/ers2/out", + "content_fields":[ // import content_fields template, tag will match internally CDR field, in case of .csv value will be represented by index of the field value + {"tag": "TOR", "field_id": "ToR", "type": "*composed", "value": "*voice", "mandatory": true}, + {"tag": "OriginID", "field_id": "OriginID", "type": "*composed", "value": "~*req.0", "mandatory": true}, + {"tag": "RequestType", "field_id": "RequestType", "type": "*composed", "value": "~*req.1", "mandatory": true}, + {"tag": "Direction", "field_id": "Direction", "type": "*composed", "value": "*out", "mandatory": true}, + {"tag": "Tenant", "field_id": "Tenant", "type": "*composed", "value": "~*req.2", "mandatory": true}, + {"tag": "Category", "field_id": "Category", "type": "*composed", "value": "call", "mandatory": true}, + {"tag": "Account", "field_id": "Account", "type": "*composed", "value": "~*req.3", "mandatory": true}, + {"tag": "Subject", "field_id": "Subject", "type": "*composed", "value": "~*req.3", "mandatory": true}, + {"tag": "Destination", "field_id": "Destination", "type": "*composed", "value": "~*req.4:s/0([1-9]\\d+)/+49${1}/", "mandatory": true}, + {"tag": "SetupTime", "field_id": "SetupTime", "type": "*composed", "value": "~*req.5", "mandatory": true}, + {"tag": "AnswerTime", "field_id": "AnswerTime", "type": "*composed", "value": "~*req.5", "mandatory": true}, + {"tag": "Usage", "field_id": "Usage", "type": "*composed", "value": "~*req.6", "mandatory": true}, + {"tag": "HDRExtra3", "field_id": "HDRExtra3", "type": "*composed", "value": "~*req.6", "mandatory": true}, + {"tag": "HDRExtra2", "field_id": "HDRExtra2", "type": "*composed", "value": "~*req.6", "mandatory": true}, + {"tag": "HDRExtra1", "field_id": "HDRExtra1", "type": "*composed", "value": "~*req.6", "mandatory": true}, + ], + }, + ], +}, + + +} diff --git a/data/conf/samples/ers_reload/second_reload/cgrates.json b/data/conf/samples/ers_reload/second_reload/cgrates.json new file mode 100644 index 000000000..ff581a0e9 --- /dev/null +++ b/data/conf/samples/ers_reload/second_reload/cgrates.json @@ -0,0 +1,216 @@ +{ +// CGRateS Configuration file +// +// Used for SessionSv1 integration tests + + +"general": { + "log_level": 7, +}, + + +"listen": { + "rpc_json": ":2012", + "rpc_gob": ":2013", + "http": ":2080", +}, + + +"stor_db": { + "db_password": "CGRateS.org", +}, + + +"rals": { + "enabled": true, +}, + + +"scheduler": { + "enabled": true, +}, + + +"chargers": { + "enabled": true, + "attributes_conns": [ + {"address": "*internal"} + ], +}, + + +"resources": { + "enabled": true, +}, + + +"attributes": { + "enabled": true, +}, + + +"cdrs": { + "enabled": true, + "chargers_conns": [ + {"address": "127.0.0.1:2012", "transport": "*json"} + ], + "rals_conns": [ + {"address": "*internal"} + ], +}, + + +"sessions": { + "enabled": true, + "chargers_conns": [ + {"address": "127.0.0.1:2012", "transport": "*json"} + ], + "rals_conns": [ + {"address": "*internal"} + ], + "cdrs_conns": [ + {"address": "*internal"} + ], + "resources_conns": [ + {"address": "*internal"} + ], + "attributes_conns": [ + {"address": "*internal"} + ], +}, + + +"ers": { + "enabled": true, + "sessions_conns": [ // connections to SessionS: <*internal|127.0.0.1:2012> + {"address": "127.0.0.1:2012", "transport": "*json"} + ], + "readers": [ + { + "id": "file_reader1", + "run_delay": -1, + "type": "*file_csv", + "flags": ["*dryrun"], + "source_path": "/tmp/ers/in", + "processed_path": "/tmp/ers/out", + }, + { + "id": "file_reader2", + "run_delay": -1, + "field_separator": ";", + "type": "*file_csv", + "source_path": "/tmp/ers2/in", + "flags": ["*dryrun"], + "processed_path": "/tmp/ers2/out", + "content_fields":[ // import content_fields template, tag will match internally CDR field, in case of .csv value will be represented by index of the field value + {"tag": "TOR", "field_id": "ToR", "type": "*composed", "value": "*voice", "mandatory": true}, + {"tag": "OriginID", "field_id": "OriginID", "type": "*composed", "value": "~*req.0", "mandatory": true}, + {"tag": "RequestType", "field_id": "RequestType", "type": "*composed", "value": "~*req.1", "mandatory": true}, + {"tag": "Direction", "field_id": "Direction", "type": "*composed", "value": "*out", "mandatory": true}, + {"tag": "Tenant", "field_id": "Tenant", "type": "*composed", "value": "~*req.2", "mandatory": true}, + {"tag": "Category", "field_id": "Category", "type": "*composed", "value": "call", "mandatory": true}, + {"tag": "Account", "field_id": "Account", "type": "*composed", "value": "~*req.3", "mandatory": true}, + {"tag": "Subject", "field_id": "Subject", "type": "*composed", "value": "~*req.3", "mandatory": true}, + {"tag": "Destination", "field_id": "Destination", "type": "*composed", "value": "~*req.4:s/0([1-9]\\d+)/+49${1}/", "mandatory": true}, + {"tag": "SetupTime", "field_id": "SetupTime", "type": "*composed", "value": "~*req.5", "mandatory": true}, + {"tag": "AnswerTime", "field_id": "AnswerTime", "type": "*composed", "value": "~*req.5", "mandatory": true}, + {"tag": "Usage", "field_id": "Usage", "type": "*composed", "value": "~*req.6", "mandatory": true}, + {"tag": "HDRExtra3", "field_id": "HDRExtra3", "type": "*composed", "value": "~*req.6", "mandatory": true}, + {"tag": "HDRExtra2", "field_id": "HDRExtra2", "type": "*composed", "value": "~*req.6", "mandatory": true}, + {"tag": "HDRExtra1", "field_id": "HDRExtra1", "type": "*composed", "value": "~*req.6", "mandatory": true}, + ], + }, + { + "id": "init_session", + "run_delay": -1, + "field_separator": ",", + "type": "*file_csv", + "source_path": "/tmp/init_session/in", + "flags": ["*initiate","*accounts","*resources","*attributes","*log"], + "processed_path": "/tmp/init_session/out", + "content_fields":[ // import content_fields template, tag will match internally CDR field, in case of .csv value will be represented by index of the field value + {"tag": "Tenant", "field_id": "Tenant", "type": "*variable", "value": "~*req.0", "mandatory": true}, + {"tag": "TOR", "field_id": "ToR", "type": "*variable", "value": "*voice", "mandatory": true}, + {"tag": "OriginID", "field_id": "OriginID", "type": "*variable", "value": "~*req.2", "mandatory": true}, + {"tag": "RequestType", "field_id": "RequestType", "type": "*variable", "value": "~*req.3", "mandatory": true}, + {"tag": "Category", "field_id": "Category", "type": "*variable", "value": "call", "mandatory": true}, + {"tag": "Account", "field_id": "Account", "type": "*variable", "value": "~*req.4", "mandatory": true}, + {"tag": "Subject", "field_id": "Subject", "type": "*variable", "value": "~*req.5", "mandatory": true}, + {"tag": "Destination", "field_id": "Destination", "type": "*variable", "value": "~*req.6", "mandatory": true}, + {"tag": "SetupTime", "field_id": "SetupTime", "type": "*variable", "value": "~*req.7", "mandatory": true}, + {"tag": "AnswerTime", "field_id": "AnswerTime", "type": "*variable", "value": "~*req.8", "mandatory": true}, + {"tag": "Usage", "field_id": "Usage", "type": "*variable", "value": "~*req.9", "mandatory": true}, + ], + }, + { + "id": "terminate_session", + "run_delay": -1, + "field_separator": ",", + "type": "*file_csv", + "source_path": "/tmp/terminate_session/in", + "flags": ["*terminate","*accounts","*resources","*log"], + "processed_path": "/tmp/terminate_session/out", + "content_fields":[ // import content_fields template, tag will match internally CDR field, in case of .csv value will be represented by index of the field value + {"tag": "Tenant", "field_id": "Tenant", "type": "*variable", "value": "~*req.0", "mandatory": true}, + {"tag": "OriginID", "field_id": "OriginID", "type": "*variable", "value": "~*req.2", "mandatory": true}, + {"tag": "RequestType", "field_id": "RequestType", "type": "*variable", "value": "~*req.3", "mandatory": true}, + {"tag": "Usage", "field_id": "Usage", "type": "*variable", "value": "~*req.9", "mandatory": true}, + ], + }, + { + "id": "create_cdr", + "run_delay": -1, + "field_separator": ",", + "type": "*file_csv", + "source_path": "/tmp/cdrs/in", + "flags": ["*cdrs","*log"], + "processed_path": "/tmp/cdrs/out", + "content_fields":[ // import content_fields template, tag will match internally CDR field, in case of .csv value will be represented by index of the field value + {"tag": "Tenant", "field_id": "Tenant", "type": "*variable", "value": "~*req.0", "mandatory": true}, + {"tag": "TOR", "field_id": "ToR", "type": "*variable", "value": "*voice", "mandatory": true}, + {"tag": "OriginID", "field_id": "OriginID", "type": "*variable", "value": "~*req.2", "mandatory": true}, + {"tag": "RequestType", "field_id": "RequestType", "type": "*variable", "value": "~*req.3", "mandatory": true}, + {"tag": "Category", "field_id": "Category", "type": "*variable", "value": "call", "mandatory": true}, + {"tag": "Account", "field_id": "Account", "type": "*variable", "value": "~*req.4", "mandatory": true}, + {"tag": "Subject", "field_id": "Subject", "type": "*variable", "value": "~*req.5", "mandatory": true}, + {"tag": "Destination", "field_id": "Destination", "type": "*variable", "value": "~*req.6", "mandatory": true}, + {"tag": "SetupTime", "field_id": "SetupTime", "type": "*variable", "value": "~*req.7", "mandatory": true}, + {"tag": "AnswerTime", "field_id": "AnswerTime", "type": "*variable", "value": "~*req.8", "mandatory": true}, + {"tag": "Usage", "field_id": "Usage", "type": "*variable", "value": "~*req.9", "mandatory": true}, + {"tag": "ExtraInfo1", "field_id": "ExtraInfo1", "type": "*constant", "value": "ExtraInfo1", "mandatory": true}, + {"tag": "ExtraInfo2", "field_id": "ExtraInfo2", "type": "*constant", "value": "ExtraInfo2", "mandatory": true}, + ], + }, + { + "id": "file_reader_with_filters", + "run_delay": -1, + "type": "*file_csv", + "flags": ["*dryrun"], + "field_separator": ";", + "source_path": "/tmp/ers_with_filters/in", + "processed_path": "/tmp/ers_with_filters/out", + "flags": ["*cdrs","*log"], + "filters":["*string:~*req.3:1002"], + "content_fields":[ // import content_fields template, tag will match internally CDR field, in case of .csv value will be represented by index of the field value + {"tag": "TOR", "field_id": "ToR", "type": "*composed", "value": "*voice", "mandatory": true}, + {"tag": "OriginID", "field_id": "OriginID", "type": "*composed", "value": "~*req.0", "mandatory": true}, + {"tag": "RequestType", "field_id": "RequestType", "type": "*composed", "value": "~*req.1", "mandatory": true}, + {"tag": "Tenant", "field_id": "Tenant", "type": "*composed", "value": "~*req.2", "mandatory": true}, + {"tag": "Category", "field_id": "Category", "type": "*composed", "value": "call", "mandatory": true}, + {"tag": "Account", "field_id": "Account", "type": "*composed", "value": "~*req.3", "mandatory": true}, + {"tag": "Source", "field_id": "Source", "type": "*composed", "value": "ers_csv", "mandatory": true}, + {"tag": "Subject", "field_id": "Subject", "type": "*composed", "value": "~*req.3", "mandatory": true}, + {"tag": "Destination", "field_id": "Destination", "type": "*composed", "value": "~*req.4:s/0([1-9]\\d+)/+49${1}/", "mandatory": true}, + {"tag": "SetupTime", "field_id": "SetupTime", "type": "*composed", "value": "~*req.5", "mandatory": true}, + {"tag": "AnswerTime", "field_id": "AnswerTime", "type": "*composed", "value": "~*req.5", "mandatory": true}, + {"tag": "Usage", "field_id": "Usage", "type": "*composed", "value": "~*req.6", "mandatory": true}, + {"tag": "HDRExtra3", "field_id": "HDRExtra3", "type": "*composed", "value": "~*req.6", "mandatory": true}, + {"tag": "HDRExtra2", "field_id": "HDRExtra2", "type": "*composed", "value": "~*req.6", "mandatory": true}, + {"tag": "HDRExtra1", "field_id": "HDRExtra1", "type": "*composed", "value": "~*req.6", "mandatory": true}, + ], + }, + ], +}, + + +} diff --git a/ers/ers_reload_it_test.go b/ers/ers_reload_it_test.go new file mode 100644 index 000000000..8bfce67c6 --- /dev/null +++ b/ers/ers_reload_it_test.go @@ -0,0 +1,146 @@ +// +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 ers + +import ( + "net/rpc" + "net/rpc/jsonrpc" + "os" + "path" + "testing" + + "github.com/cgrates/cgrates/config" + "github.com/cgrates/cgrates/engine" + "github.com/cgrates/cgrates/utils" +) + +var ( + reloadCfgPath string + reloadCfg *config.CGRConfig + reloadRPC *rpc.Client + reloadTests = []func(t *testing.T){ + testReloadITCreateCdrDirs, + testReloadITInitConfig, + testReloadITInitCdrDb, + testReloadITResetDataDb, + testReloadITStartEngine, + testReloadITRpcConn, + testReloadVerifyDisabledReaders, + testReloadReloadConfig, + testReloadVerifyFirstReload, + testReloadITKillEngine, + } +) + +func TestERsReload(t *testing.T) { + reloadCfgPath = path.Join(*dataDir, "conf", "samples", "ers_reload", "disabled") + for _, test := range reloadTests { + t.Run("TestERsReload", test) + } +} + +func testReloadITInitConfig(t *testing.T) { + var err error + if reloadCfg, err = config.NewCGRConfigFromPath(reloadCfgPath); err != nil { + t.Fatal("Got config error: ", err.Error()) + } +} + +// InitDb so we can rely on count +func testReloadITInitCdrDb(t *testing.T) { + if err := engine.InitStorDb(reloadCfg); err != nil { + t.Fatal(err) + } +} + +// Remove data in both rating and accounting db +func testReloadITResetDataDb(t *testing.T) { + if err := engine.InitDataDb(reloadCfg); err != nil { + t.Fatal(err) + } +} + +func testReloadITCreateCdrDirs(t *testing.T) { + for _, dir := range []string{"/tmp/ers/in", "/tmp/ers/out", + "/tmp/ers2/in", "/tmp/ers2/out"} { + if err := os.RemoveAll(dir); err != nil { + t.Fatal("Error removing folder: ", dir, err) + } + if err := os.MkdirAll(dir, 0755); err != nil { + t.Fatal("Error creating folder: ", dir, err) + } + } +} + +func testReloadITStartEngine(t *testing.T) { + if _, err := engine.StopStartEngine(reloadCfgPath, *waitRater); err != nil { + t.Fatal(err) + } +} + +// Connect rpc client to rater +func testReloadITRpcConn(t *testing.T) { + var err error + reloadRPC, err = jsonrpc.Dial("tcp", reloadCfg.ListenCfg().RPCJSONListen) // We connect over JSON so we can also troubleshoot if needed + if err != nil { + t.Fatal("Could not connect to rater: ", err.Error()) + } +} + +func testReloadVerifyDisabledReaders(t *testing.T) { + if len(reloadCfg.ERsCfg().Readers) != 1 && + reloadCfg.ERsCfg().Readers[0].ID != utils.MetaDefault && + reloadCfg.ERsCfg().Enabled != false { + t.Errorf("Unexpected active readers: <%+v>", utils.ToJSON(reloadCfg.ERsCfg().Readers)) + } +} + +func testReloadReloadConfig(t *testing.T) { + var reply string + if err := reloadRPC.Call(utils.ConfigSv1ReloadConfig, &config.ConfigReloadWithArgDispatcher{ + Path: path.Join(*dataDir, "conf", "samples", "ers_reload", "first_reload"), + Section: config.ERsJson, + }, &reply); err != nil { + t.Error(err) + } else if reply != utils.OK { + t.Errorf("Unexpected reply received: <%+v>", reply) + } +} + +func testReloadVerifyFirstReload(t *testing.T) { + var reply map[string]interface{} + if err := reloadRPC.Call(utils.ConfigSv1GetJSONSection, &config.StringWithArgDispatcher{ + Section: config.ERsJson, + }, &reply); err != nil { + t.Error(err) + } else if reply["Enabled"] != true { + t.Errorf("Expecting: , received: <%+v>", reply["Enabled"]) + } else if readers, canConvert := reply["Readers"].([]interface{}); !canConvert { + t.Errorf("Cannot cast Readers to slice") + } else if len(readers) != 3 { // 2 active readers and 1 default + t.Errorf("Expecting: <2>, received: <%+v>", len(readers)) + } +} + +func testReloadITKillEngine(t *testing.T) { + if err := engine.KillEngine(*waitRater); err != nil { + t.Error(err) + } +}