diff --git a/config/config.go b/config/config.go index 6f21b4f7f..39963358a 100644 --- a/config/config.go +++ b/config/config.go @@ -60,6 +60,7 @@ func SetCgrConfig(cfg *CGRConfig) { func NewDefaultCGRConfig() (*CGRConfig, error) { cfg := new(CGRConfig) + cfg.InstanceID = utils.GenUUID() cfg.DataFolderPath = "/usr/share/cgrates/" cfg.SmGenericConfig = new(SmGenericConfig) cfg.SmFsConfig = new(SmFsConfig) @@ -167,6 +168,7 @@ func NewCGRConfigFromFolder(cfgDir string) (*CGRConfig, error) { // Holds system configuration, defaults are overwritten with values from config file if found type CGRConfig struct { + InstanceID string // Identifier for this engine instance TpDbType string TpDbHost string // The host to connect to. Values that start with / are for UNIX domain sockets. TpDbPort string // The port to bind to. diff --git a/data/conf/samples/multiral1/cgrates.json b/data/conf/samples/multiral1/cgrates.json new file mode 100644 index 000000000..310b3861d --- /dev/null +++ b/data/conf/samples/multiral1/cgrates.json @@ -0,0 +1,17 @@ +{ +// CGRateS Configuration file +// +// Used for multiple RAL configuration tests +// Starts rater, scheduler + +"listen": { + "rpc_json": ":2012", // RPC JSON listening address + "rpc_gob": ":2013", // RPC GOB listening address + "http": ":2080", // HTTP listening address +}, + +"rater": { + "enabled": true, // enable Rater service: +}, + +} \ No newline at end of file diff --git a/data/conf/samples/multiral2/cgrates.json b/data/conf/samples/multiral2/cgrates.json new file mode 100644 index 000000000..8f9270ae6 --- /dev/null +++ b/data/conf/samples/multiral2/cgrates.json @@ -0,0 +1,17 @@ +{ +// CGRateS Configuration file +// +// Used for multiple RAL configuration tests +// Starts RAL + +"listen": { + "rpc_json": ":12012", // RPC JSON listening address + "rpc_gob": ":12013", // RPC GOB listening address + "http": ":12080", // HTTP listening address +}, + +"rater": { + "enabled": true, // enable Rater service: +}, + +} \ No newline at end of file diff --git a/engine/responder.go b/engine/responder.go index e43c65040..4dd81c66c 100644 --- a/engine/responder.go +++ b/engine/responder.go @@ -551,6 +551,7 @@ func (rs *Responder) Status(arg string, reply *map[string]interface{}) (err erro memstats := new(runtime.MemStats) runtime.ReadMemStats(memstats) response := make(map[string]interface{}) + response[utils.InstanceID] = config.CgrConfig().InstanceID if rs.Bal != nil { response["Raters"] = rs.Bal.GetClientAddresses() } diff --git a/general_tests/rpcclient_it_test.go b/general_tests/rpcclient_it_test.go new file mode 100644 index 000000000..987b30662 --- /dev/null +++ b/general_tests/rpcclient_it_test.go @@ -0,0 +1,154 @@ +/* +Real-time Charging System for Telecom & ISP environments +Copyright (C) ITsysCOM GmbH + +This program is free software: you can Storagetribute 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 WITH*out 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 ( + "os/exec" + "path" + "testing" + "time" + + "github.com/cgrates/cgrates/config" + "github.com/cgrates/cgrates/engine" + "github.com/cgrates/cgrates/utils" + "github.com/cgrates/rpcclient" +) + +var rpcITCfgPath1, rpcITCfgPath2 string +var rpcITCfg1, rpcITCfg2 *config.CGRConfig +var rpcRAL1, rpcRAL2 *rpcclient.RpcClient +var rpcPoolFirst *rpcclient.RpcClientPool +var ral1, ral2 *exec.Cmd +var err error +var ral1ID, ral2ID string + +func TestRPCITInitCfg(t *testing.T) { + if !*testIntegration { + return + } + rpcITCfgPath1 = path.Join(*dataDir, "conf", "samples", "multiral1") + rpcITCfgPath2 = path.Join(*dataDir, "conf", "samples", "multiral2") + // Init config first + rpcITCfg1, err = config.NewCGRConfigFromFolder(rpcITCfgPath1) + if err != nil { + t.Error(err) + } + rpcITCfg2, err = config.NewCGRConfigFromFolder(rpcITCfgPath2) + if err != nil { + t.Error(err) + } +} + +func TestRPCITStartEngine(t *testing.T) { + if !*testIntegration { + return + } + if ral1, err = engine.StopStartEngine(rpcITCfgPath1, *waitRater); err != nil { + t.Fatal(err) + } + if ral2, err = engine.StartEngine(rpcITCfgPath2, *waitRater); err != nil { + t.Fatal(err) + } +} + +// Connect rpc client to rater +func TestRPCITRpcConnPool(t *testing.T) { + if !*testIntegration { + return + } + rpcPoolFirst = rpcclient.NewRpcClientPool(rpcclient.POOL_FIRST) + rpcRAL1, err = rpcclient.NewRpcClient("tcp", rpcITCfg1.RPCJSONListen, 3, 1, rpcclient.JSON_RPC, nil) + if err != nil { + t.Fatal(err) + } + rpcPoolFirst.AddClient(rpcRAL1) + rpcRAL2, err = rpcclient.NewRpcClient("tcp", rpcITCfg2.RPCJSONListen, 3, 1, rpcclient.JSON_RPC, nil) + if err != nil { + t.Fatal(err) + } + rpcPoolFirst.AddClient(rpcRAL2) +} + +// Connect rpc client to rater +func TestRPCITStatusFirstInitial(t *testing.T) { + if !*testIntegration { + return + } + var status map[string]interface{} + if err := rpcPoolFirst.Call("Responder.Status", "", &status); err != nil { + t.Error(err) + } else if status[utils.InstanceID].(string) == "" { + t.Error("Empty InstanceID received") + } else { + ral1ID = status[utils.InstanceID].(string) + } + if err := rpcPoolFirst.Call("Responder.Status", "", &status); err != nil { // Make sure second time we land on the same instance + t.Error(err) + } else if status[utils.InstanceID].(string) != ral1ID { + t.Errorf("Expecting: %s, received: %s", ral1ID, status[utils.InstanceID].(string)) + } +} + +// Connect rpc client to rater +func TestRPCITStatusFirstFailover(t *testing.T) { + if !*testIntegration { + return + } + if err := ral1.Process.Kill(); err != nil { // Kill the first RAL + t.Error(err) + } + time.Sleep(time.Duration(*waitRater) * time.Millisecond) + var status map[string]interface{} + if err := rpcPoolFirst.Call("Responder.Status", "", &status); err != nil { + t.Error(err) + } else if status[utils.InstanceID].(string) == "" { + t.Error("Empty InstanceID received") + } else { + ral1ID = status[utils.InstanceID].(string) + } + if err := rpcPoolFirst.Call("Responder.Status", "", &status); err != nil { // Make sure second time we land on the same instance + t.Error(err) + } else if status[utils.InstanceID].(string) != ral1ID { + t.Errorf("Expecting: %s, received: %s", ral1ID, status[utils.InstanceID].(string)) + } else { + ral2ID = status[utils.InstanceID].(string) + } +} + +func TestRPCITStatusFirstFailback(t *testing.T) { + if !*testIntegration { + return + } + if ral1, err = engine.StartEngine(rpcITCfgPath1, *waitRater); err != nil { + t.Fatal(err) + } + var status map[string]interface{} + if err := rpcPoolFirst.Call("Responder.Status", "", &status); err != nil { + t.Error(err) + } else if status[utils.InstanceID].(string) == ral2ID { + t.Error("Should receive new ID") + } else { + ral1ID = status[utils.InstanceID].(string) + } + if err := rpcPoolFirst.Call("Responder.Status", "", &status); err != nil { // Make sure second time we land on the same instance + t.Error(err) + } else if status[utils.InstanceID].(string) != ral1ID { + t.Errorf("Expecting: %s, received: %s", ral1ID, status[utils.InstanceID].(string)) + } +} diff --git a/general_tests/tutorial_local_test.go b/general_tests/tutorial_local_test.go index 244c5e794..17d986ea9 100644 --- a/general_tests/tutorial_local_test.go +++ b/general_tests/tutorial_local_test.go @@ -427,7 +427,7 @@ func TestTutLocalMaxDebit(t *testing.T) { TimeStart: tStart, TimeEnd: tStart.Add(time.Duration(120) * time.Second), } - cd.CgrId = "1" + cd.CgrID = "1" if err := tutLocalRpc.Call("Responder.MaxDebit", cd, &cc); err != nil { t.Error("Got error on Responder.GetCost: ", err.Error()) } else if cc.GetDuration() == 120 { @@ -444,7 +444,7 @@ func TestTutLocalMaxDebit(t *testing.T) { TimeStart: tStart, TimeEnd: tStart.Add(time.Duration(120) * time.Second), } - cd.CgrId = "2" + cd.CgrID = "2" if err := tutLocalRpc.Call("Responder.MaxDebit", cd, &cc); err != nil { t.Error("Got error on Responder.GetCost: ", err.Error()) } else if cc.GetDuration() != time.Duration(62)*time.Second { // We have as strategy *dsconnect diff --git a/utils/consts.go b/utils/consts.go index 0bacf323e..f3b9b5b5d 100644 --- a/utils/consts.go +++ b/utils/consts.go @@ -278,6 +278,7 @@ const ( UpdatedAt = "UpdatedAt" HandlerArgSep = "|" FlagForceDuration = "fd" + InstanceID = "InstanceID" ) var (