diff --git a/data/diameter/dict/huawei/huawei.xml b/data/diameter/dict/huawei/huawei.xml
index 70e182ddd..4f48257d3 100644
--- a/data/diameter/dict/huawei/huawei.xml
+++ b/data/diameter/dict/huawei/huawei.xml
@@ -36,6 +36,15 @@
+
+
+
+
+
+
+
+
+
diff --git a/data/docker/devel/Dockerfile b/data/docker/devel/Dockerfile
index ea0a8b803..5e4917784 100644
--- a/data/docker/devel/Dockerfile
+++ b/data/docker/devel/Dockerfile
@@ -26,7 +26,7 @@ COPY mongod.conf /etc/mongod.conf
RUN useradd -c CGRateS -d /var/run/cgrates -s /bin/false -r cgrates
# install golang
-RUN wget -qO- https://storage.googleapis.com/golang/go1.6.linux-amd64.tar.gz | tar xzf - -C /root/
+RUN wget -qO- https://storage.googleapis.com/golang/go1.6.1.linux-amd64.tar.gz | tar xzf - -C /root/
#install glide
RUN GOROOT=/root/go GOPATH=/root/code /root/go/bin/go get github.com/Masterminds/glide
diff --git a/engine/calldesc.go b/engine/calldesc.go
index fca3a671d..150563676 100644
--- a/engine/calldesc.go
+++ b/engine/calldesc.go
@@ -362,6 +362,7 @@ func (cd *CallDescriptor) splitInTimeSpans() (timespans []*TimeSpan) {
// split on rating plans
afterStart, afterEnd := false, false //optimization for multiple activation periods
for _, rp := range cd.RatingInfos {
+ //log.Print("RP: ", utils.ToJSON(rp))
if !afterStart && !afterEnd && rp.ActivationTime.Before(cd.TimeStart) {
firstSpan.setRatingInfo(rp)
} else {
@@ -369,7 +370,6 @@ func (cd *CallDescriptor) splitInTimeSpans() (timespans []*TimeSpan) {
for i := 0; i < len(timespans); i++ {
newTs := timespans[i].SplitByRatingPlan(rp)
if newTs != nil {
- //log.Print("NEW TS", newTs.TimeStart)
timespans = append(timespans, newTs)
} else {
afterEnd = true
@@ -378,8 +378,23 @@ func (cd *CallDescriptor) splitInTimeSpans() (timespans []*TimeSpan) {
}
}
}
+ //log.Printf("After SplitByRatingPlan: %+v", utils.ToJSON(timespans))
+ // split on days
+ for i := 0; i < len(timespans); i++ {
+ rp := timespans[i].ratingInfo
+ newTs := timespans[i].SplitByDay()
+ if newTs != nil {
+ //log.Print("NEW TS: ", newTs.TimeStart, newTs.TimeEnd)
+ newTs.setRatingInfo(rp)
+ // insert the new timespan
+ index := i + 1
+ timespans = append(timespans, nil)
+ copy(timespans[index+1:], timespans[index:])
+ timespans[index] = newTs
+ }
+ }
}
- // utils.Logger.Debug(fmt.Sprintf("After SplitByRatingPlan: %+v", timespans))
+ //log.Printf("After SplitByDay: %+v", utils.ToJSON(timespans))
// split on rate intervals
for i := 0; i < len(timespans); i++ {
//log.Printf("==============%v==================", i)
@@ -388,6 +403,7 @@ func (cd *CallDescriptor) splitInTimeSpans() (timespans []*TimeSpan) {
// utils.Logger.Debug(fmt.Sprintf("rp: %+v", rp))
//timespans[i].RatingPlan = nil
rateIntervals := rp.SelectRatingIntevalsForTimespan(timespans[i])
+ //log.Print("RIs: ", utils.ToJSON(rateIntervals))
/*for _, interval := range rp.RateIntervals {
if !timespans[i].hasBetterRateIntervalThan(interval) {
timespans[i].SetRateInterval(interval)
@@ -520,6 +536,7 @@ func (cd *CallDescriptor) getCost() (*CallCost, error) {
cd.TOR = utils.VOICE
}
err := cd.LoadRatingPlans()
+ //log.Print("RI: ", utils.ToJSON(cd.RatingInfos))
if err != nil {
//utils.Logger.Err(fmt.Sprintf("error getting cost for key <%s>: %s", cd.GetKey(cd.Subject), err.Error()))
return &CallCost{Cost: -1}, err
diff --git a/engine/calldesc_test.go b/engine/calldesc_test.go
index ddde0471f..607a97a23 100644
--- a/engine/calldesc_test.go
+++ b/engine/calldesc_test.go
@@ -326,6 +326,43 @@ func TestGetCostRatingPlansAndRatingIntervalsMore(t *testing.T) {
}
}
+func TestGetCostRatingPlansAndRatingIntervalsMoreDays(t *testing.T) {
+ t1 := time.Date(2012, time.February, 20, 9, 50, 0, 0, time.UTC)
+ t2 := time.Date(2012, time.February, 23, 18, 10, 0, 0, time.UTC)
+ cd := &CallDescriptor{Direction: "*out", Category: "0", Tenant: "CUSTOMER_1", Subject: "rif:from:tm", Destination: "49178", TimeStart: t1, TimeEnd: t2, LoopIndex: 0, DurationIndex: t2.Sub(t1)}
+ result, _ := cd.GetCost()
+ if len(result.Timespans) != 8 ||
+ !result.Timespans[0].TimeEnd.Equal(result.Timespans[1].TimeStart) ||
+ !result.Timespans[1].TimeEnd.Equal(result.Timespans[2].TimeStart) ||
+ !result.Timespans[2].TimeEnd.Equal(result.Timespans[3].TimeStart) ||
+ !result.Timespans[3].TimeEnd.Equal(result.Timespans[4].TimeStart) ||
+ !result.Timespans[4].TimeEnd.Equal(result.Timespans[5].TimeStart) ||
+ !result.Timespans[5].TimeEnd.Equal(result.Timespans[6].TimeStart) ||
+ !result.Timespans[6].TimeEnd.Equal(result.Timespans[7].TimeStart) {
+ for _, ts := range result.Timespans {
+ t.Logf("TS %+v", ts)
+ }
+ t.Errorf("Expected %+v was %+v", 4, len(result.Timespans))
+ }
+}
+
+func TestGetCostRatingPlansAndRatingIntervalsMoreDaysWeekend(t *testing.T) {
+ t1 := time.Date(2012, time.February, 24, 9, 50, 0, 0, time.UTC)
+ t2 := time.Date(2012, time.February, 27, 18, 10, 0, 0, time.UTC)
+ cd := &CallDescriptor{Direction: "*out", Category: "0", Tenant: "CUSTOMER_1", Subject: "rif:from:tm", Destination: "49178", TimeStart: t1, TimeEnd: t2, LoopIndex: 0, DurationIndex: t2.Sub(t1)}
+ result, _ := cd.GetCost()
+ if len(result.Timespans) != 5 ||
+ !result.Timespans[0].TimeEnd.Equal(result.Timespans[1].TimeStart) ||
+ !result.Timespans[1].TimeEnd.Equal(result.Timespans[2].TimeStart) ||
+ !result.Timespans[2].TimeEnd.Equal(result.Timespans[3].TimeStart) ||
+ !result.Timespans[3].TimeEnd.Equal(result.Timespans[4].TimeStart) {
+ for _, ts := range result.Timespans {
+ t.Logf("TS %+v", ts)
+ }
+ t.Errorf("Expected %+v was %+v", 4, len(result.Timespans))
+ }
+}
+
func TestGetCostRateGroups(t *testing.T) {
t1 := time.Date(2013, time.October, 7, 14, 50, 0, 0, time.UTC)
t2 := time.Date(2013, time.October, 7, 14, 52, 12, 0, time.UTC)
diff --git a/engine/handler_derivedcharging.go b/engine/handler_derivedcharging.go
index 52f48eb03..e845ac1dd 100644
--- a/engine/handler_derivedcharging.go
+++ b/engine/handler_derivedcharging.go
@@ -50,12 +50,10 @@ func DerivedChargersMatchesDest(dcs *utils.DerivedChargers, dest string) bool {
for _, p := range utils.SplitPrefix(dest, MIN_PREFIX_MATCH) {
if x, err := cache2go.Get(utils.DESTINATION_PREFIX + p); err == nil {
destIds := x.(map[interface{}]struct{})
- for value := range dcs.DestinationIDs {
- for idId := range destIds {
- dId := idId.(string)
- if value == dId {
- return true
- }
+ for dId := range destIds {
+ includeDest, found := dcs.DestinationIDs[dId.(string)]
+ if found {
+ return includeDest
}
}
}
diff --git a/engine/handler_derivedcharging_test.go b/engine/handler_derivedcharging_test.go
index 8a2c98f54..bb793b9ba 100644
--- a/engine/handler_derivedcharging_test.go
+++ b/engine/handler_derivedcharging_test.go
@@ -119,3 +119,21 @@ func TestHandleDeivedChargersMatchDestNatRet(t *testing.T) {
t.Error("Derived charger failed to match dest")
}
}
+
+func TestHandleDeivedChargersMatchDestSpec(t *testing.T) {
+ dcs := &utils.DerivedChargers{
+ DestinationIDs: utils.NewStringMap("NAT", "SPEC"),
+ }
+ if !DerivedChargersMatchesDest(dcs, "0723045326") {
+ t.Error("Derived charger failed to match dest")
+ }
+}
+
+func TestHandleDeivedChargersMatchDestNegativeSpec(t *testing.T) {
+ dcs := &utils.DerivedChargers{
+ DestinationIDs: utils.NewStringMap("NAT", "!SPEC"),
+ }
+ if DerivedChargersMatchesDest(dcs, "0723045326") {
+ t.Error("Derived charger failed to match dest")
+ }
+}
diff --git a/engine/history_test.go b/engine/history_test.go
index 3e51c5a4a..e3d797e4b 100644
--- a/engine/history_test.go
+++ b/engine/history_test.go
@@ -47,6 +47,7 @@ func TestHistoryDestinations(t *testing.T) {
{"Id":"PSTN_71","Prefixes":["+4971"]},
{"Id":"PSTN_72","Prefixes":["+4972"]},
{"Id":"RET","Prefixes":["0723","0724"]},
+{"Id":"SPEC","Prefixes":["0723045"]},
{"Id":"URG","Prefixes":["112"]}`
if !strings.Contains(buf.String(), expected) {
t.Error("Error in destination history content:", buf.String())
diff --git a/engine/loader_csv_test.go b/engine/loader_csv_test.go
index 357f56fb8..d26ebbcc6 100644
--- a/engine/loader_csv_test.go
+++ b/engine/loader_csv_test.go
@@ -42,6 +42,7 @@ NAT,0723
NAT,+49
RET,0723
RET,0724
+SPEC,0723045
PSTN_71,+4971
PSTN_72,+4972
PSTN_70,+4970
@@ -319,7 +320,7 @@ func init() {
}
func TestLoadDestinations(t *testing.T) {
- if len(csvr.destinations) != 12 {
+ if len(csvr.destinations) != 13 {
t.Error("Failed to load destinations: ", len(csvr.destinations))
}
for _, d := range csvr.destinations {
diff --git a/engine/rateinterval.go b/engine/rateinterval.go
index 0ddcb241d..0b0b13ed2 100644
--- a/engine/rateinterval.go
+++ b/engine/rateinterval.go
@@ -289,7 +289,7 @@ Returns true if the received time result inside the interval
*/
func (i *RateInterval) Contains(t time.Time, endTime bool) bool {
if endTime {
- if t.Hour() == 0 && t.Minute() == 0 && t.Second() == 0 { // back one second to 23:59:59
+ if utils.TimeIs0h(t) { // back one second to 23:59:59
t = t.Add(-1 * time.Second)
}
}
@@ -360,23 +360,6 @@ func (ri *RateInterval) GetMaxCost() (float64, string) {
// Structure to store intervals according to weight
type RateIntervalList []*RateInterval
-func (il RateIntervalList) Len() int {
- return len(il)
-}
-
-func (il RateIntervalList) Swap(i, j int) {
- il[i], il[j] = il[j], il[i]
-}
-
-// we need higher weights earlyer in the list
-func (il RateIntervalList) Less(j, i int) bool {
- return il[i].Weight < il[j].Weight //|| il[i].Timing.StartTime > il[j].Timing.StartTime
-}
-
-func (il RateIntervalList) Sort() {
- sort.Sort(il)
-}
-
// Structure to store intervals according to weight
type RateIntervalTimeSorter struct {
referenceTime time.Time
@@ -393,6 +376,9 @@ func (il *RateIntervalTimeSorter) Swap(i, j int) {
// we need higher weights earlyer in the list
func (il *RateIntervalTimeSorter) Less(j, i int) bool {
+ if il.ris[i].Weight < il.ris[j].Weight {
+ return il.ris[i].Weight < il.ris[j].Weight
+ }
t1 := il.ris[i].Timing.getLeftMargin(il.referenceTime)
t2 := il.ris[j].Timing.getLeftMargin(il.referenceTime)
return t1.After(t2)
diff --git a/engine/ratingplan.go b/engine/ratingplan.go
index 3f1214477..fd902ed8e 100644
--- a/engine/ratingplan.go
+++ b/engine/ratingplan.go
@@ -59,12 +59,7 @@ func (rp *RatingPlan) RateIntervalList(dId string) RateIntervalList {
return ril
}
-/*
-type xCachedRatingPlan struct {
- rp *RatingPlan
- *cache2go.XEntry
-}
-*/
+// no sorter because it's sorted with RateIntervalTimeSorter
/*
Adds one ore more intervals to the internal interval list only if it is not allready in the list.
diff --git a/engine/ratingprofile.go b/engine/ratingprofile.go
index 2560e96c0..e72665a22 100644
--- a/engine/ratingprofile.go
+++ b/engine/ratingprofile.go
@@ -92,28 +92,36 @@ type RatingInfo struct {
// SelectRatingIntevalsForTimespan orders rate intervals in time preserving only those which aply to the specified timestamp
func (ri RatingInfo) SelectRatingIntevalsForTimespan(ts *TimeSpan) (result RateIntervalList) {
- ri.RateIntervals.Sort()
sorter := &RateIntervalTimeSorter{referenceTime: ts.TimeStart, ris: ri.RateIntervals}
rateIntervals := sorter.Sort()
// get the rating interval closest to begining of timespan
var delta time.Duration = -1
var bestRateIntervalIndex int
+ var bestIntervalWeight float64
for index, rateInterval := range rateIntervals {
if !rateInterval.Contains(ts.TimeStart, false) {
continue
}
+ if rateInterval.Weight < bestIntervalWeight {
+ break // don't consider lower weights'
+ }
startTime := rateInterval.Timing.getLeftMargin(ts.TimeStart)
tmpDelta := ts.TimeStart.Sub(startTime)
if (startTime.Before(ts.TimeStart) ||
startTime.Equal(ts.TimeStart)) &&
(delta == -1 || tmpDelta < delta) {
bestRateIntervalIndex = index
+ bestIntervalWeight = rateInterval.Weight
delta = tmpDelta
}
}
result = append(result, rateIntervals[bestRateIntervalIndex])
// check if later rating intervals influence this timespan
+ //log.Print("RIS: ", utils.ToIJSON(rateIntervals))
for i := bestRateIntervalIndex + 1; i < len(rateIntervals); i++ {
+ if rateIntervals[i].Weight < bestIntervalWeight {
+ break // don't consider lower weights'
+ }
startTime := rateIntervals[i].Timing.getLeftMargin(ts.TimeStart)
if startTime.Before(ts.TimeEnd) {
result = append(result, rateIntervals[i])
diff --git a/engine/ratingprofile_test.go b/engine/ratingprofile_test.go
index 4f929029a..08cfecacd 100644
--- a/engine/ratingprofile_test.go
+++ b/engine/ratingprofile_test.go
@@ -250,6 +250,143 @@ func TestRatingProfileRIforTSMidnight(t *testing.T) {
}
}
+func TestRatingProfileYearMonthDay(t *testing.T) {
+ ri := &RatingInfo{
+ RateIntervals: RateIntervalList{
+ &RateInterval{
+ Timing: &RITiming{
+ StartTime: "09:00:00",
+ },
+ },
+ &RateInterval{
+ Timing: &RITiming{
+ StartTime: "00:00:00",
+ },
+ },
+ &RateInterval{
+ Timing: &RITiming{
+ Years: utils.Years{2016},
+ Months: utils.Months{1},
+ MonthDays: utils.MonthDays{6, 7},
+ WeekDays: utils.WeekDays{},
+ StartTime: "19:00:00",
+ },
+ },
+ },
+ }
+ ts := &TimeSpan{
+ TimeStart: time.Date(2016, 1, 6, 23, 40, 0, 0, time.UTC),
+ TimeEnd: time.Date(2016, 1, 7, 1, 1, 30, 0, time.UTC),
+ }
+ rIntervals := ri.SelectRatingIntevalsForTimespan(ts)
+ if len(rIntervals) != 1 ||
+ rIntervals[0].Timing.StartTime != "19:00:00" {
+ t.Error("Wrong interval list: ", utils.ToIJSON(rIntervals))
+ }
+}
+
+func TestRatingProfileWeighted(t *testing.T) {
+ ri := &RatingInfo{
+ RateIntervals: RateIntervalList{
+ &RateInterval{
+ Timing: &RITiming{
+ StartTime: "09:00:00",
+ },
+ Weight: 10,
+ },
+ &RateInterval{
+ Timing: &RITiming{
+ StartTime: "00:00:00",
+ },
+ Weight: 10,
+ },
+ &RateInterval{
+ Timing: &RITiming{
+ StartTime: "19:00:00",
+ },
+ Weight: 10,
+ },
+ &RateInterval{
+ Timing: &RITiming{
+ Years: utils.Years{2016},
+ Months: utils.Months{1},
+ MonthDays: utils.MonthDays{6},
+ WeekDays: utils.WeekDays{},
+ StartTime: "00:00:00",
+ },
+ Weight: 11,
+ },
+ },
+ }
+ ts := &TimeSpan{
+ TimeStart: time.Date(2016, 1, 6, 23, 40, 0, 0, time.UTC),
+ TimeEnd: time.Date(2016, 1, 6, 23, 45, 30, 0, time.UTC),
+ }
+ rIntervals := ri.SelectRatingIntevalsForTimespan(ts)
+ if len(rIntervals) != 1 ||
+ rIntervals[0].Timing.StartTime != "00:00:00" ||
+ rIntervals[0].Weight != 11 {
+ t.Error("Wrong interval list: ", utils.ToIJSON(rIntervals))
+ }
+}
+
+func TestRatingProfileWeightedMultiple(t *testing.T) {
+ ri := &RatingInfo{
+ RateIntervals: RateIntervalList{
+ &RateInterval{
+ Timing: &RITiming{
+ StartTime: "09:00:00",
+ },
+ Weight: 10,
+ },
+ &RateInterval{
+ Timing: &RITiming{
+ StartTime: "00:00:00",
+ },
+ Weight: 10,
+ },
+ &RateInterval{
+ Timing: &RITiming{
+ StartTime: "19:00:00",
+ },
+ Weight: 10,
+ },
+ &RateInterval{
+ Timing: &RITiming{
+ Years: utils.Years{2016},
+ Months: utils.Months{1},
+ MonthDays: utils.MonthDays{6},
+ WeekDays: utils.WeekDays{},
+ StartTime: "00:00:00",
+ },
+ Weight: 11,
+ },
+ &RateInterval{
+ Timing: &RITiming{
+ Years: utils.Years{2016},
+ Months: utils.Months{1},
+ MonthDays: utils.MonthDays{6},
+ WeekDays: utils.WeekDays{},
+ StartTime: "18:00:00",
+ },
+ Weight: 11,
+ },
+ },
+ }
+ ts := &TimeSpan{
+ TimeStart: time.Date(2016, 1, 6, 17, 40, 0, 0, time.UTC),
+ TimeEnd: time.Date(2016, 1, 6, 23, 45, 30, 0, time.UTC),
+ }
+ rIntervals := ri.SelectRatingIntevalsForTimespan(ts)
+ if len(rIntervals) != 2 ||
+ rIntervals[0].Timing.StartTime != "00:00:00" ||
+ rIntervals[0].Weight != 11 ||
+ rIntervals[1].Timing.StartTime != "18:00:00" ||
+ rIntervals[1].Weight != 11 {
+ t.Error("Wrong interval list: ", utils.ToIJSON(rIntervals))
+ }
+}
+
func TestRatingProfileSubjectPrefixMatching(t *testing.T) {
rpSubjectPrefixMatching = true
rp, err := RatingProfileSubjectPrefixMatching("*out:cgrates.org:data:rif")
diff --git a/engine/timespans.go b/engine/timespans.go
index 8a716f307..33bc79e8d 100644
--- a/engine/timespans.go
+++ b/engine/timespans.go
@@ -612,6 +612,26 @@ func (ts *TimeSpan) SplitByRatingPlan(rp *RatingInfo) (newTs *TimeSpan) {
return
}
+// Splits the given timespan on activation period's activation time.
+func (ts *TimeSpan) SplitByDay() (newTs *TimeSpan) {
+ if ts.TimeStart.Day() == ts.TimeEnd.Day() || utils.TimeIs0h(ts.TimeEnd) {
+ return
+ }
+
+ splitDate := ts.TimeStart.AddDate(0, 0, 1)
+ splitDate = time.Date(splitDate.Year(), splitDate.Month(), splitDate.Day(), 0, 0, 0, 0, splitDate.Location())
+ newTs = &TimeSpan{
+ TimeStart: splitDate,
+ TimeEnd: ts.TimeEnd,
+ }
+ newTs.copyRatingInfo(ts)
+ newTs.DurationIndex = ts.DurationIndex
+ ts.TimeEnd = splitDate
+ ts.SetNewDurationIndex(newTs)
+ // Logger.Debug(fmt.Sprintf("RP SPLITTING: %+v %+v", ts, newTs))
+ return
+}
+
// Returns the starting time of this timespan
func (ts *TimeSpan) GetGroupStart() time.Duration {
s := ts.DurationIndex - ts.GetDuration()
diff --git a/general_tests/tp_it_test.go b/general_tests/tp_it_test.go
index 1ad58d833..cb66dca3e 100644
--- a/general_tests/tp_it_test.go
+++ b/general_tests/tp_it_test.go
@@ -161,6 +161,12 @@ func TestTpZeroCost(t *testing.T) {
if !*testIntegration {
return
}
+ var acnt *engine.Account
+ attrs := &utils.AttrGetAccount{Tenant: "cgrates.org", Account: "1012"}
+ if err := tpRPC.Call("ApierV2.GetAccount", attrs, &acnt); err != nil {
+ t.Error("Got error on ApierV2.GetAccount: ", err.Error())
+ }
+ balanceValueBefore := acnt.BalanceMap[utils.MONETARY][0].Value
tStart := time.Date(2016, 3, 31, 0, 0, 0, 0, time.UTC)
cd := engine.CallDescriptor{
Direction: "*out",
@@ -175,15 +181,13 @@ func TestTpZeroCost(t *testing.T) {
}
var cc engine.CallCost
if err := tpRPC.Call("Responder.Debit", cd, &cc); err != nil {
- t.Error("Got error on Responder.GetCost: ", err.Error())
+ t.Error("Got error on Responder.Debit: ", err.Error())
} else if cc.GetDuration() != 20*time.Second {
t.Errorf("Calling Responder.MaxDebit got callcost: %v", utils.ToIJSON(cc))
}
- var acnt *engine.Account
- attrs := &utils.AttrGetAccount{Tenant: "cgrates.org", Account: "1012"}
if err := tpRPC.Call("ApierV2.GetAccount", attrs, &acnt); err != nil {
t.Error("Got error on ApierV2.GetAccount: ", err.Error())
- } else if acnt.BalanceMap[utils.MONETARY][0].Value != 11.0 {
+ } else if acnt.BalanceMap[utils.MONETARY][0].Value != balanceValueBefore {
t.Errorf("Calling ApierV2.GetAccount received: %s", utils.ToIJSON(acnt))
}
}
diff --git a/utils/coreutils.go b/utils/coreutils.go
index 35453b5d5..461fb2d98 100644
--- a/utils/coreutils.go
+++ b/utils/coreutils.go
@@ -547,3 +547,7 @@ func SizeFmt(num float64, suffix string) string {
}
return fmt.Sprintf("%.1f%s%s", num, "Yi", suffix)
}
+
+func TimeIs0h(t time.Time) bool {
+ return t.Hour() == 0 && t.Minute() == 0 && t.Second() == 0
+}