diff --git a/apier/v1/dispatcher.go b/apier/v1/dispatcher.go index f131f5d25..f3212336a 100644 --- a/apier/v1/dispatcher.go +++ b/apier/v1/dispatcher.go @@ -75,7 +75,7 @@ type DispatcherWithAPIOpts struct { // SetDispatcherProfile add/update a new Dispatcher Profile func (apierSv1 *APIerSv1) SetDispatcherProfile(args *DispatcherWithAPIOpts, reply *string) error { - if missing := utils.MissingStructFields(args.DispatcherProfile, []string{utils.ID, utils.Subsystems}); len(missing) != 0 { + if missing := utils.MissingStructFields(args.DispatcherProfile, []string{utils.ID}); len(missing) != 0 { return utils.NewErrMandatoryIeMissing(missing...) } if args.Tenant == utils.EmptyString { diff --git a/engine/datamanager.go b/engine/datamanager.go index 95ddc91e7..b120610a5 100644 --- a/engine/datamanager.go +++ b/engine/datamanager.go @@ -2569,6 +2569,9 @@ func (dm *DataManager) SetAttributeProfile(ap *AttributeProfile, withIndex bool) if err != nil && err != utils.ErrNotFound { return err } + if len(ap.Contexts) == 0 { + ap.Contexts = append(ap.Contexts, utils.MetaAny) + } if err = dm.DataDB().SetAttributeProfileDrv(ap); err != nil { return err } @@ -2826,6 +2829,9 @@ func (dm *DataManager) SetDispatcherProfile(dpp *DispatcherProfile, withIndex bo if err != nil && err != utils.ErrDSPProfileNotFound { return err } + if len(dpp.Subsystems) == 0 { + dpp.Subsystems = append(dpp.Subsystems, utils.MetaAny) + } if err = dm.DataDB().SetDispatcherProfileDrv(dpp); err != nil { return err } diff --git a/general_tests/loader_nocontext_it_test.go b/general_tests/loader_nocontext_it_test.go new file mode 100644 index 000000000..eb21e5985 --- /dev/null +++ b/general_tests/loader_nocontext_it_test.go @@ -0,0 +1,335 @@ +//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 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 general_tests + +import ( + "net/rpc" + "os" + "path" + "reflect" + "sort" + "testing" + "time" + + v1 "github.com/cgrates/cgrates/apier/v1" + "github.com/cgrates/cgrates/config" + "github.com/cgrates/cgrates/engine" + "github.com/cgrates/cgrates/utils" +) + +var ( + ldrCtxCfgPath string + ldrCtxCfg *config.CGRConfig + ldrCtxRPC *rpc.Client + ldrCtxConfDIR string //run tests for specific configuration + ldrCtxDelay int + + sTestsLdrCtx = []func(t *testing.T){ + testLoaderNoContextRemoveFolders, + testLoaderNoContextCreateFolders, + + testLoaderNoContextLoadConfig, + testLoaderNoContextInitDataDb, + testLoaderNoContextResetStorDb, + testLoaderNoContextStartEngine, + testLoaderNoContextRpcConn, + + testLoaderNoContextWriteCSVs, + testLoaderNoContextLoadTariffPlans, + testLoaderNoContextGetFilterIndexesAfterLoad, + testLoaderNoContextSetProfiles, + testLoaderNoContextGetFilterIndexesAfterSet, + + testLoaderNoContextStopEngine, + testLoaderNoContextRemoveFolders, + } +) + +func TestLoaderNoContextIT(t *testing.T) { + switch *dbType { + case utils.MetaInternal: + ldrCtxConfDIR = "tutinternal" + case utils.MetaMySQL: + ldrCtxConfDIR = "tutmysql" + case utils.MetaMongo: + ldrCtxConfDIR = "tutmongo" + case utils.MetaPostgres: + t.SkipNow() + default: + t.Fatal("Unknown Database type") + } + + for _, stest := range sTestsLdrCtx { + t.Run(ldrCtxConfDIR, stest) + } +} + +func testLoaderNoContextLoadConfig(t *testing.T) { + var err error + ldrCtxCfgPath = path.Join(*dataDir, "conf", "samples", ldrCtxConfDIR) + if ldrCtxCfg, err = config.NewCGRConfigFromPath(ldrCtxCfgPath); err != nil { + t.Error(err) + } + ldrCtxDelay = 1000 +} + +func testLoaderNoContextInitDataDb(t *testing.T) { + if err := engine.InitDataDb(ldrCtxCfg); err != nil { + t.Fatal(err) + } +} + +func testLoaderNoContextResetStorDb(t *testing.T) { + if err := engine.InitStorDb(ldrCtxCfg); err != nil { + t.Fatal(err) + } +} + +func testLoaderNoContextStartEngine(t *testing.T) { + if _, err := engine.StopStartEngine(ldrCtxCfgPath, ldrCtxDelay); err != nil { + t.Fatal(err) + } +} + +func testLoaderNoContextRpcConn(t *testing.T) { + var err error + ldrCtxRPC, err = newRPCClient(ldrCtxCfg.ListenCfg()) // We connect over JSON so we can also troubleshoot if needed + if err != nil { + t.Fatal("Could not connect to rater: ", err.Error()) + } +} + +func testLoaderNoContextWriteCSVs(t *testing.T) { + writeFile := func(fileName, data string) error { + csvFile, err := os.Create(path.Join("/tmp/TestLoaderNoContextIT", fileName)) + if err != nil { + return err + } + defer csvFile.Close() + _, err = csvFile.WriteString(data) + if err != nil { + return err + + } + return csvFile.Sync() + } + + // Create and populate Attributes.csv + if err := writeFile(utils.AttributesCsv, ` +#Tenant,ID,Contexts,FilterIDs,ActivationInterval,AttributeFilterIDs,Path,Type,Value,Blocker,Weight +cgrates.org,ATTR_1,,*string:~*req.Field1:Value1,,,*req.Field1,*constant,Value2,false,10 +cgrates.org,ATTR_2,,,,,*req.Field2,*constant,Value2,false,10 +`); err != nil { + t.Fatal(err) + } + + // Create and populate DispatcherProfiles.csv + if err := writeFile(utils.DispatcherProfilesCsv, ` +#Tenant,ID,Subsystems,FilterIDs,ActivationInterval,Strategy,StrategyParameters,ConnID,ConnFilterIDs,ConnWeight,ConnBlocker,ConnParameters,Weight +cgrates.org,DSP1,,,,*weight,,ALL,,20,false,,10 +cgrates.org,DSP1,,,,,,ALL2,,10,,, +cgrates.org,DSP2,,*string:~*req.Field1:Value1,,*weight,,connID,,20,false,,20 +cgrates.org,DSP2,,,,,,ALL2,,10,,, +`); err != nil { + t.Fatal(err) + } +} + +func testLoaderNoContextLoadTariffPlans(t *testing.T) { + var reply string + attrs := &utils.AttrLoadTpFromFolder{FolderPath: "/tmp/TestLoaderNoContextIT"} + if err := ldrCtxRPC.Call(utils.APIerSv1LoadTariffPlanFromFolder, attrs, &reply); err != nil { + t.Error(err) + } else if reply != utils.OK { + t.Error("Unexpected reply returned", reply) + } + time.Sleep(500 * time.Millisecond) +} + +func testLoaderNoContextGetFilterIndexesAfterLoad(t *testing.T) { + // check attribute profile filter indexes + expIdx := []string{ + "*none:*any:*any:ATTR_2", + "*string:*req.Field1:Value1:ATTR_1", + } + var result []string + if err := ldrCtxRPC.Call(utils.APIerSv1GetFilterIndexes, &v1.AttrGetFilterIndexes{ + ItemType: utils.MetaAttributes, + Tenant: "cgrates.org", + Context: utils.MetaAny, + }, &result); err != nil { + t.Error(err) + } else { + sort.Strings(result) + if !reflect.DeepEqual(expIdx, result) { + t.Errorf("expected: %+v,\nreceived: %+v", expIdx, result) + } + } + + // check dispatcher profile filter indexes + expIdx = []string{ + "*none:*any:*any:DSP1", + "*string:*req.Field1:Value1:DSP2", + } + if err := ldrCtxRPC.Call(utils.APIerSv1GetFilterIndexes, &v1.AttrGetFilterIndexes{ + ItemType: utils.MetaDispatchers, + Tenant: "cgrates.org", + Context: utils.MetaAny, + }, &result); err != nil { + t.Error(err) + } else { + sort.Strings(result) + if !reflect.DeepEqual(expIdx, result) { + t.Errorf("expected: %+v,\nreceived: %+v", expIdx, result) + } + } +} + +func testLoaderNoContextSetProfiles(t *testing.T) { + // set attribute profile + attrPrf := &engine.AttributeProfileWithAPIOpts{ + AttributeProfile: &engine.AttributeProfile{ + Tenant: "cgrates.org", + ID: "ATTR_3", + // Contexts: []string{utils.MetaAny}, + FilterIDs: []string{"*string:~*req.Field3:Value3"}, + Attributes: []*engine.Attribute{ + { + Path: utils.MetaReq + utils.NestingSep + "Field3", + Type: utils.MetaConstant, + Value: config.NewRSRParsersMustCompile("Value4", utils.InfieldSep), + }, + }, + Weight: 20, + }, + } + attrPrf.Compile() + var reply string + if err := ldrCtxRPC.Call(utils.APIerSv1SetAttributeProfile, attrPrf, &reply); err != nil { + t.Error(err) + } else if reply != utils.OK { + t.Error("Unexpected reply returned", reply) + } + var attrReply *engine.AttributeProfile + if err := ldrCtxRPC.Call(utils.APIerSv1GetAttributeProfile, + utils.TenantIDWithAPIOpts{TenantID: &utils.TenantID{Tenant: "cgrates.org", ID: "ATTR_3"}}, &attrReply); err != nil { + t.Error(err) + } else { + attrReply.Compile() + attrPrf.AttributeProfile.Contexts = []string{utils.MetaAny} + if !reflect.DeepEqual(attrPrf.AttributeProfile, attrReply) { + t.Errorf("expected : %+v,\nreceived: %+v", + utils.ToJSON(attrPrf.AttributeProfile), utils.ToJSON(attrReply)) + } + } + + // set dispatcher profile + dspPrf := &v1.DispatcherWithAPIOpts{ + DispatcherProfile: &engine.DispatcherProfile{ + Tenant: "cgrates.org", + // Subsystems: []string{utils.MetaAny}, + ID: "DSP3", + FilterIDs: []string{"*string:~*req.RandomField:RandomValue"}, + Strategy: utils.MetaFirst, + Weight: 20, + }, + } + + if err := ldrCtxRPC.Call(utils.APIerSv1SetDispatcherProfile, + dspPrf, + &reply); err != nil { + t.Error(err) + } else if reply != utils.OK { + t.Error("Unexpected reply returned", reply) + } + + var dspReply *engine.DispatcherProfile + if err := ldrCtxRPC.Call(utils.APIerSv1GetDispatcherProfile, + &utils.TenantID{Tenant: "cgrates.org", ID: "DSP3"}, + &dspReply); err != nil { + t.Error(err) + } else { + dspPrf.DispatcherProfile.Subsystems = []string{utils.MetaAny} + if !reflect.DeepEqual(dspPrf.DispatcherProfile, dspReply) { + t.Errorf("expected: %+v,\nreceived: %+v", utils.ToJSON(dspPrf.DispatcherProfile), utils.ToJSON(dspReply)) + } + } +} + +func testLoaderNoContextGetFilterIndexesAfterSet(t *testing.T) { + // check attribute profile filter indexes + expIdx := []string{ + "*none:*any:*any:ATTR_2", + "*string:*req.Field1:Value1:ATTR_1", + "*string:*req.Field3:Value3:ATTR_3", + } + var result []string + if err := ldrCtxRPC.Call(utils.APIerSv1GetFilterIndexes, &v1.AttrGetFilterIndexes{ + ItemType: utils.MetaAttributes, + Tenant: "cgrates.org", + Context: utils.MetaAny, + }, &result); err != nil { + t.Error(err) + } else { + sort.Strings(result) + if !reflect.DeepEqual(expIdx, result) { + t.Errorf("expected: %+v,\nreceived: %+v", expIdx, result) + } + } + + // check dispatcher profile filter indexes + expIdx = []string{ + "*none:*any:*any:DSP1", + "*string:*req.Field1:Value1:DSP2", + "*string:*req.RandomField:RandomValue:DSP3", + } + if err := ldrCtxRPC.Call(utils.APIerSv1GetFilterIndexes, &v1.AttrGetFilterIndexes{ + ItemType: utils.MetaDispatchers, + Tenant: "cgrates.org", + Context: utils.MetaAny, + }, &result); err != nil { + t.Error(err) + } else { + sort.Strings(result) + if !reflect.DeepEqual(expIdx, result) { + t.Errorf("expected: %+v,\nreceived: %+v", expIdx, result) + } + } +} + +func testLoaderNoContextStopEngine(t *testing.T) { + if err := engine.KillEngine(ldrCtxDelay); err != nil { + t.Error(err) + } +} + +func testLoaderNoContextCreateFolders(t *testing.T) { + if err := os.MkdirAll("/tmp/TestLoaderNoContextIT", 0755); err != nil { + t.Error(err) + } +} + +func testLoaderNoContextRemoveFolders(t *testing.T) { + if err := os.RemoveAll("/tmp/TestLoaderNoContextIT"); err != nil { + t.Error(err) + } +}