From 4bda71a660b8151e45f1022e69ced6d83eccfc08 Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Wed, 8 Apr 2015 09:59:39 +0300 Subject: [PATCH] only one lcr entry per cd --- docs/lcr.rst | 9 +- engine/calldesc.go | 227 ++++++++++++++++++++++---------------------- engine/lcr.go | 16 ++-- engine/lcr_test.go | 12 ++- engine/responder.go | 2 +- 5 files changed, 130 insertions(+), 136 deletions(-) diff --git a/docs/lcr.rst b/docs/lcr.rst index 4bd8df0ce..527c53bfd 100644 --- a/docs/lcr.rst +++ b/docs/lcr.rst @@ -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 diff --git a/engine/calldesc.go b/engine/calldesc.go index d3a5c5197..b7c4eb5e4 100644 --- a/engine/calldesc.go +++ b/engine/calldesc.go @@ -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 } diff --git a/engine/lcr.go b/engine/lcr.go index 7a5173f6f..f3206dda1 100644 --- a/engine/lcr.go +++ b/engine/lcr.go @@ -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)) } } diff --git a/engine/lcr_test.go b/engine/lcr_test.go index 0f8eb2fd9..3cd7d43a2 100644 --- a/engine/lcr_test.go +++ b/engine/lcr_test.go @@ -19,6 +19,8 @@ along with this program. If not, see 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) } } diff --git a/engine/responder.go b/engine/responder.go index cdfcd3b66..933701ac7 100644 --- a/engine/responder.go +++ b/engine/responder.go @@ -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 }