Files
cgrates/apier/v1/config_it_test.go
2025-12-05 13:15:52 +01:00

653 lines
21 KiB
Go

//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 <https://www.gnu.org/licenses/>
*/
package v1
import (
"fmt"
"os/exec"
"path"
"reflect"
"testing"
"time"
"github.com/cgrates/birpc"
"github.com/cgrates/birpc/context"
"github.com/cgrates/birpc/jsonrpc"
"github.com/cgrates/cgrates/config"
"github.com/cgrates/cgrates/engine"
"github.com/cgrates/cgrates/utils"
)
var (
configCfgPath string
configCfg *config.CGRConfig
configRPC *birpc.Client
configConfigDIR string //run tests for specific configuration
sTestsConfig = []func(t *testing.T){
testConfigSInitCfg,
testConfigSInitDataDb,
testConfigSResetStorDb,
testConfigSStartEngine,
testConfigSRPCConn,
testConfigSSetConfigSessionS,
testConfigSSetConfigEEsDryRun,
testConfigSSetConfigEEs,
testConfigSv1GetJSONSectionWithoutTenant,
testConfigSSetConfigFromJSONCoreSDryRun,
testConfigSSetConfigFromJSONCoreS,
testConfigSReloadConfigCoreSDryRun,
testConfigSReloadConfigCoreS,
testConfigSKillEngine,
testConfigSStartEngineCAPSAllocated,
testConfigSCAPSPeak,
testConfigSKillEngine,
testConfigStartEngineWithConfigs,
testConfigStartEngineFromHTTP,
testConfigSKillEngine,
}
)
// Test start here
func TestConfigSIT(t *testing.T) {
switch *utils.DBType {
case utils.MetaInternal:
configConfigDIR = "tutinternal"
case utils.MetaMySQL:
configConfigDIR = "tutmysql"
case utils.MetaMongo:
configConfigDIR = "tutmongo"
case utils.MetaPostgres:
t.SkipNow()
default:
t.Fatal("Unknown Database type")
}
for _, stest := range sTestsConfig {
t.Run(configConfigDIR, stest)
}
}
func testConfigSInitCfg(t *testing.T) {
var err error
configCfgPath = path.Join(*utils.DataDir, "conf", "samples", configConfigDIR)
configCfg, err = config.NewCGRConfigFromPath(configCfgPath)
if err != nil {
t.Error(err)
}
}
func testConfigSInitDataDb(t *testing.T) {
if err := engine.InitDataDB(configCfg); err != nil {
t.Fatal(err)
}
}
// Wipe out the cdr database
func testConfigSResetStorDb(t *testing.T) {
if err := engine.InitStorDb(configCfg); err != nil {
t.Fatal(err)
}
}
// Start CGR Engine
func testConfigSStartEngine(t *testing.T) {
if _, err := engine.StopStartEngine(configCfgPath, *utils.WaitRater); err != nil {
t.Fatal(err)
}
}
// Connect rpc client to rater
func testConfigSRPCConn(t *testing.T) {
var err error
configRPC, err = newRPCClient(configCfg.ListenCfg()) // We connect over JSON so we can also troubleshoot if needed
if err != nil {
t.Fatal(err)
}
}
func testConfigSSetConfigSessionS(t *testing.T) {
var reply string
if err := configRPC.Call(context.Background(), utils.ConfigSv1SetConfig, &config.SetConfigArgs{
Tenant: "cgrates.org",
Config: map[string]any{
"sessions": map[string]any{
"enabled": true,
"resources_conns": []string{"*localhost"},
"routes_conns": []string{"*localhost"},
"attributes_conns": []string{"*localhost"},
"rals_conns": []string{"*internal"},
"cdrs_conns": []string{"*internal"},
"configs_conns": []string{"*internal"},
},
},
}, &reply); err != nil {
t.Error(err)
} else if reply != utils.OK {
t.Errorf("Expected OK received: %s", reply)
}
exp := map[string]any{
"enabled": true,
"channel_sync_interval": "0",
"alterable_fields": []any{},
"client_protocol": 2.,
"debit_interval": "0",
"listen_bijson": "127.0.0.1:2014",
"listen_bigob": "",
"stale_chan_max_extra_usage": "0",
"session_ttl": "0",
"session_indexes": []any{utils.OriginID},
"attributes_conns": []any{utils.MetaLocalHost},
"cdrs_conns": []any{utils.MetaInternal},
"chargers_conns": []any{utils.MetaInternal},
"rals_conns": []any{utils.MetaInternal},
"replication_conns": []any{},
"resources_conns": []any{utils.MetaLocalHost},
utils.IPsConnsCfg: []any{},
"routes_conns": []any{utils.MetaLocalHost},
"scheduler_conns": []any{},
"thresholds_conns": []any{},
"stats_conns": []any{},
utils.BackupIntervalCfg: "0",
"min_dur_low_balance": "0",
"stir": map[string]any{
"allowed_attest": []any{utils.MetaAny},
"default_attest": "A",
"payload_maxduration": "-1",
"privatekey_path": "",
"publickey_path": "",
},
"store_session_costs": false,
"terminate_attempts": 5.,
utils.DefaultUsageCfg: map[string]any{
utils.MetaAny: "3h0m0s",
utils.MetaVoice: "3h0m0s",
utils.MetaData: "1048576",
utils.MetaSMS: "1",
},
}
if *utils.Encoding == utils.MetaGOB {
var empty []string
exp = map[string]any{
"enabled": true,
"listen_bijson": "127.0.0.1:2014",
"listen_bigob": "",
"chargers_conns": []string{utils.MetaInternal},
"rals_conns": []string{utils.MetaInternal},
"resources_conns": []string{utils.MetaLocalHost},
utils.IPsConnsCfg: empty,
"thresholds_conns": empty,
"stats_conns": empty,
"routes_conns": []string{utils.MetaLocalHost},
"attributes_conns": []string{utils.MetaLocalHost},
"cdrs_conns": []string{utils.MetaInternal},
"replication_conns": empty,
"scheduler_conns": empty,
"session_indexes": []string{"OriginID"},
"client_protocol": 2.,
"terminate_attempts": 5,
"channel_sync_interval": "0",
"debit_interval": "0",
"session_ttl": "0",
"store_session_costs": false,
"min_dur_low_balance": "0",
"alterable_fields": empty,
"stir": map[string]any{
"allowed_attest": []string{utils.MetaAny},
"default_attest": "A",
"payload_maxduration": "-1",
"privatekey_path": "",
"publickey_path": "",
},
utils.DefaultUsageCfg: map[string]string{
utils.MetaAny: "3h0m0s",
utils.MetaVoice: "3h0m0s",
utils.MetaData: "1048576",
utils.MetaSMS: "1",
},
}
}
exp = map[string]any{
config.SessionSJson: exp,
}
var rpl map[string]any
if err := configRPC.Call(context.Background(), utils.ConfigSv1GetConfig, &config.SectionWithAPIOpts{
Tenant: "cgrates.org",
Section: config.SessionSJson,
}, &rpl); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(exp, rpl) {
t.Errorf("Expected %+v , \nreceived: %+v ", utils.ToJSON(exp), utils.ToJSON(rpl))
}
}
func testConfigSv1GetJSONSectionWithoutTenant(t *testing.T) {
exp := map[string]any{
"enabled": true,
"listen_bijson": "127.0.0.1:2014",
"listen_bigob": "",
"chargers_conns": []any{utils.MetaInternal},
"rals_conns": []any{utils.MetaInternal},
"resources_conns": []any{utils.MetaLocalHost},
utils.IPsConnsCfg: []any{},
"thresholds_conns": []any{},
"stats_conns": []any{},
"routes_conns": []any{utils.MetaLocalHost},
"attributes_conns": []any{utils.MetaLocalHost},
"cdrs_conns": []any{utils.MetaInternal},
"replication_conns": []any{},
"scheduler_conns": []any{},
"session_indexes": []any{"OriginID"},
"client_protocol": 2.,
"terminate_attempts": 5.,
"channel_sync_interval": "0",
"debit_interval": "0",
"session_ttl": "0",
"stale_chan_max_extra_usage": "0",
"store_session_costs": false,
utils.BackupIntervalCfg: "0",
"min_dur_low_balance": "0",
"alterable_fields": []any{},
"stir": map[string]any{
"allowed_attest": []any{utils.MetaAny},
"default_attest": "A",
"payload_maxduration": "-1",
"privatekey_path": "",
"publickey_path": "",
},
utils.DefaultUsageCfg: map[string]any{
utils.MetaAny: "3h0m0s",
utils.MetaVoice: "3h0m0s",
utils.MetaData: "1048576",
utils.MetaSMS: "1",
},
}
if *utils.Encoding == utils.MetaGOB {
var empty []string
exp = map[string]any{
"enabled": true,
"listen_bijson": "127.0.0.1:2014",
"chargers_conns": []string{utils.MetaInternal},
"rals_conns": []string{utils.MetaInternal},
"resources_conns": []string{utils.MetaLocalHost},
utils.IPsConnsCfg: empty,
"thresholds_conns": empty,
"stats_conns": empty,
"routes_conns": []string{utils.MetaLocalHost},
"attributes_conns": []string{utils.MetaLocalHost},
"cdrs_conns": []string{utils.MetaInternal},
"replication_conns": empty,
"scheduler_conns": empty,
"session_indexes": []string{"OriginID"},
"client_protocol": 2.,
"terminate_attempts": 5,
"channel_sync_interval": "0",
"debit_interval": "0",
"session_ttl": "0",
"stale_chan_max_extra_usage": "0",
"store_session_costs": false,
"min_dur_low_balance": "0",
"alterable_fields": empty,
"stir": map[string]any{
"allowed_attest": []string{utils.MetaAny},
"default_attest": "A",
"payload_maxduration": "-1",
"privatekey_path": "",
"publickey_path": "",
},
utils.DefaultUsageCfg: map[string]string{
utils.MetaAny: "3h0m0s",
utils.MetaVoice: "3h0m0s",
utils.MetaData: "1048576",
utils.MetaSMS: "1",
},
}
}
exp = map[string]any{
config.SessionSJson: exp,
}
var rpl map[string]any
if err := configRPC.Call(context.Background(), utils.ConfigSv1GetConfig, &config.SectionWithAPIOpts{
Section: config.SessionSJson,
}, &rpl); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(exp, rpl) {
t.Errorf("Expected %+v , received: %+v ", utils.ToJSON(exp), utils.ToJSON(rpl))
}
}
func testConfigSSetConfigEEsDryRun(t *testing.T) {
if *utils.Encoding == utils.MetaGOB {
t.SkipNow()
}
var reply string
if err := configRPC.Call(context.Background(), utils.ConfigSv1SetConfig, &config.SetConfigArgs{
Config: map[string]any{
"ees": map[string]any{
"enabled": true,
},
},
DryRun: true,
}, &reply); err != nil {
t.Error(err)
} else if reply != utils.OK {
t.Errorf("Expected OK received: %s", reply)
}
var rpl map[string]any
if err := configRPC.Call(context.Background(), utils.ConfigSv1GetConfig, &config.SectionWithAPIOpts{
Section: config.EEsJson,
}, &rpl); err != nil {
t.Error(err)
} else if rpl[config.EEsJson].(map[string]any)["enabled"] != false {
t.Errorf("Expected EEs to not change , received: %+v ", utils.ToJSON(rpl))
}
}
func testConfigSSetConfigEEs(t *testing.T) {
if *utils.Encoding == utils.MetaGOB {
t.SkipNow()
}
var reply string
if err := configRPC.Call(context.Background(), utils.ConfigSv1SetConfig, &config.SetConfigArgs{
Config: map[string]any{
"ees": map[string]any{
"enabled": true,
"attributes_conns": []string{},
"cache": map[string]any{},
"exporters": []any{map[string]any{
"id": utils.MetaDefault,
"fields": []any{},
}},
},
},
}, &reply); err != nil {
t.Error(err)
} else if reply != utils.OK {
t.Errorf("Expected OK received: %s", reply)
}
eeCfg := map[string]any{
"attempts": 1.,
"attribute_context": "",
"attribute_ids": []any{},
"export_path": "/var/spool/cgrates/ees",
"fields": []any{},
"filters": []any{},
"flags": []any{},
"id": "*default",
"synchronous": false,
"timezone": "",
"type": "*none",
"opts": map[string]any{},
"concurrent_requests": 0.,
"metrics_reset_schedule": "",
"failed_posts_dir": "/var/spool/cgrates/failed_posts",
}
exp := map[string]any{
"enabled": true,
"attributes_conns": []any{},
utils.FailedPostsCfg: map[string]any{
utils.DirCfg: "/var/spool/cgrates/failed_posts",
utils.TTLCfg: "5s",
utils.StaticTTLCfg: true,
},
"cache": map[string]any{
"*file_csv": map[string]any{"limit": -1., "ttl": "5s", "precache": false, "replicate": false, "static_ttl": false, "remote": false},
"*amqp_json_map": map[string]any{"limit": -1., "precache": false, "replicate": false, "static_ttl": false, "remote": false},
"*amqpv1_json_map": map[string]any{"limit": -1., "precache": false, "replicate": false, "static_ttl": false, "remote": false},
"*els": map[string]any{"limit": -1., "precache": false, "replicate": false, "static_ttl": false, "remote": false},
"*kafka_json_map": map[string]any{"limit": -1., "precache": false, "replicate": false, "static_ttl": false, "remote": false},
"*nats_json_map": map[string]any{"limit": -1., "precache": false, "replicate": false, "static_ttl": false, "remote": false},
"*s3_json_map": map[string]any{"limit": -1., "precache": false, "replicate": false, "static_ttl": false, "remote": false},
"*sql": map[string]any{"limit": -1., "precache": false, "replicate": false, "static_ttl": false, "remote": false},
"*sqs_json_map": map[string]any{"limit": -1., "precache": false, "replicate": false, "static_ttl": false, "remote": false},
},
"exporters": []any{eeCfg},
}
exp = map[string]any{
config.EEsJson: exp,
}
var rpl map[string]any
if err := configRPC.Call(context.Background(), utils.ConfigSv1GetConfig, &config.SectionWithAPIOpts{
Section: config.EEsJson,
}, &rpl); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(exp, rpl) {
t.Errorf("Expected %+v , received: %+v ", utils.ToJSON(exp), utils.ToJSON(rpl))
}
}
func testConfigSKillEngine(t *testing.T) {
if err := engine.KillEngine(100); err != nil {
t.Error(err)
}
}
func testConfigStartEngineWithConfigs(t *testing.T) {
var err error
configCfgPath = path.Join(*utils.DataDir, "conf", "samples", "configs_active")
configCfg, err = config.NewCGRConfigFromPath(configCfgPath)
if err != nil {
t.Error(err)
}
if _, err := engine.StopStartEngine(configCfgPath, *utils.WaitRater); err != nil {
t.Fatal(err)
}
configRPC, err = newRPCClient(configCfg.ListenCfg()) // We connect over JSON so we can also troubleshoot if needed
if err != nil {
t.Fatal(err)
}
var rply map[string]any
if err := configRPC.Call(context.Background(), utils.CoreSv1Status, &utils.TenantWithAPIOpts{}, &rply); err != nil {
t.Error(err)
} else if rply[utils.NodeID] != "EngineWithConfigSActive" {
t.Errorf("Expected %+v , received: %+v ", "EngineWithConfigSActive", rply)
}
}
func testConfigStartEngineFromHTTP(t *testing.T) {
enginePath, err := exec.LookPath("cgr-engine")
if err != nil {
t.Error(err)
}
engine := exec.Command(enginePath, "-config_path", "http://127.0.0.1:3080/configs/tutmysql/cgrates.json")
if err := engine.Start(); err != nil {
t.Error(err)
}
fib := utils.FibDuration(time.Millisecond, 0)
var jsonClnt *birpc.Client
var connected bool
for i := 0; i < 200; i++ {
time.Sleep(fib())
if jsonClnt, err = jsonrpc.Dial(utils.TCP, "localhost:2012"); err != nil {
utils.Logger.Warning(fmt.Sprintf("Error <%s> when opening test connection to: <%s>",
err.Error(), "localhost:2012"))
} else {
connected = true
break
}
}
if !connected {
t.Errorf("engine did not open port <%s>", "localhost:2012")
}
time.Sleep(100 * time.Millisecond)
var rply map[string]any
if err := jsonClnt.Call(context.Background(), utils.CoreSv1Status, &utils.TenantWithAPIOpts{}, &rply); err != nil {
t.Error(err)
}
}
func testConfigSSetConfigFromJSONCoreSDryRun(t *testing.T) {
cfgStr := `{"cores":{"caps":0,"caps_stats_interval":"0","caps_strategy":"*queue","shutdown_timeout":"1s"}}`
var reply string
if err := configRPC.Call(context.Background(), utils.ConfigSv1SetConfigFromJSON, &config.SetConfigFromJSONArgs{
Tenant: "cgrates.org",
Config: cfgStr,
DryRun: true,
}, &reply); err != nil {
t.Error(err)
} else if reply != utils.OK {
t.Errorf("Expected OK received: %s", reply)
}
expCfg := "{\"cores\":{\"caps\":0,\"caps_stats_interval\":\"0\",\"caps_strategy\":\"*busy\",\"shutdown_timeout\":\"1s\"}}"
var rpl string
if err := configRPC.Call(context.Background(), utils.ConfigSv1GetConfigAsJSON, &config.SectionWithAPIOpts{
Tenant: "cgrates.org",
Section: config.CoreSCfgJson,
}, &rpl); err != nil {
t.Error(err)
} else if expCfg != rpl {
t.Errorf("Expected %q , received: %q", expCfg, rpl)
}
}
func testConfigSSetConfigFromJSONCoreS(t *testing.T) {
cfgStr := `{"cores":{"caps":0,"caps_stats_interval":"0","caps_strategy":"*queue","shutdown_timeout":"1s"}}`
var reply string
if err := configRPC.Call(context.Background(), utils.ConfigSv1SetConfigFromJSON, &config.SetConfigFromJSONArgs{
Tenant: "cgrates.org",
Config: cfgStr,
}, &reply); err != nil {
t.Error(err)
} else if reply != utils.OK {
t.Errorf("Expected OK received: %s", reply)
}
var rpl string
if err := configRPC.Call(context.Background(), utils.ConfigSv1GetConfigAsJSON, &config.SectionWithAPIOpts{
Tenant: "cgrates.org",
Section: config.CoreSCfgJson,
}, &rpl); err != nil {
t.Error(err)
} else if cfgStr != rpl {
t.Errorf("Expected %q , received: %q", cfgStr, rpl)
}
}
func testConfigSReloadConfigCoreSDryRun(t *testing.T) {
cfgStr := `{"cores":{"caps":0,"caps_stats_interval":"0","caps_strategy":"*queue","shutdown_timeout":"1s"}}`
var reply string
if err := configRPC.Call(context.Background(), utils.ConfigSv1ReloadConfig, &config.ReloadArgs{
Tenant: "cgrates.org",
Path: path.Join(*utils.DataDir, "conf", "samples", "caps_busy"),
Section: config.CoreSCfgJson,
DryRun: true,
}, &reply); err != nil {
t.Error(err)
} else if reply != utils.OK {
t.Errorf("Expected OK received: %s", reply)
}
var rpl string
if err := configRPC.Call(context.Background(), utils.ConfigSv1GetConfigAsJSON, &config.SectionWithAPIOpts{
Tenant: "cgrates.org",
Section: config.CoreSCfgJson,
}, &rpl); err != nil {
t.Error(err)
} else if cfgStr != rpl {
t.Errorf("Expected %q , received: %q", cfgStr, rpl)
}
}
func testConfigSReloadConfigCoreS(t *testing.T) {
cfgStr := `{"cores":{"caps":2,"caps_stats_interval":"0","caps_strategy":"*busy","shutdown_timeout":"1s"}}`
var reply string
if err := configRPC.Call(context.Background(), utils.ConfigSv1ReloadConfig, &config.ReloadArgs{
Tenant: "cgrates.org",
Path: path.Join(*utils.DataDir, "conf", "samples", "caps_busy"),
Section: config.CoreSCfgJson,
}, &reply); err != nil {
t.Error(err)
} else if reply != utils.OK {
t.Errorf("Expected OK received: %s", reply)
}
var rpl string
if err := configRPC.Call(context.Background(), utils.ConfigSv1GetConfigAsJSON, &config.SectionWithAPIOpts{
Tenant: "cgrates.org",
Section: config.CoreSCfgJson,
}, &rpl); err != nil {
t.Error(err)
} else if cfgStr != rpl {
t.Errorf("Expected %q , received: %q", cfgStr, rpl)
}
}
func testConfigSStartEngineCAPSAllocated(t *testing.T) {
var err error
configCfgPath = path.Join(*utils.DataDir, "conf", "samples", "caps_peak")
configCfg, err = config.NewCGRConfigFromPath(configCfgPath)
if err != nil {
t.Error(err)
}
if _, err := engine.StopStartEngine(configCfgPath, *utils.WaitRater); err != nil {
t.Fatal(err)
}
configRPC, err = newRPCClient(configCfg.ListenCfg()) // We connect over JSON so we can also troubleshoot if needed
if err != nil {
t.Fatal(err)
}
var rply map[string]any
if err := configRPC.Call(context.Background(), utils.CoreSv1Status, &utils.TenantWithAPIOpts{}, &rply); err != nil {
t.Error(err)
} else if rply[utils.NodeID] != "CAPSPeakEngine" {
t.Errorf("Expected %+v , received: %+v ", "CAPSPeakEngine", rply)
}
if _, has := rply[utils.MetricCapsAllocated]; !has {
t.Errorf("Expected reply to contain CAPSAllocated , received <%+v>", rply)
}
}
func testConfigSCAPSPeak(t *testing.T) {
cfgStr := `{"cores":{"caps":2,"caps_stats_interval":"100ms","caps_strategy":"*queue","shutdown_timeout":"1s"}}`
var reply string
if err := configRPC.Call(context.Background(), utils.ConfigSv1ReloadConfig, &config.ReloadArgs{
Tenant: "cgrates.org",
Path: path.Join(*utils.DataDir, "conf", "samples", "caps_peak"),
Section: config.CoreSCfgJson,
}, &reply); err != nil {
t.Error(err)
} else if reply != utils.OK {
t.Errorf("Expected OK received: %s", reply)
}
var rpl string
if err := configRPC.Call(context.Background(), utils.ConfigSv1GetConfigAsJSON, &config.SectionWithAPIOpts{
Tenant: "cgrates.org",
Section: config.CoreSCfgJson,
}, &rpl); err != nil {
t.Error(err)
} else if cfgStr != rpl {
t.Errorf("Expected %q , received: %q", cfgStr, rpl)
}
var rply map[string]any
if err := configRPC.Call(context.Background(), utils.CoreSv1Status, &utils.TenantWithAPIOpts{}, &rply); err != nil {
t.Error(err)
}
if _, has := rply[utils.MetricCapsPeak]; !has {
t.Errorf("Expected reply to contain CAPSPeak , received <%+v>", rply)
}
}