From 2cc081592a085208a1cb9533d60aa5019696e6b2 Mon Sep 17 00:00:00 2001 From: TeoV Date: Fri, 8 May 2020 17:34:15 +0300 Subject: [PATCH] Allow multiple suppliers with the same ID --- apier/v1/suppliers_it_test.go | 119 +++++++++++++++++++++++++++++++++- engine/loader_csv_test.go | 47 ++++++++++++-- engine/model_helpers.go | 9 ++- loaders/loader_test.go | 49 +++++++++++--- packages/debian/changelog | 1 + utils/set.go | 14 ++++ utils/set_test.go | 37 +++++++++++ 7 files changed, 256 insertions(+), 20 deletions(-) diff --git a/apier/v1/suppliers_it_test.go b/apier/v1/suppliers_it_test.go index 0d5212fa1..d6416271b 100644 --- a/apier/v1/suppliers_it_test.go +++ b/apier/v1/suppliers_it_test.go @@ -67,10 +67,11 @@ var ( testV1SplSRemSupplierProfiles, testV1SplSGetSupplierForEvent, // reset the database and load the TP again - // testV1SplSInitDataDb, - // testV1SplSFromFolder, + testV1SplSInitDataDb, + testV1SplSFromFolder, // for the moment we decide to comment the tests // testV1SplsOneSupplierWithoutDestination, + testV1SplMultipleSupplierSameID, testV1SplSupplierPing, testV1SplSStopEngine, } @@ -1119,6 +1120,120 @@ func testV1SplsOneSupplierWithoutDestination(t *testing.T) { } } +func testV1SplMultipleSupplierSameID(t *testing.T) { + var reply *engine.SupplierProfile + if err := splSv1Rpc.Call(utils.APIerSv1GetSupplierProfile, + &utils.TenantID{Tenant: "cgrates.org", ID: "MULTIPLE_ROUTES"}, &reply); err == nil || + err.Error() != utils.ErrNotFound.Error() { + t.Error(err) + } + splPrf = &SupplierWithCache{ + SupplierProfile: &engine.SupplierProfile{ + Tenant: "cgrates.org", + ID: "MULTIPLE_ROUTES", + FilterIDs: []string{"*string:~*req.Account:SpecialCase2"}, + Sorting: utils.MetaLC, + Suppliers: []*engine.Supplier{ + { + ID: "Route1", + RatingPlanIDs: []string{"RP_LOCAL"}, + FilterIDs: []string{"*string:~*req.Month:April"}, + Weight: 10, + }, + { + ID: "Route1", + RatingPlanIDs: []string{"RP_MOBILE"}, + FilterIDs: []string{"*string:~*req.Month:May"}, + Weight: 10, + }, + }, + Weight: 100, + }, + } + + var result string + if err := splSv1Rpc.Call(utils.APIerSv1SetSupplierProfile, splPrf, &result); err != nil { + t.Error(err) + } else if result != utils.OK { + t.Error("Unexpected reply returned", result) + } + tNow := time.Now() + ev := &engine.ArgsGetSuppliers{ + CGREvent: &utils.CGREvent{ + Tenant: "cgrates.org", + Time: &tNow, + ID: "testV1RouteMultipleRouteSameID", + Event: map[string]interface{}{ + utils.Account: "SpecialCase2", + utils.Destination: "+135876", + utils.SetupTime: utils.MetaNow, + utils.Usage: "2m", + "Month": "April", + }, + }, + } + eSpls := engine.SortedSuppliers{ + ProfileID: "MULTIPLE_ROUTES", + Sorting: utils.MetaLC, + Count: 1, + SortedSuppliers: []*engine.SortedSupplier{ + { + SupplierID: "Route1", + SortingData: map[string]interface{}{ + utils.Cost: 0.0396, + "RatingPlanID": "RP_LOCAL", + utils.Weight: 10.0, + }, + }, + }, + } + var suplsReply engine.SortedSuppliers + if err := splSv1Rpc.Call(utils.SupplierSv1GetSuppliers, + ev, &suplsReply); err != nil { + t.Error(err) + } else if !reflect.DeepEqual(eSpls, suplsReply) { + t.Errorf("Expecting: %s, received: %s", + utils.ToJSON(eSpls), utils.ToJSON(suplsReply)) + } + + ev = &engine.ArgsGetSuppliers{ + CGREvent: &utils.CGREvent{ + Tenant: "cgrates.org", + Time: &tNow, + ID: "testV1RouteMultipleRouteSameID", + Event: map[string]interface{}{ + utils.Account: "SpecialCase2", + utils.Destination: "+135876", + utils.SetupTime: utils.MetaNow, + utils.Usage: "2m", + "Month": "May", + }, + }, + } + eSpls = engine.SortedSuppliers{ + ProfileID: "MULTIPLE_ROUTES", + Sorting: utils.MetaLC, + Count: 1, + SortedSuppliers: []*engine.SortedSupplier{ + { + SupplierID: "Route1", + SortingData: map[string]interface{}{ + utils.Cost: 0.0204, + "RatingPlanID": "RP_MOBILE", + utils.Weight: 10.0, + }, + }, + }, + } + if err := splSv1Rpc.Call(utils.SupplierSv1GetSuppliers, + ev, &suplsReply); err != nil { + t.Error(err) + } else if !reflect.DeepEqual(eSpls, suplsReply) { + t.Errorf("Expecting: %s, received: %s", + utils.ToJSON(eSpls), utils.ToJSON(suplsReply)) + } +} + func testV1SplSStopEngine(t *testing.T) { if err := engine.KillEngine(100); err != nil { t.Error(err) diff --git a/engine/loader_csv_test.go b/engine/loader_csv_test.go index e6d6b6416..c7468a24b 100644 --- a/engine/loader_csv_test.go +++ b/engine/loader_csv_test.go @@ -1239,24 +1239,57 @@ func TestLoadSupplierProfiles(t *testing.T) { Suppliers: []*utils.TPSupplier{ &utils.TPSupplier{ ID: "supplier1", - FilterIDs: []string{"FLTR_ACNT_dan", "FLTR_DST_DE"}, - AccountIDs: []string{"Account1", "Account1_1", "Account2"}, - RatingPlanIDs: []string{"RPL_1", "RPL_2", "RPL_3"}, - ResourceIDs: []string{"ResGroup1", "ResGroup2", "ResGroup3", "ResGroup4"}, - StatIDs: []string{"Stat1", "Stat2", "Stat3"}, + FilterIDs: []string{"FLTR_DST_DE"}, + AccountIDs: []string{"Account2"}, + RatingPlanIDs: []string{"RPL_3"}, + ResourceIDs: []string{"ResGroup3"}, + StatIDs: []string{"Stat2"}, + Weight: 10, + Blocker: false, + SupplierParameters: utils.EmptyString, + }, + &utils.TPSupplier{ + ID: "supplier1", + FilterIDs: []string{"FLTR_ACNT_dan"}, + AccountIDs: []string{"Account1", "Account1_1"}, + RatingPlanIDs: []string{"RPL_1"}, + ResourceIDs: []string{"ResGroup1"}, + StatIDs: []string{"Stat1"}, Weight: 10, Blocker: true, SupplierParameters: "param1", }, + &utils.TPSupplier{ + ID: "supplier1", + RatingPlanIDs: []string{"RPL_2"}, + ResourceIDs: []string{"ResGroup2", "ResGroup4"}, + StatIDs: []string{"Stat3"}, + Weight: 10, + Blocker: false, + SupplierParameters: utils.EmptyString, + }, }, Weight: 20, }, } resKey := utils.TenantID{Tenant: "cgrates.org", ID: "SPP_1"} + sort.Slice(eSppProfiles[resKey].Suppliers, func(i, j int) bool { + return strings.Compare(eSppProfiles[resKey].Suppliers[i].ID+ + strings.Join(eSppProfiles[resKey].Suppliers[i].FilterIDs, utils.CONCATENATED_KEY_SEP), + eSppProfiles[resKey].Suppliers[j].ID+strings.Join(eSppProfiles[resKey].Suppliers[j].FilterIDs, utils.CONCATENATED_KEY_SEP)) < 0 + }) + if len(csvr.sppProfiles) != len(eSppProfiles) { t.Errorf("Failed to load SupplierProfiles: %s", utils.ToIJSON(csvr.sppProfiles)) - } else if !reflect.DeepEqual(eSppProfiles[resKey], csvr.sppProfiles[resKey]) { - t.Errorf("Expecting: %+v, received: %+v", eSppProfiles[resKey], csvr.sppProfiles[resKey]) + } else { + sort.Slice(csvr.sppProfiles[resKey].Suppliers, func(i, j int) bool { + return strings.Compare(csvr.sppProfiles[resKey].Suppliers[i].ID+ + strings.Join(csvr.sppProfiles[resKey].Suppliers[i].FilterIDs, utils.CONCATENATED_KEY_SEP), + csvr.sppProfiles[resKey].Suppliers[j].ID+strings.Join(csvr.sppProfiles[resKey].Suppliers[j].FilterIDs, utils.CONCATENATED_KEY_SEP)) < 0 + }) + if !reflect.DeepEqual(eSppProfiles[resKey], csvr.sppProfiles[resKey]) { + t.Errorf("Expecting: %+v, received: %+v", utils.ToJSON(eSppProfiles[resKey]), utils.ToJSON(csvr.sppProfiles[resKey])) + } } } diff --git a/engine/model_helpers.go b/engine/model_helpers.go index 6bd3d3f50..959a60cee 100644 --- a/engine/model_helpers.go +++ b/engine/model_helpers.go @@ -1809,7 +1809,12 @@ func (tps TpSuppliers) AsTPSuppliers() (result []*utils.TPSupplierProfile) { if _, has := suppliersMap[tenID]; !has { suppliersMap[(&utils.TenantID{Tenant: tp.Tenant, ID: tp.ID}).TenantID()] = make(map[string]*utils.TPSupplier) } - sup, found := suppliersMap[tenID][tp.SupplierID] + supID := tp.SupplierID + if tp.SupplierFilterIDs != "" { + supID = utils.ConcatenatedKey(supID, + utils.NewStringSet(strings.Split(tp.SupplierFilterIDs, utils.INFIELD_SEP)).Sha1()) + } + sup, found := suppliersMap[tenID][supID] if !found { sup = &utils.TPSupplier{ ID: tp.SupplierID, @@ -1840,7 +1845,7 @@ func (tps TpSuppliers) AsTPSuppliers() (result []*utils.TPSupplierProfile) { accSplit := strings.Split(tp.SupplierAccountIDs, utils.INFIELD_SEP) sup.AccountIDs = append(sup.AccountIDs, accSplit...) } - suppliersMap[(&utils.TenantID{Tenant: tp.Tenant, ID: tp.ID}).TenantID()][tp.SupplierID] = sup + suppliersMap[(&utils.TenantID{Tenant: tp.Tenant, ID: tp.ID}).TenantID()][supID] = sup } if tp.SortingParameters != "" { if _, has := sortingParameterMap[tenID]; !has { diff --git a/loaders/loader_test.go b/loaders/loader_test.go index a55901883..a4e1bd1ed 100644 --- a/loaders/loader_test.go +++ b/loaders/loader_test.go @@ -770,25 +770,56 @@ func TestLoaderProcessSuppliers(t *testing.T) { Suppliers: []*engine.Supplier{ &engine.Supplier{ ID: "supplier1", - FilterIDs: []string{"FLTR_ACNT_dan", "FLTR_DST_DE"}, - AccountIDs: []string{"Account1", "Account1_1", "Account2"}, - RatingPlanIDs: []string{"RPL_1", "RPL_2", "RPL_3"}, - ResourceIDs: []string{"ResGroup1", "ResGroup2", "ResGroup3", "ResGroup4"}, - StatIDs: []string{"Stat1", "Stat2", "Stat3"}, + FilterIDs: []string{"FLTR_DST_DE"}, + AccountIDs: []string{"Account2"}, + RatingPlanIDs: []string{"RPL_3"}, + ResourceIDs: []string{"ResGroup3"}, + StatIDs: []string{"Stat2"}, + Weight: 10, + Blocker: false, + SupplierParameters: utils.EmptyString, + }, + &engine.Supplier{ + ID: "supplier1", + FilterIDs: []string{"FLTR_ACNT_dan"}, + AccountIDs: []string{"Account1", "Account1_1"}, + RatingPlanIDs: []string{"RPL_1"}, + ResourceIDs: []string{"ResGroup1"}, + StatIDs: []string{"Stat1"}, Weight: 10, Blocker: true, SupplierParameters: "param1", }, + &engine.Supplier{ + ID: "supplier1", + RatingPlanIDs: []string{"RPL_2"}, + ResourceIDs: []string{"ResGroup2", "ResGroup4"}, + StatIDs: []string{"Stat3"}, + Weight: 10, + Blocker: false, + SupplierParameters: utils.EmptyString, + }, }, Weight: 20, } - + sort.Slice(eSp3.Suppliers, func(i, j int) bool { + return strings.Compare(eSp3.Suppliers[i].ID+ + strings.Join(eSp3.Suppliers[i].FilterIDs, utils.CONCATENATED_KEY_SEP), + eSp3.Suppliers[j].ID+strings.Join(eSp3.Suppliers[j].FilterIDs, utils.CONCATENATED_KEY_SEP)) < 0 + }) if aps, err := ldr.dm.GetSupplierProfile("cgrates.org", "SPP_1", true, false, utils.NonTransactional); err != nil { t.Error(err) - } else if !reflect.DeepEqual(eSp3, aps) { - t.Errorf("expecting: %s, received: %s", - utils.ToJSON(eSp3), utils.ToJSON(aps)) + } else { + sort.Slice(aps.Suppliers, func(i, j int) bool { + return strings.Compare(aps.Suppliers[i].ID+ + strings.Join(aps.Suppliers[i].FilterIDs, utils.CONCATENATED_KEY_SEP), + aps.Suppliers[j].ID+strings.Join(aps.Suppliers[j].FilterIDs, utils.CONCATENATED_KEY_SEP)) < 0 + }) + if !reflect.DeepEqual(eSp3, aps) { + t.Errorf("expecting: %s, received: %s", + utils.ToJSON(eSp3), utils.ToJSON(aps)) + } } } diff --git a/packages/debian/changelog b/packages/debian/changelog index 6ed558bb1..f0d80c651 100644 --- a/packages/debian/changelog +++ b/packages/debian/changelog @@ -23,6 +23,7 @@ cgrates (0.10.1~dev) UNRELEASED; urgency=medium * [SessionS] Update subflags for *rals ( *authorize and *initiate ) * [AgentRequest] Improved NavigableMap * [AgentRequest] FieldAsInterface return Data instead of NMItem + * [SupplierS] Allow multiple suppliers with the same ID -- Alexandru Tripon Wed, 19 Feb 2020 15:22:59 +0200 diff --git a/utils/set.go b/utils/set.go index 770c1752e..46ba39ffa 100644 --- a/utils/set.go +++ b/utils/set.go @@ -18,6 +18,8 @@ along with this program. If not, see package utils +import "sort" + // NewStringSet returns a new StringSet func NewStringSet(dataSlice []string) (s StringSet) { s = make(StringSet) @@ -62,6 +64,18 @@ func (s StringSet) AsSlice() []string { return result } +// AsOrderedSlice returns the keys as ordered string slice +func (s StringSet) AsOrderedSlice() (ss []string) { + ss = s.AsSlice() + sort.Strings(ss) + return +} + +// Sha1 returns the Sha1 on top of ordered slice +func (s StringSet) Sha1() string { + return Sha1(s.AsOrderedSlice()...) +} + // Size returns the size of the set func (s StringSet) Size() int { return len(s) diff --git a/utils/set_test.go b/utils/set_test.go index eaebc30d9..26314ab7f 100644 --- a/utils/set_test.go +++ b/utils/set_test.go @@ -115,6 +115,43 @@ func TestAsSlice(t *testing.T) { } } +func TestAsOrderedSlice(t *testing.T) { + s := StringSet{} + eOut := make([]string, 0) + if rcv := s.AsOrderedSlice(); !reflect.DeepEqual(eOut, rcv) { + t.Errorf("Expecting: %+v, received: %+v", eOut, rcv) + } + s = StringSet{ + "test3": struct{}{}, + "test12": struct{}{}, + "test2": struct{}{}} + eOut = []string{"test12", "test2", "test3"} + rcv := s.AsOrderedSlice() + if !reflect.DeepEqual(eOut, rcv) { + t.Errorf("Expecting: %+v, received: %+v", eOut, rcv) + } +} + +func TestSetSha1(t *testing.T) { + s := StringSet{ + "test3": struct{}{}, + "test12": struct{}{}, + "test2": struct{}{}} + eOut := "8fbb49ecf2ee4116bc492505865d2125a78f2161" + if rcv := s.Sha1(); rcv != eOut { + t.Errorf("Expecting: %+v, received: %+v", eOut, rcv) + } + + s2 := StringSet{ + "test2": struct{}{}, + "test3": struct{}{}, + "test12": struct{}{}, + } + if rcv := s2.Sha1(); rcv != eOut { + t.Errorf("Expecting: %+v, received: %+v", eOut, rcv) + } +} + func TestSize(t *testing.T) { s := StringSet{} if rcv := s.Size(); rcv != 0 {