Adding TotalUsage support for update events in sessions

This commit is contained in:
DanB
2025-05-05 09:26:50 +02:00
parent 9990da643c
commit 82ec0dbbca
3 changed files with 201 additions and 3 deletions

View File

@@ -19,6 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
package sessions
import (
"math"
"runtime"
"sync"
"time"
@@ -279,6 +280,19 @@ func (s *Session) totalUsage() (tDur time.Duration) {
return
}
// lastUsage returns the first session run last usage
// not thread save
func (s *Session) lastUsage() (lUsage time.Duration) {
if len(s.SRuns) == 0 {
return
}
for _, sr := range s.SRuns {
lUsage = sr.LastUsage
break // only first
}
return
}
// AsCGREvents is a method to return the Session as CGREvents
// AsCGREvents is not thread safe since it is supposed to run by the time Session is closed
func (s *Session) asCGREvents() (cgrEvs []*utils.CGREvent) {
@@ -392,3 +406,18 @@ func (s *Session) UpdateSRuns(updEv engine.MapEvent, alterableFields utils.Strin
s.updateSRuns(updEv, alterableFields)
s.Unlock()
}
// midSessionUsage computes the midSessionUsage out of totalUsage, considering what it has been debitted so far
// lastUsage is returned for the case when too much was debitted so it can be passed to the debit function as reserve
func (s *Session) midSessionUsage(totalUsage time.Duration) (usage time.Duration, lastUsage *time.Duration) {
sTUsage := s.totalUsage()
if sTUsage == 0 {
return totalUsage, nil
}
if midUsage := totalUsage - sTUsage; midUsage >= 0 {
return midUsage, nil
} else {
tLastUsage := time.Duration(int(math.Abs(float64(midUsage)))) + s.lastUsage()
return 0, &tLastUsage
}
}

View File

@@ -1681,10 +1681,18 @@ func (sS *SessionS) updateSession(s *Session, updtEv, opts engine.MapEvent, isMs
if err != utils.ErrNotFound {
return
}
err = nil
reqMaxUsage = sS.cgrCfg.SessionSCfg().GetDefaultUsage(updtEv.GetStringIgnoreErrors(utils.ToR))
updtEv[utils.Usage] = reqMaxUsage
}
lastUsed := updtEv.GetDurationPtrIgnoreErrors(utils.LastUsed)
var totalUsage time.Duration
if totalUsage, err = updtEv.GetDuration(utils.TotalUsage); err != nil && err != utils.ErrNotFound {
return
} else {
reqMaxUsage, lastUsed = s.midSessionUsage(totalUsage)
}
err = nil
maxUsage = make(map[string]time.Duration)
for i, sr := range s.SRuns {
reqType := sr.Event.GetStringIgnoreErrors(utils.RequestType)
@@ -1692,8 +1700,8 @@ func (sS *SessionS) updateSession(s *Session, updtEv, opts engine.MapEvent, isMs
switch reqType {
case utils.MetaPrepaid, utils.MetaDynaprepaid:
if s.debitStop == nil {
if rplyMaxUsage, err = sS.debitSession(s, i, reqMaxUsage,
updtEv.GetDurationPtrIgnoreErrors(utils.LastUsed)); err != nil {
if rplyMaxUsage, err = sS.debitSession(s, i,
reqMaxUsage, lastUsed); err != nil {
return
}
break

View File

@@ -782,6 +782,167 @@ func testSessionsVoiceLastUsedNotFixed(t *testing.T) {
}
}
func testSessionsVoiceTotalUsed(t *testing.T) {
var acnt *engine.Account
attrs := &utils.AttrGetAccount{Tenant: "cgrates.org", Account: "1001"}
eAcntVal := 8.790000
if err := sessionsRPC.Call(context.Background(), utils.APIerSv2GetAccount, attrs, &acnt); err != nil {
t.Error(err)
} else if acnt.BalanceMap[utils.MetaMonetary].GetTotalValue() != eAcntVal {
t.Errorf("Expected: %f, received: %f", eAcntVal, acnt.BalanceMap[utils.MetaMonetary].GetTotalValue())
}
usage := 2 * time.Minute
initArgs := &V1InitSessionArgs{
InitSession: true,
CGREvent: &utils.CGREvent{
Tenant: "cgrates.org",
ID: "TestSessionsVoiceLastUsed",
Event: map[string]any{
utils.EventName: "TEST_EVENT",
utils.ToR: utils.MetaVoice,
utils.OriginID: "12350",
utils.AccountField: "1001",
utils.Subject: "1001",
utils.Destination: "1006",
utils.Category: "call",
utils.Tenant: "cgrates.org",
utils.RequestType: utils.MetaPrepaid,
utils.SetupTime: time.Date(2016, time.January, 5, 18, 30, 49, 0, time.UTC),
utils.AnswerTime: time.Date(2016, time.January, 5, 18, 31, 05, 0, time.UTC),
utils.Usage: usage,
},
},
}
var initRpl *V1InitSessionReply
if err := sessionsRPC.Call(context.Background(), utils.SessionSv1InitiateSession,
initArgs, &initRpl); err != nil {
t.Error(err)
}
if initRpl.MaxUsage == nil || *initRpl.MaxUsage != usage {
t.Errorf("Expecting : %+v, received: %+v", usage, initRpl.MaxUsage)
}
eAcntVal = 7.39002
if err := sessionsRPC.Call(context.Background(), utils.APIerSv2GetAccount, attrs, &acnt); err != nil {
t.Error(err)
} else if acnt.BalanceMap[utils.MetaMonetary].GetTotalValue() != eAcntVal {
t.Errorf("Expected: %f, received: %f", eAcntVal, acnt.BalanceMap[utils.MetaMonetary].GetTotalValue())
}
lastUsage := time.Minute + 30*time.Second
updateArgs := &V1UpdateSessionArgs{
UpdateSession: true,
CGREvent: &utils.CGREvent{
Tenant: "cgrates.org",
ID: "TestSessionsVoiceLastUsed",
Event: map[string]any{
utils.EventName: "Update1",
utils.ToR: utils.MetaVoice,
utils.OriginID: "12350",
utils.AccountField: "1001",
utils.Subject: "1001",
utils.Destination: "1006",
utils.Category: "call",
utils.Tenant: "cgrates.org",
utils.RequestType: utils.MetaPrepaid,
utils.Usage: usage,
utils.LastUsed: lastUsage,
},
},
}
var updateRpl *V1UpdateSessionReply
if err := sessionsRPC.Call(context.Background(), utils.SessionSv1UpdateSession, updateArgs, &updateRpl); err != nil {
t.Error(err)
}
if updateRpl.MaxUsage == nil || *updateRpl.MaxUsage != usage {
t.Errorf("Expected: %+v, received: %+v", usage, updateRpl.MaxUsage)
}
eAcntVal = 7.09005
if err := sessionsRPC.Call(context.Background(), utils.APIerSv2GetAccount, attrs, &acnt); err != nil {
t.Error(err)
} else if acnt.BalanceMap[utils.MetaMonetary].GetTotalValue() != eAcntVal {
t.Errorf("Expected: %f, received: %f", eAcntVal, acnt.BalanceMap[utils.MetaMonetary].GetTotalValue())
}
lastUsage = 2*time.Minute + 30*time.Second
updateArgs = &V1UpdateSessionArgs{
UpdateSession: true,
CGREvent: &utils.CGREvent{
Tenant: "cgrates.org",
ID: "TestSessionsVoiceLastUsed",
Event: map[string]any{
utils.EventName: "Update2",
utils.ToR: utils.MetaVoice,
utils.OriginID: "12350",
utils.AccountField: "1001",
utils.Subject: "1001",
utils.Destination: "1006",
utils.Category: "call",
utils.Tenant: "cgrates.org",
utils.RequestType: utils.MetaPrepaid,
utils.SetupTime: time.Date(2016, time.January, 5, 18, 30, 49, 0, time.UTC),
utils.AnswerTime: time.Date(2016, time.January, 5, 18, 31, 05, 0, time.UTC),
utils.Usage: usage,
utils.LastUsed: lastUsage,
},
},
}
if err := sessionsRPC.Call(context.Background(), utils.SessionSv1UpdateSession, updateArgs, &updateRpl); err != nil {
t.Error(err)
}
if updateRpl.MaxUsage == nil || *updateRpl.MaxUsage != usage {
t.Errorf("Expected: %+v, received: %+v", usage, updateRpl.MaxUsage)
}
eAcntVal = 6.590100
if err := sessionsRPC.Call(context.Background(), utils.APIerSv2GetAccount, attrs, &acnt); err != nil {
t.Error(err)
} else if acnt.BalanceMap[utils.MetaMonetary].GetTotalValue() != eAcntVal {
t.Errorf("Expected: %f, received: %f", eAcntVal, acnt.BalanceMap[utils.MetaMonetary].GetTotalValue())
}
usage = time.Minute
termArgs := &V1TerminateSessionArgs{
TerminateSession: true,
CGREvent: &utils.CGREvent{
Tenant: "cgrates.org",
ID: "TestSessionsVoiceLastUsed",
Event: map[string]any{
utils.EventName: "TEST_EVENT",
utils.ToR: utils.MetaVoice,
utils.OriginID: "12350",
utils.AccountField: "1001",
utils.Subject: "1001",
utils.Destination: "1006",
utils.Category: "call",
utils.Tenant: "cgrates.org",
utils.RequestType: utils.MetaPrepaid,
utils.SetupTime: time.Date(2016, time.January, 5, 18, 30, 49, 0, time.UTC),
utils.AnswerTime: time.Date(2016, time.January, 5, 18, 31, 05, 0, time.UTC),
utils.Usage: usage,
},
},
}
var rpl string
if err := sessionsRPC.Call(context.Background(), utils.SessionSv1TerminateSession, termArgs, &rpl); err != nil || rpl != utils.OK {
t.Error(err)
}
eAcntVal = 7.59
if err := sessionsRPC.Call(context.Background(), utils.APIerSv2GetAccount, attrs, &acnt); err != nil {
t.Error(err)
} else if acnt.BalanceMap[utils.MetaMonetary].GetTotalValue() != eAcntVal {
t.Errorf("Expected: %f, received: %f",
eAcntVal, acnt.BalanceMap[utils.MetaMonetary].GetTotalValue())
}
}
func testSessionsVoiceSessionTTL(t *testing.T) {
var acnt *engine.Account
attrs := &utils.AttrGetAccount{Tenant: "cgrates.org", Account: "1001"}