diff --git a/apier/v1/apier.go b/apier/v1/apier.go index f372e1016..5b9ecd54e 100644 --- a/apier/v1/apier.go +++ b/apier/v1/apier.go @@ -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 diff --git a/apier/v1/tpratingprofiles.go b/apier/v1/tpratingprofiles.go index 8f5e026f8..259488e90 100644 --- a/apier/v1/tpratingprofiles.go +++ b/apier/v1/tpratingprofiles.go @@ -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, diff --git a/engine/calldesc.go b/engine/calldesc.go index fb3d6e759..2b21e40b5 100644 --- a/engine/calldesc.go +++ b/engine/calldesc.go @@ -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(" 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 diff --git a/engine/loader_csv.go b/engine/loader_csv.go index 56b2d95c6..5f696c60d 100644 --- a/engine/loader_csv.go +++ b/engine/loader_csv.go @@ -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 diff --git a/engine/loader_csv_test.go b/engine/loader_csv_test.go index b7e2a2a08..1e1ed0124 100644 --- a/engine/loader_csv_test.go +++ b/engine/loader_csv_test.go @@ -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) { diff --git a/engine/loader_db.go b/engine/loader_db.go index aaa9b4396..ce1e5bb2f 100644 --- a/engine/loader_db.go +++ b/engine/loader_db.go @@ -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) } diff --git a/engine/ratingplan_test.go b/engine/ratingplan_test.go index 7bfffcde7..9e492d717 100644 --- a/engine/ratingplan_test.go +++ b/engine/ratingplan_test.go @@ -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) { diff --git a/engine/ratingprofile.go b/engine/ratingprofile.go index a1cb9d9db..ab7813932 100644 --- a/engine/ratingprofile.go +++ b/engine/ratingprofile.go @@ -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") } diff --git a/engine/storage_interface.go b/engine/storage_interface.go index c349cf6aa..c2260c773 100644 --- a/engine/storage_interface.go +++ b/engine/storage_interface.go @@ -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) diff --git a/engine/storage_map.go b/engine/storage_map.go index f78e9c2c8..891bab511 100644 --- a/engine/storage_map.go +++ b/engine/storage_map.go @@ -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) diff --git a/engine/storage_redis.go b/engine/storage_redis.go index ede874395..d7dfdb897 100644 --- a/engine/storage_redis.go +++ b/engine/storage_redis.go @@ -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 { diff --git a/engine/storage_sql.go b/engine/storage_sql.go index c4499ce20..4fe086aae 100644 --- a/engine/storage_sql.go +++ b/engine/storage_sql.go @@ -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 diff --git a/engine/tpimporter_csv.go b/engine/tpimporter_csv.go index 0ad7f7fbe..e1688e0ad 100644 --- a/engine/tpimporter_csv.go +++ b/engine/tpimporter_csv.go @@ -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,