From 82ec0dbbca044ba5e4ca12d4278a445219fb28bd Mon Sep 17 00:00:00 2001 From: DanB Date: Mon, 5 May 2025 09:26:50 +0200 Subject: [PATCH] Adding TotalUsage support for update events in sessions --- sessions/session.go | 29 ++++++ sessions/sessions.go | 14 ++- sessions/sessions_voice_it_test.go | 161 +++++++++++++++++++++++++++++ 3 files changed, 201 insertions(+), 3 deletions(-) diff --git a/sessions/session.go b/sessions/session.go index 23ea09cca..2f3e74e25 100644 --- a/sessions/session.go +++ b/sessions/session.go @@ -19,6 +19,7 @@ along with this program. If not, see 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 + } +} diff --git a/sessions/sessions.go b/sessions/sessions.go index 3de76339f..042c7e821 100644 --- a/sessions/sessions.go +++ b/sessions/sessions.go @@ -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 diff --git a/sessions/sessions_voice_it_test.go b/sessions/sessions_voice_it_test.go index 9ebb363de..9523918b1 100644 --- a/sessions/sessions_voice_it_test.go +++ b/sessions/sessions_voice_it_test.go @@ -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"}