diff --git a/apier/v1/resourcesv1.go b/apier/v1/resourcesv1.go index db7fb9648..2e8a820ab 100644 --- a/apier/v1/resourcesv1.go +++ b/apier/v1/resourcesv1.go @@ -62,7 +62,7 @@ func (rsv1 *ResourceSV1) Call(serviceMethod string, args interface{}, reply inte } // GetResourcesForEvent returns Resources matching a specific event -func (rsv1 *ResourceSV1) GetResourcesForEvent(args utils.ArgRSv1ResourceUsage, reply *[]*engine.ResourceProfile) error { +func (rsv1 *ResourceSV1) GetResourcesForEvent(args utils.ArgRSv1ResourceUsage, reply *engine.Resources) error { return rsv1.rls.V1ResourcesForEvent(args, reply) } diff --git a/apier/v1/resourcesv1_it_test.go b/apier/v1/resourcesv1_it_test.go index cb32d7681..4d69dd3f6 100644 --- a/apier/v1/resourcesv1_it_test.go +++ b/apier/v1/resourcesv1_it_test.go @@ -49,17 +49,18 @@ var sTestsRLSV1 = []func(t *testing.T){ testV1RsRpcConn, testV1RsFromFolder, testV1RsGetResourcesForEvent, - /*testV1RsAllocateResource, + testV1RsTTL0, + testV1RsAllocateResource, testV1RsAllowUsage, testV1RsReleaseResource, - testV1RsGetResourceConfigBeforeSet, - testV1RsSetResourceConfig, - testV1RsGetResourceConfigAfterSet, - testV1RsUpdateResourceConfig, - testV1RsGetResourceConfigAfterUpdate, - testV1RsRemResourceCOnfig, - testV1RsGetResourceConfigAfterDelete, - */ + testV1RsDBStore, + testV1RsGetResourceProfileBeforeSet, + testV1RsSetResourceProfile, + testV1RsGetResourceProfileAfterSet, + testV1RsUpdateResourceProfile, + testV1RsGetResourceProfileAfterUpdate, + testV1RsRemResourceProfile, + testV1RsGetResourceProfileAfterDelete, testV1RsStopEngine, } @@ -173,111 +174,336 @@ func testV1RsGetResourcesForEvent(t *testing.T) { } } -func testV1RsAllocateResource(t *testing.T) { +func testV1RsTTL0(t *testing.T) { + // only matching Resource3 + argsRU := utils.ArgRSv1ResourceUsage{ + Tenant: "cgrates.org", + UsageID: "651a8db2-4f67-4cf8-b622-169e8a482e21", + Event: map[string]interface{}{ + "Account": "3001", + "Destination": "3002"}, + Units: 1, + } var reply string + if err := rlsV1Rpc.Call("ResourceSV1.AllocateResource", argsRU, &reply); err != nil { + t.Error(err) + } + // second allocation should be also allowed + argsRU = utils.ArgRSv1ResourceUsage{ + Tenant: "cgrates.org", + UsageID: "651a8db2-4f67-4cf8-b622-169e8a482e21", + Event: map[string]interface{}{ + "Account": "3001", + "Destination": "3002"}, + Units: 1, + } + if err := rlsV1Rpc.Call("ResourceSV1.AllocateResource", argsRU, &reply); err != nil { + t.Error(err) + } + // too many units should be rejected + argsRU = utils.ArgRSv1ResourceUsage{ + Tenant: "cgrates.org", + UsageID: "651a8db2-4f67-4cf8-b622-169e8a482e22", + Event: map[string]interface{}{ + "Account": "3001", + "Destination": "3002"}, + Units: 2, + } + if err := rlsV1Rpc.Call("ResourceSV1.AllocateResource", argsRU, &reply); err == nil || + err.Error() != utils.ErrResourceUnavailable.Error() { + t.Error(err) + } + // make sure no usage was recorded + var rs *engine.Resources + args := &utils.ArgRSv1ResourceUsage{ + Tenant: "cgrates.org", + Event: map[string]interface{}{ + "Account": "3001", + "Destination": "3002"}} + if err := rlsV1Rpc.Call("ResourceSV1.GetResourcesForEvent", args, &rs); err != nil { + t.Error(err) + } else if len(*rs) != 1 { + t.Errorf("Resources: %+v", rs) + } else { + res := *rs + if len(res[0].Usages) != 0 || len(res[0].TTLIdx) != 0 { + t.Errorf("Resource should have no usage records in: %+v", res[0]) + } + } + // release should not give out errors + var releaseReply string + argsRU = utils.ArgRSv1ResourceUsage{ + Tenant: "cgrates.org", + UsageID: "651a8db2-4f67-4cf8-b622-169e8a482e25", // same ID should be accepted by first group since the previous resource should be expired + Event: map[string]interface{}{ + "Account": "3001", + "Destination": "3002"}, + } + if err := rlsV1Rpc.Call("ResourceSV1.ReleaseResource", argsRU, &releaseReply); err != nil { + t.Error(err) + } +} - attrRU := utils.ArgRSv1ResourceUsage{ +func testV1RsAllocateResource(t *testing.T) { + // first event matching Resource1 + var reply string + argsRU := utils.ArgRSv1ResourceUsage{ + Tenant: "cgrates.org", UsageID: "651a8db2-4f67-4cf8-b622-169e8a482e51", - Event: map[string]interface{}{"Account": "1002", "Subject": "1001", "Destination": "1002"}, - Units: 3, + Event: map[string]interface{}{ + "Account": "1002", + "Subject": "1001", + "Destination": "1002"}, + Units: 3, } - if err := rlsV1Rpc.Call("ResourceSV1.AllocateResource", attrRU, &reply); err != nil { + if err := rlsV1Rpc.Call("ResourceSV1.AllocateResource", argsRU, &reply); err != nil { t.Error(err) } - if reply != "ResGroup1" { - t.Errorf("Expecting: %+v, received: %+v", "ResGroup1", reply) + eAllocationMsg := "ResGroup1" + if reply != eAllocationMsg { + t.Errorf("Expecting: %+v, received: %+v", eAllocationMsg, reply) } - - time.Sleep(time.Duration(1000) * time.Millisecond) - - attrRU = utils.ArgRSv1ResourceUsage{ + // Second event to test matching of exact limit of first resource + argsRU = utils.ArgRSv1ResourceUsage{ + Tenant: "cgrates.org", UsageID: "651a8db2-4f67-4cf8-b622-169e8a482e52", - Event: map[string]interface{}{"Destination": "100"}, - Units: 5, + Event: map[string]interface{}{ + "Account": "1002", + "Subject": "1001", + "Destination": "1002"}, + Units: 4, } - if err := rlsV1Rpc.Call("ResourceSV1.AllocateResource", attrRU, &reply); err != nil { + if err := rlsV1Rpc.Call("ResourceSV1.AllocateResource", argsRU, &reply); err != nil { t.Error(err) } - if reply != "ResGroup2" { - t.Errorf("Expecting: %+v, received: %+v", "ResGroup2", reply) + eAllocationMsg = "ResGroup1" + if reply != eAllocationMsg { + t.Errorf("Expecting: %+v, received: %+v", eAllocationMsg, reply) } - - time.Sleep(time.Duration(1000) * time.Millisecond) - - attrRU = utils.ArgRSv1ResourceUsage{ + // Third event testing overflow to second resource which still has one resource available + argsRU = utils.ArgRSv1ResourceUsage{ + Tenant: "cgrates.org", UsageID: "651a8db2-4f67-4cf8-b622-169e8a482e53", - Event: map[string]interface{}{"Account": "1002", "Subject": "1001", "Destination": "1002"}, - Units: 3, + Event: map[string]interface{}{ + "Account": "dan", + "Subject": "dan", + "Destination": "1002"}, + Units: 1, } - if err := rlsV1Rpc.Call("ResourceSV1.AllocateResource", attrRU, &reply); err != nil { + if err := rlsV1Rpc.Call("ResourceSV1.AllocateResource", argsRU, &reply); err != nil { t.Error(err) } - if reply != "ResGroup1" { - t.Errorf("Expecting: %+v, received: %+v", "ResGroup1", reply) + eAllocationMsg = "ResGroup2" + if reply != eAllocationMsg { + t.Errorf("Expecting: %+v, received: %+v", eAllocationMsg, reply) } + // Test resource unavailable + argsRU = utils.ArgRSv1ResourceUsage{ + Tenant: "cgrates.org", + UsageID: "651a8db2-4f67-4cf8-b622-169e8a482e54", // same ID should be accepted by first group since the previous resource should be expired + Event: map[string]interface{}{ + "Account": "1002", + "Subject": "1001", + "Destination": "1002"}, + Units: 1, + } + if err := rlsV1Rpc.Call("ResourceSV1.AllocateResource", argsRU, &reply); err == nil || err.Error() != utils.ErrResourceUnavailable.Error() { + t.Error(err) + } + eAllocationMsg = "ResGroup1" + time.Sleep(time.Duration(1000) * time.Millisecond) // Give time for allocations on first resource to expire + argsRU = utils.ArgRSv1ResourceUsage{ + Tenant: "cgrates.org", + UsageID: "651a8db2-4f67-4cf8-b622-169e8a482e55", // same ID should be accepted by first group since the previous resource should be expired + Event: map[string]interface{}{ + "Account": "1002", + "Subject": "1001", + "Destination": "1002"}, + Units: 1, + } + if err := rlsV1Rpc.Call("ResourceSV1.AllocateResource", argsRU, &reply); err != nil { + t.Error(err) + } + eAllocationMsg = "ResGroup1" + if reply != eAllocationMsg { + t.Errorf("Expecting: %+v, received: %+v", eAllocationMsg, reply) + } } func testV1RsAllowUsage(t *testing.T) { var allowed bool - attrRU := utils.ArgRSv1ResourceUsage{ - UsageID: "651a8db2-4f67-4cf8-b622-169e8a482e51", - Event: map[string]interface{}{"Account": "1002", "Subject": "1001", "Destination": "1002"}, - Units: 1, + argsRU := utils.ArgRSv1ResourceUsage{ + Tenant: "cgrates.org", + UsageID: "651a8db2-4f67-4cf8-b622-169e8a482e61", + Event: map[string]interface{}{ + "Account": "1002", + "Subject": "1001", + "Destination": "1002"}, + Units: 6, } - if err := rlsV1Rpc.Call("ResourceSV1.AllowUsage", attrRU, &allowed); err != nil { + if err := rlsV1Rpc.Call("ResourceSV1.AllowUsage", argsRU, &allowed); err != nil { t.Error(err) - } else if !allowed { - t.Errorf("Expecting: %+v, received: %+v", true, allowed) + } else if !allowed { // already 3 usages active before allow call, we should have now more than allowed + t.Error("resource is not allowed") } - - attrRU = utils.ArgRSv1ResourceUsage{ - UsageID: "651a8db2-4f67-4cf8-b622-169e8a482e51", - Event: map[string]interface{}{"Account": "1002", "Subject": "1001", "Destination": "1002"}, - Units: 2, + argsRU = utils.ArgRSv1ResourceUsage{ + Tenant: "cgrates.org", + UsageID: "651a8db2-4f67-4cf8-b622-169e8a482e61", + Event: map[string]interface{}{ + "Account": "1002", + "Subject": "1001", + "Destination": "1002"}, + Units: 7, } - if err := rlsV1Rpc.Call("ResourceSV1.AllowUsage", attrRU, &allowed); err != nil { // already + if err := rlsV1Rpc.Call("ResourceSV1.AllowUsage", argsRU, &allowed); err != nil { t.Error(err) } else if allowed { // already 3 usages active before allow call, we should have now more than allowed - t.Error("Resource allowed") + t.Error("resource should not be allowed") } } func testV1RsReleaseResource(t *testing.T) { + // relase the only resource active for Resource1 var reply string - attrRU := utils.ArgRSv1ResourceUsage{ - UsageID: "651a8db2-4f67-4cf8-b622-169e8a482e52", - Event: map[string]interface{}{"Destination": "100"}, - Units: 2, + argsRU := utils.ArgRSv1ResourceUsage{ + Tenant: "cgrates.org", + UsageID: "651a8db2-4f67-4cf8-b622-169e8a482e55", // same ID should be accepted by first group since the previous resource should be expired + Event: map[string]interface{}{ + "Account": "1002", + "Subject": "1001", + "Destination": "1002"}, } - if err := rlsV1Rpc.Call("ResourceSV1.ReleaseResource", attrRU, &reply); err != nil { + if err := rlsV1Rpc.Call("ResourceSV1.ReleaseResource", argsRU, &reply); err != nil { t.Error(err) } + // try reserving with full units for Resource1, case which did not work in previous test + // only match Resource1 since we don't want for storing of the resource2 bellow + argsRU = utils.ArgRSv1ResourceUsage{ + Tenant: "cgrates.org", + UsageID: "651a8db2-4f67-4cf8-b622-169e8a482e61", + Event: map[string]interface{}{ + "Account": "1002", + "Subject": "1001", + "Destination": "2002"}, + Units: 7, + } var allowed bool - if err := rlsV1Rpc.Call("ResourceSV1.AllowUsage", attrRU, &allowed); err != nil { + if err := rlsV1Rpc.Call("ResourceSV1.AllowUsage", argsRU, &allowed); err != nil { t.Error(err) } else if !allowed { - t.Error("not allowed") + t.Error("resource should be allowed") } - attrRU.Units += 7 - if err := rlsV1Rpc.Call("ResourceSV1.AllowUsage", attrRU, &allowed); err != nil { + var rs *engine.Resources + args := &utils.ArgRSv1ResourceUsage{ + Tenant: "cgrates.org", + Event: map[string]interface{}{ + "Account": "1002", + "Subject": "1001", + "Destination": "1002"}} + if err := rlsV1Rpc.Call("ResourceSV1.GetResourcesForEvent", args, &rs); err != nil { t.Error(err) - } else if allowed { - t.Error("Resource should not be allowed") + } else if len(*rs) != 2 { + t.Errorf("Resources: %+v", rs) + } + // make sure Resource1 have no more active resources + for _, r := range *rs { + if r.ID == "ResGroup1" && + (len(r.Usages) != 0 || len(r.TTLIdx) != 0) { + t.Errorf("Unexpected resource: %+v", r) + } } } -func testV1RsGetResourceConfigBeforeSet(t *testing.T) { +func testV1RsDBStore(t *testing.T) { + argsRU := utils.ArgRSv1ResourceUsage{ + Tenant: "cgrates.org", + UsageID: "651a8db2-4f67-4cf8-b622-169e8a482e71", + Event: map[string]interface{}{ + "Account": "1002", + "Subject": "1001", + "Destination": "1002"}, + Units: 1, + } + var reply string + eAllocationMsg := "ResGroup1" + if err := rlsV1Rpc.Call("ResourceSV1.AllocateResource", argsRU, &reply); err != nil { + t.Error(err) + } else if reply != eAllocationMsg { + t.Errorf("Expecting: %+v, received: %+v", eAllocationMsg, reply) + } + var rs *engine.Resources + args := &utils.ArgRSv1ResourceUsage{ + Tenant: "cgrates.org", + Event: map[string]interface{}{ + "Account": "1002", + "Subject": "1001", + "Destination": "1002"}} + if err := rlsV1Rpc.Call("ResourceSV1.GetResourcesForEvent", args, &rs); err != nil { + t.Error(err) + } else if len(*rs) != 2 { + t.Errorf("Resources: %+v", rs) + } + // count resources before restart + for _, r := range *rs { + switch r.ID { + case "ResGroup1": + if len(r.Usages) != 1 || len(r.TTLIdx) != 1 { + t.Errorf("Unexpected resource: %+v", r) + } + case "ResGroup2": + if len(r.Usages) != 4 || len(r.TTLIdx) != 4 { + t.Errorf("Unexpected resource: %+v", r) + } + } + } + if _, err := engine.StopStartEngine(rlsV1CfgPath, resDelay); err != nil { + t.Fatal(err) + } + 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()) + } + rs = new(engine.Resources) + args = &utils.ArgRSv1ResourceUsage{ + Tenant: "cgrates.org", + Event: map[string]interface{}{ + "Account": "1002", + "Subject": "1001", + "Destination": "1002"}} + if err := rlsV1Rpc.Call("ResourceSV1.GetResourcesForEvent", args, &rs); err != nil { + t.Error(err) + } else if len(*rs) != 2 { + t.Errorf("Resources: %+v", rs) + } + // count resources after restart + for _, r := range *rs { + switch r.ID { + case "ResGroup1": + if len(r.Usages) != 0 || len(r.TTLIdx) != 0 { + t.Errorf("Unexpected resource: %+v", r) + } + case "ResGroup2": + if len(r.Usages) != 3 || len(r.TTLIdx) != 3 { + t.Errorf("Unexpected resource: %+v", r) + } + } + } +} + +func testV1RsGetResourceProfileBeforeSet(t *testing.T) { var reply *string - if err := rlsV1Rpc.Call("ApierV1.GetResourceConfig", &utils.TenantID{Tenant: "cgrates.org", ID: "RCFG1"}, + if err := rlsV1Rpc.Call("ApierV1.GetResourceProfile", &utils.TenantID{Tenant: "cgrates.org", ID: "RCFG1"}, &reply); err == nil || err.Error() != utils.ErrNotFound.Error() { t.Error(err) } } -func testV1RsSetResourceConfig(t *testing.T) { +func testV1RsSetResourceProfile(t *testing.T) { rlsConfig = &engine.ResourceProfile{ - ID: "RCFG1", + Tenant: "cgrates.org", + ID: "RCFG1", Filters: []*engine.RequestFilter{ &engine.RequestFilter{ Type: "type", @@ -298,16 +524,16 @@ func testV1RsSetResourceConfig(t *testing.T) { Thresholds: []string{"Val1", "Val2"}, } var result string - if err := rlsV1Rpc.Call("ApierV1.SetResourceConfig", rlsConfig, &result); err != nil { + if err := rlsV1Rpc.Call("ApierV1.SetResourceProfile", rlsConfig, &result); err != nil { t.Error(err) } else if result != utils.OK { t.Error("Unexpected reply returned", result) } } -func testV1RsGetResourceConfigAfterSet(t *testing.T) { +func testV1RsGetResourceProfileAfterSet(t *testing.T) { var reply *engine.ResourceProfile - if err := rlsV1Rpc.Call("ApierV1.GetResourceConfig", + if err := rlsV1Rpc.Call("ApierV1.GetResourceProfile", &utils.TenantID{Tenant: "cgrates.org", ID: rlsConfig.ID}, &reply); err != nil { t.Error(err) } else if !reflect.DeepEqual(reply, rlsConfig) { @@ -315,7 +541,7 @@ func testV1RsGetResourceConfigAfterSet(t *testing.T) { } } -func testV1RsUpdateResourceConfig(t *testing.T) { +func testV1RsUpdateResourceProfile(t *testing.T) { var result string rlsConfig.Filters = []*engine.RequestFilter{ &engine.RequestFilter{ @@ -329,16 +555,16 @@ func testV1RsUpdateResourceConfig(t *testing.T) { Values: []string{"1001", "1002"}, }, } - if err := rlsV1Rpc.Call("ApierV1.SetResourceConfig", rlsConfig, &result); err != nil { + if err := rlsV1Rpc.Call("ApierV1.SetResourceProfile", rlsConfig, &result); err != nil { t.Error(err) } else if result != utils.OK { t.Error("Unexpected reply returned", result) } } -func testV1RsGetResourceConfigAfterUpdate(t *testing.T) { +func testV1RsGetResourceProfileAfterUpdate(t *testing.T) { var reply *engine.ResourceProfile - if err := rlsV1Rpc.Call("ApierV1.GetResourceConfig", + if err := rlsV1Rpc.Call("ApierV1.GetResourceProfile", &utils.TenantID{Tenant: "cgrates.org", ID: rlsConfig.ID}, &reply); err != nil { t.Error(err) } else if !reflect.DeepEqual(reply, rlsConfig) { @@ -346,9 +572,9 @@ func testV1RsGetResourceConfigAfterUpdate(t *testing.T) { } } -func testV1RsRemResourceCOnfig(t *testing.T) { +func testV1RsRemResourceProfile(t *testing.T) { var resp string - if err := rlsV1Rpc.Call("ApierV1.RemResourceConfig", + if err := rlsV1Rpc.Call("ApierV1.RemResourceProfile", &utils.TenantID{Tenant: "cgrates.org", ID: rlsConfig.ID}, &resp); err != nil { t.Error(err) } else if resp != utils.OK { @@ -356,9 +582,9 @@ func testV1RsRemResourceCOnfig(t *testing.T) { } } -func testV1RsGetResourceConfigAfterDelete(t *testing.T) { +func testV1RsGetResourceProfileAfterDelete(t *testing.T) { var reply *string - if err := rlsV1Rpc.Call("ApierV1.GetResourceConfig", + if err := rlsV1Rpc.Call("ApierV1.GetResourceProfile", &utils.TenantID{Tenant: "cgrates.org", ID: "RCFG1"}, &reply); err == nil || err.Error() != utils.ErrNotFound.Error() { t.Error(err) } diff --git a/data/conf/samples/tutmongo/cgrates.json b/data/conf/samples/tutmongo/cgrates.json index e406a0baf..18963eb3b 100644 --- a/data/conf/samples/tutmongo/cgrates.json +++ b/data/conf/samples/tutmongo/cgrates.json @@ -115,6 +115,7 @@ "resources": { "enabled": true, + "store_interval": "1s", }, diff --git a/data/conf/samples/tutmysql/cgrates.json b/data/conf/samples/tutmysql/cgrates.json index ce0358ea1..0fcc01a1c 100644 --- a/data/conf/samples/tutmysql/cgrates.json +++ b/data/conf/samples/tutmysql/cgrates.json @@ -108,6 +108,7 @@ "resources": { "enabled": true, + "store_interval": "1s", }, diff --git a/data/conf/samples/tutpostgres/cgrates.json b/data/conf/samples/tutpostgres/cgrates.json index 6ebb5d62b..eb438b758 100644 --- a/data/conf/samples/tutpostgres/cgrates.json +++ b/data/conf/samples/tutpostgres/cgrates.json @@ -72,6 +72,7 @@ "resources": { "enabled": true, + "store_interval": "1s", }, diff --git a/data/tariffplans/tutorial/Resources.csv b/data/tariffplans/tutorial/Resources.csv index ecd659285..8aa3e768c 100755 --- a/data/tariffplans/tutorial/Resources.csv +++ b/data/tariffplans/tutorial/Resources.csv @@ -1,8 +1,8 @@ #Tenant[0],Id[1],FilterType[2],FilterFieldName[3],FilterFieldValues[4],ActivationInterval[5],TTL[6],Limit[7],AllocationMessage[8],Blocker[9],Stored[10],Weight[11],Thresholds[12] -cgrates.org,ResGroup1,*string,Account,1001;1002,2014-07-29T15:00:00Z,1s,7,,true,true,20, +cgrates.org,ResGroup1,*string,Account,1001;1002,2014-07-29T15:00:00Z,1s,7,,false,false,20, cgrates.org,ResGroup1,*string_prefix,Destination,10;20,,,,,,,, cgrates.org,ResGroup1,*rsr_fields,,Subject(~^1.*1$);Destination(1002),,,,,,,, -cgrates.org,ResGroup2,*destinations,Destination,DST_FS,2014-07-29T15:00:00Z,3600s,8,SPECIAL_1002,true,true,10, -cgrates.org,ResGroup3,*string,Account,3001,2014-07-29T15:00:00Z,1s,3,,true,true,20, +cgrates.org,ResGroup2,*destinations,Destination,DST_FS,2014-07-29T15:00:00Z,3600s,8,SPECIAL_1002,false,true,10, +cgrates.org,ResGroup3,*string,Account,3001,2014-07-29T15:00:00Z,0s,1,,true,false,20, #cgrates.org,ResGroup3,*timings,SetupTime,PEAK,,,,,,,, #cgrates.org,ResGroup3,*stats,,CDRST1:*min_ASR:34;CDRST_1001:*min_ASR:20,,,,,,,, diff --git a/engine/resources.go b/engine/resources.go index 54131b224..b2c1f8396 100755 --- a/engine/resources.go +++ b/engine/resources.go @@ -125,6 +125,7 @@ func (r *Resource) removeExpiredUnits() { } } r.TTLIdx = r.TTLIdx[firstActive:] + r.tUsage = nil } // totalUsage returns the sum of all usage units @@ -148,6 +149,9 @@ func (r *Resource) recordUsage(ru *ResourceUsage) (err error) { return fmt.Errorf("duplicate resource usage with id: %s", ru.TenantID()) } if r.ttl != nil { + if *r.ttl == 0 { + return // no recording for ttl of 0 + } ru = ru.Clone() // don't influence the initial ru ru.ExpiryTime = time.Now().Add(*r.ttl) } @@ -211,7 +215,8 @@ func (rs Resources) recordUsage(ru *ResourceUsage) (err error) { // clearUsage gives back the units to the pool func (rs Resources) clearUsage(ruTntID string) (err error) { for _, r := range rs { - if errClear := r.clearUsage(ruTntID); errClear != nil { + if errClear := r.clearUsage(ruTntID); errClear != nil && + r.ttl != nil && *r.ttl != 0 { // we only consider not found error in case of ttl different than 0 utils.Logger.Warning(fmt.Sprintf(", clear ruID: %s, err: %s", ruTntID, errClear.Error())) err = errClear } @@ -236,10 +241,10 @@ func (rs Resources) tenatIDsStr() []string { return ids } -// AllocateResource attempts allocating resources for a *ResourceUsage +// allocateResource attempts allocating resources for a *ResourceUsage // simulates on dryRun // returns utils.ErrResourceUnavailable if allocation is not possible -func (rs Resources) AllocateResource(ru *ResourceUsage, dryRun bool) (alcMessage string, err error) { +func (rs Resources) allocateResource(ru *ResourceUsage, dryRun bool) (alcMessage string, err error) { if len(rs) == 0 { return "", utils.ErrResourceUnavailable } @@ -248,6 +253,7 @@ func (rs Resources) AllocateResource(ru *ResourceUsage, dryRun bool) (alcMessage defer guardian.Guardian.UnguardIDs(lockIDs...) // Simulate resource usage for _, r := range rs { + r.removeExpiredUnits() if r.rPrf.Limit >= r.totalUsage()+ru.Units { if alcMessage == "" { if r.rPrf.AllocationMessage != "" { @@ -363,10 +369,12 @@ func (rS *ResourceService) runBackup() { select { case <-rS.stopBackup: return + default: } rS.storeResources() + time.Sleep(rS.storeInterval) } - time.Sleep(rS.storeInterval) + } // cachedResourcesForEvent attempts to retrieve cached resources for an event @@ -454,10 +462,10 @@ func (rS *ResourceService) matchingResourcesForEvent(tenant string, ev map[strin if err != nil { return nil, err } - if rPrf.Stored { + if rPrf.Stored && r.dirty == nil { r.dirty = utils.BoolPointer(false) } - if rPrf.UsageTTL > 0 { + if rPrf.UsageTTL >= 0 { r.ttl = utils.DurationPointer(rPrf.UsageTTL) } r.rPrf = rPrf @@ -481,7 +489,7 @@ func (rS *ResourceService) matchingResourcesForEvent(tenant string, ev map[strin } // V1ResourcesForEvent returns active resource configs matching the event -func (rS *ResourceService) V1ResourcesForEvent(args utils.ArgRSv1ResourceUsage, reply *[]*ResourceProfile) (err error) { +func (rS *ResourceService) V1ResourcesForEvent(args utils.ArgRSv1ResourceUsage, reply *Resources) (err error) { if args.Tenant == "" { return utils.NewErrMandatoryIeMissing("Tenant") } @@ -498,10 +506,8 @@ func (rS *ResourceService) V1ResourcesForEvent(args utils.ArgRSv1ResourceUsage, if len(mtcRLs) == 0 { return utils.ErrNotFound } - for _, r := range mtcRLs { - *reply = append(*reply, r.rPrf) - } - return nil + *reply = mtcRLs + return } // V1AllowUsage queries service to find if an Usage is allowed @@ -516,7 +522,7 @@ func (rS *ResourceService) V1AllowUsage(args utils.ArgRSv1ResourceUsage, allow * } cache.Set(utils.EventResourcesPrefix+args.TenantID(), mtcRLs.tenantIDs(), true, "") } - if _, err = mtcRLs.AllocateResource( + if _, err = mtcRLs.allocateResource( &ResourceUsage{ Tenant: args.Tenant, ID: args.UsageID, @@ -546,7 +552,8 @@ func (rS *ResourceService) V1AllocateResource(args utils.ArgRSv1ResourceUsage, r } else { wasCached = true } - alcMsg, err := mtcRLs.AllocateResource(&ResourceUsage{ID: args.UsageID, Units: args.Units}, false) + alcMsg, err := mtcRLs.allocateResource( + &ResourceUsage{Tenant: args.Tenant, ID: args.UsageID, Units: args.Units}, false) if err != nil { return err } @@ -572,8 +579,8 @@ func (rS *ResourceService) V1AllocateResource(args utils.ArgRSv1ResourceUsage, r rS.StoreResource(r) } else if r.dirty != nil { *r.dirty = true // mark it to be saved + rS.storedResources[r.TenantID()] = true } - rS.storedResources[r.ID] = true } rS.srMux.Unlock() *reply = alcMsg @@ -604,7 +611,7 @@ func (rS *ResourceService) V1ReleaseResource(args utils.ArgRSv1ResourceUsage, re rS.StoreResource(r) } else { *r.dirty = true // mark it to be saved - rS.storedResources[r.ID] = true + rS.storedResources[r.TenantID()] = true } } } diff --git a/engine/resources_test.go b/engine/resources_test.go index b1b154b06..ce0a5aa14 100644 --- a/engine/resources_test.go +++ b/engine/resources_test.go @@ -37,7 +37,7 @@ func TestRSRecordUsage1(t *testing.T) { Tenant: "cgrates.org", ID: "RU1", ExpiryTime: time.Date(2014, 7, 3, 13, 43, 0, 1, time.UTC), - Units: 2, + Units: 1, } ru2 = &ResourceUsage{ @@ -113,7 +113,7 @@ func TestRSRemoveExpiredUnits(t *testing.T) { if len(r1.TTLIdx) != 0 { t.Errorf("Expecting: %+v, received: %+v", 0, len(r1.TTLIdx)) } - if *r1.tUsage != 0 { + if r1.tUsage != nil && *r1.tUsage != 0 { t.Errorf("Expecting: %+v, received: %+v", 0, r1.tUsage) } } @@ -123,8 +123,8 @@ func TestRSUsedUnits(t *testing.T) { ru1.ID: ru1, } r1.tUsage = nil - if usedUnits := r1.totalUsage(); usedUnits != 2 { - t.Errorf("Expecting: %+v, received: %+v", 2, usedUnits) + if usedUnits := r1.totalUsage(); usedUnits != 1 { + t.Errorf("Expecting: %+v, received: %+v", 1, usedUnits) } } @@ -205,28 +205,21 @@ func TestRSRecordUsages(t *testing.T) { func TestRSAllocateResource(t *testing.T) { rs.clearUsage(ru1.ID) rs.clearUsage(ru2.ID) - - rs[0].rPrf.UsageTTL = time.Duration(20 * time.Second) - rs[1].rPrf.UsageTTL = time.Duration(20 * time.Second) - //ru1.ExpiryTime = time.Now() - //ru2.Time = time.Now() - - if alcMessage, err := rs.AllocateResource(ru1, false); err != nil { + ru1.ExpiryTime = time.Now().Add(time.Duration(1 * time.Second)) + ru2.ExpiryTime = time.Now().Add(time.Duration(1 * time.Second)) + if alcMessage, err := rs.allocateResource(ru1, false); err != nil { t.Error(err.Error()) } else { if alcMessage != "ALLOC" { t.Errorf("Wrong allocation message: %v", alcMessage) } } - - if _, err := rs.AllocateResource(ru2, false); err != utils.ErrResourceUnavailable { + if _, err := rs.allocateResource(ru2, false); err != utils.ErrResourceUnavailable { t.Error("Did not receive " + utils.ErrResourceUnavailable.Error() + " error") } - - rs[0].rPrf.Limit = 2 + rs[0].rPrf.Limit = 1 rs[1].rPrf.Limit = 4 - - if alcMessage, err := rs.AllocateResource(ru1, true); err != nil { + if alcMessage, err := rs.allocateResource(ru1, true); err != nil { t.Error(err.Error()) } else { if alcMessage != "RL2" { @@ -234,7 +227,7 @@ func TestRSAllocateResource(t *testing.T) { } } - if alcMessage, err := rs.AllocateResource(ru2, false); err != nil { + if alcMessage, err := rs.allocateResource(ru2, false); err != nil { t.Error(err.Error()) } else { if alcMessage != "RL2" { @@ -243,7 +236,7 @@ func TestRSAllocateResource(t *testing.T) { } ru2.Units = 0 - if _, err := rs.AllocateResource(ru2, false); err == nil { + if _, err := rs.allocateResource(ru2, false); err == nil { t.Error("Duplicate ResourceUsage id should not be allowed") } }