Add action type *dynamicAction

This commit is contained in:
arberkatellari
2025-08-07 16:07:05 +02:00
committed by Dan Christian Bogos
parent 6f7fbb0e94
commit 262f1dafa3
17 changed files with 1468 additions and 29 deletions

View File

@@ -62,7 +62,8 @@ func parseParamStringToMap(paramStr string, targetMap map[string]any) error {
// 7 Blocker: bool, should always be true
// 8 ActionProfileIDs: strings separated by "&".
// 9 Async: bool
// 10 APIOpts: set of key-value pairs (separated by "&").
// 10 EeIDs: strings separated by "&".
// 11 APIOpts: set of key-value pairs (separated by "&").
//
// Parameters are separated by ";" and must be provided in the specified order.
type actDynamicThreshold struct {
@@ -116,8 +117,8 @@ func (aL *actDynamicThreshold) execute(ctx *context.Context, data utils.MapStora
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 len(params) != 12 {
return fmt.Errorf("invalid number of parameters <%d> expected 12", len(params))
}
// parse dynamic parameters
for i := range params {
@@ -193,9 +194,15 @@ func (aL *actDynamicThreshold) execute(ctx *context.Context, data utils.MapStora
return err
}
}
// populate Threshold's APIOpts
// populate Threshold's EeIDs
if params[10] != utils.EmptyString {
if err := parseParamStringToMap(params[10], args.APIOpts); err != nil {
args.EeIDs = strings.Split(params[10], utils.ANDSep)
utils.Logger.Crit(args.EeIDs[0])
}
// populate Threshold's APIOpts
if params[11] != utils.EmptyString {
if err := parseParamStringToMap(params[11], args.APIOpts); err != nil {
return err
}
}
@@ -1896,3 +1903,278 @@ func (aL *actDynamicIP) execute(ctx *context.Context, data utils.MapStorage, trg
}
return
}
// actDynamicAction processes the `ActionDiktatsOpts` field from the action to construct a ActionProfile
//
// The ActionDiktatsOpts field format is expected as follows:
//
// 0 Tenant: string
// 1 ID: string
// 2 FilterIDs: strings separated by "&".
// 3 Weights: strings separated by "&".
// 4 Blockers: strings separated by "&".
// 5 Schedule: string
// 6 TargetType: string
// 7 TargetIDs: strings separated by "&".
// 8 ActionID: string
// 9 ActionFilterIDs: strings separated by "&".
// 10 ActionTTL: duration
// 11 ActionType: string
// 12 ActionOpts: set of key-value pairs (separated by "&").
// 13 ActionWeights: strings separated by "&".
// 14 ActionBlockers: strings separated by "&".
// 15 ActionDiktatsID: string
// 16 ActionDiktatsFilterIDs: strings separated by "&".
// 17 ActionDiktatsOpts: set of key-value pairs (separated by "&").
// 18 ActionDiktatsWeights: strings separated by "&".
// 19 ActionDiktatsBlockers: strings separated by "&".
// 20 APIOpts: set of key-value pairs (separated by "&").
//
// Parameters are separated by ";" and must be provided in the specified order.
type actDynamicAction struct {
config *config.CGRConfig
connMgr *engine.ConnManager
fltrS *engine.FilterS
aCfg *utils.APAction
tnt string
cgrEv *utils.CGREvent
}
func (aL *actDynamicAction) id() string {
return aL.aCfg.ID
}
func (aL *actDynamicAction) cfg() *utils.APAction {
return aL.aCfg
}
// execute implements actioner interface
func (aL *actDynamicAction) execute(ctx *context.Context, data utils.MapStorage, trgID string) (err error) {
if len(aL.config.ActionSCfg().AdminSConns) == 0 {
return fmt.Errorf("no connection with AdminS")
}
data[utils.MetaNow] = time.Now()
data[utils.MetaTenant] = utils.FirstNonEmpty(aL.cgrEv.Tenant, aL.tnt,
config.CgrConfig().GeneralCfg().DefaultTenant)
// Parse action parameters based on the predefined format.
if len(aL.aCfg.Diktats) == 0 {
return fmt.Errorf("No diktats were specified for action <%v>", aL.aCfg.ID)
}
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 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)
}
// 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) != 21 {
return fmt.Errorf("invalid number of parameters <%d> expected 21", len(params))
}
// parse dynamic parameters
for i := range params {
if params[i], err = utils.ParseParamForDataProvider(params[i], data, false); err != nil {
return err
}
}
// Prepare request arguments based on provided parameters.
args := &utils.ActionProfileWithAPIOpts{
ActionProfile: &utils.ActionProfile{
Tenant: params[0],
ID: params[1],
Schedule: params[5],
Targets: make(map[string]utils.StringSet),
Actions: []*utils.APAction{
{
ID: params[8],
Type: params[11],
Opts: make(map[string]any),
Diktats: []*utils.APDiktat{
{
ID: params[15],
Opts: make(map[string]any),
},
},
},
},
},
APIOpts: make(map[string]any),
}
// populate ActionProfile's FilterIDs
if params[2] != utils.EmptyString {
args.FilterIDs = strings.Split(params[2], utils.ANDSep)
}
// populate ActionProfile'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 ActionProfile'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 ActionProfile's Targets
if params[6] != utils.EmptyString {
args.Targets[params[6]] = utils.NewStringSet(strings.Split(params[7], utils.ANDSep))
}
// populate ActionProfile's Action
if params[8] != utils.EmptyString {
// populate Action's FilterIDs
if params[9] != utils.EmptyString {
args.Actions[0].FilterIDs = strings.Split(params[9], utils.ANDSep)
}
// populate Action's TTL
if params[10] != utils.EmptyString {
args.Actions[0].TTL, err = utils.ParseDurationWithNanosecs(params[10])
if err != nil {
return err
}
}
// populate Action's Opts
if params[12] != utils.EmptyString {
if err := parseParamStringToMap(params[12], args.Actions[0].Opts); err != nil {
return err
}
}
// populate Action's Weights
if params[13] != utils.EmptyString {
args.Actions[0].Weights = utils.DynamicWeights{&utils.DynamicWeight{}}
wghtSplit := strings.Split(params[13], utils.ANDSep)
if len(wghtSplit) > 2 {
return utils.ErrUnsupportedFormat
}
if wghtSplit[0] != utils.EmptyString {
args.Actions[0].Weights[0].FilterIDs = []string{wghtSplit[0]}
}
if wghtSplit[1] != utils.EmptyString {
args.Actions[0].Weights[0].Weight, err = strconv.ParseFloat(wghtSplit[1], 64)
if err != nil {
return err
}
}
}
// populate Action's Blocker
if params[14] != utils.EmptyString {
args.Actions[0].Blockers = utils.DynamicBlockers{&utils.DynamicBlocker{}}
blckrSplit := strings.Split(params[14], utils.ANDSep)
if len(blckrSplit) > 2 {
return utils.ErrUnsupportedFormat
}
if blckrSplit[0] != utils.EmptyString {
args.Actions[0].Blockers[0].FilterIDs = []string{blckrSplit[0]}
}
if blckrSplit[1] != utils.EmptyString {
args.Actions[0].Blockers[0].Blocker, err = strconv.ParseBool(blckrSplit[1])
if err != nil {
return err
}
}
}
// populate Action's Diktat
if params[15] != utils.EmptyString {
// populate Diktat's FilterIDs
if params[16] != utils.EmptyString {
args.Actions[0].Diktats[0].FilterIDs = strings.Split(params[16], utils.ANDSep)
}
// populate Diktat's Opts
if params[17] != utils.EmptyString {
if err := parseParamStringToMap(params[17], args.Actions[0].Diktats[0].Opts); err != nil {
return err
}
}
// populate Diktat's Weights
if params[18] != utils.EmptyString {
args.Actions[0].Diktats[0].Weights = utils.DynamicWeights{&utils.DynamicWeight{}}
wghtSplit := strings.Split(params[18], utils.ANDSep)
if len(wghtSplit) > 2 {
return utils.ErrUnsupportedFormat
}
if wghtSplit[0] != utils.EmptyString {
args.Actions[0].Diktats[0].Weights[0].FilterIDs = []string{wghtSplit[0]}
}
if wghtSplit[1] != utils.EmptyString {
args.Actions[0].Diktats[0].Weights[0].Weight, err = strconv.ParseFloat(wghtSplit[1], 64)
if err != nil {
return err
}
}
}
// populate Diktat's Blocker
if params[19] != utils.EmptyString {
args.Actions[0].Diktats[0].Blockers = utils.DynamicBlockers{&utils.DynamicBlocker{}}
blckrSplit := strings.Split(params[19], utils.ANDSep)
if len(blckrSplit) > 2 {
return utils.ErrUnsupportedFormat
}
if blckrSplit[0] != utils.EmptyString {
args.Actions[0].Diktats[0].Blockers[0].FilterIDs = []string{blckrSplit[0]}
}
if blckrSplit[1] != utils.EmptyString {
args.Actions[0].Diktats[0].Blockers[0].Blocker, err = strconv.ParseBool(blckrSplit[1])
if err != nil {
return err
}
}
}
}
}
// populate ActionProfile's APIOpts
if params[20] != utils.EmptyString {
if err := parseParamStringToMap(params[20], args.APIOpts); err != nil {
return err
}
}
// create the ActionProfile based on the populated parameters
var rply string
if err = aL.connMgr.Call(ctx, aL.config.ActionSCfg().AdminSConns,
utils.AdminSv1SetActionProfile, 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
}

View File

@@ -53,6 +53,8 @@ func actionTarget(act string) string {
return utils.MetaRates
case utils.MetaDynamicIP:
return utils.MetaIPs
case utils.MetaDynamicAction:
return utils.MetaActions
default:
return utils.MetaNone
}
@@ -169,6 +171,8 @@ func newActioner(ctx *context.Context, cgrEv *utils.CGREvent, cfg *config.CGRCon
return &actDynamicRate{cfg, connMgr, fltrS, aCfg, tnt, cgrEv}, nil
case utils.MetaDynamicIP:
return &actDynamicIP{cfg, connMgr, fltrS, aCfg, tnt, cgrEv}, nil
case utils.MetaDynamicAction:
return &actDynamicAction{cfg, connMgr, fltrS, aCfg, tnt, cgrEv}, nil
default:
return nil, fmt.Errorf("unsupported action type: <%s>", aCfg.Type)

View File

@@ -0,0 +1,5 @@
#Tenant[0];ID[1];FilterIDs[2];Weights[3];Blockers[4];Schedule[5];TargetType[6];TargetIDs[7];ActionID[8];ActionFilterIDs[9];ActionTTL[10];ActionType[11];ActionOpts[12];ActionWeights[13];ActionBlockers[14];ActionDiktatsID[15];ActionDiktatsFilterIDs[16];ActionDiktatsOpts[17];ActionDiktatsWeights[18];ActionDiktatsBlockers[19];APIOpts[20]
cgrates.org;ONE_TIME_ACT;;&10;;*asap;*accounts;1001&1002;TOPUP;;0s;*addBalance;;;;ADDBALUNITS;;*balancePath:*balance.TestBalance.Units&*balanceValue:10;;;
*tenant;DYNAMICLY_Action_<~*req.Account>;*string:~*req.Account:1002&*string:~*req.Account:1003;*string:~*req.Account:1002&10;*string:~*req.Account:1002&true;*asap;*accounts;1001&1002;TOPUP;*string:~*req.Account:1002&*string:~*req.Account:1003;10s;*addBalance;~*opts;*string:~*req.Account:1002&20;*string:~*req.Account:1002&true;ADDBALUNITS;*string:~*req.Account:1002&*string:~*req.Account:1003;*balancePath:*balance.TestBalance.Units&*balanceValue:10;*string:~*req.Account:1002&20;*string:~*req.Account:1002&true;~*opts
1 #Tenant[0] ID[1] FilterIDs[2] Weights[3] Blockers[4] Schedule[5] TargetType[6] TargetIDs[7] ActionID[8] ActionFilterIDs[9] ActionTTL[10] ActionType[11] ActionOpts[12] ActionWeights[13] ActionBlockers[14] ActionDiktatsID[15] ActionDiktatsFilterIDs[16] ActionDiktatsOpts[17] ActionDiktatsWeights[18] ActionDiktatsBlockers[19] APIOpts[20]
2 cgrates.org ONE_TIME_ACT &10 *asap *accounts 1001&1002 TOPUP 0s *addBalance ADDBALUNITS *balancePath:*balance.TestBalance.Units&*balanceValue:10
3 *tenant DYNAMICLY_Action_<~*req.Account> *string:~*req.Account:1002&*string:~*req.Account:1003 *string:~*req.Account:1002&10 *string:~*req.Account:1002&true *asap *accounts 1001&1002 TOPUP *string:~*req.Account:1002&*string:~*req.Account:1003 10s *addBalance ~*opts *string:~*req.Account:1002&20 *string:~*req.Account:1002&true ADDBALUNITS *string:~*req.Account:1002&*string:~*req.Account:1003 *balancePath:*balance.TestBalance.Units&*balanceValue:10 *string:~*req.Account:1002&20 *string:~*req.Account:1002&true ~*opts

View File

@@ -0,0 +1,5 @@
#Tenant[0];ID[1];FilterIDs[2];Weights[3];Blockers[4];AttributeFilterIDs[5];AttributeBlockers[6];Path[7];Type[8];Value[9];APIOpts[10]
cgrates.org;ATTR_ACNT_1001;*string:~*opts.*context:*sessions&FLTR_ACCOUNT_1001;&10;&false;;;*req.OfficeGroup;*constant;Marketing;
*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
1 #Tenant[0] ID[1] FilterIDs[2] Weights[3] Blockers[4] AttributeFilterIDs[5] AttributeBlockers[6] Path[7] Type[8] Value[9] APIOpts[10]
2 cgrates.org ATTR_ACNT_1001 *string:~*opts.*context:*sessions&FLTR_ACCOUNT_1001 &10 &false *req.OfficeGroup *constant Marketing
3 *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

View File

@@ -0,0 +1,6 @@
#Tenant[0];ID[1];Type[2];Path[3];Values[4];APIOpts[5]
cgrates.org;FLTR_ACCOUNT_1001;*string;~*req.Account;1001;
cgrates.org;FLTR_1;*string;~*req.Account;1003&1002;
*tenant;DYNAMICLY_FLTR_<~*req.Account>;*string;~*req.Account;1003&1002;~*opts
1 #Tenant[0] ID[1] Type[2] Path[3] Values[4] APIOpts[5]
2 cgrates.org FLTR_ACCOUNT_1001 *string ~*req.Account 1001
3 cgrates.org FLTR_1 *string ~*req.Account 1003&1002
4 *tenant DYNAMICLY_FLTR_<~*req.Account> *string ~*req.Account 1003&1002 ~*opts

View File

@@ -0,0 +1,5 @@
#Tenant[0];ID[1];FilterIDs[2];Weights[3];TTL[4];Stored[5];PoolID[6];PoolFilterIDs[7];PoolType[8];PoolRange[9];PoolStrategy[10];PoolMessage[11];PoolWeights[12];PoolBlockers[13];APIOpts[14]
cgrates.org;IPs1;*string:~*req.Account:1001;&10;1s;true;POOL1;*string:~*req.Destination:2001;*ipv4;172.16.1.1/24;*ascending;alloc_success;&15;;
*tenant;DYNAMICLY_IP_<~*req.Account>;*string:~*req.Account:1002&*string:~*req.Account:1003;*string:~*req.Account:1002&10;1s;true;Pool1;*string:~*req.Account:1002&*string:~*req.Account:1003;*ipv4;172.16.1.1/24;*ascending;alloc_success;*string:~*req.Account:1002&20;*string:~*req.Account:1002&true;~*opts
1 #Tenant[0] ID[1] FilterIDs[2] Weights[3] TTL[4] Stored[5] PoolID[6] PoolFilterIDs[7] PoolType[8] PoolRange[9] PoolStrategy[10] PoolMessage[11] PoolWeights[12] PoolBlockers[13] APIOpts[14]
2 cgrates.org IPs1 *string:~*req.Account:1001 &10 1s true POOL1 *string:~*req.Destination:2001 *ipv4 172.16.1.1/24 *ascending alloc_success &15
3 *tenant DYNAMICLY_IP_<~*req.Account> *string:~*req.Account:1002&*string:~*req.Account:1003 *string:~*req.Account:1002&10 1s true Pool1 *string:~*req.Account:1002&*string:~*req.Account:1003 *ipv4 172.16.1.1/24 *ascending alloc_success *string:~*req.Account:1002&20 *string:~*req.Account:1002&true ~*opts

View File

@@ -0,0 +1,6 @@
#Tenant[0];Id[1];Schedule[2];StatIDs[3];MetricIDs[4];Sorting[5];SortingParameters[6];Stored[7];ThresholdIDs[8];APIOpts[9]
cgrates.org;RANK1;@every 1s;Stats1&Stats2&Stats3&Stats4;;*asc;*acc&*pdd;false;;
cgrates.org;RANK2;@every 1s;Stats3&Stats4&Stats1&Stats2;;*desc;*acc&*pdd;false;;
*tenant;DYNAMICLY_RNK_<~*req.Account>;@every 1s;Stats1&Stats2;*acc&*tcc;*asc;*acc&*pdd;true;THID1&THID2;~*opts
1 #Tenant[0] Id[1] Schedule[2] StatIDs[3] MetricIDs[4] Sorting[5] SortingParameters[6] Stored[7] ThresholdIDs[8] APIOpts[9]
2 cgrates.org RANK1 @every 1s Stats1&Stats2&Stats3&Stats4 *asc *acc&*pdd false
3 cgrates.org RANK2 @every 1s Stats3&Stats4&Stats1&Stats2 *desc *acc&*pdd false
4 *tenant DYNAMICLY_RNK_<~*req.Account> @every 1s Stats1&Stats2 *acc&*tcc *asc *acc&*pdd true THID1&THID2 ~*opts

View File

@@ -0,0 +1,5 @@
#Tenant[0];ID[1];FilterIDs[2];Weights[3];MinCost[4];MaxCost[5];MaxCostStrategy[6];RateID[7];RateFilterIDs[8];RateActivationStart[9];RateWeights[10];RateBlocker[11];RateIntervalStart[12];RateFixedFee[13];RateRecurrentFee[14];RateUnit[15];RateIncrement[16];APIOpts[17]
cgrates.org;RT_SPECIAL_1002;*string:~*req.Account:1002;&10;0;0;*free;RT_ALWAYS;;"* * * * *";&0;false;0s;;0.01;1m;1s;
*tenant;DYNAMICLY_RNK_<~*req.Account>;@every 1s;Stats1&Stats2;*acc&*tcc;*asc;*acc&*pdd;true;THID1&THID2;~*opts
1 #Tenant[0] ID[1] FilterIDs[2] Weights[3] MinCost[4] MaxCost[5] MaxCostStrategy[6] RateID[7] RateFilterIDs[8] RateActivationStart[9] RateWeights[10] RateBlocker[11] RateIntervalStart[12] RateFixedFee[13] RateRecurrentFee[14] RateUnit[15] RateIncrement[16] APIOpts[17]
2 cgrates.org RT_SPECIAL_1002 *string:~*req.Account:1002 &10 0 0 *free RT_ALWAYS * * * * * &0 false 0s 0.01 1m 1s
3 *tenant DYNAMICLY_RNK_<~*req.Account> @every 1s Stats1&Stats2 *acc&*tcc *asc *acc&*pdd true THID1&THID2 ~*opts

View File

@@ -0,0 +1,5 @@
#Tenant[0];Id[1];FilterIDs[2];Weights[3];TTL[4];Limit[5];AllocationMessage[6];Blocker[7];Stored[8];ThresholdIDs[9];APIOpts[10]
cgrates.org;RES_ACNT_1001;FLTR_ACCOUNT_1001;&10;1h;1;;false;false;;
*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
1 #Tenant[0] Id[1] FilterIDs[2] Weights[3] TTL[4] Limit[5] AllocationMessage[6] Blocker[7] Stored[8] ThresholdIDs[9] APIOpts[10]
2 cgrates.org RES_ACNT_1001 FLTR_ACCOUNT_1001 &10 1h 1 false false
3 *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

View File

@@ -0,0 +1,5 @@
#Tenant[0];ID[1];FilterIDs[2];Weights[3];Blockers[4];Sorting[5];SortingParameters[6];RouteID[7];RouteFilterIDs[8];RouteAccountIDs[9];RouteRateProfileIDs[10];RouteResourceIDs[11];RouteStatIDs[12];RouteWeights[13];RouteBlockers[14];RouteParameters[15];APIOpts[16]
cgrates.org;ROUTE_ACNT_1001;FLTR_ACCOUNT_1001;&10;;*weight;;route1;;;;;;&20;;;
*tenant;DYNAMICLY_RT_<~*req.Account>;*string:~*req.Account:1002&*string:~*req.Account:1003;*string:~*req.Account:1002&10;*string:~*req.Account:1002&true;*weight;*dcc;route1;*string:~*req.Account:1002&*string:~*req.Account:1003;1002&1003;RTP1&RTP2;RSC1&RSC2;STAT1&STAT2;*string:~*req.Account:1002&10;*string:~*req.Account:1002&true;rtParam1;~*opts
1 #Tenant[0] ID[1] FilterIDs[2] Weights[3] Blockers[4] Sorting[5] SortingParameters[6] RouteID[7] RouteFilterIDs[8] RouteAccountIDs[9] RouteRateProfileIDs[10] RouteResourceIDs[11] RouteStatIDs[12] RouteWeights[13] RouteBlockers[14] RouteParameters[15] APIOpts[16]
2 cgrates.org ROUTE_ACNT_1001 FLTR_ACCOUNT_1001 &10 *weight route1 &20
3 *tenant DYNAMICLY_RT_<~*req.Account> *string:~*req.Account:1002&*string:~*req.Account:1003 *string:~*req.Account:1002&10 *string:~*req.Account:1002&true *weight *dcc route1 *string:~*req.Account:1002&*string:~*req.Account:1003 1002&1003 RTP1&RTP2 RSC1&RSC2 STAT1&STAT2 *string:~*req.Account:1002&10 *string:~*req.Account:1002&true rtParam1 ~*opts

View File

@@ -0,0 +1,5 @@
#Tenant[0];Id[1];FilterIDs[2];Weights[3];Blockers[4];QueueLength[5];TTL[6];MinItems[7];Stored[8];ThresholdIDs[9];MetricIDs[10];MetricFilterIDs[11];MetricBlockers[12];APIOpts[13]
cgrates.org;Stat_1;FLTR_STAT_1;&30;&true;100;10s;0;false;*none;*acd&*tcd&*asr;;;
*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
1 #Tenant[0] Id[1] FilterIDs[2] Weights[3] Blockers[4] QueueLength[5] TTL[6] MinItems[7] Stored[8] ThresholdIDs[9] MetricIDs[10] MetricFilterIDs[11] MetricBlockers[12] APIOpts[13]
2 cgrates.org Stat_1 FLTR_STAT_1 &30 &true 100 10s 0 false *none *acd&*tcd&*asr
3 *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

View File

@@ -0,0 +1,5 @@
#Tenant[0];Id[1];FilterIDs[2];Weight[3];MaxHits[4];MinHits[5];MinSleep[6];Blocker[7];ActionProfileIDs[8];Async[9];EeIDs[10];APIOpts[11]
cgrates.org;THD_ACNT_1001;FLTR_ACCOUNT_1001;&10;-1;0;0;false;TOPUP_MONETARY_10;false;;
*tenant;DYNAMICLY_THD_<~*req.Account>;*string:~*req.Account:1002;*string:~*req.Account:1002&10;1;1;1s;false;ACT_LOG_WARNING;true;EEID1&EEID2;~*opts
1 #Tenant[0] Id[1] FilterIDs[2] Weight[3] MaxHits[4] MinHits[5] MinSleep[6] Blocker[7] ActionProfileIDs[8] Async[9] EeIDs[10] APIOpts[11]
2 cgrates.org THD_ACNT_1001 FLTR_ACCOUNT_1001 &10 -1 0 0 false TOPUP_MONETARY_10 false
3 *tenant DYNAMICLY_THD_<~*req.Account> *string:~*req.Account:1002 *string:~*req.Account:1002&10 1 1 1s false ACT_LOG_WARNING true EEID1&EEID2 ~*opts

View File

@@ -0,0 +1,6 @@
#Tenant[0];Id[1];Schedule[2];StatID[3];Metrics[4];TTL[5];QueueLength[6];MinItems[7];CorrelationType[8];Tolerance[9];Stored[10];ThresholdIDs[11];APIOpts[12]
cgrates.org;TREND_1;@every 1s;Stats1_1;*acc;-1;-1;1;*last;1;false;*none
cgrates.org;TREND_2;@every 1s;Stats1_2;*tcc;-1;-1;1;*last;1;false;*none
*tenant;DYNAMICLY_TRND_<~*req.Account>;@every 1s;Stats1_1;*acc&*tcc;-1;-1;1;*last;1;true;THID1&THID2;~*opts
1 #Tenant[0];Id[1];Schedule[2];StatID[3];Metrics[4];TTL[5];QueueLength[6];MinItems[7];CorrelationType[8];Tolerance[9];Stored[10];ThresholdIDs[11];APIOpts[12]
2 cgrates.org;TREND_1;@every 1s;Stats1_1;*acc;-1;-1;1;*last;1;false;*none
3 cgrates.org;TREND_2;@every 1s;Stats1_2;*tcc;-1;-1;1;*last;1;false;*none
4 *tenant;DYNAMICLY_TRND_<~*req.Account>;@every 1s;Stats1_1;*acc&*tcc;-1;-1;1;*last;1;true;THID1&THID2;~*opts

View File

@@ -80,6 +80,10 @@ func (tp *ThresholdProfile) Clone() *ThresholdProfile {
if tp.Weights != nil {
clone.Weights = tp.Weights.Clone()
}
if tp.EeIDs != nil {
clone.EeIDs = make([]string, len(tp.EeIDs))
copy(clone.EeIDs, tp.EeIDs)
}
return clone
}

View File

@@ -3382,8 +3382,8 @@ func TestThresholdProfileClone(t *testing.T) {
if cloned.lkID != "" {
t.Errorf("lkID should not be cloned, got: %s", cloned.lkID)
}
if len(cloned.EeIDs) != 0 {
t.Errorf("EeIDs should not be cloned, got: %v", cloned.EeIDs)
if !reflect.DeepEqual(cloned.EeIDs, orig.EeIDs) {
t.Errorf("EeIDs mismatch: got %v, want %v", cloned.EeIDs, orig.EeIDs)
}
var nilTP *ThresholdProfile

File diff suppressed because it is too large Load Diff

View File

@@ -1157,6 +1157,7 @@ const (
MetaDynamicRoute = "*dynamicRoute"
MetaDynamicRate = "*dynamicRate"
MetaDynamicIP = "*dynamicIP"
MetaDynamicAction = "*dynamicAction"
// Diktats Opts Fields
MetaBalancePath = "*balancePath"