func TestTpZeroNegativeCost(t *testing.T) { tStart := time.Date(2016, 3, 31, 0, 0, 0, 0, time.UTC) cd := engine.CallDescriptor{ Direction: "*out", Category: "call", Tenant: "cgrates.org", Subject: "free", Account: "1013", Destination: "+4915", DurationIndex: 0, TimeStart: tStart, TimeEnd: tStart.Add(time.Duration(20) * time.Second), } var cc engine.CallCost if err := tpRPC.Call("Responder.Debit", cd, &cc); err != nil { t.Error("Got error on Responder.GetCost: ", 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: "1013"} if err := tpRPC.Call("ApierV2.GetAccount", attrs, &acnt); err != nil { t.Error("Got error on ApierV2.GetAccount: ", err.Error()) } else if acnt.BalanceMap[utils.VOICE][0].Value != 100.0 { t.Errorf("Calling ApierV2.GetAccount received: %s", utils.ToIJSON(acnt)) } }
func TestTpBalanceCounter(t *testing.T) { if !*testIntegration { return } tStart := time.Date(2016, 3, 31, 0, 0, 0, 0, time.UTC) cd := engine.CallDescriptor{ Direction: "*out", Category: "call", Tenant: "cgrates.org", Subject: "1001", Destination: "+49", DurationIndex: 0, TimeStart: tStart, TimeEnd: tStart.Add(time.Duration(20) * time.Second), } var cc engine.CallCost if err := tpRPC.Call("Responder.Debit", cd, &cc); err != nil { t.Error("Got error on Responder.GetCost: ", err.Error()) } else if cc.GetDuration() != 20*time.Second { t.Errorf("Calling Responder.MaxDebit got callcost: %v", cc.GetDuration()) } var acnt *engine.Account attrs := &utils.AttrGetAccount{Tenant: "cgrates.org", Account: "1001"} if err := tpRPC.Call("ApierV2.GetAccount", attrs, &acnt); err != nil { t.Error("Got error on ApierV2.GetAccount: ", err.Error()) } else if acnt.UnitCounters[utils.MONETARY][1].Counters[0].Value != 20.0 { t.Errorf("Calling ApierV2.GetBalance received: %s", utils.ToIJSON(acnt)) } }
// the debit loop method (to be stoped by sending somenthing on stopDebit channel) func (s *Session) debitLoop(runIdx int) { nextCd := s.sessionRuns[runIdx].CallDescriptor nextCd.CgrID = s.eventStart.GetCgrId(s.sessionManager.Timezone()) index := 0.0 debitPeriod := s.sessionManager.DebitInterval() for { select { case <-s.stopDebit: return default: } if index > 0 { // first time use the session start time nextCd.TimeStart = nextCd.TimeEnd } nextCd.TimeEnd = nextCd.TimeStart.Add(debitPeriod) nextCd.LoopIndex = index nextCd.DurationIndex += debitPeriod // first presumed duration cc := new(engine.CallCost) if err := s.sessionManager.Rater().Call("Responder.MaxDebit", nextCd, cc); err != nil { utils.Logger.Err(fmt.Sprintf("Could not complete debit opperation: %v", err)) if err.Error() == utils.ErrUnauthorizedDestination.Error() { s.sessionManager.DisconnectSession(s.eventStart, s.connId, UNAUTHORIZED_DESTINATION) return } s.sessionManager.DisconnectSession(s.eventStart, s.connId, SYSTEM_ERROR) return } if cc.GetDuration() == 0 { s.sessionManager.DisconnectSession(s.eventStart, s.connId, INSUFFICIENT_FUNDS) return } if s.warnMinDur != time.Duration(0) && cc.GetDuration() <= s.warnMinDur { s.sessionManager.WarnSessionMinDuration(s.eventStart.GetUUID(), s.connId) } s.sessionRuns[runIdx].CallCosts = append(s.sessionRuns[runIdx].CallCosts, cc) nextCd.TimeEnd = cc.GetEndTime() // set debited timeEnd // update call duration with real debited duration nextCd.DurationIndex -= debitPeriod nextCd.DurationIndex += cc.GetDuration() nextCd.MaxCostSoFar += cc.Cost time.Sleep(cc.GetDuration()) index++ } }
// the debit loop method (to be stoped by sending somenthing on stopDebit channel) func (s *Session) debitLoop(runIdx int) { nextCd := s.sessionRuns[runIdx].CallDescriptor nextCd.CgrId = s.eventStart.GetCgrId("") index := 0.0 debitPeriod := s.sessionManager.DebitInterval() for { select { case <-s.stopDebit: return default: } if index > 0 { // first time use the session start time nextCd.TimeStart = nextCd.TimeEnd } nextCd.TimeEnd = nextCd.TimeStart.Add(debitPeriod) nextCd.LoopIndex = index //engine.Logger.Debug(fmt.Sprintf("NEXTCD: %s", utils.ToJSON(nextCd))) nextCd.DurationIndex += debitPeriod // first presumed duration cc := new(engine.CallCost) if err := s.sessionManager.Rater().MaxDebit(nextCd, cc); err != nil { engine.Logger.Err(fmt.Sprintf("Could not complete debit opperation: %v", err)) s.sessionManager.DisconnectSession(s.eventStart, s.connId, SYSTEM_ERROR) return } if cc.GetDuration() == 0 { s.sessionManager.DisconnectSession(s.eventStart, s.connId, INSUFFICIENT_FUNDS) return } if s.warnMinDur != time.Duration(0) && cc.GetDuration() <= s.warnMinDur { s.sessionManager.WarnSessionMinDuration(s.eventStart.GetUUID(), s.connId) } s.sessionRuns[runIdx].CallCosts = append(s.sessionRuns[runIdx].CallCosts, cc) //engine.Logger.Debug(fmt.Sprintf("CALLCOST: %s", utils.ToJSON(cc))) nextCd.TimeEnd = cc.GetEndTime() // set debited timeEnd //engine.Logger.Debug(fmt.Sprintf("NEXTCD: %s DURATION: %s", utils.ToJSON(nextCd), nextCd.GetDuration().String())) // update call duration with real debited duration nextCd.DurationIndex -= debitPeriod nextCd.DurationIndex += cc.GetDuration() nextCd.MaxCostSoFar += cc.Cost time.Sleep(cc.GetDuration()) index++ } }
// the debit loop method (to be stoped by sending somenthing on stopDebit channel) func (s *Session) debitLoop(runIdx int) { nextCd := *s.sessionRuns[runIdx].callDescriptor index := 0.0 debitPeriod := s.sessionManager.GetDebitPeriod() for { select { case <-s.stopDebit: return default: } if index > 0 { // first time use the session start time nextCd.TimeStart = nextCd.TimeEnd } nextCd.TimeEnd = nextCd.TimeStart.Add(debitPeriod) nextCd.LoopIndex = index nextCd.DurationIndex += debitPeriod // first presumed duration cc := new(engine.CallCost) if err := s.sessionManager.MaxDebit(&nextCd, cc); err != nil { engine.Logger.Err(fmt.Sprintf("Could not complete debit opperation: %v", err)) s.sessionManager.DisconnectSession(s.uuid, SYSTEM_ERROR, "") return } if cc.GetDuration() == 0 { s.sessionManager.DisconnectSession(s.uuid, INSUFFICIENT_FUNDS, nextCd.Destination) return } if cc.GetDuration() <= cfg.FSMinDurLowBalance && len(cfg.FSLowBalanceAnnFile) != 0 { if _, err := fsock.FS.SendApiCmd(fmt.Sprintf("uuid_broadcast %s %s aleg\n\n", s.uuid, cfg.FSLowBalanceAnnFile)); err != nil { engine.Logger.Err(fmt.Sprintf("<SessionManager> Could not send uuid_broadcast to freeswitch: %s", err.Error())) } } s.sessionRuns[runIdx].callCosts = append(s.sessionRuns[runIdx].callCosts, cc) nextCd.TimeEnd = cc.GetEndTime() // set debited timeEnd // update call duration with real debited duration nextCd.DurationIndex -= debitPeriod nextCd.DurationIndex += nextCd.GetDuration() time.Sleep(cc.GetDuration()) index++ } }
// Processes one time events (eg: SMS) func (smg *SMGeneric) ChargeEvent(gev SMGenericEvent) (maxUsage time.Duration, err error) { cacheKey := "ChargeEvent" + gev.GetCgrId(smg.timezone) if item, err := smg.responseCache.Get(cacheKey); err == nil && item != nil { return item.Value.(time.Duration), item.Err } defer smg.responseCache.Cache(cacheKey, &cache.CacheItem{Value: maxUsage, Err: err}) var sessionRuns []*engine.SessionRun if err = smg.rater.Call("Responder.GetSessionRuns", gev.AsStoredCdr(smg.cgrCfg, smg.timezone), &sessionRuns); err != nil { return } else if len(sessionRuns) == 0 { return } var maxDurInit bool // Avoid differences between default 0 and received 0 for _, sR := range sessionRuns { cc := new(engine.CallCost) if err = smg.rater.Call("Responder.MaxDebit", sR.CallDescriptor, cc); err != nil { utils.Logger.Err(fmt.Sprintf("<SMGeneric> Could not Debit CD: %+v, RunID: %s, error: %s", sR.CallDescriptor, sR.DerivedCharger.RunID, err.Error())) break } sR.CallCosts = append(sR.CallCosts, cc) // Save it so we can revert on issues if ccDur := cc.GetDuration(); ccDur == 0 { err = utils.ErrInsufficientCredit break } else if !maxDurInit || ccDur < maxUsage { maxUsage = ccDur } } if err != nil { // Refund the ones already taken since we have error on one of the debits for _, sR := range sessionRuns { if len(sR.CallCosts) == 0 { continue } cc := sR.CallCosts[0] if len(sR.CallCosts) > 1 { for _, ccSR := range sR.CallCosts { cc.Merge(ccSR) } } // collect increments var refundIncrements engine.Increments cc.Timespans.Decompress() for _, ts := range cc.Timespans { refundIncrements = append(refundIncrements, ts.Increments...) } // refund cc if len(refundIncrements) > 0 { cd := cc.CreateCallDescriptor() cd.Increments = refundIncrements cd.CgrID = sR.CallDescriptor.CgrID cd.RunID = sR.CallDescriptor.RunID cd.Increments.Compress() //utils.Logger.Info(fmt.Sprintf("Refunding session run callcost: %s", utils.ToJSON(cd))) var response float64 err = smg.rater.Call("Responder.RefundIncrements", cd, &response) if err != nil { return } } } return } var withErrors bool for _, sR := range sessionRuns { if len(sR.CallCosts) == 0 { continue } cc := sR.CallCosts[0] if len(sR.CallCosts) > 1 { for _, ccSR := range sR.CallCosts[1:] { cc.Merge(ccSR) } } cc.Round() roundIncrements := cc.GetRoundIncrements() if len(roundIncrements) != 0 { cd := cc.CreateCallDescriptor() cd.Increments = roundIncrements var response float64 if errRefund := smg.rater.Call("Responder.RefundRounding", cd, &response); errRefund != nil { utils.Logger.Err(fmt.Sprintf("<SM> ERROR failed to refund rounding: %v", errRefund)) } } var reply string smCost := &engine.SMCost{ CGRID: gev.GetCgrId(smg.timezone), CostSource: utils.SESSION_MANAGER_SOURCE, RunID: sR.DerivedCharger.RunID, OriginHost: gev.GetOriginatorIP(utils.META_DEFAULT), OriginID: gev.GetUUID(), CostDetails: cc, } if errStore := smg.cdrsrv.Call("CdrsV1.StoreSMCost", engine.AttrCDRSStoreSMCost{Cost: smCost, CheckDuplicate: true}, &reply); errStore != nil && !strings.HasSuffix(errStore.Error(), utils.ErrExists.Error()) { withErrors = true utils.Logger.Err(fmt.Sprintf("<SMGeneric> Could not save CC: %+v, RunID: %s error: %s", cc, sR.DerivedCharger.RunID, errStore.Error())) } } if withErrors { err = ErrPartiallyExecuted return } return }
// Check call costs func TestTutLocalMaxDebit(t *testing.T) { if !*testLocal { return } tStart := time.Date(2014, 8, 4, 13, 0, 0, 0, time.UTC) cd := engine.CallDescriptor{ Direction: "*out", Category: "call", Tenant: "cgrates.org", Subject: "1001", Account: "1001", Destination: "1002", DurationIndex: 0, TimeStart: tStart, TimeEnd: tStart.Add(time.Duration(20) * time.Second), } var cc engine.CallCost if err := tutLocalRpc.Call("Responder.MaxDebit", cd, &cc); err != nil { t.Error("Got error on Responder.GetCost: ", err.Error()) } else if cc.GetDuration() == 20 { t.Errorf("Calling Responder.MaxDebit got callcost: %v", cc.GetDuration()) } cd = engine.CallDescriptor{ Direction: "*out", Category: "call", Tenant: "cgrates.org", Subject: "1001", Account: "1001", Destination: "1003", DurationIndex: 0, TimeStart: tStart, TimeEnd: tStart.Add(time.Duration(200) * time.Second), } if err := tutLocalRpc.Call("Responder.MaxDebit", cd, &cc); err != nil { t.Error("Got error on Responder.MaxDebit: ", err.Error()) } else if cc.GetDuration() == 200 { t.Errorf("Calling Responder.MaxDebit got duration: %v", cc.GetDuration()) } cd = engine.CallDescriptor{ Direction: "*out", Category: "call", Tenant: "cgrates.org", Subject: "1001", Account: "1001", Destination: "1007", DurationIndex: 0, TimeStart: tStart, TimeEnd: tStart.Add(time.Duration(120) * time.Second), } if err := tutLocalRpc.Call("Responder.MaxDebit", cd, &cc); err != nil { t.Error("Got error on Responder.GetCost: ", err.Error()) } else if cc.GetDuration() == 120 { t.Errorf("Calling Responder.MaxDebit got callcost: %v", cc.GetDuration()) } cd = engine.CallDescriptor{ Direction: "*out", Category: "call", Tenant: "cgrates.org", Subject: "1004", Account: "1004", Destination: "1007", DurationIndex: 0, TimeStart: tStart, TimeEnd: tStart.Add(time.Duration(120) * time.Second), } if err := tutLocalRpc.Call("Responder.MaxDebit", cd, &cc); err != nil { t.Error("Got error on Responder.GetCost: ", err.Error()) } else if cc.GetDuration() != time.Duration(62)*time.Second { // We have as strategy *dsconnect t.Errorf("Calling Responder.MaxDebit got callcost: %v", cc.GetDuration()) } var maxTime float64 if err := tutLocalRpc.Call("Responder.GetMaxSessionTime", cd, &maxTime); err != nil { t.Error("Got error on Responder.GetCost: ", err.Error()) } else if maxTime != 62000000000 { // We have as strategy *dsconnect t.Errorf("Calling Responder.GetMaxSessionTime got maxTime: %f", maxTime) } }