diff --git a/engine/version.go b/engine/version.go index 04df8f59f..bd3421f65 100644 --- a/engine/version.go +++ b/engine/version.go @@ -165,6 +165,7 @@ func CurrentDataDBVersions() Versions { utils.Dispatchers: 2, utils.LoadIDsVrs: 1, utils.RateProfiles: 1, + utils.ActionProfiles: 1, } } @@ -196,6 +197,7 @@ func CurrentStorDBVersions() Versions { utils.TpChargers: 1, utils.TpDispatchers: 1, utils.TpRateProfiles: 1, + utils.TpActionProfiles: 1, } } diff --git a/migrator/action_profiles.go b/migrator/action_profiles.go new file mode 100644 index 000000000..39dee5333 --- /dev/null +++ b/migrator/action_profiles.go @@ -0,0 +1,95 @@ +/* +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" + "strings" + + "github.com/cgrates/cgrates/engine" + + "github.com/cgrates/cgrates/utils" +) + +func (m *Migrator) migrateCurrentActionProfiles() (err error) { + var ids []string + ids, err = m.dmIN.DataManager().DataDB().GetKeysForPrefix(utils.ActionProfilePrefix) + if err != nil { + return err + } + for _, id := range ids { + tntID := strings.SplitN(strings.TrimPrefix(id, utils.ActionProfilePrefix), utils.InInFieldSep, 2) + if len(tntID) < 2 { + return fmt.Errorf("Invalid key <%s> when migrating from action profiles", id) + } + ap, err := m.dmIN.DataManager().GetActionProfile(tntID[0], tntID[1], false, false, utils.NonTransactional) + if err != nil { + return err + } + if ap == nil || m.dryRun { + continue + } + if err := m.dmOut.DataManager().SetActionProfile(ap, true); err != nil { + return err + } + if err := m.dmIN.DataManager().RemoveActionProfile(tntID[0], tntID[1], utils.NonTransactional, false); err != nil { + return err + } + m.stats[utils.ActionProfiles]++ + } + return +} + +func (m *Migrator) migrateActionProfiles() (err error) { + var vrs engine.Versions + current := engine.CurrentDataDBVersions() + if vrs, err = m.getVersions(utils.ActionProfiles); err != nil { + return + } + migrated := true + for { + version := vrs[utils.ActionProfiles] + for { + switch version { + default: + return fmt.Errorf("Unsupported version %v", version) + case current[utils.ActionProfiles]: + migrated = false + if m.sameDataDB { + break + } + if err = m.migrateCurrentActionProfiles(); err != nil { + return + } + } + if version == current[utils.ActionProfiles] || err == utils.ErrNoMoreData { + break + } + } + if err == utils.ErrNoMoreData || !migrated { + break + } + m.stats[utils.ActionProfiles]++ + } + //All done, update version with current one + if err = m.setVersions(utils.ActionProfiles); err != nil { + return + } + return m.ensureIndexesDataDB(engine.ColApp) +} diff --git a/migrator/actionprofiles_it_test.go b/migrator/actionprofiles_it_test.go new file mode 100644 index 000000000..0c430cd40 --- /dev/null +++ b/migrator/actionprofiles_it_test.go @@ -0,0 +1,252 @@ +// +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 ( + "log" + "path" + "reflect" + "testing" + + "github.com/cgrates/cgrates/engine" + + "github.com/cgrates/cgrates/utils" + + "github.com/cgrates/cgrates/config" +) + +var ( + actPrfPathIn string + actPrfPathOut string + actPrfCfgIn *config.CGRConfig + actPrfCfgOut *config.CGRConfig + actPrfMigrator *Migrator + actPrfAction string +) + +var sTestsActPrfIT = []func(t *testing.T){ + testActPrfITConnect, + testActPrfITFlush, + testActPrfMigrateAndMove, +} + +func TestActPrfITMove1(t *testing.T) { + var err error + actPrfPathIn = path.Join(*dataDir, "conf", "samples", "tutmongo") + actPrfCfgIn, err = config.NewCGRConfigFromPath(actPrfPathIn) + if err != nil { + t.Fatal(err) + } + actPrfPathOut = path.Join(*dataDir, "conf", "samples", "tutmysql") + actPrfCfgOut, err = config.NewCGRConfigFromPath(actPrfPathOut) + if err != nil { + t.Fatal(err) + } + actPrfAction = utils.Move + for _, stest := range sTestsActPrfIT { + t.Run("TestActPrfITMove1", stest) + } + actPrfMigrator.Close() +} + +func TestActPrfITMove2(t *testing.T) { + var err error + actPrfPathIn = path.Join(*dataDir, "conf", "samples", "tutmysql") + actPrfCfgIn, err = config.NewCGRConfigFromPath(actPrfPathIn) + if err != nil { + t.Error(err) + } + actPrfPathOut = path.Join(*dataDir, "conf", "samples", "tutmongo") + actPrfCfgOut, err = config.NewCGRConfigFromPath(actPrfPathOut) + if err != nil { + t.Error(err) + } + actPrfAction = utils.Move + for _, stest := range sTestsActPrfIT { + t.Run("TestActPrfITMove2", stest) + } + actPrfMigrator.Close() +} + +func TestActPrfITMoveEncoding(t *testing.T) { + var err error + actPrfPathIn = path.Join(*dataDir, "conf", "samples", "tutmongo") + actPrfCfgIn, err = config.NewCGRConfigFromPath(actPrfPathIn) + if err != nil { + t.Error(err) + } + actPrfPathOut = path.Join(*dataDir, "conf", "samples", "tutmongojson") + actPrfCfgOut, err = config.NewCGRConfigFromPath(actPrfPathOut) + if err != nil { + t.Error(err) + } + actPrfAction = utils.Move + for _, stest := range sTestsActPrfIT { + t.Run("TestActPrfITMove2", stest) + } + actPrfMigrator.Close() +} + +func TestActPrfITMoveEncoding2(t *testing.T) { + var err error + actPrfPathIn = path.Join(*dataDir, "conf", "samples", "tutmysql") + actPrfCfgIn, err = config.NewCGRConfigFromPath(actPrfPathIn) + if err != nil { + t.Error(err) + } + actPrfPathOut = path.Join(*dataDir, "conf", "samples", "tutmysqljson") + actPrfCfgOut, err = config.NewCGRConfigFromPath(actPrfPathOut) + if err != nil { + t.Error(err) + } + actPrfAction = utils.Move + for _, stest := range sTestsActPrfIT { + t.Run("TestActPrfITMove2", stest) + } + actPrfMigrator.Close() +} + +func testActPrfITConnect(t *testing.T) { + dataDBIn, err := NewMigratorDataDB(actPrfCfgIn.DataDbCfg().DataDbType, + actPrfCfgIn.DataDbCfg().DataDbHost, actPrfCfgIn.DataDbCfg().DataDbPort, + actPrfCfgIn.DataDbCfg().DataDbName, actPrfCfgIn.DataDbCfg().DataDbUser, + actPrfCfgIn.DataDbCfg().DataDbPass, actPrfCfgIn.GeneralCfg().DBDataEncoding, + config.CgrConfig().CacheCfg(), actPrfCfgIn.DataDbCfg().Opts) + if err != nil { + log.Fatal(err) + } + dataDBOut, err := NewMigratorDataDB(actPrfCfgOut.DataDbCfg().DataDbType, + actPrfCfgOut.DataDbCfg().DataDbHost, actPrfCfgOut.DataDbCfg().DataDbPort, + actPrfCfgOut.DataDbCfg().DataDbName, actPrfCfgOut.DataDbCfg().DataDbUser, + actPrfCfgOut.DataDbCfg().DataDbPass, actPrfCfgOut.GeneralCfg().DBDataEncoding, + config.CgrConfig().CacheCfg(), actPrfCfgOut.DataDbCfg().Opts) + if err != nil { + log.Fatal(err) + } + if reflect.DeepEqual(actPrfPathIn, actPrfPathOut) { + actPrfMigrator, err = NewMigrator(dataDBIn, dataDBOut, nil, nil, + false, true, false, false) + } else { + actPrfMigrator, err = NewMigrator(dataDBIn, dataDBOut, nil, nil, + false, false, false, false) + } + if err != nil { + log.Fatal(err) + } +} + +func testActPrfITFlush(t *testing.T) { + //dmIn + if err := actPrfMigrator.dmIN.DataManager().DataDB().Flush(utils.EmptyString); err != nil { + t.Error(err) + } + if isEmpty, err := actPrfMigrator.dmIN.DataManager().DataDB().IsDBEmpty(); err != nil { + t.Error(err) + } else if !isEmpty { + t.Errorf("Expecting: true got :%+v", isEmpty) + } + if err := engine.SetDBVersions(actPrfMigrator.dmIN.DataManager().DataDB()); err != nil { + t.Error(err) + } + + //dmOut + if err := actPrfMigrator.dmOut.DataManager().DataDB().Flush(utils.EmptyString); err != nil { + t.Error(err) + } + if isEMpty, err := actPrfMigrator.dmOut.DataManager().DataDB().IsDBEmpty(); err != nil { + t.Error(err) + } else if !isEMpty { + t.Error(err) + } + if err := engine.SetDBVersions(actPrfMigrator.dmOut.DataManager().DataDB()); err != nil { + t.Error(err) + } +} + +func testActPrfMigrateAndMove(t *testing.T) { + actPrf := &engine.ActionProfile{ + Tenant: "cgrates.org", + ID: "TEST_ID1", + FilterIDs: []string{"*string:~*req.Account:1001"}, + Weight: 20, + Schedule: utils.ASAP, + AccountIDs: map[string]struct{}{ + "1001": {}, + }, + Actions: []*engine.APAction{ + { + ID: "TOPUP", + FilterIDs: []string{}, + Type: "*topup", + Path: "~*balance.TestBalance.Value", + }, + { + ID: "TOPUP_TEST_VOICE", + FilterIDs: []string{}, + Type: "*topup", + Path: "~*balance.TestVoiceBalance.Value", + }, + }, + } + switch actPrfAction { + case utils.Migrate: // for the moment only one version of actions profiles exists + case utils.Move: + //set, get and migrate + if err := actPrfMigrator.dmIN.DataManager().SetActionProfile(actPrf, true); err != nil { + t.Error(err) + } + currentVersion := engine.CurrentDataDBVersions() + err := actPrfMigrator.dmIN.DataManager().DataDB().SetVersions(currentVersion, false) + if err != nil { + t.Error("Error when setting version for ActionPrf", err.Error()) + } + + _, err = actPrfMigrator.dmOut.DataManager().GetActionProfile(actPrf.Tenant, actPrf.ID, + false, false, utils.NonTransactional) + if err != utils.ErrNotFound { + t.Error(err) + } + + err, _ = actPrfMigrator.Migrate([]string{utils.MetaActionProfiles}) + if err != nil { + t.Error("Error when migrating ActPrf", err.Error()) + } + //compared with dmOut + receivedACtPrf, err := actPrfMigrator.dmOut.DataManager().GetActionProfile(actPrf.Tenant, actPrf.ID, + false, false, utils.NonTransactional) + if err != nil { + t.Error(err) + } + if !reflect.DeepEqual(receivedACtPrf, actPrf) { + t.Errorf("Expected %+v, received %+v", utils.ToJSON(actPrf), utils.ToJSON(receivedACtPrf)) + } + + //compared with dmIn(should be empty) + _, err = actPrfMigrator.dmIN.DataManager().GetActionProfile(actPrf.Tenant, actPrf.ID, + false, false, utils.NonTransactional) + if err != utils.ErrNotFound { + t.Error(err) + } + if actPrfMigrator.stats[utils.ActionProfiles] != 1 { + t.Errorf("Expected 1, received: %v", actPrfMigrator.stats[utils.ActionProfiles]) + } + } +} diff --git a/migrator/migrator.go b/migrator/migrator.go index c93e348c8..148be694c 100755 --- a/migrator/migrator.go +++ b/migrator/migrator.go @@ -134,6 +134,8 @@ func (m *Migrator) Migrate(taskIDs []string) (err error, stats map[string]int) { err = m.migrateRatingPlans() case utils.MetaRatingProfiles: err = m.migrateRatingProfiles() + case utils.MetaActionProfiles: + err = m.migrateActionProfiles() case utils.MetaDestinations: err = m.migrateDestinations() case utils.MetaReverseDestinations: diff --git a/migrator/rateprofiles_it_test.go b/migrator/rateprofiles_it_test.go index 202cd73d7..2c3d4ec9e 100644 --- a/migrator/rateprofiles_it_test.go +++ b/migrator/rateprofiles_it_test.go @@ -221,7 +221,7 @@ func testRatePrfITMigrateAndMove(t *testing.T) { t.Fatal(err) } switch ratePrfAction { - case utils.Migrate: // for the moment only one version of rate profiles exists + case utils.Migrate: //QQ for the moment only one version of rate profiles exists case utils.Move: if err := ratePrfMigrator.dmIN.DataManager().SetRateProfile(rPrf, true); err != nil { t.Error(err) diff --git a/utils/consts.go b/utils/consts.go index c57e75942..4dd2d9f54 100755 --- a/utils/consts.go +++ b/utils/consts.go @@ -1101,6 +1101,7 @@ const ( TpDispatcherProfiles = "TpDispatcherProfiles" TpDispatcherHosts = "TpDispatcherHosts" TpRateProfiles = "TpRateProfiles" + TpActionProfiles = "TpActionProfiles" ) // Dispatcher Const