From 61b342ddceb441f738de824ab8a77591ee2a959e Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Fri, 22 May 2015 17:16:02 +0300 Subject: [PATCH 01/10] return erro on disabled lcr supplier --- engine/calldesc.go | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/engine/calldesc.go b/engine/calldesc.go index 573009655..94265e513 100644 --- a/engine/calldesc.go +++ b/engine/calldesc.go @@ -727,6 +727,13 @@ func (cd *CallDescriptor) GetLCR(stats StatsInterface) (*LCRCost, error) { var cc *CallCost var err error if cd.account, err = accountingStorage.GetAccount(lcrCD.GetAccountKey()); err == nil { + if cd.account.Disabled { + lcrCost.SupplierCosts = append(lcrCost.SupplierCosts, &LCRSupplierCost{ + Supplier: supplier, + Error: fmt.Errorf("supplier %s is disabled", supplier), + }) + continue + } cc, err = lcrCD.debit(cd.account, true, true) } else { cc, err = lcrCD.GetCost() @@ -883,6 +890,13 @@ func (cd *CallDescriptor) GetLCR(stats StatsInterface) (*LCRCost, error) { //log.Print("CD: ", lcrCD.GetAccountKey()) if cd.account, err = accountingStorage.GetAccount(lcrCD.GetAccountKey()); err == nil { //log.Print("ACCCOUNT") + if cd.account.Disabled { + lcrCost.SupplierCosts = append(lcrCost.SupplierCosts, &LCRSupplierCost{ + Supplier: supplier, + Error: fmt.Errorf("supplier %s is disabled", supplier), + }) + continue + } cc, err = lcrCD.debit(cd.account, true, true) } else { //log.Print("STANDARD") @@ -891,11 +905,10 @@ func (cd *CallDescriptor) GetLCR(stats StatsInterface) (*LCRCost, error) { //log.Printf("CC: %+v", cc) supplier = utils.ConcatenatedKey(lcrCD.Direction, lcrCD.Tenant, lcrCD.Category, lcrCD.Subject) if err != nil || cc == nil { - //lcrCost.SupplierCosts = append(lcrCost.SupplierCosts, &LCRSupplierCost{ - // Supplier: supplier, - // Error: err, - //}) - Logger.Warning(fmt.Sprintf("LCR_WARNING: Ignoring supplier: %s, cannot calculate cost, error: %v", supplier, err)) + lcrCost.SupplierCosts = append(lcrCost.SupplierCosts, &LCRSupplierCost{ + Supplier: supplier, + Error: err, + }) continue } else { supplCost := &LCRSupplierCost{ From dbd0f369b110996682f8e1020b894f8ea612a86b Mon Sep 17 00:00:00 2001 From: DanB Date: Sat, 23 May 2015 21:02:05 +0200 Subject: [PATCH 02/10] Adding LCR processing to SM-Kamailio --- apier/v1/lcr.go | 135 +++--------------- console/lcr.go | 10 +- .../etc/kamailio/kamailio-cgrates.cfg | 53 ++----- .../kamailio/etc/kamailio/kamailio.cfg | 37 ++++- engine/action.go | 1 - engine/lcr.go | 107 ++++++++++++++ engine/lcr_test.go | 50 +++++++ sessionmanager/kamailiosm.go | 55 ++++++- sessionmanager/kamevent.go | 49 ++++++- sessionmanager/kamevent_test.go | 36 ++++- 10 files changed, 362 insertions(+), 171 deletions(-) diff --git a/apier/v1/lcr.go b/apier/v1/lcr.go index ffe981ff4..1a23804b1 100644 --- a/apier/v1/lcr.go +++ b/apier/v1/lcr.go @@ -21,77 +21,16 @@ package v1 import ( "errors" "fmt" - "time" "github.com/cgrates/cgrates/engine" "github.com/cgrates/cgrates/utils" ) -// A request for LCR -type LcrRequest struct { - Direction string - Tenant string - Category string - Account string - Subject string - Destination string - TimeStart string - Duration string -} - -// A LCR reply -type LcrReply struct { - DestinationId string - RPCategory string - Strategy string - Suppliers []*LcrSupplier -} - -// One supplier out of LCR reply -type LcrSupplier struct { - Supplier string - Cost float64 -} - // Computes the LCR for a specific request emulating a call -func (self *ApierV1) GetLcr(lcrReq LcrRequest, lcrReply *LcrReply) (err error) { - if missing := utils.MissingStructFields(&lcrReq, []string{"Account", "Destination"}); len(missing) != 0 { - return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing) - } - // Handle defaults - if len(lcrReq.Direction) == 0 { - lcrReq.Direction = utils.OUT - } - if len(lcrReq.Tenant) == 0 { - lcrReq.Tenant = self.Config.DefaultTenant - } - if len(lcrReq.Category) == 0 { - lcrReq.Category = self.Config.DefaultCategory - } - if len(lcrReq.Subject) == 0 { - lcrReq.Subject = lcrReq.Account - } - var timeStart time.Time - if len(lcrReq.TimeStart) == 0 { - timeStart = time.Now() - } else if timeStart, err = utils.ParseTimeDetectLayout(lcrReq.TimeStart); err != nil { - return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error()) - } - var callDur time.Duration - if len(lcrReq.Duration) == 0 { - callDur = time.Duration(1) * time.Minute - } else if callDur, err = utils.ParseDurationWithSecs(lcrReq.Duration); err != nil { - return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error()) - } - cd := &engine.CallDescriptor{ - Direction: lcrReq.Direction, - Tenant: lcrReq.Tenant, - Category: lcrReq.Category, - Account: lcrReq.Account, - Subject: lcrReq.Subject, - Destination: lcrReq.Destination, - TimeStart: timeStart, - TimeEnd: timeStart.Add(callDur), +func (self *ApierV1) GetLcr(lcrReq engine.LcrRequest, lcrReply *engine.LcrReply) error { + cd, err := lcrReq.AsCallDescriptor() + if err != nil { + return err } var lcrQried engine.LCRCost if err := self.Responder.GetLCR(cd, &lcrQried); err != nil { @@ -100,6 +39,10 @@ func (self *ApierV1) GetLcr(lcrReq LcrRequest, lcrReply *LcrReply) (err error) { if lcrQried.Entry == nil { return errors.New(utils.ERR_NOT_FOUND) } + if lcrQried.HasErrors() { + lcrQried.LogErrors() + return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, "LCR_COMPUTE_ERRORS") + } lcrReply.DestinationId = lcrQried.Entry.DestinationId lcrReply.RPCategory = lcrQried.Entry.RPCategory lcrReply.Strategy = lcrQried.Entry.Strategy @@ -107,68 +50,30 @@ func (self *ApierV1) GetLcr(lcrReq LcrRequest, lcrReply *LcrReply) (err error) { if dtcs, err := utils.NewDTCSFromRPKey(qriedSuppl.Supplier); err != nil { return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error()) } else { - lcrReply.Suppliers = append(lcrReply.Suppliers, &LcrSupplier{Supplier: dtcs.Subject, Cost: qriedSuppl.Cost}) + lcrReply.Suppliers = append(lcrReply.Suppliers, &engine.LcrSupplier{Supplier: dtcs.Subject, Cost: qriedSuppl.Cost}) } } return nil } // Computes the LCR for a specific request emulating a call, returns a comma separated list of suppliers -func (self *ApierV1) GetLcrSuppliers(lcrReq LcrRequest, suppliers *string) (err error) { - if missing := utils.MissingStructFields(&lcrReq, []string{"Account", "Destination"}); len(missing) != 0 { - return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing) - } - // Handle defaults - if len(lcrReq.Direction) == 0 { - lcrReq.Direction = utils.OUT - } - if len(lcrReq.Tenant) == 0 { - lcrReq.Tenant = self.Config.DefaultTenant - } - if len(lcrReq.Category) == 0 { - lcrReq.Category = self.Config.DefaultCategory - } - if len(lcrReq.Subject) == 0 { - lcrReq.Subject = lcrReq.Account - } - var timeStart time.Time - if len(lcrReq.TimeStart) == 0 { - timeStart = time.Now() - } else if timeStart, err = utils.ParseTimeDetectLayout(lcrReq.TimeStart); err != nil { - return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error()) - } - var callDur time.Duration - if len(lcrReq.Duration) == 0 { - callDur = time.Duration(1) * time.Minute - } else if callDur, err = utils.ParseDurationWithSecs(lcrReq.Duration); err != nil { - return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error()) - } - cd := &engine.CallDescriptor{ - Direction: lcrReq.Direction, - Tenant: lcrReq.Tenant, - Category: lcrReq.Category, - Account: lcrReq.Account, - Subject: lcrReq.Subject, - Destination: lcrReq.Destination, - TimeStart: timeStart, - TimeEnd: timeStart.Add(callDur), +func (self *ApierV1) GetLcrSuppliers(lcrReq engine.LcrRequest, suppliers *string) (err error) { + cd, err := lcrReq.AsCallDescriptor() + if err != nil { + return err } var lcrQried engine.LCRCost if err := self.Responder.GetLCR(cd, &lcrQried); err != nil { return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error()) } - if lcrQried.Entry == nil { - return errors.New(utils.ERR_NOT_FOUND) + if lcrQried.HasErrors() { + lcrQried.LogErrors() + return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, "LCR_ERRORS") } - for idx, qriedSuppl := range lcrQried.SupplierCosts { - if dtcs, err := utils.NewDTCSFromRPKey(qriedSuppl.Supplier); err != nil { - return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error()) - } else { - if idx != 0 { - *suppliers += "," - } - *suppliers += dtcs.Subject - } + if suppliersStr, err := lcrQried.SuppliersString(); err != nil { + return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error()) + } else { + *suppliers = suppliersStr } return nil } diff --git a/console/lcr.go b/console/lcr.go index c8773900f..59a2a5d57 100644 --- a/console/lcr.go +++ b/console/lcr.go @@ -18,13 +18,15 @@ along with this program. If not, see package console -import "github.com/cgrates/cgrates/apier/v1" +import ( + "github.com/cgrates/cgrates/engine" +) func init() { c := &CmdGetLcr{ name: "lcr", rpcMethod: "ApierV1.GetLcr", - rpcParams: &v1.LcrRequest{}, + rpcParams: &engine.LcrRequest{}, } commands[c.Name()] = c c.CommandExecuter = &CommandExecuter{c} @@ -34,7 +36,7 @@ func init() { type CmdGetLcr struct { name string rpcMethod string - rpcParams *v1.LcrRequest + rpcParams *engine.LcrRequest *CommandExecuter } @@ -55,5 +57,5 @@ func (self *CmdGetLcr) PostprocessRpcParams() error { } func (self *CmdGetLcr) RpcResult() interface{} { - return &v1.LcrReply{} + return &engine.LcrReply{} } diff --git a/data/tutorials/kamevapi/kamailio/etc/kamailio/kamailio-cgrates.cfg b/data/tutorials/kamevapi/kamailio/etc/kamailio/kamailio-cgrates.cfg index 8557e93b3..2d7b5fb73 100644 --- a/data/tutorials/kamevapi/kamailio/etc/kamailio/kamailio-cgrates.cfg +++ b/data/tutorials/kamevapi/kamailio/etc/kamailio/kamailio-cgrates.cfg @@ -1,10 +1,11 @@ # Kamailio-CGRateS related route blocks - +# Called on new connection over evapi, should normally be the case of CGRateS engine event_route[evapi:connection-new] { $sht(cgrconn=>cgr) = $evapi(srcaddr) + ":" + $evapi(srcport); # Detect presence of at least one connection } +# Called when the connection with CGRateS closes event_route[evapi:connection-closed] { $var(connClosed) = $evapi(srcaddr) + ":" + $evapi(srcport); if $sht(cgrconn=>cgr) == $var(connClosed) { @@ -12,44 +13,29 @@ event_route[evapi:connection-closed] { } } +# Message received from CGRateS, dispatch it to own route event_route[evapi:message-received] { json_get_field("$evapi(msg)", "Event", "$var(Event)"); route($(var(Event){s.rm,"})); # String characters are kept by json_get_field, remove them here } +# Called by Kamailio on new dialog event_route[dialog:start] { route(CGR_CALL_START); } +# Called by Kamailio on dialog end event_route[dialog:end] { route(CGR_CALL_END); } # Send AUTH_REQUEST to CGRateS -route[CGR_AUTH_REQUEST] { +route[CGRATES_AUTH_REQUEST] { # Auth INVITEs with CGRateS if $sht(cgrconn=>cgr) == $null { sl_send_reply("503","Charging controller unreachable"); exit; } - switch ($fU) { - case 1001: - case 1006: - case 1007: - $dlg_var(cgrReqType) = "*prepaid"; - break; - case 1002: - $dlg_var(cgrReqType) = "*postpaid"; - break; - case 1003: - $dlg_var(cgrReqType) = "*pseudoprepaid"; - break; - default: - $dlg_var(cgrReqType) = "*rated"; - } - $dlg_var(cgrTenant) = "cgrates.org"; - $dlg_var(cgrAccount) = $fU; - $dlg_var(cgrDestination) = $rU; evapi_async_relay("{\"event\":\"CGR_AUTH_REQUEST\", \"tr_index\":\"$T(id_index)\", \"tr_label\":\"$T(id_label)\", @@ -57,7 +43,8 @@ route[CGR_AUTH_REQUEST] { \"cgr_tenant\":\"$dlg_var(cgrTenant)\", \"cgr_account\":\"$dlg_var(cgrAccount)\", \"cgr_destination\":\"$dlg_var(cgrDestination)\", - \"cgr_setuptime\":\"$TS\"}"); + \"cgr_setuptime\":\"$TS\", + \"cgr_computelcr\":\"true\"}"); } # Process AUTH_REPLY from CGRateS @@ -65,25 +52,12 @@ route[CGR_AUTH_REPLY] { json_get_field("$evapi(msg)", "TransactionIndex", "$var(TransactionIndex)"); json_get_field("$evapi(msg)", "TransactionLabel", "$var(TransactionLabel)"); json_get_field("$evapi(msg)", "MaxSessionTime", "$var(MaxSessionTime)"); - json_get_field("$evapi(msg)", "AuthError", "$var(AuthError)"); + json_get_field("$evapi(msg)", "Suppliers", "$avp(CgrSuppliers)"); + json_get_field("$evapi(msg)", "Error", "$avp(CgrError)"); $var(id_index) = $(var(TransactionIndex){s.int}); $var(id_label) = $(var(TransactionLabel){s.int}); - $var(max_session_time) = $(var(MaxSessionTime){s.int}); - t_continue("$var(id_index)", "$var(id_label)", "CGR_DIALOG_TIMEOUT"); -} - -# Check AUTH_REPLY for errors and set dialog timeout if necessary, call route(RELAY) after processing -route[CGR_DIALOG_TIMEOUT] { - if $var(AuthError) != "null" { # null is converted in string by json_get_field - xlog("CGR_AUTH_ERROR: $var(AuthError)"); - sl_send_reply("503","CGR_AUTH_ERROR"); - exit; - } - if $var(max_session_time) != -1 && !dlg_set_timeout("$var(max_session_time)") { - sl_send_reply("503","CGR_MAX_SESSION_TIME_ERROR"); - exit; - } - route(RELAY); + $avp(CgrMaxSessionTime) = $(var(MaxSessionTime){s.int}); + t_continue("$var(id_index)", "$var(id_label)", "CGRATES_AUTH_REPLY"); # Unpark the transaction } # CGRateS request for session disconnect @@ -129,4 +103,5 @@ route[CGR_CALL_END] { \"cgr_destination\":\"$dlg_var(cgrDestination)\", \"cgr_answertime\":\"$dlg(start_ts)\", \"cgr_duration\":\"$var(callDur)\"}"); -} \ No newline at end of file +} + diff --git a/data/tutorials/kamevapi/kamailio/etc/kamailio/kamailio.cfg b/data/tutorials/kamevapi/kamailio/etc/kamailio/kamailio.cfg index 3dcb11b63..77293cd28 100644 --- a/data/tutorials/kamevapi/kamailio/etc/kamailio/kamailio.cfg +++ b/data/tutorials/kamevapi/kamailio/etc/kamailio/kamailio.cfg @@ -167,10 +167,45 @@ request_route { route(RELAY); } dlg_manage(); - route(CGR_AUTH_REQUEST); + switch ($fU) { + case 1001: + case 1006: + case 1007: + $dlg_var(cgrReqType) = "*prepaid"; + break; + case 1002: + $dlg_var(cgrReqType) = "*postpaid"; + break; + case 1003: + $dlg_var(cgrReqType) = "*pseudoprepaid"; + break; + default: + $dlg_var(cgrReqType) = "*rated"; + } + $dlg_var(cgrTenant) = "cgrates.org"; + $dlg_var(cgrAccount) = $fU; + $dlg_var(cgrDestination) = $rU; + route(CGRATES_AUTH_REQUEST); # Will be answered in CGRATES_AUTH_REPLY exit; } +# Here will land requests after processing them with CGRateS. Call RELAY or other routes following this route +route[CGRATES_AUTH_REPLY] { + if $avp(CgrError) != "null" { # null is converted in string by json_get_field + xlog("CGR_AUTH_ERROR: $avp(CgrError)"); + sl_send_reply("503","CGR_ERROR"); + exit; + } + if $avp(CgrMaxSessionTime) != -1 && !dlg_set_timeout("$avp(CgrMaxSessionTime)") { + sl_send_reply("503","CGR_MAX_SESSION_TIME_ERROR"); + exit; + } + if $avp(CgrSuppliers) != "" { # Enforce the supplier variable to the first one received from CGRateS, more for testing purposes + $dlg_var(supplier) = $(avp(CgrSuppliers){s.select,0,,}); + } + route(RELAY); +} + # Wrapper for relaying requests route[RELAY] { # enable additional event routes for forwarded requests diff --git a/engine/action.go b/engine/action.go index 7c4cd6022..214611bc5 100644 --- a/engine/action.go +++ b/engine/action.go @@ -175,7 +175,6 @@ func parseTemplateValue(rsrFlds utils.RSRFields, acnt *Account, action *Action) } func cdrLogAction(acc *Account, sq *StatsQueueTriggered, a *Action, acs Actions) (err error) { - defaultTemplate := map[string]utils.RSRFields{ "TOR": utils.ParseRSRFieldsMustCompile("balance_type", utils.INFIELD_SEP), "CdrHost": utils.ParseRSRFieldsMustCompile("^127.0.0.1", utils.INFIELD_SEP), diff --git a/engine/lcr.go b/engine/lcr.go index 8f7b858c4..d83efcd53 100644 --- a/engine/lcr.go +++ b/engine/lcr.go @@ -19,12 +19,15 @@ along with this program. If not, see package engine import ( + "errors" + "fmt" "sort" "strconv" "strings" "time" "github.com/cgrates/cgrates/cache2go" + "github.com/cgrates/cgrates/config" "github.com/cgrates/cgrates/utils" ) @@ -36,6 +39,74 @@ const ( LCR_STRATEGY_QOS = "*qos" ) +// A request for LCR, used in APIer and SM where we need to expose it +type LcrRequest struct { + Direction string + Tenant string + Category string + Account string + Subject string + Destination string + StartTime string + Duration string +} + +func (self *LcrRequest) AsCallDescriptor() (*CallDescriptor, error) { + if len(self.Account) == 0 || len(self.Destination) == 0 { + return nil, errors.New(utils.ERR_MANDATORY_IE_MISSING) + } + // Set defaults + if len(self.Direction) == 0 { + self.Direction = utils.OUT + } + if len(self.Tenant) == 0 { + self.Tenant = config.CgrConfig().DefaultTenant + } + if len(self.Category) == 0 { + self.Category = config.CgrConfig().DefaultCategory + } + if len(self.Subject) == 0 { + self.Subject = self.Account + } + var timeStart time.Time + var err error + if len(self.StartTime) == 0 { + timeStart = time.Now() + } else if timeStart, err = utils.ParseTimeDetectLayout(self.StartTime); err != nil { + return nil, err + } + var callDur time.Duration + if len(self.Duration) == 0 { + callDur = time.Duration(1) * time.Minute + } else if callDur, err = utils.ParseDurationWithSecs(self.Duration); err != nil { + return nil, err + } + return &CallDescriptor{ + Direction: self.Direction, + Tenant: self.Tenant, + Category: self.Category, + Account: self.Account, + Subject: self.Subject, + Destination: self.Destination, + TimeStart: timeStart, + TimeEnd: timeStart.Add(callDur), + }, nil +} + +// A LCR reply, used in APIer and SM where we need to expose it +type LcrReply struct { + DestinationId string + RPCategory string + Strategy string + Suppliers []*LcrSupplier +} + +// One supplier out of LCR reply +type LcrSupplier struct { + Supplier string + Cost float64 +} + type LCR struct { Direction string Tenant string @@ -211,6 +282,42 @@ func (lc *LCRCost) Sort() { } } +func (lc *LCRCost) HasErrors() bool { + for _, supplCost := range lc.SupplierCosts { + if supplCost.Error != nil { + return true + } + } + return false +} + +func (lc *LCRCost) LogErrors() { + for _, supplCost := range lc.SupplierCosts { + if supplCost.Error != nil { + Logger.Err(fmt.Sprintf("LCR_ERROR: supplier <%s>, error <%s>")) + } + } +} + +// Returns a list of suppliers separated via +func (lc *LCRCost) SuppliersString() (string, error) { + supplStr := "" + if lc.Entry == nil { + return "", errors.New(utils.ERR_NOT_FOUND) + } + for idx, supplCost := range lc.SupplierCosts { + if dtcs, err := utils.NewDTCSFromRPKey(supplCost.Supplier); err != nil { + return "", err + } else { + if idx != 0 { + supplStr += utils.FIELDS_SEP + } + supplStr += dtcs.Subject + } + } + return supplStr, nil +} + type LowestSupplierCostSorter []*LCRSupplierCost func (lscs LowestSupplierCostSorter) Len() int { diff --git a/engine/lcr_test.go b/engine/lcr_test.go index c98b8ab04..d70eb2358 100644 --- a/engine/lcr_test.go +++ b/engine/lcr_test.go @@ -19,9 +19,13 @@ along with this program. If not, see package engine import ( + "reflect" "sort" "testing" "time" + + "github.com/cgrates/cgrates/config" + "github.com/cgrates/cgrates/utils" ) func TestLcrQOSSorter(t *testing.T) { @@ -201,3 +205,49 @@ func TestLcrGet(t *testing.T) { t.Errorf("Bad lcr: %+v, %v", lcr, err) } } + +func TestLcrRequestAsCallDescriptor(t *testing.T) { + sTime := time.Date(2015, 04, 06, 17, 40, 0, 0, time.UTC) + callDur := time.Duration(1) * time.Minute + lcrReq := &LcrRequest{Account: "1001", StartTime: sTime.String()} + if _, err := lcrReq.AsCallDescriptor(); err == nil || err.Error() != utils.ERR_MANDATORY_IE_MISSING { + t.Error("Unexpected error received: %v", err) + } + lcrReq = &LcrRequest{Account: "1001", Destination: "1002", StartTime: sTime.String()} + eCd := &CallDescriptor{ + Direction: utils.OUT, + Tenant: config.CgrConfig().DefaultTenant, + Category: config.CgrConfig().DefaultCategory, + Account: lcrReq.Account, + Subject: lcrReq.Account, + Destination: lcrReq.Destination, + TimeStart: sTime, + TimeEnd: sTime.Add(callDur), + } + if cd, err := lcrReq.AsCallDescriptor(); err != nil { + t.Error(err) + } else if !reflect.DeepEqual(eCd, cd) { + t.Errorf("Expected: %+v, received: %+v", eCd, cd) + } +} + +func TestLCRCostSuppliersString(t *testing.T) { + lcrCost := new(LCRCost) + if _, err := lcrCost.SuppliersString(); err == nil || err.Error() != utils.ERR_NOT_FOUND { + t.Errorf("Unexpected error received: %v", err) + } + lcrCost = &LCRCost{ + Entry: &LCREntry{DestinationId: utils.ANY, RPCategory: "call", Strategy: LCR_STRATEGY_STATIC, StrategyParams: "ivo12;dan12;rif12", Weight: 10.0}, + SupplierCosts: []*LCRSupplierCost{ + &LCRSupplierCost{Supplier: "*out:tenant12:call:ivo12", Cost: 1.8, Duration: 60 * time.Second}, + &LCRSupplierCost{Supplier: "*out:tenant12:call:dan12", Cost: 0.6, Duration: 60 * time.Second}, + &LCRSupplierCost{Supplier: "*out:tenant12:call:rif12", Cost: 1.2, Duration: 60 * time.Second}, + }, + } + eSupplStr := "ivo12,dan12,rif12" + if supplStr, err := lcrCost.SuppliersString(); err != nil { + t.Error(err) + } else if supplStr != eSupplStr { + t.Errorf("Expecting: %s, received: %s", eSupplStr, supplStr) + } +} diff --git a/sessionmanager/kamailiosm.go b/sessionmanager/kamailiosm.go index 94abc2499..1fa8c582b 100644 --- a/sessionmanager/kamailiosm.go +++ b/sessionmanager/kamailiosm.go @@ -48,12 +48,13 @@ func (self *KamailioSessionManager) onCgrAuth(evData []byte, connId string) { kev, err := NewKamEvent(evData) if err != nil { engine.Logger.Info(fmt.Sprintf(" ERROR unmarshalling event: %s, error: %s", evData, err.Error())) + return } if kev.GetReqType(utils.META_DEFAULT) == utils.META_NONE { // Do not process this request return } if kev.MissingParameter() { - if kar, err := kev.AsKamAuthReply(0.0, errors.New(utils.ERR_MANDATORY_IE_MISSING)); err != nil { + if kar, err := kev.AsKamAuthReply(0.0, "", errors.New(utils.ERR_MANDATORY_IE_MISSING)); err != nil { engine.Logger.Err(fmt.Sprintf(" Failed building auth reply %s", err.Error())) } else if err = self.conns[connId].Send(kar.String()); err != nil { engine.Logger.Err(fmt.Sprintf(" Failed sending auth reply %s", err.Error())) @@ -61,16 +62,61 @@ func (self *KamailioSessionManager) onCgrAuth(evData []byte, connId string) { return } var remainingDuration float64 - if err = self.rater.GetDerivedMaxSessionTime(*kev.AsStoredCdr(), &remainingDuration); err != nil { - engine.Logger.Err(fmt.Sprintf(" Could not get max session time for %s, error: %s", kev.GetUUID(), err.Error())) + var errMaxSession error + if errMaxSession = self.rater.GetDerivedMaxSessionTime(*kev.AsStoredCdr(), &remainingDuration); errMaxSession != nil { + engine.Logger.Err(fmt.Sprintf(" Could not get max session time for %s, error: %s", kev.GetUUID(), errMaxSession.Error())) } - if kar, err := kev.AsKamAuthReply(remainingDuration, err); err != nil { + var supplStr string + var errSuppl error + if kev.ComputeLcr() { + if supplStr, errSuppl = self.getSuppliers(kev); errSuppl != nil { + engine.Logger.Err(fmt.Sprintf(" Could not get suppliers for %s, error: %s", kev.GetUUID(), errSuppl.Error())) + } + } + if errMaxSession == nil { // Overwrite the error from maxSessionTime with the one from suppliers if nil + errMaxSession = errSuppl + } + if kar, err := kev.AsKamAuthReply(remainingDuration, supplStr, errMaxSession); err != nil { engine.Logger.Err(fmt.Sprintf(" Failed building auth reply %s", err.Error())) } else if err = self.conns[connId].Send(kar.String()); err != nil { engine.Logger.Err(fmt.Sprintf(" Failed sending auth reply %s", err.Error())) } } +func (self *KamailioSessionManager) onCgrLcrReq(evData []byte, connId string) { + kev, err := NewKamEvent(evData) + if err != nil { + engine.Logger.Info(fmt.Sprintf(" ERROR unmarshalling event: %s, error: %s", evData, err.Error())) + return + } + supplStr, err := self.getSuppliers(kev) + kamLcrReply, errReply := kev.AsKamAuthReply(-1.0, supplStr, err) + kamLcrReply.Event = CGR_LCR_REPLY // Hit the CGR_LCR_REPLY event route on Kamailio side + if errReply != nil { + engine.Logger.Err(fmt.Sprintf(" Failed building auth reply %s", errReply.Error())) + } else if err = self.conns[connId].Send(kamLcrReply.String()); err != nil { + engine.Logger.Err(fmt.Sprintf(" Failed sending lcr reply %s", err.Error())) + } +} + +func (self *KamailioSessionManager) getSuppliers(kev KamEvent) (string, error) { + cd, err := kev.AsCallDescriptor() + if err != nil { + engine.Logger.Info(fmt.Sprintf(" LCR_PREPROCESS_ERROR error: %s", err.Error())) + return "", errors.New("LCR_PREPROCESS_ERROR") + } + var lcr engine.LCRCost + if err = self.Rater().GetLCR(cd, &lcr); err != nil { + engine.Logger.Info(fmt.Sprintf(" LCR_API_ERROR error: %s", err.Error())) + return "", errors.New("LCR_API_ERROR") + } + if lcr.HasErrors() { + lcr.LogErrors() + return "", errors.New("LCR_COMPUTE_ERROR") + } + return lcr.SuppliersString() +} + func (self *KamailioSessionManager) onCallStart(evData []byte, connId string) { kamEv, err := NewKamEvent(evData) if err != nil { @@ -115,6 +161,7 @@ func (self *KamailioSessionManager) Connect() error { var err error eventHandlers := map[*regexp.Regexp][]func([]byte, string){ regexp.MustCompile("CGR_AUTH_REQUEST"): []func([]byte, string){self.onCgrAuth}, + regexp.MustCompile("CGR_LCR_REQUEST"): []func([]byte, string){self.onCgrLcrReq}, regexp.MustCompile("CGR_CALL_START"): []func([]byte, string){self.onCallStart}, regexp.MustCompile("CGR_CALL_END"): []func([]byte, string){self.onCallEnd}, } diff --git a/sessionmanager/kamevent.go b/sessionmanager/kamevent.go index acb6478f3..7845e6255 100644 --- a/sessionmanager/kamevent.go +++ b/sessionmanager/kamevent.go @@ -33,7 +33,9 @@ import ( const ( EVENT = "event" CGR_AUTH_REQUEST = "CGR_AUTH_REQUEST" + CGR_LCR_REQUEST = "CGR_LCR_REQUEST" CGR_AUTH_REPLY = "CGR_AUTH_REPLY" + CGR_LCR_REPLY = "CGR_LCR_REPLY" CGR_SESSION_DISCONNECT = "CGR_SESSION_DISCONNECT" CGR_CALL_START = "CGR_CALL_START" CGR_CALL_END = "CGR_CALL_END" @@ -41,6 +43,7 @@ const ( CGR_ANSWERTIME = "cgr_answertime" CGR_STOPTIME = "cgr_stoptime" CGR_DURATION = "cgr_duration" + CGR_COMPUTELCR = "cgr_computelcr" KAM_TR_INDEX = "tr_index" KAM_TR_LABEL = "tr_label" HASH_ENTRY = "h_entry" @@ -55,7 +58,8 @@ type KamAuthReply struct { TransactionIndex int // Original transaction index TransactionLabel int // Original transaction label MaxSessionTime int // Maximum session time in case of success, -1 for unlimited - AuthError error // Reply in case of error + Suppliers string // List of suppliers, comma separated + Error error // Reply in case of error } func (self *KamAuthReply) String() string { @@ -63,6 +67,18 @@ func (self *KamAuthReply) String() string { return string(mrsh) } +type KamLcrReply struct { + Event string + Suppliers string + Error error +} + +func (self *KamLcrReply) String() string { + self.Event = CGR_LCR_REPLY + mrsh, _ := json.Marshal(self) + return string(mrsh) +} + type KamSessionDisconnect struct { Event string HashEntry string @@ -201,6 +217,12 @@ func (kev KamEvent) GetExtraFields() map[string]string { func (kev KamEvent) GetCdrSource() string { return "KAMAILIO_" + kev.GetName() } + +func (kev KamEvent) ComputeLcr() bool { + compute, _ := strconv.ParseBool(kev[CGR_COMPUTELCR]) + return compute +} + func (kev KamEvent) MissingParameter() bool { var nullTime time.Time switch kev.GetName() { @@ -211,6 +233,10 @@ func (kev KamEvent) MissingParameter() bool { return len(kev.GetAccount(utils.META_DEFAULT)) == 0 || len(kev.GetDestination(utils.META_DEFAULT)) == 0 || len(kev[KAM_TR_INDEX]) == 0 || len(kev[KAM_TR_LABEL]) == 0 + case CGR_LCR_REQUEST: + return len(kev.GetAccount(utils.META_DEFAULT)) == 0 || + len(kev.GetDestination(utils.META_DEFAULT)) == 0 || + len(kev[KAM_TR_INDEX]) == 0 || len(kev[KAM_TR_LABEL]) == 0 case CGR_CALL_START: if aTime, err := kev.GetAnswerTime(utils.META_DEFAULT); err != nil || aTime == nullTime { return true @@ -310,9 +336,9 @@ func (kev KamEvent) String() string { return string(mrsh) } -func (kev KamEvent) AsKamAuthReply(maxSessionTime float64, authErr error) (*KamAuthReply, error) { +func (kev KamEvent) AsKamAuthReply(maxSessionTime float64, suppliers string, resErr error) (*KamAuthReply, error) { var err error - kar := &KamAuthReply{Event: CGR_AUTH_REPLY} + kar := &KamAuthReply{Event: CGR_AUTH_REPLY, Suppliers: suppliers, Error: resErr} if _, hasIt := kev[KAM_TR_INDEX]; !hasIt { return nil, fmt.Errorf("%s:%s", utils.ERR_MANDATORY_IE_MISSING, KAM_TR_INDEX) } @@ -330,7 +356,20 @@ func (kev KamEvent) AsKamAuthReply(maxSessionTime float64, authErr error) (*KamA maxSessionTime = maxSessionDur.Seconds() } kar.MaxSessionTime = int(utils.Round(maxSessionTime, 0, utils.ROUNDING_MIDDLE)) - kar.AuthError = authErr - return kar, nil } + +// Converts into CallDescriptor due to responder interface needs +func (kev KamEvent) AsCallDescriptor() (*engine.CallDescriptor, error) { + lcrReq := &engine.LcrRequest{ + Direction: kev.GetDirection(utils.META_DEFAULT), + Tenant: kev.GetTenant(utils.META_DEFAULT), + Category: kev.GetCategory(utils.META_DEFAULT), + Account: kev.GetAccount(utils.META_DEFAULT), + Subject: kev.GetSubject(utils.META_DEFAULT), + Destination: kev.GetDestination(utils.META_DEFAULT), + StartTime: utils.FirstNonEmpty(kev[CGR_SETUPTIME], kev[CGR_ANSWERTIME]), + Duration: kev[CGR_DURATION], + } + return lcrReq.AsCallDescriptor() +} diff --git a/sessionmanager/kamevent_test.go b/sessionmanager/kamevent_test.go index 619e6ba7d..fff4643c2 100644 --- a/sessionmanager/kamevent_test.go +++ b/sessionmanager/kamevent_test.go @@ -21,7 +21,9 @@ package sessionmanager import ( "reflect" "testing" + "time" + "github.com/cgrates/cgrates/config" "github.com/cgrates/cgrates/engine" "github.com/cgrates/cgrates/utils" ) @@ -57,8 +59,8 @@ func TestNewKamEvent(t *testing.T) { } func TestKevAsKamAuthReply(t *testing.T) { - expectedKar := &KamAuthReply{Event: CGR_AUTH_REPLY, TransactionIndex: 29223, TransactionLabel: 698469260, MaxSessionTime: 1200} - if rcvKar, err := kamEv.AsKamAuthReply(1200000000000.0, nil); err != nil { + expectedKar := &KamAuthReply{Event: CGR_AUTH_REPLY, TransactionIndex: 29223, TransactionLabel: 698469260, MaxSessionTime: 1200, Suppliers: "supplier1,supplier2"} + if rcvKar, err := kamEv.AsKamAuthReply(1200000000000.0, "supplier1,supplier2", nil); err != nil { t.Error(err) } else if !reflect.DeepEqual(expectedKar, rcvKar) { t.Error("Received KAR: ", rcvKar) @@ -79,6 +81,15 @@ func TestKevMissingParameter(t *testing.T) { if !kamEv.MissingParameter() { t.Error("Failed detecting missing parameters") } + kamEv = KamEvent{"event": CGR_LCR_REQUEST, "tr_index": "36045", "tr_label": "612369399", "cgr_reqtype": utils.META_POSTPAID, + "cgr_account": "1001"} + if !kamEv.MissingParameter() { + t.Error("Failed detecting missing parameters") + } + kamEv = KamEvent{"event": CGR_LCR_REQUEST, CGR_ACCOUNT: "1001", CGR_DESTINATION: "1002", "tr_index": "36045", "tr_label": "612369399"} + if kamEv.MissingParameter() { + t.Error("False detecting missing parameters") + } kamEv = KamEvent{"event": "CGR_CALL_START", "callid": "9d28ec3ee068babdfe036623f42c0969@0:0:0:0:0:0:0:0", "from_tag": "3131b566", "cgr_reqtype": utils.META_POSTPAID, "cgr_account": "1001", "cgr_destination": "1002"} if !kamEv.MissingParameter() { @@ -91,3 +102,24 @@ func TestKevMissingParameter(t *testing.T) { t.Error("False detecting missing parameters") } } + +func TestKevAsCallDescriptor(t *testing.T) { + sTime := time.Date(2013, 12, 7, 8, 42, 24, 0, time.UTC) + kamEv := KamEvent{"event": CGR_LCR_REQUEST, CGR_ACCOUNT: "1001", CGR_DESTINATION: "1002", CGR_SETUPTIME: sTime.String()} + eCd := &engine.CallDescriptor{ + Direction: utils.OUT, + Tenant: config.CgrConfig().DefaultTenant, + Category: config.CgrConfig().DefaultCategory, + Account: kamEv[CGR_ACCOUNT], + Subject: kamEv[CGR_ACCOUNT], + Destination: kamEv[CGR_DESTINATION], + TimeStart: sTime, + TimeEnd: sTime.Add(time.Duration(1) * time.Minute), + } + + if cd, err := kamEv.AsCallDescriptor(); err != nil { + t.Error(err) + } else if !reflect.DeepEqual(eCd, cd) { + t.Errorf("Expecting: %+v, received: %+v", eCd, cd) + } +} From 73be367dd7321b9c5465e0953dd2f3447062575e Mon Sep 17 00:00:00 2001 From: DanB Date: Sun, 24 May 2015 17:43:37 +0200 Subject: [PATCH 03/10] Better LCR error handling, SM-Kamailio fixes for LCR handling --- .../etc/kamailio/kamailio-cgrates.cfg | 14 ++++++---- .../kamailio/etc/kamailio/kamailio.cfg | 19 ++++++-------- engine/calldesc.go | 26 ++++++++++++++++--- engine/lcr.go | 2 +- sessionmanager/kamailiosm.go | 13 ++++++---- sessionmanager/kamevent.go | 19 ++++++++------ utils/consts.go | 1 + 7 files changed, 60 insertions(+), 34 deletions(-) diff --git a/data/tutorials/kamevapi/kamailio/etc/kamailio/kamailio-cgrates.cfg b/data/tutorials/kamevapi/kamailio/etc/kamailio/kamailio-cgrates.cfg index 2d7b5fb73..2dfccbc20 100644 --- a/data/tutorials/kamevapi/kamailio/etc/kamailio/kamailio-cgrates.cfg +++ b/data/tutorials/kamevapi/kamailio/etc/kamailio/kamailio-cgrates.cfg @@ -52,11 +52,13 @@ route[CGR_AUTH_REPLY] { json_get_field("$evapi(msg)", "TransactionIndex", "$var(TransactionIndex)"); json_get_field("$evapi(msg)", "TransactionLabel", "$var(TransactionLabel)"); json_get_field("$evapi(msg)", "MaxSessionTime", "$var(MaxSessionTime)"); - json_get_field("$evapi(msg)", "Suppliers", "$avp(CgrSuppliers)"); - json_get_field("$evapi(msg)", "Error", "$avp(CgrError)"); + json_get_field("$evapi(msg)", "Suppliers", "$var(Suppliers)"); + json_get_field("$evapi(msg)", "Error", "$var(Error)"); $var(id_index) = $(var(TransactionIndex){s.int}); $var(id_label) = $(var(TransactionLabel){s.int}); - $avp(CgrMaxSessionTime) = $(var(MaxSessionTime){s.int}); + $var(CgrMaxSessionTime) = $(var(MaxSessionTime){s.int}); + $var(CgrSuppliers) = $(var(Suppliers){s.rm,"}); + $var(CgrError) = $(var(Error){s.rm,"}); t_continue("$var(id_index)", "$var(id_label)", "CGRATES_AUTH_REPLY"); # Unpark the transaction } @@ -84,7 +86,8 @@ route[CGR_CALL_START] { \"cgr_tenant\":\"$dlg_var(cgrTenant)\", \"cgr_account\":\"$dlg_var(cgrAccount)\", \"cgr_destination\":\"$dlg_var(cgrDestination)\", - \"cgr_answertime\":\"$TS\"}"); + \"cgr_answertime\":\"$TS\", + \"cgr_supplier\":\"$dlg_var(cgrSupplier)\"}"); } # Inform CGRateS about CALL_END (stop debit loops, perform accounting if desired in this way) @@ -102,6 +105,7 @@ route[CGR_CALL_END] { \"cgr_account\":\"$dlg_var(cgrAccount)\", \"cgr_destination\":\"$dlg_var(cgrDestination)\", \"cgr_answertime\":\"$dlg(start_ts)\", - \"cgr_duration\":\"$var(callDur)\"}"); + \"cgr_duration\":\"$var(callDur)\", + \"cgr_supplier\":\"$dlg_var(cgrSupplier)\"}"); } diff --git a/data/tutorials/kamevapi/kamailio/etc/kamailio/kamailio.cfg b/data/tutorials/kamevapi/kamailio/etc/kamailio/kamailio.cfg index 77293cd28..956d7a611 100644 --- a/data/tutorials/kamevapi/kamailio/etc/kamailio/kamailio.cfg +++ b/data/tutorials/kamevapi/kamailio/etc/kamailio/kamailio.cfg @@ -41,7 +41,6 @@ loadmodule "sanity.so" loadmodule "ctl.so" loadmodule "mi_rpc.so" loadmodule "nathelper.so" -loadmodule "rtpproxy.so" loadmodule "htable.so" loadmodule "auth.so" loadmodule "evapi.so" @@ -75,9 +74,6 @@ modparam("dialog", "dlg_flag", FLT_DIALOG) modparam("dialog", "send_bye", 1) modparam("dialog", "timeout_noreset", 1) -# ----- rtpproxy params ----- -modparam("rtpproxy", "rtpproxy_sock", "udp:127.0.0.1:7722") - # ----- nathelper params ----- modparam("nathelper", "natping_interval", 30) modparam("nathelper", "ping_nated_only", 1) @@ -191,17 +187,20 @@ request_route { # Here will land requests after processing them with CGRateS. Call RELAY or other routes following this route route[CGRATES_AUTH_REPLY] { - if $avp(CgrError) != "null" { # null is converted in string by json_get_field - xlog("CGR_AUTH_ERROR: $avp(CgrError)"); + xlog("CGRATES_AUTH_REPLY reply, got CgrError: $var(CgrError)"); + if $var(CgrError) != "" { + xlog("CGR_AUTH_ERROR: $var(CgrError)"); sl_send_reply("503","CGR_ERROR"); exit; } - if $avp(CgrMaxSessionTime) != -1 && !dlg_set_timeout("$avp(CgrMaxSessionTime)") { + xlog("CGRATES_AUTH_REPLY, CgrMaxSessionTime: $var(CgrMaxSessionTime)"); + if $var(CgrMaxSessionTime) != -1 && !dlg_set_timeout("$var(CgrMaxSessionTime)") { sl_send_reply("503","CGR_MAX_SESSION_TIME_ERROR"); exit; } - if $avp(CgrSuppliers) != "" { # Enforce the supplier variable to the first one received from CGRateS, more for testing purposes - $dlg_var(supplier) = $(avp(CgrSuppliers){s.select,0,,}); + xlog("CGRATES_AUTH_REPLY, CgrSuppliers: $var(CgrSuppliers)"); + if $var(CgrSuppliers) != "" { # Enforce the supplier variable to the first one received from CGRateS, more for testing purposes + $dlg_var(cgrSupplier) = $(var(CgrSuppliers){s.select,0,,}); } route(RELAY); } @@ -353,8 +352,6 @@ route[NATMANAGE] { if (!(isflagset(FLT_NATS) || isbflagset(FLB_NATB))) return; - rtpproxy_manage("co"); - if (is_request()) { if (!has_totag()) { if(t_is_branch_route()) { diff --git a/engine/calldesc.go b/engine/calldesc.go index 94265e513..3c394443a 100644 --- a/engine/calldesc.go +++ b/engine/calldesc.go @@ -785,11 +785,20 @@ func (cd *CallDescriptor) GetLCR(stats StatsInterface) (*LCRCost, error) { tccNeverConsidered := true if lcrCost.Entry.Strategy == LCR_STRATEGY_QOS || lcrCost.Entry.Strategy == LCR_STRATEGY_QOS_THRESHOLD { if stats == nil { - Logger.Warning(fmt.Sprintf("LCR_WARNING: Ignoring supplier: %s, lcr strategy: %s - no cdr_stats service configured.", supplier, lcrCost.Entry.Strategy)) + lcrCost.SupplierCosts = append(lcrCost.SupplierCosts, &LCRSupplierCost{ + Supplier: supplier, + Error: errors.New("Cdr stats service not configured"), + }) continue } rpfKey := utils.ConcatenatedKey(ratingProfileSearchKey, supplier) - if rpf, err := dataStorage.GetRatingProfile(rpfKey, false); err == nil || rpf != nil { + if rpf, err := dataStorage.GetRatingProfile(rpfKey, false); err != nil { + lcrCost.SupplierCosts = append(lcrCost.SupplierCosts, &LCRSupplierCost{ + Supplier: supplier, + Error: fmt.Errorf("Rating plan error: %s", err.Error()), + }) + continue + } else if rpf != nil { rpf.RatingPlanActivations.Sort() activeRas := rpf.RatingPlanActivations.GetActiveForCall(cd) var cdrStatsQueueIds []string @@ -800,11 +809,16 @@ func (cd *CallDescriptor) GetLCR(stats StatsInterface) (*LCRCost, error) { } } } - + statsErr := false for _, qId := range cdrStatsQueueIds { statValues := make(map[string]float64) if err := stats.GetValues(qId, &statValues); err != nil { - Logger.Warning(fmt.Sprintf("Error getting stats values for queue id %s: %v", qId, err)) + lcrCost.SupplierCosts = append(lcrCost.SupplierCosts, &LCRSupplierCost{ + Supplier: supplier, + Error: fmt.Errorf("Get stats values for queue id %s, error %s", qId, err.Error()), + }) + statsErr = true + break } if asr, exists := statValues[ASR]; exists { if asr > STATS_NA { @@ -837,6 +851,9 @@ func (cd *CallDescriptor) GetLCR(stats StatsInterface) (*LCRCost, error) { tccNeverConsidered = false } } + if statsErr { // Stats error in loop, to go next supplier + continue + } asrValues.Sort() acdValues.Sort() tcdValues.Sort() @@ -885,6 +902,7 @@ func (cd *CallDescriptor) GetLCR(stats StatsInterface) (*LCRCost, error) { } } } + var cc *CallCost var err error //log.Print("CD: ", lcrCD.GetAccountKey()) diff --git a/engine/lcr.go b/engine/lcr.go index d83efcd53..d6b559472 100644 --- a/engine/lcr.go +++ b/engine/lcr.go @@ -294,7 +294,7 @@ func (lc *LCRCost) HasErrors() bool { func (lc *LCRCost) LogErrors() { for _, supplCost := range lc.SupplierCosts { if supplCost.Error != nil { - Logger.Err(fmt.Sprintf("LCR_ERROR: supplier <%s>, error <%s>")) + Logger.Err(fmt.Sprintf("LCR_ERROR: supplier <%s>, error <%s>", supplCost.Supplier, supplCost.Error)) } } } diff --git a/sessionmanager/kamailiosm.go b/sessionmanager/kamailiosm.go index 1fa8c582b..4afab5904 100644 --- a/sessionmanager/kamailiosm.go +++ b/sessionmanager/kamailiosm.go @@ -64,13 +64,13 @@ func (self *KamailioSessionManager) onCgrAuth(evData []byte, connId string) { var remainingDuration float64 var errMaxSession error if errMaxSession = self.rater.GetDerivedMaxSessionTime(*kev.AsStoredCdr(), &remainingDuration); errMaxSession != nil { - engine.Logger.Err(fmt.Sprintf(" Could not get max session time for %s, error: %s", kev.GetUUID(), errMaxSession.Error())) + engine.Logger.Err(fmt.Sprintf(" Could not get max session time, error: %s", errMaxSession.Error())) } var supplStr string var errSuppl error if kev.ComputeLcr() { if supplStr, errSuppl = self.getSuppliers(kev); errSuppl != nil { - engine.Logger.Err(fmt.Sprintf(" Could not get suppliers for %s, error: %s", kev.GetUUID(), errSuppl.Error())) + engine.Logger.Err(fmt.Sprintf(" Could not get suppliers, error: %s", errSuppl.Error())) } } if errMaxSession == nil { // Overwrite the error from maxSessionTime with the one from suppliers if nil @@ -86,7 +86,7 @@ func (self *KamailioSessionManager) onCgrAuth(evData []byte, connId string) { func (self *KamailioSessionManager) onCgrLcrReq(evData []byte, connId string) { kev, err := NewKamEvent(evData) if err != nil { - engine.Logger.Info(fmt.Sprintf(" ERROR unmarshalling event: %s, error: %s", evData, err.Error())) + engine.Logger.Info(fmt.Sprintf(" ERROR unmarshalling event: %s, error: %s", string(evData), err.Error())) return } supplStr, err := self.getSuppliers(kev) @@ -121,15 +121,16 @@ func (self *KamailioSessionManager) onCallStart(evData []byte, connId string) { kamEv, err := NewKamEvent(evData) if err != nil { engine.Logger.Err(fmt.Sprintf(" ERROR unmarshalling event: %s, error: %s", evData, err.Error())) + return } if kamEv.GetReqType(utils.META_DEFAULT) == utils.META_NONE { // Do not process this request return } if kamEv.MissingParameter() { - self.DisconnectSession(kamEv, "", utils.ERR_MANDATORY_IE_MISSING) + self.DisconnectSession(kamEv, connId, utils.ERR_MANDATORY_IE_MISSING) return } - s := NewSession(kamEv, "", self) + s := NewSession(kamEv, connId, self) if s != nil { self.sessions = append(self.sessions, s) } @@ -139,6 +140,7 @@ func (self *KamailioSessionManager) onCallEnd(evData []byte, connId string) { kev, err := NewKamEvent(evData) if err != nil { engine.Logger.Err(fmt.Sprintf(" ERROR unmarshalling event: %s, error: %s", evData, err.Error())) + return } if kev.GetReqType(utils.META_DEFAULT) == utils.META_NONE { // Do not process this request return @@ -182,6 +184,7 @@ func (self *KamailioSessionManager) Connect() error { } func (self *KamailioSessionManager) DisconnectSession(ev engine.Event, connId, notify string) error { + engine.Logger.Debug(fmt.Sprintf("DisconnectSession, ev: %+v, connId: %s, notify: %s", ev, connId, notify)) sessionIds := ev.GetSessionIds() disconnectEv := &KamSessionDisconnect{Event: CGR_SESSION_DISCONNECT, HashEntry: sessionIds[0], HashId: sessionIds[1], Reason: notify} if err := self.conns[connId].Send(disconnectEv.String()); err != nil { diff --git a/sessionmanager/kamevent.go b/sessionmanager/kamevent.go index 7845e6255..736395593 100644 --- a/sessionmanager/kamevent.go +++ b/sessionmanager/kamevent.go @@ -43,11 +43,11 @@ const ( CGR_ANSWERTIME = "cgr_answertime" CGR_STOPTIME = "cgr_stoptime" CGR_DURATION = "cgr_duration" - CGR_COMPUTELCR = "cgr_computelcr" - KAM_TR_INDEX = "tr_index" - KAM_TR_LABEL = "tr_label" - HASH_ENTRY = "h_entry" - HASH_ID = "h_id" + + KAM_TR_INDEX = "tr_index" + KAM_TR_LABEL = "tr_label" + HASH_ENTRY = "h_entry" + HASH_ID = "h_id" ) var primaryFields = []string{EVENT, CALLID, FROM_TAG, HASH_ENTRY, HASH_ID, CGR_ACCOUNT, CGR_SUBJECT, CGR_DESTINATION, @@ -59,7 +59,7 @@ type KamAuthReply struct { TransactionLabel int // Original transaction label MaxSessionTime int // Maximum session time in case of success, -1 for unlimited Suppliers string // List of suppliers, comma separated - Error error // Reply in case of error + Error string // Reply in case of error } func (self *KamAuthReply) String() string { @@ -219,7 +219,7 @@ func (kev KamEvent) GetCdrSource() string { } func (kev KamEvent) ComputeLcr() bool { - compute, _ := strconv.ParseBool(kev[CGR_COMPUTELCR]) + compute, _ := strconv.ParseBool(kev[utils.CGR_COMPUTELCR]) return compute } @@ -338,7 +338,10 @@ func (kev KamEvent) String() string { func (kev KamEvent) AsKamAuthReply(maxSessionTime float64, suppliers string, resErr error) (*KamAuthReply, error) { var err error - kar := &KamAuthReply{Event: CGR_AUTH_REPLY, Suppliers: suppliers, Error: resErr} + kar := &KamAuthReply{Event: CGR_AUTH_REPLY, Suppliers: suppliers} + if resErr != nil { + kar.Error = resErr.Error() + } if _, hasIt := kev[KAM_TR_INDEX]; !hasIt { return nil, fmt.Errorf("%s:%s", utils.ERR_MANDATORY_IE_MISSING, KAM_TR_INDEX) } diff --git a/utils/consts.go b/utils/consts.go index b2d44175e..856b2ff09 100644 --- a/utils/consts.go +++ b/utils/consts.go @@ -186,6 +186,7 @@ const ( CGR_SUPPLIER = "cgr_supplier" DISCONNECT_CAUSE = "disconnect_cause" CGR_DISCONNECT_CAUSE = "cgr_disconnect_cause" + CGR_COMPUTELCR = "cgr_computelcr" ) var ( From 83bab44b3cc7e61f7286807a4065b474d8b49193 Mon Sep 17 00:00:00 2001 From: DanB Date: Sun, 24 May 2015 18:32:46 +0200 Subject: [PATCH 04/10] LcgProfiles added in cache stats --- apier/v1/apier.go | 6 +++++- utils/apitpdata.go | 3 +++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/apier/v1/apier.go b/apier/v1/apier.go index 7579a3c45..21699f4db 100644 --- a/apier/v1/apier.go +++ b/apier/v1/apier.go @@ -915,6 +915,7 @@ func (self *ApierV1) GetCacheStats(attrs utils.AttrCacheStats, reply *utils.Cach cs.RatingAliases = cache2go.CountEntries(engine.RP_ALIAS_PREFIX) cs.AccountAliases = cache2go.CountEntries(engine.ACC_ALIAS_PREFIX) cs.DerivedChargers = cache2go.CountEntries(engine.DERIVEDCHARGERS_PREFIX) + cs.LcrProfiles = cache2go.CountEntries(engine.LCR_PREFIX) *reply = *cs return nil } @@ -926,7 +927,8 @@ func (self *ApierV1) GetCachedItemAge(itemId string, reply *utils.CachedItemAge) cachedItemAge := new(utils.CachedItemAge) var found bool for idx, cacheKey := range []string{engine.DESTINATION_PREFIX + itemId, engine.RATING_PLAN_PREFIX + itemId, engine.RATING_PROFILE_PREFIX + itemId, - engine.ACTION_PREFIX + itemId, engine.SHARED_GROUP_PREFIX + itemId, engine.RP_ALIAS_PREFIX + itemId, engine.ACC_ALIAS_PREFIX + itemId} { + engine.ACTION_PREFIX + itemId, engine.SHARED_GROUP_PREFIX + itemId, engine.RP_ALIAS_PREFIX + itemId, engine.ACC_ALIAS_PREFIX + itemId, + engine.LCR_PREFIX + itemId} { if age, err := cache2go.GetKeyAge(cacheKey); err == nil { found = true switch idx { @@ -944,6 +946,8 @@ func (self *ApierV1) GetCachedItemAge(itemId string, reply *utils.CachedItemAge) cachedItemAge.RatingAlias = age case 6: cachedItemAge.AccountAlias = age + case 7: + cachedItemAge.LcrProfiles = age } } } diff --git a/utils/apitpdata.go b/utils/apitpdata.go index 913d3e1f3..6e429a8e8 100644 --- a/utils/apitpdata.go +++ b/utils/apitpdata.go @@ -634,6 +634,7 @@ type ApiReloadCache struct { AccAliases []string LCRIds []string DerivedChargers []string + LcrProfiles []string } type AttrCacheStats struct { // Add in the future filters here maybe so we avoid counting complete cache @@ -648,6 +649,7 @@ type CacheStats struct { RatingAliases int AccountAliases int DerivedChargers int + LcrProfiles int } type AttrCachedItemAge struct { @@ -664,6 +666,7 @@ type CachedItemAge struct { RatingAlias time.Duration AccountAlias time.Duration DerivedChargers time.Duration + LcrProfiles time.Duration } type AttrExpFileCdrs struct { From a81bf953b7ee0ea3b526bfa6167aed244492db45 Mon Sep 17 00:00:00 2001 From: DanB Date: Sun, 24 May 2015 20:50:26 +0200 Subject: [PATCH 05/10] Tutorial LcrRules.csv fixups, adding TestTutLocalLcrStatic --- data/tariffplans/tutorial/Destinations.csv | 1 - data/tariffplans/tutorial/LcrRules.csv | 8 +- data/tariffplans/tutorial/RatingProfiles.csv | 10 +-- general_tests/tutorial_fs_calls_test.go | 13 --- general_tests/tutorial_kam_calls_test.go | 13 --- general_tests/tutorial_local_test.go | 92 +++++++++++++++++++- general_tests/tutorial_osips_calls_test.go | 13 --- 7 files changed, 99 insertions(+), 51 deletions(-) diff --git a/data/tariffplans/tutorial/Destinations.csv b/data/tariffplans/tutorial/Destinations.csv index 4205c695b..1c19d97ea 100644 --- a/data/tariffplans/tutorial/Destinations.csv +++ b/data/tariffplans/tutorial/Destinations.csv @@ -2,4 +2,3 @@ DST_1002,1002 DST_1003,1003 DST_FS,10 -DST_2002,2002 diff --git a/data/tariffplans/tutorial/LcrRules.csv b/data/tariffplans/tutorial/LcrRules.csv index 4eb2c4262..2d71fcd1f 100644 --- a/data/tariffplans/tutorial/LcrRules.csv +++ b/data/tariffplans/tutorial/LcrRules.csv @@ -1,9 +1,9 @@ #Direction,Tenant,Category,Account,Subject,DestinationId,RPCategory,Strategy,StrategyParams,ActivationTime,Weight -*out,cgrates.org,call,1001,*any,DST_2002,lcr_profile1,*static,suppl2;suppl1,2014-01-14T00:00:00Z,10 +*out,cgrates.org,call,1001,*any,DST_1002,lcr_profile1,*static,suppl2;suppl1,2014-01-14T00:00:00Z,10 *out,cgrates.org,call,1001,*any,*any,lcr_profile1,*static,suppl1;suppl2,2014-01-14T00:00:00Z,10 -*out,cgrates.org,call,1002,*any,DST_2002,lcr_profile1,*highest_cost,,2014-01-14T00:00:00Z,10 +*out,cgrates.org,call,1002,*any,DST_1002,lcr_profile1,*highest_cost,,2014-01-14T00:00:00Z,10 *out,cgrates.org,call,1002,*any,*any,lcr_profile1,*qos,,2014-01-14T00:00:00Z,10 -*out,cgrates.org,call,1003,*any,DST_2002,lcr_profile1,*qos_threshold,20;;2m;;;;;;;,2014-01-14T00:00:00Z,10 +*out,cgrates.org,call,1003,*any,DST_1002,lcr_profile1,*qos_threshold,20;;2m;;;;;;;,2014-01-14T00:00:00Z,10 *out,cgrates.org,call,1003,*any,*any,lcr_profile1,*qos_threshold,40;;4m;;;;;;;,2014-01-14T00:00:00Z,10 -*out,cgrates.org,call,*any,*any,DST_2002,lcr_profile2,*lowest_cost,,2014-01-14T00:00:00Z,10 +*out,cgrates.org,call,*any,*any,DST_1002,lcr_profile2,*lowest_cost,,2014-01-14T00:00:00Z,10 *out,cgrates.org,call,*any,*any,*any,lcr_profile1,*lowest_cost,,2014-01-14T00:00:00Z,10 \ No newline at end of file diff --git a/data/tariffplans/tutorial/RatingProfiles.csv b/data/tariffplans/tutorial/RatingProfiles.csv index 7889626aa..34dc82ad9 100644 --- a/data/tariffplans/tutorial/RatingProfiles.csv +++ b/data/tariffplans/tutorial/RatingProfiles.csv @@ -2,8 +2,8 @@ *out,cgrates.org,call,*any,2014-01-14T00:00:00Z,RP_RETAIL1,, *out,cgrates.org,call,1001;1006,2014-01-14T00:00:00Z,RP_RETAIL2,, *out,cgrates.org,call,SPECIAL_1002,2014-01-14T00:00:00Z,RP_SPECIAL_1002,, -*out,cgrates.org,lcr_profile1,supplier1,2014-01-14T00:00:00Z,RP_RETAIL1,,STATS_SUPPL1 -*out,cgrates.org,lcr_profile1,supplier2,2014-01-14T00:00:00Z,RP_RETAIL2,,STATS_SUPPL2 -*out,cgrates.org,lcr_profile2,supplier1,2014-01-14T00:00:00Z,RP_RETAIL2,,STATS_SUPPL1 -*out,cgrates.org,lcr_profile2,supplier2,2014-01-14T00:00:00Z,RP_RETAIL1,,STATS_SUPPL2 -*out,cgrates.org,lcr_profile2,supplier3,2014-01-14T00:00:00Z,RP_SPECIAL_1002,, +*out,cgrates.org,lcr_profile1,suppl1,2014-01-14T00:00:00Z,RP_RETAIL1,,STATS_SUPPL1 +*out,cgrates.org,lcr_profile1,suppl2,2014-01-14T00:00:00Z,RP_RETAIL2,,STATS_SUPPL2 +*out,cgrates.org,lcr_profile2,suppl1,2014-01-14T00:00:00Z,RP_RETAIL2,,STATS_SUPPL1 +*out,cgrates.org,lcr_profile2,suppl2,2014-01-14T00:00:00Z,RP_RETAIL1,,STATS_SUPPL2 +*out,cgrates.org,lcr_profile2,suppl3,2014-01-14T00:00:00Z,RP_SPECIAL_1002,, diff --git a/general_tests/tutorial_fs_calls_test.go b/general_tests/tutorial_fs_calls_test.go index cb2e903c9..2fb87c528 100644 --- a/general_tests/tutorial_fs_calls_test.go +++ b/general_tests/tutorial_fs_calls_test.go @@ -174,19 +174,6 @@ func TestTutFsCallsAccountsBefore(t *testing.T) { } } -func TestTutFsCallsCdrStats(t *testing.T) { - if !*testCalls { - return - } - var queueIds []string - eQueueIds := []string{"*default", "CDRST1", "CDRST_1001", "CDRST_1002", "CDRST_1003", "STATS_SUPPL1", "STATS_SUPPL2"} - if err := tutFsCallsRpc.Call("CDRStatsV1.GetQueueIds", "", &queueIds); err != nil { - t.Error("Calling CDRStatsV1.GetQueueIds, got error: ", err.Error()) - } else if len(eQueueIds) != len(queueIds) { - t.Errorf("Expecting: %v, received: %v", eQueueIds, queueIds) - } -} - // Start Pjsua as listener and register it to receive calls func TestTutFsCallsStartPjsuaListener(t *testing.T) { if !*testCalls { diff --git a/general_tests/tutorial_kam_calls_test.go b/general_tests/tutorial_kam_calls_test.go index df82f730c..eacaf9eae 100644 --- a/general_tests/tutorial_kam_calls_test.go +++ b/general_tests/tutorial_kam_calls_test.go @@ -172,19 +172,6 @@ func TestTutKamCallsAccountsBefore(t *testing.T) { } } -func TestTutKamCallsCdrStats(t *testing.T) { - if !*testCalls { - return - } - var queueIds []string - eQueueIds := []string{"*default", "CDRST1", "CDRST_1001", "CDRST_1002", "CDRST_1003", "STATS_SUPPL1", "STATS_SUPPL2"} - if err := tutKamCallsRpc.Call("CDRStatsV1.GetQueueIds", "", &queueIds); err != nil { - t.Error("Calling CDRStatsV1.GetQueueIds, got error: ", err.Error()) - } else if len(eQueueIds) != len(queueIds) { - t.Errorf("Expecting: %v, received: %v", eQueueIds, queueIds) - } -} - // Start Pjsua as listener and register it to receive calls func TestTutKamCallsStartPjsuaListener(t *testing.T) { if !*testCalls { diff --git a/general_tests/tutorial_local_test.go b/general_tests/tutorial_local_test.go index b09122bb4..fb19adddc 100644 --- a/general_tests/tutorial_local_test.go +++ b/general_tests/tutorial_local_test.go @@ -42,7 +42,7 @@ func TestTutLocalInitCfg(t *testing.T) { if !*testLocal { return } - tutLocalCfgPath = path.Join(*dataDir, "conf", "samples", "cgradmin") + tutLocalCfgPath = path.Join(*dataDir, "conf", "samples", "tutlocal") // Init config first var err error tutFsLocalCfg, err = config.NewCGRConfigFromFolder(tutLocalCfgPath) @@ -116,7 +116,8 @@ func TestTutLocalCacheStats(t *testing.T) { return } var rcvStats *utils.CacheStats - expectedStats := &utils.CacheStats{Destinations: 4, RatingPlans: 3, RatingProfiles: 8, Actions: 6, SharedGroups: 1, RatingAliases: 1, AccountAliases: 1, DerivedChargers: 1} + expectedStats := &utils.CacheStats{Destinations: 3, RatingPlans: 3, RatingProfiles: 8, Actions: 6, SharedGroups: 1, RatingAliases: 1, AccountAliases: 1, + DerivedChargers: 1, LcrProfiles: 4} var args utils.AttrCacheStats if err := tutLocalRpc.Call("ApierV1.GetCacheStats", args, &rcvStats); err != nil { t.Error("Got error on ApierV1.GetCacheStats: ", err.Error()) @@ -156,6 +157,17 @@ func TestTutLocalGetCachedItemAge(t *testing.T) { } else if rcvAge.SharedGroup > time.Duration(2)*time.Second { t.Errorf("Cache too old: %d", rcvAge) } + if err := tutLocalRpc.Call("ApierV1.GetCachedItemAge", "*out:cgrates.org:call:1001:*any", &rcvAge); err != nil { + t.Error("Got error on ApierV1.GetCachedItemAge: ", err.Error()) + } else if rcvAge.SharedGroup > time.Duration(2)*time.Second { + t.Errorf("Cache too old: %d", rcvAge) + } + if err := tutLocalRpc.Call("ApierV1.GetCachedItemAge", "*out:cgrates.org:call:*any:*any", &rcvAge); err != nil { + t.Error("Got error on ApierV1.GetCachedItemAge: ", err.Error()) + } else if rcvAge.SharedGroup > time.Duration(2)*time.Second { + t.Errorf("Cache too old: %d", rcvAge) + } + /* if err := tutLocalRpc.Call("ApierV1.GetCachedItemAge", "1006", &rcvAge); err != nil { t.Error("Got error on ApierV1.GetCachedItemAge: ", err.Error()) @@ -312,6 +324,82 @@ func TestTutLocalMaxDebit(t *testing.T) { } } +// Make sure queueids were created +func TestTutFsCallsCdrStats(t *testing.T) { + if !*testCalls { + return + } + var queueIds []string + eQueueIds := []string{"*default", "CDRST1", "CDRST_1001", "CDRST_1002", "CDRST_1003", "STATS_SUPPL1", "STATS_SUPPL2"} + if err := tutLocalRpc.Call("CDRStatsV1.GetQueueIds", "", &queueIds); err != nil { + t.Error("Calling CDRStatsV1.GetQueueIds, got error: ", err.Error()) + } else if len(eQueueIds) != len(queueIds) { + t.Errorf("Expecting: %v, received: %v", eQueueIds, queueIds) + } +} + +// Check LCR +//FixMe: +/*{"id":16,"result":{"Entry":{"DestinationId":"*any","RPCategory":"lcr_profile1","Strategy":"*static","StrategyParams":"suppl1;suppl2","Weight":10},"SupplierCosts":[{"Supplier":"*out:cgrates.org:lcr_profile1:suppl1","Cost":0,"Duration":0,"Error":{},"QOS":null},{"Supplier":"*out:cgrates.org:lcr_profile1:suppl2","Cost":0,"Duration":0,"Error":{},"QOS":null}]},"error":null} + */ + +func TestTutLocalLcrStatic(t *testing.T) { + if !*testLocal { + return + } + tStart, _ := utils.ParseDate("2014-08-04T13:00:00Z") + tEnd, _ := utils.ParseDate("2014-08-04T13:01:00Z") + cd := engine.CallDescriptor{ + Direction: "*out", + Category: "call", + Tenant: "cgrates.org", + Subject: "1001", + Account: "1001", + Destination: "1002", + TimeStart: tStart, + TimeEnd: tEnd, + } + eStLcr := &engine.LCRCost{ + Entry: &engine.LCREntry{DestinationId: "DST_1002", RPCategory: "lcr_profile1", Strategy: engine.LCR_STRATEGY_STATIC, StrategyParams: "suppl2;suppl1", Weight: 10.0}, + SupplierCosts: []*engine.LCRSupplierCost{ + &engine.LCRSupplierCost{Supplier: "*out:cgrates.org:lcr_profile1:suppl2", Cost: 0.6, Duration: 60 * time.Second}, + &engine.LCRSupplierCost{Supplier: "*out:cgrates.org:lcr_profile1:suppl1", Cost: 1.2, Duration: 60 * time.Second}, + }, + } + var lcr engine.LCRCost + if err := tutLocalRpc.Call("Responder.GetLCR", cd, &lcr); err != nil { + t.Error(err) + } else if !reflect.DeepEqual(eStLcr.Entry, lcr.Entry) { + t.Errorf("Expecting: %+v, received: %+v", eStLcr.Entry, lcr.Entry) + } else if !reflect.DeepEqual(eStLcr.SupplierCosts, lcr.SupplierCosts) { + t.Errorf("Expecting: %+v, received: %+v", eStLcr.SupplierCosts[0], lcr.SupplierCosts[0]) + } + cd = engine.CallDescriptor{ + Direction: "*out", + Category: "call", + Tenant: "cgrates.org", + Subject: "1001", + Account: "1001", + Destination: "1003", + TimeStart: tStart, + TimeEnd: tEnd, + } + eStLcr = &engine.LCRCost{ + Entry: &engine.LCREntry{DestinationId: utils.ANY, RPCategory: "lcr_profile1", Strategy: engine.LCR_STRATEGY_STATIC, StrategyParams: "suppl1;suppl2", Weight: 10.0}, + SupplierCosts: []*engine.LCRSupplierCost{ + &engine.LCRSupplierCost{Supplier: "*out:cgrates.org:lcr_profile1:suppl1", Cost: 1.2, Duration: 60 * time.Second}, + &engine.LCRSupplierCost{Supplier: "*out:cgrates.org:lcr_profile1:suppl2", Cost: 1.2, Duration: 60 * time.Second}, + }, + } + if err := tutLocalRpc.Call("Responder.GetLCR", cd, &lcr); err != nil { + t.Error(err) + } else if !reflect.DeepEqual(eStLcr.Entry, lcr.Entry) { + t.Errorf("Expecting: %+v, received: %+v", eStLcr.Entry, lcr.Entry) + } else if !reflect.DeepEqual(eStLcr.SupplierCosts, lcr.SupplierCosts) { + t.Errorf("Expecting: %+v, received: %+v", eStLcr.SupplierCosts[0], lcr.SupplierCosts[0]) + } +} + func TestTutLocalStopCgrEngine(t *testing.T) { if !*testLocal { return diff --git a/general_tests/tutorial_osips_calls_test.go b/general_tests/tutorial_osips_calls_test.go index d5faf1ea1..4849ad386 100644 --- a/general_tests/tutorial_osips_calls_test.go +++ b/general_tests/tutorial_osips_calls_test.go @@ -172,19 +172,6 @@ func TestTutOsipsCallsAccountsBefore(t *testing.T) { } } -func TestTutOsipsCallsCdrStats(t *testing.T) { - if !*testCalls { - return - } - var queueIds []string - eQueueIds := []string{"*default", "CDRST1", "CDRST_1001", "CDRST_1002", "CDRST_1003", "STATS_SUPPL1", "STATS_SUPPL2"} - if err := tutOsipsCallsRpc.Call("CDRStatsV1.GetQueueIds", "", &queueIds); err != nil { - t.Error("Calling CDRStatsV1.GetQueueIds, got error: ", err.Error()) - } else if len(eQueueIds) != len(queueIds) { - t.Errorf("Expecting: %v, received: %v", eQueueIds, queueIds) - } -} - // Start Pjsua as listener and register it to receive calls func TestTutOsipsCallsStartPjsuaListener(t *testing.T) { if !*testCalls { From 2776c66b5739f2d5cac4db5bccbb633fd85835b9 Mon Sep 17 00:00:00 2001 From: DanB Date: Sun, 24 May 2015 20:52:31 +0200 Subject: [PATCH 06/10] Small test fix --- apier/v1/apier_local_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apier/v1/apier_local_test.go b/apier/v1/apier_local_test.go index 6361ade72..1520e9a1b 100644 --- a/apier/v1/apier_local_test.go +++ b/apier/v1/apier_local_test.go @@ -1745,7 +1745,7 @@ func TestApierGetCacheStats3(t *testing.T) { return } var rcvStats *utils.CacheStats - expectedStats := &utils.CacheStats{Destinations: 4, RatingPlans: 3, RatingProfiles: 8, Actions: 6, SharedGroups: 1, RatingAliases: 1, AccountAliases: 1, DerivedChargers: 1} + expectedStats := &utils.CacheStats{Destinations: 3, RatingPlans: 3, RatingProfiles: 8, Actions: 6, SharedGroups: 1, RatingAliases: 1, AccountAliases: 1, DerivedChargers: 1} var args utils.AttrCacheStats if err := rater.Call("ApierV1.GetCacheStats", args, &rcvStats); err != nil { t.Error("Got error on ApierV1.GetCacheStats: ", err.Error()) From 060beeb182ab30dc2a59306d4af9506efd0025f8 Mon Sep 17 00:00:00 2001 From: DanB Date: Mon, 25 May 2015 12:17:33 +0200 Subject: [PATCH 07/10] Complete test suite for LCR strategies in Tutorial --- data/tariffplans/tutorial/CdrStats.csv | 20 +- data/tariffplans/tutorial/LcrRules.csv | 2 +- general_tests/tutorial_local_test.go | 324 ++++++++++++++++++++++++- 3 files changed, 331 insertions(+), 15 deletions(-) diff --git a/data/tariffplans/tutorial/CdrStats.csv b/data/tariffplans/tutorial/CdrStats.csv index 8720b4312..cadb59d11 100644 --- a/data/tariffplans/tutorial/CdrStats.csv +++ b/data/tariffplans/tutorial/CdrStats.csv @@ -12,13 +12,13 @@ CDRST_1002,,,ACD,,,,,,,,,,,,,,,,,,, CDRST_1002,,,ACC,,,,,,,,,,,,,,,,,,, CDRST_1003,,,ASR,,,,,,,cgrates.org,,,,1003,,,,default,,,,CDRST3_WARN CDRST_1003,,,ACD,,,,,,,,,,,,,,,,,,, -STATS_SUPPL1,,,ACD,,,,,,,,,,,,,supplier1,,,,,, -STATS_SUPPL1,,,ASR,,,,,,,,,,,,,supplier1,,,,,, -STATS_SUPPL1,,,ACC,,,,,,,,,,,,,supplier1,,,,,, -STATS_SUPPL1,,,TCD,,,,,,,,,,,,,supplier1,,,,,, -STATS_SUPPL1,,,TCC,,,,,,,,,,,,,supplier1,,,,,, -STATS_SUPPL2,,,ACD,,,,,,,,,,,,,supplier2,,,,,, -STATS_SUPPL2,,,ASR,,,,,,,,,,,,,supplier2,,,,,, -STATS_SUPPL2,,,ACC,,,,,,,,,,,,,supplier2,,,,,, -STATS_SUPPL2,,,TCD,,,,,,,,,,,,,supplier2,,,,,, -STATS_SUPPL2,,,TCC,,,,,,,,,,,,,supplier2,,,,,, +STATS_SUPPL1,,,ACD,,,,,,,,,,,,,suppl1,,,,,, +STATS_SUPPL1,,,ASR,,,,,,,,,,,,,suppl1,,,,,, +STATS_SUPPL1,,,ACC,,,,,,,,,,,,,suppl1,,,,,, +STATS_SUPPL1,,,TCD,,,,,,,,,,,,,suppl1,,,,,, +STATS_SUPPL1,,,TCC,,,,,,,,,,,,,suppl1,,,,,, +STATS_SUPPL2,,,ACD,,,,,,,,,,,,,suppl2,,,,,, +STATS_SUPPL2,,,ASR,,,,,,,,,,,,,suppl2,,,,,, +STATS_SUPPL2,,,ACC,,,,,,,,,,,,,suppl2,,,,,, +STATS_SUPPL2,,,TCD,,,,,,,,,,,,,suppl2,,,,,, +STATS_SUPPL2,,,TCC,,,,,,,,,,,,,suppl2,,,,,, diff --git a/data/tariffplans/tutorial/LcrRules.csv b/data/tariffplans/tutorial/LcrRules.csv index 2d71fcd1f..c893832a6 100644 --- a/data/tariffplans/tutorial/LcrRules.csv +++ b/data/tariffplans/tutorial/LcrRules.csv @@ -4,6 +4,6 @@ *out,cgrates.org,call,1002,*any,DST_1002,lcr_profile1,*highest_cost,,2014-01-14T00:00:00Z,10 *out,cgrates.org,call,1002,*any,*any,lcr_profile1,*qos,,2014-01-14T00:00:00Z,10 *out,cgrates.org,call,1003,*any,DST_1002,lcr_profile1,*qos_threshold,20;;2m;;;;;;;,2014-01-14T00:00:00Z,10 -*out,cgrates.org,call,1003,*any,*any,lcr_profile1,*qos_threshold,40;;4m;;;;;;;,2014-01-14T00:00:00Z,10 +*out,cgrates.org,call,1003,*any,*any,lcr_profile1,*qos_threshold,40;;90s;;;;;;;,2014-01-14T00:00:00Z,10 *out,cgrates.org,call,*any,*any,DST_1002,lcr_profile2,*lowest_cost,,2014-01-14T00:00:00Z,10 *out,cgrates.org,call,*any,*any,*any,lcr_profile1,*lowest_cost,,2014-01-14T00:00:00Z,10 \ No newline at end of file diff --git a/general_tests/tutorial_local_test.go b/general_tests/tutorial_local_test.go index fb19adddc..ac4618da3 100644 --- a/general_tests/tutorial_local_test.go +++ b/general_tests/tutorial_local_test.go @@ -339,10 +339,6 @@ func TestTutFsCallsCdrStats(t *testing.T) { } // Check LCR -//FixMe: -/*{"id":16,"result":{"Entry":{"DestinationId":"*any","RPCategory":"lcr_profile1","Strategy":"*static","StrategyParams":"suppl1;suppl2","Weight":10},"SupplierCosts":[{"Supplier":"*out:cgrates.org:lcr_profile1:suppl1","Cost":0,"Duration":0,"Error":{},"QOS":null},{"Supplier":"*out:cgrates.org:lcr_profile1:suppl2","Cost":0,"Duration":0,"Error":{},"QOS":null}]},"error":null} - */ - func TestTutLocalLcrStatic(t *testing.T) { if !*testLocal { return @@ -400,6 +396,326 @@ func TestTutLocalLcrStatic(t *testing.T) { } } +func TestTutLocalLcrHighestCost(t *testing.T) { + if !*testLocal { + return + } + tStart, _ := utils.ParseDate("2014-08-04T13:00:00Z") + tEnd, _ := utils.ParseDate("2014-08-04T13:01:00Z") + cd := engine.CallDescriptor{ + Direction: "*out", + Category: "call", + Tenant: "cgrates.org", + Subject: "1002", + Account: "1002", + Destination: "1002", + TimeStart: tStart, + TimeEnd: tEnd, + } + eStLcr := &engine.LCRCost{ + Entry: &engine.LCREntry{DestinationId: "DST_1002", RPCategory: "lcr_profile1", Strategy: engine.LCR_STRATEGY_HIGHEST, StrategyParams: "", Weight: 10.0}, + SupplierCosts: []*engine.LCRSupplierCost{ + &engine.LCRSupplierCost{Supplier: "*out:cgrates.org:lcr_profile1:suppl1", Cost: 1.2, Duration: 60 * time.Second}, + &engine.LCRSupplierCost{Supplier: "*out:cgrates.org:lcr_profile1:suppl2", Cost: 0.6, Duration: 60 * time.Second}, + }, + } + var lcr engine.LCRCost + if err := tutLocalRpc.Call("Responder.GetLCR", cd, &lcr); err != nil { + t.Error(err) + } else if !reflect.DeepEqual(eStLcr.Entry, lcr.Entry) { + t.Errorf("Expecting: %+v, received: %+v", eStLcr.Entry, lcr.Entry) + } else if !reflect.DeepEqual(eStLcr.SupplierCosts, lcr.SupplierCosts) { + t.Errorf("Expecting: %+v, received: %+v", eStLcr.SupplierCosts[0], lcr.SupplierCosts[0]) + } +} + +func TestTutLocalLcrQos(t *testing.T) { + if !*testLocal { + return + } + tStart, _ := utils.ParseDate("2014-08-04T13:00:00Z") + tEnd, _ := utils.ParseDate("2014-08-04T13:01:00Z") + cd := engine.CallDescriptor{ + Direction: "*out", + Category: "call", + Tenant: "cgrates.org", + Subject: "1002", + Account: "1002", + Destination: "1003", + TimeStart: tStart, + TimeEnd: tEnd, + } + eStLcr := &engine.LCRCost{ + Entry: &engine.LCREntry{DestinationId: utils.ANY, RPCategory: "lcr_profile1", Strategy: engine.LCR_STRATEGY_QOS, StrategyParams: "", Weight: 10.0}, + SupplierCosts: []*engine.LCRSupplierCost{ + &engine.LCRSupplierCost{Supplier: "*out:cgrates.org:lcr_profile1:suppl1", Cost: 1.2, Duration: 60 * time.Second, + QOS: map[string]float64{engine.TCD: -1, engine.ACC: -1, engine.TCC: -1, engine.ASR: -1, engine.ACD: -1}}, + &engine.LCRSupplierCost{Supplier: "*out:cgrates.org:lcr_profile1:suppl2", Cost: 1.2, Duration: 60 * time.Second, + QOS: map[string]float64{engine.TCD: -1, engine.ACC: -1, engine.TCC: -1, engine.ASR: -1, engine.ACD: -1}}, + }, + } + eStLcr2 := &engine.LCRCost{ + Entry: &engine.LCREntry{DestinationId: utils.ANY, RPCategory: "lcr_profile1", Strategy: engine.LCR_STRATEGY_QOS, StrategyParams: "", Weight: 10.0}, + SupplierCosts: []*engine.LCRSupplierCost{ + &engine.LCRSupplierCost{Supplier: "*out:cgrates.org:lcr_profile1:suppl2", Cost: 1.2, Duration: 60 * time.Second, + QOS: map[string]float64{engine.TCD: -1, engine.ACC: -1, engine.TCC: -1, engine.ASR: -1, engine.ACD: -1}}, + &engine.LCRSupplierCost{Supplier: "*out:cgrates.org:lcr_profile1:suppl1", Cost: 1.2, Duration: 60 * time.Second, + QOS: map[string]float64{engine.TCD: -1, engine.ACC: -1, engine.TCC: -1, engine.ASR: -1, engine.ACD: -1}}, + }, + } + var lcr engine.LCRCost + // Since there is no real quality difference, the suppliers will come in random order here + if err := tutLocalRpc.Call("Responder.GetLCR", cd, &lcr); err != nil { + t.Error(err) + } else if !reflect.DeepEqual(eStLcr.Entry, lcr.Entry) { + t.Errorf("Expecting: %+v, received: %+v", eStLcr.Entry, lcr.Entry) + } else if !reflect.DeepEqual(eStLcr.SupplierCosts, lcr.SupplierCosts) && !reflect.DeepEqual(eStLcr2.SupplierCosts, lcr.SupplierCosts) { + t.Errorf("Expecting: %+v, received: %+v", eStLcr.SupplierCosts[0], lcr.SupplierCosts[0]) + } + // Post some CDRs to influence stats + testCdr1 := &engine.StoredCdr{CgrId: utils.Sha1("testcdr1", time.Date(2013, 12, 7, 8, 42, 24, 0, time.UTC).String()), + TOR: utils.VOICE, AccId: "testcdr1", CdrHost: "192.168.1.1", CdrSource: "TEST_QOS_LCR", ReqType: utils.META_RATED, + Direction: "*out", Tenant: "cgrates.org", Category: "call", Account: "1001", Subject: "1001", Destination: "1002", + SetupTime: time.Date(2014, 12, 7, 8, 42, 24, 0, time.UTC), AnswerTime: time.Date(2014, 12, 7, 8, 42, 26, 0, time.UTC), + Usage: time.Duration(2) * time.Minute, Supplier: "suppl1", + ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"}} + testCdr2 := &engine.StoredCdr{CgrId: utils.Sha1("testcdr2", time.Date(2013, 12, 7, 8, 42, 24, 0, time.UTC).String()), + TOR: utils.VOICE, AccId: "testcdr2", CdrHost: "192.168.1.1", CdrSource: "TEST_QOS_LCR", ReqType: utils.META_RATED, + Direction: "*out", Tenant: "cgrates.org", Category: "call", Account: "1002", Subject: "1002", Destination: "1003", + SetupTime: time.Date(2014, 12, 7, 8, 42, 24, 0, time.UTC), AnswerTime: time.Date(2014, 12, 7, 8, 42, 26, 0, time.UTC), + Usage: time.Duration(90) * time.Second, Supplier: "suppl2", + ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"}} + var reply string + for _, cdr := range []*engine.StoredCdr{testCdr1, testCdr2} { + if err := tutLocalRpc.Call("CdrsV2.ProcessCdr", cdr, &reply); err != nil { + t.Error("Unexpected error: ", err.Error()) + } else if reply != utils.OK { + t.Error("Unexpected reply received: ", reply) + } + } + // Based on stats, supplier1 should always be better since he has a higer ACD + eStLcr = &engine.LCRCost{ + Entry: &engine.LCREntry{DestinationId: utils.ANY, RPCategory: "lcr_profile1", Strategy: engine.LCR_STRATEGY_QOS, StrategyParams: "", Weight: 10.0}, + SupplierCosts: []*engine.LCRSupplierCost{ + &engine.LCRSupplierCost{Supplier: "*out:cgrates.org:lcr_profile1:suppl1", Cost: 1.2, Duration: 60 * time.Second, + QOS: map[string]float64{engine.TCD: 240, engine.ACC: 0.35, engine.TCC: 0.7, engine.ASR: 100, engine.ACD: 120}}, + &engine.LCRSupplierCost{Supplier: "*out:cgrates.org:lcr_profile1:suppl2", Cost: 1.2, Duration: 60 * time.Second, + QOS: map[string]float64{engine.TCD: 90, engine.ACC: 0.325, engine.TCC: 0.325, engine.ASR: 100, engine.ACD: 90}}, + }, + } + if err := tutLocalRpc.Call("Responder.GetLCR", cd, &lcr); err != nil { + t.Error(err) + } else if !reflect.DeepEqual(eStLcr.Entry, lcr.Entry) { + t.Errorf("Expecting: %+v, received: %+v", eStLcr.Entry, lcr.Entry) + } else if !reflect.DeepEqual(eStLcr.SupplierCosts, lcr.SupplierCosts) && !reflect.DeepEqual(eStLcr2.SupplierCosts, lcr.SupplierCosts) { + t.Errorf("Expecting: %+v, received: %+v", eStLcr.SupplierCosts[1], lcr.SupplierCosts[1]) + } + testCdr3 := &engine.StoredCdr{CgrId: utils.Sha1("testcdr3", time.Date(2013, 12, 7, 8, 42, 24, 0, time.UTC).String()), + TOR: utils.VOICE, AccId: "testcdr3", CdrHost: "192.168.1.1", CdrSource: "TEST_QOS_LCR", ReqType: utils.META_RATED, + Direction: "*out", Tenant: "cgrates.org", Category: "call", Account: "1003", Subject: "1003", Destination: "1004", + SetupTime: time.Date(2014, 12, 7, 8, 42, 24, 0, time.UTC), AnswerTime: time.Date(2014, 12, 7, 8, 42, 26, 0, time.UTC), + Usage: time.Duration(180) * time.Second, Supplier: "suppl2"} + if err := tutLocalRpc.Call("CdrsV2.ProcessCdr", testCdr3, &reply); err != nil { + t.Error("Unexpected error: ", err.Error()) + } else if reply != utils.OK { + t.Error("Unexpected reply received: ", reply) + } + // Since ACD has considerably increased for supplier2, we should have it as first prio now + eStLcr = &engine.LCRCost{ + Entry: &engine.LCREntry{DestinationId: utils.ANY, RPCategory: "lcr_profile1", Strategy: engine.LCR_STRATEGY_QOS, StrategyParams: "", Weight: 10.0}, + SupplierCosts: []*engine.LCRSupplierCost{ + &engine.LCRSupplierCost{Supplier: "*out:cgrates.org:lcr_profile1:suppl2", Cost: 1.2, Duration: 60 * time.Second, + QOS: map[string]float64{engine.TCD: 270, engine.ACC: 0.3625, engine.TCC: 0.725, engine.ASR: 100, engine.ACD: 135}}, + &engine.LCRSupplierCost{Supplier: "*out:cgrates.org:lcr_profile1:suppl1", Cost: 1.2, Duration: 60 * time.Second, + QOS: map[string]float64{engine.TCD: 240, engine.ACC: 0.35, engine.TCC: 0.7, engine.ASR: 100, engine.ACD: 120}}, + }, + } + if err := tutLocalRpc.Call("Responder.GetLCR", cd, &lcr); err != nil { + t.Error(err) + } else if !reflect.DeepEqual(eStLcr.Entry, lcr.Entry) { + t.Errorf("Expecting: %+v, received: %+v", eStLcr.Entry, lcr.Entry) + } else if !reflect.DeepEqual(eStLcr.SupplierCosts, lcr.SupplierCosts) && !reflect.DeepEqual(eStLcr2.SupplierCosts, lcr.SupplierCosts) { + t.Errorf("Expecting: %+v, received: %+v", eStLcr.SupplierCosts[0], lcr.SupplierCosts[0]) + } +} + +func TestTutLocalLcrQosThreshold(t *testing.T) { + if !*testLocal { + return + } + tStart, _ := utils.ParseDate("2014-08-04T13:00:00Z") + tEnd, _ := utils.ParseDate("2014-08-04T13:01:00Z") + cd := engine.CallDescriptor{ + Direction: "*out", + Category: "call", + Tenant: "cgrates.org", + Subject: "1003", + Account: "1003", + Destination: "1002", + TimeStart: tStart, + TimeEnd: tEnd, + } + eLcr := &engine.LCRCost{ + Entry: &engine.LCREntry{DestinationId: "DST_1002", RPCategory: "lcr_profile1", Strategy: engine.LCR_STRATEGY_QOS_THRESHOLD, StrategyParams: "20;;2m;;;;;;;", Weight: 10.0}, + SupplierCosts: []*engine.LCRSupplierCost{ + &engine.LCRSupplierCost{Supplier: "*out:cgrates.org:lcr_profile1:suppl2", Cost: 0.6, Duration: 60 * time.Second, + QOS: map[string]float64{engine.TCD: 270, engine.ACC: 0.3625, engine.TCC: 0.725, engine.ASR: 100, engine.ACD: 135}}, + &engine.LCRSupplierCost{Supplier: "*out:cgrates.org:lcr_profile1:suppl1", Cost: 1.2, Duration: 60 * time.Second, + QOS: map[string]float64{engine.TCD: 240, engine.ACC: 0.35, engine.TCC: 0.7, engine.ASR: 100, engine.ACD: 120}}, + }, + } + var lcr engine.LCRCost + if err := tutLocalRpc.Call("Responder.GetLCR", cd, &lcr); err != nil { + t.Error(err) + } else if !reflect.DeepEqual(eLcr.Entry, lcr.Entry) { + t.Errorf("Expecting: %+v, received: %+v", eLcr.Entry, lcr.Entry) + } else if !reflect.DeepEqual(eLcr.SupplierCosts, lcr.SupplierCosts) { + t.Errorf("Expecting: %+v, received: %+v", eLcr.SupplierCosts[0], lcr.SupplierCosts[0]) + } + testCdr4 := &engine.StoredCdr{CgrId: utils.Sha1("testcdr4", time.Date(2013, 12, 7, 8, 42, 24, 0, time.UTC).String()), + TOR: utils.VOICE, AccId: "testcdr4", CdrHost: "192.168.1.1", CdrSource: "TEST_QOS_LCR", ReqType: utils.META_RATED, + Direction: "*out", Tenant: "cgrates.org", Category: "call", Account: "1003", Subject: "1003", Destination: "1004", + SetupTime: time.Date(2014, 12, 7, 8, 42, 24, 0, time.UTC), AnswerTime: time.Date(2014, 12, 7, 8, 42, 26, 0, time.UTC), + Usage: time.Duration(60) * time.Second, Supplier: "suppl2"} + var reply string + if err := tutLocalRpc.Call("CdrsV2.ProcessCdr", testCdr4, &reply); err != nil { // Should drop ACD under the 2m required by threshold, removing suppl2 from lcr + t.Error("Unexpected error: ", err.Error()) + } else if reply != utils.OK { + t.Error("Unexpected reply received: ", reply) + } + eLcr = &engine.LCRCost{ + Entry: &engine.LCREntry{DestinationId: "DST_1002", RPCategory: "lcr_profile1", Strategy: engine.LCR_STRATEGY_QOS_THRESHOLD, StrategyParams: "20;;2m;;;;;;;", Weight: 10.0}, + SupplierCosts: []*engine.LCRSupplierCost{ + &engine.LCRSupplierCost{Supplier: "*out:cgrates.org:lcr_profile1:suppl1", Cost: 1.2, Duration: 60 * time.Second, + QOS: map[string]float64{engine.TCD: 240, engine.ACC: 0.35, engine.TCC: 0.7, engine.ASR: 100, engine.ACD: 120}}, + }, + } + if err := tutLocalRpc.Call("Responder.GetLCR", cd, &lcr); err != nil { + t.Error(err) + } else if !reflect.DeepEqual(eLcr.Entry, lcr.Entry) { + t.Errorf("Expecting: %+v, received: %+v", eLcr.Entry, lcr.Entry) + } else if !reflect.DeepEqual(eLcr.SupplierCosts, lcr.SupplierCosts) { + t.Errorf("Expecting: %+v, received: %+v", eLcr.SupplierCosts[0], lcr.SupplierCosts[0]) + } + cd = engine.CallDescriptor{ + Direction: "*out", + Category: "call", + Tenant: "cgrates.org", + Subject: "1003", + Account: "1003", + Destination: "1004", + TimeStart: tStart, + TimeEnd: tEnd, + } + eLcr = &engine.LCRCost{ + Entry: &engine.LCREntry{DestinationId: utils.ANY, RPCategory: "lcr_profile1", Strategy: engine.LCR_STRATEGY_QOS_THRESHOLD, StrategyParams: "40;;90s;;;;;;;", Weight: 10.0}, + SupplierCosts: []*engine.LCRSupplierCost{ + &engine.LCRSupplierCost{Supplier: "*out:cgrates.org:lcr_profile1:suppl1", Cost: 1.2, Duration: 60 * time.Second, + QOS: map[string]float64{engine.TCD: 240, engine.ACC: 0.35, engine.TCC: 0.7, engine.ASR: 100, engine.ACD: 120}}, + &engine.LCRSupplierCost{Supplier: "*out:cgrates.org:lcr_profile1:suppl2", Cost: 1.2, Duration: 60 * time.Second, + QOS: map[string]float64{engine.TCD: 330, engine.ACC: 0.3416666667, engine.TCC: 1.025, engine.ASR: 100, engine.ACD: 110}}, + }, + } + eLcr2 := &engine.LCRCost{ + Entry: &engine.LCREntry{DestinationId: utils.ANY, RPCategory: "lcr_profile1", Strategy: engine.LCR_STRATEGY_QOS_THRESHOLD, StrategyParams: "40;;90s;;;;;;;", Weight: 10.0}, + SupplierCosts: []*engine.LCRSupplierCost{ + &engine.LCRSupplierCost{Supplier: "*out:cgrates.org:lcr_profile1:suppl2", Cost: 1.2, Duration: 60 * time.Second, + QOS: map[string]float64{engine.TCD: 330, engine.ACC: 0.3416666667, engine.TCC: 1.025, engine.ASR: 100, engine.ACD: 110}}, + &engine.LCRSupplierCost{Supplier: "*out:cgrates.org:lcr_profile1:suppl1", Cost: 1.2, Duration: 60 * time.Second, + QOS: map[string]float64{engine.TCD: 240, engine.ACC: 0.35, engine.TCC: 0.7, engine.ASR: 100, engine.ACD: 120}}, + }, + } + if err := tutLocalRpc.Call("Responder.GetLCR", cd, &lcr); err != nil { + t.Error(err) + } else if !reflect.DeepEqual(eLcr.Entry, lcr.Entry) { + t.Errorf("Expecting: %+v, received: %+v", eLcr.Entry, lcr.Entry) + } else if !reflect.DeepEqual(eLcr.SupplierCosts, lcr.SupplierCosts) && !reflect.DeepEqual(eLcr2.SupplierCosts, lcr.SupplierCosts) { + t.Errorf("Expecting: %+v, received: %+v", eLcr.SupplierCosts[1], lcr.SupplierCosts[1]) + } + testCdr5 := &engine.StoredCdr{CgrId: utils.Sha1("testcdr5", time.Date(2013, 12, 7, 8, 42, 24, 0, time.UTC).String()), + TOR: utils.VOICE, AccId: "testcdr5", CdrHost: "192.168.1.1", CdrSource: "TEST_QOS_LCR", ReqType: utils.META_RATED, + Direction: "*out", Tenant: "cgrates.org", Category: "call", Account: "1003", Subject: "1003", Destination: "1004", + SetupTime: time.Date(2014, 12, 7, 8, 42, 24, 0, time.UTC), AnswerTime: time.Date(2014, 12, 7, 8, 42, 26, 0, time.UTC), + Usage: time.Duration(1) * time.Second, Supplier: "suppl2"} + if err := tutLocalRpc.Call("CdrsV2.ProcessCdr", testCdr5, &reply); err != nil { // Should drop ACD under the 1m required by threshold, removing suppl2 from lcr + t.Error("Unexpected error: ", err.Error()) + } else if reply != utils.OK { + t.Error("Unexpected reply received: ", reply) + } + eLcr = &engine.LCRCost{ + Entry: &engine.LCREntry{DestinationId: utils.ANY, RPCategory: "lcr_profile1", Strategy: engine.LCR_STRATEGY_QOS_THRESHOLD, StrategyParams: "40;;90s;;;;;;;", Weight: 10.0}, + SupplierCosts: []*engine.LCRSupplierCost{ + &engine.LCRSupplierCost{Supplier: "*out:cgrates.org:lcr_profile1:suppl1", Cost: 1.2, Duration: 60 * time.Second, + QOS: map[string]float64{engine.TCD: 240, engine.ACC: 0.35, engine.TCC: 0.7, engine.ASR: 100, engine.ACD: 120}}, + }, + } + if err := tutLocalRpc.Call("Responder.GetLCR", cd, &lcr); err != nil { + t.Error(err) + } else if !reflect.DeepEqual(eLcr.Entry, lcr.Entry) { + t.Errorf("Expecting: %+v, received: %+v", eLcr.Entry, lcr.Entry) + } else if !reflect.DeepEqual(eLcr.SupplierCosts, lcr.SupplierCosts) { + t.Errorf("Expecting: %+v, received: %+v", eLcr.SupplierCosts[0], lcr.SupplierCosts[0]) + } +} + +func TestTutLocalLeastCost(t *testing.T) { + if !*testLocal { + return + } + tStart, _ := utils.ParseDate("2014-08-04T13:00:00Z") + tEnd, _ := utils.ParseDate("2014-08-04T13:01:00Z") + cd := engine.CallDescriptor{ + Direction: "*out", + Category: "call", + Tenant: "cgrates.org", + Subject: "1004", + Account: "1004", + Destination: "1002", + TimeStart: tStart, + TimeEnd: tEnd, + } + eStLcr := &engine.LCRCost{ + Entry: &engine.LCREntry{DestinationId: "DST_1002", RPCategory: "lcr_profile2", Strategy: engine.LCR_STRATEGY_LOWEST, StrategyParams: "", Weight: 10.0}, + SupplierCosts: []*engine.LCRSupplierCost{ + &engine.LCRSupplierCost{Supplier: "*out:cgrates.org:lcr_profile2:suppl3", Cost: 0.01, Duration: 60 * time.Second}, + &engine.LCRSupplierCost{Supplier: "*out:cgrates.org:lcr_profile2:suppl1", Cost: 0.6, Duration: 60 * time.Second}, + &engine.LCRSupplierCost{Supplier: "*out:cgrates.org:lcr_profile2:suppl2", Cost: 1.2, Duration: 60 * time.Second}, + }, + } + var lcr engine.LCRCost + if err := tutLocalRpc.Call("Responder.GetLCR", cd, &lcr); err != nil { + t.Error(err) + } else if !reflect.DeepEqual(eStLcr.Entry, lcr.Entry) { + t.Errorf("Expecting: %+v, received: %+v", eStLcr.Entry, lcr.Entry) + } else if !reflect.DeepEqual(eStLcr.SupplierCosts, lcr.SupplierCosts) { + t.Errorf("Expecting: %+v, received: %+v", eStLcr.SupplierCosts[2], lcr.SupplierCosts[2]) + } + cd = engine.CallDescriptor{ + Direction: "*out", + Category: "call", + Tenant: "cgrates.org", + Subject: "1004", + Account: "1004", + Destination: "1003", + TimeStart: tStart, + TimeEnd: tEnd, + } + eStLcr = &engine.LCRCost{ + Entry: &engine.LCREntry{DestinationId: utils.ANY, RPCategory: "lcr_profile1", Strategy: engine.LCR_STRATEGY_LOWEST, StrategyParams: "", Weight: 10.0}, + SupplierCosts: []*engine.LCRSupplierCost{ + &engine.LCRSupplierCost{Supplier: "*out:cgrates.org:lcr_profile1:suppl1", Cost: 1.2, Duration: 60 * time.Second}, + &engine.LCRSupplierCost{Supplier: "*out:cgrates.org:lcr_profile1:suppl2", Cost: 1.2, Duration: 60 * time.Second}, + }, + } + if err := tutLocalRpc.Call("Responder.GetLCR", cd, &lcr); err != nil { + t.Error(err) + } else if !reflect.DeepEqual(eStLcr.Entry, lcr.Entry) { + t.Errorf("Expecting: %+v, received: %+v", eStLcr.Entry, lcr.Entry) + } else if !reflect.DeepEqual(eStLcr.SupplierCosts, lcr.SupplierCosts) { + t.Errorf("Expecting: %+v, received: %+v", eStLcr.SupplierCosts[0], lcr.SupplierCosts[0]) + } +} + func TestTutLocalStopCgrEngine(t *testing.T) { if !*testLocal { return From fdef1bfe6705d1183c15a70b1a01c85a314f4bf7 Mon Sep 17 00:00:00 2001 From: DanB Date: Mon, 25 May 2015 13:18:47 +0200 Subject: [PATCH 08/10] Adding QOS to LcrReply, Error as string in supplierCost --- apier/v1/lcr.go | 2 +- engine/calldesc.go | 14 +++++++------- engine/lcr.go | 7 ++++--- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/apier/v1/lcr.go b/apier/v1/lcr.go index 1a23804b1..98b979809 100644 --- a/apier/v1/lcr.go +++ b/apier/v1/lcr.go @@ -50,7 +50,7 @@ func (self *ApierV1) GetLcr(lcrReq engine.LcrRequest, lcrReply *engine.LcrReply) if dtcs, err := utils.NewDTCSFromRPKey(qriedSuppl.Supplier); err != nil { return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error()) } else { - lcrReply.Suppliers = append(lcrReply.Suppliers, &engine.LcrSupplier{Supplier: dtcs.Subject, Cost: qriedSuppl.Cost}) + lcrReply.Suppliers = append(lcrReply.Suppliers, &engine.LcrSupplier{Supplier: dtcs.Subject, Cost: qriedSuppl.Cost, QOS: qriedSuppl.QOS}) } } return nil diff --git a/engine/calldesc.go b/engine/calldesc.go index 3c394443a..3e1b26fff 100644 --- a/engine/calldesc.go +++ b/engine/calldesc.go @@ -730,7 +730,7 @@ func (cd *CallDescriptor) GetLCR(stats StatsInterface) (*LCRCost, error) { if cd.account.Disabled { lcrCost.SupplierCosts = append(lcrCost.SupplierCosts, &LCRSupplierCost{ Supplier: supplier, - Error: fmt.Errorf("supplier %s is disabled", supplier), + Error: fmt.Sprintf("supplier %s is disabled", supplier), }) continue } @@ -744,7 +744,7 @@ func (cd *CallDescriptor) GetLCR(stats StatsInterface) (*LCRCost, error) { if err != nil || cc == nil { lcrCost.SupplierCosts = append(lcrCost.SupplierCosts, &LCRSupplierCost{ Supplier: supplier, - Error: err, + Error: err.Error(), }) } else { lcrCost.SupplierCosts = append(lcrCost.SupplierCosts, &LCRSupplierCost{ @@ -787,7 +787,7 @@ func (cd *CallDescriptor) GetLCR(stats StatsInterface) (*LCRCost, error) { if stats == nil { lcrCost.SupplierCosts = append(lcrCost.SupplierCosts, &LCRSupplierCost{ Supplier: supplier, - Error: errors.New("Cdr stats service not configured"), + Error: fmt.Sprintf("Cdr stats service not configured"), }) continue } @@ -795,7 +795,7 @@ func (cd *CallDescriptor) GetLCR(stats StatsInterface) (*LCRCost, error) { if rpf, err := dataStorage.GetRatingProfile(rpfKey, false); err != nil { lcrCost.SupplierCosts = append(lcrCost.SupplierCosts, &LCRSupplierCost{ Supplier: supplier, - Error: fmt.Errorf("Rating plan error: %s", err.Error()), + Error: fmt.Sprintf("Rating plan error: %s", err.Error()), }) continue } else if rpf != nil { @@ -815,7 +815,7 @@ func (cd *CallDescriptor) GetLCR(stats StatsInterface) (*LCRCost, error) { if err := stats.GetValues(qId, &statValues); err != nil { lcrCost.SupplierCosts = append(lcrCost.SupplierCosts, &LCRSupplierCost{ Supplier: supplier, - Error: fmt.Errorf("Get stats values for queue id %s, error %s", qId, err.Error()), + Error: fmt.Sprintf("Get stats values for queue id %s, error %s", qId, err.Error()), }) statsErr = true break @@ -911,7 +911,7 @@ func (cd *CallDescriptor) GetLCR(stats StatsInterface) (*LCRCost, error) { if cd.account.Disabled { lcrCost.SupplierCosts = append(lcrCost.SupplierCosts, &LCRSupplierCost{ Supplier: supplier, - Error: fmt.Errorf("supplier %s is disabled", supplier), + Error: fmt.Sprintf("supplier %s is disabled", supplier), }) continue } @@ -925,7 +925,7 @@ func (cd *CallDescriptor) GetLCR(stats StatsInterface) (*LCRCost, error) { if err != nil || cc == nil { lcrCost.SupplierCosts = append(lcrCost.SupplierCosts, &LCRSupplierCost{ Supplier: supplier, - Error: err, + Error: err.Error(), }) continue } else { diff --git a/engine/lcr.go b/engine/lcr.go index d6b559472..6d5a68428 100644 --- a/engine/lcr.go +++ b/engine/lcr.go @@ -105,6 +105,7 @@ type LcrReply struct { type LcrSupplier struct { Supplier string Cost float64 + QOS map[string]float64 } type LCR struct { @@ -137,7 +138,7 @@ type LCRSupplierCost struct { Supplier string Cost float64 Duration time.Duration - Error error + Error string // Not error due to JSON automatic serialization into struct QOS map[string]float64 qosSortParams []string } @@ -284,7 +285,7 @@ func (lc *LCRCost) Sort() { func (lc *LCRCost) HasErrors() bool { for _, supplCost := range lc.SupplierCosts { - if supplCost.Error != nil { + if len(supplCost.Error) != 0 { return true } } @@ -293,7 +294,7 @@ func (lc *LCRCost) HasErrors() bool { func (lc *LCRCost) LogErrors() { for _, supplCost := range lc.SupplierCosts { - if supplCost.Error != nil { + if len(supplCost.Error) != 0 { Logger.Err(fmt.Sprintf("LCR_ERROR: supplier <%s>, error <%s>", supplCost.Supplier, supplCost.Error)) } } From bb97e626ab61875936c7c85d71ef6d56305ef6fe Mon Sep 17 00:00:00 2001 From: DanB Date: Mon, 25 May 2015 14:55:24 +0200 Subject: [PATCH 09/10] Check CDRStats in kamailio tutorial --- data/tariffplans/tutorial/CdrStats.csv | 8 +- .../kamevapi/cgrates/etc/cgrates/cgrates.json | 1 + general_tests/tutorial_kam_calls_test.go | 125 +++++++++++++++++- 3 files changed, 129 insertions(+), 5 deletions(-) diff --git a/data/tariffplans/tutorial/CdrStats.csv b/data/tariffplans/tutorial/CdrStats.csv index cadb59d11..1d68f6fb1 100644 --- a/data/tariffplans/tutorial/CdrStats.csv +++ b/data/tariffplans/tutorial/CdrStats.csv @@ -1,16 +1,16 @@ #Id[0],QueueLength[1],TimeWindow[2],Metric[3],SetupInterval[4],TOR[5],CdrHost[6],CdrSource[7],ReqType[8],Direction[9],Tenant[10],Category[11],Account[12],Subject[13],DestinationPrefix[14],UsageInterval[15],Supplier[16],DisconnectCause[17],MediationRunIds[18],RatedAccount[19],RatedSubject[20],CostInterval[21],Triggers[22] -CDRST1,10,0,ASR,,,,,,,cgrates.org,,,,,,,,default,,,,CDRST1_WARN +CDRST1,10,0,ASR,,,,,,,cgrates.org,,,,,,,,*default,,,,CDRST1_WARN CDRST1,,,ACD,,,,,,,,,,,,,,,,,,, CDRST1,,,ACC,,,,,,,,,,,,,,,,,,, CDRST1,,,TCD,,,,,,,,,,,,,,,,,,, CDRST1,,,TCC,,,,,,,,,,,,,,,,,,, -CDRST_1001,10,10m,ASR,,,,,,,cgrates.org,,,1001,,,,,default,,,,CDRST1001_WARN +CDRST_1001,10,10m,ASR,,,,,,,cgrates.org,,,1001,,,,,*default,,,,CDRST1001_WARN CDRST_1001,,,ACD,,,,,,,,,,,,,,,,,,, CDRST_1001,,,ACC,,,,,,,,,,,,,,,,,,, -CDRST_1002,10,10m,ASR,,,,,,,cgrates.org,,,1002,,,,,default,,,,CDRST1001_WARN +CDRST_1002,10,10m,ASR,,,,,,,cgrates.org,,,1002,,,,,*default,,,,CDRST1001_WARN CDRST_1002,,,ACD,,,,,,,,,,,,,,,,,,, CDRST_1002,,,ACC,,,,,,,,,,,,,,,,,,, -CDRST_1003,,,ASR,,,,,,,cgrates.org,,,,1003,,,,default,,,,CDRST3_WARN +CDRST_1003,,,ASR,,,,,,,cgrates.org,,,,1003,,,,*default,,,,CDRST3_WARN CDRST_1003,,,ACD,,,,,,,,,,,,,,,,,,, STATS_SUPPL1,,,ACD,,,,,,,,,,,,,suppl1,,,,,, STATS_SUPPL1,,,ASR,,,,,,,,,,,,,suppl1,,,,,, diff --git a/data/tutorials/kamevapi/cgrates/etc/cgrates/cgrates.json b/data/tutorials/kamevapi/cgrates/etc/cgrates/cgrates.json index 8ed9656ed..5bb64b85c 100644 --- a/data/tutorials/kamevapi/cgrates/etc/cgrates/cgrates.json +++ b/data/tutorials/kamevapi/cgrates/etc/cgrates/cgrates.json @@ -66,6 +66,7 @@ "rater": { "enabled": true, // enable Rater service: // "balancer": "", // register to Balancer as worker: <""|internal|x.y.z.y:1234> + "cdrstats": "internal", // address where to reach the cdrstats service, empty to disable stats functionality<""|internal|x.y.z.y:1234> }, diff --git a/general_tests/tutorial_kam_calls_test.go b/general_tests/tutorial_kam_calls_test.go index eacaf9eae..0e544f230 100644 --- a/general_tests/tutorial_kam_calls_test.go +++ b/general_tests/tutorial_kam_calls_test.go @@ -23,6 +23,7 @@ import ( "net/rpc/jsonrpc" "os" "path" + "reflect" "strings" "testing" "time" @@ -130,7 +131,7 @@ func TestTutKamCallsLoadTariffPlanFromFolder(t *testing.T) { time.Sleep(time.Duration(*waitRater) * time.Millisecond) // Give time for scheduler to execute topups } -// Make sure account was debited properly +// Make sure account was topped-up properly func TestTutKamCallsAccountsBefore(t *testing.T) { if !*testCalls { return @@ -172,6 +173,57 @@ func TestTutKamCallsAccountsBefore(t *testing.T) { } } +// Make sure all stats queues are in place +func TestTutKamCallsCdrStatsBefore(t *testing.T) { + if !*testCalls { + return + } + //eQueueIds := []string{"*default", "CDRST1", "CDRST_1001", "CDRST_1002", "CDRST_1003", "STATS_SUPPL1", "STATS_SUPPL2"} + var statMetrics map[string]float64 + eMetrics := map[string]float64{engine.ACC: -1, engine.ACD: -1, engine.ASR: -1} + if err := tutKamCallsRpc.Call("CDRStatsV1.GetMetrics", v1.AttrGetMetrics{StatsQueueId: utils.META_DEFAULT}, &statMetrics); err != nil { + t.Error("Calling CDRStatsV1.GetMetrics, got error: ", err.Error()) + } else if !reflect.DeepEqual(eMetrics, statMetrics) { + t.Errorf("Expecting: %v, received: %v", eMetrics, statMetrics) + } + eMetrics = map[string]float64{engine.ACD: -1, engine.ASR: -1, engine.TCC: -1, engine.TCD: -1, engine.ACC: -1} + if err := tutKamCallsRpc.Call("CDRStatsV1.GetMetrics", v1.AttrGetMetrics{StatsQueueId: "CDRST1"}, &statMetrics); err != nil { + t.Error("Calling CDRStatsV1.GetMetrics, got error: ", err.Error()) + } else if !reflect.DeepEqual(eMetrics, statMetrics) { + t.Errorf("Expecting: %v, received: %v", eMetrics, statMetrics) + } + eMetrics = map[string]float64{engine.ACC: -1, engine.ACD: -1, engine.ASR: -1, engine.TCC: -1, engine.TCD: -1} + if err := tutKamCallsRpc.Call("CDRStatsV1.GetMetrics", v1.AttrGetMetrics{StatsQueueId: "CDRST_1001"}, &statMetrics); err != nil { + t.Error("Calling CDRStatsV1.GetMetrics, got error: ", err.Error()) + } else if !reflect.DeepEqual(eMetrics, statMetrics) { + t.Errorf("Expecting: %v, received: %v", eMetrics, statMetrics) + } + eMetrics = map[string]float64{engine.ACD: -1, engine.ASR: -1, engine.TCC: -1, engine.TCD: -1, engine.ACC: -1} + if err := tutKamCallsRpc.Call("CDRStatsV1.GetMetrics", v1.AttrGetMetrics{StatsQueueId: "CDRST_1002"}, &statMetrics); err != nil { + t.Error("Calling CDRStatsV1.GetMetrics, got error: ", err.Error()) + } else if !reflect.DeepEqual(eMetrics, statMetrics) { + t.Errorf("Expecting: %v, received: %v", eMetrics, statMetrics) + } + eMetrics = map[string]float64{engine.ACD: -1, engine.ASR: -1, engine.TCC: -1, engine.TCD: -1, engine.ACC: -1} + if err := tutKamCallsRpc.Call("CDRStatsV1.GetMetrics", v1.AttrGetMetrics{StatsQueueId: "CDRST_1003"}, &statMetrics); err != nil { + t.Error("Calling CDRStatsV1.GetMetrics, got error: ", err.Error()) + } else if !reflect.DeepEqual(eMetrics, statMetrics) { + t.Errorf("Expecting: %v, received: %v", eMetrics, statMetrics) + } + eMetrics = map[string]float64{engine.ACD: -1, engine.ASR: -1, engine.TCC: -1, engine.TCD: -1, engine.ACC: -1} + if err := tutKamCallsRpc.Call("CDRStatsV1.GetMetrics", v1.AttrGetMetrics{StatsQueueId: "STATS_SUPPL1"}, &statMetrics); err != nil { + t.Error("Calling CDRStatsV1.GetMetrics, got error: ", err.Error()) + } else if !reflect.DeepEqual(eMetrics, statMetrics) { + t.Errorf("Expecting: %v, received: %v", eMetrics, statMetrics) + } + eMetrics = map[string]float64{engine.ACD: -1, engine.ASR: -1, engine.TCC: -1, engine.TCD: -1, engine.ACC: -1} + if err := tutKamCallsRpc.Call("CDRStatsV1.GetMetrics", v1.AttrGetMetrics{StatsQueueId: "STATS_SUPPL2"}, &statMetrics); err != nil { + t.Error("Calling CDRStatsV1.GetMetrics, got error: ", err.Error()) + } else if !reflect.DeepEqual(eMetrics, statMetrics) { + t.Errorf("Expecting: %v, received: %v", eMetrics, statMetrics) + } +} + // Start Pjsua as listener and register it to receive calls func TestTutKamCallsStartPjsuaListener(t *testing.T) { if !*testCalls { @@ -289,6 +341,9 @@ func TestTutKamCallsCdrs(t *testing.T) { if reply[0].Usage != "67" { // Usage as seconds t.Errorf("Unexpected Usage for CDR: %+v", reply[0]) } + if reply[0].Supplier != "suppl2" { // Usage as seconds + t.Errorf("Unexpected Usage for CDR: %+v", reply[0]) + } } req = utils.RpcCdrsFilter{Accounts: []string{"1001"}, RunIds: []string{"derived_run1"}, FilterOnDerived: true} if err := tutKamCallsRpc.Call("ApierV2.GetCdrs", req, &reply); err != nil { @@ -302,6 +357,9 @@ func TestTutKamCallsCdrs(t *testing.T) { if reply[0].Subject != "1002" { t.Errorf("Unexpected Subject for CDR: %+v", reply[0]) } + if reply[0].Supplier != "suppl2" { + t.Errorf("Unexpected Subject for CDR: %+v", reply[0]) + } } req = utils.RpcCdrsFilter{Accounts: []string{"1002"}, RunIds: []string{utils.META_DEFAULT}} if err := tutKamCallsRpc.Call("ApierV2.GetCdrs", req, &reply); err != nil { @@ -321,6 +379,9 @@ func TestTutKamCallsCdrs(t *testing.T) { if reply[0].Usage != "61" { // Usage as seconds t.Errorf("Unexpected Usage for CDR: %+v", reply[0]) } + if reply[0].Supplier != "suppl1" { + t.Errorf("Unexpected Subject for CDR: %+v", reply[0]) + } } req = utils.RpcCdrsFilter{Accounts: []string{"1003"}, RunIds: []string{utils.META_DEFAULT}} if err := tutKamCallsRpc.Call("ApierV2.GetCdrs", req, &reply); err != nil { @@ -340,6 +401,9 @@ func TestTutKamCallsCdrs(t *testing.T) { if reply[0].Usage != "63" { // Usage as seconds t.Errorf("Unexpected Usage for CDR: %+v", reply[0]) } + if reply[0].Supplier != "suppl1" { + t.Errorf("Unexpected Subject for CDR: %+v", reply[0]) + } } req = utils.RpcCdrsFilter{Accounts: []string{"1004"}, RunIds: []string{utils.META_DEFAULT}} if err := tutKamCallsRpc.Call("ApierV2.GetCdrs", req, &reply); err != nil { @@ -359,6 +423,9 @@ func TestTutKamCallsCdrs(t *testing.T) { if reply[0].Usage != "62" { // Usage as seconds t.Errorf("Unexpected Usage for CDR: %+v", reply[0]) } + if reply[0].Supplier != "suppl1" { + t.Errorf("Unexpected Subject for CDR: %+v", reply[0]) + } } req = utils.RpcCdrsFilter{Accounts: []string{"1006"}, RunIds: []string{utils.META_DEFAULT}} if err := tutKamCallsRpc.Call("ApierV2.GetCdrs", req, &reply); err != nil { @@ -378,6 +445,9 @@ func TestTutKamCallsCdrs(t *testing.T) { if reply[0].Usage != "64" { // Usage as seconds t.Errorf("Unexpected Usage for CDR: %+v", reply[0]) } + if reply[0].Supplier != "suppl3" { + t.Errorf("Unexpected Subject for CDR: %+v", reply[0]) + } } req = utils.RpcCdrsFilter{Accounts: []string{"1007"}, RunIds: []string{utils.META_DEFAULT}} if err := tutKamCallsRpc.Call("ApierV2.GetCdrs", req, &reply); err != nil { @@ -397,6 +467,59 @@ func TestTutKamCallsCdrs(t *testing.T) { if reply[0].Usage != "66" { // Usage as seconds t.Errorf("Unexpected Usage for CDR: %+v", reply[0]) } + if reply[0].Supplier != "suppl3" { + t.Errorf("Unexpected Subject for CDR: %+v", reply[0]) + } + } +} + +// Make sure all stats queues were updated +func TestTutKamCallsCdrStatsAfter(t *testing.T) { + if !*testCalls { + return + } + var statMetrics map[string]float64 + eMetrics := map[string]float64{engine.ACC: 0.9707714286, engine.ACD: 64.2857142857, engine.ASR: 100} + if err := tutKamCallsRpc.Call("CDRStatsV1.GetMetrics", v1.AttrGetMetrics{StatsQueueId: utils.META_DEFAULT}, &statMetrics); err != nil { + t.Error("Calling CDRStatsV1.GetMetrics, got error: ", err.Error()) + } else if !reflect.DeepEqual(eMetrics, statMetrics) { + t.Errorf("Expecting: %v, received: %v", eMetrics, statMetrics) + } + eMetrics = map[string]float64{engine.ACC: 0.927, engine.ACD: 63.8333333333, engine.ASR: 100, engine.TCC: 5.562, engine.TCD: 383} + if err := tutKamCallsRpc.Call("CDRStatsV1.GetMetrics", v1.AttrGetMetrics{StatsQueueId: "CDRST1"}, &statMetrics); err != nil { + t.Error("Calling CDRStatsV1.GetMetrics, got error: ", err.Error()) + } else if !reflect.DeepEqual(eMetrics, statMetrics) { + t.Errorf("Expecting: %v, received: %v", eMetrics, statMetrics) + } + eMetrics = map[string]float64{engine.TCC: 5.562, engine.TCD: 383, engine.ACC: 0.0217, engine.ACD: 67, engine.ASR: 100} + if err := tutKamCallsRpc.Call("CDRStatsV1.GetMetrics", v1.AttrGetMetrics{StatsQueueId: "CDRST_1001"}, &statMetrics); err != nil { + t.Error("Calling CDRStatsV1.GetMetrics, got error: ", err.Error()) + } else if !reflect.DeepEqual(eMetrics, statMetrics) { + t.Errorf("Expecting: %v, received: %v", eMetrics, statMetrics) + } + eMetrics = map[string]float64{engine.ACD: 61, engine.ASR: 100, engine.TCC: 5.562, engine.TCD: 383, engine.ACC: 1.2334} + if err := tutKamCallsRpc.Call("CDRStatsV1.GetMetrics", v1.AttrGetMetrics{StatsQueueId: "CDRST_1002"}, &statMetrics); err != nil { + t.Error("Calling CDRStatsV1.GetMetrics, got error: ", err.Error()) + } else if !reflect.DeepEqual(eMetrics, statMetrics) { + t.Errorf("Expecting: %v, received: %v", eMetrics, statMetrics) + } + eMetrics = map[string]float64{engine.TCC: 5.562, engine.TCD: 383, engine.ACC: 1.2334, engine.ACD: -1, engine.ASR: -1} + if err := tutKamCallsRpc.Call("CDRStatsV1.GetMetrics", v1.AttrGetMetrics{StatsQueueId: "CDRST_1003"}, &statMetrics); err != nil { + t.Error("Calling CDRStatsV1.GetMetrics, got error: ", err.Error()) + } else if !reflect.DeepEqual(eMetrics, statMetrics) { + t.Errorf("Expecting: %v, received: %v", eMetrics, statMetrics) + } + eMetrics = map[string]float64{engine.ACC: 1.2334, engine.ACD: 62, engine.ASR: 100, engine.TCC: 3.7002, engine.TCD: 186} + if err := tutKamCallsRpc.Call("CDRStatsV1.GetMetrics", v1.AttrGetMetrics{StatsQueueId: "STATS_SUPPL1"}, &statMetrics); err != nil { + t.Error("Calling CDRStatsV1.GetMetrics, got error: ", err.Error()) + } else if !reflect.DeepEqual(eMetrics, statMetrics) { + t.Errorf("Expecting: %v, received: %v", eMetrics, statMetrics) + } + eMetrics = map[string]float64{engine.ACD: 67, engine.ASR: 100, engine.TCC: 1.2551, engine.TCD: 134, engine.ACC: 0.62755} + if err := tutKamCallsRpc.Call("CDRStatsV1.GetMetrics", v1.AttrGetMetrics{StatsQueueId: "STATS_SUPPL2"}, &statMetrics); err != nil { + t.Error("Calling CDRStatsV1.GetMetrics, got error: ", err.Error()) + } else if !reflect.DeepEqual(eMetrics, statMetrics) { + t.Errorf("Expecting: %v, received: %v", eMetrics, statMetrics) } } From 36595d88f65956d442f5dda67655a11a39ff12c6 Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Mon, 25 May 2015 16:24:41 +0300 Subject: [PATCH 10/10] fix for cdrlog action --- data/docker/devel/run.sh | 3 -- engine/account.go | 14 +++++---- engine/account_test.go | 4 +-- engine/action.go | 56 +++++++++++++++++++++++------------- engine/actions_local_test.go | 2 +- engine/balances.go | 1 + run_devel_docker.sh | 2 +- 7 files changed, 50 insertions(+), 32 deletions(-) delete mode 100755 data/docker/devel/run.sh diff --git a/data/docker/devel/run.sh b/data/docker/devel/run.sh deleted file mode 100755 index c3bd43585..000000000 --- a/data/docker/devel/run.sh +++ /dev/null @@ -1,3 +0,0 @@ -#/usr/bin/env sh - -docker run --rm -p 3306:3306 -p 6379:6379 -p 2012:2012 -p 2013:2013 -p 2080:2080 -itv `pwd`:/root/code/src/github.com/cgrates/cgrates --name cgr cgrates diff --git a/engine/account.go b/engine/account.go index 81fd4dc04..fb2e2e246 100644 --- a/engine/account.go +++ b/engine/account.go @@ -96,11 +96,13 @@ func (ub *Account) getCreditForPrefix(cd *CallDescriptor) (duration time.Duratio // Returns the remaining credit in user's balance. func (ub *Account) debitBalanceAction(a *Action, reset bool) error { if a == nil { - return errors.New("nil minute action!") + return errors.New("nil minute action") } if a.Balance.Uuid == "" { a.Balance.Uuid = utils.GenUUID() } + bClone := a.Balance.Clone() + if ub.BalanceMap == nil { ub.BalanceMap = make(map[string]BalanceChain, 1) } @@ -115,15 +117,17 @@ func (ub *Account) debitBalanceAction(a *Action, reset bool) error { if reset { b.Value = 0 } - b.SubstractAmount(a.Balance.Value) + b.SubstractAmount(bClone.Value) found = true } } // if it is not found then we add it to the list if !found { - a.Balance.Value = -a.Balance.Value - a.Balance.dirty = true // Mark the balance as dirty since we have modified and it should be checked by action triggers - ub.BalanceMap[id] = append(ub.BalanceMap[id], a.Balance) + if bClone.Value != 0 { + bClone.Value = -bClone.Value + } + bClone.dirty = true // Mark the balance as dirty since we have modified and it should be checked by action triggers + ub.BalanceMap[id] = append(ub.BalanceMap[id], bClone) } if a.Balance.SharedGroup != "" { // add shared group member diff --git a/engine/account_test.go b/engine/account_test.go index 1ce98a279..31d30827e 100644 --- a/engine/account_test.go +++ b/engine/account_test.go @@ -940,8 +940,8 @@ func TestAccountdebitBalance(t *testing.T) { newMb := &Balance{Weight: 20, DestinationIds: "NEW"} a := &Action{BalanceType: utils.VOICE, Direction: OUTBOUND, Balance: newMb} ub.debitBalanceAction(a, false) - if len(ub.BalanceMap[utils.VOICE+OUTBOUND]) != 3 || ub.BalanceMap[utils.VOICE+OUTBOUND][2] != newMb { - t.Error("Error adding minute bucket!", len(ub.BalanceMap[utils.VOICE+OUTBOUND]), ub.BalanceMap[utils.VOICE+OUTBOUND]) + if len(ub.BalanceMap[utils.VOICE+OUTBOUND]) != 3 || ub.BalanceMap[utils.VOICE+OUTBOUND][2].Uuid != newMb.Uuid { + t.Errorf("Error adding minute bucket! %d %+v %+v", len(ub.BalanceMap[utils.VOICE+OUTBOUND]), ub.BalanceMap[utils.VOICE+OUTBOUND][2], newMb) } } diff --git a/engine/action.go b/engine/action.go index 214611bc5..290f2656d 100644 --- a/engine/action.go +++ b/engine/action.go @@ -70,6 +70,19 @@ const ( CDRLOG = "*cdrlog" ) +func (a *Action) Clone() *Action { + return &Action{ + Id: a.Id, + ActionType: a.ActionType, + BalanceType: a.BalanceType, + Direction: a.Direction, + ExtraParameters: a.ExtraParameters, + ExpirationString: a.ExpirationString, + Weight: a.Weight, + Balance: a.Balance.Clone(), + } +} + type actionTypeFunc func(*Account, *StatsQueueTriggered, *Action, Actions) error func getActionFunc(typ string) (actionTypeFunc, bool) { @@ -254,7 +267,7 @@ func cdrLogAction(acc *Account, sq *StatsQueueTriggered, a *Action, acs Actions) func resetTriggersAction(ub *Account, sq *StatsQueueTriggered, a *Action, acs Actions) (err error) { if ub == nil { - return errors.New("Nil user balance") + return errors.New("nil user balance") } ub.ResetActionTriggers(a) return @@ -262,7 +275,7 @@ func resetTriggersAction(ub *Account, sq *StatsQueueTriggered, a *Action, acs Ac func setRecurrentAction(ub *Account, sq *StatsQueueTriggered, a *Action, acs Actions) (err error) { if ub == nil { - return errors.New("Nil user balance") + return errors.New("nil user balance") } ub.SetRecurrent(a, true) return @@ -270,7 +283,7 @@ func setRecurrentAction(ub *Account, sq *StatsQueueTriggered, a *Action, acs Act func unsetRecurrentAction(ub *Account, sq *StatsQueueTriggered, a *Action, acs Actions) (err error) { if ub == nil { - return errors.New("Nil user balance") + return errors.New("nil user balance") } ub.SetRecurrent(a, false) return @@ -278,7 +291,7 @@ func unsetRecurrentAction(ub *Account, sq *StatsQueueTriggered, a *Action, acs A func allowNegativeAction(ub *Account, sq *StatsQueueTriggered, a *Action, acs Actions) (err error) { if ub == nil { - return errors.New("Nil user balance") + return errors.New("nil user balance") } ub.AllowNegative = true return @@ -286,7 +299,7 @@ func allowNegativeAction(ub *Account, sq *StatsQueueTriggered, a *Action, acs Ac func denyNegativeAction(ub *Account, sq *StatsQueueTriggered, a *Action, acs Actions) (err error) { if ub == nil { - return errors.New("Nil user balance") + return errors.New("nil user balance") } ub.AllowNegative = false return @@ -294,33 +307,35 @@ func denyNegativeAction(ub *Account, sq *StatsQueueTriggered, a *Action, acs Act func resetAccountAction(ub *Account, sq *StatsQueueTriggered, a *Action, acs Actions) (err error) { if ub == nil { - return errors.New("Nil user balance") + return errors.New("nil user balance") } return genericReset(ub) } func topupResetAction(ub *Account, sq *StatsQueueTriggered, a *Action, acs Actions) (err error) { if ub == nil { - return errors.New("Nil user balance") + return errors.New("nil user balance") } if ub.BalanceMap == nil { // Init the map since otherwise will get error if nil ub.BalanceMap = make(map[string]BalanceChain, 0) } - genericMakeNegative(a) - return genericDebit(ub, a, true) + c := a.Clone() + genericMakeNegative(c) + return genericDebit(ub, c, true) } func topupAction(ub *Account, sq *StatsQueueTriggered, a *Action, acs Actions) (err error) { if ub == nil { - return errors.New("Nil user balance") + return errors.New("nil user balance") } - genericMakeNegative(a) - return genericDebit(ub, a, false) + c := a.Clone() + genericMakeNegative(c) + return genericDebit(ub, c, false) } func debitResetAction(ub *Account, sq *StatsQueueTriggered, a *Action, acs Actions) (err error) { if ub == nil { - return errors.New("Nil user balance") + return errors.New("nil user balance") } if ub.BalanceMap == nil { // Init the map since otherwise will get error if nil ub.BalanceMap = make(map[string]BalanceChain, 0) @@ -330,14 +345,15 @@ func debitResetAction(ub *Account, sq *StatsQueueTriggered, a *Action, acs Actio func debitAction(ub *Account, sq *StatsQueueTriggered, a *Action, acs Actions) (err error) { if ub == nil { - return errors.New("Nil user balance") + return errors.New("nil user balance") } - return genericDebit(ub, a, false) + err = genericDebit(ub, a, false) + return } func resetCounterAction(ub *Account, sq *StatsQueueTriggered, a *Action, acs Actions) (err error) { if ub == nil { - return errors.New("Nil user balance") + return errors.New("nil user balance") } uc := ub.getUnitCounter(a) if uc == nil { @@ -350,7 +366,7 @@ func resetCounterAction(ub *Account, sq *StatsQueueTriggered, a *Action, acs Act func resetCountersAction(ub *Account, sq *StatsQueueTriggered, a *Action, acs Actions) (err error) { if ub == nil { - return errors.New("Nil user balance") + return errors.New("nil user balance") } ub.UnitCounters = make([]*UnitsCounter, 0) ub.initCounters() @@ -365,7 +381,7 @@ func genericMakeNegative(a *Action) { func genericDebit(ub *Account, a *Action, reset bool) (err error) { if ub == nil { - return errors.New("Nil user balance") + return errors.New("nil user balance") } if ub.BalanceMap == nil { ub.BalanceMap = make(map[string]BalanceChain) @@ -376,7 +392,7 @@ func genericDebit(ub *Account, a *Action, reset bool) (err error) { func enableUserAction(ub *Account, sq *StatsQueueTriggered, a *Action, acs Actions) (err error) { if ub == nil { - return errors.New("Nil user balance") + return errors.New("nil user balance") } ub.Disabled = false return @@ -384,7 +400,7 @@ func enableUserAction(ub *Account, sq *StatsQueueTriggered, a *Action, acs Actio func disableUserAction(ub *Account, sq *StatsQueueTriggered, a *Action, acs Actions) (err error) { if ub == nil { - return errors.New("Nil user balance") + return errors.New("nil user balance") } ub.Disabled = true return diff --git a/engine/actions_local_test.go b/engine/actions_local_test.go index 9f6390400..0b0e7a3ef 100644 --- a/engine/actions_local_test.go +++ b/engine/actions_local_test.go @@ -118,7 +118,7 @@ func TestActionsLocalSetCdrlogActions(t *testing.T) { rcvedCdrs[0].Usage != "1" || rcvedCdrs[0].MediationRunId != utils.META_DEFAULT || rcvedCdrs[0].Cost != attrsAA.Actions[0].Units { - t.Error("Received: ", rcvedCdrs[0]) + t.Errorf("Received: %+v", rcvedCdrs[0]) } } diff --git a/engine/balances.go b/engine/balances.go index 4a5f0d054..2c3cc202c 100644 --- a/engine/balances.go +++ b/engine/balances.go @@ -180,6 +180,7 @@ func (b *Balance) Clone() *Balance { SharedGroup: b.SharedGroup, TimingIDs: b.TimingIDs, Timings: b.Timings, // should not be a problem with aliasing + dirty: b.dirty, } } diff --git a/run_devel_docker.sh b/run_devel_docker.sh index 81ff9396d..c580dfda3 100755 --- a/run_devel_docker.sh +++ b/run_devel_docker.sh @@ -1,3 +1,3 @@ #!/usr/bin/env sh -docker run --rm -p 3306:3306 -p 6379:6379 -p 2012:2012 -itv /home/rif/Documents/prog/go/src/github.com/cgrates/cgrates:/root/code/src/github.com/cgrates/cgrates --name cgr cgrates +docker run --rm -p 3306:3306 -p 6379:6379 -p 2012:2012 -p 2013:2013 -p 2080:2080 -itv `pwd`:/root/code/src/github.com/cgrates/cgrates --name cgr cgrates