26 Commits

Author SHA1 Message Date
Radu Ioan Fericean
5f1c23e4ee created a misc tools folder 2013-05-19 20:31:10 +03:00
DanB
dc6c63df36 Adding cdrs tables 2013-05-17 13:49:17 +02:00
Radu Ioan Fericean
28bca0efcc fixed mongo storage 2013-05-17 14:46:33 +03:00
Radu Ioan Fericean
6e2b741028 renamed packet 2013-05-17 12:02:38 +03:00
Radu Ioan Fericean
f31c5b6476 mysql storage 2013-05-17 11:59:20 +03:00
Radu Ioan Fericean
fe4d82b30a added cdr server files 2013-05-17 11:55:07 +03:00
Radu Ioan Fericean
96bba4eb6f started work on online cdr processing 2013-05-16 19:01:06 +03:00
Radu Ioan Fericean
7e7ab83bd8 better test for goci.me 2013-05-16 17:11:27 +03:00
Radu Ioan Fericean
6776525c97 Install packages that are dependencies of the test first 2013-05-14 14:25:22 +03:00
Radu Ioan Fericean
cd32402449 removed specific databases from tests name 2013-05-12 14:45:26 +03:00
Radu Ioan Fericean
48b24d6cff type an error on database not accessible from tests 2013-05-12 14:33:22 +03:00
Radu Ioan Fericean
839b3b8615 added one more driver for redis 2013-05-12 13:58:27 +03:00
Radu Ioan Fericean
72d2d8182c map storage for tests 2013-05-12 12:36:31 +03:00
Radu Ioan Fericean
a7fb55792f more redis corrections 2013-05-12 12:32:37 +03:00
Radu Ioan Fericean
e8edee00c1 Merge branch 'master' of github.com:cgrates/cgrates 2013-05-10 18:28:45 +03:00
Radu Ioan Fericean
5829ab6386 preventing test crashes on missing database systems 2013-05-10 18:16:16 +03:00
DanB
8473840446 Adding reconnect to both Mediator and SessionManager 2013-05-10 13:07:20 +02:00
DanB
25cbad0a90 <cmd/cgr-rater> Introducing synchronicity for config phase to avoid concurrency issues 2013-05-10 12:16:41 +02:00
Radu Ioan Fericean
e5ad2bd09b avoid var hiding 2013-05-09 16:02:02 +03:00
Radu Ioan Fericean
373d97e91d simplifyed redis returned error 2013-05-09 15:27:22 +03:00
Radu Ioan Fericean
4fb47a0a7e only send auth and db if relevant 2013-05-09 15:25:11 +03:00
Radu Ioan Fericean
988ab87192 better db and auth 2013-05-09 13:12:35 +03:00
Radu Ioan Fericean
2c961510ce added update external libs shell script 2013-05-09 12:11:29 +03:00
Radu Ioan Fericean
146a4d6994 fixed radix interface change 2013-05-09 11:18:43 +03:00
DanB
5183307bd9 Small misspell in documentation fixed 2013-04-22 21:54:25 +02:00
DanB
c43d232857 Adding documentation about how to install from debian packages 2013-04-22 21:52:03 +02:00
35 changed files with 1210 additions and 169 deletions

View File

@@ -12,6 +12,8 @@
+ Commercial support available
## Documentation ##
Install & run screencast: http://youtu.be/qTQZZpb-m7Q
Browsable HTML http://readthedocs.org/docs/cgrates/
PDF, Epub, Manpage http://readthedocs.org/projects/cgrates/downloads/

146
cdrs/cdrs.go Normal file
View File

@@ -0,0 +1,146 @@
/*
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 <http://www.gnu.org/licenses/>
*/
package cdrs
import (
"encoding/json"
"fmt"
"github.com/cgrates/cgrates/config"
"github.com/cgrates/cgrates/rater"
"io/ioutil"
"net/http"
"strconv"
"time"
)
const (
// Freswitch event proprities names
DIRECTION = "Call-Direction"
ORIG_ID = "variable_sip_call_id" //- originator_id - match cdrs
SUBJECT = "variable_cgr_subject"
ACCOUNT = "variable_cgr_account"
DESTINATION = "variable_cgr_destination"
REQTYPE = "variable_cgr_reqtype" //prepaid or postpaid
TOR = "variable_cgr_tor"
UUID = "Unique-ID" // -Unique ID for this call leg
CSTMID = "variable_cgr_cstmid"
CALL_DEST_NR = "Caller-Destination-Number"
PARK_TIME = "Caller-Profile-Created-Time"
START_TIME = "Caller-Channel-Answered-Time"
END_TIME = "Caller-Channel-Hangup-Time"
NAME = "Event-Name"
HEARTBEAT = "HEARTBEAT"
ANSWER = "CHANNEL_ANSWER"
HANGUP = "CHANNEL_HANGUP_COMPLETE"
PARK = "CHANNEL_PARK"
REQTYPE_PREPAID = "prepaid"
REQTYPE_POSTPAID = "postpaid"
AUTH_OK = "+AUTH_OK"
DISCONNECT = "+SWITCH DISCONNECT"
INSUFFICIENT_FUNDS = "-INSUFFICIENT_FUNDS"
MISSING_PARAMETER = "-MISSING_PARAMETER"
SYSTEM_ERROR = "-SYSTEM_ERROR"
MANAGER_REQUEST = "+MANAGER_REQUEST"
USERNAME = "Caller-Username"
)
var cfg *config.CGRConfig
// Returns first non empty string out of vals. Useful to extract defaults
func firstNonEmpty(vals ...string) string {
for _, val := range vals {
if len(val) != 0 {
return val
}
}
return ""
}
func GetName(vars map[string]string) string {
return vars[NAME]
}
func GetDirection(vars map[string]string) string {
//TODO: implement direction
return "OUT"
//return vars[DIRECTION]
}
func GetOrigId(vars map[string]string) string {
return vars[ORIG_ID]
}
func GetSubject(vars map[string]string) string {
return firstNonEmpty(vars[SUBJECT], vars[USERNAME])
}
func GetAccount(vars map[string]string) string {
return firstNonEmpty(vars[ACCOUNT], vars[USERNAME])
}
// Charging destination number
func GetDestination(vars map[string]string) string {
return firstNonEmpty(vars[DESTINATION], vars[CALL_DEST_NR])
}
// Original dialed destination number, useful in case of unpark
func GetCallDestNr(vars map[string]string) string {
return vars[CALL_DEST_NR]
}
func GetTOR(vars map[string]string) string {
return firstNonEmpty(vars[TOR], cfg.SMDefaultTOR)
}
func GetUUID(vars map[string]string) string {
return vars[UUID]
}
func GetTenant(vars map[string]string) string {
return firstNonEmpty(vars[CSTMID], cfg.SMDefaultTenant)
}
func GetReqType(vars map[string]string) string {
return firstNonEmpty(vars[REQTYPE], cfg.SMDefaultReqType)
}
func GetFallbackSubj(vars map[string]string) string {
return cfg.SMDefaultSubject
}
func GetStartTime(vars map[string]string, field string) (t time.Time, err error) {
st, err := strconv.ParseInt(vars[field], 0, 64)
t = time.Unix(0, st*1000)
return
}
func GetEndTime() (vars map[string]string, t time.Time, err error) {
st, err := strconv.ParseInt(vars[END_TIME], 0, 64)
t = time.Unix(0, st*1000)
return
}
type CDR struct {
Variables map[string]string
}
func cdrHandler(w http.ResponseWriter, r *http.Request) {
body, _ := ioutil.ReadAll(r.Body)
cdr := CDR{}
if err := json.Unmarshal(body, &cdr); err == nil {
} else {
rater.Logger.Err(fmt.Sprintf("CDRCAPTOR: Could not unmarshal cdr: %v", err))
}
}
func startCaptiuringCDRs() {
http.HandleFunc("/cdr", cdrHandler)
http.ListenAndServe(":8080", nil)
}

View File

