diff --git a/apier/v1/apier_it_test.go b/apier/v1/apier_it_test.go
index c6a1312e8..a492833af 100644
--- a/apier/v1/apier_it_test.go
+++ b/apier/v1/apier_it_test.go
@@ -1273,7 +1273,7 @@ func TestApierResetDataAfterLoadFromFolder(t *testing.T) {
rcvStats.Aliases != 1 ||
rcvStats.ReverseAliases != 2 ||
rcvStats.ResourceProfiles != 3 ||
- rcvStats.Resources != 0 {
+ rcvStats.Resources != 3 {
t.Errorf("Expecting: %+v, received: %+v", expStats, rcvStats)
}
}
diff --git a/apier/v1/resourcesv1.go b/apier/v1/resourcesv1.go
index 812ae545c..db7fb9648 100644
--- a/apier/v1/resourcesv1.go
+++ b/apier/v1/resourcesv1.go
@@ -62,35 +62,31 @@ func (rsv1 *ResourceSV1) Call(serviceMethod string, args interface{}, reply inte
}
// GetResourcesForEvent returns Resources matching a specific event
-func (rsv1 *ResourceSV1) GetResourcesForEvent(ev map[string]interface{}, reply *[]*engine.ResourceProfile) error {
- return rsv1.rls.V1ResourcesForEvent(ev, reply)
+func (rsv1 *ResourceSV1) GetResourcesForEvent(args utils.ArgRSv1ResourceUsage, reply *[]*engine.ResourceProfile) error {
+ return rsv1.rls.V1ResourcesForEvent(args, reply)
}
// AllowUsage checks if there are limits imposed for event
-func (rsv1 *ResourceSV1) AllowUsage(args utils.AttrRLsResourceUsage, allowed *bool) error {
+func (rsv1 *ResourceSV1) AllowUsage(args utils.ArgRSv1ResourceUsage, allowed *bool) error {
return rsv1.rls.V1AllowUsage(args, allowed)
}
// V1InitiateResourceUsage records usage for an event
-func (rsv1 *ResourceSV1) AllocateResource(args utils.AttrRLsResourceUsage, reply *string) error {
+func (rsv1 *ResourceSV1) AllocateResource(args utils.ArgRSv1ResourceUsage, reply *string) error {
return rsv1.rls.V1AllocateResource(args, reply)
}
// V1TerminateResourceUsage releases usage for an event
-func (rsv1 *ResourceSV1) ReleaseResource(args utils.AttrRLsResourceUsage, reply *string) error {
+func (rsv1 *ResourceSV1) ReleaseResource(args utils.ArgRSv1ResourceUsage, reply *string) error {
return rsv1.rls.V1ReleaseResource(args, reply)
}
-type AttrGetResPrf struct {
- ID string
-}
-
// GetResourceProfile returns a resource configuration
-func (apierV1 *ApierV1) GetResourceProfile(attr AttrGetResPrf, reply *engine.ResourceProfile) error {
- if missing := utils.MissingStructFields(&attr, []string{"ID"}); len(missing) != 0 { //Params missing
+func (apierV1 *ApierV1) GetResourceProfile(arg utils.TenantID, reply *engine.ResourceProfile) error {
+ if missing := utils.MissingStructFields(&arg, []string{"Tenant", "ID"}); len(missing) != 0 { //Params missing
return utils.NewErrMandatoryIeMissing(missing...)
}
- if rcfg, err := apierV1.DataDB.GetResourceProfile(attr.ID, true, utils.NonTransactional); err != nil {
+ if rcfg, err := apierV1.DataDB.GetResourceProfile(arg.Tenant, arg.ID, true, utils.NonTransactional); err != nil {
if err.Error() != utils.ErrNotFound.Error() {
err = utils.NewErrServerError(err)
}
@@ -102,11 +98,11 @@ func (apierV1 *ApierV1) GetResourceProfile(attr AttrGetResPrf, reply *engine.Res
}
//SetResourceProfile add a new resource configuration
-func (apierV1 *ApierV1) SetResourceProfile(attr *engine.ResourceProfile, reply *string) error {
- if missing := utils.MissingStructFields(attr, []string{"ID"}); len(missing) != 0 {
+func (apierV1 *ApierV1) SetResourceProfile(res *engine.ResourceProfile, reply *string) error {
+ if missing := utils.MissingStructFields(res, []string{"Tenant", "ID"}); len(missing) != 0 {
return utils.NewErrMandatoryIeMissing(missing...)
}
- if err := apierV1.DataDB.SetResourceProfile(attr, utils.NonTransactional); err != nil {
+ if err := apierV1.DataDB.SetResourceProfile(res, utils.NonTransactional); err != nil {
return utils.APIErrorHandler(err)
}
*reply = utils.OK
@@ -114,11 +110,11 @@ func (apierV1 *ApierV1) SetResourceProfile(attr *engine.ResourceProfile, reply *
}
//RemResourceProfile remove a specific resource configuration
-func (apierV1 *ApierV1) RemResourceProfile(attrs AttrGetResPrf, reply *string) error {
- if missing := utils.MissingStructFields(&attrs, []string{"ID"}); len(missing) != 0 { //Params missing
+func (apierV1 *ApierV1) RemResourceProfile(arg utils.TenantID, reply *string) error {
+ if missing := utils.MissingStructFields(&arg, []string{"Tenant", "ID"}); len(missing) != 0 { //Params missing
return utils.NewErrMandatoryIeMissing(missing...)
}
- if err := apierV1.DataDB.RemoveResourceProfile(attrs.ID, utils.NonTransactional); err != nil {
+ if err := apierV1.DataDB.RemoveResourceProfile(arg.Tenant, arg.ID, utils.NonTransactional); err != nil {
if err.Error() != utils.ErrNotFound.Error() {
err = utils.NewErrServerError(err)
}
diff --git a/apier/v1/resourcesv1_it_test.go b/apier/v1/resourcesv1_it_test.go
index a59a8c52c..cb32d7681 100644
--- a/apier/v1/resourcesv1_it_test.go
+++ b/apier/v1/resourcesv1_it_test.go
@@ -19,7 +19,6 @@ along with this program. If not, see
*/
package v1
-/*
import (
"net/rpc"
"net/rpc/jsonrpc"
@@ -38,7 +37,7 @@ var (
rlsV1Cfg *config.CGRConfig
rlsV1Rpc *rpc.Client
rlsV1ConfDIR string //run tests for specific configuration
- rlsConfig *engine.ResourceCfg
+ rlsConfig *engine.ResourceProfile
resDelay int
)
@@ -49,8 +48,8 @@ var sTestsRLSV1 = []func(t *testing.T){
testV1RsStartEngine,
testV1RsRpcConn,
testV1RsFromFolder,
- testV1RsGetResourcesFromEvent,
- testV1RsAllocateResource,
+ testV1RsGetResourcesForEvent,
+ /*testV1RsAllocateResource,
testV1RsAllowUsage,
testV1RsReleaseResource,
testV1RsGetResourceConfigBeforeSet,
@@ -60,6 +59,7 @@ var sTestsRLSV1 = []func(t *testing.T){
testV1RsGetResourceConfigAfterUpdate,
testV1RsRemResourceCOnfig,
testV1RsGetResourceConfigAfterDelete,
+ */
testV1RsStopEngine,
}
@@ -129,14 +129,16 @@ func testV1RsFromFolder(t *testing.T) {
time.Sleep(time.Duration(1000) * time.Millisecond)
}
-func testV1RsGetResourcesFromEvent(t *testing.T) {
- var reply *[]*engine.ResourceCfg
- ev := map[string]interface{}{"Unknown": "unknown"}
- if err := rlsV1Rpc.Call("ResourceSV1.GetResourcesForEvent", ev, &reply); err == nil || err.Error() != utils.ErrNotFound.Error() {
+func testV1RsGetResourcesForEvent(t *testing.T) {
+ var reply *[]*engine.ResourceProfile
+ args := &utils.ArgRSv1ResourceUsage{
+ Tenant: "cgrates.org",
+ Event: map[string]interface{}{"Unknown": "unknown"}}
+ if err := rlsV1Rpc.Call("ResourceSV1.GetResourcesForEvent", args, &reply); err == nil || err.Error() != utils.ErrNotFound.Error() {
t.Error(err)
}
- ev = map[string]interface{}{"Destination": "10"}
- if err := rlsV1Rpc.Call("ResourceSV1.GetResourcesForEvent", ev, &reply); err != nil {
+ args.Event = map[string]interface{}{"Destination": "10"}
+ if err := rlsV1Rpc.Call("ResourceSV1.GetResourcesForEvent", args, &reply); err != nil {
t.Error(err)
}
if len(*reply) != 1 {
@@ -146,21 +148,21 @@ func testV1RsGetResourcesFromEvent(t *testing.T) {
t.Errorf("Expecting: %+v, received: %+v", "ResGroup2", (*reply)[0].ID)
}
- ev = map[string]interface{}{"Destination": "20"}
- if err := rlsV1Rpc.Call("ResourceSV1.GetResourcesForEvent", ev, &reply); err == nil || err.Error() != utils.ErrNotFound.Error() {
+ args.Event = map[string]interface{}{"Destination": "20"}
+ if err := rlsV1Rpc.Call("ResourceSV1.GetResourcesForEvent", args, &reply); err == nil || err.Error() != utils.ErrNotFound.Error() {
t.Error(err)
}
- ev = map[string]interface{}{"Account": "1002", "Subject": "test", "Destination": "1002"}
- if err := rlsV1Rpc.Call("ResourceSV1.GetResourcesForEvent", ev, &reply); err != nil {
+ args.Event = map[string]interface{}{"Account": "1002", "Subject": "test", "Destination": "1002"}
+ if err := rlsV1Rpc.Call("ResourceSV1.GetResourcesForEvent", args, &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("ResourceSV1.GetResourcesForEvent", ev, &reply); err != nil {
+ args.Event = map[string]interface{}{"Account": "1002", "Subject": "test", "Destination": "1001"}
+ if err := rlsV1Rpc.Call("ResourceSV1.GetResourcesForEvent", args, &reply); err != nil {
t.Error(err)
}
if len(*reply) != 1 {
@@ -174,7 +176,7 @@ func testV1RsGetResourcesFromEvent(t *testing.T) {
func testV1RsAllocateResource(t *testing.T) {
var reply string
- attrRU := utils.AttrRLsResourceUsage{
+ attrRU := utils.ArgRSv1ResourceUsage{
UsageID: "651a8db2-4f67-4cf8-b622-169e8a482e51",
Event: map[string]interface{}{"Account": "1002", "Subject": "1001", "Destination": "1002"},
Units: 3,
@@ -188,7 +190,7 @@ func testV1RsAllocateResource(t *testing.T) {
time.Sleep(time.Duration(1000) * time.Millisecond)
- attrRU = utils.AttrRLsResourceUsage{
+ attrRU = utils.ArgRSv1ResourceUsage{
UsageID: "651a8db2-4f67-4cf8-b622-169e8a482e52",
Event: map[string]interface{}{"Destination": "100"},
Units: 5,
@@ -202,7 +204,7 @@ func testV1RsAllocateResource(t *testing.T) {
time.Sleep(time.Duration(1000) * time.Millisecond)
- attrRU = utils.AttrRLsResourceUsage{
+ attrRU = utils.ArgRSv1ResourceUsage{
UsageID: "651a8db2-4f67-4cf8-b622-169e8a482e53",
Event: map[string]interface{}{"Account": "1002", "Subject": "1001", "Destination": "1002"},
Units: 3,
@@ -218,7 +220,7 @@ func testV1RsAllocateResource(t *testing.T) {
func testV1RsAllowUsage(t *testing.T) {
var allowed bool
- attrRU := utils.AttrRLsResourceUsage{
+ attrRU := utils.ArgRSv1ResourceUsage{
UsageID: "651a8db2-4f67-4cf8-b622-169e8a482e51",
Event: map[string]interface{}{"Account": "1002", "Subject": "1001", "Destination": "1002"},
Units: 1,
@@ -229,7 +231,7 @@ func testV1RsAllowUsage(t *testing.T) {
t.Errorf("Expecting: %+v, received: %+v", true, allowed)
}
- attrRU = utils.AttrRLsResourceUsage{
+ attrRU = utils.ArgRSv1ResourceUsage{
UsageID: "651a8db2-4f67-4cf8-b622-169e8a482e51",
Event: map[string]interface{}{"Account": "1002", "Subject": "1001", "Destination": "1002"},
Units: 2,
@@ -243,7 +245,7 @@ func testV1RsAllowUsage(t *testing.T) {
func testV1RsReleaseResource(t *testing.T) {
var reply string
- attrRU := utils.AttrRLsResourceUsage{
+ attrRU := utils.ArgRSv1ResourceUsage{
UsageID: "651a8db2-4f67-4cf8-b622-169e8a482e52",
Event: map[string]interface{}{"Destination": "100"},
Units: 2,
@@ -267,13 +269,14 @@ func testV1RsReleaseResource(t *testing.T) {
func testV1RsGetResourceConfigBeforeSet(t *testing.T) {
var reply *string
- if err := rlsV1Rpc.Call("ApierV1.GetResourceConfig", &AttrGetResCfg{ID: "RCFG1"}, &reply); err == nil || err.Error() != utils.ErrNotFound.Error() {
+ if err := rlsV1Rpc.Call("ApierV1.GetResourceConfig", &utils.TenantID{Tenant: "cgrates.org", ID: "RCFG1"},
+ &reply); err == nil || err.Error() != utils.ErrNotFound.Error() {
t.Error(err)
}
}
func testV1RsSetResourceConfig(t *testing.T) {
- rlsConfig = &engine.ResourceCfg{
+ rlsConfig = &engine.ResourceProfile{
ID: "RCFG1",
Filters: []*engine.RequestFilter{
&engine.RequestFilter{
@@ -303,8 +306,9 @@ func testV1RsSetResourceConfig(t *testing.T) {
}
func testV1RsGetResourceConfigAfterSet(t *testing.T) {
- var reply *engine.ResourceCfg
- if err := rlsV1Rpc.Call("ApierV1.GetResourceConfig", &AttrGetResCfg{ID: rlsConfig.ID}, &reply); err != nil {
+ var reply *engine.ResourceProfile
+ if err := rlsV1Rpc.Call("ApierV1.GetResourceConfig",
+ &utils.TenantID{Tenant: "cgrates.org", ID: rlsConfig.ID}, &reply); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(reply, rlsConfig) {
t.Errorf("Expecting: %+v, received: %+v", rlsConfig, reply)
@@ -333,8 +337,9 @@ func testV1RsUpdateResourceConfig(t *testing.T) {
}
func testV1RsGetResourceConfigAfterUpdate(t *testing.T) {
- var reply *engine.ResourceCfg
- if err := rlsV1Rpc.Call("ApierV1.GetResourceConfig", &AttrGetResCfg{ID: rlsConfig.ID}, &reply); err != nil {
+ var reply *engine.ResourceProfile
+ if err := rlsV1Rpc.Call("ApierV1.GetResourceConfig",
+ &utils.TenantID{Tenant: "cgrates.org", ID: rlsConfig.ID}, &reply); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(reply, rlsConfig) {
t.Errorf("Expecting: %+v, received: %+v", rlsConfig, reply)
@@ -343,7 +348,8 @@ func testV1RsGetResourceConfigAfterUpdate(t *testing.T) {
func testV1RsRemResourceCOnfig(t *testing.T) {
var resp string
- if err := rlsV1Rpc.Call("ApierV1.RemResourceConfig", &AttrGetResCfg{ID: rlsConfig.ID}, &resp); err != nil {
+ if err := rlsV1Rpc.Call("ApierV1.RemResourceConfig",
+ &utils.TenantID{Tenant: "cgrates.org", ID: rlsConfig.ID}, &resp); err != nil {
t.Error(err)
} else if resp != utils.OK {
t.Error("Unexpected reply returned", resp)
@@ -352,7 +358,8 @@ func testV1RsRemResourceCOnfig(t *testing.T) {
func testV1RsGetResourceConfigAfterDelete(t *testing.T) {
var reply *string
- if err := rlsV1Rpc.Call("ApierV1.GetResourceConfig", &AttrGetResCfg{ID: "RCFG1"}, &reply); err == nil || err.Error() != utils.ErrNotFound.Error() {
+ if err := rlsV1Rpc.Call("ApierV1.GetResourceConfig",
+ &utils.TenantID{Tenant: "cgrates.org", ID: "RCFG1"}, &reply); err == nil || err.Error() != utils.ErrNotFound.Error() {
t.Error(err)
}
}
@@ -362,4 +369,3 @@ func testV1RsStopEngine(t *testing.T) {
t.Error(err)
}
}
-*/
diff --git a/apier/v1/smgenericv1_it_test.go b/apier/v1/smgenericv1_it_test.go
index ba5a9d5af..3af5ae6e1 100644
--- a/apier/v1/smgenericv1_it_test.go
+++ b/apier/v1/smgenericv1_it_test.go
@@ -102,7 +102,7 @@ func TestSMGV1CacheStats(t *testing.T) {
var rcvStats *utils.CacheStats
expectedStats := &utils.CacheStats{Destinations: 5, ReverseDestinations: 7, RatingPlans: 4, RatingProfiles: 9,
Actions: 8, ActionPlans: 4, AccountActionPlans: 5, SharedGroups: 1, DerivedChargers: 1,
- LcrProfiles: 5, CdrStats: 6, Users: 3, Aliases: 1, ReverseAliases: 2, ResourceProfiles: 3, Resources: 0}
+ LcrProfiles: 5, CdrStats: 6, Users: 3, Aliases: 1, ReverseAliases: 2, ResourceProfiles: 3, Resources: 3}
var args utils.AttrCacheStats
if err := smgV1Rpc.Call("ApierV1.GetCacheStats", args, &rcvStats); err != nil {
t.Error("Got error on ApierV1.GetCacheStats: ", err.Error())
diff --git a/apier/v1/tpaccountactions.go b/apier/v1/tpaccountactions.go
index 796d49b7d..87045899e 100644
--- a/apier/v1/tpaccountactions.go
+++ b/apier/v1/tpaccountactions.go
@@ -51,9 +51,10 @@ func (self *ApierV1) GetTPAccountActionsByLoadId(attrs utils.TPAccountActions, r
return utils.NewErrMandatoryIeMissing(missing...)
}
if aas, err := self.StorDb.GetTPAccountActions(&attrs); err != nil {
- return utils.NewErrServerError(err)
- } else if len(aas) == 0 {
- return utils.ErrNotFound
+ if err.Error() != utils.ErrNotFound.Error() {
+ err = utils.NewErrServerError(err)
+ }
+ return err
} else {
reply = &aas
}
@@ -75,9 +76,10 @@ func (self *ApierV1) GetTPAccountActions(attrs AttrGetTPAccountActions, reply *u
return err
}
if aas, err := self.StorDb.GetTPAccountActions(filter); err != nil {
- return utils.NewErrServerError(err)
- } else if len(aas) == 0 {
- return utils.ErrNotFound
+ if err.Error() != utils.ErrNotFound.Error() {
+ err = utils.NewErrServerError(err)
+ }
+ return err
} else {
*reply = *aas[0]
}
diff --git a/apier/v1/tpaccountactions_it_test.go b/apier/v1/tpaccountactions_it_test.go
new file mode 100644
index 000000000..d0dbe4a18
--- /dev/null
+++ b/apier/v1/tpaccountactions_it_test.go
@@ -0,0 +1,212 @@
+// +build offline_tp
+
+/*
+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 (
+ "github.com/cgrates/cgrates/config"
+ "github.com/cgrates/cgrates/engine"
+ "github.com/cgrates/cgrates/utils"
+ "net/rpc"
+ "net/rpc/jsonrpc"
+ "path"
+ "reflect"
+ "testing"
+)
+
+var (
+ tpAccActionsCfgPath string
+ tpAccActionsCfg *config.CGRConfig
+ tpAccActionsRPC *rpc.Client
+ tpAccActionsDataDir = "/usr/share/cgrates"
+ tpAccActions *utils.TPAccountActions
+ tpAccActionsDelay int
+ tpAccActionsConfigDIR string //run tests for specific configuration
+ tpAccActionID = "ID:cgrates.org:1001"
+)
+
+var sTestsTPAccActions = []func(t *testing.T){
+ testTPAccActionsInitCfg,
+ testTPAccActionsResetStorDb,
+ testTPAccActionsStartEngine,
+ testTPAccActionsRpcConn,
+ testTPAccActionsGetTPAccActionBeforeSet,
+ testTPAccActionsSetTPAccAction,
+ testTPAccActionsGetTPAccActionAfterSet,
+ testTPAccActionsGetTPAccActionIds,
+ testTPAccActionsUpdateTPAccAction,
+ testTPAccActionsGetTPAccActionAfterUpdate,
+ testTPAccActionsRemTPAccAction,
+ testTPAccActionsGetTPAccActionAfterRemove,
+ testTPAccActionsKillEngine,
+}
+
+//Test start here
+func TestTPAccActionsITMySql(t *testing.T) {
+ tpAccActionsConfigDIR = "tutmysql"
+ for _, stest := range sTestsTPAccActions {
+ t.Run(tpAccActionsConfigDIR, stest)
+ }
+}
+
+func TestTPAccActionsITMongo(t *testing.T) {
+ tpAccActionsConfigDIR = "tutmongo"
+ for _, stest := range sTestsTPAccActions {
+ t.Run(tpAccActionsConfigDIR, stest)
+ }
+}
+
+func TestTTPAccActionsITPG(t *testing.T) {
+ tpAccActionsConfigDIR = "tutpostgres"
+ for _, stest := range sTestsTPAccActions {
+ t.Run(tpAccActionsConfigDIR, stest)
+ }
+}
+
+func testTPAccActionsInitCfg(t *testing.T) {
+ var err error
+ tpAccActionsCfgPath = path.Join(tpAccActionsDataDir, "conf", "samples", tpAccActionsConfigDIR)
+ tpAccActionsCfg, err = config.NewCGRConfigFromFolder(tpAccActionsCfgPath)
+ if err != nil {
+ t.Error(err)
+ }
+ tpAccActionsCfg.DataFolderPath = tpAccActionsDataDir // Share DataFolderPath through config towards StoreDb for Flush()
+ config.SetCgrConfig(tpAccActionsCfg)
+ switch tpAccActionsConfigDIR {
+ case "tutmongo": // Mongo needs more time to reset db, need to investigate
+ tpAccActionsDelay = 2000
+ default:
+ tpAccActionsDelay = 1000
+ }
+}
+
+// Wipe out the cdr database
+func testTPAccActionsResetStorDb(t *testing.T) {
+ if err := engine.InitStorDb(tpAccActionsCfg); err != nil {
+ t.Fatal(err)
+ }
+}
+
+// Start CGR Engine
+func testTPAccActionsStartEngine(t *testing.T) {
+ if _, err := engine.StopStartEngine(tpAccActionsCfgPath, tpAccActionsDelay); err != nil {
+ t.Fatal(err)
+ }
+}
+
+// Connect rpc client to rater
+func testTPAccActionsRpcConn(t *testing.T) {
+ var err error
+ tpAccActionsRPC, err = jsonrpc.Dial("tcp", tpAccActionsCfg.RPCJSONListen) // We connect over JSON so we can also troubleshoot if needed
+ if err != nil {
+ t.Fatal(err)
+ }
+}
+
+func testTPAccActionsGetTPAccActionBeforeSet(t *testing.T) {
+ var reply *utils.TPAccountActions
+ if err := tpAccActionsRPC.Call("ApierV1.GetTPAccountActions", &AttrGetTPAccountActions{TPid: "TPAcc", AccountActionsId: tpAccActionID}, &reply); err == nil || err.Error() != utils.ErrNotFound.Error() {
+ t.Error(err)
+ }
+
+}
+
+func testTPAccActionsSetTPAccAction(t *testing.T) {
+ tpAccActions = &utils.TPAccountActions{
+ TPid: "TPAcc",
+ LoadId: "ID",
+ Tenant: "cgrates.org",
+ Account: "1001",
+ ActionPlanId: "PREPAID_10",
+ ActionTriggersId: "STANDARD_TRIGGERS",
+ AllowNegative: true,
+ Disabled: false,
+ }
+ var result string
+ if err := tpAccActionsRPC.Call("ApierV1.SetTPAccountActions", tpAccActions, &result); err != nil {
+ t.Error(err)
+ } else if result != utils.OK {
+ t.Error("Unexpected reply returned", result)
+ }
+}
+
+func testTPAccActionsGetTPAccActionAfterSet(t *testing.T) {
+ var reply *utils.TPAccountActions
+ if err := tpAccActionsRPC.Call("ApierV1.GetTPAccountActions", &AttrGetTPAccountActions{TPid: "TPAcc", AccountActionsId: tpAccActionID}, &reply); err != nil {
+ t.Error(err)
+ } else if !reflect.DeepEqual(tpAccActions, reply) {
+ t.Errorf("Expecting : %+v, received: %+v", tpAccActions, reply)
+ }
+}
+
+func testTPAccActionsGetTPAccActionIds(t *testing.T) {
+ var result []string
+ expectedTPID := []string{"ID"}
+ if err := tpAccActionsRPC.Call("ApierV1.GetTPAccountActionLoadIds", &AttrGetTPAccountActionIds{TPid: "TPAcc"}, &result); err != nil {
+ t.Error(err)
+ } else if !reflect.DeepEqual(expectedTPID, result) {
+ t.Errorf("Expecting: %+v, received: %+v", expectedTPID, result)
+ }
+
+}
+
+func testTPAccActionsUpdateTPAccAction(t *testing.T) {
+ tpAccActions.ActionPlanId = "PlanOne"
+ var result string
+ if err := tpAccActionsRPC.Call("ApierV1.SetTPAccountActions", tpAccActions, &result); err != nil {
+ t.Error(err)
+ } else if result != utils.OK {
+ t.Error("Unexpected reply returned", result)
+ }
+
+}
+
+func testTPAccActionsGetTPAccActionAfterUpdate(t *testing.T) {
+ var reply *utils.TPAccountActions
+ if err := tpAccActionsRPC.Call("ApierV1.GetTPAccountActions", &AttrGetTPAccountActions{TPid: "TPAcc", AccountActionsId: tpAccActionID}, &reply); err != nil {
+ t.Error(err)
+ } else if !reflect.DeepEqual(tpAccActions, reply) {
+ t.Errorf("Expecting : %+v, received: %+v", tpAccActions, reply)
+ }
+
+}
+
+func testTPAccActionsRemTPAccAction(t *testing.T) {
+ var resp string
+ if err := tpAccActionsRPC.Call("ApierV1.RemTPAccountActions", &AttrGetTPAccountActions{TPid: "TPAcc", AccountActionsId: tpAccActionID}, &resp); err != nil {
+ t.Error(err)
+ } else if resp != utils.OK {
+ t.Error("Unexpected reply returned", resp)
+ }
+
+}
+
+func testTPAccActionsGetTPAccActionAfterRemove(t *testing.T) {
+ var reply *utils.TPAccountActions
+ if err := tpAccActionsRPC.Call("ApierV1.GetTPAccountActions", &AttrGetTPAccountActions{TPid: "TPAcc", AccountActionsId: tpAccActionID}, &reply); err == nil || err.Error() != utils.ErrNotFound.Error() {
+ t.Error(err)
+ }
+}
+
+func testTPAccActionsKillEngine(t *testing.T) {
+ if err := engine.KillEngine(tpDestinationDelay); err != nil {
+ t.Error(err)
+ }
+}
diff --git a/apier/v1/tpactionplans.go b/apier/v1/tpactionplans.go
index a94951298..e28e57ace 100644
--- a/apier/v1/tpactionplans.go
+++ b/apier/v1/tpactionplans.go
@@ -53,9 +53,10 @@ func (self *ApierV1) GetTPActionPlan(attrs AttrGetTPActionPlan, reply *utils.TPA
return utils.NewErrMandatoryIeMissing(missing...)
}
if aps, err := self.StorDb.GetTPActionPlans(attrs.TPid, attrs.ID); err != nil {
- return utils.NewErrServerError(err)
- } else if len(aps) == 0 {
- return utils.ErrNotFound
+ if err.Error() != utils.ErrNotFound.Error() {
+ err = utils.NewErrServerError(err)
+ }
+ return err
} else {
*reply = *aps[0]
}
diff --git a/apier/v1/tpactionplans_it_test.go b/apier/v1/tpactionplans_it_test.go
new file mode 100644
index 000000000..05b594159
--- /dev/null
+++ b/apier/v1/tpactionplans_it_test.go
@@ -0,0 +1,241 @@
+// +build offline_tp
+
+/*
+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 (
+ "github.com/cgrates/cgrates/config"
+ "github.com/cgrates/cgrates/engine"
+ "github.com/cgrates/cgrates/utils"
+ "net/rpc"
+ "net/rpc/jsonrpc"
+ "path"
+ "reflect"
+ "testing"
+)
+
+var (
+ tpAccPlansCfgPath string
+ tpAccPlansCfg *config.CGRConfig
+ tpAccPlansRPC *rpc.Client
+ tpAccPlansDataDir = "/usr/share/cgrates"
+ tpAccPlan *utils.TPActionPlan
+ tpAccPlansDelay int
+ tpAccPlansConfigDIR string //run tests for specific configuration
+
+)
+
+var sTestsTPAccPlans = []func(t *testing.T){
+ testTPAccPlansInitCfg,
+ testTPAccPlansResetStorDb,
+ testTPAccPlansStartEngine,
+ testTPAccPlansRpcConn,
+ testTPAccPlansGetTPAccPlanBeforeSet,
+ testTPAccPlansSetTPAccPlan,
+ testTPAccPlansGetTPAccPlanAfterSet,
+ testTPAccPlansGetTPAccPlanIds,
+ testTPAccPlansUpdateTPAccPlan,
+ testTPAccPlansGetTPAccPlanAfterUpdate,
+ testTPAccPlansRemTPAccPlan,
+ testTPAccPlansGetTPAccPlanAfterRemove,
+ testTPAccPlansKillEngine,
+}
+
+//Test start here
+func TestTPAccPlansITMySql(t *testing.T) {
+ tpAccPlansConfigDIR = "tutmysql"
+ for _, stest := range sTestsTPAccPlans {
+ t.Run(tpAccPlansConfigDIR, stest)
+ }
+}
+
+func TestTPAccPlansITMongo(t *testing.T) {
+ tpAccPlansConfigDIR = "tutmongo"
+ for _, stest := range sTestsTPAccPlans {
+ t.Run(tpAccPlansConfigDIR, stest)
+ }
+}
+
+func TestTPAccPlansITPG(t *testing.T) {
+ tpAccPlansConfigDIR = "tutpostgres"
+ for _, stest := range sTestsTPAccPlans {
+ t.Run(tpAccPlansConfigDIR, stest)
+ }
+}
+
+func testTPAccPlansInitCfg(t *testing.T) {
+ var err error
+ tpAccPlansCfgPath = path.Join(tpAccPlansDataDir, "conf", "samples", tpAccPlansConfigDIR)
+ tpAccPlansCfg, err = config.NewCGRConfigFromFolder(tpAccPlansCfgPath)
+ if err != nil {
+ t.Error(err)
+ }
+ tpAccPlansCfg.DataFolderPath = tpAccPlansDataDir // Share DataFolderPath through config towards StoreDb for Flush()
+ config.SetCgrConfig(tpAccPlansCfg)
+ switch tpAccPlansConfigDIR {
+ case "tutmongo": // Mongo needs more time to reset db, need to investigate
+ tpAccPlansDelay = 2000
+ default:
+ tpAccPlansDelay = 1000
+ }
+}
+
+// Wipe out the cdr database
+func testTPAccPlansResetStorDb(t *testing.T) {
+ if err := engine.InitStorDb(tpAccPlansCfg); err != nil {
+ t.Fatal(err)
+ }
+}
+
+// Start CGR Engine
+func testTPAccPlansStartEngine(t *testing.T) {
+ if _, err := engine.StopStartEngine(tpAccPlansCfgPath, tpAccPlansDelay); err != nil {
+ t.Fatal(err)
+ }
+}
+
+// Connect rpc client to rater
+func testTPAccPlansRpcConn(t *testing.T) {
+ var err error
+ tpAccPlansRPC, err = jsonrpc.Dial("tcp", tpAccPlansCfg.RPCJSONListen) // We connect over JSON so we can also troubleshoot if needed
+ if err != nil {
+ t.Fatal(err)
+ }
+}
+
+func testTPAccPlansGetTPAccPlanBeforeSet(t *testing.T) {
+ var reply *utils.TPActionPlan
+ if err := tpAccPlansRPC.Call("ApierV1.GetTPActionPlan", &AttrGetTPActionPlan{TPid: "TPAcc", ID: "ID"}, &reply); err == nil || err.Error() != utils.ErrNotFound.Error() {
+ t.Error(err)
+ }
+}
+
+func testTPAccPlansSetTPAccPlan(t *testing.T) {
+ tpAccPlan = &utils.TPActionPlan{
+ TPid: "TPAcc",
+ ID: "ID",
+ ActionPlan: []*utils.TPActionTiming{
+ &utils.TPActionTiming{
+ ActionsId: "AccId",
+ TimingId: "TimingID",
+ Weight: 10,
+ },
+ &utils.TPActionTiming{
+ ActionsId: "AccId2",
+ TimingId: "TimingID2",
+ Weight: 11,
+ },
+ },
+ }
+ var result string
+ if err := tpAccPlansRPC.Call("ApierV1.SetTPActionPlan", tpAccPlan, &result); err != nil {
+ t.Error(err)
+ } else if result != utils.OK {
+ t.Error("Unexpected reply returned", result)
+ }
+}
+
+func testTPAccPlansGetTPAccPlanAfterSet(t *testing.T) {
+ var reply *utils.TPActionPlan
+ if err := tpAccPlansRPC.Call("ApierV1.GetTPActionPlan", &AttrGetTPActionPlan{TPid: "TPAcc", ID: "ID"}, &reply); err != nil {
+ t.Error(err)
+ } else if !reflect.DeepEqual(tpAccPlan.TPid, reply.TPid) {
+ t.Errorf("Expecting : %+v, received: %+v", tpAccPlan.TPid, reply.TPid)
+ } else if !reflect.DeepEqual(tpAccPlan.ID, reply.ID) {
+ t.Errorf("Expecting : %+v, received: %+v", tpAccPlan.ID, reply.ID)
+ } else if !reflect.DeepEqual(len(tpAccPlan.ActionPlan), len(reply.ActionPlan)) {
+ t.Errorf("Expecting : %+v, received: %+v", len(tpAccPlan.ActionPlan), len(reply.ActionPlan))
+ }
+}
+
+func testTPAccPlansGetTPAccPlanIds(t *testing.T) {
+ var result []string
+ expectedTPID := []string{"ID"}
+ if err := tpAccPlansRPC.Call("ApierV1.GetTPActionPlanIds", &AttrGetTPActionPlanIds{TPid: "TPAcc"}, &result); err != nil {
+ t.Error(err)
+ } else if !reflect.DeepEqual(expectedTPID, result) {
+ t.Errorf("Expecting: %+v, received: %+v", expectedTPID, result)
+ }
+
+}
+
+func testTPAccPlansUpdateTPAccPlan(t *testing.T) {
+ tpAccPlan.ActionPlan = []*utils.TPActionTiming{
+ &utils.TPActionTiming{
+ ActionsId: "AccId",
+ TimingId: "TimingID",
+ Weight: 10,
+ },
+ &utils.TPActionTiming{
+ ActionsId: "AccId2",
+ TimingId: "TimingID2",
+ Weight: 11,
+ },
+ &utils.TPActionTiming{
+ ActionsId: "AccId3",
+ TimingId: "TimingID3",
+ Weight: 12,
+ },
+ }
+ var result string
+ if err := tpAccPlansRPC.Call("ApierV1.SetTPActionPlan", tpAccPlan, &result); err != nil {
+ t.Error(err)
+ } else if result != utils.OK {
+ t.Error("Unexpected reply returned", result)
+ }
+
+}
+
+func testTPAccPlansGetTPAccPlanAfterUpdate(t *testing.T) {
+ var reply *utils.TPActionPlan
+ if err := tpAccPlansRPC.Call("ApierV1.GetTPActionPlan", &AttrGetTPActionPlan{TPid: "TPAcc", ID: "ID"}, &reply); err != nil {
+ t.Error(err)
+ } else if !reflect.DeepEqual(tpAccPlan.TPid, reply.TPid) {
+ t.Errorf("Expecting : %+v, received: %+v", tpAccPlan.TPid, reply.TPid)
+ } else if !reflect.DeepEqual(tpAccPlan.ID, reply.ID) {
+ t.Errorf("Expecting : %+v, received: %+v", tpAccPlan.ID, reply.ID)
+ } else if !reflect.DeepEqual(len(tpAccPlan.ActionPlan), len(reply.ActionPlan)) {
+ t.Errorf("Expecting : %+v, received: %+v", len(tpAccPlan.ActionPlan), len(reply.ActionPlan))
+ }
+
+}
+
+func testTPAccPlansRemTPAccPlan(t *testing.T) {
+ var resp string
+ if err := tpAccPlansRPC.Call("ApierV1.RemTPActionPlan", &AttrGetTPActionPlan{TPid: "TPAcc", ID: "ID"}, &resp); err != nil {
+ t.Error(err)
+ } else if resp != utils.OK {
+ t.Error("Unexpected reply returned", resp)
+ }
+
+}
+
+func testTPAccPlansGetTPAccPlanAfterRemove(t *testing.T) {
+ var reply *utils.TPActionPlan
+ if err := tpAccPlansRPC.Call("ApierV1.GetTPActionPlan", &AttrGetTPActionPlan{TPid: "TPAcc", ID: "ID"}, &reply); err == nil || err.Error() != utils.ErrNotFound.Error() {
+ t.Error(err)
+ }
+}
+
+func testTPAccPlansKillEngine(t *testing.T) {
+ if err := engine.KillEngine(tpAccPlansDelay); err != nil {
+ t.Error(err)
+ }
+}
diff --git a/apier/v1/tpactions.go b/apier/v1/tpactions.go
index b141ba93d..8615f6b52 100644
--- a/apier/v1/tpactions.go
+++ b/apier/v1/tpactions.go
@@ -45,9 +45,10 @@ func (self *ApierV1) GetTPActions(attrs AttrGetTPActions, reply *utils.TPActions
return utils.NewErrMandatoryIeMissing(missing...)
}
if as, err := self.StorDb.GetTPActions(attrs.TPid, attrs.ID); err != nil {
- return utils.NewErrServerError(err)
- } else if len(as) == 0 {
- return utils.ErrNotFound
+ if err.Error() != utils.ErrNotFound.Error() {
+ err = utils.NewErrServerError(err)
+ }
+ return err
} else {
*reply = *as[0]
}
diff --git a/apier/v1/tpactions_it_test.go b/apier/v1/tpactions_it_test.go
new file mode 100644
index 000000000..4eac031b1
--- /dev/null
+++ b/apier/v1/tpactions_it_test.go
@@ -0,0 +1,315 @@
+// +build offline_tp
+
+/*
+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 (
+ "github.com/cgrates/cgrates/config"
+ "github.com/cgrates/cgrates/engine"
+ "github.com/cgrates/cgrates/utils"
+ "net/rpc"
+ "net/rpc/jsonrpc"
+ "path"
+ "reflect"
+ "testing"
+)
+
+var (
+ tpActionCfgPath string
+ tpActionCfg *config.CGRConfig
+ tpActionRPC *rpc.Client
+ tpActionDataDir = "/usr/share/cgrates"
+ tpActions *utils.TPActions
+ tpActionDelay int
+ tpActionConfigDIR string //run tests for specific configuration
+
+)
+
+var sTestsTPActions = []func(t *testing.T){
+ testTPActionsInitCfg,
+ testTPActionsResetStorDb,
+ testTPActionsStartEngine,
+ testTPActionsRpcConn,
+ testTPActionsGetTPActionBeforeSet,
+ testTPActionsSetTPAction,
+ testTPActionsGetTPActionAfterSet,
+ testTPActionsGetTPActionIds,
+ testTPActionsUpdateTPAction,
+ testTPActionsGetTPActionAfterUpdate,
+ testTPActionsRemTPAction,
+ testTPActionsGetTPActionAfterRemove,
+ testTPActionsKillEngine,
+}
+
+//Test start here
+func TestTPActionsITMySql(t *testing.T) {
+ tpActionConfigDIR = "tutmysql"
+ for _, stest := range sTestsTPActions {
+ t.Run(tpActionConfigDIR, stest)
+ }
+}
+
+func TestTPActionsITMongo(t *testing.T) {
+ tpActionConfigDIR = "tutmongo"
+ for _, stest := range sTestsTPActions {
+ t.Run(tpActionConfigDIR, stest)
+ }
+}
+
+func TestTPActionsITPG(t *testing.T) {
+ tpActionConfigDIR = "tutpostgres"
+ for _, stest := range sTestsTPActions {
+ t.Run(tpActionConfigDIR, stest)
+ }
+}
+
+func testTPActionsInitCfg(t *testing.T) {
+ var err error
+ tpActionCfgPath = path.Join(tpActionDataDir, "conf", "samples", tpActionConfigDIR)
+ tpActionCfg, err = config.NewCGRConfigFromFolder(tpActionCfgPath)
+ if err != nil {
+ t.Error(err)
+ }
+ tpActionCfg.DataFolderPath = tpActionDataDir // Share DataFolderPath through config towards StoreDb for Flush()
+ config.SetCgrConfig(tpActionCfg)
+ switch tpActionConfigDIR {
+ case "tutmongo": // Mongo needs more time to reset db, need to investigate
+ tpActionDelay = 2000
+ default:
+ tpActionDelay = 1000
+ }
+}
+
+// Wipe out the cdr database
+func testTPActionsResetStorDb(t *testing.T) {
+ if err := engine.InitStorDb(tpActionCfg); err != nil {
+ t.Fatal(err)
+ }
+}
+
+// Start CGR Engine
+func testTPActionsStartEngine(t *testing.T) {
+ if _, err := engine.StopStartEngine(tpActionCfgPath, tpActionDelay); err != nil {
+ t.Fatal(err)
+ }
+}
+
+// Connect rpc client to rater
+func testTPActionsRpcConn(t *testing.T) {
+ var err error
+ tpActionRPC, err = jsonrpc.Dial("tcp", tpActionCfg.RPCJSONListen) // We connect over JSON so we can also troubleshoot if needed
+ if err != nil {
+ t.Fatal(err)
+ }
+}
+
+func testTPActionsGetTPActionBeforeSet(t *testing.T) {
+ var reply *utils.TPActionPlan
+ if err := tpActionRPC.Call("ApierV1.GetTPActions", &AttrGetTPActions{TPid: "TPAcc", ID: "ID"}, &reply); err == nil || err.Error() != utils.ErrNotFound.Error() {
+ t.Error(err)
+ }
+}
+
+func testTPActionsSetTPAction(t *testing.T) {
+ tpActions = &utils.TPActions{
+ TPid: "TPAcc",
+ ID: "ID",
+ Actions: []*utils.TPAction{
+ &utils.TPAction{
+ Identifier: "*topup_reset",
+ BalanceId: "BalID",
+ BalanceUuid: "BalUuid",
+ BalanceType: "*data",
+ Directions: "*out",
+ Units: "10",
+ ExpiryTime: "*unlimited",
+ Filter: "",
+ TimingTags: "2014-01-14T00:00:00Z",
+ DestinationIds: "DST_1002",
+ RatingSubject: "SPECIAL_1002",
+ Categories: "",
+ SharedGroups: "SHARED_A",
+ BalanceWeight: "10",
+ ExtraParameters: "",
+ BalanceBlocker: "false",
+ BalanceDisabled: "false",
+ Weight: 10,
+ },
+ &utils.TPAction{
+ Identifier: "*log",
+ BalanceId: "BalID",
+ BalanceUuid: "BalUuid",
+ BalanceType: "*monetary",
+ Directions: "*out",
+ Units: "120",
+ ExpiryTime: "*unlimited",
+ Filter: "",
+ TimingTags: "2014-01-14T00:00:00Z",
+ DestinationIds: "*any",
+ RatingSubject: "SPECIAL_1002",
+ Categories: "",
+ SharedGroups: "SHARED_A",
+ BalanceWeight: "11",
+ ExtraParameters: "",
+ BalanceBlocker: "false",
+ BalanceDisabled: "false",
+ Weight: 11,
+ },
+ },
+ }
+ var result string
+ if err := tpActionRPC.Call("ApierV1.SetTPActions", tpActions, &result); err != nil {
+ t.Error(err)
+ } else if result != utils.OK {
+ t.Error("Unexpected reply returned", result)
+ }
+}
+
+func testTPActionsGetTPActionAfterSet(t *testing.T) {
+ var reply *utils.TPActions
+ if err := tpActionRPC.Call("ApierV1.GetTPActions", &AttrGetTPActions{TPid: "TPAcc", ID: "ID"}, &reply); err != nil {
+ t.Error(err)
+ } else if !reflect.DeepEqual(tpActions.TPid, reply.TPid) {
+ t.Errorf("Expecting : %+v, received: %+v", tpActions.TPid, reply.TPid)
+ } else if !reflect.DeepEqual(tpActions.ID, reply.ID) {
+ t.Errorf("Expecting : %+v, received: %+v", tpActions.ID, reply.ID)
+ } else if !reflect.DeepEqual(len(tpActions.Actions), len(reply.Actions)) {
+ t.Errorf("Expecting : %+v, received: %+v", len(tpActions.Actions), len(reply.Actions))
+ }
+}
+
+func testTPActionsGetTPActionIds(t *testing.T) {
+ var result []string
+ expectedTPID := []string{"ID"}
+ if err := tpActionRPC.Call("ApierV1.GetTPActionIds", &AttrGetTPActionIds{TPid: "TPAcc"}, &result); err != nil {
+ t.Error(err)
+ } else if !reflect.DeepEqual(expectedTPID, result) {
+ t.Errorf("Expecting: %+v, received: %+v", expectedTPID, result)
+ }
+}
+
+func testTPActionsUpdateTPAction(t *testing.T) {
+ tpActions.Actions = []*utils.TPAction{
+ &utils.TPAction{
+ Identifier: "*topup_reset",
+ BalanceId: "BalID",
+ BalanceUuid: "BalUuid",
+ BalanceType: "*data",
+ Directions: "*out",
+ Units: "10",
+ ExpiryTime: "*unlimited",
+ Filter: "",
+ TimingTags: "2014-01-14T00:00:00Z",
+ DestinationIds: "DST_1002",
+ RatingSubject: "SPECIAL_1002",
+ Categories: "",
+ SharedGroups: "SHARED_A",
+ BalanceWeight: "10",
+ ExtraParameters: "",
+ BalanceBlocker: "false",
+ BalanceDisabled: "false",
+ Weight: 10,
+ },
+ &utils.TPAction{
+ Identifier: "*log",
+ BalanceId: "BalID",
+ BalanceUuid: "BalUuid",
+ BalanceType: "*monetary",
+ Directions: "*out",
+ Units: "120",
+ ExpiryTime: "*unlimited",
+ Filter: "",
+ TimingTags: "2014-01-14T00:00:00Z",
+ DestinationIds: "*any",
+ RatingSubject: "SPECIAL_1002",
+ Categories: "",
+ SharedGroups: "SHARED_A",
+ BalanceWeight: "11",
+ ExtraParameters: "",
+ BalanceBlocker: "false",
+ BalanceDisabled: "false",
+ Weight: 11,
+ },
+ &utils.TPAction{
+ Identifier: "*topup",
+ BalanceId: "BalID",
+ BalanceUuid: "BalUuid",
+ BalanceType: "*voice",
+ Directions: "*out",
+ Units: "102400",
+ ExpiryTime: "*unlimited",
+ Filter: "",
+ TimingTags: "2014-01-14T00:00:00Z",
+ DestinationIds: "*any",
+ RatingSubject: "SPECIAL_1002",
+ Categories: "",
+ SharedGroups: "SHARED_A",
+ BalanceWeight: "20",
+ ExtraParameters: "",
+ BalanceBlocker: "false",
+ BalanceDisabled: "false",
+ Weight: 11,
+ },
+ }
+ var result string
+ if err := tpActionRPC.Call("ApierV1.SetTPActions", tpActions, &result); err != nil {
+ t.Error(err)
+ } else if result != utils.OK {
+ t.Error("Unexpected reply returned", result)
+ }
+
+}
+
+func testTPActionsGetTPActionAfterUpdate(t *testing.T) {
+ var reply *utils.TPActions
+ if err := tpActionRPC.Call("ApierV1.GetTPActions", &AttrGetTPActions{TPid: "TPAcc", ID: "ID"}, &reply); err != nil {
+ t.Error(err)
+ } else if !reflect.DeepEqual(tpActions.TPid, reply.TPid) {
+ t.Errorf("Expecting : %+v, received: %+v", tpActions.TPid, reply.TPid)
+ } else if !reflect.DeepEqual(tpActions.ID, reply.ID) {
+ t.Errorf("Expecting : %+v, received: %+v", tpActions.ID, reply.ID)
+ } else if !reflect.DeepEqual(len(tpActions.Actions), len(reply.Actions)) {
+ t.Errorf("Expecting : %+v, received: %+v", len(tpActions.Actions), len(reply.Actions))
+ }
+
+}
+
+func testTPActionsRemTPAction(t *testing.T) {
+ var resp string
+ if err := tpActionRPC.Call("ApierV1.RemTPActions", &AttrGetTPActions{TPid: "TPAcc", ID: "ID"}, &resp); err != nil {
+ t.Error(err)
+ } else if resp != utils.OK {
+ t.Error("Unexpected reply returned", resp)
+ }
+
+}
+
+func testTPActionsGetTPActionAfterRemove(t *testing.T) {
+ var reply *utils.TPActionPlan
+ if err := tpActionRPC.Call("ApierV1.GetTPActions", &AttrGetTPActions{TPid: "TPAcc", ID: "ID"}, &reply); err == nil || err.Error() != utils.ErrNotFound.Error() {
+ t.Error(err)
+ }
+}
+
+func testTPActionsKillEngine(t *testing.T) {
+ if err := engine.KillEngine(tpActionDelay); err != nil {
+ t.Error(err)
+ }
+}
diff --git a/apier/v1/tpderivedcharges.go b/apier/v1/tpderivedcharges.go
index 2ba3b3655..89002d0b8 100644
--- a/apier/v1/tpderivedcharges.go
+++ b/apier/v1/tpderivedcharges.go
@@ -48,9 +48,10 @@ func (self *ApierV1) GetTPDerivedChargers(attrs AttrGetTPDerivedChargers, reply
filter := &utils.TPDerivedChargers{TPid: attrs.TPid}
filter.SetDerivedChargersId(attrs.DerivedChargersId)
if dcs, err := self.StorDb.GetTPDerivedChargers(filter); err != nil {
- return utils.NewErrServerError(err)
- } else if len(dcs) == 0 {
- return utils.ErrNotFound
+ if err.Error() != utils.ErrNotFound.Error() {
+ err = utils.NewErrServerError(err)
+ }
+ return err
} else {
*reply = *dcs[0]
}
diff --git a/apier/v1/tpderivedcharges_it_test.go b/apier/v1/tpderivedcharges_it_test.go
new file mode 100644
index 000000000..9aeba19c0
--- /dev/null
+++ b/apier/v1/tpderivedcharges_it_test.go
@@ -0,0 +1,305 @@
+// +build offline_tp
+
+/*
+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 (
+ "github.com/cgrates/cgrates/config"
+ "github.com/cgrates/cgrates/engine"
+ "github.com/cgrates/cgrates/utils"
+ "net/rpc"
+ "net/rpc/jsonrpc"
+ "path"
+ "reflect"
+ "testing"
+)
+
+var (
+ tpDerivedChargersCfgPath string
+ tpDerivedChargersCfg *config.CGRConfig
+ tpDerivedChargersRPC *rpc.Client
+ tpDerivedChargersDataDir = "/usr/share/cgrates"
+ tpDerivedChargers *utils.TPDerivedChargers
+ tpDerivedChargersDelay int
+ tpDerivedChargersConfigDIR string //run tests for specific configuration
+ tpDerivedChargersID = "LoadID:*out:cgrates.org:call:1001:1001"
+)
+
+var sTestsTPDerivedChargers = []func(t *testing.T){
+ testTPDerivedChargersInitCfg,
+ testTPDerivedChargersResetStorDb,
+ testTPDerivedChargersStartEngine,
+ testTPDerivedChargersRpcConn,
+ testTPDerivedChargersGetTPDerivedChargersBeforeSet,
+ testTPDerivedChargersSetTPDerivedChargers,
+ testTPDerivedChargersGetTPDerivedChargersAfterSet,
+ testTPDerivedChargersGetTPDerivedChargerIds,
+ testTPDerivedChargersUpdateTPDerivedChargers,
+ testTPDerivedChargersGetTPDerivedChargersAfterUpdate,
+ testTPDerivedChargersRemTPDerivedChargers,
+ testTPDerivedChargersGetTPDerivedChargersAfterRemove,
+ testTPDerivedChargersKillEngine,
+}
+
+//Test start here
+func TestTPDerivedChargersITMySql(t *testing.T) {
+ tpDerivedChargersConfigDIR = "tutmysql"
+ for _, stest := range sTestsTPDerivedChargers {
+ t.Run(tpDerivedChargersConfigDIR, stest)
+ }
+}
+
+func TestTPDerivedChargersITMongo(t *testing.T) {
+ tpDerivedChargersConfigDIR = "tutmongo"
+ for _, stest := range sTestsTPDerivedChargers {
+ t.Run(tpDerivedChargersConfigDIR, stest)
+ }
+}
+
+func TestTPDerivedChargersITPG(t *testing.T) {
+ tpDerivedChargersConfigDIR = "tutpostgres"
+ for _, stest := range sTestsTPDerivedChargers {
+ t.Run(tpDerivedChargersConfigDIR, stest)
+ }
+}
+
+func testTPDerivedChargersInitCfg(t *testing.T) {
+ var err error
+ tpDerivedChargersCfgPath = path.Join(tpDerivedChargersDataDir, "conf", "samples", tpDerivedChargersConfigDIR)
+ tpDerivedChargersCfg, err = config.NewCGRConfigFromFolder(tpDerivedChargersCfgPath)
+ if err != nil {
+ t.Error(err)
+ }
+ tpDerivedChargersCfg.DataFolderPath = tpDerivedChargersDataDir // Share DataFolderPath through config towards StoreDb for Flush()
+ config.SetCgrConfig(tpDerivedChargersCfg)
+ switch tpDerivedChargersConfigDIR {
+ case "tutmongo": // Mongo needs more time to reset db, need to investigate
+ tpDerivedChargersDelay = 2000
+ default:
+ tpDerivedChargersDelay = 1000
+ }
+}
+
+// Wipe out the cdr database
+func testTPDerivedChargersResetStorDb(t *testing.T) {
+ if err := engine.InitStorDb(tpDerivedChargersCfg); err != nil {
+ t.Fatal(err)
+ }
+}
+
+// Start CGR Engine
+func testTPDerivedChargersStartEngine(t *testing.T) {
+ if _, err := engine.StopStartEngine(tpDerivedChargersCfgPath, tpDerivedChargersDelay); err != nil {
+ t.Fatal(err)
+ }
+}
+
+// Connect rpc client to rater
+func testTPDerivedChargersRpcConn(t *testing.T) {
+ var err error
+ tpDerivedChargersRPC, err = jsonrpc.Dial("tcp", tpDerivedChargersCfg.RPCJSONListen) // We connect over JSON so we can also troubleshoot if needed
+ if err != nil {
+ t.Fatal(err)
+ }
+}
+
+func testTPDerivedChargersGetTPDerivedChargersBeforeSet(t *testing.T) {
+ var reply *utils.TPDerivedChargers
+ if err := tpDerivedChargersRPC.Call("ApierV1.GetTPDerivedChargers", &AttrGetTPDerivedChargers{TPid: "TPD", DerivedChargersId: tpDerivedChargersID}, &reply); err == nil || err.Error() != utils.ErrNotFound.Error() {
+ t.Error(err)
+ }
+
+}
+
+func testTPDerivedChargersSetTPDerivedChargers(t *testing.T) {
+ tpDerivedChargers = &utils.TPDerivedChargers{
+ TPid: "TPD",
+ LoadId: "LoadID",
+ Direction: "*out",
+ Tenant: "cgrates.org",
+ Category: "call",
+ Account: "1001",
+ Subject: "1001",
+ DestinationIds: "",
+ DerivedChargers: []*utils.TPDerivedCharger{
+ &utils.TPDerivedCharger{
+ RunId: "derived_run1",
+ RunFilters: "",
+ ReqTypeField: "^*rated",
+ DirectionField: "*default",
+ TenantField: "*default",
+ CategoryField: "*default",
+ AccountField: "*default",
+ SubjectField: "^1002",
+ DestinationField: "*default",
+ SetupTimeField: "*default",
+ PddField: "*default",
+ AnswerTimeField: "*default",
+ UsageField: "*default",
+ SupplierField: "*default",
+ DisconnectCauseField: "*default",
+ CostField: "*default",
+ RatedField: "*default",
+ },
+ },
+ }
+ var result string
+ if err := tpDerivedChargersRPC.Call("ApierV1.SetTPDerivedChargers", tpDerivedChargers, &result); err != nil {
+ t.Error(err)
+ } else if result != utils.OK {
+ t.Error("Unexpected reply returned", result)
+ }
+}
+
+func testTPDerivedChargersGetTPDerivedChargersAfterSet(t *testing.T) {
+ var reply *utils.TPDerivedChargers
+ if err := tpDerivedChargersRPC.Call("ApierV1.GetTPDerivedChargers", &AttrGetTPDerivedChargers{TPid: "TPD", DerivedChargersId: tpDerivedChargersID}, &reply); err != nil {
+ t.Error(err)
+ } else if !reflect.DeepEqual(tpDerivedChargers.TPid, reply.TPid) {
+ t.Errorf("Expecting : %+v, received: %+v", tpDerivedChargers.TPid, reply.TPid)
+ } else if !reflect.DeepEqual(tpDerivedChargers.LoadId, reply.LoadId) {
+ t.Errorf("Expecting : %+v, received: %+v", tpDerivedChargers.LoadId, reply.LoadId)
+ } else if !reflect.DeepEqual(tpDerivedChargers.Direction, reply.Direction) {
+ t.Errorf("Expecting : %+v, received: %+v", tpDerivedChargers.Direction, reply.Direction)
+ } else if !reflect.DeepEqual(tpDerivedChargers.Tenant, reply.Tenant) {
+ t.Errorf("Expecting : %+v, received: %+v", tpDerivedChargers.Tenant, reply.Tenant)
+ } else if !reflect.DeepEqual(tpDerivedChargers.Category, reply.Category) {
+ t.Errorf("Expecting : %+v, received: %+v", tpDerivedChargers.Category, reply.Category)
+ } else if !reflect.DeepEqual(tpDerivedChargers.Account, reply.Account) {
+ t.Errorf("Expecting : %+v, received: %+v", tpDerivedChargers.Account, reply.Account)
+ } else if !reflect.DeepEqual(tpDerivedChargers.Subject, reply.Subject) {
+ t.Errorf("Expecting : %+v, received: %+v", tpDerivedChargers.Subject, reply.Subject)
+ } else if !reflect.DeepEqual(tpDerivedChargers.DestinationIds, reply.DestinationIds) {
+ t.Errorf("Expecting : %+v, received: %+v", tpDerivedChargers.DestinationIds, reply.DestinationIds)
+ } else if !reflect.DeepEqual(len(tpDerivedChargers.DerivedChargers), len(reply.DerivedChargers)) {
+ t.Errorf("Expecting : %+v, received: %+v", len(tpDerivedChargers.DerivedChargers), len(reply.DerivedChargers))
+ }
+
+}
+
+func testTPDerivedChargersGetTPDerivedChargerIds(t *testing.T) {
+ var result []string
+ expectedTPID := []string{"LoadID:*out:cgrates.org:call:1001:1001"}
+ if err := tpDerivedChargersRPC.Call("ApierV1.GetTPDerivedChargerIds", &AttrGetTPDerivedChargeIds{TPid: "TPD"}, &result); err != nil {
+ t.Error(err)
+ } else if !reflect.DeepEqual(expectedTPID, result) {
+ t.Errorf("Expecting: %+v, received: %+v", expectedTPID, result)
+ }
+
+}
+
+func testTPDerivedChargersUpdateTPDerivedChargers(t *testing.T) {
+ tpDerivedChargers.DerivedChargers = []*utils.TPDerivedCharger{
+ &utils.TPDerivedCharger{
+ RunId: "derived_run1",
+ RunFilters: "",
+ ReqTypeField: "^*rated",
+ DirectionField: "*default",
+ TenantField: "*default",
+ CategoryField: "*default",
+ AccountField: "*default",
+ SubjectField: "^1002",
+ DestinationField: "*default",
+ SetupTimeField: "*default",
+ PddField: "*default",
+ AnswerTimeField: "*default",
+ UsageField: "*default",
+ SupplierField: "*default",
+ DisconnectCauseField: "*default",
+ CostField: "*default",
+ RatedField: "*default",
+ },
+ &utils.TPDerivedCharger{
+ RunId: "derived_run2",
+ RunFilters: "",
+ ReqTypeField: "^*rated",
+ DirectionField: "*default",
+ TenantField: "*default",
+ CategoryField: "*default",
+ AccountField: "*default",
+ SubjectField: "^1003",
+ DestinationField: "*default",
+ SetupTimeField: "*default",
+ PddField: "*default",
+ AnswerTimeField: "*default",
+ UsageField: "*default",
+ SupplierField: "*default",
+ DisconnectCauseField: "*default",
+ CostField: "*default",
+ RatedField: "*default",
+ },
+ }
+ var result string
+ if err := tpDerivedChargersRPC.Call("ApierV1.SetTPDerivedChargers", tpDerivedChargers, &result); err != nil {
+ t.Error(err)
+ } else if result != utils.OK {
+ t.Error("Unexpected reply returned", result)
+ }
+
+}
+
+func testTPDerivedChargersGetTPDerivedChargersAfterUpdate(t *testing.T) {
+ var reply *utils.TPDerivedChargers
+ if err := tpDerivedChargersRPC.Call("ApierV1.GetTPDerivedChargers", &AttrGetTPDerivedChargers{TPid: "TPD", DerivedChargersId: tpDerivedChargersID}, &reply); err != nil {
+ t.Error(err)
+ } else if !reflect.DeepEqual(tpDerivedChargers.TPid, reply.TPid) {
+ t.Errorf("Expecting : %+v, received: %+v", tpDerivedChargers.TPid, reply.TPid)
+ } else if !reflect.DeepEqual(tpDerivedChargers.LoadId, reply.LoadId) {
+ t.Errorf("Expecting : %+v, received: %+v", tpDerivedChargers.LoadId, reply.LoadId)
+ } else if !reflect.DeepEqual(tpDerivedChargers.Direction, reply.Direction) {
+ t.Errorf("Expecting : %+v, received: %+v", tpDerivedChargers.Direction, reply.Direction)
+ } else if !reflect.DeepEqual(tpDerivedChargers.Tenant, reply.Tenant) {
+ t.Errorf("Expecting : %+v, received: %+v", tpDerivedChargers.Tenant, reply.Tenant)
+ } else if !reflect.DeepEqual(tpDerivedChargers.Category, reply.Category) {
+ t.Errorf("Expecting : %+v, received: %+v", tpDerivedChargers.Category, reply.Category)
+ } else if !reflect.DeepEqual(tpDerivedChargers.Account, reply.Account) {
+ t.Errorf("Expecting : %+v, received: %+v", tpDerivedChargers.Account, reply.Account)
+ } else if !reflect.DeepEqual(tpDerivedChargers.Subject, reply.Subject) {
+ t.Errorf("Expecting : %+v, received: %+v", tpDerivedChargers.Subject, reply.Subject)
+ } else if !reflect.DeepEqual(tpDerivedChargers.DestinationIds, reply.DestinationIds) {
+ t.Errorf("Expecting : %+v, received: %+v", tpDerivedChargers.DestinationIds, reply.DestinationIds)
+ } else if !reflect.DeepEqual(len(tpDerivedChargers.DerivedChargers), len(reply.DerivedChargers)) {
+ t.Errorf("Expecting : %+v, received: %+v", len(tpDerivedChargers.DerivedChargers), len(reply.DerivedChargers))
+ }
+
+}
+
+func testTPDerivedChargersRemTPDerivedChargers(t *testing.T) {
+ var resp string
+ if err := tpDerivedChargersRPC.Call("ApierV1.RemTPDerivedChargers", &AttrGetTPDerivedChargers{TPid: "TPD", DerivedChargersId: tpDerivedChargersID}, &resp); err != nil {
+ t.Error(err)
+ } else if resp != utils.OK {
+ t.Error("Unexpected reply returned", resp)
+ }
+
+}
+
+func testTPDerivedChargersGetTPDerivedChargersAfterRemove(t *testing.T) {
+ var reply *utils.TPDerivedChargers
+ if err := tpDerivedChargersRPC.Call("ApierV1.GetTPDerivedChargers", &AttrGetTPDerivedChargers{TPid: "TPD", DerivedChargersId: tpDerivedChargersID}, &reply); err == nil || err.Error() != utils.ErrNotFound.Error() {
+ t.Error(err)
+ }
+}
+
+func testTPDerivedChargersKillEngine(t *testing.T) {
+ if err := engine.KillEngine(tpDerivedChargersDelay); err != nil {
+ t.Error(err)
+ }
+}
diff --git a/apier/v1/tplcrrules.go b/apier/v1/tplcrrules.go
index 37611ac38..b0e6a7514 100644
--- a/apier/v1/tplcrrules.go
+++ b/apier/v1/tplcrrules.go
@@ -39,6 +39,15 @@ type AttrGetTPLcrRules struct {
LcrRuleId string // Lcr id
}
+type AttrRemTPLcrRules struct {
+ TPid string // Tariff plan id
+ Direction string
+ Tenant string
+ Category string
+ Account string
+ Subject string
+}
+
// Queries specific LcrRules profile on tariff plan
func (self *ApierV1) GetTPLcrRule(attr AttrGetTPLcrRules, reply *utils.TPLcrRules) error {
if missing := utils.MissingStructFields(&attr, []string{"TPid", "LcrRuleId"}); len(missing) != 0 { //Params missing
@@ -47,9 +56,10 @@ func (self *ApierV1) GetTPLcrRule(attr AttrGetTPLcrRules, reply *utils.TPLcrRule
filter := &utils.TPLcrRules{TPid: attr.TPid}
filter.SetId(attr.LcrRuleId)
if lcrs, err := self.StorDb.GetTPLCRs(filter); err != nil {
- return utils.NewErrServerError(err)
- } else if len(lcrs) == 0 {
- return utils.ErrNotFound
+ if err.Error() != utils.ErrNotFound.Error() {
+ err = utils.NewErrServerError(err)
+ }
+ return err
} else {
*reply = *lcrs[0]
}
@@ -77,11 +87,11 @@ func (self *ApierV1) GetTPLcrRuleIds(attrs AttrGetTPLcrIds, reply *[]string) err
}
// Removes specific LcrRules on Tariff plan
-func (self *ApierV1) RemTPLcrRule(attrs AttrGetTPLcrRules, reply *string) error {
- if missing := utils.MissingStructFields(&attrs, []string{"TPid", "LcrRulesId"}); len(missing) != 0 { //Params missing
+func (self *ApierV1) RemTPLcrRule(attrs AttrRemTPLcrRules, reply *string) error {
+ if missing := utils.MissingStructFields(&attrs, []string{"TPid", "Direction", "Tenant", "Category", "Account", "Subject"}); len(missing) != 0 { //Params missing
return utils.NewErrMandatoryIeMissing(missing...)
}
- if err := self.StorDb.RemTpData(utils.TBLTPLcrs, attrs.TPid, map[string]string{"tag": attrs.LcrRuleId}); err != nil {
+ if err := self.StorDb.RemTpData(utils.TBLTPLcrs, attrs.TPid, map[string]string{"direction": attrs.Direction, "tenant": attrs.Tenant, "category": attrs.Category, "account": attrs.Account, "subject": attrs.Subject}); err != nil {
return utils.NewErrServerError(err)
} else {
*reply = utils.OK
diff --git a/apier/v1/tplcrrules_it_test.go b/apier/v1/tplcrrules_it_test.go
new file mode 100644
index 000000000..135902c07
--- /dev/null
+++ b/apier/v1/tplcrrules_it_test.go
@@ -0,0 +1,259 @@
+// +build offline_tp
+
+/*
+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 (
+ "github.com/cgrates/cgrates/config"
+ "github.com/cgrates/cgrates/engine"
+ "github.com/cgrates/cgrates/utils"
+ "net/rpc"
+ "net/rpc/jsonrpc"
+ "path"
+ "reflect"
+ "testing"
+)
+
+var (
+ tpLcrRuleCfgPath string
+ tpLcrRuleCfg *config.CGRConfig
+ tpLcrRuleRPC *rpc.Client
+ tpLcrRuleDataDir = "/usr/share/cgrates"
+ tpLcrRules *utils.TPLcrRules
+ tpLcrRuleDelay int
+ tpLcrRuleConfigDIR string //run tests for specific configuration
+ tpLcrRuleID = "*out:cgrates.org:call:1001:*any"
+)
+
+var sTestsTPLcrRules = []func(t *testing.T){
+ testTPLcrRulesInitCfg,
+ testTPLcrRulesResetStorDb,
+ testTPLcrRulesStartEngine,
+ testTPLcrRulesRpcConn,
+ testTPLcrRulesGetTPLcrRulesBeforeSet,
+ testTPLcrRulesSetTPLcrRules,
+ testTPLcrRulesGetTPLcrRulesAfterSet,
+ testTPLcrRulesGetTPDestinationIds,
+ testTPLcrRulesUpdateTPLcrRules,
+ testTPLcrRulesGetTPLcrRulesAfterUpdate,
+ testTPLcrRulesRemTPLcrRules,
+ testTPLcrRulesGetTPLcrRulesAfterRemove,
+ testTPLcrRulesKillEngine,
+}
+
+//Test start here
+func TestTPLcrRulesITMySql(t *testing.T) {
+ tpLcrRuleConfigDIR = "tutmysql"
+ for _, stest := range sTestsTPLcrRules {
+ t.Run(tpLcrRuleConfigDIR, stest)
+ }
+}
+
+func TestTPLcrRulesITMongo(t *testing.T) {
+ tpLcrRuleConfigDIR = "tutmongo"
+ for _, stest := range sTestsTPLcrRules {
+ t.Run(tpLcrRuleConfigDIR, stest)
+ }
+}
+
+func TestTPLcrRulesITPG(t *testing.T) {
+ tpLcrRuleConfigDIR = "tutpostgres"
+ for _, stest := range sTestsTPLcrRules {
+ t.Run(tpLcrRuleConfigDIR, stest)
+ }
+}
+
+func testTPLcrRulesInitCfg(t *testing.T) {
+ var err error
+ tpLcrRuleCfgPath = path.Join(tpLcrRuleDataDir, "conf", "samples", tpLcrRuleConfigDIR)
+ tpLcrRuleCfg, err = config.NewCGRConfigFromFolder(tpLcrRuleCfgPath)
+ if err != nil {
+ t.Error(err)
+ }
+ tpLcrRuleCfg.DataFolderPath = tpLcrRuleDataDir // Share DataFolderPath through config towards StoreDb for Flush()
+ config.SetCgrConfig(tpLcrRuleCfg)
+ switch tpLcrRuleConfigDIR {
+ case "tutmongo": // Mongo needs more time to reset db, need to investigate
+ tpLcrRuleDelay = 2000
+ default:
+ tpLcrRuleDelay = 1000
+ }
+}
+
+// Wipe out the cdr database
+func testTPLcrRulesResetStorDb(t *testing.T) {
+ if err := engine.InitStorDb(tpLcrRuleCfg); err != nil {
+ t.Fatal(err)
+ }
+}
+
+// Start CGR Engine
+func testTPLcrRulesStartEngine(t *testing.T) {
+ if _, err := engine.StopStartEngine(tpLcrRuleCfgPath, tpLcrRuleDelay); err != nil {
+ t.Fatal(err)
+ }
+}
+
+// Connect rpc client to rater
+func testTPLcrRulesRpcConn(t *testing.T) {
+ var err error
+ tpLcrRuleRPC, err = jsonrpc.Dial("tcp", tpLcrRuleCfg.RPCJSONListen) // We connect over JSON so we can also troubleshoot if needed
+ if err != nil {
+ t.Fatal(err)
+ }
+}
+
+func testTPLcrRulesGetTPLcrRulesBeforeSet(t *testing.T) {
+ var reply *utils.TPRatingPlan
+ if err := tpLcrRuleRPC.Call("ApierV1.GetTPLcrRule", &AttrGetTPLcrRules{TPid: "TPLRC1", LcrRuleId: tpLcrRuleID}, &reply); err == nil || err.Error() != utils.ErrNotFound.Error() {
+ t.Error(err)
+ }
+}
+
+func testTPLcrRulesSetTPLcrRules(t *testing.T) {
+ tpLcrRules = &utils.TPLcrRules{
+ TPid: "TPLRC1",
+ Direction: "*out",
+ Tenant: "cgrates.org",
+ Category: "call",
+ Account: "1001",
+ Subject: "*any",
+ Rules: []*utils.TPLcrRule{
+ &utils.TPLcrRule{
+ DestinationId: "DST_1002",
+ RpCategory: "lcr_profile1",
+ Strategy: "*static",
+ StrategyParams: "suppl2;suppl1",
+ ActivationTime: "05:00:00",
+ Weight: 10,
+ },
+ &utils.TPLcrRule{
+ DestinationId: "*any",
+ RpCategory: "lcr_profile1",
+ Strategy: "*highest_cost",
+ StrategyParams: "",
+ ActivationTime: "05:00:00",
+ Weight: 10,
+ },
+ },
+ }
+ var result string
+ if err := tpLcrRuleRPC.Call("ApierV1.SetTPLcrRule", tpLcrRules, &result); err != nil {
+ t.Error(err)
+ } else if result != utils.OK {
+ t.Error("Unexpected reply returned", result)
+ }
+}
+
+func testTPLcrRulesGetTPLcrRulesAfterSet(t *testing.T) {
+ var reply *utils.TPLcrRules
+ if err := tpLcrRuleRPC.Call("ApierV1.GetTPLcrRule", &AttrGetTPLcrRules{TPid: "TPLRC1", LcrRuleId: tpLcrRuleID}, &reply); err != nil {
+ t.Error(err)
+ } else if !reflect.DeepEqual(tpLcrRules.TPid, reply.TPid) {
+ t.Errorf("Expecting : %+v, received: %+v", tpLcrRules.TPid, reply.TPid)
+ } else if !reflect.DeepEqual(tpLcrRules.GetLcrRuleId(), reply.GetLcrRuleId()) {
+ t.Errorf("Expecting : %+v, received: %+v", tpLcrRules.GetLcrRuleId(), reply.GetLcrRuleId())
+ } else if !reflect.DeepEqual(len(tpLcrRules.Rules), len(reply.Rules)) {
+ t.Errorf("Expecting : %+v, received: %+v", len(tpLcrRules.Rules), len(reply.Rules))
+ }
+
+}
+
+func testTPLcrRulesGetTPDestinationIds(t *testing.T) {
+ var result []string
+ expectedTPID := []string{"*out:cgrates.org:call:1001:*any"}
+ if err := tpLcrRuleRPC.Call("ApierV1.GetTPLcrRuleIds", &AttrGetTPLcrIds{TPid: "TPLRC1"}, &result); err != nil {
+ t.Error(err)
+ } else if !reflect.DeepEqual(expectedTPID, result) {
+ t.Errorf("Expecting: %+v, received: %+v", expectedTPID, result)
+ }
+
+}
+
+func testTPLcrRulesUpdateTPLcrRules(t *testing.T) {
+ tpLcrRules.Rules = []*utils.TPLcrRule{
+ &utils.TPLcrRule{
+ DestinationId: "DST_1002",
+ RpCategory: "lcr_profile1",
+ Strategy: "*static",
+ StrategyParams: "suppl2;suppl1",
+ ActivationTime: "03:00:00",
+ Weight: 10,
+ },
+ &utils.TPLcrRule{
+ DestinationId: "*any",
+ RpCategory: "lcr_profile1",
+ Strategy: "*highest_cost",
+ StrategyParams: "",
+ ActivationTime: "05:00:00",
+ Weight: 10,
+ },
+ &utils.TPLcrRule{
+ DestinationId: "*any",
+ RpCategory: "lcr_profile2",
+ Strategy: "*load_distribution",
+ StrategyParams: "supplier1:5",
+ ActivationTime: "01:00:00",
+ Weight: 10,
+ },
+ }
+ var result string
+ if err := tpLcrRuleRPC.Call("ApierV1.SetTPLcrRule", tpLcrRules, &result); err != nil {
+ t.Error(err)
+ } else if result != utils.OK {
+ t.Error("Unexpected reply returned", result)
+ }
+}
+
+func testTPLcrRulesGetTPLcrRulesAfterUpdate(t *testing.T) {
+ var reply *utils.TPLcrRules
+ if err := tpLcrRuleRPC.Call("ApierV1.GetTPLcrRule", &AttrGetTPLcrRules{TPid: "TPLRC1", LcrRuleId: tpLcrRuleID}, &reply); err != nil {
+ t.Error(err)
+ } else if !reflect.DeepEqual(tpLcrRules.TPid, reply.TPid) {
+ t.Errorf("Expecting : %+v, received: %+v", tpLcrRules.TPid, reply.TPid)
+ } else if !reflect.DeepEqual(tpLcrRules.GetLcrRuleId(), reply.GetLcrRuleId()) {
+ t.Errorf("Expecting : %+v, received: %+v", tpLcrRules.GetLcrRuleId(), reply.GetLcrRuleId())
+ } else if !reflect.DeepEqual(len(tpLcrRules.Rules), len(reply.Rules)) {
+ t.Errorf("Expecting : %+v, received: %+v", len(tpLcrRules.Rules), len(reply.Rules))
+ }
+}
+
+func testTPLcrRulesRemTPLcrRules(t *testing.T) {
+ var resp string
+ if err := tpLcrRuleRPC.Call("ApierV1.RemTPLcrRule", &AttrRemTPLcrRules{TPid: tpLcrRules.TPid, Direction: tpLcrRules.Direction, Tenant: tpLcrRules.Tenant, Category: tpLcrRules.Category, Account: tpLcrRules.Account, Subject: tpLcrRules.Subject}, &resp); err != nil {
+ t.Error(err)
+ } else if resp != utils.OK {
+ t.Error("Unexpected reply returned", resp)
+ }
+}
+
+func testTPLcrRulesGetTPLcrRulesAfterRemove(t *testing.T) {
+ var reply *utils.TPRatingPlan
+ if err := tpLcrRuleRPC.Call("ApierV1.GetTPLcrRule", &AttrGetTPLcrRules{TPid: "TPLRC1", LcrRuleId: tpLcrRuleID}, &reply); err == nil || err.Error() != utils.ErrNotFound.Error() {
+ t.Error(err)
+ }
+
+}
+
+func testTPLcrRulesKillEngine(t *testing.T) {
+ if err := engine.KillEngine(tpLcrRuleDelay); err != nil {
+ t.Error(err)
+ }
+}
diff --git a/apier/v1/tpresources.go b/apier/v1/tpresources.go
index 830a5c838..5b9ddd0ec 100644
--- a/apier/v1/tpresources.go
+++ b/apier/v1/tpresources.go
@@ -65,7 +65,7 @@ func (self *ApierV1) GetTPResourceIDs(attrs AttrGetTPResourceIds, reply *[]strin
if missing := utils.MissingStructFields(&attrs, []string{"TPid"}); len(missing) != 0 { //Params missing
return utils.NewErrMandatoryIeMissing(missing...)
}
- if ids, err := self.StorDb.GetTpTableIds(attrs.TPid, utils.TBLTPResources, utils.TPDistinctIds{"tag"}, nil, &attrs.Paginator); err != nil {
+ if ids, err := self.StorDb.GetTpTableIds(attrs.TPid, utils.TBLTPResources, utils.TPDistinctIds{"id"}, nil, &attrs.Paginator); err != nil {
return utils.NewErrServerError(err)
} else if ids == nil {
return utils.ErrNotFound
@@ -80,7 +80,7 @@ func (self *ApierV1) RemTPResource(attrs AttrGetTPResource, reply *string) error
if missing := utils.MissingStructFields(&attrs, []string{"TPid", "ID"}); len(missing) != 0 { //Params missing
return utils.NewErrMandatoryIeMissing(missing...)
}
- if err := self.StorDb.RemTpData(utils.TBLTPResources, attrs.TPid, map[string]string{"tag": attrs.ID}); err != nil {
+ if err := self.StorDb.RemTpData(utils.TBLTPResources, attrs.TPid, map[string]string{"id": attrs.ID}); err != nil {
return utils.NewErrServerError(err)
} else {
*reply = utils.OK
diff --git a/apier/v1/tpresources_it_test.go b/apier/v1/tpresources_it_test.go
index a49521dce..d0bc02552 100644
--- a/apier/v1/tpresources_it_test.go
+++ b/apier/v1/tpresources_it_test.go
@@ -44,7 +44,7 @@ var (
var sTestsTPResources = []func(t *testing.T){
testTPResInitCfg,
testTPResResetStorDb,
- testTPResStartEngine,
+ //testTPResStartEngine,
testTPResRpcConn,
testTPResGetTPResourceBeforeSet,
testTPResSetTPResource,
@@ -120,16 +120,16 @@ func testTPResRpcConn(t *testing.T) {
func testTPResGetTPResourceBeforeSet(t *testing.T) {
var reply *utils.TPResource
- if err := tpResRPC.Call("ApierV1.GetTPResource", AttrGetTPResource{TPid: "TPR1", ID: "Res"}, &reply); err == nil || err.Error() != utils.ErrNotFound.Error() {
+ if err := tpResRPC.Call("ApierV1.GetTPResource", AttrGetTPResource{TPid: "TPR1", ID: "ResGroup1"}, &reply); err == nil || err.Error() != utils.ErrNotFound.Error() {
t.Error(err)
}
}
func testTPResSetTPResource(t *testing.T) {
tpRes = &utils.TPResource{
- Tenant:"Tester",
- TPid: "TPR1",
- ID: "Res",
+ Tenant: "cgrates.org",
+ TPid: "TPR1",
+ ID: "ResGroup1",
Filters: []*utils.TPRequestFilter{
&utils.TPRequestFilter{
Type: "*string",
@@ -141,11 +141,11 @@ func testTPResSetTPResource(t *testing.T) {
ActivationTime: "2014-07-29T15:00:00Z",
ExpiryTime: "",
},
- UsageTTL: "1",
- Limit: "1",
- AllocationMessage: "Message",
- Blocker: false,
- Stored: false,
+ UsageTTL: "1s",
+ Limit: "7",
+ AllocationMessage: "",
+ Blocker: true,
+ Stored: true,
Weight: 20,
Thresholds: []string{"ValOne", "ValTwo"},
}
@@ -159,7 +159,7 @@ func testTPResSetTPResource(t *testing.T) {
func testTPResGetTPResourceAfterSet(t *testing.T) {
var respond *utils.TPResource
- if err := tpResRPC.Call("ApierV1.GetTPResource", &AttrGetTPResource{TPid: tpRes.TPid, ID: tpRes.ID}, &respond); err != nil {
+ if err := tpResRPC.Call("ApierV1.GetTPResource", AttrGetTPResource{TPid: tpRes.TPid, ID: tpRes.ID}, &respond); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(tpRes, respond) {
t.Errorf("Expecting : %+v, received: %+v", tpRes, respond)
@@ -189,7 +189,7 @@ func testTPResUpdateTPResource(t *testing.T) {
func testTPResGetTPResourceAfterUpdate(t *testing.T) {
var expectedTPR *utils.TPResource
- if err := tpResRPC.Call("ApierV1.GetTPResource", &AttrGetTPResource{TPid: tpRes.TPid, ID: tpRes.ID}, &expectedTPR); err != nil {
+ if err := tpResRPC.Call("ApierV1.GetTPResource", AttrGetTPResource{TPid: tpRes.TPid, ID: tpRes.ID}, &expectedTPR); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(tpRes, expectedTPR) {
t.Errorf("Expecting: %+v, received: %+v", tpRes, expectedTPR)
@@ -198,7 +198,7 @@ func testTPResGetTPResourceAfterUpdate(t *testing.T) {
func testTPResRemTPResource(t *testing.T) {
var resp string
- if err := tpResRPC.Call("ApierV1.RemTPResource", &AttrGetTPResource{TPid: tpRes.TPid, ID: tpRes.ID}, &resp); err != nil {
+ if err := tpResRPC.Call("ApierV1.RemTPResource", AttrGetTPResource{TPid: tpRes.TPid, ID: tpRes.ID}, &resp); err != nil {
t.Error(err)
} else if resp != utils.OK {
t.Error("Unexpected reply returned", resp)
@@ -207,7 +207,7 @@ func testTPResRemTPResource(t *testing.T) {
func testTPResGetTPResourceAfterRemove(t *testing.T) {
var respond *utils.TPResource
- if err := tpResRPC.Call("ApierV1.GetTPResource", &AttrGetTPStat{TPid: "TPS1", ID: "Stat1"}, &respond); err == nil || err.Error() != utils.ErrNotFound.Error() {
+ if err := tpResRPC.Call("ApierV1.GetTPResource", AttrGetTPStat{TPid: "TPS1", ID: "ResGroup1"}, &respond); err == nil || err.Error() != utils.ErrNotFound.Error() {
t.Error(err)
}
}
diff --git a/apier/v2/tp.go b/apier/v2/tp.go
index 2053fc17e..2513f87f0 100644
--- a/apier/v2/tp.go
+++ b/apier/v2/tp.go
@@ -42,7 +42,7 @@ func (self *ApierV2) RemTP(attrs AttrRemTp, reply *string) error {
}
func (self *ApierV2) ExportTPToFolder(attrs utils.AttrDirExportTP, exported *utils.ExportedTPStats) error {
- if len(*attrs.TPid) == 0 {
+ if attrs.TPid == nil || *attrs.TPid == "" {
return utils.NewErrMandatoryIeMissing("TPid")
}
dir := self.Config.TpExportPath
@@ -75,7 +75,7 @@ func (self *ApierV2) ExportTPToFolder(attrs utils.AttrDirExportTP, exported *uti
}
func (self *ApierV2) ExportTPToZipString(attrs utils.AttrDirExportTP, reply *string) error {
- if len(*attrs.TPid) == 0 {
+ if attrs.TPid == nil || *attrs.TPid == "" {
return utils.NewErrMandatoryIeMissing("TPid")
}
dir := ""
diff --git a/data/conf/samples/tutmongo/cgrates.json b/data/conf/samples/tutmongo/cgrates.json
index dd8d774d6..e406a0baf 100644
--- a/data/conf/samples/tutmongo/cgrates.json
+++ b/data/conf/samples/tutmongo/cgrates.json
@@ -27,6 +27,26 @@
},
+"cache":{
+ "destinations": {"limit": 10000, "ttl":"0s", "precache": true},
+ "reverse_destinations": {"limit": 10000, "ttl":"0s", "precache": true},
+ "rating_plans": {"limit": 10000, "ttl":"0s","precache": true},
+ "rating_profiles": {"limit": 10000, "ttl":"0s", "precache": true},
+ "lcr_rules": {"limit": 10000, "ttl":"0s", "precache": true},
+ "cdr_stats": {"limit": 10000, "ttl":"0s", "precache": true},
+ "actions": {"limit": 10000, "ttl":"0s", "precache": true},
+ "action_plans": {"limit": 10000, "ttl":"0s", "precache": true},
+ "account_action_plans": {"limit": 10000, "ttl":"0s", "precache": true},
+ "action_triggers": {"limit": 10000, "ttl":"0s", "precache": true},
+ "shared_groups": {"limit": 10000, "ttl":"0s", "precache": true},
+ "aliases": {"limit": 10000, "ttl":"0s", "precache": true},
+ "reverse_aliases": {"limit": 10000, "ttl":"0s", "precache": true},
+ "derived_chargers": {"limit": 10000, "ttl":"0s", "precache": true},
+ "resource_profiles": {"limit": 10000, "ttl":"0s", "precache": true},
+ "resources": {"limit": 10000, "ttl":"0s", "precache": true},
+},
+
+
"rals": {
"enabled": true,
"cdrstats_conns": [
@@ -57,6 +77,26 @@
},
+"cdre": {
+ "TestTutITExportCDR": {
+ "content_fields": [
+ {"tag": "CGRID", "type": "*composed", "value": "CGRID"},
+ {"tag": "RunID", "type": "*composed", "value": "RunID"},
+ {"tag":"OriginID", "type": "*composed", "value": "OriginID"},
+ {"tag":"RequestType", "type": "*composed", "value": "RequestType"},
+ {"tag":"Tenant", "type": "*composed", "value": "Tenant"},
+ {"tag":"Category", "type": "*composed", "value": "Category"},
+ {"tag":"Account", "type": "*composed", "value": "Account"},
+ {"tag":"Destination", "type": "*composed", "value": "Destination"},
+ {"tag":"AnswerTime", "type": "*composed", "value": "AnswerTime", "layout": "2006-01-02T15:04:05Z07:00"},
+ {"tag":"Usage", "type": "*composed", "value": "Usage"},
+ {"tag":"Cost", "type": "*composed", "value": "Cost", "rounding_decimals": 4},
+ {"tag":"MatchedDestinationID", "type": "*composed", "value": "~CostDetails:s/\"MatchedDestId\":.*_(\\w{4})/${1}/:s/\"MatchedDestId\":\"INTERNAL\"/ON010/"},
+ ],
+ },
+},
+
+
"cdrstats": {
"enabled": true,
},
@@ -73,11 +113,6 @@
},
-"aliases": {
- "enabled": true,
-},
-
-
"resources": {
"enabled": true,
},
@@ -89,9 +124,19 @@
},
+"historys": {
+ "enabled": true,
+},
+
+
+"aliases": {
+ "enabled": true,
+},
+
+
"sm_generic": {
"enabled": true,
},
-}
+}
\ No newline at end of file
diff --git a/data/storage/mysql/create_tariffplan_tables.sql b/data/storage/mysql/create_tariffplan_tables.sql
index e62bd1146..8bd959e31 100644
--- a/data/storage/mysql/create_tariffplan_tables.sql
+++ b/data/storage/mysql/create_tariffplan_tables.sql
@@ -397,10 +397,10 @@ CREATE TABLE tp_aliases (
DROP TABLE IF EXISTS tp_resources;
CREATE TABLE tp_resources (
- `tenant` varchar(64) NOT NULL,
- `id` int(11) NOT NULL AUTO_INCREMENT,
+ `pk` int(11) NOT NULL AUTO_INCREMENT,
`tpid` varchar(64) NOT NULL,
- `tag` varchar(64) NOT NULL,
+ `tenant` varchar(64) NOT NULL,
+ `id` varchar(64) NOT NULL,
`filter_type` varchar(16) NOT NULL,
`filter_field_name` varchar(64) NOT NULL,
`filter_field_values` varchar(256) NOT NULL,
@@ -413,9 +413,9 @@ CREATE TABLE tp_resources (
`weight` decimal(8,2) NOT NULL,
`thresholds` varchar(64) NOT NULL,
`created_at` TIMESTAMP,
- PRIMARY KEY (`id`),
+ PRIMARY KEY (`pk`),
KEY `tpid` (`tpid`),
- UNIQUE KEY `unique_tp_resource` (`tpid`, `tag`, `filter_type`, `filter_field_name`)
+ UNIQUE KEY `unique_tp_resource` (`tpid`,`tenant`, `id`, `filter_type`, `filter_field_name`)
);
--
@@ -424,9 +424,9 @@ CREATE TABLE tp_resources (
DROP TABLE IF EXISTS tp_stats;
CREATE TABLE tp_stats (
- `tenant` varchar(64) NOT NULL,
`id` int(11) NOT NULL AUTO_INCREMENT,
`tpid` varchar(64) NOT NULL,
+ `tenant` varchar(64) NOT NULL,
`tag` varchar(64) NOT NULL,
`filter_type` varchar(16) NOT NULL,
`filter_field_name` varchar(64) NOT NULL,
@@ -442,7 +442,7 @@ CREATE TABLE tp_stats (
`created_at` TIMESTAMP,
PRIMARY KEY (`id`),
KEY `tpid` (`tpid`),
- UNIQUE KEY `unique_tp_stats` (`tpid`, `tag`, `filter_type`, `filter_field_name`)
+ UNIQUE KEY `unique_tp_stats` (`tpid`, `tenant`, `tag`, `filter_type`, `filter_field_name`)
);
--
@@ -485,6 +485,3 @@ CREATE TABLE versions (
PRIMARY KEY (`id`),
UNIQUE KEY `item` (`item`)
);
-
-
-
diff --git a/data/storage/postgres/create_tariffplan_tables.sql b/data/storage/postgres/create_tariffplan_tables.sql
index c0772103c..c2e9ef515 100644
--- a/data/storage/postgres/create_tariffplan_tables.sql
+++ b/data/storage/postgres/create_tariffplan_tables.sql
@@ -393,10 +393,10 @@ CREATE INDEX tpaliases_idx ON tp_aliases (tpid,direction,tenant,category,account
DROP TABLE IF EXISTS tp_resources;
CREATE TABLE tp_resources (
- "tenant"varchar(64) NOT NULL,
- "id" SERIAL PRIMARY KEY,
+ "pk" SERIAL PRIMARY KEY,
"tpid" varchar(64) NOT NULL,
- "tag" varchar(64) NOT NULL,
+ "tenant"varchar(64) NOT NULL,
+ "id" varchar(64) NOT NULL,
"filter_type" varchar(16) NOT NULL,
"filter_field_name" varchar(64) NOT NULL,
"filter_field_values" varchar(256) NOT NULL,
@@ -411,7 +411,7 @@ CREATE TABLE tp_resources (
"created_at" TIMESTAMP WITH TIME ZONE
);
CREATE INDEX tp_resources_idx ON tp_resources (tpid);
-CREATE INDEX tp_resources_unique ON tp_resources ("tpid", "tag", "filter_type", "filter_field_name");
+CREATE INDEX tp_resources_unique ON tp_resources ("tpid", "tenant", "id", "filter_type", "filter_field_name");
--
@@ -420,9 +420,9 @@ CREATE INDEX tp_resources_unique ON tp_resources ("tpid", "tag", "filter_type",
DROP TABLE IF EXISTS tp_stats;
CREATE TABLE tp_stats (
- "tenant"varchar(64) NOT NULL,
"id" SERIAL PRIMARY KEY,
"tpid" varchar(64) NOT NULL,
+ "tenant"varchar(64) NOT NULL,
"tag" varchar(64) NOT NULL,
"filter_type" varchar(16) NOT NULL,
"filter_field_name" varchar(64) NOT NULL,
@@ -438,7 +438,7 @@ CREATE TABLE tp_stats (
"created_at" TIMESTAMP WITH TIME ZONE
);
CREATE INDEX tp_stats_idx ON tp_stats (tpid);
-CREATE INDEX tp_stats_unique ON tp_stats ("tpid", "tag", "filter_type", "filter_field_name");
+CREATE INDEX tp_stats_unique ON tp_stats ("tpid","tenant", "tag", "filter_type", "filter_field_name");
--
-- Table structure for table `tp_threshold_cfgs`
@@ -479,6 +479,3 @@ CREATE TABLE versions (
"version" INTEGER NOT NULL,
UNIQUE (item)
);
-
-
-
diff --git a/data/tariffplans/testtp/Resources.csv b/data/tariffplans/testtp/Resources.csv
index 6597b961d..a508329df 100755
--- a/data/tariffplans/testtp/Resources.csv
+++ b/data/tariffplans/testtp/Resources.csv
@@ -1,6 +1,6 @@
#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]
-Tester,ResGroup1,*string,Account,1001;1002,2014-07-29T15:00:00Z,1s,7,,true,true,20,
-Tester,ResGroup1,*string_prefix,Destination,10;20,,,,,,,,
-Tester,ResGroup1,*rsr_fields,,Subject(~^1.*1$);Destination(1002),,,,,,,,
-Tester,ResGroup2,*destinations,Destination,DST_FS,2014-07-29T15:00:00Z,3600s,8,SPECIAL_1002,true,true,10,
-Tester,ResGroup3,*cdr_stats,,CDRST1:*min_ASR:34;CDRST_1001:*min_ASR:20,,,,,,,,
+cgrates.org,ResGroup1,*string,Account,1001;1002,2014-07-29T15:00:00Z,1s,7,,true,true,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,*cdr_stats,,CDRST1:*min_ASR:34;CDRST_1001:*min_ASR:20,,,,,,,,
diff --git a/data/tariffplans/testtp/Stats.csv b/data/tariffplans/testtp/Stats.csv
index 93311a0e6..28fb633ff 100755
--- a/data/tariffplans/testtp/Stats.csv
+++ b/data/tariffplans/testtp/Stats.csv
@@ -1,2 +1,2 @@
#Tenant[0],Id[1],FilterType[2],FilterFieldName[3],FilterFieldValues[4],ActivationInterval[5],QueueLength[6],TTL[7],Metrics[8],Blocker[9],Stored[10],Weight[11],Thresholds[12]
-Tester,Stats1,*string,Account,1001;1002,2014-07-29T15:00:00Z,100,1s,*asr;*acd;*acc,true,true,20,THRESH1;THRESH2
\ No newline at end of file
+cgrates.org,Stats1,*string,Account,1001;1002,2014-07-29T15:00:00Z,100,1s,*asr;*acd;*acc,true,true,20,THRESH1;THRESH2
diff --git a/data/tariffplans/tutorial/Resources.csv b/data/tariffplans/tutorial/Resources.csv
index 30f655dcc..ecd659285 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]
-Tester,ResGroup1,*string,Account,1001;1002,2014-07-29T15:00:00Z,1s,7,,true,true,20,
-Tester,ResGroup1,*string_prefix,Destination,10;20,,,,,,,,
-Tester,ResGroup1,*rsr_fields,,Subject(~^1.*1$);Destination(1002),,,,,,,,
-Tester,ResGroup2,*destinations,Destination,DST_FS,2014-07-29T15:00:00Z,3600s,8,SPECIAL_1002,true,true,10,
-Tester,ResGroup3,*string,Account,3001,2014-07-29T15:00:00Z,1s,3,,true,true,20,
-#ResGroup3,*timings,SetupTime,PEAK,,,,,,,,
-#ResGroup3,*cdr_stats,,CDRST1:*min_ASR:34;CDRST_1001:*min_ASR:20,,,,,,,,
+cgrates.org,ResGroup1,*string,Account,1001;1002,2014-07-29T15:00:00Z,1s,7,,true,true,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,ResGroup3,*timings,SetupTime,PEAK,,,,,,,,
+#cgrates.org,ResGroup3,*stats,,CDRST1:*min_ASR:34;CDRST_1001:*min_ASR:20,,,,,,,,
diff --git a/engine/loader_csv_test.go b/engine/loader_csv_test.go
index 7c13357f8..39c0c3823 100755
--- a/engine/loader_csv_test.go
+++ b/engine/loader_csv_test.go
@@ -267,10 +267,10 @@ cgrates.org,mas,true,another,value,10
`
resProfiles = `
#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]
-Tester,ResGroup21,*string,HdrAccount,1001;1002,2014-07-29T15:00:00Z,1s,2,call,true,true,10,
-Tester,ResGroup21,*string_prefix,HdrDestination,10;20,,,,,,,,
-Tester,ResGroup21,*rsr_fields,,HdrSubject(~^1.*1$);HdrDestination(1002),,,,,,,,
-Tester,ResGroup22,*destinations,HdrDestination,DST_FS,2014-07-29T15:00:00Z,3600s,2,premium_call,true,true,10,
+cgrates.org,ResGroup21,*string,HdrAccount,1001;1002,2014-07-29T15:00:00Z,1s,2,call,true,true,10,
+cgrates.org,ResGroup21,*string_prefix,HdrDestination,10;20,,,,,,,,
+cgrates.org,ResGroup21,*rsr_fields,,HdrSubject(~^1.*1$);HdrDestination(1002),,,,,,,,
+cgrates.org,ResGroup22,*destinations,HdrDestination,DST_FS,2014-07-29T15:00:00Z,3600s,2,premium_call,true,true,10,
`
stats = `
#Tenant[0],Id[1],FilterType[2],FilterFieldName[3],FilterFieldValues[4],ActivationInterval[5],QueueLength[6],TTL[7],Metrics[8],Blocker[9],Stored[10],Weight[11],Thresholds[12]
@@ -1390,44 +1390,46 @@ func TestLoadReverseAliases(t *testing.T) {
}
func TestLoadResourceProfiles(t *testing.T) {
- eResProfiles := map[string]*utils.TPResource{
- "ResGroup21": &utils.TPResource{
- TPid: testTPID,
- Tenant: "Tester",
- ID: "ResGroup21",
- Filters: []*utils.TPRequestFilter{
- &utils.TPRequestFilter{Type: MetaString, FieldName: "HdrAccount", Values: []string{"1001", "1002"}},
- &utils.TPRequestFilter{Type: MetaStringPrefix, FieldName: "HdrDestination", Values: []string{"10", "20"}},
- &utils.TPRequestFilter{Type: MetaRSRFields, Values: []string{"HdrSubject(~^1.*1$)", "HdrDestination(1002)"}},
+ eResProfiles := map[string]map[string]*utils.TPResource{
+ "cgrates.org": map[string]*utils.TPResource{
+ "ResGroup21": &utils.TPResource{
+ TPid: testTPID,
+ Tenant: "cgrates.org",
+ ID: "ResGroup21",
+ Filters: []*utils.TPRequestFilter{
+ &utils.TPRequestFilter{Type: MetaString, FieldName: "HdrAccount", Values: []string{"1001", "1002"}},
+ &utils.TPRequestFilter{Type: MetaStringPrefix, FieldName: "HdrDestination", Values: []string{"10", "20"}},
+ &utils.TPRequestFilter{Type: MetaRSRFields, Values: []string{"HdrSubject(~^1.*1$)", "HdrDestination(1002)"}},
+ },
+ ActivationInterval: &utils.TPActivationInterval{
+ ActivationTime: "2014-07-29T15:00:00Z",
+ },
+ UsageTTL: "1s",
+ AllocationMessage: "call",
+ Weight: 10,
+ Limit: "2",
},
- ActivationInterval: &utils.TPActivationInterval{
- ActivationTime: "2014-07-29T15:00:00Z",
+ "ResGroup22": &utils.TPResource{
+ TPid: testTPID,
+ Tenant: "cgrates.org",
+ ID: "ResGroup22",
+ Filters: []*utils.TPRequestFilter{
+ &utils.TPRequestFilter{Type: MetaDestinations, FieldName: "HdrDestination", Values: []string{"DST_FS"}},
+ },
+ ActivationInterval: &utils.TPActivationInterval{
+ ActivationTime: "2014-07-29T15:00:00Z",
+ },
+ UsageTTL: "3600s",
+ AllocationMessage: "premium_call",
+ Blocker: true,
+ Stored: true,
+ Weight: 10,
+ Limit: "2",
},
- UsageTTL: "1s",
- AllocationMessage: "call",
- Weight: 10,
- Limit: "2",
- },
- "ResGroup22": &utils.TPResource{
- TPid: testTPID,
- Tenant: "Tester",
- ID: "ResGroup22",
- Filters: []*utils.TPRequestFilter{
- &utils.TPRequestFilter{Type: MetaDestinations, FieldName: "HdrDestination", Values: []string{"DST_FS"}},
- },
- ActivationInterval: &utils.TPActivationInterval{
- ActivationTime: "2014-07-29T15:00:00Z",
- },
- UsageTTL: "3600s",
- AllocationMessage: "premium_call",
- Blocker: true,
- Stored: true,
- Weight: 10,
- Limit: "2",
},
}
- if len(csvr.resProfiles) != len(eResProfiles) {
- t.Error("Failed to load resourceProfiles: ", len(csvr.resProfiles))
+ if len(csvr.resProfiles["cgrates.org"]) != len(eResProfiles["cgrates.org"]) {
+ t.Errorf("Failed to load resourceProfiles: %s", utils.ToIJSON(csvr.resProfiles))
} else if !reflect.DeepEqual(eResProfiles["ResGroup22"], csvr.resProfiles["ResGroup22"]) {
t.Errorf("Expecting: %+v, received: %+v", eResProfiles["ResGroup22"], csvr.resProfiles["ResGroup22"])
@@ -1457,10 +1459,10 @@ func TestLoadStats(t *testing.T) {
},
}
- if len(csvr.stats) != len(eStats) {
- t.Error("Failed to load stats: ", len(csvr.stats))
- } else if !reflect.DeepEqual(eStats["Stats1"], csvr.stats["Stats1"]) {
- t.Errorf("Expecting: %+v, received: %+v", eStats["Stats1"], csvr.stats["Stats1"])
+ if len(csvr.sqProfiles) != len(eStats) {
+ t.Error("Failed to load stats: ", len(csvr.sqProfiles))
+ } else if !reflect.DeepEqual(eStats["Stats1"], csvr.sqProfiles["Stats1"]) {
+ t.Errorf("Expecting: %+v, received: %+v", eStats["Stats1"], csvr.sqProfiles["Stats1"])
}
}
diff --git a/engine/loader_it_test.go b/engine/loader_it_test.go
index 168541ef5..8528ac7a7 100755
--- a/engine/loader_it_test.go
+++ b/engine/loader_it_test.go
@@ -309,21 +309,23 @@ func TestLoaderITWriteToDatabase(t *testing.T) {
}
}
- for k, rl := range loader.resProfiles {
- rcv, err := loader.dataStorage.GetResourceProfile(k, true, utils.NonTransactional)
- if err != nil {
- t.Error("Failed GetResourceProfile: ", err.Error())
- }
- rlT, err := APItoResource(rl, "UTC")
- if err != nil {
- t.Error(err)
- }
- if !reflect.DeepEqual(rlT, rcv) {
- t.Errorf("Expecting: %v, received: %v", rlT, rcv)
+ for _, mapIDs := range loader.resProfiles {
+ for _, rl := range mapIDs {
+ rcv, err := loader.dataStorage.GetResourceProfile(rl.Tenant, rl.ID, true, utils.NonTransactional)
+ if err != nil {
+ t.Error("Failed GetResourceProfile: ", err.Error())
+ }
+ rlT, err := APItoResource(rl, "UTC")
+ if err != nil {
+ t.Error(err)
+ }
+ if !reflect.DeepEqual(rlT, rcv) {
+ t.Errorf("Expecting: %v, received: %v", rlT, rcv)
+ }
}
}
- for k, st := range loader.stats {
+ for k, st := range loader.sqProfiles {
rcv, err := loader.dataStorage.GetStatQueueProfile(k)
if err != nil {
t.Error("Failed GetStatsQueue: ", err.Error())
diff --git a/engine/model_helpers.go b/engine/model_helpers.go
index c34ea2175..05a4916bd 100755
--- a/engine/model_helpers.go
+++ b/engine/model_helpers.go
@@ -1817,12 +1817,12 @@ type TpResources []*TpResource
func (tps TpResources) AsTPResources() (result []*utils.TPResource) {
mrl := make(map[string]*utils.TPResource)
for _, tp := range tps {
- rl, found := mrl[tp.Tag]
+ rl, found := mrl[tp.ID]
if !found {
rl = &utils.TPResource{
TPid: tp.Tpid,
Tenant: tp.Tenant,
- ID: tp.Tag,
+ ID: tp.ID,
Blocker: tp.Blocker,
Stored: tp.Stored,
}
@@ -1863,7 +1863,7 @@ func (tps TpResources) AsTPResources() (result []*utils.TPResource) {
FieldName: tp.FilterFieldName,
Values: strings.Split(tp.FilterFieldValues, utils.INFIELD_SEP)})
}
- mrl[tp.Tag] = rl
+ mrl[tp.ID] = rl
}
result = make([]*utils.TPResource, len(mrl))
i := 0
@@ -1880,9 +1880,11 @@ func APItoModelResource(rl *utils.TPResource) (mdls TpResources) {
}
for i, fltr := range rl.Filters {
mdl := &TpResource{
- Tpid: rl.TPid,
- Tenant: rl.Tenant,
- Tag: rl.ID,
+ Tpid: rl.TPid,
+ Tenant: rl.Tenant,
+ ID: rl.ID,
+ Blocker: rl.Blocker,
+ Stored: rl.Stored,
}
if i == 0 {
mdl.UsageTTL = rl.UsageTTL
diff --git a/engine/model_helpers_test.go b/engine/model_helpers_test.go
index bad16444a..7a911e381 100755
--- a/engine/model_helpers_test.go
+++ b/engine/model_helpers_test.go
@@ -713,8 +713,8 @@ func TestTpResourcesAsTpResources(t *testing.T) {
tps := []*TpResource{
&TpResource{
Tpid: "TEST_TPID",
- Tenant: "Tester",
- Tag: "ResGroup1",
+ Tenant: "cgrates.org",
+ ID: "ResGroup1",
FilterType: MetaStringPrefix,
FilterFieldName: "Destination",
FilterFieldValues: "+49151;+49161",
@@ -726,16 +726,16 @@ func TestTpResourcesAsTpResources(t *testing.T) {
Thresholds: "WARN_RES1;WARN_RES2"},
&TpResource{
Tpid: "TEST_TPID",
- Tag: "ResGroup1",
- Tenant: "Tester",
+ ID: "ResGroup1",
+ Tenant: "cgrates.org",
FilterType: MetaStringPrefix,
FilterFieldName: "Category",
FilterFieldValues: "call;inbound_call",
Thresholds: "WARN3"},
&TpResource{
Tpid: "TEST_TPID",
- Tenant: "Tester",
- Tag: "ResGroup2",
+ Tenant: "cgrates.org",
+ ID: "ResGroup2",
FilterType: MetaStringPrefix,
FilterFieldName: "Destination",
FilterFieldValues: "+40",
@@ -749,7 +749,7 @@ func TestTpResourcesAsTpResources(t *testing.T) {
&utils.TPResource{
TPid: tps[0].Tpid,
Tenant: tps[0].Tenant,
- ID: tps[0].Tag,
+ ID: tps[0].ID,
Filters: []*utils.TPRequestFilter{
&utils.TPRequestFilter{
Type: tps[0].FilterType,
@@ -774,7 +774,7 @@ func TestTpResourcesAsTpResources(t *testing.T) {
&utils.TPResource{
TPid: tps[2].Tpid,
Tenant: tps[2].Tenant,
- ID: tps[2].Tag,
+ ID: tps[2].ID,
Filters: []*utils.TPRequestFilter{
&utils.TPRequestFilter{
Type: tps[2].FilterType,
diff --git a/engine/models.go b/engine/models.go
index e769f00a9..0031f11f8 100755
--- a/engine/models.go
+++ b/engine/models.go
@@ -452,10 +452,10 @@ func (t TBLSMCosts) TableName() string {
}
type TpResource struct {
- ID int64
+ PK uint `gorm:"primary_key"`
Tpid string
Tenant string `index:"0" re:""`
- Tag string `index:"1" re:""`
+ ID string `index:"1" re:""`
FilterType string `index:"2" re:"^\*[A-Za-z].*"`
FilterFieldName string `index:"3" re:""`
FilterFieldValues string `index:"4" re:""`
diff --git a/engine/onstor_it_test.go b/engine/onstor_it_test.go
index f5b4709a1..6b326f23c 100644
--- a/engine/onstor_it_test.go
+++ b/engine/onstor_it_test.go
@@ -196,7 +196,7 @@ func testOnStorITSetReqFilterIndexes(t *testing.T) {
},
},
}
- if err := onStor.SetReqFilterIndexes(utils.ResourceProfilesIndex, idxes); err != nil {
+ if err := onStor.SetReqFilterIndexes(utils.ResourceProfilesStringIndex, idxes); err != nil {
t.Error(err)
}
}
@@ -227,7 +227,7 @@ func testOnStorITGetReqFilterIndexes(t *testing.T) {
},
},
}
- if idxes, err := onStor.GetReqFilterIndexes(utils.ResourceProfilesIndex); err != nil {
+ if idxes, err := onStor.GetReqFilterIndexes(utils.ResourceProfilesStringIndex); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(eIdxes, idxes) {
t.Errorf("Expecting: %+v, received: %+v", eIdxes, idxes)
@@ -242,12 +242,12 @@ func testOnStorITMatchReqFilterIndex(t *testing.T) {
"RL1": true,
"RL2": true,
}
- if rcvMp, err := onStor.MatchReqFilterIndex(utils.ResourceProfilesIndex, "Account", "1002"); err != nil {
+ if rcvMp, err := onStor.MatchReqFilterIndex(utils.ResourceProfilesStringIndex, "Account", "1002"); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(eMp, rcvMp) {
t.Errorf("Expecting: %+v, received: %+v", eMp, rcvMp)
}
- if _, err := onStor.MatchReqFilterIndex(utils.ResourceProfilesIndex, "NonexistentField", "1002"); err == nil || err != utils.ErrNotFound {
+ if _, err := onStor.MatchReqFilterIndex(utils.ResourceProfilesStringIndex, "NonexistentField", "1002"); err == nil || err != utils.ErrNotFound {
t.Error(err)
}
}
@@ -782,6 +782,7 @@ func testOnStorITCacheReverseAlias(t *testing.T) {
func testOnStorITCacheResourceProfile(t *testing.T) {
rCfg := &ResourceProfile{
+ Tenant: "cgrates.org",
ID: "RL_TEST",
Weight: 10,
Filters: []*RequestFilter{
@@ -799,19 +800,19 @@ func testOnStorITCacheResourceProfile(t *testing.T) {
if err := onStor.SetResourceProfile(rCfg, utils.NonTransactional); err != nil {
t.Error(err)
}
- expectedR := []string{"rsp_RL_TEST"}
+ expectedR := []string{"rsp_cgrates.org:RL_TEST"}
if itm, err := onStor.GetKeysForPrefix(utils.ResourceProfilesPrefix); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(expectedR, itm) {
t.Errorf("Expected : %+v, but received %+v", expectedR, itm)
}
- if _, hasIt := cache.Get(utils.ResourceProfilesPrefix + rCfg.ID); hasIt {
+ if _, hasIt := cache.Get(utils.ResourceProfilesPrefix + rCfg.TenantID()); hasIt {
t.Error("Already in cache")
}
- if err := onStor.CacheDataFromDB(utils.ResourceProfilesPrefix, []string{rCfg.ID}, false); err != nil {
+ if err := onStor.CacheDataFromDB(utils.ResourceProfilesPrefix, []string{rCfg.TenantID()}, false); err != nil {
t.Error(err)
}
- if itm, hasIt := cache.Get(utils.ResourceProfilesPrefix + rCfg.ID); !hasIt {
+ if itm, hasIt := cache.Get(utils.ResourceProfilesPrefix + rCfg.TenantID()); !hasIt {
t.Error("Did not cache")
} else if rcv := itm.(*ResourceProfile); !reflect.DeepEqual(rCfg, rcv) {
t.Errorf("Expecting: %+v, received: %+v", rCfg, rcv)
@@ -853,7 +854,8 @@ func testOnStorITCacheTiming(t *testing.T) {
func testOnStorITCacheResource(t *testing.T) {
res := &Resource{
- ID: "RL1",
+ Tenant: "cgrates.org",
+ ID: "RL1",
Usages: map[string]*ResourceUsage{
"RU1": &ResourceUsage{
ID: "RU1",
@@ -866,20 +868,20 @@ func testOnStorITCacheResource(t *testing.T) {
if err := onStor.SetResource(res); err != nil {
t.Error(err)
}
- expectedT := []string{"res_RL1"}
+ expectedT := []string{"res_cgrates.org:RL1"}
if itm, err := onStor.GetKeysForPrefix(utils.ResourcesPrefix); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(expectedT, itm) {
t.Errorf("Expected : %+v, but received %+v", expectedT, itm)
}
- if _, hasIt := cache.Get(utils.ResourcesPrefix + res.ID); hasIt {
+ if _, hasIt := cache.Get(utils.ResourcesPrefix + res.TenantID()); hasIt {
t.Error("Already in cache")
}
- if err := onStor.CacheDataFromDB(utils.ResourcesPrefix, []string{res.ID}, false); err != nil {
+ if err := onStor.CacheDataFromDB(utils.ResourcesPrefix, []string{res.TenantID()}, false); err != nil {
t.Error(err)
}
- if itm, hasIt := cache.Get(utils.ResourcesPrefix + res.ID); !hasIt {
+ if itm, hasIt := cache.Get(utils.ResourcesPrefix + res.TenantID()); !hasIt {
t.Error("Did not cache")
} else if rcv := itm.(*Resource); !reflect.DeepEqual(res, rcv) {
t.Errorf("Expecting: %+v, received: %+v", res, rcv)
@@ -1798,13 +1800,13 @@ func testOnStorITCRUDResourceProfile(t *testing.T) {
Thresholds: []string{"TEST_ACTIONS"},
UsageTTL: time.Duration(1 * time.Millisecond),
}
- if _, rcvErr := onStor.GetResourceProfile(rL.ID, true, utils.NonTransactional); rcvErr != utils.ErrNotFound {
+ if _, rcvErr := onStor.GetResourceProfile(rL.Tenant, rL.ID, true, utils.NonTransactional); rcvErr != utils.ErrNotFound {
t.Error(rcvErr)
}
if err := onStor.SetResourceProfile(rL, utils.NonTransactional); err != nil {
t.Error(err)
}
- if rcv, err := onStor.GetResourceProfile(rL.ID, true, utils.NonTransactional); err != nil {
+ if rcv, err := onStor.GetResourceProfile(rL.Tenant, rL.ID, true, utils.NonTransactional); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(rL, rcv) {
t.Errorf("Expecting: %v, received: %v", rL, rcv)
@@ -1817,7 +1819,7 @@ func testOnStorITCRUDResourceProfile(t *testing.T) {
// t.Error(rcvErr)
// }
//
- if rcv, err := onStor.GetResourceProfile(rL.ID, false, utils.NonTransactional); err != nil {
+ if rcv, err := onStor.GetResourceProfile(rL.Tenant, rL.ID, false, utils.NonTransactional); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(rL, rcv) {
t.Errorf("Expecting: %v, received: %v", rL, rcv)
@@ -1825,17 +1827,18 @@ func testOnStorITCRUDResourceProfile(t *testing.T) {
// if err = onStor.SelectDatabase(onStorCfg); err != nil {
// t.Error(err)
// }
- if err := onStor.RemoveResourceProfile(rL.ID, utils.NonTransactional); err != nil {
+ if err := onStor.RemoveResourceProfile(rL.Tenant, rL.ID, utils.NonTransactional); err != nil {
t.Error(err)
}
- if _, rcvErr := onStor.GetResourceProfile(rL.ID, true, utils.NonTransactional); rcvErr != utils.ErrNotFound {
+ if _, rcvErr := onStor.GetResourceProfile(rL.Tenant, rL.ID, true, utils.NonTransactional); rcvErr != utils.ErrNotFound {
t.Error(rcvErr)
}
}
func testOnStorITCRUDResource(t *testing.T) {
res := &Resource{
- ID: "RL1",
+ Tenant: "cgrates.org",
+ ID: "RL1",
Usages: map[string]*ResourceUsage{
"RU1": &ResourceUsage{
ID: "RU1",
@@ -1845,26 +1848,26 @@ func testOnStorITCRUDResource(t *testing.T) {
},
TTLIdx: []string{"RU1"},
}
- if _, rcvErr := onStor.GetResource("RL1", true, utils.NonTransactional); rcvErr != nil && rcvErr != utils.ErrNotFound {
+ if _, rcvErr := onStor.GetResource("cgrates.org", "RL1", true, utils.NonTransactional); rcvErr != nil && rcvErr != utils.ErrNotFound {
t.Error(rcvErr)
}
if err := onStor.SetResource(res); err != nil {
t.Error(err)
}
- if rcv, err := onStor.GetResource("RL1", true, utils.NonTransactional); err != nil {
+ if rcv, err := onStor.GetResource("cgrates.org", "RL1", true, utils.NonTransactional); err != nil {
t.Error(err)
} else if !(reflect.DeepEqual(res, rcv)) {
t.Errorf("Expecting: %v, received: %v", res, rcv)
}
- if rcv, err := onStor.GetResource("RL1", false, utils.NonTransactional); err != nil {
+ if rcv, err := onStor.GetResource("cgrates.org", "RL1", false, utils.NonTransactional); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(res, rcv) {
t.Errorf("Expecting: %v, received: %v", res, rcv)
}
- if err := onStor.RemoveResource(res.ID, utils.NonTransactional); err != nil {
+ if err := onStor.RemoveResource(res.Tenant, res.ID, utils.NonTransactional); err != nil {
t.Error(err)
}
- if _, rcvErr := onStor.GetResource(res.ID, true, utils.NonTransactional); rcvErr != utils.ErrNotFound {
+ if _, rcvErr := onStor.GetResource(res.Tenant, res.ID, true, utils.NonTransactional); rcvErr != utils.ErrNotFound {
t.Error(rcvErr)
}
}
diff --git a/engine/resources.go b/engine/resources.go
index 40a2c5ba7..54131b224 100755
--- a/engine/resources.go
+++ b/engine/resources.go
@@ -52,6 +52,11 @@ type ResourceProfile struct {
Thresholds []string // Thresholds to check after changing Limit
}
+// TenantID returns unique identifier of the ResourceProfile in a multi-tenant environment
+func (rp *ResourceProfile) TenantID() string {
+ return utils.ConcatenatedKey(rp.Tenant, rp.ID)
+}
+
// ResourceUsage represents an usage counted
type ResourceUsage struct {
Tenant string
@@ -60,11 +65,22 @@ type ResourceUsage struct {
Units float64 // Number of units used
}
+func (ru *ResourceUsage) TenantID() string {
+ return utils.ConcatenatedKey(ru.Tenant, ru.ID)
+}
+
// isActive checks ExpiryTime at some time
func (ru *ResourceUsage) isActive(atTime time.Time) bool {
return ru.ExpiryTime.IsZero() || ru.ExpiryTime.Sub(atTime) > 0
}
+// clone duplicates ru
+func (ru *ResourceUsage) Clone() (cln *ResourceUsage) {
+ cln = new(ResourceUsage)
+ *cln = *ru
+ return
+}
+
// Resource represents a resource in the system
// not thread safe, needs locking at process level
type Resource struct {
@@ -72,11 +88,17 @@ type Resource struct {
ID string
Usages map[string]*ResourceUsage
TTLIdx []string // holds ordered list of ResourceIDs based on their TTL, empty if feature is disabled
+ ttl *time.Duration // time to leave for this resource, picked up on each Resource initialization out of config
tUsage *float64 // sum of all usages
dirty *bool // the usages were modified, needs save, *bool so we only save if enabled in config
rPrf *ResourceProfile // for ordering purposes
}
+// TenantID returns the unique ID in a multi-tenant environment
+func (r *Resource) TenantID() string {
+ return utils.ConcatenatedKey(r.Tenant, r.ID)
+}
+
// removeExpiredUnits removes units which are expired from the resource
func (r *Resource) removeExpiredUnits() {
var firstActive int
@@ -123,12 +145,19 @@ func (r *Resource) totalUsage() (tU float64) {
// recordUsage records a new usage
func (r *Resource) recordUsage(ru *ResourceUsage) (err error) {
if _, hasID := r.Usages[ru.ID]; hasID {
- return fmt.Errorf("duplicate resource usage with id: %s", ru.ID)
+ return fmt.Errorf("duplicate resource usage with id: %s", ru.TenantID())
+ }
+ if r.ttl != nil {
+ ru = ru.Clone() // don't influence the initial ru
+ ru.ExpiryTime = time.Now().Add(*r.ttl)
}
r.Usages[ru.ID] = ru
if r.tUsage != nil {
*r.tUsage += ru.Units
}
+ if !ru.ExpiryTime.IsZero() {
+ r.TTLIdx = append(r.TTLIdx, ru.ID)
+ }
return
}
@@ -136,12 +165,20 @@ func (r *Resource) recordUsage(ru *ResourceUsage) (err error) {
func (r *Resource) clearUsage(ruID string) (err error) {
ru, hasIt := r.Usages[ruID]
if !hasIt {
- return fmt.Errorf("Cannot find usage record with id: %s", ruID)
+ return fmt.Errorf("cannot find usage record with id: %s", ruID)
+ }
+ if !ru.ExpiryTime.IsZero() {
+ for i, ruIDIdx := range r.TTLIdx {
+ if ruIDIdx == ruID {
+ r.TTLIdx = append(r.TTLIdx[:i], r.TTLIdx[i+1:]...)
+ break
+ }
+ }
}
- delete(r.Usages, ruID)
if r.tUsage != nil {
*r.tUsage -= ru.Units
}
+ delete(r.Usages, ruID)
return
}
@@ -165,30 +202,38 @@ func (rs Resources) recordUsage(ru *ResourceUsage) (err error) {
}
if err != nil {
for _, r := range rs[:nonReservedIdx] {
- r.clearUsage(ru.ID) // best effort
+ r.clearUsage(ru.TenantID()) // best effort
}
}
return
}
// clearUsage gives back the units to the pool
-func (rs Resources) clearUsage(ruID string) (err error) {
+func (rs Resources) clearUsage(ruTntID string) (err error) {
for _, r := range rs {
- if errClear := r.clearUsage(ruID); errClear != nil {
- utils.Logger.Warning(fmt.Sprintf(", err: %s", errClear.Error()))
+ if errClear := r.clearUsage(ruTntID); errClear != nil {
+ utils.Logger.Warning(fmt.Sprintf(", clear ruID: %s, err: %s", ruTntID, errClear.Error()))
err = errClear
}
}
return
}
-// ids returns list of resource IDs in resources
-func (rs Resources) ids() (ids []string) {
- ids = make([]string, len(rs))
+// tenantIDs returns list of TenantIDs in resources
+func (rs Resources) tenantIDs() []*utils.TenantID {
+ tntIDs := make([]*utils.TenantID, len(rs))
for i, r := range rs {
- ids[i] = r.ID
+ tntIDs[i] = &utils.TenantID{r.Tenant, r.ID}
}
- return
+ return tntIDs
+}
+
+func (rs Resources) tenatIDsStr() []string {
+ ids := make([]string, len(rs))
+ for i, r := range rs {
+ ids[i] = r.TenantID()
+ }
+ return ids
}
// AllocateResource attempts allocating resources for a *ResourceUsage
@@ -198,17 +243,18 @@ func (rs Resources) AllocateResource(ru *ResourceUsage, dryRun bool) (alcMessage
if len(rs) == 0 {
return "", utils.ErrResourceUnavailable
}
- lockIDs := utils.PrefixSliceItems(rs.ids(), utils.ResourcesPrefix)
+ lockIDs := utils.PrefixSliceItems(rs.tenatIDsStr(), utils.ResourcesPrefix)
guardian.Guardian.GuardIDs(config.CgrConfig().LockingTimeout, lockIDs...)
defer guardian.Guardian.UnguardIDs(lockIDs...)
// Simulate resource usage
for _, r := range rs {
if r.rPrf.Limit >= r.totalUsage()+ru.Units {
if alcMessage == "" {
- alcMessage = r.rPrf.AllocationMessage
- }
- if alcMessage == "" { // rl.AllocationMessage is not populated
- alcMessage = r.rPrf.ID
+ if r.rPrf.AllocationMessage != "" {
+ alcMessage = r.rPrf.AllocationMessage
+ } else {
+ alcMessage = r.rPrf.ID
+ }
}
}
}
@@ -229,7 +275,7 @@ func NewResourceService(dataDB DataDB, storeInterval time.Duration,
statS = nil
}
return &ResourceService{dataDB: dataDB, statS: statS,
- lcEventResources: make(map[string][]string),
+ lcEventResources: make(map[string][]*utils.TenantID),
storedResources: make(utils.StringMap),
storeInterval: storeInterval, stopBackup: make(chan struct{})}, nil
}
@@ -238,7 +284,7 @@ func NewResourceService(dataDB DataDB, storeInterval time.Duration,
type ResourceService struct {
dataDB DataDB // So we can load the data in cache and index it
statS rpcclient.RpcClientConnection // allows applying filters based on stats
- lcEventResources map[string][]string // cache recording resources for events in alocation phase
+ lcEventResources map[string][]*utils.TenantID // cache recording resources for events in alocation phase
lcERMux sync.RWMutex // protects the lcEventResources
storedResources utils.StringMap // keep a record of resources which need saving, map[resID]bool
srMux sync.RWMutex // protects storedResources
@@ -335,7 +381,7 @@ func (rS *ResourceService) cachedResourcesForEvent(evUUID string) (rs Resources)
if rIDsIf, has := cache.Get(utils.EventResourcesPrefix + evUUID); !has {
return nil
} else if rIDsIf != nil {
- rIDs = rIDsIf.([]string)
+ rIDs = rIDsIf.([]*utils.TenantID)
}
shortCached = true
}
@@ -343,11 +389,14 @@ func (rS *ResourceService) cachedResourcesForEvent(evUUID string) (rs Resources)
if len(rIDs) == 0 {
return
}
- lockIDs := utils.PrefixSliceItems(rIDs, utils.ResourcesPrefix)
+ lockIDs := make([]string, len(rIDs))
+ for i, rTid := range rIDs {
+ lockIDs[i] = utils.ResourcesPrefix + rTid.TenantID()
+ }
guardian.Guardian.GuardIDs(config.CgrConfig().LockingTimeout, lockIDs...)
defer guardian.Guardian.UnguardIDs(lockIDs...)
- for i, rID := range rIDs {
- if r, err := rS.dataDB.GetResource(rID, false, ""); err != nil {
+ for i, rTid := range rIDs {
+ if r, err := rS.dataDB.GetResource(rTid.Tenant, rTid.ID, false, ""); err != nil {
utils.Logger.Warning(
fmt.Sprintf(" force-uncaching resources for evUUID: <%s>, error: <%s>",
evUUID, err.Error()))
@@ -368,17 +417,17 @@ func (rS *ResourceService) cachedResourcesForEvent(evUUID string) (rs Resources)
}
// matchingResourcesForEvent returns ordered list of matching resources which are active by the time of the call
-func (rS *ResourceService) matchingResourcesForEvent(ev map[string]interface{}) (rs Resources, err error) {
+func (rS *ResourceService) matchingResourcesForEvent(tenant string, ev map[string]interface{}) (rs Resources, err error) {
matchingResources := make(map[string]*Resource)
- rIDs, err := matchingItemIDsForEvent(ev, rS.dataDB, utils.ResourceProfilesIndex)
+ rIDs, err := matchingItemIDsForEvent(ev, rS.dataDB, utils.ResourceProfilesStringIndex+tenant)
if err != nil {
return nil, err
}
- lockIDs := utils.PrefixSliceItems(rIDs.Slice(), utils.ResourceProfilesIndex)
+ lockIDs := utils.PrefixSliceItems(rIDs.Slice(), utils.ResourceProfilesStringIndex)
guardian.Guardian.GuardIDs(config.CgrConfig().LockingTimeout, lockIDs...)
defer guardian.Guardian.UnguardIDs(lockIDs...)
for resName := range rIDs {
- rPrf, err := rS.dataDB.GetResourceProfile(resName, false, utils.NonTransactional)
+ rPrf, err := rS.dataDB.GetResourceProfile(tenant, resName, false, utils.NonTransactional)
if err != nil {
if err == utils.ErrNotFound {
continue
@@ -401,13 +450,16 @@ func (rS *ResourceService) matchingResourcesForEvent(ev map[string]interface{})
if !passAllFilters {
continue
}
- r, err := rS.dataDB.GetResource(rPrf.ID, false, "")
+ r, err := rS.dataDB.GetResource(rPrf.Tenant, rPrf.ID, false, "")
if err != nil {
return nil, err
}
if rPrf.Stored {
r.dirty = utils.BoolPointer(false)
}
+ if rPrf.UsageTTL > 0 {
+ r.ttl = utils.DurationPointer(rPrf.UsageTTL)
+ }
r.rPrf = rPrf
matchingResources[rPrf.ID] = r // Cannot save it here since we could have errors after and resource will remain unused
}
@@ -429,32 +481,46 @@ func (rS *ResourceService) matchingResourcesForEvent(ev map[string]interface{})
}
// V1ResourcesForEvent returns active resource configs matching the event
-func (rS *ResourceService) V1ResourcesForEvent(ev map[string]interface{}, reply *[]*ResourceProfile) error {
- matchingRLForEv, err := rS.matchingResourcesForEvent(ev)
- if err != nil {
- return err
+func (rS *ResourceService) V1ResourcesForEvent(args utils.ArgRSv1ResourceUsage, reply *[]*ResourceProfile) (err error) {
+ if args.Tenant == "" {
+ return utils.NewErrMandatoryIeMissing("Tenant")
}
- if len(matchingRLForEv) == 0 {
+ var mtcRLs Resources
+ if args.UsageID != "" { // only cached if UsageID is present
+ mtcRLs = rS.cachedResourcesForEvent(args.TenantID())
+ }
+ if mtcRLs == nil {
+ if mtcRLs, err = rS.matchingResourcesForEvent(args.Tenant, args.Event); err != nil {
+ return err
+ }
+ cache.Set(utils.EventResourcesPrefix+args.TenantID(), mtcRLs.tenantIDs(), true, "")
+ }
+ if len(mtcRLs) == 0 {
return utils.ErrNotFound
}
- for _, r := range matchingRLForEv {
+ for _, r := range mtcRLs {
*reply = append(*reply, r.rPrf)
}
return nil
}
// V1AllowUsage queries service to find if an Usage is allowed
-func (rS *ResourceService) V1AllowUsage(args utils.AttrRLsResourceUsage, allow *bool) (err error) {
- mtcRLs := rS.cachedResourcesForEvent(args.UsageID)
+func (rS *ResourceService) V1AllowUsage(args utils.ArgRSv1ResourceUsage, allow *bool) (err error) {
+ if missing := utils.MissingStructFields(&args, []string{"Tenant", "UsageID"}); len(missing) != 0 { //Params missing
+ return utils.NewErrMandatoryIeMissing(missing...)
+ }
+ mtcRLs := rS.cachedResourcesForEvent(args.TenantID())
if mtcRLs == nil {
- if mtcRLs, err = rS.matchingResourcesForEvent(args.Event); err != nil {
+ if mtcRLs, err = rS.matchingResourcesForEvent(args.Tenant, args.Event); err != nil {
return err
}
- cache.Set(utils.EventResourcesPrefix+args.UsageID, mtcRLs.ids(), true, "")
+ cache.Set(utils.EventResourcesPrefix+args.TenantID(), mtcRLs.tenantIDs(), true, "")
}
if _, err = mtcRLs.AllocateResource(
- &ResourceUsage{ID: args.UsageID,
- Units: args.Units}, true); err != nil {
+ &ResourceUsage{
+ Tenant: args.Tenant,
+ ID: args.UsageID,
+ Units: args.Units}, true); err != nil {
if err == utils.ErrResourceUnavailable {
cache.Set(utils.EventResourcesPrefix+args.UsageID, nil, true, "")
err = nil
@@ -467,11 +533,14 @@ func (rS *ResourceService) V1AllowUsage(args utils.AttrRLsResourceUsage, allow *
}
// V1AllocateResource is called when a resource requires allocation
-func (rS *ResourceService) V1AllocateResource(args utils.AttrRLsResourceUsage, reply *string) (err error) {
+func (rS *ResourceService) V1AllocateResource(args utils.ArgRSv1ResourceUsage, reply *string) (err error) {
+ if missing := utils.MissingStructFields(&args, []string{"Tenant", "UsageID"}); len(missing) != 0 { //Params missing
+ return utils.NewErrMandatoryIeMissing(missing...)
+ }
var wasCached bool
mtcRLs := rS.cachedResourcesForEvent(args.UsageID)
if mtcRLs == nil {
- if mtcRLs, err = rS.matchingResourcesForEvent(args.Event); err != nil {
+ if mtcRLs, err = rS.matchingResourcesForEvent(args.Tenant, args.Event); err != nil {
return
}
} else {
@@ -493,7 +562,7 @@ func (rS *ResourceService) V1AllocateResource(args utils.AttrRLsResourceUsage, r
}
if wasShortCached || !wasCached {
rS.lcERMux.Lock()
- rS.lcEventResources[args.UsageID] = mtcRLs.ids()
+ rS.lcEventResources[args.UsageID] = mtcRLs.tenantIDs()
rS.lcERMux.Unlock()
}
// index it for storing
@@ -512,10 +581,13 @@ func (rS *ResourceService) V1AllocateResource(args utils.AttrRLsResourceUsage, r
}
// V1ReleaseResource is called when we need to clear an allocation
-func (rS *ResourceService) V1ReleaseResource(args utils.AttrRLsResourceUsage, reply *string) (err error) {
+func (rS *ResourceService) V1ReleaseResource(args utils.ArgRSv1ResourceUsage, reply *string) (err error) {
+ if missing := utils.MissingStructFields(&args, []string{"Tenant", "UsageID"}); len(missing) != 0 { //Params missing
+ return utils.NewErrMandatoryIeMissing(missing...)
+ }
mtcRLs := rS.cachedResourcesForEvent(args.UsageID)
if mtcRLs == nil {
- if mtcRLs, err = rS.matchingResourcesForEvent(args.Event); err != nil {
+ if mtcRLs, err = rS.matchingResourcesForEvent(args.Tenant, args.Event); err != nil {
return
}
}
diff --git a/engine/resources_test.go b/engine/resources_test.go
index 66816ad12..b1b154b06 100644
--- a/engine/resources_test.go
+++ b/engine/resources_test.go
@@ -32,23 +32,27 @@ var (
rs Resources
)
-func TestRSRecordUsage(t *testing.T) {
+func TestRSRecordUsage1(t *testing.T) {
ru1 = &ResourceUsage{
+ Tenant: "cgrates.org",
ID: "RU1",
ExpiryTime: time.Date(2014, 7, 3, 13, 43, 0, 1, time.UTC),
Units: 2,
}
ru2 = &ResourceUsage{
+ Tenant: "cgrates.org",
ID: "RU2",
ExpiryTime: time.Date(2014, 7, 3, 13, 43, 0, 1, time.UTC),
Units: 2,
}
r1 = &Resource{
- ID: "RL1",
+ Tenant: "cgrates.org",
+ ID: "RL1",
rPrf: &ResourceProfile{
- ID: "RL1",
+ Tenant: "cgrates.org",
+ ID: "RL1",
Filters: []*RequestFilter{
&RequestFilter{
Type: MetaString,
@@ -83,13 +87,13 @@ func TestRSRecordUsage(t *testing.T) {
t.Error(err.Error())
} else {
if err := r1.recordUsage(ru1); err == nil {
- t.Error("Duplicate ResourceUsage id should not be allowed")
+ t.Error("duplicate ResourceUsage id should not be allowed")
}
if _, found := r1.Usages[ru2.ID]; !found {
t.Error("ResourceUsage was not recorded")
}
if *r1.tUsage != 4 {
- t.Errorf("Expecting: %+v, received: %+v", 4, r1.tUsage)
+ t.Errorf("expecting: %+v, received: %+v", 4, r1.tUsage)
}
}
@@ -126,7 +130,8 @@ func TestRSUsedUnits(t *testing.T) {
func TestRSRsort(t *testing.T) {
r2 = &Resource{
- ID: "RL2",
+ Tenant: "cgrates.org",
+ ID: "RL2",
rPrf: &ResourceProfile{
ID: "RL2",
Filters: []*RequestFilter{
@@ -246,9 +251,11 @@ func TestRSAllocateResource(t *testing.T) {
// TestRSCacheSetGet assurace the presence of private params in cached resource
func TestRSCacheSetGet(t *testing.T) {
r := &Resource{
- ID: "RL",
+ Tenant: "cgrates.org",
+ ID: "RL",
rPrf: &ResourceProfile{
- ID: "RL",
+ Tenant: "cgrates.org",
+ ID: "RL",
Filters: []*RequestFilter{
&RequestFilter{
Type: MetaString,
@@ -273,6 +280,7 @@ func TestRSCacheSetGet(t *testing.T) {
},
Usages: map[string]*ResourceUsage{
"RU2": &ResourceUsage{
+ Tenant: "cgrates.org",
ID: "RU2",
ExpiryTime: time.Date(2014, 7, 3, 13, 43, 0, 1, time.UTC),
Units: 2,
@@ -281,7 +289,7 @@ func TestRSCacheSetGet(t *testing.T) {
tUsage: utils.Float64Pointer(2),
dirty: utils.BoolPointer(true),
}
- key := utils.ResourcesPrefix + r.ID
+ key := utils.ResourcesPrefix + r.TenantID()
cache.Set(key, r, true, "")
if x, ok := cache.Get(key); !ok {
t.Error("not in cache")
diff --git a/engine/storage_interface.go b/engine/storage_interface.go
index 9b4c4cb92..dc199ccc8 100755
--- a/engine/storage_interface.go
+++ b/engine/storage_interface.go
@@ -99,12 +99,12 @@ type DataDB interface {
RemoveAlias(string, string) error
SetReverseAlias(*Alias, string) error
GetReverseAlias(string, bool, string) ([]string, error)
- GetResourceProfile(string, bool, string) (*ResourceProfile, error)
+ GetResourceProfile(string, string, bool, string) (*ResourceProfile, error)
SetResourceProfile(*ResourceProfile, string) error
- RemoveResourceProfile(string, string) error
- GetResource(string, bool, string) (*Resource, error)
+ RemoveResourceProfile(string, string, string) error
+ GetResource(string, string, bool, string) (*Resource, error)
SetResource(*Resource) error
- RemoveResource(string, string) error
+ RemoveResource(string, string, string) error
GetTiming(string, bool, string) (*utils.TPTiming, error)
SetTiming(*utils.TPTiming, string) error
RemoveTiming(string, string) error
diff --git a/engine/storage_map.go b/engine/storage_map.go
index 22a790f87..fd7359a56 100755
--- a/engine/storage_map.go
+++ b/engine/storage_map.go
@@ -193,6 +193,7 @@ func (ms *MapStorage) PreloadCacheForPrefix(prefix string) error {
// CacheDataFromDB loads data to cache,
// prefix represents the cache prefix, IDs should be nil if all available data should be loaded
+// ToDo: convert IDs into []*utils.TenantIDs when infrastructure will be ready
func (ms *MapStorage) CacheDataFromDB(prefix string, IDs []string, mustBeCached bool) (err error) {
if !utils.IsSliceMember([]string{utils.DESTINATION_PREFIX,
utils.REVERSE_DESTINATION_PREFIX,
@@ -273,9 +274,11 @@ func (ms *MapStorage) CacheDataFromDB(prefix string, IDs []string, mustBeCached
case utils.REVERSE_ALIASES_PREFIX:
_, err = ms.GetReverseAlias(dataID, true, utils.NonTransactional)
case utils.ResourceProfilesPrefix:
- _, err = ms.GetResourceProfile(dataID, true, utils.NonTransactional)
+ tntID := utils.NewTenantID(dataID)
+ _, err = ms.GetResourceProfile(tntID.Tenant, tntID.ID, true, utils.NonTransactional)
case utils.ResourcesPrefix:
- _, err = ms.GetResource(dataID, true, utils.NonTransactional)
+ tntID := utils.NewTenantID(dataID)
+ _, err = ms.GetResource(tntID.Tenant, tntID.ID, true, utils.NonTransactional)
case utils.TimingsPrefix:
_, err = ms.GetTiming(dataID, true, utils.NonTransactional)
}
@@ -306,7 +309,9 @@ func (ms *MapStorage) HasData(categ, subject string) (bool, error) {
ms.mu.RLock()
defer ms.mu.RUnlock()
switch categ {
- case utils.DESTINATION_PREFIX, utils.RATING_PLAN_PREFIX, utils.RATING_PROFILE_PREFIX, utils.ACTION_PREFIX, utils.ACTION_PLAN_PREFIX, utils.ACCOUNT_PREFIX, utils.DERIVEDCHARGERS_PREFIX, utils.ResourcesPrefix:
+ case utils.DESTINATION_PREFIX, utils.RATING_PLAN_PREFIX, utils.RATING_PROFILE_PREFIX,
+ utils.ACTION_PREFIX, utils.ACTION_PLAN_PREFIX, utils.ACCOUNT_PREFIX, utils.DERIVEDCHARGERS_PREFIX,
+ utils.ResourcesPrefix, utils.StatQueuePrefix:
_, exists := ms.dict[categ+subject]
return exists, nil
}
@@ -1274,10 +1279,10 @@ func (ms *MapStorage) GetSMCost(cgrid, source, runid, originHost, originID strin
return
}
-func (ms *MapStorage) GetResourceProfile(id string, skipCache bool, transactionID string) (rsp *ResourceProfile, err error) {
+func (ms *MapStorage) GetResourceProfile(tenant, id string, skipCache bool, transactionID string) (rsp *ResourceProfile, err error) {
ms.mu.RLock()
defer ms.mu.RUnlock()
- key := utils.ResourceProfilesPrefix + id
+ key := utils.ResourceProfilesPrefix + utils.ConcatenatedKey(tenant, id)
if !skipCache {
if x, ok := cache.Get(key); ok {
if x != nil {
@@ -1311,23 +1316,23 @@ func (ms *MapStorage) SetResourceProfile(r *ResourceProfile, transactionID strin
if err != nil {
return err
}
- ms.dict[utils.ResourceProfilesPrefix+r.ID] = result
+ ms.dict[utils.ResourceProfilesPrefix+r.TenantID()] = result
return nil
}
-func (ms *MapStorage) RemoveResourceProfile(id string, transactionID string) error {
+func (ms *MapStorage) RemoveResourceProfile(tenant, id string, transactionID string) error {
ms.mu.Lock()
defer ms.mu.Unlock()
- key := utils.ResourceProfilesPrefix + id
+ key := utils.ResourceProfilesPrefix + utils.ConcatenatedKey(tenant, id)
delete(ms.dict, key)
cache.RemKey(key, cacheCommit(transactionID), transactionID)
return nil
}
-func (ms *MapStorage) GetResource(id string, skipCache bool, transactionID string) (r *Resource, err error) {
+func (ms *MapStorage) GetResource(tenant, id string, skipCache bool, transactionID string) (r *Resource, err error) {
ms.mu.RLock()
defer ms.mu.RUnlock()
- key := utils.ResourcesPrefix + id
+ key := utils.ResourcesPrefix + utils.ConcatenatedKey(tenant, id)
if !skipCache {
if x, ok := cache.Get(key); ok {
if x != nil {
@@ -1356,14 +1361,14 @@ func (ms *MapStorage) SetResource(r *Resource) (err error) {
if err != nil {
return err
}
- ms.dict[utils.ResourcesPrefix+r.ID] = result
+ ms.dict[utils.ResourcesPrefix+r.TenantID()] = result
return
}
-func (ms *MapStorage) RemoveResource(id string, transactionID string) (err error) {
+func (ms *MapStorage) RemoveResource(tenant, id string, transactionID string) (err error) {
ms.mu.Lock()
defer ms.mu.Unlock()
- key := utils.ResourcesPrefix + id
+ key := utils.ResourcesPrefix + utils.ConcatenatedKey(tenant, id)
delete(ms.dict, key)
cache.RemKey(key, cacheCommit(transactionID), transactionID)
return
diff --git a/engine/storage_mongo_datadb.go b/engine/storage_mongo_datadb.go
index c375fc31f..95476f443 100755
--- a/engine/storage_mongo_datadb.go
+++ b/engine/storage_mongo_datadb.go
@@ -21,7 +21,6 @@ package engine
import (
"bytes"
"compress/zlib"
- "errors"
"fmt"
"io/ioutil"
"strings"
@@ -327,7 +326,7 @@ func (ms *MongoStorage) getColNameForPrefix(prefix string) (name string, ok bool
utils.LOADINST_KEY: colLht,
utils.VERSION_PREFIX: colVer,
utils.ResourceProfilesPrefix: colRsP,
- utils.StatsPrefix: colStq,
+ utils.StatQueuePrefix: colStq,
utils.TimingsPrefix: colTmg,
utils.ResourcesPrefix: colRes,
}
@@ -527,9 +526,11 @@ func (ms *MongoStorage) CacheDataFromDB(prfx string, ids []string, mustBeCached
case utils.REVERSE_ALIASES_PREFIX:
_, err = ms.GetReverseAlias(dataID, true, utils.NonTransactional)
case utils.ResourceProfilesPrefix:
- _, err = ms.GetResourceProfile(dataID, true, utils.NonTransactional)
+ tntID := utils.NewTenantID(dataID)
+ _, err = ms.GetResourceProfile(tntID.Tenant, tntID.ID, true, utils.NonTransactional)
case utils.ResourcesPrefix:
- _, err = ms.GetResource(dataID, true, utils.NonTransactional)
+ tntID := utils.NewTenantID(dataID)
+ _, err = ms.GetResource(tntID.Tenant, tntID.ID, true, utils.NonTransactional)
case utils.TimingsPrefix:
_, err = ms.GetTiming(dataID, true, utils.NonTransactional)
}
@@ -555,7 +556,7 @@ func (ms *MongoStorage) GetKeysForPrefix(prefix string) (result []string, err er
defer session.Close()
db := session.DB(ms.db)
keyResult := struct{ Key string }{}
- idResult := struct{ Id string }{}
+ idResult := struct{ Tenant, Id string }{}
switch category {
case utils.DESTINATION_PREFIX:
iter := db.C(colDst).Find(bson.M{"key": bson.M{"$regex": bson.RegEx{Pattern: subject}}}).Select(bson.M{"key": 1}).Iter()
@@ -623,19 +624,19 @@ func (ms *MongoStorage) GetKeysForPrefix(prefix string) (result []string, err er
result = append(result, utils.REVERSE_ALIASES_PREFIX+keyResult.Key)
}
case utils.ResourceProfilesPrefix:
- iter := db.C(colRsP).Find(bson.M{"id": bson.M{"$regex": bson.RegEx{Pattern: subject}}}).Select(bson.M{"id": 1}).Iter()
+ iter := db.C(colRsP).Find(bson.M{"id": bson.M{"$regex": bson.RegEx{Pattern: subject}}}).Select(bson.M{"tenant": 1, "id": 1}).Iter()
for iter.Next(&idResult) {
- result = append(result, utils.ResourceProfilesPrefix+idResult.Id)
+ result = append(result, utils.ResourceProfilesPrefix+utils.ConcatenatedKey(idResult.Tenant, idResult.Id))
}
case utils.ResourcesPrefix:
- iter := db.C(colRes).Find(bson.M{"id": bson.M{"$regex": bson.RegEx{Pattern: subject}}}).Select(bson.M{"id": 1}).Iter()
+ iter := db.C(colRes).Find(bson.M{"id": bson.M{"$regex": bson.RegEx{Pattern: subject}}}).Select(bson.M{"tenant": 1, "id": 1}).Iter()
for iter.Next(&idResult) {
- result = append(result, utils.ResourcesPrefix+idResult.Id)
+ result = append(result, utils.ResourcesPrefix+utils.ConcatenatedKey(idResult.Tenant, idResult.Id))
}
- case utils.StatsPrefix:
+ case utils.StatQueuePrefix:
iter := db.C(colStq).Find(bson.M{"id": bson.M{"$regex": bson.RegEx{Pattern: subject}}}).Select(bson.M{"id": 1}).Iter()
for iter.Next(&idResult) {
- result = append(result, utils.StatsPrefix+idResult.Id)
+ result = append(result, utils.StatQueuePrefix+idResult.Id)
}
case utils.StatQueueProfilePrefix:
iter := db.C(colSqp).Find(bson.M{"id": bson.M{"$regex": bson.RegEx{Pattern: subject}}}).Select(bson.M{"id": 1}).Iter()
@@ -658,34 +659,40 @@ func (ms *MongoStorage) GetKeysForPrefix(prefix string) (result []string, err er
return
}
-func (ms *MongoStorage) HasData(category, subject string) (bool, error) {
+func (ms *MongoStorage) HasData(category, subject string) (has bool, err error) {
session := ms.session.Copy()
defer session.Close()
db := session.DB(ms.db)
+ var count int
switch category {
case utils.DESTINATION_PREFIX:
- count, err := db.C(colDst).Find(bson.M{"key": subject}).Count()
- return count > 0, err
+ count, err = db.C(colDst).Find(bson.M{"key": subject}).Count()
+ has = count > 0
case utils.RATING_PLAN_PREFIX:
- count, err := db.C(colRpl).Find(bson.M{"key": subject}).Count()
- return count > 0, err
+ count, err = db.C(colRpl).Find(bson.M{"key": subject}).Count()
+ has = count > 0
case utils.RATING_PROFILE_PREFIX:
- count, err := db.C(colRpf).Find(bson.M{"id": subject}).Count()
- return count > 0, err
+ count, err = db.C(colRpf).Find(bson.M{"id": subject}).Count()
+ has = count > 0
case utils.ACTION_PREFIX:
- count, err := db.C(colAct).Find(bson.M{"key": subject}).Count()
- return count > 0, err
+ count, err = db.C(colAct).Find(bson.M{"key": subject}).Count()
+ has = count > 0
case utils.ACTION_PLAN_PREFIX:
- count, err := db.C(colApl).Find(bson.M{"key": subject}).Count()
- return count > 0, err
+ count, err = db.C(colApl).Find(bson.M{"key": subject}).Count()
+ has = count > 0
case utils.ACCOUNT_PREFIX:
- count, err := db.C(colAcc).Find(bson.M{"id": subject}).Count()
- return count > 0, err
+ count, err = db.C(colAcc).Find(bson.M{"id": subject}).Count()
+ has = count > 0
case utils.ResourcesPrefix:
- count, err := db.C(colRes).Find(bson.M{"id": subject}).Count()
- return count > 0, err
+ count, err = db.C(colRes).Find(bson.M{"id": subject}).Count()
+ has = count > 0
+ case utils.StatQueuePrefix:
+ count, err = db.C(colRes).Find(bson.M{"id": subject}).Count()
+ has = count > 0
+ default:
+ err = fmt.Errorf("unsupported category in HasData: %s", category)
}
- return false, errors.New("unsupported category in HasData")
+ return
}
func (ms *MongoStorage) GetRatingPlan(key string, skipCache bool, transactionID string) (rp *RatingPlan, err error) {
@@ -1825,8 +1832,8 @@ func (ms *MongoStorage) GetAllCdrStats() (css []*CdrStats, err error) {
return
}
-func (ms *MongoStorage) GetResourceProfile(id string, skipCache bool, transactionID string) (rp *ResourceProfile, err error) {
- key := utils.ResourceProfilesPrefix + id
+func (ms *MongoStorage) GetResourceProfile(tenant, id string, skipCache bool, transactionID string) (rp *ResourceProfile, err error) {
+ key := utils.ResourceProfilesPrefix + utils.ConcatenatedKey(tenant, id)
if !skipCache {
if x, ok := cache.Get(key); ok {
if x == nil {
@@ -1838,7 +1845,7 @@ func (ms *MongoStorage) GetResourceProfile(id string, skipCache bool, transactio
session, col := ms.conn(colRsP)
defer session.Close()
rp = new(ResourceProfile)
- if err = col.Find(bson.M{"id": id}).One(rp); err != nil {
+ if err = col.Find(bson.M{"tenant": tenant, "id": id}).One(rp); err != nil {
if err == mgo.ErrNotFound {
err = utils.ErrNotFound
cache.Set(key, nil, cacheCommit(transactionID), transactionID)
@@ -1857,22 +1864,23 @@ func (ms *MongoStorage) GetResourceProfile(id string, skipCache bool, transactio
func (ms *MongoStorage) SetResourceProfile(rp *ResourceProfile, transactionID string) (err error) {
session, col := ms.conn(colRsP)
defer session.Close()
- _, err = col.Upsert(bson.M{"id": rp.ID}, rp)
+ _, err = col.Upsert(bson.M{"tenant": rp.Tenant, "id": rp.ID}, rp)
return
}
-func (ms *MongoStorage) RemoveResourceProfile(id string, transactionID string) (err error) {
+func (ms *MongoStorage) RemoveResourceProfile(tenant, id string, transactionID string) (err error) {
session, col := ms.conn(colRsP)
defer session.Close()
- if err = col.Remove(bson.M{"id": id}); err != nil {
+ if err = col.Remove(bson.M{"tenant": tenant, "id": id}); err != nil {
return
}
- cache.RemKey(utils.ResourceProfilesPrefix+id, cacheCommit(transactionID), transactionID)
+ cache.RemKey(utils.ResourceProfilesPrefix+utils.ConcatenatedKey(tenant, id),
+ cacheCommit(transactionID), transactionID)
return nil
}
-func (ms *MongoStorage) GetResource(id string, skipCache bool, transactionID string) (r *Resource, err error) {
- key := utils.ResourcesPrefix + id
+func (ms *MongoStorage) GetResource(tenant, id string, skipCache bool, transactionID string) (r *Resource, err error) {
+ key := utils.ResourcesPrefix + utils.ConcatenatedKey(tenant, id)
if !skipCache {
if x, ok := cache.Get(key); ok {
if x == nil {
@@ -1884,7 +1892,7 @@ func (ms *MongoStorage) GetResource(id string, skipCache bool, transactionID str
session, col := ms.conn(colRes)
defer session.Close()
r = new(Resource)
- if err = col.Find(bson.M{"id": id}).One(r); err != nil {
+ if err = col.Find(bson.M{"tenant": tenant, "id": id}).One(r); err != nil {
if err == mgo.ErrNotFound {
err = utils.ErrNotFound
cache.Set(key, nil, cacheCommit(transactionID), transactionID)
@@ -1898,17 +1906,18 @@ func (ms *MongoStorage) GetResource(id string, skipCache bool, transactionID str
func (ms *MongoStorage) SetResource(r *Resource) (err error) {
session, col := ms.conn(colRes)
defer session.Close()
- _, err = col.Upsert(bson.M{"id": r.ID}, r)
+ _, err = col.Upsert(bson.M{"tenant": r.Tenant, "id": r.ID}, r)
return
}
-func (ms *MongoStorage) RemoveResource(id string, transactionID string) (err error) {
+func (ms *MongoStorage) RemoveResource(tenant, id string, transactionID string) (err error) {
session, col := ms.conn(colRes)
defer session.Close()
- if err = col.Remove(bson.M{"id": id}); err != nil {
+ if err = col.Remove(bson.M{"tenant": tenant, "id": id}); err != nil {
return
}
- cache.RemKey(utils.ResourcesPrefix+id, cacheCommit(transactionID), transactionID)
+ cache.RemKey(utils.ResourcesPrefix+utils.ConcatenatedKey(tenant, id),
+ cacheCommit(transactionID), transactionID)
return nil
}
diff --git a/engine/storage_redis.go b/engine/storage_redis.go
index 2651604f9..0b50bb8b2 100755
--- a/engine/storage_redis.go
+++ b/engine/storage_redis.go
@@ -288,9 +288,11 @@ func (rs *RedisStorage) CacheDataFromDB(prfx string, ids []string, mustBeCached
case utils.REVERSE_ALIASES_PREFIX:
_, err = rs.GetReverseAlias(dataID, true, utils.NonTransactional)
case utils.ResourceProfilesPrefix:
- _, err = rs.GetResourceProfile(dataID, true, utils.NonTransactional)
+ tntID := utils.NewTenantID(dataID)
+ _, err = rs.GetResourceProfile(tntID.Tenant, tntID.ID, true, utils.NonTransactional)
case utils.ResourcesPrefix:
- _, err = rs.GetResource(dataID, true, utils.NonTransactional)
+ tntID := utils.NewTenantID(dataID)
+ _, err = rs.GetResource(tntID.Tenant, tntID.ID, true, utils.NonTransactional)
case utils.TimingsPrefix:
_, err = rs.GetTiming(dataID, true, utils.NonTransactional)
}
@@ -319,7 +321,9 @@ func (rs *RedisStorage) GetKeysForPrefix(prefix string) ([]string, error) {
// Used to check if specific subject is stored using prefix key attached to entity
func (rs *RedisStorage) HasData(category, subject string) (bool, error) {
switch category {
- case utils.DESTINATION_PREFIX, utils.RATING_PLAN_PREFIX, utils.RATING_PROFILE_PREFIX, utils.ACTION_PREFIX, utils.ACTION_PLAN_PREFIX, utils.ACCOUNT_PREFIX, utils.DERIVEDCHARGERS_PREFIX, utils.ResourcesPrefix:
+ case utils.DESTINATION_PREFIX, utils.RATING_PLAN_PREFIX, utils.RATING_PROFILE_PREFIX,
+ utils.ACTION_PREFIX, utils.ACTION_PLAN_PREFIX, utils.ACCOUNT_PREFIX, utils.DERIVEDCHARGERS_PREFIX,
+ utils.ResourcesPrefix, utils.StatQueuePrefix:
i, err := rs.Cmd("EXISTS", category+subject).Int()
return i == 1, err
}
@@ -1348,8 +1352,8 @@ func (rs *RedisStorage) GetAllCdrStats() (css []*CdrStats, err error) {
return
}
-func (rs *RedisStorage) GetResourceProfile(id string, skipCache bool, transactionID string) (rsp *ResourceProfile, err error) {
- key := utils.ResourceProfilesPrefix + id
+func (rs *RedisStorage) GetResourceProfile(tenant, id string, skipCache bool, transactionID string) (rsp *ResourceProfile, err error) {
+ key := utils.ResourceProfilesPrefix + utils.ConcatenatedKey(tenant, id)
if !skipCache {
if x, ok := cache.Get(key); ok {
if x == nil {
@@ -1383,11 +1387,11 @@ func (rs *RedisStorage) SetResourceProfile(rsp *ResourceProfile, transactionID s
if err != nil {
return err
}
- return rs.Cmd("SET", utils.ResourceProfilesPrefix+rsp.ID, result).Err
+ return rs.Cmd("SET", utils.ResourceProfilesPrefix+rsp.TenantID(), result).Err
}
-func (rs *RedisStorage) RemoveResourceProfile(id string, transactionID string) (err error) {
- key := utils.ResourceProfilesPrefix + id
+func (rs *RedisStorage) RemoveResourceProfile(tenant, id string, transactionID string) (err error) {
+ key := utils.ResourceProfilesPrefix + utils.ConcatenatedKey(tenant, id)
if err = rs.Cmd("DEL", key).Err; err != nil {
return
}
@@ -1395,8 +1399,8 @@ func (rs *RedisStorage) RemoveResourceProfile(id string, transactionID string) (
return
}
-func (rs *RedisStorage) GetResource(id string, skipCache bool, transactionID string) (r *Resource, err error) {
- key := utils.ResourcesPrefix + id
+func (rs *RedisStorage) GetResource(tenant, id string, skipCache bool, transactionID string) (r *Resource, err error) {
+ key := utils.ResourcesPrefix + utils.ConcatenatedKey(tenant, id)
if !skipCache {
if x, ok := cache.Get(key); ok {
if x == nil {
@@ -1425,11 +1429,11 @@ func (rs *RedisStorage) SetResource(r *Resource) (err error) {
if err != nil {
return err
}
- return rs.Cmd("SET", utils.ResourcesPrefix+r.ID, result).Err
+ return rs.Cmd("SET", utils.ResourcesPrefix+r.TenantID(), result).Err
}
-func (rs *RedisStorage) RemoveResource(id string, transactionID string) (err error) {
- key := utils.ResourcesPrefix + id
+func (rs *RedisStorage) RemoveResource(tenant, id string, transactionID string) (err error) {
+ key := utils.ResourcesPrefix + utils.ConcatenatedKey(tenant, id)
if err = rs.Cmd("DEL", key).Err; err != nil {
return
}
diff --git a/engine/storage_sql.go b/engine/storage_sql.go
index fdf8c4de5..80398a7f1 100755
--- a/engine/storage_sql.go
+++ b/engine/storage_sql.go
@@ -565,7 +565,7 @@ func (self *SQLStorage) SetTPResources(rls []*utils.TPResource) error {
tx := self.db.Begin()
for _, rl := range rls {
// Remove previous
- if err := tx.Where(&TpResource{Tpid: rl.TPid, Tag: rl.ID}).Delete(TpResource{}).Error; err != nil {
+ if err := tx.Where(&TpResource{Tpid: rl.TPid, ID: rl.ID}).Delete(TpResource{}).Error; err != nil {
tx.Rollback()
return err
}
@@ -1542,7 +1542,7 @@ func (self *SQLStorage) GetTPResources(tpid, id string) ([]*utils.TPResource, er
var rls TpResources
q := self.db.Where("tpid = ?", tpid)
if len(id) != 0 {
- q = q.Where("tag = ?", id)
+ q = q.Where("id = ?", id)
}
if err := q.Find(&rls).Error; err != nil {
return nil, err
diff --git a/engine/tp_reader.go b/engine/tp_reader.go
index 565d6cebf..94b5341e6 100755
--- a/engine/tp_reader.go
+++ b/engine/tp_reader.go
@@ -53,10 +53,11 @@ type TpReader struct {
cdrStats map[string]*CdrStats
users map[string]*UserProfile
aliases map[string]*Alias
- resProfiles map[string]*utils.TPResource
- stats map[string]*utils.TPStats
+ resProfiles map[string]map[string]*utils.TPResource
+ sqProfiles map[string]*utils.TPStats
thresholds map[string]*utils.TPThreshold
- resources []string // IDs of resources which need creation based on resourceConfigs
+ resources []*utils.TenantID // IDs of resources which need creation based on resourceProfiles
+ statQueues []string // IDs of statQueues which need creation based on statQueueProfiles
revDests,
revAliases,
@@ -128,8 +129,8 @@ func (tpr *TpReader) Init() {
tpr.users = make(map[string]*UserProfile)
tpr.aliases = make(map[string]*Alias)
tpr.derivedChargers = make(map[string]*utils.DerivedChargers)
- tpr.resProfiles = make(map[string]*utils.TPResource)
- tpr.stats = make(map[string]*utils.TPStats)
+ tpr.resProfiles = make(map[string]map[string]*utils.TPResource)
+ tpr.sqProfiles = make(map[string]*utils.TPStats)
tpr.thresholds = make(map[string]*utils.TPThreshold)
tpr.revDests = make(map[string][]string)
tpr.revAliases = make(map[string][]string)
@@ -1598,16 +1599,22 @@ func (tpr *TpReader) LoadResourceProfilesFiltered(tag string) error {
if err != nil {
return err
}
- mapRsPs := make(map[string]*utils.TPResource)
+ mapRsPfls := make(map[string]map[string]*utils.TPResource)
for _, rl := range rls {
- mapRsPs[rl.ID] = rl
+ if _, has := mapRsPfls[rl.Tenant]; !has {
+ mapRsPfls[rl.Tenant] = make(map[string]*utils.TPResource)
+ }
+ mapRsPfls[rl.Tenant][rl.ID] = rl
}
- tpr.resProfiles = mapRsPs
- for rID := range mapRsPs {
- if has, err := tpr.dataStorage.HasData(utils.ResourcesPrefix, rID); err != nil {
- return err
- } else if !has {
- tpr.resources = append(tpr.resources, rID)
+ tpr.resProfiles = mapRsPfls
+ for tenant, mpID := range mapRsPfls {
+ for id := range mpID {
+ rTid := &utils.TenantID{tenant, id}
+ if has, err := tpr.dataStorage.HasData(utils.ResourcesPrefix, rTid.TenantID()); err != nil {
+ return err
+ } else if !has {
+ tpr.resources = append(tpr.resources, rTid)
+ }
}
}
return nil
@@ -1626,7 +1633,14 @@ func (tpr *TpReader) LoadStatsFiltered(tag string) error {
for _, st := range tps {
mapSTs[st.ID] = st
}
- tpr.stats = mapSTs
+ tpr.sqProfiles = mapSTs
+ for sqID := range mapSTs {
+ if has, err := tpr.dataStorage.HasData(utils.StatQueuePrefix, sqID); err != nil {
+ return err
+ } else if !has {
+ tpr.statQueues = append(tpr.statQueues, sqID)
+ }
+ }
return nil
}
@@ -1944,33 +1958,35 @@ func (tpr *TpReader) WriteToDatabase(flush, verbose, disable_reverse bool) (err
if verbose {
log.Print("ResourceProfiles:")
}
- for _, tpRsp := range tpr.resProfiles {
- rsp, err := APItoResource(tpRsp, tpr.timezone)
- if err != nil {
- return err
- }
- if err = tpr.dataStorage.SetResourceProfile(rsp, utils.NonTransactional); err != nil {
- return err
- }
- if verbose {
- log.Print("\t", rsp.ID)
+ for _, mpID := range tpr.resProfiles {
+ for _, tpRsp := range mpID {
+ rsp, err := APItoResource(tpRsp, tpr.timezone)
+ if err != nil {
+ return err
+ }
+ if err = tpr.dataStorage.SetResourceProfile(rsp, utils.NonTransactional); err != nil {
+ return err
+ }
+ if verbose {
+ log.Print("\t", rsp.TenantID())
+ }
}
}
if verbose {
log.Print("Resources:")
}
- for _, rID := range tpr.resources {
- if err = tpr.dataStorage.SetResource(&Resource{ID: rID, Usages: make(map[string]*ResourceUsage)}); err != nil {
+ for _, rTid := range tpr.resources {
+ if err = tpr.dataStorage.SetResource(&Resource{Tenant: rTid.Tenant, ID: rTid.ID, Usages: make(map[string]*ResourceUsage)}); err != nil {
return
}
if verbose {
- log.Print("\t", rID)
+ log.Print("\t", rTid.TenantID())
}
}
if verbose {
log.Print("StatQueueProfiles:")
}
- for _, tpST := range tpr.stats {
+ for _, tpST := range tpr.sqProfiles {
st, err := APItoStats(tpST, tpr.timezone)
if err != nil {
return err
@@ -1982,6 +1998,18 @@ func (tpr *TpReader) WriteToDatabase(flush, verbose, disable_reverse bool) (err
log.Print("\t", st.ID)
}
}
+ if verbose {
+ log.Print("StatQueues:")
+ }
+ for _, sqID := range tpr.statQueues {
+ if err = tpr.dataStorage.SetStoredStatQueue(&StoredStatQueue{Tenant: "", ID: sqID,
+ SQMetrics: make(map[string][]byte)}); err != nil {
+ return
+ }
+ if verbose {
+ log.Print("\t", sqID)
+ }
+ }
if verbose {
log.Print("Thresholds:")
}
@@ -2037,25 +2065,27 @@ func (tpr *TpReader) WriteToDatabase(flush, verbose, disable_reverse bool) (err
if verbose {
log.Print("Indexing resource profiles")
}
- rlIdxr, err := NewReqFilterIndexer(tpr.dataStorage, utils.ResourceProfilesIndex)
- if err != nil {
- return err
- }
- for _, tpRL := range tpr.resProfiles {
- if rl, err := APItoResource(tpRL, tpr.timezone); err != nil {
+ for tenant, mpID := range tpr.resProfiles {
+ rlIdxr, err := NewReqFilterIndexer(tpr.dataStorage, utils.ResourceProfilesStringIndex+tenant)
+ if err != nil {
+ return err
+ }
+ for _, tpRL := range mpID {
+ if rl, err := APItoResource(tpRL, tpr.timezone); err != nil {
+ return err
+ } else {
+ rlIdxr.IndexFilters(rl.ID, rl.Filters)
+ }
+ }
+ if verbose {
+ log.Printf("Indexed ResourceProfile tenant: %s keys: %+v", tenant, rlIdxr.ChangedKeys().Slice())
+ }
+ if err := rlIdxr.StoreIndexes(); err != nil {
return err
- } else {
- rlIdxr.IndexFilters(rl.ID, rl.Filters)
}
}
- if verbose {
- log.Printf("Indexed ResourceProfile keys: %+v", rlIdxr.ChangedKeys().Slice())
- }
- if err := rlIdxr.StoreIndexes(); err != nil {
- return err
- }
}
- if len(tpr.stats) > 0 {
+ if len(tpr.sqProfiles) > 0 {
if verbose {
log.Print("Indexing stats")
}
@@ -2063,7 +2093,7 @@ func (tpr *TpReader) WriteToDatabase(flush, verbose, disable_reverse bool) (err
if err != nil {
return err
}
- for _, tpST := range tpr.stats {
+ for _, tpST := range tpr.sqProfiles {
if st, err := APItoStats(tpST, tpr.timezone); err != nil {
return err
} else {
@@ -2163,7 +2193,7 @@ func (tpr *TpReader) ShowStatistics() {
// resource limits
log.Print("ResourceProfiles: ", len(tpr.resProfiles))
// stats
- log.Print("Stats: ", len(tpr.stats))
+ log.Print("Stats: ", len(tpr.sqProfiles))
}
// Returns the identities loaded for a specific category, useful for cache reloads
@@ -2274,11 +2304,11 @@ func (tpr *TpReader) GetLoadedIds(categ string) ([]string, error) {
}
return keys, nil
case utils.ResourceProfilesPrefix:
- keys := make([]string, len(tpr.resProfiles))
- i := 0
- for k := range tpr.resProfiles {
- keys[i] = k
- i++
+ keys := make([]string, 0)
+ for tenant, mpID := range tpr.resProfiles {
+ for id := range mpID {
+ keys = append(keys, utils.ConcatenatedKey(tenant, id))
+ }
}
return keys, nil
case utils.ACTION_TRIGGER_PREFIX:
@@ -2297,10 +2327,10 @@ func (tpr *TpReader) GetLoadedIds(categ string) ([]string, error) {
i++
}
return keys, nil
- case utils.StatsPrefix:
- keys := make([]string, len(tpr.stats))
+ case utils.StatQueueProfilePrefix:
+ keys := make([]string, len(tpr.sqProfiles))
i := 0
- for k := range tpr.stats {
+ for k := range tpr.sqProfiles {
keys[i] = k
i++
}
diff --git a/general_tests/tut_smgeneric_it_test.go b/general_tests/tut_smgeneric_it_test.go
index 133c92035..d0af8c9e7 100644
--- a/general_tests/tut_smgeneric_it_test.go
+++ b/general_tests/tut_smgeneric_it_test.go
@@ -100,7 +100,7 @@ func TestTutSMGCacheStats(t *testing.T) {
var rcvStats *utils.CacheStats
expectedStats := &utils.CacheStats{Destinations: 5, ReverseDestinations: 7, RatingPlans: 4, RatingProfiles: 9,
Actions: 8, ActionPlans: 4, AccountActionPlans: 5, SharedGroups: 1, DerivedChargers: 1, LcrProfiles: 5,
- CdrStats: 6, Users: 3, Aliases: 1, ReverseAliases: 2, ResourceProfiles: 3, Resources: 0}
+ CdrStats: 6, Users: 3, Aliases: 1, ReverseAliases: 2, ResourceProfiles: 3, Resources: 3}
var args utils.AttrCacheStats
if err := tutSMGRpc.Call("ApierV2.GetCacheStats", args, &rcvStats); err != nil {
t.Error("Got error on ApierV2.GetCacheStats: ", err.Error())
diff --git a/general_tests/tutorial_it_test.go b/general_tests/tutorial_it_test.go
index 1c21a5503..ebc98c4e4 100644
--- a/general_tests/tutorial_it_test.go
+++ b/general_tests/tutorial_it_test.go
@@ -104,7 +104,7 @@ func TestTutITCacheStats(t *testing.T) {
var rcvStats *utils.CacheStats
expectedStats := &utils.CacheStats{Destinations: 5, ReverseDestinations: 7, RatingPlans: 4, RatingProfiles: 9,
Actions: 8, ActionPlans: 4, AccountActionPlans: 5, SharedGroups: 1, DerivedChargers: 1, LcrProfiles: 5,
- CdrStats: 6, Users: 3, Aliases: 1, ReverseAliases: 2, ResourceProfiles: 3, Resources: 0}
+ CdrStats: 6, Users: 3, Aliases: 1, ReverseAliases: 2, ResourceProfiles: 3, Resources: 3}
var args utils.AttrCacheStats
if err := tutLocalRpc.Call("ApierV1.GetCacheStats", args, &rcvStats); err != nil {
t.Error("Got error on ApierV1.GetCacheStats: ", err.Error())
diff --git a/sessionmanager/fssessionmanager.go b/sessionmanager/fssessionmanager.go
index 022b1609f..f68ef9eb9 100644
--- a/sessionmanager/fssessionmanager.go
+++ b/sessionmanager/fssessionmanager.go
@@ -213,7 +213,8 @@ func (sm *FSSessionManager) onChannelPark(ev engine.Event, connId string) {
}
if sm.rls != nil {
var reply string
- attrRU := utils.AttrRLsResourceUsage{
+ attrRU := utils.ArgRSv1ResourceUsage{
+ Tenant: ev.(FSEvent).GetTenant(utils.META_DEFAULT),
UsageID: ev.GetUUID(),
Event: ev.(FSEvent).AsMapStringInterface(sm.timezone),
Units: 1,
@@ -278,7 +279,8 @@ func (sm *FSSessionManager) onChannelHangupComplete(ev engine.Event) {
sm.ProcessCdr(ev.AsStoredCdr(config.CgrConfig().DefaultTimezone))
}
var reply string
- attrRU := utils.AttrRLsResourceUsage{
+ attrRU := utils.ArgRSv1ResourceUsage{
+ Tenant: ev.(FSEvent).GetTenant(utils.META_DEFAULT),
UsageID: ev.GetUUID(),
Event: ev.(FSEvent).AsMapStringInterface(sm.timezone),
Units: 1,
diff --git a/sessionmanager/kamailiosm.go b/sessionmanager/kamailiosm.go
index 40b72e191..22bf58a72 100644
--- a/sessionmanager/kamailiosm.go
+++ b/sessionmanager/kamailiosm.go
@@ -79,7 +79,8 @@ func (self *KamailioSessionManager) allocateResources(kev KamEvent) (err error)
if ev, err = kev.AsMapStringIface(); err != nil {
return
}
- attrRU := utils.AttrRLsResourceUsage{
+ attrRU := utils.ArgRSv1ResourceUsage{
+ Tenant: kev.GetTenant(utils.META_DEFAULT),
UsageID: kev.GetUUID(),
Event: ev,
Units: 1, // One channel reserved
@@ -212,7 +213,8 @@ func (self *KamailioSessionManager) onCallEnd(evData []byte, connId string) {
return
}
var reply string
- attrRU := utils.AttrRLsResourceUsage{
+ attrRU := utils.ArgRSv1ResourceUsage{
+ Tenant: kev.GetTenant(utils.META_DEFAULT),
UsageID: kev.GetUUID(),
Event: ev,
Units: 1,
diff --git a/utils/apitpdata.go b/utils/apitpdata.go
index db55e86b7..a24cd45ec 100755
--- a/utils/apitpdata.go
+++ b/utils/apitpdata.go
@@ -1270,7 +1270,7 @@ type AttrSetBalance struct {
type TPResource struct {
TPid string
- Tenant string
+ Tenant string
ID string // Identifier of this limit
Filters []*TPRequestFilter // Filters for the request
ActivationInterval *TPActivationInterval // Time when this limit becomes active/expires
@@ -1300,10 +1300,15 @@ type AttrRLsCache struct {
ResourceIDs []string
}
-type AttrRLsResourceUsage struct {
- Event map[string]interface{}
+type ArgRSv1ResourceUsage struct {
+ Tenant string
UsageID string // ResourceUsage Identifier
Units float64
+ Event map[string]interface{}
+}
+
+func (args *ArgRSv1ResourceUsage) TenantID() string {
+ return ConcatenatedKey(args.Tenant, args.UsageID)
}
// AsActivationTime converts TPActivationInterval into ActivationInterval
@@ -1336,7 +1341,7 @@ type AttrDisconnectSession struct {
// TPStats is used in APIs to manage remotely offline Stats config
type TPStats struct {
TPid string
- Tenant string
+ Tenant string
ID string
Filters []*TPRequestFilter
ActivationInterval *TPActivationInterval
diff --git a/utils/consts.go b/utils/consts.go
index 69ca64ba9..691d7ae87 100755
--- a/utils/consts.go
+++ b/utils/consts.go
@@ -241,9 +241,8 @@ const (
ALIASES_PREFIX = "als_"
REVERSE_ALIASES_PREFIX = "rls_"
ResourcesPrefix = "res_"
- ResourceProfilesIndex = "rsi_"
+ ResourceProfilesStringIndex = "rsi_"
ResourceProfilesPrefix = "rsp_"
- StatsPrefix = "sts_"
StatsIndex = "sti_"
ThresholdsPrefix = "ths_"
ThresholdsIndex = "thi_"
diff --git a/utils/coreutils.go b/utils/coreutils.go
index 7f0240d7f..df8db9ec3 100644
--- a/utils/coreutils.go
+++ b/utils/coreutils.go
@@ -759,3 +759,23 @@ func AppendToFile(fName, text string) error {
f.Close()
return nil
}
+
+func NewTenantID(tntID string) *TenantID {
+ if strings.Index(tntID, CONCATENATED_KEY_SEP) == -1 { // no :, ID without Tenant
+ return &TenantID{ID: tntID}
+ }
+ tIDSplt := strings.Split(tntID, CONCATENATED_KEY_SEP)
+ if len(tIDSplt) == 1 { // only Tenant present
+ return &TenantID{Tenant: tIDSplt[0]}
+ }
+ return &TenantID{Tenant: tIDSplt[0], ID: tIDSplt[1]}
+}
+
+type TenantID struct {
+ Tenant string
+ ID string
+}
+
+func (tID *TenantID) TenantID() string {
+ return ConcatenatedKey(tID.Tenant, tID.ID)
+}