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

This commit is contained in:
TeoV
2020-06-17 15:55:13 +03:00
committed by Dan Christian Bogos
parent a23b501c05
commit a7ea24a3ef
8 changed files with 228 additions and 94 deletions

View File

@@ -413,6 +413,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

@@ -224,33 +224,34 @@ type EventExporterJsonCfg struct {
// SessionSJsonCfg 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
Routes_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
Min_dur_low_balance *string
Scheduler_conns *[]string
Stir *STIRJsonCfg
Enabled *bool
Listen_bijson *string
Chargers_conns *[]string
Rals_conns *[]string
Resources_conns *[]string
Thresholds_conns *[]string
Stats_conns *[]string
Routes_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
Min_dur_low_balance *string
Scheduler_conns *[]string
Stir *STIRJsonCfg
}
// 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
@@ -255,6 +256,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)
}
@@ -320,6 +328,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()
@@ -431,6 +443,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

@@ -160,31 +160,32 @@ 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{},
"routes_conns": []string{},
"attributes_conns": []string{},
"replication_conns": []string{},
"debit_interval": "0",
"store_session_costs": false,
"min_call_duration": "0",
"max_call_duration": "3h0m0s",
"min_dur_low_balance": "0",
"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{},
"routes_conns": []string{},
"attributes_conns": []string{},
"replication_conns": []string{},
"debit_interval": "0",
"store_session_costs": false,
"min_call_duration": "0",
"max_call_duration": "3h0m0s",
"min_dur_low_balance": "0",
"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",
"stir": map[string]interface{}{
"allowed_attest": []string{"*any"},
"payload_maxduration": "-1",
@@ -237,31 +238,32 @@ 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"},
"routes_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",
"min_dur_low_balance": "0",
"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"},
"routes_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",
"min_dur_low_balance": "0",
"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",
"stir": map[string]interface{}{
"allowed_attest": []string{"*any"},
"payload_maxduration": "-1",

View File

@@ -69,6 +69,7 @@ cgrates (0.11.0~dev) UNRELEASED; urgency=medium
* [FilterS] Updated Filter indexes
* [General] Added *mo+extraDuration time support (e.g. *mo+1h will be time.Now() + 1 month + 1 hour)
* [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, 19 Feb 2020 13:25:52 +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
@@ -261,6 +262,21 @@ func (sS *SessionS) setSTerminator(s *Session, opts engine.MapEvent) {
utils.SessionS, utils.SessionTTLLastUsed, s.CGRID, opts.String(), err.Error()))
return
}
// LastUsage
var ttlLastUsage *time.Duration
if opts.HasField(utils.SessionTTLLastUsage) {
ttlLastUsage, err = opts.GetDurationPtr(utils.SessionTTLLastUsage)
} else if s.OptsStart.HasField(utils.SessionTTLLastUsage) {
ttlLastUsage, err = s.OptsStart.GetDurationPtr(utils.SessionTTLLastUsage)
} else {
ttlLastUsage = sS.cgrCfg.SessionSCfg().SessionTTLLastUsage
}
if err != nil {
utils.Logger.Warning(
fmt.Sprintf("<%s>, cannot extract <%s> for session:<%s>, from it's options: <%s>, err: <%s>",
utils.SessionS, utils.SessionTTLLastUsage, s.CGRID, opts.String(), err.Error()))
return
}
// TTLUsage
var ttlUsage *time.Duration
if opts.HasField(utils.SessionTTLUsage) {
@@ -285,23 +301,31 @@ func (sS *SessionS) setSTerminator(s *Session, opts engine.MapEvent) {
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, s.sTerminator.ttlUsage,
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,
}
)
@@ -1068,6 +1069,95 @@ 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,
Opts: map[string]interface{}{
utils.SessionTTLLastUsage: "2048",
},
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",
},
},
}
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

@@ -1843,6 +1843,7 @@ const (
SessionTTLCfg = "session_ttl"
SessionTTLMaxDelayCfg = "session_ttl_max_delay"
SessionTTLLastUsedCfg = "session_ttl_last_used"
SessionTTLLastUsageCfg = "session_ttl_last_usage"
SessionTTLUsageCfg = "session_ttl_usage"
SessionIndexesCfg = "session_indexes"
ClientProtocolCfg = "client_protocol"
@@ -2140,12 +2141,12 @@ var (
MetaZeroLeft = "*zeroleft"
)
// CGROptionsSet the posible cgr options
// CGROptionsSet the possible cgr options
var CGROptionsSet = NewStringSet([]string{STIRATest, STIRPayloadMaxDuration,
STIRIdentity, STIROriginatorTn, STIROriginatorURI, STIRDestinationTn,
STIRDestinationURI, STIRPublicKeyPath, STIRPrivateKeyPath,
DebitInterval, Context, SessionTTL, SessionTTLMaxDelay,
SessionTTLLastUsed, SessionTTLUsage, APIKey, RouteID})
SessionTTLLastUsed, SessionTTLLastUsage, SessionTTLUsage, APIKey, RouteID})
// SessionS ProccessEvent posible options
const (
@@ -2164,10 +2165,11 @@ const (
Context = "Context"
// SessionS terminator
SessionTTL = "SessionTTL"
SessionTTLMaxDelay = "SessionTTLMaxDelay"
SessionTTLLastUsed = "SessionTTLLastUsed"
SessionTTLUsage = "SessionTTLUsage"
SessionTTL = "SessionTTL"
SessionTTLMaxDelay = "SessionTTLMaxDelay"
SessionTTLLastUsed = "SessionTTLLastUsed"
SessionTTLLastUsage = "SessionTTLLastUsage"
SessionTTLUsage = "SessionTTLUsage"
// DispatcherS
APIKey = "APIKey"