Add SessionTLLLastUsage as option for an extra debit in case of ttl mechanism

This commit is contained in:
TeoV
2020-06-17 16:12:39 +03:00
committed by Dan Christian Bogos
parent cd86b8c3ca
commit 7b01fb3918
8 changed files with 210 additions and 83 deletions

View File

@@ -353,6 +353,7 @@ const CGRATES_CFG_JSON = `
//"session_ttl_max_delay": "", // activates session_ttl randomization and limits the maximum possible delay
//"session_ttl_last_used": "", // tweak LastUsed for sessions timing-out, not defined by default
//"session_ttl_usage": "", // tweak Usage for sessions timing-out, not defined by default
//"session_last_usage": "", // tweak LastUsage for session timing-out, not defined by default
"session_indexes": [], // index sessions based on these fields for GetActiveSessions API
"client_protocol": 1.0, // version of protocol to use when acting as JSON-PRC client <"0","1.0">
"channel_sync_interval": "0", // sync channels to detect stale sessions (0 to disable)

View File

@@ -192,30 +192,31 @@ type EventReaderJsonCfg struct {
// SM-Generic config section
type SessionSJsonCfg struct {
Enabled *bool
Listen_bijson *string
Chargers_conns *[]string
Rals_conns *[]string
Resources_conns *[]string
Thresholds_conns *[]string
Stats_conns *[]string
Suppliers_conns *[]string
Cdrs_conns *[]string
Replication_conns *[]string
Attributes_conns *[]string
Debit_interval *string
Store_session_costs *bool
Min_call_duration *string
Max_call_duration *string
Session_ttl *string
Session_ttl_max_delay *string
Session_ttl_last_used *string
Session_ttl_usage *string
Session_indexes *[]string
Client_protocol *float64
Channel_sync_interval *string
Terminate_attempts *int
Alterable_fields *[]string
Enabled *bool
Listen_bijson *string
Chargers_conns *[]string
Rals_conns *[]string
Resources_conns *[]string
Thresholds_conns *[]string
Stats_conns *[]string
Suppliers_conns *[]string
Cdrs_conns *[]string
Replication_conns *[]string
Attributes_conns *[]string
Debit_interval *string
Store_session_costs *bool
Min_call_duration *string
Max_call_duration *string
Session_ttl *string
Session_ttl_max_delay *string
Session_ttl_last_used *string
Session_ttl_usage *string
Session_ttl_last_usage *string
Session_indexes *[]string
Client_protocol *float64
Channel_sync_interval *string
Terminate_attempts *int
Alterable_fields *[]string
}
// FreeSWITCHAgent config section

View File

@@ -93,6 +93,7 @@ type SessionSCfg struct {
SessionTTLMaxDelay *time.Duration
SessionTTLLastUsed *time.Duration
SessionTTLUsage *time.Duration
SessionTTLLastUsage *time.Duration
SessionIndexes utils.StringMap
ClientProtocol float64
ChannelSyncInterval time.Duration
@@ -253,6 +254,13 @@ func (scfg *SessionSCfg) loadFromJsonCfg(jsnCfg *SessionSJsonCfg) (err error) {
scfg.SessionTTLUsage = &sessionTTLUsage
}
}
if jsnCfg.Session_ttl_last_usage != nil {
if sessionTTLLastUsage, err := utils.ParseDurationWithNanosecs(*jsnCfg.Session_ttl_last_usage); err != nil {
return err
} else {
scfg.SessionTTLLastUsage = &sessionTTLLastUsage
}
}
if jsnCfg.Session_indexes != nil {
scfg.SessionIndexes = utils.StringMapFromSlice(*jsnCfg.Session_indexes)
}
@@ -302,6 +310,10 @@ func (scfg *SessionSCfg) AsMapInterface() map[string]interface{} {
if scfg.SessionTTLUsage != nil {
sessionTTLUsage = scfg.SessionTTLUsage.String()
}
var sessionTTLLastUsage string = "0"
if scfg.SessionTTLLastUsage != nil {
sessionTTLLastUsage = scfg.SessionTTLLastUsage.String()
}
var channelSyncInterval string = "0"
if scfg.ChannelSyncInterval != 0 {
channelSyncInterval = scfg.ChannelSyncInterval.String()
@@ -400,6 +412,7 @@ func (scfg *SessionSCfg) AsMapInterface() map[string]interface{} {
utils.SessionTTLMaxDelayCfg: sessionTTLMaxDelay,
utils.SessionTTLLastUsedCfg: sessionTTLLastUsed,
utils.SessionTTLUsageCfg: sessionTTLUsage,
utils.SessionTTLLastUsageCfg: sessionTTLLastUsage,
utils.SessionIndexesCfg: scfg.SessionIndexes.Slice(),
utils.ClientProtocolCfg: scfg.ClientProtocol,
utils.ChannelSyncIntervalCfg: channelSyncInterval,

View File

@@ -159,30 +159,31 @@ func TestSessionSCfgAsMapInterface(t *testing.T) {
},
}`
eMap := map[string]interface{}{
"enabled": false,
"listen_bijson": "127.0.0.1:2014",
"chargers_conns": []string{},
"rals_conns": []string{},
"cdrs_conns": []string{},
"resources_conns": []string{},
"thresholds_conns": []string{},
"stats_conns": []string{},
"suppliers_conns": []string{},
"attributes_conns": []string{},
"replication_conns": []string{},
"debit_interval": "0",
"store_session_costs": false,
"min_call_duration": "0",
"max_call_duration": "3h0m0s",
"session_ttl": "0",
"session_indexes": []string{},
"client_protocol": 1.0,
"channel_sync_interval": "0",
"terminate_attempts": 5,
"alterable_fields": []string{},
"session_ttl_last_used": "0",
"session_ttl_max_delay": "0",
"session_ttl_usage": "0",
"enabled": false,
"listen_bijson": "127.0.0.1:2014",
"chargers_conns": []string{},
"rals_conns": []string{},
"cdrs_conns": []string{},
"resources_conns": []string{},
"thresholds_conns": []string{},
"stats_conns": []string{},
"suppliers_conns": []string{},
"attributes_conns": []string{},
"replication_conns": []string{},
"debit_interval": "0",
"store_session_costs": false,
"min_call_duration": "0",
"max_call_duration": "3h0m0s",
"session_ttl": "0",
"session_indexes": []string{},
"client_protocol": 1.0,
"channel_sync_interval": "0",
"terminate_attempts": 5,
"alterable_fields": []string{},
"session_ttl_last_used": "0",
"session_ttl_max_delay": "0",
"session_ttl_usage": "0",
"session_ttl_last_usage": "0",
}
if jsnCfg, err := NewCgrJsonCfgFromBytes([]byte(cfgJSONStr)); err != nil {
t.Error(err)
@@ -227,30 +228,31 @@ func TestSessionSCfgAsMapInterface(t *testing.T) {
},
}`
eMap = map[string]interface{}{
"enabled": false,
"listen_bijson": "127.0.0.1:2014",
"chargers_conns": []string{"*internal"},
"rals_conns": []string{"*internal"},
"cdrs_conns": []string{"*internal"},
"resources_conns": []string{"*internal"},
"thresholds_conns": []string{"*internal"},
"stats_conns": []string{"*internal"},
"suppliers_conns": []string{"*internal"},
"attributes_conns": []string{"*internal"},
"replication_conns": []string{"*localhost"},
"debit_interval": "0",
"store_session_costs": false,
"min_call_duration": "0",
"max_call_duration": "3h0m0s",
"session_ttl": "0",
"session_indexes": []string{},
"client_protocol": 1.0,
"channel_sync_interval": "0",
"terminate_attempts": 5,
"alterable_fields": []string{},
"session_ttl_last_used": "0",
"session_ttl_max_delay": "0",
"session_ttl_usage": "0",
"enabled": false,
"listen_bijson": "127.0.0.1:2014",
"chargers_conns": []string{"*internal"},
"rals_conns": []string{"*internal"},
"cdrs_conns": []string{"*internal"},
"resources_conns": []string{"*internal"},
"thresholds_conns": []string{"*internal"},
"stats_conns": []string{"*internal"},
"suppliers_conns": []string{"*internal"},
"attributes_conns": []string{"*internal"},
"replication_conns": []string{"*localhost"},
"debit_interval": "0",
"store_session_costs": false,
"min_call_duration": "0",
"max_call_duration": "3h0m0s",
"session_ttl": "0",
"session_indexes": []string{},
"client_protocol": 1.0,
"channel_sync_interval": "0",
"terminate_attempts": 5,
"alterable_fields": []string{},
"session_ttl_last_used": "0",
"session_ttl_max_delay": "0",
"session_ttl_usage": "0",
"session_ttl_last_usage": "0",
}
if jsnCfg, err := NewCgrJsonCfgFromBytes([]byte(cfgJSONStr)); err != nil {
t.Error(err)

View File

@@ -39,6 +39,8 @@ cgrates (0.10.1) UNRELEASED; urgency=medium
* [SupplierS] Allow multiple suppliers with the same ID
* [Engine] Skip caching if limit is 0
* [CacheS] Avoid long recaching
* [SessionS] Use correctly SessionTTLUsage when calculate end usage in case of terminate session from ttl mechanism
* [SessionS] Add SessionTLLLastUsage as option for an extra debit in case of ttl mechanism
-- DanB <danb@cgrates.org> Wed, 5 May 2020 15:22:59 +0200

View File

@@ -197,11 +197,12 @@ type riFieldNameVal struct {
// sTerminator holds the info needed to force-terminate sessions based on timer
type sTerminator struct {
timer *time.Timer
endChan chan struct{}
ttl time.Duration
ttlLastUsed *time.Duration
ttlUsage *time.Duration
timer *time.Timer
endChan chan struct{}
ttl time.Duration
ttlLastUsed *time.Duration
ttlUsage *time.Duration
ttlLastUsage *time.Duration
}
// setSTerminator installs a new terminator for a session
@@ -258,6 +259,15 @@ func (sS *SessionS) setSTerminator(s *Session) {
utils.SessionS, utils.SessionTTLUsage, s.EventStart.String(), err.Error()))
return
}
// TTLLastUsage
ttlLastUsage, err := s.EventStart.GetDurationPtrOrDefault(
utils.SessionTTLLastUsage, sS.cgrCfg.SessionSCfg().SessionTTLLastUsage)
if err != nil {
utils.Logger.Warning(
fmt.Sprintf("<%s>, cannot extract <%s> from event: <%s>, err: <%s>",
utils.SessionS, utils.SessionTTLLastUsage, s.EventStart.String(), err.Error()))
return
}
// previously defined, reset
if s.sTerminator != nil {
s.sTerminator.ttl = ttl
@@ -267,23 +277,31 @@ func (sS *SessionS) setSTerminator(s *Session) {
if ttlUsage != nil {
s.sTerminator.ttlUsage = ttlUsage
}
if ttlLastUsage != nil {
s.sTerminator.ttlLastUsage = ttlLastUsage
}
s.sTerminator.timer.Reset(s.sTerminator.ttl)
return
}
// new set
s.sTerminator = &sTerminator{
timer: time.NewTimer(ttl),
endChan: make(chan struct{}),
ttl: ttl,
ttlLastUsed: ttlLastUsed,
ttlUsage: ttlUsage,
timer: time.NewTimer(ttl),
endChan: make(chan struct{}),
ttl: ttl,
ttlLastUsed: ttlLastUsed,
ttlUsage: ttlUsage,
ttlLastUsage: ttlLastUsage,
}
// schedule automatic termination
go func() {
select {
case <-s.sTerminator.timer.C:
sS.forceSTerminate(s, s.sTerminator.ttl,
lastUsage := s.sTerminator.ttl
if s.sTerminator.ttlLastUsage != nil {
lastUsage = *s.sTerminator.ttlLastUsage
}
sS.forceSTerminate(s, lastUsage,
s.sTerminator.ttlUsage, s.sTerminator.ttlLastUsed)
case <-s.sTerminator.endChan:
s.sTerminator.timer.Stop()

View File

@@ -49,6 +49,7 @@ var (
testSessionsDataTTLExpMultiUpdates,
testSessionsDataMultipleDataNoUsage,
testSessionsDataTTLUsageProtection,
testSessionsDataTTLLastUsage,
testSessionsDataTTKillEngine,
}
)
@@ -1057,6 +1058,93 @@ func testSessionsDataTTLUsageProtection(t *testing.T) {
}
}
func testSessionsDataTTLLastUsage(t *testing.T) {
var acnt *engine.Account
acntAttrs := &utils.AttrGetAccount{Tenant: "cgrates.org",
Account: "testSessionsDataTTLLastUsage"}
eAcntVal := 102400.0
attrSetBalance := utils.AttrSetBalance{
Tenant: acntAttrs.Tenant, Account: acntAttrs.Account,
BalanceType: utils.DATA,
Value: eAcntVal,
Balance: map[string]interface{}{
utils.ID: "testSessionsDataTTLLastUsage",
},
}
var reply string
if err := sDataRPC.Call(utils.APIerSv2SetBalance, attrSetBalance, &reply); err != nil {
t.Error(err)
} else if reply != utils.OK {
t.Errorf("Received: %s", reply)
}
if err := sDataRPC.Call(utils.APIerSv2GetAccount, acntAttrs, &acnt); err != nil {
t.Error(err)
} else if totalVal := acnt.BalanceMap[utils.DATA].GetTotalValue(); totalVal != eAcntVal {
t.Errorf("Expected: %f, received: %f", eAcntVal, totalVal)
}
usage := int64(1024)
initArgs := &V1InitSessionArgs{
InitSession: true,
CGREvent: &utils.CGREvent{
Tenant: "cgrates.org",
ID: "TestSessionsDataTTLLastUsage",
Event: map[string]interface{}{
utils.EVENT_NAME: "testSessionsDataTTLLastUsage",
utils.ToR: utils.DATA,
utils.OriginID: "testSessionsDataTTLLastUsage",
utils.Account: acntAttrs.Account,
utils.Subject: acntAttrs.Account,
utils.Destination: utils.DATA,
utils.Category: "data",
utils.Tenant: "cgrates.org",
utils.RequestType: utils.META_PREPAID,
utils.SetupTime: time.Date(2016, time.January, 5, 18, 30, 59, 0, time.UTC),
utils.AnswerTime: time.Date(2016, time.January, 5, 18, 31, 05, 0, time.UTC),
utils.Usage: "1024",
utils.SessionTTLLastUsage: "2048",
},
},
}
var initRpl *V1InitSessionReply
if err := sDataRPC.Call(utils.SessionSv1InitiateSession,
initArgs, &initRpl); err != nil {
t.Error(err)
}
if initRpl.MaxUsage.Nanoseconds() != usage {
t.Errorf("Expecting : %+v, received: %+v", usage, initRpl.MaxUsage.Nanoseconds())
}
eAcntVal = 101376.000000
if err := sDataRPC.Call(utils.APIerSv2GetAccount, acntAttrs, &acnt); err != nil {
t.Error(err)
} else if dataVal := acnt.BalanceMap[utils.DATA].GetTotalValue(); dataVal != eAcntVal {
t.Errorf("Expected: %f, received: %f", eAcntVal, dataVal)
}
time.Sleep(70 * time.Millisecond)
eAcntVal = 99328.000000 // 101376.000000 ( units remains after init session ) - SessionTTLLastUsage ( 2048 )
if err := sDataRPC.Call(utils.APIerSv2GetAccount, acntAttrs, &acnt); err != nil {
t.Error(err)
} else if dataVal := acnt.BalanceMap[utils.DATA].GetTotalValue(); dataVal != eAcntVal {
t.Errorf("Expected: %f, received: %f", eAcntVal, dataVal)
}
// verify the cdr usage to be 3072 ( init usage ( 1024 ) + SessionTTLLastUsage ( 2048 ) )
var cdrs []*engine.ExternalCDR
req := utils.RPCCDRsFilter{Accounts: []string{acntAttrs.Account}}
if err := sDataRPC.Call(utils.APIerSv2GetCDRs, &req, &cdrs); err != nil {
t.Error("Unexpected error: ", err.Error())
} else if len(cdrs) != 1 {
t.Error("Unexpected number of CDRs returned: ", len(cdrs))
} else {
if cdrs[0].Usage != "3072" {
t.Errorf("Unexpected CDR Usage received, cdr: %v %+v ", cdrs[0].Usage, cdrs[0])
}
}
}
func testSessionsDataTTKillEngine(t *testing.T) {
if err := engine.KillEngine(100); err != nil {
t.Error(err)

View File

@@ -333,6 +333,7 @@ const (
SessionTTLMaxDelay = "SessionTTLMaxDelay"
SessionTTLLastUsed = "SessionTTLLastUsed"
SessionTTLUsage = "SessionTTLUsage"
SessionTTLLastUsage = "SessionTTLLastUsage"
HandlerSubstractUsage = "*substract_usage"
XML = "xml"
MetaGOB = "*gob"
@@ -1705,6 +1706,7 @@ const (
SessionTTLMaxDelayCfg = "session_ttl_max_delay"
SessionTTLLastUsedCfg = "session_ttl_last_used"
SessionTTLUsageCfg = "session_ttl_usage"
SessionTTLLastUsageCfg = "session_ttl_last_usage"
SessionIndexesCfg = "session_indexes"
ClientProtocolCfg = "client_protocol"
ChannelSyncIntervalCfg = "channel_sync_interval"