From a7ea24a3efad1b2040d652867b1cb468fd3772ff Mon Sep 17 00:00:00 2001 From: TeoV Date: Wed, 17 Jun 2020 15:55:13 +0300 Subject: [PATCH] Add SessionTLLLastUsage as option for an extra debit in case of ttl mechanism --- config/config_defaults.go | 1 + config/libconfig_json.go | 55 ++++++++-------- config/sessionscfg.go | 13 ++++ config/sessionscfg_test.go | 102 +++++++++++++++--------------- packages/debian/changelog | 1 + sessions/sessions.go | 46 ++++++++++---- sessions/sessions_data_it_test.go | 90 ++++++++++++++++++++++++++ utils/consts.go | 14 ++-- 8 files changed, 228 insertions(+), 94 deletions(-) diff --git a/config/config_defaults.go b/config/config_defaults.go index 0ba0ec668..48474fd2f 100755 --- a/config/config_defaults.go +++ b/config/config_defaults.go @@ -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) diff --git a/config/libconfig_json.go b/config/libconfig_json.go index b4051f6b6..cf4b815ea 100755 --- a/config/libconfig_json.go +++ b/config/libconfig_json.go @@ -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 diff --git a/config/sessionscfg.go b/config/sessionscfg.go index 6ec5f2d91..e8bfa9233 100644 --- a/config/sessionscfg.go +++ b/config/sessionscfg.go @@ -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, diff --git a/config/sessionscfg_test.go b/config/sessionscfg_test.go index 072a12851..00c01d8a9 100644 --- a/config/sessionscfg_test.go +++ b/config/sessionscfg_test.go @@ -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", diff --git a/packages/debian/changelog b/packages/debian/changelog index 436965f18..0be0f4e2d 100644 --- a/packages/debian/changelog +++ b/packages/debian/changelog @@ -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 Wed, 19 Feb 2020 13:25:52 +0200 diff --git a/sessions/sessions.go b/sessions/sessions.go index 08bd8a41e..bce9f5c53 100644 --- a/sessions/sessions.go +++ b/sessions/sessions.go @@ -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() diff --git a/sessions/sessions_data_it_test.go b/sessions/sessions_data_it_test.go index e75c12be2..4edaa6b3a 100644 --- a/sessions/sessions_data_it_test.go +++ b/sessions/sessions_data_it_test.go @@ -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) diff --git a/utils/consts.go b/utils/consts.go index a36b49f45..9c582be45 100755 --- a/utils/consts.go +++ b/utils/consts.go @@ -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"