/* << .Object.Property >> Property can be a attribute or a method both used without () Please also note the initial dot . Currently there are following objects that can be used: Account - the account that this action is called on Action - the action with all it's attributs Actions - the list of actions in the current action set Sq - StatsQueueTriggered object We can actually use everythiong that go templates offer. You can read more here: https://golang.org/pkg/text/template/ */ func cgrRPCAction(account *Account, sq *StatsQueueTriggered, a *Action, acs Actions) error { // parse template tmpl := template.New("extra_params") tmpl.Delims("<<", ">>") t, err := tmpl.Parse(a.ExtraParameters) if err != nil { utils.Logger.Err(fmt.Sprintf("error parsing *cgr_rpc template: %s", err.Error())) return err } var buf bytes.Buffer if err = t.Execute(&buf, struct { Account *Account Sq *StatsQueueTriggered Action *Action Actions Actions }{account, sq, a, acs}); err != nil { utils.Logger.Err(fmt.Sprintf("error executing *cgr_rpc template %s:", err.Error())) return err } processedExtraParam := buf.String() //utils.Logger.Info("ExtraParameters: " + parsedExtraParameters) req := RPCRequest{} if err := json.Unmarshal([]byte(processedExtraParam), &req); err != nil { return err } params, err := utils.GetRpcParams(req.Method) if err != nil { return err } var client rpcclient.RpcClientConnection if req.Address != utils.MetaInternal { if client, err = rpcclient.NewRpcClient("tcp", req.Address, req.Attempts, 0, config.CgrConfig().ConnectTimeout, config.CgrConfig().ReplyTimeout, req.Transport, nil); err != nil { return err } } else { client = params.Object.(rpcclient.RpcClientConnection) } in, out := params.InParam, params.OutParam //utils.Logger.Info("Params: " + utils.ToJSON(req.Params)) //p, err := utils.FromMapStringInterfaceValue(req.Params, in) mapstructure.Decode(req.Params, in) if err != nil { utils.Logger.Info("<*cgr_rpc> err: " + err.Error()) return err } utils.Logger.Info(fmt.Sprintf("<*cgr_rpc> calling: %s with: %s", req.Method, utils.ToJSON(in))) if !req.Async { err = client.Call(req.Method, in, out) utils.Logger.Info(fmt.Sprintf("<*cgr_rpc> result: %s err: %v", utils.ToJSON(out), err)) return err } go func() { err := client.Call(req.Method, in, out) utils.Logger.Info(fmt.Sprintf("<*cgr_rpc> result: %s err: %v", utils.ToJSON(out), err)) }() return nil }
func TestAcntActsDisableAcnt(t *testing.T) { acnt1Tag := "cgrates.org:1" at := &engine.ActionTiming{ ActionsID: "DISABLE_ACNT", } at.SetAccountIDs(utils.StringMap{acnt1Tag: true}) if err := at.Execute(); err != nil { t.Error(err) } expectAcnt := &engine.Account{ID: "cgrates.org:1", Disabled: true} if acnt, err := acntDbAcntActs.GetAccount(acnt1Tag); err != nil { t.Error(err) } else if !reflect.DeepEqual(expectAcnt, acnt) { t.Errorf("Expecting: %+v, received: %+v", utils.ToJSON(expectAcnt), utils.ToJSON(acnt)) } }
func CleanStalePrefixes(destIds []string) { utils.Logger.Info("Cleaning stale dest prefixes: " + utils.ToJSON(destIds)) prefixMap, err := CacheGetAllEntries(utils.DESTINATION_PREFIX) if err != nil { return } for prefix, idIDs := range prefixMap { dIDs := idIDs.(map[string]struct{}) changed := false for _, searchedDID := range destIds { if _, found := dIDs[searchedDID]; found { if len(dIDs) == 1 { // remove de prefix from cache CacheRemKey(utils.DESTINATION_PREFIX + prefix) } else { // delete the destination from list and put the new list in chache delete(dIDs, searchedDID) changed = true } } } if changed { CacheSet(utils.DESTINATION_PREFIX+prefix, dIDs) } } }
func removeBalanceAction(ub *Account, sq *StatsQueueTriggered, a *Action, acs Actions) error { if ub == nil { return fmt.Errorf("nil account for %s action", utils.ToJSON(a)) } if _, exists := ub.BalanceMap[a.Balance.GetType()]; !exists { return utils.ErrNotFound } bChain := ub.BalanceMap[a.Balance.GetType()] found := false for i := 0; i < len(bChain); i++ { if bChain[i].MatchFilter(a.Balance, false) { // delete without preserving order bChain[i] = bChain[len(bChain)-1] bChain = bChain[:len(bChain)-1] i -= 1 found = true } } ub.BalanceMap[a.Balance.GetType()] = bChain if !found { return utils.ErrNotFound } return nil }
// Attempts to refund a duration, error on failure func (self *SMGSession) refund(refundDuration time.Duration) error { initialRefundDuration := refundDuration firstCC := self.callCosts[0] // use merged cc (from close function) firstCC.Timespans.Decompress() var refundIncrements engine.Increments for i := len(firstCC.Timespans) - 1; i >= 0; i-- { ts := firstCC.Timespans[i] tsDuration := ts.GetDuration() if refundDuration <= tsDuration { lastRefundedIncrementIndex := -1 for j := len(ts.Increments) - 1; j >= 0; j-- { increment := ts.Increments[j] if increment.Duration <= refundDuration { refundIncrements = append(refundIncrements, increment) refundDuration -= increment.Duration lastRefundedIncrementIndex = j } else { break //increment duration is larger, cannot refund increment } } if lastRefundedIncrementIndex == 0 { firstCC.Timespans[i] = nil firstCC.Timespans = firstCC.Timespans[:i] } else { ts.SplitByIncrement(lastRefundedIncrementIndex) ts.Cost = ts.CalculateCost() } break // do not go to other timespans } else { refundIncrements = append(refundIncrements, ts.Increments...) // remove the timespan entirely firstCC.Timespans[i] = nil firstCC.Timespans = firstCC.Timespans[:i] // continue to the next timespan with what is left to refund refundDuration -= tsDuration } } // show only what was actualy refunded (stopped in timespan) // utils.Logger.Info(fmt.Sprintf("Refund duration: %v", initialRefundDuration-refundDuration)) if len(refundIncrements) > 0 { cd := firstCC.CreateCallDescriptor() cd.Increments = refundIncrements cd.CgrID = self.cd.CgrID cd.RunID = self.cd.RunID cd.Increments.Compress() utils.Logger.Info(fmt.Sprintf("Refunding %s duration %v with cd: %s", cd.CgrID, initialRefundDuration, utils.ToJSON(cd))) var response float64 err := self.rater.Call("Responder.RefundIncrements", cd, &response) if err != nil { return err } } //firstCC.Cost -= refundIncrements.GetTotalCost() // use updateCost instead firstCC.UpdateCost() firstCC.UpdateRatedUsage() firstCC.Timespans.Compress() return nil }
func (cc *CallCost) AsJSON() string { return utils.ToJSON(cc) }
func (self *SQLStorage) SetCDR(cdr *CDR, allowUpdate bool) error { extraFields, err := json.Marshal(cdr.ExtraFields) if err != nil { return err } tx := self.db.Begin() saved := tx.Save(&TBLCDRs{ Cgrid: cdr.CGRID, RunID: cdr.RunID, OriginHost: cdr.OriginHost, Source: cdr.Source, OriginID: cdr.OriginID, Tor: cdr.ToR, RequestType: cdr.RequestType, Direction: cdr.Direction, Tenant: cdr.Tenant, Category: cdr.Category, Account: cdr.Account, Subject: cdr.Subject, Destination: cdr.Destination, SetupTime: cdr.SetupTime, Pdd: cdr.PDD.Seconds(), AnswerTime: cdr.AnswerTime, Usage: cdr.Usage.Seconds(), Supplier: cdr.Supplier, DisconnectCause: cdr.DisconnectCause, ExtraFields: string(extraFields), CostSource: cdr.CostSource, Cost: cdr.Cost, CostDetails: cdr.CostDetailsJson(), AccountSummary: utils.ToJSON(cdr.AccountSummary), ExtraInfo: cdr.ExtraInfo, CreatedAt: time.Now(), }) if saved.Error != nil { tx.Rollback() if !allowUpdate { return saved.Error } tx = self.db.Begin() updated := tx.Model(&TBLCDRs{}).Where(&TBLCDRs{Cgrid: cdr.CGRID, RunID: cdr.RunID, OriginID: cdr.OriginID}).Updates( TBLCDRs{ OriginHost: cdr.OriginHost, Source: cdr.Source, OriginID: cdr.OriginID, Tor: cdr.ToR, RequestType: cdr.RequestType, Direction: cdr.Direction, Tenant: cdr.Tenant, Category: cdr.Category, Account: cdr.Account, Subject: cdr.Subject, Destination: cdr.Destination, SetupTime: cdr.SetupTime, Pdd: cdr.PDD.Seconds(), AnswerTime: cdr.AnswerTime, Usage: cdr.Usage.Seconds(), Supplier: cdr.Supplier, DisconnectCause: cdr.DisconnectCause, ExtraFields: string(extraFields), CostSource: cdr.CostSource, Cost: cdr.Cost, CostDetails: cdr.CostDetailsJson(), AccountSummary: utils.ToJSON(cdr.AccountSummary), ExtraInfo: cdr.ExtraInfo, UpdatedAt: time.Now(), }, ) if updated.Error != nil { tx.Rollback() return updated.Error } } tx.Commit() return nil }
func setBalanceAction(acc *Account, sq *StatsQueueTriggered, a *Action, acs Actions) error { if acc == nil { return fmt.Errorf("nil account for %s action", utils.ToJSON(a)) } return acc.setBalanceAction(a) }
func TestLoadCdrcConfigMultipleFiles(t *testing.T) { cgrCfg, err := NewCGRConfigFromFolder(".") if err != nil { t.Error(err) } eCgrCfg, _ := NewDefaultCGRConfig() eCgrCfg.CdrcProfiles = make(map[string][]*CdrcConfig) // Default instance first eCgrCfg.CdrcProfiles["/var/spool/cgrates/cdrc/in"] = []*CdrcConfig{ &CdrcConfig{ ID: utils.META_DEFAULT, Enabled: false, CdrsConns: []*HaPoolConfig{&HaPoolConfig{Address: utils.MetaInternal}}, CdrFormat: "csv", FieldSeparator: ',', DataUsageMultiplyFactor: 1024, RunDelay: 0, MaxOpenFiles: 1024, CdrInDir: "/var/spool/cgrates/cdrc/in", CdrOutDir: "/var/spool/cgrates/cdrc/out", FailedCallsPrefix: "missed_calls", CDRPath: utils.HierarchyPath([]string{""}), CdrSourceId: "freeswitch_csv", CdrFilter: utils.ParseRSRFieldsMustCompile("", utils.INFIELD_SEP), PartialRecordCache: time.Duration(10) * time.Second, HeaderFields: make([]*CfgCdrField, 0), ContentFields: []*CfgCdrField{ &CfgCdrField{Tag: "TOR", Type: utils.META_COMPOSED, FieldId: utils.TOR, Value: utils.ParseRSRFieldsMustCompile("2", utils.INFIELD_SEP), FieldFilter: utils.ParseRSRFieldsMustCompile("", utils.INFIELD_SEP), Width: 0, Strip: "", Padding: "", Layout: "", Mandatory: true}, &CfgCdrField{Tag: "OriginID", Type: utils.META_COMPOSED, FieldId: utils.ACCID, Value: utils.ParseRSRFieldsMustCompile("3", utils.INFIELD_SEP), FieldFilter: utils.ParseRSRFieldsMustCompile("", utils.INFIELD_SEP), Width: 0, Strip: "", Padding: "", Layout: "", Mandatory: true}, &CfgCdrField{Tag: "RequestType", Type: utils.META_COMPOSED, FieldId: utils.REQTYPE, Value: utils.ParseRSRFieldsMustCompile("4", utils.INFIELD_SEP), FieldFilter: utils.ParseRSRFieldsMustCompile("", utils.INFIELD_SEP), Width: 0, Strip: "", Padding: "", Layout: "", Mandatory: true}, &CfgCdrField{Tag: "Direction", Type: utils.META_COMPOSED, FieldId: utils.DIRECTION, Value: utils.ParseRSRFieldsMustCompile("5", utils.INFIELD_SEP), FieldFilter: utils.ParseRSRFieldsMustCompile("", utils.INFIELD_SEP), Width: 0, Strip: "", Padding: "", Layout: "", Mandatory: true}, &CfgCdrField{Tag: "Tenant", Type: utils.META_COMPOSED, FieldId: utils.TENANT, Value: utils.ParseRSRFieldsMustCompile("6", utils.INFIELD_SEP), FieldFilter: utils.ParseRSRFieldsMustCompile("", utils.INFIELD_SEP), Width: 0, Strip: "", Padding: "", Layout: "", Mandatory: true}, &CfgCdrField{Tag: "Category", Type: utils.META_COMPOSED, FieldId: utils.CATEGORY, Value: utils.ParseRSRFieldsMustCompile("7", utils.INFIELD_SEP), FieldFilter: utils.ParseRSRFieldsMustCompile("", utils.INFIELD_SEP), Width: 0, Strip: "", Padding: "", Layout: "", Mandatory: true}, &CfgCdrField{Tag: "Account", Type: utils.META_COMPOSED, FieldId: utils.ACCOUNT, Value: utils.ParseRSRFieldsMustCompile("8", utils.INFIELD_SEP), FieldFilter: utils.ParseRSRFieldsMustCompile("", utils.INFIELD_SEP), Width: 0, Strip: "", Padding: "", Layout: "", Mandatory: true}, &CfgCdrField{Tag: "Subject", Type: utils.META_COMPOSED, FieldId: utils.SUBJECT, Value: utils.ParseRSRFieldsMustCompile("9", utils.INFIELD_SEP), FieldFilter: utils.ParseRSRFieldsMustCompile("", utils.INFIELD_SEP), Width: 0, Strip: "", Padding: "", Layout: "", Mandatory: true}, &CfgCdrField{Tag: "Destination", Type: utils.META_COMPOSED, FieldId: utils.DESTINATION, Value: utils.ParseRSRFieldsMustCompile("10", utils.INFIELD_SEP), FieldFilter: utils.ParseRSRFieldsMustCompile("", utils.INFIELD_SEP), Width: 0, Strip: "", Padding: "", Layout: "", Mandatory: true}, &CfgCdrField{Tag: "SetupTime", Type: utils.META_COMPOSED, FieldId: utils.SETUP_TIME, Value: utils.ParseRSRFieldsMustCompile("11", utils.INFIELD_SEP), FieldFilter: utils.ParseRSRFieldsMustCompile("", utils.INFIELD_SEP), Width: 0, Strip: "", Padding: "", Layout: "", Mandatory: true}, &CfgCdrField{Tag: "AnswerTime", Type: utils.META_COMPOSED, FieldId: utils.ANSWER_TIME, Value: utils.ParseRSRFieldsMustCompile("12", utils.INFIELD_SEP), FieldFilter: utils.ParseRSRFieldsMustCompile("", utils.INFIELD_SEP), Width: 0, Strip: "", Padding: "", Layout: "", Mandatory: true}, &CfgCdrField{Tag: "Usage", Type: utils.META_COMPOSED, FieldId: utils.USAGE, Value: utils.ParseRSRFieldsMustCompile("13", utils.INFIELD_SEP), FieldFilter: utils.ParseRSRFieldsMustCompile("", utils.INFIELD_SEP), Width: 0, Strip: "", Padding: "", Layout: "", Mandatory: true}, }, TrailerFields: make([]*CfgCdrField, 0), }, } eCgrCfg.CdrcProfiles["/tmp/cgrates/cdrc1/in"] = []*CdrcConfig{ &CdrcConfig{ ID: "CDRC-CSV1", Enabled: true, CdrsConns: []*HaPoolConfig{&HaPoolConfig{Address: utils.MetaInternal}}, CdrFormat: "csv", FieldSeparator: ',', DataUsageMultiplyFactor: 1024, RunDelay: 0, MaxOpenFiles: 1024, CdrInDir: "/tmp/cgrates/cdrc1/in", CdrOutDir: "/tmp/cgrates/cdrc1/out", CDRPath: nil, CdrSourceId: "csv1", CdrFilter: utils.ParseRSRFieldsMustCompile("", utils.INFIELD_SEP), HeaderFields: make([]*CfgCdrField, 0), ContentFields: []*CfgCdrField{ &CfgCdrField{Tag: "TOR", Type: utils.META_COMPOSED, FieldId: utils.TOR, Value: utils.ParseRSRFieldsMustCompile("2", utils.INFIELD_SEP), FieldFilter: utils.ParseRSRFieldsMustCompile("", utils.INFIELD_SEP), Width: 0, Strip: "", Padding: "", Layout: "", Mandatory: true}, &CfgCdrField{Tag: "OriginID", Type: utils.META_COMPOSED, FieldId: utils.ACCID, Value: utils.ParseRSRFieldsMustCompile("3", utils.INFIELD_SEP), FieldFilter: utils.ParseRSRFieldsMustCompile("", utils.INFIELD_SEP), Width: 0, Strip: "", Padding: "", Layout: "", Mandatory: true}, &CfgCdrField{Tag: "RequestType", Type: utils.META_COMPOSED, FieldId: utils.REQTYPE, Value: utils.ParseRSRFieldsMustCompile("4", utils.INFIELD_SEP), FieldFilter: utils.ParseRSRFieldsMustCompile("", utils.INFIELD_SEP), Width: 0, Strip: "", Padding: "", Layout: "", Mandatory: true}, &CfgCdrField{Tag: "Direction", Type: utils.META_COMPOSED, FieldId: utils.DIRECTION, Value: utils.ParseRSRFieldsMustCompile("5", utils.INFIELD_SEP), FieldFilter: utils.ParseRSRFieldsMustCompile("", utils.INFIELD_SEP), Width: 0, Strip: "", Padding: "", Layout: "", Mandatory: true}, &CfgCdrField{Tag: "Tenant", Type: utils.META_COMPOSED, FieldId: utils.TENANT, Value: utils.ParseRSRFieldsMustCompile("6", utils.INFIELD_SEP), FieldFilter: utils.ParseRSRFieldsMustCompile("", utils.INFIELD_SEP), Width: 0, Strip: "", Padding: "", Layout: "", Mandatory: true}, &CfgCdrField{Tag: "Category", Type: utils.META_COMPOSED, FieldId: utils.CATEGORY, Value: utils.ParseRSRFieldsMustCompile("7", utils.INFIELD_SEP), FieldFilter: utils.ParseRSRFieldsMustCompile("", utils.INFIELD_SEP), Width: 0, Strip: "", Padding: "", Layout: "", Mandatory: true}, &CfgCdrField{Tag: "Account", Type: utils.META_COMPOSED, FieldId: utils.ACCOUNT, Value: utils.ParseRSRFieldsMustCompile("8", utils.INFIELD_SEP), FieldFilter: utils.ParseRSRFieldsMustCompile("", utils.INFIELD_SEP), Width: 0, Strip: "", Padding: "", Layout: "", Mandatory: true}, &CfgCdrField{Tag: "Subject", Type: utils.META_COMPOSED, FieldId: utils.SUBJECT, Value: utils.ParseRSRFieldsMustCompile("9", utils.INFIELD_SEP), FieldFilter: utils.ParseRSRFieldsMustCompile("", utils.INFIELD_SEP), Width: 0, Strip: "", Padding: "", Layout: "", Mandatory: true}, &CfgCdrField{Tag: "Destination", Type: utils.META_COMPOSED, FieldId: utils.DESTINATION, Value: utils.ParseRSRFieldsMustCompile("10", utils.INFIELD_SEP), FieldFilter: utils.ParseRSRFieldsMustCompile("", utils.INFIELD_SEP), Width: 0, Strip: "", Padding: "", Layout: "", Mandatory: true}, &CfgCdrField{Tag: "SetupTime", Type: utils.META_COMPOSED, FieldId: utils.SETUP_TIME, Value: utils.ParseRSRFieldsMustCompile("11", utils.INFIELD_SEP), FieldFilter: utils.ParseRSRFieldsMustCompile("", utils.INFIELD_SEP), Width: 0, Strip: "", Padding: "", Layout: "", Mandatory: true}, &CfgCdrField{Tag: "AnswerTime", Type: utils.META_COMPOSED, FieldId: utils.ANSWER_TIME, Value: utils.ParseRSRFieldsMustCompile("12", utils.INFIELD_SEP), FieldFilter: utils.ParseRSRFieldsMustCompile("", utils.INFIELD_SEP), Width: 0, Strip: "", Padding: "", Layout: "", Mandatory: true}, &CfgCdrField{Tag: "Usage", Type: utils.META_COMPOSED, FieldId: utils.USAGE, Value: utils.ParseRSRFieldsMustCompile("13", utils.INFIELD_SEP), FieldFilter: utils.ParseRSRFieldsMustCompile("", utils.INFIELD_SEP), Width: 0, Strip: "", Padding: "", Layout: "", Mandatory: true}, }, TrailerFields: make([]*CfgCdrField, 0), }, } eCgrCfg.CdrcProfiles["/tmp/cgrates/cdrc2/in"] = []*CdrcConfig{ &CdrcConfig{ ID: "CDRC-CSV2", Enabled: true, CdrsConns: []*HaPoolConfig{&HaPoolConfig{Address: utils.MetaInternal}}, CdrFormat: "csv", FieldSeparator: ',', DataUsageMultiplyFactor: 0.000976563, RunDelay: 1000000000, MaxOpenFiles: 1024, CdrInDir: "/tmp/cgrates/cdrc2/in", CdrOutDir: "/tmp/cgrates/cdrc2/out", CDRPath: nil, CdrSourceId: "csv2", CdrFilter: utils.ParseRSRFieldsMustCompile("", utils.INFIELD_SEP), HeaderFields: make([]*CfgCdrField, 0), ContentFields: []*CfgCdrField{ &CfgCdrField{FieldId: utils.TOR, Value: utils.ParseRSRFieldsMustCompile("~7:s/^(voice|data|sms|mms|generic)$/*$1/", utils.INFIELD_SEP), FieldFilter: utils.ParseRSRFieldsMustCompile("", utils.INFIELD_SEP), Width: 0, Strip: "", Padding: "", Layout: "", Mandatory: false}, &CfgCdrField{Tag: "", Type: "", FieldId: utils.ANSWER_TIME, Value: utils.ParseRSRFieldsMustCompile("1", utils.INFIELD_SEP), FieldFilter: utils.ParseRSRFieldsMustCompile("", utils.INFIELD_SEP), Width: 0, Strip: "", Padding: "", Layout: "", Mandatory: false}, &CfgCdrField{FieldId: utils.USAGE, Value: utils.ParseRSRFieldsMustCompile("~9:s/^(\\d+)$/${1}s/", utils.INFIELD_SEP), FieldFilter: utils.ParseRSRFieldsMustCompile("", utils.INFIELD_SEP), Width: 0, Strip: "", Padding: "", Layout: "", Mandatory: false}, }, TrailerFields: make([]*CfgCdrField, 0), }, } eCgrCfg.CdrcProfiles["/tmp/cgrates/cdrc3/in"] = []*CdrcConfig{ &CdrcConfig{ ID: "CDRC-CSV3", Enabled: true, CdrsConns: []*HaPoolConfig{&HaPoolConfig{Address: utils.MetaInternal}}, CdrFormat: "csv", FieldSeparator: ',', DataUsageMultiplyFactor: 1024, RunDelay: 0, MaxOpenFiles: 1024, CdrInDir: "/tmp/cgrates/cdrc3/in", CdrOutDir: "/tmp/cgrates/cdrc3/out", CDRPath: nil, CdrSourceId: "csv3", CdrFilter: utils.ParseRSRFieldsMustCompile("", utils.INFIELD_SEP), HeaderFields: make([]*CfgCdrField, 0), ContentFields: []*CfgCdrField{ &CfgCdrField{Tag: "TOR", Type: utils.META_COMPOSED, FieldId: utils.TOR, Value: utils.ParseRSRFieldsMustCompile("2", utils.INFIELD_SEP), FieldFilter: utils.ParseRSRFieldsMustCompile("", utils.INFIELD_SEP), Width: 0, Strip: "", Padding: "", Layout: "", Mandatory: true}, &CfgCdrField{Tag: "OriginID", Type: utils.META_COMPOSED, FieldId: utils.ACCID, Value: utils.ParseRSRFieldsMustCompile("3", utils.INFIELD_SEP), FieldFilter: utils.ParseRSRFieldsMustCompile("", utils.INFIELD_SEP), Width: 0, Strip: "", Padding: "", Layout: "", Mandatory: true}, &CfgCdrField{Tag: "RequestType", Type: utils.META_COMPOSED, FieldId: utils.REQTYPE, Value: utils.ParseRSRFieldsMustCompile("4", utils.INFIELD_SEP), FieldFilter: utils.ParseRSRFieldsMustCompile("", utils.INFIELD_SEP), Width: 0, Strip: "", Padding: "", Layout: "", Mandatory: true}, &CfgCdrField{Tag: "Direction", Type: utils.META_COMPOSED, FieldId: utils.DIRECTION, Value: utils.ParseRSRFieldsMustCompile("5", utils.INFIELD_SEP), FieldFilter: utils.ParseRSRFieldsMustCompile("", utils.INFIELD_SEP), Width: 0, Strip: "", Padding: "", Layout: "", Mandatory: true}, &CfgCdrField{Tag: "Tenant", Type: utils.META_COMPOSED, FieldId: utils.TENANT, Value: utils.ParseRSRFieldsMustCompile("6", utils.INFIELD_SEP), FieldFilter: utils.ParseRSRFieldsMustCompile("", utils.INFIELD_SEP), Width: 0, Strip: "", Padding: "", Layout: "", Mandatory: true}, &CfgCdrField{Tag: "Category", Type: utils.META_COMPOSED, FieldId: utils.CATEGORY, Value: utils.ParseRSRFieldsMustCompile("7", utils.INFIELD_SEP), FieldFilter: utils.ParseRSRFieldsMustCompile("", utils.INFIELD_SEP), Width: 0, Strip: "", Padding: "", Layout: "", Mandatory: true}, &CfgCdrField{Tag: "Account", Type: utils.META_COMPOSED, FieldId: utils.ACCOUNT, Value: utils.ParseRSRFieldsMustCompile("8", utils.INFIELD_SEP), FieldFilter: utils.ParseRSRFieldsMustCompile("", utils.INFIELD_SEP), Width: 0, Strip: "", Padding: "", Layout: "", Mandatory: true}, &CfgCdrField{Tag: "Subject", Type: utils.META_COMPOSED, FieldId: utils.SUBJECT, Value: utils.ParseRSRFieldsMustCompile("9", utils.INFIELD_SEP), FieldFilter: utils.ParseRSRFieldsMustCompile("", utils.INFIELD_SEP), Width: 0, Strip: "", Padding: "", Layout: "", Mandatory: true}, &CfgCdrField{Tag: "Destination", Type: utils.META_COMPOSED, FieldId: utils.DESTINATION, Value: utils.ParseRSRFieldsMustCompile("10", utils.INFIELD_SEP), FieldFilter: utils.ParseRSRFieldsMustCompile("", utils.INFIELD_SEP), Width: 0, Strip: "", Padding: "", Layout: "", Mandatory: true}, &CfgCdrField{Tag: "SetupTime", Type: utils.META_COMPOSED, FieldId: utils.SETUP_TIME, Value: utils.ParseRSRFieldsMustCompile("11", utils.INFIELD_SEP), FieldFilter: utils.ParseRSRFieldsMustCompile("", utils.INFIELD_SEP), Width: 0, Strip: "", Padding: "", Layout: "", Mandatory: true}, &CfgCdrField{Tag: "AnswerTime", Type: utils.META_COMPOSED, FieldId: utils.ANSWER_TIME, Value: utils.ParseRSRFieldsMustCompile("12", utils.INFIELD_SEP), FieldFilter: utils.ParseRSRFieldsMustCompile("", utils.INFIELD_SEP), Width: 0, Strip: "", Padding: "", Layout: "", Mandatory: true}, &CfgCdrField{Tag: "Usage", Type: utils.META_COMPOSED, FieldId: utils.USAGE, Value: utils.ParseRSRFieldsMustCompile("13", utils.INFIELD_SEP), FieldFilter: utils.ParseRSRFieldsMustCompile("", utils.INFIELD_SEP), Width: 0, Strip: "", Padding: "", Layout: "", Mandatory: true}, }, TrailerFields: make([]*CfgCdrField, 0), }, } if !reflect.DeepEqual(eCgrCfg.CdrcProfiles, cgrCfg.CdrcProfiles) { t.Errorf("Expected: \n%s\n, received: \n%s\n", utils.ToJSON(eCgrCfg.CdrcProfiles), utils.ToJSON(cgrCfg.CdrcProfiles)) } }
// Attempts to refund a duration, error on failure func (self *SMGSession) refund(refundDuration time.Duration) error { initialRefundDuration := refundDuration lastCC := self.callCosts[len(self.callCosts)-1] lastCC.Timespans.Decompress() var refundIncrements engine.Increments for i := len(lastCC.Timespans) - 1; i >= 0; i-- { ts := lastCC.Timespans[i] tsDuration := ts.GetDuration() if refundDuration <= tsDuration { lastRefundedIncrementIndex := -1 for j := len(ts.Increments) - 1; j >= 0; j-- { increment := ts.Increments[j] if increment.Duration <= refundDuration { refundIncrements = append(refundIncrements, increment) refundDuration -= increment.Duration lastRefundedIncrementIndex = j } else { break //increment duration is larger, cannot refund increment } } if lastRefundedIncrementIndex == 0 { lastCC.Timespans[i] = nil lastCC.Timespans = lastCC.Timespans[:i] } else { ts.SplitByIncrement(lastRefundedIncrementIndex) ts.Cost = ts.CalculateCost() } break // do not go to other timespans } else { refundIncrements = append(refundIncrements, ts.Increments...) // remove the timespan entirely lastCC.Timespans[i] = nil lastCC.Timespans = lastCC.Timespans[:i] // continue to the next timespan with what is left to refund refundDuration -= tsDuration } } // show only what was actualy refunded (stopped in timespan) // utils.Logger.Info(fmt.Sprintf("Refund duration: %v", initialRefundDuration-refundDuration)) if len(refundIncrements) > 0 { cd := &engine.CallDescriptor{ Direction: lastCC.Direction, Tenant: lastCC.Tenant, Category: lastCC.Category, Subject: lastCC.Subject, Account: lastCC.Account, Destination: lastCC.Destination, TOR: lastCC.TOR, Increments: refundIncrements, } cd.Increments.Compress() utils.Logger.Info(fmt.Sprintf("Refunding duration %v with cd: %s", initialRefundDuration, utils.ToJSON(cd))) var response float64 err := self.rater.RefundIncrements(cd, &response) if err != nil { return err } } //utils.Logger.Debug(fmt.Sprintf("REFUND INCR: %s", utils.ToJSON(refundIncrements))) lastCC.Cost -= refundIncrements.GetTotalCost() lastCC.UpdateRatedUsage() lastCC.Timespans.Compress() return nil }
// Processes one time events (eg: SMS) func (self *SMGeneric) ChargeEvent(gev SMGenericEvent, clnt *rpc2.Client) (maxDur time.Duration, err error) { var sessionRuns []*engine.SessionRun if err := self.rater.Call("Responder.GetSessionRuns", gev.AsStoredCdr(self.cgrCfg, self.timezone), &sessionRuns); err != nil { return nilDuration, err } else if len(sessionRuns) == 0 { return nilDuration, nil } var maxDurInit bool // Avoid differences between default 0 and received 0 for _, sR := range sessionRuns { cc := new(engine.CallCost) if err = self.rater.Call("Responder.MaxDebit", sR.CallDescriptor, cc); err != nil { utils.Logger.Err(fmt.Sprintf("<SMGeneric> Could not Debit CD: %+v, RunID: %s, error: %s", sR.CallDescriptor, sR.DerivedCharger.RunID, err.Error())) break } sR.CallCosts = append(sR.CallCosts, cc) // Save it so we can revert on issues if ccDur := cc.GetDuration(); ccDur == 0 { err = utils.ErrInsufficientCredit break } else if !maxDurInit || ccDur < maxDur { maxDur = ccDur } } if err != nil { // Refund the ones already taken since we have error on one of the debits for _, sR := range sessionRuns { if len(sR.CallCosts) == 0 { continue } cc := sR.CallCosts[0] if len(sR.CallCosts) > 1 { for _, ccSR := range sR.CallCosts { cc.Merge(ccSR) } } // collect increments var refundIncrements engine.Increments cc.Timespans.Decompress() for _, ts := range cc.Timespans { refundIncrements = append(refundIncrements, ts.Increments...) } // refund cc if len(refundIncrements) > 0 { cd := cc.CreateCallDescriptor() cd.Increments = refundIncrements cd.CgrID = sR.CallDescriptor.CgrID cd.RunID = sR.CallDescriptor.RunID cd.Increments.Compress() utils.Logger.Info(fmt.Sprintf("Refunding session run callcost: %s", utils.ToJSON(cd))) var response float64 err := self.rater.Call("Responder.RefundIncrements", cd, &response) if err != nil { return nilDuration, err } } } return nilDuration, err } var withErrors bool for _, sR := range sessionRuns { if len(sR.CallCosts) == 0 { continue } cc := sR.CallCosts[0] if len(sR.CallCosts) > 1 { for _, ccSR := range sR.CallCosts[1:] { cc.Merge(ccSR) } } cc.Round() roundIncrements := cc.GetRoundIncrements() if len(roundIncrements) != 0 { cd := cc.CreateCallDescriptor() cd.Increments = roundIncrements var response float64 if err := self.rater.Call("Responder.RefundRounding", cd, &response); err != nil { utils.Logger.Err(fmt.Sprintf("<SM> ERROR failed to refund rounding: %v", err)) } } var reply string smCost := &engine.SMCost{ CGRID: gev.GetCgrId(self.timezone), CostSource: utils.SESSION_MANAGER_SOURCE, RunID: sR.DerivedCharger.RunID, OriginHost: gev.GetOriginatorIP(utils.META_DEFAULT), OriginID: gev.GetUUID(), CostDetails: cc, } if err := self.cdrsrv.Call("CdrsV1.StoreSMCost", engine.AttrCDRSStoreSMCost{Cost: smCost, CheckDuplicate: true}, &reply); err != nil && err != utils.ErrExists { withErrors = true utils.Logger.Err(fmt.Sprintf("<SMGeneric> Could not save CC: %+v, RunID: %s error: %s", cc, sR.DerivedCharger.RunID, err.Error())) } } if withErrors { return nilDuration, ErrPartiallyExecuted } return maxDur, nil }
func TestResponderGetLCR(t *testing.T) { rsponder.Stats = NewStats(ratingStorage, accountingStorage, 0) // Load stats instance dstDe := &Destination{Id: "GERMANY", Prefixes: []string{"+49"}} if err := ratingStorage.SetDestination(dstDe, utils.NonTransactional); err != nil { t.Error(err) } if err := ratingStorage.SetReverseDestination(dstDe, utils.NonTransactional); err != nil { t.Error(err) } rp1 := &RatingPlan{ Id: "RP1", Timings: map[string]*RITiming{ "30eab300": &RITiming{ Years: utils.Years{}, Months: utils.Months{}, MonthDays: utils.MonthDays{}, WeekDays: utils.WeekDays{}, StartTime: "00:00:00", }, }, Ratings: map[string]*RIRate{ "b457f86d": &RIRate{ ConnectFee: 0, Rates: []*Rate{ &Rate{ GroupIntervalStart: 0, Value: 0.01, RateIncrement: time.Second, RateUnit: time.Second, }, }, RoundingMethod: utils.ROUNDING_MIDDLE, RoundingDecimals: 4, }, }, DestinationRates: map[string]RPRateList{ dstDe.Id: []*RPRate{ &RPRate{ Timing: "30eab300", Rating: "b457f86d", Weight: 10, }, }, }, } rp2 := &RatingPlan{ Id: "RP2", Timings: map[string]*RITiming{ "30eab300": &RITiming{ Years: utils.Years{}, Months: utils.Months{}, MonthDays: utils.MonthDays{}, WeekDays: utils.WeekDays{}, StartTime: "00:00:00", }, }, Ratings: map[string]*RIRate{ "b457f86d": &RIRate{ ConnectFee: 0, Rates: []*Rate{ &Rate{ GroupIntervalStart: 0, Value: 0.02, RateIncrement: time.Second, RateUnit: time.Second, }, }, RoundingMethod: utils.ROUNDING_MIDDLE, RoundingDecimals: 4, }, }, DestinationRates: map[string]RPRateList{ "GERMANY": []*RPRate{ &RPRate{ Timing: "30eab300", Rating: "b457f86d", Weight: 10, }, }, }, } rp3 := &RatingPlan{ Id: "RP3", Timings: map[string]*RITiming{ "30eab300": &RITiming{ Years: utils.Years{}, Months: utils.Months{}, MonthDays: utils.MonthDays{}, WeekDays: utils.WeekDays{}, StartTime: "00:00:00", }, }, Ratings: map[string]*RIRate{ "b457f86d": &RIRate{ ConnectFee: 0, Rates: []*Rate{ &Rate{ GroupIntervalStart: 0, Value: 0.03, RateIncrement: time.Second, RateUnit: time.Second, }, }, RoundingMethod: utils.ROUNDING_MIDDLE, RoundingDecimals: 4, }, }, DestinationRates: map[string]RPRateList{ "GERMANY": []*RPRate{ &RPRate{ Timing: "30eab300", Rating: "b457f86d", Weight: 10, }, }, }, } for _, rpf := range []*RatingPlan{rp1, rp2, rp3} { if err := ratingStorage.SetRatingPlan(rpf, utils.NonTransactional); err != nil { t.Error(err) } } danStatsId := "dan12_stats" var r int rsponder.Stats.Call("CDRStatsV1.AddQueue", &CdrStats{Id: danStatsId, Supplier: []string{"dan12"}, Metrics: []string{ASR, PDD, ACD, TCD, ACC, TCC, DDC}}, &r) danRpfl := &RatingProfile{Id: "*out:tenant12:call:dan12", RatingPlanActivations: RatingPlanActivations{&RatingPlanActivation{ ActivationTime: time.Date(2015, 01, 01, 8, 0, 0, 0, time.UTC), RatingPlanId: rp1.Id, FallbackKeys: []string{}, CdrStatQueueIds: []string{danStatsId}, }}, } rifStatsId := "rif12_stats" rsponder.Stats.Call("CDRStatsV1.AddQueue", &CdrStats{Id: rifStatsId, Supplier: []string{"rif12"}, Metrics: []string{ASR, PDD, ACD, TCD, ACC, TCC, DDC}}, &r) rifRpfl := &RatingProfile{Id: "*out:tenant12:call:rif12", RatingPlanActivations: RatingPlanActivations{&RatingPlanActivation{ ActivationTime: time.Date(2015, 01, 01, 8, 0, 0, 0, time.UTC), RatingPlanId: rp2.Id, FallbackKeys: []string{}, CdrStatQueueIds: []string{rifStatsId}, }}, } ivoStatsId := "ivo12_stats" rsponder.Stats.Call("CDRStatsV1.AddQueue", &CdrStats{Id: ivoStatsId, Supplier: []string{"ivo12"}, Metrics: []string{ASR, PDD, ACD, TCD, ACC, TCC, DDC}}, &r) ivoRpfl := &RatingProfile{Id: "*out:tenant12:call:ivo12", RatingPlanActivations: RatingPlanActivations{&RatingPlanActivation{ ActivationTime: time.Date(2015, 01, 01, 8, 0, 0, 0, time.UTC), RatingPlanId: rp3.Id, FallbackKeys: []string{}, CdrStatQueueIds: []string{ivoStatsId}, }}, } for _, rpfl := range []*RatingProfile{danRpfl, rifRpfl, ivoRpfl} { if err := ratingStorage.SetRatingProfile(rpfl, utils.NonTransactional); err != nil { t.Error(err) } } lcrStatic := &LCR{Direction: utils.OUT, Tenant: "tenant12", Category: "call_static", Account: utils.ANY, Subject: utils.ANY, Activations: []*LCRActivation{ &LCRActivation{ ActivationTime: time.Date(2015, 01, 01, 8, 0, 0, 0, time.UTC), Entries: []*LCREntry{ &LCREntry{DestinationId: utils.ANY, RPCategory: "call", Strategy: LCR_STRATEGY_STATIC, StrategyParams: "ivo12;dan12;rif12", Weight: 10.0}}, }, }, } lcrLowestCost := &LCR{Direction: utils.OUT, Tenant: "tenant12", Category: "call_least_cost", Account: utils.ANY, Subject: utils.ANY, Activations: []*LCRActivation{ &LCRActivation{ ActivationTime: time.Date(2015, 01, 01, 8, 0, 0, 0, time.UTC), Entries: []*LCREntry{ &LCREntry{DestinationId: utils.ANY, RPCategory: "call", Strategy: LCR_STRATEGY_LOWEST, Weight: 10.0}}, }, }, } lcrQosThreshold := &LCR{Direction: utils.OUT, Tenant: "tenant12", Category: "call_qos_threshold", Account: utils.ANY, Subject: utils.ANY, Activations: []*LCRActivation{ &LCRActivation{ ActivationTime: time.Date(2015, 01, 01, 8, 0, 0, 0, time.UTC), Entries: []*LCREntry{ &LCREntry{DestinationId: utils.ANY, RPCategory: "call", Strategy: LCR_STRATEGY_QOS_THRESHOLD, StrategyParams: "35;;;;4m;;;;;;;;;", Weight: 10.0}}, }, }, } lcrQos := &LCR{Direction: utils.OUT, Tenant: "tenant12", Category: "call_qos", Account: utils.ANY, Subject: utils.ANY, Activations: []*LCRActivation{ &LCRActivation{ ActivationTime: time.Date(2015, 01, 01, 8, 0, 0, 0, time.UTC), Entries: []*LCREntry{ &LCREntry{DestinationId: utils.ANY, RPCategory: "call", Strategy: LCR_STRATEGY_QOS, Weight: 10.0}}, }, }, } lcrLoad := &LCR{Direction: utils.OUT, Tenant: "tenant12", Category: "call_load", Account: utils.ANY, Subject: utils.ANY, Activations: []*LCRActivation{ &LCRActivation{ ActivationTime: time.Date(2015, 01, 01, 8, 0, 0, 0, time.UTC), Entries: []*LCREntry{ &LCREntry{DestinationId: utils.ANY, RPCategory: "call", Strategy: LCR_STRATEGY_LOAD, StrategyParams: "ivo12:10;dan12:3", Weight: 10.0}}, }, }, } for _, lcr := range []*LCR{lcrStatic, lcrLowestCost, lcrQosThreshold, lcrQos, lcrLoad} { if err := ratingStorage.SetLCR(lcr, utils.NonTransactional); err != nil { t.Error(err) } } cdStatic := &CallDescriptor{ TimeStart: time.Date(2015, 04, 06, 17, 40, 0, 0, time.UTC), TimeEnd: time.Date(2015, 04, 06, 17, 41, 0, 0, time.UTC), Tenant: "tenant12", Direction: utils.OUT, Category: "call_static", Destination: "+4986517174963", Account: "dan", Subject: "dan", } eStLcr := &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}, }, } var lcr LCRCost if err := rsponder.GetLCR(&AttrGetLcr{CallDescriptor: cdStatic}, &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: %s, received: %s", utils.ToJSON(eStLcr.SupplierCosts), utils.ToJSON(lcr.SupplierCosts)) } // Test *least_cost strategy here cdLowestCost := &CallDescriptor{ TimeStart: time.Date(2015, 04, 06, 17, 40, 0, 0, time.UTC), TimeEnd: time.Date(2015, 04, 06, 17, 41, 0, 0, time.UTC), Tenant: "tenant12", Direction: utils.OUT, Category: "call_least_cost", Destination: "+4986517174963", Account: "dan", Subject: "dan", } eLcLcr := &LCRCost{ Entry: &LCREntry{DestinationId: utils.ANY, RPCategory: "call", Strategy: LCR_STRATEGY_LOWEST, Weight: 10.0}, SupplierCosts: []*LCRSupplierCost{ &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}, &LCRSupplierCost{Supplier: "*out:tenant12:call:ivo12", Cost: 1.8, Duration: 60 * time.Second}, }, } var lcrLc LCRCost if err := rsponder.GetLCR(&AttrGetLcr{CallDescriptor: cdLowestCost}, &lcrLc); err != nil { t.Error(err) } else if !reflect.DeepEqual(eLcLcr.Entry, lcrLc.Entry) { t.Errorf("Expecting: %+v, received: %+v", eLcLcr.Entry, lcrLc.Entry) } else if !reflect.DeepEqual(eLcLcr.SupplierCosts, lcrLc.SupplierCosts) { t.Errorf("Expecting: %+v, received: %+v", eLcLcr.SupplierCosts, lcrLc.SupplierCosts) } bRif12 := &Balance{Value: 40, Weight: 10, DestinationIDs: utils.NewStringMap(dstDe.Id)} bIvo12 := &Balance{Value: 60, Weight: 10, DestinationIDs: utils.NewStringMap(dstDe.Id)} rif12sAccount := &Account{ID: utils.ConcatenatedKey("tenant12", "rif12"), BalanceMap: map[string]Balances{utils.VOICE: Balances{bRif12}}, AllowNegative: true} ivo12sAccount := &Account{ID: utils.ConcatenatedKey("tenant12", "ivo12"), BalanceMap: map[string]Balances{utils.VOICE: Balances{bIvo12}}, AllowNegative: true} for _, acnt := range []*Account{rif12sAccount, ivo12sAccount} { if err := accountingStorage.SetAccount(acnt); err != nil { t.Error(err) } } eLcLcr = &LCRCost{ Entry: &LCREntry{DestinationId: utils.ANY, RPCategory: "call", Strategy: LCR_STRATEGY_LOWEST, Weight: 10.0}, SupplierCosts: []*LCRSupplierCost{ &LCRSupplierCost{Supplier: "*out:tenant12:call:ivo12", Cost: 0, Duration: 60 * time.Second}, &LCRSupplierCost{Supplier: "*out:tenant12:call:rif12", Cost: 0.4, Duration: 60 * time.Second}, &LCRSupplierCost{Supplier: "*out:tenant12:call:dan12", Cost: 0.6, Duration: 60 * time.Second}, }, } if err := rsponder.GetLCR(&AttrGetLcr{CallDescriptor: cdLowestCost}, &lcrLc); err != nil { t.Error(err) } else if !reflect.DeepEqual(eLcLcr.Entry, lcrLc.Entry) { t.Errorf("Expecting: %+v, received: %+v", eLcLcr.Entry, lcrLc.Entry) } else if !reflect.DeepEqual(eLcLcr.SupplierCosts, lcrLc.SupplierCosts) { t.Errorf("Expecting: %+v, received: %+v", eLcLcr.SupplierCosts, lcrLc.SupplierCosts) } // Test *qos_threshold strategy here cdQosThreshold := &CallDescriptor{ TimeStart: time.Date(2015, 04, 06, 17, 40, 0, 0, time.UTC), TimeEnd: time.Date(2015, 04, 06, 17, 41, 0, 0, time.UTC), Tenant: "tenant12", Direction: utils.OUT, Category: "call_qos_threshold", Destination: "+4986517174963", Account: "dan", Subject: "dan", } eQTLcr := &LCRCost{ Entry: &LCREntry{DestinationId: utils.ANY, RPCategory: "call", Strategy: LCR_STRATEGY_QOS_THRESHOLD, StrategyParams: "35;;;;4m;;;;;;;;;", Weight: 10.0}, SupplierCosts: []*LCRSupplierCost{ &LCRSupplierCost{Supplier: "*out:tenant12:call:ivo12", Cost: 0, Duration: 60 * time.Second, QOS: map[string]float64{TCD: -1, ACC: -1, TCC: -1, ASR: -1, ACD: -1, PDD: -1, DDC: -1}, qosSortParams: []string{"35", "4m"}}, &LCRSupplierCost{Supplier: "*out:tenant12:call:rif12", Cost: 0.4, Duration: 60 * time.Second, QOS: map[string]float64{TCD: -1, ACC: -1, TCC: -1, ASR: -1, ACD: -1, PDD: -1, DDC: -1}, qosSortParams: []string{"35", "4m"}}, &LCRSupplierCost{Supplier: "*out:tenant12:call:dan12", Cost: 0.6, Duration: 60 * time.Second, QOS: map[string]float64{TCD: -1, ACC: -1, TCC: -1, ASR: -1, ACD: -1, PDD: -1, DDC: -1}, qosSortParams: []string{"35", "4m"}}, }, } var lcrQT LCRCost if err := rsponder.GetLCR(&AttrGetLcr{CallDescriptor: cdQosThreshold}, &lcrQT); err != nil { t.Error(err) } else if !reflect.DeepEqual(eQTLcr.Entry, lcrQT.Entry) { t.Errorf("Expecting: %+v, received: %+v", eQTLcr.Entry, lcrQT.Entry) } else if !reflect.DeepEqual(eQTLcr.SupplierCosts, lcrQT.SupplierCosts) { t.Errorf("Expecting: %+v, received: %+v", eQTLcr.SupplierCosts, lcrQT.SupplierCosts) } cdr := &CDR{Supplier: "rif12", AnswerTime: time.Now(), Usage: 3 * time.Minute, Cost: 1} rsponder.Stats.Call("CDRStatsV1.AppendCDR", cdr, &r) cdr = &CDR{Supplier: "dan12", AnswerTime: time.Now(), Usage: 5 * time.Minute, Cost: 2} rsponder.Stats.Call("CDRStatsV1.AppendCDR", cdr, &r) eQTLcr = &LCRCost{ Entry: &LCREntry{DestinationId: utils.ANY, RPCategory: "call", Strategy: LCR_STRATEGY_QOS_THRESHOLD, StrategyParams: "35;;;;4m;;;;;;;;;", Weight: 10.0}, SupplierCosts: []*LCRSupplierCost{ &LCRSupplierCost{Supplier: "*out:tenant12:call:ivo12", Cost: 0, Duration: 60 * time.Second, QOS: map[string]float64{PDD: -1, TCD: -1, ACC: -1, TCC: -1, ASR: -1, ACD: -1, DDC: -1}, qosSortParams: []string{"35", "4m"}}, &LCRSupplierCost{Supplier: "*out:tenant12:call:dan12", Cost: 0.6, Duration: 60 * time.Second, QOS: map[string]float64{PDD: -1, ACD: 300, TCD: 300, ASR: 100, ACC: 2, TCC: 2, DDC: 2}, qosSortParams: []string{"35", "4m"}}, }, } if err := rsponder.GetLCR(&AttrGetLcr{CallDescriptor: cdQosThreshold}, &lcrQT); err != nil { t.Error(err) } else if !reflect.DeepEqual(eQTLcr.Entry, lcrQT.Entry) { t.Errorf("Expecting: %+v, received: %+v", eQTLcr.Entry, lcrQT.Entry) } else if !reflect.DeepEqual(eQTLcr.SupplierCosts, lcrQT.SupplierCosts) { t.Errorf("Expecting: %+v, received: %+v", eQTLcr.SupplierCosts, lcrQT.SupplierCosts) } // Test *qos strategy here cdQos := &CallDescriptor{ TimeStart: time.Date(2015, 04, 06, 17, 40, 0, 0, time.UTC), TimeEnd: time.Date(2015, 04, 06, 17, 41, 0, 0, time.UTC), Tenant: "tenant12", Direction: utils.OUT, Category: "call_qos", Destination: "+4986517174963", Account: "dan", Subject: "dan", } eQosLcr := &LCRCost{ Entry: &LCREntry{DestinationId: utils.ANY, RPCategory: "call", Strategy: LCR_STRATEGY_QOS, Weight: 10.0}, SupplierCosts: []*LCRSupplierCost{ &LCRSupplierCost{Supplier: "*out:tenant12:call:ivo12", Cost: 0, Duration: 60 * time.Second, QOS: map[string]float64{ACD: -1, PDD: -1, TCD: -1, ASR: -1, ACC: -1, TCC: -1, DDC: -1}, qosSortParams: []string{ASR, PDD, ACD, TCD, ACC, TCC, DDC}}, &LCRSupplierCost{Supplier: "*out:tenant12:call:dan12", Cost: 0.6, Duration: 60 * time.Second, QOS: map[string]float64{ACD: 300, PDD: -1, TCD: 300, ASR: 100, ACC: 2, TCC: 2, DDC: 2}, qosSortParams: []string{ASR, PDD, ACD, TCD, ACC, TCC, DDC}}, &LCRSupplierCost{Supplier: "*out:tenant12:call:rif12", Cost: 0.4, Duration: 60 * time.Second, QOS: map[string]float64{ACD: 180, PDD: -1, TCD: 180, ASR: 100, ACC: 1, TCC: 1, DDC: 1}, qosSortParams: []string{ASR, PDD, ACD, TCD, ACC, TCC, DDC}}, }, } var lcrQ LCRCost if err := rsponder.GetLCR(&AttrGetLcr{CallDescriptor: cdQos}, &lcrQ); err != nil { t.Error(err) } else if !reflect.DeepEqual(eQosLcr.Entry, lcrQ.Entry) { t.Errorf("Expecting: %+v, received: %+v", eQosLcr.Entry, lcrQ.Entry) } else if !reflect.DeepEqual(eQosLcr.SupplierCosts, lcrQ.SupplierCosts) { t.Errorf("Expecting: %+v, received: %+v", eQosLcr.SupplierCosts, lcrQ.SupplierCosts) } }