mirror of
https://github.com/cgrates/cgrates.git
synced 2026-02-20 14:48:43 +05:00
Merge branch 'master' of https://github.com/cgrates/cgrates
This commit is contained in:
@@ -144,6 +144,26 @@ func (self *ApierV1) ExecuteAction(attr *AttrExecuteAction, reply *string) error
|
||||
return nil
|
||||
}
|
||||
|
||||
type AttrSetRatingPlan struct {
|
||||
TPid string
|
||||
RatingPlanId string
|
||||
}
|
||||
|
||||
// Process dependencies and load a specific rating plan from storDb into dataDb.
|
||||
func (self *ApierV1) SetRatingPlan(attrs AttrSetRatingPlan, reply *string) error {
|
||||
if missing := utils.MissingStructFields(&attrs, []string{"TPid", "RatingPlanId"}); len(missing) != 0 {
|
||||
return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing)
|
||||
}
|
||||
dbReader := engine.NewDbReader(self.StorDb, self.DataDb, attrs.TPid)
|
||||
|
||||
if err := dbReader.LoadRatingPlanByTag(attrs.RatingPlanId); err != nil {
|
||||
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
|
||||
}
|
||||
|
||||
*reply = OK
|
||||
return nil
|
||||
}
|
||||
|
||||
type AttrSetRatingProfile struct {
|
||||
TPid string
|
||||
RatingProfileId string
|
||||
|
||||
@@ -39,7 +39,8 @@ func (self *ApierV1) SetTPRatingProfile(attrs utils.TPRatingProfile, reply *stri
|
||||
}
|
||||
rps := make([]*engine.RatingProfile, len(attrs.RatingActivations))
|
||||
for idx, ra := range attrs.RatingActivations {
|
||||
rps[idx] = &engine.RatingProfile{Tag: attrs.RatingProfileId,
|
||||
rps[idx] = &engine.RatingProfile{
|
||||
Tag: attrs.RatingProfileId,
|
||||
Tenant: attrs.Tenant,
|
||||
TOR: attrs.TOR,
|
||||
Direction: attrs.Direction,
|
||||
|
||||
@@ -25,7 +25,6 @@ import (
|
||||
"github.com/cgrates/cgrates/history"
|
||||
"github.com/cgrates/cgrates/utils"
|
||||
"log/syslog"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -107,7 +106,7 @@ type CallDescriptor struct {
|
||||
CallDuration time.Duration // the call duration so far (till TimeEnd)
|
||||
Amount float64
|
||||
FallbackSubject string // the subject to check for destination if not found on primary subject
|
||||
RatingInfos []*RatingInfo
|
||||
RatingInfos RatingInfos
|
||||
Increments Increments
|
||||
userBalance *UserBalance
|
||||
}
|
||||
@@ -137,55 +136,84 @@ func (cd *CallDescriptor) getUserBalance() (ub *UserBalance, err error) {
|
||||
/*
|
||||
Restores the activation periods for the specified prefix from storage.
|
||||
*/
|
||||
func (cd *CallDescriptor) LoadRatingPlans() (destPrefixes []string, matchedSubject string, err error) {
|
||||
matchedSubject = cd.GetKey()
|
||||
/*if val, err := cache2go.GetXCached(cd.GetKey() + cd.Destination); err == nil {
|
||||
xaps := val.(xCachedRatingPlans)
|
||||
cd.RatingPlans = xaps.aps
|
||||
return xaps.destPrefix, matchedSubject, nil
|
||||
}*/
|
||||
destPrefixes, matchedSubject, values, err := cd.getRatingPlansForPrefix(cd.GetKey(), 1)
|
||||
func (cd *CallDescriptor) LoadRatingPlans() (err error) {
|
||||
err = cd.getRatingPlansForPrefix(cd.GetKey(), 1)
|
||||
if err != nil {
|
||||
// try general fallback
|
||||
fallbackKey := fmt.Sprintf("%s:%s:%s:%s", cd.Direction, cd.Tenant, cd.TOR, FALLBACK_SUBJECT)
|
||||
// use the default subject
|
||||
destPrefixes, matchedSubject, values, err = cd.getRatingPlansForPrefix(fallbackKey, 1)
|
||||
err = cd.getRatingPlansForPrefix(fallbackKey, 1)
|
||||
}
|
||||
//load the rating plans
|
||||
if err == nil && len(values) > 0 {
|
||||
/*
|
||||
xaps := xCachedRatingPlans{destPrefix, values, new(cache2go.XEntry)}
|
||||
xaps.XCache(cd.GetKey()+cd.Destination, debitPeriod+5*time.Second, xaps)
|
||||
*/
|
||||
cd.RatingInfos = values
|
||||
if err != nil || !cd.continousRatingInfos() {
|
||||
err = errors.New("Could not determine rating plans for call")
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (cd *CallDescriptor) getRatingPlansForPrefix(key string, recursionDepth int) (foundPrefixes []string, matchedSubject string, ris []*RatingInfo, err error) {
|
||||
matchedSubject = key
|
||||
func (cd *CallDescriptor) getRatingPlansForPrefix(key string, recursionDepth int) (err error) {
|
||||
if recursionDepth > RECURSION_MAX_DEPTH {
|
||||
err = errors.New("Max fallback recursion depth reached!" + key)
|
||||
return
|
||||
}
|
||||
rp, err := storageGetter.GetRatingProfile(key)
|
||||
if err != nil || rp == nil {
|
||||
return nil, "", nil, err
|
||||
return err
|
||||
}
|
||||
foundPrefixes, ris, err = rp.GetRatingPlansForPrefix(cd)
|
||||
if err != nil {
|
||||
if rp.FallbackKey != "" {
|
||||
if err = rp.GetRatingPlansForPrefix(cd); err != nil {
|
||||
// try rating profile fallback
|
||||
if len(rp.FallbackKeys) > 0 {
|
||||
recursionDepth++
|
||||
for _, fbk := range strings.Split(rp.FallbackKey, FALLBACK_SEP) {
|
||||
if destPrefix, matchedSubject, values, err := cd.getRatingPlansForPrefix(fbk, recursionDepth); err == nil {
|
||||
return destPrefix, matchedSubject, values, err
|
||||
for _, fbk := range rp.FallbackKeys {
|
||||
if err := cd.getRatingPlansForPrefix(fbk, recursionDepth); err == nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// checks if there is rating info for the entire call duration
|
||||
func (cd *CallDescriptor) continousRatingInfos() bool {
|
||||
if len(cd.RatingInfos) == 0 || cd.RatingInfos[0].ActivationTime.After(cd.TimeStart) {
|
||||
return false
|
||||
}
|
||||
for _, ri := range cd.RatingInfos {
|
||||
if ri.RateIntervals == nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// adds a rating infos only if that call period is not already covered
|
||||
// returns true if added
|
||||
func (cd *CallDescriptor) addRatingInfos(ris RatingInfos) bool {
|
||||
if len(cd.RatingInfos) == 0 {
|
||||
cd.RatingInfos = append(cd.RatingInfos, ris...)
|
||||
return true
|
||||
}
|
||||
cd.RatingInfos.Sort()
|
||||
// check if we dont have the start covered
|
||||
if cd.RatingInfos[0].ActivationTime.After(cd.TimeStart) {
|
||||
if ris[0].ActivationTime.Before(cd.RatingInfos[0].ActivationTime) {
|
||||
cd.RatingInfos = append(cd.RatingInfos, ris[0])
|
||||
cd.RatingInfos.Sort()
|
||||
}
|
||||
}
|
||||
for _, ri := range cd.RatingInfos {
|
||||
if ri.RateIntervals == nil {
|
||||
for i, new_ri := range ris {
|
||||
_ = i
|
||||
_ = new_ri
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/*
|
||||
Constructs the key for the storage lookup.
|
||||
The prefixLen is limiting the length of the destination prefix.
|
||||
@@ -307,7 +335,7 @@ func (cd *CallDescriptor) roundTimeSpansToIncrement(timespans []*TimeSpan) []*Ti
|
||||
Creates a CallCost structure with the cost information calculated for the received CallDescriptor.
|
||||
*/
|
||||
func (cd *CallDescriptor) GetCost() (*CallCost, error) {
|
||||
destPrefix, matchedSubject, err := cd.LoadRatingPlans()
|
||||
err := cd.LoadRatingPlans()
|
||||
if err != nil {
|
||||
Logger.Err(fmt.Sprintf("error getting cost for key %v: %v", cd.GetUserBalanceKey(), err))
|
||||
return &CallCost{Cost: -1}, err
|
||||
@@ -325,17 +353,19 @@ func (cd *CallDescriptor) GetCost() (*CallCost, error) {
|
||||
}
|
||||
// global rounding
|
||||
cost = utils.Round(cost, roundingDecimals, roundingMethod)
|
||||
startIndex := len(fmt.Sprintf("%s:%s:%s:", cd.Direction, cd.Tenant, cd.TOR))
|
||||
//startIndex := len(fmt.Sprintf("%s:%s:%s:", cd.Direction, cd.Tenant, cd.TOR))
|
||||
cc := &CallCost{
|
||||
Direction: cd.Direction,
|
||||
TOR: cd.TOR,
|
||||
Tenant: cd.Tenant,
|
||||
Subject: matchedSubject[startIndex:],
|
||||
Account: cd.Account,
|
||||
Destination: strings.Join(destPrefix, ";"),
|
||||
Cost: cost,
|
||||
ConnectFee: connectionFee,
|
||||
Timespans: timespans}
|
||||
Direction: cd.Direction,
|
||||
TOR: cd.TOR,
|
||||
Tenant: cd.Tenant,
|
||||
// TODO, FIXME: find out where to put matched subject
|
||||
//Subject: matchedSubject[startIndex:],
|
||||
Account: cd.Account,
|
||||
// TODO, FIXME: find out where to put matched prfixes
|
||||
//Destination: strings.Join(destPrefix, ";"),
|
||||
Cost: cost,
|
||||
ConnectFee: connectionFee,
|
||||
Timespans: timespans}
|
||||
//Logger.Info(fmt.Sprintf("<Rater> Get Cost: %s => %v", cd.GetKey(), cc))
|
||||
return cc, err
|
||||
}
|
||||
@@ -350,7 +380,7 @@ func (cd *CallDescriptor) GetMaxSessionTime(startTime time.Time) (seconds float6
|
||||
if cd.CallDuration == 0 {
|
||||
cd.CallDuration = cd.TimeEnd.Sub(cd.TimeStart)
|
||||
}
|
||||
_, _, err = cd.LoadRatingPlans()
|
||||
err = cd.LoadRatingPlans()
|
||||
if err != nil {
|
||||
Logger.Err(fmt.Sprintf("error getting cost for key %v: %v", cd.GetUserBalanceKey(), err))
|
||||
return 0, err
|
||||
|
||||
@@ -357,17 +357,20 @@ func (csvr *CSVReader) LoadRatingProfiles() (err error) {
|
||||
return errors.New(fmt.Sprintf("Could not load rating plans for tag: %v", record[5]))
|
||||
}
|
||||
}
|
||||
rp.RatingPlanActivations = append(rp.RatingPlanActivations, &RatingPlanActivation{at, record[5]})
|
||||
rpa := &RatingPlanActivation{
|
||||
ActivationTime: at,
|
||||
RatingPlanId: record[5],
|
||||
}
|
||||
if fallbacksubject != "" {
|
||||
var sslice utils.StringSlice = rpa.FallbackKeys
|
||||
for _, fbs := range strings.Split(fallbacksubject, ";") {
|
||||
newKey := fmt.Sprintf("%s:%s:%s:%s", direction, tenant, tor, fbs)
|
||||
var sslice utils.StringSlice = strings.Split(rp.FallbackKey, ";")
|
||||
if !sslice.Contains(newKey) {
|
||||
rp.FallbackKey += newKey + ";"
|
||||
rpa.FallbackKeys = append(rpa.FallbackKeys, newKey)
|
||||
}
|
||||
}
|
||||
rp.FallbackKey = strings.TrimRight(rp.FallbackKey, ";")
|
||||
}
|
||||
rp.RatingPlanActivations = append(rp.RatingPlanActivations, rpa)
|
||||
csvr.ratingProfiles[rp.Id] = rp
|
||||
}
|
||||
return
|
||||
|
||||
@@ -560,11 +560,11 @@ func TestLoadRatingProfiles(t *testing.T) {
|
||||
}
|
||||
rp := csvr.ratingProfiles["*out:test:0:trp"]
|
||||
expected := &RatingProfile{
|
||||
Id: "*out:test:0:trp",
|
||||
FallbackKey: "*out:test:0:rif;*out:test:0:danb",
|
||||
Id: "*out:test:0:trp",
|
||||
RatingPlanActivations: RatingPlanActivations{&RatingPlanActivation{
|
||||
ActivationTime: time.Date(2013, 10, 1, 0, 0, 0, 0, time.UTC),
|
||||
RatingPlanId: "TDRT",
|
||||
FallbackKeys: []string{"*out:test:0:rif", "*out:test:0:danb"},
|
||||
}},
|
||||
}
|
||||
if !reflect.DeepEqual(rp, expected) {
|
||||
|
||||
@@ -167,9 +167,9 @@ func (dbr *DbReader) LoadDestinationRates() (err error) {
|
||||
}
|
||||
}
|
||||
if !destinationExists {
|
||||
if dbExists, err := dbr.dataDb.ExistsDestination(dr.DestinationsTag); err != nil {
|
||||
if dest, err := dbr.dataDb.GetDestination(dr.DestinationsTag); err != nil {
|
||||
return err
|
||||
} else if !dbExists {
|
||||
} else if dest == nil {
|
||||
return errors.New(fmt.Sprintf("Could not get destination for tag %v", dr.DestinationsTag))
|
||||
}
|
||||
}
|
||||
@@ -218,20 +218,71 @@ func (dbr *DbReader) LoadRatingProfiles() error {
|
||||
}
|
||||
_, exists := dbr.ratingPlans[rp.DestRatesTimingTag]
|
||||
if !exists {
|
||||
if dbExists, err := dbr.dataDb.ExistsRatingPlan(rp.DestRatesTimingTag); err != nil {
|
||||
if rpl, err := dbr.dataDb.GetRatingPlan(rp.DestRatesTimingTag); err != nil {
|
||||
return err
|
||||
} else if !dbExists {
|
||||
} else if rpl == nil {
|
||||
return errors.New(fmt.Sprintf("Could not load rating plans for tag: %v", rp.DestRatesTimingTag))
|
||||
}
|
||||
}
|
||||
rp.RatingPlanActivations = append(rp.RatingPlanActivations, &RatingPlanActivation{at, rp.DestRatesTimingTag})
|
||||
rp.RatingPlanActivations = append(rp.RatingPlanActivations,
|
||||
&RatingPlanActivation{
|
||||
ActivationTime: at,
|
||||
RatingPlanId: rp.DestRatesTimingTag,
|
||||
FallbackKeys: rp.FallbackKeys,
|
||||
})
|
||||
dbr.ratingProfiles[rp.Id] = rp
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dbr *DbReader) LoadRatingPlanByTag(tag string) error {
|
||||
ratingPlan := &RatingPlan{}
|
||||
rps, err := dbr.storDb.GetTpRatingPlans(dbr.tpid, tag)
|
||||
if err != nil || len(rps) == 0 {
|
||||
return fmt.Errorf("No DestRateTimings profile with id %s: %v", tag, err)
|
||||
}
|
||||
for _, rp := range rps {
|
||||
|
||||
Logger.Debug(fmt.Sprintf("Rating Plan: %v", rp))
|
||||
tm, err := dbr.storDb.GetTpTimings(dbr.tpid, rp.TimingTag)
|
||||
Logger.Debug(fmt.Sprintf("Timing: %v", tm))
|
||||
if err != nil || len(tm) == 0 {
|
||||
return fmt.Errorf("No Timings profile with id %s: %v", rp.TimingTag, err)
|
||||
}
|
||||
rp.timing = tm[rp.TimingTag]
|
||||
drm, err := dbr.storDb.GetTpDestinationRates(dbr.tpid, rp.DestinationRatesTag)
|
||||
if err != nil || len(drm) == 0 {
|
||||
return fmt.Errorf("No DestinationRates profile with id %s: %v", rp.DestinationRatesTag, err)
|
||||
}
|
||||
for _, drate := range drm[rp.DestinationRatesTag] {
|
||||
Logger.Debug(fmt.Sprintf("Destination rate: %v", drate))
|
||||
rt, err := dbr.storDb.GetTpRates(dbr.tpid, drate.RateTag)
|
||||
if err != nil || len(rt) == 0 {
|
||||
return fmt.Errorf("No Rates profile with id %s: %v", drate.RateTag, err)
|
||||
}
|
||||
Logger.Debug(fmt.Sprintf("Rate: %v", rt))
|
||||
drate.rates = rt[drate.RateTag]
|
||||
ratingPlan.AddRateInterval(drate.DestinationsTag, rp.GetRateInterval(drate))
|
||||
|
||||
dms, err := dbr.storDb.GetTpDestinations(dbr.tpid, drate.DestinationsTag)
|
||||
if err != nil || len(dms) == 0 {
|
||||
if dest, err := dbr.dataDb.GetDestination(drate.DestinationsTag); err != nil {
|
||||
return err
|
||||
} else if dest == nil {
|
||||
return fmt.Errorf("Could not get destination for tag %v", drate.DestinationsTag)
|
||||
}
|
||||
}
|
||||
Logger.Debug(fmt.Sprintf("Tag: %s Destinations: %v", drate.DestinationsTag, dms))
|
||||
for _, destination := range dms {
|
||||
Logger.Debug(fmt.Sprintf("Destination: %v", destination))
|
||||
dbr.dataDb.SetDestination(destination)
|
||||
}
|
||||
}
|
||||
}
|
||||
return dbr.dataDb.SetRatingPlan(ratingPlan)
|
||||
}
|
||||
|
||||
func (dbr *DbReader) LoadRatingProfileByTag(tag string) error {
|
||||
ratingPlans := make(map[string]*RatingPlan)
|
||||
resultRatingProfile := &RatingProfile{}
|
||||
rpm, err := dbr.storDb.GetTpRatingProfiles(dbr.tpid, tag)
|
||||
if err != nil || len(rpm) == 0 {
|
||||
@@ -239,68 +290,12 @@ func (dbr *DbReader) LoadRatingProfileByTag(tag string) error {
|
||||
}
|
||||
for _, ratingProfile := range rpm {
|
||||
Logger.Debug(fmt.Sprintf("Rating profile: %v", rpm))
|
||||
resultRatingProfile.FallbackKey = ratingProfile.FallbackKey // it will be the last fallback key
|
||||
resultRatingProfile.Id = ratingProfile.Id // idem
|
||||
_, err := utils.ParseDate(ratingProfile.ActivationTime)
|
||||
resultRatingProfile.Id = ratingProfile.Id // idem
|
||||
at, err := utils.ParseDate(ratingProfile.ActivationTime)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Cannot parse activation time from %v", ratingProfile.ActivationTime)
|
||||
}
|
||||
drtm, err := dbr.storDb.GetTpRatingPlans(dbr.tpid, ratingProfile.DestRatesTimingTag)
|
||||
if err != nil || len(drtm) == 0 { // rating plan not found in storDb, check dataDb and if there will use that one
|
||||
if dbExists, err := dbr.dataDb.ExistsRatingPlan(ratingProfile.DestRatesTimingTag); err != nil {
|
||||
return err
|
||||
} else if !dbExists {
|
||||
return fmt.Errorf("No DestRateTimings profile with id %s: %v", ratingProfile.DestRatesTimingTag, err)
|
||||
}
|
||||
}
|
||||
for _, destRateTiming := range drtm {
|
||||
Logger.Debug(fmt.Sprintf("Destination rate timing: %v", rpm))
|
||||
tm, err := dbr.storDb.GetTpTimings(dbr.tpid, destRateTiming.TimingTag)
|
||||
Logger.Debug(fmt.Sprintf("Timing: %v", rpm))
|
||||
if err != nil || len(tm) == 0 {
|
||||
return fmt.Errorf("No Timings profile with id %s: %v", destRateTiming.TimingTag, err)
|
||||
}
|
||||
destRateTiming.timing = tm[destRateTiming.TimingTag]
|
||||
drm, err := dbr.storDb.GetTpDestinationRates(dbr.tpid, destRateTiming.DestinationRatesTag)
|
||||
if err != nil || len(drm) == 0 {
|
||||
return fmt.Errorf("No DestinationRates profile with id %s: %v", destRateTiming.DestinationRatesTag, err)
|
||||
}
|
||||
for _, drate := range drm[destRateTiming.DestinationRatesTag] {
|
||||
Logger.Debug(fmt.Sprintf("Destination rate: %v", rpm))
|
||||
rt, err := dbr.storDb.GetTpRates(dbr.tpid, drate.RateTag)
|
||||
if err != nil || len(rt) == 0 {
|
||||
return fmt.Errorf("No Rates profile with id %s: %v", drate.RateTag, err)
|
||||
}
|
||||
Logger.Debug(fmt.Sprintf("Rate: %v", rpm))
|
||||
drate.rates = rt[drate.RateTag]
|
||||
if _, exists := ratingPlans[destRateTiming.Tag]; !exists {
|
||||
ratingPlans[destRateTiming.Tag] = &RatingPlan{}
|
||||
}
|
||||
ratingPlans[destRateTiming.Tag].AddRateInterval(drate.DestinationsTag, destRateTiming.GetRateInterval(drate))
|
||||
|
||||
at, err := utils.ParseDate(ratingProfile.ActivationTime)
|
||||
if err != nil {
|
||||
return errors.New(fmt.Sprintf("Cannot parse activation time from %v", ratingProfile.ActivationTime))
|
||||
}
|
||||
resultRatingProfile.RatingPlanActivations = append(resultRatingProfile.RatingPlanActivations, &RatingPlanActivation{at, ratingProfile.DestRatesTimingTag})
|
||||
dms, err := dbr.storDb.GetTpDestinations(dbr.tpid, drate.DestinationsTag)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Could not get destination id %s: %v", drate.DestinationsTag, err)
|
||||
} else if len(dms) == 0 {
|
||||
if dbExists, err := dbr.dataDb.ExistsDestination(drate.DestinationsTag); err != nil {
|
||||
return err
|
||||
} else if !dbExists {
|
||||
return fmt.Errorf("Could not get destination for tag %v", drate.DestinationsTag)
|
||||
}
|
||||
} else {
|
||||
Logger.Debug(fmt.Sprintf("Tag: %s Destinations: %v", drate.DestinationsTag, dms))
|
||||
for _, destination := range dms {
|
||||
Logger.Debug(fmt.Sprintf("Destination: %v", rpm))
|
||||
dbr.dataDb.SetDestination(destination)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
resultRatingProfile.RatingPlanActivations = append(resultRatingProfile.RatingPlanActivations, &RatingPlanActivation{at, ratingProfile.DestRatesTimingTag, ratingProfile.FallbackKeys})
|
||||
}
|
||||
return dbr.dataDb.SetRatingProfile(resultRatingProfile)
|
||||
}
|
||||
|
||||
@@ -35,8 +35,8 @@ func TestApRestoreFromStorage(t *testing.T) {
|
||||
Subject: "rif:from:tm",
|
||||
Destination: "49"}
|
||||
cd.LoadRatingPlans()
|
||||
if len(cd.RatingInfos) != 2 {
|
||||
t.Error("Error restoring activation periods: ", cd.RatingInfos)
|
||||
if len(cd.RatingInfos) != 1 {
|
||||
t.Errorf("Error restoring activation periods: %+v", cd.RatingInfos[0])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ func TestFallbackMultiple(t *testing.T) {
|
||||
Subject: "fall",
|
||||
Destination: "0723045"}
|
||||
cd.LoadRatingPlans()
|
||||
if len(cd.RatingInfos) != 2 {
|
||||
if len(cd.RatingInfos) != 1 {
|
||||
t.Errorf("Error restoring rating plans: %+v", cd.RatingInfos)
|
||||
}
|
||||
}
|
||||
@@ -200,6 +200,22 @@ func TestApAddRateIntervalGroups(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetActiveForCall(t *testing.T) {
|
||||
rpas := RatingPlanActivations{
|
||||
&RatingPlanActivation{ActivationTime: time.Date(2013, 1, 1, 0, 0, 0, 0, time.UTC)},
|
||||
&RatingPlanActivation{ActivationTime: time.Date(2013, 11, 12, 11, 40, 0, 0, time.UTC)},
|
||||
&RatingPlanActivation{ActivationTime: time.Date(2013, 11, 13, 0, 0, 0, 0, time.UTC)},
|
||||
}
|
||||
cd := &CallDescriptor{
|
||||
TimeStart: time.Date(2013, 11, 12, 11, 39, 0, 0, time.UTC),
|
||||
TimeEnd: time.Date(2013, 11, 12, 11, 45, 0, 0, time.UTC),
|
||||
}
|
||||
active := rpas.GetActiveForCall(cd)
|
||||
if len(active) != 2 {
|
||||
t.Errorf("Error getting active rating plans: %+v", active)
|
||||
}
|
||||
}
|
||||
|
||||
/**************************** Benchmarks *************************************/
|
||||
|
||||
func BenchmarkRatingPlanMarshalJson(b *testing.B) {
|
||||
|
||||
@@ -27,15 +27,16 @@ import (
|
||||
|
||||
type RatingProfile struct {
|
||||
Id string
|
||||
FallbackKey string // FallbackKey is used as complete combination of Tenant:TOR:Direction:Subject
|
||||
RatingPlanActivations RatingPlanActivations
|
||||
Tag, Tenant, TOR, Direction, Subject string // used only for loading
|
||||
DestRatesTimingTag, RatesFallbackSubject, ActivationTime string // used only for loading
|
||||
Tag, Tenant, TOR, Direction, Subject string // used only for loading
|
||||
DestRatesTimingTag, RatesFallbackSubject, ActivationTime string // used only for loading
|
||||
FallbackKeys []string // used only for loading
|
||||
}
|
||||
|
||||
type RatingPlanActivation struct {
|
||||
ActivationTime time.Time
|
||||
RatingPlanId string
|
||||
FallbackKeys []string
|
||||
}
|
||||
|
||||
func (rpa *RatingPlanActivation) Equal(orpa *RatingPlanActivation) bool {
|
||||
@@ -60,45 +61,84 @@ func (rpas RatingPlanActivations) Sort() {
|
||||
sort.Sort(rpas)
|
||||
}
|
||||
|
||||
func (rpas RatingPlanActivations) GetActiveForCall(cd *CallDescriptor) RatingPlanActivations {
|
||||
rpas.Sort()
|
||||
lastBeforeCallStart := 0
|
||||
firstAfterCallEnd := len(rpas)
|
||||
for index, rpa := range rpas {
|
||||
if rpa.ActivationTime.Before(cd.TimeStart) || rpa.ActivationTime.Equal(cd.TimeStart) {
|
||||
lastBeforeCallStart = index
|
||||
}
|
||||
if rpa.ActivationTime.After(cd.TimeEnd) {
|
||||
firstAfterCallEnd = index
|
||||
break
|
||||
}
|
||||
}
|
||||
return rpas[lastBeforeCallStart:firstAfterCallEnd]
|
||||
}
|
||||
|
||||
type RatingInfo struct {
|
||||
MatchedSubject string
|
||||
MatchedPrefix string
|
||||
ActivationTime time.Time
|
||||
RateIntervals RateIntervalList
|
||||
}
|
||||
|
||||
type RatingInfos []*RatingInfo
|
||||
|
||||
func (ris RatingInfos) Len() int {
|
||||
return len(ris)
|
||||
}
|
||||
|
||||
func (ris RatingInfos) Swap(i, j int) {
|
||||
ris[i], ris[j] = ris[j], ris[i]
|
||||
}
|
||||
|
||||
func (ris RatingInfos) Less(i, j int) bool {
|
||||
return ris[i].ActivationTime.Before(ris[j].ActivationTime)
|
||||
}
|
||||
|
||||
func (ris RatingInfos) Sort() {
|
||||
sort.Sort(ris)
|
||||
}
|
||||
|
||||
// TODO: what happens if there is no match for part of the call
|
||||
func (rp *RatingProfile) GetRatingPlansForPrefix(cd *CallDescriptor) (foundPrefixes []string, ris []*RatingInfo, err error) {
|
||||
rp.RatingPlanActivations.Sort()
|
||||
for _, rpa := range rp.RatingPlanActivations {
|
||||
if rpa.ActivationTime.Before(cd.TimeEnd) {
|
||||
rpl, err := storageGetter.GetRatingPlan(rpa.RatingPlanId)
|
||||
if err != nil || rpl == nil {
|
||||
func (rp *RatingProfile) GetRatingPlansForPrefix(cd *CallDescriptor) (err error) {
|
||||
var ris RatingInfos
|
||||
for _, rpa := range rp.RatingPlanActivations.GetActiveForCall(cd) {
|
||||
rpl, err := storageGetter.GetRatingPlan(rpa.RatingPlanId)
|
||||
if err != nil || rpl == nil {
|
||||
Logger.Err(fmt.Sprintf("Error checking destination: %v", err))
|
||||
continue
|
||||
}
|
||||
bestPrecision := 0
|
||||
var rps RateIntervalList
|
||||
for dId, _ := range rpl.DestinationRates {
|
||||
//precision, err := storageGetter.DestinationContainsPrefix(dId, cd.Destination)
|
||||
d, err := storageGetter.GetDestination(dId)
|
||||
if err != nil {
|
||||
Logger.Err(fmt.Sprintf("Error checking destination: %v", err))
|
||||
continue
|
||||
}
|
||||
bestPrecision := 0
|
||||
var rps RateIntervalList
|
||||
for dId, _ := range rpl.DestinationRates {
|
||||
//precision, err := storageGetter.DestinationContainsPrefix(dId, cd.Destination)
|
||||
d, err := storageGetter.GetDestination(dId)
|
||||
if err != nil {
|
||||
Logger.Err(fmt.Sprintf("Error checking destination: %v", err))
|
||||
continue
|
||||
}
|
||||
precision := d.containsPrefix(cd.Destination)
|
||||
if precision > bestPrecision {
|
||||
bestPrecision = precision
|
||||
rps = rpl.RateIntervalList(dId)
|
||||
}
|
||||
precision := d.containsPrefix(cd.Destination)
|
||||
if precision > bestPrecision {
|
||||
bestPrecision = precision
|
||||
rps = rpl.RateIntervalList(dId)
|
||||
}
|
||||
if bestPrecision > 0 {
|
||||
ris = append(ris, &RatingInfo{rpa.ActivationTime, rps})
|
||||
foundPrefixes = append(foundPrefixes, cd.Destination[:bestPrecision])
|
||||
}
|
||||
if bestPrecision > 0 {
|
||||
ris = append(ris, &RatingInfo{rp.Id, cd.Destination[:bestPrecision], rpa.ActivationTime, rps})
|
||||
} else {
|
||||
// mark the end of previous!
|
||||
if len(cd.RatingInfos) > 0 {
|
||||
ris = append(ris, &RatingInfo{"", "", rpa.ActivationTime, nil})
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(ris) > 0 {
|
||||
return foundPrefixes, ris, nil
|
||||
cd.addRatingInfos(ris)
|
||||
return
|
||||
}
|
||||
|
||||
return nil, nil, errors.New("not found")
|
||||
return errors.New("not found")
|
||||
}
|
||||
|
||||
@@ -65,13 +65,11 @@ type DataStorage interface {
|
||||
PreCache([]string, []string) error
|
||||
GetRatingPlan(string) (*RatingPlan, error)
|
||||
SetRatingPlan(*RatingPlan) error
|
||||
ExistsRatingPlan(string) (bool, error)
|
||||
GetRatingProfile(string) (*RatingProfile, error)
|
||||
SetRatingProfile(*RatingProfile) error
|
||||
GetDestination(string) (*Destination, error)
|
||||
// DestinationContainsPrefix(string, string) (int, error)
|
||||
SetDestination(*Destination) error
|
||||
ExistsDestination(string) (bool, error)
|
||||
GetActions(string) (Actions, error)
|
||||
SetActions(string, Actions) error
|
||||
GetUserBalance(string) (*UserBalance, error)
|
||||
|
||||
@@ -83,11 +83,6 @@ func (ms *MapStorage) SetRatingPlan(rp *RatingPlan) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (ms *MapStorage) ExistsRatingPlan(rpId string) (bool, error) {
|
||||
_, exists := ms.dict[RATING_PLAN_PREFIX+rpId]
|
||||
return exists, nil
|
||||
}
|
||||
|
||||
func (ms *MapStorage) GetRatingProfile(key string) (rp *RatingProfile, err error) {
|
||||
if values, ok := ms.dict[RATING_PROFILE_PREFIX+key]; ok {
|
||||
rp = new(RatingProfile)
|
||||
@@ -142,11 +137,6 @@ func (ms *MapStorage) SetDestination(dest *Destination) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (ms *MapStorage) ExistsDestination(destId string) (bool, error) {
|
||||
_, exists := ms.dict[DESTINATION_PREFIX+destId]
|
||||
return exists, nil
|
||||
}
|
||||
|
||||
func (ms *MapStorage) GetActions(key string) (as Actions, err error) {
|
||||
if values, ok := ms.dict[ACTION_PREFIX+key]; ok {
|
||||
err = ms.ms.Unmarshal(values, &as)
|
||||
|
||||
@@ -141,10 +141,6 @@ func (rs *RedisStorage) SetRatingPlan(rp *RatingPlan) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (rs *RedisStorage) ExistsRatingPlan(rpId string) (bool, error) {
|
||||
return rs.db.Exists(RATING_PLAN_PREFIX + rpId)
|
||||
}
|
||||
|
||||
func (rs *RedisStorage) GetRatingProfile(key string) (rp *RatingProfile, err error) {
|
||||
var values string
|
||||
if values, err = rs.db.Get(RATING_PROFILE_PREFIX + key); err == nil {
|
||||
@@ -219,10 +215,6 @@ func (rs *RedisStorage) SetDestination(dest *Destination) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (rs *RedisStorage) ExistsDestination(destId string) (bool, error) {
|
||||
return rs.db.Exists(DESTINATION_PREFIX+destId)
|
||||
}
|
||||
|
||||
func (rs *RedisStorage) GetActions(key string) (as Actions, err error) {
|
||||
var values string
|
||||
if values, err = rs.db.Get(ACTION_PREFIX + key); err == nil {
|
||||
|
||||
@@ -1104,12 +1104,11 @@ func (self *SQLStorage) GetTpRatingProfiles(tpid, tag string) (map[string]*Ratin
|
||||
if fallback_subject != "" {
|
||||
for _, fbs := range strings.Split(fallback_subject, ";") {
|
||||
newKey := fmt.Sprintf("%s:%s:%s:%s", direction, tenant, tor, fbs)
|
||||
var sslice utils.StringSlice = strings.Split(rp.FallbackKey, ";")
|
||||
var sslice utils.StringSlice = rp.FallbackKeys
|
||||
if !sslice.Contains(newKey) {
|
||||
rp.FallbackKey += newKey + ";"
|
||||
rp.FallbackKeys = append(rp.FallbackKeys, newKey)
|
||||
}
|
||||
}
|
||||
rp.FallbackKey = strings.TrimRight(rp.FallbackKey, ";")
|
||||
}
|
||||
}
|
||||
return rpfs, nil
|
||||
|
||||
@@ -249,7 +249,8 @@ func (self *TPCSVImporter) importRatingProfiles(fn string) error {
|
||||
if self.ImportId != "" {
|
||||
rpTag += "_" + self.ImportId
|
||||
}
|
||||
rp := &RatingProfile{Tag: rpTag,
|
||||
rp := &RatingProfile{
|
||||
Tag: rpTag,
|
||||
Tenant: tenant,
|
||||
TOR: tor,
|
||||
Direction: direction,
|
||||
|
||||
Reference in New Issue
Block a user