diff --git a/apier/v1/tpstats.go b/apier/v1/tpstats.go new file mode 100644 index 000000000..cb2e507b2 --- /dev/null +++ b/apier/v1/tpstats.go @@ -0,0 +1,85 @@ +/* +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/utils" +) + +func (self *ApierV1) SetTPStat(attr utils.TPStats, reply *string) error { + if missing := utils.MissingStructFields(&attr, []string{"TPid", "ID"}); len(missing) != 0 { + return utils.NewErrMandatoryIeMissing(missing...) + } + if err := self.StorDb.SetTPStats([]*utils.TPStats{&attr}); err != nil { + return utils.APIErrorHandler(err) + } + *reply = utils.OK + return nil +} + +type AttrGetTPStat struct { + TPid string // Tariff plan id + ID string +} + +func (self *ApierV1) GetTPStat(attr AttrGetTPStat, reply *utils.TPStats) error { + if missing := utils.MissingStructFields(&attr, []string{"TPid", "ID"}); len(missing) != 0 { //Params missing + return utils.NewErrMandatoryIeMissing(missing...) + } + if rls, err := self.StorDb.GetTPStats(attr.TPid, attr.ID); err != nil { + if err.Error() != utils.ErrNotFound.Error() { + err = utils.NewErrServerError(err) + } + return err + } else { + *reply = *rls[0] + } + return nil +} + +type AttrGetTPStatIds struct { + TPid string // Tariff plan id + utils.Paginator +} + +func (self *ApierV1) GetTPStatIDs(attrs AttrGetTPStatIds, reply *[]string) error { + 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.TBLTPStats, utils.TPDistinctIds{"tag"}, nil, &attrs.Paginator); err != nil { + return utils.NewErrServerError(err) + } else if ids == nil { + return utils.ErrNotFound + } else { + *reply = ids + } + return nil +} + +func (self *ApierV1) RemTPStat(attrs AttrGetTPStat, 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.TBLTPStats, attrs.TPid, map[string]string{"tag": attrs.ID}); err != nil { + return utils.NewErrServerError(err) + } else { + *reply = utils.OK + } + return nil + +} diff --git a/apier/v1/tpstats_it_test.go b/apier/v1/tpstats_it_test.go new file mode 100644 index 000000000..b8e840a62 --- /dev/null +++ b/apier/v1/tpstats_it_test.go @@ -0,0 +1,175 @@ +/* +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 tpCfgPath string +var tpCfg *config.CGRConfig +var tpRPC *rpc.Client + +func TestTPStatInitCfg(t *testing.T) { + var err error + tpCfgPath = path.Join(*dataDir, "conf", "samples", "tutmysql") + tpCfg, err = config.NewCGRConfigFromFolder(tpCfgPath) + if err != nil { + t.Error(err) + } + tpCfg.DataFolderPath = *dataDir // Share DataFolderPath through config towards StoreDb for Flush() + config.SetCgrConfig(tpCfg) +} + +// Wipe out the cdr database +func TestTPStatResetStorDb(t *testing.T) { + if err := engine.InitStorDb(tpCfg); err != nil { + t.Fatal(err) + } +} + +// Start CGR Engine + +func TestTPStatStartEngine(t *testing.T) { + if _, err := engine.StopStartEngine(tpCfgPath, 1000); err != nil { + t.Fatal(err) + } +} + +// Connect rpc client to rater +func TestTPStatRpcConn(t *testing.T) { + var err error + tpRPC, err = jsonrpc.Dial("tcp", tpCfg.RPCJSONListen) // We connect over JSON so we can also troubleshoot if needed + if err != nil { + t.Fatal(err) + } +} + +var tpStat = &utils.TPStats{ + TPid: "TPS1", + ID: "Stat1", + Filters: []*utils.TPRequestFilter{ + &utils.TPRequestFilter{ + Type: "*string", + FieldName: "Account", + Values: []string{"1001", "1002"}, + }, + &utils.TPRequestFilter{ + Type: "*string_prefix", + FieldName: "Destination", + Values: []string{"10", "20"}, + }, + }, + ActivationInterval: &utils.TPActivationInterval{ + ActivationTime: "2014-07-29T15:00:00Z", + ExpiryTime: "", + }, + TTL: "1", + Metrics: []string{"MetricValue", "MetricValueTwo"}, + Blocker: true, + Stored: true, + Weight: 20, + Thresholds: nil, +} + +func TestTPStatGetTPStatIDs(t *testing.T) { + var reply []string + if err := tpRPC.Call("ApierV1.GetTPStatIDs", AttrGetTPStatIds{TPid: "TPS1"}, &reply); err == nil || err.Error() != utils.ErrNotFound.Error() { + t.Error(err) + } +} + +func TestTPStatSetTPStat(t *testing.T) { + var result string + if err := tpRPC.Call("ApierV1.SetTPStat", tpStat, &result); err != nil { + t.Error(err) + } else if result != utils.OK { + t.Error("Unexpected reply returned", result) + } +} + +func TestTPStatGetTPStat(t *testing.T) { + var respond *utils.TPStats + if err := tpRPC.Call("ApierV1.GetTPStat", &AttrGetTPStat{TPid: tpStat.TPid, ID: tpStat.ID}, &respond); err != nil { + t.Error(err) + } else if !reflect.DeepEqual(tpStat, respond) { + t.Errorf("Expecting: %+v, received: %+v", tpStat.TPid, respond.TPid) + } +} + +func TestTPStatUpdateTPStat(t *testing.T) { + var result string + tpStat.Weight = 21 + tpStat.Filters = []*utils.TPRequestFilter{ + &utils.TPRequestFilter{ + Type: "*string", + FieldName: "Account", + Values: []string{"1001", "1002"}, + }, + &utils.TPRequestFilter{ + Type: "*string_prefix", + FieldName: "Destination", + Values: []string{"10", "20"}, + }, + &utils.TPRequestFilter{ + Type: "*rsr_fields", + FieldName: "", + Values: []string{"Subject(~^1.*1$)", "Destination(1002)"}, + }, + } + if err := tpRPC.Call("ApierV1.SetTPStat", tpStat, &result); err != nil { + t.Error(err) + } else if result != utils.OK { + t.Error("Unexpected reply returned", result) + } + var expectedTPS *utils.TPStats + if err := tpRPC.Call("ApierV1.GetTPStat", &AttrGetTPStat{TPid: tpStat.TPid, ID: tpStat.ID}, &expectedTPS); err != nil { + t.Error(err) + } else if !reflect.DeepEqual(tpStat, expectedTPS) { + t.Errorf("Expecting: %+v, received: %+v", tpStat, expectedTPS) + } +} + +func TestTPStatRemTPStat(t *testing.T) { + var resp string + if err := tpRPC.Call("ApierV1.RemTPStat", &AttrGetTPStat{TPid: tpStat.TPid, ID: tpStat.ID}, &resp); err != nil { + t.Error(err) + } else if resp != utils.OK { + t.Error("Unexpected reply returned", resp) + } +} + +func TestTPStatCheckDelete(t *testing.T) { + var respond *utils.TPStats + if err := tpRPC.Call("ApierV1.GetTPStat", &AttrGetTPStat{TPid: "TPS1", ID: "Stat1"}, &respond); err == nil || err.Error() != utils.ErrNotFound.Error() { + t.Error(err) + } +} + +func TestTPStatKillEngine(t *testing.T) { + if err := engine.KillEngine(100); err != nil { + t.Error(err) + } +} diff --git a/engine/model_helpers.go b/engine/model_helpers.go index 61ea3bea6..7137c9e96 100755 --- a/engine/model_helpers.go +++ b/engine/model_helpers.go @@ -2024,8 +2024,11 @@ func APItoModelStats(st *utils.TPStats) (mdls TpStatsS) { mdl.Stored = st.Stored mdl.Weight = st.Weight mdl.QueueLength = st.QueueLength - for _, val := range st.Metrics { - mdl.Metrics = mdl.Metrics + utils.INFIELD_SEP + val + for i, val := range st.Metrics { + if i != 0 { + mdl.Metrics += utils.INFIELD_SEP + } + mdl.Metrics += val } for _, val := range st.Thresholds { mdl.Thresholds = mdl.Thresholds + utils.INFIELD_SEP + val diff --git a/engine/model_helpers_test.go b/engine/model_helpers_test.go index 64d7589d6..e174dee9e 100755 --- a/engine/model_helpers_test.go +++ b/engine/model_helpers_test.go @@ -15,6 +15,7 @@ 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 engine import ( @@ -188,6 +189,34 @@ func TestApierTPTimingAsExportSlice(t *testing.T) { } } +func TestAPItoModelStats(t *testing.T) { + tpS := &utils.TPStats{ + TPid: "TPS1", + ID: "Stat1", + Filters: []*utils.TPRequestFilter{ + &utils.TPRequestFilter{ + Type: "*string", + FieldName: "Account", + Values: []string{"1002"}, + }, + }, + ActivationInterval: &utils.TPActivationInterval{ + ActivationTime: "2014-07-29T15:00:00Z", + ExpiryTime: "", + }, + TTL: "1", + Metrics: []string{"MetricValue"}, + Blocker: true, + Stored: true, + Weight: 20, + Thresholds: nil, + } + expectedtpS := APItoModelStats(tpS) + if !reflect.DeepEqual(expectedtpS, tpS) { + t.Errorf("Expecting: %+v, received: %+v", expectedtpS, tpS) + } +} + func TestTPRatingPlanAsExportSlice(t *testing.T) { tpRpln := &utils.TPRatingPlan{ TPid: "TEST_TPID", diff --git a/engine/storage_mongo_datadb.go b/engine/storage_mongo_datadb.go index 45846614b..50574523a 100755 --- a/engine/storage_mongo_datadb.go +++ b/engine/storage_mongo_datadb.go @@ -180,7 +180,7 @@ func (ms *MongoStorage) EnsureIndexes() (err error) { Sparse: false, } for _, col := range []string{utils.TBLTPTimings, utils.TBLTPDestinations, utils.TBLTPDestinationRates, utils.TBLTPRatingPlans, - utils.TBLTPSharedGroups, utils.TBLTPCdrStats, utils.TBLTPActions, utils.TBLTPActionPlans, utils.TBLTPActionTriggers} { + utils.TBLTPSharedGroups, utils.TBLTPCdrStats, utils.TBLTPActions, utils.TBLTPActionPlans, utils.TBLTPActionTriggers, utils.TBLTPStats} { if err = db.C(col).EnsureIndex(idx); err != nil { return } diff --git a/engine/storage_mongo_stordb.go b/engine/storage_mongo_stordb.go index bfc9658d0..8bdf92996 100755 --- a/engine/storage_mongo_stordb.go +++ b/engine/storage_mongo_stordb.go @@ -379,6 +379,23 @@ func (ms *MongoStorage) GetTPResourceLimits(tpid, id string) ([]*utils.TPResourc return results, err } +func (ms *MongoStorage) GetTPStats(tpid, id string) ([]*utils.TPStats, error) { + filter := bson.M{ + "tpid": tpid, + } + if id != "" { + filter["id"] = id + } + var results []*utils.TPStats + session, col := ms.conn(utils.TBLTPStats) + defer session.Close() + err := col.Find(filter).All(&results) + if len(results) == 0 { + return results, utils.ErrNotFound + } + return results, err +} + func (ms *MongoStorage) GetTPDerivedChargers(tp *utils.TPDerivedChargers) ([]*utils.TPDerivedChargers, error) { filter := bson.M{"tpid": tp.TPid} if tp.Direction != "" { @@ -841,6 +858,20 @@ func (ms *MongoStorage) SetTPResourceLimits(tpRLs []*utils.TPResourceLimit) (err return } +func (ms *MongoStorage) SetTPRStats(tpS []*utils.TPStats) (err error) { + if len(tpS) == 0 { + return + } + session, col := ms.conn(utils.TBLTPStats) + defer session.Close() + tx := col.Bulk() + for _, tp := range tpS { + tx.Upsert(bson.M{"tpid": tp.TPid, "id": tp.ID}, tp) + } + _, err = tx.Run() + return +} + func (ms *MongoStorage) SetSMCost(smc *SMCost) error { if smc.CostDetails == nil { return nil @@ -1105,7 +1136,7 @@ func (ms *MongoStorage) GetCDRs(qryFltr *utils.CDRsFilter, remove bool) ([]*CDR, return cdrs, 0, nil } -func (ms *MongoStorage) GetTPStats(tpid, id string) ([]*utils.TPStats, error) { +func (ms *MongoStorage) GetTPStat(tpid, id string) ([]*utils.TPStats, error) { filter := bson.M{ "tpid": tpid, } diff --git a/engine/storage_sql.go b/engine/storage_sql.go index 47a31a029..ec8f8d9f0 100755 --- a/engine/storage_sql.go +++ b/engine/storage_sql.go @@ -190,9 +190,7 @@ func (self *SQLStorage) RemTpData(table, tpid string, args map[string]string) er if len(table) == 0 { // Remove tpid out of all tables for _, tblName := range []string{utils.TBLTPTimings, utils.TBLTPDestinations, utils.TBLTPRates, utils.TBLTPDestinationRates, utils.TBLTPRatingPlans, utils.TBLTPRateProfiles, utils.TBLTPSharedGroups, utils.TBLTPCdrStats, utils.TBLTPLcrs, utils.TBLTPActions, utils.TBLTPActionPlans, utils.TBLTPActionTriggers, utils.TBLTPAccountActions, - utils.TBLTPDerivedChargers, utils.TBLTPAliases, utils.TBLTPUsers, utils.TBLTPResourceLimits, - // utils.TBLTPStats - } { + utils.TBLTPDerivedChargers, utils.TBLTPAliases, utils.TBLTPUsers, utils.TBLTPResourceLimits, utils.TBLTPStats} { if err := tx.Table(tblName).Where("tpid = ?", tpid).Delete(nil).Error; err != nil { tx.Rollback() return err @@ -201,6 +199,7 @@ func (self *SQLStorage) RemTpData(table, tpid string, args map[string]string) er tx.Commit() return nil } + utils.Logger.Debug(fmt.Sprintf("#Rem sterge %s", tpid)) // Remove from a single table tx = tx.Table(table).Where("tpid = ?", tpid) // Compose filters @@ -219,8 +218,10 @@ func (self *SQLStorage) SetTPTimings(timings []*utils.ApierTPTiming) error { if len(timings) == 0 { return nil } + tx := self.db.Begin() for _, timing := range timings { + utils.Logger.Debug(fmt.Sprintf("#1(set) Id care trimite %s", timing.ID)) if err := tx.Where(&TpTiming{Tpid: timing.TPid, Tag: timing.ID}).Delete(TpTiming{}).Error; err != nil { tx.Rollback() return err @@ -1170,6 +1171,8 @@ func (self *SQLStorage) GetTPDestinationRates(tpid, id string, pagination *utils func (self *SQLStorage) GetTPTimings(tpid, id string) ([]*utils.ApierTPTiming, error) { var tpTimings TpTimings q := self.db.Where("tpid = ?", tpid) + utils.Logger.Debug(fmt.Sprintf("#1 Id care trimite %s", id)) + utils.Logger.Debug(fmt.Sprintf("#1 TPId care trimite %s", tpid)) if len(id) != 0 { q = q.Where("tag = ?", id) } @@ -1177,6 +1180,7 @@ func (self *SQLStorage) GetTPTimings(tpid, id string) ([]*utils.ApierTPTiming, e return nil, err } ts := tpTimings.AsTPTimings() + utils.Logger.Debug(fmt.Sprintf("#2 ce gaseste : %s", ts)) if len(ts) == 0 { return ts, utils.ErrNotFound }