diff --git a/engine/libsuppliers.go b/engine/libsuppliers.go index 46c0de527..89154d09c 100644 --- a/engine/libsuppliers.go +++ b/engine/libsuppliers.go @@ -19,11 +19,66 @@ along with this program. If not, see package engine import ( + "errors" "fmt" + "sort" + "time" "github.com/cgrates/cgrates/utils" ) +const ( + Weight = "Weight" +) + +// SuppliersReply is returned as part of GetSuppliers call +type SortedSuppliers struct { + ProfileID string // Profile matched + Sorting string // Sorting algorithm + SortedSuppliers []*SortedSupplier // list of supplier IDs and SortingData data +} + +// SupplierReply represents one supplier in +type SortedSupplier struct { + SupplierID string + SortingData map[string]interface{} // store here extra info like cost or stats +} + +// Sort is part of sort interface, sort based on Weight +func (sSpls *SortedSuppliers) SortWeight() { + sort.Slice(sSpls.SortedSuppliers, func(i, j int) bool { + return sSpls.SortedSuppliers[i].SortingData[Weight].(float64) > sSpls.SortedSuppliers[j].SortingData[Weight].(float64) + }) +} + +// SupplierEvent is an event processed by Supplier Service +type SupplierEvent struct { + Tenant string + ID string + Event map[string]interface{} +} + +// AnswerTime returns the AnswerTime of StatEvent +func (le *SupplierEvent) AnswerTime(timezone string) (at time.Time, err error) { + atIf, has := le.Event[utils.ANSWER_TIME] + if !has { + return at, utils.ErrNotFound + } + if at, canCast := atIf.(time.Time); canCast { + return at, nil + } + atStr, canCast := atIf.(string) + if !canCast { + return at, errors.New("cannot cast to string") + } + return utils.ParseTimeDetectLayout(atStr, timezone) +} + +// SuppliersSorter is the interface which needs to be implemented by supplier sorters +type SuppliersSorter interface { + SortSuppliers(string, []*Supplier, *SupplierEvent) (*SortedSuppliers, error) +} + // NewSupplierSortDispatcher constructs SupplierSortDispatcher func NewSupplierSortDispatcher(lcrS *SupplierService) (ssd SupplierSortDispatcher, err error) { ssd = make(map[string]SuppliersSorter) @@ -37,54 +92,33 @@ func NewSupplierSortDispatcher(lcrS *SupplierService) (ssd SupplierSortDispatche type SupplierSortDispatcher map[string]SuppliersSorter func (ssd SupplierSortDispatcher) SortSuppliers(prflID, strategy string, - suppls Suppliers) (sortedSuppls *SortedSuppliers, err error) { - fmt.Printf("Sort strategy: %s, suppliers: %s\n", strategy, utils.ToJSON(suppls)) + suppls []*Supplier, suplEv *SupplierEvent) (sortedSuppls *SortedSuppliers, err error) { sd, has := ssd[strategy] if !has { return nil, fmt.Errorf("unsupported sorting strategy: %s", strategy) } - return sd.SortSuppliers(prflID, suppls) -} - -type SuppliersSorter interface { - SortSuppliers(string, Suppliers) (*SortedSuppliers, error) -} - -// NewLeastCostSorter constructs LeastCostSorter -func NewLeastCostSorter(lcrS *SupplierService) *LeastCostSorter { - return &LeastCostSorter{lcrS: lcrS} -} - -// LeastCostSorter orders suppliers based on lowest cost -type LeastCostSorter struct { - lcrS *SupplierService -} - -func (lcs *LeastCostSorter) SortSuppliers(prflID string, - suppls Suppliers) (sortedSuppls *SortedSuppliers, err error) { - return + return sd.SortSuppliers(prflID, suppls, suplEv) } func NewWeightSorter() *WeightSorter { - return &WeightSorter{Sorting: utils.MetaWeight} + return &WeightSorter{sorting: utils.MetaWeight} } // WeightSorter orders suppliers based on their weight, no cost involved type WeightSorter struct { - Sorting string + sorting string } func (ws *WeightSorter) SortSuppliers(prflID string, - suppls Suppliers) (sortedSuppls *SortedSuppliers, err error) { - fmt.Printf("Sort suppliers: %s\n", utils.ToJSON(suppls)) - suppls.Sort() + suppls []*Supplier, suplEv *SupplierEvent) (sortedSuppls *SortedSuppliers, err error) { sortedSuppls = &SortedSuppliers{ProfileID: prflID, - Sorting: ws.Sorting, + Sorting: ws.sorting, SortedSuppliers: make([]*SortedSupplier, len(suppls))} for i, s := range suppls { sortedSuppls.SortedSuppliers[i] = &SortedSupplier{ SupplierID: s.ID, SortingData: map[string]interface{}{"Weight": s.Weight}} } + sortedSuppls.SortWeight() return } diff --git a/engine/model_helpers.go b/engine/model_helpers.go index 32c4f9486..463e258c2 100755 --- a/engine/model_helpers.go +++ b/engine/model_helpers.go @@ -2607,7 +2607,7 @@ func APItoSupplierProfile(tpTH *utils.TPSupplier, timezone string) (th *Supplier Sorting: tpTH.Sorting, Weight: tpTH.Weight, Blocker: tpTH.Blocker, - Suppliers: make(Suppliers, len(tpTH.Suppliers)), + Suppliers: make([]*Supplier, len(tpTH.Suppliers)), } for _, stp := range tpTH.SortingParams { th.SortingParams = append(th.SortingParams, stp) diff --git a/engine/spls_leastcost.go b/engine/spls_leastcost.go new file mode 100644 index 000000000..d9cebea44 --- /dev/null +++ b/engine/spls_leastcost.go @@ -0,0 +1,47 @@ +/* +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 NewLeastCostSorter(spS *SupplierService) *LeastCostSorter { + return &LeastCostSorter{spS: spS, + sorting: utils.MetaLeastCost} +} + +// LeastCostSorter sorts suppliers based on their cost +type LeastCostSorter struct { + sorting string + spS *SupplierService +} + +func (lcs *LeastCostSorter) SortSuppliers(prflID string, + suppls []*Supplier, suplEv *SupplierEvent) (sortedSuppls *SortedSuppliers, err error) { + sortedSuppls = &SortedSuppliers{ProfileID: prflID, + Sorting: lcs.sorting, + SortedSuppliers: make([]*SortedSupplier, len(suppls))} + for i, s := range suppls { + sortedSuppls.SortedSuppliers[i] = &SortedSupplier{ + SupplierID: s.ID, + SortingData: map[string]interface{}{"Weight": s.Weight}} + } + return +} diff --git a/engine/suppliers.go b/engine/suppliers.go index 2a8032704..bf9cb9d2c 100644 --- a/engine/suppliers.go +++ b/engine/suppliers.go @@ -19,7 +19,6 @@ along with this program. If not, see package engine import ( - "errors" "fmt" "sort" "time" @@ -30,29 +29,6 @@ import ( "github.com/cgrates/rpcclient" ) -// SupplierEvent is an event processed by Supplier Service -type SupplierEvent struct { - Tenant string - ID string - Event map[string]interface{} -} - -// AnswerTime returns the AnswerTime of StatEvent -func (le *SupplierEvent) AnswerTime(timezone string) (at time.Time, err error) { - atIf, has := le.Event[utils.ANSWER_TIME] - if !has { - return at, utils.ErrNotFound - } - if at, canCast := atIf.(time.Time); canCast { - return at, nil - } - atStr, canCast := atIf.(string) - if !canCast { - return at, errors.New("cannot cast to string") - } - return utils.ParseTimeDetectLayout(atStr, timezone) -} - // Supplier defines supplier related information used within a SupplierProfile type Supplier struct { ID string // SupplierID @@ -64,14 +40,6 @@ type Supplier struct { Weight float64 } -// Suppliers is a sortable list of Supplier -type Suppliers []*Supplier - -// Sort is part of sort interface, sort based on Weight -func (lss Suppliers) Sort() { - sort.Slice(lss, func(i, j int) bool { return lss[i].Weight > lss[j].Weight }) -} - // SupplierProfile represents the configuration of a Supplier profile type SupplierProfile struct { Tenant string @@ -80,7 +48,7 @@ type SupplierProfile struct { ActivationInterval *utils.ActivationInterval // Activation interval Sorting string // Sorting strategy SortingParams []string - Suppliers Suppliers + Suppliers []*Supplier Blocker bool // do not process further profiles after this one Weight float64 } @@ -98,19 +66,6 @@ func (lps SupplierProfiles) Sort() { sort.Slice(lps, func(i, j int) bool { return lps[i].Weight > lps[j].Weight }) } -// SuppliersReply is returned as part of GetSuppliers call -type SortedSuppliers struct { - ProfileID string // Profile matched - Sorting string // Sorting algorithm - SortedSuppliers []*SortedSupplier // list of supplier IDs and SortingData data -} - -// SupplierReply represents one supplier in -type SortedSupplier struct { - SupplierID string - SortingData map[string]interface{} // store here extra info like cost or stats -} - // NewLCRService initializes a LCRService func NewSupplierService(dm *DataManager, timezone string, filterS *FilterS, indexedFields []string, resourceS, @@ -237,7 +192,7 @@ func (spS *SupplierService) sortedSuppliersForEvent(ev *SupplierEvent) (sortedSu return nil, utils.ErrNotFound } lcrPrfl := suppPrfls[0] // pick up the first lcr profile as winner - var lss Suppliers + var spls []*Supplier for _, s := range lcrPrfl.Suppliers { if len(s.FilterIDs) != 0 { // filters should be applied, check them here if pass, err := spS.filterS.PassFiltersForEvent(ev.Tenant, @@ -247,9 +202,9 @@ func (spS *SupplierService) sortedSuppliersForEvent(ev *SupplierEvent) (sortedSu continue } } - lss = append(lss, s) + spls = append(spls, s) } - return spS.sorter.SortSuppliers(lcrPrfl.ID, lcrPrfl.Sorting, lss) + return spS.sorter.SortSuppliers(lcrPrfl.ID, lcrPrfl.Sorting, spls, ev) } // V1GetSuppliersForEvent returns the list of valid supplier IDs