Add action type *dynamic_resource

This commit is contained in:
arberkatellari
2025-06-27 11:34:48 +02:00
committed by Dan Christian Bogos
parent 3059b768a9
commit 7b309d388d
5 changed files with 185 additions and 2 deletions

View File

@@ -508,3 +508,133 @@ func (aL *actDynamicAttribute) execute(ctx *context.Context, data utils.MapStora
return aL.connMgr.Call(ctx, aL.config.ActionSCfg().AdminSConns,
utils.AdminSv1SetAttributeProfile, args, &rply)
}
// actDynamicResource processes the `ActionValue` field from the action to construct a ResourceProfile
//
// The ActionValue 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
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 speified for action <%v>", aL.aCfg.ID)
}
params := strings.Split(aL.aCfg.Diktats[0].Value, 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
return aL.connMgr.Call(ctx, aL.config.ActionSCfg().AdminSConns,
utils.AdminSv1SetResourceProfile, args, &rply)
}

View File

@@ -39,6 +39,8 @@ func actionTarget(act string) string {
return utils.MetaAccounts
case utils.MetaDynamicAttribute:
return utils.MetaAttributes
case utils.MetaDynamicResource:
return utils.MetaResources
default:
return utils.MetaNone
}
@@ -141,6 +143,8 @@ func newActioner(ctx *context.Context, cgrEv *utils.CGREvent, cfg *config.CGRCon
return &actDynamicStats{cfg, connMgr, aCfg, tnt, cgrEv}, nil
case utils.MetaDynamicAttribute:
return &actDynamicAttribute{cfg, connMgr, aCfg, tnt, cgrEv}, nil
case utils.MetaDynamicResource:
return &actDynamicResource{cfg, connMgr, aCfg, tnt, cgrEv}, nil
default:
return nil, fmt.Errorf("unsupported action type: <%s>", aCfg.Type)

File diff suppressed because one or more lines are too long

View File

@@ -169,6 +169,7 @@ func TestDynThdIT(t *testing.T) {
utils.MetaThresholds: {"someID": {}},
utils.MetaStats: {"someID": {}},
utils.MetaAttributes: {"someID": {}},
utils.MetaResources: {"someID": {}},
},
Actions: []*utils.APAction{
{
@@ -201,6 +202,16 @@ func TestDynThdIT(t *testing.T) {
},
},
},
{
ID: "Dynamic_Attribute_ID",
Type: utils.MetaDynamicResource,
Diktats: []*utils.APDiktat{
{
Path: "ExtraParameters",
Value: "*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",
},
},
},
},
},
}
@@ -457,7 +468,44 @@ func TestDynThdIT(t *testing.T) {
}
if !reflect.DeepEqual(exp, attrs) {
t.Errorf("Expected attributes to be the same. Before shutdown \n<%v>\nAfter rebooting <%v>", utils.ToJSON(attrs), utils.ToJSON(exp))
t.Errorf("Expected <%v>\nReceived <%v>", utils.ToJSON(attrs), utils.ToJSON(exp))
}
})
t.Run("GetDynamicResourceProfile", func(t *testing.T) {
var rsc []*utils.ResourceProfile
if err := client.Call(context.Background(), utils.AdminSv1GetResourceProfiles,
&utils.ArgsItemIDs{
Tenant: utils.CGRateSorg,
}, &rsc); err != nil {
t.Errorf("AdminSv1GetResourceProfiles failed unexpectedly: %v", err)
}
if len(rsc) != 1 {
t.Fatalf("AdminSv1GetResourceProfiles len(rsc)=%v, want 1", len(rsc))
}
exp := []*utils.ResourceProfile{
{
Tenant: "cgrates.org",
ID: "DYNAMICLY_RES_1002",
FilterIDs: []string{"*string:~*req.Account:1002"},
UsageTTL: 5 * time.Second,
Limit: 5,
AllocationMessage: "alloc_msg",
Blocker: true,
Stored: true,
Weights: utils.DynamicWeights{
{
FilterIDs: []string{"*string:~*req.Account:1002"},
Weight: 30,
},
},
ThresholdIDs: []string{"THID1", "THID2"},
},
}
if !reflect.DeepEqual(exp, rsc) {
t.Errorf("Expected <%v>\nReceived <%v>", utils.ToJSON(rsc), utils.ToJSON(exp))
}
})

View File

@@ -1133,6 +1133,7 @@ const (
MetaDynamicThreshold = "*dynamic_threshold"
MetaDynamicStats = "*dynamic_stats"
MetaDynamicAttribute = "*dynamic_attribute"
MetaDynamicResource = "*dynamic_resource"
)
// Migrator Metas