diff --git a/apis/attributes_it_test.go b/apis/attributes_it_test.go index d306b4855..6eb789e70 100644 --- a/apis/attributes_it_test.go +++ b/apis/attributes_it_test.go @@ -609,7 +609,7 @@ func testAttributeSGetAttributeForEventAnyContext(t *testing.T) { Value: "1001", }, }, - Weight: 10.0, + Weights: ";10.0", }, } var result string @@ -644,7 +644,7 @@ func testAttributeSGetAttributeForEventAnyContext(t *testing.T) { Value: "1001", }, }, - Weight: 10.0, + Weights: ";10.0", } if !reflect.DeepEqual(expAttrFromEv, attrReply) { t.Errorf("Expecting: %s, received: %s", utils.ToJSON(expAttrFromEv), utils.ToJSON(attrReply)) @@ -680,7 +680,7 @@ func testAttributeSGetAttributeForEventSameAnyContext(t *testing.T) { Value: "1001", }, }, - Weight: 10.0, + Weights: ";10.0", } if !reflect.DeepEqual(expAttrFromEv, attrReply) { t.Errorf("Expecting: %s, received: %s", utils.ToJSON(expAttrFromEv), utils.ToJSON(attrReply)) @@ -738,7 +738,7 @@ func testAttributeSGetAttributeForEvent(t *testing.T) { FilterIDs: []string{}, }, }, - Weight: 10.0, + Weights: ";10.0", } if *encoding == utils.MetaGOB { eAttrPrf.Attributes[0].FilterIDs = nil @@ -764,7 +764,7 @@ func testAttributeSGetAttributeForEvent(t *testing.T) { FilterIDs: []string{}, }, }, - Weight: 10.0, + Weights: ";10.0", }, } if err := attrSRPC.Call(context.Background(), utils.AdminSv1SetAttributeProfile, @@ -854,7 +854,7 @@ func testAttributeProcessEventWithSearchAndReplace(t *testing.T) { }, }, Blocker: true, - Weight: 10, + Weights: ";10", }, } var result string @@ -912,7 +912,7 @@ func testAttributeSProcessWithMultipleRuns(t *testing.T) { Value: "Value1", }, }, - Weight: 10, + Weights: ";10", }, } attrPrf2 := &engine.APIAttributeProfileWithAPIOpts{ @@ -926,7 +926,7 @@ func testAttributeSProcessWithMultipleRuns(t *testing.T) { Value: "Value2", }, }, - Weight: 20, + Weights: ";20", }, } attrPrf3 := &engine.APIAttributeProfileWithAPIOpts{ @@ -940,7 +940,7 @@ func testAttributeSProcessWithMultipleRuns(t *testing.T) { Value: "Value3", }, }, - Weight: 30, + Weights: ";30", }, } // Add attribute in DM @@ -1016,7 +1016,7 @@ func testAttributeSProcessWithMultipleRuns2(t *testing.T) { Value: "Value1", }, }, - Weight: 10, + Weights: ";10", }, } attrPrf2 := &engine.APIAttributeProfileWithAPIOpts{ @@ -1030,7 +1030,7 @@ func testAttributeSProcessWithMultipleRuns2(t *testing.T) { Value: "Value2", }, }, - Weight: 20, + Weights: ";20", }, } attrPrf3 := &engine.APIAttributeProfileWithAPIOpts{ @@ -1044,7 +1044,7 @@ func testAttributeSProcessWithMultipleRuns2(t *testing.T) { Value: "Value3", }, }, - Weight: 30, + Weights: ";30", }, } // Add attributeProfiles diff --git a/apis/cache_it_test.go b/apis/cache_it_test.go index 289d9aa33..508fc4781 100644 --- a/apis/cache_it_test.go +++ b/apis/cache_it_test.go @@ -449,7 +449,7 @@ func testCacheSSetMoreAttributeProfiles(t *testing.T) { Value: "Value1", }, }, - Weight: 10, + Weights: ";10", }, } attrPrf2 := &engine.APIAttributeProfileWithAPIOpts{ @@ -463,7 +463,7 @@ func testCacheSSetMoreAttributeProfiles(t *testing.T) { Value: "Value2", }, }, - Weight: 20, + Weights: ";20", }, } attrPrf3 := &engine.APIAttributeProfileWithAPIOpts{ @@ -477,7 +477,7 @@ func testCacheSSetMoreAttributeProfiles(t *testing.T) { Value: "Value3", }, }, - Weight: 30, + Weights: ";30", }, } // Add attributeProfiles diff --git a/apis/filters_test.go b/apis/filters_test.go index bd215ab13..758d5abae 100644 --- a/apis/filters_test.go +++ b/apis/filters_test.go @@ -811,7 +811,7 @@ func TestFiltersSetFilterReloadCache(t *testing.T) { APIAttributeProfile: &engine.APIAttributeProfile{ FilterIDs: []string{"FLTR_ID"}, ID: "ATTR_ID", - Weight: 10, + Weights: ";10", Attributes: []*engine.ExternalAttribute{ { Path: "*req.Account", @@ -968,7 +968,7 @@ func TestFiltersSetFilterClearCache(t *testing.T) { APIAttributeProfile: &engine.APIAttributeProfile{ FilterIDs: []string{"FLTR_ID"}, ID: "ATTR_ID", - Weight: 10, + Weights: ";10", Attributes: []*engine.ExternalAttribute{ { Path: "*req.Account", diff --git a/engine/attributes.go b/engine/attributes.go index ded22a6cc..a90d114a0 100644 --- a/engine/attributes.go +++ b/engine/attributes.go @@ -74,8 +74,10 @@ func (alS *AttributeS) attributeProfileForEvent(ctx *context.Context, tnt string } attrIDs = aPrflIDs.AsSlice() } + var apWw *apWithWeight for _, apID := range attrIDs { - aPrfl, err := alS.dm.GetAttributeProfile(ctx, tnt, apID, true, true, utils.NonTransactional) + var aPrfl *AttributeProfile + aPrfl, err = alS.dm.GetAttributeProfile(ctx, tnt, apID, true, true, utils.NonTransactional) if err != nil { if err == utils.ErrNotFound { continue @@ -92,18 +94,24 @@ func (alS *AttributeS) attributeProfileForEvent(ctx *context.Context, tnt string continue } } - if (matchAttrPrfl == nil || matchAttrPrfl.Weight < aPrfl.Weight) && + + var apfWeight float64 + if apfWeight, err = WeightFromDynamics(ctx, aPrfl.Weights, + alS.fltrS, tnt, evNm); err != nil { + return + } + if (apWw == nil || apWw.weight < apfWeight) && tntID != lastID && (profileRuns <= 0 || processedPrfNo[tntID] < profileRuns) { - matchAttrPrfl = aPrfl + apWw = &apWithWeight{aPrfl, apfWeight} } } // All good, convert from Map to Slice so we can sort - if matchAttrPrfl == nil { + if apWw == nil { return nil, utils.ErrNotFound } - (evNm[utils.MetaVars].(utils.MapStorage))[utils.MetaAttrPrfTenantID] = matchAttrPrfl.TenantIDInline() - return + (evNm[utils.MetaVars].(utils.MapStorage))[utils.MetaAttrPrfTenantID] = apWw.AttributeProfile.TenantIDInline() + return apWw.AttributeProfile, nil } // AttrSProcessEventReply reply used for proccess event diff --git a/engine/libattributes.go b/engine/libattributes.go index 6417b0a27..4ded17fa5 100644 --- a/engine/libattributes.go +++ b/engine/libattributes.go @@ -20,13 +20,17 @@ package engine import ( "fmt" - "sort" "strings" "github.com/cgrates/cgrates/config" "github.com/cgrates/cgrates/utils" ) +type apWithWeight struct { + *AttributeProfile + weight float64 +} + // Attribute used by AttributeProfile to describe a single attribute type Attribute struct { FilterIDs []string @@ -42,7 +46,7 @@ type AttributeProfile struct { FilterIDs []string Attributes []*Attribute Blocker bool // blocker flag to stop processing on multiple runs - Weight float64 + Weights utils.DynamicWeights } // AttributeProfileWithAPIOpts is used in replicatorV1 for dispatcher @@ -81,10 +85,10 @@ func (ap *AttributeProfile) TenantIDInline() string { // AttributeProfiles is a sortable list of Attribute profiles type AttributeProfiles []*AttributeProfile -// Sort is part of sort interface, sort based on Weight -func (aps AttributeProfiles) Sort() { - sort.Slice(aps, func(i, j int) bool { return aps[i].Weight > aps[j].Weight }) -} +// // Sort is part of sort interface, sort based on Weight +// func (aps AttributeProfiles) Sort() { +// sort.Slice(aps, func(i, j int) bool { return aps[i].Weights[i] > aps[j].Weights }) +// } // ExternalAttribute the attribute for external profile type ExternalAttribute struct { @@ -101,7 +105,7 @@ type APIAttributeProfile struct { FilterIDs []string Attributes []*ExternalAttribute Blocker bool // blocker flag to stop processing on multiple runs - Weight float64 + Weights string } type APIAttributeProfileWithAPIOpts struct { @@ -116,7 +120,7 @@ func NewAPIAttributeProfile(attr *AttributeProfile) (ext *APIAttributeProfile) { FilterIDs: attr.FilterIDs, Attributes: make([]*ExternalAttribute, len(attr.Attributes)), Blocker: attr.Blocker, - Weight: attr.Weight, + Weights: attr.Weights.String(utils.InfieldSep, utils.ANDSep), } for i, at := range attr.Attributes { ext.Attributes[i] = &ExternalAttribute{ @@ -135,6 +139,11 @@ func (ext *APIAttributeProfile) AsAttributeProfile() (attr *AttributeProfile, er if len(ext.Attributes) == 0 { return nil, utils.NewErrMandatoryIeMissing("Attributes") } + if ext.Weights != utils.EmptyString { + if attr.Weights, err = utils.NewDynamicWeightsFromString(ext.Weights, utils.InfieldSep, utils.ANDSep); err != nil { + return + } + } attr.Attributes = make([]*Attribute, len(ext.Attributes)) for i, extAttr := range ext.Attributes { if extAttr.Path == utils.EmptyString { @@ -154,8 +163,6 @@ func (ext *APIAttributeProfile) AsAttributeProfile() (attr *AttributeProfile, er attr.Tenant = ext.Tenant attr.ID = ext.ID attr.FilterIDs = ext.FilterIDs - attr.Blocker = ext.Blocker - attr.Weight = ext.Weight return } @@ -203,7 +210,7 @@ func (ap *AttributeProfile) Set(path []string, val interface{}, newBranch bool, ap.Blocker, err = utils.IfaceAsBool(val) case utils.Weight: if val != utils.EmptyString { - ap.Weight, err = utils.IfaceAsFloat64(val) + ap.Weights, err = utils.NewDynamicWeightsFromString(utils.IfaceAsString(val), utils.InfieldSep, utils.ANDSep) } default: return utils.ErrWrongPath @@ -248,9 +255,7 @@ func (ap *AttributeProfile) Merge(v2 interface{}) { if vi.Blocker { ap.Blocker = true } - if vi.Weight != 0 { - ap.Weight = vi.Weight - } + ap.Weights = append(ap.Weights, vi.Weights...) } func (ap *AttributeProfile) String() string { return utils.ToJSON(ap) } @@ -288,7 +293,7 @@ func (ap *AttributeProfile) FieldAsInterface(fldPath []string) (_ interface{}, e case utils.Blocker: return ap.Blocker, nil case utils.Weight: - return ap.Weight, nil + return ap.Weights, nil case utils.Attributes: return ap.Attributes, nil } diff --git a/engine/loader_csv_test.go b/engine/loader_csv_test.go index dcd2eca15..e10583680 100644 --- a/engine/loader_csv_test.go +++ b/engine/loader_csv_test.go @@ -411,7 +411,7 @@ func TestLoadAttributeProfiles(t *testing.T) { }, }, Blocker: true, - Weight: 20, + Weights: 20, }, } diff --git a/engine/model_helpers.go b/engine/model_helpers.go index 63272f0e4..e9b04741b 100644 --- a/engine/model_helpers.go +++ b/engine/model_helpers.go @@ -1065,8 +1065,8 @@ func (tps AttributeMdls) AsTPAttributes() (result []*utils.TPAttributeProfile) { Blocker: tp.Blocker, } } - if tp.Weight != 0 { - th.Weight = tp.Weight + if tp.Weights != utils.EmptyString { + th.Weights = tp.Weights } if tp.FilterIDs != utils.EmptyString { if _, has := filterMap[key.TenantID()]; !has { @@ -1116,10 +1116,11 @@ func APItoModelTPAttribute(ap *utils.TPAttributeProfile) (mdls AttributeMdls) { } mdl.FilterIDs += val } - if ap.Weight != 0 { - mdl.Weight = ap.Weight + if ap.Weights != utils.EmptyString { + mdl.Weights = ap.Weights } } + mdl.Path = reqAttribute.Path mdl.Value = reqAttribute.Value mdl.Type = reqAttribute.Type @@ -1133,11 +1134,15 @@ func APItoAttributeProfile(tpAttr *utils.TPAttributeProfile, timezone string) (a attrPrf = &AttributeProfile{ Tenant: tpAttr.Tenant, ID: tpAttr.ID, - Weight: tpAttr.Weight, Blocker: tpAttr.Blocker, FilterIDs: make([]string, len(tpAttr.FilterIDs)), Attributes: make([]*Attribute, len(tpAttr.Attributes)), } + if tpAttr.Weights != utils.EmptyString { + if attrPrf.Weights, err = utils.NewDynamicWeightsFromString(tpAttr.Weights, utils.InfieldSep, utils.ANDSep); err != nil { + return + } + } for i, fli := range tpAttr.FilterIDs { attrPrf.FilterIDs[i] = fli } @@ -1167,7 +1172,7 @@ func AttributeProfileToAPI(attrPrf *AttributeProfile) (tpAttr *utils.TPAttribute FilterIDs: make([]string, len(attrPrf.FilterIDs)), Attributes: make([]*utils.TPAttribute, len(attrPrf.Attributes)), Blocker: attrPrf.Blocker, - Weight: attrPrf.Weight, + Weights: attrPrf.Weights.String(utils.InfieldSep, utils.ANDSep), } for i, fli := range attrPrf.FilterIDs { tpAttr.FilterIDs[i] = fli diff --git a/engine/model_helpers_test.go b/engine/model_helpers_test.go index 685794528..5fd250570 100644 --- a/engine/model_helpers_test.go +++ b/engine/model_helpers_test.go @@ -1047,7 +1047,7 @@ func TestAPItoAttributeProfile(t *testing.T) { Value: "Al1", }, }, - Weight: 20, + Weights: 20, } expected := &AttributeProfile{ Tenant: "cgrates.org", @@ -1080,7 +1080,7 @@ func TestAttributeProfileToAPI(t *testing.T) { Value: "Al1", }, }, - Weight: 20, + Weights: 20, } attr := &AttributeProfile{ Tenant: "cgrates.org", @@ -1115,7 +1115,7 @@ func TestAttributeProfileToAPI2(t *testing.T) { Value: "~*req.Account", }, }, - Weight: 20, + Weights: 20, } attr := &AttributeProfile{ Tenant: "cgrates.org", @@ -1150,7 +1150,7 @@ func TestAPItoModelTPAttribute(t *testing.T) { Value: "Al1", }, }, - Weight: 20, + Weights: 20, } expected := AttributeMdls{ &AttributeMdl{ @@ -1161,7 +1161,7 @@ func TestAPItoModelTPAttribute(t *testing.T) { AttributeFilterIDs: "filter_id1;filter_id2", Path: utils.MetaReq + utils.NestingSep + "FL1", Value: "Al1", - Weight: 20, + Weights: 20, }, } rcv := APItoModelTPAttribute(tpAlsPrf) @@ -1186,7 +1186,7 @@ func TestCsvDumpForAttributeModels(t *testing.T) { Value: "Al2", }, }, - Weight: 20, + Weights: 20, } expected := AttributeMdls{ &AttributeMdl{ @@ -1196,7 +1196,7 @@ func TestCsvDumpForAttributeModels(t *testing.T) { FilterIDs: "FLTR_ACNT_dan;*ai:~*req.AnswerTime:2014-07-14T14:35:00Z;*string:~*opts.*context:con1", Path: utils.MetaReq + utils.NestingSep + "FL1", Value: "Al1", - Weight: 20, + Weights: 20, }, &AttributeMdl{ Tpid: "TP1", @@ -1231,7 +1231,7 @@ func TestModelAsTPAttribute2(t *testing.T) { FilterIDs: "FLTR_ACNT_dan;FLTR_DST_DE;*ai:~*req.AnswerTime:2014-07-14T14:35:00Z|2014-07-14T14:36:00Z;*string:~*opts.*context:con1", Path: utils.MetaReq + utils.NestingSep + "FL1", Value: "Al1", - Weight: 20, + Weights: 20, }, } expected := &utils.TPAttributeProfile{ @@ -1246,7 +1246,7 @@ func TestModelAsTPAttribute2(t *testing.T) { Value: "Al1", }, }, - Weight: 20, + Weights: 20, } expected2 := &utils.TPAttributeProfile{ TPid: "TP1", @@ -1260,7 +1260,7 @@ func TestModelAsTPAttribute2(t *testing.T) { Value: "Al1", }, }, - Weight: 20, + Weights: 20, } rcv := models.AsTPAttributes() sort.Strings(rcv[0].FilterIDs) @@ -1278,7 +1278,7 @@ func TestModelAsTPAttribute(t *testing.T) { FilterIDs: "FLTR_ACNT_dan;FLTR_DST_DE;*ai:~*req.AnswerTime:2014-07-14T14:35:00Z;*string:~*opts.*context:con1", Path: utils.MetaReq + utils.NestingSep + "FL1", Value: "Al1", - Weight: 20, + Weights: 20, }, } expected := &utils.TPAttributeProfile{ @@ -1293,7 +1293,7 @@ func TestModelAsTPAttribute(t *testing.T) { Value: "Al1", }, }, - Weight: 20, + Weights: 20, } expected2 := &utils.TPAttributeProfile{ TPid: "TP1", @@ -1307,7 +1307,7 @@ func TestModelAsTPAttribute(t *testing.T) { Value: "Al1", }, }, - Weight: 20, + Weights: 20, } rcv := models.AsTPAttributes() sort.Strings(rcv[0].FilterIDs) @@ -3979,7 +3979,7 @@ func TestAPItoAttributeProfileError1(t *testing.T) { Value: "Al1", }, }, - Weight: 20, + Weights: 20, } _, err := APItoAttributeProfile(tpAlsPrf, "UTC") @@ -4001,7 +4001,7 @@ func TestAPItoAttributeProfileError2(t *testing.T) { Value: "\"constant;`>;q=0.7;expires=3600constant\"", }, }, - Weight: 20, + Weights: 20, } _, err := APItoAttributeProfile(tpAlsPrf, "UTC") diff --git a/engine/models.go b/engine/models.go index 52ca36c1c..2f96680f4 100644 --- a/engine/models.go +++ b/engine/models.go @@ -221,7 +221,7 @@ type AttributeMdl struct { Tenant string `index:"0" re:""` ID string `index:"1" re:""` FilterIDs string `index:"2" re:""` - Weight float64 `index:"3" re:"\d+\.?\d*"` + Weights string `index:"3" re:"\d+\.?\d*"` AttributeFilterIDs string `index:"4" re:""` Path string `index:"5" re:""` Type string `index:"6" re:""` diff --git a/migrator/attributes.go b/migrator/attributes.go index a132ba4a4..f16fca9e8 100644 --- a/migrator/attributes.go +++ b/migrator/attributes.go @@ -535,10 +535,11 @@ func (v6AttrPrf v6AttributeProfile) AsAttributeProfile() (attrPrf *engine.Attrib Tenant: v6AttrPrf.Tenant, ID: v6AttrPrf.ID, FilterIDs: v6AttrPrf.FilterIDs, - Weight: v6AttrPrf.Weight, Blocker: v6AttrPrf.Blocker, Attributes: make([]*engine.Attribute, len(v6AttrPrf.Attributes)), } + attrPrf.Weights = make(utils.DynamicWeights, 1) + attrPrf.Weights[0].Weight = v6AttrPrf.Weight if strings.HasSuffix(fltr, utils.PipeSep) { attrPrf.FilterIDs = append(attrPrf.FilterIDs, strings.TrimSuffix(fltr, utils.PipeSep)) } diff --git a/utils/apitpdata.go b/utils/apitpdata.go index fe264769e..9a112c224 100644 --- a/utils/apitpdata.go +++ b/utils/apitpdata.go @@ -314,7 +314,7 @@ type TPAttributeProfile struct { FilterIDs []string Attributes []*TPAttribute Blocker bool - Weight float64 + Weights string } // TPChargerProfile is used in APIs to manage remotely offline ChargerProfile