From 909358a1247afdb820c7754af479fd257ca45d51 Mon Sep 17 00:00:00 2001 From: TeoV Date: Tue, 31 Jul 2018 08:24:07 -0400 Subject: [PATCH] Update Suppliers stategy --- apier/v1/suppliers_it_test.go | 48 ++++++++ data/tariffplans/testit/Filters.csv | 7 +- data/tariffplans/testit/Suppliers.csv | 5 +- engine/libsuppliers.go | 40 ++----- engine/libsuppliers_test.go | 154 +++++++++++++------------- engine/spls_highestcost.go | 37 +------ engine/spls_leastcost.go | 33 +----- engine/spls_qos.go | 58 +--------- engine/spls_weight.go | 50 +++++++++ engine/suppliers.go | 129 +++++++++++++++++---- engine/suppliers_test.go | 11 +- utils/apitpdata.go | 1 + utils/consts.go | 1 + 13 files changed, 322 insertions(+), 252 deletions(-) create mode 100755 engine/spls_weight.go diff --git a/apier/v1/suppliers_it_test.go b/apier/v1/suppliers_it_test.go index 5274c6573..af6493f01 100644 --- a/apier/v1/suppliers_it_test.go +++ b/apier/v1/suppliers_it_test.go @@ -58,6 +58,7 @@ var sTestsSupplierSV1 = []func(t *testing.T){ testV1SplSGetQOSSuppliers, testV1SplSGetQOSSuppliers2, testV1SplSGetQOSSuppliers3, + testV1SplSGetQOSSuppliersFiltred, testV1SplSGetSupplierWithoutFilter, testV1SplSSetSupplierProfiles, testV1SplSUpdateSupplierProfiles, @@ -673,6 +674,53 @@ func testV1SplSGetQOSSuppliers3(t *testing.T) { } } +func testV1SplSGetQOSSuppliersFiltred(t *testing.T) { + ev := &engine.ArgsGetSuppliers{ + CGREvent: utils.CGREvent{ + Tenant: "cgrates.org", + ID: "testV1SplSGetQOSSuppliers", + Event: map[string]interface{}{ + "DistincMatch": "*qos_filtred", + }, + }, + } + eSpls := engine.SortedSuppliers{ + ProfileID: "SPL_QOS_FILTRED", + Sorting: utils.MetaQOS, + SortedSuppliers: []*engine.SortedSupplier{ + &engine.SortedSupplier{ + SupplierID: "supplier1", + SortingData: map[string]interface{}{ + "*acd:Stat_1": 11.0, + "*acd:Stat_1_1": 11.0, + "*asr:Stat_1": 100.0, + "*pdd:Stat_1_1": 12.0, + "*tcd:Stat_1": 22.0, + "*tcd:Stat_1_1": 11.0, + utils.Weight: 10.0, + }, + }, + &engine.SortedSupplier{ + SupplierID: "supplier3", + SortingData: map[string]interface{}{ + "*acd:Stat_3": 11.0, + "*asr:Stat_3": 100.0, + "*tcd:Stat_3": 11.0, + utils.Weight: 35.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)) + } +} + func testV1SplSGetSupplierWithoutFilter(t *testing.T) { ev := &engine.ArgsGetSuppliers{ CGREvent: utils.CGREvent{ diff --git a/data/tariffplans/testit/Filters.csv b/data/tariffplans/testit/Filters.csv index 407ecf78a..108e0db56 100644 --- a/data/tariffplans/testit/Filters.csv +++ b/data/tariffplans/testit/Filters.csv @@ -6,6 +6,7 @@ cgrates.org,FLTR_1,*prefix,Destination,10;20, cgrates.org,FLTR_1,*rsr,,Subject(~^1.*1$);Destination(1002), cgrates.org,FLTR_ACNT_1007,*string,Account,1007,2014-07-29T15:00:00Z cgrates.org,FLTR_ACNT_dan,*string,Account,dan,2014-07-29T15:00:00Z +cgrates.org,FLTR_SPP_ACNT_dan,*string,*req.Account,dan,2014-07-29T15:00:00Z cgrates.org,FLTR_SPP_2,*string,Account,1003;1002,2014-07-29T15:00:00Z cgrates.org,FLTR_SPP_2,*prefix,Destination,10;20, cgrates.org,FLTR_SPP_2,*rsr,,Subject(~^1.*1$);Destination(1002), @@ -16,4 +17,8 @@ cgrates.org,FLTR_STAT_2,*string,Account,1002,2014-07-29T15:00:00Z cgrates.org,FLTR_STAT_3,*string,Account,1003,2014-07-29T15:00:00Z cgrates.org,FLTR_SPP_4,*string,DistincMatch,*qos2,2014-07-29T15:00:00Z cgrates.org,FLTR_SPP_5,*string,DistincMatch,*qos3,2014-07-29T15:00:00Z -cgrates.org,FLTR_STAT_1_1,*string,Stat,Stat1_1,2014-07-29T15:00:00Z \ No newline at end of file +cgrates.org,FLTR_STAT_1_1,*string,Stat,Stat1_1,2014-07-29T15:00:00Z +cgrates.org,FLTR_SPP_6,*string,DistincMatch,*qos_filtred,2014-07-29T15:00:00Z +cgrates.org,FLTR_QOS_SP1,*gte,*gs.*acd,10.0,2014-07-29T15:00:00Z +cgrates.org,FLTR_QOS_SP2,*gte,*gs.*acd,10.0,2014-07-29T15:00:00Z +cgrates.org,FLTR_QOS_SP2,*gte,*gs.*tcd,11.0, \ No newline at end of file diff --git a/data/tariffplans/testit/Suppliers.csv b/data/tariffplans/testit/Suppliers.csv index 0fb932afe..f9c95a2e5 100644 --- a/data/tariffplans/testit/Suppliers.csv +++ b/data/tariffplans/testit/Suppliers.csv @@ -4,7 +4,7 @@ cgrates.org,SPL_ACNT_1001,,,,,supplier2,,,,,,10,,, cgrates.org,SPL_WEIGHT_2,,2017-11-27T00:00:00Z,*weight,,supplier1,,,,,,10,,,5 cgrates.org,SPL_WEIGHT_1,FLTR_DST_DE;FLTR_ACNT_1007,2017-11-27T00:00:00Z,*weight,,supplier1,,,,,,10,,,10 cgrates.org,SPL_WEIGHT_1,FLTR_DST_DE,,,,supplier2,,,,,,20,,, -cgrates.org,SPL_WEIGHT_1,FLTR_ACNT_1007,,,,supplier3,FLTR_ACNT_dan,,,,,15,,, +cgrates.org,SPL_WEIGHT_1,FLTR_ACNT_1007,,,,supplier3,FLTR_SPP_ACNT_dan,,,,,15,,, cgrates.org,SPL_LEASTCOST_1,FLTR_1,2017-11-27T00:00:00Z,*least_cost,,supplier1,,,RP_SPECIAL_1002,,,10,false,,10 cgrates.org,SPL_LEASTCOST_1,,,,,supplier2,,,RP_RETAIL1,,,20,,, cgrates.org,SPL_LEASTCOST_1,,,,,supplier3,,,RP_SPECIAL_1002,,,15,,, @@ -20,3 +20,6 @@ cgrates.org,SPL_QOS_2,,,,,supplier3,,,,,Stat_3,35,,, cgrates.org,SPL_QOS_3,FLTR_SPP_5,2017-11-27T00:00:00Z,*qos,*pdd,supplier1,,,,,Stat_1;Stat_1_1,10,false,,10 cgrates.org,SPL_QOS_3,,,,,supplier2,,,,,Stat_2,20,,, cgrates.org,SPL_QOS_3,,,,,supplier3,,,,,Stat_3,35,,, +cgrates.org,SPL_QOS_FILTRED,FLTR_SPP_6,2017-11-27T00:00:00Z,*qos,*pdd,supplier1,FLTR_QOS_SP1,,,,Stat_1;Stat_1_1,10,false,,10 +cgrates.org,SPL_QOS_FILTRED,,,,,supplier2,FLTR_QOS_SP2,,,,Stat_2,20,,, +cgrates.org,SPL_QOS_FILTRED,,,,,supplier3,,,,,Stat_3,35,,, diff --git a/engine/libsuppliers.go b/engine/libsuppliers.go index e937f6a4e..67342098b 100644 --- a/engine/libsuppliers.go +++ b/engine/libsuppliers.go @@ -31,7 +31,7 @@ type SortedSupplier struct { SupplierID string SupplierParameters string SortingData map[string]interface{} // store here extra info like cost or stats - worstStats map[string]float64 + globalStats map[string]float64 } // SuppliersReply is returned as part of GetSuppliers call @@ -97,21 +97,21 @@ func (sSpls *SortedSuppliers) SortQOS(params []string) { sort.Slice(sSpls.SortedSuppliers, func(i, j int) bool { for _, param := range params { // skip to next param - if sSpls.SortedSuppliers[i].worstStats[param] == sSpls.SortedSuppliers[j].worstStats[param] { + if sSpls.SortedSuppliers[i].globalStats[param] == sSpls.SortedSuppliers[j].globalStats[param] { continue } - if (param != utils.MetaPDD && sSpls.SortedSuppliers[i].worstStats[param] == -1) || - (param == utils.MetaPDD && sSpls.SortedSuppliers[i].worstStats[param] == 1000000) { + if (param != utils.MetaPDD && sSpls.SortedSuppliers[i].globalStats[param] == -1) || + (param == utils.MetaPDD && sSpls.SortedSuppliers[i].globalStats[param] == 1000000) { return false } switch param { default: - return sSpls.SortedSuppliers[i].worstStats[param] > sSpls.SortedSuppliers[j].worstStats[param] + return sSpls.SortedSuppliers[i].globalStats[param] > sSpls.SortedSuppliers[j].globalStats[param] case utils.MetaPDD: - return sSpls.SortedSuppliers[i].worstStats[param] < sSpls.SortedSuppliers[j].worstStats[param] + return sSpls.SortedSuppliers[i].globalStats[param] < sSpls.SortedSuppliers[j].globalStats[param] } } - return sSpls.SortedSuppliers[i].worstStats[utils.Weight] > sSpls.SortedSuppliers[j].worstStats[utils.Weight] + return sSpls.SortedSuppliers[i].globalStats[utils.Weight] > sSpls.SortedSuppliers[j].globalStats[utils.Weight] }) } @@ -134,7 +134,7 @@ type SuppliersSorter interface { // NewSupplierSortDispatcher constructs SupplierSortDispatcher func NewSupplierSortDispatcher(lcrS *SupplierService) (ssd SupplierSortDispatcher, err error) { ssd = make(map[string]SuppliersSorter) - ssd[utils.MetaWeight] = NewWeightSorter() + ssd[utils.MetaWeight] = NewWeightSorter(lcrS) ssd[utils.MetaLeastCost] = NewLeastCostSorter(lcrS) ssd[utils.MetaHighestCost] = NewHighestCostSorter(lcrS) ssd[utils.MetaQOS] = NewQOSSupplierSorter(lcrS) @@ -153,27 +153,3 @@ func (ssd SupplierSortDispatcher) SortSuppliers(prflID, strategy string, } return sd.SortSuppliers(prflID, suppls, suplEv, extraOpts) } - -func NewWeightSorter() *WeightSorter { - return &WeightSorter{sorting: utils.MetaWeight} -} - -// WeightSorter orders suppliers based on their weight, no cost involved -type WeightSorter struct { - sorting string -} - -func (ws *WeightSorter) SortSuppliers(prflID string, - suppls []*Supplier, suplEv *utils.CGREvent, extraOpts *optsGetSuppliers) (sortedSuppls *SortedSuppliers, err error) { - sortedSuppls = &SortedSuppliers{ProfileID: prflID, - Sorting: ws.sorting, - SortedSuppliers: make([]*SortedSupplier, len(suppls))} - for i, s := range suppls { - sortedSuppls.SortedSuppliers[i] = &SortedSupplier{ - SupplierID: s.ID, - SortingData: map[string]interface{}{utils.Weight: s.Weight}, - SupplierParameters: s.SupplierParameters} - } - sortedSuppls.SortWeight() - return -} diff --git a/engine/libsuppliers_test.go b/engine/libsuppliers_test.go index 370b811f6..4dac55fec 100644 --- a/engine/libsuppliers_test.go +++ b/engine/libsuppliers_test.go @@ -89,64 +89,60 @@ func TestLibSuppliersSortCost(t *testing.T) { } func TestLibSuppliersSortWeight(t *testing.T) { - spl := []*Supplier{ - &Supplier{ - ID: "supplier1", - FilterIDs: []string{}, - AccountIDs: []string{}, - RatingPlanIDs: []string{}, - ResourceIDs: []string{}, - StatIDs: []string{}, - Weight: 10.0, - SupplierParameters: "param1", - }, - &Supplier{ - ID: "supplier2", - FilterIDs: []string{}, - AccountIDs: []string{}, - RatingPlanIDs: []string{}, - ResourceIDs: []string{}, - StatIDs: []string{}, - Weight: 20.0, - SupplierParameters: "param2", - }, - } - eSpls := SortedSuppliers{ - ProfileID: "SPL_WEIGHT_1", - Sorting: utils.MetaWeight, + sSpls := &SortedSuppliers{ SortedSuppliers: []*SortedSupplier{ + &SortedSupplier{ + SupplierID: "supplier1", + SortingData: map[string]interface{}{ + utils.Weight: 10.0, + }, + SupplierParameters: "param1", + }, &SortedSupplier{ SupplierID: "supplier2", SortingData: map[string]interface{}{ - "Weight": 20.0, + utils.Weight: 20.0, }, SupplierParameters: "param2", }, + &SortedSupplier{ + SupplierID: "supplier3", + SortingData: map[string]interface{}{ + utils.Weight: 10.5, + }, + SupplierParameters: "param3", + }, + }, + } + sSpls.SortWeight() + eOrderedSpls := &SortedSuppliers{ + SortedSuppliers: []*SortedSupplier{ + &SortedSupplier{ + SupplierID: "supplier2", + SortingData: map[string]interface{}{ + utils.Weight: 20.0, + }, + SupplierParameters: "param2", + }, + &SortedSupplier{ + SupplierID: "supplier3", + SortingData: map[string]interface{}{ + utils.Weight: 10.5, + }, + SupplierParameters: "param3", + }, &SortedSupplier{ SupplierID: "supplier1", SortingData: map[string]interface{}{ - "Weight": 10.0, + utils.Weight: 10.0, }, SupplierParameters: "param1", }, }, } - se := &utils.CGREvent{ - Tenant: "cgrates.org", - ID: "supplierevent1", - Event: make(map[string]interface{}), - } - ws := NewWeightSorter() - result, err := ws.SortSuppliers("SPL_WEIGHT_1", spl, se, nil) - if err != nil { - t.Error(err) - } - if !reflect.DeepEqual(eSpls.ProfileID, result.ProfileID) { - t.Errorf("Expecting: %+v, received: %+v", eSpls.ProfileID, result.ProfileID) - } else if !reflect.DeepEqual(eSpls.SortedSuppliers, result.SortedSuppliers) { - t.Errorf("Expecting: %+v, received: %+v", eSpls.SortedSuppliers, result.SortedSuppliers) - } else if !reflect.DeepEqual(eSpls.Sorting, result.Sorting) { - t.Errorf("Expecting: %+v, received: %+v", eSpls.Sorting, result.Sorting) + if !reflect.DeepEqual(eOrderedSpls, sSpls) { + t.Errorf("Expecting: %s, received: %s", + utils.ToJSON(eOrderedSpls), utils.ToJSON(sSpls)) } } @@ -288,7 +284,7 @@ func TestLibSuppliersSortQOS(t *testing.T) { SortedSuppliers: []*SortedSupplier{ &SortedSupplier{ SupplierID: "supplier1", - worstStats: map[string]float64{ + globalStats: map[string]float64{ utils.MetaACD: 0.1, utils.MetaTCD: 15.0, }, @@ -296,7 +292,7 @@ func TestLibSuppliersSortQOS(t *testing.T) { }, &SortedSupplier{ SupplierID: "supplier2", - worstStats: map[string]float64{ + globalStats: map[string]float64{ utils.MetaACD: 0.2, utils.MetaTCD: 20.0, }, @@ -304,7 +300,7 @@ func TestLibSuppliersSortQOS(t *testing.T) { }, &SortedSupplier{ SupplierID: "supplier3", - worstStats: map[string]float64{ + globalStats: map[string]float64{ utils.MetaACD: 0.05, utils.MetaTCD: 10.0, }, @@ -317,7 +313,7 @@ func TestLibSuppliersSortQOS(t *testing.T) { SortedSuppliers: []*SortedSupplier{ &SortedSupplier{ SupplierID: "supplier2", - worstStats: map[string]float64{ + globalStats: map[string]float64{ utils.MetaACD: 0.2, utils.MetaTCD: 20.0, }, @@ -325,7 +321,7 @@ func TestLibSuppliersSortQOS(t *testing.T) { }, &SortedSupplier{ SupplierID: "supplier1", - worstStats: map[string]float64{ + globalStats: map[string]float64{ utils.MetaACD: 0.1, utils.MetaTCD: 15.0, }, @@ -333,7 +329,7 @@ func TestLibSuppliersSortQOS(t *testing.T) { }, &SortedSupplier{ SupplierID: "supplier3", - worstStats: map[string]float64{ + globalStats: map[string]float64{ utils.MetaACD: 0.05, utils.MetaTCD: 10.0, }, @@ -352,7 +348,7 @@ func TestLibSuppliersSortQOS2(t *testing.T) { SortedSuppliers: []*SortedSupplier{ &SortedSupplier{ SupplierID: "supplier1", - worstStats: map[string]float64{ + globalStats: map[string]float64{ utils.MetaACD: 0.2, utils.MetaTCD: 15.0, }, @@ -360,7 +356,7 @@ func TestLibSuppliersSortQOS2(t *testing.T) { }, &SortedSupplier{ SupplierID: "supplier2", - worstStats: map[string]float64{ + globalStats: map[string]float64{ utils.MetaACD: 0.2, utils.MetaTCD: 20.0, }, @@ -368,7 +364,7 @@ func TestLibSuppliersSortQOS2(t *testing.T) { }, &SortedSupplier{ SupplierID: "supplier3", - worstStats: map[string]float64{ + globalStats: map[string]float64{ utils.MetaACD: 0.1, utils.MetaTCD: 10.0, }, @@ -381,7 +377,7 @@ func TestLibSuppliersSortQOS2(t *testing.T) { SortedSuppliers: []*SortedSupplier{ &SortedSupplier{ SupplierID: "supplier2", - worstStats: map[string]float64{ + globalStats: map[string]float64{ utils.MetaACD: 0.2, utils.MetaTCD: 20.0, }, @@ -389,7 +385,7 @@ func TestLibSuppliersSortQOS2(t *testing.T) { }, &SortedSupplier{ SupplierID: "supplier1", - worstStats: map[string]float64{ + globalStats: map[string]float64{ utils.MetaACD: 0.2, utils.MetaTCD: 15.0, }, @@ -397,7 +393,7 @@ func TestLibSuppliersSortQOS2(t *testing.T) { }, &SortedSupplier{ SupplierID: "supplier3", - worstStats: map[string]float64{ + globalStats: map[string]float64{ utils.MetaACD: 0.1, utils.MetaTCD: 10.0, }, @@ -416,7 +412,7 @@ func TestLibSuppliersSortQOS3(t *testing.T) { SortedSuppliers: []*SortedSupplier{ &SortedSupplier{ SupplierID: "supplier1", - worstStats: map[string]float64{ + globalStats: map[string]float64{ utils.MetaACD: 0.2, utils.MetaTCD: 15.0, utils.MetaASR: 1.2, @@ -425,7 +421,7 @@ func TestLibSuppliersSortQOS3(t *testing.T) { }, &SortedSupplier{ SupplierID: "supplier2", - worstStats: map[string]float64{ + globalStats: map[string]float64{ utils.MetaACD: 0.2, utils.MetaTCD: 20.0, utils.MetaASR: -1.0, @@ -434,7 +430,7 @@ func TestLibSuppliersSortQOS3(t *testing.T) { }, &SortedSupplier{ SupplierID: "supplier3", - worstStats: map[string]float64{ + globalStats: map[string]float64{ utils.MetaACD: 0.1, utils.MetaTCD: 10.0, utils.MetaASR: 1.2, @@ -448,7 +444,7 @@ func TestLibSuppliersSortQOS3(t *testing.T) { SortedSuppliers: []*SortedSupplier{ &SortedSupplier{ SupplierID: "supplier1", - worstStats: map[string]float64{ + globalStats: map[string]float64{ utils.MetaACD: 0.2, utils.MetaTCD: 15.0, utils.MetaASR: 1.2, @@ -457,7 +453,7 @@ func TestLibSuppliersSortQOS3(t *testing.T) { }, &SortedSupplier{ SupplierID: "supplier3", - worstStats: map[string]float64{ + globalStats: map[string]float64{ utils.MetaACD: 0.1, utils.MetaTCD: 10.0, utils.MetaASR: 1.2, @@ -466,7 +462,7 @@ func TestLibSuppliersSortQOS3(t *testing.T) { }, &SortedSupplier{ SupplierID: "supplier2", - worstStats: map[string]float64{ + globalStats: map[string]float64{ utils.MetaACD: 0.2, utils.MetaTCD: 20.0, utils.MetaASR: -1.0, @@ -486,7 +482,7 @@ func TestLibSuppliersSortQOS4(t *testing.T) { SortedSuppliers: []*SortedSupplier{ &SortedSupplier{ SupplierID: "supplier1", - worstStats: map[string]float64{ + globalStats: map[string]float64{ utils.MetaACD: 0.2, utils.MetaTCD: 15.0, utils.MetaASR: -1.0, @@ -496,7 +492,7 @@ func TestLibSuppliersSortQOS4(t *testing.T) { }, &SortedSupplier{ SupplierID: "supplier2", - worstStats: map[string]float64{ + globalStats: map[string]float64{ utils.MetaACD: 0.2, utils.MetaTCD: 20.0, utils.MetaASR: 1.2, @@ -506,7 +502,7 @@ func TestLibSuppliersSortQOS4(t *testing.T) { }, &SortedSupplier{ SupplierID: "supplier3", - worstStats: map[string]float64{ + globalStats: map[string]float64{ utils.MetaACD: 0.1, utils.MetaTCD: 10.0, utils.MetaASR: 1.2, @@ -521,7 +517,7 @@ func TestLibSuppliersSortQOS4(t *testing.T) { SortedSuppliers: []*SortedSupplier{ &SortedSupplier{ SupplierID: "supplier2", - worstStats: map[string]float64{ + globalStats: map[string]float64{ utils.MetaACD: 0.2, utils.MetaTCD: 20.0, utils.MetaASR: 1.2, @@ -531,7 +527,7 @@ func TestLibSuppliersSortQOS4(t *testing.T) { }, &SortedSupplier{ SupplierID: "supplier3", - worstStats: map[string]float64{ + globalStats: map[string]float64{ utils.MetaACD: 0.1, utils.MetaTCD: 10.0, utils.MetaASR: 1.2, @@ -541,7 +537,7 @@ func TestLibSuppliersSortQOS4(t *testing.T) { }, &SortedSupplier{ SupplierID: "supplier1", - worstStats: map[string]float64{ + globalStats: map[string]float64{ utils.MetaACD: 0.2, utils.MetaTCD: 15.0, utils.MetaASR: -1.0, @@ -562,7 +558,7 @@ func TestLibSuppliersSortQOS5(t *testing.T) { SortedSuppliers: []*SortedSupplier{ &SortedSupplier{ SupplierID: "supplier1", - worstStats: map[string]float64{ + globalStats: map[string]float64{ utils.MetaACD: 0.2, utils.MetaPDD: 0.5, }, @@ -570,7 +566,7 @@ func TestLibSuppliersSortQOS5(t *testing.T) { }, &SortedSupplier{ SupplierID: "supplier2", - worstStats: map[string]float64{ + globalStats: map[string]float64{ utils.MetaACD: 0.2, utils.MetaPDD: 0.6, }, @@ -578,7 +574,7 @@ func TestLibSuppliersSortQOS5(t *testing.T) { }, &SortedSupplier{ SupplierID: "supplier3", - worstStats: map[string]float64{ + globalStats: map[string]float64{ utils.MetaACD: 0.1, utils.MetaPDD: 0.2, }, @@ -591,7 +587,7 @@ func TestLibSuppliersSortQOS5(t *testing.T) { SortedSuppliers: []*SortedSupplier{ &SortedSupplier{ SupplierID: "supplier3", - worstStats: map[string]float64{ + globalStats: map[string]float64{ utils.MetaACD: 0.1, utils.MetaPDD: 0.2, }, @@ -599,7 +595,7 @@ func TestLibSuppliersSortQOS5(t *testing.T) { }, &SortedSupplier{ SupplierID: "supplier1", - worstStats: map[string]float64{ + globalStats: map[string]float64{ utils.MetaACD: 0.2, utils.MetaPDD: 0.5, }, @@ -607,7 +603,7 @@ func TestLibSuppliersSortQOS5(t *testing.T) { }, &SortedSupplier{ SupplierID: "supplier2", - worstStats: map[string]float64{ + globalStats: map[string]float64{ utils.MetaACD: 0.2, utils.MetaPDD: 0.6, }, @@ -626,7 +622,7 @@ func TestLibSuppliersSortQOS6(t *testing.T) { SortedSuppliers: []*SortedSupplier{ &SortedSupplier{ SupplierID: "supplier1", - worstStats: map[string]float64{ + globalStats: map[string]float64{ utils.MetaACD: 0.2, utils.Weight: 15.0, }, @@ -634,7 +630,7 @@ func TestLibSuppliersSortQOS6(t *testing.T) { }, &SortedSupplier{ SupplierID: "supplier2", - worstStats: map[string]float64{ + globalStats: map[string]float64{ utils.MetaACD: 0.2, utils.Weight: 25.0, }, @@ -642,7 +638,7 @@ func TestLibSuppliersSortQOS6(t *testing.T) { }, &SortedSupplier{ SupplierID: "supplier3", - worstStats: map[string]float64{ + globalStats: map[string]float64{ utils.MetaACD: 0.1, utils.Weight: 20.0, }, @@ -655,7 +651,7 @@ func TestLibSuppliersSortQOS6(t *testing.T) { SortedSuppliers: []*SortedSupplier{ &SortedSupplier{ SupplierID: "supplier2", - worstStats: map[string]float64{ + globalStats: map[string]float64{ utils.MetaACD: 0.2, utils.Weight: 25.0, }, @@ -663,7 +659,7 @@ func TestLibSuppliersSortQOS6(t *testing.T) { }, &SortedSupplier{ SupplierID: "supplier1", - worstStats: map[string]float64{ + globalStats: map[string]float64{ utils.MetaACD: 0.2, utils.Weight: 15.0, }, @@ -672,7 +668,7 @@ func TestLibSuppliersSortQOS6(t *testing.T) { &SortedSupplier{ SupplierID: "supplier3", - worstStats: map[string]float64{ + globalStats: map[string]float64{ utils.MetaACD: 0.1, utils.Weight: 20.0, }, diff --git a/engine/spls_highestcost.go b/engine/spls_highestcost.go index c2f5bc767..82afdd49e 100755 --- a/engine/spls_highestcost.go +++ b/engine/spls_highestcost.go @@ -19,8 +19,6 @@ along with this program. If not, see package engine import ( - "fmt" - "github.com/cgrates/cgrates/utils" ) @@ -35,42 +33,17 @@ type HightCostSorter struct { spS *SupplierService } -func (lcs *HightCostSorter) SortSuppliers(prflID string, suppls []*Supplier, +func (hcs *HightCostSorter) SortSuppliers(prflID string, suppls []*Supplier, ev *utils.CGREvent, extraOpts *optsGetSuppliers) (sortedSuppls *SortedSuppliers, err error) { sortedSuppls = &SortedSuppliers{ProfileID: prflID, - Sorting: lcs.sorting, + Sorting: hcs.sorting, SortedSuppliers: make([]*SortedSupplier, 0)} for _, s := range suppls { - costData, err := lcs.spS.costForEvent(ev, s.AccountIDs, s.RatingPlanIDs) - if err != nil { - if extraOpts.ignoreErrors { - utils.Logger.Warning( - fmt.Sprintf("<%s> profile: %s ignoring supplier with ID: %s, err: %s", - utils.SupplierS, prflID, s.ID, err.Error())) - continue - } + if srtSpl, pass, err := hcs.spS.populateSortingData(ev, s, extraOpts); err != nil { return nil, err - } else if len(costData) == 0 { - utils.Logger.Warning( - fmt.Sprintf("<%s> profile: %s ignoring supplier with ID: %s, missing cost information", - utils.SupplierS, prflID, s.ID)) - continue + } else if pass && srtSpl != nil { + sortedSuppls.SortedSuppliers = append(sortedSuppls.SortedSuppliers, srtSpl) } - if extraOpts.maxCost != 0 && - costData[utils.Cost].(float64) > extraOpts.maxCost { - continue - } - srtData := map[string]interface{}{ - utils.Weight: s.Weight, - } - for k, v := range costData { - srtData[k] = v - } - sortedSuppls.SortedSuppliers = append(sortedSuppls.SortedSuppliers, - &SortedSupplier{ - SupplierID: s.ID, - SortingData: srtData, - SupplierParameters: s.SupplierParameters}) } if len(sortedSuppls.SortedSuppliers) == 0 { return nil, utils.ErrNotFound diff --git a/engine/spls_leastcost.go b/engine/spls_leastcost.go index 94c5cca78..5d5d386cc 100644 --- a/engine/spls_leastcost.go +++ b/engine/spls_leastcost.go @@ -19,8 +19,6 @@ along with this program. If not, see package engine import ( - "fmt" - "github.com/cgrates/cgrates/utils" ) @@ -41,36 +39,11 @@ func (lcs *LeastCostSorter) SortSuppliers(prflID string, suppls []*Supplier, Sorting: lcs.sorting, SortedSuppliers: make([]*SortedSupplier, 0)} for _, s := range suppls { - costData, err := lcs.spS.costForEvent(ev, s.AccountIDs, s.RatingPlanIDs) - if err != nil { - if extraOpts.ignoreErrors { - utils.Logger.Warning( - fmt.Sprintf("<%s> profile: %s ignoring supplier with ID: %s, err: %s", - utils.SupplierS, prflID, s.ID, err.Error())) - continue - } + if srtSpl, pass, err := lcs.spS.populateSortingData(ev, s, extraOpts); err != nil { return nil, err - } else if len(costData) == 0 { - utils.Logger.Warning( - fmt.Sprintf("<%s> profile: %s ignoring supplier with ID: %s, missing cost information", - utils.SupplierS, prflID, s.ID)) - continue + } else if pass && srtSpl != nil { + sortedSuppls.SortedSuppliers = append(sortedSuppls.SortedSuppliers, srtSpl) } - if extraOpts.maxCost != 0 && - costData[utils.Cost].(float64) > extraOpts.maxCost { - continue - } - srtData := map[string]interface{}{ - utils.Weight: s.Weight, - } - for k, v := range costData { - srtData[k] = v - } - sortedSuppls.SortedSuppliers = append(sortedSuppls.SortedSuppliers, - &SortedSupplier{ - SupplierID: s.ID, - SortingData: srtData, - SupplierParameters: s.SupplierParameters}) } if len(sortedSuppls.SortedSuppliers) == 0 { return nil, utils.ErrNotFound diff --git a/engine/spls_qos.go b/engine/spls_qos.go index d520aab68..e82017c38 100755 --- a/engine/spls_qos.go +++ b/engine/spls_qos.go @@ -19,9 +19,6 @@ along with this program. If not, see package engine import ( - "fmt" - "strings" - "github.com/cgrates/cgrates/utils" ) @@ -36,62 +33,17 @@ type QOSSupplierSorter struct { spS *SupplierService } -func (lcs *QOSSupplierSorter) SortSuppliers(prflID string, suppls []*Supplier, +func (qos *QOSSupplierSorter) SortSuppliers(prflID string, suppls []*Supplier, ev *utils.CGREvent, extraOpts *optsGetSuppliers) (sortedSuppls *SortedSuppliers, err error) { sortedSuppls = &SortedSuppliers{ProfileID: prflID, - Sorting: lcs.sorting, + Sorting: qos.sorting, SortedSuppliers: make([]*SortedSupplier, 0)} for _, s := range suppls { - metricSupp, err := lcs.spS.statMetrics(s.StatIDs, ev.Tenant) //create metric map for suppier s - if err != nil { - if extraOpts.ignoreErrors { - utils.Logger.Warning( - fmt.Sprintf("<%s> profile: %s ignoring supplier with ID: %s, err: %s", - utils.SupplierS, prflID, s.ID, err.Error())) - continue - } + if srtSpl, pass, err := qos.spS.populateSortingData(ev, s, extraOpts); err != nil { return nil, err + } else if pass && srtSpl != nil { + sortedSuppls.SortedSuppliers = append(sortedSuppls.SortedSuppliers, srtSpl) } - - srtData := map[string]float64{ - utils.Weight: s.Weight, - } - for _, metric := range extraOpts.sortingParameters { - hasMetric := false //check if metricSupp have sortingParameter - for keyWithID, value := range metricSupp { //transfer data from metric into srtData - if metric == strings.Split(keyWithID, utils.InInFieldSep)[0] { - if val, hasKey := srtData[metric]; !hasKey || - (metric == utils.MetaPDD && val < value) || //worst values - (metric != utils.MetaPDD && val > value) { - srtData[metric] = value - hasMetric = true - } - } - } - if !hasMetric { //if not have populate with default value - switch metric { - default: - srtData[metric] = -1 - case utils.MetaPDD: - srtData[metric] = 1000000 - } - } - } - - sortingData := map[string]interface{}{ - utils.Weight: s.Weight, - } - for k, v := range metricSupp { - sortingData[k] = v - } - sortedSuppls.SortedSuppliers = append(sortedSuppls.SortedSuppliers, - &SortedSupplier{ - SupplierID: s.ID, - SortingData: sortingData, - SupplierParameters: s.SupplierParameters, - worstStats: srtData, - }, - ) } if len(sortedSuppls.SortedSuppliers) == 0 { return nil, utils.ErrNotFound diff --git a/engine/spls_weight.go b/engine/spls_weight.go new file mode 100755 index 000000000..6cc518a24 --- /dev/null +++ b/engine/spls_weight.go @@ -0,0 +1,50 @@ +/* +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 engine + +import ( + "github.com/cgrates/cgrates/utils" +) + +func NewWeightSorter(spS *SupplierService) *WeightSorter { + return &WeightSorter{spS: spS, + sorting: utils.MetaWeight} +} + +// WeightSorter orders suppliers based on their weight, no cost involved +type WeightSorter struct { + sorting string + spS *SupplierService +} + +func (ws *WeightSorter) SortSuppliers(prflID string, + suppls []*Supplier, suplEv *utils.CGREvent, extraOpts *optsGetSuppliers) (sortedSuppls *SortedSuppliers, err error) { + sortedSuppls = &SortedSuppliers{ProfileID: prflID, + Sorting: ws.sorting, + SortedSuppliers: make([]*SortedSupplier, 0)} + for _, s := range suppls { + if srtSpl, pass, err := ws.spS.populateSortingData(suplEv, s, extraOpts); err != nil { + return nil, err + } else if pass && srtSpl != nil { + sortedSuppls.SortedSuppliers = append(sortedSuppls.SortedSuppliers, srtSpl) + } + } + sortedSuppls.SortWeight() + return +} diff --git a/engine/suppliers.go b/engine/suppliers.go index 884f1efcb..51fe63206 100644 --- a/engine/suppliers.go +++ b/engine/suppliers.go @@ -23,6 +23,7 @@ import ( "reflect" "sort" "strconv" + "strings" "time" "github.com/cgrates/cgrates/config" @@ -284,6 +285,100 @@ func (spS *SupplierService) resourceUsage(resIDs []string) (tUsage float64, err return } +func (spS *SupplierService) populateSortingData(ev *utils.CGREvent, spl *Supplier, extraOpts *optsGetSuppliers) (srtSpl *SortedSupplier, pass bool, err error) { + globalStats := map[string]float64{ //used for QOS strategy + utils.Weight: spl.Weight, + } + sortedSpl := &SortedSupplier{ + SupplierID: spl.ID, + SortingData: map[string]interface{}{ + utils.Weight: spl.Weight, + }, + SupplierParameters: spl.SupplierParameters, + } + //calculate costData if we have fields + if len(spl.AccountIDs) != 0 || len(spl.RatingPlanIDs) != 0 { + costData, err := spS.costForEvent(ev, spl.AccountIDs, spl.RatingPlanIDs) + if err != nil { + if extraOpts.ignoreErrors { + utils.Logger.Warning( + fmt.Sprintf("<%s> ignoring supplier with ID: %s, err: %s", + utils.SupplierS, spl.ID, err.Error())) + } else { + return nil, false, err + } + } else if len(costData) == 0 { + utils.Logger.Warning( + fmt.Sprintf("<%s> profile: %s ignoring supplier with ID: %s, missing cost information", + utils.SupplierS, spl.ID)) + } else { + if extraOpts.maxCost != 0 && + costData[utils.Cost].(float64) > extraOpts.maxCost { + return nil, false, nil + } + for k, v := range costData { + sortedSpl.SortingData[k] = v + } + } + } + metricForFilter := map[string]interface{}{ + utils.Weight: spl.Weight, + } + //calculate metrics + if len(spl.StatIDs) != 0 { + metricSupp, err := spS.statMetrics(spl.StatIDs, ev.Tenant) //create metric map for suppier + if err != nil { + if extraOpts.ignoreErrors { + utils.Logger.Warning( + fmt.Sprintf("<%s> ignoring supplier with ID: %s, err: %s", + utils.SupplierS, spl.ID, err.Error())) + } else { + return nil, false, err + } + } + for _, metric := range extraOpts.sortingParameters { + hasMetric := false //check if metricSupp have sortingParameter + for keyWithID, value := range metricSupp { //transfer data from metric into globalStats + if metric == strings.Split(keyWithID, utils.InInFieldSep)[0] { + if val, hasKey := globalStats[metric]; !hasKey || + (metric == utils.MetaPDD && val < value) || //worst values + (metric != utils.MetaPDD && val > value) { + globalStats[metric] = value + hasMetric = true + } + } + } + if !hasMetric { //if not have populate with default value + switch metric { + default: + globalStats[metric] = -1 + case utils.MetaPDD: + globalStats[metric] = 1000000 + } + } + } + for k, v := range metricSupp { + sortedSpl.SortingData[k] = v + metricForFilter[strings.Split(k, utils.InInFieldSep)[0]] = v + } + sortedSpl.globalStats = globalStats + } + //filter the supplier + if len(spl.FilterIDs) != 0 { + nM := NewNavigableMap(nil) + nM.Set([]string{"*req"}, ev.Event, false) + nM.Set([]string{"*sd"}, sortedSpl.SortingData, false) + nM.Set([]string{"*gs"}, metricForFilter, false) + if pass, err = spS.filterS.Pass(ev.Tenant, spl.FilterIDs, + nM); err != nil { + return nil, false, err + } else if !pass { + return nil, false, nil + } + } + return sortedSpl, true, nil +} + // supliersForEvent will return the list of valid supplier IDs // for event based on filters and sorting algorithms func (spS *SupplierService) sortedSuppliersForEvent(args *ArgsGetSuppliers) (sortedSuppls *SortedSuppliers, err error) { @@ -296,40 +391,36 @@ func (spS *SupplierService) sortedSuppliersForEvent(args *ArgsGetSuppliers) (sor } else if len(suppPrfls) == 0 { return nil, utils.ErrNotFound } - splPrfl := suppPrfls[0] // pick up the first lcr profile as winner - var spls []*Supplier - for _, s := range splPrfl.Suppliers { - if len(s.FilterIDs) != 0 { // filters should be applied, check them here - if pass, err := spS.filterS.Pass(args.Tenant, s.FilterIDs, - NewNavigableMap(args.Event)); err != nil { - return nil, err - } else if !pass { - continue - } - } - spls = append(spls, s) - } + splPrfl := suppPrfls[0] // pick up the first lcr profile as winner extraOpts, err := args.asOptsGetSuppliers() // convert suppliers arguments into internal options used to limit data if err != nil { return nil, err } extraOpts.sortingParameters = splPrfl.SortingParameters // populate sortingParameters in extraOpts sortedSuppliers, err := spS.sorter.SortSuppliers(splPrfl.ID, splPrfl.Sorting, - spls, &args.CGREvent, extraOpts) + splPrfl.Suppliers, &args.CGREvent, extraOpts) if err != nil { return nil, err } + srtTmp := &SortedSuppliers{ + ProfileID: sortedSuppliers.ProfileID, + Sorting: sortedSuppliers.Sorting, + } + for _, s := range sortedSuppliers.SortedSuppliers { + + srtTmp.SortedSuppliers = append(srtTmp.SortedSuppliers, s) + } if args.Paginator.Offset != nil { - if *args.Paginator.Offset <= len(sortedSuppliers.SortedSuppliers) { - sortedSuppliers.SortedSuppliers = sortedSuppliers.SortedSuppliers[*args.Paginator.Offset:] + if *args.Paginator.Offset <= len(srtTmp.SortedSuppliers) { + srtTmp.SortedSuppliers = srtTmp.SortedSuppliers[*args.Paginator.Offset:] } } if args.Paginator.Limit != nil { - if *args.Paginator.Limit <= len(sortedSuppliers.SortedSuppliers) { - sortedSuppliers.SortedSuppliers = sortedSuppliers.SortedSuppliers[:*args.Paginator.Limit] + if *args.Paginator.Limit <= len(srtTmp.SortedSuppliers) { + srtTmp.SortedSuppliers = srtTmp.SortedSuppliers[:*args.Paginator.Limit] } } - return sortedSuppliers, nil + return srtTmp, nil } type ArgsGetSuppliers struct { diff --git a/engine/suppliers_test.go b/engine/suppliers_test.go index 88dd79915..ca3cc7bde 100644 --- a/engine/suppliers_test.go +++ b/engine/suppliers_test.go @@ -45,7 +45,7 @@ var ( Suppliers: []*Supplier{ &Supplier{ ID: "supplier1", - FilterIDs: []string{"FLTR_SUPP_1"}, + FilterIDs: []string{}, AccountIDs: []string{}, RatingPlanIDs: []string{}, ResourceIDs: []string{}, @@ -70,7 +70,7 @@ var ( Suppliers: []*Supplier{ &Supplier{ ID: "supplier2", - FilterIDs: []string{"FLTR_SUPP_2"}, + FilterIDs: []string{}, AccountIDs: []string{}, RatingPlanIDs: []string{}, ResourceIDs: []string{}, @@ -80,7 +80,7 @@ var ( }, &Supplier{ ID: "supplier3", - FilterIDs: []string{"FLTR_SUPP_2"}, + FilterIDs: []string{}, AccountIDs: []string{}, RatingPlanIDs: []string{}, ResourceIDs: []string{}, @@ -90,7 +90,7 @@ var ( }, &Supplier{ ID: "supplier1", - FilterIDs: []string{"FLTR_SUPP_2"}, + FilterIDs: []string{}, AccountIDs: []string{}, RatingPlanIDs: []string{}, ResourceIDs: []string{}, @@ -115,7 +115,7 @@ var ( Suppliers: []*Supplier{ &Supplier{ ID: "supplier1", - FilterIDs: []string{"FLTR_SUPP_3"}, + FilterIDs: []string{}, AccountIDs: []string{}, RatingPlanIDs: []string{}, ResourceIDs: []string{}, @@ -430,6 +430,7 @@ func TestSuppliersSortedForEvent(t *testing.T) { if !reflect.DeepEqual(eFirstSupplierProfile, sprf) { t.Errorf("Expecting: %+v, received: %+v", eFirstSupplierProfile, sprf) } + eFirstSupplierProfile = &SortedSuppliers{ ProfileID: "SupplierProfile2", Sorting: utils.MetaWeight, diff --git a/utils/apitpdata.go b/utils/apitpdata.go index 16f36d64e..9f0b7f9fa 100755 --- a/utils/apitpdata.go +++ b/utils/apitpdata.go @@ -1386,6 +1386,7 @@ type TPFilter struct { type TPSupplier struct { ID string // SupplierID + SortingFilter []string FilterIDs []string AccountIDs []string RatingPlanIDs []string // used when computing price diff --git a/utils/consts.go b/utils/consts.go index 26e10e2c1..d97906461 100755 --- a/utils/consts.go +++ b/utils/consts.go @@ -486,6 +486,7 @@ const ( MetaLeastCost = "*least_cost" MetaHighestCost = "*highest_cost" MetaQOS = "*qos" + MetaStatFiltered = "*stat_filtered" Weight = "Weight" Cost = "Cost" RatingPlanID = "RatingPlanID"