diff --git a/engine/version.go b/engine/version.go
index 73ad19370..62450100d 100644
--- a/engine/version.go
+++ b/engine/version.go
@@ -152,7 +152,7 @@ func CurrentDataDBVersions() Versions {
utils.Alias: 2,
utils.User: 2,
utils.Subscribers: 1,
- utils.DerivedChargersV: 1,
+ utils.DerivedChargersV: 2,
utils.Destinations: 1,
utils.ReverseDestinations: 1,
utils.RatingPlan: 1,
diff --git a/migrator/derived_chargers.go b/migrator/derived_chargers.go
index 0393d07ae..7fd477b1c 100644
--- a/migrator/derived_chargers.go
+++ b/migrator/derived_chargers.go
@@ -22,10 +22,198 @@ import (
"fmt"
"strings"
+ "github.com/cgrates/cgrates/config"
"github.com/cgrates/cgrates/engine"
"github.com/cgrates/cgrates/utils"
)
+var dcGetMapKeys = func(m utils.StringMap) (keys []string) {
+ keys = make([]string, len(m))
+ i := 0
+ for k, _ := range m {
+ keys[i] = k
+ i += 1
+ }
+ // sort.Strings(keys)
+ return keys
+}
+
+type v1DerivedCharger struct {
+ RunID string // Unique runId in the chain
+ RunFilters string // Only run the charger if all the filters match
+
+ RequestTypeField string // Field containing request type info, number in case of csv source, '^' as prefix in case of static values
+ DirectionField string // Field containing direction info
+ TenantField string // Field containing tenant info
+ CategoryField string // Field containing tor info
+ AccountField string // Field containing account information
+ SubjectField string // Field containing subject information
+ DestinationField string // Field containing destination information
+ SetupTimeField string // Field containing setup time information
+ PDDField string // Field containing setup time information
+ AnswerTimeField string // Field containing answer time information
+ UsageField string // Field containing usage information
+ SupplierField string // Field containing supplier information
+ DisconnectCauseField string // Field containing disconnect cause information
+ CostField string // Field containing cost information
+ PreRatedField string // Field marking rated request in CDR
+}
+
+type v1DerivedChargers struct {
+ DestinationIDs utils.StringMap
+ Chargers []*v1DerivedCharger
+}
+
+type v1DerivedChargersWithKey struct {
+ Key string
+ Value *v1DerivedChargers
+}
+
+func fieldinfo2Attribute(attr []*engine.Attribute, fieldName, fieldInfo string) (a []*engine.Attribute) {
+ var rp config.RSRParsers
+ if fieldInfo == utils.META_DEFAULT || len(fieldInfo) == 0 {
+ return attr
+ }
+ if strings.HasPrefix(fieldInfo, utils.STATIC_VALUE_PREFIX) {
+ fieldInfo = fieldInfo[1:]
+ }
+ var err error
+ if rp, err = config.NewRSRParsers(fieldInfo, true, utils.INFIELD_SEP); err != nil {
+ utils.Logger.Err(fmt.Sprintf("On Migrating rule: <%s>, error: %s", fieldInfo, err.Error()))
+ return attr
+ }
+ return append(attr, &engine.Attribute{
+ FieldName: fieldName,
+ Initial: utils.META_ANY,
+ Substitute: rp,
+ Append: true,
+ })
+}
+func derivedChargers2AttributeProfile(dc *v1DerivedCharger, tenant, key string, filters []string) (attr *engine.AttributeProfile) {
+ attr = &engine.AttributeProfile{
+ Tenant: tenant,
+ ID: key,
+ Contexts: []string{utils.META_ANY},
+ FilterIDs: filters,
+ ActivationInterval: nil,
+ Attributes: make([]*engine.Attribute, 0),
+ Blocker: false,
+ Weight: 10,
+ }
+ attr.Attributes = fieldinfo2Attribute(attr.Attributes, utils.RequestType, dc.RequestTypeField)
+ attr.Attributes = fieldinfo2Attribute(attr.Attributes, utils.Direction, dc.DirectionField) //still in use?
+ attr.Attributes = fieldinfo2Attribute(attr.Attributes, utils.Tenant, dc.TenantField)
+ attr.Attributes = fieldinfo2Attribute(attr.Attributes, utils.Category, dc.CategoryField)
+ attr.Attributes = fieldinfo2Attribute(attr.Attributes, utils.Account, dc.AccountField)
+ attr.Attributes = fieldinfo2Attribute(attr.Attributes, utils.Subject, dc.SubjectField)
+ attr.Attributes = fieldinfo2Attribute(attr.Attributes, utils.Destination, dc.DestinationField)
+ attr.Attributes = fieldinfo2Attribute(attr.Attributes, utils.SetupTime, dc.SetupTimeField)
+ attr.Attributes = fieldinfo2Attribute(attr.Attributes, utils.PDD, dc.PDDField)
+ attr.Attributes = fieldinfo2Attribute(attr.Attributes, utils.AnswerTime, dc.AnswerTimeField)
+ attr.Attributes = fieldinfo2Attribute(attr.Attributes, utils.Usage, dc.UsageField)
+ attr.Attributes = fieldinfo2Attribute(attr.Attributes, utils.SUPPLIER, dc.SupplierField)
+ attr.Attributes = fieldinfo2Attribute(attr.Attributes, utils.DISCONNECT_CAUSE, dc.DisconnectCauseField)
+ attr.Attributes = fieldinfo2Attribute(attr.Attributes, utils.Cost, dc.CostField)
+ attr.Attributes = fieldinfo2Attribute(attr.Attributes, utils.PreRated, dc.PreRatedField)
+ return
+}
+
+func derivedChargers2Charger(dc *v1DerivedCharger, tenant string, key string, filters []string) (ch *engine.ChargerProfile) {
+ ch = &engine.ChargerProfile{
+ Tenant: tenant,
+ ID: key,
+ FilterIDs: filters,
+ ActivationInterval: nil,
+ RunID: dc.RunID,
+ AttributeIDs: make([]string, 0),
+ Weight: 10,
+ }
+
+ filter := dc.RunFilters
+ if len(filter) != 0 {
+ if strings.HasPrefix(filter, utils.STATIC_VALUE_PREFIX) {
+ filter = filter[1:]
+ }
+ ch.FilterIDs = append(ch.FilterIDs, "*rsr::"+filter)
+ }
+ return
+}
+
+func (m *Migrator) derivedChargers2Chargers(dck *v1DerivedChargersWithKey) (err error) {
+ // (direction, tenant, category, account, subject)
+ skey := utils.SplitConcatenatedKey(dck.Key)
+ destination := ""
+ if len(dck.Value.DestinationIDs) != 0 {
+ destination = "*destination:Destination:"
+ keys := dcGetMapKeys(dck.Value.DestinationIDs)
+ destination += strings.Join(keys, utils.INFIELD_SEP)
+ }
+ filter := make([]string, 0)
+
+ if len(destination) != 0 {
+ filter = append(filter, destination)
+ }
+ if len(skey[2]) != 0 && skey[2] != utils.META_ANY {
+ filter = append(filter, "*string:Category:"+skey[2])
+ }
+ if len(skey[3]) != 0 && skey[3] != utils.META_ANY {
+ filter = append(filter, "*string:Account:"+skey[3])
+ }
+ if len(skey[4]) != 0 && skey[4] != utils.META_ANY {
+ filter = append(filter, "*string:Subject:"+skey[4])
+ }
+
+ for i, dc := range dck.Value.Chargers {
+ attr := derivedChargers2AttributeProfile(dc, skey[1], fmt.Sprintf("%s%v", dck.Key, i), filter)
+ ch := derivedChargers2Charger(dc, skey[1], fmt.Sprintf("%s%v", dck.Key, i), filter)
+ if len(attr.Attributes) != 0 {
+ if err = m.dmOut.DataManager().DataDB().SetAttributeProfileDrv(attr); err != nil {
+ return err
+ }
+ ch.AttributeIDs = append(ch.AttributeIDs, attr.ID)
+ }
+ if err = m.dmOut.DataManager().DataDB().SetChargerProfileDrv(ch); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func (m *Migrator) migrateV1DerivedChargers() (err error) {
+ for {
+ var dck *v1DerivedChargersWithKey
+ dck, err = m.dmIN.getV1DerivedChargers()
+ if err == utils.ErrNoMoreData {
+ break
+ }
+ if err != nil {
+ return err
+ }
+ if dck == nil || m.dryRun {
+ continue
+ }
+ if err = m.derivedChargers2Chargers(dck); err != nil {
+ return err
+ }
+ if err = m.dmIN.remV1DerivedChargers(dck.Key); err != nil {
+ return err
+ }
+ m.stats[utils.DerivedChargersV] += 1
+ }
+ if m.dryRun {
+ return
+ }
+ // All done, update version wtih current one
+ vrs := engine.Versions{utils.DerivedChargersV: engine.CurrentDataDBVersions()[utils.DerivedChargersV]}
+ if err = m.dmOut.DataManager().DataDB().SetVersions(vrs, false); err != nil {
+ return utils.NewCGRError(utils.Migrator,
+ utils.ServerErrorCaps,
+ err.Error(),
+ fmt.Sprintf("error: <%s> when updating DerivedChargers version into dataDB", err.Error()))
+ }
+ return
+}
+
func (m *Migrator) migrateCurrentDerivedChargers() (err error) {
var ids []string
ids, err = m.dmIN.DataManager().DataDB().GetKeysForPrefix(utils.DERIVEDCHARGERS_PREFIX)
@@ -63,9 +251,12 @@ func (m *Migrator) migrateDerivedChargers() (err error) {
return utils.NewCGRError(utils.Migrator,
utils.MandatoryIEMissingCaps,
utils.UndefinedVersion,
- "version number is not defined for ActionTriggers model")
+ "version number is not defined for DerivedChargers model")
}
+
switch vrs[utils.DerivedChargersV] {
+ case 1:
+ return m.migrateV1DerivedChargers()
case current[utils.DerivedChargersV]:
if m.sameDataDB {
return
diff --git a/migrator/derived_chargers_it_test.go b/migrator/derived_chargers_it_test.go
new file mode 100644
index 000000000..a840881ad
--- /dev/null
+++ b/migrator/derived_chargers_it_test.go
@@ -0,0 +1,286 @@
+// +build integration
+
+/*
+Real-time Online/Offline Charging System (OCS) for Telecom & ISP environments
+Copyright (C) ITsysCOM GmbH
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see
+*/
+
+package migrator
+
+import (
+ "fmt"
+ "log"
+ "path"
+ "reflect"
+ "sort"
+ "testing"
+
+ "github.com/cgrates/cgrates/config"
+ "github.com/cgrates/cgrates/engine"
+ "github.com/cgrates/cgrates/utils"
+)
+
+var (
+ dcCfgIn *config.CGRConfig
+ dcCfgOut *config.CGRConfig
+ dcMigrator *Migrator
+ dcAction string
+)
+
+var sTestsDCIT = []func(t *testing.T){
+ testDCITConnect,
+ testDCITFlush,
+ testDCITMigrateAndMove,
+}
+
+func TestDerivedChargersVMigrateITRedis(t *testing.T) {
+ inPath := path.Join(*dataDir, "conf", "samples", "tutmysql")
+ testStartDC("TestDerivedChargersVMigrateITRedis", inPath, inPath, utils.Migrate, t)
+}
+
+func TestDerivedChargersVMigrateITMongo(t *testing.T) {
+ inPath := path.Join(*dataDir, "conf", "samples", "tutmongo")
+ testStartDC("TestDerivedChargersVMigrateITMongo", inPath, inPath, utils.Migrate, t)
+}
+
+func TestDerivedChargersVITMove(t *testing.T) {
+ inPath := path.Join(*dataDir, "conf", "samples", "tutmongo")
+ outPath := path.Join(*dataDir, "conf", "samples", "tutmysql")
+ testStartDC("TestDerivedChargersVITMove", inPath, outPath, utils.Move, t)
+}
+
+func TestDerivedChargersVITMigrateMongo2Redis(t *testing.T) {
+ inPath := path.Join(*dataDir, "conf", "samples", "tutmongo")
+ outPath := path.Join(*dataDir, "conf", "samples", "tutmysql")
+ testStartDC("TestDerivedChargersVITMigrateMongo2Redis", inPath, outPath, utils.Migrate, t)
+}
+
+func TestDerivedChargersVITMoveEncoding(t *testing.T) {
+ inPath := path.Join(*dataDir, "conf", "samples", "tutmongo")
+ outPath := path.Join(*dataDir, "conf", "samples", "tutmongojson")
+ testStartDC("TestDerivedChargersVITMoveEncoding", inPath, outPath, utils.Move, t)
+}
+
+func TestDerivedChargersVITMoveEncoding2(t *testing.T) {
+ inPath := path.Join(*dataDir, "conf", "samples", "tutmysql")
+ outPath := path.Join(*dataDir, "conf", "samples", "tutmysqljson")
+ testStartDC("TestDerivedChargersVITMoveEncoding2", inPath, outPath, utils.Move, t)
+}
+
+func testStartDC(testName, inPath, outPath, action string, t *testing.T) {
+ var err error
+ dcAction = action
+ if dcCfgIn, err = config.NewCGRConfigFromFolder(inPath); err != nil {
+ t.Fatal(err)
+ }
+ if dcCfgOut, err = config.NewCGRConfigFromFolder(outPath); err != nil {
+ t.Fatal(err)
+ }
+ for _, stest := range sTestsDCIT {
+ t.Run(testName, stest)
+ }
+}
+
+func testDCITConnect(t *testing.T) {
+ dataDBIn, err := NewMigratorDataDB(dcCfgIn.DataDbCfg().DataDbType,
+ dcCfgIn.DataDbCfg().DataDbHost, dcCfgIn.DataDbCfg().DataDbPort,
+ dcCfgIn.DataDbCfg().DataDbName, dcCfgIn.DataDbCfg().DataDbUser,
+ dcCfgIn.DataDbCfg().DataDbPass, dcCfgIn.GeneralCfg().DBDataEncoding,
+ config.CgrConfig().CacheCfg(), "")
+ if err != nil {
+ log.Fatal(err)
+ }
+ dataDBOut, err := NewMigratorDataDB(dcCfgOut.DataDbCfg().DataDbType,
+ dcCfgOut.DataDbCfg().DataDbHost, dcCfgOut.DataDbCfg().DataDbPort,
+ dcCfgOut.DataDbCfg().DataDbName, dcCfgOut.DataDbCfg().DataDbUser,
+ dcCfgOut.DataDbCfg().DataDbPass, dcCfgOut.GeneralCfg().DBDataEncoding,
+ config.CgrConfig().CacheCfg(), "")
+ if err != nil {
+ log.Fatal(err)
+ }
+ dcMigrator, err = NewMigrator(dataDBIn, dataDBOut,
+ nil, nil, false, false, false)
+ if err != nil {
+ log.Fatal(err)
+ }
+}
+
+func testDCITFlush(t *testing.T) {
+ dcMigrator.dmOut.DataManager().DataDB().Flush("")
+ if err := engine.SetDBVersions(dcMigrator.dmOut.DataManager().DataDB()); err != nil {
+ t.Error("Error ", err.Error())
+ }
+ dcMigrator.dmIN.DataManager().DataDB().Flush("")
+ if err := engine.SetDBVersions(dcMigrator.dmIN.DataManager().DataDB()); err != nil {
+ t.Error("Error ", err.Error())
+ }
+}
+
+func testDCITMigrateAndMove(t *testing.T) {
+ dcGetMapKeys = func(m utils.StringMap) (keys []string) { //make sure destination are in order
+ keys = make([]string, len(m))
+ i := 0
+ for k, _ := range m {
+ keys[i] = k
+ i += 1
+ }
+ sort.Strings(keys)
+ return keys
+ }
+ derivch := &v1DerivedChargersWithKey{
+ Key: utils.ConcatenatedKey("*out", defaultTenant, utils.META_ANY, "1003", utils.META_ANY),
+ Value: &v1DerivedChargers{
+ DestinationIDs: utils.StringMap{"1001": true, "1002": true, "1003": true},
+ Chargers: []*v1DerivedCharger{
+ &v1DerivedCharger{
+ RunID: "RunID",
+ RunFilters: "~filterhdr1:s/(.+)/special_run3/",
+
+ RequestTypeField: utils.MetaDefault,
+ CategoryField: utils.MetaDefault,
+ AccountField: "^1004",
+ SubjectField: "call_1003",
+ },
+ },
+ },
+ }
+ attrProf := &engine.AttributeProfile{
+ Tenant: "cgrates.org",
+ ID: fmt.Sprintf("%s%v", derivch.Key, 0),
+ Contexts: []string{utils.META_ANY},
+ FilterIDs: []string{
+ "*destination:Destination:1001;1002;1003",
+ "*string:Account:1003",
+ },
+ ActivationInterval: nil,
+ Attributes: []*engine.Attribute{
+ {
+ FieldName: utils.Account,
+ Initial: utils.META_ANY,
+ Substitute: config.NewRSRParsersMustCompile("1004", true, utils.INFIELD_SEP),
+ Append: true,
+ },
+ {
+ FieldName: utils.Subject,
+ Initial: utils.META_ANY,
+ Substitute: config.NewRSRParsersMustCompile("call_1003", true, utils.INFIELD_SEP),
+ Append: true,
+ },
+ },
+ Blocker: false,
+ Weight: 10,
+ }
+ attrProf.Compile()
+ charger := &engine.ChargerProfile{
+ Tenant: defaultTenant,
+ ID: fmt.Sprintf("%s%v", derivch.Key, 0),
+ FilterIDs: []string{
+ "*destination:Destination:1001;1002;1003",
+ "*string:Account:1003",
+ "*rsr::~filterhdr1:s/(.+)/special_run3/",
+ },
+ ActivationInterval: nil,
+ RunID: "RunID",
+ AttributeIDs: []string{attrProf.ID},
+ Weight: 10,
+ }
+ switch dcAction {
+ case utils.Migrate:
+ err := dcMigrator.dmIN.setV1DerivedChargers(derivch)
+ if err != nil {
+ t.Error("Error when setting v1 DerivedChargersV ", err.Error())
+ }
+ currentVersion := engine.Versions{utils.DerivedChargersV: 1}
+ err = dcMigrator.dmIN.DataManager().DataDB().SetVersions(currentVersion, false)
+ if err != nil {
+ t.Error("Error when setting version for DerivedChargersV ", err.Error())
+ }
+ //check if version was set correctly
+ if vrs, err := dcMigrator.dmIN.DataManager().DataDB().GetVersions(""); err != nil {
+ t.Error(err)
+ } else if vrs[utils.DerivedChargersV] != 1 {
+ t.Errorf("Unexpected version returned: %d", vrs[utils.DerivedChargersV])
+ }
+ //migrate derivch
+ err, _ = dcMigrator.Migrate([]string{utils.MetaDerivedChargersV})
+ if err != nil {
+ t.Error("Error when migrating DerivedChargersV ", err.Error())
+ }
+ //check if version was updated
+ if vrs, err := dcMigrator.dmOut.DataManager().DataDB().GetVersions(""); err != nil {
+ t.Error(err)
+ } else if vrs[utils.DerivedChargersV] != 2 {
+ t.Errorf("Unexpected version returned: %d", vrs[utils.DerivedChargersV])
+ }
+ //check if derivch was migrate correctly
+ result, err := dcMigrator.dmOut.DataManager().DataDB().GetAttributeProfileDrv(defaultTenant, attrProf.ID)
+ if err != nil {
+ t.Fatalf("Error when getting Attributes %v", err.Error())
+ }
+ result.Compile()
+ sort.Slice(result.Attributes, func(i, j int) bool {
+ if result.Attributes[i].FieldName == result.Attributes[j].FieldName {
+ return result.Attributes[i].Initial.(string) < result.Attributes[j].Initial.(string)
+ }
+ return result.Attributes[i].FieldName < result.Attributes[j].FieldName
+ }) // only for test; map returns random keys
+ if !reflect.DeepEqual(*attrProf, *result) {
+ t.Errorf("Expecting: %+v, received: %+v", utils.ToJSON(attrProf), utils.ToJSON(result))
+ }
+ result2, err := dcMigrator.dmOut.DataManager().DataDB().GetChargerProfileDrv(defaultTenant, charger.ID)
+ if err != nil {
+ t.Fatalf("Error when getting Attributes %v", err.Error())
+ }
+ if !reflect.DeepEqual(*charger, *result2) {
+ t.Errorf("Expecting: %+v, received: %+v", utils.ToJSON(charger), utils.ToJSON(result2))
+ }
+
+ //check if old account was deleted
+ if _, err = dcMigrator.dmIN.getV1DerivedChargers(); err != utils.ErrNoMoreData {
+ t.Error("Error should be not found : ", err)
+ }
+
+ case utils.Move:
+ /* // No Move tests
+ if err := dcMigrator.dmIN.DataManager().DataDB().SetDerivedChargersV(derivch, utils.NonTransactional); err != nil {
+ t.Error(err)
+ }
+ currentVersion := engine.CurrentDataDBVersions()
+ err := dcMigrator.dmOut.DataManager().DataDB().SetVersions(currentVersion, false)
+ if err != nil {
+ t.Error("Error when setting version for DerivedChargersV ", err.Error())
+ }
+ //migrate accounts
+ err, _ = dcMigrator.Migrate([]string{utils.MetaDerivedChargersV})
+ if err != nil {
+ t.Error("Error when dcMigratorrating DerivedChargersV ", err.Error())
+ }
+ //check if account was migrate correctly
+ result, err := dcMigrator.dmOut.DataManager().DataDB().GetDerivedChargersV(derivch.GetId(), false)
+ if err != nil {
+ t.Error(err)
+ }
+ if !reflect.DeepEqual(derivch, result) {
+ t.Errorf("Expecting: %+v, received: %+v", derivch, result)
+ }
+ //check if old account was deleted
+ result, err = dcMigrator.dmIN.DataManager().DataDB().GetDerivedChargersV(derivch.GetId(), false)
+ if err != utils.ErrNotFound {
+ t.Error(err)
+ }
+ // */
+ }
+}
diff --git a/migrator/derived_chargers_test.go b/migrator/derived_chargers_test.go
new file mode 100644
index 000000000..1e3ce6189
--- /dev/null
+++ b/migrator/derived_chargers_test.go
@@ -0,0 +1,269 @@
+/*
+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 migrator
+
+import (
+ "reflect"
+ "testing"
+
+ "github.com/cgrates/cgrates/config"
+ "github.com/cgrates/cgrates/engine"
+ "github.com/cgrates/cgrates/utils"
+)
+
+func TestFieldinfo2Attribute(t *testing.T) {
+ type testA struct {
+ FieldName string
+ FieldInfo string
+ Initial []*engine.Attribute
+ Expected []*engine.Attribute
+ }
+ tests := []testA{
+ testA{
+ FieldName: utils.Account,
+ FieldInfo: utils.MetaDefault,
+ Initial: make([]*engine.Attribute, 0),
+ Expected: make([]*engine.Attribute, 0),
+ },
+ testA{
+ FieldName: utils.Account,
+ FieldInfo: "",
+ Initial: make([]*engine.Attribute, 0),
+ Expected: make([]*engine.Attribute, 0),
+ },
+ testA{
+ FieldName: utils.Account,
+ FieldInfo: "^1003",
+ Initial: make([]*engine.Attribute, 0),
+ Expected: []*engine.Attribute{
+ &engine.Attribute{
+ FieldName: utils.Account,
+ Initial: utils.META_ANY,
+ Substitute: config.NewRSRParsersMustCompile("1003", true, utils.INFIELD_SEP),
+ Append: true,
+ },
+ },
+ },
+ testA{
+ FieldName: utils.Subject,
+ FieldInfo: `~effective_caller_id_number:s/(\d+)/+$1/`,
+ Initial: make([]*engine.Attribute, 0),
+ Expected: []*engine.Attribute{
+ &engine.Attribute{
+ FieldName: utils.Subject,
+ Initial: utils.META_ANY,
+ Substitute: config.NewRSRParsersMustCompile(`~effective_caller_id_number:s/(\d+)/+$1/`, true, utils.INFIELD_SEP),
+ Append: true,
+ },
+ },
+ },
+ testA{
+ FieldName: utils.Subject,
+ FieldInfo: "^call_1003",
+ Initial: []*engine.Attribute{
+ &engine.Attribute{
+ FieldName: utils.Account,
+ Initial: utils.META_ANY,
+ Substitute: config.NewRSRParsersMustCompile("1003", true, utils.INFIELD_SEP),
+ Append: true,
+ },
+ },
+ Expected: []*engine.Attribute{
+ &engine.Attribute{
+ FieldName: utils.Account,
+ Initial: utils.META_ANY,
+ Substitute: config.NewRSRParsersMustCompile("1003", true, utils.INFIELD_SEP),
+ Append: true,
+ },
+ &engine.Attribute{
+ FieldName: utils.Subject,
+ Initial: utils.META_ANY,
+ Substitute: config.NewRSRParsersMustCompile("call_1003", true, utils.INFIELD_SEP),
+ Append: true,
+ },
+ },
+ },
+ }
+ for i, v := range tests {
+ if rply := fieldinfo2Attribute(v.Initial, v.FieldName, v.FieldInfo); !reflect.DeepEqual(v.Expected, rply) {
+ t.Errorf("For %v expected: %s ,recieved: %s", i, utils.ToJSON(v.Expected), utils.ToJSON(rply))
+ }
+ }
+}
+
+func TestDerivedChargers2AttributeProfile(t *testing.T) {
+ type testC struct {
+ DC *v1DerivedCharger
+ Tenant string
+ Key string
+ Filters []string
+ Expected *engine.AttributeProfile
+ }
+ tests := []testC{
+ testC{
+ DC: &v1DerivedCharger{
+ RequestTypeField: utils.MetaDefault,
+ CategoryField: "^*voice",
+ AccountField: "^1003",
+ },
+ Tenant: defaultTenant,
+ Key: "key1",
+ Filters: make([]string, 0),
+ Expected: &engine.AttributeProfile{
+ Tenant: defaultTenant,
+ ID: "key1",
+ Contexts: []string{utils.META_ANY},
+ FilterIDs: make([]string, 0),
+ ActivationInterval: nil,
+ Attributes: []*engine.Attribute{
+ &engine.Attribute{
+ FieldName: utils.Category,
+ Initial: utils.META_ANY,
+ Substitute: config.NewRSRParsersMustCompile("*voice", true, utils.INFIELD_SEP),
+ Append: true,
+ },
+ &engine.Attribute{
+ FieldName: utils.Account,
+ Initial: utils.META_ANY,
+ Substitute: config.NewRSRParsersMustCompile("1003", true, utils.INFIELD_SEP),
+ Append: true,
+ },
+ },
+ Blocker: false,
+ Weight: 10,
+ },
+ },
+ testC{
+ DC: &v1DerivedCharger{
+ RequestTypeField: utils.MetaDefault,
+ CategoryField: "^*voice",
+ AccountField: "^1003",
+ SubjectField: "call_1003_to_1004",
+ DestinationField: "^1004",
+ },
+ Tenant: defaultTenant,
+ Key: "key1",
+ Filters: []string{"*string:Subject:1005"},
+ Expected: &engine.AttributeProfile{
+ Tenant: defaultTenant,
+ ID: "key1",
+ Contexts: []string{utils.META_ANY},
+ FilterIDs: []string{"*string:Subject:1005"},
+ ActivationInterval: nil,
+ Attributes: []*engine.Attribute{
+ &engine.Attribute{
+ FieldName: utils.Category,
+ Initial: utils.META_ANY,
+ Substitute: config.NewRSRParsersMustCompile("*voice", true, utils.INFIELD_SEP),
+ Append: true,
+ },
+ &engine.Attribute{
+ FieldName: utils.Account,
+ Initial: utils.META_ANY,
+ Substitute: config.NewRSRParsersMustCompile("1003", true, utils.INFIELD_SEP),
+ Append: true,
+ },
+ &engine.Attribute{
+ FieldName: utils.Subject,
+ Initial: utils.META_ANY,
+ Substitute: config.NewRSRParsersMustCompile("call_1003_to_1004", true, utils.INFIELD_SEP),
+ Append: true,
+ },
+ &engine.Attribute{
+ FieldName: utils.Destination,
+ Initial: utils.META_ANY,
+ Substitute: config.NewRSRParsersMustCompile("1004", true, utils.INFIELD_SEP),
+ Append: true,
+ },
+ },
+ Blocker: false,
+ Weight: 10,
+ },
+ },
+ }
+ for i, v := range tests {
+ if rply := derivedChargers2AttributeProfile(v.DC, v.Tenant, v.Key, v.Filters); !reflect.DeepEqual(v.Expected, rply) {
+ t.Errorf("For %v expected: %s ,recieved: %s", i, utils.ToJSON(v.Expected), utils.ToJSON(rply))
+ }
+ }
+}
+
+func TestDerivedChargers2Charger(t *testing.T) {
+ type testB struct {
+ DC *v1DerivedCharger
+ Tenant string
+ Key string
+ Filters []string
+ Expected *engine.ChargerProfile
+ }
+ tests := []testB{
+ testB{
+ DC: &v1DerivedCharger{
+ RunID: "runID",
+ RunFilters: "~Header4:s/a/${1}b/{*duration_seconds&*round:2}(b&c)",
+ RequestTypeField: utils.MetaDefault,
+ CategoryField: "^*voice",
+ AccountField: "^1003",
+ },
+ Tenant: defaultTenant,
+ Key: "key2",
+ Filters: []string{
+ "*string:Category:*voice1",
+ "*string:Account:1001",
+ },
+ Expected: &engine.ChargerProfile{
+ Tenant: defaultTenant,
+ ID: "key2",
+ FilterIDs: []string{
+ "*string:Category:*voice1",
+ "*string:Account:1001",
+ "*rsr::~Header4:s/a/${1}b/{*duration_seconds&*round:2}(b&c)",
+ },
+ ActivationInterval: nil,
+ RunID: "runID",
+ AttributeIDs: make([]string, 0),
+ Weight: 10,
+ },
+ },
+ testB{
+ DC: &v1DerivedCharger{
+ RunID: "runID2",
+ RunFilters: "^1003",
+ AccountField: "^1003",
+ },
+ Tenant: defaultTenant,
+ Key: "key2",
+ Filters: []string{},
+ Expected: &engine.ChargerProfile{
+ Tenant: defaultTenant,
+ ID: "key2",
+ FilterIDs: []string{"*rsr::1003"},
+ ActivationInterval: nil,
+ RunID: "runID2",
+ AttributeIDs: make([]string, 0),
+ Weight: 10,
+ },
+ },
+ }
+ for i, v := range tests {
+ if rply := derivedChargers2Charger(v.DC, v.Tenant, v.Key, v.Filters); !reflect.DeepEqual(v.Expected, rply) {
+ t.Errorf("For %v expected: %s ,recieved: %s", i, utils.ToJSON(v.Expected), utils.ToJSON(rply))
+ }
+ }
+}
diff --git a/migrator/migrator_datadb.go b/migrator/migrator_datadb.go
index b755330bd..6bd711493 100644
--- a/migrator/migrator_datadb.go
+++ b/migrator/migrator_datadb.go
@@ -52,6 +52,9 @@ type MigratorDataDB interface {
getV1User() (v1u *v1UserProfile, err error)
setV1User(us *v1UserProfile) (err error)
remV1User(key string) (err error)
+ getV1DerivedChargers() (v1d *v1DerivedChargersWithKey, err error)
+ setV1DerivedChargers(dc *v1DerivedChargersWithKey) (err error)
+ remV1DerivedChargers(key string) (err error)
DataManager() *engine.DataManager
}
diff --git a/migrator/storage_map_datadb.go b/migrator/storage_map_datadb.go
index 704fc6dac..2b8b4c6f6 100755
--- a/migrator/storage_map_datadb.go
+++ b/migrator/storage_map_datadb.go
@@ -198,3 +198,19 @@ func (v1ms *mapMigrator) setV1User(us *v1UserProfile) (err error) {
func (v1ms *mapMigrator) remV1User(key string) (err error) {
return utils.ErrNotImplemented
}
+
+// DerivedChargers methods
+//get
+func (v1ms *mapMigrator) getV1DerivedChargers() (v1d *v1DerivedChargersWithKey, err error) {
+ return nil, utils.ErrNotImplemented
+}
+
+//set
+func (v1ms *mapMigrator) setV1DerivedChargers(dc *v1DerivedChargersWithKey) (err error) {
+ return utils.ErrNotImplemented
+}
+
+//rem
+func (v1ms *mapMigrator) remV1DerivedChargers(key string) (err error) {
+ return utils.ErrNotImplemented
+}
diff --git a/migrator/storage_mongo_datadb.go b/migrator/storage_mongo_datadb.go
index 4cc0478c4..ca798c42d 100644
--- a/migrator/storage_mongo_datadb.go
+++ b/migrator/storage_mongo_datadb.go
@@ -33,6 +33,7 @@ const (
v2ThresholdProfileCol = "threshold_profiles"
v1AliasCol = "aliases"
v1UserCol = "users"
+ v1DerivedChargersCol = "derived_chargers"
)
type mongoMigrator struct {
@@ -483,3 +484,41 @@ func (v1ms *mongoMigrator) remV1User(key string) (err error) {
_, err = v1ms.mgoDB.DB().Collection(v1UserCol).DeleteOne(v1ms.mgoDB.GetContext(), bson.M{"key": key})
return
}
+
+// DerivedChargers methods
+//get
+func (v1ms *mongoMigrator) getV1DerivedChargers() (v1d *v1DerivedChargersWithKey, err error) {
+ if v1ms.cursor == nil {
+ var cursor mongo.Cursor
+ cursor, err = v1ms.mgoDB.DB().Collection(v1DerivedChargersCol).Find(v1ms.mgoDB.GetContext(), bson.D{})
+ if err != nil {
+ return nil, err
+ }
+ v1ms.cursor = &cursor
+ }
+ if !(*v1ms.cursor).Next(v1ms.mgoDB.GetContext()) {
+ (*v1ms.cursor).Close(v1ms.mgoDB.GetContext())
+ v1ms.cursor = nil
+ return nil, utils.ErrNoMoreData
+ }
+ v1d = new(v1DerivedChargersWithKey)
+ if err := (*v1ms.cursor).Decode(v1d); err != nil {
+ return nil, err
+ }
+ return v1d, nil
+}
+
+//set
+func (v1ms *mongoMigrator) setV1DerivedChargers(dc *v1DerivedChargersWithKey) (err error) {
+ _, err = v1ms.mgoDB.DB().Collection(v1DerivedChargersCol).UpdateOne(v1ms.mgoDB.GetContext(), bson.M{"key": dc.Key},
+ bson.M{"$set": dc},
+ options.Update().SetUpsert(true),
+ )
+ return
+}
+
+//rem
+func (v1ms *mongoMigrator) remV1DerivedChargers(key string) (err error) {
+ _, err = v1ms.mgoDB.DB().Collection(v1DerivedChargersCol).DeleteOne(v1ms.mgoDB.GetContext(), bson.M{"key": key})
+ return
+}
diff --git a/migrator/storage_redis.go b/migrator/storage_redis.go
index 6e47f382c..3d0515482 100644
--- a/migrator/storage_redis.go
+++ b/migrator/storage_redis.go
@@ -570,7 +570,7 @@ func (v1rs *redisMigrator) getV1User() (v1u *v1UserProfile, err error) {
if err != nil {
return
} else if len(v1rs.dataKeys) == 0 {
- return nil, utils.ErrNotFound
+ return nil, utils.ErrNoMoreData
}
v1rs.qryIdx = utils.IntPointer(0)
}
@@ -603,3 +603,50 @@ func (v1rs *redisMigrator) setV1User(us *v1UserProfile) (err error) {
func (v1rs *redisMigrator) remV1User(key string) (err error) {
return v1rs.rds.Cmd("DEL", utils.USERS_PREFIX+key).Err
}
+
+// DerivedChargers methods
+//get
+func (v1rs *redisMigrator) getV1DerivedChargers() (v1d *v1DerivedChargersWithKey, err error) {
+ if v1rs.qryIdx == nil {
+ v1rs.dataKeys, err = v1rs.rds.GetKeysForPrefix(utils.DERIVEDCHARGERS_PREFIX)
+ if err != nil {
+ return
+ } else if len(v1rs.dataKeys) == 0 {
+ return nil, utils.ErrNoMoreData
+ }
+ v1rs.qryIdx = utils.IntPointer(0)
+ }
+ if *v1rs.qryIdx <= len(v1rs.dataKeys)-1 {
+ strVal, err := v1rs.rds.Cmd("GET", v1rs.dataKeys[*v1rs.qryIdx]).Bytes()
+ if err != nil {
+ return nil, err
+ }
+ v1d = new(v1DerivedChargersWithKey)
+ v1d.Key = strings.TrimPrefix(v1rs.dataKeys[*v1rs.qryIdx], utils.DERIVEDCHARGERS_PREFIX)
+ v1d.Value = new(v1DerivedChargers)
+ if err := v1rs.rds.Marshaler().Unmarshal(strVal, v1d.Value); err != nil {
+ return nil, err
+ }
+ *v1rs.qryIdx = *v1rs.qryIdx + 1
+ return v1d, nil
+ }
+ v1rs.qryIdx = nil
+ return nil, utils.ErrNoMoreData
+}
+
+//set
+func (v1rs *redisMigrator) setV1DerivedChargers(dc *v1DerivedChargersWithKey) (err error) {
+ if dc == nil || len(dc.Value.Chargers) == 0 {
+ return v1rs.remV1DerivedChargers(dc.Key)
+ }
+ bit, err := v1rs.rds.Marshaler().Marshal(dc.Value)
+ if err != nil {
+ return err
+ }
+ return v1rs.rds.Cmd("SET", utils.DERIVEDCHARGERS_PREFIX+dc.Key, bit).Err
+}
+
+//rem
+func (v1rs *redisMigrator) remV1DerivedChargers(key string) (err error) {
+ return v1rs.rds.Cmd("DEL", utils.DERIVEDCHARGERS_PREFIX+key).Err
+}
diff --git a/migrator/user.go b/migrator/user.go
index 030b27e21..f627d0367 100644
--- a/migrator/user.go
+++ b/migrator/user.go
@@ -33,7 +33,6 @@ type v1UserProfile struct {
Masked bool //disable if true
Profile map[string]string
Weight float64
- ponder int
}
func (ud *v1UserProfile) GetId() string {
diff --git a/utils/coreutils.go b/utils/coreutils.go
index ecafe967f..e752206ca 100644
--- a/utils/coreutils.go
+++ b/utils/coreutils.go
@@ -340,6 +340,10 @@ func ConcatenatedKey(keyVals ...string) string {
return strings.Join(keyVals, CONCATENATED_KEY_SEP)
}
+func SplitConcatenatedKey(key string) []string {
+ return strings.Split(key, CONCATENATED_KEY_SEP)
+}
+
func LCRKey(direction, tenant, category, account, subject string) string {
return ConcatenatedKey(direction, tenant, category, account, subject)
}