diff --git a/apier/v1/reslimiter_it_test.go b/apier/v1/reslimiter_it_test.go new file mode 100644 index 000000000..e1d700c7a --- /dev/null +++ b/apier/v1/reslimiter_it_test.go @@ -0,0 +1,229 @@ +// +build integration + +/* +Real-time Online/Offline Charging System (OCS) for Telecom & ISP environments +Copyright (C) ITsysCOM GmbH + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see +*/ +package v1 + +import ( + "net/rpc" + "net/rpc/jsonrpc" + "path" + "testing" + "time" + + "github.com/cgrates/cgrates/config" + "github.com/cgrates/cgrates/engine" + "github.com/cgrates/cgrates/utils" +) + +var ( + rlsV1CfgPath string + rlsV1Cfg *config.CGRConfig + rlsV1Rpc *rpc.Client +) + +func TestRLsV1LoadConfig(t *testing.T) { + var err error + rlsV1CfgPath = path.Join(*dataDir, "conf", "samples", "reslimiter") + if rlsV1Cfg, err = config.NewCGRConfigFromFolder(rlsV1CfgPath); err != nil { + t.Error(err) + } +} + +func TestRLsV1InitDataDb(t *testing.T) { + if err := engine.InitDataDb(rlsV1Cfg); err != nil { + t.Fatal(err) + } +} + +func TestRLsV1StartEngine(t *testing.T) { + if _, err := engine.StopStartEngine(rlsV1CfgPath, 1000); err != nil { + t.Fatal(err) + } +} + +func TestRLsV1RpcConn(t *testing.T) { + var err error + rlsV1Rpc, err = jsonrpc.Dial("tcp", rlsV1Cfg.RPCJSONListen) // We connect over JSON so we can also troubleshoot if needed + if err != nil { + t.Fatal("Could not connect to rater: ", err.Error()) + } +} + +func TestRLsV1TPFromFolder(t *testing.T) { + var reply string + time.Sleep(time.Duration(2000) * time.Millisecond) + attrs := &utils.AttrLoadTpFromFolder{FolderPath: path.Join(*dataDir, "tariffplans", "tutorial")} + if err := rlsV1Rpc.Call("ApierV1.LoadTariffPlanFromFolder", attrs, &reply); err != nil { + t.Error(err) + } + time.Sleep(time.Duration(1000) * time.Millisecond) +} + +func TestRLsV1GetLimitsForEvent(t *testing.T) { + var reply *[]*engine.ResourceLimit + + ev := map[string]interface{}{"Unknown": "unknown"} + if err := rlsV1Rpc.Call("RLsV1.GetLimitsForEvent", ev, &reply); err != nil { + t.Error(err) + } + if len(*reply) != 0 { + t.Errorf("Expecting: %+v, received: %+v", 0, len(*reply)) + } + + ev = map[string]interface{}{"Destination": "10"} + if err := rlsV1Rpc.Call("RLsV1.GetLimitsForEvent", ev, &reply); err != nil { + t.Error(err) + } + if len(*reply) != 1 { + t.Errorf("Expecting: %+v, received: %+v", 1, len(*reply)) + } + if (*reply)[0].ID != "ResGroup2" { + t.Errorf("Expecting: %+v, received: %+v", "ResGroup2", (*reply)[0].ID) + } + + ev = map[string]interface{}{"Destination": "20"} + if err := rlsV1Rpc.Call("RLsV1.GetLimitsForEvent", ev, &reply); err != nil { + t.Error(err) + } + if len(*reply) != 0 { + t.Errorf("Expecting: %+v, received: %+v", 0, len(*reply)) + } + + ev = map[string]interface{}{"Account": "1002", "Subject": "test", "Destination": "1002"} + if err := rlsV1Rpc.Call("RLsV1.GetLimitsForEvent", ev, &reply); err != nil { + t.Error(err) + } + if len(*reply) != 2 { + t.Errorf("Expecting: %+v, received: %+v", 2, len(*reply)) + } + + ev = map[string]interface{}{"Account": "1002", "Subject": "test", "Destination": "1001"} + if err := rlsV1Rpc.Call("RLsV1.GetLimitsForEvent", ev, &reply); err != nil { + t.Error(err) + } + if len(*reply) != 1 { + t.Errorf("Expecting: %+v, received: %+v", 1, len(*reply)) + } + if (*reply)[0].ID != "ResGroup2" { + t.Errorf("Expecting: %+v, received: %+v", "ResGroup2", (*reply)[0].ID) + } +} + +func TestRLsV1AllocateResource(t *testing.T) { + var reply string + + attrRU := utils.AttrRLsResourceUsage{ + UsageID: "651a8db2-4f67-4cf8-b622-169e8a482e51", + Event: map[string]interface{}{"Account": "1002", "Subject": "1001", "Destination": "1002"}, + Units: 3, + } + if err := rlsV1Rpc.Call("RLsV1.AllocateResource", attrRU, &reply); err != nil { + t.Error(err) + } + if reply != "ResGroup1" { + t.Errorf("Expecting: %+v, received: %+v", "ResGroup1", reply) + } + + time.Sleep(time.Duration(1000) * time.Millisecond) + + attrRU = utils.AttrRLsResourceUsage{ + UsageID: "651a8db2-4f67-4cf8-b622-169e8a482e52", + Event: map[string]interface{}{"Destination": "100"}, + Units: 5, + } + if err := rlsV1Rpc.Call("RLsV1.AllocateResource", attrRU, &reply); err != nil { + t.Error(err) + } + if reply != "ResGroup2" { + t.Errorf("Expecting: %+v, received: %+v", "ResGroup2", reply) + } + + time.Sleep(time.Duration(1000) * time.Millisecond) + + attrRU = utils.AttrRLsResourceUsage{ + UsageID: "651a8db2-4f67-4cf8-b622-169e8a482e53", + Event: map[string]interface{}{"Account": "1002", "Subject": "1001", "Destination": "1002"}, + Units: 3, + } + if err := rlsV1Rpc.Call("RLsV1.AllocateResource", attrRU, &reply); err != nil { + t.Error(err) + } + if reply != "ResGroup1" { + t.Errorf("Expecting: %+v, received: %+v", "ResGroup1", reply) + } + +} + +func TestRLsV1AllowUsage(t *testing.T) { + var reply bool + attrRU := utils.AttrRLsResourceUsage{ + UsageID: "651a8db2-4f67-4cf8-b622-169e8a482e51", + Event: map[string]interface{}{"Account": "1002", "Subject": "1001", "Destination": "1002"}, + Units: 1, + } + if err := rlsV1Rpc.Call("RLsV1.AllowUsage", attrRU, &reply); err != nil { + t.Error(err) + } else { + if reply != true { + t.Errorf("Expecting: %+v, received: %+v", true, reply) + } + } + + attrRU = utils.AttrRLsResourceUsage{ + UsageID: "651a8db2-4f67-4cf8-b622-169e8a482e51", + Event: map[string]interface{}{"Account": "1002", "Subject": "1001", "Destination": "1002"}, + Units: 2, + } + if err := rlsV1Rpc.Call("RLsV1.AllowUsage", attrRU, &reply); err != nil { + t.Error(err) + } +} + +func TestRLsV1ReleaseResource(t *testing.T) { + var reply interface{} + + attrRU := utils.AttrRLsResourceUsage{ + UsageID: "651a8db2-4f67-4cf8-b622-169e8a482e52", + Event: map[string]interface{}{"Destination": "100"}, + Units: 2, + } + if err := rlsV1Rpc.Call("RLsV1.ReleaseResource", attrRU, &reply); err != nil { + t.Error(err) + } + + if err := rlsV1Rpc.Call("RLsV1.AllowUsage", attrRU, &reply); err != nil { + t.Error(err) + } else { + if reply != true { + t.Errorf("Expecting: %+v, received: %+v", true, reply) + } + } + + attrRU.Units += 1 + if err := rlsV1Rpc.Call("RLsV1.AllowUsage", attrRU, &reply); err == nil { + t.Errorf("Expecting: %+v, received: %+v", false, reply) + } + +} + +func TestRLsV1StopEngine(t *testing.T) { + if err := engine.KillEngine(100); err != nil { + t.Error(err) + } +} diff --git a/data/conf/samples/reslimiter/cgrates.json b/data/conf/samples/reslimiter/cgrates.json new file mode 100644 index 000000000..769d7bb18 --- /dev/null +++ b/data/conf/samples/reslimiter/cgrates.json @@ -0,0 +1,40 @@ +{ + +"general": { + "log_level": 7, +}, + +"listen": { + "rpc_json": ":2012", + "rpc_gob": ":2013", + "http": ":2080", +}, + + +"stor_db": { + "db_password": "CGRateS.org", +}, + + +"rals": { + "enabled": true, + "cdrstats_conns": [ + {"address": "*internal"} + ], +}, + +"cdrstats": { + "enabled": true, +}, + + +"rls": { + "enabled": true, + "cdrstats_conns": [ + {"address": "*internal"} + ], + "cache_dump_interval": "0s", + "usage_ttl": "3h", +}, + +} diff --git a/data/tariffplans/tutorial/ResourceLimits.csv b/data/tariffplans/tutorial/ResourceLimits.csv index 53dbb60b8..2bcef8a58 100644 --- a/data/tariffplans/tutorial/ResourceLimits.csv +++ b/data/tariffplans/tutorial/ResourceLimits.csv @@ -1,6 +1,6 @@ #Id,FilterType,FilterFieldName,FilterFieldValues,ActivationInterval,TTL,Limit,AllocationReply,Weight,ActionTriggers -ResGroup1,*string,Account,1001;1002,2014-07-29T15:00:00Z,1s,2,,10, +ResGroup1,*string,Account,1001;1002,2014-07-29T15:00:00Z,1s,7,,20, ResGroup1,*string_prefix,Destination,10;20,,,,,, ResGroup1,*rsr_fields,,Subject(~^1.*1$);Destination(1002),,,,,, -ResGroup2,*destinations,Destination,DST_FS,2014-07-29T15:00:00Z,3600s,2,SPECIAL_1002,10, +ResGroup2,*destinations,Destination,DST_FS,2014-07-29T15:00:00Z,3600s,8,SPECIAL_1002,10, ResGroup2,*cdr_stats,,CDRST1:*min_ASR:34;CDRST_1001:*min_ASR:20,,,,,, diff --git a/engine/reslimiter.go b/engine/reslimiter.go index 7c5d8b115..01d008581 100644 --- a/engine/reslimiter.go +++ b/engine/reslimiter.go @@ -286,7 +286,7 @@ func (rls *ResourceLimiterService) V1AllowUsage(args utils.AttrRLsResourceUsage, return utils.NewErrServerError(err) } if _, err = mtcRLs.AllocateResource(&ResourceUsage{ID: args.UsageID, - Time: time.Now(), Units: args.Units}, false); err != nil { + Time: time.Now(), Units: args.Units}, true); err != nil { if err == utils.ErrResourceUnavailable { return // not error but still not allowed } diff --git a/engine/reslimiter_test.go b/engine/reslimiter_test.go index dda5e3892..464ca69b5 100644 --- a/engine/reslimiter_test.go +++ b/engine/reslimiter_test.go @@ -18,409 +18,276 @@ along with this program. If not, see package engine import ( - "reflect" "testing" "time" + // "reflect" "github.com/cgrates/cgrates/utils" ) -var rLS *ResourceLimiterService +var ( + rl, rl2 *ResourceLimit + ru, ru2, ru3 *ResourceUsage + rls ResourceLimits +) -/* -func TestRLsIndexStringFilters(t *testing.T) { - rls := []*ResourceLimit{ - &ResourceLimit{ - ID: "RL1", - Weight: 20, - Filters: []*RequestFilter{ - &RequestFilter{Type: MetaString, FieldName: "Account", Values: []string{"1001", "1002"}}, - &RequestFilter{Type: MetaRSRFields, Values: []string{"Subject(~^1.*1$)", "Destination(1002)"}, - rsrFields: utils.ParseRSRFieldsMustCompile("Subject(~^1.*1$);Destination(1002)", utils.INFIELD_SEP), - }}, - ActivationTime: time.Date(2014, 7, 3, 13, 43, 0, 1, time.UTC), - Limit: 2, - Usage: make(map[string]*ResourceUsage), - }, - &ResourceLimit{ - ID: "RL2", - Weight: 10, - Filters: []*RequestFilter{ - &RequestFilter{Type: MetaString, FieldName: "Account", Values: []string{"dan", "1002"}}, - &RequestFilter{Type: MetaString, FieldName: "Subject", Values: []string{"dan"}}, - }, - ActivationTime: time.Date(2014, 7, 3, 13, 43, 0, 1, time.UTC), - Limit: 1, - UsageTTL: time.Duration(1 * time.Millisecond), - Usage: make(map[string]*ResourceUsage), - }, - &ResourceLimit{ - ID: "RL4", - Weight: 10, - Filters: []*RequestFilter{ - &RequestFilter{Type: MetaStringPrefix, FieldName: "Destination", Values: []string{"+49"}}, - }, - ActivationTime: time.Date(2014, 7, 3, 13, 43, 0, 1, time.UTC), - Limit: 1, - Usage: make(map[string]*ResourceUsage), - }, - &ResourceLimit{ - ID: "RL5", - Weight: 10, - Filters: []*RequestFilter{ - &RequestFilter{Type: MetaStringPrefix, FieldName: "Destination", Values: []string{"+40"}}, - }, - ActivationTime: time.Date(2014, 7, 3, 13, 43, 0, 1, time.UTC), - Limit: 1, - UsageTTL: time.Duration(10 * time.Millisecond), - Usage: make(map[string]*ResourceUsage), - }, +func TestResourceLimitRecordUsage(t *testing.T) { + ru = &ResourceUsage{ + ID: "RU1", + Time: time.Date(2014, 7, 3, 13, 43, 0, 1, time.UTC), + Units: 2, } - for _, rl := range rls { - cache.Set(utils.ResourceLimitsPrefix+rl.ID, rl, true, "") + + ru2 = &ResourceUsage{ + ID: "RU2", + Time: time.Date(2014, 7, 3, 13, 43, 0, 1, time.UTC), + Units: 2, } - rLS = new(ResourceLimiterService) - eIndexes := map[string]map[string]utils.StringMap{ - "Account": map[string]utils.StringMap{ - "1001": utils.StringMap{ - "RL1": true, - }, - "1002": utils.StringMap{ - "RL1": true, - "RL2": true, - }, - "dan": utils.StringMap{ - "RL2": true, - }, - }, - "Subject": map[string]utils.StringMap{ - "dan": utils.StringMap{ - "RL2": true, - }, - }, - utils.NOT_AVAILABLE: map[string]utils.StringMap{ - utils.NOT_AVAILABLE: utils.StringMap{ - "RL4": true, - "RL5": true, - }, - }, - } - if err := rLS.indexStringFilters(nil); err != nil { - t.Error(err) - } else if !reflect.DeepEqual(eIndexes, rLS.stringIndexes) { - t.Errorf("Expecting: %+v, received: %+v", eIndexes, rLS.stringIndexes) - } - rl3 := &ResourceLimit{ - ID: "RL3", - Weight: 10, + + rl = &ResourceLimit{ + ID: "RL1", Filters: []*RequestFilter{ - &RequestFilter{Type: MetaString, FieldName: "Subject", Values: []string{"dan"}}, - &RequestFilter{Type: MetaString, FieldName: "Subject", Values: []string{"1003"}}, - }, - ActivationTime: time.Date(2014, 7, 3, 13, 43, 0, 1, time.UTC), - Limit: 1, - Usage: make(map[string]*ResourceUsage), - } - cache.Set(utils.ResourceLimitsPrefix+rl3.ID, rl3, true, "") - rl6 := &ResourceLimit{ // Add it so we can test expiryTime - ID: "RL6", - Weight: 10, - Filters: []*RequestFilter{ - &RequestFilter{Type: MetaString, FieldName: "Subject", Values: []string{"dan"}}, - }, - ActivationTime: time.Date(2014, 7, 3, 13, 43, 0, 1, time.UTC), - ExpiryTime: time.Date(2014, 7, 3, 13, 43, 0, 1, time.UTC), - Limit: 1, - Usage: make(map[string]*ResourceUsage), - } - cache.Set(utils.ResourceLimitsPrefix+rl6.ID, rl6, true, "") - eIndexes = map[string]map[string]utils.StringMap{ - "Account": map[string]utils.StringMap{ - "1001": utils.StringMap{ - "RL1": true, + &RequestFilter{ + Type: MetaString, + FieldName: "Account", + Values: []string{"1001", "1002"}, }, - "1002": utils.StringMap{ - "RL1": true, - "RL2": true, - }, - "dan": utils.StringMap{ - "RL2": true, + &RequestFilter{ + Type: MetaRSRFields, + Values: []string{"Subject(~^1.*1$)", "Destination(1002)"}, + rsrFields: utils.ParseRSRFieldsMustCompile("Subject(~^1.*1$);Destination(1002)", utils.INFIELD_SEP), }, }, - "Subject": map[string]utils.StringMap{ - "dan": utils.StringMap{ - "RL2": true, - "RL3": true, - "RL6": true, - }, - "1003": utils.StringMap{ - "RL3": true, + ActivationInterval: &utils.ActivationInterval{ + ActivationTime: time.Date(2014, 7, 3, 13, 43, 0, 1, time.UTC), + }, + ExpiryTime: time.Date(2014, 7, 3, 13, 43, 0, 1, time.UTC), + Weight: 100, + Limit: 2, + ActionTriggers: ActionTriggers{ + &ActionTrigger{ + // ID string // original csv tag + // UniqueID string // individual id + ThresholdType: utils.TRIGGER_MAX_EVENT_COUNTER, + ThresholdValue: 2, + // Recurrent bool // reset excuted flag each run + // MinSleep time.Duration // Minimum duration between two executions in case of recurrent triggers + // ExpirationDate time.Time + // ActivationDate time.Time + Balance: &BalanceFilter{ + Type: utils.StringPointer(utils.MONETARY), + Directions: utils.StringMapPointer(utils.NewStringMap(utils.OUT)), + }, + ActionsID: "TEST_ACTIONS", + // Weight float64 + // ActionsID string + // MinQueuedItems int // Trigger actions only if this number is hit (stats only) + // Executed bool + // LastExecutionTime time.Time }, }, - utils.NOT_AVAILABLE: map[string]utils.StringMap{ - utils.NOT_AVAILABLE: utils.StringMap{ - "RL4": true, - "RL5": true, - }, + UsageTTL: time.Duration(1 * time.Millisecond), + AllocationMessage: "ALLOC", + Usage: map[string]*ResourceUsage{ + ru.ID: ru, }, + TotalUsage: 2, } - // Test index update - if err := rLS.indexStringFilters([]string{rl3.ID, rl6.ID}); err != nil { - t.Error(err) - } else if !reflect.DeepEqual(eIndexes, rLS.stringIndexes) { - t.Errorf("Expecting: %+v, received: %+v", eIndexes, rLS.stringIndexes) - } -} -*/ -/* -func TestResourceLimitsAllowUsage(t *testing.T) { - rls := make(ResourceLimits, 0) - if !rls.AllowUsage(1.0) { - t.Error("Not allowed for empty limits") - } - rls = ResourceLimits{ - &ResourceLimit{ - ID: "RLAU1", - Weight: 20, - Filters: []*RequestFilter{ - &RequestFilter{Type: MetaString, FieldName: "Account", Values: []string{"1001", "1002"}}, - &RequestFilter{Type: MetaRSRFields, Values: []string{"Subject(~^1.*1$)", "Destination(1002)"}, - rsrFields: utils.ParseRSRFieldsMustCompile("Subject(~^1.*1$);Destination(1002)", utils.INFIELD_SEP), - }}, - ActivationInterval: &utils.ActivationInterval{ActivationTime: time.Date(2014, 7, 3, 13, 43, 0, 1, time.UTC)}, - Limit: 1, - Usage: map[string]*ResourceUsage{"call1": &ResourceUsage{ - ID: "call1", UsageTime: time.Now(), UsageUnits: 1}}, - TotalUsage: 1, - }, - &ResourceLimit{ - ID: "RLAU2", - Weight: 10, - Filters: []*RequestFilter{ - &RequestFilter{Type: MetaString, FieldName: "Account", Values: []string{"dan", "1002"}}, - &RequestFilter{Type: MetaString, FieldName: "Subject", Values: []string{"dan"}}, - }, - ActivationInterval: &utils.ActivationInterval{ActivationTime: time.Date(2014, 7, 3, 13, 43, 0, 1, time.UTC)}, - Limit: 2, - UsageTTL: time.Duration(1 * time.Millisecond), - Usage: make(map[string]*ResourceUsage), - }, - } - if !rls.AllowUsage(2.0) { - t.Error("Not allowed") - } -} -*/ -func TestRLsLoadRLs(t *testing.T) { - rls := []*ResourceLimit{ - &ResourceLimit{ - ID: "RL1", - Weight: 20, - Filters: []*RequestFilter{ - &RequestFilter{Type: MetaString, FieldName: "Account", Values: []string{"1001", "1002"}}, - &RequestFilter{Type: MetaRSRFields, Values: []string{"Subject(~^1.*1$)", "Destination(1002)"}, - rsrFields: utils.ParseRSRFieldsMustCompile("Subject(~^1.*1$);Destination(1002)", utils.INFIELD_SEP), - }}, - ActivationInterval: &utils.ActivationInterval{ActivationTime: time.Date(2014, 7, 3, 13, 43, 0, 1, time.UTC)}, - Limit: 2, - Usage: make(map[string]*ResourceUsage), - }, - &ResourceLimit{ - ID: "RL2", - Weight: 10, - Filters: []*RequestFilter{ - &RequestFilter{Type: MetaString, FieldName: "Account", Values: []string{"dan", "1002"}}, - &RequestFilter{Type: MetaString, FieldName: "Subject", Values: []string{"dan"}}, - }, - ActivationInterval: &utils.ActivationInterval{ActivationTime: time.Date(2014, 7, 3, 13, 43, 0, 1, time.UTC)}, - Limit: 1, - UsageTTL: time.Duration(1 * time.Millisecond), - Usage: make(map[string]*ResourceUsage), - }, - &ResourceLimit{ - ID: "RL3", - Weight: 10, - Filters: []*RequestFilter{ - &RequestFilter{Type: MetaString, FieldName: "Subject", Values: []string{"dan2"}}, - &RequestFilter{Type: MetaString, FieldName: "Subject", Values: []string{"1003"}}, - }, - ActivationInterval: &utils.ActivationInterval{ActivationTime: time.Date(2014, 7, 3, 13, 43, 0, 1, time.UTC)}, - Limit: 1, - Usage: make(map[string]*ResourceUsage), - }, - &ResourceLimit{ - ID: "RL4", - Weight: 10, - Filters: []*RequestFilter{ - &RequestFilter{Type: MetaStringPrefix, FieldName: "Destination", Values: []string{"+49"}}, - }, - ActivationInterval: &utils.ActivationInterval{ActivationTime: time.Date(2014, 7, 3, 13, 43, 0, 1, time.UTC)}, - Limit: 1, - Usage: make(map[string]*ResourceUsage), - }, - &ResourceLimit{ - ID: "RL5", - Weight: 10, - Filters: []*RequestFilter{ - &RequestFilter{Type: MetaStringPrefix, FieldName: "Destination", Values: []string{"+40"}}, - }, - ActivationInterval: &utils.ActivationInterval{ActivationTime: time.Date(2014, 7, 3, 13, 43, 0, 1, time.UTC)}, - Limit: 1, - UsageTTL: time.Duration(10 * time.Millisecond), - Usage: make(map[string]*ResourceUsage), - }, - &ResourceLimit{ // Add it so we can test expiryTime - ID: "RL6", - Weight: 10, - Filters: []*RequestFilter{ - &RequestFilter{Type: MetaString, FieldName: "Subject", Values: []string{"dan2"}}, - }, - ActivationInterval: &utils.ActivationInterval{ActivationTime: time.Date(2014, 7, 3, 13, 43, 0, 1, time.UTC)}, - ExpiryTime: time.Date(2014, 7, 3, 13, 43, 0, 1, time.UTC), - Limit: 1, - Usage: make(map[string]*ResourceUsage), - }, - } - rlIdxr, err := NewReqFilterIndexer(dataStorage, utils.ResourceLimitsIndex) - if err != nil { - t.Error(err) - } - for _, rl := range rls { - if err := dataStorage.SetResourceLimit(rl, utils.NonTransactional); err != nil { - t.Error(err) - } - rlIdxr.IndexFilters(rl.ID, rl.Filters) - } - if err := rlIdxr.StoreIndexes(); err != nil { - t.Error(err) - } -} - -func TestRLsMatchingResourceLimitsForEvent(t *testing.T) { - rLS = &ResourceLimiterService{dataDB: dataStorage, cdrStatS: nil} - eResLimits := ResourceLimits{ - &ResourceLimit{ - ID: "RL1", - Weight: 20, - Filters: []*RequestFilter{ - &RequestFilter{Type: MetaString, FieldName: "Account", Values: []string{"1001", "1002"}}, - &RequestFilter{Type: MetaRSRFields, Values: []string{"Subject(~^1.*1$)", "Destination(1002)"}, - rsrFields: utils.ParseRSRFieldsMustCompile("Subject(~^1.*1$);Destination(1002)", utils.INFIELD_SEP), - }}, - ActivationInterval: &utils.ActivationInterval{ActivationTime: time.Date(2014, 7, 3, 13, 43, 0, 1, time.UTC)}, - Limit: 2, - Usage: make(map[string]*ResourceUsage), - }, - &ResourceLimit{ - ID: "RL2", - Weight: 10, - Filters: []*RequestFilter{ - &RequestFilter{Type: MetaString, FieldName: "Account", Values: []string{"dan", "1002"}}, - &RequestFilter{Type: MetaString, FieldName: "Subject", Values: []string{"dan"}}, - }, - ActivationInterval: &utils.ActivationInterval{ActivationTime: time.Date(2014, 7, 3, 13, 43, 0, 1, time.UTC)}, - Limit: 1, - UsageTTL: time.Duration(1 * time.Millisecond), - Usage: make(map[string]*ResourceUsage), - }, - } - if resLimits, err := rLS.matchingResourceLimitsForEvent( - map[string]interface{}{"Account": "1002", "Subject": "dan", "Destination": "1002"}); err != nil { - t.Error(err) - } else if !reflect.DeepEqual(eResLimits[0].Filters[0], resLimits[0].Filters[0]) { - t.Errorf("Expecting: %+v, received: %+v", eResLimits, resLimits) + if err := rl.RecordUsage(ru2); err != nil { + t.Error(err.Error()) } else { - // Make sure the filters are what we expect to be after retrieving from cache: - fltr := resLimits[0].Filters[1] - if pass, _ := fltr.Pass(map[string]interface{}{"Subject": "10000001"}, "", nil); !pass { - t.Errorf("Expecting RL: %+v, received: %+v", eResLimits[0], resLimits[0]) + if err := rl.RecordUsage(ru); err == nil { + t.Error("Duplicate ResourceUsage id should not be allowed") } - if pass, _ := fltr.Pass(map[string]interface{}{"Account": "1002"}, "", nil); pass { - t.Errorf("Expecting RL: %+v, received: %+v", eResLimits[0], resLimits[0]) + if _, found := rl.Usage[ru2.ID]; !found { + t.Error("ResourceUsage was not recorded") } + if rl.TotalUsage != 4 { + t.Errorf("Expecting: %+v, received: %+v", 4, rl.TotalUsage) + } + } + +} + +func TestRLClearUsage(t *testing.T) { + rl.Usage = map[string]*ResourceUsage{ + ru.ID: ru, + } + rl.TotalUsage = 3 + + rl.ClearUsage(ru.ID) + + if len(rl.Usage) != 0 { + t.Errorf("Expecting: %+v, received: %+v", 0, len(rl.Usage)) + } + if rl.TotalUsage != 1 { + t.Errorf("Expecting: %+v, received: %+v", 1, rl.TotalUsage) } } -func TestRLsV1ResourceLimitsForEvent(t *testing.T) { - eLimits := []*ResourceLimit{ - &ResourceLimit{ - ID: "RL1", - Weight: 20, - Filters: []*RequestFilter{ - &RequestFilter{Type: MetaString, FieldName: "Account", Values: []string{"1001", "1002"}}, - &RequestFilter{Type: MetaRSRFields, Values: []string{"Subject(~^1.*1$)", "Destination(1002)"}, - rsrFields: utils.ParseRSRFieldsMustCompile("Subject(~^1.*1$);Destination(1002)", utils.INFIELD_SEP), - }}, - ActivationInterval: &utils.ActivationInterval{ActivationTime: time.Date(2014, 7, 3, 13, 43, 0, 1, time.UTC)}, - Limit: 2, - Usage: make(map[string]*ResourceUsage), - }, - &ResourceLimit{ - ID: "RL2", - Weight: 10, - Filters: []*RequestFilter{ - &RequestFilter{Type: MetaString, FieldName: "Account", Values: []string{"dan", "1002"}}, - &RequestFilter{Type: MetaString, FieldName: "Subject", Values: []string{"dan"}}, +func TestRLRemoveExpiredUnits(t *testing.T) { + rl.Usage = map[string]*ResourceUsage{ + ru.ID: ru, + } + rl.TotalUsage = 2 + + rl.removeExpiredUnits() + + if len(rl.Usage) != 0 { + t.Errorf("Expecting: %+v, received: %+v", 0, len(rl.Usage)) + } + if rl.TotalUsage != 0 { + t.Errorf("Expecting: %+v, received: %+v", 0, rl.TotalUsage) + } +} + +func TestRLUsedUnits(t *testing.T) { + rl.Usage = map[string]*ResourceUsage{ + ru.ID: ru, + } + rl.TotalUsage = 2 + + usedUnits := rl.UsedUnits() + + if len(rl.Usage) != 0 { + t.Errorf("Expecting: %+v, received: %+v", 0, len(rl.Usage)) + } + if usedUnits != 0 { + t.Errorf("Expecting: %+v, received: %+v", 0, usedUnits) + } +} + +func TestRLSort(t *testing.T) { + rl2 = &ResourceLimit{ + ID: "RL2", + Filters: []*RequestFilter{ + &RequestFilter{ + Type: MetaString, + FieldName: "Account", + Values: []string{"1001", "1002"}, + }, + &RequestFilter{ + Type: MetaRSRFields, + Values: []string{"Subject(~^1.*1$)", "Destination(1002)"}, + rsrFields: utils.ParseRSRFieldsMustCompile("Subject(~^1.*1$);Destination(1002)", utils.INFIELD_SEP), }, - ActivationInterval: &utils.ActivationInterval{ActivationTime: time.Date(2014, 7, 3, 13, 43, 0, 1, time.UTC)}, - Limit: 1, - UsageTTL: time.Duration(1 * time.Millisecond), - Usage: make(map[string]*ResourceUsage), }, + ActivationInterval: &utils.ActivationInterval{ + ActivationTime: time.Date(2014, 7, 3, 13, 43, 0, 1, time.UTC), + }, + ExpiryTime: time.Date(2014, 7, 3, 13, 43, 0, 1, time.UTC), + Weight: 50, + Limit: 2, + ActionTriggers: ActionTriggers{ + &ActionTrigger{ + // ID string // original csv tag + // UniqueID string // individual id + ThresholdType: utils.TRIGGER_MAX_EVENT_COUNTER, + ThresholdValue: 2, + // Recurrent bool // reset excuted flag each run + // MinSleep time.Duration // Minimum duration between two executions in case of recurrent triggers + // ExpirationDate time.Time + // ActivationDate time.Time + Balance: &BalanceFilter{ + Type: utils.StringPointer(utils.MONETARY), + Directions: utils.StringMapPointer(utils.NewStringMap(utils.OUT)), + }, + ActionsID: "TEST_ACTIONS", + // Weight float64 + // ActionsID string + // MinQueuedItems int // Trigger actions only if this number is hit (stats only) + // Executed bool + // LastExecutionTime time.Time + }, + }, + UsageTTL: time.Duration(1 * time.Millisecond), + // AllocationMessage: "ALLOC2", + Usage: map[string]*ResourceUsage{ + ru2.ID: ru2, + }, + TotalUsage: 2, } - var rcvLmts []*ResourceLimit - if err := rLS.V1ResourceLimitsForEvent(map[string]interface{}{"Account": "1002", "Subject": "dan", "Destination": "1002"}, &rcvLmts); err != nil { - t.Error(err) - } else if len(eLimits) != len(rcvLmts) { - t.Errorf("Expecting: %+v, received: %+v", eLimits, rcvLmts) + + rls = ResourceLimits{rl2, rl} + rls.Sort() + + if rls[0].ID != "RL1" { + t.Error("Sort failed") } } -func TestRLsV1InitiateResourceUsage(t *testing.T) { - attrRU := utils.AttrRLsResourceUsage{ - UsageID: "651a8db2-4f67-4cf8-b622-169e8a482e50", - Event: map[string]interface{}{"Account": "1002", "Subject": "dan", "Destination": "1002"}, - Units: 1, - } - var reply string - if err := rLS.V1AllocateResource(attrRU, &reply); err != nil { - t.Error(err) - } else if reply != "RL1" { - t.Error("Received reply: ", reply) - } - resLimits, err := rLS.matchingResourceLimitsForEvent(attrRU.Event) - if err != nil { - t.Error(err) - } else if len(resLimits) != 2 { - t.Errorf("Received: %+v", resLimits) - } else if resLimits[0].UsedUnits() != 1 { - t.Errorf("RL1: %+v", resLimits[0]) - } else if _, hasKey := resLimits[0].Usage[attrRU.UsageID]; !hasKey { - t.Errorf("RL1: %+v", resLimits[0]) +func TestRLsClearUsage(t *testing.T) { + rls.ClearUsage(ru2.ID) + for _, rl := range rls { + if len(rl.Usage) > 0 { + t.Errorf("Expecting: %+v, received: %+v", 0, len(rl.Usage)) + } } } -func TestRLsV1TerminateResourceUsage(t *testing.T) { - attrRU := utils.AttrRLsResourceUsage{ - UsageID: "651a8db2-4f67-4cf8-b622-169e8a482e50", - Event: map[string]interface{}{"Account": "1002", "Subject": "dan", "Destination": "1002"}, - Units: 1, - } - var reply string - if err := rLS.V1ReleaseResource(attrRU, &reply); err != nil { - t.Error(err) - } else if reply != utils.OK { - t.Error("Received reply: ", reply) - } - resLimits, err := rLS.matchingResourceLimitsForEvent(attrRU.Event) - if err != nil { - t.Error(err) - } else if len(resLimits) != 2 { - t.Errorf("Received: %+v", resLimits) - } else if resLimits[0].UsedUnits() != 0 { - t.Errorf("RL1: %+v", resLimits[0]) - } else if _, hasKey := resLimits[0].Usage[attrRU.UsageID]; hasKey { - t.Errorf("RL1: %+v", resLimits[0]) +func TestRLsRecordUsages(t *testing.T) { + if err := rls.RecordUsage(ru); err != nil { + for _, rl := range rls { + if _, found := rl.Usage[ru.ID]; found { + t.Error("Fallback on error failed") + } + } + t.Error(err.Error()) + } else { + for _, rl := range rls { + if _, found := rl.Usage[ru.ID]; !found { + t.Error("ResourceUsage not found ") + } + if rl.TotalUsage != 2 { + t.Errorf("Expecting: %+v, received: %+v", 2, rl.TotalUsage) + } + } + } +} + +func TestRLsAllocateResource(t *testing.T) { + rls.ClearUsage(ru.ID) + rls.ClearUsage(ru2.ID) + + rls[0].UsageTTL = time.Duration(20 * time.Second) + rls[1].UsageTTL = time.Duration(20 * time.Second) + ru.Time = time.Now() + ru2.Time = time.Now() + + if alcMessage, err := rls.AllocateResource(ru, false); err != nil { + t.Error(err.Error()) + } else { + if alcMessage != "ALLOC" { + t.Errorf("Wrong allocation message: %v", alcMessage) + } + } + + if _, err := rls.AllocateResource(ru2, false); err != utils.ErrResourceUnavailable { + t.Error("Did not receive " + utils.ErrResourceUnavailable.Error() + " error") + } + + rls[0].Limit = 2 + rls[1].Limit = 4 + + if alcMessage, err := rls.AllocateResource(ru, true); err != nil { + t.Error(err.Error()) + } else { + if alcMessage != "RL2" { + t.Errorf("Wrong allocation message: %v", alcMessage) + } + } + + if alcMessage, err := rls.AllocateResource(ru2, false); err != nil { + t.Error(err.Error()) + } else { + if alcMessage != "RL2" { + t.Errorf("Wrong allocation message: %v", alcMessage) + } + } + + ru2.Units = 0 + if _, err := rls.AllocateResource(ru2, false); err == nil { + t.Error("Duplicate ResourceUsage id should not be allowed") } }