This commit is contained in:
DanB
2016-04-14 19:17:54 +02:00
15 changed files with 274 additions and 39 deletions

View File

@@ -36,6 +36,15 @@
<rule avp="Recipient-Address" required="false" max="1" />
</data>
</avp>
<avp name="TransactionId" code="29001" must="V" may="P,M" must-not="-" may-encrypt="N" vendor-id="2011">
<data type="OctetString"/>
</avp>
<avp name="MC-Service-Id" code="29002" must="V" may="P,M" must-not="-" may-encrypt="N" vendor-id="2011">
<data type="UTF8String"/>
</avp>
<avp name="TransparentData" code="29003" must="V" may="P,M" must-not="-" may-encrypt="N" vendor-id="2011">
<data type="UTF8String"/>
</avp>
<avp name="CallingPartyAddress" code="20336" must="V" may="P,M" must-not="-" may-encrypt="N" vendor-id="2011">
<data type="UTF8String" />
</avp>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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