diff --git a/accounts/libaccounts.go b/accounts/libaccounts.go
index 0045a44d3..5332c783c 100644
--- a/accounts/libaccounts.go
+++ b/accounts/libaccounts.go
@@ -102,7 +102,7 @@ func processAttributeS(connMgr *engine.ConnManager, cgrEv *utils.CGREvent,
attrArgs := &engine.AttrArgsProcessEvent{
Context: utils.StringPointer(utils.FirstNonEmpty(
engine.MapEvent(cgrEv.Opts).GetStringIgnoreErrors(utils.OptsContext),
- utils.MetaAccountS)),
+ utils.MetaAccounts)),
CGREvent: cgrEv,
AttributeIDs: attrIDs,
ProcessRuns: procRuns,
diff --git a/engine/eventcost.go b/engine/eventcost.go
index 6d82d8f94..160bef0e4 100644
--- a/engine/eventcost.go
+++ b/engine/eventcost.go
@@ -54,15 +54,16 @@ func NewEventCostFromCallCost(cc *CallCost, cgrID, runID string) (ec *EventCost)
cIl := &ChargingInterval{CompressFactor: ts.CompressFactor}
rf := RatingMatchedFilters{"Subject": ts.MatchedSubject, "DestinationPrefix": ts.MatchedPrefix,
"DestinationID": ts.MatchedDestId, "RatingPlanID": ts.RatingPlanId}
- cIl.RatingID = ec.ratingIDForRateInterval(ts.RateInterval, rf)
+ isPause := ts.RatingPlanId == utils.MetaPause
+ cIl.RatingID = ec.ratingIDForRateInterval(ts.RateInterval, rf, isPause)
if len(ts.Increments) != 0 {
cIl.Increments = make([]*ChargingIncrement, 0, len(ts.Increments)+1)
}
for _, incr := range ts.Increments {
- cIl.Increments = append(cIl.Increments, ec.newChargingIncrement(incr, rf, false))
+ cIl.Increments = append(cIl.Increments, ec.newChargingIncrement(incr, rf, false, isPause))
}
if ts.RoundIncrement != nil {
- rIncr := ec.newChargingIncrement(ts.RoundIncrement, rf, true)
+ rIncr := ec.newChargingIncrement(ts.RoundIncrement, rf, true, false)
rIncr.Cost = -rIncr.Cost
cIl.Increments = append(cIl.Increments, rIncr)
}
@@ -73,7 +74,7 @@ func NewEventCostFromCallCost(cc *CallCost, cgrID, runID string) (ec *EventCost)
// newChargingIncrement creates ChargingIncrement from a Increment
// special case if is the roundIncrement the rateID is *rounding
-func (ec *EventCost) newChargingIncrement(incr *Increment, rf RatingMatchedFilters, roundedIncrement bool) (cIt *ChargingIncrement) {
+func (ec *EventCost) newChargingIncrement(incr *Increment, rf RatingMatchedFilters, roundedIncrement, isPause bool) (cIt *ChargingIncrement) {
cIt = &ChargingIncrement{
Usage: incr.Duration,
Cost: incr.Cost,
@@ -82,6 +83,9 @@ func (ec *EventCost) newChargingIncrement(incr *Increment, rf RatingMatchedFilte
if incr.BalanceInfo == nil {
return
}
+ if roundedIncrement {
+ isPause = false
+ }
rateID := utils.MetaRounding
//AccountingID
if incr.BalanceInfo.Unit != nil {
@@ -89,40 +93,53 @@ func (ec *EventCost) newChargingIncrement(incr *Increment, rf RatingMatchedFilte
ecUUID := utils.MetaNone // populate no matter what due to Unit not nil
if incr.BalanceInfo.Monetary != nil {
if !roundedIncrement {
- rateID = ec.ratingIDForRateInterval(incr.BalanceInfo.Monetary.RateInterval, rf)
+ rateID = ec.ratingIDForRateInterval(incr.BalanceInfo.Monetary.RateInterval, rf, isPause)
}
- if uuid := ec.Accounting.GetIDWithSet(
- &BalanceCharge{
- AccountID: incr.BalanceInfo.AccountID,
- BalanceUUID: incr.BalanceInfo.Monetary.UUID,
- Units: incr.Cost,
- RatingID: rateID,
- }); uuid != "" {
- ecUUID = uuid
- }
- }
- if !roundedIncrement {
- rateID = ec.ratingIDForRateInterval(incr.BalanceInfo.Unit.RateInterval, rf)
- }
- cIt.AccountingID = ec.Accounting.GetIDWithSet(
- &BalanceCharge{
- AccountID: incr.BalanceInfo.AccountID,
- BalanceUUID: incr.BalanceInfo.Unit.UUID,
- Units: incr.BalanceInfo.Unit.Consumed,
- RatingID: rateID,
- ExtraChargeID: ecUUID,
- })
- } else if incr.BalanceInfo.Monetary != nil { // Only monetary
- if !roundedIncrement {
- rateID = ec.ratingIDForRateInterval(incr.BalanceInfo.Monetary.RateInterval, rf)
- }
- cIt.AccountingID = ec.Accounting.GetIDWithSet(
- &BalanceCharge{
+ bc := &BalanceCharge{
AccountID: incr.BalanceInfo.AccountID,
BalanceUUID: incr.BalanceInfo.Monetary.UUID,
Units: incr.Cost,
RatingID: rateID,
- })
+ }
+ if isPause {
+ ecUUID = utils.MetaPause
+ ec.Accounting[ecUUID] = bc
+ } else {
+ ecUUID = ec.Accounting.GetIDWithSet(bc)
+ }
+ }
+ if !roundedIncrement {
+ rateID = ec.ratingIDForRateInterval(incr.BalanceInfo.Unit.RateInterval, rf, isPause)
+ }
+ bc := &BalanceCharge{
+ AccountID: incr.BalanceInfo.AccountID,
+ BalanceUUID: incr.BalanceInfo.Unit.UUID,
+ Units: incr.BalanceInfo.Unit.Consumed,
+ RatingID: rateID,
+ ExtraChargeID: ecUUID,
+ }
+ if isPause {
+ cIt.AccountingID = utils.MetaPause
+ ec.Accounting[utils.MetaPause] = bc
+ } else {
+ cIt.AccountingID = ec.Accounting.GetIDWithSet(bc)
+ }
+ } else if incr.BalanceInfo.Monetary != nil { // Only monetary
+ if !roundedIncrement {
+ rateID = ec.ratingIDForRateInterval(incr.BalanceInfo.Monetary.RateInterval, rf, isPause)
+ }
+ bc := &BalanceCharge{
+ AccountID: incr.BalanceInfo.AccountID,
+ BalanceUUID: incr.BalanceInfo.Monetary.UUID,
+ Units: incr.Cost,
+ RatingID: rateID,
+ }
+ if isPause {
+ cIt.AccountingID = utils.MetaPause
+ ec.Accounting[utils.MetaPause] = bc
+ } else {
+ cIt.AccountingID = ec.Accounting.GetIDWithSet(bc)
+ }
}
return
}
@@ -151,39 +168,54 @@ func (ec *EventCost) initCache() {
}
}
-func (ec *EventCost) ratingIDForRateInterval(ri *RateInterval, rf RatingMatchedFilters) string {
+func (ec *EventCost) ratingIDForRateInterval(ri *RateInterval, rf RatingMatchedFilters, isPause bool) string {
if ri == nil || ri.Rating == nil {
return ""
}
var rfUUID string
if rf != nil {
- rfUUID = ec.RatingFilters.GetIDWithSet(rf)
+ if isPause {
+ rfUUID = utils.MetaPause
+ ec.RatingFilters[rfUUID] = rf
+ } else {
+ rfUUID = ec.RatingFilters.GetIDWithSet(rf)
+ }
}
var tmID string
if ri.Timing != nil {
- tmID = ec.Timings.GetIDWithSet(
- &ChargedTiming{
- Years: ri.Timing.Years,
- Months: ri.Timing.Months,
- MonthDays: ri.Timing.MonthDays,
- WeekDays: ri.Timing.WeekDays,
- StartTime: ri.Timing.StartTime})
+ // timingID can have random UUID to be reused by other Rates from EventCost
+ tmID = ec.Timings.GetIDWithSet(&ChargedTiming{
+ Years: ri.Timing.Years,
+ Months: ri.Timing.Months,
+ MonthDays: ri.Timing.MonthDays,
+ WeekDays: ri.Timing.WeekDays,
+ StartTime: ri.Timing.StartTime,
+ })
}
var rtUUID string
if len(ri.Rating.Rates) != 0 {
- rtUUID = ec.Rates.GetIDWithSet(ri.Rating.Rates)
+ if isPause {
+ rtUUID = utils.MetaPause
+ ec.Rates[rtUUID] = ri.Rating.Rates
+ } else {
+ rtUUID = ec.Rates.GetIDWithSet(ri.Rating.Rates)
+ }
}
- return ec.Rating.GetIDWithSet(
- &RatingUnit{
- ConnectFee: ri.Rating.ConnectFee,
- RoundingMethod: ri.Rating.RoundingMethod,
- RoundingDecimals: ri.Rating.RoundingDecimals,
- MaxCost: ri.Rating.MaxCost,
- MaxCostStrategy: ri.Rating.MaxCostStrategy,
- TimingID: tmID,
- RatesID: rtUUID,
- RatingFiltersID: rfUUID,
- })
+ ru := &RatingUnit{
+ ConnectFee: ri.Rating.ConnectFee,
+ RoundingMethod: ri.Rating.RoundingMethod,
+ RoundingDecimals: ri.Rating.RoundingDecimals,
+ MaxCost: ri.Rating.MaxCost,
+ MaxCostStrategy: ri.Rating.MaxCostStrategy,
+ TimingID: tmID,
+ RatesID: rtUUID,
+ RatingFiltersID: rfUUID,
+ }
+ if isPause {
+ ec.Rating[utils.MetaPause] = ru
+ return utils.MetaPause
+ }
+ return ec.Rating.GetIDWithSet(ru)
}
func (ec *EventCost) rateIntervalForRatingID(ratingID string) (ri *RateInterval) {
@@ -459,8 +491,18 @@ func (ec *EventCost) newIntervalFromCharge(cInc *ChargingIncrement) (incr *Incre
// ratingGetIDFomEventCost retrieves UUID based on data from another EventCost
func (ec *EventCost) ratingGetIDFomEventCost(oEC *EventCost, oRatingID string) string {
- if oRatingID == "" {
- return ""
+ if oRatingID == utils.EmptyString {
+ return utils.EmptyString
+ } else if oRatingID == utils.MetaPause {
+ oCIlRating := oEC.Rating[oRatingID].Clone() // clone so we don't influence the original data
+ oCIlRating.TimingID = utils.MetaPause
+ ec.Timings[utils.MetaPause] = oEC.Timings[oCIlRating.TimingID]
+ oCIlRating.RatingFiltersID = utils.MetaPause
+ ec.RatingFilters[utils.MetaPause] = oEC.RatingFilters[oCIlRating.RatingFiltersID]
+ oCIlRating.RatesID = utils.MetaPause
+ ec.Rates[utils.MetaPause] = oEC.Rates[oCIlRating.RatesID]
+ ec.Rating[utils.MetaPause] = oCIlRating
+ return utils.MetaPause
}
oCIlRating := oEC.Rating[oRatingID].Clone() // clone so we don't influence the original data
oCIlRating.TimingID = ec.Timings.GetIDWithSet(oEC.Timings[oCIlRating.TimingID])
@@ -473,6 +515,11 @@ func (ec *EventCost) ratingGetIDFomEventCost(oEC *EventCost, oRatingID string) s
func (ec *EventCost) accountingGetIDFromEventCost(oEC *EventCost, oAccountingID string) string {
if oAccountingID == "" || oAccountingID == utils.MetaNone {
return ""
+ } else if oAccountingID == utils.MetaPause { // *pause represent a pause in debited session
+ oBC := oEC.Accounting[oAccountingID].Clone()
+ oBC.RatingID = ec.ratingGetIDFomEventCost(oEC, oBC.RatingID)
+ ec.Accounting[utils.MetaPause] = oBC
+ return utils.MetaPause
}
oBC := oEC.Accounting[oAccountingID].Clone()
oBC.RatingID = ec.ratingGetIDFomEventCost(oEC, oBC.RatingID)
diff --git a/engine/libeventcost.go b/engine/libeventcost.go
index 218947376..bcff58077 100644
--- a/engine/libeventcost.go
+++ b/engine/libeventcost.go
@@ -632,11 +632,11 @@ func NewFreeEventCost(cgrID, runID, account string, tStart time.Time, usage time
StartTime: tStart,
Cost: utils.Float64Pointer(0),
Charges: []*ChargingInterval{{
- RatingID: "*free",
+ RatingID: utils.MetaPause,
Increments: []*ChargingIncrement{
{
Usage: usage,
- AccountingID: "*free",
+ AccountingID: utils.MetaPause,
CompressFactor: 1,
},
},
@@ -644,39 +644,40 @@ func NewFreeEventCost(cgrID, runID, account string, tStart time.Time, usage time
}},
Rating: Rating{
- "*free": {
+ utils.MetaPause: {
RoundingMethod: "*up",
RoundingDecimals: 5,
- RatesID: "*free",
- RatingFiltersID: "*free",
- TimingID: "*free",
+ RatesID: utils.MetaPause,
+ RatingFiltersID: utils.MetaPause,
+ TimingID: utils.MetaPause,
},
},
Accounting: Accounting{
- "*free": {
+ utils.MetaPause: {
AccountID: account,
// BalanceUUID: "",
- RatingID: "*free",
+ RatingID: utils.MetaPause,
},
},
RatingFilters: RatingFilters{
- "*free": {
+ utils.MetaPause: {
utils.Subject: "",
utils.DestinationPrefixName: "",
utils.DestinationID: "",
- utils.RatingPlanID: "",
+ utils.RatingPlanID: utils.MetaPause,
},
},
Rates: ChargedRates{
- "*free": {
+ utils.MetaPause: {
{
- RateIncrement: usage,
- RateUnit: usage,
+ RateIncrement: 1,
+ RateUnit: 1,
},
},
},
Timings: ChargedTimings{
- "*free": {
+ utils.MetaPause: {
+
StartTime: "00:00:00",
},
},
diff --git a/engine/mapevent.go b/engine/mapevent.go
index f59c1daed..a4d72768e 100644
--- a/engine/mapevent.go
+++ b/engine/mapevent.go
@@ -285,3 +285,16 @@ func (me MapEvent) AsCDR(cfg *config.CGRConfig, tnt, tmz string) (cdr *CDR, err
func (me MapEvent) Data() map[string]interface{} {
return me
}
+
+// GetBoolOrDefault returns the value as a bool or dflt if not present in map
+func (me MapEvent) GetBoolOrDefault(fldName string, dflt bool) (out bool) {
+ fldIface, has := me[fldName]
+ if !has {
+ return dflt
+ }
+ out, err := utils.IfaceAsBool(fldIface)
+ if err != nil {
+ return dflt
+ }
+ return out
+}
diff --git a/general_tests/session5_it_test.go b/general_tests/session5_it_test.go
new file mode 100644
index 000000000..7e70079b6
--- /dev/null
+++ b/general_tests/session5_it_test.go
@@ -0,0 +1,367 @@
+// +build integration
+
+/*
+Real-time Online/Offline Charging System (OCS) for Telecom & ISP environments
+Copyright (C) ITsysCOM GmbH
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see
+*/
+
+package general_tests
+
+import (
+ "encoding/json"
+ "net/rpc"
+ "path"
+ "reflect"
+ "testing"
+ "time"
+
+ "github.com/cgrates/cgrates/config"
+ "github.com/cgrates/cgrates/engine"
+ "github.com/cgrates/cgrates/sessions"
+ "github.com/cgrates/cgrates/utils"
+)
+
+var (
+ ses5CfgDir string
+ ses5CfgPath string
+ ses5Cfg *config.CGRConfig
+ ses5RPC *rpc.Client
+
+ ses5Tests = []func(t *testing.T){
+ testSes5ItLoadConfig,
+ testSes5ItResetDataDB,
+ testSes5ItResetStorDb,
+ testSes5ItStartEngine,
+ testSes5ItRPCConn,
+ testSes5ItLoadFromFolder,
+
+ testSes5ItAllPause,
+
+ testSes5ItStopCgrEngine,
+ }
+)
+
+func TestSes5ItSessions(t *testing.T) {
+ switch *dbType {
+ case utils.MetaInternal:
+ ses5CfgDir = "tutinternal"
+ case utils.MetaMySQL:
+ ses5CfgDir = "tutmysql"
+ case utils.MetaMongo:
+ ses5CfgDir = "tutmongo"
+ case utils.MetaPostgres:
+ t.SkipNow()
+ default:
+ t.Fatal("Unknown Database type")
+ }
+ for _, stest := range ses5Tests {
+ t.Run(ses5CfgDir, stest)
+ }
+}
+
+func testSes5ItLoadConfig(t *testing.T) {
+ ses5CfgPath = path.Join(*dataDir, "conf", "samples", ses5CfgDir)
+ if ses5Cfg, err = config.NewCGRConfigFromPath(ses5CfgPath); err != nil {
+ t.Error(err)
+ }
+}
+
+func testSes5ItResetDataDB(t *testing.T) {
+ if err := engine.InitDataDb(ses5Cfg); err != nil {
+ t.Fatal(err)
+ }
+}
+
+func testSes5ItResetStorDb(t *testing.T) {
+ if err := engine.InitStorDb(ses5Cfg); err != nil {
+ t.Fatal(err)
+ }
+}
+
+func testSes5ItStartEngine(t *testing.T) {
+ if _, err := engine.StopStartEngine(ses5CfgPath, *waitRater); err != nil {
+ t.Fatal(err)
+ }
+}
+
+func testSes5ItRPCConn(t *testing.T) {
+ var err error
+ ses5RPC, err = newRPCClient(ses5Cfg.ListenCfg())
+ if err != nil {
+ t.Fatal(err)
+ }
+}
+
+func testSes5ItLoadFromFolder(t *testing.T) {
+ var reply string
+ attrs := &utils.AttrLoadTpFromFolder{FolderPath: path.Join(*dataDir, "tariffplans", "tutorial")}
+ if err := ses5RPC.Call(utils.APIerSv1LoadTariffPlanFromFolder, attrs, &reply); err != nil {
+ t.Error(err)
+ }
+ time.Sleep(100 * time.Millisecond)
+}
+
+func testSes5ItInitSession(t *testing.T, chargeable bool) {
+ args1 := &sessions.V1InitSessionArgs{
+ InitSession: true,
+ CGREvent: &utils.CGREvent{
+ Tenant: "cgrates.org",
+ Event: map[string]interface{}{
+ utils.Category: utils.Call,
+ utils.ToR: utils.MetaVoice,
+ utils.OriginID: "TestDebitIterval",
+ utils.RequestType: utils.MetaPrepaid,
+ utils.AccountField: "1001",
+ utils.Subject: "1001",
+ utils.Destination: "1002",
+ utils.SetupTime: time.Date(2018, time.January, 7, 16, 60, 0, 0, time.UTC),
+ utils.AnswerTime: time.Date(2018, time.January, 7, 16, 60, 10, 0, time.UTC),
+ utils.Usage: time.Second,
+ },
+ Opts: map[string]interface{}{
+ utils.OptsDebitInterval: "0s",
+ utils.OptsChargeable: chargeable,
+ },
+ },
+ }
+ var rply1 sessions.V1InitSessionReply
+ if err := ses5RPC.Call(utils.SessionSv1InitiateSession,
+ args1, &rply1); err != nil {
+ t.Error(err)
+ return
+ } else if rply1.MaxUsage != nil && *rply1.MaxUsage != time.Second {
+ t.Errorf("Unexpected MaxUsage: %v", rply1.MaxUsage)
+ }
+}
+
+func testSes5ItUpdateSession(t *testing.T, chargeable bool) {
+ usage := 2 * time.Second
+ updtArgs := &sessions.V1UpdateSessionArgs{
+ UpdateSession: true,
+ CGREvent: &utils.CGREvent{
+ Tenant: "cgrates.org",
+ Event: map[string]interface{}{
+ utils.Category: utils.Call,
+ utils.ToR: utils.MetaVoice,
+ utils.OriginID: "TestDebitIterval",
+ utils.RequestType: utils.MetaPrepaid,
+ utils.AccountField: "1001",
+ utils.Subject: "1001",
+ utils.Destination: "1002",
+ utils.SetupTime: time.Date(2018, time.January, 7, 16, 60, 0, 0, time.UTC),
+ utils.AnswerTime: time.Date(2018, time.January, 7, 16, 60, 10, 0, time.UTC),
+ utils.Usage: usage,
+ },
+ Opts: map[string]interface{}{
+ utils.OptsChargeable: chargeable,
+ },
+ },
+ }
+
+ var updtRpl sessions.V1UpdateSessionReply
+ if err := ses5RPC.Call(utils.SessionSv1UpdateSession, updtArgs, &updtRpl); err != nil {
+ t.Error(err)
+ }
+ if updtRpl.MaxUsage == nil || *updtRpl.MaxUsage != usage {
+ t.Errorf("Expecting : %+v, received: %+v", usage, updtRpl.MaxUsage)
+ }
+}
+
+func testSes5ItTerminateSession(t *testing.T, chargeable bool) {
+ args := &sessions.V1TerminateSessionArgs{
+ TerminateSession: true,
+ CGREvent: &utils.CGREvent{
+ Tenant: "cgrates.org",
+ Event: map[string]interface{}{
+ utils.Category: utils.Call,
+ utils.ToR: utils.MetaVoice,
+ utils.OriginID: "TestDebitIterval",
+ utils.RequestType: utils.MetaPrepaid,
+ utils.AccountField: "1001",
+ utils.Subject: "1002",
+ utils.Destination: "1002",
+ utils.SetupTime: time.Date(2018, time.January, 7, 16, 60, 0, 0, time.UTC),
+ utils.AnswerTime: time.Date(2018, time.January, 7, 16, 60, 10, 0, time.UTC),
+ utils.Usage: 5 * time.Second,
+ },
+ Opts: map[string]interface{}{
+ utils.OptsChargeable: chargeable,
+ },
+ },
+ }
+ var rply string
+ if err := ses5RPC.Call(utils.SessionSv1TerminateSession,
+ args, &rply); err != nil {
+ t.Error(err)
+ }
+ if rply != utils.OK {
+ t.Errorf("Unexpected reply: %s", rply)
+ }
+ if err := ses5RPC.Call(utils.SessionSv1ProcessCDR, &utils.CGREvent{
+ Tenant: "cgrates.org",
+ ID: "testSes5ItProccesCDR",
+ Event: map[string]interface{}{
+ utils.Category: utils.Call,
+ utils.ToR: utils.MetaVoice,
+ utils.OriginID: "TestDebitIterval",
+ utils.RequestType: utils.MetaPrepaid,
+ utils.AccountField: "1001",
+ utils.Subject: "1002",
+ utils.Destination: "1002",
+ utils.SetupTime: time.Date(2018, time.January, 7, 16, 60, 0, 0, time.UTC),
+ utils.AnswerTime: time.Date(2018, time.January, 7, 16, 60, 10, 0, time.UTC),
+ utils.Usage: 5 * time.Second,
+ },
+ }, &rply); err != nil {
+ t.Error(err)
+ } else if rply != utils.OK {
+ t.Errorf("Received reply: %s", rply)
+ }
+}
+
+func testSes5ItAllPause(t *testing.T) {
+ testSes5ItInitSession(t, false)
+ testSes5ItUpdateSession(t, false)
+ testSes5ItTerminateSession(t, false)
+ time.Sleep(20 * time.Millisecond)
+ var cdrs []*engine.ExternalCDR
+ req := utils.RPCCDRsFilter{RequestTypes: []string{utils.MetaPrepaid}}
+ if err := ses5RPC.Call(utils.APIerSv2GetCDRs, &req, &cdrs); err != nil {
+ t.Error("Unexpected error: ", err.Error())
+ }
+
+ exp := []*engine.ExternalCDR{{
+ CGRID: "1b676c7583ceb27ad7c991ded73b2417faa29a6a",
+ RunID: "*default",
+ OrderID: 0,
+ OriginHost: "",
+ Source: "",
+ OriginID: "TestDebitIterval",
+ ToR: "*voice",
+ RequestType: "*prepaid",
+ Tenant: "cgrates.org",
+ Category: "call",
+ Account: "1001",
+ Subject: "1001",
+ Destination: "1002",
+ SetupTime: "2018-01-07T19:00:00+02:00",
+ AnswerTime: "2018-01-07T19:00:10+02:00",
+ Usage: "5s",
+ ExtraFields: map[string]string{},
+ CostSource: "*sessions",
+ Cost: 0,
+ CostDetails: "",
+ ExtraInfo: "",
+ PreRated: false,
+ }}
+ if len(cdrs) == 0 {
+ t.Fatal("No cdrs returned")
+ }
+ cdrs[0].OrderID = 0
+ cdString := cdrs[0].CostDetails
+ cdrs[0].CostDetails = ""
+ if !reflect.DeepEqual(exp, cdrs) {
+ t.Errorf("Expected %s \n received: %s", utils.ToJSON(exp), utils.ToJSON(cdrs))
+ }
+
+ var cd engine.EventCost
+ if err := json.Unmarshal([]byte(cdString), &cd); err != nil {
+ t.Fatal(err)
+ }
+ evCost := engine.EventCost{
+ CGRID: "1b676c7583ceb27ad7c991ded73b2417faa29a6a",
+ RunID: "*default",
+ StartTime: time.Date(2018, time.January, 7, 16, 60, 10, 0, time.UTC),
+ Usage: utils.DurationPointer(5 * time.Second),
+ Cost: utils.Float64Pointer(0),
+ Charges: []*engine.ChargingInterval{{
+ RatingID: utils.MetaPause,
+ Increments: []*engine.ChargingIncrement{{
+ Usage: time.Second,
+ Cost: 0,
+ AccountingID: utils.MetaPause,
+ CompressFactor: 1,
+ }},
+ CompressFactor: 1,
+ }, {
+ RatingID: utils.MetaPause,
+ Increments: []*engine.ChargingIncrement{{
+ Usage: 2 * time.Second,
+ Cost: 0,
+ AccountingID: utils.MetaPause,
+ CompressFactor: 1,
+ }},
+ CompressFactor: 2,
+ }},
+ Rating: engine.Rating{
+ utils.MetaPause: {
+ ConnectFee: 0,
+ RoundingMethod: "*up",
+ RoundingDecimals: 5,
+ MaxCost: 0,
+ MaxCostStrategy: "",
+ TimingID: "",
+ RatesID: utils.MetaPause,
+ RatingFiltersID: utils.MetaPause,
+ },
+ },
+ Accounting: engine.Accounting{
+ utils.MetaPause: {
+ AccountID: "1001",
+ BalanceUUID: "",
+ RatingID: utils.MetaPause,
+ Units: 0,
+ ExtraChargeID: "",
+ },
+ },
+ RatingFilters: engine.RatingFilters{
+ utils.MetaPause: {
+ utils.DestinationID: "",
+ utils.DestinationPrefixName: "",
+ utils.RatingPlanID: utils.MetaPause,
+ utils.Subject: "",
+ },
+ },
+ Rates: engine.ChargedRates{
+ utils.MetaPause: {{
+ GroupIntervalStart: 0,
+ Value: 0,
+ RateIncrement: 1,
+ RateUnit: 1,
+ }},
+ },
+ Timings: engine.ChargedTimings{
+ "": {
+ StartTime: "00:00:00",
+ },
+ },
+ }
+ // the Timings are not relevant for this test
+ for _, r := range cd.Rating {
+ r.TimingID = ""
+ }
+ cd.Timings = evCost.Timings
+ if !reflect.DeepEqual(evCost, cd) {
+ t.Errorf("Expected %s \n received: %s", utils.ToJSON(evCost), utils.ToJSON(cd))
+ }
+
+}
+
+func testSes5ItStopCgrEngine(t *testing.T) {
+ if err := engine.KillEngine(100); err != nil {
+ t.Error(err)
+ }
+}
diff --git a/packages/debian/changelog b/packages/debian/changelog
index c810602cb..b5ceb2fa6 100644
--- a/packages/debian/changelog
+++ b/packages/debian/changelog
@@ -142,6 +142,7 @@ cgrates (0.11.0~dev) UNRELEASED; urgency=medium
* [FilterS] Optimized the automated index fields matching
* [AgentS] Added *cfg as DataProvider for AgentRequest
* [AgentS] Added *routes_maxcost flag
+ * [SessionS] Added *sessionChargeable session option to control session charging
-- DanB Wed, 19 Feb 2020 13:25:52 +0200
diff --git a/sessions/session.go b/sessions/session.go
index 9df332d3f..0763bde97 100644
--- a/sessions/session.go
+++ b/sessions/session.go
@@ -81,6 +81,7 @@ type Session struct {
debitStop chan struct{}
sTerminator *sTerminator // automatic timeout for the session
+ chargeable bool
}
// Lock exported function from sync.RWMutex
diff --git a/sessions/sessions.go b/sessions/sessions.go
index 81ab0f204..d676dae34 100644
--- a/sessions/sessions.go
+++ b/sessions/sessions.go
@@ -425,6 +425,11 @@ func (sS *SessionS) debitSession(s *Session, sRunIdx int, dur time.Duration,
return
}
sr := s.SRuns[sRunIdx]
+ if !s.chargeable {
+ sS.pause(sr, dur)
+ sr.TotalUsage += sr.LastUsage
+ return dur, nil
+ }
rDur := sr.debitReserve(dur, lastUsed) // debit out of reserve, rDur is still to be debited
if rDur == time.Duration(0) {
return dur, nil // complete debit out of reserve
@@ -498,6 +503,27 @@ func (sS *SessionS) debitSession(s *Session, sRunIdx int, dur time.Duration,
return
}
+func (sS *SessionS) pause(sr *SRun, dur time.Duration) {
+ if sr.CD.LoopIndex > 0 {
+ sr.CD.TimeStart = sr.CD.TimeEnd
+ }
+ sr.CD.TimeEnd = sr.CD.TimeStart.Add(dur)
+ sr.CD.DurationIndex += dur
+ ec := engine.NewFreeEventCost(sr.CD.CgrID, sr.CD.RunID, sr.CD.Account, sr.CD.TimeStart, dur)
+ sr.LastUsage = dur
+ sr.CD.LoopIndex++
+ if sr.EventCost == nil { // is the first increment
+ // when we start the call with debit interval 0
+ // but later we update this value with one greater than 0
+ sr.EventCost = ec
+ } else { // we already debited something
+ // copy the old AccountSummary as in Merge the old one is overwriten by the new one
+ ec.AccountSummary = sr.EventCost.AccountSummary
+ // similar to the debit merge the event costs
+ sr.EventCost.Merge(ec)
+ }
+}
+
// debitLoopSession will periodically debit sessions, ie: automatic prepaid
// threadSafe since it will run into it's own goroutine
func (sS *SessionS) debitLoopSession(s *Session, sRunIdx int,
@@ -1121,6 +1147,7 @@ func (sS *SessionS) newSession(cgrEv *utils.CGREvent, resID, clntConnID string,
ClientConnID: clntConnID,
DebitInterval: dbtItval,
}
+ s.chargeable = s.OptsStart.GetBoolOrDefault(utils.OptsChargeable, true)
if !isMsg && sS.isIndexed(s, false) { // check if already exists
return nil, utils.ErrExists
}
@@ -1367,7 +1394,7 @@ func (sS *SessionS) initSessionDebitLoops(s *Session) {
return
}
for i, sr := range s.SRuns {
- if s.DebitInterval != 0 &&
+ if s.DebitInterval > 0 &&
sr.Event.GetStringIgnoreErrors(utils.RequestType) == utils.MetaPrepaid {
if s.debitStop == nil { // init the debitStop only for the first sRun with DebitInterval and RequestType MetaPrepaid
s.debitStop = make(chan struct{})
@@ -1450,6 +1477,7 @@ func (sS *SessionS) updateSession(s *Session, updtEv, opts engine.MapEvent, isMs
s.updateSRuns(updtEv, sS.cgrCfg.SessionSCfg().AlterableFields)
sS.setSTerminator(s, opts) // reset the terminator
}
+ s.chargeable = opts.GetBoolOrDefault(utils.OptsChargeable, true)
//init has no updtEv
if updtEv == nil {
updtEv = engine.MapEvent(s.EventStart.Clone())
@@ -1517,20 +1545,24 @@ func (sS *SessionS) endSession(s *Session, tUsage, lastUsage *time.Duration,
if sr.EventCost != nil {
if !isMsg { // in case of one time charge there is no need of corrections
if notCharged := sUsage - sr.EventCost.GetUsage(); notCharged > 0 { // we did not charge enough, make a manual debit here
- if sr.CD.LoopIndex > 0 {
- sr.CD.TimeStart = sr.CD.TimeEnd
- }
- sr.CD.TimeEnd = sr.CD.TimeStart.Add(notCharged)
- sr.CD.DurationIndex += notCharged
- cc := new(engine.CallCost)
- if err = sS.connMgr.Call(sS.cgrCfg.SessionSCfg().RALsConns, nil, utils.ResponderDebit,
- &engine.CallDescriptorWithOpts{
- CallDescriptor: sr.CD,
- Opts: s.OptsStart,
- }, cc); err == nil {
- sr.EventCost.Merge(
- engine.NewEventCostFromCallCost(cc, s.CGRID,
- sr.Event.GetStringIgnoreErrors(utils.RunID)))
+ if !s.chargeable {
+ sS.pause(sr, notCharged)
+ } else {
+ if sr.CD.LoopIndex > 0 {
+ sr.CD.TimeStart = sr.CD.TimeEnd
+ }
+ sr.CD.TimeEnd = sr.CD.TimeStart.Add(notCharged)
+ sr.CD.DurationIndex += notCharged
+ cc := new(engine.CallCost)
+ if err = sS.connMgr.Call(sS.cgrCfg.SessionSCfg().RALsConns, nil, utils.ResponderDebit,
+ &engine.CallDescriptorWithOpts{
+ CallDescriptor: sr.CD,
+ Opts: s.OptsStart,
+ }, cc); err == nil {
+ sr.EventCost.Merge(
+ engine.NewEventCostFromCallCost(cc, s.CGRID,
+ sr.Event.GetStringIgnoreErrors(utils.RunID)))
+ }
}
} else if notCharged < 0 { // charged too much, try refund
if err = sS.refundSession(s, sRunIdx, -notCharged); err != nil {
@@ -2585,7 +2617,7 @@ func (sS *SessionS) BiRPCv1TerminateSession(clnt rpcclient.ClientConnector,
dbtItvl, isMsg, args.ForceDuration); err != nil {
return utils.NewErrRALs(err)
}
- if _, err = sS.updateSession(s, ev, args.Opts, isMsg); err != nil {
+ if _, err = sS.updateSession(s, ev, opts, isMsg); err != nil {
return err
}
break
@@ -2593,6 +2625,9 @@ func (sS *SessionS) BiRPCv1TerminateSession(clnt rpcclient.ClientConnector,
if !isMsg {
s.UpdateSRuns(ev, sS.cgrCfg.SessionSCfg().AlterableFields)
}
+ s.Lock()
+ s.chargeable = opts.GetBoolOrDefault(utils.OptsChargeable, true)
+ s.Unlock()
if err = sS.terminateSession(s,
ev.GetDurationPtrIgnoreErrors(utils.Usage),
ev.GetDurationPtrIgnoreErrors(utils.LastUsed),
@@ -3396,6 +3431,10 @@ func (sS *SessionS) BiRPCv1ProcessEvent(clnt rpcclient.ClientConnector,
dbtItvl, false, ralsOpts.Has(utils.MetaFD)); err != nil {
return err
}
+ } else {
+ s.Lock()
+ s.chargeable = opts.GetBoolOrDefault(utils.OptsChargeable, true)
+ s.Unlock()
}
if err = sS.terminateSession(s,
ev.GetDurationPtrIgnoreErrors(utils.Usage),
@@ -3637,7 +3676,6 @@ func (sS *SessionS) BiRPCv1DeactivateSessions(clnt rpcclient.ClientConnector,
}
func (sS *SessionS) processCDR(cgrEv *utils.CGREvent, flags []string, rply *string, clnb bool) (err error) {
-
ev := engine.MapEvent(cgrEv.Event)
cgrID := GetSetCGRID(ev)
s := sS.getRelocateSession(cgrID,
diff --git a/sessions/sessionscover_it_test.go b/sessions/sessionscover_it_test.go
index f807c8a7c..95eb7b965 100644
--- a/sessions/sessionscover_it_test.go
+++ b/sessions/sessionscover_it_test.go
@@ -308,6 +308,7 @@ func testForceSTerminatorManualTermination(t *testing.T) {
NextAutoDebit: utils.TimePointer(time.Date(2020, time.April, 18, 23, 0, 0, 0, time.UTC)),
},
},
+ chargeable: true,
}
cfg := config.NewDefaultCGRConfig()
@@ -348,6 +349,7 @@ func testForceSTerminatorPostCDRs(t *testing.T) {
NextAutoDebit: utils.TimePointer(time.Date(2020, time.April, 18, 23, 0, 0, 0, time.UTC)),
},
},
+ chargeable: true,
}
expected := "INTERNALLY_DISCONNECTED"
@@ -384,6 +386,7 @@ func testForceSTerminatorReleaseSession(t *testing.T) {
NextAutoDebit: utils.TimePointer(time.Date(2020, time.April, 18, 23, 0, 0, 0, time.UTC)),
},
},
+ chargeable: true,
}
expected := "MANDATORY_IE_MISSING: [connIDs]"
@@ -432,6 +435,7 @@ func testForceSTerminatorClientCall(t *testing.T) {
NextAutoDebit: utils.TimePointer(time.Date(2020, time.April, 18, 23, 0, 0, 0, time.UTC)),
},
},
+ chargeable: true,
}
expected := "MANDATORY_IE_MISSING: [connIDs]"
@@ -464,6 +468,7 @@ func testDebitSession(t *testing.T) {
NextAutoDebit: utils.TimePointer(time.Date(2020, time.April, 18, 23, 0, 0, 0, time.UTC)),
},
},
+ chargeable: true,
}
//RunIdx cannot be higher than the length of sessions runs
@@ -551,6 +556,7 @@ func testDebitSessionResponderMaxDebit(t *testing.T) {
NextAutoDebit: utils.TimePointer(time.Date(2020, time.April, 18, 23, 0, 0, 0, time.UTC)),
},
},
+ chargeable: true,
}
if maxDur, err := sessions.debitSession(ss, 0, 5*time.Second,
@@ -614,6 +620,7 @@ func testDebitSessionResponderMaxDebitError(t *testing.T) {
NextAutoDebit: utils.TimePointer(time.Date(2020, time.April, 18, 23, 0, 0, 0, time.UTC)),
},
},
+ chargeable: true,
}
if maxDur, err := sessions.debitSession(ss, 0, 5*time.Minute,
@@ -668,6 +675,7 @@ func testInitSessionDebitLoops(t *testing.T) {
NextAutoDebit: utils.TimePointer(time.Date(2020, time.April, 18, 23, 0, 0, 0, time.UTC)),
},
},
+ chargeable: true,
}
sessions.initSessionDebitLoops(ss)
@@ -713,6 +721,7 @@ func testDebitLoopSessionErrorDebiting(t *testing.T) {
NextAutoDebit: utils.TimePointer(time.Date(2020, time.April, 18, 23, 0, 0, 0, time.UTC)),
},
},
+ chargeable: true,
}
// session already closed
@@ -800,6 +809,7 @@ func testDebitLoopSession(t *testing.T) {
NextAutoDebit: utils.TimePointer(time.Date(2020, time.April, 18, 23, 0, 0, 0, time.UTC)),
},
},
+ chargeable: true,
}
go func() {
if _, err := sessions.debitLoopSession(ss, 0, time.Second); err != nil {
@@ -860,6 +870,7 @@ func testDebitLoopSessionFrcDiscLowerDbtInterval(t *testing.T) {
NextAutoDebit: utils.TimePointer(time.Date(2020, time.April, 18, 23, 0, 0, 0, time.UTC)),
},
},
+ chargeable: true,
}
go func() {
if _, err := sessions.debitLoopSession(ss, 0, time.Second); err != nil {
@@ -913,6 +924,7 @@ func testDebitLoopSessionLowBalance(t *testing.T) {
NextAutoDebit: utils.TimePointer(time.Date(2020, time.April, 18, 23, 0, 0, 0, time.UTC)),
},
},
+ chargeable: true,
}
sessions.cgrCfg.SessionSCfg().MinDurLowBalance = 10 * time.Second
@@ -970,6 +982,7 @@ func testDebitLoopSessionWarningSessions(t *testing.T) {
NextAutoDebit: utils.TimePointer(time.Date(2020, time.April, 18, 23, 0, 0, 0, time.UTC)),
},
},
+ chargeable: true,
}
// will disconnect faster, MinDurLowBalance higher than the debit interval
@@ -1031,6 +1044,7 @@ func testDebitLoopSessionDisconnectSession(t *testing.T) {
NextAutoDebit: utils.TimePointer(time.Date(2020, time.April, 18, 23, 0, 0, 0, time.UTC)),
},
},
+ chargeable: true,
}
// will disconnect faster
@@ -1085,6 +1099,7 @@ func testStoreSCost(t *testing.T) {
},
},
},
+ chargeable: true,
}
if err := sessions.storeSCost(ss, 0); err != nil {
@@ -1132,6 +1147,7 @@ func testRefundSession(t *testing.T) {
},
},
},
+ chargeable: true,
}
expectedErr := "no event cost"
@@ -1266,6 +1282,7 @@ func testRoundCost(t *testing.T) {
},
},
},
+ chargeable: true,
}
//mocking an error API Call
@@ -1288,6 +1305,7 @@ func testDisconnectSession(t *testing.T) {
TotalUsage: time.Minute,
},
},
+ chargeable: true,
}
sTestMock := &testMockClientConn{}
@@ -1410,6 +1428,7 @@ func testNewSession(t *testing.T) {
},
},
},
+ chargeable: true,
}
if rcv, err := sessions.newSession(cgrEv, "resourceID", "clientConnID",
time.Second, false, false); err != nil {
@@ -2080,6 +2099,7 @@ func testEndSession(t *testing.T) {
},
},
},
+ chargeable: true,
}
activationTime := time.Date(2020, 21, 07, 10, 0, 0, 0, time.UTC)
@@ -2196,6 +2216,7 @@ func testBiRPCv1GetActivePassiveSessions(t *testing.T) {
},
},
},
+ chargeable: true,
}
sr2[utils.ToR] = utils.MetaSMS
sr2[utils.Subject] = "subject2"
diff --git a/utils/consts.go b/utils/consts.go
index 64b722cc8..e8f16afb7 100755
--- a/utils/consts.go
+++ b/utils/consts.go
@@ -370,6 +370,7 @@ const (
MetaMaxCostFree = "*free"
MetaMaxCostDisconnect = "*disconnect"
MetaOut = "*out"
+ MetaPause = "*pause"
MetaVoice = "*voice"
ACD = "ACD"
@@ -989,7 +990,6 @@ const (
MetaActionProfiles = "*action_profiles"
MetaAccountProfiles = "*account_profiles"
MetaLoadIDs = "*load_ids"
- MetaAccountS = "*accounts"
)
// MetaMetrics
@@ -2500,11 +2500,12 @@ var (
)
// CGROptionsSet the possible cgr options
-var CGROptionsSet = NewStringSet([]string{OptsRatesStartTime, OptsRatesUsage, OptsSessionTTL, OptsSessionTTLMaxDelay,
- OptsSessionTTLLastUsed, OptsSessionTTLLastUsage, OptsSessionTTLUsage, OptsDebitInterval, OptsStirATest,
- OptsStirPayloadMaxDuration, OptsStirIdentity, OptsStirOriginatorTn, OptsStirOriginatorURI,
- OptsStirDestinationTn, OptsStirDestinationURI, OptsStirPublicKeyPath, OptsStirPrivateKeyPath,
- OptsAPIKey, OptsRouteID, OptsContext, OptsAttributesProcessRuns, OptsRoutesLimit, OptsRoutesOffset})
+var CGROptionsSet = NewStringSet([]string{OptsRatesStartTime, OptsRatesUsage, OptsSessionTTL,
+ OptsSessionTTLMaxDelay, OptsSessionTTLLastUsed, OptsSessionTTLLastUsage, OptsSessionTTLUsage,
+ OptsDebitInterval, OptsStirATest, OptsStirPayloadMaxDuration, OptsStirIdentity,
+ OptsStirOriginatorTn, OptsStirOriginatorURI, OptsStirDestinationTn, OptsStirDestinationURI,
+ OptsStirPublicKeyPath, OptsStirPrivateKeyPath, OptsAPIKey, OptsRouteID, OptsContext,
+ OptsAttributesProcessRuns, OptsRoutesLimit, OptsRoutesOffset, OptsChargeable})
// EventExporter metrics
const (
@@ -2536,6 +2537,7 @@ const (
OptsSessionTTLLastUsage = "*sessionTTLLastUsage"
OptsSessionTTLUsage = "*sessionTTLUsage"
OptsDebitInterval = "*sessionDebitInterval"
+ OptsChargeable = "*sessionChargeable"
// STIR
OptsStirATest = "*stirATest"
OptsStirPayloadMaxDuration = "*stirPayloadMaxDuration"