From 0937d374e57e827425951fa15631b402b1aa65e6 Mon Sep 17 00:00:00 2001 From: ionutboangiu Date: Fri, 9 May 2025 15:33:48 +0300 Subject: [PATCH] remove outdated documentation files --- docs/Makefile | 130 ----- docs/administration.rst | 10 - docs/admins.rst | 5 - docs/agents.rst | 24 - docs/architecture.rst | 16 - docs/astagent.rst | 5 - docs/attributes.rst | 142 ------ docs/caches.rst | 7 - docs/cdrs.rst | 120 ----- docs/cgr-console.rst | 43 -- docs/cgr-engine.rst | 87 ---- docs/cgr-loader.rst | 119 ----- docs/cgr-migrator.rst | 107 ---- docs/cgr-tester.rst | 97 ---- docs/chargers.rst | 66 --- docs/datadb.rst | 291 ----------- docs/diamagent.rst | 404 --------------- docs/dnsagent.rst | 5 - docs/ees.rst | 251 ---------- docs/ers.rst | 345 ------------- docs/filters.rst | 132 ----- docs/fsagent.rst | 5 - docs/guardian.rst | 39 -- docs/httpagent.rst | 5 - docs/images/CGRateSInternalArchitecture.png | Bin 96708 -> 0 bytes docs/index.rst | 19 +- docs/installation.rst | 153 ------ docs/janusagent.rst | 39 -- docs/kamagent.rst | 5 - docs/loaders.rst | 5 - docs/make.bat | 170 ------- docs/overview.rst | 200 -------- docs/prometheus.rst | 105 ---- docs/radagent.rst | 5 - docs/rankings.rst | 166 ------- docs/rates.rst | 5 - docs/resources.rst | 136 ----- docs/routes.rst | 190 ------- docs/rpcconns.rst | 161 ------ docs/sessions.rst | 390 --------------- docs/stats.rst | 146 ------ docs/stordb.rst | 7 - docs/tariffplans.rst | 19 - docs/thresholds.rst | 150 ------ docs/trends.rst | 168 ------- docs/tutorial.rst | 521 -------------------- 46 files changed, 1 insertion(+), 5214 deletions(-) delete mode 100644 docs/Makefile delete mode 100644 docs/administration.rst delete mode 100644 docs/admins.rst delete mode 100644 docs/agents.rst delete mode 100644 docs/architecture.rst delete mode 100644 docs/astagent.rst delete mode 100644 docs/attributes.rst delete mode 100644 docs/caches.rst delete mode 100644 docs/cdrs.rst delete mode 100644 docs/cgr-console.rst delete mode 100644 docs/cgr-engine.rst delete mode 100644 docs/cgr-loader.rst delete mode 100644 docs/cgr-migrator.rst delete mode 100644 docs/cgr-tester.rst delete mode 100644 docs/chargers.rst delete mode 100644 docs/datadb.rst delete mode 100644 docs/diamagent.rst delete mode 100644 docs/dnsagent.rst delete mode 100644 docs/ees.rst delete mode 100644 docs/ers.rst delete mode 100644 docs/filters.rst delete mode 100644 docs/fsagent.rst delete mode 100644 docs/guardian.rst delete mode 100644 docs/httpagent.rst delete mode 100644 docs/images/CGRateSInternalArchitecture.png delete mode 100644 docs/janusagent.rst delete mode 100644 docs/kamagent.rst delete mode 100644 docs/loaders.rst delete mode 100644 docs/make.bat delete mode 100644 docs/overview.rst delete mode 100644 docs/prometheus.rst delete mode 100644 docs/radagent.rst delete mode 100644 docs/rankings.rst delete mode 100644 docs/rates.rst delete mode 100644 docs/resources.rst delete mode 100644 docs/routes.rst delete mode 100644 docs/rpcconns.rst delete mode 100644 docs/sessions.rst delete mode 100644 docs/stats.rst delete mode 100644 docs/stordb.rst delete mode 100644 docs/tariffplans.rst delete mode 100644 docs/thresholds.rst delete mode 100644 docs/trends.rst delete mode 100644 docs/tutorial.rst diff --git a/docs/Makefile b/docs/Makefile deleted file mode 100644 index 028b6b963..000000000 --- a/docs/Makefile +++ /dev/null @@ -1,130 +0,0 @@ -# Makefile for Sphinx documentation -# - -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -PAPER = -BUILDDIR = _build - -# Internal variables. -PAPEROPT_a4 = -D latex_paper_size=a4 -PAPEROPT_letter = -D latex_paper_size=letter -ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . - -.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest - -help: - @echo "Please use \`make ' where is one of" - @echo " html to make standalone HTML files" - @echo " dirhtml to make HTML files named index.html in directories" - @echo " singlehtml to make a single large HTML file" - @echo " pickle to make pickle files" - @echo " json to make JSON files" - @echo " htmlhelp to make HTML files and a HTML help project" - @echo " qthelp to make HTML files and a qthelp project" - @echo " devhelp to make HTML files and a Devhelp project" - @echo " epub to make an epub" - @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" - @echo " latexpdf to make LaTeX files and run them through pdflatex" - @echo " text to make text files" - @echo " man to make manual pages" - @echo " changes to make an overview of all changed/added/deprecated items" - @echo " linkcheck to check all external links for integrity" - @echo " doctest to run all doctests embedded in the documentation (if enabled)" - -clean: - -rm -rf $(BUILDDIR)/* - -html: - $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." - -dirhtml: - $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." - -singlehtml: - $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml - @echo - @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." - -pickle: - $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle - @echo - @echo "Build finished; now you can process the pickle files." - -json: - $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json - @echo - @echo "Build finished; now you can process the JSON files." - -htmlhelp: - $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp - @echo - @echo "Build finished; now you can run HTML Help Workshop with the" \ - ".hhp project file in $(BUILDDIR)/htmlhelp." - -qthelp: - $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp - @echo - @echo "Build finished; now you can run "qcollectiongenerator" with the" \ - ".qhcp project file in $(BUILDDIR)/qthelp, like this:" - @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/CGRates.qhcp" - @echo "To view the help file:" - @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/CGRates.qhc" - -devhelp: - $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp - @echo - @echo "Build finished." - @echo "To view the help file:" - @echo "# mkdir -p $$HOME/.local/share/devhelp/CGRates" - @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/CGRates" - @echo "# devhelp" - -epub: - $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub - @echo - @echo "Build finished. The epub file is in $(BUILDDIR)/epub." - -latex: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo - @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." - @echo "Run \`make' in that directory to run these through (pdf)latex" \ - "(use \`make latexpdf' here to do that automatically)." - -latexpdf: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through pdflatex..." - make -C $(BUILDDIR)/latex all-pdf - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - -text: - $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text - @echo - @echo "Build finished. The text files are in $(BUILDDIR)/text." - -man: - $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man - @echo - @echo "Build finished. The manual pages are in $(BUILDDIR)/man." - -changes: - $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes - @echo - @echo "The overview file is in $(BUILDDIR)/changes." - -linkcheck: - $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck - @echo - @echo "Link check complete; look for any errors in the above output " \ - "or in $(BUILDDIR)/linkcheck/output.txt." - -doctest: - $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest - @echo "Testing of doctests in the sources finished, look at the " \ - "results in $(BUILDDIR)/doctest/output.txt." diff --git a/docs/administration.rst b/docs/administration.rst deleted file mode 100644 index c9d6a12c9..000000000 --- a/docs/administration.rst +++ /dev/null @@ -1,10 +0,0 @@ -Administration -============== - -The general steps to get CGRateS operational are: - -#. Create CSV files containing the initial data for CGRateS. -#. Load the data in the databases using the Loader application. -#. Start the engine with the required components (depending on your setup). -#. Make API calls to the component you are interested or send events from your environment towards the :ref:`SessionS` via the :ref:`Agents`. - diff --git a/docs/admins.rst b/docs/admins.rst deleted file mode 100644 index 8f5b80f6a..000000000 --- a/docs/admins.rst +++ /dev/null @@ -1,5 +0,0 @@ -AdminS -====== - - -TBD diff --git a/docs/agents.rst b/docs/agents.rst deleted file mode 100644 index 2f6825191..000000000 --- a/docs/agents.rst +++ /dev/null @@ -1,24 +0,0 @@ -.. _Agents: -Agents -====== - -**Agents** are interfaces towards external systems, implementing protocols enforced by the communication channels opened. -These can be standard or privately defined. - -All of the **Agents** implemented within CGRateS are flexible to be configured with generic parameters configurable for both *request* and *replies*. - -Following *Agents* are implemented within CGRateS: - -.. toctree:: - :maxdepth: 1 - - diamagent - radagent - httpagent - dnsagent - astagent - fsagent - kamagent - ers - janusagent - prometheus diff --git a/docs/architecture.rst b/docs/architecture.rst deleted file mode 100644 index 7c876e391..000000000 --- a/docs/architecture.rst +++ /dev/null @@ -1,16 +0,0 @@ - -************ -Architecture -************ - -The CGRateS framework consists of functionality packed within **five** software applications, described below. - -.. toctree:: - :maxdepth: 2 - - cgr-engine - cgr-console - cgr-loader - cgr-migrator - cgr-tester - diff --git a/docs/astagent.rst b/docs/astagent.rst deleted file mode 100644 index 9a2b40da8..000000000 --- a/docs/astagent.rst +++ /dev/null @@ -1,5 +0,0 @@ -AsteriskAgent -============= - - -TBD \ No newline at end of file diff --git a/docs/attributes.rst b/docs/attributes.rst deleted file mode 100644 index f6e976a93..000000000 --- a/docs/attributes.rst +++ /dev/null @@ -1,142 +0,0 @@ -.. _attributes: - -AttributeS -========== - -**AttributeS** is a standalone subsystem within **CGRateS** and it is the equivalent of a key-value store. It is accessed via `CGRateS RPC APIs `_. - -As most of the other subsystems, it is performance oriented, stored inside *DataDB* but cached inside the *cgr-engine* process. -Caching can be done dynamically/on-demand or at start-time/precached and it is configurable within *cache* section in the :ref:`JSON configuration `. - -Selection ---------- - -It is able to process generic events (hashmaps) and decision for matching it is outsourced to :ref:`FilterS`. - -If there are multiple profiles (configurations) matching, the one with highest *Weight* will be the winner. There can be only one *AttributeProfile* processing the event per *process run*. If one configures multiple *process runs* either in :ref:`JSON configuration ` or as parameter to the *.ProcessEvent* API call, the output event from one *process run* will be forwarded as input to the next selected profile. There will be independent *AttributeProfile* selection performed for each run, hence the event fields modified in one run can be applied as filters to the next *process run*, giving out the possibility to chain *AttributeProfiles* and have multiple replacements with a minimum of performance penalty (in-memory matching). - - -Parameters ----------- - - -AttributeS -^^^^^^^^^^ - -**AttributeS** is the **CGRateS** component responsible of handling the *AttributeProfiles*. - -It is configured within **attributes** section from :ref:`JSON configuration ` via the following parameters: - -enabled - Will enable starting of the service. Possible values: . - -indexed_selects - Enable profile matching exclusively on indexes. If not enabled, the *ResourceProfiles* are checked one by one which for a larger number can slow down the processing time. Possible values: . - -string_indexed_fields - Query string indexes based only on these fields for faster processing. If commented out, each field from the event will be checked against indexes. If uncommented and defined as empty list, no fields will be checked. - -prefix_indexed_fields - Query prefix indexes based only on these fields for faster processing. If defined as empty list, no fields will be checked. - -nested_fields - Applied when all event fields are checked against indexes, and decides whether subfields are also checked. - -process_runs - Limit the number of loops when processing an Event. The event loop is however clever enough to stop when the same processing occurs or no more additional profiles are matching, so higher numbers are ignored if not needed. - -.. _AttributeProfile: - -AttributeProfile -^^^^^^^^^^^^^^^^ - -Represents the configuration for a group of attributes applied. - -Tenant - The tenant on the platform (one can see the tenant as partition ID) - -ID - Identifier for the *AttributeProfile*, unique within a *Tenant* - -Context - A list of *contexts* applying to this profile. A *context* is usually associated with a logical phase during event processing (ie: *\*sessions* or *\*cdrs* for events parsed by :ref:`SessionS` or :ref:`CDRs`) - -FilterIDs - List of *FilterProfiles* which should match in order to consider the *AttributeProfile* matching the event. - -ActivationInterval - The time interval when this profile becomes active. If undefined, the profile is always active. Other options are start time, end time or both. - -Blocker - In case of multiple *process runs* are allowed, this flag will break further processing. - -Weight - Used in case of multiple profiles matching an event. The higher, the better (0 has lowest possible priority). - -Attributes - List of :ref:`Attribute` objects part of this profile. - - -.. _Attribute: - -Attribute -^^^^^^^^^ - -FilterIDs - List of *FilterProfiles* which should match in order to consider the *Attribute* matching the event. - -Path - Identifying the targeted absolute path within the processed event. - -Type - Represents the type of substitution which will be performed on the Event. The following *Types* are available: - - **\*constant** - The *Value* is a constant value, it will just set the *FieldName* to this value as it is. - - **\*variable** - The *Value* is a *RSRParser* which will be able to capture the value out of one or more fields in the event (also combined with other constants) and write it to *Path*. - - **\*composed** - Same as *\*variable* but instead of overwriting *Path*, it will append to it. - - **\*usage_difference** - Will calculate the duration difference between two field names defined in the *Value*. If the number of fields in the *Value* are different than 2, it will error. - - **\*sum** - Will sum up the values in the *Value*. - - **\*value_exponent** - Will compute the exponent of the first field in the *Value*. - -Value - The value which will be set for *Path*. It can be a list of RSRParsers capturing even from multiple sources in the same event. If the *Value* is *\*remove* the field with *Path* will be removed from *Event* - - -Inline Attribute -^^^^^^^^^^^^^^^^ - -In order to facilitate quick attribute definition (without the need of separate *AttributeProfile*), one can define attributes directly as *AttributeIDs* following the special format. - -Inline filter format:: - - attributeType:attributePath:attributeValue - -Example:: - - *constant:*req.RequestType:*prepaid - - -Use cases ---------- - -* Fields aliasing - * Number portability (replacing a dialed number with it's translation) - * Roaming (using *Category* to point out the zone where the user is roaming in so we can apply different rating or consume out of restricted account bundles). - -* Appending new fields - * Adding separate header with location information - * Adding additional rating information (ie: SMS only contains origin+destination, add *Tenant*, *Account*, *Subject*, *RequestType*) - * Using as query language (ie: append user password for a given user so we can perform authorization on SIP Proxy side). - - diff --git a/docs/caches.rst b/docs/caches.rst deleted file mode 100644 index 70f8b6686..000000000 --- a/docs/caches.rst +++ /dev/null @@ -1,7 +0,0 @@ -.. _caches: - -CacheS -====== - - -TBD \ No newline at end of file diff --git a/docs/cdrs.rst b/docs/cdrs.rst deleted file mode 100644 index 8d69b4463..000000000 --- a/docs/cdrs.rst +++ /dev/null @@ -1,120 +0,0 @@ -.. _CDRs: - -CDRs -==== - - -**CDRs** is a standalone subsystem within **CGRateS** responsible to process *CDR* events. It is accessed via `CGRateS RPC APIs `_ or separate *HTTP handlers* configured within *http* section inside :ref:`JSON configuration `. - -Due to multiple interfaces exposed, the **CDRs** is designed to function as centralized server for *CDRs* received from various sources. Examples of such sources are: - *\*real-time events* from interfaces like *Diameter*, *Radius*, *Asterisk*, *FreeSWITCH*, *Kamailio*, *OpenSIPS* - * \*files* like *.csv*, *.fwv*, *.xml*, *.json*. - * \*database events* like *sql*, *kafka*, *rabbitmq*. - -Parameters ----------- - - -CDRs -^^^^ - -**CDRs** is configured within **cdrs** section from :ref:`JSON configuration ` via the following parameters: - -enabled - Will enable starting of the service. Possible values: . - -extra_fields - Select extra fields from the request, other than the primary ones used by CGRateS (see storage schemas for listing those). Used in particular applications where the received fields are not selectable at the source(ie. FreeSWITCH JSON). - -store_cdrs - Controls storing of the received CDR within the *StorDB*. Possible values: . - -session_cost_retries - In case of decoupling the events charging from CDRs, the charges done by :ref:`SessionS` will be stored in *sessions_costs* *StorDB* table. When receiving the CDR, these costs will be retrieved and attached to the CDR. To avoid concurrency between events and CDRs, it is possible to configure a multiple number of retries from *StorDB* table. - -chargers_conns - Connections towards :ref:`ChargerS` component to query charges for CDR events. Empty to disable the functionality. - -rals_conns - Connections towards :ref:`RALs` component to query costs for CDR events. Empty to disable the functionality. - -attributes_conns - Connections towards :ref:`AttributeS` component to alter information within CDR events. Empty to disable the functionality. - -thresholds_conns - Connections towards :ref:`ThresholdS` component to monitor and react to information within CDR events. Empty to disable the functionality. - -stats_conns - Connections towards :ref:`StatS` component to compute stat metrics for CDR events. Empty to disable the functionality. - -online_cdr_exports - List of :ref:`EEs` profiles which will be processed for each CDR event. Empty to disable online CDR exports. - - -Export types ------------- - -There are two types of exports with common configuration but different data sources: - - -Online exports -^^^^^^^^^^^^^^ - -Are real-time exports, triggered by the CDR event processed by :ref:`CDRs`, and take these events as data source. - -The *online exports* are enabled via *online_cdr_exports* :ref:`JSON configuration ` option within *cdrs*. - -You can control the templates which are to be executed via the filters which are applied for each export template individually. - - -Offline exports -^^^^^^^^^^^^^^^ - -Are exports which are triggered via `CGRateS RPC APIs `_ and they have as data source the CDRs stored within *StorDB*. - - -APIs logic ----------- - -ProcessEvent -^^^^^^^^^^^^ - -Receives the CDR in the form of *CGRateS Event* together with processing flags attached. Activating of the flags will trigger specific processing mechanisms for the CDR. Missing of the flags will be interpreted based on defaults. The following flags are available, based on the processing order: - -\*attributes - Will process the event with :ref:`AttributeS`. This allows modification of content in early stages of processing(ie: add new fields, modify or remove others). Defaults to *true* if there are connections towards :ref:`AttributeS` within :ref:`JSON configuration `. - -\*chargers - Will process the event with :ref:`ChargerS`. This allows forking of the event into multiples. Defaults to *true* if there are connections towards :ref:`ChargerS` within :ref:`JSON configuration `. - -\*refund - Will perform a refund for the *CostDetails* field in the event. Defaults to *false*. - -\*rals - Will calculate the *Cost* for the event using the :ref:`RALs`. If the event is *\*prepaid* the *Cost* will be attempted to be retrieved out of event or from *sessions_costs* table in the *StorDB* and if these two steps fail, :ref:`RALs` will be queried in the end. Defaults to *false*. - -\*rerate - Will re-rate the CDR as per the *\*rals* flag, doing also an automatic refund in case of *\*prepaid*, *\*postpaid* and *\*pseudoprepaid* request types. Defaults to *false*. - -\*store - Will store the *CDR* to *StorDB*. Defaults to *store_cdrs* parameter within :ref:`JSON configuration `. If store process fails for one of the CDRs, an automated refund is performed for all derived. - -\*export - Will export the event matching export profiles. These profiles are defined within *ees* section inside :ref:`JSON configuration `. Defaults to *true* if there is at least one *online_cdr_exports* profile configured within :ref:`JSON configuration `. - -\*thresholds - Will process the event with the :ref:`ThresholdS`, allowing us to execute actions based on filters set for matching profiles. Defaults to *true* if there are connections towards :ref:`ThresholdS` within :ref:`JSON configuration `. - -\*stats - Will process the event with the :ref:`StatS`, allowing us to compute metrics based on the matching *StatQueues*. Defaults to *true* if there are connections towards :ref:`StatS` within :ref:`JSON configuration `. - - -Use cases ---------- - -* Classic rating of your CDRs. -* Rating queues where one can receive the rated CDR few milliseconds after the *CommSwitch* has issued it. With custom export profiles there can be given the feeling that the *CommSwitch* itself sends rated CDRs. -* Rating with derived charging where we calculate automatically the cost for the same CDR multiple times (ie: supplier/customer, customer/distributor or local/premium/mobile charges). -* Fraud detection on CDR Costs with profiling. -* Improve network transparency based on monitoring Cost, ASR, ACD, PDD out of CDRs. - diff --git a/docs/cgr-console.rst b/docs/cgr-console.rst deleted file mode 100644 index 1600dfef9..000000000 --- a/docs/cgr-console.rst +++ /dev/null @@ -1,43 +0,0 @@ -.. _cgr-console: - -cgr-console ------------ - -Command line tool used to interface via the APIs implemented within :ref:cgr-engine. - -Configurable via command line arguments. - -:: - - $ cgr-console -help - Usage of cgr-console: - -ca_path string - path to CA for tls connection(only for self sign certificate) - -connect_attempts int - Connect attempts (default 3) - -connect_timeout int - Connect timeout in seconds (default 1) - -crt_path string - path to certificate for tls connection - -key_path string - path to key for tls connection - -max_reconnect_interval int - Maximum reconnect interval - -reconnects int - Reconnect attempts (default 3) - -reply_timeout int - Reply timeout in seconds (default 300) - -rpc_encoding string - RPC encoding used <*gob|*json> (default "*json") - -server string - server address host:port (default "127.0.0.1:2012") - -tls - TLS connection - -verbose - Show extra info about command execution. - -version - Prints the application version. - - - -.. hint:: # cgr-console status \ No newline at end of file diff --git a/docs/cgr-engine.rst b/docs/cgr-engine.rst deleted file mode 100644 index f863771f9..000000000 --- a/docs/cgr-engine.rst +++ /dev/null @@ -1,87 +0,0 @@ -.. _cgr-engine: - -cgr-engine -========== - -Groups most of functionality from services and components. - -Customisable through the use of *json* :ref:`JSON configuration ` or command line arguments (higher prio). - -Able to read the configuration from either a local directory of *.json* files with an unlimited number of subfolders (ordered alphabetically) or a list of http paths (separated by ";"). - - -:: - -$ cgr-engine -help -Usage of cgr-engine: - -check_config - Verify the config without starting the engine - -config_path string - Configuration directory path (default "/etc/cgrates/") - -cpuprof_dir string - Directory for CPU profiles - -log_level int - Log level (0=emergency to 7=debug) (default -1) - -logger string - Logger type <*syslog|*stdout|*kafkaLog> - -memprof_dir string - Directory for memory profiles - -memprof_interval duration - Interval between memory profile saves (default 15s) - -memprof_maxfiles int - Number of memory profiles to keep (most recent) (default 1) - -memprof_timestamp - Add timestamp to memory profile files - -node_id string - Node ID of the engine - -pid string - Path to write the PID file - -preload value - Loader IDs used to load data before engine starts - -scheduled_shutdown duration - Shutdown the engine after the specified duration - -set_versions - Overwrite database versions - -single_cpu - Run on a single CPU core - -version - Print application version and exit - - -.. hint:: $ cgr-engine -config_path=/etc/cgrates - -.. figure:: images/CGRateSInternalArchitecture.png - :alt: CGRateS Internal Architecture - :align: Center - :scale: 75 % - - - Internal Architecture of **cgr-engine** - - -The components from the diagram can be found documented in the links bellow: - -.. toctree:: - :maxdepth: 1 - - agents - sessions - rates - cdrs - ees - attributes - chargers - resources - routes - stats - trends - thresholds - filters - admins - loaders - caches - guardian - datadb - stordb - rpcconns - diff --git a/docs/cgr-loader.rst b/docs/cgr-loader.rst deleted file mode 100644 index fe17e118a..000000000 --- a/docs/cgr-loader.rst +++ /dev/null @@ -1,119 +0,0 @@ -.. _cgr-loader: - -cgr-loader ----------- - -Tool used to load/import TariffPlan data into CGRateS databases. - -Can be used to: - * load TariffPlan data from **csv files** to **DataDB**. - * import TariffPlan data from **csv files** to **StorDB** as offline data. ``-to_stordb -tpid`` - * import TariffPlan data from **StorDB** to **DataDB**. ``-from_stordb -tpid`` - -Customisable through the use of :ref:`JSON configuration ` or command line arguments (higher prio). - - -:: - - $ cgr-loader -h - Usage of cgr-loader: - -api_key string - Api Key used to comosed ArgDispatcher - -caches_address string - CacheS component to contact for cache reloads, empty to disable automatic cache reloads (default "*localhost") - -caching string - Caching strategy used when loading TP - -caching_delay duration - Adds delay before cache reload - -config_path string - Configuration directory path. - -datadb_host string - The DataDb host to connect to. (default "127.0.0.1") - -datadb_name string - The name/number of the DataDb to connect to. (default "10") - -datadb_passwd string - The DataDb user's password. - -datadb_port string - The DataDb port to bind to. (default "6379") - -datadb_type string - The type of the DataDB database <*redis|*mongo> (default "*redis") - -datadb_user string - The DataDb user to sign in as. (default "cgrates") - -dbdata_encoding string - The encoding used to store object data in strings (default "msgpack") - -disable_reverse_mappings - Will disable reverse mappings rebuilding - -dry_run - When true will not save loaded data to dataDb but just parse it for consistency and errors. - -field_sep string - Separator for csv file (by default "," is used) (default ",") - -flush_stordb - Remove tariff plan data for id from the database - -from_stordb - Load the tariff plan from storDb to dataDb - -import_id string - Uniquely identify an import/load, postpended to some automatic fields - -mongoConnScheme string - Scheme for MongoDB connection (default "mongodb") - -mongoQueryTimeout duration - The timeout for queries (default 10s) - -path string - The path to folder containing the data files (default "./") - -redisCACertificate string - Path to the CA certificate - -redisClientCertificate string - Path to the client certificate - -redisClientKey string - Path to the client key - -redisCluster - Is the redis datadb a cluster - -redisClusterOndownDelay duration - The delay before executing the commands if the redis cluster is in the CLUSTERDOWN state - -redisClusterSync duration - The sync interval for the redis cluster (default 5s) - -redisConnectAttempts int - The maximum amount of dial attempts (default 20) - -redisConnectTimeout duration - The amount of wait time until timeout for a connection attempt - -redisMaxConns int - The connection pool size (default 10) - -redisReadTimeout duration - The amount of wait time until timeout for reading operations - -redisSentinel string - The name of redis sentinel - -redisTLS - Enable TLS when connecting to Redis - -redisWriteTimeout duration - The amount of wait time until timeout for writing operations - -remove - Will remove instead of adding data from DB - -route_id string - RouteID used to comosed ArgDispatcher - -rpc_encoding string - RPC encoding used <*gob|*json> (default "*json") - -scheduler_address string - (default "*localhost") - -stordb_host string - The storDb host to connect to. (default "127.0.0.1") - -stordb_name string - The name/number of the storDb to connect to. (default "cgrates") - -stordb_passwd string - The storDb user's password. (default "CGRateS.org") - -stordb_port string - The storDb port to bind to. (default "3306") - -stordb_type string - The type of the storDb database <*mysql|*postgres|*mongo> (default "*mysql") - -stordb_user string - The storDb user to sign in as. (default "cgrates") - -tenant string - (default "cgrates.org") - -timezone string - Timezone for timestamps where not specified <""|UTC|Local|$IANA_TZ_DB> (default "Local") - -to_stordb - Import the tariff plan from files to storDb - -tpid string - The tariff plan ID from the database - -verbose - Enable detailed verbose logging output - -version - Prints the application version. diff --git a/docs/cgr-migrator.rst b/docs/cgr-migrator.rst deleted file mode 100644 index 2a7070546..000000000 --- a/docs/cgr-migrator.rst +++ /dev/null @@ -1,107 +0,0 @@ -.. _cgr-migrator: - -cgr-migrator ------------- - -Command line migration tool. - -Customisable through the use of :ref:`JSON configuration ` or command line arguments (higher prio). - -:: - - $ cgr-migrator -h - Usage of cgr-migrator: - -config_path string - Configuration directory path. - -datadb_host string - the DataDB host (default "127.0.0.1") - -datadb_name string - the name/number of the DataDB (default "10") - -datadb_passwd string - the DataDB password - -datadb_port string - the DataDB port (default "6379") - -datadb_type string - the type of the DataDB Database <*redis|*mongo> (default "*redis") - -datadb_user string - the DataDB user (default "cgrates") - -dbdata_encoding string - the encoding used to store object Data in strings (default "msgpack") - -dry_run - parse loaded data for consistency and errors, without storing it - -exec string - fire up automatic migration <*set_versions|*cost_details|*accounts|*actions|*action_triggers|*action_plans|*shared_groups|*filters|*stordb|*datadb> - -mongoConnScheme string - Scheme for MongoDB connection (default "mongodb") - -mongoQueryTimeout duration - The timeout for queries (default 10s) - -out_datadb_encoding string - the encoding used to store object Data in strings in move mode (default "*datadb") - -out_datadb_host string - output DataDB host to connect to (default "*datadb") - -out_datadb_name string - output DataDB name/number (default "*datadb") - -out_datadb_password string - output DataDB password (default "*datadb") - -out_datadb_port string - output DataDB port (default "*datadb") - -out_datadb_type string - output DataDB type <*redis|*mongo> (default "*datadb") - -out_datadb_user string - output DataDB user (default "*datadb") - -out_redis_sentinel string - the name of redis sentinel (default "*datadb") - -out_stordb_host string - output StorDB host (default "*stordb") - -out_stordb_name string - output StorDB name/number (default "*stordb") - -out_stordb_password string - output StorDB password (default "*stordb") - -out_stordb_port string - output StorDB port (default "*stordb") - -out_stordb_type string - output StorDB type for move mode <*mysql|*postgres|*mongo> (default "*stordb") - -out_stordb_user string - output StorDB user (default "*stordb") - -redisCACertificate string - Path to the CA certificate - -redisClientCertificate string - Path to the client certificate - -redisClientKey string - Path to the client key - -redisCluster - Is the redis datadb a cluster - -redisClusterOndownDelay duration - The delay before executing the commands if the redis cluster is in the CLUSTERDOWN state - -redisClusterSync duration - The sync interval for the redis cluster (default 5s) - -redisConnectAttempts int - The maximum amount of dial attempts (default 20) - -redisConnectTimeout duration - The amount of wait time until timeout for a connection attempt - -redisMaxConns int - The connection pool size (default 10) - -redisReadTimeout duration - The amount of wait time until timeout for reading operations - -redisSentinel string - the name of redis sentinel - -redisTLS - Enable TLS when connecting to Redis - -redisWriteTimeout duration - The amount of wait time until timeout for writing operations - -stordb_host string - the StorDB host (default "127.0.0.1") - -stordb_name string - the name/number of the StorDB (default "cgrates") - -stordb_passwd string - the StorDB password (default "CGRateS.org") - -stordb_port string - the StorDB port (default "3306") - -stordb_type string - the type of the StorDB Database <*mysql|*postgres|*mongo> (default "*mysql") - -stordb_user string - the StorDB user (default "cgrates") - -verbose - enable detailed verbose logging output - -version - prints the application version diff --git a/docs/cgr-tester.rst b/docs/cgr-tester.rst deleted file mode 100644 index e62c61c83..000000000 --- a/docs/cgr-tester.rst +++ /dev/null @@ -1,97 +0,0 @@ -.. _cgr-tester: - -cgr-tester ----------- - -Command line stress testing tool configurable via command line arguments. - -:: - - $ cgr-tester -h - Usage of cgr-tester: - -calls int - run n number of calls (default 100) - -category string - The Record category to test. (default "call") - -config_path string - Configuration directory path. - -cps int - run n requests in parallel (default 100) - -cpuprofile string - write cpu profile to file - -datadb_host string - The DataDb host to connect to. (default "127.0.0.1") - -datadb_name string - The name/number of the DataDb to connect to. (default "10") - -datadb_pass string - The DataDb user's password. - -datadb_port string - The DataDb port to bind to. (default "6379") - -datadb_type string - The type of the DataDb database (default "*redis") - -datadb_user string - The DataDb user to sign in as. (default "cgrates") - -dbdata_encoding string - The encoding used to store object data in strings. (default "msgpack") - -destination string - The destination to use in queries. (default "1002") - -digits int - Number of digits Account and Destination will have (default 10) - -exec string - Pick what you want to test <*sessions|*cost> - -file_path string - read requests from file with path - -json - Use JSON RPC - -max_usage duration - Maximum usage a session can have (default 5s) - -memprofile string - write memory profile to this file - -min_usage duration - Minimum usage a session can have (default 1s) - -mongoConnScheme string - Scheme for MongoDB connection (default "mongodb") - -mongoQueryTimeout duration - The timeout for queries (default 10s) - -rater_address string - Rater address for remote tests. Empty for internal rater. - -redisCluster - Is the redis datadb a cluster - -redisClusterOndownDelay duration - The delay before executing the commands if the redis cluster is in the CLUSTERDOWN state - -redisClusterSync duration - The sync interval for the redis cluster (default 5s) - -redisConnectAttempts int - The maximum amount of dial attempts (default 20) - -redisConnectTimeout duration - The amount of wait time until timeout for a connection attempt - -redisMaxConns int - The connection pool size (default 10) - -redisReadTimeout duration - The amount of wait time until timeout for reading operations - -redisSentinel string - The name of redis sentinel - -redisWriteTimeout duration - The amount of wait time until timeout for writing operations - -req_separator string - separator for requests in file (default "\n\n") - -request_type string - Request type of the call (default "*rated") - -runs int - stress cycle number (default 100000) - -subject string - The rating subject to use in queries. (default "1001") - -tenant string - The type of record to use in queries. (default "cgrates.org") - -timeout duration - After last call, time out after this much duration (default 10s) - -tor string - The type of record to use in queries. (default "*voice") - -update_interval duration - Time duration added for each session update (default 1s) - -usage string - The duration to use in call simulation. (default "1m") - -verbose - Enable detailed verbose logging output - -version - Prints the application version. diff --git a/docs/chargers.rst b/docs/chargers.rst deleted file mode 100644 index ff403f1ac..000000000 --- a/docs/chargers.rst +++ /dev/null @@ -1,66 +0,0 @@ -.. _chargers: - -ChargerS -======== - -**ChargerS** is a **CGRateS** subsystem designed to produce billing runs via *DerivedCharging* mechanism. - -It works as standalone component of **CGRateS**, accessible via `CGRateS RPC `_ via a rich set of *APIs*. As input **ChargerS** is capable of receiving generic events (hashmaps) with dynamic types for fields. - -**ChargerS** is an **important** part of the charging process within **CGRateS** since with no *ChargingProfile* matching, there will be no billing run performed. - - -DerivedCharging ---------------- - -Is a process of receiving an event as input and *deriving* that into multiples (unlimited) out. The *derived* event will be a standalone clone of original with possible modifications of individual event fields. In case of billing, this will translate into multiple Events or CDRs being billed simultaneously for the same input. - - -Processing logic ----------------- - -For the received *Event* we will retrieve the list of matching *ChargingProfiles' via :ref:`FilterS`. These profiles will be then ordered based on their *Weight* - higher *Weight* will have more priority. If no profile will match due to *Filter* or *ActivationInterval*, *NOT_FOUND* will be returned back to the RPC client. - -Each *ChargingProfile* matching the *Event* will produce a standalone event based on configured *RunID*. These events will each have a special field added (or overwritten), the *RunID*, which is taken from the applied *ChargingProfile*. - -If *AttributeIDs* are different than *\*none*, the newly created *Event* will be sent to [AttributeS](AttributeS) and fields replacement will be performed based on the logic there. If the *AttributeIDs* is populated, these profile IDs will be selected directly for faster processing, otherwise (if empty) the *AttributeProfiles* will be selected using :ref:`FilterS`. - - -Parameters ----------- - -ChargerProfile -^^^^^^^^^^^^^^ - -A *ChargerProfile* is the configuration producing the *DerivedCharging* for the Event received. It's made of the following fields: - -Tenant - Is the tenant on the platform (one can see the tenant as partition ID) - -ID - Identifier for the ChargerProfile, unique within a *Tenant*. - -FilterIDs - List of *FilterProfiles* which should match in order to consider the ChargerProfile matching the event. - -ActivationInterval - Is the time interval when this profile becomes active. If undefined, the profile is always active. Other options are start time, end time or both. - -RunID - The identifier for a single bill run / charged output *Event*. - -AttributeIDs - List of *AttributeProfileIDs* which will be applied for the output *Event* in order to change some of it's fields. If empty, the list is discovered via [FilterS](FilterS) (*AttributeProfiles* matching the event). If *\*none, no AttributeProfile will be applied, event will be a simple clone of the one at input with just *RunID* being different. - -Weight - Used in case of multiple profiles matching an event. The higher, the better (0 has lowest possible priority). - - -Use cases ---------- - -* Calculating standard charges for the *Customer* calling as well as for the *Reseller*/*Distributor*. One can build chains of charging rules if multiple *Resellers* are involved. -* Calculating revenue based on *Customer* vs *Supplier* pricing. -* Calculating pricing for multiple *RouteS* for revenue protection. -* Adding *local* vs *mobile* charges for *premium numbers* when accessed from mobile headsets. -* etc. \ No newline at end of file diff --git a/docs/datadb.rst b/docs/datadb.rst deleted file mode 100644 index ffa6bbc07..000000000 --- a/docs/datadb.rst +++ /dev/null @@ -1,291 +0,0 @@ -.. _datadb: - -DataDB -====== - -**DataDB** is the subsystem within **CGRateS** responsible for storing runtime data like accounts, rating plans, and other objects that the engine needs for its operation. It supports various database backends to fit different deployment needs. - -Database Types --------------- - -DataDB supports the following database types through the ``db_type`` parameter: - -* ``*redis``: Uses Redis as the storage backend -* ``*mongo``: Uses MongoDB as the storage backend -* ``*internal``: Uses in-memory storage within the CGRateS process - -When using ``*internal`` as the ``db_type``, **CGRateS** leverages your machine's memory to store all **DataDB** records directly inside the engine. This drastically increases read/write performance, as no data leaves the process, avoiding the overhead associated with external databases. The configuration supports periodic data dumps to disk to enable persistence across reboots. - -The internal database is ideal for: - -* Environments with extremely high performance requirements -* Systems where external database dependencies should be avoided -* Lightweight deployments or containers requiring self-contained runtime -* Temporary setups or testing environments - -Remote and Replication Functionality ------------------------------------- - -Remote Functionality -~~~~~~~~~~~~~~~~~~~~ - -DataDB supports fetching data from remote CGRateS instances when items are not found locally. This allows for distributed setups where data can be stored across multiple instances. - -To use remote functionality: - -1. Define RPC connections to remote engines -2. Configure which data items should be fetched remotely by setting ``remote: true`` for specific items -3. Optionally set an identifier for the local connection with ``remote_conn_id`` - -Replication -~~~~~~~~~~~ - -DataDB supports replicating data changes to other CGRateS instances. When enabled, modifications (Set/Remove operations) are propagated to configured remote engines, ensuring data consistency across a distributed deployment. - -Unlike the remote functionality which affects Get operations, replication applies to Set and Remove operations, pushing changes outward to other nodes. - -Configuration -------------- - -A complete DataDB configuration includes database connection details, remote functionality settings, replication options, and item-specific settings. For reference, the full default configuration can be found in the :ref:`configuration` section. - -.. code-block:: json - - "data_db": { - "db_type": "*redis", - "db_host": "127.0.0.1", - "db_port": 6379, - "db_name": "10", - "db_user": "cgrates", - "db_password": "", - "remote_conns": ["engine2", "engine3"], - "remote_conn_id": "engine1", - "replication_conns": ["engine2", "engine3"], - "replication_filtered": false, - "replication_cache": "", - "replication_failed_dir": "/var/lib/cgrates/failed_replications", - "replication_interval": "1s", - "items": { - "*accounts": {"limit": -1, "ttl": "", "static_ttl": false, "remote": false, "replicate": true}, - "*rating_plans": {"limit": -1, "ttl": "", "static_ttl": false, "remote": true, "replicate": true} - // Other items... - }, - "opts": { - // Database-specific options... - } - } - -Parameters ----------- - -Basic Connection -~~~~~~~~~~~~~~~~ - -db_type - The database backend to use. Values: <*redis|*mongo|*internal> - -db_host - Database host address (e.g., "127.0.0.1") - -db_port - Port to reach the database (e.g., 6379 for Redis) - -db_name - Database name to connect to (e.g., "10" for Redis database number) - -db_user - Username for database authentication - -db_password - Password for database authentication - -Remote Functionality -~~~~~~~~~~~~~~~~~~~~ - -remote_conns - Array of connection IDs (defined in rpc_conns) that will be queried when items are not found locally - -remote_conn_id - Identifier sent to remote connections to identify this engine - -Replication Parameters -~~~~~~~~~~~~~~~~~~~~~~ - -replication_conns - Array of connection IDs (defined in rpc_conns) to which data will be replicated - -replication_filtered - When enabled, replication occurs only to connections that previously received a Get request for the item. Values: - -replication_cache - Caching action to execute on replication targets when items are replicated - -replication_failed_dir - Directory to store failed batch replications when using intervals. This directory must exist before launching CGRateS. - -replication_interval - Interval between batched replications: - - Empty/0: Immediate replication after each operation - - Duration (e.g., "1s"): Batches replications and sends them at the specified interval - -Items Configuration -~~~~~~~~~~~~~~~~~~~ - -DataDB manages multiple data types through the ``items`` map, with these configuration options for each item: - -limit - Maximum number of items of this type to store. -1 means no limit. Only applies to *internal database. - -ttl - Time-to-live for items before automatic removal. Empty string means no expiration. Only applies to *internal database. - -static_ttl - Controls TTL behavior. When true, TTL is fixed from initial creation. When false, TTL resets on each update. Only applies to *internal database. - -remote - When true, enables fetching this item type from remote connections if not found locally. - -replicate - When true, enables replication of this item type to configured remote connections. - -Internal Database Options -~~~~~~~~~~~~~~~~~~~~~~~~~ - -When using ``*internal`` as the database type, additional options are available in the ``opts`` section: - -internalDBDumpPath - Defines the path to the folder where the memory-stored **DataDB** will be dumped. This path is also used for recovery during engine startup. Ensure the folder exists before launching the engine. - -internalDBBackupPath - Path where backup copies of the dump folder will be stored. Backups are triggered via the `APIerSv1.BackupDataDBDump `_ API call. This API can also specify a custom path for backups, otherwise the default `internalDBBackupPath` is used. Backups serve as a fallback in case of dump file corruption or loss. The created folders are timestamped in UNIX time for easy identification of the latest backup. To recover using a backup, simply transfer the folders from a backup in internalDBBackupPath to internalDBDumpPath and start the engine. If backups are zipped, they need to be unzipped manually when restoring. - -internalDBStartTimeout - Specifies the maximum amount of time the engine will wait to recover the in-memory **DataDB** state from the dump files during startup. If this duration is exceeded, the engine will timeout and an error will be returned. - -internalDBDumpInterval - Specifies the time interval at which **DataDB** will be dumped to disk. This duration should be chosen based on the machine's capacity and data load. If the interval is set too long and a lot of data changes during that period, the dumping process will take longer, and in the event of an engine crash, any data not dumped will be lost. Conversely, if the interval is too short, and a high number of queries are done often to **DataDB**, some of the needed processing power for the queries will be used by the dump process. Since machine resources and data loads vary, it is recommended to simulate the load on your system and determine the optimal "sweet spot" for this interval. At engine shutdown, any remaining undumped data will automatically be written to disk, regardless of the interval setting. - - - Setting the interval to ``0s`` disables the periodic dumping, meaning any data in **DataDB** will be lost when the engine shuts down. - - Setting the interval to ``-1`` enables immediate dumping—whenever a record in **DataDB** is added, changed, or removed, it will be dumped to disk immediately. - - Manual dumping can be triggered using the `APIerSv1.DumpDataDB `_ API. - -internalDBRewriteInterval - Defines the interval for rewriting files that are not currently being used for dumping data, converting them into an optimized, streamlined version and improving recovery time. Similar to ``internalDBDumpInterval``, the rewriting will trigger based on specified intervals: - - - Setting the interval ``0s`` disables rewriting. - - Setting the interval ``-1`` triggers rewriting only once when the engine starts. - - Setting the interval ``-2`` triggers rewriting only once when the engine shuts down. - - Rewriting should be used sparingly, as the process temporarily loads the entire ``internalDBDumpPath`` folder into memory for optimization, and then writes it back to the dump folder once done. This results in a surge of memory usage, which could amount to the size of the dump file itself during the rewrite. As a rule of thumb, expect the engine's memory usage to approximately double while the rewrite process is running. Manual rewriting can be triggered at any time via the `APIerSv1.RewriteDataDB `_ API. - -internalDBFileSizeLimit - Specifies the maximum size a single dump file can reach. Upon reaching the limit, a new dump file is created. Limiting file size improves recovery time and allows for limit reached files to be rewritten. - -Redis-Specific Options -~~~~~~~~~~~~~~~~~~~~~~ - -The following options in the ``opts`` section apply when using Redis: - -redisMaxConns - Connection pool size - -redisConnectAttempts - Maximum number of connection attempts - -redisSentinel - Sentinel name when using Redis Sentinel - -redisCluster - Enables Redis Cluster mode - -redisClusterSync - Sync interval for Redis Cluster - -redisClusterOndownDelay - Delay before executing commands when Redis Cluster is in CLUSTERDOWN state - -redisConnectTimeout, redisReadTimeout, redisWriteTimeout - Timeout settings for various Redis operations - -redisTLS, redisClientCertificate, redisClientKey, redisCACertificate - TLS configuration for secure Redis connections - -MongoDB-Specific Options -~~~~~~~~~~~~~~~~~~~~~~~~ - -The following options in the ``opts`` section apply when using MongoDB: - -mongoQueryTimeout - Timeout for MongoDB queries - -mongoConnScheme - Connection scheme for MongoDB () - -Configuration Examples ----------------------- - -Persistent Internal Database -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. code-block:: json - - "data_db": { - "db_type": "*internal", - "opts": { - "internalDBDumpPath": "/var/lib/cgrates/internal_db/datadb", - "internalDBBackupPath": "/var/lib/cgrates/internal_db/backup/datadb", - "internalDBStartTimeout": "5m", - "internalDBDumpInterval": "1m", - "internalDBRewriteInterval": "15m", - "internalDBFileSizeLimit": "1GB" - } - } - -Replication Setup -~~~~~~~~~~~~~~~~~ - -First, define connections to the engines you want to replicate to: - -.. code-block:: json - - "rpc_conns": { - "rpl_engine": { - "conns": [ - { - "address": "127.0.0.1:2012", - "transport": "*json", - "connect_attempts": 5, - "reconnects": -1, - "max_reconnect_interval": "", - "connect_timeout": "1s", - "reply_timeout": "2s" - } - ] - } - } - -Then configure DataDB replication (showing only replication-related parameters): - -.. code-block:: json - - "data_db": { - "replication_conns": ["rpl_engine"], - "replication_failed_dir": "/var/lib/cgrates/failed_replications", - "replication_interval": "1s", - "items": { - "*accounts": {"replicate": true}, - "*reverse_destinations": {"replicate": true}, - "*destinations": {"replicate": true}, - "*rating_plans": {"replicate": true} - // Other items... - } - } - -Notes ------ - -* By default, both replication and remote functionality are disabled for all items and must be explicitly enabled by setting ``replicate: true`` or ``remote: true`` for each desired item -* When using replication with intervals, make sure to configure a ``replication_failed_dir`` to handle failed replications -* Failed replications can be manually replayed using the `APIerSv1.ReplayFailedReplications `_ API call -* Remote functionality and replication can be used independently or together, depending on your deployment needs diff --git a/docs/diamagent.rst b/docs/diamagent.rst deleted file mode 100644 index 6aa2ac6a1..000000000 --- a/docs/diamagent.rst +++ /dev/null @@ -1,404 +0,0 @@ -.. _Diameter: https://tools.ietf.org/html/rfc6733 - -.. _DiameterAgent: - -DiameterAgent -============= - -**DiameterAgent** translates between Diameter_ and **CGRateS**, sending *RPC* requests towards **CGRateS/SessionS** component and returning replies from it to the *DiameterClient*. - -Implements Diameter_ protocol in a standard agnostic manner, giving users the ability to implement own interfaces by defining simple *processor templates* within the :ref:`JSON configuration ` files. - -Used mostly in modern mobile networks (LTE/xG). - - -Configuration -------------- - -The **DiameterAgent** is configured within *diameter_agent* section from :ref:`JSON configuration `. - - -Sample config -^^^^^^^^^^^^^ - -With explanations in the comments: - -:: - - "diameter_agent": { - "enabled": false, // enables the diameter agent: - "listen": "127.0.0.1:3868", // address where to listen for diameter requests - "listen_net": "tcp", // transport type for diameter - "dictionaries_path": "/usr/share/cgrates/diameter/dict/", // path towards directory - // holding additional dictionaries to load - "sessions_conns": ["*internal"], // connection towards SessionS - "origin_host": "CGR-DA", // diameter Origin-Host AVP used in replies - "origin_realm": "cgrates.org", // diameter Origin-Realm AVP used in replies - "vendor_id": 0, // diameter Vendor-Id AVP used in replies - "product_name": "CGRateS", // diameter Product-Name AVP used in replies - "synced_conn_requests": false, // process one request at the time per connection - "asr_template": "*asr", // enable AbortSession message being sent to client - "request_processors": [ // decision logic for message processing - { - "id": "SMSes", // id is used for debug in logs (ie: using *log flag) - "filters": [ // list of filters to be applied on message for this processor to run - "*string:~*vars.*cmd:CCR", - "*string:~*req.CC-Request-Type:4", - "*string:~*req.Service-Context-Id:LPP" - ], - "flags": ["*event", "*accounts", "*cdrs"], // influence processing logic within CGRateS workflow - "request_fields":[ // data exchanged between Diameter and CGRateS - { - "tag": "ToR", // tag is used in debug, - "path": "*cgreq.ToR", // path is the field on CGRateS side - "type": "*constant", // type defines the method to provide the value - "value": "*sms"} - { - "tag": "OriginID", // OriginID will identify uniquely - "path": "*cgreq.OriginID", // the session on CGRateS side - "type": "*variable", // it's value will be taken from Diameter AVP: - "mandatory": true, // Multiple-Services-Credit-Control.Service-Identifier - "value": "~*req.Multiple-Services-Credit-Control.Service-Identifier" - }, - { - "tag": "OriginHost", // OriginHost combined with OriginID - "path": "*cgreq.OriginHost",// is used by CGRateS to build the CGRID - "mandatory": true, - "type": "*variable", // have the value out of special variable: *vars - "value": "*vars.OriginHost" - }, - { - "tag": "RequestType", // RequestType instructs SessionS - "path": "*cgreq.RequestType", // about charging type to apply for the event - "type": "*constant", - "value": "*prepaid" - }, - { - "tag": "Category", // Category serves for ataching Account - "path": "*cgreq.Category", // and RatingProfile to the request - "type": "*constant", - "value": "sms" - }, - { - "tag": "Account", // Account is required by charging - "path": "*cgreq.Account", - "type": "*variable", // value is taken dynamically from a group AVP - "mandatory": true, // where Subscription-Id-Type is 0 - "value": "~*req.Subscription-Id.Subscription-Id-Data<~Subscription-Id-Type(0)>" - }, - { - "tag": "Destination", // Destination is used for charging - "path": "*cgreq.Destination", // value from Diameter will be mediated before sent to CGRateS - "type": "*variable", - "mandatory": true, - "value": "~*req.Service-Information.SMS-Information.Recipient-Info.Recipient-Address.Address-Data:s/^\\+49(\\d+)/int${1}/:s/^0049(\\d+)/int${1}/:s/^49(\\d+)/int${1}/:s/^00(\\d+)/+${1}/:s/^[\\+]?(\\d+)/int${1}/:s/int(\\d+)/+49${1}/" - }, - { - "tag": "Destination", // Second Destination will overwrite the first if filter matches - "path": "*cgreq.Destination", - "filters":[ // Only overwrite when filters are matching - "*notprefix:~*req.Service-Information.SMS-Information.Recipient-Info.Recipient-Address.Address-Data:49", - "*notprefix:~*req.Service-Information.SMS-Information.Recipient-Info.Recipient-Address.Address-Data:3312" - ], - "type": "*variable", - "mandatory": true, - "value": "~*req.Service-Information.SMS-Information.Recipient-Info.Recipient-Address.Address-Data:s/^[\\+]?(\\d+)/int${1}/:s/int(\\d+)/+00${1}/" - }, - { - "tag": "SetupTime", // SetupTime is used by charging - "path": "*cgreq.SetupTime", - "type": "*variable", - "value": "~*req.Event-Timestamp", - "mandatory": true - }, - { - "tag": "AnswerTime", // AnswerTime is used by charging - "path": "*cgreq.AnswerTime", - "type": "*variable", - "mandatory": true, - "value": "~*req.Event-Timestamp" - }, - { - "tag": "Usage", // Usage is used by charging - "path": "*cgreq.Usage", - "type": "*variable", - "mandatory": true, - "value": "~*req.Multiple-Services-Credit-Control.Requested-Service-Unit.CC-Service-Specific-Units" - }, - { - "tag": "Originator-SCCP-Address", // Originator-SCCP-Address is an extra field which we want in CDR - "path": "*cgreq.Originator-SCCP-Address", // not used by CGRateS - "type": "*variable", "mandatory": true, - "value": "~*req.Service-Information.SMS-Information.Originator-SCCP-Address" - }, - ], - "reply_fields":[ // fields which are sent back to DiameterClient - { - "tag": "CCATemplate", // inject complete Template defined as *cca above - "type": "*template", - "value": "*cca" - }, - { - "tag": "ResultCode", // Change the ResultCode if the reply received from CGRateS contains a 0 MaxUsage - "filters": ["*eq:~*cgrep.MaxUsage:0"], - "path": "*rep.Result-Code", - "blocker": true, // do not consider further fields if this one is processed - "type": "*constant", - "value": "4012"}, - {"tag": "ResultCode", // Change the ResultCode AVP if there was an error received from CGRateS - "filters": ["*notempty:~*cgrep.Error:"], - "path": "*rep.Result-Code", - "blocker": true, - "type": "*constant", - "value": "5030"} - ] - } - - ] - }, - - ], - }, - - -Config params -^^^^^^^^^^^^^ - -Most of the parameters are explained in :ref:`JSON configuration `, hence we mention here only the ones where additional info is necessary or there will be particular implementation for *DiameterAgent*. - - -listen_net - The network the *DiameterAgent* will bind to. CGRateS supports both **tcp** and **sctp** specified in Diameter_ standard. - -asr_template - The template (out of templates config section) used to build the AbortSession message. If not specified the ASR message is never sent out. - -templates - Group fields based on their usability. Can be used in both processor templates as well as hardcoded within CGRateS functionality (ie *\*err* or *\*asr*). The IDs are unique, defining the same id in multiple configuration places/files will result into overwrite. - - **\*err** - Is a hardcoded template used when *DiameterAgent* cannot parse the incoming message. Aside from logging the error via internal logger the message defined via *\*err* template will be sent out. - - **\*asr** - Can be activated via *asr_template* config key to enable sending of *Diameter* *ASR* message to *DiameterClient*. - - **\*cca** - Defined for convenience to follow the standard for the fields used in *Diameter* *CCA* messages. - -request_processors - List of processor profiles applied on request/replies. - - Once a request processor will be matched (it's *filters* should match), the *request_fields* will be used to craft a request object and the flags will decide what sort of procesing logic will be applied to the crafted request. - - After request processing, there will be a second part executed: reply. The reply object will be built based on the *reply_fields* section in the - request processor. - - Once the *reply_fields* are finished, the object converted and returned to the *DiameterClient*, unless *continue* flag is enabled in the processor, which makes the next request processor to be considered. - - -filters - Will specify a list of filter rules which need to match in order for the processor to run (or field to be applied). - - For the dynamic content (prefixed with *~*) following special variables are available: - - **\*vars** - Request related shared variables between processors, populated especially by core functions. The data put inthere is not automatically transfered into requests sent to CGRateS, unless instructed inside templates. - - Following vars are automatically set by core: - - * **OriginHost**: agent configured *origin_host* - * **OriginRealm**: agent configured *origin_realm* - * **ProductName**: agent configured *product_name* - * **RemoteHost**: the Address of the remote client - * **\*app**: current request application name (out of diameter dictionary) - * **\*appid**: current request application id (out of diameter dictionary) - * **\*cmd**: current command short naming (out of diameter dictionary) plus *R" as suffix - ie: *CCR* - - **\*req** - Diameter request as it comes from the *DiameterClient*. - - Special selector format defined in case of groups *\*req.Path.To.Attribute[$groupIndex]* or *\*req.Absolute.Path.To.Attribute<~AnotherAttributeRelativePath($valueAnotherAttribute)>*. - - Example 1: *~\*req.Multiple-Services-Credit-Control.Rating-Group<1>* translates to: value of the group attribute at path Multiple-Services-Credit-Control.Rating-Group which is located in the second group (groups start at index 0). - Example 2: *~\*req.Multiple-Services-Credit-Control.Used-Service-Unit.CC-Input-Octets<~Rating-Group(1)>* which translates to: value of the group attribute at path: *Multiple-Services-Credit-Control.Used-Service-Unit.CC-Input-Octets* where Multiple-Services-Credit-Control.Used-Service-Unit.Rating-Group has value of "1". - - **\*rep** - Diameter reply going to *DiameterClient*. - - **\*cgreq** - Request sent to CGRateS. - - **\*cgrep** - Reply coming from CGRateS. - - **\*diamreq** - Diameter request generated by CGRateS (ie: *ASR*). - -flags - Found within processors, special tags enforcing the actions/verbs done on a request. There are two types of flags: **main** and **auxiliary**. - - There can be any number of flags or combination of those specified in the list however the flags have priority one against another and only some simultaneous combinations of *main* flags are possible. - - The **main** flags will select mostly the action taken on a request. - - The **auxiliary** flags only make sense in combination with **main** ones. - - Implemented **main** flags are (in order of priority, and not working simultaneously unless specified): - - **\*log** - Logs the Diameter request/reply. Can be used together with other *main* actions. - - **\*none** - Disable transfering the request from *Diameter* to *CGRateS* side. Used mostly to pasively answer *Diameter* requests or troubleshoot (mostly in combination with *\*log* flag). - - **\*dryrun** - Together with not transfering the request on CGRateS side will also log the *Diameter* request/reply, useful for troubleshooting. - - **\*auth** - Sends the request for authorization on CGRateS. - - Auxiliary flags available: **\*attributes**, **\*thresholds**, **\*stats**, **\*resources**, **\*accounts**, **\*routes**, **\*routes_ignore_errors**, **\*routes_event_cost**, **\*routes_maxcost** which are used to influence the auth behavior on CGRateS side. More info on that can be found on the **SessionS** component's API behavior. - - **\*initiate** - Initiates a session out of request on CGRateS side. - - Auxiliary flags available: **\*attributes**, **\*thresholds**, **\*stats**, **\*resources**, **\*accounts** which are used to influence the auth behavior on CGRateS side. - - **\*update** - Updates a session with the request on CGRateS side. - - Auxiliary flags available: **\*attributes**, **\*accounts** which are used to influence the behavior on CGRateS side. - - **\*terminate** - Terminates a session using the request on CGRateS side. - - Auxiliary flags available: **\*thresholds**, **\*stats**, **\*resources**, **\*accounts** which are used to influence the behavior on CGRateS side. - - **\*message** - Process the request as individual message charging on CGRateS side. - - Auxiliary flags available: **\*attributes**, **\*thresholds**, **\*stats**, **\*resources**, **\*accounts**, **\*routes**, **\*routes_ignore_errors**, **\*routes_event_cost**, **\*routes_maxcost** which are used to influence the behavior on CGRateS side. - - - **\*event** - Process the request as generic event on CGRateS side. - - Auxiliary flags available: all flags supported by the "SessionSv1.ProcessEvent" generic API - - **\*cdrs** - Build a CDR out of the request on CGRateS side. Can be used simultaneously with other flags (except **\*dryrun**) - - -path - Defined within field, specifies the path where the value will be written. Possible values: - - **\*vars** - Write the value in the special container, *\*vars*, available for the duration of the request. - - **\*cgreq** - Write the value in the request object which will be sent to CGRateS side. - - **\*cgrep** - Write the value in the reply returned by CGRateS. - - **\*rep** - Write the value to reply going out on *Diameter* side. - - **\*diamreq** - Write the value to request built by *DiameterAgent* to be sent out on *Diameter* side. - -type - Defined within field, specifies the logic type to be used when writing the value of the field. Possible values: - - **\*none** - Pass - - **\*filler** - Fills the values with an empty string - - **\*constant** - Writes out a constant - - **\*variable** - Writes out the variable value, overwriting previous one set - - **\*composed** - Writes out the variable value, postpending to previous value set - - **\*group** - Writes out the variable value, postpending to the list of variables with the same path - - **\*usage_difference** - Calculates the usage difference between two arguments passed in the *value*. Requires 2 arguments: *$stopTime;$startTime* - - **\*cc_usage** - Calculates the usage out of *CallControl* message. Requires 3 arguments: *$reqNumber;$usedCCTime;$debitInterval* - - **\*sum** - Calculates the sum of all arguments passed within *value*. It supports summing up duration, time, float, int autodetecting them in this order. - - **\*difference** - Calculates the difference between all arguments passed within *value*. Possible value types are (in this order): duration, time, float, int. - - **\*value_exponent** - Calculates the exponent of a value. It requires two values: *$val;$exp* - - **\*template** - Specifies a template of fields to be injected here. Value should be one of the template ids defined. - -value - The captured value. Possible prefixes for dynamic values are: - - **\*req** - Take data from current request coming from diameter client. - - **\*vars** - Take data from internal container labeled *\*vars*. This is valid for the duration of the request. - - **\*cgreq** - Take data from the request being sent to :ref:`SessionS`. This is valid for one active request. - - **\*cgrep** - Take data from the reply coming from :ref:`SessionS`. This is valid for one active reply. - - **\*diamreq** - Take data from the diameter request being sent to the client (ie: *ASR*). This is valid for one active reply. - - **\*rep** - Take data from the diameter reply being sent to the client. - -mandatory - Makes sure that the field cannot have empty value (errors otherwise). - -tag - Used for debug purposes in logs. - -width - Used to control the formatting, enforcing the final value to a specific number of characters. - -strip - Used when the value is higher than *width* allows it, specifying the strip strategy. Possible values are: - - **\*right** - Strip the suffix. - - **\*xright** - Strip the suffix, postpending one *x* character to mark the stripping. - - **\*left** - Strip the prefix. - - **\*xleft** - Strip the prefix, prepending one *x* character to mark the stripping. - -padding - Used to control the formatting. Applied when the data is smaller than the *width*. Possible values are: - - **\*right** - Suffix with spaces. - - **\*left** - Prefix with spaces. - - **\*zeroleft** - Prefix with *0* chars. diff --git a/docs/dnsagent.rst b/docs/dnsagent.rst deleted file mode 100644 index 467047918..000000000 --- a/docs/dnsagent.rst +++ /dev/null @@ -1,5 +0,0 @@ -DNSAgent -======== - - -TBD \ No newline at end of file diff --git a/docs/ees.rst b/docs/ees.rst deleted file mode 100644 index c957d6ea7..000000000 --- a/docs/ees.rst +++ /dev/null @@ -1,251 +0,0 @@ -.. _AMQP: https://www.amqp.org/ -.. _SQS: https://aws.amazon.com/de/sqs/ -.. _S3: https://aws.amazon.com/de/s3/ -.. _Kafka: https://kafka.apache.org/ - - -.. _EEs: - -EEs -==== - - -**EventExporterService/EEs** is a subsystem designed to convert internal, already processed events into external ones and then export them to a defined destination. It is accessible via `CGRateS RPC APIs `_. - - -Configuration -------------- - -**EEs** is configured within **ees** section from :ref:`JSON configuration `. - -Config params -^^^^^^^^^^^^^ - -Most of the parameters are explained in :ref:`JSON configuration `, hence we mention here only the ones where additional info is necessary or there will be particular implementation for *EventExporterService*. - -One **exporters** instance includes the following parameters: - -id - Exporter identificator, used mostly for debug. The id should be unique per each exporter since it can influence updating configuration from different *.json* configuration. - -type - Specify the type of export which will run. Possible values are: - - **\*file_csv** - Exports into a comma separated file format. - - **\*file_fwv** - Exports into a fixed width file format. - - **\*http_post** - Will post the CDR to a HTTP server. The export content will be a HTTP form encoded representation of the `internal CDR object `_. - - - **\*http_json_map** - Will post the CDR to a HTTP server. The export content will be a JSON serialized hmap with fields defined within the *fields* section of the template. - - **\*amqp_json_map** - Will post the CDR to an AMQP_ queue. The export content will be a JSON serialized hmap with fields defined within the *fields* section of the template. Uses AMQP_ protocol version 1.0. - - **\*amqpv1_json_map** - Will post the CDR to an AMQP_ queue. The export content will be a JSON serialized hmap with fields defined within the *fields* section of the template. Uses AMQP_ protocol version 1.0. - - **\*sqs_json_map** - Will post the CDR to an `Amazon SQS queue `_. The export content will be a JSON serialized hmap with fields defined within the *fields* section of the template. - - **\*s3_json_map** - Will post the CDR to `Amazon S3 storage `_. The export content will be a JSON serialized hmap with fields defined within the *fields* section of the template. - - **\*kafka_json_map** - Will post the CDR to an `Apache Kafka `_. The export content will be a JSON serialized hmap with fields defined within the *fields* section of the template. - - **\*nats_json_map** - Exporter for publishing messages to NATS (Message Queue) in JSON format. - - **\*virt** - In-memory exporter. - - **\*els** - Exporter for Elasticsearch. - - **\*sql** - Exporter for generic content to *SQL* databases. Supported databases are: MySQL_, PostgreSQL_ and MSSQL_. - - **\*rpc** - Exporter for calling APIs through node connections. - -export_path - Specify the export path. It has special format depending of the export type. - - **\*file_csv**, **\*file_fwv** - Standard unix-like filesystem path. - - **\*http_post**, **\*http_json_map** - Full HTTP URL - - **\*amqp_json_map**, **\*amqpv1_json_map** - AMQP URL with extra parameters. - - Sample: *amqp://guest:guest@localhost:5672/?queue_id=cgrates_cdrs&exchange=exchangename&exchange_type=fanout&routing_key=cgr_cdrs* - - **\*sqs_json_map** - SQS URL with extra parameters. - - Sample: *http://sqs.eu-west-2.amazonaws.com/?aws_region=eu-west-2&aws_key=testkey&aws_secret=testsecret&queue_id=cgrates-cdrs* - - **\*s3_json_map** - S3 URL with extra parameters. - - Sample: *http://s3.us-east-2.amazonaws.com/?aws_region=eu-west-2&aws_key=testkey&aws_secret=testsecret&queue_id=cgrates-cdrs* - - **\*kafka_json_map** - Kafka URL with extra parameters. - - Sample: *localhost:9092?topic=cgrates_cdrs* - - **\*sql** - SQL URL with extra parameters. - - Sample: *mysql://cgrates:CGRateS.org@127.0.0.1:3306* - - **\*nats** - NATS URL. - - Sample: *nats://localhost:4222* - - **\*els** - Elasticsearch URL - - Sample: *http://localhost:9200* - -filters - List of filters to pass for the export profile to execute. For the dynamic content (prefixed with *~*) following special variables are available: - - **\*req** - The *CDR* event itself. - - **\*ec** - The *EventCost* object with subpaths for all of it's nested objects. - -tenant - Tenant owning the template. It will be used mostly to match inside :ref:`FilterS`. - -synchronous - Block further exports until this one finishes. In case of *false* the control will be given to the next export template as soon as this one was started. - -attempts - Number of attempts before giving up on the export and writing the failed request to file. The failed request will be written to *failed_posts_dir*. - -fields - List of fields for the exported event. - - -One **field template** will contain the following parameters: - -path - Path for the exported content. Possible prefixes here are: - - *\*exp* - Reference to the exported record. - - *\*hdr* - Reference to the header content. Available in case of **\*file_csv** and **\*file_fwv** export types. - - *\*trl* - Reference to the trailer content. Available in case of **\*file_csv** and **\*file_fwv** export types. - -type - The field type will give out the logic for generating the value. Values used depend on the type of prefix used in path. - - For *\*exp*, following field types are implemented: - - **\*variable** - Writes out the variable value, overwriting previous one set. - - **\*composed** - Writes out the variable value, postpending to previous value set - - **\*filler** - Fills the values with a fixed lentgh string. - - **\*constant** - Writes out a constant - - **\*datetime** - Parses the value as datetime and reformats based on the *layout* attribute. - - **\*combimed** - Writes out a combined mediation considering events with the same *CGRID*. - - **\*masked_destination** - Masks the destination using *\** as suffix. Matches the destination field against the list defined via *mask_destinationd_id* field. - - **\*http_post** - Uses a HTTP server as datasource for the value exported. - - For *\*hdr* and *\*trl*, following field types are possible: - - **\*filler** - Fills the values with a string. - - **\*constant** - Writes out a constant - - **\*handler** - Will obtain the content via a handler. This works in tandem with the attribute *handler_id*. - -value - The exported value. Works in tandem with *type* attribute. Possible prefixes for dynamic values: - - **\*req** - Data is taken from the current request coming from the *CDRs* component. - -mandatory - Makes sure that the field cannot have empty value (errors otherwise). - -tag - Used for debug purposes in logs. - -width - Used to control the formatting, enforcing the final value to a specific number of characters. - -strip - Used when the value is higher than *width* allows it, specifying the strip strategy. Possible values are: - - **\*right** - Strip the suffix. - - **\*xright** - Strip the suffix, postpending one *x* character to mark the stripping. - - **\*left** - Strip the prefix. - - **\*xleft** - Strip the prefix, prepending one *x* character to mark the stripping. - -padding - Used to control the formatting. Applied when the data is smaller than the *width*. Possible values are: - - **\*right** - Suffix with spaces. - - **\*left** - Prefix with spaces. - - **\*zeroleft** - Prefix with *0* chars. - -mask_destinationd_id - The destinations profile where we match the *masked_destinations*. - -hander_id - The identifier of the handler to be executed in case of *\*handler* *type*. - - - - - - - - diff --git a/docs/ers.rst b/docs/ers.rst deleted file mode 100644 index cd9582df3..000000000 --- a/docs/ers.rst +++ /dev/null @@ -1,345 +0,0 @@ - -.. _MySQL: https://dev.mysql.com/ -.. _PostgreSQL: https://www.postgresql.org/ -.. _MSSQL: https://www.microsoft.com/en-us/sql-server/ -.. _Kamailio: https://www.kamailio.org/w/ -.. _OpenSIPS: https://opensips.org/ -.. _Kafka_: https://kafka.apache.org/ - -.. EventReaderService: - -EventReaderService -================== - - -**EventReaderService/ERs** is a subsystem designed to read events coming from external sources and convert them into internal ones. The converted events are then sent to other CGRateS subsystems, like *SessionS* where further processing logic is applied to them. - -The translation between external and internal events is done based on field mapping, defined in :ref:`JSON configuration `. - - -Configuration -------------- - -The **EventReaderService** is configured within *ers* section from :ref:`JSON configuration `. - - -Sample config -^^^^^^^^^^^^^ - -With explanations in the comments: - -:: - - "ers": { - "enabled": true, // enable the service - "sessions_conns": ["*internal"], // connection towards SessionS - "readers": [ // list of active readers - { - "id": "file_reader2", // file_reader2 reader - "run_delay": "-1", // reading of events it is triggered outside of ERs - "field_separator": ";", // field separator definition - "type": "*file_csv", // type of reader, *file_csv can read .csv files - "row_length" : 0, // Number of fields from csv file - "flags": [ // influence processing logic within CGRateS workflow - "*cdrs", // *cdrs will create CDRs - "*log" // *log will log the events to syslog - ], - "source_path": "/tmp/ers2/in", // location of the files - "processed_path": "/tmp/ers2/out", // move the files here once processed - "fields":[ // mapping definition between line index in the file and CGRateS field - { - "tag": "OriginID", // OriginID together with OriginHost will - "path": "*cgreq.OriginID", // uniquely identify the session on CGRateS side - "type": "*variable", - "value": "~*req.0",q // take the content from line index 0 - "mandatory": true // in the request file - }, - { - "tag": "RequestType", // RequestType instructs SessionS - "path": "*cgreq.RequestType",// about charging type to apply for the event - "type": "*variable", - "value": "~*req.1", - "mandatory": true - }, - { - "tag": "Category", // Category serves for ataching Account - "path": "*cgreq.Category", // and RatingProfile to the request - "type": "*constant", - "value": "call", - "mandatory": true - }, - { - "tag": "Account", // Account is required by charging - "path": "*cgreq.Account", - "type": "*variable", - "value": "~*req.3", - "mandatory": true - }, - { - "tag": "Subject", // Subject is required by charging - "path": "*cgreq.Subject", - "type": "*variable", - "value": "~*req.3", - "mandatory": true - }, - { - "tag": "Destination", // Destination is required by charging - "path": "*cgreq.Destination", - "type": "*variable", - "value": "~*req.4:s/0([1-9]\\d+)/+49${1}/", - "mandatory": true // Additional mediation is performed on number format - }, - { - "tag": "AnswerTime", // AnswerTime is required by charging - "path": "*cgreq.AnswerTime", - "type": "*variable", - "value": "~*req.5", - "mandatory": true - }, - { - "tag": "Usage", // Usage is required by charging - "path": "*cgreq.Usage", - "type": "*variable", - "value": "~*req.6", - "mandatory": true - }, - { - "tag": "HDRExtra1", // HDRExtra1 is transparently stored into CDR - "path": "*cgreq.HDRExtra1", // as extra field not used by CGRateS - "type": "*composed", - "value": "~*req.6", - "mandatory": true - } - ], - } - ] - } - - -Config params -^^^^^^^^^^^^^ - -Most of the parameters are explained in :ref:`JSON configuration `, hence we mention here only the ones where additional info is necessary or there will be particular implementation for *EventReaderService*. - - -readers - List of reader profiles which ERs manages. Simultaneous readers of the same type are possible. - -id - Reader identificator, used mostly for debug. The id should be unique per each reader since it can influence updating configuration from different *.json* configuration. - -type - Reader type. Following types are implemented: - - **\*file_csv** - Reader for *comma separated* files. - - **\*file_xml** - Reader for *.xml* formatted files. - - **\*file_fwv** - Reader for *fixed width value* formatted files. - - **\*kafka_json_map** - Reader for hashmaps within Kafka_ database. - - **\*sql** - Reader for generic content out of *SQL* databases. Supported databases are: MySQL_, PostgreSQL_ and MSSQL_. - -run_delay - Duration interval between consecutive reads from source. If 0 or less, *ERs* relies on external source (ie. Linux inotify for files) for starting the reading process. - -concurrent_requests - Limits the number of concurrent reads from source (ie: the number of simultaneously opened files). - -source_path - Path towards the events source - -processed_path - Optional path for moving the events source to after processing. - -xml_root_path - Used in case of XML content and will specify the prefix path applied to each xml element read. - -tenant - Will auto-populate the Tenant within the API calls sent to CGRateS. It has the form of a RSRParser. If undefined, default one from *general* section will be used. - -timezone - Defines the timezone for source content which does not carry that information. If undefined, default one from *general* section will be used. - -filters - List of filters to pass for the reader to process the event. For the dynamic content (prefixed with *~*) following special variables are available: - - **\*vars** - Request related shared variables between processors, populated especially by core functions. The data put inthere is not automatically transfered into requests sent to CGRateS, unless instructed inside templates. - - **\*tmp** - Temporary container to be used when exchanging information between fields. - - **\*req** - Request read from the source. In case of file content without field name, the index will be passed instead of field source path. - - **\*hdr** - Header values (available only in case of *\*file_fwv*). In case of file content without field name, the index will be passed instead of field source path. - - **\*trl** - Trailer values (available only in case of *\*file_fwv*). In case of file content without field name, the index will be passed instead of field source path. - -flags - Special tags enforcing the actions/verbs done on an event. There are two types of flags: **main** and **auxiliary**. - - There can be any number of flags or combination of those specified in the list however the flags have priority one against another and only some simultaneous combinations of *main* flags are possible. - - The **main** flags will select mostly the action taken on a request. - - The **auxiliary** flags only make sense in combination with **main** ones. - - Implemented **main** flags are (in order of priority, and not working simultaneously unless specified): - - **\*log** - Logs the Event read. Can be used together with other *main* flags. - - **\*none** - Disable transfering the Event from *Reader* to *CGRateS* side. - - **\*dryrun** - Together with not transfering the Event on CGRateS side will also log it, useful for troubleshooting. - - **\*auth** - Sends the Event for authorization on CGRateS. - - Auxiliary flags available: **\*attributes**, **\*thresholds**, **\*stats**, **\*resources**, **\*accounts**, **\*routes**, **\*routes_ignore_errors**, **\*routes_event_cost**, **\*routes_maxcost** which are used to influence the auth behavior on CGRateS side. More info on that can be found on the **SessionS** component's API behavior. - - **\*initiate** - Initiates a session out of Event on CGRateS side. - - Auxiliary flags available: **\*attributes**, **\*thresholds**, **\*stats**, **\*resources**, **\*accounts** which are used to influence the behavior on CGRateS side. - - **\*update** - Updates a session with the Event on CGRateS side. - - Auxiliary flags available: **\*attributes**, **\*accounts** which are used to influence the behavior on CGRateS side. - - **\*terminate** - Terminates a session using the Event on CGRateS side. - - Auxiliary flags available: **\*thresholds**, **\*stats**, **\*resources**, **\*accounts** which are used to influence the behavior on CGRateS side. - - **\*message** - Process the Event as individual message charging on CGRateS side. - - Auxiliary flags available: **\*attributes**, **\*thresholds**, **\*stats**, **\*resources**, **\*accounts**, **\*routes**, **\*routes_ignore_errors**, **\*routes_event_cost**, **\*routes_maxcost** which are used to influence the behavior on CGRateS side. - - **\*event** - Process the Event as generic event on CGRateS side. - - Auxiliary flags available: all flags supported by the "SessionSv1.ProcessEvent" generic API - - **\*cdrs** - Build a CDR out of the Event on CGRateS side. Can be used simultaneously with other flags (except **\*dryrun**) - -path - Defined within field, specifies the path where the value will be written. Possible values: - - **\*vars** - Write the value in the special container, *\*vars*, available for the duration of the request. - - **\*cgreq** - Write the value in the request object which will be sent to CGRateS side. - - **\*hdr** - Header values (available only in case of *\*file_fwv*). In case of file content without field name, the index will be passed instead of field source path. - - **\*trl** - Trailer values (available only in case of *\*file_fwv*). In case of file content without field name, the index will be passed instead of field source path. - - -type - Defined within field, specifies the logic type to be used when writing the value of the field. Possible values: - - **\*none** - Pass - - **\*filler** - Fills the values with an empty string - - **\*constant** - Writes out a constant - - **\*variable** - Writes out the variable value, overwriting previous one set - - **\*composed** - Writes out the variable value, postpending to previous value set - - **\*usage_difference** - Calculates the usage difference between two arguments passed in the *value*. Requires 2 arguments: *$stopTime;$startTime* - - **\*sum** - Calculates the sum of all arguments passed within *value*. It supports summing up duration, time, float, int autodetecting them in this order. - - **\*difference** - Calculates the difference between all arguments passed within *value*. Possible value types are (in this order): duration, time, float, int. - - **\*value_exponent** - Calculates the exponent of a value. It requires two values: *$val;$exp* - - **\*template** - Specifies a template of fields to be injected here. Value should be one of the template ids defined. - - -value - The captured value. Possible prefixes for dynamic values are: - - **\*req** - Take data from current request coming from the reader. - - **\*vars** - Take data from internal container labeled *\*vars*. This is valid for the duration of the request. - - **\*cgreq** - Take data from the request being sent to :ref:`SessionS`. This is valid for one active request. - - **\*cgrep** - Take data from the reply coming from :ref:`SessionS`. This is valid for one active reply. - -mandatory - Makes sure that the field cannot have empty value (errors otherwise). - -tag - Used for debug purposes in logs. - -width - Used to control the formatting, enforcing the final value to a specific number of characters. - -strip - Used when the value is higher than *width* allows it, specifying the strip strategy. Possible values are: - - **\*right** - Strip the suffix. - - **\*xright** - Strip the suffix, postpending one *x* character to mark the stripping. - - **\*left** - Strip the prefix. - - **\*xleft** - Strip the prefix, prepending one *x* character to mark the stripping. - -padding - Used to control the formatting. Applied when the data is smaller than the *width*. Possible values are: - - **\*right** - Suffix with spaces. - - **\*left** - Prefix with spaces. - - **\*zeroleft** - Prefix with *0* chars. - - - - - diff --git a/docs/filters.rst b/docs/filters.rst deleted file mode 100644 index a91d9e0f9..000000000 --- a/docs/filters.rst +++ /dev/null @@ -1,132 +0,0 @@ -.. _FilterS: - -FilterS -======= - -**FilterS** are code blocks applied to generic events (hashmaps) in order to allow/deny further processing. - -A Tenant will define multiple Filter profiles via .csv or API calls. The Filter profile ID is unique within a tenant but it can be repeated over multiple Tenants. - -In order to be used in event processing, a Filter profile will be attached inside another subsystem profile definition, otherwise Filter profile will have no effect on it's own. - -A subsystem can use a *Filter* via *FilterProfile* or in-line (ad-hock in the same place where subsystem profile is defined). - - -Filter profile --------------- - -Definition:: - - type Filter struct { - Tenant string - ID string - Rules []*FilterRule - ActivationInterval *utils.ActivationInterval - } - -A Filter profile can be shared between multiple subsystem profile definitions. - -A Filter profile can contain any number of Filter rules and each of them must pass in order for the filter profile to pass. - -A Filter profile can be activated on specific interval, if multiple filters are used within a subsystem profile at least one needs to be active and passing in order for the subsystem profile to pass the event. - - -Filter rule ------------ - -Definition:: - - type FilterRule struct { - Type string // Filter type - Element string // Name of the field providing us the Values to check (used in case of some ) - Values []string // Filter definition - } - - -The matching logic of each FilterRule is given by it's type. - -The following types are implemented: - -\*string* - Will match in full the *Element* with at least one value defined inside *Values*. - Any of the values matching will have the FilterRule as *matched*. - -\*notstring - Is the negation of *\*string*. - -\*prefix - Will match at beginning of *Element* one of the values defined inside *Values*. - -\*notprefix - Is the negation of *\*prefix*. - -\*suffix - Will match at end of *Element* one of the values defined inside *Values*. - -\*notsuffix* - Is the negation of *\*suffix*. - -\*empty - Will make sure that *Element* is empty or it does not exist in the event. - -\*notempty - Is the negation of *\*empty*. - -\*exists - Will make sure that *Element* exists in the event. - -\*notexists - Is the negation of *\*exists*. - -\*timings - Will compare the time contained in *Element* with one of the TimingIDs defined in Values. - -\*nottimings - Is the negation of *\*timings*. - -\*destinations - Will make sure that the *Element* is a prefix contained inside one of the destination IDs as *Values*. - -\*notdestinations - Is the negation of *\*destinations*. - -\*rsr - Will match the *RSRFilters* defined in Values on the Element. - -\*notrsr* - Is the negation of *\*rsr*. - -*\*lt* (less than), *\*lte* (less than or equal), *\*gt* (greather than), *\*gte* (greather than or equal) - Are comparison operators and they pass if at least one of the values defined in *Values* are passing for the *Element* of event. The operators are able to compare string, float, int, time.Time, time.Duration, however both types need to be the same, otherwise the filter will raise *incomparable* as error. - - -Inline Filter --------------- - -In order to facilitate quick filter definition (without the need of separate FilterProfile), one can define filters directly as FilterIDs following the special format. - -Inline filter format:: - - filterType:fieldName:fieldValue - -Example:: - - *string:WebsiteName:CGRateS.org - - -Subsystem profiles selection based on Filters ---------------------------------------------- - -When a subsystem will process an event it will need to find fast enough (close to real-time and most preferably with constant speed) all the profiles having filters matching the event. For low number of profiles (tens of) we can go through all available profiles and check their filters but as soon as the number of profiles is growing, processing time will exponentially grow also. As an example, the *AttributeS* need to deal with 20 mil+ profiles in case of number portability implementation. - -In order to guarantee constant processing time - **O(1)** - *CGRateS* will use internally a profile selection mechanism based on indexed filters which can be enabled within *.json* configuration file via *indexed_selects*. When *indexed_selects* is disabled, the indexes will not be used at all and profiles will be checked one by one. On the other hand, if *indexed_selects* is enabled, each FilterProfile needs to have at least one *\*string* or *\*prefix* type in order to be visible to the indexes (otherwise being completely ignored). - -The following settings are further applied once *indexed_selects* is enabled: - -string_indexed_fields - list of field names in the event which will be checked against string indexes (defaults to nil which means check all fields) - -prefix_indexed_fields - list of field names in the event which will be checked against prefix indexes (default is empty, hence prefix matching is disabled inside indexes - small optimization since for prefixes there are multiple queries done for one field) - - diff --git a/docs/fsagent.rst b/docs/fsagent.rst deleted file mode 100644 index f37719b9c..000000000 --- a/docs/fsagent.rst +++ /dev/null @@ -1,5 +0,0 @@ -FreeSWITCHAgent -=============== - - -TBD \ No newline at end of file diff --git a/docs/guardian.rst b/docs/guardian.rst deleted file mode 100644 index 255c2fdf8..000000000 --- a/docs/guardian.rst +++ /dev/null @@ -1,39 +0,0 @@ -.. _guardian: - -Guardian -======== - -Guardian is CGRateS' internal locking mechanism that ensures data consistency during concurrent operations. - -What Guardian Does ------------------- - -Guardian prevents race conditions when multiple processes try to access or modify the same data. It uses string-based locks, typically created using some variation of the tenant and ID of the resource being protected, often with a type prefix. Guardian can use either explicit IDs or generate UUIDs internally for reference-based locking when no specific ID is provided. - -When CGRateS Uses Guardian --------------------------- - -Guardian protects: - -* Account balance operations (debits/topups) - the most critical use case -* ResourceProfiles, Resources, StatQueueProfiles, StatQueues, ThresholdProfiles, and Thresholds while they're being used or loaded into cache -* Filter index updates -* There are other cases, but the ones listed above are the most frequent applications - -Performance Implications ------------------------- - -Guardian affects system performance in these ways: - -* Operations on the same resource are processed one after another, not simultaneously -* Under heavy load on the same resources, operations may queue up and wait -* System throughput is better when operations are distributed across different resources - -Configuration -------------- - -Guardian has a single configuration option: - -The `locking_timeout` setting in the general configuration determines how long Guardian will hold a lock before forcing it to release. Zero timeout (no timeout) is the default and recommended setting. However, setting a reasonable timeout can help prevent system hangs if a process fails to release a lock. - -When a timeout occurs, Guardian logs a warning and forces the lock to release. This keeps the system running, but the operation that timed out may fail. diff --git a/docs/httpagent.rst b/docs/httpagent.rst deleted file mode 100644 index d927203e6..000000000 --- a/docs/httpagent.rst +++ /dev/null @@ -1,5 +0,0 @@ -HTTPAgent -========= - - -TBD \ No newline at end of file diff --git a/docs/images/CGRateSInternalArchitecture.png b/docs/images/CGRateSInternalArchitecture.png deleted file mode 100644 index 0503c23276a4a6aa9c00dfed4ffae0d63645bb69..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 96708 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�Q01p-0Qx&WkD-|WBRF@9Xf4Guyr?+OV; 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 diff --git a/docs/index.rst b/docs/index.rst index efd29dee2..4a8145f16 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -3,31 +3,14 @@ CGRateS Documentation ===================== -.. warning:: - - **Documentation Migration in Progress** - - This documentation is currently being migrated from v0.11. - - **Currently reliable sections:** - - - **Installation:** Only Debian packages and source installation on Debian systems - - **Architecture:** General component information (some links may be broken) - - All other sections are being updated for version 1.0. - Welcome to `CGRateS`_'s documentation! `CGRateS`_ is a *very fast* (**50k+ CPS**) and *easily scalable* (**replication** included) **Real-time Enterprise Billing Suite** targeted especially for ISPs and Telecom Operators (but not only). .. toctree:: - :maxdepth: 4 + :maxdepth: 2 - overview - architecture installation configuration - administration - tutorial troubleshooting diff --git a/docs/installation.rst b/docs/installation.rst index 297be21d8..3e88c2b4b 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -9,17 +9,6 @@ Installation ============ -.. warning:: - - **Installation Note** - - For version 1.0, only these installation methods currently work: - - - Debian package installation - - Source installation on Debian systems - - Other methods (RedHat packages, Docker) are under development and will be available in future updates. - .. contents:: :local: :depth: 2 @@ -94,46 +83,6 @@ You can add the CGRateS repository to your system's sources list, depending of t .. note:: A complete archive of CGRateS packages is available at http://pkg.cgrates.org/deb/. - -Redhat-based Distributions -^^^^^^^^^^^^^^^^^^^^^^^^^^ - -For .rpm distros, we are using copr to manage the CGRateS packages: - -- If using a version of Linux with dnf: - - .. code-block:: bash - - # sudo yum install -y dnf-plugins-core on RHEL 8 or CentOS Stream - sudo dnf install -y dnf-plugins-core - sudo dnf copr -y enable cgrates/1.0 - sudo dnf install -y cgrates - -- For older distributions: - - .. code-block:: bash - - sudo yum install -y yum-plugin-copr - sudo yum copr -y enable cgrates/1.0 - sudo yum install -y cgrates - -To install a specific version of the package, run: - -.. code-block:: bash - - sudo dnf install -y cgrates-.x86_64 - -Alternatively, you can manually install a specific .rpm package as follows: - -.. code-block:: bash - - wget http://pkg.cgrates.org/rpm/nightly/epel-9-x86_64/cgrates-current.rpm - sudo dnf install ./cgrates_current.rpm - - -.. note:: - The entire archive of CGRateS rpm packages is available at https://copr.fedorainfracloud.org/coprs/cgrates/1.0/packages/ or http://pkg.cgrates.org/rpm/nightly/. - Installing from Source ---------------------- @@ -185,108 +134,6 @@ Installation: sudo ln -s $HOME/go/bin/cgr-console /usr/bin/cgr-console sudo ln -s $HOME/go/bin/cgr-tester /usr/bin/cgr-tester -Installing Using Docker ------------------------ - -CGRateS is also available as Docker images. - -Prerequisites -^^^^^^^^^^^^^ - -- `Docker`_ - -Pull Docker Images -^^^^^^^^^^^^^^^^^^ - -The following commands will pull the CGRateS components: - -:: - - sudo docker pull dkr.cgrates.org/1.0/cgr-engine - sudo docker pull dkr.cgrates.org/1.0/cgr-loader - sudo docker pull dkr.cgrates.org/1.0/cgr-migrator - sudo docker pull dkr.cgrates.org/1.0/cgr-console - sudo docker pull dkr.cgrates.org/1.0/cgr-tester - -Verify the images were pulled successfully: - -:: - - sudo docker images dkr.cgrates.org/1.0/cgr-* - REPOSITORY TAG IMAGE ID CREATED SIZE - dkr.cgrates.org/1.0/cgr-loader latest 5b667e92a475 6 weeks ago 46.5MB - dkr.cgrates.org/1.0/cgr-console latest 464bd1992ab2 6 weeks ago 103MB - dkr.cgrates.org/1.0/cgr-engine latest e20f43491aa8 6 weeks ago 111MB - ... - -.. note:: - While other version-specific tags are available, we recommend using the default **latest** tag for most use cases. - You can check available versions with: - - :: - - curl -X GET https://dkr.cgrates.org/v2/1.0/cgr-engine/tags/list - - -cgr-engine Container -^^^^^^^^^^^^^^^^^^^^ - -The current cgr-engine container entrypoint is: - -:: - - [ - "/usr/bin/cgr-engine", - "-logger=*stdout" - ] - -.. note:: - Verify the entrypoint configuration with: - - :: - - sudo docker inspect --format='{{json .Config.Entrypoint}}' dkr.cgrates.org/1.0/cgr-engine:latest - -Running cgr-engine -^^^^^^^^^^^^^^^^^^ - -Here's a basic example of running cgr-engine with common Docker parameters: - -:: - - sudo docker run --rm \ - -v /path/on/host:/etc/cgrates \ - -p 2012:2012 \ - -e DOCKER_IP=127.0.0.1 \ - -e REDIS_HOST=192.168.122.91 \ - --network bridge \ - --name cgr-engine \ - dkr.cgrates.org/1.0/cgr-engine:latest \ - -config_path=/etc/cgrates \ - -logger=*stdout - -Verify cgr-engine is responding: - -:: - - sudo docker run --rm \ - --name cgr-console \ - --network host \ - dkr.cgrates.org/1.0/cgr-console:latest \ - status - -Key parameters: - -- ``--rm``: automatically remove container when it exits -- ``-v``: mount host directory into container (format: host_path:container_path) -- ``-p``: publish container port to host (format: host_port:container_port) -- ``-e``: set environment variables (optional, only needed if referenced in configuration files) -- ``--network``: specify container networking mode (bridge for isolation, host for direct host network access) -- ``--name``: assign name to container - -.. note:: - The ``-config_path`` and ``-logger`` flags above are cgr-engine specific flags and optional, as those values are already the defaults. - .. _post_install: Post-install Configuration diff --git a/docs/janusagent.rst b/docs/janusagent.rst deleted file mode 100644 index 61cd95579..000000000 --- a/docs/janusagent.rst +++ /dev/null @@ -1,39 +0,0 @@ -.. _JanusAgent: - -JanusAgent -============= - - -**JanusAgent** is an api endpoint that connects to JanusServer through **CGRateS**. -It authorizes webrtc events in **CGRateS** for each user and after managing and creating sessions in JanusServer. - -The **JanusAgent** is configured within *janus_agent* section from :ref:`JSON configuration `. -It will listen on http port 2080 in /janus endpoint as specified in config ,it will accept same http requests that would be sent normally to JanusServer. - -Sample config - -:: - - "janus_agent": { - "enabled": false, // enables the Janus agent: - "url": "/janus", - "sessions_conns": ["*internal"], - "janus_conns": [{ // instantiate connections to multiple Janus Servers - "address": "127.0.0.1:8088", // janus API address - "type": "*ws", // type of the transport to interact via janus API - "admin_address": "localhost:7188", // janus admin address used to retrive more information for sessions and handles - "admin_password": "", // secret to pass restriction to communicate to the endpoint - }], - "request_processors": [], // request processors to be applied to Janus messages -}, - -Config params -^^^^^^^^^^^^^ - -Most of the parameters are explained in :ref:`JSON configuration `, hence we mention here only the ones where additional info is necessary or there will be particular implementation for *JanusAgent*. - -Software Installation ---------------------- - - For detailed information on installing JanusServer on Debian, please refer to its official `repository `_. - diff --git a/docs/kamagent.rst b/docs/kamagent.rst deleted file mode 100644 index 46fcb2613..000000000 --- a/docs/kamagent.rst +++ /dev/null @@ -1,5 +0,0 @@ -KamailioAgent -============= - - -TBD \ No newline at end of file diff --git a/docs/loaders.rst b/docs/loaders.rst deleted file mode 100644 index c64f8d3b0..000000000 --- a/docs/loaders.rst +++ /dev/null @@ -1,5 +0,0 @@ -LoaderS -======= - - -TBD \ No newline at end of file diff --git a/docs/make.bat b/docs/make.bat deleted file mode 100644 index 6f51d5b42..000000000 --- a/docs/make.bat +++ /dev/null @@ -1,170 +0,0 @@ -@ECHO OFF - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build -) -set BUILDDIR=_build -set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . -if NOT "%PAPER%" == "" ( - set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% -) - -if "%1" == "" goto help - -if "%1" == "help" ( - :help - echo.Please use `make ^` where ^ is one of - echo. html to make standalone HTML files - echo. dirhtml to make HTML files named index.html in directories - echo. singlehtml to make a single large HTML file - echo. pickle to make pickle files - echo. json to make JSON files - echo. htmlhelp to make HTML files and a HTML help project - echo. qthelp to make HTML files and a qthelp project - echo. devhelp to make HTML files and a Devhelp project - echo. epub to make an epub - echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter - echo. text to make text files - echo. man to make manual pages - echo. changes to make an overview over all changed/added/deprecated items - echo. linkcheck to check all external links for integrity - echo. doctest to run all doctests embedded in the documentation if enabled - goto end -) - -if "%1" == "clean" ( - for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i - del /q /s %BUILDDIR%\* - goto end -) - -if "%1" == "html" ( - %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/html. - goto end -) - -if "%1" == "dirhtml" ( - %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. - goto end -) - -if "%1" == "singlehtml" ( - %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. - goto end -) - -if "%1" == "pickle" ( - %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can process the pickle files. - goto end -) - -if "%1" == "json" ( - %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can process the JSON files. - goto end -) - -if "%1" == "htmlhelp" ( - %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can run HTML Help Workshop with the ^ -.hhp project file in %BUILDDIR%/htmlhelp. - goto end -) - -if "%1" == "qthelp" ( - %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can run "qcollectiongenerator" with the ^ -.qhcp project file in %BUILDDIR%/qthelp, like this: - echo.^> qcollectiongenerator %BUILDDIR%\qthelp\CGRates.qhcp - echo.To view the help file: - echo.^> assistant -collectionFile %BUILDDIR%\qthelp\CGRates.ghc - goto end -) - -if "%1" == "devhelp" ( - %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. - goto end -) - -if "%1" == "epub" ( - %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The epub file is in %BUILDDIR%/epub. - goto end -) - -if "%1" == "latex" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "text" ( - %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The text files are in %BUILDDIR%/text. - goto end -) - -if "%1" == "man" ( - %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The manual pages are in %BUILDDIR%/man. - goto end -) - -if "%1" == "changes" ( - %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes - if errorlevel 1 exit /b 1 - echo. - echo.The overview file is in %BUILDDIR%/changes. - goto end -) - -if "%1" == "linkcheck" ( - %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck - if errorlevel 1 exit /b 1 - echo. - echo.Link check complete; look for any errors in the above output ^ -or in %BUILDDIR%/linkcheck/output.txt. - goto end -) - -if "%1" == "doctest" ( - %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest - if errorlevel 1 exit /b 1 - echo. - echo.Testing of doctests in the sources finished, look at the ^ -results in %BUILDDIR%/doctest/output.txt. - goto end -) - -:end diff --git a/docs/overview.rst b/docs/overview.rst deleted file mode 100644 index 242e4fb23..000000000 --- a/docs/overview.rst +++ /dev/null @@ -1,200 +0,0 @@ -.. _CGRateS: http://cgrates.org -.. _Go: http://golang.org -.. _Docker: https://www.docker.com/ -.. _Kafka: https://kafka.apache.org/ -.. _redis: https://redis.io/ -.. _mongodb: https://www.mongodb.com/ -.. _api docs: https://pkg.go.dev/github.com/cgrates/cgrates/apier@master -.. _SQS: https://aws.amazon.com/de/sqs/ -.. _AMQP: https://www.amqp.org/ -.. _Asterisk: https://www.asterisk.org/ -.. _FreeSWITCH: https://freeswitch.com/ -.. _Kamailio: https://www.kamailio.org/w/ -.. _OpenSIPS: https://opensips.org/ -.. _Diameter: https://tools.ietf.org/html/rfc6733 -.. _Radius: https://tools.ietf.org/html/rfc2865 -.. _DNS: https://tools.ietf.org/html/rfc1034 -.. _ENUM: https://tools.ietf.org/html/rfc6116 - - - - -Overview -======== - -Starting as a pure **billing engine**, CGRateS has evolved over the years into a reliable **real-time charging framework**, able to accommodate various business cases in a *generic way*. - -Being an *"engine style"* the project focuses on providing best ratio between **functionality** (over 15 daemons/services implemented with a rich number of `features`_ and a development team agile in implementing new ones) and **performance** (dedicated benchmark tool, asynchronous request processing, own transactional cache component), however not losing focus of **quality** (test driven development policy). - -It is written in `Go`_ programming language and accessible from any programming language via JSON RPC. -The code is well documented (**go doc** compliant `API docs`_) and heavily tested (**5k+** tests are part of the unit test suite). - -Meant to be pluggable into existing billing infrastructure and as non-intrusive as possible, -CGRateS passes the decisions about logic flow to system administrators and incorporates as less as possible business logic. - -Modular and flexible, CGRateS provides APIs over a variety of simultaneously accessible communication interfaces: - - **In-process** : optimal when there is no need to split services over different processes - - **JSON over TCP** : most preferred due to its simplicity and readability - - **JSON over HTTP** : popular due to fast interoperability development - - **JSON over Websockets** : useful where 2 ways interaction over same TCP socket is required - - **GOB over TCP** : slightly faster than JSON one but only accessible for the moment out of Go (``_). - -.. _charging-modes: - -CGRateS is capable of four charging modes: - -- \*prepaid - - Session events monitored in real-time - - Session authorization via events with security call timer - - Real-time balance updates with configurable debit interval - - Support for simultaneous sessions out of the same account - - Real-time fraud detection with automatic mitigation - - *Advantage*: real-time overview of the costs and fast detection in case of fraud, concurrent account sessions supported - - *Disadvantage*: more CPU intensive. - -- \*pseudoprepaid - - Session authorization via events - - Charging done at the end of the session out of CDR received - - *Advantage*: less CPU intensive due to less events processed - - *Disadvantage*: as balance updates happen only at the end of the session there can be costs discrepancy in case of multiple sessions out of same account (including going on negative balance). - -- \*postpaid - - Charging done at the end of the session out of CDR received without session authorization - - Useful when no authorization is necessary (trusted accounts) and no real-time event interaction is present (balance is updated only when CDR is present). - -- \*rated - - Special charging mode where there is no accounting interaction (no balances are used) but the primary interest is attaching costs to CDRs. - - Specific mode for Wholesale business processing high-throughput CDRs - - Least CPU usage out of the four modes (fastest charging). - - -.. _features: - -Features --------- - -- Performance oriented. To get an idea about speed, we have benchmarked 50000+ req/sec on comodity hardware without any tweaks in the kernel - - Using most modern programming concepts like multiprocessor support, asynchronous code execution within microthreads, channel based locking - - Built-in data caching system with LRU and TTL support - - Linear performance increase via simple hardware addition - - On demand performance increase via in-process / over network communication between engine services. - -- Modular architecture - - Plugable into existing infrastructure - - Non-intrusive into existing setups - - Easy to enhance functionality by writing custom components - - Flexible API accessible via both **GOB** (`Go`_ specific, increased performance) or **JSON** (platform independent, universally accessible) - - Easy distribution (one binary concept, can run via NFS on all Linux servers without install). - -- Easy administration - - One binary can run on all Linux servers without additional installation (simple copy) - - Can run diskless via NFS - - Virtualization/containerization friendly(runs on Docker_). - -- GOCS (Global Online Charging System) - - Support for global networks with one master + multi-cache nodes around the globe for low query latency - - Mutiple Balance types per Account (\*monetary, \*voice, \*sms, \*data, \*generic) - - Unlimited number of Account Balances with weight based prioritization - - Various Balance filters (ie: per-destination, roaming-only, weekend-only) - - Support for Volume based discounts and automatic bonuses (ie: 5 SMS free for every 10 minutes in one hour to specific destination) - - Session based charging with support for concurrent sessions per account and per session dynamic debit interval - - Session emulation combined with Derived Charging (separate charging for distributors chaining, customer/supplier parallel calculations) - - Balance reservation and refunds - - Event based charging (ie: SMS, MESSAGE) - - Built-in Task-Scheduler supporting both one-time as well as recurrent actions (automatic subscriptions management, recurrent \*debit/\*topup, DID charging) - - Real-time balance monitors with automatic actions triggered (bonuses or fraud detection). - -- Highly configurable Rating - - Connect Fees - - Priced Units definition - - Rate increments - - Rate groups (ie. charge first minute in a call as a whole and next ones per second) - - Verbose durations(up to nanoseconds billing) - - Configurable decimals per destination - - Rating subject categorization (ie. premium/local charges, roaming) - - Recurrent rates definition (per year, month, day, dayOfWeek, time) - - Rating Profiles activation times (eg: rates becoming active at specific time in the future) - - Rating Profiles fallback (per subject destinations with fallback to server wide pricing) - - Verbose charging logs to comply strict rules imposed by some country laws. - -- Multi-Tenant from day one - - Default Tenant configurable for one-tenant systems - - Security enforced for RPC-API on Tenant level. - -- Online configuration reloads without restart - - Engine configuration from .json folder or remote http server - - Tariff Plans from .csv folder or database storage. - -- CDR server - - Optional offline database storage - - Online (rating queues) or offline (via RPC-API) exports with customizable content via .json templates - - Multiple export interfaces: files, HTTP, AMQP_, SQS_, Kafka_. - -- Generic Event Reader - - Process various sources of events and convert them into internal ones which are sent to CDR server for rating - - Conversion rules defined in .json templates - - Supported interfaces: .csv, .xml, fixed width files, Kafka_. - -- Events mediation - - Ability to add/change/remove information within *Events* to achieve additional services or correction - - Performance oriented. - -- Routing server for VoIP - - Implements strategies like *Least Cost Routing*, *Load Balacer*, *High Availability* - - Implements *Number Portability* service. - -- Resource allocation controller - - Generic filters for advanced logic - - In-memory operations for increased performance - - Backup in offline storage. - -- Stats service - - Generic stats (\*sum, \*difference, \*multiply, \*divide) - - In-memory operations for increased performance - - Backup in offline storage. - -- Thresholds monitor - - Particular implementation of *Fraud Detection with automatic mitigation* - - Execute independent actions which can serve various purposes (notifications, accounts disables, bonuses to accounts). - -- Multiple RPC interfaces - - Support for *JSON-RPC*, *GOB-PC* over TCP, HTTP, websockets - - Support for HTTP-REST interface. - -- Various agents to outside world: - - Asterisk_ - - FreeSWITCH_ - - Kamailio_ - - OpenSIPS_ - - Diameter_ - - Radius_ - - Generic HTTP - - DNS_/ENUM_. - -- Built in High-Availability mechanisms: - - Dispatcher with static or dynamic routing - - Server data replication - - Client remote data querying. - - -- Good documentation ( that's me :). - -- **"Free as in Beer"** with commercial support available on-demand. - - -Links ------ - -- CGRateS home page ``_ -- Documentation ``_ -- API docs ``_ -- Source code ``_ -- Travis CI ``_ -- Google group ``_ -- IRC `irc.freenode.net #cgrates `_ - - -License -------- - -`CGRateS`_ is released under the terms of the `[GNU GENERAL PUBLIC LICENSE Version 3] `_. diff --git a/docs/prometheus.rst b/docs/prometheus.rst deleted file mode 100644 index 51c48832b..000000000 --- a/docs/prometheus.rst +++ /dev/null @@ -1,105 +0,0 @@ -.. _prometheus_agent: - -PrometheusAgent -=============== - -**PrometheusAgent** is a CGRateS component that exposes metrics for Prometheus monitoring systems. It serves as a bridge between CGRateS and Prometheus by collecting and exposing metrics from: - -1. **Core metrics** - collected from configured CGRateS engines via CoreSv1.Status API -2. **StatQueue metrics** - values from CGRateS :ref:`StatS ` component, collected via StatSv1.GetQueueFloatMetrics API - -For core metrics, the agent computes real-time values on each Prometheus scrape request. For StatQueue metrics, it retrieves the current state of the stored StatQueues without additional calculations. - -Configuration -------------- - -Example configuration in the JSON file: - -.. code-block:: json - - "prometheus_agent": { - "enabled": true, - "path": "/prometheus", - "cores_conns": ["*internal", "external"], - "stats_conns": ["*internal", "external"], - "stat_queue_ids": ["cgrates.org:SQ_1", "SQ_2"] - } - -The default configuration can be found in the :ref:`configuration` section. - -Parameters ----------- - -enabled - Enable the PrometheusAgent module. Possible values: - -path - HTTP endpoint path where Prometheus metrics will be exposed, e.g., "/prometheus" or "/metrics" - -cores_conns - List of connection IDs to CoreS components for collecting core metrics. Empty list disables core metrics collection. Possible values: <""|*internal|$rpc_conns_id> - -stats_conns - List of connection IDs to StatS components for collecting StatQueue metrics. Empty list disables StatQueue metrics collection. Possible values: <""|*internal|$rpc_conns_id> - -stat_queue_ids - List of StatQueue IDs to collect metrics from. Can include tenant in format <[tenant]:ID>. If tenant is not specified, default tenant from general configuration is used. - -Available Metrics ------------------ - -The PrometheusAgent exposes the following metrics: - -1. **StatQueue Metrics** - - Uses the naming format ``cgrates_stats_metrics`` with labels for tenant, queue, and metric type - - Obtained from StatS services on each scrape request - - Example of StatQueue metrics output: - - .. code-block:: none - - # HELP cgrates_stats_metrics Current values for StatQueue metrics - # TYPE cgrates_stats_metrics gauge - cgrates_stats_metrics{metric="*acc",queue="SQ_1",tenant="cgrates.org"} 7.73779 - cgrates_stats_metrics{metric="*tcc",queue="SQ_1",tenant="cgrates.org"} 23.21337 - cgrates_stats_metrics{metric="*acc",queue="SQ_2",tenant="cgrates.org"} 11.34716 - cgrates_stats_metrics{metric="*tcc",queue="SQ_2",tenant="cgrates.org"} 34.04147 - -.. note:: - StatQueue metrics don't include node_id labels since StatQueues can be shared between CGRateS instances. Users should ensure StatQueue IDs are unique across their environment. - -2. **Core Metrics** (when cores_conns is configured) - - Standard Go runtime metrics (go_goroutines, go_memstats_*, etc.) - - Standard process metrics (process_cpu_seconds_total, process_open_fds, etc.) - - Node identification via "node_id" label, allowing multiple CGRateS engines to be monitored - - Example of core metrics output: - - .. code-block:: none - - # HELP go_goroutines Number of goroutines that currently exist. - # TYPE go_goroutines gauge - go_goroutines{node_id="e94160b"} 40 - - # HELP process_cpu_seconds_total Total user and system CPU time spent in seconds. - # TYPE process_cpu_seconds_total counter - process_cpu_seconds_total{node_id="e94160b"} 0.34 - - # HELP go_memstats_alloc_bytes Number of bytes allocated in heap and currently in use. - # TYPE go_memstats_alloc_bytes gauge - go_memstats_alloc_bytes{node_id="e94160b"} 1.1360808e+07 - -How It Works ------------- - -The PrometheusAgent operates differently than other CGRateS components that use connection failover: - -- When multiple connections are configured in stats_conns, the agent collects metrics from **all** connections, not just the first available one -- When multiple connections are configured in cores_conns, the agent attempts to collect metrics from **all** connections, labeling them with their respective node_id -- The agent processes metrics requests only when Prometheus sends a scrape request to the configured HTTP endpoint - -You can view all exported metrics and see what Prometheus would scrape by making a simple curl request to the HTTP endpoint: - -.. code-block:: bash - - curl http://localhost:2080/prometheus diff --git a/docs/radagent.rst b/docs/radagent.rst deleted file mode 100644 index 79605b9a5..000000000 --- a/docs/radagent.rst +++ /dev/null @@ -1,5 +0,0 @@ -RadiusAgent -=========== - - -TBD \ No newline at end of file diff --git a/docs/rankings.rst b/docs/rankings.rst deleted file mode 100644 index cd448e072..000000000 --- a/docs/rankings.rst +++ /dev/null @@ -1,166 +0,0 @@ -.. _RankingS: - - -RankingS -======== - - -**RankingS** is a standalone subsystem part of the **CGRateS** infrastructure, designed to work as an extension of the :ref:`StatS`, by regularly querying it for a list of predefined StatProfiles and ordering them based on their metrics. - -Complete interaction with **RankingS** is possible via `CGRateS RPC APIs `_. - -Due it's real-time nature, **RankingS** are designed towards high throughput being able to process thousands of queries per second. This is doable since each *Ranking* is a very light object, held in memory and eventually backed up in :ref:`DataDB`. - - -Processing logic ----------------- - -In order for **RankingS** to start querying the :ref:`StatS`, it will need to be *scheduled* to do that. Scheduling is being done using `Cron Expressions `_. - -Once *Cron Expressions* are defined within a *RankingProfile*, internal **Cron Scheduler** needs to be triggered. This can happen in two different ways: - -Automatic Query Scheduling - The profiles needing querying will be inserted into **RankingS** :ref:`JSON configuration `. By leaving any part of *ranking_id* or *tenat* empty, it will be interpreted as catch-all filter. - -API Scheduling - The profiles needing querying will be sent inside arguments to the `RankingSv1.ScheduleQueries API call `_. - - -Offline storage ---------------- - -Offline storage is optionally possible, by enabling profile *Stored* flag and configuring the *store_interval* inside :ref:`JSON configuration `. - - -Ranking querying ----------------- - -In order to query a **Ranking** (ie: to be displayed in a web interface), one should use the `RankingSv1.GetRanking API call `_ or `RankingSv1.GetRankingSummary API call `. - - -Ranking exporting ---------------- - -On each **Ranking** change, it will be possible to send a specially crafted *RankingSummary* event to one of the following subsystems: - -**ThresholdS** - Sending the **RankingUpdate** Event gives the administrator the possiblity to react to *Ranking* changes, including escalation strategies offered by the **TresholdS** paramters. - Fine tuning parameters (ie. selecting only specific ThresholdProfiles to increase speed) are available directly within the **TrendProfile**. - -**EEs** - **EEs** makes it possible to export the **RankingUpdate** to all the availabe outside interfaces of **CGRateS**. - -Both exporting options are enabled within :ref:`JSON configuration `. - - -Parameters ----------- - - -RankingS -^^^^^^ - -**RankingS** is the **CGRateS** service component responsible of generating the **Ranking** queries. - -It is configured within **RankingS** section from :ref:`JSON configuration ` via the following parameters: - -enabled - Will enable starting of the service. Possible values: . - -store_interval - Time interval for backing up the RankingS into *DataDB*. 0 To completely disable the functionality, -1 to enable synchronous backup. Anything higher than 0 will give the interval for asynchronous backups. - -stats_conns - List of connections where we will query the stats. - -scheduled_ids - Limit the RankingProfiles generating queries towards **StatS**. Empty to enable all available RankingProfiles or just tenants for all the available profiles on a tenant. - -thresholds_conns - Connection IDs towards *ThresholdS* component. If not defined, there will be no notifications sent to *ThresholdS* on *Trend* changes. - -ees_conns - Connection IDs towards the *EEs* component. If left empty, no exports will be performed on *Trend* changes. - -ees_exporter_ids - Limit the exporter profiles executed on *Ranking* changes. - - -RankingProfile -^^^^^^^^^^^^^^ - -Ís made of the following fields: - -Tenant - The tenant on the platform (one can see the tenant as partition ID). - -ID - Identifier for the *RankingProfile*, unique within a *Tenant*. - -Schedule - Cron expression scheduling gathering of the metrics. - -StatIDs - List of **StatS** instances to query. - -MetricIDs - Limit the list of metrics from the stats instance queried. - -Sorting - Sorting strategy for the StatIDs. Possible values: - - \*asc - Sort the StatIDs ascendent based on list of MetricIDs provided in SortParameters. One or more MetricIDs can be specified in hte SortingParameters for the cases when one level sort is not enough to differentiate them. If all metrics will be equal, a random sort will be applied. - - \*desc - Sort the StatIDs descendat based on list of MetricIDs provided in SortParameters. One or more MetricIDs can be specified in hte SortingParameters for the cases when one level sort is not enough to differentiate them. If all metrics will be equal, a random sort will be applied. - -SortingParameters - List of sorting parameters. For the current sorting strategies (\*asc/\*desc) there will be one or more MetricIDs defined. - Metric can be defined in compressed mode (ie. ["Metric1","Metric2"]) or extended mode (ie: ["Metric1:true", "Metric2:false"]) where *false* will reverse the sorting logic for that particular metric (ie: ["\*tcc:true","\*pdd:false"] with \*desc sorting strategy). - -Stored - Enable storing of this *Ranking* intance for persistence. - -ThresholdIDs - Limit *TresholdProfiles* processing the *RankingUpdate* for this *RankingProfile*. - - -Ranking -^^^^^^^ - -instance is made out of the following fields: - -Tenant - The tenant on the platform (one can see the tenant as partition ID). - -ID - Unique *Ranking* identifier on a *Tenant*. - -LastUpdate - Time of the last Metrics update. - -Metrics - Stat Metrics and their values at the query time. - -Sorting - Archived sorting strategy from the profile. - -SortingParameters - Archived list of sorted parameters from the profile. - -SortedStatIDs - List of queried stats, sorted based on sorting strategy and parameters. - - -Use cases ---------- - -* Ranking computation for commercial and monitoring applications. -* Revenue assurance applications. -* Fraud detection by ranking specific billing metrics during sensitive time intervals (\*acc, \*tcc, \*tcd). -* Building call patterns. -* Building statistical information to train systems capable of artificial intelligence. -* Building quality metrics used in traffic routing. - - diff --git a/docs/rates.rst b/docs/rates.rst deleted file mode 100644 index e78b6a955..000000000 --- a/docs/rates.rst +++ /dev/null @@ -1,5 +0,0 @@ -RateS -===== - - -TBD diff --git a/docs/resources.rst b/docs/resources.rst deleted file mode 100644 index 75000faf2..000000000 --- a/docs/resources.rst +++ /dev/null @@ -1,136 +0,0 @@ -.. _ResourceS: - -ResourceS -========= - - -**ResourceS** is a standalone subsystem part of the **CGRateS** infrastructure, designed to allocate virtual resources for the generic *Events* (hashmaps) it receives. - -Both receiving of *Events* as well as operational commands on the virtual resources is performed via a complete set of `CGRateS RPC APIs `_. - -Due it's real-time nature, **ResourceS** are designed towards high throughput being able to process thousands of *Events* per second. This is doable since each *Resource* is a very light object, held in memory and eventually backed up in *DataDB*. - - -Parameters ----------- - -ResourceS -^^^^^^^^^ - -**ResourceS** is the **CGRateS** component responsible of handling the *Resources*. - -It is configured within **resources** section from :ref:`JSON configuration ` via the following parameters: - -enabled - Will enable starting of the service. Possible values: . - -store_interval - Time interval for backing up the stats into *DataDB*. - -thresholds_conns - Connections IDs towards *ThresholdS* component. If not defined, there will be no notifications sent to *ThresholdS* on *Resource* changes. - -indexed_selects - Enable profile matching exclusively on indexes. If not enabled, the *ResourceProfiles* are checked one by one which for a larger number can slow down the processing time. Possible values: . - -string_indexed_fields - Query string indexes based only on these fields for faster processing. If commented out, each field from the event will be checked against indexes. If uncommented and defined as empty list, no fields will be checked. - -prefix_indexed_fields - Query prefix indexes based only on these fields for faster processing. If defined as empty list, no fields will be checked. - -nested_fields - Applied when all event fields are checked against indexes, and decides whether subfields are also checked. - - -ResourceProfile -^^^^^^^^^^^^^^^ - -The **ResourceProfile** is the configuration of a *Resource*. This will be performed over `CGRateS RPC APIs `_ or *.csv* files. A profile is comprised out of the following parameters: - -Tenant - The tenant on the platform (one can see the tenant as partition ID). - -ID - Identifier for the *ResourceProfile*, unique within a *Tenant*. - -FilterIDs - List of *FilterProfiles* which should match in order to consider the *ResourceProfile* matching the event. - -ActivationInterval - The time interval when this profile becomes active. If undefined, the profile is always active. Other options are start time, end time or both. - -UsageTTL - Autoexpire resource allocation after this time duration. - -Limit - The number of allocations this resource is entitled to. - -AllocationMessage - The message returned when this resource is responsible for allocation. - -Blocker - When specified, no futher resources are processed after this one. - -Stored - Enable offline backups for this resource - -Weight - Order the *Resources* matching the event. Higher value - higher priority. - -ThresholdIDs - List of ThresholdProfiles targetted by the *Resource*. If empty, the match will be done in :ref:`ThresholdS` component. - - -ResourceUsage -^^^^^^^^^^^^^ - -A **ResourceUsage** represents a counted allocation within a *Resource*. The following parameters are present within: - -Tenant - The tenant on the platform (one can see the tenant as partition ID). - -ID - Identifier for the *ResourceUsage*. - -ExpiryTime - Exact time when this allocation expires. - -Units - Number of units allocated by this *ResourceUsage*. - - -Processing logic ----------------- - -When a new *Event* is received, **ResourceS** will pass it to :ref:`FilterS` in order to find all *Resource* objects matching the *Event*. - -As a result of the selection process we will further get an ordered list of *Resource* which are matching the *Event* and are active at the request time. - -Depending of the *RPC API* used, we will have the following behavior further: - -ResourcesForEvent - Will simply return the list of *Resources* matching so far. - -AuthorizeResources - Out of *Resources* matching, ordered based on *Weight*, it will use the first one with available units to authorize the request. Returns *RESOURCE_UNAVAILABLE* error back in case of no available units found. No actual allocation is performed. - -AllocateResource - All of the *Resources* matching the event will be operated and requested units will be deducted, independent of being available or going on negative. The first one with value higher or equal to zero will be responsible of allocation and it's message will be returned as allocation message. If no allocation message is defined for the allocated resource, it's ID will be returned instead. - - If no resources are allocated *RESOURCE_UNAVAILABLE* will be returned as error. - -ReleaseResource - Will release all the previously allocated resources for an *UsageID*. If *UsageID* is not found (which can be the case of restart), will perform a standard search via *FilterS* and try to dealocate the resources matching there. - -Depending on configuration each *Resource* can be backed up regularly and asynchronously to DataDB so it can survive process restarts. - -After each resource modification (allocation or release) the :ref:`ThresholdS` will be notified with the *Resource* itself where mechanisms like notifications or fraud-detection can be triggered. - - -Use cases ---------- - -* Monitor resources for a group of accounts(ie. based on a special field in the events). -* Limit the number of CPS for a destination/supplier/account (done via UsageTTL of 1s). -* Limit resources for a destination/supplier/account/time of day/etc. \ No newline at end of file diff --git a/docs/routes.rst b/docs/routes.rst deleted file mode 100644 index 38c245d5d..000000000 --- a/docs/routes.rst +++ /dev/null @@ -1,190 +0,0 @@ -.. _Asterisk: https://www.asterisk.org/ -.. _FreeSWITCH: https://freeswitch.com/ -.. _Kamailio: https://www.kamailio.org/w/ -.. _OpenSIPS: https://opensips.org/ - - -.. _Routes: - -RouteS -========= - - -**RouteS** is a standalone subsystem within **CGRateS** responsible to compute a list of routes which can be used for a specific event received to process. It is accessed via `CGRateS RPC APIs `_. - -As most of the other subsystems, it is performance oriented, stored inside *DataDB* but cached inside the *cgr-engine* process. -Caching can be done dynamically/on-demand or at start-time/precached and it is configurable within *cache* section in the :ref:`JSON configuration `. - - -Processing logic ----------------- - -When a new *Event* is received, **RouteS** will pass it to :ref:`FilterS` in order to find all :ref:`SupplierProfiles` matching the *Event*. - -As a result of the selection process we will get a single :ref:`SupplierProfile` matching the *Event*, is active at the *EventTime* and has a higher priority than the other matching :ref:`SupplierProfiles`. - -Depending on the *Strategy* defined in the *SupplierProfile*, further steps will be taken (ie: query cost, stats, ordering) for each of the individual *SupplierIDs* defined within the *SupplierProfile*. - - -APIs logic ----------- - -GetSupplierProfilesForEvent -^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Given the *Event* it will return a list of ordered *SupplierProfiles* matching at the *EventTime*. - -This API is useful to test configurations. - - -GetRoutes -^^^^^^^^^^^^ - -Will return a list of *Routes* from within a *SupplierProfile* ordered based on *Strategy*. - - -Parameters ----------- - - -RouteS -^^^^^^^^^ - -**RouteS** is the **CGRateS** component responsible for handling the *SupplierProfiles*. - -It is configured within **routes** section from :ref:`JSON configuration ` via the following parameters: - -enabled - Will enable starting of the service. Possible values: . - -indexed_selects - Enable profile matching exclusively on indexes. If not enabled, the *SupplierProfiles* are checked one by one which for a larger number can slow down the processing time. Possible values: . - -string_indexed_fields - Query string indexes based only on these fields for faster processing. If commented out, each field from the event will be checked against indexes. If defined as empty list, no fields will be checked. - -prefix_indexed_fields - Query prefix indexes based only on these fields for faster processing. If defined as empty list, no fields will be checked. - -nested_fields - Applied when all event fields are checked against indexes, and decides whether subfields are also checked. - -attributes_conns - Connections to AttributeS for altering events before supplier queries. If undefined, fields modifications are disabled. - -resources_conns - Connections to ResourceS for \*res sorting, empty to disable functionality. - -stats_conns - Connections to StatS for \*stats sorting, empty to disable stats functionality. - -default_ratio - Default ratio used in case of \*load strategy - - -.. _SupplierProfile: - -SupplierProfile -^^^^^^^^^^^^^^^ - -Contains the configuration for a set of routes which will be returned in case of match. Following fields can be defined: - -Tenant - The tenant on the platform (one can see the tenant as partition ID). - -ID - The profile identificator. - -FilterIDs - List of *FilterProfileIDs* which should match in order to consider the profile matching the event. - -ActivationInterval - The time interval when this profile becomes active. If undefined, the profile is always active. Other options are start time, end time or both. - -Sorting - Sorting strategy applied when ordering the individual *Routes* defined bellow. Possible values are: - - **\*weight** - Classic method of statically sorting the routes based on their priority. - - **\*lc** - LeastCost will sort the routes based on their cost (lowest cost will have higher priority). If two routes will be identical as cost, their *Weight* will influence the sorting further. If *AccountIDs* will be specified, bundles can be also used during cost calculation, the only condition is that the bundles should cover complete usage. - - The following fields are mandatory for cost calculation: *Account*/*Subject*, *Destination*, *SetupTime*. *Usage* is optional and if present in event, it will be used for the cost calculation. - - **\*hc** - HighestCost will sort the routes based on their cost(higher cost will have higher priority). If two routes will be identical as cost, their *Weight* will influence the sorting further. - - The following fields are mandatory for cost calculation: *Account*/*Subject*, *Destination*, *SetupTime*. *Usage* is optional and if present in event, it will be used for the cost calculation. - - **\*qos** - QualityOfService strategy will sort the routes based on their stats. It takes the StatIDs to check from the supplier *StatIDs* definition. The metrics used as part of sorting are to be defined in *SortingParameters* field bellow. If Stats are missing the metrics defined in *SortingParameters* defaults for those will be populated for order (10000000 as PDD and -1 for the rest). - - **\*reas** - ResourceAscendentSorter will sort the routes based on their resource usage, lowest usage giving higher priority. The resources will be queried for each supplier based on it's *ResourceIDs* field and the final usage for each supplier will be given by the sum of all the resource usages queried. - - **\*reds** - ResourceDescendentSorter will sort the routes based on their resource usage, highest usage giving higher priority. The resources will be queried for each supplier based on it's *ResourceIDs* field and the final usage for each supplier will be given by the sum of all the resource usages queried. - - **\*load** - LoadDistribution will sort the routes based on their load. An important parameter is the *\*ratio* which is defined as *supplierID:Ratio* within the SortingParameters. If no supplierID is present within SortingParameters, the system will look for *\*default* or fallback in the configuration to *default_ratio* within :ref:`JSON configuration `. The *\*ratio* will specify the probability to get traffic on a *Supplier*, the higher the *\*ratio* more chances will a *Supplier* get for traffic. - - The load will be calculated out of the *StatIDs* parameter of each *Supplier*. It is possible to also specify there directly the metric being used in the format *StatID:MetricID*. If only *StatID* is instead specified, all metrics will be summed to get the final value. - - -SortingParameters - Will define additional parameters for each strategy. Following extra parameters are available(based on strategy): - - **\*qos** - List of metrics to be used for sorting in order of importance. - -Weight - Priority in case of multiple *SupplierProfiles* matching an *Event*. Higher *Weight* will have more priority. - -Routes - List of :ref:`Supplier` objects which are part of this *SupplierProfile* - - -.. _Supplier: - -Supplier -^^^^^^^^ - -The *Supplier* represents one supplier within the *SupplierProfile*. Following parameters are defined for it: - -ID - Supplier ID, will be returned via APIs. Should be known on the remote side and match some business logic (ie: gateway id or directly an IP address). - -FilterIDs - List of *FilterProfileIDs* which should match in order to consider the *Supplier* in use/active. - -AccountIDs - List of account IDs which should be checked in case of some strategies (ie: \*lc, \*hc). - -RatingPlanIDs - List of RatingPlanIDs which should be checked in case of some strategies (ie: \*lc, \*hc). - -ResourceIDs - List of ResourceIDs which should be checked in case of some strategies (ie: \*reas or \*reds). - -StatIDs - List of StatIDs which should be checked in case of some strategies (ie: \*qos or \*load). Can also be defined as *StatID:MetricID*. - -Weight - Used for sorting in some strategies (ie: \*weight, \*lc or \*hc). - -Blocker - No more routes are provided after this one. - -SupplierParameters - Container which is trasparently passed to the remote client to be used in it's own logic (ie: gateway prefix stripping or other gateway parameters). - - - -Use cases ---------- - -* Calculate LCR directly by querying APIs (GetRoutes). -* LCR system together with Kamailio_ *dispatcher* module where the *SupplierID* whithin *CGRateS* will be used as dispatcher set within Kamailio_. -* LCR system together with OpenSIPS_ drouting module where the *SupplierID* whithin *CGRateS* will be used as drouting carrier id. -* LCR system together with FreeSWITCH_ or Asterisk_ where the *SupplierID* whithin *CGRateS* will be used as gateway ID within the dialplan of FreesWITCH_ or Asterisk_. \ No newline at end of file diff --git a/docs/rpcconns.rst b/docs/rpcconns.rst deleted file mode 100644 index 7926d4746..000000000 --- a/docs/rpcconns.rst +++ /dev/null @@ -1,161 +0,0 @@ -.. _rpc_conns: - -RPCConns -======== - -**RPCConns** defines connection pools used by CGRateS components for inter-service communication. These pools enable services to interact both within a single CGRateS instance or across multiple instances. - - -Configuration Structure ------------------------ - -Example configuration in the JSON file: - -.. code-block:: json - - { - "rpc_conns": { - "conn1": { - "strategy": "*first", - "pool_size": 0, - "conns": [{ - "address": "192.168.122.210:2012", - "transport": "*json", - "connect_attempts": 5, - "reconnects": -1, - "connect_timeout": "1s", - "reply_timeout": "2s" - }] - } - } - } - - -Predefined Connection Pools ---------------------------- - -\*internal - Direct in-process communication - -\*birpc_internal - Bidirectional in-process communication - -\*localhost - JSON-RPC connection to local cgr-engine on port 2012 - -\*bijson_localhost - Bidirectional JSON-RPC connection to local cgr-engine on port 2014 - -Bidirectional Communication with SessionS ------------------------------------------ - -Bidirectional connections are specifically designed and used for communication between agents and the :ref:`SessionS ` component. While agents can send requests using standard connections, bidirectional connections are necessary when SessionS needs to communicate back to the agents. - -When using bidirectional connections, SessionS maintains references to all connected agents, allowing it to send requests back to specific agents when needed (for example, to force disconnect a session or query active sessions). - -.. note:: - Bidirectional connections (``*birpc_internal``, ``*birpc_json``, ``*birpc_gob``) are exclusively used for Agent-SessionS communication. All other service interactions use standard one-way connections. - - -Parameters ----------- - - -Pool Parameters -^^^^^^^^^^^^^^^ - -Strategy - Controls connection selection within the pool. Possible values: - - * ``*first``: Uses first available connection, fails over on network/timeout/missing service errors - * ``*next``: Round-robin between connections with same failover as ``*first`` - * ``*random``: Random connection selection with same failover as ``*first`` - * ``*first_positive``: Tries connections in order until getting any successful response - * ``*first_positive_async``: Async version of ``*first_positive`` - * ``*broadcast``: Sends to all connections, returns first successful response - * ``*broadcast_sync``: Sends to all, waits for completion, logs errors that wouldn't trigger failover in ``*first`` - * ``*broadcast_async``: Sends to all without waiting for responses - * ``*parallel``: Pool that creates and reuses connections up to a limit - -.. note:: - Connections attempt failover to the next available connection in the pool on connection errors, timeouts, or service errors. Service errors (usually referring to "can't find service" errors) occur when attempting to reach services that are either temporarily unavailable during engine initialization or disabled in that particular instance. - -PoolSize - Sets the connection limit for ``*parallel`` strategy (0 means unlimited) - - -Connection Parameters -^^^^^^^^^^^^^^^^^^^^^ - -Address - Network address, ``*internal``, or ``*birpc_internal`` - -Transport - Protocol (``*json``, ``*gob``, ``*birpc_json``, ``*birpc_gob``, ``*http_jsonrpc``). When using ``*internal`` or ``*birpc_internal`` addresses, defaults to the address value. Otherwise defaults to ``*gob``. - -ConnectAttempts - Number of initial connection attempts - -Reconnects - Max number of reconnection attempts (-1 for infinite) - -MaxReconnectInterval - Maximum delay between reconnects - -ConnectTimeout - Connection timeout (e.g., "1s") - -ReplyTimeout - Response timeout (e.g., "2s") - -TLS - Enable TLS encryption - -ClientKey - Path to TLS client key file - -ClientCertificate - Path to TLS client certificate - -CaCertificate - Path to CA certificate - - -Transport Performance ---------------------- - -\*internal, \*birpc_internal - In-process communication (by far the fastest) - -\*gob, \*birpc_gob - Binary protocol that provides better performance at the cost of being harder to troubleshoot - -\*json, \*birpc_json - Standard JSON protocol - slower but easier to debug since you can read the traffic - -\*http_jsonrpc - HTTP-based JSON-RPC protocol - slower than direct JSON-RPC due to HTTP overhead, but can integrate with web infrastructure and provides easy debugging through standard HTTP tools - -.. note:: - While the "transport" parameter name is used in the configuration, it actually specifies the codec (*json, *gob) used for data encoding. All network connections use TCP, while internal ones skip networking completely. - -Using Connection Pools ----------------------- - -Components reference connection pools through "_conns" configuration fields: - -.. code-block:: json - - { - "cdrs": { - "enabled": true, - "rals_conns": ["*internal"], - "ees_conns": ["conn1"] - } - } - -This configuration approach allows: - -* Deploying services across single or multiple instances -* Selecting transports based on performance requirements -* Automatic failover between connections diff --git a/docs/sessions.rst b/docs/sessions.rst deleted file mode 100644 index b3ccb8709..000000000 --- a/docs/sessions.rst +++ /dev/null @@ -1,390 +0,0 @@ -.. _SessionS: - -SessionS -======== - - -**SessionS** is a standalone subsystem within **CGRateS** responsible to manage virtual sessions based on events received. It is accessed via `CGRateS RPC APIs `_. - - -Parameters ----------- - -SessionS -^^^^^^^^ - -Configured within **sessions** section within :ref:`JSON configuration ` via the following parameters: - -enabled - Will enable starting of the service. Possible values: . - -listen_bijson - Address where the *SessionS* listens for bidirectional JSON requests. - -chargers_conns - Connections towards :ref:`ChargerS` component to query charges for events. - -rals_conns - Connections towards :ref:`RALs` component to implement auth and balance reservation for events. - -cdrs_conns - Connections towards :ref:`CDRs` component where CDRs and session costs will be sent. - -resources_conns - Connections towards :ref:`ResourceS` component for resources management. - -thresholds_conns - Connections towards :ref:`ThresholdS` component to monitor and react to information within events. - -stats_conns - Connections towards :ref:`StatS` component to compute stat metrics for events. - -routes_conns - Connections towards :ref:`RouteS` component to compute routes for events. - -attributes_conns - Connections towards :ref:`AttributeS` component for altering the events. - -replication_conns - Connections towards other :ref:`SessionS` components, used in case of session high-availability. - -debit_interval - Default debit interval in case of *\*prepaid* requests. Zero will disable automatic debits in favour of manual ones. - -store_session_costs - Used in case of decoupling events charging from CDR processing. The session costs debitted by *SessionS* will be stored into *StorDB.sessions_costs* table and merged into the CDR later when received. - -default_usage - Imposes the default usage for each tipe of call. - -session_ttl - Enables automatic detection/removal of stale sessions. Zero will disable the functionality. - -session_ttl_max_delay - Used in tandem with *session_ttl* to randomize disconnects in order to avoid system peaks. - -session_ttl_last_used - Used in tandem with *session_ttl* to emulate the last used information for missing terminate event. - -session_ttl_usage - Used in tandem with *session_ttl* to emulate the total usage information for the incomplete session. - -session_indexes - List of fields to index out of events. Used to speed up response time for session queries. - -client_protocol - Protocol version used when acting as a JSON-RPC client (ie: force disconnecting the sessions). - -channel_sync_interval - Sync channels at regular intervals to detect stale sessions. Zero will disable this functionality. - -terminate_attempts - Limit the number of attempts to terminate a session in case of errors. - -alterable_fields - List of fields which are allowed to be changed by update/terminate events. - - -Processing logic ----------------- - -Depends on the implementation of particular *RPC API* used. - - -GetActiveSessions, GetActiveSessionsCount, GetPassiveSessions, GetPassiveSessionsCount -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Returns the list of sessions based on the received filters. - - -SetPassiveSession -^^^^^^^^^^^^^^^^^ - -Used by *CGRateS* in High-Availability setups to replicate sessions between different *SessionS* nodes. - - -ReplicateSessions -^^^^^^^^^^^^^^^^^ - -Starts manually a replication process. Useful in cases when a node comes back online or entering maintenance mode. - - -AuthorizeEvent, AuthorizeEventWithDigest -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - - -Used for event authorization. It's behaviour can be controlled via a number of different parameters: - - -GetAttributes - Activates altering of the event by :ref:`AttributeS`. - -AttributeIDs - Selects only specific attribute profiles (instead of discovering them via :ref:`FilterS`). Faster in processing than the discovery mechanism. - -AuthorizeResources - Activates event authorization via :ref:`ResourceS`. Returns *RESOURCE_UNAVAILABLE* if no resources left for the event. - -GetMaxUsage - Queries :ref:`RALs` for event's maximum usage allowed. - -ProcessThresholds - Sends the event to :ref:`ThresholdS` to be used in monitoring. - -ThresholdIDs - Selects only specific threshold profiles (instead of discovering them via :ref:`FilterS`). Faster in processing than the discovery mechanism. - -ProcessStats - Sends the event to :ref:`StatS` for computing stat metrics. - -StatIDs - Selects only specific stat profiles (instead of discovering them via :ref:`FilterS`). Faster in processing than the discovery mechanism. - -GetRoutes - Sends the event to :ref:`RouteS` to return the list of routes for it as part as authorization. - -RoutesMaxCost - Mechanism to implement revenue assurance for routes coming from :ref:`RouteS` component. Can be defined as a number or special meta variable: *\*event_cost*, assuring that the route cost will never be higher than event cost. - -RoutesIgnoreErrors - Instructs to ignore routes with errors(ie: without price for specific destination in tariff plan). Without this setting the whole query will fail instead of just the route being ignored. - - -InitiateSession, InitiateSessionWithDigest -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Used in case of session initiation. It's behaviour can be influenced by following arguments: - - -GetAttributes - Activates altering of the event by :ref:`AttributeS`. - -AttributeIDs - Selects only specific attribute profiles (instead of discovering them via :ref:`FilterS`). Faster in processing than the discovery mechanism. - -AllocateResources - Process the event with :ref:`ResourceS`, allocating the matching requests. Returns *RESOURCE_UNAVAILABLE* if no resources left for the event. - -InitSession - Initiates the session executing following steps: - - * Fork session based on matched :ref:`ChargerS` profiles. - - * Start debit loops for *\*prepaid* requests if *DebitInterval* is higher than 0. - - * Index the session internally and start internal timers for detecting stale sessions. - -ProcessThresholds - Sends the event to :ref:`ThresholdS` to be used in monitoring. - -ThresholdIDs - Selects only specific threshold profiles (instead of discovering them via :ref:`FilterS`). Faster in processing than the discovery mechanism. - -ProcessStats - Sends the event to :ref:`StatS` for computing stat metrics. - -StatIDs - Selects only specific stat profiles (instead of discovering them via :ref:`FilterS`). Faster in processing than the discovery mechanism. - - -UpdateSession -^^^^^^^^^^^^^ - -Used to update an existing session or initiating a new one if none found. It's behaviour can be influenced by the following arguments: - -GetAttributes - Use :ref:`AttributeS` to alter the event. - -AttributeIDs - Selects only specific attribute profiles (instead of discovering them via :ref:`FilterS`). Faster in processing than the discovery mechanism. - -UpdateSession - Involves charging mechanism into processing. Following steps are further executed: - - * Relocate session if *InitialOriginID* field is present in the event. - - * Initiate session if the *CGRID* is not found within the active sessions. - - * Update timers for session stale detection mechanism. - - * Debit the session usage for all the derived *\*prepaid* sessions. - - -TerminateSession -^^^^^^^^^^^^^^^^ - -Used to terminate an existing session or to initiate+terminate a new one. It's behaviour can be influenced by the following arguments: - -TerminateSession - Stop the charging process. Involves the following steps: - - * Relocate session if *InitialOriginID* field is present in the event. - - * Initiate session if the *CGRID* is not found within the active sessions. - - * Unindex the session so it does not longer show up in active sessions queries. - - * Stop the timer for session stale detection mechanism. - - * Stop the debit loops if exist. - - * Balance the charges (refund or debit more). - - * Store the session costs if configured. - - * Cache the session for later CDRs if configured. - -ReleaseResources - Will release the aquired resources within :ref:`ResourceS`. - -ProcessThresholds - Send the event to :ref:`ThresholdS` for monitoring. - -ThresholdIDs - Selects only specific threshold profiles (instead of discovering them via :ref:`FilterS`). Faster in processing than the discovery mechanism. - -ProcessStats - Send the event to :ref:`StatS` for building the stat metrics. - -StatIDs - Selects only specific stat profiles (instead of discovering them via :ref:`FilterS`). Faster in processing than the discovery mechanism. - - - -ProcessMessage -^^^^^^^^^^^^^^ - -Optimized for event charging, without creating sessions based on it. Influenced by the following arguments: - -GetAttributes - Alter the event via :ref:`AttributeS`. - -AttributeIDs - Selects only specific attribute profiles (instead of discovering them via :ref:`FilterS`). Faster in processing than the discovery mechanism. - -AllocateResources - Alter the event via :ref:`ResourceS` for resource allocation. - -Debit - Debit the event via :ref:`RALs`. Uses :ref:`ChargerS` to fork the event if needed. - -ProcessThresholds - Send the event to :ref:`ThresholdS` for monitoring. - -ThresholdIDs - Selects only specific threshold profiles (instead of discovering them via :ref:`FilterS`). Faster in processing than the discovery mechanism. - -ProcessStats - Send the event to :ref:`StatS` for building the stat metrics. - -StatIDs - Selects only specific stat profiles (instead of discovering them via :ref:`FilterS`). Faster in processing than the discovery mechanism. - -GetRoutes - Sends the event to :ref:`RouteS` to return the list of routes for it. - -RoutesMaxCost - Mechanism to implement revenue assurance for routes coming from :ref:`RouteS` component. Can be a number or special meta variable: *\*event_cost*, assuring that the route cost will never be higher than event cost. - -RoutesIgnoreErrors - Instructs to ignore routes with errors(ie: without price for specific destination in tariff plan). Without this setting the whole query will fail instead of just the route being ignored. - - - -ProcessCDR -^^^^^^^^^^ - -Build the CDR out of the event and send it to :ref:`CDRs`. It has the ability to use cached sessions for obtaining additional information like fields with values or derived charges, forking also the CDR based on that. - - -ProcessEvent -^^^^^^^^^^^^ - -Will generically process an event, having the ability to merge all the functionality of previous processing APIs. - -Instead of arguments, the options for enabling various functionaity will come in the form of *Flags*. These will be of two types: **main** and **auxiliary**, the last ones being considered suboptions of the first. The available flags are: - - -\*attributes - Activates altering of the event via :ref:`AttributeS`. - -\*cost - Queries :ref:`RALs` for event cost. - -\*resources - Process the event with :ref:`ResourceS`. Additional auxiliary flags can be specified here: - - **\*authorize** - Authorize the event. - - **\*allocate** - Allocate resources for the event. - - **\*release** - Release the resources used for the event. - -\*rals - Process the event with :ref:`RALs`. Auxiliary flags available: - - **\*authorize** - Authorize the event. - - **\*initiate** - Initialize a session out of event. - - **\*update** - Update a sesssion (or initialize + update) out of event. - - **\*terminate** - Terminate a session (or initialize + terminate) out of event. - -\*routes - Process the event with :ref:`Routes`. Auxiliary flags available: - - **\*ignore_errors** - Ignore the routes with errors instead of failing the request completely. - - **\*event_cost** - Ignore routes with cost higher than the event cost. - -\*thresholds - Process the event with :ref:`ThresholdS` for monitoring. - -\*stats - Process the event with :ref:`StatS` for metrics calculation. - -\*cdrs - Create a CDR out of the event with :ref:`CDRs`. - - -GetCost -^^^^^^^ - -Queries the cost for event from :ref:`RALs`. Additional processing options can be selected via the *Flags* argument. Possible flags: - -\*attributes - Use :ref:`AttributeS` to alter the event before cost being calculated. - - -SyncSessions -^^^^^^^^^^^^ - -Manually initiate a sync sessions mechanism. All the connections will be synced and stale sessions will be automatically disconnected. - - -ForceDisconnect -^^^^^^^^^^^^^^^ - -Disconnect the session matching the filter. - - -ActivateSessions -^^^^^^^^^^^^^^^^ - -Manually activate a session which is marked as passive. - - -DeactivateSessions -^^^^^^^^^^^^^^^^^^ - -Manually deactivate a session which is marked as active. \ No newline at end of file diff --git a/docs/stats.rst b/docs/stats.rst deleted file mode 100644 index 3e06dcb4b..000000000 --- a/docs/stats.rst +++ /dev/null @@ -1,146 +0,0 @@ -.. _stats: - -StatS -===== - - -**StatS** is a standalone subsystem part of the **CGRateS** infrastructure, designed to aggregate and calculate statistical metrics for the generic *Events* (hashmaps) it receives. - -Both receiving of *Events* as well as *Metrics* displaying is performed via a complete set of `CGRateS RPC APIs `_. - -Due it's real-time nature, **StatS** are designed towards high throughput being able to process thousands of *Events* per second. This is doable since each *StatQueue* is a very light object, held in memory and eventually backed up in *DataDB*. - - -Processing logic ----------------- - -When a new *Event* is received, **StatS** will pass it to :ref:`FilterS` in order to find all *StatProfiles* matching the *Event*. - -As a result of the selection process we will further get an ordered list of *StatProfiles* which are matching the *Event* and are active at the request time. - -For each of these profiles we will further calculate the metrics it has configured for the *Event* received. If *ThresholdIDs* are not *\*none*, we will include the *Metrics* into special *StatUpdate* events, defined internally, and pass them further to the [ThresholdS](ThresholdS) for processing. - -Depending on configuration each *StatQueue* can be backed up regularly and asynchronously to DataDB so it can survive process restarts. - - -Parameters ----------- - - -StatS -^^^^^ - -**StatS** is the **CGRateS** component responsible of handling the *StatQueues*. - -It is configured within **stats** section from :ref:`JSON configuration ` via the following parameters: - -enabled - Will enable starting of the service. Possible values: . - -store_interval - Time interval for backing up the stats into *DataDB*. - -store_uncompressed_limit - After this limit is hit the events within *StatQueue* will be stored aggregated. - -thresholds_conns - Connections IDs towards *ThresholdS* component. If not defined, there will be no notifications sent to *ThresholdS* on *StatQueue* changes. - -indexed_selects - Enable profile matching exclusively on indexes. If not enabled, the *StatQueues* are checked one by one which for a larger number can slow down the processing time. Possible values: . - -string_indexed_fields - Query string indexes based only on these fields for faster processing. If commented out, each field from the event will be checked against indexes. If uncommented and defined as empty list, no fields will be checked. - -prefix_indexed_fields - Query prefix indexes based only on these fields for faster processing. If defined as empty list, no fields will be checked. - -nested_fields - Applied when all event fields are checked against indexes, and decides whether subfields are also checked. - - -StatQueueProfile -^^^^^^^^^^^^^^^^ - -Ís made of the following fields: - -Tenant - The tenant on the platform (one can see the tenant as partition ID). - -ID - Identifier for the *StatQueueProfile*, unique within a *Tenant*. - -FilterIDs - List of *FilterProfileIDs* which should match in order to consider the profile matching the event. - -ActivationInterval - The time interval when this profile becomes active. If undefined, the profile is always active. Other options are start time, end time or both. - -QueueLength - Maximum number of items stored in the queue. Once the *QueueLength* is reached, new items entering will cause oldest one to be dropped (FIFO mode). - -TTL - Time duration causing items in the queue to expire and be removed automatically from the queue. - -Metrics - List of statistical metrics to build for items within this *StatQueue*. See [bellow](#statqueue-metrics) for possible values here. - -ThresholdIDs - List of threshold IDs to check on when new items are updating the queue metrics. - -Blocker - Do not process further *StatQueues*. - -Stored - Enable offline backups for this *StatQueue* - -Weight - Order the *StatQueues* matching the event. Higher value - higher priority. - -MinItems - Display metrics only if the number of items in the queue is higher than this. - - -StatQueue Metrics -^^^^^^^^^^^^^^^^^ - -Following metrics are implemented: - -\*asr - `Answer-seizure ratio `_. Relies on *AnswerTime* field in the *Event*. -\*acd - `Average call duration `_. Uses *AnswerTime* and *Usage* fields in the *Event*. -\*tcd - Total call duration. Uses *Usage* out of *Event*. - -\*acc - Average call cost. Uses *Cost* field out of *Event*. - -\*tcc - Total call cost. Uses *Cost* field out of *Event*. - -\*pdd - `Post dial delay `. Uses *PDD* field in the event. - -\*ddc - Distinct destination count will keep the number of unique destinations found in *Events*. Relies on *Destination* field in the *Event*. - -\*sum - Generic metric to calculate mathematical sum for a specific field in the *Events*. Format: <*\*sum#FieldName*>. - -\*average - Generic metric to calculate the mathematical average of a specific field in the *Events*. Format: <*\*average#FieldName*>. - -\*distinct - Generic metric to return the distinct number of appearance of a field name within *Events*. Format: <*\*distinct#FieldName*>. - - -Use cases ---------- - -* Aggregate various traffic metrics for traffic transparency. -* Revenue assurance applications. -* Fraud detection by aggregating specific billing metrics during sensitive time intervals (\*acc, \*tcc, \*tcd). -* Building call patterns. -* Building statistical information to train systems capable of artificial intelligence. -* Building quality metrics used in traffic routing. diff --git a/docs/stordb.rst b/docs/stordb.rst deleted file mode 100644 index b6dc86ce4..000000000 --- a/docs/stordb.rst +++ /dev/null @@ -1,7 +0,0 @@ -.. _stordb: - -StorDB -====== - - -TBD \ No newline at end of file diff --git a/docs/tariffplans.rst b/docs/tariffplans.rst deleted file mode 100644 index 6a4970002..000000000 --- a/docs/tariffplans.rst +++ /dev/null @@ -1,19 +0,0 @@ -.. _tariffplan: - -TariffPlans -=========== - -Major concept within CGRateS architecture, implement mechanisms to load rating as well as account data into CGRateS. - -Currently TariffPlans can be loaded using 2 different approaches: - -Direct load out of TP-CSV files -------------------------------- - -This represents the fastest and easiest way to manage small set of TP definitions. It has the advantage of being simple to define and load but on the other hand as soon as the data set grows it becomes relatively hard to be maintaned. - -Due to complex data definition we have split information necessary on each load process in more .csv files, identified by names close to their utility. - -Each individual CSV file can have any number of rows starting with comment character (#) which will be ignored on processing. - -Examples of TariffPlans as CSVs can be found on the `GitHub repository `_ . \ No newline at end of file diff --git a/docs/thresholds.rst b/docs/thresholds.rst deleted file mode 100644 index 9add710e5..000000000 --- a/docs/thresholds.rst +++ /dev/null @@ -1,150 +0,0 @@ -.. _ThresholdS: - -ThresholdS -========== - - -**ThresholdS** is a standalone subsystem within **CGRateS** responsible to execute a list of *Actions* for a specific event received to process. It is accessed via `CGRateS RPC APIs `_. - -As most of the other subsystems, it is performance oriented, stored inside *DataDB* but cached inside the *cgr-engine* process. -Caching can be done dynamically/on-demand or at start-time/precached and it is configurable within *cache* section in the :ref:`JSON configuration `. - - -Processing logic ----------------- - -When a new *Event* is received, **ThresholdS** will pass it to :ref:`FilterS` in order to find all *SupplierProfiles* matching the *Event*. - -As a result of the selection process we will get a list of :ref:`Thresholds` matching the *Event* and are active at the *EventTime*. - - - -APIs logic ----------- - - -GetThresholdIDs -^^^^^^^^^^^^^^^ - -Returns a list of *ThresholdIDs* configured on a *Tenant*. - - -GetThresholdsForEvent -^^^^^^^^^^^^^^^^^^^^^ - -Returns a list of :ref:`Thresholds` matching the event. - - -GetThreshold -^^^^^^^^^^^^ - -Returns a specific :ref:`Threshold` based on it's *Tenant* and *ID*. - - -ProcessEvent -^^^^^^^^^^^^ - -Technically processes the *Event*, executing all the *Actions* configured within all the matching :ref:`Thresholds`. - - -Parameters ----------- - - -ThresholdS -^^^^^^^^^^ - -**ThresholdS** is the **CGRateS** component responsible for handling the :ref:`Thresholds`. - -It is configured within **thresholds** section from :ref:`JSON configuration ` via the following parameters: - -enabled - Will enable starting of the service. Possible values: . - -store_interval - Time interval for backing up the thresholds into *DataDB*. - -indexed_selects - Enable profile matching exclusively on indexes. If not enabled, the :ref:`Thresholds` are checked one by one which for a larger number can slow down the processing time. Possible values: . - -string_indexed_fields - Query string indexes based only on these fields for faster processing. If commented out, each field from the event will be checked against indexes. If defined as empty list, no fields will be checked. - -prefix_indexed_fields - Query prefix indexes based only on these fields for faster processing. If defined as empty list, no fields will be checked. - -nested_fields - Applied when all event fields are checked against indexes, and decides whether subfields are also checked. - - -.. _ThresholdProfile: - -ThresholdProfile -^^^^^^^^^^^^^^^^ - -Contains the configuration to create a :ref:`Threshold`. Following fields can be defined: - -Tenant - The tenant on the platform (one can see the tenant as partition ID). - -ID - The profile identificator. - -FilterIDs - List of *FilterProfileIDs* which should match in order to consider the profile matching the event. - -ActivationInterval - The time interval when this profile becomes active. If undefined, the profile is always active. Other options are start time, end time or both. - -MaxHits - Limit number of hits for this threshold. Once this is reached, the threshold is considered disabled. - -MinHits - Only execute actions after this number is reached. - -MinSleep - Disable the threshold for consecutive hits for the duration of *MinSleep*. - -Blocker - Do not process thresholds who's *Weight* is lower. - -Weight - Sorts the execution of multiple thresholds matching the event. The higher the *Weight* is, the higher the priority to be executed. - -ActionIDs - List of *Actions* to execute for this threshold. - -Async - If true, do not wait for actions to complete. - - -.. _Threshold: - -Threshold -^^^^^^^^^ - -Represents one threshold, instantiated from a :ref:`ThresholdProfile`. It contains the following fields: - - -Tenant - The tenant on the platform (one can see the tenant as partition ID). - -ID - The threshold identificator. - -Hits - Number of hits so far. - -Snooze - If initialized, it will contain the time when this threshold will become active again. - - - -Use cases ---------- - -* Improve network transparency and automatic reaction to outages monitoring stats produced by :ref:`StatS`. -* Monitor active channels used by a supplier/customer/reseller/destination/weekends/etc out of :ref:`ResourceS` events. -* Monitor balance consumption out of *Account* events. -* Monitor calls out of :ref:`CDRs` events or :ref:`SessionS`. -* Fraud detection with automatic mitigation based of all events mentioned above. \ No newline at end of file diff --git a/docs/trends.rst b/docs/trends.rst deleted file mode 100644 index 1fb19e5c7..000000000 --- a/docs/trends.rst +++ /dev/null @@ -1,168 +0,0 @@ -.. _trends: - -TrendS -===== - - -**TrendS** is a standalone subsystem part of the **CGRateS** infrastructure, designed to work as an extension of the :ref:`StatS`, by regularly querying it, storing it's values in a time-series-like database and calculate trend percentages based on their evolution. - -Complete interaction with **TrendS** is possible via `CGRateS RPC APIs `_. - -Due it's real-time nature, **TrendS** are designed towards high throughput being able to process thousands of queries per second. This is doable since each *Trend* is a very light object, held in memory and eventually backed up in :ref:`DataDB`. - - -Processing logic ----------------- - -In order for **TrendS** to start querying the :ref:`StatS`, it will need to be *scheduled* to do that. Scheduling is being done using `Cron Expressions `_. - -Once *Cron Expressions* are defined within a *TrendProfile*, internal **Cron Scheduler** needs to be triggered. This can happen in two different ways: - -Automatic Query Scheduling - The profiles needing querying will be inserted into **trends** :ref:`JSON configuration `. By leaving any part of *trend_id* or *tenat* empty, it will be interpreted as catch-all filter. - -API Scheduling - The profiles needing querying will be sent inside arguments to the `TrendSv1.ScheduleQueries API call `_. - - -Offline storage ---------------- - -Offline storage is optionally possible, by enabling profile *Stored* flag and configuring the *store_interval* inside :ref:`JSON configuration `. - - -Trend querying --------------- - -In order to query a **Trend** (ie: to be displayed in a web interface), one should use the `TrendSv1.GetTrend API call `_ which also offers pagination parameters. - - -Trend exporting ---------------- - -On each **Trend** change, it will be possible to send a specially crafted event, *TrendUpdate* to one of the following subsystems: - -**ThresholdS** - Sending the **TrendUpdate** Event gives the administrator the possiblity to react to *Trend* changes, including escalation strategies offered by the **TresholdS** paramters. - Fine tuning parameters (ie. selecting only specific ThresholdProfiles to increase speed1) are available directly within the **TrendProfile**. - -**EEs** - **EEs** makes it possible to export the **TrendUpdate** to all the availabe outside interfaces of **CGRateS**. - -Both exporting options are enabled within :ref:`JSON configuration `. - - -Parameters ----------- - - -TrendS -^^^^^^ - -**TrendS** is the **CGRateS** component responsible of generating the **Trend** queries. - -It is configured within **trends** section from :ref:`JSON configuration ` via the following parameters: - -enabled - Will enable starting of the service. Possible values: . - -store_interval - Time interval for backing up the trends into *DataDB*. 0 To completely disable the functionality, -1 to enable synchronous backup. Anything higher than 0 will give the interval for asynchronous backups. - -stats_conns - List of connections where we will query the stats. - -scheduled_ids - Limit the TrendProfiles queried. Empty to query all the available TrendProfiles or just tenants for all the available profiles on a tenant. - -thresholds_conns - Connection IDs towards *ThresholdS* component. If not defined, there will be no notifications sent to *ThresholdS* on *Trend* changes. - -ees_conns - Connection IDs towards the *EEs* component. If left empty, no exports will be performed on *Trend* changes. - -ees_exporter_ids - Limit the exporter profiles executed on *Trend* changes. - - -TrendProfile -^^^^^^^^^^^^ - -Ís made of the following fields: - -Tenant - The tenant on the platform (one can see the tenant as partition ID). - -ID - Identifier for the *TrendProfile*, unique within a *Tenant*. - -Schedule - Cron expression scheduling gathering of the metrics. - -StatID - StatS identifier which will be queried. - -Metrics - Limit the list of metrics from the stats instance queried. - -TTL - Automatic cleanup of the queried values from inside *Trend Metrics*. - -QueueLength - Limit the size of *Trend Metrics*. Older values will be removed first. - -MinItems - Issue *TrendUpdate* events to external subsystems only if MinItems are reched to limit false alarms. - -CorrelationType - The correlation strategy to use when computing the trend. *\*average* will consider all previous query values and *\*last* only the last one. - -Tolerance - Allow a deviation of the values when computin the trend. This is defined as percentage of increase/decrease. - -Stored - Enable storing of this *Trend* for persistence. - -ThresholdIDs - Limit *TresholdProfiles* processing the *TrendUpdate* for this *TrendProfile*. - - -Trend -^^^^^ - -is made out of the following fields: - -Tenant - The tenant on the platform (one can see the tenant as partition ID). - -ID - Unique *Trend* identifier on a *Tenant* - -RunTimes - Times when the stat queries were ran by the scheduler - -Metrics - History of the queried metrics, indexed by the query time. One query stores the following values: - - ID - Metric ID on the *StatS* side - - Value - Value of the metric at the time of query - - TrendGrowth - Computed trend growth for the metric values, stored in percentage numbers. - - TrendLabel - Computed trend label for the metric values. Possible values are: *positive, *negative, *constant, N/A. - - -Use cases ---------- - -* Aggregate various traffic metrics for traffic transparency. -* Revenue assurance applications. -* Fraud detection by aggregating specific billing metrics during sensitive time intervals (\*acc, \*tcc, \*tcd). -* Building call patterns. -* Building statistical information to train systems capable of artificial intelligence. -* Building quality metrics used in traffic routing. diff --git a/docs/tutorial.rst b/docs/tutorial.rst deleted file mode 100644 index 86f113481..000000000 --- a/docs/tutorial.rst +++ /dev/null @@ -1,521 +0,0 @@ -Tutorial -======== - -.. warning:: - - **Tutorial Not Available for Version 1.0** - - This tutorial was created for a previous version of CGRateS and is not compatible with version 1.0. - -.. contents:: - :local: - :depth: 3 - -Introduction ------------- - -This tutorial provides detailed instructions for setting up a SIP Server and managing communication between the server and the CGRateS instance. - -.. note:: - - The development and testing of the instructions in this tutorial has been done on a Debian 11 (Bullseye) virtual machine. - - -Scenario Overview ------------------ - -The tutorial comprises the following steps: - -1. **SIP Server Setup**: - Select and install a SIP Server. The tutorial supports the following options: - - - FreeSWITCH_ - - Asterisk_ - - Kamailio_ - - OpenSIPS_ - -2. **CGRateS Initialization**: - Launch a CGRateS instance with the corresponding agent configured. In this context, an "agent" refers to a component within CGRateS that manages communication between CGRateS and the SIP Servers. - -3. **Account Configuration**: - Establish user accounts for different request types. - -4. **Balance Addition**: - Allocate suitable balances to the user accounts. - -5. **Call Simulation**: - Use Zoiper_ (or any other SIP UA of your choice) to register the user accounts and simulate calls between the configured accounts, and then verify the balance updates post-calls. - -6. **Fraud Detection Setup**: - Implement a fraud detection mechanism to secure and maintain the integrity of the service. - -As we progress through the tutorial, each step will be elaborated in detail. Let's embark on this journey with the SIP Server Setup. - - - -Software Installation ---------------------- - -*CGRateS* already has a section within this documentation regarding installation. It can be found :ref:`here`. - -Regarding the SIP Servers, click on the tab corresponding to the choice you made and follow the steps in order to set up: - -.. tabs:: - - .. group-tab:: FreeSWITCH - - For detailed information on installing FreeSWITCH_ on Debian, please refer to its official `installation wiki `_. - - Before installing FreeSWITCH_, you need to authenticate by creating a SignalWire Personal Access Token. To generate your personal token, follow the instructions in the `SignalWire official wiki on creating a personal token `_. - - To install FreeSWITCH_ and configure it, we have chosen the simplest method using *vanilla* packages. - - .. code-block:: bash - - TOKEN=YOURSIGNALWIRETOKEN # Insert your SignalWire Personal Access Token here - sudo apt-get update && apt-get install -y gnupg2 wget lsb-release - wget --http-user=signalwire --http-password=$TOKEN -O /usr/share/keyrings/signalwire-freeswitch-repo.gpg https://freeswitch.signalwire.com/repo/deb/debian-release/signalwire-freeswitch-repo.gpg - echo "machine freeswitch.signalwire.com login signalwire password $TOKEN" > /etc/apt/auth.conf - chmod 600 /etc/apt/auth.conf - echo "deb [signed-by=/usr/share/keyrings/signalwire-freeswitch-repo.gpg] https://freeswitch.signalwire.com/repo/deb/debian-release/ `lsb_release -sc` main" > /etc/apt/sources.list.d/freeswitch.list - echo "deb-src [signed-by=/usr/share/keyrings/signalwire-freeswitch-repo.gpg] https://freeswitch.signalwire.com/repo/deb/debian-release/ `lsb_release -sc` main" >> /etc/apt/sources.list.d/freeswitch.list - - # If /etc/freeswitch does not exist, the standard vanilla configuration is deployed - sudo apt-get update && apt-get install -y freeswitch-meta-all - - .. group-tab:: Asterisk - - To install Asterisk_, follow these steps: - - .. code-block:: bash - - # Install the necessary dependencies - sudo apt-get install -y build-essential libasound2-dev autoconf \ - openssl libssl-dev libxml2-dev \ - libncurses5-dev uuid-dev sqlite3 \ - libsqlite3-dev pkg-config libedit-dev \ - libjansson-dev - - # Download Asterisk - wget https://downloads.asterisk.org/pub/telephony/asterisk/asterisk-20-current.tar.gz -P /tmp - - # Extract the downloaded archive - sudo tar -xzvf /tmp/asterisk-20-current.tar.gz -C /usr/src - - # Change the working directory to the extracted Asterisk source - cd /usr/src/asterisk-20*/ - - # Compile and install Asterisk - sudo ./configure --with-jansson-bundled - sudo make menuselect.makeopts - sudo make - sudo make install - sudo make samples - sudo make config - sudo ldconfig - - # Create the Asterisk system user - sudo adduser --quiet --system --group --disabled-password --shell /bin/false --gecos "Asterisk" asterisk - - .. group-tab:: Kamailio - - Kamailio_ can be installed using the commands below, as documented in the `Kamailio Debian Installation Guide `_. - - .. code-block:: bash - - wget -O- http://deb.kamailio.org/kamailiodebkey.gpg | sudo apt-key add - - echo "deb http://deb.kamailio.org/kamailio57 bullseye main" > /etc/apt/sources.list.d/kamailio.list - sudo apt-get update - sudo apt-get install kamailio kamailio-extra-modules kamailio-json-modules - - .. group-tab:: OpenSIPS - - We got OpenSIPS_ installed via following commands: - - .. code-block:: bash - - curl https://apt.opensips.org/opensips-org.gpg -o /usr/share/keyrings/opensips-org.gpg - echo "deb [signed-by=/usr/share/keyrings/opensips-org.gpg] https://apt.opensips.org bookworm 3.4-releases" >/etc/apt/sources.list.d/opensips.list - echo "deb [signed-by=/usr/share/keyrings/opensips-org.gpg] https://apt.opensips.org bookworm cli-nightly" >/etc/apt/sources.list.d/opensips-cli.list - sudo apt-get update - sudo apt-get install opensips opensips-mysql-module opensips-cgrates-module opensips-cli - -Configuration and initialization --------------------------------- - -This section will be dedicated to configuring both the chosen SIP Server, as well as CGRateS and then get them running. - -Regarding the SIP Servers, we have prepared custom configurations in advance, as well as an init scripts that can be used to start the services using said configurations. It can also be used to stop/restart/check on the status of the services. Another way to do that would be to copy the configuration in the default folder, where the Server will be searching for the configuration before starting, with it usually being /etc/. - -.. tabs:: - - .. group-tab:: FreeSWITCH - - - The FreeSWITCH_ setup consists of: - - - *vanilla* configuration + "mod_json_cdr" for CDR generation; - - configurations for the following users (found in *etc/freeswitch/directory/default*): 1001-prepaid, 1002-postpaid, 1003-pseudoprepaid, 1004-rated, 1006-prepaid, 1007-rated; - - addition of CGRateS' own extensions befoure routing towards users in the dialplan (found in *etc/freeswitch/dialplan/default.xml*). - - - To start FreeSWITCH_ with the prepared custom configuration, run: - - .. code-block:: bash - - sudo /usr/share/cgrates/tutorials/fs_evsock/freeswitch/etc/init.d/freeswitch start - - To verify that FreeSWITCH_ is running, run the following command: - - .. code-block:: bash - - sudo fs_cli -x status - - - .. group-tab:: Asterisk - - - The Asterisk_ setup consists of: - - - *basic-pbx* configuration sample; - - configurations for the following users: 1001-prepaid, 1002-postpaid, 1003-pseudoprepaid, 1004-rated, 1007-rated. - - - To start Asterisk_ with the prepared custom configuration, run: - - .. code-block:: bash - - sudo /usr/share/cgrates/tutorials/asterisk_ari/asterisk/etc/init.d/asterisk start - - - To verify that Asterisk_ is running, run the following commands: - - .. code-block:: bash - - sudo asterisk -r -s /tmp/cgr_asterisk_ari/asterisk/run/asterisk.ctl - ari show status - - .. group-tab:: Kamailio - - The Kamailio_ setup consists of: - - - default configuration with small modifications to add **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; - - configurations for the following users: 1001-prepaid, 1002-postpaid, 1003-pseudoprepaid, stored using the CGRateS AttributeS subsystem. - - - To start Kamailio_ with the prepared custom configuration, run: - - .. code-block:: bash - - sudo /usr/share/cgrates/tutorials/kamevapi/kamailio/etc/init.d/kamailio start - - To verify that Kamailio_ is running, run the following command: - - .. code-block:: bash - - sudo kamctl moni - - .. group-tab:: OpenSIPS - - The OpenSIPS_ setup consists of: - - *residential* configuration; - - user accounts configuration not needed since it's enough for them to only be defined within CGRateS; - - for simplicity, no authentication was configured (WARNING: Not suitable for production). - - creating database for the DRouting module, using the following command: - - .. code-block:: bash - - opensips-cli -x database create - - After creating the database for DRouting module populate the tables with routing info: - - .. code-block:: bash - - insert into dr_gateways (gwid,type,address) values("gw2_1",0,"sip:127.0.0.1:5082"); - insert into dr_gateways (gwid,type,address) values("gw1_1",0,"sip:127.0.0.1:5081"); - insert into dr_carriers (carrierid,gwlist) values("route1","gw1_1"); - insert into dr_carriers (carrierid,gwlist) values("route2","gw2_1"); - - - To start OpenSIPS_ with the prepared custom configuration, run: - - .. code-block:: bash - - sudo mv /etc/opensips /etc/opensips.old - sudo cp -r /usr/share/cgrates/tutorials/osips/opensips/etc/opensips /etc - sudo systemctl restart opensips - - - To verify that OpenSIPS_ is running, run the following command: - - .. code-block:: bash - - opensips-cli -x mi uptime - - - Since we are using OpenSIPS_ with DRouting module we have to set up a SIP entity that OpenSIPS_ can forward the calls to for our setup. - In this example we use SIPp a free Open Source test tool / traffic generator for the SIP protocol. - The install SiPp use commands below : - - .. code-block:: bash - - apt update - apt install git pkg-config dh-autoreconf ncurses-dev build-essential libssl-dev libpcap-dev libncurses5-dev libsctp-dev lksctp-tools cmake - git clone https://github.com/SIPp/sipp.git - cd sipp - git checkout v3.7.0 - git submodule init - git submodule update - ./build.sh --common - cmake . -DUSE_SSL=1 -DUSE_SCTP=0 -DUSE_PCAP=1 -DUSE_GSL=1 - make all - make install - - - - Write SIPp XML scenario named uas.xml or to your liking with the content below,this scenario will simulate calls with OpenSIPS_ . - Change "OpenSIPS_IP" in the line ** with your OpenSIPS_ IP. - - .. code-block:: XML - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Content-Length: 0 ]]> - - - Content-Type: application/sdp Content-Length: [len] v=0 o=user1 53655765 2353687637 IN IP[local_ip_type] [local_ip] s=- c=IN IP[media_ip_type] [media_ip] t=0 0 m=audio [media_port] RTP/AVP 0 a=rtpmap:0 PCMU/8000 ]]> - - - - - Content-Length: 0 ]]> - - - - - - - - - - - - Run the SIPp with the command below: - - .. code-block:: bash - - sipp -sf uas.xml -p 5082 - - - - -**CGRateS** will be configured with the following subsystems enabled: - - - **SessionS**: started as gateway between the SIP Server and rest of CGRateS subsystems; - - **ChargerS**: used to decide the number of billing runs for customer/supplier charging; - - **AttributeS**: used to populate extra data to requests (ie: prepaid/postpaid, passwords, paypal account, LCR profile); - - **RALs**: used to calculate costs as well as account bundle management; - - **SupplierS**: selection of suppliers for each session (in case of OpenSIPS_, it will work in tandem with their DRouting module); - - **StatS**: computing statistics in real-time regarding sessions and their charging; - - **ThresholdS**: monitoring and reacting to events coming from above subsystems; - - **EEs**: exporting rated CDRs from CGR StorDB (export path: */tmp*). - -Just as with the SIP Servers, we have also prepared configurations and init scripts for CGRateS. And just as well, you can manage the CGRateS service using systemctl if you prefer. You can even start it using the cgr-engine binary, like so: - - .. code-block:: bash - - cgr-engine -config_path= -logger=*stdout - -.. note:: - The logger flag from the command above is optional, it's usually more convenient for us to check for logs in the terminal that cgrates was started in rather than checking the syslog. - - -.. tabs:: - - .. group-tab:: FreeSWITCH - - .. code-block:: bash - - sudo /usr/share/cgrates/tutorials/fs_evsock/cgrates/etc/init.d/cgrates start - - .. group-tab:: Asterisk - - .. code-block:: bash - - sudo /usr/share/cgrates/tutorials/asterisk_ari/cgrates/etc/init.d/cgrates start - - .. group-tab:: Kamailio - - .. code-block:: bash - - sudo /usr/share/cgrates/tutorials/kamevapi/cgrates/etc/init.d/cgrates start - - .. group-tab:: OpenSIPS - - .. code-block:: bash - - sudo systemctl restart opensips - -.. note:: - If you have chosen OpenSIPS_, CGRateS has to be started first since the dependency is reversed. - - -Loading **CGRateS** Tariff Plans --------------------------------- - -Now that we have **CGRateS** installed and started with one of the custom configurations, we can load the prepared data out of the shared folder, containing the following rules: - -- Create the necessary timings (always, asap, peak, offpeak). -- Configure 3 destinations (1002, 1003 and 10 used as catch all rule). -- As rating we configure the following: - - - Rate id: *RT_10CNT* with connect fee of 20cents, 10cents per minute for the first 60s in 60s increments followed by 5cents per minute in 1s increments. - - Rate id: *RT_20CNT* with connect fee of 40cents, 20cents per minute for the first 60s in 60s increments, followed by 10 cents per minute charged in 1s increments. - - Rate id: *RT_40CNT* with connect fee of 80cents, 40cents per minute for the first 60s in 60s increments, follwed by 20cents per minute charged in 10s increments. - - Rate id: *RT_1CNT* having no connect fee and a rate of 1 cent per minute, chargeable in 1 minute increments. - - Rate id: *RT_1CNT_PER_SEC* having no connect fee and a rate of 1 cent per second, chargeable in 1 second increments. - -- Accounting part will have following configured: - - - Create 3 accounts: 1001, 1002, 1003. - - 1001, 1002 will receive 10units of **\*monetary** balance. - - -.. code-block:: bash - - cgr-loader -verbose -path=/usr/share/cgrates/tariffplans/tutorial - -To verify that all actions successfully performed, we use following *cgr-console* commands: - -- Make sure all our balances were topped-up: - - .. code-block:: bash - - cgr-console 'accounts Tenant="cgrates.org" AccountIds=["1001"]' - cgr-console 'accounts Tenant="cgrates.org" AccountIds=["1002"]' - -- Query call costs so we can see our calls will have expected costs (final cost will result as sum of *ConnectFee* and *Cost* fields): - - .. code-block:: bash - - cgr-console 'cost Category="call" Tenant="cgrates.org" Subject="1001" Destination="1002" AnswerTime="2014-08-04T13:00:00Z" Usage="20s"' - cgr-console 'cost Category="call" Tenant="cgrates.org" Subject="1001" Destination="1002" AnswerTime="2014-08-04T13:00:00Z" Usage="1m25s"' - cgr-console 'cost Category="call" Tenant="cgrates.org" Subject="1001" Destination="1003" AnswerTime="2014-08-04T13:00:00Z" Usage="20s"' - - -Test calls ----------- - - -1001 -> 1002 -~~~~~~~~~~~~ - -Since the user 1001 is marked as *prepaid* inside the telecom switch, calling between 1001 and 1002 should generate pre-auth and prepaid debits which can be checked with *accounts* command integrated within *cgr-console* tool. Charging will be done based on time of day as described in the tariff plan definition above. - -.. note:: - - An important particularity to note here is the ability of **CGRateS** SessionManager to refund units booked in advance (eg: if debit occurs every 10s and rate increments are set to 1s, the SessionManager will be smart enough to refund pre-booked credits for calls stoped in the middle of debit interval). - -Check that 1001 balance is properly deducted, during the call, and moreover considering that general balance has priority over the shared one debits for this call should take place at first out of general balance. - -.. code-block:: bash - - cgr-console 'accounts Tenant="cgrates.org" AccountIds=["1001"]' - - -1002 -> 1001 -~~~~~~~~~~~~ - -The user 1002 is marked as *postpaid* inside the telecom switch hence his calls will be debited at the end of the call instead of during a call and his balance will be able to go on negative without influencing his new calls (no pre-auth). - -To check that we had debits we use again console command, this time not during the call but at the end of it: - -.. code-block:: bash - - cgr-console 'accounts Tenant="cgrates.org" AccountIds=["1002"]' - - -1001 -> 1003 -~~~~~~~~~~~~ -The user 1001 call user 1003 and after 12 seconds the call will be disconnected. - -CDR Processing --------------- - - - The SIP Server generates a CDR event at the end of each call (i.e., FreeSWITCH_ via HTTP Post and Kamailio_ via evapi) - - The event is directed towards the port configured inside cgrates.json due to the automatic handler registration built into the SessionS subsystem. - - The event reaches CGRateS through the SessionS subsystem in close to real-time. - - Once inside CGRateS, the event is instantly rated and ready for export. - - -CDR Exporting -------------- - -Once the CDRs are mediated, they are available to be exported. To export them, you first need to configure your EEs in configs (already done by the cgrates script from earlier). Important fields to populate are "id" (sample: tutorial_export), "type" (sample: *file_csv), "export_path" (sample: /tmp), and "fields" where you define all the data that you want to export. After that, you can use available RPC APIs or directly call export_cdrs from the console to export them: - -.. code-block:: bash - - cgr-console 'export_cdrs ExporterIDs=["tutorial_export"]' - -Your exported files will be appear on your defined "export_path" folder after the command is executed. In this case the folder is /tmp -For all available parameters you can check by running ``cgr-console help export_cdrs``. - -Fraud detection ---------------- - -We have configured some action triggers for our tariffplans where more than 20 units of balance topped-up triggers a notification over syslog, and most importantly, an action trigger to monitor for 100 or more units topped-up which will also trigger an account disable together with killing it's calls if prepaid debits are used. - -To verify this mechanism simply add some random units into one account's balance: - -.. code-block:: bash - - cgr-console 'balance_set Tenant="cgrates.org" Account="1003" Value=23 BalanceType="*monetary" Balance={"ID":"MonetaryBalance"}' - tail -f /var/log/syslog -n 20 - - cgr-console 'balance_set Tenant="cgrates.org" Account="1001" Value=101 BalanceType="*monetary" Balance={"ID":"MonetaryBalance"}' - tail -f /var/log/syslog -n 20 - -On the CDRs side we will be able to integrate CdrStats monitors as part of our Fraud Detection system (eg: the increase of average cost for 1001 and 1002 accounts will signal us abnormalities, hence we will be notified via syslog). - - -.. _Zoiper: https://www.zoiper.com/ -.. _Asterisk: http://www.asterisk.org/ -.. _FreeSWITCH: https://freeswitch.com/ -.. _Kamailio: https://www.kamailio.org/w/ -.. _OpenSIPS: https://opensips.org/ -.. _CGRateS: http://www.cgrates.org/