From 076dd821a9e238556785208e51fb70e519fa5e87 Mon Sep 17 00:00:00 2001 From: arberkatellari Date: Thu, 17 Jul 2025 14:55:44 +0200 Subject: [PATCH] Add FilterIDs, Weights & Blockers functionality to dynamic type actions --- actions/dynamic.go | 810 ++++++++++++-------- actions/libactions.go | 8 +- general_tests/dynamic_thresholds_it_test.go | 407 ++++++++-- 3 files changed, 829 insertions(+), 396 deletions(-) diff --git a/actions/dynamic.go b/actions/dynamic.go index 8f6b01168..c8c826a0b 100644 --- a/actions/dynamic.go +++ b/actions/dynamic.go @@ -19,7 +19,9 @@ along with this program. If not, see package actions import ( + "cmp" "fmt" + "slices" "strconv" "strings" "time" @@ -66,6 +68,7 @@ func parseParamStringToMap(paramStr string, targetMap map[string]any) error { type actDynamicThreshold struct { config *config.CGRConfig connMgr *engine.ConnManager + fltrS *engine.FilterS aCfg *utils.APAction tnt string cgrEv *utils.CGREvent @@ -91,95 +94,127 @@ func (aL *actDynamicThreshold) execute(ctx *context.Context, data utils.MapStora if len(aL.aCfg.Diktats) == 0 { return fmt.Errorf("No diktats were speified for action <%v>", aL.aCfg.ID) } - params := strings.Split(utils.IfaceAsString(aL.aCfg.Diktats[0].Opts[utils.MetaTemplate]), - utils.InfieldSep) - if len(params) != 11 { - return fmt.Errorf("invalid number of parameters <%d> expected 11", len(params)) - } - // parse dynamic parameters - for i := range params { - if params[i], err = utils.ParseParamForDataProvider(params[i], data, false); err != nil { + weights := make(map[string]float64) // stores sorting weights by Diktat ID + diktats := make([]*utils.APDiktat, 0) // list of diktats which have *template in opts, will be weight sorted later + for _, diktat := range aL.aCfg.Diktats { + if _, has := diktat.Opts[utils.MetaTemplate]; !has { + continue + } + if pass, err := aL.fltrS.Pass(ctx, aL.tnt, diktat.FilterIDs, data); err != nil { + return err + } else if !pass { + continue + } + weight, err := engine.WeightFromDynamics(ctx, diktat.Weights, aL.fltrS, aL.tnt, data) + if err != nil { return err } + weights[diktat.ID] = weight + diktats = append(diktats, diktat) } - // Prepare request arguments based on provided parameters. - args := &engine.ThresholdProfileWithAPIOpts{ - ThresholdProfile: &engine.ThresholdProfile{ - Tenant: params[0], - ID: params[1], - }, - APIOpts: make(map[string]any), - } - // populate Threshold's FilterIDs - if params[2] != utils.EmptyString { - args.FilterIDs = strings.Split(params[2], utils.ANDSep) - } - // populate Threshold's Weight - if params[3] != utils.EmptyString { - args.Weights = utils.DynamicWeights{&utils.DynamicWeight{}} - wghtSplit := strings.Split(params[3], utils.ANDSep) - if len(wghtSplit) > 2 { - return utils.ErrUnsupportedFormat + // Sort by weight (higher values first). + slices.SortFunc(diktats, func(a, b *utils.APDiktat) int { + return cmp.Compare(weights[b.ID], weights[a.ID]) + }) + for _, diktat := range diktats { + params := strings.Split(utils.IfaceAsString(diktat.Opts[utils.MetaTemplate]), + utils.InfieldSep) + if len(params) != 11 { + return fmt.Errorf("invalid number of parameters <%d> expected 11", len(params)) } - if wghtSplit[0] != utils.EmptyString { - args.Weights[0].FilterIDs = []string{wghtSplit[0]} + // parse dynamic parameters + for i := range params { + if params[i], err = utils.ParseParamForDataProvider(params[i], data, false); err != nil { + return err + } } - if wghtSplit[1] != utils.EmptyString { - args.Weights[0].Weight, err = strconv.ParseFloat(wghtSplit[1], 64) + // Prepare request arguments based on provided parameters. + args := &engine.ThresholdProfileWithAPIOpts{ + ThresholdProfile: &engine.ThresholdProfile{ + Tenant: params[0], + ID: params[1], + }, + APIOpts: make(map[string]any), + } + // populate Threshold's FilterIDs + if params[2] != utils.EmptyString { + args.FilterIDs = strings.Split(params[2], utils.ANDSep) + } + // populate Threshold's Weight + if params[3] != utils.EmptyString { + args.Weights = utils.DynamicWeights{&utils.DynamicWeight{}} + wghtSplit := strings.Split(params[3], utils.ANDSep) + if len(wghtSplit) > 2 { + return utils.ErrUnsupportedFormat + } + if wghtSplit[0] != utils.EmptyString { + args.Weights[0].FilterIDs = []string{wghtSplit[0]} + } + if wghtSplit[1] != utils.EmptyString { + args.Weights[0].Weight, err = strconv.ParseFloat(wghtSplit[1], 64) + if err != nil { + return err + } + } + } + // populate Threshold's MaxHits + if params[4] != utils.EmptyString { + args.MaxHits, err = strconv.Atoi(params[4]) if err != nil { return err } } - } - // populate Threshold's MaxHits - if params[4] != utils.EmptyString { - args.MaxHits, err = strconv.Atoi(params[4]) - if err != nil { + // populate Threshold's MinHits + if params[5] != utils.EmptyString { + args.MinHits, err = strconv.Atoi(params[5]) + if err != nil { + return err + } + } + // populate Threshold's MinSleep + if params[6] != utils.EmptyString { + args.MinSleep, err = utils.ParseDurationWithNanosecs(params[6]) + if err != nil { + return err + } + } + // populate Threshold's Blocker + if params[7] != utils.EmptyString { + args.Blocker, err = strconv.ParseBool(params[7]) + if err != nil { + return err + } + } + // populate Threshold's ActionProfileIDs + if params[8] != utils.EmptyString { + args.ActionProfileIDs = strings.Split(params[8], utils.ANDSep) + } + // populate Threshold's Async bool + if params[9] != utils.EmptyString { + args.Async, err = strconv.ParseBool(params[9]) + if err != nil { + return err + } + } + // populate Threshold's APIOpts + if params[10] != utils.EmptyString { + if err := parseParamStringToMap(params[10], args.APIOpts); err != nil { + return err + } + } + // create the ThresholdProfile based on the populated parameters + var rply string + if err = aL.connMgr.Call(ctx, aL.config.ActionSCfg().AdminSConns, + utils.AdminSv1SetThresholdProfile, args, &rply); err != nil { return err } - } - // populate Threshold's MinHits - if params[5] != utils.EmptyString { - args.MinHits, err = strconv.Atoi(params[5]) - if err != nil { + if blocker, err := engine.BlockerFromDynamics(ctx, diktat.Blockers, aL.fltrS, aL.tnt, data); err != nil { return err + } else if blocker { + break } } - // populate Threshold's MinSleep - if params[6] != utils.EmptyString { - args.MinSleep, err = utils.ParseDurationWithNanosecs(params[6]) - if err != nil { - return err - } - } - // populate Threshold's Blocker - if params[7] != utils.EmptyString { - args.Blocker, err = strconv.ParseBool(params[7]) - if err != nil { - return err - } - } - // populate Threshold's ActionProfileIDs - if params[8] != utils.EmptyString { - args.ActionProfileIDs = strings.Split(params[8], utils.ANDSep) - } - // populate Threshold's Async bool - if params[9] != utils.EmptyString { - args.Async, err = strconv.ParseBool(params[9]) - if err != nil { - return err - } - } - // populate Threshold's APIOpts - if params[10] != utils.EmptyString { - if err := parseParamStringToMap(params[10], args.APIOpts); err != nil { - return err - } - } - // create the ThresholdProfile based on the populated parameters - var rply string - return aL.connMgr.Call(ctx, aL.config.ActionSCfg().AdminSConns, - utils.AdminSv1SetThresholdProfile, args, &rply) + return } // actDynamicStats processes the `ActionDiktatsOpts` field from the action to construct a StatQueueProfile @@ -205,6 +240,7 @@ func (aL *actDynamicThreshold) execute(ctx *context.Context, data utils.MapStora type actDynamicStats struct { config *config.CGRConfig connMgr *engine.ConnManager + fltrS *engine.FilterS aCfg *utils.APAction tnt string cgrEv *utils.CGREvent @@ -230,140 +266,172 @@ func (aL *actDynamicStats) execute(ctx *context.Context, data utils.MapStorage, if len(aL.aCfg.Diktats) == 0 { return fmt.Errorf("No diktats were speified for action <%v>", aL.aCfg.ID) } - params := strings.Split(utils.IfaceAsString(aL.aCfg.Diktats[0].Opts[utils.MetaTemplate]), - utils.InfieldSep) - if len(params) != 14 { - return fmt.Errorf("invalid number of parameters <%d> expected 14", len(params)) - } - // parse dynamic parameters - for i := range params { - if params[i], err = utils.ParseParamForDataProvider(params[i], data, false); err != nil { + weights := make(map[string]float64) // stores sorting weights by Diktat ID + diktats := make([]*utils.APDiktat, 0) // list of diktats which have *template in opts, will be weight sorted later + for _, diktat := range aL.aCfg.Diktats { + if _, has := diktat.Opts[utils.MetaTemplate]; !has { + continue + } + if pass, err := aL.fltrS.Pass(ctx, aL.tnt, diktat.FilterIDs, data); err != nil { + return err + } else if !pass { + continue + } + weight, err := engine.WeightFromDynamics(ctx, diktat.Weights, aL.fltrS, aL.tnt, data) + if err != nil { return err } + weights[diktat.ID] = weight + diktats = append(diktats, diktat) } - // Prepare request arguments based on provided parameters. - args := &engine.StatQueueProfileWithAPIOpts{ - StatQueueProfile: &engine.StatQueueProfile{ - Tenant: params[0], - ID: params[1], - }, - APIOpts: make(map[string]any), - } - // populate Stat's FilterIDs - if params[2] != utils.EmptyString { - args.FilterIDs = strings.Split(params[2], utils.ANDSep) - } - // populate Stat's Weights - if params[3] != utils.EmptyString { - args.Weights = utils.DynamicWeights{&utils.DynamicWeight{}} - wghtSplit := strings.Split(params[3], utils.ANDSep) - if len(wghtSplit) > 2 { - return utils.ErrUnsupportedFormat + // Sort by weight (higher values first). + slices.SortFunc(diktats, func(a, b *utils.APDiktat) int { + return cmp.Compare(weights[b.ID], weights[a.ID]) + }) + for _, diktat := range diktats { + params := strings.Split(utils.IfaceAsString(diktat.Opts[utils.MetaTemplate]), + utils.InfieldSep) + if len(params) != 14 { + return fmt.Errorf("invalid number of parameters <%d> expected 14", len(params)) } - if wghtSplit[0] != utils.EmptyString { - args.Weights[0].FilterIDs = []string{wghtSplit[0]} - } - if wghtSplit[1] != utils.EmptyString { - args.Weights[0].Weight, err = strconv.ParseFloat(wghtSplit[1], 64) - if err != nil { + // parse dynamic parameters + for i := range params { + if params[i], err = utils.ParseParamForDataProvider(params[i], data, false); err != nil { return err } } - } - // populate Stat's Blockers - if params[4] != utils.EmptyString { - args.Blockers = utils.DynamicBlockers{&utils.DynamicBlocker{}} - blckrSplit := strings.Split(params[4], utils.ANDSep) - if len(blckrSplit) > 2 { - return utils.ErrUnsupportedFormat + // Prepare request arguments based on provided parameters. + args := &engine.StatQueueProfileWithAPIOpts{ + StatQueueProfile: &engine.StatQueueProfile{ + Tenant: params[0], + ID: params[1], + }, + APIOpts: make(map[string]any), } - if blckrSplit[0] != utils.EmptyString { - args.Blockers[0].FilterIDs = []string{blckrSplit[0]} + // populate Stat's FilterIDs + if params[2] != utils.EmptyString { + args.FilterIDs = strings.Split(params[2], utils.ANDSep) } - if blckrSplit[1] != utils.EmptyString { - args.Blockers[0].Blocker, err = strconv.ParseBool(blckrSplit[1]) - if err != nil { - return err + // populate Stat's Weights + if params[3] != utils.EmptyString { + args.Weights = utils.DynamicWeights{&utils.DynamicWeight{}} + wghtSplit := strings.Split(params[3], utils.ANDSep) + if len(wghtSplit) > 2 { + return utils.ErrUnsupportedFormat } - } - } - // populate Stat's QueueLengh - if params[5] != utils.EmptyString { - args.QueueLength, err = strconv.Atoi(params[5]) - if err != nil { - return err - } - } - // populate Stat's TTL - if params[6] != utils.EmptyString { - args.TTL, err = utils.ParseDurationWithNanosecs(params[6]) - if err != nil { - return err - } - } - // populate Stat's MinItems - if params[7] != utils.EmptyString { - args.MinItems, err = strconv.Atoi(params[7]) - if err != nil { - return err - } - } - // populate Stat's Stored - if params[8] != utils.EmptyString { - args.Stored, err = strconv.ParseBool(params[8]) - if err != nil { - return err - } - } - // populate Stat's ThresholdIDs - if params[9] != utils.EmptyString { - args.ThresholdIDs = strings.Split(params[9], utils.ANDSep) - } - // populate Stat's MetricID - if params[10] != utils.EmptyString { - metrics := strings.Split(params[10], utils.ANDSep) - args.Metrics = make([]*engine.MetricWithFilters, len(metrics)) - for i, strM := range metrics { - args.Metrics[i] = &engine.MetricWithFilters{MetricID: strM} - } - } - // populate Stat's metricFliterIDs - if params[11] != utils.EmptyString { - metricFliters := strings.Split(params[11], utils.ANDSep) - for i := range args.Metrics { - args.Metrics[i].FilterIDs = metricFliters - } - } - // populate Stat's metricBlockers - if params[12] != utils.EmptyString { - blckrSplit := strings.Split(params[12], utils.ANDSep) - if len(blckrSplit) > 2 { - return utils.ErrUnsupportedFormat - } - for i := range args.Metrics { - args.Metrics[i].Blockers = utils.DynamicBlockers{&utils.DynamicBlocker{}} - if blckrSplit[0] != utils.EmptyString { - args.Metrics[i].Blockers[0].FilterIDs = []string{blckrSplit[0]} + if wghtSplit[0] != utils.EmptyString { + args.Weights[0].FilterIDs = []string{wghtSplit[0]} } - if blckrSplit[1] != utils.EmptyString { - args.Metrics[i].Blockers[0].Blocker, err = strconv.ParseBool(blckrSplit[1]) + if wghtSplit[1] != utils.EmptyString { + args.Weights[0].Weight, err = strconv.ParseFloat(wghtSplit[1], 64) if err != nil { return err } } } - } - // populate Stat's APIOpts - if params[13] != utils.EmptyString { - if err := parseParamStringToMap(params[13], args.APIOpts); err != nil { + // populate Stat's Blockers + if params[4] != utils.EmptyString { + args.Blockers = utils.DynamicBlockers{&utils.DynamicBlocker{}} + blckrSplit := strings.Split(params[4], utils.ANDSep) + if len(blckrSplit) > 2 { + return utils.ErrUnsupportedFormat + } + if blckrSplit[0] != utils.EmptyString { + args.Blockers[0].FilterIDs = []string{blckrSplit[0]} + } + if blckrSplit[1] != utils.EmptyString { + args.Blockers[0].Blocker, err = strconv.ParseBool(blckrSplit[1]) + if err != nil { + return err + } + } + } + // populate Stat's QueueLengh + if params[5] != utils.EmptyString { + args.QueueLength, err = strconv.Atoi(params[5]) + if err != nil { + return err + } + } + // populate Stat's TTL + if params[6] != utils.EmptyString { + args.TTL, err = utils.ParseDurationWithNanosecs(params[6]) + if err != nil { + return err + } + } + // populate Stat's MinItems + if params[7] != utils.EmptyString { + args.MinItems, err = strconv.Atoi(params[7]) + if err != nil { + return err + } + } + // populate Stat's Stored + if params[8] != utils.EmptyString { + args.Stored, err = strconv.ParseBool(params[8]) + if err != nil { + return err + } + } + // populate Stat's ThresholdIDs + if params[9] != utils.EmptyString { + args.ThresholdIDs = strings.Split(params[9], utils.ANDSep) + } + // populate Stat's MetricID + if params[10] != utils.EmptyString { + metrics := strings.Split(params[10], utils.ANDSep) + args.Metrics = make([]*engine.MetricWithFilters, len(metrics)) + for i, strM := range metrics { + args.Metrics[i] = &engine.MetricWithFilters{MetricID: strM} + } + } + // populate Stat's metricFliterIDs + if params[11] != utils.EmptyString { + metricFliters := strings.Split(params[11], utils.ANDSep) + for i := range args.Metrics { + args.Metrics[i].FilterIDs = metricFliters + } + } + // populate Stat's metricBlockers + if params[12] != utils.EmptyString { + blckrSplit := strings.Split(params[12], utils.ANDSep) + if len(blckrSplit) > 2 { + return utils.ErrUnsupportedFormat + } + for i := range args.Metrics { + args.Metrics[i].Blockers = utils.DynamicBlockers{&utils.DynamicBlocker{}} + if blckrSplit[0] != utils.EmptyString { + args.Metrics[i].Blockers[0].FilterIDs = []string{blckrSplit[0]} + } + if blckrSplit[1] != utils.EmptyString { + args.Metrics[i].Blockers[0].Blocker, err = strconv.ParseBool(blckrSplit[1]) + if err != nil { + return err + } + } + } + } + // populate Stat's APIOpts + if params[13] != utils.EmptyString { + if err := parseParamStringToMap(params[13], args.APIOpts); err != nil { + return err + } + } + + // create the StatQueueProfile based on the populated parameters + var rply string + if err = aL.connMgr.Call(ctx, aL.config.ActionSCfg().AdminSConns, + utils.AdminSv1SetStatQueueProfile, args, &rply); err != nil { return err } + if blocker, err := engine.BlockerFromDynamics(ctx, diktat.Blockers, aL.fltrS, aL.tnt, data); err != nil { + return err + } else if blocker { + break + } } - - // create the StatQueueProfile based on the populated parameters - var rply string - return aL.connMgr.Call(ctx, aL.config.ActionSCfg().AdminSConns, - utils.AdminSv1SetStatQueueProfile, args, &rply) + return } // actDynamicAttribute processes the `ActionDiktatsOpts` field from the action to construct a AttributeProfile @@ -386,6 +454,7 @@ func (aL *actDynamicStats) execute(ctx *context.Context, data utils.MapStorage, type actDynamicAttribute struct { config *config.CGRConfig connMgr *engine.ConnManager + fltrS *engine.FilterS aCfg *utils.APAction tnt string cgrEv *utils.CGREvent @@ -411,105 +480,137 @@ func (aL *actDynamicAttribute) execute(ctx *context.Context, data utils.MapStora if len(aL.aCfg.Diktats) == 0 { return fmt.Errorf("No diktats were speified for action <%v>", aL.aCfg.ID) } - params := strings.Split(utils.IfaceAsString(aL.aCfg.Diktats[0].Opts[utils.MetaTemplate]), - utils.InfieldSep) - if len(params) != 11 { - return fmt.Errorf("invalid number of parameters <%d> expected 11", len(params)) - } - // parse dynamic parameters - for i := range params { - if params[i], err = utils.ParseParamForDataProvider(params[i], data, false); err != nil { + weights := make(map[string]float64) // stores sorting weights by Diktat ID + diktats := make([]*utils.APDiktat, 0) // list of diktats which have *template in opts, will be weight sorted later + for _, diktat := range aL.aCfg.Diktats { + if _, has := diktat.Opts[utils.MetaTemplate]; !has { + continue + } + if pass, err := aL.fltrS.Pass(ctx, aL.tnt, diktat.FilterIDs, data); err != nil { + return err + } else if !pass { + continue + } + weight, err := engine.WeightFromDynamics(ctx, diktat.Weights, aL.fltrS, aL.tnt, data) + if err != nil { return err } + weights[diktat.ID] = weight + diktats = append(diktats, diktat) } - // Prepare request arguments based on provided parameters. - args := &utils.APIAttributeProfileWithAPIOpts{ - APIAttributeProfile: &utils.APIAttributeProfile{ - Tenant: params[0], - ID: params[1], - }, - APIOpts: make(map[string]any), - } - // populate Attribute's FilterIDs - if params[2] != utils.EmptyString { - args.FilterIDs = strings.Split(params[2], utils.ANDSep) - } - // populate Attribute's Weights - if params[3] != utils.EmptyString { - args.Weights = utils.DynamicWeights{&utils.DynamicWeight{}} - wghtSplit := strings.Split(params[3], utils.ANDSep) - if len(wghtSplit) > 2 { - return utils.ErrUnsupportedFormat + // Sort by weight (higher values first). + slices.SortFunc(diktats, func(a, b *utils.APDiktat) int { + return cmp.Compare(weights[b.ID], weights[a.ID]) + }) + for _, diktat := range diktats { + params := strings.Split(utils.IfaceAsString(diktat.Opts[utils.MetaTemplate]), + utils.InfieldSep) + if len(params) != 11 { + return fmt.Errorf("invalid number of parameters <%d> expected 11", len(params)) } - if wghtSplit[0] != utils.EmptyString { - args.Weights[0].FilterIDs = []string{wghtSplit[0]} - } - if wghtSplit[1] != utils.EmptyString { - args.Weights[0].Weight, err = strconv.ParseFloat(wghtSplit[1], 64) - if err != nil { + // parse dynamic parameters + for i := range params { + if params[i], err = utils.ParseParamForDataProvider(params[i], data, false); err != nil { return err } } - } - // populate Attribute's Blockers - if params[4] != utils.EmptyString { - args.Blockers = utils.DynamicBlockers{&utils.DynamicBlocker{}} - blckrSplit := strings.Split(params[4], utils.ANDSep) - if len(blckrSplit) > 2 { - return utils.ErrUnsupportedFormat + // Prepare request arguments based on provided parameters. + args := &utils.APIAttributeProfileWithAPIOpts{ + APIAttributeProfile: &utils.APIAttributeProfile{ + Tenant: params[0], + ID: params[1], + }, + APIOpts: make(map[string]any), } - if blckrSplit[0] != utils.EmptyString { - args.Blockers[0].FilterIDs = []string{blckrSplit[0]} + // populate Attribute's FilterIDs + if params[2] != utils.EmptyString { + args.FilterIDs = strings.Split(params[2], utils.ANDSep) } - if blckrSplit[1] != utils.EmptyString { - args.Blockers[0].Blocker, err = strconv.ParseBool(blckrSplit[1]) - if err != nil { - return err - } - } - } - // populate Attribute's Attributes - if params[7] != utils.EmptyString { - var attrFltrIDs []string - if params[5] != utils.EmptyString { - attrFltrIDs = strings.Split(params[5], utils.ANDSep) - } - var attrFltrBlckrs utils.DynamicBlockers - if params[6] != utils.EmptyString { - attrFltrBlckrs = utils.DynamicBlockers{&utils.DynamicBlocker{}} - blckrSplit := strings.Split(params[6], utils.ANDSep) - if len(blckrSplit) > 2 { + // populate Attribute's Weights + if params[3] != utils.EmptyString { + args.Weights = utils.DynamicWeights{&utils.DynamicWeight{}} + wghtSplit := strings.Split(params[3], utils.ANDSep) + if len(wghtSplit) > 2 { return utils.ErrUnsupportedFormat } - if blckrSplit[0] != utils.EmptyString { - attrFltrBlckrs[0].FilterIDs = []string{blckrSplit[0]} + if wghtSplit[0] != utils.EmptyString { + args.Weights[0].FilterIDs = []string{wghtSplit[0]} } - if blckrSplit[1] != utils.EmptyString { - attrFltrBlckrs[0].Blocker, err = strconv.ParseBool(blckrSplit[1]) + if wghtSplit[1] != utils.EmptyString { + args.Weights[0].Weight, err = strconv.ParseFloat(wghtSplit[1], 64) if err != nil { return err } } } - args.Attributes = append(args.Attributes, &utils.ExternalAttribute{ - FilterIDs: attrFltrIDs, - Blockers: attrFltrBlckrs, - Path: params[7], - Type: params[8], - Value: params[9], - }) - } - // populate Attribute's APIOpts - if params[10] != utils.EmptyString { - if err := parseParamStringToMap(params[10], args.APIOpts); err != nil { + // populate Attribute's Blockers + if params[4] != utils.EmptyString { + args.Blockers = utils.DynamicBlockers{&utils.DynamicBlocker{}} + blckrSplit := strings.Split(params[4], utils.ANDSep) + if len(blckrSplit) > 2 { + return utils.ErrUnsupportedFormat + } + if blckrSplit[0] != utils.EmptyString { + args.Blockers[0].FilterIDs = []string{blckrSplit[0]} + } + if blckrSplit[1] != utils.EmptyString { + args.Blockers[0].Blocker, err = strconv.ParseBool(blckrSplit[1]) + if err != nil { + return err + } + } + } + // populate Attribute's Attributes + if params[7] != utils.EmptyString { + var attrFltrIDs []string + if params[5] != utils.EmptyString { + attrFltrIDs = strings.Split(params[5], utils.ANDSep) + } + var attrFltrBlckrs utils.DynamicBlockers + if params[6] != utils.EmptyString { + attrFltrBlckrs = utils.DynamicBlockers{&utils.DynamicBlocker{}} + blckrSplit := strings.Split(params[6], utils.ANDSep) + if len(blckrSplit) > 2 { + return utils.ErrUnsupportedFormat + } + if blckrSplit[0] != utils.EmptyString { + attrFltrBlckrs[0].FilterIDs = []string{blckrSplit[0]} + } + if blckrSplit[1] != utils.EmptyString { + attrFltrBlckrs[0].Blocker, err = strconv.ParseBool(blckrSplit[1]) + if err != nil { + return err + } + } + } + args.Attributes = append(args.Attributes, &utils.ExternalAttribute{ + FilterIDs: attrFltrIDs, + Blockers: attrFltrBlckrs, + Path: params[7], + Type: params[8], + Value: params[9], + }) + } + // populate Attribute's APIOpts + if params[10] != utils.EmptyString { + if err := parseParamStringToMap(params[10], args.APIOpts); err != nil { + return err + } + } + + // create the AttributeProfile based on the populated parameters + var rply string + if err = aL.connMgr.Call(ctx, aL.config.ActionSCfg().AdminSConns, + utils.AdminSv1SetAttributeProfile, args, &rply); err != nil { return err } + if blocker, err := engine.BlockerFromDynamics(ctx, diktat.Blockers, aL.fltrS, aL.tnt, data); err != nil { + return err + } else if blocker { + break + } } - - // create the AttributeProfile based on the populated parameters - var rply string - return aL.connMgr.Call(ctx, aL.config.ActionSCfg().AdminSConns, - utils.AdminSv1SetAttributeProfile, args, &rply) + return } // actDynamicResource processes the `ActionDiktatsOpts` field from the action to construct a ResourceProfile @@ -532,6 +633,7 @@ func (aL *actDynamicAttribute) execute(ctx *context.Context, data utils.MapStora type actDynamicResource struct { config *config.CGRConfig connMgr *engine.ConnManager + fltrS *engine.FilterS aCfg *utils.APAction tnt string cgrEv *utils.CGREvent @@ -557,88 +659,120 @@ func (aL *actDynamicResource) execute(ctx *context.Context, data utils.MapStorag if len(aL.aCfg.Diktats) == 0 { return fmt.Errorf("No diktats were speified for action <%v>", aL.aCfg.ID) } - params := strings.Split(utils.IfaceAsString(aL.aCfg.Diktats[0].Opts[utils.MetaTemplate]), - utils.InfieldSep) - if len(params) != 11 { - return fmt.Errorf("invalid number of parameters <%d> expected 11", len(params)) - } - // parse dynamic parameters - for i := range params { - if params[i], err = utils.ParseParamForDataProvider(params[i], data, false); err != nil { + weights := make(map[string]float64) // stores sorting weights by Diktat ID + diktats := make([]*utils.APDiktat, 0) // list of diktats which have *template in opts, will be weight sorted later + for _, diktat := range aL.aCfg.Diktats { + if _, has := diktat.Opts[utils.MetaTemplate]; !has { + continue + } + if pass, err := aL.fltrS.Pass(ctx, aL.tnt, diktat.FilterIDs, data); err != nil { + return err + } else if !pass { + continue + } + weight, err := engine.WeightFromDynamics(ctx, diktat.Weights, aL.fltrS, aL.tnt, data) + if err != nil { return err } + weights[diktat.ID] = weight + diktats = append(diktats, diktat) } - // Prepare request arguments based on provided parameters. - args := &utils.ResourceProfileWithAPIOpts{ - ResourceProfile: &utils.ResourceProfile{ - Tenant: params[0], - ID: params[1], - AllocationMessage: params[6], - }, - APIOpts: make(map[string]any), - } - // populate Resource's FilterIDs - if params[2] != utils.EmptyString { - args.FilterIDs = strings.Split(params[2], utils.ANDSep) - } - // populate Resource's Weights - if params[3] != utils.EmptyString { - args.Weights = utils.DynamicWeights{&utils.DynamicWeight{}} - wghtSplit := strings.Split(params[3], utils.ANDSep) - if len(wghtSplit) > 2 { - return utils.ErrUnsupportedFormat + // Sort by weight (higher values first). + slices.SortFunc(diktats, func(a, b *utils.APDiktat) int { + return cmp.Compare(weights[b.ID], weights[a.ID]) + }) + for _, diktat := range diktats { + params := strings.Split(utils.IfaceAsString(diktat.Opts[utils.MetaTemplate]), + utils.InfieldSep) + if len(params) != 11 { + return fmt.Errorf("invalid number of parameters <%d> expected 11", len(params)) } - if wghtSplit[0] != utils.EmptyString { - args.Weights[0].FilterIDs = []string{wghtSplit[0]} + // parse dynamic parameters + for i := range params { + if params[i], err = utils.ParseParamForDataProvider(params[i], data, false); err != nil { + return err + } } - if wghtSplit[1] != utils.EmptyString { - args.Weights[0].Weight, err = strconv.ParseFloat(wghtSplit[1], 64) + // Prepare request arguments based on provided parameters. + args := &utils.ResourceProfileWithAPIOpts{ + ResourceProfile: &utils.ResourceProfile{ + Tenant: params[0], + ID: params[1], + AllocationMessage: params[6], + }, + APIOpts: make(map[string]any), + } + // populate Resource's FilterIDs + if params[2] != utils.EmptyString { + args.FilterIDs = strings.Split(params[2], utils.ANDSep) + } + // populate Resource's Weights + if params[3] != utils.EmptyString { + args.Weights = utils.DynamicWeights{&utils.DynamicWeight{}} + wghtSplit := strings.Split(params[3], utils.ANDSep) + if len(wghtSplit) > 2 { + return utils.ErrUnsupportedFormat + } + if wghtSplit[0] != utils.EmptyString { + args.Weights[0].FilterIDs = []string{wghtSplit[0]} + } + if wghtSplit[1] != utils.EmptyString { + args.Weights[0].Weight, err = strconv.ParseFloat(wghtSplit[1], 64) + if err != nil { + return err + } + } + } + // populate Resource's UsageTTL + if params[4] != utils.EmptyString { + args.UsageTTL, err = utils.ParseDurationWithNanosecs(params[4]) if err != nil { return err } } - } - // populate Resource's UsageTTL - if params[4] != utils.EmptyString { - args.UsageTTL, err = utils.ParseDurationWithNanosecs(params[4]) - if err != nil { - return err + // populate Resource's Limit + if params[5] != utils.EmptyString { + args.Limit, err = strconv.ParseFloat(params[5], 64) + if err != nil { + return err + } } - } - // populate Resource's Limit - if params[5] != utils.EmptyString { - args.Limit, err = strconv.ParseFloat(params[5], 64) - if err != nil { - return err + // populate Resource's Blocker + if params[7] != utils.EmptyString { + args.Blocker, err = strconv.ParseBool(params[7]) + if err != nil { + return err + } } - } - // populate Resource's Blocker - if params[7] != utils.EmptyString { - args.Blocker, err = strconv.ParseBool(params[7]) - if err != nil { - return err + // populate Resource's Stored + if params[8] != utils.EmptyString { + args.Stored, err = strconv.ParseBool(params[8]) + if err != nil { + return err + } } - } - // populate Resource's Stored - if params[8] != utils.EmptyString { - args.Stored, err = strconv.ParseBool(params[8]) - if err != nil { - return err + // populate Resource's ThresholdIDs + if params[9] != utils.EmptyString { + args.ThresholdIDs = strings.Split(params[9], utils.ANDSep) } - } - // populate Resource's ThresholdIDs - if params[9] != utils.EmptyString { - args.ThresholdIDs = strings.Split(params[9], utils.ANDSep) - } - // populate Resource's APIOpts - if params[10] != utils.EmptyString { - if err := parseParamStringToMap(params[10], args.APIOpts); err != nil { - return err + // populate Resource's APIOpts + if params[10] != utils.EmptyString { + if err := parseParamStringToMap(params[10], args.APIOpts); err != nil { + return err + } } - } - // create the ResourceProfile based on the populated parameters - var rply string - return aL.connMgr.Call(ctx, aL.config.ActionSCfg().AdminSConns, - utils.AdminSv1SetResourceProfile, args, &rply) + // create the ResourceProfile based on the populated parameters + var rply string + if err = aL.connMgr.Call(ctx, aL.config.ActionSCfg().AdminSConns, + utils.AdminSv1SetResourceProfile, args, &rply); err != nil { + return err + } + if blocker, err := engine.BlockerFromDynamics(ctx, diktat.Blockers, aL.fltrS, aL.tnt, data); err != nil { + return err + } else if blocker { + break + } + } + return } diff --git a/actions/libactions.go b/actions/libactions.go index e78ebd3ec..420c505f9 100644 --- a/actions/libactions.go +++ b/actions/libactions.go @@ -138,13 +138,13 @@ func newActioner(ctx *context.Context, cgrEv *utils.CGREvent, cfg *config.CGRCon case utils.MetaRemBalance: return &actRemBalance{cfg, connMgr, aCfg, tnt}, nil case utils.MetaDynamicThreshold: - return &actDynamicThreshold{cfg, connMgr, aCfg, tnt, cgrEv}, nil + return &actDynamicThreshold{cfg, connMgr, fltrS, aCfg, tnt, cgrEv}, nil case utils.MetaDynamicStats: - return &actDynamicStats{cfg, connMgr, aCfg, tnt, cgrEv}, nil + return &actDynamicStats{cfg, connMgr, fltrS, aCfg, tnt, cgrEv}, nil case utils.MetaDynamicAttribute: - return &actDynamicAttribute{cfg, connMgr, aCfg, tnt, cgrEv}, nil + return &actDynamicAttribute{cfg, connMgr, fltrS, aCfg, tnt, cgrEv}, nil case utils.MetaDynamicResource: - return &actDynamicResource{cfg, connMgr, aCfg, tnt, cgrEv}, nil + return &actDynamicResource{cfg, connMgr, fltrS, aCfg, tnt, cgrEv}, nil default: return nil, fmt.Errorf("unsupported action type: <%s>", aCfg.Type) diff --git a/general_tests/dynamic_thresholds_it_test.go b/general_tests/dynamic_thresholds_it_test.go index 6ea8b29ed..5667223ee 100644 --- a/general_tests/dynamic_thresholds_it_test.go +++ b/general_tests/dynamic_thresholds_it_test.go @@ -209,10 +209,55 @@ func TestDynThdIT(t *testing.T) { Type: utils.MetaDynamicThreshold, Diktats: []*utils.APDiktat{ { - ID: "CreateDynamicThreshold1001", + ID: "CreateDynamicThreshold1002", + FilterIDs: []string{"*string:~*req.Account:1002"}, Opts: map[string]any{ "*template": "*tenant;DYNAMICLY_THD_<~*req.Account>;*string:~*req.Account:1002;*string:~*req.Account:1002&10;1;1;1s;false;ACT_LOG_WARNING;true;~*opts", }, + Weights: utils.DynamicWeights{ + { + Weight: 50, + }, + }, + }, + { + ID: "CreateDynamicThreshold1002NotFoundFilter", + FilterIDs: []string{"*string:~*req.Account:1003"}, + Opts: map[string]any{ + "*template": "*tenant;DYNAMICLY_THD_2_<~*req.Account>;*string:~*req.Account:1002;*string:~*req.Account:1002&10;1;1;1s;false;ACT_LOG_WARNING;true;~*opts", + }, + Weights: utils.DynamicWeights{ + { + Weight: 90, + }, + }, + }, + { + ID: "CreateDynamicThreshold1002Blocker", + Opts: map[string]any{ + "*template": "*tenant;DYNAMICLY_THD_3_<~*req.Account>;*string:~*req.Account:1002;*string:~*req.Account:1002&10;1;1;1s;false;ACT_LOG_WARNING;true;~*opts", + }, + Weights: utils.DynamicWeights{ + { + Weight: 20, + }, + }, + Blockers: utils.DynamicBlockers{ + { + Blocker: true, + }, + }, + }, + { + ID: "CreateDynamicThreshold1002Blocked", + Opts: map[string]any{ + "*template": "*tenant;DYNAMICLY_THD_4_<~*req.Account>;*string:~*req.Account:1002;*string:~*req.Account:1002&10;1;1;1s;false;ACT_LOG_WARNING;true;~*opts", + }, + Weights: utils.DynamicWeights{ + { + Weight: 10, + }, + }, }, }, }, @@ -221,10 +266,55 @@ func TestDynThdIT(t *testing.T) { Type: utils.MetaDynamicStats, Diktats: []*utils.APDiktat{ { - ID: "CreateDynamicStat1001", + ID: "CreateDynamicStat1002", + FilterIDs: []string{"*string:~*req.Account:1002"}, Opts: map[string]any{ "*template": "*tenant;DYNAMICLY_STAT_<~*req.Account>;*string:~*req.Account:1002;*string:~*req.Account:1002&30;*string:~*req.Account:1002&true;100;-1;0;false;*none;*tcc&*tcd;*string:~*req.Account:1002;*string:~*req.Account:1002&true;~*opts", }, + Weights: utils.DynamicWeights{ + { + Weight: 50, + }, + }, + }, + { + ID: "CreateDynamicStat1002NotFoundFilter", + FilterIDs: []string{"*string:~*req.Account:1003"}, + Opts: map[string]any{ + "*template": "*tenant;DYNAMICLY_STAT_2_<~*req.Account>;*string:~*req.Account:1002;*string:~*req.Account:1002&30;*string:~*req.Account:1002&true;100;-1;0;false;*none;*tcc&*tcd;*string:~*req.Account:1002;*string:~*req.Account:1002&true;~*opts", + }, + Weights: utils.DynamicWeights{ + { + Weight: 90, + }, + }, + }, + { + ID: "CreateDynamicStat1002Blocker", + Opts: map[string]any{ + "*template": "*tenant;DYNAMICLY_STAT_3_<~*req.Account>;*string:~*req.Account:1002;*string:~*req.Account:1002&30;*string:~*req.Account:1002&true;100;-1;0;false;*none;*tcc&*tcd;*string:~*req.Account:1002;*string:~*req.Account:1002&true;~*opts", + }, + Weights: utils.DynamicWeights{ + { + Weight: 20, + }, + }, + Blockers: utils.DynamicBlockers{ + { + Blocker: true, + }, + }, + }, + { + ID: "CreateDynamicStat10022Blocked", + Opts: map[string]any{ + "*template": "*tenant;DYNAMICLY_STAT_4_<~*req.Account>;*string:~*req.Account:1002;*string:~*req.Account:1002&30;*string:~*req.Account:1002&true;100;-1;0;false;*none;*tcc&*tcd;*string:~*req.Account:1002;*string:~*req.Account:1002&true;~*opts", + }, + Weights: utils.DynamicWeights{ + { + Weight: 10, + }, + }, }, }, }, @@ -233,22 +323,112 @@ func TestDynThdIT(t *testing.T) { Type: utils.MetaDynamicAttribute, Diktats: []*utils.APDiktat{ { - ID: "CreateDynamicAttribute1001", + ID: "CreateDynamicAttribute1002", + FilterIDs: []string{"*string:~*req.Account:1002"}, Opts: map[string]any{ "*template": "*tenant;DYNAMICLY_ATTR_<~*req.Account>;*string:~*req.Account:<~*req.Account>;*string:~*req.Account:<~*req.Account>&30;*string:~*req.Account:<~*req.Account>&true;*string:~*req.Account:<~*req.Account>;*string:~*req.Account:<~*req.Account>&true;*req.Subject;*constant;SUPPLIER1;~*opts", }, + Weights: utils.DynamicWeights{ + { + Weight: 50, + }, + }, + }, + { + ID: "CreateDynamicAttribute1002", + FilterIDs: []string{"*string:~*req.Account:1003NotFoundFilter"}, + Opts: map[string]any{ + "*template": "*tenant;DYNAMICLY_ATTR_2_<~*req.Account>;*string:~*req.Account:<~*req.Account>;*string:~*req.Account:<~*req.Account>&30;*string:~*req.Account:<~*req.Account>&true;*string:~*req.Account:<~*req.Account>;*string:~*req.Account:<~*req.Account>&true;*req.Subject;*constant;SUPPLIER1;~*opts", + }, + Weights: utils.DynamicWeights{ + { + Weight: 90, + }, + }, + }, + { + ID: "CreateDynamicAttribute1002Blockers", + Opts: map[string]any{ + "*template": "*tenant;DYNAMICLY_ATTR_3_<~*req.Account>;*string:~*req.Account:<~*req.Account>;*string:~*req.Account:<~*req.Account>&30;*string:~*req.Account:<~*req.Account>&true;*string:~*req.Account:<~*req.Account>;*string:~*req.Account:<~*req.Account>&true;*req.Subject;*constant;SUPPLIER1;~*opts", + }, + Weights: utils.DynamicWeights{ + { + Weight: 20, + }, + }, + Blockers: utils.DynamicBlockers{ + { + Blocker: true, + }, + }, + }, + { + ID: "CreateDynamicAttribute1002Blocked", + Opts: map[string]any{ + "*template": "*tenant;DYNAMICLY_ATTR_4_<~*req.Account>;*string:~*req.Account:<~*req.Account>;*string:~*req.Account:<~*req.Account>&30;*string:~*req.Account:<~*req.Account>&true;*string:~*req.Account:<~*req.Account>;*string:~*req.Account:<~*req.Account>&true;*req.Subject;*constant;SUPPLIER1;~*opts", + }, + Weights: utils.DynamicWeights{ + { + Weight: 10, + }, + }, }, }, }, { - ID: "Dynamic_Attribute_ID", + ID: "Dynamic_Resource_ID", Type: utils.MetaDynamicResource, Diktats: []*utils.APDiktat{ { - ID: "CreateDynamicResource1001", + ID: "CreateDynamicResource1002", + FilterIDs: []string{"*string:~*req.Account:1002"}, Opts: map[string]any{ "*template": "*tenant;DYNAMICLY_RES_<~*req.Account>;*string:~*req.Account:<~*req.Account>;*string:~*req.Account:<~*req.Account>&30;5s;5;alloc_msg;true;true;THID1&THID2;~*opts", }, + Weights: utils.DynamicWeights{ + { + Weight: 50, + }, + }, + }, + { + ID: "CreateDynamicResource1002NotFoundFilter", + FilterIDs: []string{"*string:~*req.Account:1003"}, + Opts: map[string]any{ + "*template": "*tenant;DYNAMICLY_RES_2_<~*req.Account>;*string:~*req.Account:<~*req.Account>;*string:~*req.Account:<~*req.Account>&30;5s;5;alloc_msg;true;true;THID1&THID2;~*opts", + }, + Weights: utils.DynamicWeights{ + { + Weight: 90, + }, + }, + }, + { + ID: "CreateDynamicResource1002Blocker", + Opts: map[string]any{ + "*template": "*tenant;DYNAMICLY_RES_3_<~*req.Account>;*string:~*req.Account:<~*req.Account>;*string:~*req.Account:<~*req.Account>&30;5s;5;alloc_msg;true;true;THID1&THID2;~*opts", + }, + Weights: utils.DynamicWeights{ + { + Weight: 20, + }, + }, + Blockers: utils.DynamicBlockers{ + { + Blocker: true, + }, + }, + }, + { + ID: "CreateDynamicResource1002Blocked", + Opts: map[string]any{ + "*template": "*tenant;DYNAMICLY_RES_4_<~*req.Account>;*string:~*req.Account:<~*req.Account>;*string:~*req.Account:<~*req.Account>&30;5s;5;alloc_msg;true;true;THID1&THID2;~*opts", + }, + Weights: utils.DynamicWeights{ + { + Weight: 10, + }, + }, }, }, }, @@ -360,8 +540,8 @@ func TestDynThdIT(t *testing.T) { }, &thrsholds); err != nil { t.Errorf("AdminSv1GetThresholdProfiles failed unexpectedly: %v", err) } - if len(thrsholds) != 2 { - t.Fatalf("AdminSv1GetThresholdProfiles len(thrsholds)=%v, want 2", len(thrsholds)) + if len(thrsholds) != 3 { + t.Fatalf("AdminSv1GetThresholdProfiles len(thrsholds)=%v, want 3", len(thrsholds)) } sort.Slice(thrsholds, func(i, j int) bool { return thrsholds[i].ID > thrsholds[j].ID @@ -379,6 +559,23 @@ func TestDynThdIT(t *testing.T) { ActionProfileIDs: []string{"DYNAMIC_THRESHOLD_ACTION"}, Async: true, }, + { + Tenant: utils.CGRateSorg, + ID: "DYNAMICLY_THD_3_1002", + FilterIDs: []string{"*string:~*req.Account:1002"}, + MaxHits: 1, + MinHits: 1, + MinSleep: time.Second, + Blocker: false, + Weights: utils.DynamicWeights{ + &utils.DynamicWeight{ + FilterIDs: []string{"*string:~*req.Account:1002"}, + Weight: 10, + }, + }, + ActionProfileIDs: []string{"ACT_LOG_WARNING"}, + Async: true, + }, { Tenant: utils.CGRateSorg, ID: "DYNAMICLY_THD_1002", @@ -403,60 +600,110 @@ func TestDynThdIT(t *testing.T) { }) t.Run("GetDynamicStatQueueProfile", func(t *testing.T) { - exp := &engine.StatQueueProfile{ - Tenant: utils.CGRateSorg, - ID: "DYNAMICLY_STAT_1002", - FilterIDs: []string{"*string:~*req.Account:1002"}, - Weights: utils.DynamicWeights{ - { - FilterIDs: []string{"*string:~*req.Account:1002"}, - Weight: 30, + exp := []*engine.StatQueueProfile{ + { + Tenant: utils.CGRateSorg, + ID: "DYNAMICLY_STAT_3_1002", + FilterIDs: []string{"*string:~*req.Account:1002"}, + Weights: utils.DynamicWeights{ + { + FilterIDs: []string{"*string:~*req.Account:1002"}, + Weight: 30, + }, }, - }, - Blockers: utils.DynamicBlockers{ - { - FilterIDs: []string{"*string:~*req.Account:1002"}, - Blocker: true, + Blockers: utils.DynamicBlockers{ + { + FilterIDs: []string{"*string:~*req.Account:1002"}, + Blocker: true, + }, }, - }, - QueueLength: 100, - TTL: -1, - MinItems: 0, - Stored: false, - ThresholdIDs: []string{utils.MetaNone}, - Metrics: []*engine.MetricWithFilters{ - { - MetricID: utils.MetaTCC, - FilterIDs: []string{"*string:~*req.Account:1002"}, - Blockers: utils.DynamicBlockers{ - { - FilterIDs: []string{"*string:~*req.Account:1002"}, - Blocker: true, + QueueLength: 100, + TTL: -1, + MinItems: 0, + Stored: false, + ThresholdIDs: []string{utils.MetaNone}, + Metrics: []*engine.MetricWithFilters{ + { + MetricID: utils.MetaTCC, + FilterIDs: []string{"*string:~*req.Account:1002"}, + Blockers: utils.DynamicBlockers{ + { + FilterIDs: []string{"*string:~*req.Account:1002"}, + Blocker: true, + }, + }, + }, + { + MetricID: utils.MetaTCD, + FilterIDs: []string{"*string:~*req.Account:1002"}, + Blockers: utils.DynamicBlockers{ + { + FilterIDs: []string{"*string:~*req.Account:1002"}, + Blocker: true, + }, }, }, }, - { - MetricID: utils.MetaTCD, - FilterIDs: []string{"*string:~*req.Account:1002"}, - Blockers: utils.DynamicBlockers{ - { - FilterIDs: []string{"*string:~*req.Account:1002"}, - Blocker: true, + }, + { + Tenant: utils.CGRateSorg, + ID: "DYNAMICLY_STAT_1002", + FilterIDs: []string{"*string:~*req.Account:1002"}, + Weights: utils.DynamicWeights{ + { + FilterIDs: []string{"*string:~*req.Account:1002"}, + Weight: 30, + }, + }, + Blockers: utils.DynamicBlockers{ + { + FilterIDs: []string{"*string:~*req.Account:1002"}, + Blocker: true, + }, + }, + QueueLength: 100, + TTL: -1, + MinItems: 0, + Stored: false, + ThresholdIDs: []string{utils.MetaNone}, + Metrics: []*engine.MetricWithFilters{ + { + MetricID: utils.MetaTCC, + FilterIDs: []string{"*string:~*req.Account:1002"}, + Blockers: utils.DynamicBlockers{ + { + FilterIDs: []string{"*string:~*req.Account:1002"}, + Blocker: true, + }, + }, + }, + { + MetricID: utils.MetaTCD, + FilterIDs: []string{"*string:~*req.Account:1002"}, + Blockers: utils.DynamicBlockers{ + { + FilterIDs: []string{"*string:~*req.Account:1002"}, + Blocker: true, + }, }, }, }, }, } - var rply *engine.StatQueueProfile - if err := client.Call(context.Background(), utils.AdminSv1GetStatQueueProfile, &utils.TenantIDWithAPIOpts{ - TenantID: &utils.TenantID{ - Tenant: utils.CGRateSorg, - ID: "DYNAMICLY_STAT_1002", - }, + var rply []*engine.StatQueueProfile + if err := client.Call(context.Background(), utils.AdminSv1GetStatQueueProfiles, &utils.ArgsItemIDs{ + Tenant: utils.CGRateSorg, }, &rply); err != nil { t.Error(err) - } else if !reflect.DeepEqual(exp, rply) { + } else if len(rply) != 2 { + t.Fatalf("AdminSv1GetStatQueueProfiles len(rply)=%v, want 2", len(rply)) + } + sort.Slice(rply, func(i, j int) bool { + return rply[i].ID > rply[j].ID + }) + + if !reflect.DeepEqual(exp, rply) { t.Errorf("Expected <%v> \n received <%v>", utils.ToJSON(exp), utils.ToJSON(rply)) } }) @@ -469,11 +716,44 @@ func TestDynThdIT(t *testing.T) { }, &attrs); err != nil { t.Errorf("AdminSv1GetAttributeProfiles failed unexpectedly: %v", err) } - if len(attrs) != 1 { - t.Fatalf("AdminSv1GetAttributeProfiles len(attrs)=%v, want 1", len(attrs)) + if len(attrs) != 2 { + t.Fatalf("AdminSv1GetAttributeProfiles len(attrs)=%v, want 2", len(attrs)) } - + sort.Slice(attrs, func(i, j int) bool { + return attrs[i].ID > attrs[j].ID + }) exp := []*utils.APIAttributeProfile{ + { + Tenant: utils.CGRateSorg, + ID: "DYNAMICLY_ATTR_3_1002", + FilterIDs: []string{"*string:~*req.Account:1002"}, + Weights: utils.DynamicWeights{ + { + FilterIDs: []string{"*string:~*req.Account:1002"}, + Weight: 30, + }, + }, + Blockers: utils.DynamicBlockers{ + { + FilterIDs: []string{"*string:~*req.Account:1002"}, + Blocker: true, + }, + }, + Attributes: []*utils.ExternalAttribute{ + { + FilterIDs: []string{"*string:~*req.Account:1002"}, + Blockers: utils.DynamicBlockers{ + { + FilterIDs: []string{"*string:~*req.Account:1002"}, + Blocker: true, + }, + }, + Path: "*req.Subject", + Type: "*constant", + Value: "SUPPLIER1", + }, + }, + }, { Tenant: utils.CGRateSorg, ID: "DYNAMICLY_ATTR_1002", @@ -520,11 +800,30 @@ func TestDynThdIT(t *testing.T) { }, &rsc); err != nil { t.Errorf("AdminSv1GetResourceProfiles failed unexpectedly: %v", err) } - if len(rsc) != 1 { - t.Fatalf("AdminSv1GetResourceProfiles len(rsc)=%v, want 1", len(rsc)) + if len(rsc) != 2 { + t.Fatalf("AdminSv1GetResourceProfiles len(rsc)=%v, want 2", len(rsc)) } - + sort.Slice(rsc, func(i, j int) bool { + return rsc[i].ID > rsc[j].ID + }) exp := []*utils.ResourceProfile{ + { + Tenant: "cgrates.org", + ID: "DYNAMICLY_RES_3_1002", + FilterIDs: []string{"*string:~*req.Account:1002"}, + UsageTTL: 5 * time.Second, + Limit: 5, + AllocationMessage: "alloc_msg", + Blocker: true, + Stored: true, + Weights: utils.DynamicWeights{ + { + FilterIDs: []string{"*string:~*req.Account:1002"}, + Weight: 30, + }, + }, + ThresholdIDs: []string{"THID1", "THID2"}, + }, { Tenant: "cgrates.org", ID: "DYNAMICLY_RES_1002",