diff --git a/engine/filters.go b/engine/filters.go index f459be631..b5f24c727 100644 --- a/engine/filters.go +++ b/engine/filters.go @@ -27,24 +27,42 @@ import ( ) const ( - MetaString = "*string" - MetaStringPrefix = "*string_prefix" - MetaTimings = "*timings" - MetaRSRFields = "*rsr_fields" - MetaStatS = "*stats" - MetaDestinations = "*destinations" - MetaMinCapPrefix = "*min_" - MetaMaxCapPrefix = "*max_" + MetaString = "*string" + MetaStringPrefix = "*string_prefix" + MetaTimings = "*timings" + MetaRSRFields = "*rsr_fields" + MetaStatS = "*stats" + MetaDestinations = "*destinations" + MetaMinCapPrefix = "*min_" + MetaMaxCapPrefix = "*max_" + MetaLessThan = "*lt" + MetaLessOrEqual = "*lte" + MetaGreaterThan = "*gt" + MetaGreaterOrEqual = "*gte" ) +type Filter struct { + Tenant string + ID string + RequestFilters []*RequestFilter + ActivationInterval *utils.ActivationInterval +} + +func (flt *Filter) TenantID() string { + return utils.ConcatenatedKey(flt.Tenant, flt.ID) +} + func NewRequestFilter(rfType, fieldName string, vals []string) (*RequestFilter, error) { - if !utils.IsSliceMember([]string{MetaStringPrefix, MetaTimings, MetaRSRFields, MetaStatS, MetaDestinations}, rfType) { + if !utils.IsSliceMember([]string{MetaStringPrefix, MetaTimings, MetaRSRFields, MetaStatS, MetaDestinations, + MetaLessThan, MetaLessOrEqual, MetaGreaterThan, MetaGreaterOrEqual}, rfType) { return nil, fmt.Errorf("Unsupported filter Type: %s", rfType) } - if fieldName == "" && utils.IsSliceMember([]string{MetaStringPrefix, MetaTimings, MetaDestinations}, rfType) { + if fieldName == "" && utils.IsSliceMember([]string{MetaStringPrefix, MetaTimings, MetaDestinations, + MetaLessThan, MetaLessOrEqual, MetaGreaterThan, MetaGreaterOrEqual}, rfType) { return nil, fmt.Errorf("FieldName is mandatory for Type: %s", rfType) } - if len(vals) == 0 && utils.IsSliceMember([]string{MetaStringPrefix, MetaTimings, MetaRSRFields, MetaDestinations, MetaDestinations}, rfType) { + if len(vals) == 0 && utils.IsSliceMember([]string{MetaStringPrefix, MetaTimings, MetaRSRFields, + MetaDestinations, MetaDestinations, MetaLessThan, MetaLessOrEqual, MetaGreaterThan, MetaGreaterOrEqual}, rfType) { return nil, fmt.Errorf("Values is mandatory for Type: %s", rfType) } rf := &RequestFilter{Type: rfType, FieldName: fieldName, Values: vals} @@ -63,7 +81,7 @@ type RFStatSThreshold struct { // RequestFilter filters requests coming into various places // Pass rule: default negative, one mathing rule should pass the filter type RequestFilter struct { - Type string // Filter type (*string, *timing, *rsr_filters, *cdr_stats) + Type string // Filter type (*string, *timing, *rsr_filters, *stats, *lt, *lte, *gt, *gte) FieldName string // Name of the field providing us the Values to check (used in case of some ) Values []string // Filter definition ActivationInterval *utils.ActivationInterval @@ -71,17 +89,6 @@ type RequestFilter struct { statSThresholds []*RFStatSThreshold // Cached compiled RFStatsThreshold out of Values } -type Filter struct { - Tenant string - ID string - RequestFilters []*RequestFilter - ActivationInterval *utils.ActivationInterval -} - -func (flt *Filter) TenantID() string { - return utils.ConcatenatedKey(flt.Tenant, flt.ID) -} - // Separate method to compile RSR fields func (rf *RequestFilter) CompileValues() (err error) { if rf.Type == MetaRSRFields { @@ -127,6 +134,8 @@ func (fltr *RequestFilter) Pass(req interface{}, extraFieldsLabel string, rpcCln return fltr.passRSRFields(req, extraFieldsLabel) case MetaStatS: return fltr.passStatS(req, extraFieldsLabel, rpcClnt) + case MetaLessThan, MetaLessOrEqual, MetaGreaterThan, MetaGreaterOrEqual: + return fltr.passGreaterThan(req, extraFieldsLabel) default: return false, utils.ErrNotImplemented } @@ -228,3 +237,29 @@ func (fltr *RequestFilter) passStatS(req interface{}, extraFieldsLabel string, s } return false, nil } + +func (fltr *RequestFilter) passGreaterThan(req interface{}, extraFieldsLabel string) (bool, error) { + fldIf, err := utils.ReflectFieldInterface(req, fltr.FieldName, extraFieldsLabel) + if err != nil { + if err == utils.ErrNotFound { + return false, nil + } + return false, err + } + for _, val := range fltr.Values { + ifVal := utils.StringToInterface(val) + orEqual := false + if fltr.Type == MetaGreaterOrEqual || + fltr.Type == MetaLessThan { + orEqual = true + } + if gte, err := utils.GreaterThan(fldIf, ifVal, orEqual); err != nil { + return false, err + } else if utils.IsSliceMember([]string{MetaGreaterThan, MetaGreaterOrEqual}, fltr.Type) && gte { + return true, nil + } else if !gte && utils.IsSliceMember([]string{MetaLessThan, MetaLessOrEqual}, fltr.Type) && !gte { + return true, nil + } + } + return false, nil +} diff --git a/engine/filter_test.go b/engine/filters_test.go similarity index 79% rename from engine/filter_test.go rename to engine/filters_test.go index 1c8fcd969..10b385679 100644 --- a/engine/filter_test.go +++ b/engine/filters_test.go @@ -139,3 +139,64 @@ func TestReqFilterPassDestinations(t *testing.T) { t.Error("Passing") } } + +func TestReqFilterPassGreaterThan(t *testing.T) { + rf, err := NewRequestFilter(MetaLessThan, "ASR", []string{"40"}) + if err != nil { + t.Error(err) + } + ev := map[string]interface{}{ + "ASR": 20, + } + if passes, err := rf.passGreaterThan(ev, ""); err != nil { + t.Error(err) + } else if !passes { + t.Error("not passing") + } + ev = map[string]interface{}{ + "ASR": 40, + } + if passes, err := rf.passGreaterThan(ev, ""); err != nil { + t.Error(err) + } else if passes { + t.Error("equal should not be passing") + } + rf, err = NewRequestFilter(MetaLessOrEqual, "ASR", []string{"40"}) + if err != nil { + t.Error(err) + } + if passes, err := rf.passGreaterThan(ev, ""); err != nil { + t.Error(err) + } else if !passes { + t.Error("not passing") + } + rf, err = NewRequestFilter(MetaGreaterOrEqual, "ASR", []string{"40"}) + if err != nil { + t.Error(err) + } + if passes, err := rf.passGreaterThan(ev, ""); err != nil { + t.Error(err) + } else if !passes { + t.Error("not passing") + } + ev = map[string]interface{}{ + "ASR": 20, + } + if passes, err := rf.passGreaterThan(ev, ""); err != nil { + t.Error(err) + } else if passes { + t.Error("should not pass") + } + rf, err = NewRequestFilter(MetaGreaterOrEqual, "ACD", []string{"1m50s"}) + if err != nil { + t.Error(err) + } + ev = map[string]interface{}{ + "ACD": time.Duration(2 * time.Minute), + } + if passes, err := rf.passGreaterThan(ev, ""); err != nil { + t.Error(err) + } else if !passes { + t.Error("not pass") + } +} diff --git a/utils/reflect.go b/utils/reflect.go index ec67d4d33..38488bb5f 100644 --- a/utils/reflect.go +++ b/utils/reflect.go @@ -161,28 +161,45 @@ func AsMapStringIface(item interface{}) (map[string]interface{}, error) { // GreaterThan attempts to compare two items // returns the result or error if not comparable func GreaterThan(item, oItem interface{}, orEqual bool) (gte bool, err error) { + valItm := reflect.ValueOf(item) + valOtItm := reflect.ValueOf(oItem) + // convert to wider type so we can be compatible with StringToInterface function + switch valItm.Kind() { + case reflect.Float32: + item = valItm.Float() + valItm = reflect.ValueOf(item) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32: + item = valItm.Int() + valItm = reflect.ValueOf(item) + } + switch valOtItm.Kind() { + case reflect.Float32: + oItem = valOtItm.Float() + valOtItm = reflect.ValueOf(oItem) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32: + oItem = valOtItm.Int() + valOtItm = reflect.ValueOf(oItem) + } typItem := reflect.TypeOf(item) typOItem := reflect.TypeOf(oItem) - fmt.Println(typItem.Comparable(), - typOItem.Comparable(), - typItem, - typOItem, - typItem == typOItem) if !typItem.Comparable() || !typOItem.Comparable() || typItem != typOItem { return false, errors.New("incomparable") } - if orEqual && reflect.DeepEqual(item, oItem) { - return true, nil - } - valItm := reflect.ValueOf(item) - valOItm := reflect.ValueOf(oItem) switch typItem.Kind() { case reflect.Float32, reflect.Float64: - gte = valItm.Float() > valOItm.Float() + if orEqual { + gte = valItm.Float() >= valOtItm.Float() + } else { + gte = valItm.Float() > valOtItm.Float() + } case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - gte = valItm.Int() > valOItm.Int() + if orEqual { + gte = valItm.Int() >= valOtItm.Int() + } else { + gte = valItm.Int() > valOtItm.Int() + } default: // unsupported comparison err = fmt.Errorf("unsupported type: %v", typItem) }