only one lcr entry per cd

This commit is contained in:
Radu Ioan Fericean
2015-04-08 09:59:39 +03:00
parent 34ca406e88
commit 4bda71a660
5 changed files with 130 additions and 136 deletions

View File

@@ -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

View File

@@ -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
}

View File

@@ -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))
}
}

View File

@@ -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)
}
}

View File

@@ -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
}