From 7b01fb39181b13de7c362d5a3db3db903ea68d18 Mon Sep 17 00:00:00 2001 From: TeoV Date: Wed, 17 Jun 2020 16:12:39 +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 | 49 ++++++++-------- config/smconfig.go | 13 ++++ config/smconfig_test.go | 98 ++++++++++++++++--------------- packages/debian/changelog | 2 + sessions/sessions.go | 40 +++++++++---- sessions/sessions_data_it_test.go | 88 +++++++++++++++++++++++++++ utils/consts.go | 2 + 8 files changed, 210 insertions(+), 83 deletions(-) diff --git a/config/config_defaults.go b/config/config_defaults.go index 55f0556d8..cfed98566 100755 --- a/config/config_defaults.go +++ b/config/config_defaults.go @@ -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) diff --git a/config/libconfig_json.go b/config/libconfig_json.go index f1327b14a..3a1d0c27d 100755 --- a/config/libconfig_json.go +++ b/config/libconfig_json.go @@ -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 diff --git a/config/smconfig.go b/config/smconfig.go index 1c2a54f72..3af4c30e1 100644 --- a/config/smconfig.go +++ b/config/smconfig.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 @@ -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, diff --git a/config/smconfig_test.go b/config/smconfig_test.go index 6a9c6efad..aaece3f08 100644 --- a/config/smconfig_test.go +++ b/config/smconfig_test.go @@ -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) diff --git a/packages/debian/changelog b/packages/debian/changelog index e5edd2622..10c30ec35 100644 --- a/packages/debian/changelog +++ b/packages/debian/changelog @@ -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 Wed, 5 May 2020 15:22:59 +0200 diff --git a/sessions/sessions.go b/sessions/sessions.go index aad8502ea..355db856d 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 @@ -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() diff --git a/sessions/sessions_data_it_test.go b/sessions/sessions_data_it_test.go index 394a1eff1..e14003372 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, } ) @@ -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) diff --git a/utils/consts.go b/utils/consts.go index 01ac75ef4..ed6bcf81e 100755 --- a/utils/consts.go +++ b/utils/consts.go @@ -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"