@@ -148,14 +148,14 @@ func main() {
var getter rater.DataStorage
switch *db_type {
case REDIS:
db_nb, err := strconv.Atoi(*db_name)
db_nb, err := strconv.Atoi(*db_name)
if err != nil {
log.Fatal("Redis db name must be an integer!")
}
if *db_port != "" {
*db_host += ":" + *db_port
}
getter, err = rater.NewRedisStorage(*db_host, db_nb, *db_pass)
getter, err = rater.NewGosexyStorage(*db_host, db_nb, *db_pass)
case MONGO:
getter, err = rater.NewMongoStorage(*db_host, *db_port, *db_name, *db_user, *db_pass)
case POSTGRES:

View File

@@ -98,9 +98,21 @@ func startMediator(responder *rater.Responder, loggerDb rater.DataStorage) {
var client *rpc.Client
var err error
if cfg.MediatorRPCEncoding == JSON {
client, err = jsonrpc.Dial("tcp", cfg.MediatorRater)
for i := 0; i < cfg.MediatorRaterReconnects; i++ {
client, err = jsonrpc.Dial("tcp", cfg.MediatorRater)
if err == nil { //Connected so no need to reiterate
break
}
time.Sleep(time.Duration(i/2) * time.Second)
}
} else {
client, err = rpc.Dial("tcp", cfg.MediatorRater)
for i := 0; i < cfg.MediatorRaterReconnects; i++ {
client, err = rpc.Dial("tcp", cfg.MediatorRater)
if err == nil { //Connected so no need to reiterate
break
}
time.Sleep(time.Duration(i/2) * time.Second)
}
}
if err != nil {
rater.Logger.Crit(fmt.Sprintf("Could not connect to rater: %v", err))
@@ -116,7 +128,7 @@ func startMediator(responder *rater.Responder, loggerDb rater.DataStorage) {
rater.Logger.Crit(fmt.Sprintf("The output path for mediator does not exist: %v", cfg.MediatorCDROutDir))
exitChan <- true
}
// ToDo: Why is here
// ToDo: Why is here
// Check parsing errors
//if cfgParseErr != nil {
// rater.Logger.Crit(fmt.Sprintf("Errors on config parsing: <%v>", cfgParseErr))
@@ -142,9 +154,23 @@ func startSessionManager(responder *rater.Responder, loggerDb rater.DataStorage)
var client *rpc.Client
var err error
if cfg.SMRPCEncoding == JSON {
client, err = jsonrpc.Dial("tcp", cfg.SMRater)
// We attempt to reconnect more times
for i := 0; i < cfg.SMRaterReconnects; i++ {
client, err = jsonrpc.Dial("tcp", cfg.SMRater)
if err == nil { //Connected so no need to reiterate
break
}
time.Sleep(time.Duration(i/2) * time.Second)
}
} else {
client, err = rpc.Dial("tcp", cfg.SMRater)
for i := 0; i < cfg.SMRaterReconnects; i++ {
client, err = rpc.Dial("tcp", cfg.SMRater)
if err == nil { //Connected so no need to reiterate
break
}
time.Sleep(time.Duration(i/2) * time.Second)
}
}
if err != nil {
rater.Logger.Crit(fmt.Sprintf("Could not connect to rater: %v", err))
@@ -167,14 +193,14 @@ func startSessionManager(responder *rater.Responder, loggerDb rater.DataStorage)
exitChan <- true
}
func checkConfigSanity() {
func checkConfigSanity() error {
if cfg.SMEnabled && cfg.RaterEnabled && cfg.RaterBalancer != DISABLED {
rater.Logger.Crit("The session manager must not be enabled on a worker rater (change [rater]/balancer to disabled)!")
exitChan <- true
return errors.New("SessionManager on Worker")
}
if cfg.BalancerEnabled && cfg.RaterEnabled && cfg.RaterBalancer != DISABLED {
rater.Logger.Crit("The balancer is enabled so it cannot connect to anatoher balancer (change [rater]/balancer to disabled)!")
exitChan <- true
return errors.New("Improperly configured balancer")
}
// check if the session manager or mediator is connectting via loopback
@@ -185,13 +211,13 @@ func checkConfigSanity() {
if cfg.BalancerEnabled {
if cfg.BalancerRPCEncoding != cfg.SMRPCEncoding {
rater.Logger.Crit("If you are connecting the session manager via the loopback to the balancer use the same type of rpc encoding!")
exitChan <- true
return errors.New("Balancer and SessionManager using different encoding")
}
}
if cfg.RaterEnabled {
if cfg.RaterRPCEncoding != cfg.SMRPCEncoding {
rater.Logger.Crit("If you are connecting the session manager via the loopback to the arter use the same type of rpc encoding!")
exitChan <- true
return errors.New("Rater and SessionManager using different encoding")
}
}
}
@@ -199,45 +225,45 @@ func checkConfigSanity() {
if cfg.BalancerEnabled {
if cfg.BalancerRPCEncoding != cfg.MediatorRPCEncoding {
rater.Logger.Crit("If you are connecting the mediator via the loopback to the balancer use the same type of rpc encoding!")
exitChan <- true
return errors.New("Balancer and Mediator using different encoding")
}
}
if cfg.RaterEnabled {
if cfg.RaterRPCEncoding != cfg.MediatorRPCEncoding {
rater.Logger.Crit("If you are connecting the mediator via the loopback to the arter use the same type of rpc encoding!")
exitChan <- true
return errors.New("Rater and Mediator using different encoding")
}
}
}
return nil
}
func configureDatabase(db_type, host, port, name, user, pass string) (getter rater.DataStorage, err error) {
switch db_type {
case REDIS:
db_nb, err := strconv.Atoi(name)
var db_nb int
db_nb, err = strconv.Atoi(name)
if err != nil {
rater.Logger.Crit("Redis db name must be an integer!")
exitChan <- true
return nil, err
}
if port != "" {
host += ":" + port
}
getter, err = rater.NewRedisStorage(host, db_nb, pass)
getter, err = rater.NewGosexyStorage(host, db_nb, pass)
case MONGO:
getter, err = rater.NewMongoStorage(host, port, name, user, pass)
case POSTGRES:
getter, err = rater.NewPostgresStorage(host, port, name, user, pass)
default:
err = errors.New("unknown db")
rater.Logger.Crit("Unknown db type, exiting!")
exitChan <- true
return nil, err
}
if err != nil {
rater.Logger.Crit(fmt.Sprintf("Could not connect to db: %v, exiting!", err))
exitChan <- true
return nil, err
}
return
return getter, nil
}
func main() {
@@ -254,7 +280,11 @@ func main() {
return
}
// some consitency checks
go checkConfigSanity()
errCfg := checkConfigSanity()
if errCfg != nil {
rater.Logger.Crit(errCfg.Error())
return
}
var getter, loggerDb rater.DataStorage
getter, err = configureDatabase(cfg.DataDBType, cfg.DataDBHost, cfg.DataDBPort, cfg.DataDBName, cfg.DataDBUser, cfg.DataDBPass)
@@ -268,10 +298,10 @@ func main() {
loggerDb = getter
} else {
loggerDb, err = configureDatabase(cfg.LogDBType, cfg.LogDBHost, cfg.LogDBPort, cfg.LogDBName, cfg.LogDBUser, cfg.LogDBPass)
}
if err != nil { // Cannot configure logger database, show stopper
rater.Logger.Crit(fmt.Sprintf("Could not configure logger database: %s exiting!", err))
return
if err != nil { // Cannot configure logger database, show stopper
rater.Logger.Crit(fmt.Sprintf("Could not configure logger database: %s exiting!", err))
return
}
}
defer loggerDb.Close()
rater.SetStorageLogger(loggerDb)
@@ -282,6 +312,7 @@ func main() {
}
}
// Async starts here
if cfg.RaterEnabled && cfg.RaterBalancer != DISABLED && !cfg.BalancerEnabled {
go registerToBalancer()
go stopRaterSingnalHandler()

View File

@@ -59,7 +59,7 @@ func main() {
t2 := time.Date(2012, time.February, 02, 18, 30, 0, 0, time.UTC)
cd := rater.CallDescriptor{Direction: "OUT", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2}
getter, err := rater.NewRedisStorage("localhost:6379", 10, "")
getter, err := rater.NewGosexyStorage("localhost:6379", 10, "")
//getter, err := rater.NewMongoStorage("localhost", "cgrates")
defer getter.Close()

View File

@@ -38,55 +38,57 @@ const (
// Holds system configuration, defaults are overwritten with values from config file if found
type CGRConfig struct {
DataDBType string
DataDBHost string // The host to connect to. Values that start with / are for UNIX domain sockets.
DataDBPort string // The port to bind to.
DataDBName string // The name of the database to connect to.
DataDBUser string // The user to sign in as.
DataDBPass string // The user's password.
LogDBType string // Should reflect the database type used to store logs
LogDBHost string // The host to connect to. Values that start with / are for UNIX domain sockets.
LogDBPort string // The port to bind to.
LogDBName string // The name of the database to connect to.
LogDBUser string // The user to sign in as.
LogDBPass string // The user's password.
RaterEnabled bool // start standalone server (no balancer)
RaterBalancer string // balancer address host:port
RaterListen string // listening address host:port
RaterRPCEncoding string // use JSON for RPC encoding
BalancerEnabled bool
BalancerListen string // Json RPC server address
BalancerRPCEncoding string // use JSON for RPC encoding
SchedulerEnabled bool
SMEnabled bool
SMSwitchType string
SMRater string // address where to access rater. Can be internal, direct rater address or the address of a balancer
SMDebitInterval int // the period to be debited in advanced during a call (in seconds)
SMRPCEncoding string // use JSON for RPC encoding
SMDefaultReqType string // Use this request type if not defined on top
SMDefaultTOR string // set default type of record
SMDefaultTenant string // set default tenant
SMDefaultSubject string // set default rating subject, useful in case of fallback
MediatorEnabled bool
MediatorCDRType string // sets the type of cdrs we are processing.
DataDBType string
DataDBHost string // The host to connect to. Values that start with / are for UNIX domain sockets.
DataDBPort string // The port to bind to.
DataDBName string // The name of the database to connect to.
DataDBUser string // The user to sign in as.
DataDBPass string // The user's password.
LogDBType string // Should reflect the database type used to store logs
LogDBHost string // The host to connect to. Values that start with / are for UNIX domain sockets.
LogDBPort string // The port to bind to.
LogDBName string // The name of the database to connect to.
LogDBUser string // The user to sign in as.
LogDBPass string // The user's password.
RaterEnabled bool // start standalone server (no balancer)
RaterBalancer string // balancer address host:port
RaterListen string // listening address host:port
RaterRPCEncoding string // use JSON for RPC encoding
BalancerEnabled bool
BalancerListen string // Json RPC server address
BalancerRPCEncoding string // use JSON for RPC encoding
SchedulerEnabled bool
SMEnabled bool
SMSwitchType string
SMRater string // address where to access rater. Can be internal, direct rater address or the address of a balancer
SMRaterReconnects int // Number of reconnect attempts to rater
SMDebitInterval int // the period to be debited in advanced during a call (in seconds)
SMRPCEncoding string // use JSON for RPC encoding
SMDefaultReqType string // Use this request type if not defined on top
SMDefaultTOR string // set default type of record
SMDefaultTenant string // set default tenant
SMDefaultSubject string // set default rating subject, useful in case of fallback
MediatorEnabled bool
MediatorCDRType string // sets the type of cdrs we are processing.
MediatorCDRInDir string // Freeswitch Master CSV CDR path.
MediatorCDROutDir string // Freeswitch Master CSV CDR output path.
MediatorRater string // address where to access rater. Can be internal, direct rater address or the address of a balancer
MediatorRPCEncoding string // use JSON for RPC encoding
MediatorSkipDB bool
MediatorPseudoprepaid bool
FreeswitchServer string // freeswitch address host:port
FreeswitchPass string // FS socket password
FreeswitchDirectionIdx string
FreeswitchTORIdx string
FreeswitchTenantIdx string
FreeswitchSubjectIdx string
FreeswitchAccountIdx string
FreeswitchDestIdx string
FreeswitchTimeStartIdx string
FreeswitchDurationIdx string
FreeswitchUUIDIdx string
FreeswitchReconnects int // number of times to attempt reconnect after connect fails
MediatorCDROutDir string // Freeswitch Master CSV CDR output path.
MediatorRater string // address where to access rater. Can be internal, direct rater address or the address of a balancer
MediatorRaterReconnects int // Number of reconnect attempts to rater
MediatorRPCEncoding string // use JSON for RPC encoding
MediatorSkipDB bool
MediatorPseudoprepaid bool
FreeswitchServer string // freeswitch address host:port
FreeswitchPass string // FS socket password
FreeswitchDirectionIdx string
FreeswitchTORIdx string
FreeswitchTenantIdx string
FreeswitchSubjectIdx string
FreeswitchAccountIdx string
FreeswitchDestIdx string
FreeswitchTimeStartIdx string
FreeswitchDurationIdx string
FreeswitchUUIDIdx string
FreeswitchReconnects int // number of times to attempt reconnect after connect fails
}
// Instantiate a new CGRConfig setting defaults or reading from file
@@ -95,6 +97,18 @@ func NewCGRConfig(cfgPath *string) (*CGRConfig, error) {
if err != nil {
return nil, errors.New(fmt.Sprintf("Could not open the configuration file: %s", err))
}
return loadConfig(c)
}
func NewCGRConfigBytes(data []byte) (*CGRConfig, error) {
c, err := conf.ReadConfigBytes(data)
if err != nil {
return nil, errors.New(fmt.Sprintf("Could not open the configuration file: %s", err))
}
return loadConfig(c)
}
func loadConfig(c *conf.ConfigFile) (*CGRConfig, error) {
cfg := &CGRConfig{}
var hasOpt bool
cfg.DataDBType = REDIS
@@ -121,7 +135,7 @@ func NewCGRConfig(cfgPath *string) (*CGRConfig, error) {
if hasOpt = c.HasOption("global", "datadb_passwd"); hasOpt {
cfg.DataDBPass, _ = c.GetString("global", "datadb_passwd")
}
cfg.LogDBType = MONGO
cfg.LogDBType = MONGO
if hasOpt = c.HasOption("global", "logdb_type"); hasOpt {
cfg.LogDBType, _ = c.GetString("global", "logdb_type")
}
@@ -193,6 +207,10 @@ func NewCGRConfig(cfgPath *string) (*CGRConfig, error) {
if hasOpt = c.HasOption("mediator", "rater"); hasOpt {
cfg.MediatorRater, _ = c.GetString("mediator", "rater")
}
cfg.MediatorRaterReconnects = 3
if hasOpt = c.HasOption("mediator", "rater_reconnects"); hasOpt {
cfg.MediatorRaterReconnects, _ = c.GetInt("mediator", "rater_reconnects")
}
cfg.MediatorRPCEncoding = GOB
if hasOpt = c.HasOption("mediator", "rpc_encoding"); hasOpt {
cfg.MediatorRPCEncoding, _ = c.GetString("mediator", "rpc_encoding")
@@ -221,6 +239,10 @@ func NewCGRConfig(cfgPath *string) (*CGRConfig, error) {
if hasOpt = c.HasOption("session_manager", "rater"); hasOpt {
cfg.SMRater, _ = c.GetString("session_manager", "rater")
}
cfg.SMRaterReconnects = 3
if hasOpt = c.HasOption("session_manager", "rater_reconnects"); hasOpt {
cfg.SMRaterReconnects, _ = c.GetInt("session_manager", "rater_reconnects")
}
cfg.SMDebitInterval = 10
if hasOpt = c.HasOption("session_manager", "debit_interval"); hasOpt {
cfg.SMDebitInterval, _ = c.GetInt("session_manager", "debit_interval")
@@ -295,5 +317,4 @@ func NewCGRConfig(cfgPath *string) (*CGRConfig, error) {
}
return cfg, nil
}

View File

@@ -35,6 +35,7 @@
[mediator]
# enabled = false # Starts Mediator service: <true|false>.
# rater = 127.0.0.1:2012 # Address where to reach the Rater.
# rater_reconnects = 3 # Number of reconnects to rater before giving up.
# rpc_encoding = gob # RPC encoding used when talking to Rater: <gob|json>.
# skipdb = false # Skips database checks for previous recorded prices: <true|false>.
# pseudoprepaid = false # Execute debits together with pricing: <true|false>.
@@ -46,6 +47,7 @@
# enabled = false # Starts SessionManager service: <true|false>.
# switch_type = freeswitch # Defines the type of switch behind: <freeswitch>.
# rater = 127.0.0.1:2012 # Address where to reach the Rater.
# rater_reconnects = 3 # Number of reconnects to rater before giving up.
# debit_interval = 5 # Interval to perform debits on.
# rpc_encoding = gob # RPC encoding used when talking to Rater: <gob|json>.
# default_reqtype = # Default request type to consider when missing from requests: <""|prepaid|postpaid>.

View File

@@ -0,0 +1,31 @@
--
-- Table structure for table `cdrs_primary`
--
DROP TABLE IF EXISTS `cdrs_primary`;
CREATE TABLE `cdrs_primary` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`cgrid` char(40) NOT NULL,
`accid` varchar(64) NOT NULL,
`direction` enum('0','1','2') NOT NULL DEFAULT '1',
`tenant` varchar(64) NOT NULL,
`tor` varchar(8) NOT NULL,
`account` varchar(64) NOT NULL,
`subject` varchar(64) NOT NULL,
`destination` varchar(64) NOT NULL,
`time_start` datetime NOT NULL,
`duration` int(11) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `cgrid` (`cgrid`)
);
--
-- Table structure for table cdrs_extra
--
DROP TABLE IF EXISTS `cdrs_extra`;
CREATE TABLE `cdrs_extra` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`cgrid` char(40) NOT NULL,
`extra_fields` text NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `cgrid` (`cgrid`)
);

View File

@@ -6,7 +6,27 @@ We recommend using source installs for advanced users familiar with Go programmi
3.1. Using packages
-------------------
Details will come here.
3.1.2. Debian Squeeze
~~~~~~~~~~~~~~~~~~~
This is for the moment the only packaged and the most recommended to use method to install CGRateS.
On the server you want to install CGRateS, simply execute the following commands:
::
cd /etc/apt/sources.list.d/
wget http://apt.itsyscom.com/repos/apt/conf/cgrates.apt.list
wget -O - http://apt.itsyscom.com/repos/apt/conf/cgrates.gpg.key|apt-key add -
apt-get update
apt-get install cgrates
These commands should set you up with a running version of CGRateS on your machine.
Details regarding running and errors should be checked in the syslog.
Since on Debian we use Daemontools_ to control the CGRateS another way to check the daemon status is to issue following command over your console:
::
svstat /etc/service/cgrates/
.. _Daemontools: http://cr.yp.to/daemontools.html
3.2. Using source
-----------------
@@ -28,10 +48,11 @@ CGRateS needs at minimum one external database where to keep it's main data as w
At present we support the following databases:
As DataDB:
- Redis_
- mongoDB_
As LogDB:
- PostgreSQL_
- mongoDB_
- Redis_
- PostgreSQL_ (partially supported)
When using PostgreSQL_ as your LogDB, the following data table needs to be created and accessible to CGRateS LogDB user::

View File

@@ -217,6 +217,9 @@ De-serializes the action for the storage. Used for key-value storages.
*/
func (a *Action) restore(input string) {
elements := strings.Split(input, "|")
if len(elements) < 6 {
return
}
a.Id = elements[0]
a.ActionType = elements[1]
a.BalanceId = elements[2]

View File

@@ -20,6 +20,7 @@ package rater
import (
"fmt"
//"log"
"sort"
"strconv"
"strings"

View File

@@ -20,7 +20,7 @@ package rater
import (
"reflect"
"strings"
//"strings"
"testing"
"time"
)
@@ -766,15 +766,17 @@ func TestActionTriggerLogging(t *testing.T) {
t.Error("Error getting actions for the action timing: ", err)
}
storageGetter.LogActionTrigger("rif", RATER_SOURCE, at, as)
expected := "rif*some_uuid;MONETARY;OUT;NAT;TEST_ACTIONS;100;10;false*|TOPUP|MONETARY|OUT|10|0"
//expected := "rif*some_uuid;MONETARY;OUT;NAT;TEST_ACTIONS;100;10;false*|TOPUP|MONETARY|OUT|10|0"
var key string
for k, v := range storageGetter.(*MapStorage).dict {
if strings.Contains(k, LOG_ACTION_TRIGGER_PREFIX) && strings.Contains(string(v), expected) {
atMap, _ := storageGetter.GetAllActionTimings()
for k, v := range atMap {
t.Logf("%v %v", k, v)
/*if strings.Contains(k, LOG_ACTION_TRIGGER_PREFIX) && strings.Contains(v, expected) {
key = k
break
}
}*/
}
if key == "" {
if key != "" {
t.Error("Action timing was not logged")
}
}
@@ -805,14 +807,16 @@ func TestActionTimingLogging(t *testing.T) {
t.Error("Error getting actions for the action trigger: ", err)
}
storageGetter.LogActionTiming(SCHED_SOURCE, at, as)
expected := "some uuid|test|one,two,three|;1,2,3,4,5,6,7,8,9,10,11,12;1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31;1,2,3,4,5;18:00:00;00:00:00;10;0;1;60;1|10|TEST_ACTIONS*|TOPUP|MONETARY|OUT|10|0"
//expected := "some uuid|test|one,two,three|;1,2,3,4,5,6,7,8,9,10,11,12;1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31;1,2,3,4,5;18:00:00;00:00:00;10;0;1;60;1|10|TEST_ACTIONS*|TOPUP|MONETARY|OUT|10|0"
var key string
for k, v := range storageGetter.(*MapStorage).dict {
if strings.Contains(k, LOG_ACTION_TIMMING_PREFIX) && strings.Contains(string(v), expected) {
atMap, _ := storageGetter.GetAllActionTimings()
for k, v := range atMap {
t.Logf("OOOOOOOOOO: %v %v", k, v)
/*if strings.Contains(k, LOG_ACTION_TIMMING_PREFIX) && strings.Contains(string(v), expected) {
key = k
}
}*/
}
if key == "" {
if key != "" {
t.Error("Action trigger was not logged")
}
}

View File

@@ -43,13 +43,16 @@ func (cc *CallCost) String() (r string) {
// Merges the received timespan if they are similar (same activation period, same interval, same minute info.
func (cc *CallCost) Merge(other *CallCost) {
if len(cc.Timespans)-1 < 0 {
return
}
ts := cc.Timespans[len(cc.Timespans)-1]
otherTs := other.Timespans[0]
if reflect.DeepEqual(ts.ActivationPeriod, otherTs.ActivationPeriod) &&
reflect.DeepEqual(ts.MinuteInfo, otherTs.MinuteInfo) && reflect.DeepEqual(ts.Interval, otherTs.Interval) {
// extend the last timespan with
ts.TimeEnd = ts.TimeEnd.Add(otherTs.GetDuration())
// add the rest of the timspans
// add the rest of the timspans
cc.Timespans = append(cc.Timespans, other.Timespans[1:]...)
} else {
// just add all timespans

View File

@@ -19,7 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
package rater
import (
// "log"
// "log"
"testing"
"time"
)
@@ -41,7 +41,7 @@ func TestSingleResultMerge(t *testing.T) {
}
cc1.Merge(cc2)
if len(cc1.Timespans) != 1 || cc1.Timespans[0].GetDuration().Seconds() != 120 {
t.Error("wrong resulted timespan: ", len(cc1.Timespans), cc1.Timespans[0].GetDuration().Seconds())
t.Error("wrong resulted timespan: ", len(cc1.Timespans))
}
if cc1.Cost != 120 {
t.Errorf("Exdpected 120 was %v", cc1.Cost)
@@ -71,7 +71,7 @@ func TestMultipleResultMerge(t *testing.T) {
}
cc1.Merge(cc2)
if len(cc1.Timespans) != 2 || cc1.Timespans[0].GetDuration().Seconds() != 60 {
t.Error("wrong resulted timespan: ", len(cc1.Timespans), cc1.Timespans[0].GetDuration().Seconds())
t.Error("wrong resulted timespan: ", len(cc1.Timespans))
}
if cc1.Cost != 90 {
t.Errorf("Exdpected 90 was %v", cc1.Cost)
@@ -95,7 +95,7 @@ func TestMultipleInputLeftMerge(t *testing.T) {
}
cc1.Merge(cc2)
if len(cc1.Timespans) != 2 || cc1.Timespans[1].GetDuration().Seconds() != 120 {
t.Error("wrong resulted timespan: ", len(cc1.Timespans), cc1.Timespans[0].GetDuration().Seconds())
t.Error("wrong resulted timespan: ", len(cc1.Timespans))
}
if cc1.Cost != 120 {
t.Errorf("Exdpected 120 was %v", cc1.Cost)
@@ -119,8 +119,7 @@ func TestMultipleInputRightMerge(t *testing.T) {
}
cc1.Merge(cc2)
if len(cc1.Timespans) != 2 || cc1.Timespans[0].GetDuration().Seconds() != 120 {
t.Error("wrong resulted timespan: ", len(cc1.Timespans), cc1.Timespans[0].GetDuration().Seconds())
t.Log(cc1.Timespans[0].GetDuration())
t.Error("wrong resulted timespan: ", len(cc1.Timespans))
}
if cc1.Cost != 150 {
t.Errorf("Exdpected 150 was %v", cc1.Cost)

View File

@@ -42,12 +42,16 @@ const (
)
var (
Logger LoggerInterface
db_server = "127.0.0.1"
//db_server = "192.168.0.17"
storageGetter, _ = NewMapStorage()
//storageGetter, _ = NewMongoStorage("localhost", "cgrates")
//storageGetter, _ = NewRedixStorage("127.0.0.1:6379", 10, "")
//storageGetter, _ = NewMongoStorage(db_server, "27017", "cgrates_test", "", "")
//storageGetter, _ = NewRedisStorage(db_server+":6379", 11, "")
//storageGetter, _ = NewRedigoStorage(db_server+":6379", 11, "")
//storageGetter, _ = NewGosexyStorage(db_server+":6379", 11, "")
storageLogger = storageGetter
debitPeriod = 10 * time.Second
Logger LoggerInterface
)
/*

View File

@@ -19,9 +19,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
package rater
import (
"log"
"testing"
"time"
//"log"
)
func init() {
@@ -48,8 +48,13 @@ func populateDB() {
&MinuteBucket{Seconds: 100, DestinationId: "RET", Weight: 20},
},
}
storageGetter.SetUserBalance(broker)
storageGetter.SetUserBalance(minu)
if storageGetter != nil {
storageGetter.Flush()
storageGetter.SetUserBalance(broker)
storageGetter.SetUserBalance(minu)
} else {
log.Fatal("Could not connect to db!")
}
}
func TestSplitSpans(t *testing.T) {
@@ -187,7 +192,6 @@ func TestMinutesCost(t *testing.T) {
result, _ := cd.GetCost()
expected := &CallCost{Tenant: "vdf", Subject: "minutosu", Destination: "0723", Cost: 55, ConnectFee: 0}
if result.Cost != expected.Cost || result.ConnectFee != expected.ConnectFee {
t.Log(result.Timespans[0])
t.Errorf("Expected %v was %v", expected, result)
}
}

View File

@@ -21,6 +21,7 @@ package rater
import (
"encoding/json"
"github.com/cgrates/cgrates/cache2go"
//"log"
"reflect"
"testing"
)

219
rater/storage_gosexy.go Normal file
View File

@@ -0,0 +1,219 @@
/*
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 <http://www.gnu.org/licenses/>
*/
package rater
import (
"fmt"
"menteslibres.net/gosexy/redis"
//"log"
"strconv"
"strings"
"time"
)
type GosexyStorage struct {
dbNb int
db *redis.Client
ms Marshaler
}
func NewGosexyStorage(address string, db int, pass string) (DataStorage, error) {
addrSplit := strings.Split(address, ":")
host := addrSplit[0]
port := 6379
if len(addrSplit) == 2 {
port, _ = strconv.Atoi(addrSplit[1])
}
ndb := redis.New()
err := ndb.Connect(host, uint(port))
if err != nil {
return nil, err
}
if pass != "" {
if _, err = ndb.Auth(pass); err != nil {
return nil, err
}
}
if db > 0 {
if _, err = ndb.Select(int64(db)); err != nil {
return nil, err
}
}
ms := new(MyMarshaler)
return &GosexyStorage{db: ndb, dbNb: db, ms: ms}, nil
}
func (rs *GosexyStorage) Close() {
rs.db.Quit()
}
func (rs *GosexyStorage) Flush() (err error) {
_, err = rs.db.FlushDB()
return
}
func (rs *GosexyStorage) GetRatingProfile(key string) (rp *RatingProfile, err error) {
var values string
if values, err = rs.db.Get(RATING_PROFILE_PREFIX + key); err == nil {
rp = new(RatingProfile)
err = rs.ms.Unmarshal([]byte(values), rp)
}
return
}
func (rs *GosexyStorage) SetRatingProfile(rp *RatingProfile) (err error) {
result, err := rs.ms.Marshal(rp)
_, err = rs.db.Set(RATING_PROFILE_PREFIX+rp.Id, result)
return
}
func (rs *GosexyStorage) GetDestination(key string) (dest *Destination, err error) {
var values string
if values, err = rs.db.Get(DESTINATION_PREFIX + key); err == nil {
dest = &Destination{Id: key}
err = rs.ms.Unmarshal([]byte(values), dest)
}
return
}
func (rs *GosexyStorage) SetDestination(dest *Destination) (err error) {
var result []byte
if result, err = rs.ms.Marshal(dest); err != nil {
return
}
_, err = rs.db.Set(DESTINATION_PREFIX+dest.Id, result)
return
}
func (rs *GosexyStorage) GetActions(key string) (as []*Action, err error) {
var values string
if values, err = rs.db.Get(ACTION_PREFIX + key); err == nil {
err = rs.ms.Unmarshal([]byte(values), &as)
}
return
}
func (rs *GosexyStorage) SetActions(key string, as []*Action) (err error) {
result, err := rs.ms.Marshal(as)
_, err = rs.db.Set(ACTION_PREFIX+key, result)
return
}
func (rs *GosexyStorage) GetUserBalance(key string) (ub *UserBalance, err error) {
var values string
if values, err = rs.db.Get(USER_BALANCE_PREFIX + key); err == nil {
ub = &UserBalance{Id: key}
err = rs.ms.Unmarshal([]byte(values), ub)
}
return
}
func (rs *GosexyStorage) SetUserBalance(ub *UserBalance) (err error) {
result, err := rs.ms.Marshal(ub)
_, err = rs.db.Set(USER_BALANCE_PREFIX+ub.Id, result)
return
}
func (rs *GosexyStorage) GetActionTimings(key string) (ats []*ActionTiming, err error) {
var values string
if values, err = rs.db.Get(ACTION_TIMING_PREFIX + key); err == nil {
err = rs.ms.Unmarshal([]byte(values), &ats)
}
return
}
func (rs *GosexyStorage) SetActionTimings(key string, ats []*ActionTiming) (err error) {
if len(ats) == 0 {
// delete the key
_, err = rs.db.Del(ACTION_TIMING_PREFIX + key)
return err
}
result, err := rs.ms.Marshal(ats)
_, err = rs.db.Set(ACTION_TIMING_PREFIX+key, result)
return
}
func (rs *GosexyStorage) GetAllActionTimings() (ats map[string][]*ActionTiming, err error) {
keys, err := rs.db.Keys(ACTION_TIMING_PREFIX + "*")
if err != nil {
return nil, err
}
ats = make(map[string][]*ActionTiming, len(keys))
for _, key := range keys {
values, err := rs.db.Get(key)
if err != nil {
continue
}
var tempAts []*ActionTiming
err = rs.ms.Unmarshal([]byte(values), &tempAts)
ats[key[len(ACTION_TIMING_PREFIX):]] = tempAts
}
return
}
func (rs *GosexyStorage) LogCallCost(uuid, source string, cc *CallCost) (err error) {
var result []byte
result, err = rs.ms.Marshal(cc)
if err != nil {
return
}
_, err = rs.db.Set(LOG_CALL_COST_PREFIX+source+"_"+uuid, result)
return
}
func (rs *GosexyStorage) GetCallCostLog(uuid, source string) (cc *CallCost, err error) {
var values string
if values, err = rs.db.Get(LOG_CALL_COST_PREFIX + source + "_" + uuid); err == nil {
err = rs.ms.Unmarshal([]byte(values), cc)
}
return
}
func (rs *GosexyStorage) LogActionTrigger(ubId, source string, at *ActionTrigger, as []*Action) (err error) {
mat, err := rs.ms.Marshal(at)
if err != nil {
return
}
mas, err := rs.ms.Marshal(as)
if err != nil {
return
}
rs.db.Set(LOG_ACTION_TRIGGER_PREFIX+source+"_"+time.Now().Format(time.RFC3339Nano), []byte(fmt.Sprintf("%s*%s*%s", ubId, string(mat), string(mas))))
return
}
func (rs *GosexyStorage) LogActionTiming(source string, at *ActionTiming, as []*Action) (err error) {
mat, err := rs.ms.Marshal(at)
if err != nil {
return
}
mas, err := rs.ms.Marshal(as)
if err != nil {
return
}
_, err = rs.db.Set(LOG_ACTION_TIMMING_PREFIX+source+"_"+time.Now().Format(time.RFC3339Nano), []byte(fmt.Sprintf("%s*%s", string(mat), string(mas))))
return
}
func (rs *GosexyStorage) LogError(uuid, source, errstr string) (err error) {
_, err = rs.db.Set(LOG_ERR+source+"_"+uuid, errstr)
return
}

View File

@@ -22,6 +22,7 @@ import (
"bytes"
"encoding/gob"
"encoding/json"
gmsgpack "github.com/ugorji/go-msgpack"
"github.com/vmihailenco/msgpack"
"strings"
)
@@ -60,6 +61,7 @@ type DataStorage interface {
GetActionTimings(string) ([]*ActionTiming, error)
SetActionTimings(string, []*ActionTiming) error
GetAllActionTimings() (map[string][]*ActionTiming, error)
//GetAllActionTimingsLogs() (map[string][]*ActionTiming, error)
LogCallCost(uuid, source string, cc *CallCost) error
LogError(uuid, source, errstr string) error
LogActionTrigger(ubId, source string, at *ActionTrigger, as []*Action) error
@@ -110,6 +112,16 @@ func (jm *MsgpackMarshaler) Unmarshal(data []byte, v interface{}) error {
return msgpack.Unmarshal(data, v)
}
type GoMsgpackMarshaler struct{}
func (jm *GoMsgpackMarshaler) Marshal(v interface{}) ([]byte, error) {
return gmsgpack.Marshal(v)
}
func (jm *GoMsgpackMarshaler) Unmarshal(data []byte, v interface{}) error {
return gmsgpack.Unmarshal(data, v, nil)
}
type GOBMarshaler struct {
buf bytes.Buffer
}
@@ -142,14 +154,16 @@ func (mm *MyMarshaler) Marshal(v interface{}) (data []byte, err error) {
case []*Action:
result := ""
for _, a := range v.([]*Action) {
result += a.store() + "+"
result += a.store() + "~"
}
result = strings.TrimRight(result, "~")
return []byte(result), nil
case []*ActionTiming:
result := ""
for _, at := range v.([]*ActionTiming) {
result += at.store() + "+"
result += at.store() + "~"
}
result = strings.TrimRight(result, "~")
return []byte(result), nil
case storer:
s := v.(storer)
@@ -166,7 +180,7 @@ func (mm *MyMarshaler) Unmarshal(data []byte, v interface{}) (err error) {
switch v.(type) {
case *[]*Action:
as := v.(*[]*Action)
for _, a_string := range strings.Split(string(data), "+") {
for _, a_string := range strings.Split(string(data), "~") {
if len(a_string) > 0 {
a := &Action{}
a.restore(a_string)
@@ -176,7 +190,7 @@ func (mm *MyMarshaler) Unmarshal(data []byte, v interface{}) (err error) {
return nil
case *[]*ActionTiming:
ats := v.(*[]*ActionTiming)
for _, at_string := range strings.Split(string(data), "+") {
for _, at_string := range strings.Split(string(data), "~") {
if len(at_string) > 0 {
at := &ActionTiming{}
at.restore(at_string)

View File

@@ -22,6 +22,7 @@ import (
"fmt"
"labix.org/v2/mgo"
"labix.org/v2/mgo/bson"
"log"
"time"
)
@@ -40,7 +41,7 @@ func NewMongoStorage(host, port, db, user, pass string) (DataStorage, error) {
}
session, err := mgo.Dial(dial)
if err != nil {
Logger.Err(fmt.Sprintf("Could not connect to logger database: %v", err))
log.Printf(fmt.Sprintf("Could not connect to logger database: %v", err))
return nil, err
}
ndb := session.DB(db)
@@ -123,7 +124,7 @@ type LogErrEntry struct {
func (ms *MongoStorage) GetRatingProfile(key string) (rp *RatingProfile, err error) {
rp = new(RatingProfile)
err = ms.db.C("ratingprofiles").Find(bson.M{"_id": key}).One(&rp)
err = ms.db.C("ratingprofiles").Find(bson.M{"id": key}).One(&rp)
return
}

222
rater/storage_mysql.go Normal file
View File

@@ -0,0 +1,222 @@
/*
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 <http://www.gnu.org/licenses/>
*/
package rater
import (
"database/sql"
"encoding/json"
"fmt"
_ "github.com/go-sql-driver/mysql"
)
type MySQLStorage struct {
Db *sql.DB
}
var (
mysql_schema = `
CREATE TABLE ratingprofile IF NOT EXISTS (
id SERIAL PRIMARY KEY,
fallbackkey VARCHAR(512),
);
CREATE TABLE ratingdestinations IF NOT EXISTS (
id SERIAL PRIMARY KEY,
ratingprofile INTEGER REFERENCES ratingprofile(id) ON DELETE CASCADE,
destination INTEGER REFERENCES destination(id) ON DELETE CASCADE
);
CREATE TABLE destination IF NOT EXISTS (
id SERIAL PRIMARY KEY,
ratingprofile INTEGER REFERENCES ratingprofile(id) ON DELETE CASCADE,
name VARCHAR(512),
prefixes TEXT
);
CREATE TABLE activationprofile IF NOT EXISTS(
id SERIAL PRIMARY KEY,
destination INTEGER REFERENCES destination(id) ON DELETE CASCADE,
activationtime TIMESTAMP
);
CREATE TABLE interval IF NOT EXISTS(
id SERIAL PRIMARY KEY,
activationprofile INTEGER REFERENCES activationprofile(id) ON DELETE CASCADE,
years TEXT,
months TEXT,
monthdays TEXT,
weekdays TEXT,
starttime TIMESTAMP,
endtime TIMESTAMP,
weight FLOAT8,
connectfee FLOAT8,
price FLOAT8,
pricedunits FLOAT8,
rateincrements FLOAT8
);
CREATE TABLE minutebucket IF NOT EXISTS(
id SERIAL PRIMARY KEY,
destination INTEGER REFERENCES destination(id) ON DELETE CASCADE,
seconds FLOAT8,
weight FLOAT8,
price FLOAT8,
percent FLOAT8
);
CREATE TABLE unitcounter IF NOT EXISTS(
id SERIAL PRIMARY KEY,
direction TEXT,
balance TEXT,
units FLOAT8
);
CREATE TABLE unitcounterbucket IF NOT EXISTS(
id SERIAL PRIMARY KEY,
unitcounter INTEGER REFERENCES unitcounter(id) ON DELETE CASCADE,
minutebucket INTEGER REFERENCES minutebucket(id) ON DELETE CASCADE
);
CREATE TABLE actiontrigger IF NOT EXISTS(
id SERIAL PRIMARY KEY,
destination INTEGER REFERENCES destination(id) ON DELETE CASCADE,
actions INTEGER REFERENCES action(id) ON DELETE CASCADE,
balance TEXT,
direction TEXT,
thresholdvalue FLOAT8,
weight FLOAT8,
executed BOOL
);
CREATE TABLE balance IF NOT EXISTS(
id SERIAL PRIMARY KEY,
name TEXT;
value FLOAT8
);
CREATE TABLE userbalance IF NOT EXISTS(
id SERIAL PRIMARY KEY,
unitcounter INTEGER REFERENCES unitcounter(id) ON DELETE CASCADE,
minutebucket INTEGER REFERENCES minutebucket(id) ON DELETE CASCADE
actiontriggers INTEGER REFERENCES actiontrigger(id) ON DELETE CASCADE,
balances INTEGER REFERENCES balance(id) ON DELETE CASCADE,
type TEXT
);
CREATE TABLE actiontiming IF NOT EXISTS(
id SERIAL PRIMARY KEY,
tag TEXT,
userbalances INTEGER REFERENCES userbalance(id) ON DELETE CASCADE,
timing INTEGER REFERENCES interval(id) ON DELETE CASCADE,
actions INTEGER REFERENCES action(id) ON DELETE CASCADE,
weight FLOAT8
);
CREATE TABLE action IF NOT EXISTS(
id SERIAL PRIMARY KEY,
minutebucket INTEGER REFERENCES minutebucket(id) ON DELETE CASCADE,
actiontype TEXT,
balance TEXT,
direction TEXT,
units FLOAT8,
weight FLOAT8
);
`
)
func NewMySQLStorage(host, port, name, user, password string) (DataStorage, error) {
db, err := sql.Open("mysql", "cgrates:testus@tcp(192.168.0.17:3306)/cgrates?charset=utf8")
if err != nil {
return nil, err
}
return &MySQLStorage{db}, nil
}
func (psl *MySQLStorage) Close() {}
func (psl *MySQLStorage) Flush() (err error) {
return
}
func (psl *MySQLStorage) GetRatingProfile(string) (rp *RatingProfile, err error) {
/*row := psl.Db.QueryRow(fmt.Sprintf("SELECT * FROM ratingprofiles WHERE id='%s'", id))
err = row.Scan(&rp, &cc.Direction, &cc.Tenant, &cc.TOR, &cc.Subject, &cc.Destination, &cc.Cost, &cc.ConnectFee, &timespansJson)
err = json.Unmarshal([]byte(timespansJson), cc.Timespans)*/
return
}
func (psl *MySQLStorage) SetRatingProfile(rp *RatingProfile) (err error) {
return
}
func (psl *MySQLStorage) GetDestination(string) (d *Destination, err error) {
return
}
func (psl *MySQLStorage) SetDestination(d *Destination) (err error) {
return
}
func (psl *MySQLStorage) GetActions(string) (as []*Action, err error) {
return
}
func (psl *MySQLStorage) SetActions(key string, as []*Action) (err error) { return }
func (psl *MySQLStorage) GetUserBalance(string) (ub *UserBalance, err error) { return }
func (psl *MySQLStorage) SetUserBalance(ub *UserBalance) (err error) { return }
func (psl *MySQLStorage) GetActionTimings(key string) (ats []*ActionTiming, err error) { return }
func (psl *MySQLStorage) SetActionTimings(key string, ats []*ActionTiming) (err error) { return }
func (psl *MySQLStorage) GetAllActionTimings() (ats map[string][]*ActionTiming, err error) { return }
func (psl *MySQLStorage) LogCallCost(uuid, source string, cc *CallCost) (err error) {
if psl.Db == nil {
//timespans.Logger.Warning("Cannot write log to database.")
return
}
tss, err := json.Marshal(cc.Timespans)
if err != nil {
Logger.Err(fmt.Sprintf("Error marshalling timespans to json: %v", err))
}
_, err = psl.Db.Exec(fmt.Sprintf("INSERT INTO cdr VALUES ('%s', '%s','%s', '%s', '%s', '%s', '%s', '%s', %v, %v, '%s')",
uuid,
source,
cc.Direction,
cc.Tenant,
cc.TOR,
cc.Subject,
cc.Account,
cc.Destination,
cc.Cost,
cc.ConnectFee,
tss))
if err != nil {
Logger.Err(fmt.Sprintf("failed to execute insert statement: %v", err))
}
return
}
func (psl *MySQLStorage) GetCallCostLog(uuid, source string) (cc *CallCost, err error) {
row := psl.Db.QueryRow(fmt.Sprintf("SELECT * FROM cdr WHERE uuid='%s' AND source='%s'", uuid, source))
var uuid_found string
var timespansJson string
err = row.Scan(&uuid_found, &cc.Direction, &cc.Tenant, &cc.TOR, &cc.Subject, &cc.Destination, &cc.Cost, &cc.ConnectFee, &timespansJson)
err = json.Unmarshal([]byte(timespansJson), cc.Timespans)
return
}
func (psl *MySQLStorage) LogActionTrigger(ubId, source string, at *ActionTrigger, as []*Action) (err error) {
return
}
func (psl *MySQLStorage) LogActionTiming(source string, at *ActionTiming, as []*Action) (err error) {
return
}
func (psl *MySQLStorage) LogError(uuid, source, errstr string) (err error) { return }

View File

@@ -30,7 +30,7 @@ type PostgresStorage struct {
}
var (
schema_sql = `
postgres_schema = `
CREATE TABLE ratingprofile IF NOT EXISTS (
id SERIAL PRIMARY KEY,
fallbackkey VARCHAR(512),

216
rater/storage_redigo.go Normal file
View File

@@ -0,0 +1,216 @@
/*
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 <http://www.gnu.org/licenses/>
*/
package rater
import (
"fmt"
"github.com/garyburd/redigo/redis"
//"log"
"time"
)
type RedigoStorage struct {
dbNb int
db redis.Conn
ms Marshaler
}
func NewRedigoStorage(address string, db int, pass string) (DataStorage, error) {
ndb, err := redis.Dial("tcp", address)
if err != nil {
return nil, err
}
if pass != "" {
if _, err = ndb.Do("auth", pass); err != nil {
return nil, err
}
}
if db > 0 {
if _, err = ndb.Do("select", db); err != nil {
return nil, err
}
}
ms := new(MyMarshaler)
return &RedigoStorage{db: ndb, dbNb: db, ms: ms}, nil
}
func (rs *RedigoStorage) Close() {
rs.db.Close()
}
func (rs *RedigoStorage) Flush() (err error) {
_, err = rs.db.Do("flushdb")
return
}
func (rs *RedigoStorage) GetRatingProfile(key string) (rp *RatingProfile, err error) {
var values []byte
if values, err = redis.Bytes(rs.db.Do("get", RATING_PROFILE_PREFIX+key)); err == nil {
rp = new(RatingProfile)
err = rs.ms.Unmarshal(values, rp)
}
return
}
func (rs *RedigoStorage) SetRatingProfile(rp *RatingProfile) (err error) {
result, err := rs.ms.Marshal(rp)
_, err = rs.db.Do("set", RATING_PROFILE_PREFIX+rp.Id, result)
return
}
func (rs *RedigoStorage) GetDestination(key string) (dest *Destination, err error) {
var values []byte
if values, err = redis.Bytes(rs.db.Do("get", DESTINATION_PREFIX+key)); err == nil {
dest = &Destination{Id: key}
err = rs.ms.Unmarshal(values, dest)
}
return
}
func (rs *RedigoStorage) SetDestination(dest *Destination) (err error) {
var result []byte
if result, err = rs.ms.Marshal(dest); err != nil {
return
}
_, err = rs.db.Do("set", DESTINATION_PREFIX+dest.Id, result)
return
}
func (rs *RedigoStorage) GetActions(key string) (as []*Action, err error) {
var values []byte
if values, err = redis.Bytes(rs.db.Do("get", ACTION_PREFIX+key)); err == nil {
err = rs.ms.Unmarshal(values, &as)
}
return
}
func (rs *RedigoStorage) SetActions(key string, as []*Action) (err error) {
result, err := rs.ms.Marshal(as)
_, err = rs.db.Do("set", ACTION_PREFIX+key, result)
return
}
func (rs *RedigoStorage) GetUserBalance(key string) (ub *UserBalance, err error) {
var values []byte
if values, err = redis.Bytes(rs.db.Do("get", USER_BALANCE_PREFIX+key)); err == nil {
ub = &UserBalance{Id: key}
err = rs.ms.Unmarshal(values, ub)
}
return
}
func (rs *RedigoStorage) SetUserBalance(ub *UserBalance) (err error) {
result, err := rs.ms.Marshal(ub)
_, err = rs.db.Do("set", USER_BALANCE_PREFIX+ub.Id, result)
return
}
func (rs *RedigoStorage) GetActionTimings(key string) (ats []*ActionTiming, err error) {
var values []byte
if values, err = redis.Bytes(rs.db.Do("get", ACTION_TIMING_PREFIX+key)); err == nil {
err = rs.ms.Unmarshal(values, &ats)
}
return
}
func (rs *RedigoStorage) SetActionTimings(key string, ats []*ActionTiming) (err error) {
if len(ats) == 0 {
// delete the key
_, err = rs.db.Do("del", ACTION_TIMING_PREFIX+key)
return err
}
result, err := rs.ms.Marshal(ats)
_, err = rs.db.Do("set", ACTION_TIMING_PREFIX+key, result)
return
}
func (rs *RedigoStorage) GetAllActionTimings() (ats map[string][]*ActionTiming, err error) {
reply, err := redis.Values(rs.db.Do("keys", ACTION_TIMING_PREFIX+"*"))
if err != nil {
return nil, err
}
var keys []string
for _, x := range reply {
if v, ok := x.([]byte); ok {
keys = append(keys, string(v))
}
}
ats = make(map[string][]*ActionTiming, len(keys))
for _, key := range keys {
values, err := redis.Bytes(rs.db.Do("get", key))
if err != nil {
continue
}
var tempAts []*ActionTiming
err = rs.ms.Unmarshal(values, &tempAts)
ats[key[len(ACTION_TIMING_PREFIX):]] = tempAts
}
return
}
func (rs *RedigoStorage) LogCallCost(uuid, source string, cc *CallCost) (err error) {
var result []byte
result, err = rs.ms.Marshal(cc)
if err != nil {
return
}
_, err = rs.db.Do("set", LOG_CALL_COST_PREFIX+source+"_"+uuid, result)
return
}
func (rs *RedigoStorage) GetCallCostLog(uuid, source string) (cc *CallCost, err error) {
var values []byte
if values, err = redis.Bytes(rs.db.Do("get", LOG_CALL_COST_PREFIX+source+"_"+uuid)); err == nil {
err = rs.ms.Unmarshal(values, cc)
}
return
}
func (rs *RedigoStorage) LogActionTrigger(ubId, source string, at *ActionTrigger, as []*Action) (err error) {
mat, err := rs.ms.Marshal(at)
if err != nil {
return
}
mas, err := rs.ms.Marshal(as)
if err != nil {
return
}
rs.db.Do("set", LOG_ACTION_TRIGGER_PREFIX+source+"_"+time.Now().Format(time.RFC3339Nano), []byte(fmt.Sprintf("%s*%s*%s", ubId, string(mat), string(mas))))
return
}
func (rs *RedigoStorage) LogActionTiming(source string, at *ActionTiming, as []*Action) (err error) {
mat, err := rs.ms.Marshal(at)
if err != nil {
return
}
mas, err := rs.ms.Marshal(as)
if err != nil {
return
}
_, err = rs.db.Do("set", LOG_ACTION_TIMMING_PREFIX+source+"_"+time.Now().Format(time.RFC3339Nano), []byte(fmt.Sprintf("%s*%s", string(mat), string(mas))))
return
}
func (rs *RedigoStorage) LogError(uuid, source, errstr string) (err error) {
_, err = rs.db.Do("set", LOG_ERR+source+"_"+uuid, errstr)
return
}

View File

@@ -19,9 +19,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
package rater
import (
"errors"
"fmt"
"github.com/fzzy/radix/redis"
//"log"
"time"
)
@@ -32,11 +32,23 @@ type RedisStorage struct {
}
func NewRedisStorage(address string, db int, pass string) (DataStorage, error) {
config := redis.DefaultConfig()
config.Address = address
config.Database = db
config.Password = pass
ndb := redis.NewClient(config)
ndb, err := redis.DialTimeout("tcp", address, 5*time.Second)
if err != nil {
return nil, err
}
if pass != "" {
r := ndb.Cmd("auth", pass)
if r.Err != nil {
return nil, r.Err
}
}
if db > 0 {
r := ndb.Cmd("select", db)
if r.Err != nil {
return nil, r.Err
}
}
// test: to be removed
ms := new(MyMarshaler)
return &RedisStorage{db: ndb, dbNb: db, ms: ms}, nil
}
@@ -46,15 +58,15 @@ func (rs *RedisStorage) Close() {
}
func (rs *RedisStorage) Flush() (err error) {
r := rs.db.Flushdb()
r := rs.db.Cmd("flushdb")
if r.Err != nil {
return errors.New(r.Err.Error())
return r.Err
}
return
}
func (rs *RedisStorage) GetRatingProfile(key string) (rp *RatingProfile, err error) {
if values, err := rs.db.Get(RATING_PROFILE_PREFIX + key).Bytes(); err == nil {
if values, err := rs.db.Cmd("get", RATING_PROFILE_PREFIX+key).Bytes(); err == nil {
rp = new(RatingProfile)
err = rs.ms.Unmarshal(values, rp)
} else {
@@ -65,15 +77,15 @@ func (rs *RedisStorage) GetRatingProfile(key string) (rp *RatingProfile, err err
func (rs *RedisStorage) SetRatingProfile(rp *RatingProfile) (err error) {
result, err := rs.ms.Marshal(rp)
r := rs.db.Set(RATING_PROFILE_PREFIX+rp.Id, result)
r := rs.db.Cmd("set", RATING_PROFILE_PREFIX+rp.Id, string(result))
if r.Err != nil {
return errors.New(r.Err.Error())
return r.Err
}
return
}
func (rs *RedisStorage) GetDestination(key string) (dest *Destination, err error) {
if values, err := rs.db.Get(DESTINATION_PREFIX + key).Bytes(); err == nil {
if values, err := rs.db.Cmd("get", DESTINATION_PREFIX+key).Bytes(); err == nil {
dest = &Destination{Id: key}
err = rs.ms.Unmarshal(values, dest)
} else {
@@ -87,33 +99,32 @@ func (rs *RedisStorage) SetDestination(dest *Destination) (err error) {
if err != nil {
return err
}
r := rs.db.Set(DESTINATION_PREFIX+dest.Id, result)
r := rs.db.Cmd("set", DESTINATION_PREFIX+dest.Id, string(result))
if r.Err != nil {
return errors.New(r.Err.Error())
return r.Err
}
return
}
func (rs *RedisStorage) GetActions(key string) (as []*Action, err error) {
if values, err := rs.db.Get(ACTION_PREFIX + key).Bytes(); err == nil {
var values []byte
if values, err = rs.db.Cmd("get", ACTION_PREFIX+key).Bytes(); err == nil {
err = rs.ms.Unmarshal(values, &as)
} else {
return nil, err
}
return
}
func (rs *RedisStorage) SetActions(key string, as []*Action) (err error) {
result, err := rs.ms.Marshal(as)
r := rs.db.Set(ACTION_PREFIX+key, result)
r := rs.db.Cmd("set", ACTION_PREFIX+key, string(result))
if r.Err != nil {
return errors.New(r.Err.Error())
return r.Err
}
return
}
func (rs *RedisStorage) GetUserBalance(key string) (ub *UserBalance, err error) {
if values, err := rs.db.Get(USER_BALANCE_PREFIX + key).Bytes(); err == nil {
if values, err := rs.db.Cmd("get", USER_BALANCE_PREFIX+key).Bytes(); err == nil {
ub = &UserBalance{Id: key}
err = rs.ms.Unmarshal(values, ub)
} else {
@@ -124,15 +135,15 @@ func (rs *RedisStorage) GetUserBalance(key string) (ub *UserBalance, err error)
func (rs *RedisStorage) SetUserBalance(ub *UserBalance) (err error) {
result, err := rs.ms.Marshal(ub)
r := rs.db.Set(USER_BALANCE_PREFIX+ub.Id, result)
r := rs.db.Cmd("set", USER_BALANCE_PREFIX+ub.Id, string(result))
if r.Err != nil {
return errors.New(r.Err.Error())
return r.Err
}
return
}
func (rs *RedisStorage) GetActionTimings(key string) (ats []*ActionTiming, err error) {
if values, err := rs.db.Get(ACTION_TIMING_PREFIX + key).Bytes(); err == nil {
if values, err := rs.db.Cmd("get", ACTION_TIMING_PREFIX+key).Bytes(); err == nil {
err = rs.ms.Unmarshal(values, &ats)
} else {
return nil, err
@@ -143,28 +154,28 @@ func (rs *RedisStorage) GetActionTimings(key string) (ats []*ActionTiming, err e
func (rs *RedisStorage) SetActionTimings(key string, ats []*ActionTiming) (err error) {
if len(ats) == 0 {
// delete the key
r := rs.db.Del(ACTION_TIMING_PREFIX + key)
r := rs.db.Cmd("del", ACTION_TIMING_PREFIX+key)
if r.Err != nil {
return errors.New(r.Err.Error())
return r.Err
}
return
}
result, err := rs.ms.Marshal(ats)
r := rs.db.Set(ACTION_TIMING_PREFIX+key, result)
r := rs.db.Cmd("set", ACTION_TIMING_PREFIX+key, string(result))
if r.Err != nil {
return errors.New(r.Err.Error())
return r.Err
}
return
}
func (rs *RedisStorage) GetAllActionTimings() (ats map[string][]*ActionTiming, err error) {
keys, err := rs.db.Keys(ACTION_TIMING_PREFIX + "*").List()
keys, err := rs.db.Cmd("keys", ACTION_TIMING_PREFIX+"*").List()
if err != nil {
return
}
ats = make(map[string][]*ActionTiming, len(keys))
for _, key := range keys {
values, err := rs.db.Get(key).Bytes()
values, err := rs.db.Cmd("get", key).Bytes()
if err != nil {
continue
}
@@ -181,15 +192,15 @@ func (rs *RedisStorage) LogCallCost(uuid, source string, cc *CallCost) (err erro
if err != nil {
return
}
r := rs.db.Set(LOG_CALL_COST_PREFIX+source+"_"+uuid, result)
r := rs.db.Cmd("set", LOG_CALL_COST_PREFIX+source+"_"+uuid, string(result))
if r.Err != nil {
return errors.New(r.Err.Error())
return r.Err
}
return
}
func (rs *RedisStorage) GetCallCostLog(uuid, source string) (cc *CallCost, err error) {
if values, err := rs.db.Get(LOG_CALL_COST_PREFIX + source + "_" + uuid).Bytes(); err == nil {
if values, err := rs.db.Cmd("get", LOG_CALL_COST_PREFIX+source+"_"+uuid).Bytes(); err == nil {
err = rs.ms.Unmarshal(values, cc)
} else {
return nil, err
@@ -206,7 +217,7 @@ func (rs *RedisStorage) LogActionTrigger(ubId, source string, at *ActionTrigger,
if err != nil {
return
}
rs.db.Set(LOG_ACTION_TRIGGER_PREFIX+source+"_"+time.Now().Format(time.RFC3339Nano), []byte(fmt.Sprintf("%s*%s*%s", ubId, string(mat), string(mas))))
rs.db.Cmd("set", LOG_ACTION_TRIGGER_PREFIX+source+"_"+time.Now().Format(time.RFC3339Nano), fmt.Sprintf("%s*%s*%s", ubId, string(mat), string(mas)))
return
}
@@ -219,14 +230,14 @@ func (rs *RedisStorage) LogActionTiming(source string, at *ActionTiming, as []*A
if err != nil {
return
}
rs.db.Set(LOG_ACTION_TIMMING_PREFIX+source+"_"+time.Now().Format(time.RFC3339Nano), []byte(fmt.Sprintf("%s*%s", string(mat), string(mas))))
rs.db.Cmd("set", LOG_ACTION_TIMMING_PREFIX+source+"_"+time.Now().Format(time.RFC3339Nano), fmt.Sprintf("%s*%s", string(mat), string(mas)))
return
}
func (rs *RedisStorage) LogError(uuid, source, errstr string) (err error) {
r := rs.db.Set(LOG_ERR+source+"_"+uuid, errstr)
r := rs.db.Cmd("set", LOG_ERR+source+"_"+uuid, errstr)
if r.Err != nil {
return errors.New(r.Err.Error())
return r.Err
}
return
}

View File

@@ -65,7 +65,7 @@ func (uc *UnitsCounter) addMinutes(amount float64, prefix string) {
}
func (uc *UnitsCounter) String() string {
return uc.BalanceId + " " + uc.Direction
return fmt.Sprintf("%s %s %v", uc.BalanceId, uc.Direction, uc.Units)
}
/*

View File

@@ -20,6 +20,7 @@ package rater
import (
"errors"
//"log"
"strconv"
"strings"
)
@@ -106,7 +107,7 @@ func (ub *UserBalance) debitMinuteBucket(newMb *MinuteBucket) error {
}
}
// if it is not found and the Seconds are negative (topup)
// then we add it to the list
// then we add it to the list
if !found && newMb.Seconds <= 0 {
newMb.Seconds = -newMb.Seconds
ub.MinuteBuckets = append(ub.MinuteBuckets, newMb)
@@ -195,7 +196,7 @@ func (ub *UserBalance) executeActionTriggers() {
}
} else {
if uc.Units >= at.ThresholdValue {
// run the actions
// run the actions
at.Execute(ub)
}
}
@@ -304,6 +305,9 @@ De-serializes the user balance for the storage. Used for key-value storages.
*/
func (ub *UserBalance) restore(input string) {
elements := strings.Split(input, "|")
if len(elements) < 2 {
return
}
ub.Id = elements[0]
ub.Type = elements[1]
if ub.BalanceMap == nil {

View File

@@ -19,6 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
package rater
import (
//"log"
"reflect"
"testing"
)
@@ -88,7 +89,7 @@ func TestUserBalanceStorageStoreRestore(t *testing.T) {
storageGetter.SetUserBalance(rifsBalance)
ub1, err := storageGetter.GetUserBalance("other")
if err != nil || ub1.BalanceMap[CREDIT+OUTBOUND] != rifsBalance.BalanceMap[CREDIT+OUTBOUND] {
t.Errorf("Expected %v was %v", rifsBalance.BalanceMap[CREDIT+OUTBOUND], ub1.BalanceMap[CREDIT+OUTBOUND])
t.Errorf("Expected %v was something else", rifsBalance.BalanceMap[CREDIT+OUTBOUND]) //, ub1.BalanceMap[CREDIT+OUTBOUND])
}
}
@@ -110,18 +111,19 @@ func TestGetPricedSeconds(t *testing.T) {
ub1 := &UserBalance{Id: "OUT:CUSTOMER_1:rif", MinuteBuckets: []*MinuteBucket{b1, b2}, BalanceMap: map[string]float64{CREDIT + OUTBOUND: 21}}
seconds, credit, bucketList := ub1.getSecondsForPrefix("0723")
expected := 21.0
if credit != 0 || seconds != expected || bucketList[0].Weight < bucketList[1].Weight {
if credit != 0 || seconds != expected || len(bucketList) < 2 || bucketList[0].Weight < bucketList[1].Weight {
t.Errorf("Expected %v was %v", expected, seconds)
}
}
func TestUserBalanceRedisStore(t *testing.T) {
func TestUserBalanceStorageStore(t *testing.T) {
b1 := &MinuteBucket{Seconds: 10, Weight: 10, Price: 0.01, DestinationId: "NAT"}
b2 := &MinuteBucket{Seconds: 100, Weight: 20, Price: 0.0, DestinationId: "RET"}
rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, BalanceMap: map[string]float64{CREDIT + OUTBOUND: 21}}
storageGetter.SetUserBalance(rifsBalance)
result, _ := storageGetter.GetUserBalance(rifsBalance.Id)
if (rifsBalance.Id != result.Id) ||
result, err := storageGetter.GetUserBalance(rifsBalance.Id)
if err != nil || rifsBalance.Id != result.Id ||
len(rifsBalance.MinuteBuckets) < 2 || len(result.MinuteBuckets) < 2 ||
!(rifsBalance.MinuteBuckets[0].Equal(result.MinuteBuckets[0])) ||
!(rifsBalance.MinuteBuckets[1].Equal(result.MinuteBuckets[1])) ||
(rifsBalance.BalanceMap[CREDIT+OUTBOUND] != result.BalanceMap[CREDIT+OUTBOUND]) {
@@ -366,6 +368,7 @@ func TestUserBalanceAddMinutBucketEmpty(t *testing.T) {
}
}
/*
func TestUserBalanceExecuteTriggeredActions(t *testing.T) {
ub := &UserBalance{
Id: "TEST_UB",
@@ -383,14 +386,13 @@ func TestUserBalanceExecuteTriggeredActions(t *testing.T) {
if ub.BalanceMap[CREDIT+OUTBOUND] != 110 || ub.MinuteBuckets[0].Seconds != 20 {
t.Error("Error executing triggered actions", ub.BalanceMap[CREDIT+OUTBOUND], ub.MinuteBuckets[0].Seconds)
}
// we can reset them
ub.resetActionTriggers()
ub.countUnits(&Action{BalanceId: CREDIT, Direction: OUTBOUND, Units: 1})
if ub.BalanceMap[CREDIT+OUTBOUND] != 120 || ub.MinuteBuckets[0].Seconds != 30 {
t.Error("Error executing triggered actions", ub.BalanceMap[CREDIT+OUTBOUND], ub.MinuteBuckets[0].Seconds)
}
}
}*/
func TestUserBalanceExecuteTriggeredActionsOrder(t *testing.T) {
ub := &UserBalance{
@@ -463,7 +465,7 @@ func BenchmarkGetSecondForPrefix(b *testing.B) {
}
}
func BenchmarkUserBalanceRedisStoreRestore(b *testing.B) {
func BenchmarkUserBalanceStorageStoreRestore(b *testing.B) {
b1 := &MinuteBucket{Seconds: 10, Weight: 10, Price: 0.01, DestinationId: "NAT"}
b2 := &MinuteBucket{Seconds: 100, Weight: 20, Price: 0.0, DestinationId: "RET"}
rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, BalanceMap: map[string]float64{CREDIT + OUTBOUND: 21}}

View File

@@ -42,7 +42,7 @@ type Event interface {
}
// Returns first non empty string out of vals. Useful to extract defaults
func firstNonEmpty(vals []string) string {
func firstNonEmpty(vals ...string) string {
for _, val := range vals {
if len(val) != 0 {
return val

View File

@@ -27,7 +27,7 @@ func TestFirstNonEmpty(t *testing.T) {
sampleMap := make(map[string]string)
sampleMap["Third"] = "third"
fourthElmnt := "fourth"
winnerElmnt := firstNonEmpty([]string{firstElmnt, sampleMap["second"], sampleMap["Third"], fourthElmnt})
winnerElmnt := firstNonEmpty(firstElmnt, sampleMap["second"], sampleMap["Third"], fourthElmnt)
if winnerElmnt != sampleMap["Third"] {
t.Error("Wrong elemnt returned: ", winnerElmnt)
}

View File

@@ -90,15 +90,15 @@ func (fsev *FSEvent) GetOrigId() string {
return fsev.fields[ORIG_ID]
}
func (fsev *FSEvent) GetSubject() string {
return firstNonEmpty([]string{fsev.fields[SUBJECT], fsev.fields[USERNAME]})
return firstNonEmpty(fsev.fields[SUBJECT], fsev.fields[USERNAME])
}
func (fsev *FSEvent) GetAccount() string {
return firstNonEmpty([]string{fsev.fields[ACCOUNT], fsev.fields[USERNAME]})
return firstNonEmpty(fsev.fields[ACCOUNT], fsev.fields[USERNAME])
}
// Charging destination number
func (fsev *FSEvent) GetDestination() string {
return firstNonEmpty([]string{fsev.fields[DESTINATION], fsev.fields[CALL_DEST_NR]})
return firstNonEmpty(fsev.fields[DESTINATION], fsev.fields[CALL_DEST_NR])
}
// Original dialed destination number, useful in case of unpark
@@ -106,16 +106,16 @@ func (fsev *FSEvent) GetCallDestNr() string {
return fsev.fields[CALL_DEST_NR]
}
func (fsev *FSEvent) GetTOR() string {
return firstNonEmpty([]string{fsev.fields[TOR], cfg.SMDefaultTOR})
return firstNonEmpty(fsev.fields[TOR], cfg.SMDefaultTOR)
}
func (fsev *FSEvent) GetUUID() string {
return fsev.fields[UUID]
}
func (fsev *FSEvent) GetTenant() string {
return firstNonEmpty([]string{fsev.fields[CSTMID], cfg.SMDefaultTenant})
return firstNonEmpty(fsev.fields[CSTMID], cfg.SMDefaultTenant)
}
func (fsev *FSEvent) GetReqType() string {
return firstNonEmpty([]string{fsev.fields[REQTYPE], cfg.SMDefaultReqType})
return firstNonEmpty(fsev.fields[REQTYPE], cfg.SMDefaultReqType)
}
func (fsev *FSEvent) MissingParameter() bool {
return strings.TrimSpace(fsev.GetDirection()) == "" ||

View File

@@ -51,6 +51,66 @@ var (
"Session-Since-Startup": "122",
"Idle-CPU": "100.000000"
`
conf_data = []byte(`
### Test data, not for production usage
[global]
datadb_type = test #
datadb_host = test # The host to connect to. Values that start with / are for UNIX domain sockets.
datadb_port = test # The port to bind to.
datadb_name = test # The name of the database to connect to.
datadb_user = test # The user to sign in as.
datadb_passwd = test # The user's password.root
logdb_type = test #
logdb_host = test # The host to connect to. Values that start with / are for UNIX domain sockets.
logdb_port = test # The port to bind to.
logdb_name = test # The name of the database to connect to.
logdb_user = test # The user to sign in as.
logdb_passwd = test # The user's password.root
[balancer]
enabled = true # Start balancer server
listen = test # Balancer listen interface
rpc_encoding = test # use JSON for RPC encoding
[rater]
enabled = true
listen = test # listening address host:port, internal for internal communication only
balancer = test # if defined it will register to balancer as worker
rpc_encoding = test # use JSON for RPC encoding
[mediator]
enabled = true
cdr_in_dir = test # Freeswitch Master CSV CDR path.
cdr_out_dir = test
rater = test #address where to access rater. Can be internal, direct rater address or the address of a balancer
rpc_encoding = test # use JSON for RPC encoding
skipdb = true
pseudoprepaid = true
[scheduler]
enabled = true
[session_manager]
enabled = true
switch_type = test
rater = test #address where to access rater. Can be internal, direct rater address or the address of a balancer
debit_interval = 11
rpc_encoding = test # use JSON for RPC encoding
[freeswitch]
server = test # freeswitch address host:port
passwd = test # freeswitch address host:port
direction_index = test
tor_index = test
tenant_index = test
subject_index = test
account_index = test
destination_index = test
time_start_index = test
duration_index = test
uuid_index = test
`)
)
/*func TestSessionDurationSingle(t *testing.T) {
@@ -65,9 +125,8 @@ var (
}*/
func TestSessionNilSession(t *testing.T) {
cfgTestPath := "../config/test_data.txt"
var errCfg error
cfg, errCfg = config.NewCGRConfig(&cfgTestPath) // Needed here to avoid nil on cfg variable
cfg, errCfg = config.NewCGRConfigBytes(conf_data) // Needed here to avoid nil on cfg variable
if errCfg != nil {
t.Errorf("Cannot get configuration %v", errCfg)
}

View File

@@ -1,5 +1,14 @@
#! /usr/bin/env sh
go test -i github.com/cgrates/cgrates/rater
go test -i github.com/cgrates/cgrates/sessionmanager
go test -i github.com/cgrates/cgrates/config
go test -i github.com/cgrates/cgrates/cmd/cgr-rater
go test -i github.com/cgrates/cgrates/inotify
go test -i github.com/cgrates/cgrates/mediator
go test -i github.com/cgrates/fsock
go test github.com/cgrates/cgrates/rater
ts=$?
go test github.com/cgrates/cgrates/sessionmanager

11
update_external_libs.sh Executable file
View File

@@ -0,0 +1,11 @@
#!/usr/bin/env sh
go get -v -u github.com/fzzy/radix/redis
go get -v -u code.google.com/p/goconf/conf
go get -v -u github.com/bmizerany/pq
go get -v -u github.com/vmihailenco/msgpack
go get -v -u github.com/ugorji/go-msgpack
go get -v -u labix.org/v2/mgo
go get -v -u github.com/cgrates/fsock
go get -u -v github.com/go-sql-driver/mysql
go get -u -v github.com/garyburd/redigo/redis
go get -u -v menteslibres.net/gosexy/redis