mirror of
https://github.com/cgrates/cgrates.git
synced 2026-02-12 02:26:26 +05:00
Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5f1c23e4ee | ||
|
|
dc6c63df36 | ||
|
|
28bca0efcc | ||
|
|
6e2b741028 | ||
|
|
f31c5b6476 | ||
|
|
fe4d82b30a | ||
|
|
96bba4eb6f | ||
|
|
7e7ab83bd8 | ||
|
|
6776525c97 | ||
|
|
cd32402449 | ||
|
|
48b24d6cff | ||
|
|
839b3b8615 | ||
|
|
72d2d8182c | ||
|
|
a7fb55792f | ||
|
|
e8edee00c1 | ||
|
|
5829ab6386 | ||
|
|
8473840446 | ||
|
|
25cbad0a90 | ||
|
|
e5ad2bd09b | ||
|
|
373d97e91d | ||
|
|
4fb47a0a7e | ||
|
|
988ab87192 | ||
|
|
2c961510ce | ||
|
|
146a4d6994 | ||
|
|
5183307bd9 | ||
|
|
c43d232857 |
@@ -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
146
cdrs/cdrs.go
Normal 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)
|
||||
}
|
||||
@@ -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:
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
121
config/config.go
121
config/config.go
@@ -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
|
||||
|
||||
}
|
||||
|
||||
@@ -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>.
|
||||
|
||||
31
data/storage/mysql/create_cdrs_tables.sql
Normal file
31
data/storage/mysql/create_cdrs_tables.sql
Normal 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`)
|
||||
);
|
||||
@@ -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::
|
||||
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -20,6 +20,7 @@ package rater
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
//"log"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
/*
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ package rater
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/cgrates/cgrates/cache2go"
|
||||
//"log"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
219
rater/storage_gosexy.go
Normal file
219
rater/storage_gosexy.go
Normal 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
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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
222
rater/storage_mysql.go
Normal 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, ×pansJson)
|
||||
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, ×pansJson)
|
||||
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 }
|
||||
@@ -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
216
rater/storage_redigo.go
Normal 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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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}}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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()) == "" ||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
9
test.sh
9
test.sh
@@ -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
11
update_external_libs.sh
Executable 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
|
||||
Reference in New Issue
Block a user