mirror of
https://github.com/cgrates/cgrates.git
synced 2026-02-11 18:16:24 +05:00
2181 lines
69 KiB
Go
2181 lines
69 KiB
Go
/*
|
|
Real-time Online/Offline Charging System (OCS) for Telecom & ISP environments
|
|
Copyright (C) ITsysCOM GmbH
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU Affero General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU Affero General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this program. If not, see <https://www.gnu.org/licenses/>
|
|
*/
|
|
|
|
package actions
|
|
|
|
import (
|
|
"cmp"
|
|
"fmt"
|
|
"slices"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/cgrates/birpc/context"
|
|
"github.com/cgrates/cgrates/config"
|
|
"github.com/cgrates/cgrates/engine"
|
|
"github.com/cgrates/cgrates/utils"
|
|
)
|
|
|
|
// parseParamStringToMap parses a string containing key-value pairs separated by "&" and assigns
|
|
// these pairs to a given map. Each pair is expected to be in the format "key:value".
|
|
func parseParamStringToMap(paramStr string, targetMap map[string]any) error {
|
|
for tuple := range strings.SplitSeq(paramStr, utils.ANDSep) {
|
|
// Use strings.Cut to split 'tuple' into key-value pairs at the first occurrence of ':'.
|
|
// This ensures that additional ':' characters within the value do not affect parsing.
|
|
keyVal := strings.SplitN(tuple, utils.InInFieldSep, 2)
|
|
if len(keyVal) != 2 {
|
|
return fmt.Errorf("invalid key-value pair: %s", tuple)
|
|
}
|
|
targetMap[keyVal[0]] = keyVal[1]
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// actDynamicThreshold processes the `ActionDiktatsOpts` field from the action to construct a Threshold profile
|
|
//
|
|
// The ActionDiktatsOpts field format is expected as follows:
|
|
//
|
|
// 0 Tenant: string
|
|
// 1 ID: string
|
|
// 2 FilterIDs: strings separated by "&".
|
|
// 3 Weight: strings separated by "&". Should be higher than the threshold weight that
|
|
// triggers this action
|
|
// 4 MaxHits: integer
|
|
// 5 MinHits: integer
|
|
// 6 MinSleep: duration
|
|
// 7 Blocker: bool, should always be true
|
|
// 8 ActionProfileIDs: strings separated by "&".
|
|
// 9 Async: bool
|
|
// 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 {
|
|
config *config.CGRConfig
|
|
connMgr *engine.ConnManager
|
|
fltrS *engine.FilterS
|
|
aCfg *utils.APAction
|
|
tnt string
|
|
cgrEv *utils.CGREvent
|
|
}
|
|
|
|
func (aL *actDynamicThreshold) id() string {
|
|
return aL.aCfg.ID
|
|
}
|
|
|
|
func (aL *actDynamicThreshold) cfg() *utils.APAction {
|
|
return aL.aCfg
|
|
}
|
|
|
|
// execute implements actioner interface
|
|
func (aL *actDynamicThreshold) 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) != 12 {
|
|
return fmt.Errorf("invalid number of parameters <%d> expected 12", 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 := &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 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 EeIDs
|
|
if params[10] != utils.EmptyString {
|
|
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
|
|
}
|
|
}
|
|
// 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
|
|
}
|
|
if blocker, err := engine.BlockerFromDynamics(ctx, diktat.Blockers, aL.fltrS, aL.tnt, data); err != nil {
|
|
return err
|
|
} else if blocker {
|
|
break
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// actDynamicStats processes the `ActionDiktatsOpts` field from the action to construct a StatQueueProfile
|
|
//
|
|
// 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 QueueLength: integer
|
|
// 6 TTL: duration
|
|
// 7 MinItems: integer
|
|
// 8 Stored: bool
|
|
// 9 ThresholdIDs: strings separated by "&".
|
|
// 10 MetricIDs: strings separated by "&".
|
|
// 11 MetricFilterIDs: strings separated by "&".
|
|
// 12 MetricBlockers: strings separated by "&".
|
|
// 13 APIOpts: set of key-value pairs (separated by "&").
|
|
//
|
|
// Parameters are separated by ";" and must be provided in the specified order.
|
|
type actDynamicStats struct {
|
|
config *config.CGRConfig
|
|
connMgr *engine.ConnManager
|
|
fltrS *engine.FilterS
|
|
aCfg *utils.APAction
|
|
tnt string
|
|
cgrEv *utils.CGREvent
|
|
}
|
|
|
|
func (aL *actDynamicStats) id() string {
|
|
return aL.aCfg.ID
|
|
}
|
|
|
|
func (aL *actDynamicStats) cfg() *utils.APAction {
|
|
return aL.aCfg
|
|
}
|
|
|
|
// execute implements actioner interface
|
|
func (aL *actDynamicStats) 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) != 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 {
|
|
return err
|
|
}
|
|
}
|
|
// 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
|
|
}
|
|
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 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
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// actDynamicAttribute processes the `ActionDiktatsOpts` field from the action to construct a AttributeProfile
|
|
//
|
|
// 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 Blocker: strings separated by "&".
|
|
// 5 AttributeFilterIDs: strings separated by "&".
|
|
// 6 AttributeBlockers: strings separated by "&".
|
|
// 7 Path: string
|
|
// 8 Type: string
|
|
// 9 Value: strings separated by "&".
|
|
// 10 APIOpts: set of key-value pairs (separated by "&").
|
|
//
|
|
// Parameters are separated by ";" and must be provided in the specified order.
|
|
type actDynamicAttribute struct {
|
|
config *config.CGRConfig
|
|
connMgr *engine.ConnManager
|
|
fltrS *engine.FilterS
|
|
aCfg *utils.APAction
|
|
tnt string
|
|
cgrEv *utils.CGREvent
|
|
}
|
|
|
|
func (aL *actDynamicAttribute) id() string {
|
|
return aL.aCfg.ID
|
|
}
|
|
|
|
func (aL *actDynamicAttribute) cfg() *utils.APAction {
|
|
return aL.aCfg
|
|
}
|
|
|
|
// execute implements actioner interface
|
|
func (aL *actDynamicAttribute) 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) != 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 {
|
|
return err
|
|
}
|
|
}
|
|
// 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
|
|
}
|
|
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 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
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// actDynamicResource processes the `ActionDiktatsOpts` field from the action to construct a ResourceProfile
|
|
//
|
|
// 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 TTL: duration
|
|
// 5 Limit: float
|
|
// 6 AllocationMessage: string
|
|
// 7 Blocker: bool
|
|
// 8 Stored: bool
|
|
// 9 ThresholdIDs: strings separated by "&".
|
|
// 10 APIOpts: set of key-value pairs (separated by "&").
|
|
//
|
|
// Parameters are separated by ";" and must be provided in the specified order.
|
|
type actDynamicResource struct {
|
|
config *config.CGRConfig
|
|
connMgr *engine.ConnManager
|
|
fltrS *engine.FilterS
|
|
aCfg *utils.APAction
|
|
tnt string
|
|
cgrEv *utils.CGREvent
|
|
}
|
|
|
|
func (aL *actDynamicResource) id() string {
|
|
return aL.aCfg.ID
|
|
}
|
|
|
|
func (aL *actDynamicResource) cfg() *utils.APAction {
|
|
return aL.aCfg
|
|
}
|
|
|
|
// execute implements actioner interface
|
|
func (aL *actDynamicResource) 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) != 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 {
|
|
return err
|
|
}
|
|
}
|
|
// 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 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 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 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
|
|
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
|
|
}
|
|
|
|
// actDynamicTrend processes the `ActionDiktatsOpts` field from the action to construct a TrendProfile
|
|
//
|
|
// The ActionDiktatsOpts field format is expected as follows:
|
|
//
|
|
// 0 Tenant: string
|
|
// 1 ID: string
|
|
// 2 Schedule: string
|
|
// 3 StatID: string
|
|
// 4 Metrics: strings separated by "&".
|
|
// 5 TTL: duration
|
|
// 6 QueueLength: integer
|
|
// 7 MinItems: integer
|
|
// 8 CorrelationType: string
|
|
// 9 Tolerance: float
|
|
// 10 Stored: bool
|
|
// 11 ThresholdIDs: strings separated by "&".
|
|
// 12 APIOpts: set of key-value pairs (separated by "&").
|
|
//
|
|
// Parameters are separated by ";" and must be provided in the specified order.
|
|
type actDynamicTrend struct {
|
|
config *config.CGRConfig
|
|
connMgr *engine.ConnManager
|
|
fltrS *engine.FilterS
|
|
aCfg *utils.APAction
|
|
tnt string
|
|
cgrEv *utils.CGREvent
|
|
}
|
|
|
|
func (aL *actDynamicTrend) id() string {
|
|
return aL.aCfg.ID
|
|
}
|
|
|
|
func (aL *actDynamicTrend) cfg() *utils.APAction {
|
|
return aL.aCfg
|
|
}
|
|
|
|
// execute implements actioner interface
|
|
func (aL *actDynamicTrend) 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) != 13 {
|
|
return fmt.Errorf("invalid number of parameters <%d> expected 13", 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.TrendProfileWithAPIOpts{
|
|
TrendProfile: &utils.TrendProfile{
|
|
Tenant: params[0],
|
|
ID: params[1],
|
|
Schedule: params[2],
|
|
StatID: params[3],
|
|
CorrelationType: params[8],
|
|
},
|
|
APIOpts: make(map[string]any),
|
|
}
|
|
// populate Trend's Metrics
|
|
if params[4] != utils.EmptyString {
|
|
args.Metrics = strings.Split(params[4], utils.ANDSep)
|
|
}
|
|
// populate Trend's TTL
|
|
if params[5] != utils.EmptyString {
|
|
args.TTL, err = utils.ParseDurationWithNanosecs(params[5])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
// populate Trend's QueueLengh
|
|
if params[6] != utils.EmptyString {
|
|
args.QueueLength, err = strconv.Atoi(params[6])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
// populate Trend's MinItems
|
|
if params[7] != utils.EmptyString {
|
|
args.MinItems, err = strconv.Atoi(params[7])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
// populate Trend's Tolerance
|
|
if params[9] != utils.EmptyString {
|
|
args.Tolerance, err = strconv.ParseFloat(params[9], 64)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
// populate Trend's Stored
|
|
if params[10] != utils.EmptyString {
|
|
args.Stored, err = strconv.ParseBool(params[10])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
// populate Trend's ThresholdIDs
|
|
if params[11] != utils.EmptyString {
|
|
args.ThresholdIDs = strings.Split(params[11], utils.ANDSep)
|
|
}
|
|
// populate Trend's APIOpts
|
|
if params[12] != utils.EmptyString {
|
|
if err := parseParamStringToMap(params[12], args.APIOpts); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// create the TrendProfile based on the populated parameters
|
|
var rply string
|
|
if err = aL.connMgr.Call(ctx, aL.config.ActionSCfg().AdminSConns,
|
|
utils.AdminSv1SetTrendProfile, 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
|
|
}
|
|
|
|
// actDynamicRanking processes the `ActionDiktatsOpts` field from the action to construct a RankingProfile
|
|
//
|
|
// The ActionDiktatsOpts field format is expected as follows:
|
|
//
|
|
// 0 Tenant: string
|
|
// 1 ID: string
|
|
// 2 Schedule: string
|
|
// 3 StatIDs: strings separated by "&".
|
|
// 4 MetricIDs: strings separated by "&".
|
|
// 5 Sorting: string
|
|
// 6 SortingParameters: strings separated by "&".
|
|
// 7 Stored: bool
|
|
// 8 ThresholdIDs: strings separated by "&".
|
|
// 9 APIOpts: set of key-value pairs (separated by "&").
|
|
//
|
|
// Parameters are separated by ";" and must be provided in the specified order.
|
|
type actDynamicRanking struct {
|
|
config *config.CGRConfig
|
|
connMgr *engine.ConnManager
|
|
fltrS *engine.FilterS
|
|
aCfg *utils.APAction
|
|
tnt string
|
|
cgrEv *utils.CGREvent
|
|
}
|
|
|
|
func (aL *actDynamicRanking) id() string {
|
|
return aL.aCfg.ID
|
|
}
|
|
|
|
func (aL *actDynamicRanking) cfg() *utils.APAction {
|
|
return aL.aCfg
|
|
}
|
|
|
|
// execute implements actioner interface
|
|
func (aL *actDynamicRanking) 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) != 10 {
|
|
return fmt.Errorf("invalid number of parameters <%d> expected 10", 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.RankingProfileWithAPIOpts{
|
|
RankingProfile: &utils.RankingProfile{
|
|
Tenant: params[0],
|
|
ID: params[1],
|
|
Schedule: params[2],
|
|
Sorting: params[5],
|
|
},
|
|
APIOpts: make(map[string]any),
|
|
}
|
|
// populate Ranking's StatIDs
|
|
if params[3] != utils.EmptyString {
|
|
args.StatIDs = strings.Split(params[3], utils.ANDSep)
|
|
}
|
|
// populate Ranking's MetricIDs
|
|
if params[4] != utils.EmptyString {
|
|
args.MetricIDs = strings.Split(params[4], utils.ANDSep)
|
|
}
|
|
// populate Ranking's SortingParameters
|
|
if params[6] != utils.EmptyString {
|
|
args.SortingParameters = strings.Split(params[6], utils.ANDSep)
|
|
}
|
|
// populate Ranking's Stored
|
|
if params[7] != utils.EmptyString {
|
|
args.Stored, err = strconv.ParseBool(params[7])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
// populate Ranking's ThresholdIDs
|
|
if params[8] != utils.EmptyString {
|
|
args.ThresholdIDs = strings.Split(params[8], utils.ANDSep)
|
|
}
|
|
// populate Ranking's APIOpts
|
|
if params[9] != utils.EmptyString {
|
|
if err := parseParamStringToMap(params[9], args.APIOpts); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// create the RankingProfile based on the populated parameters
|
|
var rply string
|
|
if err = aL.connMgr.Call(ctx, aL.config.ActionSCfg().AdminSConns,
|
|
utils.AdminSv1SetRankingProfile, 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
|
|
}
|
|
|
|
// actDynamicFilter processes the `ActionDiktatsOpts` field from the action to construct a Filter
|
|
//
|
|
// The ActionDiktatsOpts field format is expected as follows:
|
|
//
|
|
// 0 Tenant: string
|
|
// 1 ID: string
|
|
// 2 Type: string
|
|
// 3 Path: string
|
|
// 4 Values: strings separated by "&".
|
|
// 5 APIOpts: set of key-value pairs (separated by "&").
|
|
//
|
|
// Parameters are separated by ";" and must be provided in the specified order.
|
|
type actDynamicFilter struct {
|
|
config *config.CGRConfig
|
|
connMgr *engine.ConnManager
|
|
fltrS *engine.FilterS
|
|
aCfg *utils.APAction
|
|
tnt string
|
|
cgrEv *utils.CGREvent
|
|
}
|
|
|
|
func (aL *actDynamicFilter) id() string {
|
|
return aL.aCfg.ID
|
|
}
|
|
|
|
func (aL *actDynamicFilter) cfg() *utils.APAction {
|
|
return aL.aCfg
|
|
}
|
|
|
|
// execute implements actioner interface
|
|
func (aL *actDynamicFilter) 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) != 6 {
|
|
return fmt.Errorf("invalid number of parameters <%d> expected 6", len(params))
|
|
}
|
|
// parse dynamic parameters
|
|
for i := range params {
|
|
var onlyEncapsulatead bool
|
|
if i == 3 { // dont parse un-encapsulated "< >" string from Path
|
|
onlyEncapsulatead = true
|
|
}
|
|
if params[i], err = utils.ParseParamForDataProvider(params[i], data, onlyEncapsulatead); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
// Prepare request arguments based on provided parameters.
|
|
args := &engine.FilterWithAPIOpts{
|
|
Filter: &engine.Filter{
|
|
Tenant: params[0],
|
|
ID: params[1],
|
|
Rules: []*engine.FilterRule{{
|
|
Type: params[2],
|
|
Element: params[3],
|
|
}},
|
|
},
|
|
APIOpts: make(map[string]any),
|
|
}
|
|
// populate Filter's Values
|
|
if params[4] != utils.EmptyString {
|
|
args.Filter.Rules[0].Values = strings.Split(params[4], utils.ANDSep)
|
|
}
|
|
// populate Filter's APIOpts
|
|
if params[5] != utils.EmptyString {
|
|
if err := parseParamStringToMap(params[5], args.APIOpts); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// create the Filter based on the populated parameters
|
|
var rply string
|
|
if err = aL.connMgr.Call(ctx, aL.config.ActionSCfg().AdminSConns,
|
|
utils.AdminSv1SetFilter, 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
|
|
}
|
|
|
|
// actDynamicRoute processes the `ActionDiktatsOpts` field from the action to construct a RouteProfile
|
|
//
|
|
// 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 Sorting: string
|
|
// 6 SortingParameters: strings separated by "&".
|
|
// 7 RouteID: string
|
|
// 8 RouteFilterIDs: strings separated by "&".
|
|
// 9 RouteAccountIDs: strings separated by "&".
|
|
// 10 RouteRateProfileIDs: strings separated by "&".
|
|
// 11 RouteResourceIDs: strings separated by "&".
|
|
// 12 RouteStatIDs: strings separated by "&".
|
|
// 13 RouteWeights: strings separated by "&".
|
|
// 14 RouteBlockers: strings separated by "&".
|
|
// 15 RouteParameters: string
|
|
// 16 APIOpts: set of key-value pairs (separated by "&").
|
|
//
|
|
// Parameters are separated by ";" and must be provided in the specified order.
|
|
type actDynamicRoute struct {
|
|
config *config.CGRConfig
|
|
connMgr *engine.ConnManager
|
|
dm *engine.DataManager
|
|
fltrS *engine.FilterS
|
|
aCfg *utils.APAction
|
|
tnt string
|
|
cgrEv *utils.CGREvent
|
|
}
|
|
|
|
func (aL *actDynamicRoute) id() string {
|
|
return aL.aCfg.ID
|
|
}
|
|
|
|
func (aL *actDynamicRoute) cfg() *utils.APAction {
|
|
return aL.aCfg
|
|
}
|
|
|
|
// execute implements actioner interface
|
|
func (aL *actDynamicRoute) 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) != 17 {
|
|
return fmt.Errorf("invalid number of parameters <%d> expected 17", len(params))
|
|
}
|
|
// parse dynamic parameters
|
|
for i := range params {
|
|
if params[i], err = utils.ParseParamForDataProvider(params[i], data, false); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
// Take only the string after @, for cases when the RouteProfileID is gotten from a switch agents event
|
|
routeFieldParts := strings.Split(params[1], utils.AtChar)
|
|
routeProfileFound := new(utils.RouteProfile)
|
|
if len(routeFieldParts) > 2 {
|
|
return fmt.Errorf("more than 1 \"@\" character for RouteProfileID: <%s>", params[1])
|
|
} else if len(routeFieldParts) > 1 {
|
|
params[1] = routeFieldParts[1]
|
|
if routeProfileFound, err = aL.dm.GetRouteProfile(context.Background(),
|
|
utils.FirstNonEmpty(aL.cgrEv.Tenant, aL.tnt,
|
|
config.CgrConfig().GeneralCfg().DefaultTenant),
|
|
params[1], true, true, utils.NonTransactional); err != nil &&
|
|
err.Error() != utils.ErrNotFound.Error() {
|
|
return err
|
|
}
|
|
}
|
|
// Prepare request arguments based on provided parameters.
|
|
args := &utils.RouteProfileWithAPIOpts{
|
|
RouteProfile: &utils.RouteProfile{
|
|
Tenant: utils.FirstNonEmpty(params[0], routeProfileFound.Tenant),
|
|
ID: params[1],
|
|
Sorting: utils.FirstNonEmpty(params[5], routeProfileFound.Sorting),
|
|
},
|
|
APIOpts: make(map[string]any),
|
|
}
|
|
// populate RouteProfile's FilterIDs
|
|
if params[2] != utils.EmptyString {
|
|
args.FilterIDs = strings.Split(params[2], utils.ANDSep)
|
|
} else {
|
|
args.FilterIDs = routeProfileFound.FilterIDs
|
|
}
|
|
// populate RouteProfile'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
|
|
}
|
|
}
|
|
} else {
|
|
args.Weights = routeProfileFound.Weights
|
|
}
|
|
// populate RouteProfile'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
|
|
}
|
|
}
|
|
} else {
|
|
args.Blockers = routeProfileFound.Blockers
|
|
}
|
|
// populate RouteProfile's SortingParameters
|
|
if params[6] != utils.EmptyString {
|
|
args.SortingParameters = strings.Split(params[6], utils.ANDSep)
|
|
} else {
|
|
args.SortingParameters = routeProfileFound.SortingParameters
|
|
}
|
|
// populate RouteProfile's Routes
|
|
if params[7] != utils.EmptyString {
|
|
// keep the existing routes if routeProfile already existed, and modify the specified Routes by ID
|
|
var routeModified bool // if route doesnt exist in the found route Profile
|
|
for _, existingRoute := range routeProfileFound.Routes {
|
|
if existingRoute.ID == params[7] { // modify routes with ID
|
|
// populate RouteProfile's RouteFilterIDs
|
|
if params[8] != utils.EmptyString {
|
|
existingRoute.FilterIDs = strings.Split(params[8], utils.ANDSep)
|
|
}
|
|
// populate RouteProfile's RouteAccountIDs
|
|
if params[9] != utils.EmptyString {
|
|
existingRoute.AccountIDs = strings.Split(params[9], utils.ANDSep)
|
|
}
|
|
// populate RouteProfile's RouteRateProfileIDs
|
|
if params[10] != utils.EmptyString {
|
|
existingRoute.RateProfileIDs = strings.Split(params[10], utils.ANDSep)
|
|
}
|
|
// populate RouteProfile's RouteResourceIDs
|
|
if params[11] != utils.EmptyString {
|
|
existingRoute.ResourceIDs = strings.Split(params[11], utils.ANDSep)
|
|
}
|
|
// populate RouteProfile's RouteStatIDs
|
|
if params[12] != utils.EmptyString {
|
|
existingRoute.StatIDs = strings.Split(params[12], utils.ANDSep)
|
|
}
|
|
// populate RouteProfile's RouteWeight
|
|
if params[13] != utils.EmptyString {
|
|
existingRoute.Weights = utils.DynamicWeights{&utils.DynamicWeight{}}
|
|
wghtSplit := strings.Split(params[13], utils.ANDSep)
|
|
if len(wghtSplit) > 2 {
|
|
return utils.ErrUnsupportedFormat
|
|
}
|
|
if wghtSplit[0] != utils.EmptyString {
|
|
existingRoute.Weights[0].FilterIDs = []string{wghtSplit[0]}
|
|
}
|
|
if wghtSplit[1] != utils.EmptyString {
|
|
existingRoute.Weights[0].Weight, err = strconv.ParseFloat(wghtSplit[1], 64)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
// populate RouteProfile's RouteBlocker
|
|
if params[14] != utils.EmptyString {
|
|
existingRoute.Blockers = utils.DynamicBlockers{&utils.DynamicBlocker{}}
|
|
blckrSplit := strings.Split(params[14], utils.ANDSep)
|
|
if len(blckrSplit) > 2 {
|
|
return utils.ErrUnsupportedFormat
|
|
}
|
|
if blckrSplit[0] != utils.EmptyString {
|
|
existingRoute.Blockers[0].FilterIDs = []string{blckrSplit[0]}
|
|
}
|
|
if blckrSplit[1] != utils.EmptyString {
|
|
existingRoute.Blockers[0].Blocker, err = strconv.ParseBool(blckrSplit[1])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
// populate RouteProfile's RouteParameters
|
|
if params[15] != utils.EmptyString {
|
|
existingRoute.RouteParameters = params[15]
|
|
}
|
|
routeModified = true
|
|
}
|
|
args.Routes = append(args.Routes, existingRoute)
|
|
}
|
|
if !routeModified { // if no existing routes were modified, append a new route
|
|
appendRoute := new(utils.Route) // new route to be appended
|
|
// populate RouteProfile's RouteID
|
|
appendRoute.ID = params[7]
|
|
// populate RouteProfile's RouteFilterIDs
|
|
if params[8] != utils.EmptyString {
|
|
appendRoute.FilterIDs = strings.Split(params[8], utils.ANDSep)
|
|
}
|
|
// populate RouteProfile's RouteAccountIDs
|
|
if params[9] != utils.EmptyString {
|
|
appendRoute.AccountIDs = strings.Split(params[9], utils.ANDSep)
|
|
}
|
|
// populate RouteProfile's RouteRateProfileIDs
|
|
if params[10] != utils.EmptyString {
|
|
appendRoute.RateProfileIDs = strings.Split(params[10], utils.ANDSep)
|
|
}
|
|
// populate RouteProfile's RouteResourceIDs
|
|
if params[11] != utils.EmptyString {
|
|
appendRoute.ResourceIDs = strings.Split(params[11], utils.ANDSep)
|
|
}
|
|
// populate RouteProfile's RouteStatIDs
|
|
if params[12] != utils.EmptyString {
|
|
appendRoute.StatIDs = strings.Split(params[12], utils.ANDSep)
|
|
}
|
|
// populate RouteProfile's RouteWeight
|
|
if params[13] != utils.EmptyString {
|
|
appendRoute.Weights = utils.DynamicWeights{&utils.DynamicWeight{}}
|
|
wghtSplit := strings.Split(params[13], utils.ANDSep)
|
|
if len(wghtSplit) > 2 {
|
|
return utils.ErrUnsupportedFormat
|
|
}
|
|
if wghtSplit[0] != utils.EmptyString {
|
|
appendRoute.Weights[0].FilterIDs = []string{wghtSplit[0]}
|
|
}
|
|
if wghtSplit[1] != utils.EmptyString {
|
|
appendRoute.Weights[0].Weight, err = strconv.ParseFloat(wghtSplit[1], 64)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
// populate RouteProfile's RouteBlocker
|
|
if params[14] != utils.EmptyString {
|
|
appendRoute.Blockers = utils.DynamicBlockers{&utils.DynamicBlocker{}}
|
|
blckrSplit := strings.Split(params[14], utils.ANDSep)
|
|
if len(blckrSplit) > 2 {
|
|
return utils.ErrUnsupportedFormat
|
|
}
|
|
if blckrSplit[0] != utils.EmptyString {
|
|
appendRoute.Blockers[0].FilterIDs = []string{blckrSplit[0]}
|
|
}
|
|
if blckrSplit[1] != utils.EmptyString {
|
|
appendRoute.Blockers[0].Blocker, err = strconv.ParseBool(blckrSplit[1])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
// populate RouteProfile's RouteParameters
|
|
appendRoute.RouteParameters = params[15]
|
|
args.Routes = append(args.Routes, appendRoute)
|
|
}
|
|
} else {
|
|
args.Routes = routeProfileFound.Routes
|
|
}
|
|
// populate RouteProfile's APIOpts
|
|
if params[16] != utils.EmptyString {
|
|
if err := parseParamStringToMap(params[16], args.APIOpts); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// create the RouteProfile based on the populated parameters
|
|
var rply string
|
|
if err = aL.connMgr.Call(ctx, aL.config.ActionSCfg().AdminSConns,
|
|
utils.AdminSv1SetRouteProfile, 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
|
|
}
|
|
|
|
// actDynamicRate processes the `ActionDiktatsOpts` field from the action to construct a RateProfile
|
|
//
|
|
// 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 MinCost: string
|
|
// 5 MaxCost: string
|
|
// 6 MaxCostStrategy: string
|
|
// 7 RateID: string
|
|
// 8 RateFilterIDs: strings separated by "&".
|
|
// 9 RateActivationStart: string
|
|
// 10 RateWeights: strings separated by "&".
|
|
// 11 RateBlocker: bool
|
|
// 12 RateIntervalStart: string
|
|
// 13 RateFixedFee: string
|
|
// 14 RateRecurrentFee: string
|
|
// 15 RateUnit: string
|
|
// 16 RateIncrement: string
|
|
// 17 APIOpts: set of key-value pairs (separated by "&").
|
|
//
|
|
// Parameters are separated by ";" and must be provided in the specified order.
|
|
type actDynamicRate struct {
|
|
config *config.CGRConfig
|
|
connMgr *engine.ConnManager
|
|
fltrS *engine.FilterS
|
|
aCfg *utils.APAction
|
|
tnt string
|
|
cgrEv *utils.CGREvent
|
|
}
|
|
|
|
func (aL *actDynamicRate) id() string {
|
|
return aL.aCfg.ID
|
|
}
|
|
|
|
func (aL *actDynamicRate) cfg() *utils.APAction {
|
|
return aL.aCfg
|
|
}
|
|
|
|
// execute implements actioner interface
|
|
func (aL *actDynamicRate) 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) != 18 {
|
|
return fmt.Errorf("invalid number of parameters <%d> expected 18", 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.APIRateProfile{
|
|
RateProfile: &utils.RateProfile{
|
|
Tenant: params[0],
|
|
ID: params[1],
|
|
MaxCostStrategy: params[6],
|
|
Rates: map[string]*utils.Rate{
|
|
params[7]: {
|
|
ID: params[7],
|
|
ActivationTimes: params[9],
|
|
IntervalRates: []*utils.IntervalRate{{}},
|
|
},
|
|
},
|
|
},
|
|
APIOpts: make(map[string]any),
|
|
}
|
|
// populate RateProfile's FilterIDs
|
|
if params[2] != utils.EmptyString {
|
|
args.FilterIDs = strings.Split(params[2], utils.ANDSep)
|
|
}
|
|
// populate RateProfile'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 RateProfile's MinCost
|
|
if params[4] != utils.EmptyString {
|
|
args.MinCost, err = utils.NewDecimalFromString(params[4])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
// populate RateProfile's MaxCost
|
|
if params[5] != utils.EmptyString {
|
|
args.MaxCost, err = utils.NewDecimalFromString(params[5])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
// populate RateProfile's Rate
|
|
if params[7] != utils.EmptyString {
|
|
// populate Rate's FilterIDs
|
|
if params[8] != utils.EmptyString {
|
|
args.Rates[params[7]].FilterIDs = strings.Split(params[8], utils.ANDSep)
|
|
}
|
|
// populate Rate's Weights
|
|
if params[10] != utils.EmptyString {
|
|
args.Rates[params[7]].Weights = utils.DynamicWeights{&utils.DynamicWeight{}}
|
|
wghtSplit := strings.Split(params[10], utils.ANDSep)
|
|
if len(wghtSplit) > 2 {
|
|
return utils.ErrUnsupportedFormat
|
|
}
|
|
if wghtSplit[0] != utils.EmptyString {
|
|
args.Rates[params[7]].Weights[0].FilterIDs = []string{wghtSplit[0]}
|
|
}
|
|
if wghtSplit[1] != utils.EmptyString {
|
|
args.Rates[params[7]].Weights[0].Weight, err = strconv.ParseFloat(wghtSplit[1], 64)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
// populate Rate's Blocker
|
|
if params[11] != utils.EmptyString {
|
|
args.Rates[params[7]].Blocker, err = strconv.ParseBool(params[11])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
// populate Rate's IntervalStart
|
|
if params[12] != utils.EmptyString {
|
|
args.Rates[params[7]].IntervalRates[0].IntervalStart, err = utils.NewDecimalFromString(params[12])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
// populate Rate's FixedFee
|
|
if params[13] != utils.EmptyString {
|
|
args.Rates[params[7]].IntervalRates[0].FixedFee, err = utils.NewDecimalFromString(params[13])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
// populate Rate's RecurrentFee
|
|
if params[14] != utils.EmptyString {
|
|
args.Rates[params[7]].IntervalRates[0].RecurrentFee, err = utils.NewDecimalFromString(params[14])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
// populate Rate's Unit
|
|
if params[15] != utils.EmptyString {
|
|
args.Rates[params[7]].IntervalRates[0].Unit, err = utils.NewDecimalFromString(params[15])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
// populate Rate's Increment
|
|
if params[16] != utils.EmptyString {
|
|
args.Rates[params[7]].IntervalRates[0].Increment, err = utils.NewDecimalFromString(params[16])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
// populate RateProfile's APIOpts
|
|
if params[17] != utils.EmptyString {
|
|
if err := parseParamStringToMap(params[17], args.APIOpts); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// create the RateProfile based on the populated parameters
|
|
var rply string
|
|
if err = aL.connMgr.Call(ctx, aL.config.ActionSCfg().AdminSConns,
|
|
utils.AdminSv1SetRateProfile, 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
|
|
}
|
|
|
|
// actDynamicIP processes the `ActionDiktatsOpts` field from the action to construct a IPProfile
|
|
//
|
|
// 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 TTL: duration
|
|
// 5 Stored: bool
|
|
// 6 PoolID: string
|
|
// 7 PoolFilterIDs: strings separated by "&".
|
|
// 8 PoolType: string
|
|
// 9 PoolRange: string
|
|
// 10 PoolStrategy: string
|
|
// 11 PoolMessage: string
|
|
// 12 PoolWeights: strings separated by "&".
|
|
// 13 PoolBlockers: strings separated by "&".
|
|
// 14 APIOpts: set of key-value pairs (separated by "&").
|
|
//
|
|
// Parameters are separated by ";" and must be provided in the specified order.
|
|
type actDynamicIP struct {
|
|
config *config.CGRConfig
|
|
connMgr *engine.ConnManager
|
|
fltrS *engine.FilterS
|
|
aCfg *utils.APAction
|
|
tnt string
|
|
cgrEv *utils.CGREvent
|
|
}
|
|
|
|
func (aL *actDynamicIP) id() string {
|
|
return aL.aCfg.ID
|
|
}
|
|
|
|
func (aL *actDynamicIP) cfg() *utils.APAction {
|
|
return aL.aCfg
|
|
}
|
|
|
|
// execute implements actioner interface
|
|
func (aL *actDynamicIP) 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) != 15 {
|
|
return fmt.Errorf("invalid number of parameters <%d> expected 15", 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.IPProfileWithAPIOpts{
|
|
IPProfile: &utils.IPProfile{
|
|
Tenant: params[0],
|
|
ID: params[1],
|
|
Pools: []*utils.IPPool{
|
|
{
|
|
ID: params[6],
|
|
Type: params[8],
|
|
Range: params[9],
|
|
Strategy: params[10],
|
|
Message: params[11],
|
|
},
|
|
},
|
|
},
|
|
APIOpts: make(map[string]any),
|
|
}
|
|
// populate IPProfile's FilterIDs
|
|
if params[2] != utils.EmptyString {
|
|
args.FilterIDs = strings.Split(params[2], utils.ANDSep)
|
|
}
|
|
// populate IPProfile'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 IPProfile's TTL
|
|
if params[4] != utils.EmptyString {
|
|
args.TTL, err = utils.ParseDurationWithNanosecs(params[4])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
// populate IPProfile's Stored
|
|
if params[5] != utils.EmptyString {
|
|
args.Stored, err = strconv.ParseBool(params[5])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
// populate IPProfile's Pool
|
|
if params[6] != utils.EmptyString {
|
|
// populate Pool's FilterIDs
|
|
if params[7] != utils.EmptyString {
|
|
args.Pools[0].FilterIDs = strings.Split(params[7], utils.ANDSep)
|
|
}
|
|
// populate Pool's Weights
|
|
if params[12] != utils.EmptyString {
|
|
args.Pools[0].Weights = utils.DynamicWeights{&utils.DynamicWeight{}}
|
|
wghtSplit := strings.Split(params[12], utils.ANDSep)
|
|
if len(wghtSplit) > 2 {
|
|
return utils.ErrUnsupportedFormat
|
|
}
|
|
if wghtSplit[0] != utils.EmptyString {
|
|
args.Pools[0].Weights[0].FilterIDs = []string{wghtSplit[0]}
|
|
}
|
|
if wghtSplit[1] != utils.EmptyString {
|
|
args.Pools[0].Weights[0].Weight, err = strconv.ParseFloat(wghtSplit[1], 64)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
// populate Pool's Blocker
|
|
if params[13] != utils.EmptyString {
|
|
args.Pools[0].Blockers = utils.DynamicBlockers{&utils.DynamicBlocker{}}
|
|
blckrSplit := strings.Split(params[13], utils.ANDSep)
|
|
if len(blckrSplit) > 2 {
|
|
return utils.ErrUnsupportedFormat
|
|
}
|
|
if blckrSplit[0] != utils.EmptyString {
|
|
args.Pools[0].Blockers[0].FilterIDs = []string{blckrSplit[0]}
|
|
}
|
|
if blckrSplit[1] != utils.EmptyString {
|
|
args.Pools[0].Blockers[0].Blocker, err = strconv.ParseBool(blckrSplit[1])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// populate IPProfile's APIOpts
|
|
if params[14] != utils.EmptyString {
|
|
if err := parseParamStringToMap(params[14], args.APIOpts); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// create the IPProfile based on the populated parameters
|
|
var rply string
|
|
if err = aL.connMgr.Call(ctx, aL.config.ActionSCfg().AdminSConns,
|
|
utils.AdminSv1SetIPProfile, 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
|
|
}
|
|
|
|
// 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
|
|
}
|