tested cgr-loader

This commit is contained in:
Radu Ioan Fericean
2012-07-11 18:14:53 +03:00
parent b4bbec8e76
commit 10fb664a8e
8 changed files with 338 additions and 154 deletions

View File

@@ -19,10 +19,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
package main
import (
"encoding/csv"
"github.com/cgrates/cgrates/timespans"
"log"
"os"
"fmt"
"strconv"
)
@@ -34,16 +32,14 @@ var (
accountActions []*timespans.UserBalance
)
func loadActions() {
fp, err := os.Open(*actionsFn)
func (csvr *CSVReader) loadActions(fn string) {
csvReader, fp, err := csvr.readerFunc(fn)
if err != nil {
log.Printf("Could not open actions file: %v", err)
return
}
defer fp.Close()
csvReader := csv.NewReader(fp)
csvReader.Comma = sep
csvReader.TrailingComma = true
if fp != nil {
defer fp.Close()
}
for record, err := csvReader.Read(); err == nil; record, err = csvReader.Read() {
tag := record[0]
if tag == "Tag" {
@@ -104,16 +100,14 @@ func loadActions() {
log.Print(actions)
}
func loadActionTimings() {
fp, err := os.Open(*actiontimingsFn)
func (csvr *CSVReader) loadActionTimings(fn string) {
csvReader, fp, err := csvr.readerFunc(fn)
if err != nil {
log.Printf("Could not open actions timings file: %v", err)
return
}
defer fp.Close()
csvReader := csv.NewReader(fp)
csvReader.Comma = sep
csvReader.TrailingComma = true
if fp != nil {
defer fp.Close()
}
for record, err := csvReader.Read(); err == nil; record, err = csvReader.Read() {
tag := record[0]
if tag == "Tag" {
@@ -151,16 +145,14 @@ func loadActionTimings() {
log.Print(actionsTimings)
}
func loadActionTriggers() {
fp, err := os.Open(*actiontriggersFn)
func (csvr *CSVReader) loadActionTriggers(fn string) {
csvReader, fp, err := csvr.readerFunc(fn)
if err != nil {
log.Printf("Could not open destination balance actions file: %v", err)
return
}
defer fp.Close()
csvReader := csv.NewReader(fp)
csvReader.Comma = sep
csvReader.TrailingComma = true
if fp != nil {
defer fp.Close()
}
for record, err := csvReader.Read(); err == nil; record, err = csvReader.Read() {
tag := record[0]
if tag == "Tag" {
@@ -190,16 +182,14 @@ func loadActionTriggers() {
log.Print(actionsTriggers)
}
func loadAccountActions() {
fp, err := os.Open(*accountactionsFn)
func (csvr *CSVReader) loadAccountActions(fn string) {
csvReader, fp, err := csvr.readerFunc(fn)
if err != nil {
log.Printf("Could not open account actions file: %v", err)
return
}
defer fp.Close()
csvReader := csv.NewReader(fp)
csvReader.Comma = sep
csvReader.TrailingComma = true
if fp != nil {
defer fp.Close()
}
for record, err := csvReader.Read(); err == nil; record, err = csvReader.Read() {
if record[0] == "Tenant" {
continue

View File

@@ -22,12 +22,15 @@ import (
"flag"
"github.com/cgrates/cgrates/timespans"
"log"
"encoding/csv"
"strings"
"os"
)
var (
separator = flag.String("separator", ",", "Default field separator")
redisserver = flag.String("redisserver", "127.0.0.1:6379", "redis server address (tcp:127.0.0.1:6379)")
redisdb = flag.Int("rdb", 10, "redis database number (10)")
redissrv = flag.String("redissrv", "127.0.0.1:6379", "redis server address (tcp:127.0.0.1:6379)")
redisdb = flag.Int("redisdb", 10, "redis database number (10)")
redispass = flag.String("pass", "", "redis database password")
flush = flag.Bool("flush", false, "Flush the database before importing")
monthsFn = flag.String("month", "Months.csv", "Months file")
@@ -46,7 +49,7 @@ var (
)
func writeToDatabase() {
storage, err := timespans.NewRedisStorage(*redisserver, *redisdb)
storage, err := timespans.NewRedisStorage(*redissrv, *redisdb)
if err != nil {
log.Fatalf("Could not open database connection: %v", err)
}
@@ -82,17 +85,40 @@ func writeToDatabase() {
}
}
func openFileCSVReader(fn string) (csvReader *csv.Reader, fp *os.File, err error) {
fp, err = os.Open(fn)
if err != nil {
return
}
csvReader = csv.NewReader(fp)
csvReader.Comma = sep
csvReader.TrailingComma = true
return
}
func openStringCSVReader(data string) (csvReader *csv.Reader, fp *os.File, err error) {
csvReader = csv.NewReader(strings.NewReader(data))
csvReader.Comma = ','
csvReader.TrailingComma = true
return
}
type CSVReader struct {
readerFunc func(string) (*csv.Reader, *os.File, error)
}
func main() {
flag.Parse()
sep = []rune(*separator)[0]
loadDestinations()
loadRates()
loadTimings()
loadRateTimings()
loadRatingProfiles()
loadActions()
loadActionTimings()
loadActionTriggers()
loadAccountActions()
csvr := &CSVReader{openFileCSVReader}
csvr.loadDestinations(*destinationsFn)
csvr.loadRates(*ratesFn)
csvr.loadTimings(*timingsFn)
csvr.loadRateTimings(*ratetimingsFn)
csvr.loadRatingProfiles(*ratingprofilesFn)
csvr.loadActions(*actionsFn)
csvr.loadActionTimings(*actiontimingsFn)
csvr.loadActionTriggers(*actiontriggersFn)
csvr.loadAccountActions(*accountactionsFn)
writeToDatabase()
}

View File

@@ -0,0 +1,140 @@
/*
Rating system designed to be used in VoIP Carriers World
Copyright (C) 2012 Radu Ioan Fericean
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 main
import (
"testing"
)
var (
dest = `
Tag,Prefix
NAT,0256
NAT,0257
NAT,0723
RET,0723
RET,0724
`
rts = `
P1,NAT,0,1,1
P2,NAT,0,0.5,1
`
ts = `
WORKDAYS_00,*all,*all,1;2;3;4;5,00:00:00
WORKDAYS_18,*all,*all,1;2;3;4;5,18:00:00
WEEKENDS,*all,*all,6;7,00:00:00
ONE_TIME_RUN,*none,*none,*none,*now
`
rtts = `
EVENING,P1,WORKDAYS_00,10
EVENING,P2,WORKDAYS_18,10
EVENING,P2,WEEKENDS,10
`
rp = `
vdf,0,OUT,rif,,EVENING,2012-01-01T00:00:00Z
vdf,0,OUT,rif,,EVENING,2012-02-28T00:00:00Z
vdf,0,OUT,minu,,EVENING,2012-01-01T00:00:00Z
vdf,0,OUT,minu,,EVENING,2012-02-28T00:00:00Z
`
a = `
MINI,TOPUP,MINUTES,100,NAT,ABSOLUTE,0,10,10
`
atms = `
MORE_MINUTES,MINI,ONE_TIME_RUN,10
`
atrs = `
STANDARD_TRIGGER,MINUTES,10,GERMANY_O2,SOME_1,10
STANDARD_TRIGGER,MINUTES,200,GERMANY,SOME_2,10
`
accs = `
vdf,minitsboy,OUT,MORE_MINUTES,STANDARD_TRIGGER
`
)
func TestDestinations(t *testing.T) {
csvr := &CSVReader{openStringCSVReader}
csvr.loadDestinations(dest)
if len(destinations) != 2 {
t.Error("Failed to load destinations: ", destinations)
}
}
func TestRates(t *testing.T) {
csvr := &CSVReader{openStringCSVReader}
csvr.loadRates(rts)
if len(rates) != 2 {
t.Error("Failed to load rates: ", rates)
}
}
func TestTimimgs(t *testing.T) {
csvr := &CSVReader{openStringCSVReader}
csvr.loadTimings(ts)
if len(timings) != 4 {
t.Error("Failed to load timings: ", timings)
}
}
func TestRateTimings(t *testing.T) {
csvr := &CSVReader{openStringCSVReader}
csvr.loadRateTimings(rtts)
if len(activationPeriods) != 1 {
t.Error("Failed to load rate timings: ", activationPeriods)
}
}
func TestRatingProfiles(t *testing.T) {
csvr := &CSVReader{openStringCSVReader}
csvr.loadRatingProfiles(rp)
if len(ratingProfiles) != 4 {
t.Error("Failed to load rating profiles: ", ratingProfiles)
}
}
func TestActions(t *testing.T) {
csvr := &CSVReader{openStringCSVReader}
csvr.loadActions(a)
if len(actions) != 1 {
t.Error("Failed to load actions: ", actions)
}
}
func TestActionTimings(t *testing.T) {
csvr := &CSVReader{openStringCSVReader}
csvr.loadActionTimings(atms)
if len(actionsTimings) != 1 {
t.Error("Failed to load action timings: ", actionsTimings)
}
}
func TestActionTriggers(t *testing.T) {
csvr := &CSVReader{openStringCSVReader}
csvr.loadActionTriggers(atrs)
if len(actionsTriggers) != 1 {
t.Error("Failed to load action triggers: ", actionsTriggers)
}
}
func TestAccountActions(t *testing.T) {
csvr := &CSVReader{openStringCSVReader}
csvr.loadAccountActions(accs)
if len(accountActions) != 1 {
t.Error("Failed to load account actions: ", accountActions)
}
}

View File

@@ -19,11 +19,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
package main
import (
"encoding/csv"
"github.com/cgrates/cgrates/timespans"
"log"
"fmt"
"os"
"time"
)
@@ -36,17 +34,16 @@ var (
ratingProfiles = make(map[string]CallDescriptors)
)
func loadDestinations() {
fp, err := os.Open(*destinationsFn)
func (csvr *CSVReader) loadDestinations(fn string) {
csvReader, fp, err := csvr.readerFunc(fn)
if err != nil {
log.Printf("Could not open destinations file: %v", err)
return
}
defer fp.Close()
csvReader := csv.NewReader(fp)
csvReader.Comma = sep
csvReader.TrailingComma = true
if fp != nil {
defer fp.Close()
}
for record, err := csvReader.Read(); err == nil; record, err = csvReader.Read() {
tag := record[0]
if tag == "Tag" {
// skip header line
@@ -63,20 +60,18 @@ func loadDestinations() {
dest = &timespans.Destination{Id: tag}
destinations = append(destinations, dest)
}
dest.Prefixes = append(dest.Prefixes, record[1:]...)
dest.Prefixes = append(dest.Prefixes, record[1])
}
}
func loadRates() {
fp, err := os.Open(*ratesFn)
func (csvr *CSVReader) loadRates(fn string) {
csvReader, fp, err := csvr.readerFunc(fn)
if err != nil {
log.Printf("Could not open rates timing file: %v", err)
return
}
defer fp.Close()
csvReader := csv.NewReader(fp)
csvReader.Comma = sep
csvReader.TrailingComma = true
if fp != nil {
defer fp.Close()
}
for record, err := csvReader.Read(); err == nil; record, err = csvReader.Read() {
tag := record[0]
if tag == "Tag" {
@@ -91,16 +86,14 @@ func loadRates() {
}
}
func loadTimings() {
fp, err := os.Open(*timingsFn)
func (csvr *CSVReader) loadTimings(fn string) {
csvReader, fp, err := csvr.readerFunc(fn)
if err != nil {
log.Printf("Could not open timings file: %v", err)
return
}
defer fp.Close()
csvReader := csv.NewReader(fp)
csvReader.Comma = sep
csvReader.TrailingComma = true
if fp != nil {
defer fp.Close()
}
for record, err := csvReader.Read(); err == nil; record, err = csvReader.Read() {
tag := record[0]
if tag == "Tag" {
@@ -113,16 +106,14 @@ func loadTimings() {
}
}
func loadRateTimings() {
fp, err := os.Open(*ratetimingsFn)
func (csvr *CSVReader) loadRateTimings(fn string) {
csvReader, fp, err := csvr.readerFunc(fn)
if err != nil {
log.Printf("Could not open rates timings file: %v", err)
return
}
defer fp.Close()
csvReader := csv.NewReader(fp)
csvReader.Comma = sep
csvReader.TrailingComma = true
if fp != nil {
defer fp.Close()
}
for record, err := csvReader.Read(); err == nil; record, err = csvReader.Read() {
tag := record[0]
if tag == "Tag" {
@@ -153,16 +144,14 @@ func loadRateTimings() {
}
}
func loadRatingProfiles() {
fp, err := os.Open(*ratingprofilesFn)
func (csvr *CSVReader) loadRatingProfiles(fn string) {
csvReader, fp, err := csvr.readerFunc(fn)
if err != nil {
log.Printf("Could not open destinations rates file: %v", err)
return
}
defer fp.Close()
csvReader := csv.NewReader(fp)
csvReader.Comma = sep
csvReader.TrailingComma = true
if fp != nil {
defer fp.Close()
}
for record, err := csvReader.Read(); err == nil; record, err = csvReader.Read() {
tag := record[0]
if tag == "Tenant" {
@@ -232,7 +221,7 @@ func loadRatingProfiles() {
log.Printf("Could not open destinations rates file: %v", err)
return
}
defer fp.Close()
if fp != nil {defer fp.Close()}
csvReader := csv.NewReader(fp)
csvReader.Comma = sep
csvReader.TrailingComma = true

View File

@@ -1,5 +1,47 @@
Api Calls
========
The general API usage of the CGRateS involves creating a CallDescriptor structure sending it to the balancer via JSON/GOB RPC and getting a response from the balancer in form of a CallCost structure or a numeric value for requested information.
CallDescriptor structure
------------------------
- TOR int
- CstmId, Subject, DestinationPrefix string
- TimeStart, TimeEnd time.Time
- Amount float64
TOR
Type Of Record, used to differentiate between various type of records
CstmId
Customer Identification used for multi tenant databases
Subject
Subject for this query
DestinationPrefix
Destination prefix to be matched
TimeStart, TimeEnd
The start end end of the call in question
Amount
The amount requested in various API calls (e.g. DebitSMS amount)
CallCost structure
------------------
- TOR int
- CstmId, Subject, DestinationPrefix string
- Cost, ConnectFee float64
- Timespans []*TimeSpan
TOR
Type Of Record, used to differentiate between various type of records (for query identification and confirmation)
CstmId
Customer Identification used for multi tenant databases (for query identification and confirmation)
Subject
Subject for this query (for query identification and confirmation)
DestinationPrefix
Destination prefix to be matched (for query identification and confirmation)
Cost
The requested cost
ConnectFee
The requested connection cost
Timespans
The timespans in witch the initial TimeStart-TimeEnd was split in for cost determination with all pricing and cost information attached.
As stated before the balancer (or the rater directly) can be accesed via json rpc.
The smallest python snippet to acces the CGRateS balancer is this:

View File

@@ -1,3 +1,4 @@
.. _`data-importing`:
Data importing
=============

View File

@@ -1,58 +1,18 @@
Tutorial
========
The general usage of the CGRateS involves creating a CallDescriptor structure sending it to the balancer via JSON RPC and getting a response from the balancer inf form of a CallCost structure or a numeric value for requested information.
The general steps to get up and running with CGRateS are:
#. Create JSON files containing rates, budgets, tariff plans and destinations, see :ref:`data-importing`.
#. Create CSV files containing the initial data for CGRateS, see :ref:`data-importing`.
#. Load the data in the databases using the loader tool.
#. Start the balancer, see :ref:`running`.
#. Start the balancer or rater and connect it to the call switch, see :ref:`running`.
#. Start one ore more raters.
#. Make API calls to the balancer/rater.
CallDescriptor structure
------------------------
- TOR int
- CstmId, Subject, DestinationPrefix string
- TimeStart, TimeEnd time.Time
- Amount float64
TOR
Type Of Record, used to differentiate between various type of records
CstmId
Customer Identification used for multi tenant databases
Subject
Subject for this query
DestinationPrefix
Destination prefix to be matched
TimeStart, TimeEnd
The start end end of the call in question
Amount
The amount requested in various API calls (e.g. DebitSMS amount)
CallCost structure
------------------
- TOR int
- CstmId, Subject, DestinationPrefix string
- Cost, ConnectFee float64
- Timespans []*TimeSpan
TOR
Type Of Record, used to differentiate between various type of records (for query identification and confirmation)
CstmId
Customer Identification used for multi tenant databases (for query identification and confirmation)
Subject
Subject for this query (for query identification and confirmation)
DestinationPrefix
Destination prefix to be matched (for query identification and confirmation)
Cost
The requested cost
ConnectFee
The requested connection cost
Timespans
The timespans in witch the initial TimeStart-TimeEnd was split in for cost determination with all pricing and cost information attached.
#. Make API calls to the balancer/rater or just let the session manager do the work.
Instalation
-----------
**Using packages**
**Using source**
After the go environment is installed_ and setup_ just issue the following commands:
@@ -62,8 +22,6 @@ After the go environment is installed_ and setup_ just issue the following comma
This will install the sources and compile all available tools
After that navigate
.. _installed: http://golang.org/doc/install
.. _setup: http://golang.org/doc/code.html
@@ -79,9 +37,13 @@ cgr-balancer
rif@grace:~$ cgr-balancer --help
Usage of cgr-balancer:
-httpapiaddr="127.0.0.1:8000": HTTP API server address (localhost:2002)
-jsonrpcaddr="127.0.0.1:2001": JSON RPC server address (localhost:2001)
-rateraddr="127.0.0.1:2000": Rater server address (localhost:2000)
-freeswitchpass="ClueCon": freeswitch address host:port
-freeswitchsrv="localhost:8021": freeswitch address host:port
-httpapiaddr="127.0.0.1:8000": Http API server address (localhost:2002)
-json=false: use JSON for RPC encoding
-jsonrpcaddr="127.0.0.1:2001": Json RPC server address (localhost:2001)
-rateraddr="127.0.0.1:2000": Rater server address (localhost:2000)
cgr-rater
The cgr-rater can be provided with the balancer server address and can be configured to listen to a specific interface and port.
@@ -90,8 +52,14 @@ cgr-rater
rif@grace:~$ cgr-rater --help
Usage of cgr-rater:
-balancer="127.0.0.1:2000": balancer address host:port
-json=false: use json for rpc encoding
-freeswitch=false: connect to freeswitch server
-freeswitchpass="ClueCon": freeswitch address host:port
-freeswitchsrv="localhost:8021": freeswitch address host:port
-json=false: use JSON for RPC encoding
-listen="127.0.0.1:1234": listening address host:port
-redisdb=10: redis database number
-redissrv="127.0.0.1:6379": redis address host:port
-standalone=false: start standalone server (no balancer)
cgr-console
The cgr-console is a command line tool used to access the balancer (or the rater directly) to call all the API methods offered by CGRateS.
@@ -108,20 +76,6 @@ cgr-console
-tor=0: Type of record
-ts="2012-02-09T00:00:00Z": Time start
rif@grace:~$ cgr-cgrates
List of commands:
getcost
getmaxsessiontime
debitbalance
debitsms
debitseconds
addvolumediscountseconds
resetvolumediscountseconds
addrecievedcallseconds
resetuserbudget
status
cgr-loader
The loader is the most configurable tool because it has options for each of the three supported databases (kyoto, redis and mongodb).
Apart from that multi-database options it is quite easy to be used.
@@ -133,17 +87,50 @@ cgr-loader
rif@grace:~$ cgr-loader --help
Usage of cgr-loader:
-apfile="ap.json": Activation Periods containing intervals file
-destfile="dest.json": Destinations file
-kyotofile="storage.kch": kyoto storage file (storage.kch)
-mdb="test": mongo database name (test)
-mongoserver="127.0.0.1:27017": mongo server address (127.0.0.1:27017)
-accountactions="AccountActions.csv": Account actions file
-actions="Actions.csv": Actions file
-actiontimings="ActionTimings.csv": Actions timings file
-actiontriggers="ActionTriggers.csv": Actions triggers file
-destinations="Destinations.csv": Destinations file
-flush=false: Flush the database before importing
-month="Months.csv": Months file
-monthdays="MonthDays.csv": Month days file
-pass="": redis database password
-rates="Rates.csv": Rates file
-ratetimings="RateTimings.csv": Rates timings file
-ratingprofiles="RatingProfiles.csv": Rating profiles file
-rdb=10: redis database number (10)
-redisserver="tcp:127.0.0.1:6379": redis server address (tcp:127.0.0.1:6379)
-storage="all": kyoto|redis|mongo
-tpfile="tp.json": Tariff plans file
-ubfile="ub.json": User budgets file
-redisserver="127.0.0.1:6379": redis server address (tcp:127.0.0.1:6379)
-separator=",": Default field separator
-timings="Timings.csv": Timings file
-weekdays="WeekDays.csv": Week days file
rif@grace:~$ cgr-balancer --help
Usage of cgr-balancer:
-freeswitchpass="ClueCon": freeswitch address host:port
-freeswitchsrv="localhost:8021": freeswitch address host:port
-httpapiaddr="127.0.0.1:8000": Http API server address (localhost:2002)
-json=false: use JSON for RPC encoding
-jsonrpcaddr="127.0.0.1:2001": Json RPC server address (localhost:2001)
-rateraddr="127.0.0.1:2000": Rater server address (localhost:2000)
rif@grace:~$ cgr-sessionmanager --help
Usage of cgr-sessionmanager:
-balancer="127.0.0.1:2000": balancer address host:port
-freeswitchpass="ClueCon": freeswitch address host:port
-freeswitchsrv="localhost:8021": freeswitch address host:port
-json=false: use JSON for RPC encoding
-redisdb=10: redis database number
-redissrv="127.0.0.1:6379": redis address host:port
-standalone=false: run standalone (run as a rater)
rif@grace:~$ cgr-mediator --help
Usage of cgr-mediator:
-dbname="cgrates": The name of the database to connect to.
-freeswitchcdr="Master.csv": Freeswitch Master CSV CDR file.
-host="localhost": The host to connect to. Values that start with / are for unix domain sockets.
-password="": The user's password.
-port="5432": The port to bind to.
-resultfile="out.csv": Generated file containing CDR and price info.
-user="": The user to sign in as.

View File

@@ -64,6 +64,15 @@ func (d *Destination) containsPrefix(prefix string) (bool, int) {
return false, 0
}
func (d *Destination) String() (result string) {
result = d.Id + ": "
for _, p := range d.Prefixes {
result += p + ", "
}
result = strings.TrimRight(result, ", ")
return result
}
func (d *Destination) store() (result string) {
for _, p := range d.Prefixes {
result += p + ","