diff --git a/apier/tprateprofiles.go b/apier/tprateprofiles.go new file mode 100644 index 000000000..36c1bc5aa --- /dev/null +++ b/apier/tprateprofiles.go @@ -0,0 +1,79 @@ +/* +Rating system designed to be used in VoIP Carriers World +Copyright (C) 2013 ITsysCOM + +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 apier + +// This file deals with tp_rate_profiles management over APIs + +import ( + "errors" + "fmt" + "github.com/cgrates/cgrates/utils" +) + +// Creates a new RateProfile within a tariff plan +func (self *Apier) SetTPRateProfile(attrs utils.TPRateProfile, reply *string) error { + if missing := utils.MissingStructFields(&attrs, []string{"TPid", "RateProfileId", "Tenant", "TOR", "Direction", "Subject", "RatingActivations"}); len(missing) != 0 { + return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing) + } + if exists, err := self.StorDb.ExistsTPRateProfile(attrs.TPid, attrs.RateProfileId); err != nil { + return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error()) + } else if exists { + return errors.New(utils.ERR_DUPLICATE) + } + if err := self.StorDb.SetTPRateProfile(&attrs); err != nil { + return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error()) + } + *reply = "OK" + return nil +} + +type AttrGetTPRateProfile struct { + TPid string // Tariff plan id + RateProfileId string // RateProfile id +} + +// Queries specific RateProfile on tariff plan +func (self *Apier) GetTPRateProfile(attrs AttrGetTPRateProfile, reply *utils.TPRateProfile) error { + if missing := utils.MissingStructFields(&attrs, []string{"TPid", "RateProfileId"}); len(missing) != 0 { //Params missing + return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing) + } + if dr, err := self.StorDb.GetTPRateProfile(attrs.TPid, attrs.RateProfileId); err != nil { + return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error()) + } else if dr == nil { + return errors.New(utils.ERR_NOT_FOUND) + } else { + *reply = *dr + } + return nil +} + +// Queries RateProfile identities on specific tariff plan. +func (self *Apier) GetTPRateProfileIds(attrs utils.AttrTPRateProfileIds, reply *[]string) error { + if missing := utils.MissingStructFields(&attrs, []string{"TPid"}); len(missing) != 0 { //Params missing + return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing) + } + if ids, err := self.StorDb.GetTPRateProfileIds(&attrs); err != nil { + return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error()) + } else if ids == nil { + return errors.New(utils.ERR_NOT_FOUND) + } else { + *reply = ids + } + return nil +} diff --git a/data/storage/mysql/create_tariffplan_tables.sql b/data/storage/mysql/create_tariffplan_tables.sql index b070a1cf9..cd947e818 100644 --- a/data/storage/mysql/create_tariffplan_tables.sql +++ b/data/storage/mysql/create_tariffplan_tables.sql @@ -12,6 +12,7 @@ CREATE TABLE `tp_timings` ( `time` varchar(16) NOT NULL, PRIMARY KEY (`id`), KEY `tpid` (`tpid`), + KEY `tpid_tag` (`tpid`,`tag`), UNIQUE KEY `tpid_tmid` (`tpid`,`tag`) ); @@ -26,6 +27,7 @@ CREATE TABLE `tp_destinations` ( `prefix` varchar(24) NOT NULL, PRIMARY KEY (`id`), KEY `tpid` (`tpid`), + KEY `tpid_tag` (`tpid`,`tag`), UNIQUE KEY `tpid_dest_prefix` (`tpid`,`tag`,`prefix`) ); @@ -44,6 +46,7 @@ CREATE TABLE `tp_rates` ( `weight` DECIMAL(5,2) NOT NULL, PRIMARY KEY (`id`), KEY `tpid` (`tpid`), + KEY `tpid_tag` (`tpid`,`tag`), UNIQUE KEY `tpid_tag_rate_weight` (`tpid`,`tag`,`weight`) ); @@ -59,6 +62,7 @@ CREATE TABLE `tp_destination_rates` ( `rates_tag` varchar(24) NOT NULL, PRIMARY KEY (`id`), KEY `tpid` (`tpid`), + KEY `tpid_tag` (`tpid`,`tag`), UNIQUE KEY `tpid_tag_dst_rates` (`tpid`,`tag`,`destinations_tag`) ); @@ -75,6 +79,7 @@ CREATE TABLE `tp_destrate_timings` ( `weight` DECIMAL(5,2) NOT NULL, PRIMARY KEY (`id`), KEY `tpid` (`tpid`), + KEY `tpid_tag` (`tpid`,`tag`), UNIQUE KEY `tpid_tag_destrates_timings_weight` (`tpid`,`tag`,`destrates_tag`,`timing_tag`,`weight`) ); @@ -85,15 +90,17 @@ CREATE TABLE `tp_destrate_timings` ( CREATE TABLE `tp_rate_profiles` ( `id` int(11) NOT NULL AUTO_INCREMENT, `tpid` char(40) NOT NULL, + `tag` varchar(24) NOT NULL, `tenant` varchar(64) NOT NULL, `tor` varchar(16) NOT NULL, `direction` varchar(8) NOT NULL, `subject` varchar(64) NOT NULL, + `activation_time` int(11) NOT NULL, + `destrates_timing_tag` varchar(24) NOT NULL, `rates_fallback_subject` varchar(64), - `rates_timing_tag` varchar(24) NOT NULL, - `activation_time` char(20) NOT NULL, PRIMARY KEY (`id`), - KEY `tpid` (`tpid`) + KEY `tpid_tag` (`tpid`, `tag`), + UNIQUE KEY `tpid_tag_tenant_tor_dir_subj_atime` (`tpid`,`tag`, `tenant`,`tor`,`direction`,`subject`,`activation_time`) ); -- diff --git a/rater/storage_interface.go b/rater/storage_interface.go index 7a4e325e1..97116205f 100644 --- a/rater/storage_interface.go +++ b/rater/storage_interface.go @@ -79,6 +79,10 @@ type DataStorage interface { SetTPDestRateTiming(*utils.TPDestRateTiming) error GetTPDestRateTiming(string, string) (*utils.TPDestRateTiming, error) GetTPDestRateTimingIds(string) ([]string, error) + ExistsTPRateProfile(string, string) (bool, error) + SetTPRateProfile(*utils.TPRateProfile) error + GetTPRateProfile(string, string) (*utils.TPRateProfile, error) + GetTPRateProfileIds(*utils.AttrTPRateProfileIds) ([]string, error) // End Apier functions GetActions(string) (Actions, error) SetActions(string, Actions) error diff --git a/rater/storage_map.go b/rater/storage_map.go index ae1b77ad1..ef52b65fc 100644 --- a/rater/storage_map.go +++ b/rater/storage_map.go @@ -157,7 +157,21 @@ func (ms *MapStorage) GetTPDestRateTimingIds(tpid string) ([]string, error) { return nil, errors.New(utils.ERR_NOT_IMPLEMENTED) } +func (ms *MapStorage) ExistsTPRateProfile(tpid, rpId string) (bool, error) { + return false, errors.New(utils.ERR_NOT_IMPLEMENTED) +} +func (ms *MapStorage) SetTPRateProfile(rp *utils.TPRateProfile) error { + return errors.New(utils.ERR_NOT_IMPLEMENTED) +} + +func (ms *MapStorage) GetTPRateProfile(tpid, rpId string) (*utils.TPRateProfile, error) { + return nil, errors.New(utils.ERR_NOT_IMPLEMENTED) +} + +func (ms *MapStorage) GetTPRateProfileIds(filters *utils.AttrTPRateProfileIds) ([]string, error) { + return nil, errors.New(utils.ERR_NOT_IMPLEMENTED) +} func (ms *MapStorage) GetActions(key string) (as Actions, err error) { if values, ok := ms.dict[ACTION_PREFIX+key]; ok { diff --git a/rater/storage_mongo.go b/rater/storage_mongo.go index 499ecd21d..d008a4464 100644 --- a/rater/storage_mongo.go +++ b/rater/storage_mongo.go @@ -160,7 +160,7 @@ func (ms *MongoStorage) ExistsTPTiming(tpid, tmId string) (bool, error) { } func (ms *MongoStorage) GetTPTiming(tpid, tmId string) (*Timing, error) { - return nil, nil + return nil, errors.New(utils.ERR_NOT_IMPLEMENTED) } func (ms *MongoStorage) GetTPTimingIds(tpid string) ([]string, error) { @@ -193,7 +193,7 @@ func (ms *MongoStorage) SetTPRate(rt *utils.TPRate) error { } func (ms *MongoStorage) GetTPRate(tpid, rtId string) (*utils.TPRate, error) { - return nil, nil + return nil, errors.New(utils.ERR_NOT_IMPLEMENTED) } func (ms *MongoStorage) GetTPRateIds(tpid string) ([]string, error) { @@ -209,7 +209,7 @@ func (ms *MongoStorage) SetTPDestinationRate(dr *utils.TPDestinationRate) error } func (ms *MongoStorage) GetTPDestinationRate(tpid, drId string) (*utils.TPDestinationRate, error) { - return nil, nil + return nil, errors.New(utils.ERR_NOT_IMPLEMENTED) } func (ms *MongoStorage) GetTPDestinationRateIds(tpid string) ([]string, error) { @@ -225,13 +225,29 @@ func (ms *MongoStorage) SetTPDestRateTiming(drt *utils.TPDestRateTiming) error { } func (ms *MongoStorage) GetTPDestRateTiming(tpid, drtId string) (*utils.TPDestRateTiming, error) { - return nil, nil + return nil, errors.New(utils.ERR_NOT_IMPLEMENTED) } func (ms *MongoStorage) GetTPDestRateTimingIds(tpid string) ([]string, error) { return nil, errors.New(utils.ERR_NOT_IMPLEMENTED) } +func (ms *MongoStorage) ExistsTPRateProfile(tpid, rpId string) (bool, error) { + return false, errors.New(utils.ERR_NOT_IMPLEMENTED) +} + +func (ms *MongoStorage) SetTPRateProfile(rp *utils.TPRateProfile) error { + return errors.New(utils.ERR_NOT_IMPLEMENTED) +} + +func (ms *MongoStorage) GetTPRateProfile(tpid, rpId string) (*utils.TPRateProfile, error) { + return nil, errors.New(utils.ERR_NOT_IMPLEMENTED) +} + +func (ms *MongoStorage) GetTPRateProfileIds(filters *utils.AttrTPRateProfileIds) ([]string, error) { + return nil, errors.New(utils.ERR_NOT_IMPLEMENTED) +} + func (ms *MongoStorage) GetActions(key string) (as Actions, err error) { result := AcKeyValue{} err = ms.db.C("actions").Find(bson.M{"key": key}).One(&result) diff --git a/rater/storage_redis.go b/rater/storage_redis.go index 46260bdc7..736858248 100644 --- a/rater/storage_redis.go +++ b/rater/storage_redis.go @@ -115,7 +115,7 @@ func (rs *RedisStorage) ExistsTPTiming(tpid, tmId string) (bool, error) { } func (rs *RedisStorage) GetTPTiming(tpid, tmId string) (*Timing, error) { - return nil, nil + return nil, errors.New(utils.ERR_NOT_IMPLEMENTED) } func (rs *RedisStorage) GetTPTimingIds(tpid string) ([]string, error) { @@ -148,7 +148,7 @@ func (rs *RedisStorage) SetTPRate(rt *utils.TPRate) error { } func (rs *RedisStorage) GetTPRate(tpid, rtId string) (*utils.TPRate, error) { - return nil, nil + return nil, errors.New(utils.ERR_NOT_IMPLEMENTED) } func (rs *RedisStorage) GetTPRateIds(tpid string) ([]string, error) { @@ -180,13 +180,29 @@ func (rs *RedisStorage) SetTPDestRateTiming(drt *utils.TPDestRateTiming) error { } func (rs *RedisStorage) GetTPDestRateTiming(tpid, drtId string) (*utils.TPDestRateTiming, error) { - return nil, nil + return nil, errors.New(utils.ERR_NOT_IMPLEMENTED) } func (rs *RedisStorage) GetTPDestRateTimingIds(tpid string) ([]string, error) { return nil, errors.New(utils.ERR_NOT_IMPLEMENTED) } +func (rs *RedisStorage) ExistsTPRateProfile(tpid, rpId string) (bool, error) { + return false, errors.New(utils.ERR_NOT_IMPLEMENTED) +} + +func (rs *RedisStorage) SetTPRateProfile(rp *utils.TPRateProfile) error { + return errors.New(utils.ERR_NOT_IMPLEMENTED) +} + +func (rs *RedisStorage) GetTPRateProfile(tpid, rpId string) (*utils.TPRateProfile, error) { + return nil, errors.New(utils.ERR_NOT_IMPLEMENTED) +} + +func (rs *RedisStorage) GetTPRateProfileIds(filters *utils.AttrTPRateProfileIds) ([]string, error) { + return nil, errors.New(utils.ERR_NOT_IMPLEMENTED) +} + func (rs *RedisStorage) GetActions(key string) (as Actions, err error) { var values string if values, err = rs.db.Get(ACTION_PREFIX + key); err == nil { diff --git a/rater/storage_sql.go b/rater/storage_sql.go index 7dc8dcf0a..633669f9d 100644 --- a/rater/storage_sql.go +++ b/rater/storage_sql.go @@ -413,6 +413,103 @@ func (self *SQLStorage) GetTPDestRateTimingIds(tpid string) ([]string, error) { return ids, nil } +func (self *SQLStorage) ExistsTPRateProfile(tpid, rpId string) (bool, error) { + var exists bool + err := self.Db.QueryRow(fmt.Sprintf("SELECT EXISTS (SELECT 1 FROM %s WHERE tpid='%s' AND tag='%s')", utils.TBL_TP_RATE_PROFILES, tpid, rpId)).Scan(&exists) + if err != nil { + return false, err + } + return exists, nil +} + +func (self *SQLStorage) SetTPRateProfile(rp *utils.TPRateProfile) error { + var qry string + if len(rp.RatingActivations) == 0 { // Possibility to only set fallback rate subject + qry = fmt.Sprintf("INSERT INTO %s (tpid,tag,tenant,tor,direction,subject,activation_time,destrates_timing_tag,rates_fallback_subject) VALUES ('%s', '%s', '%s', '%s', '%s', '%s', 0,'','%s')", + utils.TBL_TP_RATE_PROFILES, rp.TPid, rp.RateProfileId, rp.Tenant, rp.TOR, rp.Direction, rp.Subject, rp.RatesFallbackSubject) + } else { + qry = fmt.Sprintf("INSERT INTO %s (tpid,tag,tenant,tor,direction,subject,activation_time,destrates_timing_tag,rates_fallback_subject) VALUES ", utils.TBL_TP_RATE_PROFILES) + // Using multiple values in query to spare some network processing time + for idx, rpa := range rp.RatingActivations { + if idx!=0 { //Consecutive values after the first will be prefixed with "," as separator + qry += "," + } + qry += fmt.Sprintf("('%s', '%s', '%s', '%s', '%s', '%s', %d,'%s','%s')", rp.TPid, rp.RateProfileId, rp.Tenant, rp.TOR, rp.Direction, rp.Subject, rpa.ActivationTime, rpa.DestRateTimingId, rp.RatesFallbackSubject) + } + } + if _, err := self.Db.Exec(qry); err != nil { + return err + } + return nil +} + +func (self *SQLStorage) GetTPRateProfile(tpid, rpId string) (*utils.TPRateProfile, error) { + rows, err := self.Db.Query(fmt.Sprintf("SELECT tenant,tor,direction,subject,activation_time,destrates_timing_tag,rates_fallback_subject FROM %s WHERE tpid='%s' AND tag='%s'", utils.TBL_TP_RATE_PROFILES, tpid, rpId)) + if err != nil { + return nil, err + } + defer rows.Close() + rp := &utils.TPRateProfile{TPid: tpid, RateProfileId: rpId} + i := 0 + for rows.Next() { + i++ //Keep here a reference so we know we got at least one result + var tenant, tor, direction, subject, drtId, fallbackSubj string + var aTime int64 + err = rows.Scan(&tenant, &tor, &direction, &subject, &aTime, &drtId, &fallbackSubj) + if err != nil { + return nil, err + } + if i == 1 { // Save some info on first iteration + rp.Tenant = tenant + rp.TOR = tor + rp.Direction = direction + rp.Subject = subject + rp.RatesFallbackSubject = fallbackSubj + } + rp.RatingActivations = append(rp.RatingActivations, utils.RatingActivation{aTime, drtId}) + } + if i == 0 { + return nil, nil + } + return rp, nil +} + +func (self *SQLStorage) GetTPRateProfileIds(filters *utils.AttrTPRateProfileIds) ([]string, error) { + qry := fmt.Sprintf("SELECT DISTINCT tag FROM %s where tpid='%s'", utils.TBL_TP_RATE_PROFILES, filters.TPid) + if filters.Tenant != "" { + qry += fmt.Sprintf(" AND tenant='%s'", filters.Tenant) + } + if filters.TOR != "" { + qry += fmt.Sprintf(" AND tor='%s'", filters.TOR) + } + if filters.Direction != "" { + qry += fmt.Sprintf(" AND direction='%s'", filters.Direction) + } + if filters.Subject != "" { + qry += fmt.Sprintf(" AND subject='%s'", filters.Subject) + } + rows, err := self.Db.Query(qry) + if err != nil { + return nil, err + } + defer rows.Close() + ids := []string{} + i := 0 + for rows.Next() { + i++ //Keep here a reference so we know we got at least one + var id string + err = rows.Scan(&id) + if err != nil { + return nil, err + } + ids = append(ids, id) + } + if i == 0 { + return nil, nil + } + return ids, nil +} + func (self *SQLStorage) GetActions(string) (as Actions, err error) { return } diff --git a/utils/tpdata.go b/utils/tpdata.go index dc6048d66..514d894ba 100644 --- a/utils/tpdata.go +++ b/utils/tpdata.go @@ -56,3 +56,27 @@ type DestRateTiming struct { TimingId string // The timing identity Weight float64 // Binding priority taken into consideration when more DestinationRates are active on a time slot } + +type TPRateProfile struct { + TPid string // Tariff plan id + RateProfileId string // RateProfile id + Tenant string // Tenant's Id + TOR string // TypeOfRecord + Direction string // Traffic direction, OUT is the only one supported for now + Subject string // Rating subject, usually the same as account + RatesFallbackSubject string // Fallback on this subject if rates not found for destination + RatingActivations []RatingActivation // Activate rate profiles at specific time +} + +type RatingActivation struct { + ActivationTime int64 // Time when this profile will become active, defined as unix epoch time + DestRateTimingId string // Id of DestRateTiming profile +} + +type AttrTPRateProfileIds struct { + TPid string // Tariff plan id + Tenant string // Tenant's Id + TOR string // TypeOfRecord + Direction string // Traffic direction + Subject string // Rating subject, usually the same as account +}