Add StartTime, IntervalStart and Usage as opts for rates

This commit is contained in:
ionutboangiu
2021-09-20 16:31:44 +03:00
committed by Dan Christian Bogos
parent a897848d7b
commit 0d5945cf12
10 changed files with 122 additions and 45 deletions

View File

@@ -1108,7 +1108,10 @@ const CGRATES_CFG_JSON = `
"rate_nested_fields": false, // determines which field is checked when matching indexed filters(true: all; false: only the one on the first level)
"verbosity": 1000, // number of increment iterations allowed
"opts":{ //
// "*rateProfileIDs": [], //
// "*rateProfileIDs": [], //
"*startTime": "*now", //
"*usage": "1m", //
"*intervalStart": "0", //
},
},

View File

@@ -1970,7 +1970,11 @@ func TestDfRateSJsonCfg(t *testing.T) {
Rate_suffix_indexed_fields: &[]string{},
Rate_nested_fields: utils.BoolPointer(false),
Verbosity: utils.IntPointer(1000),
Opts: &RatesOptsJson{},
Opts: &RatesOptsJson{
StartTime: utils.StringPointer(utils.MetaNow),
Usage: utils.StringPointer("1m"),
IntervalStart: utils.StringPointer("0"),
},
}
dfCgrJSONCfg, err := NewCgrJsonCfgFromBytes([]byte(CGRATES_CFG_JSON))
if err != nil {

File diff suppressed because one or more lines are too long

View File

@@ -25,6 +25,9 @@ import (
type RatesOpts struct {
RateProfileIDs []string
StartTime string
Usage string
IntervalStart string
}
// RateSCfg the rates config section
@@ -51,6 +54,15 @@ func (rateOpts *RatesOpts) loadFromJSONCfg(jsnCfg *RatesOptsJson) (err error) {
if jsnCfg.RateProfileIDs != nil {
rateOpts.RateProfileIDs = *jsnCfg.RateProfileIDs
}
if jsnCfg.StartTime != nil {
rateOpts.StartTime = *jsnCfg.StartTime
}
if jsnCfg.Usage != nil {
rateOpts.Usage = *jsnCfg.Usage
}
if jsnCfg.IntervalStart != nil {
rateOpts.IntervalStart = *jsnCfg.IntervalStart
}
return nil
}
@@ -115,6 +127,9 @@ func (rCfg *RateSCfg) loadFromJSONCfg(jsnCfg *RateSJsonCfg) (err error) {
func (rCfg RateSCfg) AsMapInterface(string) interface{} {
opts := map[string]interface{}{
utils.MetaRateProfileIDsCfg: rCfg.Opts.RateProfileIDs,
utils.MetaStartTime: rCfg.Opts.StartTime,
utils.MetaUsage: rCfg.Opts.Usage,
utils.MetaIntervalStartCfg: rCfg.Opts.IntervalStart,
}
mp := map[string]interface{}{
utils.EnabledCfg: rCfg.Enabled,
@@ -153,6 +168,9 @@ func (rateOpts *RatesOpts) Clone() *RatesOpts {
}
return &RatesOpts{
RateProfileIDs: rtIDs,
StartTime: rateOpts.StartTime,
Usage: rateOpts.Usage,
IntervalStart: rateOpts.IntervalStart,
}
}
func (RateSCfg) SName() string { return RateSJSON }
@@ -193,6 +211,9 @@ func (rCfg RateSCfg) Clone() (cln *RateSCfg) {
type RatesOptsJson struct {
RateProfileIDs *[]string `json:"*rateProfileIDs"`
StartTime *string `json:"*startTime"`
Usage *string `json:"*usage"`
IntervalStart *string `json:"*intervalStart"`
}
type RateSJsonCfg struct {
@@ -218,6 +239,15 @@ func diffRatesOptsJsonCfg(d *RatesOptsJson, v1, v2 *RatesOpts) *RatesOptsJson {
if !utils.SliceStringEqual(v1.RateProfileIDs, v2.RateProfileIDs) {
d.RateProfileIDs = utils.SliceStringPointer(v2.RateProfileIDs)
}
if v1.StartTime != v2.StartTime {
d.StartTime = utils.StringPointer(v2.StartTime)
}
if v1.Usage != v2.Usage {
d.Usage = utils.StringPointer(v2.Usage)
}
if v1.IntervalStart != v2.IntervalStart {
d.IntervalStart = utils.StringPointer(v2.IntervalStart)
}
return d
}

View File

@@ -53,7 +53,11 @@ func TestRateSConfigloadFromJsonCfg(t *testing.T) {
RateSuffixIndexedFields: &[]string{"*req.index1"},
RateNestedFields: true,
Verbosity: 20,
Opts: &RatesOpts{},
Opts: &RatesOpts{
StartTime: utils.MetaNow,
Usage: "1m",
IntervalStart: "0",
},
}
jsonCfg := NewDefaultCGRConfig()
if err = jsonCfg.rateSCfg.loadFromJSONCfg(cfgJSON); err != nil {
@@ -80,6 +84,9 @@ func TestRatesCfgAsMapInterface(t *testing.T) {
utils.Verbosity: 1000,
utils.OptsCfg: map[string]interface{}{
utils.MetaRateProfileIDsCfg: []string(nil),
utils.MetaStartTime: utils.MetaNow,
utils.MetaUsage: "1m",
utils.MetaIntervalStartCfg: "0",
},
}
if cgrCfg, err := NewCGRConfigFromJSONStringWithDefaults(cfgJSONStr); err != nil {
@@ -120,6 +127,9 @@ func TestRatesCfgAsMapInterface1(t *testing.T) {
utils.Verbosity: 1000,
utils.OptsCfg: map[string]interface{}{
utils.MetaRateProfileIDsCfg: []string(nil),
utils.MetaStartTime: utils.MetaNow,
utils.MetaUsage: "1m",
utils.MetaIntervalStartCfg: "0",
},
}
if cgrCfg, err := NewCGRConfigFromJSONStringWithDefaults(cfgJSONStr); err != nil {

View File

@@ -168,11 +168,11 @@ func (rS *RateS) rateProfileCostForEvent(ctx *context.Context, rtPfl *utils.Rate
}
}
var sTime time.Time
if sTime, err = args.StartTime(rS.cfg.GeneralCfg().DefaultTimezone); err != nil {
if sTime, err = args.StartTime(rS.cfg.RateSCfg().Opts.StartTime, rS.cfg.GeneralCfg().DefaultTimezone); err != nil {
return
}
var usage *decimal.Big
if usage, err = args.Usage(); err != nil {
if usage, err = args.Usage(rS.cfg.RateSCfg().Opts.Usage); err != nil {
return
}
var ordRts []*orderedRate
@@ -190,7 +190,7 @@ func (rS *RateS) rateProfileCostForEvent(ctx *context.Context, rtPfl *utils.Rate
rpCost.MaxCost = rtPfl.MaxCost
}
var ivalStart *decimal.Big
if ivalStart, err = args.IntervalStart(); err != nil {
if ivalStart, err = args.IntervalStart(rS.cfg.RateSCfg().Opts.IntervalStart); err != nil {
return
}
var costIntervals []*utils.RateSInterval

View File

@@ -983,18 +983,18 @@ type ArgExportCDRs struct {
}
// StartTime returns the event time used to check active rate profiles
func (args *CGREvent) StartTime(tmz string) (sTime time.Time, err error) {
func (args *CGREvent) StartTime(configSTime, tmz string) (time.Time, error) {
if tIface, has := args.APIOpts[OptsRatesStartTime]; has {
return IfaceAsTime(tIface, tmz)
}
if tIface, has := args.APIOpts[MetaStartTime]; has {
return IfaceAsTime(tIface, tmz)
}
return time.Now(), nil
return ParseTimeDetectLayout(configSTime, tmz)
}
// usage returns the event time used to check active rate profiles
func (args *CGREvent) Usage() (usage *decimal.Big, err error) {
func (args *CGREvent) Usage(configUsage string) (usage *decimal.Big, err error) {
// first search for the rateUsage in opts
if uIface, has := args.APIOpts[OptsRatesUsage]; has {
return IfaceAsBig(uIface)
@@ -1004,15 +1004,15 @@ func (args *CGREvent) Usage() (usage *decimal.Big, err error) {
return IfaceAsBig(uIface)
}
// if the usage is not found in the event populate with default value and overwrite the NOT_FOUND error with nil
return decimal.New(int64(time.Minute), 0), nil
return StringAsBig(configUsage)
}
// IntervalStart returns the inerval start out of APIOpts received for the event
func (args *CGREvent) IntervalStart() (ivlStart *decimal.Big, err error) {
func (args *CGREvent) IntervalStart(configIvlStart string) (ivlStart *decimal.Big, err error) {
if iface, has := args.APIOpts[OptsRatesIntervalStart]; has {
return IfaceAsBig(iface)
}
return decimal.New(0, 0), nil
return StringAsBig(configIvlStart)
}
type TPActionProfile struct {

View File

@@ -563,7 +563,7 @@ func TestStartTimeNow(t *testing.T) {
},
}
timpulet1 := time.Now()
result, err := ev.StartTime("")
result, err := ev.StartTime(MetaNow, "")
timpulet2 := time.Now()
if err != nil {
t.Errorf("Expected <nil> , received <%+v>", err)
@@ -583,7 +583,21 @@ func TestStartTime(t *testing.T) {
OptsRatesRateProfileIDs: []string{"123", "456", "789"},
},
}
if result, err := ev.StartTime(""); err != nil {
if result, err := ev.StartTime(MetaNow, ""); err != nil {
t.Errorf("Expected <nil> , received <%+v>", err)
} else if !reflect.DeepEqual(result.String(), "2018-01-07 17:00:10 +0000 UTC") {
t.Errorf("Expected <2018-01-07 17:00:10 +0000 UTC> , received <%+v>", result)
}
}
func TestStartTime2(t *testing.T) {
ev := &CGREvent{
Tenant: "cgrates.org",
ID: "TestEvent",
Event: map[string]interface{}{},
APIOpts: map[string]interface{}{},
}
if result, err := ev.StartTime("2018-01-07T17:00:10Z", ""); err != nil {
t.Errorf("Expected <nil> , received <%+v>", err)
} else if !reflect.DeepEqual(result.String(), "2018-01-07 17:00:10 +0000 UTC") {
t.Errorf("Expected <2018-01-07 17:00:10 +0000 UTC> , received <%+v>", result)
@@ -600,7 +614,7 @@ func TestStartTimeError(t *testing.T) {
OptsRatesRateProfileIDs: []string{"123", "456", "789"},
},
}
_, err := ev.StartTime("")
_, err := ev.StartTime(MetaNow, "")
if err == nil && err.Error() != "received <Unsupported time format" {
t.Errorf("Expected <nil> , received <%+v>", err)
}
@@ -615,7 +629,7 @@ func TestUsageMinute(t *testing.T) {
OptsRatesRateProfileIDs: []string{"123", "456", "789"},
},
}
if rcv, err := ev.Usage(); err != nil {
if rcv, err := ev.Usage("60s"); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(decimal.New(int64(time.Minute), 0), rcv) {
t.Errorf("Expected %+v, received %+v", decimal.New(int64(time.Minute), 0), rcv)
@@ -632,7 +646,7 @@ func TestUsageError(t *testing.T) {
OptsRatesRateProfileIDs: []string{"123", "456", "789"},
},
}
_, err := ev.Usage()
_, err := ev.Usage("1m")
if err == nil && err.Error() != "received <Unsupported time format" {
t.Errorf("Expected <nil> , received <%+v>", err)
}
@@ -649,7 +663,7 @@ func TestUsage(t *testing.T) {
},
}
if result, err := ev.Usage(); err != nil {
if result, err := ev.Usage("1m"); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(result.String(), "130000000000") {
t.Errorf("Expected <130000000000> , received <%+v>", result.String())
@@ -742,7 +756,7 @@ func TestATDUsage(t *testing.T) {
},
}
_, err := ev.Usage()
_, err := ev.Usage("1m")
expected := "cannot convert field: bool to decimal.Big"
if err == nil || err.Error() != expected {
t.Errorf("\nExpected: <%+v>, \nReceived: <%+v>", expected, err.Error())
@@ -772,7 +786,7 @@ func TestIntervalStart(t *testing.T) {
OptsRatesRateProfileIDs: []string{"RP_1001"},
},
}
rcv, err := args.IntervalStart()
rcv, err := args.IntervalStart("0")
exp := new(decimal.Big).SetUint64(1)
if err != nil {
t.Error(err)
@@ -787,7 +801,7 @@ func TestIntervalStartDefault(t *testing.T) {
OptsRatesRateProfileIDs: []string{"RP_1001"},
},
}
rcv, err := args.IntervalStart()
rcv, err := args.IntervalStart("0")
exp := new(decimal.Big).SetUint64(0)
if err != nil {
t.Error(err)

View File

@@ -1923,6 +1923,7 @@ const (
// RateSCfg
MetaRateProfileIDsCfg = "*rateProfileIDs"
MetaIntervalStartCfg = "*intervalStart"
RateIndexedSelectsCfg = "rate_indexed_selects"
RateNestedFieldsCfg = "rate_nested_fields"
RateStringIndexedFieldsCfg = "rate_string_indexed_fields"

View File

@@ -72,6 +72,29 @@ func IfaceAsTime(itm interface{}, timezone string) (t time.Time, err error) {
return
}
func StringAsBig(itm string) (b *decimal.Big, err error) {
if strings.HasSuffix(itm, NsSuffix) ||
strings.HasSuffix(itm, UsSuffix) ||
strings.HasSuffix(itm, µSuffix) ||
strings.HasSuffix(itm, MsSuffix) ||
strings.HasSuffix(itm, SSuffix) ||
strings.HasSuffix(itm, MSuffix) ||
strings.HasSuffix(itm, HSuffix) {
var tm time.Duration
if tm, err = time.ParseDuration(itm); err != nil {
return
}
return decimal.New(int64(tm), 0), nil
}
z, ok := new(decimal.Big).SetString(itm)
// verify ok and check if the value was converted successfuly
// and the big is a valid number
if !ok || z.IsNaN(0) {
return nil, fmt.Errorf("can't convert <%+v> to decimal", itm)
}
return z, nil
}
func IfaceAsBig(itm interface{}) (b *decimal.Big, err error) {
switch it := itm.(type) {
case time.Duration:
@@ -101,26 +124,7 @@ func IfaceAsBig(itm interface{}) (b *decimal.Big, err error) {
case float64: // automatically hitting here also ints
return new(decimal.Big).SetFloat64(it), nil
case string:
if strings.HasSuffix(it, NsSuffix) ||
strings.HasSuffix(it, UsSuffix) ||
strings.HasSuffix(it, µSuffix) ||
strings.HasSuffix(it, MsSuffix) ||
strings.HasSuffix(it, SSuffix) ||
strings.HasSuffix(it, MSuffix) ||
strings.HasSuffix(it, HSuffix) {
var tm time.Duration
if tm, err = time.ParseDuration(it); err != nil {
return
}
return decimal.New(int64(tm), 0), nil
}
z, ok := new(decimal.Big).SetString(it)
// verify ok and check if the value was converted successfuly
// and the big is a valid number
if !ok || z.IsNaN(0) {
return nil, fmt.Errorf("can't convert <%+v> to decimal", it)
}
return z, nil
return StringAsBig(it)
case *Decimal:
return it.Big, nil
case *decimal.Big: