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 (dbr *DbReader) LoadRatingProfileFiltered(qriedRpf *utils.TPRatingProfile) error { var resultRatingProfile *RatingProfile mpTpRpfs, err := dbr.storDb.GetTpRatingProfiles(qriedRpf) //map[string]*utils.TPRatingProfile if err != nil { return fmt.Errorf("No RateProfile for filter %v, error: %v", qriedRpf, err) } for _, tpRpf := range mpTpRpfs { // Logger.Debug(fmt.Sprintf("Rating profile: %v", tpRpf)) resultRatingProfile = &RatingProfile{Id: tpRpf.KeyId()} for _, tpRa := range tpRpf.RatingPlanActivations { at, err := utils.ParseDate(tpRa.ActivationTime) if err != nil { return fmt.Errorf("Cannot parse activation time from %v", tpRa.ActivationTime) } _, exists := dbr.ratingPlans[tpRa.RatingPlanId] if !exists { if dbExists, err := dbr.dataDb.HasData(RATING_PLAN_PREFIX, tpRa.RatingPlanId); err != nil { return err } else if !dbExists { return fmt.Errorf("Could not load rating plans for tag: %v", tpRa.RatingPlanId) } } resultRatingProfile.RatingPlanActivations = append(resultRatingProfile.RatingPlanActivations, &RatingPlanActivation{at, tpRa.RatingPlanId, utils.FallbackSubjKeys(tpRpf.Direction, tpRpf.Tenant, tpRpf.Category, tpRa.FallbackSubjects)}) } if err := dbr.dataDb.SetRatingProfile(resultRatingProfile); err != nil { return err } } return nil }
func (at *ActionPlan) Execute() (err error) { if len(at.AccountIds) == 0 { // nothing to do if no accounts set return } at.resetStartTimeCache() aac, err := at.getActions() if err != nil { utils.Logger.Err(fmt.Sprintf("Failed to get actions for %s: %s", at.ActionsId, err)) return } for _, a := range aac { if expDate, parseErr := utils.ParseDate(a.ExpirationString); (a.Balance == nil || a.Balance.ExpirationDate.IsZero()) && parseErr == nil && !expDate.IsZero() { a.Balance.ExpirationDate = expDate } // handle remove action if a.ActionType == REMOVE_ACCOUNT { for _, accId := range at.AccountIds { _, err := Guardian.Guard(func() (interface{}, error) { if err := accountingStorage.RemoveAccount(accId); err != nil { utils.Logger.Warning(fmt.Sprintf("Could not remove account Id: %s: %d", accId, err)) } return 0, nil }, 0, accId) if err != nil { utils.Logger.Warning(fmt.Sprintf("Error executing action plan: %v", err)) } } continue // do not go to getActionFunc } actionFunction, exists := getActionFunc(a.ActionType) if !exists { // do not allow the action plan to be rescheduled at.Timing = nil utils.Logger.Crit(fmt.Sprintf("Function type %v not available, aborting execution!", a.ActionType)) return } for _, accId := range at.AccountIds { _, err := Guardian.Guard(func() (interface{}, error) { ub, err := accountingStorage.GetAccount(accId) if err != nil { utils.Logger.Warning(fmt.Sprintf("Could not get user balances for this id: %s. Skipping!", 0, accId)) return 0, err } else if ub.Disabled && a.ActionType != ENABLE_ACCOUNT { return 0, fmt.Errorf("Account %s is disabled", accId) } //utils.Logger.Info(fmt.Sprintf("Executing %v on %+v", a.ActionType, ub)) err = actionFunction(ub, nil, a, aac) //utils.Logger.Info(fmt.Sprintf("After execute, account: %+v", ub)) accountingStorage.SetAccount(ub) return 0, nil }, 0, accId) if err != nil { utils.Logger.Warning(fmt.Sprintf("Error executing action plan: %v", err)) } } } storageLogger.LogActionPlan(utils.SCHED_SOURCE, at, aac) return }
func (csvr *CSVReader) LoadActions() (err error) { csvReader, fp, err := csvr.readerFunc(csvr.actionsFn, csvr.sep, utils.ACTIONS_NRCOLS) if err != nil { log.Print("Could not load action file: ", err) // allow writing of the other values return nil } if fp != nil { defer fp.Close() } for record, err := csvReader.Read(); err == nil; record, err = csvReader.Read() { tag := record[0] var units float64 if len(record[4]) == 0 { // Not defined units = 0.0 } else { units, err = strconv.ParseFloat(record[4], 64) if err != nil { return fmt.Errorf("Could not parse action units: %v", err) } } var balanceWeight float64 if len(record[9]) == 0 { // Not defined balanceWeight = 0.0 } else { balanceWeight, err = strconv.ParseFloat(record[9], 64) if err != nil { return fmt.Errorf("Could not parse action balance weight: %v", err) } } weight, err := strconv.ParseFloat(record[12], 64) if err != nil { return fmt.Errorf("Could not parse action weight: %v", err) } a := &Action{ Id: utils.GenUUID(), ActionType: record[1], BalanceType: record[2], Direction: record[3], Weight: weight, ExpirationString: record[5], ExtraParameters: record[11], Balance: &Balance{ Uuid: utils.GenUUID(), Value: units, Weight: balanceWeight, DestinationId: record[6], RatingSubject: record[7], Category: record[8], SharedGroup: record[10], }, } if _, err := utils.ParseDate(a.ExpirationString); err != nil { return fmt.Errorf("Could not parse expiration time: %v", err) } csvr.actions[tag] = append(csvr.actions[tag], a) } return }
func (self *TPCSVImporter) importRatingProfiles(fn string) error { if self.Verbose { log.Printf("Processing file: <%s> ", fn) } fParser, err := NewTPCSVFileParser(self.DirPath, fn) if err != nil { return err } lineNr := 0 rpfs := make(map[string]*utils.TPRatingProfile) for { lineNr++ record, err := fParser.ParseNextLine() if err == io.EOF { // Reached end of file break } else if err != nil { if self.Verbose { log.Printf("Ignoring line %d, warning: <%s> ", lineNr, err.Error()) } continue } direction, tenant, tor, subject, ratingPlanTag, fallbacksubject := record[0], record[1], record[2], record[3], record[5], record[6] _, err = utils.ParseDate(record[4]) if err != nil { if self.Verbose { log.Printf("Ignoring line %d, warning: <%s> ", lineNr, err.Error()) } continue } loadId := utils.CSV_LOAD //Autogenerate rating profile id if self.ImportId != "" { loadId += "_" + self.ImportId } newRp := &utils.TPRatingProfile{ TPid: self.TPid, LoadId: loadId, Tenant: tenant, Category: tor, Direction: direction, Subject: subject, RatingPlanActivations: []*utils.TPRatingActivation{ &utils.TPRatingActivation{ActivationTime: record[4], RatingPlanId: ratingPlanTag, FallbackSubjects: fallbacksubject}}, } if rp, hasIt := rpfs[newRp.KeyId()]; hasIt { rp.RatingPlanActivations = append(rp.RatingPlanActivations, newRp.RatingPlanActivations...) } else { rpfs[newRp.KeyId()] = newRp } } if err := self.StorDb.SetTPRatingProfiles(self.TPid, rpfs); err != nil { if self.Verbose { log.Printf("Ignoring line %d, storDb operational error: <%s> ", lineNr, err.Error()) } } return nil }
func (self *ApierV1) AddBalance(attr *AttrAddBalance, reply *string) error { expTime, err := utils.ParseDate(attr.ExpiryTime) if err != nil { *reply = err.Error() return err } tag := utils.ConcatenatedKey(attr.Direction, attr.Tenant, attr.Account) if _, err := self.AccountDb.GetAccount(tag); err != nil { // create user balance if not exists account := &engine.Account{ Id: tag, } if err := self.AccountDb.SetAccount(account); err != nil { *reply = err.Error() return err } } at := &engine.ActionPlan{ AccountIds: []string{tag}, } if attr.Direction == "" { attr.Direction = engine.OUTBOUND } aType := engine.DEBIT // reverse the sign as it is a debit attr.Value = -attr.Value if attr.Overwrite { aType = engine.DEBIT_RESET } at.SetActions(engine.Actions{ &engine.Action{ ActionType: aType, BalanceType: attr.BalanceType, Direction: attr.Direction, Balance: &engine.Balance{ Uuid: attr.BalanceUuid, Id: attr.BalanceId, Value: attr.Value, ExpirationDate: expTime, RatingSubject: attr.RatingSubject, DestinationIds: attr.DestinationId, Weight: attr.Weight, SharedGroup: attr.SharedGroup, Disabled: attr.Disabled, }, }, }) if err := at.Execute(); err != nil { *reply = err.Error() return err } *reply = OK return nil }
func (csvr *CSVReader) LoadRatingProfiles() (err error) { csvReader, fp, err := csvr.readerFunc(csvr.ratingprofilesFn, csvr.sep, utils.RATE_PROFILES_NRCOLS) if err != nil { log.Print("Could not load rating profiles file: ", err) // allow writing of the other values return nil } if fp != nil { defer fp.Close() } for record, err := csvReader.Read(); err == nil; record, err = csvReader.Read() { direction, tenant, tor, subject, fallbacksubject := record[0], record[1], record[2], record[3], record[6] at, err := utils.ParseDate(record[4]) if err != nil { return fmt.Errorf("Cannot parse activation time from %v", record[4]) } // extract aliases from subject aliases := strings.Split(subject, ";") csvr.dirtyRpAliases = append(csvr.dirtyRpAliases, &TenantRatingSubject{Tenant: tenant, Subject: aliases[0]}) if len(aliases) > 1 { subject = aliases[0] for _, alias := range aliases[1:] { csvr.rpAliases[utils.RatingSubjectAliasKey(tenant, alias)] = subject } } key := fmt.Sprintf("%s:%s:%s:%s", direction, tenant, tor, subject) rp, ok := csvr.ratingProfiles[key] if !ok { rp = &RatingProfile{Id: key} csvr.ratingProfiles[key] = rp } _, exists := csvr.ratingPlans[record[5]] if !exists && csvr.dataStorage != nil { if exists, err = csvr.dataStorage.HasData(RATING_PLAN_PREFIX, record[5]); err != nil { return err } } if !exists { return fmt.Errorf("Could not load rating plans for tag: %v", record[5]) } rpa := &RatingPlanActivation{ ActivationTime: at, RatingPlanId: record[5], FallbackKeys: utils.FallbackSubjKeys(direction, tenant, tor, fallbacksubject), } rp.RatingPlanActivations = append(rp.RatingPlanActivations, rpa) csvr.ratingProfiles[rp.Id] = rp } return }
// Sets a specific rating profile working with data directly in the RatingDb without involving storDb func (self *ApierV1) SetRatingProfile(attrs AttrSetRatingProfile, reply *string) error { if missing := utils.MissingStructFields(&attrs, []string{"Tenant", "TOR", "Direction", "Subject", "RatingPlanActivations"}); len(missing) != 0 { return utils.NewErrMandatoryIeMissing(missing...) } for _, rpa := range attrs.RatingPlanActivations { if missing := utils.MissingStructFields(rpa, []string{"ActivationTime", "RatingPlanId"}); len(missing) != 0 { return fmt.Errorf("%s:RatingPlanActivation:%v", utils.ErrMandatoryIeMissing.Error(), missing) } } tpRpf := utils.TPRatingProfile{Tenant: attrs.Tenant, Category: attrs.Category, Direction: attrs.Direction, Subject: attrs.Subject} keyId := tpRpf.KeyId() if !attrs.Overwrite { if exists, err := self.RatingDb.HasData(utils.RATING_PROFILE_PREFIX, keyId); err != nil { return utils.NewErrServerError(err) } else if exists { return utils.ErrExists } } rpfl := &engine.RatingProfile{Id: keyId, RatingPlanActivations: make(engine.RatingPlanActivations, len(attrs.RatingPlanActivations))} for idx, ra := range attrs.RatingPlanActivations { at, err := utils.ParseDate(ra.ActivationTime) if err != nil { return fmt.Errorf(fmt.Sprintf("%s:Cannot parse activation time from %v", utils.ErrServerError.Error(), ra.ActivationTime)) } if exists, err := self.RatingDb.HasData(utils.RATING_PLAN_PREFIX, ra.RatingPlanId); err != nil { return utils.NewErrServerError(err) } else if !exists { return fmt.Errorf(fmt.Sprintf("%s:RatingPlanId:%s", utils.ErrNotFound.Error(), ra.RatingPlanId)) } rpfl.RatingPlanActivations[idx] = &engine.RatingPlanActivation{ActivationTime: at, RatingPlanId: ra.RatingPlanId, FallbackKeys: utils.FallbackSubjKeys(tpRpf.Direction, tpRpf.Tenant, tpRpf.Category, ra.FallbackSubjects)} } if err := self.RatingDb.SetRatingProfile(rpfl); err != nil { return utils.NewErrServerError(err) } //Automatic cache of the newly inserted rating profile if err := self.RatingDb.CacheRatingPrefixValues(map[string][]string{ utils.RATING_PROFILE_PREFIX: []string{utils.RATING_PROFILE_PREFIX + keyId}, }); err != nil { return err } *reply = OK return nil }
func (at *ActionTrigger) Execute(ub *Account, sq *StatsQueueTriggered) (err error) { // check for min sleep time if at.Recurrent && !at.lastExecutionTime.IsZero() && time.Since(at.lastExecutionTime) < at.MinSleep { return } at.lastExecutionTime = time.Now() if ub != nil && ub.Disabled { return fmt.Errorf("User %s is disabled and there are triggers in action!", ub.Id) } // does NOT need to Lock() because it is triggered from a method that took the Lock var aac Actions aac, err = accountingStorage.GetActions(at.ActionsId, false) aac.Sort() if err != nil { Logger.Err(fmt.Sprintf("Failed to get actions: %v", err)) return } at.Executed = true atLeastOneActionExecuted := false for _, a := range aac { if a.Balance == nil { a.Balance = &Balance{} } a.Balance.ExpirationDate, _ = utils.ParseDate(a.ExpirationString) actionFunction, exists := getActionFunc(a.ActionType) if !exists { Logger.Warning(fmt.Sprintf("Function type %v not available, aborting execution!", a.ActionType)) return } //go Logger.Info(fmt.Sprintf("Executing %v, %v: %v", ub, sq, a)) err = actionFunction(ub, sq, a) if err == nil { atLeastOneActionExecuted = true } } if !atLeastOneActionExecuted || at.Recurrent { at.Executed = false } if ub != nil { storageLogger.LogActionTrigger(ub.Id, RATER_SOURCE, at, aac) accountingStorage.SetAccount(ub) } return }
// Sets a specific rating profile working with data directly in the RatingDb without involving storDb func (self *ApierV1) SetRatingProfile(attrs AttrSetRatingProfile, reply *string) error { if missing := utils.MissingStructFields(&attrs, []string{"Tenant", "TOR", "Direction", "Subject", "RatingPlanActivations"}); len(missing) != 0 { return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing) } for _, rpa := range attrs.RatingPlanActivations { if missing := utils.MissingStructFields(rpa, []string{"ActivationTime", "RatingPlanId"}); len(missing) != 0 { return fmt.Errorf("%s:RatingPlanActivation:%v", utils.ERR_MANDATORY_IE_MISSING, missing) } } tpRpf := utils.TPRatingProfile{Tenant: attrs.Tenant, Category: attrs.Category, Direction: attrs.Direction, Subject: attrs.Subject} keyId := tpRpf.KeyId() if !attrs.Overwrite { if exists, err := self.RatingDb.HasData(engine.RATING_PROFILE_PREFIX, keyId); err != nil { return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error()) } else if exists { return errors.New(utils.ERR_EXISTS) } } rpfl := &engine.RatingProfile{Id: keyId, RatingPlanActivations: make(engine.RatingPlanActivations, len(attrs.RatingPlanActivations))} for idx, ra := range attrs.RatingPlanActivations { at, err := utils.ParseDate(ra.ActivationTime) if err != nil { return fmt.Errorf(fmt.Sprintf("%s:Cannot parse activation time from %v", utils.ERR_SERVER_ERROR, ra.ActivationTime)) } if exists, err := self.RatingDb.HasData(engine.RATING_PLAN_PREFIX, ra.RatingPlanId); err != nil { return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error()) } else if !exists { return fmt.Errorf(fmt.Sprintf("%s:RatingPlanId:%s", utils.ERR_NOT_FOUND, ra.RatingPlanId)) } rpfl.RatingPlanActivations[idx] = &engine.RatingPlanActivation{ActivationTime: at, RatingPlanId: ra.RatingPlanId, FallbackKeys: utils.FallbackSubjKeys(tpRpf.Direction, tpRpf.Tenant, tpRpf.Category, ra.FallbackSubjects)} } if err := self.RatingDb.SetRatingProfile(rpfl); err != nil { return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error()) } //Automatic cache of the newly inserted rating profile didNotChange := []string{} if err := self.RatingDb.CacheRating(didNotChange, didNotChange, []string{engine.RATING_PROFILE_PREFIX + keyId}, didNotChange, didNotChange); err != nil { return err } *reply = OK return nil }
func (tpr *TpReader) LoadRatingProfilesFiltered(qriedRpf *TpRatingProfile) error { var resultRatingProfile *RatingProfile mpTpRpfs, err := tpr.lr.GetTpRatingProfiles(qriedRpf) if err != nil { return fmt.Errorf("no RateProfile for filter %v, error: %v", qriedRpf, err) } rpfs, err := TpRatingProfiles(mpTpRpfs).GetRatingProfiles() if err != nil { return err } for _, tpRpf := range rpfs { resultRatingProfile = &RatingProfile{Id: tpRpf.KeyId()} for _, tpRa := range tpRpf.RatingPlanActivations { at, err := utils.ParseDate(tpRa.ActivationTime) if err != nil { return fmt.Errorf("cannot parse activation time from %v", tpRa.ActivationTime) } _, exists := tpr.ratingPlans[tpRa.RatingPlanId] if !exists && tpr.ratingStorage != nil { if exists, err = tpr.ratingStorage.HasData(utils.RATING_PLAN_PREFIX, tpRa.RatingPlanId); err != nil { return err } } if !exists { return fmt.Errorf("could not load rating plans for tag: %v", tpRa.RatingPlanId) } resultRatingProfile.RatingPlanActivations = append(resultRatingProfile.RatingPlanActivations, &RatingPlanActivation{ ActivationTime: at, RatingPlanId: tpRa.RatingPlanId, FallbackKeys: utils.FallbackSubjKeys(tpRpf.Direction, tpRpf.Tenant, tpRpf.Category, tpRa.FallbackSubjects), CdrStatQueueIds: strings.Split(tpRa.CdrStatQueueIds, utils.INFIELD_SEP), }) } if err := tpr.ratingStorage.SetRatingProfile(resultRatingProfile); err != nil { return err } } return nil }
func (at *ActionTiming) Execute() (err error) { if len(at.AccountIds) == 0 { // nothing to do if no accounts set return } at.resetStartTimeCache() aac, err := at.getActions() if err != nil { Logger.Err(fmt.Sprintf("Failed to get actions for %s: %s", at.ActionsId, err)) return } for _, a := range aac { if expDate, parseErr := utils.ParseDate(a.ExpirationString); a.Balance.ExpirationDate.IsZero() && parseErr == nil && !expDate.IsZero() { a.Balance.ExpirationDate = expDate } actionFunction, exists := getActionFunc(a.ActionType) if !exists { Logger.Crit(fmt.Sprintf("Function type %v not available, aborting execution!", a.ActionType)) return } for _, ubId := range at.AccountIds { _, err := AccLock.Guard(ubId, func() (float64, error) { ub, err := accountingStorage.GetAccount(ubId) if err != nil { Logger.Warning(fmt.Sprintf("Could not get user balances for this id: %s. Skipping!", ubId)) return 0, err } else if ub.Disabled { return 0, fmt.Errorf("User %s is disabled", ubId) } //Logger.Info(fmt.Sprintf("Executing %v on %v", a.ActionType, ub.Id)) err = actionFunction(ub, nil, a) accountingStorage.SetAccount(ub) return 0, nil }) if err != nil { Logger.Warning(fmt.Sprintf("Error executing action timing: %v", err)) } } } storageLogger.LogActionTiming(SCHED_SOURCE, at, aac) return }
func (self *ApierV1) EnableDisableBalance(attr *AttrAddBalance, reply *string) error { expTime, err := utils.ParseDate(attr.ExpiryTime) if err != nil { *reply = err.Error() return err } tag := utils.ConcatenatedKey(attr.Direction, attr.Tenant, attr.Account) if _, err := self.AccountDb.GetAccount(tag); err != nil { return utils.ErrNotFound } at := &engine.ActionPlan{ AccountIds: []string{tag}, } if attr.Direction == "" { attr.Direction = utils.OUT } at.SetActions(engine.Actions{ &engine.Action{ ActionType: engine.ENABLE_DISABLE_BALANCE, BalanceType: attr.BalanceType, Direction: attr.Direction, Balance: &engine.Balance{ Uuid: attr.BalanceUuid, Id: attr.BalanceId, Value: attr.Value, ExpirationDate: expTime, RatingSubject: attr.RatingSubject, DestinationIds: attr.DestinationId, Weight: attr.Weight, SharedGroup: attr.SharedGroup, Disabled: attr.Disabled, }, }, }) if err := at.Execute(); err != nil { *reply = err.Error() return err } *reply = OK return nil }
func (dbr *DbReader) LoadRatingProfiles() error { mpTpRpfs, err := dbr.storDb.GetTpRatingProfiles(&utils.TPRatingProfile{TPid: dbr.tpid}) //map[string]*utils.TPRatingProfile if err != nil { return err } for _, tpRpf := range mpTpRpfs { // extract aliases from subject aliases := strings.Split(tpRpf.Subject, ";") dbr.dirtyRpAliases = append(dbr.dirtyRpAliases, &TenantRatingSubject{Tenant: tpRpf.Tenant, Subject: aliases[0]}) if len(aliases) > 1 { tpRpf.Subject = aliases[0] for _, alias := range aliases[1:] { dbr.rpAliases[utils.RatingSubjectAliasKey(tpRpf.Tenant, alias)] = tpRpf.Subject } } rpf := &RatingProfile{Id: tpRpf.KeyId()} for _, tpRa := range tpRpf.RatingPlanActivations { at, err := utils.ParseDate(tpRa.ActivationTime) if err != nil { return fmt.Errorf("Cannot parse activation time from %v", tpRa.ActivationTime) } _, exists := dbr.ratingPlans[tpRa.RatingPlanId] if !exists { if dbExists, err := dbr.dataDb.HasData(RATING_PLAN_PREFIX, tpRa.RatingPlanId); err != nil { return err } else if !dbExists { return fmt.Errorf("Could not load rating plans for tag: %v", tpRa.RatingPlanId) } } rpf.RatingPlanActivations = append(rpf.RatingPlanActivations, &RatingPlanActivation{ ActivationTime: at, RatingPlanId: tpRa.RatingPlanId, FallbackKeys: utils.FallbackSubjKeys(tpRpf.Direction, tpRpf.Tenant, tpRpf.Category, tpRa.FallbackSubjects), }) } dbr.ratingProfiles[tpRpf.KeyId()] = rpf } return nil }
func (tpr *TpReader) LoadRatingProfiles() (err error) { tps, err := tpr.lr.GetTpRatingProfiles(&TpRatingProfile{Tpid: tpr.tpid}) if err != nil { return err } mpTpRpfs, err := TpRatingProfiles(tps).GetRatingProfiles() if err != nil { return err } for _, tpRpf := range mpTpRpfs { rpf := &RatingProfile{Id: tpRpf.KeyId()} for _, tpRa := range tpRpf.RatingPlanActivations { at, err := utils.ParseDate(tpRa.ActivationTime) if err != nil { return fmt.Errorf("cannot parse activation time from %v", tpRa.ActivationTime) } _, exists := tpr.ratingPlans[tpRa.RatingPlanId] if !exists && tpr.ratingStorage != nil { // Only query if there is a connection, eg on dry run there is none if exists, err = tpr.ratingStorage.HasData(utils.RATING_PLAN_PREFIX, tpRa.RatingPlanId); err != nil { return err } } if !exists { return fmt.Errorf("could not load rating plans for tag: %v", tpRa.RatingPlanId) } rpf.RatingPlanActivations = append(rpf.RatingPlanActivations, &RatingPlanActivation{ ActivationTime: at, RatingPlanId: tpRa.RatingPlanId, FallbackKeys: utils.FallbackSubjKeys(tpRpf.Direction, tpRpf.Tenant, tpRpf.Category, tpRa.FallbackSubjects), CdrStatQueueIds: strings.Split(tpRa.CdrStatQueueIds, utils.INFIELD_SEP), }) } tpr.ratingProfiles[tpRpf.KeyId()] = rpf } return nil }
func (self *ApierV1) RemoveBalances(attr *AttrAddBalance, reply *string) error { expTime, err := utils.ParseDate(attr.ExpiryTime) if err != nil { *reply = err.Error() return err } accId := utils.ConcatenatedKey(attr.Tenant, attr.Account) if _, err := self.AccountDb.GetAccount(accId); err != nil { return utils.ErrNotFound } at := &engine.ActionPlan{ AccountIds: []string{accId}, } at.SetActions(engine.Actions{ &engine.Action{ ActionType: engine.REMOVE_BALANCE, BalanceType: attr.BalanceType, Balance: &engine.Balance{ Uuid: attr.BalanceUuid, Id: attr.BalanceId, Value: attr.Value, ExpirationDate: expTime, RatingSubject: attr.RatingSubject, Directions: utils.ParseStringMap(attr.Directions), DestinationIds: utils.ParseStringMap(attr.DestinationIds), Weight: attr.Weight, SharedGroups: utils.ParseStringMap(attr.SharedGroups), Disabled: attr.Disabled, }, }, }) if err := at.Execute(); err != nil { *reply = err.Error() return err } *reply = OK return nil }
func (tpr *TpReader) LoadAccountActionsFiltered(qriedAA *TpAccountAction) error { accountActions, err := tpr.lr.GetTpAccountActions(qriedAA) if err != nil { return errors.New(err.Error() + ": " + fmt.Sprintf("%+v", qriedAA)) } storAas, err := TpAccountActions(accountActions).GetAccountActions() if err != nil { return err } for _, accountAction := range storAas { id := accountAction.KeyId() var actionsIds []string // collects action ids // action timings if accountAction.ActionPlanId != "" { // get old userBalanceIds exitingAccountIds := make(utils.StringMap) existingActionPlan, err := tpr.ratingStorage.GetActionPlan(accountAction.ActionPlanId, true) if err == nil && existingActionPlan != nil { exitingAccountIds = existingActionPlan.AccountIDs } tpap, err := tpr.lr.GetTpActionPlans(tpr.tpid, accountAction.ActionPlanId) if err != nil { return errors.New(err.Error() + " (ActionPlan): " + accountAction.ActionPlanId) } else if len(tpap) == 0 { return fmt.Errorf("no action plan with id <%s>", accountAction.ActionPlanId) } aps, err := TpActionPlans(tpap).GetActionPlans() if err != nil { return err } var actionPlan *ActionPlan ats := aps[accountAction.ActionPlanId] for _, at := range ats { // Check action exists before saving it inside actionTiming key // ToDo: try saving the key after the actions was retrieved in order to save one query here. if actions, err := tpr.lr.GetTpActions(tpr.tpid, at.ActionsId); err != nil { return errors.New(err.Error() + " (Actions): " + at.ActionsId) } else if len(actions) == 0 { return fmt.Errorf("no action with id <%s>", at.ActionsId) } var t *utils.TPTiming if at.TimingId != utils.ASAP { tptm, err := tpr.lr.GetTpTimings(tpr.tpid, at.TimingId) if err != nil { return errors.New(err.Error() + " (Timing): " + at.TimingId) } else if len(tptm) == 0 { return fmt.Errorf("no timing with id <%s>", at.TimingId) } tm, err := TpTimings(tptm).GetTimings() if err != nil { return err } t = tm[at.TimingId] } else { t = tpr.timings[at.TimingId] // *asap } if actionPlan == nil { actionPlan = &ActionPlan{ Id: accountAction.ActionPlanId, } } actionPlan.ActionTimings = append(actionPlan.ActionTimings, &ActionTiming{ Uuid: utils.GenUUID(), Weight: at.Weight, Timing: &RateInterval{ Timing: &RITiming{ Months: t.Months, MonthDays: t.MonthDays, WeekDays: t.WeekDays, StartTime: t.StartTime, }, }, ActionsID: at.ActionsId, }) // collect action ids from timings actionsIds = append(actionsIds, at.ActionsId) exitingAccountIds[id] = true actionPlan.AccountIDs = exitingAccountIds } // write tasks for _, at := range actionPlan.ActionTimings { if at.IsASAP() { for accID := range actionPlan.AccountIDs { t := &Task{ Uuid: utils.GenUUID(), AccountID: accID, ActionsID: at.ActionsID, } if err = tpr.ratingStorage.PushTask(t); err != nil { return err } } } } // write action plan err = tpr.ratingStorage.SetActionPlan(accountAction.ActionPlanId, actionPlan, false) if err != nil { return errors.New(err.Error() + " (SetActionPlan): " + accountAction.ActionPlanId) } } // action triggers var actionTriggers ActionTriggers //ActionTriggerPriotityList []*ActionTrigger if accountAction.ActionTriggersId != "" { tpatrs, err := tpr.lr.GetTpActionTriggers(tpr.tpid, accountAction.ActionTriggersId) if err != nil { return errors.New(err.Error() + " (ActionTriggers): " + accountAction.ActionTriggersId) } atrs, err := TpActionTriggers(tpatrs).GetActionTriggers() if err != nil { return err } atrsMap := make(map[string][]*ActionTrigger) for key, atrsLst := range atrs { atrs := make([]*ActionTrigger, len(atrsLst)) for idx, apiAtr := range atrsLst { minSleep, _ := utils.ParseDurationWithSecs(apiAtr.MinSleep) balanceExpTime, _ := utils.ParseDate(apiAtr.BalanceExpirationDate) expTime, _ := utils.ParseTimeDetectLayout(apiAtr.ExpirationDate, tpr.timezone) actTime, _ := utils.ParseTimeDetectLayout(apiAtr.ActivationDate, tpr.timezone) if apiAtr.UniqueID == "" { apiAtr.UniqueID = utils.GenUUID() } atrs[idx] = &ActionTrigger{ ID: key, UniqueID: apiAtr.UniqueID, ThresholdType: apiAtr.ThresholdType, ThresholdValue: apiAtr.ThresholdValue, Recurrent: apiAtr.Recurrent, MinSleep: minSleep, ExpirationDate: expTime, ActivationDate: actTime, BalanceId: apiAtr.BalanceId, BalanceType: apiAtr.BalanceType, BalanceDirections: utils.ParseStringMap(apiAtr.BalanceDirections), BalanceDestinationIds: utils.ParseStringMap(apiAtr.BalanceDestinationIds), BalanceWeight: apiAtr.BalanceWeight, BalanceExpirationDate: balanceExpTime, BalanceTimingTags: utils.ParseStringMap(apiAtr.BalanceTimingTags), BalanceRatingSubject: apiAtr.BalanceRatingSubject, BalanceCategories: utils.ParseStringMap(apiAtr.BalanceCategories), BalanceSharedGroups: utils.ParseStringMap(apiAtr.BalanceSharedGroups), BalanceBlocker: apiAtr.BalanceBlocker, BalanceDisabled: apiAtr.BalanceDisabled, Weight: apiAtr.Weight, ActionsId: apiAtr.ActionsId, } } atrsMap[key] = atrs } actionTriggers = atrsMap[accountAction.ActionTriggersId] // collect action ids from triggers for _, atr := range actionTriggers { actionsIds = append(actionsIds, atr.ActionsId) } // write action triggers err = tpr.ratingStorage.SetActionTriggers(accountAction.ActionTriggersId, actionTriggers) if err != nil { return errors.New(err.Error() + " (SetActionTriggers): " + accountAction.ActionTriggersId) } } // actions acts := make(map[string][]*Action) for _, actId := range actionsIds { tpas, err := tpr.lr.GetTpActions(tpr.tpid, actId) if err != nil { return err } as, err := TpActions(tpas).GetActions() if err != nil { return err } for tag, tpacts := range as { enacts := make([]*Action, len(tpacts)) for idx, tpact := range tpacts { // check filter field if len(tpact.Filter) > 0 { if _, err := structmatcher.NewStructMatcher(tpact.Filter); err != nil { return fmt.Errorf("error parsing action %s filter field: %v", tag, err) } } enacts[idx] = &Action{ Id: tag + strconv.Itoa(idx), ActionType: tpact.Identifier, BalanceType: tpact.BalanceType, Weight: tpact.Weight, ExtraParameters: tpact.ExtraParameters, ExpirationString: tpact.ExpiryTime, Filter: tpact.Filter, Balance: &Balance{ Id: tpact.BalanceId, Value: tpact.Units, Weight: tpact.BalanceWeight, RatingSubject: tpact.RatingSubject, Categories: utils.ParseStringMap(tpact.Categories), Directions: utils.ParseStringMap(tpact.Directions), DestinationIds: utils.ParseStringMap(tpact.DestinationIds), SharedGroups: utils.ParseStringMap(tpact.SharedGroups), TimingIDs: utils.ParseStringMap(tpact.TimingTags), Blocker: tpact.BalanceBlocker, Disabled: tpact.BalanceDisabled, }, } } acts[tag] = enacts } } // write actions for k, as := range acts { err = tpr.ratingStorage.SetActions(k, as) if err != nil { return err } } ub, err := tpr.accountingStorage.GetAccount(id) if err != nil { ub = &Account{ Id: id, } } ub.ActionTriggers = actionTriggers // init counters ub.InitCounters() if err := tpr.accountingStorage.SetAccount(ub); err != nil { return err } } return nil }
func (at *ActionPlan) Execute() (err error) { if len(at.AccountIds) == 0 { // nothing to do if no accounts set return } at.resetStartTimeCache() aac, err := at.getActions() if err != nil { utils.Logger.Err(fmt.Sprintf("Failed to get actions for %s: %s", at.ActionsId, err)) return } _, err = Guardian.Guard(func() (interface{}, error) { for _, accId := range at.AccountIds { ub, err := accountingStorage.GetAccount(accId) if err != nil { utils.Logger.Warning(fmt.Sprintf("Could not get user balances for this id: %s. Skipping!", accId)) return 0, err } transactionFailed := false toBeSaved := true for _, a := range aac { if ub.Disabled && a.ActionType != ENABLE_ACCOUNT { continue // disabled acocunts are not removed from action plan //return 0, fmt.Errorf("Account %s is disabled", accId) } if expDate, parseErr := utils.ParseDate(a.ExpirationString); (a.Balance == nil || a.Balance.ExpirationDate.IsZero()) && parseErr == nil && !expDate.IsZero() { a.Balance.ExpirationDate = expDate } // handle remove action if a.ActionType == REMOVE_ACCOUNT { if err := accountingStorage.RemoveAccount(accId); err != nil { utils.Logger.Err(fmt.Sprintf("Could not remove account Id: %s: %v", accId, err)) transactionFailed = true break } // clean the account id from all action plans allATs, err := ratingStorage.GetAllActionPlans() if err != nil && err != utils.ErrNotFound { utils.Logger.Err(fmt.Sprintf("Could not get action plans: %s: %v", accId, err)) transactionFailed = true break } for key, ats := range allATs { changed := false for _, at := range ats { for i := 0; i < len(at.AccountIds); i++ { if at.AccountIds[i] == accId { // delete without preserving order at.AccountIds[i] = at.AccountIds[len(at.AccountIds)-1] at.AccountIds = at.AccountIds[:len(at.AccountIds)-1] i -= 1 changed = true } } } if changed { // save action plan ratingStorage.SetActionPlans(key, ats) // cache ratingStorage.CacheRatingPrefixValues(map[string][]string{utils.ACTION_PLAN_PREFIX: []string{utils.ACTION_PLAN_PREFIX + key}}) } } toBeSaved = false continue // do not go to getActionFunc // TODO: maybe we should break here as the account is gone // will leave continue for now as the next action can create another acount } actionFunction, exists := getActionFunc(a.ActionType) if !exists { // do not allow the action plan to be rescheduled at.Timing = nil utils.Logger.Err(fmt.Sprintf("Function type %v not available, aborting execution!", a.ActionType)) transactionFailed = true break } if err := actionFunction(ub, nil, a, aac); err != nil { utils.Logger.Err(fmt.Sprintf("Error executing action %s: %v!", a.ActionType, err)) transactionFailed = true break } toBeSaved = true } if !transactionFailed && toBeSaved { accountingStorage.SetAccount(ub) } } return 0, nil }, 0, at.AccountIds...) if err != nil { utils.Logger.Warning(fmt.Sprintf("Error executing action plan: %v", err)) return err } storageLogger.LogActionPlan(utils.SCHED_SOURCE, at, aac) return }
func (at *ActionTiming) Execute() (err error) { at.ResetStartTimeCache() aac, err := at.getActions() if err != nil { utils.Logger.Err(fmt.Sprintf("Failed to get actions for %s: %s", at.ActionsID, err)) return } for accID, _ := range at.accountIDs { _, err = Guardian.Guard(func() (interface{}, error) { acc, err := accountingStorage.GetAccount(accID) if err != nil { utils.Logger.Warning(fmt.Sprintf("Could not get account id: %s. Skipping!", accID)) return 0, err } transactionFailed := false removeAccountActionFound := false for _, a := range aac { // check action filter if len(a.Filter) > 0 { matched, err := acc.matchActionFilter(a.Filter) //log.Print("Checkng: ", a.Filter, matched) if err != nil { return 0, err } if !matched { continue } } if a.Balance == nil { a.Balance = &BalanceFilter{} } if a.ExpirationString != "" { // if it's *unlimited then it has to be zero time if expDate, parseErr := utils.ParseDate(a.ExpirationString); parseErr == nil { a.Balance.ExpirationDate = &time.Time{} *a.Balance.ExpirationDate = expDate } } actionFunction, exists := getActionFunc(a.ActionType) if !exists { // do not allow the action plan to be rescheduled at.Timing = nil utils.Logger.Err(fmt.Sprintf("Function type %v not available, aborting execution!", a.ActionType)) transactionFailed = true break } if err := actionFunction(acc, nil, a, aac); err != nil { utils.Logger.Err(fmt.Sprintf("Error executing action %s: %v!", a.ActionType, err)) transactionFailed = true break } if a.ActionType == REMOVE_ACCOUNT { removeAccountActionFound = true } } if !transactionFailed && !removeAccountActionFound { accountingStorage.SetAccount(acc) } return 0, nil }, 0, accID) } if len(at.accountIDs) == 0 { // action timing executing without accounts for _, a := range aac { if expDate, parseErr := utils.ParseDate(a.ExpirationString); (a.Balance == nil || a.Balance.EmptyExpirationDate()) && parseErr == nil && !expDate.IsZero() { a.Balance.ExpirationDate = &time.Time{} *a.Balance.ExpirationDate = expDate } actionFunction, exists := getActionFunc(a.ActionType) if !exists { // do not allow the action plan to be rescheduled at.Timing = nil utils.Logger.Err(fmt.Sprintf("Function type %v not available, aborting execution!", a.ActionType)) break } if err := actionFunction(nil, nil, a, aac); err != nil { utils.Logger.Err(fmt.Sprintf("Error executing accountless action %s: %v!", a.ActionType, err)) break } } } if err != nil { utils.Logger.Warning(fmt.Sprintf("Error executing action plan: %v", err)) return err } storageLogger.LogActionTiming(utils.SCHED_SOURCE, at, aac) return }
func (at *ActionTrigger) Execute(ub *Account, sq *StatsQueueTriggered) (err error) { // check for min sleep time if at.Recurrent && !at.LastExecutionTime.IsZero() && time.Since(at.LastExecutionTime) < at.MinSleep { return } at.LastExecutionTime = time.Now() if ub != nil && ub.Disabled { return fmt.Errorf("User %s is disabled and there are triggers in action!", ub.ID) } // does NOT need to Lock() because it is triggered from a method that took the Lock var aac Actions aac, err = ratingStorage.GetActions(at.ActionsID, false, utils.NonTransactional) if err != nil { utils.Logger.Err(fmt.Sprintf("Failed to get actions: %v", err)) return } aac.Sort() at.Executed = true transactionFailed := false removeAccountActionFound := false for _, a := range aac { // check action filter if len(a.Filter) > 0 { matched, err := ub.matchActionFilter(a.Filter) if err != nil { return err } if !matched { continue } } if a.Balance == nil { a.Balance = &BalanceFilter{} } if a.ExpirationString != "" { // if it's *unlimited then it has to be zero time' if expDate, parseErr := utils.ParseDate(a.ExpirationString); parseErr == nil { a.Balance.ExpirationDate = &time.Time{} *a.Balance.ExpirationDate = expDate } } actionFunction, exists := getActionFunc(a.ActionType) if !exists { utils.Logger.Err(fmt.Sprintf("Function type %v not available, aborting execution!", a.ActionType)) transactionFailed = false break } //go utils.Logger.Info(fmt.Sprintf("Executing %v, %v: %v", ub, sq, a)) if err := actionFunction(ub, sq, a, aac); err != nil { utils.Logger.Err(fmt.Sprintf("Error executing action %s: %v!", a.ActionType, err)) transactionFailed = false break } if a.ActionType == REMOVE_ACCOUNT { removeAccountActionFound = true } } if transactionFailed || at.Recurrent { at.Executed = false } if !transactionFailed && ub != nil && !removeAccountActionFound { Publish(CgrEvent{ "EventName": utils.EVT_ACTION_TRIGGER_FIRED, "Uuid": at.UniqueID, "Id": at.ID, "ActionIds": at.ActionsID, }) accountingStorage.SetAccount(ub) } return }
func (self *ApierV1) SetActionTrigger(attr AttrSetActionTrigger, reply *string) error { if missing := utils.MissingStructFields(&attr, []string{"GroupID"}); len(missing) != 0 { return utils.NewErrMandatoryIeMissing(missing...) } atrs, _ := self.RatingDb.GetActionTriggers(attr.GroupID, false, utils.NonTransactional) var newAtr *engine.ActionTrigger if attr.UniqueID != "" { //search for exiting one for _, atr := range atrs { if atr.UniqueID == attr.UniqueID { newAtr = atr break } } } if newAtr == nil { newAtr = &engine.ActionTrigger{} atrs = append(atrs, newAtr) } newAtr.ID = attr.GroupID if attr.UniqueID != "" { newAtr.UniqueID = attr.UniqueID } else { newAtr.UniqueID = utils.GenUUID() } if attr.ThresholdType != nil { newAtr.ThresholdType = *attr.ThresholdType } if attr.ThresholdValue != nil { newAtr.ThresholdValue = *attr.ThresholdValue } if attr.Recurrent != nil { newAtr.Recurrent = *attr.Recurrent } if attr.MinSleep != nil { minSleep, err := utils.ParseDurationWithSecs(*attr.MinSleep) if err != nil { *reply = err.Error() return err } newAtr.MinSleep = minSleep } if attr.ExpirationDate != nil { expTime, err := utils.ParseTimeDetectLayout(*attr.ExpirationDate, self.Config.DefaultTimezone) if err != nil { *reply = err.Error() return err } newAtr.ExpirationDate = expTime } if attr.ActivationDate != nil { actTime, err := utils.ParseTimeDetectLayout(*attr.ActivationDate, self.Config.DefaultTimezone) if err != nil { *reply = err.Error() return err } newAtr.ActivationDate = actTime } newAtr.Balance = &engine.BalanceFilter{} if attr.BalanceID != nil { newAtr.Balance.ID = attr.BalanceID } if attr.BalanceType != nil { newAtr.Balance.Type = attr.BalanceType } if attr.BalanceDirections != nil { newAtr.Balance.Directions = utils.StringMapPointer(utils.NewStringMap(*attr.BalanceDirections...)) } if attr.BalanceDestinationIds != nil { newAtr.Balance.DestinationIDs = utils.StringMapPointer(utils.NewStringMap(*attr.BalanceDestinationIds...)) } if attr.BalanceWeight != nil { newAtr.Balance.Weight = attr.BalanceWeight } if attr.BalanceExpirationDate != nil { balanceExpTime, err := utils.ParseDate(*attr.BalanceExpirationDate) if err != nil { *reply = err.Error() return err } newAtr.Balance.ExpirationDate = &balanceExpTime } if attr.BalanceTimingTags != nil { newAtr.Balance.TimingIDs = utils.StringMapPointer(utils.NewStringMap(*attr.BalanceTimingTags...)) } if attr.BalanceRatingSubject != nil { newAtr.Balance.RatingSubject = attr.BalanceRatingSubject } if attr.BalanceCategories != nil { newAtr.Balance.Categories = utils.StringMapPointer(utils.NewStringMap(*attr.BalanceCategories...)) } if attr.BalanceSharedGroups != nil { newAtr.Balance.SharedGroups = utils.StringMapPointer(utils.NewStringMap(*attr.BalanceSharedGroups...)) } if attr.BalanceBlocker != nil { newAtr.Balance.Blocker = attr.BalanceBlocker } if attr.BalanceDisabled != nil { newAtr.Balance.Disabled = attr.BalanceDisabled } if attr.MinQueuedItems != nil { newAtr.MinQueuedItems = *attr.MinQueuedItems } if attr.ActionsID != nil { newAtr.ActionsID = *attr.ActionsID } if err := self.RatingDb.SetActionTriggers(attr.GroupID, atrs, utils.NonTransactional); err != nil { *reply = err.Error() return err } //no cache for action triggers *reply = utils.OK return nil }
func (self *ApierV1) SetAccountActionTriggers(attr AttrSetAccountActionTriggers, reply *string) error { if missing := utils.MissingStructFields(&attr, []string{"Tenant", "Account"}); len(missing) != 0 { return utils.NewErrMandatoryIeMissing(missing...) } accID := utils.AccountKey(attr.Tenant, attr.Account) var account *engine.Account _, err := engine.Guardian.Guard(func() (interface{}, error) { if acc, err := self.AccountDb.GetAccount(accID); err == nil { account = acc } else { return 0, err } for _, at := range account.ActionTriggers { if (attr.UniqueID == "" || at.UniqueID == attr.UniqueID) && (attr.GroupID == "" || at.ID == attr.GroupID) { // we have a winner if attr.ThresholdType != nil { at.ThresholdType = *attr.ThresholdType } if attr.ThresholdValue != nil { at.ThresholdValue = *attr.ThresholdValue } if attr.Recurrent != nil { at.Recurrent = *attr.Recurrent } if attr.Executed != nil { at.Executed = *attr.Executed } if attr.MinSleep != nil { minSleep, err := utils.ParseDurationWithSecs(*attr.MinSleep) if err != nil { return 0, err } at.MinSleep = minSleep } if attr.ExpirationDate != nil { expTime, err := utils.ParseTimeDetectLayout(*attr.ExpirationDate, self.Config.DefaultTimezone) if err != nil { return 0, err } at.ExpirationDate = expTime } if attr.ActivationDate != nil { actTime, err := utils.ParseTimeDetectLayout(*attr.ActivationDate, self.Config.DefaultTimezone) if err != nil { return 0, err } at.ActivationDate = actTime } at.Balance = &engine.BalanceFilter{} if attr.BalanceID != nil { at.Balance.ID = attr.BalanceID } if attr.BalanceType != nil { at.Balance.Type = attr.BalanceType } if attr.BalanceDirections != nil { at.Balance.Directions = utils.StringMapPointer(utils.NewStringMap(*attr.BalanceDirections...)) } if attr.BalanceDestinationIds != nil { at.Balance.DestinationIDs = utils.StringMapPointer(utils.NewStringMap(*attr.BalanceDestinationIds...)) } if attr.BalanceWeight != nil { at.Balance.Weight = attr.BalanceWeight } if attr.BalanceExpirationDate != nil { balanceExpTime, err := utils.ParseDate(*attr.BalanceExpirationDate) if err != nil { return 0, err } at.Balance.ExpirationDate = &balanceExpTime } if attr.BalanceTimingTags != nil { at.Balance.TimingIDs = utils.StringMapPointer(utils.NewStringMap(*attr.BalanceTimingTags...)) } if attr.BalanceRatingSubject != nil { at.Balance.RatingSubject = attr.BalanceRatingSubject } if attr.BalanceCategories != nil { at.Balance.Categories = utils.StringMapPointer(utils.NewStringMap(*attr.BalanceCategories...)) } if attr.BalanceSharedGroups != nil { at.Balance.SharedGroups = utils.StringMapPointer(utils.NewStringMap(*attr.BalanceSharedGroups...)) } if attr.BalanceBlocker != nil { at.Balance.Blocker = attr.BalanceBlocker } if attr.BalanceDisabled != nil { at.Balance.Disabled = attr.BalanceDisabled } if attr.MinQueuedItems != nil { at.MinQueuedItems = *attr.MinQueuedItems } if attr.ActionsID != nil { at.ActionsID = *attr.ActionsID } } } account.ExecuteActionTriggers(nil) if err := self.AccountDb.SetAccount(account); err != nil { return 0, err } return 0, nil }, 0, accID) if err != nil { *reply = err.Error() return err } *reply = utils.OK return nil }
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, %+v received: %+v, %+v", eLcr.SupplierCosts[0], eLcr.SupplierCosts[1], lcr.SupplierCosts[0], lcr.SupplierCosts[1]) } 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 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.DDC: -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, engine.DDC: -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.DDC: -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, engine.DDC: -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, %+v, received: %+v, %+v", eStLcr.SupplierCosts[0], eStLcr.SupplierCosts[1], lcr.SupplierCosts[0], 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, %+v, received: %+v, %+v", eStLcr.SupplierCosts[0], eStLcr.SupplierCosts[1], lcr.SupplierCosts[0], lcr.SupplierCosts[1]) } }
func (tpr *TpReader) LoadAccountActionsFiltered(qriedAA *TpAccountAction) error { accountActions, err := tpr.lr.GetTpAccountActions(qriedAA) if err != nil { return errors.New(err.Error() + ": " + fmt.Sprintf("%+v", qriedAA)) } storAas, err := TpAccountActions(accountActions).GetAccountActions() if err != nil { return err } for _, accountAction := range storAas { id := accountAction.KeyId() var actionsIds []string // collects action ids // action timings if accountAction.ActionPlanId != "" { // get old userBalanceIds var exitingAccountIds []string existingActionPlans, err := tpr.ratingStorage.GetActionPlans(accountAction.ActionPlanId) if err == nil && len(existingActionPlans) > 0 { // all action timings from a specific tag shuld have the same list of user balances from the first one exitingAccountIds = existingActionPlans[0].AccountIds } tpap, err := tpr.lr.GetTpActionPlans(tpr.tpid, accountAction.ActionPlanId) if err != nil { return errors.New(err.Error() + " (ActionPlan): " + accountAction.ActionPlanId) } else if len(tpap) == 0 { return fmt.Errorf("no action plan with id <%s>", accountAction.ActionPlanId) } aps, err := TpActionPlans(tpap).GetActionPlans() if err != nil { return err } var actionTimings []*ActionPlan ats := aps[accountAction.ActionPlanId] for _, at := range ats { // Check action exists before saving it inside actionTiming key // ToDo: try saving the key after the actions was retrieved in order to save one query here. if actions, err := tpr.lr.GetTpActions(tpr.tpid, at.ActionsId); err != nil { return errors.New(err.Error() + " (Actions): " + at.ActionsId) } else if len(actions) == 0 { return fmt.Errorf("no action with id <%s>", at.ActionsId) } var t *utils.TPTiming if at.TimingId != utils.ASAP { tptm, err := tpr.lr.GetTpTimings(tpr.tpid, at.TimingId) if err != nil { return errors.New(err.Error() + " (Timing): " + at.TimingId) } else if len(tptm) == 0 { return fmt.Errorf("no timing with id <%s>", at.TimingId) } tm, err := TpTimings(tptm).GetTimings() if err != nil { return err } t = tm[at.TimingId] } else { t = tpr.timings[at.TimingId] // *asap } actPln := &ActionPlan{ Uuid: utils.GenUUID(), Id: accountAction.ActionPlanId, Weight: at.Weight, Timing: &RateInterval{ Timing: &RITiming{ Months: t.Months, MonthDays: t.MonthDays, WeekDays: t.WeekDays, StartTime: t.StartTime, }, }, ActionsId: at.ActionsId, } // collect action ids from timings actionsIds = append(actionsIds, actPln.ActionsId) //add user balance id if no already in found := false for _, ubId := range exitingAccountIds { if ubId == id { found = true break } } if !found { actPln.AccountIds = append(exitingAccountIds, id) } actionTimings = append(actionTimings, actPln) } // write action triggers err = tpr.ratingStorage.SetActionPlans(accountAction.ActionPlanId, actionTimings) if err != nil { return errors.New(err.Error() + " (SetActionPlan): " + accountAction.ActionPlanId) } } // action triggers var actionTriggers ActionTriggers //ActionTriggerPriotityList []*ActionTrigger if accountAction.ActionTriggersId != "" { tpatrs, err := tpr.lr.GetTpActionTriggers(tpr.tpid, accountAction.ActionTriggersId) if err != nil { return errors.New(err.Error() + " (ActionTriggers): " + accountAction.ActionTriggersId) } atrs, err := TpActionTriggers(tpatrs).GetActionTriggers() if err != nil { return err } atrsMap := make(map[string][]*ActionTrigger) for key, atrsLst := range atrs { atrs := make([]*ActionTrigger, len(atrsLst)) for idx, apiAtr := range atrsLst { minSleep, _ := utils.ParseDurationWithSecs(apiAtr.MinSleep) expTime, _ := utils.ParseDate(apiAtr.BalanceExpirationDate) atrs[idx] = &ActionTrigger{ ThresholdType: apiAtr.ThresholdType, ThresholdValue: apiAtr.ThresholdValue, Recurrent: apiAtr.Recurrent, MinSleep: minSleep, BalanceId: apiAtr.BalanceId, BalanceType: apiAtr.BalanceType, BalanceDirection: apiAtr.BalanceDirection, BalanceDestinationIds: apiAtr.BalanceDestinationIds, BalanceWeight: apiAtr.BalanceWeight, BalanceExpirationDate: expTime, BalanceRatingSubject: apiAtr.BalanceRatingSubject, BalanceCategory: apiAtr.BalanceCategory, BalanceSharedGroup: apiAtr.BalanceSharedGroup, Weight: apiAtr.Weight, ActionsId: apiAtr.ActionsId, } } atrsMap[key] = atrs } actionTriggers = atrsMap[accountAction.ActionTriggersId] // collect action ids from triggers for _, atr := range actionTriggers { actionsIds = append(actionsIds, atr.ActionsId) } // write action triggers err = tpr.ratingStorage.SetActionTriggers(accountAction.ActionTriggersId, actionTriggers) if err != nil { return errors.New(err.Error() + " (SetActionTriggers): " + accountAction.ActionTriggersId) } } // actions acts := make(map[string][]*Action) for _, actId := range actionsIds { tpas, err := tpr.lr.GetTpActions(tpr.tpid, actId) if err != nil { return err } as, err := TpActions(tpas).GetActions() if err != nil { return err } for tag, tpacts := range as { enacts := make([]*Action, len(tpacts)) for idx, tpact := range tpacts { enacts[idx] = &Action{ Id: tag + strconv.Itoa(idx), ActionType: tpact.Identifier, BalanceType: tpact.BalanceType, Direction: tpact.Direction, Weight: tpact.Weight, ExtraParameters: tpact.ExtraParameters, ExpirationString: tpact.ExpiryTime, Balance: &Balance{ Value: tpact.Units, Weight: tpact.BalanceWeight, RatingSubject: tpact.RatingSubject, DestinationIds: tpact.DestinationIds, SharedGroup: tpact.SharedGroup, }, } } acts[tag] = enacts } } // write actions for k, as := range acts { err = tpr.ratingStorage.SetActions(k, as) if err != nil { return err } } ub, err := tpr.accountingStorage.GetAccount(id) if err != nil { ub = &Account{ Id: id, } } ub.ActionTriggers = actionTriggers.Clone() if err := tpr.accountingStorage.SetAccount(ub); err != nil { return err } } return nil }
// Check call costs func TestTutLocalGetCosts(t *testing.T) { if !*testLocal { return } tStart, _ := utils.ParseDate("2014-08-04T13:00:00Z") tEnd, _ := utils.ParseDate("2014-08-04T13:00:20Z") cd := engine.CallDescriptor{ Direction: "*out", Category: "call", Tenant: "cgrates.org", Subject: "1001", Account: "1001", Destination: "1002", DurationIndex: 0, TimeStart: tStart, TimeEnd: tEnd, } var cc engine.CallCost if err := tutLocalRpc.Call("Responder.GetCost", cd, &cc); err != nil { t.Error("Got error on Responder.GetCost: ", err.Error()) } else if cc.Cost != 0.6 { t.Errorf("Calling Responder.GetCost got callcost: %v", cc.Cost) } // Make sure that the same cost is returned via users aliasing cd = engine.CallDescriptor{ Direction: "*out", Category: "call", Tenant: utils.USERS, Subject: utils.USERS, Account: utils.USERS, Destination: "1002", DurationIndex: 0, TimeStart: tStart, TimeEnd: tEnd, ExtraFields: map[string]string{"Uuid": "388539dfd4f5cefee8f488b78c6c244b9e19138e"}, } if err := tutLocalRpc.Call("Responder.GetCost", cd, &cc); err != nil { t.Error("Got error on Responder.GetCost: ", err.Error()) } else if cc.Cost != 0.6 { t.Errorf("Calling Responder.GetCost got callcost: %v", cc.Cost) } tStart, _ = utils.ParseDate("2014-08-04T13:00:00Z") tEnd, _ = utils.ParseDate("2014-08-04T13:01:25Z") cd = engine.CallDescriptor{ Direction: "*out", Category: "call", Tenant: "cgrates.org", Subject: "1001", Account: "1001", Destination: "1002", DurationIndex: 0, TimeStart: tStart, TimeEnd: tEnd, } if err := tutLocalRpc.Call("Responder.GetCost", cd, &cc); err != nil { t.Error("Got error on Responder.GetCost: ", err.Error()) } else if cc.Cost != 0.6417 { // 0.01 first minute, 0.04 25 seconds with RT_20CNT t.Errorf("Calling Responder.GetCost got callcost: %v", cc.Cost) } tStart, _ = utils.ParseDate("2014-08-04T13:00:00Z") tEnd, _ = utils.ParseDate("2014-08-04T13:00:20Z") cd = engine.CallDescriptor{ Direction: "*out", Category: "call", Tenant: "cgrates.org", Subject: "1001", Account: "1001", Destination: "1003", DurationIndex: 0, TimeStart: tStart, TimeEnd: tEnd, } if err := tutLocalRpc.Call("Responder.GetCost", cd, &cc); err != nil { t.Error("Got error on Responder.GetCost: ", err.Error()) } else if cc.Cost != 1 { t.Errorf("Calling Responder.GetCost got callcost: %v", cc.Cost) } tStart, _ = utils.ParseDate("2014-08-04T13:00:00Z") tEnd, _ = utils.ParseDate("2014-08-04T13:01:25Z") cd = engine.CallDescriptor{ Direction: "*out", Category: "call", Tenant: "cgrates.org", Subject: "1001", Account: "1001", Destination: "1003", DurationIndex: 0, TimeStart: tStart, TimeEnd: tEnd, } if err := tutLocalRpc.Call("Responder.GetCost", cd, &cc); err != nil { t.Error("Got error on Responder.GetCost: ", err.Error()) } else if cc.Cost != 1.3 { t.Errorf("Calling Responder.GetCost got callcost: %v", cc.Cost) } tStart, _ = utils.ParseDate("2014-08-04T13:00:00Z") tEnd, _ = utils.ParseDate("2014-08-04T13:00:20Z") cd = engine.CallDescriptor{ Direction: "*out", Category: "call", Tenant: "cgrates.org", Subject: "1001", Account: "1001", Destination: "1004", DurationIndex: 0, TimeStart: tStart, TimeEnd: tEnd, } if err := tutLocalRpc.Call("Responder.GetCost", cd, &cc); err != nil { t.Error("Got error on Responder.GetCost: ", err.Error()) } else if cc.Cost != 1 { t.Errorf("Calling Responder.GetCost got callcost: %v", cc.Cost) } tStart, _ = utils.ParseDate("2014-08-04T13:00:00Z") tEnd, _ = utils.ParseDate("2014-08-04T13:01:25Z") cd = engine.CallDescriptor{ Direction: "*out", Category: "call", Tenant: "cgrates.org", Subject: "1001", Account: "1001", Destination: "1004", DurationIndex: 0, TimeStart: tStart, TimeEnd: tEnd, } if err := tutLocalRpc.Call("Responder.GetCost", cd, &cc); err != nil { t.Error("Got error on Responder.GetCost: ", err.Error()) } else if cc.Cost != 1.3 { t.Errorf("Calling Responder.GetCost got callcost: %v", cc.Cost) } tStart = time.Date(2014, 8, 4, 13, 0, 0, 0, time.UTC) cd = engine.CallDescriptor{ Direction: "*out", Category: "call", Tenant: "cgrates.org", Subject: "1001", Account: "1001", Destination: "1007", TimeStart: tStart, TimeEnd: tStart.Add(time.Duration(50) * time.Second), } if err := tutLocalRpc.Call("Responder.GetCost", cd, &cc); err != nil { t.Error("Got error on Responder.GetCost: ", err.Error()) } else if cc.Cost != 0.5 { t.Errorf("Calling Responder.GetCost got callcost: %s", cc.AsJSON()) } cd = engine.CallDescriptor{ Direction: "*out", Category: "call", Tenant: "cgrates.org", Subject: "1001", Account: "1001", Destination: "1007", TimeStart: tStart, TimeEnd: tStart.Add(time.Duration(70) * time.Second), } if err := tutLocalRpc.Call("Responder.GetCost", cd, &cc); err != nil { t.Error("Got error on Responder.GetCost: ", err.Error()) } else if cc.Cost != 0.62 { t.Errorf("Calling Responder.GetCost got callcost: %v", cc.Cost) } cd = engine.CallDescriptor{ Direction: "*out", Category: "call", Tenant: "cgrates.org", Subject: "1002", Account: "1002", Destination: "1007", TimeStart: tStart, TimeEnd: tStart.Add(time.Duration(50) * time.Second), } if err := tutLocalRpc.Call("Responder.GetCost", cd, &cc); err != nil { t.Error("Got error on Responder.GetCost: ", err.Error()) } else if cc.Cost != 0.5 { t.Errorf("Calling Responder.GetCost got callcost: %s", cc.AsJSON()) } cd = engine.CallDescriptor{ Direction: "*out", Category: "call", Tenant: "cgrates.org", Subject: "1002", Account: "1002", Destination: "1007", TimeStart: tStart, TimeEnd: tStart.Add(time.Duration(70) * time.Second), } if err := tutLocalRpc.Call("Responder.GetCost", cd, &cc); err != nil { t.Error("Got error on Responder.GetCost: ", err.Error()) } else if cc.Cost != 0.7 { // In case of *disconnect strategy, it will not be applied so we can go on negative costs t.Errorf("Calling Responder.GetCost got callcost: %s", cc.AsJSON()) } }
func (attr *AttrSetAccountActionTriggers) UpdateActionTrigger(at *engine.ActionTrigger, timezone string) (updated bool, err error) { if at == nil { return false, errors.New("Empty ActionTrigger") } if at.ID == "" { // New AT, update it's data if missing := utils.MissingStructFields(attr, []string{"GroupID", "ThresholdType", "ThresholdValue"}); len(missing) != 0 { return false, utils.NewErrMandatoryIeMissing(missing...) } at.ID = *attr.GroupID if attr.UniqueID != nil { at.UniqueID = *attr.UniqueID } } if attr.GroupID != nil && *attr.GroupID != at.ID { return } if attr.UniqueID != nil && *attr.UniqueID != at.UniqueID { return } // at matches updated = true if attr.ThresholdType != nil { at.ThresholdType = *attr.ThresholdType } if attr.ThresholdValue != nil { at.ThresholdValue = *attr.ThresholdValue } if attr.Recurrent != nil { at.Recurrent = *attr.Recurrent } if attr.Executed != nil { at.Executed = *attr.Executed } if attr.MinSleep != nil { if at.MinSleep, err = utils.ParseDurationWithSecs(*attr.MinSleep); err != nil { return } } if attr.ExpirationDate != nil { if at.ExpirationDate, err = utils.ParseTimeDetectLayout(*attr.ExpirationDate, timezone); err != nil { return } } if attr.ActivationDate != nil { if at.ActivationDate, err = utils.ParseTimeDetectLayout(*attr.ActivationDate, timezone); err != nil { return } } if at.Balance == nil { at.Balance = &engine.BalanceFilter{} } if attr.BalanceID != nil { at.Balance.ID = attr.BalanceID } if attr.BalanceType != nil { at.Balance.Type = attr.BalanceType } if attr.BalanceDirections != nil { at.Balance.Directions = utils.StringMapPointer(utils.NewStringMap(*attr.BalanceDirections...)) } if attr.BalanceDestinationIds != nil { at.Balance.DestinationIDs = utils.StringMapPointer(utils.NewStringMap(*attr.BalanceDestinationIds...)) } if attr.BalanceWeight != nil { at.Balance.Weight = attr.BalanceWeight } if attr.BalanceExpirationDate != nil { balanceExpTime, err := utils.ParseDate(*attr.BalanceExpirationDate) if err != nil { return false, err } at.Balance.ExpirationDate = &balanceExpTime } if attr.BalanceTimingTags != nil { at.Balance.TimingIDs = utils.StringMapPointer(utils.NewStringMap(*attr.BalanceTimingTags...)) } if attr.BalanceRatingSubject != nil { at.Balance.RatingSubject = attr.BalanceRatingSubject } if attr.BalanceCategories != nil { at.Balance.Categories = utils.StringMapPointer(utils.NewStringMap(*attr.BalanceCategories...)) } if attr.BalanceSharedGroups != nil { at.Balance.SharedGroups = utils.StringMapPointer(utils.NewStringMap(*attr.BalanceSharedGroups...)) } if attr.BalanceBlocker != nil { at.Balance.Blocker = attr.BalanceBlocker } if attr.BalanceDisabled != nil { at.Balance.Disabled = attr.BalanceDisabled } if attr.MinQueuedItems != nil { at.MinQueuedItems = *attr.MinQueuedItems } if attr.ActionsID != nil { at.ActionsID = *attr.ActionsID } return }
func (at *ActionTrigger) Execute(ub *Account, sq *StatsQueueTriggered) (err error) { // check for min sleep time if at.Recurrent && !at.lastExecutionTime.IsZero() && time.Since(at.lastExecutionTime) < at.MinSleep { return } at.lastExecutionTime = time.Now() if ub != nil && ub.Disabled { return fmt.Errorf("User %s is disabled and there are triggers in action!", ub.Id) } // does NOT need to Lock() because it is triggered from a method that took the Lock var aac Actions aac, err = ratingStorage.GetActions(at.ActionsId, false) aac.Sort() if err != nil { utils.Logger.Err(fmt.Sprintf("Failed to get actions: %v", err)) return } at.Executed = true transactionFailed := false toBeSaved := true for _, a := range aac { if a.Balance == nil { a.Balance = &Balance{} } a.Balance.ExpirationDate, _ = utils.ParseDate(a.ExpirationString) // handle remove action if a.ActionType == REMOVE_ACCOUNT { accId := ub.Id if err := accountingStorage.RemoveAccount(accId); err != nil { utils.Logger.Err(fmt.Sprintf("Could not remove account Id: %s: %v", accId, err)) transactionFailed = true break } // clean the account id from all action plans allATs, err := ratingStorage.GetAllActionPlans() if err != nil && err != utils.ErrNotFound { utils.Logger.Err(fmt.Sprintf("Could not get action plans: %s: %v", accId, err)) transactionFailed = true break } for key, ats := range allATs { changed := false for _, at := range ats { for i := 0; i < len(at.AccountIds); i++ { if at.AccountIds[i] == accId { // delete without preserving order at.AccountIds[i] = at.AccountIds[len(at.AccountIds)-1] at.AccountIds = at.AccountIds[:len(at.AccountIds)-1] i -= 1 changed = true } } } if changed { // save action plan ratingStorage.SetActionPlans(key, ats) // cache ratingStorage.CacheRatingPrefixValues(map[string][]string{utils.ACTION_PLAN_PREFIX: []string{utils.ACTION_PLAN_PREFIX + key}}) } } toBeSaved = false continue // do not go to getActionFunc // TODO: maybe we should break here as the account is gone // will leave continue for now as the next action can create another acount } actionFunction, exists := getActionFunc(a.ActionType) if !exists { utils.Logger.Err(fmt.Sprintf("Function type %v not available, aborting execution!", a.ActionType)) transactionFailed = false break } //go utils.Logger.Info(fmt.Sprintf("Executing %v, %v: %v", ub, sq, a)) if err := actionFunction(ub, sq, a, aac); err != nil { utils.Logger.Err(fmt.Sprintf("Error executing action %s: %v!", a.ActionType, err)) transactionFailed = false break } toBeSaved = true } if transactionFailed || at.Recurrent { at.Executed = false } if !transactionFailed && ub != nil { storageLogger.LogActionTrigger(ub.Id, utils.RATER_SOURCE, at, aac) if toBeSaved { accountingStorage.SetAccount(ub) } } return }
func (tpr *TpReader) LoadCdrStatsFiltered(tag string, save bool) (err error) { tps, err := tpr.lr.GetTpCdrStats(tpr.tpid, tag) if err != nil { return err } storStats, err := TpCdrStats(tps).GetCdrStats() if err != nil { return err } var actionsIds []string // collect action ids for tag, tpStats := range storStats { for _, tpStat := range tpStats { var cs *CdrStats var exists bool if cs, exists = tpr.cdrStats[tag]; !exists { cs = &CdrStats{Id: tag} } // action triggers triggerTag := tpStat.ActionTriggers if triggerTag != "" { _, exists := tpr.actionsTriggers[triggerTag] if !exists { tpatrs, err := tpr.lr.GetTpActionTriggers(tpr.tpid, triggerTag) if err != nil { return errors.New(err.Error() + " (ActionTriggers): " + triggerTag) } atrsM, err := TpActionTriggers(tpatrs).GetActionTriggers() if err != nil { return err } for _, atrsLst := range atrsM { atrs := make([]*ActionTrigger, len(atrsLst)) for idx, apiAtr := range atrsLst { minSleep, _ := utils.ParseDurationWithSecs(apiAtr.MinSleep) expTime, _ := utils.ParseDate(apiAtr.BalanceExpirationDate) atrs[idx] = &ActionTrigger{ ThresholdType: apiAtr.ThresholdType, ThresholdValue: apiAtr.ThresholdValue, Recurrent: apiAtr.Recurrent, MinSleep: minSleep, BalanceId: apiAtr.BalanceId, BalanceType: apiAtr.BalanceType, BalanceDirection: apiAtr.BalanceDirection, BalanceDestinationIds: apiAtr.BalanceDestinationIds, BalanceWeight: apiAtr.BalanceWeight, BalanceExpirationDate: expTime, BalanceRatingSubject: apiAtr.BalanceRatingSubject, BalanceCategory: apiAtr.BalanceCategory, BalanceSharedGroup: apiAtr.BalanceSharedGroup, Weight: apiAtr.Weight, ActionsId: apiAtr.ActionsId, } } tpr.actionsTriggers[triggerTag] = atrs } } // collect action ids from triggers for _, atr := range tpr.actionsTriggers[triggerTag] { actionsIds = append(actionsIds, atr.ActionsId) } } triggers, exists := tpr.actionsTriggers[triggerTag] if triggerTag != "" && !exists { // only return error if there was something there for the tag return fmt.Errorf("could not get action triggers for cdr stats id %s: %s", cs.Id, triggerTag) } // write action triggers err = tpr.ratingStorage.SetActionTriggers(triggerTag, triggers) if err != nil { return errors.New(err.Error() + " (SetActionTriggers): " + triggerTag) } UpdateCdrStats(cs, triggers, tpStat, tpr.timezone) tpr.cdrStats[tag] = cs } } // actions for _, actId := range actionsIds { _, exists := tpr.actions[actId] if !exists { tpas, err := tpr.lr.GetTpActions(tpr.tpid, actId) if err != nil { return err } as, err := TpActions(tpas).GetActions() if err != nil { return err } for tag, tpacts := range as { enacts := make([]*Action, len(tpacts)) for idx, tpact := range tpacts { enacts[idx] = &Action{ Id: tag + strconv.Itoa(idx), ActionType: tpact.Identifier, BalanceType: tpact.BalanceType, Direction: tpact.Direction, Weight: tpact.Weight, ExtraParameters: tpact.ExtraParameters, ExpirationString: tpact.ExpiryTime, Balance: &Balance{ Value: tpact.Units, Weight: tpact.BalanceWeight, RatingSubject: tpact.RatingSubject, DestinationIds: tpact.DestinationIds, SharedGroup: tpact.SharedGroup, }, } } tpr.actions[tag] = enacts } } } if save { // write actions for k, as := range tpr.actions { err = tpr.ratingStorage.SetActions(k, as) if err != nil { return err } } for _, stat := range tpr.cdrStats { if err := tpr.ratingStorage.SetCdrStats(stat); err != nil { return err } } } return nil }
func (tpr *TpReader) LoadCdrStatsFiltered(tag string, save bool) (err error) { tps, err := tpr.lr.GetTpCdrStats(tpr.tpid, tag) if err != nil { return err } storStats, err := TpCdrStats(tps).GetCdrStats() if err != nil { return err } var actionsIds []string // collect action ids for tag, tpStats := range storStats { for _, tpStat := range tpStats { var cs *CdrStats var exists bool if cs, exists = tpr.cdrStats[tag]; !exists { cs = &CdrStats{Id: tag} } // action triggers triggerTag := tpStat.ActionTriggers if triggerTag != "" { _, exists := tpr.actionsTriggers[triggerTag] if !exists { tpatrs, err := tpr.lr.GetTpActionTriggers(tpr.tpid, triggerTag) if err != nil { return errors.New(err.Error() + " (ActionTriggers): " + triggerTag) } atrsM, err := TpActionTriggers(tpatrs).GetActionTriggers() if err != nil { return err } for _, atrsLst := range atrsM { atrs := make([]*ActionTrigger, len(atrsLst)) for idx, apiAtr := range atrsLst { minSleep, _ := utils.ParseDurationWithSecs(apiAtr.MinSleep) balanceExpTime, _ := utils.ParseDate(apiAtr.BalanceExpirationDate) expTime, _ := utils.ParseTimeDetectLayout(apiAtr.ExpirationDate, tpr.timezone) actTime, _ := utils.ParseTimeDetectLayout(apiAtr.ActivationDate, tpr.timezone) if apiAtr.UniqueID == "" { apiAtr.UniqueID = utils.GenUUID() } atrs[idx] = &ActionTrigger{ ID: triggerTag, UniqueID: apiAtr.UniqueID, ThresholdType: apiAtr.ThresholdType, ThresholdValue: apiAtr.ThresholdValue, Recurrent: apiAtr.Recurrent, MinSleep: minSleep, ExpirationDate: expTime, ActivationDate: actTime, BalanceId: apiAtr.BalanceId, BalanceType: apiAtr.BalanceType, BalanceDirections: utils.ParseStringMap(apiAtr.BalanceDirections), BalanceDestinationIds: utils.ParseStringMap(apiAtr.BalanceDestinationIds), BalanceWeight: apiAtr.BalanceWeight, BalanceExpirationDate: balanceExpTime, BalanceRatingSubject: apiAtr.BalanceRatingSubject, BalanceCategories: utils.ParseStringMap(apiAtr.BalanceCategories), BalanceSharedGroups: utils.ParseStringMap(apiAtr.BalanceSharedGroups), BalanceTimingTags: utils.ParseStringMap(apiAtr.BalanceTimingTags), Weight: apiAtr.Weight, ActionsId: apiAtr.ActionsId, } } tpr.actionsTriggers[triggerTag] = atrs } } // collect action ids from triggers for _, atr := range tpr.actionsTriggers[triggerTag] { actionsIds = append(actionsIds, atr.ActionsId) } } triggers, exists := tpr.actionsTriggers[triggerTag] if triggerTag != "" && !exists { // only return error if there was something there for the tag return fmt.Errorf("could not get action triggers for cdr stats id %s: %s", cs.Id, triggerTag) } // write action triggers err = tpr.ratingStorage.SetActionTriggers(triggerTag, triggers) if err != nil { return errors.New(err.Error() + " (SetActionTriggers): " + triggerTag) } UpdateCdrStats(cs, triggers, tpStat, tpr.timezone) tpr.cdrStats[tag] = cs } } // actions for _, actId := range actionsIds { _, exists := tpr.actions[actId] if !exists { tpas, err := tpr.lr.GetTpActions(tpr.tpid, actId) if err != nil { return err } as, err := TpActions(tpas).GetActions() if err != nil { return err } for tag, tpacts := range as { enacts := make([]*Action, len(tpacts)) for idx, tpact := range tpacts { // check filter field if len(tpact.Filter) > 0 { if _, err := structmatcher.NewStructMatcher(tpact.Filter); err != nil { return fmt.Errorf("error parsing action %s filter field: %v", tag, err) } } enacts[idx] = &Action{ Id: tag + strconv.Itoa(idx), ActionType: tpact.Identifier, BalanceType: tpact.BalanceType, Weight: tpact.Weight, ExtraParameters: tpact.ExtraParameters, ExpirationString: tpact.ExpiryTime, Filter: tpact.Filter, Balance: &Balance{ Id: tpact.BalanceId, Value: tpact.Units, Weight: tpact.BalanceWeight, RatingSubject: tpact.RatingSubject, Categories: utils.ParseStringMap(tpact.Categories), Directions: utils.ParseStringMap(tpact.Directions), DestinationIds: utils.ParseStringMap(tpact.DestinationIds), SharedGroups: utils.ParseStringMap(tpact.SharedGroups), TimingIDs: utils.ParseStringMap(tpact.TimingTags), Blocker: tpact.BalanceBlocker, Disabled: tpact.BalanceDisabled, }, } } tpr.actions[tag] = enacts } } } if save { // write actions for k, as := range tpr.actions { err = tpr.ratingStorage.SetActions(k, as) if err != nil { return err } } for _, stat := range tpr.cdrStats { if err := tpr.ratingStorage.SetCdrStats(stat); err != nil { return err } } } return nil }