Added *sessionChargeable session option. Fixes #1702

This commit is contained in:
Trial97
2021-02-15 08:13:26 +02:00
committed by Dan Christian Bogos
parent a2688b9536
commit d2e04360bd
10 changed files with 585 additions and 94 deletions

View File

@@ -81,6 +81,7 @@ type Session struct {
debitStop chan struct{}
sTerminator *sTerminator // automatic timeout for the session
chargeable bool
}
// Lock exported function from sync.RWMutex

View File

@@ -425,6 +425,11 @@ func (sS *SessionS) debitSession(s *Session, sRunIdx int, dur time.Duration,
return
}
sr := s.SRuns[sRunIdx]
if !s.chargeable {
sS.pause(sr, dur)
sr.TotalUsage += sr.LastUsage
return dur, nil
}
rDur := sr.debitReserve(dur, lastUsed) // debit out of reserve, rDur is still to be debited
if rDur == time.Duration(0) {
return dur, nil // complete debit out of reserve
@@ -498,6 +503,27 @@ func (sS *SessionS) debitSession(s *Session, sRunIdx int, dur time.Duration,
return
}
func (sS *SessionS) pause(sr *SRun, dur time.Duration) {
if sr.CD.LoopIndex > 0 {
sr.CD.TimeStart = sr.CD.TimeEnd
}
sr.CD.TimeEnd = sr.CD.TimeStart.Add(dur)
sr.CD.DurationIndex += dur
ec := engine.NewFreeEventCost(sr.CD.CgrID, sr.CD.RunID, sr.CD.Account, sr.CD.TimeStart, dur)
sr.LastUsage = dur
sr.CD.LoopIndex++
if sr.EventCost == nil { // is the first increment
// when we start the call with debit interval 0
// but later we update this value with one greater than 0
sr.EventCost = ec
} else { // we already debited something
// copy the old AccountSummary as in Merge the old one is overwriten by the new one
ec.AccountSummary = sr.EventCost.AccountSummary
// similar to the debit merge the event costs
sr.EventCost.Merge(ec)
}
}
// debitLoopSession will periodically debit sessions, ie: automatic prepaid
// threadSafe since it will run into it's own goroutine
func (sS *SessionS) debitLoopSession(s *Session, sRunIdx int,
@@ -1121,6 +1147,7 @@ func (sS *SessionS) newSession(cgrEv *utils.CGREvent, resID, clntConnID string,
ClientConnID: clntConnID,
DebitInterval: dbtItval,
}
s.chargeable = s.OptsStart.GetBoolOrDefault(utils.OptsChargeable, true)
if !isMsg && sS.isIndexed(s, false) { // check if already exists
return nil, utils.ErrExists
}
@@ -1367,7 +1394,7 @@ func (sS *SessionS) initSessionDebitLoops(s *Session) {
return
}
for i, sr := range s.SRuns {
if s.DebitInterval != 0 &&
if s.DebitInterval > 0 &&
sr.Event.GetStringIgnoreErrors(utils.RequestType) == utils.MetaPrepaid {
if s.debitStop == nil { // init the debitStop only for the first sRun with DebitInterval and RequestType MetaPrepaid
s.debitStop = make(chan struct{})
@@ -1450,6 +1477,7 @@ func (sS *SessionS) updateSession(s *Session, updtEv, opts engine.MapEvent, isMs
s.updateSRuns(updtEv, sS.cgrCfg.SessionSCfg().AlterableFields)
sS.setSTerminator(s, opts) // reset the terminator
}
s.chargeable = opts.GetBoolOrDefault(utils.OptsChargeable, true)
//init has no updtEv
if updtEv == nil {
updtEv = engine.MapEvent(s.EventStart.Clone())
@@ -1517,20 +1545,24 @@ func (sS *SessionS) endSession(s *Session, tUsage, lastUsage *time.Duration,
if sr.EventCost != nil {
if !isMsg { // in case of one time charge there is no need of corrections
if notCharged := sUsage - sr.EventCost.GetUsage(); notCharged > 0 { // we did not charge enough, make a manual debit here
if sr.CD.LoopIndex > 0 {
sr.CD.TimeStart = sr.CD.TimeEnd
}
sr.CD.TimeEnd = sr.CD.TimeStart.Add(notCharged)
sr.CD.DurationIndex += notCharged
cc := new(engine.CallCost)
if err = sS.connMgr.Call(sS.cgrCfg.SessionSCfg().RALsConns, nil, utils.ResponderDebit,
&engine.CallDescriptorWithOpts{
CallDescriptor: sr.CD,
Opts: s.OptsStart,
}, cc); err == nil {
sr.EventCost.Merge(
engine.NewEventCostFromCallCost(cc, s.CGRID,
sr.Event.GetStringIgnoreErrors(utils.RunID)))
if !s.chargeable {
sS.pause(sr, notCharged)
} else {
if sr.CD.LoopIndex > 0 {
sr.CD.TimeStart = sr.CD.TimeEnd
}
sr.CD.TimeEnd = sr.CD.TimeStart.Add(notCharged)
sr.CD.DurationIndex += notCharged
cc := new(engine.CallCost)
if err = sS.connMgr.Call(sS.cgrCfg.SessionSCfg().RALsConns, nil, utils.ResponderDebit,
&engine.CallDescriptorWithOpts{
CallDescriptor: sr.CD,
Opts: s.OptsStart,
}, cc); err == nil {
sr.EventCost.Merge(
engine.NewEventCostFromCallCost(cc, s.CGRID,
sr.Event.GetStringIgnoreErrors(utils.RunID)))
}
}
} else if notCharged < 0 { // charged too much, try refund
if err = sS.refundSession(s, sRunIdx, -notCharged); err != nil {
@@ -2585,7 +2617,7 @@ func (sS *SessionS) BiRPCv1TerminateSession(clnt rpcclient.ClientConnector,
dbtItvl, isMsg, args.ForceDuration); err != nil {
return utils.NewErrRALs(err)
}
if _, err = sS.updateSession(s, ev, args.Opts, isMsg); err != nil {
if _, err = sS.updateSession(s, ev, opts, isMsg); err != nil {
return err
}
break
@@ -2593,6 +2625,9 @@ func (sS *SessionS) BiRPCv1TerminateSession(clnt rpcclient.ClientConnector,
if !isMsg {
s.UpdateSRuns(ev, sS.cgrCfg.SessionSCfg().AlterableFields)
}
s.Lock()
s.chargeable = opts.GetBoolOrDefault(utils.OptsChargeable, true)
s.Unlock()
if err = sS.terminateSession(s,
ev.GetDurationPtrIgnoreErrors(utils.Usage),
ev.GetDurationPtrIgnoreErrors(utils.LastUsed),
@@ -3396,6 +3431,10 @@ func (sS *SessionS) BiRPCv1ProcessEvent(clnt rpcclient.ClientConnector,
dbtItvl, false, ralsOpts.Has(utils.MetaFD)); err != nil {
return err
}
} else {
s.Lock()
s.chargeable = opts.GetBoolOrDefault(utils.OptsChargeable, true)
s.Unlock()
}
if err = sS.terminateSession(s,
ev.GetDurationPtrIgnoreErrors(utils.Usage),
@@ -3637,7 +3676,6 @@ func (sS *SessionS) BiRPCv1DeactivateSessions(clnt rpcclient.ClientConnector,
}
func (sS *SessionS) processCDR(cgrEv *utils.CGREvent, flags []string, rply *string, clnb bool) (err error) {
ev := engine.MapEvent(cgrEv.Event)
cgrID := GetSetCGRID(ev)
s := sS.getRelocateSession(cgrID,

View File

@@ -308,6 +308,7 @@ func testForceSTerminatorManualTermination(t *testing.T) {
NextAutoDebit: utils.TimePointer(time.Date(2020, time.April, 18, 23, 0, 0, 0, time.UTC)),
},
},
chargeable: true,
}
cfg := config.NewDefaultCGRConfig()
@@ -348,6 +349,7 @@ func testForceSTerminatorPostCDRs(t *testing.T) {
NextAutoDebit: utils.TimePointer(time.Date(2020, time.April, 18, 23, 0, 0, 0, time.UTC)),
},
},
chargeable: true,
}
expected := "INTERNALLY_DISCONNECTED"
@@ -384,6 +386,7 @@ func testForceSTerminatorReleaseSession(t *testing.T) {
NextAutoDebit: utils.TimePointer(time.Date(2020, time.April, 18, 23, 0, 0, 0, time.UTC)),
},
},
chargeable: true,
}
expected := "MANDATORY_IE_MISSING: [connIDs]"
@@ -432,6 +435,7 @@ func testForceSTerminatorClientCall(t *testing.T) {
NextAutoDebit: utils.TimePointer(time.Date(2020, time.April, 18, 23, 0, 0, 0, time.UTC)),
},
},
chargeable: true,
}
expected := "MANDATORY_IE_MISSING: [connIDs]"
@@ -464,6 +468,7 @@ func testDebitSession(t *testing.T) {
NextAutoDebit: utils.TimePointer(time.Date(2020, time.April, 18, 23, 0, 0, 0, time.UTC)),
},
},
chargeable: true,
}
//RunIdx cannot be higher than the length of sessions runs
@@ -551,6 +556,7 @@ func testDebitSessionResponderMaxDebit(t *testing.T) {
NextAutoDebit: utils.TimePointer(time.Date(2020, time.April, 18, 23, 0, 0, 0, time.UTC)),
},
},
chargeable: true,
}
if maxDur, err := sessions.debitSession(ss, 0, 5*time.Second,
@@ -614,6 +620,7 @@ func testDebitSessionResponderMaxDebitError(t *testing.T) {
NextAutoDebit: utils.TimePointer(time.Date(2020, time.April, 18, 23, 0, 0, 0, time.UTC)),
},
},
chargeable: true,
}
if maxDur, err := sessions.debitSession(ss, 0, 5*time.Minute,
@@ -668,6 +675,7 @@ func testInitSessionDebitLoops(t *testing.T) {
NextAutoDebit: utils.TimePointer(time.Date(2020, time.April, 18, 23, 0, 0, 0, time.UTC)),
},
},
chargeable: true,
}
sessions.initSessionDebitLoops(ss)
@@ -713,6 +721,7 @@ func testDebitLoopSessionErrorDebiting(t *testing.T) {
NextAutoDebit: utils.TimePointer(time.Date(2020, time.April, 18, 23, 0, 0, 0, time.UTC)),
},
},
chargeable: true,
}
// session already closed
@@ -800,6 +809,7 @@ func testDebitLoopSession(t *testing.T) {
NextAutoDebit: utils.TimePointer(time.Date(2020, time.April, 18, 23, 0, 0, 0, time.UTC)),
},
},
chargeable: true,
}
go func() {
if _, err := sessions.debitLoopSession(ss, 0, time.Second); err != nil {
@@ -860,6 +870,7 @@ func testDebitLoopSessionFrcDiscLowerDbtInterval(t *testing.T) {
NextAutoDebit: utils.TimePointer(time.Date(2020, time.April, 18, 23, 0, 0, 0, time.UTC)),
},
},
chargeable: true,
}
go func() {
if _, err := sessions.debitLoopSession(ss, 0, time.Second); err != nil {
@@ -913,6 +924,7 @@ func testDebitLoopSessionLowBalance(t *testing.T) {
NextAutoDebit: utils.TimePointer(time.Date(2020, time.April, 18, 23, 0, 0, 0, time.UTC)),
},
},
chargeable: true,
}
sessions.cgrCfg.SessionSCfg().MinDurLowBalance = 10 * time.Second
@@ -970,6 +982,7 @@ func testDebitLoopSessionWarningSessions(t *testing.T) {
NextAutoDebit: utils.TimePointer(time.Date(2020, time.April, 18, 23, 0, 0, 0, time.UTC)),
},
},
chargeable: true,
}
// will disconnect faster, MinDurLowBalance higher than the debit interval
@@ -1031,6 +1044,7 @@ func testDebitLoopSessionDisconnectSession(t *testing.T) {
NextAutoDebit: utils.TimePointer(time.Date(2020, time.April, 18, 23, 0, 0, 0, time.UTC)),
},
},
chargeable: true,
}
// will disconnect faster
@@ -1085,6 +1099,7 @@ func testStoreSCost(t *testing.T) {
},
},
},
chargeable: true,
}
if err := sessions.storeSCost(ss, 0); err != nil {
@@ -1132,6 +1147,7 @@ func testRefundSession(t *testing.T) {
},
},
},
chargeable: true,
}
expectedErr := "no event cost"
@@ -1266,6 +1282,7 @@ func testRoundCost(t *testing.T) {
},
},
},
chargeable: true,
}
//mocking an error API Call
@@ -1288,6 +1305,7 @@ func testDisconnectSession(t *testing.T) {
TotalUsage: time.Minute,
},
},
chargeable: true,
}
sTestMock := &testMockClientConn{}
@@ -1410,6 +1428,7 @@ func testNewSession(t *testing.T) {
},
},
},
chargeable: true,
}
if rcv, err := sessions.newSession(cgrEv, "resourceID", "clientConnID",
time.Second, false, false); err != nil {
@@ -2080,6 +2099,7 @@ func testEndSession(t *testing.T) {
},
},
},
chargeable: true,
}
activationTime := time.Date(2020, 21, 07, 10, 0, 0, 0, time.UTC)
@@ -2196,6 +2216,7 @@ func testBiRPCv1GetActivePassiveSessions(t *testing.T) {
},
},
},
chargeable: true,
}
sr2[utils.ToR] = utils.MetaSMS
sr2[utils.Subject] = "subject2"