mirror of
https://github.com/cgrates/cgrates.git
synced 2026-02-11 18:16:24 +05:00
only one lcr entry per cd
This commit is contained in:
@@ -54,9 +54,9 @@ The LCR rules for a specific call descriptor are searched using direction, tenan
|
||||
|
||||
Because a rule can have several entries they will be sorted by activation time.
|
||||
|
||||
Next the system will find out if one ore more LCR entries apply to this call considering entry's activation time. If more than one applies the call will be split into LCR time-spans attaching the corresponding entry to each timespan.
|
||||
Next the system will find out the most recent LCR entry that applies to this call considering entries activation times.
|
||||
|
||||
Each timespan is than iterated and acted upon according to it's entry strategy. For static strategy the cost is calculated for each supplier found in the parameters and the suppliers are listed as they are found.
|
||||
The LCR entry is processed according to it's strategy. For static strategy the cost is calculated for each supplier found in the parameters and the suppliers are listed as they are found.
|
||||
|
||||
For the QOS strategies the suppliers are searched using call descriptor parameters (direction, tenant, category, account, subject), than the cdrstats module is queried for the QOS values and the suppliers are filtered or sorted according to the StrategyParameters field.
|
||||
|
||||
@@ -64,7 +64,7 @@ For the lowest/highest cost strategies the matched suppliers are sorted ascendin
|
||||
|
||||
::
|
||||
|
||||
[{
|
||||
{
|
||||
"Entry": {
|
||||
"DestinationId": "*any",
|
||||
"RPCategory": "LCR_STANDARD",
|
||||
@@ -72,8 +72,7 @@ For the lowest/highest cost strategies the matched suppliers are sorted ascendin
|
||||
"StrategyParams": "",
|
||||
"Weight": 20
|
||||
},
|
||||
"StartTime": "2015-04-06T17:40:00Z",
|
||||
"SupplierCosts": [{"Supplier":"rif", Cost:"2.0"},{"Supplier":"dan", Cost:"1.0"}]
|
||||
}]
|
||||
}
|
||||
|
||||
.. [WIKI2015] http://en.wikipedia.org/wiki/Least-cost_routing
|
||||
|
||||
@@ -671,7 +671,7 @@ func (cd *CallDescriptor) GetLCRFromStorage() (*LCR, error) {
|
||||
return nil, errors.New(utils.ERR_NOT_FOUND)
|
||||
}
|
||||
|
||||
func (cd *CallDescriptor) GetLCR(stats StatsInterface) (LCRCost, error) {
|
||||
func (cd *CallDescriptor) GetLCR(stats StatsInterface) (*LCRCost, error) {
|
||||
lcr, err := cd.GetLCRFromStorage()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -681,138 +681,133 @@ func (cd *CallDescriptor) GetLCR(stats StatsInterface) (LCRCost, error) {
|
||||
|
||||
// find if one ore more entries apply to this cd (create lcr timespans)
|
||||
// create timespans and attach lcr entries to them
|
||||
lcrCost := LCRCost{&LCRTimeSpan{StartTime: cd.TimeStart}}
|
||||
lcrCost := &LCRCost{}
|
||||
for _, lcrActivation := range lcr.Activations {
|
||||
//log.Printf("Activation: %+v", lcrActivation)
|
||||
lcrEntry := lcrActivation.GetLCREntryForPrefix(cd.Destination)
|
||||
//log.Printf("Entry: %+v", lcrEntry)
|
||||
if lcrActivation.ActivationTime.Before(cd.TimeStart) ||
|
||||
lcrActivation.ActivationTime.Equal(cd.TimeStart) {
|
||||
lcrCost[0].Entry = lcrEntry
|
||||
lcrCost.Entry = lcrEntry
|
||||
} else {
|
||||
if lcrActivation.ActivationTime.Before(cd.TimeEnd) {
|
||||
// add lcr timespan
|
||||
lcrCost = append(lcrCost, &LCRTimeSpan{
|
||||
StartTime: lcrActivation.ActivationTime,
|
||||
Entry: lcrEntry,
|
||||
// because lcr is sorted the folowing ones will
|
||||
// only activate later than cd.Timestart
|
||||
break
|
||||
}
|
||||
}
|
||||
if lcrCost.Entry == nil {
|
||||
return lcrCost, nil
|
||||
}
|
||||
//log.Printf("Entry: %+v", ts.Entry)
|
||||
if lcrCost.Entry.Strategy == LCR_STRATEGY_STATIC {
|
||||
for _, supplier := range lcrCost.Entry.GetParams() {
|
||||
lcrCD := cd.Clone()
|
||||
lcrCD.Subject = supplier
|
||||
if cd.account, err = accountingStorage.GetAccount(cd.GetAccountKey()); err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if cc, err := lcrCD.debit(cd.account, true, true); err != nil || cc == nil {
|
||||
lcrCost.SupplierCosts = append(lcrCost.SupplierCosts, &LCRSupplierCost{
|
||||
Supplier: supplier,
|
||||
Error: err,
|
||||
})
|
||||
} else {
|
||||
lcrCost.SupplierCosts = append(lcrCost.SupplierCosts, &LCRSupplierCost{
|
||||
Supplier: supplier,
|
||||
Cost: cc.Cost,
|
||||
Duration: cc.GetDuration().String(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, ts := range lcrCost {
|
||||
//log.Printf("TS: %+v", ts)
|
||||
if ts.Entry == nil {
|
||||
continue
|
||||
}
|
||||
//log.Printf("Entry: %+v", ts.Entry)
|
||||
if ts.Entry.Strategy == LCR_STRATEGY_STATIC {
|
||||
for _, supplier := range ts.Entry.GetParams() {
|
||||
lcrCD := cd.Clone()
|
||||
lcrCD.Subject = supplier
|
||||
if cd.account, err = accountingStorage.GetAccount(cd.GetAccountKey()); err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if cc, err := lcrCD.debit(cd.account, true, true); err != nil || cc == nil {
|
||||
ts.SupplierCosts = append(ts.SupplierCosts, &LCRSupplierCost{
|
||||
Supplier: supplier,
|
||||
Error: err,
|
||||
})
|
||||
} else {
|
||||
ts.SupplierCosts = append(ts.SupplierCosts, &LCRSupplierCost{
|
||||
Supplier: supplier,
|
||||
Cost: cc.Cost,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
// find rating profiles
|
||||
ratingProfileSearchKey := utils.ConcatenatedKey(lcr.Direction, lcr.Tenant, lcrCost.Entry.RPCategory)
|
||||
suppliers := cache2go.GetEntriesKeys(RATING_PROFILE_PREFIX + ratingProfileSearchKey)
|
||||
for _, supplier := range suppliers {
|
||||
split := strings.Split(supplier, ":")
|
||||
supplier = split[len(split)-1]
|
||||
lcrCD := cd.Clone()
|
||||
lcrCD.Subject = supplier
|
||||
if cd.account, err = accountingStorage.GetAccount(cd.GetAccountKey()); err != nil {
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
// find rating profiles
|
||||
ratingProfileSearchKey := utils.ConcatenatedKey(lcr.Direction, lcr.Tenant, ts.Entry.RPCategory)
|
||||
suppliers := cache2go.GetEntriesKeys(RATING_PROFILE_PREFIX + ratingProfileSearchKey)
|
||||
for _, supplier := range suppliers {
|
||||
split := strings.Split(supplier, ":")
|
||||
supplier = split[len(split)-1]
|
||||
lcrCD := cd.Clone()
|
||||
lcrCD.Subject = supplier
|
||||
if cd.account, err = accountingStorage.GetAccount(cd.GetAccountKey()); err != nil {
|
||||
continue
|
||||
}
|
||||
var asr, acd float64
|
||||
var qosSortParams []string
|
||||
if ts.Entry.Strategy == LCR_STRATEGY_QOS || ts.Entry.Strategy == LCR_STRATEGY_QOS_WITH_THRESHOLD {
|
||||
rpfKey := utils.ConcatenatedKey(ratingProfileSearchKey, supplier)
|
||||
if rpf, err := dataStorage.GetRatingProfile(rpfKey, false); err == nil || rpf != nil {
|
||||
rpf.RatingPlanActivations.Sort()
|
||||
activeRas := rpf.RatingPlanActivations.GetActiveForCall(cd)
|
||||
var cdrStatsQueueIds []string
|
||||
for _, ra := range activeRas {
|
||||
for _, qId := range ra.CdrStatQueueIds {
|
||||
if qId != "" {
|
||||
cdrStatsQueueIds = append(cdrStatsQueueIds, qId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var asrValues sort.Float64Slice
|
||||
var acdValues sort.Float64Slice
|
||||
for _, qId := range cdrStatsQueueIds {
|
||||
statValues := make(map[string]float64)
|
||||
if err := stats.GetValues(qId, &statValues); err != nil {
|
||||
Logger.Warning(fmt.Sprintf("Error getting stats values for queue id %s: %v", qId, err))
|
||||
}
|
||||
if asr, exists := statValues[ASR]; exists {
|
||||
asrValues = append(asrValues, asr)
|
||||
}
|
||||
if acd, exists := statValues[ACD]; exists {
|
||||
acdValues = append(acdValues, acd)
|
||||
}
|
||||
}
|
||||
asrValues.Sort()
|
||||
acdValues.Sort()
|
||||
asr = utils.Avg(asrValues)
|
||||
acd = utils.Avg(acdValues)
|
||||
if ts.Entry.Strategy == LCR_STRATEGY_QOS_WITH_THRESHOLD {
|
||||
qosSortParams = ts.Entry.GetParams()
|
||||
}
|
||||
if ts.Entry.Strategy == LCR_STRATEGY_QOS_WITH_THRESHOLD {
|
||||
// filter suppliers by qos thresholds
|
||||
asrMin, asrMax, acdMin, acdMax := ts.Entry.GetQOSLimits()
|
||||
// skip current supplier if off limits
|
||||
if asrMin > 0 && asrValues[0] < asrMin {
|
||||
continue
|
||||
}
|
||||
if asrMax > 0 && asrValues[len(asrValues)-1] > asrMax {
|
||||
continue
|
||||
}
|
||||
if acdMin > 0 && acdValues[0] < float64(acdMin) {
|
||||
continue
|
||||
}
|
||||
if acdMax > 0 && acdValues[len(acdValues)-1] > float64(acdMax) {
|
||||
continue
|
||||
var asr, acd float64
|
||||
var qosSortParams []string
|
||||
if lcrCost.Entry.Strategy == LCR_STRATEGY_QOS || lcrCost.Entry.Strategy == LCR_STRATEGY_QOS_WITH_THRESHOLD {
|
||||
rpfKey := utils.ConcatenatedKey(ratingProfileSearchKey, supplier)
|
||||
if rpf, err := dataStorage.GetRatingProfile(rpfKey, false); err == nil || rpf != nil {
|
||||
rpf.RatingPlanActivations.Sort()
|
||||
activeRas := rpf.RatingPlanActivations.GetActiveForCall(cd)
|
||||
var cdrStatsQueueIds []string
|
||||
for _, ra := range activeRas {
|
||||
for _, qId := range ra.CdrStatQueueIds {
|
||||
if qId != "" {
|
||||
cdrStatsQueueIds = append(cdrStatsQueueIds, qId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if cc, err := lcrCD.debit(cd.account, true, true); err != nil || cc == nil {
|
||||
ts.SupplierCosts = append(ts.SupplierCosts, &LCRSupplierCost{
|
||||
Supplier: supplier,
|
||||
Error: err,
|
||||
})
|
||||
} else {
|
||||
ts.SupplierCosts = append(ts.SupplierCosts, &LCRSupplierCost{
|
||||
Supplier: supplier,
|
||||
Cost: cc.Cost,
|
||||
QOS: map[string]float64{
|
||||
"ASR": asr,
|
||||
"ACD": acd,
|
||||
},
|
||||
qosSortParams: qosSortParams,
|
||||
})
|
||||
|
||||
var asrValues sort.Float64Slice
|
||||
var acdValues sort.Float64Slice
|
||||
for _, qId := range cdrStatsQueueIds {
|
||||
statValues := make(map[string]float64)
|
||||
if err := stats.GetValues(qId, &statValues); err != nil {
|
||||
Logger.Warning(fmt.Sprintf("Error getting stats values for queue id %s: %v", qId, err))
|
||||
}
|
||||
if asr, exists := statValues[ASR]; exists {
|
||||
asrValues = append(asrValues, asr)
|
||||
}
|
||||
if acd, exists := statValues[ACD]; exists {
|
||||
acdValues = append(acdValues, acd)
|
||||
}
|
||||
}
|
||||
asrValues.Sort()
|
||||
acdValues.Sort()
|
||||
asr = utils.Avg(asrValues)
|
||||
acd = utils.Avg(acdValues)
|
||||
if lcrCost.Entry.Strategy == LCR_STRATEGY_QOS_WITH_THRESHOLD {
|
||||
qosSortParams = lcrCost.Entry.GetParams()
|
||||
}
|
||||
if lcrCost.Entry.Strategy == LCR_STRATEGY_QOS_WITH_THRESHOLD {
|
||||
// filter suppliers by qos thresholds
|
||||
asrMin, asrMax, acdMin, acdMax := lcrCost.Entry.GetQOSLimits()
|
||||
// skip current supplier if off limits
|
||||
if asrMin > 0 && asrValues[0] < asrMin {
|
||||
continue
|
||||
}
|
||||
if asrMax > 0 && asrValues[len(asrValues)-1] > asrMax {
|
||||
continue
|
||||
}
|
||||
if acdMin > 0 && acdValues[0] < float64(acdMin) {
|
||||
continue
|
||||
}
|
||||
if acdMax > 0 && acdValues[len(acdValues)-1] > float64(acdMax) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// sort according to strategy
|
||||
ts.Sort()
|
||||
if cc, err := lcrCD.debit(cd.account, true, true); err != nil || cc == nil {
|
||||
lcrCost.SupplierCosts = append(lcrCost.SupplierCosts, &LCRSupplierCost{
|
||||
Supplier: supplier,
|
||||
Error: err,
|
||||
})
|
||||
} else {
|
||||
lcrCost.SupplierCosts = append(lcrCost.SupplierCosts, &LCRSupplierCost{
|
||||
Supplier: supplier,
|
||||
Cost: cc.Cost,
|
||||
Duration: cc.GetDuration().String(),
|
||||
QOS: map[string]float64{
|
||||
"ASR": asr,
|
||||
"ACD": acd,
|
||||
},
|
||||
qosSortParams: qosSortParams,
|
||||
})
|
||||
}
|
||||
}
|
||||
// sort according to strategy
|
||||
lcrCost.Sort()
|
||||
}
|
||||
return lcrCost, nil
|
||||
}
|
||||
|
||||
@@ -57,10 +57,7 @@ type LCREntry struct {
|
||||
precision int
|
||||
}
|
||||
|
||||
type LCRCost []*LCRTimeSpan
|
||||
|
||||
type LCRTimeSpan struct {
|
||||
StartTime time.Time
|
||||
type LCRCost struct {
|
||||
SupplierCosts []*LCRSupplierCost
|
||||
Entry *LCREntry
|
||||
}
|
||||
@@ -68,6 +65,7 @@ type LCRTimeSpan struct {
|
||||
type LCRSupplierCost struct {
|
||||
Supplier string
|
||||
Cost float64
|
||||
Duration string
|
||||
Error error
|
||||
QOS map[string]float64
|
||||
qosSortParams []string
|
||||
@@ -182,14 +180,14 @@ func (lcra *LCRActivation) GetLCREntryForPrefix(destination string) *LCREntry {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (lts *LCRTimeSpan) Sort() {
|
||||
switch lts.Entry.Strategy {
|
||||
func (lc *LCRCost) Sort() {
|
||||
switch lc.Entry.Strategy {
|
||||
case LCR_STRATEGY_LOWEST:
|
||||
sort.Sort(LowestSupplierCostSorter(lts.SupplierCosts))
|
||||
sort.Sort(LowestSupplierCostSorter(lc.SupplierCosts))
|
||||
case LCR_STRATEGY_HIGHEST:
|
||||
sort.Sort(HighestSupplierCostSorter(lts.SupplierCosts))
|
||||
sort.Sort(HighestSupplierCostSorter(lc.SupplierCosts))
|
||||
case LCR_STRATEGY_QOS:
|
||||
sort.Sort(QOSSorter(lts.SupplierCosts))
|
||||
sort.Sort(QOSSorter(lc.SupplierCosts))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
package engine
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"sort"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -182,10 +184,10 @@ func TestLcrGet(t *testing.T) {
|
||||
Account: "rif",
|
||||
Subject: "rif",
|
||||
}
|
||||
lcrs, err := cd.GetLCR(nil)
|
||||
//lcr, _ := json.Marshal(lcrs[0])
|
||||
//log.Print("LCR: ", string(lcr))
|
||||
if err != nil || len(lcrs) != 1 {
|
||||
t.Errorf("Bad lcr: %+v, %v", lcrs, err)
|
||||
lcr, err := cd.GetLCR(nil)
|
||||
jsn, _ := json.Marshal(lcr)
|
||||
log.Print("LCR: ", string(jsn))
|
||||
if err != nil || lcr == nil {
|
||||
t.Errorf("Bad lcr: %+v, %v", lcr, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -237,7 +237,7 @@ func (rs *Responder) ProcessCdr(cdr *StoredCdr, reply *string) error {
|
||||
|
||||
func (rs *Responder) GetLCR(cd *CallDescriptor, reply *LCRCost) error {
|
||||
lcrCost, err := cd.GetLCR(rs.Stats)
|
||||
*reply = lcrCost
|
||||
*reply = *lcrCost
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user