From 30857b1fa8328ebff8378ecb0c693f9073c5df0d Mon Sep 17 00:00:00 2001 From: DanB Date: Mon, 3 Aug 2015 21:20:40 +0200 Subject: [PATCH 01/20] Version changed to rc7 --- packages/debian/changelog | 6 ++++++ packages/debian/control | 2 +- utils/consts.go | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/debian/changelog b/packages/debian/changelog index 4d1301026..304fea7e3 100644 --- a/packages/debian/changelog +++ b/packages/debian/changelog @@ -1,3 +1,9 @@ +cgrates (0.9.1~rc7) UNRELEASED; urgency=low + + * RC7. + + -- DanB Wednesday, 3 August 2015 14:04:00 -0600 + cgrates (0.9.1~rc6) UNRELEASED; urgency=low * RC6. diff --git a/packages/debian/control b/packages/debian/control index c35a86a2f..702abca7a 100644 --- a/packages/debian/control +++ b/packages/debian/control @@ -9,6 +9,6 @@ Homepage: http://cgrates.org Package: cgrates Architecture: amd64 Suggests: git, redis-server, mysql-server -Version: 0.9.1-rc6 +Version: 0.9.1-rc7 Description: Carrier Grade Real-time Charging System CGRateS is a very fast and easy scalable real-time charging system for Telecom environments. diff --git a/utils/consts.go b/utils/consts.go index ac075bdb1..bc3abaabf 100644 --- a/utils/consts.go +++ b/utils/consts.go @@ -26,7 +26,7 @@ var ( ) const ( - VERSION = "0.9.1~rc6" + VERSION = "0.9.1~rc7" POSTGRES = "postgres" MYSQL = "mysql" MONGO = "mongo" From e44dede8f9f3682173ca4956bcb2dac9fc71d0f4 Mon Sep 17 00:00:00 2001 From: DanB Date: Tue, 4 Aug 2015 00:37:57 +0200 Subject: [PATCH 02/20] Doc updates --- docs/cgrates_cfg.rst | 176 ---------------------------------------- docs/cgrates_json.rst | 10 +++ docs/configuration.rst | 6 +- docs/installation.rst | 16 +++- docs/tut_freeswitch.rst | 2 +- 5 files changed, 27 insertions(+), 183 deletions(-) delete mode 100644 docs/cgrates_cfg.rst create mode 100644 docs/cgrates_json.rst diff --git a/docs/cgrates_cfg.rst b/docs/cgrates_cfg.rst deleted file mode 100644 index 4a3343995..000000000 --- a/docs/cgrates_cfg.rst +++ /dev/null @@ -1,176 +0,0 @@ -cgr-engine configuration file -============================= - -Organized into configuration sections. All configuration options come with defaults and we have tried our best to choose the best ones for a minimum of efforts necessary when running. - -Bellow is the default configuration file which comes hardcoded into cgr-engine, most of them being explained and exemplified there. - -:: - - [global] - # ratingdb_type = redis # Rating subsystem database: . - # ratingdb_host = 127.0.0.1 # Rating subsystem database host address. - # ratingdb_port = 6379 # Rating subsystem port to reach the database. - # ratingdb_name = 10 # Rating subsystem database name to connect to. - # ratingdb_user = # Rating subsystem username to use when connecting to database. - # ratingdb_passwd = # Rating subsystem password to use when connecting to database. - # accountdb_type = redis # Accounting subsystem database: . - # accountdb_host = 127.0.0.1 # Accounting subsystem database host address. - # accountdb_port = 6379 # Accounting subsystem port to reach the database. - # accountdb_name = 11 # Accounting subsystem database name to connect to. - # accountdb_user = # Accounting subsystem username to use when connecting to database. - # accountdb_passwd = # Accounting subsystem password to use when connecting to database. - # stordb_type = mysql # Stor database type to use: - # stordb_host = 127.0.0.1 # The host to connect to. Values that start with / are for UNIX domain sockets. - # stordb_port = 3306 # The port to reach the logdb. - # stordb_name = cgrates # The name of the log database to connect to. - # stordb_user = cgrates # Username to use when connecting to stordb. - # stordb_passwd = CGRateS.org # Password to use when connecting to stordb. - # dbdata_encoding = msgpack # The encoding used to store object data in strings: - # rpc_json_listen = 127.0.0.1:2012 # RPC JSON listening address - # rpc_gob_listen = 127.0.0.1:2013 # RPC GOB listening address - # http_listen = 127.0.0.1:2080 # HTTP listening address - # default_reqtype = rated # Default request type to consider when missing from requests: <""|prepaid|postpaid|pseudoprepaid|rated>. - # default_category = call # Default Type of Record to consider when missing from requests. - # default_tenant = cgrates.org # Default Tenant to consider when missing from requests. - # default_subject = cgrates # Default rating Subject to consider when missing from requests. - # rounding_decimals = 10 # System level precision for floats - # http_skip_tls_veify = false # If enabled Http Client will accept any TLS certificate - # xmlcfg_path = # Path towards additional config defined in xml file - - [balancer] - # enabled = false # Start Balancer service: . - - [rater] - # enabled = false # Enable RaterCDRSExportPath service: . - # balancer = # Register to Balancer as worker: <""|internal|127.0.0.1:2013>. - - [scheduler] - # enabled = false # Starts Scheduler service: . - - [cdrs] - # enabled = false # Start the CDR Server service: . - # extra_fields = # Extra fields to store in CDRs for non-generic CDRs - # mediator = # Address where to reach the Mediator. Empty for disabling mediation. <""|internal> - # cdrstats = # Address where to reach the cdrstats service: - # store_disable = false # When true, CDRs will not longer be saved in stordb, useful for cdrstats only scenario - - [cdre] - # cdr_format = csv # Exported CDRs format - # data_usage_multiply_factor = 0.0 # Multiply data usage before export (eg: convert from KBytes to Bytes) - # cost_multiply_factor = 0.0 # Multiply cost before export (0.0 to disable), eg: add VAT - # cost_rounding_decimals = -1 # Rounding decimals for Cost values. -1 to disable rounding - # cost_shift_digits = 0 # Shift digits in the cost on export (eg: convert from EUR to cents) - # mask_destination_id = # Destination id containing called addresses to be masked on export - # mask_length = 0 # Length of the destination suffix to be masked - # export_dir = /var/log/cgrates/cdre # Path where the exported CDRs will be placed - # export_template = cgrid,mediation_runid,tor,accid,reqtype,direction,tenant,category,account,subject,destination,setup_time,answer_time,usage,cost - # Exported fields template <""|fld1,fld2|*xml:instance_name> - [cdrc] - # enabled = false # Enable CDR client functionality - # cdrs = internal # Address where to reach CDR server. - # run_delay = 0 # Sleep interval in seconds between consecutive runs, 0 to use automation via inotify - # cdr_type = csv # CDR file format . - # csv_separator = , # Separator used in case of csv files. One character only supported and needs to be right after equal sign - # cdr_in_dir = /var/log/cgrates/cdrc/in # Absolute path towards the directory where the CDRs are stored. - # cdr_out_dir = /var/log/cgrates/cdrc/out # Absolute path towards the directory where processed CDRs will be moved. - # cdr_source_id = csv # Free form field, tag identifying the source of the CDRs within CGRS database. - # tor_field = 2 # TypeOfRecord field identifier. Use index number in case of .csv cdrs. - # accid_field = 3 # Accounting id field identifier. Use index number in case of .csv cdrs. - # reqtype_field = 4 # Request type field identifier. Use index number in case of .csv cdrs. - # direction_field = 5 # Direction field identifier. Use index numbers in case of .csv cdrs. - # tenant_field = 6 # Tenant field identifier. Use index numbers in case of .csv cdrs. - # category_field = 7 # Type of Record field identifier. Use index numbers in case of .csv cdrs. - # account_field = 8 # Account field identifier. Use index numbers in case of .csv cdrs. - # subject_field = 9 # Subject field identifier. Use index numbers in case of .csv CDRs. - # destination_field = 10 # Destination field identifier. Use index numbers in case of .csv cdrs. - # setup_time_field = 11 # Setup time field identifier. Use index numbers in case of .csv cdrs. - # answer_time_field = 12 # Answer time field identifier. Use index numbers in case of .csv cdrs. - # usage_field = 13 # Usage field identifier. Use index numbers in case of .csv cdrs. - # extra_fields = # Extra fields identifiers. For .csv, format: :[...,:] - - [mediator] - # enabled = false # Starts Mediator service: . - # reconnects = 3 # Number of reconnects to rater/cdrs before giving up. - # rater = internal # Address where to reach the Rater: - # cdrstats = internal # Address where to reach the cdrstats service: - # store_disable = false # When true, CDRs will not longer be saved in stordb, useful for cdrstats only scenario - - - [cdrstats] - # enabled = false # Starts the cdrstats service: - # queue_length = 50 # Number of items in the stats buffer - # time_window = 1h # Will only keep the CDRs who's call setup time is not older than time.Now()-TimeWindow - # metrics = ASR, ACD, ACC # Stat metric ids to build - # setup_interval = # Filter on CDR SetupTime - # tors = # Filter on CDR TOR fields - # cdr_hosts= # Filter on CDR CdrHost fields - # cdr_sources = # Filter on CDR CdrSource fields - # req_types = # Filter on CDR ReqType fields - # directions = # Filter on CDR Direction fields - # tenants = # Filter on CDR Tenant fields - # categories = # Filter on CDR Category fields - # accounts = # Filter on CDR Account fields - # subjects = # Filter on CDR Subject fields - # destination_prefixes = # Filter on CDR Destination prefixes - # usage_interval = # Filter on CDR Usage - # mediation_run_ids = # Filter on CDR MediationRunId fields - # rated_accounts = # Filter on CDR RatedAccount fields - # rated_subjects = # Filter on CDR RatedSubject fields - # cost_intervals = # Filter on CDR Cost - - [session_manager] - # enabled = false # Starts SessionManager service: - # switch_type = freeswitch # Defines the type of switch behind: - # rater = internal # Address where to reach the Rater <""|internal|127.0.0.1:2013> - # cdrs = # Address where to reach CDR Server, empty to disable CDR capturing <""|internal|127.0.0.1:2013> - # reconnects = 3 # Number of reconnects to rater/cdrs before giving up. - # debit_interval = 10 # Interval to perform debits on. - # min_call_duration = 0s # Only authorize calls with allowed duration bigger than this - # max_call_duration = 3h # Maximum call duration a prepaid call can last - - [freeswitch] - # server = 127.0.0.1:8021 # Adress where to connect to FreeSWITCH socket. - # passwd = ClueCon # FreeSWITCH socket password. - # reconnects = 5 # Number of attempts on connect failure. - # min_dur_low_balance = 5s # Threshold which will trigger low balance warnings for prepaid calls (needs to be lower than debit_interval) - # low_balance_ann_file = # File to be played when low balance is reached for prepaid calls - # empty_balance_context = # If defined, prepaid calls will be transfered to this context on empty balance - # empty_balance_ann_file = # File to be played before disconnecting prepaid calls on empty balance (applies only if no context defined) - # cdr_extra_fields = # Extra fields to store in CDRs in case of processing them - - [opensips] - # listen_udp = 127.0.0.1:2020 # Address where to listen for datagram events coming from OpenSIPS - # mi_addr = 127.0.0.1:8020 # Adress where to reach OpenSIPS mi_datagram module - # events_subscribe_interval = 60s # Automatic events subscription to OpenSIPS, 0 to disable it - # reconnects = 3 # Number of attempts on connect failure. - - [derived_charging] - # run_ids = # Identifiers of additional sessions control. - # run_filters = # List of cdr field filters for each run. - # reqtype_fields = # Name of request type fields to be used during additional sessions control <""|*default|field_name>. - # direction_fields = # Name of direction fields to be used during additional sessions control <""|*default|field_name>. - # tenant_fields = # Name of tenant fields to be used during additional sessions control <""|*default|field_name>. - # category_fields = # Name of tor fields to be used during additional sessions control <""|*default|field_name>. - # account_fields = # Name of account fields to be used during additional sessions control <""|*default|field_name>. - # subject_fields = # Name of fields to be used during additional sessions control <""|*default|field_name>. - # destination_fields = # Name of destination fields to be used during additional sessions control <""|*default|field_name>. - # setup_time_fields = # Name of setup_time fields to be used during additional sessions control <""|*default|field_name>. - # answer_time_fields = # Name of answer_time fields to be used during additional sessions control <""|*default|field_name>. - # usage_fields = # Name of usage fields to be used during additional sessions control <""|*default|field_name>. - # combined_chargers = true # Combine accounts specific derived_chargers with server configured ones . - - [history_server] - # enabled = false # Starts History service: . - # history_dir = /var/log/cgrates/history # Location on disk where to store history files. - # save_interval = 1s # Interval to save changed cache into .git archive - - [history_agent] - # enabled = false # Starts History as a client: . - # server = internal # Address where to reach the master history server: - - [mailer] - # server = localhost # The server to use when sending emails out - # auth_user = cgrates # Authenticate to email server using this user - # auth_passwd = CGRateS.org # Authenticate to email server with this password - # from_address = cgr-mailer@localhost.localdomain # From address used when sending emails out diff --git a/docs/cgrates_json.rst b/docs/cgrates_json.rst new file mode 100644 index 000000000..7b48d6d29 --- /dev/null +++ b/docs/cgrates_json.rst @@ -0,0 +1,10 @@ +cgr-engine configuration file +============================= + +Organized into configuration sections. All configuration options come with defaults and we have tried our best to choose the best ones for a minimum of efforts necessary when running. + +Bellow is the default configuration file which comes hardcoded into cgr-engine, most of them being explained and exemplified there. + +:: + + :file: ../data/conf/cgrates/cgrates.json \ No newline at end of file diff --git a/docs/configuration.rst b/docs/configuration.rst index fa9159c3c..7012fc964 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -3,14 +3,14 @@ Configuration The behaviour of **CGRateS** can be externally influenced by following means: -- Engine configuration file, ussually located at */etc/cgrates/cgrates.cfg* -- Tariff Plans: set of files used to import customer rating and accounting data into CGRateS. +- Engine configuration files, usually located at */etc/cgrates/*. There can be one or multiple file/folder hierarchies behind configuration folder with support for automatic includes. The folders/files will be imported in alphabetical order into final configuration object. +- Tariff Plans: set of files used to import various data used in CGRateS subsystems (eg: Rating, Accounting, LCR, DerivedCharging, etc). - RPC APIs: set of JSON/GOB encoded APIs remotely available for various operational/administrative tasks. .. toctree:: :maxdepth: 2 - cgrates_cfg + cgrates_json tariff_plans diff --git a/docs/installation.rst b/docs/installation.rst index c3f8fdaef..9179ed1d0 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -7,8 +7,8 @@ We recommend using source installs for advanced users familiar with Go programmi 3.1. Using packages ------------------- -3.1.2. Debian Wheezy -~~~~~~~~~~~~~~~~~~~~ +3.1.2. Debian Jessie/Wheezy +~~~~~~~~~~~~~~~~~~~~~~~~~~~ This is for the moment the only packaged and the most recommended to use method to install CGRateS. @@ -22,7 +22,7 @@ On the server you want to install CGRateS, simply execute the following commands apt-get install cgrates Once the installation is completed, one should perform the post-install section in order to have the CGRateS properly set and ready to run. -After post-install actions are performed, CGRateS will be configured in */etc/cgrates/cgrates.cfg* and enabled in */etc/default/cgrates*. +After post-install actions are performed, CGRateS will be configured in */etc/cgrates/cgrates.json* and enabled in */etc/default/cgrates*. 3.2. Using source ----------------- @@ -64,8 +64,18 @@ Once database is installed, CGRateS database needs to be set-up out of provided cd /usr/share/cgrates/storage/mysql/ ./setup_cgr_db.sh root CGRateS.org localhost +- PostgreSQL_ + +Once database is installed, CGRateS database needs to be set-up out of provided scripts (example for the paths set-up by debian package) + + :: + + cd /usr/share/cgrates/storage/postgres/ + ./setup_cgr_db.sh root CGRateS.org localhost + .. _Redis: http://redis.io/ .. _MySQL: http://www.mysql.org/ +.. _PostgreSQL: http://www.postgresql.org/ Git diff --git a/docs/tut_freeswitch.rst b/docs/tut_freeswitch.rst index 0ddd523cf..e03b9c859 100644 --- a/docs/tut_freeswitch.rst +++ b/docs/tut_freeswitch.rst @@ -1,4 +1,4 @@ -FreeSWITCH Integration Tutorials +sFreeSWITCH Integration Tutorials ================================ In these tutorials we exemplify few cases of integration between FreeSWITCH_ and **CGRateS**. We start with common steps, installation and postinstall processes then we dive into particular configurations, depending on the case we run. From a95a82bac80883b3d938f52506c60ed077506a77 Mon Sep 17 00:00:00 2001 From: DanB Date: Tue, 4 Aug 2015 00:52:32 +0200 Subject: [PATCH 03/20] Doc updates --- docs/cgrates_json.rst | 252 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 251 insertions(+), 1 deletion(-) diff --git a/docs/cgrates_json.rst b/docs/cgrates_json.rst index 7b48d6d29..60c26ca7a 100644 --- a/docs/cgrates_json.rst +++ b/docs/cgrates_json.rst @@ -7,4 +7,254 @@ Bellow is the default configuration file which comes hardcoded into cgr-engine, :: - :file: ../data/conf/cgrates/cgrates.json \ No newline at end of file + { + + // Real-time Charging System for Telecom & ISP environments + // Copyright (C) ITsysCOM GmbH + // + // This file contains the default configuration hardcoded into CGRateS. + // This is what you get when you load CGRateS with an empty configuration file. + + //"general": { + // "http_skip_tls_verify": false, // if enabled Http Client will accept any TLS certificate + // "rounding_decimals": 10, // system level precision for floats + // "dbdata_encoding": "msgpack", // encoding used to store object data in strings: + // "tpexport_dir": "/var/log/cgrates/tpe", // path towards export folder for offline Tariff Plans + // "default_reqtype": "*rated", // default request type to consider when missing from requests: <""|*prepaid|*postpaid|*pseudoprepaid|*rated> + // "default_category": "call", // default Type of Record to consider when missing from requests + // "default_tenant": "cgrates.org", // default Tenant to consider when missing from requests + // "default_subject": "cgrates", // default rating Subject to consider when missing from requests + // "connect_attempts": 3, // initial server connect attempts + // "reconnects": -1, // number of retries in case of connection lost + //}, + + + //"listen": { + // "rpc_json": "127.0.0.1:2012", // RPC JSON listening address + // "rpc_gob": "127.0.0.1:2013", // RPC GOB listening address + // "http": "127.0.0.1:2080", // HTTP listening address + //}, + + + //"tariffplan_db": { // database used to store active tariff plan configuration + // "db_type": "redis", // tariffplan_db type: + // "db_host": "127.0.0.1", // tariffplan_db host address + // "db_port": 6379, // port to reach the tariffplan_db + // "db_name": "10", // tariffplan_db name to connect to + // "db_user": "", // sername to use when connecting to tariffplan_db + // "db_passwd": "", // password to use when connecting to tariffplan_db + //}, + + + //"data_db": { // database used to store runtime data (eg: accounts, cdr stats) + // "db_type": "redis", // data_db type: + // "db_host": "127.0.0.1", // data_db host address + // "db_port": 6379, // data_db port to reach the database + // "db_name": "11", // data_db database name to connect to + // "db_user": "", // username to use when connecting to data_db + // "db_passwd": "", // password to use when connecting to data_db + //}, + + + //"stor_db": { // database used to store offline tariff plans and CDRs + // "db_type": "mysql", // stor database type to use: + // "db_host": "127.0.0.1", // the host to connect to + // "db_port": 3306, // the port to reach the stordb + // "db_name": "cgrates", // stor database name + // "db_user": "cgrates", // username to use when connecting to stordb + // "db_passwd": "CGRateS.org", // password to use when connecting to stordb + // "max_open_conns": 100, // maximum database connections opened + // "max_idle_conns": 10, // maximum database connections idle + //}, + + + //"balancer": { + // "enabled": false, // start Balancer service: + //}, + + + //"rater": { + // "enabled": false, // enable Rater service: + // "balancer": "", // register to balancer as worker: <""|internal|x.y.z.y:1234> + // "cdrstats": "", // address where to reach the cdrstats service, empty to disable stats functionality: <""|internal|x.y.z.y:1234> + // "historys": "", // address where to reach the history service, empty to disable history functionality: <""|internal|x.y.z.y:1234> + // "pubsubs": "", // address where to reach the pubusb service, empty to disable pubsub functionality: <""|internal|x.y.z.y:1234> + // "users": "", // address where to reach the user service, empty to disable user profile functionality: <""|internal|x.y.z.y:1234> + //}, + + + //"scheduler": { + // "enabled": false, // start Scheduler service: + //}, + + + //"cdrs": { + // "enabled": false, // start the CDR Server service: + // "extra_fields": [], // extra fields to store in CDRs for non-generic CDRs + // "store_cdrs": true, // store cdrs in storDb + // "rater": "internal", // address where to reach the Rater for cost calculation, empty to disable functionality: <""|internal|x.y.z.y:1234> + // "cdrstats": "", // address where to reach the cdrstats service, empty to disable stats functionality<""|internal|x.y.z.y:1234> + // "reconnects": 5, // number of reconnect attempts to rater or cdrs + // "cdr_replication":[], // replicate the raw CDR to a number of servers + //}, + + + //"cdrstats": { + // "enabled": false, // starts the cdrstats service: + // "save_interval": "1m", // interval to save changed stats into dataDb storage + //}, + + + //"cdre": { + // "*default": { + // "cdr_format": "csv", // exported CDRs format + // "field_separator": ",", + // "data_usage_multiply_factor": 1, // multiply data usage before export (eg: convert from KBytes to Bytes) + // "sms_usage_multiply_factor": 1, // multiply data usage before export (eg: convert from SMS unit to call duration in some billing systems) + // "generic_usage_multiply_factor": 1, // multiply data usage before export (eg: convert from GENERIC unit to call duration in some billing systems) + // "cost_multiply_factor": 1, // multiply cost before export, eg: add VAT + // "cost_rounding_decimals": -1, // rounding decimals for Cost values. -1 to disable rounding + // "cost_shift_digits": 0, // shift digits in the cost on export (eg: convert from EUR to cents) + // "mask_destination_id": "MASKED_DESTINATIONS", // destination id containing called addresses to be masked on export + // "mask_length": 0, // length of the destination suffix to be masked + // "export_dir": "/var/log/cgrates/cdre", // path where the exported CDRs will be placed + // "header_fields": [], // template of the exported header fields + // "content_fields": [ // template of the exported content fields + // {"tag": "CgrId", "cdr_field_id": "cgrid", "type": "cdrfield", "value": "cgrid"}, + // {"tag":"RunId", "cdr_field_id": "mediation_runid", "type": "cdrfield", "value": "mediation_runid"}, + // {"tag":"Tor", "cdr_field_id": "tor", "type": "cdrfield", "value": "tor"}, + // {"tag":"AccId", "cdr_field_id": "accid", "type": "cdrfield", "value": "accid"}, + // {"tag":"ReqType", "cdr_field_id": "reqtype", "type": "cdrfield", "value": "reqtype"}, + // {"tag":"Direction", "cdr_field_id": "direction", "type": "cdrfield", "value": "direction"}, + // {"tag":"Tenant", "cdr_field_id": "tenant", "type": "cdrfield", "value": "tenant"}, + // {"tag":"Category", "cdr_field_id": "category", "type": "cdrfield", "value": "category"}, + // {"tag":"Account", "cdr_field_id": "account", "type": "cdrfield", "value": "account"}, + // {"tag":"Subject", "cdr_field_id": "subject", "type": "cdrfield", "value": "subject"}, + // {"tag":"Destination", "cdr_field_id": "destination", "type": "cdrfield", "value": "destination"}, + // {"tag":"SetupTime", "cdr_field_id": "setup_time", "type": "cdrfield", "value": "setup_time", "layout": "2006-01-02T15:04:05Z07:00"}, + // {"tag":"AnswerTime", "cdr_field_id": "answer_time", "type": "cdrfield", "value": "answer_time", "layout": "2006-01-02T15:04:05Z07:00"}, + // {"tag":"Usage", "cdr_field_id": "usage", "type": "cdrfield", "value": "usage"}, + // {"tag":"Cost", "cdr_field_id": "cost", "type": "cdrfield", "value": "cost"}, + // ], + // "trailer_fields": [], // template of the exported trailer fields + // } + //}, + + + //"cdrc": { + // "*default": { + // "enabled": false, // enable CDR client functionality + // "dry_run": false, // do not send the CDRs to CDRS, just parse them + // "cdrs": "internal", // address where to reach CDR server. + // "cdr_format": "csv", // CDR file format + // "field_separator": ",", // separator used in case of csv files + // "run_delay": 0, // sleep interval in seconds between consecutive runs, 0 to use automation via inotify + // "max_open_files": 1024, // maximum simultaneous files to process, 0 for unlimited + // "data_usage_multiply_factor": 1024, // conversion factor for data usage + // "cdr_in_dir": "/var/log/cgrates/cdrc/in", // absolute path towards the directory where the CDRs are stored + // "cdr_out_dir": "/var/log/cgrates/cdrc/out", // absolute path towards the directory where processed CDRs will be moved + // "failed_calls_prefix": "missed_calls", // used in case of flatstore CDRs to avoid searching for BYE records + // "cdr_source_id": "freeswitch_csv", // free form field, tag identifying the source of the CDRs within CDRS database + // "cdr_filter": "", // filter CDR records to import + // "partial_record_cache": "10s", // duration to cache partial records when not pairing + // "header_fields": [], // template of the import header fields + // "content_fields":[ // import content_fields template, tag will match internally CDR field, in case of .csv value will be represented by index of the field value + // {"tag": "tor", "cdr_field_id": "tor", "type": "cdrfield", "value": "2", "mandatory": true}, + // {"tag": "accid", "cdr_field_id": "accid", "type": "cdrfield", "value": "3", "mandatory": true}, + // {"tag": "reqtype", "cdr_field_id": "reqtype", "type": "cdrfield", "value": "4", "mandatory": true}, + // {"tag": "direction", "cdr_field_id": "direction", "type": "cdrfield", "value": "5", "mandatory": true}, + // {"tag": "tenant", "cdr_field_id": "tenant", "type": "cdrfield", "value": "6", "mandatory": true}, + // {"tag": "category", "cdr_field_id": "category", "type": "cdrfield", "value": "7", "mandatory": true}, + // {"tag": "account", "cdr_field_id": "account", "type": "cdrfield", "value": "8", "mandatory": true}, + // {"tag": "subject", "cdr_field_id": "subject", "type": "cdrfield", "value": "9", "mandatory": true}, + // {"tag": "destination", "cdr_field_id": "destination", "type": "cdrfield", "value": "10", "mandatory": true}, + // {"tag": "setup_time", "cdr_field_id": "setup_time", "type": "cdrfield", "value": "11", "mandatory": true}, + // {"tag": "answer_time", "cdr_field_id": "answer_time", "type": "cdrfield", "value": "12", "mandatory": true}, + // {"tag": "usage", "cdr_field_id": "usage", "type": "cdrfield", "value": "13", "mandatory": true}, + // ], + // "trailer_fields": [], // template of the import trailer fields + // } + //}, + + + //"sm_freeswitch": { + // "enabled": false, // starts SessionManager service: + // "rater": "internal", // address where to reach the Rater <""|internal|127.0.0.1:2013> + // "cdrs": "internal", // address where to reach CDR Server, empty to disable CDR capturing <""|internal|x.y.z.y:1234> + // "reconnects": 5, // number of reconnect attempts to rater or cdrs + // "create_cdr": false, // create CDR out of events and sends them to CDRS component + // "cdr_extra_fields": [], // extra fields to store in CDRs when creating them + // "debit_interval": "10s", // interval to perform debits on. + // "min_call_duration": "0s", // only authorize calls with allowed duration higher than this + // "max_call_duration": "3h", // maximum call duration a prepaid call can last + // "min_dur_low_balance": "5s", // threshold which will trigger low balance warnings for prepaid calls (needs to be lower than debit_interval) + // "low_balance_ann_file": "", // file to be played when low balance is reached for prepaid calls + // "empty_balance_context": "", // if defined, prepaid calls will be transfered to this context on empty balance + // "empty_balance_ann_file": "", // file to be played before disconnecting prepaid calls on empty balance (applies only if no context defined) + // "subscribe_park": true, // subscribe via fsock to receive park events + // "channel_sync_interval": "5m", // sync channels with freeswitch regularly + // "connections":[ // instantiate connections to multiple FreeSWITCH servers + // {"server": "127.0.0.1:8021", "password": "ClueCon", "reconnects": 5} + // ], + //}, + + + //"sm_kamailio": { + // "enabled": false, // starts SessionManager service: + // "rater": "internal", // address where to reach the Rater <""|internal|127.0.0.1:2013> + // "cdrs": "internal", // address where to reach CDR Server, empty to disable CDR capturing <""|internal|x.y.z.y:1234> + // "reconnects": 5, // number of reconnect attempts to rater or cdrs + // "create_cdr": false, // create CDR out of events and sends them to CDRS component + // "debit_interval": "10s", // interval to perform debits on. + // "min_call_duration": "0s", // only authorize calls with allowed duration higher than this + // "max_call_duration": "3h", // maximum call duration a prepaid call can last + // "connections":[ // instantiate connections to multiple Kamailio servers + // {"evapi_addr": "127.0.0.1:8448", "reconnects": 5} + // ], + //}, + + + //"sm_opensips": { + // "enabled": false, // starts SessionManager service: + // "listen_udp": "127.0.0.1:2020", // address where to listen for datagram events coming from OpenSIPS + // "rater": "internal", // address where to reach the Rater <""|internal|127.0.0.1:2013> + // "cdrs": "internal", // address where to reach CDR Server, empty to disable CDR capturing <""|internal|x.y.z.y:1234> + // "reconnects": 5, // number of reconnects if connection is lost + // "create_cdr": false, // create CDR out of events and sends them to CDRS component + // "debit_interval": "10s", // interval to perform debits on. + // "min_call_duration": "0s", // only authorize calls with allowed duration higher than this + // "max_call_duration": "3h", // maximum call duration a prepaid call can last + // "events_subscribe_interval": "60s", // automatic events subscription to OpenSIPS, 0 to disable it + // "mi_addr": "127.0.0.1:8020", // address where to reach OpenSIPS MI to send session disconnects + //}, + + + //"historys": { + // "enabled": false, // starts History service: . + // "history_dir": "/var/log/cgrates/history", // location on disk where to store history files. + // "save_interval": "1s", // interval to save changed cache into .git archive + //}, + + + //"pubsubs": { + // "enabled": false, // starts PubSub service: . + //}, + + + //"users": { + // "enabled": false, // starts User service: . + // "indexes": [], // user profile field indexes + //}, + + + //"mailer": { + // "server": "localhost", // the server to use when sending emails out + // "auth_user": "cgrates", // authenticate to email server using this user + // "auth_passwd": "CGRateS.org", // authenticate to email server with this password + // "from_address": "cgr-mailer@localhost.localdomain" // from address used when sending emails out + //}, + + + } + +:file: ../data/conf/cgrates/cgrates.json \ No newline at end of file From 41c76cc85ff6b61feae50465df32d1652df563e4 Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Tue, 4 Aug 2015 12:08:01 +0300 Subject: [PATCH 04/20] removed split on days --- engine/calldesc.go | 16 ---------------- engine/timespans.go | 16 ---------------- 2 files changed, 32 deletions(-) diff --git a/engine/calldesc.go b/engine/calldesc.go index 026a02c57..6fcd0f793 100644 --- a/engine/calldesc.go +++ b/engine/calldesc.go @@ -348,22 +348,6 @@ func (cd *CallDescriptor) splitInTimeSpans() (timespans []*TimeSpan) { } } } - // split on days - /*for i := 0; i < len(timespans); i++ { - if timespans[i].TimeStart.Day() != timespans[i].TimeEnd.Day() { - //log.Print("TS: ", timespans[i].TimeStart, timespans[i].TimeEnd) - start := timespans[i].TimeStart - newTs := timespans[i].SplitByTime(time.Date(start.Year(), start.Month(), start.Day(), 0, 0, 0, 0, start.Location()).Add(24 * time.Hour)) - if newTs != nil { - //log.Print("NEW TS: ", newTs.TimeStart, newTs.TimeEnd) - // insert the new timespan - index := i + 1 - timespans = append(timespans, nil) - copy(timespans[index+1:], timespans[index:]) - timespans[index] = newTs - } - } - }*/ // Logger.Debug(fmt.Sprintf("After SplitByRatingPlan: %+v", timespans)) // split on rate intervals for i := 0; i < len(timespans); i++ { diff --git a/engine/timespans.go b/engine/timespans.go index a4197f9ec..efbb079cc 100644 --- a/engine/timespans.go +++ b/engine/timespans.go @@ -416,22 +416,6 @@ func (ts *TimeSpan) SplitByRateInterval(i *RateInterval, data bool) (nts *TimeSp return } -/*func (ts *TimeSpan) SplitByTime(splitTime time.Time) (nts *TimeSpan) { - if splitTime.Equal(ts.TimeEnd) { - return - } - nts = &TimeSpan{ - TimeStart: splitTime, - TimeEnd: ts.TimeEnd, - } - nts.copyRatingInfo(ts) - ts.TimeEnd = splitTime - nts.SetRateInterval(ts.RateInterval) - nts.DurationIndex = ts.DurationIndex - ts.SetNewDurationIndex(nts) - return -}*/ - // Split the timespan at the given increment start func (ts *TimeSpan) SplitByIncrement(index int) *TimeSpan { if index <= 0 || index >= len(ts.Increments) { From c80a6d785797593367338dabc7d115659a6072be Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Tue, 4 Aug 2015 19:29:17 +0300 Subject: [PATCH 05/20] added paginator for GetLcr --- engine/calldesc.go | 10 +++++++++- engine/lcr_test.go | 2 +- engine/responder.go | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/engine/calldesc.go b/engine/calldesc.go index 6fcd0f793..8b38da817 100644 --- a/engine/calldesc.go +++ b/engine/calldesc.go @@ -779,7 +779,7 @@ func (cd *CallDescriptor) GetLCRFromStorage() (*LCR, error) { return nil, utils.ErrNotFound } -func (cd *CallDescriptor) GetLCR(stats StatsInterface) (*LCRCost, error) { +func (cd *CallDescriptor) GetLCR(stats StatsInterface, p *utils.Paginator) (*LCRCost, error) { cd.account = nil // make sure it's not cached lcr, err := cd.GetLCRFromStorage() if err != nil { @@ -1111,5 +1111,13 @@ func (cd *CallDescriptor) GetLCR(stats StatsInterface) (*LCRCost, error) { // sort according to strategy lcrCost.Sort() } + if p != nil { + if p.Offset != nil && *p.Offset > 0 && *p.Offset < len(lcrCost.SupplierCosts)-1 { + lcrCost.SupplierCosts = lcrCost.SupplierCosts[*p.Offset:] + } + if p.Limit != nil && *p.Limit > 0 && *p.Limit < len(lcrCost.SupplierCosts)-1 { + lcrCost.SupplierCosts = lcrCost.SupplierCosts[:*p.Limit] + } + } return lcrCost, nil } diff --git a/engine/lcr_test.go b/engine/lcr_test.go index 93fb2c6d4..1bd083d87 100644 --- a/engine/lcr_test.go +++ b/engine/lcr_test.go @@ -204,7 +204,7 @@ func TestLcrGet(t *testing.T) { Account: "rif", Subject: "rif", } - lcr, err := cd.GetLCR(nil) + lcr, err := cd.GetLCR(nil, nil) //jsn, _ := json.Marshal(lcr) //log.Print("LCR: ", string(jsn)) if err != nil || lcr == nil { diff --git a/engine/responder.go b/engine/responder.go index 88a4b1688..e0471e835 100644 --- a/engine/responder.go +++ b/engine/responder.go @@ -335,7 +335,7 @@ func (rs *Responder) GetLCR(cd *CallDescriptor, reply *LCRCost) error { udRcv := upData.(*CallDescriptor) *cd = *udRcv } - lcrCost, err := cd.GetLCR(rs.Stats) + lcrCost, err := cd.GetLCR(rs.Stats, nil) if err != nil { return err } From a17e8445cb3bf61fd9db63d71b00085ba4fe9a2f Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Tue, 4 Aug 2015 19:39:38 +0300 Subject: [PATCH 06/20] better getlcr paginator --- engine/calldesc.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/engine/calldesc.go b/engine/calldesc.go index 8b38da817..ba5e7c130 100644 --- a/engine/calldesc.go +++ b/engine/calldesc.go @@ -1112,10 +1112,10 @@ func (cd *CallDescriptor) GetLCR(stats StatsInterface, p *utils.Paginator) (*LCR lcrCost.Sort() } if p != nil { - if p.Offset != nil && *p.Offset > 0 && *p.Offset < len(lcrCost.SupplierCosts)-1 { + if p.Offset != nil && *p.Offset > 0 && *p.Offset < len(lcrCost.SupplierCosts) { lcrCost.SupplierCosts = lcrCost.SupplierCosts[*p.Offset:] } - if p.Limit != nil && *p.Limit > 0 && *p.Limit < len(lcrCost.SupplierCosts)-1 { + if p.Limit != nil && *p.Limit > 0 && *p.Limit < len(lcrCost.SupplierCosts) { lcrCost.SupplierCosts = lcrCost.SupplierCosts[:*p.Limit] } } From c63868375620b956592d1d428b46f13aa5a3cb66 Mon Sep 17 00:00:00 2001 From: DanB Date: Tue, 4 Aug 2015 21:38:05 +0200 Subject: [PATCH 07/20] Paginator for GetLcr requests --- apier/v1/lcr.go | 4 ++-- engine/lcr.go | 1 + engine/responder.go | 23 ++++++++++++++--------- engine/responder_test.go | 12 ++++++------ sessionmanager/fssessionmanager.go | 4 ++-- sessionmanager/kamailiosm.go | 2 +- sessionmanager/session_test.go | 2 +- 7 files changed, 27 insertions(+), 21 deletions(-) diff --git a/apier/v1/lcr.go b/apier/v1/lcr.go index a5beb1e9b..1f73fac8b 100644 --- a/apier/v1/lcr.go +++ b/apier/v1/lcr.go @@ -32,7 +32,7 @@ func (self *ApierV1) GetLcr(lcrReq engine.LcrRequest, lcrReply *engine.LcrReply) return err } var lcrQried engine.LCRCost - if err := self.Responder.GetLCR(cd, &lcrQried); err != nil { + if err := self.Responder.GetLCR(&engine.AttrGetLcr{CallDescriptor: cd, Paginator: lcrReq.Paginator}, &lcrQried); err != nil { return utils.NewErrServerError(err) } if lcrQried.Entry == nil { @@ -62,7 +62,7 @@ func (self *ApierV1) GetLcrSuppliers(lcrReq engine.LcrRequest, suppliers *string return err } var lcrQried engine.LCRCost - if err := self.Responder.GetLCR(cd, &lcrQried); err != nil { + if err := self.Responder.GetLCR(&engine.AttrGetLcr{CallDescriptor: cd, Paginator: lcrReq.Paginator}, &lcrQried); err != nil { return utils.NewErrServerError(err) } if lcrQried.HasErrors() { diff --git a/engine/lcr.go b/engine/lcr.go index 70099aa22..192635584 100644 --- a/engine/lcr.go +++ b/engine/lcr.go @@ -56,6 +56,7 @@ type LcrRequest struct { Destination string StartTime string Duration string + *utils.Paginator } func (self *LcrRequest) AsCallDescriptor() (*CallDescriptor, error) { diff --git a/engine/responder.go b/engine/responder.go index e0471e835..6813245c3 100644 --- a/engine/responder.go +++ b/engine/responder.go @@ -40,6 +40,11 @@ type SessionRun struct { CallCosts []*CallCost } +type AttrGetLcr struct { + *CallDescriptor + *utils.Paginator +} + type Responder struct { Bal *balancer2go.Balancer ExitChan chan bool @@ -325,17 +330,17 @@ func (rs *Responder) LogCallCost(ccl *CallCostLog, reply *string) error { return nil } -func (rs *Responder) GetLCR(cd *CallDescriptor, reply *LCRCost) error { - if cd.Subject == "" { - cd.Subject = cd.Account +func (rs *Responder) GetLCR(attrs *AttrGetLcr, reply *LCRCost) error { + if attrs.CallDescriptor.Subject == "" { + attrs.CallDescriptor.Subject = attrs.CallDescriptor.Account } - if upData, err := LoadUserProfile(cd, "ExtraFields"); err != nil { + if upData, err := LoadUserProfile(attrs.CallDescriptor, "ExtraFields"); err != nil { return err } else { udRcv := upData.(*CallDescriptor) - *cd = *udRcv + *attrs.CallDescriptor = *udRcv } - lcrCost, err := cd.GetLCR(rs.Stats, nil) + lcrCost, err := attrs.CallDescriptor.GetLCR(rs.Stats, attrs.Paginator) if err != nil { return err } @@ -505,7 +510,7 @@ type Connector interface { GetSessionRuns(*StoredCdr, *[]*SessionRun) error ProcessCdr(*StoredCdr, *string) error LogCallCost(*CallCostLog, *string) error - GetLCR(*CallDescriptor, *LCRCost) error + GetLCR(*AttrGetLcr, *LCRCost) error } type RPCClientConnector struct { @@ -552,6 +557,6 @@ func (rcc *RPCClientConnector) LogCallCost(ccl *CallCostLog, reply *string) erro return rcc.Client.Call("CDRSV1.LogCallCost", ccl, reply) } -func (rcc *RPCClientConnector) GetLCR(cd *CallDescriptor, reply *LCRCost) error { - return rcc.Client.Call("Responder.GetLCR", cd, reply) +func (rcc *RPCClientConnector) GetLCR(attrs *AttrGetLcr, reply *LCRCost) error { + return rcc.Client.Call("Responder.GetLCR", attrs, reply) } diff --git a/engine/responder_test.go b/engine/responder_test.go index 68d5054a7..9938431bd 100644 --- a/engine/responder_test.go +++ b/engine/responder_test.go @@ -395,7 +395,7 @@ func TestGetLCR(t *testing.T) { }, } var lcr LCRCost - if err := rsponder.GetLCR(cdStatic, &lcr); err != nil { + if err := rsponder.GetLCR(&AttrGetLcr{CallDescriptor: cdStatic}, &lcr); err != nil { t.Error(err) } else if !reflect.DeepEqual(eStLcr.Entry, lcr.Entry) { t.Errorf("Expecting: %+v, received: %+v", eStLcr.Entry, lcr.Entry) @@ -422,7 +422,7 @@ func TestGetLCR(t *testing.T) { }, } var lcrLc LCRCost - if err := rsponder.GetLCR(cdLowestCost, &lcrLc); err != nil { + if err := rsponder.GetLCR(&AttrGetLcr{CallDescriptor: cdLowestCost}, &lcrLc); err != nil { t.Error(err) } else if !reflect.DeepEqual(eLcLcr.Entry, lcrLc.Entry) { t.Errorf("Expecting: %+v, received: %+v", eLcLcr.Entry, lcrLc.Entry) @@ -447,7 +447,7 @@ func TestGetLCR(t *testing.T) { &LCRSupplierCost{Supplier: "*out:tenant12:call:dan12", Cost: 0.6, Duration: 60 * time.Second}, }, } - if err := rsponder.GetLCR(cdLowestCost, &lcrLc); err != nil { + if err := rsponder.GetLCR(&AttrGetLcr{CallDescriptor: cdLowestCost}, &lcrLc); err != nil { t.Error(err) } else if !reflect.DeepEqual(eLcLcr.Entry, lcrLc.Entry) { t.Errorf("Expecting: %+v, received: %+v", eLcLcr.Entry, lcrLc.Entry) @@ -476,7 +476,7 @@ func TestGetLCR(t *testing.T) { }, } var lcrQT LCRCost - if err := rsponder.GetLCR(cdQosThreshold, &lcrQT); err != nil { + if err := rsponder.GetLCR(&AttrGetLcr{CallDescriptor: cdQosThreshold}, &lcrQT); err != nil { t.Error(err) } else if !reflect.DeepEqual(eQTLcr.Entry, lcrQT.Entry) { t.Errorf("Expecting: %+v, received: %+v", eQTLcr.Entry, lcrQT.Entry) @@ -495,7 +495,7 @@ func TestGetLCR(t *testing.T) { &LCRSupplierCost{Supplier: "*out:tenant12:call:dan12", Cost: 0.6, Duration: 60 * time.Second, QOS: map[string]float64{PDD: -1, ACD: 300, TCD: 300, ASR: 100, ACC: 2, TCC: 2, DDC: 2}, qosSortParams: []string{"35", "4m"}}, }, } - if err := rsponder.GetLCR(cdQosThreshold, &lcrQT); err != nil { + if err := rsponder.GetLCR(&AttrGetLcr{CallDescriptor: cdQosThreshold}, &lcrQT); err != nil { t.Error(err) } else if !reflect.DeepEqual(eQTLcr.Entry, lcrQT.Entry) { t.Errorf("Expecting: %+v, received: %+v", eQTLcr.Entry, lcrQT.Entry) @@ -524,7 +524,7 @@ func TestGetLCR(t *testing.T) { }, } var lcrQ LCRCost - if err := rsponder.GetLCR(cdQos, &lcrQ); err != nil { + if err := rsponder.GetLCR(&AttrGetLcr{CallDescriptor: cdQos}, &lcrQ); err != nil { t.Error(err) } else if !reflect.DeepEqual(eQosLcr.Entry, lcrQ.Entry) { t.Errorf("Expecting: %+v, received: %+v", eQosLcr.Entry, lcrQ.Entry) diff --git a/sessionmanager/fssessionmanager.go b/sessionmanager/fssessionmanager.go index 913cc0ab7..321cc9574 100644 --- a/sessionmanager/fssessionmanager.go +++ b/sessionmanager/fssessionmanager.go @@ -193,7 +193,7 @@ func (sm *FSSessionManager) setCgrLcr(ev engine.Event, connId string) error { TimeStart: startTime, TimeEnd: startTime.Add(config.CgrConfig().MaxCallDuration), } - if err := sm.rater.GetLCR(cd, &lcrCost); err != nil { + if err := sm.rater.GetLCR(&engine.AttrGetLcr{CallDescriptor: cd}, &lcrCost); err != nil { return err } supps := []string{} @@ -238,7 +238,7 @@ func (sm *FSSessionManager) onChannelPark(ev engine.Event, connId string) { return } var lcr engine.LCRCost - if err = sm.Rater().GetLCR(cd, &lcr); err != nil { + if err = sm.Rater().GetLCR(&engine.AttrGetLcr{CallDescriptor: cd}, &lcr); err != nil { engine.Logger.Info(fmt.Sprintf(" LCR_API_ERROR: %s", err.Error())) sm.unparkCall(ev.GetUUID(), connId, ev.GetCallDestNr(utils.META_DEFAULT), SYSTEM_ERROR) } diff --git a/sessionmanager/kamailiosm.go b/sessionmanager/kamailiosm.go index e511134d6..cc86f9c31 100644 --- a/sessionmanager/kamailiosm.go +++ b/sessionmanager/kamailiosm.go @@ -106,7 +106,7 @@ func (self *KamailioSessionManager) getSuppliers(kev KamEvent) (string, error) { return "", errors.New("LCR_PREPROCESS_ERROR") } var lcr engine.LCRCost - if err = self.Rater().GetLCR(cd, &lcr); err != nil { + if err = self.Rater().GetLCR(&engine.AttrGetLcr{CallDescriptor: cd}, &lcr); err != nil { engine.Logger.Info(fmt.Sprintf(" LCR_API_ERROR error: %s", err.Error())) return "", errors.New("LCR_API_ERROR") } diff --git a/sessionmanager/session_test.go b/sessionmanager/session_test.go index d982f9a13..954f36bb0 100644 --- a/sessionmanager/session_test.go +++ b/sessionmanager/session_test.go @@ -99,7 +99,7 @@ func (mc *MockConnector) GetDerivedMaxSessionTime(*engine.StoredCdr, *float64) e func (mc *MockConnector) GetSessionRuns(*engine.StoredCdr, *[]*engine.SessionRun) error { return nil } func (mc *MockConnector) ProcessCdr(*engine.StoredCdr, *string) error { return nil } func (mc *MockConnector) LogCallCost(*engine.CallCostLog, *string) error { return nil } -func (mc *MockConnector) GetLCR(*engine.CallDescriptor, *engine.LCRCost) error { return nil } +func (mc *MockConnector) GetLCR(*engine.AttrGetLcr, *engine.LCRCost) error { return nil } func TestSessionRefund(t *testing.T) { mc := &MockConnector{} From 37454f8736886fc39259c0103478e75bfe4de317 Mon Sep 17 00:00:00 2001 From: Eloy Coto Date: Wed, 5 Aug 2015 10:17:52 +0100 Subject: [PATCH 08/20] ActionsTriggers: StatsQueueTriggered with public Metrics to get the info in call_url and call_url_async --- engine/action.go | 2 +- engine/stats_queue.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/engine/action.go b/engine/action.go index 8a6355728..0f44c9404 100644 --- a/engine/action.go +++ b/engine/action.go @@ -486,7 +486,7 @@ func mailAsync(ub *Account, sq *StatsQueueTriggered, a *Action, acs Actions) err message = []byte(fmt.Sprintf("To: %s\r\nSubject: [CGR Notification] Threshold hit on Balance: %s\r\n\r\nTime: \r\n\t%s\r\n\r\nBalance:\r\n\t%s\r\n\r\nYours faithfully,\r\nCGR Balance Monitor\r\n", toAddrStr, ub.Id, time.Now(), balJsn)) } else if sq != nil { message = []byte(fmt.Sprintf("To: %s\r\nSubject: [CGR Notification] Threshold hit on StatsQueueId: %s\r\n\r\nTime: \r\n\t%s\r\n\r\nStatsQueueId:\r\n\t%s\r\n\r\nMetrics:\r\n\t%+v\r\n\r\nTrigger:\r\n\t%+v\r\n\r\nYours faithfully,\r\nCGR CDR Stats Monitor\r\n", - toAddrStr, sq.Id, time.Now(), sq.Id, sq.metrics, sq.Trigger)) + toAddrStr, sq.Id, time.Now(), sq.Id, sq.Metrics, sq.Trigger)) } auth := smtp.PlainAuth("", cgrCfg.MailerAuthUser, cgrCfg.MailerAuthPass, strings.Split(cgrCfg.MailerServer, ":")[0]) // We only need host part, so ignore port go func() { diff --git a/engine/stats_queue.go b/engine/stats_queue.go index f7c189f6b..1f328da2e 100644 --- a/engine/stats_queue.go +++ b/engine/stats_queue.go @@ -215,12 +215,12 @@ func (sq *StatsQueue) GetId() string { // Convert data into a struct which can be used in actions based on triggers hit func (sq *StatsQueue) Triggered(at *ActionTrigger) *StatsQueueTriggered { - return &StatsQueueTriggered{Id: sq.conf.Id, metrics: sq.getStats(), Trigger: at} + return &StatsQueueTriggered{Id: sq.conf.Id, Metrics: sq.getStats(), Trigger: at} } // Struct to be passed to triggered actions type StatsQueueTriggered struct { Id string // StatsQueueId - metrics map[string]float64 + Metrics map[string]float64 Trigger *ActionTrigger } From 6b5f15abcce85f4b00e821b36f00f8fec84c5dd3 Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Wed, 5 Aug 2015 14:04:12 +0300 Subject: [PATCH 09/20] unified lcr supplier format --- engine/calldesc.go | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/engine/calldesc.go b/engine/calldesc.go index ba5e7c130..7ee1c3f08 100644 --- a/engine/calldesc.go +++ b/engine/calldesc.go @@ -815,12 +815,13 @@ func (cd *CallDescriptor) GetLCR(stats StatsInterface, p *utils.Paginator) (*LCR lcrCD.Account = supplier lcrCD.Subject = supplier lcrCD.Category = lcrCost.Entry.RPCategory + fullSupplier := utils.ConcatenatedKey(lcrCD.Direction, lcrCD.Tenant, lcrCD.Category, lcrCD.Subject) var cc *CallCost var err error if cd.account, err = accountingStorage.GetAccount(lcrCD.GetAccountKey()); err == nil { if cd.account.Disabled { lcrCost.SupplierCosts = append(lcrCost.SupplierCosts, &LCRSupplierCost{ - Supplier: supplier, + Supplier: fullSupplier, Error: fmt.Sprintf("supplier %s is disabled", supplier), }) continue @@ -830,16 +831,15 @@ func (cd *CallDescriptor) GetLCR(stats StatsInterface, p *utils.Paginator) (*LCR cc, err = lcrCD.GetCost() } - supplier = utils.ConcatenatedKey(lcrCD.Direction, lcrCD.Tenant, lcrCD.Category, lcrCD.Subject) //log.Printf("CC: %+v", cc.Timespans[0].ratingInfo.RateIntervals[0].Rating.Rates[0]) if err != nil || cc == nil { lcrCost.SupplierCosts = append(lcrCost.SupplierCosts, &LCRSupplierCost{ - Supplier: supplier, + Supplier: fullSupplier, Error: err.Error(), }) } else { lcrCost.SupplierCosts = append(lcrCost.SupplierCosts, &LCRSupplierCost{ - Supplier: supplier, + Supplier: fullSupplier, Cost: cc.Cost, Duration: cc.GetDuration(), }) @@ -862,6 +862,7 @@ func (cd *CallDescriptor) GetLCR(stats StatsInterface, p *utils.Paginator) (*LCR lcrCD.Category = category lcrCD.Account = supplier lcrCD.Subject = supplier + fullSupplier := utils.ConcatenatedKey(lcrCD.Direction, lcrCD.Tenant, lcrCD.Category, lcrCD.Subject) var qosSortParams []string var asrValues sort.Float64Slice var pddValues sort.Float64Slice @@ -881,7 +882,7 @@ func (cd *CallDescriptor) GetLCR(stats StatsInterface, p *utils.Paginator) (*LCR if utils.IsSliceMember([]string{LCR_STRATEGY_QOS, LCR_STRATEGY_QOS_THRESHOLD, LCR_STRATEGY_LOAD}, lcrCost.Entry.Strategy) { if stats == nil { lcrCost.SupplierCosts = append(lcrCost.SupplierCosts, &LCRSupplierCost{ - Supplier: supplier, + Supplier: fullSupplier, Error: fmt.Sprintf("Cdr stats service not configured"), }) continue @@ -889,7 +890,7 @@ func (cd *CallDescriptor) GetLCR(stats StatsInterface, p *utils.Paginator) (*LCR rpfKey := utils.ConcatenatedKey(ratingProfileSearchKey, supplier) if rpf, err := ratingStorage.GetRatingProfile(rpfKey, false); err != nil { lcrCost.SupplierCosts = append(lcrCost.SupplierCosts, &LCRSupplierCost{ - Supplier: supplier, + Supplier: fullSupplier, Error: fmt.Sprintf("Rating plan error: %s", err.Error()), }) continue @@ -921,7 +922,7 @@ func (cd *CallDescriptor) GetLCR(stats StatsInterface, p *utils.Paginator) (*LCR statValues := make(map[string]float64) if err := stats.GetValues(qId, &statValues); err != nil { lcrCost.SupplierCosts = append(lcrCost.SupplierCosts, &LCRSupplierCost{ - Supplier: supplier, + Supplier: fullSupplier, Error: fmt.Sprintf("Get stats values for queue id %s, error %s", qId, err.Error()), }) statsErr = true @@ -975,7 +976,7 @@ func (cd *CallDescriptor) GetLCR(stats StatsInterface, p *utils.Paginator) (*LCR if lcrCost.Entry.Strategy == LCR_STRATEGY_LOAD { if len(supplierQueues) > 0 { lcrCost.SupplierCosts = append(lcrCost.SupplierCosts, &LCRSupplierCost{ - Supplier: supplier, + Supplier: fullSupplier, supplierQueues: supplierQueues, }) } @@ -1055,7 +1056,7 @@ func (cd *CallDescriptor) GetLCR(stats StatsInterface, p *utils.Paginator) (*LCR //log.Print("ACCCOUNT") if cd.account.Disabled { lcrCost.SupplierCosts = append(lcrCost.SupplierCosts, &LCRSupplierCost{ - Supplier: supplier, + Supplier: fullSupplier, Error: fmt.Sprintf("supplier %s is disabled", supplier), }) continue @@ -1066,16 +1067,15 @@ func (cd *CallDescriptor) GetLCR(stats StatsInterface, p *utils.Paginator) (*LCR cc, err = lcrCD.GetCost() } //log.Printf("CC: %+v", cc) - supplier = utils.ConcatenatedKey(lcrCD.Direction, lcrCD.Tenant, lcrCD.Category, lcrCD.Subject) if err != nil || cc == nil { lcrCost.SupplierCosts = append(lcrCost.SupplierCosts, &LCRSupplierCost{ - Supplier: supplier, + Supplier: fullSupplier, Error: err.Error(), }) continue } else { supplCost := &LCRSupplierCost{ - Supplier: supplier, + Supplier: fullSupplier, Cost: cc.Cost, Duration: cc.GetDuration(), } From 313aab0952912edc3c09ac4fe06586432a291dce Mon Sep 17 00:00:00 2001 From: DanB Date: Wed, 5 Aug 2015 13:16:09 +0200 Subject: [PATCH 10/20] Adding *load_distribution as strategy exemplified --- data/tariffplans/tutorial/LcrRules.csv | 2 ++ 1 file changed, 2 insertions(+) diff --git a/data/tariffplans/tutorial/LcrRules.csv b/data/tariffplans/tutorial/LcrRules.csv index d4a3502ba..9898a0642 100644 --- a/data/tariffplans/tutorial/LcrRules.csv +++ b/data/tariffplans/tutorial/LcrRules.csv @@ -5,5 +5,7 @@ *out,cgrates.org,call,1002,*any,*any,lcr_profile1,*qos,,2014-01-14T00:00:00Z,10 *out,cgrates.org,call,1003,*any,DST_1002,lcr_profile1,*qos_threshold,20;;;;2m;;;;;;;,2014-01-14T00:00:00Z,10 *out,cgrates.org,call,1003,*any,*any,lcr_profile1,*qos_threshold,40;;;;90s;;;;;;;,2014-01-14T00:00:00Z,10 +*out,cgrates.org,call,1004,*any,DST_1002,lcr_profile1,*load_distribution,supplier1:5;supplier2:3;*default:1,2014-01-14T00:00:00Z,10 +*out,cgrates.org,call,1004,*any,*any,lcr_profile1,*load_distribution,,2014-01-14T00:00:00Z,10 *out,cgrates.org,call,*any,*any,DST_1002,lcr_profile2,*lowest_cost,,2014-01-14T00:00:00Z,10 *out,cgrates.org,call,*any,*any,*any,lcr_profile1,*lowest_cost,,2014-01-14T00:00:00Z,10 \ No newline at end of file From 5e3163f5c2c3762eb1a556b7c8d62f0bc876bdab Mon Sep 17 00:00:00 2001 From: DanB Date: Wed, 5 Aug 2015 13:50:22 +0200 Subject: [PATCH 11/20] Do not provide Cost in *load_distribution strategy --- engine/responder.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/engine/responder.go b/engine/responder.go index 6813245c3..839e1c466 100644 --- a/engine/responder.go +++ b/engine/responder.go @@ -344,6 +344,11 @@ func (rs *Responder) GetLCR(attrs *AttrGetLcr, reply *LCRCost) error { if err != nil { return err } + if lcrCost.Entry.Strategy == LCR_STRATEGY_LOAD { + for _, suppl := range lcrCost.SupplierCosts { + suppl.Cost = -1 // In case of load distribution we don't calculate costs + } + } *reply = *lcrCost return nil } From 7dec80663b2dc22c756fecdb11b1b492299e8da7 Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Wed, 5 Aug 2015 15:49:33 +0300 Subject: [PATCH 12/20] fix for console pointer Paginator --- console/command_executer.go | 11 ++++++++++- console/lcr.go | 5 +++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/console/command_executer.go b/console/command_executer.go index 97620f3bb..bf836da45 100644 --- a/console/command_executer.go +++ b/console/command_executer.go @@ -67,8 +67,17 @@ func (ce *CommandExecuter) clientArgs(iface interface{}) (args []string) { for i := 0; i < typ.NumField(); i++ { valField := val.Field(i) typeField := typ.Field(i) + //log.Printf("%v (%v)", typeField.Name, valField.Kind()) switch valField.Kind() { - case reflect.Struct: + case reflect.Ptr, reflect.Struct: + if valField.Kind() == reflect.Ptr { + valField = reflect.New(valField.Type().Elem()).Elem() + if valField.Kind() != reflect.Struct { + //log.Printf("Here: %v (%v)", typeField.Name, valField.Kind()) + args = append(args, typeField.Name) + continue + } + } args = append(args, ce.clientArgs(valField.Interface())...) default: args = append(args, typeField.Name) diff --git a/console/lcr.go b/console/lcr.go index 711bcc9ef..cd93043d3 100644 --- a/console/lcr.go +++ b/console/lcr.go @@ -20,13 +20,14 @@ package console import ( "github.com/cgrates/cgrates/engine" + "github.com/cgrates/cgrates/utils" ) func init() { c := &CmdGetLcr{ name: "lcr", rpcMethod: "ApierV1.GetLcr", - rpcParams: &engine.LcrRequest{}, + rpcParams: &engine.LcrRequest{Paginator: &utils.Paginator{}}, } commands[c.Name()] = c c.CommandExecuter = &CommandExecuter{c} @@ -50,7 +51,7 @@ func (self *CmdGetLcr) RpcMethod() string { func (self *CmdGetLcr) RpcParams(reset bool) interface{} { if reset || self.rpcParams == nil { - self.rpcParams = &engine.LcrRequest{} + self.rpcParams = &engine.LcrRequest{Paginator: &utils.Paginator{}} } return self.rpcParams } From 9b8afb675fe7b39a81a1b86b15644832927d33a6 Mon Sep 17 00:00:00 2001 From: DanB Date: Thu, 6 Aug 2015 16:56:53 +0200 Subject: [PATCH 13/20] Fix uninitialized ExtraFields in exporter --- cdre/cdrexporter.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cdre/cdrexporter.go b/cdre/cdrexporter.go index 2e25a72d0..3c971116e 100644 --- a/cdre/cdrexporter.go +++ b/cdre/cdrexporter.go @@ -310,6 +310,8 @@ func (cdre *CdrExporter) composeTrailer() error { func (cdre *CdrExporter) processCdr(cdr *engine.StoredCdr) error { if cdr == nil || len(cdr.CgrId) == 0 { // We do not export empty CDRs return nil + } else if cdr.ExtraFields == nil { // Avoid assignment in nil map if not initialized + cdr.ExtraFields = make(map[string]string) } // Cost multiply if cdre.dataUsageMultiplyFactor != 0.0 && cdr.TOR == utils.DATA { From 95694b688e33f686eb98dd8ad830b86848924403 Mon Sep 17 00:00:00 2001 From: DanB Date: Mon, 10 Aug 2015 15:47:38 +0200 Subject: [PATCH 14/20] LcrRequest.IgnoreErrors flag implementation, fixes #142 --- apier/v1/lcr.go | 13 ++++++++----- engine/lcr.go | 20 ++++++++++++-------- general_tests/tutorial_local_test.go | 10 +++++----- 3 files changed, 25 insertions(+), 18 deletions(-) diff --git a/apier/v1/lcr.go b/apier/v1/lcr.go index 1f73fac8b..513bc64e0 100644 --- a/apier/v1/lcr.go +++ b/apier/v1/lcr.go @@ -38,14 +38,17 @@ func (self *ApierV1) GetLcr(lcrReq engine.LcrRequest, lcrReply *engine.LcrReply) if lcrQried.Entry == nil { return utils.ErrNotFound } - if lcrQried.HasErrors() { - lcrQried.LogErrors() - return fmt.Errorf("%s:%s", utils.ErrServerError.Error(), "LCR_COMPUTE_ERRORS") - } lcrReply.DestinationId = lcrQried.Entry.DestinationId lcrReply.RPCategory = lcrQried.Entry.RPCategory lcrReply.Strategy = lcrQried.Entry.Strategy for _, qriedSuppl := range lcrQried.SupplierCosts { + if qriedSuppl.Error != "" { + engine.Logger.Err(fmt.Sprintf("LCR_ERROR: supplier <%s>, error <%s>", qriedSuppl.Supplier, qriedSuppl.Error)) + if !lcrReq.IgnoreErrors { + return fmt.Errorf("%s:%s", utils.ErrServerError.Error(), "LCR_COMPUTE_ERRORS") + } + continue + } if dtcs, err := utils.NewDTCSFromRPKey(qriedSuppl.Supplier); err != nil { return utils.NewErrServerError(err) } else { @@ -65,7 +68,7 @@ func (self *ApierV1) GetLcrSuppliers(lcrReq engine.LcrRequest, suppliers *string if err := self.Responder.GetLCR(&engine.AttrGetLcr{CallDescriptor: cd, Paginator: lcrReq.Paginator}, &lcrQried); err != nil { return utils.NewErrServerError(err) } - if lcrQried.HasErrors() { + if lcrQried.HasErrors() && !lcrReq.IgnoreErrors { lcrQried.LogErrors() return fmt.Errorf("%s:%s", utils.ErrServerError.Error(), "LCR_ERRORS") } diff --git a/engine/lcr.go b/engine/lcr.go index 192635584..f5925d256 100644 --- a/engine/lcr.go +++ b/engine/lcr.go @@ -48,14 +48,15 @@ const ( // A request for LCR, used in APIer and SM where we need to expose it type LcrRequest struct { - Direction string - Tenant string - Category string - Account string - Subject string - Destination string - StartTime string - Duration string + Direction string + Tenant string + Category string + Account string + Subject string + Destination string + StartTime string + Duration string + IgnoreErrors bool *utils.Paginator } @@ -434,6 +435,9 @@ func (lc *LCRCost) SuppliersSlice() ([]string, error) { } supps := []string{} for _, supplCost := range lc.SupplierCosts { + if supplCost.Error != "" { + continue // Do not add the supplier with cost errors to list of suppliers available + } if dtcs, err := utils.NewDTCSFromRPKey(supplCost.Supplier); err != nil { return nil, err } else if len(dtcs.Subject) != 0 { diff --git a/general_tests/tutorial_local_test.go b/general_tests/tutorial_local_test.go index 041a25a76..ede31b1ee 100644 --- a/general_tests/tutorial_local_test.go +++ b/general_tests/tutorial_local_test.go @@ -117,7 +117,7 @@ func TestTutLocalCacheStats(t *testing.T) { } var rcvStats *utils.CacheStats expectedStats := &utils.CacheStats{Destinations: 4, RatingPlans: 3, RatingProfiles: 8, Actions: 7, SharedGroups: 1, RatingAliases: 1, AccountAliases: 1, - DerivedChargers: 1, LcrProfiles: 4, CdrStats: 6, Users: 2} + DerivedChargers: 1, LcrProfiles: 5, CdrStats: 6, Users: 2} var args utils.AttrCacheStats if err := tutLocalRpc.Call("ApierV1.GetCacheStats", args, &rcvStats); err != nil { t.Error("Got error on ApierV1.GetCacheStats: ", err.Error()) @@ -898,8 +898,8 @@ func TestTutLocalLeastCost(t *testing.T) { Direction: "*out", Category: "call", Tenant: "cgrates.org", - Subject: "1004", - Account: "1004", + Subject: "1005", + Account: "1005", Destination: "1002", TimeStart: tStart, TimeEnd: tEnd, @@ -924,8 +924,8 @@ func TestTutLocalLeastCost(t *testing.T) { Direction: "*out", Category: "call", Tenant: "cgrates.org", - Subject: "1004", - Account: "1004", + Subject: "1005", + Account: "1005", Destination: "1003", TimeStart: tStart, TimeEnd: tEnd, From ad682a5aed2a67753b904b6d3d53773808a2c793 Mon Sep 17 00:00:00 2001 From: DanB Date: Thu, 13 Aug 2015 12:16:30 +0200 Subject: [PATCH 15/20] LcrReq parameter change StartTime->SetupTime for uniformity with MaxUsageReq --- apier/v1/lcr.go | 6 ++++-- engine/lcr.go | 6 +++--- engine/lcr_test.go | 4 ++-- general_tests/tutorial_kam_calls_test.go | 2 ++ sessionmanager/fsevent.go | 2 +- sessionmanager/kamevent.go | 2 +- 6 files changed, 13 insertions(+), 9 deletions(-) diff --git a/apier/v1/lcr.go b/apier/v1/lcr.go index 513bc64e0..2b8966ac1 100644 --- a/apier/v1/lcr.go +++ b/apier/v1/lcr.go @@ -68,9 +68,11 @@ func (self *ApierV1) GetLcrSuppliers(lcrReq engine.LcrRequest, suppliers *string if err := self.Responder.GetLCR(&engine.AttrGetLcr{CallDescriptor: cd, Paginator: lcrReq.Paginator}, &lcrQried); err != nil { return utils.NewErrServerError(err) } - if lcrQried.HasErrors() && !lcrReq.IgnoreErrors { + if lcrQried.HasErrors() { lcrQried.LogErrors() - return fmt.Errorf("%s:%s", utils.ErrServerError.Error(), "LCR_ERRORS") + if !lcrReq.IgnoreErrors { + return fmt.Errorf("%s:%s", utils.ErrServerError.Error(), "LCR_COMPUTE_ERRORS") + } } if suppliersStr, err := lcrQried.SuppliersString(); err != nil { return utils.NewErrServerError(err) diff --git a/engine/lcr.go b/engine/lcr.go index f5925d256..b2df016e3 100644 --- a/engine/lcr.go +++ b/engine/lcr.go @@ -54,7 +54,7 @@ type LcrRequest struct { Account string Subject string Destination string - StartTime string + SetupTime string Duration string IgnoreErrors bool *utils.Paginator @@ -79,9 +79,9 @@ func (self *LcrRequest) AsCallDescriptor() (*CallDescriptor, error) { } var timeStart time.Time var err error - if len(self.StartTime) == 0 { + if len(self.SetupTime) == 0 { timeStart = time.Now() - } else if timeStart, err = utils.ParseTimeDetectLayout(self.StartTime); err != nil { + } else if timeStart, err = utils.ParseTimeDetectLayout(self.SetupTime); err != nil { return nil, err } var callDur time.Duration diff --git a/engine/lcr_test.go b/engine/lcr_test.go index 1bd083d87..79f036087 100644 --- a/engine/lcr_test.go +++ b/engine/lcr_test.go @@ -215,11 +215,11 @@ func TestLcrGet(t *testing.T) { func TestLcrRequestAsCallDescriptor(t *testing.T) { sTime := time.Date(2015, 04, 06, 17, 40, 0, 0, time.UTC) callDur := time.Duration(1) * time.Minute - lcrReq := &LcrRequest{Account: "2001", StartTime: sTime.String()} + lcrReq := &LcrRequest{Account: "2001", SetupTime: sTime.String()} if _, err := lcrReq.AsCallDescriptor(); err == nil || err != utils.ErrMandatoryIeMissing { t.Error("Unexpected error received: %v", err) } - lcrReq = &LcrRequest{Account: "2001", Destination: "2002", StartTime: sTime.String()} + lcrReq = &LcrRequest{Account: "2001", Destination: "2002", SetupTime: sTime.String()} eCd := &CallDescriptor{ Direction: utils.OUT, Tenant: config.CgrConfig().DefaultTenant, diff --git a/general_tests/tutorial_kam_calls_test.go b/general_tests/tutorial_kam_calls_test.go index a4dba99b3..5bdc35ebe 100644 --- a/general_tests/tutorial_kam_calls_test.go +++ b/general_tests/tutorial_kam_calls_test.go @@ -508,6 +508,7 @@ func TestTutKamCallsCdrs(t *testing.T) { } } +/* // Make sure all stats queues were updated func TestTutKamCallsCdrStatsAfter(t *testing.T) { if !*testCalls { @@ -557,6 +558,7 @@ func TestTutKamCallsCdrStatsAfter(t *testing.T) { t.Errorf("Expecting: %v, received: %v", eMetrics, statMetrics) } } +*/ // Make sure account was debited properly func TestTutKamCallsAccountFraud1001(t *testing.T) { diff --git a/sessionmanager/fsevent.go b/sessionmanager/fsevent.go index 914c6e806..2e1273aec 100644 --- a/sessionmanager/fsevent.go +++ b/sessionmanager/fsevent.go @@ -386,7 +386,7 @@ func (fsev FSEvent) AsCallDescriptor() (*engine.CallDescriptor, error) { Account: fsev.GetAccount(utils.META_DEFAULT), Subject: fsev.GetSubject(utils.META_DEFAULT), Destination: fsev.GetDestination(utils.META_DEFAULT), - StartTime: utils.FirstNonEmpty(fsev[SETUP_TIME], fsev[ANSWER_TIME]), + SetupTime: utils.FirstNonEmpty(fsev[SETUP_TIME], fsev[ANSWER_TIME]), Duration: fsev[DURATION], } return lcrReq.AsCallDescriptor() diff --git a/sessionmanager/kamevent.go b/sessionmanager/kamevent.go index 2ec757dcf..07669b43e 100644 --- a/sessionmanager/kamevent.go +++ b/sessionmanager/kamevent.go @@ -381,7 +381,7 @@ func (kev KamEvent) AsCallDescriptor() (*engine.CallDescriptor, error) { Account: kev.GetAccount(utils.META_DEFAULT), Subject: kev.GetSubject(utils.META_DEFAULT), Destination: kev.GetDestination(utils.META_DEFAULT), - StartTime: utils.FirstNonEmpty(kev[CGR_SETUPTIME], kev[CGR_ANSWERTIME]), + SetupTime: utils.FirstNonEmpty(kev[CGR_SETUPTIME], kev[CGR_ANSWERTIME]), Duration: kev[CGR_DURATION], } return lcrReq.AsCallDescriptor() From 2aebd5e080b36f9aeb4ff0be9ea3308576f31e94 Mon Sep 17 00:00:00 2001 From: DanB Date: Mon, 17 Aug 2015 12:50:33 +0200 Subject: [PATCH 16/20] Improved tutorial calls tests --- .../freeswitch/etc/freeswitch_conf.tar.gz | Bin 28272 -> 28299 bytes general_tests/tutorial_fs_calls_test.go | 131 +++++-- general_tests/tutorial_kam_calls_test.go | 326 +++++++++--------- general_tests/tutorial_osips_calls_test.go | 232 ++++++++++--- 4 files changed, 456 insertions(+), 233 deletions(-) diff --git a/data/tutorials/fs_evsock/freeswitch/etc/freeswitch_conf.tar.gz b/data/tutorials/fs_evsock/freeswitch/etc/freeswitch_conf.tar.gz index f6212599410880d5e7d7a1a2f34eec63d79d5b6b..777211ea0e1e31f10e6bd3f1b2f19b95ec916108 100644 GIT binary patch delta 9425 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 delta 9442 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%0D+GuGM)HGV?sz{dM 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 oxSuD#|FU; Date: Mon, 17 Aug 2015 13:06:52 +0200 Subject: [PATCH 17/20] ApierV1.SetAccount - add Disabled option, proper AllowNegative implementation --- apier/v1/accounts.go | 9 +++++++-- utils/apitpdata.go | 3 ++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/apier/v1/accounts.go b/apier/v1/accounts.go index f9c3b53ed..b8702597c 100644 --- a/apier/v1/accounts.go +++ b/apier/v1/accounts.go @@ -168,8 +168,7 @@ func (self *ApierV1) SetAccount(attr utils.AttrSetAccount, reply *string) error ub = bal } else { // Not found in db, create it here ub = &engine.Account{ - Id: balanceId, - AllowNegative: attr.AllowNegative, + Id: balanceId, } } @@ -183,6 +182,12 @@ func (self *ApierV1) SetAccount(attr utils.AttrSetAccount, reply *string) error at.AccountIds = append(at.AccountIds, balanceId) } } + if attr.AllowNegative != nil { + ub.AllowNegative = *attr.AllowNegative + } + if attr.Disabled != nil { + ub.Disabled = *attr.Disabled + } // All prepared, save account if err := self.AccountDb.SetAccount(ub); err != nil { return 0, err diff --git a/utils/apitpdata.go b/utils/apitpdata.go index 3adbde62a..861242b50 100644 --- a/utils/apitpdata.go +++ b/utils/apitpdata.go @@ -1080,7 +1080,8 @@ type AttrSetAccount struct { Direction string Account string ActionPlanId string - AllowNegative bool + AllowNegative *bool + Disabled *bool } type AttrRemoveAccount struct { From 5149b080c079166b6bdf86c70d8d307142bafd79 Mon Sep 17 00:00:00 2001 From: DanB Date: Tue, 18 Aug 2015 13:05:34 +0200 Subject: [PATCH 18/20] New kamailio tutorial in docs, various doc improvements, fixes in local calls --- .../freeswitch/etc/freeswitch_conf.tar.gz | Bin 28299 -> 28291 bytes .../kamailio/etc/kamailio/kamailio.cfg | 2 +- docs/installation.rst | 11 ++-- docs/tut_freeswitch.rst | 5 +- docs/tut_freeswitch_json.rst | 12 ++-- docs/tut_opensips_event.rst | 12 ++-- docs/tut_opensips_installs.rst | 4 +- docs/tutorials.rst | 1 + general_tests/tutorial_fs_calls_test.go | 3 +- general_tests/tutorial_kam_calls_test.go | 53 +++++++++--------- general_tests/tutorial_osips_calls_test.go | 27 +++++---- 11 files changed, 65 insertions(+), 65 deletions(-) diff --git a/data/tutorials/fs_evsock/freeswitch/etc/freeswitch_conf.tar.gz b/data/tutorials/fs_evsock/freeswitch/etc/freeswitch_conf.tar.gz index 777211ea0e1e31f10e6bd3f1b2f19b95ec916108..310388a91667dd05bd8eee2bccef9d42c3c33f3b 100644 GIT binary patch delta 9486 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 ghX>F4-}C4B^Za@KJb#`)ztrb{0j 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 diff --git a/data/tutorials/kamevapi/kamailio/etc/kamailio/kamailio.cfg b/data/tutorials/kamevapi/kamailio/etc/kamailio/kamailio.cfg index a61eea930..1c962a6aa 100644 --- a/data/tutorials/kamevapi/kamailio/etc/kamailio/kamailio.cfg +++ b/data/tutorials/kamevapi/kamailio/etc/kamailio/kamailio.cfg @@ -60,7 +60,7 @@ loadmodule "jsonrpc-s.so" # ----------------- setting module-specific parameters --------------- # ----- mi_fifo params ----- -modparam("mi_fifo", "fifo_name", "/tmp/cgr_kamevapi/kamailio/run/kamailio_fifo") +modparam("mi_fifo", "fifo_name", "/tmp/kamailio_fifo") # ----- tm params ----- modparam("tm", "failure_reply_mode", 3) diff --git a/docs/installation.rst b/docs/installation.rst index 9179ed1d0..56d6c0ea3 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -45,18 +45,20 @@ Database setup ~~~~~~~~~~~~~~ For it's operation CGRateS uses more database types, depending on it's nature, install and configuration being further necessary. + At present we support the following databases: -As DataDB types (rating and accounting subsystems): + - Redis_ -As StorDB (persistent storage for CDRs and tariff plan versions). - +Used as DataDb, optimized for real-time information access. Once installed there should be no special requirements in terms of setup since no schema is necessary. + - MySQL_ +Used as StorDb, optimized for CDR archiving and offline Tariff Plan versioning. Once database is installed, CGRateS database needs to be set-up out of provided scripts (example for the paths set-up by debian package) :: @@ -66,12 +68,13 @@ Once database is installed, CGRateS database needs to be set-up out of provided - PostgreSQL_ +Used as StorDb, optimized for CDR archiving and offline Tariff Plan versioning. Once database is installed, CGRateS database needs to be set-up out of provided scripts (example for the paths set-up by debian package) :: cd /usr/share/cgrates/storage/postgres/ - ./setup_cgr_db.sh root CGRateS.org localhost + ./setup_cgr_db.sh .. _Redis: http://redis.io/ .. _MySQL: http://www.mysql.org/ diff --git a/docs/tut_freeswitch.rst b/docs/tut_freeswitch.rst index e03b9c859..dc54b7bdd 100644 --- a/docs/tut_freeswitch.rst +++ b/docs/tut_freeswitch.rst @@ -1,7 +1,7 @@ -sFreeSWITCH Integration Tutorials +FreeSWITCH Integration Tutorials ================================ -In these tutorials we exemplify few cases of integration between FreeSWITCH_ and **CGRateS**. We start with common steps, installation and postinstall processes then we dive into particular configurations, depending on the case we run. +In these tutorials we exemplify few cases of integration between FreeSWITCH_ and **CGRateS**. We start with common steps, installation and postinstall processes then we dive into particular configurations. .. toctree:: @@ -10,7 +10,6 @@ In these tutorials we exemplify few cases of integration between FreeSWITCH_ and tut_freeswitch_installs tut_cgrates_installs tut_jitsi_installs - tut_freeswitch_csv tut_freeswitch_json tut_cgrates_usage diff --git a/docs/tut_freeswitch_json.rst b/docs/tut_freeswitch_json.rst index 0501bcb69..3d9479b9d 100644 --- a/docs/tut_freeswitch_json.rst +++ b/docs/tut_freeswitch_json.rst @@ -4,7 +4,7 @@ FreeSWITCH_ generating *http-json* CDRs Scenario -------- -- FreeSWITCH with *vanilla* configuration, replacing *mod_cdr_csv* with *mod_json_cdr*. +- FreeSWITCH with *vanilla* configuration adding *mod_json_cdr* for CDR generation. - Modified following users (with configs in *etc/freeswitch/directory/default*): 1001-prepaid, 1002-postpaid, 1003-pseudoprepaid, 1004-rated, 1006-prepaid, 1007-rated. - Have added inside default dialplan CGR own extensions just before routing towards users (*etc/freeswitch/dialplan/default.xml*). @@ -13,8 +13,8 @@ Scenario - **CGRateS** with following components: - CGR-SM started as prepaid controller, with debits taking place at 5s intervals. - - CGR-Mediator component attaching costs to the raw CDRs from FreeSWITCH_ inside CGR StorDB. - - CGR-CDRE exporting mediated CDRs from CGR StorDB (export path: */tmp*). + - CGR-CDRS component receiving raw CDRs from FreeSWITCH, storing them and attaching costs inside CGR StorDB. + - CGR-CDRE exporting processed CDRs from CGR StorDB (export path: */tmp*). - CGR-History component keeping the archive of the rates modifications (path browsable with git client at */tmp/cgr_history*). @@ -23,7 +23,7 @@ Starting FreeSWITCH_ with custom configuration :: - /usr/share/cgrates/tutorials/fs_json/freeswitch/etc/init.d/freeswitch start + /usr/share/cgrates/tutorials/fs_evsock/freeswitch/etc/init.d/freeswitch start To verify that FreeSWITCH_ is running we run the console command: @@ -37,7 +37,7 @@ Starting **CGRateS** with custom configuration :: - /usr/share/cgrates/tutorials/fs_json/cgrates/etc/init.d/cgrates start + /usr/share/cgrates/tutorials/fs_evsock/cgrates/etc/init.d/cgrates start Check that cgrates is running @@ -49,7 +49,7 @@ Check that cgrates is running CDR processing -------------- -At the end of each call FreeSWITCH_ will issue a http post with the CDR. This will reach inside **CGRateS** through the *CDRS* component (close to real-time). Once in-there it will be instantly mediated and it is ready to be exported: +At the end of each call FreeSWITCH_ will issue a http post with the CDR. This will reach inside **CGRateS** through the *CDRS* component (close to real-time). Once in-there it will be instantly rated and it is ready to be exported: :: diff --git a/docs/tut_opensips_event.rst b/docs/tut_opensips_event.rst index ca94318fd..9f7473a35 100644 --- a/docs/tut_opensips_event.rst +++ b/docs/tut_opensips_event.rst @@ -12,8 +12,8 @@ Scenario - **CGRateS** with following components: - CGR-SM started as translator between OpenSIPS_ and **cgr-rater** for both authorization events (pseudoprepaid) as well as CDR ones. - - CGR-Mediator component attaching costs to the raw CDRs from OpenSIPS_ inside CGR StorDB. - - CGR-CDRE exporting mediated CDRs from CGR StorDB (export path: */tmp*). + - CGR-CDRS component processing raw CDRs from CGR-SM component and storing them inside CGR StorDB. + - CGR-CDRE exporting rated CDRs from CGR StorDB (export path: */tmp*). - CGR-History component keeping the archive of the rates modifications (path browsable with git client at */tmp/cgr_history*). @@ -22,7 +22,7 @@ Starting OpenSIPS_ with custom configuration :: - /usr/share/cgrates/tutorials/osips_event/opensips/etc/init.d/opensips start + /usr/share/cgrates/tutorials/osips_async/opensips/etc/init.d/opensips start To verify that OpenSIPS_ is running we run the console command: @@ -36,9 +36,9 @@ Starting **CGRateS** with custom configuration :: - /usr/share/cgrates/tutorials/osips_event/cgrates/etc/init.d/cgrates start + /usr/share/cgrates/tutorials/osips_async/cgrates/etc/init.d/cgrates start -Check that cgrates is running +Make sure that cgrates is running :: @@ -48,7 +48,7 @@ Check that cgrates is running CDR processing -------------- -At the end of each call OpenSIPS_ will generate an CDR event and due to automatic handler registration built in **CGRateS-SM** component, this will be directed towards the port configured inside *cgrates.cfg*. This event will reach inside **CGRateS** through the *SM* component (close to real-time). Once in-there it will be instantly mediated and it is ready to be exported. +At the end of each call OpenSIPS_ will generate an CDR event and due to automatic handler registration built in **CGRateS-SM** component, this will be directed towards the port configured inside *cgrates.json*. This event will reach inside **CGRateS** through the *SM* component (close to real-time). Once in-there it will be instantly rated and be ready for export. **CGRateS** Usage diff --git a/docs/tut_opensips_installs.rst b/docs/tut_opensips_installs.rst index 89aa1a6cc..2080e548e 100644 --- a/docs/tut_opensips_installs.rst +++ b/docs/tut_opensips_installs.rst @@ -12,9 +12,9 @@ We got OpenSIPS_ installed via following commands: wget http://apt.opensips.org/key.asc apt-key add key.asc cd /etc/apt/sources.list.d/ - wget http://apt.itsyscom.com/conf/opensips.apt.list + wget http://apt.itsyscom.com/conf/opensips.wheezy.apt.list apt-get update - apt-get install + apt-get install opensips opensips-json-module opensips-restclient-module Once installed we proceed with loading the configuration out of specific tutorial cases bellow. diff --git a/docs/tutorials.rst b/docs/tutorials.rst index ff937c41e..8e5d72b64 100644 --- a/docs/tutorials.rst +++ b/docs/tutorials.rst @@ -5,4 +5,5 @@ :maxdepth: 2 tut_freeswitch + tut_kamailio tut_opensips diff --git a/general_tests/tutorial_fs_calls_test.go b/general_tests/tutorial_fs_calls_test.go index 7183a3f34..54835be23 100644 --- a/general_tests/tutorial_fs_calls_test.go +++ b/general_tests/tutorial_fs_calls_test.go @@ -99,8 +99,7 @@ func TestTutFsCallsRestartFS(t *testing.T) { if !*testCalls { return } - engine.KillProcName("freeswitch", 5000) - if err := engine.CallScript(path.Join(*dataDir, "tutorials", "fs_evsock", "freeswitch", "etc", "init.d", "freeswitch"), "start", 3000); err != nil { + if err := engine.CallScript(path.Join(*dataDir, "tutorials", "fs_evsock", "freeswitch", "etc", "init.d", "freeswitch"), "start", 5000); err != nil { t.Fatal(err) } } diff --git a/general_tests/tutorial_kam_calls_test.go b/general_tests/tutorial_kam_calls_test.go index de34bbff0..12ce0cfb9 100644 --- a/general_tests/tutorial_kam_calls_test.go +++ b/general_tests/tutorial_kam_calls_test.go @@ -44,7 +44,7 @@ func TestTutKamCallsInitCfg(t *testing.T) { } // Init config first var err error - tutKamCallsCfg, err = config.NewCGRConfigFromFolder(path.Join(*dataDir, "tutorials", "fs_evsock", "cgrates", "etc", "cgrates")) + tutKamCallsCfg, err = config.NewCGRConfigFromFolder(path.Join(*dataDir, "tutorials", "kamevapi", "cgrates", "etc", "cgrates")) if err != nil { t.Error(err) } @@ -73,12 +73,12 @@ func TestTutKamCallsResetStorDb(t *testing.T) { } // start FS server -func TestTutKamCallsStartFS(t *testing.T) { +func TestTutKamCallsStartKamailio(t *testing.T) { if !*testCalls { return } - engine.KillProcName("freeswitch", 5000) - if err := engine.CallScript(path.Join(*dataDir, "tutorials", "fs_evsock", "freeswitch", "etc", "init.d", "freeswitch"), "start", 3000); err != nil { + engine.KillProcName("kamailio", 3000) + if err := engine.CallScript(path.Join(*dataDir, "tutorials", "kamevapi", "kamailio", "etc", "init.d", "kamailio"), "start", 2000); err != nil { t.Fatal(err) } } @@ -89,18 +89,17 @@ func TestTutKamCallsStartEngine(t *testing.T) { return } engine.KillProcName("cgr-engine", *waitRater) - if err := engine.CallScript(path.Join(*dataDir, "tutorials", "fs_evsock", "cgrates", "etc", "init.d", "cgrates"), "start", 100); err != nil { + if err := engine.CallScript(path.Join(*dataDir, "tutorials", "kamevapi", "cgrates", "etc", "init.d", "cgrates"), "start", 100); err != nil { t.Fatal(err) } } // Restart FS so we make sure reconnects are working -func TestTutKamCallsRestartFS(t *testing.T) { +func TestTutKamCallsRestartKamailio(t *testing.T) { if !*testCalls { return } - engine.KillProcName("freeswitch", 5000) - if err := engine.CallScript(path.Join(*dataDir, "tutorials", "fs_evsock", "freeswitch", "etc", "init.d", "freeswitch"), "start", 3000); err != nil { + if err := engine.CallScript(path.Join(*dataDir, "tutorials", "kamevapi", "kamailio", "etc", "init.d", "kamailio"), "restart", 3000); err != nil { t.Fatal(err) } } @@ -226,12 +225,12 @@ func TestTutKamCallsStartPjsuaListener(t *testing.T) { } var err error acnts := []*engine.PjsuaAccount{ - &engine.PjsuaAccount{Id: "sip:1001@127.0.0.1", Username: "1001", Password: "1234", Realm: "*", Registrar: "sip:127.0.0.1:5060"}, - &engine.PjsuaAccount{Id: "sip:1002@127.0.0.1", Username: "1002", Password: "1234", Realm: "*", Registrar: "sip:127.0.0.1:5060"}, - &engine.PjsuaAccount{Id: "sip:1003@127.0.0.1", Username: "1003", Password: "1234", Realm: "*", Registrar: "sip:127.0.0.1:5060"}, - &engine.PjsuaAccount{Id: "sip:1004@127.0.0.1", Username: "1004", Password: "1234", Realm: "*", Registrar: "sip:127.0.0.1:5060"}, - &engine.PjsuaAccount{Id: "sip:1006@127.0.0.1", Username: "1006", Password: "1234", Realm: "*", Registrar: "sip:127.0.0.1:5060"}, - &engine.PjsuaAccount{Id: "sip:1007@127.0.0.1", Username: "1007", Password: "1234", Realm: "*", Registrar: "sip:127.0.0.1:5060"}} + &engine.PjsuaAccount{Id: "sip:1001@127.0.0.1", Username: "1001", Password: "CGRateS.org", Realm: "*", Registrar: "sip:127.0.0.1:5060"}, + &engine.PjsuaAccount{Id: "sip:1002@127.0.0.1", Username: "1002", Password: "CGRateS.org", Realm: "*", Registrar: "sip:127.0.0.1:5060"}, + &engine.PjsuaAccount{Id: "sip:1003@127.0.0.1", Username: "1003", Password: "CGRateS.org", Realm: "*", Registrar: "sip:127.0.0.1:5060"}, + &engine.PjsuaAccount{Id: "sip:1004@127.0.0.1", Username: "1004", Password: "CGRateS.org", Realm: "*", Registrar: "sip:127.0.0.1:5060"}, + &engine.PjsuaAccount{Id: "sip:1006@127.0.0.1", Username: "1006", Password: "CGRateS.org", Realm: "*", Registrar: "sip:127.0.0.1:5060"}, + &engine.PjsuaAccount{Id: "sip:1007@127.0.0.1", Username: "1007", Password: "CGRateS.org", Realm: "*", Registrar: "sip:127.0.0.1:5060"}} if tutKamCallsPjSuaListener, err = engine.StartPjsuaListener(acnts, 5070, *waitRater); err != nil { t.Fatal(err) } @@ -242,7 +241,7 @@ func TestTutKamCallsCall1001To1002(t *testing.T) { if !*testCalls { return } - if err := engine.PjsuaCallUri(&engine.PjsuaAccount{Id: "sip:1001@127.0.0.1", Username: "1001", Password: "1234", Realm: "*"}, "sip:1002@127.0.0.1", + if err := engine.PjsuaCallUri(&engine.PjsuaAccount{Id: "sip:1001@127.0.0.1", Username: "1001", Password: "CGRateS.org", Realm: "*"}, "sip:1002@127.0.0.1", "sip:127.0.0.1:5060", time.Duration(67)*time.Second, 5071); err != nil { t.Fatal(err) } @@ -253,7 +252,7 @@ func TestTutKamCallsCall1001To1003(t *testing.T) { if !*testCalls { return } - if err := engine.PjsuaCallUri(&engine.PjsuaAccount{Id: "sip:1001@127.0.0.1", Username: "1001", Password: "1234", Realm: "*"}, "sip:1003@127.0.0.1", + if err := engine.PjsuaCallUri(&engine.PjsuaAccount{Id: "sip:1001@127.0.0.1", Username: "1001", Password: "CGRateS.org", Realm: "*"}, "sip:1003@127.0.0.1", "sip:127.0.0.1:5060", time.Duration(65)*time.Second, 5072); err != nil { t.Fatal(err) } @@ -263,7 +262,7 @@ func TestTutKamCallsCall1002To1001(t *testing.T) { if !*testCalls { return } - if err := engine.PjsuaCallUri(&engine.PjsuaAccount{Id: "sip:1002@127.0.0.1", Username: "1002", Password: "1234", Realm: "*"}, "sip:1001@127.0.0.1", + if err := engine.PjsuaCallUri(&engine.PjsuaAccount{Id: "sip:1002@127.0.0.1", Username: "1002", Password: "CGRateS.org", Realm: "*"}, "sip:1001@127.0.0.1", "sip:127.0.0.1:5060", time.Duration(61)*time.Second, 5073); err != nil { t.Fatal(err) } @@ -273,7 +272,7 @@ func TestTutKamCallsCall1003To1001(t *testing.T) { if !*testCalls { return } - if err := engine.PjsuaCallUri(&engine.PjsuaAccount{Id: "sip:1003@127.0.0.1", Username: "1003", Password: "1234", Realm: "*"}, "sip:1001@127.0.0.1", + if err := engine.PjsuaCallUri(&engine.PjsuaAccount{Id: "sip:1003@127.0.0.1", Username: "1003", Password: "CGRateS.org", Realm: "*"}, "sip:1001@127.0.0.1", "sip:127.0.0.1:5060", time.Duration(63)*time.Second, 5074); err != nil { t.Fatal(err) } @@ -283,7 +282,7 @@ func TestTutKamCallsCall1004To1001(t *testing.T) { if !*testCalls { return } - if err := engine.PjsuaCallUri(&engine.PjsuaAccount{Id: "sip:1004@127.0.0.1", Username: "1004", Password: "1234", Realm: "*"}, "sip:1001@127.0.0.1", + if err := engine.PjsuaCallUri(&engine.PjsuaAccount{Id: "sip:1004@127.0.0.1", Username: "1004", Password: "CGRateS.org", Realm: "*"}, "sip:1001@127.0.0.1", "sip:127.0.0.1:5060", time.Duration(62)*time.Second, 5075); err != nil { t.Fatal(err) } @@ -293,7 +292,7 @@ func TestTutKamCallsCall1006To1002(t *testing.T) { if !*testCalls { return } - if err := engine.PjsuaCallUri(&engine.PjsuaAccount{Id: "sip:1006@127.0.0.1", Username: "1006", Password: "1234", Realm: "*"}, "sip:1002@127.0.0.1", + if err := engine.PjsuaCallUri(&engine.PjsuaAccount{Id: "sip:1006@127.0.0.1", Username: "1006", Password: "CGRateS.org", Realm: "*"}, "sip:1002@127.0.0.1", "sip:127.0.0.1:5060", time.Duration(64)*time.Second, 5076); err != nil { t.Fatal(err) } @@ -303,7 +302,7 @@ func TestTutKamCallsCall1007To1002(t *testing.T) { if !*testCalls { return } - if err := engine.PjsuaCallUri(&engine.PjsuaAccount{Id: "sip:1007@127.0.0.1", Username: "1007", Password: "1234", Realm: "*"}, "sip:1002@127.0.0.1", + if err := engine.PjsuaCallUri(&engine.PjsuaAccount{Id: "sip:1007@127.0.0.1", Username: "1007", Password: "CGRateS.org", Realm: "*"}, "sip:1002@127.0.0.1", "sip:127.0.0.1:5060", time.Duration(66)*time.Second, 5077); err != nil { t.Fatal(err) } @@ -341,7 +340,7 @@ func TestTutKamCalls1001Cdrs(t *testing.T) { t.Error("Unexpected number of CDRs returned: ", len(reply)) } else { cgrId = reply[0].CgrId - if reply[0].CdrSource != "freeswitch_json" { + if reply[0].CdrSource != "KAMAILIO_CGR_CALL_END" { t.Errorf("Unexpected CdrSource for CDR: %+v", reply[0]) } if reply[0].ReqType != utils.META_PREPAID { @@ -415,7 +414,7 @@ func TestTutKamCalls1002Cdrs(t *testing.T) { } else if len(reply) != 1 { t.Error("Unexpected number of CDRs returned: ", len(reply)) } else { - if reply[0].CdrSource != "freeswitch_json" { + if reply[0].CdrSource != "KAMAILIO_CGR_CALL_END" { t.Errorf("Unexpected CdrSource for CDR: %+v", reply[0]) } if reply[0].ReqType != utils.META_POSTPAID { @@ -442,7 +441,7 @@ func TestTutKamCalls1003Cdrs(t *testing.T) { } else if len(reply) != 1 { t.Error("Unexpected number of CDRs returned: ", len(reply)) } else { - if reply[0].CdrSource != "freeswitch_json" { + if reply[0].CdrSource != "KAMAILIO_CGR_CALL_END" { t.Errorf("Unexpected CdrSource for CDR: %+v", reply[0]) } if reply[0].ReqType != utils.META_PSEUDOPREPAID { @@ -470,7 +469,7 @@ func TestTutKamCalls1004Cdrs(t *testing.T) { } else if len(reply) != 1 { t.Error("Unexpected number of CDRs returned: ", len(reply)) } else { - if reply[0].CdrSource != "freeswitch_json" { + if reply[0].CdrSource != "KAMAILIO_CGR_CALL_END" { t.Errorf("Unexpected CdrSource for CDR: %+v", reply[0]) } if reply[0].ReqType != utils.META_RATED { @@ -498,7 +497,7 @@ func TestTutKamCalls1006Cdrs(t *testing.T) { } else if len(reply) != 1 { t.Error("Unexpected number of CDRs returned: ", len(reply)) } else { - if reply[0].CdrSource != "freeswitch_json" { + if reply[0].CdrSource != "KAMAILIO_CGR_CALL_END" { t.Errorf("Unexpected CdrSource for CDR: %+v", reply[0]) } if reply[0].ReqType != utils.META_PREPAID { @@ -528,7 +527,7 @@ func TestTutKamCalls1007Cdrs(t *testing.T) { } else if len(reply) != 1 { t.Error("Unexpected number of CDRs returned: ", len(reply)) } else { - if reply[0].CdrSource != "freeswitch_json" { + if reply[0].CdrSource != "KAMAILIO_CGR_CALL_END" { t.Errorf("Unexpected CdrSource for CDR: %+v", reply[0]) } if reply[0].ReqType != utils.META_PREPAID { diff --git a/general_tests/tutorial_osips_calls_test.go b/general_tests/tutorial_osips_calls_test.go index 7a7fdceae..25fa947de 100644 --- a/general_tests/tutorial_osips_calls_test.go +++ b/general_tests/tutorial_osips_calls_test.go @@ -44,7 +44,7 @@ func TestTutOsipsCallsInitCfg(t *testing.T) { } // Init config first var err error - tutOsipsCallsCfg, err = config.NewCGRConfigFromFolder(path.Join(*dataDir, "tutorials", "fs_evsock", "cgrates", "etc", "cgrates")) + tutOsipsCallsCfg, err = config.NewCGRConfigFromFolder(path.Join(*dataDir, "tutorials", "osips_async", "cgrates", "etc", "cgrates")) if err != nil { t.Error(err) } @@ -73,12 +73,12 @@ func TestTutOsipsCallsResetStorDb(t *testing.T) { } // start FS server -func TestTutOsipsCallsStartFS(t *testing.T) { +func TestTutOsipsCallsStartOsips(t *testing.T) { if !*testCalls { return } - engine.KillProcName("freeswitch", 5000) - if err := engine.CallScript(path.Join(*dataDir, "tutorials", "fs_evsock", "freeswitch", "etc", "init.d", "freeswitch"), "start", 3000); err != nil { + engine.KillProcName("opensips", 3000) + if err := engine.CallScript(path.Join(*dataDir, "tutorials", "osips_async", "opensips", "etc", "init.d", "opensips"), "start", 3000); err != nil { t.Fatal(err) } } @@ -89,18 +89,17 @@ func TestTutOsipsCallsStartEngine(t *testing.T) { return } engine.KillProcName("cgr-engine", *waitRater) - if err := engine.CallScript(path.Join(*dataDir, "tutorials", "fs_evsock", "cgrates", "etc", "init.d", "cgrates"), "start", 100); err != nil { + if err := engine.CallScript(path.Join(*dataDir, "tutorials", "osips_async", "cgrates", "etc", "init.d", "cgrates"), "start", 100); err != nil { t.Fatal(err) } } // Restart FS so we make sure reconnects are working -func TestTutOsipsCallsRestartFS(t *testing.T) { +func TestTutOsipsCallsRestartOsips(t *testing.T) { if !*testCalls { return } - engine.KillProcName("freeswitch", 5000) - if err := engine.CallScript(path.Join(*dataDir, "tutorials", "fs_evsock", "freeswitch", "etc", "init.d", "freeswitch"), "start", 3000); err != nil { + if err := engine.CallScript(path.Join(*dataDir, "tutorials", "osips_async", "opensips", "etc", "init.d", "opensips"), "restart", 3000); err != nil { t.Fatal(err) } } @@ -341,7 +340,7 @@ func TestTutOsipsCalls1001Cdrs(t *testing.T) { t.Error("Unexpected number of CDRs returned: ", len(reply)) } else { cgrId = reply[0].CgrId - if reply[0].CdrSource != "freeswitch_json" { + if reply[0].CdrSource != "OSIPS_E_ACC_EVENT" { t.Errorf("Unexpected CdrSource for CDR: %+v", reply[0]) } if reply[0].ReqType != utils.META_PREPAID { @@ -415,7 +414,7 @@ func TestTutOsipsCalls1002Cdrs(t *testing.T) { } else if len(reply) != 1 { t.Error("Unexpected number of CDRs returned: ", len(reply)) } else { - if reply[0].CdrSource != "freeswitch_json" { + if reply[0].CdrSource != "OSIPS_E_ACC_EVENT" { t.Errorf("Unexpected CdrSource for CDR: %+v", reply[0]) } if reply[0].ReqType != utils.META_POSTPAID { @@ -442,7 +441,7 @@ func TestTutOsipsCalls1003Cdrs(t *testing.T) { } else if len(reply) != 1 { t.Error("Unexpected number of CDRs returned: ", len(reply)) } else { - if reply[0].CdrSource != "freeswitch_json" { + if reply[0].CdrSource != "OSIPS_E_ACC_EVENT" { t.Errorf("Unexpected CdrSource for CDR: %+v", reply[0]) } if reply[0].ReqType != utils.META_PSEUDOPREPAID { @@ -470,7 +469,7 @@ func TestTutOsipsCalls1004Cdrs(t *testing.T) { } else if len(reply) != 1 { t.Error("Unexpected number of CDRs returned: ", len(reply)) } else { - if reply[0].CdrSource != "freeswitch_json" { + if reply[0].CdrSource != "OSIPS_E_ACC_EVENT" { t.Errorf("Unexpected CdrSource for CDR: %+v", reply[0]) } if reply[0].ReqType != utils.META_RATED { @@ -498,7 +497,7 @@ func TestTutOsipsCalls1006Cdrs(t *testing.T) { } else if len(reply) != 1 { t.Error("Unexpected number of CDRs returned: ", len(reply)) } else { - if reply[0].CdrSource != "freeswitch_json" { + if reply[0].CdrSource != "OSIPS_E_ACC_EVENT" { t.Errorf("Unexpected CdrSource for CDR: %+v", reply[0]) } if reply[0].ReqType != utils.META_PREPAID { @@ -528,7 +527,7 @@ func TestTutOsipsCalls1007Cdrs(t *testing.T) { } else if len(reply) != 1 { t.Error("Unexpected number of CDRs returned: ", len(reply)) } else { - if reply[0].CdrSource != "freeswitch_json" { + if reply[0].CdrSource != "OSIPS_E_ACC_EVENT" { t.Errorf("Unexpected CdrSource for CDR: %+v", reply[0]) } if reply[0].ReqType != utils.META_PREPAID { From 18c3161b8dca255f0e45d3816eb143f3a5a2c189 Mon Sep 17 00:00:00 2001 From: DanB Date: Tue, 18 Aug 2015 13:06:05 +0200 Subject: [PATCH 19/20] Kamailio tutorial doc files --- docs/tut_kamailio.rst | 18 +++++++++++ docs/tut_kamailio_evapi.rst | 59 ++++++++++++++++++++++++++++++++++ docs/tut_kamailio_installs.rst | 20 ++++++++++++ 3 files changed, 97 insertions(+) create mode 100644 docs/tut_kamailio.rst create mode 100644 docs/tut_kamailio_evapi.rst create mode 100644 docs/tut_kamailio_installs.rst diff --git a/docs/tut_kamailio.rst b/docs/tut_kamailio.rst new file mode 100644 index 000000000..035305881 --- /dev/null +++ b/docs/tut_kamailio.rst @@ -0,0 +1,18 @@ +Kamailio_ Integration Tutorials +=============================== + +In these tutorials we exemplify few cases of integration between Kamailio_ and CGRateS_. We start with common steps, installation and postinstall processes then we dive into particular configurations, depending on the case we run. + + +.. toctree:: + :maxdepth: 2 + + tut_kamailio_installs + tut_cgrates_installs + tut_jitsi_installs + tut_kamailio_evapi + tut_cgrates_usage + +.. _Kamailio: http://www.kamailio.org/ +.. _CGRateS: http://www.cgrates.org/ + diff --git a/docs/tut_kamailio_evapi.rst b/docs/tut_kamailio_evapi.rst new file mode 100644 index 000000000..04df505ff --- /dev/null +++ b/docs/tut_kamailio_evapi.rst @@ -0,0 +1,59 @@ +Kamailio_ interaction via *evapi* module +========================================= + +Scenario +-------- + + - Kamailio default configuration modified for **CGRateS** interaction. For script maintainability and simplicity we have separated CGRateS specific routes in *kamailio-cgrates.cfg* file which is included in main *kamailio.cfg* via include directive. + + - Considering the following users (with configs hardcoded in the *kamailio.cfg* configuration script and loaded in htable): 1001-prepaid, 1002-postpaid, 1003-pseudoprepaid, 1004-rated, 1005-rated, 1006-prepaid, 1007-prepaid. + +- **CGRateS** with following components: + + - CGR-SM started as translator between Kamailio_ and CGR-Rater for both authorization events as well as accounting ones. + - CGR-CDRS component processing raw CDRs from CGR-SM component and storing them inside CGR StorDB. + - CGR-CDRE exporting rated CDRs from CGR StorDB (export path: */tmp*). + - CGR-History component keeping the archive of the rates modifications (path browsable with git client at */tmp/cgr_history*). + + +Starting Kamailio_ with custom configuration +---------------------------------------------- + +:: + + /usr/share/cgrates/tutorials/kamevapi/kamailio/etc/init.d/kamailio start + +To verify that Kamailio_ is running we run the console command: + +:: + + kamctl moni + + +Starting **CGRateS** with custom configuration +---------------------------------------------- + +:: + + /usr/share/cgrates/tutorials/kamevapi/cgrates/etc/init.d/cgrates start + +Make sure that cgrates is running + +:: + + cgr-console status + + +CDR processing +-------------- + +At the end of each call Kamailio_ will generate an CDR event via *evapi* and this will be directed towards the port configured inside *cgrates.json*. This event will reach inside **CGRateS** through the *SM* component (close to real-time). Once in-there it will be instantly rated and be ready for export. + + +**CGRateS** Usage +----------------- + +Since it is common to most of the tutorials, the example for **CGRateS** usage is provided in a separate page `here `_ + + +.. _Kamailio: http://www.kamailio.org/ diff --git a/docs/tut_kamailio_installs.rst b/docs/tut_kamailio_installs.rst new file mode 100644 index 000000000..ff44d32e1 --- /dev/null +++ b/docs/tut_kamailio_installs.rst @@ -0,0 +1,20 @@ +Software installation +===================== + +As operating system we have choosen Debian Wheezy, since all the software components we use provide packaging for it. + +Kamailio_ +--------- + +We got Kamailio_ installed via following commands: +:: + + apt-key adv --recv-keys --keyserver keyserver.ubuntu.com 0xfb40d3e6508ea4c8 + cd /etc/apt/sources.list.d/ + wget http://apt.itsyscom.com/conf/kamailio.apt.list . + apt-get update + apt-get install kamailio kamailio-extra-modules kamailio-json-modules + +Once installed we proceed with loading the configuration out of specific tutorial cases bellow. + +.. _Kamailio: http://www.kamailio.org/ \ No newline at end of file From 9db4948f3b802b1ee9d7608f4df6b8a96e305ed6 Mon Sep 17 00:00:00 2001 From: DanB Date: Tue, 18 Aug 2015 13:38:04 +0200 Subject: [PATCH 20/20] Small fix tutorial fs --- general_tests/tutorial_fs_calls_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/general_tests/tutorial_fs_calls_test.go b/general_tests/tutorial_fs_calls_test.go index 54835be23..6ae5fa55f 100644 --- a/general_tests/tutorial_fs_calls_test.go +++ b/general_tests/tutorial_fs_calls_test.go @@ -99,7 +99,7 @@ func TestTutFsCallsRestartFS(t *testing.T) { if !*testCalls { return } - if err := engine.CallScript(path.Join(*dataDir, "tutorials", "fs_evsock", "freeswitch", "etc", "init.d", "freeswitch"), "start", 5000); err != nil { + if err := engine.CallScript(path.Join(*dataDir, "tutorials", "fs_evsock", "freeswitch", "etc", "init.d", "freeswitch"), "restart", 5000); err != nil { t.Fatal(err) } }