mirror of
https://github.com/cgrates/cgrates.git
synced 2026-02-24 16:48:45 +05:00
Add action type *dynamic_trend
This commit is contained in:
committed by
Dan Christian Bogos
parent
959a9f38d2
commit
36abb6fac8
115
engine/action.go
115
engine/action.go
@@ -201,6 +201,7 @@ func init() {
|
||||
actionFuncMap[utils.MetaDynamicRoute] = dynamicRoute
|
||||
actionFuncMap[utils.MetaDynamicRanking] = dynamicRanking
|
||||
actionFuncMap[utils.MetaDynamicRatingProfile] = dynamicRatingProfile
|
||||
actionFuncMap[utils.MetaDynamicRanking] = dynamicTrend
|
||||
}
|
||||
|
||||
func getActionFunc(typ string) (f actionTypeFunc, exists bool) {
|
||||
@@ -1535,7 +1536,7 @@ func dynamicThreshold(_ *Account, act *Action, _ Actions, _ *FilterS, ev any,
|
||||
}
|
||||
// populate Threshold's MinSleep
|
||||
if params[6] != utils.EmptyString {
|
||||
thProf.MinSleep, err = time.ParseDuration(params[6])
|
||||
thProf.MinSleep, err = utils.ParseDurationWithNanosecs(params[6])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1659,9 +1660,9 @@ func dynamicStats(_ *Account, act *Action, _ Actions, _ *FilterS, ev any,
|
||||
return err
|
||||
}
|
||||
}
|
||||
// populate Stat's QueueLeTTLngh
|
||||
// populate Stat's TTL
|
||||
if params[5] != utils.EmptyString {
|
||||
stQProf.TTL, err = time.ParseDuration(params[5])
|
||||
stQProf.TTL, err = utils.ParseDurationWithNanosecs(params[5])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -2549,3 +2550,111 @@ func dynamicRatingProfile(_ *Account, act *Action, _ Actions, _ *FilterS, ev any
|
||||
var reply string
|
||||
return connMgr.Call(context.Background(), connCfg.ConnIDs, utils.APIerSv1SetRatingProfile, ratingProf, &reply)
|
||||
}
|
||||
|
||||
// dynamicTrend processes the `ExtraParameters` field from the action to
|
||||
// construct a TrendProfile
|
||||
//
|
||||
// The ExtraParameters 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.
|
||||
func dynamicTrend(_ *Account, act *Action, _ Actions, _ *FilterS, ev any,
|
||||
_ SharedActionsData, connCfg ActionConnCfg) (err error) {
|
||||
cgrEv, canCast := ev.(*utils.CGREvent)
|
||||
if !canCast {
|
||||
return errors.New("Couldn't cast event to CGREvent")
|
||||
}
|
||||
dP := utils.MapStorage{ // create DataProvider from event
|
||||
utils.MetaReq: cgrEv.Event,
|
||||
utils.MetaTenant: cgrEv.Tenant,
|
||||
utils.MetaNow: time.Now(),
|
||||
utils.MetaOpts: cgrEv.APIOpts,
|
||||
}
|
||||
// Parse action parameters based on the predefined format.
|
||||
params := strings.Split(act.ExtraParameters, utils.InfieldSep)
|
||||
if len(params) != 13 {
|
||||
return errors.New(fmt.Sprintf("invalid number of parameters <%d> expected 13", len(params)))
|
||||
}
|
||||
// parse dynamic parameters
|
||||
for i := range params {
|
||||
if params[i], err = utils.ParseParamForDataProvider(params[i], dP, false); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// Prepare request arguments based on provided parameters.
|
||||
trend := &TrendProfileWithAPIOpts{
|
||||
TrendProfile: &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 {
|
||||
trend.Metrics = strings.Split(params[4], utils.ANDSep)
|
||||
}
|
||||
// populate Trend's TTL
|
||||
if params[5] != utils.EmptyString {
|
||||
trend.TTL, err = utils.ParseDurationWithNanosecs(params[5])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// populate Trend's QueueLength
|
||||
if params[6] != utils.EmptyString {
|
||||
trend.QueueLength, err = strconv.Atoi(params[6])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// populate Trend's MinItems
|
||||
if params[7] != utils.EmptyString {
|
||||
trend.MinItems, err = strconv.Atoi(params[7])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// populate Trend's Tolerance
|
||||
if params[9] != utils.EmptyString {
|
||||
trend.Tolerance, err = strconv.ParseFloat(params[9], 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// populate Trend's Stored
|
||||
if params[10] != utils.EmptyString {
|
||||
trend.Stored, err = strconv.ParseBool(params[10])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// populate Trend's ThresholdIDs
|
||||
if params[11] != utils.EmptyString {
|
||||
trend.ThresholdIDs = strings.Split(params[11], utils.ANDSep)
|
||||
}
|
||||
// populate Trend's APIOpts
|
||||
if params[12] != utils.EmptyString {
|
||||
if err := parseParamStringToMap(params[12], trend.APIOpts); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// create the TrendProfile based on the populated parameters
|
||||
var reply string
|
||||
return connMgr.Call(context.Background(), connCfg.ConnIDs, utils.APIerSv1SetTrendProfile, trend, &reply)
|
||||
}
|
||||
|
||||
@@ -6942,3 +6942,182 @@ func TestDynamicRatingProfile(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDynamicTrend(t *testing.T) {
|
||||
tempConn := connMgr
|
||||
tmpDm := dm
|
||||
tmpCache := Cache
|
||||
defer func() {
|
||||
config.SetCgrConfig(config.NewDefaultCGRConfig())
|
||||
SetConnManager(tempConn)
|
||||
dm = tmpDm
|
||||
Cache = tmpCache
|
||||
}()
|
||||
Cache.Clear(nil)
|
||||
var tpwo *TrendProfileWithAPIOpts
|
||||
ccMock := &ccMock{
|
||||
calls: map[string]func(ctx *context.Context, args any, reply any) error{
|
||||
utils.APIerSv1SetTrendProfile: func(ctx *context.Context, args, reply any) error {
|
||||
var canCast bool
|
||||
if tpwo, canCast = args.(*TrendProfileWithAPIOpts); !canCast {
|
||||
return fmt.Errorf("couldnt cast")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
},
|
||||
}
|
||||
connID := utils.ConcatenatedKey(utils.MetaInternal, utils.MetaApier)
|
||||
clientconn := make(chan birpc.ClientConnector, 1)
|
||||
clientconn <- ccMock
|
||||
NewConnManager(config.NewDefaultCGRConfig(), map[string]chan birpc.ClientConnector{
|
||||
connID: clientconn,
|
||||
})
|
||||
testcases := []struct {
|
||||
name string
|
||||
extraParams string
|
||||
connIDs []string
|
||||
expTpwo *TrendProfileWithAPIOpts
|
||||
expectedErr string
|
||||
}{
|
||||
{
|
||||
name: "SuccessfulRequest",
|
||||
connIDs: []string{connID},
|
||||
expTpwo: &TrendProfileWithAPIOpts{
|
||||
TrendProfile: &TrendProfile{
|
||||
Tenant: "cgrates.org",
|
||||
ID: "TREND1",
|
||||
Schedule: "0 12 * * *",
|
||||
StatID: "Stats2",
|
||||
Metrics: []string{"*acc", "*tcc"},
|
||||
TTL: -1,
|
||||
QueueLength: -1,
|
||||
MinItems: 1,
|
||||
CorrelationType: "*average",
|
||||
Tolerance: 2.1,
|
||||
Stored: true,
|
||||
ThresholdIDs: []string{"TD1", "TD2"},
|
||||
},
|
||||
APIOpts: map[string]any{
|
||||
"key": "value",
|
||||
},
|
||||
},
|
||||
extraParams: "cgrates.org;TREND1;0 12 * * *;Stats2;*acc&*tcc;-1;-1;1;*average;2.1;true;TD1&TD2;key:value",
|
||||
},
|
||||
{
|
||||
name: "SuccessfulRequestWithDynamicPaths",
|
||||
connIDs: []string{connID},
|
||||
expTpwo: &TrendProfileWithAPIOpts{
|
||||
TrendProfile: &TrendProfile{
|
||||
Tenant: "cgrates.org",
|
||||
ID: "TREND_1001",
|
||||
Schedule: "0 12 * * *",
|
||||
StatID: "Stats2",
|
||||
Metrics: []string{"*acc", "*tcc"},
|
||||
TTL: -1,
|
||||
QueueLength: -1,
|
||||
MinItems: 1,
|
||||
CorrelationType: "*average",
|
||||
Tolerance: 2.1,
|
||||
Stored: true,
|
||||
ThresholdIDs: []string{"TD1", "TD2"},
|
||||
},
|
||||
APIOpts: map[string]any{
|
||||
"key": "value",
|
||||
},
|
||||
},
|
||||
extraParams: "*tenant;TREND_<~*req.Account>;0 12 * * *;Stats2;*acc&*tcc;-1;-1;1;*average;2.1;true;TD1&TD2;key:value",
|
||||
},
|
||||
{
|
||||
name: "SuccessfulRequestEmptyFields",
|
||||
connIDs: []string{connID},
|
||||
expTpwo: &TrendProfileWithAPIOpts{
|
||||
TrendProfile: &TrendProfile{
|
||||
Tenant: "cgrates.org",
|
||||
ID: "TREND_1001",
|
||||
Schedule: "0 12 * * *",
|
||||
StatID: "",
|
||||
Metrics: nil,
|
||||
TTL: 0,
|
||||
QueueLength: 0,
|
||||
MinItems: 0,
|
||||
CorrelationType: "",
|
||||
Tolerance: 0,
|
||||
Stored: false,
|
||||
ThresholdIDs: nil,
|
||||
},
|
||||
APIOpts: map[string]any{},
|
||||
},
|
||||
extraParams: "cgrates.org;TREND_1001;0 12 * * *;;;;;;;;;;",
|
||||
},
|
||||
{
|
||||
name: "MissingConns",
|
||||
extraParams: "cgrates.org;TREND1;0 12 * * *;Stats2;*acc&*tcc;-1;-1;1;*average;2.1;true;TD1&TD2;key:value",
|
||||
expectedErr: "MANDATORY_IE_MISSING: [connIDs]",
|
||||
},
|
||||
{
|
||||
name: "WrongNumberOfParams",
|
||||
extraParams: "tenant;TREND1;;",
|
||||
expectedErr: "invalid number of parameters <4> expected 13",
|
||||
},
|
||||
{
|
||||
name: "TTLFail",
|
||||
extraParams: "cgrates.org;TREND1;0 12 * * *;Stats2;*acc&*tcc;BadString;-1;1;*average;2.1;true;TD1&TD2;key:value",
|
||||
expectedErr: `time: invalid duration "BadString"`,
|
||||
},
|
||||
{
|
||||
name: "QueueLengthFail",
|
||||
extraParams: "cgrates.org;TREND1;0 12 * * *;Stats2;*acc&*tcc;-1;BadString;1;*average;2.1;true;TD1&TD2;key:value",
|
||||
expectedErr: `strconv.Atoi: parsing "BadString": invalid syntax`,
|
||||
},
|
||||
{
|
||||
name: "MinItemsFail",
|
||||
extraParams: "cgrates.org;TREND1;0 12 * * *;Stats2;*acc&*tcc;-1;-1;BadString;*average;2.1;true;TD1&TD2;key:value",
|
||||
expectedErr: `strconv.Atoi: parsing "BadString": invalid syntax`,
|
||||
},
|
||||
{
|
||||
name: "ToleranceFail",
|
||||
extraParams: "cgrates.org;TREND1;0 12 * * *;Stats2;*acc&*tcc;-1;-1;1;*average;BadString;true;TD1&TD2;key:value",
|
||||
expectedErr: `strconv.ParseFloat: parsing "BadString": invalid syntax`,
|
||||
},
|
||||
{
|
||||
name: "StoredFail",
|
||||
extraParams: "cgrates.org;TREND1;0 12 * * *;Stats2;*acc&*tcc;-1;-1;1;*average;2.1;BadString;TD1&TD2;key:value",
|
||||
expectedErr: `strconv.ParseBool: parsing "BadString": invalid syntax`,
|
||||
},
|
||||
{
|
||||
name: "InvalidOptsMap",
|
||||
extraParams: "cgrates.org;TREND1;0 12 * * *;Stats2;*acc&*tcc;-1;-1;1;*average;2.1;true;TD1&TD2;opt",
|
||||
expectedErr: "invalid key-value pair: opt",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testcases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
action := &Action{ExtraParameters: tc.extraParams}
|
||||
ev := &utils.CGREvent{
|
||||
Tenant: "cgrates.org",
|
||||
ID: "evID",
|
||||
Time: &time.Time{},
|
||||
Event: map[string]any{
|
||||
utils.AccountField: "1001",
|
||||
},
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
tpwo = nil
|
||||
})
|
||||
err := dynamicTrend(nil, action, nil, nil, ev,
|
||||
SharedActionsData{}, ActionConnCfg{
|
||||
ConnIDs: tc.connIDs,
|
||||
})
|
||||
if tc.expectedErr != "" {
|
||||
if err == nil || err.Error() != tc.expectedErr {
|
||||
t.Errorf("expected error <%v>, received <%v>", tc.expectedErr, err)
|
||||
}
|
||||
} else if err != nil {
|
||||
t.Error(err)
|
||||
} else if !reflect.DeepEqual(tpwo, tc.expTpwo) {
|
||||
t.Errorf("Expected <%v>\nReceived\n<%v>", utils.ToJSON(tc.expTpwo), utils.ToJSON(tpwo))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user